aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/git-lfs-authenticate/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/git-lfs-authenticate/main.go')
-rw-r--r--cmd/git-lfs-authenticate/main.go141
1 files changed, 0 insertions, 141 deletions
diff --git a/cmd/git-lfs-authenticate/main.go b/cmd/git-lfs-authenticate/main.go
deleted file mode 100644
index 59ed978..0000000
--- a/cmd/git-lfs-authenticate/main.go
+++ /dev/null
@@ -1,141 +0,0 @@
1package main
2
3import (
4 "bytes"
5 "crypto/hmac"
6 "crypto/sha256"
7 "encoding/binary"
8 "encoding/hex"
9 "encoding/json"
10 "errors"
11 "fmt"
12 "io"
13 "io/fs"
14 "os"
15 "path"
16 "strings"
17 "time"
18)
19
20func die(msg string, args ...any) {
21 fmt.Fprint(os.Stderr, "Fatal: ")
22 fmt.Fprintf(os.Stderr, msg, args...)
23 fmt.Fprint(os.Stderr, "\n")
24 os.Exit(1)
25}
26
27type authenticateResponse struct {
28 // When providing href, the Git LFS client will use href as the base URL
29 // instead of building the base URL using the Service Discovery mechanism.
30 // It should end with /info/lfs. See
31 // https://github.com/git-lfs/git-lfs/blob/baf40ac99850a62fe98515175d52df5c513463ec/docs/api/server-discovery.md#ssh
32 HRef string `json:"href,omitempty"`
33 Header map[string]string `json:"header"`
34 // In seconds.
35 ExpiresIn int64 `json:"expires_in,omitempty"`
36 // The expires_at (RFC3339) property could also be used, but we leave it
37 // out since we don't use it. The Git LFS docs recommend using expires_in
38 // instead (???)
39}
40
41func wipe(b []byte) {
42 for i := range b {
43 b[i] = 0
44 }
45}
46
47const usage = "Usage: git-lfs-authenticate <REPO> upload/download"
48
49func main() {
50 // Even though not explicitly described in the Git LFS documentation, the
51 // git-lfs-authenticate command is expected to either exit succesfully with
52 // exit code 0 and to then print credentials in the prescribed JSON format
53 // to standard out. On errors, the command should exit with a non-zero exit
54 // code and print the error message in plain text to standard error. See
55 // https://github.com/git-lfs/git-lfs/blob/baf40ac99850a62fe98515175d52df5c513463ec/lfshttp/ssh.go#L76-L117
56
57 if len(os.Args) != 3 {
58 fmt.Println(usage)
59 os.Exit(1)
60 }
61
62 repo := strings.TrimPrefix(path.Clean(os.Args[1]), "/")
63 operation := os.Args[2]
64 if operation != "download" && operation != "upload" {
65 fmt.Println(usage)
66 os.Exit(1)
67 }
68 if repo == ".." || strings.HasPrefix(repo, "../") {
69 die("highly illegal repo name (Anzeige ist raus)")
70 }
71 if !strings.HasSuffix(repo, ".git") {
72 die("expected repo name to have '.git' suffix")
73 }
74
75 repoDir := path.Join(repo)
76 finfo, err := os.Stat(repoDir)
77 if err != nil {
78 if errors.Is(err, fs.ErrNotExist) {
79 die("repo not found")
80 }
81 die("could not stat repo: %s", err)
82 }
83 if !finfo.IsDir() {
84 die("repo not found")
85 }
86
87 hrefBase := os.Getenv("GITOLFS3_HREF_BASE")
88 if hrefBase == "" {
89 die("incomplete configuration: base URL not provided")
90 }
91 if !strings.HasSuffix(hrefBase, "/") {
92 hrefBase += "/"
93 }
94
95 keyPath := os.Getenv("GITOLFS3_KEY_PATH")
96 if keyPath == "" {
97 die("incomplete configuration: key path not provided")
98 }
99
100 keyStr, err := os.ReadFile(keyPath)
101 if err != nil {
102 wipe(keyStr)
103 die("cannot read key")
104 }
105 keyStr = bytes.TrimSpace(keyStr)
106 defer wipe(keyStr)
107 if hex.DecodedLen(len(keyStr)) != 64 {
108 die("bad key length")
109 }
110 key := make([]byte, 64)
111 defer wipe(key)
112 if _, err = hex.Decode(key, keyStr); err != nil {
113 die("cannot decode key")
114 }
115
116 expiresIn := time.Minute * 5
117 expiresAtUnix := time.Now().Add(expiresIn).Unix()
118
119 tag := hmac.New(sha256.New, key)
120 io.WriteString(tag, "git-lfs-authenticate")
121 tag.Write([]byte{0})
122 io.WriteString(tag, repo)
123 tag.Write([]byte{0})
124 io.WriteString(tag, operation)
125 tag.Write([]byte{0})
126 binary.Write(tag, binary.BigEndian, &expiresAtUnix)
127 tagStr := hex.EncodeToString(tag.Sum(nil))
128
129 response := authenticateResponse{
130 Header: map[string]string{
131 "Authorization": "Gitolfs3-Hmac-Sha256 " + tagStr,
132 },
133 ExpiresIn: int64(expiresIn.Seconds()),
134 HRef: fmt.Sprintf("%s%s?p=1&te=%d",
135 hrefBase,
136 path.Join(repo, "/info/lfs"),
137 expiresAtUnix,
138 ),
139 }
140 json.NewEncoder(os.Stdout).Encode(response)
141}