diff --git a/.gitea/workflows/tests.yml b/.gitea/workflows/tests.yml index 904ad7c..7958fc9 100644 --- a/.gitea/workflows/tests.yml +++ b/.gitea/workflows/tests.yml @@ -1,7 +1,7 @@ on: push: branches: - - "*" + - "**" name: 🧪 ✨ Unit Tests Workflow diff --git a/crypt/file.go b/crypt/file.go index 68636bb..e29d206 100644 --- a/crypt/file.go +++ b/crypt/file.go @@ -4,15 +4,20 @@ import ( "bytes" "crypto/aes" "crypto/cipher" + "crypto/hmac" "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/subtle" "fmt" "os" ) +const hmacKey = "::HMAC::" + type EncryptedFile struct { ciphertext []byte + hmac []byte plainText []byte nonce []byte privatePem []byte @@ -25,7 +30,13 @@ type EncryptedFile struct { func (f *EncryptedFile) packFile() []byte { file := append(f.nonce, f.ciphertext...) - return append(file, f.symmetricKeyEnc...) + file = append(file, f.symmetricKeyEnc...) + if len(f.hmac) > 0 { + file = append(file, []byte(hmacKey)...) + file = append(file, f.hmac...) + } + + return file } func (f *EncryptedFile) EncryptFile() error { @@ -48,9 +59,29 @@ func (f *EncryptedFile) EncryptFile() error { cbc.CryptBlocks(ciphertext, plaintextP) f.ciphertext = ciphertext + mac, err := f.generateHmac() + if err != nil { + return err + } + + f.hmac = mac + return nil } +func (f *EncryptedFile) generateHmac() ([]byte, error) { + if len(f.symmetricKey) == 0 { + return nil, fmt.Errorf("symmetric key is not set") + } + + mac := hmac.New(sha256.New, f.symmetricKey) + mac.Write(f.nonce) + mac.Write(f.ciphertext) + + f.hmac = mac.Sum(nil) + return f.hmac, nil +} + func (f *EncryptedFile) OsReadPlainTextFile(path string) error { plaintext, err := os.ReadFile(path) if err != nil { @@ -84,7 +115,21 @@ func (f *EncryptedFile) WriteDecryptedFileToDisk(filePath string) error { func (f *EncryptedFile) unpackFileAndDecrypt(packedFile []byte) error { keyLen := f.privateKey.Size() + minReqLen := aes.BlockSize + keyLen + len(hmacKey) + + if len(packedFile) < minReqLen { + return fmt.Errorf("packed file is too short to be valid") + } + + if bytes.Contains(packedFile, []byte(hmacKey)) { + parts := bytes.SplitN(packedFile, []byte(hmacKey), 2) + packedFile, f.hmac = parts[0], parts[1] + } + lenWithoutKey := len(packedFile) - keyLen + if lenWithoutKey < aes.BlockSize { + return fmt.Errorf("packed file is too short to contain valid nonce and ciphertext") + } packedFile, f.symmetricKeyEnc = packedFile[0:lenWithoutKey], packedFile[lenWithoutKey:] @@ -93,10 +138,22 @@ func (f *EncryptedFile) unpackFileAndDecrypt(packedFile []byte) error { return err } + if len(f.hmac) > 0 { + mac, err := f.generateHmac() + if err != nil { + return err + } + + if !hmac.Equal(mac, f.hmac) { + return fmt.Errorf("hmac verification failed") + } + } + a, err := aes.NewCipher(f.symmetricKey) if err != nil { return err } + f.nonce, f.ciphertext = packedFile[0:aes.BlockSize], packedFile[aes.BlockSize:] cbc := cipher.NewCBCDecrypter(a, f.nonce) diff --git a/crypt/keys.go b/crypt/keys.go index c424c4c..2f0242f 100644 --- a/crypt/keys.go +++ b/crypt/keys.go @@ -7,6 +7,7 @@ import ( "crypto/sha512" "crypto/x509" "encoding/pem" + "fmt" "os" ) @@ -85,6 +86,10 @@ func (f *EncryptedFile) GenerateSymmetricKey() error { func (f *EncryptedFile) ParsePublicPem() error { pemKeyBin, _ := pem.Decode(f.PublicPem) + if pemKeyBin == nil { + return fmt.Errorf("failed to parse PEM block containing the public key") + } + if bytes.Contains(f.PublicPem, []byte("-----BEGIN PUBLIC KEY-----")) { key, err := x509.ParsePKIXPublicKey(pemKeyBin.Bytes) if err != nil { @@ -109,6 +114,10 @@ func (f *EncryptedFile) ParsePublicPem() error { func (f *EncryptedFile) ParsePrivatePem() error { pemKeyBin, _ := pem.Decode(f.privatePem) + if pemKeyBin == nil { + return fmt.Errorf("failed to parse PEM block containing the private key") + } + if bytes.Contains(f.privatePem, []byte("-----BEGIN PRIVATE KEY-----")) { key, err := x509.ParsePKCS8PrivateKey(pemKeyBin.Bytes) if err != nil {