Refactor how commit authorship is formatted

---
type: change
message: |-
  Refactor how commit authorship is formatted

  This ended up being a loose thread that, when pulled, untangled a bunch of other
  stuff. Notably the account argument to Repo.Commit is no longer needed, and I
  added an anon argument to TestSignifierPGP which simplified a number of tests.
change_hash: AHMeFpSJb/AoLiELW5pImUiQ+PS0PWibliqcQuFTjC3o
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl6TVu8ACgkQlcRvpqQRSKwExw/+OQa++h0ZkFuguU3593D+9P7l8bElMWu+Je40RrDHz4fD0i51UJGcnkk/9fryUWGmWpbCnDv9k7OGztipH/P2HDhuBn+/Dc7X8CcRsRC7/n+o1gd6f1Sc9q4zTgPKkE+ZrE226e8GNVgpZRspbZHa1IwSs/fIkboavnWFowV6SiLColtwYqCfCJXvEP52D+OjKvi4iatRUzoVIOYNHGI4uOufuQRYPZIQRGxgcvUUB/VhjyBB39BV5cHO8oTFmmXH6+eFj4bHWjHsRzp5ferUmsRCdvo2lkoxXkeqN0okyUcwpXQXI7l6BL9OyCxHifIK9G2BaOAsp7A6piwNzaUGk1RIHZpJ69dTfTre1jolOhkGY9lXGAMdSo+ifsFqKj3sXZNjSEJ49riYP98ERnhF1APHN+xL1dkUd8eTTMRh9+C8Bi7twWkUJ2wH5CL1brkpkHIwXOa7jszdeliMK9aZRT7lyxvjCx0uVFTeXbq0RSRb9Oeo+TJhRIu7kLpMKmzX9y/fRaGiPcjr8OD2cfWhACsaVGuU+oXmJXk4uJ+ADfm4IZy7IOEQdr+3Cg33y2mxRq2APwLaGjvA6UFLfar1/nAKK+uTQDF3DssHdLgEfsH2Lu5orc3+FtAblhBiwrN5a732hLceEMkUvQXwOHbZqddkbuqQ6FqHMDIsvBdK6gI=
  account: mediocregopher
This commit is contained in:
mediocregopher 2020-04-12 11:59:23 -06:00
parent 43b564e711
commit 3d89fe5fd9
12 changed files with 128 additions and 86 deletions

View File

@ -61,7 +61,7 @@ func cmdCommit(ctx context.Context, cmd *dcmd.Cmd) {
return fmt.Errorf("accrediting commit: %w", err) return fmt.Errorf("accrediting commit: %w", err)
} }
gitCommit, err := repo.Commit(commit, *accountID) gitCommit, err := repo.Commit(commit)
if err != nil { if err != nil {
return fmt.Errorf("committing to git: %w", err) return fmt.Errorf("committing to git: %w", err)
} }

View File

