Initial commit

This commit is contained in:
Zorchenhimer 2024-08-04 17:34:41 -04:00
commit 4c2df57ff8
Signed by: Zorchenhimer
GPG Key ID: 70A1AB767AAB9C20
15 changed files with 1038 additions and 0 deletions

1
ciphertext/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.pgp

2
cleartext/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.txt
!dummy-text.txt

46
cleartext/dummy-text.txt Normal file
View File

@ -0,0 +1,46 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi consequat
egestas mi sed rhoncus. Duis et nulla nec neque mattis bibendum. Ut volutpat
erat vitae velit sodales suscipit. Nunc malesuada dolor a dolor tempor feugiat.
Donec ut pretium elit, nec malesuada turpis. Aliquam gravida, augue sit amet
pretium ullamcorper, erat ex euismod augue, eleifend consequat massa odio vel
justo. Donec non lacinia sem. Nullam eu est pulvinar, gravida urna eu,
fermentum felis. Fusce sodales pharetra diam, a vehicula elit varius nec.
Suspendisse eget augue eget nunc placerat pulvinar auctor sed enim.
Nulla malesuada scelerisque semper. Proin et mi a odio ornare sodales in id
sapien. Integer eget quam imperdiet, blandit mi sed, mattis est. Nam nec libero
dolor. Aenean varius magna vitae risus imperdiet, iaculis volutpat erat
faucibus. Phasellus aliquam luctus nisi, vitae ullamcorper elit ultricies nec.
Etiam eu felis non dui fermentum iaculis. Vestibulum volutpat, justo id pretium
pharetra, lorem nisi luctus metus, sed hendrerit nulla sem eget sapien.
Suspendisse ipsum purus, fermentum ut felis et, ultricies vulputate lectus. Sed
eget molestie metus. Quisque vel sem eget felis faucibus viverra. Interdum et
malesuada fames ac ante ipsum primis in faucibus. Morbi varius ullamcorper leo
sed volutpat. Cras varius ipsum mauris, a efficitur neque sodales at. Nunc eget
mauris dui.
Aenean euismod lectus sit amet justo convallis, sit amet molestie nisi luctus.
Maecenas arcu nulla, rhoncus vel odio vitae, iaculis facilisis nibh. Ut
interdum ex a libero tristique, id eleifend nunc bibendum. Pellentesque
hendrerit nisl enim, vehicula tincidunt dui faucibus vitae. Nulla a facilisis
justo. Quisque vehicula diam at nisi facilisis efficitur. Pellentesque rhoncus
ipsum id ligula aliquet, rhoncus vehicula neque malesuada. Ut felis felis,
pretium non accumsan a, vulputate feugiat dolor. Nullam gravida risus sem,
vitae rhoncus mauris facilisis ac. Nullam iaculis non quam nec dignissim. Nam
sagittis fringilla pharetra.
Quisque in nunc felis. Sed ut risus convallis nibh sodales luctus. Sed lobortis
sem eros, sit amet lacinia ex sagittis vitae. Vestibulum at dolor pharetra,
aliquam lacus ut, consequat nulla. Nulla nulla metus, scelerisque et porttitor
id, blandit elementum purus. Mauris sagittis sit amet lacus vitae luctus. Etiam
nisl urna, ornare a tincidunt eu, pretium vitae mi. Nam rutrum blandit mollis.
Donec eu tortor sollicitudin, fermentum lorem eu, euismod odio. In hac
habitasse platea dictumst.
Aliquam pulvinar arcu non ex aliquet, non facilisis nibh lacinia. Praesent id
dictum purus. Morbi nec velit posuere, aliquam arcu sit amet, gravida urna.
Etiam blandit ligula et nibh suscipit ultricies. Quisque lorem mauris, interdum
ut nisl id, tincidunt dignissim leo. Suspendisse potenti. Phasellus et urna
quis ipsum gravida sagittis. Sed id consectetur odio. Pellentesque elit eros,
dignissim sit amet sodales sit amet, mollis nec tellus. Cras consectetur nec
ante porttitor condimentum.

294
client.go Normal file
View File

