main
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}