Move password encryption to argon2id
This commit is contained in:
parent
c1611114d0
commit
c269db5b02
5 changed files with 201 additions and 3 deletions
|
@ -1,12 +1,14 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.mstar.dev/mstar/goutils/other"
|
"git.mstar.dev/mstar/goutils/other"
|
||||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/argon2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
|
|
||||||
|
@ -14,12 +16,31 @@ import (
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const saltLen = 32
|
||||||
|
|
||||||
|
func generateSalt(length int) ([]byte, error) {
|
||||||
|
salt := make([]byte, length)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return salt, nil
|
||||||
|
}
|
||||||
|
|
||||||
func hashPassword(password string) ([]byte, error) {
|
func hashPassword(password string) ([]byte, error) {
|
||||||
return bcrypt.GenerateFromPassword([]byte(password), 14)
|
salt, err := generateSalt(saltLen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)
|
||||||
|
hash = append(hash, salt...)
|
||||||
|
// return bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||||
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func comparePassword(password string, hash []byte) bool {
|
func comparePassword(password string, hash []byte) bool {
|
||||||
return bcrypt.CompareHashAndPassword(hash, []byte(password)) == nil
|
salt := hash[len(hash)-saltLen:]
|
||||||
|
|
||||||
|
return bytes.Equal(argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32), hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a login process with a username (NOT account ID) and password
|
// Start a login process with a username (NOT account ID) and password
|
||||||
|
|
106
auth-new/totp.go
Normal file
106
auth-new/totp.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.mstar.dev/mstar/goutils/other"
|
||||||
|
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||||
|
"github.com/pquerna/otp"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
|
||||||
|
"git.mstar.dev/mstar/linstrom/config"
|
||||||
|
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||||
|
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
const totpUnverifiedSuffix = "-NOT_VERIFIED"
|
||||||
|
|
||||||
|
func (a *Authenticator) PerformTotpLogin(
|
||||||
|
username string,
|
||||||
|
totpToken string,
|
||||||
|
) (LoginNextState, string, error) {
|
||||||
|
if ok, err := a.canUsernameLogin(username); !ok {
|
||||||
|
return 0, "", other.Error("auth", "user may not login", err)
|
||||||
|
}
|
||||||
|
acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First()
|
||||||
|
if err != nil {
|
||||||
|
return LoginNextFailure, "", other.Error("auth", "failed to find account", err)
|
||||||
|
}
|
||||||
|
secrets := sliceutils.Map(
|
||||||
|
sliceutils.Filter(acc.AuthMethods, func(t models.UserAuthMethod) bool {
|
||||||
|
return t.AuthMethod == models.AuthMethodGAuth
|
||||||
|
}),
|
||||||
|
func(t models.UserAuthMethod) string {
|
||||||
|
return string(t.Token)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
found := false
|
||||||
|
for _, secret := range secrets {
|
||||||
|
if totp.Validate(totpToken, secret) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return LoginNextFailure, "", other.Error(
|
||||||
|
"auth",
|
||||||
|
"no fitting credential found",
|
||||||
|
ErrInvalidCombination,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
token := models.AccessToken{
|
||||||
|
User: *acc,
|
||||||
|
UserId: acc.ID,
|
||||||
|
ExpiresAt: time.Now().Add(time.Hour * 24 * 365),
|
||||||
|
}
|
||||||
|
err = dbgen.AccessToken.
|
||||||
|
// technically, the chance of a conflict is so incredibly low that it should never ever occur
|
||||||
|
// since both the username and a randomly generated uuid would need to be created the same. Twice
|
||||||
|
// But, just in case, do this
|
||||||
|
Clauses(clause.OnConflict{DoNothing: true}).
|
||||||
|
Omit(dbgen.AccessToken.Token).
|
||||||
|
Create(&token)
|
||||||
|
if err != nil {
|
||||||
|
return LoginNextFailure, "", other.Error(
|
||||||
|
"auth",
|
||||||
|
"failed to create new access token",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return LoginNextSucess, token.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Authenticator) StartTotpRegistration(
|
||||||
|
username string,
|
||||||
|
tokenName string,
|
||||||
|
) (*otp.Key, error) {
|
||||||
|
if ok, err := a.canUsernameLogin(username); !ok {
|
||||||
|
return nil, other.Error("auth", "user may not login", err)
|
||||||
|
}
|
||||||
|
acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, other.Error("auth", "failed to find account", err)
|
||||||
|
}
|
||||||
|
key, err := totp.Generate(totp.GenerateOpts{
|
||||||
|
Issuer: config.GlobalConfig.General.GetFullDomain(),
|
||||||
|
AccountName: username,
|
||||||
|
SecretSize: 160,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
secret := key.Secret()
|
||||||
|
authToken := models.UserAuthMethod{
|
||||||
|
UserId: acc.ID,
|
||||||
|
User: *acc,
|
||||||
|
Token: []byte(secret),
|
||||||
|
AuthMethod: models.AuthMethodGAuth,
|
||||||
|
Name: tokenName + totpUnverifiedSuffix,
|
||||||
|
}
|
||||||
|
err = dbgen.UserAuthMethod.Create(&authToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
2
go.mod
2
go.mod
|
@ -34,6 +34,7 @@ require (
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 // indirect
|
github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
@ -68,6 +69,7 @@ require (
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||||
|
github.com/pquerna/otp v1.4.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -48,6 +48,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ=
|
github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ=
|
||||||
github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||||
github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8=
|
github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8=
|
||||||
|
@ -272,6 +274,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||||
|
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||||
|
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
|
65
storage-new/helpers.go
Normal file
65
storage-new/helpers.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copied and adjusted from: https://bruinsslot.jp/post/golang-crypto/
|
||||||
|
|
||||||
|
func Encrypt(key, data []byte) ([]byte, error) {
|
||||||
|
// key, salt, err := DeriveKey(key, nil)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
blockCipher, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(blockCipher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
|
if _, err = rand.Read(nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext := gcm.Seal(nonce, nonce, data, nil)
|
||||||
|
|
||||||
|
// ciphertext = append(ciphertext, salt...)
|
||||||
|
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decrypt(key, data []byte) ([]byte, error) {
|
||||||
|
// salt, data := data[len(data)-32:], data[:len(data)-32]
|
||||||
|
|
||||||
|
// key, _, err := DeriveKey(key, salt)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
blockCipher, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(blockCipher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
|
||||||
|
|
||||||
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return plaintext, nil
|
||||||
|
}
|
Loading…
Reference in a new issue