From 0ed2a066a02a76622cd7e07877fb294b1e32690f Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 22 Mar 2015 21:03:01 +0100 Subject: [PATCH] Document crypto and master key JSON struct --- cmd/restic/cmd_cat.go | 36 +++++++++++++++++++++++------------- crypto.go | 37 +++++++++++++++++++++++++++++++++++++ doc/Design.md | 18 +++++++++++++++--- key.go | 15 +++++++++++++-- 4 files changed, 88 insertions(+), 18 deletions(-) diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go index e2d8112b6..ab249a52a 100644 --- a/cmd/restic/cmd_cat.go +++ b/cmd/restic/cmd_cat.go @@ -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") diff --git a/crypto.go b/crypto.go index c9184e3ec..743e34a05 100644 --- a/crypto.go +++ b/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) { diff --git a/doc/Design.md b/doc/Design.md index 6e837bb5c..22caf4e6e 100644 --- a/doc/Design.md +++ b/doc/Design.md @@ -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. diff --git a/key.go b/key.go index b59b28035..717390126 100644 --- a/key.go +++ b/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 ""