Golang Rewrite
This commit is contained in:
commit
561bf06826
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.idea/
|
||||
opensend
|
||||
opensend-arm
|
113
config.go
Normal file
113
config.go
Normal 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
59
deviceDiscovery.go
Normal 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
127
fileCrypto.go
Normal 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
166
files.go
Normal 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
9
go.mod
Normal 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
36
go.sum
Normal 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
75
keyCrypto.go
Normal 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
65
keyExchange.go
Normal 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
137
main.go
Normal 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") }
|
||||
}
|
Reference in New Issue
Block a user