 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			312 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			312 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2019 The age Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| // Package format implements the age file format.
 | |
| package format
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"encoding/base64"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| type Header struct {
 | |
| 	Recipients []*Stanza
 | |
| 	MAC        []byte
 | |
| }
 | |
| 
 | |
| // Stanza is assignable to age.Stanza, and if this package is made public,
 | |
| // age.Stanza can be made a type alias of this type.
 | |
| type Stanza struct {
 | |
| 	Type string
 | |
| 	Args []string
 | |
| 	Body []byte
 | |
| }
 | |
| 
 | |
| var b64 = base64.RawStdEncoding.Strict()
 | |
| 
 | |
| func DecodeString(s string) ([]byte, error) {
 | |
| 	// CR and LF are ignored by DecodeString, but we don't want any malleability.
 | |
| 	if strings.ContainsAny(s, "\n\r") {
 | |
| 		return nil, errors.New(`unexpected newline character`)
 | |
| 	}
 | |
| 	return b64.DecodeString(s)
 | |
| }
 | |
| 
 | |
| var EncodeToString = b64.EncodeToString
 | |
| 
 | |
| const ColumnsPerLine = 64
 | |
| 
 | |
| const BytesPerLine = ColumnsPerLine / 4 * 3
 | |
| 
 | |
| // NewWrappedBase64Encoder returns a WrappedBase64Encoder that writes to dst.
 | |
| func NewWrappedBase64Encoder(enc *base64.Encoding, dst io.Writer) *WrappedBase64Encoder {
 | |
| 	w := &WrappedBase64Encoder{dst: dst}
 | |
| 	w.enc = base64.NewEncoder(enc, WriterFunc(w.writeWrapped))
 | |
| 	return w
 | |
| }
 | |
| 
 | |
| type WriterFunc func(p []byte) (int, error)
 | |
| 
 | |
| func (f WriterFunc) Write(p []byte) (int, error) { return f(p) }
 | |
| 
 | |
| // WrappedBase64Encoder is a standard base64 encoder that inserts an LF
 | |
| // character every ColumnsPerLine bytes. It does not insert a newline neither at
 | |
| // the beginning nor at the end of the stream, but it ensures the last line is
 | |
| // shorter than ColumnsPerLine, which means it might be empty.
 | |
| type WrappedBase64Encoder struct {
 | |
| 	enc     io.WriteCloser
 | |
| 	dst     io.Writer
 | |
| 	written int
 | |
| 	buf     bytes.Buffer
 | |
| }
 | |
| 
 | |
| func (w *WrappedBase64Encoder) Write(p []byte) (int, error) { return w.enc.Write(p) }
 | |
| 
 | |
| func (w *WrappedBase64Encoder) Close() error {
 | |
| 	return w.enc.Close()
 | |
| }
 | |
| 
 | |
