AndyQ / NFCPassportReader

NFCPassportReader for iOS 13
MIT License
749 stars 242 forks source link

About NFC Tag Data Validation? #32

Closed puneetmahali closed 4 years ago

puneetmahali commented 4 years ago

Hi @AndyQ , Can you please let me know if I need to give some security checks with backend then which data(Something like Json data) need to validate with the DB? I mean like somehow we can validate DG(all Data Groups e.g. DG1, DG2......DG15) or after tag detecting we can store all chip data somewhere and validate it from the database.

AndyQ commented 4 years ago

Sorry, I'm not quite sure what you mean.

I'm can't say what checks you should or would need to carry out - that's totally up to you and what your requirements are!

For example if you needed to verify that a passport was genuine and not forged then you would want to do passive and active authentication, plus check revocation lists, and what ever checks you feel you would need to do. The code currently can do passive and active with - which includes verifying hashes of the datagroups to check for tampering and checking the certificates on the passport, but as mentioned before doesn't yet check revocation lists.

If however you just wanted to read the passport data and didn't actually care if the document is valid then you could skip the checks.

I'd recommend reading the ICAO documentation on epassports (I think I've linked it from github bit of not then a simple google search will find it). That goes into much more detail on what security checks should be done by dedicated readers which may help.

Cheers Andy

Sent from my iPhone

On 7 Jan 2020, at 18:01, Puneet Mahali notifications@github.com wrote:

 Hi @AndyQ , Can you please let me know if I need to give some security checks with backend then which data(Something like Json data) need to validate with the DB? I mean like somehow we can validate DG(all Data Groups e.g. DG1, DG2......DG15) or after tag detecting we can store all chip data somewhere and validate it from the database.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

puneetmahali commented 4 years ago

Hi Andy, Yeah I read the ICAO 9303 but my question is: After detecting the tag from the passport chip. Is there any way to see all detected data groups data in array / dictionary format like name, address, Date of birth etc etc. so , I can send the all data into the database?

Sent from my iPhone

On 7. Jan 2020, at 7:09 PM, Andy Qua notifications@github.com wrote:

Sorry, I'm not quite sure what you mean.

I'm can't say what checks you should or would need to carry out - that's totally up to you and what your requirements are!

For example if you needed to verify that a passport was genuine and not forged then you would want to do passive and active authentication, plus check revocation lists, and what ever checks you feel you would need to do. The code currently can do passive and active with - which includes verifying hashes of the datagroups to check for tampering and checking the certificates on the passport, but as mentioned before doesn't yet check revocation lists.

If however you just wanted to read the passport data and didn't actually care if the document is valid then you could skip the checks.

I'd recommend reading the ICAO documentation on epassports (I think I've linked it from github bit of not then a simple google search will find it). That goes into much more detail on what security checks should be done by dedicated readers which may help.

Cheers Andy

Sent from my iPhone

On 7 Jan 2020, at 18:01, Puneet Mahali notifications@github.com wrote:

 Hi @AndyQ , Can you please let me know if I need to give some security checks with backend then which data(Something like Json data) need to validate with the DB? I mean like somehow we can validate DG(all Data Groups e.g. DG1, DG2......DG15) or after tag detecting we can store all chip data somewhere and validate it from the database.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe. — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe.

AndyQ commented 4 years ago

Yes that's in the Passport model returned.

