diff --git a/.circleci/config.yml b/.circleci/config.yml index 7795bc5..06195d4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,5 @@ defaults: &defaults - working_directory: /go/src/moul.io/golang-repo-template + working_directory: /go/src/moul.io/quicssh docker: - image: circleci/golang:1.12 environment: diff --git a/.goreleaser.yml b/.goreleaser.yml index 3fae919..8d1e790 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -18,7 +18,7 @@ changelog: - '^docs:' - '^test:' brew: - name: golang-repo-template + name: quicssh github: owner: moul name: homebrew-moul @@ -26,4 +26,4 @@ brew: name: moul-bot email: "m+bot@42.am" homepage: https://manfred.life/ - description: "golang-repo-template" + description: "quicssh" diff --git a/Dockerfile b/Dockerfile index cfa2fc4..5cee82d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.12-alpine as builder RUN apk add --no-cache git gcc musl-dev make ENV GO111MODULE=on -WORKDIR /go/src/moul.io/golang-repo-template +WORKDIR /go/src/moul.io/quicssh COPY go.* ./ RUN go mod download COPY . ./ @@ -10,6 +10,6 @@ RUN make install # minimalist runtime FROM alpine:3.10 -COPY --from=builder /go/bin/golang-repo-template /bin/ -ENTRYPOINT ["/bin/golang-repo-template"] +COPY --from=builder /go/bin/quicssh /bin/ +ENTRYPOINT ["/bin/quicssh"] CMD [] diff --git a/README.md b/README.md index 93bd04b..c3e5f39 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,29 @@ -# golang-repo-template +# quicssh -:smile: golang-repo-template does +:smile: quicssh does -[![CircleCI](https://circleci.com/gh/moul/golang-repo-template.svg?style=shield)](https://circleci.com/gh/moul/golang-repo-template) -[![GoDoc](https://godoc.org/moul.io/golang-repo-template?status.svg)](https://godoc.org/moul.io/golang-repo-template) -[![License](https://img.shields.io/github/license/moul/golang-repo-template.svg)](https://github.com/moul/golang-repo-template/blob/master/LICENSE) -[![GitHub release](https://img.shields.io/github/release/moul/golang-repo-template.svg)](https://github.com/moul/golang-repo-template/releases) -[![Go Report Card](https://goreportcard.com/badge/moul.io/golang-repo-template)](https://goreportcard.com/report/moul.io/golang-repo-template) -[![Docker Metrics](https://images.microbadger.com/badges/image/moul/golang-repo-template.svg)](https://microbadger.com/images/moul/golang-repo-template) +[![CircleCI](https://circleci.com/gh/moul/quicssh.svg?style=shield)](https://circleci.com/gh/moul/quicssh) +[![GoDoc](https://godoc.org/moul.io/quicssh?status.svg)](https://godoc.org/moul.io/quicssh) +[![License](https://img.shields.io/github/license/moul/quicssh.svg)](https://github.com/moul/quicssh/blob/master/LICENSE) +[![GitHub release](https://img.shields.io/github/release/moul/quicssh.svg)](https://github.com/moul/quicssh/releases) +[![Go Report Card](https://goreportcard.com/badge/moul.io/quicssh)](https://goreportcard.com/report/moul.io/quicssh) +[![Docker Metrics](https://images.microbadger.com/badges/image/moul/quicssh.svg)](https://microbadger.com/images/moul/quicssh) [![Made by Manfred Touron](https://img.shields.io/badge/made%20by-Manfred%20Touron-blue.svg?style=flat)](https://manfred.life/) ## Usage ```console -$ golang-repo-template -h +$ quicssh -h ... ``` ## Install ```console -$ go get -u moul.io/golang-repo-template +$ go get -u moul.io/quicssh ``` ## License © 2019 [Manfred Touron](https://manfred.life) - -[Apache-2.0 License](https://github.com/moul/golang-repo-template/blob/master/LICENSE) +[Apache-2.0 License](https://github.com/moul/quicssh/blob/master/LICENSE) diff --git a/client.go b/client.go new file mode 100644 index 0000000..1155c38 --- /dev/null +++ b/client.go @@ -0,0 +1,53 @@ +package main + +import ( + "crypto/tls" + "log" + "os" + "sync" + + quic "github.com/lucas-clemente/quic-go" + "golang.org/x/net/context" + cli "gopkg.in/urfave/cli.v2" +) + +func client(c *cli.Context) error { + ctx, cancel := context.WithCancel(context.Background()) + + config := &tls.Config{ + InsecureSkipVerify: true, + NextProtos: []string{"quicssh"}, + } + + log.Printf("Dialing %q...", c.String("addr")) + session, err := quic.DialAddr(c.String("addr"), config, nil) + if err != nil { + return err + } + defer session.Close() + + log.Printf("Opening stream sync...") + stream, err := session.OpenStreamSync(ctx) + if err != nil { + return err + } + + log.Printf("Piping stream with QUIC...") + var wg sync.WaitGroup + wg.Add(3) + c1 := readAndWrite(ctx, stream, os.Stdout, &wg) + c2 := readAndWrite(ctx, os.Stdin, stream, &wg) + select { + case err = <-c1: + if err != nil { + return err + } + case err = <-c2: + if err != nil { + return err + } + } + cancel() + wg.Wait() + return nil +} diff --git a/doc.go b/doc.go index 9804be8..48c384d 100644 --- a/doc.go +++ b/doc.go @@ -13,4 +13,4 @@ // limitations under the License. // Package main is ... -package main // import "moul.io/golang-repo-template" +package main // import "moul.io/quicssh" diff --git a/go.mod b/go.mod index f5ecf05..dce8342 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,9 @@ -module moul.io/golang-repo-template +module moul.io/quicssh go 1.12 + +require ( + github.com/lucas-clemente/quic-go v0.7.1-0.20190710050138-1441923ab031 + golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7 + gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..aaefb30 --- /dev/null +++ b/go.sum @@ -0,0 +1,46 @@ +github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/lucas-clemente/quic-go v0.7.1-0.20190710050138-1441923ab031 h1:wjcGvgllMOQw8wNYFH6acq/KlTAdjKMSo1EUYybHXto= +github.com/lucas-clemente/quic-go v0.7.1-0.20190710050138-1441923ab031/go.mod h1:lb5aAxL68VvhZ00e7yYuQVK/9FLggtYy4qo7oI5qzqA= +github.com/marten-seemann/qpack v0.1.0 h1:/0M7lkda/6mus9B8u34Asqm8ZhHAAt9Ho0vniNuVSVg= +github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= +github.com/marten-seemann/qtls v0.3.1 h1:ySYIvhFjFY2JsNHY6VACvomMEDy3EvdPA6yciUFAiHw= +github.com/marten-seemann/qtls v0.3.1/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25 h1:jsG6UpNLt9iAsb0S2AGW28DveNzzgmbXR+ENoPjUeIU= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7 h1:Qe/u+eY379X4He4GBMFZYu3pmh1ML5yT1aL1ndNM1zQ= +golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8 h1:Ggy3mWN4l3PUFPfSG0YB3n5fVYggzysUmiUQ89SnX6Y= +gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8/go.mod h1:cKXr3E0k4aosgycml1b5z33BVV6hai1Kh7uDgFOkbcs= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 5f3a415..8141af2 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,63 @@ -package main // import "moul.io/golang-repo-template" +package main -import "fmt" +import ( + "io" + "os" + "sync" + + "golang.org/x/net/context" + cli "gopkg.in/urfave/cli.v2" +) func main() { - fmt.Println("Hello World!") + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "server", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "bind", Value: "localhost:4242"}, + }, + Action: server, + }, + { + Name: "client", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "addr", Value: "localhost:4242"}, + }, + Action: client, + }, + }, + } + if err := app.Run(os.Args); err != nil { + panic(err) + } +} + +func readAndWrite(ctx context.Context, r io.Reader, w io.Writer, wg *sync.WaitGroup) <-chan error { + c := make(chan error) + go func() { + if wg != nil { + defer wg.Done() + } + buff := make([]byte, 1024) + + for { + select { + case <-ctx.Done(): + return + default: + nr, err := r.Read(buff) + if err != nil { + return + } + if nr > 0 { + _, err := w.Write(buff[:nr]) + if err != nil { + return + } + } + } + } + }() + return c } diff --git a/server.go b/server.go new file mode 100644 index 0000000..d028a95 --- /dev/null +++ b/server.go @@ -0,0 +1,127 @@ +package main + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "io" + "log" + "math/big" + "net" + "sync" + + quic "github.com/lucas-clemente/quic-go" + "golang.org/x/net/context" + cli "gopkg.in/urfave/cli.v2" +) + +func server(c *cli.Context) error { + // generate TLS certificate + key, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + return err + } + template := x509.Certificate{SerialNumber: big.NewInt(1)} + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) + if err != nil { + return err + } + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return err + } + config := &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + NextProtos: []string{"quicssh"}, + } + + // configure listener + listener, err := quic.ListenAddr(c.String("bind"), config, nil) + if err != nil { + return err + } + defer listener.Close() + log.Printf("Listening at %q...", c.String("bind")) + + ctx := context.Background() + for { + log.Printf("Accepting connection...") + session, err := listener.Accept(ctx) + if err != nil { + log.Printf("listener error: %v", err) + continue + } + + go serverSessionHandler(ctx, session) + } + return nil +} + +func serverSessionHandler(ctx context.Context, session quic.Session) { + log.Printf("hanling session...") + defer session.Close() + for { + stream, err := session.AcceptStream(ctx) + if err != nil { + log.Printf("session error: %v", err) + break + } + go serverStreamHandler(ctx, stream) + } +} + +func serverStreamHandler(ctx context.Context, conn io.ReadWriteCloser) { + log.Printf("handling stream...") + defer conn.Close() + + rConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.IP{127, 0, 0, 1}, Port: 22}) + if err != nil { + log.Printf("dial error: %v", err) + return + } + defer rConn.Close() + + ctx, cancel := context.WithCancel(ctx) + + var wg sync.WaitGroup + wg.Add(2) + c1 := readAndWrite(ctx, conn, rConn, &wg) + c2 := readAndWrite(ctx, rConn, conn, &wg) + select { + case err = <-c1: + if err != nil { + log.Printf("readAndWrite error on c1: %v", err) + return + } + case err = <-c2: + if err != nil { + log.Printf("readAndWrite error on c2: %v", err) + return + } + } + cancel() + wg.Wait() + log.Printf("Piping finished") +} + +func netCopy(input io.Reader, output io.Writer) (err error) { + buf := make([]byte, 8192) + for { + count, err := input.Read(buf) + if err != nil { + if err == io.EOF && count > 0 { + output.Write(buf[:count]) + } + break + } + if count > 0 { + log.Println(buf, count) + output.Write(buf[:count]) + } + } + return +} diff --git a/test.crt b/test.crt new file mode 100644 index 0000000..e2462fe --- /dev/null +++ b/test.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAIIvbSaJPzA1MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTkwNzEzMjE1MzMxWhcNMjAwNzEyMjE1MzMxWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAmbucNcxAnXxFPT64uY1lKenrphuDg1MLrmvghoTRq+eVJH6QM58z+V3d +2xbaPoIuFBMYPQP17oKHFl5V/tmZjqRKY9yg+JH71A2lmIu99vVYUiubzVnZX1QZ +d+csr36B/R2fFCUONvb+BiCm65ArNd9pTaNRc2rxHbRa9nCTK5AT97DVgfuk9N+l +SebekMQma/qD4f7VgDhi5FQV5JNen0WdT0/V2KJkMv/yKnvnnXiK3jRtCDSu/nj4 +CsMrLUffmpPn8FE5UImyDGYgor0V4N1mqo4NUWuJkaAxHvI5sT0+f9rm/8gnr/dX +GnIvPRA33gwhYmAcNavqbf8N0xl/jwIDAQABo1AwTjAdBgNVHQ4EFgQUIP0H61kW +YT7pBmwwLiVDImggl7owHwYDVR0jBBgwFoAUIP0H61kWYT7pBmwwLiVDImggl7ow +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAT7KVlI90mpToEEQGfgO7 +MUUvllsuoYvzKDpic8svWgM1v3KKMwquD19sH+x8RwRZ6SVY+zSqn12VEL9W3nNa +bTYtmtGwfkYfvIM3LldbTm15228YBEvDQUY+QAdJs/E9TpA4T/sOjz0COjOFWzso +WS6x2J5I/d0POBRwDdASnZDCfSui/xUQxbucw28eITMyknZgvvVN6dejDjZSrZfw +9nk93KQml4IUNWnfhrVtL1dUX1MyhBOIBmdPq33pe2GlyxqczSyn3+1H36wiPj4l +wd7ZCyv1RiW6CaxtewSY4ct+NqunZ66cOBS/0aw5VxyuIVUAp5DtKVT2f35h/5+x +iQ== +-----END CERTIFICATE----- diff --git a/test.key b/test.key new file mode 100644 index 0000000..bebc75e --- /dev/null +++ b/test.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCZu5w1zECdfEU9 +Pri5jWUp6eumG4ODUwuua+CGhNGr55UkfpAznzP5Xd3bFto+gi4UExg9A/XugocW +XlX+2ZmOpEpj3KD4kfvUDaWYi7329VhSK5vNWdlfVBl35yyvfoH9HZ8UJQ429v4G +IKbrkCs132lNo1FzavEdtFr2cJMrkBP3sNWB+6T036VJ5t6QxCZr+oPh/tWAOGLk +VBXkk16fRZ1PT9XYomQy//Iqe+edeIreNG0INK7+ePgKwystR9+ak+fwUTlQibIM +ZiCivRXg3Waqjg1Ra4mRoDEe8jmxPT5/2ub/yCev91caci89EDfeDCFiYBw1q+pt +/w3TGX+PAgMBAAECggEAA+pANyqFdr1EciPXxnnwWpnnc2p99ek2gfGjXSmiwVL7 +fFtwxq/GPhKC5OJ3GmJsU/yMgHlKWRGf6RTr8bqO65AJiPOEcfAdzq+uSO0+IDzt +S+JqbFdebswQffo4LBv3qX+InpW2//VYUMWiGpuoTg3re5uuJldR3qTKMD57sP9H +NzIPYRxaESn/owPGzp+MfjhGwx5yAmyQeorNaMydU7sKX/9uTmRLW82Uh+SV8u2n +TswwR/iwHAPfvNr1rI2EiUBFDHlL1NIXDGKnMqqQFk/Y7kQkB47NA3n2IbPVkncL +cJfMimOC7U/7k2m6J8J9aWOBfIf0S5A7EaJAe0jqGQKBgQDL7fB4/7dSEnqHR2qi +3LLNk9y6l7XTLtYXkPiNcpxseT1/n8IK/hhIcQL7a16abVU4/0GFRpYOj1AEt6IB +EnqM+DsGz4ho4X2ucOFJ80Af9UUUbSmtJowzSU+DXf+d3U7PcNf7vzXJmyUbWmGZ +lpLXPTtsZTLum3U+nYkdj6trKwKBgQDA/ISbN5wIadL4vCvi/EHzzH6TPOip1arc +QiKibaqKaDzdQhELJjg9Lkma8xgcVSovmMaQm+RATqUTuwSNufyNQ0cxsdV/lPH1 +bFJwVBtyBzbiZ4/kR0ZTBNRX6NVFHokPjiGTVWyWGA5gp8iPSXB3gE9sIBSpw9qL +ArjL+257LQKBgEe4ii9z9/xUZWV4d4eJyRTGIQY63wbD3SXypYfRvDPmO/vLqwoE +rXOk02CrNV1ogGWIWHnQBmxeeMz/7GkmH5W+o7vUd2wziek05/cDJxVWRJJXhiXQ +fdR3vxA7me/iappIXJ28dOVPvDAvjE3hCAnNDj4kJVKHuCdqblPIOIh7AoGAG9kU +hZVrtacXo3770kBWgAjFRxfl9wP3KNt+RfQPRPOvvLnY3cQBH4r7YhmsJAKCGOYx +2RI1yLXQil1VVeI9uGC5+EjSJxvmImUkLENmxniWCeupzuYeFsK+pYTaqaOzYYRA +AhO0nKASCw6LGWoeiZABZffnI2w4sBCPfBfnJG0CgYEAjXqU+Z2KHsajeQcsD5BJ +FWh+XxllgLbNFOVdhiOyicbZVgxhb4rU9JRSGFrRV+5fYVIDaXsPD9FYEx5C9oxx +B5TGPh6p6VF4m2lElKXShOzrvh1QA9Nhpnqmyxtb44EIYaEV34M7W9+juDCycHFX +7bcslckWYTuELLtggVYRV/I= +-----END PRIVATE KEY-----