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:
parent
136c0a5495
commit
54f45d0471
46 changed files with 2588 additions and 1770 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,3 +7,4 @@ vendor
|
|||
*.html
|
||||
reports
|
||||
.idea
|
||||
v2
|
||||
54
.golangci.yml
Normal file
54
.golangci.yml
Normal 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]\
|
||||
39
.travis.yml
39
.travis.yml
|
|
@ -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
498
CHANGELOG.md
Normal 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`
|
||||
17
Changelog.md
17
Changelog.md
|
|
@ -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`
|
||||
|
||||
|
|
@ -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
251
README.md
|
|
@ -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()
|
||||
```
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
24
build.sh
24
build.sh
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
455
crypto/key.go
455
crypto/key.go
|
|
@ -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
128
crypto/key_clear.go
Normal 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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
151
crypto/password.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
221
crypto/sessionkey.go
Normal 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
145
crypto/sessionkey_test.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
11
go.mod
|
|
@ -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
20
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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-----$")
|
||||
|
||||
|
|
|
|||
119
helper/helper.go
119
helper/helper.go
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
48
helper/key.go
Normal 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()
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue