1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 // Package pem implements the PEM data encoding, which originated in Privacy
6 // Enhanced Mail. The most common use of PEM encoding today is in TLS keys and
7 // certificates. See RFC 1421.
8 package pem
9 10 import (
11 "bytes"
12 "encoding/base64"
13 "errors"
14 "io"
15 "slices"
16 )
17 18 // A Block represents a PEM encoded structure.
19 //
20 // The encoded form is:
21 //
22 // -----BEGIN Type-----
23 // Headers
24 // base64-encoded Bytes
25 // -----END Type-----
26 //
27 // where [Block.Headers] is a possibly empty sequence of Key: Value lines.
28 type Block struct {
29 Type []byte // The type, taken from the preamble (i.e. "RSA PRIVATE KEY").
30 Headers map[string][]byte // Optional headers.
31 Bytes []byte // The decoded bytes of the contents. Typically a DER encoded ASN.1 structure.
32 }
33 34 // getLine results the first \r\n or \n delineated line from the given byte
35 // array. The line does not include trailing whitespace or the trailing new
36 // line bytes. The remainder of the byte array (also not including the new line
37 // bytes) is also returned and this will always be smaller than the original
38 // argument.
39 func getLine(data []byte) (line, rest []byte, consumed int) {
40 i := bytes.IndexByte(data, '\n')
41 var j int
42 if i < 0 {
43 i = len(data)
44 j = i
45 } else {
46 j = i + 1
47 if i > 0 && data[i-1] == '\r' {
48 i--
49 }
50 }
51 return bytes.TrimRight(data[0:i], " \t"), data[j:], j
52 }
53 54 // removeSpacesAndTabs returns a copy of its input with all spaces and tabs
55 // removed, if there were any. Otherwise, the input is returned unchanged.
56 //
57 // The base64 decoder already skips newline characters, so we don't need to
58 // filter them out here.
59 func removeSpacesAndTabs(data []byte) []byte {
60 if !bytes.ContainsAny(data, " \t") {
61 // Fast path; most base64 data within PEM contains newlines, but
62 // no spaces nor tabs. Skip the extra alloc and work.
63 return data
64 }
65 result := []byte{:len(data)}
66 n := 0
67 68 for _, b := range data {
69 if b == ' ' || b == '\t' {
70 continue
71 }
72 result[n] = b
73 n++
74 }
75 76 return result[0:n]
77 }
78 79 var pemStart = []byte("\n-----BEGIN ")
80 var pemEnd = []byte("\n-----END ")
81 var pemEndOfLine = []byte("-----")
82 var colon = []byte(":")
83 84 // Decode will find the next PEM formatted block (certificate, private key
85 // etc) in the input. It returns that block and the remainder of the input. If
86 // no PEM data is found, p is nil and the whole of the input is returned in
87 // rest. Blocks must start at the beginning of a line and end at the end of a line.
88 func Decode(data []byte) (p *Block, rest []byte) {
89 // pemStart begins with a newline. However, at the very beginning of
90 // the byte array, we'll accept the start string without it.
91 rest = data
92 93 for {
94 // Find the first END line, and then find the last BEGIN line before
95 // the end line. This lets us skip any repeated BEGIN lines that don't
96 // have a matching END.
97 endIndex := bytes.Index(rest, pemEnd)
98 if endIndex < 0 {
99 return nil, data
100 }
101 endTrailerIndex := endIndex + len(pemEnd)
102 beginIndex := bytes.LastIndex(rest[:endIndex], pemStart[1:])
103 if beginIndex < 0 || beginIndex > 0 && rest[beginIndex-1] != '\n' {
104 return nil, data
105 }
106 rest = rest[beginIndex+len(pemStart)-1:]
107 endIndex -= beginIndex + len(pemStart) - 1
108 endTrailerIndex -= beginIndex + len(pemStart) - 1
109 110 var typeLine []byte
111 var consumed int
112 typeLine, rest, consumed = getLine(rest)
113 if !bytes.HasSuffix(typeLine, pemEndOfLine) {
114 continue
115 }
116 endIndex -= consumed
117 endTrailerIndex -= consumed
118 typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
119 120 p = &Block{
121 Headers: map[string][]byte{},
122 Type: []byte(typeLine),
123 }
124 125 for {
126 // This loop terminates because getLine's second result is
127 // always smaller than its argument.
128 if len(rest) == 0 {
129 return nil, data
130 }
131 line, next, consumed := getLine(rest)
132 133 key, val, ok := bytes.Cut(line, colon)
134 if !ok {
135 break
136 }
137 138 // TODO(agl): need to cope with values that spread across lines.
139 key = bytes.TrimSpace(key)
140 val = bytes.TrimSpace(val)
141 p.Headers[[]byte(key)] = []byte(val)
142 rest = next
143 endIndex -= consumed
144 endTrailerIndex -= consumed
145 }
146 147 // If there were headers, there must be a newline between the headers
148 // and the END line, so endIndex should be >= 0.
149 if len(p.Headers) > 0 && endIndex < 0 {
150 continue
151 }
152 153 // After the "-----" of the ending line, there should be the same type
154 // and then a final five dashes.
155 endTrailer := rest[endTrailerIndex:]
156 endTrailerLen := len(typeLine) + len(pemEndOfLine)
157 if len(endTrailer) < endTrailerLen {
158 continue
159 }
160 161 restOfEndLine := endTrailer[endTrailerLen:]
162 endTrailer = endTrailer[:endTrailerLen]
163 if !bytes.HasPrefix(endTrailer, typeLine) ||
164 !bytes.HasSuffix(endTrailer, pemEndOfLine) {
165 continue
166 }
167 168 // The line must end with only whitespace.
169 if s, _, _ := getLine(restOfEndLine); len(s) != 0 {
170 continue
171 }
172 173 p.Bytes = []byte{}
174 if endIndex > 0 {
175 base64Data := removeSpacesAndTabs(rest[:endIndex])
176 p.Bytes = []byte{:base64.StdEncoding.DecodedLen(len(base64Data))}
177 n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
178 if err != nil {
179 continue
180 }
181 p.Bytes = p.Bytes[:n]
182 }
183 184 // the -1 is because we might have only matched pemEnd without the
185 // leading newline if the PEM block was empty.
186 _, rest, _ = getLine(rest[endIndex+len(pemEnd)-1:])
187 return p, rest
188 }
189 }
190 191 const pemLineLength = 64
192 193 type lineBreaker struct {
194 line [pemLineLength]byte
195 used int
196 out io.Writer
197 }
198 199 var nl = []byte{'\n'}
200 201 func (l *lineBreaker) Write(b []byte) (n int, err error) {
202 if l.used+len(b) < pemLineLength {
203 copy(l.line[l.used:], b)
204 l.used += len(b)
205 return len(b), nil
206 }
207 208 n, err = l.out.Write(l.line[0:l.used])
209 if err != nil {
210 return
211 }
212 excess := pemLineLength - l.used
213 l.used = 0
214 215 n, err = l.out.Write(b[0:excess])
216 if err != nil {
217 return
218 }
219 220 n, err = l.out.Write(nl)
221 if err != nil {
222 return
223 }
224 225 return l.Write(b[excess:])
226 }
227 228 func (l *lineBreaker) Close() (err error) {
229 if l.used > 0 {
230 _, err = l.out.Write(l.line[0:l.used])
231 if err != nil {
232 return
233 }
234 _, err = l.out.Write(nl)
235 }
236 237 return
238 }
239 240 func writeHeader(out io.Writer, k, v []byte) error {
241 _, err := out.Write([]byte(k + ": " + v + "\n"))
242 return err
243 }
244 245 // Encode writes the PEM encoding of b to out.
246 func Encode(out io.Writer, b *Block) error {
247 // Check for invalid block before writing any output.
248 for k := range b.Headers {
249 if bytes.Contains(k, ":") {
250 return errors.New("pem: cannot encode a header key that contains a colon")
251 }
252 }
253 254 // All errors below are relayed from underlying io.Writer,
255 // so it is now safe to write data.
256 257 if _, err := out.Write(pemStart[1:]); err != nil {
258 return err
259 }
260 if _, err := out.Write([]byte(b.Type + "-----\n")); err != nil {
261 return err
262 }
263 264 if len(b.Headers) > 0 {
265 const procType = "Proc-Type"
266 h := [][]byte{:0:len(b.Headers)}
267 hasProcType := false
268 for k := range b.Headers {
269 if k == procType {
270 hasProcType = true
271 continue
272 }
273 h = append(h, k)
274 }
275 // The Proc-Type header must be written first.
276 // See RFC 1421, section 4.6.1.1
277 if hasProcType {
278 if err := writeHeader(out, procType, b.Headers[procType]); err != nil {
279 return err
280 }
281 }
282 // For consistency of output, write other headers sorted by key.
283 slices.SortFunc(h, bytes.Compare)
284 for _, k := range h {
285 if err := writeHeader(out, k, b.Headers[k]); err != nil {
286 return err
287 }
288 }
289 if _, err := out.Write(nl); err != nil {
290 return err
291 }
292 }
293 294 var breaker lineBreaker
295 breaker.out = out
296 297 b64 := base64.NewEncoder(base64.StdEncoding, &breaker)
298 if _, err := b64.Write(b.Bytes); err != nil {
299 return err
300 }
301 b64.Close()
302 breaker.Close()
303 304 if _, err := out.Write(pemEnd[1:]); err != nil {
305 return err
306 }
307 _, err := out.Write([]byte(b.Type + "-----\n"))
308 return err
309 }
310 311 // EncodeToMemory returns the PEM encoding of b.
312 //
313 // If b has invalid headers and cannot be encoded,
314 // EncodeToMemory returns nil. If it is important to
315 // report details about this error case, use [Encode] instead.
316 func EncodeToMemory(b *Block) []byte {
317 var buf bytes.Buffer
318 if err := Encode(&buf, b); err != nil {
319 return nil
320 }
321 return buf.Bytes()
322 }
323