@ -48,14 +48,18 @@ type CommitCommon struct {
Credentials []sigcred.Credential `yaml:"credentials"` Credentials []sigcred.Credential `yaml:"credentials"`
} }
func (cc CommitCommon) credAccountIDs() []string { func (cc CommitCommon) credIDs() []string {
m := map[string]struct{}{} m := map[string]struct{}{}
for _, cred := range cc.Credentials { for _, cred := range cc.Credentials {
if cred.AccountID != "" {
m[cred.AccountID] = struct{}{} m[cred.AccountID] = struct{}{}
} else if cred.AnonID != "" {
m[cred.AnonID] = struct{}{}
}
} }
s := make([]string, 0, len(m)) s := make([]string, 0, len(m))
for accountID := range m { for id := range m {
s = append(s, accountID) s = append(s, id)
} }
sort.Strings(s) sort.Strings(s)
return s return s
@ -222,10 +226,9 @@ func (r *Repo) CommitBare(params CommitBareParams) (GitCommit, error) {
return r.GetGitCommit(commitHash) return r.GetGitCommit(commitHash)
} }
// Commit uses the given Commit to create a git commit object (with the // Commit uses the given Commit to create a git commit object and commits it to
// specified accountID as the author) and commits it to the current HEAD, // the current HEAD, returning the full GitCommit.
// returning the full GitCommit. func (r *Repo) Commit(commit Commit) (GitCommit, error) {
func (r *Repo) Commit(commit Commit, accountID string) (GitCommit, error) {
headRef, err := r.TraverseReferenceChain(plumbing.HEAD, func(ref *plumbing.Reference) bool { headRef, err := r.TraverseReferenceChain(plumbing.HEAD, func(ref *plumbing.Reference) bool {
return ref.Type() == plumbing.HashReference return ref.Type() == plumbing.HashReference
}) })
@ -248,7 +251,7 @@ func (r *Repo) Commit(commit Commit, accountID string) (GitCommit, error) {
gitCommit, err := r.CommitBare(CommitBareParams{ gitCommit, err := r.CommitBare(CommitBareParams{
Commit: commit, Commit: commit,
Author: accountID, Author: strings.Join(commit.Common.credIDs(), ", "),
ParentHash: headHash, ParentHash: headHash,
GitTree: stagedTree, GitTree: stagedTree,
}) })

View File

@ -78,9 +78,8 @@ func TestChangeCommitVerify(t *testing.T) {
h := newHarness(t) h := newHarness(t)
for _, step := range test.steps { for _, step := range test.steps {
h.stage(step.tree) h.stage(step.tree)
account := h.cfg.Accounts[0]
gitCommit := h.changeCommit(step.msg, account.ID, h.sig) gitCommit := h.changeCommit(step.msg, h.sig)
if step.msgHead == "" { if step.msgHead == "" {
step.msgHead = strings.TrimSpace(step.msg) + "\n\n" step.msgHead = strings.TrimSpace(step.msg) + "\n\n"
} }
@ -106,11 +105,11 @@ func TestCombineCommitChanges(t *testing.T) {
h := newHarness(t) h := newHarness(t)
// commit initial config, so the root user can modify it in the next commit // commit initial config, so the root user can modify it in the next commit
h.changeCommit("initial commit", h.cfg.Accounts[0].ID, h.sig) h.changeCommit("initial commit", h.sig)
// add a toot user and modify the access controls such that both accounts // add a toot user and modify the access controls such that both accounts
// are required for the main branch // are required for the main branch
tootSig, tootPubKeyBody := sigcred.TestSignifierPGP("toot", h.rand) tootSig, tootPubKeyBody := sigcred.TestSignifierPGP("toot", false, h.rand)
h.cfg.Accounts = append(h.cfg.Accounts, Account{ h.cfg.Accounts = append(h.cfg.Accounts, Account{
ID: "toot", ID: "toot",
Signifiers: []sigcred.Signifier{{PGPPublicKey: &sigcred.SignifierPGP{ Signifiers: []sigcred.Signifier{{PGPPublicKey: &sigcred.SignifierPGP{
@ -145,21 +144,21 @@ func TestCombineCommitChanges(t *testing.T) {
} }
h.stageCfg() h.stageCfg()
tootCommit := h.changeCommit("add toot", h.cfg.Accounts[0].ID, h.sig) tootCommit := h.changeCommit("add toot", h.sig)
// make a single change commit in another branch using root. Then add a // make a single change commit in another branch using root. Then add a
// credential using toot, and combine them onto main. // credential using toot, and combine them onto main.
otherBranch := plumbing.NewBranchReferenceName("other") otherBranch := plumbing.NewBranchReferenceName("other")
h.checkout(otherBranch) h.checkout(otherBranch)
h.stage(map[string]string{"foo": "bar"}) h.stage(map[string]string{"foo": "bar"})
fooCommit := h.changeCommit("add foo file", h.cfg.Accounts[0].ID, h.sig) fooCommit := h.changeCommit("add foo file", h.sig)
// now adding a credential commit from toot should work // now adding a credential commit from toot should work
credCommitObj, err := h.repo.NewCommitCredential(fooCommit.Interface.GetHash()) credCommitObj, err := h.repo.NewCommitCredential(fooCommit.Interface.GetHash())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
credCommit := h.tryCommit(true, credCommitObj, h.cfg.Accounts[1].ID, tootSig) credCommit := h.tryCommit(true, credCommitObj, tootSig)
allCommits, err := h.repo.GetGitCommitRange( allCommits, err := h.repo.GetGitCommitRange(
tootCommit.GitCommit.Hash, tootCommit.GitCommit.Hash,

View File

@ -1,10 +1,11 @@
package dehub package dehub
import ( import (
"dehub.dev/src/dehub.git/yamlutil"
"fmt" "fmt"
"strings" "strings"
"dehub.dev/src/dehub.git/yamlutil"
"gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/object"
) )
@ -31,8 +32,8 @@ func (r *Repo) NewCommitComment(msg string) (Commit, error) {
// MessageHead implements the method for the CommitInterface interface. // MessageHead implements the method for the CommitInterface interface.
func (cc CommitComment) MessageHead(common CommitCommon) (string, error) { func (cc CommitComment) MessageHead(common CommitCommon) (string, error) {
msgAbbrev := abbrevCommitMessage(cc.Message) msgAbbrev := abbrevCommitMessage(cc.Message)
credAccounts := strings.Join(common.credAccountIDs(), ", ") credIDs := strings.Join(common.credIDs(), ", ")
return fmt.Sprintf("Comment by %s: %s", credAccounts, msgAbbrev), nil return fmt.Sprintf("Comment by %s: %s", credIDs, msgAbbrev), nil
} }
// Hash implements the method for the CommitInterface. // Hash implements the method for the CommitInterface.

View File

@ -1,11 +1,12 @@
package dehub package dehub
import ( import (
"dehub.dev/src/dehub.git/yamlutil"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strings" "strings"
"dehub.dev/src/dehub.git/yamlutil"
"gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/object"
) )
@ -63,8 +64,8 @@ func (cc CommitCredential) MessageHead(common CommitCommon) (string, error) {
hash64 = hash64[:6] + "..." hash64 = hash64[:6] + "..."
} }
credAccounts := strings.Join(common.credAccountIDs(), ", ") credIDs := strings.Join(common.credIDs(), ", ")
return fmt.Sprintf("Credential of hash %s by %s", hash64, credAccounts), nil return fmt.Sprintf("Credential of hash %s by %s", hash64, credIDs), nil
} }
// Hash implements the method for the CommitInterface. // Hash implements the method for the CommitInterface.

View File

@ -1,9 +1,10 @@
package dehub package dehub
import ( import (
"dehub.dev/src/dehub.git/sigcred"
"testing" "testing"
"dehub.dev/src/dehub.git/sigcred"
"gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
@ -13,7 +14,7 @@ func TestCredentialCommitVerify(t *testing.T) {
// create a new account and modify the config so that that account is only // create a new account and modify the config so that that account is only
// allowed to add verifications to a single branch // allowed to add verifications to a single branch
tootSig, tootPubKeyBody := sigcred.TestSignifierPGP("toot", h.rand) tootSig, tootPubKeyBody := sigcred.TestSignifierPGP("toot", false, h.rand)
h.cfg.Accounts = append(h.cfg.Accounts, Account{ h.cfg.Accounts = append(h.cfg.Accounts, Account{
ID: "toot", ID: "toot",
Signifiers: []sigcred.Signifier{{PGPPublicKey: &sigcred.SignifierPGP{ Signifiers: []sigcred.Signifier{{PGPPublicKey: &sigcred.SignifierPGP{
@ -47,7 +48,7 @@ func TestCredentialCommitVerify(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
h.stageCfg() h.stageCfg()
rootGitCommit := h.changeCommit("initial commit", h.cfg.Accounts[0].ID, h.sig) rootGitCommit := h.changeCommit("initial commit", h.sig)
// toot user wants to create a credential commit for the root commit, for // toot user wants to create a credential commit for the root commit, for
// whatever reason. // whatever reason.
@ -57,9 +58,9 @@ func TestCredentialCommitVerify(t *testing.T) {
t.Fatalf("creating credential commit for hash %x: %v", rootChangeHash, err) t.Fatalf("creating credential commit for hash %x: %v", rootChangeHash, err)
} }
h.tryCommit(false, credCommit, "toot", tootSig) h.tryCommit(false, credCommit, tootSig)
// toot tries again in their own branch, and should be allowed. // toot tries again in their own branch, and should be allowed.
h.checkout(tootBranch) h.checkout(tootBranch)
h.tryCommit(true, credCommit, "toot", tootSig) h.tryCommit(true, credCommit, tootSig)
} }

View File

@ -16,12 +16,12 @@ func TestConfigChange(t *testing.T) {
// commit the initial staged changes, which merely include the config and // commit the initial staged changes, which merely include the config and
// public key // public key
gitCommit := h.changeCommit("commit configuration", h.cfg.Accounts[0].ID, h.sig) gitCommit := h.changeCommit("commit configuration", h.sig)
gitCommits = append(gitCommits, gitCommit) gitCommits = append(gitCommits, gitCommit)
// create a new account and add it to the configuration. That commit should // create a new account and add it to the configuration. That commit should
// not be verifiable, though // not be verifiable, though
newSig, newPubKeyBody := sigcred.TestSignifierPGP("toot", h.rand) newSig, newPubKeyBody := sigcred.TestSignifierPGP("toot", false, h.rand)
h.cfg.Accounts = append(h.cfg.Accounts, Account{ h.cfg.Accounts = append(h.cfg.Accounts, Account{
ID: "toot", ID: "toot",
Signifiers: []sigcred.Signifier{{PGPPublicKey: &sigcred.SignifierPGP{ Signifiers: []sigcred.Signifier{{PGPPublicKey: &sigcred.SignifierPGP{
@ -34,16 +34,16 @@ func TestConfigChange(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("creating CommitChange: %v", err) t.Fatalf("creating CommitChange: %v", err)
} }
h.tryCommit(false, badCommit, h.cfg.Accounts[1].ID, newSig) h.tryCommit(false, badCommit, newSig)
// now add with the root user, this should work. // now add with the root user, this should work.
h.stageCfg() h.stageCfg()
gitCommit = h.changeCommit("add toot user", h.cfg.Accounts[0].ID, h.sig) gitCommit = h.changeCommit("add toot user", h.sig)
gitCommits = append(gitCommits, gitCommit) gitCommits = append(gitCommits, gitCommit)
// _now_ the toot user should be able to do things. // _now_ the toot user should be able to do things.
h.stage(map[string]string{"foo/bar": "what a cool file"}) h.stage(map[string]string{"foo/bar": "what a cool file"})
gitCommit = h.changeCommit("add a cool file", h.cfg.Accounts[1].ID, newSig) gitCommit = h.changeCommit("add a cool file", newSig)
gitCommits = append(gitCommits, gitCommit) gitCommits = append(gitCommits, gitCommit)
if err := h.repo.VerifyCommits(MainRefName, gitCommits); err != nil { if err := h.repo.VerifyCommits(MainRefName, gitCommits); err != nil {
@ -63,13 +63,13 @@ func TestMainAncestryRequirement(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("creating CommitChange: %v", err) t.Fatalf("creating CommitChange: %v", err)
} }
h.tryCommit(false, badCommit, h.cfg.Accounts[0].ID, h.sig) h.tryCommit(false, badCommit, h.sig)
}) })
t.Run("new branch, single commit", func(t *testing.T) { t.Run("new branch, single commit", func(t *testing.T) {
h := newHarness(t) h := newHarness(t)
h.stageCfg() h.stageCfg()
h.changeCommit("add cfg", h.cfg.Accounts[0].ID, h.sig) h.changeCommit("add cfg", h.sig)
// set HEAD to this other branch which doesn't really exist // set HEAD to this other branch which doesn't really exist
ref := plumbing.NewSymbolicReference(plumbing.HEAD, otherBranch) ref := plumbing.NewSymbolicReference(plumbing.HEAD, otherBranch)
@ -82,13 +82,13 @@ func TestMainAncestryRequirement(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("creating CommitChange: %v", err) t.Fatalf("creating CommitChange: %v", err)
} }
h.tryCommit(false, badCommit, h.cfg.Accounts[0].ID, h.sig) h.tryCommit(false, badCommit, h.sig)
}) })
} }
func TestAnonymousCommits(t *testing.T) { func TestAnonymousCommits(t *testing.T) {
h := newHarness(t) h := newHarness(t)
anonSig, anonPubKeyBody := sigcred.TestSignifierPGP("", h.rand) anonSig, _ := sigcred.TestSignifierPGP("anon", true, h.rand)
h.cfg.AccessControls = []accessctl.AccessControl{{ h.cfg.AccessControls = []accessctl.AccessControl{{
Action: accessctl.ActionAllow, Action: accessctl.ActionAllow,
@ -97,15 +97,5 @@ func TestAnonymousCommits(t *testing.T) {
}, },
}} }}
h.stageCfg() h.stageCfg()
h.changeCommit("this will work", anonSig)
// manually accredit the commit this time
goodCommit, err := h.repo.NewCommitChange("this will work")
if err != nil {
t.Fatalf("creating CommitChange: %v", err)
} else if goodCommit, err = h.repo.AccreditCommit(goodCommit, anonSig); err != nil {
t.Fatalf("accreditting CommitChange: %v", err)
}
// There is, unfortunately, not a prettier way to do this
goodCommit.Common.Credentials[0].PGPSignature.PubKeyBody = string(anonPubKeyBody)
h.tryCommit(true, goodCommit, "", nil)
} }

View File

@ -26,7 +26,7 @@ type harness struct {
func newHarness(t *testing.T) *harness { func newHarness(t *testing.T) *harness {
rand := rand.New(rand.NewSource(0xb4eadb01)) rand := rand.New(rand.NewSource(0xb4eadb01))
sig, pubKeyBody := sigcred.TestSignifierPGP("root", rand) sig, pubKeyBody := sigcred.TestSignifierPGP("root", false, rand)
pubKeyPath := filepath.Join(DehubDir, "root.asc") pubKeyPath := filepath.Join(DehubDir, "root.asc")
cfg := &Config{ cfg := &Config{
@ -158,7 +158,7 @@ func (h *harness) reset(to plumbing.Hash, mode git.ResetMode) {
func (h *harness) tryCommit( func (h *harness) tryCommit(
shouldSucceed bool, shouldSucceed bool,
commit Commit, commit Commit,
accountID string, accountSig sigcred.SignifierInterface, accountSig sigcred.SignifierInterface,
) GitCommit { ) GitCommit {
if accountSig != nil { if accountSig != nil {
var err error var err error
@ -167,7 +167,7 @@ func (h *harness) tryCommit(
} }
} }
gitCommit, err := h.repo.Commit(commit, accountID) gitCommit, err := h.repo.Commit(commit)
if err != nil { if err != nil {
h.t.Fatalf("failed to commit ChangeCommit: %v", err) h.t.Fatalf("failed to commit ChangeCommit: %v", err)
} }
@ -197,14 +197,13 @@ func (h *harness) tryCommit(
func (h *harness) changeCommit( func (h *harness) changeCommit(
msg string, msg string,
accountID string,
sig sigcred.SignifierInterface, sig sigcred.SignifierInterface,
) GitCommit { ) GitCommit {
commit, err := h.repo.NewCommitChange(msg) commit, err := h.repo.NewCommitChange(msg)
if err != nil { if err != nil {
h.t.Fatalf("creating ChangeCommit: %v", err) h.t.Fatalf("creating ChangeCommit: %v", err)
} }
return h.tryCommit(true, commit, accountID, sig) return h.tryCommit(true, commit, sig)
} }
func TestHasStagedChanges(t *testing.T) { func TestHasStagedChanges(t *testing.T) {
@ -225,12 +224,12 @@ func TestHasStagedChanges(t *testing.T) {
harness.stage(map[string]string{"foo": "bar"}) harness.stage(map[string]string{"foo": "bar"})
assertHasStaged(true) assertHasStaged(true)
harness.changeCommit("first commit", "root", harness.sig) harness.changeCommit("first commit", harness.sig)
assertHasStaged(false) assertHasStaged(false)
harness.stage(map[string]string{"foo": ""}) // delete foo harness.stage(map[string]string{"foo": ""}) // delete foo
assertHasStaged(true) assertHasStaged(true)
harness.changeCommit("second commit", "root", harness.sig) harness.changeCommit("second commit", harness.sig)
assertHasStaged(false) assertHasStaged(false)
} }
@ -258,7 +257,7 @@ access_controls:
`}) `})
// this commit should be created and verify fine // this commit should be created and verify fine
harness.changeCommit("first commit, this is going great", "root", harness.sig) harness.changeCommit("first commit, this is going great", harness.sig)
// this commit should not be verifiable, because toot isn't in accounts and // this commit should not be verifiable, because toot isn't in accounts and
// the default access controls should be being used // the default access controls should be being used
@ -267,7 +266,7 @@ access_controls:
if err != nil { if err != nil {
t.Fatalf("creating CommitChange: %v", err) t.Fatalf("creating CommitChange: %v", err)
} }
harness.tryCommit(false, badCommit, "toot", nil) harness.tryCommit(false, badCommit, nil)
// make a commit fixing the config. everything should still be fine. // make a commit fixing the config. everything should still be fine.
harness.stage(map[string]string{ConfigPath: ` harness.stage(map[string]string{ConfigPath: `
@ -278,7 +277,7 @@ accounts:
- type: pgp_public_key_file - type: pgp_public_key_file
path: ".dehub/root.asc" path: ".dehub/root.asc"
`}) `})
harness.changeCommit("Fix the config!", "root", harness.sig) harness.changeCommit("Fix the config!", harness.sig)
} }
// TestThisRepoStillVerifies opens this actual repository and ensures that all // TestThisRepoStillVerifies opens this actual repository and ensures that all
@ -314,7 +313,7 @@ func TestShortHashResolving(t *testing.T) {
// TODO ideally this test would test the conflicting hashes are noticed, but // TODO ideally this test would test the conflicting hashes are noticed, but
// that's hard... // that's hard...
h := newHarness(t) h := newHarness(t)
hash := h.changeCommit("first commit", h.cfg.Accounts[0].ID, h.sig).GitCommit.Hash hash := h.changeCommit("first commit", h.sig).GitCommit.Hash
hashStr := hash.String() hashStr := hash.String()
t.Log(hashStr) t.Log(hashStr)

View File

@ -14,10 +14,16 @@ type Credential struct {
// AccountID specifies the account which generated this Credential. // AccountID specifies the account which generated this Credential.
// //
// NOTE that the Credentials produced by the Signifier.Sign method do not // NOTE that Credentials produced by the direct implementations of
// fill this field in, and it may be empty in cases where a non-account user // SignifierInterface won't fill in this field, unless specifically
// has added a credential to a commit. // documented. The SignifierInterface produced by the Interface() method of
// Signifier _will_ fill this field in, however.
AccountID string `yaml:"account,omitempty"` AccountID string `yaml:"account,omitempty"`
// AnonID specifies an identifier for the anonymous user which produced this
// credential. This field is mutually exclusive with AccountID, and won't be
// set by any SignifierInterface unless specifically documented.
AnonID string `yaml:"-"`
} }
// MarshalYAML implements the yaml.Marshaler interface. // MarshalYAML implements the yaml.Marshaler interface.

View File

@ -20,7 +20,7 @@ func TestSelfVerifyingCredentials(t *testing.T) {
{ {
descr: "pgp sig no body", descr: "pgp sig no body",
mkCred: func(toSign []byte) (Credential, error) { mkCred: func(toSign []byte) (Credential, error) {
privKey, _ := TestSignifierPGP("", rand) privKey, _ := TestSignifierPGP("", false, rand)
return privKey.Sign(nil, toSign) return privKey.Sign(nil, toSign)
}, },
expErr: true, expErr: true,
@ -28,10 +28,8 @@ func TestSelfVerifyingCredentials(t *testing.T) {
{ {
descr: "pgp sig with body", descr: "pgp sig with body",
mkCred: func(toSign []byte) (Credential, error) { mkCred: func(toSign []byte) (Credential, error) {
privKey, pubKeyBody := TestSignifierPGP("", rand) privKey, _ := TestSignifierPGP("", true, rand)
cred, err := privKey.Sign(nil, toSign) return privKey.Sign(nil, toSign)
cred.PGPSignature.PubKeyBody = string(pubKeyBody)
return cred, err
}, },
}, },
} }

View File

@ -122,22 +122,57 @@ func (s pgpKey) MarshalBinary() ([]byte, error) {
body := new(bytes.Buffer) body := new(bytes.Buffer)
armorEncoder, err := armor.Encode(body, "PGP PUBLIC KEY", nil) armorEncoder, err := armor.Encode(body, "PGP PUBLIC KEY", nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("error initializing armor encoder: %w", err) return nil, fmt.Errorf("initializing armor encoder: %w", err)
} else if err := s.entity.Serialize(armorEncoder); err != nil { } else if err := s.entity.Serialize(armorEncoder); err != nil {
return nil, fmt.Errorf("error encoding public key: %w", err) return nil, fmt.Errorf("encoding public key: %w", err)
} else if err := armorEncoder.Close(); err != nil { } else if err := armorEncoder.Close(); err != nil {
return nil, fmt.Errorf("error closing armor encoder: %w", err) return nil, fmt.Errorf("closing armor encoder: %w", err)
} }
return body.Bytes(), nil return body.Bytes(), nil
} }
func (s pgpKey) userID() (*packet.UserId, error) {
if l := len(s.entity.Identities); l == 0 {
return nil, errors.New("pgp key has no identity information")
} else if l > 1 {
return nil, errors.New("multiple identities on a single pgp key is unsupported")
}
var identity *openpgp.Identity
for _, identity = range s.entity.Identities {
break
}
return identity.UserId, nil
}
func anonPGPSignifier(pgpKey pgpKey, sigInt SignifierInterface) (SignifierInterface, error) {
keyID := pgpKey.entity.PrimaryKey.KeyIdString()
userID, err := pgpKey.userID()
if err != nil {
return nil, err
}
pubKeyBody, err := pgpKey.MarshalBinary()
if err != nil {
return nil, err
}
return signifierMiddleware{
SignifierInterface: sigInt,
signCallback: func(cred *Credential) {
cred.PGPSignature.PubKeyBody = string(pubKeyBody)
cred.AnonID = fmt.Sprintf("%s %q", keyID, userID.Email)
},
}, nil
}
// TestSignifierPGP returns a direct implementation of the SignifierInterface // TestSignifierPGP returns a direct implementation of the SignifierInterface
// which uses a random private key generated in memory, as well as an armored // which uses a random private key generated in memory, as well as an armored
// version of its public key. // version of its public key.
// //
// NOTE that the key returned is very weak, and should only be used for tests. // NOTE that the key returned is very weak, and should only be used for tests.
func TestSignifierPGP(accountID string, randReader io.Reader) (SignifierInterface, []byte) { func TestSignifierPGP(name string, anon bool, randReader io.Reader) (SignifierInterface, []byte) {
entity, err := openpgp.NewEntity(accountID, "", accountID+"@example.com", &packet.Config{ entity, err := openpgp.NewEntity(name, "", name+"@example.com", &packet.Config{
Rand: randReader, Rand: randReader,
RSABits: 512, RSABits: 512,
}) })
@ -151,7 +186,14 @@ func TestSignifierPGP(accountID string, randReader io.Reader) (SignifierInterfac
panic(err) panic(err)
} }
return accountSignifier(accountID, pgpKey), pubKeyBody if anon {
sigInt, err := anonPGPSignifier(pgpKey, pgpKey)
if err != nil {
panic(err)
}
return sigInt, pubKeyBody
}
return accountSignifier(name, pgpKey), pubKeyBody
} }
// SignifierPGP describes a pgp public key whose corresponding private key will // SignifierPGP describes a pgp public key whose corresponding private key will
@ -185,23 +227,25 @@ func cmdGPG(stdin []byte, args ...string) ([]byte, error) {
// LoadSignifierPGP loads a pgp key using the given identifier. The key is // LoadSignifierPGP loads a pgp key using the given identifier. The key is
// assumed to be stored in the client's keyring already. // assumed to be stored in the client's keyring already.
// //
// If setPubKeyBody is true, then CredentialPGPSignature instances produced by // If this is being called for an anonymous user to use, then anon can be set to
// the returned Signifier will have their PubKeyBody field set. // true. This will have the effect of setting the PubKeyBody and AnonID of all
func LoadSignifierPGP(keyID string, setPubKeyBody bool) (SignifierInterface, error) { // produced Credentials.
func LoadSignifierPGP(keyID string, anon bool) (SignifierInterface, error) {
pubKey, err := cmdGPG(nil, "-a", "--export", keyID) pubKey, err := cmdGPG(nil, "-a", "--export", keyID)
if err != nil { if err != nil {
return nil, fmt.Errorf("loading public key: %w", err) return nil, fmt.Errorf("loading public key: %w", err)
} }
var sigInt SignifierInterface = &SignifierPGP{Body: string(pubKey)}
if setPubKeyBody { sig := &SignifierPGP{Body: string(pubKey)}
sigInt = signifierMiddleware{ if !anon {
SignifierInterface: sigInt, return sig, nil
signCallback: func(cred *Credential) {
cred.PGPSignature.PubKeyBody = string(pubKey)
},
} }
pgpKey, err := sig.load(nil)
if err != nil {
return nil, err
} }
return sigInt, nil return anonPGPSignifier(pgpKey, sig)
} }
func (s SignifierPGP) load(fs fs.FS) (pgpKey, error) { func (s SignifierPGP) load(fs fs.FS) (pgpKey, error) {

View File

@ -38,7 +38,7 @@ func TestPGPVerification(t *testing.T) {
seed := time.Now().UnixNano() seed := time.Now().UnixNano()
t.Logf("seed: %d", seed) t.Logf("seed: %d", seed)
rand := rand.New(rand.NewSource(seed)) rand := rand.New(rand.NewSource(seed))
privKey, pubKeyBody := TestSignifierPGP("", rand) privKey, pubKeyBody := TestSignifierPGP("", false, rand)
sig, fs := test.init(pubKeyBody) sig, fs := test.init(pubKeyBody)
data := make([]byte, rand.Intn(1024)) data := make([]byte, rand.Intn(1024))