2024/01/28

Windows Helloとセキュリティキー

以前 Google Pixel を購入したときに Google Store ポイントをもらっていて、それがあと数ヶ月で期限切れになりそうだった。
せっかくなので何か購入しておこうと思ったのだが、使えるのが Google Store だけのためポイントだけで購入できるものは少ない。まあ、おまけでポイントをもらっただけなのでぜいたくは言えない。

私が持っていなくて、合って面白そうだなと思ったのがセキュリティキーだった。

Titan Security Key - FIDO2 USB-A/USB-C + NFC
https://store.google.com/product/titan_security_key?hl=ja

以前のバージョンは単なる FIDO だったが、最近更新されて FIDO2 対応になっている(対応、という呼び名が正しいかどうかわからんが...)。

最大250種類のパスキーの保存が可能なGoogle Titan セキュリティ キーを使ってパスキー認証してみた - GIGAZINE
https://gigazine.net/news/20231219-google-titan-security-pass-key/

PIN を求められるが、これはGoogle ログインのときの PIN だと思ったのだが、ダイアログが「Windowsセキュリティ」だから各デバイスで管理するのかもしれん。
キーの金属部にタッチする必要があるが、これは指紋認証ではなく単にタッチするだけだ。


FIDO2 になったので Windows のアカウントや Windows11 のログインに使うことができる。

 

Windows11 のアカウント画面を見てみると、セキュリティキーのログインは Windows Hello ではないことになっている。

image

そもそも Windows Hello って何よ?

Windows Hello の概要とセットアップ - Microsoft サポート
https://support.microsoft.com/ja-jp/windows/windows-hello-%E3%81%AE%E6%A6%82%E8%A6%81%E3%81%A8%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97-dae28983-8242-bb2a-d3d1-87c9d265a5f0#WindowsVersion=Windows_11

Windows Hello は、PIN、顔認識、または指紋を使って Windows 11 デバイスにすばやくアクセスできる、よりプライベートで安全な方法です。 指紋または顔認識によるサインインの設定の一部として PIN を設定する必要がありますが、PIN だけでサインインすることもできます。

これらのオプションは、PIN が 1 つのデバイスにのみ関連付けられており、Microsoft アカウントで回復するためにバックアップされるため、PC へのサインインをより簡単かつ安全にするのに役立ちます。

だそうだ。

PIN はキャッシュカードの暗証番号のようなもので、ネットワークなどに流したりせずに使うものという認識である。
ここの説明では「Windows11デバイスに」とあるので、その Windows11 デバイスに PIN を設定しているのだろう。顔認証や指紋でログインできるのは、それらと PIN を結びつけているためか。

紛らわしくなるのは、Windows アカウント(ネットの方)も Windows Hello などでログインできるところだ。

image

覚えていないのだけど、たぶん Microsoft アカウントにログインしたときに Windows Hello と結びつけたのだろう。
Microsoft アカウントのところではこういう選択肢があった。

image

こんな感じでセキュリティキーでのログインも可能になった。

しかし、こうやってログイン方式を追加するだけだと、ログインできるルートが増えて危険度が高くなったことにならないだろうか。かといって、認証アプリを使う方式だとアプリが使えなくなったら困るし、セキュリティキーを使う場合も紛失があったら困るし、減らすのも難しい。

セキュリティキーを使う場合は2つ登録しておいて、1つ紛失してももう片方でアクセスできるようにするのが望ましい、というのをどこかで読んだ気がする。つまり、セキュリティキー以外ではログインできないようにしておくということなのだろう。

キーでのログインしかできないようにした場合、気にするのはこの辺だろうか。

  • キーを紛失した
    • 複数にして、無くしたと分かったらもう片方でログインして片方でのログインを禁止する。
  • キーを紛失してログインされた
    • 犯人は身近にしかいないはず??
  • その代わり、キーが一度も手元から離れていないなら心配することはない?
    • 「外部からログインできないようになっている」という場合以外は、条件さえ整えば外部からログインできるはずなのだ。
    • 生体認証も、何らかの方法で「生体」を使うことができるなら突破できるはずだ。
    • 結局のところ、ログインの突破を規定回数以内に成功しなかったらアカウントをロックする以外に方法が無いような気がする。

 

あーもー。

セキュリティはイタチごっこだとよく言われる。
が、イタチごっこってそもそもなによ?

いたちごっこ - Wikipedia
https://ja.wikipedia.org/wiki/%E3%81%84%E3%81%9F%E3%81%A1%E3%81%94%E3%81%A3%E3%81%93

ずいずいずっころばしとも違うのか。

ともかく、外部からログインできるようになっていて、破ることができないログインシステムは存在しないはずだ。ログインできるようになっているのだから、破ろうと破るまいと手続きが正当であればログインできるのだから。

そういうシステムなのに、その人しかログインできないようになっている場合、それはその人自身が変化するということを視野に入れていないだけかもしれない。「変化する自身」を外部に取り出したのがセキュリティキーかもしれないが、これはこれで持ち運ぶことができるようになっているし。

いやー、私にはどうするのがよいか考えつかないね。

 

(2024/01/28 22:07追記)

Windows11 のアカウント設定でセキュリティキーを設定したつもりだったが、ログインではそういう項目は出てこなかった。
追加設定で「Windows Hello サインインのみ」が有効になっていたから外してみたが、それでも変わらず。

image

よく見ると、セキュリティキーでサインインするとは書いてあるが、「アプリにサインインする」と書いてあった。

image

管理ボタンを押すと、PIN の設定などができる。
毎回 PIN 入力を求められていたのはここか!

image

そしてダイアログのタイトルは「Windows Helloセットアップ」なのだよなぁ。

リセットは「セキュリティキーからすべてを削除して」と書いてあるので、キーの方に PIN が設定されるのだろうか。

 

↑の方では Windows Hello のことしか調べていなかったが、セキュリティキーも調べるとちょっと違った。

Windows Hello またはセキュリティ キーで Microsoft アカウントにサインインする - Microsoft サポート
https://support.microsoft.com/ja-jp/windows/windows-hello-%E3%81%BE%E3%81%9F%E3%81%AF%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3-%E3%82%AD%E3%83%BC%E3%81%A7-microsoft-%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AB%E3%82%B5%E3%82%A4%E3%83%B3%E3%82%A4%E3%83%B3%E3%81%99%E3%82%8B-800a8c01-6b61-49f5-0660-c2159bea4d84

「Microsoftアカウントに」なのだ。

つまり、こういうことか。

  • Microsoftアカウントにサインインするのに、FIDO2 のセキュリティキーが使用できる
  • Windows11 では FIDO2 のセキュリティキーを管理することができる
    • が、Windows11 のログインに FIDO2 のセキュリティキーが使えるわけではない

まあ、これを書いているノートPCは指紋認証があるから、セキュリティキーはなくてもよいのだけどね。

2024/01/14

スタックメモリとヒープメモリの使い分けはコンパイラ任せ (1)

golang は chan で時空を超えて(?)値を渡すことができる。

といっても、golang って C言語になかなか近いところがあるので、ポインタで渡すときはヒープにメモリを確保しておかないと危ないよね?
と思って new なり make なり使うべきかと考えていたのだが、ガベッジコレクションもあることを考えるとそう簡単ではない気がした。

 

心配になって検索すると、どうも golang ではスタックメモリやらヒープメモリやらを実装する人が決められないというか、コンパイラが決定するというような記事ばかりが出てくる。

https://duckduckgo.com/?t=ffab&q=golang+stack+heap&ia=web

ただ、そうするとかなりな部分がヒープメモリで管理することになってしまうんじゃなかろうか。いや、関数呼び出しで値がプリミティブだったらスタックにする、みたいなところだけやっておけば十分なのか?

 

よし、忘れよう。

ならば実装する人が気にするのは、値そのものを持つのか、あるいはアドレスで持つのかというところだけになる。
まず、値をリードオンリーで済ませられるかどうか。

  • 値を渡した先でも加工する可能性がある
    • 渡した先でも加工する可能性があるのであれば、それは値そのものを渡す必要がある。
    • 渡した先は参照だけだが、渡された瞬間の値だけがほしいのであれば、それも値そのものを渡した方がよいだろう。アドレスを渡して渡された先がそれを自分でコピーする前に渡した元が加工する可能性があるからだ。
    • 渡した先は参照だけで、渡された元の値の変化も含めて読みたい場合。これならアドレスを渡せばいいと思ったのだけど、そもそも同じメモリ空間をロックせずに読んだり書いたりするというのはダメな行為だ。
  • 値を渡した先は参照のみ
    • 渡した元で値を加工する可能性があるなら、やっぱりアドレスを渡すのは良くないな。

というわけで、プリミティブ型であれば値をそのまま渡してしまえば良いとは思うのだが、CPUのバス幅を超えるようなデータの場合はちょっと考えた方がよいと思う。

 

調べる前に推測してみよう。

chan で渡すデータはアドレスがよいと思う。
なぜなら、値を渡すという行為は何らかのコピーになるのでコストが高いからだ。加工する可能性があるならばchan で渡す前にコピーを作って chan ではコピーしたメモリのアドレスを渡すべきだろう。
コンパイラが賢そうなので、実装する人がそういうのを考慮しなくてもよいのかもしれないが、たぶんそこは実装されたままコンパイルされるんじゃなかろうか。言語仕様としてアドレスにする&があるのだから、そうでなかったらコピーして渡しそうな気はする。ただまあ、それでも実装としてコピーするかコンパイラがコピーするかの違いになるから、あんまり変わらないか、もしかしたら明示的に書いたので最適化できずにコストが高くなってしまう可能性もあるのか...。

さて、推測はした。
答え合わせをしよう。


同じメモリを読み書きするとなると、どうしてもロック・アンロックの機構は必要になる。

どういう単位でアクセスするコンテキストが切り替わるかはわからないが、100バイトあるデータの内50バイト書き換えたタイミングでコンテキストが切り替わる、なんてことも想定しないといかんだろう。
golangではない普通のスレッドモデルで実装していたときだが、32bit の CPU だったのに日付(RTC)を 64bit で持っていて、ごく希に 32bit だけ書き換えたタイミングでコンテキストが切り替わることがあってデータが以上になるということが発生したことがある。あれは痛恨のミスだった。

だから、golang が chan でデータを送りつけるモデルを持つというのはわかりやすかった。
相手が読み取りタイミングで読み取ってほしいけど、こっちはこっちでそのタイミングを気にしたくないということは多いのだ。さっき書いた時刻も getTime() みたいな API を作ってたのでこうなったが、相手が送ってきた最後のデータをこちらが好きなタイミングで読む、だったら問題は少ない。相手が送ってきたデータをこちらが受け取るタイミングと、そのデータをこちらが読み込むタイミングがあるので、そこを共有メモリで実現するなら mutex などでロックする必要がある。が、chan はそういうことがいらないようになっているので現場レベルでは気にしなくてよい。たぶん。

 

そう考えると、chan で渡すデータについては glang でうまいことやってくれるとしても、そこから先は golang がやってくっるとは思えない。
いや、それは私の理解であって golang が実際どうなのかはまだわかっていない。

時間が足りないので次回にしよう。

YAMLフォーマット

JSONは便利だ。
文字も書けるし数字も書ける。配列も書けるし構造体も書ける。

ここがちょっとなあ、と思うところもある。

  • 数字が 53bit まで
  • 行末にコンマがいるし、次に継続するものがない場合はコンマがあってはダメ
  • コメントが書けなさそう

53bit までしかないので、64bit値になる可能性がある場合は文字列にしてデコード側で変換している。

コンマが必要だったり不要だったりなのは面倒なだけなのだが、ちょっとしたJSONなのでライブラリなど無しで作ろうとした場合、コンマを出力するかどうかを判定しながらになるのがもどかしい。
まあ、コンマがあるおかげで1行で書いたりもできるのだが、設定ファイルのように人間が読み書きする前提だったら改行を末尾としたり、インデントでブロックと見なしたりすればいいやんという気持ちにもなってしまう。

あと、コメントが書きたいことがしばしばあるのだが、そういうのはない。
データ構造だから、まあ仕方ないよね。

 

だからかどうか知らないが YAML もよく使われる。
私の場合は docker compose の設定くらいしかお世話になっていないのだが、

いま(2024/01/14)の時点で、YAML仕様はバージョン1.2、リビジョン1.2.2(2021/10/01)が最新だそうだ。

https://spec.yaml.io/main/spec/1.2.2/

ページを開いて、スクロールバーの小ささに驚く。こんなに量が多いのか......
docker compose の YAMLファイルくらいしか覚えがなかったので、もうちょっとさらっとした仕様かと思っていた。

BMF みたいなのを読むのもだるいので、こういうときは cheat sheet で検索だ。

https://duckduckgo.com/?q=yaml+cheat&ia=web

たくさんあるので、見たいものを見ると良いだろう。
私はあまり難しいことを書きたいわけでもないので、

  • 数値の bit数については記述がないので制限はなさそう。
  • 文字列だからダブルクォーテーションで囲む必要があるという訳ではない。ただ数字だけ書くと数値として扱われるので、書く人が使い分ける。
  • 数値は10進数以外に8進数(0o)や16進数(0x)が使えるようだ(2.4. Tags)
  • 改行コードとしてはいつもの3種(LF, CR, CR/LF)が使える (5.4. Line Break Characters)
  • コメントは # 始まり (6.6. Comments)
  • 1項目は key:value のような感じ。
  • インデントは行の先頭からのスペースで表す。がTAB文字はダメ (6.1. Indentation Spaces)
  • 配列的なものはハイフンを頭に付けて、インデントを同じ高さにして並べる
    • "key:" で改行して、その下にインデントを1つ分付けて各行に並べる
  • ハイフンではなく同じインデントの高さで並べると、JSONのObjectのような扱いになる

このくらい知っておけば普段使いには困らないだろう。

docker compose の yaml でよくある「-」始まりは配列だし、インデントを付けているのも見やすさというよりもYAMLフォーマットの仕様だからということになる。

ここで "sectets" の下が key:value なのに 「-」がついていた。これは "source"から"mode"までが1つのオブジェクトで、それが複数あってもよいけどここでは1つしかないですよ、ということらしい。YAMLをJSONに変換するオンラインコンバーターのサイトがあったので試してみた。

https://jsonformatter.org/yaml-to-json

サンプル

items: 
  - name: hello 
    value: 10 
  - name: world 
    value: 20
  

JSON変換

{ 
    "items": [ 
         { 
            "name": "hello", 
            "value": 10 
        }, 
        { 
            "name": "world", 
            "value": 20 
        } 
    ] 
}
  

うむ。

「-」を付けることを考えると、インデントはスペース2つ分にするのが扱いやすいか。

2024/01/08

golangの無名関数

私が一番なじんでいる開発言語はC言語だ。
今後は変わるかもしれないが、まだ一番だ。

二番をgolangにしようとしている。まあ、C++歴もまあまあ長いのだがしばらく使っていないし、それ以外の言語も C言語っぽいから使えているという程度のように思っている。
golang も C言語っぽい。例外がなかったり、*や&を使ったりするなどバイナリになったときのイメージが C言語に近いのもよろしい。

私が golang を覚えるときは C言語と同じだったり違いだったりという差分でやろうとしている。全部覚えられるほど記憶がよくはないのだよ。。。
golang は新しい言語なので、新しい(というわけでもないだろうが)仕様がある。


Javaにもあったと思うのでそんなに新しい言語仕様ではないと思うのだが、C言語自体にはなかったはずである。型として関数を指定できるという意味では関数ポインタに近いような感じはするのだが、イコールとまではいかない。

関数の引数に func(xxx) があるとする。それだけなら関数ポインタと同じだと思う。C言語でも 同じシグネイチャの関数であれば引数に指定できるからだ。しかしそれは無名関数の機能?ではなく、単に引数として func が使えるというだけだろう。

では func をどうやって定義するかというと、ここら辺が golang の特徴ということになるだろうか。

  • グローバルの func
  • 関数の戻り値の func
  • 無名関数の func

 関数を戻り値として返すことができるということで2番目と3番目は分けたが、C言語では関数を戻り値にできなかったはずだから別にした。いや、たぶん関数ポインタとして返すことはできるのだが、関数そのものを返すということはできないはずだ。そういう意味では3番目も同じく、C言語で関数名のない関数を定義することはできなかったはずだ。

defer に直接 func で中身を書くことができたりするのは無名関数があるからだろう。局所的に関数を設定できるのは便利といえば便利だ。ローカル変数を引数で渡さなくても済むし。


そう、ローカル変数を渡さなくて良いのだ。どういうしくみなんだろうか?
クロージャと呼ぶらしいので go.dev/spec を検索したがここしか出てこなかった。

Function literals
https://go.dev/ref/spec#Function_literals

function literals だったり anonymous function だったり closures だったり、よくわからんものが出てくる。

  • function literals は anonymous function として表される
  • function literals は closures である

anonymous function は「無名関数」のこと。匿名関数と書いてあることもあるがまあいい。名前がないので特定できないから匿名だ、と思っておけば良いだろう。

function literals は「関数リテラル」だ(そのまま)。関数を値にしたものとでも思っておけば良いだろう。ということは、名前がある関数は関数リテラルにはならないということか。
しかし、関数を引数にすることはできるので、名前がある関数でも関数値にはできるはずだ。一般的に「関数を表す式」のことを関数リテラルと呼び、いろいろ言語実装があるのだがたいていの言語では無名関数を呼び出したかったら式にするしかないので、無名関数は関数リテラルが同じものになったということだろう。

クロージャは、関数周辺で定義された変数を参照できる、と書いてある。defer func() を直接書いて、その中にクローズする処理なんかを書くことができるのもクロージャだからだろうが、全然意識したことがなかった。defer だからスコープが関数の中で閉じてるし、そういうものだと思っていたのだ。

が、これは func() を返す関数でも同じことで、返す関数の中でローカル変数を参照していたとしても、それを含めて扱ってくれる。

https://go.dev/play/p/eoGL5r405rI
package main

import "fmt"

func count() func() uint8 {
	var cnt uint8
	return func() uint8 {
		cnt++
		return cnt
	}
}

func main() {
	a := count()
	b := count()
	fmt.Printf("count A: %d\n", a())
	fmt.Printf("count B: %d\n", b())
	fmt.Printf("count A: %d\n", a())
	fmt.Printf("count A: %d\n", a())
	fmt.Printf("count B: %d\n", b())
}


a と b それぞれで cnt を別々に保持してくれるので、外部でグローバル変数を用意して渡す、みたいなことをしなくてよい。不安になってしまうのだが、これは慣れるしかないだろう。

2023/12/24

golang の取りあえず context

居酒屋に入って「取りあえずビール(中ジョッキ)」とやってしまうような感覚で、golang の引数に context があったら、上位から渡されたのがあればそれを、なければ context.Background() で与えていることが多い。

もう少し調べておこう。

 

まず、context.Background() はこれだ。

https://cs.opensource.google/go/go/+/release-branch.go1.21:src/context/context.go;l=211-213

backgroundCtx{} を返すだけで、NewBackgroundCtx() という名前にしたい気もするが、これはアドレスではなくインスタンスを返すから new が付かないのだろうか。
その下に context.TODO() もある。同じようにを todoCtx{} 返すだけである。

標準の context.Context はいくつあるのだろう?

目視なのでとりこぼしがあるかもしれない。

サンプルでよく見る context.Background()context.TODO() だが、どちらも emptyCtx を組み込んだだけになっている。

context.Background() は「これで contextを始める!」という top-level の場合に使い、context.TODO() は「まだよくわからんけどこれでやっておこう」という unclear なときに使う。

cancelCtx は、名前からするとキャンセルを通知できるようになっているのだろう。
じゃあ withoutCancelCtx は? emptyCtx との違いは Value() があるくらいだろうか。cancelCtx を使っていたけどキャンセル機構をなくしたくなった場合とか?
いや、cancelCtx を直接作る関数は用意されていない(引数にcontextを取る)から扱いが違うんだろうか。

 

だいたい、なんで Background() などは type Context の下にあるのだろう? レシーバーがあれば Types の下になるのは分かるのだが、戻り値が1つでそれが type だったら Types の下にぶら下がるんだろうか。

 こんなはっきりしない終わり方は嫌なので、ちゃんと context について説明しているサイトを読もう(この記事台無し......)!

よくわかるcontextの使い方
https://zenn.dev/hsaki/books/golang-context