package provider_test import ( "bytes" "errors" "testing" datekeys "g.activething.com/go/DateKeys" "g.activething.com/go/DateKeys/internal/testkit" "g.activething.com/go/DateKeys/profile" "g.activething.com/go/DateKeys/provider" ) func TestVerifyPublishedReleases(t *testing.T) { p := profile.Quicknet() for _, round := range testkit.Rounds { if err := provider.Verify(p, provider.Condition{Round: round}, testkit.Release(round)); err != nil { t.Fatalf("round %d: %v", round, err) } } } func TestVerifyRejects(t *testing.T) { p := profile.Quicknet() r1000, r1001 := testkit.Release(1000), testkit.Release(1001) xPlusP, err := testkit.AddModulus(testkit.Release(testkit.XPlusPRound).Signature, 0) if err != nil { t.Fatal(err) } uncompressed := bytes.Clone(r1000.Signature) uncompressed[0] &^= testkit.FlagCompressed for _, tc := range []struct { name string cond uint64 rel provider.Release want error }{ // Spec §17: a valid signature of another round is not enough. {"valid release of another round", 1000, r1001, datekeys.ErrRoundMismatch}, // Spec §63 step 10: the round is compared before the signature. {"another round and a short signature", 1000, provider.Release{Round: 1001, Signature: r1001.Signature[:47]}, datekeys.ErrRoundMismatch}, // A signature of round 1001 relabelled as round 1000. {"signature of another round relabelled", 1000, provider.Release{Round: 1000, Signature: r1001.Signature}, datekeys.ErrReleaseInvalid}, {"all-zero signature", 1000, provider.Release{Round: 1000, Signature: make([]byte, 48)}, datekeys.ErrReleaseInvalid}, {"flipped bit", 1000, provider.Release{Round: 1000, Signature: flip(r1000.Signature)}, datekeys.ErrReleaseInvalid}, {"short signature", 1000, provider.Release{Round: 1000, Signature: r1000.Signature[:47]}, datekeys.ErrReleaseInvalid}, {"G2-sized signature", 1000, provider.Release{Round: 1000, Signature: bytes.Repeat(r1000.Signature, 2)}, datekeys.ErrReleaseInvalid}, // Spec §12.2, §63 step 10: the canonical encoding of a point of G1 // other than the point at infinity. For a decoder that reduces x // modulo p, x + p is the published signature of its round. {"signature re-encoded with x + p", testkit.XPlusPRound, provider.Release{Round: testkit.XPlusPRound, Signature: xPlusP}, datekeys.ErrReleaseInvalid}, {"signature the point at infinity", 1000, provider.Release{Round: 1000, Signature: testkit.Infinity(48)}, datekeys.ErrReleaseInvalid}, {"infinity flag and a payload", 1000, provider.Release{Round: 1000, Signature: testkit.InfinityWithPayload(r1000.Signature)}, datekeys.ErrReleaseInvalid}, {"point at infinity with the sort flag", 1000, provider.Release{Round: 1000, Signature: testkit.Negated(testkit.Infinity(48))}, datekeys.ErrReleaseInvalid}, {"compression flag cleared", 1000, provider.Release{Round: 1000, Signature: uncompressed}, datekeys.ErrReleaseInvalid}, {"negated signature", 1000, provider.Release{Round: 1000, Signature: testkit.Negated(r1000.Signature)}, datekeys.ErrReleaseInvalid}, {"round zero", 0, provider.Release{Round: 0, Signature: r1000.Signature}, datekeys.ErrDateKeyInvalid}, {"round beyond the profile", p.MaxRound() + 1, provider.Release{Round: p.MaxRound() + 1, Signature: r1000.Signature}, datekeys.ErrDateKeyInvalid}, } { t.Run(tc.name, func(t *testing.T) { if err := provider.Verify(p, provider.Condition{Round: tc.cond}, tc.rel); !errors.Is(err, tc.want) { t.Fatalf("got %v, want %v", err, tc.want) } }) } } func TestVerifyUsesThePinnedKeyOnly(t *testing.T) { // A profile with another public key rejects the genuine signature. p := profile.Quicknet() p.PublicKey = append([]byte(nil), p.PublicKey...) p.PublicKey[len(p.PublicKey)-1] ^= 1 err := provider.Verify(p, provider.Condition{Round: 1000}, testkit.Release(1000)) if err == nil { t.Fatal("verified under a different key") } } func flip(b []byte) []byte { c := append([]byte(nil), b...) c[10] ^= 0x01 return c }