diff --git a/auth/auth.go b/auth/auth.go index 85fd915..6a6faa8 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -30,7 +30,6 @@ import ( "crypto/rand" "encoding/hex" "encoding/json" - "errors" "fmt" "strings" "sync" @@ -642,16 +641,18 @@ func (a *Authenticator) ReadResponseFile(userID, path string) (*Session, error) // Tries Argon2id (.hash) first, then falls back to legacy LTHN (.lthn). // Returns nil on success, or an error describing the failure. func (a *Authenticator) verifyPassword(userID, password string) error { + const op = "auth.verifyPassword" + // Try Argon2id hash first (.hash file) if a.medium.IsFile(userPath(userID, ".hash")) { storedHash, err := a.medium.Read(userPath(userID, ".hash")) if err == nil && strings.HasPrefix(storedHash, "$argon2id$") { valid, verr := crypt.VerifyPassword(password, storedHash) if verr != nil { - return errors.New("failed to verify password") + return coreerr.E(op, "failed to verify password", nil) } if !valid { - return errors.New("invalid password") + return coreerr.E(op, "invalid password", nil) } return nil } @@ -660,10 +661,10 @@ func (a *Authenticator) verifyPassword(userID, password string) error { // Fall back to legacy LTHN hash (.lthn file) storedHash, err := a.medium.Read(userPath(userID, ".lthn")) if err != nil { - return errors.New("user not found") + return coreerr.E(op, "user not found", nil) } if !lthn.Verify(password, storedHash) { - return errors.New("invalid password") + return coreerr.E(op, "invalid password", nil) } return nil } @@ -671,9 +672,11 @@ func (a *Authenticator) verifyPassword(userID, password string) error { // createSession generates a cryptographically random session token and // stores the session via the SessionStore. func (a *Authenticator) createSession(userID string) (*Session, error) { + const op = "auth.createSession" + tokenBytes := make([]byte, 32) if _, err := rand.Read(tokenBytes); err != nil { - return nil, fmt.Errorf("auth: failed to generate session token: %w", err) + return nil, coreerr.E(op, "failed to generate session token", err) } session := &Session{ @@ -683,7 +686,7 @@ func (a *Authenticator) createSession(userID string) (*Session, error) { } if err := a.store.Set(session); err != nil { - return nil, fmt.Errorf("auth: failed to persist session: %w", err) + return nil, coreerr.E(op, "failed to persist session", err) } return session, nil diff --git a/crypt/chachapoly/chachapoly.go b/crypt/chachapoly/chachapoly.go index 2520c67..7a1f326 100644 --- a/crypt/chachapoly/chachapoly.go +++ b/crypt/chachapoly/chachapoly.go @@ -5,6 +5,8 @@ import ( "fmt" "io" + coreerr "forge.lthn.ai/core/go-log" + "golang.org/x/crypto/chacha20poly1305" ) @@ -32,7 +34,7 @@ func Decrypt(ciphertext []byte, key []byte) ([]byte, error) { minLen := aead.NonceSize() + aead.Overhead() if len(ciphertext) < minLen { - return nil, fmt.Errorf("ciphertext too short: got %d bytes, need at least %d bytes", len(ciphertext), minLen) + return nil, coreerr.E("chachapoly.Decrypt", fmt.Sprintf("ciphertext too short: got %d bytes, need at least %d bytes", len(ciphertext), minLen), nil) } nonce, ciphertext := ciphertext[:aead.NonceSize()], ciphertext[aead.NonceSize():] diff --git a/crypt/pgp/pgp.go b/crypt/pgp/pgp.go index 67170d5..627c43b 100644 --- a/crypt/pgp/pgp.go +++ b/crypt/pgp/pgp.go @@ -6,10 +6,10 @@ package pgp import ( "bytes" - "errors" - "fmt" "io" + coreerr "forge.lthn.ai/core/go-log" + "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/ProtonMail/go-crypto/openpgp/packet" @@ -25,9 +25,11 @@ type KeyPair struct { // If password is non-empty, the private key is encrypted with it. // Returns a KeyPair with armored public and private keys. func CreateKeyPair(name, email, password string) (*KeyPair, error) { + const op = "pgp.CreateKeyPair" + entity, err := openpgp.NewEntity(name, "", email, nil) if err != nil { - return nil, fmt.Errorf("pgp: failed to create entity: %w", err) + return nil, coreerr.E(op, "failed to create entity", err) } // Sign all the identities @@ -39,12 +41,12 @@ func CreateKeyPair(name, email, password string) (*KeyPair, error) { if password != "" { err = entity.PrivateKey.Encrypt([]byte(password)) if err != nil { - return nil, fmt.Errorf("pgp: failed to encrypt private key: %w", err) + return nil, coreerr.E(op, "failed to encrypt private key", err) } for _, subkey := range entity.Subkeys { err = subkey.PrivateKey.Encrypt([]byte(password)) if err != nil { - return nil, fmt.Errorf("pgp: failed to encrypt subkey: %w", err) + return nil, coreerr.E(op, "failed to encrypt subkey", err) } } } @@ -53,11 +55,11 @@ func CreateKeyPair(name, email, password string) (*KeyPair, error) { pubKeyBuf := new(bytes.Buffer) pubKeyWriter, err := armor.Encode(pubKeyBuf, openpgp.PublicKeyType, nil) if err != nil { - return nil, fmt.Errorf("pgp: failed to create armored public key writer: %w", err) + return nil, coreerr.E(op, "failed to create armored public key writer", err) } if err := entity.Serialize(pubKeyWriter); err != nil { pubKeyWriter.Close() - return nil, fmt.Errorf("pgp: failed to serialize public key: %w", err) + return nil, coreerr.E(op, "failed to serialize public key", err) } pubKeyWriter.Close() @@ -65,18 +67,18 @@ func CreateKeyPair(name, email, password string) (*KeyPair, error) { privKeyBuf := new(bytes.Buffer) privKeyWriter, err := armor.Encode(privKeyBuf, openpgp.PrivateKeyType, nil) if err != nil { - return nil, fmt.Errorf("pgp: failed to create armored private key writer: %w", err) + return nil, coreerr.E(op, "failed to create armored private key writer", err) } if password != "" { // Manual serialization to avoid re-signing encrypted keys if err := serializeEncryptedEntity(privKeyWriter, entity); err != nil { privKeyWriter.Close() - return nil, fmt.Errorf("pgp: failed to serialize private key: %w", err) + return nil, coreerr.E(op, "failed to serialize private key", err) } } else { if err := entity.SerializePrivate(privKeyWriter, nil); err != nil { privKeyWriter.Close() - return nil, fmt.Errorf("pgp: failed to serialize private key: %w", err) + return nil, coreerr.E(op, "failed to serialize private key", err) } } privKeyWriter.Close() @@ -115,27 +117,29 @@ func serializeEncryptedEntity(w io.Writer, e *openpgp.Entity) error { // Encrypt encrypts data for the recipient identified by their armored public key. // Returns the encrypted data as armored PGP output. func Encrypt(data []byte, publicKeyArmor string) ([]byte, error) { + const op = "pgp.Encrypt" + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(publicKeyArmor))) if err != nil { - return nil, fmt.Errorf("pgp: failed to read public key ring: %w", err) + return nil, coreerr.E(op, "failed to read public key ring", err) } buf := new(bytes.Buffer) armoredWriter, err := armor.Encode(buf, "PGP MESSAGE", nil) if err != nil { - return nil, fmt.Errorf("pgp: failed to create armor encoder: %w", err) + return nil, coreerr.E(op, "failed to create armor encoder", err) } w, err := openpgp.Encrypt(armoredWriter, keyring, nil, nil, nil) if err != nil { armoredWriter.Close() - return nil, fmt.Errorf("pgp: failed to create encryption writer: %w", err) + return nil, coreerr.E(op, "failed to create encryption writer", err) } if _, err := w.Write(data); err != nil { w.Close() armoredWriter.Close() - return nil, fmt.Errorf("pgp: failed to write data: %w", err) + return nil, coreerr.E(op, "failed to write data", err) } w.Close() armoredWriter.Close() @@ -146,16 +150,18 @@ func Encrypt(data []byte, publicKeyArmor string) ([]byte, error) { // Decrypt decrypts armored PGP data using the given armored private key. // If the private key is encrypted, the password is used to decrypt it first. func Decrypt(data []byte, privateKeyArmor, password string) ([]byte, error) { + const op = "pgp.Decrypt" + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKeyArmor))) if err != nil { - return nil, fmt.Errorf("pgp: failed to read private key ring: %w", err) + return nil, coreerr.E(op, "failed to read private key ring", err) } // Decrypt the private key if it is encrypted for _, entity := range keyring { if entity.PrivateKey != nil && entity.PrivateKey.Encrypted { if err := entity.PrivateKey.Decrypt([]byte(password)); err != nil { - return nil, fmt.Errorf("pgp: failed to decrypt private key: %w", err) + return nil, coreerr.E(op, "failed to decrypt private key", err) } } for _, subkey := range entity.Subkeys { @@ -168,17 +174,17 @@ func Decrypt(data []byte, privateKeyArmor, password string) ([]byte, error) { // Decode armored message block, err := armor.Decode(bytes.NewReader(data)) if err != nil { - return nil, fmt.Errorf("pgp: failed to decode armored message: %w", err) + return nil, coreerr.E(op, "failed to decode armored message", err) } md, err := openpgp.ReadMessage(block.Body, keyring, nil, nil) if err != nil { - return nil, fmt.Errorf("pgp: failed to read message: %w", err) + return nil, coreerr.E(op, "failed to read message", err) } plaintext, err := io.ReadAll(md.UnverifiedBody) if err != nil { - return nil, fmt.Errorf("pgp: failed to read plaintext: %w", err) + return nil, coreerr.E(op, "failed to read plaintext", err) } return plaintext, nil @@ -188,19 +194,21 @@ func Decrypt(data []byte, privateKeyArmor, password string) ([]byte, error) { // the armored private key. If the key is encrypted, the password is used // to decrypt it first. func Sign(data []byte, privateKeyArmor, password string) ([]byte, error) { + const op = "pgp.Sign" + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKeyArmor))) if err != nil { - return nil, fmt.Errorf("pgp: failed to read private key ring: %w", err) + return nil, coreerr.E(op, "failed to read private key ring", err) } signer := keyring[0] if signer.PrivateKey == nil { - return nil, errors.New("pgp: private key not found in keyring") + return nil, coreerr.E(op, "private key not found in keyring", nil) } if signer.PrivateKey.Encrypted { if err := signer.PrivateKey.Decrypt([]byte(password)); err != nil { - return nil, fmt.Errorf("pgp: failed to decrypt private key: %w", err) + return nil, coreerr.E(op, "failed to decrypt private key", err) } } @@ -208,7 +216,7 @@ func Sign(data []byte, privateKeyArmor, password string) ([]byte, error) { config := &packet.Config{} err = openpgp.ArmoredDetachSign(buf, signer, bytes.NewReader(data), config) if err != nil { - return nil, fmt.Errorf("pgp: failed to sign message: %w", err) + return nil, coreerr.E(op, "failed to sign message", err) } return buf.Bytes(), nil @@ -217,14 +225,16 @@ func Sign(data []byte, privateKeyArmor, password string) ([]byte, error) { // Verify verifies an armored detached signature against the given data // and armored public key. Returns nil if the signature is valid. func Verify(data, signature []byte, publicKeyArmor string) error { + const op = "pgp.Verify" + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(publicKeyArmor))) if err != nil { - return fmt.Errorf("pgp: failed to read public key ring: %w", err) + return coreerr.E(op, "failed to read public key ring", err) } _, err = openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewReader(data), bytes.NewReader(signature), nil) if err != nil { - return fmt.Errorf("pgp: signature verification failed: %w", err) + return coreerr.E(op, "signature verification failed", err) } return nil diff --git a/crypt/rsa/rsa.go b/crypt/rsa/rsa.go index 9f1cd0c..a20ea0d 100644 --- a/crypt/rsa/rsa.go +++ b/crypt/rsa/rsa.go @@ -6,8 +6,9 @@ import ( "crypto/sha256" "crypto/x509" "encoding/pem" - "errors" "fmt" + + coreerr "forge.lthn.ai/core/go-log" ) // Service provides RSA functionality. @@ -20,12 +21,14 @@ func NewService() *Service { // GenerateKeyPair creates a new RSA key pair. func (s *Service) GenerateKeyPair(bits int) (publicKey, privateKey []byte, err error) { + const op = "rsa.GenerateKeyPair" + if bits < 2048 { - return nil, nil, fmt.Errorf("rsa: key size too small: %d (minimum 2048)", bits) + return nil, nil, coreerr.E(op, fmt.Sprintf("key size too small: %d (minimum 2048)", bits), nil) } privKey, err := rsa.GenerateKey(rand.Reader, bits) if err != nil { - return nil, nil, fmt.Errorf("failed to generate private key: %w", err) + return nil, nil, coreerr.E(op, "failed to generate private key", err) } privKeyBytes := x509.MarshalPKCS1PrivateKey(privKey) @@ -36,7 +39,7 @@ func (s *Service) GenerateKeyPair(bits int) (publicKey, privateKey []byte, err e pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey) if err != nil { - return nil, nil, fmt.Errorf("failed to marshal public key: %w", err) + return nil, nil, coreerr.E(op, "failed to marshal public key", err) } pubKeyPEM := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", @@ -48,24 +51,26 @@ func (s *Service) GenerateKeyPair(bits int) (publicKey, privateKey []byte, err e // Encrypt encrypts data with a public key. func (s *Service) Encrypt(publicKey, data, label []byte) ([]byte, error) { + const op = "rsa.Encrypt" + block, _ := pem.Decode(publicKey) if block == nil { - return nil, errors.New("failed to decode public key") + return nil, coreerr.E(op, "failed to decode public key", nil) } pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("failed to parse public key: %w", err) + return nil, coreerr.E(op, "failed to parse public key", err) } rsaPub, ok := pub.(*rsa.PublicKey) if !ok { - return nil, errors.New("not an RSA public key") + return nil, coreerr.E(op, "not an RSA public key", nil) } ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPub, data, label) if err != nil { - return nil, fmt.Errorf("failed to encrypt data: %w", err) + return nil, coreerr.E(op, "failed to encrypt data", err) } return ciphertext, nil @@ -73,19 +78,21 @@ func (s *Service) Encrypt(publicKey, data, label []byte) ([]byte, error) { // Decrypt decrypts data with a private key. func (s *Service) Decrypt(privateKey, ciphertext, label []byte) ([]byte, error) { + const op = "rsa.Decrypt" + block, _ := pem.Decode(privateKey) if block == nil { - return nil, errors.New("failed to decode private key") + return nil, coreerr.E(op, "failed to decode private key", nil) } priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { - return nil, fmt.Errorf("failed to parse private key: %w", err) + return nil, coreerr.E(op, "failed to parse private key", err) } plaintext, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, ciphertext, label) if err != nil { - return nil, fmt.Errorf("failed to decrypt data: %w", err) + return nil, coreerr.E(op, "failed to decrypt data", err) } return plaintext, nil diff --git a/trust/trust.go b/trust/trust.go index 69e4a24..9794a6e 100644 --- a/trust/trust.go +++ b/trust/trust.go @@ -11,11 +11,12 @@ package trust import ( - "errors" "fmt" "iter" "sync" "time" + + coreerr "forge.lthn.ai/core/go-log" ) // Tier represents an agent's trust level in the system. @@ -98,10 +99,10 @@ func NewRegistry() *Registry { // Returns an error if the agent name is empty or the tier is invalid. func (r *Registry) Register(agent Agent) error { if agent.Name == "" { - return errors.New("trust.Register: agent name is required") + return coreerr.E("trust.Register", "agent name is required", nil) } if !agent.Tier.Valid() { - return fmt.Errorf("trust.Register: invalid tier %d for agent %q", agent.Tier, agent.Name) + return coreerr.E("trust.Register", fmt.Sprintf("invalid tier %d for agent %q", agent.Tier, agent.Name), nil) } if agent.CreatedAt.IsZero() { agent.CreatedAt = time.Now()