website/pkg/encryption/coldstorage/coldstorage.go
2024-05-29 23:20:24 -07:00

151 lines
4.6 KiB
Go

package coldstorage
import (
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"code.nonshy.com/nonshy/website/pkg/encryption/keygen"
"code.nonshy.com/nonshy/website/pkg/log"
)
var (
ColdStorageDirectory = "./coldstorage"
ColdStorageKeysDirectory = path.Join(ColdStorageDirectory, "keys")
ColdStoragePrivateKeyFile = path.Join(ColdStorageKeysDirectory, "private.pem")
ColdStoragePublicKeyFile = path.Join(ColdStorageKeysDirectory, "public.pem")
)
// Initialize generates the RSA key pairs for the first time and creates
// the cold storage directories. It writes the keys to disk and returns the x509 encoded
// public key which goes in the settings.json (the keys on disk are for your bookkeeping).
func Initialize() ([]byte, error) {
log.Warn("NOTICE: rolling a random RSA key pair for cold storage")
rsaKey, err := keygen.NewRSAKeys()
if err != nil {
return nil, fmt.Errorf("generate RSA key: %s", err)
}
// Encode to x509
x509, err := keygen.SerializePublicKey(rsaKey.Public())
if err != nil {
return nil, fmt.Errorf("encode RSA public key to x509: %s", err)
}
// Write the public/private key files to disk.
if _, err := os.Stat(ColdStorageKeysDirectory); os.IsNotExist(err) {
log.Info("Notice: creating cold storage directory")
if err := os.MkdirAll(ColdStorageKeysDirectory, 0755); err != nil {
return nil, fmt.Errorf("create %s: %s", ColdStorageKeysDirectory, err)
}
}
if err := keygen.WriteRSAKeys(
rsaKey,
path.Join(ColdStorageKeysDirectory, "private.pem"),
path.Join(ColdStorageKeysDirectory, "public.pem"),
); err != nil {
return nil, fmt.Errorf("export newly generated public/private key files: %s", err)
}
return x509, nil
}
// Warning returns an error message if the private key is still on disk at its
// original generated location: it should be moved offline for security.
func Warning() error {
if _, err := os.Stat(ColdStoragePrivateKeyFile); os.IsNotExist(err) {
return nil
}
return errors.New("the private key file at ./coldstorage/keys should be moved off of the server and kept offline for safety")
}
// FileToColdStorage will copy a file, encrypted, into cold storage at the given filename.
func FileToColdStorage(sourceFilePath, outputFileName string, publicKeyPEM []byte) error {
if len(publicKeyPEM) == 0 {
return errors.New("no RSA public key")
}
// Load the public key from PEM encoding.
publicKey, err := keygen.DeserializePublicKey(publicKeyPEM)
if err != nil {
return fmt.Errorf("deserializing public key: %s", err)
}
// Generate a unique AES key for encrypting this file in one direction.
aesKey, err := keygen.NewAESKey()
if err != nil {
return err
}
// Encrypt the AES key and store it on disk next to the cold storage file.
ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, aesKey)
if err != nil {
return fmt.Errorf("encrypt error: %s", err)
}
err = os.WriteFile(
filepath.Join(ColdStorageDirectory, outputFileName+".aes"),
ciphertext,
0600,
)
if err != nil {
return err
}
// Read the original plaintext file going into cold storage.
plaintext, err := os.ReadFile(sourceFilePath)
if err != nil {
return fmt.Errorf("source file: %s", err)
}
// Encrypt the plaintext with the AES key.
ciphertext, err = keygen.EncryptWithAESKey(plaintext, aesKey)
if err != nil {
return err
}
// Write it to disk.
return os.WriteFile(filepath.Join(ColdStorageDirectory, outputFileName+".enc"), ciphertext, 0600)
}
// FileFromColdStorage decrypts a cold storage file and writes it to the output file.
//
// The command `nonshy coldstorage decrypt` uses this function. Requirements:
//
// - privateKeyFile is the RSA private key originally generated for cold storage
// - aesKeyFile is the unique .aes file for the cold storage item
// - ciphertextFile is the encrypted cold storage item
// - outputFile is where you want to save the result to
func FileFromColdStorage(privateKeyFile, aesKeyFile, ciphertextFile, outputFile string) error {
privateKey, err := keygen.PrivateKeyFromFile(privateKeyFile)
if err != nil {
return fmt.Errorf("private key file: %s", err)
}
encryptedAESKey, err := os.ReadFile(aesKeyFile)
if err != nil {
return fmt.Errorf("reading aes key file: %s", err)
}
aesKey, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedAESKey)
if err != nil {
return fmt.Errorf("decrypting the aes key file: %s", err)
}
ciphertext, err := os.ReadFile(ciphertextFile)
if err != nil {
return fmt.Errorf("reading cold storage file: %s", err)
}
plaintext, err := keygen.DecryptWithAESKey(ciphertext, aesKey)
if err != nil {
return fmt.Errorf("decrypting cold storage file: %s", err)
}
return os.WriteFile(outputFile, plaintext, 0644)
}