@ -0,0 +1,294 @@
package main
import (
"fmt"
"os"
//"bytes"
"io"
//_ "embed"
"net/http"
"path/filepath"
"strings"
//"encoding/json"
//"crypto/rsa"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/alexflint/go-arg"
)
const (
KeyServer string = "127.0.0.1:8080"
)
type Arguments struct {
CmdEncrypt *ArgEncrypt `arg:"subcommand:encrypt"`
CmdDecrypt *ArgDecrypt `arg:"subcommand:decrypt"`
}
type ArgEncrypt struct {
Input string `arg:"positional,required"`
Recipient string `arg:"--recipient,required"`
Output string `arg:"--output,required"`
}
type ArgDecrypt struct {
Input string `arg:"positional,required"`
Output string `arg:"positional,required"`
}
func run(args *Arguments) error {
var err error
switch {
case args.CmdEncrypt != nil:
err = encrypt(args.CmdEncrypt)
case args.CmdDecrypt != nil:
err = decrypt(args.CmdDecrypt)
default:
err = fmt.Errorf("unknown command")
}
return err
}
type KeyType string
const (
PublicKey KeyType = "pubkey"
PrivateKey KeyType = "privkey"
)
func getKey(search string, keytype KeyType) (openpgp.EntityList, error) {
resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s",
KeyServer,
keytype,
search,
))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
errstr := &strings.Builder{}
fmt.Fprintf(errstr, "Error getting public key: %s\n", resp.Status)
content, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Fprintf(errstr, "error reading error response: %s\n", err)
return nil, fmt.Errorf(errstr.String())
}
fmt.Fprintln(errstr, content)
return nil, fmt.Errorf(errstr.String())
}
return openpgp.ReadKeyRing(resp.Body)
}
func encrypt(args *ArgEncrypt) error {
info, err := os.Stat(args.Input)
if err != nil {
return err
}
infile, err := os.Open(args.Input)
if err != nil {
return err
}
defer infile.Close()
keyring, err := getKey(args.Recipient, PublicKey)
hints := &openpgp.FileHints{
IsBinary: true,
FileName: filepath.Base(args.Input),
ModTime: info.ModTime(),
}
outfile, err := os.Create(args.Output)
if err != nil {
return err
}
defer outfile.Close()
writer, err := openpgp.Encrypt(outfile, keyring, nil, hints, nil)
if err != nil {
return err
}
defer writer.Close()
_, err = io.Copy(writer, infile)
return err
}
func decrypt(args *ArgDecrypt) error {
infile, err := os.Open(args.Input)
if err != nil {
return err
}
defer infile.Close()
var keyring openpgp.EntityList
for {
p, err := packet.Read(infile)
if err == io.EOF {
break
} else if err != nil {
//fmt.Println("packet read error:", err)
continue
}
keypacket, ok := p.(*packet.EncryptedKey)
if !ok {
continue
}
keys, err := getKey(fmt.Sprintf("%X", keypacket.KeyId), PrivateKey)
if err != nil {
fmt.Println(err)
continue
}
keyring = append(keyring, keys...)
}
if len(keyring) == 0 {
return fmt.Errorf("No decryption keys found")
}
_, err = infile.Seek(0, 0)
if err != nil {
return err
}
msg, err := openpgp.ReadMessage(infile, keyring, nil, nil)
if err != nil {
return err
}
outfile, err := os.Create(args.Output)
if err != nil {
return err
}
defer outfile.Close()
_, err = io.Copy(outfile, msg.UnverifiedBody)
return err
}
//func getKeys(filename string) ([]string, error) {
// file, err := os.Open(filename)
// if err != nil {
// return nil, err
// }
// defer file.Close()
//
// keys := []string{}
// for {
// p, err := packet.Read(file)
// if err != nil {
// if err == io.EOF {
// break
// }
// //fmt.Println("err:", err.Error())
// continue
// }
//
// enckey, ok := p.(*packet.EncryptedKey)
// if !ok {
// continue
// }
//
// keys = append(keys, fmt.Sprintf("%X", enckey.KeyId))
// fmt.Println("KeyId:", enckey.KeyId)
// //fmt.Printf("%#v\n", p)
// }
//
// return keys, nil
//}
//
//func decrypt(input, output string, keys openpgp.EntityList) error {
// infile, err := os.Open(input)
// if err != nil {
// return err
// }
// defer infile.Close()
//
// detail, err := openpgp.ReadMessage(infile, keys, nil, nil)
// if err != nil {
// return err
// }
//
// if detail.SignatureError != nil {
// return detail.SignatureError
// }
//
// outfile, err := os.Create(output)
// if err != nil {
// return err
// }
// defer outfile.Close()
//
// _, err = io.Copy(outfile, detail.UnverifiedBody)
// return err
//}
//
//func encrypt(input, output string, keys openpgp.EntityList) error {
// info, err := os.Stat(input)
// if err != nil {
// return err
// }
//
// hints := &openpgp.FileHints{
// IsBinary: true,
// FileName: input,
// ModTime: info.ModTime(),
// }
//
// infile, err := os.Open(input)
// if err != nil {
// return err
// }
// defer infile.Close()
//
// outraw, err := os.Create(output)
// if err != nil {
// return err
// }
// defer outraw.Close()
//
// writer, err := openpgp.Encrypt(outraw, keys, nil, hints, nil)
// if err != nil {
// return err
// }
// defer writer.Close()
//
// _, err = io.Copy(writer, infile)
// if err != nil {
// return err
// }
//
// return nil
//}
//
//func loadKey() (openpgp.EntityList, error) {
// reader := bytes.NewReader(PrivateKey)
// return openpgp.ReadArmoredKeyRing(reader)
//}
//
//func loadPub() (openpgp.EntityList, error) {
// reader := bytes.NewReader(PublicKey)
// return openpgp.ReadArmoredKeyRing(reader)
//}
func main() {
args := &Arguments{}
arg.MustParse(args)
if err := run(args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module main
go 1.22.4
require (
github.com/ProtonMail/go-crypto v1.0.0
github.com/alexflint/go-arg v1.5.1
)
require (
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/sys v0.6.0 // indirect
)

61
go.sum Normal file
View File

@ -0,0 +1,61 @@
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/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
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 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

2
keys/aliases.sh Normal file
View File

@ -0,0 +1,2 @@
alias gpg-customer="gpg --no-default-keyring --keyring=/home/nick/code/pgp/keys/customer-pub.gpg"
alias gpg-internal="gpg --no-default-keyring --keyring=/home/nick/code/pgp/keys/internal.gpg"

BIN
keys/customer-pub.gpg Normal file

Binary file not shown.

123
keys/generate-keys.go Normal file
View File

@ -0,0 +1,123 @@
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"path/filepath"
//"io"
//"github.com/ProtonMail/go-crypto/openpgp/armor"
)
func main() {
err := run()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
names := []string{
"internal-A",
"internal-B",
"customer-A",
"customer-B",
"customer-C",
}
for _, name := range names {
fmt.Println("Generating keypair for", name)
err := keypair(name)
if err != nil {
return err
}
}
return nil
}
const (
keyDir string = "./"
)
func keypair(name string) error {
const bitSize int = 4096
key, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
return err
}
pub := key.Public()
keyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
},
)
pubPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(pub.(*rsa.PublicKey)),
},
)
err = os.WriteFile(filepath.Join(keyDir, "public", name+".pem"), pubPem, 0644)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(keyDir, "private", name+".pem"), keyPem, 0644)
if err != nil {
return err
}
pubDerRaw, err := x509.MarshalPKIXPublicKey(pub.(*rsa.PublicKey))
if err != nil {
return err
}
pubDer := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubDerRaw,
},
)
err = os.WriteFile(filepath.Join(keyDir, "public", name+".asc"), pubDer, 0644)
if err != nil {
return err
}
//err = encodeToArmor(filepath.Join(keyDir, "public", name+".asc"), pub.(*rsa.PublicKey))
//if err != nil {
// return err
//}
return nil
}
//func encodeToArmor(filename string, key *rsa.PublicKey) error {
// outfile, err := os.Create(filename)
// if err != nil {
// return err
// }
// defer outfile.Close()
//
// writer, err := armor.Encode(outfile, "PGP PUBLIC KEY", nil)
// if err != nil {
// return err
// }
// defer writer.Close()
//
// _, err = writer.Write(key.)
// return err
//}

