Initial commit

This commit is contained in:
Euphoria Laxis 2024-07-25 16:25:01 +02:00
commit b5f16b51cb
Signed by: euphoria-laxis
GPG Key ID: 6892E47DD43F59EF
14 changed files with 600 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
### Go
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
# Go workspace file
go.work
### GoLand
.idea/

24
LICENSE Normal file
View File

@ -0,0 +1,24 @@
// euphoria-laxis.fr/go-packages/go-pgp@v0.1.0
MIT License
Copyright (c) 2020 Jason Chavannes <jason.chavannes@gmail.com>
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.

60
README.md Normal file
View File

@ -0,0 +1,60 @@
# Go PGP
Layer on top of `golang.org/x/crypto/openpgp` to handle a few PGP use cases.
Forked from [jchavannes/go-pgp](https://github.com/jchavannes/go-pgp). Updated
to be used as a package.
## Examples
### Encryption
[pgp/encrypt_test.go](pgp/encrypt_test.go)
#### Encrypt
````go
// Create public key entity
publicKeyPacket, _ := pgp.GetPublicKeyPacket([]byte(TestPublicKey))
pubEntity, _ := pgp.CreateEntityFromKeys(publicKeyPacket, nil)
// Encrypt message
encrypted, _ := pgp.Encrypt(pubEntity, []byte(TestMessage))
````
#### Decrypt
````go
// Create private key entity
privEntity, _ := pgp.GetEntity([]byte(TestPublicKey), []byte(TestPrivateKey))
// Decrypt message
decrypted, _ := pgp.Decrypt(privEntity, encrypted)
````
### Signing
[pgp/sign_test.go](pgp/sign_test.go)
#### Sign
````go
// Create private key entity
entity, _ := pgp.GetEntity([]byte(TestPublicKey), []byte(TestPrivateKey))
// Sign message
signature, _ := pgp.Sign(entity, []byte(TestMessage))
````
#### Verify
````go
// Create public key packet
pubKeyPacket, _ := pgp.GetPublicKeyPacket([]byte(TestPublicKey))
// Verify signature
err = pgp.Verify(pubKeyPacket, []byte(TestMessage), signature)
if err == nil {
fmt.Println("Signature verified.")
}
````

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module euphoria-laxis.fr/go-packages/go-pgp
go 1.22
require golang.org/x/crypto v0.25.0

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=

39
pgp/decrypt.go Normal file
View File

@ -0,0 +1,39 @@
package pgp
import (
"bytes"
_ "crypto/sha256"
"errors"
"fmt"
"io/ioutil"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
_ "golang.org/x/crypto/ripemd160"
)
func Decrypt(entity *openpgp.Entity, encrypted []byte) ([]byte, error) {
// Decode message
block, err := armor.Decode(bytes.NewReader(encrypted))
if err != nil {
return []byte{}, fmt.Errorf("error decoding: %v", err)
}
if block.Type != "PGP MESSAGE" {
return []byte{}, errors.New("invalid message type")
}
// Decrypt message
entityList := openpgp.EntityList{entity}
messageReader, err := openpgp.ReadMessage(block.Body, entityList, nil, nil)
if err != nil {
return []byte{}, fmt.Errorf("error reading message: %v", err)
}
var read []byte
read, err = ioutil.ReadAll(messageReader.UnverifiedBody)
if err != nil {
return []byte{}, fmt.Errorf("error reading unverified body: %v", err)
}
// Return output - an unencrypted, and uncompressed message
return read, nil
}

106
pgp/encrypt_test.go Normal file
View File

@ -0,0 +1,106 @@
package pgp
import (
"fmt"
"testing"
"golang.org/x/crypto/openpgp"
)
func TestEncrypt(t *testing.T) {
fmt.Println("Encrypt test: START")
pubEntity, err := GetEntity([]byte(testPublicKey), []byte{})
if err != nil {
t.Error(fmt.Errorf("error getting entity: %v", err))
}
fmt.Println("Created public key entity.")
var encrypted []byte
encrypted, err = Encrypt(pubEntity, []byte(testMessage))
if err != nil {
t.Error(err)
}
fmt.Println("Encrypted test message with public key entity.")
var privateEntity *openpgp.Entity
privateEntity, err = GetEntity([]byte(testPublicKey), []byte(testPrivateKey))
if err != nil {
t.Error(fmt.Errorf("error getting entity: %v", err))
}
fmt.Println("Created private key entity.")
var decrypted []byte
decrypted, err = Decrypt(privateEntity, encrypted)
if err != nil {
t.Error(err)
}
fmt.Println("Decrypted message with private key entity.")
decryptedMessage := string(decrypted)
if decryptedMessage != testMessage {
t.Error(fmt.Errorf("decrypted message does not equal original"))
}
fmt.Println("Decrypted message equals original message.")
fmt.Println("Encrypt test: END")
}
const (
testMessage = "hello world"
testPrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
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
=5NT7
-----END PGP PRIVATE KEY BLOCK-----`
testPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
xsFNBFkNK+ABEADUpjJ/kz3j+iz9qnzUb6ONw+WHSLp8umnd1z06SBVkWFjYReqf
oPCOq67XDseK71ZSevrIt7EdTLAzl0xN8kB+8iedAGM5OCakDe3R8L83OGy1Em26
PbrrYs3TYKGDXW65TsGYCoETROGgU2zPvuBDU1RvVvd9vAlWHQis43BOWaaakCEc
00V3sdNcfV+lz7fNUXEgtmTCCr9NWX4gO3YeenIIxep4WD27VwscW5Q2B1cnxcFL
+TZzE2oVjtXljGSO94XsekuNU47zwJZNGyU6SSlSZ+KVXuSdkRRfNYHlgDWg5b8C
xVmdVUfsx3bmNlOlXoyETj83xvRlLxn3PYIgOz6OlYGba5oDogK2QLXGTXK1o9OE
kgoghmCNQqxocvb1hQXT8cEynIbAdc6/JknYaoic6ka1iTTz3uN8FEPw5gRlidcQ
3wkbmqIS0LJs3JmVbD7/BxMY1dwqMyvulfnLiTsWSPvk41o7dHf077t23V9w78Jg
h4Xq4HRvt37PtuO6eWW3c5aUIWmvvDqMbMEqp2y23noYoVNqEpVoHolDdoCSurv/
XxbNBnj46XwaIs6OlrO2htV0al2/WVTNnSLxCyoHXoJEDXyaOyNKn1jM/FczgYQJ
069uC804ohOfjLmbtUEYE7Hjeo5utPm2ryjnakgV5AStKgL0SyFZUwN/DwARAQAB
=gO1a
-----END PGP PUBLIC KEY BLOCK-----`
)

43
pgp/encypt.go Normal file
View File

@ -0,0 +1,43 @@
package pgp
import (
"bytes"
_ "crypto/sha256"
"fmt"
"io"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
_ "golang.org/x/crypto/ripemd160"
)
func Encrypt(entity *openpgp.Entity, message []byte) ([]byte, error) {
// Create buffer to write output to
buf := new(bytes.Buffer)
// Create encoder
encoderWriter, err := armor.Encode(buf, "PGP MESSAGE", make(map[string]string))
if err != nil {
return []byte{}, fmt.Errorf("error creating OpenPGP armor: %v", err)
}
// Create encryptor with encoder
encryptorWriter, err := openpgp.Encrypt(encoderWriter, []*openpgp.Entity{entity}, nil, nil, nil)
if err != nil {
return []byte{}, fmt.Errorf("Error creating entity for encryption: %v", err)
}
// Write message to compressor
messageReader := bytes.NewReader(message)
_, err = io.Copy(encryptorWriter, messageReader)
if err != nil {
return []byte{}, fmt.Errorf("Error writing data to message reader: %v", err)
}
//compressorWriter.Close()
encryptorWriter.Close()
encoderWriter.Close()
// Return buffer output - an encoded, encrypted, and compressed message
return buf.Bytes(), nil
}

84
pgp/entity.go Normal file
View File

@ -0,0 +1,84 @@
package pgp
import (
"crypto"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
func GetEntity(publicKey []byte, privateKey []byte) (*openpgp.Entity, error) {
publicKeyPacket, err := getPublicKeyPacket(publicKey)
if err != nil {
return nil, err
}
var privateKeyPacket *packet.PrivateKey
if len(privateKey) > 0 {
privateKeyPacket, err = getPrivateKeyPacket(privateKey)
if err != nil {
return nil, err
}
}
return createEntityFromKeys(publicKeyPacket, privateKeyPacket)
}
// From https://gist.github.com/eliquious/9e96017f47d9bd43cdf9
func createEntityFromKeys(pubKey *packet.PublicKey, privKey *packet.PrivateKey) (*openpgp.Entity, error) {
config := packet.Config{
DefaultHash: crypto.SHA256,
DefaultCipher: packet.CipherAES256,
DefaultCompressionAlgo: packet.CompressionZLIB,
CompressionConfig: &packet.CompressionConfig{
Level: 9,
},
RSABits: 4096,
}
currentTime := config.Now()
uid := packet.NewUserId("", "", "")
e := openpgp.Entity{
PrimaryKey: pubKey,
PrivateKey: privKey,
Identities: make(map[string]*openpgp.Identity),
}
isPrimaryId := false
e.Identities[uid.Id] = &openpgp.Identity{
Name: uid.Name,
UserId: uid,
SelfSignature: &packet.Signature{
CreationTime: currentTime,
SigType: packet.SigTypePositiveCert,
PubKeyAlgo: packet.PubKeyAlgoRSA,
Hash: config.Hash(),
IsPrimaryId: &isPrimaryId,
FlagsValid: true,
FlagSign: true,
FlagCertify: true,
IssuerKeyId: &e.PrimaryKey.KeyId,
},
}
keyLifetimeSecs := uint32(86400 * 365)
e.Subkeys = make([]openpgp.Subkey, 1)
e.Subkeys[0] = openpgp.Subkey{
PublicKey: pubKey,
PrivateKey: privKey,
Sig: &packet.Signature{
CreationTime: currentTime,
SigType: packet.SigTypeSubkeyBinding,
PubKeyAlgo: packet.PubKeyAlgoRSA,
Hash: config.Hash(),
PreferredHash: []uint8{8}, // SHA-256
FlagsValid: true,
FlagEncryptStorage: true,
FlagEncryptCommunications: true,
IssuerKeyId: &e.PrimaryKey.KeyId,
KeyLifetimeSecs: &keyLifetimeSecs,
},
}
return &e, nil
}

51
pgp/generate.go Normal file
View File

@ -0,0 +1,51 @@
package pgp
import (
"bytes"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
)
type PGPKeyPair struct {
PublicKey string
PrivateKey string
}
func GenerateKeyPair(fullname string, comment string, email string) (PGPKeyPair, error) {
var e *openpgp.Entity
e, err := openpgp.NewEntity(fullname, comment, email, nil)
if err != nil {
return PGPKeyPair{}, err
}
for _, id := range e.Identities {
err := id.SelfSignature.SignUserId(id.UserId.Id, e.PrimaryKey, e.PrivateKey, nil)
if err != nil {
return PGPKeyPair{}, err
}
}
buf := new(bytes.Buffer)
w, err := armor.Encode(buf, openpgp.PublicKeyType, nil)
if err != nil {
return PGPKeyPair{}, err
}
e.Serialize(w)
w.Close()
pubKey := buf.String()
buf = new(bytes.Buffer)
w, err = armor.Encode(buf, openpgp.PrivateKeyType, nil)
if err != nil {
return PGPKeyPair{}, err
}
e.SerializePrivate(w, nil)
w.Close()
privateKey := buf.String()
return PGPKeyPair{
PublicKey: pubKey,
PrivateKey: privateKey,
}, nil
}

57
pgp/key.go Normal file
View File

@ -0,0 +1,57 @@
package pgp
import (
"bytes"
"errors"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
func getPublicKeyPacket(publicKey []byte) (*packet.PublicKey, error) {
publicKeyReader := bytes.NewReader(publicKey)
block, err := armor.Decode(publicKeyReader)
if err != nil {
return nil, err
}
if block.Type != openpgp.PublicKeyType {
return nil, errors.New("Invalid public key data")
}
packetReader := packet.NewReader(block.Body)
pkt, err := packetReader.Next()
if err != nil {
return nil, err
}
key, ok := pkt.(*packet.PublicKey)
if !ok {
return nil, err
}
return key, nil
}
func getPrivateKeyPacket(privateKey []byte) (*packet.PrivateKey, error) {
privateKeyReader := bytes.NewReader(privateKey)
block, err := armor.Decode(privateKeyReader)
if err != nil {
return nil, err
}
if block.Type != openpgp.PrivateKeyType {
return nil, errors.New("Invalid private key data")
}
packetReader := packet.NewReader(block.Body)
pkt, err := packetReader.Next()
if err != nil {
return nil, err
}
key, ok := pkt.(*packet.PrivateKey)
if !ok {
return nil, errors.New("Unable to cast to Private Key")
}
return key, nil
}

17
pgp/sign.go Normal file
View File

@ -0,0 +1,17 @@
package pgp
import (
"bytes"
"golang.org/x/crypto/openpgp"
)
func Sign(entity *openpgp.Entity, message []byte) ([]byte, error) {
writer := new(bytes.Buffer)
reader := bytes.NewReader(message)
err := openpgp.ArmoredDetachSign(writer, entity, reader, nil)
if err != nil {
return []byte{}, err
}
return writer.Bytes(), nil
}

38
pgp/sign_test.go Normal file
View File

@ -0,0 +1,38 @@
package pgp
import (
"fmt"
"testing"
"golang.org/x/crypto/openpgp"
)
func TestSignature(t *testing.T) {
fmt.Println("Signature test: START")
entity, err := GetEntity([]byte(testPublicKey), []byte(testPrivateKey))
if err != nil {
t.Error(err)
}
fmt.Println("Created private key entity.")
var signature []byte
signature, err = Sign(entity, []byte(testMessage))
if err != nil {
t.Error(err)
}
fmt.Println("Created signature of test message with private key entity.")
var publicKeyEntity *openpgp.Entity
publicKeyEntity, err = GetEntity([]byte(testPublicKey), []byte{})
if err != nil {
t.Error(err)
}
fmt.Println("Created public key entity.")
err = Verify(publicKeyEntity, []byte(testMessage), signature)
if err != nil {
t.Error(err)
}
fmt.Println("Signature verified using public key entity.")
fmt.Println("Signature test: END")
}

52
pgp/verify.go Normal file
View File

@ -0,0 +1,52 @@
package pgp
import (
"bytes"
"errors"
"fmt"
"io"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
func Verify(publicKeyEntity *openpgp.Entity, message []byte, signature []byte) error {
sig, err := decodeSignature(signature)
if err != nil {
return err
}
hash := sig.Hash.New()
messageReader := bytes.NewReader(message)
io.Copy(hash, messageReader)
err = publicKeyEntity.PrimaryKey.VerifySignature(hash, sig)
if err != nil {
return err
}
return nil
}
func decodeSignature(signature []byte) (*packet.Signature, error) {
signatureReader := bytes.NewReader(signature)
block, err := armor.Decode(signatureReader)
if err != nil {
return nil, fmt.Errorf("Error decoding OpenPGP Armor: %s", err)
}
if block.Type != openpgp.SignatureType {
return nil, errors.New("Invalid signature file")
}
reader := packet.NewReader(block.Body)
pkt, err := reader.Next()
if err != nil {
return nil, err
}
sig, ok := pkt.(*packet.Signature)
if !ok {
return nil, errors.New("Error parsing signature")
}
return sig, nil
}