age

Simple, secure encryption with UNIX-style composability.
git clone git://git.sgregoratto.me/age
Log | Files | Refs | README | LICENSE

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 }