From 6095ead99248963ae70091c5bb3399eed49c0826 Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Wed, 24 Jan 2024 18:58:58 +0100 Subject: Remove Go and C source The Rust implementation now implements all features I need --- cmd/git-lfs-authenticate/main.c | 253 --------------------------------------- cmd/git-lfs-authenticate/main.go | 141 ---------------------- 2 files changed, 394 deletions(-) delete mode 100644 cmd/git-lfs-authenticate/main.c delete mode 100644 cmd/git-lfs-authenticate/main.go (limited to 'cmd/git-lfs-authenticate') diff --git a/cmd/git-lfs-authenticate/main.c b/cmd/git-lfs-authenticate/main.c deleted file mode 100644 index a7ec031..0000000 --- a/cmd/git-lfs-authenticate/main.c +++ /dev/null @@ -1,253 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -void die(const char *format, ...) { - fputs("Fatal: ", stderr); - va_list ap; - va_start(ap, format); - vfprintf(stderr, format, ap); - va_end(ap); - fputc('\n', stderr); - exit(EXIT_FAILURE); -} - -#define USAGE "Usage: git-lfs-authenticate upload/download" - -bool hasprefix(const char *str, const char *prefix) { - if (strlen(prefix) > strlen(str)) - return false; - return !strncmp(str, prefix, strlen(prefix)); -} - -bool hassuffix(const char *str, const char *suffix) { - if (strlen(suffix) > strlen(str)) - return false; - str += strlen(str) - strlen(suffix); - return !strcmp(str, suffix); -} - -const char *trimspace(const char *str, size_t *length) { - while (*length > 0 && isspace(str[0])) { - str++; (*length)--; - } - while (*length > 0 && isspace(str[*length - 1])) - (*length)--; - return str; -} - -void printescjson(const char *str) { - for (size_t i = 0; i < strlen(str); i++) { - switch (str[i]) { - case '"': fputs("\\\"", stdout); break; - case '\\': fputs("\\\\", stdout); break; - case '\b': fputs("\\b", stdout); break; - case '\f': fputs("\\f", stdout); break; - case '\n': fputs("\\n", stdout); break; - case '\r': fputs("\\r", stdout); break; - case '\t': fputs("\\t", stdout); break; - default: fputc(str[i], stdout); - } - } -} - -void checkrepopath(const char *path) { - if (strstr(path, "//") || strstr(path, "/./") || strstr(path, "/../") - || hasprefix(path, "./") || hasprefix(path, "../") || hasprefix(path, "/../")) - die("Bad repository name: Is unresolved path"); - if (strlen(path) > 100) - die("Bad repository name: Longer than 100 characters"); - if (hasprefix(path, "/")) - die("Bad repository name: Unexpected absolute path"); - if (!hassuffix(path, ".git")) - die("Bad repository name: Expected '.git' repo path suffix"); - - struct stat statbuf; - if (stat(path, &statbuf)) { - if (errno == ENOENT) - die("Repo not found"); - die("Could not stat repo: %s", strerror(errno)); - } - if (!S_ISDIR(statbuf.st_mode)) { - die("Repo not found"); - } -} - -char *readkeyfile(const char *path, size_t *len) { - FILE *f = fopen(path, "r"); - if (!f) - die("Cannot read key file: %s", strerror(errno)); - *len = 0; - size_t bufcap = 4096; - char *buf = malloc(bufcap); - while (!feof(f) && !ferror(f)) { - if (*len + 4096 > bufcap) { - bufcap *= 2; - buf = realloc(buf, bufcap); - } - *len += fread(buf + *len, sizeof(char), 4096, f); - } - if (ferror(f) && !feof(f)) { - OPENSSL_cleanse(buf, *len); - die("Failed to read key file (length: %lu)", *len); - } - fclose(f); - return buf; -} - -#define KEYSIZE 64 - -void readkey(const char *path, uint8_t dest[KEYSIZE]) { - size_t keybuf_len = 0; - char *keybuf = readkeyfile(path, &keybuf_len); - - size_t keystr_len = keybuf_len; - const char *keystr = trimspace(keybuf, &keystr_len); - if (keystr_len != 2 * KEYSIZE) { - OPENSSL_cleanse(keybuf, keybuf_len); - die("Bad key length"); - } - - for (size_t i = 0; i < KEYSIZE; i++) { - const char c = keystr[i]; - uint8_t nibble = 0; - if (c >= '0' && c <= '9') - nibble = c - '0'; - else if (c >= 'a' && c <= 'f') - nibble = c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - nibble = c - 'A' + 10; - else { - OPENSSL_cleanse(keybuf, keybuf_len); - OPENSSL_cleanse(dest, KEYSIZE); - die("Cannot decode key"); - } - size_t ikey = i / 2; - if (i % 2) dest[ikey] |= nibble; - else dest[ikey] = nibble << 4; - } - - OPENSSL_cleanse(keybuf, keybuf_len); - free(keybuf); -} - -void u64tobe(uint64_t x, uint8_t b[8]) { - b[0] = (uint8_t)(x >> 56); - b[1] = (uint8_t)(x >> 48); - b[2] = (uint8_t)(x >> 40); - b[3] = (uint8_t)(x >> 32); - b[4] = (uint8_t)(x >> 24); - b[5] = (uint8_t)(x >> 16); - b[6] = (uint8_t)(x >> 8); - b[7] = (uint8_t)(x >> 0); -} - -void *memcat(void *dest, const void *src, size_t n) { - return memcpy(dest, src, n) + n; -} - -#define MAX_TAG_SIZE EVP_MAX_MD_SIZE - -typedef struct taginfo { - const char *authtype; - const char *repopath; - const char *operation; - const int64_t expiresat_s; -} taginfo_t; - -void maketag(const taginfo_t info, uint8_t key[KEYSIZE], uint8_t dest[MAX_TAG_SIZE], uint32_t *len) { - uint8_t expiresat_b[8]; - u64tobe(info.expiresat_s, expiresat_b); - - const uint8_t zero[1] = { 0 }; - const size_t fullsize = strlen(info.authtype) + - 1 + strlen(info.repopath) + - 1 + strlen(info.operation) + - 1 + sizeof(expiresat_b); - uint8_t *claimbuf = alloca(fullsize); - uint8_t *head = claimbuf; - head = memcat(head, info.authtype, strlen(info.authtype)); - head = memcat(head, zero, 1); - head = memcat(head, info.repopath, strlen(info.repopath)); - head = memcat(head, zero, 1); - head = memcat(head, info.operation, strlen(info.operation)); - head = memcat(head, zero, 1); - head = memcat(head, expiresat_b, sizeof(expiresat_b)); - assert(head == claimbuf + fullsize); - - memset(dest, 0, MAX_TAG_SIZE); - *len = 0; - if (!HMAC(EVP_sha256(), key, KEYSIZE, claimbuf, fullsize, dest, len)) { - OPENSSL_cleanse(key, KEYSIZE); - die("Failed to generate tag"); - } -} - -#define MAX_HEXTAG_STRLEN MAX_TAG_SIZE * 2 - -void makehextag(const taginfo_t info, uint8_t key[KEYSIZE], char dest[MAX_HEXTAG_STRLEN + 1]) { - uint8_t rawtag[MAX_TAG_SIZE]; - uint32_t rawtag_len; - maketag(info, key, rawtag, &rawtag_len); - - memset(dest, 0, MAX_HEXTAG_STRLEN + 1); - for (size_t i = 0; i < rawtag_len; i++) { - uint8_t b = rawtag[i]; - dest[i * 2] = (b >> 4) + ((b >> 4) < 10 ? '0' : 'a'); - dest[i*2 + 1] = (b & 0x0F) + ((b & 0x0F) < 10 ? '0' : 'a'); - } -} - -int main(int argc, char *argv[]) { - if (argc != 3) { - puts(USAGE); - exit(EXIT_FAILURE); - } - - const char *repopath = argv[1]; - const char *operation = argv[2]; - if (strcmp(operation, "download") && strcmp(operation, "upload")) { - puts(USAGE); - exit(EXIT_FAILURE); - } - checkrepopath(repopath); - - const char *hrefbase = getenv("GITOLFS3_HREF_BASE"); - const char *keypath = getenv("GITOLFS3_KEY_PATH"); - - if (!hrefbase || strlen(hrefbase) == 0) - die("Incomplete configuration: Base URL not provided"); - if (hrefbase[strlen(hrefbase) - 1] != '/') - die("Bad configuration: Base URL should end with slash"); - if (!keypath || strlen(keypath) == 0) - die("Incomplete configuration: Key path not provided"); - - uint8_t key[64]; - readkey(keypath, key); - - int64_t expiresin_s = 5 * 60; - int64_t expiresat_s = (int64_t)time(NULL) + expiresin_s; - - taginfo_t taginfo = { - .authtype = "git-lfs-authenticate", - .repopath = repopath, - .operation = operation, - .expiresat_s = expiresat_s, - }; - char hextag[MAX_HEXTAG_STRLEN + 1]; - makehextag(taginfo, key, hextag); - - printf("{\"header\":{\"Authorization\":\"Gitolfs3-Hmac-Sha256 %s\"}," - "\"expires_in\":%ld,\"href\":\"", hextag, expiresin_s); - printescjson(hrefbase); - printescjson(repopath); - printf("/info/lfs?p=1&te=%ld\"}\n", expiresat_s); -} 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 @@ -package main - -import ( - "bytes" - "crypto/hmac" - "crypto/sha256" - "encoding/binary" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "io/fs" - "os" - "path" - "strings" - "time" -) - -func die(msg string, args ...any) { - fmt.Fprint(os.Stderr, "Fatal: ") - fmt.Fprintf(os.Stderr, msg, args...) - fmt.Fprint(os.Stderr, "\n") - os.Exit(1) -} - -type authenticateResponse struct { - // When providing href, the Git LFS client will use href as the base URL - // instead of building the base URL using the Service Discovery mechanism. - // It should end with /info/lfs. See - // https://github.com/git-lfs/git-lfs/blob/baf40ac99850a62fe98515175d52df5c513463ec/docs/api/server-discovery.md#ssh - HRef string `json:"href,omitempty"` - Header map[string]string `json:"header"` - // In seconds. - ExpiresIn int64 `json:"expires_in,omitempty"` - // The expires_at (RFC3339) property could also be used, but we leave it - // out since we don't use it. The Git LFS docs recommend using expires_in - // instead (???) -} - -func wipe(b []byte) { - for i := range b { - b[i] = 0 - } -} - -const usage = "Usage: git-lfs-authenticate upload/download" - -func main() { - // Even though not explicitly described in the Git LFS documentation, the - // git-lfs-authenticate command is expected to either exit succesfully with - // exit code 0 and to then print credentials in the prescribed JSON format - // to standard out. On errors, the command should exit with a non-zero exit - // code and print the error message in plain text to standard error. See - // https://github.com/git-lfs/git-lfs/blob/baf40ac99850a62fe98515175d52df5c513463ec/lfshttp/ssh.go#L76-L117 - - if len(os.Args) != 3 { - fmt.Println(usage) - os.Exit(1) - } - - repo := strings.TrimPrefix(path.Clean(os.Args[1]), "/") - operation := os.Args[2] - if operation != "download" && operation != "upload" { - fmt.Println(usage) - os.Exit(1) - } - if repo == ".." || strings.HasPrefix(repo, "../") { - die("highly illegal repo name (Anzeige ist raus)") - } - if !strings.HasSuffix(repo, ".git") { - die("expected repo name to have '.git' suffix") - } - - repoDir := path.Join(repo) - finfo, err := os.Stat(repoDir) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - die("repo not found") - } - die("could not stat repo: %s", err) - } - if !finfo.IsDir() { - die("repo not found") - } - - hrefBase := os.Getenv("GITOLFS3_HREF_BASE") - if hrefBase == "" { - die("incomplete configuration: base URL not provided") - } - if !strings.HasSuffix(hrefBase, "/") { - hrefBase += "/" - } - - keyPath := os.Getenv("GITOLFS3_KEY_PATH") - if keyPath == "" { - die("incomplete configuration: key path not provided") - } - - keyStr, err := os.ReadFile(keyPath) - if err != nil { - wipe(keyStr) - die("cannot read key") - } - keyStr = bytes.TrimSpace(keyStr) - defer wipe(keyStr) - if hex.DecodedLen(len(keyStr)) != 64 { - die("bad key length") - } - key := make([]byte, 64) - defer wipe(key) - if _, err = hex.Decode(key, keyStr); err != nil { - die("cannot decode key") - } - - expiresIn := time.Minute * 5 - expiresAtUnix := time.Now().Add(expiresIn).Unix() - - tag := hmac.New(sha256.New, key) - io.WriteString(tag, "git-lfs-authenticate") - tag.Write([]byte{0}) - io.WriteString(tag, repo) - tag.Write([]byte{0}) - io.WriteString(tag, operation) - tag.Write([]byte{0}) - binary.Write(tag, binary.BigEndian, &expiresAtUnix) - tagStr := hex.EncodeToString(tag.Sum(nil)) - - response := authenticateResponse{ - Header: map[string]string{ - "Authorization": "Gitolfs3-Hmac-Sha256 " + tagStr, - }, - ExpiresIn: int64(expiresIn.Seconds()), - HRef: fmt.Sprintf("%s%s?p=1&te=%d", - hrefBase, - path.Join(repo, "/info/lfs"), - expiresAtUnix, - ), - } - json.NewEncoder(os.Stdout).Encode(response) -} -- cgit v1.2.3