Class: PdfOxide::PdfSigner
- Inherits:
-
Object
- Object
- PdfOxide::PdfSigner
- Defined in:
- lib/pdf_oxide/pdf_signer.rb
Overview
PAdES B-B / B-T / B-LT / B-LTA digital-signature signer (v0.3.50 #235 + v0.3.51 5-arg shim).
Mirrors ‘fyi.oxide.pdf.PdfSigner`. Routes every sign through the 5-arg shim `pdf_sign_bytes_pades_opts` (the 18-arg legacy entry exists but isn’t exercised here — purego on SysV/AMD64 can’t register it).
Per ‘feedback_extraction_graceful_fallback`: signing is a **security operation** — every non-zero return fails closed.
Defined Under Namespace
Classes: PadesSignOptions
Constant Summary collapse
- LEVELS =
PAdES baseline level codes (mirrors Java’s ‘SignatureLevel` enum).
{ b: 0, t: 1, lt: 2, lta: 3 }.freeze
Class Method Summary collapse
-
.document_has_timestamp?(document_handle) ⇒ Boolean
Whether the doc carries a document-scoped /DocTimeStamp.
-
.pades_level(signature_handle) ⇒ Integer?
The PAdES level of an existing signature handle, or nil if no signatures.
-
.sign(pdf:, certificate_handle:, level:, tsa_url: nil, reason: nil, location: nil) ⇒ String
Static convenience — sign without constructing a Signer instance.
- .sign_with_handle(pdf, certificate_handle:, level_code:, tsa_url:, reason:, location:) ⇒ Object
Instance Method Summary collapse
-
#initialize(certificate_handle) ⇒ PdfSigner
constructor
A new instance of PdfSigner.
-
#sign(pdf, level:, tsa_url: nil, reason: nil, location: nil) ⇒ String
Sign a PDF (bytes) at the requested PAdES level.
Constructor Details
#initialize(certificate_handle) ⇒ PdfSigner
Returns a new instance of PdfSigner.
42 43 44 45 46 |
# File 'lib/pdf_oxide/pdf_signer.rb', line 42 def initialize(certificate_handle) raise ::PdfOxide::ArgumentError, 'certificate_handle required' if certificate_handle.nil? || certificate_handle.null? @certificate_handle = certificate_handle end |
Class Method Details
.document_has_timestamp?(document_handle) ⇒ Boolean
Returns whether the doc carries a document-scoped /DocTimeStamp.
95 96 97 98 99 100 101 102 103 104 |
# File 'lib/pdf_oxide/pdf_signer.rb', line 95 def self.(document_handle) raise ::PdfOxide::ArgumentError, 'document_handle required' if document_handle.nil? || document_handle.null? err = ::FFI::MemoryPointer.new(:int32) r = Bindings.(document_handle, err) code = err.read_int32 raise SignatureError, "pdf_document_has_timestamp failed (#{code})" if code != 0 r != 0 end |
.pades_level(signature_handle) ⇒ Integer?
Returns the PAdES level of an existing signature handle, or nil if no signatures.
83 84 85 86 87 88 89 90 91 92 |
# File 'lib/pdf_oxide/pdf_signer.rb', line 83 def self.pades_level(signature_handle) raise ::PdfOxide::ArgumentError, 'signature_handle required' if signature_handle.nil? || signature_handle.null? err = ::FFI::MemoryPointer.new(:int32) ordinal = Bindings.pdf_signature_get_pades_level(signature_handle, err) code = err.read_int32 raise SignatureError, "pdf_signature_get_pades_level failed (#{code})" if code != 0 ordinal end |
.sign(pdf:, certificate_handle:, level:, tsa_url: nil, reason: nil, location: nil) ⇒ String
Static convenience — sign without constructing a Signer instance.
77 78 79 |
# File 'lib/pdf_oxide/pdf_signer.rb', line 77 def self.sign(pdf:, certificate_handle:, level:, tsa_url: nil, reason: nil, location: nil) new(certificate_handle).sign(pdf, level: level, tsa_url: tsa_url, reason: reason, location: location) end |
.sign_with_handle(pdf, certificate_handle:, level_code:, tsa_url:, reason:, location:) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/pdf_oxide/pdf_signer.rb', line 107 def self.sign_with_handle(pdf, certificate_handle:, level_code:, tsa_url:, reason:, location:) binary = pdf.dup.force_encoding(Encoding::BINARY) pdf_buf = ::FFI::MemoryPointer.new(:uint8, binary.bytesize) pdf_buf.write_bytes(binary, 0, binary.bytesize) # Hold Ruby string buffers in locals so GC doesn't free them while # the C call is in flight. tsa_buf = string_ptr(tsa_url) reason_buf = string_ptr(reason) location_buf = string_ptr(location) opts = PadesSignOptions.new opts[:certificate_handle] = certificate_handle opts[:certs] = ::FFI::Pointer::NULL opts[:cert_lens] = ::FFI::Pointer::NULL opts[:n_certs] = 0 opts[:crls] = ::FFI::Pointer::NULL opts[:crl_lens] = ::FFI::Pointer::NULL opts[:n_crls] = 0 opts[:ocsps] = ::FFI::Pointer::NULL opts[:ocsp_lens] = ::FFI::Pointer::NULL opts[:n_ocsps] = 0 opts[:tsa_url] = tsa_buf || ::FFI::Pointer::NULL opts[:reason] = reason_buf || ::FFI::Pointer::NULL opts[:location] = location_buf || ::FFI::Pointer::NULL opts[:level] = level_code out_len = ::FFI::MemoryPointer.new(:size_t) err = ::FFI::MemoryPointer.new(:int32) out_ptr = Bindings.pdf_sign_bytes_pades_opts(pdf_buf, binary.bytesize, opts.to_ptr, out_len, err) code = err.read_int32 raise SignatureError, "pdf_sign_bytes_pades_opts failed (#{code}); security op fails closed" if code != 0 raise SignatureError, 'pdf_sign_bytes_pades_opts returned null; security op fails closed' if out_ptr.nil? || out_ptr.null? len = out_len.read(:size_t) signed = out_ptr.read_string(len) Bindings.free_bytes(out_ptr) if Bindings.respond_to?(:free_bytes) signed.force_encoding(Encoding::BINARY) end |
Instance Method Details
#sign(pdf, level:, tsa_url: nil, reason: nil, location: nil) ⇒ String
Sign a PDF (bytes) at the requested PAdES level.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/pdf_oxide/pdf_signer.rb', line 55 def sign(pdf, level:, tsa_url: nil, reason: nil, location: nil) raise ::PdfOxide::ArgumentError, 'pdf cannot be empty' if pdf.nil? || pdf.empty? level_code = LEVELS.fetch(level) do raise ::PdfOxide::ArgumentError, "level must be one of #{LEVELS.keys.inspect}, got #{level.inspect}" end if level != :b && (tsa_url.nil? || tsa_url.empty?) raise ::PdfOxide::ArgumentError, "PAdES #{level} requires tsa_url" end self.class.sign_with_handle( pdf, certificate_handle: @certificate_handle, level_code: level_code, tsa_url: tsa_url, reason: reason, location: location ) end |