diff --git a/auth/auth.go b/auth/auth.go index dbacbc9..0b90ec6 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "errors" "math/big" - "maxwarden/config" + "maxwarden/constants" "maxwarden/entries" "maxwarden/security" "maxwarden/users" @@ -28,7 +28,7 @@ type Identity struct { } func NewIdentity(userid int32, securityStamp string, masterKey string, rememberMe bool) *Identity { - expirationDuration := time.Duration(time.Hour * 24 * time.Duration(config.IDENTITY_COOKIE_EXPIRY_DAYS)) + expirationDuration := time.Duration(time.Hour * 24 * time.Duration(constants.IDENTITY_COOKIE_EXPIRY_DAYS)) expiration := time.Now().Add(expirationDuration) // hash password input @@ -63,7 +63,7 @@ func Authenticate(username string, password string) (int32, string, bool) { user, userErr := users.FetchByUsername(username) - if userErr != nil || user.FailedAttempts > int32(config.MAX_LOGIN_ATTEMPTS) { + if userErr != nil || user.FailedAttempts > int32(constants.MAX_LOGIN_ATTEMPTS) { // set user password to dummy password to keep timing consistent when validating password user.Password = "$2a$14$KW5OO1wZqGGq3SrpBFj0Oema5DG8Ph7lZJvq0ECkkYBpNFom6b9vO" security.ComparePasswords(password, user.Password) @@ -95,7 +95,7 @@ func Authenticate(username string, password string) (int32, string, bool) { // } mk := security.SHA512_58(password) - user.Data, _ = security.EncryptDataWithKey(&secrets, mk) + user.Data, _ = security.EncryptData(&secrets, mk) } users.Update(user) @@ -109,8 +109,8 @@ func CheckPasswordCriteria(password string) error { return errors.New("Password cannot be blank.") } - if len(password) < config.PASSWORD_MIN_LENGTH { - return errors.New("Password must be at least " + strconv.Itoa(config.PASSWORD_MIN_LENGTH) + " characters long.") + if len(password) < constants.PASSWORD_MIN_LENGTH { + return errors.New("Password must be at least " + strconv.Itoa(constants.PASSWORD_MIN_LENGTH) + " characters long.") } uppercaseCount := 0 @@ -136,20 +136,20 @@ func CheckPasswordCriteria(password string) error { } } - if uppercaseCount < config.PASSWORD_REQUIRED_UPPERCASE { - return errors.New("Password must contain at least " + strconv.Itoa(config.PASSWORD_REQUIRED_UPPERCASE) + " uppercase character(s).") + if uppercaseCount < constants.PASSWORD_REQUIRED_UPPERCASE { + return errors.New("Password must contain at least " + strconv.Itoa(constants.PASSWORD_REQUIRED_UPPERCASE) + " uppercase character(s).") } - if lowercaseCount < config.PASSWORD_REQUIRED_LOWERCASE { - return errors.New("Password must contain at least " + strconv.Itoa(config.PASSWORD_REQUIRED_LOWERCASE) + " lowercase character(s).") + if lowercaseCount < constants.PASSWORD_REQUIRED_LOWERCASE { + return errors.New("Password must contain at least " + strconv.Itoa(constants.PASSWORD_REQUIRED_LOWERCASE) + " lowercase character(s).") } - if numberCount < config.PASSWORD_REQUIRED_NUMBERS { - return errors.New("Password must contain at least " + strconv.Itoa(config.PASSWORD_REQUIRED_NUMBERS) + " number(s).") + if numberCount < constants.PASSWORD_REQUIRED_NUMBERS { + return errors.New("Password must contain at least " + strconv.Itoa(constants.PASSWORD_REQUIRED_NUMBERS) + " number(s).") } - if symbolCount < config.PASSWORD_REQUIRED_SYMBOLS { - return errors.New("Password must contain at least " + strconv.Itoa(config.PASSWORD_REQUIRED_SYMBOLS) + " symbol(s).") + if symbolCount < constants.PASSWORD_REQUIRED_SYMBOLS { + return errors.New("Password must contain at least " + strconv.Itoa(constants.PASSWORD_REQUIRED_SYMBOLS) + " symbol(s).") } return nil diff --git a/cmd/passgen/main.go b/cmd/passgen/main.go index c94daeb..93f1e66 100644 --- a/cmd/passgen/main.go +++ b/cmd/passgen/main.go @@ -20,7 +20,7 @@ func main() { } masterKey := security.SHA512_58(os.Args[1]) - cryptData, _ := security.EncryptDataWithKey(&testData, masterKey) + cryptData, _ := security.EncryptData(&testData, masterKey) println(passHash) println(cryptData) diff --git a/config/config.go b/config/config.go index d499149..e7c6fbf 100644 --- a/config/config.go +++ b/config/config.go @@ -10,34 +10,6 @@ import ( "github.com/joho/godotenv" ) -const ( - SESSION_COOKIE_NAME = "_maxwarden_session" - SESSION_COOKIE_EXPIRY_DAYS int = 100 - SESSION_COOKIE_ENTROPY int = 33 - - IDENTITY_COOKIE_NAME string = "_maxwarden_identity" - IDENTITY_COOKIE_EXPIRY_DAYS int = 30 - IDENTITY_TOKEN_EXPIRY_DAYS int = 30 - IDENTITY_COOKIE_ENTROPY int = 33 - IDENTITY_LOGIN_PATH string = "/auth/login" - IDENTITY_LOGOUT_PATH string = "/auth/logout" - IDENTITY_DEFAULT_PATH string = "/app" - IDENTITY_AUTH_REDIRECT bool = true - IDENTITY_AUTH_KEY string = "CORRECT_HORSE_BATTERY_STAPLE" - - // This key is NOT used for the hashing of passwords, or secure session data over the wire. - // It is ONLY used for performing quick file and string hashes, where security is not a factor. - DATA_HASH_KEY string = "01234567890123456789012345678901" - - PASSWORD_MIN_LENGTH int = 8 - PASSWORD_REQUIRED_UPPERCASE int = 1 - PASSWORD_REQUIRED_LOWERCASE int = 1 - PASSWORD_REQUIRED_NUMBERS int = 1 - PASSWORD_REQUIRED_SYMBOLS int = 0 - - MAX_LOGIN_ATTEMPTS int = 5 -) - type configuration struct { Domain string `env:"DOMAIN"` Host string `env:"HOST"` diff --git a/constants/constants.go b/constants/constants.go new file mode 100644 index 0000000..2e24a18 --- /dev/null +++ b/constants/constants.go @@ -0,0 +1,29 @@ +package constants + +const ( + SESSION_COOKIE_NAME = "_maxwarden_session" + SESSION_COOKIE_EXPIRY_DAYS int = 100 + SESSION_COOKIE_ENTROPY int = 33 + + IDENTITY_COOKIE_NAME string = "_maxwarden_identity" + IDENTITY_COOKIE_EXPIRY_DAYS int = 30 + IDENTITY_TOKEN_EXPIRY_DAYS int = 30 + IDENTITY_COOKIE_ENTROPY int = 33 + IDENTITY_LOGIN_PATH string = "/auth/login" + IDENTITY_LOGOUT_PATH string = "/auth/logout" + IDENTITY_DEFAULT_PATH string = "/app" + IDENTITY_AUTH_REDIRECT bool = true + IDENTITY_AUTH_KEY string = "CORRECT_HORSE_BATTERY_STAPLE" + + // This key is NOT used for the hashing of passwords, or secure session data over the wire. + // It is ONLY used for performing quick file and string hashes, where security is not a factor. + DATA_HASH_KEY string = "01234567890123456789012345678901" + + PASSWORD_MIN_LENGTH int = 8 + PASSWORD_REQUIRED_UPPERCASE int = 1 + PASSWORD_REQUIRED_LOWERCASE int = 1 + PASSWORD_REQUIRED_NUMBERS int = 1 + PASSWORD_REQUIRED_SYMBOLS int = 0 + + MAX_LOGIN_ATTEMPTS int = 5 +) \ No newline at end of file diff --git a/entries/entries.go b/entries/entries.go index cc85dd5..ec58e9a 100644 --- a/entries/entries.go +++ b/entries/entries.go @@ -44,7 +44,7 @@ func Filter(f EntryFilter) ([]Secret, error) { user, _ := users.FetchById(f.UserId) // we need to do the rest in memory because the data is encrypted, so we need to decrypt the data - secrets, decErr := security.DecryptDataWithKey[[]Secret](user.Data, f.MasterKey) + secrets, decErr := security.DecryptData[[]Secret](user.Data, f.MasterKey) if decErr != nil { return nil, decErr } @@ -83,7 +83,7 @@ func Filter(f EntryFilter) ([]Secret, error) { func FetchSecretFromID(userId int32, masterKey string, secretId string) (Secret, error) { user, _ := users.FetchById(userId) - secrets, decErr := security.DecryptDataWithKey[[]Secret](user.Data, masterKey) + secrets, decErr := security.DecryptData[[]Secret](user.Data, masterKey) if decErr != nil { return Secret{}, decErr } @@ -104,7 +104,7 @@ func FetchSecretFromID(userId int32, masterKey string, secretId string) (Secret, func DeleteSecret(userId int32, masterKey string, secretId string) error { user, _ := users.FetchById(userId) - secrets, decErr := security.DecryptDataWithKey[[]Secret](user.Data, masterKey) + secrets, decErr := security.DecryptData[[]Secret](user.Data, masterKey) if decErr != nil { return decErr } @@ -121,7 +121,7 @@ func DeleteSecret(userId int32, masterKey string, secretId string) error { } } - enc, _ := security.EncryptDataWithKey(&output, masterKey) + enc, _ := security.EncryptData(&output, masterKey) user.Data = enc _, userErr := users.Update(user) @@ -132,7 +132,7 @@ func DeleteSecret(userId int32, masterKey string, secretId string) error { func Update(userId int32, masterKey string, secret Secret) error { user, _ := users.FetchById(userId) - secrets, _ := security.DecryptDataWithKey[[]Secret](user.Data, masterKey) + secrets, _ := security.DecryptData[[]Secret](user.Data, masterKey) if secrets == nil { return errors.New("user secrets are null") } @@ -149,7 +149,7 @@ func Update(userId int32, masterKey string, secret Secret) error { } } - enc, _ := security.EncryptDataWithKey(secrets, masterKey) + enc, _ := security.EncryptData(secrets, masterKey) user.Data = enc _, updateErr := users.Update(user) @@ -160,7 +160,7 @@ func Update(userId int32, masterKey string, secret Secret) error { func Add(userId int32, masterKey string, secret Secret) error { user, _ := users.FetchById(userId) - secrets, _ := security.DecryptDataWithKey[[]Secret](user.Data, masterKey) + secrets, _ := security.DecryptData[[]Secret](user.Data, masterKey) if secrets == nil { return errors.New("user secrets are null") } @@ -171,7 +171,7 @@ func Add(userId int32, masterKey string, secret Secret) error { *secrets = append(*secrets, secret) - enc, _ := security.EncryptDataWithKey(secrets, masterKey) + enc, _ := security.EncryptData(secrets, masterKey) user.Data = enc _, updateErr := users.Update(user) diff --git a/handlers/auth/login.go b/handlers/auth/login.go index d945696..c642983 100644 --- a/handlers/auth/login.go +++ b/handlers/auth/login.go @@ -7,7 +7,7 @@ import ( . "maragu.dev/gomponents/html" "maxwarden/auth" - "maxwarden/config" + "maxwarden/constants" "maxwarden/middleware" "log" @@ -46,7 +46,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { return } - defaultPath := config.IDENTITY_DEFAULT_PATH + defaultPath := constants.IDENTITY_DEFAULT_PATH http.Redirect(w, r, defaultPath, http.StatusFound) } } diff --git a/handlers/auth/logout.go b/handlers/auth/logout.go index db9b019..9bbb420 100644 --- a/handlers/auth/logout.go +++ b/handlers/auth/logout.go @@ -2,7 +2,7 @@ package auth import ( "net/http" - "maxwarden/config" + "maxwarden/constants" "maxwarden/middleware" ) @@ -10,5 +10,5 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) { middleware.DeleteIdentityCookie(w, r) middleware.DeleteSessionCookie(w, r) - http.Redirect(w, r, config.IDENTITY_LOGIN_PATH, http.StatusFound) + http.Redirect(w, r, constants.IDENTITY_LOGIN_PATH, http.StatusFound) } diff --git a/middleware/identity.go b/middleware/identity.go index 09c5bda..97c25b7 100644 --- a/middleware/identity.go +++ b/middleware/identity.go @@ -3,12 +3,13 @@ package middleware import ( "context" "log" - "net/http" - "net/url" "maxwarden/auth" "maxwarden/config" + "maxwarden/constants" "maxwarden/security" "maxwarden/users" + "net/http" + "net/url" "strings" "time" ) @@ -16,10 +17,10 @@ import ( type identityKey struct{} func LoadIdentity(h http.HandlerFunc, requireAuth bool) http.HandlerFunc { - loginPath := config.IDENTITY_LOGIN_PATH - logoutPath := config.IDENTITY_LOGOUT_PATH - defaultPath := config.IDENTITY_DEFAULT_PATH - redirect := config.IDENTITY_AUTH_REDIRECT + loginPath := constants.IDENTITY_LOGIN_PATH + logoutPath := constants.IDENTITY_LOGOUT_PATH + defaultPath := constants.IDENTITY_DEFAULT_PATH + redirect := constants.IDENTITY_AUTH_REDIRECT return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var identity *auth.Identity @@ -35,7 +36,10 @@ func LoadIdentity(h http.HandlerFunc, requireAuth bool) http.HandlerFunc { if len(splitToken) >= 2 { token = splitToken[1] - identity, _ = security.DecryptData[auth.Identity]([]byte(security.DecodeBase58(token))) + identity, _ = security.DecryptData[auth.Identity]( + []byte(security.DecodeBase58(token)), + config.GetConfig().IdentityPrivateKey, + ) } if identity == nil { @@ -51,9 +55,12 @@ func LoadIdentity(h http.HandlerFunc, requireAuth bool) http.HandlerFunc { return } } else { - identityCookie, err := r.Cookie(config.IDENTITY_COOKIE_NAME) + identityCookie, err := r.Cookie(constants.IDENTITY_COOKIE_NAME) if err == nil { - identity, _ = security.DecryptData[auth.Identity]([]byte(security.DecodeBase58(identityCookie.Value))) + identity, _ = security.DecryptData[auth.Identity]( + []byte(security.DecodeBase58(identityCookie.Value)), + config.GetConfig().IdentityPrivateKey, + ) } if identity == nil { @@ -117,7 +124,7 @@ func PutIdentityCookie(w http.ResponseWriter, r *http.Request, identity *auth.Id // calculate total bytes used by other cookies var totalBytes int for _, cookie := range cookies { - if cookie.Name == config.IDENTITY_COOKIE_NAME { + if cookie.Name == constants.IDENTITY_COOKIE_NAME { continue } else { totalBytes += len(cookie.Value) @@ -139,7 +146,7 @@ func PutIdentityCookie(w http.ResponseWriter, r *http.Request, identity *auth.Id // The key should not be checked into VCS, and be regenerated if theft is // suspected. Resetting the key will log *everyone* out, since no sessions // or identities will validate. - identityData, err := security.EncryptData(identity) + identityData, err := security.EncryptData(identity, config.GetConfig().IdentityPrivateKey) if err != nil { return } @@ -152,7 +159,7 @@ func PutIdentityCookie(w http.ResponseWriter, r *http.Request, identity *auth.Id } httpCookie := &http.Cookie{ - Name: config.IDENTITY_COOKIE_NAME, + Name: constants.IDENTITY_COOKIE_NAME, Value: security.EncodeBase58(identityData), HttpOnly: true, Secure: r.URL.Scheme == "https", @@ -167,7 +174,7 @@ func PutIdentityCookie(w http.ResponseWriter, r *http.Request, identity *auth.Id func DeleteIdentityCookie(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &http.Cookie{ - Name: config.IDENTITY_COOKIE_NAME, + Name: constants.IDENTITY_COOKIE_NAME, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour), Path: "/", diff --git a/middleware/session.go b/middleware/session.go index 02f6f33..f3dfb96 100644 --- a/middleware/session.go +++ b/middleware/session.go @@ -3,9 +3,10 @@ package middleware import ( "context" "log" - "net/http" "maxwarden/config" + "maxwarden/constants" "maxwarden/security" + "net/http" "time" ) @@ -15,9 +16,13 @@ func LoadSession(h http.HandlerFunc) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var sessionMap map[string]interface{} - sessionCookie, err := r.Cookie(config.SESSION_COOKIE_NAME) + sessionCookie, err := r.Cookie(constants.SESSION_COOKIE_NAME) if err == nil { - decryptMap, _ := security.DecryptData[map[string]interface{}]([]byte(security.DecodeBase58(sessionCookie.Value))) + decryptMap, _ := security.DecryptData[map[string]interface{}]( + []byte(security.DecodeBase58(sessionCookie.Value)), + config.GetConfig().IdentityPrivateKey, + ) + sessionMap = *decryptMap } @@ -41,14 +46,14 @@ func PutSessionCookie(w http.ResponseWriter, r *http.Request, session map[string // calculate total bytes used by other cookies var totalBytes int for _, cookie := range cookies { - if cookie.Name == config.SESSION_COOKIE_NAME { + if cookie.Name == constants.SESSION_COOKIE_NAME { continue } else { totalBytes += len(cookie.Value) } } - sessionData, err := security.EncryptData(&session) + sessionData, err := security.EncryptData(&session, config.GetConfig().IdentityPrivateKey) if err != nil { return } @@ -61,7 +66,7 @@ func PutSessionCookie(w http.ResponseWriter, r *http.Request, session map[string } httpCookie := &http.Cookie{ - Name: config.SESSION_COOKIE_NAME, + Name: constants.SESSION_COOKIE_NAME, Value: security.EncodeBase58(sessionData), HttpOnly: true, Secure: r.URL.Scheme == "https", @@ -74,7 +79,7 @@ func PutSessionCookie(w http.ResponseWriter, r *http.Request, session map[string func DeleteSessionCookie(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &http.Cookie{ - Name: config.SESSION_COOKIE_NAME, + Name: constants.SESSION_COOKIE_NAME, MaxAge: -1, Expires: time.Now().Add(-100 * time.Hour), Path: "/", diff --git a/security/crypt.go b/security/crypt.go index 1097c01..ab96bc6 100644 --- a/security/crypt.go +++ b/security/crypt.go @@ -11,12 +11,12 @@ import ( "fmt" "io" "log" - "maxwarden/config" "os" + "maxwarden/constants" "github.com/btcsuite/btcutil/base58" - "github.com/minio/highwayhash" + "golang.org/x/crypto/bcrypt" ) @@ -59,7 +59,7 @@ func SHA512_58(in string) string { } func HighwayHash58(in string) (string, error) { - key := []byte(config.DATA_HASH_KEY) + key := []byte(constants.DATA_HASH_KEY) hasher, err := highwayhash.New(key) if err != nil { @@ -77,7 +77,7 @@ func HighwayHash58(in string) (string, error) { } func HighwayHash(in string) (string, error) { - key := []byte(config.DATA_HASH_KEY) + key := []byte(constants.DATA_HASH_KEY) hasher, err := highwayhash.New(key) if err != nil { @@ -93,7 +93,7 @@ func HighwayHash(in string) (string, error) { } func QuickFileHash(filepath string) (string, error) { - key := []byte(config.DATA_HASH_KEY) + key := []byte(constants.DATA_HASH_KEY) file, err := os.Open(filepath) if err != nil { @@ -196,17 +196,7 @@ func DecryptSecret(encryptedData []byte, passKey string) ([]byte, error) { return decryptedData, nil } -// Encrypt data using default private key -func EncryptData[T any](data *T) ([]byte, error) { - return EncryptDataWithKey(data, config.GetConfig().IdentityPrivateKey) -} - -// Decrypt data using default private key -func DecryptData[T any](data []byte) (*T, error) { - return DecryptDataWithKey[T](data, config.GetConfig().IdentityPrivateKey) -} - -func EncryptDataWithKey[T any](data *T, key string) ([]byte, error) { +func EncryptData[T any](data *T, key string) ([]byte, error) { // serialize b := bytes.Buffer{} e := gob.NewEncoder(&b) @@ -224,7 +214,7 @@ func EncryptDataWithKey[T any](data *T, key string) ([]byte, error) { return out, nil } -func DecryptDataWithKey[T any](data []byte, key string) (*T, error) { +func DecryptData[T any](data []byte, key string) (*T, error) { dest := new(T) secret, err := DecryptSecret(data, key)