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