| func (w *WrappedBase64Encoder) writeWrapped(p []byte) (int, error) {
 | |
| 	if w.buf.Len() != 0 {
 | |
| 		panic("age: internal error: non-empty WrappedBase64Encoder.buf")
 | |
| 	}
 | |
| 	for len(p) > 0 {
 | |
| 		toWrite := ColumnsPerLine - (w.written % ColumnsPerLine)
 | |
| 		if toWrite > len(p) {
 | |
| 			toWrite = len(p)
 | |
| 		}
 | |
| 		n, _ := w.buf.Write(p[:toWrite])
 | |
| 		w.written += n
 | |
| 		p = p[n:]
 | |
| 		if w.written%ColumnsPerLine == 0 {
 | |
| 			w.buf.Write([]byte("\n"))
 | |
| 		}
 | |
| 	}
 | |
| 	if _, err := w.buf.WriteTo(w.dst); err != nil {
 | |
| 		// We always return n = 0 on error because it's hard to work back to the
 | |
| 		// input length that ended up written out. Not ideal, but Write errors
 | |
| 		// are not recoverable anyway.
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	return len(p), nil
 | |
| }
 | |
| 
 | |
| // LastLineIsEmpty returns whether the last output line was empty, either
 | |
| // because no input was written, or because a multiple of BytesPerLine was.
 | |
| //
 | |
| // Calling LastLineIsEmpty before Close is meaningless.
 | |
| func (w *WrappedBase64Encoder) LastLineIsEmpty() bool {
 | |
| 	return w.written%ColumnsPerLine == 0
 | |
| }
 | |
| 
 | |
| const intro = "age-encryption.org/v1\n"
 | |
| 
 | |
| var stanzaPrefix = []byte("->")
 | |
| var footerPrefix = []byte("---")
 | |
| 
 | |
| func (r *Stanza) Marshal(w io.Writer) error {
 | |
| 	if _, err := w.Write(stanzaPrefix); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	for _, a := range append([]string{r.Type}, r.Args...) {
 | |
| 		if _, err := io.WriteString(w, " "+a); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	if _, err := io.WriteString(w, "\n"); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	ww := NewWrappedBase64Encoder(b64, w)
 | |
| 	if _, err := ww.Write(r.Body); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	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
 | |
| 	}
 | |
| 	for _, r := range h.Recipients {
 | |
| 		if err := r.Marshal(w); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	_, err := fmt.Fprintf(w, "%s", footerPrefix)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (h *Header) Marshal(w io.Writer) error {
 | |
| 	if err := h.MarshalWithoutMAC(w); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	mac := b64.EncodeToString(h.MAC)
 | |
| 	_, err := fmt.Fprintf(w, " %s\n", mac)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| type StanzaReader struct {
 | |
| 	r   *bufio.Reader
 | |
| 	err error
 | |
| }
 | |
| 
 | |
| func NewStanzaReader(r *bufio.Reader) *StanzaReader {
 | |
| 	return &StanzaReader{r: r}
 | |
| }
 | |
| 
 | |
| func (r *StanzaReader) ReadStanza() (s *Stanza, err error) {
 | |
| 	// Read errors are unrecoverable.
 | |
| 	if r.err != nil {
 | |
| 		return nil, r.err
 | |
| 	}
 | |
| 	defer func() { r.err = err }()
 | |
| 
 | |
| 	s = &Stanza{}
 | |
| 
 | |
| 	line, err := r.r.ReadBytes('\n')
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to read line: %w", err)
 | |
| 	}
 | |
| 	if !bytes.HasPrefix(line, stanzaPrefix) {
 | |
| 		return nil, fmt.Errorf("malformed stanza opening line: %q", line)
 | |
| 	}
 | |
| 	prefix, args := splitArgs(line)
 | |
| 	if prefix != string(stanzaPrefix) || len(args) < 1 {
 | |
| 		return nil, fmt.Errorf("malformed stanza: %q", line)
 | |
| 	}
 | |
| 	for _, a := range args {
 | |
| 		if !isValidString(a) {
 | |
| 			return nil, fmt.Errorf("malformed stanza: %q", line)
 | |
| 		}
 | |
| 	}
 | |
| 	s.Type = args[0]
 | |
| 	s.Args = args[1:]
 | |
| 
 | |
| 	for {
 | |
| 		line, err := r.r.ReadBytes('\n')
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("failed to read line: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		b, err := DecodeString(strings.TrimSuffix(string(line), "\n"))
 | |
| 		if err != nil {
 | |
| 			if bytes.HasPrefix(line, footerPrefix) || bytes.HasPrefix(line, stanzaPrefix) {
 | |
| 				return nil, fmt.Errorf("malformed body line %q: stanza ended without a short line\nnote: this might be a file encrypted with an old beta version of age or rage; use age v1.0.0-beta6 or rage to decrypt it", line)
 | |
| 			}
 | |
| 			return nil, errorf("malformed body line %q: %v", line, err)
 | |
| 		}
 | |
| 		if len(b) > BytesPerLine {
 | |
| 			return nil, errorf("malformed body line %q: too long", line)
 | |
| 		}
 | |
| 		s.Body = append(s.Body, b...)
 | |
| 		if len(b) < BytesPerLine {
 | |
| 			// A stanza body always ends with a short line.
 | |
| 			return s, nil
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type ParseError struct {
 | |
| 	err error
 | |
| }
 | |
| 
 | |
| func (e *ParseError) Error() string {
 | |
| 	return "parsing age header: " + e.err.Error()
 | |
| }
 | |
| 
 | |
| func (e *ParseError) Unwrap() error {
 | |
| 	return e.err
 | |
| }
 | |
| 
 | |
| func errorf(format string, a ...interface{}) error {
 | |
| 	return &ParseError{fmt.Errorf(format, a...)}
 | |
| }
 | |
| 
 | |
| // Parse returns the header and a Reader that begins at the start of the
 | |
| // payload.
 | |
| func Parse(input io.Reader) (*Header, io.Reader, error) {
 | |
| 	h := &Header{}
 | |
| 	rr := bufio.NewReader(input)
 | |
| 
 | |
| 	line, err := rr.ReadString('\n')
 | |
| 	if err != nil {
 | |
| 		return nil, nil, errorf("failed to read intro: %w", err)
 | |
| 	}
 | |
| 	if line != intro {
 | |
| 		return nil, nil, errorf("unexpected intro: %q", line)
 | |
| 	}
 | |
| 
 | |
| 	sr := NewStanzaReader(rr)
 | |
| 	for {
 | |
| 		peek, err := rr.Peek(len(footerPrefix))
 | |
| 		if err != nil {
 | |
| 			return nil, nil, errorf("failed to read header: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		if bytes.Equal(peek, footerPrefix) {
 | |
| 			line, err := rr.ReadBytes('\n')
 | |
| 			if err != nil {
 | |
| 				return nil, nil, fmt.Errorf("failed to read header: %w", err)
 | |
| 			}
 | |
| 
 | |
| 			prefix, args := splitArgs(line)
 | |
| 			if prefix != string(footerPrefix) || len(args) != 1 {
 | |
| 				return nil, nil, errorf("malformed closing line: %q", line)
 | |
| 			}
 | |
| 			h.MAC, err = DecodeString(args[0])
 | |
| 			if err != nil || len(h.MAC) != 32 {
 | |
| 				return nil, nil, errorf("malformed closing line %q: %v", line, err)
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		s, err := sr.ReadStanza()
 | |
| 		if err != nil {
 | |
| 			return nil, nil, fmt.Errorf("failed to parse header: %w", err)
 | |
| 		}
 | |
| 		h.Recipients = append(h.Recipients, s)
 | |
| 	}
 | |
| 
 | |
| 	// If input is a bufio.Reader, rr might be equal to input because
 | |
| 	// bufio.NewReader short-circuits. In this case we can just return it (and
 | |
| 	// we would end up reading the buffer twice if we prepended the peek below).
 | |
| 	if rr == input {
 | |
| 		return h, rr, nil
 | |
| 	}
 | |
| 	// Otherwise, unwind the bufio overread and return the unbuffered input.
 | |
| 	buf, err := rr.Peek(rr.Buffered())
 | |
| 	if err != nil {
 | |
| 		return nil, nil, errorf("internal error: %v", err)
 | |
| 	}
 | |
| 	payload := io.MultiReader(bytes.NewReader(buf), input)
 | |
| 	return h, payload, nil
 | |
| }
 | |
| 
 | |
| func splitArgs(line []byte) (string, []string) {
 | |
| 	l := strings.TrimSuffix(string(line), "\n")
 | |
| 	parts := strings.Split(l, " ")
 | |
| 	return parts[0], parts[1:]
 | |
| }
 | |
| 
 | |
| func isValidString(s string) bool {
 | |
| 	if len(s) == 0 {
 | |
| 		return false
 | |
| 	}
 | |
| 	for _, c := range s {
 | |
| 		if c < 33 || c > 126 {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 |