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