// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.
// Package pem implements the PEM data encoding, which originated in Privacy// Enhanced Mail. The most common use of PEM encoding today is in TLS keys and// certificates. See RFC 1421.
package pemimport ()// A Block represents a PEM encoded structure.//// The encoded form is:// -----BEGIN Type-----// Headers// base64-encoded Bytes// -----END Type-----// where Headers is a possibly empty sequence of Key: Value lines.typeBlockstruct { Type string// The type, taken from the preamble (i.e. "RSA PRIVATE KEY"). Headers map[string]string// Optional headers. Bytes []byte// The decoded bytes of the contents. Typically a DER encoded ASN.1 structure.}// getLine results the first \r\n or \n delineated line from the given byte// array. The line does not include trailing whitespace or the trailing new// line bytes. The remainder of the byte array (also not including the new line// bytes) is also returned and this will always be smaller than the original// argument.func ( []byte) (, []byte) { := bytes.IndexByte(, '\n')varintif < 0 { = len() = } else { = + 1if > 0 && [-1] == '\r' { -- } }returnbytes.TrimRight([0:], " \t"), [:]}// removeSpacesAndTabs returns a copy of its input with all spaces and tabs// removed, if there were any. Otherwise, the input is returned unchanged.//// The base64 decoder already skips newline characters, so we don't need to// filter them out here.func ( []byte) []byte {if !bytes.ContainsAny(, " \t") {// Fast path; most base64 data within PEM contains newlines, but // no spaces nor tabs. Skip the extra alloc and work.return } := make([]byte, len()) := 0for , := range {if == ' ' || == '\t' {continue } [] = ++ }return [0:]}varpemStart = []byte("\n-----BEGIN ")varpemEnd = []byte("\n-----END ")varpemEndOfLine = []byte("-----")// Decode will find the next PEM formatted block (certificate, private key// etc) in the input. It returns that block and the remainder of the input. If// no PEM data is found, p is nil and the whole of the input is returned in// rest.func ( []byte) ( *Block, []byte) {// pemStart begins with a newline. However, at the very beginning of // the byte array, we'll accept the start string without it. = ifbytes.HasPrefix(, pemStart[1:]) { = [len(pemStart)-1 : len()] } elseif := bytes.Index(, pemStart); >= 0 { = [+len(pemStart) : len()] } else {returnnil, } , := getLine()if !bytes.HasSuffix(, pemEndOfLine) {returndecodeError(, ) } = [0 : len()-len(pemEndOfLine)] = &Block{Headers: make(map[string]string),Type: string(), }for {// This loop terminates because getLine's second result is // always smaller than its argument.iflen() == 0 {returnnil, } , := getLine() := bytes.IndexByte(, ':')if == -1 {break }// TODO(agl): need to cope with values that spread across lines. , := [:], [+1:] = bytes.TrimSpace() = bytes.TrimSpace() .Headers[string()] = string() = }var , int// If there were no headers, the END line might occur // immediately, without a leading newline.iflen(.Headers) == 0 && bytes.HasPrefix(, pemEnd[1:]) { = 0 = len(pemEnd) - 1 } else { = bytes.Index(, pemEnd) = + len(pemEnd) }if < 0 {returndecodeError(, ) }// After the "-----" of the ending line, there should be the same type // and then a final five dashes. := [:] := len() + len(pemEndOfLine)iflen() < {returndecodeError(, ) } := [:] = [:]if !bytes.HasPrefix(, ) || !bytes.HasSuffix(, pemEndOfLine) {returndecodeError(, ) }// The line must end with only whitespace.if , := getLine(); len() != 0 {returndecodeError(, ) } := removeSpacesAndTabs([:]) .Bytes = make([]byte, base64.StdEncoding.DecodedLen(len())) , := base64.StdEncoding.Decode(.Bytes, )if != nil {returndecodeError(, ) } .Bytes = .Bytes[:]// the -1 is because we might have only matched pemEnd without the // leading newline if the PEM block was empty. _, = getLine([+len(pemEnd)-1:])return}func (, []byte) (*Block, []byte) {// If we get here then we have rejected a likely looking, but // ultimately invalid PEM block. We need to start over from a new // position. We have consumed the preamble line and will have consumed // any lines which could be header lines. However, a valid preamble // line is not a valid header line, therefore we cannot have consumed // the preamble line for the any subsequent block. Thus, we will always // find any valid block, no matter what bytes precede it. // // For example, if the input is // // -----BEGIN MALFORMED BLOCK----- // junk that may look like header lines // or data lines, but no END line // // -----BEGIN ACTUAL BLOCK----- // realdata // -----END ACTUAL BLOCK----- // // we've failed to parse using the first BEGIN line // and now will try again, using the second BEGIN line. , := Decode()if == nil { = }return , }constpemLineLength = 64typelineBreakerstruct { line [pemLineLength]byte used int out io.Writer}varnl = []byte{'\n'}func ( *lineBreaker) ( []byte) ( int, error) {if .used+len() < pemLineLength {copy(.line[.used:], ) .used += len()returnlen(), nil } , = .out.Write(.line[0:.used])if != nil {return } := pemLineLength - .used .used = 0 , = .out.Write([0:])if != nil {return } , = .out.Write(nl)if != nil {return }return .([:])}func ( *lineBreaker) () ( error) {if .used > 0 { _, = .out.Write(.line[0:.used])if != nil {return } _, = .out.Write(nl) }return}func ( io.Writer, , string) error { , := .Write([]byte( + ": " + + "\n"))return}// Encode writes the PEM encoding of b to out.func ( io.Writer, *Block) error {// Check for invalid block before writing any output.for := range .Headers {ifstrings.Contains(, ":") {returnerrors.New("pem: cannot encode a header key that contains a colon") } }// All errors below are relayed from underlying io.Writer, // so it is now safe to write data.if , := .Write(pemStart[1:]); != nil {return }if , := .Write([]byte(.Type + "-----\n")); != nil {return }iflen(.Headers) > 0 {const = "Proc-Type" := make([]string, 0, len(.Headers)) := falsefor := range .Headers {if == { = truecontinue } = append(, ) }// The Proc-Type header must be written first. // See RFC 1421, section 4.6.1.1if {if := writeHeader(, , .Headers[]); != nil {return } }// For consistency of output, write other headers sorted by key.sort.Strings()for , := range {if := writeHeader(, , .Headers[]); != nil {return } }if , := .Write(nl); != nil {return } }varlineBreaker .out = := base64.NewEncoder(base64.StdEncoding, &)if , := .Write(.Bytes); != nil {return } .Close() .Close()if , := .Write(pemEnd[1:]); != nil {return } , := .Write([]byte(.Type + "-----\n"))return}// EncodeToMemory returns the PEM encoding of b.//// If b has invalid headers and cannot be encoded,// EncodeToMemory returns nil. If it is important to// report details about this error case, use Encode instead.func ( *Block) []byte {varbytes.Bufferif := Encode(&, ); != nil {returnnil }return .Bytes()}