14.4.5. การตรวจสอบเซิร์ฟเวอร์สาธารณะ (กล้องทำหน้าที่เป็น client)¶
ทุกสิ่งที่กล่าวถึงในหน้าก่อนหน้าเกี่ยวกับ client "ที่มี root อยู่แล้ว" เป็นความจริงสำหรับเบราว์เซอร์ โทรศัพท์ และ PC -- แต่ ไม่ เป็นความจริงสำหรับกล้อง ssl ของ MicroPython ไม่มี trust store ในตัว: กล้องที่เพิ่งแฟลชใหม่จะไม่เชื่อถือ CA ใดเลย และค่าเริ่มต้น (ssl.CERT_NONE) จะไม่ตรวจสอบสิ่งใดและเปิดช่องโหว่ man-in-the-middle ไว้อย่างสมบูรณ์ ดังนั้นเมื่อกล้องเป็น client ที่เชื่อมต่อออกไปยัง TLS server สาธารณะ (HTTPS API, MQTT broker, ...) และคุณต้องการให้ตรวจสอบ server นั้นจริงๆ คุณต้องจัดหา trust anchor เองด้วยตัวเอง
กลไกเหมือนกับตัวอย่าง self-signed client ใน ใบรับรอง Self-signed ทั้งหมด โดยมีความแตกต่างเพียงอย่างเดียวคือไฟล์ที่คุณโหลดเป็น CA certificate จริงแทนที่จะเป็น certificate ของ peer เอง:
รับ CA certificate ที่เป็น anchor สำหรับ chain ของ server "Anchor" หมายถึง certificate ที่อยู่บน (หรือใกล้) ยอดของ chain ของ server ที่คุณเลือกเป็นจุดเริ่มต้นของความเชื่อถือ TLS server จะส่ง leaf และมักจะส่ง intermediate(s) ด้วย แต่จะไม่ส่ง root คุณต้องขอ trust anchor นั้นด้วยตัวเองและ โดยไม่ขึ้นกับ server -- การเชื่อถือสิ่งที่ server ส่งมาให้โดยตรงจะทำลายจุดประสงค์ทั้งหมดของการตรวจสอบ
ขั้นแรก ให้ค้นหาว่า CA ใดที่ออก certificate ของ server จริงๆ ตัวอย่างเช่น สำหรับ
openmv.ioopenssl s_client -connect openmv.io:443 -showcerts < /dev/nullบล็อก
Certificate chainแสดงรายการ certificate แต่ละรายการพร้อม subject (s:) และ issuer (i:); OpenSSL เวอร์ชันใหม่กว่าจะพิมพ์บรรทัดa:(ประเภท key) และv:(ความถูกต้อง) ที่คุณสามารถเพิกเฉยได้ที่นี่:Certificate chain 0 s:CN=openmv.io i:C=US, O=Let's Encrypt, CN=E8 1 s:C=US, O=Let's Encrypt, CN=E8 i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
Entry 0 คือ leaf (
openmv.io) ออกโดย intermediateE8Entry 1 คือ intermediate นั้น ออกโดย rootISRG Root X1issuer (i:) ของ entry บนสุดชี้ไปที่ root -- ที่นี่คือISRG Root X1(intermediate คือE8แทนที่จะเป็นR10/R11ที่คุณอาจเคยเห็นที่อื่น เนื่องจากopenmv.ioใช้ ECDSA certificate; Let's Encrypt จะเซ็น ECDSA leaf ด้วย intermediate ชุดEและ RSA leaf ด้วยชุดRทั้งคู่ chain ไปที่ISRG Root X1)OpenSSL ยังพิมพ์บรรทัด
depth=และอาจรายงาน root ด้วยVerification: OKสิ่งนี้เกิดขึ้นเพราะ PC ของคุณ เชื่อถือISRG Root X1อยู่แล้ว -- server ไม่ได้ ส่งมันมา (server ไม่เคยส่ง root ของตน) และกล้องซึ่งไม่มี trust store ก็จะไม่มีมันเช่นกัน นั่นคือเหตุผลที่คุณต้องจัดหามันเองดาวน์โหลด root นั้น จากหน้า root ที่ CA เผยแพร่เอง Let's Encrypt รวบรวม root ทั้งหมดไว้ใน หน้า certificate ของ Let's Encrypt; ไฟล์โดยตรงสำหรับ ISRG Root X1 คือ isrgrootx1.pem (พวกเขายังเสนอเวอร์ชันที่เข้ารหัสล่วงหน้าเป็น isrgrootx1.der) CA อื่นๆ เผยแพร่ root ของตนบนหน้า "root certificates" / "repository" ที่คล้ายกัน ชุด public ที่เป็นแม่แบบคือ Mozilla CA program (CCADB) ยืนยันว่าคุณดาวน์โหลดไฟล์ถูกต้องโดยเปรียบเทียบ fingerprint กับค่าที่ CA เผยแพร่ (เพิ่ม
-inform DERหากคุณดาวน์โหลด.der):openssl x509 -in isrgrootx1.pem -noout -subject -fingerprint -sha256หากคุณไม่ต้องการติดตาม root คุณสามารถคัดลอก intermediate โดยตรงจาก output ของ
-showcerts(บล็อก-----BEGIN CERTIFICATE-----ที่สอง) ไว้ใช้เป็น trust แล้วยอมรับว่าคุณต้องรีเฟรชเมื่อ CA หมุนเวียน intermediate -- บ่อยกว่า root มาก (ดูการแลกเปลี่ยนด้านล่าง)แปลงเป็น DER เหมือนเดิม:
openssl x509 -in isrgrootx1.pem -outform DER -out ca.derคัดลอก
ca.derไปยังกล้อง (filesystem หรือ ROMFS) แล้วโหลดเป็น trust anchor:import socket import ssl import ntptime ntptime.settime() # validity check needs the clock addr = socket.getaddrinfo("api.example.com", 443)[0][-1] sock = socket.socket() sock.connect(addr) ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.verify_mode = ssl.CERT_REQUIRED ctx.load_verify_locations(cafile="ca.der") ssock = ctx.wrap_socket(sock, server_hostname="api.example.com")
server_hostnameจำเป็นที่นี่: มันขับเคลื่อน SNI และเป็นชื่อที่ตรวจสอบกับsubjectAltNameของ server certificate
Tip
ทางลัดสำหรับกรณีทั่วไป Let's Encrypt เป็น public CA ที่ใช้งานมากที่สุด และทั้ง RSA และ ECDSA certificate ของ Let's Encrypt ในปัจจุบัน chain ไปที่ ISRG Root X1 (ดังที่ตัวอย่าง openmv.io ด้านบนแสดง) หาก server ที่กล้องของคุณพูดคุยด้วยใช้ Let's Encrypt คุณสามารถข้ามการตรวจสอบทั้งหมดได้: เพียงวาง isrgrootx1.der บนกล้องแล้ว load_verify_locations มัน
สิ่งนี้ ไม่ได้ ทำให้ TLS ใช้งานได้กับ ทุก ไซต์ server ที่ certificate มาจาก CA อื่น (DigiCert, Google Trust Services, Amazon, Sectigo, ...) จะยังล้มเหลวในการตรวจสอบ และเนื่องจากกล้องเชื่อถือ DER certificate เดียวต่อ ssl.SSLContext คุณไม่สามารถรวม root ทุกตัวเหมือนเบราว์เซอร์ได้ เมื่อไม่แน่ใจ ให้ระบุ CA จริงของ server ตามที่แสดงด้านบนและเชื่อถือ root นั้น
การเลือก certificate ที่จะเชื่อถือเป็นการแลกเปลี่ยน:
Root (แนะนำ) มีอายุยาวนาน -- มักเป็นหลายสิบปี -- ดังนั้น
ca.derจะแทบไม่เปลี่ยนแปลง จำเป็นต้องให้ server ส่ง intermediate เพื่อให้ mbedTLS สร้าง path leaf → intermediate → trusted root ของคุณ ซึ่ง server สาธารณะที่กำหนดค่าถูกต้องแทบทั้งหมดทำIntermediate ก็ใช้ได้เช่นกัน และยังคงทำงานได้แม้ว่า server จะไม่ส่ง intermediate แต่ intermediate จะถูกหมุนเวียนบ่อยกว่า root มาก ดังนั้นคุณจะต้องรีเฟรช
ca.derบ่อยขึ้นLeaf เอง (certificate pinning) เข้มงวดที่สุด แต่ leaf จะเปลี่ยนในทุก renewal -- ประมาณทุก 90 วันสำหรับ Let's Encrypt -- ดังนั้นสิ่งนี้จึงสมเหตุสมผลเฉพาะเมื่อคุณควบคุม server ด้วยและสามารถส่ง pin ใหม่ไปยังกล้องทุกตัวพร้อมกันได้ นี่คือสิ่งที่ตัวอย่าง self-signed client ทำ
Note
ssl.SSLContext.load_verify_locations() รับ CA certificate ที่เข้ารหัส DER เพียงหนึ่งใบ ดังนั้นกล้องจึงเชื่อถือ anchor เดียวในเวลาเดียวกัน เพื่อเข้าถึง server ภายใต้ CA ที่แตกต่างกัน ให้ใช้ ssl.SSLContext แยกต่างหากต่อ anchor และเนื่องจาก certificate นั้นจะหมดอายุหรือถูกหมุนเวียนโดย CA ในที่สุด ให้ถือปฏิบัติเหมือน certificate อื่นๆ บนอุปกรณ์