named passphrases (+deletion by name)
This commit is contained in:
@ -2,29 +2,34 @@ package secretstore
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
Salt [aes.BlockSize]byte
|
||||
Keys []KeyEntry
|
||||
|
||||
unlocked bool
|
||||
key [32]byte
|
||||
salt [aes.BlockSize]byte
|
||||
keys []keyEntry
|
||||
}
|
||||
|
||||
type keyEntry struct {
|
||||
hash [64]byte
|
||||
encKey [32]byte
|
||||
type KeyEntry struct {
|
||||
Name string
|
||||
Hash [64]byte
|
||||
EncKey [32]byte
|
||||
}
|
||||
|
||||
func New() (s *Store) {
|
||||
@ -77,30 +82,32 @@ func (s *Store) Close() {
|
||||
}
|
||||
|
||||
func (s *Store) IsNew() bool {
|
||||
return len(s.keys) == 0
|
||||
return len(s.Keys) == 0
|
||||
}
|
||||
|
||||
func (s *Store) Unlocked() bool {
|
||||
return s.unlocked
|
||||
}
|
||||
|
||||
func (s *Store) Init(passphrase []byte) (err error) {
|
||||
func (s *Store) Init(name string, passphrase []byte) (err error) {
|
||||
err = randRead(s.key[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = randRead(s.salt[:])
|
||||
err = randRead(s.Salt[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.AddKey(passphrase)
|
||||
s.AddKey(name, passphrase)
|
||||
|
||||
s.unlocked = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var jsonFormatHdr = []byte("{json}")
|
||||
|
||||
func (s *Store) ReadFrom(in io.Reader) (n int64, err error) {
|
||||
memzero(s.key[:])
|
||||
s.unlocked = false
|
||||
@ -117,56 +124,52 @@ func (s *Store) ReadFrom(in io.Reader) (n int64, err error) {
|
||||
n += int64(nr)
|
||||
}
|
||||
|
||||
// read the salt
|
||||
readFull(s.salt[:])
|
||||
// read the file's start (json header or start of salt)
|
||||
|
||||
readFull(s.Salt[:len(jsonFormatHdr)])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// read the (encrypted) keys
|
||||
s.keys = make([]keyEntry, 0)
|
||||
for {
|
||||
k := keyEntry{}
|
||||
readFull(k.hash[:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
readFull(k.encKey[:])
|
||||
if !bytes.Equal(s.Salt[:len(jsonFormatHdr)], jsonFormatHdr) {
|
||||
// old key file
|
||||
|
||||
// finish reading the salt
|
||||
readFull(s.Salt[len(jsonFormatHdr):])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.keys = append(s.keys, k)
|
||||
// read the (encrypted) keys
|
||||
s.Keys = make([]KeyEntry, 0)
|
||||
for {
|
||||
k := KeyEntry{Name: "key-" + strconv.Itoa(len(s.Keys))}
|
||||
readFull(k.Hash[:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
readFull(k.EncKey[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.Keys = append(s.Keys, k)
|
||||
}
|
||||
}
|
||||
|
||||
err = json.NewDecoder(in).Decode(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Store) WriteTo(out io.Writer) (n int64, err error) {
|
||||
write := func(ba []byte) {
|
||||
var nr int
|
||||
nr, err = out.Write(ba)
|
||||
n += int64(nr)
|
||||
}
|
||||
|
||||
write(s.salt[:])
|
||||
_, err = out.Write(jsonFormatHdr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, k := range s.keys {
|
||||
write(k.hash[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
write(k.encKey[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = json.NewEncoder(out).Encode(s)
|
||||
return
|
||||
}
|
||||
|
||||
@ -178,8 +181,8 @@ func (s *Store) Unlock(passphrase []byte) (ok bool) {
|
||||
defer memzero(key[:])
|
||||
|
||||
var idx = -1
|
||||
for i := range s.keys {
|
||||
if hash == s.keys[i].hash {
|
||||
for i := range s.Keys {
|
||||
if hash == s.Keys[i].Hash {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
@ -189,28 +192,28 @@ func (s *Store) Unlock(passphrase []byte) (ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
s.decryptTo(s.key[:], s.keys[idx].encKey[:], &key)
|
||||
s.decryptTo(s.key[:], s.Keys[idx].EncKey[:], &key)
|
||||
|
||||
s.unlocked = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Store) AddKey(passphrase []byte) {
|
||||
func (s *Store) AddKey(name string, passphrase []byte) {
|
||||
key, hash := s.keyPairFromPassword(passphrase)
|
||||
memzero(passphrase)
|
||||
|
||||
defer memzero(key[:])
|
||||
|
||||
k := keyEntry{hash: hash}
|
||||
k := KeyEntry{Name: name, Hash: hash}
|
||||
|
||||
encKey := s.encrypt(s.key[:], &key)
|
||||
copy(k.encKey[:], encKey)
|
||||
copy(k.EncKey[:], encKey)
|
||||
|
||||
s.keys = append(s.keys, k)
|
||||
s.Keys = append(s.Keys, k)
|
||||
}
|
||||
|
||||
func (s *Store) keyPairFromPassword(password []byte) (key [32]byte, hash [64]byte) {
|
||||
keySlice := argon2.IDKey(password, s.salt[:], 1, 64*1024, 4, 32)
|
||||
keySlice := argon2.IDKey(password, s.Salt[:], 1, 64*1024, 4, 32)
|
||||
|
||||
copy(key[:], keySlice)
|
||||
memzero(keySlice)
|
||||
@ -236,12 +239,12 @@ func (s *Store) NewDecrypter(iv [aes.BlockSize]byte) cipher.Stream {
|
||||
|
||||
func (s *Store) encrypt(src []byte, key *[32]byte) (dst []byte) {
|
||||
dst = make([]byte, len(src))
|
||||
newEncrypter(s.salt, key).XORKeyStream(dst, src)
|
||||
newEncrypter(s.Salt, key).XORKeyStream(dst, src)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Store) decryptTo(dst []byte, src []byte, key *[32]byte) {
|
||||
newDecrypter(s.salt, key).XORKeyStream(dst, src)
|
||||
newDecrypter(s.Salt, key).XORKeyStream(dst, src)
|
||||
}
|
||||
|
||||
func newEncrypter(iv [aes.BlockSize]byte, key *[32]byte) cipher.Stream {
|
||||
|
Reference in New Issue
Block a user