[インデックス 12887] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/pem
パッケージにおける、PEM (Privacy-Enhanced Mail) エンコードされたデータのヘッダー行末尾に存在するスペースやタブを適切に無視するように修正するものです。具体的には、getLine
関数がヘッダー行を解析する際に、行末の空白文字(スペースとタブ)をトリムする処理が追加されました。これにより、一部のPEMエンコードされたデータが正しくデコードされない問題が解決されます。
コミット
commit 55af51d5c0f5e2bbe80ae6dd0df6aed48e6ebd09
Author: Adam Langley <agl@golang.org>
Date: Thu Apr 12 12:33:52 2012 -0400
encoding/pem: ignore spaces and tabs at the end of header lines.
Fixes #3502.
R=bradfitz
CC=golang-dev
https://golang.org/cl/6011046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/55af51d5c0f5e2bbe80ae6dd0df6aed48e6ebd09
元コミット内容
encoding/pem: ignore spaces and tabs at the end of header lines.
Fixes #3502.
R=bradfitz
CC=golang-dev
https://golang.org/cl/6011046
変更の背景
この変更は、Go言語の encoding/pem
パッケージが、PEM形式のデータブロックのヘッダー行(例: -----BEGIN CERTIFICATE-----
)の末尾に意図しないスペースやタブが存在する場合に、そのデータを正しく解析できないというバグ(Issue #3502)を修正するために行われました。
PEM形式は、X.509証明書、RSA秘密鍵、PKCS#7データなど、様々なバイナリデータをASCII形式で表現するための標準的な方法です。通常、PEMブロックは -----BEGIN <LABEL>-----
で始まり、-----END <LABEL>-----
で終わります。これらのヘッダー行は厳密なフォーマットが期待されますが、一部のシステムやツールが生成するPEMデータでは、これらのヘッダー行の末尾に余分な空白文字(スペースやタブ)が付加されることがありました。
Goの encoding/pem
パッケージは、これらの余分な空白文字を許容するように設計されていなかったため、そのような不正なフォーマットのPEMデータを読み込もうとすると、解析エラーが発生し、アプリケーションが証明書や鍵をロードできないなどの問題を引き起こしていました。このコミットは、このような実世界のPEMデータの多様性に対応し、より堅牢なパーサーを提供することを目的としています。
前提知識の解説
PEM (Privacy-Enhanced Mail) 形式
PEMは、バイナリデータをASCIIテキスト形式で表現するためのエンコーディングスキームです。主に公開鍵暗号やデジタル証明書、秘密鍵などのデータを安全に転送・保存するために使用されます。PEM形式のデータは、以下のような構造を持っています。
-----BEGIN <LABEL>-----
<Base64 encoded data>
-----END <LABEL>-----
- ヘッダー行:
-----BEGIN <LABEL>-----
で始まり、<LABEL>
はデータの種類(例:CERTIFICATE
,RSA PRIVATE KEY
)を示します。 - フッター行:
-----END <LABEL>-----
で終わり、ヘッダー行のラベルと一致します。 - Base64エンコードされたデータ: ヘッダー行とフッター行の間に、実際のバイナリデータがBase64エンコードされて格納されます。通常、このデータは64文字ごとに改行されます。
PEM形式の仕様では、ヘッダー行とフッター行の末尾に空白文字が存在することは想定されていませんが、実装によっては誤って空白文字が付加されるケースが存在します。
Go言語の encoding/pem
パッケージ
Go言語の標準ライブラリ encoding/pem
パッケージは、PEM形式のデータをエンコードおよびデコードするための機能を提供します。このパッケージは、主に Encode
関数と Decode
関数を提供し、Block
構造体を用いてPEMブロックのデータを表現します。
pem.Decode(data []byte) (*pem.Block, []byte)
: PEMエンコードされたバイトスライスを解析し、最初のPEMブロックと残りのデータを返します。pem.Encode(w io.Writer, b *pem.Block) error
:pem.Block
構造体をPEM形式で指定されたio.Writer
に書き込みます。
このパッケージは、TLS/SSL証明書やSSH鍵の処理など、暗号化関連の多くのGoアプリケーションで利用されています。
bytes.TrimRight
関数
bytes.TrimRight
はGo言語の bytes
パッケージに含まれる関数で、バイトスライスの末尾から指定された文字セットに含まれる文字をすべて削除します。
func TrimRight(s []byte, cutset string) []byte
この関数は、文字列の末尾から特定の文字(cutset
で指定)を削除する際に非常に便利です。今回のケースでは、ヘッダー行の末尾からスペースとタブを削除するために使用されています。
技術的詳細
このコミットの技術的な核心は、src/pkg/encoding/pem/pem.go
ファイル内の getLine
関数の修正にあります。
getLine
関数は、PEMデータから1行を読み取り、その行と残りのデータを返すユーティリティ関数です。元の実装では、行の末尾にある改行文字 (\r\n
または \n
) を削除する処理は含まれていましたが、それ以外の空白文字(スペースやタブ)を削除する処理は含まれていませんでした。
修正前:
func getLine(data []byte) (line, rest []byte) {
i := bytes.Index(data, []byte{'\n'})
var j int
if i < 0 {
i = len(data)
j = i
} else {
j = i + 1
if i > 0 && data[i-1] == '\r' {
i--
}
}
return data[0:i], data[j:]
}
このコードでは、data[0:i]
が改行文字を除いた行の内容を返しますが、もし元の data
の i
の位置の直前にスペースやタブがあった場合、それらは line
に含まれたままになります。
修正後:
func getLine(data []byte) (line, rest []byte) {
i := bytes.Index(data, []byte{'\n'})
var j int
if i < 0 {
i = len(data)
j = i
} else {
j = i + 1
if i > 0 && data[i-1] == '\r' {
i--
}
}
return bytes.TrimRight(data[0:i], " \t"), data[j:]
}
変更点として、return data[0:i], data[j:]
が return bytes.TrimRight(data[0:i], " \t"), data[j:]
に変更されました。
これにより、data[0:i]
で抽出された行の内容が bytes.TrimRight
関数に渡され、その行の末尾からすべてのスペース (
) とタブ (\t
) が削除されたクリーンな行が返されるようになりました。
この変更により、encoding/pem
パッケージは、ヘッダー行の末尾に余分な空白文字が含まれていても、それを無視して正しくPEMブロックを解析できるようになりました。これは、異なるシステムやライブラリによって生成された、わずかに非標準的なPEMデータとの互換性を向上させる上で非常に重要です。
また、src/pkg/encoding/pem/pem_test.go
には、この修正が正しく機能することを確認するためのテストケースが追加されています。具体的には、-----BEGIN CERTIFICATE-----
や -----BEGIN RSA PRIVATE KEY----- \t
のように、ヘッダー行の末尾にスペースやタブを含むPEMブロックのテストデータが追加され、これらが正しくデコードされることが検証されています。
コアとなるコードの変更箇所
src/pkg/encoding/pem/pem.go
--- a/src/pkg/encoding/pem/pem.go
+++ b/src/pkg/encoding/pem/pem.go
@@ -28,9 +28,10 @@ type Block struct {
}
// getLine results the first \r\n or \n delineated line from the given byte
-// array. The line does not include the \r\n or \n. 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.
+// 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(data []byte) (line, rest []byte) {
i := bytes.Index(data, []byte{'\n'})
var j int
@@ -43,7 +44,7 @@ func getLine(data []byte) (line, rest []byte) {
i--
}
}
- return data[0:i], data[j:]
+ return bytes.TrimRight(data[0:i], " \t"), data[j:]
}
// removeWhitespace returns a copy of its input with all spaces, tab and
src/pkg/encoding/pem/pem_test.go
--- a/src/pkg/encoding/pem/pem_test.go
+++ b/src/pkg/encoding/pem/pem_test.go
@@ -127,13 +127,13 @@ Certificate chain
-----BEGIN CERTIFICATE-----
testing
-----BEGIN CERTIFICATE-----
------BEGIN CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
MIID6TCCA1ICAQEwDQYJKoZIhvcNAQEFBQAwgYsxCzAJBgNVBAYTAlVTMRMwEQYD
VQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRQwEgYDVQQK
-EwtHb29nbGUgSW5jLjEMMAoGA1UECxMDRW5nMQwwCgYDVQQDEwNhZ2wxHTAbBgkq
-hkiG9w0BCQEWDmFnbEBnb29nbGUuY29tMB4XDTA5MDkwOTIyMDU0M1oXDTEwMDkw
-OTIyMDU0M1owajELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAf
-BgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEjMCEGA1UEAxMaZXVyb3Bh
+EwtHb29nbGUgSW5jLjEMMAoGA1UECxMDRW5nMQwwCgYDVQQDEwNhZ2wxHTAbBgkq
+hkiG9w0BCQEWDmFnbEBnb29nbGUuY29tMB4XDTA5MDkwOTIyMDU0M1oXDTEwMDkw
+OTIyMDU0M1owajELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAf
+BgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEjMCEGA1UEAxMaZXVyb3Bh
LnNmby5jb3JwLmdvb2dsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQC6pgYt7/EibBDumASF+S0qvqdL/f/nouJw2T1Qc8GmXF/iiUcrsgzh/Fd8
pDhz/T96Qg9IyR4ztuc2MXrmPra+zAuSf5bevFReSqvpIt8Duv0HbDbcqs/XKPfB
@@ -149,15 +149,15 @@ Pomjn71GNTtDeWAXibjCgdL6iHACCF6Htbl0zGlG0OAK+bdn0QIDAQABMA0GCSqG
SIb3DQEBBQUAA4GBAOKnQDtqBV24vVqvesL5dnmyFpFPXBn3WdFfwD6DzEb21UVG
5krmJiu+ViipORJPGMkgoL6BjU21XI95VQbun5P8vvg8Z+FnFsvRFY3e1CCzAVQY
ZsUkLw2I7zI/dNlWdB8Xp7v+3w9sX5N3J/WuJ1KOO5m26kRlHQo7EzT3974g
------END CERTIFICATE-----\n
+-----END CERTIFICATE-----
1 s:/C=ZA/O=Ca Inc./CN=CA Inc
------BEGIN RSA PRIVATE KEY-----
-Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-EDE3-CBC,80C7C7A09690757A
-\n
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,80C7C7A09690757A
+
eQp5ZkH6CyHBz7BZfUPxyLCCmftsBJ7HlqGb8Ld21cSwnzWZ4/SIlhyrUtsfw7VR
-2TTwA+odo9ex7GdxOTaH8oZFumIRoiEjHsk8U7Bhntp+ekkPP79xunnN7hb7hkhr
+2TTwA+odo9ex7GdxOTaH8oZFumIRoiEjHsk8U7Bhntp+ekkPP79xunnN7hb7hkhr
yGDQZgA7s2cQHQ71v3gwT2BACAft26jCjbM1wgNzBnJ8M0Rzn68YWqaPtdBu8qb/\n
zVR5JB1mnqvTSbFsfF5yMc6o2WQ9jJCl6KypnMl+BpL+dlvdjYVK4l9lYsB1Hs3d\n
+zDBbWxos818zzhS8/y6eIfiSG27cqrbhURbmgiSfDXjncK4m/pLcQ7mmBL6mFOr\n
コアとなるコードの解説
このコミットの核となる変更は、src/pkg/encoding/pem/pem.go
ファイル内の getLine
関数にあります。
getLine
関数は、PEMブロックの解析において、-----BEGIN ...-----
や -----END ...-----
といったヘッダー/フッター行、あるいはBase64エンコードされたデータ行を1行ずつ読み込む役割を担っています。
変更前は、この関数は行の末尾にある改行コード (\r\n
または \n
) を適切に処理していましたが、改行コードの直前に存在する可能性のあるスペースやタブといった「末尾の空白文字」は考慮していませんでした。そのため、もし入力されたPEMデータが -----BEGIN CERTIFICATE-----
のようにヘッダー行の末尾にスペースを含んでいた場合、getLine
関数はそのスペースを含んだままの行を返していました。
この「末尾の空白文字」が問題となるのは、encoding/pem
パッケージがこれらの行を内部的に処理する際に、厳密なフォーマットを期待しているためです。例えば、-----BEGIN CERTIFICATE-----
という文字列と -----BEGIN CERTIFICATE-----
という文字列は、Goの文字列比較では異なるものとして扱われます。これにより、ヘッダーのラベルが正しく認識されず、PEMブロックのデコードに失敗する原因となっていました。
修正では、bytes.TrimRight(data[0:i], " \t")
が導入されました。
data[0:i]
は、改行コードを除いた現在の行のバイトスライスです。bytes.TrimRight
関数は、このバイトスライスの末尾から、第2引数で指定された文字セット(この場合はスペース\t
)に含まれる文字をすべて削除します。
この変更により、getLine
関数は、ヘッダー行の末尾に余分なスペースやタブが存在しても、それらを自動的に取り除き、クリーンなヘッダー行を返すようになりました。これにより、encoding/pem
パッケージは、より多様な(わずかに非標準的な)PEMデータソースからの入力を許容し、堅牢性が向上しました。
pem_test.go
の変更は、この修正が意図通りに機能することを検証するためのものです。テストデータに意図的に末尾に空白文字を含むPEMヘッダーを追加し、それらが正しく解析されることを確認しています。これは、回帰テストとしても機能し、将来の変更がこの修正を壊さないことを保証します。
関連リンク
- Go Issue #3502: https://code.google.com/p/go/issues/detail?id=3502 (古いGoogle Codeのリンクですが、当時のIssueトラッカーです)
- Go CL 6011046: https://golang.org/cl/6011046 (Goの変更リストへのリンク)
参考にした情報源リンク
- Go
encoding/pem
package documentation: https://pkg.go.dev/encoding/pem - Go
bytes
package documentation: https://pkg.go.dev/bytes - RFC 1421 (PEM Part I: Message Encryption and Authentication Procedures): https://datatracker.ietf.org/doc/html/rfc1421 (PEM形式の基本的な仕様)
- Base64 encoding: https://en.wikipedia.org/wiki/Base64
- X.509 certificate: https://en.wikipedia.org/wiki/X.509
- RSA (cryptosystem): https://en.wikipedia.org/wiki/RSA_(cryptosystem)
- PKCS #7: https://en.wikipedia.org/wiki/PKCS_7
- Go言語のIssueトラッカー (現在のGitHub): https://github.com/golang/go/issues
- Go言語の変更リスト (Gerrit): https://go-review.googlesource.com/