key_schedule_test.go raw

   1  package mls
   2  
   3  import (
   4  	"bytes"
   5  	"fmt"
   6  	"testing"
   7  )
   8  
   9  type pskSecretTest struct {
  10  	CipherSuite CipherSuite `json:"cipher_suite"`
  11  
  12  	PSKs []struct {
  13  		PSKID    testBytes `json:"psk_id"`
  14  		PSK      testBytes `json:"psk"`
  15  		PSKNonce testBytes `json:"psk_nonce"`
  16  	} `json:"psks"`
  17  
  18  	PSKSecret testBytes `json:"psk_secret"`
  19  }
  20  
  21  func testPSKSecret(t *testing.T, tc *pskSecretTest) {
  22  	var (
  23  		pskIDs []preSharedKeyID
  24  		psks   [][]byte
  25  	)
  26  	for _, psk := range tc.PSKs {
  27  		pskIDs = append(pskIDs, preSharedKeyID{
  28  			pskType:  pskTypeExternal,
  29  			pskID:    []byte(psk.PSKID),
  30  			pskNonce: []byte(psk.PSKNonce),
  31  		})
  32  		psks = append(psks, []byte(psk.PSK))
  33  	}
  34  
  35  	pskSecret, err := extractPSKSecret(tc.CipherSuite, pskIDs, psks)
  36  	if err != nil {
  37  		t.Fatalf("extractPSKSecret() = %v", err)
  38  	}
  39  	if !bytes.Equal(pskSecret, []byte(tc.PSKSecret)) {
  40  		t.Errorf("extractPSKSecret() = %v, want %v", pskSecret, tc.PSKSecret)
  41  	}
  42  }
  43  
  44  func TestPSKSecret(t *testing.T) {
  45  	var tests []pskSecretTest
  46  	loadTestVector(t, "testdata/psk_secret.json", &tests)
  47  
  48  	for i, tc := range tests {
  49  		t.Run(fmt.Sprintf("[%v]", i), func(t *testing.T) {
  50  			testPSKSecret(t, &tc)
  51  		})
  52  	}
  53  }
  54  
  55  type keyScheduleTest struct {
  56  	CipherSuite CipherSuite `json:"cipher_suite"`
  57  
  58  	GroupID           testBytes `json:"group_id"`
  59  	InitialInitSecret testBytes `json:"initial_init_secret"`
  60  
  61  	Epochs []struct {
  62  		TreeHash                testBytes `json:"tree_hash"`
  63  		CommitSecret            testBytes `json:"commit_secret"`
  64  		PSKSecret               testBytes `json:"psk_secret"`
  65  		ConfirmedTranscriptHash testBytes `json:"confirmed_transcript_hash"`
  66  
  67  		GroupContext testBytes `json:"group_context"`
  68  
  69  		JoinerSecret  testBytes `json:"joiner_secret"`
  70  		WelcomeSecret testBytes `json:"welcome_secret"`
  71  		InitSecret    testBytes `json:"init_secret"`
  72  
  73  		SenderDataSecret   testBytes `json:"sender_data_secret"`
  74  		EncryptionSecret   testBytes `json:"encryption_secret"`
  75  		ExporterSecret     testBytes `json:"exporter_secret"`
  76  		EpochAuthenticator testBytes `json:"epoch_authenticator"`
  77  		ExternalSecret     testBytes `json:"external_secret"`
  78  		ConfirmationKey    testBytes `json:"confirmation_key"`
  79  		MembershipKey      testBytes `json:"membership_key"`
  80  		ResumptionPSK      testBytes `json:"resumption_psk"`
  81  
  82  		ExternalPub testBytes `json:"external_pub"`
  83  		Exporter    struct {
  84  			Label   string    `json:"label"`
  85  			Context testBytes `json:"context"`
  86  			Length  uint16    `json:"length"`
  87  			Secret  testBytes `json:"secret"`
  88  		} `json:"exporter"`
  89  	} `json:"epochs"`
  90  }
  91  
  92  func testKeySchedule(t *testing.T, tc *keyScheduleTest) {
  93  	initSecret := []byte(tc.InitialInitSecret)
  94  	for i, epoch := range tc.Epochs {
  95  		t.Logf("epoch %d", i)
  96  
  97  		ctx := groupContext{
  98  			version:                 protocolVersionMLS10,
  99  			cipherSuite:             tc.CipherSuite,
 100  			groupID:                 GroupID(tc.GroupID),
 101  			epoch:                   uint64(i),
 102  			treeHash:                []byte(epoch.TreeHash),
 103  			confirmedTranscriptHash: []byte(epoch.ConfirmedTranscriptHash),
 104  		}
 105  		rawCtx, err := marshal(&ctx)
 106  		if err != nil {
 107  			t.Fatalf("marshal(groupContext) = %v", err)
 108  		} else if !bytes.Equal(rawCtx, []byte(epoch.GroupContext)) {
 109  			t.Errorf("marshal(groupContext) = %v, want %v", rawCtx, epoch.GroupContext)
 110  		}
 111  
 112  		joinerSecret, err := ctx.extractJoinerSecret(initSecret, []byte(epoch.CommitSecret))
 113  		if err != nil {
 114  			t.Errorf("extractJoinerSecret() = %v", err)
 115  		} else if !bytes.Equal(joinerSecret, []byte(epoch.JoinerSecret)) {
 116  			t.Errorf("extractJoinerSecret() = %v, want %v", joinerSecret, epoch.JoinerSecret)
 117  		}
 118  
 119  		welcomeSecret, err := extractWelcomeSecret(ctx.cipherSuite, joinerSecret, []byte(epoch.PSKSecret))
 120  		if err != nil {
 121  			t.Errorf("extractWelcomeSecret() = %v", err)
 122  		} else if !bytes.Equal(welcomeSecret, []byte(epoch.WelcomeSecret)) {
 123  			t.Errorf("extractWelcomeSecret() = %v, want %v", welcomeSecret, epoch.WelcomeSecret)
 124  		}
 125  
 126  		epochSecret, err := ctx.extractEpochSecret(joinerSecret, []byte(epoch.PSKSecret))
 127  		if err != nil {
 128  			t.Fatalf("extractEpochSecret() = %v", err)
 129  		}
 130  
 131  		initSecret, err = ctx.cipherSuite.deriveSecret(epochSecret, secretLabelInit)
 132  		if err != nil {
 133  			t.Errorf("deriveSecret(init) = %v", err)
 134  		} else if !bytes.Equal(initSecret, []byte(epoch.InitSecret)) {
 135  			t.Errorf("deriveSecret(init) = %v, want %v", initSecret, epoch.InitSecret)
 136  		}
 137  
 138  		secrets := []struct {
 139  			label []byte
 140  			want  testBytes
 141  		}{
 142  			{secretLabelSenderData, epoch.SenderDataSecret},
 143  			{secretLabelEncryption, epoch.EncryptionSecret},
 144  			{secretLabelExporter, epoch.ExporterSecret},
 145  			{secretLabelExternal, epoch.ExternalSecret},
 146  			{secretLabelConfirm, epoch.ConfirmationKey},
 147  			{secretLabelMembership, epoch.MembershipKey},
 148  			{secretLabelResumption, epoch.ResumptionPSK},
 149  		}
 150  		for _, secret := range secrets {
 151  			sec, err := ctx.cipherSuite.deriveSecret(epochSecret, secret.label)
 152  			if err != nil {
 153  				t.Errorf("deriveSecret(%v) = %v", string(secret.label), err)
 154  			} else if !bytes.Equal(sec, []byte(secret.want)) {
 155  				t.Errorf("deriveSecret(%v) = %v, want %v", string(secret.label), sec, secret.want)
 156  			}
 157  		}
 158  
 159  		externalSecret := []byte(epoch.ExternalSecret)
 160  		if ctx.cipherSuite.Supported() {
 161  			kem, _, _ := ctx.cipherSuite.hpke().Params()
 162  			pub, _ := kem.Scheme().DeriveKeyPair(externalSecret)
 163  			if b, err := pub.MarshalBinary(); err != nil {
 164  				t.Errorf("kem.PublicKey.MarshalBinary() = %v", err)
 165  			} else if !bytes.Equal(b, epoch.ExternalPub) {
 166  				t.Errorf("kem.PublicKey.MarshalBinary() = %v, want %v", b, epoch.ExternalPub)
 167  			}
 168  		}
 169  
 170  		exporterSecret := []byte(epoch.ExporterSecret)
 171  		b, err := deriveExporter(ctx.cipherSuite, exporterSecret, []byte(epoch.Exporter.Label), []byte(epoch.Exporter.Context), epoch.Exporter.Length)
 172  		if err != nil {
 173  			t.Errorf("deriveExporter() = %v", err)
 174  		} else if !bytes.Equal(b, epoch.Exporter.Secret) {
 175  			t.Errorf("deriveExporter() = %v, want %v", b, epoch.Exporter.Secret)
 176  		}
 177  	}
 178  }
 179  
 180  func TestKeySchedule(t *testing.T) {
 181  	var tests []keyScheduleTest
 182  	loadTestVector(t, "testdata/key-schedule.json", &tests)
 183  
 184  	for i, tc := range tests {
 185  		t.Run(fmt.Sprintf("[%v]", i), func(t *testing.T) {
 186  			testKeySchedule(t, &tc)
 187  		})
 188  	}
 189  }
 190  
 191  type transcriptHashesTest struct {
 192  	CipherSuite CipherSuite `json:"cipher_suite"`
 193  
 194  	ConfirmationKey             testBytes `json:"confirmation_key"`
 195  	AuthenticatedContent        testBytes `json:"authenticated_content"`
 196  	InterimTranscriptHashBefore testBytes `json:"interim_transcript_hash_before"`
 197  
 198  	ConfirmedTranscriptHashAfter testBytes `json:"confirmed_transcript_hash_after"`
 199  	InterimTranscriptHashAfter   testBytes `json:"interim_transcript_hash_after"`
 200  }
 201  
 202  func testTranscriptHashes(t *testing.T, tc *transcriptHashesTest) {
 203  	cs := tc.CipherSuite
 204  
 205  	var authContent authenticatedContent
 206  	if err := unmarshal([]byte(tc.AuthenticatedContent), &authContent); err != nil {
 207  		t.Fatalf("unmarshal() = %v", err)
 208  	} else if authContent.content.contentType != contentTypeCommit {
 209  		t.Fatalf("contentType = %v, want %v", authContent.content.contentType, contentTypeCommit)
 210  	}
 211  
 212  	if !authContent.auth.verifyConfirmationTag(cs, []byte(tc.ConfirmationKey), []byte(tc.ConfirmedTranscriptHashAfter)) {
 213  		t.Errorf("verifyConfirmationTag() failed")
 214  	}
 215  
 216  	confirmedTranscriptHashAfter, err := authContent.confirmedTranscriptHashInput().hash(cs, []byte(tc.InterimTranscriptHashBefore))
 217  	if err != nil {
 218  		t.Fatalf("confirmedTranscriptHashInput.hash() = %v", err)
 219  	} else if !bytes.Equal(confirmedTranscriptHashAfter, []byte(tc.ConfirmedTranscriptHashAfter)) {
 220  		t.Errorf("confirmedTranscriptHashInput.hash() = %v, want %v", confirmedTranscriptHashAfter, tc.ConfirmedTranscriptHashAfter)
 221  	}
 222  
 223  	interimTranscriptHashAfter, err := nextInterimTranscriptHash(cs, confirmedTranscriptHashAfter, authContent.auth.confirmationTag)
 224  	if err != nil {
 225  		t.Fatalf("nextInterimTranscriptHash() = %v", err)
 226  	} else if !bytes.Equal(interimTranscriptHashAfter, []byte(tc.InterimTranscriptHashAfter)) {
 227  		t.Errorf("nextInterimTranscriptHash() = %v, want %v", interimTranscriptHashAfter, tc.InterimTranscriptHashAfter)
 228  	}
 229  }
 230  
 231  func TestTranscriptHashes(t *testing.T) {
 232  	var tests []transcriptHashesTest
 233  	loadTestVector(t, "testdata/transcript-hashes.json", &tests)
 234  
 235  	for i, tc := range tests {
 236  		t.Run(fmt.Sprintf("[%v]", i), func(t *testing.T) {
 237  			testTranscriptHashes(t, &tc)
 238  		})
 239  	}
 240  }
 241