mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 06:38:09 -05:00
check
This commit is contained in:
112
README.md
Normal file
112
README.md
Normal file
@@ -0,0 +1,112 @@
|
||||

|
||||
|
||||
Monorepo for the Proof of Passport protocol.
|
||||
|
||||
Proof of Passport lets users scan the NFC chip in their government-issued passport and prove the correctness of the signature in a zk-SNARK.
|
||||
This unlocks two interesting use cases:
|
||||
- For sybil-resistance, proof of passport can provide a source of unique identity.
|
||||
- For identity and privacy, proof of passport allows selective disclosure of private data. For instance, users can disclose their nationality or their date of birth without revealing any other private information.
|
||||
|
||||
As a first application, users who can prove they indeed hold a valid passport can verify this proof on-chain to mint a Soulbound Token (SBT).
|
||||
|
||||
## Subdirectories
|
||||
|
||||
- `app`: Mobile app
|
||||
- `circuits`: Circom circuits
|
||||
- `contracts`: Solidity contracts
|
||||
|
||||
## Roadmap
|
||||
|
||||
- ✅ Basic passport verifier circuit
|
||||
- 🚧 Optimization
|
||||
- 🚧 Selective disclosure
|
||||
- ✅ Basic react native frontend
|
||||
- ✅ Passport verification pipeline, android
|
||||
- 🚧 Passport verification pipeline, iOS
|
||||
- 🚧 Reimplementation of the passport NFC specs in javascript
|
||||
- 🚧 Contracts
|
||||
- 🚧 On-chain registry of CSCA pubkeys based on the official ICAO masterlist
|
||||
|
||||
## FAQ
|
||||
|
||||
#### What exactly is being signed ?
|
||||
|
||||
The circuit looks like this:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/zk-passport/proof-of-passport/assets/62038140/593e6530-6ce1-4468-b088-b8defc512de8" width="50%" height="50%">
|
||||
</p>
|
||||
|
||||
Most of the data of interest is in the Datagroup 1, which contains the following info:
|
||||
- First name
|
||||
- Last name
|
||||
- Nationality
|
||||
- Date of birth
|
||||
- Gender
|
||||
- Expiration date of passport
|
||||
- Passport number
|
||||
|
||||
This goes through a bunch of hashes, concatenations with other data, and then is signed. By verifying the signature, we can make sure the personnal information cannot be altered.
|
||||
|
||||
#### What is the signature algorithm ?
|
||||
|
||||
Most countries use RSA with sha256 but some of them use other signature algorithms like ECDSA and other hash functions like SHA-512. You can find a summary of the signature algorithm used [here](https://github.com/zk-passport/modulus-extractooor/blob/main/signature_algorithms.json)
|
||||
|
||||
#### I just read my passport but it says my signature algorithm is not implemented. What do I do ?
|
||||
|
||||
Currently we only support the most common one `SHA256withRSA`. We will support the others shortly. Feel free to try your hand at implementing one!
|
||||
|
||||
#### What's the ICAO ?
|
||||
|
||||
The International Civil Aviation Organization (ICAO) is a specialized agency of the United Nations. Among other things, it establishes the specifications for passports, that have to be followed by all countries. The full passport specs are available [here](https://www.icao.int/publications/pages/publication.aspx?docnum=9303).
|
||||
|
||||
#### Where can I see those public keys ?
|
||||
|
||||
You can download the full list of public keys on the [ICAO website](https://download.pkd.icao.int/), in the strange `.ldif` format. The parsed list is [here](https://github.com/zk-passport/modulus-extractooor/blob/main/publicKeysParsed.json)
|
||||
|
||||
#### What can be proven ?
|
||||
|
||||
Here is all that can be proven:
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/zk-passport/proof-of-passport/assets/62038140/84ff70d2-1d82-4bee-9b57-d10c2d53f00a" width="70%" height="70%">
|
||||
</p>
|
||||
|
||||
Note that we can't access DG3 and DG4 which are optional fingerprint and iris scan without government authorization.
|
||||
|
||||
#### Even the photo is signed ?!
|
||||
|
||||
Yep. Currently we don't use it. If you have an idea of some fun zkml to do with it, let us know!
|
||||
|
||||
#### When I mint a Proof of passport SBT, what prevents someone else to frontrun my transaction ?
|
||||
|
||||
The SBT circuit includes a commitment to your address. If someone else tries to mint it, they will mint it to your address.
|
||||
|
||||
## Project Ideas
|
||||
|
||||
- Integrate Proof of Passport to Gitcoin passport or a similar system to allow better sybil resistance in quadratic funding
|
||||
- Combine with other sources of identity to provide quantified levels of uniqueness, [totem](https://github.com/0xturboblitz/totem)-style. Examples can be [anon aadhaar](https://github.com/privacy-scaling-explorations/anon-aadhaar), [Japan's my number cards](https://github.com/MynaWallet/monorepo) or [Taiwan DID](https://github.com/tw-did/tw-did/)
|
||||
|
||||
- Add Proof of Passport as a [Zupass](https://github.com/proofcarryingdata/zupass) PCD
|
||||
- Build a social network/anonymous message board for people from one specific country
|
||||
- Create a sybil-resistance tool to protect social networks against spambots
|
||||
- Do an airdrop farming protection tool
|
||||
- Gate an adult content website to a specific age
|
||||
- Passport Wallet: use [active authentication](https://en.wikipedia.org/wiki/Biometric_passport#:~:text=Active%20Authentication%20(AA),Using%20AA%20is%20optional.) to build a wallet, a multisig or a recovery module using passport signatures
|
||||
|
||||
## Licensing
|
||||
|
||||
Everything we write is MIT licensed. Circom and circomlib are GPL tho.
|
||||
|
||||
## Contributing
|
||||
|
||||
We are actively looking for contributors. Please check the [open issues](https://github.com/zk-passport/proof-of-passport/issues) if you don't know were to start!
|
||||
|
||||
## Contact us
|
||||
|
||||
Contact me @FlorentTavernier on telegram for any feedback.
|
||||
|
||||
Thanks to [Youssef](https://github.com/yssf-io), [Aayush](https://twitter.com/yush_g), [Andy](https://twitter.com/viv_boop), [Vivek](https://twitter.com/viv_boop), [Marcus](https://github.com/base0010) and [Andrew](https://github.com/AndrewCLu) for contributing ideas and helping build this technology, and to [EF PSE](https://pse.dev/) for supporting this work through grants!
|
||||
|
||||
|
||||
//check
|
||||
5
app/.babelrc
Normal file
5
app/.babelrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"plugins": [
|
||||
["module:react-native-dotenv"]
|
||||
]
|
||||
}
|
||||
2
app/.bundle/config
Normal file
2
app/.bundle/config
Normal file
@@ -0,0 +1,2 @@
|
||||
BUNDLE_PATH: "vendor/bundle"
|
||||
BUNDLE_FORCE_RUBY_PLATFORM: 1
|
||||
6
app/.env.example
Normal file
6
app/.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
# replace with your values
|
||||
DEFAULT_PNUMBER=13HF12435
|
||||
DEFAULT_DOB=060222
|
||||
DEFAULT_DOE=240312
|
||||
DEFAULT_ADDRESS=0xf4a...a2f
|
||||
LOCAL_IP=http://192.168.1.1:3000
|
||||
4
app/.eslintrc.js
Normal file
4
app/.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: '@react-native',
|
||||
};
|
||||
70
app/.gitignore
vendored
Normal file
70
app/.gitignore
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
ios/.xcode.env.local
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
build/
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
.cxx/
|
||||
*.keystore
|
||||
!debug.keystore
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/
|
||||
|
||||
**/fastlane/report.xml
|
||||
**/fastlane/Preview.html
|
||||
**/fastlane/screenshots
|
||||
**/fastlane/test_output
|
||||
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
|
||||
# Ruby / CocoaPods
|
||||
/ios/Pods/
|
||||
/vendor/bundle/
|
||||
|
||||
# Temporary files created by Metro to check the health of the file watcher
|
||||
.metro-health-check*
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
.env
|
||||
|
||||
.expo/
|
||||
7
app/.prettierrc.js
Normal file
7
app/.prettierrc.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
arrowParens: 'avoid',
|
||||
bracketSameLine: true,
|
||||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
};
|
||||
652
app/App.tsx
Normal file
652
app/App.tsx
Normal file
@@ -0,0 +1,652 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
useColorScheme,
|
||||
NativeModules,
|
||||
DeviceEventEmitter,
|
||||
TextInput,
|
||||
} from 'react-native';
|
||||
|
||||
import {
|
||||
Colors,
|
||||
DebugInstructions,
|
||||
Header,
|
||||
LearnMoreLinks,
|
||||
ReloadInstructions,
|
||||
} from 'react-native/Libraries/NewAppScreen';
|
||||
import {
|
||||
Text,
|
||||
GluestackUIProvider,
|
||||
Checkbox,
|
||||
CheckboxIndicator,
|
||||
CheckboxIcon,
|
||||
CheckIcon,
|
||||
CheckboxLabel,
|
||||
Input,
|
||||
InputField,
|
||||
ButtonText,
|
||||
ButtonIcon,
|
||||
Button,
|
||||
Spinner,
|
||||
View,
|
||||
ButtonSpinner,
|
||||
} from "@gluestack-ui/themed"
|
||||
import { config } from "@gluestack-ui/config" // Optional if you want to use default theme
|
||||
import Toast, { BaseToast, ErrorToast, SuccessToast, ToastProps } from 'react-native-toast-message';
|
||||
|
||||
// @ts-ignore
|
||||
import PassportReader from 'react-native-passport-reader';
|
||||
import {getFirstName, formatDuration, checkInputs } from './utils/utils';
|
||||
import {
|
||||
DEFAULT_PNUMBER,
|
||||
DEFAULT_DOB,
|
||||
DEFAULT_DOE,
|
||||
DEFAULT_ADDRESS,
|
||||
} from '@env';
|
||||
import {DataHash, PassportData} from '../common/src/utils/types';
|
||||
import {AWS_ENDPOINT} from '../common/src/constants/constants';
|
||||
import {
|
||||
hash,
|
||||
toUnsignedByte,
|
||||
bytesToBigDecimal,
|
||||
dataHashesObjToArray,
|
||||
formatAndConcatenateDataHashes,
|
||||
formatMrz,
|
||||
splitToWords
|
||||
} from '../common/src/utils/utils';
|
||||
import { samplePassportData } from '../common/src/utils/passportDataStatic';
|
||||
|
||||
import "@ethersproject/shims"
|
||||
import { ethers } from "ethers";
|
||||
import axios from 'axios';
|
||||
import groth16ExportSolidityCallData from './utils/snarkjs';
|
||||
import contractAddresses from "./deployments/addresses.json"
|
||||
import proofOfPassportArtefact from "./deployments/ProofOfPassport.json";
|
||||
|
||||
console.log('DEFAULT_PNUMBER', DEFAULT_PNUMBER);
|
||||
|
||||
const SKIP_SCAN = false;
|
||||
|
||||
const attributeToPosition = {
|
||||
issuing_state: [2, 5],
|
||||
name: [5, 44],
|
||||
passport_number: [44, 52],
|
||||
nationality: [54, 57],
|
||||
date_of_birth: [57, 63],
|
||||
gender: [64, 65],
|
||||
expiry_date: [65, 71],
|
||||
}
|
||||
|
||||
function App(): JSX.Element {
|
||||
const isDarkMode = useColorScheme() === 'dark';
|
||||
const [passportNumber, setPassportNumber] = useState(DEFAULT_PNUMBER ?? '');
|
||||
const [dateOfBirth, setDateOfBirth] = useState(DEFAULT_DOB ?? '');
|
||||
const [dateOfExpiry, setDateOfExpiry] = useState(DEFAULT_DOE ?? '');
|
||||
const [address, setAddress] = useState(DEFAULT_ADDRESS ?? '');
|
||||
const [passportData, setPassportData] = useState<PassportData | null>(null);
|
||||
const [step, setStep] = useState('enterDetails');
|
||||
const [testResult, setTestResult] = useState<any>(null);
|
||||
const [error, setError] = useState<any>(null);
|
||||
|
||||
const [generatingProof, setGeneratingProof] = useState<boolean>(false);
|
||||
|
||||
const [proofTime, setProofTime] = useState<number>(0);
|
||||
const [totalTime, setTotalTime] = useState<number>(0);
|
||||
const [proof, setProof] = useState<{proof: string, inputs: string} | null>(null);
|
||||
const [minting, setMinting] = useState<boolean>(false);
|
||||
const [mintText, setMintText] = useState<string | null>(null);
|
||||
|
||||
const [disclosure, setDisclosure] = useState({
|
||||
issuing_state: false,
|
||||
name: false,
|
||||
passport_number: false,
|
||||
nationality: false,
|
||||
date_of_birth: false,
|
||||
gender: false,
|
||||
expiry_date: false,
|
||||
});
|
||||
|
||||
const handleDisclosureChange = (field: keyof typeof disclosure) => {
|
||||
setDisclosure(
|
||||
{...disclosure,
|
||||
[field]: !disclosure[field]
|
||||
});
|
||||
};
|
||||
|
||||
const backgroundStyle = {
|
||||
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const logEventListener = DeviceEventEmitter.addListener('LOG_EVENT', e => {
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
return () => {
|
||||
logEventListener.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (SKIP_SCAN && passportData === null) {
|
||||
setPassportData(samplePassportData as PassportData);
|
||||
setStep('scanCompleted');
|
||||
}
|
||||
}, []);
|
||||
|
||||
async function handleResponse(response: any) {
|
||||
const {
|
||||
mrz,
|
||||
signatureAlgorithm,
|
||||
modulus,
|
||||
curveName,
|
||||
publicKeyQ,
|
||||
dataGroupHashes,
|
||||
eContent,
|
||||
encryptedDigest,
|
||||
} = response;
|
||||
|
||||
const passportData: PassportData = {
|
||||
mrz: mrz.replace(/\n/g, ''),
|
||||
signatureAlgorithm: signatureAlgorithm,
|
||||
pubKey: {
|
||||
modulus: modulus,
|
||||
curveName: curveName,
|
||||
publicKeyQ: publicKeyQ,
|
||||
},
|
||||
dataGroupHashes: dataHashesObjToArray(JSON.parse(dataGroupHashes)),
|
||||
eContent: JSON.parse(eContent),
|
||||
encryptedDigest: JSON.parse(encryptedDigest),
|
||||
};
|
||||
|
||||
console.log('mrz', passportData.mrz);
|
||||
console.log('signatureAlgorithm', passportData.signatureAlgorithm);
|
||||
console.log('pubKey', passportData.pubKey);
|
||||
console.log('dataGroupHashes', passportData.dataGroupHashes);
|
||||
console.log('eContent', passportData.eContent);
|
||||
console.log('encryptedDigest', passportData.encryptedDigest);
|
||||
|
||||
setPassportData(passportData);
|
||||
setStep('scanCompleted');
|
||||
}
|
||||
|
||||
async function scan() {
|
||||
const check = checkInputs(passportNumber, dateOfBirth, dateOfExpiry)
|
||||
if (!check.success) {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1: check.message,
|
||||
})
|
||||
return
|
||||
}
|
||||
// 1. start a scan
|
||||
// 2. press the back of your android phone against the passport
|
||||
// 3. wait for the scan(...) Promise to get resolved/rejected
|
||||
console.log('scanning...');
|
||||
setStep('scanning');
|
||||
try {
|
||||
const response = await PassportReader.scan({
|
||||
documentNumber: passportNumber,
|
||||
dateOfBirth: dateOfBirth,
|
||||
dateOfExpiry: dateOfExpiry,
|
||||
});
|
||||
console.log('response', response);
|
||||
console.log('scanned');
|
||||
handleResponse(response);
|
||||
} catch (e: any) {
|
||||
console.log('error during scan :', e);
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1: e.message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleProve = async () => {
|
||||
if (passportData === null) {
|
||||
console.log('passport data is null');
|
||||
return;
|
||||
}
|
||||
|
||||
setGeneratingProof(true)
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
// 1. TODO check signature to make sure the proof will work
|
||||
|
||||
// 2. Format all the data as inputs for the circuit
|
||||
const formattedMrz = formatMrz(passportData.mrz);
|
||||
const mrzHash = hash(formatMrz(passportData.mrz));
|
||||
const concatenatedDataHashes = formatAndConcatenateDataHashes(
|
||||
mrzHash,
|
||||
passportData.dataGroupHashes as DataHash[],
|
||||
);
|
||||
|
||||
|
||||
const reveal_bitmap = Array.from({ length: 88 }, (_) => '0');
|
||||
|
||||
for(const attribute in disclosure) {
|
||||
if (disclosure[attribute as keyof typeof disclosure]) {
|
||||
const [start, end] = attributeToPosition[attribute as keyof typeof attributeToPosition];
|
||||
for(let i = start; i <= end; i++) {
|
||||
reveal_bitmap[i] = '1';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (passportData.signatureAlgorithm !== "SHA256withRSA") {
|
||||
console.log(`${passportData.signatureAlgorithm} not supported for proof right now.`);
|
||||
setError(`${passportData.signatureAlgorithm} not supported for proof right now.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const inputs = {
|
||||
mrz: Array.from(formattedMrz).map(byte => String(byte)),
|
||||
reveal_bitmap: reveal_bitmap.map(byte => String(byte)),
|
||||
dataHashes: Array.from(concatenatedDataHashes.map(toUnsignedByte)).map(byte => String(byte)),
|
||||
eContentBytes: Array.from(passportData.eContent.map(toUnsignedByte)).map(byte => String(byte)),
|
||||
signature: splitToWords(
|
||||
BigInt(bytesToBigDecimal(passportData.encryptedDigest)),
|
||||
BigInt(64),
|
||||
BigInt(32)
|
||||
),
|
||||
pubkey: splitToWords(
|
||||
BigInt(passportData.pubKey.modulus as string),
|
||||
BigInt(64),
|
||||
BigInt(32)
|
||||
),
|
||||
address,
|
||||
}
|
||||
|
||||
// 3. Generate a proof of passport
|
||||
const start = Date.now();
|
||||
NativeModules.RNPassportReader.provePassport(inputs, (err: any, res: any) => {
|
||||
const end = Date.now();
|
||||
|
||||
if (err) {
|
||||
console.error(err);
|
||||
setError(
|
||||
"err: " + err.toString(),
|
||||
);
|
||||
return
|
||||
}
|
||||
console.log("res", res);
|
||||
const parsedResponse = JSON.parse(res);
|
||||
console.log('parsedResponse', parsedResponse);
|
||||
console.log('parsedResponse.duration', parsedResponse.duration);
|
||||
|
||||
const deserializedProof = JSON.parse(parsedResponse.serialized_proof);
|
||||
console.log('deserializedProof', deserializedProof);
|
||||
|
||||
const deserializedInputs = JSON.parse(parsedResponse.serialized_inputs);
|
||||
console.log('deserializedInputs', deserializedInputs);
|
||||
|
||||
setProofTime(parsedResponse.duration);
|
||||
setTotalTime(end - start);
|
||||
|
||||
setProof({
|
||||
proof: JSON.stringify(deserializedProof),
|
||||
inputs: JSON.stringify(deserializedInputs),
|
||||
});
|
||||
setGeneratingProof(false)
|
||||
setStep('proofGenerated');
|
||||
});
|
||||
};
|
||||
|
||||
const handleMint = async () => {
|
||||
setMinting(true)
|
||||
if (!proof?.proof || !proof?.inputs) {
|
||||
console.log('proof or inputs is null');
|
||||
return;
|
||||
}
|
||||
if (!contractAddresses.ProofOfPassport || !proofOfPassportArtefact.abi) {
|
||||
console.log('contracts addresses or abi not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the proof and publicInputs as calldata for the verifier contract
|
||||
const p = JSON.parse(proof.proof);
|
||||
const i = JSON.parse(proof.inputs);
|
||||
// const p = {"a": ["16502577771187684977980616374304236605057905196561863637384296592370445017998", "3901861368174142739149849352179287633574688417834634300291202761562972709023"], "b": [["14543689684654938043989715590415160645004827219804187355799512446208262437248", "2758656853017552407340621959452084149765188239766723663849017782705599048610"], ["11277365272183899064677884160333958573750879878546952615484891009952508146334", "6233152645613613236466445508816847016425532566954931368157994995587995754446"]], "c": ["6117026818273543012196632774531089444191538074414171872462281003025766583671", "10261526153619394223629018490329697233150978685332753612996629076672112420472"]}
|
||||
// const i = ["0", "0", "0", "146183216590389235917737925524385821154", "43653084046336027166990", "21085389953176386480267", "56519161086598100699293", "15779090386165698845937", "23690430366843652392111", "22932463418406768540896", "51019038683800409078189", "50360649287615093470666", "47789371969706091489401", "15311247864741754764238", "20579290199534174842880", "1318168358802144844680228651107716082931624381008"]
|
||||
console.log('p', p);
|
||||
console.log('i', i);
|
||||
const cd = groth16ExportSolidityCallData(p, i);
|
||||
const callData = JSON.parse(`[${cd}]`);
|
||||
console.log('callData', callData);
|
||||
|
||||
// format transaction
|
||||
// for now, we do it all on mumbai
|
||||
try {
|
||||
const provider = new ethers.JsonRpcProvider('https://polygon-mumbai-bor.publicnode.com');
|
||||
const proofOfPassportOnMumbai = new ethers.Contract(contractAddresses.ProofOfPassport, proofOfPassportArtefact.abi, provider);
|
||||
|
||||
const transactionRequest = await proofOfPassportOnMumbai
|
||||
.mint.populateTransaction(...callData);
|
||||
console.log('transactionRequest', transactionRequest);
|
||||
|
||||
const response = await axios.post(AWS_ENDPOINT, {
|
||||
chain: "mumbai",
|
||||
tx_data: transactionRequest
|
||||
});
|
||||
console.log('response status', response.status)
|
||||
console.log('response data', response.data)
|
||||
setMintText(`Network: Mumbai. Transaction hash: ${response.data.hash}`)
|
||||
const receipt = await provider.waitForTransaction(response.data.hash);
|
||||
console.log('receipt', receipt)
|
||||
if (receipt?.status === 1) {
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
text1: 'Proof of passport minted',
|
||||
})
|
||||
setMintText(`SBT minted. Network: Mumbai. Transaction hash: ${response.data.hash}`)
|
||||
} else {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1: 'Proof of passport minting failed',
|
||||
})
|
||||
setMintText(`Error minting SBT. Network: Mumbai. Transaction hash: ${response.data.hash}`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GluestackUIProvider config={config}>
|
||||
<SafeAreaView style={backgroundStyle}>
|
||||
<StatusBar
|
||||
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
||||
backgroundColor={backgroundStyle.backgroundColor}
|
||||
/>
|
||||
<ScrollView
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
style={{
|
||||
backgroundColor: isDarkMode ? Colors.black : Colors.white,
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
{step === 'enterDetails' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>Welcome to Proof of Passport</Text>
|
||||
<Text style={{textAlign: "center", fontSize: 20, marginTop: 20, marginBottom: 20}}>Enter Your Passport Details</Text>
|
||||
<Text>Passport Number</Text>
|
||||
<Input
|
||||
variant="outline"
|
||||
size="md"
|
||||
marginBottom={10}
|
||||
marginTop={4}
|
||||
>
|
||||
<InputField
|
||||
value={passportNumber}
|
||||
onChangeText={setPassportNumber}
|
||||
placeholder={"Passport Number"}
|
||||
/>
|
||||
</Input>
|
||||
<Text>Date of Birth</Text>
|
||||
<Input
|
||||
variant="outline"
|
||||
size="md"
|
||||
marginBottom={10}
|
||||
marginTop={4}
|
||||
>
|
||||
<InputField
|
||||
value={dateOfBirth}
|
||||
onChangeText={setDateOfBirth}
|
||||
placeholder={"YYMMDD"}
|
||||
/>
|
||||
</Input>
|
||||
<Text>Date of Expiry</Text>
|
||||
<Input
|
||||
variant="outline"
|
||||
size="md"
|
||||
marginBottom={10}
|
||||
marginTop={4}
|
||||
>
|
||||
<InputField
|
||||
value={dateOfExpiry}
|
||||
onChangeText={setDateOfExpiry}
|
||||
placeholder={"YYMMDD"}
|
||||
/>
|
||||
</Input>
|
||||
|
||||
<Button
|
||||
onPress={scan}
|
||||
marginTop={10}
|
||||
>
|
||||
<ButtonText>Scan Passport with NFC</ButtonText>
|
||||
{/* <ButtonIcon as={AddIcon} /> */}
|
||||
</Button>
|
||||
</View>
|
||||
) : null}
|
||||
{step === 'scanning' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>Put your phone on your passport</Text>
|
||||
<Spinner
|
||||
size={60}
|
||||
style={{marginTop: 70}}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
{step === 'scanCompleted' && passportData ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>
|
||||
Hi {getFirstName(passportData.mrz)}
|
||||
</Text>
|
||||
<View
|
||||
marginTop={20}
|
||||
marginBottom={20}
|
||||
>
|
||||
<Text
|
||||
marginBottom={5}
|
||||
>
|
||||
Signature algorithm: {passportData.signatureAlgorithm}
|
||||
</Text>
|
||||
<Text
|
||||
marginBottom={10}
|
||||
>
|
||||
What do you want to disclose ?
|
||||
</Text>
|
||||
{Object.keys(disclosure).map((key) => {
|
||||
const keyy = key as keyof typeof disclosure;
|
||||
const indexes = attributeToPosition[keyy];
|
||||
const keyFormatted = keyy.replace(/_/g, ' ').split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
||||
const mrzAttribute = passportData.mrz.slice(indexes[0], indexes[1])
|
||||
const mrzAttributeFormatted = mrzAttribute.replace(/</g, ' ')
|
||||
|
||||
return (
|
||||
<View key={key} margin={2} width={"$full"} flexDirection="row" justifyContent="space-between">
|
||||
<View maxWidth={"$5/6"}>
|
||||
<Text
|
||||
style={{fontWeight: "bold"}}
|
||||
>
|
||||
{keyFormatted}:{" "}
|
||||
</Text>
|
||||
<Text>
|
||||
{mrzAttributeFormatted}
|
||||
</Text>
|
||||
</View>
|
||||
<Checkbox
|
||||
value={key}
|
||||
isChecked={disclosure[keyy]}
|
||||
onChange={() => handleDisclosureChange(keyy)}
|
||||
size="lg"
|
||||
aria-label={key}
|
||||
>
|
||||
<CheckboxIndicator mr="$2">
|
||||
<CheckboxIcon as={CheckIcon} />
|
||||
</CheckboxIndicator>
|
||||
</Checkbox>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
<Text>Enter your address or ens</Text>
|
||||
<Input
|
||||
variant="outline"
|
||||
size="md"
|
||||
marginBottom={10}
|
||||
marginTop={4}
|
||||
>
|
||||
<InputField
|
||||
value={address}
|
||||
onChangeText={setAddress}
|
||||
placeholder="Your Address or ens name"
|
||||
/>
|
||||
</Input>
|
||||
|
||||
{generatingProof ?
|
||||
<Button
|
||||
onPress={handleProve}
|
||||
>
|
||||
<ButtonSpinner mr="$1" />
|
||||
<ButtonText>Generating zk proof</ButtonText>
|
||||
</Button>
|
||||
: <Button
|
||||
onPress={handleProve}
|
||||
>
|
||||
<ButtonText>Generate zk proof</ButtonText>
|
||||
</Button>
|
||||
}
|
||||
</View>
|
||||
) : null}
|
||||
{step === 'proofGenerated' ? (
|
||||
<View style={styles.sectionContainer}>
|
||||
<Text style={styles.header}>Zero-knowledge proof generated</Text>
|
||||
|
||||
<Text style={{fontWeight: "bold"}}>
|
||||
Proof:
|
||||
</Text>
|
||||
<Text>
|
||||
{JSON.stringify(proof)}
|
||||
</Text>
|
||||
|
||||
<Text>
|
||||
<Text style={{ fontWeight: 'bold' }}>Proof Duration:</Text> {formatDuration(proofTime)}
|
||||
</Text>
|
||||
<Text>
|
||||
<Text style={{ fontWeight: 'bold' }}>Total Duration:</Text> {formatDuration(totalTime)}
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
onPress={handleMint}
|
||||
marginTop={10}
|
||||
>
|
||||
<ButtonText>Mint Proof of Passport</ButtonText>
|
||||
</Button>
|
||||
{mintText && <Text>
|
||||
{mintText}
|
||||
</Text>}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
<View style={{...styles.sectionContainer, ...styles.testSection, marginTop: 80}}>
|
||||
<Text style={{...styles.sectionDescription, textAlign: "center"}}>Test functions</Text>
|
||||
|
||||
<Button
|
||||
onPress={async () => {
|
||||
NativeModules.RNPassportReader.callRustLib((err: any, res: any) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
setTestResult(err);
|
||||
} else {
|
||||
console.log(res); // Should log "5"
|
||||
setTestResult(res);
|
||||
}
|
||||
});
|
||||
}}
|
||||
marginTop={10}
|
||||
>
|
||||
<ButtonText>Call arkworks lib</ButtonText>
|
||||
</Button>
|
||||
{testResult && <Text>{testResult}</Text>}
|
||||
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
<Toast config={toastConfig} />
|
||||
</GluestackUIProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
sectionContainer: {
|
||||
marginTop: 32,
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: '600',
|
||||
},
|
||||
sectionDescription: {
|
||||
marginTop: 8,
|
||||
fontSize: 18,
|
||||
fontWeight: '400',
|
||||
},
|
||||
highlight: {
|
||||
fontWeight: '700',
|
||||
},
|
||||
header: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
marginTop: 20,
|
||||
},
|
||||
testSection: {
|
||||
backgroundColor: '#f2f2f2', // different background color
|
||||
padding: 10,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#dcdcdc', // adding a border top with a light color
|
||||
marginTop: 15,
|
||||
},
|
||||
});
|
||||
|
||||
export default App;
|
||||
|
||||
|
||||
export const toastConfig = {
|
||||
info: (props: ToastProps) => (
|
||||
<BaseToast
|
||||
{...props}
|
||||
contentContainerStyle={{ paddingHorizontal: 15 }}
|
||||
text1Style={{
|
||||
fontSize: 15,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
text2Style={{
|
||||
fontSize: 15,
|
||||
fontWeight: "500",
|
||||
}}
|
||||
/>
|
||||
),
|
||||
error: (props: ToastProps) => (
|
||||
<ErrorToast
|
||||
{...props}
|
||||
contentContainerStyle={{ paddingHorizontal: 15}}
|
||||
text1Style={{
|
||||
fontSize: 15,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
text2Style={{
|
||||
fontSize: 15,
|
||||
fontWeight: "400",
|
||||
}}
|
||||
/>
|
||||
),
|
||||
success: (props: ToastProps) => (
|
||||
<SuccessToast
|
||||
{...props}
|
||||
contentContainerStyle={{ paddingHorizontal: 15 }}
|
||||
text1Style={{
|
||||
fontSize: 15,
|
||||
fontWeight: "600",
|
||||
}}
|
||||
text2Style={{
|
||||
fontSize: 15,
|
||||
fontWeight: "400",
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
6
app/Gemfile
Normal file
6
app/Gemfile
Normal file
@@ -0,0 +1,6 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
||||
ruby ">= 2.6.10"
|
||||
|
||||
gem 'cocoapods', '~> 1.12'
|
||||
100
app/Gemfile.lock
Normal file
100
app/Gemfile.lock
Normal file
@@ -0,0 +1,100 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
activesupport (6.1.7.4)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
atomos (0.1.3)
|
||||
claide (1.1.0)
|
||||
cocoapods (1.12.1)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.12.1)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.6.0, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.6.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 2.3.0, < 3.0)
|
||||
xcodeproj (>= 1.21.0, < 2.0)
|
||||
cocoapods-core (1.12.1)
|
||||
activesupport (>= 5.0, < 8)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (1.6.3)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored2 (3.1.2)
|
||||
concurrent-ruby (1.2.2)
|
||||
escape (0.0.4)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
ffi (1.15.5)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.6.3)
|
||||
minitest (5.18.1)
|
||||
molinillo (0.8.0)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
netrc (0.11.0)
|
||||
public_suffix (4.0.7)
|
||||
rexml (3.2.5)
|
||||
ruby-macho (2.5.1)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
xcodeproj (1.22.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
zeitwerk (2.6.8)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods (~> 1.12)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.6.10p210
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.2
|
||||
54
app/README.md
Normal file
54
app/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Proof of Passport App
|
||||
|
||||
Only Android right now, under heavy development
|
||||
|
||||
#### Requirements
|
||||
|
||||
Install `nodejs v18`
|
||||
|
||||
#### Installation
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
In `/common`, also run:
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
#### Add circuit build
|
||||
|
||||
Go to the `circuit` folder of the monorepo and build the circuit.
|
||||
|
||||
#### Build native lib
|
||||
|
||||
In `/script`, run:
|
||||
```
|
||||
./build_rust.sh
|
||||
```
|
||||
This will build the `libhalo2_circom_passport.so` lib and copy it to the desired place to be used by the app.
|
||||
The config used is in `android/react-native-passport-reader/android/build.gradle`.
|
||||
You can go there to change the profile (`debug` or `release`)
|
||||
|
||||
You might need to set the rust-toolchain rust version as global default. Example:
|
||||
```
|
||||
rustup default 1.67.0
|
||||
```
|
||||
And install the targets like this:
|
||||
```
|
||||
rustup target add aarch64-linux-android
|
||||
```
|
||||
|
||||
To run the server, first connect your phone to your computer, allow access, then:
|
||||
```
|
||||
yarn start
|
||||
```
|
||||
Then press `a` for android or `i` for iOS
|
||||
|
||||
To export an apk:
|
||||
```
|
||||
cd android
|
||||
./gradlew assembleRelease
|
||||
```
|
||||
The built apk it located at `android/app/build/outputs/apk/release/app-release.apk`
|
||||
17
app/__tests__/App.test.tsx
Normal file
17
app/__tests__/App.test.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
import 'react-native';
|
||||
import React from 'react';
|
||||
import App from '../App';
|
||||
|
||||
// Note: import explicitly to use the types shiped with jest.
|
||||
import {it} from '@jest/globals';
|
||||
|
||||
// Note: test renderer must be required after react-native.
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
it('renders correctly', () => {
|
||||
renderer.create(<App />);
|
||||
});
|
||||
131
app/android/app/build.gradle
Normal file
131
app/android/app/build.gradle
Normal file
@@ -0,0 +1,131 @@
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "com.facebook.react"
|
||||
|
||||
/**
|
||||
* This is the configuration block to customize your React Native Android app.
|
||||
* By default you don't need to apply any configuration, just uncomment the lines you need.
|
||||
*/
|
||||
react {
|
||||
/* Folders */
|
||||
// The root of your project, i.e. where "package.json" lives. Default is '..'
|
||||
// root = file("../")
|
||||
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
|
||||
// reactNativeDir = file("../node_modules/react-native")
|
||||
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
|
||||
// codegenDir = file("../node_modules/@react-native/codegen")
|
||||
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
|
||||
// cliFile = file("../node_modules/react-native/cli.js")
|
||||
|
||||
/* Variants */
|
||||
// The list of variants to that are debuggable. For those we're going to
|
||||
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
|
||||
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
|
||||
// debuggableVariants = ["liteDebug", "prodDebug"]
|
||||
|
||||
/* Bundling */
|
||||
// A list containing the node command and its flags. Default is just 'node'.
|
||||
// nodeExecutableAndArgs = ["node"]
|
||||
//
|
||||
// The command to run when bundling. By default is 'bundle'
|
||||
// bundleCommand = "ram-bundle"
|
||||
//
|
||||
// The path to the CLI configuration file. Default is empty.
|
||||
// bundleConfig = file(../rn-cli.config.js)
|
||||
//
|
||||
// The name of the generated asset file containing your JS bundle
|
||||
// bundleAssetName = "MyApplication.android.bundle"
|
||||
//
|
||||
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
|
||||
// entryFile = file("../js/MyApplication.android.js")
|
||||
//
|
||||
// A list of extra flags to pass to the 'bundle' commands.
|
||||
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
|
||||
// extraPackagerArgs = []
|
||||
|
||||
/* Hermes Commands */
|
||||
// The hermes compiler command to run. By default it is 'hermesc'
|
||||
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
|
||||
//
|
||||
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
|
||||
// hermesFlags = ["-O", "-output-source-map"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore (JSC)
|
||||
*
|
||||
* For example, to use the international variant, you can use:
|
||||
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
|
||||
*
|
||||
* The international variant includes ICU i18n library and necessary data
|
||||
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
def jscFlavor = 'org.webkit:android-jsc:+'
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
namespace "com.proofofpassport"
|
||||
defaultConfig {
|
||||
applicationId "com.proofofpassport"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
// see https://reactnative.dev/docs/signed-apk-android.
|
||||
signingConfig signingConfigs.debug
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// The version of react-native is set by the React Native Gradle Plugin
|
||||
implementation("com.facebook.react:react-android")
|
||||
implementation project(':react-native-passport-reader')
|
||||
// implementation files('libs/jmrtd-0.5.5.jar')
|
||||
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.squareup.okhttp3', module:'okhttp'
|
||||
}
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
|
||||
if (hermesEnabled.toBoolean()) {
|
||||
implementation("com.facebook.react:hermes-android")
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
BIN
app/android/app/debug.keystore
Normal file
BIN
app/android/app/debug.keystore
Normal file
Binary file not shown.
10
app/android/app/proguard-rules.pro
vendored
Normal file
10
app/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
16
app/android/app/src/debug/AndroidManifest.xml
Normal file
16
app/android/app/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
||||
|
||||
<application
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="28"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.proofofpassport;
|
||||
|
||||
import android.content.Context;
|
||||
import com.facebook.flipper.android.AndroidFlipperClient;
|
||||
import com.facebook.flipper.android.utils.FlipperUtils;
|
||||
import com.facebook.flipper.core.FlipperClient;
|
||||
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
|
||||
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
|
||||
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
|
||||
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
|
||||
import com.facebook.react.ReactInstanceEventListener;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* Class responsible of loading Flipper inside your React Native application. This is the debug
|
||||
* flavor of it. Here you can add your own plugins and customize the Flipper setup.
|
||||
*/
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
if (FlipperUtils.shouldEnableFlipper(context)) {
|
||||
final FlipperClient client = AndroidFlipperClient.getInstance(context);
|
||||
|
||||
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
|
||||
client.addPlugin(new DatabasesFlipperPlugin(context));
|
||||
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
|
||||
client.addPlugin(CrashReporterPlugin.getInstance());
|
||||
|
||||
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
|
||||
NetworkingModule.setCustomClientBuilder(
|
||||
new NetworkingModule.CustomClientBuilder() {
|
||||
@Override
|
||||
public void apply(OkHttpClient.Builder builder) {
|
||||
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
|
||||
}
|
||||
});
|
||||
client.addPlugin(networkFlipperPlugin);
|
||||
client.start();
|
||||
|
||||
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
|
||||
// Hence we run if after all native modules have been initialized
|
||||
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
|
||||
if (reactContext == null) {
|
||||
reactInstanceManager.addReactInstanceEventListener(
|
||||
new ReactInstanceEventListener() {
|
||||
@Override
|
||||
public void onReactContextInitialized(ReactContext reactContext) {
|
||||
reactInstanceManager.removeReactInstanceEventListener(this);
|
||||
reactContext.runOnNativeModulesQueueThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
client.addPlugin(new FrescoFlipperPlugin());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
client.addPlugin(new FrescoFlipperPlugin());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
app/android/app/src/main/AndroidManifest.xml
Normal file
36
app/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/passport_logo"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
|
||||
<action android:name="android.nfc.action.TAG_DISCOVERED"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.nfc.action.TECH_DISCOVERED"
|
||||
android:resource="@xml/nfc_tech_filter" />
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
BIN
app/android/app/src/main/assets/masterList
Normal file
BIN
app/android/app/src/main/assets/masterList
Normal file
Binary file not shown.
@@ -0,0 +1,55 @@
|
||||
package com.proofofpassport;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
|
||||
import com.facebook.react.defaults.DefaultReactActivityDelegate;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import io.tradle.nfc.RNPassportReaderModule;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript. This is used to schedule
|
||||
* rendering of the component.
|
||||
*/
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "Proof of Passport";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
|
||||
* DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
|
||||
* (aka React 18) with two boolean flags.
|
||||
*/
|
||||
@Override
|
||||
protected ReactActivityDelegate createReactActivityDelegate() {
|
||||
return new DefaultReactActivityDelegate(
|
||||
this,
|
||||
getMainComponentName(),
|
||||
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
|
||||
DefaultNewArchitectureEntryPoint.getFabricEnabled()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
Log.d("MAIN_ACTIVITY", "onNewIntent: " + intent.getAction());
|
||||
RNPassportReaderModule.Companion.getInstance().receiveIntent(intent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.proofofpassport;
|
||||
|
||||
import android.app.Application;
|
||||
import com.facebook.react.PackageList;
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import io.tradle.nfc.RNPassportReaderPackage;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
private final ReactNativeHost mReactNativeHost = new DefaultReactNativeHost(
|
||||
this
|
||||
) {
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
List<ReactPackage> packages = new PackageList(this).getPackages();
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// packages.add(new RNPassportReaderPackage());
|
||||
return packages;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getJSMainModuleName() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNewArchEnabled() {
|
||||
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean isHermesEnabled() {
|
||||
return BuildConfig.IS_HERMES_ENABLED;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SoLoader.init(this, /* native exopackage */false);
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
DefaultNewArchitectureEntryPoint.load();
|
||||
}
|
||||
ReactNativeFlipper.initializeFlipper(
|
||||
this,
|
||||
getReactNativeHost().getReactInstanceManager()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
|
||||
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
|
||||
android:insetTop="@dimen/abc_edit_text_inset_top_material"
|
||||
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
|
||||
|
||||
<selector>
|
||||
<!--
|
||||
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
|
||||
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
|
||||
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
|
||||
|
||||
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||
|
||||
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
|
||||
-->
|
||||
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
|
||||
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
|
||||
</selector>
|
||||
|
||||
</inset>
|
||||
BIN
app/android/app/src/main/res/mipmap-hdpi/passport_logo.png
Normal file
BIN
app/android/app/src/main/res/mipmap-hdpi/passport_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
BIN
app/android/app/src/main/res/mipmap-mdpi/passport_logo.png
Normal file
BIN
app/android/app/src/main/res/mipmap-mdpi/passport_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/android/app/src/main/res/mipmap-xhdpi/passport_logo.png
Normal file
BIN
app/android/app/src/main/res/mipmap-xhdpi/passport_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
app/android/app/src/main/res/mipmap-xxhdpi/passport_logo.png
Normal file
BIN
app/android/app/src/main/res/mipmap-xxhdpi/passport_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
app/android/app/src/main/res/mipmap-xxxhdpi/passport_logo.png
Normal file
BIN
app/android/app/src/main/res/mipmap-xxxhdpi/passport_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
3
app/android/app/src/main/res/values/strings.xml
Normal file
3
app/android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Proof of Passport</string>
|
||||
</resources>
|
||||
9
app/android/app/src/main/res/values/styles.xml
Normal file
9
app/android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
5
app/android/app/src/main/res/xml/nfc_tech_filter.xml
Normal file
5
app/android/app/src/main/res/xml/nfc_tech_filter.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<tech-list>
|
||||
<tech>android.nfc.tech.IsoDep</tech>
|
||||
</tech-list>
|
||||
</resources>
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.proofofpassport;
|
||||
|
||||
import android.content.Context;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
||||
/**
|
||||
* Class responsible of loading Flipper inside your React Native application. This is the release
|
||||
* flavor of it so it's empty as we don't want to load Flipper.
|
||||
*/
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
// Do nothing as we don't want to initialize Flipper on Release.
|
||||
}
|
||||
}
|
||||
38
app/android/build.gradle
Normal file
38
app/android/build.gradle
Normal file
@@ -0,0 +1,38 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "33.0.0"
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 33
|
||||
targetSdkVersion = 33
|
||||
|
||||
kotlin_version = '1.7.20'
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "23.1.7779620"
|
||||
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
// classpath("com.android.tools.build:gradle")
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||
classpath 'org.mozilla.rust-android-gradle:plugin:0.9.3'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
44
app/android/gradle.properties
Normal file
44
app/android/gradle.properties
Normal file
@@ -0,0 +1,44 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.182.0
|
||||
|
||||
# Use this property to specify which architecture you want to build.
|
||||
# You can also override it from the CLI using
|
||||
# ./gradlew <task> -PreactNativeArchitectures=x86_64
|
||||
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
|
||||
|
||||
# Use this property to enable support to the new architecture.
|
||||
# This will allow you to use TurboModules and the Fabric render in
|
||||
# your application. You should enable this flag either if you want
|
||||
# to write custom TurboModules/Fabric components OR use libraries that
|
||||
# are providing them.
|
||||
newArchEnabled=false
|
||||
|
||||
# Use this property to enable or disable the Hermes JS engine.
|
||||
# If set to false, you will be using JSC instead.
|
||||
hermesEnabled=true
|
||||
BIN
app/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
app/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
app/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
app/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
244
app/android/gradlew
vendored
Executable file
244
app/android/gradlew
vendored
Executable file
@@ -0,0 +1,244 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
92
app/android/gradlew.bat
vendored
Normal file
92
app/android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
2
app/android/react-native-passport-reader/.gitignore
vendored
Normal file
2
app/android/react-native-passport-reader/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
android/src/main/jniLibs/arm64/libark_circom_passport.so
|
||||
15
app/android/react-native-passport-reader/ORIGINAL_LICENSE.md
Normal file
15
app/android/react-native-passport-reader/ORIGINAL_LICENSE.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## License
|
||||
|
||||
Apache License, Version 2.0
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
61
app/android/react-native-passport-reader/README.md
Normal file
61
app/android/react-native-passport-reader/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
# react-native-passport-reader
|
||||
|
||||
Adapted from [passport-reader](https://github.com/tananaev/passport-reader). Individual modifications are too many to enumerate, but essentially: the workflow code was adapted to the needs of a React Native module, and the scanning code was largely left as is.
|
||||
|
||||
## Getting started
|
||||
|
||||
```sh
|
||||
$ npm install react-native-passport-reader --save
|
||||
$ react-native link react-native-passport-reader
|
||||
```
|
||||
|
||||
In your `android/app/build.gradle` add `packagingOptions`:
|
||||
|
||||
```
|
||||
android {
|
||||
...
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In `AndroidManifest.xml` add:
|
||||
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="false" />
|
||||
```
|
||||
|
||||
If your app will not function without nfc capabilities, set `android:required` above to `true`
|
||||
|
||||
## Usage
|
||||
```js
|
||||
import PassportReader from 'react-native-passport-reader'
|
||||
// { scan, cancel, isSupported }
|
||||
|
||||
async function scan () {
|
||||
// 1. start a scan
|
||||
// 2. press the back of your android phone against the passport
|
||||
// 3. wait for the scan(...) Promise to get resolved/rejected
|
||||
|
||||
const {
|
||||
firstName,
|
||||
lastName,
|
||||
gender,
|
||||
issuer,
|
||||
nationality,
|
||||
photo
|
||||
} = await PassportReader.scan({
|
||||
// yes, you need to know a bunch of data up front
|
||||
// this is data you can get from reading the MRZ zone of the passport
|
||||
documentNumber: 'ofDocumentBeingScanned',
|
||||
dateOfBirth: 'yyMMdd',
|
||||
dateOfExpiry: 'yyMMdd'
|
||||
})
|
||||
|
||||
const { base64, width, height } = photo
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
defaultConfig {
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
multiDexEnabled = true
|
||||
ndkVersion = "23.1.7779620"
|
||||
|
||||
}
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
warning 'InvalidPackage'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'com.google.code.gson:gson:2.8.9' // Check for the latest version
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'com.google.android.material:material:1.7.0'
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'com.wdullaer:materialdatetimepicker:3.5.2'
|
||||
implementation 'org.jmrtd:jmrtd:0.7.18' // getting different errors whether it's 18 or 40 // 23 <== this works
|
||||
// implementation files('../../../../../jmrtd-0.7.18-sources/target/jmrtd-0.7.18.jar')
|
||||
// implementation files('../../app/libs/jmrtd-0.7.18.jar')
|
||||
implementation 'net.sf.scuba:scuba-sc-android:0.0.18'
|
||||
implementation 'com.madgag.spongycastle:prov:1.54.0.0'
|
||||
implementation 'com.gemalto.jp2:jp2-android:1.0.3'
|
||||
implementation 'com.github.mhshams:jnbis:1.1.0'
|
||||
// implementation 'org.bouncycastle:bcpkix-jdk15on:1.65' // do not update
|
||||
implementation 'commons-io:commons-io:2.8.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
|
||||
implementation 'com.android.support:appcompat-v7:25.1.0'
|
||||
implementation 'edu.ucar:jj2000:5.2'
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
}
|
||||
|
||||
|
||||
apply plugin: 'org.mozilla.rust-android-gradle.rust-android'
|
||||
|
||||
cargo {
|
||||
module = "../../../ark-circom-passport" // this works
|
||||
// module = "/Users/turboblitz/code/my-code/passport-sbt/app/halo2-passport"
|
||||
libname = "ark_circom_passport" // Or whatever matches Cargo.toml's [package] name.
|
||||
// targets = ["arm", "x86"] // failing
|
||||
// targets = ["arm64", "darwin-aarch64"] // add this one for apple later
|
||||
targets = ["arm64"] // Those work
|
||||
apiLevel = 29
|
||||
profile = 'release'
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.tradle.nfc">
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="false" />
|
||||
</manifest>
|
||||
Binary file not shown.
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Anton Tananaev (anton.tananaev@gmail.com)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.tradle.nfc
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import com.gemalto.jp2.JP2Decoder
|
||||
import org.jnbis.WsqDecoder
|
||||
import java.io.InputStream
|
||||
|
||||
object ImageUtil {
|
||||
|
||||
fun decodeImage(context: Context?, mimeType: String, inputStream: InputStream?): Bitmap {
|
||||
return if (mimeType.equals("image/jp2", ignoreCase = true) || mimeType.equals(
|
||||
"image/jpeg2000",
|
||||
ignoreCase = true
|
||||
)
|
||||
) {
|
||||
JP2Decoder(inputStream).decode()
|
||||
} else if (mimeType.equals("image/x-wsq", ignoreCase = true)) {
|
||||
val wsqDecoder = WsqDecoder()
|
||||
val bitmap = wsqDecoder.decode(inputStream)
|
||||
val byteData = bitmap.pixels
|
||||
val intData = IntArray(byteData.size)
|
||||
for (j in byteData.indices) {
|
||||
intData[j] = 0xFF000000.toInt() or
|
||||
(byteData[j].toInt() and 0xFF shl 16) or
|
||||
(byteData[j].toInt() and 0xFF shl 8) or
|
||||
(byteData[j].toInt() and 0xFF)
|
||||
}
|
||||
Bitmap.createBitmap(
|
||||
intData,
|
||||
0,
|
||||
bitmap.width,
|
||||
bitmap.width,
|
||||
bitmap.height,
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
} else {
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,649 @@
|
||||
/*
|
||||
* Copyright 2016 - 2022 Anton Tananaev (anton.tananaev@gmail.com)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
|
||||
package io.tradle.nfc
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.widget.EditText
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import io.tradle.nfc.ImageUtil.decodeImage
|
||||
import net.sf.scuba.smartcards.CardService
|
||||
import org.apache.commons.io.IOUtils
|
||||
|
||||
import org.bouncycastle.asn1.ASN1InputStream
|
||||
import org.bouncycastle.asn1.cms.ContentInfo
|
||||
import org.bouncycastle.asn1.cms.SignedData
|
||||
import org.bouncycastle.asn1.ASN1Primitive
|
||||
import org.bouncycastle.asn1.ASN1Sequence
|
||||
import org.bouncycastle.asn1.ASN1Set
|
||||
import org.bouncycastle.asn1.x509.Certificate
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec
|
||||
import org.bouncycastle.jce.interfaces.ECPublicKey
|
||||
|
||||
|
||||
import org.jmrtd.BACKey
|
||||
import org.jmrtd.BACKeySpec
|
||||
import org.jmrtd.PassportService
|
||||
import org.jmrtd.lds.CardAccessFile
|
||||
import org.jmrtd.lds.ChipAuthenticationPublicKeyInfo
|
||||
import org.jmrtd.lds.PACEInfo
|
||||
import org.jmrtd.lds.SODFile
|
||||
import org.jmrtd.lds.SecurityInfo
|
||||
import org.jmrtd.lds.icao.DG14File
|
||||
import org.jmrtd.lds.icao.DG1File
|
||||
import org.jmrtd.lds.icao.DG2File
|
||||
import org.jmrtd.lds.iso19794.FaceImageInfo
|
||||
|
||||
import org.json.JSONObject
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.DataInputStream
|
||||
import java.io.InputStream
|
||||
import java.io.IOException
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.KeyStore
|
||||
import java.security.MessageDigest
|
||||
import java.security.Signature
|
||||
import java.security.cert.CertPathValidator
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.PKIXParameters
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.MGF1ParameterSpec
|
||||
import java.security.spec.PSSParameterSpec
|
||||
import java.text.ParseException
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.security.PublicKey
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import javax.crypto.Cipher
|
||||
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReadableNativeMap
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||
import com.facebook.react.bridge.ReactMethod
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule
|
||||
import com.facebook.react.bridge.LifecycleEventListener
|
||||
import com.facebook.react.bridge.Callback
|
||||
|
||||
class Response(json: String) : JSONObject(json) {
|
||||
val type: String? = this.optString("type")
|
||||
val data = this.optJSONArray("data")
|
||||
?.let { 0.until(it.length()).map { i -> it.optJSONObject(i) } } // returns an array of JSONObject
|
||||
?.map { Foo(it.toString()) } // transforms each JSONObject of the array into Foo
|
||||
}
|
||||
|
||||
class Foo(json: String) : JSONObject(json) {
|
||||
val id = this.optInt("id")
|
||||
val title: String? = this.optString("title")
|
||||
}
|
||||
|
||||
class RNPassportReaderModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), LifecycleEventListener {
|
||||
// private var passportNumberFromIntent = false
|
||||
// private var encodePhotoToBase64 = false
|
||||
private var scanPromise: Promise? = null
|
||||
private var opts: ReadableMap? = null
|
||||
|
||||
data class Data(val id: String, val digest: String, val signature: String, val publicKey: String)
|
||||
|
||||
data class PassportData(
|
||||
val dg1File: DG1File,
|
||||
val dg2File: DG2File,
|
||||
val sodFile: SODFile
|
||||
)
|
||||
|
||||
interface DataCallback {
|
||||
fun onDataReceived(data: String)
|
||||
}
|
||||
|
||||
init {
|
||||
instance = this
|
||||
reactContext.addLifecycleEventListener(this)
|
||||
}
|
||||
|
||||
override fun onCatalystInstanceDestroy() {
|
||||
reactContext.removeLifecycleEventListener(this)
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return "RNPassportReader"
|
||||
}
|
||||
|
||||
fun sendDataToJS(passportData: PassportData) {
|
||||
val gson = Gson()
|
||||
|
||||
val dataMap = Arguments.createMap()
|
||||
dataMap.putString("passportData", gson.toJson(passportData))
|
||||
// Add all the other fields of the YourDataClass object to the map
|
||||
|
||||
reactApplicationContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||
.emit("ReadDataTaskCompleted", dataMap)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun scan(opts: ReadableMap, promise: Promise) {
|
||||
val mNfcAdapter = NfcAdapter.getDefaultAdapter(reactApplicationContext)
|
||||
// val mNfcAdapter = NfcAdapter.getDefaultAdapter(this.reactContext)
|
||||
if (mNfcAdapter == null) {
|
||||
promise.reject("E_NOT_SUPPORTED", "NFC chip reading not supported")
|
||||
return
|
||||
}
|
||||
|
||||
if (!mNfcAdapter.isEnabled) {
|
||||
promise.reject("E_NOT_ENABLED", "NFC chip reading not enabled")
|
||||
return
|
||||
}
|
||||
|
||||
if (scanPromise != null) {
|
||||
promise.reject("E_ONE_REQ_AT_A_TIME", "Already running a scan")
|
||||
return
|
||||
}
|
||||
|
||||
this.opts = opts
|
||||
this.scanPromise = promise
|
||||
Log.d("RNPassportReaderModule", "opts set to: " + opts.toString())
|
||||
}
|
||||
|
||||
private fun resetState() {
|
||||
scanPromise = null
|
||||
opts = null
|
||||
}
|
||||
|
||||
override fun onHostDestroy() {
|
||||
resetState()
|
||||
}
|
||||
|
||||
override fun onHostResume() {
|
||||
val mNfcAdapter = NfcAdapter.getDefaultAdapter(this.reactContext)
|
||||
mNfcAdapter?.let {
|
||||
val activity = currentActivity
|
||||
activity?.let {
|
||||
val intent = Intent(it.applicationContext, it.javaClass)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
val pendingIntent = PendingIntent.getActivity(it, 0, intent, PendingIntent.FLAG_MUTABLE) // PendingIntent.FLAG_UPDATE_CURRENT
|
||||
val filter = arrayOf(arrayOf(IsoDep::class.java.name))
|
||||
mNfcAdapter.enableForegroundDispatch(it, pendingIntent, null, filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHostPause() {
|
||||
val mNfcAdapter = NfcAdapter.getDefaultAdapter(this.reactContext)
|
||||
mNfcAdapter?.disableForegroundDispatch(currentActivity)
|
||||
}
|
||||
|
||||
fun receiveIntent(intent: Intent) {
|
||||
Log.d("RNPassportReaderModule", "receiveIntent: " + intent.action)
|
||||
if (scanPromise == null) return
|
||||
if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action) {
|
||||
val tag: Tag? = intent.extras?.getParcelable(NfcAdapter.EXTRA_TAG)
|
||||
if (tag?.techList?.contains("android.nfc.tech.IsoDep") == true) {
|
||||
val passportNumber = opts?.getString(PARAM_DOC_NUM)
|
||||
val expirationDate = opts?.getString(PARAM_DOE)
|
||||
val birthDate = opts?.getString(PARAM_DOB)
|
||||
if (!passportNumber.isNullOrEmpty() && !expirationDate.isNullOrEmpty() && !birthDate.isNullOrEmpty()) {
|
||||
val bacKey: BACKeySpec = BACKey(passportNumber, birthDate, expirationDate)
|
||||
ReadTask(IsoDep.get(tag), bacKey).execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun toBase64(bitmap: Bitmap, quality: Int): String {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream)
|
||||
val byteArray = byteArrayOutputStream.toByteArray()
|
||||
return JPEG_DATA_URI_PREFIX + Base64.encodeToString(byteArray, Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private inner class ReadTask(private val isoDep: IsoDep, private val bacKey: BACKeySpec) : AsyncTask<Void?, Void?, Exception?>() {
|
||||
|
||||
private lateinit var dg1File: DG1File
|
||||
private lateinit var dg2File: DG2File
|
||||
private lateinit var dg14File: DG14File
|
||||
private lateinit var sodFile: SODFile
|
||||
private var imageBase64: String? = null
|
||||
private var bitmap: Bitmap? = null
|
||||
private var chipAuthSucceeded = false
|
||||
private var passiveAuthSuccess = false
|
||||
private lateinit var dg14Encoded: ByteArray
|
||||
|
||||
override fun doInBackground(vararg params: Void?): Exception? {
|
||||
try {
|
||||
isoDep.timeout = 10000
|
||||
Log.e("MY_LOGS", "This should obvsly log")
|
||||
val cardService = CardService.getInstance(isoDep)
|
||||
Log.e("MY_LOGS", "cardService gotten")
|
||||
cardService.open()
|
||||
Log.e("MY_LOGS", "cardService opened")
|
||||
val service = PassportService(
|
||||
cardService,
|
||||
PassportService.NORMAL_MAX_TRANCEIVE_LENGTH,
|
||||
PassportService.DEFAULT_MAX_BLOCKSIZE,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
Log.e("MY_LOGS", "service gotten")
|
||||
service.open()
|
||||
Log.e("MY_LOGS", "service opened")
|
||||
var paceSucceeded = false
|
||||
try {
|
||||
Log.e("MY_LOGS", "trying to get cardAccessFile...")
|
||||
val cardAccessFile = CardAccessFile(service.getInputStream(PassportService.EF_CARD_ACCESS))
|
||||
Log.e("MY_LOGS", "cardAccessFile: ${cardAccessFile}")
|
||||
|
||||
val securityInfoCollection = cardAccessFile.securityInfos
|
||||
for (securityInfo: SecurityInfo in securityInfoCollection) {
|
||||
if (securityInfo is PACEInfo) {
|
||||
Log.e("MY_LOGS", "trying PACE...")
|
||||
service.doPACE(
|
||||
bacKey,
|
||||
securityInfo.objectIdentifier,
|
||||
PACEInfo.toParameterSpec(securityInfo.parameterId),
|
||||
null,
|
||||
)
|
||||
Log.e("MY_LOGS", "PACE succeeded")
|
||||
paceSucceeded = true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w("MY_LOGS", e)
|
||||
}
|
||||
Log.e("MY_LOGS", "Sending select applet command with paceSucceeded: ${paceSucceeded}") // this is false so PACE doesn't succeed
|
||||
service.sendSelectApplet(paceSucceeded)
|
||||
if (!paceSucceeded) {
|
||||
try {
|
||||
Log.e("MY_LOGS", "trying to get EF_COM...")
|
||||
service.getInputStream(PassportService.EF_COM).read()
|
||||
} catch (e: Exception) {
|
||||
Log.e("MY_LOGS", "doing BAC")
|
||||
service.doBAC(bacKey) // <======================== error happens here
|
||||
Log.e("MY_LOGS", "BAC done")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val dg1In = service.getInputStream(PassportService.EF_DG1)
|
||||
dg1File = DG1File(dg1In)
|
||||
val dg2In = service.getInputStream(PassportService.EF_DG2)
|
||||
dg2File = DG2File(dg2In)
|
||||
val sodIn = service.getInputStream(PassportService.EF_SOD)
|
||||
sodFile = SODFile(sodIn)
|
||||
|
||||
// val gson = Gson()
|
||||
// Log.d(TAG, "============FIRST CONSOLE LOG=============")
|
||||
// Log.d(TAG, "dg1File: " + gson.toJson(dg1File))
|
||||
// Log.d(TAG, "dg2File: " + gson.toJson(dg2File))
|
||||
// Log.d(TAG, "sodFile.docSigningCertificate: ${sodFile.docSigningCertificate}")
|
||||
// Log.d(TAG, "publicKey: ${sodFile.docSigningCertificate.publicKey}")
|
||||
// Log.d(TAG, "publicKey: ${sodFile.docSigningCertificate.publicKey.toString()}")
|
||||
// Log.d(TAG, "publicKey: ${sodFile.docSigningCertificate.publicKey.format}")
|
||||
// Log.d(TAG, "publicKey: ${Base64.encodeToString(sodFile.docSigningCertificate.publicKey.encoded, Base64.DEFAULT)}")
|
||||
// Log.d(TAG, "sodFile.docSigningCertificate: ${gson.toJson(sodFile.docSigningCertificate)}")
|
||||
// Log.d(TAG, "sodFile.dataGroupHashes: ${sodFile.dataGroupHashes}")
|
||||
// Log.d(TAG, "sodFile.dataGroupHashes: ${gson.toJson(sodFile.dataGroupHashes)}")
|
||||
// Log.d(TAG, "concatenated: $concatenated")
|
||||
// Log.d(TAG, "concatenated: ${gson.toJson(concatenated)}")
|
||||
// Log.d(TAG, "concatenated: ${gson.toJson(concatenated.joinToString("") { "%02x".format(it) })}")
|
||||
// Log.d(TAG, "sodFile.eContent: ${sodFile.eContent}")
|
||||
// Log.d(TAG, "sodFile.eContent: ${gson.toJson(sodFile.eContent)}")
|
||||
// Log.d(TAG, "sodFile.eContent: ${gson.toJson(sodFile.eContent.joinToString("") { "%02x".format(it) })}")
|
||||
// Log.d(TAG, "sodFile.encryptedDigest: ${sodFile.encryptedDigest}")
|
||||
// Log.d(TAG, "sodFile.encryptedDigest: ${gson.toJson(sodFile.encryptedDigest)}")
|
||||
// Log.d(TAG, "sodFile.encryptedDigest: ${gson.toJson(sodFile.encryptedDigest.joinToString("") { "%02x".format(it) })}")
|
||||
// var id = passportNumberView.text.toString()
|
||||
// try {
|
||||
// postData(id, sodFile.eContent.joinToString("") { "%02x".format(it) }, sodFile.encryptedDigest.joinToString("") { "%02x".format(it) }, sodFile.docSigningCertificate.publicKey.toString())
|
||||
// } catch (e: IOException) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
// Log.d(TAG, "============LET'S VERIFY THE SIGNATURE=============")
|
||||
|
||||
doChipAuth(service)
|
||||
doPassiveAuth()
|
||||
|
||||
// Log.d(TAG, "============SIGNATURE VERIFIED=============")
|
||||
// sendDataToJS(PassportData(dg1File, dg2File, sodFile))
|
||||
// Log.d(TAG, "============DATA SENT TO JS=============")
|
||||
|
||||
val allFaceImageInfo: MutableList<FaceImageInfo> = ArrayList()
|
||||
dg2File.faceInfos.forEach {
|
||||
allFaceImageInfo.addAll(it.faceImageInfos)
|
||||
}
|
||||
if (allFaceImageInfo.isNotEmpty()) {
|
||||
val faceImageInfo = allFaceImageInfo.first()
|
||||
val imageLength = faceImageInfo.imageLength
|
||||
val dataInputStream = DataInputStream(faceImageInfo.imageInputStream)
|
||||
val buffer = ByteArray(imageLength)
|
||||
dataInputStream.readFully(buffer, 0, imageLength)
|
||||
val inputStream: InputStream = ByteArrayInputStream(buffer, 0, imageLength)
|
||||
bitmap = decodeImage(reactContext, faceImageInfo.mimeType, inputStream)
|
||||
imageBase64 = Base64.encodeToString(buffer, Base64.DEFAULT)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return e
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun doChipAuth(service: PassportService) {
|
||||
try {
|
||||
val dg14In = service.getInputStream(PassportService.EF_DG14)
|
||||
dg14Encoded = IOUtils.toByteArray(dg14In)
|
||||
val dg14InByte = ByteArrayInputStream(dg14Encoded)
|
||||
dg14File = DG14File(dg14InByte)
|
||||
val dg14FileSecurityInfo = dg14File.securityInfos
|
||||
for (securityInfo: SecurityInfo in dg14FileSecurityInfo) {
|
||||
if (securityInfo is ChipAuthenticationPublicKeyInfo) {
|
||||
service.doEACCA(
|
||||
securityInfo.keyId,
|
||||
ChipAuthenticationPublicKeyInfo.ID_CA_ECDH_AES_CBC_CMAC_256,
|
||||
securityInfo.objectIdentifier,
|
||||
securityInfo.subjectPublicKey,
|
||||
)
|
||||
chipAuthSucceeded = true
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doPassiveAuth() {
|
||||
try {
|
||||
Log.d(TAG, "Starting passive authentication...")
|
||||
val digest = MessageDigest.getInstance(sodFile.digestAlgorithm)
|
||||
Log.d(TAG, "Using digest algorithm: ${sodFile.digestAlgorithm}")
|
||||
|
||||
|
||||
val dataHashes = sodFile.dataGroupHashes
|
||||
|
||||
val dg14Hash = if (chipAuthSucceeded) digest.digest(dg14Encoded) else ByteArray(0)
|
||||
val dg1Hash = digest.digest(dg1File.encoded)
|
||||
val dg2Hash = digest.digest(dg2File.encoded)
|
||||
|
||||
// val gson = Gson()
|
||||
// Log.d(TAG, "dataHashes " + gson.toJson(dataHashes))
|
||||
// val hexMap = sodFile.dataGroupHashes.mapValues { (_, value) ->
|
||||
// value.joinToString("") { "%02x".format(it) }
|
||||
// }
|
||||
// Log.d(TAG, "hexMap: ${gson.toJson(hexMap)}")
|
||||
// Log.d(TAG, "concatenated: $concatenated")
|
||||
// Log.d(TAG, "concatenated: ${gson.toJson(concatenated)}")
|
||||
// Log.d(TAG, "concatenated: ${gson.toJson(concatenated.joinToString("") { "%02x".format(it) })}")
|
||||
// Log.d(TAG, "dg1File.encoded " + gson.toJson(dg1File.encoded))
|
||||
// Log.d(TAG, "dg1File.encoded.joinToString " + gson.toJson(dg1File.encoded.joinToString("") { "%02x".format(it) }))
|
||||
// Log.d(TAG, "dg1Hash " + gson.toJson(dg1Hash))
|
||||
// Log.d(TAG, "dg1Hash.joinToString " + gson.toJson(dg1Hash.joinToString("") { "%02x".format(it) }))
|
||||
// Log.d(TAG, "dg2File.encoded " + gson.toJson(dg2File.encoded))
|
||||
// Log.d(TAG, "dg2File.encoded.joinToString " + gson.toJson(dg2File.encoded.joinToString("") { "%02x".format(it) }))
|
||||
// Log.d(TAG, "dg2Hash " + gson.toJson(dg2Hash))
|
||||
// Log.d(TAG, "dg2HashjoinToString " + gson.toJson(dg2Hash.joinToString("") { "%02x".format(it) }))
|
||||
|
||||
Log.d(TAG, "Comparing data group hashes...")
|
||||
|
||||
if (Arrays.equals(dg1Hash, dataHashes[1]) && Arrays.equals(dg2Hash, dataHashes[2])
|
||||
&& (!chipAuthSucceeded || Arrays.equals(dg14Hash, dataHashes[14]))) {
|
||||
|
||||
Log.d(TAG, "Data group hashes match.")
|
||||
|
||||
val asn1InputStream = ASN1InputStream(getReactApplicationContext().assets.open("masterList"))
|
||||
val keystore = KeyStore.getInstance(KeyStore.getDefaultType())
|
||||
keystore.load(null, null)
|
||||
val cf = CertificateFactory.getInstance("X.509")
|
||||
|
||||
var p: ASN1Primitive?
|
||||
var obj = asn1InputStream.readObject()
|
||||
|
||||
while (obj != null) {
|
||||
p = obj
|
||||
val asn1 = ASN1Sequence.getInstance(p)
|
||||
if (asn1 == null || asn1.size() == 0) {
|
||||
throw IllegalArgumentException("Null or empty sequence passed.")
|
||||
}
|
||||
|
||||
if (asn1.size() != 2) {
|
||||
throw IllegalArgumentException("Incorrect sequence size: " + asn1.size())
|
||||
}
|
||||
val certSet = ASN1Set.getInstance(asn1.getObjectAt(1))
|
||||
for (i in 0 until certSet.size()) {
|
||||
val certificate = Certificate.getInstance(certSet.getObjectAt(i))
|
||||
val pemCertificate = certificate.encoded
|
||||
val javaCertificate = cf.generateCertificate(ByteArrayInputStream(pemCertificate))
|
||||
keystore.setCertificateEntry(i.toString(), javaCertificate)
|
||||
}
|
||||
obj = asn1InputStream.readObject()
|
||||
|
||||
}
|
||||
|
||||
val docSigningCertificates = sodFile.docSigningCertificates
|
||||
Log.d(TAG, "Checking document signing certificates for validity...")
|
||||
for (docSigningCertificate: X509Certificate in docSigningCertificates) {
|
||||
docSigningCertificate.checkValidity()
|
||||
Log.d(TAG, "Certificate: ${docSigningCertificate.subjectDN} is valid.")
|
||||
}
|
||||
|
||||
val cp = cf.generateCertPath(docSigningCertificates)
|
||||
val pkixParameters = PKIXParameters(keystore)
|
||||
pkixParameters.isRevocationEnabled = false
|
||||
val cpv = CertPathValidator.getInstance(CertPathValidator.getDefaultType())
|
||||
Log.d(TAG, "Validating certificate path...")
|
||||
cpv.validate(cp, pkixParameters)
|
||||
var sodDigestEncryptionAlgorithm = sodFile.docSigningCertificate.sigAlgName
|
||||
var isSSA = false
|
||||
if ((sodDigestEncryptionAlgorithm == "SSAwithRSA/PSS")) {
|
||||
sodDigestEncryptionAlgorithm = "SHA256withRSA/PSS"
|
||||
isSSA = true
|
||||
|
||||
}
|
||||
val sign = Signature.getInstance(sodDigestEncryptionAlgorithm)
|
||||
if (isSSA) {
|
||||
sign.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))
|
||||
}
|
||||
sign.initVerify(sodFile.docSigningCertificate)
|
||||
sign.update(sodFile.eContent)
|
||||
|
||||
passiveAuthSuccess = sign.verify(sodFile.encryptedDigest)
|
||||
Log.d(TAG, "Passive authentication success: $passiveAuthSuccess")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Exception in passive authentication", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: Exception?) {
|
||||
if (scanPromise == null) return
|
||||
|
||||
if (result != null) {
|
||||
// Log.w(TAG, exceptionStack(result))
|
||||
if (result is IOException) {
|
||||
scanPromise?.reject("E_SCAN_FAILED_DISCONNECT", "Lost connection to chip on card")
|
||||
} else {
|
||||
scanPromise?.reject("E_SCAN_FAILED", result)
|
||||
}
|
||||
|
||||
resetState()
|
||||
return
|
||||
}
|
||||
|
||||
val mrzInfo = dg1File.mrzInfo
|
||||
|
||||
// var quality = 100
|
||||
// if (opts?.hasKey("quality") == true) {
|
||||
// quality = (opts?.getDouble("quality") ?: 1.0 * 100).toInt()
|
||||
// }
|
||||
val gson = Gson()
|
||||
|
||||
val signedDataField = SODFile::class.java.getDeclaredField("signedData")
|
||||
signedDataField.isAccessible = true
|
||||
|
||||
// val signedData = signedDataField.get(sodFile) as SignedData
|
||||
|
||||
val eContentAsn1InputStream = ASN1InputStream(sodFile.eContent.inputStream())
|
||||
// val eContentDecomposed: ASN1Primitive = eContentAsn1InputStream.readObject()
|
||||
|
||||
val passport = Arguments.createMap()
|
||||
passport.putString("mrz", mrzInfo.toString())
|
||||
passport.putString("signatureAlgorithm", sodFile.docSigningCertificate.sigAlgName) // this one is new
|
||||
|
||||
val publicKey = sodFile.docSigningCertificate.publicKey
|
||||
if (publicKey is RSAPublicKey) {
|
||||
passport.putString("modulus", publicKey.modulus.toString())
|
||||
} else if (publicKey is ECPublicKey) {
|
||||
// Handle the elliptic curve public key case
|
||||
|
||||
// val w = publicKey.getW()
|
||||
// passport.putString("publicKeyW", w.toString())
|
||||
|
||||
// val ecParams = publicKey.getParams()
|
||||
// passport.putInt("cofactor", ecParams.getCofactor())
|
||||
// passport.putString("curve", ecParams.getCurve().toString())
|
||||
// passport.putString("generator", ecParams.getGenerator().toString())
|
||||
// passport.putString("order", ecParams.getOrder().toString())
|
||||
// if (ecParams is ECNamedCurveSpec) {
|
||||
// passport.putString("curveName", ecParams.getName())
|
||||
// }
|
||||
|
||||
// Old one, probably wrong:
|
||||
// passport.putString("curveName", (publicKey.parameters as ECNamedCurveSpec).name)
|
||||
// passport.putString("curveName", (publicKey.parameters.algorithm)) or maybe this
|
||||
passport.putString("publicKeyQ", publicKey.q.toString())
|
||||
}
|
||||
|
||||
passport.putString("dataGroupHashes", gson.toJson(sodFile.dataGroupHashes))
|
||||
passport.putString("eContent", gson.toJson(sodFile.eContent))
|
||||
passport.putString("encryptedDigest", gson.toJson(sodFile.encryptedDigest))
|
||||
|
||||
|
||||
// Another way to get signing time is to get into signedData.signerInfos, then search for the ICO identifier 1.2.840.113549.1.9.5
|
||||
// passport.putString("signerInfos", gson.toJson(signedData.signerInfos))
|
||||
|
||||
// Log.d(TAG, "signedData.digestAlgorithms: ${gson.toJson(signedData.digestAlgorithms)}")
|
||||
// Log.d(TAG, "signedData.signerInfos: ${gson.toJson(signedData.signerInfos)}")
|
||||
// Log.d(TAG, "signedData.certificates: ${gson.toJson(signedData.certificates)}")
|
||||
|
||||
// val base64 = bitmap?.let { toBase64(it, quality) }
|
||||
// val photo = Arguments.createMap()
|
||||
// photo.putString("base64", base64 ?: "")
|
||||
// photo.putInt("width", bitmap?.width ?: 0)
|
||||
// photo.putInt("height", bitmap?.height ?: 0)
|
||||
// passport.putMap("photo", photo)
|
||||
// passport.putString("dg2File", gson.toJson(dg2File))
|
||||
|
||||
scanPromise?.resolve(passport)
|
||||
resetState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertDate(input: String?): String? {
|
||||
if (input == null) {
|
||||
return null
|
||||
}
|
||||
return try {
|
||||
SimpleDateFormat("yyMMdd", Locale.US).format(SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(input)!!)
|
||||
} catch (e: ParseException) {
|
||||
// Log.w(RNPassportReaderModule::class.java.simpleName, e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
//-------------functions related to calling rust lib----------------//
|
||||
|
||||
// Declare native method
|
||||
external fun callRustCode(): String
|
||||
|
||||
@ReactMethod
|
||||
fun callRustLib(callback: Callback) {
|
||||
// Call the Rust function
|
||||
val resultFromRust = callRustCode()
|
||||
|
||||
// Return the result to JavaScript through the callback
|
||||
callback.invoke(null, resultFromRust)
|
||||
}
|
||||
|
||||
external fun provePassport(
|
||||
mrz: List<String>,
|
||||
reveal_bitmap: List<String>,
|
||||
dataHashes: List<String>,
|
||||
eContentBytes: List<String>,
|
||||
signature: List<String>,
|
||||
pubkey: List<String>,
|
||||
address: String
|
||||
): String
|
||||
|
||||
@ReactMethod
|
||||
fun provePassport(inputs: ReadableMap, callback: Callback) {
|
||||
Log.d(TAG, "inputsaaa: " + inputs.toString())
|
||||
|
||||
val mrz = inputs.getArray("mrz")?.toArrayList()?.map { it as String } ?: listOf()
|
||||
val reveal_bitmap = inputs.getArray("reveal_bitmap")?.toArrayList()?.map { it as String } ?: listOf()
|
||||
val data_hashes = inputs.getArray("dataHashes")?.toArrayList()?.map { it as String } ?: listOf()
|
||||
val e_content_bytes = inputs.getArray("eContentBytes")?.toArrayList()?.map { it as String } ?: listOf()
|
||||
val signature = inputs.getArray("signature")?.toArrayList()?.map { it as String } ?: listOf()
|
||||
val pubkey = inputs.getArray("pubkey")?.toArrayList()?.map { it as String } ?: listOf()
|
||||
val address = inputs.getString("address") ?: ""
|
||||
|
||||
val resultFromProof = provePassport(mrz, reveal_bitmap, data_hashes, e_content_bytes, signature, pubkey, address)
|
||||
|
||||
Log.d(TAG, "resultFromProof: " + resultFromProof.toString())
|
||||
|
||||
// Return the result to JavaScript through the callback
|
||||
callback.invoke(null, resultFromProof)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = RNPassportReaderModule::class.java.simpleName
|
||||
private const val PARAM_DOC_NUM = "documentNumber";
|
||||
private const val PARAM_DOB = "dateOfBirth";
|
||||
private const val PARAM_DOE = "dateOfExpiry";
|
||||
const val JPEG_DATA_URI_PREFIX = "data:image/jpeg;base64,"
|
||||
private const val KEY_IS_SUPPORTED = "isSupported"
|
||||
var instance: RNPassportReaderModule? = null
|
||||
init {
|
||||
System.loadLibrary("ark_circom_passport")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.tradle.nfc
|
||||
import io.tradle.nfc.RNPassportReaderModule
|
||||
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.uimanager.ViewManager
|
||||
|
||||
class RNPassportReaderPackage : ReactPackage {
|
||||
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
||||
return listOf(RNPassportReaderModule(reactContext))
|
||||
}
|
||||
|
||||
// No need to override createJSModules method as it's removed in newer versions of React Native
|
||||
|
||||
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
28
app/android/react-native-passport-reader/index.android.js
Normal file
28
app/android/react-native-passport-reader/index.android.js
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
import { NativeModules } from 'react-native'
|
||||
|
||||
const { RNPassportReader } = NativeModules
|
||||
const DATE_REGEX = /^\d{6}$/
|
||||
|
||||
module.exports = {
|
||||
...RNPassportReader,
|
||||
scan
|
||||
}
|
||||
|
||||
function scan({ documentNumber, dateOfBirth, dateOfExpiry, quality=1 }) {
|
||||
assert(typeof documentNumber === 'string', 'expected string "documentNumber"')
|
||||
assert(isDate(dateOfBirth), 'expected string "dateOfBirth" in format "yyMMdd"')
|
||||
assert(isDate(dateOfExpiry), 'expected string "dateOfExpiry" in format "yyMMdd"')
|
||||
return RNPassportReader.scan({ documentNumber, dateOfBirth, dateOfExpiry, quality })
|
||||
}
|
||||
|
||||
|
||||
function assert (statement, err) {
|
||||
if (!statement) {
|
||||
throw new Error(err || 'Assertion failed')
|
||||
}
|
||||
}
|
||||
|
||||
function isDate (str) {
|
||||
return typeof str === 'string' && DATE_REGEX.test(str)
|
||||
}
|
||||
15
app/android/react-native-passport-reader/package.json
Normal file
15
app/android/react-native-passport-reader/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "react-native-passport-reader",
|
||||
"version": "1.0.3",
|
||||
"description": "read the NFC chip in a passport",
|
||||
"main": "index.android.js",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-component",
|
||||
"nfc",
|
||||
"android",
|
||||
"scanner"
|
||||
],
|
||||
"author": "Mark Vayngrib <mark@tradle.io> (http://github.com/mvayngrib)",
|
||||
"license": "APLv2"
|
||||
}
|
||||
6
app/android/settings.gradle
Normal file
6
app/android/settings.gradle
Normal file
@@ -0,0 +1,6 @@
|
||||
rootProject.name = 'Proof of Passport'
|
||||
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||
include ':app'
|
||||
includeBuild('../node_modules/@react-native/gradle-plugin')
|
||||
include ':react-native-passport-reader'
|
||||
project(':react-native-passport-reader').projectDir = new File(rootProject.projectDir, './react-native-passport-reader/android')
|
||||
4
app/app.json
Normal file
4
app/app.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "Proof of Passport",
|
||||
"displayName": "Proof of Passport"
|
||||
}
|
||||
3
app/ark-circom-passport/.gitignore
vendored
Normal file
3
app/ark-circom-passport/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
rsa
|
||||
passport
|
||||
4958
app/ark-circom-passport/Cargo.lock
generated
Normal file
4958
app/ark-circom-passport/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
app/ark-circom-passport/Cargo.toml
Normal file
46
app/ark-circom-passport/Cargo.toml
Normal file
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "ark_circom_passport"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "ark_circom_passport"
|
||||
path = "src/passport.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
# [profile.dev]
|
||||
# opt-level = 3
|
||||
|
||||
[patch.crates-io]
|
||||
# NOTE: Forked wasmer to work around memory limits
|
||||
# See https://github.com/wasmerio/wasmer/commit/09c7070
|
||||
wasmer = { git = "https://github.com/oskarth/wasmer.git", rev = "09c7070" }
|
||||
|
||||
|
||||
[dependencies]
|
||||
ark-circom = { git = "https://github.com/0xturboblitz/circom-compat.git" }
|
||||
ark-bn254 = { version = "=0.4.0" }
|
||||
ark-groth16 = { version = "=0.4.0", default-features = false, features = ["parallel"] }
|
||||
ark-std = { version = "=0.4.0", default-features = false, features = ["parallel"] }
|
||||
ark-crypto-primitives = { version = "=0.4.0" }
|
||||
ark-ec = { version = "=0.4.1" }
|
||||
ark-ff = { version = "=0.4.1", default-features = false, features = ["parallel", "asm"] }
|
||||
ark-relations = { version = "=0.4.0", default-features = false }
|
||||
ark-serialize = { version = "=0.4.1", default-features = false }
|
||||
color-eyre = "=0.6.2"
|
||||
ethers = "=2.0.7"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
jni = "0.18"
|
||||
log = "0.4"
|
||||
android_logger = "0.8"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
byteorder = "=1.4.3"
|
||||
num-traits = { version = "=0.2.15", default-features = false }
|
||||
hex = "0.4"
|
||||
num-bigint = { version = "=0.4.3", default-features = false, features = [
|
||||
"rand",
|
||||
] }
|
||||
wasmer = { git = "https://github.com/oskarth/wasmer.git", rev = "09c7070" }
|
||||
once_cell = "1.8"
|
||||
6
app/ark-circom-passport/README.md
Normal file
6
app/ark-circom-passport/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# ark circom proof and verification of passport circuit
|
||||
|
||||
To run tests and see logs:
|
||||
```
|
||||
cargo test -- --nocapture
|
||||
```
|
||||
1
app/ark-circom-passport/artifacts/verifier_artifact.json
Normal file
1
app/ark-circom-passport/artifacts/verifier_artifact.json
Normal file
File diff suppressed because one or more lines are too long
3
app/ark-circom-passport/rust-toolchain.toml
Normal file
3
app/ark-circom-passport/rust-toolchain.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
version = "1.67.0"
|
||||
460
app/ark-circom-passport/src/passport.rs
Normal file
460
app/ark-circom-passport/src/passport.rs
Normal file
@@ -0,0 +1,460 @@
|
||||
use ark_circom::{ethereum, CircomBuilder, CircomConfig, circom::CircomReduction, WitnessCalculator};
|
||||
use ark_std::rand::thread_rng;
|
||||
use color_eyre::Result;
|
||||
use std::os::raw::c_int;
|
||||
|
||||
use ark_bn254::Bn254;
|
||||
use ark_crypto_primitives::snark::SNARK;
|
||||
use ark_groth16::{Groth16, Proof};
|
||||
use ark_ec::AffineRepr;
|
||||
|
||||
use num_bigint::{BigInt, Sign, ToBigInt};
|
||||
extern crate hex;
|
||||
use hex::decode;
|
||||
|
||||
type GrothBn = Groth16<Bn254>;
|
||||
|
||||
extern crate jni;
|
||||
use jni::objects::{JClass, JObject, JValue, JString};
|
||||
use jni::JNIEnv;
|
||||
use jni::sys::jstring;
|
||||
|
||||
use log::Level;
|
||||
use android_logger::Config;
|
||||
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
use serde_json::json;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::Instant,
|
||||
convert::TryInto,
|
||||
sync::Mutex
|
||||
};
|
||||
|
||||
use wasmer::{Module, Store};
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
|
||||
mod zkey;
|
||||
pub use zkey::{read_zkey, read_zkey_from_include_bytes};
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_io_tradle_nfc_RNPassportReaderModule_callRustCode(
|
||||
env: JNIEnv,
|
||||
_: JClass,
|
||||
) -> jstring {
|
||||
android_logger::init_once(Config::default().with_min_level(Level::Trace));
|
||||
log::error!("PROOF OF PASSPORT ---- log before imports");
|
||||
|
||||
let my_int: c_int = -1;
|
||||
let my_str: String = "no_proof".to_string();
|
||||
|
||||
let combined = json!({
|
||||
"my_int": my_int,
|
||||
"my_str": my_str
|
||||
});
|
||||
|
||||
let combined_str = combined.to_string();
|
||||
let output = env.new_string(combined_str).expect("Couldn't create java string!");
|
||||
|
||||
output.into_inner()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_io_tradle_nfc_RNPassportReaderModule_provePassport(
|
||||
env: JNIEnv,
|
||||
_: JClass,
|
||||
mrz: JObject,
|
||||
reveal_bitmap: JObject,
|
||||
data_hashes: JObject,
|
||||
e_content_bytes: JObject,
|
||||
signature: JObject,
|
||||
pubkey: JObject,
|
||||
address: JString,
|
||||
) -> jstring {
|
||||
|
||||
log::error!("PROOF OF PASSPORT ---- formatting inputsaaaa...");
|
||||
fn run_proof(
|
||||
mrz: JObject,
|
||||
reveal_bitmap: JObject,
|
||||
data_hashes: JObject,
|
||||
e_content_bytes: JObject,
|
||||
signature: JObject,
|
||||
pubkey: JObject,
|
||||
address: JString,
|
||||
env: JNIEnv
|
||||
) -> Result<jstring, Box<dyn std::error::Error>> {
|
||||
android_logger::init_once(Config::default().with_min_level(Level::Trace));
|
||||
|
||||
|
||||
log::error!("PROOF OF PASSPORT ---- loading zkey...");
|
||||
let start = Instant::now();
|
||||
let now = Instant::now();
|
||||
let file_bytes: &'static [u8] = include_bytes!("../passport/proof_of_passport_final.zkey");
|
||||
log::error!("PROOF OF PASSPORT ---- zkey size: {}", file_bytes.len());
|
||||
let (params, matrices) = read_zkey_from_include_bytes(file_bytes).unwrap();
|
||||
log::error!("PROOF OF PASSPORT ---- zkey loaded. Took: {:?}", now.elapsed());
|
||||
let now = Instant::now();
|
||||
|
||||
println!("loading circuit...");
|
||||
const MAIN_WASM: &'static [u8] = include_bytes!("../passport/proof_of_passport.wasm");
|
||||
const MAIN_R1CS: &'static [u8] = include_bytes!("../passport/proof_of_passport.r1cs");
|
||||
log::error!("PROOF OF PASSPORT ---- WASM and R1CS loaded. Took: {:?}", now.elapsed());
|
||||
let now = Instant::now();
|
||||
let cfg = CircomConfig::<Bn254>::from_bytes(MAIN_WASM, MAIN_R1CS)?;
|
||||
log::error!("PROOF OF PASSPORT ---- Circuit config: {:?}", now.elapsed());
|
||||
let now = Instant::now();
|
||||
|
||||
log::error!("PROOF OF PASSPORT ---- formatting inputs...");
|
||||
log::error!("PROOF OF PASSPORT ---- mrz_veccccccc");
|
||||
|
||||
let mut builder = CircomBuilder::new(cfg,);
|
||||
let mrz_vec: Vec<String> = java_arraylist_to_rust_vec(&env, mrz)?;
|
||||
let reveal_bitmap_vec: Vec<String> = java_arraylist_to_rust_vec(&env, reveal_bitmap)?;
|
||||
let data_hashes_vec: Vec<String> = java_arraylist_to_rust_vec(&env, data_hashes)?;
|
||||
let e_content_bytes_vec: Vec<String> = java_arraylist_to_rust_vec(&env, e_content_bytes)?;
|
||||
let signature_vec: Vec<String> = java_arraylist_to_rust_vec(&env, signature)?;
|
||||
let pubkey_vec: Vec<String> = java_arraylist_to_rust_vec(&env, pubkey)?;
|
||||
let address_str: String = env.get_string(address)?.into();
|
||||
|
||||
log::error!("PROOF OF PASSPORT ---- mrz_vec {:?}", mrz_vec);
|
||||
log::error!("PROOF OF PASSPORT ---- reveal_bitmap_vec {:?}", reveal_bitmap_vec);
|
||||
log::error!("PROOF OF PASSPORT ---- data_hashes_vec {:?}", data_hashes_vec);
|
||||
log::error!("PROOF OF PASSPORT ---- e_content_bytes_vec {:?}", e_content_bytes_vec);
|
||||
log::error!("PROOF OF PASSPORT ---- signature_vec {:?}", signature_vec);
|
||||
log::error!("PROOF OF PASSPORT ---- pubkey_vec {:?}", pubkey_vec);
|
||||
log::error!("PROOF OF PASSPORT ---- address_str {:?}", address_str);
|
||||
|
||||
mrz_vec.iter()
|
||||
.filter_map(|s| s.parse::<u128>().ok())
|
||||
.for_each(|n| builder.push_input("mrz", n));
|
||||
reveal_bitmap_vec.iter()
|
||||
.filter_map(|s| s.parse::<u128>().ok())
|
||||
.for_each(|n| builder.push_input("reveal_bitmap", n));
|
||||
data_hashes_vec.iter()
|
||||
.filter_map(|s| s.parse::<u128>().ok())
|
||||
.for_each(|n| builder.push_input("dataHashes", n));
|
||||
e_content_bytes_vec.iter()
|
||||
.filter_map(|s| s.parse::<u128>().ok())
|
||||
.for_each(|n| builder.push_input("eContentBytes", n));
|
||||
signature_vec.iter()
|
||||
.filter_map(|s| s.parse::<u128>().ok())
|
||||
.for_each(|n| builder.push_input("signature", n));
|
||||
pubkey_vec.iter()
|
||||
.filter_map(|s| s.parse::<u128>().ok())
|
||||
.for_each(|n| builder.push_input("pubkey", n));
|
||||
|
||||
|
||||
let address_bigint = BigInt::from_bytes_be(Sign::Plus, &decode(&address_str[2..])?);
|
||||
builder.push_input("address", address_bigint);
|
||||
|
||||
let circom = builder.build().unwrap(); // this here calculates the witness
|
||||
log::error!("PROOF OF PASSPORT ---- witness calculated. Took: {:?}", now.elapsed());
|
||||
let now = Instant::now();
|
||||
let inputs = circom.get_public_inputs().unwrap();
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let proof = Groth16::<Bn254, CircomReduction>::prove(¶ms, circom, &mut rng).unwrap();
|
||||
log::error!("PROOF OF PASSPORT ---- proof: {:?}", proof);
|
||||
log::error!("PROOF OF PASSPORT ---- proof done. Took: {:?}", now.elapsed());
|
||||
let now = Instant::now();
|
||||
|
||||
let pvk = Groth16::<Bn254>::process_vk(¶ms.vk).unwrap();
|
||||
let verified = Groth16::<Bn254>::verify_with_processed_vk(&pvk, &inputs, &proof).unwrap();
|
||||
assert!(verified);
|
||||
|
||||
log::error!("PROOF OF PASSPORT ---- proof verified. Took: {:?}", now.elapsed());
|
||||
|
||||
log::error!("PROOF OF PASSPORT ---- inputs: {:?}", inputs);
|
||||
|
||||
let converted_inputs: ethereum::Inputs = inputs.as_slice().into();
|
||||
let inputs_str: Vec<String> = converted_inputs.0.iter().map(|value| format!("{}", value)).collect();
|
||||
let serialized_inputs = serde_json::to_string(&inputs_str).unwrap();
|
||||
log::error!("PROOF OF PASSPORT ---- Serialized inputs: {:?}", serialized_inputs);
|
||||
|
||||
let proof_str = proof_to_proof_str(&proof);
|
||||
let serialized_proof = serde_json::to_string(&proof_str).unwrap();
|
||||
|
||||
log::error!("PROOF OF PASSPORT ---- Serialized proof: {:?}", serialized_proof);
|
||||
|
||||
let combined = json!({
|
||||
"duration": start.elapsed().as_millis(),
|
||||
"serialized_proof": serialized_proof,
|
||||
"serialized_inputs": serialized_inputs
|
||||
});
|
||||
|
||||
let combined_str = combined.to_string();
|
||||
let output = env.new_string(combined_str).expect("Couldn't create java string!");
|
||||
|
||||
Ok(output.into_inner())
|
||||
}
|
||||
|
||||
match run_proof(
|
||||
mrz,
|
||||
reveal_bitmap,
|
||||
data_hashes,
|
||||
e_content_bytes,
|
||||
signature,
|
||||
pubkey,
|
||||
address,
|
||||
env
|
||||
) {
|
||||
Ok(output) => output,
|
||||
Err(_) => env.new_string("error").expect("Couldn't create java string!").into_inner(),
|
||||
}
|
||||
}
|
||||
|
||||
fn java_arraylist_to_rust_vec(env: &JNIEnv, java_list: JObject) -> Result<Vec<String>, jni::errors::Error> {
|
||||
let size = env.call_method(java_list, "size", "()I", &[])?.i()? as i32;
|
||||
let mut vec = Vec::with_capacity(size.try_into().unwrap());
|
||||
|
||||
for i in 0..size {
|
||||
let java_string = env.call_method(java_list, "get", "(I)Ljava/lang/Object;", &[JValue::from(i)])?.l()?;
|
||||
let rust_string: String = env.get_string(java_string.into())?.into();
|
||||
vec.push(rust_string);
|
||||
}
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize)]
|
||||
struct ProofStr {
|
||||
a: (String, String),
|
||||
b: ((String, String), (String, String)),
|
||||
c: (String, String),
|
||||
}
|
||||
|
||||
fn proof_to_proof_str(proof: &Proof<Bn254>) -> ProofStr {
|
||||
let a_xy = proof.a.xy().unwrap();
|
||||
let b_xy = proof.b.xy().unwrap();
|
||||
let c_xy = proof.c.xy().unwrap();
|
||||
|
||||
let b_c0_c0 = b_xy.0.c0.to_string();
|
||||
let b_c0_c1 = b_xy.0.c1.to_string();
|
||||
let b_c1_c0 = b_xy.1.c0.to_string();
|
||||
let b_c1_c1 = b_xy.1.c1.to_string();
|
||||
|
||||
ProofStr {
|
||||
a: (a_xy.0.to_string(), a_xy.1.to_string()),
|
||||
b: ((b_c0_c0, b_c0_c1), (b_c1_c0, b_c1_c1)),
|
||||
c: (c_xy.0.to_string(), c_xy.1.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use ethers::{
|
||||
contract::ContractError,
|
||||
prelude::abigen,
|
||||
providers::{Http, Middleware, Provider},
|
||||
utils::Anvil,
|
||||
};
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::File,
|
||||
sync::Arc,
|
||||
collections::HashMap
|
||||
};
|
||||
use ark_circom::{
|
||||
circom::CircomReduction,
|
||||
WitnessCalculator
|
||||
};
|
||||
use num_bigint::ToBigInt;
|
||||
|
||||
|
||||
// We need to implement the conversion from the Ark-Circom's internal Ethereum types to
|
||||
// the ones expected by the abigen'd types. Could we maybe provide a convenience
|
||||
// macro for these, given that there's room for implementation error?
|
||||
abigen!(Groth16Verifier, "./artifacts/verifier_artifact.json");
|
||||
use groth_16_verifier::{G1Point, G2Point, Proof as EthProof, VerifyingKey as Groth16VerifyingKey};
|
||||
|
||||
impl From<ethereum::G1> for G1Point {
|
||||
fn from(src: ethereum::G1) -> Self {
|
||||
Self { x: src.x, y: src.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ethereum::G2> for G2Point {
|
||||
fn from(src: ethereum::G2) -> Self {
|
||||
// We should use the `.as_tuple()` method which handles converting
|
||||
// the G2 elements to have the second limb first
|
||||
let src = src.as_tuple();
|
||||
Self { x: src.0, y: src.1 }
|
||||
}
|
||||
}
|
||||
impl From<ethereum::Proof> for EthProof {
|
||||
fn from(src: ethereum::Proof) -> Self {
|
||||
Self {
|
||||
a: src.a.into(),
|
||||
b: src.b.into(),
|
||||
c: src.c.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<ethereum::VerifyingKey> for Groth16VerifyingKey {
|
||||
fn from(src: ethereum::VerifyingKey) -> Self {
|
||||
Self {
|
||||
alfa_1: src.alpha1.into(),
|
||||
beta_2: src.beta2.into(),
|
||||
gamma_2: src.gamma2.into(),
|
||||
delta_2: src.delta2.into(),
|
||||
ic: src.ic.into_iter().map(|i| i.into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Middleware> Groth16Verifier<M> {
|
||||
async fn check_proof<
|
||||
I: Into<ethereum::Inputs>,
|
||||
P: Into<ethereum::Proof>,
|
||||
VK: Into<ethereum::VerifyingKey>,
|
||||
>(
|
||||
&self,
|
||||
proof: P,
|
||||
vk: VK,
|
||||
inputs: I,
|
||||
) -> Result<bool, ContractError<M>> {
|
||||
// convert into the expected format by the contract
|
||||
let proof = proof.into().into();
|
||||
let vk = vk.into().into();
|
||||
let inputs = inputs.into().0;
|
||||
println!("inputs in gorth16 verifier: {:?}", &inputs);
|
||||
|
||||
// query the contract
|
||||
let res = self.verify(inputs, proof, vk).call().await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_proof() -> Result<(), Box<dyn Error>> {
|
||||
let now = Instant::now();
|
||||
|
||||
println!("loading zkey...");
|
||||
let path = "./passport/proof_of_passport_final.zkey";
|
||||
let mut file = File::open(path).unwrap();
|
||||
let (params, matrices) = read_zkey(&mut file).unwrap();
|
||||
|
||||
println!("Circuit loaded. Took: {:?}", now.elapsed());
|
||||
let now = Instant::now();
|
||||
|
||||
let mrz_vec: Vec<String> = vec!["97", "91", "95", "31", "88", "80", "60", "70", "82", "65", "84", "65", "86", "69", "82", "78", "73", "69", "82", "60", "60", "70", "76", "79", "82", "69", "78", "84", "60", "72", "85", "71", "85", "69", "83", "60", "74", "69", "65", "78", "60", "60", "60", "60", "60", "60", "60", "60", "60", "49", "57", "72", "65", "51", "52", "56", "50", "56", "52", "70", "82", "65", "48", "48", "48", "55", "49", "57", "49", "77", "50", "57", "49", "50", "48", "57", "53", "60", "60", "60", "60", "60", "60", "60", "60", "60", "60", "60", "60", "60", "60", "48", "50"].iter().map(|&s| s.to_string()).collect();
|
||||
let reveal_bitmap_vec: Vec<String> = vec!["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "1", "1", "1", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"].iter().map(|&s| s.to_string()).collect();
|
||||
let data_hashes_vec: Vec<String> = vec!["48", "130", "1", "37", "2", "1", "0", "48", "11", "6", "9", "96", "134", "72", "1", "101", "3", "4", "2", "1", "48", "130", "1", "17", "48", "37", "2", "1", "1", "4", "32", "99", "19", "179", "205", "55", "104", "45", "214", "133", "101", "233", "177", "130", "1", "37", "89", "125", "229", "139", "34", "132", "146", "28", "116", "248", "186", "63", "195", "96", "151", "26", "215", "48", "37", "2", "1", "2", "4", "32", "63", "234", "106", "78", "31", "16", "114", "137", "237", "17", "92", "71", "134", "47", "62", "78", "189", "233", "201", "213", "53", "4", "47", "189", "201", "133", "6", "121", "34", "131", "64", "142", "48", "37", "2", "1", "3", "4", "32", "136", "155", "87", "144", "121", "15", "152", "127", "85", "25", "154", "80", "20", "58", "51", "75", "193", "116", "234", "0", "60", "30", "29", "30", "183", "141", "72", "247", "255", "203", "100", "124", "48", "37", "2", "1", "11", "4", "32", "0", "194", "104", "108", "237", "246", "97", "230", "116", "198", "69", "110", "26", "87", "17", "89", "110", "199", "108", "250", "36", "21", "39", "87", "110", "102", "250", "213", "174", "131", "171", "174", "48", "37", "2", "1", "12", "4", "32", "190", "82", "180", "235", "222", "33", "79", "50", "152", "136", "142", "35", "116", "224", "6", "242", "156", "141", "128", "247", "10", "61", "98", "86", "248", "45", "207", "210", "90", "232", "175", "38", "48", "37", "2", "1", "13", "4", "32", "91", "222", "210", "193", "63", "222", "104", "82", "36", "41", "138", "253", "70", "15", "148", "208", "156", "45", "105", "171", "241", "195", "185", "43", "217", "162", "146", "201", "222", "89", "238", "38", "48", "37", "2", "1", "14", "4", "32", "76", "123", "216", "13", "52", "227", "72", "245", "59", "193", "238", "166", "103", "49", "24", "164", "171", "188", "194", "197", "156", "187", "249", "28", "198", "95", "69", "15", "182", "56", "54", "38"].iter().map(|&s| s.to_string()).collect();
|
||||
let e_content_bytes_vec: Vec<String> = vec!["49", "102", "48", "21", "6", "9", "42", "134", "72", "134", "247", "13", "1", "9", "3", "49", "8", "6", "6", "103", "129", "8", "1", "1", "1", "48", "28", "6", "9", "42", "134", "72", "134", "247", "13", "1", "9", "5", "49", "15", "23", "13", "49", "57", "49", "50", "49", "54", "49", "55", "50", "50", "51", "56", "90", "48", "47", "6", "9", "42", "134", "72", "134", "247", "13", "1", "9", "4", "49", "34", "4", "32", "176", "96", "59", "213", "131", "82", "89", "248", "105", "125", "37", "177", "158", "162", "137", "43", "13", "39", "115", "6", "59", "229", "81", "110", "49", "75", "255", "184", "155", "73", "116", "86"].iter().map(|&s| s.to_string()).collect();
|
||||
let signature_vec: Vec<String> = vec!["1004979219314799894", "6361443755252600907", "6439012883494616023", "9400879716815088139", "17551897985575934811", "11779273958797828281", "2536315921873401485", "3748173260178203981", "12475215309213288577", "6281117468118442715", "1336292932993922350", "14238156234566069988", "11985045093510507012", "3585865343992378960", "16170829868787473084", "17039645001628184779", "486540501180074772", "5061439412388381188", "12478821212163933993", "7430448406248319432", "746345521572597865", "5002454658692185142", "3715069341922830389", "11010599232161942094", "1577500614971981868", "13656226284809645063", "3918261659477120323", "5578832687955645075", "3416933977282345392", "15829829506526117610", "17465616637242519010", "6519177967447716150"].iter().map(|&s| s.to_string()).collect();
|
||||
let pubkey_vec: Vec<String> = vec!["9539992759301679521", "1652651398804391575", "7756096264856639170", "15028348881266521487", "13451582891670014060", "11697656644529425980", "14590137142310897374", "1172377360308996086", "6389592621616098288", "6767780215543232436", "11347756978427069433", "2593119277386338350", "18385617576997885505", "14960211320702750252", "8706817324429498800", "15168543370367053559", "8708916123725550363", "18006178692029805686", "6398208271038376723", "15000821494077560096", "17674982305626887153", "2867958270953137726", "9287774520059158342", "9813100051910281130", "13494313215150203208", "7792741716144106392", "6553490305289731807", "32268224696386820", "15737886769048580611", "669518601007982974", "11424760966478363403", "16073833083611347461"].iter().map(|&s| s.to_string()).collect();
|
||||
let address_str: String = "0xEde0fA5A7b196F512204f286666E5eC03E1005D2".to_string();
|
||||
|
||||
fn parse_and_insert(hash_map: &mut HashMap<String, Vec<BigInt>>, key: &str, data: Vec<&str>) {
|
||||
let parsed_data: Vec<BigInt> = data.into_iter()
|
||||
.filter_map(|s| s.parse::<u128>().ok().and_then(|num| num.to_bigint()))
|
||||
.collect();
|
||||
hash_map.insert(key.to_string(), parsed_data);
|
||||
}
|
||||
|
||||
let mut inputs: HashMap<String, Vec<num_bigint::BigInt>> = HashMap::new();
|
||||
parse_and_insert(&mut inputs, "mrz", mrz_vec.iter().map(AsRef::as_ref).collect());
|
||||
parse_and_insert(&mut inputs, "reveal_bitmap", reveal_bitmap_vec.iter().map(AsRef::as_ref).collect());
|
||||
parse_and_insert(&mut inputs, "dataHashes", data_hashes_vec.iter().map(AsRef::as_ref).collect());
|
||||
parse_and_insert(&mut inputs, "eContentBytes", e_content_bytes_vec.iter().map(AsRef::as_ref).collect());
|
||||
parse_and_insert(&mut inputs, "signature", signature_vec.iter().map(AsRef::as_ref).collect());
|
||||
parse_and_insert(&mut inputs, "pubkey", pubkey_vec.iter().map(AsRef::as_ref).collect());
|
||||
let address_bigint = BigInt::from_bytes_be(Sign::Plus, &decode(&address_str[2..])?);
|
||||
inputs.insert("address".to_string(), vec![address_bigint]);
|
||||
|
||||
let mut rng = thread_rng();
|
||||
use ark_std::UniformRand;
|
||||
let num_inputs = matrices.num_instance_variables;
|
||||
let num_constraints = matrices.num_constraints;
|
||||
let rng = &mut rng;
|
||||
|
||||
let r = ark_bn254::Fr::rand(rng);
|
||||
let s = ark_bn254::Fr::rand(rng);
|
||||
|
||||
let mut wtns = WitnessCalculator::new("./passport/proof_of_passport.wasm").unwrap();
|
||||
let full_assignment = wtns
|
||||
.calculate_witness_element::<Bn254, _>(inputs, false)
|
||||
.unwrap();
|
||||
println!("witness calculated. Took: {:?}", now.elapsed());
|
||||
let now = Instant::now();
|
||||
|
||||
let proof = Groth16::<Bn254, CircomReduction>::create_proof_with_reduction_and_matrices(
|
||||
¶ms,
|
||||
r,
|
||||
s,
|
||||
&matrices,
|
||||
num_inputs,
|
||||
num_constraints,
|
||||
full_assignment.as_slice(),
|
||||
)
|
||||
.unwrap();
|
||||
println!("proof done. Took: {:?}", now.elapsed());
|
||||
let now = Instant::now();
|
||||
|
||||
let pvk = Groth16::<Bn254>::process_vk(¶ms.vk).unwrap();
|
||||
let inputs = &full_assignment[1..num_inputs];
|
||||
let verified = Groth16::<Bn254>::verify_with_processed_vk(&pvk, inputs, &proof).unwrap();
|
||||
println!("proof verified. Took: {:?}", now.elapsed());
|
||||
|
||||
assert!(verified);
|
||||
|
||||
println!("inputs: {:?}", inputs);
|
||||
|
||||
let converted_inputs: ethereum::Inputs = inputs.into();
|
||||
let inputs_str: Vec<String> = converted_inputs.0.iter().map(|value| format!("{}", value)).collect();
|
||||
let serialized_inputs = serde_json::to_string(&inputs_str).unwrap();
|
||||
println!("Serialized inputs: {:?}", serialized_inputs);
|
||||
|
||||
let proof_str = proof_to_proof_str(&proof);
|
||||
let serialized_proof = serde_json::to_string(&proof_str).unwrap();
|
||||
|
||||
println!("Serialized proof: {:?}", serialized_proof);
|
||||
|
||||
let combined = json!({
|
||||
"duration": now.elapsed().as_millis(),
|
||||
"serialized_proof": serialized_proof,
|
||||
"serialized_inputs": serialized_inputs
|
||||
});
|
||||
|
||||
let combined_str = combined.to_string();
|
||||
println!("proof and inputs: {:?}", combined_str);
|
||||
|
||||
|
||||
// // launch the network & compile the verifier
|
||||
// println!("launching network");
|
||||
|
||||
// let anvil = Anvil::new().spawn();
|
||||
// let acc = anvil.addresses()[0];
|
||||
// let provider = Provider::<Http>::try_from(anvil.endpoint())?;
|
||||
// let provider = provider.with_sender(acc);
|
||||
// let provider = Arc::new(provider);
|
||||
|
||||
// // deploy the verifier
|
||||
// let contract = Groth16Verifier::deploy(provider.clone(), ())?
|
||||
// .send()
|
||||
// .await?;
|
||||
|
||||
// println!("verifier deployed");
|
||||
// println!("contract {:?}", contract);
|
||||
// // check the proof on chain
|
||||
// let onchain_verified = contract
|
||||
// .check_proof(proof, params.vk, inputs.as_slice())
|
||||
// .await?;
|
||||
|
||||
// println!("proof verified on chain");
|
||||
// println!("onchain_verified {:?}", onchain_verified);
|
||||
|
||||
// assert!(onchain_verified);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
379
app/ark-circom-passport/src/zkey.rs
Normal file
379
app/ark-circom-passport/src/zkey.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
//! ZKey Parsing
|
||||
//!
|
||||
//! Each ZKey file is broken into sections:
|
||||
//! Header(1)
|
||||
//! Prover Type 1 Groth
|
||||
//! HeaderGroth(2)
|
||||
//! n8q
|
||||
//! q
|
||||
//! n8r
|
||||
//! r
|
||||
//! NVars
|
||||
//! NPub
|
||||
//! DomainSize (multiple of 2
|
||||
//! alpha1
|
||||
//! beta1
|
||||
//! delta1
|
||||
//! beta2
|
||||
//! gamma2
|
||||
//! delta2
|
||||
//! IC(3)
|
||||
//! Coefs(4)
|
||||
//! PointsA(5)
|
||||
//! PointsB1(6)
|
||||
//! PointsB2(7)
|
||||
//! PointsC(8)
|
||||
//! PointsH(9)
|
||||
//! Contributions(10)
|
||||
use ark_ff::{BigInteger256, PrimeField};
|
||||
use ark_relations::r1cs::ConstraintMatrices;
|
||||
use ark_serialize::{CanonicalDeserialize, SerializationError};
|
||||
use ark_std::log2;
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Read, Seek, SeekFrom},
|
||||
};
|
||||
|
||||
use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G2Affine};
|
||||
use ark_groth16::{ProvingKey, VerifyingKey};
|
||||
use num_traits::Zero;
|
||||
use std::io::Cursor;
|
||||
|
||||
type IoResult<T> = Result<T, SerializationError>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Section {
|
||||
position: u64,
|
||||
#[allow(dead_code)]
|
||||
size: usize,
|
||||
}
|
||||
|
||||
/// Reads a SnarkJS ZKey file into an Arkworks ProvingKey.
|
||||
pub fn read_zkey<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
) -> IoResult<(ProvingKey<Bn254>, ConstraintMatrices<Fr>)> {
|
||||
let mut binfile = BinFile::new(reader)?;
|
||||
let proving_key = binfile.proving_key()?;
|
||||
let matrices = binfile.matrices()?;
|
||||
Ok((proving_key, matrices))
|
||||
}
|
||||
|
||||
pub fn read_zkey_from_include_bytes(
|
||||
data: &[u8],
|
||||
) -> IoResult<(ProvingKey<Bn254>, ConstraintMatrices<Fr>)> {
|
||||
let mut cursor = Cursor::new(data);
|
||||
let mut binfile = BinFile::new(&mut cursor)?;
|
||||
let proving_key = binfile.proving_key()?;
|
||||
let matrices = binfile.matrices()?;
|
||||
Ok((proving_key, matrices))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BinFile<'a, R> {
|
||||
#[allow(dead_code)]
|
||||
ftype: String,
|
||||
#[allow(dead_code)]
|
||||
version: u32,
|
||||
sections: HashMap<u32, Vec<Section>>,
|
||||
reader: &'a mut R,
|
||||
}
|
||||
|
||||
impl<'a, R: Read + Seek> BinFile<'a, R> {
|
||||
fn new(reader: &'a mut R) -> IoResult<Self> {
|
||||
let mut magic = [0u8; 4];
|
||||
reader.read_exact(&mut magic)?;
|
||||
|
||||
let version = reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
let num_sections = reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
let mut sections = HashMap::new();
|
||||
for _ in 0..num_sections {
|
||||
let section_id = reader.read_u32::<LittleEndian>()?;
|
||||
let section_length = reader.read_u64::<LittleEndian>()?;
|
||||
|
||||
let section = sections.entry(section_id).or_insert_with(Vec::new);
|
||||
section.push(Section {
|
||||
position: reader.stream_position()?,
|
||||
size: section_length as usize,
|
||||
});
|
||||
|
||||
reader.seek(SeekFrom::Current(section_length as i64))?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
ftype: std::str::from_utf8(&magic[..]).unwrap().to_string(),
|
||||
version,
|
||||
sections,
|
||||
reader,
|
||||
})
|
||||
}
|
||||
|
||||
fn proving_key(&mut self) -> IoResult<ProvingKey<Bn254>> {
|
||||
let header = self.groth_header()?;
|
||||
let ic = self.ic(header.n_public)?;
|
||||
|
||||
let a_query = self.a_query(header.n_vars)?;
|
||||
let b_g1_query = self.b_g1_query(header.n_vars)?;
|
||||
let b_g2_query = self.b_g2_query(header.n_vars)?;
|
||||
let l_query = self.l_query(header.n_vars - header.n_public - 1)?;
|
||||
let h_query = self.h_query(header.domain_size as usize)?;
|
||||
|
||||
let vk = VerifyingKey::<Bn254> {
|
||||
alpha_g1: header.verifying_key.alpha_g1,
|
||||
beta_g2: header.verifying_key.beta_g2,
|
||||
gamma_g2: header.verifying_key.gamma_g2,
|
||||
delta_g2: header.verifying_key.delta_g2,
|
||||
gamma_abc_g1: ic,
|
||||
};
|
||||
|
||||
let pk = ProvingKey::<Bn254> {
|
||||
vk,
|
||||
beta_g1: header.verifying_key.beta_g1,
|
||||
delta_g1: header.verifying_key.delta_g1,
|
||||
a_query,
|
||||
b_g1_query,
|
||||
b_g2_query,
|
||||
h_query,
|
||||
l_query,
|
||||
};
|
||||
|
||||
Ok(pk)
|
||||
}
|
||||
|
||||
fn get_section(&self, id: u32) -> Section {
|
||||
self.sections.get(&id).unwrap()[0].clone()
|
||||
}
|
||||
|
||||
fn groth_header(&mut self) -> IoResult<HeaderGroth> {
|
||||
let section = self.get_section(2);
|
||||
let header = HeaderGroth::new(&mut self.reader, §ion)?;
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
fn ic(&mut self, n_public: usize) -> IoResult<Vec<G1Affine>> {
|
||||
// the range is non-inclusive so we do +1 to get all inputs
|
||||
self.g1_section(n_public + 1, 3)
|
||||
}
|
||||
|
||||
/// Returns the [`ConstraintMatrices`] corresponding to the zkey
|
||||
pub fn matrices(&mut self) -> IoResult<ConstraintMatrices<Fr>> {
|
||||
let header = self.groth_header()?;
|
||||
|
||||
let section = self.get_section(4);
|
||||
self.reader.seek(SeekFrom::Start(section.position))?;
|
||||
let num_coeffs: u32 = self.reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
// insantiate AB
|
||||
let mut matrices = vec![vec![vec![]; header.domain_size as usize]; 2];
|
||||
let mut max_constraint_index = 0;
|
||||
for _ in 0..num_coeffs {
|
||||
let matrix: u32 = self.reader.read_u32::<LittleEndian>()?;
|
||||
let constraint: u32 = self.reader.read_u32::<LittleEndian>()?;
|
||||
let signal: u32 = self.reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
let value: Fr = deserialize_field_fr(&mut self.reader)?;
|
||||
max_constraint_index = std::cmp::max(max_constraint_index, constraint);
|
||||
matrices[matrix as usize][constraint as usize].push((value, signal as usize));
|
||||
}
|
||||
|
||||
let num_constraints = max_constraint_index as usize - header.n_public;
|
||||
// Remove the public input constraints, Arkworks adds them later
|
||||
matrices.iter_mut().for_each(|m| {
|
||||
m.truncate(num_constraints);
|
||||
});
|
||||
// This is taken from Arkworks' to_matrices() function
|
||||
let a = matrices[0].clone();
|
||||
let b = matrices[1].clone();
|
||||
let a_num_non_zero: usize = a.iter().map(|lc| lc.len()).sum();
|
||||
let b_num_non_zero: usize = b.iter().map(|lc| lc.len()).sum();
|
||||
let matrices = ConstraintMatrices {
|
||||
num_instance_variables: header.n_public + 1,
|
||||
num_witness_variables: header.n_vars - header.n_public,
|
||||
num_constraints,
|
||||
|
||||
a_num_non_zero,
|
||||
b_num_non_zero,
|
||||
c_num_non_zero: 0,
|
||||
|
||||
a,
|
||||
b,
|
||||
c: vec![],
|
||||
};
|
||||
|
||||
Ok(matrices)
|
||||
}
|
||||
|
||||
fn a_query(&mut self, n_vars: usize) -> IoResult<Vec<G1Affine>> {
|
||||
self.g1_section(n_vars, 5)
|
||||
}
|
||||
|
||||
fn b_g1_query(&mut self, n_vars: usize) -> IoResult<Vec<G1Affine>> {
|
||||
self.g1_section(n_vars, 6)
|
||||
}
|
||||
|
||||
fn b_g2_query(&mut self, n_vars: usize) -> IoResult<Vec<G2Affine>> {
|
||||
self.g2_section(n_vars, 7)
|
||||
}
|
||||
|
||||
fn l_query(&mut self, n_vars: usize) -> IoResult<Vec<G1Affine>> {
|
||||
self.g1_section(n_vars, 8)
|
||||
}
|
||||
|
||||
fn h_query(&mut self, n_vars: usize) -> IoResult<Vec<G1Affine>> {
|
||||
self.g1_section(n_vars, 9)
|
||||
}
|
||||
|
||||
fn g1_section(&mut self, num: usize, section_id: usize) -> IoResult<Vec<G1Affine>> {
|
||||
let section = self.get_section(section_id as u32);
|
||||
self.reader.seek(SeekFrom::Start(section.position))?;
|
||||
deserialize_g1_vec(self.reader, num as u32)
|
||||
}
|
||||
|
||||
fn g2_section(&mut self, num: usize, section_id: usize) -> IoResult<Vec<G2Affine>> {
|
||||
let section = self.get_section(section_id as u32);
|
||||
self.reader.seek(SeekFrom::Start(section.position))?;
|
||||
deserialize_g2_vec(self.reader, num as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug, CanonicalDeserialize)]
|
||||
pub struct ZVerifyingKey {
|
||||
alpha_g1: G1Affine,
|
||||
beta_g1: G1Affine,
|
||||
beta_g2: G2Affine,
|
||||
gamma_g2: G2Affine,
|
||||
delta_g1: G1Affine,
|
||||
delta_g2: G2Affine,
|
||||
}
|
||||
|
||||
impl ZVerifyingKey {
|
||||
fn new<R: Read>(reader: &mut R) -> IoResult<Self> {
|
||||
let alpha_g1 = deserialize_g1(reader)?;
|
||||
let beta_g1 = deserialize_g1(reader)?;
|
||||
let beta_g2 = deserialize_g2(reader)?;
|
||||
let gamma_g2 = deserialize_g2(reader)?;
|
||||
let delta_g1 = deserialize_g1(reader)?;
|
||||
let delta_g2 = deserialize_g2(reader)?;
|
||||
|
||||
Ok(Self {
|
||||
alpha_g1,
|
||||
beta_g1,
|
||||
beta_g2,
|
||||
gamma_g2,
|
||||
delta_g1,
|
||||
delta_g2,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct HeaderGroth {
|
||||
#[allow(dead_code)]
|
||||
n8q: u32,
|
||||
#[allow(dead_code)]
|
||||
q: BigInteger256,
|
||||
#[allow(dead_code)]
|
||||
n8r: u32,
|
||||
#[allow(dead_code)]
|
||||
r: BigInteger256,
|
||||
|
||||
n_vars: usize,
|
||||
n_public: usize,
|
||||
|
||||
domain_size: u32,
|
||||
#[allow(dead_code)]
|
||||
power: u32,
|
||||
|
||||
verifying_key: ZVerifyingKey,
|
||||
}
|
||||
|
||||
impl HeaderGroth {
|
||||
fn new<R: Read + Seek>(reader: &mut R, section: &Section) -> IoResult<Self> {
|
||||
reader.seek(SeekFrom::Start(section.position))?;
|
||||
Self::read(reader)
|
||||
}
|
||||
|
||||
fn read<R: Read>(mut reader: &mut R) -> IoResult<Self> {
|
||||
// TODO: Impl From<u32> in Arkworks
|
||||
let n8q: u32 = u32::deserialize_uncompressed(&mut reader)?;
|
||||
// group order r of Bn254
|
||||
let q = BigInteger256::deserialize_uncompressed(&mut reader)?;
|
||||
|
||||
let n8r: u32 = u32::deserialize_uncompressed(&mut reader)?;
|
||||
// Prime field modulus
|
||||
let r = BigInteger256::deserialize_uncompressed(&mut reader)?;
|
||||
|
||||
let n_vars = u32::deserialize_uncompressed(&mut reader)? as usize;
|
||||
let n_public = u32::deserialize_uncompressed(&mut reader)? as usize;
|
||||
|
||||
let domain_size: u32 = u32::deserialize_uncompressed(&mut reader)?;
|
||||
let power = log2(domain_size as usize);
|
||||
|
||||
let verifying_key = ZVerifyingKey::new(&mut reader)?;
|
||||
|
||||
Ok(Self {
|
||||
n8q,
|
||||
q,
|
||||
n8r,
|
||||
r,
|
||||
n_vars,
|
||||
n_public,
|
||||
domain_size,
|
||||
power,
|
||||
verifying_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// need to divide by R, since snarkjs outputs the zkey with coefficients
|
||||
// multiplieid by R^2
|
||||
fn deserialize_field_fr<R: Read>(reader: &mut R) -> IoResult<Fr> {
|
||||
let bigint = BigInteger256::deserialize_uncompressed(reader)?;
|
||||
Ok(Fr::new_unchecked(Fr::new_unchecked(bigint).into_bigint()))
|
||||
}
|
||||
|
||||
// skips the multiplication by R because Circom points are already in Montgomery form
|
||||
fn deserialize_field<R: Read>(reader: &mut R) -> IoResult<Fq> {
|
||||
let bigint = BigInteger256::deserialize_uncompressed(reader)?;
|
||||
// if you use Fq::new it multiplies by R
|
||||
Ok(Fq::new_unchecked(bigint))
|
||||
}
|
||||
|
||||
pub fn deserialize_field2<R: Read>(reader: &mut R) -> IoResult<Fq2> {
|
||||
let c0 = deserialize_field(reader)?;
|
||||
let c1 = deserialize_field(reader)?;
|
||||
Ok(Fq2::new(c0, c1))
|
||||
}
|
||||
|
||||
fn deserialize_g1<R: Read>(reader: &mut R) -> IoResult<G1Affine> {
|
||||
let x = deserialize_field(reader)?;
|
||||
let y = deserialize_field(reader)?;
|
||||
let infinity = x.is_zero() && y.is_zero();
|
||||
if infinity {
|
||||
Ok(G1Affine::identity())
|
||||
} else {
|
||||
Ok(G1Affine::new_unchecked(x, y))
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_g2<R: Read>(reader: &mut R) -> IoResult<G2Affine> {
|
||||
let f1 = deserialize_field2(reader)?;
|
||||
let f2 = deserialize_field2(reader)?;
|
||||
let infinity = f1.is_zero() && f2.is_zero();
|
||||
if infinity {
|
||||
Ok(G2Affine::identity())
|
||||
} else {
|
||||
Ok(G2Affine::new_unchecked(f1, f2))
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_g1_vec<R: Read>(reader: &mut R, n_vars: u32) -> IoResult<Vec<G1Affine>> {
|
||||
(0..n_vars).map(|_| deserialize_g1(reader)).collect()
|
||||
}
|
||||
|
||||
fn deserialize_g2_vec<R: Read>(reader: &mut R, n_vars: u32) -> IoResult<Vec<G2Affine>> {
|
||||
(0..n_vars).map(|_| deserialize_g2(reader)).collect()
|
||||
}
|
||||
6
app/babel.config.js
Normal file
6
app/babel.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: [
|
||||
["@babel/plugin-transform-private-methods", { "loose": true }],
|
||||
],
|
||||
};
|
||||
30
app/countries.md
Normal file
30
app/countries.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Working:
|
||||
- France
|
||||
- Guy from ETH Global Paris (Moldavia ? Bulgaria ?)
|
||||
- Malaysia
|
||||
|
||||
Crashing:
|
||||
- Thailand
|
||||
- Britain
|
||||
|
||||
|
||||
E FATAL EXCEPTION: main
|
||||
Process: com.proofofpassport, PID: 14479
|
||||
java.lang.ClassCastException: org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey cannot be cast to java.security.interfaces.RSAPublicKey
|
||||
at io.tradle.nfc.RNPassportReaderModule$ReadTask.onPostExecute(RNPassportReaderModule.kt:510)
|
||||
at io.tradle.nfc.RNPassportReaderModule$ReadTask.onPostExecute(RNPassportReaderModule.kt:238)
|
||||
at android.os.AsyncTask.finish(AsyncTask.java:771)
|
||||
at android.os.AsyncTask.access$900(AsyncTask.java:199)
|
||||
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:788)
|
||||
at android.os.Handler.dispatchMessage(Handler.java:106)
|
||||
at android.os.Looper.loopOnce(Looper.java:226)
|
||||
at android.os.Looper.loop(Looper.java:313)
|
||||
at android.app.ActivityThread.main(ActivityThread.java:8751)
|
||||
at java.lang.reflect.Method.invoke(Native Method)
|
||||
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
|
||||
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
|
||||
2023-10-31 16:41:37.052 625-625 SurfaceFlinger pid-625 E Attempt to update InputPolicyFlags without permission ACCESS_SURFACE_FLINGER
|
||||
2023-10-31 16:41:37.069 625-625 SurfaceFlinger pid-625 E Attempt to update InputPolicyFlags without permission ACCESS_SURFACE_FLINGER
|
||||
2023-10-31 16:41:37.088 625-625 SurfaceFlinger pid-625 E Attempt to update InputPolicyFlags without permission ACCESS_SURFACE_FLINGER
|
||||
2023-10-31 16:41:37.102 625-625 SurfaceFlinger pid-625 E Attempt to update InputPolicyFlags without permission ACCESS_SURFACE_FLINGER
|
||||
2023-10-31 16:41:37.108 1377-2420 TaskStackL...erAbstract pid-1377 E onTaskSnapshotChanged calle
|
||||
1
app/declarations.d.ts
vendored
Normal file
1
app/declarations.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module '@env';
|
||||
45
app/deployments/Groth16Verifier.json
Normal file
45
app/deployments/Groth16Verifier.json
Normal file
File diff suppressed because one or more lines are too long
655
app/deployments/ProofOfPassport.json
Normal file
655
app/deployments/ProofOfPassport.json
Normal file
File diff suppressed because one or more lines are too long
1
app/deployments/addresses.json
Normal file
1
app/deployments/addresses.json
Normal file
@@ -0,0 +1 @@
|
||||
{"ProofOfPassport":"0x7D459347d092D35f043f73021f06c19f834f8c3E","Groth16Verifier":"0x72d8Fb7Dd33c264b53D313ff3BF25D993c0AD050"}
|
||||
9
app/index.js
Normal file
9
app/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {AppRegistry} from 'react-native';
|
||||
import App from './App';
|
||||
import {name as appName} from './app.json';
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App);
|
||||
11
app/ios/.xcode.env
Normal file
11
app/ios/.xcode.env
Normal file
@@ -0,0 +1,11 @@
|
||||
# This `.xcode.env` file is versioned and is used to source the environment
|
||||
# used when running script phases inside Xcode.
|
||||
# To customize your local environment, you can create an `.xcode.env.local`
|
||||
# file that is not versioned.
|
||||
|
||||
# NODE_BINARY variable contains the PATH to the node executable.
|
||||
#
|
||||
# Customize the NODE_BINARY variable here.
|
||||
# For example, to use nvm with brew, add the following line
|
||||
# . "$(brew --prefix nvm)/nvm.sh" --no-use
|
||||
export NODE_BINARY=$(command -v node)
|
||||
5
app/ios/AwesomeProject-Bridging-Header.h
Normal file
5
app/ios/AwesomeProject-Bridging-Header.h
Normal file
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "React/RCTBridgeModule.h"
|
||||
712
app/ios/AwesomeProject.xcodeproj/project.pbxproj
Normal file
712
app/ios/AwesomeProject.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,712 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
00E356F31AD99517003FC87E /* AwesomeProjectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* AwesomeProjectTests.m */; };
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
1ADA5CBFFFB043C12B3C4011 /* Pods_AwesomeProject.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F81894AE401F61E2393104D /* Pods_AwesomeProject.framework */; };
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
|
||||
905B70052A72767900AFA232 /* PassportReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905B70042A72767900AFA232 /* PassportReader.swift */; };
|
||||
905B70072A72774000AFA232 /* PassportReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 905B70062A72774000AFA232 /* PassportReader.m */; };
|
||||
905B700B2A72A5E900AFA232 /* masterList.pem in Resources */ = {isa = PBXBuildFile; fileRef = 905B700A2A72A5E900AFA232 /* masterList.pem */; };
|
||||
D5B4CD952178B51C14F77614 /* Pods_AwesomeProject_AwesomeProjectTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 455B3F561C1D3D833AB15B26 /* Pods_AwesomeProject_AwesomeProjectTests.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
|
||||
remoteInfo = AwesomeProject;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
00E356EE1AD99517003FC87E /* AwesomeProjectTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AwesomeProjectTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
00E356F21AD99517003FC87E /* AwesomeProjectTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AwesomeProjectTests.m; sourceTree = "<group>"; };
|
||||
0D05C085F8CBAC104F72E160 /* Pods-AwesomeProject-AwesomeProjectTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AwesomeProject-AwesomeProjectTests.release.xcconfig"; path = "Target Support Files/Pods-AwesomeProject-AwesomeProjectTests/Pods-AwesomeProject-AwesomeProjectTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* AwesomeProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AwesomeProject.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = AwesomeProject/AppDelegate.h; sourceTree = "<group>"; };
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = AwesomeProject/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = AwesomeProject/Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = AwesomeProject/Info.plist; sourceTree = "<group>"; };
|
||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = AwesomeProject/main.m; sourceTree = "<group>"; };
|
||||
26A432E0434B89485C7E3765 /* Pods-AwesomeProject.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AwesomeProject.debug.xcconfig"; path = "Target Support Files/Pods-AwesomeProject/Pods-AwesomeProject.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
455B3F561C1D3D833AB15B26 /* Pods_AwesomeProject_AwesomeProjectTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AwesomeProject_AwesomeProjectTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = AwesomeProject/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
905B70032A72767800AFA232 /* AwesomeProject-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AwesomeProject-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
905B70042A72767900AFA232 /* PassportReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassportReader.swift; sourceTree = "<group>"; };
|
||||
905B70062A72774000AFA232 /* PassportReader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PassportReader.m; sourceTree = "<group>"; };
|
||||
905B70082A729CD400AFA232 /* AwesomeProject.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = AwesomeProject.entitlements; path = AwesomeProject/AwesomeProject.entitlements; sourceTree = "<group>"; };
|
||||
905B700A2A72A5E900AFA232 /* masterList.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = masterList.pem; path = AwesomeProject/masterList.pem; sourceTree = "<group>"; };
|
||||
9F81894AE401F61E2393104D /* Pods_AwesomeProject.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AwesomeProject.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A1B7A34F7E4C3F2D19C5D973 /* Pods-AwesomeProject.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AwesomeProject.release.xcconfig"; path = "Target Support Files/Pods-AwesomeProject/Pods-AwesomeProject.release.xcconfig"; sourceTree = "<group>"; };
|
||||
BCC787E0FDFB928F9DA23E3F /* Pods-AwesomeProject-AwesomeProjectTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AwesomeProject-AwesomeProjectTests.debug.xcconfig"; path = "Target Support Files/Pods-AwesomeProject-AwesomeProjectTests/Pods-AwesomeProject-AwesomeProjectTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
00E356EB1AD99517003FC87E /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D5B4CD952178B51C14F77614 /* Pods_AwesomeProject_AwesomeProjectTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1ADA5CBFFFB043C12B3C4011 /* Pods_AwesomeProject.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
00E356EF1AD99517003FC87E /* AwesomeProjectTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
00E356F21AD99517003FC87E /* AwesomeProjectTests.m */,
|
||||
00E356F01AD99517003FC87E /* Supporting Files */,
|
||||
);
|
||||
path = AwesomeProjectTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
00E356F01AD99517003FC87E /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
00E356F11AD99517003FC87E /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
13B07FAE1A68108700A75B9A /* AwesomeProject */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
905B700A2A72A5E900AFA232 /* masterList.pem */,
|
||||
905B70082A729CD400AFA232 /* AwesomeProject.entitlements */,
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.mm */,
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
|
||||
13B07FB71A68108700A75B9A /* main.m */,
|
||||
905B70042A72767900AFA232 /* PassportReader.swift */,
|
||||
905B70032A72767800AFA232 /* AwesomeProject-Bridging-Header.h */,
|
||||
905B70062A72774000AFA232 /* PassportReader.m */,
|
||||
);
|
||||
name = AwesomeProject;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
9F81894AE401F61E2393104D /* Pods_AwesomeProject.framework */,
|
||||
455B3F561C1D3D833AB15B26 /* Pods_AwesomeProject_AwesomeProjectTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07FAE1A68108700A75B9A /* AwesomeProject */,
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||
00E356EF1AD99517003FC87E /* AwesomeProjectTests */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
BBD78D7AC51CEA395F1C20DB /* Pods */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
83CBBA001A601CBA00E9B192 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07F961A680F5B00A75B9A /* AwesomeProject.app */,
|
||||
00E356EE1AD99517003FC87E /* AwesomeProjectTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
26A432E0434B89485C7E3765 /* Pods-AwesomeProject.debug.xcconfig */,
|
||||
A1B7A34F7E4C3F2D19C5D973 /* Pods-AwesomeProject.release.xcconfig */,
|
||||
BCC787E0FDFB928F9DA23E3F /* Pods-AwesomeProject-AwesomeProjectTests.debug.xcconfig */,
|
||||
0D05C085F8CBAC104F72E160 /* Pods-AwesomeProject-AwesomeProjectTests.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
00E356ED1AD99517003FC87E /* AwesomeProjectTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "AwesomeProjectTests" */;
|
||||
buildPhases = (
|
||||
2CEEDB4F696405D4E4B0CA04 /* [CP] Check Pods Manifest.lock */,
|
||||
00E356EA1AD99517003FC87E /* Sources */,
|
||||
00E356EB1AD99517003FC87E /* Frameworks */,
|
||||
00E356EC1AD99517003FC87E /* Resources */,
|
||||
4786C99960C198CBC0841652 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
00E356F51AD99517003FC87E /* PBXTargetDependency */,
|
||||
);
|
||||
name = AwesomeProjectTests;
|
||||
productName = AwesomeProjectTests;
|
||||
productReference = 00E356EE1AD99517003FC87E /* AwesomeProjectTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
13B07F861A680F5B00A75B9A /* AwesomeProject */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "AwesomeProject" */;
|
||||
buildPhases = (
|
||||
141C3B9CB8F4134662795CF4 /* [CP] Check Pods Manifest.lock */,
|
||||
FD10A7F022414F080027D42C /* Start Packager */,
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||
FAF2988CBB80C1B370711DF7 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = AwesomeProject;
|
||||
productName = AwesomeProject;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* AwesomeProject.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1210;
|
||||
TargetAttributes = {
|
||||
00E356ED1AD99517003FC87E = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
TestTargetID = 13B07F861A680F5B00A75B9A;
|
||||
};
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
LastSwiftMigration = 1430;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "AwesomeProject" */;
|
||||
compatibilityVersion = "Xcode 12.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 83CBB9F61A601CBA00E9B192;
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
13B07F861A680F5B00A75B9A /* AwesomeProject */,
|
||||
00E356ED1AD99517003FC87E /* AwesomeProjectTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
00E356EC1AD99517003FC87E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
905B700B2A72A5E900AFA232 /* masterList.pem in Resources */,
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(SRCROOT)/.xcode.env.local",
|
||||
"$(SRCROOT)/.xcode.env",
|
||||
);
|
||||
name = "Bundle React Native code and images";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
|
||||
};
|
||||
141C3B9CB8F4134662795CF4 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-AwesomeProject-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
2CEEDB4F696405D4E4B0CA04 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-AwesomeProject-AwesomeProjectTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
4786C99960C198CBC0841652 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-AwesomeProject-AwesomeProjectTests/Pods-AwesomeProject-AwesomeProjectTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-AwesomeProject-AwesomeProjectTests/Pods-AwesomeProject-AwesomeProjectTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AwesomeProject-AwesomeProjectTests/Pods-AwesomeProject-AwesomeProjectTests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
FAF2988CBB80C1B370711DF7 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-AwesomeProject/Pods-AwesomeProject-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-AwesomeProject/Pods-AwesomeProject-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AwesomeProject/Pods-AwesomeProject-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
FD10A7F022414F080027D42C /* Start Packager */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Start Packager";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
00E356EA1AD99517003FC87E /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
00E356F31AD99517003FC87E /* AwesomeProjectTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
13B07F871A680F5B00A75B9A /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
905B70072A72774000AFA232 /* PassportReader.m in Sources */,
|
||||
905B70052A72767900AFA232 /* PassportReader.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 13B07F861A680F5B00A75B9A /* AwesomeProject */;
|
||||
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
00E356F61AD99517003FC87E /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = BCC787E0FDFB928F9DA23E3F /* Pods-AwesomeProject-AwesomeProjectTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = AwesomeProjectTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
"$(inherited)",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AwesomeProject.app/AwesomeProject";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
00E356F71AD99517003FC87E /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 0D05C085F8CBAC104F72E160 /* Pods-AwesomeProject-AwesomeProjectTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
INFOPLIST_FILE = AwesomeProjectTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
"$(inherited)",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AwesomeProject.app/AwesomeProject";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 26A432E0434B89485C7E3765 /* Pods-AwesomeProject.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = AwesomeProject/AwesomeProject.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = G46U6J456T;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = AwesomeProject/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.nfcios;
|
||||
PRODUCT_NAME = AwesomeProject;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "AwesomeProject-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A1B7A34F7E4C3F2D19C5D973 /* Pods-AwesomeProject.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = AwesomeProject/AwesomeProject.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = G46U6J456T;
|
||||
INFOPLIST_FILE = AwesomeProject/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.warroom.nfcios;
|
||||
PRODUCT_NAME = AwesomeProject;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "AwesomeProject-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
83CBBA201A601CBA00E9B192 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
/usr/lib/swift,
|
||||
"$(inherited)",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(SDKROOT)/usr/lib/swift\"",
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
"\"$(inherited)\"",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-DFOLLY_NO_CONFIG",
|
||||
"-DFOLLY_MOBILE=1",
|
||||
"-DFOLLY_USE_LIBCPP=1",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
83CBBA211A601CBA00E9B192 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
/usr/lib/swift,
|
||||
"$(inherited)",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(SDKROOT)/usr/lib/swift\"",
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
"\"$(inherited)\"",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-DFOLLY_NO_CONFIG",
|
||||
"-DFOLLY_MOBILE=1",
|
||||
"-DFOLLY_USE_LIBCPP=1",
|
||||
);
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "AwesomeProjectTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
00E356F61AD99517003FC87E /* Debug */,
|
||||
00E356F71AD99517003FC87E /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "AwesomeProject" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
13B07F941A680F5B00A75B9A /* Debug */,
|
||||
13B07F951A680F5B00A75B9A /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "AwesomeProject" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
83CBBA201A601CBA00E9B192 /* Debug */,
|
||||
83CBBA211A601CBA00E9B192 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1210"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "AwesomeProject.app"
|
||||
BlueprintName = "AwesomeProject"
|
||||
ReferencedContainer = "container:AwesomeProject.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||
BuildableName = "AwesomeProjectTests.xctest"
|
||||
BlueprintName = "AwesomeProjectTests"
|
||||
ReferencedContainer = "container:AwesomeProject.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "AwesomeProject.app"
|
||||
BlueprintName = "AwesomeProject"
|
||||
ReferencedContainer = "container:AwesomeProject.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "AwesomeProject.app"
|
||||
BlueprintName = "AwesomeProject"
|
||||
ReferencedContainer = "container:AwesomeProject.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
10
app/ios/AwesomeProject.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
app/ios/AwesomeProject.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:AwesomeProject.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
368
app/ios/AwesomeProject/AES_3DES_DESEncryption.swift
Normal file
368
app/ios/AwesomeProject/AES_3DES_DESEncryption.swift
Normal file
@@ -0,0 +1,368 @@
|
||||
import Foundation
|
||||
import CommonCrypto
|
||||
|
||||
|
||||
/// Encrypts a message using AES/CBC/NOPADDING with a specified key and initialisation vector
|
||||
/// - Parameter key: Key use to encrypt
|
||||
/// - Parameter message: Message to encrypt
|
||||
/// - Parameter iv: Initialisation vector
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public func AESEncrypt(key:[UInt8], message:[UInt8], iv:[UInt8]) -> [UInt8] {
|
||||
|
||||
let dataLength = message.count
|
||||
|
||||
let cryptLen = message.count + kCCBlockSizeAES128
|
||||
var cryptData = Data(count: cryptLen)
|
||||
|
||||
let keyLength = size_t(key.count)
|
||||
let operation: CCOperation = CCOperation(kCCEncrypt)
|
||||
let algorithm: CCAlgorithm = CCAlgorithm(kCCAlgorithmAES)
|
||||
let options: CCOptions = CCOptions(0)
|
||||
|
||||
var numBytesEncrypted = 0
|
||||
|
||||
var cryptStatus: CCCryptorStatus = CCCryptorStatus(kCCSuccess)
|
||||
key.withUnsafeBytes {keyBytes in
|
||||
message.withUnsafeBytes{ dataBytes in
|
||||
iv.withUnsafeBytes{ ivBytes in
|
||||
cryptData.withUnsafeMutableBytes{ cryptBytes in
|
||||
|
||||
cryptStatus = CCCrypt(operation,
|
||||
algorithm,
|
||||
options,
|
||||
keyBytes.baseAddress,
|
||||
keyLength,
|
||||
ivBytes.baseAddress,
|
||||
dataBytes.baseAddress,
|
||||
dataLength,
|
||||
cryptBytes.bindMemory(to: UInt8.self).baseAddress,
|
||||
cryptLen,
|
||||
&numBytesEncrypted)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cryptStatus == kCCSuccess {
|
||||
cryptData.count = Int(numBytesEncrypted)
|
||||
|
||||
return [UInt8](cryptData)
|
||||
} else {
|
||||
Log.error("AES Encrypt Error: \(cryptStatus)")
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/// Decrypts a message using AES/CBC/NOPADDING with a specified key and initialisation vector
|
||||
/// - Parameter key: Key use to decrypt
|
||||
/// - Parameter message: Message to decrypt
|
||||
/// - Parameter iv: Initialisation vector
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public func AESDecrypt(key:[UInt8], message:[UInt8], iv:[UInt8]) -> [UInt8] {
|
||||
var fixedKey = key
|
||||
if key.count == 16 {
|
||||
fixedKey += key[0..<8]
|
||||
}
|
||||
|
||||
let data = Data(message)
|
||||
let dataLength = message.count
|
||||
|
||||
let cryptLen = data.count + kCCBlockSizeAES128
|
||||
var cryptData = Data(count: cryptLen)
|
||||
|
||||
let keyLength = size_t(key.count)
|
||||
let operation: CCOperation = UInt32(kCCDecrypt)
|
||||
let algorithm: CCAlgorithm = UInt32(kCCAlgorithmAES)
|
||||
let options: CCOptions = UInt32(0)
|
||||
|
||||
var numBytesEncrypted = 0
|
||||
|
||||
let cryptStatus = fixedKey.withUnsafeBytes {keyBytes in
|
||||
message.withUnsafeBytes{ dataBytes in
|
||||
cryptData.withUnsafeMutableBytes{ cryptBytes in
|
||||
CCCrypt(operation,
|
||||
algorithm,
|
||||
options,
|
||||
keyBytes.baseAddress,
|
||||
keyLength,
|
||||
iv,
|
||||
dataBytes.baseAddress,
|
||||
dataLength,
|
||||
cryptBytes.bindMemory(to: UInt8.self).baseAddress,
|
||||
cryptLen,
|
||||
&numBytesEncrypted)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cryptStatus == kCCSuccess {
|
||||
cryptData.count = Int(numBytesEncrypted)
|
||||
|
||||
return [UInt8](cryptData)
|
||||
} else {
|
||||
Log.error("AES Decrypt Error: \(cryptStatus)")
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/// Decrypts a message using AES/ECB/NOPADDING with a specified key and initialisation vector
|
||||
/// - Parameter key: Key use to decrypt
|
||||
/// - Parameter message: Message to decrypt
|
||||
/// - Parameter iv: Initialisation vector
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public func AESECBEncrypt(key:[UInt8], message:[UInt8]) -> [UInt8] {
|
||||
|
||||
let dataLength = message.count
|
||||
|
||||
let cryptLen = message.count + kCCBlockSizeAES128
|
||||
var cryptData = Data(count: cryptLen)
|
||||
|
||||
let keyLength = size_t(key.count)
|
||||
let operation: CCOperation = CCOperation(kCCEncrypt)
|
||||
let algorithm: CCAlgorithm = CCAlgorithm(kCCAlgorithmAES)
|
||||
let options: CCOptions = CCOptions(kCCOptionECBMode)
|
||||
|
||||
var numBytesEncrypted = 0
|
||||
|
||||
let cryptStatus = key.withUnsafeBytes {keyBytes in
|
||||
message.withUnsafeBytes{ dataBytes in
|
||||
cryptData.withUnsafeMutableBytes{ cryptBytes in
|
||||
|
||||
CCCrypt(operation,
|
||||
algorithm,
|
||||
options,
|
||||
keyBytes.baseAddress,
|
||||
keyLength,
|
||||
nil,
|
||||
dataBytes.baseAddress,
|
||||
dataLength,
|
||||
cryptBytes.bindMemory(to: UInt8.self).baseAddress,
|
||||
cryptLen,
|
||||
&numBytesEncrypted)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cryptStatus == kCCSuccess {
|
||||
cryptData.count = Int(numBytesEncrypted)
|
||||
|
||||
return [UInt8](cryptData)
|
||||
} else {
|
||||
Log.error("AESECBEncrypt Error: \(cryptStatus)")
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/// Encrypts a message using DES3 with a specified key and initialisation vector
|
||||
/// - Parameter key: Key use to encrypt
|
||||
/// - Parameter message: Message to encrypt
|
||||
/// - Parameter iv: Initialisation vector
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public func tripleDESEncrypt(key:[UInt8], message:[UInt8], iv:[UInt8]) -> [UInt8] {
|
||||
// Fix key data - if length is 16 then take the first 98 bytes and append them to the end to make 24 bytes
|
||||
var fixedKey = key
|
||||
if key.count == 16 {
|
||||
fixedKey += key[0..<8]
|
||||
}
|
||||
|
||||
let dataLength = message.count
|
||||
|
||||
let cryptLen = message.count + kCCBlockSize3DES
|
||||
var cryptData = Data(count: cryptLen)
|
||||
|
||||
let keyLength = size_t(kCCKeySize3DES)
|
||||
let operation: CCOperation = UInt32(kCCEncrypt)
|
||||
let algorithm: CCAlgorithm = UInt32(kCCAlgorithm3DES)
|
||||
let options: CCOptions = UInt32(0)
|
||||
|
||||
var numBytesEncrypted = 0
|
||||
|
||||
let cryptStatus = fixedKey.withUnsafeBytes {keyBytes in
|
||||
message.withUnsafeBytes{ dataBytes in
|
||||
iv.withUnsafeBytes{ ivBytes in
|
||||
cryptData.withUnsafeMutableBytes{ cryptBytes in
|
||||
CCCrypt(operation,
|
||||
algorithm,
|
||||
options,
|
||||
keyBytes.baseAddress,
|
||||
keyLength,
|
||||
ivBytes.baseAddress,
|
||||
dataBytes.baseAddress,
|
||||
dataLength,
|
||||
cryptBytes.bindMemory(to: UInt8.self).baseAddress,
|
||||
cryptLen,
|
||||
&numBytesEncrypted)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cryptStatus == kCCSuccess {
|
||||
cryptData.count = Int(numBytesEncrypted)
|
||||
|
||||
return [UInt8](cryptData)
|
||||
} else {
|
||||
Log.error("Error: \(cryptStatus)")
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/// Decrypts a message using DES3 with a specified key and initialisation vector
|
||||
/// - Parameter key: Key use to decrypt
|
||||
/// - Parameter message: Message to decrypt
|
||||
/// - Parameter iv: Initialisation vector
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public func tripleDESDecrypt(key:[UInt8], message:[UInt8], iv:[UInt8]) -> [UInt8] {
|
||||
var fixedKey = key
|
||||
if key.count == 16 {
|
||||
fixedKey += key[0..<8]
|
||||
}
|
||||
|
||||
let data = Data(message)
|
||||
let dataLength = message.count
|
||||
|
||||
let cryptLen = data.count + kCCBlockSize3DES
|
||||
var cryptData = Data(count: cryptLen)
|
||||
|
||||
let keyLength = size_t(kCCKeySize3DES)
|
||||
let operation: CCOperation = UInt32(kCCDecrypt)
|
||||
let algorithm: CCAlgorithm = UInt32(kCCAlgorithm3DES)
|
||||
let options: CCOptions = UInt32(0)
|
||||
|
||||
var numBytesEncrypted = 0
|
||||
|
||||
let cryptStatus = fixedKey.withUnsafeBytes {keyBytes in
|
||||
message.withUnsafeBytes{ dataBytes in
|
||||
cryptData.withUnsafeMutableBytes{ cryptBytes in
|
||||
CCCrypt(operation,
|
||||
algorithm,
|
||||
options,
|
||||
keyBytes.baseAddress,
|
||||
keyLength,
|
||||
iv,
|
||||
dataBytes.baseAddress,
|
||||
dataLength,
|
||||
cryptBytes.bindMemory(to: UInt8.self).baseAddress,
|
||||
cryptLen,
|
||||
&numBytesEncrypted)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cryptStatus == kCCSuccess {
|
||||
cryptData.count = Int(numBytesEncrypted)
|
||||
|
||||
return [UInt8](cryptData)
|
||||
} else {
|
||||
Log.error("Error: \(cryptStatus)")
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
/// Encrypts a message using DES with a specified key and initialisation vector
|
||||
/// - Parameter key: Key use to encrypt
|
||||
/// - Parameter message: Message to encrypt
|
||||
/// - Parameter iv: Initialisation vector
|
||||
/// - Parameter options: Encryption options to use
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public func DESEncrypt(key:[UInt8], message:[UInt8], iv:[UInt8], options:UInt32 = 0) -> [UInt8] {
|
||||
|
||||
let dataLength = message.count
|
||||
|
||||
let cryptLen = message.count + kCCBlockSizeDES
|
||||
var cryptData = Data(count: cryptLen)
|
||||
|
||||
let keyLength = size_t(kCCKeySizeDES)
|
||||
let operation: CCOperation = UInt32(kCCEncrypt)
|
||||
let algorithm: CCAlgorithm = UInt32(kCCAlgorithmDES)
|
||||
let options: CCOptions = options
|
||||
|
||||
var numBytesEncrypted = 0
|
||||
|
||||
let cryptStatus = key.withUnsafeBytes {keyBytes in
|
||||
message.withUnsafeBytes{ dataBytes in
|
||||
iv.withUnsafeBytes{ ivBytes in
|
||||
cryptData.withUnsafeMutableBytes{ cryptBytes in
|
||||
CCCrypt(operation,
|
||||
algorithm,
|
||||
options,
|
||||
keyBytes.baseAddress,
|
||||
keyLength,
|
||||
ivBytes.baseAddress,
|
||||
dataBytes.baseAddress,
|
||||
dataLength,
|
||||
cryptBytes.bindMemory(to: UInt8.self).baseAddress,
|
||||
cryptLen,
|
||||
&numBytesEncrypted)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cryptStatus == kCCSuccess {
|
||||
cryptData.count = Int(numBytesEncrypted)
|
||||
|
||||
return [UInt8](cryptData)
|
||||
} else {
|
||||
Log.error("Error: \(cryptStatus)")
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/// Decrypts a message using DES with a specified key and initialisation vector
|
||||
/// - Parameter key: Key use to decrypt
|
||||
/// - Parameter message: Message to decrypt
|
||||
/// - Parameter iv: Initialisation vector
|
||||
/// - Parameter options: Decryption options to use
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public func DESDecrypt(key:[UInt8], message:[UInt8], iv:[UInt8], options:UInt32 = 0) -> [UInt8] {
|
||||
|
||||
let dataLength = message.count
|
||||
|
||||
let cryptLen = message.count + kCCBlockSizeDES
|
||||
var cryptData = Data(count: cryptLen)
|
||||
|
||||
let keyLength = size_t(kCCKeySizeDES)
|
||||
let operation: CCOperation = UInt32(kCCDecrypt)
|
||||
let algorithm: CCAlgorithm = UInt32(kCCAlgorithmDES)
|
||||
let options: CCOptions = options
|
||||
|
||||
var numBytesEncrypted = 0
|
||||
|
||||
let cryptStatus = key.withUnsafeBytes {keyBytes in
|
||||
message.withUnsafeBytes{ dataBytes in
|
||||
iv.withUnsafeBytes{ ivBytes in
|
||||
cryptData.withUnsafeMutableBytes{ cryptBytes in
|
||||
CCCrypt(operation,
|
||||
algorithm,
|
||||
options,
|
||||
keyBytes.baseAddress,
|
||||
keyLength,
|
||||
nil,
|
||||
dataBytes.baseAddress,
|
||||
dataLength,
|
||||
cryptBytes.bindMemory(to: UInt8.self).baseAddress,
|
||||
cryptLen,
|
||||
&numBytesEncrypted)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cryptStatus == kCCSuccess {
|
||||
cryptData.count = Int(numBytesEncrypted)
|
||||
|
||||
return [UInt8](cryptData)
|
||||
} else {
|
||||
Log.error("Error: \(cryptStatus)")
|
||||
}
|
||||
return []
|
||||
}
|
||||
6
app/ios/AwesomeProject/AppDelegate.h
Normal file
6
app/ios/AwesomeProject/AppDelegate.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#import <RCTAppDelegate.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : RCTAppDelegate
|
||||
|
||||
@end
|
||||
26
app/ios/AwesomeProject/AppDelegate.mm
Normal file
26
app/ios/AwesomeProject/AppDelegate.mm
Normal file
@@ -0,0 +1,26 @@
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
self.moduleName = @"AwesomeProject";
|
||||
// You can add your custom initial props in the dictionary below.
|
||||
// They will be passed down to the ViewController used by React Native.
|
||||
self.initialProps = @{};
|
||||
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
||||
{
|
||||
#if DEBUG
|
||||
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
|
||||
#else
|
||||
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
10
app/ios/AwesomeProject/AwesomeProject.entitlements
Normal file
10
app/ios/AwesomeProject/AwesomeProject.entitlements
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.nfc.readersession.formats</key>
|
||||
<array>
|
||||
<string>TAG</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,62 @@
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class ActiveAuthenticationInfo : SecurityInfo {
|
||||
|
||||
var oid : String
|
||||
var version : Int
|
||||
var signatureAlgorithmOID : String?
|
||||
|
||||
static func checkRequiredIdentifier(_ oid : String) -> Bool {
|
||||
return ID_AA_OID == oid
|
||||
}
|
||||
|
||||
init(oid: String, version: Int, signatureAlgorithmOID: String? = nil) {
|
||||
self.oid = oid
|
||||
self.version = version
|
||||
self.signatureAlgorithmOID = signatureAlgorithmOID
|
||||
}
|
||||
|
||||
public override func getObjectIdentifier() -> String {
|
||||
return oid
|
||||
}
|
||||
|
||||
public override func getProtocolOIDString() -> String {
|
||||
return ActiveAuthenticationInfo.toProtocolOIDString(oid:oid)
|
||||
}
|
||||
|
||||
public func getSignatureAlgorithmOIDString() -> String? {
|
||||
return ActiveAuthenticationInfo.toSignatureAlgorithmOIDString(oid: signatureAlgorithmOID)
|
||||
}
|
||||
|
||||
private static func toProtocolOIDString(oid : String) -> String {
|
||||
if ID_AA_OID == oid {
|
||||
return "id-AA"
|
||||
}
|
||||
|
||||
return oid
|
||||
}
|
||||
|
||||
private static func toSignatureAlgorithmOIDString(oid: String?) -> String? {
|
||||
if (ECDSA_PLAIN_SHA1_OID == oid) {
|
||||
return "ecdsa-plain-SHA1";
|
||||
}
|
||||
if (ECDSA_PLAIN_SHA224_OID == oid) {
|
||||
return "ecdsa-plain-SHA224";
|
||||
}
|
||||
if (ECDSA_PLAIN_SHA256_OID == oid) {
|
||||
return "ecdsa-plain-SHA256";
|
||||
}
|
||||
if (ECDSA_PLAIN_SHA384_OID == oid) {
|
||||
return "ecdsa-plain-SHA384";
|
||||
}
|
||||
if (ECDSA_PLAIN_SHA512_OID == oid) {
|
||||
return "ecdsa-plain-SHA512";
|
||||
}
|
||||
if (ECDSA_PLAIN_RIPEMD160_OID == oid) {
|
||||
return "ecdsa-plain-RIPEMD160";
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
59
app/ios/AwesomeProject/DataGroups/COM.swift
Normal file
59
app/ios/AwesomeProject/DataGroups/COM.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class COM : DataGroup {
|
||||
public private(set) var version : String = "Unknown"
|
||||
public private(set) var unicodeVersion : String = "Unknown"
|
||||
public private(set) var dataGroupsPresent : [String] = []
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
datagroupType = .COM
|
||||
}
|
||||
|
||||
override func parse(_ data: [UInt8]) throws {
|
||||
var tag = try getNextTag()
|
||||
if tag != 0x5F01 {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
|
||||
// Version is 4 bytes (ascii) - AABB
|
||||
// AA is major number, BB is minor number
|
||||
// e.g. 48 49 48 55 -> 01 07 -> 1.7
|
||||
var versionBytes = try getNextValue()
|
||||
if versionBytes.count == 4 {
|
||||
let aa = Int( String(cString: Array(versionBytes[0..<2] + [0]) )) ?? -1
|
||||
let bb = Int( String(cString: Array(versionBytes[2...] + [0])) ) ?? -1
|
||||
if aa != -1 && bb != -1 {
|
||||
version = "\(aa).\(bb)"
|
||||
}
|
||||
}
|
||||
tag = try getNextTag()
|
||||
if tag != 0x5F36 {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
|
||||
versionBytes = try getNextValue()
|
||||
if versionBytes.count == 6 {
|
||||
let aa = Int( String(cString: Array(versionBytes[0..<2] + [0])) ) ?? -1
|
||||
let bb = Int( String(cString: Array(versionBytes[2..<4] + [0])) ) ?? -1
|
||||
let cc = Int( String(cString: Array(versionBytes[4...]) + [0]) ) ?? -1
|
||||
if aa != -1 && bb != -1 && cc != -1 {
|
||||
unicodeVersion = "\(aa).\(bb).\(cc)"
|
||||
}
|
||||
}
|
||||
|
||||
tag = try getNextTag()
|
||||
if tag != 0x5C {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
|
||||
let vals = try getNextValue()
|
||||
for v in vals {
|
||||
if let index = DataGroupParser.tags.firstIndex(of: v) {
|
||||
dataGroupsPresent.append( DataGroupParser.dataGroupNames[index] )
|
||||
}
|
||||
}
|
||||
Log.debug( "DG Found - \(dataGroupsPresent)" )
|
||||
}
|
||||
}
|
||||
31
app/ios/AwesomeProject/DataGroups/CardAccess.swift
Normal file
31
app/ios/AwesomeProject/DataGroups/CardAccess.swift
Normal file
@@ -0,0 +1,31 @@
|
||||
import Foundation
|
||||
|
||||
// SecurityInfos ::= SET of SecurityInfo
|
||||
// SecurityInfo ::= SEQUENCE {
|
||||
// protocol OBJECT IDENTIFIER,
|
||||
// requiredData ANY DEFINED BY protocol,
|
||||
// optionalData ANY DEFINED BY protocol OPTIONAL
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class CardAccess {
|
||||
private var asn1 : ASN1Item!
|
||||
public private(set) var securityInfos : [SecurityInfo] = [SecurityInfo]()
|
||||
|
||||
var paceInfo : PACEInfo? {
|
||||
get {
|
||||
return (securityInfos.filter { ($0 as? PACEInfo) != nil }).first as? PACEInfo
|
||||
}
|
||||
}
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
let p = SimpleASN1DumpParser()
|
||||
asn1 = try p.parse(data: Data(data))
|
||||
|
||||
// Bit of a hack at the moment - passing in the body - if we had a decent ASN1 parser then this would be better! ;)
|
||||
for i in 0 ..< asn1.getNumberOfChildren() {
|
||||
if let child = asn1.getChild(i),
|
||||
let secInfo = SecurityInfo.getInstance( object:child, body : data ) {
|
||||
securityInfos.append(secInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
app/ios/AwesomeProject/DataGroups/ChipAuthenticationInfo.swift
Normal file
128
app/ios/AwesomeProject/DataGroups/ChipAuthenticationInfo.swift
Normal file
@@ -0,0 +1,128 @@
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class ChipAuthenticationInfo : SecurityInfo {
|
||||
|
||||
var oid : String
|
||||
var version : Int
|
||||
var keyId : Int?
|
||||
|
||||
static func checkRequiredIdentifier(_ oid : String) -> Bool {
|
||||
return ID_CA_DH_3DES_CBC_CBC_OID == oid
|
||||
|| ID_CA_ECDH_3DES_CBC_CBC_OID == oid
|
||||
|| ID_CA_DH_AES_CBC_CMAC_128_OID == oid
|
||||
|| ID_CA_DH_AES_CBC_CMAC_192_OID == oid
|
||||
|| ID_CA_DH_AES_CBC_CMAC_256_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_128_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_192_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_256_OID == oid
|
||||
}
|
||||
|
||||
init(oid: String, version: Int, keyId: Int? = nil) {
|
||||
self.oid = oid
|
||||
self.version = version
|
||||
self.keyId = keyId
|
||||
}
|
||||
|
||||
public override func getObjectIdentifier() -> String {
|
||||
return oid
|
||||
}
|
||||
|
||||
public override func getProtocolOIDString() -> String {
|
||||
return ChipAuthenticationInfo.toProtocolOIDString(oid:oid)
|
||||
}
|
||||
|
||||
// The keyid refers to a specific key if there are multiple otherwise if not set, only one key is present so set to 0
|
||||
public func getKeyId() -> Int {
|
||||
return keyId ?? 0
|
||||
}
|
||||
|
||||
/// Returns the key agreement algorithm - DH or ECDH for the given Chip Authentication oid
|
||||
/// - Parameter oid: the object identifier
|
||||
/// - Returns: key agreement algorithm
|
||||
/// - Throws: InvalidDataPassed error if invalid oid specified
|
||||
public static func toKeyAgreementAlgorithm( oid : String ) throws -> String {
|
||||
if ID_CA_DH_3DES_CBC_CBC_OID == oid
|
||||
|| ID_CA_DH_AES_CBC_CMAC_128_OID == oid
|
||||
|| ID_CA_DH_AES_CBC_CMAC_192_OID == oid
|
||||
|| ID_CA_DH_AES_CBC_CMAC_256_OID == oid {
|
||||
return "DH";
|
||||
} else if ID_CA_ECDH_3DES_CBC_CBC_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_128_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_192_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_256_OID == oid {
|
||||
return "ECDH";
|
||||
}
|
||||
|
||||
throw NFCPassportReaderError.InvalidDataPassed( "Unable to lookup key agreement algorithm - invalid oid" )
|
||||
}
|
||||
|
||||
/// Returns the cipher algorithm - DESede or AES for the given Chip Authentication oid
|
||||
/// - Parameter oid: the object identifier
|
||||
/// - Returns: the cipher algorithm type
|
||||
/// - Throws: InvalidDataPassed error if invalid oid specified
|
||||
public static func toCipherAlgorithm( oid : String ) throws -> String {
|
||||
if ID_CA_DH_3DES_CBC_CBC_OID == oid
|
||||
|| ID_CA_ECDH_3DES_CBC_CBC_OID == oid {
|
||||
return "DESede";
|
||||
} else if ID_CA_DH_AES_CBC_CMAC_128_OID == oid
|
||||
|| ID_CA_DH_AES_CBC_CMAC_192_OID == oid
|
||||
|| ID_CA_DH_AES_CBC_CMAC_256_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_128_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_192_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_256_OID == oid {
|
||||
return "AES";
|
||||
}
|
||||
throw NFCPassportReaderError.InvalidDataPassed( "Unable to lookup cipher algorithm - invalid oid" )
|
||||
}
|
||||
|
||||
/// Returns the key length in bits (128, 192, or 256) for the given Chip Authentication oid
|
||||
/// - Parameter oid: the object identifier
|
||||
/// - Returns: the key length in bits
|
||||
/// - Throws: InvalidDataPassed error if invalid oid specified
|
||||
public static func toKeyLength( oid : String ) throws -> Int {
|
||||
if ID_CA_DH_3DES_CBC_CBC_OID == oid
|
||||
|| ID_CA_ECDH_3DES_CBC_CBC_OID == oid
|
||||
|| ID_CA_DH_AES_CBC_CMAC_128_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_128_OID == oid {
|
||||
return 128;
|
||||
} else if ID_CA_DH_AES_CBC_CMAC_192_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_192_OID == oid {
|
||||
return 192;
|
||||
} else if ID_CA_DH_AES_CBC_CMAC_256_OID == oid
|
||||
|| ID_CA_ECDH_AES_CBC_CMAC_256_OID == oid {
|
||||
return 256;
|
||||
}
|
||||
|
||||
throw NFCPassportReaderError.InvalidDataPassed( "Unable to get key length - invalid oid" )
|
||||
}
|
||||
|
||||
private static func toProtocolOIDString(oid : String) -> String {
|
||||
if ID_CA_DH_3DES_CBC_CBC_OID == oid {
|
||||
return "id-CA-DH-3DES-CBC-CBC"
|
||||
}
|
||||
if ID_CA_DH_AES_CBC_CMAC_128_OID == oid {
|
||||
return "id-CA-DH-AES-CBC-CMAC-128"
|
||||
}
|
||||
if ID_CA_DH_AES_CBC_CMAC_192_OID == oid {
|
||||
return "id-CA-DH-AES-CBC-CMAC-192"
|
||||
}
|
||||
if ID_CA_DH_AES_CBC_CMAC_256_OID == oid {
|
||||
return "id-CA-DH-AES-CBC-CMAC-256"
|
||||
}
|
||||
if ID_CA_ECDH_3DES_CBC_CBC_OID == oid {
|
||||
return "id-CA-ECDH-3DES-CBC-CBC"
|
||||
}
|
||||
if ID_CA_ECDH_AES_CBC_CMAC_128_OID == oid {
|
||||
return "id-CA-ECDH-AES-CBC-CMAC-128"
|
||||
}
|
||||
if ID_CA_ECDH_AES_CBC_CMAC_192_OID == oid {
|
||||
return "id-CA-ECDH-AES-CBC-CMAC-192"
|
||||
}
|
||||
if ID_CA_ECDH_AES_CBC_CMAC_256_OID == oid {
|
||||
return "id-CA-ECDH-AES-CBC-CMAC-256"
|
||||
}
|
||||
|
||||
return oid
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class ChipAuthenticationPublicKeyInfo : SecurityInfo {
|
||||
var oid : String
|
||||
var pubKey : OpaquePointer
|
||||
var keyId : Int?
|
||||
|
||||
|
||||
static func checkRequiredIdentifier(_ oid : String) -> Bool {
|
||||
return ID_PK_DH_OID == oid
|
||||
|| ID_PK_ECDH_OID == oid
|
||||
}
|
||||
|
||||
init(oid:String, pubKey:OpaquePointer, keyId: Int? = nil) {
|
||||
self.oid = oid
|
||||
self.pubKey = pubKey
|
||||
self.keyId = keyId
|
||||
}
|
||||
|
||||
public override func getObjectIdentifier() -> String {
|
||||
return oid
|
||||
}
|
||||
|
||||
public override func getProtocolOIDString() -> String {
|
||||
return ChipAuthenticationPublicKeyInfo.toProtocolOIDString(oid:oid)
|
||||
}
|
||||
|
||||
// The keyid refers to a specific key if there are multiple otherwise if not set, only one key is present so set to 0
|
||||
public func getKeyId() -> Int {
|
||||
return keyId ?? 0
|
||||
}
|
||||
|
||||
|
||||
private static func toProtocolOIDString(oid : String) -> String {
|
||||
if ID_PK_DH_OID == oid {
|
||||
return "id-PK-DH"
|
||||
}
|
||||
if ID_PK_ECDH_OID == oid {
|
||||
return "id-PK-ECDH"
|
||||
}
|
||||
|
||||
return oid
|
||||
}
|
||||
|
||||
}
|
||||
78
app/ios/AwesomeProject/DataGroups/DataGroup.swift
Normal file
78
app/ios/AwesomeProject/DataGroups/DataGroup.swift
Normal file
@@ -0,0 +1,78 @@
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class DataGroup {
|
||||
public var datagroupType : DataGroupId = .Unknown
|
||||
|
||||
/// Body contains the actual data
|
||||
public private(set) var body : [UInt8] = []
|
||||
|
||||
/// Data contains the whole DataGroup data (as that is what the hash is calculated from
|
||||
public private(set) var data : [UInt8] = []
|
||||
|
||||
var pos = 0
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
self.data = data
|
||||
|
||||
// Skip the first byte which is the header byte
|
||||
pos = 1
|
||||
let _ = try getNextLength()
|
||||
self.body = [UInt8](data[pos...])
|
||||
|
||||
try parse(data)
|
||||
}
|
||||
|
||||
func parse( _ data:[UInt8] ) throws {
|
||||
}
|
||||
|
||||
func getNextTag() throws -> Int {
|
||||
var tag = 0
|
||||
|
||||
// Fix for some passports that may have invalid data - ensure that we do have data!
|
||||
guard data.count > pos else {
|
||||
throw NFCPassportReaderError.TagNotValid
|
||||
}
|
||||
|
||||
if binToHex(data[pos]) & 0x0F == 0x0F {
|
||||
tag = Int(binToHex(data[pos..<pos+2]))
|
||||
pos += 2
|
||||
} else {
|
||||
tag = Int(data[pos])
|
||||
pos += 1
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func getNextLength() throws -> Int {
|
||||
let end = pos+4 < data.count ? pos+4 : data.count
|
||||
let (len, lenOffset) = try asn1Length([UInt8](data[pos..<end]))
|
||||
pos += lenOffset
|
||||
return len
|
||||
}
|
||||
|
||||
func getNextValue() throws -> [UInt8] {
|
||||
let length = try getNextLength()
|
||||
let value = [UInt8](data[pos ..< pos+length])
|
||||
pos += length
|
||||
return value
|
||||
}
|
||||
|
||||
public func hash( _ hashAlgorythm: String ) -> [UInt8] {
|
||||
var ret : [UInt8] = []
|
||||
if hashAlgorythm == "SHA1" {
|
||||
ret = calcSHA1Hash(self.data)
|
||||
} else if hashAlgorythm == "SHA224" {
|
||||
ret = calcSHA224Hash(self.data)
|
||||
} else if hashAlgorythm == "SHA256" {
|
||||
ret = calcSHA256Hash(self.data)
|
||||
} else if hashAlgorythm == "SHA384" {
|
||||
ret = calcSHA384Hash(self.data)
|
||||
} else if hashAlgorythm == "SHA512" {
|
||||
ret = calcSHA512Hash(self.data)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
}
|
||||
108
app/ios/AwesomeProject/DataGroups/DataGroup1.swift
Normal file
108
app/ios/AwesomeProject/DataGroups/DataGroup1.swift
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public enum DocTypeEnum: String {
|
||||
case TD1
|
||||
case TD2
|
||||
case OTHER
|
||||
|
||||
var desc: String {
|
||||
get {
|
||||
return self.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class DataGroup1 : DataGroup {
|
||||
|
||||
public private(set) var elements : [String:String] = [:]
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
datagroupType = .DG1
|
||||
}
|
||||
|
||||
override func parse(_ data: [UInt8]) throws {
|
||||
let tag = try getNextTag()
|
||||
if tag != 0x5F1F {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
let body = try getNextValue()
|
||||
let docType = getMRZType(length:body.count)
|
||||
|
||||
switch docType {
|
||||
case .TD1:
|
||||
self.parseTd1(body)
|
||||
case .TD2:
|
||||
self.parseTd2(body)
|
||||
default:
|
||||
self.parseOther(body)
|
||||
}
|
||||
|
||||
// Store MRZ data
|
||||
elements["5F1F"] = String(bytes: body, encoding:.utf8)
|
||||
}
|
||||
|
||||
func parseTd1(_ data : [UInt8]) {
|
||||
elements["5F03"] = String(bytes: data[0..<2], encoding:.utf8)
|
||||
elements["5F28"] = String( bytes:data[2..<5], encoding:.utf8)
|
||||
elements["5A"] = String( bytes:data[5..<14], encoding:.utf8)
|
||||
elements["5F04"] = String( bytes:data[14..<15], encoding:.utf8)
|
||||
elements["53"] = (String( bytes:data[15..<30], encoding:.utf8) ?? "") +
|
||||
(String( bytes:data[48..<59], encoding:.utf8) ?? "")
|
||||
elements["5F57"] = String( bytes:data[30..<36], encoding:.utf8)
|
||||
elements["5F05"] = String( bytes:data[36..<37], encoding:.utf8)
|
||||
elements["5F35"] = String( bytes:data[37..<38], encoding:.utf8)
|
||||
elements["59"] = String( bytes:data[38..<44], encoding:.utf8)
|
||||
elements["5F06"] = String( bytes:data[44..<45], encoding:.utf8)
|
||||
elements["5F2C"] = String( bytes:data[45..<48], encoding:.utf8)
|
||||
elements["5F07"] = String( bytes:data[59..<60], encoding:.utf8)
|
||||
elements["5B"] = String( bytes:data[60...], encoding:.utf8)
|
||||
}
|
||||
|
||||
func parseTd2(_ data : [UInt8]) {
|
||||
elements["5F03"] = String( bytes:data[0..<2], encoding:.utf8)
|
||||
elements["5F28"] = String( bytes:data[2..<5], encoding:.utf8)
|
||||
elements["5B"] = String( bytes:data[5..<36], encoding:.utf8)
|
||||
elements["5A"] = String( bytes:data[36..<45], encoding:.utf8)
|
||||
elements["5F04"] = String( bytes:data[45..<46], encoding:.utf8)
|
||||
elements["5F2C"] = String( bytes:data[46..<49], encoding:.utf8)
|
||||
elements["5F57"] = String( bytes:data[49..<55], encoding:.utf8)
|
||||
elements["5F05"] = String( bytes:data[55..<56], encoding:.utf8)
|
||||
elements["5F35"] = String( bytes:data[56..<57], encoding:.utf8)
|
||||
elements["59"] = String( bytes:data[57..<63], encoding:.utf8)
|
||||
elements["5F06"] = String( bytes:data[63..<64], encoding:.utf8)
|
||||
elements["53"] = String( bytes:data[64..<71], encoding:.utf8)
|
||||
elements["5F07"] = String( bytes:data[71..<72], encoding:.utf8)
|
||||
}
|
||||
|
||||
func parseOther(_ data : [UInt8]) {
|
||||
elements["5F03"] = String( bytes:data[0..<2], encoding:.utf8)
|
||||
elements["5F28"] = String( bytes:data[2..<5], encoding:.utf8)
|
||||
elements["5B"] = String( bytes:data[5..<44], encoding:.utf8)
|
||||
elements["5A"] = String( bytes:data[44..<53], encoding:.utf8)
|
||||
elements["5F04"] = String( bytes:[data[53]], encoding:.utf8)
|
||||
elements["5F2C"] = String( bytes:data[54..<57], encoding:.utf8)
|
||||
elements["5F57"] = String( bytes:data[57..<63], encoding:.utf8)
|
||||
elements["5F05"] = String( bytes:[data[63]], encoding:.utf8)
|
||||
elements["5F35"] = String( bytes:[data[64]], encoding:.utf8)
|
||||
elements["59"] = String( bytes:data[65..<71], encoding:.utf8)
|
||||
elements["5F06"] = String( bytes:[data[71]], encoding:.utf8)
|
||||
elements["53"] = String( bytes:data[72..<86], encoding:.utf8)
|
||||
elements["5F02"] = String( bytes:[data[86]], encoding:.utf8)
|
||||
elements["5F07"] = String( bytes:[data[87]], encoding:.utf8)
|
||||
}
|
||||
|
||||
private func getMRZType(length: Int) -> DocTypeEnum {
|
||||
if length == 0x5A {
|
||||
return .TD1
|
||||
}
|
||||
if length == 0x48 {
|
||||
return .TD2
|
||||
}
|
||||
return .OTHER
|
||||
}
|
||||
|
||||
}
|
||||
62
app/ios/AwesomeProject/DataGroups/DataGroup11.swift
Normal file
62
app/ios/AwesomeProject/DataGroups/DataGroup11.swift
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class DataGroup11 : DataGroup {
|
||||
|
||||
public private(set) var fullName : String?
|
||||
public private(set) var personalNumber : String?
|
||||
public private(set) var dateOfBirth : String?
|
||||
public private(set) var placeOfBirth : String?
|
||||
public private(set) var address : String?
|
||||
public private(set) var telephone : String?
|
||||
public private(set) var profession : String?
|
||||
public private(set) var title : String?
|
||||
public private(set) var personalSummary : String?
|
||||
public private(set) var proofOfCitizenship : String?
|
||||
public private(set) var tdNumbers : String?
|
||||
public private(set) var custodyInfo : String?
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
datagroupType = .DG11
|
||||
}
|
||||
|
||||
override func parse(_ data: [UInt8]) throws {
|
||||
var tag = try getNextTag()
|
||||
if tag != 0x5C {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
_ = try getNextValue()
|
||||
|
||||
repeat {
|
||||
tag = try getNextTag()
|
||||
let val = try String( bytes:getNextValue(), encoding:.utf8)
|
||||
if tag == 0x5F0E {
|
||||
fullName = val
|
||||
} else if tag == 0x5F10 {
|
||||
personalNumber = val
|
||||
} else if tag == 0x5F11 {
|
||||
placeOfBirth = val
|
||||
} else if tag == 0x5F2B {
|
||||
dateOfBirth = val
|
||||
} else if tag == 0x5F42 {
|
||||
address = val
|
||||
} else if tag == 0x5F12 {
|
||||
telephone = val
|
||||
} else if tag == 0x5F13 {
|
||||
profession = val
|
||||
} else if tag == 0x5F14 {
|
||||
title = val
|
||||
} else if tag == 0x5F15 {
|
||||
personalSummary = val
|
||||
} else if tag == 0x5F16 {
|
||||
proofOfCitizenship = val
|
||||
} else if tag == 0x5F17 {
|
||||
tdNumbers = val
|
||||
} else if tag == 0x5F18 {
|
||||
custodyInfo = val
|
||||
}
|
||||
} while pos < data.count
|
||||
}
|
||||
}
|
||||
72
app/ios/AwesomeProject/DataGroups/DataGroup12.swift
Normal file
72
app/ios/AwesomeProject/DataGroups/DataGroup12.swift
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class DataGroup12 : DataGroup {
|
||||
|
||||
public private(set) var issuingAuthority : String?
|
||||
public private(set) var dateOfIssue : String?
|
||||
public private(set) var otherPersonsDetails : String?
|
||||
public private(set) var endorsementsOrObservations : String?
|
||||
public private(set) var taxOrExitRequirements : String?
|
||||
public private(set) var frontImage : [UInt8]?
|
||||
public private(set) var rearImage : [UInt8]?
|
||||
public private(set) var personalizationTime : String?
|
||||
public private(set) var personalizationDeviceSerialNr : String?
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
datagroupType = .DG12
|
||||
}
|
||||
|
||||
override func parse(_ data: [UInt8]) throws {
|
||||
var tag = try getNextTag()
|
||||
if tag != 0x5C {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
|
||||
// Skip the taglist - ideally we would check this but...
|
||||
let _ = try getNextValue()
|
||||
|
||||
repeat {
|
||||
tag = try getNextTag()
|
||||
let val = try getNextValue()
|
||||
|
||||
if tag == 0x5F19 {
|
||||
issuingAuthority = String( bytes:val, encoding:.utf8)
|
||||
} else if tag == 0x5F26 {
|
||||
dateOfIssue = parseDateOfIssue(value: val)
|
||||
} else if tag == 0xA0 {
|
||||
// Not yet handled
|
||||
} else if tag == 0x5F1B {
|
||||
endorsementsOrObservations = String( bytes:val, encoding:.utf8)
|
||||
} else if tag == 0x5F1C {
|
||||
taxOrExitRequirements = String( bytes:val, encoding:.utf8)
|
||||
} else if tag == 0x5F1D {
|
||||
frontImage = val
|
||||
} else if tag == 0x5F1E {
|
||||
rearImage = val
|
||||
} else if tag == 0x5F55 {
|
||||
personalizationTime = String( bytes:val, encoding:.utf8)
|
||||
} else if tag == 0x5F56 {
|
||||
personalizationDeviceSerialNr = String( bytes:val, encoding:.utf8)
|
||||
}
|
||||
} while pos < data.count
|
||||
}
|
||||
|
||||
private func parseDateOfIssue(value: [UInt8]) -> String? {
|
||||
if value.count == 4 {
|
||||
return decodeBCD(value: value)
|
||||
} else {
|
||||
return decodeASCII(value: value)
|
||||
}
|
||||
}
|
||||
|
||||
private func decodeASCII(value: [UInt8]) -> String? {
|
||||
return String(bytes:value, encoding:.utf8)
|
||||
}
|
||||
|
||||
private func decodeBCD(value: [UInt8]) -> String? {
|
||||
value.map({ String(format: "%02X", $0) }).joined()
|
||||
}
|
||||
}
|
||||
31
app/ios/AwesomeProject/DataGroups/DataGroup14.swift
Normal file
31
app/ios/AwesomeProject/DataGroups/DataGroup14.swift
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// SecurityInfos ::= SET of SecurityInfo
|
||||
// SecurityInfo ::= SEQUENCE {
|
||||
// protocol OBJECT IDENTIFIER,
|
||||
// requiredData ANY DEFINED BY protocol,
|
||||
// optionalData ANY DEFINED BY protocol OPTIONAL
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class DataGroup14 : DataGroup {
|
||||
private var asn1 : ASN1Item!
|
||||
public private(set) var securityInfos : [SecurityInfo] = [SecurityInfo]()
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
datagroupType = .DG14
|
||||
}
|
||||
|
||||
override func parse(_ data: [UInt8]) throws {
|
||||
let p = SimpleASN1DumpParser()
|
||||
asn1 = try p.parse(data: Data(body))
|
||||
|
||||
// Bit of a hack at the moment - passing in the body - if we had a decent ASN1 parser then this would be better! ;)
|
||||
for i in 0 ..< asn1.getNumberOfChildren() {
|
||||
if let child = asn1.getChild(i),
|
||||
let secInfo = SecurityInfo.getInstance( object:child, body : body ) {
|
||||
securityInfos.append(secInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/ios/AwesomeProject/DataGroups/DataGroup15.swift
Normal file
40
app/ios/AwesomeProject/DataGroups/DataGroup15.swift
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
import Foundation
|
||||
import OpenSSL
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class DataGroup15 : DataGroup {
|
||||
|
||||
public private(set) var rsaPublicKey : OpaquePointer?
|
||||
public private(set) var ecdsaPublicKey : OpaquePointer?
|
||||
|
||||
deinit {
|
||||
if ( ecdsaPublicKey != nil ) {
|
||||
EVP_PKEY_free(ecdsaPublicKey);
|
||||
}
|
||||
if ( rsaPublicKey != nil ) {
|
||||
EVP_PKEY_free(rsaPublicKey);
|
||||
}
|
||||
}
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
datagroupType = .DG15
|
||||
}
|
||||
|
||||
|
||||
override func parse(_ data: [UInt8]) throws {
|
||||
|
||||
// the public key can either be in EC (elliptic curve) or RSA format
|
||||
// Try ec first and if this fails try RSA
|
||||
// Note - this will be improved in a later version to read the ASN1 body to
|
||||
// check the actual type
|
||||
if let key = try? OpenSSLUtils.readECPublicKey( data:body ) {
|
||||
// NOTE We are responsible for freeing the key!
|
||||
ecdsaPublicKey = key
|
||||
} else if let key = try? OpenSSLUtils.readRSAPublicKey( data:body ) {
|
||||
|
||||
rsaPublicKey = key
|
||||
}
|
||||
}
|
||||
}
|
||||
164
app/ios/AwesomeProject/DataGroups/DataGroup2.swift
Normal file
164
app/ios/AwesomeProject/DataGroups/DataGroup2.swift
Normal file
@@ -0,0 +1,164 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if !os(macOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class DataGroup2 : DataGroup {
|
||||
public private(set) var nrImages : Int = 0
|
||||
public private(set) var versionNumber : Int = 0
|
||||
public private(set) var lengthOfRecord : Int = 0
|
||||
public private(set) var numberOfFacialImages : Int = 0
|
||||
public private(set) var facialRecordDataLength : Int = 0
|
||||
public private(set) var nrFeaturePoints : Int = 0
|
||||
public private(set) var gender : Int = 0
|
||||
public private(set) var eyeColor : Int = 0
|
||||
public private(set) var hairColor : Int = 0
|
||||
public private(set) var featureMask : Int = 0
|
||||
public private(set) var expression : Int = 0
|
||||
public private(set) var poseAngle : Int = 0
|
||||
public private(set) var poseAngleUncertainty : Int = 0
|
||||
public private(set) var faceImageType : Int = 0
|
||||
public private(set) var imageDataType : Int = 0
|
||||
public private(set) var imageWidth : Int = 0
|
||||
public private(set) var imageHeight : Int = 0
|
||||
public private(set) var imageColorSpace : Int = 0
|
||||
public private(set) var sourceType : Int = 0
|
||||
public private(set) var deviceType : Int = 0
|
||||
public private(set) var quality : Int = 0
|
||||
public private(set) var imageData : [UInt8] = []
|
||||
|
||||
|
||||
#if !os(macOS)
|
||||
func getImage() -> UIImage? {
|
||||
if imageData.count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
let image = UIImage(data:Data(imageData) )
|
||||
return image
|
||||
}
|
||||
#endif
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
datagroupType = .DG2
|
||||
}
|
||||
|
||||
override func parse(_ data: [UInt8]) throws {
|
||||
var tag = try getNextTag()
|
||||
if tag != 0x7F61 {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
_ = try getNextLength()
|
||||
|
||||
// Tag should be 0x02
|
||||
tag = try getNextTag()
|
||||
if tag != 0x02 {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
nrImages = try Int(getNextValue()[0])
|
||||
|
||||
// Next tag is 0x7F60
|
||||
tag = try getNextTag()
|
||||
if tag != 0x7F60 {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
_ = try getNextLength()
|
||||
|
||||
// Next tag is 0xA1 (Biometric Header Template) - don't care about this
|
||||
tag = try getNextTag()
|
||||
if tag != 0xA1 {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
_ = try getNextValue()
|
||||
|
||||
// Now we get to the good stuff - next tag is either 5F2E or 7F2E
|
||||
tag = try getNextTag()
|
||||
if tag != 0x5F2E && tag != 0x7F2E {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
let value = try getNextValue()
|
||||
|
||||
try parseISO19794_5( data:value )
|
||||
}
|
||||
|
||||
func parseISO19794_5( data : [UInt8] ) throws {
|
||||
// Validate header - 'F', 'A' 'C' 0x00 - 0x46414300
|
||||
if data[0] != 0x46 && data[1] != 0x41 && data[2] != 0x43 && data[3] != 0x00 {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
|
||||
var offset = 4
|
||||
versionNumber = binToInt(data[offset..<offset+4])
|
||||
offset += 4
|
||||
lengthOfRecord = binToInt(data[offset..<offset+4])
|
||||
offset += 4
|
||||
numberOfFacialImages = binToInt(data[offset..<offset+2])
|
||||
offset += 2
|
||||
|
||||
facialRecordDataLength = binToInt(data[offset..<offset+4])
|
||||
offset += 4
|
||||
nrFeaturePoints = binToInt(data[offset..<offset+2])
|
||||
offset += 2
|
||||
gender = binToInt(data[offset..<offset+1])
|
||||
offset += 1
|
||||
eyeColor = binToInt(data[offset..<offset+1])
|
||||
offset += 1
|
||||
hairColor = binToInt(data[offset..<offset+1])
|
||||
offset += 1
|
||||
featureMask = binToInt(data[offset..<offset+3])
|
||||
offset += 3
|
||||
expression = binToInt(data[offset..<offset+2])
|
||||
offset += 2
|
||||
poseAngle = binToInt(data[offset..<offset+3])
|
||||
offset += 3
|
||||
poseAngleUncertainty = binToInt(data[offset..<offset+3])
|
||||
offset += 3
|
||||
|
||||
// Features (not handled). There shouldn't be any but if for some reason there were,
|
||||
// then we are going to skip over them
|
||||
// The Feature block is 8 bytes
|
||||
offset += nrFeaturePoints * 8
|
||||
|
||||
faceImageType = binToInt(data[offset..<offset+1])
|
||||
offset += 1
|
||||
imageDataType = binToInt(data[offset..<offset+1])
|
||||
offset += 1
|
||||
imageWidth = binToInt(data[offset..<offset+2])
|
||||
offset += 2
|
||||
imageHeight = binToInt(data[offset..<offset+2])
|
||||
offset += 2
|
||||
imageColorSpace = binToInt(data[offset..<offset+1])
|
||||
offset += 1
|
||||
sourceType = binToInt(data[offset..<offset+1])
|
||||
offset += 1
|
||||
deviceType = binToInt(data[offset..<offset+2])
|
||||
offset += 2
|
||||
quality = binToInt(data[offset..<offset+2])
|
||||
offset += 2
|
||||
|
||||
|
||||
// Make sure that the image data at least has a valid header
|
||||
// Either JPG or JPEG2000
|
||||
|
||||
let jpegHeader : [UInt8] = [0xff,0xd8,0xff,0xe0,0x00,0x10,0x4a,0x46,0x49,0x46]
|
||||
let jpeg2000BitmapHeader : [UInt8] = [0x00,0x00,0x00,0x0c,0x6a,0x50,0x20,0x20,0x0d,0x0a]
|
||||
let jpeg2000CodestreamBitmapHeader : [UInt8] = [0xff,0x4f,0xff,0x51]
|
||||
|
||||
if data.count < offset+jpeg2000CodestreamBitmapHeader.count {
|
||||
throw NFCPassportReaderError.UnknownImageFormat
|
||||
}
|
||||
|
||||
|
||||
if [UInt8](data[offset..<offset+jpegHeader.count]) != jpegHeader &&
|
||||
[UInt8](data[offset..<offset+jpeg2000BitmapHeader.count]) != jpeg2000BitmapHeader &&
|
||||
[UInt8](data[offset..<offset+jpeg2000CodestreamBitmapHeader.count]) != jpeg2000CodestreamBitmapHeader {
|
||||
throw NFCPassportReaderError.UnknownImageFormat
|
||||
}
|
||||
|
||||
imageData = [UInt8](data[offset...])
|
||||
}
|
||||
}
|
||||
44
app/ios/AwesomeProject/DataGroups/DataGroup7.swift
Normal file
44
app/ios/AwesomeProject/DataGroups/DataGroup7.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
#if !os(macOS)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class DataGroup7 : DataGroup {
|
||||
|
||||
public private(set) var imageData : [UInt8] = []
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
datagroupType = .DG7
|
||||
}
|
||||
|
||||
#if !os(macOS)
|
||||
func getImage() -> UIImage? {
|
||||
if imageData.count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
let image = UIImage(data:Data(imageData) )
|
||||
return image
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
override func parse(_ data: [UInt8]) throws {
|
||||
var tag = try getNextTag()
|
||||
if tag != 0x02 {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
_ = try getNextValue()
|
||||
|
||||
tag = try getNextTag()
|
||||
if tag != 0x5F43 {
|
||||
throw NFCPassportReaderError.InvalidResponse
|
||||
}
|
||||
|
||||
imageData = try getNextValue()
|
||||
}
|
||||
}
|
||||
98
app/ios/AwesomeProject/DataGroups/DataGroupId.swift
Normal file
98
app/ios/AwesomeProject/DataGroups/DataGroupId.swift
Normal file
@@ -0,0 +1,98 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public enum DataGroupId : Int, CaseIterable {
|
||||
case COM = 0x60
|
||||
case DG1 = 0x61
|
||||
case DG2 = 0x75
|
||||
case DG3 = 0x63
|
||||
case DG4 = 0x76
|
||||
case DG5 = 0x65
|
||||
case DG6 = 0x66
|
||||
case DG7 = 0x67
|
||||
case DG8 = 0x68
|
||||
case DG9 = 0x69
|
||||
case DG10 = 0x6A
|
||||
case DG11 = 0x6B
|
||||
case DG12 = 0x6C
|
||||
case DG13 = 0x6D
|
||||
case DG14 = 0x6E
|
||||
case DG15 = 0x6F
|
||||
case DG16 = 0x70
|
||||
case SOD = 0x77
|
||||
case Unknown = 0x00
|
||||
|
||||
public func getName() -> String {
|
||||
switch( self ) {
|
||||
case .COM: return "COM"
|
||||
case .DG1: return "DG1"
|
||||
case .DG2: return "DG2"
|
||||
case .DG3: return "DG3"
|
||||
case .DG4: return "DG4"
|
||||
case .DG5: return "DG5"
|
||||
case .DG6: return "DG6"
|
||||
case .DG7: return "DG7"
|
||||
case .DG8: return "DG8"
|
||||
case .DG9: return "DG9"
|
||||
case .DG10: return "DG10"
|
||||
case .DG11: return "DG11"
|
||||
case .DG12: return "DG12"
|
||||
case .DG13: return "DG13"
|
||||
case .DG14: return "DG14"
|
||||
case .DG15: return "DG15"
|
||||
case .DG16: return "DG16"
|
||||
case .SOD: return "SOD"
|
||||
case .Unknown: return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
static public func getIDFromName( name: String ) -> DataGroupId {
|
||||
switch( name ) {
|
||||
case "COM": return .COM
|
||||
case "DG1": return .DG1
|
||||
case "DG2": return .DG2
|
||||
case "DG3": return .DG3
|
||||
case "DG4": return .DG4
|
||||
case "DG5": return .DG5
|
||||
case "DG6": return .DG6
|
||||
case "DG7": return .DG7
|
||||
case "DG8": return .DG8
|
||||
case "DG9": return .DG9
|
||||
case "DG10": return .DG10
|
||||
case "DG11": return .DG11
|
||||
case "DG12": return .DG12
|
||||
case "DG13": return .DG13
|
||||
case "DG14": return .DG14
|
||||
case "DG15": return .DG15
|
||||
case "DG16": return .DG16
|
||||
case "SOD": return .SOD
|
||||
default: return .Unknown
|
||||
}
|
||||
}
|
||||
|
||||
func getFileIDTag() -> [UInt8]? {
|
||||
switch( self ) {
|
||||
case .COM: return [0x01,0x1E]
|
||||
case .DG1: return [0x01,0x01]
|
||||
case .DG2: return [0x01,0x02]
|
||||
case .DG3: return [0x01,0x03]
|
||||
case .DG4: return [0x01,0x04]
|
||||
case .DG5: return [0x01,0x05]
|
||||
case .DG6: return [0x01,0x06]
|
||||
case .DG7: return [0x01,0x07]
|
||||
case .DG8: return [0x01,0x08]
|
||||
case .DG9: return [0x01,0x09]
|
||||
case .DG10: return [0x01,0x0A]
|
||||
case .DG11: return [0x01,0x0B]
|
||||
case .DG12: return [0x01,0x0C]
|
||||
case .DG13: return [0x01,0x0D]
|
||||
case .DG14: return [0x01,0x0E]
|
||||
case .DG15: return [0x01,0x0F]
|
||||
case .DG16: return [0x01,0x10]
|
||||
case .SOD: return [0x01,0x1D]
|
||||
case .Unknown: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
app/ios/AwesomeProject/DataGroups/NotImplementedDG.swift
Normal file
11
app/ios/AwesomeProject/DataGroups/NotImplementedDG.swift
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class NotImplementedDG : DataGroup {
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
datagroupType = .Unknown
|
||||
}
|
||||
}
|
||||
|
||||
397
app/ios/AwesomeProject/DataGroups/PACEInfo.swift
Normal file
397
app/ios/AwesomeProject/DataGroups/PACEInfo.swift
Normal file
@@ -0,0 +1,397 @@
|
||||
|
||||
import Foundation
|
||||
import OpenSSL
|
||||
|
||||
public enum PACEMappingType {
|
||||
case GM // Generic Mapping
|
||||
case IM // Integrated Mapping
|
||||
case CAM // Chip Authentication Mapping
|
||||
}
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class PACEInfo : SecurityInfo {
|
||||
|
||||
// Standardized domain parameters. Based on Table 6.
|
||||
public static let PARAM_ID_GFP_1024_160 = 0
|
||||
public static let PARAM_ID_GFP_2048_224 = 1
|
||||
public static let PARAM_ID_GFP_2048_256 = 2
|
||||
public static let PARAM_ID_ECP_NIST_P192_R1 = 8
|
||||
public static let PARAM_ID_ECP_BRAINPOOL_P192_R1 = 9
|
||||
public static let PARAM_ID_ECP_NIST_P224_R1 = 10
|
||||
public static let PARAM_ID_ECP_BRAINPOOL_P224_R1 = 11
|
||||
public static let PARAM_ID_ECP_NIST_P256_R1 = 12
|
||||
public static let PARAM_ID_ECP_BRAINPOOL_P256_R1 = 13
|
||||
public static let PARAM_ID_ECP_BRAINPOOL_P320_R1 = 14
|
||||
public static let PARAM_ID_ECP_NIST_P384_R1 = 15
|
||||
public static let PARAM_ID_ECP_BRAINPOOL_P384_R1 = 16
|
||||
public static let PARAM_ID_ECP_BRAINPOOL_P512_R1 = 17
|
||||
public static let PARAM_ID_ECP_NIST_P521_R1 = 18
|
||||
|
||||
static let allowedIdentifiers = [
|
||||
ID_PACE_DH_GM_3DES_CBC_CBC,
|
||||
ID_PACE_DH_GM_AES_CBC_CMAC_128,
|
||||
ID_PACE_DH_GM_AES_CBC_CMAC_192,
|
||||
ID_PACE_DH_GM_AES_CBC_CMAC_256,
|
||||
ID_PACE_DH_IM_3DES_CBC_CBC,
|
||||
ID_PACE_DH_IM_AES_CBC_CMAC_128,
|
||||
ID_PACE_DH_IM_AES_CBC_CMAC_192,
|
||||
ID_PACE_DH_IM_AES_CBC_CMAC_256,
|
||||
ID_PACE_ECDH_GM_3DES_CBC_CBC,
|
||||
ID_PACE_ECDH_GM_AES_CBC_CMAC_128,
|
||||
ID_PACE_ECDH_GM_AES_CBC_CMAC_192,
|
||||
ID_PACE_ECDH_GM_AES_CBC_CMAC_256,
|
||||
ID_PACE_ECDH_IM_3DES_CBC_CBC,
|
||||
ID_PACE_ECDH_IM_AES_CBC_CMAC_128,
|
||||
ID_PACE_ECDH_IM_AES_CBC_CMAC_192,
|
||||
ID_PACE_ECDH_IM_AES_CBC_CMAC_256,
|
||||
ID_PACE_ECDH_CAM_AES_CBC_CMAC_128,
|
||||
ID_PACE_ECDH_CAM_AES_CBC_CMAC_192,
|
||||
ID_PACE_ECDH_CAM_AES_CBC_CMAC_256]
|
||||
|
||||
var oid : String
|
||||
var version : Int
|
||||
var parameterId : Int?
|
||||
|
||||
static func checkRequiredIdentifier(_ oid : String) -> Bool {
|
||||
return allowedIdentifiers.contains( oid )
|
||||
}
|
||||
|
||||
init(oid: String, version: Int, parameterId: Int?) {
|
||||
self.oid = oid
|
||||
self.version = version
|
||||
self.parameterId = parameterId
|
||||
}
|
||||
|
||||
public override func getObjectIdentifier() -> String {
|
||||
return oid
|
||||
}
|
||||
|
||||
public override func getProtocolOIDString() -> String {
|
||||
return PACEInfo.toProtocolOIDString(oid:oid)
|
||||
}
|
||||
|
||||
public func getVersion() -> Int {
|
||||
return version
|
||||
}
|
||||
|
||||
public func getParameterId() -> Int? {
|
||||
return parameterId
|
||||
}
|
||||
|
||||
public func getParameterSpec() throws -> Int32 {
|
||||
return try PACEInfo.getParameterSpec(stdDomainParam: self.parameterId ?? -1 )
|
||||
}
|
||||
|
||||
public func getMappingType() throws -> PACEMappingType {
|
||||
return try PACEInfo.toMappingType(oid: oid); // Either GM, CAM, or IM.
|
||||
}
|
||||
|
||||
public func getKeyAgreementAlgorithm() throws -> String {
|
||||
return try PACEInfo.toKeyAgreementAlgorithm(oid: oid); // Either DH or ECDH.
|
||||
}
|
||||
|
||||
public func getCipherAlgorithm() throws -> String {
|
||||
return try PACEInfo.toCipherAlgorithm(oid: oid); // Either DESede or AES.
|
||||
}
|
||||
|
||||
public func getDigestAlgorithm() throws -> String {
|
||||
return try PACEInfo.toDigestAlgorithm(oid: oid); // Either SHA-1 or SHA-256.
|
||||
}
|
||||
|
||||
public func getKeyLength() throws -> Int {
|
||||
return try PACEInfo.toKeyLength(oid: oid); // Of the enc cipher. Either 128, 192, or 256.
|
||||
}
|
||||
|
||||
/// Caller is required to free the returned EVP_PKEY value
|
||||
public func createMappingKey( ) throws -> OpaquePointer {
|
||||
// This will get freed later
|
||||
let mappingKey : OpaquePointer = EVP_PKEY_new()
|
||||
|
||||
switch try getKeyAgreementAlgorithm() {
|
||||
case "DH":
|
||||
Log.debug( "Generating DH mapping keys")
|
||||
//The EVP_PKEY_CTX_set_dh_rfc5114() and EVP_PKEY_CTX_set_dhx_rfc5114() macros are synonymous. They set the DH parameters to the values defined in RFC5114. The rfc5114 parameter must be 1, 2 or 3 corresponding to RFC5114 sections 2.1, 2.2 and 2.3. or 0 to clear the stored value. This macro can be called during parameter generation. The ctx must have a key type of EVP_PKEY_DHX. The rfc5114 parameter and the nid parameter are mutually exclusive.
|
||||
var dhKey : OpaquePointer? = nil
|
||||
switch try getParameterSpec() {
|
||||
case 0:
|
||||
Log.verbose( "Using DH_get_1024_160" )
|
||||
dhKey = DH_get_1024_160()
|
||||
case 1:
|
||||
Log.verbose( "Using DH_get_2048_224" )
|
||||
dhKey = DH_get_2048_224()
|
||||
case 2:
|
||||
Log.verbose( "Using DH_get_2048_256" )
|
||||
dhKey = DH_get_2048_256()
|
||||
default:
|
||||
// Error
|
||||
break
|
||||
}
|
||||
guard dhKey != nil else {
|
||||
throw NFCPassportReaderError.InvalidDataPassed("Unable to create DH mapping key")
|
||||
}
|
||||
defer{ DH_free( dhKey ) }
|
||||
|
||||
DH_generate_key(dhKey)
|
||||
EVP_PKEY_set1_DH(mappingKey, dhKey)
|
||||
|
||||
case "ECDH":
|
||||
let parameterSpec = try getParameterSpec()
|
||||
Log.debug( "Generating ECDH mapping keys from parameterSpec - \(parameterSpec)")
|
||||
guard let ecKey = EC_KEY_new_by_curve_name(parameterSpec) else {
|
||||
throw NFCPassportReaderError.InvalidDataPassed("Unable to create EC mapping key")
|
||||
}
|
||||
defer{ EC_KEY_free( ecKey ) }
|
||||
|
||||
EC_KEY_generate_key(ecKey)
|
||||
EVP_PKEY_set1_EC_KEY(mappingKey, ecKey)
|
||||
default:
|
||||
throw NFCPassportReaderError.InvalidDataPassed("Unsupported agreement algorithm")
|
||||
}
|
||||
|
||||
return mappingKey
|
||||
}
|
||||
|
||||
public static func getParameterSpec(stdDomainParam : Int) throws -> Int32 {
|
||||
switch (stdDomainParam) {
|
||||
case PARAM_ID_GFP_1024_160:
|
||||
return 0 // "rfc5114_1024_160";
|
||||
case PARAM_ID_GFP_2048_224:
|
||||
return 1 // "rfc5114_2048_224";
|
||||
case PARAM_ID_GFP_2048_256:
|
||||
return 2 // "rfc5114_2048_256";
|
||||
case PARAM_ID_ECP_NIST_P192_R1:
|
||||
return NID_X9_62_prime192v1 // "secp192r1";
|
||||
case PARAM_ID_ECP_NIST_P224_R1:
|
||||
return NID_secp224r1 // "secp224r1";
|
||||
case PARAM_ID_ECP_NIST_P256_R1:
|
||||
return NID_X9_62_prime256v1 //"secp256r1";
|
||||
case PARAM_ID_ECP_NIST_P384_R1:
|
||||
return NID_secp384r1 // "secp384r1";
|
||||
case PARAM_ID_ECP_BRAINPOOL_P192_R1:
|
||||
return NID_brainpoolP192r1 //"brainpoolp192r1";
|
||||
case PARAM_ID_ECP_BRAINPOOL_P224_R1:
|
||||
return NID_brainpoolP224r1 // "brainpoolp224r1";
|
||||
case PARAM_ID_ECP_BRAINPOOL_P256_R1:
|
||||
return NID_brainpoolP256r1 // "brainpoolp256r1";
|
||||
case PARAM_ID_ECP_BRAINPOOL_P320_R1:
|
||||
return NID_brainpoolP320r1 //"brainpoolp320r1";
|
||||
case PARAM_ID_ECP_BRAINPOOL_P384_R1:
|
||||
return NID_brainpoolP384r1 //"brainpoolp384r1";
|
||||
case PARAM_ID_ECP_BRAINPOOL_P512_R1:
|
||||
return NID_brainpoolP512r1 //"";
|
||||
case PARAM_ID_ECP_NIST_P521_R1:
|
||||
return NID_secp521r1 //"secp224r1";
|
||||
default:
|
||||
throw NFCPassportReaderError.InvalidDataPassed( "Unable to lookup p arameterSpec - invalid oid" )
|
||||
}
|
||||
}
|
||||
|
||||
public static func toMappingType( oid : String ) throws -> PACEMappingType {
|
||||
if ID_PACE_DH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_256 == oid {
|
||||
return PACEMappingType.GM
|
||||
} else if ID_PACE_DH_IM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_IM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_256 == oid {
|
||||
return PACEMappingType.IM
|
||||
} else if ID_PACE_ECDH_CAM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_256 == oid {
|
||||
return PACEMappingType.CAM
|
||||
}
|
||||
|
||||
throw NFCPassportReaderError.InvalidDataPassed( "Unable to lookup mapping type - invalid oid" )
|
||||
}
|
||||
|
||||
|
||||
/// Returns the key agreement algorithm - DH or ECDH for the given Chip Authentication oid
|
||||
/// - Parameter oid: the object identifier
|
||||
/// - Returns: key agreement algorithm
|
||||
/// - Throws: InvalidDataPassed error if invalid oid specified
|
||||
public static func toKeyAgreementAlgorithm( oid : String ) throws -> String {
|
||||
if ID_PACE_DH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_DH_IM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_256 == oid {
|
||||
return "DH"
|
||||
} else if ID_PACE_ECDH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_IM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_256 == oid {
|
||||
return "ECDH"
|
||||
}
|
||||
throw NFCPassportReaderError.InvalidDataPassed( "Unable to lookup key agreement algorithm - invalid oid" )
|
||||
}
|
||||
|
||||
/// Returns the cipher algorithm - DESede or AES for the given Chip Authentication oid
|
||||
/// - Parameter oid: the object identifier
|
||||
/// - Returns: the cipher algorithm type
|
||||
/// - Throws: InvalidDataPassed error if invalid oid specified
|
||||
public static func toCipherAlgorithm( oid : String ) throws -> String {
|
||||
if ID_PACE_DH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_DH_IM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_IM_3DES_CBC_CBC == oid {
|
||||
return "DESede"
|
||||
} else if ID_PACE_DH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_256 == oid {
|
||||
return "AES"
|
||||
}
|
||||
throw NFCPassportReaderError.InvalidDataPassed( "Unable to lookup cipher algorithm - invalid oid" )
|
||||
}
|
||||
|
||||
public static func toDigestAlgorithm( oid : String ) throws -> String {
|
||||
if ID_PACE_DH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_DH_IM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_IM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_128 == oid {
|
||||
return "SHA-1"
|
||||
} else if ID_PACE_DH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_256 == oid {
|
||||
return "SHA-256"
|
||||
}
|
||||
|
||||
throw NFCPassportReaderError.InvalidDataPassed( "Unable to lookup digest algorithm - invalid oid" )
|
||||
|
||||
}
|
||||
/// Returns the key length in bits (128, 192, or 256) for the given Chip Authentication oid
|
||||
/// - Parameter oid: the object identifier
|
||||
/// - Returns: the key length in bits
|
||||
/// - Throws: InvalidDataPassed error if invalid oid specified
|
||||
public static func toKeyLength( oid : String ) throws -> Int {
|
||||
if ID_PACE_DH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_DH_IM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_GM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_ECDH_IM_3DES_CBC_CBC == oid
|
||||
|| ID_PACE_DH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_128 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_128 == oid {
|
||||
return 128
|
||||
} else if ID_PACE_DH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_192 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_192 == oid {
|
||||
return 192
|
||||
} else if ID_PACE_DH_GM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_DH_IM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_GM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_IM_AES_CBC_CMAC_256 == oid
|
||||
|| ID_PACE_ECDH_CAM_AES_CBC_CMAC_256 == oid {
|
||||
return 256
|
||||
}
|
||||
throw NFCPassportReaderError.InvalidDataPassed( "Unable to get key length - invalid oid" )
|
||||
}
|
||||
|
||||
private static func toProtocolOIDString(oid : String) -> String {
|
||||
if ID_PACE_DH_GM_3DES_CBC_CBC == oid {
|
||||
return "id-PACE-DH-GM-3DES-CBC-CBC"
|
||||
}
|
||||
if ID_PACE_DH_GM_AES_CBC_CMAC_128 == oid {
|
||||
return "id-PACE-DH-GM-AES-CBC-CMAC-128"
|
||||
}
|
||||
if ID_PACE_DH_GM_AES_CBC_CMAC_192 == oid {
|
||||
return "id-PACE-DH-GM-AES-CBC-CMAC-192"
|
||||
}
|
||||
if ID_PACE_DH_GM_AES_CBC_CMAC_256 == oid {
|
||||
return "id-PACE-DH-GM-AES-CBC-CMAC-256"
|
||||
}
|
||||
if ID_PACE_DH_IM_3DES_CBC_CBC == oid {
|
||||
return "id-PACE-DH-IM-3DES-CBC-CBC"
|
||||
}
|
||||
if ID_PACE_DH_IM_AES_CBC_CMAC_128 == oid {
|
||||
return "id-PACE-DH-IM-AES-CBC-CMAC-128"
|
||||
}
|
||||
if ID_PACE_DH_IM_AES_CBC_CMAC_192 == oid {
|
||||
return "id-PACE-DH-IM-AES-CBC-CMAC-192"
|
||||
}
|
||||
if ID_PACE_DH_IM_AES_CBC_CMAC_256 == oid {
|
||||
return "id-PACE_DH-IM-AES-CBC-CMAC-256"
|
||||
}
|
||||
if ID_PACE_ECDH_GM_3DES_CBC_CBC == oid {
|
||||
return "id-PACE-ECDH-GM-3DES-CBC-CBC"
|
||||
}
|
||||
if ID_PACE_ECDH_GM_AES_CBC_CMAC_128 == oid {
|
||||
return "id-PACE-ECDH-GM-AES-CBC-CMAC-128"
|
||||
}
|
||||
if ID_PACE_ECDH_GM_AES_CBC_CMAC_192 == oid {
|
||||
return "id-PACE-ECDH-GM-AES-CBC-CMAC-192"
|
||||
}
|
||||
if ID_PACE_ECDH_GM_AES_CBC_CMAC_256 == oid {
|
||||
return "id-PACE-ECDH-GM-AES-CBC-CMAC-256"
|
||||
}
|
||||
if ID_PACE_ECDH_IM_3DES_CBC_CBC == oid {
|
||||
return "id-PACE-ECDH-IM_3DES-CBC-CBC"
|
||||
}
|
||||
if ID_PACE_ECDH_IM_AES_CBC_CMAC_128 == oid {
|
||||
return "id-PACE-ECDH-IM-AES-CBC-CMAC-128"
|
||||
}
|
||||
if ID_PACE_ECDH_IM_AES_CBC_CMAC_192 == oid {
|
||||
return "id-PACE-ECDH-IM-AES-CBC-CMAC-192"
|
||||
}
|
||||
if ID_PACE_ECDH_IM_AES_CBC_CMAC_256 == oid {
|
||||
return "id-PACE-ECDH-IM-AES-CBC-CMAC-256"
|
||||
}
|
||||
if ID_PACE_ECDH_CAM_AES_CBC_CMAC_128 == oid {
|
||||
return "id-PACE-ECDH-CAM-AES-CBC-CMAC-128"
|
||||
}
|
||||
if ID_PACE_ECDH_CAM_AES_CBC_CMAC_192 == oid {
|
||||
return "id-PACE-ECDH-CAM-AES-CBC-CMAC-192"
|
||||
}
|
||||
if ID_PACE_ECDH_CAM_AES_CBC_CMAC_256 == oid {
|
||||
return "id-PACE-ECDH-CAM-AES-CBC-CMAC-256"
|
||||
}
|
||||
|
||||
return oid
|
||||
}
|
||||
}
|
||||
234
app/ios/AwesomeProject/DataGroups/SOD.swift
Normal file
234
app/ios/AwesomeProject/DataGroups/SOD.swift
Normal file
@@ -0,0 +1,234 @@
|
||||
|
||||
import Foundation
|
||||
import OpenSSL
|
||||
|
||||
|
||||
// Format of SOD: ASN1 - Signed Data (taken from rfc5652 - https://tools.ietf.org/html/rfc5652):
|
||||
// The SOD is a CMS container of type Signed-data
|
||||
//
|
||||
// Note - ideally I'd be using a proper ASN1 parser, however currently there isn't a reliable one for Swift
|
||||
// and I haven't written on (yet?). So for the moment, I'm relying on the output from ASN1Dump and a
|
||||
// simple parser for that
|
||||
//
|
||||
// Sequence
|
||||
// Object ID: signedData
|
||||
// Content: SignedData
|
||||
// SignedData ::= SEQUENCE {
|
||||
// INTEGER version CMSVersion,
|
||||
// SET digestAlgorithms DigestAlgorithmIdentifiers,
|
||||
// SEQUENCE encapContentInfo EncapsulatedContentInfo,
|
||||
// certificates [0] IMPLICIT CertificateSet OPTIONAL,
|
||||
// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
|
||||
// SET signerInfos SignerInfos }
|
||||
//
|
||||
// AlgorithmIdentifier ::= SEQUENCE {
|
||||
// algorithm OBJECT IDENTIFIER,
|
||||
// parameters ANY OPTIONAL
|
||||
// }
|
||||
//
|
||||
// EncapsulatedContentInfo ::= SEQUENCE {
|
||||
// eContentType ContentType,
|
||||
// eContent [0] EXPLICIT OCTET STRING OPTIONAL }
|
||||
//
|
||||
// ContentType ::= OBJECT IDENTIFIER
|
||||
//
|
||||
// SignerInfos ::= SET OF SignerInfo
|
||||
//
|
||||
// SignerInfo ::= SEQUENCE {
|
||||
// version CMSVersion,
|
||||
// sid SignerIdentifier,
|
||||
// digestAlgorithm DigestAlgorithmIdentifier,
|
||||
// signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
|
||||
// signatureAlgorithm SignatureAlgorithmIdentifier,
|
||||
// signature SignatureValue,
|
||||
// unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
|
||||
//
|
||||
// SignerIdentifier ::= CHOICE {
|
||||
// issuerAndSerialNumber IssuerAndSerialNumber,
|
||||
// subjectKeyIdentifier [0] SubjectKeyIdentifier }
|
||||
//
|
||||
// SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
// UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
|
||||
// Attribute ::= SEQUENCE {
|
||||
// attrType OBJECT IDENTIFIER,
|
||||
// attrValues SET OF AttributeValue }
|
||||
// AttributeValue ::= ANY
|
||||
// SignatureValue ::= OCTET STRING
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
class SOD : DataGroup {
|
||||
|
||||
public private(set) var pkcs7CertificateData : [UInt8] = []
|
||||
private var asn1 : ASN1Item!
|
||||
private var pubKey : OpaquePointer?
|
||||
|
||||
required init( _ data : [UInt8] ) throws {
|
||||
try super.init(data)
|
||||
self.pkcs7CertificateData = body
|
||||
datagroupType = .SOD
|
||||
}
|
||||
|
||||
deinit {
|
||||
if ( pubKey != nil ) {
|
||||
EVP_PKEY_free(pubKey);
|
||||
}
|
||||
}
|
||||
|
||||
override func parse(_ data: [UInt8]) throws {
|
||||
let p = SimpleASN1DumpParser()
|
||||
asn1 = try p.parse(data: Data(body))
|
||||
}
|
||||
|
||||
/// Returns the public key from the embedded X509 certificate
|
||||
/// - Returns pointer to the public key
|
||||
func getPublicKey( ) throws -> OpaquePointer {
|
||||
|
||||
if let key = pubKey {
|
||||
return key
|
||||
}
|
||||
|
||||
let certs = try OpenSSLUtils.getX509CertificatesFromPKCS7(pkcs7Der:Data(pkcs7CertificateData))
|
||||
if let key = X509_get_pubkey (certs[0].cert) {
|
||||
pubKey = key
|
||||
return key
|
||||
}
|
||||
|
||||
throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("Unable to get public key")
|
||||
}
|
||||
|
||||
|
||||
/// Extracts the encapsulated content section from a SignedData PKCS7 container (if present)
|
||||
/// - Returns: The encapsulated content from a PKCS7 container if we could read it
|
||||
/// - Throws: Error if we can't find or read the encapsulated content
|
||||
func getEncapsulatedContent() throws -> Data {
|
||||
guard let signedData = asn1.getChild(1)?.getChild(0),
|
||||
let encContent = signedData.getChild(2)?.getChild(1),
|
||||
let content = encContent.getChild(0) else {
|
||||
|
||||
throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("Data in invalid format")
|
||||
}
|
||||
|
||||
var sigData : Data?
|
||||
if content.type.hasPrefix("OCTET STRING" ) {
|
||||
sigData = Data(hexRepToBin( content.value ))
|
||||
}
|
||||
|
||||
guard let ret = sigData else { throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("noDataReturned") }
|
||||
return ret
|
||||
}
|
||||
|
||||
/// Gets the digest algorithm used to hash the encapsulated content in the signed data section (if present)
|
||||
/// - Returns: The digest algorithm used to hash the encapsulated content in the signed data section
|
||||
/// - Throws: Error if we can't find or read the digest algorithm
|
||||
func getEncapsulatedContentDigestAlgorithm() throws -> String {
|
||||
guard let signedData = asn1.getChild(1)?.getChild(0),
|
||||
let digestAlgo = signedData.getChild(1)?.getChild(0)?.getChild(0) else {
|
||||
throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("Data in invalid format")
|
||||
}
|
||||
|
||||
return String(digestAlgo.value)
|
||||
}
|
||||
|
||||
/// Gets the signed attributes section (if present)
|
||||
/// - Returns: the signed attributes section
|
||||
/// - Throws: Error if we can't find or read the signed attributes
|
||||
func getSignedAttributes( ) throws -> Data {
|
||||
|
||||
// Get the SignedAttributes section.
|
||||
guard let signedData = asn1.getChild(1)?.getChild(0),
|
||||
let signerInfo = signedData.getChild(4),
|
||||
let signedAttrs = signerInfo.getChild(0)?.getChild(3) else {
|
||||
|
||||
throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("Data in invalid format")
|
||||
}
|
||||
|
||||
var bytes = [UInt8](self.pkcs7CertificateData[signedAttrs.pos ..< signedAttrs.pos + signedAttrs.headerLen + signedAttrs.length])
|
||||
|
||||
// The first byte will be 0xA0 -> as its a explicit tag for a contextual item which we need to convert
|
||||
// for the hash to calculate correctly
|
||||
// We know that the actual tag is a SET (0x31) - See section 5.4 of https://tools.ietf.org/html/rfc5652
|
||||
// So we need to change this from 0xA0 to 0x31
|
||||
if bytes[0] == 0xA0 {
|
||||
bytes[0] = 0x31
|
||||
}
|
||||
let signedAttribs = Data(bytes)
|
||||
|
||||
return signedAttribs
|
||||
}
|
||||
|
||||
/// Gets the message digest from the signed attributes section (if present)
|
||||
/// - Returns: the message digest
|
||||
/// - Throws: Error if we can't find or read the message digest
|
||||
func getMessageDigestFromSignedAttributes( ) throws -> Data {
|
||||
|
||||
// For the SOD, the SignedAttributes consists of:
|
||||
// A Content type Object (which has the value of the attributes content type)
|
||||
// A messageDigest Object which has the message digest as it value
|
||||
// We want the messageDigest value
|
||||
|
||||
guard let signedData = asn1.getChild(1)?.getChild(0),
|
||||
let signerInfo = signedData.getChild(4),
|
||||
let signedAttrs = signerInfo.getChild(0)?.getChild(3) else {
|
||||
|
||||
throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("Data in invalid format")
|
||||
}
|
||||
|
||||
// Find the messageDigest in the signedAttributes section
|
||||
var sigData : Data?
|
||||
for i in 0 ..< signedAttrs.getNumberOfChildren() {
|
||||
let attrObj = signedAttrs.getChild(i)
|
||||
if attrObj?.getChild(0)?.value == "messageDigest" {
|
||||
if let set = attrObj?.getChild(1),
|
||||
let digestVal = set.getChild(0) {
|
||||
|
||||
if digestVal.type.hasPrefix("OCTET STRING" ) {
|
||||
sigData = Data(hexRepToBin( digestVal.value ) )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let messageDigest = sigData else { throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("No messageDigest Returned") }
|
||||
|
||||
return messageDigest
|
||||
}
|
||||
|
||||
/// Gets the signature data (if present)
|
||||
/// - Returns: the signature
|
||||
/// - Throws: Error if we can't find or read the signature
|
||||
func getSignature( ) throws -> Data {
|
||||
|
||||
guard let signedData = asn1.getChild(1)?.getChild(0),
|
||||
let signerInfo = signedData.getChild(4),
|
||||
let signature = signerInfo.getChild(0)?.getChild(5) else {
|
||||
|
||||
throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("Data in invalid format")
|
||||
}
|
||||
|
||||
var sigData : Data?
|
||||
if signature.type.hasPrefix("OCTET STRING" ) {
|
||||
sigData = Data(hexRepToBin( signature.value ))
|
||||
}
|
||||
|
||||
guard let ret = sigData else { throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("noDataReturned") }
|
||||
return ret
|
||||
}
|
||||
|
||||
/// Gets the signature algorithm used (if present)
|
||||
/// - Returns: the signature algorithm used
|
||||
/// - Throws: Error if we can't find or read the signature algorithm
|
||||
func getSignatureAlgorithm( ) throws -> String {
|
||||
|
||||
guard let signedData = asn1.getChild(1)?.getChild(0),
|
||||
let signerInfo = signedData.getChild(4),
|
||||
let signatureAlgo = signerInfo.getChild(0)?.getChild(4)?.getChild(0) else {
|
||||
|
||||
throw OpenSSLError.UnableToExtractSignedDataFromPKCS7("Data in invalid format")
|
||||
}
|
||||
|
||||
// Vals I've seen are:
|
||||
// sha1WithRSAEncryption => default pkcs1
|
||||
// sha256WithRSAEncryption => default pkcs1
|
||||
// rsassaPss => pss
|
||||
return signatureAlgo.value
|
||||
}
|
||||
}
|
||||
130
app/ios/AwesomeProject/DataGroups/SecurityInfo.swift
Normal file
130
app/ios/AwesomeProject/DataGroups/SecurityInfo.swift
Normal file
@@ -0,0 +1,130 @@
|
||||
|
||||
import Foundation
|
||||
import OpenSSL
|
||||
|
||||
@available(iOS 13, macOS 10.15,*)
|
||||
public class SecurityInfo {
|
||||
// Active Authentication OID
|
||||
static let ID_AA_OID = "2.23.136.1.1.5"
|
||||
|
||||
// Active Authentication Signature Algorithm OIDS
|
||||
// Specified in BSI TR 03111 Section 5.2.1.
|
||||
static let ECDSA_PLAIN_SIGNATURES = "0.4.0.127.0.7.1.1.4.1";
|
||||
static let ECDSA_PLAIN_SHA1_OID = ECDSA_PLAIN_SIGNATURES + ".1"; // 0.4.0.127.0.7.1.1.4.1.1, ecdsa-plain-SHA1
|
||||
static let ECDSA_PLAIN_SHA224_OID = ECDSA_PLAIN_SIGNATURES + ".2"; // 0.4.0.127.0.7.1.1.4.1.2, ecdsa-plain-SHA224
|
||||
static let ECDSA_PLAIN_SHA256_OID = ECDSA_PLAIN_SIGNATURES + ".3"; // 0.4.0.127.0.7.1.1.4.1.3, ecdsa-plain-SHA256
|
||||
static let ECDSA_PLAIN_SHA384_OID = ECDSA_PLAIN_SIGNATURES + ".4"; // 0.4.0.127.0.7.1.1.4.1.4, ecdsa-plain-SHA384
|
||||
static let ECDSA_PLAIN_SHA512_OID = ECDSA_PLAIN_SIGNATURES + ".5"; // 0.4.0.127.0.7.1.1.4.1.5, ecdsa-plain-SHA512
|
||||
static let ECDSA_PLAIN_RIPEMD160_OID = ECDSA_PLAIN_SIGNATURES + ".6"; // 0.4.0.127.0.7.1.1.4.1.6, ecdsa-plain-RIPEMD160
|
||||
|
||||
// Chip Authentication Public Key OIDS
|
||||
static let ID_PK_DH_OID = "0.4.0.127.0.7.2.2.1.1"
|
||||
static let ID_PK_ECDH_OID = "0.4.0.127.0.7.2.2.1.2"
|
||||
|
||||
// Chip Authentication OIDS
|
||||
static let ID_CA_DH_3DES_CBC_CBC_OID = "0.4.0.127.0.7.2.2.3.1.1"
|
||||
static let ID_CA_ECDH_3DES_CBC_CBC_OID = "0.4.0.127.0.7.2.2.3.2.1"
|
||||
static let ID_CA_DH_AES_CBC_CMAC_128_OID = "0.4.0.127.0.7.2.2.3.1.2"
|
||||
static let ID_CA_DH_AES_CBC_CMAC_192_OID = "0.4.0.127.0.7.2.2.3.1.3"
|
||||
static let ID_CA_DH_AES_CBC_CMAC_256_OID = "0.4.0.127.0.7.2.2.3.1.4"
|
||||
static let ID_CA_ECDH_AES_CBC_CMAC_128_OID = "0.4.0.127.0.7.2.2.3.2.2"
|
||||
static let ID_CA_ECDH_AES_CBC_CMAC_192_OID = "0.4.0.127.0.7.2.2.3.2.3"
|
||||
static let ID_CA_ECDH_AES_CBC_CMAC_256_OID = "0.4.0.127.0.7.2.2.3.2.4"
|
||||
|
||||
|
||||
// PACE OIDS
|
||||
static let ID_BSI = "0.4.0.127.0.7"
|
||||
static let ID_PACE = ID_BSI + ".2.2.4"
|
||||
static let ID_PACE_DH_GM = ID_PACE + ".1"
|
||||
static let ID_PACE_DH_GM_3DES_CBC_CBC = ID_PACE_DH_GM + ".1"; // 0.4.0.127.0.7.2.2.4.1.1, id-PACE-DH-GM-3DES-CBC-CBC
|
||||
static let ID_PACE_DH_GM_AES_CBC_CMAC_128 = ID_PACE_DH_GM + ".2"; // 0.4.0.127.0.7.2.2.4.1.2, id-PACE-DH-GM-AES-CBC-CMAC-128
|
||||
static let ID_PACE_DH_GM_AES_CBC_CMAC_192 = ID_PACE_DH_GM + ".3"; // 0.4.0.127.0.7.2.2.4.1.3, id-PACE-DH-GM-AES-CBC-CMAC-192
|
||||
static let ID_PACE_DH_GM_AES_CBC_CMAC_256 = ID_PACE_DH_GM + ".4"; // 0.4.0.127.0.7.2.2.4.1.4, id-PACE-DH-GM-AES-CBC-CMAC-256
|
||||
|
||||
static let ID_PACE_ECDH_GM = ID_PACE + ".2"
|
||||
static let ID_PACE_ECDH_GM_3DES_CBC_CBC = ID_PACE_ECDH_GM + ".1"; // 0.4.0.127.0.7.2.2.4.2.1, id-PACE-ECDH-GM-3DES-CBC-CBC
|
||||
static let ID_PACE_ECDH_GM_AES_CBC_CMAC_128 = ID_PACE_ECDH_GM + ".2"; // 0.4.0.127.0.7.2.2.4.2.2, id-PACE-ECDH-GM-AES-CBC-CMAC-128
|
||||
static let ID_PACE_ECDH_GM_AES_CBC_CMAC_192 = ID_PACE_ECDH_GM + ".3"; // 0.4.0.127.0.7.2.2.4.2.3, id-PACE-ECDH-GM-AES-CBC-CMAC-192
|
||||
static let ID_PACE_ECDH_GM_AES_CBC_CMAC_256 = ID_PACE_ECDH_GM + ".4"; // 0.4.0.127.0.7.2.2.4.2.4, id-PACE-ECDH-GM-AES-CBC-CMAC-256
|
||||
|
||||
static let ID_PACE_DH_IM = ID_PACE + ".3"
|
||||
static let ID_PACE_DH_IM_3DES_CBC_CBC = ID_PACE_DH_IM + ".1"; // 0.4.0.127.0.7.2.2.4.3.1, id-PACE-DH-IM-3DES-CBC-CBC
|
||||
static let ID_PACE_DH_IM_AES_CBC_CMAC_128 = ID_PACE_DH_IM + ".2"; // 0.4.0.127.0.7.2.2.4.3.2, id-PACE-DH-IM-AES-CBC-CMAC-128
|
||||
static let ID_PACE_DH_IM_AES_CBC_CMAC_192 = ID_PACE_DH_IM + ".3"; // 0.4.0.127.0.7.2.2.4.3.3, id-PACE-DH-IM-AES-CBC-CMAC-192
|
||||
static let ID_PACE_DH_IM_AES_CBC_CMAC_256 = ID_PACE_DH_IM + ".4"; // 0.4.0.127.0.7.2.2.4.3.4, id-PACE-DH-IM-AES-CBC-CMAC-256
|
||||
|
||||
static let ID_PACE_ECDH_IM = ID_PACE + ".4"
|
||||
static let ID_PACE_ECDH_IM_3DES_CBC_CBC = ID_PACE_ECDH_IM + ".1"; // 0.4.0.127.0.7.2.2.4.4.1, id-PACE-ECDH-IM-3DES-CBC-CBC
|
||||
static let ID_PACE_ECDH_IM_AES_CBC_CMAC_128 = ID_PACE_ECDH_IM + ".2"; // 0.4.0.127.0.7.2.2.4.4.2, id-PACE-ECDH-IM-AES-CBC-CMAC-128
|
||||
static let ID_PACE_ECDH_IM_AES_CBC_CMAC_192 = ID_PACE_ECDH_IM + ".3"; // 0.4.0.127.0.7.2.2.4.4.3, id-PACE-ECDH-IM-AES-CBC-CMAC-192
|
||||
static let ID_PACE_ECDH_IM_AES_CBC_CMAC_256 = ID_PACE_ECDH_IM + ".4"; // 0.4.0.127.0.7.2.2.4.4.4, id-PACE-ECDH-IM-AES-CBC-CMAC-256
|
||||
|
||||
static let ID_PACE_ECDH_CAM = ID_PACE + ".6"
|
||||
static let ID_PACE_ECDH_CAM_AES_CBC_CMAC_128 = ID_PACE_ECDH_CAM + ".2"; // 0.4.0.127.0.7.2.2.4.6.2, id-PACE-ECDH-CAM-AES-CBC-CMAC-128
|
||||
static let ID_PACE_ECDH_CAM_AES_CBC_CMAC_192 = ID_PACE_ECDH_CAM + ".3"; // 0.4.0.127.0.7.2.2.4.6.3, id-PACE-ECDH-CAM-AES-CBC-CMAC-192
|
||||
static let ID_PACE_ECDH_CAM_AES_CBC_CMAC_256 = ID_PACE_ECDH_CAM + ".4"; // 0.4.0.127.0.7.2.2.4.6.4, id-PACE-ECDH-CAM-AES-CBC-CMAC-256
|
||||
|
||||
public func getObjectIdentifier() -> String {
|
||||
preconditionFailure("This method must be overridden")
|
||||
}
|
||||
|
||||
public func getProtocolOIDString() -> String {
|
||||
preconditionFailure("This method must be overridden")
|
||||
}
|
||||
|
||||
static func getInstance( object : ASN1Item, body: [UInt8] ) -> SecurityInfo? {
|
||||
let oid = object.getChild(0)?.value ?? ""
|
||||
let requiredData = object.getChild(1)!
|
||||
var optionalData : ASN1Item? = nil
|
||||
if (object.getNumberOfChildren() == 3) {
|
||||
optionalData = object.getChild(2)
|
||||
}
|
||||
|
||||
if ChipAuthenticationPublicKeyInfo.checkRequiredIdentifier(oid) {
|
||||
|
||||
let keyData : [UInt8] = [UInt8](body[requiredData.pos ..< requiredData.pos+requiredData.headerLen+requiredData.length])
|
||||
|
||||
var subjectPublicKeyInfo : OpaquePointer? = nil
|
||||
let _ = keyData.withUnsafeBytes { (ptr) in
|
||||
var newPtr = ptr.baseAddress?.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
subjectPublicKeyInfo = d2i_PUBKEY(nil, &newPtr, keyData.count)
|
||||
}
|
||||
|
||||
if let subjectPublicKeyInfo = subjectPublicKeyInfo {
|
||||
|
||||
if optionalData == nil {
|
||||
return ChipAuthenticationPublicKeyInfo(oid:oid, pubKey:subjectPublicKeyInfo);
|
||||
} else {
|
||||
let keyId = Int(optionalData!.value, radix: 16)
|
||||
return ChipAuthenticationPublicKeyInfo(oid:oid, pubKey:subjectPublicKeyInfo, keyId: keyId);
|
||||
}
|
||||
|
||||
}
|
||||
} else if ChipAuthenticationInfo.checkRequiredIdentifier(oid) {
|
||||
let version = Int(requiredData.value) ?? -1
|
||||
if let optionalData = optionalData {
|
||||
let keyId = Int(optionalData.value, radix: 16)
|
||||
return ChipAuthenticationInfo(oid: oid, version: version, keyId: keyId);
|
||||
} else {
|
||||
return ChipAuthenticationInfo(oid: oid, version: version);
|
||||
}
|
||||
} else if PACEInfo.checkRequiredIdentifier(oid) {
|
||||
let version = Int(requiredData.value) ?? -1
|
||||
var parameterId : Int? = nil
|
||||
|
||||
if let optionalData = optionalData {
|
||||
parameterId = Int(optionalData.value, radix:16)
|
||||
}
|
||||
return PACEInfo(oid: oid, version: version, parameterId: parameterId);
|
||||
} else if ActiveAuthenticationInfo.checkRequiredIdentifier(oid) {
|
||||
let version = Int(requiredData.value) ?? -1
|
||||
if let optionalData = optionalData {
|
||||
return ActiveAuthenticationInfo(oid: oid, version: version, signatureAlgorithmOID: optionalData.value)
|
||||
} else {
|
||||
return ActiveAuthenticationInfo(oid: oid, version: version)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
app/ios/AwesomeProject/Images.xcassets/Contents.json
Normal file
6
app/ios/AwesomeProject/Images.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
56
app/ios/AwesomeProject/Info.plist
Normal file
56
app/ios/AwesomeProject/Info.plist
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>AwesomeProject</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string></string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NFCReaderUsageDescription</key>
|
||||
<string>Need NFC to read Passport</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
|
||||
<array>
|
||||
<string>A0000002471001</string>
|
||||
<string>A0000002472001</string>
|
||||
<string>00000000000000</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
50
app/ios/AwesomeProject/NFCViewDisplayMessage.swift
Normal file
50
app/ios/AwesomeProject/NFCViewDisplayMessage.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
import Foundation
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public enum NFCViewDisplayMessage {
|
||||
case requestPresentPassport
|
||||
case authenticatingWithPassport(Int)
|
||||
case readingDataGroupProgress(DataGroupId, Int)
|
||||
case error(NFCPassportReaderError)
|
||||
case successfulRead
|
||||
}
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
extension NFCViewDisplayMessage {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .requestPresentPassport:
|
||||
return "Hold your iPhone near an NFC enabled passport."
|
||||
case .authenticatingWithPassport(let progress):
|
||||
let progressString = handleProgress(percentualProgress: progress)
|
||||
return "Authenticating with passport.....\n\n\(progressString)"
|
||||
case .readingDataGroupProgress(let dataGroup, let progress):
|
||||
let progressString = handleProgress(percentualProgress: progress)
|
||||
return "Reading \(dataGroup).....\n\n\(progressString)"
|
||||
case .error(let tagError):
|
||||
switch tagError {
|
||||
case NFCPassportReaderError.TagNotValid:
|
||||
return "Tag not valid."
|
||||
case NFCPassportReaderError.MoreThanOneTagFound:
|
||||
return "More than 1 tags was found. Please present only 1 tag."
|
||||
case NFCPassportReaderError.ConnectionError:
|
||||
return "Connection error. Please try again."
|
||||
case NFCPassportReaderError.InvalidMRZKey:
|
||||
return "MRZ Key not valid for this document."
|
||||
case NFCPassportReaderError.ResponseError(let description, let sw1, let sw2):
|
||||
return "Sorry, there was a problem reading the passport. \(description) - (0x\(sw1), 0x\(sw2)"
|
||||
default:
|
||||
return "Sorry, there was a problem reading the passport. Please try again"
|
||||
}
|
||||
case .successfulRead:
|
||||
return "Passport read successfully"
|
||||
}
|
||||
}
|
||||
|
||||
func handleProgress(percentualProgress: Int) -> String {
|
||||
let p = (percentualProgress/20)
|
||||
let full = String(repeating: "🟢 ", count: p)
|
||||
let empty = String(repeating: "⚪️ ", count: 5-p)
|
||||
return "\(full)\(empty)"
|
||||
}
|
||||
}
|
||||
697
app/ios/AwesomeProject/OpenSSLUtils.swift
Normal file
697
app/ios/AwesomeProject/OpenSSLUtils.swift
Normal file
@@ -0,0 +1,697 @@
|
||||
import Foundation
|
||||
import OpenSSL
|
||||
import CryptoTokenKit
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public class OpenSSLUtils {
|
||||
private static var loaded = false
|
||||
|
||||
/// Returns any OpenSSL Error as a String
|
||||
public static func getOpenSSLError() -> String {
|
||||
|
||||
guard let out = BIO_new(BIO_s_mem()) else { return "Unknown" }
|
||||
defer { BIO_free(out) }
|
||||
|
||||
ERR_print_errors( out )
|
||||
let str = OpenSSLUtils.bioToString( bio:out )
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
/// Extracts the contents of a BIO object and returns it as a String
|
||||
/// - Parameter bio: a Pointer to a BIO buffer
|
||||
/// - Returns: A string containing the contents of the BIO buffer
|
||||
static func bioToString( bio : OpaquePointer ) -> String {
|
||||
|
||||
let len = BIO_ctrl(bio, BIO_CTRL_PENDING, 0, nil)
|
||||
var buffer = [CChar](repeating: 0, count: len+1)
|
||||
BIO_read(bio, &buffer, Int32(len))
|
||||
|
||||
// Ensure last value is 0 (null terminated) otherwise we get buffer overflow!
|
||||
buffer[len] = 0
|
||||
let ret = String(cString:buffer)
|
||||
return ret
|
||||
}
|
||||
|
||||
static func X509ToPEM( x509: OpaquePointer ) -> String {
|
||||
|
||||
let out = BIO_new(BIO_s_mem())!
|
||||
defer { BIO_free( out) }
|
||||
|
||||
PEM_write_bio_X509(out, x509);
|
||||
let str = OpenSSLUtils.bioToString( bio:out )
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
static func pubKeyToPEM( pubKey: OpaquePointer ) -> String {
|
||||
|
||||
let out = BIO_new(BIO_s_mem())!
|
||||
defer { BIO_free( out) }
|
||||
|
||||
PEM_write_bio_PUBKEY(out, pubKey);
|
||||
let str = OpenSSLUtils.bioToString( bio:out )
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
static func privKeyToPEM( privKey: OpaquePointer ) -> String {
|
||||
|
||||
let out = BIO_new(BIO_s_mem())!
|
||||
defer { BIO_free( out) }
|
||||
|
||||
PEM_write_bio_PrivateKey(out, privKey, nil, nil, 0, nil, nil)
|
||||
let str = OpenSSLUtils.bioToString( bio:out )
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
static func pkcs7DataToPEM( pkcs7: Data ) -> String {
|
||||
|
||||
let inf = BIO_new(BIO_s_mem())!
|
||||
defer { BIO_free( inf) }
|
||||
let out = BIO_new(BIO_s_mem())!
|
||||
defer { BIO_free( out) }
|
||||
|
||||
let _ = pkcs7.withUnsafeBytes { (ptr) in
|
||||
BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: Int8.self), Int32(pkcs7.count))
|
||||
}
|
||||
guard let p7 = d2i_PKCS7_bio(inf, nil) else { return "" }
|
||||
defer { PKCS7_free(p7) }
|
||||
|
||||
PEM_write_bio_PKCS7(out, p7)
|
||||
let str = OpenSSLUtils.bioToString( bio:out )
|
||||
return str
|
||||
}
|
||||
|
||||
|
||||
/// Extracts a X509 certificate in PEM format from a PKCS7 container
|
||||
/// - Parameter pkcs7Der: The PKCS7 container in DER format
|
||||
/// - Returns: The PEM formatted X509 certificate
|
||||
/// - Throws: A OpenSSLError.UnableToGetX509CertificateFromPKCS7 are thrown for any error
|
||||
static func getX509CertificatesFromPKCS7( pkcs7Der : Data ) throws -> [X509Wrapper] {
|
||||
|
||||
guard let inf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToGetX509CertificateFromPKCS7("Unable to allocate input buffer") }
|
||||
defer { BIO_free(inf) }
|
||||
let _ = pkcs7Der.withUnsafeBytes { (ptr) in
|
||||
BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: Int8.self), Int32(pkcs7Der.count))
|
||||
}
|
||||
guard let p7 = d2i_PKCS7_bio(inf, nil) else { throw OpenSSLError.UnableToGetX509CertificateFromPKCS7("Unable to read PKCS7 DER data") }
|
||||
defer { PKCS7_free(p7) }
|
||||
|
||||
var certs : OpaquePointer? = nil
|
||||
let i = OBJ_obj2nid(p7.pointee.type);
|
||||
switch (i) {
|
||||
case NID_pkcs7_signed:
|
||||
if let sign = p7.pointee.d.sign {
|
||||
certs = sign.pointee.cert
|
||||
}
|
||||
break;
|
||||
case NID_pkcs7_signedAndEnveloped:
|
||||
if let signed_and_enveloped = p7.pointee.d.signed_and_enveloped {
|
||||
certs = signed_and_enveloped.pointee.cert
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var ret = [X509Wrapper]()
|
||||
if let certs = certs {
|
||||
let certCount = sk_X509_num(certs)
|
||||
for i in 0 ..< certCount {
|
||||
let x = sk_X509_value(certs, i);
|
||||
if let x509 = X509Wrapper(with:x) {
|
||||
ret.append( x509 )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
/// Checks whether a trust chain can be built up to verify a X509 certificate. A CAFile containing a list of trusted certificates (each in PEM format)
|
||||
/// is used to build the trust chain.
|
||||
/// The trusted certificates in this use case are typically from a Countries master list (see the scripts for more informaton on how to prepare this)
|
||||
/// - Parameter x509Cert: The X509 certificate (in PEM format) to verify
|
||||
/// - Parameter CAFile: The URL path of a file containing the list of certificates used to try to discover and build a trust chain
|
||||
/// - Returns: either the X509 issue signing certificate that was used to sign the passed in X509 certificate or an error
|
||||
static func verifyTrustAndGetIssuerCertificate( x509 : X509Wrapper, CAFile : URL ) -> Result<X509Wrapper, OpenSSLError> {
|
||||
|
||||
guard let cert_ctx = X509_STORE_new() else { return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to create certificate store")) }
|
||||
defer { X509_STORE_free(cert_ctx) }
|
||||
|
||||
X509_STORE_set_verify_cb(cert_ctx) { (ok, ctx) -> Int32 in
|
||||
let cert_error = X509_STORE_CTX_get_error(ctx)
|
||||
|
||||
if ok == 0 {
|
||||
let errVal = X509_verify_cert_error_string(Int(cert_error))
|
||||
let val = errVal!.withMemoryRebound(to: CChar.self, capacity: 1000) { (ptr) in
|
||||
return String(cString: ptr)
|
||||
}
|
||||
|
||||
Log.error("error \(cert_error) at \(X509_STORE_CTX_get_error_depth(ctx)) depth lookup:\(val)" )
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
guard let lookup = X509_STORE_add_lookup(cert_ctx, X509_LOOKUP_file()) else { return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to add lookup to store")) }
|
||||
|
||||
// Load masterList.pem file
|
||||
var rc = X509_LOOKUP_ctrl(lookup, X509_L_FILE_LOAD, CAFile.path, Int(X509_FILETYPE_PEM), nil)
|
||||
|
||||
guard let store = X509_STORE_CTX_new() else {
|
||||
return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to create new X509_STORE_CTX"))
|
||||
}
|
||||
defer { X509_STORE_CTX_free(store) }
|
||||
|
||||
X509_STORE_set_flags(cert_ctx, 0)
|
||||
rc = X509_STORE_CTX_init(store, cert_ctx, x509.cert, nil)
|
||||
if rc == 0 {
|
||||
return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to initialise X509_STORE_CTX"))
|
||||
}
|
||||
|
||||
// discover and verify X509 certificte chain
|
||||
let i = X509_verify_cert(store);
|
||||
if i != 1 {
|
||||
let err = X509_STORE_CTX_get_error(store)
|
||||
|
||||
return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Verification of certificate failed - errorCode \(err)"))
|
||||
}
|
||||
|
||||
// Get chain and issue certificate is the last cert in the chain
|
||||
let chain = X509_STORE_CTX_get1_chain(store);
|
||||
let nrCertsInChain = sk_X509_num(chain)
|
||||
if nrCertsInChain > 1 {
|
||||
let cert = sk_X509_value(chain, nrCertsInChain-1)
|
||||
if let certWrapper = X509Wrapper(with: cert) {
|
||||
return .success( certWrapper )
|
||||
}
|
||||
}
|
||||
|
||||
return .failure(OpenSSLError.UnableToVerifyX509CertificateForSOD("Unable to get issuer certificate - not found"))
|
||||
}
|
||||
|
||||
|
||||
/// Verifies the signed data section against the stored certificate and extracts the signed data section from a PKCS7 container (if present and valid)
|
||||
/// - Parameter pkcs7Der: The PKCS7 container in DER format
|
||||
/// - Returns: The signed data from a PKCS7 container if we could read it
|
||||
///
|
||||
/// - Note: To test from the command line using openssl (NOTE NOT THE default mac version as it doesn't currently support CMS):
|
||||
/// extract the SOD Base64 from an exported passport (you will need to unescape slashes!) - save this to ppt.b64
|
||||
/// convert to binary (cat ppt.b64 | base64 -D > ppt.bin
|
||||
/// extract the der file from the SOD (which includes header) - tail -c+5 ppt.bin > aq.der (blindy discards header)
|
||||
/// convert der to PEM - openssl pkcs7 -in ppt.der --inform der -out ppt.pem -outform pem
|
||||
/// verify signature data against included document signing cert - openssl cms -verify -in ppt.pem -inform pem -noverify
|
||||
/// the -noverify is don't verify against the signers certifcate (as we don' thave that!)
|
||||
///
|
||||
/// This should return Verification Successful and the signed data
|
||||
static func verifyAndReturnSODEncapsulatedDataUsingCMS( sod : SOD ) throws -> Data {
|
||||
|
||||
guard let inf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("CMS - Unable to allocate input buffer") }
|
||||
defer { BIO_free(inf) }
|
||||
|
||||
guard let out = BIO_new(BIO_s_mem()) else { throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("CMS - Unable to allocate output buffer") }
|
||||
defer { BIO_free(out) }
|
||||
|
||||
let _ = sod.body.withUnsafeBytes { (ptr) in
|
||||
BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(sod.body.count))
|
||||
}
|
||||
|
||||
guard let cms = d2i_CMS_bio(inf, nil) else {
|
||||
throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("CMS - Verification of P7 failed - unable to create CMS")
|
||||
}
|
||||
defer { CMS_ContentInfo_free(cms) }
|
||||
|
||||
let flags : UInt32 = UInt32(CMS_NO_SIGNER_CERT_VERIFY)
|
||||
|
||||
if CMS_verify(cms, nil, nil, nil, out, flags) == 0 {
|
||||
throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("CMS - Verification of P7 failed - unable to verify signature")
|
||||
}
|
||||
|
||||
Log.debug("Verification successful\n");
|
||||
let len = BIO_ctrl(out, BIO_CTRL_PENDING, 0, nil)
|
||||
var buffer = [UInt8](repeating: 0, count: len)
|
||||
BIO_read(out, &buffer, Int32(len))
|
||||
let sigData = Data(buffer)
|
||||
|
||||
return sigData
|
||||
}
|
||||
|
||||
|
||||
static func verifyAndReturnSODEncapsulatedData( sod : SOD ) throws -> Data {
|
||||
|
||||
let encapsulatedContent = try sod.getEncapsulatedContent()
|
||||
let signedAttribsHashAlgo = try sod.getEncapsulatedContentDigestAlgorithm()
|
||||
let signedAttributes = try sod.getSignedAttributes()
|
||||
let messageDigest = try sod.getMessageDigestFromSignedAttributes()
|
||||
let signature = try sod.getSignature()
|
||||
let sigType = try sod.getSignatureAlgorithm()
|
||||
|
||||
let pubKey = try sod.getPublicKey()
|
||||
|
||||
let mdHash : Data = try Data(calcHash(data: [UInt8](encapsulatedContent), hashAlgorithm: signedAttribsHashAlgo))
|
||||
|
||||
// Make sure that hash equals the messageDigest
|
||||
if messageDigest != mdHash {
|
||||
// Invalid - signed data hash doesn't match message digest hash
|
||||
throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("messageDigest Hash doesn't hatch that of the signed attributes")
|
||||
}
|
||||
|
||||
// Verify signed attributes
|
||||
if !verifySignature( data : [UInt8](signedAttributes), signature : [UInt8](signature), pubKey : pubKey, digestType: sigType ) {
|
||||
|
||||
throw OpenSSLError.VerifyAndReturnSODEncapsulatedData("Unable to verify signature for signed attributes")
|
||||
}
|
||||
|
||||
return encapsulatedContent
|
||||
}
|
||||
|
||||
/// Parses a signed data structures encoded in ASN1 format and returns the structure in text format
|
||||
/// - Parameter data: The data to be parsed in ASN1 format
|
||||
/// - Returns: The parsed data as A String
|
||||
static func ASN1Parse( data: Data ) throws -> String {
|
||||
|
||||
guard let out = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToParseASN1("Unable to allocate output buffer") }
|
||||
defer { BIO_free(out) }
|
||||
|
||||
var parsed : String = ""
|
||||
let _ = try data.withUnsafeBytes { (ptr) in
|
||||
let rc = ASN1_parse_dump(out, ptr.baseAddress?.assumingMemoryBound(to: UInt8.self), data.count, 0, 0)
|
||||
if rc == 0 {
|
||||
let str = OpenSSLUtils.getOpenSSLError()
|
||||
Log.debug( "Failed to parse ASN1 Data - \(str)" )
|
||||
throw OpenSSLError.UnableToParseASN1("Failed to parse ASN1 Data - \(str)")
|
||||
}
|
||||
|
||||
parsed = bioToString(bio: out)
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Reads an RSA Public Key in DER format and converts it to an OpenSSL EVP_PKEY value for use whilst decrypting or verifying an RSA signature
|
||||
/// - Parameter data: The RSA key in DER format
|
||||
/// - Returns: The EVP_PKEY value
|
||||
/// NOTE THE CALLER IS RESPONSIBLE FOR FREEING THE RETURNED KEY USING
|
||||
/// EVP_PKEY_free(pemKey);
|
||||
static func readRSAPublicKey( data : [UInt8] ) throws -> OpaquePointer? {
|
||||
|
||||
guard let inf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToReadECPublicKey("Unable to allocate output buffer") }
|
||||
defer { BIO_free(inf) }
|
||||
|
||||
let _ = data.withUnsafeBytes { (ptr) in
|
||||
BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(data.count))
|
||||
}
|
||||
|
||||
guard let rsakey = d2i_RSA_PUBKEY_bio(inf, nil) else { throw OpenSSLError.UnableToReadECPublicKey("Failed to load") }
|
||||
defer{ RSA_free(rsakey) }
|
||||
|
||||
let key = EVP_PKEY_new()
|
||||
if EVP_PKEY_set1_RSA(key, rsakey) != 1 {
|
||||
EVP_PKEY_free(key)
|
||||
throw OpenSSLError.UnableToReadECPublicKey("Failed to load")
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
/// This code is taken pretty much from rsautl.c - to decrypt a signature with a public key
|
||||
/// NOTE: Current no padding is used! - This seems to be the default for Active Authentication RSA signatures (guess)
|
||||
/// - Parameter signature: The RSA encrypted signature to decrypt
|
||||
/// - Parameter pubKey: The RSA Public Key
|
||||
/// - Returns: The decrypted signature data
|
||||
static func decryptRSASignature( signature : Data, pubKey : OpaquePointer ) throws -> [UInt8] {
|
||||
|
||||
let pad = RSA_NO_PADDING
|
||||
let rsa = EVP_PKEY_get1_RSA( pubKey )
|
||||
|
||||
let keysize = RSA_size(rsa);
|
||||
var outputBuf = [UInt8](repeating: 0, count: Int(keysize))
|
||||
|
||||
// Decrypt signature
|
||||
var outlen : Int32 = 0
|
||||
let _ = signature.withUnsafeBytes { (sigPtr) in
|
||||
let _ = outputBuf.withUnsafeMutableBytes { (outPtr) in
|
||||
outlen = RSA_public_decrypt(Int32(signature.count), sigPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), outPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), rsa, pad)
|
||||
}
|
||||
}
|
||||
|
||||
if outlen == 0 {
|
||||
let error = OpenSSLUtils.getOpenSSLError()
|
||||
throw OpenSSLError.UnableToDecryptRSASignature( "RSA_public_decrypt failed - \(error)" )
|
||||
}
|
||||
|
||||
return outputBuf
|
||||
}
|
||||
|
||||
/// Reads an ECDSA Public Key in DER format and converts it to an OpenSSL EVP_PKEY value for use whilst verifying a ECDSA signature
|
||||
/// - Parameter data: The ECDSA key in DER forma
|
||||
/// - Returns: The EVP_PKEY value
|
||||
/// NOTE THE CALLER IS RESPONSIBLE FOR FREEING THE RETURNED KEY USING
|
||||
/// EVP_PKEY_free(pemKey);
|
||||
static func readECPublicKey( data : [UInt8] ) throws -> OpaquePointer? {
|
||||
|
||||
guard let inf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToReadECPublicKey("Unable to allocate output buffer") }
|
||||
defer { BIO_free(inf) }
|
||||
|
||||
let _ = data.withUnsafeBytes { (ptr) in
|
||||
BIO_write(inf, ptr.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(data.count))
|
||||
}
|
||||
|
||||
guard let eckey = d2i_EC_PUBKEY_bio(inf, nil) else { throw OpenSSLError.UnableToReadECPublicKey("Failed to load") }
|
||||
defer{ EC_KEY_free(eckey) }
|
||||
|
||||
guard let outf = BIO_new(BIO_s_mem()) else { throw OpenSSLError.UnableToReadECPublicKey("Unable to allocate output buffer") }
|
||||
defer { BIO_free(outf) }
|
||||
let _ = PEM_write_bio_EC_PUBKEY(outf, eckey);
|
||||
let pemKey = PEM_read_bio_PUBKEY(outf, nil, nil, nil)
|
||||
|
||||
return pemKey
|
||||
}
|
||||
|
||||
|
||||
/// Verifies Active Authentication data valid against an ECDSA signature and ECDSA Public Key - used in Active Authentication
|
||||
/// - Parameter publicKey: The OpenSSL EVP_PKEY ECDSA key
|
||||
/// - Parameter signature: The ECDSA signature to verify
|
||||
/// - Parameter data: The data used to generate the signature
|
||||
/// - Returns: True if the signature was verified
|
||||
static func verifyECDSASignature( publicKey:OpaquePointer, signature: [UInt8], data: [UInt8], digestType: String = "" ) -> Bool {
|
||||
|
||||
// We first need to convert the signature from PLAIN ECDSA to ASN1 DER encoded
|
||||
let ecsig = ECDSA_SIG_new()
|
||||
defer { ECDSA_SIG_free(ecsig) }
|
||||
let sigData = signature
|
||||
let l = sigData.count / 2
|
||||
sigData.withUnsafeBufferPointer { (unsafeBufPtr) in
|
||||
let unsafePointer = unsafeBufPtr.baseAddress!
|
||||
let r = BN_bin2bn(unsafePointer, Int32(l), nil)
|
||||
let s = BN_bin2bn((unsafePointer + l), Int32(l), nil)
|
||||
ECDSA_SIG_set0(ecsig, r, s)
|
||||
}
|
||||
let sigSize = i2d_ECDSA_SIG(ecsig, nil)
|
||||
var derBytes = [UInt8](repeating: 0, count: Int(sigSize))
|
||||
derBytes.withUnsafeMutableBufferPointer { (unsafeBufPtr) in
|
||||
var unsafePointer = unsafeBufPtr.baseAddress
|
||||
let _ = i2d_ECDSA_SIG(ecsig, &unsafePointer)
|
||||
}
|
||||
|
||||
let rc = verifySignature(data: data, signature: derBytes, pubKey: publicKey, digestType: digestType)
|
||||
return rc
|
||||
}
|
||||
|
||||
/// Verifies that a signature is valid for some data and a Public Key
|
||||
/// - Parameter data: The data used to generate the signature
|
||||
/// - Parameter signature: The signature to verify
|
||||
/// - Parameter publicKey: The OpenSSL EVP_PKEY key
|
||||
/// - Parameter digestType: the type of hash to use (empty string to use no digest type)
|
||||
/// - Returns: True if the signature was verified
|
||||
static func verifySignature( data : [UInt8], signature : [UInt8], pubKey : OpaquePointer, digestType: String ) -> Bool {
|
||||
|
||||
var digest = "sha256"
|
||||
let digestType = digestType.lowercased()
|
||||
if digestType.contains( "sha1" ) {
|
||||
digest = "sha1"
|
||||
} else if digestType.contains( "sha224" ) {
|
||||
digest = "sha224"
|
||||
} else if digestType.contains( "sha256" ) || digestType.contains( "rsassapss" ) {
|
||||
digest = "sha256"
|
||||
} else if digestType.contains( "sha384" ) {
|
||||
digest = "sha384"
|
||||
} else if digestType.contains( "sha512" ) {
|
||||
digest = "sha512"
|
||||
}
|
||||
|
||||
// Fix for some invalid ECDSA based signatures
|
||||
// An ECDSA signature comprises of a Sequence of 2 big integers (R & S) and the verification
|
||||
// is a linear equation of these two integers, the data hash and the public key
|
||||
// However, in some passports the encoding of the integers is incorrect and has a leading 00
|
||||
// causing the verification to fail.
|
||||
// So in this case, we check to see if it is actually a valid BigInteger, and if not, we remove the
|
||||
// leading prefix, check again and if a valid big integer this time then we use this otherwise
|
||||
// we keep the original value
|
||||
// If we change any values then we re-generate the signature and use this
|
||||
var fixedSignature = signature
|
||||
if digestType.contains( "ecdsa" ) {
|
||||
// Decode signature
|
||||
if let sequence = TKBERTLVRecord(from:Data(signature)),
|
||||
sequence.tag == 0x30,
|
||||
var intRecords = TKBERTLVRecord.sequenceOfRecords(from: sequence.value),
|
||||
intRecords.count == 2 {
|
||||
|
||||
var didFix = false
|
||||
for (idx, rec) in intRecords.enumerated() {
|
||||
// Only process if the first byte is a 0
|
||||
if rec.value[0] != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// There is a feature in TKBERTLVRecord.sequenceOfRecords where the 2nd record.data call
|
||||
// contains the data for the whole data not the actual record
|
||||
// (reported as FB9077037)
|
||||
// So for the moment, work aroud this and create a new record
|
||||
let fixedRec = TKBERTLVRecord( tag: rec.tag, value: rec.value)
|
||||
let data = [UInt8](fixedRec.data)
|
||||
|
||||
// Check to see if a valid Big Integer (we need the whole record including tag and length for the d2i_ASN1_INTEGER call)
|
||||
data.withUnsafeBufferPointer { (ptr) in
|
||||
var address = ptr.baseAddress
|
||||
let v = d2i_ASN1_INTEGER(nil, &address, data.count)
|
||||
defer { ASN1_INTEGER_free(v) }
|
||||
if v == nil {
|
||||
// Not a valid BigInteger, so remove the first value and try again
|
||||
let newRec = TKBERTLVRecord( tag: rec.tag, value: rec.value[1...])
|
||||
|
||||
let data2 = [UInt8](newRec.data)
|
||||
data2.withUnsafeBufferPointer { (ptr) in
|
||||
var address = ptr.baseAddress
|
||||
let v2 = d2i_ASN1_INTEGER(nil, &address, data2.count)
|
||||
defer { ASN1_INTEGER_free(v2) }
|
||||
if v2 != nil {
|
||||
// OK, we have a valid BigInteger this time so replace the original
|
||||
// record with the new one
|
||||
intRecords[idx] = newRec
|
||||
didFix = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We only reencode if we changed any of the integers, otherwise assume they were actually
|
||||
// correctly encoded
|
||||
if didFix {
|
||||
// re-encode
|
||||
let newSequence = TKBERTLVRecord( tag: sequence.tag, records: intRecords)
|
||||
fixedSignature = [UInt8](newSequence.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let md = EVP_get_digestbyname(digest)
|
||||
|
||||
let ctx = EVP_MD_CTX_new()
|
||||
var pkey_ctx : OpaquePointer?
|
||||
|
||||
defer{ EVP_MD_CTX_free( ctx) }
|
||||
|
||||
var nRes = EVP_DigestVerifyInit(ctx, &pkey_ctx, md, nil, pubKey)
|
||||
if ( nRes != 1 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if digestType.contains( "rsassapss" ) {
|
||||
EVP_PKEY_CTX_ctrl_str(pkey_ctx, "rsa_padding_mode", "pss" )
|
||||
EVP_PKEY_CTX_ctrl_str(pkey_ctx, "rsa_pss_saltlen", "auto" )
|
||||
}
|
||||
|
||||
nRes = EVP_DigestUpdate(ctx, data, data.count);
|
||||
if ( nRes != 1 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nRes = EVP_DigestVerifyFinal(ctx, fixedSignature, fixedSignature.count);
|
||||
if (nRes != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
static func generateAESCMAC( key: [UInt8], message : [UInt8] ) -> [UInt8] {
|
||||
let ctx = CMAC_CTX_new();
|
||||
defer { CMAC_CTX_free(ctx) }
|
||||
var key = key
|
||||
|
||||
var mac = [UInt8](repeating: 0, count: 32)
|
||||
var maclen : Int = 0
|
||||
|
||||
if key.count == 16 {
|
||||
CMAC_Init(ctx, &key, key.count, EVP_aes_128_cbc(), nil)
|
||||
} else if key.count == 24 {
|
||||
CMAC_Init(ctx, &key, key.count, EVP_aes_192_cbc(), nil)
|
||||
} else if key.count == 32 {
|
||||
CMAC_Init(ctx, &key, key.count, EVP_aes_256_cbc(), nil)
|
||||
}
|
||||
CMAC_Update(ctx, message, message.count);
|
||||
CMAC_Final(ctx, &mac, &maclen);
|
||||
|
||||
Log.verbose( "aesMac - mac - \(binToHexRep(mac))" )
|
||||
|
||||
return [UInt8](mac[0..<maclen])
|
||||
}
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
static func asn1EncodeOID (oid : String) -> [UInt8] {
|
||||
|
||||
let obj = OBJ_txt2obj( oid.cString(using: .utf8), 1)
|
||||
let payloadLen = i2d_ASN1_OBJECT(obj, nil)
|
||||
|
||||
var data = [UInt8](repeating: 0, count: Int(payloadLen))
|
||||
|
||||
let _ = data.withUnsafeMutableBytes { (ptr) in
|
||||
var newPtr = ptr.baseAddress?.assumingMemoryBound(to: UInt8.self)
|
||||
_ = i2d_ASN1_OBJECT(obj, &newPtr)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public static func getPublicKeyData(from key:OpaquePointer) -> [UInt8]? {
|
||||
var data : [UInt8] = []
|
||||
// Get Key type
|
||||
let v = EVP_PKEY_base_id( key )
|
||||
if v == EVP_PKEY_DH || v == EVP_PKEY_DHX {
|
||||
guard let dh = EVP_PKEY_get0_DH(key) else {
|
||||
return nil
|
||||
}
|
||||
var dhPubKey : OpaquePointer?
|
||||
DH_get0_key(dh, &dhPubKey, nil)
|
||||
|
||||
let nrBytes = (BN_num_bits(dhPubKey)+7)/8
|
||||
data = [UInt8](repeating: 0, count: Int(nrBytes))
|
||||
_ = BN_bn2bin(dhPubKey, &data)
|
||||
} else if v == EVP_PKEY_EC {
|
||||
|
||||
guard let ec = EVP_PKEY_get0_EC_KEY(key),
|
||||
let ec_pub = EC_KEY_get0_public_key(ec),
|
||||
let ec_group = EC_KEY_get0_group(ec) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let form = EC_KEY_get_conv_form(ec)
|
||||
let len = EC_POINT_point2oct(ec_group, ec_pub, form, nil, 0, nil)
|
||||
data = [UInt8](repeating: 0, count: Int(len))
|
||||
if len == 0 {
|
||||
return nil
|
||||
}
|
||||
_ = EC_POINT_point2oct(ec_group, ec_pub, form, &data, len, nil)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Caller is responsible for freeing the key
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
public static func decodePublicKeyFromBytes(pubKeyData: [UInt8], params: OpaquePointer) -> OpaquePointer? {
|
||||
var pubKey : OpaquePointer?
|
||||
|
||||
let keyType = EVP_PKEY_base_id( params )
|
||||
if keyType == EVP_PKEY_DH || keyType == EVP_PKEY_DHX {
|
||||
|
||||
let dhKey = DH_new()
|
||||
defer{ DH_free(dhKey) }
|
||||
|
||||
// We don't free this as its part of the key!
|
||||
let bn = BN_bin2bn(pubKeyData, Int32(pubKeyData.count), nil)
|
||||
DH_set0_key(dhKey, bn, nil)
|
||||
|
||||
pubKey = EVP_PKEY_new()
|
||||
guard EVP_PKEY_set1_DH(pubKey, dhKey) == 1 else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
let ec = EVP_PKEY_get1_EC_KEY(params)
|
||||
let group = EC_KEY_get0_group(ec);
|
||||
let ecp = EC_POINT_new(group);
|
||||
let key = EC_KEY_new();
|
||||
defer {
|
||||
EC_KEY_free(ec)
|
||||
EC_POINT_free(ecp)
|
||||
EC_KEY_free(key)
|
||||
}
|
||||
|
||||
// Read EC_Point from public key data
|
||||
guard EC_POINT_oct2point(group, ecp, pubKeyData, pubKeyData.count, nil) == 1,
|
||||
EC_KEY_set_group(key, group) == 1,
|
||||
EC_KEY_set_public_key(key, ecp) == 1 else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
pubKey = EVP_PKEY_new()
|
||||
guard EVP_PKEY_set1_EC_KEY(pubKey, key) == 1 else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return pubKey
|
||||
}
|
||||
|
||||
|
||||
public static func computeSharedSecret( privateKeyPair: OpaquePointer, publicKey: OpaquePointer ) -> [UInt8] {
|
||||
|
||||
// Oddly it seems that we cant use EVP_PKEY stuff for DH as it uses DTX keys which OpenSSL doesn't quite handle right
|
||||
// OR I'm misunderstanding something (which is more possible)
|
||||
// Works fine though for ECDH keys
|
||||
var secret : [UInt8]
|
||||
let keyType = EVP_PKEY_base_id( privateKeyPair )
|
||||
if keyType == EVP_PKEY_DH || keyType == EVP_PKEY_DHX {
|
||||
// Get bn for public key
|
||||
let dh = EVP_PKEY_get1_DH(privateKeyPair);
|
||||
|
||||
let dh_pub = EVP_PKEY_get1_DH(publicKey)
|
||||
var bn = BN_new()
|
||||
DH_get0_key( dh_pub, &bn, nil )
|
||||
|
||||
secret = [UInt8](repeating: 0, count: Int(DH_size(dh)))
|
||||
let len = DH_compute_key(&secret, bn, dh);
|
||||
|
||||
Log.verbose( "OpenSSLUtils.computeSharedSecret - DH secret len - \(len)" )
|
||||
} else {
|
||||
let ctx = EVP_PKEY_CTX_new(privateKeyPair, nil)
|
||||
defer{ EVP_PKEY_CTX_free(ctx) }
|
||||
|
||||
if EVP_PKEY_derive_init(ctx) != 1 {
|
||||
// error
|
||||
Log.error( "ERROR - \(OpenSSLUtils.getOpenSSLError())" )
|
||||
}
|
||||
|
||||
// Set the public key
|
||||
if EVP_PKEY_derive_set_peer( ctx, publicKey ) != 1 {
|
||||
// error
|
||||
Log.error( "ERROR - \(OpenSSLUtils.getOpenSSLError())" )
|
||||
}
|
||||
|
||||
// get buffer length needed for shared secret
|
||||
var keyLen = 0
|
||||
if EVP_PKEY_derive(ctx, nil, &keyLen) != 1 {
|
||||
// Error
|
||||
Log.error( "ERROR - \(OpenSSLUtils.getOpenSSLError())" )
|
||||
}
|
||||
|
||||
// Derive the shared secret
|
||||
secret = [UInt8](repeating: 0, count: keyLen)
|
||||
if EVP_PKEY_derive(ctx, &secret, &keyLen) != 1 {
|
||||
// Error
|
||||
Log.error( "ERROR - \(OpenSSLUtils.getOpenSSLError())" )
|
||||
}
|
||||
}
|
||||
return secret
|
||||
}
|
||||
|
||||
}
|
||||
614
app/ios/AwesomeProject/PACEHandler.swift
Normal file
614
app/ios/AwesomeProject/PACEHandler.swift
Normal file
@@ -0,0 +1,614 @@
|
||||
import Foundation
|
||||
import OpenSSL
|
||||
import CryptoTokenKit
|
||||
|
||||
#if !os(macOS)
|
||||
import CoreNFC
|
||||
import CryptoKit
|
||||
|
||||
@available(iOS 15, *)
|
||||
private enum PACEHandlerError {
|
||||
case DHKeyAgreementError(String)
|
||||
case ECDHKeyAgreementError(String)
|
||||
|
||||
var value: String {
|
||||
switch self {
|
||||
case .DHKeyAgreementError(let errMsg): return errMsg
|
||||
case .ECDHKeyAgreementError(let errMsg): return errMsg
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15, *)
|
||||
extension PACEHandlerError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
return NSLocalizedString(value, comment: "PACEHandlerError")
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15, *)
|
||||
public class PACEHandler {
|
||||
|
||||
|
||||
private static let MRZ_PACE_KEY_REFERENCE : UInt8 = 0x01
|
||||
private static let CAN_PACE_KEY_REFERENCE : UInt8 = 0x02 // Not currently supported
|
||||
private static let PIN_PACE_KEY_REFERENCE : UInt8 = 0x03 // Not currently supported
|
||||
private static let CUK_PACE_KEY_REFERENCE : UInt8 = 0x04 // Not currently supported
|
||||
|
||||
var tagReader : TagReader
|
||||
var paceInfo : PACEInfo
|
||||
|
||||
var isPACESupported : Bool = false
|
||||
var paceError : String = ""
|
||||
|
||||
// Params used
|
||||
private var paceKey : [UInt8] = []
|
||||
private var paceKeyType : UInt8 = 0
|
||||
private var paceOID : String = ""
|
||||
private var parameterSpec : Int32 = -1
|
||||
private var mappingType : PACEMappingType!
|
||||
private var agreementAlg : String = ""
|
||||
private var cipherAlg : String = ""
|
||||
private var digestAlg : String = ""
|
||||
private var keyLength : Int = -1
|
||||
|
||||
public init(cardAccess : CardAccess, tagReader: TagReader) throws {
|
||||
self.tagReader = tagReader
|
||||
|
||||
guard let pi = cardAccess.paceInfo else {
|
||||
throw NFCPassportReaderError.NotYetSupported( "PACE not supported" )
|
||||
}
|
||||
|
||||
self.paceInfo = pi
|
||||
isPACESupported = true
|
||||
}
|
||||
|
||||
public func doPACE( mrzKey : String ) async throws {
|
||||
guard isPACESupported else {
|
||||
throw NFCPassportReaderError.NotYetSupported( "PACE not supported" )
|
||||
}
|
||||
|
||||
Log.info( "Performing PACE with \(paceInfo.getProtocolOIDString())" )
|
||||
|
||||
paceOID = paceInfo.getObjectIdentifier()
|
||||
parameterSpec = try paceInfo.getParameterSpec()
|
||||
|
||||
mappingType = try paceInfo.getMappingType() // Either GM, CAM, or IM.
|
||||
agreementAlg = try paceInfo.getKeyAgreementAlgorithm() // Either DH or ECDH.
|
||||
cipherAlg = try paceInfo.getCipherAlgorithm() // Either DESede or AES.
|
||||
digestAlg = try paceInfo.getDigestAlgorithm() // Either SHA-1 or SHA-256.
|
||||
keyLength = try paceInfo.getKeyLength() // Get key length the enc cipher. Either 128, 192, or 256.
|
||||
|
||||
paceKeyType = PACEHandler.MRZ_PACE_KEY_REFERENCE
|
||||
paceKey = try createPaceKey( from: mrzKey )
|
||||
|
||||
// Temporary logging
|
||||
Log.verbose("doPace - inpit parameters" )
|
||||
Log.verbose("paceOID - \(paceOID)" )
|
||||
Log.verbose("parameterSpec - \(parameterSpec)" )
|
||||
Log.verbose("mappingType - \(mappingType!)" )
|
||||
Log.verbose("agreementAlg - \(agreementAlg)" )
|
||||
Log.verbose("cipherAlg - \(cipherAlg)" )
|
||||
Log.verbose("digestAlg - \(digestAlg)" )
|
||||
Log.verbose("keyLength - \(keyLength)" )
|
||||
Log.verbose("keyLength - \(mrzKey)" )
|
||||
Log.verbose("paceKey - \(binToHexRep(paceKey, asArray:true))" )
|
||||
|
||||
// First start the initial auth call
|
||||
_ = try await tagReader.sendMSESetATMutualAuth(oid: paceOID, keyType: paceKeyType)
|
||||
|
||||
let decryptedNonce = try await self.doStep1()
|
||||
let ephemeralParams = try await self.doStep2(passportNonce: decryptedNonce)
|
||||
let (ephemeralKeyPair, passportPublicKey) = try await self.doStep3KeyExchange(ephemeralParams: ephemeralParams)
|
||||
let (encKey, macKey) = try await self.doStep4KeyAgreement( pcdKeyPair: ephemeralKeyPair, passportPublicKey: passportPublicKey)
|
||||
try self.paceCompleted( ksEnc: encKey, ksMac: macKey )
|
||||
Log.debug("PACE SUCCESSFUL" )
|
||||
}
|
||||
|
||||
/// Handles an error during the PACE process
|
||||
/// Logs and stoes the error and returns false to the caller
|
||||
/// - Parameters:
|
||||
/// - stage: Where in the PACE process the error occurred
|
||||
/// - error: The error message
|
||||
func handleError( _ stage: String, _ error: String, needToTerminateGA: Bool = false ) {
|
||||
Log.error( "PACEHandler: \(stage) - \(error)" )
|
||||
Log.error( " OpenSSLError: \(OpenSSLUtils.getOpenSSLError())" )
|
||||
self.paceError = "\(stage) - \(error)"
|
||||
//self.completedHandler?( false )
|
||||
|
||||
/*
|
||||
if needToTerminateGA {
|
||||
// This is to fix some passports that don't automatically terminate command chaining!
|
||||
// No idea if this is the correct way to do it but testing.....
|
||||
let terminateGA = wrapDO(b:0x83, arr:[0x00])
|
||||
tagReader.sendGeneralAuthenticate(data:terminateGA, isLast:true, completed: { [weak self] response, error in
|
||||
self?.completedHandler?( false )
|
||||
})
|
||||
} else {
|
||||
self.completedHandler?( false )
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// Performs PACE Step 1- receives an encrypted nonce from the passport and decypts it with the PACE key - derived from MRZ, CAN (not yet supported)
|
||||
func doStep1() async throws -> [UInt8] {
|
||||
Log.debug("Doing PACE Step1...")
|
||||
let response = try await tagReader.sendGeneralAuthenticate(data: [], isLast: false)
|
||||
|
||||
let data = response.data
|
||||
let encryptedNonce = try unwrapDO(tag: 0x80, wrappedData: data)
|
||||
Log.verbose( "Encrypted nonce - \(binToHexRep(encryptedNonce, asArray:true))" )
|
||||
|
||||
let decryptedNonce: [UInt8]
|
||||
if self.cipherAlg == "DESede" {
|
||||
let iv = [UInt8](repeating:0, count: 8)
|
||||
decryptedNonce = tripleDESDecrypt(key: self.paceKey, message: encryptedNonce, iv: iv)
|
||||
} else if self.cipherAlg == "AES" {
|
||||
let iv = [UInt8](repeating:0, count: 16)
|
||||
decryptedNonce = AESDecrypt(key: self.paceKey, message: encryptedNonce, iv: iv)
|
||||
} else {
|
||||
throw NFCPassportReaderError.UnsupportedCipherAlgorithm
|
||||
}
|
||||
|
||||
Log.verbose( "Decrypted nonce - \(binToHexRep(decryptedNonce, asArray:true) )" )
|
||||
return decryptedNonce
|
||||
}
|
||||
|
||||
|
||||
/// Performs PACE Step 2 - computes ephemeral parameters by mapping the nonce received from the passport
|
||||
/// (and if IM used the nonce generated by us)
|
||||
///
|
||||
/// Using the supported
|
||||
/// - Parameters:
|
||||
/// - passportNonce: The decrypted nonce received from the passport
|
||||
func doStep2( passportNonce: [UInt8]) async throws -> OpaquePointer {
|
||||
Log.debug( "Doing PACE Step2...")
|
||||
switch(mappingType) {
|
||||
case .CAM, .GM:
|
||||
Log.debug( " Using General Mapping (GM)...")
|
||||
return try await doPACEStep2GM(passportNonce: passportNonce)
|
||||
case .IM:
|
||||
Log.debug( " Using Integrated Mapping (IM)...")
|
||||
return try await doPACEStep2IM(passportNonce: passportNonce)
|
||||
default:
|
||||
throw NFCPassportReaderError.PACEError( "Step2GM", "Unsupported Mapping Type" )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Performs PACEStep 2 using Generic Mapping
|
||||
///
|
||||
/// Using the supported
|
||||
/// - Parameters:
|
||||
/// - passportNonce: The decrypted nonce received from the passport
|
||||
func doPACEStep2GM(passportNonce : [UInt8]) async throws -> OpaquePointer {
|
||||
|
||||
let mappingKey : OpaquePointer
|
||||
mappingKey = try self.paceInfo.createMappingKey( )
|
||||
|
||||
guard let pcdMappingEncodedPublicKey = OpenSSLUtils.getPublicKeyData(from: mappingKey) else {
|
||||
throw NFCPassportReaderError.PACEError( "Step2GM", "Unable to get public key from mapping key")
|
||||
}
|
||||
Log.verbose( "public mapping key - \(binToHexRep(pcdMappingEncodedPublicKey, asArray:true))")
|
||||
|
||||
Log.debug( "Sending public mapping key to passport..")
|
||||
let step2Data = wrapDO(b:0x81, arr:pcdMappingEncodedPublicKey)
|
||||
let response = try await tagReader.sendGeneralAuthenticate(data:step2Data, isLast:false)
|
||||
|
||||
let piccMappingEncodedPublicKey = try unwrapDO(tag: 0x82, wrappedData: response.data)
|
||||
|
||||
Log.debug( "Received passports public mapping key")
|
||||
Log.verbose( " public mapping key - \(binToHexRep(piccMappingEncodedPublicKey, asArray: true))")
|
||||
|
||||
// Do mapping agreement
|
||||
|
||||
// First, Convert nonce to BIGNUM
|
||||
guard let bn_nonce = BN_bin2bn(passportNonce, Int32(passportNonce.count), nil) else {
|
||||
throw NFCPassportReaderError.PACEError( "Step2GM", "Unable to convert picc nonce to bignum" )
|
||||
}
|
||||
defer { BN_free(bn_nonce) }
|
||||
|
||||
// ephmeralParams are free'd in stage 3
|
||||
let ephemeralParams : OpaquePointer
|
||||
if self.agreementAlg == "DH" {
|
||||
Log.debug( "Doing DH Mapping agreement")
|
||||
ephemeralParams = try self.doDHMappingAgreement(mappingKey: mappingKey, passportPublicKeyData: piccMappingEncodedPublicKey, nonce: bn_nonce )
|
||||
} else if self.agreementAlg == "ECDH" {
|
||||
Log.debug( "Doing ECDH Mapping agreement")
|
||||
ephemeralParams = try self.doECDHMappingAgreement(mappingKey: mappingKey, passportPublicKeyData: piccMappingEncodedPublicKey, nonce: bn_nonce )
|
||||
} else {
|
||||
throw NFCPassportReaderError.PACEError( "Step2GM", "Unsupported agreement algorithm" )
|
||||
}
|
||||
|
||||
// Need to free the mapping key we created now
|
||||
EVP_PKEY_free(mappingKey)
|
||||
return ephemeralParams
|
||||
}
|
||||
|
||||
func doPACEStep2IM( passportNonce: [UInt8] ) async throws -> OpaquePointer {
|
||||
// Not implemented yet
|
||||
throw NFCPassportReaderError.PACEError( "Step2IM", "IM not yet implemented" )
|
||||
}
|
||||
|
||||
/// Generates an ephemeral public/private key pair based on mapping parameters from step 2, and then sends
|
||||
/// the public key to the passport and receives its ephmeral public key in exchange
|
||||
/// - Parameters:
|
||||
/// - ephemeralParams: The ehpemeral mapping keys generated by step2
|
||||
/// - Returns:
|
||||
/// - Tuple of Generated Ephemeral KeyPair and the Passport's public key
|
||||
func doStep3KeyExchange(ephemeralParams: OpaquePointer) async throws -> (OpaquePointer, OpaquePointer) {
|
||||
Log.debug( "Doing PACE Step3 - Key Exchange")
|
||||
|
||||
// Generate ephemeral keypair from ephemeralParams
|
||||
var ephKeyPair : OpaquePointer? = nil
|
||||
let pctx = EVP_PKEY_CTX_new(ephemeralParams, nil)
|
||||
EVP_PKEY_keygen_init(pctx)
|
||||
EVP_PKEY_keygen(pctx, &ephKeyPair)
|
||||
EVP_PKEY_CTX_free(pctx)
|
||||
|
||||
guard let ephemeralKeyPair = ephKeyPair else {
|
||||
throw NFCPassportReaderError.PACEError( "Step3 KeyEx", "Unable to get create ephermeral key pair" )
|
||||
}
|
||||
|
||||
Log.debug( "Generated Ephemeral key pair")
|
||||
|
||||
// We've finished with the ephemeralParams now - we can now free it
|
||||
EVP_PKEY_free( ephemeralParams )
|
||||
|
||||
guard let publicKey = OpenSSLUtils.getPublicKeyData( from: ephemeralKeyPair ) else {
|
||||
throw NFCPassportReaderError.PACEError( "Step3 KeyEx", "Unable to get public key from ephermeral key pair" )
|
||||
}
|
||||
Log.verbose( "Ephemeral public key - \(binToHexRep(publicKey, asArray: true))")
|
||||
|
||||
// exchange public keys
|
||||
Log.debug( "Sending ephemeral public key to passport")
|
||||
let step3Data = wrapDO(b:0x83, arr:publicKey)
|
||||
let response = try await tagReader.sendGeneralAuthenticate(data:step3Data, isLast:false)
|
||||
let passportEncodedPublicKey = try? unwrapDO(tag: 0x84, wrappedData: response.data)
|
||||
guard let passportPublicKey = OpenSSLUtils.decodePublicKeyFromBytes(pubKeyData: passportEncodedPublicKey!, params: ephemeralKeyPair) else {
|
||||
throw NFCPassportReaderError.PACEError( "Step3 KeyEx", "Unable to decode passports ephemeral key" )
|
||||
}
|
||||
|
||||
Log.verbose( "Received passports ephemeral public key - \(binToHexRep(passportEncodedPublicKey!, asArray: true))" )
|
||||
return (ephemeralKeyPair, passportPublicKey)
|
||||
}
|
||||
|
||||
/// This performs PACE Step 4 - Key Agreement.
|
||||
/// Here the shared secret is computed from our ephemeral private key and the passports ephemeral public key
|
||||
/// The new secure messaging (ksEnc and ksMac) keys are computed from the shared secret
|
||||
/// An authentication token is generated from the passports public key and the computed ksMac key
|
||||
/// Then, the authetication token is send to the passport, it returns its own computed authentication token
|
||||
/// We then compute an expected authentication token from the ksMac key and our ephemeral public key
|
||||
/// Finally we compare the recieved auth token to the expected token and if they are the same then PACE has succeeded!
|
||||
/// - Parameters:
|
||||
/// - pcdKeyPair: our ephemeral key pair
|
||||
/// - passportPublicKey: passports ephemeral public key
|
||||
/// - Returns:
|
||||
/// - Tuple of KSEnc KSMac
|
||||
func doStep4KeyAgreement( pcdKeyPair: OpaquePointer, passportPublicKey: OpaquePointer) async throws -> ([UInt8], [UInt8]) {
|
||||
Log.debug( "Doing PACE Step4 Key Agreement...")
|
||||
|
||||
Log.debug( "Computing shared secret...")
|
||||
let sharedSecret = OpenSSLUtils.computeSharedSecret(privateKeyPair: pcdKeyPair, publicKey: passportPublicKey)
|
||||
Log.verbose( "Shared secret - \(binToHexRep(sharedSecret, asArray:true))")
|
||||
|
||||
Log.debug( "Deriving ksEnc and ksMac keys from shared secret")
|
||||
let gen = SecureMessagingSessionKeyGenerator()
|
||||
let encKey = try! gen.deriveKey(keySeed: sharedSecret, cipherAlgName: cipherAlg, keyLength: keyLength, mode: .ENC_MODE)
|
||||
let macKey = try! gen.deriveKey(keySeed: sharedSecret, cipherAlgName: cipherAlg, keyLength: keyLength, mode: .MAC_MODE)
|
||||
Log.verbose( "encKey - \(binToHexRep(encKey, asArray:true))")
|
||||
Log.verbose( "macKey - \(binToHexRep(macKey, asArray:true))")
|
||||
|
||||
// Step 4 - generate authentication token
|
||||
Log.debug( "Generating authentication token")
|
||||
guard let pcdAuthToken = try? generateAuthenticationToken( publicKey: passportPublicKey, macKey: macKey) else {
|
||||
throw NFCPassportReaderError.PACEError( "Step3 KeyAgreement", "Unable to generate authentication token using passports public key" )
|
||||
}
|
||||
Log.verbose( "authentication token - \(pcdAuthToken)")
|
||||
|
||||
Log.debug( "Sending auth token to passport")
|
||||
let step4Data = wrapDO(b:0x85, arr:pcdAuthToken)
|
||||
let response = try await tagReader.sendGeneralAuthenticate(data:step4Data, isLast:true)
|
||||
|
||||
let tvlResp = TKBERTLVRecord.sequenceOfRecords(from: Data(response.data))!
|
||||
if tvlResp[0].tag != 0x86 {
|
||||
Log.warning("Was expecting tag 0x86, found: \(binToHex(UInt8(tvlResp[0].tag)))")
|
||||
}
|
||||
// Calculate expected authentication token
|
||||
let expectedPICCToken = try self.generateAuthenticationToken( publicKey: pcdKeyPair, macKey: macKey)
|
||||
|
||||
Log.verbose( "Expecting authentication token from passport - \(expectedPICCToken)")
|
||||
|
||||
let piccToken = [UInt8](tvlResp[0].value)
|
||||
Log.verbose( "Received authentication token from passport - \(piccToken)")
|
||||
|
||||
guard piccToken == expectedPICCToken else {
|
||||
Log.error( "Error PICC Token mismatch!\npicToken - \(piccToken)\nexpectedPICCToken - \(expectedPICCToken)" )
|
||||
throw NFCPassportReaderError.PACEError( "Step3 KeyAgreement", "Error PICC Token mismatch!\npicToken - \(piccToken)\nexpectedPICCToken - \(expectedPICCToken)" )
|
||||
}
|
||||
|
||||
Log.debug( "Auth token from passport matches expected token!" )
|
||||
|
||||
// This will be added for CAM when supported
|
||||
// var encryptedChipAuthenticationData : [UInt8]? = nil
|
||||
// if (sself.mappingType == PACEMappingType.CAM) {
|
||||
// if tvlResp[1].tag != 0x8A {
|
||||
// Log.warning("CAM: Was expecting tag 0x86, found: \(binToHex(UInt8(tvlResp[1].tag)))")
|
||||
// }
|
||||
// encryptedChipAuthenticationData = [UInt8](tvlResp[1].value)
|
||||
// }
|
||||
|
||||
// We're done!
|
||||
return (encKey, macKey)
|
||||
}
|
||||
|
||||
/// Called once PACE has completed with the newly generated ksEnc and ksMac keys for restarting secure messaging
|
||||
/// - Parameters:
|
||||
/// - ksEnc: the computed encryption key derived from the key agreement
|
||||
/// - ksMac: the computed mac key derived from the key agreement
|
||||
func paceCompleted( ksEnc: [UInt8], ksMac: [UInt8] ) throws {
|
||||
// Restart secure messaging
|
||||
let ssc = withUnsafeBytes(of: 0.bigEndian, Array.init)
|
||||
if (cipherAlg.hasPrefix("DESede")) {
|
||||
Log.info( "Restarting secure messaging using DESede encryption")
|
||||
let sm = SecureMessaging(encryptionAlgorithm: .DES, ksenc: ksEnc, ksmac: ksMac, ssc: ssc)
|
||||
tagReader.secureMessaging = sm
|
||||
} else if (cipherAlg.hasPrefix("AES")) {
|
||||
Log.info( "Restarting secure messaging using AES encryption")
|
||||
let sm = SecureMessaging(encryptionAlgorithm: .AES, ksenc: ksEnc, ksmac: ksMac, ssc: ssc)
|
||||
tagReader.secureMessaging = sm
|
||||
} else {
|
||||
throw NFCPassportReaderError.PACEError( "PACECompleted", "Not restarting secure messaging as unsupported cipher algorithm requested - \(cipherAlg)" )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK - PACEHandler Utility functions
|
||||
@available(iOS 15, *)
|
||||
extension PACEHandler {
|
||||
|
||||
/// Does the DH key Mapping agreement
|
||||
/// - Parameter mappingKey - Pointer to an EVP_PKEY structure containing the mapping key
|
||||
/// - Parameter passportPublicKeyData - byte array containing the publick key read from the passport
|
||||
/// - Parameter nonce - Pointer to an BIGNUM structure containing the unencrypted nonce
|
||||
/// - Returns the EVP_PKEY containing the mapped ephemeral parameters
|
||||
func doDHMappingAgreement( mappingKey : OpaquePointer, passportPublicKeyData: [UInt8], nonce: OpaquePointer ) throws -> OpaquePointer {
|
||||
guard let dh_mapping_key = EVP_PKEY_get1_DH(mappingKey) else {
|
||||
// Error
|
||||
throw PACEHandlerError.DHKeyAgreementError( "Unable to get DH mapping key" )
|
||||
}
|
||||
|
||||
// Compute the shared secret using the mapping key and the passports public mapping key
|
||||
let bn = BN_bin2bn(passportPublicKeyData, Int32(passportPublicKeyData.count), nil)
|
||||
defer { BN_free( bn ) }
|
||||
|
||||
var secret = [UInt8](repeating: 0, count: Int(DH_size(dh_mapping_key)))
|
||||
DH_compute_key( &secret, bn, dh_mapping_key)
|
||||
|
||||
// Convert the secret to a bignum
|
||||
let bn_h = BN_bin2bn(secret, Int32(secret.count), nil)
|
||||
defer { BN_clear_free(bn_h) }
|
||||
|
||||
// Initialize ephemeral parameters with parameters from the mapping key
|
||||
guard let ephemeral_key = DHparams_dup(dh_mapping_key) else {
|
||||
// Error
|
||||
throw PACEHandlerError.DHKeyAgreementError("Unable to get initialise ephemeral parameters from DH mapping key")
|
||||
}
|
||||
defer{ DH_free(ephemeral_key) }
|
||||
|
||||
var p : OpaquePointer? = nil
|
||||
var q : OpaquePointer? = nil
|
||||
var g : OpaquePointer? = nil
|
||||
DH_get0_pqg(dh_mapping_key, &p, &q, &g)
|
||||
|
||||
// map to new generator
|
||||
guard let bn_g = BN_new() else {
|
||||
throw PACEHandlerError.DHKeyAgreementError( "Unable to create bn_g" )
|
||||
}
|
||||
defer{ BN_free(bn_g) }
|
||||
guard let new_g = BN_new() else {
|
||||
throw PACEHandlerError.DHKeyAgreementError( "Unable to create new_g" )
|
||||
}
|
||||
defer{ BN_free(new_g) }
|
||||
|
||||
// bn_g = g^nonce mod p
|
||||
// ephemeral_key->g = bn_g mod p * h => (g^nonce mod p) * h mod p
|
||||
let bn_ctx = BN_CTX_new()
|
||||
guard BN_mod_exp(bn_g, g, nonce, p, bn_ctx) == 1,
|
||||
BN_mod_mul(new_g, bn_g, bn_h, p, bn_ctx) == 1 else {
|
||||
// Error
|
||||
throw PACEHandlerError.DHKeyAgreementError( "Failed to generate new parameters" )
|
||||
}
|
||||
|
||||
guard DH_set0_pqg(ephemeral_key, BN_dup(p), BN_dup(q), BN_dup(new_g)) == 1 else {
|
||||
// Error
|
||||
throw PACEHandlerError.DHKeyAgreementError( "Unable to set DH pqg paramerters" )
|
||||
}
|
||||
|
||||
// Set the ephemeral params
|
||||
guard let ephemeralParams = EVP_PKEY_new() else {
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create ephemeral params" )
|
||||
}
|
||||
|
||||
guard EVP_PKEY_set1_DH(ephemeralParams, ephemeral_key) == 1 else {
|
||||
// Error
|
||||
EVP_PKEY_free( ephemeralParams )
|
||||
throw PACEHandlerError.DHKeyAgreementError( "Unable to set ephemeral parameters" )
|
||||
}
|
||||
return ephemeralParams
|
||||
}
|
||||
|
||||
/// Does the ECDH key Mapping agreement
|
||||
/// - Parameter mappingKey - Pointer to an EVP_PKEY structure containing the mapping key
|
||||
/// - Parameter passportPublicKeyData - byte array containing the publick key read from the passport
|
||||
/// - Parameter nonce - Pointer to an BIGNUM structure containing the unencrypted nonce
|
||||
/// - Returns the EVP_PKEY containing the mapped ephemeral parameters
|
||||
func doECDHMappingAgreement( mappingKey : OpaquePointer, passportPublicKeyData: [UInt8], nonce: OpaquePointer ) throws -> OpaquePointer {
|
||||
|
||||
let ec_mapping_key = EVP_PKEY_get1_EC_KEY(mappingKey)
|
||||
|
||||
guard let group = EC_GROUP_dup(EC_KEY_get0_group(ec_mapping_key)) else {
|
||||
// Error
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to get EC group" )
|
||||
}
|
||||
defer { EC_GROUP_free(group) }
|
||||
|
||||
guard let order = BN_new() else {
|
||||
// Error
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create order bignum" )
|
||||
}
|
||||
defer { BN_free( order ) }
|
||||
|
||||
guard let cofactor = BN_new() else {
|
||||
// error
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create cofactor bignum" )
|
||||
}
|
||||
defer { BN_free( cofactor ) }
|
||||
|
||||
guard EC_GROUP_get_order(group, order, nil) == 1 ||
|
||||
EC_GROUP_get_cofactor(group, cofactor, nil) == 1 else {
|
||||
// Handle error
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to get order or cofactor from group" )
|
||||
}
|
||||
|
||||
// Create the shared secret in the form of a ECPoint
|
||||
|
||||
// Ideally I'd use OpenSSLUtls.computeSharedSecret for this but for reasons as yet unknown, it only returns the first 32 bytes
|
||||
// NOT the full 64 bytes (would then convert to 65 with e header of 4 for uncompressed)
|
||||
guard let sharedSecretMappingPoint = self.computeECDHMappingKeyPoint(privateKey: mappingKey, inputKey: passportPublicKeyData) else {
|
||||
// Error
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Failed to compute new shared secret mapping point from mapping key and passport public mapping key" )
|
||||
}
|
||||
defer { EC_POINT_free( sharedSecretMappingPoint ) }
|
||||
|
||||
// Map the nonce using Generic mapping to get the new parameters (inc a new generator)
|
||||
guard let newGenerater = EC_POINT_new(group) else {
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create new mapping generator point" )
|
||||
}
|
||||
defer{ EC_POINT_free(newGenerater) }
|
||||
|
||||
// g = (generator * nonce) + (sharedSecretMappingPoint * 1)
|
||||
guard EC_POINT_mul(group, newGenerater, nonce, sharedSecretMappingPoint, BN_value_one(), nil) == 1 else {
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Failed to map nonce to get new generator params" )
|
||||
}
|
||||
|
||||
// Initialize ephemeral parameters with parameters from the mapping key
|
||||
guard let ephemeralParams = EVP_PKEY_new() else {
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to create ephemeral params" )
|
||||
}
|
||||
|
||||
let ephemeral_key = EC_KEY_dup(ec_mapping_key)
|
||||
defer{ EC_KEY_free(ephemeral_key) }
|
||||
|
||||
// configure the new EC_KEY
|
||||
guard EVP_PKEY_set1_EC_KEY(ephemeralParams, ephemeral_key) == 1,
|
||||
EC_GROUP_set_generator(group, newGenerater, order, cofactor) == 1,
|
||||
EC_GROUP_check(group, nil) == 1,
|
||||
EC_KEY_set_group(ephemeral_key, group) == 1 else {
|
||||
// Error
|
||||
|
||||
EVP_PKEY_free( ephemeralParams )
|
||||
throw PACEHandlerError.ECDHKeyAgreementError( "Unable to configure new ephemeral params" )
|
||||
}
|
||||
return ephemeralParams
|
||||
}
|
||||
|
||||
/// Generate Authentication token from a publicKey and and a mac key
|
||||
/// - Parameters:
|
||||
/// - publicKey: An EVP_PKEY structure containing a public key data which will be used to generate the auth code
|
||||
/// - macKey: The mac key derived from the key agreement
|
||||
/// - Throws: An error if we are unable to encode the public key data
|
||||
/// - Returns: The authentication token (8 bytes)
|
||||
func generateAuthenticationToken( publicKey: OpaquePointer, macKey: [UInt8] ) throws -> [UInt8] {
|
||||
var encodedPublicKeyData = try encodePublicKey(oid:self.paceOID, key:publicKey)
|
||||
|
||||
if cipherAlg == "DESede" {
|
||||
// If DESede (3DES), we need to pad the data
|
||||
encodedPublicKeyData = pad(encodedPublicKeyData, blockSize: 8)
|
||||
}
|
||||
|
||||
Log.verbose( "Generating Authentication Token" )
|
||||
Log.verbose( "EncodedPubKey = \(binToHexRep(encodedPublicKeyData, asArray: true))" )
|
||||
Log.verbose( "macKey = \(binToHexRep(macKey, asArray: true))" )
|
||||
|
||||
let maccedPublicKeyDataObject = mac(algoName: cipherAlg == "DESede" ? .DES : .AES, key: macKey, msg: encodedPublicKeyData)
|
||||
|
||||
// Take 8 bytes for auth token
|
||||
let authToken = [UInt8](maccedPublicKeyDataObject[0..<8])
|
||||
Log.verbose( "Generated authToken = \(binToHexRep(authToken, asArray: true))" )
|
||||
return authToken
|
||||
}
|
||||
|
||||
/// Encodes a PublicKey as an TLV strucuture based on TR-SAC 1.01 4.5.1 and 4.5.2
|
||||
/// - Parameters:
|
||||
/// - oid: The object identifier specifying the key type
|
||||
/// - key: The ECP_PKEY public key to encode
|
||||
/// - Throws: Error if unable to encode
|
||||
/// - Returns: the encoded public key in tlv format
|
||||
func encodePublicKey( oid : String, key : OpaquePointer ) throws -> [UInt8] {
|
||||
let encodedOid = oidToBytes(oid:oid, replaceTag: false)
|
||||
guard let pubKeyData = OpenSSLUtils.getPublicKeyData(from: key) else {
|
||||
Log.error( "PACEHandler: encodePublicKey() - Unable to get public key data" )
|
||||
throw NFCPassportReaderError.InvalidDataPassed("Unable to get public key data")
|
||||
}
|
||||
|
||||
let keyType = EVP_PKEY_base_id( key )
|
||||
let tag : TKTLVTag
|
||||
if keyType == EVP_PKEY_DH || keyType == EVP_PKEY_DHX {
|
||||
tag = 0x84
|
||||
} else {
|
||||
tag = 0x86
|
||||
}
|
||||
|
||||
guard let encOid = TKBERTLVRecord(from: Data(encodedOid)) else {
|
||||
throw NFCPassportReaderError.InvalidASN1Value
|
||||
}
|
||||
let encPub = TKBERTLVRecord(tag:tag, value: Data(pubKeyData))
|
||||
let record = TKBERTLVRecord(tag: 0x7F49, records:[encOid, encPub])
|
||||
let data = record.data
|
||||
|
||||
return [UInt8](data)
|
||||
}
|
||||
|
||||
/// Computes a key seed based on an MRZ key
|
||||
/// - Parameter the mrz key
|
||||
/// - Returns a encoded key based on the mrz key that can be used for PACE
|
||||
func createPaceKey( from mrzKey: String ) throws -> [UInt8] {
|
||||
let buf: [UInt8] = Array(mrzKey.utf8)
|
||||
let hash = calcSHA1Hash(buf)
|
||||
|
||||
let smskg = SecureMessagingSessionKeyGenerator()
|
||||
let key = try smskg.deriveKey(keySeed: hash, cipherAlgName: cipherAlg, keyLength: keyLength, nonce: nil, mode: .PACE_MODE, paceKeyReference: paceKeyType)
|
||||
return key
|
||||
}
|
||||
|
||||
/// Performs the ECDH PACE GM key agreement protocol by multiplying a private key with a public key
|
||||
/// - Parameters:
|
||||
/// - key: an EVP_PKEY structure containng a ECDH private key
|
||||
/// - inputKey: a public key
|
||||
/// - Returns: a new EC_POINT
|
||||
func computeECDHMappingKeyPoint( privateKey : OpaquePointer, inputKey : [UInt8] ) -> OpaquePointer? {
|
||||
|
||||
let ecdh = EVP_PKEY_get1_EC_KEY(privateKey)
|
||||
defer { EC_KEY_free(ecdh) }
|
||||
|
||||
let privateECKey = EC_KEY_get0_private_key(ecdh) // BIGNUM
|
||||
|
||||
// decode public key
|
||||
guard let group = EC_KEY_get0_group(ecdh) else{ return nil }
|
||||
guard let ecp = EC_POINT_new(group) else { return nil }
|
||||
defer { EC_POINT_free(ecp) }
|
||||
guard EC_POINT_oct2point(group, ecp, inputKey, inputKey.count,nil) != 0 else { return nil }
|
||||
|
||||
// create our output point
|
||||
let output = EC_POINT_new(group)
|
||||
|
||||
// Multiply our private key with the passports public key to get a new point
|
||||
EC_POINT_mul(group, output, nil, ecp, privateECKey, nil)
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
405
app/ios/AwesomeProject/PassportReader.swift
Normal file
405
app/ios/AwesomeProject/PassportReader.swift
Normal file
@@ -0,0 +1,405 @@
|
||||
import Foundation
|
||||
|
||||
#if !os(macOS)
|
||||
import UIKit
|
||||
import CoreNFC
|
||||
|
||||
@available(iOS 15, *)
|
||||
public class PassportReader : NSObject {
|
||||
private typealias NFCCheckedContinuation = CheckedContinuation<NFCPassportModel, Error>
|
||||
private var nfcContinuation: NFCCheckedContinuation?
|
||||
|
||||
private var passport : NFCPassportModel = NFCPassportModel()
|
||||
|
||||
private var readerSession: NFCTagReaderSession?
|
||||
private var currentlyReadingDataGroup : DataGroupId?
|
||||
|
||||
private var dataGroupsToRead : [DataGroupId] = []
|
||||
private var readAllDatagroups = false
|
||||
private var skipSecureElements = true
|
||||
private var skipCA = false
|
||||
private var skipPACE = false
|
||||
|
||||
private var bacHandler : BACHandler?
|
||||
private var caHandler : ChipAuthenticationHandler?
|
||||
private var paceHandler : PACEHandler?
|
||||
private var mrzKey : String = ""
|
||||
private var dataAmountToReadOverride : Int? = nil
|
||||
|
||||
private var scanCompletedHandler: ((NFCPassportModel?, NFCPassportReaderError?)->())!
|
||||
private var nfcViewDisplayMessageHandler: ((NFCViewDisplayMessage) -> String?)?
|
||||
private var masterListURL : URL?
|
||||
private var shouldNotReportNextReaderSessionInvalidationErrorUserCanceled : Bool = false
|
||||
|
||||
// By default, Passive Authentication uses the new RFS5652 method to verify the SOD, but can be switched to use
|
||||
// the previous OpenSSL CMS verification if necessary
|
||||
public var passiveAuthenticationUsesOpenSSL : Bool = false
|
||||
|
||||
public init( logLevel: LogLevel = .info, masterListURL: URL? = nil ) {
|
||||
super.init()
|
||||
|
||||
Log.logLevel = logLevel
|
||||
self.masterListURL = masterListURL
|
||||
}
|
||||
|
||||
public func setMasterListURL( _ masterListURL : URL ) {
|
||||
self.masterListURL = masterListURL
|
||||
}
|
||||
|
||||
// This function allows you to override the amount of data the TagReader tries to read from the NFC
|
||||
// chip. NOTE - this really shouldn't be used for production but is useful for testing as different
|
||||
// passports support different data amounts.
|
||||
// It appears that the most reliable is 0xA0 (160 chars) but some will support arbitary reads (0xFF or 256)
|
||||
public func overrideNFCDataAmountToRead( amount: Int ) {
|
||||
dataAmountToReadOverride = amount
|
||||
}
|
||||
|
||||
public func readPassport( mrzKey : String, tags : [DataGroupId] = [], skipSecureElements : Bool = true, skipCA : Bool = false, skipPACE : Bool = false, customDisplayMessage : ((NFCViewDisplayMessage) -> String?)? = nil) async throws -> NFCPassportModel {
|
||||
|
||||
self.passport = NFCPassportModel()
|
||||
self.mrzKey = mrzKey
|
||||
self.skipCA = skipCA
|
||||
self.skipPACE = skipPACE
|
||||
|
||||
self.dataGroupsToRead.removeAll()
|
||||
self.dataGroupsToRead.append( contentsOf:tags)
|
||||
self.nfcViewDisplayMessageHandler = customDisplayMessage
|
||||
self.skipSecureElements = skipSecureElements
|
||||
self.currentlyReadingDataGroup = nil
|
||||
self.bacHandler = nil
|
||||
self.caHandler = nil
|
||||
self.paceHandler = nil
|
||||
|
||||
// If no tags specified, read all
|
||||
if self.dataGroupsToRead.count == 0 {
|
||||
// Start off with .COM, will always read (and .SOD but we'll add that after), and then add the others from the COM
|
||||
self.dataGroupsToRead.append(contentsOf:[.COM, .SOD] )
|
||||
self.readAllDatagroups = true
|
||||
} else {
|
||||
// We are reading specific datagroups
|
||||
self.readAllDatagroups = false
|
||||
}
|
||||
|
||||
guard NFCNDEFReaderSession.readingAvailable else {
|
||||
throw NFCPassportReaderError.NFCNotSupported
|
||||
}
|
||||
|
||||
if NFCTagReaderSession.readingAvailable {
|
||||
readerSession = NFCTagReaderSession(pollingOption: [.iso14443], delegate: self, queue: nil)
|
||||
|
||||
self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.requestPresentPassport )
|
||||
readerSession?.begin()
|
||||
}
|
||||
|
||||
return try await withCheckedThrowingContinuation({ (continuation: NFCCheckedContinuation) in
|
||||
self.nfcContinuation = continuation
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15, *)
|
||||
extension PassportReader : NFCTagReaderSessionDelegate {
|
||||
// MARK: - NFCTagReaderSessionDelegate
|
||||
public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
|
||||
// If necessary, you may perform additional operations on session start.
|
||||
// At this point RF polling is enabled.
|
||||
Log.debug( "tagReaderSessionDidBecomeActive" )
|
||||
}
|
||||
|
||||
public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
|
||||
// If necessary, you may handle the error. Note session is no longer valid.
|
||||
// You must create a new session to restart RF polling.
|
||||
Log.debug( "tagReaderSession:didInvalidateWithError - \(error.localizedDescription)" )
|
||||
self.readerSession?.invalidate()
|
||||
self.readerSession = nil
|
||||
|
||||
if let readerError = error as? NFCReaderError, readerError.code == NFCReaderError.readerSessionInvalidationErrorUserCanceled
|
||||
&& self.shouldNotReportNextReaderSessionInvalidationErrorUserCanceled {
|
||||
|
||||
self.shouldNotReportNextReaderSessionInvalidationErrorUserCanceled = false
|
||||
} else {
|
||||
var userError = NFCPassportReaderError.UnexpectedError
|
||||
if let readerError = error as? NFCReaderError {
|
||||
Log.error( "tagReaderSession:didInvalidateWithError - Got NFCReaderError - \(readerError.localizedDescription)" )
|
||||
switch (readerError.code) {
|
||||
case NFCReaderError.readerSessionInvalidationErrorUserCanceled:
|
||||
Log.error( " - User cancelled session" )
|
||||
userError = NFCPassportReaderError.UserCanceled
|
||||
default:
|
||||
Log.error( " - some other error - \(readerError.localizedDescription)" )
|
||||
userError = NFCPassportReaderError.UnexpectedError
|
||||
}
|
||||
} else {
|
||||
Log.error( "tagReaderSession:didInvalidateWithError - Received error - \(error.localizedDescription)" )
|
||||
}
|
||||
nfcContinuation?.resume(throwing: userError)
|
||||
nfcContinuation = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
|
||||
Log.debug( "tagReaderSession:didDetect - \(tags[0])" )
|
||||
if tags.count > 1 {
|
||||
Log.debug( "tagReaderSession:more than 1 tag detected! - \(tags)" )
|
||||
|
||||
let errorMessage = NFCViewDisplayMessage.error(.MoreThanOneTagFound)
|
||||
self.invalidateSession(errorMessage: errorMessage, error: NFCPassportReaderError.MoreThanOneTagFound)
|
||||
return
|
||||
}
|
||||
|
||||
let tag = tags.first!
|
||||
var passportTag: NFCISO7816Tag
|
||||
switch tags.first! {
|
||||
case let .iso7816(tag):
|
||||
passportTag = tag
|
||||
default:
|
||||
Log.debug( "tagReaderSession:invalid tag detected!!!" )
|
||||
|
||||
let errorMessage = NFCViewDisplayMessage.error(NFCPassportReaderError.TagNotValid)
|
||||
self.invalidateSession(errorMessage:errorMessage, error: NFCPassportReaderError.TagNotValid)
|
||||
return
|
||||
}
|
||||
|
||||
Task { [passportTag] in
|
||||
do {
|
||||
try await session.connect(to: tag)
|
||||
|
||||
Log.debug( "tagReaderSession:connected to tag - starting authentication" )
|
||||
self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.authenticatingWithPassport(0) )
|
||||
|
||||
let tagReader = TagReader(tag:passportTag)
|
||||
|
||||
if let newAmount = self.dataAmountToReadOverride {
|
||||
tagReader.overrideDataAmountToRead(newAmount: newAmount)
|
||||
}
|
||||
|
||||
tagReader.progress = { [unowned self] (progress) in
|
||||
if let dgId = self.currentlyReadingDataGroup {
|
||||
self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.readingDataGroupProgress(dgId, progress) )
|
||||
} else {
|
||||
self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.authenticatingWithPassport(progress) )
|
||||
}
|
||||
}
|
||||
|
||||
let passportModel = try await self.startReading( tagReader : tagReader)
|
||||
nfcContinuation?.resume(returning: passportModel)
|
||||
nfcContinuation = nil
|
||||
|
||||
|
||||
} catch let error as NFCPassportReaderError {
|
||||
let errorMessage = NFCViewDisplayMessage.error(error)
|
||||
self.invalidateSession(errorMessage: errorMessage, error: error)
|
||||
} catch let error {
|
||||
|
||||
nfcContinuation?.resume(throwing: error)
|
||||
nfcContinuation = nil
|
||||
Log.debug( "tagReaderSession:failed to connect to tag - \(error.localizedDescription)" )
|
||||
let errorMessage = NFCViewDisplayMessage.error(NFCPassportReaderError.ConnectionError)
|
||||
self.invalidateSession(errorMessage: errorMessage, error: NFCPassportReaderError.ConnectionError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateReaderSessionMessage(alertMessage: NFCViewDisplayMessage ) {
|
||||
self.readerSession?.alertMessage = self.nfcViewDisplayMessageHandler?(alertMessage) ?? alertMessage.description
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15, *)
|
||||
extension PassportReader {
|
||||
|
||||
func startReading(tagReader : TagReader) async throws -> NFCPassportModel {
|
||||
|
||||
if !skipPACE {
|
||||
do {
|
||||
let data = try await tagReader.readCardAccess()
|
||||
Log.verbose( "Read CardAccess - data \(binToHexRep(data))" )
|
||||
let cardAccess = try CardAccess(data)
|
||||
passport.cardAccess = cardAccess
|
||||
|
||||
Log.info( "Starting Password Authenticated Connection Establishment (PACE)" )
|
||||
|
||||
let paceHandler = try PACEHandler( cardAccess: cardAccess, tagReader: tagReader )
|
||||
try await paceHandler.doPACE(mrzKey: mrzKey )
|
||||
passport.PACEStatus = .success
|
||||
Log.debug( "PACE Succeeded" )
|
||||
} catch {
|
||||
passport.PACEStatus = .failed
|
||||
Log.error( "PACE Failed - falling back to BAC" )
|
||||
}
|
||||
|
||||
_ = try await tagReader.selectPassportApplication()
|
||||
}
|
||||
|
||||
// If either PACE isn't supported, we failed whilst doing PACE or we didn't even attempt it, then fall back to BAC
|
||||
if passport.PACEStatus != .success {
|
||||
try await doBACAuthentication(tagReader : tagReader)
|
||||
}
|
||||
|
||||
// Now to read the datagroups
|
||||
try await readDataGroups(tagReader: tagReader)
|
||||
|
||||
self.updateReaderSessionMessage(alertMessage: NFCViewDisplayMessage.successfulRead)
|
||||
|
||||
try await doActiveAuthenticationIfNeccessary(tagReader : tagReader)
|
||||
self.shouldNotReportNextReaderSessionInvalidationErrorUserCanceled = true
|
||||
self.readerSession?.invalidate()
|
||||
|
||||
// If we have a masterlist url set then use that and verify the passport now
|
||||
self.passport.verifyPassport(masterListURL: self.masterListURL, useCMSVerification: self.passiveAuthenticationUsesOpenSSL)
|
||||
|
||||
return self.passport
|
||||
}
|
||||
|
||||
|
||||
func doActiveAuthenticationIfNeccessary( tagReader : TagReader) async throws {
|
||||
guard self.passport.activeAuthenticationSupported else {
|
||||
return
|
||||
}
|
||||
|
||||
Log.info( "Performing Active Authentication" )
|
||||
|
||||
let challenge = generateRandomUInt8Array(8)
|
||||
Log.verbose( "Generated Active Authentication challange - \(binToHexRep(challenge))")
|
||||
let response = try await tagReader.doInternalAuthentication(challenge: challenge)
|
||||
self.passport.verifyActiveAuthentication( challenge:challenge, signature:response.data )
|
||||
}
|
||||
|
||||
|
||||
func doBACAuthentication(tagReader : TagReader) async throws {
|
||||
self.currentlyReadingDataGroup = nil
|
||||
|
||||
Log.info( "Starting Basic Access Control (BAC)" )
|
||||
|
||||
self.passport.BACStatus = .failed
|
||||
|
||||
self.bacHandler = BACHandler( tagReader: tagReader )
|
||||
try await bacHandler?.performBACAndGetSessionKeys( mrzKey: mrzKey )
|
||||
Log.info( "Basic Access Control (BAC) - SUCCESS!" )
|
||||
|
||||
self.passport.BACStatus = .success
|
||||
}
|
||||
|
||||
func readDataGroups( tagReader: TagReader ) async throws {
|
||||
|
||||
// Read COM
|
||||
var DGsToRead = [DataGroupId]()
|
||||
|
||||
self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.readingDataGroupProgress(.COM, 0) )
|
||||
if let com = try await readDataGroup(tagReader:tagReader, dgId:.COM) as? COM {
|
||||
self.passport.addDataGroup( .COM, dataGroup:com )
|
||||
|
||||
// SOD and COM shouldn't be present in the DG list but just in case (worst case here we read the sod twice)
|
||||
DGsToRead = [.SOD] + com.dataGroupsPresent.map { DataGroupId.getIDFromName(name:$0) }
|
||||
DGsToRead.removeAll { $0 == .COM }
|
||||
}
|
||||
|
||||
if DGsToRead.contains( .DG14 ) {
|
||||
DGsToRead.removeAll { $0 == .DG14 }
|
||||
|
||||
if !skipCA {
|
||||
// Do Chip Authentication
|
||||
if let dg14 = try await readDataGroup(tagReader:tagReader, dgId:.DG14) as? DataGroup14 {
|
||||
self.passport.addDataGroup( .DG14, dataGroup:dg14 )
|
||||
let caHandler = ChipAuthenticationHandler(dg14: dg14, tagReader: tagReader)
|
||||
|
||||
if caHandler.isChipAuthenticationSupported {
|
||||
do {
|
||||
// Do Chip authentication and then continue reading datagroups
|
||||
try await caHandler.doChipAuthentication()
|
||||
self.passport.chipAuthenticationStatus = .success
|
||||
} catch {
|
||||
Log.info( "Chip Authentication failed - re-establishing BAC")
|
||||
self.passport.chipAuthenticationStatus = .failed
|
||||
|
||||
// Failed Chip Auth, need to re-establish BAC
|
||||
try await doBACAuthentication(tagReader: tagReader)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we are skipping secure elements then remove .DG3 and .DG4
|
||||
if self.skipSecureElements {
|
||||
DGsToRead = DGsToRead.filter { $0 != .DG3 && $0 != .DG4 }
|
||||
}
|
||||
|
||||
if self.readAllDatagroups != true {
|
||||
DGsToRead = DGsToRead.filter { dataGroupsToRead.contains($0) }
|
||||
}
|
||||
for dgId in DGsToRead {
|
||||
self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.readingDataGroupProgress(dgId, 0) )
|
||||
if let dg = try await readDataGroup(tagReader:tagReader, dgId:dgId) {
|
||||
self.passport.addDataGroup( dgId, dataGroup:dg )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readDataGroup( tagReader : TagReader, dgId : DataGroupId ) async throws -> DataGroup? {
|
||||
|
||||
self.currentlyReadingDataGroup = dgId
|
||||
Log.info( "Reading tag - \(dgId)" )
|
||||
var readAttempts = 0
|
||||
|
||||
self.updateReaderSessionMessage( alertMessage: NFCViewDisplayMessage.readingDataGroupProgress(dgId, 0) )
|
||||
|
||||
repeat {
|
||||
do {
|
||||
let response = try await tagReader.readDataGroup(dataGroup:dgId)
|
||||
let dg = try DataGroupParser().parseDG(data: response)
|
||||
return dg
|
||||
} catch let error as NFCPassportReaderError {
|
||||
Log.error( "TagError reading tag - \(error)" )
|
||||
|
||||
// OK we had an error - depending on what happened, we may want to try to re-read this
|
||||
// E.g. we failed to read the last Datagroup because its protected and we can't
|
||||
let errMsg = error.value
|
||||
Log.error( "ERROR - \(errMsg)" )
|
||||
|
||||
var redoBAC = false
|
||||
if errMsg == "Session invalidated" || errMsg == "Class not supported" || errMsg == "Tag connection lost" {
|
||||
// Check if we have done Chip Authentication, if so, set it to nil and try to redo BAC
|
||||
if self.caHandler != nil {
|
||||
self.caHandler = nil
|
||||
redoBAC = true
|
||||
} else {
|
||||
// Can't go any more!
|
||||
throw error
|
||||
}
|
||||
} else if errMsg == "Security status not satisfied" || errMsg == "File not found" {
|
||||
// Can't read this element as we aren't allowed - remove it and return out so we re-do BAC
|
||||
self.dataGroupsToRead.removeFirst()
|
||||
redoBAC = true
|
||||
} else if errMsg == "SM data objects incorrect" || errMsg == "Class not supported" {
|
||||
// Can't read this element security objects now invalid - and return out so we re-do BAC
|
||||
redoBAC = true
|
||||
} else if errMsg.hasPrefix( "Wrong length" ) || errMsg.hasPrefix( "End of file" ) { // Should now handle errors 0x6C xx, and 0x67 0x00
|
||||
// OK passport can't handle max length so drop it down
|
||||
tagReader.reduceDataReadingAmount()
|
||||
redoBAC = true
|
||||
}
|
||||
|
||||
if redoBAC {
|
||||
// Redo BAC and try again
|
||||
try await doBACAuthentication(tagReader : tagReader)
|
||||
} else {
|
||||
// Some other error lets have another try
|
||||
}
|
||||
}
|
||||
readAttempts += 1
|
||||
} while ( readAttempts < 2 )
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func invalidateSession(errorMessage: NFCViewDisplayMessage, error: NFCPassportReaderError) {
|
||||
// Mark the next 'invalid session' error as not reportable (we're about to cause it by invalidating the
|
||||
// session). The real error is reported back with the call to the completed handler
|
||||
self.shouldNotReportNextReaderSessionInvalidationErrorUserCanceled = true
|
||||
self.readerSession?.invalidate(errorMessage: self.nfcViewDisplayMessageHandler?(errorMessage) ?? errorMessage.description)
|
||||
nfcContinuation?.resume(throwing: error)
|
||||
nfcContinuation = nil
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user