From 2d213b64d1c897d7a0fdbc93e5cab90f84d7334a Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Thu, 7 Feb 2019 15:13:12 +0800
Subject: [PATCH] use native golang SSH library but ssh-keygen when enable
 built-in SSH server to remove dependent on that command lines (#5976)

* use native golang SSH library but ssh-keygen when enable built-in SSH server to remove dependent on that command lines

* fix tests and add comment head
---
 .../git_helper_for_declarative_test.go        |  4 +-
 modules/ssh/ssh.go                            | 45 ++++++++++++++++++-
 2 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/integrations/git_helper_for_declarative_test.go b/integrations/git_helper_for_declarative_test.go
index 572abe95a2..60c7c29f68 100644
--- a/integrations/git_helper_for_declarative_test.go
+++ b/integrations/git_helper_for_declarative_test.go
@@ -12,20 +12,20 @@ import (
 	"net/http"
 	"net/url"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"testing"
 	"time"
 
 	"code.gitea.io/git"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/ssh"
 	"github.com/Unknwon/com"
 	"github.com/stretchr/testify/assert"
 )
 
 func withKeyFile(t *testing.T, keyname string, callback func(string)) {
 	keyFile := filepath.Join(setting.AppDataPath, keyname)
-	err := exec.Command("ssh-keygen", "-f", keyFile, "-t", "rsa", "-N", "").Run()
+	err := ssh.GenKeyPair(keyFile)
 	assert.NoError(t, err)
 
 	//Setup ssh wrapper
diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go
index 9b9c21278e..d9606488b2 100644
--- a/modules/ssh/ssh.go
+++ b/modules/ssh/ssh.go
@@ -1,10 +1,15 @@
 // Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2017 The Gitea Authors. All rights reserved.
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
 package ssh
 
 import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
 	"io"
 	"io/ioutil"
 	"net"
@@ -176,9 +181,9 @@ func Listen(host string, port int, ciphers []string, keyExchanges []string, macs
 			log.Error(4, "Failed to create dir %s: %v", filePath, err)
 		}
 
-		_, stderr, err := com.ExecCmd("ssh-keygen", "-f", keyPath, "-t", "rsa", "-N", "")
+		err := GenKeyPair(keyPath)
 		if err != nil {
-			log.Fatal(4, "Failed to generate private key: %v - %s", err, stderr)
+			log.Fatal(4, "Failed to generate private key: %v", err)
 		}
 		log.Trace("SSH: New private key is generateed: %s", keyPath)
 	}
@@ -195,3 +200,39 @@ func Listen(host string, port int, ciphers []string, keyExchanges []string, macs
 
 	go listen(config, host, port)
 }
+
+// GenKeyPair make a pair of public and private keys for SSH access.
+// Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
+// Private Key generated is PEM encoded
+func GenKeyPair(keyPath string) error {
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		return err
+	}
+
+	privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
+	f, err := os.OpenFile(keyPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	if err := pem.Encode(f, privateKeyPEM); err != nil {
+		return err
+	}
+
+	// generate public key
+	pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
+	if err != nil {
+		return err
+	}
+
+	public := ssh.MarshalAuthorizedKey(pub)
+	p, err := os.OpenFile(keyPath+".pub", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		return err
+	}
+	defer p.Close()
+	_, err = p.Write(public)
+	return err
+}