From cbe0e654728676d6623b1246c4db3ece9e765785 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 20 Mar 2020 14:14:00 -0700 Subject: [PATCH] Initial Commit --- .gitignore | 10 +++ api/api.go | 127 ++++++++++++++++++++++++++ go.mod | 10 +++ go.sum | 62 +++++++++++++ lockbox.go | 36 ++++++++ qml/main.qml | 245 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 490 insertions(+) create mode 100644 .gitignore create mode 100644 api/api.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lockbox.go create mode 100644 qml/main.qml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30b012f --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*moc.cpp +moc_*.go +*cpp +*.h +rcc* +.idea/ +vendor/ +deploy/ +tor/ +*moc.go diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000..da52c46 --- /dev/null +++ b/api/api.go @@ -0,0 +1,127 @@ +package api + +import ( + "bufio" + "crypto/rand" + "encoding/base64" + "encoding/json" + "fmt" + "git.openprivacy.ca/openprivacy/log" + "github.com/therecipe/qt/core" + "github.com/therecipe/qt/qml" + "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/nacl/box" + "io/ioutil" + "os" + "path" + "strings" +) + +type LockBoxAPI struct { + core.QObject + + QMLEngine *qml.QQmlApplicationEngine + Translator *core.QTranslator + + _ func(bool, string) `signal:"Decrypted"` + _ func(bool, string) `signal:"Saved"` + + _ func(inputFilename string, outputFilename string, keyfile string) `signal:"decryptFile,auto"` + _ func(keyFilename string) `signal:"generateKey,auto"` +} + +func (lapi *LockBoxAPI) decryptFile(inputFilename string, outputFilename string, keyfile string) { + log.Infof("Decrypting File...%v to %v", inputFilename, outputFilename) + privateKey := [32]byte{} + publicKey := [32]byte{} + keyencoded, err := ioutil.ReadFile(cleanPath(keyfile)) + if err == nil { + key, err := base64.StdEncoding.DecodeString(string(keyencoded)) + if err == nil { + copy(privateKey[:], key[:]) + curve25519.ScalarBaseMult(&publicKey, &privateKey) + } + } + + file, err := os.Open(cleanPath(inputFilename)) + outputfile := []string{} + if err == nil { + defer file.Close() + + scanner := bufio.NewScanner(file) + schemeDefined := false + schema := []string{} + for scanner.Scan() { + line := scanner.Text() + lines := strings.Split(line, "|") + if len(lines) == 2 { + data, err := base64.StdEncoding.DecodeString(lines[1]) + if err == nil { + message, ok := box.OpenAnonymous([]byte{}, data, &publicKey, &privateKey) + if ok { + log.Infof("Message: %v, %v\n", string(message), ok) + data := make(map[string]string) + err := json.Unmarshal(message, &data) + if err == nil { + outputLine := "" + if schemeDefined == false { + for k := range data { + schema = append(schema, k) + outputLine += fmt.Sprintf(`"%v",`, strings.ReplaceAll(k, "\"", "\\\"")) + } + outputfile = append(outputfile, outputLine) + schemeDefined = true + } + outputLine = "" + for _, k := range schema { + outputLine += fmt.Sprintf(`"%v",`, strings.ReplaceAll(data[k], "\"", "\\\"")) + } + outputfile = append(outputfile, outputLine) + } + } else { + lapi.Decrypted(false, "Error Decrypting File: You might be using the wrong decryption key with this file") + return + } + } + } + } + + if err := scanner.Err(); err != nil { + } + err := ioutil.WriteFile(cleanPath(outputFilename), []byte(strings.Join(outputfile, "\n")), 0644) + if err == nil { + lapi.Decrypted(true, "File Decrypted Successfully!") + return + } + } + + lapi.Decrypted(false, err.Error()) +} + +func (lapi *LockBoxAPI) generateKey(keyPath string) { + log.Infof("Saving Key to Path to %v", keyPath) + public, private, err := box.GenerateKey(rand.Reader) + if err == nil { + publicStr := base64.StdEncoding.EncodeToString(public[:]) + privateStr := base64.StdEncoding.EncodeToString(private[:]) + err = ioutil.WriteFile(path.Join(cleanPath(keyPath), "key.public"), []byte(publicStr), 0644) + if err == nil { + err = ioutil.WriteFile(path.Join(cleanPath(keyPath), "key.private"), []byte(privateStr), 0644) + if err == nil { + lapi.Saved(true, "Key File Saved") + return + } else { + lapi.Saved(false, err.Error()) + return + } + } else { + lapi.Saved(false, err.Error()) + return + } + } + lapi.Saved(false, err.Error()) +} + +func cleanPath(path string) string { + return strings.Replace(path, "file://", "", 1) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cb4b39b --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module git.openprivacy.ca/openprivacy/lockbox + +go 1.13 + +require ( + cwtch.im/tapir v0.1.17 // indirect + git.openprivacy.ca/openprivacy/log v1.0.0 + github.com/therecipe/qt v0.0.0-20191101232336-18864661ae4f + golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0701bb6 --- /dev/null +++ b/go.sum @@ -0,0 +1,62 @@ +cwtch.im/tapir v0.1.17 h1:2jVZUe1a88tMI4aJPvRTO4Id3NN3PsM62cT5lntEChk= +cwtch.im/tapir v0.1.17/go.mod h1:HzezugpEx+nZ3LdyDsl0w6n45IJYnOt8uqldkLWmaqs= +git.openprivacy.ca/openprivacy/connectivity v1.1.0 h1:9PEeKuPdoIRYeA62BUkBW2BfK4KqKEXz1fvUxZoP4xs= +git.openprivacy.ca/openprivacy/connectivity v1.1.0/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E= +git.openprivacy.ca/openprivacy/connectivity v1.1.1 h1:hKxBOmxP7Jdu3K1BJ93mRtKNiWUoP6YHt/o2snE2Z0w= +git.openprivacy.ca/openprivacy/connectivity v1.1.1/go.mod h1:4P8mirZZslKbo2zBrXXVjgEdqGwHo/6qoFBwFQW6d6E= +git.openprivacy.ca/openprivacy/libricochet-go v1.0.11 h1:C7QFFzG0p5XKu0zcOIdLGwEpA9uU0BceBM7CfVK5D40= +git.openprivacy.ca/openprivacy/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y= +git.openprivacy.ca/openprivacy/log v1.0.0/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca h1:Q2r7AxHdJwWfLtBZwvW621M3sPqxPc6ITv2j1FGsYpw= +github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA= +github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= +github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= +github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/therecipe/qt v0.0.0-20191101232336-18864661ae4f h1:06ICDSmDOBUC9jwgv44ngvyHzwudJNLa5H+rbCyDFRY= +github.com/therecipe/qt v0.0.0-20191101232336-18864661ae4f/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a h1:aczoJ0HPNE92XKa7DrIzkNN6esOKO2TBwiiYoKcINhA= +golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/lockbox.go b/lockbox.go new file mode 100644 index 0000000..c1e78fa --- /dev/null +++ b/lockbox.go @@ -0,0 +1,36 @@ +package main + +import ( + "git.openprivacy.ca/openprivacy/lockbox/api" + "git.openprivacy.ca/openprivacy/log" + "github.com/therecipe/qt/core" + "github.com/therecipe/qt/qml" + "github.com/therecipe/qt/quickcontrols2" + "github.com/therecipe/qt/widgets" + "os" +) + +func main() { + log.SetLevel(log.LevelDebug) + + core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true) + + app := widgets.NewQApplication(len(os.Args), os.Args) + app.SetAttribute(core.Qt__AA_EnableHighDpiScaling, true) + quickcontrols2.QQuickStyle_SetStyle("Material") + engine := qml.NewQQmlApplicationEngine(nil) + + lockapi := api.NewLockBoxAPI(nil) + + engine.RootContext().SetContextProperty("lockbox", lockapi) + // load the embedded qml file + // created by either qtrcc or qtdeploy + //engine.Load(core.NewQUrl3("qrc:/qml/main.qml", 0)) + // you can also load a local file like this instead: + engine.Load(core.QUrl_FromLocalFile("./qml/main.qml")) + + // start the main Qt event loop + // and block until app.Exit() is called + // or the window is closed by the user + widgets.QApplication_Exec() +} diff --git a/qml/main.qml b/qml/main.qml new file mode 100644 index 0000000..c6eaf14 --- /dev/null +++ b/qml/main.qml @@ -0,0 +1,245 @@ +import QtQuick 2.7 //ApplicationWindow +import QtQuick.Controls 2.1 //Dialog +import QtQuick.Controls 2.12 +import QtQuick 2.12 +import QtQuick.Dialogs 1.3 +import QtQuick.Layouts 1.12 + + +ApplicationWindow { + id: root + visible: true + title: "Open Privacy LockBox" + minimumWidth: 640 + minimumHeight: 640 + + FileDialog { + id: fileDialog + title: "Please choose a file to decrypt" + nameFilters: [ "Dat File (*.dat)", "All files (*)" ] + folder: shortcuts.home + selectFolder: false + selectMultiple: false + onAccepted: { + console.log("You chose: " + fileDialog.fileUrls) + inputFileLabel.text = fileDialog.fileUrls[0] + } + onRejected: { + console.log("Canceled") + } + Component.onCompleted: visible = false + } + + Column { + anchors.horizontalCenter: parent.horizontalCenter + width:parent.width + padding:10 + + Row { + width:parent.width + padding:10 + Label { + text: "Decrypt a File" + font.pixelSize: 22 + } + } + + Row { + width:parent.width + padding:10 + TextField { + width:parent.width * 0.50 + id:inputFileLabel + placeholderText: "Select an Input File" + } + Button { + Layout.alignment: Qt.AlignRight + text: "Select File To Decrypt" + onClicked: fileDialog.visible = true + } + } + Row { + width:parent.width + padding:10 + TextField { + id:outputFileLabel + width:parent.width * 0.50 + placeholderText: "Select an Output File" + } + Button { + Layout.alignment: Qt.AlignRight + text: "Select File To Output" + onClicked: outputFileDialog.visible = true + } + } + Row { + width:parent.width + padding:10 + TextField { + width:parent.width * 0.50 + id:keyFileLabel + placeholderText: "Select a Key File" + } + Button { + Layout.alignment: Qt.AlignRight + text: "Select Private Key" + onClicked: keyFileDialog.visible = true + } + } + Row { + width:parent.width + padding:10 + Button { + text: "Decrypt" + onClicked: function() { + lockbox.decryptFile(fileDialog.fileUrls[0],outputFileDialog.fileUrls[0],keyFileDialog.fileUrls[0]) + } + } + } + Row { + width:parent.width + padding:10 + Rectangle { + width: parent.width-20 + height: 1 + color: "black" + border.color: "black" + border.width: 1 + } + } + + Row { + width:parent.width + padding:10 + Label { + text: "Generate New Encryption Keys" + font.pixelSize: 22 + } + } + Row { + width:parent.width + padding:10 + TextField { + width:parent.width * 0.50 + id:keyFileGenLabel + placeholderText: "Select a Key File" + } + Button { + Layout.alignment: Qt.AlignRight + text: "Select a Folder to Save Keys" + onClicked: function() { + keyFileCreateDialog.visible = true + } + } + } + + Row { + width:parent.width + Button { + text: "Generate a Decryption Key" + onClicked: function() { + lockbox.generateKey(keyFileCreateDialog.fileUrls[0]) + } + } + } + + } + + + FileDialog { + id: outputFileDialog + title: "Please choose a file save to" + nameFilters: [ "CSV File (*.csv)", "All files (*)" ] + folder: shortcuts.home + selectFolder: false + selectMultiple: false + selectExisting:false + onAccepted: { + console.log("You chose: " + outputFileDialog.fileUrls) + outputFileLabel.text = outputFileDialog.fileUrls[0] + } + onRejected: { + console.log("Canceled") + } + Component.onCompleted: visible = false + + + } + + + FileDialog { + id: keyFileDialog + title: "Please choose an encryption key file" + nameFilters: [ "Key File (*.private)", "All files (*)" ] + folder: shortcuts.home + selectFolder: false + selectMultiple: false + onAccepted: { + console.log("You chose: " + keyFileDialog.fileUrls) + keyFileLabel.text = keyFileDialog.fileUrls[0] + } + onRejected: { + console.log("Canceled") + } + Component.onCompleted: visible = false + } + + FileDialog { + id: keyFileCreateDialog + title: "Please choose where to save the encryption key file" + nameFilters: [ "Key File (*.private)", "All files (*)" ] + folder: shortcuts.home + selectFolder: true + selectMultiple: false + onAccepted: { + console.log("You chose: " + keyFileCreateDialog.fileUrls) + keyFileGenLabel.text = keyFileCreateDialog.fileUrls[0] + } + onRejected: { + console.log("Canceled") + } + Component.onCompleted: visible = false + } + + Connections { // POPUPS ARE INVOKED BY GO FUNCS + target: lockbox + + onDecrypted: function(status, message) { + if (status) { + messageDialog.icon = StandardIcon.Information + messageDialog.title = "Decrypted Successfully" + } else { + messageDialog.icon = StandardIcon.Critical + messageDialog.title = "Error Decrypting" + } + + messageDialog.text = message + messageDialog.open() + } + + onSaved: function(status, message) { + if (status) { + messageDialog.icon = StandardIcon.Information + messageDialog.title = "Encryption Keys Generating Successfully" + } else { + messageDialog.icon = StandardIcon.Critical + messageDialog.title = "Error Generating Encryption Keys" + } + + messageDialog.text = message + messageDialog.open() + } + } + + + MessageDialog { + id: messageDialog + title: "May I have your attention please" + text: "It's so cool that you are using Qt Quick." + onAccepted: { + messageDialog.close() + } + Component.onCompleted: visible = false + } + +}