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
|
*.html
|
||||||
reports
|
reports
|
||||||
.idea
|
.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
|
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:
|
go:
|
||||||
- 1.11.x
|
- 1.11.x
|
||||||
- 1.12.x
|
- 1.12.x
|
||||||
env:
|
- 1.13.x
|
||||||
- GO111MODULE=on GOFLAGS=-mod=readonly
|
|
||||||
|
# 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
|
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
|
GopenPGP is a high-level OpenPGP library built on top of [a fork of the golang
|
||||||
crypto library](https://github.com/ProtonMail/crypto).
|
crypto library](https://github.com/ProtonMail/crypto).
|
||||||
|
|
@ -10,7 +10,6 @@ crypto library](https://github.com/ProtonMail/crypto).
|
||||||
- [Download/Install](#downloadinstall)
|
- [Download/Install](#downloadinstall)
|
||||||
- [Documentation](#documentation)
|
- [Documentation](#documentation)
|
||||||
- [Using with Go Mobile](#using-with-go-mobile)
|
- [Using with Go Mobile](#using-with-go-mobile)
|
||||||
- [Other notes](#other-notes)
|
|
||||||
- [Full documentation](#full-documentation)
|
- [Full documentation](#full-documentation)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
- [Set up](#set-up)
|
- [Set up](#set-up)
|
||||||
|
|
@ -24,102 +23,89 @@ crypto library](https://github.com/ProtonMail/crypto).
|
||||||
<!-- /TOC -->
|
<!-- /TOC -->
|
||||||
|
|
||||||
## Download/Install
|
## 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
|
replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20191122234321-e77a1f03baa0
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
(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
|
## 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
|
## Using with Go Mobile
|
||||||
|
The use with gomobile is still to be documented
|
||||||
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
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Set up
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/ProtonMail/gopenpgp/crypto"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Encrypt / Decrypt with password
|
### Encrypt / Decrypt with password
|
||||||
|
|
||||||
```go
|
```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
|
// Encrypt data with password
|
||||||
armor, err := helper.EncryptMessageWithToken(password, "my message")
|
armor, err := helper.EncryptMessageWithPassword(password, "my message")
|
||||||
|
|
||||||
// Decrypt data with password
|
// 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
|
```go
|
||||||
import "github.com/ProtonMail/gopenpgp/constants"
|
import "github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
import "github.com/ProtonMail/gopenpgp/helper"
|
|
||||||
|
|
||||||
// Encrypt data with password
|
const password = []byte("hunter2")
|
||||||
armor, err := helper.EncryptMessageWithTokenAlgo(password, "my message", constants.ThreeDES)
|
|
||||||
|
|
||||||
// Decrypt data with password
|
|
||||||
message, err := helper.DecryptMessageWithToken(password, armor)
|
|
||||||
```
|
|
||||||
|
|
||||||
To encrypt binary data, reuse the key multiple times, or use more advanced modes:
|
|
||||||
```go
|
|
||||||
import "github.com/ProtonMail/gopenpgp/constants"
|
|
||||||
|
|
||||||
var key = crypto.NewSymmetricKeyFromToken("my secret password", constants.AES256)
|
|
||||||
var message = crypto.NewPlainMessage(data)
|
var message = crypto.NewPlainMessage(data)
|
||||||
|
// Or
|
||||||
|
message = crypto.NewPlainMessageFromString(string)
|
||||||
|
|
||||||
// Encrypt data with password
|
// 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
|
// Decrypt data with password
|
||||||
decrypted, err := key.Decrypt(password, encrypted)
|
decrypted, err := DecryptMessageWithPassword(encrypted, password)
|
||||||
|
|
||||||
//Original message in decrypted.GetBinary()
|
//Original message in decrypted.GetBinary()
|
||||||
```
|
```
|
||||||
|
|
@ -127,7 +113,7 @@ decrypted, err := key.Decrypt(password, encrypted)
|
||||||
### Encrypt / Decrypt with PGP keys
|
### Encrypt / Decrypt with PGP keys
|
||||||
|
|
||||||
```go
|
```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
|
// put keys in backtick (``) to avoid errors caused by spaces or tabs
|
||||||
const pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
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
|
-----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
|
// encrypt message using public key
|
||||||
armor, err := helper.EncryptMessageArmored(pubkey, "plain text")
|
armor, err := helper.EncryptMessageArmored(pubkey, "plain text")
|
||||||
|
|
@ -162,40 +148,51 @@ decrypted, err := helper.DecryptVerifyMessageArmored(pubkey, privkey, passphrase
|
||||||
With binary data or advanced modes:
|
With binary data or advanced modes:
|
||||||
```go
|
```go
|
||||||
// Keys initialization as before (omitted)
|
// 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)
|
pgpMessage, err := publicKeyRing.Encrypt(binMessage, privateKeyRing)
|
||||||
|
|
||||||
// Armored message in pgpMessage.GetArmored()
|
// Armored message in pgpMessage.GetArmored()
|
||||||
// pgpMessage can be obtained from NewPGPMessageFromArmored(ciphertext)
|
// 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())
|
message, err := privateKeyRing.Decrypt(pgpMessage, publicKeyRing, crypto.GetUnixTime())
|
||||||
|
|
||||||
|
privateKeyRing.ClearPrivateParams()
|
||||||
|
|
||||||
// Original data in message.GetString()
|
// Original data in message.GetString()
|
||||||
// `err` can be a SignatureVerificationError
|
// `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.
|
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.
|
The library supports RSA with different key lengths or Curve25519 keys.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
const (
|
const (
|
||||||
localPart = "name.surname"
|
name = "Max Mustermann"
|
||||||
domain = "example.com"
|
email = "max.mustermann@example.com"
|
||||||
passphrase = "LongSecret"
|
passphrase = []byte("LongSecret")
|
||||||
rsaBits = 2048
|
rsaBits = 2048
|
||||||
ecBits = 256
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RSA
|
// RSA, string
|
||||||
rsaKey, err := crypto.GenerateKey(localPart, domain, passphrase, "rsa", rsaBits)
|
rsaKey, err := helper.GenerateKey(name, email, passphrase, "rsa", rsaBits)
|
||||||
|
|
||||||
// Curve25519
|
// Curve25519, string
|
||||||
ecKey, err := crypto.GenerateKey(localPart, domain, passphrase, "x25519", ecBits)
|
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
|
### Detached signatures for plain text messages
|
||||||
|
|
@ -206,14 +203,14 @@ The output is an armored signature.
|
||||||
```go
|
```go
|
||||||
const privkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
const privkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
...
|
...
|
||||||
-----END PGP PRIVATE KEY BLOCK-----` // encrypted private key
|
-----END PGP PRIVATE KEY BLOCK-----` // Encrypted private key
|
||||||
const passphrase = "LongSecret"
|
const passphrase = []byte("LongSecret") // Private key passphrase
|
||||||
const trimNewlines = false
|
|
||||||
|
|
||||||
var message = NewPlaintextMessage("Verified message")
|
var message = crypto.NewPlaintextMessage("Verified message")
|
||||||
|
|
||||||
signingKeyRing, err := crypto.BuildKeyRingArmored(privkey)
|
privateKeyObj, err := crypto.NewKeyFromArmored(privkey)
|
||||||
signingKeyRing.UnlockWithPassphrase(passphrase) // if private key is locked with passphrase
|
unlockedKeyObj = privateKeyObj.Unlock(passphrase)
|
||||||
|
signingKeyRing, err := crypto.NewKeyRing(unlockedKeyObj)
|
||||||
|
|
||||||
pgpSignature, err := signingKeyRing.SignDetached(message, trimNewlines)
|
pgpSignature, err := signingKeyRing.SignDetached(message, trimNewlines)
|
||||||
|
|
||||||
|
|
@ -232,9 +229,11 @@ const signature = `-----BEGIN PGP SIGNATURE-----
|
||||||
...
|
...
|
||||||
-----END PGP SIGNATURE-----`
|
-----END PGP SIGNATURE-----`
|
||||||
|
|
||||||
message := NewPlaintextMessage("Verified message")
|
message := crypto.NewPlaintextMessage("Verified message")
|
||||||
pgpSignature, err := NewPGPSignatureFromArmored(signature)
|
pgpSignature, err := crypto.NewPGPSignatureFromArmored(signature)
|
||||||
signingKeyRing, err := crypto.BuildKeyRingArmored(pubkey)
|
|
||||||
|
publicKeyObj, err := crypto.NewKeyFromArmored(pubkey)
|
||||||
|
signingKeyRing, err := crypto.NewKeyFromArmored(publicKeyObj)
|
||||||
|
|
||||||
err := signingKeyRing.VerifyDetached(message, pgpSignature, crypto.GetUnixTime())
|
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
|
-----END PGP PRIVATE KEY BLOCK-----` // encrypted private key
|
||||||
const passphrase = "LongSecret"
|
const passphrase = "LongSecret"
|
||||||
|
|
||||||
var message = NewPlainMessage(data)
|
var message = crypto.NewPlainMessage(data)
|
||||||
|
|
||||||
signingKeyRing, err := crypto.BuildKeyRingArmored(privkey)
|
privateKeyObj, err := crypto.NewKeyFromArmored(privkey)
|
||||||
signingKeyRing.UnlockWithPassphrase(passphrase) // if private key is locked with passphrase
|
unlockedKeyObj := privateKeyObj.Unlock(passphrase)
|
||||||
|
signingKeyRing, err := crypto.NewKeyRing(unlockedKeyObj)
|
||||||
|
|
||||||
pgpSignature, err := signingKeyRing.SignDetached(message)
|
pgpSignature, err := signingKeyRing.SignDetached(message)
|
||||||
|
|
||||||
|
|
@ -273,9 +273,11 @@ const signature = `-----BEGIN PGP SIGNATURE-----
|
||||||
...
|
...
|
||||||
-----END PGP SIGNATURE-----`
|
-----END PGP SIGNATURE-----`
|
||||||
|
|
||||||
message := NewPlainMessage("Verified message")
|
message := crypto.NewPlainMessage("Verified message")
|
||||||
pgpSignature, err := NewPGPSignatureFromArmored(signature)
|
pgpSignature, err := crypto.NewPGPSignatureFromArmored(signature)
|
||||||
signingKeyRing, err := crypto.BuildKeyRingArmored(pubkey)
|
|
||||||
|
publicKeyObj, err := crypto.NewKeyFromArmored(pubkey)
|
||||||
|
signingKeyRing, err := crypto.NewKeyFromArmored(publicKeyObj)
|
||||||
|
|
||||||
err := signingKeyRing.VerifyDetached(message, pgpSignature, crypto.GetUnixTime())
|
err := signingKeyRing.VerifyDetached(message, pgpSignature, crypto.GetUnixTime())
|
||||||
|
|
||||||
|
|
@ -287,35 +289,56 @@ if err == nil {
|
||||||
### Cleartext signed messages
|
### Cleartext signed messages
|
||||||
```go
|
```go
|
||||||
// Keys initialization as before (omitted)
|
// Keys initialization as before (omitted)
|
||||||
|
armored, err := helper.SignCleartextMessageArmored(privateKey, passphrase, plaintext)
|
||||||
armored, err := SignCleartextMessageArmored(privateKey, passphrase, plaintext)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To verify the message it has to be provided unseparated to the library.
|
To verify the message it has to be provided unseparated to the library.
|
||||||
If verification fails an error will be returned.
|
If verification fails an error will be returned.
|
||||||
```go
|
```go
|
||||||
// Keys initialization as before (omitted)
|
// Keys initialization as before (omitted)
|
||||||
|
verifiedPlainText, err := helper.VerifyCleartextMessageArmored(publicKey, armored, crypto.GetUnixTime())
|
||||||
var verifyTime = crypto.GetUnixTime()
|
|
||||||
|
|
||||||
verifiedPlainText, err := VerifyCleartextMessageArmored(publicKey, armored, verifyTime)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Encrypting and decrypting session Keys
|
### Encrypting and decrypting session Keys
|
||||||
|
A session key can be generated, encrypted to a Asymmetric/Symmetric key packet and obtained from it
|
||||||
```go
|
```go
|
||||||
// Keys initialization as before (omitted)
|
// Keys initialization as before (omitted)
|
||||||
|
|
||||||
symmetricKey := &SymmetricKey{
|
sessionKey, err := crypto.GenerateSessionKey()
|
||||||
Key: "RandomTokenabcdef",
|
|
||||||
Algo: constants.AES256,
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
```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"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/constants"
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
"github.com/ProtonMail/gopenpgp/internal"
|
"github.com/ProtonMail/gopenpgp/v2/internal"
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp/armor"
|
"golang.org/x/crypto/openpgp/armor"
|
||||||
)
|
)
|
||||||
|
|
@ -33,11 +33,12 @@ func ArmorWithType(input []byte, armorType string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
_, err = w.Write(input)
|
if _, err = w.Write(input); err != nil {
|
||||||
if err != nil {
|
return "", err
|
||||||
|
}
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
w.Close()
|
|
||||||
return b.String(), nil
|
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 {
|
if ap.err != nil {
|
||||||
return nil, ap.err
|
return nil, ap.err
|
||||||
}
|
}
|
||||||
(*ap.w).Close()
|
if err := (*ap.w).Close(); err != nil {
|
||||||
(*ap.pipe).Close()
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := (*ap.pipe).Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
ap.done.Wait()
|
ap.done.Wait()
|
||||||
if ap.garbageCollector > 0 {
|
if ap.garbageCollector > 0 {
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
|
|
||||||
|
|
@ -10,44 +10,46 @@ import (
|
||||||
// const testAttachmentEncrypted =
|
// const testAttachmentEncrypted =
|
||||||
// `0ksB0fHC6Duezx/0TqpK/82HSl8+qCY0c2BCuyrSFoj6Dubd93T3//32jVYa624NYvfvxX+UxFKYKJxG09gFsU1IVc87cWvUgmUmgjU=`
|
// `0ksB0fHC6Duezx/0TqpK/82HSl8+qCY0c2BCuyrSFoj6Dubd93T3//32jVYa624NYvfvxX+UxFKYKJxG09gFsU1IVc87cWvUgmUmgjU=`
|
||||||
|
|
||||||
|
var testAttachmentKey, _ = base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
|
||||||
|
|
||||||
func TestAttachmentGetKey(t *testing.T) {
|
func TestAttachmentGetKey(t *testing.T) {
|
||||||
testKeyPacketsDecoded, err := base64.StdEncoding.DecodeString(readTestFile("attachment_keypacket", false))
|
testKeyPacketsDecoded, err := base64.StdEncoding.DecodeString(readTestFile("attachment_keypacket", false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while decoding base64 KeyPacket, got:", err)
|
t.Fatal("Expected no error while decoding base64 KeyPacket, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(testKeyPacketsDecoded)
|
sessionKey, err := keyRingTestPrivate.DecryptSessionKey(testKeyPacketsDecoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while decrypting KeyPacket, got:", err)
|
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) {
|
func TestAttachmentSetKey(t *testing.T) {
|
||||||
keyPackets, err := testPublicKeyRing.EncryptSessionKey(testSymmetricKey)
|
keyPackets, err := keyRingTestPublic.EncryptSessionKey(testSessionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while encrypting attachment key, got:", err)
|
t.Fatal("Expected no error while encrypting attachment key, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
symmetricKey, err := testPrivateKeyRing.DecryptSessionKey(keyPackets)
|
sessionKey, err := keyRingTestPrivate.DecryptSessionKey(keyPackets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while decrypting attachment key, got:", err)
|
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) {
|
func TestAttachmentEncryptDecrypt(t *testing.T) {
|
||||||
var testAttachmentCleartext = "cc,\ndille."
|
var testAttachmentCleartext = "cc,\ndille."
|
||||||
var message = NewPlainMessage([]byte(testAttachmentCleartext))
|
var message = NewPlainMessage([]byte(testAttachmentCleartext))
|
||||||
|
|
||||||
encSplit, err := testPrivateKeyRing.EncryptAttachment(message, "s.txt")
|
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "s.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while encrypting attachment, got:", err)
|
t.Fatal("Expected no error while encrypting attachment, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
redecData, err := testPrivateKeyRing.DecryptAttachment(encSplit)
|
redecData, err := keyRingTestPrivate.DecryptAttachment(encSplit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while decrypting attachment, got:", err)
|
t.Fatal("Expected no error while decrypting attachment, got:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -59,14 +61,14 @@ func TestAttachmentEncrypt(t *testing.T) {
|
||||||
var testAttachmentCleartext = "cc,\ndille."
|
var testAttachmentCleartext = "cc,\ndille."
|
||||||
var message = NewPlainMessage([]byte(testAttachmentCleartext))
|
var message = NewPlainMessage([]byte(testAttachmentCleartext))
|
||||||
|
|
||||||
encSplit, err := testPrivateKeyRing.EncryptAttachment(message, "s.txt")
|
encSplit, err := keyRingTestPrivate.EncryptAttachment(message, "s.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while encrypting attachment, got:", err)
|
t.Fatal("Expected no error while encrypting attachment, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pgpMessage := NewPGPMessage(encSplit.GetBinary())
|
pgpMessage := NewPGPMessage(encSplit.GetBinary())
|
||||||
|
|
||||||
redecData, err := testPrivateKeyRing.Decrypt(pgpMessage, nil, 0)
|
redecData, err := keyRingTestPrivate.Decrypt(pgpMessage, nil, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while decrypting attachment, got:", err)
|
t.Fatal("Expected no error while decrypting attachment, got:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +80,7 @@ func TestAttachmentDecrypt(t *testing.T) {
|
||||||
var testAttachmentCleartext = "cc,\ndille."
|
var testAttachmentCleartext = "cc,\ndille."
|
||||||
var message = NewPlainMessage([]byte(testAttachmentCleartext))
|
var message = NewPlainMessage([]byte(testAttachmentCleartext))
|
||||||
|
|
||||||
encrypted, err := testPrivateKeyRing.Encrypt(message, nil)
|
encrypted, err := keyRingTestPrivate.Encrypt(message, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while encrypting attachment, got:", err)
|
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)
|
t.Fatal("Expected no error while unarmoring, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
redecData, err := testPrivateKeyRing.DecryptAttachment(pgpSplitMessage)
|
redecData, err := keyRingTestPrivate.DecryptAttachment(pgpSplitMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while decrypting attachment, got:", err)
|
t.Fatal("Expected no error while decrypting attachment, got:", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,21 @@ package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"strings"
|
"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 {
|
func readTestFile(name string, trimNewlines bool) string {
|
||||||
data, err := ioutil.ReadFile("testdata/" + name)
|
data, err := ioutil.ReadFile("testdata/" + name) //nolint
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -17,3 +25,48 @@ func readTestFile(name string, trimNewlines bool) string {
|
||||||
}
|
}
|
||||||
return string(data)
|
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"
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/armor"
|
"github.com/ProtonMail/gopenpgp/v2/armor"
|
||||||
"github.com/ProtonMail/gopenpgp/constants"
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp"
|
"golang.org/x/crypto/openpgp"
|
||||||
|
xarmor "golang.org/x/crypto/openpgp/armor"
|
||||||
"golang.org/x/crypto/openpgp/packet"
|
"golang.org/x/crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsKeyExpired checks whether the given (unarmored, binary) key is expired.
|
// Key contains a single private or public key
|
||||||
func IsKeyExpired(publicKey []byte) (bool, error) {
|
type Key struct {
|
||||||
now := getNow()
|
// PGP entities in this keyring.
|
||||||
pubKeyReader := bytes.NewReader(publicKey)
|
entity *openpgp.Entity
|
||||||
pubKeyEntries, err := openpgp.ReadKeyRing(pubKeyReader)
|
}
|
||||||
|
|
||||||
|
// --- 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 {
|
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 false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, errors.New("keys expired")
|
|
||||||
|
return key.entity.PrivateKey.Encrypted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsArmoredKeyExpired checks whether the given armored key is expired.
|
// IsUnlocked checks if a private key is unlocked
|
||||||
func IsArmoredKeyExpired(publicKey string) (bool, error) {
|
func (key *Key) IsUnlocked() (bool, error) {
|
||||||
rawPubKey, err := armor.Unarmor(publicKey)
|
if key.entity.PrivateKey == nil {
|
||||||
if err != nil {
|
return true, errors.New("gopenpgp: a public key cannot be unlocked")
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
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(
|
func generateKey(
|
||||||
name, email, passphrase, keyType string,
|
name, email string,
|
||||||
|
keyType string,
|
||||||
bits int,
|
bits int,
|
||||||
prime1, prime2, prime3, prime4 []byte,
|
prime1, prime2, prime3, prime4 []byte,
|
||||||
) (string, error) {
|
) (*Key, error) {
|
||||||
if len(email) <= 0 {
|
if len(email) == 0 {
|
||||||
return "", errors.New("invalid email format")
|
return nil, errors.New("gopenpgp: invalid email format")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(name) <= 0 {
|
if len(name) == 0 {
|
||||||
return "", errors.New("invalid name format")
|
return nil, errors.New("gopenpgp: invalid name format")
|
||||||
}
|
}
|
||||||
|
|
||||||
comments := ""
|
comments := ""
|
||||||
|
|
@ -84,114 +383,16 @@ func generateKey(
|
||||||
|
|
||||||
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
|
newEntity, err := openpgp.NewEntity(name, comments, email, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := newEntity.SelfSign(nil); err != nil {
|
if err := newEntity.SelfSign(nil); err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rawPwd := []byte(passphrase)
|
if newEntity.PrivateKey == nil {
|
||||||
if newEntity.PrivateKey != nil && !newEntity.PrivateKey.Encrypted {
|
return nil, errors.New("gopenpgp: error in generating private key")
|
||||||
if err := newEntity.PrivateKey.Encrypt(rawPwd); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sub := range newEntity.Subkeys {
|
return &Key{newEntity}, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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 (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"io/ioutil"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"golang.org/x/crypto/openpgp/armor"
|
||||||
|
|
||||||
"golang.org/x/crypto/rsa"
|
"golang.org/x/crypto/rsa"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const name = "Richard M. Stallman"
|
const keyTestName = "Max Mustermann"
|
||||||
const domain = "rms@protonmail.ch"
|
const keyTestDomain = "max.mustermann@protonmail.ch"
|
||||||
|
|
||||||
var passphrase = "I love GNU"
|
var keyTestPassphrase = []byte("I love GNU")
|
||||||
var rsaKey, ecKey, rsaPublicKey, ecPublicKey string
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rsaPrivateKeyRing *KeyRing
|
keyTestArmoredRSA string
|
||||||
ecPrivateKeyRing *KeyRing
|
keyTestArmoredEC string
|
||||||
rsaPublicKeyRing *KeyRing
|
keyTestRSA *Key
|
||||||
ecPublicKeyRing *KeyRing
|
keyTestEC *Key
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateKeys(t *testing.T) {
|
func initGenerateKeys() {
|
||||||
rsaKey, err = GenerateKey(name, domain, passphrase, "rsa", 1024)
|
var err error
|
||||||
|
keyTestRSA, err = GenerateKey(keyTestName, keyTestDomain, "rsa", 1024)
|
||||||
if err != nil {
|
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 {
|
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-----$")
|
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP PRIVATE KEY BLOCK-----.*-----END PGP PRIVATE KEY BLOCK-----$")
|
||||||
assert.Regexp(t, rTest, rsaKey)
|
assert.Regexp(t, rTest, noPasswordRSA)
|
||||||
assert.Regexp(t, rTest, ecKey)
|
assert.Regexp(t, rTest, noPasswordEC)
|
||||||
|
assert.Regexp(t, rTest, keyTestArmoredRSA)
|
||||||
|
assert.Regexp(t, rTest, keyTestArmoredEC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateKeyRings(t *testing.T) {
|
func TestLockUnlockKeys(t *testing.T) {
|
||||||
rsaPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaKey))
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Cannot read RSA key:", err)
|
t.Fatal("Cannot unarmor key:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rsaPublicKey, err = rsaPrivateKeyRing.GetArmoredPublicKey()
|
_, err = publicKey.IsLocked()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
t.Fatal("Cannot extract RSA public key:", err)
|
t.Fatal("Should not be able to check locked on public key:")
|
||||||
}
|
}
|
||||||
|
|
||||||
rsaPublicKeyRing, err = ReadArmoredKeyRing(strings.NewReader(rsaPublicKey))
|
_, err = publicKey.IsUnlocked()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
t.Fatal("Cannot read RSA public key:", err)
|
t.Fatal("Should not be able to check unlocked on public key:")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = rsaPrivateKeyRing.UnlockWithPassphrase(passphrase)
|
_, err = publicKey.Unlock(testMailboxPassword)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
t.Fatal("Cannot decrypt RSA key:", err)
|
t.Fatal("Should not be able to unlock public key:")
|
||||||
}
|
}
|
||||||
|
|
||||||
ecPrivateKeyRing, err = ReadArmoredKeyRing(strings.NewReader(ecKey))
|
_, err = publicKey.Lock(keyTestPassphrase)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
t.Fatal("Cannot read EC key:", err)
|
t.Fatal("Should not be able to lock public key:")
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdatePrivateKeysPassphrase(t *testing.T) {
|
func testLockUnlockKey(t *testing.T, armoredKey string, pass []byte) {
|
||||||
newPassphrase := "I like GNU"
|
var err error
|
||||||
rsaKey, err = UpdatePrivateKeyPassphrase(rsaKey, passphrase, newPassphrase)
|
|
||||||
|
lockedKey, err := NewKeyFromArmored(armoredKey)
|
||||||
if err != nil {
|
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 {
|
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() {
|
func ExampleKey_PrintFingerprints() {
|
||||||
_, _ = PrintFingerprints(readTestFile("keyring_publicKey", false))
|
keyringKey, _ := NewKeyFromArmored(readTestFile("keyring_publicKey", false))
|
||||||
|
keyringKey.PrintFingerprints()
|
||||||
// Output:
|
// Output:
|
||||||
// SubKey:37e4bcf09b36e34012d10c0247dc67b5cb8267f6
|
// SubKey:37e4bcf09b36e34012d10c0247dc67b5cb8267f6
|
||||||
// PrimaryKey:6e8ba229b0cccaf6962f97953eb6259edf21df24
|
// PrimaryKey:6e8ba229b0cccaf6962f97953eb6259edf21df24
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsArmoredKeyExpired(t *testing.T) {
|
func TestIsExpired(t *testing.T) {
|
||||||
rsaRes, err := IsArmoredKeyExpired(rsaPublicKey)
|
assert.Exactly(t, false, keyTestRSA.IsExpired())
|
||||||
|
assert.Exactly(t, false, keyTestEC.IsExpired())
|
||||||
|
|
||||||
|
expiredKey, err := NewKeyFromArmored(readTestFile("key_expiredKey", false))
|
||||||
if err != nil {
|
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 {
|
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, true, expiredKey.IsExpired())
|
||||||
assert.Exactly(t, false, ecRes)
|
assert.Exactly(t, true, futureKey.IsExpired())
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateKeyWithPrimes(t *testing.T) {
|
func TestGenerateKeyWithPrimes(t *testing.T) {
|
||||||
|
|
@ -139,24 +217,96 @@ func TestGenerateKeyWithPrimes(t *testing.T) {
|
||||||
prime4, _ := base64.StdEncoding.DecodeString(
|
prime4, _ := base64.StdEncoding.DecodeString(
|
||||||
"58UEDXTX29Q9JqvuE3Tn+Qj275CXBnJbA8IVM4d05cPYAZ6H43bPN01pbJqJTJw/cuFxs+8C+HNw3/MGQOExqw==")
|
"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 {
|
if err != nil {
|
||||||
t.Fatal("Cannot generate RSA key:", err)
|
t.Fatal("Cannot generate RSA key with primes:", err)
|
||||||
}
|
|
||||||
rTest := regexp.MustCompile("(?s)^-----BEGIN PGP PRIVATE KEY BLOCK-----.*-----END PGP PRIVATE KEY BLOCK-----$")
|
|
||||||
assert.Regexp(t, rTest, staticRsaKey)
|
|
||||||
|
|
||||||
staticRsaKeyRing, err := ReadArmoredKeyRing(strings.NewReader(staticRsaKey))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Cannot read RSA key:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = staticRsaKeyRing.UnlockWithPassphrase(passphrase)
|
pk := staticRsaKey.entity.PrivateKey.PrivateKey.(*rsa.PrivateKey)
|
||||||
if err != nil {
|
assert.Exactly(t, prime1, pk.Primes[0].Bytes())
|
||||||
t.Fatal("Cannot decrypt RSA key:", err)
|
assert.Exactly(t, prime2, pk.Primes[1].Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
pk := staticRsaKeyRing.GetEntities()[0].PrivateKey.PrivateKey.(*rsa.PrivateKey)
|
func TestCheckIntegrity(t *testing.T) {
|
||||||
assert.Exactly(t, prime1, pk.Primes[1].Bytes())
|
isVerified, err := keyTestRSA.Check()
|
||||||
assert.Exactly(t, prime2, pk.Primes[0].Bytes())
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/crypto/openpgp"
|
"golang.org/x/crypto/openpgp"
|
||||||
"golang.org/x/crypto/openpgp/armor"
|
|
||||||
"golang.org/x/crypto/openpgp/packet"
|
"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.
|
// KeyRing contains multiple private and public keys.
|
||||||
|
|
@ -32,13 +24,52 @@ type Identity struct {
|
||||||
Email string
|
Email string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEntities returns openpgp entities contained in this KeyRing.
|
// --- New keyrings
|
||||||
func (keyRing *KeyRing) GetEntities() openpgp.EntityList {
|
|
||||||
return keyRing.entities
|
// 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.
|
// --- Add keys to keyring
|
||||||
func (keyRing *KeyRing) GetSigningEntity() (*openpgp.Entity, error) {
|
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
|
var signEntity *openpgp.Entity
|
||||||
|
|
||||||
for _, e := range keyRing.entities {
|
for _, e := range keyRing.entities {
|
||||||
|
|
@ -58,218 +89,20 @@ func (keyRing *KeyRing) GetSigningEntity() (*openpgp.Entity, error) {
|
||||||
return signEntity, nil
|
return signEntity, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock tries to unlock as many keys as possible with the following password. Note
|
// --- Extract info from key
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entity.Subkeys can be used for encryption
|
// CountEntities returns the number of entities in the keyring
|
||||||
for _, subKey := range e.Subkeys {
|
func (keyRing *KeyRing) CountEntities() int {
|
||||||
if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage ||
|
return len(keyRing.entities)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnlockWithPassphrase is a wrapper for Unlock that uses strings
|
// CountDecryptionEntities returns the number of entities in the keyring
|
||||||
func (keyRing *KeyRing) UnlockWithPassphrase(passphrase string) error {
|
func (keyRing *KeyRing) CountDecryptionEntities() int {
|
||||||
return keyRing.Unlock([]byte(passphrase))
|
return len(keyRing.entities.DecryptionKeys())
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identities returns the list of identities associated with this key ring.
|
// Identities returns the list of identities associated with this key ring.
|
||||||
func (keyRing *KeyRing) Identities() []*Identity {
|
func (keyRing *KeyRing) GetIdentities() []*Identity {
|
||||||
var identities []*Identity
|
var identities []*Identity
|
||||||
for _, e := range keyRing.entities {
|
for _, e := range keyRing.entities {
|
||||||
for _, id := range e.Identities {
|
for _, id := range e.Identities {
|
||||||
|
|
@ -282,28 +115,16 @@ func (keyRing *KeyRing) Identities() []*Identity {
|
||||||
return identities
|
return identities
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyIds returns array of IDs of keys in this KeyRing.
|
// GetKeyIDs returns array of IDs of keys in this KeyRing.
|
||||||
func (keyRing *KeyRing) KeyIds() []uint64 {
|
func (keyRing *KeyRing) GetKeyIDs() []uint64 {
|
||||||
var res []uint64
|
var res = make([]uint64, len(keyRing.entities))
|
||||||
for _, e := range keyRing.entities {
|
for id, e := range keyRing.entities {
|
||||||
res = append(res, e.PrimaryKey.KeyId)
|
res[id] = e.PrimaryKey.KeyId
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadArmoredKeyRing reads an armored data into keyring.
|
// --- Filter keyrings
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterExpiredKeys takes a given KeyRing list and it returns only those
|
// FilterExpiredKeys takes a given KeyRing list and it returns only those
|
||||||
// KeyRings which contain at least, one unexpired Key. It returns only unexpired
|
// 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 {
|
for _, contactKeyRing := range contactKeys {
|
||||||
keyRingHasUnexpiredEntity := false
|
keyRingHasUnexpiredEntity := false
|
||||||
keyRingHasTotallyExpiredEntity := false
|
keyRingHasTotallyExpiredEntity := false
|
||||||
for _, entity := range contactKeyRing.GetEntities() {
|
for _, entity := range contactKeyRing.entities {
|
||||||
hasExpired := false
|
hasExpired := false
|
||||||
hasUnexpired := false
|
hasUnexpired := false
|
||||||
for _, subkey := range entity.Subkeys {
|
for _, subkey := range entity.Subkeys {
|
||||||
|
|
@ -333,7 +154,12 @@ func FilterExpiredKeys(contactKeys []*KeyRing) (filteredKeys []*KeyRing, err err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if keyRingHasUnexpiredEntity {
|
if keyRingHasUnexpiredEntity {
|
||||||
filteredKeys = append(filteredKeys, contactKeyRing)
|
keyRingCopy, err := contactKeyRing.Copy()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredKeys = append(filteredKeys, keyRingCopy)
|
||||||
} else if keyRingHasTotallyExpiredEntity {
|
} else if keyRingHasTotallyExpiredEntity {
|
||||||
hasExpiredEntity = true
|
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
|
// 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 {
|
if len(keyRing.entities) == 0 {
|
||||||
return nil
|
return nil, errors.New("gopenpgp: No key available in this keyring")
|
||||||
}
|
}
|
||||||
newKeyRing := &KeyRing{}
|
newKeyRing := &KeyRing{}
|
||||||
newKeyRing.entities = keyRing.entities[:1]
|
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
|
// SignDetached generates and returns a PGPSignature for a given PlainMessage
|
||||||
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
|
func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, error) {
|
||||||
signEntity, err := keyRing.GetSigningEntity()
|
signEntity, err := keyRing.getSigningEntity()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -56,11 +56,9 @@ func (keyRing *KeyRing) SignDetached(message *PlainMessage) (*PGPSignature, erro
|
||||||
|
|
||||||
// VerifyDetached verifies a PlainMessage with embedded a PGPSignature
|
// VerifyDetached verifies a PlainMessage with embedded a PGPSignature
|
||||||
// and returns a SignatureVerificationError if fails
|
// and returns a SignatureVerificationError if fails
|
||||||
func (keyRing *KeyRing) VerifyDetached(
|
func (keyRing *KeyRing) VerifyDetached(message *PlainMessage, signature *PGPSignature, verifyTime int64) error {
|
||||||
message *PlainMessage, signature *PGPSignature, verifyTime int64,
|
|
||||||
) (error) {
|
|
||||||
return verifySignature(
|
return verifySignature(
|
||||||
keyRing.GetEntities(),
|
keyRing.entities,
|
||||||
message.NewReader(),
|
message.NewReader(),
|
||||||
signature.GetBinary(),
|
signature.GetBinary(),
|
||||||
verifyTime,
|
verifyTime,
|
||||||
|
|
@ -78,7 +76,7 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
|
||||||
|
|
||||||
if privateKey != nil && len(privateKey.entities) > 0 {
|
if privateKey != nil && len(privateKey.entities) > 0 {
|
||||||
var err error
|
var err error
|
||||||
signEntity, err = privateKey.GetSigningEntity()
|
signEntity, err = privateKey.getSigningEntity()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -101,8 +99,11 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = encryptWriter.Write(data)
|
_, err = encryptWriter.Write(data)
|
||||||
encryptWriter.Close()
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = encryptWriter.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -114,11 +115,11 @@ func asymmetricEncrypt(data []byte, publicKey *KeyRing, privateKey *KeyRing, isB
|
||||||
func asymmetricDecrypt(
|
func asymmetricDecrypt(
|
||||||
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
|
encryptedIO io.Reader, privateKey *KeyRing, verifyKey *KeyRing, verifyTime int64,
|
||||||
) (plaintext []byte, err error) {
|
) (plaintext []byte, err error) {
|
||||||
privKeyEntries := privateKey.GetEntities()
|
privKeyEntries := privateKey.entities
|
||||||
var additionalEntries openpgp.EntityList
|
var additionalEntries openpgp.EntityList
|
||||||
|
|
||||||
if verifyKey != nil {
|
if verifyKey != nil {
|
||||||
additionalEntries = verifyKey.GetEntities()
|
additionalEntries = verifyKey.entities
|
||||||
}
|
}
|
||||||
|
|
||||||
if additionalEntries != nil {
|
if additionalEntries != nil {
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,15 @@ package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp/packet"
|
"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.
|
// 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)
|
keyReader := bytes.NewReader(keyPacket)
|
||||||
packets := packet.NewReader(keyReader)
|
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 nil, errors.New("gopenpgp: unable to decrypt session key")
|
||||||
}
|
}
|
||||||
|
|
||||||
return newSymmetricKeyFromEncrypted(ek)
|
return newSessionKeyFromEncrypted(ek)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptSessionKey encrypts the session key with the unarmored
|
// EncryptSessionKey encrypts the session key with the unarmored
|
||||||
// publicKey and returns a binary public-key encrypted session key packet.
|
// 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{}
|
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
|
var pub *packet.PublicKey
|
||||||
for _, e := range keyRing.GetEntities() {
|
for _, e := range keyRing.entities {
|
||||||
if encryptionKey, ok := e.EncryptionKey(getNow()); ok {
|
if encryptionKey, ok := e.EncryptionKey(getNow()); ok {
|
||||||
pub = encryptionKey.PublicKey
|
pub = encryptionKey.PublicKey
|
||||||
break
|
break
|
||||||
|
|
@ -78,7 +65,7 @@ func (keyRing *KeyRing) EncryptSessionKey(sessionSplit *SymmetricKey) ([]byte, e
|
||||||
return nil, errors.New("cannot set key: no public key available")
|
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)
|
err = fmt.Errorf("gopenpgp: cannot set key: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1,38 +1,27 @@
|
||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp/armor"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/constants"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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 []byte
|
||||||
|
|
||||||
var testSymmetricKey = &SymmetricKey{
|
|
||||||
Key: decodedSymmetricKey,
|
|
||||||
Algo: constants.AES256,
|
|
||||||
}
|
|
||||||
|
|
||||||
var testWrongSymmetricKey = &SymmetricKey{
|
|
||||||
Key: []byte("WrongPass"),
|
|
||||||
Algo: constants.AES256,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Corresponding key in testdata/keyring_privateKey
|
// Corresponding key in testdata/keyring_privateKey
|
||||||
const testMailboxPassword = "apple"
|
var testMailboxPassword = []byte("apple")
|
||||||
|
|
||||||
// Corresponding key in testdata/keyring_privateKeyLegacy
|
// Corresponding key in testdata/keyring_privateKeyLegacy
|
||||||
// const testMailboxPasswordLegacy = "123"
|
// const testMailboxPasswordLegacy = [][]byte{ []byte("123") }
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testPrivateKeyRing *KeyRing
|
keyRingTestPrivate *KeyRing
|
||||||
testPublicKeyRing *KeyRing
|
keyRingTestPublic *KeyRing
|
||||||
|
keyRingTestMultiple *KeyRing
|
||||||
)
|
)
|
||||||
|
|
||||||
var testIdentity = &Identity{
|
var testIdentity = &Identity{
|
||||||
|
|
@ -40,75 +29,83 @@ var testIdentity = &Identity{
|
||||||
Email: "",
|
Email: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func initKeyRings() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
testPrivateKeyRing, err = BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
|
testSymmetricKey, err = RandomToken(32)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic("Expected no error while unarmoring private key, got:" + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
|
keyRingTestPrivate, err = NewKeyRing(privateKey)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
panic(err)
|
panic("Able to create a keyring with a locked key")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode armored keys
|
unlockedKey, err := privateKey.Unlock(testMailboxPassword)
|
||||||
block, err := armor.Decode(strings.NewReader(s))
|
|
||||||
if err != nil {
|
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 {
|
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)
|
publicKey, err := NewKeyFromArmored(readTestFile("keyring_publicKey", false))
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(block.Body)
|
|
||||||
if err != nil {
|
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 {
|
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) {
|
err = keyRingTestMultiple.AddKey(keyTestRSA)
|
||||||
encryptedKeyRing, _ := BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
|
if err != nil {
|
||||||
isCorrect := encryptedKeyRing.CheckPassphrase("Wrong password")
|
panic("Expected no error while adding RSA key to keyring, got:" + err.Error())
|
||||||
assert.Exactly(t, false, isCorrect)
|
}
|
||||||
|
|
||||||
isCorrect = encryptedKeyRing.CheckPassphrase(testMailboxPassword)
|
err = keyRingTestMultiple.AddKey(keyTestEC)
|
||||||
assert.Exactly(t, true, isCorrect)
|
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) {
|
func TestIdentities(t *testing.T) {
|
||||||
identities := testPrivateKeyRing.Identities()
|
identities := keyRingTestPrivate.GetIdentities()
|
||||||
assert.Len(t, identities, 1)
|
assert.Len(t, identities, 1)
|
||||||
assert.Exactly(t, identities[0], testIdentity)
|
assert.Exactly(t, identities[0], testIdentity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilterExpiredKeys(t *testing.T) {
|
func TestFilterExpiredKeys(t *testing.T) {
|
||||||
expiredKey, _ := BuildKeyRingArmored(readTestFile("key_expiredKey", false))
|
expiredKey, err := NewKeyFromArmored(readTestFile("key_expiredKey", false))
|
||||||
keys := []*KeyRing{testPrivateKeyRing, expiredKey}
|
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)
|
unexpired, err := FilterExpiredKeys(keys)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -116,60 +113,89 @@ func TestFilterExpiredKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Len(t, unexpired, 1)
|
assert.Len(t, unexpired, 1)
|
||||||
assert.Exactly(t, unexpired[0], testPrivateKeyRing)
|
assert.Exactly(t, unexpired[0].GetKeyIDs(), keyRingTestPrivate.GetKeyIDs())
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyIds(t *testing.T) {
|
func TestKeyIds(t *testing.T) {
|
||||||
keyIDs := testPrivateKeyRing.KeyIds()
|
keyIDs := keyRingTestPrivate.GetKeyIDs()
|
||||||
var assertKeyIDs = []uint64{4518840640391470884}
|
var assertKeyIDs = []uint64{4518840640391470884}
|
||||||
assert.Exactly(t, assertKeyIDs, keyIDs)
|
assert.Exactly(t, assertKeyIDs, keyIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMutlipleKeyRing(t *testing.T) {
|
func TestMultipleKeyRing(t *testing.T) {
|
||||||
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
|
assert.Exactly(t, 3, len(keyRingTestMultiple.entities))
|
||||||
assert.Exactly(t, 1, len(testPublicKeyRing.entities))
|
assert.Exactly(t, 3, keyRingTestMultiple.CountEntities())
|
||||||
|
assert.Exactly(t, 3, keyRingTestMultiple.CountDecryptionEntities())
|
||||||
|
|
||||||
ids := testPublicKeyRing.KeyIds()
|
assert.Exactly(t, 3, len(keyRingTestMultiple.GetKeys()))
|
||||||
assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0])
|
|
||||||
|
|
||||||
err = testPublicKeyRing.ReadFrom(strings.NewReader(readTestFile("mime_publicKey", false)), true)
|
testKey, err := keyRingTestMultiple.GetKey(1)
|
||||||
if err != nil {
|
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()
|
keys := keyRingCopy.GetKeys()
|
||||||
assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0])
|
assertRSACleared(t, keys[0].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
|
||||||
assert.Exactly(t, uint64(0x374130b32ee1e5ea), ids[1])
|
assertEdDSACleared(t, keys[1].entity.PrivateKey.PrivateKey.(ed25519.PrivateKey))
|
||||||
|
assertRSACleared(t, keys[2].entity.PrivateKey.PrivateKey.(*rsa.PrivateKey))
|
||||||
singleKey := testPublicKeyRing.FirstKey()
|
}
|
||||||
assert.Exactly(t, 1, len(singleKey.entities))
|
|
||||||
|
func TestClearPrivateWithSubkeys(t *testing.T) {
|
||||||
ids = singleKey.KeyIds()
|
keyRingCopy, err := keyRingTestMultiple.Copy()
|
||||||
assert.Exactly(t, uint64(0x3eb6259edf21df24), ids[0])
|
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"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/armor"
|
"github.com/ProtonMail/gopenpgp/v2/armor"
|
||||||
"github.com/ProtonMail/gopenpgp/constants"
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
"github.com/ProtonMail/gopenpgp/internal"
|
"github.com/ProtonMail/gopenpgp/v2/internal"
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp/clearsign"
|
"golang.org/x/crypto/openpgp/clearsign"
|
||||||
"golang.org/x/crypto/openpgp/packet"
|
"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,
|
// A Cleartext message is a signed PGP message, that is not encrypted,
|
||||||
// i.e. the ones beginning with -----BEGIN PGP SIGNED MESSAGE-----
|
// i.e. the ones beginning with -----BEGIN PGP SIGNED MESSAGE-----
|
||||||
type ClearTextMessage struct {
|
type ClearTextMessage struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
Signature []byte
|
Signature []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +146,7 @@ func NewPGPSignatureFromArmored(armored string) (*PGPSignature, error) {
|
||||||
// NewClearTextMessage generates a new ClearTextMessage from data and signature
|
// NewClearTextMessage generates a new ClearTextMessage from data and signature
|
||||||
func NewClearTextMessage(data []byte, signature []byte) *ClearTextMessage {
|
func NewClearTextMessage(data []byte, signature []byte) *ClearTextMessage {
|
||||||
return &ClearTextMessage{
|
return &ClearTextMessage{
|
||||||
Data: data,
|
Data: data,
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +183,7 @@ func (msg *PlainMessage) GetBase64() string {
|
||||||
return base64.StdEncoding.EncodeToString(msg.Data)
|
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 {
|
func (msg *PlainMessage) NewReader() io.Reader {
|
||||||
return bytes.NewReader(msg.GetBinary())
|
return bytes.NewReader(msg.GetBinary())
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +203,7 @@ func (msg *PGPMessage) GetBinary() []byte {
|
||||||
return msg.Data
|
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 {
|
func (msg *PGPMessage) NewReader() io.Reader {
|
||||||
return bytes.NewReader(msg.GetBinary())
|
return bytes.NewReader(msg.GetBinary())
|
||||||
}
|
}
|
||||||
|
|
@ -225,7 +225,7 @@ func (msg *PGPSplitMessage) GetBinaryKeyPacket() []byte {
|
||||||
|
|
||||||
// GetBinary returns the unarmored binary joined packets as a []byte
|
// GetBinary returns the unarmored binary joined packets as a []byte
|
||||||
func (msg *PGPSplitMessage) GetBinary() []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
|
// 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)
|
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)
|
// SeparateKeyAndData returns the first keypacket and the (hopefully unique) dataPacket (not verified)
|
||||||
// * estimatedLength is the estimate length of the message
|
// * estimatedLength is the estimate length of the message
|
||||||
// * garbageCollector > 0 activates the garbage collector
|
// * 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
|
// Store encrypted key and symmetrically encrypted packet separately
|
||||||
var encryptedKey *packet.EncryptedKey
|
var encryptedKey *packet.EncryptedKey
|
||||||
var decryptErr error
|
|
||||||
for {
|
for {
|
||||||
var p packet.Packet
|
var p packet.Packet
|
||||||
if p, err = packets.Next(); err == io.EOF {
|
if p, err = packets.Next(); err == io.EOF {
|
||||||
|
|
@ -259,7 +263,7 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
|
||||||
encryptedKey = p
|
encryptedKey = p
|
||||||
|
|
||||||
case *packet.SymmetricallyEncrypted:
|
case *packet.SymmetricallyEncrypted:
|
||||||
// FIXME: add support for multiple keypackets
|
// TODO: add support for multiple keypackets
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
// 2^16 is an estimation of the size difference between input and output, the size difference is most probably
|
// 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.
|
// 16 bytes at a maximum though.
|
||||||
|
|
@ -267,8 +271,14 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
|
||||||
// in low-memory environments
|
// in low-memory environments
|
||||||
b.Grow(1<<16 + estimatedLength)
|
b.Grow(1<<16 + estimatedLength)
|
||||||
// empty encoded length + start byte
|
// empty encoded length + start byte
|
||||||
b.Write(make([]byte, 6))
|
if _, err := b.Write(make([]byte, 6)); err != nil {
|
||||||
b.WriteByte(byte(1))
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.WriteByte(byte(1)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
actualLength := 1
|
actualLength := 1
|
||||||
block := make([]byte, 128)
|
block := make([]byte, 128)
|
||||||
for {
|
for {
|
||||||
|
|
@ -276,7 +286,9 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
b.Write(block[:n])
|
if _, err := b.Write(block[:n]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
actualLength += n
|
actualLength += n
|
||||||
gcCounter += n
|
gcCounter += n
|
||||||
if gcCounter > garbageCollector && garbageCollector > 0 {
|
if gcCounter > garbageCollector && garbageCollector > 0 {
|
||||||
|
|
@ -287,17 +299,18 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
|
||||||
|
|
||||||
// quick encoding
|
// quick encoding
|
||||||
symEncryptedData := b.Bytes()
|
symEncryptedData := b.Bytes()
|
||||||
if actualLength < 192 {
|
switch {
|
||||||
|
case actualLength < 192:
|
||||||
symEncryptedData[4] = byte(210)
|
symEncryptedData[4] = byte(210)
|
||||||
symEncryptedData[5] = byte(actualLength)
|
symEncryptedData[5] = byte(actualLength)
|
||||||
symEncryptedData = symEncryptedData[4:]
|
symEncryptedData = symEncryptedData[4:]
|
||||||
} else if actualLength < 8384 {
|
case actualLength < 8384:
|
||||||
actualLength = actualLength - 192
|
actualLength -= 192
|
||||||
symEncryptedData[3] = byte(210)
|
symEncryptedData[3] = byte(210)
|
||||||
symEncryptedData[4] = 192 + byte(actualLength>>8)
|
symEncryptedData[4] = 192 + byte(actualLength>>8)
|
||||||
symEncryptedData[5] = byte(actualLength)
|
symEncryptedData[5] = byte(actualLength)
|
||||||
symEncryptedData = symEncryptedData[3:]
|
symEncryptedData = symEncryptedData[3:]
|
||||||
} else {
|
default:
|
||||||
symEncryptedData[0] = byte(210)
|
symEncryptedData[0] = byte(210)
|
||||||
symEncryptedData[1] = byte(255)
|
symEncryptedData[1] = byte(255)
|
||||||
symEncryptedData[2] = byte(actualLength >> 24)
|
symEncryptedData[2] = byte(actualLength >> 24)
|
||||||
|
|
@ -305,13 +318,9 @@ func (msg *PGPMessage) SeparateKeyAndData(estimatedLength, garbageCollector int)
|
||||||
symEncryptedData[4] = byte(actualLength >> 8)
|
symEncryptedData[4] = byte(actualLength >> 8)
|
||||||
symEncryptedData[5] = byte(actualLength)
|
symEncryptedData[5] = byte(actualLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
outSplit.DataPacket = symEncryptedData
|
outSplit.DataPacket = symEncryptedData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if decryptErr != nil {
|
|
||||||
return nil, fmt.Errorf("gopenpgp: cannot decrypt encrypted key packet: %v", decryptErr)
|
|
||||||
}
|
|
||||||
if encryptedKey == nil {
|
if encryptedKey == nil {
|
||||||
return nil, errors.New("gopenpgp: packets don't include an encrypted key packet")
|
return nil, errors.New("gopenpgp: packets don't include an encrypted key packet")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,48 +4,47 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/crypto/openpgp/packet"
|
"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")
|
var message = NewPlainMessageFromString("The secret code is... 1, 2, 3, 4, 5")
|
||||||
|
|
||||||
// Encrypt data with password
|
// Encrypt data with password
|
||||||
encrypted, err := testSymmetricKey.Encrypt(message)
|
encrypted, err := EncryptMessageWithPassword(message, testSymmetricKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when encrypting, got:", err)
|
t.Fatal("Expected no error when encrypting, got:", err)
|
||||||
}
|
}
|
||||||
// Decrypt data with wrong password
|
// Decrypt data with wrong password
|
||||||
_, err = testWrongSymmetricKey.Decrypt(encrypted)
|
_, err = DecryptMessageWithPassword(encrypted, []byte("Wrong password"))
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
// Decrypt data with the good password
|
// Decrypt data with the good password
|
||||||
decrypted, err := testSymmetricKey.Decrypt(encrypted)
|
decrypted, err := DecryptMessageWithPassword(encrypted, testSymmetricKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when decrypting, got:", err)
|
t.Fatal("Expected no error when decrypting, got:", err)
|
||||||
}
|
}
|
||||||
assert.Exactly(t, message.GetString(), decrypted.GetString())
|
assert.Exactly(t, message.GetString(), decrypted.GetString())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBinaryMessageEncryptionWithSymmetricKey(t *testing.T) {
|
func TestBinaryMessageEncryptionWithPassword(t *testing.T) {
|
||||||
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
|
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
|
||||||
var message = NewPlainMessage(binData)
|
var message = NewPlainMessage(binData)
|
||||||
|
|
||||||
// Encrypt data with password
|
// Encrypt data with password
|
||||||
encrypted, err := testSymmetricKey.Encrypt(message)
|
encrypted, err := EncryptMessageWithPassword(message, testSymmetricKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when encrypting, got:", err)
|
t.Fatal("Expected no error when encrypting, got:", err)
|
||||||
}
|
}
|
||||||
// Decrypt data with wrong password
|
// Decrypt data with wrong password
|
||||||
_, err = testWrongSymmetricKey.Decrypt(encrypted)
|
_, err = DecryptMessageWithPassword(encrypted, []byte("Wrong password"))
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
// Decrypt data with the good password
|
// Decrypt data with the good password
|
||||||
decrypted, err := testSymmetricKey.Decrypt(encrypted)
|
decrypted, err := DecryptMessageWithPassword(encrypted, testSymmetricKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when decrypting, got:", err)
|
t.Fatal("Expected no error when decrypting, got:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -55,21 +54,12 @@ func TestBinaryMessageEncryptionWithSymmetricKey(t *testing.T) {
|
||||||
func TestTextMessageEncryption(t *testing.T) {
|
func TestTextMessageEncryption(t *testing.T) {
|
||||||
var message = NewPlainMessageFromString("plain text")
|
var message = NewPlainMessageFromString("plain text")
|
||||||
|
|
||||||
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
|
ciphertext, err := keyRingTestPublic.Encrypt(message, keyRingTestPrivate)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when encrypting, got:", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when decrypting, got:", err)
|
t.Fatal("Expected no error when decrypting, got:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -80,28 +70,19 @@ func TestBinaryMessageEncryption(t *testing.T) {
|
||||||
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
|
binData, _ := base64.StdEncoding.DecodeString("ExXmnSiQ2QCey20YLH6qlLhkY3xnIBC1AwlIXwK/HvY=")
|
||||||
var message = NewPlainMessage(binData)
|
var message = NewPlainMessage(binData)
|
||||||
|
|
||||||
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
|
ciphertext, err := keyRingTestPublic.Encrypt(message, keyRingTestPrivate)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when encrypting, got:", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when decrypting, got:", err)
|
t.Fatal("Expected no error when decrypting, got:", err)
|
||||||
}
|
}
|
||||||
assert.Exactly(t, message.GetBinary(), decrypted.GetBinary())
|
assert.Exactly(t, message.GetBinary(), decrypted.GetBinary())
|
||||||
|
|
||||||
// Decrypt without verifying
|
// Decrypt without verifying
|
||||||
decrypted, err = testPrivateKeyRing.Decrypt(ciphertext, nil, 0)
|
decrypted, err = keyRingTestPrivate.Decrypt(ciphertext, nil, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when decrypting, got:", err)
|
t.Fatal("Expected no error when decrypting, got:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -109,29 +90,40 @@ func TestBinaryMessageEncryption(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIssue11(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 {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while bulding private keyring, got:", err)
|
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 {
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while building public keyring, got:", err)
|
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))
|
pgpMessage, err := NewPGPMessageFromArmored(readTestFile("issue11_message", false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while unlocking private keyring, got:", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while decrypting/verifying, got:", err)
|
t.Fatal("Expected no error while decrypting/verifying, got:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -140,20 +132,12 @@ func TestIssue11(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSignedMessageDecryption(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))
|
pgpMessage, err := NewPGPMessageFromArmored(readTestFile("message_signed", false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when unarmoring, got:", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when decrypting, got:", err)
|
t.Fatal("Expected no error when decrypting, got:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -162,24 +146,9 @@ func TestSignedMessageDecryption(t *testing.T) {
|
||||||
|
|
||||||
func TestMultipleKeyMessageEncryption(t *testing.T) {
|
func TestMultipleKeyMessageEncryption(t *testing.T) {
|
||||||
var message = NewPlainMessageFromString("plain text")
|
var message = NewPlainMessageFromString("plain text")
|
||||||
|
assert.Exactly(t, 3, len(keyRingTestMultiple.entities))
|
||||||
|
|
||||||
testPublicKeyRing, _ = BuildKeyRingArmored(readTestFile("keyring_publicKey", false))
|
ciphertext, err := keyRingTestMultiple.Encrypt(message, keyRingTestPrivate)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when encrypting, got:", err)
|
t.Fatal("Expected no error when encrypting, got:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -189,17 +158,15 @@ func TestMultipleKeyMessageEncryption(t *testing.T) {
|
||||||
for {
|
for {
|
||||||
var p packet.Packet
|
var p packet.Packet
|
||||||
if p, err = packets.Next(); err == io.EOF {
|
if p, err = packets.Next(); err == io.EOF {
|
||||||
err = nil
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch p.(type) {
|
if _, ok := p.(*packet.EncryptedKey); ok {
|
||||||
case *packet.EncryptedKey:
|
numKeyPackets++
|
||||||
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 {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when decrypting, got:", err)
|
t.Fatal("Expected no error when decrypting, got:", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ func parseMIME(
|
||||||
|
|
||||||
err = gomime.VisitAll(bytes.NewReader(mmBodyData), h, signatureCollector)
|
err = gomime.VisitAll(bytes.NewReader(mmBodyData), h, signatureCollector)
|
||||||
if err == nil && verifierKey != nil {
|
if err == nil && verifierKey != nil {
|
||||||
err = signatureCollector.verified;
|
err = signatureCollector.verified
|
||||||
}
|
}
|
||||||
|
|
||||||
return bodyCollector,
|
return bodyCollector,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Corresponding key in testdata/mime_privateKey
|
// Corresponding key in testdata/mime_privateKey
|
||||||
const privateKeyPassword = "test"
|
var MIMEKeyPassword = []byte("test")
|
||||||
|
|
||||||
// define call back interface
|
// define call back interface
|
||||||
type Callbacks struct {
|
type Callbacks struct {
|
||||||
|
|
@ -37,13 +37,22 @@ func TestDecrypt(t *testing.T) {
|
||||||
callbacks := Callbacks{
|
callbacks := Callbacks{
|
||||||
Testing: t,
|
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 {
|
if err != nil {
|
||||||
t.Fatal("Cannot unlock private key:", err)
|
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))
|
message, err := NewPGPMessageFromArmored(readTestFile("mime_pgpMessage", false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Cannot decode armored message:", err)
|
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)
|
body, atts, attHeaders, err := parseMIME(readTestFile("mime_testMessage", false), nil)
|
||||||
|
|
||||||
if err != 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
|
_ = 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"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp"
|
"golang.org/x/crypto/openpgp"
|
||||||
"golang.org/x/crypto/openpgp/packet"
|
|
||||||
pgpErrors "golang.org/x/crypto/openpgp/errors"
|
pgpErrors "golang.org/x/crypto/openpgp/errors"
|
||||||
|
"golang.org/x/crypto/openpgp/packet"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/constants"
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
"github.com/ProtonMail/gopenpgp/internal"
|
"github.com/ProtonMail/gopenpgp/v2/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignatureVerificationError is returned from Decrypt and VerifyDetached functions when signature verification fails
|
// SignatureVerificationError is returned from Decrypt and VerifyDetached functions when signature verification fails
|
||||||
type SignatureVerificationError struct {
|
type SignatureVerificationError struct {
|
||||||
Status int
|
Status int
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ func (e SignatureVerificationError) Error() string {
|
||||||
|
|
||||||
// newSignatureFailed creates a new SignatureVerificationError, type SIGNATURE_FAILED
|
// newSignatureFailed creates a new SignatureVerificationError, type SIGNATURE_FAILED
|
||||||
func newSignatureFailed() SignatureVerificationError {
|
func newSignatureFailed() SignatureVerificationError {
|
||||||
return SignatureVerificationError {
|
return SignatureVerificationError{
|
||||||
constants.SIGNATURE_FAILED,
|
constants.SIGNATURE_FAILED,
|
||||||
"Invalid signature",
|
"Invalid signature",
|
||||||
}
|
}
|
||||||
|
|
@ -40,7 +40,7 @@ func newSignatureFailed() SignatureVerificationError {
|
||||||
|
|
||||||
// newSignatureNotSigned creates a new SignatureVerificationError, type SIGNATURE_NOT_SIGNED
|
// newSignatureNotSigned creates a new SignatureVerificationError, type SIGNATURE_NOT_SIGNED
|
||||||
func newSignatureNotSigned() SignatureVerificationError {
|
func newSignatureNotSigned() SignatureVerificationError {
|
||||||
return SignatureVerificationError {
|
return SignatureVerificationError{
|
||||||
constants.SIGNATURE_NOT_SIGNED,
|
constants.SIGNATURE_NOT_SIGNED,
|
||||||
"Missing signature",
|
"Missing signature",
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ func newSignatureNotSigned() SignatureVerificationError {
|
||||||
|
|
||||||
// newSignatureNoVerifier creates a new SignatureVerificationError, type SIGNATURE_NO_VERIFIER
|
// newSignatureNoVerifier creates a new SignatureVerificationError, type SIGNATURE_NO_VERIFIER
|
||||||
func newSignatureNoVerifier() SignatureVerificationError {
|
func newSignatureNoVerifier() SignatureVerificationError {
|
||||||
return SignatureVerificationError {
|
return SignatureVerificationError{
|
||||||
constants.SIGNATURE_NO_VERIFIER,
|
constants.SIGNATURE_NO_VERIFIER,
|
||||||
"No matching signature",
|
"No matching signature",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,8 @@ func (sc *SignatureCollector) Accept(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,39 +2,23 @@ package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/constants"
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const signedPlainText = "Signed message\n"
|
const signedPlainText = "Signed message\n"
|
||||||
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
|
|
||||||
|
|
||||||
var signingKeyRing *KeyRing
|
|
||||||
var textSignature, binSignature *PGPSignature
|
var textSignature, binSignature *PGPSignature
|
||||||
var message *PlainMessage
|
var message *PlainMessage
|
||||||
var signatureTest = regexp.MustCompile("(?s)^-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
|
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) {
|
func TestSignTextDetached(t *testing.T) {
|
||||||
var err error
|
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)
|
message = NewPlainMessageFromString(signedPlainText)
|
||||||
textSignature, err = signingKeyRing.SignDetached(message)
|
textSignature, err = keyRingTestPrivate.SignDetached(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Cannot generate signature:", err)
|
t.Fatal("Cannot generate signature:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -48,15 +32,15 @@ func TestSignTextDetached(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyTextDetachedSig(t *testing.T) {
|
func TestVerifyTextDetachedSig(t *testing.T) {
|
||||||
verificationError := signingKeyRing.VerifyDetached(message, textSignature, testTime)
|
verificationError := keyRingTestPublic.VerifyDetached(message, textSignature, testTime)
|
||||||
if verificationError != nil {
|
if verificationError != nil {
|
||||||
t.Fatal("Cannot verify plaintext signature:", err)
|
t.Fatal("Cannot verify plaintext signature:", verificationError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyTextDetachedSigWrong(t *testing.T) {
|
func TestVerifyTextDetachedSigWrong(t *testing.T) {
|
||||||
fakeMessage := NewPlainMessageFromString("wrong text")
|
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")
|
assert.EqualError(t, verificationError, "Signature Verification Error: Invalid signature")
|
||||||
|
|
||||||
|
|
@ -67,7 +51,7 @@ func TestVerifyTextDetachedSigWrong(t *testing.T) {
|
||||||
func TestSignBinDetached(t *testing.T) {
|
func TestSignBinDetached(t *testing.T) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
binSignature, err = signingKeyRing.SignDetached(NewPlainMessage([]byte(signedPlainText)))
|
binSignature, err = keyRingTestPrivate.SignDetached(NewPlainMessage([]byte(signedPlainText)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Cannot generate signature:", err)
|
t.Fatal("Cannot generate signature:", err)
|
||||||
}
|
}
|
||||||
|
|
@ -81,8 +65,8 @@ func TestSignBinDetached(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyBinDetachedSig(t *testing.T) {
|
func TestVerifyBinDetachedSig(t *testing.T) {
|
||||||
verificationError := signingKeyRing.VerifyDetached(message, binSignature, testTime)
|
verificationError := keyRingTestPublic.VerifyDetached(message, binSignature, testTime)
|
||||||
if verificationError != nil {
|
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
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdateTime updates cached time
|
// UpdateTime updates cached time
|
||||||
|
|
@ -31,7 +31,7 @@ func getNow() time.Time {
|
||||||
return time.Now()
|
return time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Unix(pgp.latestServerTime + extrapolate, 0)
|
return time.Unix(pgp.latestServerTime+extrapolate, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDiff() (int64, error) {
|
func getDiff() (int64, error) {
|
||||||
|
|
@ -40,12 +40,10 @@ func getDiff() (int64, error) {
|
||||||
return int64(time.Since(pgp.latestClientTime).Seconds()), nil
|
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
|
// getTimeGenerator Returns a time generator function
|
||||||
func getTimeGenerator() func() time.Time {
|
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
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20190521135552-09454e3dbe72
|
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/pkg/errors v0.8.1
|
||||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f
|
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-20191122234321-e77a1f03baa0 h1:mCww5Yl0Pm4PZPSooupyWDgihrh96p6+O4PY1hs0FBw=
|
||||||
github.com/ProtonMail/crypto v0.0.0-20190814153124-b5b07a6add54/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
github.com/ProtonMail/crypto v0.0.0-20191122234321-e77a1f03baa0/go.mod h1:MBriIAodHvZ+YvwvMJWCTmseW/LkeVRPWp/iZKvee4g=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20190521135552-09454e3dbe72 h1:hGCc4Oc2fD3I5mNnZ1VlREncVc9EXJF8dxW3sw16gWM=
|
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20190521135552-09454e3dbe72/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
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/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.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/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-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 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
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/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=
|
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 (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"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 {
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -19,4 +21,8 @@ func readTestFile(name string, trimNewlines bool) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Corresponding key in ../crypto/testdata/keyring_privateKey
|
// 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 (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/gopenpgp/internal"
|
"github.com/ProtonMail/gopenpgp/v2/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignCleartextMessageArmored signs text given a private key and its passphrase, canonicalizes and trims the newlines,
|
// SignCleartextMessageArmored signs text given a private key and its passphrase, canonicalizes and trims the newlines,
|
||||||
// and returns the PGP-compliant special armoring
|
// and returns the PGP-compliant special armoring
|
||||||
func SignCleartextMessageArmored(privateKey, passphrase, text string) (string, error) {
|
func SignCleartextMessageArmored(privateKey string, passphrase []byte, text string) (string, error) {
|
||||||
signingKeyRing, err := crypto.BuildKeyRingArmored(privateKey)
|
signingKey, err := crypto.NewKeyFromArmored(privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = signingKeyRing.UnlockWithPassphrase(passphrase)
|
unlockedKey, err := signingKey.Unlock(passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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
|
// VerifyCleartextMessageArmored verifies PGP-compliant armored signed plain text given the public key
|
||||||
// and returns the text or err if the verification fails
|
// and returns the text or err if the verification fails
|
||||||
func VerifyCleartextMessageArmored(publicKey, armored string, verifyTime int64) (string, error) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/ProtonMail/gopenpgp/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const signedPlainText = "Signed message\n"
|
const signedPlainText = "Signed message\n"
|
||||||
const testTime = 1557754627 // 2019-05-13T13:37:07+00:00
|
|
||||||
var signedMessageTest = regexp.MustCompile(
|
var signedMessageTest = regexp.MustCompile(
|
||||||
"(?s)^-----BEGIN PGP SIGNED MESSAGE-----.*-----BEGIN PGP SIGNATURE-----.*-----END PGP SIGNATURE-----$")
|
"(?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
|
package helper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/constants"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/gopenpgp/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EncryptMessageWithToken encrypts a string with a passphrase using AES256
|
// EncryptMessageWithPassword encrypts a string with a passphrase using AES256
|
||||||
func EncryptMessageWithToken(
|
func EncryptMessageWithPassword(password []byte, plaintext string) (ciphertext string, err error) {
|
||||||
passphrase, plaintext string,
|
|
||||||
) (ciphertext string, err error) {
|
|
||||||
return EncryptMessageWithTokenAlgo(passphrase, plaintext, constants.AES256)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptMessageWithTokenAlgo encrypts a string with a random token and an algorithm chosen from constants.*
|
|
||||||
func EncryptMessageWithTokenAlgo(
|
|
||||||
token, plaintext, algo string,
|
|
||||||
) (ciphertext string, err error) {
|
|
||||||
var pgpMessage *crypto.PGPMessage
|
var pgpMessage *crypto.PGPMessage
|
||||||
|
|
||||||
var message = crypto.NewPlainMessageFromString(plaintext)
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,19 +24,17 @@ func EncryptMessageWithTokenAlgo(
|
||||||
return ciphertext, nil
|
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.
|
// 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 message *crypto.PlainMessage
|
||||||
var pgpMessage *crypto.PGPMessage
|
var pgpMessage *crypto.PGPMessage
|
||||||
|
|
||||||
var key = crypto.NewSymmetricKeyFromToken(token, "")
|
|
||||||
|
|
||||||
if pgpMessage, err = crypto.NewPGPMessageFromArmored(ciphertext); err != nil {
|
if pgpMessage, err = crypto.NewPGPMessageFromArmored(ciphertext); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if message, err = key.Decrypt(pgpMessage); err != nil {
|
if message, err = crypto.DecryptMessageWithPassword(pgpMessage, password); err != nil {
|
||||||
return "", err
|
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
|
// 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 publicKeyRing *crypto.KeyRing
|
||||||
var pgpMessage *crypto.PGPMessage
|
var pgpMessage *crypto.PGPMessage
|
||||||
|
|
||||||
var message = crypto.NewPlainMessageFromString(plaintext)
|
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
|
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
|
// EncryptSignMessageArmored generates an armored signed PGP message given a plaintext and an armored public key
|
||||||
// a private key and its passphrase
|
// a private key and its passphrase
|
||||||
func EncryptSignMessageArmored(
|
func EncryptSignMessageArmored(
|
||||||
publicKey, privateKey, passphrase, plaintext string,
|
publicKey, privateKey string, passphrase []byte, plaintext string,
|
||||||
) (ciphertext string, err error) {
|
) (ciphertext string, err error) {
|
||||||
|
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
|
||||||
var publicKeyRing, privateKeyRing *crypto.KeyRing
|
var publicKeyRing, privateKeyRing *crypto.KeyRing
|
||||||
var pgpMessage *crypto.PGPMessage
|
var pgpMessage *crypto.PGPMessage
|
||||||
|
|
||||||
var message = crypto.NewPlainMessageFromString(plaintext)
|
var message = crypto.NewPlainMessageFromString(plaintext)
|
||||||
|
|
||||||
if publicKeyRing, err = crypto.BuildKeyRingArmored(publicKey); err != nil {
|
if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if privateKeyRing, err = crypto.BuildKeyRingArmored(privateKey); err != nil {
|
if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil {
|
||||||
return "", err
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,17 +112,22 @@ func EncryptSignMessageArmored(
|
||||||
|
|
||||||
// DecryptMessageArmored decrypts an armored PGP message given a private key and its passphrase
|
// DecryptMessageArmored decrypts an armored PGP message given a private key and its passphrase
|
||||||
func DecryptMessageArmored(
|
func DecryptMessageArmored(
|
||||||
privateKey, passphrase, ciphertext string,
|
privateKey string, passphrase []byte, ciphertext string,
|
||||||
) (plaintext string, err error) {
|
) (plaintext string, err error) {
|
||||||
|
var privateKeyObj, privateKeyUnlocked *crypto.Key
|
||||||
var privateKeyRing *crypto.KeyRing
|
var privateKeyRing *crypto.KeyRing
|
||||||
var pgpMessage *crypto.PGPMessage
|
var pgpMessage *crypto.PGPMessage
|
||||||
var message *crypto.PlainMessage
|
var message *crypto.PlainMessage
|
||||||
|
|
||||||
if privateKeyRing, err = crypto.BuildKeyRingArmored(privateKey); err != nil {
|
if privateKeyObj, err = crypto.NewKeyFromArmored(privateKey); err != nil {
|
||||||
return "", err
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,21 +146,30 @@ func DecryptMessageArmored(
|
||||||
// and verifies the embedded signature.
|
// and verifies the embedded signature.
|
||||||
// Returns the plain data or an error on signature verification failure.
|
// Returns the plain data or an error on signature verification failure.
|
||||||
func DecryptVerifyMessageArmored(
|
func DecryptVerifyMessageArmored(
|
||||||
publicKey, privateKey, passphrase, ciphertext string,
|
publicKey, privateKey string, passphrase []byte, ciphertext string,
|
||||||
) (plaintext string, err error) {
|
) (plaintext string, err error) {
|
||||||
|
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
|
||||||
var publicKeyRing, privateKeyRing *crypto.KeyRing
|
var publicKeyRing, privateKeyRing *crypto.KeyRing
|
||||||
var pgpMessage *crypto.PGPMessage
|
var pgpMessage *crypto.PGPMessage
|
||||||
var message *crypto.PlainMessage
|
var message *crypto.PlainMessage
|
||||||
|
|
||||||
if publicKeyRing, err = crypto.BuildKeyRingArmored(publicKey); err != nil {
|
if publicKeyObj, err = crypto.NewKeyFromArmored(publicKey); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if privateKeyRing, err = crypto.BuildKeyRingArmored(privateKey); err != nil {
|
if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil {
|
||||||
return "", err
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,24 +188,32 @@ func DecryptVerifyMessageArmored(
|
||||||
// and its passphrase, the filename, and the unencrypted file data.
|
// and its passphrase, the filename, and the unencrypted file data.
|
||||||
// Returns keypacket, dataPacket and unarmored (!) signature separate.
|
// Returns keypacket, dataPacket and unarmored (!) signature separate.
|
||||||
func EncryptSignAttachment(
|
func EncryptSignAttachment(
|
||||||
publicKey, privateKey, passphrase, fileName string,
|
publicKey, privateKey string, passphrase []byte, fileName string, plainData []byte,
|
||||||
plainData []byte,
|
|
||||||
) (keyPacket, dataPacket, signature []byte, err error) {
|
) (keyPacket, dataPacket, signature []byte, err error) {
|
||||||
|
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
|
||||||
var publicKeyRing, privateKeyRing *crypto.KeyRing
|
var publicKeyRing, privateKeyRing *crypto.KeyRing
|
||||||
var packets *crypto.PGPSplitMessage
|
var packets *crypto.PGPSplitMessage
|
||||||
var signatureObj *crypto.PGPSignature
|
var signatureObj *crypto.PGPSignature
|
||||||
|
|
||||||
var binMessage = crypto.NewPlainMessage(plainData)
|
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
|
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
|
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
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,25 +232,34 @@ func EncryptSignAttachment(
|
||||||
// and an armored (!) signature, given a publicKey, and a privateKey with its passphrase.
|
// and an armored (!) signature, given a publicKey, and a privateKey with its passphrase.
|
||||||
// Returns the plain data or an error on signature verification failure.
|
// Returns the plain data or an error on signature verification failure.
|
||||||
func DecryptVerifyAttachment(
|
func DecryptVerifyAttachment(
|
||||||
publicKey, privateKey, passphrase string,
|
publicKey, privateKey string,
|
||||||
keyPacket, dataPacket []byte,
|
passphrase, keyPacket, dataPacket []byte,
|
||||||
armoredSignature string,
|
armoredSignature string,
|
||||||
) (plainData []byte, err error) {
|
) (plainData []byte, err error) {
|
||||||
|
var publicKeyObj, privateKeyObj, unlockedKeyObj *crypto.Key
|
||||||
var publicKeyRing, privateKeyRing *crypto.KeyRing
|
var publicKeyRing, privateKeyRing *crypto.KeyRing
|
||||||
var detachedSignature *crypto.PGPSignature
|
var detachedSignature *crypto.PGPSignature
|
||||||
var message *crypto.PlainMessage
|
var message *crypto.PlainMessage
|
||||||
|
|
||||||
var packets = crypto.NewPGPSplitMessage(keyPacket, dataPacket)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if privateKeyRing, err = crypto.BuildKeyRingArmored(privateKey); err != nil {
|
if publicKeyRing, err = crypto.NewKeyRing(publicKeyObj); err != nil {
|
||||||
return nil, err
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,23 @@ package helper
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAESEncryption(t *testing.T) {
|
func TestAESEncryption(t *testing.T) {
|
||||||
var plaintext = "Symmetric secret"
|
var plaintext = "Symmetric secret"
|
||||||
var passphrase = "passphrase"
|
var passphrase = []byte("passphrase")
|
||||||
|
|
||||||
ciphertext, err := EncryptMessageWithToken(passphrase, plaintext)
|
ciphertext, err := EncryptMessageWithPassword(passphrase, plaintext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when encrypting, got:", err)
|
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")
|
assert.EqualError(t, err, "gopenpgp: wrong password in symmetric decryption")
|
||||||
|
|
||||||
decrypted, err := DecryptMessageWithToken(passphrase, ciphertext)
|
decrypted, err := DecryptMessageWithPassword(passphrase, ciphertext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error when decrypting, got:", err)
|
t.Fatal("Expected no error when decrypting, got:", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
package helper
|
package helper
|
||||||
|
|
||||||
import (
|
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 {
|
type ExplicitVerifyMessage struct {
|
||||||
Message *crypto.PlainMessage
|
Message *crypto.PlainMessage
|
||||||
SignatureVerificationError *crypto.SignatureVerificationError
|
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.
|
// and verifies the embedded signature.
|
||||||
// Returns the plain data or an error on signature verification failure.
|
// Returns the plain data or an error on signature verification failure.
|
||||||
func DecryptExplicitVerify(
|
func DecryptExplicitVerify(
|
||||||
|
|
@ -19,7 +20,7 @@ func DecryptExplicitVerify(
|
||||||
) (*ExplicitVerifyMessage, error) {
|
) (*ExplicitVerifyMessage, error) {
|
||||||
var explicitVerify *ExplicitVerifyMessage
|
var explicitVerify *ExplicitVerifyMessage
|
||||||
|
|
||||||
message, err := privateKeyRing.Decrypt(pgpMessage, publicKeyRing, verifyTime);
|
message, err := privateKeyRing.Decrypt(pgpMessage, publicKeyRing, verifyTime)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
castedErr, isType := err.(crypto.SignatureVerificationError)
|
castedErr, isType := err.(crypto.SignatureVerificationError)
|
||||||
|
|
@ -28,12 +29,12 @@ func DecryptExplicitVerify(
|
||||||
}
|
}
|
||||||
|
|
||||||
explicitVerify = &ExplicitVerifyMessage{
|
explicitVerify = &ExplicitVerifyMessage{
|
||||||
Message: message,
|
Message: message,
|
||||||
SignatureVerificationError: &castedErr,
|
SignatureVerificationError: &castedErr,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
explicitVerify = &ExplicitVerifyMessage{
|
explicitVerify = &ExplicitVerifyMessage{
|
||||||
Message: message,
|
Message: message,
|
||||||
SignatureVerificationError: nil,
|
SignatureVerificationError: nil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,22 @@ package helper
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/constants"
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
"github.com/ProtonMail/gopenpgp/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIOSSignedMessageDecryption(t *testing.T) {
|
func TestIOSSignedMessageDecryption(t *testing.T) {
|
||||||
testPrivateKeyRing, _ := crypto.BuildKeyRingArmored(readTestFile("keyring_privateKey", false))
|
privateKey, _ := crypto.NewKeyFromArmored(readTestFile("keyring_privateKey", false))
|
||||||
testPublicKeyRing, _ := crypto.BuildKeyRingArmored(readTestFile("mime_publicKey", false))
|
|
||||||
|
|
||||||
// Password defined in base_test
|
// Password defined in base_test
|
||||||
err := testPrivateKeyRing.UnlockWithPassphrase(testMailboxPassword)
|
privateKey, err := privateKey.Unlock(testMailboxPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error unlocking privateKey, got:", err)
|
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))
|
pgpMessage, err := crypto.NewPGPMessageFromArmored(readTestFile("message_signed", false))
|
||||||
if err != nil {
|
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, constants.SIGNATURE_NO_VERIFIER, decrypted.SignatureVerificationError.Status)
|
||||||
assert.Exactly(t, readTestFile("message_plaintext", true), decrypted.Message.GetString())
|
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)
|
pgpMessage, err = testPublicKeyRing.Encrypt(decrypted.Message, testPrivateKeyRing)
|
||||||
if err != nil {
|
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
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/crypto/openpgp/armor"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/openpgp/armor"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Unarmor unarmors an armored string.
|
// Unarmor unarmors an armored string.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ package internal
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/constants"
|
"github.com/ProtonMail/gopenpgp/v2/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TrimNewlines removes whitespace from the end of each line of the input
|
// 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)
|
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.
|
// highest power of 2 you can derive within 100 milliseconds.
|
||||||
func DeriveKey(password string, salt []byte, N int) ([]byte, error) {
|
func DeriveKey(password string, salt []byte, n int) ([]byte, error) {
|
||||||
return scrypt.Key([]byte(password), salt, N, 8, 1, 32)
|
return scrypt.Key([]byte(password), salt, n, 8, 1, 32)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ package subtle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSubtle_EncryptWithoutIntegrity(t *testing.T) {
|
func TestSubtle_EncryptWithoutIntegrity(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue