internal/age,internal/format: implement armored file generation

This commit is contained in:
Filippo Valsorda
2019-11-24 21:10:18 -05:00
parent c624abc0ad
commit 4c4e446f72
5 changed files with 129 additions and 26 deletions

69
internal/format/armor.go Normal file
View File

@@ -0,0 +1,69 @@
// 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 "io"
import "encoding/base64"
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} }
func ArmoredWriter(dst io.Writer) io.WriteCloser {
// TODO: write a test with aligned and misaligned sizes, and 8 and 10 steps.
w := base64.NewEncoder(b64, &newlineWriter{dst: dst})
return struct {
io.Writer
io.Closer
}{
Writer: w,
Closer: CloserFunc(func() error {
if err := w.Close(); err != nil {
return err
}
_, err := dst.Write([]byte("\n--- end of file ---\n"))
return err
}),
}
}

View File

@@ -18,6 +18,7 @@ import (
)
type Header struct {
Armor bool
Recipients []*Recipient
MAC []byte
}
@@ -40,9 +41,11 @@ func DecodeString(s string) ([]byte, error) {
var EncodeToString = b64.EncodeToString
const bytesPerLine = 56 / 4 * 3 // 56 columns of Base64
const columnsPerLine = 56
const bytesPerLine = columnsPerLine / 4 * 3
const intro = "This is a file encrypted with age-tool.com, version 1\n"
const introWithArmor = "This is an armored file encrypted with age-tool.com, version 1\n"
var recipientPrefix = []byte("->")
var footerPrefix = []byte("---")
@@ -59,22 +62,26 @@ func (r *Recipient) Marshal(w io.Writer) error {
if _, err := io.WriteString(w, "\n"); err != nil {
return err
}
for i := 0; i < len(r.Body); i += bytesPerLine {
n := bytesPerLine
if n > len(r.Body)-i {
n = len(r.Body) - i
}
s := EncodeToString(r.Body[i : i+n])
if _, err := io.WriteString(w, s+"\n"); err != nil {
return err
}
ww := base64.NewEncoder(b64, &newlineWriter{dst: w})
if _, err := ww.Write(r.Body); err != nil {
return err
}
return nil
if err := ww.Close(); err != nil {
return err
}
_, err := io.WriteString(w, "\n")
return err
}
func (h *Header) MarshalWithoutMAC(w io.Writer) error {
if _, err := io.WriteString(w, intro); err != nil {
return err
if h.Armor {
if _, err := io.WriteString(w, introWithArmor); err != nil {
return err
}
} else {
if _, err := io.WriteString(w, intro); err != nil {
return err
}
}
for _, r := range h.Recipients {
if err := r.Marshal(w); err != nil {