💡 ตัวอย่างทำงานเต็มที่พร้อมใช้งานบน GitHub:
vault-transit-pdf-signing-python

ทำไมการเซ็นอัตโนมัติถึงล้มเหลวในการตรวจสอบการดูแลคีย์

ทีมปฏิบัติการด้านการปฏิบัติตามกฎระเบียบและการเงินเซ็นเอกสารเป็นจำนวนหลายพันฉบับ: สัญญาเงินกู้, ใบแจ้งยอด, ชุดนโยบาย, จดหมายตรวจสอบ ฯลฯ เกือบไม่มีเอกสารใดที่เซ็นโดยคนคลิกปุ่ม; บริการทำการเซ็นให้ตามกำหนดเวลา และใบรับรองที่แนบมากับเอกสารเหล่านั้นบรรจุชื่อขององค์กร ซึ่งทำให้คีย์การเซ็นเป็นหนึ่งในทรัพย์สินที่อ่อนไหวที่สุดของบริษัท และโดยทั่วไปคีย์นี้จะอยู่ในไฟล์ PFX ควบคู่กับแอปพลิเคชันที่ใช้มัน

การเซ็นภายนอกเป็นความสามารถของ GroupDocs.Signature สำหรับ Python ผ่าน .NET ที่ส่งค่า digest ของเอกสารไปยังโค้ดที่คุณจัดหาแทนการเซ็นด้วยคีย์ส่วนตัวที่แอปพลิเคชันถือครอง มันมีอยู่เพราะการจัดการไฟล์ PFX ไม่สามารถผ่านการตรวจสอบได้ ไฟล์เป็นสำเนา และสำเนาจะกระจายไป: ไปยังอิมเมจของคอนเทนเนอร์, ไปยัง pipeline การปรับใช้, ไปยังการสำรองข้อมูล, ไปยังแล็ปท็อปของนักพัฒนาที่ดีบักปัญหาในโปรดักชันเมื่อฤดูใบไม้ผลิที่ผ่านมา ใครก็ตามที่ถือสำเนาเหล่านั้นพร้อมรหัสผ่านสามารถเซ็นในนามของคุณได้จนกว่าจะมีคนสังเกตและเพิกถอนใบรับรอง

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

ทำไมไฟล์ใบรับรองที่ป้องกันด้วยรหัสผ่านยังไม่เพียงพอ

สัญชาตญาณแรกคือการล็อกไฟล์ PFX: จำกัดสิทธิ์การเข้าถึงไฟล์, เก็บรหัสผ่านใน secret manager, เข้ารหัสโวลุ่ม แต่ละขั้นตอนช่วยได้และไม่มีขั้นตอนใดเปลี่ยนความจริงพื้นฐาน ณ เวลาที่ทำการเซ็น คีย์ส่วนตัวจะถูกโหลดเข้าสู่กระบวนการ ดังนั้นการดัมพ์หน่วยความจำ, การหลบหนีคอนเทนเนอร์ หรือบันทึกข้อความที่ไม่ระมัดระวังยังคงสามารถเปิดเผยคีย์ได้ การควบคุมที่คุณต้องการคือคีย์ที่ไม่สามารถอ่านได้เลย ไม่ใช่คีย์ที่อ่านได้ยากขึ้น

Vault’s Transit engine ให้สิ่งนั้น มันทำการคริปโตกราฟีเป็นบริการ: คุณส่งค่าให้มัน, มันเซ็นหรือเข้ารหัสด้วยคีย์ที่ถืออยู่, แล้วส่งผลลัพธ์กลับ คีย์ที่สร้างภายในสามารถตั้งค่าให้ไม่สามารถส่งออกได้ และมันไม่เก็บข้อมูลที่รับเข้ามาไว้ GroupDocs.Signature ครอบคลุมส่วนที่เหลือ เพราะ DigitalSignOptions.custom_sign_hash ให้คุณต่อเชื่อมผู้เซ็นของคุณเองเข้าสู่ pipeline การเซ็น PDF

การตั้งค่า: GroupDocs.Signature สำหรับ Python กับ Vault Transit

GroupDocs.Signature ทำทุกอย่างจนถึงขั้นตอนคริปโตกราฟี มันเปิด PDF, กำหนดช่วงไบต์ที่ลายเซ็นครอบคลุม, แฮชช่วงนั้น, แล้วเรียกผู้เซ็นของคุณพร้อม digest ผู้เซ็นของคุณส่ง digest ไปยัง Vault และรับไบต์ที่ Vault สร้างขึ้น ไลบรารีจะฝังไบต์เหล่านั้นพร้อมใบรับรองและเขียน PDF ที่เซ็นแล้วตามปกติ

คุณต้องใช้ Python 3.11 หรือ 3.13, แพ็กเกจ groupdocs-signature-net และ asn1crypto, และ Docker สำหรับคอนเทนเนอร์ Vault โหมดพัฒนา ใบอนุญาต GroupDocs เป็นทางเลือก; หากไม่มีโหมดประเมินผลจะรับเอกสารได้สูงสุด 2 หน้า ตัวอย่าง repository ทำงานตั้งแต่ต้นจนจบด้วยสองคำสั่งเดียวกัน และโค้ดเดียวกันสามารถชี้ไปยัง Vault โปรดักชันได้โดยการเปลี่ยนตัวแปรสภาพแวดล้อมสองค่า

การสร้างขั้นตอนต่อขั้นตอน

ขั้นตอนที่ 1 - อ่านคีย์การเซ็นจาก Vault

แอปพลิเคชันขอคีย์จาก Vault ที่อนุญาตให้ใช้ได้ และได้รับหมายเลขเวอร์ชันและคีย์สาธารณะ ส่วนครึ่งส่วนตัวจะไม่ปรากฏในคำตอบ

key = vault.read_key(KEY_NAME)
if key is None:
    # First run: enable Transit and create an RSA-2048 key.
    vault.enable_transit()
    vault.create_key(KEY_NAME, "rsa-2048")
    key = vault.read_key(KEY_NAME)
print(f"Signing key: '{KEY_NAME}', version {key.version}")

การอ่านเวอร์ชันเพียงครั้งเดียวคือจุดประสงค์ของขั้นตอนนี้ ใบรับรองถือคีย์สาธารณะหนึ่งค่า ดังนั้นใบรับรองและลายเซ็นทุกอันต้องมาจากเวอร์ชันเดียวกัน

ขั้นตอนที่ 2 - สร้างใบรับรองสำหรับคีย์ที่ไม่มีใครสามารถส่งออกได้

ลายเซ็น PDF มีใบรับรอง X.509 เพื่อให้ผู้ตรวจสอบสามารถค้นหาคีย์สาธารณะได้ ด้วยคีย์ที่ไม่สามารถส่งออกได้ ใบรับรองจะถูกประกอบรอบคีย์สาธารณะและเซ็นโดย Vault

tbs_certificate = x509.TbsCertificate({...})   # built around the Vault public key

# Hash locally, then have Vault sign the certificate's own digest.
digest = hashlib.sha256(tbs_certificate.dump()).digest()
signature = vault.sign_hash(key_name, key.version, digest, "sha2-256")

certificate = x509.Certificate({
    "tbs_certificate": tbs_certificate,
    "signature_algorithm": algorithm,
    "signature_value": signature,
})

ใบรับรองตั้งค่าให้เริ่มต้นหนึ่งวันก่อนเวลาปัจจุบัน ตัวอ่าน PDF จะเปรียบเทียบเวลาการเซ็นกับวันเริ่มต้นนั้น และบนเครื่องที่อยู่สองชั่วโมงตะวันออกของ UTC ใบรับรองที่สร้างไม่กี่วินาทีก่อนการเซ็นถูก Adobe Acrobat รายงานว่า “หมดอายุ” หรือ “ยังไม่ถึงวันใช้งาน”

ขั้นตอนที่ 3 - เซ็น PDF ผ่านผู้เซ็นภายนอก

certificate_object = DigitalVerifyOptions(
    certificate_stream=io.BytesIO(certificate)).certificate
digital_signature = DigitalSignature(certificate=certificate_object)
digital_signature.sign_time = datetime.now().astimezone()

options = DigitalSignOptions()
options.signature = digital_signature
options.custom_sign_hash = VaultSigner(vault, KEY_NAME, key.version)
options.hash_algorithm = HashAlgorithm.SHA256
options.reason = "Document signed with HashiCorp Vault Transit"

with gs.Signature(str(input_pdf)) as signature:
    signature.sign(str(output_pdf), options)

สองบรรทัดเป็นเฉพาะสำหรับ Python การผูกไม่มีประเภท X.509 ดังนั้นอ็อบเจ็กต์ใบรับรองจึงต้องดึงออกมาจาก DigitalVerifyOptions; การส่งพาธไปยัง DigitalSignOptions จะทำให้เกิดข้อผิดพลาด “Specified certificate doesn’t contain private key”. ส่วน sign_time ถูกตั้งให้รับรู้โซนเวลาโดยเจตนา เพราะค่าที่มีออฟเซ็ตจะถูกเขียนลงใน PDF ด้วย

ภายในผู้เซ็น digest จะถูกส่งไปยัง Vault พร้อม prehashed = true และ pkcs1v15. สองฟิลด์นี้กำหนดว่าลายเซ็นจะเป็นที่ถูกต้องหรือไม่ และฟิลด์แรกจะล้มเหลวแบบเงียบเมื่อขาดหายไป

ขั้นตอนที่ 4 - ตรวจสอบก่อนให้บริการส่งออกอะไรเลย

options = DigitalVerifyOptions(certificate_stream=io.BytesIO(certificate))
with gs.Signature(str(signed_pdf)) as signature:
    is_valid = signature.verify(options).is_valid

การเซ็นและตรวจสอบในรันเดียวกันทำให้โหมดความล้มเหลวเงียบหนึ่งแบบกลายเป็นโหมดความล้มเหลวทันที สำหรับ pipeline เอกสารนี่คือประตู: ไฟล์ที่ไม่ผ่านการตรวจสอบจะไม่ถึงคลังเก็บ, ฝ่ายตรงข้าม หรือพอร์ทัลลูกค้า

ผลกระทบต่อธุรกิจ: สิ่งที่เปลี่ยนแปลงสำหรับการตรวจสอบ

ประเด็น PFX บนโฮสต์แอปพลิเคชัน การเซ็นผ่าน Vault Transit
สำเนาของคีย์ส่วนตัว หนึ่งสำเนาต่อโฮสต์, อิมเมจ, การสำรองข้อมูลและแล็ปท็อป หนึ่งสำเนา, อยู่ภายใน Vault
สิทธิ์ที่แอปเซ็นต้องการ การเข้าถึงเต็มที่ไฟล์คีย์ อ่านคีย์, เรียก transit/sign
หลักฐานว่าเอกสารถูกเซ็น บันทึกแอปพลิเคชัน, หากมีคนเพิ่ม รายการตรวจสอบของ Vault หนึ่งรายการต่อลายเซ็น
การตอบสนองต่อการรั่วไหลที่สงสัย เพิกถอน, ออกใบใหม่, ปรับใช้ใหม่ทุกที่ หมุนคีย์ใน Vault, ออกใบใหม่ครั้งเดียว
ใครสามารถเซ็นในนามของคุณ ใครก็ตามที่มีไฟล์และรหัสผ่าน ใครก็ตามที่ถือโทเคน Vault ที่ถูกต้อง

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

กระบวนการทำงานจริง: สัญญาเงินกู้ที่เซ็นโดยบริการ

แพลตฟอร์มให้กู้สร้าง PDF สัญญาเมื่อใบสมัครได้รับการอนุมัติ, เซ็นมัน, แล้ววางไว้ในคลังเอกสารของลูกค้า ก่อนการเปลี่ยนแปลงนี้ บริการเซ็นถือใบรับรองของบริษัทและขั้นตอนใน runbook การปรับใช้มีขั้นตอนคัดลอก PFX ไปยังโฮสต์ใหม่ หลังจากเปลี่ยนแปลง บริการถือโทเคน AppRole ที่มีสองสิทธิ์ และคีย์ส่วนตัวของใบรับรองไม่เคยมีอยู่ที่ไหนเลย ยกเว้นใน Vault

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

สิ่งที่ควรทำให้ถูกต้องเป็นอันดับแรก

การจัดการ digest คีย์ หากใช้ชื่ออัลกอริทึมผิดจะล้มเหลวอย่างชัดเจน พร้อมข้อความตอบจาก Vault 500 crypto/rsa: input must be hashed message และเสียเวลาประมาณหนึ่งนาที หากขาดแฟล็ก prehashed จะไม่มีข้อผิดพลาดเลย: Vault จะทำการแฮช digest อีกครั้ง, ลายเซ็นจะครอบคลุมค่าที่ผิด, และ PDF จะดูเหมือนเสร็จสมบูรณ์ ตรวจสอบทุกไฟล์ที่คุณเซ็นและทำให้ความล้มเหลวนั้นไม่เป็นที่มองไม่เห็น

สถานที่อื่น ๆ ที่การออกแบบนี้เหมาะสม

ฮุคไม่มีความรู้ว่าอะไรอยู่เบื้องหลัง ดังนั้นสี่ขั้นตอนเดียวกันทำงานได้กับโมดูลฮาร์ดแวร์ความปลอดภัย, KMS บนคลาวด์ หรือสมาร์ทการ์ด ฉันพอร์ตการสร้างนี้จากพี่น้อง .NET โดยคาดว่าผู้เซ็นจะต้องทำการปรับใหม่ และส่วนที่เปลี่ยนแปลงจริง ๆ มีเพียงการส่งมอบใบรับรอง; เนื้อหาคำขอไปยัง Vault และการล็อกเวอร์ชันยังคงเหมือนเดิม ทีมในอุตสาหกรรมสุขภาพ, รัฐบาลและประกันภัยต่างใช้มันด้วยเหตุผลเดียวกับที่การเงินใช้: คีย์ต้องอยู่ในที่ที่สามารถพิสูจน์ได้ว่าถูกใช้เพื่ออะไร

เริ่มต้นใช้งาน

แหล่งข้อมูล