diff --git a/auth/envauth/README.md b/auth/envauth/README.md index d9b0522..bb4de10 100644 --- a/auth/envauth/README.md +++ b/auth/envauth/README.md @@ -12,7 +12,7 @@ creds := envauth.BasicCredentials{ Password: os.Getenv("BASIC_AUTH_PASSWORD"), } -verified := creds.Verify("username", "password") +err := creds.Verify("username", "password") ``` ## Basic Credentials: Username + Password @@ -44,12 +44,17 @@ func main() { Password: password, } - verified := creds.Verify("api", "secret") - if verified { - println("Authentication successful") - } else { - println("Authentication failed") + if err := creds.Verify("api", "secret"); err != nil { + switch err { + case envauth.ErrUnauthorized: + println("Authentication failed") + default: + panic(err) + } + os.Exit(1) } + + println("Authentication successful") } ``` @@ -104,11 +109,16 @@ func main() { Iterations: iterations, } - verified := creds.Verify("api", "secret") - if verified { - println("Authentication successful") - } else { - println("Authentication failed") + if err := creds.Verify("api", "secret"); err != nil { + switch err { + case envauth.ErrUnauthorized: + println("Authentication failed") + default: + panic(err) + } + os.Exit(1) } + + println("Authentication successful") } ``` diff --git a/auth/envauth/envauth.go b/auth/envauth/envauth.go index 9cd1f1c..3be4d5e 100644 --- a/auth/envauth/envauth.go +++ b/auth/envauth/envauth.go @@ -4,10 +4,13 @@ import ( "crypto/pbkdf2" "crypto/sha256" "crypto/subtle" + "errors" ) +var ErrUnauthorized = errors.New("unauthorized") + type BasicAuthVerifier interface { - Verify(string, string) bool + Verify(string, string) error } // BasicCredentials holds user credentials @@ -18,9 +21,9 @@ type BasicCredentials struct { // Returns true if username and password match. // Uses SHA-256 and constant-time techniques to avoid revealing whether the username or password matches through timing attacks. -func (c BasicCredentials) Verify(username string, password string) bool { +func (c BasicCredentials) Verify(username string, password string) error { if len(password) == 0 { - return false + return ErrUnauthorized } equal := 1 @@ -38,7 +41,10 @@ func (c BasicCredentials) Verify(username string, password string) bool { v = subtle.ConstantTimeCompare(knownPasswordHash[:], passwordHash[:]) // 1 if same equal = subtle.ConstantTimeSelect(v, equal, 0) // v ? x : y - return equal == 1 + if equal == 1 { + return nil + } + return ErrUnauthorized } // PBKDF2Credentials holds user credentials @@ -51,24 +57,24 @@ type PBKDF2Credentials struct { // Returns true if username and password match. // Uses PBKDF2 and constant-time techniques to avoid revealing whether the username or password matches through timing attacks. -func (c PBKDF2Credentials) Verify(username string, password string) bool { +func (c PBKDF2Credentials) Verify(username string, password string) error { keyLen := len(c.DerivedKey) dkKnownUser, err := pbkdf2.Key(sha256.New, c.Username, c.Salt, c.Iterations, keyLen) if err != nil { - return false + return err } if len(password) == 0 { - return false + return ErrUnauthorized } dkUser, err := pbkdf2.Key(sha256.New, username, c.Salt, c.Iterations, keyLen) if err != nil { - return false + return err } dkPass, err := pbkdf2.Key(sha256.New, password, c.Salt, c.Iterations, keyLen) if err != nil { - return false + return err } equal := 1 @@ -79,7 +85,11 @@ func (c PBKDF2Credentials) Verify(username string, password string) bool { v = subtle.ConstantTimeCompare(dkPass, c.DerivedKey) // 1 if same equal = subtle.ConstantTimeSelect(v, equal, 0) // v ? x : y - return equal == 1 + if equal == 1 { + return nil + } + return ErrUnauthorized + } func (c PBKDF2Credentials) DeriveKey(username string, password string, keyLen int) ([]byte, error) { diff --git a/auth/envauth/envauth_test.go b/auth/envauth/envauth_test.go index 138d611..1c2989e 100644 --- a/auth/envauth/envauth_test.go +++ b/auth/envauth/envauth_test.go @@ -14,50 +14,50 @@ func TestBasicCredentials_Verify(t *testing.T) { creds BasicCredentials username string password string - want bool + want error }{ { name: "empty username, correct password", creds: BasicCredentials{Username: "", Password: "secret"}, username: "", password: "secret", - want: true, + want: nil, }, { name: "correct username, correct password", creds: BasicCredentials{Username: "user", Password: "secret"}, username: "user", password: "secret", - want: true, + want: nil, }, { name: "incorrect username, correct password", creds: BasicCredentials{Username: "user", Password: "secret"}, username: "wrong", password: "secret", - want: false, + want: ErrUnauthorized, }, { name: "correct username, incorrect password", creds: BasicCredentials{Username: "user", Password: "secret"}, username: "user", password: "wrong", - want: false, + want: ErrUnauthorized, }, { name: "correct username, empty password", creds: BasicCredentials{Username: "user", Password: "secret"}, username: "user", password: "", - want: false, + want: ErrUnauthorized, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.creds.Verify(tt.username, tt.password) - if got != tt.want { - t.Errorf("Verify(%q, %q) = %v; want %v", tt.username, tt.password, got, tt.want) + err := tt.creds.Verify(tt.username, tt.password) + if err != tt.want { + t.Errorf("Verify(%q, %q) = %v; want %v", tt.username, tt.password, err, tt.want) } }) } @@ -78,57 +78,57 @@ func TestPBKDF2Credentials_Verify(t *testing.T) { creds PBKDF2Credentials username string password string - want bool + want error }{ { name: "empty username, correct password", creds: PBKDF2Credentials{Username: "", DerivedKey: secretDigest, Salt: salt, Iterations: 1000}, username: "", password: "secret", - want: true, + want: nil, }, { name: "correct username, correct password", creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000}, username: "user", password: "secret", - want: true, + want: nil, }, { name: "incorrect username, correct password", creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000}, username: "wrong", password: "secret", - want: false, + want: ErrUnauthorized, }, { name: "correct username, incorrect password", creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000}, username: "user", password: "wrong", - want: false, + want: ErrUnauthorized, }, { name: "correct username, empty password", creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000}, username: "user", password: "", - want: false, + want: ErrUnauthorized, }, { name: "empty username, empty pre-computed digest", creds: PBKDF2Credentials{Username: "", DerivedKey: emptyDigest, Salt: salt, Iterations: 1000}, username: "", password: "", - want: false, + want: ErrUnauthorized, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.creds.Verify(tt.username, tt.password) - if got != tt.want { - t.Errorf("Verify(%q, %q) = %v; want %v", tt.username, tt.password, got, tt.want) + err := tt.creds.Verify(tt.username, tt.password) + if err != tt.want { + t.Errorf("Verify(%q, %q) = %v; want %v", tt.username, tt.password, err, tt.want) } }) }