You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
4.3 KiB
140 lines
4.3 KiB
package chanbackup |
|
|
|
import ( |
|
"bytes" |
|
"crypto/rand" |
|
"crypto/sha256" |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
|
|
"github.com/lightningnetwork/lnd/keychain" |
|
"golang.org/x/crypto/chacha20poly1305" |
|
) |
|
|
|
// TODO(roasbeef): interface in front of? |
|
|
|
// baseEncryptionKeyLoc is the KeyLocator that we'll use to derive the base |
|
// encryption key used for encrypting all static channel backups. We use this |
|
// to then derive the actual key that we'll use for encryption. We do this |
|
// rather than using the raw key, as we assume that we can't obtain the raw |
|
// keys, and we don't want to require that the HSM know our target cipher for |
|
// encryption. |
|
// |
|
// TODO(roasbeef): possibly unique encrypt? |
|
var baseEncryptionKeyLoc = keychain.KeyLocator{ |
|
Family: keychain.KeyFamilyStaticBackup, |
|
Index: 0, |
|
} |
|
|
|
// genEncryptionKey derives the key that we'll use to encrypt all of our static |
|
// channel backups. The key itself, is the sha2 of a base key that we get from |
|
// the keyring. We derive the key this way as we don't force the HSM (or any |
|
// future abstractions) to be able to derive and know of the cipher that we'll |
|
// use within our protocol. |
|
func genEncryptionKey(keyRing keychain.KeyRing) ([]byte, error) { |
|
// key = SHA256(baseKey) |
|
baseKey, err := keyRing.DeriveKey( |
|
baseEncryptionKeyLoc, |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
encryptionKey := sha256.Sum256( |
|
baseKey.PubKey.SerializeCompressed(), |
|
) |
|
|
|
// TODO(roasbeef): throw back in ECDH? |
|
|
|
return encryptionKey[:], nil |
|
} |
|
|
|
// encryptPayloadToWriter attempts to write the set of bytes contained within |
|
// the passed byes.Buffer into the passed io.Writer in an encrypted form. We |
|
// use a 24-byte chachapoly AEAD instance with a randomized nonce that's |
|
// pre-pended to the final payload and used as associated data in the AEAD. We |
|
// use the passed keyRing to generate the encryption key, see genEncryptionKey |
|
// for further details. |
|
func encryptPayloadToWriter(payload bytes.Buffer, w io.Writer, |
|
keyRing keychain.KeyRing) error { |
|
|
|
// First, we'll derive the key that we'll use to encrypt the payload |
|
// for safe storage without giving away the details of any of our |
|
// channels. The final operation is: |
|
// |
|
// key = SHA256(baseKey) |
|
encryptionKey, err := genEncryptionKey(keyRing) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// Before encryption, we'll initialize our cipher with the target |
|
// encryption key, and also read out our random 24-byte nonce we use |
|
// for encryption. Note that we use NewX, not New, as the latter |
|
// version requires a 12-byte nonce, not a 24-byte nonce. |
|
cipher, err := chacha20poly1305.NewX(encryptionKey) |
|
if err != nil { |
|
return err |
|
} |
|
var nonce [chacha20poly1305.NonceSizeX]byte |
|
if _, err := rand.Read(nonce[:]); err != nil { |
|
return err |
|
} |
|
|
|
// Finally, we encrypted the final payload, and write out our |
|
// ciphertext with nonce pre-pended. |
|
ciphertext := cipher.Seal(nil, nonce[:], payload.Bytes(), nonce[:]) |
|
|
|
if _, err := w.Write(nonce[:]); err != nil { |
|
return err |
|
} |
|
if _, err := w.Write(ciphertext); err != nil { |
|
return err |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// decryptPayloadFromReader attempts to decrypt the encrypted bytes within the |
|
// passed io.Reader instance using the key derived from the passed keyRing. For |
|
// further details regarding the key derivation protocol, see the |
|
// genEncryptionKey method. |
|
func decryptPayloadFromReader(payload io.Reader, |
|
keyRing keychain.KeyRing) ([]byte, error) { |
|
|
|
// First, we'll re-generate the encryption key that we use for all the |
|
// SCBs. |
|
encryptionKey, err := genEncryptionKey(keyRing) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// Next, we'll read out the entire blob as we need to isolate the nonce |
|
// from the rest of the ciphertext. |
|
packedBackup, err := ioutil.ReadAll(payload) |
|
if err != nil { |
|
return nil, err |
|
} |
|
if len(packedBackup) < chacha20poly1305.NonceSizeX { |
|
return nil, fmt.Errorf("payload size too small, must be at "+ |
|
"least %v bytes", chacha20poly1305.NonceSizeX) |
|
} |
|
|
|
nonce := packedBackup[:chacha20poly1305.NonceSizeX] |
|
ciphertext := packedBackup[chacha20poly1305.NonceSizeX:] |
|
|
|
// Now that we have the cipher text and the nonce separated, we can go |
|
// ahead and decrypt the final blob so we can properly serialized the |
|
// SCB. |
|
cipher, err := chacha20poly1305.NewX(encryptionKey) |
|
if err != nil { |
|
return nil, err |
|
} |
|
plaintext, err := cipher.Open(nil, nonce, ciphertext, nonce) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return plaintext, nil |
|
}
|
|
|