main
Raw Download raw file
  1// Copyright 2010 The Go Authors. All rights reserved.
  2// Use of this source code is governed by a BSD-style
  3// license that can be found in the LICENSE file.
  4
  5package armor
  6
  7import (
  8	"encoding/base64"
  9	"io"
 10	"sort"
 11)
 12
 13var armorHeaderSep = []byte(": ")
 14var blockEnd = []byte("\n=")
 15var newline = []byte("\n")
 16var armorEndOfLineOut = []byte("-----\n")
 17
 18const crc24Init = 0xb704ce
 19const crc24Poly = 0x1864cfb
 20
 21// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1
 22func crc24(crc uint32, d []byte) uint32 {
 23	for _, b := range d {
 24		crc ^= uint32(b) << 16
 25		for i := 0; i < 8; i++ {
 26			crc <<= 1
 27			if crc&0x1000000 != 0 {
 28				crc ^= crc24Poly
 29			}
 30		}
 31	}
 32	return crc
 33}
 34
 35// writeSlices writes its arguments to the given Writer.
 36func writeSlices(out io.Writer, slices ...[]byte) (err error) {
 37	for _, s := range slices {
 38		_, err = out.Write(s)
 39		if err != nil {
 40			return err
 41		}
 42	}
 43	return
 44}
 45
 46// lineBreaker breaks data across several lines, all of the same byte length
 47// (except possibly the last). Lines are broken with a single '\n'.
 48type lineBreaker struct {
 49	lineLength  int
 50	line        []byte
 51	used        int
 52	out         io.Writer
 53	haveWritten bool
 54}
 55
 56func newLineBreaker(out io.Writer, lineLength int) *lineBreaker {
 57	return &lineBreaker{
 58		lineLength: lineLength,
 59		line:       make([]byte, lineLength),
 60		used:       0,
 61		out:        out,
 62	}
 63}
 64
 65func (l *lineBreaker) Write(b []byte) (n int, err error) {
 66	n = len(b)
 67
 68	if n == 0 {
 69		return
 70	}
 71
 72	if l.used == 0 && l.haveWritten {
 73		_, err = l.out.Write([]byte{'\n'})
 74		if err != nil {
 75			return
 76		}
 77	}
 78
 79	if l.used+len(b) < l.lineLength {
 80		l.used += copy(l.line[l.used:], b)
 81		return
 82	}
 83
 84	l.haveWritten = true
 85	_, err = l.out.Write(l.line[0:l.used])
 86	if err != nil {
 87		return
 88	}
 89	excess := l.lineLength - l.used
 90	l.used = 0
 91
 92	_, err = l.out.Write(b[0:excess])
 93	if err != nil {
 94		return
 95	}
 96
 97	_, err = l.Write(b[excess:])
 98	return
 99}
100
101func (l *lineBreaker) Close() (err error) {
102	if l.used > 0 {
103		_, err = l.out.Write(l.line[0:l.used])
104		if err != nil {
105			return
106		}
107	}
108
109	return
110}
111
112// encoding keeps track of a running CRC24 over the data which has been written
113// to it and outputs a OpenPGP checksum when closed, followed by an armor
114// trailer.
115//
116// It's built into a stack of io.Writers:
117//
118//	encoding -> base64 encoder -> lineBreaker -> out
119type encoding struct {
120	out        io.Writer
121	breaker    *lineBreaker
122	b64        io.WriteCloser
123	crc        uint32
124	crcEnabled bool
125	blockType  []byte
126}
127
128func (e *encoding) Write(data []byte) (n int, err error) {
129	if e.crcEnabled {
130		e.crc = crc24(e.crc, data)
131	}
132	return e.b64.Write(data)
133}
134
135func (e *encoding) Close() (err error) {
136	err = e.b64.Close()
137	if err != nil {
138		return
139	}
140	e.breaker.Close()
141
142	if e.crcEnabled {
143		var checksumBytes [3]byte
144		checksumBytes[0] = byte(e.crc >> 16)
145		checksumBytes[1] = byte(e.crc >> 8)
146		checksumBytes[2] = byte(e.crc)
147
148		var b64ChecksumBytes [4]byte
149		base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:])
150
151		return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine)
152	}
153	return writeSlices(e.out, newline, armorEnd, e.blockType, armorEndOfLine)
154}
155
156func encode(out io.Writer, blockType string, headers map[string]string, checksum bool) (w io.WriteCloser, err error) {
157	bType := []byte(blockType)
158	err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
159	if err != nil {
160		return
161	}
162
163	keys := make([]string, len(headers))
164	i := 0
165	for k := range headers {
166		keys[i] = k
167		i++
168	}
169	sort.Strings(keys)
170	for _, k := range keys {
171		err = writeSlices(out, []byte(k), armorHeaderSep, []byte(headers[k]), newline)
172		if err != nil {
173			return
174		}
175	}
176
177	_, err = out.Write(newline)
178	if err != nil {
179		return
180	}
181
182	e := &encoding{
183		out:        out,
184		breaker:    newLineBreaker(out, 64),
185		blockType:  bType,
186		crc:        crc24Init,
187		crcEnabled: checksum,
188	}
189	e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
190	return e, nil
191}
192
193// Encode returns a WriteCloser which will encode the data written to it in
194// OpenPGP armor.
195func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
196	return encode(out, blockType, headers, true)
197}
198
199// EncodeWithChecksumOption returns a WriteCloser which will encode the data written to it in
200// OpenPGP armor and provides the option to include a checksum.
201// When forming ASCII Armor, the CRC24 footer SHOULD NOT be generated,
202// unless interoperability with implementations that require the CRC24 footer
203// to be present is a concern.
204func EncodeWithChecksumOption(out io.Writer, blockType string, headers map[string]string, doChecksum bool) (w io.WriteCloser, err error) {
205	return encode(out, blockType, headers, doChecksum)
206}