{"record":{"id":"7792eec2377de9f0","repo":"prestodb/presto","slug":"signature-mismatch-in-plaintext-footer","errorCode":null,"errorMessage":"Signature mismatch in plaintext footer","messagePattern":"Signature mismatch in plaintext footer","errorType":"exception","errorClass":"TagVerificationException","httpStatus":null,"severity":"critical","filePath":"presto-parquet/src/main/java/com/facebook/presto/parquet/cache/MetadataReader.java","lineNumber":341,"sourceCode":"    private static void verifyFooterIntegrity(BasicSliceInput from, InternalFileDecryptor fileDecryptor, int combinedFooterLength)\n    {\n        byte[] nonce = new byte[NONCE_LENGTH];\n        from.read(nonce);\n        byte[] gcmTag = new byte[GCM_TAG_LENGTH];\n        from.read(gcmTag);\n\n        AesGcmEncryptor footerSigner = fileDecryptor.createSignedFooterEncryptor();\n        int footerSignatureLength = NONCE_LENGTH + GCM_TAG_LENGTH;\n        byte[] serializedFooter = new byte[combinedFooterLength - footerSignatureLength];\n        from.setPosition(0);\n        from.read(serializedFooter, 0, serializedFooter.length);\n\n        byte[] signedFooterAuthenticationData = AesCipher.createFooterAAD(fileDecryptor.getFileAAD());\n        byte[] encryptedFooterBytes = footerSigner.encrypt(false, serializedFooter, nonce, signedFooterAuthenticationData);\n        byte[] calculatedTag = new byte[GCM_TAG_LENGTH];\n        System.arraycopy(encryptedFooterBytes, encryptedFooterBytes.length - GCM_TAG_LENGTH, calculatedTag, 0, GCM_TAG_LENGTH);\n        if (!Arrays.equals(gcmTag, calculatedTag)) {\n            throw new TagVerificationException(\"Signature mismatch in plaintext footer\");\n        }\n    }\n\n    private static MessageType readParquetSchema(List<SchemaElement> schema)\n    {\n        Iterator<SchemaElement> schemaIterator = schema.iterator();\n        SchemaElement rootSchema = schemaIterator.next();\n        Types.MessageTypeBuilder builder = Types.buildMessage();\n        readTypeSchema(builder, schemaIterator, rootSchema.getNum_children());\n        return builder.named(rootSchema.name);\n    }\n\n    private static void readTypeSchema(Types.GroupBuilder<?> builder, Iterator<SchemaElement> schemaIterator, int typeCount)\n    {\n        for (int i = 0; i < typeCount; i++) {\n            SchemaElement element = schemaIterator.next();\n            Types.Builder<?, ?> typeBuilder;\n            if (element.type == null) {","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-parquet/src/main/java/com/facebook/presto/parquet/cache/MetadataReader.java#L323-L359","documentation":"verifyFooterIntegrity recomputes the GCM authentication tag over the serialized footer using the footer key and footer AAD, and compares it to the tag stored in the file's footer_signing_key_metadata. A mismatch means the plaintext footer was tampered with, corrupted, or signed/verified with different key/AAD material — the encrypted-column contents may not correspond to the visible footer.","triggerScenarios":"Reading a file with a plaintext footer + footer signing metadata while fileDecryptor is present: encrypt(false, serializedFooter, nonce, footerAAD) produces a tag that differs from the stored gcmTag — wrong signing key, altered footer bytes, or different file AAD.","commonSituations":"Footer manually edited or file partially rewritten after signing; wrong key configured so the signature can't validate; AAD prefix differences between writer and reader; bit-rot or truncation of the footer region.","solutions":["Ensure the correct footer signing key (same as footer key at write time) is configured","Verify file integrity end-to-end (checksum) and re-download/re-copy the file if corrupted","Match the AAD prefix configuration used by the writer","Do NOT bypass verification; re-write the file from a trusted source if intentional modification occurred"],"exampleFix":"// before: key mismatch across environments\nString key = envSpecificKey();\n// after: use the key recorded for the table's encryption config\nString key = keyRetriever.getKey(footerSigningKeyId);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    readParquetMetadata(dataSource);\n} catch (TagVerificationException e) {\n    // footer signature mismatch: possible tampering — quarantine the file, never ignore\n    quarantineFile(path, e);\n    throw e;\n}","preventionTips":["Never edit or partially rewrite signed-encrypted Parquet files in place","Use the same footer/signing key across writer and all readers","Re-copy from a trusted source rather than bypassing verification","Monitor for TagVerificationException as a tampering signal"],"tags":["parquet","encryption","integrity","tampering"],"backgroundTag":"gcm-tag-verification-failed","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}