|
4 | 4 | "bytes" |
5 | 5 | "context" |
6 | 6 | "crypto/rand" |
| 7 | + "encoding/base64" |
| 8 | + "encoding/json" |
7 | 9 | "fmt" |
8 | 10 | "io" |
9 | 11 | "os" |
@@ -452,3 +454,47 @@ func TestEncryptedBackupRestore(t *testing.T) { |
452 | 454 | t.Errorf("want an 'encrypted' hint in the error, got: %v", err) |
453 | 455 | } |
454 | 456 | } |
| 457 | + |
| 458 | +// `verify` refuses a document that is not a manifest, rather than passing over it. |
| 459 | +// |
| 460 | +// Verify is schema-agnostic up to the signature, which is correct: a signature is over bytes. |
| 461 | +// Then it unmarshals into a Manifest, and a drill report unmarshals cleanly - it simply has no |
| 462 | +// artifacts. So `verify -manifest {ts}.drill.json` reported "signature valid, 0 of 0 ok" and |
| 463 | +// exited zero, on the evidence surface, for a document the command cannot read. That check |
| 464 | +// would have gone on passing if the report were swapped for anything else signed by the same |
| 465 | +// key, which is the definition of a check that cannot fail. |
| 466 | +func TestVerifyRefusesADocumentThatIsNotAManifest(t *testing.T) { |
| 467 | + ctx := context.Background() |
| 468 | + md := newMemDest(true) |
| 469 | + pubPEM, privPEM, _ := crypto.GenerateKeyPair() |
| 470 | + signer, _ := crypto.ParsePrivateKey(privPEM) |
| 471 | + pub, _ := crypto.ParsePublicKey(pubPEM) |
| 472 | + |
| 473 | + // A drill report, signed exactly as the engine signs one, so the only thing under test is |
| 474 | + // what verify does after the signature holds. |
| 475 | + report := pipeline.DrillReport{Schema: pipeline.DrillSchema, DrillID: "d1", Status: pipeline.StatusSuccess} |
| 476 | + canon, err := json.Marshal(report) |
| 477 | + if err != nil { |
| 478 | + t.Fatal(err) |
| 479 | + } |
| 480 | + const key = "github.com/octo/drills/20260903T000000Z.drill.json" |
| 481 | + if _, err := md.PutImmutable(ctx, key, strings.NewReader(string(canon)), int64(len(canon)), dest.Retention{}); err != nil { |
| 482 | + t.Fatal(err) |
| 483 | + } |
| 484 | + sig := base64.StdEncoding.EncodeToString(crypto.Sign(signer, canon)) |
| 485 | + if _, err := md.PutImmutable(ctx, key+".sig", strings.NewReader(sig), int64(len(sig)), dest.Retention{}); err != nil { |
| 486 | + t.Fatal(err) |
| 487 | + } |
| 488 | + |
| 489 | + res, err := pipeline.Verify(ctx, pipeline.VerifyDeps{Dest: md, PublicKey: pub}, key) |
| 490 | + if err == nil { |
| 491 | + t.Fatal("verify passed over a drill report; it counted zero artifacts as a success") |
| 492 | + } |
| 493 | + if !strings.Contains(err.Error(), pipeline.DrillSchema) { |
| 494 | + t.Errorf("the refusal does not say what the document was: %v", err) |
| 495 | + } |
| 496 | + // The signature still held, and saying so is not the same as saying the document verified. |
| 497 | + if res == nil || !res.SignatureValid { |
| 498 | + t.Error("the signature check itself should still have run and passed") |
| 499 | + } |
| 500 | +} |
0 commit comments