Source file src/encoding/pem/pem.go

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

View as plain text