From 5810421685732868e73e88473e65e77bd9c51ce9 Mon Sep 17 00:00:00 2001 From: Spiegel Date: Fri, 15 May 2026 12:37:55 +0900 Subject: [PATCH] parse: align SEIPD v2 chunk size rendering --- docs/rfc9580-coverage.md | 100 +++++++++++++++++++++++++++++++++++++++ parse/tags/tag18.go | 6 ++- parse/tags/tag18_test.go | 15 ++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 docs/rfc9580-coverage.md diff --git a/docs/rfc9580-coverage.md b/docs/rfc9580-coverage.md new file mode 100644 index 0000000..0bad066 --- /dev/null +++ b/docs/rfc9580-coverage.md @@ -0,0 +1,100 @@ +# RFC 9580 Coverage (Current State) + +This note summarizes current implementation status for features related to +RFC 4880, RFC 5581, RFC 6637, and draft RFC 4880bis / RFC 9580. + +## Summary + +- Stable base support is present for RFC 4880, RFC 5581, and RFC 6637. +- Partial support for draft RFC 4880bis features is present (mainly version 5 era features). +- RFC 9580 finalization coverage is incomplete, especially around version 6 semantics. + +## Implemented (Confirmed) + +1. Packet and subpacket IDs for AEAD-era features +- Tag 20 AEAD Encrypted Data Packet exists. + - parse/tags/tag20.go + - parse/values/tagid.go +- Subpacket 39 Preferred AEAD Ciphersuites exists. + - parse/tags/sub39.go + - parse/values/subpacketid.go +- Subpacket 33 Issuer Fingerprint exists with v4/v5 handling notes. + - parse/tags/sub33.go + +2. AEAD algorithm model +- AEAD algorithm IDs and IV/tag lengths are modeled. + - parse/values/aeadid.go + +3. S2K Argon2 support +- S2K ID 4 Argon2 parsing exists. + - parse/s2k/s2k.go + - parse/values/s2kid.go + +4. Version 5 packet handling (draft marker) +- Version model treats 5 as draft for multiple packet families. + - parse/values/version.go +- Secret key and secret subkey tests include Version 5 (draft) examples. + - parse/tags/tag05_test.go + - parse/tags/tag07_test.go + +5. SEIPD v2 parser path exists +- Tag 18 supports version 1 and version 2 parsing branches. + - parse/tags/tag18.go + +## Partial / Inconsistent + +1. Chunk size interpretation differs between Tag 18 and Tag 20 +- Tag 20 converts encoded chunk parameter to actual size (1 << (c + 6)). + - parse/tags/tag20.go +- Tag 18 currently exposes the raw one-octet value as plain integer. + - parse/tags/tag18.go + +2. Draft-oriented wording remains in output and tests +- Version 5 is labeled as draft in the Version model. + - parse/values/version.go +- Existing expected outputs in tests reflect draft wording. + - parse/tags/tag05_test.go + - parse/tags/tag07_test.go + +## Missing / Likely Gaps for RFC 9580 + +1. Version 6-oriented paths are not visible in version helpers +- Current helper constructors only encode old/current/draft sets around v4/v5. + - parse/values/version.go + +2. Key-version gated fingerprint handling may be too narrow +- One-pass signature packet path currently only accepts key version 5. + - parse/tags/tag04.go +- Public-key encrypted session key packet path recognizes key version 4/5 only. + - parse/tags/tag01.go + +3. No obvious v6-focused tests or test vectors in parser tests +- Current tests include v5 vectors and draft labels. + - parse/tags/tag02_test.go + - parse/tags/tag05_test.go + - parse/tags/tag07_test.go + +## Proposed Implementation Order (Small PR Units) + +1. Normalize feature inventory in docs and wording +- Decide whether Version 5 should still be surfaced as draft in user-visible output. + +2. Align chunk size behavior +- Make Tag 18 chunk-size rendering consistent with Tag 20. +- Add/adjust tests for expected value format. + +3. Add v6 version model and packet handling gates +- Extend version helpers for v6-aware labeling where required. +- Update tag01/tag04 key-version checks and fingerprint-length logic as needed. + +4. Add test vectors for v6 paths +- Introduce focused parser tests before broad refactors. + +5. Update README and architecture notes +- Keep claimed support level synchronized with actual parser behavior. + +## Validation Checklist per PR + +- task test +- task govulncheck +- Expected output snapshots updated only where behavior intentionally changed diff --git a/parse/tags/tag18.go b/parse/tags/tag18.go index f708cfe..dbfbd4b 100644 --- a/parse/tags/tag18.go +++ b/parse/tags/tag18.go @@ -79,9 +79,11 @@ func (t *tag18) parseV2(rootInfo *result.Item) (*result.Item, error) { if err != nil { return rootInfo, errs.New("illegal chunk size", errs.WithCause(err)) } + chunkSize := uint64(1) << (sz + 6) rootInfo.Add(result.NewItem( - result.Name("chunk size"), - result.Value(strconv.Itoa(int(sz))), + result.Name("Chunk size"), + result.Value(strconv.FormatUint(chunkSize, 10)), + result.DumpStr(values.DumpByteString(byte(sz), true)), )) // [04] Thirty-two octets of salt. s, err := t.reader.ReadBytes(32) diff --git a/parse/tags/tag18_test.go b/parse/tags/tag18_test.go index a2818c1..932a8bf 100644 --- a/parse/tags/tag18_test.go +++ b/parse/tags/tag18_test.go @@ -11,6 +11,7 @@ import ( var ( tag18Body1 = []byte{0x01, 0x6a, 0xe6, 0x71, 0xca, 0xff, 0xf6, 0xb1, 0xff, 0x3f, 0x71, 0xc8, 0x77, 0x45, 0x88, 0x51, 0xff, 0xe3, 0xf2, 0xc3, 0x95, 0x57, 0xe7, 0x29, 0x80, 0xe8, 0xe5, 0x86, 0x7c, 0xea, 0x98, 0xf4, 0x04, 0xb3, 0x8a, 0xf8, 0x88, 0xc8, 0x91, 0xf7, 0x56, 0x7b, 0xcb, 0xad, 0x75, 0x40, 0x48, 0xd1, 0x5a, 0x3f, 0x3f, 0x2c, 0x1d, 0xe4, 0x36, 0xbb, 0xe9, 0xf7, 0x77, 0xb2, 0xb8, 0x2a, 0x44, 0x03, 0xbe, 0x78, 0xe2, 0x05, 0x3b, 0x44, 0xb6, 0xd8, 0x4e, 0x61, 0xa5, 0x43, 0x05, 0x76, 0x8a, 0x3c, 0x64} + tag18Body2 = []byte{0x02, 0x09, 0x02, 0x0e, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xde, 0xad, 0xbe, 0xef} ) const ( @@ -28,6 +29,19 @@ const ( 01 6a e6 71 ca ff f6 b1 ff 3f 71 c8 77 45 88 51 ff e3 f2 c3 95 57 e7 29 80 e8 e5 86 7c ea 98 f4 04 b3 8a f8 88 c8 91 f7 56 7b cb ad 75 40 48 d1 5a 3f 3f 2c 1d e4 36 bb e9 f7 77 b2 b8 2a 44 03 be 78 e2 05 3b 44 b6 d8 4e 61 a5 43 05 76 8a 3c 64 Encrypted data (plain text + MDC SHA1(20 bytes); sym alg is specified in sym-key encrypted session key) 6a e6 71 ca ff f6 b1 ff 3f 71 c8 77 45 88 51 ff e3 f2 c3 95 57 e7 29 80 e8 e5 86 7c ea 98 f4 04 b3 8a f8 88 c8 91 f7 56 7b cb ad 75 40 48 d1 5a 3f 3f 2c 1d e4 36 bb e9 f7 77 b2 b8 2a 44 03 be 78 e2 05 3b 44 b6 d8 4e 61 a5 43 05 76 8a 3c 64 +` + tag18Result21 = `Sym. Encrypted Integrity Protected Data Packet (tag 18) (40 bytes) + 02 09 02 0e 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f de ad be ef + Symmetric Algorithm: AES with 256-bit key (sym 9) + 09 + AEAD Algorithm: OCB mode (aead 2) + 02 + Chunk size: 1048576 + 0e + salt + 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f + Encrypted data (4 bytes) + de ad be ef ` ) @@ -42,6 +56,7 @@ func TestTag1(t *testing.T) { {tag: 18, content: tag18Body1, ktm: nil, cxt: context.ModeNotSpecified, res: tag18Result11}, {tag: 18, content: tag18Body1, ktm: nil, cxt: context.ModePubEnc, res: tag18Result12}, {tag: 18, content: tag18Body1, ktm: nil, cxt: context.ModeSymEnc, res: tag18Result13}, + {tag: 18, content: tag18Body2, ktm: nil, cxt: context.ModeNotSpecified, res: tag18Result21}, } for _, tc := range testCases { op := &packet.OpaquePacket{Tag: tc.tag, Contents: tc.content}