keyproofs/pkg/opgp/util.go

139 lines
3.1 KiB
Go
Raw Normal View History

package opgp
2020-11-23 13:58:19 -07:00
import (
"bytes"
"context"
"crypto/sha1"
"fmt"
"io"
"net/http"
"net/mail"
"net/url"
"strings"
"github.com/rs/zerolog/log"
"github.com/sour-is/crypto/openpgp"
"github.com/sour-is/keyproofs/pkg/opgp/entity"
2020-11-23 13:58:19 -07:00
"github.com/tv42/zbase32"
"golang.org/x/crypto/openpgp/armor"
)
func GetKey(ctx context.Context, id string) (entity *entity.Entity, err error) {
2020-11-23 13:58:19 -07:00
if isFingerprint(id) {
addr := "https://keys.openpgp.org/vks/v1/by-fingerprint/" + strings.ToUpper(id)
return getEntityHTTP(ctx, addr, true)
} else if email, err := mail.ParseAddress(id); err == nil {
addr, advAddr := getWKDPubKeyAddr(email)
2020-11-23 13:58:19 -07:00
req, err := getEntityHTTP(ctx, addr, false)
if err == nil {
return req, err
}
req, err = getEntityHTTP(ctx, advAddr, false)
if err == nil {
return req, err
}
2020-11-23 13:58:19 -07:00
addr = "https://keys.openpgp.org/vks/v1/by-email/" + url.QueryEscape(id)
return getEntityHTTP(ctx, addr, true)
} else {
return entity, fmt.Errorf("Parse address: %w", err)
}
}
func getEntityHTTP(ctx context.Context, url string, useArmored bool) (entity *entity.Entity, err error) {
2020-11-23 13:58:19 -07:00
log := log.Ctx(ctx)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return entity, err
}
cl := http.Client{}
resp, err := cl.Do(req)
if err != nil {
return entity, fmt.Errorf("Requesting key: %w\nRemote URL: %v", err, url)
}
2020-11-23 13:58:19 -07:00
log.Debug().
Bool("useArmored", useArmored).
Str("status", resp.Status).
Str("url", url).
Msg("getEntityHTTP")
if resp.StatusCode != 200 {
return entity, fmt.Errorf("bad response from remote: %s\nRemote URL: %v", resp.Status, url)
}
defer resp.Body.Close()
if resp.Header.Get("Content-Type") == "application/pgp-keys" {
useArmored = true
}
return ReadKey(resp.Body, useArmored)
}
func ReadKey(r io.Reader, useArmored bool) (e *entity.Entity, err error) {
2020-11-23 13:58:19 -07:00
var buf bytes.Buffer
var w io.Writer = &buf
e = &entity.Entity{}
2020-11-23 13:58:19 -07:00
2020-12-03 12:32:24 -07:00
defer func() {
if e != nil {
e.ArmorText = buf.String()
}
}()
2020-12-02 09:24:07 -07:00
2020-11-23 13:58:19 -07:00
if !useArmored {
var aw io.WriteCloser
aw, err = armor.Encode(&buf, "PGP PUBLIC KEY BLOCK", nil)
if err != nil {
return e, fmt.Errorf("Read key: %w", err)
}
2020-12-03 12:32:24 -07:00
defer aw.Close()
2020-11-23 13:58:19 -07:00
w = aw
}
r = io.TeeReader(r, w)
var lis openpgp.EntityList
if useArmored {
lis, err = openpgp.ReadArmoredKeyRing(r)
} else {
lis, err = openpgp.ReadKeyRing(r)
}
if err != nil {
return e, fmt.Errorf("Read key: %w", err)
}
e, err = entity.GetOne(lis)
2020-11-23 13:58:19 -07:00
if err != nil {
return e, fmt.Errorf("Parse key: %w", err)
}
return
}
func isFingerprint(s string) bool {
for _, r := range s {
switch r {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F':
default:
return false
}
}
return true
}
func getWKDPubKeyAddr(email *mail.Address) (string, string) {
2020-11-23 13:58:19 -07:00
parts := strings.SplitN(email.Address, "@", 2)
hash := sha1.Sum([]byte(parts[0]))
lp := zbase32.EncodeToString(hash[:])
return fmt.Sprintf("https://%s/.well-known/openpgpkey/hu/%s", parts[1], lp),
fmt.Sprintf("https://openpgpkey.%s/.well-known/openpgpkey/hu/%s/%s", parts[1], parts[1], lp)
2020-11-23 13:58:19 -07:00
}