Openpgp security update (V2) (#31)

* Change keyring unlock functionalities

* Add keyring#Lock, keyring#CheckIntegrity, tests

* Update helpers, fix bugs

* Update go.mod with ProtonMail/crypto commit

* Change key management system

* Clear keys from memory + tests

* Create SessionKey with direct encryption for datapackets. Move symmetrickey to password.

* Fix upstream dependencies

* Update module to V2, documentation

* Add linter

* Add v2 folder to .gitignore

* Minor changes to KeyID getters

* Remove old changelog

* Improve docs, remove compilation script
This commit is contained in:
wussler 2019-12-27 19:35:43 +01:00 committed by GitHub
parent 136c0a5495
commit 54f45d0471
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 2588 additions and 1770 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ vendor
*.html
reports
.idea
v2

54
.golangci.yml Normal file
View file

@ -0,0 +1,54 @@
linters-settings:
godox:
keywords: # default keywords are TODO, BUG, and FIXME, but we override this by ignoring TODO
- BUG
- FIXME
funlen:
lines: 100
statements: 80
issues:
exclude-use-default: false
exclude:
- Using the variable on range scope `tt` in function literal
linters:
enable:
- deadcode # Finds unused code [fast: true, auto-fix: false]
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false]
- gosimple # Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false]
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false]
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
- structcheck # Finds unused struct fields [fast: true, auto-fix: false]
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
- unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
- varcheck # Finds unused global variables and constants [fast: true, auto-fix: false]
- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
# - dupl # Tool for code clone detection [fast: true, auto-fix: false]
- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
# - gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false]
# - gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false]
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
# - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
- gosec # Inspects source code for security problems [fast: true, auto-fix: false]
- interfacer # Linter that suggests narrower interface types [fast: true, auto-fix: false]
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false]
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
- prealloc # Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
- scopelint # Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
# - stylecheck # Stylecheck is a replacement for golint [fast: true, auto-fix: false]
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
- unparam # Reports unused function parameters [fast: true, auto-fix: false]
- whitespace # Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
disable:
- wsl # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false]
- lll # Reports long lines [fast: true, auto-fix: false]\

View file

@ -1,7 +1,42 @@
# use the latest ubuntu environment (18.04) available on travis
dist: bionic
language: go
# Force-enable Go modules. Also force go to use the code in vendor/
# These will both be unnecessary when Go 1.14 lands.
env: GO111MODULE=on
go:
- 1.11.x
- 1.12.x
env:
- GO111MODULE=on GOFLAGS=-mod=readonly
- 1.13.x
# Only clone the most recent commit.
git:
depth: 1
# Skip the install step. Don't `go get` dependencies. Only build with the code
# in vendor/
install: true
# Don't email me the results of the test runs.
notifications:
email: false
# Anything in before_script that returns a nonzero exit code will flunk the
# build and immediately stop. It's sorta like having set -e enabled in bash.
# Make sure you've pinned the version of golangci-lint by running this command
# in your project directory:
# GO111MODULE=on go get github.com/golangci/golangci-lint@v1.21.0
# You should see this line in your go.mod file:
# github.com/golangci/golangci-lint v1.21.0
before_script:
- go install github.com/golangci/golangci-lint/cmd/golangci-lint
# script always runs to completion (set +e). If we have linter issues AND a
# failing test, we want to see both. Configure golangci-lint with a
# .golangci.yml file at the top level of your repo.
script:
- golangci-lint run # run a bunch of code checkers/linters in parallel
- go test -v -race ./... # Run all the tests with the race detector enabled

498
CHANGELOG.md Normal file
View file

@ -0,0 +1,498 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.0] - 2019-12-??
Since the open-sourcing of the library in May the API has been updated, listening to internal and
external feedback, in order to have a flexible library, that can be used in a simple settings,
with batteries included, or by more advanced users that might want to interact directly with
the inner structure of the PGP messages and keys.
It allows direct interaction with keys and keyrings, passphrases, as well as session keys.
It is designed with gomobile users in mind, so that they can use the full power of the library,
without having to rely on a further wrapper.
This version comes with some design improvements, in particular the introduction of keys
### Security
- Dropped the use of strings for secrets
- New key checking functions
- Clear memory after use, in an attempt to reduce leftover secrets in RAM.
- Improved testing, in this and the underlying crypto library
### Fixed
- `KeyRing`s can now only be unencrypted, removing the problem of mixed encrypted/decrypted keyring, that caused keys not to be recognised.
- Explicit key decryption and encryption.
- Underlying crypto library update.
- Underlying MIME library update.
- Fixed ECC critical bugs.
- Removed gopenpgp/pmcrypto object as it could create multiple globals. Methods are now static on the crypto object.
### Removed
- `Signature` struct
- `Signature#KeyRing` function
- `Signature#IsBy` function
- `pmKeyObject` struct
- `encodedLength` function, internal and and unused
- `EncryptCore` is now internal.
- `RandomTokenWith`, `RandomToken` now takes a size
- In the `KeyRing` struct:
- `KeyRing#GetEntities`, entities are handled by the lib
- `KeyRing#GetSigningEntity`, has been made internal
- `KeyRing#Unlock`, the unlocking functionalities are on now on the key object
- `BuildKeyRingNoError`, `BuildKeyRingArmored`, `BuildKeyRing` use `NewKey` or `NewKeyFromArmored` and handle errors
then join them into KeyRings.
- `ReadKeyRing`, `ReadArmoredKeyRing`, use `NewKeyFromArmoredReader` or `NewKeyFromReader`.
- `UnmarshalJSON`, the interface to unmarshal JSON is not relevant to this library.
### Added
- `Key` struct, to store, import (unserialize) and export (serialize) keys.
```go
// Key contains a single private or public key
type Key struct {
// PGP entities in this keyring.
entity *openpgp.Entity
}
// With the functions
NewKeyFromArmoredReader(r io.Reader) (key *Key, err error)
NewKeyFromReader(r io.Reader) (key *Key, err error)
NewKey(binKeys []byte) (key *Key, err error)
NewKeyFromArmored(armored string) (key *Key, err error)
GenerateKey(name, email string, keyType string, bits int) (*Key, error)
GenerateRSAKeyWithPrimes(name, email string, bits int, primeone, primetwo, primethree, primefour []byte) (*Key, error)
(key *Key) Clone() (*Key, error)
(key *Key) Lock(passphrase []byte) (*Key, error)
(key *Key) Unlock(passphrase []byte) (*Key, error)
(key *Key) Serialize() ([]byte, error)
(key *Key) Armor() (string, error)
(key *Key) GetArmoredPublicKey() (s string, err error)
(key *Key) GetPublicKey() (b []byte, err error)
(key *Key) IsExpired() bool
(key *Key) IsPrivate() bool
(key *Key) IsLocked() (bool, error)
(key *Key) IsUnlocked() (bool, error)
(key *Key) Check() (bool, error)
(key *Key) PrintFingerprints()
(key *Key) GetHexKeyID() string
(key *Key) GetKeyID() uint64
(key *Key) GetFingerprint() string
(key *Key) ClearPrivateParams() (ok bool)
```
- In the `KeyRing` object:
```go
NewKeyRing(key *Key) (*KeyRing, error)
(keyRing *KeyRing) AddKey(key *Key) error
(keyRing *KeyRing) GetKeys() []*Key
(keyRing *KeyRing) GetKey(n int) (*Key, error)
(keyRing *KeyRing) CountEntities() int
(keyRing *KeyRing) CountDecryptionEntities() int
(keyRing *KeyRing) GetIdentities() []*Identity
(keyRing *KeyRing) FirstKey() (*KeyRing, error)
(keyRing *KeyRing) Clone() (*KeyRing, error)
(keyRing *KeyRing) ClearPrivateParams()
```
- `PlainMessage` struct, to store un-encrypted messages
```go
// PlainMessage stores a plain text / unencrypted message.
type PlainMessage struct {
// The content of the message
Data []byte
// if the content is text or binary
TextType bool
}
// With the functions
NewPlainMessage(data []byte) *PlainMessage
NewPlainMessageFromString(text string) *PlainMessage
(msg *PlainMessage) GetBinary()
(msg *PlainMessage) GetString()
(msg *PlainMessage) GetBase64()
(msg *PlainMessage) NewReader()
(msg *PlainMessage) IsText()
(msg *PlainMessage) IsBinary()
```
- `PGPMessage` struct, to store encrypted PGP messages
```go
// PGPMessage stores a PGP-encrypted message.
type PGPMessage struct {
// The content of the message
Data []byte
}
// With the functions
NewPGPMessage(data []byte) *PGPMessage
NewPGPMessageFromArmored(armored string) (*PGPMessage, error)
(msg *PGPMessage) GetBinary() []byte
(msg *PGPMessage) NewReader() io.Reader
(msg *PGPMessage) GetArmored() (string, error)
(msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int) (outSplit *PGPSplitMessage, err error)
```
- `PGPSignature` struct, to store detached PGP signatures
```go
// PGPSignature stores a PGP-encoded detached signature.
type PGPSignature struct {
// The content of the message
Data []byte
}
// With the functions
NewPGPSignature(data []byte) *PGPSignature
NewPGPSignatureFromArmored(armored string) (*PGPSignature, error)
(msg *PGPSignature) GetBinary() []byte
(msg *PGPSignature) GetArmored() (string, error)
```
- `SignatureVerificationError` struct, to separate signature verification errors from decryption errors
```go
// SignatureVerificationError is returned from Decrypt and VerifyDetached functions when signature verification fails
type SignatureVerificationError struct {
Status int
Message string
}
```
### Changed
- `IsKeyExpiredBin` has been renamed to `IsKeyExpired`
- `IsKeyExpired` has been renamed to `IsArmoredKeyExpired`
- `CheckKey` has been renamed to `PrintFingerprints`
- `KeyRing#ArmoredPublicKeyString` has been renamed to `KeyRing#GetArmoredPublicKey`
- `KeyRing#KeyIds` has been renamed to `KeyRing#GetKeyIDs`
- `GetTimeUnix` was renamed to `GetUnixTime`
- `EncryptedSplit` has been changed to `PGPSplitMessage`
```go
models.EncryptedSplit struct {
DataPacket []byte
KeyPacket []byte
Algo string
}
// Is now
crypto.PGPSplitMessage struct {
DataPacket []byte
KeyPacket []byte
}
// With the functions
NewPGPSplitMessage(keyPacket []byte, dataPacket []byte) *PGPSplitMessage
NewPGPSplitMessageFromArmored(encrypted string) (*PGPSplitMessage, error)
(msg *PGPSplitMessage) GetBinaryDataPacket() []byte
(msg *PGPSplitMessage) GetBinaryKeyPacket() []byte
(msg *PGPSplitMessage) GetBinary() []byte
(msg *PGPSplitMessage) GetArmored() (string, error)
```
- `DecryptSignedVerify` has been changed to `ExplicitVerifyMessage`
```go
models.DecryptSignedVerify struct {
//clear text
Plaintext string
//bitmask verify status : 0
Verify int
//error message if verify failed
Message string
}
// Is now
// ExplicitVerifyMessage contains explicitely the signature verification error, for gomobile users
type ExplicitVerifyMessage struct {
Message *crypto.PlainMessage
SignatureVerificationError *crypto.SignatureVerificationError
}
// With the new helper
DecryptExplicitVerify (pgpMessage *crypto.PGPMessage, privateKeyRing, publicKeyRing *crypto.KeyRing, verifyTime int64) (*ExplicitVerifyMessage, error)
```
- `SignedString` has been changed to `ClearTextMessage`
```go
// SignedString wraps string with Signature
type SignedString struct {
String string
Signed *Signature
}
// Is now
// ClearTextMessage, split signed clear text message container
type ClearTextMessage struct {
Data []byte
Signature []byte
}
// With the functions
NewClearTextMessage(data []byte, signature []byte) *ClearTextMessage
NewClearTextMessageFromArmored(signedMessage string) (*ClearTextMessage, error)
(msg *ClearTextMessage) GetBinary() []byte
(msg *ClearTextMessage) GetString() string
(msg *ClearTextMessage) GetBinarySignature() []byte
(msg *ClearTextMessage) GetArmored() (string, error)
```
- `SymmetricKey` has been renamed to `SessionKey`
```go
// SessionKey stores a decrypted session key.
type SessionKey struct {
// The decrypted binary session key.
Key []byte
// The symmetric encryption algorithm used with this key.
Algo string
}
// With the functions
NewSessionKeyFromToken(token []byte, algo string) *SessionKey
GenerateSessionKey() (*SessionKey, error)
GenerateSessionKeyAlgo(algo string) (sk *SessionKey, err error)
(sk *SessionKey) GetCipherFunc() packet.CipherFunction
(sk *SessionKey) GetBase64Key() string
(sk *SessionKey) Encrypt(message *PlainMessage) ([]byte, error)
(sk *SessionKey) Decrypt(dataPacket []byte) (*PlainMessage, error)
(sk *SessionKey) Clear() (ok bool)
```
- `ReadClearSignedMessage` moved to crypto package and renamed to `NewClearTextMessageFromArmored`. Changed to return `ClearTextMessage`.
```go
ReadClearSignedMessage(signedMessage string) (string, error)
// Is now
NewClearTextMessageFromArmored(signedMessage string) (*ClearTextMessage, error)
// In addition, were added:
NewClearTextMessage(data []byte, signature []byte) *ClearTextMessage
(msg *ClearTextMessage) GetBinary() []byte
(msg *ClearTextMessage) GetString() string
(msg *ClearTextMessage) GetBinarySignature() []byte
(msg *ClearTextMessage) GetArmored() (string, error)
// As helpers were added:
SignCleartextMessageArmored(privateKey string, passphrase []byte, text string) (string, error)
VerifyCleartextMessageArmored(publicKey, armored string, verifyTime int64) (string, error)
SignCleartextMessage(keyRing *crypto.KeyRing, text string) (string, error)
VerifyCleartextMessage(keyRing *crypto.KeyRing, armored string, verifyTime int64) (string, error)
```
- `EncryptAttachment`'s parameters are changed to messages.
```go
(pm *PmCrypto) EncryptAttachment(plainData []byte, fileName string, publicKey *KeyRing) (*models.EncryptedSplit, error)
// Is now
(keyRing *KeyRing) EncryptAttachment(message *PlainMessage, fileName string) (*PGPSplitMessage, error)
// As a helper was added:
EncryptSignAttachment(publicKey, privateKey string, passphrase []byte, fileName string, plainData []byte) (keyPacket, dataPacket, signature []byte, err error)
```
- `DecryptAttachment` has been moved to KeyRing struct (like `EncryptAttachment`)
```go
(pm *PmCrypto) DecryptAttachment(keyPacket []byte, dataPacket []byte, kr *KeyRing, passphrase string) ([]byte, error)
// Is now
(keyRing *KeyRing) DecryptAttachment(message *PGPSplitMessage) (*PlainMessage, error)
// As a helper was added:
DecryptVerifyAttachment(publicKey, privateKey string, passphrase, keyPacket, dataPacket []byte, armoredSignature string) (plainData []byte, err error)
```
- `EncryptAttachmentLowMemory` was renamed to `NewLowMemoryAttachmentProcessor`.
```go
(pm *PmCrypto) EncryptAttachmentLowMemory(estimatedSize int, fileName string, publicKey *KeyRing) (*AttachmentProcessor, error)
// Is now
(keyRing *KeyRing) NewLowMemoryAttachmentProcessor(estimatedSize int, fileName string) (*AttachmentProcessor, error)
```
- `SplitArmor` was renamed to `NewPGPSplitMessageFromArmored` and the model changed.
```go
SplitArmor(encrypted string) (*models.EncryptedSplit, error)
// Is now
NewPGPSplitMessageFromArmored(encrypted string) (*PGPSplitMessage, error)
```
- `DecryptAttKey` was renamed to `DecryptSessionKey` and the parameter keypacket changed to `[]byte` as it's binary, not armored.
```go
DecryptAttKey(kr *KeyRing, keyPacket string) (key *SymmetricKey, err error):
// Is now
(keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SessionKey, error)
```
- `SetKey` has been renamed to `EncryptSessionKey`, and the keypacket return value changed to `[]byte`.
```go
SetKey(kr *KeyRing, symKey *SymmetricKey) (packets string, err error):
// Is now
(keyRing *KeyRing) EncryptSessionKey(sessionSplit *SessionKey) ([]byte, error)
```
- `SeparateKeyAndData` has been split in two different function, as it did not only separate the data, but when provided a KeyRing decrypted the session key too.
```go
SeparateKeyAndData(kr *KeyRing, r io.Reader, estimatedLength int, garbageCollector int) (outSplit *models.EncryptedSplit, err error):
// Is now the conjunction of the following function:
// To separate key and data
(msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int) (outSplit *PGPSplitMessage, err error)
// To decrypt the SessionKey
(keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SessionKey, error)
```
- `EncryptSymmetric` has been changed, now the procedure is split in two parts: `Encrypt` and `SeparateKeyAndData`
```go
(kr *KeyRing) EncryptSymmetric(textToEncrypt string, canonicalizeText bool) (outSplit *models.EncryptedSplit, err error):
// Is now the conjunction of the following function:
// To encrypt
(keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error)
// To separate key and data
(msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int) (outSplit *PGPSplitMessage, err error)
```
- `GenerateKey`'s signature has been altered:
- It now returns a `Key` struct
- `userName` and `domain` are now joined in `email`, the `name` parameter was added (To emulate the old behaviour `name = email = userName + "@" + domain`).
```go
(pm *PmCrypto) GenerateKey(userName, domain, passphrase, keyType string, bits int) (string, error) :
// Is now
GenerateKey(name, email string, keyType string, bits int) (*Key, error)
// As a helper was added:
GenerateKey(name, email string, passphrase []byte, keyType string, bits int) (string, error)
```
- `GenerateRSAKeyWithPrimes`'s signature has been altered:
- It now returns a `Key` struct
- `userName` and `domain` are now joined in `email`, the `name` parameter was added (To emulate the old behaviour `name = email = userName + "@" + domain`).
```go
(pm *PmCrypto) GenerateRSAKeyWithPrimes(userName, domain, passphrase, keyType string, bits int, prime1, prime2, prime3, prime4 []byte) (string, error):
GenerateRSAKeyWithPrimes(name, email string, bits int, primeone, primetwo, primethree, primefour []byte,) (*Key, error)
```
- `Encrypt`, `EncryptArmored`, `EncryptString`, `EncryptMessage` functions have been changed to return and accept messages.
```go
(kr *KeyRing) Encrypt(w io.Writer, sign *KeyRing, filename string, canonicalizeText bool) (io.WriteCloser, error)
// Is now
(keyRing *KeyRing) Encrypt(message *PlainMessage, privateKey *KeyRing) (*PGPMessage, error)
// As a helpers were added:
EncryptMessageArmored(publicKey, plaintext string) (ciphertext string, err error)
EncryptSignMessageArmored(publicKey, privateKey string, passphrase []byte, plaintext string) (ciphertext string, err error) {
```
- `Decrypt`, `DecryptArmored`, `DecryptString`, `DecryptMessage`, `DecryptMessageVerify`, and `DecryptMessageStringKey` functions have been changed to return and accept messages (Same as Encrypt*).
If signature verification fails they will return a SignatureVerificationError.
```go
(kr *KeyRing) DecryptString(encrypted string) (SignedString, error)
// Is now
(keyRing *KeyRing) Decrypt(message *PGPMessage, verifyKey *KeyRing, verifyTime int64) (*PlainMessage, error)
// As a helpers were added:
DecryptMessageArmored(privateKey string, passphrase []byte, ciphertext string) (plaintext string, err error)
DecryptVerifyMessageArmored(publicKey, privateKey string, passphrase []byte, ciphertext string) (plaintext string, err error)
DecryptExplicitVerify(pgpMessage *crypto.PGPMessage, privateKeyRing, publicKeyRing *crypto.KeyRing, verifyTime int64) (*ExplicitVerifyMessage, error) {
```
- `DecryptStringIfNeeded` has been replaced with `IsPGPMessage` + `Decrypt*`.
```go
(kr *KeyRing) DecryptStringIfNeeded(data string) (decrypted string, err error)
// Is now the conjunction of the following function:
// To check if the data is a PGP message
IsPGPMessage(data string) bool
// To decrypt
(keyRing *KeyRing) Decrypt(message *PGPMessage, verifyKey *KeyRing, verifyTime int64) (*PlainMessage, error)
```
- `SignString` and `DetachedSign` have been replaced by signing methods.
```go
(kr *KeyRing) SignString(message string, canonicalizeText bool) (signed string, err error)
(kr *KeyRing) DetachedSign(w io.Writer, toSign io.Reader, canonicalizeText bool, armored bool)
// Are now
(keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error)
```
- `VerifyString` has been altered in the same way as as signing.
Returns SignatureVerificationError if the verification fails.
```go
(kr *KeyRing) VerifyString(message, signature string, sign *KeyRing) (err error)
// Is now
(keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) error
```
- `EncryptMessageWithPassword` uses AES-256 instead of AES-128, and has a new signature.
```go
(pm *PmCrypto) EncryptMessageWithPassword(plaintext string, password string) (string, error)
// Is now
EncryptMessageWithPassword(message *PlainMessage, password []byte) (*PGPMessage, error)
// As a helper was added:
EncryptMessageWithPassword(password []byte, plaintext string) (ciphertext string, err error)
```
- `DecryptMessageWithPassword` accepts all symmetric algorithms known to the lib, and has a new signature
```go
(pm *PmCrypto) DecryptMessageWithPassword(encrypted string, password string) (string, error)
// Is now
DecryptMessageWithPassword(message *PGPMessage, password []byte) (*PlainMessage, error)
// As a helper was added:
DecryptMessageWithPassword(password []byte, ciphertext string) (plaintext string, err error)
```
- `DecryptMIMEMessage` was moved to `KeyRing`, and the parameters transformed to messages
```go
(pm *PmCrypto) DecryptMIMEMessage(encryptedText string, verifierKey *KeyRing, privateKeyRing *KeyRing, passphrase string, callbacks MIMECallbacks, verifyTime int64):
// Is now
(keyRing *KeyRing) DecryptMIMEMessage(message *PGPMessage, verifyKey *KeyRing, callbacks MIMECallbacks, verifyTime int64)
```
- `RandomToken` now takes a size
```go
(pm *PmCrypto) RandomToken() ([]byte, error)
// Is now
RandomToken(size int) ([]byte, error)
```
- `GetSessionFromKeyPacket` was changed to `DecryptSessionKey`.
```go
(pm *PmCrypto) GetSessionFromKeyPacket(keyPackage []byte, privateKey *KeyRing, passphrase string) (*SymmetricKey, error)
// Is now
(keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SessionKey, error)
```
- `KeyPacketWithPublicKey` and `KeyPacketWithPublicKeyBin` have been merged to `EncryptSessionKey`.
```go
(pm *PmCrypto) KeyPacketWithPublicKey(sessionSplit *SymmetricKey, publicKey string) ([]byte, error)
(pm *PmCrypto) KeyPacketWithPublicKeyBin(sessionSplit *SymmetricKey, publicKey []byte) ([]byte, error)
(keyRing *KeyRing) EncryptSessionKey(sk *SessionKey) ([]byte, error)
```
- `GetSessionFromSymmetricPacket` was renamed to `DecryptSessionKeyWithPassword`.
```go
(pm *PmCrypto) GetSessionFromSymmetricPacket(keyPackage []byte, password string) (*SymmetricKey, error)
// Is now
DecryptSessionKeyWithPassword(keyPacket, password []byte) (*SessionKey, error)
```
- `SymmetricKeyPacketWithPassword` has been renamed to `EncryptSessionKeyWithPassword`
```go
(pm *PmCrypto) SymmetricKeyPacketWithPassword(sessionSplit *SymmetricKey, password string) ([]byte, error):
EncryptSessionKeyWithPassword(sk *SessionKey, password []byte]) ([]byte, error)
```
- `SignTextDetached` and `SignBinDetached` have been changed to `SignDetached`
```go
(pm *PmCrypto) SignTextDetached(plaintext string, privateKey *KeyRing, passphrase string, trim bool) (string, error)
(pm *PmCrypto) SignBinDetached(plainData []byte, privateKey *KeyRing, passphrase string) (string, error)
// Are now
(keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error)
// As helpers were added:
SignCleartextMessage(keyRing *crypto.KeyRing, text string) (string, error)
SignCleartextMessageArmored(privateKey string, passphrase []byte, text string) (string, error)
```
- `VerifyTextSignDetachedBinKey` and `VerifyBinSignDetachedBinKey` have been changed to `Verify`.
```go
(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)
// Are now
(keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) error
// As helpers were added:
VerifyCleartextMessage(keyRing *crypto.KeyRing, armored string, verifyTime int64) (string, error)
VerifyCleartextMessageArmored(publicKey, armored string, verifyTime int64) (string, error)
```
## [1.0.0] - 2019-05-15
Initial release, opensourcing of the internal library `PMCrypto`, and subsequent renaming to `gopenpgp`

