src/pkg/encoding/pem/pem.go - The Go Programming Language

Golang

Source file src/pkg/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		"io"
    14	)
    15	
    16	// A Block represents a PEM encoded structure.
    17	//
    18	// The encoded form is:
    19	//    -----BEGIN Type-----
    20	//    Headers
    21	//    base64-encoded Bytes
    22	//    -----END Type-----
    23	// where Headers is a possibly empty sequence of Key: Value lines.
    24	type Block struct {
    25		Type    string            // The type, taken from the preamble (i.e. "RSA PRIVATE KEY").
    26		Headers map[string]string // Optional headers.
    27		Bytes   []byte            // The decoded bytes of the contents. Typically a DER encoded ASN.1 structure.
    28	}
    29	
    30	// getLine results the first \r\n or \n delineated line from the given byte
    31	// array. The line does not include trailing whitespace or the trailing new
    32	// line bytes. The remainder of the byte array (also not including the new line
    33	// bytes) is also returned and this will always be smaller than the original
    34	// argument.
    35	func getLine(data []byte) (line, rest []byte) {
    36		i := bytes.Index(data, []byte{'\n'})
    37		var j int
    38		if i < 0 {
    39			i = len(data)
    40			j = i
    41		} else {
    42			j = i + 1
    43			if i > 0 && data[i-1] == '\r' {
    44				i--
    45			}
    46		}
    47		return bytes.TrimRight(data[0:i], " \t"), data[j:]
    48	}
    49	
    50	// removeWhitespace returns a copy of its input with all spaces, tab and
    51	// newline characters removed.
    52	func removeWhitespace(data []byte) []byte {
    53		result := make([]byte, len(data))
    54		n := 0
    55	
    56		for _, b := range data {
    57			if b == ' ' || b == '\t' || b == '\r' || b == '\n' {
    58				continue
    59			}
    60			result[n] = b
    61			n++
    62		}
    63	
    64		return result[0:n]
    65	}
    66	
    67	var pemStart = []byte("\n-----BEGIN ")
    68	var pemEnd = []byte("\n-----END ")
    69	var pemEndOfLine = []byte("-----")
    70	
    71	// Decode will find the next PEM formatted block (certificate, private key
    72	// etc) in the input. It returns that block and the remainder of the input. If
    73	// no PEM data is found, p is nil and the whole of the input is returned in
    74	// rest.
    75	func Decode(data []byte) (p *Block, rest []byte) {
    76		// pemStart begins with a newline. However, at the very beginning of
    77		// the byte array, we'll accept the start string without it.
    78		rest = data
    79		if bytes.HasPrefix(data, pemStart[1:]) {
    80			rest = rest[len(pemStart)-1 : len(data)]
    81		} else if i := bytes.Index(data, pemStart); i >= 0 {
    82			rest = rest[i+len(pemStart) : len(data)]
    83		} else {
    84			return nil, data
    85		}
    86	
    87		typeLine, rest := getLine(rest)
    88		if !bytes.HasSuffix(typeLine, pemEndOfLine) {
    89			return decodeError(data, rest)
    90		}
    91		typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
    92	
    93		p = &Block{
    94			Headers: make(map[string]string),
    95			Type:    string(typeLine),
    96		}
    97	
    98		for {
    99			// This loop terminates because getLine's second result is
   100			// always smaller than its argument.
   101			if len(rest) == 0 {
   102				return nil, data
   103			}
   104			line, next := getLine(rest)
   105	
   106			i := bytes.Index(line, []byte{':'})
   107			if i == -1 {
   108				break
   109			}
   110	
   111			// TODO(agl): need to cope with values that spread across lines.
   112			key, val := line[0:i], line[i+1:]
   113			key = bytes.TrimSpace(key)
   114			val = bytes.TrimSpace(val)
   115			p.Headers[string(key)] = string(val)
   116			rest = next
   117		}
   118	
   119		i := bytes.Index(rest, pemEnd)
   120		if i < 0 {
   121			return decodeError(data, rest)
   122		}
   123		base64Data := removeWhitespace(rest[0:i])
   124	
   125		p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
   126		n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
   127		if err != nil {
   128			return decodeError(data, rest)
   129		}
   130		p.Bytes = p.Bytes[0:n]
   131	
   132		_, rest = getLine(rest[i+len(pemEnd):])
   133	
   134		return
   135	}
   136	
   137	func decodeError(data, rest []byte) (*Block, []byte) {
   138		// If we get here then we have rejected a likely looking, but
   139		// ultimately invalid PEM block. We need to start over from a new
   140		// position.  We have consumed the preamble line and will have consumed
   141		// any lines which could be header lines. However, a valid preamble
   142		// line is not a valid header line, therefore we cannot have consumed
   143		// the preamble line for the any subsequent block. Thus, we will always
   144		// find any valid block, no matter what bytes precede it.
   145		//
   146		// For example, if the input is
   147		//
   148		//    -----BEGIN MALFORMED BLOCK-----
   149		//    junk that may look like header lines
   150		//   or data lines, but no END line
   151		//
   152		//    -----BEGIN ACTUAL BLOCK-----
   153		//    realdata
   154		//    -----END ACTUAL BLOCK-----
   155		//
   156		// we've failed to parse using the first BEGIN line
   157		// and now will try again, using the second BEGIN line.
   158		p, rest := Decode(rest)
   159		if p == nil {
   160			rest = data
   161		}
   162		return p, rest
   163	}
   164	
   165	const pemLineLength = 64
   166	
   167	type lineBreaker struct {
   168		line [pemLineLength]byte
   169		used int
   170		out  io.Writer
   171	}
   172	
   173	func (l *lineBreaker) Write(b []byte) (n int, err error) {
   174		if l.used+len(b) < pemLineLength {
   175			copy(l.line[l.used:], b)
   176			l.used += len(b)
   177			return len(b), nil
   178		}
   179	
   180		n, err = l.out.Write(l.line[0:l.used])
   181		if err != nil {
   182			return
   183		}
   184		excess := pemLineLength - l.used
   185		l.used = 0
   186	
   187		n, err = l.out.Write(b[0:excess])
   188		if err != nil {
   189			return
   190		}
   191	
   192		n, err = l.out.Write([]byte{'\n'})
   193		if err != nil {
   194			return
   195		}
   196	
   197		return l.Write(b[excess:])
   198	}
   199	
   200	func (l *lineBreaker) Close() (err error) {
   201		if l.used > 0 {
   202			_, err = l.out.Write(l.line[0:l.used])
   203			if err != nil {
   204				return
   205			}
   206			_, err = l.out.Write([]byte{'\n'})
   207		}
   208	
   209		return
   210	}
   211	
   212	func Encode(out io.Writer, b *Block) (err error) {
   213		_, err = out.Write(pemStart[1:])
   214		if err != nil {
   215			return
   216		}
   217		_, err = out.Write([]byte(b.Type + "-----\n"))
   218		if err != nil {
   219			return
   220		}
   221	
   222		if len(b.Headers) > 0 {
   223			for k, v := range b.Headers {
   224				_, err = out.Write([]byte(k + ": " + v + "\n"))
   225				if err != nil {
   226					return
   227				}
   228			}
   229			_, err = out.Write([]byte{'\n'})
   230			if err != nil {
   231				return
   232			}
   233		}
   234	
   235		var breaker lineBreaker
   236		breaker.out = out
   237	
   238		b64 := base64.NewEncoder(base64.StdEncoding, &breaker)
   239		_, err = b64.Write(b.Bytes)
   240		if err != nil {
   241			return
   242		}
   243		b64.Close()
   244		breaker.Close()
   245	
   246		_, err = out.Write(pemEnd[1:])
   247		if err != nil {
   248			return
   249		}
   250		_, err = out.Write([]byte(b.Type + "-----\n"))
   251		return
   252	}
   253	
   254	func EncodeToMemory(b *Block) []byte {
   255		var buf bytes.Buffer
   256		Encode(&buf, b)
   257		return buf.Bytes()
   258	}