111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
package vault
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// Message is an encrypted file that contains the settings for a secure note,
|
|
// but does not contain the note itself.
|
|
type Message struct {
|
|
Email string `json:"email"` // owner email address
|
|
Verified bool `json:"verified"` // verified owner email?
|
|
VerifyToken string `json:"token"` // verification token for confirmation link
|
|
Timeout int `json:"timeout"` // hours for the unlock window
|
|
|
|
PasswordHash []byte `json:"hash"` // to verify the password is correct
|
|
Created time.Time `json:"created"`
|
|
|
|
// ephemeral keys that don't save to disk
|
|
Password string `json:"-"` // randomly generated Diceware password
|
|
Message string `json:"-"` // temporary holding space for the message text
|
|
}
|
|
|
|
// NewMessage creates a new message.
|
|
func NewMessage(email string, timeout int, message string, passwordLength int) (*Message, error) {
|
|
password, err := Diceware(passwordLength)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hash, err := GenerateHash(password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
verify, err := GenerateHash(password + email)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Message{
|
|
Email: email,
|
|
VerifyToken: hex.EncodeToString(verify),
|
|
Timeout: timeout,
|
|
Message: message,
|
|
Password: password,
|
|
PasswordHash: hash,
|
|
}, nil
|
|
}
|
|
|
|
// Create the message for the first time, which will trigger a confirmation email
|
|
// to be sent to the message's owner.
|
|
func (m *Message) Create(root string) error {
|
|
// Make sure defaults are sane.
|
|
m.Verified = false
|
|
m.Created = time.Now().UTC()
|
|
if m.PasswordHash == nil {
|
|
return errors.New("no hashed password?")
|
|
}
|
|
|
|
// Get the profile folder.
|
|
profile, err := m.Profile(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Save the metadata file.
|
|
WriteSecureJSON(profile, m.PasswordHash, m)
|
|
|
|
// Write the message itself, encrypted.
|
|
textfile := filepath.Join(profile, "message.bin")
|
|
return WriteEncrypted(m.PasswordHash, textfile, []byte(m.Message))
|
|
}
|
|
|
|
// Profile returns the directory where this message keeps its files.
|
|
//
|
|
// May panic if it can't create the directory.
|
|
func (m *Message) Profile(root string) (string, error) {
|
|
// Turn the hash into a directory to store its information.
|
|
profile := filepath.Join(root, HashToFilename(m.PasswordHash))
|
|
if _, err := os.Stat(profile); os.IsNotExist(err) {
|
|
err := os.MkdirAll(profile, 0755)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
return profile, nil
|
|
}
|
|
|
|
// Unlocker is a request to open an encrypted message. This document won't
|
|
// exist until the first time the password is entered to unlock a message.
|
|
type Unlocker struct {
|
|
// Email address of the requester.
|
|
Email string `json:"email"` // email address of the unlocker
|
|
|
|
// Is the request email verified? They must verify before the unlock
|
|
// process can begin, in case the e-mail server has problems sending
|
|
// messages, we don't want to start unlocking messages!
|
|
Verified bool `json:"verified"` // unlocker email address is verified.
|
|
|
|
// This is set when the unlock request is first created.
|
|
Created time.Time `json:"created"`
|
|
|
|
// This is set after the email is verified, and is the timeout window
|
|
// before the message will be decrypted.
|
|
NotBefore time.Time `json:"notBefore"`
|
|
}
|