Commit a8d46fc1 authored by ale's avatar ale
Browse files

Merge branch 'openpgp' into 'master'

Add support for OpenPGP keys attached to email resources

See merge request !46
parents 23c16ba8 48b9ace8
Pipeline #30467 passed with stages
in 3 minutes and 59 seconds
package accountserver
import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"log"
"strings"
"time"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/tv42/zbase32"
)
// GetResourceRequest requests a specific resource.
......@@ -373,3 +381,141 @@ func (r *DeleteEmailAliasRequest) Serve(rctx *RequestContext) (interface{}, erro
rctx.audit.Log(rctx, rctx.Resource, fmt.Sprintf("removed alias %s", r.Addr))
return nil, nil
}
type SetOpenPGPKeyRequest struct {
ResourceRequestBase
// Set to empty value to delete key.
OpenPGPKey []byte `json:"openpgp_key"`
key *crypto.Key
}
var pgpArmor = []byte("-----BEGIN PGP PUBLIC KEY BLOCK-----")
func splitAddress(addr string) (local, domain string, err error) {
parts := strings.Split(addr, "@")
if len(parts) != 2 {
return "", "", errors.New("wkd: invalid email address")
}
return parts[0], parts[1], nil
}
func hashLocal(local string) string {
local = strings.ToLower(local)
hashedLocal := sha1.Sum([]byte(local))
return zbase32.EncodeToString(hashedLocal[:])
}
// wkdHashAddress returns the WKD hash for the local part of a given email address.
func wkdHashAddress(addr string) (string, error) {
local, _, err := splitAddress(addr)
if err != nil {
return "", err
}
return hashLocal(local), nil
}
func parseOpenPGPKey(data []byte, email string) (key *crypto.Key, err error) {
// Accept either armored or raw inputs.
if bytes.HasPrefix(data, pgpArmor) {
key, err = crypto.NewKeyFromArmored(string(data))
} else {
key, err = crypto.NewKey(data)
}
if err != nil {
return
}
// Detect uploads of private keys!
if key.IsPrivate() {
err = errors.New("input is a private key")
return
}
// Verify that the key matches the user identity.
entity := key.GetEntity()
var found bool
for _, identity := range entity.Identities {
if identity.UserId.Email == email {
found = true
break
}
}
if !found {
err = errors.New("key identity does not match user")
}
return
}
func getPGPKeyExpiry(key *crypto.Key) int64 {
ent := key.GetEntity()
i := ent.PrimaryIdentity()
if i.SelfSignature.KeyLifetimeSecs != nil && *i.SelfSignature.KeyLifetimeSecs > 0 {
return ent.PrimaryKey.CreationTime.Add(
time.Duration(*i.SelfSignature.KeyLifetimeSecs) * time.Second).Unix()
}
// Key does not expire.
return 0
}
func newOpenPGPKey(email string, key *crypto.Key) (*OpenPGPKey, error) {
data, err := key.GetPublicKey()
if err != nil {
return nil, err
}
wkdHash, err := wkdHashAddress(email)
if err != nil {
return nil, err
}
return &OpenPGPKey{
Key: data,
ID: key.GetHexKeyID(),
Hash: wkdHash,
Expiry: getPGPKeyExpiry(key),
}, nil
}
func (r *SetOpenPGPKeyRequest) Validate(rctx *RequestContext) error {
if rctx.Resource.Type != ResourceTypeEmail {
return newValidationError(nil, "type", "this operation only works on email resources")
}
// If a key is present, validate it.
if len(r.OpenPGPKey) > 0 {
key, err := parseOpenPGPKey(r.OpenPGPKey, rctx.Resource.Name)
if err != nil {
return newValidationError(nil, "openpgp_key", err.Error())
}
r.key = key
}
return nil
}
func (r *SetOpenPGPKeyRequest) Serve(rctx *RequestContext) (interface{}, error) {
var auditMsg string
rsrc := rctx.Resource
if r.key == nil {
rsrc.Email.OpenPGPKey = nil
auditMsg = "deleted GPG key"
} else {
pgpKey, err := newOpenPGPKey(rsrc.Name, r.key)
if err != nil {
return nil, err
}
rsrc.Email.OpenPGPKey = pgpKey
auditMsg = fmt.Sprintf("updated GPG key %s", pgpKey.ID)
}
if err := rctx.TX.UpdateResource(rctx.Context, rsrc); err != nil {
return nil, err
}
rctx.audit.Log(rctx, rctx.Resource, auditMsg)
return nil, nil
}
......@@ -149,24 +149,51 @@ func (h *emailResourceHandler) FromLDAP(entry *ldap.Entry) (*as.Resource, error)
email := entry.GetAttributeValue("mail")
// Also we don't want []byte("") for the PGP key, but a nil slice.
var openPGPKey *as.OpenPGPKey
if s := entry.GetAttributeValue("openPGPKey"); s != "" {
openPGPKey = &as.OpenPGPKey{
Key: []byte(s),
ID: entry.GetAttributeValue("openPGPKeyId"),
Hash: entry.GetAttributeValue("openPGPKeyHash"),
}
// We're ok with openPgpKeyExpiry defaulting to zero.
// nolint: errcheck
openPGPKey.Expiry, _ = strconv.ParseInt(entry.GetAttributeValue("openPGPKeyExpiry"), 10, 64)
}
return &as.Resource{
ID: as.ResourceID(entry.DN),
Type: as.ResourceTypeEmail,
Name: email,
Email: &as.Email{
Aliases: entry.GetAttributeValues("mailAlternateAddress"),
Maildir: entry.GetAttributeValue("mailMessageStore"),
Aliases: entry.GetAttributeValues("mailAlternateAddress"),
Maildir: entry.GetAttributeValue("mailMessageStore"),
OpenPGPKey: openPGPKey,
},
}, nil
}
func (h *emailResourceHandler) ToLDAP(rsrc *as.Resource) []ldap.PartialAttribute {
return []ldap.PartialAttribute{
attrs := []ldap.PartialAttribute{
{Type: "objectClass", Vals: []string{"top", "virtualMailUser"}},
{Type: "mail", Vals: s2l(rsrc.Name)},
{Type: "mailAlternateAddress", Vals: rsrc.Email.Aliases},
{Type: "mailMessageStore", Vals: s2l(rsrc.Email.Maildir)},
}
if pgpKey := rsrc.Email.OpenPGPKey; pgpKey != nil {
attrs = append(attrs, ldap.PartialAttribute{Type: "openPGPKey", Vals: by2l(pgpKey.Key)})
attrs = append(attrs, ldap.PartialAttribute{Type: "openPGPKeyId", Vals: s2l(pgpKey.ID)})
attrs = append(attrs, ldap.PartialAttribute{Type: "openPGPKeyHash", Vals: s2l(pgpKey.Hash)})
attrs = append(attrs, ldap.PartialAttribute{Type: "openPGPKeyExpiry", Vals: []string{strconv.FormatInt(pgpKey.Expiry, 10)}})
} else {
// Empty attrs.
for _, t := range []string{"openPGPKey", "openPGPKeyId", "openPGPKeyHash", "openPGPKeyExpiry"} {
attrs = append(attrs, ldap.PartialAttribute{Type: t})
}
}
return attrs
}
func (h *emailResourceHandler) SearchQuery() *queryTemplate {
......
......@@ -88,6 +88,15 @@ func s2l(s string) []string {
return []string{s}
}
// Convert a []byte to a []string with a single item, or nil if the
// slice is empty.
func by2l(b []byte) []string {
if len(b) == 0 {
return nil
}
return []string{string(b)}
}
// Returns true if a LDAP object has the specified objectClass.
func isObjectClass(entry *ldap.Entry, class string) bool {
classes := entry.GetAttributeValues("objectClass")
......
......@@ -8,6 +8,7 @@ require (
git.autistici.org/id/auth v0.0.0-20211206094959-05544c3c95bb
git.autistici.org/id/go-sso v0.0.0-20210117165919-e56e6579953d
git.autistici.org/id/usermetadb v0.0.0-20210507085300-ad16aa223703
github.com/ProtonMail/gopenpgp/v2 v2.4.5
github.com/go-ldap/ldap/v3 v3.4.1
github.com/go-test/deep v1.0.7
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
......@@ -15,6 +16,7 @@ require (
github.com/pquerna/otp v1.3.0
github.com/prometheus/client_golang v1.11.0
github.com/sethvargo/go-password v0.2.0
github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
......
......@@ -66,6 +66,12 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f h1:J2FzIrXN82q5uyUraeJpLIm7U6PffRwje2ORho5yIik=
github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/gopenpgp/v2 v2.4.5 h1:G7fOIAEcdwRUreFBUNVrZqiJPZQe6nn6V/5aNr6ZfYw=
github.com/ProtonMail/gopenpgp/v2 v2.4.5/go.mod h1:0byYFEOo6x4F/1YqhN7Z6m015Cqnxllz3CGb5cjJueY=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
......@@ -507,6 +513,7 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
......@@ -577,6 +584,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
......@@ -608,6 +616,8 @@ github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1C
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ=
github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo=
github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa h1:2EwhXkNkeMjX9iFYGWLPQLPhw9O58BhnYgtYKeqybcY=
github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa/go.mod h1:is48sjgBanWcA5CQrPBu9Y5yABY/T2awj/zI65bq704=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
......@@ -662,6 +672,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8=
......@@ -669,6 +680,7 @@ golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
......@@ -690,10 +702,12 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
......@@ -869,6 +883,7 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
......
package integrationtest
import (
"bytes"
"testing"
as "git.autistici.org/ai3/accountserver"
"github.com/ProtonMail/gopenpgp/v2/crypto"
)
func TestIntegration_SetNewPGPKey(t *testing.T) {
stop, _, c := startService(t)
defer stop()
rsrcID := as.ResourceID("mail=uno@investici.org,uid=uno@investici.org,ou=People,dc=example,dc=com")
testKey, err := crypto.GenerateKey("uno", "uno@investici.org", "rsa", 1024)
if err != nil {
t.Fatal(err)
}
testKeyData, _ := testKey.GetPublicKey()
// First, set the key to something new.
err = c.request("/api/resource/email/set_openpgp_key", &as.SetOpenPGPKeyRequest{
ResourceRequestBase: as.ResourceRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket("uno@investici.org"),
},
ResourceID: rsrcID,
},
OpenPGPKey: testKeyData,
}, nil)
if err != nil {
t.Fatal(err)
}
// Verify that the key is set by calling GetUser.
var user as.User
err = c.request("/api/user/get", &as.GetUserRequest{
UserRequestBase: as.UserRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket("uno@investici.org"),
},
Username: "uno@investici.org",
},
}, &user)
if err != nil {
t.Fatal(err)
}
email := user.GetResourcesByType(as.ResourceTypeEmail)[0]
if email.Email.OpenPGPKey == nil {
t.Fatal("email.OpenPGPKey is nil")
}
if !bytes.Equal(email.Email.OpenPGPKey.Key, testKeyData) {
t.Fatal("email.OpenPGPKey.Key does not match expected data")
}
if email.Email.OpenPGPKey.ID != testKey.GetHexKeyID() {
t.Fatalf("email.OpenPGPKey.ID mismatch: %s, expected %s", email.Email.OpenPGPKey.ID, testKey.GetHexKeyID())
}
if email.Email.OpenPGPKey.Hash != "og5xkb3w4f6n39ysbu6yx5btzg6pfke6" {
t.Fatalf("email.OpenPGPKey.Hash unexpected: %s", email.Email.OpenPGPKey.Hash)
}
// Now unset the key.
err = c.request("/api/resource/email/set_openpgp_key", &as.SetOpenPGPKeyRequest{
ResourceRequestBase: as.ResourceRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket("uno@investici.org"),
},
ResourceID: rsrcID,
},
}, nil)
if err != nil {
t.Fatal(err)
}
// Fetch the user again and see that it has no OpenPGPKey entry.
err = c.request("/api/user/get", &as.GetUserRequest{
UserRequestBase: as.UserRequestBase{
RequestBase: as.RequestBase{
SSO: c.ssoTicket("uno@investici.org"),
},
Username: "uno@investici.org",
},
}, &user)
if err != nil {
t.Fatal(err)
}
email = user.GetResourcesByType(as.ResourceTypeEmail)[0]
if email.Email.OpenPGPKey != nil {
t.Fatal("email.OpenPGPKey is still set after being deleted")
}
}
......@@ -146,6 +146,7 @@ var (
{"/api/resource/reset_password", &as.ResetResourcePasswordRequest{}},
{"/api/resource/email/add_alias", &as.AddEmailAliasRequest{}},
{"/api/resource/email/delete_alias", &as.DeleteEmailAliasRequest{}},
{"/api/resource/email/set_openpgp_key", &as.SetOpenPGPKeyRequest{}},
{"/api/recover_account", &as.AccountRecoveryRequest{}},
}
)
......
......@@ -520,11 +520,22 @@ type RawResource struct {
Owner string `json:"owner"`
}
type OpenPGPKey struct {
// Key ID is hex-encoded (to avoid JS int64 misrepresentation as float).
ID string `json:"key_id"`
Hash string `json:"wkd_hash"`
Expiry int64 `json:"expiry"`
// Key data being a []byte will force base64-encoding when serializing to JSON.
Key []byte `json:"key"`
}
// Email resource attributes.
type Email struct {
Aliases []string `json:"aliases,omitempty"`
Maildir string `json:"maildir"`
QuotaLimit int `json:"quota_limit"`
Aliases []string `json:"aliases,omitempty"`
Maildir string `json:"maildir"`
QuotaLimit int `json:"quota_limit"`
OpenPGPKey *OpenPGPKey `json:"openpgp"`
}
// MailingList resource attributes.
......
# This source code refers to The Go Authors for copyright purposes.
# The master list of authors is in the main Go distribution,
# visible at https://tip.golang.org/AUTHORS.
# This source code was written by the Go contributors.
# The master list of contributors is in the main Go distribution,
# visible at https://tip.golang.org/CONTRIBUTORS.
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.
package bitcurves
// Copyright 2010 The Go Authors. All rights reserved.
// Copyright 2011 ThePiachu. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package bitelliptic implements several Koblitz elliptic curves over prime
// fields.
// This package operates, internally, on Jacobian coordinates. For a given
// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, z1)
// where x = x1/z1² and y = y1/z1³. The greatest speedups come when the whole
// calculation can be performed within the transform (as in ScalarMult and
// ScalarBaseMult). But even for Add and Double, it's faster to apply and
// reverse the transform than to operate in affine coordinates.
import (
"crypto/elliptic"
"io"
"math/big"
"sync"
)
// A BitCurve represents a Koblitz Curve with a=0.
// See http://www.hyperelliptic.org/EFD/g1p/auto-shortw.html
type BitCurve struct {
Name string
P *big.Int // the order of the underlying field
N *big.Int // the order of the base point
B *big.Int // the constant of the BitCurve equation
Gx, Gy *big.Int // (x,y) of the base point
BitSize int // the size of the underlying field
}
// Params returns the parameters of the given BitCurve (see BitCurve struct)
func (bitCurve *BitCurve) Params() (cp *elliptic.CurveParams) {
cp = new(elliptic.CurveParams)
cp.Name = bitCurve.Name
cp.P = bitCurve.P
cp.N = bitCurve.N
cp.Gx = bitCurve.Gx
cp.Gy = bitCurve.Gy
cp.BitSize = bitCurve.BitSize
return cp
}
// IsOnCurve returns true if the given (x,y) lies on the BitCurve.
func (bitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
// y² = x³ + b
y2 := new(big.Int).Mul(y, y) //y²
y2.Mod(y2, bitCurve.P) //y²%P
x3 := new(big.Int).Mul(x, x) //x²
x3.Mul(x3, x) //x³
x3.Add(x3, bitCurve.B) //x³+B
x3.Mod(x3, bitCurve.P) //(x³+B)%P
return x3.Cmp(y2) == 0
}
// affineFromJacobian reverses the Jacobian transform. See the comment at the
// top of the file.
func (bitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
if z.Cmp(big.NewInt(0)) == 0 {
panic("bitcurve: Can't convert to affine with Jacobian Z = 0")
}
// x = YZ^2 mod P
zinv := new(big.Int).ModInverse(z, bitCurve.P)
zinvsq := new(big.Int).Mul(zinv, zinv)
xOut = new(big.Int).Mul(x, zinvsq)
xOut.Mod(xOut, bitCurve.P)
// y = YZ^3 mod P
zinvsq.Mul(zinvsq, zinv)
yOut = new(big.Int).Mul(y, zinvsq)
yOut.Mod(yOut, bitCurve.P)
return xOut, yOut
}
// Add returns the sum of (x1,y1) and (x2,y2)
func (bitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
z := new(big.Int).SetInt64(1)
x, y, z := bitCurve.addJacobian(x1, y1, z, x2, y2, z)
return bitCurve.affineFromJacobian(x, y, z)
}