blob: 409f07fb0b505edb603db48624abeb5d2bb7919a [file] [log] [blame]
Adam Langleyfd74a832009-10-21 17:53:50 -07001// 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// This package 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.
8package pem
9
10import (
11 "bytes";
12 "encoding/base64";
13 "strings";
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.
24type 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 the \r\n or \n. The remainder of the byte
32// array (also not including the new line bytes) is also returned and this will
33// always be smaller than the original argument.
34func getLine(data []byte) (line, rest []byte) {
35 i := bytes.Index(data, []byte{'\n'});
36 var j int;
37 if i < 0 {
38 i = len(data);
39 j = i;
40 } else {
Robert Griesemerbaba2922009-11-09 21:13:17 -080041 j = i + 1;
Adam Langleyfd74a832009-10-21 17:53:50 -070042 if i > 0 && data[i-1] == '\r' {
Robert Griesemer40621d52009-11-09 12:07:39 -080043 i--
Adam Langleyfd74a832009-10-21 17:53:50 -070044 }
45 }
46 return data[0:i], data[j:len(data)];
47}
48
49// removeWhitespace returns a copy of its input with all spaces, tab and
50// newline characters removed.
51func removeWhitespace(data []byte) []byte {
52 result := make([]byte, len(data));
53 n := 0;
54
55 for _, b := range data {
56 if b == ' ' || b == '\t' || b == '\r' || b == '\n' {
Robert Griesemer40621d52009-11-09 12:07:39 -080057 continue
Adam Langleyfd74a832009-10-21 17:53:50 -070058 }
59 result[n] = b;
60 n++;
61 }
62
63 return result[0:n];
64}
65
66var pemStart = strings.Bytes("\n-----BEGIN ")
67var pemEnd = strings.Bytes("\n-----END ")
68var pemEndOfLine = strings.Bytes("-----")
69
70// Decode will find the next PEM formatted block (certificate, private key
71// etc) in the input. It returns that block and the remainder of the input. If
Adam Langley7d680932009-10-21 19:47:52 -070072// no PEM data is found, p is nil and the whole of the input is returned in
73// rest.
Adam Langleyfd74a832009-10-21 17:53:50 -070074func Decode(data []byte) (p *Block, rest []byte) {
75 // pemStart begins with a newline. However, at the very beginning of
76 // the byte array, we'll accept the start string without it.
77 rest = data;
78 if bytes.HasPrefix(data, pemStart[1:len(pemStart)]) {
Robert Griesemer40621d52009-11-09 12:07:39 -080079 rest = rest[len(pemStart)-1 : len(data)]
Adam Langleyfd74a832009-10-21 17:53:50 -070080 } else if i := bytes.Index(data, pemStart); i >= 0 {
Robert Griesemer40621d52009-11-09 12:07:39 -080081 rest = rest[i+len(pemStart) : len(data)]
Adam Langleyfd74a832009-10-21 17:53:50 -070082 } else {
Robert Griesemer40621d52009-11-09 12:07:39 -080083 return nil, data
Adam Langleyfd74a832009-10-21 17:53:50 -070084 }
85
86 typeLine, rest := getLine(rest);
87 if !bytes.HasSuffix(typeLine, pemEndOfLine) {
Robert Griesemer40621d52009-11-09 12:07:39 -080088 goto Error
Adam Langleyfd74a832009-10-21 17:53:50 -070089 }
90 typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)];
91
92 p = &Block{
93 Headers: make(map[string]string),
94 Type: string(typeLine),
95 };
96
97 for {
98 // This loop terminates because getLine's second result is
99 // always smaller than it's argument.
100 if len(rest) == 0 {
Robert Griesemer40621d52009-11-09 12:07:39 -0800101 return nil, data
Adam Langleyfd74a832009-10-21 17:53:50 -0700102 }
103 line, next := getLine(rest);
104
105 i := bytes.Index(line, []byte{':'});
106 if i == -1 {
Robert Griesemer40621d52009-11-09 12:07:39 -0800107 break
Adam Langleyfd74a832009-10-21 17:53:50 -0700108 }
109
110 // TODO(agl): need to cope with values that spread across lines.
Robert Griesemerbaba2922009-11-09 21:13:17 -0800111 key, val := line[0:i], line[i+1:len(line)];
Adam Langleyfd74a832009-10-21 17:53:50 -0700112 key = bytes.TrimSpace(key);
113 val = bytes.TrimSpace(val);
114 p.Headers[string(key)] = string(val);
115 rest = next;
116 }
117
118 i := bytes.Index(rest, pemEnd);
119 if i < 0 {
Robert Griesemer40621d52009-11-09 12:07:39 -0800120 goto Error
Adam Langleyfd74a832009-10-21 17:53:50 -0700121 }
122 base64Data := removeWhitespace(rest[0:i]);
123
124 p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)));
Adam Langley93253a82009-11-03 17:32:08 -0800125 n, err := base64.StdEncoding.Decode(p.Bytes, base64Data);
Adam Langleyfd74a832009-10-21 17:53:50 -0700126 if err != nil {
Robert Griesemer40621d52009-11-09 12:07:39 -0800127 goto Error
Adam Langleyfd74a832009-10-21 17:53:50 -0700128 }
129 p.Bytes = p.Bytes[0:n];
130
131 _, rest = getLine(rest[i+len(pemEnd) : len(rest)]);
132
133 return;
134
135Error:
136 // If we get here then we have rejected a likely looking, but
137 // ultimately invalid PEM block. We need to start over from a new
138 // position. We have consumed the preamble line and will have consumed
139 // any lines which could be header lines. However, a valid preamble
140 // line is not a valid header line, therefore we cannot have consumed
141 // the preamble line for the any subsequent block. Thus, we will always
142 // find any valid block, no matter what bytes preceed it.
143 //
144 // For example, if the input is
145 //
146 // -----BEGIN MALFORMED BLOCK-----
147 // junk that may look like header lines
148 // or data lines, but no END line
149 //
150 // -----BEGIN ACTUAL BLOCK-----
151 // realdata
152 // -----END ACTUAL BLOCK-----
153 //
154 // we've failed to parse using the first BEGIN line
155 // and now will try again, using the second BEGIN line.
156 p, rest = Decode(rest);
157 if p == nil {
Robert Griesemer40621d52009-11-09 12:07:39 -0800158 rest = data
Adam Langleyfd74a832009-10-21 17:53:50 -0700159 }
160 return;
161}