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

     1  package renter
     2  
     3  // skyfile_encryption.go provides utilities for encrypting and decrypting
     4  // skyfiles.
     5  
     6  import (
     7  	"gitlab.com/NebulousLabs/errors"
     8  
     9  	"gitlab.com/SkynetLabs/skyd/build"
    10  	"gitlab.com/SkynetLabs/skyd/skykey"
    11  	"gitlab.com/SkynetLabs/skyd/skymodules"
    12  	"go.sia.tech/siad/modules"
    13  
    14  	"github.com/aead/chacha20/chacha"
    15  )
    16  
    17  var errNoSkykeyMatchesSkyfileEncryptionID = errors.New("Unable to find matching skykey for public ID encryption")
    18  
    19  // DecryptBaseSector attempts to decrypt the baseSector. If it has the
    20  // necessary Skykey, it will decrypt the baseSector in-place. It returns the
    21  // file-specific skykey to be used for decrypting the rest of the associated
    22  // skyfile.
    23  func (r *Renter) DecryptBaseSector(baseSector []byte) (skykey.Skykey, error) {
    24  	if err := r.tg.Add(); err != nil {
    25  		return skykey.Skykey{}, err
    26  	}
    27  	defer r.tg.Done()
    28  	return r.managedDecryptBaseSector(baseSector)
    29  }
    30  
    31  // managedCheckSkyfileEncryptionIDMatch tries to find a Skykey that can decrypt
    32  // the identifier and be used for decrypting the associated skyfile. It returns
    33  // an error if it is not found.
    34  func (r *Renter) managedCheckSkyfileEncryptionIDMatch(encryptionIdentifier []byte, nonce []byte) (skykey.Skykey, error) {
    35  	allSkykeys := r.staticSkykeyManager.Skykeys()
    36  	for _, sk := range allSkykeys {
    37  		matches, err := sk.MatchesSkyfileEncryptionID(encryptionIdentifier, nonce)
    38  		if err != nil {
    39  			r.staticLog.Debugln("SkykeyEncryptionID match err", err)
    40  			continue
    41  		}
    42  		if matches {
    43  			return sk, nil
    44  		}
    45  	}
    46  	return skykey.Skykey{}, errNoSkykeyMatchesSkyfileEncryptionID
    47  }
    48  
    49  // managedDecryptBaseSector attempts to decrypt the baseSector. If it has the
    50  // necessary Skykey, it will decrypt the baseSector in-place. It returns the
    51  // file-specific skykey to be used for decrypting the rest of the associated
    52  // skyfile.
    53  func (r *Renter) managedDecryptBaseSector(baseSector []byte) (skykey.Skykey, error) {
    54  	// Sanity check - baseSector should not be more than modules.SectorSize.
    55  	// Note that the base sector may be smaller in the event of a packed
    56  	// skyfile.
    57  	if uint64(len(baseSector)) > modules.SectorSize {
    58  		build.Critical("decryptBaseSector given a baseSector that is too large")
    59  		return skykey.Skykey{}, errors.New("baseSector too large")
    60  	}
    61  	var sl skymodules.SkyfileLayout
    62  	sl.Decode(baseSector)
    63  
    64  	if !skymodules.IsEncryptedLayout(sl) {
    65  		build.Critical("Expected layout to be marked as encrypted!")
    66  	}
    67  
    68  	// Get the nonce to be used for getting private-id skykeys, and for deriving the
    69  	// file-specific skykey.
    70  	nonce := make([]byte, chacha.XNonceSize)
    71  	copy(nonce[:], sl.KeyData[skykey.SkykeyIDLen:skykey.SkykeyIDLen+chacha.XNonceSize])
    72  
    73  	// Grab the key ID from the layout.
    74  	var keyID skykey.SkykeyID
    75  	copy(keyID[:], sl.KeyData[:skykey.SkykeyIDLen])
    76  
    77  	// Try to get the skykey associated with that ID.
    78  	masterSkykey, err := r.staticSkykeyManager.KeyByID(keyID)
    79  	// If the ID is unknown, use the key ID as an encryption identifier and try
    80  	// finding the associated skykey.
    81  	if errors.Contains(err, skykey.ErrNoSkykeysWithThatID) {
    82  		masterSkykey, err = r.managedCheckSkyfileEncryptionIDMatch(keyID[:], nonce)
    83  	}
    84  	if err != nil {
    85  		return skykey.Skykey{}, errors.AddContext(err, "Unable to find associated skykey")
    86  	}
    87  
    88  	// Derive the file-specific key.
    89  	fileSkykey, err := masterSkykey.SubkeyWithNonce(nonce)
    90  	if err != nil {
    91  		return skykey.Skykey{}, errors.AddContext(err, "Unable to derive file-specific subkey")
    92  	}
    93  
    94  	// Derive the base sector subkey and use it to decrypt the base sector.
    95  	baseSectorKey, err := fileSkykey.DeriveSubkey(skymodules.BaseSectorNonceDerivation[:])
    96  	if err != nil {
    97  		return skykey.Skykey{}, errors.AddContext(err, "Unable to derive baseSector subkey")
    98  	}
    99  
   100  	// Get the cipherkey.
   101  	ck, err := baseSectorKey.CipherKey()
   102  	if err != nil {
   103  		return skykey.Skykey{}, errors.AddContext(err, "Unable to get baseSector cipherkey")
   104  	}
   105  
   106  	_, err = ck.DecryptBytesInPlace(baseSector, 0)
   107  	if err != nil {
   108  		return skykey.Skykey{}, errors.New("Error decrypting baseSector for download")
   109  	}
   110  
   111  	// Save the visible-by-default fields of the baseSector's layout.
   112  	version := sl.Version
   113  	cipherType := sl.CipherType
   114  	var keyData [64]byte
   115  	copy(keyData[:], sl.KeyData[:])
   116  
   117  	// Decode the now decrypted layout.
   118  	sl.Decode(baseSector)
   119  
   120  	// Reset the visible-by-default fields.
   121  	// (They were turned into random values by the decryption)
   122  	sl.Version = version
   123  	sl.CipherType = cipherType
   124  	copy(sl.KeyData[:], keyData[:])
   125  
   126  	// Now re-copy the decrypted layout into the decrypted baseSector.
   127  	copy(baseSector[:skymodules.SkyfileLayoutSize], sl.Encode())
   128  
   129  	return fileSkykey, nil
   130  }
   131  
   132  // encryptBaseSectorWithSkykey encrypts the baseSector in place using the given
   133  // Skykey. Certain fields of the layout are restored in plaintext into the
   134  // encrypted baseSector to indicate to downloaders what Skykey was used.
   135  func encryptBaseSectorWithSkykey(baseSector []byte, plaintextLayout skymodules.SkyfileLayout, sk skykey.Skykey) error {
   136  	baseSectorKey, err := sk.DeriveSubkey(skymodules.BaseSectorNonceDerivation[:])
   137  	if err != nil {
   138  		return errors.AddContext(err, "Unable to derive baseSector subkey")
   139  	}
   140  
   141  	// Get the cipherkey.
   142  	ck, err := baseSectorKey.CipherKey()
   143  	if err != nil {
   144  		return errors.AddContext(err, "Unable to get baseSector cipherkey")
   145  	}
   146  
   147  	// Encrypt the base sector. This is calling DecryptBytesInPlace because
   148  	// we only allow the stream cipher ChaCha for which encryption and
   149  	// decryption are the same thing since its just xoring the data with a
   150  	// keystream.
   151  	_, err = ck.DecryptBytesInPlace(baseSector, 0)
   152  	if err != nil {
   153  		return errors.New("Error decrypting baseSector for download")
   154  	}
   155  
   156  	// Re-add the visible-by-default fields of the baseSector.
   157  	var encryptedLayout skymodules.SkyfileLayout
   158  	encryptedLayout.Decode(baseSector)
   159  	encryptedLayout.Version = plaintextLayout.Version
   160  	encryptedLayout.CipherType = baseSectorKey.CipherType()
   161  
   162  	// Add the key ID or the encrypted skyfile identifier, depending on the key
   163  	// type.
   164  	switch sk.Type {
   165  	case skykey.TypePublicID:
   166  		keyID := sk.ID()
   167  		copy(encryptedLayout.KeyData[:skykey.SkykeyIDLen], keyID[:])
   168  
   169  	case skykey.TypePrivateID:
   170  		encryptedIdentifier, err := sk.GenerateSkyfileEncryptionID()
   171  		if err != nil {
   172  			return errors.AddContext(err, "Unable to generate encrypted skyfile ID")
   173  		}
   174  		copy(encryptedLayout.KeyData[:skykey.SkykeyIDLen], encryptedIdentifier[:])
   175  
   176  	default:
   177  		build.Critical("No encryption implemented for this skykey type")
   178  		return errors.AddContext(errors.New("No encryption implemented for skykey type"), string(sk.Type))
   179  	}
   180  
   181  	// Add the nonce to the base sector, in plaintext.
   182  	nonce := sk.Nonce()
   183  	copy(encryptedLayout.KeyData[skykey.SkykeyIDLen:skykey.SkykeyIDLen+len(nonce)], nonce[:])
   184  
   185  	// Now re-copy the encrypted layout into the baseSector.
   186  	copy(baseSector[:skymodules.SkyfileLayoutSize], encryptedLayout.Encode())
   187  	return nil
   188  }
   189  
   190  // encryptionEnabled checks if encryption is enabled for the
   191  // SkyfileUploadParameters. It returns true if either the SkykeyName or SkykeyID
   192  // is set
   193  func encryptionEnabled(sup *skymodules.SkyfileUploadParameters) bool {
   194  	return sup.SkykeyName != "" || sup.SkykeyID != skykey.SkykeyID{}
   195  }
   196  
   197  // generateCipherKey generates a Cipher Key for the FileUploadParams from the
   198  // SkyfileUploadParameters
   199  func generateCipherKey(fup *skymodules.FileUploadParams, sup skymodules.SkyfileUploadParameters) error {
   200  	if encryptionEnabled(&sup) {
   201  		fanoutSkykey, err := sup.FileSpecificSkykey.DeriveSubkey(skymodules.FanoutNonceDerivation[:])
   202  		if err != nil {
   203  			return errors.AddContext(err, "unable to derive fanout subkey")
   204  		}
   205  		fup.CipherKey, err = fanoutSkykey.CipherKey()
   206  		if err != nil {
   207  			return errors.AddContext(err, "unable to get skykey cipherkey")
   208  		}
   209  		fup.CipherType = sup.FileSpecificSkykey.CipherType()
   210  	}
   211  	return nil
   212  }
   213  
   214  // managedGenerateFilekey generates the FileSpecificSkykey to be used for
   215  // encryption and sets it in the SkyfileUploadParameters
   216  func (r *Renter) managedGenerateFilekey(sup *skymodules.SkyfileUploadParameters, nonce []byte) error {
   217  	// If encryption is not enabled then nothing to do.
   218  	if !encryptionEnabled(sup) {
   219  		return nil
   220  	}
   221  
   222  	// Get the Key
   223  	var key skykey.Skykey
   224  	var err error
   225  	if sup.SkykeyName != "" {
   226  		key, err = r.SkykeyByName(sup.SkykeyName)
   227  	} else {
   228  		key, err = r.SkykeyByID(sup.SkykeyID)
   229  	}
   230  	if err != nil {
   231  		return errors.AddContext(err, "unable to get skykey")
   232  	}
   233  
   234  	// Generate the Subkey
   235  	if len(nonce) == 0 {
   236  		sup.FileSpecificSkykey, err = key.GenerateFileSpecificSubkey()
   237  	} else {
   238  		sup.FileSpecificSkykey, err = key.SubkeyWithNonce(nonce)
   239  	}
   240  	if err != nil {
   241  		return errors.AddContext(err, "unable to generate subkey")
   242  	}
   243  	return nil
   244  }