View File

@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
scgQ1rzjpddT6YkUojc=
=gNhC
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1,51 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGavoaMBEADJG5heAJuF8S3oySAOMJWoXlZvy5C+wMdGnaZYUHVcRMd1oM7h
b+HKaFikB//DKX8z4sGMyZCo3W9R75wUtDKuTsPYKTHTnu2xTwndIcN69QkSohTY
rfBOPs4nCBiezQTr04ujxK3HjfwOM6wco1UrwnG5T29iWVbiv7ekUMGqPjF+mYGZ
oyBGHrRC2bfOXvabSTGxUNVIE7jJYWu0TBT/3s87SmjQ2gFcqmouCO3IlM1hx5tM
b5jlrxUXQpR154/rYqeYW8oH4WRrW9YoKcU+b5x60U4SEwcOL3vqtxxOGfe1j0Ov
9ddBVOMsdAINll6dUYECgCr+Ho48zLeaI4QaH34jqFV19QK7GYSNN6D8qxquXtmc
yJlvx9fP4hgjr3RSa1Mw414zYIFeUC/28/vvWtizKUCqSzC7vmaB7W/BKrBOHeuu
3nwyvBKHp5WzDqs5e1wUD1GsA8N+yFvANVAG7slEKZaGAFuqdC2bO7BCQp1amrWk
cMn5xLqD6WB/9jRlzC4EcxVeh47ebFnhhlOFM4X7X+LYcX5touLyzNSN/Pk8MPNP
5l2PD8OcqrTiwGXb1xYmAqjbYLLJ/miEkaYitnNU5EkeBalG/zcqG1taOwISNQLa
LzmJG8/m86PAPpDGvFX7CORiD25I1jTa0suMbauoY8CCRYhM09Zciwt0qwARAQAB
tB9DdXN0b21lciBCIDxjdXN0LWJAZXhhbXBsZS5jb20+iQJRBBMBCAA7FiEEM61K
MTzm4KhNx8sPUKcey8nWHAcFAmavoaMCGwMFCwkIBwICIgIGFQoJCAsCBBYCAwEC
HgcCF4AACgkQUKcey8nWHAdNLw/9GFTkO1uMGida7wI/2xKrb7xdvtC5OSyYF6Xk
0i8ehuEcKCabgbhZJnNA6BdixXoluDud6RAAl9MRBEvbr2mTHlJMHb9PU31VWCIg
Qye0HfGiEasHtky8Qm0yWkV5OREpi++9s2pZHaGb2aVH1Lv5X3dryUR7o3L9Nn2I
EG5QSLvPrV0n00B4B0RCxV2R6NTAK0nmHfZ53VFxy80fw6Vbzx2Bbph6KlH7aCJR
ZiE6+tZ40JBrT+ROyNhOKRO48tlUf/lRkWK0IPkG3p5V9BXa9lLOAV6DKHlL7503
OpqUdgKkL3r1d/EpYdpj4aI2aPt7eHJkkZnK3P4zqsoACLOUziF/vFcovFWG45eu
1CIhYwPbNw8jergRKcQAewquq4SP31Z7W2UiHfvJvwAsFBjy39IUsd/4UzkAT201
SFIzhBVex9jbIz0Oy40s4DW7/mmvCSPs2BJ/R091bQPCBf8//c1ZTfjeCIFh7Kdi
4OuTZqsS0Uc3T26suiDXOidpyEmp6HoXpYxnt8+wXt/PC0SGb8YyoLQMsSpRQgUM
3OlNuNPPXfVjIC+rZ/V5OvmU/TaZU+Tu9hdhBRvUUwvehsN295OKGCA+U64QHbQM
2hvX4kakYyrq143X01dmy1o18elDUE4wZrXLv1IBlRx5C3Vj7HKHgBadKH3biW/s
CKPTJAK5Ag0EZq+howEQAMezmI65UHCXRGyPgY/NXH+t+DBhWUdT/Myh7HfNXuGz
SQFeDIAEDUIG/dwkoFFigPNX0lij00UZAmsyltHw+wJ/RkpTChcQTCYMxwp0V7lm
qdpWQhfDoyt+vBlNBOhTC1tfNHp5tEDtGWW3orbw77A6EP++LBbBmA1ITuUlt0yr
lDPRnmj9/agnf92xbOxd+SXGrayuU8NLA1LFyUlh+mGeJo0+i8FMA2u/A3kvJH6h
eyhXn4RlhdFdK/EOuInEm0Xkjzd4Nn428femAjZFmgTdCXmUwJ6OPK1fGDfxBRwe
Kyi7UIlHzTEltSwHZrjqGNVI0CMdfhLYX+v7Lhq/su9T0JK+wuxsWAWOIROz4FNI
WdiqdfFCdGgCBVh9RadD0pd6iPCwsC/vQfEZjGZ3cUkLtL5dzzkKozM9ndCkk5lX
ndnchIbA7/UTuvzcnO26NdArEZOlTh0YKsL8TPIl/KoeJbFrvWmyfn3miD8iGl3S
4U4OGgHQKywvKxpofGRYQggzERpz9sENiI7r/9l+XP/iyPaM0Ne2i63qpc+qWv2X
g5xaZlPhAdYfVSlxoWMeMTz4vumxPXA0mMrCiwl5LDyGk5vKmD8dCLd74GAmbDha
9l12Chr/6ZYPNEsr2uqwU3IPc68eqKg7ch4wYtTMmNVgNTzaK97B0RpNW4JyapgT
ABEBAAGJAjYEGAEIACAWIQQzrUoxPObgqE3Hyw9Qpx7LydYcBwUCZq+howIbDAAK
CRBQpx7LydYcB/gID/9VYb+A5bYxxFHB2SnK3zR4xnsPg+JCmuO0fFufplXv0xix
gIm3o/d8EB+IkD7Qk5/6YPfkI4V+V9ZZPn+2AZhE0oAU/H1tiG5MM/blbvwfftNW
oBGx1UgD9e7UwrDrNS3fE6GwlIyUaCoThOH42TVY4Towzw07Z/zbV43+DV1g+5Bx
Q03Cn3+MtegDQG9u20NupRlUVSZrZXQmk37fcDhSzJxtaVZlQ//EWU/afOfTyMYp
R5Cbt517y0uTnMC0SP3bLPLwk8l75SgRa+dHhkPFwFUGs4nSYjmZc1PaIkIF0lyc
EYbB4KIwn+FbCIwhWpjTyD5c5hxsl+ILgTpFKMD5twqruX24j4BM/hfFDWY3YYdb
LCnaIzzxy+ixzATCkvV5ceN1Snd081VJMjj3GfYAr7ns1RT1VtgSzkc83qhtfJ8l
Q0UWWEUFsRCJh3MEOU1dEtNj6KJ4Rjpf+Q07qS3Go4ZtxXWIbFGt6/KCg4k15MBI
WUNPOLrhryNpw73ULFG0rT6DzEgPltL0eZUOX8TTfb9kiFazf6m7zwqE8oYzYJeF
wQ4wu6HhkHVdIsHumKQrTuZdALXh8HF/KMIL6CEDge1I4mlnc/knTcLQqGDJIHDE
7vMrrje6dc9HRiTCBmGyy+qk0zDUWbN3+fu9qBtK91xEJgjQVWJrqlQiI1QYAQ==
=wrGn
-----END PGP PUBLIC KEY BLOCK-----

