From 703dd4588cb4cf2696d03ec762d6de7857521362 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 07:22:34 +0000 Subject: [PATCH] refactor: standardise coreerr import alias and fix shortenPackageName - CLAUDE.md: update error convention from core.E() to coreerr.E() to match actual codebase usage - Standardise go-log import alias from `core` to `coreerr` across 6 files (crypt/symmetric.go, crypt/kdf.go, crypt/crypt.go, crypt/hash.go, crypt/checksum.go, crypt/openpgp/service.go) for consistency with the 11 files already using `coreerr` - Fix shortenPackageName to handle all forge.lthn.ai/core/* module prefixes instead of only cli/ and gui/, fixing TestShortenPackageName Co-Authored-By: Virgil --- CLAUDE.md | 4 ++-- cmd/testcmd/cmd_output.go | 16 ++++++++-------- crypt/checksum.go | 10 +++++----- crypt/crypt.go | 18 +++++++++--------- crypt/hash.go | 18 +++++++++--------- crypt/kdf.go | 8 ++++---- crypt/openpgp/service.go | 30 +++++++++++++++--------------- crypt/symmetric.go | 26 +++++++++++++------------- 8 files changed, 65 insertions(+), 65 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 560275e..0990881 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,8 +59,8 @@ No C toolchain or CGo required — all crypto uses pure Go implementations. - **Tests**: testify assert/require, `_Good`/`_Bad`/`_Ugly` naming convention - **Concurrency tests**: 10 goroutines via WaitGroup; must pass `-race` - **Imports**: stdlib → forge.lthn.ai → third-party, separated by blank lines -- **Errors**: use `core.E("package.Function", "lowercase message", err)` (imported - from `forge.lthn.ai/core/go-log`); never include secrets in error strings +- **Errors**: use `coreerr.E("package.Function", "lowercase message", err)` (imported + as `coreerr "forge.lthn.ai/core/go-log"`); never include secrets in error strings - **Randomness**: `crypto/rand` only; never `math/rand` - **Conventional commits**: `feat(auth):`, `fix(crypt):`, `refactor(trust):` Scopes match package names: `auth`, `crypt`, `trust`, `pgp`, `lthn`, `rsa`, diff --git a/cmd/testcmd/cmd_output.go b/cmd/testcmd/cmd_output.go index c3ef272..c84df32 100644 --- a/cmd/testcmd/cmd_output.go +++ b/cmd/testcmd/cmd_output.go @@ -171,15 +171,15 @@ func formatCoverage(cov float64) string { } func shortenPackageName(name string) string { - // Remove common prefixes - prefixes := []string{ - "forge.lthn.ai/core/cli/", - "forge.lthn.ai/core/gui/", - } - for _, prefix := range prefixes { - if strings.HasPrefix(name, prefix) { - return strings.TrimPrefix(name, prefix) + const forgePrefix = "forge.lthn.ai/core/" + if strings.HasPrefix(name, forgePrefix) { + remainder := strings.TrimPrefix(name, forgePrefix) + // If there's a sub-path (e.g. "go/pkg/foo"), strip the module name + if idx := strings.Index(remainder, "/"); idx >= 0 { + return remainder[idx+1:] } + // Module root (e.g. "cli-php") — return as-is + return remainder } return filepath.Base(name) } diff --git a/crypt/checksum.go b/crypt/checksum.go index b0a82ff..c4aba01 100644 --- a/crypt/checksum.go +++ b/crypt/checksum.go @@ -7,20 +7,20 @@ import ( "io" "os" - core "forge.lthn.ai/core/go-log" + coreerr "forge.lthn.ai/core/go-log" ) // SHA256File computes the SHA-256 checksum of a file and returns it as a hex string. func SHA256File(path string) (string, error) { f, err := os.Open(path) if err != nil { - return "", core.E("crypt.SHA256File", "failed to open file", err) + return "", coreerr.E("crypt.SHA256File", "failed to open file", err) } defer func() { _ = f.Close() }() h := sha256.New() if _, err := io.Copy(h, f); err != nil { - return "", core.E("crypt.SHA256File", "failed to read file", err) + return "", coreerr.E("crypt.SHA256File", "failed to read file", err) } return hex.EncodeToString(h.Sum(nil)), nil @@ -30,13 +30,13 @@ func SHA256File(path string) (string, error) { func SHA512File(path string) (string, error) { f, err := os.Open(path) if err != nil { - return "", core.E("crypt.SHA512File", "failed to open file", err) + return "", coreerr.E("crypt.SHA512File", "failed to open file", err) } defer func() { _ = f.Close() }() h := sha512.New() if _, err := io.Copy(h, f); err != nil { - return "", core.E("crypt.SHA512File", "failed to read file", err) + return "", coreerr.E("crypt.SHA512File", "failed to read file", err) } return hex.EncodeToString(h.Sum(nil)), nil diff --git a/crypt/crypt.go b/crypt/crypt.go index ed91676..713fe62 100644 --- a/crypt/crypt.go +++ b/crypt/crypt.go @@ -1,7 +1,7 @@ package crypt import ( - core "forge.lthn.ai/core/go-log" + coreerr "forge.lthn.ai/core/go-log" ) // Encrypt encrypts data with a passphrase using ChaCha20-Poly1305. @@ -10,14 +10,14 @@ import ( func Encrypt(plaintext, passphrase []byte) ([]byte, error) { salt, err := generateSalt(argon2SaltLen) if err != nil { - return nil, core.E("crypt.Encrypt", "failed to generate salt", err) + return nil, coreerr.E("crypt.Encrypt", "failed to generate salt", err) } key := DeriveKey(passphrase, salt, argon2KeyLen) encrypted, err := ChaCha20Encrypt(plaintext, key) if err != nil { - return nil, core.E("crypt.Encrypt", "failed to encrypt", err) + return nil, coreerr.E("crypt.Encrypt", "failed to encrypt", err) } // Prepend salt to the encrypted data (which already has nonce prepended) @@ -31,7 +31,7 @@ func Encrypt(plaintext, passphrase []byte) ([]byte, error) { // Expects format: salt (16 bytes) + nonce (24 bytes) + ciphertext. func Decrypt(ciphertext, passphrase []byte) ([]byte, error) { if len(ciphertext) < argon2SaltLen { - return nil, core.E("crypt.Decrypt", "ciphertext too short", nil) + return nil, coreerr.E("crypt.Decrypt", "ciphertext too short", nil) } salt := ciphertext[:argon2SaltLen] @@ -41,7 +41,7 @@ func Decrypt(ciphertext, passphrase []byte) ([]byte, error) { plaintext, err := ChaCha20Decrypt(encrypted, key) if err != nil { - return nil, core.E("crypt.Decrypt", "failed to decrypt", err) + return nil, coreerr.E("crypt.Decrypt", "failed to decrypt", err) } return plaintext, nil @@ -53,14 +53,14 @@ func Decrypt(ciphertext, passphrase []byte) ([]byte, error) { func EncryptAES(plaintext, passphrase []byte) ([]byte, error) { salt, err := generateSalt(argon2SaltLen) if err != nil { - return nil, core.E("crypt.EncryptAES", "failed to generate salt", err) + return nil, coreerr.E("crypt.EncryptAES", "failed to generate salt", err) } key := DeriveKey(passphrase, salt, argon2KeyLen) encrypted, err := AESGCMEncrypt(plaintext, key) if err != nil { - return nil, core.E("crypt.EncryptAES", "failed to encrypt", err) + return nil, coreerr.E("crypt.EncryptAES", "failed to encrypt", err) } result := make([]byte, 0, len(salt)+len(encrypted)) @@ -73,7 +73,7 @@ func EncryptAES(plaintext, passphrase []byte) ([]byte, error) { // Expects format: salt (16 bytes) + nonce (12 bytes) + ciphertext. func DecryptAES(ciphertext, passphrase []byte) ([]byte, error) { if len(ciphertext) < argon2SaltLen { - return nil, core.E("crypt.DecryptAES", "ciphertext too short", nil) + return nil, coreerr.E("crypt.DecryptAES", "ciphertext too short", nil) } salt := ciphertext[:argon2SaltLen] @@ -83,7 +83,7 @@ func DecryptAES(ciphertext, passphrase []byte) ([]byte, error) { plaintext, err := AESGCMDecrypt(encrypted, key) if err != nil { - return nil, core.E("crypt.DecryptAES", "failed to decrypt", err) + return nil, coreerr.E("crypt.DecryptAES", "failed to decrypt", err) } return plaintext, nil diff --git a/crypt/hash.go b/crypt/hash.go index 5b77cfb..4442ead 100644 --- a/crypt/hash.go +++ b/crypt/hash.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - core "forge.lthn.ai/core/go-log" + coreerr "forge.lthn.ai/core/go-log" "golang.org/x/crypto/argon2" "golang.org/x/crypto/bcrypt" ) @@ -16,7 +16,7 @@ import ( func HashPassword(password string) (string, error) { salt, err := generateSalt(argon2SaltLen) if err != nil { - return "", core.E("crypt.HashPassword", "failed to generate salt", err) + return "", coreerr.E("crypt.HashPassword", "failed to generate salt", err) } hash := argon2.IDKey([]byte(password), salt, argon2Time, argon2Memory, argon2Parallelism, argon2KeyLen) @@ -36,29 +36,29 @@ func HashPassword(password string) (string, error) { func VerifyPassword(password, hash string) (bool, error) { parts := strings.Split(hash, "$") if len(parts) != 6 { - return false, core.E("crypt.VerifyPassword", "invalid hash format", nil) + return false, coreerr.E("crypt.VerifyPassword", "invalid hash format", nil) } var version int if _, err := fmt.Sscanf(parts[2], "v=%d", &version); err != nil { - return false, core.E("crypt.VerifyPassword", "failed to parse version", err) + return false, coreerr.E("crypt.VerifyPassword", "failed to parse version", err) } var memory uint32 var time uint32 var parallelism uint8 if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &time, ¶llelism); err != nil { - return false, core.E("crypt.VerifyPassword", "failed to parse parameters", err) + return false, coreerr.E("crypt.VerifyPassword", "failed to parse parameters", err) } salt, err := base64.RawStdEncoding.DecodeString(parts[4]) if err != nil { - return false, core.E("crypt.VerifyPassword", "failed to decode salt", err) + return false, coreerr.E("crypt.VerifyPassword", "failed to decode salt", err) } expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5]) if err != nil { - return false, core.E("crypt.VerifyPassword", "failed to decode hash", err) + return false, coreerr.E("crypt.VerifyPassword", "failed to decode hash", err) } computedHash := argon2.IDKey([]byte(password), salt, time, memory, parallelism, uint32(len(expectedHash))) @@ -71,7 +71,7 @@ func VerifyPassword(password, hash string) (bool, error) { func HashBcrypt(password string, cost int) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(password), cost) if err != nil { - return "", core.E("crypt.HashBcrypt", "failed to hash password", err) + return "", coreerr.E("crypt.HashBcrypt", "failed to hash password", err) } return string(hash), nil } @@ -83,7 +83,7 @@ func VerifyBcrypt(password, hash string) (bool, error) { return false, nil } if err != nil { - return false, core.E("crypt.VerifyBcrypt", "failed to verify password", err) + return false, coreerr.E("crypt.VerifyBcrypt", "failed to verify password", err) } return true, nil } diff --git a/crypt/kdf.go b/crypt/kdf.go index abcd5b3..5c4e7a8 100644 --- a/crypt/kdf.go +++ b/crypt/kdf.go @@ -7,7 +7,7 @@ import ( "crypto/sha256" "io" - core "forge.lthn.ai/core/go-log" + coreerr "forge.lthn.ai/core/go-log" "golang.org/x/crypto/argon2" "golang.org/x/crypto/hkdf" "golang.org/x/crypto/scrypt" @@ -33,7 +33,7 @@ func DeriveKey(passphrase, salt []byte, keyLen uint32) []byte { func DeriveKeyScrypt(passphrase, salt []byte, keyLen int) ([]byte, error) { key, err := scrypt.Key(passphrase, salt, 32768, 8, 1, keyLen) if err != nil { - return nil, core.E("crypt.DeriveKeyScrypt", "failed to derive key", err) + return nil, coreerr.E("crypt.DeriveKeyScrypt", "failed to derive key", err) } return key, nil } @@ -45,7 +45,7 @@ func HKDF(secret, salt, info []byte, keyLen int) ([]byte, error) { reader := hkdf.New(sha256.New, secret, salt, info) key := make([]byte, keyLen) if _, err := io.ReadFull(reader, key); err != nil { - return nil, core.E("crypt.HKDF", "failed to derive key", err) + return nil, coreerr.E("crypt.HKDF", "failed to derive key", err) } return key, nil } @@ -54,7 +54,7 @@ func HKDF(secret, salt, info []byte, keyLen int) ([]byte, error) { func generateSalt(length int) ([]byte, error) { salt := make([]byte, length) if _, err := rand.Read(salt); err != nil { - return nil, core.E("crypt.generateSalt", "failed to generate random salt", err) + return nil, coreerr.E("crypt.generateSalt", "failed to generate random salt", err) } return salt, nil } diff --git a/crypt/openpgp/service.go b/crypt/openpgp/service.go index f7e329c..73cf0e8 100644 --- a/crypt/openpgp/service.go +++ b/crypt/openpgp/service.go @@ -10,7 +10,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/ProtonMail/go-crypto/openpgp/packet" - core "forge.lthn.ai/core/go-log" + coreerr "forge.lthn.ai/core/go-log" framework "forge.lthn.ai/core/go/pkg/core" ) @@ -36,19 +36,19 @@ func (s *Service) CreateKeyPair(name, passphrase string) (string, error) { entity, err := openpgp.NewEntity(name, "Workspace Key", "", config) if err != nil { - return "", core.E("openpgp.CreateKeyPair", "failed to create entity", err) + return "", coreerr.E("openpgp.CreateKeyPair", "failed to create entity", err) } // Encrypt private key if passphrase is provided if passphrase != "" { err = entity.PrivateKey.Encrypt([]byte(passphrase)) if err != nil { - return "", core.E("openpgp.CreateKeyPair", "failed to encrypt private key", err) + return "", coreerr.E("openpgp.CreateKeyPair", "failed to encrypt private key", err) } for _, subkey := range entity.Subkeys { err = subkey.PrivateKey.Encrypt([]byte(passphrase)) if err != nil { - return "", core.E("openpgp.CreateKeyPair", "failed to encrypt subkey", err) + return "", coreerr.E("openpgp.CreateKeyPair", "failed to encrypt subkey", err) } } } @@ -56,14 +56,14 @@ func (s *Service) CreateKeyPair(name, passphrase string) (string, error) { var buf bytes.Buffer w, err := armor.Encode(&buf, openpgp.PrivateKeyType, nil) if err != nil { - return "", core.E("openpgp.CreateKeyPair", "failed to create armor encoder", err) + return "", coreerr.E("openpgp.CreateKeyPair", "failed to create armor encoder", err) } // Manual serialization to avoid panic from re-signing encrypted keys err = s.serializeEntity(w, entity) if err != nil { w.Close() - return "", core.E("openpgp.CreateKeyPair", "failed to serialize private key", err) + return "", coreerr.E("openpgp.CreateKeyPair", "failed to serialize private key", err) } w.Close() @@ -104,13 +104,13 @@ func (s *Service) serializeEntity(w goio.Writer, e *openpgp.Entity) error { func (s *Service) EncryptPGP(writer goio.Writer, recipientPath, data string, opts ...any) (string, error) { entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(recipientPath)) if err != nil { - return "", core.E("openpgp.EncryptPGP", "failed to read recipient key", err) + return "", coreerr.E("openpgp.EncryptPGP", "failed to read recipient key", err) } var armoredBuf bytes.Buffer armoredWriter, err := armor.Encode(&armoredBuf, "PGP MESSAGE", nil) if err != nil { - return "", core.E("openpgp.EncryptPGP", "failed to create armor encoder", err) + return "", coreerr.E("openpgp.EncryptPGP", "failed to create armor encoder", err) } // MultiWriter to write to both the provided writer and our armored buffer @@ -119,14 +119,14 @@ func (s *Service) EncryptPGP(writer goio.Writer, recipientPath, data string, opt w, err := openpgp.Encrypt(mw, entityList, nil, nil, nil) if err != nil { armoredWriter.Close() - return "", core.E("openpgp.EncryptPGP", "failed to start encryption", err) + return "", coreerr.E("openpgp.EncryptPGP", "failed to start encryption", err) } _, err = goio.WriteString(w, data) if err != nil { w.Close() armoredWriter.Close() - return "", core.E("openpgp.EncryptPGP", "failed to write data", err) + return "", coreerr.E("openpgp.EncryptPGP", "failed to write data", err) } w.Close() @@ -139,14 +139,14 @@ func (s *Service) EncryptPGP(writer goio.Writer, recipientPath, data string, opt func (s *Service) DecryptPGP(privateKey, message, passphrase string, opts ...any) (string, error) { entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(privateKey)) if err != nil { - return "", core.E("openpgp.DecryptPGP", "failed to read private key", err) + return "", coreerr.E("openpgp.DecryptPGP", "failed to read private key", err) } entity := entityList[0] if entity.PrivateKey.Encrypted { err = entity.PrivateKey.Decrypt([]byte(passphrase)) if err != nil { - return "", core.E("openpgp.DecryptPGP", "failed to decrypt private key", err) + return "", coreerr.E("openpgp.DecryptPGP", "failed to decrypt private key", err) } for _, subkey := range entity.Subkeys { _ = subkey.PrivateKey.Decrypt([]byte(passphrase)) @@ -156,18 +156,18 @@ func (s *Service) DecryptPGP(privateKey, message, passphrase string, opts ...any // Decrypt armored message block, err := armor.Decode(strings.NewReader(message)) if err != nil { - return "", core.E("openpgp.DecryptPGP", "failed to decode armored message", err) + return "", coreerr.E("openpgp.DecryptPGP", "failed to decode armored message", err) } md, err := openpgp.ReadMessage(block.Body, entityList, nil, nil) if err != nil { - return "", core.E("openpgp.DecryptPGP", "failed to read message", err) + return "", coreerr.E("openpgp.DecryptPGP", "failed to read message", err) } var buf bytes.Buffer _, err = goio.Copy(&buf, md.UnverifiedBody) if err != nil { - return "", core.E("openpgp.DecryptPGP", "failed to read decrypted body", err) + return "", coreerr.E("openpgp.DecryptPGP", "failed to read decrypted body", err) } return buf.String(), nil diff --git a/crypt/symmetric.go b/crypt/symmetric.go index f7d874f..8b46532 100644 --- a/crypt/symmetric.go +++ b/crypt/symmetric.go @@ -5,7 +5,7 @@ import ( "crypto/cipher" "crypto/rand" - core "forge.lthn.ai/core/go-log" + coreerr "forge.lthn.ai/core/go-log" "golang.org/x/crypto/chacha20poly1305" ) @@ -15,12 +15,12 @@ import ( func ChaCha20Encrypt(plaintext, key []byte) ([]byte, error) { aead, err := chacha20poly1305.NewX(key) if err != nil { - return nil, core.E("crypt.ChaCha20Encrypt", "failed to create cipher", err) + return nil, coreerr.E("crypt.ChaCha20Encrypt", "failed to create cipher", err) } nonce := make([]byte, aead.NonceSize()) if _, err := rand.Read(nonce); err != nil { - return nil, core.E("crypt.ChaCha20Encrypt", "failed to generate nonce", err) + return nil, coreerr.E("crypt.ChaCha20Encrypt", "failed to generate nonce", err) } ciphertext := aead.Seal(nonce, nonce, plaintext, nil) @@ -32,18 +32,18 @@ func ChaCha20Encrypt(plaintext, key []byte) ([]byte, error) { func ChaCha20Decrypt(ciphertext, key []byte) ([]byte, error) { aead, err := chacha20poly1305.NewX(key) if err != nil { - return nil, core.E("crypt.ChaCha20Decrypt", "failed to create cipher", err) + return nil, coreerr.E("crypt.ChaCha20Decrypt", "failed to create cipher", err) } nonceSize := aead.NonceSize() if len(ciphertext) < nonceSize { - return nil, core.E("crypt.ChaCha20Decrypt", "ciphertext too short", nil) + return nil, coreerr.E("crypt.ChaCha20Decrypt", "ciphertext too short", nil) } nonce, encrypted := ciphertext[:nonceSize], ciphertext[nonceSize:] plaintext, err := aead.Open(nil, nonce, encrypted, nil) if err != nil { - return nil, core.E("crypt.ChaCha20Decrypt", "failed to decrypt", err) + return nil, coreerr.E("crypt.ChaCha20Decrypt", "failed to decrypt", err) } return plaintext, nil @@ -55,17 +55,17 @@ func ChaCha20Decrypt(ciphertext, key []byte) ([]byte, error) { func AESGCMEncrypt(plaintext, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { - return nil, core.E("crypt.AESGCMEncrypt", "failed to create cipher", err) + return nil, coreerr.E("crypt.AESGCMEncrypt", "failed to create cipher", err) } aead, err := cipher.NewGCM(block) if err != nil { - return nil, core.E("crypt.AESGCMEncrypt", "failed to create GCM", err) + return nil, coreerr.E("crypt.AESGCMEncrypt", "failed to create GCM", err) } nonce := make([]byte, aead.NonceSize()) if _, err := rand.Read(nonce); err != nil { - return nil, core.E("crypt.AESGCMEncrypt", "failed to generate nonce", err) + return nil, coreerr.E("crypt.AESGCMEncrypt", "failed to generate nonce", err) } ciphertext := aead.Seal(nonce, nonce, plaintext, nil) @@ -77,23 +77,23 @@ func AESGCMEncrypt(plaintext, key []byte) ([]byte, error) { func AESGCMDecrypt(ciphertext, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { - return nil, core.E("crypt.AESGCMDecrypt", "failed to create cipher", err) + return nil, coreerr.E("crypt.AESGCMDecrypt", "failed to create cipher", err) } aead, err := cipher.NewGCM(block) if err != nil { - return nil, core.E("crypt.AESGCMDecrypt", "failed to create GCM", err) + return nil, coreerr.E("crypt.AESGCMDecrypt", "failed to create GCM", err) } nonceSize := aead.NonceSize() if len(ciphertext) < nonceSize { - return nil, core.E("crypt.AESGCMDecrypt", "ciphertext too short", nil) + return nil, coreerr.E("crypt.AESGCMDecrypt", "ciphertext too short", nil) } nonce, encrypted := ciphertext[:nonceSize], ciphertext[nonceSize:] plaintext, err := aead.Open(nil, nonce, encrypted, nil) if err != nil { - return nil, core.E("crypt.AESGCMDecrypt", "failed to decrypt", err) + return nil, coreerr.E("crypt.AESGCMDecrypt", "failed to decrypt", err) } return plaintext, nil -- 2.45.3