github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libpages/config/passwords.go (about) 1 // Copyright 2018 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package config 6 7 import ( 8 "context" 9 "crypto/rand" 10 "crypto/sha256" 11 "encoding/hex" 12 "errors" 13 "fmt" 14 "io" 15 "strings" 16 "sync" 17 18 "golang.org/x/crypto/bcrypt" 19 "golang.org/x/time/rate" 20 ) 21 22 type passwordType int 23 24 const ( 25 _ passwordType = iota 26 passwordTypeBcrypt 27 passwordTypeSHA256 28 ) 29 30 const passwordHashDivider = ":" 31 const sha256PasswordHashPrefix = "sha256" 32 const saltSize = 12 33 34 var sha256PasswordHashLength = len(sha256PasswordHashPrefix) + 35 len(passwordHashDivider) + 36 hex.EncodedLen(saltSize) + 37 len(passwordHashDivider) + 38 hex.EncodedLen(sha256.Size) 39 var sha256PasswordHashSaltIndex = len(sha256PasswordHashPrefix) + 40 len(passwordHashDivider) 41 var sha256PasswordHashSaltEnd = sha256PasswordHashSaltIndex + 42 hex.EncodedLen(saltSize) 43 var sha256PasswordHashSHA256Index = sha256PasswordHashSaltEnd + 44 len(passwordHashDivider) 45 46 // InvalidPasswordHash is the error that happens when there's an invalid 47 // password hash in the config. 48 type InvalidPasswordHash struct{} 49 50 // Error implements the error interface. 51 func (InvalidPasswordHash) Error() string { 52 return "invalid passwordhash" 53 } 54 55 type password interface { 56 check(ctx context.Context, 57 limiter *rate.Limiter, cleartext string) (bool, error) 58 passwordType() passwordType 59 } 60 61 type sha256Password struct { 62 hash [sha256.Size]byte 63 salt [saltSize]byte 64 } 65 66 var _ password = (*sha256Password)(nil) 67 68 func (p *sha256Password) check(_ context.Context, _ *rate.Limiter, 69 cleartext string) (match bool, err error) { 70 sum := sha256.New() 71 if _, err = sum.Write(p.salt[:]); err != nil { 72 return false, fmt.Errorf("calculating sha256 error: %v", err) 73 } 74 if _, err = io.WriteString(sum, cleartext); err != nil { 75 return false, fmt.Errorf("calculating sha256 error: %v", err) 76 } 77 return p.hash == sha256.Sum256(append(p.salt[:], cleartext...)), nil 78 } 79 80 func (p *sha256Password) passwordType() passwordType { 81 return passwordTypeSHA256 82 } 83 84 type bcryptCachingPassword struct { 85 bcryptHash []byte 86 87 lock sync.RWMutex 88 cleartext string 89 } 90 91 var _ password = (*bcryptCachingPassword)(nil) 92 93 func (p *bcryptCachingPassword) getCachedCleartext() string { 94 p.lock.RLock() 95 defer p.lock.RUnlock() 96 return p.cleartext 97 } 98 99 func (p *bcryptCachingPassword) setCachedCleartext(cleartext string) { 100 p.lock.Lock() 101 defer p.lock.Unlock() 102 p.cleartext = cleartext 103 } 104 105 func (p *bcryptCachingPassword) passwordType() passwordType { 106 return passwordTypeBcrypt 107 } 108 109 func (p *bcryptCachingPassword) check(ctx context.Context, 110 limiter *rate.Limiter, cleartext string) (bool, error) { 111 cachedCleartext := p.getCachedCleartext() 112 if len(cachedCleartext) > 0 { 113 return cachedCleartext == cleartext, nil 114 } 115 116 if err := limiter.Wait(ctx); err != nil { 117 return false, err 118 } 119 120 match := bcrypt.CompareHashAndPassword( 121 p.bcryptHash, []byte(cleartext)) == nil 122 if match { 123 p.setCachedCleartext(cleartext) 124 } 125 126 return match, nil 127 } 128 129 // GenerateSHA256PasswordHash generates a SHA256 based password hash. 130 func GenerateSHA256PasswordHash(cleartext string) (string, error) { 131 salt := make([]byte, saltSize) 132 n, err := rand.Read(salt) 133 if err != nil || n != saltSize { 134 return "", errors.New("reading random bytes error") 135 } 136 137 hash := sha256.Sum256(append(salt, cleartext...)) 138 return sha256PasswordHashPrefix + passwordHashDivider + 139 hex.EncodeToString(salt) + passwordHashDivider + 140 hex.EncodeToString(hash[:]), nil 141 } 142 143 // newPassword takes a password hash (usually from a .kbp_config file) and 144 // makes a password object out of it. Accepted password hashes are: 145 // 1. bcrypt hashes. For example: 146 // $2a$04$DXabUWtVUX/nOEQ2R8aBT.wRUZxllKA2Lbm6Z3cGhkRLwMb6u8Esq 147 // 2. sha256 hashes in format of sha256:<hex of salt>:<hex of sha256sum>. 148 // For example: 149 // sha256:249704a205894bb003b9f82a:6f2e235f076f1c7e1cfedec477091343dd4b1a678b11554321ee1a493925695c 150 func newPassword(passwordHashFromConfig string) (password, error) { 151 if strings.HasPrefix(passwordHashFromConfig, sha256PasswordHashPrefix) { 152 if len(passwordHashFromConfig) != sha256PasswordHashLength { 153 return nil, InvalidPasswordHash{} 154 } 155 salt, err := hex.DecodeString( 156 passwordHashFromConfig[sha256PasswordHashSaltIndex:sha256PasswordHashSaltEnd]) 157 if err != nil { 158 return nil, InvalidPasswordHash{} 159 } 160 hash, err := hex.DecodeString( 161 passwordHashFromConfig[sha256PasswordHashSHA256Index:]) 162 if err != nil { 163 return nil, InvalidPasswordHash{} 164 } 165 p := &sha256Password{} 166 copy(p.hash[:], hash) 167 copy(p.salt[:], salt) 168 return p, nil 169 } 170 if _, err := bcrypt.Cost([]byte(passwordHashFromConfig)); err == nil { 171 return &bcryptCachingPassword{ 172 bcryptHash: []byte(passwordHashFromConfig), 173 }, nil 174 } 175 return nil, InvalidPasswordHash{} 176 }