SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor

วิธีเพิ่ม CA ของคุณเองลงใน Trust Store ของ Ubuntu

เรียนรู้วิธีติดตั้ง Root CA ในไดเรกทอรี /usr/local/share/ca-certificates และใช้คำสั่ง update-ca-certificates เพื่อให้ Ubuntu เชื่อถือใบรับรอง HTTPS ภายในของคุณอย่างถูกต้อง

การเพิ่ม CA ของคุณเองลงใน trust store ของ Ubuntu

หากต้องการเพิ่ม CA ของคุณเองลงใน trust store ของ Ubuntu ให้คัดลอก root certificate ไปไว้ใน /usr/local/share/ca-certificates/ โดยตั้งชื่อไฟล์ให้ลงท้ายด้วย .crt จากนั้นรันคำสั่ง sudo update-ca-certificates ทั้งนี้ CA (certificate authority) คือคู่กุญแจที่ใบรับรองได้รับอนุญาตให้ลงนามในใบรับรองอื่นได้ เมื่อเครื่องของคุณเชื่อถือ root ของคุณแล้ว ใบรับรองทุกใบที่ root นั้นลงนามจะได้รับการยอมรับ ส่งผลให้การเชื่อมต่อ HTTPS ระหว่างบริการของคุณเองไม่ล้มเหลวในการตรวจสอบอีกต่อไป

คู่มือนี้จะสร้าง chain ทั้งหมดแบบออฟไลน์ด้วย openssl โดยคุณจะต้องสร้าง root key และ root certificate จากนั้นออกใบรับรอง leaf certificate สำหรับเซิร์ฟเวอร์หนึ่งใบ แล้วจึงติดตั้ง root และสังเกตการเปลี่ยนแปลงของผลลัพธ์จากคำสั่งตรวจสอบ ลำดับขั้นตอนดังกล่าวคือประเด็นสำคัญ เพราะการตรวจสอบก่อนและหลังการติดตั้งจะช่วยให้คุณเห็นว่าการติดตั้งคือสิ่งที่ทำให้ผลลัพธ์เปลี่ยนไป

Ubuntu 24.04 มาพร้อมกับ OpenSSL 3 และแพ็กเกจ ca-certificates ในอิมเมจเริ่มต้น ดังนั้นจึงไม่มีสิ่งใดที่ต้องติดตั้งเพิ่มเติม (ตรวจสอบเมื่อเดือนสิงหาคม 2026)

เมื่อใดที่คุณควรใช้งาน CA ของตนเอง

Public CA เช่น Let's Encrypt จำเป็นต้องมีชื่อใน public DNS และเซิร์ฟเวอร์ที่สามารถเข้าถึงได้ ชื่อภายใน (internal names) จึงไม่เข้าเงื่อนไขนี้ ฐานข้อมูลบนเครือข่ายส่วนตัวหรือแผงควบคุมผู้ดูแลระบบที่ผูกไว้กับ tunnel ไม่สามารถขอ public certificate ได้ และไม่ควรเปิดเผยบริการเหล่านี้สู่สาธารณะเพียงเพื่อขอรับ certificate

การใช้ self-signed certificate บน Ubuntu แก้ปัญหาได้เพียงโฮสต์เดียวเท่านั้น ไคลเอนต์แต่ละรายต้องเชื่อถือ certificate นั้น และเมื่อมีโฮสต์ถัดไปก็ต้องเริ่มกระบวนการเดิมซ้ำอีกครั้ง การมี private CA จะช่วยยกระดับการจัดการขึ้นไปอีกขั้น ไคลเอนต์จะเชื่อถือ root CA เพียงครั้งเดียว และ certificate ทุกใบที่ root CA ลงนามหลังจากนั้นจะได้รับความเชื่อถือโดยอัตโนมัติ รวมถึง certificate สำหรับโฮสต์ที่ยังไม่มีตัวตนอยู่ในขณะนี้ด้วย

ต้นทุนที่ต้องแลกมานั้นเป็นเรื่องจริง root key สามารถลงนามในสิ่งใดก็ได้ตามที่ข้อกำหนดอนุญาต ดังนั้นใครก็ตามที่อ่าน ca.key ได้ จะสามารถออก certificate ที่เครื่องของคุณยอมรับได้ทันที คุณต้องปกป้อง root key นี้เช่นเดียวกับที่คุณปกป้อง private key ใน การจัดการ SSH key หากบริการของคุณมีชื่อ public DNS ให้ข้ามขั้นตอนเหล่านี้ทั้งหมดแล้วใช้ public CA แทน เพราะ Certbot กับ nginx และ Let's Encrypt ใช้แรงน้อยกว่าและไม่จำเป็นต้องติดตั้งซอฟต์แวร์ใดๆ เพิ่มเติมที่ฝั่งไคลเอนต์

สร้าง CA key และ root certificate

ให้ทำงานในไดเรกทอรีที่เฉพาะผู้ใช้ของคุณเท่านั้นที่เปิดอ่านได้ โดย root key จะต้องไม่ถูกย้ายออกจากไดเรกทอรีนี้

install -d -m 700 ~/ca
cd ~/ca
openssl genrsa -aes256 -out ca.key 4096
chmod 600 ca.key

-aes256 จะเข้ารหัส key ด้วยรหัสผ่านที่คุณกำหนด และคำสั่งถัดไปทุกคำสั่งที่ใช้ key นี้ในการลงนามจะเรียกขอรหัสผ่านดังกล่าว หากคุณละเว้น -aes256 ตัว key จะถูกเก็บไว้บนดิสก์ในรูปแบบที่ไม่ได้เข้ารหัส ซึ่งจะทำให้การสำรองข้อมูลหรือการมีบัญชีผู้ดูแลระบบอื่นเพียงบัญชีเดียวก็เพียงพอที่จะทำให้ผู้อื่นสามารถออกใบรับรองที่เครื่องของคุณเชื่อถือได้

จากนั้นให้สร้าง root certificate ซึ่งเป็นใบรับรองที่ CA key จะลงนามรับรองด้วยตนเอง

openssl req -x509 -new -key ca.key -sha256 -days 3650 \
  -subj "/O=Example Internal/CN=Example Internal Root CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" \
  -addext "subjectKeyIdentifier=hash" \
  -addext "nameConstraints=critical,permitted;DNS:internal.example" \
  -out ca.crt

ให้แทนที่ internal.example ด้วยส่วนต่อท้ายชื่อที่คุณใช้งานจริง และโปรดอ่านส่วนถัดไปก่อนที่คุณจะเก็บส่วนขยาย (extension) สุดท้ายนั้นไว้

ส่วนขยายแต่ละรายการมีหน้าที่เฉพาะตัวดังนี้:

  • basicConstraints ร่วมกับ CA:TRUE คือสิ่งที่ทำให้ใบรับรองนี้เป็น CA certificate หากไม่มีส่วนนี้ ไคลเอนต์จะปฏิเสธใบรับรองใดก็ตามที่ key นี้ลงนาม แม้ว่าลายเซ็นจะถูกต้องก็ตาม
  • pathlen:0 ระบุว่า CA นี้สามารถลงนามใบรับรองระดับปลายทาง (leaf certificate) ได้ แต่ไม่สามารถลงนาม CA อื่นที่อยู่ต่ำกว่าได้
  • keyUsage จำกัดการใช้งาน key ให้ทำได้เพียงการลงนามใบรับรองและรายการเพิกถอน (revocation list) เท่านั้น เพื่อป้องกันไม่ให้ key เดียวกันถูกนำไปใช้เป็น TLS server key โดยไม่ได้ตั้งใจ
  • subjectKeyIdentifier กำหนดตัวระบุ (identifier) ให้กับ root เพื่อให้ leaf certificate อ้างอิงกลับมาได้ ซึ่งเป็นวิธีที่ไคลเอนต์ใช้ค้นหาผู้ออกใบรับรองที่ถูกต้องจากรายการที่มีอยู่หลายร้อยรายการ
  • nameConstraints จำกัดชื่อที่ CA นี้ได้รับอนุญาตให้รับรองได้

ให้ตรวจสอบสิ่งที่คุณสร้างขึ้นแทนการคาดเดาว่าคำสั่งทำงานได้ตามที่คุณต้องการ

openssl x509 -noout -subject -issuer -serial -dates -in ca.crt
openssl x509 -noout -text -in ca.crt

ค่า Subject และ issuer จะแสดงเป็นสตริงเดียวกัน เนื่องจาก root certificate เป็นการลงนามรับรองตนเอง ส่วนค่า serial และวันที่ทั้งสองรายการจะมาจากไฟล์ที่คุณเพิ่งสร้างขึ้น ดังนั้นให้คัดลอกค่าเหล่านั้นจากผลลัพธ์ที่ได้โดยตรง แทนที่จะคัดลอกมาจากคู่มือของผู้อื่น

จำกัดขอบเขตสิ่งที่ CA ของคุณได้รับอนุญาตให้ลงนาม

Root CA ที่อยู่ใน system store จะได้รับความเชื่อถือสำหรับทุกชื่อบนอินเทอร์เน็ต เว้นแต่คุณจะกำหนดไว้เป็นอย่างอื่น นี่ถือเป็นอำนาจที่สูงมากซึ่งรวมอยู่ในไฟล์เดียวบนเซิร์ฟเวอร์เดียว nameConstraints จะช่วยลดอำนาจดังกล่าวลง เมื่อมี permitted;DNS:internal.example อยู่ใน root ใบรับรองที่ออกโดย CA นี้สำหรับชื่อที่อยู่นอกเหนือ internal.example จะถูกปฏิเสธทันที แม้ว่าลายเซ็นจะถูกต้องก็ตาม

ให้ทดสอบแทนการเชื่อถือโดยทันที

