Golang Rewrite

This commit is contained in:
Elara 2020-12-03 02:12:43 -08:00
commit 561bf06826
10 changed files with 790 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea/
opensend
opensend-arm

113
config.go Normal file
View File

@ -0,0 +1,113 @@
package main
import (
"encoding/json"
"github.com/pkg/browser"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
)
// Create config type to store action type and data
type Config struct {
ActionType string
ActionData string
}
// Instantiate and return a new Config struct
func NewConfig(actionType string, actionData string) *Config {
return &Config{ActionType: actionType, ActionData: actionData}
}
// Create config file
func (config *Config) CreateFile(dir string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Create config file at given directory
configFile, err := os.Create(dir + "/config.json")
if err != nil { log.Fatal().Err(err).Msg("Error creating config file") }
// Close config file at the end of this function
defer configFile.Close()
// Marshal given Config struct into a []byte
jsonData, err := json.Marshal(config)
if err != nil { log.Fatal().Err(err).Msg("Error encoding JSON") }
// Write []byte to previously created config file
bytesWritten, err := configFile.Write(jsonData)
if err != nil { log.Fatal().Err(err).Msg("Error writing JSON to file") }
// Log bytes written
log.Info().Str("file", "config.json").Msg("Wrote " + strconv.Itoa(bytesWritten) + " bytes")
}
// Collect all required files into given directory
func (config *Config) CollectFiles(dir string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// If action type is file
if config.ActionType == "file" {
// Open file path in config.ActionData
src, err := os.Open(config.ActionData)
if err != nil { log.Fatal().Err(err).Msg("Error opening file from config") }
// Close source file at the end of this function
defer src.Close()
// Create new file with the same name at given directory
dst, err := os.Create(dir + "/" + filepath.Base(config.ActionData))
if err != nil { log.Fatal().Err(err).Msg("Error creating file") }
// Close new file at the end of this function
defer dst.Close()
// Copy data from source file to destination file
_, err = io.Copy(dst, src)
if err != nil { log.Fatal().Err(err).Msg("Error copying data to file") }
// Replace file path in config.ActionData with file name
config.ActionData = filepath.Base(config.ActionData)
}
}
// Read config file at given file path
func (config *Config) ReadFile(filePath string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Read file at filePath
fileData, err := ioutil.ReadFile(filePath)
if err != nil { log.Fatal().Err(err).Msg("Error reading config file") }
// Unmarshal data from JSON into config struct
err = json.Unmarshal(fileData, config)
if err != nil { log.Fatal().Err(err).Msg("Error decoding JSON") }
}
// Execute action specified in config
func (config *Config) ExecuteAction(srcDir string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// If action is file
if config.ActionType == "file" {
// Open file from config at given directory
src, err := os.Open(srcDir + "/" + config.ActionData)
if err != nil { log.Fatal().Err(err).Msg("Error reading file from config") }
// Close source file at the end of this function
defer src.Close()
// Get user's home directory
homeDir, err := os.UserHomeDir()
if err != nil { log.Fatal().Err(err).Msg("Error getting home directory") }
// Create file in user's Downloads directory
dst, err := os.Create(homeDir + "/Downloads/" + config.ActionData)
if err != nil { log.Fatal().Err(err).Msg("Error creating file") }
// Close destination file at the end of this function
defer dst.Close()
// Copy data from source file to destination file
_, err = io.Copy(dst, src)
if err != nil { log.Fatal().Err(err).Msg("Error copying data to file") }
// If action is url
} else if config.ActionType == "url" {
// Attempt to open URL in browser
err := browser.OpenURL(config.ActionData)
if err != nil { log.Fatal().Err(err).Msg("Error opening browser") }
// Catchall
} else {
// Log unknown action type
log.Fatal().Msg("Unknown action type " + config.ActionType)
}
}

59
deviceDiscovery.go Normal file
View File

@ -0,0 +1,59 @@
package main
import (
"context"
"github.com/grandcat/zeroconf"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
"time"
)
// Discover opensend receivers on the network
func DiscoverReceivers() ([]string, []string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Create zeroconf resolver
resolver, err := zeroconf.NewResolver(nil)
if err != nil { log.Fatal().Err(err).Msg("Error creating zeroconf resolver") }
// Create channel for zeroconf entries
entries := make(chan *zeroconf.ServiceEntry)
// Create slice to store hostnames of discovered receivers
var discoveredReceivers []string
// Create slice to store IPs of discovered receivers
var discoveredReceiverIPs []string
// Concurrently run mDNS query
go func(results <-chan *zeroconf.ServiceEntry) {
// For each entry
for entry := range results {
// Append hostname to discoveredReceivers
discoveredReceivers = append(discoveredReceivers, entry.HostName)
// Append IP to discoveredReceiverIPs
discoveredReceiverIPs = append(discoveredReceiverIPs, entry.AddrIPv4[0].String())
}
}(entries)
// Create context with 4 second timeout
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
// Cancel context at the end of this function
defer cancel()
// Browse for mDNS entries
err = resolver.Browse(ctx, "_opensend._tcp", "local.", entries)
if err != nil { log.Fatal().Err(err).Msg("Error browsing zeroconf services") }
// Send Done signal to context
<-ctx.Done()
// Return discovered receiver slices
return discoveredReceivers, discoveredReceiverIPs
}
// Register opensend zeroconf service on the network
func RegisterService() func() {
// Get computer hostname
hostname, _ := os.Hostname()
// Register zeroconf service {hostname}._opensend._tcp.local.
server, err := zeroconf.Register(hostname, "_opensend._tcp", "local.", 9797, []string{"txtv=0", "lo=1", "la=2"}, nil)
if err != nil { log.Fatal().Err(err).Msg("Error registering zeroconf service") }
// Return server.Shutdown() function to allow for shutdown in main()
return server.Shutdown
}

127
fileCrypto.go Normal file
View File

@ -0,0 +1,127 @@
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/hex"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
// Encrypt given file using the shared key
func EncryptFile(filePath string, newFilePath string, sharedKey string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Read data from file
data, err := ioutil.ReadFile(filePath)
if err != nil { log.Fatal().Err(err).Msg("Error reading file") }
// Create md5 hash of password in order to make it the required size
md5Hash := md5.New()
md5Hash.Write([]byte(sharedKey))
// Encode md5 hash bytes into hexadecimal
hashedKey := hex.EncodeToString(md5Hash.Sum(nil))
// Create new AES cipher
block, _ := aes.NewCipher([]byte(hashedKey))
// Create GCM for AES cipher
gcm, err := cipher.NewGCM(block)
if err != nil { log.Fatal().Err(err).Msg("Error creating GCM") }
// Make byte slice for nonce
nonce := make([]byte, gcm.NonceSize())
// Read random bytes into nonce slice
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil { log.Fatal().Err(err).Msg("Error creating nonce") }
// Encrypt data
ciphertext := gcm.Seal(nonce, nonce, data, nil)
// Create new file
newFile, err := os.Create(newFilePath)
if err != nil { log.Fatal().Err(err).Msg("Error creating file") }
// Defer file close
defer newFile.Close()
// Write ciphertext to new file
bytesWritten, err := newFile.Write(ciphertext)
// Log bytes written and to which file
log.Info().Str("file", filepath.Base(newFilePath)).Msg("Wrote " + strconv.Itoa(bytesWritten) + " bytes")
}
// Decrypt given file using the shared key
func DecryptFile(filePath string, newFilePath string, sharedKey string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Read data from file
data, err := ioutil.ReadFile(filePath)
if err != nil { log.Fatal().Err(err).Msg("Error reading file") }
// Create md5 hash of password in order to make it the required size
md5Hash := md5.New()
md5Hash.Write([]byte(sharedKey))
hashedKey := hex.EncodeToString(md5Hash.Sum(nil))
// Create new AES cipher
block, _ := aes.NewCipher([]byte(hashedKey))
// Create GCM for AES cipher
gcm, err := cipher.NewGCM(block)
if err != nil { log.Fatal().Err(err).Msg("Error creating GCM") }
// Get standard GCM nonce size
nonceSize := gcm.NonceSize()
// Get nonce and ciphertext from data
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
// Decrypt data
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil { log.Fatal().Err(err).Msg("Error decrypting data") }
// Create new file
newFile, err := os.Create(newFilePath)
if err != nil { log.Fatal().Err(err).Msg("Error creating file") }
// Defer file close
defer newFile.Close()
// Write ciphertext to new file
bytesWritten, err := newFile.Write(plaintext)
// Log bytes written and to which file
log.Info().Str("file", filepath.Base(newFilePath)).Msg("Wrote " + strconv.Itoa(bytesWritten) + " bytes")
}
// Encrypt files in given directory using shared key
func EncryptFiles(dir string, sharedKey string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Walk given directory
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
// If error reading, return err
if err != nil { return err }
// If file is not a directory and is not the key
if !info.IsDir() && !strings.Contains(path, "aesKey"){
// Encrypt the file using shared key, appending .enc
EncryptFile(path, path + ".enc", sharedKey)
// Remove unencrypted file
err := os.Remove(path)
if err != nil {return err}
}
// Return nil if no error occurs
return nil
})
if err != nil { log.Fatal().Err(err).Msg("Error encrypting files") }
}
// Decrypt files in given directory using shared key
func DecryptFiles(dir string, sharedKey string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Walk given directory
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
// If error reading, return err
if err != nil { return err }
// If file is not a directory and is encrypted
if !info.IsDir() && strings.Contains(path, ".enc") {
// Decrypt the file using the shared key, removing .enc
DecryptFile(path, strings.TrimSuffix(path, ".enc"), sharedKey)
}
// Return nil if no errors occurred
return nil
})
if err != nil { log.Fatal().Err(err).Msg("Error decrypting files") }
}

