133728e barerepo 1mo
1
// Package auth runs the same `ssh-keygen -Y verify` the user can run, hiding nothing.
133728e barerepo 1mo
2
package auth
133728e barerepo 1mo
4
import (
133728e barerepo 1mo
5
"context"
133728e barerepo 1mo
6
"crypto/rand"
133728e barerepo 1mo
7
"encoding/base64"
133728e barerepo 1mo
8
"errors"
133728e barerepo 1mo
9
"os"
133728e barerepo 1mo
10
"os/exec"
133728e barerepo 1mo
11
"path/filepath"
133728e barerepo 1mo
12
"strings"
133728e barerepo 1mo
14
"github.com/barerepo/server/internal/gitx"
133728e barerepo 1mo
15
"github.com/barerepo/server/internal/store"
133728e barerepo 1mo
18
// Namespace keeps a signature made here from working elsewhere, and the reverse. 31.3.
133728e barerepo 1mo
19
const Namespace = "barerepo-auth"
133728e barerepo 1mo
21
// SignupNamespace is separate, so a captured sign-in cannot claim an account.
133728e barerepo 1mo
22
const SignupNamespace = "barerepo-signup"
133728e barerepo 1mo
24
// NewNonce is 32 random bytes, encoded so a person can paste it. Chapter 10 step 2.
133728e barerepo 1mo
25
func NewNonce() (string, error) {
133728e barerepo 1mo
26
buf := make([]byte, 32)
133728e barerepo 1mo
27
if _, err := rand.Read(buf); err != nil {
133728e barerepo 1mo
28
return "", err
133728e barerepo 1mo
30
return base64.RawURLEncoding.EncodeToString(buf), nil
133728e barerepo 1mo
33
var errBadSignature = errors.New("that signature does not match any key on this account")
133728e barerepo 1mo
35
// Verify tries every key one at a time, because chapter 32.5 needs to know which one answered.
133728e barerepo 1mo
36
func Verify(ctx context.Context, account, nonce, signature string, keys []store.PubKey) (*store.PubKey, error) {
133728e barerepo 1mo
37
return verify(ctx, Namespace, account, nonce, signature, keys)
133728e barerepo 1mo
40
// VerifySignup stops one person claiming an account with another's published public key.
133728e barerepo 1mo
41
func VerifySignup(ctx context.Context, name, nonce, signature, pubkey string) error {
133728e barerepo 1mo
42
algo, blob, comment, fp, err := store.ParsePubKey(pubkey)
133728e barerepo 1mo
43
if err != nil {
133728e barerepo 1mo
44
return err
133728e barerepo 1mo
46
key := store.PubKey{Algo: algo, Blob: blob, Comment: comment, Fingerprint: fp}
133728e barerepo 1mo
47
if _, err := verify(ctx, SignupNamespace, name, nonce, signature, []store.PubKey{key}); err != nil {
133728e barerepo 1mo
48
if errors.Is(err, errBadSignature) {
133728e barerepo 1mo
49
return errors.New("that signature was not made by the key you pasted, with -n barerepo-signup")
133728e barerepo 1mo
51
return err
133728e barerepo 1mo
53
return nil
133728e barerepo 1mo
56
func verify(ctx context.Context, namespace, account, nonce, signature string, keys []store.PubKey) (*store.PubKey, error) {
133728e barerepo 1mo
57
if len(keys) == 0 || !gitx.ValidName(account) {
133728e barerepo 1mo
58
return nil, errBadSignature
133728e barerepo 1mo
60
if !strings.Contains(signature, "BEGIN SSH SIGNATURE") {
133728e barerepo 1mo
61
return nil, errors.New("that is not an ssh signature. it starts with -----BEGIN SSH SIGNATURE-----")
133728e barerepo 1mo
64
dir, err := os.MkdirTemp("", "barerepo-auth-")
133728e barerepo 1mo
65
if err != nil {
133728e barerepo 1mo
66
return nil, err
133728e barerepo 1mo
68
defer os.RemoveAll(dir)
133728e barerepo 1mo
70
sigPath := filepath.Join(dir, "nonce.sig")
133728e barerepo 1mo
71
if err := os.WriteFile(sigPath, []byte(signature), 0o600); err != nil {
133728e barerepo 1mo
72
return nil, err
133728e barerepo 1mo
74
signersPath := filepath.Join(dir, "allowed_signers")
133728e barerepo 1mo
76
for i := range keys {
133728e barerepo 1mo
77
k := keys[i]
133728e barerepo 1mo
78
line := account + " " + k.Algo + " " + k.Blob + "\n"
133728e barerepo 1mo
79
if err := os.WriteFile(signersPath, []byte(line), 0o600); err != nil {
133728e barerepo 1mo
80
return nil, err
133728e barerepo 1mo
82
cmd := exec.CommandContext(ctx, "ssh-keygen",
133728e barerepo 1mo
83
"-Y", "verify",
133728e barerepo 1mo
84
"-f", signersPath,
133728e barerepo 1mo
85
"-I", account,
133728e barerepo 1mo
86
"-n", namespace,
133728e barerepo 1mo
87
"-s", sigPath)
133728e barerepo 1mo
88
cmd.Stdin = strings.NewReader(nonce)
133728e barerepo 1mo
89
cmd.Env = []string{"PATH=/usr/local/bin:/usr/bin:/bin", "HOME=" + dir, "LC_ALL=C"}
133728e barerepo 1mo
90
if out, err := cmd.CombinedOutput(); err == nil {
133728e barerepo 1mo
91
return &k, nil
133728e barerepo 1mo
92
} else {
133728e barerepo 1mo
93
_ = out // ssh-keygen's wording is not ours to show
133728e barerepo 1mo
96
return nil, errBadSignature