First commit.

This commit is contained in:
Naoriel Sa' Rocí 2024-05-10 22:24:14 +02:00
commit d3e5c3236c
27 changed files with 1766 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.log
.vscode/

8
CHANGELOG.md Normal file
View File

@ -0,0 +1,8 @@
# git.sa-roci.de/oss/go_pgp Release notes
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## v. 0.0.1
- Initial Release.

71
CreateOptions.go Normal file
View File

@ -0,0 +1,71 @@
package go_pgp
import (
"crypto"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
const (
// The minimal number of RSA bits
MinRSABits = 3072
)
type createOption func(cfg *packet.Config)
func HashOption(h crypto.Hash) createOption {
if !h.Available() {
h = 0
}
return func(cfg *packet.Config) {
cfg.DefaultHash = h
}
}
func CipherOption(cipher packet.CipherFunction) createOption {
if !cipher.IsSupported() {
cipher = 0
}
return func(cfg *packet.Config) {
cfg.DefaultCipher = cipher
}
}
func RSABitsOption(numBits int) createOption {
if numBits < MinRSABits {
numBits = MinRSABits
}
return func(cfg *packet.Config) {
cfg.RSABits = numBits
}
}
type CompressionLevel int
const (
DefaultCompression CompressionLevel = iota - 1
NoCompression
CompressionLevel1
CompressionLevel2
CompressionLevel3
CompressionLevel4
CompressionLevel5
CompressionLevel6
CompressionLevel7
CompressionLevel8
CompressionLevel9
compressionLevelOutOfRange
)
const CompressionLevelMax = compressionLevelOutOfRange - 1
func CompressionOption(level CompressionLevel) createOption {
if level < DefaultCompression || level > CompressionLevelMax {
level = CompressionLevel5
}
return func(cfg *packet.Config) {
cfg.CompressionConfig = &packet.CompressionConfig{
Level: int(level),
}
}
}

138
CreateOptions_test.go Normal file
View File

@ -0,0 +1,138 @@
package go_pgp
import (
"crypto"
"math/rand"
"testing"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
func TestHashOption(t *testing.T) {
testCases := []struct {
in crypto.Hash
}{
{},
{crypto.MD4},
{crypto.MD5},
{crypto.SHA1},
{crypto.SHA224},
{crypto.SHA256},
{crypto.SHA384},
{crypto.SHA512},
{crypto.MD5SHA1},
{crypto.RIPEMD160},
{crypto.SHA3_224},
{crypto.SHA3_256},
{crypto.SHA3_384},
{crypto.SHA3_512},
{crypto.SHA512_224},
{crypto.SHA512_256},
{crypto.BLAKE2b_256},
{crypto.BLAKE2b_384},
{crypto.BLAKE2b_512},
}
for i, testCase := range testCases {
testConfig := defaultConfig
optionFunc := HashOption(testCase.in)
optionFunc(&testConfig)
if testCase.in.Available() {
if testConfig.DefaultHash != testCase.in {
t.Errorf("TestHashOption: test case %d: expected hash %d but got %d", i+1, testCase.in, testConfig.DefaultHash)
}
} else if testConfig.DefaultHash != 0 {
t.Errorf("TestHashOption: test case %d: expected hash 0 but got %d", i+1, testConfig.DefaultHash)
}
}
}
func TestCipherOption(t *testing.T) {
testCases := []struct {
in packet.CipherFunction
}{
{},
{packet.Cipher3DES},
{packet.CipherCAST5},
{packet.CipherAES128},
{packet.CipherAES192},
{packet.CipherAES256},
}
for i, testCase := range testCases {
testConfig := defaultConfig
optionFunc := CipherOption(testCase.in)
optionFunc(&testConfig)
if testCase.in.IsSupported() {
if testConfig.DefaultCipher != testCase.in {
t.Errorf("TestCipherOption: test case %d: expected cipher %d but got %d", i+1, testCase.in, testConfig.DefaultCipher)
}
} else if testConfig.DefaultCipher != 0 {
t.Errorf("TestCipherOption: test case %d: expected cipher 0 but got %d", i+1, testConfig.DefaultCipher)
}
}
}
func TestRSABitsOption(t *testing.T) {
testCases := []struct {
in int
}{
{},
{1},
{MinRSABits - 1},
{MinRSABits},
{MinRSABits + 1},
{rand.Intn(10480)},
{rand.Intn(10480)},
{rand.Intn(10480)},
{rand.Intn(10480)},
{rand.Intn(10480)},
}
for i, testCase := range testCases {
testConfig := defaultConfig
optionFunc := RSABitsOption(testCase.in)
optionFunc(&testConfig)
if testCase.in > MinRSABits {
if testConfig.RSABits != testCase.in {
t.Errorf("TestRSABitsOption: test case %d: expected %d RSA bits but got %d", i+1, testCase.in, testConfig.DefaultCipher)
}
} else if testConfig.RSABits != MinRSABits {
t.Errorf("TestRSABitsOption: test case %d: expected %d RSA bits but got %d", i+1, MinRSABits, testConfig.DefaultCipher)
}
}
}
func TestCompressionOption(t *testing.T) {
testCases := []struct {
in int
}{
{-1},
{0},
{1},
{2},
{3},
{4},
{5},
{6},
{7},
{8},
{9},
{10},
}
for i, testCase := range testCases {
testConfig := defaultConfig
optionFunc := CompressionOption(CompressionLevel(testCase.in))
optionFunc(&testConfig)
if testCase.in >= int(DefaultCompression) && testCase.in <= int(CompressionLevelMax) {
if testConfig.CompressionConfig == nil {
t.Errorf("TestCompressionOption: test case %d: the compression is not set", i+1)
} else if testConfig.CompressionConfig.Level != testCase.in {
t.Errorf("TestCompressionOption: test case %d: expected compression level %d but got %d", i+1, testCase.in, testConfig.CompressionConfig.Level)
}
} else if testConfig.CompressionConfig.Level != int(CompressionLevel5) {
t.Errorf("TestCompressionOption: test case %d: expected compression level %d but got %d", i+1, int(CompressionLevel5), testConfig.CompressionConfig.Level)
}
}
}

87
Creation.go Normal file
View File

@ -0,0 +1,87 @@
package go_pgp
import (
"crypto"
"errors"
"fmt"
// required in order to use the crypto.SHA256- and -SHA512-Hashes
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
var (
defaultConfig = packet.Config{
RSABits: 4096,
DefaultHash: crypto.SHA512,
DefaultCompressionAlgo: packet.CompressionZIP,
DefaultCipher: packet.CipherAES256,
CompressionConfig: &packet.CompressionConfig{
Level: 5,
},
}
// ErrUndefinedPGPEntity defines the error for a null-reference to an openpgp.Entity
ErrUndefinedPGPEntity = errors.New("pgp entity undefined")
// ErrNoData defines the error for missing processable data
ErrNoData = errors.New("no data to process")
)
type Entity struct {
openpgp.Entity
cfg *packet.Config
}
// SetPassword encrypts all contained unencrypted private keys
// using the given passphrase.
func (e *Entity) SetPassword(passphrase []byte) error {
return e.Entity.EncryptPrivateKeys(passphrase, e.cfg)
}
// CreatePGPEntity creates an OpenPGP Entity with only a name given
func CreatePGPEntity(name string, options ...createOption) (*Entity, error) {
return createPGPEntity(name, "", "", options...)
}
// CreatePGPEntityEmail creates an OpenPGP Entity with a name and email given
func CreatePGPEntityEmail(name, email string, options ...createOption) (*Entity, error) {
return createPGPEntity(name, "", email, options...)
}
// CreateCommentedPGPEntity creates an OpenPGP Entity with a name and comment given
func CreateCommentedPGPEntity(name, comment string, options ...createOption) (entity *Entity, err error) {
return createPGPEntity(name, comment, "", options...)
}
// CreateCommentedPGPEntity creates an OpenPGP Entity with a name and comment given
func createPGPEntity(name, comment, email string, options ...createOption) (entity *Entity, err error) {
if name == "" && email == "" {
return nil, fmt.Errorf("name or email must be specified")
}
cfg := defaultConfig
for _, option := range options {
if option != nil {
option(&cfg)
}
}
var e *openpgp.Entity
e, err = openpgp.NewEntity(name, comment, email, &cfg)
if nil == err {
for _, identity := range e.Identities {
if nil != identity && nil != identity.SelfSignature {
identity.SelfSignature.PreferredHash = []uint8{8, 9, 10} //cf. "golang.org/x/crypto/openpgp/s2k" -> s2k.HashIdToHash
}
}
entity = &Entity{
Entity: *e,
cfg: &cfg,
}
}
return
}

112
Creation_test.go Normal file
View File

@ -0,0 +1,112 @@
package go_pgp
import (
"testing"
)
const (
testPassword = "p4ssw0rd"
)
func TestCreatePGPEntity(t *testing.T) {
entity, err := CreatePGPEntity("")
if err == nil || entity != nil {
t.Error("TestCreatePGPEntity: test case 1: unexpectedly succeeded in creating an unnamed entity")
}
entity, err = CreatePGPEntity("Me", RSABitsOption(0))
if err != nil {
t.Fatal("TestCreatePGPEntity: test case 2.1: received an error, trying to create a named entity")
} else if entity == nil {
t.Fatal("TestCreatePGPEntity: test case 2.1: failed to create a named entity")
}
if entity.PrimaryIdentity().Name != "Me" {
t.Errorf("TestCreatePGPEntity: test case 2.2: expected identity name 'Me' but got '%s'", entity.PrimaryIdentity().Name)
}
}
func TestSetPassword(t *testing.T) {
entity, _ := CreatePGPEntity("Me", RSABitsOption(0))
if entity.PrivateKey.Encrypted {
t.Fatal("TestSetPassword: test case 1: private key should not be encrypted yet")
}
err := entity.SetPassword([]byte(testPassword))
if err != nil || !entity.PrivateKey.Encrypted {
t.Fatal("TestSetPassword: test case 2: encryption of private key failed")
}
err = entity.SetPassword([]byte(testPassword + "2"))
if err != nil || !entity.PrivateKey.Encrypted {
t.Fatal("TestSetPassword: test case 3: second encryption of private key failed")
}
}
func TestCreatePGPEntityEmail(t *testing.T) {
entity, err := CreatePGPEntityEmail("", "")
if err == nil || entity != nil {
t.Error("TestCreatePGPEntityEmail: test case 1: unexpectedly succeeded in creating an unnamed entity")
}
expectedName := "<Jenkins@heye-international.com>"
entity, err = CreatePGPEntityEmail("", "Jenkins@heye-international.com")
if err != nil {
t.Fatal("TestCreatePGPEntityEmail: test case 2.1: received an error, trying to create a named entity")
} else if entity == nil {
t.Fatal("TestCreatePGPEntityEmail: test case 2.1: failed to create a named entity")
}
if name := entity.PrimaryIdentity().Name; name != expectedName {
t.Errorf("TestCreatePGPEntityEmail: test case 2.2: expected identity name '%s' but got '%s'", expectedName, name)
}
expectedName = "Me <Jenkins@heye-international.com>"
entity, err = CreatePGPEntityEmail("Me", "Jenkins@heye-international.com")
if err != nil {
t.Fatal("TestCreatePGPEntityEmail: test case 3.1: received an error, trying to create a named entity")
} else if entity == nil {
t.Fatal("TestCreatePGPEntityEmail: test case 3.1: failed to create a named entity")
}
if name := entity.PrimaryIdentity().Name; name != expectedName {
t.Errorf("TestCreatePGPEntityEmail: test case 3.2: expected identity name '%s' but got '%s'", expectedName, name)
}
expectedName = "Me"
entity, err = CreatePGPEntityEmail("Me", "")
if err != nil {
t.Fatal("TestCreatePGPEntityEmail: test case 4.1: received an error, trying to create a named entity")
} else if entity == nil {
t.Fatal("TestCreatePGPEntityEmail: test case 4.1: failed to create a named entity")
}
if name := entity.PrimaryIdentity().Name; name != expectedName {
t.Errorf("TestCreatePGPEntityEmail: test case 4.2: expected identity name '%s' but got '%s'", expectedName, name)
}
}
func TestCreateCommentedPGPEntity(t *testing.T) {
entity, err := CreateCommentedPGPEntity("", "myComment")
if err == nil || entity != nil {
t.Error("TestCreateCommentedPGPEntity: test case 1: unexpectedly succeeded in creating an unnamed entity")
}
expectedName := "Me (myComment)"
entity, err = CreateCommentedPGPEntity("Me", "myComment")
if err != nil {
t.Fatal("TestCreateCommentedPGPEntity: test case 2.1: received an error, trying to create a named entity")
} else if entity == nil {
t.Fatal("TestCreateCommentedPGPEntity: test case 2.1: failed to create a named entity")
}
if name := entity.PrimaryIdentity().Name; name != expectedName {
t.Errorf("TestCreateCommentedPGPEntity: test case 2.2: expected identity name '%s' but got '%s'", expectedName, name)
}
expectedName = "Me"
entity, err = CreateCommentedPGPEntity("Me", "")
if err != nil {
t.Fatal("TestCreateCommentedPGPEntity: test case 3.1: received an error, trying to create a named entity")
} else if entity == nil {
t.Fatal("TestCreateCommentedPGPEntity: test case 3.1: failed to create a named entity")
}
if name := entity.PrimaryIdentity().Name; name != expectedName {
t.Errorf("TestCreateCommentedPGPEntity: test case 3.2: expected identity name '%s' but got '%s'", expectedName, name)
}
}

165
DeCryption.go Normal file
View File

@ -0,0 +1,165 @@
package go_pgp
import (
"bytes"
"crypto"
"errors"
"hash"
"io"
"strconv"
// required in order to use the crypto.SHA256- and -SHA512-Hashes
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
type FileHints struct {
openpgp.FileHints
}
type EntityList struct {
openpgp.EntityList
}
// EncryptData encrypts a given payload with the public key of the target entity and optionally signs the data with the private key of the source entity
func EncryptData(data []byte, source, target *Entity, hints *FileHints) (encryptedData []byte, err error) {
if nil == target {
err = ErrUndefinedPGPEntity
return
}
if len(data) == 0 {
return data, nil
}
out := new(bytes.Buffer)
for _, identity := range target.Identities {
if nil != identity && nil != identity.SelfSignature {
identity.SelfSignature.PreferredHash = []uint8{8, 9, 10} //cf. "golang.org/x/crypto/openpgp/s2k" -> s2k.HashIdToHash
}
}
if hints == nil {
hints = &FileHints{}
}
var writer io.WriteCloser
if source != nil {
writer, err = openpgp.Encrypt(out, []*openpgp.Entity{&target.Entity}, &source.Entity, &hints.FileHints, target.cfg)
} else {
writer, err = openpgp.Encrypt(out, []*openpgp.Entity{&target.Entity}, nil, &hints.FileHints, target.cfg)
}
if nil != err {
return
}
_, err = writer.Write(data)
if nil != err {
return
}
writer.Close()
encryptedData = out.Bytes()
return
}
// DecryptData decrypts a given payload with the private key of the given entity
func DecryptData(data []byte, entity *Entity, entityPassword *string) (decryptedData []byte, err error) {
return DecryptDataVerify(data, entity, nil, entityPassword)
}
// DecryptDataVerify decrypts a given payload with the private key of the given entity
func DecryptDataVerify(data []byte, entity *Entity, remoteKeys *EntityList, entityPassword *string) (decryptedData []byte, err error) {
if nil == entity {
return nil, ErrUndefinedPGPEntity
}
if len(data) == 0 {
return nil, ErrNoData
}
if nil != entityPassword && 0 < len(*entityPassword) {
passphraseByte := []byte(*entityPassword)
if entity.PrivateKey.Encrypted {
entity.PrivateKey.Decrypt(passphraseByte)
}
if nil != entity.Subkeys {
for _, subkey := range entity.Subkeys {
if subkey.PrivateKey.Encrypted {
subkey.PrivateKey.Decrypt(passphraseByte)
}
}
}
}
keyring := openpgp.EntityList{&entity.Entity}
if remoteKeys != nil {
if el := remoteKeys.EntityList; len(el) > 0 {
keyring = append(keyring, el...)
}
}
message, err := openpgp.ReadMessage(bytes.NewBuffer(data), keyring, nil, entity.cfg)
if nil != err {
return
}
unverifiedBody, bodyErr := io.ReadAll(message.UnverifiedBody)
if bodyErr == nil && remoteKeys != nil && len(remoteKeys.EntityList) > 0 && message.IsSigned {
var keys []openpgp.Key
var signatureType packet.SignatureType
var hashFunc crypto.Hash
packetType := 0
if nil != message.Signature {
if nil != message.Signature.IssuerKeyId {
keys = remoteKeys.KeysById(*message.Signature.IssuerKeyId)
hashFunc = message.Signature.Hash
signatureType = message.Signature.SigType
if 0 < len(keys) {
packetType = 1
}
}
}
if hashFunc == 0 && packetType == 0 && len(keys) == 0 {
if keys = remoteKeys.KeysById(message.SignedByKeyId); len(keys) > 0 {
return unverifiedBody, nil
}
}
h, wrappedHash, err := hashForSignature(hashFunc, signatureType)
if err != nil {
return nil, &PGPError{err}
}
if _, err := wrappedHash.Write(unverifiedBody); err != nil && err != io.EOF {
return nil, &PGPError{err}
}
for _, key := range keys {
switch packetType {
case 1:
err = key.PublicKey.VerifySignature(h, message.Signature)
default:
return nil, &PGPError{errors.New("bad signature")}
}
if err == nil {
return unverifiedBody, nil
}
}
return nil, &PGPError{err}
}
return unverifiedBody, bodyErr
}
// hashForSignature returns a pair of hashes that can be used to verify a
// signature. The signature may specify that the contents of the signed message
// should be preprocessed (i.e. to normalize line endings). Thus this function
// returns two hashes. The second should be used to hash the message itself and
// performs any needed preprocessing.
func hashForSignature(hashID crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
if !hashID.Available() {
return nil, nil, errors.New("hash not available: " + strconv.Itoa(int(hashID)))
}
h := hashID.New()
switch sigType {
case packet.SigTypeBinary:
return h, h, nil
case packet.SigTypeText:
return h, openpgp.NewCanonicalTextHash(h), nil
}
return nil, nil, errors.New("unsupported signature type: " + strconv.Itoa(int(sigType)))
}

168
DeCryption_test.go Normal file
View File

@ -0,0 +1,168 @@
package go_pgp
import (
"bytes"
"crypto"
"os"
"path/filepath"
"sync"
"testing"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
var (
initialised bool
initMutex sync.Mutex
testSenderEntity *Entity
testReceiverEntity *Entity
)
func readTestFile(name string) ([]byte, error) {
folder, err := filepath.Abs("testdata")
if err != nil {
return nil, err
}
return os.ReadFile(filepath.Join(folder, name))
}
func initTest() {
initMutex.Lock()
defer initMutex.Unlock()
if !initialised {
armoredBinary, _ := readTestFile("testReceiverEntityArmored.pgp")
list, _ := ReadArmoredKeyRingBinary(armoredBinary)
receiverCfg := defaultConfig
testReceiverEntity = &Entity{*list.EntityList[0], &receiverCfg}
armoredBinary, _ = readTestFile("testSenderEntityArmored.pgp")
list, _ = ReadArmoredKeyRingBinary(armoredBinary)
senderCfg := defaultConfig
testSenderEntity = &Entity{*list.EntityList[0], &senderCfg}
initialised = true
}
}
func TestEncryptData(t *testing.T) {
initTest()
decryptionPassword := testPassword
testData := []byte("myTestData")
encrypted, err := EncryptData(testData, testSenderEntity, testSenderEntity, nil)
if err != nil || len(encrypted) == 0 {
t.Errorf("TestEncryptData: test case 1: failed to encrypt data")
}
decrypted, err := DecryptData(encrypted, testSenderEntity, &decryptionPassword)
if err != nil || len(decrypted) == 0 {
t.Errorf("TestEncryptData: test case 2.1: failed to decrypt data")
} else if !bytes.Equal(testData, decrypted) {
t.Errorf("TestEncryptData: test case 2.2: expected decrypted data '%s' but got '%s'", string(testData), string(decrypted))
}
encrypted, err = EncryptData(testData, testSenderEntity, testReceiverEntity, nil)
if err != nil || len(encrypted) == 0 {
t.Errorf("TestEncryptData: test case 3: failed to encrypt data")
}
decrypted, err = DecryptData(encrypted, testReceiverEntity, &decryptionPassword)
if err != nil || len(decrypted) == 0 {
t.Errorf("TestEncryptData: test case 4.1: failed to decrypt data")
} else if !bytes.Equal(testData, decrypted) {
t.Errorf("TestEncryptData: test case 4.2: expected decrypted data '%s' but got '%s'", string(testData), string(decrypted))
}
decrypted, err = DecryptDataVerify(encrypted, testReceiverEntity, &EntityList{EntityList: openpgp.EntityList{&testSenderEntity.Entity}}, &decryptionPassword)
if err != nil || len(decrypted) == 0 {
t.Errorf("TestEncryptData: test case 5.1: failed to decrypt data")
} else if !bytes.Equal(testData, decrypted) {
t.Errorf("TestEncryptData: test case 5.2: expected decrypted data '%s' but got '%s'", string(testData), string(decrypted))
}
decrypted, err = DecryptData(encrypted, testSenderEntity, &decryptionPassword)
if err == nil || len(decrypted) > 0 {
t.Errorf("TestEncryptData: test case 6: unexpectedly succeeded to decrypt data")
}
decrypted, err = DecryptDataVerify(encrypted, testSenderEntity, &EntityList{EntityList: openpgp.EntityList{&testReceiverEntity.Entity}}, &decryptionPassword)
if err == nil || len(decrypted) > 0 {
t.Errorf("TestEncryptData: test case 7: unexpectedly succeeded to decrypt data")
}
encrypted, err = EncryptData(testData, nil, testReceiverEntity, nil)
if err != nil || len(encrypted) == 0 {
t.Errorf("TestEncryptData: test case 8: failed to encrypt data")
}
decrypted, err = DecryptData(encrypted, testReceiverEntity, &decryptionPassword)
if err != nil || len(decrypted) == 0 {
t.Errorf("TestEncryptData: test case 9.1: failed to decrypt data")
} else if !bytes.Equal(testData, decrypted) {
t.Errorf("TestEncryptData: test case 9.2: expected decrypted data '%s' but got '%s'", string(testData), string(decrypted))
}
decrypted, err = DecryptDataVerify(encrypted, testReceiverEntity, &EntityList{EntityList: openpgp.EntityList{&testSenderEntity.Entity}}, &decryptionPassword)
if err != nil || len(decrypted) == 0 {
t.Errorf("TestEncryptData: test case 10.1: failed to decrypt data")
} else if !bytes.Equal(testData, decrypted) {
t.Errorf("TestEncryptData: test case 10.2: expected decrypted data '%s' but got '%s'", string(testData), string(decrypted))
}
// Error cases
_, err = EncryptData(testData, nil, nil, nil)
if err == nil {
t.Errorf("TestEncryptData: test case 11: should fail to encrypt data for undefined entity")
}
encrypted, err = EncryptData(nil, nil, testReceiverEntity, nil)
if err != nil || encrypted != nil {
t.Errorf("TestEncryptData: test case 12: should not encrypt nil data and return no error")
}
encrypted, err = EncryptData([]byte{}, nil, testReceiverEntity, nil)
if err != nil || encrypted == nil || len(encrypted) != 0 {
t.Errorf("TestEncryptData: test case 13: should return empty data and no error")
}
}
func TestHashForSignature(t *testing.T) {
testCases := []struct {
hashID crypto.Hash
signatureType packet.SignatureType
expectError bool
}{
{
hashID: crypto.MD4,
signatureType: 0,
expectError: true,
},
{
hashID: crypto.SHA256,
signatureType: packet.SigTypeBinary,
expectError: false,
},
{
hashID: crypto.SHA256,
signatureType: packet.SigTypeText,
expectError: false,
},
{
hashID: crypto.SHA256,
signatureType: packet.SigTypeCasualCert,
expectError: true,
},
}
for i, testCase := range testCases {
_, _, err := hashForSignature(testCase.hashID, testCase.signatureType)
if err != nil {
if !testCase.expectError {
t.Errorf("TestHashForSignature: test case %d: unexpected error '%s'", i+1, err)
}
} else if testCase.expectError {
t.Errorf("TestHashForSignature: test case %d: expected error did not occur", i+1)
}
}
}

101
Export.go Normal file
View File

@ -0,0 +1,101 @@
package go_pgp
import (
"bytes"
"io"
"os"
// required in order to use the crypto.SHA256- and -SHA512-Hashes
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
)
func writeKeyDataToFile(data []byte, filePath, dataType string, armored bool) error {
out, err := os.Create(filePath)
if nil != err {
return err
}
defer out.Close()
if !armored {
_, err = out.Write(data)
if nil == err {
out.Sync()
}
} else {
var armored io.WriteCloser
armored, err = armor.Encode(out, dataType, nil)
if nil != err {
return err
}
_, err = armored.Write(data)
if nil == err {
armored.Close()
out.Sync()
}
}
return err
}
// WritePublicEntityData will write the entitys complete public data to a file
func WritePublicEntityData(entity *Entity, filePath string) error {
data, err := ExtractPublicEntityData(entity)
if nil != err {
return err
}
return writeKeyDataToFile(data, filePath, openpgp.PublicKeyType, false)
}
// WriteArmoredPublicEntityData will write the entitys complete public data to an armored file
func WriteArmoredPublicEntityData(entity *Entity, filePath string) error {
data, err := ExtractPublicEntityData(entity)
if nil != err {
return err
}
return writeKeyDataToFile(data, filePath, openpgp.PublicKeyType, true)
}
// WritePrivateEntityData will write the entitys public and private keys to a file
func WritePrivateEntityData(entity *Entity, filePath string) error {
data, err := ExtractPrivateEntityData(entity)
if nil != err {
return err
}
return writeKeyDataToFile(data, filePath, openpgp.PrivateKeyType, false)
}
// WriteArmoredPrivateEntityData will write the entitys public and private keys to an armored file
func WriteArmoredPrivateEntityData(entity *Entity, filePath string) error {
data, err := ExtractPrivateEntityData(entity)
if nil != err {
return err
}
return writeKeyDataToFile(data, filePath, openpgp.PrivateKeyType, true)
}
// ExtractPublicEntityData will write the entitys complete public data to a []byte
func ExtractPublicEntityData(entity *Entity) ([]byte, error) {
if nil == entity {
return nil, ErrUndefinedPGPEntity
}
out := new(bytes.Buffer)
entity.Serialize(out)
return out.Bytes(), nil
}
// ExtractPrivateEntityData will write the entitys public and private keys to a []byte
func ExtractPrivateEntityData(entity *Entity) ([]byte, error) {
if nil == entity {
return nil, ErrUndefinedPGPEntity
}
out := new(bytes.Buffer)
entity.SerializePrivate(out, &defaultConfig)
return out.Bytes(), nil
}

138
Export_test.go Normal file
View File

@ -0,0 +1,138 @@
package go_pgp
import (
"path/filepath"
"testing"
)
func TestWritePublicEntityData(t *testing.T) {
initTest()
err := WritePublicEntityData(testSenderEntity, "ä/ö/ü.@")
if err == nil {
t.Errorf("TestWritePublicEntityData: test case 1: the file 'ä/ö/ü.@' should not have been written")
t.FailNow()
}
filePath := filepath.Join(t.TempDir(), "public.pgp")
err = WritePublicEntityData(testSenderEntity, filePath)
if err != nil {
t.Errorf("TestWritePublicEntityData: test case 2: failed to write public entity data")
t.FailNow()
}
el, err := ReadKeyRingFromFile(filePath)
if err != nil || len(el.EntityList) == 0 {
t.Errorf("TestWritePublicEntityData: test case 3: failed to read public entity data")
t.FailNow()
}
importedIdentity := el.EntityList[0].PrimaryIdentity()
originalIdentity := testSenderEntity.PrimaryIdentity()
if importedIdentity.Name != originalIdentity.Name {
t.Errorf("TestWritePublicEntityData: test case 4: expected primary identity name '%s' but got '%s'", originalIdentity.Name, importedIdentity.Name)
}
if err = WritePublicEntityData(nil, filePath); err == nil {
t.Error("TestWritePublicEntityData: test case 5: an undefined entity shoud not be exportable")
}
}
func TestWritePrivateEntityData(t *testing.T) {
initTest()
filePath := filepath.Join(t.TempDir(), "private.pgp")
err := WritePrivateEntityData(testSenderEntity, filePath)
if err != nil {
t.Errorf("TestWritePrivateEntityData: test case 1: failed to write public entity data")
t.FailNow()
}
el, err := ReadKeyRingFromFile(filePath)
if err != nil || len(el.EntityList) == 0 {
t.Errorf("TestWritePrivateEntityData: test case 2: failed to read public entity data")
t.FailNow()
}
entity := el.EntityList[0]
importedIdentity := entity.PrimaryIdentity()
originalIdentity := testSenderEntity.PrimaryIdentity()
if importedIdentity.Name != originalIdentity.Name {
t.Errorf("TestWritePrivateEntityData: test case 3: expected primary identity name '%s' but got '%s'", originalIdentity.Name, importedIdentity.Name)
}
if entity.PrivateKey == nil {
t.Errorf("TestWritePrivateEntityData: test case 4: private key is missing")
}
if err = WritePrivateEntityData(nil, filePath); err == nil {
t.Error("TestWritePrivateEntityData: test case 5: an undefined entity shoud not be exportable")
}
}
func TestWriteArmoredPublicEntityData(t *testing.T) {
initTest()
filePath := filepath.Join(t.TempDir(), "public.pgp")
err := WriteArmoredPublicEntityData(testSenderEntity, filePath)
if err != nil {
t.Errorf("TestWriteArmoredPublicEntityData: test case 1: failed to write public entity data")
t.FailNow()
}
el, err := ReadArmoredKeyRingFromFile(filePath)
if err != nil || len(el.EntityList) == 0 {
t.Errorf("TestWriteArmoredPublicEntityData: test case 2: failed to read public entity data")
t.FailNow()
}
importedIdentity := el.EntityList[0].PrimaryIdentity()
originalIdentity := testSenderEntity.PrimaryIdentity()
if importedIdentity.Name != originalIdentity.Name {
t.Errorf("TestWriteArmoredPublicEntityData: test case 3: expected primary identity name '%s' but got '%s'", originalIdentity.Name, importedIdentity.Name)
}
if err = WriteArmoredPublicEntityData(nil, filePath); err == nil {
t.Error("TestWriteArmoredPublicEntityData: test case 4: an undefined entity shoud not be exportable")
}
}
func TestWriteArmoredPrivateEntityData(t *testing.T) {
initTest()
filePath := filepath.Join(t.TempDir(), "private.pgp")
err := WriteArmoredPrivateEntityData(testSenderEntity, filePath)
if err != nil {
t.Errorf("TestWriteArmoredPrivateEntityData: test case 1: failed to write public entity data")
t.FailNow()
}
el, err := ReadArmoredKeyRingFromFile(filePath)
if err != nil || len(el.EntityList) == 0 {
t.Errorf("TestWriteArmoredPrivateEntityData: test case 2: failed to read public entity data")
t.FailNow()
}
entity := el.EntityList[0]
importedIdentity := entity.PrimaryIdentity()
originalIdentity := testSenderEntity.PrimaryIdentity()
if importedIdentity.Name != originalIdentity.Name {
t.Errorf("TestWriteArmoredPrivateEntityData: test case 3: expected primary identity name '%s' but got '%s'", originalIdentity.Name, importedIdentity.Name)
}
if entity.PrivateKey == nil {
t.Errorf("TestWriteArmoredPrivateEntityData: test case 4: private key is missing")
}
if err = WriteArmoredPrivateEntityData(nil, filePath); err == nil {
t.Error("TestWriteArmoredPrivateEntityData: test case 5: an undefined entity shoud not be exportable")
}
}
func TestExtractPublicEntityData(t *testing.T) {
if _, err := ExtractPublicEntityData(nil); err == nil {
t.Errorf("TestExtractPublicEntityData: test case 1: no key should be extractable from a 'nil' entity")
}
}
func TestExtractPrivateEntityData(t *testing.T) {
if _, err := ExtractPrivateEntityData(nil); err == nil {
t.Errorf("TestExtractPrivateEntityData: test case 1: no key should be extractable from a 'nil' entity")
}
}

46
Import.go Normal file
View File

@ -0,0 +1,46 @@
package go_pgp
import (
"bytes"
"os"
"github.com/ProtonMail/go-crypto/openpgp"
)
func convertImportResult(el openpgp.EntityList, err error) (EntityList, error) {
if err != nil {
return EntityList{}, err
}
result := EntityList{
EntityList: el,
}
return result, nil
}
func ReadArmoredKeyRingBinary(data []byte) (EntityList, error) {
r := bytes.NewReader(data)
return convertImportResult(openpgp.ReadArmoredKeyRing(r))
}
func ReadKeyRingBinary(data []byte) (EntityList, error) {
r := bytes.NewReader(data)
return convertImportResult(openpgp.ReadKeyRing(r))
}
func ReadArmoredKeyRingFromFile(filepath string) (EntityList, error) {
file, err := os.Open(filepath)
if err != nil {
return EntityList{}, err
}
defer file.Close()
return convertImportResult(openpgp.ReadArmoredKeyRing(file))
}
func ReadKeyRingFromFile(filepath string) (EntityList, error) {
file, err := os.Open(filepath)
if err != nil {
return EntityList{}, err
}
defer file.Close()
return convertImportResult(openpgp.ReadKeyRing(file))
}

110
Import_test.go Normal file
View File

@ -0,0 +1,110 @@
package go_pgp
import (
"path/filepath"
"testing"
)
func TestReadArmoredKeyRingBinary(t *testing.T) {
binary, err := readTestFile("testReceiverEntityArmored.pgp")
if err != nil {
t.Fatal("'testdata/testReceiverEntityArmored.pgp' could not be found")
}
entity, err := ReadArmoredKeyRingBinary(binary)
if err != nil {
t.Fatal("'testdata/testReceiverEntityArmored.pgp' could not be parsed")
}
if len(entity.EntityList) == 0 {
t.Fatal("no entities could be retrieved from 'testdata/testReceiverEntityArmored.pgp'")
}
binary, err = readTestFile("testReceiverEntityArmored_bad.pgp")
if err != nil {
t.Fatal("'testdata/testReceiverEntityArmored_bad.pgp' could not be found")
}
entity, err = ReadArmoredKeyRingBinary(binary)
if err == nil {
t.Fatal("'testdata/testReceiverEntityArmored_bad.pgp' should not be parseable")
}
}
func TestReadKeyRingBinary(t *testing.T) {
binary, err := readTestFile("testReceiverEntity.pgp")
if err != nil {
t.Fatal("'testdata/testReceiverEntity.pgp' could not be found")
}
entity, err := ReadKeyRingBinary(binary)
if err != nil {
t.Fatal("'testdata/testReceiverEntity.pgp' could not be parsed")
}
if len(entity.EntityList) == 0 {
t.Fatal("no entities could be retrieved from 'testdata/testReceiverEntity.pgp'")
}
binary, err = readTestFile("testReceiverEntity_bad.pgp")
if err != nil {
t.Fatal("'testdata/testReceiverEntity_bad.pgp' could not be found")
}
entity, err = ReadKeyRingBinary(binary)
if err == nil {
t.Fatal("'testdata/testReceiverEntity_bad.pgp' should not be parseable")
}
}
func TestReadArmoredKeyRingFromFile(t *testing.T) {
folder, err := filepath.Abs("testdata")
if err != nil {
t.Fatal("absolute path to 'testdata' folder could not be determined")
}
entity, err := ReadArmoredKeyRingFromFile(filepath.Join(folder, "testReceiverEntityArmored.pgp"))
if err != nil {
t.Fatal("'testdata/testReceiverEntityArmored.pgp' could not be parsed")
}
if len(entity.EntityList) == 0 {
t.Fatal("no entities could be retrieved from 'testdata/testReceiverEntityArmored.pgp'")
}
_, err = ReadArmoredKeyRingFromFile(filepath.Join(folder, "testReceiverEntityArmored_bad.pgp"))
if err == nil {
t.Error("'testdata/testReceiverEntityArmored_bad.pgp' should not be parseable")
}
_, err = ReadArmoredKeyRingFromFile(filepath.Join(folder, "doesNotExist.pgp"))
if err == nil {
t.Error("'testdata/doesNotExist.pgp' should not be found")
}
}
func TestReadKeyRingFromFile(t *testing.T) {
folder, err := filepath.Abs("testdata")
if err != nil {
t.Fatal("absolute path to 'testdata' folder could not be determined")
}
entity, err := ReadKeyRingFromFile(filepath.Join(folder, "testReceiverEntity.pgp"))
if err != nil {
t.Fatal("'testdata/testReceiverEntity.pgp' could not be parsed")
}
if len(entity.EntityList) == 0 {
t.Fatal("no entities could be retrieved from 'testdata/testReceiverEntity.pgp'")
}
_, err = ReadKeyRingFromFile(filepath.Join(folder, "testReceiverEntity_bad.pgp"))
if err == nil {
t.Error("'testdata/testReceiverEntity_bad.pgp' should not be parseable")
}
_, err = ReadKeyRingFromFile(filepath.Join(folder, "doesNotExist.pgp"))
if err == nil {
t.Error("'testdata/doesNotExist.pgp' should not be found")
}
}

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 Sa Rocí Solutions
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

15
PGPError.go Normal file
View File

@ -0,0 +1,15 @@
package go_pgp
const pgpErrorPrefix = "failed to verify PGP signature: "
type PGPError struct {
InternalError error
}
func (err *PGPError) Error() string {
return pgpErrorPrefix + err.InternalError.Error()
}
func (err *PGPError) Unwrap() error {
return err.InternalError
}

31
PGPError_test.go Normal file
View File

@ -0,0 +1,31 @@
package go_pgp
import (
"errors"
"fmt"
"io"
"testing"
)
func TestPGPErrorError(t *testing.T) {
testCases := []struct{ err error }{
{
err: fmt.Errorf("aarrr!!!"),
},
}
for i, testCase := range testCases {
err := PGPError{testCase.err}
expected := fmt.Sprintf("%s%s", pgpErrorPrefix, testCase.err.Error())
if result := err.Error(); result != expected {
t.Errorf("TestPGPErrorError: test case %d: expected '%s' but got '%s'", i+1, expected, result)
}
}
}
func TestPGPErrorUnwrap(t *testing.T) {
tester := &PGPError{io.EOF}
if !errors.Is(tester, io.EOF) {
t.Error("TestPGPErrorError: test case 1: Unwrapping failed")
}
}

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# git.sa-roci.de/oss/go_pgp
A convenience wrapper module for the Golang PGP implementation of github.com/ProtonMail/go-crypto, which is published under the BSD-3-Clause license.

104
Signatures.go Normal file
View File

@ -0,0 +1,104 @@
package go_pgp
import (
"bytes"
"errors"
"io"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
)
func CreateSignature(data []byte, signer *Entity) ([]byte, error) {
if signer == nil {
return nil, errors.New("signer undefined")
}
var result []byte
var err error
buf := bytes.NewBuffer([]byte{})
err = openpgp.ArmoredDetachSign(buf, &signer.Entity, bytes.NewBuffer(data), signer.cfg)
if err == nil {
result = buf.Bytes()
}
return result, err
}
func CreateTextSignature(text string, signer *Entity) ([]byte, error) {
if signer == nil {
return nil, errors.New("signer undefined")
}
var result []byte
var err error
buf := bytes.NewBuffer([]byte{})
err = openpgp.DetachSignText(buf, &signer.Entity, bytes.NewBufferString(text), signer.cfg)
if err == nil {
result = buf.Bytes()
}
return result, err
}
func CreateArmoredSignature(data []byte, signer *Entity) (string, error) {
if signer == nil {
return "", errors.New("signer undefined")
}
result := ""
var err error
buf := bytes.NewBufferString("")
err = openpgp.ArmoredDetachSign(buf, &signer.Entity, bytes.NewBuffer(data), signer.cfg)
if err == nil {
result = buf.String()
}
return result, err
}
func CreateArmoredTextSignature(text string, signer *Entity) (string, error) {
if signer == nil {
return "", errors.New("signer undefined")
}
result := ""
var err error
buf := bytes.NewBufferString("")
err = openpgp.ArmoredDetachSignText(buf, &signer.Entity, bytes.NewBufferString(text), signer.cfg)
if err == nil {
result = buf.String()
}
return result, err
}
func CheckSignature(keyring EntityList, signed, signature []byte) (*Entity, error) {
var _signed io.Reader
var _signature io.Reader
// Default readers
_signed = bytes.NewBuffer(signed)
_signature = bytes.NewBuffer(signature)
// Unarmor signature if possible
if unarmored, err := armor.Decode(_signature); err == nil {
_signature = unarmored.Body
}
// Check signature
signer, err := openpgp.CheckDetachedSignature(keyring, _signed, _signature, nil)
if signer != nil {
return &Entity{*signer, nil}, err
}
return nil, err
}

76
Signatures_test.go Normal file
View File

@ -0,0 +1,76 @@
package go_pgp
import (
"testing"
"github.com/ProtonMail/go-crypto/openpgp"
)
const (
testPayload = `test
data`
)
func TestCreateSignature(t *testing.T) {
initTest()
if sig, err := CreateSignature([]byte(testPayload), nil); err == nil || len(sig) > 0 {
t.Error("TestCreateSignature: test case 1: if the signer entity is undefined the function should not succeed")
}
if sig, err := CreateSignature([]byte(testPayload), testSenderEntity); err != nil || len(sig) == 0 {
t.Error("TestCreateSignature: test case 2: if the signer entity is defined the function should succeed")
}
}
func TestCreateTextSignature(t *testing.T) {
initTest()
if sig, err := CreateTextSignature(testPayload, nil); err == nil || len(sig) > 0 {
t.Error("TestCreateTextSignature: test case 1: if the signer entity is undefined the function should not succeed")
}
if sig, err := CreateTextSignature(testPayload, testSenderEntity); err != nil || len(sig) == 0 {
t.Error("TestCreateTextSignature: test case 2: if the signer entity is defined the function should succeed")
}
}
func TestCreateArmoredSignature(t *testing.T) {
initTest()
if sig, err := CreateArmoredSignature([]byte(testPayload), nil); err == nil || len(sig) > 0 {
t.Error("TestCreateArmoredSignature: test case 1: if the signer entity is undefined the function should not succeed")
}
if sig, err := CreateArmoredSignature([]byte(testPayload), testSenderEntity); err != nil || len(sig) == 0 {
t.Error("TestCreateArmoredSignature: test case 2: if the signer entity is defined the function should succeed")
}
}
func TestCreateArmoredTextSignature(t *testing.T) {
initTest()
if sig, err := CreateArmoredTextSignature(testPayload, nil); err == nil || len(sig) > 0 {
t.Error("TestCreateArmoredTextSignature: test case 1: if the signer entity is undefined the function should not succeed")
}
if sig, err := CreateArmoredTextSignature(testPayload, testSenderEntity); err != nil || len(sig) == 0 {
t.Error("TestCreateArmoredTextSignature: test case 2: if the signer entity is defined the function should succeed")
}
}
func TestCheckSignature(t *testing.T) {
initTest()
armoredSignature, _ := CreateArmoredSignature([]byte(testPayload), testSenderEntity)
e, err := CheckSignature(EntityList{EntityList: openpgp.EntityList{&testSenderEntity.Entity}}, []byte(testPayload), []byte(armoredSignature))
if e == nil || err != nil {
t.Error("TestCheckSignature: test case 1: verification of the signature should have succeeded")
}
e, err = CheckSignature(EntityList{EntityList: openpgp.EntityList{&testReceiverEntity.Entity}}, []byte(testPayload), []byte(armoredSignature))
if e != nil || err == nil {
t.Error("TestCheckSignature: test case 2: verification of the signature should not have succeeded")
}
}

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module git.sa-roci.de/oss/go_pgp
go 1.20
require github.com/ProtonMail/go-crypto v1.0.0
require (
github.com/cloudflare/circl v1.3.7 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/sys v0.18.0 // indirect
)

51
go.sum Normal file
View File

@ -0,0 +1,51 @@
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

9
go_pgp.code-workspace Normal file
View File

@ -0,0 +1,9 @@
{
"folders": [
{
"name": "git.sa-roci.de/oss/go_pgp",
"path": "."
}
],
"settings": {}
}

BIN
testdata/testReceiverEntity.pgp vendored Normal file

Binary file not shown.

105
testdata/testReceiverEntityArmored.pgp vendored Normal file
View File

@ -0,0 +1,105 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
8bhGpKfbM+WOa+BicpXvFs82Cac5EFFw
=s85P
-----END PGP PRIVATE KEY BLOCK-----

View File

@ -0,0 +1,101 @@
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
8bhGpKfbM+WOa+BicpXvFs82Cac5EFFw

BIN
testdata/testReceiverEntity_bad.pgp vendored Normal file

Binary file not shown.

BIN
testdata/testSenderEntity.pgp vendored Normal file

Binary file not shown.

105
testdata/testSenderEntityArmored.pgp vendored Normal file
View File

@ -0,0 +1,105 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
j2cS6+ZAFwEKAh8MaTL51D08ND0TYQ==
=RZn6
-----END PGP PRIVATE KEY BLOCK-----