| 1 | package maintenance |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "crypto/ecdh" |
| 6 | "crypto/rand" |
| 7 | "encoding/binary" |
| 8 | "reflect" |
| 9 | "strings" |
| 10 | "testing" |
| 11 | ) |
| 12 | |
| 13 | func TestInputExactSignedBytes(t *testing.T) { |
| 14 | in := Input{Arguments: []string{"operator-v1", "", "space value", "$(not-a-shell)"}, Stdin: []byte(" \n{\"signed\": \"\\u003c\", \"n\": 1e0} \n")} |
| 15 | frame, err := EncodeInput(in) |
| 16 | if err != nil { |
| 17 | t.Fatal(err) |
| 18 | } |
| 19 | got, err := DecodeInput(frame) |
| 20 | if err != nil || !reflect.DeepEqual(in, got) { |
| 21 | t.Fatalf("round trip: %v", err) |
| 22 | } |
| 23 | clear(frame) |
| 24 | if !reflect.DeepEqual(in, got) { |
| 25 | t.Fatal("decoded input aliases frame") |
| 26 | } |
| 27 | priv, err := ecdh.P256().GenerateKey(rand.Reader) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | fingerprint, _ := RecipientFingerprint(priv.PublicKey()) |
| 32 | binding := Binding{Version: 1, Direction: DirectionInput, Account: "account", App: "home", Host: "test.example", RequestID: "request", AttemptID: "attempt", Recipient: fingerprint} |
| 33 | sealed, err := SealInput(priv.PublicKey(), binding, in) |
| 34 | if err != nil { |
| 35 | t.Fatal(err) |
| 36 | } |
| 37 | opened, err := OpenInput(priv, binding, sealed) |
| 38 | if err != nil || !reflect.DeepEqual(in, opened) { |
| 39 | t.Fatalf("sealed round trip: %v", err) |
| 40 | } |
| 41 | for _, direction := range []Direction{DirectionResult, DirectionEnvironment} { |
| 42 | wrong := binding |
| 43 | wrong.Direction = direction |
| 44 | if _, err := SealInput(priv.PublicKey(), wrong, in); err != ErrInput { |
| 45 | t.Fatal("accepted wrong sealing direction") |
| 46 | } |
| 47 | // An otherwise valid envelope of the wrong purpose must also be refused. |
| 48 | frame, _ := EncodeInput(in) |
| 49 | ciphertext, err := Seal(priv.PublicKey(), wrong, frame) |
| 50 | if err != nil { |
| 51 | t.Fatal(err) |
| 52 | } |
| 53 | if _, err := OpenInput(priv, wrong, ciphertext); err != ErrInput { |
| 54 | t.Fatal("accepted wrong opening direction") |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | func TestInputLimitsAndRefusals(t *testing.T) { |
| 60 | maxJSON := []byte(`"` + strings.Repeat("x", MaxInputBytes-2) + `"`) |
| 61 | args := make([]string, MaxArguments) |
| 62 | args[0] = strings.Repeat("x", MaxArgumentBytes) |
| 63 | boundary := Input{Arguments: args, Stdin: maxJSON} |
| 64 | frame, err := EncodeInput(boundary) |
| 65 | if err != nil { |
| 66 | t.Fatal(err) |
| 67 | } |
| 68 | if _, err := DecodeInput(frame); err != nil { |
| 69 | t.Fatal(err) |
| 70 | } |
| 71 | bad := []Input{ |
| 72 | {nil, []byte(`{}`)}, |
| 73 | {make([]string, MaxArguments+1), []byte(`{}`)}, |
| 74 | {[]string{strings.Repeat("x", MaxArgumentBytes), "x"}, []byte(`{}`)}, |
| 75 | {[]string{"ok\x00bad"}, []byte(`{}`)}, |
| 76 | {[]string{string([]byte{255})}, []byte(`{}`)}, |
| 77 | {[]string{"ok"}, append(bytes.Clone(maxJSON), ' ')}, |
| 78 | {[]string{"ok"}, nil}, |
| 79 | {[]string{"ok"}, []byte(`{} {}`)}, |
| 80 | {[]string{"ok"}, []byte{'"', 255, '"'}}, |
| 81 | } |
| 82 | for i, in := range bad { |
| 83 | if _, err := EncodeInput(in); err != ErrInput { |
| 84 | t.Fatalf("case %d encode accepted", i) |
| 85 | } |
| 86 | // Construct unchecked frames so decoder limits cannot rely on the encoder. |
| 87 | raw := binary.BigEndian.AppendUint32([]byte(inputPrefix), uint32(len(in.Arguments))) |
| 88 | for _, arg := range in.Arguments { |
| 89 | raw = appendField(raw, []byte(arg)) |
| 90 | } |
| 91 | raw = appendField(raw, in.Stdin) |
| 92 | if _, err := DecodeInput(raw); err != ErrInput { |
| 93 | t.Fatalf("case %d decode accepted", i) |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | func TestInputMalformedFrames(t *testing.T) { |
| 99 | frame, _ := EncodeInput(Input{[]string{"operator-v1", "argument"}, []byte(`{"ok":true}`)}) |
| 100 | for n := 0; n < len(frame); n++ { |
| 101 | if _, err := DecodeInput(frame[:n]); err != ErrInput { |
| 102 | t.Fatalf("accepted truncation %d", n) |
| 103 | } |
| 104 | } |
| 105 | for _, offset := range []int{0, 4, 8, 8 + 4 + len("operator-v1"), 8 + 4 + len("operator-v1") + 4 + len("argument")} { |
| 106 | broken := bytes.Clone(frame) |
| 107 | binary.BigEndian.PutUint32(broken[offset:offset+4], ^uint32(0)) |
| 108 | if _, err := DecodeInput(broken); err != ErrInput { |
| 109 | t.Fatalf("accepted corrupt frame at %d", offset) |
| 110 | } |
| 111 | } |
| 112 | if _, err := DecodeInput(append(bytes.Clone(frame), 0)); err != ErrInput { |
| 113 | t.Fatal("accepted trailing bytes") |
| 114 | } |
| 115 | if _, err := DecodeInput(make([]byte, maxInputFrame+1)); err != ErrInput { |
| 116 | t.Fatal("accepted oversized frame") |
| 117 | } |
| 118 | } |
| 119 | |