There are a couple of methods that may help: The dataGroupsPresent property says which datagroups were read (may be less that you requested if they weren't present) The getDataGroup method will get the raw datagroup.

If you want a different cut of this then you would probably need to modify the code to return what you need.

Cheers Andy

Sent from my iPhone

On 7 Jan 2020, at 18:55, Puneet Mahali notifications@github.com wrote:

Hi Andy, Yeah I read the ICAO 9303 but my question is: After detecting the tag from the passport chip. Is there any way to see all detected data groups data in array / dictionary format like name, address, Date of birth etc etc. so , I can send the all data into the database?

Sent from my iPhone

On 7. Jan 2020, at 7:09 PM, Andy Qua notifications@github.com wrote:

Sorry, I'm not quite sure what you mean.

I'm can't say what checks you should or would need to carry out - that's totally up to you and what your requirements are!

For example if you needed to verify that a passport was genuine and not forged then you would want to do passive and active authentication, plus check revocation lists, and what ever checks you feel you would need to do. The code currently can do passive and active with - which includes verifying hashes of the datagroups to check for tampering and checking the certificates on the passport, but as mentioned before doesn't yet check revocation lists.

If however you just wanted to read the passport data and didn't actually care if the document is valid then you could skip the checks.

I'd recommend reading the ICAO documentation on epassports (I think I've linked it from github bit of not then a simple google search will find it). That goes into much more detail on what security checks should be done by dedicated readers which may help.

Cheers Andy

Sent from my iPhone

On 7 Jan 2020, at 18:01, Puneet Mahali notifications@github.com wrote:

 Hi @AndyQ , Can you please let me know if I need to give some security checks with backend then which data(Something like Json data) need to validate with the DB? I mean like somehow we can validate DG(all Data Groups e.g. DG1, DG2......DG15) or after tag detecting we can store all chip data somewhere and validate it from the database.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe. — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

puneetmahali commented 4 years ago

I tried with the getDataGroup methods but I didn't get the data over there on debug time but anyway I will try it tomorrow again. Basically I need some kind of json data so I can validate from the backend(my own backend) so I can check the security checks somehow.

For e.g.:- When I read the Passport tag then it's detect the DG1, DG2 & DG14. So, I want these DG's(datagroups) to send into the my database. or as you said the getDataGroup will provide the raw data. So, I am assuming that raw data should be in array or dictionary format which holds the all chip datagroup details.

PS:-Can you please add some comment on case no. #30 . About:- I want to create my own pods with the help of your code in Swift but In framework project I am unable to set up / install the OpenSSL. Can you please explain the installation steps. I will be so thankful to you.

AndyQ commented 4 years ago

So once you have a specific DataGroup - you have two fields you have access to: body - a UInt8 array containing the raw data that makes up the datagroup (including header) data - a UInt8 array containing the raw datagroup data (what the hashes are calculated from)

You could create your own Json request based on that - but what extra things are you trying to validate above what is currently done?

if you want the entire raw data you could also just capture the byte stream before it gets parsed out into DataGroups if you really wanted to.

puneetmahali commented 4 years ago

Hi @AndyQ , Thanks for the answer on #30 . In this case, I don't want to use x.509 certificates n all at the moment. I am using the method like:- // If we want to read only specific data groups we can using: let dataGroups : [DataGroupId] = [.COM, .SOD, .DG1, .DG2, .DG7, .DG11, .DG12, .DG14, .DG15] passportReader.readPassport(mrzKey: mrzKey, tags:dataGroups, completed: { (passport, error) in if let passport = passport { // All good, we got a passport DispatchQueue.main.async { self.passportDetails.passport = passport self.showDetails = true }

Here I am getting the data from passport(means passport.dataGroupsRead) it hold the five array of the detected Groups COM,.SOD, DG1, DG2, DG14. Noe the DG1 holds the 15 elements dictonary like: ▿ 15 elements ▿ 0 : 2 elements

puneetmahali commented 4 years ago

So once you have a specific DataGroup - you have two fields you have access to: body - a UInt8 array containing the raw data that makes up the datagroup (including header) data - a UInt8 array containing the raw datagroup data (what the hashes are calculated from)

You could create your own Json request based on that - but what extra things are you trying to validate above what is currently done?

if you want the entire raw data you could also just capture the byte stream before it gets parsed out into DataGroups if you really wanted to.

I saw this comment lil late. Anyway, At the moment I need the detected groups data in a Json form with the hash keys. For example- If I detected the DG1 then I need all DG1 group detected data like id, Document_Type, Issuing State etc etc in a proper format which is mentioned in this document page no. 35. The Dict should hold the Key like DG1 and value says like filed, field value from the Passport chip & Hash Key.

