diff --git a/src/noir/lib/inclusion-check/country/src/lib.nr b/src/noir/lib/inclusion-check/country/src/lib.nr index ac6bda3f4..127b9301d 100644 --- a/src/noir/lib/inclusion-check/country/src/lib.nr +++ b/src/noir/lib/inclusion-check/country/src/lib.nr @@ -29,6 +29,28 @@ pub unconstrained fn unsafe_get_index( index } +/// Check that the country list is correctly padded at the end with the empty +/// country code (three null characters). Once an empty country code is encountered, +/// all subsequent entries must also be empty. This prevents injection of country +/// codes after the first padding entry. +/// +/// # Arguments +/// +/// * `country_list`: The list of countries +pub fn check_country_list_padding(country_list: [Alpha3CountryCode; N]) { + let empty = EMPTY_ALPHA3_COUNTRY_CODE.as_bytes(); + let mut found_padding = false; + for i in 0..N { + let is_empty = country_list[i].as_bytes() == empty; + if is_empty { + found_padding = true; + } + if found_padding { + assert(is_empty, "Country list cannot contain country codes after the padding"); + } + } +} + /// Check if the nationality from the MRZ is in the country list /// /// # Arguments @@ -36,6 +58,10 @@ pub unconstrained fn unsafe_get_index( /// * `dg1`: The MRZ /// * `country_list`: The list of countries pub fn check_nationality_inclusion(dg1: DG1Data, country_list: [Alpha3CountryCode; N]) { + // Make sure the country list is properly padded with empty country codes at the end + // so that no additional country code can be injected after the first padding entry + check_country_list_padding(country_list); + let nationality_bytes = get_nationality_from_mrz(dg1); // Safety: get the index of the country in the list from an unconstrained function @@ -59,6 +85,10 @@ pub fn check_issuing_country_inclusion( dg1: DG1Data, country_list: [Alpha3CountryCode; N], ) { + // Make sure the country list is properly padded with empty country codes at the end + // so that no additional country code can be injected after the first padding entry + check_country_list_padding(country_list); + let issuing_country_bytes = get_issuing_country_from_mrz(dg1); // Safety: get the index of the country in the list from an unconstrained function diff --git a/src/noir/lib/inclusion-check/country/src/tests.nr b/src/noir/lib/inclusion-check/country/src/tests.nr index 0e1ac97e3..5af9882ea 100644 --- a/src/noir/lib/inclusion-check/country/src/tests.nr +++ b/src/noir/lib/inclusion-check/country/src/tests.nr @@ -1,4 +1,7 @@ -use super::{check_issuing_country_inclusion, check_nationality_inclusion, unsafe_get_index}; +use super::{ + check_country_list_padding, check_issuing_country_inclusion, check_nationality_inclusion, + unsafe_get_index, +}; use utils::{constants::{EMPTY_ALPHA3_COUNTRY_CODE, SAMPLE_DG1}, types::Alpha3CountryCode}; fn pad_country_list(country_list: [Alpha3CountryCode; N]) -> [Alpha3CountryCode; 200] { @@ -47,3 +50,54 @@ fn test_check_issuing_country_inclusion_fail() { let country_list = pad_country_list(["FRA", "NZL", "USA"]); check_issuing_country_inclusion(SAMPLE_DG1, country_list); } + +#[test] +fn test_check_country_list_padding_padded() { + let country_list = pad_country_list(["AUS", "NZL", "USA"]); + check_country_list_padding(country_list); +} + +#[test] +fn test_check_country_list_padding_full_list() { + let country_list: [Alpha3CountryCode; 3] = ["AUS", "NZL", "USA"]; + check_country_list_padding(country_list); +} + +#[test] +fn test_check_country_list_padding_fully_empty() { + let country_list: [Alpha3CountryCode; 3] = + [EMPTY_ALPHA3_COUNTRY_CODE, EMPTY_ALPHA3_COUNTRY_CODE, EMPTY_ALPHA3_COUNTRY_CODE]; + check_country_list_padding(country_list); +} + +#[test(should_fail_with = "Country list cannot contain country codes after the padding")] +fn test_check_country_list_padding_injection_after_padding() { + let country_list: [Alpha3CountryCode; 4] = + ["AUS", EMPTY_ALPHA3_COUNTRY_CODE, "USA", EMPTY_ALPHA3_COUNTRY_CODE]; + check_country_list_padding(country_list); +} + +#[test(should_fail_with = "Country list cannot contain country codes after the padding")] +fn test_check_country_list_padding_starts_with_empty() { + let country_list: [Alpha3CountryCode; 3] = + [EMPTY_ALPHA3_COUNTRY_CODE, "AUS", EMPTY_ALPHA3_COUNTRY_CODE]; + check_country_list_padding(country_list); +} + +#[test(should_fail_with = "Country list cannot contain country codes after the padding")] +fn test_check_nationality_inclusion_fail_injection_after_padding() { + let mut country_list: [Alpha3CountryCode; 200] = [EMPTY_ALPHA3_COUNTRY_CODE; 200]; + country_list[0] = "AUS"; + // Inject a country code after the padding has started + country_list[5] = "USA"; + check_nationality_inclusion(SAMPLE_DG1, country_list); +} + +#[test(should_fail_with = "Country list cannot contain country codes after the padding")] +fn test_check_issuing_country_inclusion_fail_injection_after_padding() { + let mut country_list: [Alpha3CountryCode; 200] = [EMPTY_ALPHA3_COUNTRY_CODE; 200]; + country_list[0] = "AUS"; + // Inject a country code after the padding has started + country_list[5] = "USA"; + check_issuing_country_inclusion(SAMPLE_DG1, country_list); +}