Fungsi PLCreateSelfSignedCertificate milik PDFlibPas membangun sebuah sertifikat RSA/SHA-256 self-signed dan mengekspornya, private key termasuk, langsung ke dalam sebuah file PFX terlindungi-password, hanya menggunakan Win32 CryptoAPI yang sudah terinstal pada setiap mesin Windows. Tanpa tool eksternal, tanpa certificate authority, tanpa langkah makecert atau OpenSSL manual: satu pemanggilan fungsi, satu sertifikat cukup baik untuk menjalankan sebuah test signing
Skenario yang membuat fungsi ini layak dimiliki hampir selalu sebuah pipeline CI. Sebuah smoke test signing membutuhkan sebuah PFX sungguhan dengan sebuah private key sungguhan di baliknya, dan meng-commit satu ke dalam repository adalah masalah keamanannya sendiri, karena sebuah private key yang di-commit adalah sebuah private key yang bocor sejak commit itu mendarat. Menjalankan makecert.exe atau sebuah pemanggilan OpenSSL dari sebuah build script juga bekerja, tetapi kemudian pipeline itu bergantung pada sebuah tool yang harus diinstal, ditemukan pada PATH, dan dijaga konsisten-versi di seluruh build agent. Menghasilkan sertifikat di dalam proses yang sama yang menjalankan test itu, dengan pemanggilan Win32 CryptoAPI yang sama yang sudah dikirimkan Windows, sepenuhnya menghilangkan ketergantungan itu
Apa sebenarnya yang dihasilkan PLCreateSelfSignedCertificate?
PLCreateSelfSignedCertificate menghasilkan sebuah file PFX terlindungi-password yang memegang sebuah sertifikat RSA self-signed dan private key-nya, ditandatangani dengan sha256RSA, digerakkan oleh lima parameter: SubjectName, PFXFileName, PFXPassword, ValidDays, dan KeyBits, dan mengembalikan sebuah flag sukses Boolean polos. SubjectName menerima sebuah string X.500 penuh seperti 'CN=Alice, O=Example', dan sebuah nama telanjang tanpa tanda = di dalamnya secara otomatis diberi prefix CN=. ValidDays di bawah 1 jatuh kembali ke 365, dan KeyBits di luar rentang 1024 hingga 16384 jatuh kembali ke 2048. PDFlibPas sudah merilis fungsi ini sejak v3.224.0, bisa dijangkau bukan hanya dari unit Delphi tetapi juga lewat permukaan DLL dan ActiveX, dan komentar dokumentasinya sendiri terus terang tentang di mana ia berhenti berguna: setiap viewer utama menandai sebuah sertifikat self-signed sebagai tidak dipercaya kecuali seseorang menginstalnya secara eksplisit, jadi perlakukan apa yang dihasilkannya sebagai sebuah sertifikat untuk menjalankan sebuah jalur kode, bukan sebuah signature yang seharusnya diminta dipercaya siapa pun di luar tim Anda
var
Success: Boolean;
begin
Success := PLCreateSelfSignedCertificate(
'CN=PDFlibPas CI Test, O=Example Corp',
'ci-test-signer.pfx',
'a-strong-throwaway-password',
365, // ValidDays
2048); // KeyBits
if not Success then
raise Exception.Create('Self-signed certificate generation failed');
end;
Mengapa CryptGenKey meng-encode panjang key dalam parameter flags?
CryptGenKey mengemas dua pengaturan tak berkaitan ke dalam satu parameter dwFlags tunggal. Word rendah membawa flag perilaku, CRYPT_EXPORTABLE di antaranya, sementara word tinggi, untuk sebuah key key-exchange RSA, membawa panjang key yang diminta dalam bit. Menyerahkan 2048 seolah itu sekadar flag lain menempatkannya di word rendah sebagai gantinya, di mana ia tidak cocok dengan flag perilaku apa pun yang didefinisikan CryptoAPI, sehingga pemanggilan itu menghasilkan sebuah key pada panjang default apa pun yang jatuh kembali provider, bukan panjang yang dikira diminta pemanggil. Mendapatkan sebuah key RSA 2048-bit sungguhan berarti menggeser angka itu ke word tinggi lebih dulu
// Key length lives in the upper 16 bits of the CryptGenKey flags;
// the low word carries behavior flags such as CRYPT_EXPORTABLE.
if not CryptGenKey(hProv, AT_KEYEXCHANGE,
(Cardinal(KeyBits) shl 16) or CRYPT_EXPORTABLE, hKey) then
Exit;
Apa yang terjadi jika Anda lupa CRYPT_EXPORTABLE?
Hapus CRYPT_EXPORTABLE dari nilai flags yang sama itu dan CryptGenKey tetap berhasil, tetapi ia menandai private key yang dihasilkan tidak-bisa-diekspor pada tingkat CSP. Segala sesuatu di hilir juga tetap melaporkan sukses: CertCreateSelfSignCertificate mengembalikan sebuah context sertifikat valid, dan PFXExportCertStoreEx, bahkan dipanggil dengan EXPORT_PRIVATE_KEYS, tetap berhasil dan menulis sebuah file PFX yang terbuka, ter-parse, dan terlihat sepenuhnya biasa. Apa yang tidak dikandungnya adalah private key, karena CSP menolak membiarkannya keluar dari key container, dan PFXExportCertStoreEx tidak pernah memperlakukan penolakan itu sebagai alasan menggagalkan seluruh ekspor
Kegagalannya hanya muncul belakangan, dan di tempat yang sama sekali berbeda: sebuah pemanggilan signing membuka PFX itu, menemukan sebuah sertifikat tanpa private key terpasang, dan melaporkan persis error yang akan Anda dapatkan dari sebuah PFX korup atau salah, bukan dari sebuah flag hilang tiga lapis di hulu. Siapa pun yang men-debug dari sisi signing saja bisa menghabiskan satu sore pada file yang salah sebelum menyadari bug sesungguhnya adalah satu bit hilang tunggal pada waktu key-generation, dalam sebuah pemanggilan fungsi yang sama sekali berbeda, mungkin dalam sebuah build script yang sama sekali berbeda
Mengapa ProvType harus cocok antara CryptAcquireContextW dan sertifikatnya?
ProvType harus cocok karena CertCreateSelfSignCertificate menyelesaikan private key sertifikat baru itu lewat sebuah record CRYPT_KEY_PROV_INFO, dan satu field dalam record itu, ProvType, harus menyebutkan persis nilai tipe CSP yang sama yang diserahkan ke CryptAcquireContextW ketika key container itu dibuka, PROV_RSA_AES, secara numerik 24, dalam implementasi PDFlibPas. Atur ProvType ke nol, atau ke konstanta provider apa pun selain yang sebenarnya dimiliki container itu, dan sertifikat itu tetap bisa dibuat, tetapi link tercatatnya kembali ke private key tidak lagi terselesaikan ke container yang memegangnya, yang muncul belakangan sebagai sebuah kegagalan signing atau export yang tidak ada hubungannya dengan konten kriptografi sesungguhnya sertifikat itu
// The provider type used to open the key container must match the
// provider type recorded in the certificate's key-provider info.
CryptAcquireContextW(hProv, PWideChar(Container), nil,
PROV_RSA_AES, CRYPT_NEWKEYSET);
// ... generate the key, build the subject name blob, then:
KeyProvInfo.ProvType := PROV_RSA_AES; // same constant, both call sites
Menyatukannya: dari container GUID ke PFX terlindungi-password
Rantai pemanggilan di dalam PLCreateSelfSignedCertificate mengikuti satu garis lurus, membuka sebuah key container baru yang dinamai berdasarkan sebuah GUID yang baru dihasilkan sehingga run CI konkuren tidak pernah bertabrakan atas nama container, menghasilkan pasangan key RSA di dalamnya dengan kedua flag yang dibahas di atas, meng-encode SubjectName ke dalam sebuah blob nama X.500 lewat CertStrToNameW, dan memanggil CertCreateSelfSignCertificate dengan sebuah jendela validitas yang dihitung dari ValidDays dan diserahkan sebagai sebuah struktur berbentuk-SYSTEMTIME polos. Context sertifikat hasilnya masuk ke dalam sebuah store sertifikat di-memori yang dibuka dengan CertOpenStore dan CERT_STORE_PROV_MEMORY, murni agar PFXExportCertStoreEx memiliki sebuah store untuk diekspor, karena API itu bekerja terhadap sebuah handle store alih-alih sebuah context sertifikat telanjang
// Each call opens a throwaway container named after a fresh GUID:
CryptAcquireContextW(hProv, PWideChar(Container), nil,
PROV_RSA_AES, CRYPT_NEWKEYSET);
// ... generate the key, self-sign the certificate, export the PFX ...
// then delete the container once the PFX holds its own copy of the key:
CryptAcquireContextW(hProv, PWideChar(Container), nil,
PROV_RSA_AES, CRYPT_DELETEKEYSET);
PFXExportCertStoreEx itu sendiri mengikuti konvensi dua-lintasan Win32 biasa: panggil sekali dengan sebuah buffer panjang-nol untuk mengetahui berapa banyak byte yang dibutuhkan PFX, alokasikan sebanyak itu, lalu panggil lagi untuk mengisi buffer. Begitu byte itu ada di disk, PDFlibPas menghapus key container sekali-pakai itu dengan CRYPT_DELETEKEYSET alih-alih meninggalkannya, karena PFX itu sudah membawa salinannya sendiri dari setiap byte materi key yang dipegang container tersebut. Lewati pembersihan itu dan setiap pemanggilan ke PLCreateSelfSignedCertificate meninggalkan sebuah key container yatim-piatu bernama-GUID duduk di profil pengguna pemanggil, yang persis merupakan jenis kebocoran yang akan diakumulasi sebuah agen CI yang menjalankan fungsi ini pada setiap build selama berbulan-bulan sebelum ada yang memperhatikan
Apakah sebuah sertifikat self-signed aman digunakan untuk signing produksi?
Tidak: sebuah sertifikat self-signed aman untuk menjalankan sebuah jalur kode signing dan tidak aman untuk sebuah signature yang diharapkan dipercaya siapa pun di luar tim, karena tidak ada apa pun yang merantainya kembali ke sebuah root yang sudah dipercaya software relying party. Langkah alami berikutnya untuk sebuah PFX seperti ini adalah sebuah pemanggilan signing sungguhan, dibahas di membangun sebuah workbench kepatuhan dan signing di Delphi dengan PDFlibPas, di mana sebuah PFX yang dibangun dengan cara ini menggerakkan separuh signing sebuah pipeline yang juga menjalankan preflight PDF/A dan audit ByteRange. Signing hanyalah separuh dari apa yang berada di sekitar sebuah sertifikat, meski begitu, dan separuh lainnya persis di mana sebuah leaf self-signed seharusnya gagal: signing dan validasi PAdES di Delphi dengan PDFlibPas membahas pemeriksaan chain-of-trust yang dijalankan sebuah validator kesesuaian, dan sebuah validator yang menelusuri rantai kembali ke sebuah root terpercaya tidak punya alasan mempercayai sebuah sertifikat yang baru saja diciptakan fungsi ini lima menit lalu dari tidak ada apa-apa
PLCreateSelfSignedCertificate adalah satu fungsi di antara API sertifikat dan signing dalam PDFlibPas PDF library untuk Delphi dan C++Builder, dan ada persis untuk celah yang dijelaskan di sini: sebuah test signing yang membutuhkan sebuah pasangan key sungguhan di baliknya dan tidak ada apa pun eksternal untuk menghasilkannya