This change drops the (hard) dependency on libssh2 and instead uses Go's implementation of SSH when libgit2 is not built with it.
This commit is contained in:
parent
29eaaff9fd
commit
e434188460
|
@ -9,7 +9,7 @@ import "C"
|
|||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"runtime"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
@ -57,10 +57,22 @@ func (o *Cred) GetUserpassPlaintext() (username, password string, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func NewCredUsername(username string) (int, Cred) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
// GetSSHKey returns the SSH-specific key information from the Cred object.
|
||||
func (o *Cred) GetSSHKey() (username, publickey, privatekey, passphrase string, err error) {
|
||||
if o.Type() != CredTypeSshKey {
|
||||
err = fmt.Errorf("credential is not an SSH key: %v", o.Type())
|
||||
return
|
||||
}
|
||||
|
||||
sshKeyCredPtr := (*C.git_cred_ssh_key)(unsafe.Pointer(o.ptr))
|
||||
username = C.GoString(sshKeyCredPtr.username)
|
||||
publickey = C.GoString(sshKeyCredPtr.publickey)
|
||||
privatekey = C.GoString(sshKeyCredPtr.privatekey)
|
||||
passphrase = C.GoString(sshKeyCredPtr.passphrase)
|
||||
return
|
||||
}
|
||||
|
||||
func NewCredUsername(username string) (int, Cred) {
|
||||
cred := Cred{}
|
||||
cusername := C.CString(username)
|
||||
ret := C.git_cred_username_new(&cred.ptr, cusername)
|
||||
|
|
5
git.go
5
git.go
|
@ -157,6 +157,11 @@ func initLibGit2() {
|
|||
// they're the only ones setting it up.
|
||||
C.git_openssl_set_locking()
|
||||
}
|
||||
if features&FeatureSSH == 0 {
|
||||
if err := registerManagedSSH(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown frees all the resources acquired by libgit2. Make sure no
|
||||
|
|
23
remote.go
23
remote.go
|
@ -16,6 +16,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type TransferProgress struct {
|
||||
|
@ -239,20 +241,25 @@ type Certificate struct {
|
|||
Hostkey HostkeyCertificate
|
||||
}
|
||||
|
||||
// HostkeyKind is a bitmask of the available hashes in HostkeyCertificate.
|
||||
type HostkeyKind uint
|
||||
|
||||
const (
|
||||
HostkeyMD5 HostkeyKind = C.GIT_CERT_SSH_MD5
|
||||
HostkeySHA1 HostkeyKind = C.GIT_CERT_SSH_SHA1
|
||||
HostkeyMD5 HostkeyKind = C.GIT_CERT_SSH_MD5
|
||||
HostkeySHA1 HostkeyKind = C.GIT_CERT_SSH_SHA1
|
||||
HostkeySHA256 HostkeyKind = 1 << 2
|
||||
HostkeyRaw HostkeyKind = 1 << 3
|
||||
)
|
||||
|
||||
// Server host key information. If Kind is HostkeyMD5 the MD5 field
|
||||
// will be filled. If Kind is HostkeySHA1, then HashSHA1 will be
|
||||
// filled.
|
||||
// Server host key information. A bitmask containing the available fields.
|
||||
// Check for combinations of: HostkeyMD5, HostkeySHA1, HostkeySHA256, HostkeyRaw.
|
||||
type HostkeyCertificate struct {
|
||||
Kind HostkeyKind
|
||||
HashMD5 [16]byte
|
||||
HashSHA1 [20]byte
|
||||
Kind HostkeyKind
|
||||
HashMD5 [16]byte
|
||||
HashSHA1 [20]byte
|
||||
HashSHA256 [32]byte
|
||||
Hostkey []byte
|
||||
SSHPublicKey ssh.PublicKey
|
||||
}
|
||||
|
||||
type PushOptions struct {
|
||||
|
|
|
@ -59,6 +59,7 @@ cmake -DTHREADSAFE=ON \
|
|||
-DBUILD_CLAR=OFF \
|
||||
-DBUILD_SHARED_LIBS"=${BUILD_SHARED_LIBS}" \
|
||||
-DUSE_HTTPS=OFF \
|
||||
-DUSE_SSH=OFF \
|
||||
-DCMAKE_C_FLAGS=-fPIC \
|
||||
-DCMAKE_BUILD_TYPE="RelWithDebInfo" \
|
||||
-DCMAKE_INSTALL_PREFIX="${BUILD_INSTALL_PREFIX}" \
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
package git
|
||||
|
||||
/*
|
||||
#include <git2.h>
|
||||
|
||||
void _go_git_credential_free(git_cred *cred);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// RegisterManagedSSHTransport registers a Go-native implementation of an SSH
|
||||
// transport that doesn't rely on any system libraries (e.g. libssh2).
|
||||
//
|
||||
// If Shutdown or ReInit are called, make sure that the smart transports are
|
||||
// freed before it.
|
||||
func RegisterManagedSSHTransport(protocol string) (*RegisteredSmartTransport, error) {
|
||||
return NewRegisteredSmartTransport(protocol, false, sshSmartSubtransportFactory)
|
||||
}
|
||||
|
||||
func registerManagedSSH() error {
|
||||
globalRegisteredSmartTransports.Lock()
|
||||
defer globalRegisteredSmartTransports.Unlock()
|
||||
|
||||
for _, protocol := range []string{"ssh", "ssh+git", "git+ssh"} {
|
||||
if _, ok := globalRegisteredSmartTransports.transports[protocol]; ok {
|
||||
continue
|
||||
}
|
||||
managed, err := newRegisteredSmartTransport(protocol, false, sshSmartSubtransportFactory, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register transport for %q: %v", protocol, err)
|
||||
}
|
||||
globalRegisteredSmartTransports.transports[protocol] = managed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sshSmartSubtransportFactory(remote *Remote, transport *Transport) (SmartSubtransport, error) {
|
||||
return &sshSmartSubtransport{
|
||||
transport: transport,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type sshSmartSubtransport struct {
|
||||
transport *Transport
|
||||
|
||||
lastAction SmartServiceAction
|
||||
client *ssh.Client
|
||||
session *ssh.Session
|
||||
stdin io.WriteCloser
|
||||
stdout io.Reader
|
||||
currentStream *sshSmartSubtransportStream
|
||||
}
|
||||
|
||||
func (t *sshSmartSubtransport) Action(urlString string, action SmartServiceAction) (SmartSubtransportStream, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
u, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cmd string
|
||||
switch action {
|
||||
case SmartServiceActionUploadpackLs, SmartServiceActionUploadpack:
|
||||
if t.currentStream != nil {
|
||||
if t.lastAction == SmartServiceActionUploadpackLs {
|
||||
return t.currentStream, nil
|
||||
}
|
||||
t.Close()
|
||||
}
|
||||
cmd = fmt.Sprintf("git-upload-pack %q", u.Path)
|
||||
|
||||
case SmartServiceActionReceivepackLs, SmartServiceActionReceivepack:
|
||||
if t.currentStream != nil {
|
||||
if t.lastAction == SmartServiceActionReceivepackLs {
|
||||
return t.currentStream, nil
|
||||
}
|
||||
t.Close()
|
||||
}
|
||||
cmd = fmt.Sprintf("git-receive-pack %q", u.Path)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected action: %v", action)
|
||||
}
|
||||
|
||||
cred, err := t.transport.SmartCredentials("", CredTypeSshKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer C._go_git_credential_free(cred.ptr)
|
||||
|
||||
sshConfig, err := getSSHConfigFromCredential(cred)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshConfig.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
marshaledKey := key.Marshal()
|
||||
cert := &Certificate{
|
||||
Kind: CertificateHostkey,
|
||||
Hostkey: HostkeyCertificate{
|
||||
Kind: HostkeySHA1 | HostkeyMD5 | HostkeySHA256 | HostkeyRaw,
|
||||
HashMD5: md5.Sum(marshaledKey),
|
||||
HashSHA1: sha1.Sum(marshaledKey),
|
||||
HashSHA256: sha256.Sum256(marshaledKey),
|
||||
Hostkey: marshaledKey,
|
||||
SSHPublicKey: key,
|
||||
},
|
||||
}
|
||||
|
||||
return t.transport.SmartCertificateCheck(cert, true, hostname)
|
||||
}
|
||||
|
||||
var addr string
|
||||
if u.Port() != "" {
|
||||
addr = fmt.Sprintf("%s:%s", u.Hostname(), u.Port())
|
||||
} else {
|
||||
addr = fmt.Sprintf("%s:22", u.Hostname())
|
||||
}
|
||||
|
||||
t.client, err = ssh.Dial("tcp", addr, sshConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.session, err = t.client.NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.stdin, err = t.session.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.stdout, err = t.session.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := t.session.Start(cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.lastAction = action
|
||||
t.currentStream = &sshSmartSubtransportStream{
|
||||
owner: t,
|
||||
}
|
||||
|
||||
return t.currentStream, nil
|
||||
}
|
||||
|
||||
func (t *sshSmartSubtransport) Close() error {
|
||||
t.currentStream = nil
|
||||
if t.client != nil {
|
||||
t.stdin.Close()
|
||||
t.session.Wait()
|
||||
t.session.Close()
|
||||
t.client = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *sshSmartSubtransport) Free() {
|
||||
}
|
||||
|
||||
type sshSmartSubtransportStream struct {
|
||||
owner *sshSmartSubtransport
|
||||
}
|
||||
|
||||
func (stream *sshSmartSubtransportStream) Read(buf []byte) (int, error) {
|
||||
return stream.owner.stdout.Read(buf)
|
||||
}
|
||||
|
||||
func (stream *sshSmartSubtransportStream) Write(buf []byte) (int, error) {
|
||||
return stream.owner.stdin.Write(buf)
|
||||
}
|
||||
|
||||
func (stream *sshSmartSubtransportStream) Free() {
|
||||
}
|
||||
|
||||
func getSSHConfigFromCredential(cred *Cred) (*ssh.ClientConfig, error) {
|
||||
switch cred.Type() {
|
||||
case CredTypeSshCustom:
|
||||
credSSHCustom := (*C.git_cred_ssh_custom)(unsafe.Pointer(cred.ptr))
|
||||
data, ok := pointerHandles.Get(credSSHCustom.payload).(*credentialSSHCustomData)
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported custom SSH credentials")
|
||||
}
|
||||
return &ssh.ClientConfig{
|
||||
User: C.GoString(credSSHCustom.username),
|
||||
Auth: []ssh.AuthMethod{ssh.PublicKeys(data.signer)},
|
||||
}, nil
|
||||
}
|
||||
|
||||
username, _, privatekey, passphrase, ret := cred.GetSSHKey()
|
||||
if ret != nil {
|
||||
return nil, ret
|
||||
}
|
||||
|
||||
pemBytes, err := ioutil.ReadFile(privatekey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var key ssh.Signer
|
||||
if passphrase != "" {
|
||||
key, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
key, err = ssh.ParsePrivateKey(pemBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{ssh.PublicKeys(key)},
|
||||
}, nil
|
||||
}
|
|
@ -434,6 +434,11 @@ void _go_git_populate_credential_ssh_custom(git_cred_ssh_custom *cred)
|
|||
cred->sign_callback = credential_ssh_sign_callback;
|
||||
}
|
||||
|
||||
void _go_git_credential_free(git_cred *cred)
|
||||
{
|
||||
cred->free(cred);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue