エラーメッセージ
asn1: structure error: tags don't match
完全なメッセージには、期待されたタグと検出されたタグに加えてフィールドの情報も付きます。例:「tags don't match (16 vs {class:0 tag:2 ...}) ... AlgorithmIdentifier @2」。入力が途中で切れている場合は、関連する別のエラー「asn1: syntax error: data truncated」になります。現在の Go では、このメッセージは公開鍵用と PKCS#8 用のパーサーから出力されます。x509.ParseCertificate は、同じ誤りを「x509: malformed certificate」として報告します。
Go encoding/asn1 — surfaced through crypto/x509 parsers
このエラーの意味
Go の DER デコーダーが、変換先の構造体が必要とするものとは異なる ASN.1 タグを検出しました。おおむね正しい DER を間違った Go の構造体にパースしているか、そもそも DER ではないものをパースしています。メッセージ中の数値は ASN.1 タグで(16 = SEQUENCE、2 = INTEGER、PEM ファイルなら 13 = 先頭のダッシュ)、たいていは何を取り違えたのかを正確に示しています。
発生する理由
DER パーサーに渡された PEM のバイト列
よくあるParsePKIXPublicKey、ParsePKCS8PrivateKey などの関数は生の DER を受け取ります。先に pem.Decode を通さずに PEM テキスト(ダッシュと base64)を渡すことが、最もよくある原因です。(ParseCertificate も PEM テキストを拒否し、「x509: malformed certificate」を返します。)
鍵コンテナーに合わないパーサー
よくある秘密鍵や証明書全体を ParsePKIXPublicKey に渡している場合です。コンテナーごとに構造が異なるため、タグのチェックが最初に食い違うフィールドで取り違えを検出します。秘密鍵用パーサーの取り違えは、もっとわかりやすく報告されます。PKCS#8 のバイト列に ParseECPrivateKey を使うと「x509: failed to parse private key (use ParsePKCS8PrivateKey instead for this key format)」と表示され、逆の場合も同様です。
そもそも証明書や鍵ではない
ときどき64 バイトの生の署名、JWT、プロトコルのフレーミングのバイト列などが ASN.1 としてパースされています。入力が 0x30(SEQUENCE)以外で始まる場合、X.509 系のパーサーはどれも受け付けません。
修正方法
- 1.
必ず先に pem.Decode を通す
パーサーを呼び出す前に、DER ブロックを取り出して(ラベルも確認して)ください。
go block, _ := pem.Decode(pemBytes) if block == nil { return fmt.Errorf("input is not PEM") } cert, err := x509.ParseCertificate(block.Bytes) // block.Type == "CERTIFICATE" - 2.
コンテナーのパーサーを順番に試す
秘密鍵のコンテナーがわからない場合は、PKCS#8、SEC1、PKCS#1 の順に試してください。出どころの異なる鍵を扱うときの標準的な切り分け手順です。
go if k, err := x509.ParsePKCS8PrivateKey(der); err == nil { return k, nil } if k, err := x509.ParseECPrivateKey(der); err == nil { return k, nil } if k, err := x509.ParsePKCS1PrivateKey(der); err == nil { return k, nil } return nil, errors.New("unrecognized private key format") - 3.
先頭のバイトを確認する
hexdump の 1 行を見れば、そもそも DER なのかどうかがわかります。また、openssl asn1parse で構造を表示すれば、選んだパーサーと照らし合わせられます。
bash head -c 16 key.der | xxd # 30 81/82 ... → SEQUENCE, plausibly DER openssl asn1parse -inform der -in key.der | head -3