Implement GetArmoredWithCustomHeaders (#48)
* Implement GetArmoredWithCustomHeaders ArmorWithTypeAndCustomHeaders can be reused by other PGP armoured objects. * Update linting, and lint accordingly `godot` has been improved and `goerr113` has been added (and ignored here). * Add custom headers for keys * Minor comment changes Co-authored-by: Aron Wussler <aron@wussler.it>
This commit is contained in:
parent
b1e005fec3
commit
dcc82c9fc3
10 changed files with 176 additions and 31 deletions
|
|
@ -21,6 +21,7 @@ linters:
|
||||||
- gochecknoglobals # Checks that no globals are present in Go code [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]
|
- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
|
||||||
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
|
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
|
||||||
|
- goerr113 # Golang linter to check the errors handling expressions [fast: true, auto-fix: false]
|
||||||
- gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
|
- gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
|
||||||
- lll # Reports long lines [fast: true, auto-fix: false]
|
- lll # Reports long lines [fast: true, auto-fix: false]
|
||||||
- testpackage # Makes you use a separate _test package [fast: true, auto-fix: false]
|
- testpackage # Makes you use a separate _test package [fast: true, auto-fix: false]
|
||||||
|
|
|
||||||
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -4,6 +4,24 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Key Armoring with custom headers
|
||||||
|
```go
|
||||||
|
(key *Key) ArmorWithCustomHeaders(comment, version string) (string, error)
|
||||||
|
(key *Key) GetArmoredPublicKeyWithCustomHeaders(comment, version string) (string, error)
|
||||||
|
```
|
||||||
|
- Message armoring with custom headers
|
||||||
|
```go
|
||||||
|
(msg *PGPMessage) GetArmoredWithCustomHeaders(comment, version string) (string, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved key and message armoring testing
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Public key armoring headers
|
||||||
|
|
||||||
## [2.0.1] - 2020-05-01
|
## [2.0.1] - 2020-05-01
|
||||||
### Security
|
### Security
|
||||||
- Updated underlying crypto library
|
- Updated underlying crypto library
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,35 @@ func ArmorWithTypeBuffered(w io.Writer, armorType string) (io.WriteCloser, error
|
||||||
|
|
||||||
// ArmorWithType armors input with the given armorType.
|
// ArmorWithType armors input with the given armorType.
|
||||||
func ArmorWithType(input []byte, armorType string) (string, error) {
|
func ArmorWithType(input []byte, armorType string) (string, error) {
|
||||||
|
return armorWithTypeAndHeaders(input, armorType, internal.ArmorHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArmorWithTypeAndCustomHeaders armors input with the given armorType and
|
||||||
|
// headers.
|
||||||
|
func ArmorWithTypeAndCustomHeaders(input []byte, armorType, version, comment string) (string, error) {
|
||||||
|
headers := make(map[string]string)
|
||||||
|
if version != "" {
|
||||||
|
headers["Version"] = version
|
||||||
|
}
|
||||||
|
if comment != "" {
|
||||||
|
headers["Comment"] = comment
|
||||||
|
}
|
||||||
|
return armorWithTypeAndHeaders(input, armorType, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unarmor unarmors an armored input into a byte array.
|
||||||
|
func Unarmor(input string) ([]byte, error) {
|
||||||
|
b, err := internal.Unarmor(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ioutil.ReadAll(b.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func armorWithTypeAndHeaders(input []byte, armorType string, headers map[string]string) (string, error) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
|
|
||||||
w, err := armor.Encode(&b, armorType, internal.ArmorHeaders)
|
w, err := armor.Encode(&b, armorType, headers)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -41,12 +67,3 @@ func ArmorWithType(input []byte, armorType string) (string, error) {
|
||||||
}
|
}
|
||||||
return b.String(), nil
|
return b.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unarmor unarmors an armored key.
|
|
||||||
func Unarmor(input string) ([]byte, error) {
|
|
||||||
b, err := internal.Unarmor(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return ioutil.ReadAll(b.Body)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
openpgp "golang.org/x/crypto/openpgp"
|
openpgp "golang.org/x/crypto/openpgp"
|
||||||
xarmor "golang.org/x/crypto/openpgp/armor"
|
|
||||||
packet "golang.org/x/crypto/openpgp/packet"
|
packet "golang.org/x/crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -190,6 +189,7 @@ func (key *Key) Serialize() ([]byte, error) {
|
||||||
return buffer.Bytes(), err
|
return buffer.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Armor returns the armored key as a string with default gopenpgp headers.
|
||||||
func (key *Key) Armor() (string, error) {
|
func (key *Key) Armor() (string, error) {
|
||||||
serialized, err := key.Serialize()
|
serialized, err := key.Serialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -199,21 +199,36 @@ func (key *Key) Armor() (string, error) {
|
||||||
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
|
return armor.ArmorWithType(serialized, constants.PrivateKeyHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetArmoredPublicKey returns the armored public keys from this keyring.
|
// ArmorWithCustomHeaders returns the armored key as a string, with
|
||||||
func (key *Key) GetArmoredPublicKey() (s string, err error) {
|
// the given headers. Empty parameters are omitted from the headers.
|
||||||
var outBuf bytes.Buffer
|
func (key *Key) ArmorWithCustomHeaders(comment, version string) (string, error) {
|
||||||
aw, err := xarmor.Encode(&outBuf, openpgp.PublicKeyType, nil)
|
serialized, err := key.Serialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = key.entity.Serialize(aw); err != nil {
|
return armor.ArmorWithTypeAndCustomHeaders(serialized, constants.PrivateKeyHeader, version, comment)
|
||||||
_ = aw.Close()
|
}
|
||||||
|
|
||||||
|
// GetArmoredPublicKey returns the armored public keys from this keyring.
|
||||||
|
func (key *Key) GetArmoredPublicKey() (s string, err error) {
|
||||||
|
serialized, err := key.GetPublicKey()
|
||||||
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = aw.Close()
|
return armor.ArmorWithType(serialized, constants.PublicKeyHeader)
|
||||||
return outBuf.String(), err
|
}
|
||||||
|
|
||||||
|
// GetArmoredPublicKeyWithCustomHeaders returns the armored public key as a string, with
|
||||||
|
// the given headers. Empty parameters are omitted from the headers.
|
||||||
|
func (key *Key) GetArmoredPublicKeyWithCustomHeaders(comment, version string) (string, error) {
|
||||||
|
serialized, err := key.GetPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return armor.ArmorWithTypeAndCustomHeaders(serialized, constants.PublicKeyHeader, version, comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicKey returns the unarmored public keys from this keyring.
|
// GetPublicKey returns the unarmored public keys from this keyring.
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,25 @@ func TestArmorKeys(t *testing.T) {
|
||||||
t.Fatal("Cannot armor unprotected EC key:" + err.Error())
|
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-----.*Version: GopenPGP [0-9]+\.[0-9]+\.[0-9]+.*-----END PGP PRIVATE KEY BLOCK-----$`)
|
||||||
assert.Regexp(t, rTest, noPasswordRSA)
|
assert.Regexp(t, rTest, noPasswordRSA)
|
||||||
assert.Regexp(t, rTest, noPasswordEC)
|
assert.Regexp(t, rTest, noPasswordEC)
|
||||||
assert.Regexp(t, rTest, keyTestArmoredRSA)
|
assert.Regexp(t, rTest, keyTestArmoredRSA)
|
||||||
assert.Regexp(t, rTest, keyTestArmoredEC)
|
assert.Regexp(t, rTest, keyTestArmoredEC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArmorKeysWithCustomHeader(t *testing.T) {
|
||||||
|
comment := "User-defined private key comment"
|
||||||
|
version := "User-defined private key version"
|
||||||
|
armored, err := keyTestRSA.ArmorWithCustomHeaders(comment, version)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Could not armor the private key:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, armored, "Comment: "+comment)
|
||||||
|
assert.Contains(t, armored, "Version: "+version)
|
||||||
|
}
|
||||||
|
|
||||||
func TestLockUnlockKeys(t *testing.T) {
|
func TestLockUnlockKeys(t *testing.T) {
|
||||||
testLockUnlockKey(t, keyTestArmoredRSA, keyTestPassphrase)
|
testLockUnlockKey(t, keyTestArmoredRSA, keyTestPassphrase)
|
||||||
testLockUnlockKey(t, keyTestArmoredEC, keyTestPassphrase)
|
testLockUnlockKey(t, keyTestArmoredEC, keyTestPassphrase)
|
||||||
|
|
@ -257,7 +269,7 @@ func TestFailCheckIntegrity(t *testing.T) {
|
||||||
assert.Exactly(t, false, isVerified)
|
assert.Exactly(t, false, isVerified)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArmorPublicKey(t *testing.T) {
|
func TestGetPublicKey(t *testing.T) {
|
||||||
publicKey, err := keyTestRSA.GetPublicKey()
|
publicKey, err := keyTestRSA.GetPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while obtaining public key, got:", err)
|
t.Fatal("Expected no error while obtaining public key, got:", err)
|
||||||
|
|
@ -265,19 +277,21 @@ func TestArmorPublicKey(t *testing.T) {
|
||||||
|
|
||||||
decodedKey, err := NewKey(publicKey)
|
decodedKey, err := NewKey(publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while creating public key ring, got:", err)
|
t.Fatal("Expected no error while creating public key, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
privateFingerprint := keyTestRSA.GetFingerprint()
|
privateFingerprint := keyTestRSA.GetFingerprint()
|
||||||
publicFingerprint := decodedKey.GetFingerprint()
|
publicFingerprint := decodedKey.GetFingerprint()
|
||||||
|
|
||||||
|
assert.False(t, decodedKey.IsPrivate())
|
||||||
|
assert.True(t, keyTestRSA.IsPrivate())
|
||||||
assert.Exactly(t, privateFingerprint, publicFingerprint)
|
assert.Exactly(t, privateFingerprint, publicFingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetArmoredPublicKey(t *testing.T) {
|
func TestGetArmoredPublicKey(t *testing.T) {
|
||||||
privateKey, err := NewKeyFromArmored(readTestFile("keyring_privateKey", false))
|
privateKey, err := NewKeyFromArmored(readTestFile("keyring_privateKey", false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Expected no error while unarmouring private key, got:", err)
|
t.Fatal("Expected no error while unarmoring private key, got:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := privateKey.GetArmoredPublicKey()
|
s, err := privateKey.GetArmoredPublicKey()
|
||||||
|
|
@ -309,6 +323,47 @@ func TestGetArmoredPublicKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Exactly(t, eb, b)
|
assert.Exactly(t, eb, b)
|
||||||
|
|
||||||
|
publicKey, err := keyTestRSA.GetArmoredPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Expected no error while obtaining armored public key, got:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedKey, err := NewKeyFromArmored(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Expected no error while creating public key from armored, got:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, decodedKey.IsPrivate())
|
||||||
|
assert.True(t, keyTestRSA.IsPrivate())
|
||||||
|
assert.Contains(t, publicKey, "Version: GopenPGP")
|
||||||
|
|
||||||
|
privateFingerprint := keyTestRSA.GetFingerprint()
|
||||||
|
publicFingerprint := decodedKey.GetFingerprint()
|
||||||
|
|
||||||
|
assert.Exactly(t, privateFingerprint, publicFingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetArmoredPublicKeyWithCustomHeaders(t *testing.T) {
|
||||||
|
comment := "User-defined public key comment"
|
||||||
|
version := "User-defined public key version"
|
||||||
|
armored, err := keyTestRSA.GetArmoredPublicKeyWithCustomHeaders(comment, version)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Could not armor the public key:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, armored, "Comment: "+comment)
|
||||||
|
assert.Contains(t, armored, "Version: "+version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetArmoredPublicKeyWithEmptyCustomHeaders(t *testing.T) {
|
||||||
|
armored, err := keyTestRSA.GetArmoredPublicKeyWithCustomHeaders("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Could not armor the public key:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotContains(t, armored, "Version")
|
||||||
|
assert.NotContains(t, armored, "Comment")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSHA256FingerprintsV4(t *testing.T) {
|
func TestGetSHA256FingerprintsV4(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,10 @@ import (
|
||||||
|
|
||||||
var testSymmetricKey []byte
|
var testSymmetricKey []byte
|
||||||
|
|
||||||
// Corresponding key in testdata/keyring_privateKey
|
// Password for key in testdata/keyring_privateKeyLegacy: "123".
|
||||||
|
// Corresponding key in testdata/keyring_privateKey.
|
||||||
var testMailboxPassword = []byte("apple")
|
var testMailboxPassword = []byte("apple")
|
||||||
|
|
||||||
// Corresponding key in testdata/keyring_privateKeyLegacy
|
|
||||||
// const testMailboxPasswordLegacy = [][]byte{ []byte("123") }
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
keyRingTestPrivate *KeyRing
|
keyRingTestPrivate *KeyRing
|
||||||
keyRingTestPublic *KeyRing
|
keyRingTestPublic *KeyRing
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ type PGPSplitMessage struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// A ClearTextMessage is a signed but not encrypted PGP message,
|
// A ClearTextMessage is a signed but not encrypted PGP message,
|
||||||
// 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
|
||||||
|
|
@ -217,6 +217,12 @@ func (msg *PGPMessage) GetArmored() (string, error) {
|
||||||
return armor.ArmorWithType(msg.Data, constants.PGPMessageHeader)
|
return armor.ArmorWithType(msg.Data, constants.PGPMessageHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetArmoredWithCustomHeaders returns the armored message as a string, with
|
||||||
|
// the given headers. Empty parameters are omitted from the headers.
|
||||||
|
func (msg *PGPMessage) GetArmoredWithCustomHeaders(comment, version string) (string, error) {
|
||||||
|
return armor.ArmorWithTypeAndCustomHeaders(msg.Data, constants.PGPMessageHeader, version, comment)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBinaryDataPacket returns the unarmored binary datapacket as a []byte.
|
// GetBinaryDataPacket returns the unarmored binary datapacket as a []byte.
|
||||||
func (msg *PGPSplitMessage) GetBinaryDataPacket() []byte {
|
func (msg *PGPSplitMessage) GetBinaryDataPacket() []byte {
|
||||||
return msg.DataPacket
|
return msg.DataPacket
|
||||||
|
|
|
||||||
|
|
@ -178,3 +178,39 @@ func TestMultipleKeyMessageEncryption(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Exactly(t, message.GetString(), decrypted.GetString())
|
assert.Exactly(t, message.GetString(), decrypted.GetString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMessageGetArmoredWithCustomHeaders(t *testing.T) {
|
||||||
|
var message = NewPlainMessageFromString("plain text")
|
||||||
|
|
||||||
|
ciphertext, err := keyRingTestPublic.Encrypt(message, keyRingTestPrivate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Expected no error when encrypting, got:", err)
|
||||||
|
}
|
||||||
|
comment := "User-defined comment"
|
||||||
|
version := "User-defined version"
|
||||||
|
armored, err := ciphertext.GetArmoredWithCustomHeaders(comment, version)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Could not armor the ciphertext:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, armored, "Comment: "+comment)
|
||||||
|
assert.Contains(t, armored, "Version: "+version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessageGetArmoredWithEmptyHeaders(t *testing.T) {
|
||||||
|
var message = NewPlainMessageFromString("plain text")
|
||||||
|
|
||||||
|
ciphertext, err := keyRingTestPublic.Encrypt(message, keyRingTestPrivate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Expected no error when encrypting, got:", err)
|
||||||
|
}
|
||||||
|
comment := ""
|
||||||
|
version := ""
|
||||||
|
armored, err := ciphertext.GetArmoredWithCustomHeaders(comment, version)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Could not armor the ciphertext:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotContains(t, armored, "Version")
|
||||||
|
assert.NotContains(t, armored, "Comment")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,9 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Corresponding key in testdata/mime_privateKey
|
// Corresponding key in testdata/mime_privateKey.
|
||||||
var MIMEKeyPassword = []byte("test")
|
var MIMEKeyPassword = []byte("test")
|
||||||
|
|
||||||
// define call back interface
|
|
||||||
type Callbacks struct {
|
type Callbacks struct {
|
||||||
Testing *testing.T
|
Testing *testing.T
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func readTestFile(name string, trimNewlines bool) string {
|
||||||
return string(data)
|
return string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Corresponding key in ../crypto/testdata/keyring_privateKey
|
// Corresponding key in ../crypto/testdata/keyring_privateKey.
|
||||||
var testMailboxPassword = []byte("apple")
|
var testMailboxPassword = []byte("apple")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue