Setup SSL cert for dev environment with mkcert

ปกติเวลาเราต้องการทดสอบ web server แบบ https เราก็จะ gen self sign cert เช่น ใช้ openssl แต่ถ้าต้องการกำหนด DNS แบบ SAN ด้วยก็จะยุ่งยากขึ้นมาอีกหน่อย พอเปิด URL ด้วย web browser ก็จะโดนด่าตลอดว่าหน้าเว็บไม่ปลอดภัย ต้องมากด allow ทุกครั้งไป

สำหรับบล็อกนี้ผมจะมาแนะนำ mkcert เป็น tools เอาไว้ช่วย generate ssl cert เช่นกัน แต่มีความพิเศษกว่า tools อื่นๆ เพราะจะมีการ trust CA cert และ update trust store ของ OS ให้ด้วย

# TL;DR

ยาวไปไม่อ่าน ขอสั้นๆ เลย

$ mkcert -install
$ mkcert domain.com "*.domain.com" localhost 127.0.0.1 ::1

ก็จะได้ไฟล์ public/private cert ไปใช้งาน จบ !!!

# Installation

ทำการติดตั้ง mkcert ถ้าต้องการ trust root CA ให้กับ Firefox ด้วยให้ติดตั้ง nss เพิ่มดังนี้

$ brew install mkcert
$ brew install nss

# Setup SSL Cert for Local Dev

# 1. Install Local Root CA

ทำการติดตั้ง root CA กันก่อน โดยใช้คำสั่ง

$ mkcert -install

สร้าง local root CA ไว้ที่ /Users/username/Library/Application Support/mkcert โดยจะมีไฟล์ private & public cert คือ rootCA-key.pem และ rootCA.pem

เราสามารถดู path ของ root CA โดยใช้คำสั่ง

$ mkcert -CAROOT

ในขั้นตอนการ install mkcert นั้น จะทำ trust root CA เพิ่มไว้ที่ trust store ของ OS ให้ด้วย ทำให้ Web Browser ที่ใช้ trust store ของ OS อย่าง Chrome หรือ Safari จะ trust certificate ที่ออกโดย CA นี้ เมื่อเราเปิด url https ที่ address bar ก็จะไม่เป็นสีแดง

# 2. Generate SSL Cert

เราจะสร้าง SSL certificate แบบ wild card ชื่อ *.somebank.com เพื่อให้ใช้กับ sub domain ได้ เช่น dev.somebank.com โดยใช้คำสั่งดังนี้

$ mkcert somebank.com "*.somebank.com" localhost 127.0.0.1 ::1
Created a new certificate valid for the following names 📜
 - "somebank.com"
 - "*.somebank.com"
 - "localhost"
 - "127.0.0.1"
 - "::1"

The certificate is at "./somebank.com+4.pem" and the key at "./somebank.com+4-key.pem" ✅

จากตัวอย่างจะได้ cert มา 2 ไฟล์ คือ public key ชื่อ somebank.com+4.pem และ private key ชื่อ somebank.com+4-key.pem

สามารถตรวจสอบรายละเอียดของ cert ได้โดยใช้คำสั่งของ OpenSSL ดังนี้

$ openssl x509 -in somebank.com+4.pem -text -noout 

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            ed:66:a7:8c:8e:e2:61:d4:f3:dc:aa:c8:99:a7:42:e9
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: O=mkcert development CA, OU=junlapong@PHOENIX-MKIII.local, CN=mkcert junlapong@PHOENIX-MKIII.local
        Validity
            Not Before: Jul 30 04:27:49 2019 GMT
            Not After : Jul 30 04:27:49 2029 GMT
        Subject: O=mkcert development certificate, OU=junlapong@PHOENIX-MKIII.local
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
...

หรือถ้าต้องการดูเฉพาะชื่อ DNS ใช้ grep กรองเอา ดังนี้

$ openssl x509 -in somebank.com+4.pem -text -noout | grep "DNS:"

DNS:somebank.com, DNS:*.somebank.com, DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1

# 3. Add Local DNS Name

ใช้ editor ที่ถนัด เปิดไฟล์ /etc/hosts เพื่อเพิ่ม DNS Name โดยผูกไว้กับ IP 127.0.0.1

$ sudo vi /etc/hosts

ในตัวอย่างนี้จะเพิ่ม DNS Name dev.somebank.com

127.0.0.1	localhost dev.somebank.com

# 4. Example

เราจะลองสร้าง HTTP/2 server ด้วยภาษา Go กัน โดยเขียนไฟล์ main.go และกำหนด path ให้กับ public & private certs ตามตัวอย่างด้านล่าง

package main

import (
	"net/http"

	"golang.org/x/net/http2"
)

func main() {

	var srv http.Server
	srv.Addr = ":443"

	//Enable http2
	http2.ConfigureServer(&srv, nil)
	http.HandleFunc("/", homePage)

	srv.ListenAndServeTLS("/path/to/certs/somebank.com.pem", "/path/to/certs/somebank.com-key.pem")

}

func homePage(w http.ResponseWriter, r *http.Request) {

	w.Write([]byte("<h1><center> Hello from Go! </h1></center>"))

}

ทำการรันโปรแกรมด้วยคำสั่ง

$ go run main.go

ทดสอบเปิด URL https://dev.somebank.com ด้วย FireFox จะเห็นว่าลูกกุญแจที่ address bar เป็นสีเขียวแล้ว 😄 แต่ถ้าเปิดด้วย Chrome และ Safari จะเห็นกุญแจที่ address bar เป็นสีเทา

ทดสอบด้วย curl

$ export CAROOT="`mkcert -CAROOT`"
$ curl --cacert "$CAROOT/rootCA.pem" -I https://dev.somebank.com

HTTP/2 200 
content-type: text/html; charset=utf-8
content-length: 42
date: Tue, 30 Jul 2019 04:37:38 GMT

ทดสอบด้วย HTTPie

$ http --verify=no https://dev.somebank.com

HTTP/1.1 200 OK
Content-Length: 42
Content-Type: text/html; charset=utf-8
Date: Tue, 30 Jul 2019 04:47:39 GMT

<h1><center> Hello from Go! </h1></center>

# อ้างอิง

  • https://github.com/FiloSottile/mkcert
  • https://github.com/Shyp/generate-tls-cert