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
5// Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
6// very similar to PEM except that it has an additional CRC checksum.
7package armor // import "github.com/ProtonMail/go-crypto/openpgp/armor"
8
9import (
10 "bufio"
11 "bytes"
12 "encoding/base64"
13 "io"
14
15 "github.com/ProtonMail/go-crypto/openpgp/errors"
16)
17
18// A Block represents an OpenPGP armored structure.
19//
20// The encoded form is:
21//
22// -----BEGIN Type-----
23// Headers
24//
25// base64-encoded Bytes
26// '=' base64 encoded checksum (optional) not checked anymore
27// -----END Type-----
28//
29// where Headers is a possibly empty sequence of Key: Value lines.
30//
31// Since the armored data can be very large, this package presents a streaming
32// interface.
33type Block struct {
34 Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE").
35 Header map[string]string // Optional headers.
36 Body io.Reader // A Reader from which the contents can be read
37 lReader lineReader
38 oReader openpgpReader
39}
40
41var ArmorCorrupt error = errors.StructuralError("armor invalid")
42
43var armorStart = []byte("-----BEGIN ")
44var armorEnd = []byte("-----END ")
45var armorEndOfLine = []byte("-----")
46
47// lineReader wraps a line based reader. It watches for the end of an armor block
48type lineReader struct {
49 in *bufio.Reader
50 buf []byte
51 eof bool
52}
53
54func (l *lineReader) Read(p []byte) (n int, err error) {
55 if l.eof {
56 return 0, io.EOF
57 }
58
59 if len(l.buf) > 0 {
60 n = copy(p, l.buf)
61 l.buf = l.buf[n:]
62 return
63 }
64
65 line, isPrefix, err := l.in.ReadLine()
66 if err != nil {
67 return
68 }
69 if isPrefix {
70 return 0, ArmorCorrupt
71 }
72
73 if bytes.HasPrefix(line, armorEnd) {
74 l.eof = true
75 return 0, io.EOF
76 }
77
78 if len(line) == 5 && line[0] == '=' {
79 // This is the checksum line
80 // Don't check the checksum
81
82 l.eof = true
83 return 0, io.EOF
84 }
85
86 if len(line) > 96 {
87 return 0, ArmorCorrupt
88 }
89
90 n = copy(p, line)
91 bytesToSave := len(line) - n
92 if bytesToSave > 0 {
93 if cap(l.buf) < bytesToSave {
94 l.buf = make([]byte, 0, bytesToSave)
95 }
96 l.buf = l.buf[0:bytesToSave]
97 copy(l.buf, line[n:])
98 }
99
100 return
101}
102
103// openpgpReader passes Read calls to the underlying base64 decoder.
104type openpgpReader struct {
105 lReader *lineReader
106 b64Reader io.Reader
107}
108
109func (r *openpgpReader) Read(p []byte) (n int, err error) {
110 n, err = r.b64Reader.Read(p)
111 return
112}
113
114// Decode reads a PGP armored block from the given Reader. It will ignore
115// leading garbage. If it doesn't find a block, it will return nil, io.EOF. The
116// given Reader is not usable after calling this function: an arbitrary amount
117// of data may have been read past the end of the block.
118func Decode(in io.Reader) (p *Block, err error) {
119 r := bufio.NewReaderSize(in, 100)
120 var line []byte
121 ignoreNext := false
122
123TryNextBlock:
124 p = nil
125
126 // Skip leading garbage
127 for {
128 ignoreThis := ignoreNext
129 line, ignoreNext, err = r.ReadLine()
130 if err != nil {
131 return
132 }
133 if ignoreNext || ignoreThis {
134 continue
135 }
136 line = bytes.TrimSpace(line)
137 if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) {
138 break
139 }
140 }
141
142 p = new(Block)
143 p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)])
144 p.Header = make(map[string]string)
145 nextIsContinuation := false
146 var lastKey string
147
148 // Read headers
149 for {
150 isContinuation := nextIsContinuation
151 line, nextIsContinuation, err = r.ReadLine()
152 if err != nil {
153 p = nil
154 return
155 }
156 if isContinuation {
157 p.Header[lastKey] += string(line)
158 continue
159 }
160 line = bytes.TrimSpace(line)
161 if len(line) == 0 {
162 break
163 }
164
165 i := bytes.Index(line, []byte(":"))
166 if i == -1 {
167 goto TryNextBlock
168 }
169 lastKey = string(line[:i])
170 var value string
171 if len(line) > i+2 {
172 value = string(line[i+2:])
173 }
174 p.Header[lastKey] = value
175 }
176
177 p.lReader.in = r
178 p.oReader.lReader = &p.lReader
179 p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader)
180 p.Body = &p.oReader
181
182 return
183}