Skip to content
This repository has been archived by the owner on Aug 26, 2019. It is now read-only.

Commit

Permalink
SSProto 2
Browse files Browse the repository at this point in the history
Merge pull request #5 from Hexawolf/dev
  • Loading branch information
Hexawolf committed Nov 10, 2018
2 parents 46f9dae + 447a65a commit 945952f
Show file tree
Hide file tree
Showing 19 changed files with 431 additions and 359 deletions.
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: go

go:
- "1.x"

env:
- GO111MODULE=on
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# SSProto

[![Build Status](https://travis-ci.com/Hexawolf/SSProto.svg?branch=dev)](https://travis-ci.com/Hexawolf/SSProto)

SSProto is a fairly simple but flexible and secure TCP protocol for deploying
game client updates. Initially, it was developed as a part of Hexamine project
(a Minecraft server) but turns out to be useful outside of Minecraft
Expand Down Expand Up @@ -30,14 +32,7 @@ specifically for Hexamine client, it is obvious that SSProto (as well as this
program) may be tuned for more applications where it is essential to keep only
specific server files in sync with clients.

## Is it secure?

All communications are secured by hardcoded TLS key. However, it is expected
that received files may contain executable data. For this reason, files are
also signed by ed448-decaf key, which is fast enough if you care about the
speed.

## Copyrights?
## License

Copyright © 2018 Hexawolf

Expand Down
9 changes: 3 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ module github.com/Hexawolf/SSProto

require (
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.7
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shirou/gopsutil v2.17.12+incompatible
github.com/shirou/gopsutil v2.18.10+incompatible
github.com/stretchr/testify v1.2.2 // indirect
github.com/twstrike/ed448 v0.0.0-20180709001919-35f66737d61d
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 // indirect
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16
golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f h1:5ZfJxyXo8KyX8DgGXC5B7ILL8y51fci/qYz2B4j8iLY=
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shirou/gopsutil v2.18.10+incompatible h1:cy84jW6EVRPa5g9HAHrlbxMSIjBhDSX0OFYyMYminYs=
github.com/shirou/gopsutil v2.18.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0=
golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
11 changes: 5 additions & 6 deletions ss-client/build.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
#!/bin/sh

if [ $# -ne 4 ]; then
echo "Usage: ./build.sh KEY-FILE CERTIFICATE SERVER-ADDRESS FILENAME"
echo "E.g. ./build.sh ss.key cert.pem doggoat.de Updater"
if [ $# -ne 3 ]; then
echo "Usage: ./build.sh CERTIFICATE SERVER-ADDRESS FILENAME"
echo "E.g. ./build.sh cert.pem doggoat.de:48879 Updater"
echo "Also you can use EXTRABUILDFLAGS envvar to specify additional"
echo "arguments to pass to go build."
exit 1
fi

key=$(tail -n 1 "$1")
cert=$(head -n -1 "$2" | tail -n +2 | paste -s -d "")
cert=$(head -n -1 "$1" | tail -n +2 | paste -s -d "")

go build -o $4 --ldflags="-s -w -X main.certEnc=$cert -X main.keyEnc=$key -X main.targetHost=$3" $EXTRABUILDFLAGS
go build -o $3 --ldflags="-s -w -X main.certEnc=$cert -X main.targetHost=$2" $EXTRABUILDFLAGS
37 changes: 11 additions & 26 deletions ss-client/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,64 +13,49 @@
package main

import (
"encoding/base64"

"crypto/tls"
"crypto/x509"

"github.com/twstrike/ed448"
"io/ioutil"
"crypto/rand"
"io/ioutil"
"strings"
)

var publicKey [56]byte
var curve ed448.DecafCurve
var conf tls.Config

// Both variables are set by build script.
var certEnc, keyEnc string

// LoadKeys deserializes certificate stored in memory.
func LoadKeys() error {
publicKeySlice, pubErr := base64.StdEncoding.DecodeString(keyEnc)
if pubErr != nil {
return pubErr
}
copy(publicKey[:], publicKeySlice)
curve = ed448.NewDecafCurve()

certs := x509.NewCertPool()
cert := "-----BEGIN CERTIFICATE-----\n" + certEnc + "\n-----END CERTIFICATE-----"
certs.AppendCertsFromPEM([]byte(cert))
conf = tls.Config{
RootCAs: certs,
RootCAs: certs,
// Extract domain from targetHost
ServerName: strings.Split(targetHost, ":")[0],
}
return nil
}

func Verify(data []byte, signature [112]byte) bool {
verify, err := curve.Verify(signature, data, publicKey)
return verify && err == nil
}

func newUUID() ([]byte, error) {
v := make([]byte, 32)
_, err := rand.Read(v)
return v, err
}

// UUID tries to load from config/uuid.bin or generate a new random sequence of 32 bytes. This
// sequence is used for client identification.
func UUID() ([]byte, error) {
uuidLocation := "config/uuid.bin"
if fileExists(uuidLocation) {
return ioutil.ReadFile(uuidLocation)
} else {
b, err := newUUID()
if err != nil {
return nil, err
}
ioutil.WriteFile(uuidLocation, b, 0600)
return b, nil
}
b, err := newUUID()
if err != nil {
return nil, err
}
ioutil.WriteFile(uuidLocation, b, 0600)
return b, nil
}
5 changes: 4 additions & 1 deletion ss-client/hwinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ package main

import (
"runtime"

"github.com/shirou/gopsutil/mem"
)

// MachineInfo contains memory statistics of a machine running client.
type MachineInfo struct {
// Total amount of RAM on this system
MemoryTotal uint64 `json:"mem_total"`
Expand All @@ -41,9 +43,10 @@ type MachineInfo struct {
MemoryFree uint64 `json:"mem_free"`

// User's operating system, just GOOS variable
OS string `json:"os"`
OS string `json:"os"`
}

// GetMachineInfo tries to get some machine information and create a MachineInfo instance
func GetMachineInfo() MachineInfo {
var info MachineInfo
v, err := mem.VirtualMemory()
Expand Down
15 changes: 5 additions & 10 deletions ss-client/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
package main

import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"os"
"io/ioutil"
"crypto/sha256"

"golang.org/x/crypto/blake2b"
)

// excludedGlob is a collection of snowflakes ❄️
Expand Down Expand Up @@ -70,18 +71,12 @@ func collectHashList() (map[string][]byte, error) {
return nil, err
}

// A very special snowflake for Hexamine ❄️
authlib := "libraries/com/mojang/authlib/1.5.25/authlib-1.5.25.jar"
if fileExists(authlib) {
list = append(list, filepath.ToSlash(authlib))
}

for _, path := range list {
blob, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
sum := sha256.Sum256(blob)
sum := blake2b.Sum256(blob)
res[path] = sum[:]
}
return res, nil
Expand Down
103 changes: 57 additions & 46 deletions ss-client/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
package main

import (
"crypto/sha256"
"encoding/binary"
"encoding/json"
"fmt"
"io"
)

// WriteHWInfo writes machine information in form of JSON to given Writer.
func WriteHWInfo(out io.Writer) error {
b, err := json.Marshal(GetMachineInfo())
if err != nil {
Expand All @@ -32,50 +33,32 @@ func WriteHWInfo(out io.Writer) error {
return err
}

func SendHashListEntry(pipe io.ReadWriter, path string, hash []byte) (bool, error) {
err := binary.Write(pipe, binary.LittleEndian, hash)
// SendHashListEntry writes serializes hashlist entry to out io.Writer.
func SendHashListEntry(out io.Writer, path string, hash []byte) error {
err := binary.Write(out, binary.LittleEndian, hash)
if err != nil {
return false, err
return err
}
bytesPath := []byte(path)
err = binary.Write(pipe, binary.LittleEndian, uint64(len(bytesPath)))
if err != nil {
return false, err
}
err = binary.Write(pipe, binary.LittleEndian, bytesPath)
err = binary.Write(out, binary.LittleEndian, uint64(len(bytesPath)))
if err != nil {
return false, err
return err
}
resp := false
err = binary.Read(pipe, binary.LittleEndian, &resp)
return resp, err
}

func FinishHashList(pipe io.ReadWriter) error {
zeroes := [32]byte{}
_, err := pipe.Write(zeroes[:])
return err
return binary.Write(out, binary.LittleEndian, bytesPath)
}

// Packet is an update unit that contains file that needs to be updated and some metadata
type Packet struct {
Hash [32]byte
Signature [112]byte
FilePath string
Blob []byte
FilePath string
Blob io.Reader
Size uint64
}

// ReadPacket deserializes packet structure from a binary stream
func ReadPacket(in io.Reader) (*Packet, error) {
res := new(Packet)
err := binary.Read(in, binary.LittleEndian, &res.Hash)
if err != nil {
return nil, err
}
err = binary.Read(in, binary.LittleEndian, &res.Signature)
if err != nil {
return nil, err
}
var size uint64
err = binary.Read(in, binary.LittleEndian, &size)
err := binary.Read(in, binary.LittleEndian, &size)
if err != nil {
return nil, err
}
Expand All @@ -85,25 +68,53 @@ func ReadPacket(in io.Reader) (*Packet, error) {
return nil, err
}
res.FilePath = string(pathBytes)
size = uint64(0)
err = binary.Read(in, binary.LittleEndian, &size)
if err != nil {
return nil, err
}
res.Blob = make([]byte, size)
err = binary.Read(in, binary.LittleEndian, &res.Blob)
err = binary.Read(in, binary.LittleEndian, &res.Size)
if err != nil {
return nil, err
}
res.Blob = io.LimitReader(in, int64(res.Size))
return res, nil
}

func (p Packet) Verify() bool {
sum := sha256.Sum256(p.Blob)
if sum != p.Hash {
return false
// WriteTo implements io.WriterTo for Packet. Each packet must be
// written before reading next one.
func (p Packet) WriteTo(w io.Writer) (int64, error) {
return io.Copy(w, p.Blob)
}

func copyWithProgress(filename string, size uint64, src io.Reader, dst io.Writer) error {
written := uint64(0)
buf := make([]byte, 65536) // There is nothing wrong with using big buffers.

eof := false
for !eof {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {
written += uint64(nw)
}
if ew != nil {
return ew
}
if nr != nw {
return io.ErrShortWrite
}
}
if er != nil {
if er == io.EOF {
eof = true
} else {
return er
}
}

fmt.Printf("\rReceiving %s (%s of %s, %v%%)...",
filename, humanReadableSize(written), humanReadableSize(size),
int(float64(written)/float64(size)*100))
}
// crypto.go
isValid := Verify(sum[:], p.Signature)
return isValid
// This whitespace should override indicator left on line.
fmt.Printf("\rReceived %s \n", filename)

return nil
}
Loading

0 comments on commit 945952f

Please sign in to comment.