2022-04-08 05:59:40 +02:00
package data
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
2022-10-24 06:42:22 +02:00
"database/sql/driver"
2022-04-08 05:59:40 +02:00
"encoding/base64"
"encoding/json"
2022-04-08 06:40:22 +02:00
"fmt"
"io"
2022-12-17 07:22:57 +01:00
"os"
2022-04-08 06:40:22 +02:00
"time"
2022-04-08 05:59:40 +02:00
"github.com/ddworken/hishtory/shared"
)
const (
2022-04-08 06:40:22 +02:00
KdfUserID = "user_id"
KdfEncryptionKey = "encryption_key"
2022-11-01 18:23:35 +01:00
CONFIG_PATH = ".hishtory.config"
DB_PATH = ".hishtory.db"
2022-04-08 05:59:40 +02:00
)
2022-12-17 07:22:57 +01:00
const (
defaultHishtoryPath = ".hishtory"
)
2022-04-08 05:59:40 +02:00
type HistoryEntry struct {
2022-10-24 06:42:22 +02:00
LocalUsername string ` json:"local_username" gorm:"uniqueIndex:compositeindex" `
Hostname string ` json:"hostname" gorm:"uniqueIndex:compositeindex" `
Command string ` json:"command" gorm:"uniqueIndex:compositeindex" `
CurrentWorkingDirectory string ` json:"current_working_directory" gorm:"uniqueIndex:compositeindex" `
HomeDirectory string ` json:"home_directory" gorm:"uniqueIndex:compositeindex" `
ExitCode int ` json:"exit_code" gorm:"uniqueIndex:compositeindex" `
2024-04-14 19:19:46 +02:00
StartTime time . Time ` json:"start_time" gorm:"uniqueIndex:compositeindex,index:start_time_index" `
2023-02-19 07:26:18 +01:00
EndTime time . Time ` json:"end_time" gorm:"uniqueIndex:compositeindex,index:end_time_index" `
2022-10-24 06:42:22 +02:00
DeviceId string ` json:"device_id" gorm:"uniqueIndex:compositeindex" `
2023-09-22 22:16:24 +02:00
EntryId string ` json:"entry_id" gorm:"uniqueIndex:compositeindex,uniqueIndex:entry_id_index" `
2022-10-24 06:42:22 +02:00
CustomColumns CustomColumns ` json:"custom_columns" `
}
2022-10-24 07:01:53 +02:00
type CustomColumns [ ] CustomColumn
2022-10-24 06:42:22 +02:00
type CustomColumn struct {
Name string ` json:"name" `
Val string ` json:"value" `
}
2023-10-08 00:11:49 +02:00
func ( c * CustomColumns ) Scan ( value any ) error {
2022-10-24 06:42:22 +02:00
bytes , ok := value . ( [ ] byte )
if ! ok {
return fmt . Errorf ( "failed to unmarshal CustomColumns value %#v" , value )
}
return json . Unmarshal ( bytes , c )
}
func ( c CustomColumns ) Value ( ) ( driver . Value , error ) {
return json . Marshal ( c )
2022-04-08 05:59:40 +02:00
}
2022-09-20 07:49:48 +02:00
func ( h * HistoryEntry ) GoString ( ) string {
return fmt . Sprintf ( "%#v" , * h )
}
2022-04-08 05:59:40 +02:00
func sha256hmac ( key , additionalData string ) [ ] byte {
h := hmac . New ( sha256 . New , [ ] byte ( key ) )
h . Write ( [ ] byte ( additionalData ) )
return h . Sum ( nil )
}
func UserId ( key string ) string {
2022-04-08 06:40:22 +02:00
return base64 . URLEncoding . EncodeToString ( sha256hmac ( key , KdfUserID ) )
2022-04-08 05:59:40 +02:00
}
func EncryptionKey ( userSecret string ) [ ] byte {
2022-04-08 06:40:22 +02:00
return sha256hmac ( userSecret , KdfEncryptionKey )
2022-04-08 05:59:40 +02:00
}
func makeAead ( userSecret string ) ( cipher . AEAD , error ) {
key := EncryptionKey ( userSecret )
block , err := aes . NewCipher ( key )
if err != nil {
return nil , err
}
aead , err := cipher . NewGCM ( block )
if err != nil {
return nil , err
}
return aead , nil
}
func Encrypt ( userSecret string , data , additionalData [ ] byte ) ( [ ] byte , [ ] byte , error ) {
aead , err := makeAead ( userSecret )
if err != nil {
2023-09-05 21:08:55 +02:00
return [ ] byte { } , [ ] byte { } , fmt . Errorf ( "failed to make AEAD: %w" , err )
2022-04-08 05:59:40 +02:00
}
nonce := make ( [ ] byte , 12 )
if _ , err := io . ReadFull ( rand . Reader , nonce ) ; err != nil {
2023-09-05 21:08:55 +02:00
return [ ] byte { } , [ ] byte { } , fmt . Errorf ( "failed to read a nonce: %w" , err )
2022-04-08 05:59:40 +02:00
}
ciphertext := aead . Seal ( nil , nonce , data , additionalData )
_ , err = aead . Open ( nil , nonce , ciphertext , additionalData )
if err != nil {
2023-09-05 21:08:55 +02:00
return [ ] byte { } , [ ] byte { } , fmt . Errorf ( "failed to open AEAD: %w" , err )
2022-04-08 05:59:40 +02:00
}
return ciphertext , nonce , nil
}
func Decrypt ( userSecret string , data , additionalData , nonce [ ] byte ) ( [ ] byte , error ) {
aead , err := makeAead ( userSecret )
if err != nil {
2023-09-05 21:08:55 +02:00
return [ ] byte { } , fmt . Errorf ( "failed to make AEAD: %w" , err )
2022-04-08 05:59:40 +02:00
}
plaintext , err := aead . Open ( nil , nonce , data , additionalData )
if err != nil {
2023-09-05 21:08:55 +02:00
return [ ] byte { } , fmt . Errorf ( "failed to decrypt: %w" , err )
2022-04-08 05:59:40 +02:00
}
return plaintext , nil
}
func EncryptHistoryEntry ( userSecret string , entry HistoryEntry ) ( shared . EncHistoryEntry , error ) {
data , err := json . Marshal ( entry )
if err != nil {
return shared . EncHistoryEntry { } , err
}
ciphertext , nonce , err := Encrypt ( userSecret , data , [ ] byte ( UserId ( userSecret ) ) )
if err != nil {
return shared . EncHistoryEntry { } , err
}
return shared . EncHistoryEntry {
EncryptedData : ciphertext ,
Nonce : nonce ,
UserId : UserId ( userSecret ) ,
2022-09-20 07:49:48 +02:00
Date : entry . EndTime ,
2023-09-22 22:16:24 +02:00
EncryptedId : entry . EntryId ,
2022-04-08 05:59:40 +02:00
ReadCount : 0 ,
} , nil
}
func DecryptHistoryEntry ( userSecret string , entry shared . EncHistoryEntry ) ( HistoryEntry , error ) {
if entry . UserId != UserId ( userSecret ) {
2022-04-08 06:40:22 +02:00
return HistoryEntry { } , fmt . Errorf ( "refusing to decrypt history entry with mismatching UserId" )
2022-04-08 05:59:40 +02:00
}
plaintext , err := Decrypt ( userSecret , entry . EncryptedData , [ ] byte ( UserId ( userSecret ) ) , entry . Nonce )
if err != nil {
return HistoryEntry { } , nil
}
var decryptedEntry HistoryEntry
err = json . Unmarshal ( plaintext , & decryptedEntry )
if err != nil {
return HistoryEntry { } , nil
}
2023-11-19 17:55:15 +01:00
if decryptedEntry . EntryId != "" && entry . EncryptedId != "" && decryptedEntry . EntryId != entry . EncryptedId {
return HistoryEntry { } , fmt . Errorf ( "rejecting encrypted history entry that contains mismatching IDs (outer=%s inner=%s)" , entry . EncryptedId , decryptedEntry . EntryId )
}
2022-04-08 05:59:40 +02:00
return decryptedEntry , nil
}
2022-12-17 07:22:57 +01:00
func GetHishtoryPath ( ) string {
hishtoryPath := os . Getenv ( "HISHTORY_PATH" )
if hishtoryPath != "" {
return hishtoryPath
}
return defaultHishtoryPath
}