gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/skyfileencryption_test.go (about)

     1  package renter
     2  
     3  import (
     4  	"bytes"
     5  	"os"
     6  	"testing"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  	"gitlab.com/SkynetLabs/skyd/skykey"
    10  	"gitlab.com/SkynetLabs/skyd/skymodules"
    11  	"go.sia.tech/siad/crypto"
    12  
    13  	"gitlab.com/NebulousLabs/fastrand"
    14  )
    15  
    16  // TestSkyfileBaseSectorEncryption runs base sector encryption tests with every
    17  // supported SkykeyType.
    18  func TestSkyfileBaseSectorEncryption(t *testing.T) {
    19  	if testing.Short() {
    20  		t.SkipNow()
    21  	}
    22  	t.Parallel()
    23  
    24  	rt, err := newRenterTester(t.Name())
    25  	if err != nil {
    26  		t.Fatal(err)
    27  	}
    28  	r := rt.renter
    29  	defer func() {
    30  		if err := rt.Close(); err != nil {
    31  			t.Fatal(err)
    32  		}
    33  	}()
    34  
    35  	testBaseSectorEncryptionWithType(t, r, skykey.TypePublicID)
    36  	testBaseSectorEncryptionWithType(t, r, skykey.TypePrivateID)
    37  }
    38  
    39  // testBaseSectorEncryptionWithType tests base sector encryption and decryption
    40  // with multiple Skykeys of the specified type.
    41  func testBaseSectorEncryptionWithType(t *testing.T, r *Renter, skykeyType skykey.SkykeyType) {
    42  	// Create the 2 test skykeys, with different types
    43  	keyName1 := t.Name() + "1" + skykeyType.ToString()
    44  	sk1, err := r.CreateSkykey(keyName1, skykeyType)
    45  	if err != nil {
    46  		t.Fatal(err)
    47  	}
    48  
    49  	// Create a file that fits in one base sector and set it up for encryption.
    50  	fileBytes := fastrand.Bytes(1000)
    51  	metadata := skymodules.SkyfileMetadata{
    52  		Mode:     os.FileMode(0777),
    53  		Filename: "encryption_test_file",
    54  	}
    55  	// Grab the metadata bytes.
    56  	metadataBytes, err := skymodules.SkyfileMetadataBytes(metadata)
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  	ll := skymodules.SkyfileLayout{
    61  		Version:      skymodules.SkyfileVersion,
    62  		Filesize:     uint64(len(fileBytes)),
    63  		MetadataSize: uint64(len(metadataBytes)),
    64  		CipherType:   crypto.TypePlain,
    65  	}
    66  	baseSector, _, _ := skymodules.BuildBaseSector(ll.Encode(), nil, metadataBytes, fileBytes) // 'nil' because there is no fanout
    67  
    68  	// Make a helper function for producing copies of the basesector
    69  	// because encryption is done in-place.
    70  	baseSectorCopy := func() []byte {
    71  		bsCopy := make([]byte, len(baseSector))
    72  		copy(bsCopy[:], baseSector[:])
    73  		return bsCopy
    74  	}
    75  
    76  	fsKey1, err := sk1.GenerateFileSpecificSubkey()
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  
    81  	// Encryption of the same base sector with the same key should yield the same
    82  	// result, and it should be different from the plaintext.
    83  	bsCopy1 := baseSectorCopy()
    84  	bsCopy2 := baseSectorCopy()
    85  	err = encryptBaseSectorWithSkykey(bsCopy1, ll, fsKey1)
    86  	if err != nil {
    87  		t.Fatal(err)
    88  	}
    89  	err = encryptBaseSectorWithSkykey(bsCopy2, ll, fsKey1)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  	if !bytes.Equal(bsCopy1, bsCopy2) {
    94  		t.Fatal("Expected encrypted basesector copies to be equal")
    95  	}
    96  	if bytes.Equal(baseSector, bsCopy2) {
    97  		t.Fatal("Expected encrypted basesector copy to be different from original base sector")
    98  	}
    99  
   100  	// Create a different file-specific key. The encrypted basesector should be
   101  	// different.
   102  	fsKey2, err := sk1.GenerateFileSpecificSubkey()
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	bsCopy3 := baseSectorCopy()
   107  	err = encryptBaseSectorWithSkykey(bsCopy3, ll, fsKey2)
   108  	if err != nil {
   109  		t.Fatal(err)
   110  	}
   111  	if bytes.Equal(baseSector, bsCopy3) {
   112  		t.Fatal("Expected encrypted basesector copy to be different from original base sector")
   113  	}
   114  	if bytes.Equal(bsCopy2, bsCopy3) {
   115  		t.Fatal("Basesectors encrypted with different file-specific keys should be different.")
   116  	}
   117  
   118  	// Create a entirely different skykey and sanity check that it produces
   119  	// different ciphertexts.
   120  	keyName2 := t.Name() + "2" + skykeyType.ToString()
   121  	sk2, err := r.CreateSkykey(keyName2, skykeyType)
   122  	if err != nil {
   123  		t.Fatal(err)
   124  	}
   125  	otherFSKey, err := sk2.GenerateFileSpecificSubkey()
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	otherBSCopy := baseSectorCopy()
   130  	err = encryptBaseSectorWithSkykey(otherBSCopy, ll, otherFSKey)
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	if bytes.Equal(otherBSCopy, baseSector) {
   135  		t.Fatal("Expected base sector encrypted with different skykey to be different from original base sector.")
   136  	}
   137  	if bytes.Equal(otherBSCopy, bsCopy1) {
   138  		t.Fatal("Expected base sector encrypted with different skykey to be differen from original base sector.")
   139  	}
   140  	if bytes.Equal(otherBSCopy, bsCopy3) {
   141  		t.Fatal("Expected base sector encrypted with different skykey to be different from original base sector.")
   142  	}
   143  
   144  	// Now decrypt all the base sectors. They should all be equal to the original
   145  	// now.
   146  	sk, err := r.managedDecryptBaseSector(bsCopy1)
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  	_, err = r.managedDecryptBaseSector(bsCopy2)
   151  	if err != nil {
   152  		t.Fatal(err)
   153  	}
   154  	_, err = r.managedDecryptBaseSector(bsCopy3)
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	_, err = r.managedDecryptBaseSector(otherBSCopy)
   159  	if err != nil {
   160  		t.Fatal(err)
   161  	}
   162  
   163  	// All baseSectors should be equal in everything except their keydata.
   164  	equalExceptKeyData := func(x, y []byte) error {
   165  		xLayout, xFanoutBytes, xSM, _, xPayload, err := skymodules.ParseSkyfileMetadata(x)
   166  		if err != nil {
   167  			return err
   168  		}
   169  		yLayout, yFanoutBytes, ySM, _, yPayload, err := skymodules.ParseSkyfileMetadata(y)
   170  		if err != nil {
   171  			return err
   172  		}
   173  
   174  		// Check layout equality.
   175  		if xLayout.Version != yLayout.Version {
   176  			return errors.New("Expected version to match")
   177  		}
   178  		if xLayout.Filesize != yLayout.Filesize {
   179  			return errors.New("Expected filesizes to match")
   180  		}
   181  		if xLayout.MetadataSize != yLayout.MetadataSize {
   182  			return errors.New("Expected metadatasizes to match")
   183  		}
   184  		if xLayout.FanoutSize != yLayout.FanoutSize {
   185  			return errors.New("Expected fanoutsize to match")
   186  		}
   187  		if xLayout.FanoutDataPieces != yLayout.FanoutDataPieces {
   188  			return errors.New("Expected fanoutDataPieces to match")
   189  		}
   190  		if xLayout.FanoutParityPieces != yLayout.FanoutParityPieces {
   191  			return errors.New("Expected fanoutParityPieces to match")
   192  		}
   193  		// (Key data and cipher type won't match because the unencrypted baseSector won't have any key
   194  		// data)
   195  
   196  		if !bytes.Equal(xFanoutBytes, yFanoutBytes) {
   197  			return errors.New("Expected fanoutBytes to match")
   198  		}
   199  
   200  		// Check that xSM and ySM both have the original Mode/Filename.
   201  		if xSM.Mode != metadata.Mode {
   202  			return errors.New("x Mode doesn't match original")
   203  		}
   204  		if ySM.Mode != metadata.Mode {
   205  			return errors.New("y Mode doesn't match original")
   206  		}
   207  		if xSM.Filename != metadata.Filename {
   208  			return errors.New("x filename doesn't match original")
   209  		}
   210  		if ySM.Filename != metadata.Filename {
   211  			return errors.New("y filename doesn't match original")
   212  		}
   213  
   214  		if !bytes.Equal(xPayload, yPayload) {
   215  			return errors.New("Expected x and y payload to match")
   216  		}
   217  		return nil
   218  	}
   219  
   220  	// Base sector 1 and 2 should be *exactly* equal.
   221  	// They used the exact same key throughout.
   222  	if !bytes.Equal(bsCopy1, bsCopy2) {
   223  		t.Fatal("Expected decrypted basesector copies to be equal")
   224  	}
   225  
   226  	// Check (almost) equality.
   227  	err = equalExceptKeyData(baseSector, bsCopy1)
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  	err = equalExceptKeyData(bsCopy1, bsCopy3)
   232  	if err != nil {
   233  		t.Fatal(err)
   234  	}
   235  	err = equalExceptKeyData(bsCopy1, otherBSCopy)
   236  	if err != nil {
   237  		t.Fatal(err)
   238  	}
   239  
   240  	// bsCopy3 should not be exactly equal to bsCopy2 because of its different keyData.
   241  	if bytes.Equal(bsCopy3, bsCopy2) {
   242  		t.Fatal("Expected copies with different file-specific keys to be different")
   243  	}
   244  	// the original will also be different because it has no keydata.
   245  	if bytes.Equal(baseSector, bsCopy2) {
   246  		t.Fatal("Expected copies with different file-specific keys to be different")
   247  	}
   248  	// the original will also be different because it has no keydata.
   249  	if bytes.Equal(baseSector, bsCopy3) {
   250  		t.Fatal("Expected copies with different file-specific keys to be different")
   251  	}
   252  	// the original will also be different because it has no keydata.
   253  	if bytes.Equal(baseSector, otherBSCopy) {
   254  		t.Fatal("Expected copies with different file-specific keys to be different")
   255  	}
   256  
   257  	// Testing fanout key derivation.
   258  	layoutForFanout, _, _, _, _, err := skymodules.ParseSkyfileMetadata(bsCopy1)
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  	fanoutKey, err := skymodules.DeriveFanoutKey(&layoutForFanout, sk)
   263  	if err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	fanoutKeyEntropy := fanoutKey.Key()
   267  
   268  	// Check that deriveFanoutKey produces the same derived key as a manual
   269  	// derivation from the original.The fact that it is different fsKey1 is
   270  	// guaranteed by skykey module tests.
   271  	fanoutKey2, err := fsKey1.DeriveSubkey(skymodules.FanoutNonceDerivation[:])
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  	if !bytes.Equal(fanoutKey2.Entropy[:], fanoutKeyEntropy[:]) {
   276  		t.Fatal("Expected fanout key returned from deriveFanoutKey to be same as manual derivation")
   277  	}
   278  }
   279  
   280  // TestBaseSectorKeyID checks that keyIDs are set correctly in base sectors
   281  // encrypted using TypePublicID and TypePrivateID skykeys.
   282  func TestBaseSectorKeyID(t *testing.T) {
   283  	if testing.Short() {
   284  		t.SkipNow()
   285  	}
   286  	t.Parallel()
   287  
   288  	rt, err := newRenterTester(t.Name())
   289  	if err != nil {
   290  		t.Fatal(err)
   291  	}
   292  	r := rt.renter
   293  	defer func() {
   294  		if err := rt.Close(); err != nil {
   295  			t.Fatal(err)
   296  		}
   297  	}()
   298  
   299  	// Create a test skykey.
   300  	publicIDKeyName := t.Name() + "-public-id-key"
   301  	publicIDKey, err := r.CreateSkykey(publicIDKeyName, skykey.TypePublicID)
   302  	if err != nil {
   303  		t.Fatal(err)
   304  	}
   305  
   306  	// Create a file that fits in one base sector and set it up for encryption.
   307  	fileBytes := fastrand.Bytes(1000)
   308  	metadata := skymodules.SkyfileMetadata{
   309  		Mode:     os.FileMode(0777),
   310  		Filename: "encryption_test_file",
   311  	}
   312  	// Grab the metadata bytes.
   313  	metadataBytes, err := skymodules.SkyfileMetadataBytes(metadata)
   314  	if err != nil {
   315  		t.Fatal(err)
   316  	}
   317  	ll := skymodules.SkyfileLayout{
   318  		Version:      skymodules.SkyfileVersion,
   319  		Filesize:     uint64(len(fileBytes)),
   320  		MetadataSize: uint64(len(metadataBytes)),
   321  		CipherType:   crypto.TypePlain,
   322  	}
   323  	baseSector, _, _ := skymodules.BuildBaseSector(ll.Encode(), nil, metadataBytes, fileBytes) // 'nil' because there is no fanout
   324  
   325  	// Make a helper function for producing copies of the basesector
   326  	// because encryption is done in-place.
   327  	baseSectorCopy := func() []byte {
   328  		bsCopy := make([]byte, len(baseSector))
   329  		copy(bsCopy[:], baseSector[:])
   330  		return bsCopy
   331  	}
   332  
   333  	fsSkykey1, err := publicIDKey.GenerateFileSpecificSubkey()
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	bsCopy := baseSectorCopy()
   338  	err = encryptBaseSectorWithSkykey(bsCopy, ll, fsSkykey1)
   339  	if err != nil {
   340  		t.Fatal(err)
   341  	}
   342  	var encLayout skymodules.SkyfileLayout
   343  	encLayout.Decode(bsCopy)
   344  
   345  	// Check that skykey ID is stored correctly in the layout.
   346  	var keyID skykey.SkykeyID
   347  	copy(keyID[:], encLayout.KeyData[:skykey.SkykeyIDLen])
   348  	if keyID != publicIDKey.ID() {
   349  		t.Log(encLayout)
   350  		t.Log(keyID, publicIDKey.ID())
   351  		t.Fatal("Expected keyID to match skykey ID.")
   352  	}
   353  
   354  	// Create a TypePrivateID skykey to check the key ID not set, but the
   355  	// encrypted identifier is set.
   356  	privateIDKeyName := t.Name() + "-private-id-key"
   357  	privateIDKey, err := r.CreateSkykey(privateIDKeyName, skykey.TypePrivateID)
   358  	if err != nil {
   359  		t.Fatal(err)
   360  	}
   361  	fsSkykey2, err := privateIDKey.GenerateFileSpecificSubkey()
   362  	if err != nil {
   363  		t.Fatal(err)
   364  	}
   365  	bsCopy2 := baseSectorCopy()
   366  	err = encryptBaseSectorWithSkykey(bsCopy2, ll, fsSkykey2)
   367  	if err != nil {
   368  		t.Fatal(err)
   369  	}
   370  	var encLayout2 skymodules.SkyfileLayout
   371  	encLayout2.Decode(bsCopy2)
   372  
   373  	// Check that skykey ID is NOT in the layout.
   374  	var keyID2 skykey.SkykeyID
   375  	copy(keyID2[:], encLayout2.KeyData[:skykey.SkykeyIDLen])
   376  	privateID := privateIDKey.ID()
   377  	if keyID2 == privateID {
   378  		t.Log(keyID, privateID)
   379  		t.Fatal("Expected keyID to match skykey ID.")
   380  	}
   381  	// Check if the key ID is anywhere in the base sector. There should be enough
   382  	// entropy in the 16-byte key ID to prevent incidental collisions (as opposed
   383  	// to accidental inclusion).
   384  	if bytes.Contains(bsCopy2, privateID[:]) {
   385  		t.Log(privateID, bsCopy2)
   386  		t.Fatal("Expected skykey ID to NOT be in base sector")
   387  	}
   388  
   389  	// Now check for the expected skyfile encryption ID.
   390  	expectedEncID, err := fsSkykey2.GenerateSkyfileEncryptionID()
   391  	if err != nil {
   392  		t.Fatal(err)
   393  	}
   394  	if keyID2 != expectedEncID {
   395  		t.Log(expectedEncID, keyID2)
   396  		t.Fatal("Expected to find the skyfile encryption ID")
   397  	}
   398  }