package vault import ( "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? 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 } return &Message{ Email: email, 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"` }