1
2
3
4
5
6
7
8 package pem
9
10 import (
11 "bytes"
12 "encoding/base64"
13 "errors"
14 "io"
15 "sort"
16 "strings"
17 )
18
19
20
21
22
23
24
25
26
27 type Block struct {
28 Type string
29 Headers map[string]string
30 Bytes []byte
31 }
32
33
34
35
36
37
38 func getLine(data []byte) (line, rest []byte) {
39 i := bytes.IndexByte(data, '\n')
40 var j int
41 if i < 0 {
42 i = len(data)
43 j = i
44 } else {
45 j = i + 1
46 if i > 0 && data[i-1] == '\r' {
47 i--
48 }
49 }
50 return bytes.TrimRight(data[0:i], " \t"), data[j:]
51 }
52
53
54
55 func removeWhitespace(data []byte) []byte {
56 result := make([]byte, len(data))
57 n := 0
58
59 for _, b := range data {
60 if b == ' ' || b == '\t' || b == '\r' || b == '\n' {
61 continue
62 }
63 result[n] = b
64 n++
65 }
66
67 return result[0:n]
68 }
69
70 var pemStart = []byte("\n-----BEGIN ")
71 var pemEnd = []byte("\n-----END ")
72 var pemEndOfLine = []byte("-----")
73
74
75
76
77
78 func Decode(data []byte) (p *Block, rest []byte) {
79
80
81 rest = data
82 if bytes.HasPrefix(data, pemStart[1:]) {
83 rest = rest[len(pemStart)-1 : len(data)]
84 } else if i := bytes.Index(data, pemStart); i >= 0 {
85 rest = rest[i+len(pemStart) : len(data)]
86 } else {
87 return nil, data
88 }
89
90 typeLine, rest := getLine(rest)
91 if !bytes.HasSuffix(typeLine, pemEndOfLine) {
92 return decodeError(data, rest)
93 }
94 typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
95
96 p = &Block{
97 Headers: make(map[string]string),
98 Type: string(typeLine),
99 }
100
101 for {
102
103
104 if len(rest) == 0 {
105 return nil, data
106 }
107 line, next := getLine(rest)
108
109 i := bytes.IndexByte(line, ':')
110 if i == -1 {
111 break
112 }
113
114
115 key, val := line[:i], line[i+1:]
116 key = bytes.TrimSpace(key)
117 val = bytes.TrimSpace(val)
118 p.Headers[string(key)] = string(val)
119 rest = next
120 }
121
122 var endIndex, endTrailerIndex int
123
124
125
126 if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) {
127 endIndex = 0
128 endTrailerIndex = len(pemEnd) - 1
129 } else {
130 endIndex = bytes.Index(rest, pemEnd)
131 endTrailerIndex = endIndex + len(pemEnd)
132 }
133
134 if endIndex < 0 {
135 return decodeError(data, rest)
136 }
137
138
139
140 endTrailer := rest[endTrailerIndex:]
141 endTrailerLen := len(typeLine) + len(pemEndOfLine)
142 if len(endTrailer) < endTrailerLen {
143 return decodeError(data, rest)
144 }
145
146 restOfEndLine := endTrailer[endTrailerLen:]
147 endTrailer = endTrailer[:endTrailerLen]
148 if !bytes.HasPrefix(endTrailer, typeLine) ||
149 !bytes.HasSuffix(endTrailer, pemEndOfLine) {
150 return decodeError(data, rest)
151 }
152
153
154 if s, _ := getLine(restOfEndLine); len(s) != 0 {
155 return decodeError(data, rest)
156 }
157
158 base64Data := removeWhitespace(rest[:endIndex])
159 p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
160 n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
161 if err != nil {
162 return decodeError(data, rest)
163 }
164 p.Bytes = p.Bytes[:n]
165
166
167
168 _, rest = getLine(rest[endIndex+len(pemEnd)-1:])
169
170 return
171 }
172
173 func decodeError(data, rest []byte) (*Block, []byte) {
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194 p, rest := Decode(rest)
195 if p == nil {
196 rest = data
197 }
198 return p, rest
199 }
200
201 const pemLineLength = 64
202
203 type lineBreaker struct {
204 line [pemLineLength]byte
205 used int
206 out io.Writer
207 }
208
209 var nl = []byte{'\n'}
210
211 func (l *lineBreaker) Write(b []byte) (n int, err error) {
212 if l.used+len(b) < pemLineLength {
213 copy(l.line[l.used:], b)
214 l.used += len(b)
215 return len(b), nil
216 }
217
218 n, err = l.out.Write(l.line[0:l.used])
219 if err != nil {
220 return
221 }
222 excess := pemLineLength - l.used
223 l.used = 0
224
225 n, err = l.out.Write(b[0:excess])
226 if err != nil {
227 return
228 }
229
230 n, err = l.out.Write(nl)
231 if err != nil {
232 return
233 }
234
235 return l.Write(b[excess:])
236 }
237
238 func (l *lineBreaker) Close() (err error) {
239 if l.used > 0 {
240 _, err = l.out.Write(l.line[0:l.used])
241 if err != nil {
242 return
243 }
244 _, err = l.out.Write(nl)
245 }
246
247 return
248 }
249
250 func writeHeader(out io.Writer, k, v string) error {
251 _, err := out.Write([]byte(k + ": " + v + "\n"))
252 return err
253 }
254
255
256 func Encode(out io.Writer, b *Block) error {
257
258 for k := range b.Headers {
259 if strings.Contains(k, ":") {
260 return errors.New("pem: cannot encode a header key that contains a colon")
261 }
262 }
263
264
265
266
267 if _, err := out.Write(pemStart[1:]); err != nil {
268 return err
269 }
270 if _, err := out.Write([]byte(b.Type + "-----\n")); err != nil {
271 return err
272 }
273
274 if len(b.Headers) > 0 {
275 const procType = "Proc-Type"
276 h := make([]string, 0, len(b.Headers))
277 hasProcType := false
278 for k := range b.Headers {
279 if k == procType {
280 hasProcType = true
281 continue
282 }
283 h = append(h, k)
284 }
285
286
287 if hasProcType {
288 if err := writeHeader(out, procType, b.Headers[procType]); err != nil {
289 return err
290 }
291 }
292
293 sort.Strings(h)
294 for _, k := range h {
295 if err := writeHeader(out, k, b.Headers[k]); err != nil {
296 return err
297 }
298 }
299 if _, err := out.Write(nl); err != nil {
300 return err
301 }
302 }
303
304 var breaker lineBreaker
305 breaker.out = out
306
307 b64 := base64.NewEncoder(base64.StdEncoding, &breaker)
308 if _, err := b64.Write(b.Bytes); err != nil {
309 return err
310 }
311 b64.Close()
312 breaker.Close()
313
314 if _, err := out.Write(pemEnd[1:]); err != nil {
315 return err
316 }
317 _, err := out.Write([]byte(b.Type + "-----\n"))
318 return err
319 }
320
321
322
323
324
325
326 func EncodeToMemory(b *Block) []byte {
327 var buf bytes.Buffer
328 if err := Encode(&buf, b); err != nil {
329 return nil
330 }
331 return buf.Bytes()
332 }
333
View as plain text