format.go (3713B)
1 // Copyright 2019 Google LLC 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file or at 5 // https://developers.google.com/open-source/licenses/bsd 6 7 package format 8 9 import ( 10 "bufio" 11 "bytes" 12 "encoding/base64" 13 "errors" 14 "fmt" 15 "io" 16 "strings" 17 ) 18 19 type Header struct { 20 Recipients []*Recipient 21 MAC []byte 22 } 23 24 type Recipient struct { 25 Type string 26 Args []string 27 Body []byte 28 } 29 30 var b64 = base64.RawURLEncoding.Strict() 31 32 func DecodeString(s string) ([]byte, error) { 33 // CR and LF are ignored by DecodeString. LF is handled by the parser, 34 // but CR can introduce malleability. 35 if strings.Contains(s, "\r") { 36 return nil, errors.New(`invalid character: \r`) 37 } 38 return b64.DecodeString(s) 39 } 40 41 var EncodeToString = b64.EncodeToString // TODO: wrap lines 42 43 const intro = "This is a file encrypted with age-tool.com, version 1\n" 44 45 var recipientPrefix = []byte("->") 46 var footerPrefix = []byte("---") 47 48 func (h *Header) MarshalWithoutMAC(w io.Writer) error { 49 if _, err := io.WriteString(w, intro); err != nil { 50 return err 51 } 52 for _, r := range h.Recipients { 53 if _, err := w.Write(recipientPrefix); err != nil { 54 return err 55 } 56 for _, a := range append([]string{r.Type}, r.Args...) { 57 if _, err := io.WriteString(w, " "+a); err != nil { 58 return err 59 } 60 } 61 if _, err := io.WriteString(w, "\n"); err != nil { 62 return err 63 } 64 // TODO: check that Body ends with a newline. 65 if _, err := w.Write(r.Body); err != nil { 66 return err 67 } 68 } 69 _, err := fmt.Fprintf(w, "%s", footerPrefix) 70 return err 71 } 72 73 func (h *Header) Marshal(w io.Writer) error { 74 if err := h.MarshalWithoutMAC(w); err != nil { 75 return err 76 } 77 mac := b64.EncodeToString(h.MAC) 78 _, err := fmt.Fprintf(w, " %s\n", mac) 79 return err 80 } 81 82 type ParseError string 83 84 func (e ParseError) Error() string { 85 return "parsing age header: " + string(e) 86 } 87 88 func errorf(format string, a ...interface{}) error { 89 return ParseError(fmt.Sprintf(format, a...)) 90 } 91 92 // Parse returns the header and a Reader that begins at the start of the 93 // payload. 94 func Parse(input io.Reader) (*Header, io.Reader, error) { 95 h := &Header{} 96 rr := bufio.NewReader(input) 97 98 line, err := rr.ReadString('\n') 99 if err != nil { 100 return nil, nil, errorf("failed to read intro: %v", err) 101 } 102 if line != intro { 103 return nil, nil, errorf("unexpected intro: %q", line) 104 } 105 106 var r *Recipient 107 for { 108 line, err := rr.ReadBytes('\n') 109 if err != nil { 110 return nil, nil, errorf("failed to read header: %v", err) 111 } 112 113 if bytes.HasPrefix(line, footerPrefix) { 114 prefix, args := splitArgs(line) 115 if prefix != string(footerPrefix) || len(args) != 1 { 116 return nil, nil, errorf("malformed closing line: %q", line) 117 } 118 h.MAC, err = DecodeString(args[0]) 119 if err != nil { 120 return nil, nil, errorf("malformed closing line %q: %v", line, err) 121 } 122 break 123 124 } else if bytes.HasPrefix(line, recipientPrefix) { 125 r = &Recipient{} 126 prefix, args := splitArgs(line) 127 if prefix != string(recipientPrefix) || len(args) < 1 { 128 return nil, nil, errorf("malformed recipient: %q", line) 129 } 130 r.Type = args[0] 131 r.Args = args[1:] 132 h.Recipients = append(h.Recipients, r) 133 134 } else if r != nil { 135 r.Body = append(r.Body, line...) 136 137 } else { 138 return nil, nil, errorf("unexpected line: %q", line) 139 } 140 } 141 142 // Unwind the bufio overread and return the unbuffered input. 143 buf, err := rr.Peek(rr.Buffered()) 144 if err != nil { 145 return nil, nil, errorf("internal error: %v", err) 146 } 147 payload := io.MultiReader(bytes.NewReader(buf), input) 148 149 return h, payload, nil 150 } 151 152 func splitArgs(line []byte) (string, []string) { 153 l := strings.TrimSuffix(string(line), "\n") 154 parts := strings.Split(l, " ") 155 return parts[0], parts[1:] 156 }