View file

@ -1,17 +0,0 @@
# ProtonMail Bridge Changelog
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## [1.0.0] - 2019-03-07
* `master` refactor of master contains all changes from `oldMaster`
### Added
* `FirstKeyID` into `KeyRing` object to be able match salts
### Changed
* If statement re-factor following linter recommendations
* Constants rename following linter recomendations
* Comments following linter recomendations (not complete)
* Update the crypto and mime dependencies
* Error handling in `GetSessionFromKeyPaket`

View file

@ -1,474 +0,0 @@
# 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
}
```
### SignedString
```
// SignedString wraps string with Signature
type SignedString struct {
String string
Signed *Signature
}
```
is now
```
// ClearTextMessage, split signed clear text message container
type ClearTextMessage struct {
Data []byte
Signature []byte
}
```
## Dropped
### Signature
```
type Signature struct {
md *openpgp.MessageDetails
}
```
### pmKeyObject
```
type pmKeyObject struct {
ID string
Version int
Flags int
Fingerprint string
PublicKey string `json:",omitempty"`
PrivateKey string
Primary int
}
```
## 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
}
```
### SignatureVerificationError
```
// SignatureVerificationError is returned from Decrypt and VerifyDetached functions when signature verification fails
type SignatureVerificationError struct {
Status int
Message string
}
```
# API changes
## armor.go
### ReadClearSignedMessage
Moved to crypto package. Changed to return ClearTextMessage.
```
ReadClearSignedMessage(signedMessage string) (string, error):
* NewClearTextMessageFromArmored(signedMessage string) (*ClearTextMessage, error)
```
In addition, were added:
```
* NewClearTextMessage(data []byte, signature []byte) *ClearTextMessage
* (msg *ClearTextMessage) GetBinary() []byte
* (msg *ClearTextMessage) GetString() string
* (msg *ClearTextMessage) GetBinarySignature() []byte
* (msg *ClearTextMessage) GetArmored() (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):
* IsKeyExpired(publicKey []byte) (bool, error)
```
### IsKeyExpired
Renamed.
```
(pm *PmCrypto) IsKeyExpired(publicKey string) (bool, error):
* 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):
* 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) :
* GenerateKey(name, email, passphrase, keyType string, bits int) (string, error):
```
### UpdatePrivateKeyPassphrase
No change.
### CheckKey
Renamed.
```
(pm *PmCrypto) CheckKey(pubKey string) (string, error):
* 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*. If signature verification fails it will return a SignatureVerificationError.
```
(kr *KeyRing) DecryptString(encrypted string) (SignedString, error):
* (if binary data) func (keyRing *KeyRing) Decrypt(message *PGPMessage, verifyKey *KeyRing, verifyTime int64) (*PlainMessage, 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):
* 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. Returns SignatureVerificationError if the verification fails.
```
(kr *KeyRing) VerifyString(message, signature string, sign *KeyRing) (err error):
* (to verify) (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) 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
Dropped.
### 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, 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):
* 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) (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.

251
README.md
View file

@ -1,4 +1,4 @@
# GopenPGP
# GopenPGP V2
GopenPGP is a high-level OpenPGP library built on top of [a fork of the golang
crypto library](https://github.com/ProtonMail/crypto).
@ -10,7 +10,6 @@ crypto library](https://github.com/ProtonMail/crypto).
- [Download/Install](#downloadinstall)
- [Documentation](#documentation)
- [Using with Go Mobile](#using-with-go-mobile)
- [Other notes](#other-notes)
- [Full documentation](#full-documentation)
- [Examples](#examples)
- [Set up](#set-up)
@ -24,102 +23,89 @@ crypto library](https://github.com/ProtonMail/crypto).
<!-- /TOC -->
## Download/Install
### Vendored install
To use this library using [Go Modules](https://github.com/golang/go/wiki/Modules) just edit your
`go.mod` configuration to contain:
```gomod
require {
...
github.com/ProtonMail/gopenpgp/v2 v2.0.0
}
This package uses [Go Modules](https://github.com/golang/go/wiki/Modules), and
thus requires Go 1.11+. If you're also using Go Modules, simply import it and
start using it (see [Set up](#set-up)). If not, run:
```bash
go get github.com/ProtonMail/gopenpgp # or git clone this repository into the following path
cd $GOPATH/src/github.com/ProtonMail/gopenpgp
GO111MODULE=on go mod vendor
replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20191122234321-e77a1f03baa0
```
(After that, the code will also work in Go 1.10, but you need Go 1.11 for the `go mod` command.)
It can then be installed by running:
```sh
go mod vendor
```
Finally your software can include it in your software as follows:
```go
package main
import (
"fmt"
"github.com/ProtonMail/gopenpgp/v2/crypto"
)
func main() {
fmt.Println(crypto.GetUnixTime())
}
```
### Git-Clone install
To install for development mode, cloning the repository, it can be done in the following way:
```bash
cd $GOPATH
mkdir -p src/github.com/ProtonMail/
cd $GOPATH/src/github.com/ProtonMail/
git clone git@github.com:ProtonMail/gopenpgp.git
cd gopenpgp
ln -s . v2
go mod
```
## Documentation
A full overview of the API can be found here:
https://godoc.org/gopkg.in/ProtonMail/gopenpgp.v2/crypto
https://godoc.org/github.com/ProtonMail/gopenpgp/crypto
In this document examples are provided and the proper use of (almost) all functions is tested.
## Using with Go Mobile
Setup Go Mobile and build/bind the source code:
Go Mobile repo: https://github.com/golang/mobile
Go Mobile wiki: https://github.com/golang/go/wiki/Mobile
1. Install Go: `brew install go`
2. Install Gomobile: `go get -u golang.org/x/mobile/cmd/gomobile`
3. Install Gobind: `go install golang.org/x/mobile/cmd/gobind`
4. Install Android SDK and NDK using Android Studio
5. Set env: `export ANDROID_HOME="/AndroidSDK"` (path to your SDK)
6. Init gomobile: `gomobile init -ndk /AndroidSDK/ndk-bundle/` (path to your NDK)
7. Copy Go module dependencies to the vendor directory: `go mod vendor`
8. Build examples:
`gomobile build -target=android #or ios`
Bind examples:
`gomobile bind -target ios -o frameworks/name.framework`
`gomobile bind -target android`
The bind will create framework for iOS and jar&aar files for Android (x86_64 and ARM).
## Other notes
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
The use with gomobile is still to be documented
## Examples
### Set up
```go
import "github.com/ProtonMail/gopenpgp/crypto"
```
### Encrypt / Decrypt with password
```go
import "github.com/ProtonMail/gopenpgp/helper"
import "github.com/ProtonMail/gopenpgp/v2/helper"
const password = "my secret password"
const password = []byte("hunter2")
// Encrypt data with password
armor, err := helper.EncryptMessageWithToken(password, "my message")
armor, err := helper.EncryptMessageWithPassword(password, "my message")
// Decrypt data with password
message, err := helper.DecryptMessageWithToken(password, armor)
message, err := helper.DecryptMessageWithPassword(password, armor)
```
To use more encryption algorithms:
To encrypt binary data or use more advanced modes:
```go
import "github.com/ProtonMail/gopenpgp/constants"
import "github.com/ProtonMail/gopenpgp/helper"
import "github.com/ProtonMail/gopenpgp/v2/constants"
// Encrypt data with password
armor, err := helper.EncryptMessageWithTokenAlgo(password, "my message", constants.ThreeDES)
const password = []byte("hunter2")
// 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.NewSymmetricKeyFromToken("my secret password", constants.AES256)
var message = crypto.NewPlainMessage(data)
// Or
message = crypto.NewPlainMessageFromString(string)
// Encrypt data with password
encrypted, err := key.Encrypt(message)
encrypted, err := EncryptMessageWithPassword(message, password)
// Encrypted message in encrypted.GetBinary() or encrypted.GetArmored()
// Decrypt data with password
decrypted, err := key.Decrypt(password, encrypted)
decrypted, err := DecryptMessageWithPassword(encrypted, password)
//Original message in decrypted.GetBinary()
```
@ -127,7 +113,7 @@ decrypted, err := key.Decrypt(password, encrypted)
### Encrypt / Decrypt with PGP keys
```go
import "github.com/ProtonMail/gopenpgp/helper"
import "github.com/ProtonMail/gopenpgp/v2/helper"
// put keys in backtick (``) to avoid errors caused by spaces or tabs
const pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
@ -138,7 +124,7 @@ const privkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----` // encrypted private key
const passphrase = `the passphrase of the private key` // what the privKey is encrypted with
const passphrase = []byte(`the passphrase of the private key`) // Passphrase of the privKey
// encrypt message using public key
armor, err := helper.EncryptMessageArmored(pubkey, "plain text")
@ -162,40 +148,51 @@ decrypted, err := helper.DecryptVerifyMessageArmored(pubkey, privkey, passphrase
With binary data or advanced modes:
```go
// Keys initialization as before (omitted)
var binMessage = NewPlainMessage(data)
var binMessage = crypto.NewPlainMessage(data)
publicKeyObj, err := crypto.NewKeyFromArmored(publicKey)
publicKeyRing, err := crypto.NewKeyFromArmored(publicKeyObj)
publicKeyRing, err := crypto.BuildKeyRingArmored(publicKey)
privateKeyRing, err := crypto.BuildKeyRingArmored(privateKey)
err = privateKeyRing.UnlockWithPassphrase(passphrase)
pgpMessage, err := publicKeyRing.Encrypt(binMessage, privateKeyRing)
// Armored message in pgpMessage.GetArmored()
// pgpMessage can be obtained from NewPGPMessageFromArmored(ciphertext)
privateKeyObj, err := crypto.NewKeyFromArmored(privateKey)
unlockedKeyObj = privateKeyObj.Unlock(passphrase)
privateKeyRing, err := crypto.NewKeyRing(unlockedKeyObj)
message, err := privateKeyRing.Decrypt(pgpMessage, publicKeyRing, crypto.GetUnixTime())
privateKeyRing.ClearPrivateParams()
// Original data in message.GetString()
// `err` can be a SignatureVerificationError
```
### Generate key
### 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
const (
localPart = "name.surname"
domain = "example.com"
passphrase = "LongSecret"
name = "Max Mustermann"
email = "max.mustermann@example.com"
passphrase = []byte("LongSecret")
rsaBits = 2048
ecBits = 256
)
// RSA
rsaKey, err := crypto.GenerateKey(localPart, domain, passphrase, "rsa", rsaBits)
// RSA, string
rsaKey, err := helper.GenerateKey(name, email, passphrase, "rsa", rsaBits)
// Curve25519
ecKey, err := crypto.GenerateKey(localPart, domain, passphrase, "x25519", ecBits)
// Curve25519, string
ecKey, err := helper.GenerateKey(name, email, passphrase, "x25519", 0)
// RSA, Key struct
rsaKey, err := crypto.GenerateKey(name, email, "rsa", rsaBits)
// Curve25519, Key struct
ecKey, err := crypto.GenerateKey(name, email, "x25519", 0)
```
### Detached signatures for plain text messages
@ -206,14 +203,14 @@ The output is an armored signature.
```go
const privkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----` // encrypted private key
const passphrase = "LongSecret"
const trimNewlines = false
-----END PGP PRIVATE KEY BLOCK-----` // Encrypted private key
const passphrase = []byte("LongSecret") // Private key passphrase
var message = NewPlaintextMessage("Verified message")
var message = crypto.NewPlaintextMessage("Verified message")
signingKeyRing, err := crypto.BuildKeyRingArmored(privkey)
signingKeyRing.UnlockWithPassphrase(passphrase) // if private key is locked with passphrase
privateKeyObj, err := crypto.NewKeyFromArmored(privkey)
unlockedKeyObj = privateKeyObj.Unlock(passphrase)
signingKeyRing, err := crypto.NewKeyRing(unlockedKeyObj)
pgpSignature, err := signingKeyRing.SignDetached(message, trimNewlines)
@ -232,9 +229,11 @@ const signature = `-----BEGIN PGP SIGNATURE-----
...
-----END PGP SIGNATURE-----`
message := NewPlaintextMessage("Verified message")
pgpSignature, err := NewPGPSignatureFromArmored(signature)
signingKeyRing, err := crypto.BuildKeyRingArmored(pubkey)
message := crypto.NewPlaintextMessage("Verified message")
pgpSignature, err := crypto.NewPGPSignatureFromArmored(signature)
publicKeyObj, err := crypto.NewKeyFromArmored(pubkey)
signingKeyRing, err := crypto.NewKeyFromArmored(publicKeyObj)
err := signingKeyRing.VerifyDetached(message, pgpSignature, crypto.GetUnixTime())
@ -251,10 +250,11 @@ const privkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
-----END PGP PRIVATE KEY BLOCK-----` // encrypted private key
const passphrase = "LongSecret"
var message = NewPlainMessage(data)
var message = crypto.NewPlainMessage(data)
signingKeyRing, err := crypto.BuildKeyRingArmored(privkey)
signingKeyRing.UnlockWithPassphrase(passphrase) // if private key is locked with passphrase
privateKeyObj, err := crypto.NewKeyFromArmored(privkey)
unlockedKeyObj := privateKeyObj.Unlock(passphrase)
signingKeyRing, err := crypto.NewKeyRing(unlockedKeyObj)
pgpSignature, err := signingKeyRing.SignDetached(message)
@ -273,9 +273,11 @@ const signature = `-----BEGIN PGP SIGNATURE-----
...
-----END PGP SIGNATURE-----`
message := NewPlainMessage("Verified message")
pgpSignature, err := NewPGPSignatureFromArmored(signature)
signingKeyRing, err := crypto.BuildKeyRingArmored(pubkey)
message := crypto.NewPlainMessage("Verified message")
pgpSignature, err := crypto.NewPGPSignatureFromArmored(signature)
publicKeyObj, err := crypto.NewKeyFromArmored(pubkey)
signingKeyRing, err := crypto.NewKeyFromArmored(publicKeyObj)
err := signingKeyRing.VerifyDetached(message, pgpSignature, crypto.GetUnixTime())
@ -287,35 +289,56 @@ if err == nil {
### Cleartext signed messages
```go
// Keys initialization as before (omitted)
armored, err := SignCleartextMessageArmored(privateKey, passphrase, plaintext)
armored, err := helper.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 verifyTime = crypto.GetUnixTime()
verifiedPlainText, err := VerifyCleartextMessageArmored(publicKey, armored, verifyTime)
verifiedPlainText, err := helper.VerifyCleartextMessageArmored(publicKey, armored, crypto.GetUnixTime())
```
### Encrypting and decrypting session Keys
A session key can be generated, encrypted to a Asymmetric/Symmetric key packet and obtained from it
```go
// Keys initialization as before (omitted)
symmetricKey := &SymmetricKey{
Key: "RandomTokenabcdef",
Algo: constants.AES256,
}
sessionKey, err := crypto.GenerateSessionKey()
keyPacket, err := publicKey.EncryptSessionKey(symmetricKey)
keyPacket, err := publicKey.EncryptSessionKey(sessionKey)
keyPacketSymm, err := crypto.EncryptSessionKeyWithPassword(sessionKey, password)
```
`KeyPacket` is a `[]byte` containing the session key encrypted with the private key.
`KeyPacket` is a `[]byte` containing the session key encrypted with the private key or password.
```go
outputSymmetricKey, err := privateKey.DecryptSessionKey(keyPacket)
decodedKeyPacket, err := privateKey.DecryptSessionKey(keyPacket)
decodedSymmKeyPacket, err := crypto.DecryptSessionKeyWithPassword(keyPacketSymm, password)
```
`outputSymmetricKey` is an object of type `*SymmetricKey` that can be used to decrypt the correspondig message.
`decodedKeyPacket` and `decodedSymmKeyPacket` are objects of type `*SymmetricKey` that can
be used to decrypt the corresponding symmetrically encrypted data packets:
```go
var message = crypto.NewPlainMessage(data)
// Encrypt data with password
dataPacket, err := sessionKey.Encrypt(message)
// Decrypt data with password
decrypted, err := sessionKey.Decrypt(password, dataPacket)
//Original message in decrypted.GetBinary()
```
Note that it is not possible to process signatures when using data packets directly.
Joining the data packet and a key packet gives us a valid PGP message:
```go
pgpSplitMessage := NewPGPSplitMessage(keyPacket, dataPacket)
pgpMessage := pgpSplitMessage.GetPGPMessage()
// And vice-versa
newPGPSplitMessage, err := pgpMessage.SeparateKeyAndData()
// Key Packet is in newPGPSplitMessage.GetKeyPacket()
// Data Packet is in newPGPSplitMessage.GetDataPacket()
```

View file

@ -7,8 +7,8 @@ import (
"io"
"io/ioutil"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/ProtonMail/gopenpgp/v2/internal"
"golang.org/x/crypto/openpgp/armor"
)
@ -33,11 +33,12 @@ func ArmorWithType(input []byte, armorType string) (string, error) {
if err != nil {
return "", err
}
_, err = w.Write(input)
if err != nil {
if _, err = w.Write(input); err != nil {
return "", err
}
if err := w.Close(); err != nil {
return "", err
}
w.Close()
return b.String(), nil
}

View file

@ -1,24 +0,0 @@
#!/bin/bash
SCRIPT_LOCATION=$(cd $(dirname $0); echo $PWD)
OUTPUT_PATH="dist"
ANDROID_OUT=${OUTPUT_PATH}/"Android"
IOS_OUT=${OUTPUT_PATH}/"iOS"
mkdir -p $ANDROID_OUT
mkdir -p $IOS_OUT
printf "\e[0;32mStart Building iOS framework .. Location: ${IOS_OUT} \033[0m\n\n"
PACKAGE_PATH=github.com/ProtonMail/gopenpgp
gomobile bind -target ios -o ${IOS_OUT}/Crypto.framework $PACKAGE_PATH/crypto $PACKAGE_PATH/armor $PACKAGE_PATH/constants $PACKAGE_PATH/models $PACKAGE_PATH/subtle
printf "\e[0;32mStart Building Android lib .. Location: ${ANDROID_OUT} \033[0m\n\n"
gomobile bind -target android -javapkg com.proton.gopenpgp -o ${ANDROID_OUT}/gopenpgp.aar $PACKAGE_PATH/crypto $PACKAGE_PATH/armor $PACKAGE_PATH/constants $PACKAGE_PATH/models $PACKAGE_PATH/subtle
printf "\e[0;32mInstalling frameworks. \033[0m\n\n"
printf "\e[0;32mAll Done. \033[0m\n\n"

View file

@ -34,8 +34,14 @@ func (ap *AttachmentProcessor) Finish() (*PGPSplitMessage, error) {
if ap.err != nil {
return nil, ap.err
}
(*ap.w).Close()
(*ap.pipe).Close()
if err := (*ap.w).Close(); err != nil {
return nil, err
}
if err := (*ap.pipe).Close(); err != nil {
return nil, err
}
ap.done.Wait()
if ap.garbageCollector > 0 {
runtime.GC()

View file

@ -10,44 +10,46 @@ import (
// const testAttachmentEncrypted =
// `0ksB0fHC6Duezx/0TqpK/82HSl8+qCY0c2BCuyrSFoj6Dubd93T3//32jVYa624NYvfvxX+UxFKYKJxG09gFsU1IVc87cWvUgmUmgjU=`
var testAttachmentKey, _ = base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
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)
}
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(testKeyPacketsDecoded)
sessionKey, err := keyRingTestPrivate.DecryptSessionKey(testKeyPacketsDecoded)
if err != nil {
t.Fatal("Expected no error while decrypting KeyPacket, got:", err)
}
assert.Exactly(t, testSymmetricKey, symmetricKey)
assert.Exactly(t, testAttachmentKey, sessionKey.Key)
}
func TestAttachmentSetKey(t *testing.T) {
keyPackets, err := testPublicKeyRing.EncryptSessionKey(testSymmetricKey)
keyPackets, err := keyRingTestPublic.EncryptSessionKey(testSessionKey)
if err != nil {
t.Fatal("Expected no error while encrypting attachment key, got:", err)
}
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(keyPackets)
sessionKey, err := keyRingTestPrivate.DecryptSessionKey(keyPackets)
if err != nil {
t.Fatal("Expected no error while decrypting attachment key, got:", err)
}
assert.Exactly(t, testSymmetricKey, symmetricKey)
assert.Exactly(t, testSessionKey, sessionKey)
}
func TestAttachmentEncryptDecrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille."
var message = NewPlainMessage([]byte(testAttachmentCleartext))
encSplit, err := testPrivateKeyRing.EncryptAttachment(message, "s.txt")
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "s.txt")
if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err)
}
redecData, err := testPrivateKeyRing.DecryptAttachment(encSplit)
redecData, err := keyRingTestPrivate.DecryptAttachment(encSplit)
if err != nil {
t.Fatal("Expected no error while decrypting attachment, got:", err)
}
@ -59,14 +61,14 @@ func TestAttachmentEncrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille."
var message = NewPlainMessage([]byte(testAttachmentCleartext))
encSplit, err := testPrivateKeyRing.EncryptAttachment(message, "s.txt")
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "s.txt")
if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err)
}
pgpMessage := NewPGPMessage(encSplit.GetBinary())
redecData, err := testPrivateKeyRing.Decrypt(pgpMessage, nil, 0)
redecData, err := keyRingTestPrivate.Decrypt(pgpMessage, nil, 0)
if err != nil {
t.Fatal("Expected no error while decrypting attachment, got:", err)
}
@ -78,7 +80,7 @@ func TestAttachmentDecrypt(t *testing.T) {
var testAttachmentCleartext = "cc,\ndille."
var message = NewPlainMessage([]byte(testAttachmentCleartext))
encrypted, err := testPrivateKeyRing.Encrypt(message, nil)
encrypted, err := keyRingTestPrivate.Encrypt(message, nil)
if err != nil {
t.Fatal("Expected no error while encrypting attachment, got:", err)
}
@ -93,7 +95,7 @@ func TestAttachmentDecrypt(t *testing.T) {
t.Fatal("Expected no error while unarmoring, got:", err)
}
redecData, err := testPrivateKeyRing.DecryptAttachment(pgpSplitMessage)
redecData, err := keyRingTestPrivate.DecryptAttachment(pgpSplitMessage)
if err != nil {
t.Fatal("Expected no error while decrypting attachment, got:", err)
}

View file

@ -2,13 +2,21 @@ package crypto
import (
"io/ioutil"
"math/big"
"strings"
"testing"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/openpgp/ecdh"
"golang.org/x/crypto/rsa"
"github.com/stretchr/testify/assert"
)
var err error
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
func readTestFile(name string, trimNewlines bool) string {
data, err := ioutil.ReadFile("testdata/" + name)
data, err := ioutil.ReadFile("testdata/" + name) //nolint
if err != nil {
panic(err)
}
@ -17,3 +25,48 @@ func readTestFile(name string, trimNewlines bool) string {
}
return string(data)
}
func init() {
UpdateTime(testTime) // 2019-05-13T13:37:07+00:00
initGenerateKeys()
initArmoredKeys()
initKeyRings()
}
func assertBigIntCleared(t *testing.T, x *big.Int) {
w := x.Bits()
for k := range w {
assert.Exactly(t, big.Word(0x00), w[k])
}
}
func assertMemCleared(t *testing.T, b []byte) {
for k := range b {
assert.Exactly(t, uint8(0x00), b[k])
}
}
func assertRSACleared(t *testing.T, rsaPriv *rsa.PrivateKey) {
assertBigIntCleared(t, rsaPriv.D)
for idx := range rsaPriv.Primes {
assertBigIntCleared(t, rsaPriv.Primes[idx])
}
assertBigIntCleared(t, rsaPriv.Precomputed.Qinv)
assertBigIntCleared(t, rsaPriv.Precomputed.Dp)
assertBigIntCleared(t, rsaPriv.Precomputed.Dq)
for idx := range rsaPriv.Precomputed.CRTValues {
assertBigIntCleared(t, rsaPriv.Precomputed.CRTValues[idx].Exp)
assertBigIntCleared(t, rsaPriv.Precomputed.CRTValues[idx].Coeff)
assertBigIntCleared(t, rsaPriv.Precomputed.CRTValues[idx].R)
}
}
func assertEdDSACleared(t *testing.T, priv ed25519.PrivateKey) {
assertMemCleared(t, priv)
}
func assertECDHCleared(t *testing.T, priv *ecdh.PrivateKey) {
assertMemCleared(t, priv.D)
}

View file

@ -4,54 +4,353 @@ import (
"bytes"
"crypto"
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"strconv"
"strings"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/v2/armor"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
xarmor "golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
// IsKeyExpired checks whether the given (unarmored, binary) key is expired.
func IsKeyExpired(publicKey []byte) (bool, error) {
now := getNow()
pubKeyReader := bytes.NewReader(publicKey)
pubKeyEntries, err := openpgp.ReadKeyRing(pubKeyReader)
// Key contains a single private or public key
type Key struct {
// PGP entities in this keyring.
entity *openpgp.Entity
}
// --- Create Key object
// NewKeyFromArmoredReader reads an armored data into a key.
func NewKeyFromArmoredReader(r io.Reader) (key *Key, err error) {
key = &Key{}
err = key.readFrom(r, true)
if err != nil {
return true, err
return nil, err
}
for _, e := range pubKeyEntries {
if _, ok := e.EncryptionKey(now); ok {
return key, nil
}
// NewKeyFromReader reads an binary data into Key
func NewKeyFromReader(r io.Reader) (key *Key, err error) {
key = &Key{}
err = key.readFrom(r, false)
if err != nil {
return nil, err
}
return key, nil
}
// NewKey creates a new key from the first key in the unarmored binary data
func NewKey(binKeys []byte) (key *Key, err error) {
return NewKeyFromReader(bytes.NewReader(binKeys))
}
// NewKeyFromArmored creates a new key from the first key in an armored
func NewKeyFromArmored(armored string) (key *Key, err error) {
return NewKeyFromArmoredReader(strings.NewReader(armored))
}
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
func GenerateRSAKeyWithPrimes(
name, email string,
bits int,
primeone, primetwo, primethree, primefour []byte,
) (*Key, error) {
return generateKey(name, email, "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 GenerateKey(name, email string, keyType string, bits int) (*Key, error) {
return generateKey(name, email, keyType, bits, nil, nil, nil, nil)
}
// --- Operate on key
// Copy creates a deep copy of the key.
func (key *Key) Copy() (*Key, error) {
serialized, err := key.Serialize()
if err != nil {
return nil, err
}
return NewKey(serialized)
}
// Lock locks a copy of the key.
func (key *Key) Lock(passphrase []byte) (*Key, error) {
unlocked, err := key.IsUnlocked()
if err != nil {
return nil, err
}
if !unlocked {
return nil, errors.New("gopenpgp: key is not unlocked")
}
lockedKey, err := key.Copy()
if err != nil {
return nil, err
}
err = lockedKey.entity.PrivateKey.Encrypt(passphrase)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in locking key")
}
for _, sub := range lockedKey.entity.Subkeys {
if sub.PrivateKey != nil {
if err := sub.PrivateKey.Encrypt(passphrase); err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in locking sub key")
}
}
}
locked, err := lockedKey.IsLocked()
if err != nil {
return nil, err
}
if !locked {
return nil, errors.New("gopenpgp: unable to lock key")
}
return lockedKey, nil
}
// Unlock unlocks a copy of the key
func (key *Key) Unlock(passphrase []byte) (*Key, error) {
isLocked, err := key.IsLocked()
if err != nil {
return nil, err
}
if !isLocked {
return nil, errors.New("gopenpgp: key is not locked")
}
unlockedKey, err := key.Copy()
if err != nil {
return nil, err
}
err = unlockedKey.entity.PrivateKey.Decrypt(passphrase)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in unlocking key")
}
for _, sub := range unlockedKey.entity.Subkeys {
if sub.PrivateKey != nil {
if err := sub.PrivateKey.Decrypt(passphrase); err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in unlocking sub key")
}
}
}
isUnlocked, err := unlockedKey.IsUnlocked()
if err != nil {
return nil, err
}
if !isUnlocked {
return nil, errors.New("gopenpgp: unable to unlock key")
}
return unlockedKey, nil
}
// --- Export key
func (key *Key) Serialize() ([]byte, error) {
var buffer bytes.Buffer
var err error
if key.entity.PrivateKey == nil {
err = key.entity.Serialize(&buffer)
} else {
err = key.entity.SerializePrivateNoSign(&buffer, nil)
}
return buffer.Bytes(), err
}
func (key *Key) Armor() (string, error) {
serialized, err := key.Serialize()
if err != nil {
return "", err
}
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// GetArmoredPublicKey returns the armored public keys from this keyring.
func (key *Key) GetArmoredPublicKey() (s string, err error) {
var outBuf bytes.Buffer
aw, err := xarmor.Encode(&outBuf, openpgp.PublicKeyType, nil)
if err != nil {
return "", err
}
if err = key.entity.Serialize(aw); err != nil {
_ = aw.Close()
return "", err
}
err = aw.Close()
return outBuf.String(), err
}
// GetPublicKey returns the unarmored public keys from this keyring.
func (key *Key) GetPublicKey() (b []byte, err error) {
var outBuf bytes.Buffer
if err = key.entity.Serialize(&outBuf); err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
// --- Key object properties
// IsExpired checks whether the key is expired.
func (key *Key) IsExpired() bool {
_, ok := key.entity.EncryptionKey(getNow())
return !ok
}
// IsPrivate returns true if the key is private
func (key *Key) IsPrivate() bool {
return key.entity.PrivateKey != nil
}
// IsLocked checks if a private key is locked
func (key *Key) IsLocked() (bool, error) {
if key.entity.PrivateKey == nil {
return true, errors.New("gopenpgp: a public key cannot be locked")
}
for _, sub := range key.entity.Subkeys {
if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
return false, nil
}
}
return true, errors.New("keys expired")
return key.entity.PrivateKey.Encrypted, nil
}
// IsArmoredKeyExpired checks whether the given armored key is expired.
func IsArmoredKeyExpired(publicKey string) (bool, error) {
rawPubKey, err := armor.Unarmor(publicKey)
if err != nil {
return false, err
// IsUnlocked checks if a private key is unlocked
func (key *Key) IsUnlocked() (bool, error) {
if key.entity.PrivateKey == nil {
return true, errors.New("gopenpgp: a public key cannot be unlocked")
}
return IsKeyExpired(rawPubKey)
for _, sub := range key.entity.Subkeys {
if sub.PrivateKey != nil && sub.PrivateKey.Encrypted {
return false, nil
}
}
return !key.entity.PrivateKey.Encrypted, nil
}
// Check verifies if the public keys match the private key parameters by signing and verifying
func (key *Key) Check() (bool, error) {
var err error
testSign := bytes.Repeat([]byte{0x01}, 64)
testReader := bytes.NewReader(testSign)
if !key.IsPrivate() {
return false, errors.New("gopenpgp: can check only private key")
}
var signBuf bytes.Buffer
if err = openpgp.DetachSign(&signBuf, key.entity, testReader, nil); err != nil {
return false, errors.New("gopenpgp: unable to sign with key")
}
testReader = bytes.NewReader(testSign)
signer, err := openpgp.CheckDetachedSignature(openpgp.EntityList{key.entity}, testReader, &signBuf, nil)
if signer == nil || err != nil {
return false, nil
}
return true, nil
}
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
func (key *Key) PrintFingerprints() {
for _, subKey := range key.entity.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint[:]))
}
}
fmt.Println("PrimaryKey:" + hex.EncodeToString(key.entity.PrimaryKey.Fingerprint[:]))
}
// GetHexKeyID returns the key ID, hex encoded as a string
func (key *Key) GetHexKeyID() string {
return strconv.FormatUint(key.GetKeyID(), 16)
}
// GetKeyID returns the key ID, encoded as 8-byte int
func (key *Key) GetKeyID() uint64 {
return key.entity.PrimaryKey.KeyId
}
// GetFingerprint gets the fingerprint from the key
func (key *Key) GetFingerprint() string {
return hex.EncodeToString(key.entity.PrimaryKey.Fingerprint[:])
}
// --- Internal methods
// readFrom reads unarmored and armored keys from r and adds them to the keyring.
func (key *Key) readFrom(r io.Reader, armored bool) error {
var err error
var entities openpgp.EntityList
if armored {
entities, err = openpgp.ReadArmoredKeyRing(r)
} else {
entities, err = openpgp.ReadKeyRing(r)
}
if err != nil {
return err
}
if len(entities) > 1 {
return errors.New("gopenpgp: the key contains too many entities")
}
if len(entities) == 0 {
return errors.New("gopenpgp: the key does not contain any entity")
}
key.entity = entities[0]
return nil
}
func generateKey(
name, email, passphrase, keyType string,
name, email string,
keyType string,
bits int,
prime1, prime2, prime3, prime4 []byte,
) (string, error) {
if len(email) <= 0 {
return "", errors.New("invalid email format")
) (*Key, error) {
if len(email) == 0 {
return nil, errors.New("gopenpgp: invalid email format")
}
if len(name) <= 0 {
return "", errors.New("invalid name format")
if len(name) == 0 {
return nil, errors.New("gopenpgp: invalid name format")
}
comments := ""
@ -84,114 +383,16 @@ func generateKey(
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
if err != nil {
return "", err
return nil, err
}
if err := newEntity.SelfSign(nil); err != nil {
return "", err
return nil, err
}
rawPwd := []byte(passphrase)
if newEntity.PrivateKey != nil && !newEntity.PrivateKey.Encrypted {
if err := newEntity.PrivateKey.Encrypt(rawPwd); err != nil {
return "", err
}
if newEntity.PrivateKey == nil {
return nil, errors.New("gopenpgp: error in generating private key")
}
for _, sub := range newEntity.Subkeys {
if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
if err := sub.PrivateKey.Encrypt(rawPwd); err != nil {
return "", err
}
}
}
w := bytes.NewBuffer(nil)
if err := newEntity.SerializePrivateNoSign(w, nil); err != nil {
return "", err
}
serialized := w.Bytes()
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// GenerateRSAKeyWithPrimes generates a RSA key using the given primes.
func GenerateRSAKeyWithPrimes(
name, email, passphrase string,
bits int,
primeone, primetwo, primethree, primefour []byte,
) (string, error) {
return 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 GenerateKey(name, email, passphrase, keyType string, bits int) (string, error) {
return 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.
func UpdatePrivateKeyPassphrase(
privateKey string, oldPassphrase string, newPassphrase string,
) (string, error) {
privKey := strings.NewReader(privateKey)
privKeyEntries, err := openpgp.ReadArmoredKeyRing(privKey)
if err != nil {
return "", err
}
oldrawPwd := []byte(oldPassphrase)
newRawPwd := []byte(newPassphrase)
w := bytes.NewBuffer(nil)
for _, e := range privKeyEntries {
if e.PrivateKey != nil && e.PrivateKey.Encrypted {
if err := e.PrivateKey.Decrypt(oldrawPwd); err != nil {
return "", err
}
}
if e.PrivateKey != nil && !e.PrivateKey.Encrypted {
if err := e.PrivateKey.Encrypt(newRawPwd); err != nil {
return "", err
}
}
for _, sub := range e.Subkeys {
if sub.PrivateKey != nil && sub.PrivateKey.Encrypted {
if err := sub.PrivateKey.Decrypt(oldrawPwd); err != nil {
return "", err
}
}
if sub.PrivateKey != nil && !sub.PrivateKey.Encrypted {
if err := sub.PrivateKey.Encrypt(newRawPwd); err != nil {
return "", err
}
}
}
if err := e.SerializePrivateNoSign(w, nil); err != nil {
return "", err
}
}
serialized := w.Bytes()
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
}
// PrintFingerprints is a debug helper function that prints the key and subkey fingerprints.
func PrintFingerprints(pubKey string) (string, error) {
pubKeyReader := strings.NewReader(pubKey)
entries, err := openpgp.ReadArmoredKeyRing(pubKeyReader)
if err != nil {
return "", err
}
for _, e := range entries {
for _, subKey := range e.Subkeys {
if !subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications {
fmt.Println("SubKey:" + hex.EncodeToString(subKey.PublicKey.Fingerprint[:]))
}
}
fmt.Println("PrimaryKey:" + hex.EncodeToString(e.PrimaryKey.Fingerprint[:]))
}
return "", nil
return &Key{newEntity}, nil
}

128
crypto/key_clear.go Normal file
View file

@ -0,0 +1,128 @@
package crypto
import (
"crypto/dsa"
"crypto/ecdsa"
"errors"
"math/big"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/openpgp/ecdh"
"golang.org/x/crypto/openpgp/elgamal"
"golang.org/x/crypto/rsa"
)
func (sk *SessionKey) Clear() (ok bool) {
clearMem(sk.Key)
return true
}
func (key *Key) ClearPrivateParams() (ok bool) {
num := key.clearPrivateWithSubkeys()
key.entity.PrivateKey = nil
for k := range key.entity.Subkeys {
key.entity.Subkeys[k].PrivateKey = nil
}
return num > 0
}
func (key *Key) clearPrivateWithSubkeys() (num int) {
num = 0
if key.entity.PrivateKey != nil {
err := clearPrivateKey(key.entity.PrivateKey.PrivateKey)
if err == nil {
num++
}
}
for k := range key.entity.Subkeys {
if key.entity.Subkeys[k].PrivateKey != nil {
err := clearPrivateKey(key.entity.Subkeys[k].PrivateKey.PrivateKey)
if err == nil {
num++
}
}
}
return num
}
func clearPrivateKey(privateKey interface{}) error {
switch priv := privateKey.(type) {
case *rsa.PrivateKey:
return clearRSAPrivateKey(priv)
case *dsa.PrivateKey:
return clearDSAPrivateKey(priv)
case *elgamal.PrivateKey:
return clearElGamalPrivateKey(priv)
case *ecdsa.PrivateKey:
return clearECDSAPrivateKey(priv)
case ed25519.PrivateKey:
return clearEdDSAPrivateKey(priv)
case *ecdh.PrivateKey:
return clearECDHPrivateKey(priv)
default:
return errors.New("gopenpgp: unknown private key")
}
}
func clearBigInt(n *big.Int) {
w := n.Bits()
for k := range w {
w[k] = 0x00
}
}
func clearMem(w []byte) {
for k := range w {
w[k] = 0x00
}
}
func clearRSAPrivateKey(rsaPriv *rsa.PrivateKey) error {
clearBigInt(rsaPriv.D)
for idx := range rsaPriv.Primes {
clearBigInt(rsaPriv.Primes[idx])
}
clearBigInt(rsaPriv.Precomputed.Qinv)
clearBigInt(rsaPriv.Precomputed.Dp)
clearBigInt(rsaPriv.Precomputed.Dq)
for idx := range rsaPriv.Precomputed.CRTValues {
clearBigInt(rsaPriv.Precomputed.CRTValues[idx].Exp)
clearBigInt(rsaPriv.Precomputed.CRTValues[idx].Coeff)
clearBigInt(rsaPriv.Precomputed.CRTValues[idx].R)
}
return nil
}
func clearDSAPrivateKey(priv *dsa.PrivateKey) error {
clearBigInt(priv.X)
return nil
}
func clearElGamalPrivateKey(priv *elgamal.PrivateKey) error {
clearBigInt(priv.X)
return nil
}
func clearECDSAPrivateKey(priv *ecdsa.PrivateKey) error {
clearBigInt(priv.D)
return nil
}
func clearEdDSAPrivateKey(priv ed25519.PrivateKey) error {
clearMem(priv)
return nil
}
func clearECDHPrivateKey(priv *ecdh.PrivateKey) error {
clearMem(priv.D)
return nil
}

View file

@ -2,131 +2,209 @@ package crypto
import (
"encoding/base64"
"io/ioutil"
"regexp"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/rsa"
"github.com/stretchr/testify/assert"
)
const name = "Richard M. Stallman"
const domain = "rms@protonmail.ch"
const keyTestName = "Max Mustermann"
const keyTestDomain = "max.mustermann@protonmail.ch"
var passphrase = "I love GNU"
var rsaKey, ecKey, rsaPublicKey, ecPublicKey string
var keyTestPassphrase = []byte("I love GNU")
var (
rsaPrivateKeyRing *KeyRing
ecPrivateKeyRing *KeyRing
rsaPublicKeyRing *KeyRing
ecPublicKeyRing *KeyRing
keyTestArmoredRSA string
keyTestArmoredEC string
keyTestRSA *Key
keyTestEC *Key
)
func TestGenerateKeys(t *testing.T) {
rsaKey, err = GenerateKey(name, domain, passphrase, "rsa", 1024)
func initGenerateKeys() {
var err error
keyTestRSA, err = GenerateKey(keyTestName, keyTestDomain, "rsa", 1024)
if err != nil {
t.Fatal("Cannot generate RSA key:", err)
panic("Cannot generate RSA key:" + err.Error())
}
ecKey, err = GenerateKey(name, domain, passphrase, "x25519", 256)
keyTestEC, err = GenerateKey(keyTestName, keyTestDomain, "x25519", 256)
if err != nil {
t.Fatal("Cannot generate EC key:", err)
panic("Cannot generate EC key:" + err.Error())
}
}
func initArmoredKeys() {
var err error
lockedRSA, err := keyTestRSA.Lock(keyTestPassphrase)
if err != nil {
panic("Cannot lock RSA key:" + err.Error())
}
keyTestArmoredRSA, err = lockedRSA.Armor()
if err != nil {
panic("Cannot armor protected RSA key:" + err.Error())
}
lockedEC, err := keyTestEC.Lock(keyTestPassphrase)
if err != nil {
panic("Cannot lock EC key:" + err.Error())
}
keyTestArmoredEC, err = lockedEC.Armor()
if err != nil {
panic("Cannot armor protected EC key:" + err.Error())
}
}
func TestArmorKeys(t *testing.T) {
var err error
noPasswordRSA, err := keyTestRSA.Armor()
if err != nil {
t.Fatal("Cannot armor unprotected RSA key:" + err.Error())
}
noPasswordEC, err := keyTestEC.Armor()
if err != nil {
t.Fatal("Cannot armor unprotected EC key:" + err.Error())
}
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP PRIVATE KEY BLOCK-----.*-----END PGP PRIVATE KEY BLOCK-----$")
assert.Regexp(t, rTest, rsaKey)
assert.Regexp(t, rTest, ecKey)
assert.Regexp(t, rTest, noPasswordRSA)
assert.Regexp(t, rTest, noPasswordEC)
assert.Regexp(t, rTest, keyTestArmoredRSA)
assert.Regexp(t, rTest, keyTestArmoredEC)
}
func TestGenerateKeyRings(t *testing.T) {
rsaPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaKey))
func TestLockUnlockKeys(t *testing.T) {
testLockUnlockKey(t, keyTestArmoredRSA, keyTestPassphrase)
testLockUnlockKey(t, keyTestArmoredEC, keyTestPassphrase)
testLockUnlockKey(t, readTestFile("keyring_privateKey", false), testMailboxPassword)
publicKey, err := NewKeyFromArmored(readTestFile("keyring_publicKey", false))
if err != nil {
t.Fatal("Cannot read RSA key:", err)
t.Fatal("Cannot unarmor key:", err)
}
rsaPublicKey, err = rsaPrivateKeyRing.GetArmoredPublicKey()
if err != nil {
t.Fatal("Cannot extract RSA public key:", err)
_, err = publicKey.IsLocked()
if err == nil {
t.Fatal("Should not be able to check locked on public key:")
}
rsaPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaPublicKey))
if err != nil {
t.Fatal("Cannot read RSA public key:", err)
_, err = publicKey.IsUnlocked()
if err == nil {
t.Fatal("Should not be able to check unlocked on public key:")
}
err = rsaPrivateKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt RSA key:", err)
_, err = publicKey.Unlock(testMailboxPassword)
if err == nil {
t.Fatal("Should not be able to unlock public key:")
}
ecPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(ecKey))
if err != nil {
t.Fatal("Cannot read EC key:", err)
}
ecPublicKey, err = ecPrivateKeyRing.GetArmoredPublicKey()
if err != nil {
t.Fatal("Cannot extract EC public key:", err)
}
ecPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(ecPublicKey))
if err != nil {
t.Fatal("Cannot read EC public key:", err)
}
err = ecPrivateKeyRing.UnlockWithPassphrase(passphrase)
if err != nil {
t.Fatal("Cannot decrypt EC key:", err)
_, err = publicKey.Lock(keyTestPassphrase)
if err == nil {
t.Fatal("Should not be able to lock public key:")
}
}
func TestUpdatePrivateKeysPassphrase(t *testing.T) {
newPassphrase := "I like GNU"
rsaKey, err = UpdatePrivateKeyPassphrase(rsaKey, passphrase, newPassphrase)
func testLockUnlockKey(t *testing.T, armoredKey string, pass []byte) {
var err error
lockedKey, err := NewKeyFromArmored(armoredKey)
if err != nil {
t.Fatal("Error in changing RSA key's passphrase:", err)
t.Fatal("Cannot unarmor key:", err)
}
ecKey, err = UpdatePrivateKeyPassphrase(ecKey, passphrase, newPassphrase)
// Check if key is locked
locked, err := lockedKey.IsLocked()
if err != nil {
t.Fatal("Error in changing EC key's passphrase:", err)
t.Fatal("Cannot check if key is unlocked:", err)
}
passphrase = newPassphrase
if !locked {
t.Fatal("Key should be fully locked")
}
unlockedKey, err := lockedKey.Unlock(pass)
if err != nil {
t.Fatal("Cannot unlock key:", err)
}
// Check if key was successfully unlocked
unlocked, err := unlockedKey.IsUnlocked()
if err != nil {
t.Fatal("Cannot check if key is unlocked:", err)
}
if !unlocked {
t.Fatal("Key should be fully unlocked")
}
// Check if action is performed on copy
locked, err = lockedKey.IsLocked()
if err != nil {
t.Fatal("Cannot check if key is unlocked:", err)
}
if !locked {
t.Fatal("Key should be fully locked")
}
// re-lock key
relockedKey, err := unlockedKey.Lock(keyTestPassphrase)
if err != nil {
t.Fatal("Cannot lock key:", err)
}
// Check if key was successfully locked
relocked, err := relockedKey.IsLocked()
if err != nil {
t.Fatal("Cannot check if key is unlocked:", err)
}
if !relocked {
t.Fatal("Key should be fully locked")
}
// Check if action is performed on copy
unlocked, err = unlockedKey.IsUnlocked()
if err != nil {
t.Fatal("Cannot check if key is unlocked:", err)
}
if !unlocked {
t.Fatal("Key should be fully unlocked")
}
}
func ExamplePrintFingerprints() {
_, _ = PrintFingerprints(readTestFile("keyring_publicKey", false))
func ExampleKey_PrintFingerprints() {
keyringKey, _ := NewKeyFromArmored(readTestFile("keyring_publicKey", false))
keyringKey.PrintFingerprints()
// Output:
// SubKey:37e4bcf09b36e34012d10c0247dc67b5cb8267f6
// PrimaryKey:6e8ba229b0cccaf6962f97953eb6259edf21df24
}
func TestIsArmoredKeyExpired(t *testing.T) {
rsaRes, err := IsArmoredKeyExpired(rsaPublicKey)
func TestIsExpired(t *testing.T) {
assert.Exactly(t, false, keyTestRSA.IsExpired())
assert.Exactly(t, false, keyTestEC.IsExpired())
expiredKey, err := NewKeyFromArmored(readTestFile("key_expiredKey", false))
if err != nil {
t.Fatal("Error in checking expiration of RSA key:", err)
t.Fatal("Cannot unarmor expired key:", err)
}
ecRes, err := IsArmoredKeyExpired(ecPublicKey)
futureKey, err := NewKeyFromArmored(readTestFile("key_futureKey", false))
if err != nil {
t.Fatal("Error in checking expiration of EC key:", err)
t.Fatal("Cannot unarmor future key:", err)
}
assert.Exactly(t, false, rsaRes)
assert.Exactly(t, false, ecRes)
UpdateTime(1557754627) // 2019-05-13T13:37:07+00:00
expRes, expErr := IsArmoredKeyExpired(readTestFile("key_expiredKey", false))
futureRes, futureErr := 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")
assert.Exactly(t, true, expiredKey.IsExpired())
assert.Exactly(t, true, futureKey.IsExpired())
}
func TestGenerateKeyWithPrimes(t *testing.T) {
@ -139,24 +217,96 @@ func TestGenerateKeyWithPrimes(t *testing.T) {
prime4, _ := base64.StdEncoding.DecodeString(
"58UEDXTX29Q9JqvuE3Tn+Qj275CXBnJbA8IVM4d05cPYAZ6H43bPN01pbJqJTJw/cuFxs+8C+HNw3/MGQOExqw==")
staticRsaKey, err := GenerateRSAKeyWithPrimes(name, domain, passphrase, 1024, prime1, prime2, prime3, prime4)
staticRsaKey, err := GenerateRSAKeyWithPrimes(keyTestName, keyTestDomain, 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)
t.Fatal("Cannot generate RSA key with primes:", 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())
pk := staticRsaKey.entity.PrivateKey.PrivateKey.(*rsa.PrivateKey)
assert.Exactly(t, prime1, pk.Primes[0].Bytes())
assert.Exactly(t, prime2, pk.Primes[1].Bytes())
}
func TestCheckIntegrity(t *testing.T) {
isVerified, err := keyTestRSA.Check()
if err != nil {
t.Fatal("Expected no error while checking correct passphrase, got:", err)
}
assert.Exactly(t, true, isVerified)
}
func TestFailCheckIntegrity(t *testing.T) {
// This test is done with ECC because in an RSA key we would need to replace the primes, but maintaining the moduli,
// that is a private struct element.
k1, _ := GenerateKey(keyTestName, keyTestDomain, "x25519", 256)
k2, _ := GenerateKey(keyTestName, keyTestDomain, "x25519", 256)
k1.entity.PrivateKey.PrivateKey = k2.entity.PrivateKey.PrivateKey // Swap private keys
k3, err := k1.Copy()
if err != nil {
t.Fatal("Expected no error while locking keyring kr3, got:", err)
}
isVerified, err := k3.Check()
if err != nil {
t.Fatal("Expected no error while checking correct passphrase, got:", err)
}
assert.Exactly(t, false, isVerified)
}
func TestArmorPublicKey(t *testing.T) {
publicKey, err := keyTestRSA.GetPublicKey()
if err != nil {
t.Fatal("Expected no error while obtaining public key, got:", err)
}
decodedKey, err := NewKey(publicKey)
if err != nil {
t.Fatal("Expected no error while creating public key ring, got:", err)
}
privateFingerprint := keyTestRSA.GetFingerprint()
publicFingerprint := decodedKey.GetFingerprint()
assert.Exactly(t, privateFingerprint, publicFingerprint)
}
func TestGetArmoredPublicKey(t *testing.T) {
privateKey, err := NewKeyFromArmored(readTestFile("keyring_privateKey", false))
if err != nil {
t.Fatal("Expected no error while unarmouring private key, got:", err)
}
s, err := privateKey.GetArmoredPublicKey()
if err != nil {
t.Fatal("Expected no error while getting armored public key, got:", err)
}
// Decode armored keys
block, err := armor.Decode(strings.NewReader(s))
if err != nil {
t.Fatal("Expected no error while decoding armored public key, got:", err)
}
expected, err := armor.Decode(strings.NewReader(readTestFile("keyring_publicKey", false)))
if err != nil {
t.Fatal("Expected no error while decoding expected armored public key, got:", err)
}
assert.Exactly(t, expected.Type, block.Type)
b, err := ioutil.ReadAll(block.Body)
if err != nil {
t.Fatal("Expected no error while reading armored public key body, got:", err)
}
eb, err := ioutil.ReadAll(expected.Body)
if err != nil {
t.Fatal("Expected no error while reading expected armored public key body, got:", err)
}
assert.Exactly(t, eb, b)
}

View file

@ -2,19 +2,11 @@ package crypto
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"encoding/hex"
"errors"
"io"
"time"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
xrsa "golang.org/x/crypto/rsa"
armorUtils "github.com/ProtonMail/gopenpgp/armor"
)
// KeyRing contains multiple private and public keys.
@ -32,13 +24,52 @@ type Identity struct {
Email string
}
// GetEntities returns openpgp entities contained in this KeyRing.
func (keyRing *KeyRing) GetEntities() openpgp.EntityList {
return keyRing.entities
// --- New keyrings
// NewKeyRing creates a new KeyRing, empty if key is nil
func NewKeyRing(key *Key) (*KeyRing, error) {
keyRing := &KeyRing{}
var err error
if key != nil {
err = keyRing.AddKey(key)
}
return keyRing, err
}
// GetSigningEntity returns first private unlocked signing entity from keyring.
func (keyRing *KeyRing) GetSigningEntity() (*openpgp.Entity, error) {
// --- Add keys to keyring
func (keyRing *KeyRing) AddKey(key *Key) error {
if key.IsPrivate() {
unlocked, err := key.IsUnlocked()
if err != nil || !unlocked {
return errors.New("gopenpgp: unable to add locked key to a keyring")
}
}
keyRing.appendKey(key)
return nil
}
// --- Extract keys from keyring
// GetKeys returns openpgp keys contained in this KeyRing.
func (keyRing *KeyRing) GetKeys() []*Key {
keys := make([]*Key, keyRing.CountEntities())
for i, entity := range keyRing.entities {
keys[i] = &Key{entity}
}
return keys
}
// GetKey returns the n-th openpgp key contained in this KeyRing.
func (keyRing *KeyRing) GetKey(n int) (*Key, error) {
if n >= keyRing.CountEntities() {
return nil, errors.New("gopenpgp: out of bound when fetching key")
}
return &Key{keyRing.entities[n]}, nil
}
// getSigningEntity returns first private unlocked signing entity from keyring.
func (keyRing *KeyRing) getSigningEntity() (*openpgp.Entity, error) {
var signEntity *openpgp.Entity
for _, e := range keyRing.entities {
@ -58,218 +89,20 @@ func (keyRing *KeyRing) GetSigningEntity() (*openpgp.Entity, error) {
return signEntity, nil
}
// 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 (keyRing *KeyRing) Unlock(passphrase []byte) error {
// Build a list of keys to decrypt
var keys []*packet.PrivateKey
for _, e := range keyRing.entities {
// Entity.PrivateKey must be a signing key
if e.PrivateKey != nil {
keys = append(keys, e.PrivateKey)
}
// --- Extract info from key
// Entity.Subkeys can be used for encryption
for _, subKey := range e.Subkeys {
if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage ||
subKey.Sig.FlagEncryptCommunications) {
keys = append(keys, subKey.PrivateKey)
}
}
}
if len(keys) == 0 {
return errors.New("gopenpgp: cannot unlock key ring, no private key available")
}
var err error
var n int
for _, key := range keys {
if !key.Encrypted {
continue // Key already decrypted
}
if err = key.Decrypt(passphrase); err == nil {
n++
}
}
if n == 0 {
return err
}
return nil
// CountEntities returns the number of entities in the keyring
func (keyRing *KeyRing) CountEntities() int {
return len(keyRing.entities)
}
// 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 (keyRing *KeyRing) WriteArmoredPublicKey(w io.Writer) (err error) {
aw, err := armor.Encode(w, openpgp.PublicKeyType, nil)
if err != nil {
return
}
for _, e := range keyRing.entities {
if err = e.Serialize(aw); err != nil {
aw.Close()
return
}
}
err = aw.Close()
return
}
// GetArmoredPublicKey returns the armored public keys from this keyring.
func (keyRing *KeyRing) GetArmoredPublicKey() (s string, err error) {
b := &bytes.Buffer{}
if err = keyRing.WriteArmoredPublicKey(b); err != nil {
return
}
s = b.String()
return
}
// WritePublicKey outputs unarmored public keys from the keyring to w.
func (keyRing *KeyRing) WritePublicKey(w io.Writer) (err error) {
for _, e := range keyRing.entities {
if err = e.Serialize(w); err != nil {
return
}
}
return
}
// GetPublicKey returns the unarmored public keys from this keyring.
func (keyRing *KeyRing) GetPublicKey() (b []byte, err error) {
var outBuf bytes.Buffer
if err = keyRing.WritePublicKey(&outBuf); err != nil {
return
}
b = outBuf.Bytes()
return
}
// GetFingerprint gets the fingerprint from the keyring.
func (keyRing *KeyRing) GetFingerprint() (string, error) {
for _, entity := range keyRing.entities {
fp := entity.PrimaryKey.Fingerprint
return hex.EncodeToString(fp[:]), nil
}
return "", errors.New("can't find public key")
}
// CheckPassphrase checks if private key passphrase is correct for every sub key.
func (keyRing *KeyRing) CheckPassphrase(passphrase string) bool {
var keys []*packet.PrivateKey
for _, entity := range keyRing.entities {
keys = append(keys, entity.PrivateKey)
}
var decryptError error
var n int
for _, key := range keys {
if !key.Encrypted {
continue // Key already decrypted
}
if decryptError = key.Decrypt([]byte(passphrase)); decryptError == nil {
n++
}
}
return n != 0
}
// ReadFrom reads unarmored and armored keys from r and adds them to the keyring.
func (keyRing *KeyRing) ReadFrom(r io.Reader, armored bool) error {
var err error
var entities openpgp.EntityList
if armored {
entities, err = openpgp.ReadArmoredKeyRing(r)
} else {
entities, err = openpgp.ReadKeyRing(r)
}
for _, entity := range entities {
if entity.PrivateKey != nil {
switch entity.PrivateKey.PrivateKey.(type) {
// TODO: type mismatch after crypto lib update, fix this:
case *rsa.PrivateKey:
entity.PrimaryKey = packet.NewRSAPublicKey(
time.Now(),
entity.PrivateKey.PrivateKey.(*rsa.PrivateKey).Public().(*xrsa.PublicKey))
case *ecdsa.PrivateKey:
entity.PrimaryKey = packet.NewECDSAPublicKey(
time.Now(),
entity.PrivateKey.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey))
}
}
for _, subkey := range entity.Subkeys {
if subkey.PrivateKey != nil {
switch subkey.PrivateKey.PrivateKey.(type) {
case *rsa.PrivateKey:
subkey.PublicKey = packet.NewRSAPublicKey(
time.Now(),
subkey.PrivateKey.PrivateKey.(*rsa.PrivateKey).Public().(*xrsa.PublicKey))
case *ecdsa.PrivateKey:
subkey.PublicKey = packet.NewECDSAPublicKey(
time.Now(),
subkey.PrivateKey.PrivateKey.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey))
}
}
}
}
if err != nil {
return err
}
if len(entities) == 0 {
return errors.New("gopenpgp: key ring doesn't contain any key")
}
keyRing.entities = append(keyRing.entities, entities...)
return nil
}
// BuildKeyRing reads keyring from binary data
func BuildKeyRing(binKeys []byte) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
entriesReader := bytes.NewReader(binKeys)
err = keyRing.ReadFrom(entriesReader, false)
return
}
// BuildKeyRingNoError does not return error on fail
func BuildKeyRingNoError(binKeys []byte) (keyRing *KeyRing) {
keyRing, _ = BuildKeyRing(binKeys)
return
}
// BuildKeyRingArmored reads armored string and returns keyring
func BuildKeyRingArmored(key string) (keyRing *KeyRing, err error) {
keyRaw, err := armorUtils.Unarmor(key)
if err != nil {
return nil, err
}
keyReader := bytes.NewReader(keyRaw)
keyEntries, err := openpgp.ReadKeyRing(keyReader)
return &KeyRing{entities: keyEntries}, err
// CountDecryptionEntities returns the number of entities in the keyring
func (keyRing *KeyRing) CountDecryptionEntities() int {
return len(keyRing.entities.DecryptionKeys())
}
// Identities returns the list of identities associated with this key ring.
func (keyRing *KeyRing) Identities() []*Identity {
func (keyRing *KeyRing) GetIdentities() []*Identity {
var identities []*Identity
for _, e := range keyRing.entities {
for _, id := range e.Identities {
@ -282,28 +115,16 @@ func (keyRing *KeyRing) Identities() []*Identity {
return identities
}
// KeyIds returns array of IDs of keys in this KeyRing.
func (keyRing *KeyRing) KeyIds() []uint64 {
var res []uint64
for _, e := range keyRing.entities {
res = append(res, e.PrimaryKey.KeyId)
// GetKeyIDs returns array of IDs of keys in this KeyRing.
func (keyRing *KeyRing) GetKeyIDs() []uint64 {
var res = make([]uint64, len(keyRing.entities))
for id, e := range keyRing.entities {
res[id] = e.PrimaryKey.KeyId
}
return res
}
// ReadArmoredKeyRing reads an armored data into keyring.
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) (keyRing *KeyRing, err error) {
keyRing = &KeyRing{}
err = keyRing.ReadFrom(r, false)
return
}
// --- Filter keyrings
// FilterExpiredKeys takes a given KeyRing list and it returns only those
// KeyRings which contain at least, one unexpired Key. It returns only unexpired
@ -316,7 +137,7 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
for _, contactKeyRing := range contactKeys {
keyRingHasUnexpiredEntity := false
keyRingHasTotallyExpiredEntity := false
for _, entity := range contactKeyRing.GetEntities() {
for _, entity := range contactKeyRing.entities {
hasExpired := false
hasUnexpired := false
for _, subkey := range entity.Subkeys {
@ -333,7 +154,12 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
}
}
if keyRingHasUnexpiredEntity {
filteredKeys = append(filteredKeys, contactKeyRing)
keyRingCopy, err := contactKeyRing.Copy()
if err != nil {
return nil, err
}
filteredKeys = append(filteredKeys, keyRingCopy)
} else if keyRingHasTotallyExpiredEntity {
hasExpiredEntity = true
}
@ -347,12 +173,57 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
}
// FirstKey returns a KeyRing with only the first key of the original one
func (keyRing *KeyRing) FirstKey() *KeyRing {
func (keyRing *KeyRing) FirstKey() (*KeyRing, error) {
if len(keyRing.entities) == 0 {
return nil
return nil, errors.New("gopenpgp: No key available in this keyring")
}
newKeyRing := &KeyRing{}
newKeyRing.entities = keyRing.entities[:1]
return newKeyRing
return newKeyRing.Copy()
}
// Copy creates a deep copy of the keyring
func (keyRing *KeyRing) Copy() (*KeyRing, error) {
newKeyRing := &KeyRing{}
entities := make([]*openpgp.Entity, len(keyRing.entities))
for id, entity := range keyRing.entities {
var buffer bytes.Buffer
var err error
if entity.PrivateKey == nil {
err = entity.Serialize(&buffer)
} else {
err = entity.SerializePrivateNoSign(&buffer, nil)
}
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to copy key: error in serializing entity")
}
bt := buffer.Bytes()
entities[id], err = openpgp.ReadEntity(packet.NewReader(bytes.NewReader(bt)))
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to copy key: error in reading entity")
}
}
newKeyRing.entities = entities
newKeyRing.FirstKeyID = keyRing.FirstKeyID
return newKeyRing, nil
}
func (keyRing *KeyRing) ClearPrivateParams() {
for _, key := range keyRing.GetKeys() {
key.ClearPrivateParams()
}
}
// INTERNAL FUNCTIONS
// append appends a key to the keyring
func (keyRing *KeyRing) appendKey(key *Key) {
keyRing.entities = append(keyRing.entities, key.entity)
}

View file

@ -39,7 +39,7 @@ func (keyRing *KeyRing) Decrypt(
// SignDetached generates and returns a PGPSignature for a given PlainMessage
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
signEntity, err := keyRing.GetSigningEntity()
signEntity, err := keyRing.getSigningEntity()
if err != nil {
return nil, err
}
@ -56,11 +56,9 @@ func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, erro
// VerifyDetached verifies a PlainMessage with embedded a PGPSignature
// and returns a SignatureVerificationError if fails
func (keyRing *KeyRing) VerifyDetached(
message *PlainMessage, signature *PGPSignature, verifyTime int64,
) (error) {
func (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) error {
return verifySignature(
keyRing.GetEntities(),
keyRing.entities,
message.NewReader(),
signature.GetBinary(),
verifyTime,
@ -78,7 +76,7 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
if privateKey != nil && len(privateKey.entities) > 0 {
var err error
signEntity, err = privateKey.GetSigningEntity()
signEntity, err = privateKey.getSigningEntity()
if err != nil {
return nil, err
}
@ -101,8 +99,11 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
}
_, err = encryptWriter.Write(data)
encryptWriter.Close()
if err != nil {
return nil, err
}
err = encryptWriter.Close()
if err != nil {
return nil, err
}
@ -114,11 +115,11 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
func asymmetricDecrypt(
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
) (plaintext []byte, err error) {
privKeyEntries := privateKey.GetEntities()
privKeyEntries := privateKey.entities
var additionalEntries openpgp.EntityList
if verifyKey != nil {
additionalEntries = verifyKey.GetEntities()
additionalEntries = verifyKey.entities
}
if additionalEntries != nil {

View file

@ -2,31 +2,15 @@ package crypto
import (
"bytes"
"errors"
"fmt"
"io"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp/packet"
)
// RandomToken generated a random token of the same size of the keysize of the default cipher.
func RandomToken() ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
return RandomTokenSize(config.DefaultCipher.KeySize())
}
// RandomTokenSize generates a random token with the specified key size
func RandomTokenSize(size int) ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
symKey := make([]byte, size)
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
return nil, err
}
return symKey, nil
}
// DecryptSessionKey returns the decrypted session key from a binary encrypted session key packet.
func (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SymmetricKey, error) {
func (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SessionKey, error) {
keyReader := bytes.NewReader(keyPacket)
packets := packet.NewReader(keyReader)
@ -57,18 +41,21 @@ func (keyRing *KeyRing) DecryptSessionKey(keyPacket []byte) (*SymmetricKey, erro
return nil, errors.New("gopenpgp: unable to decrypt session key")
}
return newSymmetricKeyFromEncrypted(ek)
return newSessionKeyFromEncrypted(ek)
}
// EncryptSessionKey encrypts the session key with the unarmored
// publicKey and returns a binary public-key encrypted session key packet.
func (keyRing *KeyRing) EncryptSessionKey(sessionSplit *SymmetricKey) ([]byte, error) {
func (keyRing *KeyRing) EncryptSessionKey(sk *SessionKey) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf := sessionSplit.GetCipherFunc()
cf, err := sk.GetCipherFunc()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key")
}
var pub *packet.PublicKey
for _, e := range keyRing.GetEntities() {
for _, e := range keyRing.entities {
if encryptionKey, ok := e.EncryptionKey(getNow()); ok {
pub = encryptionKey.PublicKey
break
@ -78,7 +65,7 @@ func (keyRing *KeyRing) EncryptSessionKey(sessionSplit *SymmetricKey) ([]byte, e
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, sk.Key, nil); err != nil {
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
return nil, err
}

View file

@ -1,38 +1,27 @@
package crypto
import (
"encoding/base64"
"io/ioutil"
"strings"
"testing"
"golang.org/x/crypto/openpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/openpgp/ecdh"
"golang.org/x/crypto/rsa"
)
var decodedSymmetricKey, _ = base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var testSymmetricKey = &SymmetricKey{
Key: decodedSymmetricKey,
Algo: constants.AES256,
}
var testWrongSymmetricKey = &SymmetricKey{
Key: []byte("WrongPass"),
Algo: constants.AES256,
}
var testSymmetricKey []byte
// Corresponding key in testdata/keyring_privateKey
const testMailboxPassword = "apple"
var testMailboxPassword = []byte("apple")
// Corresponding key in testdata/keyring_privateKeyLegacy
// const testMailboxPasswordLegacy = "123"
// const testMailboxPasswordLegacy = [][]byte{ []byte("123") }
var (
testPrivateKeyRing *KeyRing
testPublicKeyRing *KeyRing
keyRingTestPrivate *KeyRing
keyRingTestPublic *KeyRing
keyRingTestMultiple *KeyRing
)
var testIdentity = &Identity{
@ -40,75 +29,83 @@ var testIdentity = &Identity{
Email: "",
}
func init() {
func initKeyRings() {
var err error
testPrivateKeyRing, err = BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
testSymmetricKey, err = RandomToken(32)
if err != nil {
panic(err)
panic("Expected no error while generating random token, got:" + err.Error())
}
testPublicKeyRing, err = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
privateKey, err := NewKeyFromArmored(readTestFile("keyring_privateKey", false))
if err != nil {
panic(err)
panic("Expected no error while unarmoring private key, got:" + err.Error())
}
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
panic(err)
}
}
func TestKeyRing_ArmoredPublicKeyString(t *testing.T) {
s, err := testPrivateKeyRing.GetArmoredPublicKey()
if err != nil {
t.Fatal("Expected no error while getting armored public key, got:", err)
keyRingTestPrivate, err = NewKeyRing(privateKey)
if err == nil {
panic("Able to create a keyring with a locked key")
}
// Decode armored keys
block, err := armor.Decode(strings.NewReader(s))
unlockedKey, err := privateKey.Unlock(testMailboxPassword)
if err != nil {
t.Fatal("Expected no error while decoding armored public key, got:", err)
panic("Expected no error while unlocking private key, got:" + err.Error())
}
expected, err := armor.Decode(strings.NewReader(readTestFile("keyring_publicKey", false)))
keyRingTestPrivate, err = NewKeyRing(unlockedKey)
if err != nil {
t.Fatal("Expected no error while decoding expected armored public key, got:", err)
panic("Expected no error while building private keyring, got:" + err.Error())
}
assert.Exactly(t, expected.Type, block.Type)
b, err := ioutil.ReadAll(block.Body)
publicKey, err := NewKeyFromArmored(readTestFile("keyring_publicKey", false))
if err != nil {
t.Fatal("Expected no error while reading armored public key body, got:", err)
panic("Expected no error while unarmoring public key, got:" + err.Error())
}
eb, err := ioutil.ReadAll(expected.Body)
keyRingTestPublic, err = NewKeyRing(publicKey)
if err != nil {
t.Fatal("Expected no error while reading expected armored public key body, got:", err)
panic("Expected no error while building public keyring, got:" + err.Error())
}
assert.Exactly(t, eb, b)
}
keyRingTestMultiple, err = NewKeyRing(nil)
if err != nil {
panic("Expected no error while building empty keyring, got:" + err.Error())
}
func TestCheckPassphrase(t *testing.T) {
encryptedKeyRing, _ := BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
isCorrect := encryptedKeyRing.CheckPassphrase("Wrong password")
assert.Exactly(t, false, isCorrect)
err = keyRingTestMultiple.AddKey(keyTestRSA)
if err != nil {
panic("Expected no error while adding RSA key to keyring, got:" + err.Error())
}
isCorrect = encryptedKeyRing.CheckPassphrase(testMailboxPassword)
assert.Exactly(t, true, isCorrect)
err = keyRingTestMultiple.AddKey(keyTestEC)
if err != nil {
panic("Expected no error while adding EC key to keyring, got:" + err.Error())
}
err = keyRingTestMultiple.AddKey(unlockedKey)
if err != nil {
panic("Expected no error while adding unlocked key to keyring, got:" + err.Error())
}
}
func TestIdentities(t *testing.T) {
identities := testPrivateKeyRing.Identities()
identities := keyRingTestPrivate.GetIdentities()
assert.Len(t, identities, 1)
assert.Exactly(t, identities[0], testIdentity)
}
func TestFilterExpiredKeys(t *testing.T) {
expiredKey, _ := BuildKeyRingArmored(readTestFile("key_expiredKey", false))
keys := []*KeyRing{testPrivateKeyRing, expiredKey}
expiredKey, err := NewKeyFromArmored(readTestFile("key_expiredKey", false))
if err != nil {
t.Fatal("Cannot unarmor expired key:", err)
}
expiredKeyRing, err := NewKeyRing(expiredKey)
if err != nil {
t.Fatal("Cannot create keyring with expired key:", err)
}
keys := []*KeyRing{keyRingTestPrivate, expiredKeyRing}
unexpired, err := FilterExpiredKeys(keys)
if err != nil {
@ -116,60 +113,89 @@ 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 := 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)
assert.Exactly(t, unexpired[0].GetKeyIDs(), keyRingTestPrivate.GetKeyIDs())
}
func TestKeyIds(t *testing.T) {
keyIDs := testPrivateKeyRing.KeyIds()
keyIDs := keyRingTestPrivate.GetKeyIDs()
var assertKeyIDs = []uint64{4518840640391470884}
assert.Exactly(t, assertKeyIDs, keyIDs)
}
func TestMutlipleKeyRing(t *testing.T) {
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
assert.Exactly(t, 1, len(testPublicKeyRing.entities))
func TestMultipleKeyRing(t *testing.T) {
assert.Exactly(t, 3, len(keyRingTestMultiple.entities))
assert.Exactly(t, 3, keyRingTestMultiple.CountEntities())
assert.Exactly(t, 3, keyRingTestMultiple.CountDecryptionEntities())
ids := testPublicKeyRing.KeyIds()
assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0])
assert.Exactly(t, 3, len(keyRingTestMultiple.GetKeys()))
err = testPublicKeyRing.ReadFrom(strings.NewReader(readTestFile("mime_publicKey", false)), true)
testKey, err := keyRingTestMultiple.GetKey(1)
if err != nil {
t.Fatal("Expected no error while adding a key to the keyring, got:", err)
t.Fatal("Expected no error while extracting key, got:", err)
}
assert.Exactly(t, keyTestEC, testKey)
_, err = keyRingTestMultiple.GetKey(3)
assert.NotNil(t, err)
singleKeyRing, err := keyRingTestMultiple.FirstKey()
if err != nil {
t.Fatal("Expected no error while filtering the first key, got:", err)
}
assert.Exactly(t, 1, len(singleKeyRing.entities))
assert.Exactly(t, 1, singleKeyRing.CountEntities())
assert.Exactly(t, 1, singleKeyRing.CountDecryptionEntities())
}
func TestClearPrivateKey(t *testing.T) {
keyRingCopy, err := keyRingTestMultiple.Copy()
if err != nil {
t.Fatal("Expected no error while copying keyring, got:", err)
}
assert.Exactly(t, 2, len(testPublicKeyRing.entities))
for _, key := range keyRingCopy.GetKeys() {
assert.Nil(t, clearPrivateKey(key.entity.PrivateKey.PrivateKey))
}
ids = testPublicKeyRing.KeyIds()
assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0])
assert.Exactly(t, uint64(0x374130b32ee1e5ea), ids[1])
singleKey := testPublicKeyRing.FirstKey()
assert.Exactly(t, 1, len(singleKey.entities))
ids = singleKey.KeyIds()
assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0])
keys := keyRingCopy.GetKeys()
assertRSACleared(t, keys[0].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
assertEdDSACleared(t, keys[1].entity.PrivateKey.PrivateKey.(ed25519.PrivateKey))
assertRSACleared(t, keys[2].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
}
func TestClearPrivateWithSubkeys(t *testing.T) {
keyRingCopy, err := keyRingTestMultiple.Copy()
if err != nil {
t.Fatal("Expected no error while copying keyring, got:", err)
}
for _, key := range keyRingCopy.GetKeys() {
assert.Exactly(t, 2, key.clearPrivateWithSubkeys())
}
keys := keyRingCopy.GetKeys()
assertRSACleared(t, keys[0].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
assertRSACleared(t, keys[0].entity.Subkeys[0].PrivateKey.PrivateKey.(*rsa.PrivateKey))
assertEdDSACleared(t, keys[1].entity.PrivateKey.PrivateKey.(ed25519.PrivateKey))
assertECDHCleared(t, keys[1].entity.Subkeys[0].PrivateKey.PrivateKey.(*ecdh.PrivateKey))
assertRSACleared(t, keys[2].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
assertRSACleared(t, keys[2].entity.Subkeys[0].PrivateKey.PrivateKey.(*rsa.PrivateKey))
}
func TestClearPrivateParams(t *testing.T) {
keyRingCopy, err := keyRingTestMultiple.Copy()
if err != nil {
t.Fatal("Expected no error while copying keyring, got:", err)
}
for _, key := range keyRingCopy.GetKeys() {
assert.True(t, key.IsPrivate())
assert.True(t, key.ClearPrivateParams())
assert.False(t, key.IsPrivate())
assert.Nil(t, key.entity.PrivateKey)
assert.Nil(t, key.entity.Subkeys[0].PrivateKey)
assert.False(t, key.ClearPrivateParams())
}
}

View file

@ -10,9 +10,9 @@ import (
"regexp"
"runtime"
"github.com/ProtonMail/gopenpgp/armor"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/ProtonMail/gopenpgp/v2/armor"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/ProtonMail/gopenpgp/v2/internal"
"golang.org/x/crypto/openpgp/clearsign"
"golang.org/x/crypto/openpgp/packet"
@ -51,7 +51,7 @@ type PGPSplitMessage struct {
// A Cleartext message is a signed PGP message, that is not encrypted,
// i.e. the ones beginning with -----BEGIN PGP SIGNED MESSAGE-----
type ClearTextMessage struct {
Data []byte
Data []byte
Signature []byte
}
@ -146,7 +146,7 @@ func NewPGPSignatureFromArmored(armored string) (*PGPSignature, error) {
// NewClearTextMessage generates a new ClearTextMessage from data and signature
func NewClearTextMessage(data []byte, signature []byte) *ClearTextMessage {
return &ClearTextMessage{
Data: data,
Data: data,
Signature: signature,
}
}
@ -183,7 +183,7 @@ func (msg *PlainMessage) GetBase64() string {
return base64.StdEncoding.EncodeToString(msg.Data)
}
// NewReader returns a New io.Reader for the bianry data of the message
// NewReader returns a New io.Reader for the binary data of the message
func (msg *PlainMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
@ -203,7 +203,7 @@ func (msg *PGPMessage) GetBinary() []byte {
return msg.Data
}
// NewReader returns a New io.Reader for the unarmored bianry data of the message
// NewReader returns a New io.Reader for the unarmored binary data of the message
func (msg *PGPMessage) NewReader() io.Reader {
return bytes.NewReader(msg.GetBinary())
}
@ -225,7 +225,7 @@ func (msg *PGPSplitMessage) GetBinaryKeyPacket() []byte {
// GetBinary returns the unarmored binary joined packets as a []byte
func (msg *PGPSplitMessage) GetBinary() []byte {
return append(msg.KeyPacket , msg.DataPacket...)
return append(msg.KeyPacket, msg.DataPacket...)
}
// GetArmored returns the armored message as a string, with joined data and key packets
@ -233,6 +233,11 @@ func (msg *PGPSplitMessage) GetArmored() (string, error) {
return armor.ArmorWithType(msg.GetBinary(), constants.PGPMessageHeader)
}
// GetPGPMessage joins asymmetric session key packet with the symmetric data packet to obtain a PGP message
func (msg *PGPSplitMessage) GetPGPMessage() *PGPMessage {
return NewPGPMessage(append(msg.KeyPacket, msg.DataPacket...))
}
// SeparateKeyAndData returns the first keypacket and the (hopefully unique) dataPacket (not verified)
// * estimatedLength is the estimate length of the message
// * garbageCollector > 0 activates the garbage collector
@ -244,7 +249,6 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
// 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 {
@ -259,7 +263,7 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
encryptedKey = p
case *packet.SymmetricallyEncrypted:
// FIXME: add support for multiple keypackets
// TODO: 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.
@ -267,8 +271,14 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
// in low-memory environments
b.Grow(1<<16 + estimatedLength)
// empty encoded length + start byte
b.Write(make([]byte, 6))
b.WriteByte(byte(1))
if _, err := b.Write(make([]byte, 6)); err != nil {
return nil, err
}
if err := b.WriteByte(byte(1)); err != nil {
return nil, err
}
actualLength := 1
block := make([]byte, 128)
for {
@ -276,7 +286,9 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
if err == io.EOF {
break
}
b.Write(block[:n])
if _, err := b.Write(block[:n]); err != nil {
return nil, err
}
actualLength += n
gcCounter += n
if gcCounter > garbageCollector && garbageCollector > 0 {
@ -287,17 +299,18 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
// quick encoding
symEncryptedData := b.Bytes()
if actualLength < 192 {
switch {
case actualLength < 192:
symEncryptedData[4] = byte(210)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[4:]
} else if actualLength < 8384 {
actualLength = actualLength - 192
case actualLength < 8384:
actualLength -= 192
symEncryptedData[3] = byte(210)
symEncryptedData[4] = 192 + byte(actualLength>>8)
symEncryptedData[5] = byte(actualLength)
symEncryptedData = symEncryptedData[3:]
} else {
default:
symEncryptedData[0] = byte(210)
symEncryptedData[1] = byte(255)
symEncryptedData[2] = byte(actualLength >> 24)
@ -305,13 +318,9 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
symEncryptedData[4] = byte(actualLength >> 8)
symEncryptedData[5] = byte(actualLength)
}
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")
}

View file

@ -4,48 +4,47 @@ import (
"bytes"
"encoding/base64"
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/crypto/openpgp/packet"
)
func TestTextMessageEncryptionWithSymmetricKey(t *testing.T) {
func TestTextMessageEncryptionWithPassword(t *testing.T) {
var message = NewPlainMessageFromString("The secret code is... 1, 2, 3, 4, 5")
// Encrypt data with password
encrypted, err := testSymmetricKey.Encrypt(message)
encrypted, err := EncryptMessageWithPassword(message, testSymmetricKey)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
// Decrypt data with wrong password
_, err = testWrongSymmetricKey.Decrypt(encrypted)
_, err = DecryptMessageWithPassword(encrypted, []byte("Wrong password"))
assert.NotNil(t, err)
// Decrypt data with the good password
decrypted, err := testSymmetricKey.Decrypt(encrypted)
decrypted, err := DecryptMessageWithPassword(encrypted, testSymmetricKey)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetString(), decrypted.GetString())
}
func TestBinaryMessageEncryptionWithSymmetricKey(t *testing.T) {
func TestBinaryMessageEncryptionWithPassword(t *testing.T) {
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var message = NewPlainMessage(binData)
// Encrypt data with password
encrypted, err := testSymmetricKey.Encrypt(message)
encrypted, err := EncryptMessageWithPassword(message, testSymmetricKey)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
// Decrypt data with wrong password
_, err = testWrongSymmetricKey.Decrypt(encrypted)
_, err = DecryptMessageWithPassword(encrypted, []byte("Wrong password"))
assert.NotNil(t, err)
// Decrypt data with the good password
decrypted, err := testSymmetricKey.Decrypt(encrypted)
decrypted, err := DecryptMessageWithPassword(encrypted, testSymmetricKey)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
@ -55,21 +54,12 @@ func TestBinaryMessageEncryptionWithSymmetricKey(t *testing.T) {
func TestTextMessageEncryption(t *testing.T) {
var message = NewPlainMessageFromString("plain text")
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
testPrivateKeyRing, err = BuildKeyRingArmored(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)
ciphertext, err := keyRingTestPublic.Encrypt(message, keyRingTestPrivate)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
decrypted, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, GetUnixTime())
decrypted, err := keyRingTestPrivate.Decrypt(ciphertext, keyRingTestPublic, GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
@ -80,28 +70,19 @@ func TestBinaryMessageEncryption(t *testing.T) {
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
var message = NewPlainMessage(binData)
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
testPrivateKeyRing, err = BuildKeyRingArmored(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)
ciphertext, err := keyRingTestPublic.Encrypt(message, keyRingTestPrivate)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
decrypted, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, GetUnixTime())
decrypted, err := keyRingTestPrivate.Decrypt(ciphertext, keyRingTestPublic, GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetBinary(), decrypted.GetBinary())
// Decrypt without verifying
decrypted, err = testPrivateKeyRing.Decrypt(ciphertext, nil, 0)
decrypted, err = keyRingTestPrivate.Decrypt(ciphertext, nil, 0)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
@ -109,29 +90,40 @@ func TestBinaryMessageEncryption(t *testing.T) {
}
func TestIssue11(t *testing.T) {
myKeyring, err := BuildKeyRingArmored(readTestFile("issue11_privatekey", false))
var issue11Password = []byte("1234")
issue11Key, err := NewKeyFromArmored(readTestFile("issue11_privatekey", false))
if err != nil {
t.Fatal("Expected no error while unarmoring private keyring, got:", err)
}
issue11Key, err = issue11Key.Unlock(issue11Password)
if err != nil {
t.Fatal("Expected no error while unlocking private key, got:", err)
}
issue11Keyring, err := NewKeyRing(issue11Key)
if err != nil {
t.Fatal("Expected no error while bulding private keyring, got:", err)
}
err = myKeyring.UnlockWithPassphrase("1234");
senderKey, err := NewKeyFromArmored(readTestFile("issue11_publickey", false))
if err != nil {
t.Fatal("Expected no error while unlocking private keyring, got:", err)
t.Fatal("Expected no error while unarmoring public keyring, got:", err)
}
assert.Exactly(t, "643b3595e6ee4fdf", senderKey.GetHexKeyID())
senderKeyring, err := BuildKeyRingArmored(readTestFile("issue11_publickey", false))
senderKeyring, err := NewKeyRing(senderKey)
if err != nil {
t.Fatal("Expected no error while building public keyring, got:", err)
}
assert.Exactly(t, []uint64{0x643b3595e6ee4fdf}, senderKeyring.KeyIds())
pgpMessage, err := NewPGPMessageFromArmored(readTestFile("issue11_message", false))
if err != nil {
t.Fatal("Expected no error while unlocking private keyring, got:", err)
}
plainMessage, err := myKeyring.Decrypt(pgpMessage, senderKeyring, 0)
plainMessage, err := issue11Keyring.Decrypt(pgpMessage, senderKeyring, 0)
if err != nil {
t.Fatal("Expected no error while decrypting/verifying, got:", err)
}
@ -140,20 +132,12 @@ func TestIssue11(t *testing.T) {
}
func TestSignedMessageDecryption(t *testing.T) {
testPrivateKeyRing, err = BuildKeyRingArmored(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)
}
pgpMessage, err := NewPGPMessageFromArmored(readTestFile("message_signed", false))
if err != nil {
t.Fatal("Expected no error when unarmoring, got:", err)
}
decrypted, err := testPrivateKeyRing.Decrypt(pgpMessage, nil, 0)
decrypted, err := keyRingTestPrivate.Decrypt(pgpMessage, nil, 0)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
@ -162,24 +146,9 @@ func TestSignedMessageDecryption(t *testing.T) {
func TestMultipleKeyMessageEncryption(t *testing.T) {
var message = NewPlainMessageFromString("plain text")
assert.Exactly(t, 3, len(keyRingTestMultiple.entities))
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
err = testPublicKeyRing.ReadFrom(strings.NewReader(readTestFile("mime_publicKey", false)), true)
if err != nil {
t.Fatal("Expected no error adding second public key, got:", err)
}
assert.Exactly(t, 2, len(testPublicKeyRing.entities))
testPrivateKeyRing, err = BuildKeyRingArmored(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)
ciphertext, err := keyRingTestMultiple.Encrypt(message, keyRingTestPrivate)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
@ -189,17 +158,15 @@ func TestMultipleKeyMessageEncryption(t *testing.T) {
for {
var p packet.Packet
if p, err = packets.Next(); err == io.EOF {
err = nil
break
}
switch p.(type) {
case *packet.EncryptedKey:
numKeyPackets++
if _, ok := p.(*packet.EncryptedKey); ok {
numKeyPackets++
}
}
assert.Exactly(t, 2, numKeyPackets)
assert.Exactly(t, 3, numKeyPackets)
decrypted, err := testPrivateKeyRing.Decrypt(ciphertext, testPublicKeyRing, GetUnixTime())
decrypted, err := keyRingTestPrivate.Decrypt(ciphertext, keyRingTestPublic, GetUnixTime())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}

View file

@ -77,7 +77,7 @@ func parseMIME(
err = gomime.VisitAll(bytes.NewReader(mmBodyData), h, signatureCollector)
if err == nil && verifierKey != nil {
err = signatureCollector.verified;
err = signatureCollector.verified
}
return bodyCollector,

View file

@ -7,7 +7,7 @@ import (
)
// Corresponding key in testdata/mime_privateKey
const privateKeyPassword = "test"
var MIMEKeyPassword = []byte("test")
// define call back interface
type Callbacks struct {
@ -37,13 +37,22 @@ func TestDecrypt(t *testing.T) {
callbacks := Callbacks{
Testing: t,
}
privateKeyRing, _ := BuildKeyRingArmored(readTestFile("mime_privateKey", false))
err = privateKeyRing.UnlockWithPassphrase(privateKeyPassword)
privateKey, err := NewKeyFromArmored(readTestFile("mime_privateKey", false))
if err != nil {
t.Fatal("Cannot unarmor private key:", err)
}
privateKey, err = privateKey.Unlock(MIMEKeyPassword)
if err != nil {
t.Fatal("Cannot unlock private key:", err)
}
privateKeyRing, err := NewKeyRing(privateKey)
if err != nil {
t.Fatal("Cannot create private keyring:", err)
}
message, err := NewPGPMessageFromArmored(readTestFile("mime_pgpMessage", false))
if err != nil {
t.Fatal("Cannot decode armored message:", err)
@ -60,7 +69,7 @@ func TestParse(t *testing.T) {
body, atts, attHeaders, err := parseMIME(readTestFile("mime_testMessage", false), nil)
if err != nil {
t.Error("Expected no error while parsing message, got:", err)
t.Fatal("Expected no error while parsing message, got:", err)
}
_ = atts

151
crypto/password.go Normal file
View file

@ -0,0 +1,151 @@
package crypto
import (
"bytes"
"io"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
"github.com/pkg/errors"
)
// Encrypt encrypts a PlainMessage to PGPMessage with a SymmetricKey
// * message : The plain data as a PlainMessage
// * password: A password that will be derived into an encryption key
// * output : The encrypted data as PGPMessage
func EncryptMessageWithPassword(message *PlainMessage, password []byte) (*PGPMessage, error) {
encrypted, err := passwordEncrypt(message.GetBinary(), password)
if err != nil {
return nil, err
}
return NewPGPMessage(encrypted), nil
}
// Decrypt decrypts password protected pgp binary messages
// * encrypted: The encrypted data as PGPMessage
// * password: A password that will be derived into an encryption key
// * output: The decrypted data as PlainMessage
func DecryptMessageWithPassword(message *PGPMessage, password []byte) (*PlainMessage, error) {
decrypted, err := passwordDecrypt(message.NewReader(), password)
if err != nil {
return nil, err
}
binMessage := NewPlainMessage(decrypted)
return binMessage, nil
}
// DecryptSessionKeyWithPassword decrypts the binary symmetrically encrypted
// session key packet and returns the session key.
func DecryptSessionKeyWithPassword(keyPacket, password []byte) (*SessionKey, 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
}
if p, ok := p.(*packet.SymmetricKeyEncrypted); ok {
symKeys = append(symKeys, p)
}
}
// Try the symmetric passphrase first
if len(symKeys) != 0 && password != nil {
for _, s := range symKeys {
key, cipherFunc, err := s.Decrypt(password)
if err == nil {
return &SessionKey{
Key: key,
Algo: getAlgo(cipherFunc),
}, nil
}
}
}
return nil, errors.New("gopenpgp: password incorrect")
}
// EncryptSessionKeyWithPassword encrypts the session key with the password and
// returns a binary symmetrically encrypted session key packet.
func EncryptSessionKeyWithPassword(sk *SessionKey, password []byte) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf, err := sk.GetCipherFunc()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
}
if len(password) == 0 {
return nil, errors.New("gopenpgp: password can't be empty")
}
config := &packet.Config{
DefaultCipher: cf,
}
err = packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, sk.Key, password, config)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt session key with password")
}
return outbuf.Bytes(), nil
}
// ----- INTERNAL FUNCTIONS ------
func passwordEncrypt(message []byte, password []byte) ([]byte, error) {
var outBuf bytes.Buffer
config := &packet.Config{
Time: getTimeGenerator(),
}
encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, password, nil, config)
if err != nil {
return nil, err
}
_, err = encryptWriter.Write(message)
if err != nil {
return nil, err
}
err = encryptWriter.Close()
if err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
func passwordDecrypt(encryptedIO io.Reader, password []byte) ([]byte, error) {
firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled {
firstTimeCalled = false
return password, nil
}
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
}
config := &packet.Config{
Time: 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
}

View file

@ -1,74 +0,0 @@
package crypto
import (
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/stretchr/testify/assert"
)
var testRandomToken []byte
func TestRandomToken(t *testing.T) {
var err error
testRandomToken, err = RandomToken()
if err != nil {
t.Fatal("Expected no error while generating default length random token, got:", err)
}
token40, err := RandomTokenSize(40)
if err != nil {
t.Fatal("Expected no error while generating random token, got:", err)
}
assert.Len(t, testRandomToken, 32)
assert.Len(t, token40, 40)
}
func TestAsymmetricKeyPacket(t *testing.T) {
symmetricKey := &SymmetricKey{
Key: testRandomToken,
Algo: constants.AES256,
}
privateKeyRing, _ := ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
_ = privateKeyRing.UnlockWithPassphrase(testMailboxPassword)
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 := privateKeyRing.DecryptSessionKey(keyPacket)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
assert.Exactly(t, symmetricKey, outputSymmetricKey)
}
func TestSymmetricKeyPacket(t *testing.T) {
symmetricKey := &SymmetricKey{
Key: testRandomToken,
Algo: constants.AES256,
}
password := "I like encryption"
keyPacket, err := symmetricKey.EncryptToKeyPacket(password)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
_, err = NewSymmetricKeyFromKeyPacket(keyPacket, "Wrong password")
assert.EqualError(t, err, "gopenpgp: password incorrect")
outputSymmetricKey, err := NewSymmetricKeyFromKeyPacket(keyPacket, password)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
assert.Exactly(t, symmetricKey, outputSymmetricKey)
}

221
crypto/sessionkey.go Normal file
View file

@ -0,0 +1,221 @@
package crypto
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// SessionKey stores a decrypted session key.
type SessionKey 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 SessionKey.
func (sk *SessionKey) GetCipherFunc() (packet.CipherFunction, error) {
cf, ok := symKeyAlgos[sk.Algo]
if !ok {
return cf, errors.New("gopenpgp: unsupported cipher function: " + sk.Algo)
}
return cf, nil
}
// GetBase64Key returns the session key as base64 encoded string.
func (sk *SessionKey) GetBase64Key() string {
return base64.StdEncoding.EncodeToString(sk.Key)
}
// RandomToken generates a random token with the specified key size
func RandomToken(size int) ([]byte, error) {
config := &packet.Config{DefaultCipher: packet.CipherAES256}
symKey := make([]byte, size)
if _, err := io.ReadFull(config.Random(), symKey); err != nil {
return nil, err
}
return symKey, nil
}
// GenerateSessionKeyAlgo generates a random key of the correct length for the specified algorithm
func GenerateSessionKeyAlgo(algo string) (sk *SessionKey, err error) {
cf, ok := symKeyAlgos[algo]
if !ok {
return nil, errors.New("gopenpgp: unknown symmetric key generation algorithm")
}
r, err := RandomToken(cf.KeySize())
if err != nil {
return nil, err
}
sk = &SessionKey{
Key: r,
Algo: algo,
}
return sk, nil
}
// GenerateSessionKey generates a random key for the default cipher
func GenerateSessionKey() (*SessionKey, error) {
return GenerateSessionKeyAlgo(constants.AES256)
}
func NewSessionKeyFromToken(token []byte, algo string) *SessionKey {
return &SessionKey{
Key: token,
Algo: algo,
}
}
func newSessionKeyFromEncrypted(ek *packet.EncryptedKey) (*SessionKey, 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)
}
symmetricKey := &SessionKey{
Key: ek.Key,
Algo: algo,
}
return symmetricKey, nil
}
// Encrypt encrypts a PlainMessage to PGPMessage with a SessionKey
// * message : The plain data as a PlainMessage
// * output : The encrypted data as PGPMessage
func (sk *SessionKey) Encrypt(message *PlainMessage) ([]byte, error) {
var encBuf bytes.Buffer
var encryptWriter io.WriteCloser
dc, err := sk.GetCipherFunc()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt with session key")
}
config := &packet.Config{
Time: getTimeGenerator(),
DefaultCipher: dc,
}
encryptWriter, err = packet.SerializeSymmetricallyEncrypted(&encBuf, config.Cipher(), sk.Key, config)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to encrypt")
}
if algo := config.Compression(); algo != packet.CompressionNone {
encryptWriter, err = packet.SerializeCompressed(encryptWriter, algo, config.CompressionConfig)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: error in compression")
}
}
encryptWriter, err = packet.SerializeLiteral(encryptWriter, false, "", 0)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to serialize")
}
_, err = encryptWriter.Write(message.GetBinary())
if err != nil {
return nil, err
}
err = encryptWriter.Close()
if err != nil {
return nil, err
}
return encBuf.Bytes(), nil
}
// Decrypt decrypts password protected pgp binary messages
// * encrypted: PGPMessage
// * output: PlainMessage
func (sk *SessionKey) Decrypt(dataPacket []byte) (*PlainMessage, error) {
var messageReader = bytes.NewReader(dataPacket)
var decrypted io.ReadCloser
var decBuf bytes.Buffer
// Read symmetrically encrypted data packet
packets := packet.NewReader(messageReader)
p, err := packets.Next()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to read symmetric packet")
}
// Decrypt data packet
switch p := p.(type) {
case *packet.SymmetricallyEncrypted:
dc, err := sk.GetCipherFunc()
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt with session key")
}
decrypted, err = p.Decrypt(dc, sk.Key)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to decrypt symmetric packet")
}
default:
return nil, errors.New("gopenpgp: invalid packet type")
}
_, err = decBuf.ReadFrom(decrypted)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to read from decrypted symmetric packet")
}
config := &packet.Config{
Time: getTimeGenerator(),
}
// Push decrypted packet as literal packet and use openpgp's reader
keyring := openpgp.EntityList{} // Ignore signatures, since we have no private key
md, err := openpgp.ReadMessage(&decBuf, keyring, nil, config)
if err != nil {
return nil, errors.Wrap(err, "gopenpgp: unable to decode symmetric packet")
}
messageBuf := new(bytes.Buffer)
_, err = messageBuf.ReadFrom(md.UnverifiedBody)
if err != nil {
return nil, err
}
return NewPlainMessage(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
}

145
crypto/sessionkey_test.go Normal file
View file

@ -0,0 +1,145 @@
package crypto
import (
"testing"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/stretchr/testify/assert"
)
var testSessionKey *SessionKey
func init() {
var err error
testSessionKey, err = GenerateSessionKey()
if err != nil {
panic("Expected no error while generating random session key with default algorithm, got:" + err.Error())
}
}
func TestRandomToken(t *testing.T) {
token40, err := RandomToken(40)
if err != nil {
t.Fatal("Expected no error while generating random token, got:", err)
}
assert.Len(t, token40, 40)
}
func TestGenerateSessionKey(t *testing.T) {
assert.Len(t, testSessionKey.Key, 32)
}
func TestAsymmetricKeyPacket(t *testing.T) {
keyPacket, err := keyRingTestPublic.EncryptSessionKey(testSessionKey)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
// Password defined in keyring_test
outputSymmetricKey, err := keyRingTestPrivate.DecryptSessionKey(keyPacket)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
assert.Exactly(t, testSessionKey, outputSymmetricKey)
}
func TestSymmetricKeyPacket(t *testing.T) {
password := []byte("I like encryption")
keyPacket, err := EncryptSessionKeyWithPassword(testSessionKey, password)
if err != nil {
t.Fatal("Expected no error while generating key packet, got:", err)
}
_, err = DecryptSessionKeyWithPassword(keyPacket, []byte("Wrong password"))
assert.EqualError(t, err, "gopenpgp: password incorrect")
outputSymmetricKey, err := DecryptSessionKeyWithPassword(keyPacket, password)
if err != nil {
t.Fatal("Expected no error while decrypting key packet, got:", err)
}
assert.Exactly(t, testSessionKey, outputSymmetricKey)
}
func TestDataPacketEncryption(t *testing.T) {
var message = NewPlainMessageFromString("The secret code is... 1, 2, 3, 4, 5")
// Encrypt data with session key
dataPacket, err := testSessionKey.Encrypt(message)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
// Decrypt data with wrong session key
wrongKey := SessionKey{
Key: []byte("wrong pass"),
Algo: constants.AES256,
}
_, err = wrongKey.Decrypt(dataPacket)
assert.NotNil(t, err)
// Decrypt data with the good session key
decrypted, err := testSessionKey.Decrypt(dataPacket)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, message.GetString(), decrypted.GetString())
// Encrypt session key
keyPacket, err := keyRingTestPublic.EncryptSessionKey(testSessionKey)
if err != nil {
t.Fatal("Unable to encrypt key packet, got:", err)
}
// Join key packet and data packet in single message
splitMessage := NewPGPSplitMessage(keyPacket, dataPacket)
// Armor and un-armor message. In alternative it can also be done with NewPgpMessage(splitMessage.GetBinary())
armored, err := splitMessage.GetArmored()
if err != nil {
t.Fatal("Unable to armor split message, got:", err)
}
pgpMessage, err := NewPGPMessageFromArmored(armored)
if err != nil {
t.Fatal("Unable to unarmor pgp message, got:", err)
}
// Test if final decryption succeeds
finalMessage, err := keyRingTestPrivate.Decrypt(pgpMessage, nil, 0)
if err != nil {
t.Fatal("Unable to decrypt joined keypacket and datapacket, got:", err)
}
assert.Exactly(t, message.GetString(), finalMessage.GetString())
}
func TestDataPacketDecryption(t *testing.T) {
pgpMessage, err := NewPGPMessageFromArmored(readTestFile("message_signed", false))
if err != nil {
t.Fatal("Expected no error when unarmoring, got:", err)
}
split, err := pgpMessage.SeparateKeyAndData(1024, 0)
if err != nil {
t.Fatal("Expected no error when splitting, got:", err)
}
sessionKey, err := keyRingTestPrivate.DecryptSessionKey(split.GetBinaryKeyPacket())
if err != nil {
t.Fatal("Expected no error when decrypting session key, got:", err)
}
decrypted, err := sessionKey.Decrypt(split.GetBinaryDataPacket())
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}
assert.Exactly(t, readTestFile("message_plaintext", true), decrypted.GetString())
}
func TestSessionKeyClear(t *testing.T) {
testSessionKey.Clear()
assertMemCleared(t, testSessionKey.Key)
}

View file

@ -8,16 +8,16 @@ import (
"time"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
pgpErrors "golang.org/x/crypto/openpgp/errors"
"golang.org/x/crypto/openpgp/packet"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/ProtonMail/gopenpgp/v2/internal"
)
// SignatureVerificationError is returned from Decrypt and VerifyDetached functions when signature verification fails
type SignatureVerificationError struct {
Status int
Status int
Message string
}
@ -32,7 +32,7 @@ func (e SignatureVerificationError) Error() string {
// newSignatureFailed creates a new SignatureVerificationError, type SIGNATURE_FAILED
func newSignatureFailed() SignatureVerificationError {
return SignatureVerificationError {
return SignatureVerificationError{
constants.SIGNATURE_FAILED,
"Invalid signature",
}
@ -40,7 +40,7 @@ func newSignatureFailed() SignatureVerificationError {
// newSignatureNotSigned creates a new SignatureVerificationError, type SIGNATURE_NOT_SIGNED
func newSignatureNotSigned() SignatureVerificationError {
return SignatureVerificationError {
return SignatureVerificationError{
constants.SIGNATURE_NOT_SIGNED,
"Missing signature",
}
@ -48,7 +48,7 @@ func newSignatureNotSigned() SignatureVerificationError {
// newSignatureNoVerifier creates a new SignatureVerificationError, type SIGNATURE_NO_VERIFIER
func newSignatureNoVerifier() SignatureVerificationError {
return SignatureVerificationError {
return SignatureVerificationError{
constants.SIGNATURE_NO_VERIFIER,
"No matching signature",
}

View file

@ -85,7 +85,8 @@ func (sc *SignatureCollector) Accept(
if err != nil {
return err
}
buffer, err = gomime.DecodeCharset(buffer, params)
mediaType, _, _ := mime.ParseMediaType(header.Get("Content-Type"))
buffer, err = gomime.DecodeCharset(buffer, mediaType, params)
if err != nil {
return err
}

View file

@ -2,39 +2,23 @@ package crypto
import (
"regexp"
"strings"
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/stretchr/testify/assert"
)
const signedPlainText = "Signed message\n"
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
var signingKeyRing *KeyRing
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) {
var err error
signingKeyRing, err = ReadArmoredKeyRing(strings.NewReader(readTestFile("keyring_privateKey", false)))
if err != nil {
t.Fatal("Cannot read private key:", err)
}
// Password defined in keyring_test
err = signingKeyRing.UnlockWithPassphrase(testMailboxPassword)
if err != nil {
t.Fatal("Cannot decrypt private key:", err)
}
message = NewPlainMessageFromString(signedPlainText)
textSignature, err = signingKeyRing.SignDetached(message)
textSignature, err = keyRingTestPrivate.SignDetached(message)
if err != nil {
t.Fatal("Cannot generate signature:", err)
}
@ -48,15 +32,15 @@ func TestSignTextDetached(t *testing.T) {
}
func TestVerifyTextDetachedSig(t *testing.T) {
verificationError := signingKeyRing.VerifyDetached(message, textSignature, testTime)
verificationError := keyRingTestPublic.VerifyDetached(message, textSignature, testTime)
if verificationError != nil {
t.Fatal("Cannot verify plaintext signature:", err)
t.Fatal("Cannot verify plaintext signature:", verificationError)
}
}
func TestVerifyTextDetachedSigWrong(t *testing.T) {
fakeMessage := NewPlainMessageFromString("wrong text")
verificationError := signingKeyRing.VerifyDetached(fakeMessage, textSignature, testTime)
verificationError := keyRingTestPublic.VerifyDetached(fakeMessage, textSignature, testTime)
assert.EqualError(t, verificationError, "Signature Verification Error: Invalid signature")
@ -67,7 +51,7 @@ func TestVerifyTextDetachedSigWrong(t *testing.T) {
func TestSignBinDetached(t *testing.T) {
var err error
binSignature, err = signingKeyRing.SignDetached(NewPlainMessage([]byte(signedPlainText)))
binSignature, err = keyRingTestPrivate.SignDetached(NewPlainMessage([]byte(signedPlainText)))
if err != nil {
t.Fatal("Cannot generate signature:", err)
}
@ -81,8 +65,8 @@ func TestSignBinDetached(t *testing.T) {
}
func TestVerifyBinDetachedSig(t *testing.T) {
verificationError := signingKeyRing.VerifyDetached(message, binSignature, testTime)
verificationError := keyRingTestPublic.VerifyDetached(message, binSignature, testTime)
if verificationError != nil {
t.Fatal("Cannot verify binary signature:", err)
t.Fatal("Cannot verify binary signature:", verificationError)
}
}

View file

@ -1,225 +0,0 @@
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 &SymmetricKey{
Key: []byte(passphrase),
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)
}
symmetricKey := &SymmetricKey{
Key: ek.Key,
Algo: algo,
}
return symmetricKey, 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: 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: 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
}

View file

@ -1,8 +1,8 @@
package crypto
import (
"time"
"errors"
"time"
)
// UpdateTime updates cached time
@ -31,7 +31,7 @@ func getNow() time.Time {
return time.Now()
}
return time.Unix(pgp.latestServerTime + extrapolate, 0)
return time.Unix(pgp.latestServerTime+extrapolate, 0)
}
func getDiff() (int64, error) {
@ -40,12 +40,10 @@ func getDiff() (int64, error) {
return int64(time.Since(pgp.latestClientTime).Seconds()), nil
}
return 0, errors.New("Latest server time not available")
return 0, errors.New("gopenpgp: latest server time not available")
}
// getTimeGenerator Returns a time generator function
func getTimeGenerator() func() time.Time {
return func() time.Time {
return getNow()
}
return getNow
}

11
go.mod
View file

@ -1,11 +1,12 @@
module github.com/ProtonMail/gopenpgp
module github.com/ProtonMail/gopenpgp/v2
go 1.12
require (
github.com/ProtonMail/go-mime v0.0.0-20190521135552-09454e3dbe72
github.com/stretchr/testify v1.2.2
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a
github.com/pkg/errors v0.8.1
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392
)
replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20190814153124-b5b07a6add54
replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20191122234321-e77a1f03baa0

20
go.sum
View file

@ -1,25 +1,31 @@
github.com/ProtonMail/crypto v0.0.0-20190814153124-b5b07a6add54 h1:b9Mgk9zYaSxsqeaq/qCUsPBIR95BcyjzTL+uFoPBG1o=
github.com/ProtonMail/crypto v0.0.0-20190814153124-b5b07a6add54/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
github.com/ProtonMail/go-mime v0.0.0-20190521135552-09454e3dbe72 h1:hGCc4Oc2fD3I5mNnZ1VlREncVc9EXJF8dxW3sw16gWM=
github.com/ProtonMail/go-mime v0.0.0-20190521135552-09454e3dbe72/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/crypto v0.0.0-20191122234321-e77a1f03baa0 h1:mCww5Yl0Pm4PZPSooupyWDgihrh96p6+O4PY1hs0FBw=
github.com/ProtonMail/crypto v0.0.0-20191122234321-e77a1f03baa0/go.mod h1:MBriIAodHvZ+YvwvMJWCTmseW/LkeVRPWp/iZKvee4g=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -3,12 +3,14 @@ package helper
import (
"io/ioutil"
"strings"
"github.com/ProtonMail/gopenpgp/v2/crypto"
)
var err error
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
func readTestFile(name string, trimNewlines bool) string {
data, err := ioutil.ReadFile("../crypto/testdata/" + name)
data, err := ioutil.ReadFile("../crypto/testdata/" + name) //nolint
if err != nil {
panic(err)
}
@ -19,4 +21,8 @@ func readTestFile(name string, trimNewlines bool) string {
}
// Corresponding key in ../crypto/testdata/keyring_privateKey
const testMailboxPassword = "apple"
var testMailboxPassword = []byte("apple")
func init() {
crypto.UpdateTime(testTime) // 2019-05-13T13:37:07+00:00
}

View file

@ -3,30 +3,40 @@ package helper
import (
"strings"
"github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/internal"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/gopenpgp/v2/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 := crypto.BuildKeyRingArmored(privateKey)
func SignCleartextMessageArmored(privateKey string, passphrase []byte, text string) (string, error) {
signingKey, err := crypto.NewKeyFromArmored(privateKey)
if err != nil {
return "", err
}
err = signingKeyRing.UnlockWithPassphrase(passphrase)
unlockedKey, err := signingKey.Unlock(passphrase)
if err != nil {
return "", err
}
return SignCleartextMessage(signingKeyRing, text)
keyRing, err := crypto.NewKeyRing(unlockedKey)
if err != nil {
return "", err
}
return SignCleartextMessage(keyRing, 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 := crypto.BuildKeyRingArmored(publicKey)
signingKey, err := crypto.NewKeyFromArmored(publicKey)
if err != nil {
return "", err
}
verifyKeyRing, err := crypto.NewKeyRing(signingKey)
if err != nil {
return "", err
}

View file

@ -4,12 +4,12 @@ import (
"regexp"
"testing"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/stretchr/testify/assert"
"github.com/ProtonMail/gopenpgp/crypto"
)
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-----$")

View file

@ -1,29 +1,19 @@
// helper contains several functions with a simple interface to extend usability and compatibility with gomobile
package helper
import (
"errors"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
)
// 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) {
// EncryptMessageWithPassword encrypts a string with a passphrase using AES256
func EncryptMessageWithPassword(password []byte, plaintext 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 {
if pgpMessage, err = crypto.EncryptMessageWithPassword(message, password); err != nil {
return "", err
}
@ -34,19 +24,17 @@ func EncryptMessageWithTokenAlgo(
return ciphertext, nil
}
// DecryptMessageWithToken decrypts an armored message with a random token.
// DecryptMessageWithPassword decrypts an armored message with a random token.
// The algorithm is derived from the armoring.
func DecryptMessageWithToken(token, ciphertext string) (plaintext string, err error) {
func DecryptMessageWithPassword(password []byte, 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 {
if message, err = crypto.DecryptMessageWithPassword(pgpMessage, password); err != nil {
return "", err
}
@ -54,13 +42,18 @@ func DecryptMessageWithToken(token, ciphertext string) (plaintext string, err er
}
// EncryptMessageArmored generates an armored PGP message given a plaintext and an armored public key
func EncryptMessageArmored(publicKey, plaintext string) (ciphertext string, err error) {
func EncryptMessageArmored(key, plaintext string) (ciphertext string, err error) {
var publicKey *crypto.Key
var publicKeyRing *crypto.KeyRing
var pgpMessage *crypto.PGPMessage
var message = crypto.NewPlainMessageFromString(plaintext)
if publicKeyRing, err = crypto.BuildKeyRingArmored(publicKey); err != nil {
if publicKey, err = crypto.NewKeyFromArmored(key); err != nil {
return "", err
}
if publicKeyRing, err = crypto.NewKeyRing(publicKey); err != nil {
return "", err
}
@ -78,22 +71,31 @@ func EncryptMessageArmored(publicKey, plaintext string) (ciphertext string, err
// 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,
publicKey, privateKey string, passphrase []byte, plaintext string,
) (ciphertext string, err error) {
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
var publicKeyRing, privateKeyRing *crypto.KeyRing
var pgpMessage *crypto.PGPMessage
var message = crypto.NewPlainMessageFromString(plaintext)
if publicKeyRing, err = crypto.BuildKeyRingArmored(publicKey); err != nil {
if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil {
return "", err
}
if privateKeyRing, err = crypto.BuildKeyRingArmored(privateKey); err != nil {
if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil {
return "", err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); err != nil {
if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil {
return "", err
}
if unlockedKeyObj, err = privateKeyObj.Unlock(passphrase); err != nil {
return "", err
}
if privateKeyRing, err = crypto.NewKeyRing(unlockedKeyObj); err != nil {
return "", err
}
@ -110,17 +112,22 @@ func EncryptSignMessageArmored(
// DecryptMessageArmored decrypts an armored PGP message given a private key and its passphrase
func DecryptMessageArmored(
privateKey, passphrase, ciphertext string,
privateKey string, passphrase []byte, ciphertext string,
) (plaintext string, err error) {
var privateKeyObj, privateKeyUnlocked *crypto.Key
var privateKeyRing *crypto.KeyRing
var pgpMessage *crypto.PGPMessage
var message *crypto.PlainMessage
if privateKeyRing, err = crypto.BuildKeyRingArmored(privateKey); err != nil {
if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil {
return "", err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); err != nil {
if privateKeyUnlocked, err = privateKeyObj.Unlock(passphrase); err != nil {
return "", err
}
if privateKeyRing, err = crypto.NewKeyRing(privateKeyUnlocked); err != nil {
return "", err
}
@ -139,21 +146,30 @@ func DecryptMessageArmored(
// and verifies the embedded signature.
// Returns the plain data or an error on signature verification failure.
func DecryptVerifyMessageArmored(
publicKey, privateKey, passphrase, ciphertext string,
publicKey, privateKey string, passphrase []byte, ciphertext string,
) (plaintext string, err error) {
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
var publicKeyRing, privateKeyRing *crypto.KeyRing
var pgpMessage *crypto.PGPMessage
var message *crypto.PlainMessage
if publicKeyRing, err = crypto.BuildKeyRingArmored(publicKey); err != nil {
if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil {
return "", err
}
if privateKeyRing, err = crypto.BuildKeyRingArmored(privateKey); err != nil {
if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil {
return "", err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); err != nil {
if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil {
return "", err
}
if unlockedKeyObj, err = privateKeyObj.Unlock(passphrase); err != nil {
return "", err
}
if privateKeyRing, err = crypto.NewKeyRing(unlockedKeyObj); err != nil {
return "", err
}
@ -172,24 +188,32 @@ func DecryptVerifyMessageArmored(
// 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,
publicKey, privateKey string, passphrase []byte, fileName string, plainData []byte,
) (keyPacket, dataPacket, signature []byte, err error) {
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
var publicKeyRing, privateKeyRing *crypto.KeyRing
var packets *crypto.PGPSplitMessage
var signatureObj *crypto.PGPSignature
var binMessage = crypto.NewPlainMessage(plainData)
if publicKeyRing, err = crypto.BuildKeyRingArmored(publicKey); err != nil {
if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil {
return nil, nil, nil, err
}
if privateKeyRing, err = crypto.BuildKeyRingArmored(privateKey); err != nil {
if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil {
return nil, nil, nil, err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); err != nil {
if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil {
return nil, nil, nil, err
}
if unlockedKeyObj, err = privateKeyObj.Unlock(passphrase); err != nil {
return nil, nil, nil, err
}
if privateKeyRing, err = crypto.NewKeyRing(unlockedKeyObj); err != nil {
return nil, nil, nil, err
}
@ -208,25 +232,34 @@ func EncryptSignAttachment(
// 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,
publicKey, privateKey string,
passphrase, keyPacket, dataPacket []byte,
armoredSignature string,
) (plainData []byte, err error) {
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
var publicKeyRing, privateKeyRing *crypto.KeyRing
var detachedSignature *crypto.PGPSignature
var message *crypto.PlainMessage
var packets = crypto.NewPGPSplitMessage(keyPacket, dataPacket)
if publicKeyRing, err = crypto.BuildKeyRingArmored(publicKey); err != nil {
if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil {
return nil, err
}
if privateKeyRing, err = crypto.BuildKeyRingArmored(privateKey); err != nil {
if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil {
return nil, err
}
if err = privateKeyRing.UnlockWithPassphrase(passphrase); err != nil {
if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil {
return nil, err
}
if unlockedKeyObj, err = privateKeyObj.Unlock(passphrase); err != nil {
return nil, err
}
if privateKeyRing, err = crypto.NewKeyRing(unlockedKeyObj); err != nil {
return nil, err
}

View file

@ -3,23 +3,23 @@ package helper
import (
"testing"
"github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/stretchr/testify/assert"
)
func TestAESEncryption(t *testing.T) {
var plaintext = "Symmetric secret"
var passphrase = "passphrase"
var passphrase = []byte("passphrase")
ciphertext, err := EncryptMessageWithToken(passphrase, plaintext)
ciphertext, err := EncryptMessageWithPassword(passphrase, plaintext)
if err != nil {
t.Fatal("Expected no error when encrypting, got:", err)
}
_, err = DecryptMessageWithToken("Wrong passphrase", ciphertext)
_, err = DecryptMessageWithPassword([]byte("Wrong passphrase"), ciphertext)
assert.EqualError(t, err, "gopenpgp: wrong password in symmetric decryption")
decrypted, err := DecryptMessageWithToken(passphrase, ciphertext)
decrypted, err := DecryptMessageWithPassword(passphrase, ciphertext)
if err != nil {
t.Fatal("Expected no error when decrypting, got:", err)
}

View file

@ -1,15 +1,16 @@
package helper
import (
"github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/crypto"
)
// ExplicitVerifyMessage contains explicitly the signature verification error, for gomobile users
type ExplicitVerifyMessage struct {
Message *crypto.PlainMessage
Message *crypto.PlainMessage
SignatureVerificationError *crypto.SignatureVerificationError
}
// DecryptVerifyMessageArmored decrypts an armored PGP message given a private key and its passphrase
// DecryptExplicitVerify decrypts an armored PGP message given a private key and its passphrase
// and verifies the embedded signature.
// Returns the plain data or an error on signature verification failure.
func DecryptExplicitVerify(
@ -19,7 +20,7 @@ func DecryptExplicitVerify(
) (*ExplicitVerifyMessage, error) {
var explicitVerify *ExplicitVerifyMessage
message, err := privateKeyRing.Decrypt(pgpMessage, publicKeyRing, verifyTime);
message, err := privateKeyRing.Decrypt(pgpMessage, publicKeyRing, verifyTime)
if err != nil {
castedErr, isType := err.(crypto.SignatureVerificationError)
@ -28,12 +29,12 @@ func DecryptExplicitVerify(
}
explicitVerify = &ExplicitVerifyMessage{
Message: message,
Message: message,
SignatureVerificationError: &castedErr,
}
} else {
explicitVerify = &ExplicitVerifyMessage{
Message: message,
Message: message,
SignatureVerificationError: nil,
}
}

View file

@ -3,20 +3,22 @@ package helper
import (
"testing"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/crypto"
"github.com/ProtonMail/gopenpgp/v2/constants"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/stretchr/testify/assert"
)
func TestIOSSignedMessageDecryption(t *testing.T) {
testPrivateKeyRing, _ := crypto.BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
testPublicKeyRing, _ := crypto.BuildKeyRingArmored(readTestFile("mime_publicKey", false))
privateKey, _ := crypto.NewKeyFromArmored(readTestFile("keyring_privateKey", false))
// Password defined in base_test
err := testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
privateKey, err := privateKey.Unlock(testMailboxPassword)
if err != nil {
t.Fatal("Expected no error unlocking privateKey, got:", err)
}
testPrivateKeyRing, _ := crypto.NewKeyRing(privateKey)
publicKey, _ := crypto.NewKeyFromArmored(readTestFile("mime_publicKey", false))
testPublicKeyRing, _ := crypto.NewKeyRing(publicKey)
pgpMessage, err := crypto.NewPGPMessageFromArmored(readTestFile("message_signed", false))
if err != nil {
@ -31,7 +33,8 @@ func TestIOSSignedMessageDecryption(t *testing.T) {
assert.Exactly(t, constants.SIGNATURE_NO_VERIFIER, decrypted.SignatureVerificationError.Status)
assert.Exactly(t, readTestFile("message_plaintext", true), decrypted.Message.GetString())
testPublicKeyRing, _ = crypto.BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
publicKey, _ = crypto.NewKeyFromArmored(readTestFile("keyring_publicKey", false))
testPublicKeyRing, _ = crypto.NewKeyRing(publicKey)
pgpMessage, err = testPublicKeyRing.Encrypt(decrypted.Message, testPrivateKeyRing)
if err != nil {

48
helper/key.go Normal file
View file

@ -0,0 +1,48 @@
package helper
import (
"github.com/ProtonMail/gopenpgp/v2/crypto"
)
// UpdatePrivateKeyPassphrase decrypts the given armored privateKey with oldPassphrase,
// re-encrypts it with newPassphrase, and returns the new armored key.
func UpdatePrivateKeyPassphrase(
privateKey string,
oldPassphrase, newPassphrase []byte,
) (string, error) {
key, err := crypto.NewKeyFromArmored(privateKey)
if err != nil {
return "", err
}
unlocked, err := key.Unlock(oldPassphrase)
if err != nil {
return "", err
}
locked, err := unlocked.Lock(newPassphrase)
if err != nil {
return "", err
}
unlocked.ClearPrivateParams()
return locked.Armor()
}
// GenerateKey generates a key of the given keyType ("rsa" or "x25519"), encrypts it, and returns an armored string.
// If keyType is "rsa", bits is the RSA bitsize of the key.
// If keyType is "x25519" bits is unused.
func GenerateKey(name, email string, passphrase []byte, keyType string, bits int) (string, error) {
key, err := crypto.GenerateKey(name, email, keyType, bits)
if err != nil {
return "", err
}
locked, err := key.Lock(passphrase)
if err != nil {
return "", err
}
key.ClearPrivateParams()
return locked.Armor()
}

View file

@ -1,8 +1,9 @@
package internal
import (
"golang.org/x/crypto/openpgp/armor"
"strings"
"golang.org/x/crypto/openpgp/armor"
)
// Unarmor unarmors an armored string.

View file

@ -4,7 +4,7 @@ package internal
import (
"regexp"
"github.com/ProtonMail/gopenpgp/constants"
"github.com/ProtonMail/gopenpgp/v2/constants"
)
// TrimNewlines removes whitespace from the end of each line of the input

View file

@ -28,8 +28,8 @@ func DecryptWithoutIntegrity(key, input, iv []byte) ([]byte, error) {
return EncryptWithoutIntegrity(key, input, iv)
}
// DeriveKey derives a key from a password using scrypt. N should be set to the
// DeriveKey derives a key from a password using scrypt. n should be set to the
// highest power of 2 you can derive within 100 milliseconds.
func DeriveKey(password string, salt []byte, N int) ([]byte, error) {
return scrypt.Key([]byte(password), salt, N, 8, 1, 32)
func DeriveKey(password string, salt []byte, n int) ([]byte, error) {
return scrypt.Key([]byte(password), salt, n, 8, 1, 32)
}

View file

@ -2,8 +2,9 @@ package subtle
import (
"encoding/hex"
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSubtle_EncryptWithoutIntegrity(t *testing.T) {