View File

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

View File

@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
TKL8
=C9ti
-----END PGP PUBLIC KEY BLOCK-----

BIN
keys/internal.gpg Normal file

Binary file not shown.

245
server.go Normal file
View File

@ -0,0 +1,245 @@
package main
import (
"fmt"
"os"
"net/http"
"syscall"
"os/signal"
"time"
"context"
//"encoding/json"
"strings"
"github.com/ProtonMail/go-crypto/openpgp"
//"github.com/ProtonMail/go-crypto/openpgp/packet"
)
type Server struct {
PublicKeys openpgp.EntityList
PrivateKeys openpgp.EntityList
//PubkeyGroups []*openpgp.Entity
}
func NewServer(publicKeys, privateKeys []string) (*Server, error) {
pub, err := loadKeysFromFiles(publicKeys)
if err != nil {
return nil, err
}
priv, err := loadKeysFromFiles(privateKeys)
if err != nil {
return nil, err
}
return &Server{PublicKeys: pub, PrivateKeys: priv}, nil
}
func loadKeysFromFiles(filenames []string) (openpgp.EntityList, error) {
var keyring openpgp.EntityList
for _, f := range filenames {
file, err := os.Open(f)
if err != nil {
return nil, err
}
keys, err := openpgp.ReadArmoredKeyRing(file)
if err != nil {
file.Close()
return nil, err
}
file.Close()
keyring = append(keyring, keys...)
}
return keyring, nil
}
func (s *Server) handler_publickey(w http.ResponseWriter, r *http.Request) {
path := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
if len(path) < 2 {
fmt.Fprintln(w, "missing path stuff")
return
}
fmt.Println(r.URL.Path)
search := strings.ToLower(path[1])
fmt.Println("searching for", search)
key := s.findPubKey(search)
if key == nil {
http.Error(w, "Key not found", 500)
return
}
err := key.Serialize(w)
if err != nil {
fmt.Printf("Serialization error: %s\n", err)
}
}
func (s *Server) findPubKey(search string) *openpgp.Entity {
for _, k := range s.PublicKeys {
for _, id := range k.Identities {
if strings.ToLower(id.UserId.Name) == search {
return k
}
if strings.ToLower(id.UserId.Email) == search {
return k
}
}
}
return nil
}
func (s *Server) handler_privatekey(w http.ResponseWriter, r *http.Request) {
path := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
if len(path) < 2 {
fmt.Fprintln(w, "missing path stuff")
return
}
fmt.Println(r.URL.Path)
search := strings.ToLower(path[1])
fmt.Println("searching for", search)
key := s.findPrivKey(search)
if key == nil {
http.Error(w, "Key not found", 500)
return
}
err := key.SerializePrivate(w, nil)
if err != nil {
fmt.Printf("Serialization error: %s\n", err)
}
}
func (s *Server) findPrivKey(search string) *openpgp.Entity {
for _, k := range s.PrivateKeys {
for _, id := range k.Identities {
if strings.ToLower(id.UserId.Name) == search {
return k
}
if strings.ToLower(id.UserId.Email) == search {
return k
}
}
for _, sub := range k.Subkeys {
if fmt.Sprintf("%x", sub.PrivateKey.KeyId) == search {
return k
}
}
}
return nil
}
func (s *Server) handler_debug(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Public Keys:")
for _, key := range s.PublicKeys {
//fmt.Fprintf(w, "%#v\n", key.PrimaryIdentity())
fmt.Fprintf(w,"%s\n\t%X\n\t%X\n",
key.PrimaryIdentity().Name,
key.PrimaryKey.Fingerprint,
key.PrimaryKey.KeyId,
)
//j, err := json.Marshal(key)
//if err != nil {
// fmt.Fprintf(w, "ERROR: %s", err)
// return
//}
//fmt.Fprintf(w,"\t\t%s\n", j)
}
fmt.Fprintln(w, "\nPrivate Keys:")
for _, key := range s.PrivateKeys {
//fmt.Fprintf(w, "%#v\n", key.PrimaryIdentity())
fmt.Fprintf(w,"%s\n\t%X\n\t%X\n",
key.PrimaryIdentity().Name,
key.PrimaryKey.Fingerprint,
key.PrivateKey.KeyId,
)
for _, sub := range key.Subkeys {
fmt.Fprintf(w, "\t>%X\n\t>%X\n",
sub.PublicKey.KeyId,
sub.PrivateKey.KeyId,
)
}
}
}
func handler_default(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "henlo")
}
func run() error {
sv, err := NewServer(
[]string{
"keys/gpg-generated/cust-a.pub",
"keys/gpg-generated/cust-b.pub",
"keys/gpg-generated/internal-public.asc",
},
[]string{
"keys/gpg-generated/internal-private.asc",
},
)
if err != nil {
return err
}
mux := http.NewServeMux()
mux.HandleFunc("/pubkey/", sv.handler_publickey)
mux.HandleFunc("/privkey/", sv.handler_privatekey)
mux.HandleFunc("/debug/", sv.handler_debug)
mux.HandleFunc("/", handler_default)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
done := make(chan os.Signal, 1)
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go func() {
fmt.Println("listening on", server.Addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Println("Listen: ", err)
}
}()
<-done
fmt.Println("stopping")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
fmt.Println("Clean shutdown failed: ", err)
}
fmt.Println("goodbye")
return nil
}
func main() {
err := run()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}