Refactor api (#6)

* Refactor library, remove duplicates

* Rebuild structure to use Messages and Signature models

* Use PGPSplitMessage

* Remove signature model

* Various fixes

* Add helpers with tests

* Fixes, add some docs, add tests

* Add attachment helpers

* Add helpers Symmetric encryption

* Edit docs + examples

* Rename kr to keyRing

* Various fixes for documentation

* Edit JSON handling functions, add decrypt keyring via token

* Add proposal changes doc

* Fix CI

* Drop *Message functions, join CleartextMessage and BinaryMessage

* Change canonicalization and trimming only to text signatures

* Add cleartextsignature, detach signature from message model, move helpers

* Documentation, remove optional parameters

* Move verification to separate model

* Don't return message in VerifyDetached

* Update table of contents in readme

* Appease golint

* Run go fmt

* Rename Encrypt/DecryptMessageWithPassword to ..WithToken

These functions shouldn't be used with user-provided passwords,
as they don't do any key-stretching.

* Change key generation usernames
This commit is contained in:
wussler 2019-06-03 17:00:01 +02:00 committed by GitHub
parent 82d49bf235
commit e65ed17b41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2573 additions and 1478 deletions

460
ProposalChanges.md Normal file
View file

@ -0,0 +1,460 @@
# Model changes
## Modified
### EncryptedSplit
```
models.EncryptedSplit struct {
DataPacket []byte
KeyPacket []byte
Algo string
}
```
is now
```
crypto.PGPSplitMessage struct {
DataPacket []byte
KeyPacket []byte
}
```
### DecryptSignedVerify
```
models.DecryptSignedVerify struct {
//clear text
Plaintext string
//bitmask verify status : 0
Verify int
//error message if verify failed
Message string
}
```
is now
```
// PlainMessage stores an unencrypted text message.
crypto.PlainMessage struct {
// The content of the message
Text string
// If the decoded message was correctly signed. See constants.SIGNATURE* for all values.
Verified int
}
```
### pmKeyObject
```
type pmKeyObject struct {
ID string
Version int
Flags int
Fingerprint string
PublicKey string `json:",omitempty"`
PrivateKey string
Primary int
}
```
is now
```
type pgpKeyObject struct {
ID string
Version int
Flags int
PrivateKey string
Primary int
Token string `json:",omitempty"`
Signature string `json:",omitempty"`
}
```
## Dropped
### Signature
```
type Signature struct {
md *openpgp.MessageDetails
}
```
### SignedString
```
// SignedString wraps string with Signature
type SignedString struct {
String string
Signed *Signature
}
```
## New
### PGPMessage
```
// PGPMessage stores a PGP-encrypted message.
type PGPMessage struct {
// The content of the message
Data []byte
}
```
### PGPSignature
```
// PGPSignature stores a PGP-encoded detached signature.
type PGPSignature struct {
// The content of the message
Data []byte
}
```
# API changes
## armor.go
### ReadClearSignedMessage
Added signature info to returned info.
```
ReadClearSignedMessage(signedMessage string) (string, error):
```
## attachment.go
### AttachmentProcessor
No change.
### EncryptAttachment
Change encryption parameters to messages: either contextual signature with helper or using messages.
```
(pm *PmCrypto) EncryptAttachment(plainData []byte, fileName string, publicKey *KeyRing) (*models.EncryptedSplit, error):
* (helper) EncryptSignAttachment(publicKey, privateKey, passphrase, fileName string, plainData []byte) (keyPacket, dataPacket, signature []byte, err error)
* (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, fileName string) (*PGPSplitMessage, error)
```
### EncryptAttachmentLowMemory
Renamed.
```
(pm *PmCrypto) EncryptAttachmentLowMemory(estimatedSize int, fileName string, publicKey *KeyRing) (*AttachmentProcessor, error):
* (keyRing *KeyRing) NewLowMemoryAttachmentProcessor(estimatedSize int, fileName string) (*AttachmentProcessor, error)
```
### SplitArmor
Renamed, changed model.
```
SplitArmor(encrypted string) (*models.EncryptedSplit, error):
* NewPGPSplitMessageFromArmored(encrypted string) (*PGPSplitMessage, error)
```
### DecryptAttachment
Same as `EncryptAttachment`.
```
(pm *PmCrypto) DecryptAttachment(keyPacket []byte, dataPacket []byte, kr *KeyRing, passphrase string) ([]byte, error):
* (helper) DecryptVerifyAttachment(publicKey, privateKey, passphrase string, keyPacket, dataPacket []byte, armoredSignature string) (plainData []byte, err error)
* (keyRing *KeyRing) DecryptAttachment(message *PGPSplitMessage) (*PlainMessage, error)
```
## key.go
`SymmetricKey` model and functions have been moved to symmetrickey.go
### DecryptAttKey
Renamed, change to `[]byte` as it's a binary keypacket.
```
DecryptAttKey(kr *KeyRing, keyPacket string) (key *SymmetricKey, err error):
* (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SymmetricKey, error)
```
### SeparateKeyAndData
This function has been split in two, as it **did not** only separate the data, but when provided a KeyRing decrypt the session key too.
```
SeparateKeyAndData(kr *KeyRing, r io.Reader, estimatedLength int, garbageCollector int) (outSplit *models.EncryptedSplit, err error):
* (for separating key and data) (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int) (outSplit *PGPSplitMessage, err error)
* (for decrypting SessionKey) (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SymmetricKey, error)
```
### encodedLength
Dropped as already present in `SeparateKeyAndData` and unused.
### SetKey
Renamed, change to `[]byte` as it's a binary keypacket.
```
SetKey(kr *KeyRing, symKey *SymmetricKey) (packets string, err error):
* (keyRing *KeyRing) EncryptSessionKey(sessionSplit *SymmetricKey) ([]byte, error)
```
### IsKeyExpiredBin
Renamed.
```
(pm *PmCrypto) IsKeyExpiredBin(publicKey []byte) (bool, error):
* (pgp *GopenPGP) IsKeyExpired(publicKey []byte) (bool, error)
```
### IsKeyExpired
Renamed.
```
(pm *PmCrypto) IsKeyExpired(publicKey string) (bool, error):
* (pgp *GopenPGP) IsArmoredKeyExpired(publicKey string) (bool, error)
```
### GenerateRSAKeyWithPrimes
`userName` and `domain` joined in `email`.
Added `name` parameter.
To emulate the old behaviour `name = email = userName + "@" + domain`.
```
(pm *PmCrypto) GenerateRSAKeyWithPrimes(userName, domain, passphrase, keyType string, bits int, prime1, prime2, prime3, prime4 []byte) (string, error):
* (pgp *GopenPGP) GenerateRSAKeyWithPrimes(name, email, passphrase, keyType string, bits int, prime1, prime2, prime3, prime4 []byte) (string, error):
```
### GenerateKey
`userName` and `domain` joined in `email`.
Added `name` parameter.
To emulate the old behaviour `name = email = userName + "@" + domain`.
```
(pm *PmCrypto) GenerateKey(userName, domain, passphrase, keyType string, bits int) (string, error) :
* (pgp *GopenPGP) GenerateKey(name, email, passphrase, keyType string, bits int) (string, error):
```
### UpdatePrivateKeyPassphrase
No change.
### CheckKey
Renamed.
```
(pm *PmCrypto) CheckKey(pubKey string) (string, error):
* (pgp *GopenPGP) PrintFingerprints(pubKey string) (string, error)
```
## keyring.go
### Signature.KeyRing
Dropped with signature.
### Signature.IsBy
Dropped with signature.
### GetEntities
No change.
### GetSigningEntity
KeyRings must be already unlocked when provided to encrypt/decrypt/sign/verify functions.
```
(kr *KeyRing) GetSigningEntity(passphrase string) *openpgp.Entity:
* (keyRing *KeyRing) GetSigningEntity() (*openpgp.Entity, error)
```
### Encrypt, EncryptArmored, EncryptString
This function has been divided in different sub-functions and wrappers have been provided for the key unlock and message models.
```
(kr *KeyRing) Encrypt(w io.Writer, sign *KeyRing, filename string, canonicalizeText bool) (io.WriteCloser, error):
* (if binary data) (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error)
* (if plain text, wrapped) (helper) EncryptMessageArmored(publicKey, plaintext string) (ciphertext string, err error)
* (if plain text, wrapped, signed) (helper) EncryptSignMessageArmored(publicKey, privateKey, passphrase, plaintext string) (ciphertext string, err error)
```
### EncryptCore
Made an internal function.
### EncryptSymmetric
Dropped, now the procedure is split in two parts.
```
(kr *KeyRing) EncryptSymmetric(textToEncrypt string, canonicalizeText bool) (outSplit *models.EncryptedSplit, err error):
* (for encrypting) (keyRing *KeyRing) Encrypt*
* (for splitting) (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int) (outSplit *PGPSplitMessage, err error)
* (alternative) (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, fileName string) (*PGPSplitMessage, error)
```
### DecryptString, Decrypt, DecryptArmored
Same as Encrypt*
```
(kr *KeyRing) DecryptString(encrypted string) (SignedString, error):
* (if binary data) func (keyRing *KeyRing) Decrypt(message *PGPMessage, verifyKey *KeyRing, verifyTime int64) (*PlainMessage, *Verification, error)
* (if plain text, wrapped) (helper) DecryptMessageArmored(privateKey, passphrase, ciphertext string) (plaintext string, err error)
* (if plain text, wrapped, verified) (helper) DecryptVerifyMessageArmored(publicKey, privateKey, passphrase, ciphertext string) (plaintext string, err error)
```
### DecryptStringIfNeeded
Replaced with `IsPGPMessage` + `Decrypt*`.
```
(kr *KeyRing) DecryptStringIfNeeded(data string) (decrypted string, err error):
* (pgp *GopenPGP) IsPGPMessage(data string) bool
```
### SignString, DetachedSign
Replaced by signing methods.
```
(kr *KeyRing) SignString(message string, canonicalizeText bool) (signed string, err error):
(kr *KeyRing) DetachedSign(w io.Writer, toSign io.Reader, canonicalizeText bool, armored bool):
* (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error)
```
### VerifyString
Same as signing.
```
(kr *KeyRing) VerifyString(message, signature string, sign *KeyRing) (err error):
* (to verify) (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) (*Verification, error)
```
### Unlock
No change. Added:
```
(keyRing *KeyRing) UnlockWithPassphrase(passphrase string) error
```
### WriteArmoredPublicKey
No change.
### ArmoredPublicKeyString
Renamed.
```
(kr *KeyRing) ArmoredPublicKeyString() (s string, err error):
* (keyRing *KeyRing) GetArmoredPublicKey() (s string, err error)
```
### BuildKeyRing
No change.
### BuildKeyRingNoError
No change.
### BuildKeyRingArmored
No change.
### UnmarshalJSON
Renamed.
```
(kr *KeyRing) UnmarshalJSON(b []byte) (err error):
* (keyRing *KeyRing) ReadFromJSON(jsonData []byte) (err error)
```
### Identities
No change
### KeyIds
No change.
### ReadArmoredKeyRing
No change.
### ReadKeyRing
No change.
### FilterExpiredKeys
No change.
## message.go
Many functions are duplicates of keyring.go
### EncryptMessage
See Encrypt*
```
(pm *PmCrypto) EncryptMessage(plaintext string, publicKey *KeyRing, privateKey *KeyRing, passphrase string, trim bool) (string, error):
* (if binary data) (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error)
* (if plain text, wrapped) (helper) EncryptMessageArmored(publicKey, plaintext string) (ciphertext string, err error)
* (if plain text, wrapped, signed) (helper) EncryptSignMessageArmored(publicKey, privateKey, passphrase, plaintext string) (ciphertext string, err error)
```
### DecryptMessage, DecryptMessageVerify, DecryptMessageStringKey
See Decrypt*
```
(pm *PmCrypto) DecryptMessage(encryptedText string, privateKey *KeyRing, passphrase string) (string, error):
(pm *PmCrypto) DecryptMessageStringKey(encryptedText string, privateKey string, passphrase string) (string, error):
(pm *PmCrypto) DecryptMessageVerify(encryptedText string, verifierKey *KeyRing, privateKeyRing *KeyRing, passphrase string, verifyTime int64) (*models.DecryptSignedVerify, error) :
* (if binary data) (keyRing *KeyRing) Decrypt(message *PGPMessage, verifyKey *KeyRing, verifyTime int64) (*PlainMessage, *Verification, error)
* (if plain text, wrapped) (helper) DecryptMessageArmored(privateKey, passphrase, ciphertext string) (plaintext string, err error)
* (if plain text, wrapped, verified) (helper) DecryptVerifyMessageArmored(publicKey, privateKey, passphrase, ciphertext string) (plaintext string, err error)
```
### EncryptMessageWithPassword
The function has been renamed and moved to `SymmetricKey` to allow more encryption modes. Previously AES-128 (! not 256 as stated) was used.
```
(pm *PmCrypto) EncryptMessageWithPassword(plaintext string, password string) (string, error):
* (if binary data) (symmetricKey *SymmetricKey) Encrypt(message *PlainMessage) (*PGPMessage, error)
* (if plain text, wrapped) (helper) EncryptMessageWithToken(token, plaintext string) (ciphertext string, err error)
* (if plain text, wrapped) (helper) EncryptMessageWithTokenAlgo(token, plaintext, algo string) (ciphertext string, err error)
```
### DecryptMessageWithPassword
See `EncryptMessageWithPassword`.
```
(pm *PmCrypto) DecryptMessageWithPassword(encrypted string, password string) (string, error):
* (if binary data) (symmetricKey *SymmetricKey) Decrypt(message *PGPMessage) (*PlainMessage, error)
* (if plain text, wrapped, for all ciphers) (helper) DecryptMessageWithToken(token, ciphertext string) (plaintext string, err error)
```
## mime.go
### DecryptMIMEMessage
Moved to `KeyRing`.
```
(pm *PmCrypto) DecryptMIMEMessage(encryptedText string, verifierKey *KeyRing, privateKeyRing *KeyRing, passphrase string, callbacks MIMECallbacks, verifyTime int64):
* (keyRing *KeyRing) DecryptMIMEMessage(message *PGPMessage, verifyKey *KeyRing, callbacks MIMECallbacks, verifyTime int64)
```
## session.go
### RandomToken
No change.
### RandomTokenWith
Renamed.
```
(pm *PmCrypto) RandomTokenWith(size int) ([]byte, error):
* (pgp *GopenPGP) RandomTokenSize(size int) ([]byte, error)
```
### GetSessionFromKeyPacket
Dropped, use now `DecryptSessionKey`.
```
(pm *PmCrypto) GetSessionFromKeyPacket(keyPackage []byte, privateKey *KeyRing, passphrase string) (*SymmetricKey, error):
* (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SymmetricKey, error)
```
### KeyPacketWithPublicKey, KeyPacketWithPublicKeyBin
Dropped, use now `EncryptSessionKey`.
```
(pm *PmCrypto) KeyPacketWithPublicKey(sessionSplit *SymmetricKey, publicKey string) ([]byte, error):
(pm *PmCrypto) KeyPacketWithPublicKeyBin(sessionSplit *SymmetricKey, publicKey []byte) ([]byte, error):
* (keyRing *KeyRing) EncryptSessionKey(sessionSplit *SymmetricKey) ([]byte, error)
```
### GetSessionFromSymmetricPacket
Renamed, moved to `SymmetricKey`.
```
(pm *PmCrypto) GetSessionFromSymmetricPacket(keyPackage []byte, password string) (*SymmetricKey, error):
* NewSymmetricKeyFromKeyPacket(keyPacket []byte, password string) (*SymmetricKey, error)
```
### SymmetricKeyPacketWithPassword
Renamed, moved to `SymmetricKey`.
```
(pm *PmCrypto) SymmetricKeyPacketWithPassword(sessionSplit *SymmetricKey, password string) ([]byte, error):
* (symmetricKey *SymmetricKey) EncryptToKeyPacket(password string) ([]byte, error)
```
## sign_detached.go
### SignTextDetached
Moved to `KeyRing`, changed to `Sign`.
```
(pm *PmCrypto) SignTextDetached(plaintext string, privateKey *KeyRing, passphrase string, trim bool) (string, error):
* (if just signature) (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error)
* (if PGP SIGNED MESSAGE) (helper) SignCleartextMessage(keyRing *crypto.KeyRing, text string) (string, error)
* (if PGP SIGNED MESSAGE) (helper) SignCleartextMessageArmored(privateKey, passphrase, text string) (string, error)
```
### SignBinDetached
Moved to `KeyRing`.
```
(pm *PmCrypto) SignBinDetached(plainData []byte, privateKey *KeyRing, passphrase string) (string, error):
* (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error)
```
### VerifyTextSignDetachedBinKey, VerifyBinSignDetachedBinKey
Moved to `KeyRing`, changed to Verify.
See signature_test.go for use examples.
```
(pm *PmCrypto) VerifyTextSignDetachedBinKey(signature string, plaintext string, publicKey *KeyRing, verifyTime int64) (bool, error):
(pm *PmCrypto) VerifyBinSignDetachedBinKey(signature string, plainData []byte, publicKey *KeyRing, verifyTime int64) (bool, error):
* (to verify) (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) (*Verification, error)
* (if PGP SIGNED MESSAGE) (helper) VerifyCleartextMessage(keyRing *crypto.KeyRing, armored string, verifyTime int64) (string, error)
* (if PGP SIGNED MESSAGE) (helper) VerifyCleartextMessageArmored(publicKey, armored string, verifyTime int64) (string, error)
```
## signature_collector.go
No change.
## time.go
### UpdateTime
No change.
### GetTimeUnix
Renamed.
```
(pm *PmCrypto) GetTimeUnix() int64:
(pm *PmCrypto) GetUnixTime() int64
```
### GetTime
No change.

185
README.md
View file

@ -11,14 +11,15 @@ crypto library](https://github.com/ProtonMail/crypto).
- [Documentation](#documentation)
- [Using with Go Mobile](#using-with-go-mobile)
- [Other notes](#other-notes)
- [Full documentation](#full-documentation)
- [Examples](#examples)
- [Set up](#set-up)
- [Encrypt and decrypt](#encrypt-and-decrypt)
- [Encrypt / Decrypt with password](#encrypt--decrypt-with-password)
- [Encrypt / Decrypt with PGP keys](#encrypt--decrypt-with-pgp-keys)
- [Encrypt / Decrypt with password](#encrypt--decrypt-with-password)
- [Encrypt / Decrypt with PGP keys](#encrypt--decrypt-with-pgp-keys)
- [Generate key](#generate-key)
- [Sign plain text messages](#sign-plain-text-messages)
- [Detached signatures for plain text messages](#detached-signatures-for-plain-text-messages)
- [Detached signatures for binary data](#detached-signatures-for-binary-data)
- [Cleartext signed messages](#cleartext-signed-messages)
<!-- /TOC -->
@ -70,6 +71,9 @@ If you wish to use build.sh, you may need to modify the paths in it.
Interfacing between Go and Swift:
https://medium.com/@matryer/tutorial-calling-go-code-from-swift-on-ios-and-vice-versa-with-gomobile-7925620c17a4.
## Full documentation
The full documentation for this API is available here: https://godoc.org/gopkg.in/ProtonMail/gopenpgp.v0/crypto
## Examples
### Set up
@ -78,27 +82,53 @@ https://medium.com/@matryer/tutorial-calling-go-code-from-swift-on-ios-and-vice-
import "github.com/ProtonMail/gopenpgp/crypto"
```
### Encrypt and decrypt
Encryption and decryption will use the AES256 algorithm by default.
#### Encrypt / Decrypt with password
### Encrypt / Decrypt with password
```go
var pgp = crypto.GopenPGP{}
import "github.com/ProtonMail/gopenpgp/helper"
const password = "my secret password"
// Encrypt data with password
armor, err := pgp.EncryptMessageWithPassword("my message", password)
armor, err := helper.EncryptMessageWithToken(password, "my message")
// Decrypt data with password
message, err := pgp.DecryptMessageWithPassword(armor, password)
message, err := helper.DecryptMessageWithToken(password, armor)
```
#### Encrypt / Decrypt with PGP keys
To use more encryption algorithms:
```go
import "github.com/ProtonMail/gopenpgp/constants"
import "github.com/ProtonMail/gopenpgp/helper"
// Encrypt data with password
armor, err := helper.EncryptMessageWithTokenAlgo(password, "my message", constants.ThreeDES)
// Decrypt data with password
message, err := helper.DecryptMessageWithToken(password, armor)
```
To encrypt binary data, reuse the key multiple times, or use more advanced modes:
```go
import "github.com/ProtonMail/gopenpgp/constants"
var key = crypto.NewSymmetricKey("my secret password", constants.AES256)
var message = crypto.NewPlainMessage(data)
// Encrypt data with password
encrypted, err := key.Encrypt(message)
// Decrypt data with password
decrypted, err := key.Decrypt(password, encrypted)
//Original message in decrypted.GetBinary()
```
### Encrypt / Decrypt with PGP keys
```go
import "github.com/ProtonMail/gopenpgp/helper"
// put keys in backtick (``) to avoid errors caused by spaces or tabs
const pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
@ -110,29 +140,52 @@ const privkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
const passphrase = `the passphrase of the private key` // what the privKey is encrypted with
publicKeyRing, err := crypto.ReadArmoredKeyRing(strings.NewReader(pubkey))
privateKeyRing, err := crypto.ReadArmoredKeyRing(strings.NewReader(privkey))
privateKeyRing.Unlock([]byte(passphrase)) // if private key is locked with passphrase
// encrypt message using public key, can be optionally signed using private key
armor, err := publicKeyRing.EncryptMessage("plain text", privateKeyRing)
// encrypt message using public key
armor, err := helper.EncryptMessageArmored(pubkey, "plain text")
// decrypt armored encrypted message using the private key
signedText, err := privateKeyRing.DecryptMessage(armor)
plainText = signedText.String
// verify signature (optional)
signed = signedText.Signed.IsBy(publicKeyRing)
decrypted, err := helper.DecryptMessageArmored(privkey, passphrase, armor)
```
With signatures:
```go
// Keys initialization as before (omitted)
// encrypt message using public key, sign with the private key
armor, err := helper.EncryptSignMessageArmored(pubkey, privkey, passphrase, "plain text")
// decrypt armored encrypted message using the private key, verify with the public key
// err != nil if verification fails
decrypted, err := helper.DecryptVerifyMessageArmored(pubkey, privkey, passphrase, armor)
```
With binary data or advanced modes:
```go
// Keys initialization as before (omitted)
var binMessage = NewPlainMessage(data)
publicKeyRing, err := pgp.BuildKeyRingArmored(publicKey)
privateKeyRing, err := pgp.BuildKeyRingArmored(privateKey)
err = privateKeyRing.UnlockWithPassphrase(passphrase)
pgpMessage, err := publicKeyRing.Encrypt(binMessage, privateKeyRing)
// Armored message in pgpMessage.GetArmored()
// pgpMessage can be obtained from NewPGPMessageFromArmored(ciphertext)
message, verification, err := privateKeyRing.Decrypt(pgpMessage, publicKeyRing, pgp.GetUnixTime())
// Original data in message.GetString()
if verification.IsValid() {
// verification success
}
```
### Generate key
Keys are generated with the `GenerateKey` function, that returns the armored key as a string and a potential error.
The library supports RSA with different key lengths or Curve25519 keys.
```go
var pgp = crypto.GopenPGP{}
var pgp = crypto.GetGopenPGP()
const (
localPart = "name.surname"
@ -149,7 +202,7 @@ rsaKey, err := pgp.GenerateKey(localPart, domain, passphrase, "rsa", rsaBits)
ecKey, err := pgp.GenerateKey(localPart, domain, passphrase, "x25519", ecBits)
```
### Sign plain text messages
### Detached signatures for plain text messages
To sign plain text data either an unlocked private keyring or a passphrase must be provided.
The output is an armored signature.
@ -158,20 +211,25 @@ The output is an armored signature.
const privkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----` // encrypted private key
passphrase = "LongSecret"
const passphrase = "LongSecret"
const trimNewlines = false
signingKeyRing, err := crypto.ReadArmoredKeyRing(strings.NewReader(privkey))
var message = NewPlaintextMessage("Verified message")
signature, err := signingKeyRing.SignTextDetached(plaintext, passphrase, trimNewlines)
// passphrase is optional if the key is already unlocked
signingKeyRing, err := pgp.BuildKeyRingArmored(privkey)
signingKeyRing.UnlockWithPassphrase(passphrase) // if private key is locked with passphrase
pgpSignature, err := signingKeyRing.SignDetached(message, trimNewlines)
// The armored signature is in pgpSignature.GetArmored()
// The signed text is in message.GetString()
```
To verify a signature either private or public keyring can be provided.
The newlines in the text are never trimmed in the verification process.
The function outputs a bool, if the verification fails `verified` will be false, and the error will be not `nil`.
```go
var pgp = crypto.GetGopenPGP()
const pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`
@ -180,36 +238,43 @@ const signature = `-----BEGIN PGP SIGNATURE-----
...
-----END PGP SIGNATURE-----`
const verifyTime = 0
const trimNewlines = false
message := NewPlaintextMessage("Verified message")
pgpSignature, err := NewPGPSignatureFromArmored(signature)
signingKeyRing, err := pgp.BuildKeyRingArmored(pubkey)
signingKeyRing, err := crypto.ReadArmoredKeyRing(strings.NewReader(pubkey))
verification, err := signingKeyRing.VerifyDetached(message, pgpSignature, pgp.GetUnixTime())
verified, err := signingKeyRing.VerifyTextDetachedSig(signature, signedPlainText, verifyTime, trimNewlines)
if verification.IsValid() {
// verification success
}
```
### Detached signatures for binary data
To sign binary data either an unlocked private keyring or a passphrase must be provided.
The output is an armored signature.
```go
var pgp = crypto.GetGopenPGP()
const privkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----` // encrypted private key
passphrase = "LongSecret"
const passphrase = "LongSecret"
signingKeyRing, err := crypto.ReadArmoredKeyRing(strings.NewReader(privkey))
var message = NewPlainMessage(data)
signature, err := signingKeyRing.SignBinDetached(data, passphrase)
// passphrase is optional if the key is already unlocked
signingKeyRing, err := pgp.BuildKeyRingArmored(privkey)
signingKeyRing.UnlockWithPassphrase(passphrase) // if private key is locked with passphrase
pgpSignature, err := signingKeyRing.SignDetached(message)
// The armored signature is in pgpSignature.GetArmored()
// The signed text is in message.GetBinary()
```
To verify a signature either private or public keyring can be provided.
The newlines in the text are never trimmed in the verification process.
The function outputs a bool, if the verification fails `verified` will be false, and the error will be not `nil`.
```go
var pgp = crypto.GetGopenPGP()
const pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`
@ -218,9 +283,31 @@ const signature = `-----BEGIN PGP SIGNATURE-----
...
-----END PGP SIGNATURE-----`
const verifyTime = 0
message := NewPlainMessage("Verified message")
pgpSignature, err := NewPGPSignatureFromArmored(signature)
signingKeyRing, err := pgp.BuildKeyRingArmored(pubkey)
signingKeyRing, err := crypto.ReadArmoredKeyRing(strings.NewReader(pubkey))
verification, err := signingKeyRing.VerifyDetached(message, pgpSignature, pgp.GetUnixTime())
verified, err := signingKeyRing.VerifyBinDetachedSig(signature, data, verifyTime)
if verification.IsValid() {
// verification success
}
```
### Cleartext signed messages
```go
// Keys initialization as before (omitted)
armored, err := SignCleartextMessageArmored(privateKey, passphrase, plaintext)
```
To verify the message it has to be provided unseparated to the library.
If verification fails an error will be returned.
```go
// Keys initialization as before (omitted)
var pgp = crypto.GetGopenPGP()
var verifyTime = pgp.GetUnixTime()
verifiedPlainText, err := VerifyCleartextMessageArmored(publicKey, armored, verifyTime)
```

View file

@ -5,12 +5,14 @@ package armor
import (
"bytes"
"errors"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/clearsign"
"io"
"io/ioutil"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/clearsign"
)
// ArmorKey armors input as a public key.
@ -50,11 +52,32 @@ func Unarmor(input string) ([]byte, error) {
return ioutil.ReadAll(b.Body)
}
// ReadClearSignedMessage returns the message body from a clearsigned message.
func ReadClearSignedMessage(signedMessage string) (string, error) {
// ReadClearSignedMessage returns the message body and unarmored signature from a clearsigned message.
func ReadClearSignedMessage(signedMessage string) (string, []byte, error) {
modulusBlock, rest := clearsign.Decode([]byte(signedMessage))
if len(rest) != 0 {
return "", errors.New("pmapi: extra data after modulus")
return "", nil, errors.New("pmapi: extra data after modulus")
}
return string(modulusBlock.Bytes), nil
signature, err := ioutil.ReadAll(modulusBlock.ArmoredSignature.Body)
if err != nil {
return "", nil, err
}
return string(modulusBlock.Bytes), signature, nil
}
// ArmorClearSignedMessage armors plaintext and signature with the PGP SIGNED MESSAGE armoring
func ArmorClearSignedMessage(plaintext []byte, signature []byte) (string, error) {
armSignature, err := ArmorWithType(signature, constants.PGPSignatureHeader)
if err != nil {
return "", err
}
str := "-----BEGIN PGP SIGNED MESSAGE-----\r\nHash:SHA512\r\n\r\n"
str += string(plaintext)
str += "\r\n"
str += armSignature
return str, nil
}

View file

@ -6,6 +6,7 @@ const (
ArmorHeaderVersion = "GopenPGP 0.0.1 (" + Version + ")"
ArmorHeaderComment = "https://gopenpgp.org"
PGPMessageHeader = "PGP MESSAGE"
PGPSignatureHeader = "PGP SIGNATURE"
PublicKeyHeader = "PGP PUBLIC KEY BLOCK"
PrivateKeyHeader = "PGP PRIVATE KEY BLOCK"
)

View file

@ -9,3 +9,10 @@ const (
AES192 = "aes192"
AES256 = "aes256"
)
const (
SIGNATURE_OK int = 0
SIGNATURE_NOT_SIGNED int = 1
SIGNATURE_NO_VERIFIER int = 2
SIGNATURE_FAILED int = 3
)

View file

@ -2,15 +2,11 @@ package crypto
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"runtime"
"sync"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/models"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
@ -21,7 +17,7 @@ type AttachmentProcessor struct {
w *io.WriteCloser
pipe *io.PipeWriter
done sync.WaitGroup
split *models.EncryptedSplit
split *PGPSplitMessage
garbageCollector int
err error
}
@ -34,7 +30,7 @@ func (ap *AttachmentProcessor) Process(plainData []byte) {
}
// Finish closes the attachment and returns the encrypted data
func (ap *AttachmentProcessor) Finish() (*models.EncryptedSplit, error) {
func (ap *AttachmentProcessor) Finish() (*PGPSplitMessage, error) {
if ap.err != nil {
return nil, ap.err
}
@ -47,10 +43,10 @@ func (ap *AttachmentProcessor) Finish() (*models.EncryptedSplit, error) {
return ap.split, nil
}
// encryptAttachment creates an AttachmentProcessor which can be used to encrypt
// newAttachmentProcessor creates an AttachmentProcessor which can be used to encrypt
// a file. It takes an estimatedSize and fileName as hints about the file.
func (pgp *GopenPGP) encryptAttachment(
estimatedSize int, fileName string, publicKey *KeyRing, garbageCollector int,
func (keyRing *KeyRing) newAttachmentProcessor(
estimatedSize int, fileName string, garbageCollector int,
) (*AttachmentProcessor, error) {
attachmentProc := &AttachmentProcessor{}
// You could also add these one at a time if needed.
@ -70,17 +66,18 @@ func (pgp *GopenPGP) encryptAttachment(
go func() {
defer attachmentProc.done.Done()
split, splitError := SeparateKeyAndData(nil, reader, estimatedSize, garbageCollector)
ciphertext, _ := ioutil.ReadAll(reader)
message := NewPGPMessage(ciphertext)
split, splitError := message.SeparateKeyAndData(estimatedSize, garbageCollector)
if attachmentProc.err != nil {
attachmentProc.err = splitError
}
split.Algo = constants.AES256
attachmentProc.split = split
}()
var ew io.WriteCloser
var encryptErr error
ew, encryptErr = openpgp.Encrypt(writer, publicKey.entities, nil, hints, config)
ew, encryptErr = openpgp.Encrypt(writer, keyRing.entities, nil, hints, config)
if encryptErr != nil {
return nil, encryptErr
}
@ -90,15 +87,14 @@ func (pgp *GopenPGP) encryptAttachment(
return attachmentProc, nil
}
// EncryptAttachment encrypts a file. fileName
func (pgp *GopenPGP) EncryptAttachment(
plainData []byte, fileName string, publicKey *KeyRing,
) (*models.EncryptedSplit, error) {
ap, err := pgp.encryptAttachment(len(plainData), fileName, publicKey, -1)
// EncryptAttachment encrypts a file given a PlainMessage and a fileName.
// Returns a PGPSplitMessage containing a session key packet and symmetrically encrypted data
func (keyRing *KeyRing) EncryptAttachment(message *PlainMessage, fileName string) (*PGPSplitMessage, error) {
ap, err := keyRing.newAttachmentProcessor(len(message.GetBinary()), fileName, -1)
if err != nil {
return nil, err
}
ap.Process(plainData)
ap.Process(message.GetBinary())
split, err := ap.Finish()
if err != nil {
return nil, err
@ -106,47 +102,23 @@ func (pgp *GopenPGP) EncryptAttachment(
return split, nil
}
// EncryptAttachmentLowMemory creates an AttachmentProcessor which can be used
// NewLowMemoryAttachmentProcessor creates an AttachmentProcessor which can be used
// to encrypt a file. It takes an estimatedSize and fileName as hints about the
// file. It is optimized for low-memory environments and collects garbage every
// megabyte.
func (pgp *GopenPGP) EncryptAttachmentLowMemory(
estimatedSize int, fileName string, publicKey *KeyRing,
func (keyRing *KeyRing) NewLowMemoryAttachmentProcessor(
estimatedSize int, fileName string,
) (*AttachmentProcessor, error) {
return pgp.encryptAttachment(estimatedSize, fileName, publicKey, 1<<20)
return keyRing.newAttachmentProcessor(estimatedSize, fileName, 1<<20)
}
// SplitArmor is a helper method which splits an armored message into its
// session key packet and symmetrically encrypted data packet.
func SplitArmor(encrypted string) (*models.EncryptedSplit, error) {
var err error
// DecryptAttachment takes a PGPSplitMessage, containing a session key packet and symmetrically encrypted data
// and returns a decrypted PlainMessage
func (keyRing *KeyRing) DecryptAttachment(message *PGPSplitMessage) (*PlainMessage, error) {
privKeyEntries := keyRing.entities
encryptedRaw, err := armorUtils.Unarmor(encrypted)
if err != nil {
return nil, err
}
encryptedReader := bytes.NewReader(encryptedRaw)
return SeparateKeyAndData(nil, encryptedReader, len(encrypted), -1)
}
// DecryptAttachment takes a session key packet and symmetrically encrypted data
// packet. privateKeys is a KeyRing that can contain multiple keys. The
// passphrase is used to unlock keys in privateKeys.
func (pgp *GopenPGP) DecryptAttachment(
keyPacket, dataPacket []byte,
kr *KeyRing, passphrase string,
) ([]byte, error) {
privKeyEntries := kr.entities
if err := kr.Unlock([]byte(passphrase)); err != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt attachment: %v", err)
return nil, err
}
keyReader := bytes.NewReader(keyPacket)
dataReader := bytes.NewReader(dataPacket)
keyReader := bytes.NewReader(message.GetKeyPacket())
dataReader := bytes.NewReader(message.GetDataPacket())
encryptedReader := io.MultiReader(keyReader, dataReader)
@ -163,5 +135,5 @@ func (pgp *GopenPGP) DecryptAttachment(
return nil, err
}
return b, nil
return NewPlainMessage(b), nil
}

View file

@ -2,7 +2,6 @@ package crypto
import (
"encoding/base64"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@ -13,54 +12,45 @@ import (
func TestAttachmentGetKey(t *testing.T) {
testKeyPacketsDecoded, err := base64.StdEncoding.DecodeString(readTestFile("attachment_keypacket", false))
if err != nil {
t.Fatal("Expected no error while decoding base64 KeyPacket, got:", err)
}
split, err := SeparateKeyAndData(
testPrivateKeyRing,
strings.NewReader(string(testKeyPacketsDecoded)),
len(testKeyPacketsDecoded),
-1)
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(testKeyPacketsDecoded)
if err != nil {
t.Fatal("Expected no error while decrypting attachment key, got:", err)
t.Fatal("Expected no error while decrypting KeyPacket, got:", err)
}
assert.Exactly(t, testSymmetricKey.Key, split.KeyPacket)
assert.Exactly(t, testSymmetricKey, symmetricKey)
}
func TestAttachmentSetKey(t *testing.T) {
packets, err := testPublicKeyRing.EncryptKey(testSymmetricKey)
keyPackets, err := testPublicKeyRing.EncryptSessionKey(testSymmetricKey)
if err != nil {
t.Fatal("Expected no error while encrypting attachment key, got:", err)
}
keyPackets, err := base64.StdEncoding.DecodeString(packets)
if err != nil {
t.Fatal("Expected no error while decoding base64 KeyPacket, got:", err)
}
split, err := SeparateKeyAndData(testPrivateKeyRing, strings.NewReader(string(keyPackets)), len(keyPackets), -1)
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(keyPackets)
if err != nil {
t.Fatal("Expected no error while decrypting attachment key, got:", err)
}
assert.Exactly(t, testSymmetricKey.Key, split.KeyPacket)
assert.Exactly(t, testSymmetricKey, symmetricKey)
}
func TestAttachnentEncryptDecrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille."
var message = NewPlainMessage([]byte(testAttachmentCleartext))
encSplit, err := pgp.EncryptAttachment([]byte(testAttachmentCleartext), "s.txt", testPrivateKeyRing)
encSplit, err := testPrivateKeyRing.EncryptAttachment(message, "s.txt")
if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err)
}
redecData, err := pgp.DecryptAttachment(encSplit.KeyPacket, encSplit.DataPacket, testPrivateKeyRing, "")
redecData, err := testPrivateKeyRing.DecryptAttachment(encSplit)
if err != nil {
t.Fatal("Expected no error while decrypting attachment, got:", err)
}
assert.Exactly(t, testAttachmentCleartext, string(redecData))
assert.Exactly(t, message, redecData)
}

View file

@ -3,278 +3,22 @@ package crypto
import (
"bytes"
"crypto"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"runtime"
"strings"
"time"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/models"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// SymmetricKey stores a decrypted session key.
type SymmetricKey struct {
// The decrypted binary session key.
Key []byte
// The symmetric encryption algorithm used with this key.
Algo string
}
var symKeyAlgos = map[string]packet.CipherFunction{
constants.ThreeDES: packet.Cipher3DES,
constants.TripleDES: packet.Cipher3DES,
constants.CAST5: packet.CipherCAST5,
constants.AES128: packet.CipherAES128,
constants.AES192: packet.CipherAES192,
constants.AES256: packet.CipherAES256,
}
// GetCipherFunc returns the cipher function corresponding to the algorithm used
// with this SymmetricKey.
func (sk *SymmetricKey) GetCipherFunc() packet.CipherFunction {
cf, ok := symKeyAlgos[sk.Algo]
if ok {
return cf
}
panic("gopenpgp: unsupported cipher function: " + sk.Algo)
}
// GetBase64Key returns the session key as base64 encoded string.
func (sk *SymmetricKey) GetBase64Key() string {
return base64.StdEncoding.EncodeToString(sk.Key)
}
func newSymmetricKey(ek *packet.EncryptedKey) *SymmetricKey {
var algo string
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
break
}
}
if algo == "" {
panic(fmt.Sprintf("gopenpgp: unsupported cipher function: %v", ek.CipherFunc))
}
return &SymmetricKey{
Key: ek.Key, //base64.StdEncoding.EncodeToString(ek.Key),
Algo: algo,
}
}
// DecryptAttKey decrypts a public-key encrypted session key and returns the
// decrypted symmetric session key.
func DecryptAttKey(kr *KeyRing, keyPacket string) (key *SymmetricKey, err error) {
r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(keyPacket))
packets := packet.NewReader(r)
var p packet.Packet
if p, err = packets.Next(); err != nil {
return
}
ek := p.(*packet.EncryptedKey)
var decryptErr error
for _, key := range kr.entities.DecryptionKeys() {
priv := key.PrivateKey
if priv.Encrypted {
continue
}
if decryptErr = ek.Decrypt(priv, nil); decryptErr == nil {
break
}
}
if decryptErr != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
return
}
key = newSymmetricKey(ek)
return
}
// SeparateKeyAndData reads a binary PGP message from r and splits it into its
// session key packet and symmetrically encrypted data packet.
func SeparateKeyAndData(
kr *KeyRing, r io.Reader,
estimatedLength, garbageCollector int,
) (outSplit *models.EncryptedSplit, err error) {
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
packets := packet.NewReader(r)
outSplit = &models.EncryptedSplit{}
gcCounter := 0
// Store encrypted key and symmetrically encrypted packet separately
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
if kr != nil {
for _, key := range kr.entities.DecryptionKeys() {
priv := key.PrivateKey
if priv.Encrypted {
continue
}
if decryptErr = ek.Decrypt(priv, nil); decryptErr == nil {
break
}
}
}
case *packet.SymmetricallyEncrypted:
// The code below is optimized to not
var b bytes.Buffer
// 2^16 is an estimation of the size difference between input and output, the size difference is most probably
// 16 bytes at a maximum though.
// We need to avoid triggering a grow from the system as this will allocate too much memory causing problems
// in low-memory environments
b.Grow(1<<16 + estimatedLength)
// empty encoded length + start byte
b.Write(make([]byte, 6))
b.WriteByte(byte(1))
actualLength := 1
block := make([]byte, 128)
for {
n, err := p.Contents.Read(block)
if err == io.EOF {
break
}
b.Write(block[:n])
actualLength += n
gcCounter += n
if gcCounter > garbageCollector && garbageCollector > 0 {
runtime.GC()
gcCounter = 0
}
}
// quick encoding
symEncryptedData := b.Bytes()
if actualLength < 192 {
symEncryptedData[4] = byte(210)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[4:]
} else if actualLength < 8384 {
actualLength = actualLength - 192
symEncryptedData[3] = byte(210)
symEncryptedData[4] = 192 + byte(actualLength>>8)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[3:]
} else {
symEncryptedData[0] = byte(210)
symEncryptedData[1] = byte(255)
symEncryptedData[2] = byte(actualLength >> 24)
symEncryptedData[3] = byte(actualLength >> 16)
symEncryptedData[4] = byte(actualLength >> 8)
symEncryptedData[5] = byte(actualLength)
}
outSplit.DataPacket = symEncryptedData
}
}
if decryptErr != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
return nil, err
}
if ek == nil {
err = errors.New("gopenpgp: packets don't include an encrypted key packet")
return nil, err
}
if kr == nil {
var buf bytes.Buffer
if err := ek.Serialize(&buf); err != nil {
err = fmt.Errorf("gopenpgp: cannot serialize encrypted key: %v", err)
return nil, err
}
outSplit.KeyPacket = buf.Bytes()
} else {
key := newSymmetricKey(ek)
outSplit.KeyPacket = key.Key
outSplit.Algo = key.Algo
}
return outSplit, nil
}
// EncryptKey encrypts the provided key.
func (kr *KeyRing) EncryptKey(symKey *SymmetricKey) (packets string, err error) {
b := &bytes.Buffer{}
w := base64.NewEncoder(base64.StdEncoding, b)
cf := symKey.GetCipherFunc()
if len(kr.entities) == 0 {
err = fmt.Errorf("gopenpgp: cannot set key: key ring is empty")
return
}
var pub *packet.PublicKey
for _, e := range kr.entities {
for _, subKey := range e.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
pub = subKey.PublicKey
break
}
}
if pub == nil && len(e.Identities) > 0 {
var i *openpgp.Identity
for _, i = range e.Identities {
break
}
if i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptStorage || i.SelfSignature.FlagEncryptCommunications {
pub = e.PrimaryKey
}
}
if pub != nil {
break
}
}
if pub == nil {
err = fmt.Errorf("gopenpgp: cannot set key: no public key available")
return "", err
}
if err = packet.SerializeEncryptedKey(w, pub, cf, symKey.Key, nil); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return "", err
}
if err = w.Close(); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return "", err
}
return b.String(), nil
}
// IsKeyExpiredBin checks whether the given (unarmored, binary) key is expired.
func (pgp *GopenPGP) IsKeyExpiredBin(publicKey []byte) (bool, error) {
// IsKeyExpired checks whether the given (unarmored, binary) key is expired.
func (pgp *GopenPGP) IsKeyExpired(publicKey []byte) (bool, error) {
now := pgp.getNow()
pubKeyReader := bytes.NewReader(publicKey)
pubKeyEntries, err := openpgp.ReadKeyRing(pubKeyReader)
@ -325,34 +69,26 @@ func (pgp *GopenPGP) IsKeyExpiredBin(publicKey []byte) (bool, error) {
return true, errors.New("keys expired")
}
const (
ok = 0
notSigned = 1
noVerifier = 2
failed = 3
)
// IsKeyExpired checks whether the given armored key is expired.
func (pgp *GopenPGP) IsKeyExpired(publicKey string) (bool, error) {
// IsArmoredKeyExpired checks whether the given armored key is expired.
func (pgp *GopenPGP) IsArmoredKeyExpired(publicKey string) (bool, error) {
rawPubKey, err := armor.Unarmor(publicKey)
if err != nil {
return false, err
}
return pgp.IsKeyExpiredBin(rawPubKey)
return pgp.IsKeyExpired(rawPubKey)
}
func (pgp *GopenPGP) generateKey(
userName, domain, passphrase, keyType string,
name, email, passphrase, keyType string,
bits int,
prime1, prime2, prime3, prime4 []byte,
) (string, error) {
if len(userName) <= 0 {
return "", errors.New("invalid user name format")
if len(email) <= 0 {
return "", errors.New("invalid email format")
}
var email = userName
if len(domain) > 0 {
email = email + "@" + domain
if len(name) <= 0 {
return "", errors.New("invalid name format")
}
comments := ""
@ -383,7 +119,7 @@ func (pgp *GopenPGP) generateKey(
cfg.RSAPrimes = bigPrimes[:]
}
newEntity, err := openpgp.NewEntity(email, comments, email, cfg)
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
if err != nil {
return "", err
}
@ -417,23 +153,22 @@ func (pgp *GopenPGP) generateKey(
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
func (pgp *GopenPGP) GenerateRSAKeyWithPrimes(
userName, domain, passphrase string,
name, email, passphrase string,
bits int,
primeone, primetwo, primethree, primefour []byte,
) (string, error) {
return pgp.generateKey(userName, domain, passphrase, "rsa", bits, primeone, primetwo, primethree, primefour)
return pgp.generateKey(name, email, passphrase, "rsa", bits, primeone, primetwo, primethree, primefour)
}
// GenerateKey generates a key of the given keyType ("rsa" or "x25519"). If
// keyType is "rsa", bits is the RSA bitsize of the key. If keyType is "x25519",
// bits is unused.
func (pgp *GopenPGP) GenerateKey(userName, domain, passphrase, keyType string, bits int) (string, error) {
return pgp.generateKey(userName, domain, passphrase, keyType, bits, nil, nil, nil, nil)
// GenerateKey generates a key of the given keyType ("rsa" or "x25519").
// If keyType is "rsa", bits is the RSA bitsize of the key.
// If keyType is "x25519" bits is unused.
func (pgp *GopenPGP) GenerateKey(name, email, passphrase, keyType string, bits int) (string, error) {
return pgp.generateKey(name, email, passphrase, keyType, bits, nil, nil, nil, nil)
}
// UpdatePrivateKeyPassphrase decrypts the given armored privateKey with
// oldPassphrase, re-encrypts it with newPassphrase, and returns the new armored
// key.
// UpdatePrivateKeyPassphrase decrypts the given armored privateKey with oldPassphrase,
// re-encrypts it with newPassphrase, and returns the new armored key.
func (pgp *GopenPGP) UpdatePrivateKeyPassphrase(
privateKey string, oldPassphrase string, newPassphrase string,
) (string, error) {
@ -479,9 +214,8 @@ func (pgp *GopenPGP) UpdatePrivateKeyPassphrase(
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// CheckKey is a debug helper function that prints the key and subkey
// fingerprints.
func (pgp *GopenPGP) CheckKey(pubKey string) (string, error) {
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
func (pgp *GopenPGP) PrintFingerprints(pubKey string) (string, error) {
pubKeyReader := strings.NewReader(pubKey)
entries, err := openpgp.ReadArmoredKeyRing(pubKeyReader)
if err != nil {

View file

@ -6,12 +6,13 @@ import (
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/rsa"
)
const name = "richard.stallman"
const domain = "protonmail.ch"
const name = "Richard M. Stallman"
const domain = "rms@protonmail.ch"
var passphrase = "I love GNU"
var rsaKey, ecKey, rsaPublicKey, ecPublicKey string
@ -55,7 +56,7 @@ func TestGenerateKeyRings(t *testing.T) {
t.Fatal("Cannot read RSA public key:", err)
}
err = rsaPrivateKeyRing.Unlock([]byte(passphrase))
err = rsaPrivateKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt RSA key:", err)
}
@ -75,40 +76,12 @@ func TestGenerateKeyRings(t *testing.T) {
t.Fatal("Cannot read EC public key:", err)
}
err = ecPrivateKeyRing.Unlock([]byte(passphrase))
err = ecPrivateKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt EC key:", err)
}
}
func TestEncryptDecryptKeys(t *testing.T) {
var pass, _ = base64.StdEncoding.DecodeString("H2CAwzpdexjxXucVYMERDiAc/td8aGPrr6ZhfMnZlLI=")
var testSymmetricKey = &SymmetricKey{
Key: pass,
Algo: constants.AES256,
}
packet, err := rsaPublicKeyRing.EncryptKey(testSymmetricKey)
if err != nil {
t.Fatal("Cannot encrypt keypacket with RSA keyring", err)
}
rsaTestSymmetricKey, err := DecryptAttKey(rsaPrivateKeyRing, packet)
if err != nil {
t.Fatal("Cannot decrypt keypacket with RSA keyring", err)
}
assert.Exactly(t, testSymmetricKey, rsaTestSymmetricKey)
packet, err = ecPublicKeyRing.EncryptKey(testSymmetricKey)
if err != nil {
t.Fatal("Cannot encrypt keypacket with EC keyring", err)
}
ecTestSymmetricKey, err := DecryptAttKey(ecPrivateKeyRing, packet)
if err != nil {
t.Fatal("Cannot decrypt keypacket with EC keyring", err)
}
assert.Exactly(t, testSymmetricKey, ecTestSymmetricKey)
}
func TestUpdatePrivateKeysPassphrase(t *testing.T) {
newPassphrase := "I like GNU"
rsaKey, err = pgp.UpdatePrivateKeyPassphrase(rsaKey, passphrase, newPassphrase)
@ -124,20 +97,20 @@ func TestUpdatePrivateKeysPassphrase(t *testing.T) {
passphrase = newPassphrase
}
func ExampleCheckKeys() {
_, _ = pgp.CheckKey(readTestFile("keyring_publicKey", false))
func ExamplePrintFingerprints() {
_, _ = pgp.PrintFingerprints(readTestFile("keyring_publicKey", false))
// Output:
// SubKey:37e4bcf09b36e34012d10c0247dc67b5cb8267f6
// PrimaryKey:6e8ba229b0cccaf6962f97953eb6259edf21df24
}
func TestIsKeyExpired(t *testing.T) {
rsaRes, err := pgp.IsKeyExpired(rsaPublicKey)
func TestIsArmoredKeyExpired(t *testing.T) {
rsaRes, err := pgp.IsArmoredKeyExpired(rsaPublicKey)
if err != nil {
t.Fatal("Error in checking expiration of RSA key:", err)
}
ecRes, err := pgp.IsKeyExpired(ecPublicKey)
ecRes, err := pgp.IsArmoredKeyExpired(ecPublicKey)
if err != nil {
t.Fatal("Error in checking expiration of EC key:", err)
}
@ -147,11 +120,43 @@ func TestIsKeyExpired(t *testing.T) {
pgp.UpdateTime(1557754627) // 2019-05-13T13:37:07+00:00
expRes, expErr := pgp.IsKeyExpired(readTestFile("key_expiredKey", false))
futureRes, futureErr := pgp.IsKeyExpired(readTestFile("key_futureKey", false))
expRes, expErr := pgp.IsArmoredKeyExpired(readTestFile("key_expiredKey", false))
futureRes, futureErr := pgp.IsArmoredKeyExpired(readTestFile("key_futureKey", false))
assert.Exactly(t, true, expRes)
assert.Exactly(t, true, futureRes)
assert.EqualError(t, expErr, "keys expired")
assert.EqualError(t, futureErr, "keys expired")
}
func TestGenerateKeyWithPrimes(t *testing.T) {
prime1, _ := base64.StdEncoding.DecodeString(
"/thF8zjjk6fFx/y9NId35NFx8JTA7jvHEl+gI0dp9dIl9trmeZb+ESZ8f7bNXUmTI8j271kyenlrVJiqwqk80Q==")
prime2, _ := base64.StdEncoding.DecodeString(
"0HyyG/TShsw7yObD+DDP9Ze39ye1Redljx+KOZ3iNDmuuwwI1/5y44rD/ezAsE7A188NsotMDTSy5xtfHmu0xQ==")
prime3, _ := base64.StdEncoding.DecodeString(
"3OyJpAdnQXNjPNzI1u3BWDmPrzWw099E0UfJj5oJJILSbsAg/DDrmrdrIZDt7f24d06HCnTErCNWjvFJ3Kdq4w==")
prime4, _ := base64.StdEncoding.DecodeString(
"58UEDXTX29Q9JqvuE3Tn+Qj275CXBnJbA8IVM4d05cPYAZ6H43bPN01pbJqJTJw/cuFxs+8C+HNw3/MGQOExqw==")
staticRsaKey, err := pgp.GenerateRSAKeyWithPrimes(name, domain, passphrase, 1024, prime1, prime2, prime3, prime4)
if err != nil {
t.Fatal("Cannot generate RSA key:", err)
}
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP PRIVATE KEY BLOCK-----.*-----END PGP PRIVATE KEY BLOCK-----$")
assert.Regexp(t, rTest, staticRsaKey)
staticRsaKeyRing, err := ReadArmoredKeyRing(strings.NewReader(staticRsaKey))
if err != nil {
t.Fatal("Cannot read RSA key:", err)
}
err = staticRsaKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt RSA key:", err)
}
pk := staticRsaKeyRing.GetEntities()[0].PrivateKey.PrivateKey.(*rsa.PrivateKey)
assert.Exactly(t, prime1, pk.Primes[1].Bytes())
assert.Exactly(t, prime2, pk.Primes[0].Bytes())
}

View file

@ -8,31 +8,35 @@ import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"regexp"
"strings"
"time"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
pgperrors "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
xrsa "golang.org/x/crypto/rsa"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/models"
)
// KeyRing contains multiple private and public keys.
type KeyRing struct {
// PGP entities in this keyring.
entities openpgp.EntityList
// FirstKeyID as obtained from API to match salt
FirstKeyID string
}
// A keypair contains a private key and a public key.
type pgpKeyObject struct {
ID string
Version int
Flags int
Fingerprint string
PublicKey string `json:",omitempty"`
PrivateKey string
Primary int
ID string
Version int
Flags int
PrivateKey string
Primary int
Token *string `json:",omitempty"`
Signature *string `json:",omitempty"`
}
// PrivateKeyReader
@ -46,87 +50,22 @@ type Identity struct {
Email string
}
// Signature is be used to check a signature. Because the signature is checked
// when the reader is consumed, Signature must only be used after EOF has been
// seen. A signature is only valid if s.Err() returns nil, otherwise the
// sender's identity cannot be trusted.
type Signature struct {
md *openpgp.MessageDetails
}
// SignedString wraps string with a Signature
type SignedString struct {
String string
Signed *Signature
}
var errKeyringNotUnlocked = errors.New("gopenpgp: cannot sign message, key ring is not unlocked")
// Err returns a non-nil error if the signature is invalid.
func (s *Signature) Err() error {
return s.md.SignatureError
}
// KeyRing returns the key ring that was used to produce the signature, if
// available.
func (s *Signature) KeyRing() *KeyRing {
if s.md.SignedBy == nil {
return nil
}
return &KeyRing{
entities: openpgp.EntityList{s.md.SignedBy.Entity},
}
}
// IsBy returns true if the signature has been created by kr's owner.
func (s *Signature) IsBy(kr *KeyRing) bool {
// Use fingerprint if possible
if s.md.SignedBy != nil {
for _, e := range kr.entities {
if e.PrimaryKey.Fingerprint == s.md.SignedBy.PublicKey.Fingerprint {
return true
}
}
return false
}
for _, e := range kr.entities {
if e.PrimaryKey.KeyId == s.md.SignedByKeyId {
return true
}
}
return false
}
// KeyRing contains multiple private and public keys.
type KeyRing struct {
// PGP entities in this keyring.
entities openpgp.EntityList
// FirstKeyID as obtained from API to match salt
FirstKeyID string
}
// GetEntities returns openpgp entities contained in this KeyRing.
func (kr *KeyRing) GetEntities() openpgp.EntityList {
return kr.entities
func (keyRing *KeyRing) GetEntities() openpgp.EntityList {
return keyRing.entities
}
// GetSigningEntity returns first private unlocked signing entity from keyring.
func (kr *KeyRing) GetSigningEntity(passphrase string) (*openpgp.Entity, error) {
func (keyRing *KeyRing) GetSigningEntity() (*openpgp.Entity, error) {
var signEntity *openpgp.Entity
for _, e := range kr.entities {
for _, e := range keyRing.entities {
// Entity.PrivateKey must be a signing key
if e.PrivateKey != nil {
if e.PrivateKey.Encrypted {
if err := e.PrivateKey.Decrypt([]byte(passphrase)); err != nil {
continue
}
if !e.PrivateKey.Encrypted {
signEntity = e
break
}
signEntity = e
break
}
}
if signEntity == nil {
@ -137,184 +76,15 @@ func (kr *KeyRing) GetSigningEntity(passphrase string) (*openpgp.Entity, error)
return signEntity, nil
}
// Encrypt encrypts data to this keyring's owner. If sign is not nil, it also
// signs data with it. The keyring sign must be unlocked to be able to sign data,
// if not an error will be returned.
func (kr *KeyRing) Encrypt(w io.Writer, sign *KeyRing, filename string, canonicalizeText bool) (io.WriteCloser, error) {
// The API returns keys sorted by descending priority
// Only encrypt to the first one
var encryptEntities []*openpgp.Entity
for _, e := range kr.entities {
encryptEntities = append(encryptEntities, e)
break
}
var signEntity *openpgp.Entity
if sign != nil {
// To sign a message, the private key must be decrypted
for _, e := range sign.entities {
// Entity.PrivateKey must be a signing key
if e.PrivateKey != nil && !e.PrivateKey.Encrypted {
signEntity = e
break
}
}
if signEntity == nil {
return nil, errKeyringNotUnlocked
}
}
return EncryptCore(
w,
encryptEntities,
signEntity,
filename,
canonicalizeText,
func() time.Time { return GetGopenPGP().GetTime() })
}
// EncryptCore is lower-level encryption method used by KeyRing.Encrypt.
func EncryptCore(w io.Writer, encryptEntities []*openpgp.Entity, signEntity *openpgp.Entity, filename string,
canonicalizeText bool, timeGenerator func() time.Time) (io.WriteCloser, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: timeGenerator}
hints := &openpgp.FileHints{
IsBinary: !canonicalizeText,
FileName: filename,
}
if canonicalizeText {
return openpgp.EncryptText(w, encryptEntities, signEntity, hints, config)
}
return openpgp.Encrypt(w, encryptEntities, signEntity, hints, config)
}
// An io.WriteCloser that both encrypts and armors data.
type armorEncryptWriter struct {
aw io.WriteCloser // Armored writer
ew io.WriteCloser // Encrypted writer
}
// Write encrypted data
func (w *armorEncryptWriter) Write(b []byte) (n int, err error) {
return w.ew.Write(b)
}
// Close armor and encryption io.WriteClose
func (w *armorEncryptWriter) Close() (err error) {
if err = w.ew.Close(); err != nil {
return
}
err = w.aw.Close()
return
}
// EncryptArmored encrypts and armors data to the keyring's owner.
// Wrapper of Encrypt.
func (kr *KeyRing) EncryptArmored(w io.Writer, sign *KeyRing) (wc io.WriteCloser, err error) {
aw, err := armorUtils.ArmorWithTypeBuffered(w, constants.PGPMessageHeader)
if err != nil {
return
}
ew, err := kr.Encrypt(aw, sign, "", false)
if err != nil {
aw.Close()
return
}
wc = &armorEncryptWriter{aw: aw, ew: ew}
return
}
// EncryptMessage encrypts and armors a string to the keyring's owner.
// Wrapper of Encrypt.
func (kr *KeyRing) EncryptMessage(s string, sign *KeyRing) (encrypted string, err error) {
var b bytes.Buffer
w, err := kr.EncryptArmored(&b, sign)
if err != nil {
return
}
if _, err = w.Write([]byte(s)); err != nil {
return
}
if err = w.Close(); err != nil {
return
}
encrypted = b.String()
return
}
// EncryptSymmetric data using generated symmetric key encrypted with this KeyRing.
// Wrapper of Encrypt.
func (kr *KeyRing) EncryptSymmetric(textToEncrypt string, canonicalizeText bool) (outSplit *models.EncryptedSplit,
err error) {
var encryptedWriter io.WriteCloser
buffer := &bytes.Buffer{}
if encryptedWriter, err = kr.Encrypt(buffer, kr, "msg.txt", canonicalizeText); err != nil {
return
}
if _, err = io.Copy(encryptedWriter, bytes.NewBufferString(textToEncrypt)); err != nil {
return
}
encryptedWriter.Close()
if outSplit, err = SeparateKeyAndData(kr, buffer, len(textToEncrypt), -1); err != nil {
return
}
return
}
// DecryptMessage decrypts an armored string sent to the keypair's owner.
// If error is errors.ErrSignatureExpired (from golang.org/x/crypto/openpgp/errors),
// contents are still provided if library clients wish to process this message further.
func (kr *KeyRing) DecryptMessage(encrypted string) (SignedString, error) {
r, signed, err := kr.DecryptArmored(strings.NewReader(encrypted))
if err != nil && err != pgperrors.ErrSignatureExpired {
return SignedString{String: encrypted, Signed: nil}, err
}
b, err := ioutil.ReadAll(r)
if err != nil && err != pgperrors.ErrSignatureExpired {
return SignedString{String: encrypted, Signed: nil}, err
}
s := string(b)
return SignedString{String: s, Signed: signed}, nil
}
// DecryptMessageIfNeeded data if has armored PGP message format, if not return original data.
// If error is errors.ErrSignatureExpired (from golang.org/x/crypto/openpgp/errors),
// contents are still provided if library clients wish to process this message further.
func (kr *KeyRing) DecryptMessageIfNeeded(data string) (decrypted string, err error) {
if re := regexp.MustCompile("^-----BEGIN " + constants.PGPMessageHeader + "-----(?s:.+)-----END " +
constants.PGPMessageHeader + "-----"); re.MatchString(data) {
var signed SignedString
signed, err = kr.DecryptMessage(data)
decrypted = signed.String
} else {
decrypted = data
}
return
}
// Unlock tries to unlock as many keys as possible with the following password. Note
// that keyrings can contain keys locked with different passwords, and thus
// err == nil does not mean that all keys have been successfully decrypted.
// If err != nil, the password is wrong for every key, and err is the last error
// encountered.
func (kr *KeyRing) Unlock(passphrase []byte) error {
func (keyRing *KeyRing) Unlock(passphrase []byte) error {
// Build a list of keys to decrypt
var keys []*packet.PrivateKey
for _, e := range kr.entities {
for _, e := range keyRing.entities {
// Entity.PrivateKey must be a signing key
if e.PrivateKey != nil {
keys = append(keys, e.PrivateKey)
@ -352,48 +122,19 @@ func (kr *KeyRing) Unlock(passphrase []byte) error {
return nil
}
// Decrypt decrypts a message sent to the keypair's owner. If the message is not
// signed, signed will be nil.
// If error is errors.ErrSignatureExpired (from golang.org/x/crypto/openpgp/errors),
// contents are still provided if library clients wish to process this message further.
func (kr *KeyRing) Decrypt(r io.Reader) (decrypted io.Reader, signed *Signature, err error) {
md, err := openpgp.ReadMessage(r, kr.entities, nil, nil)
if err != nil && err != pgperrors.ErrSignatureExpired {
return
}
decrypted = md.UnverifiedBody
if md.IsSigned {
signed = &Signature{md}
}
return
}
// DecryptArmored decrypts an armored message sent to the keypair's owner.
// If error is errors.ErrSignatureExpired (from golang.org/x/crypto/openpgp/errors),
// contents are still provided if library clients wish to process this message further.
func (kr *KeyRing) DecryptArmored(r io.Reader) (decrypted io.Reader, signed *Signature, err error) {
block, err := armor.Decode(r)
if err != nil && err != pgperrors.ErrSignatureExpired {
return
}
if block.Type != constants.PGPMessageHeader {
err = errors.New("gopenpgp: not an armored PGP message")
return
}
return kr.Decrypt(block.Body)
// UnlockWithPassphrase is a wrapper for Unlock that uses strings
func (keyRing *KeyRing) UnlockWithPassphrase(passphrase string) error {
return keyRing.Unlock([]byte(passphrase))
}
// WriteArmoredPublicKey outputs armored public keys from the keyring to w.
func (kr *KeyRing) WriteArmoredPublicKey(w io.Writer) (err error) {
func (keyRing *KeyRing) WriteArmoredPublicKey(w io.Writer) (err error) {
aw, err := armor.Encode(w, openpgp.PublicKeyType, nil)
if err != nil {
return
}
for _, e := range kr.entities {
for _, e := range keyRing.entities {
if err = e.Serialize(aw); err != nil {
aw.Close()
return
@ -405,9 +146,9 @@ func (kr *KeyRing) WriteArmoredPublicKey(w io.Writer) (err error) {
}
// GetArmoredPublicKey returns the armored public keys from this keyring.
func (kr *KeyRing) GetArmoredPublicKey() (s string, err error) {
func (keyRing *KeyRing) GetArmoredPublicKey() (s string, err error) {
b := &bytes.Buffer{}
if err = kr.WriteArmoredPublicKey(b); err != nil {
if err = keyRing.WriteArmoredPublicKey(b); err != nil {
return
}
@ -416,8 +157,8 @@ func (kr *KeyRing) GetArmoredPublicKey() (s string, err error) {
}
// WritePublicKey outputs unarmored public keys from the keyring to w.
func (kr *KeyRing) WritePublicKey(w io.Writer) (err error) {
for _, e := range kr.entities {
func (keyRing *KeyRing) WritePublicKey(w io.Writer) (err error) {
for _, e := range keyRing.entities {
if err = e.Serialize(w); err != nil {
return
}
@ -427,9 +168,9 @@ func (kr *KeyRing) WritePublicKey(w io.Writer) (err error) {
}
// GetPublicKey returns the unarmored public keys from this keyring.
func (kr *KeyRing) GetPublicKey() (b []byte, err error) {
func (keyRing *KeyRing) GetPublicKey() (b []byte, err error) {
var outBuf bytes.Buffer
if err = kr.WritePublicKey(&outBuf); err != nil {
if err = keyRing.WritePublicKey(&outBuf); err != nil {
return
}
@ -438,8 +179,8 @@ func (kr *KeyRing) GetPublicKey() (b []byte, err error) {
}
// GetFingerprint gets the fingerprint from the keyring.
func (kr *KeyRing) GetFingerprint() (string, error) {
for _, entity := range kr.entities {
func (keyRing *KeyRing) GetFingerprint() (string, error) {
for _, entity := range keyRing.entities {
fp := entity.PrimaryKey.Fingerprint
return hex.EncodeToString(fp[:]), nil
}
@ -447,10 +188,10 @@ func (kr *KeyRing) GetFingerprint() (string, error) {
}
// CheckPassphrase checks if private key passphrase is correct for every sub key.
func (kr *KeyRing) CheckPassphrase(passphrase string) bool {
func (keyRing *KeyRing) CheckPassphrase(passphrase string) bool {
var keys []*packet.PrivateKey
for _, entity := range kr.entities {
for _, entity := range keyRing.entities {
keys = append(keys, entity.PrivateKey)
}
var decryptError error
@ -468,7 +209,7 @@ func (kr *KeyRing) CheckPassphrase(passphrase string) bool {
}
// readFrom reads unarmored and armored keys from r and adds them to the keyring.
func (kr *KeyRing) readFrom(r io.Reader, armored bool) error {
func (keyRing *KeyRing) readFrom(r io.Reader, armored bool) error {
var err error
var entities openpgp.EntityList
if armored {
@ -515,27 +256,27 @@ func (kr *KeyRing) readFrom(r io.Reader, armored bool) error {
return errors.New("gopenpgp: key ring doesn't contain any key")
}
kr.entities = append(kr.entities, entities...)
keyRing.entities = append(keyRing.entities, entities...)
return nil
}
// BuildKeyRing reads keyring from binary data
func (pgp *GopenPGP) BuildKeyRing(binKeys []byte) (kr *KeyRing, err error) {
kr = &KeyRing{}
func (pgp *GopenPGP) BuildKeyRing(binKeys []byte) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
entriesReader := bytes.NewReader(binKeys)
err = kr.readFrom(entriesReader, false)
err = keyRing.readFrom(entriesReader, false)
return
}
// BuildKeyRingNoError does not return error on fail
func (pgp *GopenPGP) BuildKeyRingNoError(binKeys []byte) (kr *KeyRing) {
kr, _ = pgp.BuildKeyRing(binKeys)
func (pgp *GopenPGP) BuildKeyRingNoError(binKeys []byte) (keyRing *KeyRing) {
keyRing, _ = pgp.BuildKeyRing(binKeys)
return
}
// BuildKeyRingArmored reads armored string and returns keyring
func (pgp *GopenPGP) BuildKeyRingArmored(key string) (kr *KeyRing, err error) {
func (pgp *GopenPGP) BuildKeyRingArmored(key string) (keyRing *KeyRing, err error) {
keyRaw, err := armorUtils.Unarmor(key)
if err != nil {
return nil, err
@ -545,36 +286,98 @@ func (pgp *GopenPGP) BuildKeyRingArmored(key string) (kr *KeyRing, err error) {
return &KeyRing{entities: keyEntries}, err
}
// UnmarshalJSON implements encoding/json.Unmarshaler.
func (kr *KeyRing) UnmarshalJSON(b []byte) (err error) {
kr.entities = nil
keyObjs := []pgpKeyObject{}
if err = json.Unmarshal(b, &keyObjs); err != nil {
return
// ReadFromJSON reads multiple keys from a json array and fills the keyring
func (keyRing *KeyRing) ReadFromJSON(jsonData []byte) (err error) {
keyObjs, err := unmarshalJSON(jsonData)
if err != nil {
return err
}
if len(keyObjs) == 0 {
return
return keyRing.newKeyRingFromPGPKeyObject(keyObjs)
}
// UnlockJSONKeyRing reads keys from a JSON array, creates a newKeyRing,
// then tries to unlock them with the provided keyRing using the token in the structure.
// If the token is not available it will fall back to just reading the keys, and leave them locked.
func (keyRing *KeyRing) UnlockJSONKeyRing(jsonData []byte) (newKeyRing *KeyRing, err error) {
keyObjs, err := unmarshalJSON(jsonData)
newKeyRing = &KeyRing{}
err = newKeyRing.newKeyRingFromPGPKeyObject(keyObjs)
if err != nil {
return nil, err
}
for _, ko := range keyObjs {
if ko.Token == nil || ko.Signature == nil {
continue
}
message, err := NewPGPMessageFromArmored(*ko.Token)
if err != nil {
return nil, err
}
signature, err := NewPGPSignatureFromArmored(*ko.Signature)
if err != nil {
return nil, err
}
token, _, err := keyRing.Decrypt(message, nil, 0)
if err != nil {
return nil, err
}
ver, err := keyRing.VerifyDetached(token, signature, 0)
if err != nil {
return nil, err
}
if !ver.IsValid() {
return nil, errors.New("gopenpgp: unable to verify token")
}
err = newKeyRing.Unlock(token.GetBinary())
if err != nil {
return nil, errors.New("gopenpgp: wrong token")
}
}
return newKeyRing, nil
}
// newKeyRingFromPGPKeyObject fills a KeyRing given an array of pgpKeyObject
func (keyRing *KeyRing) newKeyRingFromPGPKeyObject(keyObjs []pgpKeyObject) error {
keyRing.entities = nil
for i, ko := range keyObjs {
if i == 0 {
kr.FirstKeyID = ko.ID
keyRing.FirstKeyID = ko.ID
}
err = kr.readFrom(ko.PrivateKeyReader(), true)
err := keyRing.readFrom(ko.PrivateKeyReader(), true)
if err != nil {
return err
}
}
return nil
}
// unmarshalJSON implements encoding/json.Unmarshaler.
func unmarshalJSON(jsonData []byte) ([]pgpKeyObject, error) {
keyObjs := []pgpKeyObject{}
if err := json.Unmarshal(jsonData, &keyObjs); err != nil {
return nil, err
}
if len(keyObjs) == 0 {
return nil, errors.New("gopenpgp: no key found")
}
return keyObjs, nil
}
// Identities returns the list of identities associated with this key ring.
func (kr *KeyRing) Identities() []*Identity {
func (keyRing *KeyRing) Identities() []*Identity {
var identities []*Identity
for _, e := range kr.entities {
for _, e := range keyRing.entities {
for _, id := range e.Identities {
identities = append(identities, &Identity{
Name: id.UserId.Name,
@ -586,25 +389,25 @@ func (kr *KeyRing) Identities() []*Identity {
}
// KeyIds returns array of IDs of keys in this KeyRing.
func (kr *KeyRing) KeyIds() []uint64 {
func (keyRing *KeyRing) KeyIds() []uint64 {
var res []uint64
for _, e := range kr.entities {
for _, e := range keyRing.entities {
res = append(res, e.PrimaryKey.KeyId)
}
return res
}
// ReadArmoredKeyRing reads an armored data into keyring.
func ReadArmoredKeyRing(r io.Reader) (kr *KeyRing, err error) {
kr = &KeyRing{}
err = kr.readFrom(r, true)
func ReadArmoredKeyRing(r io.Reader) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
err = keyRing.readFrom(r, true)
return
}
// ReadKeyRing reads an binary data into keyring.
func ReadKeyRing(r io.Reader) (kr *KeyRing, err error) {
kr = &KeyRing{}
err = kr.readFrom(r, false)
func ReadKeyRing(r io.Reader) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
err = keyRing.readFrom(r, false)
return
}
@ -643,7 +446,7 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
}
if len(filteredKeys) == 0 && hasExpiredEntity {
return filteredKeys, errors.New("all contacts keys are expired")
return filteredKeys, errors.New("gopenpgp: all contacts keys are expired")
}
return filteredKeys, nil

255
crypto/keyring_message.go Normal file
View file

@ -0,0 +1,255 @@
package crypto
import (
"bytes"
"crypto"
"errors"
"io"
"io/ioutil"
"math"
"time"
"golang.org/x/crypto/openpgp"
pgpErrors "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
)
// Encrypt encrypts a PlainMessage, outputs a PGPMessage.
// If an unlocked private key is also provided it will also sign the message.
// message : The plaintext input as a PlainMessage
// privateKey : (optional) an unlocked private keyring to include signature in the message
func (keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error) {
encrypted, err := asymmetricEncrypt(message.GetBinary(), keyRing, privateKey, true)
if err != nil {
return nil, err
}
return NewPGPMessage(encrypted), nil
}
// Decrypt decrypts encrypted string using pgp keys, returning a PlainMessage
// message : The encrypted input as a PGPMessage
// verifyKey : Public key for signature verification (optional)
// verifyTime : Time at verification (necessary only if verifyKey is not nil)
func (keyRing *KeyRing) Decrypt(
message *PGPMessage, verifyKey *KeyRing, verifyTime int64,
) (*PlainMessage, *Verification, error) {
decrypted, verifyStatus, err := asymmetricDecrypt(message.NewReader(), keyRing, verifyKey, verifyTime)
if err != nil {
return nil, nil, err
}
return NewPlainMessage(decrypted), newVerification(verifyStatus), nil
}
// SignDetached generates and returns a PGPSignature for a given PlainMessage
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
signEntity, err := keyRing.GetSigningEntity()
if err != nil {
return nil, err
}
config := &packet.Config{DefaultHash: crypto.SHA512, Time: pgp.getTimeGenerator()}
var outBuf bytes.Buffer
//sign bin
if err := openpgp.DetachSign(&outBuf, signEntity, message.NewReader(), config); err != nil {
return nil, err
}
return NewPGPSignature(outBuf.Bytes()), nil
}
// VerifyDetached verifies a PlainMessage with embedded a PGPSignature
// and returns a Verification with the filled Verified field.
func (keyRing *KeyRing) VerifyDetached(
message *PlainMessage, signature *PGPSignature, verifyTime int64,
) (*Verification, error) {
var err error
verifyVal, err := verifySignature(
keyRing.GetEntities(),
message.NewReader(),
signature.GetBinary(),
verifyTime,
)
return newVerification(verifyVal), err
}
// ------ INTERNAL FUNCTIONS -------
// Core for encryption+signature functions
func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isBinary bool) ([]byte, error) {
var outBuf bytes.Buffer
var encryptWriter io.WriteCloser
var signEntity *openpgp.Entity
var err error
if privateKey != nil && len(privateKey.entities) > 0 {
var err error
signEntity, err = privateKey.GetSigningEntity()
if err != nil {
return nil, err
}
}
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pgp.getTimeGenerator()}
hints := &openpgp.FileHints{
IsBinary: isBinary,
FileName: "",
}
if isBinary {
encryptWriter, err = openpgp.Encrypt(&outBuf, publicKey.entities, signEntity, hints, config)
} else {
encryptWriter, err = openpgp.EncryptText(&outBuf, publicKey.entities, signEntity, hints, config)
}
if err != nil {
return nil, err
}
_, err = encryptWriter.Write(data)
encryptWriter.Close()
if err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
// Core for decryption+verification functions
func asymmetricDecrypt(
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
) (plaintext []byte, verified int, err error) {
privKeyEntries := privateKey.GetEntities()
var additionalEntries openpgp.EntityList
if verifyKey != nil {
additionalEntries = verifyKey.GetEntities()
}
if additionalEntries != nil {
privKeyEntries = append(privKeyEntries, additionalEntries...)
}
config := &packet.Config{Time: pgp.getTimeGenerator()}
messageDetails, err := openpgp.ReadMessage(encryptedIO, privKeyEntries, nil, config)
if err != nil {
return nil, constants.SIGNATURE_NOT_SIGNED, err
}
if verifyKey != nil {
processSignatureExpiration(messageDetails, verifyTime)
}
body, err := ioutil.ReadAll(messageDetails.UnverifiedBody)
if err != nil {
return nil, constants.SIGNATURE_NOT_SIGNED, err
}
if verifyKey != nil {
verifyStatus, verifyError := verifyDetailsSignature(messageDetails, verifyKey)
if verifyStatus == constants.SIGNATURE_FAILED {
return nil, verifyStatus, errors.New(verifyError)
}
return body, verifyStatus, nil
}
return body, constants.SIGNATURE_NOT_SIGNED, nil
}
// processSignatureExpiration handles signature time verification manually, so we can add a margin to the
// creationTime check.
func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) {
if md.SignatureError == pgpErrors.ErrSignatureExpired {
if verifyTime > 0 {
created := md.Signature.CreationTime.Unix()
expires := int64(math.MaxInt64)
if md.Signature.SigLifetimeSecs != nil {
expires = int64(*md.Signature.SigLifetimeSecs) + created
}
if created-internal.CreationTimeOffset <= verifyTime && verifyTime <= expires {
md.SignatureError = nil
}
} else {
// verifyTime = 0: time check disabled, everything is okay
md.SignatureError = nil
}
}
}
// Verify signature from message details
func verifyDetailsSignature(md *openpgp.MessageDetails, verifierKey *KeyRing) (int, string) {
if md.IsSigned {
if md.SignedBy != nil {
if len(verifierKey.entities) > 0 {
matches := verifierKey.entities.KeysById(md.SignedByKeyId)
if len(matches) > 0 {
if md.SignatureError == nil {
return constants.SIGNATURE_OK, ""
}
return constants.SIGNATURE_FAILED, md.SignatureError.Error()
}
} else {
return constants.SIGNATURE_NO_VERIFIER, ""
}
} else {
return constants.SIGNATURE_NO_VERIFIER, ""
}
}
return constants.SIGNATURE_NOT_SIGNED, ""
}
// verifySignature verifies if a signature is valid with the entity list
func verifySignature(
pubKeyEntries openpgp.EntityList, origText io.Reader, signature []byte, verifyTime int64) (int, error) {
config := &packet.Config{}
if verifyTime == 0 {
config.Time = func() time.Time {
return time.Unix(0, 0)
}
} else {
config.Time = func() time.Time {
return time.Unix(verifyTime+internal.CreationTimeOffset, 0)
}
}
signatureReader := bytes.NewReader(signature)
signer, err := openpgp.CheckDetachedSignature(pubKeyEntries, origText, signatureReader, config)
if err == pgpErrors.ErrSignatureExpired && signer != nil {
if verifyTime > 0 { // if verifyTime = 0: time check disabled, everything is okay
// Maybe the creation time offset pushed it over the edge
// Retry with the actual verification time
config.Time = func() time.Time {
return time.Unix(verifyTime, 0)
}
_, err = signatureReader.Seek(0, io.SeekStart)
if err != nil {
return constants.SIGNATURE_FAILED, err
}
signer, err = openpgp.CheckDetachedSignature(pubKeyEntries, origText, signatureReader, config)
if err != nil {
return constants.SIGNATURE_FAILED, err
}
}
}
if signer == nil {
return constants.SIGNATURE_FAILED, errors.New("gopenpgp: signer is empty")
}
// if signer.PrimaryKey.KeyId != signed.PrimaryKey.KeyId {
// // t.Errorf("wrong signer got:%x want:%x", signer.PrimaryKey.KeyId, 0)
// return false, errors.New("signer is nil")
// }
return constants.SIGNATURE_OK, nil
}

View file

@ -19,14 +19,17 @@ var testSymmetricKey = &SymmetricKey{
Algo: constants.AES256,
}
var testWrongSymmetricKey = &SymmetricKey{
Key: []byte("WrongPass"),
Algo: constants.AES256,
}
// Corresponding key in testdata/keyring_privateKey
const testMailboxPassword = "apple"
// Corresponding key in testdata/keyring_privateKeyLegacy
// const testMailboxPasswordLegacy = "123"
const testToken = "d79ca194a22810a5363eeddfdef7dfbc327c6229"
var (
testPrivateKeyRing *KeyRing
testPublicKeyRing *KeyRing
@ -50,43 +53,12 @@ func init() {
panic(err)
}
err = testPrivateKeyRing.Unlock([]byte(testMailboxPassword))
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
panic(err)
}
}
func TestKeyRing_Decrypt(t *testing.T) {
decString, err := testPrivateKeyRing.DecryptMessageIfNeeded(readTestFile("keyring_token", false))
if err != nil {
t.Fatal("Cannot decrypt token:", err)
}
assert.Exactly(t, testToken, decString)
}
func TestKeyRing_Encrypt(t *testing.T) {
encrypted, err := testPublicKeyRing.EncryptMessage(testToken, testPrivateKeyRing)
if err != nil {
t.Fatal("Cannot encrypt token:", err)
}
// We can't just check if encrypted == testEncryptedToken
// Decrypt instead
ss, err := testPrivateKeyRing.DecryptMessage(encrypted)
if err != nil {
t.Fatal("Cannot decrypt token:", err)
}
assert.Exactly(t, testToken, ss.String)
signatureKeyRing := ss.Signed.KeyRing()
assert.Exactly(t, testPrivateKeyRing, signatureKeyRing)
isby := ss.Signed.IsBy(testPublicKeyRing)
assert.Exactly(t, true, isby)
}
func TestKeyRing_ArmoredPublicKeyString(t *testing.T) {
s, err := testPrivateKeyRing.GetArmoredPublicKey()
if err != nil {
@ -134,10 +106,9 @@ func TestIdentities(t *testing.T) {
assert.Exactly(t, identities[0], testIdentity)
}
func TestFilterExpiredKeys(t *testing.T) {
expiredKey, _ := ReadArmoredKeyRing(strings.NewReader(readTestFile("key_expiredKey", false)))
keys := []*KeyRing {testPrivateKeyRing, expiredKey}
keys := []*KeyRing{testPrivateKeyRing, expiredKey}
unexpired, err := FilterExpiredKeys(keys)
if err != nil {
@ -147,3 +118,69 @@ func TestFilterExpiredKeys(t *testing.T) {
assert.Len(t, unexpired, 1)
assert.Exactly(t, unexpired[0], testPrivateKeyRing)
}
func TestGetPublicKey(t *testing.T) {
publicKey, err := testPrivateKeyRing.GetPublicKey()
if err != nil {
t.Fatal("Expected no error while obtaining public key, got:", err)
}
publicKeyRing, err := pgp.BuildKeyRing(publicKey)
if err != nil {
t.Fatal("Expected no error while creating public key ring, got:", err)
}
privateFingerprint, err := testPrivateKeyRing.GetFingerprint()
if err != nil {
t.Fatal("Expected no error while extracting private fingerprint, got:", err)
}
publicFingerprint, err := publicKeyRing.GetFingerprint()
if err != nil {
t.Fatal("Expected no error while extracting public fingerprint, got:", err)
}
assert.Exactly(t, privateFingerprint, publicFingerprint)
}
func TestKeyIds(t *testing.T) {
keyIDs := testPrivateKeyRing.KeyIds()
var assertKeyIDs = []uint64{4518840640391470884}
assert.Exactly(t, assertKeyIDs, keyIDs)
}
func TestReadFromJson(t *testing.T) {
decodedKeyRing := &KeyRing{}
err = decodedKeyRing.ReadFromJSON([]byte(readTestFile("keyring_jsonKeys", false)))
if err != nil {
t.Fatal("Expected no error while reading JSON, got:", err)
}
fingerprint, err := decodedKeyRing.GetFingerprint()
if err != nil {
t.Fatal("Expected no error while extracting fingerprint, got:", err)
}
assert.Exactly(t, "91eacacca6837890efa7000470e569d5c182bef6", fingerprint)
}
func TestUnlockJson(t *testing.T) {
userKeyRing, err := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_userKey", false)))
if err != nil {
t.Fatal("Expected no error while creating keyring, got:", err)
}
err = userKeyRing.UnlockWithPassphrase("testpassphrase")
if err != nil {
t.Fatal("Expected no error while creating keyring, got:", err)
}
addressKeyRing, err := userKeyRing.UnlockJSONKeyRing([]byte(readTestFile("keyring_newJSONKeys", false)))
if err != nil {
t.Fatal("Expected no error while reading and decrypting JSON, got:", err)
}
for _, e := range addressKeyRing.entities {
assert.Exactly(t, false, e.PrivateKey.Encrypted)
}
}

View file

@ -2,271 +2,326 @@ package crypto
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"time"
"regexp"
"runtime"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
pgpErrors "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/ProtonMail/gopenpgp/models"
"golang.org/x/crypto/openpgp/packet"
)
// DecryptMessageStringKey decrypts 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 (pgp *GopenPGP) DecryptMessageStringKey(
encryptedText, privateKey, passphrase string,
) (string, error) {
privKeyRaw, err := armorUtils.Unarmor(privateKey)
if err != nil {
return "", err
}
privKeyReader := bytes.NewReader(privKeyRaw)
privKeyEntries, err := openpgp.ReadKeyRing(privKeyReader)
if err != nil {
return "", err
}
// ---- MODELS -----
return pgp.DecryptMessage(encryptedText, &KeyRing{entities: privKeyEntries}, passphrase)
// PlainMessage stores an unencrypted message.
type PlainMessage struct {
// The content of the message
Data []byte
// if the content is text or binary
TextType bool
}
// DecryptMessage decrypts encrypted string using keyring
// encryptedText : string armored encrypted
// privateKey : keyring with private key to decrypt message, could be multiple keys
// passphrase : match with private key to decrypt message
func (pgp *GopenPGP) DecryptMessage(encryptedText string, privateKey *KeyRing, passphrase string) (string, error) {
md, err := decryptCore(encryptedText, nil, privateKey, passphrase, pgp.getTimeGenerator())
if err != nil {
return "", err
}
decrypted := md.UnverifiedBody
b, err := ioutil.ReadAll(decrypted)
if err != nil {
return "", err
}
return string(b), nil
// Verification for a PlainMessage
type Verification struct {
// If the decoded message was correctly signed. See constants.SIGNATURE* for all values.
Verified int
}
func decryptCore(
encryptedText string, additionalEntries openpgp.EntityList,
privKey *KeyRing, passphrase string,
timeFunc func() time.Time,
) (*openpgp.MessageDetails, error) {
rawPwd := []byte(passphrase)
if err := privKey.Unlock(rawPwd); err != nil {
err = fmt.Errorf("gopenpgp: cannot decrypt passphrase: %v", err)
return nil, err
}
privKeyEntries := privKey.entities
if additionalEntries != nil {
privKeyEntries = append(privKeyEntries, additionalEntries...)
}
encryptedio, err := internal.Unarmor(encryptedText)
if err != nil {
return nil, err
}
config := &packet.Config{Time: timeFunc}
md, err := openpgp.ReadMessage(encryptedio.Body, privKeyEntries, nil, config)
return md, err
// PGPMessage stores a PGP-encrypted message.
type PGPMessage struct {
// The content of the message
Data []byte
}
// DecryptMessageVerify decrypts message and verify the signature
// encryptedText: string armored encrypted
// verifierKey []byte: unarmored verifier keys
// privateKeyRing []byte: unarmored private key to decrypt. could be multiple
// passphrase: match with private key to decrypt message
func (pgp *GopenPGP) DecryptMessageVerify(
encryptedText string, verifierKey, privateKeyRing *KeyRing,
passphrase string, verifyTime int64,
) (*models.DecryptSignedVerify, error) {
out := &models.DecryptSignedVerify{}
out.Verify = failed
// PGPSignature stores a PGP-encoded detached signature.
type PGPSignature struct {
// The content of the signature
Data []byte
}
var verifierEntries openpgp.EntityList
if len(verifierKey.entities) == 0 {
out.Verify = noVerifier
// PGPSplitMessage contains a separate session key packet and symmetrically
// encrypted data packet.
type PGPSplitMessage struct {
DataPacket []byte
KeyPacket []byte
}
// ---- GENERATORS -----
// NewPlainMessage generates a new binary PlainMessage ready for encryption,
// signature, or verification from the unencrypted binary data.
func NewPlainMessage(data []byte) *PlainMessage {
return &PlainMessage{
Data: data,
TextType: false,
}
}
md, err := decryptCore(
encryptedText,
verifierEntries,
privateKeyRing,
passphrase,
func() time.Time { return time.Unix(0, 0) }) // TODO: I doubt this time is correct
// NewPlainMessageFromString generates a new text PlainMessage,
// ready for encryption, signature, or verification from an unencrypted string.
func NewPlainMessageFromString(text string) *PlainMessage {
return &PlainMessage{
Data: []byte(text),
TextType: true,
}
}
// newVerification returns a new instance of *Verification with the specified value
func newVerification(value int) *Verification {
return &Verification{
Verified: value,
}
}
// NewPGPMessage generates a new PGPMessage from the unarmored binary data.
func NewPGPMessage(data []byte) *PGPMessage {
return &PGPMessage{
Data: data,
}
}
// NewPGPMessageFromArmored generates a new PGPMessage from an armored string ready for decryption.
func NewPGPMessageFromArmored(armored string) (*PGPMessage, error) {
encryptedIO, err := internal.Unarmor(armored)
if err != nil {
return nil, err
}
decrypted := md.UnverifiedBody
b, err := ioutil.ReadAll(decrypted)
message, err := ioutil.ReadAll(encryptedIO.Body)
if err != nil {
return nil, err
}
processSignatureExpiration(md, verifyTime)
return &PGPMessage{
Data: message,
}, nil
}
out.Plaintext = string(b)
if md.IsSigned {
if md.SignedBy != nil {
if len(verifierKey.entities) > 0 {
matches := verifierKey.entities.KeysById(md.SignedByKeyId)
if len(matches) > 0 {
if md.SignatureError == nil {
out.Verify = ok
} else {
out.Message = md.SignatureError.Error()
out.Verify = failed
}
// NewPGPSplitMessage generates a new PGPSplitMessage from the binary unarmored keypacket,
// datapacket, and encryption algorithm.
func NewPGPSplitMessage(keyPacket []byte, dataPacket []byte) *PGPSplitMessage {
return &PGPSplitMessage{
KeyPacket: keyPacket,
DataPacket: dataPacket,
}
}
// NewPGPSplitMessageFromArmored generates a new PGPSplitMessage by splitting an armored message into its
// session key packet and symmetrically encrypted data packet.
func NewPGPSplitMessageFromArmored(encrypted string) (*PGPSplitMessage, error) {
message, err := NewPGPMessageFromArmored(encrypted)
if err != nil {
return nil, err
}
return message.SeparateKeyAndData(len(encrypted), -1)
}
// NewPGPSignature generates a new PGPSignature from the unarmored binary data.
func NewPGPSignature(data []byte) *PGPSignature {
return &PGPSignature{
Data: data,
}
}
// NewPGPSignatureFromArmored generates a new PGPSignature from the armored string ready for verification.
func NewPGPSignatureFromArmored(armored string) (*PGPSignature, error) {
encryptedIO, err := internal.Unarmor(armored)
if err != nil {
return nil, err
}
signature, err := ioutil.ReadAll(encryptedIO.Body)
if err != nil {
return nil, err
}
return &PGPSignature{
Data: signature,
}, nil
}
// ---- MODEL METHODS -----
// GetBinary returns the binary content of the message as a []byte
func (msg *PlainMessage) GetBinary() []byte {
return msg.Data
}
// GetString returns the content of the message as a string
func (msg *PlainMessage) GetString() string {
return string(msg.Data)
}
// GetBase64 returns the base-64 encoded binary content of the message as a string
func (msg *PlainMessage) GetBase64() string {
return base64.StdEncoding.EncodeToString(msg.Data)
}
// GetVerification returns the verification status of a verification,
// to use after the KeyRing.Decrypt* or KeyRing.Verify* functions.
// The int value returned is to compare to constants.SIGNATURE*.
func (ver *Verification) GetVerification() int {
return ver.Verified
}
// IsValid returns true if the message is signed and the signature is valid.
// To use after the KeyRing.Decrypt* or KeyRing.Verify* functions.
func (ver *Verification) IsValid() bool {
return ver.Verified == constants.SIGNATURE_OK
}
// NewReader returns a New io.Reader for the bianry data of the message
func (msg *PlainMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
// IsText returns whether the message is a text message
func (msg *PlainMessage) IsText() bool {
return msg.TextType
}
// IsBinary returns whether the message is a binary message
func (msg *PlainMessage) IsBinary() bool {
return !msg.TextType
}
// GetBinary returns the unarmored binary content of the message as a []byte
func (msg *PGPMessage) GetBinary() []byte {
return msg.Data
}
// NewReader returns a New io.Reader for the unarmored bianry data of the message
func (msg *PGPMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
// GetArmored returns the armored message as a string
func (msg *PGPMessage) GetArmored() (string, error) {
return armor.ArmorWithType(msg.Data, constants.PGPMessageHeader)
}
// GetDataPacket returns the unarmored binary datapacket as a []byte
func (msg *PGPSplitMessage) GetDataPacket() []byte {
return msg.DataPacket
}
// GetKeyPacket returns the unarmored binary keypacket as a []byte
func (msg *PGPSplitMessage) GetKeyPacket() []byte {
return msg.KeyPacket
}
// SeparateKeyAndData returns the first keypacket and the (hopefully unique) dataPacket (not verified)
func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int) (outSplit *PGPSplitMessage, err error) {
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
packets := packet.NewReader(bytes.NewReader(msg.Data))
outSplit = &PGPSplitMessage{}
gcCounter := 0
// Store encrypted key and symmetrically encrypted packet separately
var encryptedKey *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:
if encryptedKey != nil && encryptedKey.Key != nil {
break
}
encryptedKey = p
case *packet.SymmetricallyEncrypted:
// FIXME: add support for multiple keypackets
var b bytes.Buffer
// 2^16 is an estimation of the size difference between input and output, the size difference is most probably
// 16 bytes at a maximum though.
// We need to avoid triggering a grow from the system as this will allocate too much memory causing problems
// in low-memory environments
b.Grow(1<<16 + estimatedLength)
// empty encoded length + start byte
b.Write(make([]byte, 6))
b.WriteByte(byte(1))
actualLength := 1
block := make([]byte, 128)
for {
n, err := p.Contents.Read(block)
if err == io.EOF {
break
}
b.Write(block[:n])
actualLength += n
gcCounter += n
if gcCounter > garbageCollector && garbageCollector > 0 {
runtime.GC()
gcCounter = 0
}
}
// quick encoding
symEncryptedData := b.Bytes()
if actualLength < 192 {
symEncryptedData[4] = byte(210)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[4:]
} else if actualLength < 8384 {
actualLength = actualLength - 192
symEncryptedData[3] = byte(210)
symEncryptedData[4] = 192 + byte(actualLength>>8)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[3:]
} else {
out.Verify = noVerifier
symEncryptedData[0] = byte(210)
symEncryptedData[1] = byte(255)
symEncryptedData[2] = byte(actualLength >> 24)
symEncryptedData[3] = byte(actualLength >> 16)
symEncryptedData[4] = byte(actualLength >> 8)
symEncryptedData[5] = byte(actualLength)
}
} else {
out.Verify = noVerifier
}
} else {
out.Verify = notSigned
}
return out, nil
}
// processSignatureExpiration handles signature time verification manually, so we can add a margin to the
// creationTime check.
func processSignatureExpiration(md *openpgp.MessageDetails, verifyTime int64) {
if md.SignatureError == pgpErrors.ErrSignatureExpired {
if verifyTime > 0 {
created := md.Signature.CreationTime.Unix()
expires := int64(math.MaxInt64)
if md.Signature.SigLifetimeSecs != nil {
expires = int64(*md.Signature.SigLifetimeSecs) + created
}
if created-internal.CreationTimeOffset <= verifyTime && verifyTime <= expires {
md.SignatureError = nil
}
} else {
// verifyTime = 0: time check disabled, everything is okay
md.SignatureError = nil
outSplit.DataPacket = symEncryptedData
}
}
if decryptErr != nil {
return nil, fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
}
if encryptedKey == nil {
return nil, errors.New("gopenpgp: packets don't include an encrypted key packet")
}
var buf bytes.Buffer
if err := encryptedKey.Serialize(&buf); err != nil {
return nil, fmt.Errorf("gopenpgp: cannot serialize encrypted key: %v", err)
}
outSplit.KeyPacket = buf.Bytes()
return outSplit, nil
}
// EncryptMessageWithPassword encrypts a plain text to pgp message with a password
// plainText string: clear text
// output string: armored pgp message
func (pgp *GopenPGP) EncryptMessageWithPassword(plainText string, password string) (string, error) {
var outBuf bytes.Buffer
w, err := armor.Encode(&outBuf, constants.PGPMessageHeader, internal.ArmorHeaders)
if err != nil {
return "", err
}
config := &packet.Config{Time: pgp.getTimeGenerator()}
plaintext, err := openpgp.SymmetricallyEncrypt(w, []byte(password), nil, config)
if err != nil {
return "", err
}
message := []byte(plainText)
_, err = plaintext.Write(message)
if err != nil {
return "", err
}
err = plaintext.Close()
if err != nil {
return "", err
}
w.Close()
return outBuf.String(), nil
// GetBinary returns the unarmored binary content of the signature as a []byte
func (msg *PGPSignature) GetBinary() []byte {
return msg.Data
}
// EncryptMessage encrypts message with unarmored public key, if pass private key and passphrase will also sign
// the message
// publicKey : bytes unarmored public key
// plainText : the input
// privateKey : optional required when you want to sign
// passphrase : optional required when you pass the private key and this passphrase should decrypt the private key
// trim : bool true if need to trim new lines
func (pgp *GopenPGP) EncryptMessage(
plainText string, publicKey, privateKey *KeyRing,
passphrase string, trim bool,
) (string, error) {
if trim {
plainText = internal.TrimNewlines(plainText)
}
var outBuf bytes.Buffer
w, err := armor.Encode(&outBuf, constants.PGPMessageHeader, internal.ArmorHeaders)
if err != nil {
return "", err
}
var signEntity *openpgp.Entity
if len(passphrase) > 0 && len(privateKey.entities) > 0 {
var err error
signEntity, err = privateKey.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
}
ew, err := EncryptCore(w, publicKey.entities, signEntity, "", false, pgp.getTimeGenerator())
if err != nil {
return "", err
}
_, err = ew.Write([]byte(plainText))
ew.Close()
w.Close()
return outBuf.String(), err
// GetArmored returns the armored signature as a string
func (msg *PGPSignature) GetArmored() (string, error) {
return armor.ArmorWithType(msg.Data, constants.PGPSignatureHeader)
}
// DecryptMessageWithPassword decrypts a pgp message with a password
// encrypted string : armored pgp message
// output string : clear text
func (pgp *GopenPGP) DecryptMessageWithPassword(encrypted string, password string) (string, error) {
encryptedio, err := internal.Unarmor(encrypted)
if err != nil {
return "", err
}
// ---- UTILS -----
firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled {
firstTimeCalled = false
return []byte(password), nil
}
return nil, errors.New("password incorrect")
}
config := &packet.Config{Time: pgp.getTimeGenerator()}
md, err := openpgp.ReadMessage(encryptedio.Body, nil, prompt, config)
if err != nil {
return "", err
}
messageBuf := bytes.NewBuffer(nil)
_, err = io.Copy(messageBuf, md.UnverifiedBody)
if err != nil {
return "", err
}
return messageBuf.String(), nil
// IsPGPMessage checks if data if has armored PGP message format.
func (pgp *GopenPGP) IsPGPMessage(data string) bool {
re := regexp.MustCompile("^-----BEGIN " + constants.PGPMessageHeader + "-----(?s:.+)-----END " +
constants.PGPMessageHeader + "-----")
return re.MatchString(data)
}

View file

@ -1,49 +1,104 @@
package crypto
import (
"github.com/stretchr/testify/assert"
"encoding/base64"
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
)
func TestMessageEncryptionWithPassword(t *testing.T) {
var pgp = GopenPGP{}
const password = "my secret password"
func TestTextMessageEncryptionWithSymmetricKey(t *testing.T) {
var message = NewPlainMessageFromString("The secret code is... 1, 2, 3, 4, 5")
// Encrypt data with password
armor, err := pgp.EncryptMessageWithPassword("my message", password)
encrypted, err := testSymmetricKey.Encrypt(message)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
// Decrypt data with wrong password
_, err = pgp.DecryptMessageWithPassword(armor, "wrong password")
_, err = testWrongSymmetricKey.Decrypt(encrypted)
assert.NotNil(t, err)
// Decrypt data with the good password
text, err := pgp.DecryptMessageWithPassword(armor, password)
decrypted, err := testSymmetricKey.Decrypt(encrypted)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, "my message", text)
assert.Exactly(t, message.GetString(), decrypted.GetString())
}
func TestMessageEncryption(t *testing.T) {
var pgp = GopenPGP{}
var (
message = "plain text"
)
func TestBinaryMessageEncryptionWithSymmetricKey(t *testing.T) {
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var message = NewPlainMessage(binData)
testPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
_ = testPrivateKeyRing.Unlock([]byte(testMailboxPassword))
testPublicKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_publicKey", false)))
armor, err := pgp.EncryptMessage(message, testPublicKeyRing, testPrivateKeyRing, testMailboxPassword, false)
// Encrypt data with password
encrypted, err := testSymmetricKey.Encrypt(message)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
plainText, err := pgp.DecryptMessage(armor, testPrivateKeyRing, testMailboxPassword)
// Decrypt data with wrong password
_, err = testWrongSymmetricKey.Decrypt(encrypted)
assert.NotNil(t, err)
// Decrypt data with the good password
decrypted, err := testSymmetricKey.Decrypt(encrypted)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message, plainText)
assert.Exactly(t, message, decrypted)
}
func TestTextMessageEncryption(t *testing.T) {
var message = NewPlainMessageFromString("plain text")
testPublicKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_publicKey", false)))
testPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
// Password defined in keyring_test
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Expected no error unlocking privateKey, got:", err)
}
ciphertext, err := testPublicKeyRing.Encrypt(message, testPrivateKeyRing)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
decrypted, ver, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, pgp.GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetString(), decrypted.GetString())
assert.Exactly(t, constants.SIGNATURE_OK, ver.GetVerification())
assert.Exactly(t, true, ver.IsValid())
}
func TestBinaryMessageEncryption(t *testing.T) {
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var message = NewPlainMessage(binData)
testPublicKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_publicKey", false)))
testPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
// Password defined in keyring_test
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Expected no error unlocking privateKey, got:", err)
}
ciphertext, err := testPublicKeyRing.Encrypt(message, testPrivateKeyRing)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
decrypted, ver, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, pgp.GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetBinary(), decrypted.GetBinary())
assert.Exactly(t, constants.SIGNATURE_OK, ver.GetVerification())
assert.Exactly(t, true, ver.IsValid())
}

View file

@ -8,11 +8,52 @@ import (
"strings"
gomime "github.com/ProtonMail/go-mime"
"github.com/ProtonMail/gopenpgp/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// MIMECallbacks defines callback methods to process a MIME message.
type MIMECallbacks interface {
OnBody(body string, mimetype string)
OnAttachment(headers string, data []byte)
// Encrypted headers can be in an attachment and thus be placed at the end of the mime structure.
OnEncryptedHeaders(headers string)
OnVerified(verified int)
OnError(err error)
}
// DecryptMIMEMessage decrypts a MIME message.
func (keyRing *KeyRing) DecryptMIMEMessage(
message *PGPMessage, verifyKey *KeyRing, callbacks MIMECallbacks, verifyTime int64,
) {
decryptedMessage, verification, err := keyRing.Decrypt(message, verifyKey, verifyTime)
if err != nil {
callbacks.OnError(err)
return
}
body, verified, attachments, attachmentHeaders, err := pgp.parseMIME(decryptedMessage.GetString(), verifyKey)
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 verification.GetVerification() != constants.SIGNATURE_NOT_SIGNED {
callbacks.OnVerified(verification.GetVerification())
} else {
callbacks.OnVerified(verified)
}
}
// ----- INTERNAL FUNCTIONS -----
func (pgp GopenPGP) parseMIME(
mimeBody string, verifierKey *KeyRing,
) (*gomime.BodyCollector, int, []string, []string, error) {
@ -49,42 +90,3 @@ func (pgp GopenPGP) parseMIME(
return body, verified, atts, attHeaders, err
}
// MIMECallbacks defines callback methods to process a MIME message.
type MIMECallbacks interface {
OnBody(body string, mimetype string)
OnAttachment(headers string, data []byte)
// Encrypted headers can be in an attachment and thus be placed at the end of the mime structure.
OnEncryptedHeaders(headers string)
OnVerified(verified int)
OnError(err error)
}
// DecryptMIMEMessage decrypts a MIME message.
func (pgp *GopenPGP) DecryptMIMEMessage(
encryptedText string, verifierKey, privateKeyRing *KeyRing,
passphrase string, callbacks MIMECallbacks, verifyTime int64,
) {
decsignverify, err := pgp.DecryptMessageVerify(encryptedText, verifierKey, privateKeyRing, passphrase, verifyTime)
if err != nil {
callbacks.OnError(err)
return
}
body, verified, attachments, attachmentHeaders, err := pgp.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)
}
}

View file

@ -1,8 +1,8 @@
package crypto
import (
"github.com/stretchr/testify/assert"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/stretchr/testify/assert"
"io/ioutil"
"testing"
)
@ -48,18 +48,26 @@ func TestDecrypt(t *testing.T) {
block, err = internal.Unarmor(readTestFile("mime_privateKey", false))
if err != nil {
t.Fatal("Cannot unarmor private key: ", err)
t.Fatal("Cannot unarmor private key:", err)
}
privateKeyUnarmored, _ := ioutil.ReadAll(block.Body)
privateKeyRing, _ := pgp.BuildKeyRing(privateKeyUnarmored)
err = privateKeyRing.UnlockWithPassphrase(privateKeyPassword)
if err != nil {
t.Fatal("Cannot unlock private key:", err)
}
pgp.DecryptMIMEMessage(
readTestFile("mime_pgpMessage", false),
message, err := NewPGPMessageFromArmored(readTestFile("mime_pgpMessage", false))
if err != nil {
t.Fatal("Cannot decode armored message:", err)
}
privateKeyRing.DecryptMIMEMessage(
message,
pgp.BuildKeyRingNoError(publicKeyUnarmored),
pgp.BuildKeyRingNoError(privateKeyUnarmored),
privateKeyPassword,
&callbacks,
pgp.GetTimeUnix())
pgp.GetUnixTime())
}
func TestParse(t *testing.T) {

View file

@ -6,26 +6,18 @@ import (
"fmt"
"io"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// RandomToken generates a random token with the key size of the default cipher.
// RandomToken generated a random token of the same size of the keysize of the default cipher.
func (pgp *GopenPGP) RandomToken() ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
keySize := config.DefaultCipher.KeySize()
symKey := make([]byte, keySize)
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
return nil, err
}
return symKey, nil
return pgp.RandomTokenSize(config.DefaultCipher.KeySize())
}
// RandomTokenWith generates a random token with the given key size.
func (pgp *GopenPGP) RandomTokenWith(size int) ([]byte, error) {
// RandomTokenSize generates a random token with the specified key size
func (pgp *GopenPGP) RandomTokenSize(size int) ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
symKey := make([]byte, size)
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
@ -34,12 +26,8 @@ func (pgp *GopenPGP) RandomTokenWith(size int) ([]byte, error) {
return symKey, nil
}
// GetSessionFromKeyPacket returns the decrypted session key from a binary
// public-key encrypted session key packet.
func (pgp *GopenPGP) GetSessionFromKeyPacket(
keyPacket []byte, privateKey *KeyRing, passphrase string,
) (*SymmetricKey,
error) {
// DecryptSessionKey returns the decrypted session key from a binary encrypted session key packet.
func (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SymmetricKey, error) {
keyReader := bytes.NewReader(keyPacket)
packets := packet.NewReader(keyReader)
@ -50,15 +38,11 @@ func (pgp *GopenPGP) GetSessionFromKeyPacket(
}
ek := p.(*packet.EncryptedKey)
rawPwd := []byte(passphrase)
var decryptErr error
for _, key := range privateKey.entities.DecryptionKeys() {
for _, key := range keyRing.entities.DecryptionKeys() {
priv := key.PrivateKey
if priv.Encrypted {
if err := priv.Decrypt(rawPwd); err != nil {
continue
}
continue
}
if decryptErr = ek.Decrypt(priv, nil); decryptErr == nil {
@ -70,38 +54,22 @@ func (pgp *GopenPGP) GetSessionFromKeyPacket(
return nil, decryptErr
}
return getSessionSplit(ek)
}
// KeyPacketWithPublicKey encrypts the session key with the armored publicKey
// and returns a binary public-key encrypted session key packet.
func (pgp *GopenPGP) KeyPacketWithPublicKey(sessionSplit *SymmetricKey, publicKey string) ([]byte, error) {
pubkeyRaw, err := armor.Unarmor(publicKey)
if err != nil {
return nil, err
if ek == nil {
return nil, errors.New("gopenpgp: unable to decrypt session key")
}
return pgp.KeyPacketWithPublicKeyBin(sessionSplit, pubkeyRaw)
return newSymmetricKeyFromEncrypted(ek)
}
// KeyPacketWithPublicKeyBin encrypts the session key with the unarmored
// EncryptSessionKey encrypts the session key with the unarmored
// publicKey and returns a binary public-key encrypted session key packet.
func (pgp *GopenPGP) KeyPacketWithPublicKeyBin(sessionSplit *SymmetricKey, publicKey []byte) ([]byte, error) {
publicKeyReader := bytes.NewReader(publicKey)
pubKeyEntries, err := openpgp.ReadKeyRing(publicKeyReader)
if err != nil {
return nil, err
}
func (keyRing *KeyRing) EncryptSessionKey(sessionSplit *SymmetricKey) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf := sessionSplit.GetCipherFunc()
if len(pubKeyEntries) == 0 {
return nil, errors.New("cannot set key: key ring is empty")
}
var pub *packet.PublicKey
for _, e := range pubKeyEntries {
for _, e := range keyRing.GetEntities() {
for _, subKey := range e.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
pub = subKey.PublicKey
@ -125,106 +93,9 @@ func (pgp *GopenPGP) KeyPacketWithPublicKeyBin(sessionSplit *SymmetricKey, publi
return nil, errors.New("cannot set key: no public key available")
}
if err = packet.SerializeEncryptedKey(outbuf, pub, cf, sessionSplit.Key, nil); err != nil {
if err := packet.SerializeEncryptedKey(outbuf, pub, cf, sessionSplit.Key, nil); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return nil, err
}
return outbuf.Bytes(), nil
}
// GetSessionFromSymmetricPacket decrypts the binary symmetrically encrypted
// session key packet and returns the session key.
func (pgp *GopenPGP) GetSessionFromSymmetricPacket(keyPacket []byte, password string) (*SymmetricKey, error) {
keyReader := bytes.NewReader(keyPacket)
packets := packet.NewReader(keyReader)
var symKeys []*packet.SymmetricKeyEncrypted
for {
var p packet.Packet
var err error
if p, err = packets.Next(); err != nil {
break
}
switch p := p.(type) {
case *packet.SymmetricKeyEncrypted:
symKeys = append(symKeys, p)
}
}
pwdRaw := []byte(password)
// Try the symmetric passphrase first
if len(symKeys) != 0 && pwdRaw != nil {
for _, s := range symKeys {
key, cipherFunc, err := s.Decrypt(pwdRaw)
if err == nil {
return &SymmetricKey{
Key: key,
Algo: getAlgo(cipherFunc),
}, nil
}
}
}
return nil, errors.New("password incorrect")
}
// SymmetricKeyPacketWithPassword encrypts the session key with the password and
// returns a binary symmetrically encrypted session key packet.
func (pgp *GopenPGP) SymmetricKeyPacketWithPassword(sessionSplit *SymmetricKey, password string) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf := sessionSplit.GetCipherFunc()
if len(password) <= 0 {
return nil, errors.New("password can't be empty")
}
pwdRaw := []byte(password)
config := &packet.Config{
DefaultCipher: cf,
}
err := packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, sessionSplit.Key, pwdRaw, config)
if err != nil {
return nil, err
}
return outbuf.Bytes(), nil
}
func getSessionSplit(ek *packet.EncryptedKey) (*SymmetricKey, error) {
if ek == nil {
return nil, errors.New("can't decrypt key packet")
}
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
break
}
}
if ek.Key == nil {
return nil, errors.New("can't decrypt key packet key is nil")
}
return &SymmetricKey{
Key: ek.Key,
Algo: algo,
}, nil
}
func getAlgo(cipher packet.CipherFunction) string {
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == cipher {
algo = k
break
}
}
return algo
}

View file

@ -4,46 +4,44 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
)
var testRandomToken []byte
func TestRandomToken(t *testing.T) {
var err error
testRandomToken, err = pgp.RandomToken()
if err != nil {
t.Fatal("Expected no error while generating default length random token, got:", err)
}
token40, err := pgp.RandomTokenSize(40)
if err != nil {
t.Fatal("Expected no error while generating random token, got:", err)
}
assert.Len(t, testRandomToken, 32)
}
func TestRandomTokenWith(t *testing.T) {
token, err := pgp.RandomTokenWith(40)
if err != nil {
t.Fatal("Expected no error while generating random token, got:", err)
}
assert.Len(t, token, 40)
assert.Len(t, token40, 40)
}
func TestAsymmetricKeyPacket(t *testing.T) {
symmetricKey := &SymmetricKey{
Key: testRandomToken,
Key: testRandomToken,
Algo: constants.AES256,
}
privateKeyRing, _ := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
publicKey, _ := testPrivateKeyRing.GetArmoredPublicKey()
_ = privateKeyRing.UnlockWithPassphrase(testMailboxPassword)
keyPacket, err := pgp.KeyPacketWithPublicKey(symmetricKey, publicKey)
keyPacket, err := privateKeyRing.EncryptSessionKey(symmetricKey)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
// Password defined in keyring_test
outputSymmetricKey, err := pgp.GetSessionFromKeyPacket(keyPacket, privateKeyRing, testMailboxPassword)
outputSymmetricKey, err := privateKeyRing.DecryptSessionKey(keyPacket)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
@ -53,21 +51,21 @@ func TestAsymmetricKeyPacket(t *testing.T) {
func TestSymmetricKeyPacket(t *testing.T) {
symmetricKey := &SymmetricKey{
Key: testRandomToken,
Key: testRandomToken,
Algo: constants.AES256,
}
password := "I like encryption"
keyPacket, err := pgp.SymmetricKeyPacketWithPassword(symmetricKey, password)
keyPacket, err := symmetricKey.EncryptToKeyPacket(password)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
_, err = pgp.GetSessionFromSymmetricPacket(keyPacket, "Wrong password")
assert.EqualError(t, err, "password incorrect")
_, err = NewSymmetricKeyFromKeyPacket(keyPacket, "Wrong password")
assert.EqualError(t, err, "gopenpgp: password incorrect")
outputSymmetricKey, err := pgp.GetSessionFromSymmetricPacket(keyPacket, password)
outputSymmetricKey, err := NewSymmetricKeyFromKeyPacket(keyPacket, password)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}

View file

@ -1,128 +0,0 @@
package crypto
import (
"bytes"
"errors"
"io"
"strings"
"time"
"github.com/ProtonMail/gopenpgp/internal"
"golang.org/x/crypto/openpgp"
errorsPGP "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
)
// SignTextDetached creates an armored detached signature of a given string.
func (kr *KeyRing) SignTextDetached(plainText string, passphrase string, trimNewlines bool) (string, error) {
signEntity, err := kr.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pgp.getTimeGenerator()}
if trimNewlines {
plainText = internal.TrimNewlines(plainText)
}
att := strings.NewReader(plainText)
var outBuf bytes.Buffer
//SignText
if err := openpgp.ArmoredDetachSignText(&outBuf, signEntity, att, config); err != nil {
return "", err
}
return outBuf.String(), nil
}
// SignBinDetached creates an armored detached signature of binary data.
func (kr *KeyRing) SignBinDetached(plainData []byte, passphrase string) (string, error) {
//sign with 0x00
signEntity, err := kr.GetSigningEntity(passphrase)
if err != nil {
return "", err
}
config := &packet.Config{DefaultCipher: packet.CipherAES256, Time: pgp.getTimeGenerator()}
att := bytes.NewReader(plainData)
var outBuf bytes.Buffer
//sign bin
if err := openpgp.ArmoredDetachSign(&outBuf, signEntity, att, config); err != nil {
return "", err
}
return outBuf.String(), nil
}
// VerifyTextDetachedSig verifies an armored detached signature given the plaintext as a string.
func (kr *KeyRing) VerifyTextDetachedSig(
signature string, plainText string, verifyTime int64, trimNewlines bool,
) (bool, error) {
if trimNewlines {
plainText = internal.TrimNewlines(plainText)
}
origText := bytes.NewReader(bytes.NewBufferString(plainText).Bytes())
return verifySignature(kr.GetEntities(), origText, signature, verifyTime)
}
// VerifyBinDetachedSig verifies an armored detached signature given the plaintext as binary data.
func (kr *KeyRing) VerifyBinDetachedSig(signature string, plainData []byte, verifyTime int64) (bool, error) {
origText := bytes.NewReader(plainData)
return verifySignature(kr.GetEntities(), origText, signature, verifyTime)
}
// Internal
func verifySignature(
pubKeyEntries openpgp.EntityList, origText *bytes.Reader,
signature string, verifyTime int64,
) (bool, error) {
config := &packet.Config{}
if verifyTime == 0 {
config.Time = func() time.Time {
return time.Unix(0, 0)
}
} else {
config.Time = func() time.Time {
return time.Unix(verifyTime+internal.CreationTimeOffset, 0)
}
}
signatureReader := strings.NewReader(signature)
signer, err := openpgp.CheckArmoredDetachedSignature(pubKeyEntries, origText, signatureReader, config)
if err == errorsPGP.ErrSignatureExpired && signer != nil {
if verifyTime > 0 { // if verifyTime = 0: time check disabled, everything is okay
// Maybe the creation time offset pushed it over the edge
// Retry with the actual verification time
config.Time = func() time.Time {
return time.Unix(verifyTime, 0)
}
_, err = signatureReader.Seek(0, io.SeekStart)
if err != nil {
return false, err
}
signer, err = openpgp.CheckArmoredDetachedSignature(pubKeyEntries, origText, signatureReader, config)
if err != nil {
return false, err
}
}
}
if signer == nil {
return false, errors.New("gopenpgp: signer is empty")
}
// if signer.PrimaryKey.KeyId != signed.PrimaryKey.KeyId {
// // t.Errorf("wrong signer got:%x want:%x", signer.PrimaryKey.KeyId, 0)
// return false, errors.New("signer is nil")
// }
return true, nil
}

View file

@ -8,6 +8,7 @@ import (
"net/textproto"
gomime "github.com/ProtonMail/go-mime"
"github.com/ProtonMail/gopenpgp/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
@ -51,7 +52,7 @@ func (sc *SignatureCollector) Accept(
}
}
if len(multiparts) != 2 {
sc.verified = notSigned
sc.verified = constants.SIGNATURE_NOT_SIGNED
// Invalid multipart/signed format just pass along
_, err = ioutil.ReadAll(rawBody)
if err != nil {
@ -96,12 +97,12 @@ func (sc *SignatureCollector) Accept(
_, err = openpgp.CheckArmoredDetachedSignature(sc.keyring, rawBody, bytes.NewReader(buffer), sc.config)
if err != nil {
sc.verified = failed
sc.verified = constants.SIGNATURE_FAILED
} else {
sc.verified = ok
sc.verified = constants.SIGNATURE_OK
}
} else {
sc.verified = noVerifier
sc.verified = constants.SIGNATURE_NO_VERIFIER
}
return nil
}

View file

@ -5,87 +5,86 @@ import (
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
)
const signedPlainText = "Signed message"
const signedPlainText = "Signed message\n"
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
var signingKeyRing *KeyRing
var signature, signatureBin string
var textSignature, binSignature *PGPSignature
var message *PlainMessage
var signatureTest = regexp.MustCompile("(?s)^-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
var signedMessageTest = regexp.MustCompile(
"(?s)^-----BEGIN PGP SIGNED MESSAGE-----.*-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
func TestSignTextDetached(t *testing.T) {
signingKeyRing, err := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
var err error
signingKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
if err != nil {
t.Fatal("Cannot read private key:", err)
}
signature, err = signingKeyRing.SignTextDetached(signedPlainText, "", true)
assert.EqualError(t, err, "gopenpgp: cannot sign message, unable to unlock signer key")
// Password defined in keyring_test
signature, err = signingKeyRing.SignTextDetached(signedPlainText, testMailboxPassword, true)
if err != nil {
t.Fatal("Cannot generate signature with encrypted key:", err)
}
// Reset keyring to locked state
signingKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
// Password defined in keyring_test
err = signingKeyRing.Unlock([]byte(testMailboxPassword))
err = signingKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Cannot decrypt private key:", err)
}
signatureDec, err := signingKeyRing.SignTextDetached(signedPlainText, "", true)
message = NewPlainMessageFromString(signedPlainText)
textSignature, err = signingKeyRing.SignDetached(message)
if err != nil {
t.Fatal("Cannot generate signature with decrypted key:", err)
t.Fatal("Cannot generate signature:", err)
}
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
assert.Regexp(t, rTest, signature)
assert.Exactly(t, signatureDec, signature)
armoredSignature, err := textSignature.GetArmored()
if err != nil {
t.Fatal("Cannot armor signature:", err)
}
assert.Regexp(t, signatureTest, armoredSignature)
}
func TestVerifyTextDetachedSig(t *testing.T) {
signedMessage, err := signingKeyRing.VerifyDetached(message, textSignature, testTime)
if err != nil {
t.Fatal("Cannot verify plaintext signature:", err)
}
assert.Exactly(t, constants.SIGNATURE_OK, signedMessage.GetVerification())
}
func TestVerifyTextDetachedSigWrong(t *testing.T) {
fakeMessage := NewPlainMessageFromString("wrong text")
signedMessage, err := signingKeyRing.VerifyDetached(fakeMessage, textSignature, testTime)
assert.EqualError(t, err, "gopenpgp: signer is empty")
assert.Exactly(t, constants.SIGNATURE_FAILED, signedMessage.GetVerification())
}
func TestSignBinDetached(t *testing.T) {
var err error
// Reset keyring to locked state
signingKeyRing, _ = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
signatureBin, err = signingKeyRing.SignBinDetached([]byte(signedPlainText), "")
assert.EqualError(t, err, "gopenpgp: cannot sign message, unable to unlock signer key")
// Password defined in keyring_test
signatureBin, err = signingKeyRing.SignBinDetached([]byte(signedPlainText), testMailboxPassword)
binSignature, err = signingKeyRing.SignDetached(NewPlainMessage([]byte(signedPlainText)))
if err != nil {
t.Fatal("Cannot generate signature with encrypted key:", err)
t.Fatal("Cannot generate signature:", err)
}
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
assert.Regexp(t, rTest, signatureBin)
}
func TestVerifyTextDetachedSig(t *testing.T) {
verified, err := signingKeyRing.VerifyTextDetachedSig(signature, signedPlainText, testTime, true)
armoredSignature, err := binSignature.GetArmored()
if err != nil {
t.Fatal("Cannot verify plaintext signature:", err)
t.Fatal("Cannot armor signature:", err)
}
assert.Exactly(t, true, verified)
}
func TestVerifyTextDetachedSigWrong(t *testing.T) {
verified, err := signingKeyRing.VerifyTextDetachedSig(signature, "wrong text", testTime, true)
assert.EqualError(t, err, "gopenpgp: signer is empty")
assert.Exactly(t, false, verified)
assert.Regexp(t, signatureTest, armoredSignature)
}
func TestVerifyBinDetachedSig(t *testing.T) {
verified, err := signingKeyRing.VerifyBinDetachedSig(signatureBin, []byte(signedPlainText), testTime)
signedMessage, err := signingKeyRing.VerifyDetached(message, binSignature, testTime)
if err != nil {
t.Fatal("Cannot verify binary signature:", err)
}
assert.Exactly(t, true, verified)
assert.Exactly(t, constants.SIGNATURE_OK, signedMessage.GetVerification())
}

224
crypto/symmetrickey.go Normal file
View file

@ -0,0 +1,224 @@
package crypto
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"github.com/ProtonMail/gopenpgp/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// SymmetricKey stores a decrypted session key.
type SymmetricKey struct {
// The decrypted binary session key.
Key []byte
// The symmetric encryption algorithm used with this key.
Algo string
}
var symKeyAlgos = map[string]packet.CipherFunction{
constants.ThreeDES: packet.Cipher3DES,
constants.TripleDES: packet.Cipher3DES,
constants.CAST5: packet.CipherCAST5,
constants.AES128: packet.CipherAES128,
constants.AES192: packet.CipherAES192,
constants.AES256: packet.CipherAES256,
}
// GetCipherFunc returns the cipher function corresponding to the algorithm used
// with this SymmetricKey.
func (symmetricKey *SymmetricKey) GetCipherFunc() packet.CipherFunction {
cf, ok := symKeyAlgos[symmetricKey.Algo]
if ok {
return cf
}
panic("gopenpgp: unsupported cipher function: " + symmetricKey.Algo)
}
// GetBase64Key returns the session key as base64 encoded string.
func (symmetricKey *SymmetricKey) GetBase64Key() string {
return base64.StdEncoding.EncodeToString(symmetricKey.Key)
}
func NewSymmetricKeyFromToken(passphrase, algo string) *SymmetricKey {
return NewSymmetricKey([]byte(passphrase), algo)
}
func NewSymmetricKey(key []byte, algo string) *SymmetricKey {
return &SymmetricKey{
Key: key,
Algo: algo,
}
}
func newSymmetricKeyFromEncrypted(ek *packet.EncryptedKey) (*SymmetricKey, error) {
var algo string
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
break
}
}
if algo == "" {
return nil, fmt.Errorf("gopenpgp: unsupported cipher function: %v", ek.CipherFunc)
}
return NewSymmetricKey(ek.Key, algo), nil
}
// Encrypt encrypts a PlainMessage to PGPMessage with a SymmetricKey
// message : The plain data as a PlainMessage
// output : The encrypted data as PGPMessage
func (symmetricKey *SymmetricKey) Encrypt(message *PlainMessage) (*PGPMessage, error) {
encrypted, err := symmetricEncrypt(message.GetBinary(), symmetricKey)
if err != nil {
return nil, err
}
return NewPGPMessage(encrypted), nil
}
// Decrypt decrypts password protected pgp binary messages
// encrypted: PGPMessage
// output: PlainMessage
func (symmetricKey *SymmetricKey) Decrypt(message *PGPMessage) (*PlainMessage, error) {
decrypted, err := symmetricDecrypt(message.NewReader(), symmetricKey)
if err != nil {
return nil, err
}
binMessage := NewPlainMessage(decrypted)
return binMessage, nil
}
// NewSymmetricKeyFromKeyPacket decrypts the binary symmetrically encrypted
// session key packet and returns the session key.
func NewSymmetricKeyFromKeyPacket(keyPacket []byte, password string) (*SymmetricKey, error) {
keyReader := bytes.NewReader(keyPacket)
packets := packet.NewReader(keyReader)
var symKeys []*packet.SymmetricKeyEncrypted
for {
var p packet.Packet
var err error
if p, err = packets.Next(); err != nil {
break
}
switch p := p.(type) {
case *packet.SymmetricKeyEncrypted:
symKeys = append(symKeys, p)
}
}
pwdRaw := []byte(password)
// Try the symmetric passphrase first
if len(symKeys) != 0 && pwdRaw != nil {
for _, s := range symKeys {
key, cipherFunc, err := s.Decrypt(pwdRaw)
if err == nil {
return &SymmetricKey{
Key: key,
Algo: getAlgo(cipherFunc),
}, nil
}
}
}
return nil, errors.New("gopenpgp: password incorrect")
}
// EncryptToKeyPacket encrypts the session key with the password and
// returns a binary symmetrically encrypted session key packet.
func (symmetricKey *SymmetricKey) EncryptToKeyPacket(password string) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf := symmetricKey.GetCipherFunc()
if len(password) <= 0 {
return nil, errors.New("gopenpgp: password can't be empty")
}
pwdRaw := []byte(password)
config := &packet.Config{
DefaultCipher: cf,
}
err := packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, symmetricKey.Key, pwdRaw, config)
if err != nil {
return nil, err
}
return outbuf.Bytes(), nil
}
// ----- INTERNAL FUNCTIONS ------
func symmetricEncrypt(message []byte, sk *SymmetricKey) ([]byte, error) {
var outBuf bytes.Buffer
config := &packet.Config{
Time: pgp.getTimeGenerator(),
DefaultCipher: sk.GetCipherFunc(),
}
encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, sk.Key, nil, config)
if err != nil {
return nil, err
}
_, err = encryptWriter.Write(message)
encryptWriter.Close()
if err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
func symmetricDecrypt(encryptedIO io.Reader, sk *SymmetricKey) ([]byte, error) {
firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled {
firstTimeCalled = false
return sk.Key, nil
}
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
}
config := &packet.Config{
Time: pgp.getTimeGenerator(),
}
md, err := openpgp.ReadMessage(encryptedIO, nil, prompt, config)
if err != nil {
return nil, err
}
messageBuf := bytes.NewBuffer(nil)
_, err = io.Copy(messageBuf, md.UnverifiedBody)
if err != nil {
return nil, err
}
return messageBuf.Bytes(), nil
}
func getAlgo(cipher packet.CipherFunction) string {
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == cipher {
algo = k
break
}
}
return algo
}

20
crypto/testdata/keyring_jsonKeys vendored Normal file
View file

@ -0,0 +1,20 @@
[
{
"ID": "qpRBAbx3s0gw5fdpS4VHPmxFZG42NPWGZW2g0fDhwHeO7duCQgOdnxcSDUxTwfLjfpeIHuPzUQPKeEN8q4TsRA==",
"Primary": 1,
"Flags": 3,
"Version": 3,
"Activation": null,
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\n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\nSmpbVZj1NSvPN4xFOnZaytD99Yt/8ZFonY9Rjg==\n=5ACd\n-----END PGP PRIVATE KEY BLOCK-----\n",
"Token": null
},
{
"ID": "88xeHAObMEZp8b15R3643qJFbRoAXPm-FNuHxWO_VBVEqm294wuKCbIdoytQwHq2uwTxAXB-c4Jh0R47F34qig==",
"Primary": 0,
"Flags": 3,
"Version": 3,
"Activation": null,
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nxYYEXMcTAxYJKwYBBAHaRw8BAQdAjYhUufSrYtCsWhJMjCaEkdJb1Zuauh8P\nS0WipcZO2dD+CQMIxbf+ePNBNFVgXUTPHWmy2PpBS2FHQWgONx0UQLRi+JyT\nMJmxS/CToS54Eo0sWEjaZROZr8f5smCgBZqZsRWv4CA35qBG/RxJMkVH2lKQ\nuM0zZWxlY3Ryb25AcHJvdG9ubWFpbC5ibHVlIDxlbGVjdHJvbkBwcm90b25t\nYWlsLmJsdWU+wncEEBYKAB8FAlzaAdIGCwkHCAMCBBUICgIDFgIBAhkBAhsD\nAh4BAAoJEP7+7R3IrxGwxh0BAKbR76lG0OH83tI5quvyNf7yt+ck/uV8MrPZ\ntcBLHwO3AQCd5CHU3Rx7Huw0QsVkJYr7t3JEYfSaTK2wTxUFKudSDceLBFzH\nEwMSCisGAQQBl1UBBQEBB0Dijq86qjP8AEv98lTBqw69HAAxo0S4Eqh1P4qU\n1irhLwMBCAf+CQMIXvD746WZtFtglCNKwMqcHMFJ0sadSDo6ntYdiQwSM42E\n0jdfdM+JUIfDw9cOXflCcdW8yUJSRaBL1BXwtCcr686pkPZ/79qxuqYY6+Nq\nzMJhBBgWCAAJBQJc2gHSAhsMAAoJEP7+7R3IrxGwKzUA/j/00OybZkE3oTDO\n2fLjBZtlKa7T1n4+vZb+T8dvl2wnAP9Ln3wTzY9oXN/n/WgSi8Q5iT2to7zx\n25aU/PlFqHQmBQ==\n=3PyZ\n-----END PGP PRIVATE KEY BLOCK-----\n",
"Token": null
}
]

12
crypto/testdata/keyring_newJSONKeys vendored Normal file
View file

@ -0,0 +1,12 @@
[
{
"ID": "hKRtZeTDhvzfAaycb5BOVx6Y3hc3gs4QvET8H_YZBTwAQBPp3h6FI4nnkJePCYuM9CG0zf7TQzOJeB2rPi0YmQ==",
"Primary": 1,
"Flags": 3,
"Version": 3,
"Activation": null,
"PrivateKey": "-----BEGIN PGP PRIVATE KEY BLOCK-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nxcMGBFzGzj4BCADh9uuhG3mDhlbFMgQpvP6AReyJYa6KSV9a6AJsNI0/DkVz\nFenIodav/r4RBgSoblqxCa/QH6galbX0eB8Vtxx4xTXCcHHZzTM9oAb4s8r6\ndGchNsSkoLspVwmc2fYfDRCTcapmeza7Jym8gD9wFUSWSwpC49jDeT6VIcOd\n+qNXgIJX1V36BOAB53lKenMYnxfzzAyJDeL/sQ/SWE9h/X+wGdkVmk7gk3pa\nQ9URoxAf7MO808hHyQYcjTV4tOHD/9k+AZC6sqmr2KAefYCxCMoCDj1wQp3J\nN6MfK2Zvc1r5KFQM2YzUXrMNrycAjo7gGeZnsvz758Q+8ouSP7iXGRl/ABEB\nAAH+CQMIQt3QQIJZYjRgTVgIQNSEkhMn4GJ5lQQPBXD0/YyK3xP0bm/bsX8S\nz9dAm1nkP0l8Q9z3lR6OcAX2O/KsqJdQGmJEZAaadz2je+EEWo1FDYaL76E6\n6z+AuteP8UtP61jBt3pTfIlhW8o7o2SlM1lvytpvc5FQplZ/iUuwfsIltCiD\n9VDkprNOVrRsjoh8BV4TrrIvU8qga58Aeg7SjOmm+3oMZ7yPTYirr11Tx/m3\nj1uzdEDfiPk4LmvlzSsWwKZuy6fSul+n92+9qN81wmdts/I2ucuKvOINZim4\nlk/p2AOsPjWpAgkefpTVLZnu2IH+VAyaXt1Fl84badXx4N921nPs7ova1Ud5\n2RddBc7b/01DtOyBSWDoNskLGpsc2mqz9kdkwwQKNjChzZc9nmY9M+AIfgT3\n+2DSQIuoJYPX69DKi/bZDwRzoHmiwiHT6Us7qxd6kD1dzCIHTptxwZQp4Tow\nnN6lmtK4S6O2B47+ROn8s0N+EH9GR8F6mvOTayNLH5yicpR3M4Of0ClvFa0G\n+JUKBXIQXUvF03G3nTPU17nLibC81UmbK3zobfbrLfuU2gU+sY+OfE1E/+GO\nSFpZcrkoRqRr39CfLkLk+GjU7RCLNddb5LxgaurVZo5h0Y7Rr8VvOQMWjjl/\nvTAG7gU/HtXi24TijNC0fP6j9w43K4b6t1SZYn5us7RRlFlWGKMSPf5Q9j6/\nryo3xULUQv0lTCQPtfQQ5UWE5ZpQ4Kjt/k5+/YtfuOcMbrDU4qa+H+rrisBu\nko7f4Wn0iYjRwRIuWh1NfUM3rIbNhq7/wonasEFOeFdPwprzMaawx3rL0Pq5\n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\nV0GB3IX8qBHjAfPmqN9JPxBIn3/6ag==\n=U1yD\n-----END PGP PRIVATE KEY BLOCK-----\n",
"Token": "-----BEGIN PGP MESSAGE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwcBMAxW/uQhm1PKxAQf8D4BAoXqnYgoTr7jEiabP9NkeuD6Z88fZDgUzXte5\n6aYMmCtPr0Vef1eDl2x0S0q/YJgR+A5Icmjxk+jG9nMSnRPlAozJfTCXu/Oy\nfkI4CcClGQv7U1EzfknBVNEKmuPO8XlEkGZ5lW/TzWk8ZtQqJsVuqZlZ5qQE\n/p8FNYEZ5RHPhhKlotA+T6XKh23z9mcN/JNEsSNcx+guERbivtTwnd5JpxQJ\nMNWzUYh+K0W7LgxoAo2jevDd4CZ/0sAAViIl+QrShodseV9agbldGUUoIMzn\nHUuIV6VYi7X82eKQXDInrtPc9IHekbDDFoncLnsGGrEqD/8O/qOHHOPwNwU+\n+cHATAOUTDv/ccWq1QEIALl8SNOQBmCuanAceUTSwCkM1fC3Ddqa8ZmnMMyG\nnDWDI7XkFU31CO10lN20/kAWjdN2073B/NT+17cp8fCqbQ1pAFJHpabdqmbI\ntm3pCC5M6otTN+MhjgFYcBuxo0rq9qtuEzz5j4Ub9MIIJTurUHMEPMI462Dc\ndK/d8BvDkU7q67Lkp65vpe9e/pv0lMMrQjdohnTHNgbZhbI/Z5LU0ApD//Ye\ncSC26BRUMJITiGKb9pKGAi0/ig0jJfzykgEarzOsY/v0W7016AMka+NsHuNc\n7Qfyg2LApF5s9Z6aK+Uy851haUk+p+abjBdcWACziAmimVGjlRYm49ra4UJ7\nc6LSeAElM584e4Z3VnqajAJWbmWt2atgBQmcwEBBsdNAtzIagNydMbBBIELA\n5wGUbMXfQxrMOo/Mdac/5EMTHT0/kVQNIBHtpX5SknWvbc5DjIHoH3+BbL87\neaxhKW94hMubKNKT3dbm4PKHtsMiS3TGkFZ3GjvA0Cy/Kw==\n=b1N8\n-----END PGP MESSAGE-----\n",
"Signature": "-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwsBcBAEBCAAGBQJcxtSkAAoJEGfweL9m9sQefz8IAJhpEOvG7d+PVgq3bEL+\nXzY8yTUKB1ZXEMbcR/uHUWSfu0F8zy4CNtSG5HUVjNxx1xzzJVJcxWB/ljO+\n/bJSFOFexTyNh8i/xU+CiBfm5RhAFTYF9xFwfD3LKp5gaalJAhWhArk1/Wuh\nWTDpFpk39uzBRKNwcnSgiJYPxOjAZxj+w/hhHPwmco5cUwMiMR5MNrfzKf+x\nwX3Cfs9fsiiCDzohBzbK0FFsMnJ8aXNVsDBjEA1KrB7sdyaf8FnaM0RsFRDb\njxtIgfcFBbyNJh4414Unt4AYTIrIWhK4OOXI3AfsJy8p6KRBKQUcUkKcDxab\nPTXOPZsZ+UgQ5MevyVnP1zfCwFwEAQEIAAYFAlzG1KQACgkQm1DMMBzxetx/\nPwf9F04uHtix0zDpP1IvG4VYlor4rjYTdfXqxxiFXHO6MZXJoigS1E71E8r4\nsqZ6PoQ5/xCj2A01KRhuF1Bon3mEZEwaIUuBqTV91sLsVWfoxgyPAYpr6gK/\n1W9JhVNNrVRMGox42LQUjyiq0ESrfWmqC8SuMfZMoUoBZycHicA50RbyOUnT\nLO57ArL3JIVmYtyosaXM3idzmNHmaXSkcGt4cvTVysJZQrneaxmikfm5CH2O\n1z2goLBNnzsbRionoV6gCukZOiM/d14yiyeYtsFJ7u/vodkI5y0M0sF8VnN/\njL9keP4ZpHiJ4MBd6tyIH0pLueDRlurFL3fcHsEzD5SrOA==\n=wGCk\n-----END PGP SIGNATURE-----\n"
}
]

62
crypto/testdata/keyring_userKey vendored Normal file
View file

@ -0,0 +1,62 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v4.4.5
Comment: testpassphrase
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
uaSC3IcBmBsj1fNb4eYXElILjQ==
=fMOl
-----END PGP PRIVATE KEY BLOCK-----

18
crypto/testdata/message_expired vendored Normal file
View file

@ -0,0 +1,18 @@
-----BEGIN PGP MESSAGE-----
Comment: GPGTools - https://gpgtools.org
owEBWwKk/ZANAwAKAeyAexA3gWZ0AawUYgloZWxsby50eHRaX2WpaGVsbG+JAjME
AAEKAB0WIQTxcIn7ZRrhwd51IZbsgHsQN4FmdAUCWl9lqQAKCRDsgHsQN4FmdCln
D/44x1bcrOXg+DbRStSrC75wFa+cvPEmaTZyqN6d7qlQCMxOcPlq6lbZ74QWfEq7
i1ZYHp4AU8jALw0QqBQQE5FvABleQKpVfY22s83Bqy+P0DB9ntpD+t+oZrxGCLmL
MbZJNFnGro48gHt+/OQKLuftiVwE2opHfgogVKNL74FmYA0hMItdzpn4OPNFkP8t
Iq/m0hkXlTAKqBPITVLv1FN16v+Sm1iC317eP/HOTYqVZdJN3svVF8ZBfg29a8p6
6nl67fZhXgrt0OB6KSNIZEwMTWjFAqi365mtTssqAA0un94+cQ/WvAC5QcMM8g5S
i3G7vny9AsXor+GDU1z7UDWs3wBV4mVRdj7bBIS6PK+6oe012aNpRObcI2bU2BT/
H/7uHZWfwEmpfvH9RVZgoeETA3vSx7MDrNyDt3gwv2hxOHEd7nnVQ3EKG33173o1
/5/oEmn2USujKGhHJ2Zo3aWNRuUWZlvBaYw+PwB2R0UiuJbi0KofNYPssNdpw4sg
Qs7Nb2/Ilo1zn5bDh+WDrUrn6zHKAfBytBPpwPFWPZ8W10HUlC5vMZSKH5/UZhj5
kLlUC1zKjFPpRhO27ImTJuImil4lR2/CFjB1duG3JGJQaYIq8RFJOjvTVY29wl0i
pFy6y1Ofv2lLHB9K7N7dvvee2nvpUMkLEL52oFQ6Jc7sdg==
=Q4tk
-----END PGP MESSAGE-----

View file

@ -17,8 +17,8 @@ func (pgp *GopenPGP) UpdateTime(newTime int64) {
pgp.latestClientTime = time.Now()
}
// GetTimeUnix gets latest cached time
func (pgp *GopenPGP) GetTimeUnix() int64 {
// GetUnixTime gets latest cached time
func (pgp *GopenPGP) GetUnixTime() int64 {
return pgp.getNow().Unix()
}
@ -27,6 +27,9 @@ func (pgp *GopenPGP) GetTime() time.Time {
return pgp.getNow()
}
// ----- INTERNAL FUNCTIONS -----
// getNow returns current time
func (pgp *GopenPGP) getNow() time.Time {
if pgp.latestServerTime > 0 && !pgp.latestClientTime.IsZero() {
// Until is monotonic, it uses a monotonic clock in this case instead of the wall clock
@ -37,6 +40,7 @@ func (pgp *GopenPGP) getNow() time.Time {
return time.Now()
}
// getTimeGenerator Returns a time generator function
func (pgp *GopenPGP) getTimeGenerator() func() time.Time {
return func() time.Time {
return pgp.getNow()

22
helper/base_test.go Normal file
View file

@ -0,0 +1,22 @@
package helper
import (
"io/ioutil"
"strings"
)
var err error
func readTestFile(name string, trimNewlines bool) string {
data, err := ioutil.ReadFile("../crypto/testdata/" + name)
if err != nil {
panic(err)
}
if trimNewlines {
return strings.TrimRight(string(data), "\n")
}
return string(data)
}
// Corresponding key in ../crypto/testdata/keyring_privateKey
const testMailboxPassword = "apple"

82
helper/cleartext.go Normal file
View file

@ -0,0 +1,82 @@
package helper
import (
"errors"
"strings"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/internal"
)
// SignCleartextMessageArmored signs text given a private key and its passphrase, canonicalizes and trims the newlines,
// and returns the PGP-compliant special armoring
func SignCleartextMessageArmored(privateKey, passphrase, text string) (string, error) {
signingKeyRing, err := pgp.BuildKeyRingArmored(privateKey)
if err != nil {
return "", err
}
err = signingKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
return "", err
}
return SignCleartextMessage(signingKeyRing, text)
}
// VerifyCleartextMessageArmored verifies PGP-compliant armored signed plain text given the public key
// and returns the text or err if the verification fails
func VerifyCleartextMessageArmored(publicKey, armored string, verifyTime int64) (string, error) {
verifyKeyRing, err := pgp.BuildKeyRingArmored(publicKey)
if err != nil {
return "", err
}
return VerifyCleartextMessage(verifyKeyRing, armored, verifyTime)
}
// SignCleartextMessage signs text given a private keyring, canonicalizes and trims the newlines,
// and returns the PGP-compliant special armoring
func SignCleartextMessage(keyRing *crypto.KeyRing, text string) (string, error) {
text = canonicalizeAndTrim(text)
message := crypto.NewPlainMessageFromString(text)
signature, err := keyRing.SignDetached(message)
if err != nil {
return "", err
}
return armor.ArmorClearSignedMessage(message.GetBinary(), signature.GetBinary())
}
// VerifyCleartextMessage verifies PGP-compliant armored signed plain text given the public keyring
// and returns the text or err if the verification fails
func VerifyCleartextMessage(keyRing *crypto.KeyRing, armored string, verifyTime int64) (string, error) {
text, signatureData, err := armor.ReadClearSignedMessage(armored)
if err != nil {
return "", err
}
message := crypto.NewPlainMessageFromString(text)
signature := crypto.NewPGPSignature(signatureData)
ver, err := keyRing.VerifyDetached(message, signature, verifyTime)
if err != nil {
return "", err
}
if !ver.IsValid() {
return "", errors.New("gopenpgp: unable to verify attachment")
}
return message.GetString(), nil
}
// ----- INTERNAL FUNCTIONS -----
// canonicalizeAndTrim alters a string canonicalizing and trimming the newlines
func canonicalizeAndTrim(text string) string {
text = internal.TrimNewlines(text)
text = strings.Replace(strings.Replace(text, "\r\n", "\n", -1), "\n", "\r\n", -1)
return text
}

45
helper/cleartext_test.go Normal file
View file

@ -0,0 +1,45 @@
package helper
import (
"regexp"
"testing"
"github.com/stretchr/testify/assert"
)
const signedPlainText = "Signed message\n"
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
var signedMessageTest = regexp.MustCompile(
"(?s)^-----BEGIN PGP SIGNED MESSAGE-----.*-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
func TestSignClearText(t *testing.T) {
// Password defined in base_test
armored, err := SignCleartextMessageArmored(
readTestFile("keyring_privateKey", false),
testMailboxPassword,
signedPlainText,
)
if err != nil {
t.Fatal("Cannot armor message:", err)
}
assert.Regexp(t, signedMessageTest, armored)
verified, err := VerifyCleartextMessageArmored(
readTestFile("keyring_publicKey", false),
armored,
pgp.GetUnixTime(),
)
if err != nil {
t.Fatal("Cannot verify message:", err)
}
assert.Exactly(t, canonicalizeAndTrim(signedPlainText), verified)
}
func TestMessageCanonicalizeAndTrim(t *testing.T) {
text := "Hi \ntest!\r\n\n"
canon := canonicalizeAndTrim(text)
assert.Exactly(t, "Hi\r\ntest!\r\n\r\n", canon)
}

258
helper/helper.go Normal file
View file

@ -0,0 +1,258 @@
package helper
import (
"errors"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/crypto"
)
var pgp = crypto.GetGopenPGP()
// EncryptMessageWithToken encrypts a string with a passphrase using AES256
func EncryptMessageWithToken(
passphrase, plaintext string,
) (ciphertext string, err error) {
return EncryptMessageWithTokenAlgo(passphrase, plaintext, constants.AES256)
}
// EncryptMessageWithTokenAlgo encrypts a string with a random token and an algorithm chosen from constants.*
func EncryptMessageWithTokenAlgo(
token, plaintext, algo string,
) (ciphertext string, err error) {
var pgpMessage *crypto.PGPMessage
var message = crypto.NewPlainMessageFromString(plaintext)
var key = crypto.NewSymmetricKeyFromToken(token, algo)
if pgpMessage, err = key.Encrypt(message); err != nil {
return "", err
}
if ciphertext, err = pgpMessage.GetArmored(); err != nil {
return "", err
}
return ciphertext, nil
}
// DecryptMessageWithToken decrypts an armored message with a random token.
// The algorithm is derived from the armoring.
func DecryptMessageWithToken(token, ciphertext string) (plaintext string, err error) {
var message *crypto.PlainMessage
var pgpMessage *crypto.PGPMessage
var key = crypto.NewSymmetricKeyFromToken(token, "")
if pgpMessage, err = crypto.NewPGPMessageFromArmored(ciphertext); err != nil {
return "", err
}
if message, err = key.Decrypt(pgpMessage); err != nil {
return "", err
}
return message.GetString(), nil
}
// EncryptMessageArmored generates an armored PGP message given a plaintext and an armored public key
func EncryptMessageArmored(publicKey, plaintext string) (ciphertext string, err error) {
var publicKeyRing *crypto.KeyRing
var pgpMessage *crypto.PGPMessage
var message = crypto.NewPlainMessageFromString(plaintext)
if publicKeyRing, err = pgp.BuildKeyRingArmored(publicKey); err != nil {
return "", err
}
if pgpMessage, err = publicKeyRing.Encrypt(message, nil); err != nil {
return "", err
}
if ciphertext, err = pgpMessage.GetArmored(); err != nil {
return "", err
}
return ciphertext, nil
}
// EncryptSignMessageArmored generates an armored signed PGP message given a plaintext and an armored public key
// a private key and its passphrase
func EncryptSignMessageArmored(
publicKey, privateKey, passphrase, plaintext string,
) (ciphertext string, err error) {
var publicKeyRing, privateKeyRing *crypto.KeyRing
var pgpMessage *crypto.PGPMessage
var message = crypto.NewPlainMessageFromString(plaintext)
if publicKeyRing, err = pgp.BuildKeyRingArmored(publicKey); err != nil {
return "", err
}
if privateKeyRing, err = pgp.BuildKeyRingArmored(privateKey); err != nil {
return "", err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); err != nil {
return "", err
}
if pgpMessage, err = publicKeyRing.Encrypt(message, privateKeyRing); err != nil {
return "", err
}
if ciphertext, err = pgpMessage.GetArmored(); err != nil {
return "", err
}
return ciphertext, nil
}
// DecryptMessageArmored decrypts an armored PGP message given a private key and its passphrase
func DecryptMessageArmored(
privateKey, passphrase, ciphertext string,
) (plaintext string, err error) {
var privateKeyRing *crypto.KeyRing
var pgpMessage *crypto.PGPMessage
var message *crypto.PlainMessage
if privateKeyRing, err = pgp.BuildKeyRingArmored(privateKey); err != nil {
return "", err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); err != nil {
return "", err
}
if pgpMessage, err = crypto.NewPGPMessageFromArmored(ciphertext); err != nil {
return "", err
}
if message, _, err = privateKeyRing.Decrypt(pgpMessage, nil, 0); err != nil {
return "", err
}
return message.GetString(), nil
}
// 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 DecryptVerifyMessageArmored(
publicKey, privateKey, passphrase, ciphertext string,
) (plaintext string, err error) {
var publicKeyRing, privateKeyRing *crypto.KeyRing
var pgpMessage *crypto.PGPMessage
var message *crypto.PlainMessage
var verification *crypto.Verification
if publicKeyRing, err = pgp.BuildKeyRingArmored(publicKey); err != nil {
return "", err
}
if privateKeyRing, err = pgp.BuildKeyRingArmored(privateKey); err != nil {
return "", err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); err != nil {
return "", err
}
if pgpMessage, err = crypto.NewPGPMessageFromArmored(ciphertext); err != nil {
return "", err
}
if message, verification, err = privateKeyRing.Decrypt(pgpMessage, publicKeyRing, pgp.GetUnixTime()); err != nil {
return "", err
}
if !verification.IsValid() {
return "", errors.New("gopenpgp: unable to verify message")
}
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, passphrase, fileName string,
plainData []byte,
) (keyPacket, dataPacket, signature []byte, err error) {
var publicKeyRing, privateKeyRing *crypto.KeyRing
var packets *crypto.PGPSplitMessage
var signatureObj *crypto.PGPSignature
var binMessage = crypto.NewPlainMessage(plainData)
if publicKeyRing, err = pgp.BuildKeyRingArmored(publicKey); err != nil {
return nil, nil, nil, err
}
if privateKeyRing, err = pgp.BuildKeyRingArmored(privateKey); err != nil {
return nil, nil, nil, err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); 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.GetKeyPacket(), packets.GetDataPacket(), 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.
func DecryptVerifyAttachment(
publicKey, privateKey, passphrase string,
keyPacket, dataPacket []byte,
armoredSignature string,
) (plainData []byte, err error) {
var publicKeyRing, privateKeyRing *crypto.KeyRing
var detachedSignature *crypto.PGPSignature
var message *crypto.PlainMessage
var verification *crypto.Verification
var packets = crypto.NewPGPSplitMessage(keyPacket, dataPacket)
if publicKeyRing, err = pgp.BuildKeyRingArmored(publicKey); err != nil {
return nil, err
}
if privateKeyRing, err = pgp.BuildKeyRingArmored(privateKey); err != nil {
return nil, err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); err != nil {
return nil, err
}
if detachedSignature, err = crypto.NewPGPSignatureFromArmored(armoredSignature); err != nil {
return nil, err
}
if message, err = privateKeyRing.DecryptAttachment(packets); err != nil {
return nil, err
}
if verification, err = publicKeyRing.VerifyDetached(message, detachedSignature, pgp.GetUnixTime()); err != nil {
return nil, errors.New("gopenpgp: unable to verify attachment")
}
if !verification.IsValid() {
return nil, errors.New("gopenpgp: unable to verify attachment")
}
return message.GetBinary(), nil
}

131
helper/helper_test.go Normal file
View file

@ -0,0 +1,131 @@
package helper
import (
"testing"
"github.com/ProtonMail/gopenpgp/crypto"
"github.com/stretchr/testify/assert"
)
func TestAESEncryption(t *testing.T) {
var plaintext = "Symmetric secret"
var passphrase = "passphrase"
ciphertext, err := EncryptMessageWithToken(passphrase, plaintext)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
_, err = DecryptMessageWithToken("Wrong passphrase", ciphertext)
assert.EqualError(t, err, "gopenpgp: wrong password in symmetric decryption")
decrypted, err := DecryptMessageWithToken(passphrase, ciphertext)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, plaintext, decrypted)
}
func TestArmoredTextMessageEncryption(t *testing.T) {
var plaintext = "Secret message"
armored, err := EncryptMessageArmored(readTestFile("keyring_publicKey", false), plaintext)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
assert.Exactly(t, true, pgp.IsPGPMessage(armored))
decrypted, err := DecryptMessageArmored(
readTestFile("keyring_privateKey", false),
testMailboxPassword, // Password defined in base_test
armored,
)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, plaintext, decrypted)
}
func TestArmoredTextMessageEncryptionVerification(t *testing.T) {
var plaintext = "Secret message"
armored, err := EncryptSignMessageArmored(
readTestFile("keyring_publicKey", false),
readTestFile("keyring_privateKey", false),
testMailboxPassword, // Password defined in base_test
plaintext,
)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
assert.Exactly(t, true, pgp.IsPGPMessage(armored))
_, err = DecryptVerifyMessageArmored(
readTestFile("mime_publicKey", false), // Wrong public key
readTestFile("keyring_privateKey", false),
testMailboxPassword, // Password defined in base_test
armored,
)
assert.EqualError(t, err, "gopenpgp: unable to verify message")
decrypted, err := DecryptVerifyMessageArmored(
readTestFile("keyring_publicKey", false),
readTestFile("keyring_privateKey", false),
testMailboxPassword, // Password defined in base_test
armored,
)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, plaintext, decrypted)
}
func TestAttachmentEncryptionVerification(t *testing.T) {
var attachment = []byte("Secret file\r\nRoot password:hunter2")
keyPacket, dataPacket, signature, err := EncryptSignAttachment(
readTestFile("keyring_publicKey", false),
readTestFile("keyring_privateKey", false),
testMailboxPassword, // Password defined in base_test
"password.txt",
attachment,
)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
sig := crypto.NewPGPSignature(signature)
armoredSig, err := sig.GetArmored()
if err != nil {
t.Fatal("Expected no error when armoring signature, got:", err)
}
_, err = DecryptVerifyAttachment(
readTestFile("mime_publicKey", false), // Wrong public key
readTestFile("keyring_privateKey", false),
testMailboxPassword, // Password defined in base_test
keyPacket,
dataPacket,
armoredSig,
)
assert.EqualError(t, err, "gopenpgp: unable to verify attachment")
decrypted, err := DecryptVerifyAttachment(
readTestFile("keyring_publicKey", false),
readTestFile("keyring_privateKey", false),
testMailboxPassword, // Password defined in base_test
keyPacket,
dataPacket,
armoredSig,
)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, attachment, decrypted)
}

View file

@ -1,26 +1,8 @@
// Package models provides structs containing message data.
package models
// EncryptedSplit contains a separate session key packet and symmetrically
// encrypted data packet.
type EncryptedSplit struct {
DataPacket []byte
KeyPacket []byte
Algo string
}
// EncryptedSigned contains an encrypted message and signature.
type EncryptedSigned struct {
Encrypted string
Signature string
}
// DecryptSignedVerify contains a decrypted message and verification result.
type DecryptSignedVerify struct {
//clear text
Plaintext string
//bitmask verify status : 0
Verify int
//error message if verify failed
Message string
}

View file

@ -1,8 +1,8 @@
package subtle
import (
"github.com/stretchr/testify/assert"
"encoding/hex"
"github.com/stretchr/testify/assert"
"testing"
)