forked from TrueCloudLab/restic
Document crypto and master key JSON struct
This commit is contained in:
parent
65a653693e
commit
0ed2a066a0
4 changed files with 88 additions and 18 deletions
|
@ -23,11 +23,11 @@ func init() {
|
|||
}
|
||||
|
||||
func (cmd CmdCat) Usage() string {
|
||||
return "[data|tree|snapshot|key|lock] ID"
|
||||
return "[data|tree|snapshot|key|masterkey|lock] ID"
|
||||
}
|
||||
|
||||
func (cmd CmdCat) Execute(args []string) error {
|
||||
if len(args) != 2 {
|
||||
if len(args) < 1 || (args[0] != "masterkey" && len(args) != 2) {
|
||||
return fmt.Errorf("type or ID not specified, Usage: %s", cmd.Usage())
|
||||
}
|
||||
|
||||
|
@ -38,18 +38,21 @@ func (cmd CmdCat) Execute(args []string) error {
|
|||
|
||||
tpe := args[0]
|
||||
|
||||
id, err := backend.ParseID(args[1])
|
||||
if err != nil {
|
||||
id = nil
|
||||
|
||||
if tpe != "snapshot" {
|
||||
return err
|
||||
}
|
||||
|
||||
// find snapshot id with prefix
|
||||
id, err = s.FindSnapshot(args[1])
|
||||
var id backend.ID
|
||||
if tpe != "masterkey" {
|
||||
id, err = backend.ParseID(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
id = nil
|
||||
|
||||
if tpe != "snapshot" {
|
||||
return err
|
||||
}
|
||||
|
||||
// find snapshot id with prefix
|
||||
id, err = s.FindSnapshot(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +117,14 @@ func (cmd CmdCat) Execute(args []string) error {
|
|||
}
|
||||
|
||||
fmt.Println(string(buf))
|
||||
return nil
|
||||
case "masterkey":
|
||||
buf, err := json.MarshalIndent(s.Key().Master(), "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(buf))
|
||||
return nil
|
||||
case "lock":
|
||||
return errors.New("not yet implemented")
|
||||
|
|
37
crypto.go
37
crypto.go
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -147,6 +148,42 @@ func generateRandomIV() (iv IV) {
|
|||
return
|
||||
}
|
||||
|
||||
type jsonMACKey struct {
|
||||
K []byte `json:"k"`
|
||||
R []byte `json:"r"`
|
||||
}
|
||||
|
||||
func (m *MACKey) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(jsonMACKey{K: m.K[:], R: m.R[:]})
|
||||
}
|
||||
|
||||
func (m *MACKey) UnmarshalJSON(data []byte) error {
|
||||
j := jsonMACKey{}
|
||||
err := json.Unmarshal(data, &j)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy(m.K[:], j.K)
|
||||
copy(m.R[:], j.R)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *AESKey) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(k[:])
|
||||
}
|
||||
|
||||
func (k *AESKey) UnmarshalJSON(data []byte) error {
|
||||
d := make([]byte, AESKeySize)
|
||||
err := json.Unmarshal(data, &d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy(k[:], d)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts and signs data. Stored in ciphertext is IV || Ciphertext ||
|
||||
// MAC. Encrypt returns the ciphertext's length.
|
||||
func Encrypt(ks *MasterKeys, ciphertext, plaintext []byte) (int, error) {
|
||||
|
|
|
@ -107,9 +107,21 @@ last 32 byte). If the password is incorrect or the key file has been tampered
|
|||
with, the computed MAC will not match the last 16 bytes of the data, and
|
||||
restic exits with an error. Otherwise, the data is decrypted with the
|
||||
encryption key derived from `scrypt`. This yields a JSON document which
|
||||
contains the master signing and encryption keys for this repository. All data
|
||||
in the repository is encrypted and signed with these master keys with AES-256
|
||||
in Counter mode and signed with Poly1305-AES as described above.
|
||||
contains the master signing and encryption keys for this repository, encoded in
|
||||
Base64. The command `restic cat masterkey` can be used as follows to decrypt
|
||||
and pretty-print the master key:
|
||||
|
||||
$ restic -r /tmp/restic-repo cat masterkey
|
||||
{
|
||||
"sign": {
|
||||
"k": "evFWd9wWlndL9jc501268g==",
|
||||
"r": "E9eEDnSJZgqwTOkDtOp+Dw=="
|
||||
},
|
||||
"encrypt": "UQCqa0lKZ94PygPxMRqkePTZnHRYh1k1pX2k2lM2v3Q="
|
||||
}
|
||||
|
||||
All data in the repository is encrypted and signed with these master keys with
|
||||
AES-256 in Counter mode and signed with Poly1305-AES as described above.
|
||||
|
||||
A repository can have several different passwords, with a key file for each.
|
||||
This way, the password can be changed without having to re-encrypt all data.
|
||||
|
|
15
key.go
15
key.go
|
@ -64,8 +64,8 @@ type Key struct {
|
|||
// encrypted and signed as a JSON data structure in the Data field of the Key
|
||||
// structure.
|
||||
type MasterKeys struct {
|
||||
Sign MACKey
|
||||
Encrypt AESKey
|
||||
Sign MACKey `json:"sign"`
|
||||
Encrypt AESKey `json:"encrypt"`
|
||||
}
|
||||
|
||||
// CreateKey initializes a master key in the given backend and encrypts it with
|
||||
|
@ -304,6 +304,17 @@ func (k *Key) DecryptUserFrom(rd io.Reader) (io.ReadCloser, error) {
|
|||
return DecryptFrom(k.user, rd)
|
||||
}
|
||||
|
||||
// Master() returns the master keys for this repository. Only included for
|
||||
// debug purposes.
|
||||
func (k *Key) Master() *MasterKeys {
|
||||
return k.master
|
||||
}
|
||||
|
||||
// User() returns the user keys for this key. Only included for debug purposes.
|
||||
func (k *Key) User() *MasterKeys {
|
||||
return k.user
|
||||
}
|
||||
|
||||
func (k *Key) String() string {
|
||||
if k == nil {
|
||||
return "<Key nil>"
|
||||
|
|
Loading…
Reference in a new issue