| 1 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | |
| 3 | package sealbox_test |
| 4 | |
| 5 | import ( |
| 6 | "crypto/rand" |
| 7 | "encoding/base64" |
| 8 | "errors" |
| 9 | "testing" |
| 10 | |
| 11 | "golang.org/x/crypto/nacl/box" |
| 12 | |
| 13 | "github.com/tenseleyFlow/shithub/internal/auth/sealbox" |
| 14 | ) |
| 15 | |
| 16 | func TestNewAndOpenAnonymous_RoundTrip(t *testing.T) { |
| 17 | b, err := sealbox.New() |
| 18 | if err != nil { |
| 19 | t.Fatalf("New: %v", err) |
| 20 | } |
| 21 | if b.PublicKeyBase64() == "" { |
| 22 | t.Fatal("empty public key") |
| 23 | } |
| 24 | pubKey := b.PublicKey() |
| 25 | |
| 26 | plaintext := []byte("super-secret-value") |
| 27 | ciphertext, err := box.SealAnonymous(nil, plaintext, &pubKey, rand.Reader) |
| 28 | if err != nil { |
| 29 | t.Fatalf("SealAnonymous: %v", err) |
| 30 | } |
| 31 | encrypted := base64.StdEncoding.EncodeToString(ciphertext) |
| 32 | |
| 33 | out, err := b.OpenAnonymous(encrypted) |
| 34 | if err != nil { |
| 35 | t.Fatalf("OpenAnonymous: %v", err) |
| 36 | } |
| 37 | if string(out) != string(plaintext) { |
| 38 | t.Errorf("round-trip: got %q, want %q", out, plaintext) |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | func TestFromBase64_DerivesPublicKeyConsistently(t *testing.T) { |
| 43 | // Encrypt against a known keypair, then verify FromBase64 yields |
| 44 | // a Box whose public key matches and which can decrypt. |
| 45 | original, err := sealbox.New() |
| 46 | if err != nil { |
| 47 | t.Fatalf("New: %v", err) |
| 48 | } |
| 49 | pubKey := original.PublicKey() |
| 50 | plaintext := []byte("hello") |
| 51 | ct, _ := box.SealAnonymous(nil, plaintext, &pubKey, rand.Reader) |
| 52 | |
| 53 | // Round-trip via a new Box loaded from the original's private key. |
| 54 | // We need access to the private key — for tests, dump it via a |
| 55 | // secondary path: re-construct from a known fixed private key. |
| 56 | priv32 := make([]byte, 32) |
| 57 | priv32[0] = 1 // deterministic non-zero scalar |
| 58 | loaded, err := sealbox.FromBase64(base64.StdEncoding.EncodeToString(priv32)) |
| 59 | if err != nil { |
| 60 | t.Fatalf("FromBase64: %v", err) |
| 61 | } |
| 62 | loadedPub := loaded.PublicKey() |
| 63 | pt := []byte("via-fromb64") |
| 64 | ct2, _ := box.SealAnonymous(nil, pt, &loadedPub, rand.Reader) |
| 65 | out, err := loaded.OpenAnonymous(base64.StdEncoding.EncodeToString(ct2)) |
| 66 | if err != nil { |
| 67 | t.Fatalf("OpenAnonymous loaded: %v", err) |
| 68 | } |
| 69 | if string(out) != string(pt) { |
| 70 | t.Errorf("loaded round-trip: got %q, want %q", out, pt) |
| 71 | } |
| 72 | // And confirm the unrelated keypair's ciphertext can't open here. |
| 73 | _, err = loaded.OpenAnonymous(base64.StdEncoding.EncodeToString(ct)) |
| 74 | if !errors.Is(err, sealbox.ErrDecryptFailed) { |
| 75 | t.Errorf("expected ErrDecryptFailed for foreign ciphertext; got %v", err) |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | func TestFromBase64_RejectsBadInput(t *testing.T) { |
| 80 | cases := []string{ |
| 81 | "", |
| 82 | "!!!not-base64!!!", |
| 83 | base64.StdEncoding.EncodeToString([]byte("too-short")), |
| 84 | } |
| 85 | for _, in := range cases { |
| 86 | if _, err := sealbox.FromBase64(in); !errors.Is(err, sealbox.ErrInvalidPrivateKey) { |
| 87 | t.Errorf("FromBase64(%q): got %v, want ErrInvalidPrivateKey", in, err) |
| 88 | } |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | func TestOpenAnonymous_RejectsMalformed(t *testing.T) { |
| 93 | b, err := sealbox.New() |
| 94 | if err != nil { |
| 95 | t.Fatalf("New: %v", err) |
| 96 | } |
| 97 | if _, err := b.OpenAnonymous("not-base64!@#"); !errors.Is(err, sealbox.ErrCiphertextMalformed) { |
| 98 | t.Errorf("malformed base64: got %v, want ErrCiphertextMalformed", err) |
| 99 | } |
| 100 | if _, err := b.OpenAnonymous(base64.StdEncoding.EncodeToString([]byte("too-short"))); !errors.Is(err, sealbox.ErrDecryptFailed) { |
| 101 | t.Errorf("too-short ciphertext: got %v, want ErrDecryptFailed", err) |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | func TestKeyID_StableForFixedKey(t *testing.T) { |
| 106 | priv32 := make([]byte, 32) |
| 107 | priv32[0] = 7 |
| 108 | b1, err := sealbox.FromBase64(base64.StdEncoding.EncodeToString(priv32)) |
| 109 | if err != nil { |
| 110 | t.Fatalf("FromBase64: %v", err) |
| 111 | } |
| 112 | b2, err := sealbox.FromBase64(base64.StdEncoding.EncodeToString(priv32)) |
| 113 | if err != nil { |
| 114 | t.Fatalf("FromBase64: %v", err) |
| 115 | } |
| 116 | if b1.KeyID() != b2.KeyID() { |
| 117 | t.Errorf("KeyID not stable: %q vs %q", b1.KeyID(), b2.KeyID()) |
| 118 | } |
| 119 | if b1.KeyID() == "" || len(b1.KeyID()) != 16 { |
| 120 | t.Errorf("KeyID shape: got %q (len=%d)", b1.KeyID(), len(b1.KeyID())) |
| 121 | } |
| 122 | } |
| 123 |