mTLS เรื่องที่ Developer, Security และ Operation ควรรู้จัก

mTLS คืออะไร ทำงานอย่างไร และจะหยิบ NGINX และ curl มาเป็นเครื่องมือในการเรียนรู้

author image
drs

Technology evangelistic advocacy

Posted on 2023-12-31 00:51:47 +0700

เริ่มจาก TLS

Transport Layer Security (TLS) เป็นโปรโตคอล (นิยามข้อตกลงในการสื่อสาร เพื่อวัตประสงค์บางอย่างที่ตกลงกัน) ที่ออกแบบมาสำหรับการสื่อสารผ่านอินเทอร์เน็ต เพื่อป้องกันการดักฟัง การดัดแปลง และการปลอมแปลงข้อความ ดังนั้นเราจะเป็น TLS จึงถูกนำไปประยุกต์ใช้ได้กับหลาย ๆ กรณี เพื่อปรับปรุงการสื่อสารที่อาจจะมีความเสี่ยงกับการถูกดักฟัง ดัดแปลง และปลอมแปลงข้อความ

หนึ่งในโปรโตคอลที่นำเอา TLS ไปทำงานร่วมกัน และพวกเราคุ้นเคยกันดี ก็คือ HTTP ที่ถูกนำเสนอในเดือน พฤษภาคม 2543 ในหัวข้อ HTTP Over TLS ​มีรายละเอียดการทำเอา TLS ทำงานร่วมกัน HTTP เพื่อให้การสื่อสารด้วย HTTP มีความปลอดภัยมากขึ้น ในรายละเอียดยังมีการเอ่ยถึงการนิยายม URI ที่จากเดิมที่เป็น http ให้ใช้เป็น https ในกรณีที่ใช้ HTTP Over TLS

Mutual TLS authentication

ถ้าไปหาคำแปลของคำว่า mutual จะเจอว่าหนึ่งในคำแปลก็คือ "ทั้งสองฝ่าย" ... แล้ว authentication มันไปเกี่ยวอะไรกับ TLS ... ถ้าจะเจาะจงไปที่ HTTPs กันไปเลย เวลาที่เราใช้ browser (client) เพื่อร้องขอไปยัง server ด้วย URL ที่เป็น https ... ตอนที่กำลังเริ่มเชื่อมต่อกัน ที่ฝั่ง server จะมีการส่ง server certificate มาให้ จากนั่นที่ฝั่ง browser จะตรวจสอบ signature ใน server certificate ว่าที่ฝั่ง browser (client) รู้จักไหม (ขอไม่ลงรายละเอียดนะครับ ไม่งั้นยาวแน่ ๆ) ถ้าตรวจสอบแล้วว่าไม่รู้จัก browser เองการจะไม่ยอมทำงานต่อ และแสดงความข้อความที่ไม่ยอมเชื่อมต่อกับ server ออกมาเพื่อให้ผู้ใช้ทราบ

ในทางกลับกัน ฝั่ง server ต้องการตรวจสอบฝั่ง browser บ้างล่ะ? ... ถ้าดูรายละเอียดในโปรโตคอล TLS จะเจอว่าที่ฝั่ง server สามารถร้องขอ certificate จากฝั่ง client ได้เหมือนกัน โดยการส่ง "CertificateRequest" กลับมาด้วย ถ้าฝั่ง client ไม่ส่ง client certficate มาให้ หรือ ส่งมาแล้วฝั่ง server ตรวจสอบแล้วไม่รู้จัก ฝั่ง server ก็จะปฎิเสธการเชื่อมต่อ อาจจะส่ง HTTP Code ตามที่กำหนดไว้กลับมาก็ได้

       Client                                           Server

Key  ^ ClientHello
Exch | + key_share*
     | + signature_algorithms*
     | + psk_key_exchange_modes*
     v + pre_shared_key*       -------->
                                                  ServerHello  ^ Key
                                                 + key_share*  | Exch
                                            + pre_shared_key*  v
                                        {EncryptedExtensions}  ^  Server
                                        {CertificateRequest*}  v  Params
                                               {Certificate*}  ^
                                         {CertificateVerify*}  | Auth
                                                   {Finished}  v
                               <--------  [Application Data*]
     ^ {Certificate*}
Auth | {CertificateVerify*}
     v {Finished}              -------->
       [Application Data]      <------->  [Application Data]

              +  Indicates noteworthy extensions sent in the
                 previously noted message.

              *  Indicates optional or situation-dependent
                 messages/extensions that are not always sent.

              {} Indicates messages protected using keys
                 derived from a [sender]_handshake_traffic_secret.

              [] Indicates messages protected using keys
                 derived from [sender]_application_traffic_secret_N.

                  Message Flow for Full TLS Handshake

Reference: The Transport Layer Security (TLS) Protocol Version 1.3

ให้ Server ส่ง CertificateRequest มาที่ Client

ในบทความนี้เราจะใช้ NGINX เป็นตัวอย่างในการทดสอบ และเรียนรู้เรื่อง mTLS ... โดยปกติ ที่เราเห็นกับอยู่โดยทั่วไป เราก็มักจะเห็นว่า NGINX จะทำให้หน้าที่รองรับการร้องข้อ HTTP over TLS และจะไม่มีการส่ง "CertificateRequest" กลับมาที่ฝั่ง Client

หากเราต้องการให้ NGINX ถามหา Client Certificate และตรวจสอบ ต้องเพิ่ม configuration ในส่วนที่เกี่ยวข้องกับ TLS ดังนี้

  • ssl_verify_client เป็นการกำหนดให้ NGINX ส่ง CertificateRequest และตรวจสอบในเงื่อนไขที่กำหนด โดยปกติหาไม่มีการกำหนดค่า ssl_verify_client จะมีค่าเป็น off ดังนั้น เราจะไม่เห็นการร้องขอ certificate ไปที่ client และหากเราต้องการให้มีการส่ง CertificateRequest ไป ก็ให้กำหนดค่านี้เป็น on ... นอกจากตัวเลือกที่เป็น on และ off แล้วยังไม่อีก 2 ทางเลือก คือ optional ก็เปิดให้เป็นทางเลือกว่าฝั่ง client จะส่ง หรือไม่ส่ง Client Certficate มาก็ได้ แต่จะไปเลือกตรวจสอบที่ตัวแปร ssl_client_verify แทน กับอีกตัวเลือกก็คือ optional_no_ca ก็เปิดให้เป็นทางเลือกว่าฝั่ง client จะส่ง หรือไม่ส่ง Client Certficate และมีตรวจสอบว่า Trusted CA เป็นคน sigend มาหรือไม่ ซึ่งทางฝั่ง server สามารถนำ certificate ไปใช้งานได้ผ่านตัวแปร ssl_client_cert
  • ssl_client_certificate เป็นการกำหนดไฟล์ที่เก็บ Trusted CA Certificate เพื่อเอาไว้ตรวจสอบ Client Certificate

ตัวอย่าง NGINX Configuration ที่กำหนดให้ server ร้องขอ Client Certificate


server {
    listen 443 ssl ;
    listen [::]:443 ssl ;

    ssl_certificate /etc/nginx/conf.d/server1.crt;
    ssl_certificate_key /etc/nginx/conf.d/server1.key;

    ssl_dhparam /etc/nginx/conf.d/dhparam;
    error_log /tmp/log debug;

    ssl_client_certificate /etc/nginx/conf.d/ca.crt;
    # เลือกกำหนดอย่างได้อย่างหนึ่ง
    ssl_verify_client      {off,on,optional,optional_no_ca};
   
    location / {
    location / {
      default_type text/plain;
      if ($ssl_client_verify != SUCCESS) {
        return 403 "Unauthorized Access\n";
      }
      return 200 'Welcome to my service';
    }
}

ข้อมูลเบื้องต้นของระบบที่ใช้ในการทดสอบดังนี้

  • NGINX ในรูปแบบของ container โดยใช้ Office NGINX Image จาก Docker Hub ในวันที่ทดสอบเป็น NGINX v1.25.3
  • hostname ชื่อว่า server.d8k.dev
  • เลือกใช้ ssl_verify_client เป็น optional
  • ตรวจสอบผลการ verify ตัว Client Certificate Verification ผ่านตัวแปร $ssl_client_verify
  • curl 8.4.0 (x86_64-apple-darwin23.0) บน Macbook Air M2
  • ca.key และ ca.crt เป็น Key Pair ที่ทำหน้าที่เป็น Certificate Authority
  • server1.key และ server1.crt เป็น Key Pair สำหรับฝั่ง server ที่ถูกกำหนดให้มีคุณสมบัติ TLS Web Server Authentication
  • user1.key และ user1.crt เป็น Key Pair สำหรับฝั่ง client ที่ถูกกำหนดให้มีคุณสมบัติ TLS Web Client Authentication
  • stranger.key และ stranger.crt เป็น Key Pair คู่เดียวที่ไม่ได้ถูก signed โดย Certificate Authority เพื่อใช้ในการทดสอบฝั่ง client

วิธีการสร้าง key pair

openssl genrsa -out ca.key 4096
openssl req -new -key ca.key -subj "/CN=My CA" -out ca.csr
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt -extfile myca.cnf -extensions v3_ca

openssl genrsa -out server1.key 4096
openssl req -new -key server1.key -subj "/CN=server1" -out server1.csr
echo subjectAltName = DNS:localhost,DNS:server.d8k.dev,IP:192.168.254.108,IP:127.0.0.1 >> server1.cnf
echo extendedKeyUsage = serverAuth >> server1.cnf
openssl x509 -req -in server1.csr -CAkey ca.key -CA ca.crt -out server1.crt -CAcreateserial -extfile server1.cnf

openssl genrsa -out user1.key 4096
openssl req -new -key user1.key -subj "/CN=user1"  -out user1.csr
echo extendedKeyUsage = clientAuth >> user1.cnf
openssl x509 -req -in user1.csr -CAkey ca.key -CA ca.crt -out user1.crt -CAcreateserial -extfile user1.cnf

openssl genrsa -out stranger.key 4096
openssl req -new -key stranger.key -subj "/CN=stranger" -out stranger.csr
openssl x509 -req -in stranger.csr -signkey stranger.key -out stranger.crt

openssl dhparam -out dhparam 2048

ที่ฝั่ง client ใช้คำสั่ง curl ในการทดสอบ

curl เป็นเครื่องมือที่ทำหน้าที่เป็น client เพื่อร้องขอไปยัง server ปลายทางที่รองร้บ protocol ที่หลากหลาย ในกรณีของเรา เราจะใช้ curl ร้องขอไปยัง server ปลายที่ ด้วย HTTP over TLS หรือ  https แล้วให้ส่ง Client Certification ไปด้วย

คำสั่ง ​curl ที่เรียกใช้ option ในการทดสอบดังนี้

  • --cacert <file> เพื่อบอกกับ curl ว่าให้ใช้ certificate ตัวนี้ในการตรวจสอบ Service Certificate ดังนั้นในกรณีที่ฝั่ง server เป็น Self Signed Certificate อย่างในกรณีที่จะทดสอบ เพื่อให้ curl ตรวจสอบ certificate ได้ จึงต้องกำหนดค่า cacert
  • --cert <certificate[:password]> เพื่อกำหนดไฟล์ Client Certficate เมื่อมีการร้องขอจาก server
  • --key <key> เพื่อกำหนดไฟล์ Private Key ที่คู่กับ Client Certificate

หลังจากที่สั่งให้ NGINX เริ่มทำงาน โดยกำหนด ให้ ssl_verify_client optional; แล้ว และ กำหนด ไฟล์ CA Certficate ที่ siged ให้กับ user1.crt ให้กับ ssl_client_certificate แล้ว มาลองดูผลการทำงานของ NGINX ที่ทดสอบด้วย curl ดังนี้

  1. ทดสอบโดยเรียกคำสั่ง curl https://server.d8k.dev

  2. > curl https://server.d8k.dev
    curl: (60) SSL certificate problem: unable to get local issuer certificate
    More details here: https://curl.se/docs/sslcerts.html
    
    curl failed to verify the legitimacy of the server and therefore could not
    establish a secure connection to it. To learn more about this situation and
    how to fix it, please visit the web page mentioned above.
    
  3. ทดสอบโดยเรียกคำสั่ง curl --cacert ca.crt https://server.d8k.dev เพื่อกำหนดให้ curl ใช้ไฟล์ ca.crt ตรวจสอบ Server Certificate

  4.  > curl --cacert ./ca.crt https://server.d8k.dev
    Unauthorized Access
    
  5. ทดสอบโดยเรียกคำสั่ง curt https://server.d8k.dev พร้อม option --cacert, --key และ --cert โดยใช้ key pair ที่ถูก signed โดย CA

  6. > curl --cacert ./ca.crt --key user1.key --cert user1.crt https://server.d8k.dev
    Welcome to my service
    
  7. ทดสอบโดยเรียกคำสั่ง curl https://server.d8k.dev พร้อม option --cacert, --key และ --cert โดยใช้ key pair ที่ไม่ได้ถูก signed โดย CA

  8. > curl --cacert ./ca.crt --key stranger.key --cert stranger.crt https://server.d8k.dev
    <html>
    <head><title>400 The SSL certificate error</title></head>
    <body>
    <center><h1>400 Bad Request</h1></center>
    <center>The SSL certificate error</center>
    <hr><center>nginx/1.25.3</center>
    </body>
    </html>
    

Reference:



cover


เก็บตก นิดหน่อย

สำหรับท่านที่สนใจ ศึกษาเรื่อง TLS แบบจริงจัง อยากเห็นว่าในแต่ละขั้นนตอนในการเชื่อมต่อ TLS ใน HTTPS เป็นอย่างไร แนะนำคำสั่งนี้เลยครับ

> echo -e "GET /\n" | openssl s_client  -key user1.key -cert user1.crt -connect server.d8k.dev:443 -CAfile ca.crt -state -ign_eof
Connecting to 192.168.254.108
CONNECTED(00000006)
SSL_connect:before SSL initialization
SSL_connect:SSLv3/TLS write client hello
SSL_connect:SSLv3/TLS write client hello
SSL_connect:SSLv3/TLS read server hello
SSL_connect:TLSv1.3 read encrypted extensions
SSL_connect:SSLv3/TLS read server certificate request
depth=1 CN=My CA
verify return:1
depth=0 CN=server.d8k.dev
verify return:1
SSL_connect:SSLv3/TLS read server certificate
SSL_connect:TLSv1.3 read server certificate verify
SSL_connect:SSLv3/TLS read finished
SSL_connect:SSLv3/TLS write change cipher spec
SSL_connect:SSLv3/TLS write client certificate
SSL_connect:SSLv3/TLS write certificate verify
SSL_connect:SSLv3/TLS write finished
---
Certificate chain
 0 s:CN=server.d8k.dev
   i:CN=My CA
   a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256
   v:NotBefore: Dec 29 19:58:31 2023 GMT; NotAfter: Jan 28 19:58:31 2024 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFQjCCAyqgAwIBAgIUBmayoWOT0D+a4dczHLVqGBJI04IwDQYJKoZIhvcNAQEL
BQAwEDEOMAwGA1UEAwwFTXkgQ0EwHhcNMjMxMjI5MTk1ODMxWhcNMjQwMTI4MTk1
[... truncated output ...]
VtRYR6ndfVMDbWR1aC4DeQAIgUBO0Z7QVZDQ3fd5ienbqoxVgtTkyuHcurzseqE4
Boq7zjrOWaXrvsq3ZUWu9AvIXTyKqxte0B9V2Cxcp+nkKEJC93oRr84vjCqv/oLi
zT++Zm0E
-----END CERTIFICATE-----
subject=CN=server.d8k.dev
issuer=CN=My CA
---
Acceptable client certificate CA names
CN=My CA
Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:Ed25519:Ed448:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:RSA+SHA224
Shared Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:Ed25519:Ed448:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2259 bytes and written 3553 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 4096 bit
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSL negotiation finished successfully
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    Session-ID: ABFFBB7B7AA8ECFB39D8941239643B1101E6A456DEA12F8A3BF7AA94E9F430F0
    Session-ID-ctx:
    Resumption PSK: C55FBA0997BBEBECC8A2A9237F2A4F562DB882C18B7BC816B807F349D425B6F943F7A16947ABFFC021435153217B752A
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - ce 41 16 79 dc c4 df 7b-dc 1e 09 e9 ef fe e6 37   .A.y...{.......7
    0010 - 4e f3 5e 23 c1 21 3a 10-1e c1 40 92 d7 75 01 3c   N.^#.!:...@..u.<
    [... truncated output ...]
    05e0 - a9 a2 75 32 74 d4 57 b8-af 3c a2 fd 24 c6 35 b2   ..u2t.W..<..$.5.
    05f0 - 97 6e 9a 63 8b 14 1c dd-dc 35 ab 9d bf 62 ee 8c   .n.c.....5...b..

    Start Time: 1704005301
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
    Max Early Data: 0
---
SSL_connect:SSLv3/TLS read server session ticket
read R BLOCK
SSL_connect:SSL negotiation finished successfully
SSL_connect:SSL negotiation finished successfully
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    Session-ID: 70FB69D829601BA65B2C7C200E28612F6D64210904DF26DD3F7D0D5831BC1E6F
    Session-ID-ctx:
    Resumption PSK: 37FC3E74B70A1B9500E7CCE70D9B1A6DDC3B7F11ECA6A3D32BDCB90C2F43856794C33305653DF50A3FA87AB1946C7586
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - ce 41 16 79 dc c4 df 7b-dc 1e 09 e9 ef fe e6 37   .A.y...{.......7
    0010 - a3 c2 ac 6f 2b 16 76 f1-6d 6f 38 b4 86 93 68 85   ...o+.v.mo8...h.
    [... truncated output ...]
    05e0 - 6b 01 9f 77 f0 a4 d1 f0-88 75 ac 58 e7 ed 8a fb   k..w.....u.X....
    05f0 - be 56 0f 3a 2e f9 f4 a8-58 c2 0d ff 1a 16 29 68   .V.:....X.....)h

    Start Time: 1704005301
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
    Max Early Data: 0
---
SSL_connect:SSLv3/TLS read server session ticket
read R BLOCK
Welcome to my service
SSL3 alert read:warning:close notify
closed
SSL3 alert write:warning:close notify

Share on

Tags

Human knowledge belongs to the world

a line from the movie "Antitrust"