-
Notifications
You must be signed in to change notification settings - Fork 210
Fix/jwt output #1452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix/jwt output #1452
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -251,10 +251,6 @@ async function main() { | |
| ); | ||
| } | ||
|
|
||
| // Decode for verification/logging (not used in circuit) | ||
| const eatNonce0Buffer = Buffer.from(eatNonce0Base64url, 'base64url'); | ||
| console.log(`[INFO] eat_nonce[0] decoded: ${eatNonce0Buffer.length} bytes`); | ||
|
|
||
| // Find offset of eat_nonce[0] in the decoded payload JSON | ||
| // Decode the payload from base64url to get the exact JSON string | ||
| const payloadJSON = Buffer.from(payloadB64, 'base64url').toString('utf8'); | ||
|
|
@@ -285,6 +281,30 @@ async function main() { | |
| eatNonce0CharCodes[i] = eatNonce0Base64url.charCodeAt(i); | ||
| } | ||
|
|
||
| const eatNonce1Base64url = payload.eat_nonce[1]; | ||
| console.log(`[INFO] eat_nonce[1] (base64url): ${eatNonce1Base64url}`); | ||
| console.log(`[INFO] eat_nonce[1] string length: ${eatNonce1Base64url.length} characters`); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Missing array length check before accessing second nonceThe code accesses |
||
|
|
||
| if (eatNonce1Base64url.length > MAX_EAT_NONCE_B64_LENGTH) { | ||
| throw new Error( | ||
| `[ERROR] eat_nonce[1] length ${eatNonce1Base64url.length} exceeds max ${MAX_EAT_NONCE_B64_LENGTH}` | ||
| ); | ||
| } | ||
|
|
||
| const eatNonce1ValueOffset = payloadJSON.indexOf(eatNonce1Base64url); | ||
| if (eatNonce1ValueOffset === -1) { | ||
| console.error('[ERROR] Could not find eat_nonce[1] value in decoded payload JSON'); | ||
| console.error('[DEBUG] Payload JSON:', payloadJSON); | ||
| console.error('[DEBUG] Looking for:', eatNonce1Base64url); | ||
| throw new Error('[ERROR] Could not find eat_nonce[1] value in decoded payload JSON'); | ||
| } | ||
| console.log(`[INFO] eat_nonce[1] value offset in payload: ${eatNonce1ValueOffset}`); | ||
|
|
||
| const eatNonce1CharCodes = new Array(MAX_EAT_NONCE_B64_LENGTH).fill(0); | ||
| for (let i = 0; i < eatNonce1Base64url.length; i++) { | ||
| eatNonce1CharCodes[i] = eatNonce1Base64url.charCodeAt(i); | ||
| } | ||
|
|
||
| // Extract image_digest from payload.submods.container.image_digest | ||
| if (!payload.submods?.container?.image_digest) { | ||
| throw new Error('[ERROR] No image_digest found in payload.submods.container'); | ||
|
|
@@ -378,6 +398,9 @@ async function main() { | |
| eat_nonce_0_key_offset: eatNonce0KeyOffset.toString(), | ||
| eat_nonce_0_value_offset: eatNonce0ValueOffset.toString(), | ||
|
|
||
| // EAT nonce[1] (circuit will extract value directly from payload) | ||
| eat_nonce_1_b64_length: eatNonce1Base64url.length.toString(), | ||
|
|
||
| // Container image digest (circuit will extract value directly from payload) | ||
| image_digest_length: imageDigest.length.toString(), | ||
| image_digest_key_offset: imageDigestKeyOffset.toString(), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,50 +74,51 @@ template ExtractAndVerifyJSONField( | |
| // Check character at colon+1: must be '[' (91) or space (32) | ||
| signal char_after_colon <== ItemAtIndex(maxJSONLength)(json, colon_position + 1); | ||
|
|
||
| signal value_start <== ItemAtIndex(maxJSONLength)(json, value_offset); | ||
|
|
||
| // is_bracket: 1 if char is '[', 0 otherwise | ||
| component is_bracket = IsEqual(); | ||
| is_bracket.in[0] <== char_after_colon; | ||
| is_bracket.in[1] <== 91; // '[' | ||
|
|
||
| // is_space: 1 if char is space, 0 otherwise | ||
| component is_space = IsEqual(); | ||
| is_space.in[0] <== char_after_colon; | ||
| is_space.in[1] <== 32; // ' ' | ||
|
|
||
| // Exactly one must be true: char is either '[' or space | ||
| is_bracket.out + is_space.out === 1; | ||
| // is_quote: 1 if char is quote, 0 otherwise | ||
| component is_quote = IsEqual(); | ||
| is_quote.in[0] <== char_after_colon; | ||
| is_quote.in[1] <== 34; // " | ||
|
|
||
| // If bracket at colon+1: check quote at colon+2, value at colon+3 | ||
| // If space at colon+1: check bracket at colon+2, quote at colon+3, value at colon+4 | ||
| // Exactly one must be true: char is either [ or quote | ||
| is_bracket.out + is_quote.out === 1; | ||
|
|
||
| // When is_bracket=1 (no space): expect quote at colon+2 | ||
| // When is_bracket=1 : expect quote at colon+2 | ||
| signal char_at_plus2 <== ItemAtIndex(maxJSONLength)(json, colon_position + 2); | ||
| // When is_space=1: expect bracket at colon+2 | ||
| // Constraint: if is_bracket=1, char_at_plus2 must be quote(34) | ||
| // if is_space=1, char_at_plus2 must be bracket(91) | ||
| // if is_quote=1, char_at_plus2 must be value[0] | ||
| is_bracket.out * (char_at_plus2 - 34) === 0; // If bracket at +1, quote at +2 | ||
| is_space.out * (char_at_plus2 - 91) === 0; // If space at +1, bracket at +2 | ||
|
|
||
| // When is_space=1: check quote at colon+3 | ||
| signal char_at_plus3 <== ItemAtIndex(maxJSONLength)(json, colon_position + 3); | ||
| is_space.out * (char_at_plus3 - 34) === 0; // If space at +1, quote at +3 | ||
|
|
||
| // Enforce value_offset based on pattern | ||
| // Pattern 1 (no space): :[" -> value at colon+3 | ||
| // Pattern 2 (space): : [" -> value at colon+4 | ||
| signal expected_value_offset <== colon_position + 3 + is_space.out; | ||
| value_offset === expected_value_offset; | ||
| component is_value_after_quote = IsEqual(); | ||
| is_value_after_quote.in[0] <== char_at_plus2; | ||
| is_value_after_quote.in[1] <== value_start; | ||
| is_quote.out * (1 - is_value_after_quote.out) === 0; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Missing value_offset constraint allows arbitrary value extractionThe constraint |
||
|
|
||
| // Extract value from JSON and output directly | ||
| extracted_value <== SelectSubArray( | ||
| maxJSONLength, | ||
| maxValueLength | ||
| )(json, value_offset, value_length); | ||
|
|
||
| // Validate value ends with closing quote and bracket: "value"] | ||
| // Validate value ends with closing quote and then either ']' or ',' after | ||
| signal closing_quote <== ItemAtIndex(maxJSONLength)(json, value_offset + value_length); | ||
| closing_quote === 34; // ASCII code for " | ||
|
|
||
| signal closing_bracket <== ItemAtIndex(maxJSONLength)(json, value_offset + value_length + 1); | ||
| closing_bracket === 93; // ASCII code for ] | ||
| // The character following the closing quote must be either ']' (93) or ',' (44) | ||
| signal char_after_quote <== ItemAtIndex(maxJSONLength)(json, value_offset + value_length + 1); | ||
| component is_closing_bracket = IsEqual(); | ||
| is_closing_bracket.in[0] <== char_after_quote; | ||
| is_closing_bracket.in[1] <== 93; // ']' | ||
|
|
||
| component is_comma = IsEqual(); | ||
| is_comma.in[0] <== char_after_quote; | ||
| is_comma.in[1] <== 44; // ',' | ||
|
|
||
| // Exactly one of the two must be true | ||
| is_closing_bracket.out + is_comma.out === 1; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Incorrect base64 length constant truncates valid nonces
The
MAX_EAT_NONCE_B64_LENGTHwas changed from 99 to 74, but the variable represents base64url character length, not decoded byte length. The comment even states "74 bytes decoded = 99 b64url chars". This causes the circuit to reject valid nonces with base64 lengths between 75-99 characters. Theprepare.tsfile still uses 99, creating an inconsistency. The length check at line 167 and theExtractAndVerifyJSONFieldtemplate instantiation will fail for full-length nonces that are valid per the GCP spec.Additional Locations (1)
circuits/circuits/gcp_jwt_verifier/gcp_jwt_verifier.circom#L163-L168