Commit f8ad66ea authored by ale's avatar ale

Upgrade ai3/go-common

parent 3d44524a
Pipeline #7570 failed with stages
in 17 seconds
......@@ -2,6 +2,7 @@ package clientutil
import (
"context"
"net/http"
)
// BackendConfig specifies the configuration of a service backend.
......@@ -39,6 +40,13 @@ type Backend interface {
// *without* a shard ID on a sharded service is an error.
Call(context.Context, string, string, interface{}, interface{}) error
// Make a simple HTTP GET request to the remote backend,
// without parsing the response as JSON.
//
// Useful for streaming large responses, where the JSON
// encoding overhead is undesirable.
Get(context.Context, string, string) (*http.Response, error)
// Close all resources associated with the backend.
Close()
}
......
......@@ -168,6 +168,44 @@ func (b *balancedBackend) Call(ctx context.Context, shard, path string, req, res
}, backoff.WithContext(newExponentialBackOff(), ctx))
}
// Makes a generic HTTP GET request to the backend uri.
func (b *balancedBackend) Get(ctx context.Context, shard, path string) (*http.Response, error) {
// Create the target sequence for this call. If there are multiple
// targets, reduce the timeout on each individual call accordingly to
// accomodate eventual failover.
seq, err := b.makeSequence(shard)
if err != nil {
return nil, err
}
innerTimeout := 1 * time.Hour
if deadline, ok := ctx.Deadline(); ok {
innerTimeout = time.Until(deadline) / time.Duration(seq.Len())
}
if b.requestMaxTimeout > 0 && innerTimeout > b.requestMaxTimeout {
innerTimeout = b.requestMaxTimeout
}
req, err := http.NewRequest("GET", b.getURIForRequest(shard, path), nil)
if err != nil {
return nil, err
}
// Call the backends in the sequence until one succeeds, with an
// exponential backoff policy controlled by the outer Context.
var resp *http.Response
err = backoff.Retry(func() error {
innerCtx, cancel := context.WithTimeout(ctx, innerTimeout)
defer cancel()
// When do() returns successfully, we already know that the
// response had an HTTP status of 200.
var rerr error
resp, rerr = b.do(innerCtx, seq, req)
return rerr
}, backoff.WithContext(newExponentialBackOff(), ctx))
return resp, err
}
// Initialize a new target sequence.
func (b *balancedBackend) makeSequence(shard string) (*sequence, error) {
var tg targetGenerator = b.backendTracker
......
module git.autistici.org/ai3/go-common
go 1.14
require (
contrib.go.opencensus.io/exporter/zipkin v0.1.1
github.com/amoghe/go-crypt v0.0.0-20191109212615-b2ff80594b7f
github.com/bbrks/wrap v2.3.0+incompatible
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe
github.com/google/go-cmp v0.4.0
github.com/gorilla/handlers v1.4.2
github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
github.com/openzipkin/zipkin-go v0.2.2
github.com/prometheus/client_golang v1.5.1
github.com/russross/blackfriday/v2 v2.0.1
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/theckman/go-flock v0.7.1
github.com/tstranex/u2f v1.0.0
go.opencensus.io v0.22.3
golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d
gopkg.in/ldap.v3 v3.1.0
)
This diff is collapsed.
package pwhash
import (
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"golang.org/x/crypto/argon2"
)
var (
argonKeyLen uint32 = 32
argonSaltLen = 16
)
// Argon2PasswordHash uses the Argon2 hashing algorithm.
type argon2PasswordHash struct {
// Encoding parameters.
params argon2Params
// Codec for string encoding.
codec argon2Codec
}
// newArgon2PasswordHash returns an Argon2i-based PasswordHash using the
// specified parameters for time, memory, and number of threads.
func newArgon2PasswordHash(time, mem uint32, threads uint8, codec argon2Codec) PasswordHash {
return &argon2PasswordHash{
params: argon2Params{
Time: time,
Memory: mem,
Threads: threads,
},
codec: codec,
}
}
// NewArgon2 returns an Argon2i-based PasswordHash using the default parameters.
func NewArgon2() PasswordHash {
return NewArgon2WithParams(
defaultArgon2Params.Time,
defaultArgon2Params.Memory,
defaultArgon2Params.Threads,
)
}
// NewArgon2WithParams returns an Argon2i-based PasswordHash using the
// specified parameters for time, memory, and number of threads.
func NewArgon2WithParams(time, mem uint32, threads uint8) PasswordHash {
return newArgon2PasswordHash(time, mem, threads, &a2Codec{})
}
// NewArgon2StdWithParams returns an Argon2i-based PasswordHash using
// the specified parameters for time, memory, and number of
// threads. This will use the string encoding ("$argon2$") documented
// in the argon2 reference implementation.
func NewArgon2StdWithParams(time, mem uint32, threads uint8) PasswordHash {
return newArgon2PasswordHash(time, mem, threads, &argon2StdCodec{})
}
// ComparePassword returns true if the given password matches the
// encrypted one.
func (s *argon2PasswordHash) ComparePassword(encrypted, password string) bool {
params, salt, dk, err := s.codec.decodeArgon2Hash(encrypted)
if err != nil {
return false
}
dk2 := argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, argonKeyLen)
return subtle.ConstantTimeCompare(dk, dk2) == 1
}
// Encrypt the given password with the Argon2 algorithm.
func (s *argon2PasswordHash) Encrypt(password string) string {
salt := getRandomBytes(argonSaltLen)
dk := argon2.Key([]byte(password), salt, s.params.Time, s.params.Memory, s.params.Threads, argonKeyLen)
return s.codec.encodeArgon2Hash(s.params, salt, dk)
}
type argon2Params struct {
Time uint32
Memory uint32
Threads uint8
}
// Default Argon2 parameters are tuned for a high-traffic
// authentication service (<1ms per operation).
var defaultArgon2Params = argon2Params{
Time: 1,
Memory: 4 * 1024,
Threads: 4,
}
type argon2Codec interface {
encodeArgon2Hash(argon2Params, []byte, []byte) string
decodeArgon2Hash(string) (argon2Params, []byte, []byte, error)
}
type a2Codec struct{}
func (*a2Codec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
return fmt.Sprintf("$a2$%d$%d$%d$%x$%x", params.Time, params.Memory, params.Threads, salt, dk)
}
func (*a2Codec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$a2$") {
err = errors.New("not an Argon2 password hash")
return
}
parts := strings.SplitN(s[4:], "$", 5)
if len(parts) != 5 {
err = errors.New("bad encoding")
return
}
var i uint64
if i, err = strconv.ParseUint(parts[0], 10, 32); err != nil {
return
}
params.Time = uint32(i)
if i, err = strconv.ParseUint(parts[1], 10, 32); err != nil {
return
}
params.Memory = uint32(i)
if i, err = strconv.ParseUint(parts[2], 10, 8); err != nil {
return
}
params.Threads = uint8(i)
salt, err = hex.DecodeString(parts[3])
if err != nil {
return
}
dk, err = hex.DecodeString(parts[4])
return
}
type argon2StdCodec struct{}
func (*argon2StdCodec) encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
encSalt := base64.RawStdEncoding.EncodeToString(salt)
encDK := base64.RawStdEncoding.EncodeToString(dk)
return fmt.Sprintf("$argon2i$v=19$m=%d,t=%d,p=%d$%s$%s", params.Memory, params.Time, params.Threads, encSalt, encDK)
}
func parseArgon2HashParams(s string) (params argon2Params, err error) {
params = defaultArgon2Params
parts := strings.Split(s, ",")
for _, ss := range parts {
kv := strings.SplitN(ss, "=", 2)
if len(kv) != 2 {
err = errors.New("bad parameter encoding")
return
}
var i uint64
switch kv[0] {
case "t":
i, err = strconv.ParseUint(kv[1], 10, 32)
params.Time = uint32(i)
case "m":
i, err = strconv.ParseUint(kv[1], 10, 32)
params.Memory = uint32(i)
case "p":
i, err = strconv.ParseUint(kv[1], 10, 8)
params.Threads = uint8(i)
default:
err = errors.New("unknown parameter in hash")
}
if err != nil {
return
}
}
return
}
func (*argon2StdCodec) decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$argon2i$") {
err = errors.New("not an Argon2 password hash")
return
}
parts := strings.SplitN(s[9:], "$", 4)
if len(parts) != 4 {
err = errors.New("bad encoding")
return
}
if parts[0] != "v=19" {
err = errors.New("bad argon2 hash version")
return
}
params, err = parseArgon2HashParams(parts[1])
if err != nil {
return
}
if salt, err = base64.RawStdEncoding.DecodeString(parts[2]); err != nil {
return
}
dk, err = base64.RawStdEncoding.DecodeString(parts[3])
return
}
package pwhash
import (
"crypto/subtle"
"fmt"
"github.com/amoghe/go-crypt"
)
// systemCryptPasswordHash uses the glibc crypt function.
type systemCryptPasswordHash struct {
hashStr string
}
// NewSystemCrypt returns a PasswordHash that uses the system crypt(3)
// function, specifically glibc with its SHA512 algorithm.
func NewSystemCrypt() PasswordHash {
return &systemCryptPasswordHash{"$6$"}
}
// ComparePassword returns true if the given password matches the
// encrypted one.
func (s *systemCryptPasswordHash) ComparePassword(encrypted, password string) bool {
enc2, err := crypt.Crypt(password, encrypted)
if err != nil {
return false
}
return subtle.ConstantTimeCompare([]byte(encrypted), []byte(enc2)) == 1
}
// Encrypt the given password using glibc crypt.
func (s *systemCryptPasswordHash) Encrypt(password string) string {
salt := fmt.Sprintf("%s%x$", s.hashStr, getRandomBytes(16))
enc, err := crypt.Crypt(password, salt)
if err != nil {
panic(err)
}
return enc
}
......@@ -23,17 +23,8 @@ package pwhash
import (
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"errors"
"fmt"
"io"
"strconv"
"strings"
"github.com/amoghe/go-crypt"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/scrypt"
)
// PasswordHash is the interface for a password hashing algorithm
......@@ -47,245 +38,6 @@ type PasswordHash interface {
Encrypt(string) string
}
// systemCryptPasswordHash uses the glibc crypt function.
type systemCryptPasswordHash struct {
hashStr string
}
// NewSystemCrypt returns a PasswordHash that uses the system crypt(3)
// function, specifically glibc with its SHA512 algorithm.
func NewSystemCrypt() PasswordHash {
return &systemCryptPasswordHash{"$6$"}
}
// ComparePassword returns true if the given password matches the
// encrypted one.
func (s *systemCryptPasswordHash) ComparePassword(encrypted, password string) bool {
enc2, err := crypt.Crypt(password, encrypted)
if err != nil {
return false
}
return subtle.ConstantTimeCompare([]byte(encrypted), []byte(enc2)) == 1
}
// Encrypt the given password using glibc crypt.
func (s *systemCryptPasswordHash) Encrypt(password string) string {
salt := fmt.Sprintf("%s%x$", s.hashStr, getRandomBytes(16))
enc, err := crypt.Crypt(password, salt)
if err != nil {
panic(err)
}
return enc
}
var (
argonKeyLen uint32 = 32
argonSaltLen = 16
)
// Argon2PasswordHash uses the Argon2 hashing algorithm.
type argon2PasswordHash struct {
// Encoding parameters.
params argon2Params
}
// NewArgon2 returns an Argon2i-based PasswordHash using the default parameters.
func NewArgon2() PasswordHash {
return NewArgon2WithParams(
defaultArgon2Params.Time,
defaultArgon2Params.Memory,
defaultArgon2Params.Threads,
)
}
// NewArgon2WithParams returns an Argon2i-based PasswordHash using the
// specified parameters for time, memory, and number of threads.
func NewArgon2WithParams(time, mem uint32, threads uint8) PasswordHash {
return &argon2PasswordHash{
params: argon2Params{
Time: time,
Memory: mem,
Threads: threads,
},
}
}
// ComparePassword returns true if the given password matches the
// encrypted one.
func (s *argon2PasswordHash) ComparePassword(encrypted, password string) bool {
params, salt, dk, err := decodeArgon2Hash(encrypted)
if err != nil {
return false
}
dk2 := argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, argonKeyLen)
return subtle.ConstantTimeCompare(dk, dk2) == 1
}
// Encrypt the given password with the Argon2 algorithm.
func (s *argon2PasswordHash) Encrypt(password string) string {
salt := getRandomBytes(argonSaltLen)
dk := argon2.Key([]byte(password), salt, s.params.Time, s.params.Memory, s.params.Threads, argonKeyLen)
return encodeArgon2Hash(s.params, salt, dk)
}
type argon2Params struct {
Time uint32
Memory uint32
Threads uint8
}
// Default Argon2 parameters are tuned for a high-traffic
// authentication service (<1ms per operation).
var defaultArgon2Params = argon2Params{
Time: 1,
Memory: 4 * 1024,
Threads: 4,
}
func encodeArgon2Hash(params argon2Params, salt, dk []byte) string {
return fmt.Sprintf("$a2$%d$%d$%d$%x$%x", params.Time, params.Memory, params.Threads, salt, dk)
}
func decodeArgon2Hash(s string) (params argon2Params, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$a2$") {
err = errors.New("not an Argon2 password hash")
return
}
parts := strings.SplitN(s[4:], "$", 5)
if len(parts) != 5 {
err = errors.New("bad encoding")
return
}
var i uint64
if i, err = strconv.ParseUint(parts[0], 10, 32); err != nil {
return
}
params.Time = uint32(i)
if i, err = strconv.ParseUint(parts[1], 10, 32); err != nil {
return
}
params.Memory = uint32(i)
if i, err = strconv.ParseUint(parts[2], 10, 8); err != nil {
return
}
params.Threads = uint8(i)
if salt, err = hex.DecodeString(parts[3]); err != nil {
return
}
dk, err = hex.DecodeString(parts[4])
return
}
var (
scryptKeyLen = 32
scryptSaltLen = 16
)
// ScryptPasswordHash uses the scrypt hashing algorithm.
type scryptPasswordHash struct {
params scryptParams
}
// NewScrypt returns a PasswordHash that uses the scrypt algorithm
// with the default parameters.
func NewScrypt() PasswordHash {
return NewScryptWithParams(
defaultScryptParams.N,
defaultScryptParams.R,
defaultScryptParams.P,
)
}
// NewScryptWithParams returns a PasswordHash that uses the scrypt
// algorithm with the specified parameters.
func NewScryptWithParams(n, r, p int) PasswordHash {
return &scryptPasswordHash{
params: scryptParams{
N: n,
R: r,
P: p,
},
}
}
// ComparePassword returns true if the given password matches
// the encrypted one.
func (s *scryptPasswordHash) ComparePassword(encrypted, password string) bool {
params, salt, dk, err := decodeScryptHash(encrypted)
if err != nil {
return false
}
dk2, err := scrypt.Key([]byte(password), salt, params.N, params.R, params.P, scryptKeyLen)
if err != nil {
return false
}
return subtle.ConstantTimeCompare(dk, dk2) == 1
}
// Encrypt the given password with the scrypt algorithm.
func (s *scryptPasswordHash) Encrypt(password string) string {
salt := getRandomBytes(scryptSaltLen)
dk, err := scrypt.Key([]byte(password), salt, s.params.N, s.params.R, s.params.P, scryptKeyLen)
if err != nil {
panic(err)
}
return encodeScryptHash(s.params, salt, dk)
}
type scryptParams struct {
N int
R int
P int
}
var defaultScryptParams = scryptParams{
N: 16384,
R: 8,
P: 1,
}
func encodeScryptHash(params scryptParams, salt, dk []byte) string {
return fmt.Sprintf("$s$%d$%d$%d$%x$%x", params.N, params.R, params.P, salt, dk)
}
func decodeScryptHash(s string) (params scryptParams, salt []byte, dk []byte, err error) {
if !strings.HasPrefix(s, "$s$") {
err = errors.New("not a scrypt password hash")
return
}
parts := strings.SplitN(s[3:], "$", 5)
if len(parts) != 5 {
err = errors.New("bad encoding")
return
}
if params.N, err = strconv.Atoi(parts[0]); err != nil {
return
}
if params.R, err = strconv.Atoi(parts[1]); err != nil {
return
}
if params.P, err = strconv.Atoi(parts[2]); err != nil {
return
}
if salt, err = hex.DecodeString(parts[3]); err != nil {
return
}
dk, err = hex.DecodeString(parts[4])
return
}
func getRandomBytes(n int) []byte {
b := make([]byte, n)
_, err := io.ReadFull(rand.Reader, b[:])
......
package pwhash
import (
"crypto/subtle"
"encoding/hex"
"errors"