diff --git a/armor.go b/armor.go deleted file mode 100644 index 071f4ad..0000000 --- a/armor.go +++ /dev/null @@ -1,54 +0,0 @@ -package pm - -import ( - "bytes" - "io/ioutil" - "strings" - - "golang.org/x/crypto/openpgp/armor" -) - -const ( - pgpMessageType string = "PGP MESSAGE" - pgpPublicBlockType string = "PGP PUBLIC KEY BLOCK" - pgpPrivateBlockType string = "PGP PRIVATE KEY BLOCK" -) - -// ArmorKey make bytes input key to armor format -func ArmorKey(input []byte) (string, error) { - return ArmorWithType(input, pgpPublicBlockType) -} - -// ArmorWithType make bytes input to armor format -func ArmorWithType(input []byte, armorType string) (string, error) { - var b bytes.Buffer - w, err := armor.Encode(&b, armorType, armorHeader) - if err != nil { - return "", err - } - _, err = w.Write(input) - if err != nil { - return "", err - } - w.Close() - return b.String(), nil -} - -// UnArmor an armored key to bytes key -func UnArmor(input string) ([]byte, error) { - b, err := unArmor(input) - if err != nil { - return nil, err - } - return ioutil.ReadAll(b.Body) -} - -func unArmor(input string) (*armor.Block, error) { - io := strings.NewReader(input) - b, err := armor.Decode(io) - if err != nil { - return nil, err - } - return b, nil - -} diff --git a/armor/armor.go b/armor/armor.go new file mode 100644 index 0000000..678f0dd --- /dev/null +++ b/armor/armor.go @@ -0,0 +1,129 @@ +package armor + +import ( + "bytes" + "errors" + "gitlab.com/ProtonMail/go-pm-crypto/internal" + "gitlab.com/ProtonMail/go-pm-crypto/models" + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/clearsign" + "golang.org/x/crypto/openpgp/packet" + "io" + "io/ioutil" +) + +// ArmorKey make bytes input key to armor format +func ArmorKey(input []byte) (string, error) { + return ArmorWithType(input, PUBLIC_KEY_HEADER) +} + +// ArmorWithType make bytes input to armor format +func ArmorWithType(input []byte, armorType string) (string, error) { + var b bytes.Buffer + w, err := armor.Encode(&b, armorType, internal.ArmorHeaders) + if err != nil { + return "", err + } + _, err = w.Write(input) + if err != nil { + return "", err + } + w.Close() + return b.String(), nil +} + +// Unarmor an armored key to bytes key +func Unarmor(input string) ([]byte, error) { + b, err := internal.Unarmor(input) + if err != nil { + return nil, err + } + return ioutil.ReadAll(b.Body) +} + +//ReadClearSignedMessage read clear message from a clearsign package +func ReadClearSignedMessage(signedMessage string) (string, error) { + modulusBlock, rest := clearsign.Decode([]byte(signedMessage)) + if len(rest) != 0 { + return "", errors.New("pmapi: extra data after modulus") + } + return string(modulusBlock.Bytes), nil +} + +//SeparateKeyAndData ... +func SplitArmor(encrypted string) (*models.EncryptedSplit, error) { + + var err error + + encryptedRaw, err := Unarmor(encrypted) + if err != nil { + return nil, err + } + + encryptedReader := bytes.NewReader(encryptedRaw) + + //kr *KeyRing, r io.Reader) (key *SymmetricKey, symEncryptedData []byte, + packets := packet.NewReader(encryptedReader) + + outSplit := &models.EncryptedSplit{} + + // Save encrypted key and signature apart + var ek *packet.EncryptedKey + // var decryptErr error + for { + var p packet.Packet + if p, err = packets.Next(); err == io.EOF { + err = nil + break + } + switch p := p.(type) { + case *packet.EncryptedKey: + // We got an encrypted key. Try to decrypt it with each available key + if ek != nil && ek.Key != nil { + break + } + ek = p + break + case *packet.SymmetricallyEncrypted: + var packetContents []byte + if packetContents, err = ioutil.ReadAll(p.Contents); err != nil { + return nil, err + } + + encodedLength := encodedLength(len(packetContents) + 1) + var symEncryptedData []byte + symEncryptedData = append(symEncryptedData, byte(210)) + symEncryptedData = append(symEncryptedData, encodedLength...) + symEncryptedData = append(symEncryptedData, byte(1)) + symEncryptedData = append(symEncryptedData, packetContents...) + + outSplit.DataPacket = symEncryptedData + break + + } + } + + var buf bytes.Buffer + ek.Serialize(&buf) + outSplit.KeyPacket = buf.Bytes() + + return outSplit, err +} + +//encode length based on 4.2.2. in the RFC +func encodedLength(length int) (b []byte) { + if length < 192 { + b = append(b, byte(length)) + } else if length < 8384 { + length = length - 192 + b = append(b, 192+byte(length>>8)) + b = append(b, byte(length)) + } else { + b = append(b, byte(255)) + b = append(b, byte(length>>24)) + b = append(b, byte(length>>16)) + b = append(b, byte(length>>8)) + b = append(b, byte(length)) + } + return +} diff --git a/armor/constants.go b/armor/constants.go new file mode 100644 index 0000000..191d19b --- /dev/null +++ b/armor/constants.go @@ -0,0 +1,13 @@ +package armor + +import ( + "gitlab.com/ProtonMail/go-pm-crypto/internal" +) + +const ( + ARMOR_HEADER_VERSION = internal.ARMOR_HEADER_VERSION + ARMOR_HEADER_COMMENT = internal.ARMOR_HEADER_COMMENT + MESSAGE_HEADER string = "PGP MESSAGE" + PUBLIC_KEY_HEADER string = "PGP PUBLIC KEY BLOCK" + PRIVATE_KEY_HEADER string = "PGP PRIVATE KEY BLOCK" +) diff --git a/build.sh b/build.sh index f253880..ae2de9f 100755 --- a/build.sh +++ b/build.sh @@ -2,21 +2,20 @@ SCRIPT_LOCATION=$(cd $(dirname $0);echo $PWD) -OUTPUT_PATH="bin" +OUTPUT_PATH="dist" ANDROID_OUT=${OUTPUT_PATH}/"Android" IOS_OUT=${OUTPUT_PATH}/"iOS" - +mkdir -p $ANDROID_OUT +mkdir -p $IOS_OUT # CHECK="${1-0}" # if [ ${CHECK} -eq "1" ]; then printf "\e[0;32mStart Building iOS framework .. Location: ${IOS_OUT} \033[0m\n\n" -gomobile bind -target ios -o ${IOS_OUT}/PM.framework - +gomobile bind -target ios -o ${IOS_OUT}/pmcrypto.framework proton/pmcrypto/crypto proton/pmcrypto/armor proton/pmcrypto/constants proton/pmcrypto/key proton/pmcrypto/models printf "\e[0;32mStart Building Android lib .. Location: ${ANDROID_OUT} \033[0m\n\n" -gomobile bind -target android -o ${ANDROID_OUT}/PM.aar - +gomobile bind -target android -javapkg com.proton.pmcrypto -o ${ANDROID_OUT}/pmcrypto.aar proton/pmcrypto/crypto proton/pmcrypto/armor proton/pmcrypto/constants proton/pmcrypto/key proton/pmcrypto/models printf "\e[0;32mInstalling frameworks. \033[0m\n\n" diff --git a/common.go b/common.go deleted file mode 100644 index e93bfee..0000000 --- a/common.go +++ /dev/null @@ -1,36 +0,0 @@ -package pm - -import ( - "regexp" -) - -var armorHeader = map[string]string{ - "Version": "OpenPGP Golang 0.0.1 (" + Version() + ")", - "Comment": "https://protonmail.com", -} - -// Key ... add later -// protonmail key object -type Key struct { - KeyID string - PublicKey string - PrivateKey string - FingerPrint string -} - -//Address ... add later protonmail address object -type Address struct { - // address_id : string; - // #optional - // address_name : string; - keys []Key -} - -func trimNewlines(input string) string { - var re = regexp.MustCompile(`(?m)[ \t]*$`) - return re.ReplaceAllString(input, "") -} - -// Amount of seconds that a signature may be created after the verify time -// Consistent with the 2 day slack allowed in the ProtonMail Email Parser -var creationTimeOffset = int64(60 * 60 * 24 * 2) \ No newline at end of file diff --git a/constants/version.go b/constants/version.go new file mode 100644 index 0000000..066d937 --- /dev/null +++ b/constants/version.go @@ -0,0 +1,3 @@ +package constants + +const VERSION = "ddacebe0" \ No newline at end of file diff --git a/attachment.go b/crypto/attachment.go similarity index 63% rename from attachment.go rename to crypto/attachment.go index 8e416a2..f5c8019 100644 --- a/attachment.go +++ b/crypto/attachment.go @@ -1,4 +1,4 @@ -package pm +package crypto import ( "bytes" @@ -8,13 +8,16 @@ import ( "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/packet" + armorUtils "proton/pmcrypto/armor" + "proton/pmcrypto/internal" + "proton/pmcrypto/models" ) //EncryptAttachmentBinKey ... -func (o *OpenPGP) EncryptAttachmentBinKey(plainData []byte, fileName string, publicKey []byte) (*EncryptedSplit, error) { +func (pm *PmCrypto) EncryptAttachmentBinKey(plainData []byte, fileName string, publicKey []byte) (*models.EncryptedSplit, error) { var outBuf bytes.Buffer - w, err := armor.Encode(&outBuf, pgpMessageType, armorHeader) + w, err := armor.Encode(&outBuf, armorUtils.MESSAGE_HEADER, internal.ArmorHeaders) if err != nil { return nil, err } @@ -30,7 +33,7 @@ func (o *OpenPGP) EncryptAttachmentBinKey(plainData []byte, fileName string, pub config := &packet.Config{ DefaultCipher: packet.CipherAES256, - Time: o.getTimeGenerator(), + Time: pm.getTimeGenerator(), } ew, err := openpgp.Encrypt(w, pubKeyEntries, nil, hints, config) @@ -39,7 +42,7 @@ func (o *OpenPGP) EncryptAttachmentBinKey(plainData []byte, fileName string, pub ew.Close() w.Close() - split, err := SeparateKeyAndData(outBuf.String()) + split, err := armorUtils.SplitArmor(outBuf.String()) if err != nil { return nil, err } @@ -48,19 +51,19 @@ func (o *OpenPGP) EncryptAttachmentBinKey(plainData []byte, fileName string, pub } //EncryptAttachment ... -func (o *OpenPGP) EncryptAttachment(plainData []byte, fileName string, publicKey string) (*EncryptedSplit, error) { - rawPubKey, err := UnArmor(publicKey) +func (pm *PmCrypto) EncryptAttachment(plainData []byte, fileName string, publicKey string) (*models.EncryptedSplit, error) { + rawPubKey, err := armorUtils.Unarmor(publicKey) if err != nil { return nil, err } - return o.EncryptAttachmentBinKey(plainData, fileName, rawPubKey) + return pm.EncryptAttachmentBinKey(plainData, fileName, rawPubKey) } //DecryptAttachmentBinKey ... //keyPacket //dataPacket //privateKeys could be mutiple private keys -func (o *OpenPGP) DecryptAttachmentBinKey(keyPacket []byte, dataPacket []byte, privateKeys []byte, passphrase string) ([]byte, error) { +func (pm *PmCrypto) DecryptAttachmentBinKey(keyPacket []byte, dataPacket []byte, privateKeys []byte, passphrase string) ([]byte, error) { privKeyRaw := bytes.NewReader(privateKeys) privKeyEntries, err := openpgp.ReadKeyRing(privKeyRaw) if err != nil { @@ -86,7 +89,7 @@ func (o *OpenPGP) DecryptAttachmentBinKey(keyPacket []byte, dataPacket []byte, p encryptedReader := io.MultiReader(keyReader, dataReader) - config := &packet.Config{ Time: o.getTimeGenerator() } + config := &packet.Config{Time: pm.getTimeGenerator()} md, err := openpgp.ReadMessage(encryptedReader, privKeyEntries, nil, config) if err != nil { @@ -103,24 +106,24 @@ func (o *OpenPGP) DecryptAttachmentBinKey(keyPacket []byte, dataPacket []byte, p } //DecryptAttachment ... -func (o *OpenPGP) DecryptAttachment(keyPacket []byte, dataPacket []byte, privateKey string, passphrase string) ([]byte, error) { - rawPrivKey, err := UnArmor(privateKey) +func (pm *PmCrypto) DecryptAttachment(keyPacket []byte, dataPacket []byte, privateKey string, passphrase string) ([]byte, error) { + rawPrivKey, err := armorUtils.Unarmor(privateKey) if err != nil { return nil, err } - return o.DecryptAttachmentBinKey(keyPacket, dataPacket, rawPrivKey, passphrase) + return pm.DecryptAttachmentBinKey(keyPacket, dataPacket, rawPrivKey, passphrase) } //EncryptAttachmentWithPassword ... -func (o *OpenPGP) EncryptAttachmentWithPassword(plainData []byte, password string) (string, error) { +func (pm *PmCrypto) EncryptAttachmentWithPassword(plainData []byte, password string) (string, error) { var outBuf bytes.Buffer - w, err := armor.Encode(&outBuf, pgpMessageType, armorHeader) + w, err := armor.Encode(&outBuf, armorUtils.MESSAGE_HEADER, internal.ArmorHeaders) if err != nil { return "", err } - config := &packet.Config{ Time: o.getTimeGenerator() } + config := &packet.Config{Time: pm.getTimeGenerator()} plaintext, err := openpgp.SymmetricallyEncrypt(w, []byte(password), nil, config) if err != nil { @@ -141,7 +144,7 @@ func (o *OpenPGP) EncryptAttachmentWithPassword(plainData []byte, password strin } //DecryptAttachmentWithPassword ... -func (o *OpenPGP) DecryptAttachmentWithPassword(keyPacket []byte, dataPacket []byte, password string) ([]byte, error) { +func (pm *PmCrypto) DecryptAttachmentWithPassword(keyPacket []byte, dataPacket []byte, password string) ([]byte, error) { encrypted := append(keyPacket, dataPacket...) @@ -151,7 +154,7 @@ func (o *OpenPGP) DecryptAttachmentWithPassword(keyPacket []byte, dataPacket []b return []byte(password), nil } - config := &packet.Config{ Time: o.getTimeGenerator() } + config := &packet.Config{Time: pm.getTimeGenerator()} md, err := openpgp.ReadMessage(encryptedReader, nil, prompt, config) if err != nil { diff --git a/key.go b/crypto/key.go similarity index 62% rename from key.go rename to crypto/key.go index 9ff7e9d..a418ec2 100644 --- a/key.go +++ b/crypto/key.go @@ -1,38 +1,19 @@ -package pm +package crypto import ( "bytes" "crypto" "encoding/hex" "errors" - "fmt" "strings" "time" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/packet" "math/big" + "proton/pmcrypto/armor" ) -//EncryptedSplit when encrypt attachemt -type EncryptedSplit struct { - DataPacket []byte - KeyPacket []byte - Algo string -} - -//SessionSplit splited session -type SessionSplit struct { - Session []byte - Algo string -} - -//EncryptedSigned encrypt_sign_package -type EncryptedSigned struct { - Encrypted string - Signature string -} - const ( ok = 0 notSigned = 1 @@ -40,49 +21,9 @@ const ( failed = 3 ) -//DecryptSignedVerify decrypt_sign_verify -type DecryptSignedVerify struct { - //clear text - Plaintext string - //bitmask verify status : 0 - Verify int - //error message if verify failed - Message string -} - -//CheckPassphrase check is private key passphrase ok -func CheckPassphrase(privateKey string, passphrase string) bool { - privKeyReader := strings.NewReader(privateKey) - entries, err := openpgp.ReadArmoredKeyRing(privKeyReader) - if err != nil { - fmt.Println(err) - return false - } - - var keys []*packet.PrivateKey - - for _, e := range entries { - keys = append(keys, e.PrivateKey) - } - var decryptError error - var n int - for _, key := range keys { - if !key.Encrypted { - continue // Key already decrypted - } - if decryptError = key.Decrypt([]byte(passphrase)); decryptError == nil { - n++ - } - } - if n == 0 { - return false - } - return true -} - //IsKeyExpiredBin ... -func (o *OpenPGP) IsKeyExpiredBin(publicKey []byte) (bool, error) { - now := o.getNow() +func (pm *PmCrypto) IsKeyExpiredBin(publicKey []byte) (bool, error) { + now := pm.getNow() pubKeyReader := bytes.NewReader(publicKey) pubKeyEntries, err := openpgp.ReadKeyRing(pubKeyReader) if err != nil { @@ -134,15 +75,15 @@ func (o *OpenPGP) IsKeyExpiredBin(publicKey []byte) (bool, error) { //IsKeyExpired .... // will user the cached time to check -func (o *OpenPGP) IsKeyExpired(publicKey string) (bool, error) { - rawPubKey, err := UnArmor(publicKey) +func (pm *PmCrypto) IsKeyExpired(publicKey string) (bool, error) { + rawPubKey, err := armor.Unarmor(publicKey) if err != nil { return false, err } - return o.IsKeyExpiredBin(rawPubKey) + return pm.IsKeyExpiredBin(rawPubKey) } -func (o *OpenPGP) generateKey(userName string, domain string, passphrase string, keyType string, bits int, +func (pm *PmCrypto) generateKey(userName string, domain string, passphrase string, keyType string, bits int, prime1 []byte, prime2 []byte, prime3 []byte, prime4 []byte) (string, error) { if len(userName) <= 0 { @@ -157,9 +98,9 @@ func (o *OpenPGP) generateKey(userName string, domain string, passphrase string, comments := "" cfg := &packet.Config{ - Algorithm: packet.PubKeyAlgoRSA, + Algorithm: packet.PubKeyAlgoRSA, RSABits: bits, - Time: o.getTimeGenerator(), + Time: pm.getTimeGenerator(), DefaultHash: crypto.SHA256, DefaultCipher: packet.CipherAES256, } @@ -191,7 +132,6 @@ func (o *OpenPGP) generateKey(userName string, domain string, passphrase string, return "", err } - rawPwd := []byte(passphrase) if newEntity.PrivateKey != nil && !newEntity.PrivateKey.Encrypted { if err := newEntity.PrivateKey.Encrypt(rawPwd); err != nil { @@ -212,12 +152,12 @@ func (o *OpenPGP) generateKey(userName string, domain string, passphrase string, return "", err } serialized := w.Bytes() - return ArmorWithType(serialized, pgpPrivateBlockType) + return armor.ArmorWithType(serialized, armor.PRIVATE_KEY_HEADER) } -func (o *OpenPGP) GenerateRSAKeyWithPrimes(userName string, domain string, passphrase string, bits int, - primeone []byte, primetwo []byte, primethree []byte, primefour []byte) (string, error) { - return o.generateKey(userName, domain, passphrase, "rsa", bits, primeone, primetwo, primethree, primefour) +func (pm *PmCrypto) GenerateRSAKeyWithPrimes(userName string, domain string, passphrase string, bits int, + primeone []byte, primetwo []byte, primethree []byte, primefour []byte) (string, error) { + return pm.generateKey(userName, domain, passphrase, "rsa", bits, primeone, primetwo, primethree, primefour) } // GenerateKey ... @@ -226,11 +166,12 @@ func (o *OpenPGP) GenerateRSAKeyWithPrimes(userName string, domain string, passp // #static generate_key_with_email(email : string, passphrase : string, bits : i32) : open_pgp_key; // # generate new key // #static generate_new_key(user_id : string, email : string, passphrase : string, bits : i32) : open_pgp_key; -func (o *OpenPGP) GenerateKey(userName string, domain string, passphrase string, keyType string, bits int) (string, error) { - return o.generateKey(userName, domain, passphrase, keyType, bits, nil, nil, nil, nil) +func (pm *PmCrypto) GenerateKey(userName string, domain string, passphrase string, keyType string, bits int) (string, error) { + return pm.generateKey(userName, domain, passphrase, keyType, bits, nil, nil, nil, nil) } + // UpdatePrivateKeyPassphrase ... -func (o *OpenPGP) UpdatePrivateKeyPassphrase(privateKey string, oldPassphrase string, newPassphrase string) (string, error) { +func (pm *PmCrypto) UpdatePrivateKeyPassphrase(privateKey string, oldPassphrase string, newPassphrase string) (string, error) { privKey := strings.NewReader(privateKey) privKeyEntries, err := openpgp.ReadArmoredKeyRing(privKey) @@ -271,48 +212,11 @@ func (o *OpenPGP) UpdatePrivateKeyPassphrase(privateKey string, oldPassphrase st } serialized := w.Bytes() - return ArmorWithType(serialized, pgpPrivateBlockType) -} - -// PublicKey get a public key from a private key -func PublicKey(privateKey string) (string, error) { - privKeyReader := strings.NewReader(privateKey) - entries, err := openpgp.ReadArmoredKeyRing(privKeyReader) - if err != nil { - return "", err - } - - var outBuf bytes.Buffer - for _, e := range entries { - e.Serialize(&outBuf) - } - - outString, err := ArmorWithType(outBuf.Bytes(), pgpPublicBlockType) - if err != nil { - return "", nil - } - - return outString, nil -} - -// PublicKeyBinOut get a public key from a private key -func PublicKeyBinOut(privateKey string) ([]byte, error) { - privKeyReader := strings.NewReader(privateKey) - entries, err := openpgp.ReadArmoredKeyRing(privKeyReader) - if err != nil { - return nil, err - } - - var outBuf bytes.Buffer - for _, e := range entries { - e.Serialize(&outBuf) - } - - return outBuf.Bytes(), nil + return armor.ArmorWithType(serialized, armor.PRIVATE_KEY_HEADER) } // CheckKey print out the key and subkey fingerprint -func CheckKey(pubKey string) (string, error) { +func (pm *PmCrypto) CheckKey(pubKey string) (string, error) { pubKeyReader := strings.NewReader(pubKey) entries, err := openpgp.ReadArmoredKeyRing(pubKeyReader) if err != nil { diff --git a/message.go b/crypto/message.go similarity index 65% rename from message.go rename to crypto/message.go index ceab76e..e2bd6fb 100644 --- a/message.go +++ b/crypto/message.go @@ -1,4 +1,4 @@ -package pm +package crypto import ( "bytes" @@ -10,35 +10,38 @@ import ( "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/armor" - "golang.org/x/crypto/openpgp/packet" errors2 "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/packet" "math" - ) + armorUtils "proton/pmcrypto/armor" + "proton/pmcrypto/internal" + "proton/pmcrypto/models" +) // DecryptMessage decrypt encrypted message use private key (string ) // encryptedText : string armored encrypted // privateKey : armored private use to decrypt message // passphrase : match with private key to decrypt message -func (o *OpenPGP) DecryptMessage(encryptedText string, privateKey string, passphrase string) (string, error) { - privKeyRaw, err := UnArmor(privateKey) +func (pm *PmCrypto) DecryptMessage(encryptedText string, privateKey string, passphrase string) (string, error) { + privKeyRaw, err := armorUtils.Unarmor(privateKey) if err != nil { return "", err } - return o.DecryptMessageBinKey(encryptedText, privKeyRaw, passphrase) + return pm.DecryptMessageBinKey(encryptedText, privKeyRaw, passphrase) } // DecryptMessageBinKey decrypt encrypted message use private key (bytes ) // encryptedText : string armored encrypted // privateKey : unarmored private use to decrypt message could be mutiple keys // passphrase : match with private key to decrypt message -func (o *OpenPGP) DecryptMessageBinKey(encryptedText string, privateKey []byte, passphrase string) (string, error) { +func (pm *PmCrypto) DecryptMessageBinKey(encryptedText string, privateKey []byte, passphrase string) (string, error) { privKey := bytes.NewReader(privateKey) privKeyEntries, err := openpgp.ReadKeyRing(privKey) if err != nil { return "", err } - encryptedio, err := unArmor(encryptedText) + encryptedio, err := internal.Unarmor(encryptedText) if err != nil { return "", err } @@ -56,7 +59,7 @@ func (o *OpenPGP) DecryptMessageBinKey(encryptedText string, privateKey []byte, } } - config := &packet.Config{ Time: o.getTimeGenerator() } + config := &packet.Config{Time: pm.getTimeGenerator()} md, err := openpgp.ReadMessage(encryptedio.Body, privKeyEntries, nil, config) if err != nil { @@ -73,57 +76,57 @@ func (o *OpenPGP) DecryptMessageBinKey(encryptedText string, privateKey []byte, return string(b), nil } -// DecryptMessageVerifyPrivbinkeys decrypt message and verify the signature -// veriferKey string: armored verifier keys +// DecryptMessageVerifyPrivBinKeys decrypt message and verify the signature +// verifierKey string: armored verifier keys // privateKey []byte: unarmored private key to decrypt. could be mutiple -func (o *OpenPGP) DecryptMessageVerifyPrivbinkeys(encryptedText string, veriferKey string, privateKeys []byte, passphrase string, verifyTime int64) (*DecryptSignedVerify, error) { +func (pm *PmCrypto) DecryptMessageVerifyPrivBinKeys(encryptedText string, verifierKey string, privateKeys []byte, passphrase string, verifyTime int64) (*models.DecryptSignedVerify, error) { - if len(veriferKey) > 0 { - verifierRaw, err := UnArmor(veriferKey) + if len(verifierKey) > 0 { + verifierRaw, err := armorUtils.Unarmor(verifierKey) if err != nil { return nil, err } - return o.decryptMessageVerifyAllBin(encryptedText, verifierRaw, privateKeys, passphrase, verifyTime) + return pm.decryptMessageVerifyAllBin(encryptedText, verifierRaw, privateKeys, passphrase, verifyTime) } - return o.decryptMessageVerifyAllBin(encryptedText, nil, privateKeys, passphrase, verifyTime) + return pm.decryptMessageVerifyAllBin(encryptedText, nil, privateKeys, passphrase, verifyTime) } -// DecryptMessageVerifyBinKeyPrivbinkeys decrypt message and verify the signature -// veriferKey []byte: unarmored verifier keys +// DecryptMessageVerifyBinKeyPrivBinKeys decrypt message and verify the signature +// verifierKey []byte: unarmored verifier keys // privateKey []byte: unarmored private key to decrypt. could be mutiple -func (o *OpenPGP) DecryptMessageVerifyBinKeyPrivbinkeys(encryptedText string, veriferKey []byte, privateKeys []byte, passphrase string, verifyTime int64) (*DecryptSignedVerify, error) { - return o.decryptMessageVerifyAllBin(encryptedText, veriferKey, privateKeys, passphrase, verifyTime) +func (pm *PmCrypto) DecryptMessageVerifyBinKeyPrivBinKeys(encryptedText string, verifierKey []byte, privateKeys []byte, passphrase string, verifyTime int64) (*models.DecryptSignedVerify, error) { + return pm.decryptMessageVerifyAllBin(encryptedText, verifierKey, privateKeys, passphrase, verifyTime) } // DecryptMessageVerify decrypt message and verify the signature -// veriferKey string: armored verifier keys +// verifierKey string: armored verifier keys // privateKey string: private to decrypt -func (o *OpenPGP) DecryptMessageVerify(encryptedText string, veriferKey string, privateKey string, passphrase string, verifyTime int64) (*DecryptSignedVerify, error) { - if len(veriferKey) > 0 { - verifierRaw, err := UnArmor(veriferKey) +func (pm *PmCrypto) DecryptMessageVerify(encryptedText string, verifierKey string, privateKey string, passphrase string, verifyTime int64) (*models.DecryptSignedVerify, error) { + if len(verifierKey) > 0 { + verifierRaw, err := armorUtils.Unarmor(verifierKey) if err != nil { return nil, err } - return o.DecryptMessageVerifyBinKey(encryptedText, verifierRaw, privateKey, passphrase, verifyTime) + return pm.DecryptMessageVerifyBinKey(encryptedText, verifierRaw, privateKey, passphrase, verifyTime) } - return o.DecryptMessageVerifyBinKey(encryptedText, nil, privateKey, passphrase, verifyTime) + return pm.DecryptMessageVerifyBinKey(encryptedText, nil, privateKey, passphrase, verifyTime) } // DecryptMessageVerifyBinKey decrypt message and verify the signature -// veriferKey []byte: unarmored verifier keys +// verifierKey []byte: unarmored verifier keys // privateKey string: private to decrypt -func (o *OpenPGP) DecryptMessageVerifyBinKey(encryptedText string, veriferKey []byte, privateKey string, passphrase string, verifyTime int64) (*DecryptSignedVerify, error) { - privateKeyRaw, err := UnArmor(privateKey) +func (pm *PmCrypto) DecryptMessageVerifyBinKey(encryptedText string, verifierKey []byte, privateKey string, passphrase string, verifyTime int64) (*models.DecryptSignedVerify, error) { + privateKeyRaw, err := armorUtils.Unarmor(privateKey) if err != nil { return nil, err } - return o.decryptMessageVerifyAllBin(encryptedText, veriferKey, privateKeyRaw, passphrase, verifyTime) + return pm.decryptMessageVerifyAllBin(encryptedText, verifierKey, privateKeyRaw, passphrase, verifyTime) } // decryptMessageVerifyAllBin // decrypt_message_verify_single_key(private_key: string, passphras: string, encrypted : string, signature : string) : decrypt_sign_verify; // decrypt_message_verify(passphras: string, encrypted : string, signature : string) : decrypt_sign_verify; -func (o *OpenPGP) decryptMessageVerifyAllBin(encryptedText string, veriferKey []byte, privateKey []byte, passphrase string, verifyTime int64) (*DecryptSignedVerify, error) { +func (pm *PmCrypto) decryptMessageVerifyAllBin(encryptedText string, verifierKey []byte, privateKey []byte, passphrase string, verifyTime int64) (*models.DecryptSignedVerify, error) { privKey := bytes.NewReader(privateKey) privKeyEntries, err := openpgp.ReadKeyRing(privKey) if err != nil { @@ -144,12 +147,12 @@ func (o *OpenPGP) decryptMessageVerifyAllBin(encryptedText string, veriferKey [] } } - out := &DecryptSignedVerify{} + out := &models.DecryptSignedVerify{} out.Verify = failed var verifierEntries openpgp.EntityList - if len(veriferKey) > 0 { - verifierReader := bytes.NewReader(veriferKey) + if len(verifierKey) > 0 { + verifierReader := bytes.NewReader(verifierKey) verifierEntries, err = openpgp.ReadKeyRing(verifierReader) if err != nil { return nil, err @@ -162,7 +165,7 @@ func (o *OpenPGP) decryptMessageVerifyAllBin(encryptedText string, veriferKey [] out.Verify = noVerifier } - encryptedio, err := unArmor(encryptedText) + encryptedio, err := internal.Unarmor(encryptedText) if err != nil { return nil, err } @@ -219,7 +222,7 @@ func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) { if md.Signature.KeyLifetimeSecs != nil { expires = int64(*md.Signature.KeyLifetimeSecs) + created } - if created - creationTimeOffset <= verifyTime && verifyTime <= expires { + if created-internal.CreationTimeOffset <= verifyTime && verifyTime <= expires { md.SignatureError = nil } } else { @@ -234,12 +237,12 @@ func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) { // plainText : the input // privateKey : optional required when you want to sign // passphrase : optional required when you pass the private key and this passphrase must could decrypt the private key -func (o *OpenPGP) EncryptMessage(plainText string, publicKey string, privateKey string, passphrase string, trim bool) (string, error) { - rawPubKey, err := UnArmor(publicKey) +func (pm *PmCrypto) EncryptMessage(plainText string, publicKey string, privateKey string, passphrase string, trim bool) (string, error) { + rawPubKey, err := armorUtils.Unarmor(publicKey) if err != nil { return "", err } - return o.EncryptMessageBinKey(plainText, rawPubKey, privateKey, passphrase, trim) + return pm.EncryptMessageBinKey(plainText, rawPubKey, privateKey, passphrase, trim) } // EncryptMessageBinKey encrypt message with unarmored public key, if pass private key and passphrase will also sign the message @@ -247,13 +250,13 @@ func (o *OpenPGP) EncryptMessage(plainText string, publicKey string, privateKey // plainText : the input // privateKey : optional required when you want to sign // passphrase : optional required when you pass the private key and this passphrase must could decrypt the private key -func (o *OpenPGP) EncryptMessageBinKey(plainText string, publicKey []byte, privateKey string, passphrase string, trim bool) (string, error) { +func (pm *PmCrypto) EncryptMessageBinKey(plainText string, publicKey []byte, privateKey string, passphrase string, trim bool) (string, error) { if trim { - plainText = trimNewlines(plainText) + plainText = internal.TrimNewlines(plainText) } var outBuf bytes.Buffer - w, err := armor.Encode(&outBuf, pgpMessageType, armorHeader) + w, err := armor.Encode(&outBuf, armorUtils.MESSAGE_HEADER, internal.ArmorHeaders) if err != nil { return "", err } @@ -287,11 +290,11 @@ func (o *OpenPGP) EncryptMessageBinKey(plainText string, publicKey []byte, priva } if signEntity == nil { - return "", errors.New("cannot sign message, singer key is not unlocked") + return "", errors.New("cannot sign message, signer key is not unlocked") } } - config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: o.getTimeGenerator() } + config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pm.getTimeGenerator()} ew, err := openpgp.Encrypt(w, pubKeyEntries, signEntity, nil, config) @@ -304,15 +307,15 @@ func (o *OpenPGP) EncryptMessageBinKey(plainText string, publicKey []byte, priva //EncryptMessageWithPassword encrypt a plain text to pgp message with a password //plainText string: clear text //output string: armored pgp message -func (o *OpenPGP) EncryptMessageWithPassword(plainText string, password string) (string, error) { +func (pm *PmCrypto) EncryptMessageWithPassword(plainText string, password string) (string, error) { var outBuf bytes.Buffer - w, err := armor.Encode(&outBuf, pgpMessageType, armorHeader) + w, err := armor.Encode(&outBuf, armorUtils.MESSAGE_HEADER, internal.ArmorHeaders) if err != nil { return "", err } - config := &packet.Config{ Time: o.getTimeGenerator() } + config := &packet.Config{Time: pm.getTimeGenerator()} plaintext, err := openpgp.SymmetricallyEncrypt(w, []byte(password), nil, config) if err != nil { return "", err @@ -334,8 +337,8 @@ func (o *OpenPGP) EncryptMessageWithPassword(plainText string, password string) //DecryptMessageWithPassword decrypt a pgp message with a password //encrypted string : armored pgp message //output string : clear text -func (o *OpenPGP) DecryptMessageWithPassword(encrypted string, password string) (string, error) { - encryptedio, err := unArmor(encrypted) +func (pm *PmCrypto) DecryptMessageWithPassword(encrypted string, password string) (string, error) { + encryptedio, err := internal.Unarmor(encrypted) if err != nil { return "", err } @@ -344,7 +347,7 @@ func (o *OpenPGP) DecryptMessageWithPassword(encrypted string, password string) return []byte(password), nil } - config := &packet.Config{ Time: o.getTimeGenerator() } + config := &packet.Config{Time: pm.getTimeGenerator()} md, err := openpgp.ReadMessage(encryptedio.Body, nil, prompt, config) if err != nil { return "", err diff --git a/crypto/mime.go b/crypto/mime.go new file mode 100644 index 0000000..3ad5ec7 --- /dev/null +++ b/crypto/mime.go @@ -0,0 +1,83 @@ +package crypto + +import ( + "proton/pmmime" + "net/mail" + "strings" + "golang.org/x/crypto/openpgp/packet" + "net/textproto" + "io/ioutil" + "bytes" + "golang.org/x/crypto/openpgp" + "proton/pmcrypto/armor" +) + +// ======================== Attachments Collector ============== +// Collect contents of all attachment parts and return +// them as a string + + +func (pm PmCrypto) parseMIME(mimeBody string, verifierKey []byte) (*pmmime.BodyCollector, int, []string, []string, error) { + pubKey := bytes.NewReader(verifierKey) + pubKeyEntries, err := openpgp.ReadKeyRing(pubKey) + + mm, err := mail.ReadMessage(strings.NewReader(mimeBody)) + if err != nil { + return nil, 0, nil, nil, err + } + config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pm.getTimeGenerator()} + + h := textproto.MIMEHeader(mm.Header) + mmBodyData, err := ioutil.ReadAll(mm.Body) + + printAccepter := pmmime.NewMIMEPrinter() + bodyCollector := pmmime.NewBodyCollector(printAccepter) + attachmentsCollector := pmmime.NewAttachmentsCollector(bodyCollector) + mimeVisitor := pmmime.NewMimeVisitor(attachmentsCollector) + str, err := armor.ArmorKey(verifierKey) + signatureCollector := newSignatureCollector(mimeVisitor, pubKeyEntries, config) + err = pmmime.VisitAll(bytes.NewReader(mmBodyData), h, signatureCollector) + + verified := signatureCollector.verified + body := bodyCollector + atts := attachmentsCollector.GetAttachments() + attHeaders := attachmentsCollector.GetAttHeaders() + + return body, verified, atts, attHeaders, nil +} + +// define call back interface +type MIMECallbacks interface { + OnBody(body string, mimetype string) + OnAttachment(headers string, data []byte) + // Encrypted headers can be an attachment and thus be placed at the end of the mime structure + OnEncryptedHeaders(headers string) + OnVerified(verified int) + OnError(err error) +} + +func (pm *PmCrypto) DecryptMIMEMessage(encryptedText string, verifierKey []byte, privateKeys []byte, + passphrase string, callbacks MIMECallbacks, verifyTime int64) { + decsignverify, err := pm.decryptMessageVerifyAllBin(encryptedText, verifierKey, privateKeys, passphrase, verifyTime) + if err != nil { + callbacks.OnError(err) + return + } + + body, verified, attachments, attachmentHeaders, err := pm.parseMIME(decsignverify.Plaintext, verifierKey) + if err != nil { + callbacks.OnError(err) + return + } + bodyContent, bodyMimeType := body.GetBody() + callbacks.OnBody(bodyContent, bodyMimeType) + for i := 0; i < len(attachments); i++ { + callbacks.OnAttachment(attachmentHeaders[i], []byte(attachments[i])) + } + callbacks.OnEncryptedHeaders("") + if decsignverify.Verify == notSigned { + callbacks.OnVerified(verified) + } else { + callbacks.OnVerified(decsignverify.Verify) + } +} \ No newline at end of file diff --git a/crypto/mime_test.go b/crypto/mime_test.go new file mode 100644 index 0000000..d2718ae --- /dev/null +++ b/crypto/mime_test.go @@ -0,0 +1,411 @@ +package crypto + +import ( + "fmt" + "io/ioutil" + "proton/pmcrypto/internal" + "testing" +) + +const publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +OQTmvM6R +=DvFv +-----END PGP PUBLIC KEY BLOCK-----` +const testMessage = `-----BEGIN PGP MESSAGE----- +Version: ProtonMail +Comment: https://protonmail.com + +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 +LloBMV1YNGsW45M+oQ== +=BRHT +-----END PGP MESSAGE-----` +const privatekeypassword = "test" +const privatekey = ` +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: PmCrypto.js v3.1.2 +Comment: https://openpgpjs.org + +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 +=xWqS +-----END PGP PRIVATE KEY BLOCK----- +` + +// define call back interface +type Callbacks struct { +} + +func (t *Callbacks) OnBody(body string, mimetype string) { + fmt.Println(body) +} +func (t Callbacks) OnAttachment(headers string, data []byte) { + fmt.Println(headers) +} +func (t Callbacks) OnEncryptedHeaders(headers string) { + fmt.Println(headers) +} +func (t Callbacks) OnVerified(verified int) { + fmt.Println(verified) +} +func (t Callbacks) OnError(err error) { + fmt.Println(err) +} + +func TestDecrypt(t *testing.T) { + callbacks := Callbacks{} + o := PmCrypto{} + block, _ := internal.Unarmor(publicKey) + publicKeyUnarmored, _ := ioutil.ReadAll(block.Body) + block, _ = internal.Unarmor(privatekey) + privateKeyUnarmored, _ := ioutil.ReadAll(block.Body) + o.DecryptMIMEMessage(testMessage, publicKeyUnarmored, privateKeyUnarmored, privatekeypassword, + &callbacks, o.GetTime()) +} + +func TestParse(t *testing.T) { + testMessage := + `Content-Type: multipart/signed; protocol="application/pgp-signature"; micalg=pgp-sha256; boundary="---------------------4a9ea9f4dad3f36079bdb3f1e7b75bd0"; charset=UTF-8 +X-Spam-Status: No, score=1.2 required=7.0 tests=ALL_TRUSTED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_ENVFROM_END_DIGIT,FREEMAIL_FROM, FREEMAIL_REPLYTO_END_DIGIT,HTML_IMAGE_ONLY_08,HTML_MESSAGE autolearn=no autolearn_force=no version=3.4.0 +X-Spam-Level: * +X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on mail.protonmail.ch + +-----------------------4a9ea9f4dad3f36079bdb3f1e7b75bd0 +Content-Type: multipart/mixed; boundary="---------------------f0e64db835d0f5c3674df52a164b06bb" + +-----------------------f0e64db835d0f5c3674df52a164b06bb +Content-Type: multipart/alternative; boundary="---------------------3ca028eaeffb3ca0fb0dd6461f639c2b" + +-----------------------3ca028eaeffb3ca0fb0dd6461f639c2b +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain;charset=utf-8 + + + + +[Screenshot from 2018-02-06 17-13-21.png] + +--=C2=A0 +Julien Palard +https://mdk.fr + + +-----------------------3ca028eaeffb3ca0fb0dd6461f639c2b +Content-Type: multipart/related; boundary="---------------------ce717c368c2d3981c954ad7c46cd7bf2" + +-----------------------ce717c368c2d3981c954ad7c46cd7bf2 +Content-Type: text/html;charset=utf-8 +Content-Transfer-Encoding: base64 + +PGRpdj48ZGl2Pjxicj48L2Rpdj48ZGl2Pjxicj48L2Rpdj48ZGl2IGNsYXNzPSJwcm90b25tYWls +X3NpZ25hdHVyZV9ibG9jayI+PGRpdiBjbGFzcz0icHJvdG9ubWFpbF9zaWduYXR1cmVfYmxvY2st +dXNlciI+PGRpdj48aW1nIHNyYz0iY2lkOjQ2MDk4ZTliQHByb3Rvbm1haWwuY29tIiBjbGFzcz0i +cHJvdG9uLWVtYmVkZGVkIiBhbHQ9IlNjcmVlbnNob3QgZnJvbSAyMDE4LTAyLTA2IDE3LTEzLTIx +LnBuZyI+PGJyPjwvZGl2PjxkaXY+LS0mbmJzcDs8YnI+PC9kaXY+PGRpdj5KdWxpZW4gUGFsYXJk +PGJyPjwvZGl2PjxkaXY+PGNvZGUgc3R5bGU9ImZvbnQtZmFtaWx5OidTRk1vbm8tUmVndWxhcics +IENvbnNvbGFzLCAnTGliZXJhdGlvbiBNb25vJywgTWVubG8sIENvdXJpZXIsIG1vbm9zcGFjZTtm +b250LXNpemU6MTEuODk5OTk5NjE4NTMwMjczcHg7cGFkZGluZzowcHg7bWFyZ2luOjBweDtiYWNr +Z3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O3doaXRlLXNwYWNlOnByZTtib3JkZXI6MHB4O2Rpc3Bs +YXk6aW5saW5lO292ZXJmbG93OnZpc2libGU7bGluZS1oZWlnaHQ6aW5oZXJpdDsiPjxhIGhyZWY9 +Imh0dHBzOi8vbWRrLmZyIj5odHRwczovL21kay5mcjwvYT48L2NvZGU+PGJyPjwvZGl2PjwvZGl2 +PjxkaXYgY2xhc3M9InByb3Rvbm1haWxfc2lnbmF0dXJlX2Jsb2NrLXByb3RvbiBwcm90b25tYWls +X3NpZ25hdHVyZV9ibG9jay1lbXB0eSI+PGJyPjwvZGl2PjwvZGl2PjwvZGl2Pg== +-----------------------ce717c368c2d3981c954ad7c46cd7bf2-- +-----------------------3ca028eaeffb3ca0fb0dd6461f639c2b-- +-----------------------f0e64db835d0f5c3674df52a164b06bb +Content-Type: image/png; filename="Screenshot from 2018-02-06 17-13-21.png"; name="Screenshot from 2018-02-06 17-13-21.png" +Content-Disposition: inline; filename="Screenshot from 2018-02-06 17-13-21.png"; name="Screenshot from 2018-02-06 17-13-21.png" +Content-ID: <46098e9b@protonmail.com> + +wsBcBAEBCAAQBQJbjodcCRDdVS3pfakUGgAAlhoH/2jBRjOSx5EdkiyyNcCT +4DVm+ACoF1KTWE5fLRuDPvSiD934cFoZShJs32r0Wcj/4W4tVhLYzjjtf6xO +Ymqe0p3o4oYxxMXIAd4COrnOPGjeD1ausqT6iUCAadqXoDYfowEg4f0Wd0RK +ElsP/OZaDjsNoRE3WeRcHTr5XWZxhEsIMgnW591iaTliYvbysLoQ08i3G53c +p6q+IANRznx5rDhMdo+shFvhcI2Zszg6X2WuCMhFtUyrqEN8WlZYXMX8PGPO +1kNDRl7B7/r4Ap+FffLpeYw+8rG6lQXGc3RCOAnfMq9X/9Ziqzxr7flYtRJH +RIzX2CG47PuGl/uvImFW/Iw= + +-----------------------f0e64db835d0f5c3674df52a164b06bb +Content-Type: application/pgp-keys; filename="publickey - kaykeytest3@protonmail.com - 0xE1DADAE3.asc"; name="publickey - kaykeytest3@protonmail.com - 0xE1DADAE3.asc" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="publickey - kaykeytest3@protonmail.com - 0xE1DADAE3.asc"; name="publickey - kaykeytest3@protonmail.com - 0xE1DADAE3.asc" + +LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tDQpWZXJzaW9uOiBPcGVuUEdQLmpz +IHYzLjEuMg0KQ29tbWVudDogaHR0cHM6Ly9vcGVucGdwanMub3JnDQoNCnhzQk5CRnQ2N2I4QkNB +QzBTMlU3VUJ3dnkyUnZqcEpwOHlyMlE0R25oNzk0QjUwVG1TMlViTXJRaEovaQpUWFRTZmJhb21l +NkdkcmJhSjlvVW0xY2hhMU5hVlNnWHE0RVB2WWxmSWVVdkt1U0tnTVdxaVMzT2xSdGYKT3JwMVZp +dXNpZWtldVczL2JtYUZBbzFLQWZvajBMNlF6cVVIY3ZZNnY1RExoWGV2MWlaUW9TN2lrQk9PCmor +Z05lUDJabjVIOXdQTFlxdWlqK0pnWFY4djRRQk5uNU82NUNYRkZIbjl1Mzl1Yng2bDJtY2tqaDVm +VQppU1FiUkh2RzhIdG96Y2xIcWV1bjBpanNjZEtoOTBsaENsWHlDcHlkZ2JwT2pjL2dDQ2R3WmVh +djNNSG0KZnhsd2VReDhmc0xxZ1h5TlNFd3pyODh0MTFWMFQ4TmRjeFdia1lRNllQN2R4UU10RkU0 +bWM5akxBQkVCCkFBSE5PU0pyWVhsclpYbDBaWE4wTTBCd2NtOTBiMjV0WVdsc0xtTnZiU0lnUEd0 +aGVXdGxlWFJsYzNRegpRSEJ5YjNSdmJtMWhhV3d1WTI5dFBzTEFkUVFRQVFnQUtRVUNXM3J0dndZ +TENRY0lBd0lKRU4xVkxlbDkKcVJRYUJCVUlDZ0lERmdJQkFoa0JBaHNEQWg0QkFBQ0dEZ2Y5SHVq +aG9QUGN5SHpLd25JbFJJaENRL1dLCm13SVVFdEw4eU03aGRvci9zK0kyL1A3WkJHWE1ReXBKb3JD +YTU1NUY2ODRMZnpWdUtBUVFMdXZpNHR6aQo5N2JSNE1ldHY4a2ptSUI1Rk0vNUhpWllWd090SDFK +dU5iM2RQWG9CVjJha09ScnFCeVRjbjEyeUIwOVkKd0tZR0Y4K25XNFhLU1F2V3VBWWVIZ0dQKzMz +ZTdCc0VvODhuRmJJUXl2cmUzUTN0TkJrWTVVVnA2L1M2ClpLZWJwaEFRQVlzM0ZVVlZVeThlZkhr +aEFmZFlSdUFBUUFoWnJGanU0V1J0RGZkUHdLR0UzZDZsUGswSApNSFNTUXNHb2thQTVoTnUycE1S +Mnl5UmtUa3JyNnRXbmlET2ZvSmtKcmc2VllHNVRwaHZnOE43dVdzZHQKZkhDdDBwWW9uYTRzclZx +a0RzN0FUUVJiZXUyL0FRZ0F6b2tCSGZucnFXUXE4RUVSYUVlOUpmZkQ3V0FpCnZyU29lNDdxQ05l +S2I2MmcvLzh0Q1dKMHVlRExTMFRyY0V4cnVzUi8rSFdoYUFTV2srL0lPTGhjRTcrVgp4V2Nab0dp +bzNuc1puTktWdnFhendNSW1vMnFqdUllUnVCVkJqRW5OOERhOXlxNkJ3YVkvSEs2czM5Y3IKQVVV +d2FtWjVuMDNtSFR6MzBMQllYR1VldVc5c3Z4NDIzbTlXb3N2WlQvUWRKWW1qMG44NmpGNGs2Tnh2 +ClV4ejR1bmV1YWt1Wld5NmNURHg1WFdWVWF1YzlyV1Z5Uzk3cjJ0NllpRlF6MTFiOU5oZm4rcDVs +UmdZbgpzMzQwT1MyVmppK1BVeUVKZ2YzYi9MQlJ5V3Erbk1Vc2lRV09yYlQxblVCVzNRdDViWWhH +MUhnT1o5eEoKVjd2dndSZHFwQ1p2cVFBUkFRQUJ3c0JmQkJnQkNBQVRCUUpiZXUyL0NSRGRWUzNw +ZmFrVUdnSWJEQUFBClhrTUgvaTBoTnRwRWIrYUZONC9Ba0JCam1GOFJZV243anRyK2UyU05STHdF +RUMxcmdmakhjWXZnanJFTQo2Y3hBcXVzSWJKSEdnSUVYZ0s1YUlNOHBGLzRuamN2VXVxa0x1b1o3 +QVJsanpwUTcvaHhyc0FNWnFYR2gKeEU4eXJZVlY0dGhhZWw0Q1NRUTRvRWlpbXB4Z28velE5bzhk +NGNQUTM4Ni9VNEgvMm9OczFUMElCOTZWCkJsTy9pM0w5eGM4K2w4RG8vcm5ieVV1Ym9LUVNKZmtp +RXNLMkRabEZoOVArdVltQ1AzVGJSb2NIUFZtVwpBT3NHVVhJSXROaVNTc05ORUlHeVBSNE8rMXRq +VUJPMFNHRnZoVVZUNnJLYTlaYUVyMzI2ZmU2S0pmZU8KYTl4a21WZEdaQm9SdENmbHhiakdnYjRq +dTJ3Z1E1TE5KWUNWZy9WRkxIRGI3MjQ9DQo9YmNvMw0KLS0tLS1FTkQgUEdQIFBVQkxJQyBLRVkg +QkxPQ0stLS0tLQ0KDQo= +-----------------------f0e64db835d0f5c3674df52a164b06bb-- +-----------------------4a9ea9f4dad3f36079bdb3f1e7b75bd0 +Content-Type: application/pgp-signature; name="signature.asc" +Content-Description: PmCrypto digital signature +Content-Disposition: attachment; filename="signature.asc" + +-----BEGIN PGP SIGNATURE----- + Version: ProtonMail +Comment: https://protonmail.com + +wsBcBAEBCAAQBQJbjodcCRDdVS3pfakUGgAAlhoH/2jBRjOSx5EdkiyyNcCT +4DVm+ACoF1KTWE5fLRuDPvSiD934cFoZShJs32r0Wcj/4W4tVhLYzjjtf6xO +Ymqe0p3o4oYxxMXIAd4COrnOPGjeD1ausqT6iUCAadqXoDYfowEg4f0Wd0RK +ElsP/OZaDjsNoRE3WeRcHTr5XWZxhEsIMgnW591iaTliYvbysLoQ08i3G53c +p6q+IANRznx5rDhMdo+shFvhcI2Zszg6X2WuCMhFtUyrqEN8WlZYXMX8PGPO +1kNDRl7B7/r4Ap+FffLpeYw+8rG6lQXGc3RCOAnfMq9X/9Ziqzxr7flYtRJH +RIzX2CG47PuGl/uvImFW/Iw= +=t7ak +-----END PGP SIGNATURE----- + + +-----------------------4a9ea9f4dad3f36079bdb3f1e7b75bd0-- +` + o := PmCrypto{} + body, _, atts, attHeaders, err := o.parseMIME(testMessage, nil) + if err != nil { + t.Error(err) + } + + fmt.Println() + fmt.Println("==BODY:") + fmt.Println(body.GetBody()) + fmt.Println("==BODY HEADERS:") + fmt.Println(body.GetHeaders()) + + fmt.Println("==ATTACHMENTS:") + fmt.Println(len(atts)) + for i, attachment := range atts { + fmt.Println("==ATTACHMENT HEADERS:") + fmt.Println(attHeaders[i]) + fmt.Println("==ATTACHMENT:") + fmt.Println(attachment) + } +} diff --git a/openpgp.go b/crypto/pmcrypto.go similarity index 59% rename from openpgp.go rename to crypto/pmcrypto.go index f812722..504b309 100644 --- a/openpgp.go +++ b/crypto/pmcrypto.go @@ -1,11 +1,10 @@ -package pm +package crypto import "time" -// OpenPGP structure to manage mutiple address keys and user keys -type OpenPGP struct { - addresses []*Address - +// PmCrypto structure to manage multiple address keys and user keys +// Called PGP crypto because it cannot have the same name as the package by gomobile's ridiculous rules. +type PmCrypto struct { //latestServerTime unix time cache latestServerTime int64 latestClientTime time.Time @@ -13,28 +12,28 @@ type OpenPGP struct { // //AddAddress add a new address to key ring // //add a new address into addresses list -// func (pgp *OpenPGP) AddAddress(address *Address) (bool, error) { +// func (pgp *PmCrypto) AddAddress(address *Address) (bool, error) { // return true, errors.New("this is not implemented yet, will add this later") // } // //RemoveAddress remove address from the keyring // // // //#remove a exsit address from the list based on address id -// func (pgp *OpenPGP) RemoveAddress(addressID string) (bool, error) { +// func (pgp *PmCrypto) RemoveAddress(addressID string) (bool, error) { // return true, errors.New("this is not implemented yet, will add this later") // } // //CleanAddresses clear all addresses in keyring -// func (pgp *OpenPGP) CleanAddresses() (bool, error) { +// func (pgp *PmCrypto) CleanAddresses() (bool, error) { // return true, errors.New("this is not implemented yet, will add this later") // } // //EncryptMessage encrypt message use address id -// func (pgp *OpenPGP) EncryptMessage(addressID string, plainText string, passphrase string, trim bool) (string, error) { +// func (pgp *PmCrypto) EncryptMessage(addressID string, plainText string, passphrase string, trim bool) (string, error) { // return "", errors.New("this is not implemented yet, will add this later") // } // //DecryptMessage decrypt message, this will lookup all keys -// func (pgp *OpenPGP) DecryptMessage(encryptText string, passphras string) (string, error) { +// func (pgp *PmCrypto) DecryptMessage(encryptText string, passphras string) (string, error) { // return "", errors.New("this is not implemented yet, will add this later") // } diff --git a/session.go b/crypto/session.go similarity index 66% rename from session.go rename to crypto/session.go index 3a10ee5..f1188b5 100644 --- a/session.go +++ b/crypto/session.go @@ -1,19 +1,20 @@ -package pm +package crypto import ( "bytes" "errors" "fmt" "io" - "io/ioutil" "strings" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/packet" + "proton/pmcrypto/armor" + "proton/pmcrypto/models" ) //RandomToken ... -func RandomToken() ([]byte, error) { +func (pm *PmCrypto) RandomToken() ([]byte, error) { config := &packet.Config{DefaultCipher: packet.CipherAES256} keySize := config.DefaultCipher.KeySize() symKey := make([]byte, keySize) @@ -24,7 +25,7 @@ func RandomToken() ([]byte, error) { } // RandomTokenWith ... -func RandomTokenWith(size int) ([]byte, error) { +func (pm *PmCrypto) RandomTokenWith(size int) ([]byte, error) { config := &packet.Config{DefaultCipher: packet.CipherAES256} symKey := make([]byte, size) if _, err := io.ReadFull(config.Random(), symKey); err != nil { @@ -34,7 +35,7 @@ func RandomTokenWith(size int) ([]byte, error) { } //GetSessionFromKeyPacketBinkeys get session key no encoding in and out -func GetSessionFromKeyPacketBinkeys(keyPackage []byte, privateKey []byte, passphrase string) (*SessionSplit, error) { +func (pm *PmCrypto) GetSessionFromKeyPacketBinkeys(keyPackage []byte, privateKey []byte, passphrase string) (*models.SessionSplit, error) { keyReader := bytes.NewReader(keyPackage) packets := packet.NewReader(keyReader) @@ -75,7 +76,7 @@ func GetSessionFromKeyPacketBinkeys(keyPackage []byte, privateKey []byte, passph } //GetSessionFromKeyPacket get session key no encoding in and out -func GetSessionFromKeyPacket(keyPackage []byte, privateKey string, passphrase string) (*SessionSplit, error) { +func (pm *PmCrypto) GetSessionFromKeyPacket(keyPackage []byte, privateKey string, passphrase string) (*models.SessionSplit, error) { keyReader := bytes.NewReader(keyPackage) packets := packet.NewReader(keyReader) @@ -116,16 +117,16 @@ func GetSessionFromKeyPacket(keyPackage []byte, privateKey string, passphrase st } //KeyPacketWithPublicKey ... -func KeyPacketWithPublicKey(sessionSplit *SessionSplit, publicKey string) ([]byte, error) { - pubkeyRaw, err := UnArmor(publicKey) +func (pm *PmCrypto) KeyPacketWithPublicKey(sessionSplit *models.SessionSplit, publicKey string) ([]byte, error) { + pubkeyRaw, err := armor.Unarmor(publicKey) if err != nil { return nil, err } - return KeyPacketWithPublicKeyBin(sessionSplit, pubkeyRaw) + return pm.KeyPacketWithPublicKeyBin(sessionSplit, pubkeyRaw) } // KeyPacketWithPublicKeyBin ... -func KeyPacketWithPublicKeyBin(sessionSplit *SessionSplit, publicKey []byte) ([]byte, error) { +func (pm *PmCrypto) KeyPacketWithPublicKeyBin(sessionSplit *models.SessionSplit, publicKey []byte) ([]byte, error) { publicKeyReader := bytes.NewReader(publicKey) pubKeyEntries, err := openpgp.ReadKeyRing(publicKeyReader) @@ -170,7 +171,7 @@ func KeyPacketWithPublicKeyBin(sessionSplit *SessionSplit, publicKey []byte) ([] } //GetSessionFromSymmetricPacket ... -func GetSessionFromSymmetricPacket(keyPackage []byte, password string) (*SessionSplit, error) { +func (pm *PmCrypto) GetSessionFromSymmetricPacket(keyPackage []byte, password string) (*models.SessionSplit, error) { keyReader := bytes.NewReader(keyPackage) packets := packet.NewReader(keyReader) @@ -196,7 +197,7 @@ func GetSessionFromSymmetricPacket(keyPackage []byte, password string) (*Session for _, s := range symKeys { key, cipherFunc, err := s.Decrypt(pwdRaw) if err == nil { - return &SessionSplit{ + return &models.SessionSplit{ Session: key, Algo: getAlgo(cipherFunc), }, nil @@ -209,7 +210,7 @@ func GetSessionFromSymmetricPacket(keyPackage []byte, password string) (*Session } // SymmetricKeyPacketWithPassword ... -func SymmetricKeyPacketWithPassword(sessionSplit *SessionSplit, password string) ([]byte, error) { +func (pm *PmCrypto) SymmetricKeyPacketWithPassword(sessionSplit *models.SessionSplit, password string) ([]byte, error) { outbuf := &bytes.Buffer{} cf := cipherFunc(sessionSplit.Algo) @@ -249,7 +250,7 @@ func cipherFunc(algo string) packet.CipherFunction { return packet.CipherAES256 } -func getSessionSplit(ek *packet.EncryptedKey) (*SessionSplit, error) { +func getSessionSplit(ek *packet.EncryptedKey) (*models.SessionSplit, error) { if ek == nil { return nil, errors.New("can't decrypt key packet") } @@ -265,7 +266,7 @@ func getSessionSplit(ek *packet.EncryptedKey) (*SessionSplit, error) { return nil, errors.New("can't decrypt key packet key is nil") } - return &SessionSplit{ + return &models.SessionSplit{ Session: ek.Key, Algo: algo, }, nil @@ -282,81 +283,3 @@ func getAlgo(cipher packet.CipherFunction) string { return algo } - -//encode length based on 4.2.2. in the RFC -func encodedLength(length int) (b []byte) { - if length < 192 { - b = append(b, byte(length)) - } else if length < 8384 { - length = length - 192 - b = append(b, 192+byte(length>>8)) - b = append(b, byte(length)) - } else { - b = append(b, byte(255)) - b = append(b, byte(length>>24)) - b = append(b, byte(length>>16)) - b = append(b, byte(length>>8)) - b = append(b, byte(length)) - } - return -} - -//SeparateKeyAndData ... -func SeparateKeyAndData(encrypted string) (*EncryptedSplit, error) { - - var err error - - encryptedRaw, err := UnArmor(encrypted) - if err != nil { - return nil, err - } - - encryptedReader := bytes.NewReader(encryptedRaw) - - //kr *KeyRing, r io.Reader) (key *SymmetricKey, symEncryptedData []byte, - packets := packet.NewReader(encryptedReader) - - outSplit := &EncryptedSplit{} - - // Save encrypted key and signature apart - var ek *packet.EncryptedKey - // var decryptErr error - for { - var p packet.Packet - if p, err = packets.Next(); err == io.EOF { - err = nil - break - } - switch p := p.(type) { - case *packet.EncryptedKey: - // We got an encrypted key. Try to decrypt it with each available key - if ek != nil && ek.Key != nil { - break - } - ek = p - break - case *packet.SymmetricallyEncrypted: - var packetContents []byte - if packetContents, err = ioutil.ReadAll(p.Contents); err != nil { - return nil, err - } - - encodedLength := encodedLength(len(packetContents) + 1) - var symEncryptedData []byte - symEncryptedData = append(symEncryptedData, byte(210)) - symEncryptedData = append(symEncryptedData, encodedLength...) - symEncryptedData = append(symEncryptedData, byte(1)) - symEncryptedData = append(symEncryptedData, packetContents...) - - outSplit.DataPacket = symEncryptedData - break - - } - } - - var buf bytes.Buffer - ek.Serialize(&buf) - outSplit.KeyPacket = buf.Bytes() - - return outSplit, err -} diff --git a/sign_detached.go b/crypto/sign_detached.go similarity index 78% rename from sign_detached.go rename to crypto/sign_detached.go index e32ec2a..14772d6 100644 --- a/sign_detached.go +++ b/crypto/sign_detached.go @@ -1,4 +1,4 @@ -package pm +package crypto import ( "bytes" @@ -7,28 +7,19 @@ import ( "time" "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/clearsign" "golang.org/x/crypto/openpgp/packet" errors2 "golang.org/x/crypto/openpgp/errors" "io" + "proton/pmcrypto/internal" ) -//ReadClearSignedMessage read clear message from a clearsign package -func ReadClearSignedMessage(signedMessage string) (string, error) { - modulusBlock, rest := clearsign.Decode([]byte(signedMessage)) - if len(rest) != 0 { - return "", errors.New("pmapi: extra data after modulus") - } - return string(modulusBlock.Bytes), nil -} - // SignTextDetached sign detached text type -func (o *OpenPGP) SignTextDetached(plainText string, privateKey string, passphrase string, trim bool) (string, error) { +func (pm *PmCrypto) SignTextDetached(plainText string, privateKey string, passphrase string, trim bool) (string, error) { //sign with 0x01 text var signEntity *openpgp.Entity if trim { - plainText = trimNewlines(plainText) + plainText = internal.TrimNewlines(plainText) } signerReader := strings.NewReader(privateKey) @@ -54,7 +45,7 @@ func (o *OpenPGP) SignTextDetached(plainText string, privateKey string, passphra return "", errors.New("cannot sign message, signer key is not unlocked") } - config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: o.getTimeGenerator() } + config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pm.getTimeGenerator() } att := strings.NewReader(plainText) @@ -68,12 +59,12 @@ func (o *OpenPGP) SignTextDetached(plainText string, privateKey string, passphra } // SignTextDetachedBinKey ... -func (o *OpenPGP) SignTextDetachedBinKey(plainText string, privateKey []byte, passphrase string, trim bool) (string, error) { +func (pm *PmCrypto) SignTextDetachedBinKey(plainText string, privateKey []byte, passphrase string, trim bool) (string, error) { //sign with 0x01 var signEntity *openpgp.Entity if trim { - plainText = trimNewlines(plainText) + plainText = internal.TrimNewlines(plainText) } signerReader := bytes.NewReader(privateKey) @@ -99,7 +90,7 @@ func (o *OpenPGP) SignTextDetachedBinKey(plainText string, privateKey []byte, pa return "", errors.New("cannot sign message, singer key is not unlocked") } - config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: o.getTimeGenerator() } + config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pm.getTimeGenerator() } att := strings.NewReader(plainText) @@ -113,7 +104,7 @@ func (o *OpenPGP) SignTextDetachedBinKey(plainText string, privateKey []byte, pa } // SignBinDetached sign bin data -func (o *OpenPGP) SignBinDetached(plainData []byte, privateKey string, passphrase string) (string, error) { +func (pm *PmCrypto) SignBinDetached(plainData []byte, privateKey string, passphrase string) (string, error) { //sign with 0x00 var signEntity *openpgp.Entity @@ -140,7 +131,7 @@ func (o *OpenPGP) SignBinDetached(plainData []byte, privateKey string, passphras return "", errors.New("cannot sign message, singer key is not unlocked") } - config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: o.getTimeGenerator() } + config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pm.getTimeGenerator() } att := bytes.NewReader(plainData) @@ -154,7 +145,7 @@ func (o *OpenPGP) SignBinDetached(plainData []byte, privateKey string, passphras } // SignBinDetachedBinKey ... -func (o *OpenPGP) SignBinDetachedBinKey(plainData []byte, privateKey []byte, passphrase string) (string, error) { +func (pm *PmCrypto) SignBinDetachedBinKey(plainData []byte, privateKey []byte, passphrase string) (string, error) { //sign with 0x00 var signEntity *openpgp.Entity @@ -181,7 +172,7 @@ func (o *OpenPGP) SignBinDetachedBinKey(plainData []byte, privateKey []byte, pas return "", errors.New("cannot sign message, singer key is not unlocked") } - config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: o.getTimeGenerator() } + config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pm.getTimeGenerator() } att := bytes.NewReader(plainData) @@ -195,7 +186,7 @@ func (o *OpenPGP) SignBinDetachedBinKey(plainData []byte, privateKey []byte, pas } // VerifyTextSignDetached ... -func (o *OpenPGP) VerifyTextSignDetached(signature string, plainText string, publicKey string, verifyTime int64) (bool, error) { +func (pm *PmCrypto) VerifyTextSignDetached(signature string, plainText string, publicKey string, verifyTime int64) (bool, error) { pubKeyReader := strings.NewReader(publicKey) @@ -204,7 +195,7 @@ func (o *OpenPGP) VerifyTextSignDetached(signature string, plainText string, pub return false, err } - plainText = trimNewlines(plainText) + plainText = internal.TrimNewlines(plainText) origText := bytes.NewReader(bytes.NewBufferString(plainText).Bytes()) @@ -212,7 +203,7 @@ func (o *OpenPGP) VerifyTextSignDetached(signature string, plainText string, pub } // VerifyTextSignDetachedBinKey ... -func (o *OpenPGP) VerifyTextSignDetachedBinKey(signature string, plainText string, publicKey []byte, verifyTime int64) (bool, error) { +func (pm *PmCrypto) VerifyTextSignDetachedBinKey(signature string, plainText string, publicKey []byte, verifyTime int64) (bool, error) { pubKeyReader := bytes.NewReader(publicKey) @@ -221,7 +212,7 @@ func (o *OpenPGP) VerifyTextSignDetachedBinKey(signature string, plainText strin return false, err } - plainText = trimNewlines(plainText) + plainText = internal.TrimNewlines(plainText) origText := bytes.NewReader(bytes.NewBufferString(plainText).Bytes()) return verifySignature(pubKeyEntries, origText, signature, verifyTime) @@ -235,7 +226,7 @@ func verifySignature(pubKeyEntries openpgp.EntityList, origText *bytes.Reader, s } } else { config.Time = func() time.Time { - return time.Unix(verifyTime + creationTimeOffset, 0) + return time.Unix(verifyTime + internal.CreationTimeOffset, 0) } } signatureReader := strings.NewReader(signature) @@ -272,7 +263,7 @@ func verifySignature(pubKeyEntries openpgp.EntityList, origText *bytes.Reader, s // VerifyBinSignDetached ... -func (o *OpenPGP) VerifyBinSignDetached(signature string, plainData []byte, publicKey string, verifyTime int64) (bool, error) { +func (pm *PmCrypto) VerifyBinSignDetached(signature string, plainData []byte, publicKey string, verifyTime int64) (bool, error) { pubKeyReader := strings.NewReader(publicKey) @@ -286,7 +277,7 @@ func (o *OpenPGP) VerifyBinSignDetached(signature string, plainData []byte, publ } // VerifyBinSignDetachedBinKey ... -func (o *OpenPGP) VerifyBinSignDetachedBinKey(signature string, plainData []byte, publicKey []byte, verifyTime int64) (bool, error) { +func (pm *PmCrypto) VerifyBinSignDetachedBinKey(signature string, plainData []byte, publicKey []byte, verifyTime int64) (bool, error) { pubKeyReader := bytes.NewReader(publicKey) pubKeyEntries, err := openpgp.ReadKeyRing(pubKeyReader) diff --git a/crypto/signature_collector.go b/crypto/signature_collector.go new file mode 100644 index 0000000..bd1d544 --- /dev/null +++ b/crypto/signature_collector.go @@ -0,0 +1,100 @@ +package crypto + +import ( + "bufio" + "bytes" + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" + "io" + "io/ioutil" + "mime" + "mime/multipart" + "net/textproto" + "proton/pmmime" +) + +type SignatureCollector struct { + config *packet.Config + keyring openpgp.KeyRing + target pmmime.VisitAcceptor + signature string + verified int +} + +func newSignatureCollector(targetAccepter pmmime.VisitAcceptor, keyring openpgp.KeyRing, config *packet.Config) *SignatureCollector { + return &SignatureCollector{ + target: targetAccepter, + config: config, + keyring: keyring, + } +} + +func (sc *SignatureCollector) Accept(part io.Reader, header textproto.MIMEHeader, hasPlainSibling bool, isFirst, isLast bool) (err error) { + parentMediaType, params, _ := mime.ParseMediaType(header.Get("Content-Type")) + if parentMediaType == "multipart/signed" { + newPart, rawBody := pmmime.GetRawMimePart(part, "--"+params["boundary"]) + var multiparts []io.Reader + var multipartHeaders []textproto.MIMEHeader + if multiparts, multipartHeaders, err = pmmime.GetMultipartParts(newPart, params); err != nil { + return + } else { + hasPlainChild := false + for _, header := range multipartHeaders { + mediaType, _, _ := mime.ParseMediaType(header.Get("Content-Type")) + if mediaType == "text/plain" { + hasPlainChild = true + } + } + if len(multiparts) != 2 { + sc.verified = notSigned + // Invalid multipart/signed format just pass along + ioutil.ReadAll(rawBody) + for i, p := range multiparts { + if err = sc.target.Accept(p, multipartHeaders[i], hasPlainChild, true, true); err != nil { + return + } + } + return + } + + // actual multipart/signed format + err = sc.target.Accept(multiparts[0], multipartHeaders[0], hasPlainChild, true, true) + if err != nil { + return + } + // TODO: Sunny proposed to move this also to pm-mime library + partData, _ := ioutil.ReadAll(multiparts[1]) + decodedPart := pmmime.DecodeContentEncoding(bytes.NewReader(partData), multipartHeaders[1].Get("Content-Transfer-Encoding")) + buffer, err := ioutil.ReadAll(decodedPart) + if err != nil { + return err + } + buffer, err = pmmime.DecodeCharset(buffer, params) + if err != nil { + return err + } + sc.signature = string(buffer) + str, _ := ioutil.ReadAll(rawBody) + rawBody = bytes.NewReader(str) + if sc.keyring != nil { + _, err = openpgp.CheckArmoredDetachedSignature(sc.keyring, rawBody, bytes.NewReader(buffer), sc.config) + + if err != nil { + sc.verified = failed + } else { + sc.verified = ok + } + } else { + sc.verified = noVerifier + } + return nil + } + return + } + sc.target.Accept(part, header, hasPlainSibling, isFirst, isLast) + return nil +} + +func (ac SignatureCollector) GetSignature() string { + return ac.signature +} diff --git a/crypto/time.go b/crypto/time.go new file mode 100644 index 0000000..a2e980d --- /dev/null +++ b/crypto/time.go @@ -0,0 +1,32 @@ +package crypto + +import ( + "time" +) + +// UpdateTime update cached time +func (pm *PmCrypto) UpdateTime(newTime int64) { + pm.latestServerTime = newTime + pm.latestClientTime = time.Now() +} + +//GetTime get latest cached time +func (pm *PmCrypto) GetTime() int64 { + return pm.getNow().Unix() +} + +func (pm *PmCrypto) getNow() time.Time { + if pm.latestServerTime > 0 && !pm.latestClientTime.IsZero() { + // Sub is monotome, it uses a monotime time clock in this case instead of the wall clock + extrapolate := int64(pm.latestClientTime.Sub(time.Now()).Seconds()) + return time.Unix(pm.latestServerTime + extrapolate, 0) + } + + return time.Now() +} + +func (pm *PmCrypto) getTimeGenerator() func() time.Time { + return func() time.Time { + return pm.getNow() + } +} diff --git a/dist/Android/pmcrypto-sources.jar b/dist/Android/pmcrypto-sources.jar new file mode 100644 index 0000000..1787558 Binary files /dev/null and b/dist/Android/pmcrypto-sources.jar differ diff --git a/dist/Android/pmcrypto.aar b/dist/Android/pmcrypto.aar new file mode 100644 index 0000000..f4d33e2 Binary files /dev/null and b/dist/Android/pmcrypto.aar differ diff --git a/glide.lock b/glide.lock index 2491881..428a4b2 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,10 @@ -hash: 217e5bc8c4d3160eeddd18dda7f8f8785f1b2b7f2ca58779b94ed8fd91ab226d -updated: 2018-06-05T11:36:14.582087-07:00 +hash: 0e0069ce69a4e3bde6233726f21a880347400808a97c2e6f7a173e4d7daea51c +updated: 2018-09-07T10:20:50.211694+02:00 imports: +- name: github.com/Sirupsen/logrus + version: 3791101e143bf0f32515ac23e831475684f61229 - name: golang.org/x/crypto - version: 4dad02e057d03c4f12a519a73d4b4f1837de135d + version: 9e4251120d8c43f10024d798bc6dde21d40704a0 repo: https://github.com/ProtonMail/crypto.git subpackages: - bitcurves @@ -11,6 +13,8 @@ imports: - curve25519 - ed25519 - ed25519/internal/edwards25519 + - internal/randutil + - internal/syscall/unix - openpgp - openpgp/aes/keywrap - openpgp/armor @@ -23,4 +27,23 @@ imports: - openpgp/internal/encoding - openpgp/packet - openpgp/s2k + - rand + - rsa + - ssh/terminal +- name: golang.org/x/sys + version: 8cf3aee429924738c56c34bb819c4ea8273fc434 + subpackages: + - unix + - windows +- name: golang.org/x/text + version: 4ae1256249243a4eb350a9a372e126557f2aa346 + subpackages: + - encoding + - encoding/charmap + - encoding/internal + - encoding/internal/identifier + - transform +- name: proton/pmmime + version: 347d69204aa62d6958bcb3b9bbacccb24a36372b + repo: git@gitlab.protontech.ch:ProtonMail/go-pm-mime.git testImports: [] diff --git a/glide.yaml b/glide.yaml index fb866bb..d62c93c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,5 +1,7 @@ -package: pm +package: proton/pmcrypto import: - package: golang.org/x/crypto version: v1.0.0 repo: https://github.com/ProtonMail/crypto.git +- package: proton/pmmime + repo: git@gitlab.protontech.ch:ProtonMail/go-pm-mime.git diff --git a/internal/armor.go b/internal/armor.go new file mode 100644 index 0000000..93b105b --- /dev/null +++ b/internal/armor.go @@ -0,0 +1,16 @@ +package internal + +import ( + "golang.org/x/crypto/openpgp/armor" + "strings" +) + +func Unarmor(input string) (*armor.Block, error) { + io := strings.NewReader(input) + b, err := armor.Decode(io) + if err != nil { + return nil, err + } + return b, nil + +} diff --git a/internal/common.go b/internal/common.go new file mode 100644 index 0000000..b949f1e --- /dev/null +++ b/internal/common.go @@ -0,0 +1,25 @@ +package internal + +import ( + "gitlab.com/ProtonMail/go-pm-crypto/constants" + "regexp" +) + +func TrimNewlines(input string) string { + var re = regexp.MustCompile(`(?m)[ \t]*$`) + return re.ReplaceAllString(input, "") +} + +// Amount of seconds that a signature may be created after the verify time +// Consistent with the 2 day slack allowed in the ProtonMail Email Parser +const CreationTimeOffset = int64(60 * 60 * 24 * 2) + +const ( + ARMOR_HEADER_VERSION = "Pmcrypto Golang 0.0.1 (" + constants.VERSION + ")" + ARMOR_HEADER_COMMENT = "https://protonmail.com" +) + +var ArmorHeaders = map[string]string{ + "Version": ARMOR_HEADER_VERSION, + "Comment": ARMOR_HEADER_COMMENT, +} diff --git a/fingerprint.go b/key/fingerprint.go similarity index 89% rename from fingerprint.go rename to key/fingerprint.go index b89ddf7..08394c3 100644 --- a/fingerprint.go +++ b/key/fingerprint.go @@ -1,4 +1,4 @@ -package pm +package key import ( "bytes" @@ -6,11 +6,12 @@ import ( "errors" "golang.org/x/crypto/openpgp" + "proton/pmcrypto/armor" ) // GetFingerprint get a armored public key fingerprint func GetFingerprint(publicKey string) (string, error) { - rawPubKey, err := UnArmor(publicKey) + rawPubKey, err := armor.Unarmor(publicKey) if err != nil { return "", err } diff --git a/key/key.go b/key/key.go new file mode 100644 index 0000000..b54f83e --- /dev/null +++ b/key/key.go @@ -0,0 +1,78 @@ +package key + +import ( + "strings" + "golang.org/x/crypto/openpgp" + "fmt" + "golang.org/x/crypto/openpgp/packet" + "bytes" + "proton/pmcrypto/armor" +) + +//CheckPassphrase check is private key passphrase ok +func CheckPassphrase(privateKey string, passphrase string) bool { + privKeyReader := strings.NewReader(privateKey) + entries, err := openpgp.ReadArmoredKeyRing(privKeyReader) + if err != nil { + fmt.Println(err) + return false + } + + var keys []*packet.PrivateKey + + for _, e := range entries { + keys = append(keys, e.PrivateKey) + } + var decryptError error + var n int + for _, key := range keys { + if !key.Encrypted { + continue // Key already decrypted + } + if decryptError = key.Decrypt([]byte(passphrase)); decryptError == nil { + n++ + } + } + if n == 0 { + return false + } + return true +} + + +// PublicKey get a public key from a private key +func PublicKey(privateKey string) (string, error) { + privKeyReader := strings.NewReader(privateKey) + entries, err := openpgp.ReadArmoredKeyRing(privKeyReader) + if err != nil { + return "", err + } + + var outBuf bytes.Buffer + for _, e := range entries { + e.Serialize(&outBuf) + } + + outString, err := armor.ArmorWithType(outBuf.Bytes(), armor.PUBLIC_KEY_HEADER) + if err != nil { + return "", nil + } + + return outString, nil +} + +// PublicKeyBinOut get a public key from a private key +func PublicKeyBinOut(privateKey string) ([]byte, error) { + privKeyReader := strings.NewReader(privateKey) + entries, err := openpgp.ReadArmoredKeyRing(privKeyReader) + if err != nil { + return nil, err + } + + var outBuf bytes.Buffer + for _, e := range entries { + e.Serialize(&outBuf) + } + + return outBuf.Bytes(), nil +} diff --git a/models/models.go b/models/models.go new file mode 100644 index 0000000..9f6ea50 --- /dev/null +++ b/models/models.go @@ -0,0 +1,30 @@ +package models + +//EncryptedSplit when encrypt attachemt +type EncryptedSplit struct { + DataPacket []byte + KeyPacket []byte + Algo string +} + +//SessionSplit split session +type SessionSplit struct { + Session []byte + Algo string +} + +//EncryptedSigned encrypt_sign_package +type EncryptedSigned struct { + Encrypted string + Signature string +} + +//DecryptSignedVerify decrypt_sign_verify +type DecryptSignedVerify struct { + //clear text + Plaintext string + //bitmask verify status : 0 + Verify int + //error message if verify failed + Message string +} diff --git a/time.go b/time.go deleted file mode 100644 index 458f452..0000000 --- a/time.go +++ /dev/null @@ -1,32 +0,0 @@ -package pm - -import ( - "time" -) - -// UpdateTime update cached time -func (o *OpenPGP) UpdateTime(newTime int64) { - o.latestServerTime = newTime - o.latestClientTime = time.Now() -} - -//GetTime get latest cached time -func (o *OpenPGP) GetTime() int64 { - return o.getNow().Unix() -} - -func (o *OpenPGP) getNow() time.Time { - if o.latestServerTime > 0 && !o.latestClientTime.IsZero() { - // Sub is monotome, it uses a monotime time clock in this case instead of the wall clock - extrapolate := int64(o.latestClientTime.Sub(time.Now()).Seconds()) - return time.Unix(o.latestServerTime + extrapolate, 0) - } - - return time.Now() -} - -func (o *OpenPGP) getTimeGenerator() func() time.Time { - return func() time.Time { - return o.getNow() - } -} diff --git a/version.go b/version.go deleted file mode 100644 index c8cbd8b..0000000 --- a/version.go +++ /dev/null @@ -1,6 +0,0 @@ -package pm - -// Version get current lib version -func Version() string { - return "ddacebe0" -}