Repository navigation
fix: require the face to match the chip photo before issuing - #78
Closed
jessevanmuijden wants to merge 2 commits into
Closed
jessevanmuijden wants to merge 2 commits into
jessevanmuijden wants to merge 2 commits into
Conversation
FaceTec reports two face matches: matchLevel against the photo printed on the document, which the SPOCP rules check, and matchLevelNFCToFaceMap against the photo on the chip, which facetec-api ignored. The chip gates prove the chip is genuine and the printed-photo match proves the face matches the data page, but nothing tied the chip to the person in front of the camera: a genuine document with a substituted printed photo passed every check, and its holder's identity and chip portrait were issued to someone else. A scan whose chip match is below facetec.min_chip_face_match_level (default 6, the default face-match-level rule; 0 selects the default, so the gate cannot be switched off), or for which FaceTec reported none, is now refused with chip_photo_mismatch. The gate runs after the chip gates and before the expiry gate, on /process-request and on the legacy /v1/id-scan. The level is recorded as chip_face_match_level in the issuance audit log. Fixes sirosfoundation#77
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This was referenced Oct 9, 2026
govulncheck reports five advisories against golang.org/x/net v0.59.0 (GO-2026-6603, -6610, -6611, -6612, -6617), all fixed in v0.60.0, reached through http2 (the audit-log webhook client and fmt). x/net is an indirect dependency, so dependabot's go-deps group (sirosfoundation#76) does not cover it.
Contributor
Author
|
Superseded by #80: the same commits, opened from a branch in this repo so the SonarCloud scan gets its token (fork PRs never do). Nothing else changed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #77
A hard gate on issuance: the user's face must match the photo on the document's chip, not only the photo printed on it.
Why
FaceTec reports two face matches:
matchLevelcompares the live FaceMap with the photo printed on the document. The SPOCP rules check it (face-match-level >= 06).matchLevelNFCToFaceMapcompares the live FaceMap with the photo on the chip (DG2). facetec-api ignored it.The chip gates (#66, #64) prove the chip is genuine, and the printed-photo match proves the face matches the data page. Nothing tied the chip to the person in front of the camera, so a genuine document with a substituted printed photo passed every check. Its holder's name, birth date and chip portrait would be issued to someone else.
The gate
ExtractScanResultreadsmatchLevelNFCToFaceMapintoIDScanResult.ChipFaceMatchLevel. It's a pointer, so "not reported" stays distinct from0. A value of the wrong type is an error (fail closed), like the other result fields.chip_photo_mismatch(new code) when the chip match is belowfacetec.min_chip_face_match_level, or when FaceTec reported none.FACETEC_MIN_CHIP_FACE_MATCH_LEVEL, default 6, the same as the defaultface-match-levelrule.0selects the default, so the gate cannot be switched off; values outside 0–10 are refused at startup./process-requestit runs after the NFC and chip gates and before the expiry gate. The order is now liveness → NFC → chip → chip photo → expiry → SPOCP.chip_face_match_level(-1when not reported).Legacy
/v1/id-scanThe legacy path gets the same gate. It reads
matchLevelNFCToFaceMapfrom FaceTec's legacy response if present. If that response does not carry it, every legacy issuance is now refused (fail closed). I think that's acceptable: the only legacy clients are the SDKs' FaceTec 9 delegates, which cannot run against FaceTec 10 (siros-sdk-kotlin#246). The apps use/process-request. Say so if the legacy path should be exempt instead.Evidence
FaceTec Server 10.1.4 returns the field. In a recorded session on the dev stack it is
0on the response before the chip read and7on the final response, after an authenticated read (nfcAuthenticationStatusEnumInt: 4).Apps
chip_photo_mismatchis a new refusal code. Until the apps and SDKs below merge, clients show their generic "could not be verified" text.ChipPhotoMismatch/chipPhotoMismatch,errorCodeidv_chip_photo_mismatch), in feat(idv): typed error for chip_photo_mismatch siros-sdk-kotlin#254 and feat(idv): typed error for chip_photo_mismatch siros-sdk-swift#191.The check itself stays server-side; the clients only explain the refusal.
Tests
matchLevelNFCToFaceMap: reported, reported as 0, absent, and a non-numeric value (an error).chipFaceRejection: not reported, below, at and above the minimum, a 0 level, the default minimum, and a stricter configured one./v1/id-scanpath.Validatebounds.go vetis clean, andgo test -race ./...passes, includingtests/integration.