AndyQ commented 4 years ago

OK, so DG1 is held internally as LDS tag identifiers (see ICAO 9303 Page doc 10 pages 15-18 for specific details but for example, 5A is the document number, 5F1F are the MRZ data elements, 5F03 is the document type, etc.

If you look a the top of the NFCPassportModel, you can see how the main elements from DG1 are extracted from the passportDataElements (DG1) You could get checksums out using keys 5F04, 5F05, 5F06, and. 5F07 (page 17 of above doc)

So that should give you pretty much what you need.

puneetmahali commented 4 years ago

Yeah I already work with PassportModel Class and try to figure it out how can I deal with that? Yes, on page number 17 there is all data checksums of these things but Somehow I need to make a Json (5A is the document number, 5F1F are the MRZ data elements, 5F03 is the document type, etc.) with document number, mrz data element document type etc. Also I am not getting the hash keys. Please let me know how can I get the HAsh Keys. I know in PassportModel class have the function:

`public func getHashesForDatagroups( hashAlgorythm: String ) -> [DataGroupId:[UInt8]] { var ret = [DataGroupId:[UInt8]]()

    for (key, value) in dataGroupsRead {
        if hashAlgorythm == "SHA256" {
            ret[key] = calcSHA256Hash(value.body)
        } else if hashAlgorythm == "SHA1" {
            ret[key] = calcSHA1Hash(value.body)
        }
    }

    return ret
}`

But I am getting 0 key/value pair from this.

PS:- Somehow I need to produce the Json something like this: `{ "5B": { "name": "firstName", "data": "Puneet", "hash": "xxx" }, "5F57": { "name": "dateOfBirth", "data": "920912", "hash": "xxx" }, "5F35": { "name": "gender", "data": "M", "hash": "xxx" }, "5F2C": { "name": "nationality", "data": "D", "hash": "xxxx" }, "59": { "name": "documentExpiryDate", "data": "241217", "hash": "xxx" }

}`

Please correct me if I am wrong, for each filed we have different hash key?

AndyQ commented 4 years ago

Ahh you won't get hashes for the individual fields unless you calculate those yourself.

The hashes stored in the Document Security Object (SOD) are for the each datagroup as a whole so you compare the calculated hash of the DataGroup data against the one stored in the SOD.

Also, you obviously need to have read the SOD to get the hashes out.

puneetmahali commented 4 years ago

From this function right?

`private func parseSODSignatureContent( _ content : String ) throws -> (String, [DataGroupId : String]){ var currentDG = "" var sodHashAlgo = "" var sodHashes : [DataGroupId : String] = [:]

    let lines = content.components(separatedBy: "\n")

    let dgList : [DataGroupId] = [.COM,.DG1,.DG2,.DG3,.DG4,.DG5,.DG6,.DG7,.DG8,.DG9,.DG10,.DG11,.DG12,.DG13,.DG14,.DG15,.DG16,.SOD]

}`

AndyQ commented 4 years ago

Yup - well thats were I extract all the hashes.

However thats only done IF you have a valid master list added and passive authentication is done (which I may change at some point to always do that if we a SOD read).

Anyway, if we did do passive authentication, then both the SOD hashes and the calculated DG hashes plus whether they match can be retrieved from the dataGroupHashes variable on the NFCPassportModel.

puneetmahali commented 4 years ago

Well. As I said earlier I am not using any certificates and Master list. Therefore without those how can we get the hash keys. I am lil confuse here. I am just read the specific data groups and wants the hash key somehow.

puneetmahali commented 4 years ago

At the moment NFCPassportModel not reading the SOD that's why not getting the hash keys right.

puneetmahali commented 4 years ago

As per the documentation: PassiveAuthentication Passive Authentication is now part of the main library and can be used to ensure that an E-Passport is valid and hasn't been tampered with.

So, I don't have Master list but after passive authentication the hash keys should be present.

AndyQ commented 4 years ago

Yup but currently I only extract the hashes from the SOD IF you have a master list (even though I don't think the master list isn't needed for the hash check)

This possibly isn't desirable and I'll look at changing that to always extract the hashes and checking them if possible.

I'll try to take a look tomorrow and see if that works.

puneetmahali commented 4 years ago

Thanks @AndyQ .Yes please check it how can we achieve the hash keys without master list. So we can get the hash key when we read only specific data groups.

I will be so thankful to you if you have a look into it.

AndyQ commented 4 years ago

OK, I've updated to 1.0.6 - the SOD Hashes are now always extracted and checked (if the SOD was read) even if the master list isn't provided.

puneetmahali commented 4 years ago

Thanks @AndyQ . I will going to check it out. One more quick question, I want to remove the OpenSSL as well. So, Will it affect anything to get the data? because I think OpenSSL will help to get the certificates file right. As I said earlier I am not using any kind of certificates and try to get the specific data groups only then doesn't make sense to use the OpenSSL. Right?

AndyQ commented 4 years ago

Well, you'd need to write your own ASN1 parser as I use the one in OpenSSL to extract the hashes from the SOD.

puneetmahali commented 4 years ago

So, You mean need to add one more file for example replacing the OpenSSLUtils file which having my own ASN1 parser. Do you have an idea how can I write the ASN1 parser? I mean any clue or more description will be helpful. Thanks a lot...!!

AndyQ commented 4 years ago

Sadly no - I used the one in OpenSSL so I didn't have to ! Its not a trivial thing to do and you'll need to really understand the certificate format too to make sense of the data.

There are a couple of ASN1 parsers for iOS on GitHub but I haven't looked at them in any detail.

puneetmahali commented 4 years ago

Thanks @AndyQ , I will check it out and if I have some problem then I will let you know.

puneetmahali commented 4 years ago

Hi @AndyQ I need one more help. From NFCPassportModel class I need to access: public func getDataGroup( _ id : DataGroupId ) -> DataGroup? { return dataGroupsRead[id] }

`public func getHashesForDatagroups( hashAlgorythm: String ) -> [DataGroupId:[UInt8]] { var ret = [DataGroupId:[UInt8]]()

    for (key, value) in dataGroupsRead {
        if hashAlgorythm == "SHA256" {
            ret[key] = calcSHA256Hash(value.body)
        } else if hashAlgorythm == "SHA1" {
            ret[key] = calcSHA1Hash(value.body)
        }
    }

    return ret
}`

I need these Func data with passport image and want to save into one dict / array. So I can produce the single Json with all data. Can you please let me know how can I do that?

puneetmahali commented 4 years ago

@AndyQ Somehow I need to create a like below Josn pattern: { "DG1": { "sodHash": "xxxxxxx", "53" : { "personalNumber": "<<<<<<<<<<<<<<" }, "59" : { "documentExpiryDate": "270528" }, "5F03" : { "documentSubType": "P<" }, "5A" : { "documentNumber": "<<<<<<<<<<<<<<" }, "5F06" : { "xxx": "3" }, "5F02" : { "personalNumber": "<" }, "5F35" : { "gender": "F" }, "5B" : { "lastName": "<<<<<<<<<<<<<<<<<<<<<<<<" }, "5F05" : { "personalNumber": "5" }, "5F28" : { "issuingAuthority": "D<<" }, "5F57" : { "dateOfBirth": "641012" }, "5F2C" : { "nationality": "D<<" }, "5F04" : { "personalNumber": "3" }, "5F07" : { "personalNumber": "4" }, "5F1F" : { "passportMRZ": "P<D<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<4" } } }

puneetmahali commented 4 years ago

from Passport Data elements I am getting something like: { "53": "<<<<<<<<<<<<<<", "59": "270528", "5F1F": "P<D<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<4", "5F06": "3", "5A": "C01X12122", "5F02": "<", "5F35": "F", "5B": "<<<<<<<<<<<<<<<<<<<<<<<<", "5F05": "5", "5F28": "D<<", "5F2C": "D<<", "5F04": "3", "5F57": "641012", "5F07": "4", "5F03": "P<" }

So, I need to merge the sodHashKey & description of the Key like 5F, 5F15 etc etc. Please give me an Idea so I can produce the Json. Thanks in advance.

AndyQ commented 4 years ago

You could also do it by using the existing methods on the NFCPassportModel class - BUT you'd have to have to hardcode the associated tag number e.g. you'd need to manually set "5A" to be the document number which isn't great - but the tag numbers and what they map to are shown in the NFCPassportModel

Ideally though you'd want direct access to the underlying DataGroups.which currently isn't exposed through the passport model, however it should be.

I'll make a version available shortly which exposes the dataGroupsRead as a read only public property on the model so you can get access to the underlying datagroup values. (You already have access to the hashes.

puneetmahali commented 4 years ago

Hi @AndyQ Thanks for your quick reply. Yes that's what I want. So, at the moment I have Hashes data into one dict which types is Struct and another dict is String type. For example:- var passportDataElementDictionary : [String:String] = [:] var dataGroupHashDictionary : [DataGroupId: DataGroupHash] = [:] // DataGroupId is enum and DataGroupHash is struct type.

So, now I need to merge these two dictionaries so I can parse into the single json. But unfortunately I am not able to do because the passport manager classes doesn't allow me also the dictionaries is different data types.

Can you please give me an idea for this or would be great if you can update the library. Thanks in advance!

AndyQ commented 4 years ago

I've just pushed out 1.0.7 which allows access to the underlying datagroups through the dataGroupsRead property.

I've also made the getName() property on DataGroupId public too so you can get the String name of a DataGroupId item.

puneetmahali commented 4 years ago

let me check.

puneetmahali commented 4 years ago

Hi @AndyQ , Thanks for the update. Still I am on the same point. I am already make public and get access to those properties / functions.

**In NFCPassportModel class - line number 73(public private(set) var dataGroupHashes = [DataGroupId: DataGroupHash]()) & line number 104 `public var passportDataElements : [String:String]? { guard let dg1 = dataGroupsRead[.DG1] as? DataGroup1 else { return nil }

    return dg1.elements
}

So, I need to merge these two dictionaries**`

How can I merge these two dictionaries and save into one json.

AndyQ commented 4 years ago

If you really need the above JSON format then you would probably need to do something like:

guard let dg1 = passportModel.dataGroupsRead[.DG1] as? DataGroup1,
        let sodHash = passportModel.dataGroupHashes[.SOD] else { return nil }

var dg1Val : [String:Any] = ["sodHash" : sodHash]

// These map from passport key to key description and should contain all the keys you want in the final doc
let passportDesc = ["53" : "personalNumber", "59" : "documentExpiryDate", .......]
for (key,val) in passportDesc {
    print( "key - \(key), val - \(val)")
    if let passportVal = dg1.elements[key] {
        dg1Val[key] = [val:passportVal]
    }
}

var jsonDoc = ["DG1": dg1Val]
puneetmahali commented 4 years ago

guard let dg1 = passportModel.dataGroupsRead[.DG1] as? DataGroup1, let sodHash = passportModel.dataGroupHashes[.SOD] else { return nil }

here return nil gives the error - unexpected non-void return value in void function

puneetmahali commented 4 years ago

because I called in main async thread and there is no completion handler, I think so...

puneetmahali commented 4 years ago

I am not getting sodHashes also from here: let sodHash = passportModel.dataGroupHashes[.SOD] It should be comes but I don't know why it comes nil.

AndyQ commented 4 years ago

Ahhh - just realised! the SOD itself doesn't have a hash! Only the Datagroup hashes are stored in the SOD!

Which makes sense when you think about it as how could you store the hash of the SOD inside itself?

puneetmahali commented 4 years ago

Yeah make sense I am not check that anyway it's working now. If I cancel the session it's not handling the session error like Invalidate session or something similar. Can you please check it?

AndyQ commented 4 years ago

Can't replicate that - I'm getting the didInvalidateWithError called when I cancel the session.

I'm closing this bug now - if you are still not getting the Invalidate - could you please raise a new bug with some more details?