Files
age/internal/format/armor.go
Filippo Valsorda c434eee6e8 internal/format: switch armor to PEM
It's with a heavy heart that I admit using the ASCII header as part of
the armor was clever, and you know what we think about being clever
around here.

Still, PEM is so lax, we target a subset without headers, and without
garbage before and after the markers.

-----BEGIN AGE ENCRYPTED FILE-----
VGhpcyBpcyBhIGZpbGUgZW5jcnlwdGVkIHdpdGggYWdlLXRvb2wuY29tLCB2ZXJz
aW9uIDEKLT4gWDI1NTE5IGozWWtNTWtaVGNDc0tKVGtMN29aam9NT2FUaGpBTVdU
Y1k5ZHVNdWJhUlkKb0F5d2N4ZW1lSTM1SkZiWHIxcHRFWW0rMjNzK3RuOTg1OHpN
L0ZkVzNCTQotLS0gQWZqdXFFaXNhbmYxbGpPRVZsSS9QM0wyM0RrTHRWWElsQnFu
ejFmRW4zdwq1FMc+yjVJBDuBUZSPMi0nCAtELIObQOHHQlQnvhk6BCITceOD5DbN
S7b6oumB8i/hEJvTtsOLgTBofzqzB90iAQ==
-----END AGE ENCRYPTED FILE-----

AGE-SECRET-KEY-1Y77J4M9R7GEKMZHR6YFDLDWV74VK2YQV4C7SR2H7SSVVJ05HQS4Q7NNMS3
2019-12-27 17:13:20 +01:00

175 lines
3.6 KiB
Go

// Copyright 2019 Google LLC
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
package format
import (
"bufio"
"bytes"
"encoding/base64"
"errors"
"io"
)
type newlineWriter struct {
dst io.Writer
written int
}
func (w *newlineWriter) Write(p []byte) (n int, err error) {
for len(p) > 0 {
remainingInLine := columnsPerLine - (w.written % columnsPerLine)
if remainingInLine == columnsPerLine && w.written != 0 {
if _, err := w.dst.Write([]byte("\n")); err != nil {
return n, err
}
}
toWrite := remainingInLine
if toWrite > len(p) {
toWrite = len(p)
}
nn, err := w.dst.Write(p[:toWrite])
n += nn
w.written += nn
p = p[nn:]
if err != nil {
return n, err
}
}
return n, nil
}
type CloserFunc func() error
func (f CloserFunc) Close() error { return f() }
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error { return nil }
func NopCloser(w io.Writer) io.WriteCloser { return nopCloser{w} }
const armorPreamble = "-----BEGIN AGE ENCRYPTED FILE-----"
const armorEnd = "-----END AGE ENCRYPTED FILE-----"
type armoredWriter struct {
started, closed bool
encoder io.WriteCloser
dst io.Writer
}
func (a *armoredWriter) Write(p []byte) (int, error) {
if !a.started {
if _, err := io.WriteString(a.dst, armorPreamble+"\n"); err != nil {
return 0, err
}
}
a.started = true
return a.encoder.Write(p)
}
func (a *armoredWriter) Close() error {
if a.closed {
return errors.New("ArmoredWriter already closed")
}
a.closed = true
if err := a.encoder.Close(); err != nil {
return err
}
_, err := io.WriteString(a.dst, "\n"+armorEnd+"\n")
return err
}
func ArmoredWriter(dst io.Writer) io.WriteCloser {
// TODO: write a test with aligned and misaligned sizes, and 8 and 10 steps.
return &armoredWriter{dst: dst,
encoder: base64.NewEncoder(base64.StdEncoding.Strict(),
&newlineWriter{dst: dst})}
}
type armoredReader struct {
r *bufio.Reader
started bool
unread []byte // backed by buf
buf [bytesPerLine]byte
err error
}
func ArmoredReader(r io.Reader) io.Reader {
return &armoredReader{r: bufio.NewReader(r)}
}
func (r *armoredReader) Read(p []byte) (int, error) {
if len(r.unread) > 0 {
n := copy(p, r.unread)
r.unread = r.unread[n:]
return n, nil
}
if r.err != nil {
return 0, r.err
}
getLine := func() ([]byte, error) {
line, err := r.r.ReadBytes('\n')
if err != nil && len(line) == 0 {
if err == io.EOF {
err = errors.New("invalid armor: unexpected EOF")
}
return nil, err
}
return bytes.TrimSpace(line), nil
}
if !r.started {
line, err := getLine()
if err != nil {
return 0, r.setErr(err)
}
if string(line) != armorPreamble {
return 0, r.setErr(errors.New("invalid armor first line: " + string(line)))
}
r.started = true
}
line, err := getLine()
if err != nil {
return 0, r.setErr(err)
}
if string(line) == armorEnd {
return 0, r.setErr(io.EOF)
}
if len(line) > columnsPerLine {
return 0, r.setErr(errors.New("invalid armor: column limit exceeded"))
}
r.unread = r.buf[:]
n, err := base64.StdEncoding.Strict().Decode(r.unread, line)
if err != nil {
return 0, r.setErr(err)
}
r.unread = r.unread[:n]
if n < bytesPerLine {
line, err := getLine()
if err != nil {
return 0, r.setErr(err)
}
if string(line) != armorEnd {
return 0, r.setErr(errors.New("invalid armor closing line: " + string(line)))
}
r.err = io.EOF
}
nn := copy(p, r.unread)
r.unread = r.unread[nn:]
return nn, nil
}
func (r *armoredReader) setErr(err error) error {
r.err = err
return err
}