...

Source file src/encoding/pem/pem.go

Documentation: encoding/pem

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

View as plain text