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:
zugzwang 2020-05-06 18:50:18 +02:00 committed by GitHub
parent b1e005fec3
commit dcc82c9fc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 176 additions and 31 deletions

View file

@ -16,7 +16,6 @@ import (
"github.com/pkg/errors"
openpgp "golang.org/x/crypto/openpgp"
xarmor "golang.org/x/crypto/openpgp/armor"
packet "golang.org/x/crypto/openpgp/packet"
)
@ -190,6 +189,7 @@ func (key *Key) Serialize() ([]byte, error) {
return buffer.Bytes(), err
}
// Armor returns the armored key as a string with default gopenpgp headers.
func (key *Key) Armor() (string, error) {
serialized, err := key.Serialize()
if err != nil {
@ -199,21 +199,36 @@ func (key *Key) Armor() (string, error) {
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)
// ArmorWithCustomHeaders returns the armored key as a string, with
// the given headers. Empty parameters are omitted from the headers.
func (key *Key) ArmorWithCustomHeaders(comment, version string) (string, error) {
serialized, err := key.Serialize()
if err != nil {
return "", err
}
if err = key.entity.Serialize(aw); err != nil {
_ = aw.Close()
return armor.ArmorWithTypeAndCustomHeaders(serialized, constants.PrivateKeyHeader, version, comment)
}
// 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
}
err = aw.Close()
return outBuf.String(), err
return armor.ArmorWithType(serialized, constants.PublicKeyHeader)
}
// 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.

View file

@ -73,13 +73,25 @@ func TestArmorKeys(t *testing.T) {
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, noPasswordEC)
assert.Regexp(t, rTest, keyTestArmoredRSA)
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) {
testLockUnlockKey(t, keyTestArmoredRSA, keyTestPassphrase)
testLockUnlockKey(t, keyTestArmoredEC, keyTestPassphrase)
@ -257,7 +269,7 @@ func TestFailCheckIntegrity(t *testing.T) {
assert.Exactly(t, false, isVerified)
}
func TestArmorPublicKey(t *testing.T) {
func TestGetPublicKey(t *testing.T) {
publicKey, err := keyTestRSA.GetPublicKey()
if err != nil {
t.Fatal("Expected no error while obtaining public key, got:", err)
@ -265,19 +277,21 @@ func TestArmorPublicKey(t *testing.T) {
decodedKey, err := NewKey(publicKey)
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()
publicFingerprint := decodedKey.GetFingerprint()
assert.False(t, decodedKey.IsPrivate())
assert.True(t, keyTestRSA.IsPrivate())
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)
t.Fatal("Expected no error while unarmoring private key, got:", err)
}
s, err := privateKey.GetArmoredPublicKey()
@ -309,6 +323,47 @@ func TestGetArmoredPublicKey(t *testing.T) {
}
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) {

View file

@ -12,12 +12,10 @@ import (
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")
// Corresponding key in testdata/keyring_privateKeyLegacy
// const testMailboxPasswordLegacy = [][]byte{ []byte("123") }
var (
keyRingTestPrivate *KeyRing
keyRingTestPublic *KeyRing

View file

@ -48,7 +48,7 @@ type PGPSplitMessage struct {
}
// 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 {
Data []byte
Signature []byte
@ -217,6 +217,12 @@ func (msg *PGPMessage) GetArmored() (string, error) {
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.
func (msg *PGPSplitMessage) GetBinaryDataPacket() []byte {
return msg.DataPacket

View file

@ -178,3 +178,39 @@ func TestMultipleKeyMessageEncryption(t *testing.T) {
}
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")
}

View file

@ -6,10 +6,9 @@ import (
"github.com/stretchr/testify/assert"
)
// Corresponding key in testdata/mime_privateKey
// Corresponding key in testdata/mime_privateKey.
var MIMEKeyPassword = []byte("test")
// define call back interface
type Callbacks struct {
Testing *testing.T
}