dethnote/src/vault/models.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"`
}