From b35dd025bdc2a9fd000598a412da4651bd60dbf9 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 30 May 2026 11:53:31 -0700 Subject: [PATCH] Get native auth working Former-commit-id: e5345f67172b4eade91dc926dd96a8584a0f206d --- browsergateway/browsergateway.go | 16 ++ browsergateway/ssh.go | 11 +- go.mod | 5 +- go.sum | 22 +++ main.go | 2 +- nativessh/auth.go | 17 +- nativessh/pam_linux.go | 280 ++++++------------------------- newt.REMOVED.git-id | 1 + 8 files changed, 119 insertions(+), 235 deletions(-) create mode 100644 newt.REMOVED.git-id diff --git a/browsergateway/browsergateway.go b/browsergateway/browsergateway.go index 2d0c6a8..60fecb5 100644 --- a/browsergateway/browsergateway.go +++ b/browsergateway/browsergateway.go @@ -96,6 +96,22 @@ func (g *Gateway) isAllowed(targetType, host string, port int, authToken string) return false } +// isTokenValid reports whether the given authToken matches any registered +// target of the specified type. Used for native SSH mode where there is no +// external destination to match against. +func (g *Gateway) isTokenValid(targetType, authToken string) bool { + g.mu.RLock() + defer g.mu.RUnlock() + for _, t := range g.targets { + if t.Type == targetType { + if subtle.ConstantTimeCompare([]byte(authToken), []byte(t.AuthToken)) == 1 { + return true + } + } + } + return false +} + // Start serves the browser gateway HTTP server on the provided listener. // It returns nil when the listener is closed (normal shutdown). func (g *Gateway) Start(ln net.Listener) error { diff --git a/browsergateway/ssh.go b/browsergateway/ssh.go index eaea313..bf5a83a 100644 --- a/browsergateway/ssh.go +++ b/browsergateway/ssh.go @@ -2,7 +2,6 @@ package browsergateway import ( "context" - "crypto/subtle" "encoding/json" "fmt" "log" @@ -12,6 +11,7 @@ import ( "time" "github.com/coder/websocket" + "github.com/fosrl/newt/logger" "golang.org/x/crypto/ssh" ) @@ -36,11 +36,14 @@ type sshServerMsg struct { // HandleSSH is an http.HandlerFunc for SSH-over-WebSocket connections. func (g *Gateway) HandleSSH(w http.ResponseWriter, r *http.Request) { + logger.Debug("SSH connection request from %s", r.RemoteAddr) ctx := r.Context() token := r.URL.Query().Get("authToken") - var nativeSSH = false + // "mode=native" (default) connects to the local SSH daemon on this host. + // "mode=proxy" connects to an arbitrary host+port supplied in query params. + nativeSSH := r.URL.Query().Get("mode") != "proxy" // In proxy mode we also need host + username from query params. var target, username string @@ -62,8 +65,8 @@ func (g *Gateway) HandleSSH(w http.ResponseWriter, r *http.Request) { } target = net.JoinHostPort(host, port) } else { - // Native SSH mode: validate the gateway token then read the target username. - if subtle.ConstantTimeCompare([]byte(token), []byte(g.authToken)) != 1 { + // Native SSH mode: validate the token against any registered ssh target. + if !g.isTokenValid("ssh", token) { http.Error(w, "unauthorized", http.StatusUnauthorized) return } diff --git a/go.mod b/go.mod index b5a8f8e..3207ed1 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( golang.org/x/crypto v0.50.0 golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 golang.org/x/net v0.53.0 - golang.org/x/sys v0.43.0 + golang.org/x/sys v0.45.0 golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 golang.zx2c4.com/wireguard/windows v0.5.3 @@ -44,11 +44,14 @@ require ( github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-crypt/crypt v0.14.15 // indirect + github.com/go-crypt/x v0.4.16 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/jsimonetti/pwscheme v0.0.0-20220922140336-67a4d090f150 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect diff --git a/go.sum b/go.sum index bb059f0..4db0e23 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,10 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gaissmai/bart v0.26.1 h1:+w4rnLGNlA2GDVn382Tfe3jOsK5vOr5n4KmigJ9lbTo= github.com/gaissmai/bart v0.26.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= +github.com/go-crypt/crypt v0.14.15 h1:q1i5OMpL05r935IxWmXgpDAVF0nvi4SMoHhGXLBQUEQ= +github.com/go-crypt/crypt v0.14.15/go.mod h1:0n/to1VqIZPENj2yEUa/sLLYYnmupma6cp+QMX4zfF0= +github.com/go-crypt/x v0.4.16 h1:WXdY28H/0MsXnH+gwerxuCcvBTJPkBG90u6oS4gIPZI= +github.com/go-crypt/x v0.4.16/go.mod h1:vmVFA/d/oLrEaCbqsLcjBMlTqF8u8pvH/c4+EJ/ped8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -43,6 +47,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +github.com/jsimonetti/pwscheme v0.0.0-20220922140336-67a4d090f150 h1:ta6N7DaOQEACq28cLa0iRqXIbchByN9Lfll08CT2GBc= +github.com/jsimonetti/pwscheme v0.0.0-20220922140336-67a4d090f150/go.mod h1:SiNTKDgjKQORnazFVHXhpny7UtU0iJOqtxd7R7sCfDI= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -113,22 +119,38 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220921155015-db77216a4ee9/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= diff --git a/main.go b/main.go index 281abf9..25d4102 100644 --- a/main.go +++ b/main.go @@ -534,7 +534,7 @@ func runNewtMain(ctx context.Context) { // Start auth daemon if enabled if !disableSSH { if err := startAuthDaemon(ctx); err != nil { - logger.Fatal("Failed to start auth daemon: %v", err) + logger.Warn("Did not start on site auth daemon: %v", err) } } diff --git a/nativessh/auth.go b/nativessh/auth.go index a060ef7..2de7a21 100644 --- a/nativessh/auth.go +++ b/nativessh/auth.go @@ -3,6 +3,7 @@ package nativessh import ( "bufio" "fmt" + "log" "os" "os/user" "path/filepath" @@ -58,19 +59,31 @@ func SystemUserExists(username string) bool { // // Returns nil on the first method that succeeds, or an error if all fail. func Authenticate(username, password, privateKeyPEM string) error { + log.Printf("nativessh: authenticating user %q (hasPassword=%v, hasPrivateKey=%v)", username, password != "", privateKeyPEM != "") if !SystemUserExists(username) { + log.Printf("nativessh: user %q not found on system", username) return fmt.Errorf("user %q does not exist", username) } if privateKeyPEM != "" { signer, err := ssh.ParsePrivateKey([]byte(privateKeyPEM)) - if err == nil && CheckAuthorizedKeys(username, signer.PublicKey()) { + if err != nil { + log.Printf("nativessh: failed to parse private key for %q: %v", username, err) + } else if CheckAuthorizedKeys(username, signer.PublicKey()) { + log.Printf("nativessh: private key auth succeeded for %q", username) return nil + } else { + log.Printf("nativessh: private key not in authorized_keys for %q", username) } } if password != "" { - if err := VerifySystemPassword(username, password); err == nil { + if err := VerifySystemPassword(username, password); err != nil { + log.Printf("nativessh: password auth failed for %q: %v", username, err) + } else { + log.Printf("nativessh: password auth succeeded for %q", username) return nil } + } else { + log.Printf("nativessh: no password provided for %q", username) } return fmt.Errorf("authentication failed for user %q", username) } diff --git a/nativessh/pam_linux.go b/nativessh/pam_linux.go index 53e8506..f33828b 100644 --- a/nativessh/pam_linux.go +++ b/nativessh/pam_linux.go @@ -5,25 +5,71 @@ package nativessh import ( "bufio" "bytes" - "crypto/sha512" "errors" "fmt" + "log" "os" - "strconv" "strings" - "golang.org/x/crypto/bcrypt" + "github.com/go-crypt/crypt" + "github.com/go-crypt/x/yescrypt" ) -// VerifySystemPassword authenticates username/password by reading /etc/shadow -// and verifying the stored hash using pure-Go cryptography (no CGo required). -// Supported hash schemes: bcrypt ($2a$/$2b$/$2y$) and SHA-512 crypt ($6$). +// VerifySystemPassword authenticates username/password by reading /etc/shadow. +// Supports yescrypt ($y$), bcrypt ($2b$/$2a$/$2y$), SHA-512 ($6$), SHA-256 +// ($5$), argon2, scrypt, and other schemes handled by go-crypt/crypt. func VerifySystemPassword(username, password string) error { hash, err := readShadowHash(username) if err != nil { + log.Printf("nativessh/pam: readShadowHash for %q failed: %v", username, err) return fmt.Errorf("shadow: %w", err) } - return cryptVerify(password, hash) + + // Log the scheme prefix only (never the full hash). + scheme := "unknown" + for _, prefix := range []string{"$y$", "$2a$", "$2b$", "$2y$", "$6$", "$5$", "$1$"} { + if strings.HasPrefix(hash, prefix) { + scheme = prefix + break + } + } + log.Printf("nativessh/pam: verifying password for %q using scheme %s", username, scheme) + + // Yescrypt ($y$) is not in go-crypt/crypt's default decoder; handle it directly. + if strings.HasPrefix(hash, "$y$") { + computed, err := yescrypt.Hash([]byte(password), []byte(hash)) + if err != nil { + log.Printf("nativessh/pam: yescrypt.Hash for %q failed: %v", username, err) + return fmt.Errorf("yescrypt: %w", err) + } + if !bytes.Equal(computed, []byte(hash)) { + log.Printf("nativessh/pam: yescrypt mismatch for %q", username) + return errors.New("authentication failed") + } + return nil + } + + decoder, err := crypt.NewDefaultDecoder() + if err != nil { + return fmt.Errorf("crypt decoder: %w", err) + } + + digest, err := decoder.Decode(hash) + if err != nil { + log.Printf("nativessh/pam: failed to decode hash for %q: %v", username, err) + return fmt.Errorf("unsupported password hash scheme %q: %w", scheme, err) + } + + match, err := digest.MatchAdvanced(password) + if err != nil { + log.Printf("nativessh/pam: MatchAdvanced for %q failed: %v", username, err) + return err + } + if !match { + log.Printf("nativessh/pam: password mismatch for %q", username) + return errors.New("authentication failed") + } + return nil } // readShadowHash reads /etc/shadow and returns the password hash for username. @@ -51,223 +97,3 @@ func readShadowHash(username string) (string, error) { } return "", errors.New("user not found in shadow database") } - -// cryptVerify verifies password against a crypt(3) hash string. -func cryptVerify(password, hash string) error { - switch { - case strings.HasPrefix(hash, "$2a$"), strings.HasPrefix(hash, "$2b$"), strings.HasPrefix(hash, "$2y$"): - return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) - case strings.HasPrefix(hash, "$6$"): - computed, err := sha512CryptHash([]byte(password), hash) - if err != nil { - return err - } - if computed != hash { - return errors.New("authentication failed") - } - return nil - default: - return fmt.Errorf("unsupported password hash scheme") - } -} - -// --- SHA-512 crypt ($6$) --- -// Specification: https://www.akkadia.org/docs/sha-crypt.txt - -const ( - sha512CryptMagic = "$6$" - sha512CryptRoundsDefault = 5000 - sha512CryptRoundsMin = 1000 - sha512CryptRoundsMax = 999999999 - sha512CryptSaltLenMax = 16 - sha512CryptAlphabet = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" -) - -var sha512CryptRoundsPrefix = []byte("rounds=") - -// sha512CryptHash computes a SHA-512 crypt hash for key. saltStr may be a -// full stored hash string (for verification) or just the salt parameters. -func sha512CryptHash(key []byte, saltStr string) (string, error) { - salt := []byte(saltStr) - - if !bytes.HasPrefix(salt, []byte(sha512CryptMagic)) { - return "", errors.New("sha512crypt: invalid prefix") - } - salt = salt[len(sha512CryptMagic):] - - rounds := sha512CryptRoundsDefault - isRoundsDef := false - - if bytes.HasPrefix(salt, sha512CryptRoundsPrefix) { - salt = salt[len(sha512CryptRoundsPrefix):] - i := bytes.IndexByte(salt, '$') - if i < 0 { - return "", errors.New("sha512crypt: malformed rounds field") - } - r, err := strconv.Atoi(string(salt[:i])) - if err != nil { - return "", fmt.Errorf("sha512crypt: invalid rounds: %w", err) - } - salt = salt[i+1:] - isRoundsDef = true - rounds = r - if rounds < sha512CryptRoundsMin { - rounds = sha512CryptRoundsMin - } else if rounds > sha512CryptRoundsMax { - rounds = sha512CryptRoundsMax - } - } - - // When saltStr is a full hash, strip the stored hash after the last '$'. - if i := bytes.IndexByte(salt, '$'); i >= 0 { - salt = salt[:i] - } - if len(salt) > sha512CryptSaltLenMax { - salt = salt[:sha512CryptSaltLenMax] - } - - // Alternate = SHA512(key + salt + key) - altH := sha512.New() - altH.Write(key) - altH.Write(salt) - altH.Write(key) - altSum := altH.Sum(nil) - - // Digest A = SHA512(key + salt + altSum-cycling-to-len(key) + bit-pattern) - aH := sha512.New() - aH.Write(key) - aH.Write(salt) - for i := len(key); i > 0; i -= 64 { - if i > 64 { - aH.Write(altSum) - } else { - aH.Write(altSum[:i]) - } - } - for i := len(key); i > 0; i >>= 1 { - if i&1 != 0 { - aH.Write(altSum) - } else { - aH.Write(key) - } - } - aSum := aH.Sum(nil) - - // P-sequence: SHA512(key×len(key)) cycled to len(key) bytes - pH := sha512.New() - for i := 0; i < len(key); i++ { - pH.Write(key) - } - pSeq := sha512CryptCycle(pH.Sum(nil), len(key)) - - // S-sequence: SHA512(salt×(16+aSum[0])) cycled to len(salt) bytes - sH := sha512.New() - for i := 0; i < 16+int(aSum[0]); i++ { - sH.Write(salt) - } - sSeq := sha512CryptCycle(sH.Sum(nil), len(salt)) - - // Iterative hashing rounds - cSum := aSum - for i := 0; i < rounds; i++ { - c := sha512.New() - if i&1 != 0 { - c.Write(pSeq) - } else { - c.Write(cSum) - } - if i%3 != 0 { - c.Write(sSeq) - } - if i%7 != 0 { - c.Write(pSeq) - } - if i&1 != 0 { - c.Write(cSum) - } else { - c.Write(pSeq) - } - cSum = c.Sum(nil) - } - - // Build the output string - out := []byte(sha512CryptMagic) - if isRoundsDef { - out = append(out, fmt.Sprintf("rounds=%d$", rounds)...) - } - out = append(out, salt...) - out = append(out, '$') - out = append(out, sha512CryptEncode(cSum)...) - return string(out), nil -} - -// sha512CryptCycle returns exactly n bytes by cycling the 64-byte src slice. -func sha512CryptCycle(src []byte, n int) []byte { - dst := make([]byte, 0, n) - for i := n; i > 64; i -= 64 { - dst = append(dst, src...) - } - if rem := n % 64; rem == 0 && n > 0 { - dst = append(dst, src...) - } else if rem > 0 { - dst = append(dst, src[:rem]...) - } - return dst -} - -// sha512CryptEncode applies the sha512crypt byte permutation and encodes the -// result with the crypt(3) base-64 alphabet (86 output characters for 64 input bytes). -func sha512CryptEncode(sum []byte) []byte { - perm := []byte{ - sum[42], sum[21], sum[0], - sum[1], sum[43], sum[22], - sum[23], sum[2], sum[44], - sum[45], sum[24], sum[3], - sum[4], sum[46], sum[25], - sum[26], sum[5], sum[47], - sum[48], sum[27], sum[6], - sum[7], sum[49], sum[28], - sum[29], sum[8], sum[50], - sum[51], sum[30], sum[9], - sum[10], sum[52], sum[31], - sum[32], sum[11], sum[53], - sum[54], sum[33], sum[12], - sum[13], sum[55], sum[34], - sum[35], sum[14], sum[56], - sum[57], sum[36], sum[15], - sum[16], sum[58], sum[37], - sum[38], sum[17], sum[59], - sum[60], sum[39], sum[18], - sum[19], sum[61], sum[40], - sum[41], sum[20], sum[62], - sum[63], - } - src := perm - out := make([]byte, 0, 86) - for len(src) > 0 { - switch len(src) { - default: - out = append(out, - sha512CryptAlphabet[src[0]&0x3f], - sha512CryptAlphabet[((src[0]>>6)|(src[1]<<2))&0x3f], - sha512CryptAlphabet[((src[1]>>4)|(src[2]<<4))&0x3f], - sha512CryptAlphabet[(src[2]>>2)&0x3f], - ) - src = src[3:] - case 2: - out = append(out, - sha512CryptAlphabet[src[0]&0x3f], - sha512CryptAlphabet[((src[0]>>6)|(src[1]<<2))&0x3f], - sha512CryptAlphabet[(src[1]>>4)&0x3f], - ) - src = src[2:] - case 1: - out = append(out, - sha512CryptAlphabet[src[0]&0x3f], - sha512CryptAlphabet[(src[0]>>6)&0x3f], - ) - src = src[1:] - } - } - return out -} diff --git a/newt.REMOVED.git-id b/newt.REMOVED.git-id new file mode 100644 index 0000000..2233bac --- /dev/null +++ b/newt.REMOVED.git-id @@ -0,0 +1 @@ +34062da7bd1b98e4a7953d2f8b43860d2222add0 \ No newline at end of file