How to verify PDF digital signature in Ruby - ruby

I added a digital signature as mentioned in "Insert digital signature into existing pdf file" and stored that certificate as a PEM file in local. How can I verify the signature with a stored certificate?
This is sample code from source:
open 'certificate.pem', 'w' do |io| io.write cert.to_pem end #Saving certificate
cert = OpenSSL::X509::Certificate.new(File::read('certificate.pem')) #Opening certificate to verify. This gives error. how to convert pem string to certificate.
pdf = PDF.read('test.pdf') #opening certified pdf to validate signature
pdf.verify(trusted_certs: [cert]) if pdf.signed? #This gives error.
Edited:
After adding cert.sign key, OpenSSL::Digest::SHA1.new the above works.But the verification fails.
Using the following code i added digital signature into pdf.
require 'openssl'
require 'origami'
include Origami
key = OpenSSL::PKey::RSA.new 2048
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
cert.sign key, OpenSSL::Digest::SHA1.new
open 'certificate.pem', 'w' do |io| io.write cert.to_pem end
OUTPUTFILE = "outfile.pdf"
pdf = PDF.read('testing.pdf')
pdf.sign(cert, key,
:method => 'adbe.pkcs7.sha1',
#:annotation => sigannot,
:location => "Portugal",
:contact => "myemail#email.tt",
:reason => "Proof of Concept"
)
pdf.save(OUTPUTFILE)
After that i used the following code to verify digital signature using stored certificate. But it gives false.
signed_cert = OpenSSL::X509::Certificate.new(File::read('certificate.pem'))
pdf = PDF.read("outfile.pdf")
if pdf.signed?
pdf.verify(trusted_certs: [signed_cert]) #This gives false
end
What am I doing wrong?

Related

Certificate to pem to certificate not working: nested asn1 error

I want to create a certificate from a string. Why does this not work:
OpenSSL::X509::Certificate.new(OpenSSL::X509::Certificate.new.to_pem)
It returns: OpenSSL::X509::CertificateError: nested asn1 error
So I got the answer myself. The certificate needs at least these information:
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.sign key, OpenSSL::Digest::SHA1.new
Then this is possible:
OpenSSL::X509::Certificate.new(cert.to_pem) => returns

JSON Encoding issue Ruby 2.0.0

So I have a secure messaging script written up that uses JSON to send messages between a client and server. At one point I'm generating signed message digests using SHA1 and this is giving me some characters that I can't parse in JSON.generate to send my messages in JSON format around. Can anyone help me get around this error "in `encode': "\xAB" from ASCII-8BIT to UTF-8 (Encoding::UndefinedConversionError)" ?
The main problem I'm having is with this section of code:
#make new private / public rsa key-pair
rsakey = OpenSSL::PKey::RSA.new 2048
#hash the key using sha1
sha1 = OpenSSL::Digest::SHA1.new
digest = sha1.digest(rsakey.public_key.to_pem)
pubkey = JSON.generate({
key: rsakey.public_key.to_pem,
digest: digest
})
It's not allowing me to do a JSON.generate of digest. Does anyone know of a workaround or another way to encode my message digest?
My full code is below:
# encoding: utf-8
require 'socket'
require 'openssl'
require 'json'
port = 9090
s = TCPServer.open(port)
#make new private / public rsa key-pair
rsakey = OpenSSL::PKey::RSA.new 2048
#hash the key using sha1
sha1 = OpenSSL::Digest::SHA1.new
digest = sha1.digest(rsakey.public_key.to_pem)
pubkey = JSON.generate({
key: rsakey.public_key.to_pem,
digest: digest
})
loop {
client = s.accept
#get public key from alice
incoming = client.gets()
alice = JSON.parse(incoming)
alice_key = OpenSSL::PKey::RSA.new alice['key']
#send public key to alice
puts pubkey
client.puts pubkey
#get encrypted package from alice
json_full_package = client.gets()
full_package = JSON.parse(json_full_package)
#decrypt and print package
cipher = OpenSSL::Cipher.new("DES3")
cipher.decrypt
key = rsakey.private_decrypt(full_package['key'])
iv = rsakey.private_decrypt(full_package['iv'])
json_package = cipher.update(full_package['package'])
package = JSON.parse(json_package)
decrypted_digest = alice_key.public_decrypt(package['signed_digest'])
sha1 = OpenSSL::Digest::SHA1.new
digest = sha1.digest(package['data'])
throw 'failed digest' unless digest == decrypted_digest
puts package['data']
client.close
}
# encoding: utf-8
require 'socket'
require 'openssl'
require 'json'
host = 'lab1-15.eng.utah.edu'
port = 9090
s = TCPSocket.open(host, port)
pubkey_q = false
keyF = File.new("public_key.pem", 'w')
#generate alice's key pair
key = OpenSSL::PKey::RSA.new 2048
to_bob_public = JSON.generate({
key: key.public_key.to_pem
})
s.send(to_bob_public)
#get public key certificate from bob
while line = s.gets
puts line.chop
bob = JSON.parse(line)
end
bob_key = OpenSSL::PKey::RSA.new bob['key']
bob_digest = bob['digest']
#verify public key
sha1 = OpenSSL::Digest::SHA1.new
t_digest = sha1.digest(bob['key'])
throw "not verified" unless t_digest == bob_digest
data = File.read('document') #data is original message
#hash the document using sha1
sha1 = OpenSSL::Digest::SHA1.new
digest = sha1.digest(data)
#sign with private key
signed_digest = key.private_encrypt(digest)
#package this in json
package = JSON.generate({
signed_digest: signed_digest,
data: data
})
#make cipher for encryption
cipher = OpenSSL::Cipher.new("DES3")
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv
#encrypt data
encrypted = cipher.update(package)
#encrypt key and iv using bob's public key
encrypted_cipher_key = bob_key.public_encrypt(key)
encrypted_cipher_iv = bob_key.public_encrypt(iv)
full_package = JSON.generate({
key: encrypted_cipher_key,
iv: encrypted_cipher_iv,
package: encrypted
})
#send full_package to bob
s.send(full_package)
s.close
Use #hexdigest instead of #digest and you'll get hex. Right now you're attempting to encode binary into JSON (as that's what #digest returns) and that is causing the encode failure.

bad decrypt error in ruby

While executing the cipher.final, It says bad decrypt error. I tried to find out the problem.But,I can't find . Can you tell what is wrong with my code?
Here is my code:
require 'openssl'
require 'base64'
require 'hex_string'
result_h ="4fcd6b1ac843a2f8bf13f2e53dd5c1544fcd6b1ac843a2f8"
key = result_h.to_byte_string
encrypt_str="79994A6EF73DA76C";
cipher = OpenSSL::Cipher.new("DES-EDE3-CBC")
cipher.decrypt
cipher.key = key
data = encrypt_str.to_byte_string
res = cipher.update( data )
res << cipher.final
result_h= res.unpack("H*")[0]
puts result_h.inspect;
Error is:
in `final': bad decrypt (OpenSSL::Cipher::CipherError)
I had a similar problem. In the specific example:
require 'openssl'
require 'base64'
require 'hex_string'
result_h ="4fcd6b1ac843a2f8bf13f2e53dd5c1544fcd6b1ac843a2f8"
key = result_h.to_byte_string
encrypt_str="79994A6EF73DA76C";
cipher = OpenSSL::Cipher.new("DES-EDE3-CBC")
cipher.decrypt
cipher.padding = 0
cipher.key = key
data = encrypt_str.to_byte_string
res = cipher.update( data )
res << cipher.final
result_h= res.unpack("H*")[0]
puts result_h.inspect;
=> "0befe932733d76e6"
If you add cipher.padding = 0 it gives you a result.
I was using a different decryption key as compared to the one I provided for encryption. This was when this error hit me.

Insert digital signature into existing pdf file

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
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 => "myemail#email.tt",
: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 => "myemail#email.tt",
:reason => "Proof of Concept"
)
# Save the resulting file
pdf.save(OUTPUTFILE)
If you're working on a project for pay, you might want to consider jPDFSecure, a commercial Java library built for developers to digitally sign PDF documents and change security settings on PDF Documents. With jPDFSecure, your application or java applet can encrypt PDF documents, set permissions and passwords, and create and apply digital signatures. jPDFSecure is optimized for performance and is built on top of Qoppa's proprietary PDF technology so there is no need for any third party software or drivers.
jPDFSecure has a simple interface to load PDF documents from files, network drives,URLs and even input streams, which can be generated runtime or come directly from a database. After changing security settings, jPDFSecure can save the document to a file, a java.io.OutputStream or a javax.servlet.ServletOutputStream when running in a Java EE application server to output the file directly to a browser.
jPDFSecure is platform independent and can be used in any environment that supports Java, including Windows, Mac OSX and Linux.

Ruby Generate Self-Signed Certificate

I'm trying to generate a self-signed certificate in ruby, but am running into trouble. This is what I currently have right now:
require 'openssl'
if ARGV.length != 3 then
puts "USAGE: #{__FILE__} <type[der|pem]> <private-out> <public-out>"
exit
end
type = ARGV[0].downcase
privateKeyFile = ARGV[1]
publicKeyFile = ARGV[2]
values = [{ 'C' => 'US'},
{'ST' => 'SomeState'},
{ 'L' => 'SomeCity'},
{ 'O' => 'Organization'},
{'OU' => 'Organizational Unit'},
{'CN' => "somesite.com"}]
name = values.collect{ |l| l.collect { |k, v| "/#{k}=#{v}" }.join }.join
key = OpenSSL::PKey::RSA.generate(1024)
pub = key.public_key
ca = OpenSSL::X509::Name.parse(name)
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 1
cert.subject = ca
cert.issuer = ca
cert.public_key = pub
cert.not_before = Time.now
cert.not_before = Time.now + (360 * 24 * 3600)
File.open(privateKeyFile + "." + type, "w") {|f| f.write key.send("to_#{type}") }
File.open(publicKeyFile + "." + type, "w") {|f| f.write cert.send("to_#{type}") }
When I try to use the generated private key and certificate in apache, I get this error:
[Thu Mar 04 10:58:44 2010] [error] Init: Unable to read server certificate from file /etc/ssl/certs/gnarly.pem
[Thu Mar 04 10:58:44 2010] [error] SSL Library Error: 218529960 error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
[Thu Mar 04 10:58:44 2010] [error] SSL Library Error: 218595386 error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error
This is what my certificate says:
-----BEGIN CERTIFICATE-----
<lots of stuff>
-----END CERTIFICATE-----
It calls itself a certificate instead of a CSR, which is what most of the things I've found online say about that apache2 error (that I might have gotten the CSR and CERT mixed up). My guess is that I'm not generating the right type of certificate. Maybe I have to change the serial or version attributes. Also, I'm not doing any self-signing anywhere, not that I know of anyways. I know you can do something like this though:
require "openssl"
key = OpenSSL::PKey::RSA.generate(1024)
signature = key.sign(OpenSSL::Digest::SHA1.new, "data to sign")
Reminder: My goal is to generate a self-signed certificate, in case my long-ish question lost focus on the way.
EDIT: I guess the real question is how to sign a certificate with a key
I created a helper class for this from code I lifted directly from nickyp's gist that I found on a Google search. The only dependency you need is the openssl gem (gem install openssl)
require 'openssl'
class SelfSignedCertificate
def initialize
#key = OpenSSL::PKey::RSA.new(1024)
public_key = #key.public_key
subject = "/C=BE/O=Test/OU=Test/CN=Test"
#cert = OpenSSL::X509::Certificate.new
#cert.subject = #cert.issuer = OpenSSL::X509::Name.parse(subject)
#cert.not_before = Time.now
#cert.not_after = Time.now + 365 * 24 * 60 * 60
#cert.public_key = public_key
#cert.serial = 0x0
#cert.version = 2
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = #cert
ef.issuer_certificate = #cert
#cert.extensions = [
ef.create_extension("basicConstraints","CA:TRUE", true),
ef.create_extension("subjectKeyIdentifier", "hash"),
# ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
]
#cert.add_extension ef.create_extension("authorityKeyIdentifier",
"keyid:always,issuer:always")
#cert.sign #key, OpenSSL::Digest::SHA1.new
end
def self_signed_pem
#cert.to_pem
end
def private_key
#key
end
end
Usage:
my_cert = SelfSignedCertificate.new
puts "Private Key:\n#{my_cert.private_key}"
puts "Self-signed PEM:\n#{my_cert.self_signed_pem}"
Output:
Private Key:
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDTtjPd3X9KX9BZpXKS82tM74Bs/hXsSLgnkitrc+oR4oF5PVko
NZL3j51gkX3jJRSG9tNPQC5NVR+5h7tXPxU5TAQZl6MUiV+YWuRng98GeCjP3ePp
meSStsKEMUiZI8YLVWrdbIjS+Q+lZnYMffeEOAoMSaei9hR4rOX0i+9hdwIDAQAB
AoGBALdAc/6sFd0zuC2Qhu7p4kvS11AAUsuWWkhuPkUhLU9TxwxBbOXgEZlVZzzK
UrQFSZJVHazweeOYNgCqmx82zE+cB4YzRLqkCPUD9t1bZcgk31tV39MSrC9CDKCB
inUTMKflPbHL0B+Lq24S8KfuW9bOPofhspjlV7cZCX5adFiBAkEA7KOMkiQMyq9X
ZVeRzJU0LmVdjrb7UBD5NebV+KaN8O7q+W4FG0nihcNj7xt2fZnvKM4FMfRwDP3G
HRUfR0alQQJBAOUIjKXYyoUsk+tLASoYLX+uPocjd7YSB9UPK2lFxqHOzekAlynF
u1JWEDPOjZNtNHmsQKOp5AWTUnm33JxfQLcCQByY5zQCB0m3RuiIXKZMobG5rkTA
+D4EzxkkfFdASYcEWIEsOpHBrA5ePoV23Crxn2VfAGG5GJF5WafKFa2XbAECQFL/
5Ch+BfZ5DynnxoZAuMxakuJaYhmjMx9tHehKlw8waMKVqjJDK/1MnxaHNhtFKg0l
9U7aVH4Iw4zEqrgodMUCQQCWZEUepSGoRVs1YDtag4FKSTMGXcnI/jllJmxHQhf4
uiy/8Hb+FW49w3KO1zeq7WdXw7W7Q1uO94npYX5p535d
-----END RSA PRIVATE KEY-----
Signed PEM:
-----BEGIN CERTIFICATE-----
MIICgjCCAeugAwIBAgIBADANBgkqhkiG9w0BAQUFADA6MQswCQYDVQQGEwJCRTEN
MAsGA1UECgwEVGVzdDENMAsGA1UECwwEVGVzdDENMAsGA1UEAwwEVGVzdDAeFw0x
NDA4MjEwMTI1MTZaFw0xNTA4MjEwMTI1MTZaMDoxCzAJBgNVBAYTAkJFMQ0wCwYD
VQQKDARUZXN0MQ0wCwYDVQQLDARUZXN0MQ0wCwYDVQQDDARUZXN0MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQDTtjPd3X9KX9BZpXKS82tM74Bs/hXsSLgnkitr
c+oR4oF5PVkoNZL3j51gkX3jJRSG9tNPQC5NVR+5h7tXPxU5TAQZl6MUiV+YWuRn
g98GeCjP3ePpmeSStsKEMUiZI8YLVWrdbIjS+Q+lZnYMffeEOAoMSaei9hR4rOX0
i+9hdwIDAQABo4GXMIGUMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNNbdqck
QT/B5hdQqimtW1Wnf+fmMGIGA1UdIwRbMFmAFNNbdqckQT/B5hdQqimtW1Wnf+fm
oT6kPDA6MQswCQYDVQQGEwJCRTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEVGVz
dDENMAsGA1UEAwwEVGVzdIIBADANBgkqhkiG9w0BAQUFAAOBgQB80KzzhkXCgJ0s
1zXJXuocNDU5v0Z42ditNX9jS8pXuhHwcQbx7PVfOieO3GHC5YzzgMHGR3i2U2CQ
rz9hP937ERxCfqpxhfMAD3Q+3rHsdGdNIauzzFb6XoXsM7koRnM27I6qvO3bamcz
AVGH5eLic9IjZTQbZizFzNoR+H2N/g==
-----END CERTIFICATE-----
There is a create_self_signed_cert method in webrick/ssl, which is easy to understand and useful.
I've since found several very good sources for examples using OpenSSL:
http://snippets.dzone.com/posts/show/6309
http://projects.reductivelabs.com/projects/puppet/repository/revisions/master/entry/lib/puppet/sslcertificates.rb
http://projects.reductivelabs.com/projects/puppet/repository/revisions/master/entry/lib/puppet/sslcertificates/ca.rb
http://projects.reductivelabs.com/projects/puppet/repository/revisions/master/entry/lib/puppet/sslcertificates/certificate.rb
I still haven't found any good documentation for this yet, although I don't think it would take too long to write down what's in the examples.
I've also figured out how to do what I wanted from the puppet source code. Hope this helps someone else who's frustrated at the lack of documentation of OpenSSL in ruby.

Resources