Rod Hynes 9 лет назад
Родитель
Сommit
e54ccd827f
3 измененных файлов с 140 добавлено и 109 удалено
  1. 78 65
      psiphon/common/osl/osl.go
  2. 61 44
      psiphon/common/osl/osl_test.go
  3. 1 0
      psiphon/remoteServerList_test.go

+ 78 - 65
psiphon/common/osl/osl.go

@@ -87,9 +87,9 @@ type Scheme struct {
 	// If empty, the scheme applies to all regions.
 	Regions []string
 
-	// PropagationChannelIDs is a list of client propagation channel IDs
-	// this scheme applies to.
-	// If empty, the scheme applies to all propagation channels IDs.
+	// PropagationChannelIDs is a list of client propagtion channel IDs
+	// this scheme applies to. Propagation channel IDs are an input
+	// to SLOK key derivation.
 	PropagationChannelIDs []string
 
 	// MasterKey is the base random key used for SLOK key derivation. It
@@ -189,13 +189,14 @@ type KeySplit struct {
 
 // ClientSeedState tracks the progress of a client towards seeding SLOKs.
 type ClientSeedState struct {
-	scheme           *Scheme
-	signalIssueSLOKs chan struct{}
-	progress         []*TrafficValues
-	progressSLOKTime int64
-	mutex            sync.Mutex
-	issuedSLOKs      map[string]*SLOK
-	payloadSLOKs     []*SLOK
+	scheme               *Scheme
+	propagationChannelID string
+	signalIssueSLOKs     chan struct{}
+	progress             []*TrafficValues
+	progressSLOKTime     int64
+	mutex                sync.Mutex
+	issuedSLOKs          map[string]*SLOK
+	payloadSLOKs         []*SLOK
 }
 
 // ClientSeedPortForward map a client port forward, which is relaying
@@ -213,8 +214,9 @@ type ClientSeedPortForward struct {
 // used to derive the SLOK secret key and ID.
 // Note: SeedSpecID is not a []byte as slokReference is used as a map key.
 type slokReference struct {
-	SeedSpecID string
-	Time       time.Time
+	PropagationChannelID string
+	SeedSpecID           string
+	Time                 time.Time
 }
 
 // SLOK is a seeded SLOK issued to a client. The client will store the
@@ -334,10 +336,6 @@ func LoadConfig(configJSON []byte) (*Config, error) {
 // client progress towards seeding SLOKs. psiphond maintains one
 // ClientSeedState for each connected client.
 //
-// NewClientSeedState selects the first Scheme in config.Schemes
-// with an active Epoch and which permits the client's region and
-// propagation channel ID. Only the first matching scheme is selected.
-//
 // A signal is sent on signalIssueSLOKs when sufficient progress
 // has been made that a new SLOK *may* be issued. psiphond will
 // receive the signal and then call GetClientSeedPayload/IssueSLOKs
@@ -357,10 +355,8 @@ func (config *Config) NewClientSeedState(
 		// schemes with many propagation channel IDs or region filters, use
 		// maps for more efficient lookup.
 		if scheme.epoch.Before(time.Now().UTC()) &&
-			(len(scheme.PropagationChannelIDs) == 0 ||
-				common.Contains(scheme.PropagationChannelIDs, propagationChannelID)) &&
-			(len(scheme.Regions) == 0 ||
-				common.Contains(scheme.Regions, clientRegion)) {
+			common.Contains(scheme.PropagationChannelIDs, propagationChannelID) &&
+			(len(scheme.Regions) == 0 || common.Contains(scheme.Regions, clientRegion)) {
 
 			// Empty progress is initialized up front for all seed specs. Once
 			// created, the progress structure is read-only (the slice, not the
@@ -371,12 +367,13 @@ func (config *Config) NewClientSeedState(
 			}
 
 			return &ClientSeedState{
-				scheme:           scheme,
-				signalIssueSLOKs: signalIssueSLOKs,
-				progressSLOKTime: getSLOKTime(scheme.SeedPeriodNanoseconds),
-				progress:         progress,
-				issuedSLOKs:      make(map[string]*SLOK),
-				payloadSLOKs:     nil,
+				scheme:               scheme,
+				propagationChannelID: propagationChannelID,
+				signalIssueSLOKs:     signalIssueSLOKs,
+				progressSLOKTime:     getSLOKTime(scheme.SeedPeriodNanoseconds),
+				progress:             progress,
+				issuedSLOKs:          make(map[string]*SLOK),
+				payloadSLOKs:         nil,
 			}
 		}
 	}
@@ -532,8 +529,9 @@ func (state *ClientSeedState) issueSLOKs() {
 		if progress.exceeds(&seedSpec.Targets) {
 
 			ref := &slokReference{
-				SeedSpecID: string(seedSpec.ID),
-				Time:       progressSLOKTime,
+				PropagationChannelID: state.propagationChannelID,
+				SeedSpecID:           string(seedSpec.ID),
+				Time:                 progressSLOKTime,
 			}
 
 			state.scheme.derivedSLOKCacheMutex.RLock()
@@ -584,6 +582,7 @@ func deriveSLOK(
 
 	key := deriveKeyHKDF(
 		scheme.MasterKey,
+		[]byte(ref.PropagationChannelID),
 		[]byte(ref.SeedSpecID),
 		timeBytes)
 
@@ -683,6 +682,8 @@ type KeyShares struct {
 
 // Pave creates the full set of OSL files, for all schemes in the
 // configuration, to be dropped in an out-of-band distribution site.
+// Only OSLs for the propagation channel ID associated with the
+// distribution site are paved. This function is used by automation.
 //
 // The Name component of each file relates to the values returned by
 // the client functions GetRegistryURL and GetOSLFileURL.
@@ -701,6 +702,7 @@ type KeyShares struct {
 // to OSLs in the case where OSLs are repaved in subsequent calls.
 func (config *Config) Pave(
 	endTime time.Time,
+	propagationChannelID string,
 	signingPublicKey string,
 	signingPrivateKey string,
 	paveServerEntries []map[time.Time]string) ([]*PaveFile, error) {
@@ -723,48 +725,51 @@ func (config *Config) Pave(
 			slokTimePeriodsPerOSL *= keySplit.Total
 		}
 
-		oslTime := scheme.epoch
-		for !oslTime.After(endTime) {
-
-			firstSLOKTime := oslTime
-			fileKey, fileSpec, err := makeOSLFileSpec(scheme, firstSLOKTime)
-			if err != nil {
-				return nil, common.ContextError(err)
-			}
-
-			registry.FileSpecs = append(registry.FileSpecs, fileSpec)
+		if common.Contains(scheme.PropagationChannelIDs, propagationChannelID) {
+			oslTime := scheme.epoch
+			for !oslTime.After(endTime) {
 
-			serverEntries, ok := paveServerEntries[schemeIndex][oslTime]
-			if ok {
-
-				signedServerEntries, err := common.WriteAuthenticatedDataPackage(
-					serverEntries,
-					signingPublicKey,
-					signingPrivateKey)
+				firstSLOKTime := oslTime
+				fileKey, fileSpec, err := makeOSLFileSpec(
+					scheme, propagationChannelID, firstSLOKTime)
 				if err != nil {
 					return nil, common.ContextError(err)
 				}
 
-				boxedServerEntries, err := box(fileKey, compress(signedServerEntries))
-				if err != nil {
-					return nil, common.ContextError(err)
-				}
+				registry.FileSpecs = append(registry.FileSpecs, fileSpec)
 
-				md5sum := md5.Sum(boxedServerEntries)
-				fileSpec.MD5Sum = md5sum[:]
+				serverEntries, ok := paveServerEntries[schemeIndex][oslTime]
+				if ok {
 
-				fileName := fmt.Sprintf(
-					OSL_FILENAME_FORMAT, hex.EncodeToString(fileSpec.ID))
+					signedServerEntries, err := common.WriteAuthenticatedDataPackage(
+						serverEntries,
+						signingPublicKey,
+						signingPrivateKey)
+					if err != nil {
+						return nil, common.ContextError(err)
+					}
 
-				paveFiles = append(paveFiles, &PaveFile{
-					Name:     fileName,
-					Contents: boxedServerEntries,
-				})
-			}
+					boxedServerEntries, err := box(fileKey, compress(signedServerEntries))
+					if err != nil {
+						return nil, common.ContextError(err)
+					}
+
+					md5sum := md5.Sum(boxedServerEntries)
+					fileSpec.MD5Sum = md5sum[:]
 
-			oslTime = oslTime.Add(
-				time.Duration(
-					int64(slokTimePeriodsPerOSL) * scheme.SeedPeriodNanoseconds))
+					fileName := fmt.Sprintf(
+						OSL_FILENAME_FORMAT, hex.EncodeToString(fileSpec.ID))
+
+					paveFiles = append(paveFiles, &PaveFile{
+						Name:     fileName,
+						Contents: boxedServerEntries,
+					})
+				}
+
+				oslTime = oslTime.Add(
+					time.Duration(
+						int64(slokTimePeriodsPerOSL) * scheme.SeedPeriodNanoseconds))
+			}
 		}
 	}
 
@@ -796,11 +801,13 @@ func (config *Config) Pave(
 // tree, given sufficient SLOKs.
 func makeOSLFileSpec(
 	scheme *Scheme,
+	propagationChannelID string,
 	firstSLOKTime time.Time) ([]byte, *OSLFileSpec, error) {
 
 	ref := &slokReference{
-		SeedSpecID: string(scheme.SeedSpecs[0].ID),
-		Time:       firstSLOKTime,
+		PropagationChannelID: propagationChannelID,
+		SeedSpecID:           string(scheme.SeedSpecs[0].ID),
+		Time:                 firstSLOKTime,
 	}
 	firstSLOK := deriveSLOK(scheme, ref)
 	oslID := firstSLOK.ID
@@ -814,6 +821,7 @@ func makeOSLFileSpec(
 		scheme,
 		fileKey,
 		scheme.SeedPeriodKeySplits,
+		propagationChannelID,
 		&firstSLOKTime)
 	if err != nil {
 		return nil, nil, common.ContextError(err)
@@ -832,6 +840,7 @@ func divideKey(
 	scheme *Scheme,
 	key []byte,
 	keySplits []KeySplit,
+	propagationChannelID string,
 	nextSLOKTime *time.Time) (*KeyShares, error) {
 
 	keySplitIndex := len(keySplits) - 1
@@ -855,6 +864,7 @@ func divideKey(
 				scheme,
 				shareKey,
 				keySplits[0:keySplitIndex],
+				propagationChannelID,
 				nextSLOKTime)
 			if err != nil {
 				return nil, common.ContextError(err)
@@ -864,6 +874,7 @@ func divideKey(
 			keyShare, err := divideKeyWithSeedSpecSLOKs(
 				scheme,
 				shareKey,
+				propagationChannelID,
 				nextSLOKTime)
 			if err != nil {
 				return nil, common.ContextError(err)
@@ -890,6 +901,7 @@ func divideKey(
 func divideKeyWithSeedSpecSLOKs(
 	scheme *Scheme,
 	key []byte,
+	propagationChannelID string,
 	nextSLOKTime *time.Time) (*KeyShares, error) {
 
 	var boxedShares [][]byte
@@ -904,8 +916,9 @@ func divideKeyWithSeedSpecSLOKs(
 	for index, seedSpec := range scheme.SeedSpecs {
 
 		ref := &slokReference{
-			SeedSpecID: string(seedSpec.ID),
-			Time:       *nextSLOKTime,
+			PropagationChannelID: propagationChannelID,
+			SeedSpecID:           string(seedSpec.ID),
+			Time:                 *nextSLOKTime,
 		}
 		slok := deriveSLOK(scheme, ref)
 

+ 61 - 44
psiphon/common/osl/osl_test.go

@@ -300,62 +300,76 @@ func TestOSL(t *testing.T) {
 		t.Fatalf("GenerateAuthenticatedDataPackageKeys failed: %s", err)
 	}
 
-	files := make(map[string][]byte)
+	pavedRegistries := make(map[string][]byte)
+	pavedOSLFileContents := make(map[string]map[string][]byte)
 
 	t.Run("pave OSLs", func(t *testing.T) {
 
 		// Pave sufficient OSLs to cover simulated elapsed time of all test cases.
 		endTime := epoch.Add(1000 * time.Millisecond)
 
-		// Dummy server entry payloads will be the OSL ID, which the following
-		// tests use to verify that the correct OSL file decrypts successfully.
-		paveServerEntries := make([]map[time.Time]string, len(config.Schemes))
-		for schemeIndex, scheme := range config.Schemes {
+		// In actual deployment, paved files for each propagation channel ID
+		// are dropped in distinct distribution sites.
+		for _, propagationChannelID := range []string{
+			"2995DB0C968C59C4F23E87988D9C0D41",
+			"E742C25A6D8BA8C17F37E725FA628569",
+			"36F1CF2DF1250BF0C7BA0629CE3DC657"} {
 
-			paveServerEntries[schemeIndex] = make(map[time.Time]string)
+			// Dummy server entry payloads will be the OSL ID, which the following
+			// tests use to verify that the correct OSL file decrypts successfully.
+			paveServerEntries := make([]map[time.Time]string, len(config.Schemes))
+			for schemeIndex, scheme := range config.Schemes {
 
-			slokTimePeriodsPerOSL := 1
-			for _, keySplit := range scheme.SeedPeriodKeySplits {
-				slokTimePeriodsPerOSL *= keySplit.Total
-			}
+				paveServerEntries[schemeIndex] = make(map[time.Time]string)
+
+				slokTimePeriodsPerOSL := 1
+				for _, keySplit := range scheme.SeedPeriodKeySplits {
+					slokTimePeriodsPerOSL *= keySplit.Total
+				}
 
-			oslTime := scheme.epoch
-			for oslTime.Before(endTime) {
-				firstSLOKRef := &slokReference{
-					SeedSpecID: string(scheme.SeedSpecs[0].ID),
-					Time:       oslTime,
+				oslTime := scheme.epoch
+				for oslTime.Before(endTime) {
+					firstSLOKRef := &slokReference{
+						PropagationChannelID: propagationChannelID,
+						SeedSpecID:           string(scheme.SeedSpecs[0].ID),
+						Time:                 oslTime,
+					}
+					firstSLOK := deriveSLOK(scheme, firstSLOKRef)
+					oslID := firstSLOK.ID
+					paveServerEntries[schemeIndex][oslTime] =
+						base64.StdEncoding.EncodeToString(oslID)
+
+					oslTime = oslTime.Add(
+						time.Duration(
+							int64(slokTimePeriodsPerOSL) * scheme.SeedPeriodNanoseconds))
 				}
-				firstSLOK := deriveSLOK(scheme, firstSLOKRef)
-				oslID := firstSLOK.ID
-				paveServerEntries[schemeIndex][oslTime] =
-					base64.StdEncoding.EncodeToString(oslID)
-
-				oslTime = oslTime.Add(
-					time.Duration(
-						int64(slokTimePeriodsPerOSL) * scheme.SeedPeriodNanoseconds))
 			}
-		}
 
-		paveFiles, err := config.Pave(
-			endTime,
-			signingPublicKey,
-			signingPrivateKey,
-			paveServerEntries)
-		if err != nil {
-			t.Fatalf("Pave failed: %s", err)
-		}
+			paveFiles, err := config.Pave(
+				endTime,
+				propagationChannelID,
+				signingPublicKey,
+				signingPrivateKey,
+				paveServerEntries)
+			if err != nil {
+				t.Fatalf("Pave failed: %s", err)
+			}
 
-		// Check that the paved file name matches the name the client will look for.
-		if len(paveFiles) < 1 || paveFiles[len(paveFiles)-1].Name != GetOSLRegistryURL("") {
-			t.Fatalf("invalid registry pave file")
-		}
+			// Check that the paved file name matches the name the client will look for.
+			if len(paveFiles) < 1 || paveFiles[len(paveFiles)-1].Name != GetOSLRegistryURL("") {
+				t.Fatalf("invalid registry pave file")
+			}
 
-		for _, paveFile := range paveFiles {
-			files[paveFile.Name] = paveFile.Contents
+			pavedRegistries[propagationChannelID] = paveFiles[len(paveFiles)-1].Contents
+
+			pavedOSLFileContents[propagationChannelID] = make(map[string][]byte)
+			for _, paveFile := range paveFiles[0:len(paveFiles)] {
+				pavedOSLFileContents[propagationChannelID][paveFile.Name] = paveFile.Contents
+			}
 		}
 	})
 
-	if len(files) == 0 {
+	if len(pavedRegistries) != 3 {
 		// Previous subtest failed. Following tests cannot be completed, so abort.
 		t.Fatalf("pave failed")
 	}
@@ -480,8 +494,9 @@ func TestOSL(t *testing.T) {
 					slok := deriveSLOK(
 						testCase.scheme,
 						&slokReference{
-							SeedSpecID: string(testCase.scheme.SeedSpecs[seedSpecIndex].ID),
-							Time:       epoch.Add(time.Duration(timePeriod) * time.Millisecond),
+							PropagationChannelID: testCase.propagationChannelID,
+							SeedSpecID:           string(testCase.scheme.SeedSpecs[seedSpecIndex].ID),
+							Time:                 epoch.Add(time.Duration(timePeriod) * time.Millisecond),
 						})
 
 					slokMap[string(slok.ID)] = slok.Key
@@ -497,12 +512,13 @@ func TestOSL(t *testing.T) {
 
 			checkRegistryStartTime := time.Now()
 
-			registry, _, err := UnpackRegistry(files[GetOSLRegistryURL("")], signingPublicKey)
+			registry, _, err := UnpackRegistry(
+				pavedRegistries[testCase.propagationChannelID], signingPublicKey)
 			if err != nil {
 				t.Fatalf("UnpackRegistry failed: %s", err)
 			}
 
-			t.Logf("registry size: %d", len(files[GetOSLRegistryURL("")]))
+			t.Logf("registry size: %d", len(pavedRegistries[testCase.propagationChannelID]))
 			t.Logf("registry OSL count: %d", len(registry.FileSpecs))
 
 			oslIDs := registry.GetSeededOSLIDs(
@@ -519,7 +535,8 @@ func TestOSL(t *testing.T) {
 			}
 
 			for _, oslID := range oslIDs {
-				oslFileContents, ok := files[GetOSLFileURL("", oslID)]
+				oslFileContents, ok :=
+					pavedOSLFileContents[testCase.propagationChannelID][GetOSLFileURL("", oslID)]
 				if !ok {
 					t.Fatalf("unknown OSL file name")
 				}

+ 1 - 0
psiphon/remoteServerList_test.go

@@ -139,6 +139,7 @@ func TestObfuscatedRemoteServerLists(t *testing.T) {
 
 	paveFiles, err := oslConfig.Pave(
 		epoch,
+		propagationChannelID,
 		signingPublicKey,
 		signingPrivateKey,
 		[]map[time.Time]string{