Skip to content

Commit

Permalink
Add capability to merge signatures signed by different parties
Browse files Browse the repository at this point in the history
Signatures generated at different times may need to be merged when the private
keys are no longer available. To support this use case, the
(*JSONSignature).Merge method has been added. It allows the caller to merge
signatures with the same payload from other sources into the receiver. The
resulting signature can verify content from all of the available public keys.

Signed-off-by: Stephen J Day <[email protected]>
  • Loading branch information
stevvooe committed Jan 9, 2015
1 parent 1dc3c57 commit a9625ce
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 0 deletions.
16 changes: 16 additions & 0 deletions jsonsign.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,3 +564,19 @@ func (js *JSONSignature) PrettySignature(signatureKey string) ([]byte, error) {

return buf.Bytes(), nil
}

// Merge combines the signatures from one or more other signatures into the
// method receiver. If the payloads differ for any argument, an error will be
// returned and the receiver will not be modified.
func (js *JSONSignature) Merge(others ...*JSONSignature) error {
merged := js.signatures
for _, other := range others {
if js.payload != other.payload {
return fmt.Errorf("payloads differ from merge target")
}
merged = append(merged, other.signatures...)
}

js.signatures = merged
return nil
}
83 changes: 83 additions & 0 deletions jsonsign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package libtrust

import (
"bytes"
"crypto/rand"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"testing"

"github.com/docker/libtrust/testutil"
Expand Down Expand Up @@ -295,3 +297,84 @@ func TestInvalidChain(t *testing.T) {
t.Fatalf("Unexpected chains returned from invalid verify")
}
}

func TestMergeSignatures(t *testing.T) {
pk1, err := GenerateECP256PrivateKey()
if err != nil {
t.Fatalf("unexpected error generating private key 1: %v", err)
}

pk2, err := GenerateECP256PrivateKey()
if err != nil {
t.Fatalf("unexpected error generating private key 2: %v", err)
}

payload := make([]byte, 1<<10)
if _, err = io.ReadFull(rand.Reader, payload); err != nil {
t.Fatalf("error generating payload: %v", err)
}

payload, _ = json.Marshal(map[string]interface{}{"data": payload})

sig1, err := NewJSONSignature(payload)
if err != nil {
t.Fatalf("unexpected error creating signature 1: %v", err)
}

if err := sig1.Sign(pk1); err != nil {
t.Fatalf("unexpected error signing with pk1: %v", err)
}

sig2, err := NewJSONSignature(payload)
if err != nil {
t.Fatalf("unexpected error creating signature 2: %v", err)
}

if err := sig2.Sign(pk2); err != nil {
t.Fatalf("unexpected error signing with pk2: %v", err)
}

// Now, we actually merge into sig1
if err := sig1.Merge(sig2); err != nil {
t.Fatalf("unexpected error merging: %v", err)
}

// Verify the new signature package
pubkeys, err := sig1.Verify()
if err != nil {
t.Fatalf("unexpected error during verify: %v", err)
}

// Make sure the pubkeys match the two private keys from before
privkeys := map[string]PrivateKey{
pk1.KeyID(): pk1,
pk2.KeyID(): pk2,
}

found := map[string]struct{}{}

for _, pubkey := range pubkeys {
if _, ok := privkeys[pubkey.KeyID()]; !ok {
t.Fatalf("unexpected public key found during verification: %v", pubkey)
}

found[pubkey.KeyID()] = struct{}{}
}

// Make sure we've found all the private keys from verification
for keyid, _ := range privkeys {
if _, ok := found[keyid]; !ok {
t.Fatalf("public key %v not found during verification", keyid)
}
}

// Create another signature, with a different payload, and ensure we get an error.
sig3, err := NewJSONSignature([]byte("{}"))
if err != nil {
t.Fatalf("unexpected error making signature for sig3: %v", err)
}

if err := sig1.Merge(sig3); err == nil {
t.Fatalf("error expected during invalid merge with different payload")
}
}

0 comments on commit a9625ce

Please sign in to comment.