diff --git a/CHANGELOG.md b/CHANGELOG.md index fbaf266b..21c91c64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.0.0] - 2019-12-?? +## [2.0.0] - 2020-01-06 Since the open-sourcing of the library in May the API has been updated, listening to internal and external feedback, in order to have a flexible library, that can be used in a simple settings, with batteries included, or by more advanced users that might want to interact directly with diff --git a/README.md b/README.md index fa44aad4..ab47b5ff 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,28 @@ https://godoc.org/gopkg.in/ProtonMail/gopenpgp.v2/crypto In this document examples are provided and the proper use of (almost) all functions is tested. ## Using with Go Mobile -The use with gomobile is still to be documented +This library can be compiled with [Gomobile](https://github.com/golang/go/wiki/Mobile) too. +First ensure you have a working installation of gomobile: +```bash +gomobile version +``` +In case this fails, install it with: +```bash +go get -u golang.org/x/mobile/cmd/gomobile +``` +Then ensure your path env var has gomobile's binary, and it is properly init-ed: +```bash +export PATH="$PATH:$GOPATH/bin" +gomobile init +``` +Then you must ensure that the Android or iOS frameworks are installed and the respective env vars set. + +Finally, build the application +```bash +sh build.sh +``` +This script will build for both android and iOS at the same time, +to filter one out you can comment out the line in the corresponding section. ## Examples diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..d0071266 --- /dev/null +++ b/build.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +PACKAGE_PATH="github.com/ProtonMail/gopenpgp" +cd "${GOPATH}"/src/${PACKAGE_PATH} || exit +if ! [ -L "v2" ]; then + ln -s . v2 +fi + +printf "\e[0;32mStart installing vendor \033[0m\n\n" +export GO111MODULE=on +go mod vendor +printf "\e[0;32mDone \033[0m\n\n" + +OUTPUT_PATH="dist" + +ANDROID_OUT=${OUTPUT_PATH}/"Android" +ANDROID_OUT_FILE_NAME="gopenpgp" +ANDROID_OUT_FILE=${ANDROID_OUT}/${ANDROID_OUT_FILE_NAME}.aar +ANDROID_JAVA_PAG="com.proton.${ANDROID_OUT_FILE_NAME}" + +IOS_OUT=${OUTPUT_PATH}/"iOS" +IOS_OUT_FILE_NAME="Crypto" +IOS_OUT_FILE=${IOS_OUT}/${IOS_OUT_FILE_NAME}.framework + + + +mkdir -p $ANDROID_OUT +mkdir -p $IOS_OUT + +install() +{ + INSTALL_NAME=$1 + FROM_PATH=$2 + INSTALL_PATH=$3 + if [[ -z "${INSTALL_PATH}" ]]; then + printf "\e[0;32m ${INSTALL_NAME} project path is undefined! ignore this !\033[0m\n"; + else + printf "\n\e[0;32mDo you wise to install the library into ${INSTALL_NAME} project \033[0m\n" + printf "\e[0;37m${INSTALL_NAME} Project Path: \033[0m" + printf "\e[0;37m${INSTALL_PATH} \033[0m" + printf "\n" + while true; do + read -p "[Yy] or [Nn]:" yn + case $yn in + [Yy]* ) + printf "\e[0;32m Installing .... \033[0m\n"; + cp -rf ${FROM_PATH} ${INSTALL_PATH}/ + printf "\n\e[0;32mInstalled \033[0m\n\n" + break;; + [Nn]* ) exit;; + * ) echo "Please answer yes or no.";; + esac + done + fi +} + +# import function, add internal package in the build +import() +{ + PACKAGES=" ${PACKAGES} ${PACKAGE_PATH}/v2/$1" +} + +external() +{ + PACKAGES="${PACKAGES} $1" +} + +######## MARK -- Main + +#flags +DFLAGS="-s -w" + +PACKAGES="" +#add internal package +## crypto must be the first one, and the framework name better same with the first package name +import crypto +import armor +import constants +import models +import subtle +import helper + +## add external package +if [ "$1" != '' ]; then + external $1 +fi + +printf "PACKAGES: ${PACKAGES}\n" +## start building + +printf "\e[0;32mStart Building iOS framework .. Location: ${IOS_OUT} \033[0m\n\n" +gomobile bind -target ios -o ${IOS_OUT_FILE} -ldflags="${DFLAGS}" ${PACKAGES} +# install iOS ${IOS_OUT_FILE} ${IOS_PROJECT_PATH} + +printf "\e[0;32mStart Building Android lib .. Location: ${ANDROID_OUT} \033[0m\n\n" +gomobile bind -target android -javapkg ${ANDROID_JAVA_PAG} -o ${ANDROID_OUT_FILE} -ldflags="${DFLAGS}" ${PACKAGES} +# install Android ${ANDROID_OUT} ${ANDROID_PROJECT_PATH} + +printf "\e[0;32mInstalling frameworks. \033[0m\n\n" + +printf "\e[0;32mAll Done. \033[0m\n\n" \ No newline at end of file diff --git a/helper/helper.go b/helper/helper.go index b8ffd073..c232eb1a 100644 --- a/helper/helper.go +++ b/helper/helper.go @@ -184,50 +184,6 @@ func DecryptVerifyMessageArmored( return message.GetString(), nil } -// EncryptSignAttachment encrypts an attachment using a detached signature, given a publicKey, a privateKey -// and its passphrase, the filename, and the unencrypted file data. -// Returns keypacket, dataPacket and unarmored (!) signature separate. -func EncryptSignAttachment( - publicKey, privateKey string, passphrase []byte, fileName string, plainData []byte, -) (keyPacket, dataPacket, signature []byte, err error) { - var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key - var publicKeyRing, privateKeyRing *crypto.KeyRing - var packets *crypto.PGPSplitMessage - var signatureObj *crypto.PGPSignature - - var binMessage = crypto.NewPlainMessage(plainData) - - if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil { - return nil, nil, nil, err - } - - if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil { - return nil, nil, nil, err - } - - if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil { - return nil, nil, nil, err - } - - if unlockedKeyObj, err = privateKeyObj.Unlock(passphrase); err != nil { - return nil, nil, nil, err - } - - if privateKeyRing, err = crypto.NewKeyRing(unlockedKeyObj); err != nil { - return nil, nil, nil, err - } - - if packets, err = publicKeyRing.EncryptAttachment(binMessage, fileName); err != nil { - return nil, nil, nil, err - } - - if signatureObj, err = privateKeyRing.SignDetached(binMessage); err != nil { - return nil, nil, nil, err - } - - return packets.GetBinaryKeyPacket(), packets.GetBinaryDataPacket(), signatureObj.GetBinary(), nil -} - // DecryptVerifyAttachment decrypts and verifies an attachment split into the keyPacket, dataPacket // and an armored (!) signature, given a publicKey, and a privateKey with its passphrase. // Returns the plain data or an error on signature verification failure. diff --git a/helper/ios.go b/helper/ios.go deleted file mode 100644 index 654858aa..00000000 --- a/helper/ios.go +++ /dev/null @@ -1,43 +0,0 @@ -package helper - -import ( - "github.com/ProtonMail/gopenpgp/v2/crypto" -) - -// ExplicitVerifyMessage contains explicitly the signature verification error, for gomobile users -type ExplicitVerifyMessage struct { - Message *crypto.PlainMessage - SignatureVerificationError *crypto.SignatureVerificationError -} - -// DecryptExplicitVerify decrypts an armored PGP message given a private key and its passphrase -// and verifies the embedded signature. -// Returns the plain data or an error on signature verification failure. -func DecryptExplicitVerify( - pgpMessage *crypto.PGPMessage, - privateKeyRing, publicKeyRing *crypto.KeyRing, - verifyTime int64, -) (*ExplicitVerifyMessage, error) { - var explicitVerify *ExplicitVerifyMessage - - message, err := privateKeyRing.Decrypt(pgpMessage, publicKeyRing, verifyTime) - - if err != nil { - castedErr, isType := err.(crypto.SignatureVerificationError) - if !isType { - return nil, err - } - - explicitVerify = &ExplicitVerifyMessage{ - Message: message, - SignatureVerificationError: &castedErr, - } - } else { - explicitVerify = &ExplicitVerifyMessage{ - Message: message, - SignatureVerificationError: nil, - } - } - - return explicitVerify, nil -} diff --git a/helper/mobile.go b/helper/mobile.go new file mode 100644 index 00000000..f2360e7e --- /dev/null +++ b/helper/mobile.go @@ -0,0 +1,67 @@ +package helper + +import ( + "github.com/ProtonMail/gopenpgp/v2/crypto" +) + +type ExplicitVerifyMessage struct { + Message *crypto.PlainMessage + SignatureVerificationError *crypto.SignatureVerificationError +} + +// DecryptVerifyMessageArmored decrypts an armored PGP message given a private key and its passphrase +// and verifies the embedded signature. +// Returns the plain data or an error on signature verification failure. +func DecryptExplicitVerify( + pgpMessage *crypto.PGPMessage, + privateKeyRing, publicKeyRing *crypto.KeyRing, + verifyTime int64, +) (*ExplicitVerifyMessage, error) { + var explicitVerify *ExplicitVerifyMessage + + message, err := privateKeyRing.Decrypt(pgpMessage, publicKeyRing, verifyTime) + + if err != nil { + castedErr, isType := err.(crypto.SignatureVerificationError) + if !isType { + return nil, err + } + + explicitVerify = &ExplicitVerifyMessage{ + Message: message, + SignatureVerificationError: &castedErr, + } + } else { + explicitVerify = &ExplicitVerifyMessage{ + Message: message, + SignatureVerificationError: nil, + } + } + + return explicitVerify, nil +} + +// DecryptAttachment takes a keypacket and datpacket +// and returns a decrypted PlainMessage +// Specifically designed for attachments rather than text messages. +func DecryptAttachment(keyPacket []byte, dataPacket []byte, keyRing *crypto.KeyRing) (*crypto.PlainMessage, error) { + splitMessage := crypto.NewPGPSplitMessage(keyPacket, dataPacket) + + decrypted, err := keyRing.DecryptAttachment(splitMessage) + if err != nil { + return nil, err + } + return decrypted, nil +} + +// EncryptAttachment encrypts a file given a plainData and a fileName. +// Returns a PGPSplitMessage containing a session key packet and symmetrically encrypted data. +// Specifically designed for attachments rather than text messages. +func EncryptAttachment(plainData []byte, fileName string, keyRing *crypto.KeyRing) (*crypto.PGPSplitMessage, error) { + plainMessage := crypto.NewPlainMessage(plainData) + decrypted, err := keyRing.EncryptAttachment(plainMessage, fileName) + if err != nil { + return nil, err + } + return decrypted, nil +} diff --git a/helper/ios_test.go b/helper/mobile_test.go similarity index 97% rename from helper/ios_test.go rename to helper/mobile_test.go index c1ae6448..fd6f8283 100644 --- a/helper/ios_test.go +++ b/helper/mobile_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestIOSSignedMessageDecryption(t *testing.T) { +func TestMobileSignedMessageDecryption(t *testing.T) { privateKey, _ := crypto.NewKeyFromArmored(readTestFile("keyring_privateKey", false)) // Password defined in base_test privateKey, err := privateKey.Unlock(testMailboxPassword) diff --git a/helper/sign_attachment.go b/helper/sign_attachment.go new file mode 100644 index 00000000..2cb00033 --- /dev/null +++ b/helper/sign_attachment.go @@ -0,0 +1,50 @@ +// +build !ios +// +build !android + +package helper + +import "github.com/ProtonMail/gopenpgp/v2/crypto" + +// EncryptSignAttachment encrypts an attachment using a detached signature, given a publicKey, a privateKey +// and its passphrase, the filename, and the unencrypted file data. +// Returns keypacket, dataPacket and unarmored (!) signature separate. +func EncryptSignAttachment( + publicKey, privateKey string, passphrase []byte, fileName string, plainData []byte, +) (keyPacket, dataPacket, signature []byte, err error) { + var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key + var publicKeyRing, privateKeyRing *crypto.KeyRing + var packets *crypto.PGPSplitMessage + var signatureObj *crypto.PGPSignature + + var binMessage = crypto.NewPlainMessage(plainData) + + if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil { + return nil, nil, nil, err + } + + if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil { + return nil, nil, nil, err + } + + if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil { + return nil, nil, nil, err + } + + if unlockedKeyObj, err = privateKeyObj.Unlock(passphrase); err != nil { + return nil, nil, nil, err + } + + if privateKeyRing, err = crypto.NewKeyRing(unlockedKeyObj); err != nil { + return nil, nil, nil, err + } + + if packets, err = publicKeyRing.EncryptAttachment(binMessage, fileName); err != nil { + return nil, nil, nil, err + } + + if signatureObj, err = privateKeyRing.SignDetached(binMessage); err != nil { + return nil, nil, nil, err + } + + return packets.GetBinaryKeyPacket(), packets.GetBinaryDataPacket(), signatureObj.GetBinary(), nil +}