aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Beck <simon.beck@earthnet.ch>2022-02-08 17:59:59 +0100
committerSimon Beck <simon.beck@earthnet.ch>2022-02-10 20:51:01 +0100
commitf05e41c9aad83f3d45aff620a739a116c32b4c47 (patch)
tree13c8de24260478e90419292ffd6c3035d1f95ee6
parentdbd900371466edfdc7bb7f09080c6698e4f8e647 (diff)
downloadbottin-f05e41c9aad83f3d45aff620a739a116c32b4c47.tar.gz
bottin-f05e41c9aad83f3d45aff620a739a116c32b4c47.zip
Improve password hash handling
This adds support for more hash algorithms. Also a stored password will be updated to SSHA512 upon a successful bind. It will also automatically hash a cleartext password if the `userpassword` field is modified with a cleartext one. Hashes supported: * SSHA * SSHA256 * SSHA512
-rw-r--r--go.mod2
-rw-r--r--go.sum10
-rw-r--r--main.go39
-rw-r--r--ssha.go78
-rw-r--r--write.go23
5 files changed, 94 insertions, 58 deletions
diff --git a/go.mod b/go.mod
index a7ce11d..410153e 100644
--- a/go.mod
+++ b/go.mod
@@ -3,9 +3,9 @@ module bottin
go 1.13
require (
- github.com/go-ldap/ldap/v3 v3.3.0
github.com/google/uuid v1.1.1
github.com/hashicorp/consul/api v1.3.0
+ github.com/jsimonetti/pwscheme v0.0.0-20220125093853-4d9895f5db73
github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
)
diff --git a/go.sum b/go.sum
index 8954334..c1ba571 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,3 @@
-github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
-github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -9,11 +7,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
-github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
-github.com/go-ldap/ldap v2.5.1+incompatible h1:Opaoft5zMW8IU/VRULB0eGMBQ9P5buRvCW6sFTRmMn8=
-github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
-github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
@@ -49,6 +42,8 @@ github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG67
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/jsimonetti/pwscheme v0.0.0-20220125093853-4d9895f5db73 h1:ZhC4QngptYaGx53+ph1RjxcH8fkCozBaY+935TNX4i8=
+github.com/jsimonetti/pwscheme v0.0.0-20220125093853-4d9895f5db73/go.mod h1:t0Q9JvoMTfTYdAWIk2MF69iz+Qpdk9D+PgVu6fVmaDI=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -88,6 +83,7 @@ golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/main.go b/main.go
index 4e5abce..2b37803 100644
--- a/main.go
+++ b/main.go
@@ -12,8 +12,8 @@ import (
"os/signal"
"syscall"
- ldap "bottin/ldapserver"
message "bottin/goldap"
+ ldap "bottin/ldapserver"
consul "github.com/hashicorp/consul/api"
log "github.com/sirupsen/logrus"
@@ -320,7 +320,6 @@ func (server *Server) init() error {
return err
}
-
admin_pass_str, environnement_variable_exist := os.LookupEnv("BOTTIN_DEFAULT_ADMIN_PW")
if !environnement_variable_exist {
admin_pass := make([]byte, 8)
@@ -329,11 +328,15 @@ func (server *Server) init() error {
return err
}
admin_pass_str = base64.RawURLEncoding.EncodeToString(admin_pass)
- } else {
+ } else {
server.logger.Debug("BOTTIN_DEFAULT_ADMIN_PW environment variable is set, using it for admin's password")
}
- admin_pass_hash := SSHAEncode([]byte(admin_pass_str))
+ admin_pass_hash, err := SSHAEncode(admin_pass_str)
+ if err != nil {
+ server.logger.Error("can't create admin password")
+ panic(err)
+ }
admin_dn := "cn=admin," + server.config.Suffix
admin_attributes := Entry{
@@ -434,8 +437,8 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (
}
for _, hash := range passwd {
- valid := SSHAMatches(hash, []byte(r.AuthenticationSimple()))
- if valid {
+ valid, err := SSHAMatches(hash, string(r.AuthenticationSimple()))
+ if valid && err == nil {
groups, err := server.getAttribute(string(r.Name()), ATTR_MEMBEROF)
if err != nil {
return ldap.LDAPResultOperationsError, err
@@ -444,8 +447,32 @@ func (server *Server) handleBindInternal(state *State, r *message.BindRequest) (
user: string(r.Name()),
groups: groups,
}
+
+ updatePasswordHash(string(r.AuthenticationSimple()), hash, server, string(r.Name()))
+
return ldap.LDAPResultSuccess, nil
+ } else {
+ return ldap.LDAPResultInvalidCredentials, fmt.Errorf("can't authenticate: %w", err)
}
}
return ldap.LDAPResultInvalidCredentials, fmt.Errorf("No password match")
}
+
+// Update the hash if it's not already SSHA512
+func updatePasswordHash(password string, currentHash string, server *Server, dn string) {
+ hashType, err := determineHashType(currentHash)
+ if err != nil {
+ server.logger.Errorf("can't determine hash type of password")
+ return
+ }
+ if hashType != SSHA512 {
+ reencodedPassword, err := SSHAEncode(password)
+ if err != nil {
+ server.logger.Errorf("can't encode password")
+ return
+ }
+ server.putAttributes(dn, Entry{
+ ATTR_USERPASSWORD: []string{reencodedPassword},
+ })
+ }
+}
diff --git a/ssha.go b/ssha.go
index 15b8e1d..202ce12 100644
--- a/ssha.go
+++ b/ssha.go
@@ -1,59 +1,53 @@
package main
import (
- "bytes"
- "crypto/rand"
- "crypto/sha1"
- "encoding/base64"
- "fmt"
- "strings"
-
- log "github.com/sirupsen/logrus"
+ "errors"
+
+ "github.com/jsimonetti/pwscheme/ssha"
+ "github.com/jsimonetti/pwscheme/ssha256"
+ "github.com/jsimonetti/pwscheme/ssha512"
+)
+
+const (
+ SSHA = "{SSHA}"
+ SSHA256 = "{SSHA256}"
+ SSHA512 = "{SSHA512}"
)
-// Encode encodes the []byte of raw password
-func SSHAEncode(rawPassPhrase []byte) string {
- hash := makeSSHAHash(rawPassPhrase, makeSalt())
- b64 := base64.StdEncoding.EncodeToString(hash)
- return fmt.Sprintf("{ssha}%s", b64)
+// Encode encodes the string to ssha512
+func SSHAEncode(rawPassPhrase string) (string, error) {
+ return ssha512.Generate(rawPassPhrase, 16)
}
// Matches matches the encoded password and the raw password
-func SSHAMatches(encodedPassPhrase string, rawPassPhrase []byte) bool {
- if !strings.EqualFold(encodedPassPhrase[:6], "{ssha}") {
- return false
- }
-
- bhash, err := base64.StdEncoding.DecodeString(encodedPassPhrase[6:])
+func SSHAMatches(encodedPassPhrase string, rawPassPhrase string) (bool, error) {
+ hashType, err := determineHashType(encodedPassPhrase)
if err != nil {
- return false
+ return false, errors.New("invalid password hash stored")
}
- salt := bhash[20:]
-
- newssha := makeSSHAHash(rawPassPhrase, salt)
- if bytes.Compare(newssha, bhash) != 0 {
- return false
+ switch hashType {
+ case SSHA:
+ return ssha.Validate(rawPassPhrase, encodedPassPhrase)
+ case SSHA256:
+ return ssha256.Validate(rawPassPhrase, encodedPassPhrase)
+ case SSHA512:
+ return ssha512.Validate(rawPassPhrase, encodedPassPhrase)
}
- return true
-}
-// makeSalt make a 32 byte array containing random bytes.
-func makeSalt() []byte {
- sbytes := make([]byte, 32)
- _, err := rand.Read(sbytes)
- if err != nil {
- log.Panicf("Could not read random bytes: %s", err)
- }
- return sbytes
+ return false, errors.New("no matching hash type found")
}
-// makeSSHAHash make hasing using SHA-1 with salt. This is not the final output though. You need to append {SSHA} string with base64 of this hash.
-func makeSSHAHash(passphrase, salt []byte) []byte {
- sha := sha1.New()
- sha.Write(passphrase)
- sha.Write(salt)
+func determineHashType(hash string) (string, error) {
+ if len(hash) >= 7 && string(hash[0:6]) == SSHA {
+ return SSHA, nil
+ }
+ if len(hash) >= 10 && string(hash[0:9]) == SSHA256 {
+ return SSHA256, nil
+ }
+ if len(hash) >= 10 && string(hash[0:9]) == SSHA512 {
+ return SSHA512, nil
+ }
- h := sha.Sum(nil)
- return append(h, salt...)
+ return "", errors.New("no valid hash found")
}
diff --git a/write.go b/write.go
index 2dd42c6..55ab5e0 100644
--- a/write.go
+++ b/write.go
@@ -7,8 +7,9 @@ import (
ldap "bottin/ldapserver"
- consul "github.com/hashicorp/consul/api"
message "bottin/goldap"
+
+ consul "github.com/hashicorp/consul/api"
)
// Generic item modification function --------
@@ -38,7 +39,7 @@ func (server *Server) putAttributes(dn string, attrs Entry) error {
// Retreieve previously existing attributes, which we will use to delete
// entries with the wrong case
- previous_pairs, _, err := server.kv.List(prefix + "/attribute=", &server.readOpts)
+ previous_pairs, _, err := server.kv.List(prefix+"/attribute=", &server.readOpts)
if err != nil {
return err
}
@@ -65,6 +66,24 @@ func (server *Server) putAttributes(dn string, attrs Entry) error {
}
}
+ // if the password is not yet hashed we hash it
+ if k == ATTR_USERPASSWORD {
+ tmpValues := []string{}
+ for _, pw := range values {
+ _, err := determineHashType(pw)
+ if err != nil {
+ encodedPassword, err := SSHAEncode(pw)
+ if err != nil {
+ return err
+ }
+ tmpValues = append(tmpValues, encodedPassword)
+ } else {
+ tmpValues = append(tmpValues, pw)
+ }
+ }
+ values = tmpValues
+ }
+
// If we have zero values, delete associated k/v pair
// Otherwise, write new values
if len(values) == 0 {