Skip to content

Commit 5aeacad

Browse files
committed
crypto/openpgp: add error and armor
error is needed by all the OpenPGP packages as they return a shared family of error types. armor implements OpenPGP armoring. It's very like PEM except: a) it includes a CRC24 checksum b) PEM values are small (a few KB) and so encoding/pem assumes that they fit in memory. Armored data can be very large and so this package presents a streaming interface. R=r, nsz, rsc CC=golang-dev https://golang.org/cl/3786043
1 parent 1aec7c7 commit 5aeacad

File tree

6 files changed

+548
-0
lines changed

6 files changed

+548
-0
lines changed

src/pkg/crypto/openpgp/armor/Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
include ../../../../Make.inc
6+
7+
TARG=crypto/openpgp/armor
8+
GOFILES=\
9+
armor.go\
10+
encode.go\
11+
12+
include ../../../../Make.pkg

src/pkg/crypto/openpgp/armor/armor.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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+
// This package implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
6+
// very similar to PEM except that it has an additional CRC checksum.
7+
package armor
8+
9+
import (
10+
"bytes"
11+
"crypto/openpgp/error"
12+
"encoding/base64"
13+
"encoding/line"
14+
"io"
15+
"os"
16+
)
17+
18+
// A Block represents an OpenPGP armored structure.
19+
//
20+
// The encoded form is:
21+
// -----BEGIN Type-----
22+
// Headers
23+
//
24+
// base64-encoded Bytes
25+
// '=' base64 encoded checksum
26+
// -----END Type-----
27+
// where Headers is a possibly empty sequence of Key: Value lines.
28+
//
29+
// Since the armored data can be very large, this package presents a streaming
30+
// interface.
31+
type Block struct {
32+
Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE").
33+
Header map[string]string // Optional headers.
34+
Body io.Reader // A Reader from which the contents can be read
35+
lReader lineReader
36+
oReader openpgpReader
37+
}
38+
39+
var ArmorCorrupt os.Error = error.StructuralError("armor invalid")
40+
41+
const crc24Init = 0xb704ce
42+
const crc24Poly = 0x1864cfb
43+
const crc24Mask = 0xffffff
44+
45+
// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1
46+
func crc24(crc uint32, d []byte) uint32 {
47+
for _, b := range d {
48+
crc ^= uint32(b) << 16
49+
for i := 0; i < 8; i++ {
50+
crc <<= 1
51+
if crc&0x1000000 != 0 {
52+
crc ^= crc24Poly
53+
}
54+
}
55+
}
56+
return crc
57+
}
58+
59+
var armorStart = []byte("-----BEGIN ")
60+
var armorEnd = []byte("-----END ")
61+
var armorEndOfLine = []byte("-----")
62+
63+
// lineReader wraps a line based reader. It watches for the end of an armor
64+
// block and records the expected CRC value.
65+
type lineReader struct {
66+
in *line.Reader
67+
buf []byte
68+
eof bool
69+
crc uint32
70+
}
71+
72+
func (l *lineReader) Read(p []byte) (n int, err os.Error) {
73+
if l.eof {
74+
return 0, os.EOF
75+
}
76+
77+
if len(l.buf) > 0 {
78+
n = copy(p, l.buf)
79+
l.buf = l.buf[n:]
80+
return
81+
}
82+
83+
line, isPrefix, err := l.in.ReadLine()
84+
if err != nil {
85+
return
86+
}
87+
if isPrefix {
88+
return 0, ArmorCorrupt
89+
}
90+
91+
if len(line) == 5 && line[0] == '=' {
92+
// This is the checksum line
93+
var expectedBytes [3]byte
94+
var m int
95+
m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:])
96+
if m != 3 || err != nil {
97+
return
98+
}
99+
l.crc = uint32(expectedBytes[0])<<16 |
100+
uint32(expectedBytes[1])<<8 |
101+
uint32(expectedBytes[2])
102+
103+
line, _, err = l.in.ReadLine()
104+
if err != nil && err != os.EOF {
105+
return
106+
}
107+
if !bytes.HasPrefix(line, armorEnd) {
108+
return 0, ArmorCorrupt
109+
}
110+
111+
l.eof = true
112+
return 0, os.EOF
113+
}
114+
115+
if len(line) != 64 {
116+
return 0, ArmorCorrupt
117+
}
118+
119+
n = copy(p, line)
120+
bytesToSave := len(line) - n
121+
if bytesToSave > 0 {
122+
if cap(l.buf) < bytesToSave {
123+
l.buf = make([]byte, 0, bytesToSave)
124+
}
125+
l.buf = l.buf[0:bytesToSave]
126+
copy(l.buf, line[n:])
127+
}
128+
129+
return
130+
}
131+
132+
// openpgpReader passes Read calls to the underlying base64 decoder, but keeps
133+
// a running CRC of the resulting data and checks the CRC against the value
134+
// found by the lineReader at EOF.
135+
type openpgpReader struct {
136+
lReader *lineReader
137+
b64Reader io.Reader
138+
currentCRC uint32
139+
}
140+
141+
func (r *openpgpReader) Read(p []byte) (n int, err os.Error) {
142+
n, err = r.b64Reader.Read(p)
143+
r.currentCRC = crc24(r.currentCRC, p[:n])
144+
145+
if err == os.EOF {
146+
if r.lReader.crc != uint32(r.currentCRC&crc24Mask) {
147+
return 0, ArmorCorrupt
148+
}
149+
}
150+
151+
return
152+
}
153+
154+
// Decode reads a PGP armored block from the given Reader. It will ignore
155+
// leading garbage. If it doesn't find a block, it will return nil, os.EOF. The
156+
// given Reader is not usable after calling this function: an arbitary amount
157+
// of data may have been read past the end of the block.
158+
func Decode(in io.Reader) (p *Block, err os.Error) {
159+
r := line.NewReader(in, 100)
160+
var line []byte
161+
ignoreNext := false
162+
163+
TryNextBlock:
164+
p = nil
165+
166+
// Skip leading garbage
167+
for {
168+
ignoreThis := ignoreNext
169+
line, ignoreNext, err = r.ReadLine()
170+
if err != nil {
171+
return
172+
}
173+
if ignoreNext || ignoreThis {
174+
continue
175+
}
176+
line = bytes.TrimSpace(line)
177+
if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) {
178+
break
179+
}
180+
}
181+
182+
p = new(Block)
183+
p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)])
184+
p.Header = make(map[string]string)
185+
nextIsContinuation := false
186+
var lastKey string
187+
188+
// Read headers
189+
for {
190+
isContinuation := nextIsContinuation
191+
line, nextIsContinuation, err = r.ReadLine()
192+
if err != nil {
193+
p = nil
194+
return
195+
}
196+
if isContinuation {
197+
p.Header[lastKey] += string(line)
198+
continue
199+
}
200+
line = bytes.TrimSpace(line)
201+
if len(line) == 0 {
202+
break
203+
}
204+
205+
i := bytes.Index(line, []byte(": "))
206+
if i == -1 {
207+
goto TryNextBlock
208+
}
209+
lastKey = string(line[:i])
210+
p.Header[lastKey] = string(line[i+2:])
211+
}
212+
213+
p.lReader.in = r
214+
p.oReader.currentCRC = crc24Init
215+
p.oReader.lReader = &p.lReader
216+
p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader)
217+
p.Body = &p.oReader
218+
219+
return
220+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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
6+
7+
import (
8+
"bytes"
9+
"hash/adler32"
10+
"io/ioutil"
11+
"testing"
12+
)
13+
14+
func TestDecodeEncode(t *testing.T) {
15+
buf := bytes.NewBuffer([]byte(armorExample1))
16+
result, err := Decode(buf)
17+
if err != nil {
18+
t.Error(err)
19+
}
20+
expectedType := "PGP SIGNATURE"
21+
if result.Type != expectedType {
22+
t.Errorf("result.Type: got:%s want:%s", result.Type, expectedType)
23+
}
24+
if len(result.Header) != 1 {
25+
t.Errorf("len(result.Header): got:%d want:1", len(result.Header))
26+
}
27+
v, ok := result.Header["Version"]
28+
if !ok || v != "GnuPG v1.4.10 (GNU/Linux)" {
29+
t.Errorf("result.Header: got:%#v", result.Header)
30+
}
31+
32+
contents, err := ioutil.ReadAll(result.Body)
33+
if err != nil {
34+
t.Error(err)
35+
}
36+
37+
if adler32.Checksum(contents) != 0x789d7f00 {
38+
t.Errorf("contents: got: %x", contents)
39+
}
40+
41+
buf = bytes.NewBuffer(nil)
42+
w, err := Encode(buf, result.Type, result.Header)
43+
if err != nil {
44+
t.Error(err)
45+
}
46+
_, err = w.Write(contents)
47+
if err != nil {
48+
t.Error(err)
49+
}
50+
w.Close()
51+
52+
if !bytes.Equal(buf.Bytes(), []byte(armorExample1)) {
53+
t.Errorf("got: %s\nwant: %s", string(buf.Bytes()), armorExample1)
54+
}
55+
}
56+
57+
func TestLongHeader(t *testing.T) {
58+
buf := bytes.NewBuffer([]byte(armorLongLine))
59+
result, err := Decode(buf)
60+
if err != nil {
61+
t.Error(err)
62+
return
63+
}
64+
value, ok := result.Header["Version"]
65+
if !ok {
66+
t.Errorf("missing Version header")
67+
}
68+
if value != longValueExpected {
69+
t.Errorf("got: %s want: %s", value, longValueExpected)
70+
}
71+
}
72+
73+
const armorExample1 = `-----BEGIN PGP SIGNATURE-----
74+
Version: GnuPG v1.4.10 (GNU/Linux)
75+
76+
iQEcBAABAgAGBQJMtFESAAoJEKsQXJGvOPsVj40H/1WW6jaMXv4BW+1ueDSMDwM8
77+
kx1fLOXbVM5/Kn5LStZNt1jWWnpxdz7eq3uiqeCQjmqUoRde3YbB2EMnnwRbAhpp
78+
cacnAvy9ZQ78OTxUdNW1mhX5bS6q1MTEJnl+DcyigD70HG/yNNQD7sOPMdYQw0TA
79+
byQBwmLwmTsuZsrYqB68QyLHI+DUugn+kX6Hd2WDB62DKa2suoIUIHQQCd/ofwB3
80+
WfCYInXQKKOSxu2YOg2Eb4kLNhSMc1i9uKUWAH+sdgJh7NBgdoE4MaNtBFkHXRvv
81+
okWuf3+xA9ksp1npSY/mDvgHijmjvtpRDe6iUeqfCn8N9u9CBg8geANgaG8+QA4=
82+
=wfQG
83+
-----END PGP SIGNATURE-----`
84+
85+
const armorLongLine = `-----BEGIN PGP SIGNATURE-----
86+
Version: 0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz
87+
88+
iQEcBAABAgAGBQJMtFESAAoJEKsQXJGvOPsVj40H/1WW6jaMXv4BW+1ueDSMDwM8
89+
kx1fLOXbVM5/Kn5LStZNt1jWWnpxdz7eq3uiqeCQjmqUoRde3YbB2EMnnwRbAhpp
90+
cacnAvy9ZQ78OTxUdNW1mhX5bS6q1MTEJnl+DcyigD70HG/yNNQD7sOPMdYQw0TA
91+
byQBwmLwmTsuZsrYqB68QyLHI+DUugn+kX6Hd2WDB62DKa2suoIUIHQQCd/ofwB3
92+
WfCYInXQKKOSxu2YOg2Eb4kLNhSMc1i9uKUWAH+sdgJh7NBgdoE4MaNtBFkHXRvv
93+
okWuf3+xA9ksp1npSY/mDvgHijmjvtpRDe6iUeqfCn8N9u9CBg8geANgaG8+QA4=
94+
=wfQG
95+
-----END PGP SIGNATURE-----`
96+
97+
const longValueExpected = "0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz"

0 commit comments

Comments
 (0)