openssl req -new -newkey rsa:2048 -nodes -keyout /tmp/outside.key -out /tmp/outside.csr \
  -subj "/CN=www.example.com"
openssl x509 -req -in /tmp/outside.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 30 -sha256 \
  -extfile <(printf 'subjectAltName=DNS:www.example.com\n') -out /tmp/outside.crt
openssl verify -CAfile ca.crt /tmp/outside.crt
echo $?

ใบรับรองจะถูกออกให้สำเร็จเนื่องจาก CA ของคุณลงนามทุกอย่างที่คุณร้องขอ แต่จะไปล้มเหลวในขั้นตอนการตรวจสอบ (Verification): สถานะการทำงาน (exit status) จะไม่เป็นศูนย์ และ OpenSSL จะระบุข้อจำกัดที่ถูกละเมิด นี่คือคุณค่าของ extension นี้ แม้กุญแจ CA จะถูกขโมยไป ก็ยังไม่สามารถสร้างใบรับรองที่ใช้งานได้สำหรับชื่อที่อยู่นอกเหนือ subtree ที่กำหนดไว้ ลบไฟล์ที่เหลือทิ้งด้วย rm /tmp/outside.* เมื่อเสร็จสิ้น

มีสี่สิ่งที่ควรทราบก่อนที่คุณจะกำหนดข้อจำกัดนี้: มันถูกทำเครื่องหมายเป็น critical ดังนั้นไคลเอนต์ที่ไม่เข้าใจ extension นี้จะต้องปฏิเสธ chain แทนที่จะเพิกเฉย ซึ่งเป็นแนวทางที่ปลอดภัย แต่อาจทำให้ TLS library รุ่นเก่าทำงานผิดพลาดได้ Subtree ที่อนุญาตสำหรับชื่อ DNS จะไม่จำกัด IP address SANs เนื่องจากประเภทชื่อที่ไม่มีการระบุ subtree จะยังคงไม่มีข้อจำกัด ดังนั้นให้เพิ่ม permitted;IP:10.0.0.0/255.255.0.0 ใน extension เดียวกันหากใบรับรองของคุณมีการระบุ IP address Subtree ต้องครอบคลุมทุกชื่อที่คุณจะออกใบรับรองในอนาคต รวมถึงชื่อโฮสต์แบบสั้น ดังนั้นใบรับรองสำหรับชื่อเปล่าๆ อย่าง app จะไม่ผ่านการตรวจสอบหากเทียบกับตัวอย่างข้างต้น และเนื่องจากข้อจำกัดนี้ถูกฝังอยู่ใน root การเปลี่ยนใจในภายหลังหมายถึงคุณต้องสร้าง root certificate ใหม่และติดตั้งใหม่ทั้งหมดบนไคลเอนต์ทุกเครื่อง

ออกใบรับรองระดับใบไม้ (leaf certificate) ที่ลงนามโดย CA ของคุณ

ใบรับรองระดับใบไม้คือใบรับรองที่เซิร์ฟเวอร์แสดงต่อไคลเอนต์ ให้เริ่มต้นด้วยการสร้างคีย์ของใบรับรองนั้นและไฟล์ CSR (certificate signing request) ซึ่งจะบรรจุคีย์สาธารณะและชื่อที่ต้องการ โดยไฟล์นี้จะถูกลงนามด้วยคีย์ของใบรับรองระดับใบไม้เพื่อพิสูจน์ว่าผู้ขอถือครองคีย์ส่วนตัวอยู่จริง

openssl req -new -newkey rsa:2048 -nodes \
  -keyout app.key -out app.csr \
  -subj "/CN=app.internal.example"
chmod 600 app.key

ชื่อที่สำคัญจะต้องระบุไว้ในไฟล์ extension ไม่ใช่ใน CSR ไคลเอนต์จะตรวจสอบชื่อโฮสต์เทียบกับ subjectAltName (SAN) และจะเพิกเฉยต่อ common name โดยสิ้นเชิง ดังนั้นใบรับรองที่มีเพียง CN แต่ไม่มี SAN จะไม่ผ่านการตรวจสอบชื่อโฮสต์ในไคลเอนต์ปัจจุบันทุกตัว ไม่ว่า CN จะระบุไว้อย่างไรก็ตาม

basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:app.internal.example, DNS:api.internal.example
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always

บันทึกข้อมูลดังกล่าวเป็น app.ext จากนั้นลงนามคำขอด้วย CA

openssl x509 -req -in app.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 397 -sha256 -extfile app.ext -out app.crt

-CAcreateserial จะเขียนไฟล์ ca.srl ไว้ข้าง CA เพื่อเก็บหมายเลขลำดับถัดไป เพื่อให้มั่นใจว่าไม่มีใบรับรองสองฉบับใดจาก CA นี้ที่ใช้หมายเลขซ้ำกัน ให้เก็บไฟล์นั้นไว้ในไดเรกทอรีของ CA ส่วน -days 397 เป็นเพียงตัวเลือก ไม่ใช่ข้อจำกัดของเครื่องมือ การกำหนดอายุการใช้งานให้สั้นมีความสำคัญมากกว่าการใช้ CA สาธารณะ เนื่องจาก CA ส่วนตัวไม่มีโครงสร้างพื้นฐานสำหรับการเพิกถอนใบรับรอง (revocation infrastructure) กล่าวคือไม่มี CRL และไม่มี OCSP responder เว้นแต่คุณจะสร้างขึ้นมาเอง ดังนั้นหากคีย์ของใบรับรองระดับใบไม้รั่วไหล คีย์นั้นจะยังคงใช้งานได้จนกว่าใบรับรองจะหมดอายุ

ตรวจสอบผลลัพธ์ก่อนที่จะนำไปใส่ใน trust store

openssl x509 -noout -subject -issuer -serial -dates -in app.crt
openssl x509 -noout -ext subjectAltName -in app.crt

บรรทัด issuer จะระบุชื่อ CA แทนที่จะเป็นชื่อของใบรับรองระดับใบไม้เอง ส่วนบรรทัด SAN จะแสดงรายการชื่อที่ใบรับรองนี้มีผลบังคับใช้ ซึ่งไคลเอนต์จะตรวจสอบเทียบกับรายการนี้เท่านั้นและจะไม่ตรวจสอบจากส่วนอื่น

ตรวจสอบด้วย -CAfile อย่างชัดเจน ก่อนดำเนินการติดตั้งใดๆ

openssl verify -CAfile ca.crt app.crt
echo $?

คำสั่งนี้ถามคำถามที่เฉพาะเจาะจงเพียงข้อเดียวคือ app.crt เชื่อมโยงไปยังใบรับรองใน ca.crt หรือไม่ คำสั่งนี้ไม่ได้ระบุถึงสิ่งที่เครื่องนี้เชื่อถือ เนื่องจากคุณได้ระบุ root ให้ OpenSSL ผ่านบรรทัดคำสั่งโดยตรง หากเกิดความล้มเหลวในขั้นตอนนี้ แสดงว่าเป็นปัญหาที่ตัวใบรับรองเอง ดังนั้นให้แก้ไขให้เรียบร้อยก่อนดำเนินการต่อ

จากนั้นให้ตรวจสอบกับระบบ

openssl verify app.crt
echo $?

หากไม่มี -CAfile ทาง OpenSSL จะกลับไปใช้ไดเรกทอรีใบรับรองที่ติดตั้งมาพร้อมกับตัวโปรแกรม openssl version -d จะแสดงไดเรกทอรีหลักที่ build ของคุณใช้งานอยู่ และบน Ubuntu ไดเรกทอรี certs ภายใต้ไดเรกทอรีดังกล่าวจะชี้ไปยัง /etc/ssl/certs ในขณะนี้ root ของคุณยังไม่ได้ถูกเพิ่มเข้าไป การตรวจสอบจึงล้มเหลวเนื่องจากสายโซ่ใบรับรองไปถึงผู้ออกใบรับรอง (issuer) ที่ไม่มีอยู่ในคลังเก็บ และไม่มีที่อื่นให้ค้นหาอีก โปรดสังเกตสถานะการจบการทำงาน (exit status) ซึ่งเป็นสิ่งที่จะเปลี่ยนแปลงในอีกสองขั้นตอนถัดไป

การใช้ client จริงในการทดสอบจะให้ผลลัพธ์ที่ดีกว่า openssl verify เพราะจะมีการตรวจสอบชื่อโฮสต์ (hostname) ควบคู่ไปกับสายโซ่ใบรับรองด้วย ให้ทำการให้บริการใบรับรองและดึงข้อมูลออกมาทดสอบ

openssl s_server -accept 8443 -cert app.crt -key app.key -www &
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/

--resolve จะส่งการเชื่อมต่อไปยัง 127.0.0.1 ในขณะที่ยังคงร้องขอ app.internal.example ดังนั้น SAN จึงตรงกันและคำถามเดียวที่เหลืออยู่คือเรื่องความน่าเชื่อถือ curl จะล้มเหลวและแสดงเหตุผลที่ไม่สามารถตรวจสอบสายโซ่ใบรับรองได้ ให้เพิ่ม -v เพื่อดูรายละเอียดเพิ่มเติม และปล่อยให้เซิร์ฟเวอร์ทดสอบทำงานต่อไป

ติดตั้ง root certificate ลงใน /usr/local/share/ca-certificates

sudo cp ca.crt /usr/local/share/ca-certificates/example-internal-root.crt
sudo chmod 644 /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificates

รายละเอียดที่กำหนดว่าวิธีนี้จะใช้งานได้หรือไม่:

  • ชื่อไฟล์ต้องลงท้ายด้วย .crt หน้าคู่มือ update-ca-certificates ระบุว่า certificate ที่มีนามสกุล .crt ซึ่งพบภายใต้ /usr/local/share/ca-certificates จะถูกรวมเข้าและเชื่อถือโดยปริยาย ไฟล์ที่ชื่อ root.pem หรือ root.cer จะถูกข้ามไปโดยไม่มีการแจ้งเตือนใดๆ
  • เนื้อหาต้องอยู่ในรูปแบบ PEM ซึ่งเป็นบล็อก base64 ที่ล้อมรอบด้วยบรรทัด BEGIN CERTIFICATE และ END CERTIFICATE ไฟล์ DER ที่ถูกเปลี่ยนชื่อเป็น .crt จะยังคงเป็นข้อมูลไบนารีและระบบจะไม่สามารถอ่านได้ ให้แปลงไฟล์ด้วย openssl x509 -inform DER -in ca.der -out ca.crt
  • เฉพาะ root certificate เท่านั้นที่ควรวางไว้ที่นี่ ส่วน CA private key และ leaf certificate ไม่ควรอยู่ใน trust store

update-ca-certificates จะแสดงจำนวน certificate ที่เพิ่มและลบออก หากไม่มีการเพิ่ม certificate ใดๆ แสดงว่าเป็นเพราะนามสกุลไฟล์หรือรูปแบบไฟล์ไม่ถูกต้อง

ให้ยืนยันการเปลี่ยนแปลงจากฝั่งของระบบ แทนที่จะดูจากข้อความดังกล่าวเพียงอย่างเดียว

ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in /usr/local/share/ca-certificates/example-internal-root.crt).0
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crt

คำสั่งแรกจะสร้างชื่อไฟล์จาก subject hash ของ certificate ของคุณและแสดงรายการออกมา update-ca-certificates ได้สร้าง symlink นั้นขึ้นมา โดยมันจะชี้กลับไปยังไฟล์ที่คุณติดตั้งไว้ ส่วนคำสั่งที่สองจะนับจำนวน certificate ในไฟล์ bundle รวม ให้รันคำสั่งนี้ก่อนการติดตั้งด้วย เพื่อที่คุณจะสามารถตรวจสอบได้ว่าจำนวนเพิ่มขึ้นหนึ่งรายการจริง

เมื่อคุณคัดลอก root certificate นี้ไปยังเครื่องอื่น ให้ตรวจสอบว่าไฟล์ที่คัดลอกมานั้นสมบูรณ์ก่อนทำการติดตั้ง root certificate เป็นไฟล์ที่สำคัญที่สุดในระบบ หากเกิดความผิดพลาดจะส่งผลกระทบสูง ดังนั้นควรปฏิบัติกับมันเหมือนการดาวน์โหลดไฟล์อื่นๆ ที่คุณต้อง ตรวจสอบด้วย checksum ก่อนใช้งานเสมอ

ตรวจสอบอีกครั้งกับระบบจัดเก็บของเครื่อง

openssl verify app.crt
echo $?
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/

ใช้คำสั่งเดิม ไฟล์ใบรับรองเดิม แต่ผลลัพธ์ที่ได้จะแตกต่างออกไป ไม่มีการเปลี่ยนแปลงใดๆ เกี่ยวกับ app.crt และเซิร์ฟเวอร์ยังคงเป็นตัวเดิมที่คุณเริ่มทำงานไว้ก่อนหน้านี้ ความแตกต่างเพียงอย่างเดียวคือตอนนี้ root CA ได้ถูกจัดเก็บไว้ในที่ที่ไคลเอนต์เหล่านั้นอ่าน ทำให้สายโซ่ความเชื่อถือสมบูรณ์ นี่คือกลไกที่ควรจดจำไว้ว่า การตรวจสอบความถูกต้องคือการค้นหาผู้ออกใบรับรองที่ไคลเอนต์เชื่อถืออยู่แล้ว และการติดตั้ง CA คือวิธีการนำผู้ออกใบรับรองนั้นเข้าไปอยู่ในตำแหน่งที่ไคลเอนต์ใช้ค้นหา

หยุดการทำงานของเซิร์ฟเวอร์ทดสอบด้วย kill %1

เหตุผลที่ /etc/ssl/certs ไม่ใช่ตำแหน่งสำหรับวางไฟล์ของคุณ

/etc/ssl/certs เป็นผลลัพธ์ที่ถูกสร้างขึ้นโดยอัตโนมัติ update-ca-certificates จะเติมไฟล์ดังกล่าวด้วย symlink ที่ชี้กลับไปยังไฟล์ certificate จริง และเขียนไฟล์ bundle รวม /etc/ssl/certs/ca-certificates.crt ไว้ข้างๆ กัน

certificate ที่คุณคัดลอกลงในไดเรกทอรีนั้นด้วยตนเองจะไม่ถูกตรวจพบโดยระบบใดๆ การค้นหาไดเรกทอรีของ OpenSSL จะเปิดเฉพาะไฟล์ที่ตั้งชื่อตามค่า hash ของ subject ของ certificate เท่านั้น ดังนั้นไฟล์ที่ชื่อว่า myca.crt จึงไม่ถูกมองเห็นโดย OpenSSL ส่วน curl บน Ubuntu จะอ่านไฟล์ bundle ซึ่งไฟล์ bundle นี้จะถูกสร้างขึ้นใหม่จากแหล่งข้อมูลที่ลงทะเบียนไว้ ดังนั้นไฟล์ที่คุณคัดลอกไว้จึงไม่อยู่ในพาธนั้นเช่นกัน หากคุณรัน update-ca-certificates --fresh symlink ในไดเรกทอรีจะถูกลบและสร้างขึ้นใหม่ ซึ่งจะทำให้ลิงก์ที่คุณสร้างขึ้นด้วยตนเองถูกลบไปด้วย

อีกส่วนหนึ่งคือ /usr/share/ca-certificates ซึ่งเป็นของแพ็กเกจ ca-certificates และถูกระบุไว้ใน /etc/ca-certificates.conf การอัปเดตแพ็กเกจจะเขียนทับไฟล์นี้เสมอ /usr/local/share/ca-certificates คือไดเรกทอรีที่สงวนไว้สำหรับผู้ดูแลระบบท้องถิ่น ดังนั้น CA ของคุณจะยังคงอยู่แม้จะมีการอัปเกรดแพ็กเกจที่จัดการส่วนอื่นๆ ของระบบก็ตาม

โปรแกรมใดบ้างที่ไม่สนใจ system trust store

การติดตั้ง root certificate จะช่วยแก้ปัญหาให้กับทุกโปรแกรมที่เรียกใช้ OpenSSL หรืออ่านไฟล์จาก /etc/ssl/certs ซึ่งครอบคลุมถึง curl, wget, git, โมดูล ssl มาตรฐานของ Python และโปรแกรมที่เขียนด้วยภาษา Go ซึ่งอ่านไฟล์ระบบบน Linux อยู่แล้ว อย่างไรก็ตาม รันไทม์ที่มาพร้อมกับรายการ certificate ของตนเองจะไม่ได้รับผลกระทบ และนี่คือสาเหตุหลักที่ทำให้เกิดความสับสนหลังจากติดตั้งเสร็จสิ้น

  • Node.js ใช้รายการที่คอมไพล์มาในตัว คุณต้องชี้ไปยัง root certificate ของคุณด้วย NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/example-internal-root.crt โดยกำหนดค่าใน environment ก่อนที่โปรเซสจะเริ่มทำงาน เนื่องจาก Node จะอ่านตัวแปรนี้เพียงครั้งเดียวตอนเริ่มต้น Node เวอร์ชันปัจจุบันมีตัวเลือกให้อ่านจาก system store ได้ ให้รัน node --help | grep -i system-ca เพื่อตรวจสอบว่าเวอร์ชันของคุณรองรับหรือไม่
  • ไลบรารี requests ของ Python ใช้ชุดไฟล์ certifi ให้กำหนดค่า REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt สำหรับโปรเซสนั้น หรือส่งค่า verify="/etc/ssl/certs/ca-certificates.crt" ไปยังการเรียกใช้งาน ส่วน pip ก็ต้องใช้ --cert ด้วยเหตุผลเดียวกัน
  • Java อ่านค่าจาก keystore บน Ubuntu แพ็กเกจ ca-certificates-java จะติดตั้ง hook ไว้ที่ /etc/ca-certificates/update.d/ ดังนั้น update-ca-certificates จะทำการรีเฟรช Java keystore ให้โดยอัตโนมัติหากมีแพ็กเกจนี้อยู่ หากไม่มี ให้ import root certificate ด้วย keytool -importcert
  • Firefox เก็บ store ของตนเองและไม่เคยตรวจสอบที่ /etc/ssl/certs คุณต้อง import ผ่านการตั้งค่า certificate ของเบราว์เซอร์ ส่วน Chromium บน Linux จะอ่านฐานข้อมูล NSS เฉพาะผู้ใช้ ซึ่งคุณสามารถแก้ไขได้ด้วย certutil จากแพ็กเกจ libnss3-tools
  • คอนเทนเนอร์มีระบบไฟล์เป็นของตนเอง ดังนั้น store ของโฮสต์จึงไม่มีผลใดๆ ภายในคอนเทนเนอร์ คุณต้องคัดลอก root certificate เข้าไปในอิมเมจและรัน update-ca-certificates ระหว่างการ build โปรดวางแผนเรื่องนี้หากบริการของคุณทำงานภายใต้ Docker Compose บน VPS

หากโปรแกรมยังคงปฏิเสธ certificate หลังจากติดตั้งอย่างถูกต้องแล้ว ให้ตรวจสอบว่าโปรแกรมนั้นเปิดไฟล์ใดบ้างก่อนที่จะแก้ไขสิ่งอื่น strace -f -e trace=openat <command> 2>&1 | grep -i cert เป็นเครื่องมือที่ตรงไปตรงมาและสามารถตอบคำถามนี้ได้ในการรันเพียงครั้งเดียว

การรักษาความสามารถในการใช้งาน CA ในระยะยาว

การออกใบรับรอง leaf ใหม่คือการทำขั้นตอน CSR และขั้นตอนการลงนามซ้ำอีกครั้ง โดยใช้ไฟล์ app.ext เดิม ไคลเอนต์ไม่จำเป็นต้องดำเนินการใดๆ เนื่องจาก root ที่ไคลเอนต์เชื่อถือไม่ได้มีการเปลี่ยนแปลง ให้เก็บรักษา ca.srl และไฟล์ .ext ทุกไฟล์ไว้ในไดเรกทอรีของ CA เพื่อให้การออกใบรับรองครั้งถัดไปเป็นการทำซ้ำคำสั่งที่เคยใช้งานได้จริง ไม่ใช่การพยายามนึกย้อนกลับจากความจำ

ให้สำรองข้อมูล ca.key และ ca.crt ไว้ภายนอกเครื่องเซิร์ฟเวอร์และยังคงต้องเข้ารหัสไว้ หากทำกุญแจสูญหาย คุณจะไม่สามารถออกใบรับรองใหม่ได้อีก และจะต้องสร้าง CA ชุดที่สองขึ้นมาพร้อมกับติดตั้ง root ใหม่ลงในทุกที่ที่เคยติดตั้ง root ชุดแรกไว้ ให้จดบันทึกรายการของเครื่องและที่เก็บใบรับรองของแอปพลิเคชันทุกแห่งที่ได้รับ root นี้ไป เพราะรายการดังกล่าวคือสิ่งที่ทำให้การหมุนเวียนและการถอนใบรับรองเป็นไปได้

เมื่อ root ใกล้หมดอายุ ให้สร้าง root ชุดใหม่ล่วงหน้าและติดตั้งทั้งสอง root ควบคู่กันไป การมี root สองชุดในที่เก็บใบรับรองสามารถทำได้และไคลเอนต์จะยอมรับทั้งสองชุด ให้ทำการออกใบรับรอง leaf ใหม่โดยอ้างอิงกับ root ชุดใหม่ จากนั้นจึงถอน root ชุดเก่าออกเมื่อไม่มีบริการใดใช้งาน root นั้นแล้ว

การลบ CA ออกจาก trust store

sudo rm /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificates --fresh

--fresh จะลบ symlink ใน /etc/ssl/certs ออกและสร้างใหม่จากแหล่งข้อมูลที่ยังคงเหลืออยู่ ดังนั้นเมื่อลบ root ออกไปแล้ว ไฟล์ในไดเรกทอรีและ bundle จะถูกจัดการให้สอดคล้องกัน ให้ตรวจสอบการลบด้วยวิธีเดียวกับที่คุณใช้ตรวจสอบตอนติดตั้ง

openssl verify app.crt
echo $?
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crt
ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in ~/ca/ca.crt).0

การตรวจสอบจะล้มเหลวอีกครั้ง จำนวนใบรับรองจะกลับไปเท่ากับค่าเริ่มต้น และ hash symlink จะหายไป

คำสั่งดังกล่าวจะส่งผลกระทบต่อ system store เท่านั้น ให้ยกเลิกการติดตั้งในตำแหน่งอื่นด้วยตนเอง: ล้างข้อมูลใน NODE_EXTRA_CA_CERTS, ลบ alias ออกจาก Java keystore ใดๆ, ลบ root ออกจากโปรไฟล์ของเบราว์เซอร์แต่ละตัว และสร้าง container image ใหม่ที่เคยรวมใบรับรองนี้ไว้ การลบ root ไม่ได้ทำให้ใบรับรองที่ root นั้นเคยเซ็นชื่อไว้กลายเป็นโมฆะ ใบรับรองเหล่านั้นจะยังคงใช้งานได้บนเครื่องที่ยังเชื่อถือ root ดังกล่าวอยู่ ซึ่งเป็นเหตุผลสำคัญในทางปฏิบัติว่าทำไม CA ส่วนตัวจึงต้องมีรายการบันทึกว่าได้นำ root ไปติดตั้งไว้ที่ใดบ้าง CA ที่คุณไม่สามารถเพิกถอนได้อย่างสมบูรณ์จะกลายเป็นช่องโหว่ถาวร ดังนั้นควรทดสอบการลบในเครื่องทดสอบหนึ่งเครื่องตั้งแต่วันที่คุณเริ่มติดตั้ง ในขณะที่รายการติดตั้งยังคงมีจำนวนน้อยอยู่

FAQ

ฉันควรวาง CA certificate ไว้ที่ไหนบน Ubuntu?

ให้วางไว้ใน /usr/local/share/ca-certificates/ โดยใช้ชื่อไฟล์ที่ลงท้ายด้วย .crt และมีเนื้อหาเป็นรูปแบบ PEM จากนั้นให้รันคำสั่ง sudo update-ca-certificates ไดเรกทอรีดังกล่าวถูกสงวนไว้สำหรับผู้ดูแลระบบท้องถิ่น ดังนั้นการอัปเกรดแพ็กเกจจะไม่ส่งผลกระทบต่อไฟล์ในนั้น ส่วน /usr/share/ca-certificates เป็นของแพ็กเกจ ca-certificates และ /etc/ssl/certs ถูกสร้างขึ้นจากทั้งสองแหล่ง ดังนั้นไฟล์ที่วางไว้ในไดเรกทอรีเหล่านั้นจะถูกเขียนทับหรือถูกละเลย

ทำไม curl ยังคงปฏิเสธ certificate หลังจากรัน update-ca-certificates แล้ว?

ให้ตรวจสอบสาเหตุตามลำดับดังนี้ ไฟล์อาจไม่ได้ลงท้ายด้วย .crt หรืออาจเป็นรูปแบบ DER แทนที่จะเป็น PEM ซึ่งในกรณีนี้ update-ca-certificates จะข้ามไฟล์นั้นไปและไม่ได้เพิ่มข้อมูลใดๆ Certificate อาจไม่มี subjectAltName ที่ตรงกับชื่อโฮสต์ ซึ่งเป็นปัญหาเรื่องชื่อโฮสต์ไม่ใช่ปัญหาเรื่องความน่าเชื่อถือ ให้ตรวจสอบด้วย openssl x509 -noout -ext subjectAltName -in app.crt เซิร์ฟเวอร์อาจส่งมาเพียงแค่ leaf certificate ในขณะที่จำเป็นต้องมี intermediate certificate ด้วย นอกจากนี้ curl อาจถูกชี้ไปยัง bundle อื่นโดย CURL_CA_BUNDLE หรือ --cacert และสำหรับบริการที่ทำงานค้างไว้นานจำเป็นต้องรีสตาร์ท เนื่องจากโปรแกรมส่วนใหญ่อ่าน trust store เพียงครั้งเดียวตอนเริ่มต้นทำงาน

ระบบ trust store ครอบคลุมถึง Firefox, Chrome, Node และ Java หรือไม่?

ไม่ครอบคลุม curl, wget, git, โมดูล ssl มาตรฐานของ Python และโปรแกรมที่เขียนด้วย Go จะอ่านไฟล์ของระบบ ดังนั้นโปรแกรมเหล่านี้จะใช้งานได้ทันทีหลังจากรัน update-ca-certificates ส่วน Firefox จะเก็บ trust store ของตนเอง Chromium บน Linux จะใช้ฐานข้อมูล NSS แยกตามผู้ใช้ ซึ่งแก้ไขได้ด้วย certutil จากแพ็กเกจ libnss3-tools สำหรับ Node.js จำเป็นต้องตั้งค่า NODE_EXTRA_CA_CERTS ให้ชี้ไปยังไฟล์ root ของคุณ ส่วน Java จะอ่าน keystore ซึ่ง update-ca-certificates จะรีเฟรชก็ต่อเมื่อติดตั้งแพ็กเกจ ca-certificates-java เท่านั้น สำหรับ requests ของ Python จะใช้ certifi และจำเป็นต้องมี REQUESTS_CA_BUNDLE

ฉันจะลบ CA ออกจาก trust store ของ Ubuntu ได้อย่างไร?

ให้ลบไฟล์ออกจาก /usr/local/share/ca-certificates/ แล้วรัน sudo update-ca-certificates --fresh ตัวเลือก --fresh จะล้าง symlink ใน /etc/ssl/certs และสร้างใหม่ ทำให้ certificate ถูกลบออกจาก hash symlinks และ bundle ca-certificates.crt ไปพร้อมกัน ให้ยืนยันผลลัพธ์โดยรัน openssl verify กับ certificate ที่ CA นั้นเซ็นรับรองแล้วตรวจสอบ exit status จากนั้นให้ดำเนินการลบใน store อื่นๆ ที่คุณเคยเพิ่ม CA นั้นเข้าไปด้วย เพราะคำสั่งดังกล่าวจะไม่ส่งผลต่อ store อื่นเหล่านั้น

ฉันสามารถใช้ private CA แทน Let's Encrypt สำหรับเว็บไซต์สาธารณะได้หรือไม่?

ไม่ได้ เบราว์เซอร์ของผู้เข้าชมไม่เคยรู้จัก root ของคุณมาก่อน จึงจะแสดงคำเตือนเต็มหน้าจอ และคุณไม่สามารถติดตั้ง root ของคุณบนเครื่องที่คุณไม่ได้เป็นผู้ควบคุมได้ private CA มีไว้สำหรับชื่อโดเมนที่เครื่องของคุณเองเป็นผู้แก้ไขเท่านั้นและสำหรับไคลเอนต์ที่คุณดูแลอยู่ สำหรับเว็บไซต์ใดๆ ที่บุคคลภายนอกเข้าถึง ให้ขอ certificate จาก public CA เท่านั้น

#tls#certificates#openssl#ubuntu#security#pki