// 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 pem import ( ) // A Block represents a PEM encoded structure. // // The encoded form is: // // -----BEGIN Type----- // Headers // base64-encoded Bytes // -----END Type----- // // where [Block.Headers] is a possibly empty sequence of Key: Value lines. type Block struct { 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 getLine( []byte) (, []byte) { := bytes.IndexByte(, '\n') var int if < 0 { = len() = } else { = + 1 if > 0 && [-1] == '\r' { -- } } return bytes.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 removeSpacesAndTabs( []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()) := 0 for , := range { if == ' ' || == '\t' { continue } [] = ++ } return [0:] } var pemStart = []byte("\n-----BEGIN ") var pemEnd = []byte("\n-----END ") var pemEndOfLine = []byte("-----") var colon = []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. = for { if bytes.HasPrefix(, pemStart[1:]) { = [len(pemStart)-1:] } else if , , := bytes.Cut(, pemStart); { = } else { return nil, } var []byte , = getLine() if !bytes.HasSuffix(, pemEndOfLine) { continue } = [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. if len() == 0 { return nil, } , := getLine() , , := bytes.Cut(, colon) if ! { break } // TODO(agl): need to cope with values that spread across lines. = bytes.TrimSpace() = bytes.TrimSpace() .Headers[string()] = string() = } var , int // If there were no headers, the END line might occur // immediately, without a leading newline. if len(.Headers) == 0 && bytes.HasPrefix(, pemEnd[1:]) { = 0 = len(pemEnd) - 1 } else { = bytes.Index(, pemEnd) = + len(pemEnd) } if < 0 { continue } // After the "-----" of the ending line, there should be the same type // and then a final five dashes. := [:] := len() + len(pemEndOfLine) if len() < { continue } := [:] = [:] if !bytes.HasPrefix(, ) || !bytes.HasSuffix(, pemEndOfLine) { continue } // The line must end with only whitespace. if , := getLine(); len() != 0 { continue } := removeSpacesAndTabs([:]) .Bytes = make([]byte, base64.StdEncoding.DecodedLen(len())) , := base64.StdEncoding.Decode(.Bytes, ) if != nil { continue } .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 , } } const pemLineLength = 64 type lineBreaker struct { line [pemLineLength]byte used int out io.Writer } var nl = []byte{'\n'} func ( *lineBreaker) ( []byte) ( int, error) { if .used+len() < pemLineLength { copy(.line[.used:], ) .used += len() return len(), 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 writeHeader( 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 { if strings.Contains(, ":") { return errors.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 } if len(.Headers) > 0 { const = "Proc-Type" := make([]string, 0, len(.Headers)) := false for := range .Headers { if == { = true continue } = append(, ) } // The Proc-Type header must be written first. // See RFC 1421, section 4.6.1.1 if { if := writeHeader(, , .Headers[]); != nil { return } } // For consistency of output, write other headers sorted by key. slices.Sort() for , := range { if := writeHeader(, , .Headers[]); != nil { return } } if , := .Write(nl); != nil { return } } var lineBreaker .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 { var bytes.Buffer if := Encode(&, ); != nil { return nil } return .Bytes() }