From e57ff6c391f9a61c1784c98983dca411f7c6db61 Mon Sep 17 00:00:00 2001 From: lhchavez Date: Sun, 6 Dec 2020 11:55:04 -0800 Subject: [PATCH] Add `NewCredentialSSHKeyFromSigner` (#706) This change adds `NewCredentialSSHKeyFromSigner`, which allows idiomatic use of SSH keys from Go. This also lets us spin off an SSH server in the tests. (cherry picked from commit abf02bc7d79dfb7b0bbcd404ebecb202cff2a18e) --- .github/workflows/ci.yml | 5 + .travis.yml | 1 + credentials.go | 62 ++++ go.mod | 6 +- go.sum | 12 +- remote_test.go | 276 ++++++++++++++++++ script/build-libgit2.sh | 12 +- testdata/TestGitRepository.git/HEAD | 1 + testdata/TestGitRepository.git/config | 6 + testdata/TestGitRepository.git/description | 1 + testdata/TestGitRepository.git/info/exclude | 6 + testdata/TestGitRepository.git/info/refs | 8 + .../objects/info/commit-graph | Bin 0 -> 2296 bytes .../TestGitRepository.git/objects/info/packs | 2 + ...4e169a0858c13d9ae781a91d76fc33769b8.bitmap | Bin 0 -> 1334 bytes ...ace4e169a0858c13d9ae781a91d76fc33769b8.idx | Bin 0 -> 3032 bytes ...ce4e169a0858c13d9ae781a91d76fc33769b8.pack | Bin 0 -> 6072 bytes testdata/TestGitRepository.git/packed-refs | 9 + .../TestGitRepository.git/refs/heads/master | 1 + wrapper.c | 23 ++ 20 files changed, 421 insertions(+), 10 deletions(-) create mode 100644 testdata/TestGitRepository.git/HEAD create mode 100644 testdata/TestGitRepository.git/config create mode 100644 testdata/TestGitRepository.git/description create mode 100644 testdata/TestGitRepository.git/info/exclude create mode 100644 testdata/TestGitRepository.git/info/refs create mode 100644 testdata/TestGitRepository.git/objects/info/commit-graph create mode 100644 testdata/TestGitRepository.git/objects/info/packs create mode 100644 testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.bitmap create mode 100644 testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.idx create mode 100644 testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack create mode 100644 testdata/TestGitRepository.git/packed-refs create mode 100644 testdata/TestGitRepository.git/refs/heads/master diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b68d52..bfbc303 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: GOPATH: /home/runner/work/git2go run: | git submodule update --init + sudo apt-get install -y --no-install-recommends libssh2-1-dev make build-libgit2-static go get -tags static -t github.com/${{ github.repository }}/... go build -tags static github.com/${{ github.repository }}/... @@ -62,6 +63,7 @@ jobs: - name: Build run: | git submodule update --init + sudo apt-get install -y --no-install-recommends libssh2-1-dev make build-libgit2-static - name: Test run: make TEST_ARGS=-test.v test-static @@ -84,6 +86,7 @@ jobs: - name: Build run: | git submodule update --init + sudo apt-get install -y --no-install-recommends libssh2-1-dev make build-libgit2-dynamic - name: Test run: make TEST_ARGS=-test.v test-dynamic @@ -108,6 +111,7 @@ jobs: - name: Build libgit2 ${{ matrix.libgit2 }} run: | git submodule update --init + sudo apt-get install -y --no-install-recommends libssh2-1-dev sudo env BUILD_LIBGIT_REF=v${{ matrix.libgit2 }} ./script/build-libgit2.sh --dynamic --system - name: Test run: make TEST_ARGS=-test.v test @@ -130,6 +134,7 @@ jobs: - name: Build libgit2 run: | git submodule update --init + sudo apt-get install -y --no-install-recommends libssh2-1-dev sudo ./script/build-libgit2.sh --static --system - name: Test run: go test --count=1 --tags "static,system_libgit2" ./... diff --git a/.travis.yml b/.travis.yml index 1717c8e..0b7f482 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ go: - tip install: + - sudo apt-get install -y --no-install-recommends libssh2-1-dev - make build-libgit2-static - go get --tags "static" ./... diff --git a/credentials.go b/credentials.go index b1051b9..deb399a 100644 --- a/credentials.go +++ b/credentials.go @@ -3,15 +3,20 @@ package git /* #include #include +#include git_credential_t _go_git_credential_credtype(git_credential *cred); +void _go_git_populate_credential_ssh_custom(git_credential_ssh_custom *cred); */ import "C" import ( + "crypto/rand" "fmt" "runtime" "strings" "unsafe" + + "golang.org/x/crypto/ssh" ) // CredentialType is a bitmask of supported credential types. @@ -192,6 +197,63 @@ func NewCredentialSSHKeyFromAgent(username string) (*Credential, error) { return cred, nil } +type credentialSSHCustomData struct { + signer ssh.Signer +} + +//export credentialSSHCustomFree +func credentialSSHCustomFree(cred *C.git_credential_ssh_custom) { + if cred == nil { + return + } + + C.free(unsafe.Pointer(cred.username)) + C.free(unsafe.Pointer(cred.publickey)) + pointerHandles.Untrack(cred.payload) + C.free(unsafe.Pointer(cred)) +} + +//export credentialSSHSignCallback +func credentialSSHSignCallback( + errorMessage **C.char, + sig **C.uchar, + sig_len *C.size_t, + data *C.uchar, + data_len C.size_t, + handle unsafe.Pointer, +) C.int { + signer := pointerHandles.Get(handle).(*credentialSSHCustomData).signer + signature, err := signer.Sign(rand.Reader, C.GoBytes(unsafe.Pointer(data), C.int(data_len))) + if err != nil { + return setCallbackError(errorMessage, err) + } + *sig = (*C.uchar)(C.CBytes(signature.Blob)) + *sig_len = C.size_t(len(signature.Blob)) + return C.int(ErrorCodeOK) +} + +// NewCredentialSSHKeyFromSigner creates new SSH credentials using the provided signer. +func NewCredentialSSHKeyFromSigner(username string, signer ssh.Signer) (*Credential, error) { + publicKey := signer.PublicKey().Marshal() + + ccred := (*C.git_credential_ssh_custom)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_credential_ssh_custom{})))) + ccred.parent.credtype = C.GIT_CREDENTIAL_SSH_CUSTOM + ccred.username = C.CString(username) + ccred.publickey = (*C.char)(C.CBytes(publicKey)) + ccred.publickey_len = C.size_t(len(publicKey)) + C._go_git_populate_credential_ssh_custom(ccred) + + data := credentialSSHCustomData{ + signer: signer, + } + ccred.payload = pointerHandles.Track(&data) + + cred := newCredential() + cred.ptr = &ccred.parent + + return cred, nil +} + func NewCredentialDefault() (*Credential, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() diff --git a/go.mod b/go.mod index c190305..458b8ca 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,8 @@ module github.com/libgit2/git2go/v30 go 1.13 -require golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de +require ( + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 + golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c + golang.org/x/sys v0.0.0-20201204225414-ed752295db88 // indirect +) diff --git a/go.sum b/go.sum index 1769e6b..35ff116 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,13 @@ +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/remote_test.go b/remote_test.go index 4cc3298..b97d764 100644 --- a/remote_test.go +++ b/remote_test.go @@ -1,8 +1,21 @@ package git import ( + "bytes" + "crypto/rand" + "crypto/rsa" "fmt" + "io" + "net" + "os" + "os/exec" + "strings" + "sync" "testing" + "time" + + "github.com/google/shlex" + "golang.org/x/crypto/ssh" ) func TestListRemotes(t *testing.T) { @@ -184,3 +197,266 @@ func TestRemotePrune(t *testing.T) { t.Fatal("Expected error getting a pruned reference") } } + +func newChannelPipe(t *testing.T, w io.Writer, wg *sync.WaitGroup) (*os.File, error) { + pr, pw, err := os.Pipe() + if err != nil { + return nil, err + } + + wg.Add(1) + go func() { + _, err := io.Copy(w, pr) + if err != nil && err != io.EOF { + t.Logf("Failed to copy: %v", err) + } + wg.Done() + }() + + return pw, nil +} + +func startSSHServer(t *testing.T, hostKey ssh.Signer, authorizedKeys []ssh.PublicKey) net.Listener { + t.Helper() + + marshaledAuthorizedKeys := make([][]byte, len(authorizedKeys)) + for i, authorizedKey := range authorizedKeys { + marshaledAuthorizedKeys[i] = authorizedKey.Marshal() + } + + config := &ssh.ServerConfig{ + PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { + marshaledPubKey := pubKey.Marshal() + for _, marshaledAuthorizedKey := range marshaledAuthorizedKeys { + if bytes.Equal(marshaledPubKey, marshaledAuthorizedKey) { + return &ssh.Permissions{ + // Record the public key used for authentication. + Extensions: map[string]string{ + "pubkey-fp": ssh.FingerprintSHA256(pubKey), + }, + }, nil + } + } + t.Logf("unknown public key for %q:\n\t%+v\n\t%+v\n", c.User(), pubKey.Marshal(), authorizedKeys) + return nil, fmt.Errorf("unknown public key for %q", c.User()) + }, + } + config.AddHostKey(hostKey) + + listener, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("Failed to listen for connection: %v", err) + } + + go func() { + nConn, err := listener.Accept() + if err != nil { + if strings.Contains(err.Error(), "use of closed network connection") { + return + } + t.Logf("Failed to accept incoming connection: %v", err) + return + } + defer nConn.Close() + + conn, chans, reqs, err := ssh.NewServerConn(nConn, config) + if err != nil { + t.Logf("failed to handshake: %+v, %+v", conn, err) + return + } + + // The incoming Request channel must be serviced. + go func() { + for newRequest := range reqs { + t.Logf("new request %v", newRequest) + } + }() + + // Service only the first channel request + newChannel := <-chans + defer func() { + for newChannel := range chans { + t.Logf("new channel %v", newChannel) + newChannel.Reject(ssh.UnknownChannelType, "server closing") + } + }() + + // Channels have a type, depending on the application level + // protocol intended. In the case of a shell, the type is + // "session" and ServerShell may be used to present a simple + // terminal interface. + if newChannel.ChannelType() != "session" { + newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") + return + } + channel, requests, err := newChannel.Accept() + if err != nil { + t.Logf("Could not accept channel: %v", err) + return + } + defer channel.Close() + + // Sessions have out-of-band requests such as "shell", + // "pty-req" and "env". Here we handle only the + // "exec" request. + req := <-requests + if req.Type != "exec" { + req.Reply(false, nil) + return + } + // RFC 4254 Section 6.5. + var payload struct { + Command string + } + if err := ssh.Unmarshal(req.Payload, &payload); err != nil { + t.Logf("invalid payload on channel %v: %v", channel, err) + req.Reply(false, nil) + return + } + args, err := shlex.Split(payload.Command) + if err != nil { + t.Logf("invalid command on channel %v: %v", channel, err) + req.Reply(false, nil) + return + } + if len(args) < 2 || (args[0] != "git-upload-pack" && args[0] != "git-receive-pack") { + t.Logf("invalid command (%v) on channel %v: %v", args, channel, err) + req.Reply(false, nil) + return + } + req.Reply(true, nil) + + go func(in <-chan *ssh.Request) { + for req := range in { + t.Logf("draining request %v", req) + } + }(requests) + + // The first parameter is the (absolute) path of the repository. + args[1] = "./testdata" + args[1] + + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdin = channel + var wg sync.WaitGroup + stdoutPipe, err := newChannelPipe(t, channel, &wg) + if err != nil { + t.Logf("Failed to create stdout pipe: %v", err) + return + } + cmd.Stdout = stdoutPipe + stderrPipe, err := newChannelPipe(t, channel.Stderr(), &wg) + if err != nil { + t.Logf("Failed to create stderr pipe: %v", err) + return + } + cmd.Stderr = stderrPipe + + go func() { + wg.Wait() + channel.CloseWrite() + }() + + err = cmd.Start() + if err != nil { + t.Logf("Failed to start %v: %v", args, err) + return + } + + // Once the process has started, we need to close the write end of the + // pipes from this process so that we can know when the child has done + // writing to it. + stdoutPipe.Close() + stderrPipe.Close() + + timer := time.AfterFunc(5*time.Second, func() { + t.Log("process timed out, terminating") + cmd.Process.Kill() + }) + defer timer.Stop() + + err = cmd.Wait() + if err != nil { + t.Logf("Failed to run %v: %v", args, err) + return + } + }() + return listener +} + +func TestRemoteSSH(t *testing.T) { + t.Parallel() + pubKeyUsername := "testuser" + + hostPrivKey, err := rsa.GenerateKey(rand.Reader, 512) + if err != nil { + t.Fatalf("Failed to generate the host RSA private key: %v", err) + } + hostSigner, err := ssh.NewSignerFromKey(hostPrivKey) + if err != nil { + t.Fatalf("Failed to generate SSH hostSigner: %v", err) + } + + privKey, err := rsa.GenerateKey(rand.Reader, 512) + if err != nil { + t.Fatalf("Failed to generate the user RSA private key: %v", err) + } + signer, err := ssh.NewSignerFromKey(privKey) + if err != nil { + t.Fatalf("Failed to generate SSH signer: %v", err) + } + // This is in the format "xx:xx:xx:...", so we remove the colons so that it + // matches the fmt.Sprintf() below. + // Note that not all libssh2 implementations support the SHA256 fingerprint, + // so we use MD5 here for testing. + publicKeyFingerprint := strings.Replace(ssh.FingerprintLegacyMD5(hostSigner.PublicKey()), ":", "", -1) + + listener := startSSHServer(t, hostSigner, []ssh.PublicKey{signer.PublicKey()}) + defer listener.Close() + + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + certificateCheckCallbackCalled := false + fetchOpts := FetchOptions{ + RemoteCallbacks: RemoteCallbacks{ + CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) ErrorCode { + hostkeyFingerprint := fmt.Sprintf("%x", cert.Hostkey.HashMD5[:]) + if hostkeyFingerprint != publicKeyFingerprint { + t.Logf("server hostkey %q, want %q", hostkeyFingerprint, publicKeyFingerprint) + return ErrorCodeAuth + } + certificateCheckCallbackCalled = true + return ErrorCodeOK + }, + CredentialsCallback: func(url, username string, allowedTypes CredentialType) (*Credential, error) { + if allowedTypes&(CredentialTypeSSHKey|CredentialTypeSSHCustom|CredentialTypeSSHMemory) != 0 { + return NewCredentialSSHKeyFromSigner(pubKeyUsername, signer) + } + if (allowedTypes & CredentialTypeUsername) != 0 { + return NewCredentialUsername(pubKeyUsername) + } + return nil, fmt.Errorf("unknown credential type %+v", allowedTypes) + }, + }, + } + + remote, err := repo.Remotes.Create( + "origin", + fmt.Sprintf("ssh://%s/TestGitRepository", listener.Addr().String()), + ) + checkFatal(t, err) + defer remote.Free() + + err = remote.Fetch(nil, &fetchOpts, "") + checkFatal(t, err) + if !certificateCheckCallbackCalled { + t.Fatalf("CertificateCheckCallback was not called") + } + + heads, err := remote.Ls() + checkFatal(t, err) + + if len(heads) == 0 { + t.Error("Expected remote heads") + } +} diff --git a/script/build-libgit2.sh b/script/build-libgit2.sh index 1d03220..ad5645a 100755 --- a/script/build-libgit2.sh +++ b/script/build-libgit2.sh @@ -68,11 +68,11 @@ cmake -DTHREADSAFE=ON \ -DCMAKE_BUILD_TYPE="RelWithDebInfo" \ -DCMAKE_INSTALL_PREFIX="${BUILD_INSTALL_PREFIX}" \ -DCMAKE_INSTALL_LIBDIR="lib" \ - "${VENDORED_PATH}" && + "${VENDORED_PATH}" -if which gmake nproc >/dev/null && [ -f Makefile ]; then - # Make the build parallel if gmake is available and cmake used Makefiles. - exec gmake "-j$(nproc --all)" install +if which make nproc >/dev/null && [ -f Makefile ]; then + # Make the build parallel if make is available and cmake used Makefiles. + exec make "-j$(nproc --all)" install +else + exec cmake --build . --target install fi - -exec cmake --build . --target install diff --git a/testdata/TestGitRepository.git/HEAD b/testdata/TestGitRepository.git/HEAD new file mode 100644 index 0000000..cb089cd --- /dev/null +++ b/testdata/TestGitRepository.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/testdata/TestGitRepository.git/config b/testdata/TestGitRepository.git/config new file mode 100644 index 0000000..44b9489 --- /dev/null +++ b/testdata/TestGitRepository.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true +[remote "origin"] + url = https://github.com/libgit2/TestGitRepository diff --git a/testdata/TestGitRepository.git/description b/testdata/TestGitRepository.git/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/testdata/TestGitRepository.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/testdata/TestGitRepository.git/info/exclude b/testdata/TestGitRepository.git/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/testdata/TestGitRepository.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/testdata/TestGitRepository.git/info/refs b/testdata/TestGitRepository.git/info/refs new file mode 100644 index 0000000..e10a563 --- /dev/null +++ b/testdata/TestGitRepository.git/info/refs @@ -0,0 +1,8 @@ +0966a434eb1a025db6b71485ab63a3bfbea520b6 refs/heads/first-merge +49322bb17d3acc9146f98c97d078513228bbf3c0 refs/heads/master +42e4e7c5e507e113ebbb7801b16b52cf867b7ce1 refs/heads/no-parent +d96c4e80345534eccee5ac7b07fc7603b56124cb refs/tags/annotated_tag +c070ad8c08840c8116da865b2d65593a6bb9cd2a refs/tags/annotated_tag^{} +55a1a760df4b86a02094a904dfa511deb5655905 refs/tags/blob +8f50ba15d49353813cc6e20298002c0d17b0a9ee refs/tags/commit_tree +6e0c7bdb9b4ed93212491ee778ca1c65047cab4e refs/tags/nearly-dangling diff --git a/testdata/TestGitRepository.git/objects/info/commit-graph b/testdata/TestGitRepository.git/objects/info/commit-graph new file mode 100644 index 0000000000000000000000000000000000000000..013e2f092cc416b5682fd65b574111cb1edf3f18 GIT binary patch literal 2296 zcmZ>E5Aa}QWMT04ba7*V02d(J2f}1=advSGfv{O$xVpHzLf9O4AT)^Nc!FT#WibL} z31KFn8hT)6pgpv}EI@N{VOF3VxtI;8mz@O60aS|*a{}eLfS8*Kmc&Y)^=t*_~c)ud(^yWQkO7?h`sDlTEjOpU$rHUGvE2-JBwCw%t4F^yK-`r|b`f zU+=D9+?XA7zOB0Ep{J4d##*a06WxCHOutYOXr!_G^MQzcZjrmg1*g1qO}gN1I#*3G zKPWGw0_6xwyB?bujlSO^!j_~gm*uSImFG1x#lg*TyP{$q*NgEG5)U}En4mePFbgR}J6 zlZXE5i0{x4v3~Z>e_z9zx;mGG9dWdpKHn8pUq4MeC+UT4nD%t93!pYsVU+?9~R3mxV8|EUh@YaDB&R`KW{| zY%dHigzZ?PYH?LmfJgV!jV!ITi@rWT^X;QV!~UJC9sIgFQodVHO_w)H6T6nJ_1pUX z+1=*^xm|LaS6)~!=h;Q2NrrxxZ$A9SXZddfFkV4rEvQTqa0=a+xo@_sv2_}w^*xzl zk@rXWSKr(`Cl@HkUjQV4K*cF^^WWYqeXS3hcIF%LUen)pb7KA_&)SY?Gsf#);ow0j|E7hqv)8Y2D}_yKe?a9;ycD<_%lpRabp2e!gHzW@Ye{wtERrDgK z5BJ|{R;#ZE%CSMs0fzIYi*0H##v5mAKbO8+-Lo@7`BRB4^9rCEP#pkry9_W4TX(kC zH-9;_i^ulHZ~g3emIbRmwoU=cu|mxO#>FOCg%xX`msxtsE!dk@{WJE(>d#9|P5{*) zh2cisPpeK(&S=O@Wm;gh@)n25m95*bs0LJKLEW<{@~rpyL$%9vvRincGwhQ|SpIBg&{B{*)EuCjw`^?styfW8I=keE zfWiW2`Hx>FW-bQofTo84pcXoeBUlH1|0G literal 0 HcmV?d00001 diff --git a/testdata/TestGitRepository.git/objects/info/packs b/testdata/TestGitRepository.git/objects/info/packs new file mode 100644 index 0000000..d876b38 --- /dev/null +++ b/testdata/TestGitRepository.git/objects/info/packs @@ -0,0 +1,2 @@ +P pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack + diff --git a/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.bitmap b/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.bitmap new file mode 100644 index 0000000000000000000000000000000000000000..df3442c48506365faca4f279cb15d53ba68897f1 GIT binary patch literal 1334 zcma))J4*vW5P;`Qd=&8kQSgBhQ4u>+NC-p|mBa_y`2(UULaJCvP{A`n5R8S`ofd+H zjaa47PO!1CR_qi6+hApJWSzO)Iiq`m1G6(b`|Zr^V`gDx84PFyfUbwjx7Xa+UOD)D zRp>o>%HKtD*BI|XZBU8^z!2@iaR8!d-8Dl9QG19X761^2kSQ_Z`B5i0E^Gx!6Ms#vVRFO>lCMR_=YOX_LN!0`hDQ`q4 z$ILVsMUCv!N%|o^+a%wNLh(wQY9gF$gV0~Lu;kvv8`kUthpG@<+sZah(b)gaJ)YDAK$4}zE4=F z#CzqjlHX52JzA;C4~}9i+$Yc3(w_P}6z4PS7f)}NEAsq&Zd!l5*w_wMH{s+UlV0?1 K9iEro+r9wqU5lUq literal 0 HcmV?d00001 diff --git a/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.idx b/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.idx new file mode 100644 index 0000000000000000000000000000000000000000..aff5c2e53fad98138238cb6c093bfecc501336fe GIT binary patch literal 3032 zcmb`Jc{G&m8^<5}zBSf}i9$@)N#T{GXj0ZA*+QXg!)wW8RMs(M$rfeOkdiej8Wd$I zLQVFiLiQ46O-jGZIdx8_^Xu*HocB5BbMEWDuJ7}G?&q)j`_-!1sp%ieJG5 zGv;5w3jHH}wA>ywLvk@Pp5E|0R{T+2tqCN3xuIB z@(oc~*AV*+;xLy0B>w}Xz+(+GAOpyLLk`w0|9}F_)=>Ng2G*ZZg5IB?40;tn^*5-) zd=2a$unn{tKd~M5YwQ5DeuFm5f8;ONe@6%E@9czH_a}D2UJuy)FVP3x8hieby`cXY z1L*xDjFoEb&Y5NiW38}RvWHJ#e!$l}4%Zy)+x3k0<#z=m3yxi_slJ9T=a_jsdMP|r zzL3~=g^GTW$RI}w;nWHA-FMD|-@9BMRo~Smml&5A%Fj&iE|gtaR58uQHql}X*sR3b z`TGv1o<6#6tdX2z^D_+`WN z0y$r|#wJMbk?`^ql4*IOe%riXrPhWewGPTeom6+{sTKF()s(FdC>R1(pMGFg5`Cj+ z{G=vMl6v2jxNJXBFqeaElnoF~=y4---aF_k;C61KXtDOj02*&4!1r{%LI$nxDVgqjRr+k0yk_=ln&TX)xvE(=sbMwr z)l%rl(Scz9)3#CJH$`j0YgT6BYVU=q%!jKCve;*GHDu=oiY~rP-b4#4rAN3% zD^u&-`1J+pHA%4QVd5J1S>zlxM`P5U)I-G$G19YteZ( zp}p|TnLQ85M^+3KI`Kq#=BJRizwqoSAFl2AP#DE z#$I)8R|f4@5`Ka+4SWT?w<%r^e2QY(Be+9_`$)E1U97b{Dq0mD5;G_1rczTypX$oF zb0SMk?5ooshMi>_60Bu0eBthU;V{`Jr>q>)^yubU!aYMAPtDFS%vc2tx~H6reFC3drSocIIZxo5<(^|&h(WiO1kEPfCTsi`Q~WgJ6x?)5Yd!CGRc zo{kn0**>3SDs$S@+{YK+yFNjgkCmuBAS-KcmlOLDGxC+ox0xfQe<`m|mh-KF#AUK# z*;vh2yA6E2t)@ceceEr?JxI(w)!F>!3>F=&^+N~vw%}clS|()(Q+oKY}rXHOS#lZTaWzFSUpVPX__x_y2ZSAv8VS5rpsGV ztt3@1#Vf&B1Q%q{#3ZwPtgN@|&GUlQ{n5N>_yyUJ{;oFO+HeZGol*Mw7ZS}pXL#fW zOSG7l5kEgY9s}CXTMw;!o`SQad z7BO@|RvZ3>&zmhb##YzgqA+@mQk)zg?6c>U(!2%vhaYkiXC*|YAc%QA5bfZO7BPXS zx`-gc6Ht2rN1%obC;&N=>nubqxL<{%5QGKlwamx|D_aAUw;`HR5Jb=zylf!CLB?So z18>MBJmAR@gCOj1_cKB^V1;}uxDmV)0mxRUXwdHiJ!B&G8JJyy@0r4E7rg%v{5!zo z34C`6`sKhX==(tj{@jq^)&+p}6!>%^2va=#R}!4$=v;CzrT zIig L^{l)X(6IO)otvu# literal 0 HcmV?d00001 diff --git a/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack b/testdata/TestGitRepository.git/objects/pack/pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack new file mode 100644 index 0000000000000000000000000000000000000000..a3a120687a9a123eb9cd9504086363461a7064cd GIT binary patch literal 6072 zcmbVQcRZY1w|)mhlpr#?jB*U38!Z@R7+pjQq7%IZ5haP1M4}UYbb>^SPKe%n^xiws zLK33HhunKk?mao*mwW%1-|yY;-tXGaUTZz;S$kBFQi=coV4VGA!b#l{61$MfP22?F zUXNQZ^#xR#9p@QIBpm=32VlaHf^nQODx~St8v8QFMorE1>5=&|OH&<7>58&kw7Pl} zl zYiH-ugt;i!2SUIE6AQIZp>S!SkJIqT{xnwkUYSQ+M6j0VSm@q5ZNm2Sr}0EV@e|O~ zMkD*UD`&?GQnbe8Ax7;BOK(?o^l}7y5=%OHZ_Hzs=x|Jgp?M^+X=fIExn^&ZhCbx4 zWKCZ#4FT)xVi{v^Z)l=}4Ncb3T_55e>!RyJ40ZMQ53=+*o7mDs6xKIY zN0YQ*FSp=TVoUJr>DhnFkcLs>`5*c}*8>F0$&cnR4BZmmx4OkRN^-rLYZ7BqmAVVo z$Gne;$@w-cs4ETPi#kC`aRLPhT;&T_1|qG`GDUh{$k(OVIxM5mOzQe3sM3bW=(>|d ziahp$54097&82b>_+g>CZwe&OHqfx9hrmWLz6vv8NzhP9*YwXDf=^lR+ey%wN*l1h zFwKlZJ)<@=6Nw8`N3dlti5u!Lb?CA3bA=Z;D$8bOPU79MO=_IKnfZ`5rn-^(aYO3@ zV&t2ccyG?>;FqtP#hV}TpAo;I2_(41;x~XC`AG3S)cN85_ob9-dl#RGq}j^gYqp1A z`yAECq|0Zi5IAwUftvt0<&`E&x$$6qm_(8rliNG-28O?wMh3}(y5{V*b7f8rTI+7` zH3rrzScA6oUntU|29Z~~bKCrjbWKaM9!7}{-iQRX!(GE-F}H-SNT;Xu;um;F<&{D>f^IJhbh&}6dvc!?`@`l!Jx;!4T_R8Hdw@NCy)jC+omBJyh zaDl5+7q5YvSY~cN*F?Z5k#Um`bVdJZrFU&R7Wh$W`s{9=?#|{;eO3-DDNZ{5LtG!2 zVk_Z>Pi%dCdBrthsEIK@zGfmTxR&=}B!RXz^Q8Q0I@c2#^tB`6KslR&p_QusD5&#k zGj8|r@oE}vP^WG@U#LaVaHXaGP7>iHZ@hl1R^DN&wSC<3b#-ATo0_9i@u45_RQdq_@okwWABUc$E>~Nl7d_H&}ASxlq`}Bq2t9Ybxq{$b9LY6EZ zYn$8(Yk%~yB+cXd2D{EN_7mO^63X%omVchjTM?zd4r7H=3p;AS|Jm^RXbr! zZS;N6#z3E%5;v*i3$9SdDUru@lr>2Ux0hRwQQKet_;@n0T$z8*+fO4bR9eXfiRbbDAAapt>Q}~NIGo3G22~tO*N>~I zJA51GIB+?b9R#Pa&_mkCKsrwd#4ZiWSgvD7F1~u;alK6NzTQsuA{ZK{TcTON5vJB} zB;**Q(=N5jXN4XAZuqFA7j5r6HBTI0I=1@k7R?HC93oiO#C+{e+#ve8}n@z)%^)=|=Qob3Cq_ zaC`W3XjX+yqKuCdyQSxYVu{QTxNZs8f{^D?1Ge3pAMVmcGTNEoNgqFF9e|P&tv8bvTKL*jwSA>sf`3Ima`uw80x3j! zB-qn@5!@mCL5)_=Xtt{p)g2rYl)o427#GhW?qek~Q)y(2gJV~!xvn;H5>LN7d?IW0 zx@OA;o-E*&peQh}ATW;sfUkhW--6u|czFN}U(|E|YgfPBg(Y}6SK6`}Y#hB~+_O|m z!qw*VpX=xe-esmJ61(U3tpui;a%?xe0dDi| zLJlNkwo?qv21BoV2LPUwWieOu^CWl?lLE!l@p^VbYutp~FFfR4pIp5r=2^POZJo

PUx1cbEwA`^6+BD6*z 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 } +static int credential_ssh_sign_callback( + LIBSSH2_SESSION *session, + unsigned char **sig, size_t *sig_len, + const unsigned char *data, size_t data_len, + void **abstract) +{ + char *error_message = NULL; + const int ret = credentialSSHSignCallback( + &error_message, + sig, + sig_len, + (unsigned char *)data, + data_len, + (void *)*(uintptr_t *)abstract); + return set_callback_error(error_message, ret); +} + +void _go_git_populate_credential_ssh_custom(git_credential_ssh_custom *cred) +{ + cred->parent.free = (void (*)(git_credential *))credentialSSHCustomFree; + cred->sign_callback = credential_ssh_sign_callback; +} + int _go_git_odb_write_pack(git_odb_writepack **out, git_odb *db, void *progress_payload) { return git_odb_write_pack(out, db, transfer_progress_callback, progress_payload);