Golangでターミナル接続sshクライアントを作成する(PKCS11での物理トークン(Yubikey)を用いた接続)
Pocket

ここ最近、仕事が変わったのとRustでこさえてたツールの改修に手間取っててあまり触ってたなかったのだけど、ある程度一段落したのでGoのsshクライアントの改修に着手した。
で、sshでの接続方式というといくつかあると思うが、最近はYubikeyをPIVカードとして利用して、Yubikey内の秘密鍵を使ってssh接続するというやり方もあるので、それをGolangで実装しようと思いやってみた。

これ、実はかなり手間取ってしまい、結構時間がかかってしまった…。
Yubikey等のPIVカードに入ってる秘密鍵を使ってssh接続する場合、OpenSCなどのPKCS11を扱えるライブラリが必要になるのだが、それがいまいちよくわからなかった(実装できた今もさっぱりわからない…)。

Golangは結構いろんな人がいろんなライブラリを提供してくれてて、その中にpkcs11を扱うための、OpenSCなどのラッパーとして扱えるライブラリもあるのだが、それらを利用する。
ssh接続をする場合はYubikeyのKeyPairを使って、Signerとして扱える必要があるので、今回はcrypto11というライブラリを主に使っている(ココいくまでも右往左往してしまった…)。

Sponsored Links

以下、サンプルコード。
一応こっちにも上げてある。

● ssh_term_pkcs11.go

package main
import (
"crypto"
"fmt"
"os"
"os/signal"
"syscall"
"github.com/ThalesIgnite/crypto11"
"github.com/miekg/pkcs11"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
var (
// ↓この2つは指定が必要
pkcs11Path = "/usr/local/lib/opensc-pkcs11.so"
tokenLabel = "tokenLabel"
// 鍵のIDは決め打ち。(p11toolでの出力だと「01」に該当)
// 自動取得にする場合は、"pkcs11.NewAttribute"で指定して取得することで対応すればいい。
// ↓ サンプル
// ------
// findTemplate := []*pkcs11.Attribute{
//     pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
//     pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true),
//     pkcs11.NewAttribute(pkcs11.CKA_ID, true),
//     pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA),
// }
// p.FindObjectsInit(session, findTemplate)
// ------
keyID = []byte{'\x01'}
// sshの接続情報
host = "target.host"
port = "22"
user = "user"
)
func main() {
// PINコードの入力を受け付け
fmt.Printf("PIN: ")
tokenPin, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
fmt.Println("err: {}", err)
os.Exit(1)
}
// pkcs11のConfigを作成
config := &crypto11.PKCS11Config{
Path:       pkcs11Path,
TokenLabel: tokenLabel,
Pin:        string(tokenPin),
}
// pkcs11のConfigを使いctxを作成
ctx, err := crypto11.Configure(config)
if err != nil {
fmt.Println("err: {}", err)
os.Exit(1)
}
// pkcs11のセッションを作成
keysession, err := ctx.OpenSession(0, pkcs11.CKF_SERIAL_SESSION)
if err != nil {
fmt.Println("err: {}", err)
os.Exit(1)
}
pkcs11Session := &crypto11.PKCS11Session{ctx, keysession}
// PIVのSignerを作成する(slot,idは決め打ち)
prv, err := crypto11.FindKeyPairOnSession(pkcs11Session, 0, keyID, nil)
if err != nil {
fmt.Println("err: {}", err)
os.Exit(1)
}
signer := prv.(crypto.Signer)
// PIVのSignerからssh用のSignerを作成する
sshSigner, err := ssh.NewSignerFromSigner(signer)
if err != nil {
fmt.Println("err: {}", err)
os.Exit(1)
}
auth := []ssh.AuthMethod{}
auth = append(auth, ssh.PublicKeys(sshSigner))
// ---------------------
//
// こっから下はコピペのやつ
//
// ---------------------
// Create sshClientConfig
sshConfig := &ssh.ClientConfig{
User:            user,
Auth:            auth,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
BannerCallback:  ssh.BannerDisplayStderr(),
}
// SSH connect.
client, err := ssh.Dial("tcp", host+":"+port, sshConfig)
if err != nil {
fmt.Println("client: {}", err)
os.Exit(0)
}
// Create Session
session, err := client.NewSession()
defer session.Close()
// キー入力を接続先が認識できる形式に変換する(ここがキモ)
fd := int(os.Stdin.Fd())
state, err := terminal.MakeRaw(fd)
if err != nil {
fmt.Println(err)
}
defer terminal.Restore(fd, state)
// ターミナルサイズの取得
w, h, err := terminal.GetSize(fd)
if err != nil {
fmt.Println(err)
}
modes := ssh.TerminalModes{
ssh.ECHO:          1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
err = session.RequestPty("xterm", h, w, modes)
if err != nil {
fmt.Println(err)
}
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin
err = session.Shell()
if err != nil {
fmt.Println(err)
}
// ターミナルサイズの変更検知・処理
signal_chan := make(chan os.Signal, 1)
signal.Notify(signal_chan, syscall.SIGWINCH)
go func() {
for {
s := <-signal_chan
switch s {
case syscall.SIGWINCH:
fd := int(os.Stdout.Fd())
w, h, _ = terminal.GetSize(fd)
session.WindowChange(h, w)
}
}
}()
err = session.Wait()
if err != nil {
fmt.Println(err)
}
}

 

ひとまず、これでPIVカードに入ってる秘密鍵を利用してssh接続できるはず…(自分の環境(Mac + OpenSC + Yubikey)ではできた)。
あまり一般的な接続方式ではないけど、今後は物理トークンを用いた接続も使われていくことがあるだろうし、うまいこと付き合っていきたいところだなぁと(´・ω・`)。

PKCS11、ちゃんと扱える様になりたいのだけど、自分が理解できるような情報がいまいち見つけられず、未だによくわかってない…。
誰か詳しい人いたら教えてください(´;ω;`)。

 

Pocket

Written by blacknon

インフラエンジニア(…のつもり)。 仕事で使うならクライアントはWindowsよりはUNIXの方が好き。 大体いつも眠い。

Leave a Comment

メールアドレスが公開されることはありません。

*