166
files.go Normal file
View File

@ -0,0 +1,166 @@
package main
import (
"context"
"fmt"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
)
// Save encrypted key to file
func SaveEncryptedKey(encryptedKey []byte, filePath string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Create file at given file path
keyFile, err := os.Create(filePath)
if err != nil { log.Fatal().Err(err).Msg("Error creating file") }
// Close file at the end of this function
defer keyFile.Close()
// Write encrypted key to file
bytesWritten, err := keyFile.Write(encryptedKey)
if err != nil { log.Fatal().Err(err).Msg("Error writing key to file") }
// Log bytes written
log.Info().Str("file", filepath.Base(filePath)).Msg("Wrote " + strconv.Itoa(bytesWritten) + " bytes")
}
// Create HTTP server to transmit files
func SendFiles(dir string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Instantiate http.Server struct
srv := &http.Server{}
// Listen on all ipv4 addresses on port 9898
listener, err := net.Listen("tcp4", ":9898")
if err != nil { log.Fatal().Err(err).Msg("Error starting listener") }
// If client connects to /:filePath
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
// Set file to first path components of URL, excluding first /
file := req.URL.Path[1:]
// Read file at specified location
fileData, err := ioutil.ReadFile(dir + "/" + file)
// If there was an error reading
if err != nil {
// Warn user of error
log.Warn().Err(err).Msg("Error reading file")
// Otherwise
} else {
// Inform user client has requested a file
log.Info().Str("file", file).Msg("GET File")
}
// Write file to ResponseWriter
_, err = fmt.Fprint(res, string(fileData))
if err != nil { log.Fatal().Err(err).Msg("Error writing response") }
})
// If client connects to /index
http.HandleFunc("/index", func(res http.ResponseWriter, req *http.Request) {
// Inform user a client has requested the file index
log.Info().Msg("GET Index")
// Get directory listing
dirListing, err := ioutil.ReadDir(dir)
if err != nil { log.Fatal().Err(err).Msg("Error reading directory") }
// Create new slice to house filenames for index
var indexSlice []string
// For each file in listing
for _, file := range dirListing {
// If the file is not the key
if !strings.Contains(file.Name(), "savedKey.aesKey") {
// Append the file path to indexSlice
indexSlice = append(indexSlice, dir + "/" + file.Name())
}
}
// Join index slice into string
indexStr := strings.Join(indexSlice, ";")
// Write index to ResponseWriter
_, err = fmt.Fprint(res, indexStr)
if err != nil { log.Fatal().Err(err).Msg("Error writing response") }
})
// If client connects to /key
http.HandleFunc("/key", func(res http.ResponseWriter, req *http.Request) {
// Inform user a client has requested the key
log.Info().Msg("GET Key")
// Read saved key
key, err := ioutil.ReadFile(dir + "/savedKey.aesKey")
if err != nil { log.Fatal().Err(err).Msg("Error reading key") }
// Write saved key to ResponseWriter
_, err = fmt.Fprint(res, string(key))
if err != nil { log.Fatal().Err(err).Msg("Error writing response") }
})
// If client connects to /stop
http.HandleFunc("/stop", func(res http.ResponseWriter, req *http.Request) {
// Inform user a client has requested server shutdown
log.Info().Msg("GET Stop")
log.Info().Msg("Shutdown signal received")
// Shutdown server and send to empty context
err := srv.Shutdown(context.TODO())
if err != nil { log.Fatal().Err(err).Msg("Error stopping server") }
})
// Start HTTP Server
_ = srv.Serve(listener)
}
// Get files from sender
func RecvFiles(dir string, senderAddr string) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Get server address by getting the IP without the port, prepending http:// and appending :9898
serverAddr := "http://" + strings.Split(senderAddr, ":")[0] + ":9898"
// GET /index on sender's HTTP server
response, err := http.Get(serverAddr + "/index")
if err != nil { log.Fatal().Err(err).Msg("Error getting index") }
// Close response body at the end of this function
defer response.Body.Close()
// Create index slice for storage of file index
var index []string
// If server responded with 200 OK
if response.StatusCode == http.StatusOK {
// Read response body
body, err := ioutil.ReadAll(response.Body)
if err != nil { log.Fatal().Err(err).Msg("Error reading HTTP response") }
// Get string from body
bodyStr := string(body)
// Split string to form index
index = strings.Split(bodyStr, ";")
}
// For each file in the index
for _, file := range index {
// GET current file in index
response, err := http.Get(serverAddr + "/" + filepath.Base(file))
if err != nil { log.Fatal().Err(err).Msg("Error getting file") }
// If server responded with 200 OK
if response.StatusCode == http.StatusOK {
// Create new file at index filepath
newFile, err := os.Create(file)
if err != nil { log.Fatal().Err(err).Msg("Error creating file") }
// Copy response body to new file
bytesWritten, err := io.Copy(newFile, response.Body)
if err != nil { log.Fatal().Err(err).Msg("Error writing to file") }
// Log bytes written
log.Info().Str("file", filepath.Base(file)).Msg("Wrote " + strconv.Itoa(int(bytesWritten)) + " bytes")
// Close new file
newFile.Close()
}
// Close response body
response.Body.Close()
}
}
// Send stop signal to sender's HTTP server
func SendSrvStopSignal(senderAddr string) {
// Get server address by getting the IP without the port, prepending http:// and appending :9898
serverAddr := "http://" + strings.Split(senderAddr, ":")[0] + ":9898"
// GET /stop on sender's HTTP servers ignoring any errors
_, _ = http.Get(serverAddr + "/stop")
}

9
go.mod Normal file
View File

@ -0,0 +1,9 @@
module opensend
go 1.15
require (
github.com/grandcat/zeroconf v1.0.0
github.com/pkg/browser v0.0.0-20201112035734-206646e67786
github.com/rs/zerolog v1.20.0
)

36
go.sum Normal file
View File

@ -0,0 +1,36 @@
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/pkg/browser v0.0.0-20201112035734-206646e67786 h1:4Gk0Dsp90g2YwfsxDOjvkEIgKGh+2R9FlvormRycveA=
github.com/pkg/browser v0.0.0-20201112035734-206646e67786/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

75
keyCrypto.go Normal file
View File

@ -0,0 +1,75 @@
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"io/ioutil"
"net/http"
"os"
"strings"
)
// Generate RSA keypair
func GenerateRSAKeypair() (*rsa.PrivateKey, *rsa.PublicKey) {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Generate private/public RSA keypair
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil { log.Fatal().Err(err).Msg("Error generating RSA keypair") }
// Get public key
publicKey := privateKey.PublicKey
// Return keypair
return privateKey, &publicKey
}
// Get public key from sender
func GetKey(senderAddr string) []byte {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Get server address by getting the IP without the port, prepending http:// and appending :9898
serverAddr := "http://" + strings.Split(senderAddr, ":")[0] + ":9898"
// GET /key on the sender's HTTP server
response, err := http.Get(serverAddr + "/key")
if err != nil { log.Fatal().Err(err).Msg("Error getting key") }
// Close response body at the end of this function
defer response.Body.Close()
// If server responded with 200 OK
if response.StatusCode == http.StatusOK {
// Read response body into key
key, err := ioutil.ReadAll(response.Body)
if err != nil { log.Fatal().Err(err).Msg("Error reading HTTP response") }
// Return key
return key
// Otherwise
} else {
// Fatally log status code
if err != nil { log.Fatal().Int("code", response.StatusCode).Msg("HTTP Error Response Code Received") }
}
// Return nil if all else fails
return nil
}
// Encrypt shared key with received public key
func EncryptKey(sharedKey string, recvPubKey *rsa.PublicKey) []byte {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Encrypt shared key using RSA
encryptedSharedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, recvPubKey, []byte(sharedKey), nil)
if err != nil { log.Fatal().Err(err).Msg("Error encrypting shared key") }
// Return encrypted key
return encryptedSharedKey
}
// Decrypt shared key using private RSA key
func DecryptKey(encryptedKey []byte, privateKey *rsa.PrivateKey) string {
// Decrypt shared key using RSA
decryptedKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, encryptedKey, nil)
if err != nil { log.Fatal().Err(err).Msg("Error decrypting shared key") }
// Get string of decrypted key
sharedKey := string(decryptedKey)
// Return shared key
return sharedKey
}

65
keyExchange.go Normal file
View File

@ -0,0 +1,65 @@
package main
import (
"crypto/rsa"
"encoding/gob"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"net"
"os"
)
// Exchange keys with sender
func ReceiverKeyExchange(key *rsa.PublicKey) string {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Create TCP listener on port 9797
listener, err := net.Listen("tcp", ":9797")
if err != nil { log.Fatal().Err(err).Msg("Error starting listener") }
// Create string for sender address
var senderAddr string
// Create channel to send break signal
breakChannel := make(chan bool)
for {
// Accept connection on listener
connection, err := listener.Accept()
// Get sender address and store it in senderAddr
senderAddr = connection.RemoteAddr().String()
if err != nil { log.Fatal().Err(err).Msg("Error accepting connections") }
// Concurrently handle connection
go func(conn net.Conn) {
// Create gob encoder with connection as io.Writer
encoder := gob.NewEncoder(conn)
// Encode key into connection
err := encoder.Encode(key)
if err != nil { log.Fatal().Err(err).Msg("Error encoding key") }
// Send signal to breakChannel
breakChannel <- true
}(connection)
// Wait for break signal
select {
// When break signal arrives
case _ = <-breakChannel:
// Return sender address
return senderAddr
}
}
}
// Exchange keys with receiver
func SenderKeyExchange(receiverIP string) *rsa.PublicKey {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Connect to TCP socket on receiver IP port 9797
connection, err := net.Dial("tcp", receiverIP + ":9797")
if err != nil { log.Fatal().Err(err).Msg("Error connecting to sender") }
// Create gob decoder
decoder := gob.NewDecoder(connection)
// Instantiate rsa.PublicKey struct
recvPubKey := &rsa.PublicKey{}
// Decode key
err = decoder.Decode(recvPubKey)
if err != nil { log.Fatal().Err(err).Msg("Error decoding key") }
// Return received key
return recvPubKey
}

137
main.go Normal file
View File

@ -0,0 +1,137 @@
package main
import (
"bufio"
"crypto/rand"
"encoding/hex"
"flag"
"fmt"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"io"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
)
func main() {
// Use ConsoleWriter logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// Create 32 byte buffer
sharedKeyBytes := make([]byte, 32)
// Read random bytes into buffer
_, err := io.ReadFull(rand.Reader, sharedKeyBytes)
if err != nil { log.Fatal().Err(err).Msg("Error generating random bytes") }
// Encode random bytes to hexadecimal
sharedKey := hex.EncodeToString(sharedKeyBytes)
// Get user's home directory
homeDir, err := os.UserHomeDir()
if err != nil { log.Fatal().Err(err).Msg("Error getting home directory") }
// Define opensend directory as ~/.opensend
opensendDir := homeDir + "/.opensend"
// Create channel for signals
sig := make(chan os.Signal, 1)
// Send message on channel upon reception of SIGINT or SIGTERM
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
// Intercept signal
go func() {
select {
// Wait for sig to be written
case <-sig:
// Warn user that a signal has been received and that opensend is shutting down
log.Warn().Msg("Signal received. Shutting down.")
// Remove opensend directory to avoid future conflicts
_ = os.RemoveAll(opensendDir)
// Exit with code 0
os.Exit(0)
}
}()
// Create -t flag for type
actionType := flag.String("t", "","Type of data being sent")
// Create -d flag for data
actionData := flag.String("d", "", "Data to send")
// Create -s flag for sending
sendFlag := flag.Bool("s", false, "Send data")
// Create -r flag for receiving
recvFlag := flag.Bool("r", false, "Receive data")
// Parse flags
flag.Parse()
// Create opensend dir ignoring errors
_ = os.Mkdir(opensendDir, 0755)
// If -s given
if *sendFlag {
// Discover all _opensend._tcp.local. mDNS services
discoveredReceivers, discoveredIPs := DiscoverReceivers()
// Create reader for STDIN
reader := bufio.NewReader(os.Stdin)
// Print hostnames of each receiver
for index, receiver := range discoveredReceivers {
// Print hostname and index+1
fmt.Println("[" + strconv.Itoa(index + 1) + "]", receiver)
}
// Prompt user for choice
fmt.Print("Choose a receiver: ")
choiceStr, _ := reader.ReadString('\n')
// Convert input to int after trimming spaces
choiceInt, err := strconv.Atoi(strings.TrimSpace(choiceStr))
if err != nil { log.Fatal().Err(err).Msg("Error converting choice to int") }
// Set choiceIndex to choiceInt-1 to allow for indexing
choiceIndex := choiceInt - 1
// Get IP of chosen receiver
choiceIP := discoveredIPs[choiceIndex]
// Exchange RSA keys with receiver
rawKey := SenderKeyExchange(choiceIP)
// Encrypt shared key using RSA public key
key := EncryptKey(sharedKey, rawKey)
// Save encrypted key in opensend directory as savedKey.aesKey
SaveEncryptedKey(key, opensendDir + "/savedKey.aesKey")
// Instantiate Config object
config := NewConfig(*actionType, *actionData)
// Collect any files that may be required for transaction into opensend directory
config.CollectFiles(opensendDir)
// Create config file in opensend directory
config.CreateFile(opensendDir)
// Encrypt all files in opensend directory using shared key
EncryptFiles(opensendDir, sharedKey)
// Send all files in opensend directory using an HTTP server on port 9898
SendFiles(opensendDir)
// If -r given
} else if *recvFlag {
// Register {hostname}._opensend._tcp.local. mDNS service and pass shutdown function
zeroconfShutdown := RegisterService()
// Shutdown zeroconf server at the end of main()
defer zeroconfShutdown()
// Generate keypair
privateKey, publicKey := GenerateRSAKeypair()
// Exchange keys with sender
senderIP := ReceiverKeyExchange(publicKey)
// Sleep 300ms to allow sender time to start HTTP server
time.Sleep(300*time.Millisecond)
// Get files from sender and place them into the opensend directory
RecvFiles(opensendDir, senderIP)
// Get encrypted shared key from sender
encryptedKey := GetKey(senderIP)
// Send stop signal to sender's HTTP server
SendSrvStopSignal(senderIP)
// Decrypt shared key
sharedKey := DecryptKey(encryptedKey, privateKey)
// Decrypt all files in opensend directory using shared key
DecryptFiles(opensendDir, sharedKey)
// Instantiate Config
config := &Config{}
// Read config file in opensend directory
config.ReadFile(opensendDir + "/config.json")
// Execute JSON action using files within opensend directory
config.ExecuteAction(opensendDir)
}
// Remove opensend directory
err = os.RemoveAll(opensendDir)
if err != nil { log.Fatal().Err(err).Msg("Error remove opensend dir") }
}