test_secret_tree.mx raw
1 //go:build !wasm
2
3 package mls
4
5 import "errors"
6
7 // TestSecretTree validates secret tree derivation and sender data key/nonce
8 // expansion against RFC 9420 test vectors (cipher_suite 3: entries 6,7,8).
9 func TestSecretTree() error {
10 cs := CipherSuite0x0003
11
12 for ei, entry := range stEntries {
13 // sender data key + nonce (independent of tree).
14 sdKey, err := expandSenderDataKey(cs, entry.sdSecret, entry.sdCiphertext)
15 if err != nil {
16 return errors.New("entry " | itoa(ei) | ": expandSenderDataKey: " | err.Error())
17 }
18 if !bytesEqual(sdKey, entry.sdKey) {
19 return errors.New("entry " | itoa(ei) | ": sender data key mismatch")
20 }
21 sdNonce, err := expandSenderDataNonce(cs, entry.sdSecret, entry.sdCiphertext)
22 if err != nil {
23 return errors.New("entry " | itoa(ei) | ": expandSenderDataNonce: " | err.Error())
24 }
25 if !bytesEqual(sdNonce, entry.sdNonce) {
26 return errors.New("entry " | itoa(ei) | ": sender data nonce mismatch")
27 }
28
29 // Build the secret tree.
30 tree, err := deriveSecretTree(cs, numLeaves(entry.numLeaves), entry.encryptionSecret)
31 if err != nil {
32 return errors.New("entry " | itoa(ei) | ": deriveSecretTree: " | err.Error())
33 }
34
35 for li, leaf := range entry.leaves {
36 ni := leafIndex(li).nodeIndex()
37
38 // Test both ratchet labels.
39 for _, label := range []ratchetLabel{ratchetLabelHandshake, ratchetLabelApplication} {
40 root, err := tree.deriveRatchetRoot(cs, ni, label)
41 if err != nil {
42 return errors.New("entry " | itoa(ei) | " leaf " | itoa(li) | ": deriveRatchetRoot: " | err.Error())
43 }
44
45 for _, gen := range leaf.gens {
46 // Advance ratchet from 0 to target generation.
47 r := root
48 for r.generation < uint32(gen.generation) {
49 r, err = r.deriveNext(cs)
50 if err != nil {
51 return errors.New("entry " | itoa(ei) | " leaf " | itoa(li) | ": deriveNext: " | err.Error())
52 }
53 }
54
55 key, err := r.deriveKey(cs)
56 if err != nil {
57 return errors.New("entry " | itoa(ei) | " leaf " | itoa(li) | ": deriveKey: " | err.Error())
58 }
59 nonce, err := r.deriveNonce(cs)
60 if err != nil {
61 return errors.New("entry " | itoa(ei) | " leaf " | itoa(li) | ": deriveNonce: " | err.Error())
62 }
63
64 var wantKey, wantNonce []byte
65 if string(label) == string(ratchetLabelHandshake) {
66 wantKey = gen.hsKey
67 wantNonce = gen.hsNonce
68 } else {
69 wantKey = gen.appKey
70 wantNonce = gen.appNonce
71 }
72 if !bytesEqual(key, wantKey) {
73 return errors.New("entry " | itoa(ei) | " leaf " | itoa(li) | " gen " | itoa(int(gen.generation)) | " " | string(label) | " key mismatch")
74 }
75 if !bytesEqual(nonce, wantNonce) {
76 return errors.New("entry " | itoa(ei) | " leaf " | itoa(li) | " gen " | itoa(int(gen.generation)) | " " | string(label) | " nonce mismatch")
77 }
78 }
79 }
80 }
81 }
82
83 return nil
84 }
85
86 type stGen struct {
87 generation uint32
88 appKey []byte
89 appNonce []byte
90 hsKey []byte
91 hsNonce []byte
92 }
93
94 type stLeaf struct {
95 gens []stGen
96 }
97
98 type stEntry struct {
99 numLeaves uint
100 encryptionSecret []byte
101 sdSecret []byte
102 sdCiphertext []byte
103 sdKey []byte
104 sdNonce []byte
105 leaves []stLeaf
106 }