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  }