Skip to content

Instantly share code, notes, and snippets.

@tobstarr
Last active January 6, 2016 13:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tobstarr/a4afa1ec7abf06fbe9e5 to your computer and use it in GitHub Desktop.
Save tobstarr/a4afa1ec7abf06fbe9e5 to your computer and use it in GitHub Desktop.
openpgp-issue

openpgp issue

It seems openpgp is stripping out all "\r" characters when encrypting plaintext.

Test

$ go run main.go
enc=gpg dec=gpg         OK  input="hello world" output="hello world"
enc=gpg dec=gpg         OK  input="\n" output="\n"
enc=gpg dec=gpg         OK  input="\t" output="\t"
enc=gpg dec=gpg         OK  input="\r" output="\r"
enc=gpg dec=gpg         OK  input="hello\t\r\nworld\r\n" output="hello\t\r\nworld\r\n"
enc=gpg dec=openpgp     OK  input="hello world" output="hello world"
enc=gpg dec=openpgp     OK  input="\n" output="\n"
enc=gpg dec=openpgp     OK  input="\t" output="\t"
enc=gpg dec=openpgp     OK  input="\r" output="\r"
enc=gpg dec=openpgp     OK  input="hello\t\r\nworld\r\n" output="hello\t\r\nworld\r\n"
enc=openpgp dec=gpg     OK  input="hello world" output="hello world"
enc=openpgp dec=gpg     OK  input="\n" output="\n"
enc=openpgp dec=gpg     OK  input="\t" output="\t"
enc=openpgp dec=gpg     ERR input="\r" output=""
enc=openpgp dec=gpg     ERR input="hello\t\r\nworld\r\n" output="hello\t\nworld\n"
enc=openpgp dec=openpgp OK  input="hello world" output="hello world"
enc=openpgp dec=openpgp OK  input="\n" output="\n"
enc=openpgp dec=openpgp OK  input="\t" output="\t"
enc=openpgp dec=openpgp OK  input="\r" output="\r"
enc=openpgp dec=openpgp OK  input="hello\t\r\nworld\r\n" output="hello\t\r\nworld\r\n"
package main
import (
"bufio"
"bytes"
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/url"
"os"
"os/exec"
"strings"
"golang.org/x/crypto/openpgp"
)
var l = log.New(os.Stderr, "", 0)
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
ring, err := loadSecring()
if err != nil {
return err
}
combos := []struct {
Name string
Enc func(string, openpgp.EntityList) (string, error)
Dec func(string) (string, error)
}{
{Name: "enc=gpg dec=gpg ", Enc: encryptGPG, Dec: decryptGPG},
{Name: "enc=gpg dec=openpgp ", Enc: encryptGPG, Dec: decryptOpenPGP},
{Name: "enc=openpgp dec=gpg ", Enc: encryptOpenPGP, Dec: decryptGPG},
{Name: "enc=openpgp dec=openpgp", Enc: encryptOpenPGP, Dec: decryptOpenPGP},
}
for _, c := range combos {
for _, pt := range []string{"hello world", "\n", "\t", "\r", "hello\t\r\nworld\r\n"} {
enc, err := c.Enc(pt, ring)
if err != nil {
return err
}
dec, err := c.Dec(enc)
if err != nil {
return err
}
status := "OK "
if pt != dec {
status = "ERR"
}
l.Printf("%s %s input=%q output=%q", c.Name, status, pt, dec)
}
}
return nil
}
func decryptGPG(in string) (string, error) {
c := exec.Command("gpg", "--no-tty", "-q")
stderr := &bytes.Buffer{}
stdout := &bytes.Buffer{}
c.Stdin = strings.NewReader(in)
c.Stdout = stdout
c.Stderr = io.MultiWriter(stderr, os.Stdout)
if err := c.Run(); err != nil {
return "", fmt.Errorf("%s: %s", err, stderr.String())
}
return stdout.String(), nil
}
func md5sum(in string) string {
cs := md5.New()
_, _ = io.WriteString(cs, in)
return hex.EncodeToString(cs.Sum(nil))
}
func encryptGPG(in string, list openpgp.EntityList) (string, error) {
args := []string{"-e"}
for _, r := range list {
if r.PrimaryKey == nil {
continue
}
args = append(args, "-r", r.PrimaryKey.KeyIdShortString())
}
stderr := &bytes.Buffer{}
stdout := &bytes.Buffer{}
c := exec.Command("gpg", args...)
c.Stdin = strings.NewReader(in)
c.Stdout = stdout
c.Stderr = io.MultiWriter(stderr, os.Stderr)
if err := c.Run(); err != nil {
return "", err
}
return stdout.String(), nil
}
func encryptOpenPGP(input string, list openpgp.EntityList) (string, error) {
buf := &bytes.Buffer{}
wc, err := openpgp.Encrypt(buf, list, nil, &openpgp.FileHints{IsBinary: true}, nil)
if err != nil {
return "", err
}
_, err = io.WriteString(wc, input)
if err != nil {
return "", err
}
if err := wc.Close(); err != nil {
return "", err
}
return buf.String(), nil
}
func loadSecring() (openpgp.EntityList, error) {
f, err := os.Open(os.ExpandEnv("$HOME/.gnupg/secring.gpg"))
if err != nil {
return nil, err
}
defer func() {
if err := f.Close(); err != nil {
l.Printf("err=%q", err)
}
}()
return openpgp.ReadKeyRing(f)
}
func decryptOpenPGP(in string) (string, error) {
con, err := NewGpgAgentConn()
if err != nil {
return "", err
}
defer con.Close()
ring, err := loadSecring()
if err != nil {
return "", err
}
m, err := openpgp.ReadMessage(strings.NewReader(in), ring, Prompt(con), nil)
if err != nil {
return "", err
}
b, err := ioutil.ReadAll(m.UnverifiedBody)
if err != nil && err != io.EOF {
return "", err
}
return string(b), nil
}
// GPG Agent
func Prompt(con *Conn) func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
return func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
for _, k := range keys {
ids := []string{}
for i := range k.Entity.Identities {
ids = append(ids, i)
}
prompt := "passphrase for gpg key " + k.PrivateKey.KeyIdShortString()
cacheKey := k.PrivateKey.KeyIdString()
preq := &PassphraseRequest{CacheKey: cacheKey, Prompt: prompt, Desc: strings.Join(ids, ", ")}
pp, err := con.GetPassphrase(preq)
if err != nil {
continue
}
if err := k.PrivateKey.Decrypt([]byte(pp)); err == nil {
return nil, nil
} else {
con.RemoveFromCache(cacheKey)
}
}
return nil, fmt.Errorf("unable to decrypt message. tried %d keys", len(keys))
}
}
// Conn is a connection to the GPG agent.
type Conn struct {
c io.ReadWriteCloser
br *bufio.Reader
}
var (
ErrNoAgent = errors.New("GPG_AGENT_INFO not set in environment")
ErrNoData = errors.New("GPG_ERR_NO_DATA cache miss")
ErrCancel = errors.New("gpgagent: Cancel")
)
// NewGpgAgentConn connects to the GPG Agent as described in the
// GPG_AGENT_INFO environment variable.
func NewGpgAgentConn() (*Conn, error) {
sp := strings.SplitN(os.Getenv("GPG_AGENT_INFO"), ":", 3)
if len(sp) == 0 || len(sp[0]) == 0 {
return nil, ErrNoAgent
}
addr := &net.UnixAddr{Net: "unix", Name: sp[0]}
uc, err := net.DialUnix("unix", nil, addr)
if err != nil {
return nil, err
}
br := bufio.NewReader(uc)
lineb, err := br.ReadSlice('\n')
if err != nil {
return nil, err
}
line := string(lineb)
if !strings.HasPrefix(line, "OK") {
return nil, fmt.Errorf("gpgagent: didn't get OK; got %q", line)
}
return &Conn{uc, br}, nil
}
func (c *Conn) Close() error {
c.br = nil
return c.c.Close()
}
// PassphraseRequest is a request to get a passphrase from the GPG
// Agent.
type PassphraseRequest struct {
CacheKey, Error, Prompt, Desc string
// If the option --no-ask is used and the passphrase is not in
// the cache the user will not be asked to enter a passphrase
// but the error code GPG_ERR_NO_DATA is returned. (ErrNoData)
NoAsk bool
}
func (c *Conn) RemoveFromCache(cacheKey string) error {
_, err := fmt.Fprintf(c.c, "CLEAR_PASSPHRASE %s\n", url.QueryEscape(cacheKey))
if err != nil {
return err
}
lineb, err := c.br.ReadSlice('\n')
if err != nil {
return err
}
line := string(lineb)
if !strings.HasPrefix(line, "OK") {
return fmt.Errorf("gpgagent: CLEAR_PASSPHRASE returned %q", line)
}
return nil
}
func (c *Conn) GetPassphrase(pr *PassphraseRequest) (passphrase string, outerr error) {
defer func() {
if e, ok := recover().(string); ok {
passphrase = ""
outerr = errors.New(e)
}
}()
set := func(cmd string, val string) {
if val == "" {
return
}
_, err := fmt.Fprintf(c.c, "%s %s\n", cmd, val)
if err != nil {
panic("gpgagent: failed to send " + cmd)
}
line, _, err := c.br.ReadLine()
if err != nil {
panic("gpgagent: failed to read " + cmd)
}
if !strings.HasPrefix(string(line), "OK") {
panic("gpgagent: response to " + cmd + " was " + string(line))
}
}
if d := os.Getenv("DISPLAY"); d != "" {
set("OPTION", "display="+d)
}
tty, err := os.Readlink("/proc/self/fd/0")
if err == nil {
set("OPTION", "ttyname="+tty)
}
set("OPTION", "ttytype="+os.Getenv("TERM"))
opts := ""
if pr.NoAsk {
opts += "--no-ask "
}
encOrX := func(s string) string {
if s == "" {
return "X"
}
return url.QueryEscape(s)
}
_, err = fmt.Fprintf(c.c, "GET_PASSPHRASE %s%s %s %s %s\n",
opts,
url.QueryEscape(pr.CacheKey),
encOrX(pr.Error),
encOrX(pr.Prompt),
encOrX(pr.Desc))
if err != nil {
return "", err
}
lineb, err := c.br.ReadSlice('\n')
if err != nil {
return "", err
}
line := string(lineb)
if strings.HasPrefix(line, "OK ") {
decb, err := hex.DecodeString(line[3 : len(line)-1])
if err != nil {
return "", err
}
return string(decb), nil
}
fields := strings.Split(line, " ")
if len(fields) >= 2 && fields[0] == "ERR" {
switch fields[1] {
case "67108922":
return "", ErrNoData
case "83886179":
return "", ErrCancel
}
}
return "", errors.New(line)
}
default:
go run main.go
@rsc
Copy link

rsc commented Jan 5, 2016

Can you update the output to match the current program?
The program shows two decodings for each input but the output only shows one.
Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment