2022/07,江端さんの技術メモ

ローカルネットワークにおける「オレオレ証明書」の作り方


"http: TLS handshake error from 192.168.0.22:59914: remote error: tls: unknown certificate"のエラーを、ようやく消せました


1. 目的

ローカルな実験環境で、Go言語で作ったサーバを動かし、Websocket用の鍵を作成し、TLS通信(https://)を実現します

2. 狙い

最近、ブラウザの"https"縛りがきつくて、ローカルのindex.htmlを叩くだけでは、画面が出てこなくなりました。特にスマホで顕著です。セキュリティ的には良いことなのかもしれませんが、研究用の開発をする人間(私)にとっては、『正直、面倒くさいなぁ』と思っています。

あくまでインターナルな環境での動作テストで使えれば足り、インターネットに出るつもりはありません。つまりIPアドレス、直接指定で実験できれば足りるのです(e.g. https://192.168.0.8:8080)

ところが問題があります。当然、インターネットに出ないことが必要なのです(というかインターネットに出たらアウト(始末書もの))。

  • 当然、公式の認証鍵はインターネットでの使用を前提としている上に、高価です。
  • Let's encrypt (https://letsencrypt.org/ja/) は、IPアドレスは使えないようです(ドメイン名のみ)。またクローズなローカルネットでは使えません。
  • ZeroSSL (https://zenn.dev/mattn/articles/b2c4c92c9116b1) は、インターネット上でオープンされているIPアドレスには使えますが、やはりクローズなローカルネットでは使えません。

という訳で、今回は、mkcert (https://qiita.com/k_kind/items/b87777efa3d29dcc4467) を使うことにしました。

このメモでは、mkcertを使って、インターネットに繋っていないローカルネットワークで、TLS通信を実現することを目的とするものです。

3. mkcertの入手方法と鍵の作りかた

https://github.com/FiloSottile/mkcert を覗いて、バイナリがダウンロードできそうことが分かりました。

もって、ここから、Windows10で使えそうなバイナリをダウンロードして下さい。

ダウンロードしたところから、直接叩いてみたら、最初に、

# mkcert -install

しろ、と言われます。

本当はmkcertにリネームした方が良いのでしょうが、面倒なので、そのまま

# mkcert-v1.4.1-windows-amd64.exe -install

を強行しました。

その後、"localhost","127.0.0.1","192.168.0.8"の3つのアドレスを追加することにしましたので、

# mkcert-v1.4.1-windows-amd64.exe localhost 127.0.0.1 192.168.0.8

としました。

この結果は以下の通りです。

>mkcert-v1.4.1-windows-amd64.exe localhost 127.0.0.1 192.168.0.8
Using the local CA at "C:\Users\ebata\AppData\Local\mkcert" 
Created a new certificate valid for the following names 
 - "localhost"
 - "127.0.0.1"
 - "192.168.0.8"

The certificate is at "./localhost+2.pem" and the key at "./localhost+2-key.pem" 

と入力すると、鍵が、カレントディレクトリにできるようです。

で、

  • "localhost+1-key.pem"を "key.pem"とリネームして、
  • "localhost+1.pem"を"cert.pem"とリネームして、

鍵を使うプログラム(main.go, server24.go)のあるディレクトリの全部に放り込んで下さい。

4. 鍵の使い方

4.1. Goプログラム(server24.go, main.go)の修正

4.1.1. http.ListenAndServeTLS()への置き換え

さらに、main.go, server24.goのhttp.ListenAndServe()を、以下のようなhttp.ListenAndServeTLS()に置き換えて下さい

    var addr = flag.String("addr", ":8080", "http service address")

    /*
        log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
    */

    var httpErr error
    if _, err := os.Stat("./cert.pem"); err == nil {
        fmt.Println("file ", "cert.crt found switching to https")
        if httpErr = http.ListenAndServeTLS(*addr, "./cert.pem", "./key.pem", nil); httpErr != nil {
            log.Fatal("The process exited with https error: ", httpErr.Error())
        }
    } else {
        httpErr = http.ListenAndServe(*addr, nil)
        if httpErr != nil {
            log.Fatal("The process exited with http error: ", httpErr.Error())
        }
    }

4.1.2. アドレスの修正

  • "localhost"の記載を、外向きのipアドレス(192.168.0.8)に書き換えて下さい。

4.2. index.htmlの修正

  • "localhost"の記載を、外向きのipアドレス(192.168.0.8)に書き換えて下さい。

5. スマホ側への鍵の設定方法

スマホ側は、当然に、"192.168.0.8"などというあやしげなサーバを信じる訳がないので、それを信じさせる処理を行う必要があります。

これをスマホに信じさせるには、ルート証明書の作成が必要となりますが、これはすでに出来ています。

5.1. ルート証明書の場所

# mkcert-v1.4.1-windows-amd64.exe -CAROOT

で、rootCA-key.pem rootCA.pem の場所が分かります。

("key.pem"、"cert.pem"は、使わないので注意して下さい(それに気がつかずにエラい目に遭いました))

C:\Users\ebata\Downloads>mkcert-v1.4.1-windows-amd64.exe -CAROOT
C:\Users\ebata\AppData\Local\mkcert

C:\Users\ebata\Downloads>cd C:\Users\ebata\AppData\Local\mkcert

C:\Users\ebata\AppData\Local\mkcert>ls
rootCA-key.pem  rootCA.pem

5.2. iPad/iPhoneの場合

  1. スマホにrootCA.pemをメールで送り込みます。ただし、iPad/iPhoneの場合、gmailメーラからではrootCA.pemを直接インストールできないので、safariから、gmail.comにログインして、メーラーを出して、そこからrootCA.pemを叩いて取り出して下さい。
  2. 次に、「設定」をクリックすると「プロファイルがダウンロード済み」というメッセージが出てくるので、そこをクリックして、「インストール」を押して下さい(ここでしつこく「パスコードの入力」を要求されますが、くじけずに何度でも入力して下さい)
    1.ダウンロードが出来たら、[設定]→ [プロファイルがダウンロード済み]を選択して下さい。

あとは、インストールボタンを押し続けて、インストールが完了して下さい。

5.2.1. ルート証明書を信頼する

  1. [設定] → [一般] → [情報] → [証明書信頼設定] → ルート証明書を全面的に信頼するの項目で、インストールした証明書をONにして下さい。

  1. [設定] → [プロファイル]でインストールしたプロファイルを選択するとルート証明書を確認できます。

5.3. Andoroidの場合

5.3.1. ルート証明書のインストール

  1. rootCA.pem を、rootCA.cerにリネームして、スマホにrootCA.cerをメールで送り込みます。
  2. メーラからrootCA.cerをクリックすれば、インストールされます(ことがあります)。

あるいは、

  1. ルート証明書ファイルを端末の内部ストレージまたはSDカードのルートディレクトリに配置します。
  2. ストレージからのインストールまたはSDカードからのインストールを選択します。
    • ストレージからインストールする場合:
      [設定]−[ユーザー設定]−[セキュリティ]−[認証情報ストレージ]−[ストレージからのインストール]
    • SDカードからインストールする場合:
      [設定]−[ユーザー設定]−[セキュリティ]−[認証情報ストレージ]−[SDカードからのインストール]
  3. 証明書に名称を設定し、[OK]ボタンをタップして、インストールが完了します。

最後に、

  1. 証明書がインストールされたか確認します。[設定]−[ユーザー設定]−[セキュリティ]−[認証情報ストレージ]−[信頼できる認証情報]の[ユーザー]タブで確認できます(ただし、私はこのメニューは確認できませんでした)

5.4. Windowsの場合

Windowsがスマホになることはないと思いますが、リモートの端末になることはあるので、記載しておきます。

  1. rootCA.pemを、rootCA.cerにリネームして、Windowsに送り込みます。
  2. rootCA.cerを右クリックして、「証明書のインストール」を選びます。
  3. 「証明書をすべて次のストアに配置する(P)」のラジオボタンをクリックし、「参照」ボタンをクリックします。
    1.「使用する証明書ストアを選択してください(C)」の画面で、「信頼されたルート証明機関」を選択します。

  1. 先ほどの画面に戻るので「次へ」ボタンを押下し、さらに「完了」ボタンを押下して下さい。正しくインポートされました。」と表示されればインストール成功です。

6. 結果

上記の処理を行うことで、iPhone,iPad,Android,WindowsBoxからのアクセスを行っても、サーバのコンソールから、

http: TLS handshake error from 192.168.0.22:59914: remote error: tls: unknown certificate
http: TLS handshake error from 192.168.0.22:59920: remote error: tls: unknown certificate
http: TLS handshake error from 192.168.0.22:59922: remote error: tls: unknown certificate

が出てこなくなり、ローカルネットでのセキュアなWebSocket通信が実現できる目処が立ちました。

以上

2022/07,江端さんの技術メモ

筋トレには興味ないのですが、『視野に入るところに「鉄棒」があれば、なんとなくぶら下がってみたくなる』というのは、新しい発見でした。

私は、年内に「1回の懸垂」を目標としていたのですが、現在、「20回の懸垂」を、1日2セットやっています。

I had set a goal of "one pull-up" by the end of the year, and now I am doing "20 pull-ups" two sets a day.

仕事が嫌になると、なんとなく部屋の鉄棒にぶら下がっているうちに、こんなことになっていました。

This was the case that I end up hanging out on the bars in my room, whenever I tired to work.

―― リモートオフィス恐るべし

"Remote work, that is amazing"

-----

江端:「今、私は、10代を含めて、人生で一番マッスルだと思う」

Ebata: "Right now, I think I have the most muscle in my life, including my teenage years"

嫁さん:「それは、それで、どうかなと思うぞ」

Wife: "I'm not so sure about that"

2022/07,江端さんの技術メモ

// go get github.com/lib/pq を忘れずに
// go run main12.go

/*
	Channelによるブロックを回避する方法として、Goのタイマー time.Timerで、定期的にブロックを破れるかのテストプログラム
*/

package main

import (
	"fmt"
	"time"

	_ "github.com/lib/pq"
)

var Ch1 chan interface{}

func channel_maker() {
	for {
		time.Sleep(2 * time.Second) // 2秒待つ ()
		Ch1 <- "Ebata is great"
	}
}

func main() {
	Ch1 = make(chan interface{}) // チャネルの初期化
	go channel_maker()

	ping := time.NewTimer(5 * time.Second) // イベントが何もなくても5秒後に発火するようにする
	defer ping.Stop()                      // main()を抜ける前に無効にしておく(なくてもいいかも)

	for {
		select {
		case a := <-Ch1:
			fmt.Println(a)
		case <-ping.C:
			fmt.Println("A ping is coming")
			ping = time.NewTimer(5 * time.Second) // イベントが何もなくても5秒後に発火するようにする
		}
	}
}

うむ・・・ちゃんと動く。困った。

2022/07,江端さんの技術メモ

// go run main3.go

/*
	(1)固定長配列を試してみる
*/

package main

import "fmt"

type LocInfo struct {
	Lon float64
	Lat float64
}

func main() {

	var Li [60]LocInfo // 要素0で初期化されている

	for i := 0; i < 60; i++ {
		Li[i].Lon = float64(i)
		Li[i].Lat = float64(i)
	}

	fmt.Println(Li)

	Li[32].Lon = 0.001
	Li[32].Lat = 0.001

	fmt.Println(Li)

}

2022/07,江端さんの技術メモ

// go get github.com/lib/pq を忘れずに
// go run main9.go

/*
	(1)GolangでOpenStreetMap上にマップマッピングするプリミティブな江端式定番マッピング方法
	(http://kobore.net/over90.jpg参照)
*/

package main

import (
	"database/sql"
	"fmt"
	"log"
	"math"

	_ "github.com/lib/pq"
)

var source int
var longitude float64
var latitude float64
var dist float64

func rad2deg(a float64) float64 {
	return a / math.Pi * 180.0
}

func deg2rad(a float64) float64 {
	return a / 180.0 * math.Pi
}

func distance_km(a_longitude, a_latitude, b_longitude, b_latitude float64) (float64, float64) {
	earth_r := 6378.137

	loRe := deg2rad(b_longitude - a_longitude) // 東西  経度は135度
	laRe := deg2rad(b_latitude - a_latitude)   // 南北  緯度は34度39分

	EWD := math.Cos(deg2rad(a_latitude)) * earth_r * loRe // 東西距離
	NSD := earth_r * laRe                                 //南北距離

	distance_km := math.Sqrt(math.Pow(NSD, 2) + math.Pow(EWD, 2))
	rad_up := math.Atan2(NSD, EWD)

	return distance_km, rad_up
}

func diff_longitude(diff_p_x, latitude float64) float64 {

	earth_r := 6378.137
	// ↓ これが正解だけど、
	loRe := diff_p_x / earth_r / math.Cos(deg2rad(latitude)) // 東西
	// 面倒なので、これで統一しよう(あまり差が出ないしね)
	//loRe := diff_p_x / earth_r / math.Cos(deg2rad(35.700759)) // 東西
	diff_lo := rad2deg(loRe) // 東西

	return diff_lo // 東西
}

func diff_latitude(diff_p_y float64) float64 {
	earth_r := 6378.137
	laRe := diff_p_y / earth_r // 南北
	diff_la := rad2deg(laRe)   // 南北

	return diff_la // 南北
}

func main() {
	db, err := sql.Open("postgres", "user=postgres password=password host=localhost port=15432 dbname=utsu_rail_db sslmode=disable")
	if err != nil {
		log.Fatal("OpenError: ", err)
	}
	defer db.Close()

	rows, err := db.Query("SELECT seq,x1,y1 from lrt")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	x1, y1 := -1.0, -1.0
	_x1, _y1, _x2, _y2 := -1.0, -1.0, -1.0, -1.0
	px, py := -1.0, -1.0
	flag := 0
	f_flag := 0
	seq := -1

	for rows.Next() {

		if f_flag == 0 { // 初回だけ2二回入力
			if err := rows.Scan(&seq, &x1, &y1); err != nil {
				fmt.Println(err)
			}
			_x1, _y1 = x1, y1
			//fmt.Println(x1, y1)
			f_flag = 1
			continue
		}

		if err := rows.Scan(&seq, &x1, &y1); err != nil {
			fmt.Println(err)
		}
		//fmt.Println(seq, ",", x1, ",", y1)

		_x2, _y2 = x1, y1

		_, rad_up := distance_km(_x1, _y1, _x2, _y2)

		px, py = _x1, _y1

		for {
			// 5.56m/s → 時速20

			px += diff_longitude(0.00556*2*math.Cos(rad_up), py)
			py += diff_latitude(0.00556 * 2 * math.Sin(rad_up))

			//double rad0 = atan2((end_y - start_y),(end_x - start_x));
			//double rad1 = atan2((end_y - test_person.p_y),(end_x - test_person.p_x));

			rad0 := math.Atan2((_y2 - _y1), (_x2 - _x1))
			rad1 := math.Atan2((_y2 - py), (_x2 - px))
			// ここは、http://kobore.net/over90.jpg で解説してある

			if math.Abs(rad0-rad1) >= math.Pi*0.5 {
				// 終点越えの場合、終点に座標を矯正する
				px, py = _x2, _y2
				flag = 1 // フラグを上げろ
			}

			fmt.Println(px, ",", py)

			if flag == 1 {
				flag = 0
				_x1, _y1 = _x2, _y2
				break
			}
		}

	}

	if err := db.Ping(); err != nil {
		log.Fatal("PingError: ", err)
	}

}

2022/07,江端さんの技術メモ

// go get github.com/lib/pq を忘れずに

package main

import (
	"fmt"

	_ "github.com/lib/pq"
)

// GetLoc GetLoc
type GetLoc struct {
	ID    int     `json:"id"`
	Lat   float64 `json:"lat"`
	Lng   float64 `json:"lng"`
	TYPE  string  `json:"type"` // "USER","BUS","CONTROL
	POPUP int     `json:"popup"`
	//Address string  `json:"address"`
}

func person(gl2, gl3 *GetLoc) {

	if gl2.Lng > 0.0 {
		fmt.Println("pass1", gl2)
	} else {
		fmt.Println("pass2", gl2)
	}
}

func person_real() {
	var gl2, gl3 GetLoc

	gl2.Lng = 139.00

	person(&gl2, &gl3)

}

func main() {

	var gl2, gl3 GetLoc

	person(&gl2, &gl3)

	person_real()

}

2022/07,江端さんの技術メモ

の後で、「一体、江端は何を考えているんだ」と思われるかもしれませんが、『宇都宮ライトレールの利用を拒否させるような、ダイクストラをどうやって作ろうか』と考えています ―― しかも、できるだけ手を抜いて。https://wp.kobore.net/%e6%b1%9f%e7%ab%af%e3%81%95%e3%82%93%e3%81%ae%e6%8a%80%e8%a1%93%e3%83%a1%e3%83%a2/post-6473/

ところで、今、いくつかDBを作っていますが、混乱しかけているので、メモを残しておきます。

utsu_tram_db  : 道路と鉄道の強制結合
utsu_tram_db2: 鉄道のコストを下げて、宇都宮ライトレールを優先的に選ばれるようにした
utsu_tram_db3: 宇都宮ライトレールを単線にして、取り扱いをラクにした

ただ、今、ここで、バスが宇都宮ライトレールの上を驀進するようになってきましたので、これを何とかしないといけなくなりました。

ここで逆転の発想で、

utsu_tram_db4: 宇都宮ライトレールを誰も使いたくなくなるくらいに、コストを爆上げしてやればいい

と気がつきました。

で、utsu_tram_db3と、utsu_tram_db4を併用してやれば良い、と気がつきました。


この続きを記載したのですが、反映に失敗したようです。

という訳で簡単に説明しますと、utsu_tram_db3のコスト(現在0.2倍)を、逆に100倍にしたものをutsu_tram_db4として作成しました。現在上手く動いています(色々失敗もしましたが、それを書き残す気力は、もうありません。この週末、20時間以上コーディングしていて、フラフラです)


ちなみに、上記の作業で作ったデータベースを、他の人に渡す為に、以下の作業を行いました。

# pg_dump -U postgres -p 15432 utsu_tram_db3 > utsu_tram_db3.sql
# pg_dump -U postgres -p 15432 utsu_tram_db4 > utsu_tram_db4.sql

で作った、2つのデータベースのダンプ(utsu_tram_db3.sqlと、utsu_tram_db4.sql)を圧縮したのが、こちら。

utsu_tram_db.zip

これを解凍して、

# psql -U postgres -p 15432 utsu_tram_db3 < utsu_tram_db3.sql
# psql -U postgres -p 15432 utsu_tram_db4 < utsu_tram_db4.sql

で、PostgreSQLにインポートできます(source番号なども完全一致する(はず))。