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