mirror of
https://github.com/FiloSottile/age.git
synced 2026-01-03 10:55:14 +00:00
internal/armor: new package
This commit is contained in:
@@ -1,174 +0,0 @@
|
||||
// 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(errors.New("invalid armor: " + err.Error()))
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age/internal/format"
|
||||
)
|
||||
|
||||
func TestArmor(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
w := format.ArmoredWriter(buf)
|
||||
plain := make([]byte, 611)
|
||||
if _, err := w.Write(plain); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(buf.Bytes())
|
||||
if block == nil {
|
||||
t.Fatal("PEM decoding failed")
|
||||
}
|
||||
if !bytes.Equal(block.Bytes, plain) {
|
||||
t.Error("PEM decoded value doesn't match")
|
||||
}
|
||||
|
||||
r := format.ArmoredReader(buf)
|
||||
out, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(out, plain) {
|
||||
t.Error("decoded value doesn't match")
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,43 @@ func DecodeString(s string) ([]byte, error) {
|
||||
|
||||
var EncodeToString = b64.EncodeToString
|
||||
|
||||
const columnsPerLine = 64
|
||||
const bytesPerLine = columnsPerLine / 4 * 3
|
||||
const ColumnsPerLine = 64
|
||||
const BytesPerLine = ColumnsPerLine / 4 * 3
|
||||
|
||||
// NewlineWriter returns a Writer that writes to dst, inserting an LF character
|
||||
// every ColumnsPerLine bytes. It does not insert a newline neither at the
|
||||
// beginning nor at the end of the stream.
|
||||
func NewlineWriter(dst io.Writer) io.Writer {
|
||||
return &newlineWriter{dst: dst}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const intro = "age-encryption.org/v1\n"
|
||||
|
||||
@@ -63,7 +98,7 @@ func (r *Recipient) Marshal(w io.Writer) error {
|
||||
if len(r.Body) == 0 {
|
||||
return nil
|
||||
}
|
||||
ww := base64.NewEncoder(b64, &newlineWriter{dst: w})
|
||||
ww := base64.NewEncoder(b64, NewlineWriter(w))
|
||||
if _, err := ww.Write(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -158,14 +193,14 @@ func Parse(input io.Reader) (*Header, io.Reader, error) {
|
||||
if err != nil {
|
||||
return nil, nil, errorf("malformed body line %q: %v", line, err)
|
||||
}
|
||||
if len(b) > bytesPerLine {
|
||||
if len(b) > BytesPerLine {
|
||||
return nil, nil, errorf("malformed body line %q: too long", line)
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil, nil, errorf("malformed body line %q: line is empty", line)
|
||||
}
|
||||
r.Body = append(r.Body, b...)
|
||||
if len(b) < bytesPerLine {
|
||||
if len(b) < BytesPerLine {
|
||||
// Only the last line of a body can be short.
|
||||
r = nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user