Review of 083: finishing an enrolment whose token was spent takes proof of the key's private half, a live lease and a first delivery — a public key alone cannot replay a spent token; shutdown leaves held messages for the broker; identical builds supersede; what is held leaves room in the prefetch
This commit is contained in:
@@ -3,6 +3,8 @@ package link_test
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -23,7 +25,7 @@ func TestAnEnrolmentMetByAHeldTokenIsAskedToTryAgain(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.Claim(ctx, issued.Secret, "another enrolment's key"); err != nil {
|
||||
if _, err := inv.Claim(ctx, issued.Secret, "another enrolment's key", false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
public, _, err := ed25519.GenerateKey(rand.Reader)
|
||||
@@ -42,3 +44,70 @@ func TestAnEnrolmentMetByAHeldTokenIsAskedToTryAgain(t *testing.T) {
|
||||
t.Fatalf("a token that cannot be used was not refused outright: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// **A spent token cannot be replayed with a node's public key** (novox/hq issue 083, on review). A
|
||||
// public key is no secret: finishing an enrolment whose token that key spent takes proof the
|
||||
// presenter holds its private half, and a proof made with another key — or for another request —
|
||||
// is refused outright.
|
||||
func TestASpentTokenCannotBeReplayedWithAPublicKeyAlone(t *testing.T) {
|
||||
inv := inventory.ForTest(t)
|
||||
ctx := t.Context()
|
||||
if _, err := inv.AddNode(ctx, "laptop"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
issued, err := inv.IssueToken(ctx, "laptop", time.Hour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
victim, victimPrivate, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The victim's enrolment spent the token.
|
||||
by := claimantOf(victim)
|
||||
if _, err := inv.Claim(ctx, issued.Secret, by, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.Spend(ctx, issued.Secret, by); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Someone with the leaked token and the victim's public key, and no proof.
|
||||
_, err = link.Enrolment{Inventory: inv}.Enrol(ctx, link.EnrolRequest{
|
||||
Node: "laptop", Secret: issued.Secret, PublicKey: victim, SealingKey: "the attacker's"})
|
||||
if err == nil || errors.Is(err, link.ErrTryAgain) {
|
||||
t.Fatalf("a spent token was taken again with a public key alone: %v", err)
|
||||
}
|
||||
// With a proof made by another key.
|
||||
_, forger, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
forged := ed25519.Sign(forger, link.EnrolProof(issued.Secret, victim, "", "the attacker's", ""))
|
||||
_, err = link.Enrolment{Inventory: inv}.Enrol(ctx, link.EnrolRequest{
|
||||
Node: "laptop", Secret: issued.Secret, PublicKey: victim, SealingKey: "the attacker's", Proof: forged})
|
||||
if err == nil {
|
||||
t.Fatal("a spent token was taken again with a proof made by another key")
|
||||
}
|
||||
// With the victim's own proof, but for another request — keys swapped in.
|
||||
theirs := ed25519.Sign(victimPrivate, link.EnrolProof(issued.Secret, victim, "", "the victim's", ""))
|
||||
_, err = link.Enrolment{Inventory: inv}.Enrol(ctx, link.EnrolRequest{
|
||||
Node: "laptop", Secret: issued.Secret, PublicKey: victim, SealingKey: "the attacker's", Proof: theirs})
|
||||
if err == nil {
|
||||
t.Fatal("a spent token was taken again with a proof made for another request")
|
||||
}
|
||||
// And the victim's own, proven request is not honoured when the broker redelivered it: the
|
||||
// first delivery may already have been answered.
|
||||
_, err = link.Enrolment{Inventory: inv}.Enrol(ctx, link.EnrolRequest{
|
||||
Node: "laptop", Secret: issued.Secret, PublicKey: victim, SealingKey: "the victim's",
|
||||
Proof: theirs, Redelivered: true})
|
||||
if err == nil {
|
||||
t.Fatal("a redelivered request finished an enrolment already spent")
|
||||
}
|
||||
}
|
||||
|
||||
// claimantOf is the claimant the enrolment derives from a key, recomputed here.
|
||||
func claimantOf(public ed25519.PublicKey) string {
|
||||
sum := sha256.Sum256(public)
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user