| 1 | package maintenance |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "crypto/ecdh" |
| 6 | "encoding/binary" |
| 7 | "encoding/json" |
| 8 | "errors" |
| 9 | "strings" |
| 10 | "unicode/utf8" |
| 11 | ) |
| 12 | |
| 13 | const ( |
| 14 | MaxInputBytes = 64 << 10 |
| 15 | MaxArguments = 32 |
| 16 | MaxArgumentBytes = 8 << 10 |
| 17 | inputPrefix = "CMI1" |
| 18 | maxInputFrame = len(inputPrefix) + 4 + 4*MaxArguments + MaxArgumentBytes + 4 + MaxInputBytes |
| 19 | ) |
| 20 | |
| 21 | var ErrInput = errors.New("maintenance: invalid private input") |
| 22 | |
| 23 | // Input contains arguments for the pinned artifact and its exact JSON stdin. |
| 24 | // The worker supplies the existing database path; this value grants no authority |
| 25 | // to select an executable, shell, database, environment, or working directory. |
| 26 | type Input struct { |
| 27 | Arguments []string |
| 28 | Stdin []byte |
| 29 | } |
| 30 | |
| 31 | func validInput(in Input) bool { |
| 32 | if len(in.Arguments) == 0 || len(in.Arguments) > MaxArguments || len(in.Stdin) > MaxInputBytes || !utf8.Valid(in.Stdin) || !json.Valid(in.Stdin) { |
| 33 | return false |
| 34 | } |
| 35 | total := 0 |
| 36 | for _, arg := range in.Arguments { |
| 37 | if len(arg) > MaxArgumentBytes-total || strings.ContainsRune(arg, 0) || !utf8.ValidString(arg) { |
| 38 | return false |
| 39 | } |
| 40 | total += len(arg) |
| 41 | } |
| 42 | return true |
| 43 | } |
| 44 | |
| 45 | // EncodeInput retains whitespace, escapes, and every other byte of signed input. |
| 46 | // JSON semantics, including allowed fields, remain the app command's concern. |
| 47 | func EncodeInput(in Input) ([]byte, error) { |
| 48 | if !validInput(in) { |
| 49 | return nil, ErrInput |
| 50 | } |
| 51 | out := binary.BigEndian.AppendUint32([]byte(inputPrefix), uint32(len(in.Arguments))) |
| 52 | for _, arg := range in.Arguments { |
| 53 | out = appendField(out, []byte(arg)) |
| 54 | } |
| 55 | return appendField(out, in.Stdin), nil |
| 56 | } |
| 57 | |
| 58 | // DecodeInput returns owned data only after checking the complete bounded frame. |
| 59 | func DecodeInput(frame []byte) (Input, error) { |
| 60 | if len(frame) > maxInputFrame || len(frame) < 8 || !bytes.Equal(frame[:4], []byte(inputPrefix)) { |
| 61 | return Input{}, ErrInput |
| 62 | } |
| 63 | count := binary.BigEndian.Uint32(frame[4:8]) |
| 64 | if count == 0 || count > MaxArguments { |
| 65 | return Input{}, ErrInput |
| 66 | } |
| 67 | rest := frame[8:] |
| 68 | take := func() ([]byte, bool) { |
| 69 | if len(rest) < 4 { |
| 70 | return nil, false |
| 71 | } |
| 72 | n := binary.BigEndian.Uint32(rest[:4]) |
| 73 | rest = rest[4:] |
| 74 | if uint64(n) > uint64(len(rest)) { |
| 75 | return nil, false |
| 76 | } |
| 77 | value := rest[:int(n)] |
| 78 | rest = rest[int(n):] |
| 79 | return value, true |
| 80 | } |
| 81 | in := Input{Arguments: make([]string, 0, int(count))} |
| 82 | for range count { |
| 83 | arg, ok := take() |
| 84 | if !ok { |
| 85 | return Input{}, ErrInput |
| 86 | } |
| 87 | in.Arguments = append(in.Arguments, string(arg)) |
| 88 | } |
| 89 | stdin, ok := take() |
| 90 | if !ok || len(rest) != 0 { |
| 91 | return Input{}, ErrInput |
| 92 | } |
| 93 | in.Stdin = stdin |
| 94 | if !validInput(in) { |
| 95 | return Input{}, ErrInput |
| 96 | } |
| 97 | in.Stdin = bytes.Clone(stdin) |
| 98 | return in, nil |
| 99 | } |
| 100 | |
| 101 | func SealInput(pub *ecdh.PublicKey, binding Binding, in Input) (string, error) { |
| 102 | if binding.Direction != DirectionInput { |
| 103 | return "", ErrInput |
| 104 | } |
| 105 | frame, err := EncodeInput(in) |
| 106 | if err != nil { |
| 107 | return "", ErrInput |
| 108 | } |
| 109 | defer clear(frame) |
| 110 | sealed, err := Seal(pub, binding, frame) |
| 111 | if err != nil { |
| 112 | return "", ErrInput |
| 113 | } |
| 114 | return sealed, nil |
| 115 | } |
| 116 | |
| 117 | func OpenInput(priv *ecdh.PrivateKey, binding Binding, sealed string) (Input, error) { |
| 118 | if binding.Direction != DirectionInput { |
| 119 | return Input{}, ErrInput |
| 120 | } |
| 121 | frame, err := Open(priv, binding, sealed) |
| 122 | if err != nil { |
| 123 | return Input{}, ErrInput |
| 124 | } |
| 125 | defer clear(frame) |
| 126 | return DecodeInput(frame) |
| 127 | } |
| 128 | |