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