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