Insert digital signature into existing pdf file

MrWater picture MrWater · Aug 22, 2012 · Viewed 9.2k times · Source

I need to insert a digital signature into already existing pdf files, using a rails application server. (Basically, clients upload pdf files and the server signs them with a local certificate)

I've been using JSignpdf to insert digital signatures into pdf files, and started probing for gems for ruby...

I've found another portable file to do this job on rubypdf site http://soft.rubypdf.com/software/pdf-digital-signe, but cannot find any gem or even example code to do this in ruby.

I've looked also at Digital signature verification with OpenSSL, but couldn't understand how to actually sign an already existing document, with a local certificate file.

I also took a peak at http://code.google.com/p/origami-pdf/ , but this seems a bit harsh for a supposingly "simple" (at least in concept) task.

Any ideas/suggestions?

Thank you

Answer

MrWater picture MrWater · Aug 29, 2012

After some research, recurring to the OpenSSL documentation and exploring the Origami solution, i built the code below, and managed to insert a locally generated signature/certificate into a pdf document. Now I just need to figure out how to use this with an external generated certificate (check version 2 below, where i solved it). I've opened a new question where you can find some details on a difficulty i had with OpenSSL and DER encoded certificates.

To develop version 2, i also spent some time wondering how to add an annotation - so the signature becomes visible in Adobe reader - without adding a new page to the document. From origami documentation, i found the get_page method, which solved my last problem on this. I'm using Adobe Reader X, for the record.

Hope you find this useful as I will ;-).

VERSION 1 - Generate certificate and key file, and insert them directly into the document

require 'openssl'

begin
  require 'origami'
rescue LoadError
  ORIGAMIDIR = "C:\RailsInstaller\Ruby1.9.3\lib\ruby\gems\1.9.1\gems\origami-1.2.4\lib"
  $: << ORIGAMIDIR
  require 'origami'
end
include Origami

# Code below is based on documentation available on
# http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL.html
key = OpenSSL::PKey::RSA.new 2048

open 'private_key.pem', 'w' do |io| io.write key.to_pem end
open 'public_key.pem', 'w' do |io| io.write key.public_key.to_pem end

cipher = OpenSSL::Cipher::Cipher.new 'AES-128-CBC'
pass_phrase = 'Origami rocks'

key_secure = key.export cipher, pass_phrase

open 'private_key.pem', 'w' do |io|
  io.write key_secure
end

#Create the certificate

name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example'

cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 0
cert.not_before = Time.now
cert.not_after = Time.now + 3600

cert.public_key = key.public_key
cert.subject = name


OUTPUTFILE = "test.pdf"

contents = ContentStream.new.setFilter(:FlateDecode)
contents.write OUTPUTFILE,
  :x => 350, :y => 750, :rendering => Text::Rendering::STROKE, :size => 30

pdf = PDF.read('Sample.pdf')


# Open certificate files

#sigannot = Annotation::Widget::Signature.new
#sigannot.Rect = Rectangle[:llx => 89.0, :lly => 386.0, :urx => 190.0, :ury => 353.0]

#page.add_annot(sigannot)

# Sign the PDF with the specified keys
pdf.sign(cert, key, 
  :method => 'adbe.pkcs7.sha1',
  #:annotation => sigannot, 
  :location => "Portugal", 
  :contact => "[email protected]", 
  :reason => "Proof of Concept"
)

# Save the resulting file
pdf.save(OUTPUTFILE)

VERSION 2 - Use existing certificates to sign a pdf document

require 'openssl'

begin
  require 'origami'
rescue LoadError
  ORIGAMIDIR = "C:\RailsInstaller\Ruby1.9.3\lib\ruby\gems\1.9.1\gems\origami-1.2.4\lib"
  $: << ORIGAMIDIR
  require 'origami'
end
include Origami

INPUTFILE = "Sample.pdf"
@inputfile = String.new(INPUTFILE)
OUTPUTFILE = @inputfile.insert(INPUTFILE.rindex("."),"_signed")
CERTFILE = "certificate.pem"
RSAKEYFILE = "private_key.pem"
passphrase = "your passphrase"

key4pem=File.read RSAKEYFILE

key = OpenSSL::PKey::RSA.new key4pem, passphrase
cert = OpenSSL::X509::Certificate.new(File.read CERTFILE)

pdf = PDF.read(INPUTFILE)
page = pdf.get_page(1)

# Add signature annotation (so it becomes visibles in pdf document)

sigannot = Annotation::Widget::Signature.new
sigannot.Rect = Rectangle[:llx => 89.0, :lly => 386.0, :urx => 190.0, :ury => 353.0]

page.add_annot(sigannot)

# Sign the PDF with the specified keys
pdf.sign(cert, key, 
  :method => 'adbe.pkcs7.sha1',
  :annotation => sigannot, 
  :location => "Portugal", 
  :contact => "[email protected]", 
  :reason => "Proof of Concept"
)

# Save the resulting file
pdf.save(OUTPUTFILE)