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:
parent
82d49bf235
commit
e65ed17b41
34 changed files with 2573 additions and 1478 deletions
460
ProposalChanges.md
Normal file
460
ProposalChanges.md
Normal 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
185
README.md
|
|
@ -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)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
310
crypto/key.go
310
crypto/key.go
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
255
crypto/keyring_message.go
Normal 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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
224
crypto/symmetrickey.go
Normal 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
20
crypto/testdata/keyring_jsonKeys
vendored
Normal 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
12
crypto/testdata/keyring_newJSONKeys
vendored
Normal 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
62
crypto/testdata/keyring_userKey
vendored
Normal 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
18
crypto/testdata/message_expired
vendored
Normal 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-----
|
||||
|
|
@ -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
22
helper/base_test.go
Normal 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
82
helper/cleartext.go
Normal 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
45
helper/cleartext_test.go
Normal 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
258
helper/helper.go
Normal 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
131
helper/helper_test.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package subtle
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"encoding/hex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue