1.この記事の対象の人

    • Go で、USBトークンを操作してみたい人

 

    Go で、PKCS#11 を使ってみたい人

ハードウェアと鍵を操作するので、念のために以下を記載しておきます。
この記事の情報をあなたが利用することによって生ずるいかなる損害に対しても、当方は一切責任を負いません。

・USB トークンとは
この記事では、 USB 形態のセキュリティトークンで PKCS#11 が使えるデバイスとします。
Amazon や量販店で気軽に買えるのは YubiKey ですね。
今回は YubiKey 5 NFC使います。(以下 YubiKey )

・PKCS#11 とは
暗号トークンへの汎用インタフェースを定義する API です。
詳しくは、PKCS#11仕様書や、IPAによるレポートを参照してください

2.概要

この記事では、
1. YubiKey で秘密鍵と証明書(公開鍵)を生成
2. OpenSC tool で YubiKey の SlotID を特定
3. Go のコードから PKCS#11 を利用して、YubiKey を操作
します。

3.事前準備

Linux 環境の方がいろいろ楽ですが、この記事では Windows 環境でがんばります。
準備に手間がかかりますがご容赦ください。

3.1 Go 用 PKCS#11用パッケージ

Go で PKCS#11 を利用する場合、この pkcs11 パッケージを使います。このパッケージの利用には gcc が必要です。 gcc をあらかじめインストールしてください。

私の Windows 環境では、 MinGW-w64 をインストールしたら動きました※

※64 Bit 環境の方は、Install 時に Architecture:x86_64 を指定してください。(気が付かないとはまる)

3.2 YubiKey Manager

あらかじめ、YubiKey に秘密鍵と証明書(公開鍵)を生成しておきます。
今回、生成は YubiKey Manager 経由で行います。あらかじめインストールしてください。

事前に YubiKey は接続しておいてください。

    1. YubiKey Manager 起動

 

    1. Application をクリック

 

    1. PIV をクリック

 

    1. Certificates の Configure Certificates をクリック

 

    1. Authentication※ タブをクリック

 

    1. Generate をクリック

 

    1. Self-signed certificate -> Algorithm: RSA2048 -> 以降は適当に入力

 

    Management Key は default の まま。 PIN は “123456” を指定

秘密鍵と証明書が YubiKey 内部の Certificate slot に生成されます。

※ Certificate slot が Authentication または Card Authentication でないと、この記事のコードは動きませんでした。

3.3 OpenSC

今回 OpenSC の tool とライブラリを使います。
あらかじめインストールしておいてください。

4.事前調査

インストールした OpenSC の pkcs11-tool を使って、YubiKey がどのスロットにいるか確認します。
YubiKey がどのスロットにいるかは、環境によって異なります。

> pkcs11-tool.exe --module "C:\Program Files\OpenSC Project\OpenSC\pkcs11\onepin-opensc-pkcs11.dll" -L
Available slots:
中略
Slot 5 (0x5): Yubico YubiKey OTP+FIDO+CCID 0
  token label        : gopkicookbook3
  token manufacturer : piv_II
  token model        : PKCS#15 emulated
  token flags        : login required, rng, token initialized, PIN initialized
  hardware version   : 0.0
  firmware version   : 0.0
  serial num         : xxxxxxxxxxxxxx
  pin min/max        : 4/8

Slot 5 (0x5): Yubico YubiKey OTP+FIDO+CCID 0

この環境では YubiKey は Slot ID 5 番にいるようです。(Slot ID は都度かわる可能性があります)

次に YubiKey が対応している PKCS#11 メカニズム一覧を確認します。
メカニズムとは、利用可能な機能みたないなものです。

> pkcs11-tool.exe --module="C:\Program Files\OpenSC Project\OpenSC\pkcs11\onepin-opensc-pkcs11.dll" -M --slot 5
Supported mechanisms:
  SHA-1, digest
  SHA224, digest
  SHA256, digest
  SHA384, digest
  SHA512, digest
  MD5, digest
  RIPEMD160, digest
  GOSTR3411, digest
  ECDSA, keySize={256,384}, hw, sign, other flags=0x1800000
  ECDH1-COFACTOR-DERIVE, keySize={256,384}, hw, derive, other flags=0x1800000
  ECDH1-DERIVE, keySize={256,384}, hw, derive, other flags=0x1800000
  RSA-X-509, keySize={1024,3072}, hw, decrypt, sign, verify
  RSA-PKCS, keySize={1024,3072}, hw, decrypt, sign, verify
  SHA1-RSA-PKCS, keySize={1024,3072}, sign, verify
  SHA224-RSA-PKCS, keySize={1024,3072}, sign, verify
  SHA256-RSA-PKCS, keySize={1024,3072}, sign, verify
  SHA384-RSA-PKCS, keySize={1024,3072}, sign, verify
  SHA512-RSA-PKCS, keySize={1024,3072}, sign, verify
  MD5-RSA-PKCS, keySize={1024,3072}, sign, verify
  RIPEMD160-RSA-PKCS, keySize={1024,3072}, sign, verify
  RSA-PKCS-PSS, keySize={1024,3072}, hw, sign, verify
  SHA1-RSA-PKCS-PSS, keySize={1024,3072}, sign, verify
  SHA224-RSA-PKCS-PSS, keySize={1024,3072}, sign, verify
  SHA256-RSA-PKCS-PSS, keySize={1024,3072}, sign, verify
  SHA384-RSA-PKCS-PSS, keySize={1024,3072}, sign, verify
  SHA512-RSA-PKCS-PSS, keySize={1024,3072}, sign, verify

SHA256, digest

YubiKey は、SHA256 で メッセージダイジェストが作れるようです。

SHA256-RSA-PKCS, keySize={1024,3072}, sign, verify

YubiKey は、ハッシュアルゴリズムに SHA256 を利用する RSASSA-PKCS1-v1_5 形式の電子署名の作成と検証ができるようです。
RSASSA-PKCS1-v1_5 形式の電子署名とは何かは、 RFC 8017 を参照してください。

それでは、いよいよ YubiKey を Go から PKCS#11 で操作して

    • SHA256 で メッセージダイジェスト作成

 

    RSASSA-PKCS1-v1_5形式の電子署名の作成と検証

を行います。

5. Go でのセッションの作成とログイン

YubiKey を操作するためには、 YubiKey のいる「スロット」を指定して、「セッション」を作成して「一般ユーザ」として「ログイン」する必要があります。
また「一般ユーザ」として「ログイン」するときに「PIN」が必要となります。

    • 「スロット」は事前に OpenSC tool で調べた、あなたの環境の YubiKey の Slot ID を指定します。

 

    • 「PIN」は、YubiKey で設定した PIN を指定します。デフォルトは、「123456」です。

 

    「モジュールパス」には、 OpenSC の onepin-opensc-pkcs11.dll( Linux なら so ) へのパスを指定します。
    //Specify your YubiKey PIN
    yubiKeyDefaultPin := `123456`

    //Specify the path to onepin-opensc-pkcs11.dll/so
    modulePath := `C:\Program Files\OpenSC Project\OpenSC\pkcs11\onepin-opensc-pkcs11.dll`

    //Specify th slotID in your environment
    var slotId uint = 5

    p := pkcs11.New(modulePath)
    p.Initialize()

    session, err := p.OpenSession(slotId, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)

    err = p.Login(session, pkcs11.CKU_USER, yubiKeyDefaultPin)

これでYubiKey を操作する準備ができました。

6. Go でのメッセージダイジェストの作成

PKCS#11 を利用して YubiKey で、メッセージダイジェストを作成します。
文字列「”Hello World.”」からメッセージダイジェストを作成します。
事前調査に「 SHA256 」でメッセージダイジェストが利用できることが分かったので、
メカニズムに CKM_SHA256 を指定します。

    message := []byte("Hello World.")

    var msgDigest []byte

    p.DigestInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_SHA256, nil)})

    err = p.DigestUpdate(session, message)

    msgDigest, err = p.DigestFinal(session)

    fmt.Printf("SHA256 Digest: %x\n", msgDigest)

生成したメッセージダイジェストは、以下のようになりました

f4bb1975bf1f81f76ce824f7536c1e101a8060a632a52289d530a6f600d52c92

OpenSSLの出力と比較してみます。

$ echo -n "Hello World." | openssl sha256
(stdin)= f4bb1975bf1f81f76ce824f7536c1e101a8060a632a52289d530a6f600d52c92

同じですね。

7. Go での秘密鍵と公開鍵のオブジェクトハンドラの取得

電子署名の作成と検証には、秘密鍵と公開鍵が必要です。
YubiKey から秘密鍵と公開鍵を直接取得して利用したいところですが…できません。
Yubikey は、セキュリティトークンなので秘密鍵を直接取り出すことはできないのです。※

YubiKey の中に格納された秘密鍵と公開鍵を利用するには、秘密鍵と公開鍵に対応するオブジェクトハンドラを利用します。
電子署名したいデータを YubiKey に渡して、オブジェクトハンドラ経由で鍵を操作し、電子署名の作成と検証を行います。

ではオブジェクトハンドラを取得します。
PKCS#11 を利用してオブジェクトハンドラを取得する場合、取得したいオブジェクトハンドラの属性値を指定して取得します
今回は秘密鍵と公開鍵のオブジェクトハンドラなのでそれぞれ、

    • CKO_PRIVATE_KEY

 

    CKO_PUBLIC_KEY

を指定します。

    privateKeyTemplate := []*pkcs11.Attribute{
        pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
    }

    publicKeyTemplate := []*pkcs11.Attribute{
        pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
    }

    err := p.FindObjectsInit(session, privateKeyTemplate)

    objects, _, err := p.FindObjects(session, 1)

    err = p.FindObjectsFinal(session)

    pvk := objects[0]

    err = p.FindObjectsInit(session, publicKeyTemplate)

    objects, _, err = p.FindObjects(session, 1)

    err = p.FindObjectsFinal(session)

    puk := objects[0]

    return pvk, puk

※公開鍵は(証明書の一部として)直接取り出すことができます。

8. Go での電子署名の作成と検証

PKCS#11 を利用して YubiKey で、RSASSA-PKCS1-v1_5 形式の電子署名の作成と検証をおこないます。
文字列「”Hello World. again”」から電子署名を作成し、その電子署名を検証します。
事前調査で 「ハッシュアルゴリズムに SHA256 を利用する RSASSA-PKCS1-v1_5 形式の電子署名」が利用できることが分かったので、
メカニズムに CKM_SHA256_RSA_PKCS を指定します。

    //https://tools.ietf.org/html/rfc8017#section-8.2
    toBeSignedData := []byte("Hello World. again")

    var signature []byte
    err = p.SignInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_SHA256_RSA_PKCS, nil)}, privateKeyObjectHandler)

    signature, err = p.Sign(session, toBeSignedData)

    fmt.Printf("Signature: %x\n", signature)

    err = p.VerifyInit(session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_SHA256_RSA_PKCS, nil)}, publicKeyObjectHandler)

    err = p.Verify(session, toBeSignedData, signature)

9. コード

コードはこちら
https://github.com/tardevnull/gopkicookbook3

bannerAds