[インデックス 10632] ファイルの概要
このコミットは、Go言語の実験的なSSHパッケージ(exp/ssh
)におけるクライアント認証テストのクリーンアップと機能拡張を目的としています。具体的には、テストコードの可読性と管理性を向上させ、RSAおよびDSA鍵ネゴシエーションのテストを追加し、さらにパッケージレベルの変数名が標準ライブラリのstrings
パッケージと衝突する問題を解消しています。
コミット
- コミットハッシュ:
bf59f081c16764633e072824fdc582a6ce9136db
- 作者: Dave Cheney dave@cheney.net
- コミット日時: 2011年12月6日 火曜日 18:13:20 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bf59f081c16764633e072824fdc582a6ce9136db
元コミット内容
exp/ssh: cleanup client auth tests
This CL cleans up the client auth tests, making the
individual test body more manageable.
Also, adds tests for rsa and dsa key negotiation.
Finally, remove the package level use of the variable
strings, which avoids conflicting with the strings pkg.
R=gustav.paul, agl, n13m3y3r, rsc
CC=golang-dev
https://golang.org/cl/5447049
変更の背景
このコミットの背景には、主に以下の3つの目的があります。
- テストコードの改善: 既存のクライアント認証テストは、個々のテストボディが肥大化しており、可読性や保守性が低い状態でした。これを改善し、より管理しやすい形にリファクタリングする必要がありました。
- 認証方式の網羅性向上: SSHプロトコルにおけるクライアント認証では、パスワード認証だけでなく、公開鍵認証が広く利用されます。特にRSAとDSAは主要な鍵アルゴリズムであり、これらの鍵ネゴシエーションが正しく機能するかを確認するためのテストが不足していました。
- 名前空間の衝突回避:
src/pkg/exp/ssh/common_test.go
ファイル内で、strings
という名前のパッケージレベル変数が定義されていました。これはGo標準ライブラリのstrings
パッケージと名前が衝突する可能性があり、将来的な問題や混乱を避けるために解消する必要がありました。
これらの課題に対処することで、exp/ssh
パッケージの品質と堅牢性を向上させることが目指されました。
前提知識の解説
SSH (Secure Shell)
SSHは、ネットワークを介してコンピュータを安全に操作するためのプロトコルです。主にリモートログインやファイル転送(SCP, SFTP)に利用されます。データの暗号化と認証により、盗聴や改ざんを防ぎます。
SSHクライアント認証
SSHクライアント認証は、クライアントがサーバーに対して自身の身元を証明するプロセスです。主な認証方式には以下のものがあります。
- パスワード認証: ユーザー名とパスワードを用いて認証します。
- 公開鍵認証: クライアントが秘密鍵を保持し、サーバーが対応する公開鍵を保持することで認証を行います。クライアントは秘密鍵で署名したデータをサーバーに送り、サーバーは公開鍵でその署名を検証します。これにより、パスワードをネットワーク上に流すことなく安全な認証が可能です。
公開鍵暗号方式
公開鍵暗号方式は、公開鍵と秘密鍵のペアを使用する暗号方式です。
- 公開鍵: 誰でも利用できる鍵で、データの暗号化や署名の検証に使用されます。
- 秘密鍵: 鍵の所有者のみが保持する鍵で、データの復号や署名の生成に使用されます。
SSHの公開鍵認証では、クライアントが秘密鍵で認証要求に署名し、サーバーがクライアントの公開鍵でその署名を検証します。
RSA (Rivest–Shamir–Adleman)
RSAは、最も広く使われている公開鍵暗号アルゴリズムの一つです。大きな素数の積を基盤とした数学的な困難性(素因数分解問題)を利用しています。SSHの公開鍵認証で一般的に使用されます。
DSA (Digital Signature Algorithm)
DSAは、デジタル署名に特化した公開鍵暗号アルゴリズムです。RSAと同様にSSHの公開鍵認証で使用されますが、RSAとは異なる数学的原理(離散対数問題)に基づいています。
PEM (Privacy-Enhanced Mail)
PEMは、公開鍵や秘密鍵、証明書などの暗号関連データをASCII形式でエンコードするための標準的なフォーマットです。-----BEGIN RSA PRIVATE KEY-----
や-----END RSA PRIVATE KEY-----
のようなヘッダーとフッターで囲まれたブロックが特徴です。
Go言語のcrypto
パッケージ
Go言語の標準ライブラリには、暗号化とハッシュ化のための豊富なパッケージ群が提供されています。
crypto/rsa
: RSA暗号アルゴリズムの実装を提供します。crypto/dsa
: DSA暗号アルゴリズムの実装を提供します。crypto/x509
: X.509証明書やPKCS#1形式の鍵のパース(解析)機能を提供します。crypto/rand
: 暗号学的に安全な乱数ジェネレータを提供します。鍵生成や署名プロセスで利用されます。encoding/pem
: PEM形式のエンコード/デコード機能を提供します。
技術的詳細
このコミットは、主にsrc/pkg/exp/ssh/client_auth_test.go
とsrc/pkg/exp/ssh/common_test.go
の2つのファイルに影響を与えています。
src/pkg/exp/ssh/client_auth_test.go
の変更点
- テスト用鍵の定数化と分離:
- 以前は
_pem
という単一のグローバル定数でサーバーの秘密鍵を保持していましたが、これをtestServerPrivateKey
にリネームし、さらにクライアント用の秘密鍵testClientPrivateKey
を新しく追加しました。これにより、テストの意図が明確になり、鍵の役割が分離されました。
- 以前は
keychain
構造体の汎用化:keychain
構造体のkeys
フィールドが[]*rsa.PrivateKey
から[]interface{}
に変更されました。これにより、RSA鍵だけでなくDSA鍵も保持できるようになり、複数の種類の鍵を扱うテストが可能になりました。Key
メソッドとSign
メソッドも、型アサーション(switch key := k.keys[i].(type)
)を用いてRSAとDSAの両方の鍵タイプに対応するように修正されました。これにより、異なるアルゴリズムの公開鍵認証をテストできるようになりました。
- テストヘルパー関数
newMockAuthServer
の導入:- 以前は各テスト関数内でサーバーのリスニング、接続の受け入れ、ハンドシェイクのロジックが重複して記述されていました。これを
newMockAuthServer
というヘルパー関数に集約しました。この関数はモックサーバーを起動し、そのアドレスを返します。これにより、個々のテスト関数が簡潔になり、テストのセットアップが容易になりました。 newMockAuthServer
は、サーバーが1つのハンドシェイクを処理した後に終了するように設計されており、テストの独立性と効率性を高めています。
- 以前は各テスト関数内でサーバーのリスニング、接続の受け入れ、ハンドシェイクのロジックが重複して記述されていました。これを
- テストの追加とリファクタリング:
- 既存の
TestClientAuthPublickey
とTestClientAuthPassword
は、newMockAuthServer
を使用するようにリファクタリングされ、テストボディが大幅に簡素化されました。 TestClientAuthWrongPassword
が追加され、誤ったパスワードと公開鍵認証を組み合わせた場合の挙動がテストされるようになりました。TestClientAuthInvalidPublickey
が追加され、サーバーがRSA鍵のみを期待している場合にDSA鍵で認証を試みた際の失敗ケースがテストされるようになりました。TestClientAuthRSAandDSA
が追加され、クライアントがRSA鍵とDSA鍵の両方を持つkeychain
を使用し、サーバーがRSA鍵で認証できることを確認するテストが追加されました。これは、クライアントが複数の認証方法を試行するシナリオをカバーします。
- 既存の
- グローバル変数の削減:
pkey
というグローバルなRSA秘密鍵変数が削除され、テスト内で必要に応じて鍵が生成または設定されるようになりました。これにより、テスト間の依存関係が減少し、テストの独立性が向上しました。
src/pkg/exp/ssh/common_test.go
の変更点
strings
変数のスコープ変更:strings
という名前のmap[string]string
変数が、パッケージレベルのグローバル変数から、その変数を使用する唯一の関数であるTestSafeString
の内部に移動されました。- この変更は、コミットメッセージにある「remove the package level use of the variable strings, which avoids conflicting with the strings pkg」という目的を達成するためのものです。これにより、Go標準ライブラリの
strings
パッケージとの名前衝突の可能性が完全に排除されました。
コアとなるコードの変更箇所
src/pkg/exp/ssh/client_auth_test.go
--- a/src/pkg/exp/ssh/client_auth_test.go
+++ b/src/pkg/exp/ssh/client_auth_test.go
@@ -7,17 +7,20 @@ package ssh
import (
"bytes"
"crypto"
- "crypto/rand"
+ "crypto/dsa"
"crypto/rsa"
+ _ "crypto/sha1"
"crypto/x509"
"encoding/pem"
"errors"
"io"
"io/ioutil"
+ "math/big"
"testing"
)
-const _pem = `-----BEGIN RSA PRIVATE KEY-----
+// private key for mock server
+const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
@@ -45,25 +48,32 @@ gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
-----END RSA PRIVATE KEY-----`
-// reused internally by tests
-var serverConfig = new(ServerConfig)
+// reused internally by tests
+var (
+ rsakey *rsa.PrivateKey
+ dsakey *dsa.PrivateKey
+ clientKeychain = new(keychain)
+ clientPassword = password("tiger")
+ serverConfig = &ServerConfig{
+ PasswordCallback: func(user, pass string) bool {
+ return user == "testuser" && pass == string(clientPassword)
+ },
+ PubKeyCallback: func(user, algo string, pubkey []byte) bool {
+ key := clientKeychain.keys[0].(*rsa.PrivateKey).PublicKey
+ expected := []byte(serializePublickey(key))
+ algoname := algoName(key)
+ return user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected)
+ },
+ }
+)
func init() {
- if err := serverConfig.SetRSAPrivateKey([]byte(_pem)); err != nil {
+ if err := serverConfig.SetRSAPrivateKey([]byte(testServerPrivateKey)); err != nil {
panic("unable to set private key: " + err.Error())
}
-}
-
-const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
-MIIBOwIBAAJBALdGZxkXDAjsYk10ihwU6Id2KeILz1TAJuoq4tOgDWxEEGeTrcld
-r/ZwVaFzjWzxaf6zQIJbfaSEAhqD5yo72+sCAwEAAQJBAK8PEVU23Wj8mV0QjwcJ
-tZ4GcTUYQL7cF4+ezTCE9a1NrGnCP2RuQkHEKxuTVrxXt+6OF15/1/fuXnxKjmJC
-nxkCIQDaXvPPBi0c7vAxGwNY9726x01/dNbHCE0CBtcotobxpwIhANbbQbh3JHVW
-2haQh4fAG5mhesZKAGcxTyv4mQ7uMSQdAiAj+4dzMpJWdSzQ+qGHlHMIBvVHLkqB
-y2VdEyF7DPCZewIhAI7GOI/6LDIFOvtPo6Bj2nNmyQ1HU6k/LRtNIXi4c9NJAiAr
-rrxx26itVhJmcvoUhOjwuzSlP2bE5VHAvkGB352YBg==
------END RSA PRIVATE KEY-----`
+
+ block, _ := pem.Decode([]byte(testClientPrivateKey))
+ rsakey, _ = x509.ParsePKCS1PrivateKey(block.Bytes)
+
+ clientKeychain.keys = append(clientKeychain.keys, rsakey)
+ dsakey = new(dsa.PrivateKey)
+ // ... DSA key initialization ...
+}
// keychain implements the ClientPublickey interface
type keychain struct {
- keys []*rsa.PrivateKey
+ keys []interface{}
}
func (k *keychain) Key(i int) (interface{}, error) {
if i < 0 || i >= len(k.keys) {
return nil, nil
}
- return k.keys[i].PublicKey, nil
+ switch key := k.keys[i].(type) {
+ case *rsa.PrivateKey:
+ return key.PublicKey, nil
+ case *dsa.PrivateKey:
+ return key.PublicKey, nil
+ }
+ panic("unknown key type")
}
func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
h := hashFunc.New()
h.Write(data)
digest := h.Sum(nil)
- return rsa.SignPKCS1v15(rand, k.keys[i], hashFunc, digest)
+ switch key := k.keys[i].(type) {
+ case *rsa.PrivateKey:
+ return rsa.SignPKCS1v15(rand, key, hashFunc, digest)
+ }
+ return nil, errors.New("unknown key type")
}
func (k *keychain) loadPEM(file string) error {
@@ -91,158 +105,153 @@ func (k *keychain) loadPEM(file string) error {
return nil
}
-var pkey *rsa.PrivateKey
-
-func init() {
- var err error
- pkey, err = rsa.GenerateKey(rand.Reader, 512)
- if err != nil {
- panic("unable to generate public key")
- }
-}
-
-func TestClientAuthPublickey(t *testing.T) {
-\tk := new(keychain)\n-\tk.keys = append(k.keys, pkey)\n+// newMockAuthServer creates a new Server bound to
+// the loopback interface. The server exits after
+// processing one handshake.
+func newMockAuthServer(t *testing.T) string {
+ l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
+ if err != nil {
+ t.Fatalf("unable to newMockAuthServer: %s", err)
+ }
+ go func() {
+ defer l.Close()
+ c, err := l.Accept()
+ defer c.Close()
+ if err != nil {
+ t.Errorf("Unable to accept incoming connection: %v", err)
+ return
+ }
+ if err := c.Handshake(); err != nil {
+ // not Errorf because this is expected to
+ // fail for some tests.
+ t.Logf("Handshaking error: %v", err)
+ return
+ }
+ }()
+ return l.Addr().String()
+}
+
+func TestClientAuthPublickey(t *testing.T) {
serverConfig.PubKeyCallback = func(user, algo string, pubkey []byte) bool {
- \texpected := []byte(serializePublickey(k.keys[0].PublicKey))\n-\t\talgoname := algoName(k.keys[0].PublicKey)\n-\t\treturn user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected)\n+\t\tkey := clientKeychain.keys[0].(*rsa.PrivateKey).PublicKey
+\t\texpected := []byte(serializePublickey(key))
+\t\talgoname := algoName(key)
+\t\treturn user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected)
}
serverConfig.PasswordCallback = nil
- l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
- if err != nil {
- t.Fatalf("unable to listen: %s", err)
- }
- defer l.Close()
-
- done := make(chan bool, 1)
- go func() {
- c, err := l.Accept()
- if err != nil {
- t.Fatal(err)
- }
- defer c.Close()
- if err := c.Handshake(); err != nil {
- t.Error(err)
- }
- done <- true
- }()
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthPublickey(clientKeychain),
+ },
+ }
+
+ c, err := Dial("tcp", newMockAuthServer(t), config)
+ if err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+ c.Close()
+}
-config := &ClientConfig{
- User: "testuser",
- Auth: []ClientAuth{
- ClientAuthPublickey(k),
- },
-}
-
-c, err := Dial("tcp", l.Addr().String(), config)
-if err != nil {
- t.Fatalf("unable to dial remote side: %s", err)
-}
-defer c.Close()
-<-done
-}
-
-// password implements the ClientPassword interface
-type password string
-
-func (p password) Password(user string) (string, error) {
- return string(p), nil
-}
-
func TestClientAuthPassword(t *testing.T) {
-\tpw := password("tiger")
-\n-\tserverConfig.PasswordCallback = func(user, pass string) bool {\n-\t\treturn user == "testuser" && pass == string(pw)\n+\tconfig := &ClientConfig{
+\t\tUser: "testuser",
+\t\tAuth: []ClientAuth{
+\t\t\tClientAuthPassword(clientPassword),
+\t\t},\n \t}
-\tserverConfig.PubKeyCallback = nil
-\n-\tl, err := Listen("tcp", "127.0.0.1:0", serverConfig)\n-\tif err != nil {\n-\t\tt.Fatalf("unable to listen: %s", err)\n+\tc, err := Dial("tcp", newMockAuthServer(t), config)
+\tif err != nil {
+\t\tt.Fatalf("unable to dial remote side: %s", err)
\t}
-\tdefer l.Close()\n-\n-\tdone := make(chan bool)\n-\tgo func() {\n-\t\tc, err := l.Accept()\n-\t\tif err != nil {\n-\t\t\tt.Fatal(err)\n-\t\t}\n-\t\tif err := c.Handshake(); err != nil {\n-\t\t\tt.Error(err)\n-\t\t}\n-\t\tdefer c.Close()\n-\t\tdone <- true\n-\t}()\n+\tc.Close()
+}
+
+func TestClientAuthWrongPassword(t *testing.T) {
+ wrongPw := password("wrong")
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthPassword(wrongPw),
+ ClientAuthPublickey(clientKeychain),
+ },
+ }
+
+ c, err := Dial("tcp", newMockAuthServer(t), config)
+ if err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+ c.Close()
+}
+
+// the mock server will only authenticate ssh-rsa keys
+func TestClientAuthInvalidPublickey(t *testing.T) {
+ kc := new(keychain)
+ kc.keys = append(kc.keys, dsakey)
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthPublickey(kc),
+ },
+ }
+
+ c, err := Dial("tcp", newMockAuthServer(t), config)
+ if err == nil {
+ c.Close()
+ t.Fatalf("dsa private key should not have authenticated with rsa public key")
+ }
+}
+
+// the client should authenticate with the second key
+func TestClientAuthRSAandDSA(t *testing.T) {
+ kc := new(keychain)
+ kc.keys = append(kc.keys, dsakey, rsakey)
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []ClientAuth{
+ ClientAuthPublickey(kc),
+ },
+ }
+
+ c, err := Dial("tcp", newMockAuthServer(t), config)
+ if err != nil {
+ t.Fatalf("client could not authenticate with rsa key: %v", err)
+ }
+ c.Close()
+}
src/pkg/exp/ssh/common_test.go
--- a/src/pkg/exp/ssh/common_test.go
+++ b/src/pkg/exp/ssh/common_test.go
@@ -8,15 +8,15 @@ import (
"testing"
)
-var strings = map[string]string{
- "\\x20\\x0d\\x0a": "\\x20\\x0d\\x0a",
- "flibble": "flibble",
- "new\\x20line": "new\\x20line",
- "123456\\x07789": "123456 789",
- "\\t\\t\\x10\\r\\n": "\\t\\t \\r\\n",
-}
-
func TestSafeString(t *testing.T) {
+\tstrings := map[string]string{
+\t\t"\\x20\\x0d\\x0a": "\\x20\\x0d\\x0a",
+\t\t"flibble": "flibble",
+\t\t"new\\x20line": "new\\x20line",
+\t\t"123456\\x07789": "123456 789",
+\t\t"\\t\\t\\x10\\r\\n": "\\t\\t \\r\\n",
+\t}
+\
for s, expected := range strings {
actual := safeString(s)
if expected != actual {
コアとなるコードの解説
client_auth_test.go
におけるkeychain
の変更
type keychain struct {
keys []interface{} // 変更点: []*rsa.PrivateKey から []interface{} へ
}
func (k *keychain) Key(i int) (interface{}, error) {
// ...
switch key := k.keys[i].(type) { // 型アサーションでRSAとDSAを区別
case *rsa.PrivateKey:
return key.PublicKey, nil
case *dsa.PrivateKey:
return key.PublicKey, nil
}
panic("unknown key type")
}
func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
// ...
switch key := k.keys[i].(type) { // 型アサーションでRSAを処理
case *rsa.PrivateKey:
return rsa.SignPKCS1v15(rand, key, hashFunc, digest)
}
return nil, errors.New("unknown key type") // DSA署名ロジックは追加されていないが、インターフェースは汎用化
}
この変更により、keychain
はRSAとDSAの両方の秘密鍵を保持できるようになりました。Key
メソッドはそれぞれの鍵タイプに応じた公開鍵を返し、Sign
メソッドはRSA鍵の署名処理をサポートします。これにより、異なる公開鍵アルゴリズムを用いた認証テストが可能になります。
client_auth_test.go
におけるnewMockAuthServer
の導入
func newMockAuthServer(t *testing.T) string {
l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
if err != nil {
t.Fatalf("unable to newMockAuthServer: %s", err)
}
go func() {
defer l.Close()
c, err := l.Accept()
defer c.Close()
if err != nil {
t.Errorf("Unable to accept incoming connection: %v", err)
return
}
if err := c.Handshake(); err != nil {
t.Logf("Handshaking error: %v", err) // エラーログは出すが、テスト失敗とはしない場合がある
return
}
}()
return l.Addr().String()
}
この関数は、テスト用のモックSSHサーバーを起動し、そのリスニングアドレスを返します。サーバーはバックグラウンドで動作し、1つのクライアント接続を受け入れてハンドシェイクを試みた後、自身をクローズします。これにより、各テストケースでサーバーのセットアップとクリーンアップのロジックを簡潔に記述できるようになり、テストコードの重複が排除され、可読性が大幅に向上しました。
common_test.go
におけるstrings
変数のスコープ変更
--- a/src/pkg/exp/ssh/common_test.go
+++ b/src/pkg/exp/ssh/common_test.go
@@ -8,15 +8,15 @@ import (
"testing"
)
-var strings = map[string]string{ // 削除
- "\\x20\\x0d\\x0a": "\\x20\\x0d\\x0a",
- "flibble": "flibble",
- "new\\x20line": "new\\x20line",
- "123456\\x07789": "123456 789",
- "\\t\\t\\x10\\r\\n": "\\t\\t \\r\\n",
-}
-
func TestSafeString(t *testing.T) {
+\tstrings := map[string]string{ // 関数内部に移動
+\t\t"\\x20\\x0d\\x0a": "\\x20\\x0d\\x0a",
+\t\t"flibble": "flibble",
+\t\t"new\\x20line": "new\\x20line",
+\t\t"123456\\x07789": "123456 789",
+\t\t"\\t\\t\\x10\\r\\n": "\\t\\t \\r\\n",
+\t}
+\
for s, expected := range strings {
actual := safeString(s)
if expected != actual {
strings
変数をパッケージレベルからTestSafeString
関数内部に移動することで、この変数が他のパッケージや標準ライブラリのstrings
パッケージと名前衝突を起こす可能性がなくなりました。これは、Go言語における良いプラクティスの一つであり、名前空間の汚染を防ぎ、コードの健全性を保つ上で重要です。
関連リンク
- Go Code Review (CL 5447049): https://golang.org/cl/5447049 - このコミットの元となったGoのコードレビューページです。詳細な変更履歴や議論を確認できます。
参考にした情報源リンク
- Go Code Review (CL 5447049): https://golang.org/cl/5447049
- SSH (Secure Shell): https://ja.wikipedia.org/wiki/Secure_Shell
- 公開鍵暗号: https://ja.wikipedia.org/wiki/%E5%85%AC%E9%96%8B%E9%8D%B5%E6%9A%97%E5%8F%B7
- RSA (暗号): https://ja.wikipedia.org/wiki/RSA_(%E6%9A%97%E5%8F%B7)
- Digital Signature Algorithm: https://ja.wikipedia.org/wiki/Digital_Signature_Algorithm
- PEM (ファイル形式): https://ja.wikipedia.org/wiki/PEM_(%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%BD%A2%E5%BC%8F)
- Go言語
crypto
パッケージ: https://pkg.go.dev/crypto (Go言語の公式ドキュメント) - Go言語
crypto/rsa
パッケージ: https://pkg.go.dev/crypto/rsa - Go言語
crypto/dsa
パッケージ: https://pkg.go.dev/crypto/dsa - Go言語
crypto/x509
パッケージ: https://pkg.go.dev/crypto/x509 - Go言語
crypto/rand
パッケージ: https://pkg.go.dev/crypto/rand - Go言語
encoding/pem
パッケージ: https://pkg.go.dev/encoding/pem - Go言語
strings
パッケージ: https://pkg.go.dev/strings