Skip to content

Instantly share code, notes, and snippets.

@blacknon
Last active January 30, 2023 02:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save blacknon/4d588483065b66a2a98d391f24074206 to your computer and use it in GitHub Desktop.
Save blacknon/4d588483065b66a2a98d391f24074206 to your computer and use it in GitHub Desktop.
goでpkcs11で認証してsshでシェルに接続する検証・サンプルコード(2)
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 (
pkcs11Provider = "/usr/local/lib/opensc-pkcs11.so"
addr = "target.host"
port = "22"
user = "user"
)
type P11 struct {
Ctx *pkcs11.Ctx
Label string
SlotID uint
KeyID map[int][]byte // これでいいのか…?
PIN string
SessionHandle pkcs11.SessionHandle
Signers []ssh.Signer
}
// ctx(pkcs11のメインのオブジェクト)を作成する
func (p *P11) CreateCtx() (err error) {
ctx := pkcs11.New(pkcs11Provider)
err = ctx.Initialize()
if err != nil {
return
}
p.Ctx = ctx
return
}
// PKCS11を経由してハードウェアトークンの情報を取得する
func (p *P11) GetTokenLabel() (err error) {
slots, err := p.Ctx.GetSlotList(false)
if err != nil {
return
}
if len(slots) > 1 {
err = fmt.Errorf("err: %s", "Single token only")
return
}
if len(slots) == 0 {
err = fmt.Errorf("err: %s", "No token")
return
}
slotID := slots[0]
tokenInfo, err := p.Ctx.GetTokenInfo(slotID)
if err != nil {
return
}
p.SlotID = slotID
p.Label = tokenInfo.Label
return
}
// structにPINが無い場合、取得させる
func (p *P11) GetPIN() (err error) {
if p.PIN == "" {
fmt.Printf("PIN: ")
pin, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return err
}
if len(pin) == 0 {
err = fmt.Errorf("err: %s", "PIN empty")
return err
}
p.PIN = string(pin)
}
fmt.Println()
return
}
// CtxをCrypto11のものに作り変える
func (p *P11) RecreateCtx() (err error) {
p.Ctx.Destroy()
config := &crypto11.PKCS11Config{
Path: pkcs11Provider,
TokenLabel: p.Label,
Pin: p.PIN,
}
ctx, err := crypto11.Configure(config)
if err != nil {
return
}
session, err := ctx.OpenSession(p.SlotID, pkcs11.CKF_SERIAL_SESSION)
if err != nil {
return
}
p.Ctx = ctx
p.SessionHandle = session
return
}
// Token内の鍵ファイルのIDを取得する(トークン内に鍵ファイルが複数ある可能性が高いので配列)
func (p *P11) GetKeyID() (err error) {
// find templeteの最初にIDを取得する
findTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_ID, true),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true),
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA),
}
// Objectを探す
p.Ctx.FindObjectsInit(p.SessionHandle, findTemplate)
obj, _, err := p.Ctx.FindObjects(p.SessionHandle, 1000)
if err != nil {
fmt.Println("find obj")
return
}
// Objectの検索を終了(deferの方が良いのか??)
err = p.Ctx.FindObjectsFinal(p.SessionHandle)
p.KeyID = map[int][]byte{}
for num, objValue := range obj {
attrs, _ := p.Ctx.GetAttributeValue(p.SessionHandle, objValue, findTemplate)
p.KeyID[num] = attrs[0].Value
}
return
}
func (p *P11) GetSigner() (signers []ssh.Signer, err error) {
c11Session := &crypto11.PKCS11Session{p.Ctx, p.SessionHandle}
for _, keyID := range p.KeyID {
prv, err := crypto11.FindKeyPairOnSession(c11Session, p.SlotID, keyID, nil)
if err != nil {
return signers, err
}
// append signer
cryptoSigner := prv.(crypto.Signer)
sshSigner, _ := ssh.NewSignerFromSigner(cryptoSigner)
signers = append(signers, sshSigner)
}
return signers, err
}
type Ssh struct {
Addr string
Port string
User string
}
func (s *Ssh) ConnectTerm(signers []ssh.Signer) (err error) {
fmt.Println("Start ConnectTerm")
auth := []ssh.AuthMethod{}
for _, signer := range signers {
auth = append(auth, ssh.PublicKeys(signer))
}
sshConfig := &ssh.ClientConfig{
User: s.User,
Auth: auth,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
BannerCallback: ssh.BannerDisplayStderr(),
}
// SSH connect.
client, err := ssh.Dial("tcp", s.Addr+":"+s.Port, sshConfig)
if err != nil {
fmt.Printf("ssh connect err: %s\n", err)
return
}
// Create Session
session, err := client.NewSession()
defer session.Close()
// キー入力を接続先が認識できる形式に変換する(ここがキモ)
fd := int(os.Stdin.Fd())
state, err := terminal.MakeRaw(fd)
if err != nil {
return
}
defer terminal.Restore(fd, state)
// ターミナルサイズの取得
w, h, err := terminal.GetSize(fd)
if err != nil {
return
}
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 {
return
}
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin
err = session.Shell()
if err != nil {
return
}
// ターミナルサイズの変更検知・処理
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 {
return
}
return
}
func main() {
var err error
p := new(P11)
err = p.CreateCtx()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = p.GetTokenLabel()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = p.GetPIN()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = p.RecreateCtx()
if err != nil {
fmt.Println("RecreateCtx")
fmt.Println(err)
os.Exit(1)
}
err = p.GetKeyID()
if err != nil {
fmt.Println("GetKeyID")
fmt.Println(err)
os.Exit(1)
}
signers, err := p.GetSigner()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// ssh connect
s := new(Ssh)
s.Addr = addr
s.Port = port
s.User = user
err = s.ConnectTerm(signers)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment