Merge pull request '[agent/claude:opus] DX audit and fix. 1) Review CLAUDE.md — update any outdate...' (#3) from agent/dx-audit-and-fix--1--review-claude-md into main
This commit is contained in:
commit
e691a9ce51
8 changed files with 65 additions and 65 deletions
|
|
@ -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
|
- **Tests**: testify assert/require, `_Good`/`_Bad`/`_Ugly` naming convention
|
||||||
- **Concurrency tests**: 10 goroutines via WaitGroup; must pass `-race`
|
- **Concurrency tests**: 10 goroutines via WaitGroup; must pass `-race`
|
||||||
- **Imports**: stdlib → forge.lthn.ai → third-party, separated by blank lines
|
- **Imports**: stdlib → forge.lthn.ai → third-party, separated by blank lines
|
||||||
- **Errors**: use `core.E("package.Function", "lowercase message", err)` (imported
|
- **Errors**: use `coreerr.E("package.Function", "lowercase message", err)` (imported
|
||||||
from `forge.lthn.ai/core/go-log`); never include secrets in error strings
|
as `coreerr "forge.lthn.ai/core/go-log"`); never include secrets in error strings
|
||||||
- **Randomness**: `crypto/rand` only; never `math/rand`
|
- **Randomness**: `crypto/rand` only; never `math/rand`
|
||||||
- **Conventional commits**: `feat(auth):`, `fix(crypt):`, `refactor(trust):`
|
- **Conventional commits**: `feat(auth):`, `fix(crypt):`, `refactor(trust):`
|
||||||
Scopes match package names: `auth`, `crypt`, `trust`, `pgp`, `lthn`, `rsa`,
|
Scopes match package names: `auth`, `crypt`, `trust`, `pgp`, `lthn`, `rsa`,
|
||||||
|
|
|
||||||
|
|
@ -171,15 +171,15 @@ func formatCoverage(cov float64) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func shortenPackageName(name string) string {
|
func shortenPackageName(name string) string {
|
||||||
// Remove common prefixes
|
const forgePrefix = "forge.lthn.ai/core/"
|
||||||
prefixes := []string{
|
if strings.HasPrefix(name, forgePrefix) {
|
||||||
"forge.lthn.ai/core/cli/",
|
remainder := strings.TrimPrefix(name, forgePrefix)
|
||||||
"forge.lthn.ai/core/gui/",
|
// If there's a sub-path (e.g. "go/pkg/foo"), strip the module name
|
||||||
}
|
if idx := strings.Index(remainder, "/"); idx >= 0 {
|
||||||
for _, prefix := range prefixes {
|
return remainder[idx+1:]
|
||||||
if strings.HasPrefix(name, prefix) {
|
|
||||||
return strings.TrimPrefix(name, prefix)
|
|
||||||
}
|
}
|
||||||
|
// Module root (e.g. "cli-php") — return as-is
|
||||||
|
return remainder
|
||||||
}
|
}
|
||||||
return filepath.Base(name)
|
return filepath.Base(name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,20 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"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.
|
// SHA256File computes the SHA-256 checksum of a file and returns it as a hex string.
|
||||||
func SHA256File(path string) (string, error) {
|
func SHA256File(path string) (string, error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
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() }()
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
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
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
|
@ -30,13 +30,13 @@ func SHA256File(path string) (string, error) {
|
||||||
func SHA512File(path string) (string, error) {
|
func SHA512File(path string) (string, error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
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() }()
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
h := sha512.New()
|
h := sha512.New()
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
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
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package crypt
|
package crypt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
core "forge.lthn.ai/core/go-log"
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encrypt encrypts data with a passphrase using ChaCha20-Poly1305.
|
// Encrypt encrypts data with a passphrase using ChaCha20-Poly1305.
|
||||||
|
|
@ -10,14 +10,14 @@ import (
|
||||||
func Encrypt(plaintext, passphrase []byte) ([]byte, error) {
|
func Encrypt(plaintext, passphrase []byte) ([]byte, error) {
|
||||||
salt, err := generateSalt(argon2SaltLen)
|
salt, err := generateSalt(argon2SaltLen)
|
||||||
if err != nil {
|
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)
|
key := DeriveKey(passphrase, salt, argon2KeyLen)
|
||||||
|
|
||||||
encrypted, err := ChaCha20Encrypt(plaintext, key)
|
encrypted, err := ChaCha20Encrypt(plaintext, key)
|
||||||
if err != nil {
|
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)
|
// 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.
|
// Expects format: salt (16 bytes) + nonce (24 bytes) + ciphertext.
|
||||||
func Decrypt(ciphertext, passphrase []byte) ([]byte, error) {
|
func Decrypt(ciphertext, passphrase []byte) ([]byte, error) {
|
||||||
if len(ciphertext) < argon2SaltLen {
|
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]
|
salt := ciphertext[:argon2SaltLen]
|
||||||
|
|
@ -41,7 +41,7 @@ func Decrypt(ciphertext, passphrase []byte) ([]byte, error) {
|
||||||
|
|
||||||
plaintext, err := ChaCha20Decrypt(encrypted, key)
|
plaintext, err := ChaCha20Decrypt(encrypted, key)
|
||||||
if err != nil {
|
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
|
return plaintext, nil
|
||||||
|
|
@ -53,14 +53,14 @@ func Decrypt(ciphertext, passphrase []byte) ([]byte, error) {
|
||||||
func EncryptAES(plaintext, passphrase []byte) ([]byte, error) {
|
func EncryptAES(plaintext, passphrase []byte) ([]byte, error) {
|
||||||
salt, err := generateSalt(argon2SaltLen)
|
salt, err := generateSalt(argon2SaltLen)
|
||||||
if err != nil {
|
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)
|
key := DeriveKey(passphrase, salt, argon2KeyLen)
|
||||||
|
|
||||||
encrypted, err := AESGCMEncrypt(plaintext, key)
|
encrypted, err := AESGCMEncrypt(plaintext, key)
|
||||||
if err != nil {
|
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))
|
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.
|
// Expects format: salt (16 bytes) + nonce (12 bytes) + ciphertext.
|
||||||
func DecryptAES(ciphertext, passphrase []byte) ([]byte, error) {
|
func DecryptAES(ciphertext, passphrase []byte) ([]byte, error) {
|
||||||
if len(ciphertext) < argon2SaltLen {
|
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]
|
salt := ciphertext[:argon2SaltLen]
|
||||||
|
|
@ -83,7 +83,7 @@ func DecryptAES(ciphertext, passphrase []byte) ([]byte, error) {
|
||||||
|
|
||||||
plaintext, err := AESGCMDecrypt(encrypted, key)
|
plaintext, err := AESGCMDecrypt(encrypted, key)
|
||||||
if err != nil {
|
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
|
return plaintext, nil
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
core "forge.lthn.ai/core/go-log"
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
@ -16,7 +16,7 @@ import (
|
||||||
func HashPassword(password string) (string, error) {
|
func HashPassword(password string) (string, error) {
|
||||||
salt, err := generateSalt(argon2SaltLen)
|
salt, err := generateSalt(argon2SaltLen)
|
||||||
if err != nil {
|
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)
|
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) {
|
func VerifyPassword(password, hash string) (bool, error) {
|
||||||
parts := strings.Split(hash, "$")
|
parts := strings.Split(hash, "$")
|
||||||
if len(parts) != 6 {
|
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
|
var version int
|
||||||
if _, err := fmt.Sscanf(parts[2], "v=%d", &version); err != nil {
|
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 memory uint32
|
||||||
var time uint32
|
var time uint32
|
||||||
var parallelism uint8
|
var parallelism uint8
|
||||||
if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &time, ¶llelism); err != nil {
|
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])
|
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||||
if err != nil {
|
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])
|
expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
|
||||||
if err != nil {
|
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)))
|
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) {
|
func HashBcrypt(password string, cost int) (string, error) {
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), cost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), cost)
|
||||||
if err != nil {
|
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
|
return string(hash), nil
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +83,7 @@ func VerifyBcrypt(password, hash string) (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if err != 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
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
core "forge.lthn.ai/core/go-log"
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
"golang.org/x/crypto/argon2"
|
"golang.org/x/crypto/argon2"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
"golang.org/x/crypto/scrypt"
|
"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) {
|
func DeriveKeyScrypt(passphrase, salt []byte, keyLen int) ([]byte, error) {
|
||||||
key, err := scrypt.Key(passphrase, salt, 32768, 8, 1, keyLen)
|
key, err := scrypt.Key(passphrase, salt, 32768, 8, 1, keyLen)
|
||||||
if err != nil {
|
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
|
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)
|
reader := hkdf.New(sha256.New, secret, salt, info)
|
||||||
key := make([]byte, keyLen)
|
key := make([]byte, keyLen)
|
||||||
if _, err := io.ReadFull(reader, key); err != nil {
|
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
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ func HKDF(secret, salt, info []byte, keyLen int) ([]byte, error) {
|
||||||
func generateSalt(length int) ([]byte, error) {
|
func generateSalt(length int) ([]byte, error) {
|
||||||
salt := make([]byte, length)
|
salt := make([]byte, length)
|
||||||
if _, err := rand.Read(salt); err != nil {
|
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
|
return salt, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
"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"
|
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)
|
entity, err := openpgp.NewEntity(name, "Workspace Key", "", config)
|
||||||
if err != nil {
|
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
|
// Encrypt private key if passphrase is provided
|
||||||
if passphrase != "" {
|
if passphrase != "" {
|
||||||
err = entity.PrivateKey.Encrypt([]byte(passphrase))
|
err = entity.PrivateKey.Encrypt([]byte(passphrase))
|
||||||
if err != nil {
|
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 {
|
for _, subkey := range entity.Subkeys {
|
||||||
err = subkey.PrivateKey.Encrypt([]byte(passphrase))
|
err = subkey.PrivateKey.Encrypt([]byte(passphrase))
|
||||||
if err != nil {
|
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
|
var buf bytes.Buffer
|
||||||
w, err := armor.Encode(&buf, openpgp.PrivateKeyType, nil)
|
w, err := armor.Encode(&buf, openpgp.PrivateKeyType, nil)
|
||||||
if err != 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
|
// Manual serialization to avoid panic from re-signing encrypted keys
|
||||||
err = s.serializeEntity(w, entity)
|
err = s.serializeEntity(w, entity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Close()
|
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()
|
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) {
|
func (s *Service) EncryptPGP(writer goio.Writer, recipientPath, data string, opts ...any) (string, error) {
|
||||||
entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(recipientPath))
|
entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(recipientPath))
|
||||||
if err != nil {
|
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
|
var armoredBuf bytes.Buffer
|
||||||
armoredWriter, err := armor.Encode(&armoredBuf, "PGP MESSAGE", nil)
|
armoredWriter, err := armor.Encode(&armoredBuf, "PGP MESSAGE", nil)
|
||||||
if err != 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
|
// 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)
|
w, err := openpgp.Encrypt(mw, entityList, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armoredWriter.Close()
|
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)
|
_, err = goio.WriteString(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.Close()
|
w.Close()
|
||||||
armoredWriter.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()
|
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) {
|
func (s *Service) DecryptPGP(privateKey, message, passphrase string, opts ...any) (string, error) {
|
||||||
entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(privateKey))
|
entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(privateKey))
|
||||||
if err != nil {
|
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]
|
entity := entityList[0]
|
||||||
if entity.PrivateKey.Encrypted {
|
if entity.PrivateKey.Encrypted {
|
||||||
err = entity.PrivateKey.Decrypt([]byte(passphrase))
|
err = entity.PrivateKey.Decrypt([]byte(passphrase))
|
||||||
if err != nil {
|
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 {
|
for _, subkey := range entity.Subkeys {
|
||||||
_ = subkey.PrivateKey.Decrypt([]byte(passphrase))
|
_ = subkey.PrivateKey.Decrypt([]byte(passphrase))
|
||||||
|
|
@ -156,18 +156,18 @@ func (s *Service) DecryptPGP(privateKey, message, passphrase string, opts ...any
|
||||||
// Decrypt armored message
|
// Decrypt armored message
|
||||||
block, err := armor.Decode(strings.NewReader(message))
|
block, err := armor.Decode(strings.NewReader(message))
|
||||||
if err != nil {
|
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)
|
md, err := openpgp.ReadMessage(block.Body, entityList, nil, nil)
|
||||||
if err != 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
|
var buf bytes.Buffer
|
||||||
_, err = goio.Copy(&buf, md.UnverifiedBody)
|
_, err = goio.Copy(&buf, md.UnverifiedBody)
|
||||||
if err != nil {
|
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
|
return buf.String(), nil
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
|
||||||
core "forge.lthn.ai/core/go-log"
|
coreerr "forge.lthn.ai/core/go-log"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -15,12 +15,12 @@ import (
|
||||||
func ChaCha20Encrypt(plaintext, key []byte) ([]byte, error) {
|
func ChaCha20Encrypt(plaintext, key []byte) ([]byte, error) {
|
||||||
aead, err := chacha20poly1305.NewX(key)
|
aead, err := chacha20poly1305.NewX(key)
|
||||||
if err != nil {
|
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())
|
nonce := make([]byte, aead.NonceSize())
|
||||||
if _, err := rand.Read(nonce); err != nil {
|
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)
|
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) {
|
func ChaCha20Decrypt(ciphertext, key []byte) ([]byte, error) {
|
||||||
aead, err := chacha20poly1305.NewX(key)
|
aead, err := chacha20poly1305.NewX(key)
|
||||||
if err != nil {
|
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()
|
nonceSize := aead.NonceSize()
|
||||||
if len(ciphertext) < 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:]
|
nonce, encrypted := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||||
plaintext, err := aead.Open(nil, nonce, encrypted, nil)
|
plaintext, err := aead.Open(nil, nonce, encrypted, nil)
|
||||||
if err != 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
|
return plaintext, nil
|
||||||
|
|
@ -55,17 +55,17 @@ func ChaCha20Decrypt(ciphertext, key []byte) ([]byte, error) {
|
||||||
func AESGCMEncrypt(plaintext, key []byte) ([]byte, error) {
|
func AESGCMEncrypt(plaintext, key []byte) ([]byte, error) {
|
||||||
block, err := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
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)
|
aead, err := cipher.NewGCM(block)
|
||||||
if err != nil {
|
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())
|
nonce := make([]byte, aead.NonceSize())
|
||||||
if _, err := rand.Read(nonce); err != nil {
|
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)
|
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) {
|
func AESGCMDecrypt(ciphertext, key []byte) ([]byte, error) {
|
||||||
block, err := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
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)
|
aead, err := cipher.NewGCM(block)
|
||||||
if err != nil {
|
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()
|
nonceSize := aead.NonceSize()
|
||||||
if len(ciphertext) < 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:]
|
nonce, encrypted := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||||
plaintext, err := aead.Open(nil, nonce, encrypted, nil)
|
plaintext, err := aead.Open(nil, nonce, encrypted, nil)
|
||||||
if err != 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
|
return plaintext, nil
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue