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 }