Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Digital signature verification with OpenSSL

Tags:

ruby

openssl

How can I verify CMS/PKCS #7 messages using OpenSSL in Ruby?
PKCS #7 message is used as a digital signature for user messages, so I need to sign a new user message and verify the incoming one. I haven't found anything helpfull in documentation and google. I have found few code samples for signing, but nothing for verifying:

signed = OpenSSL::PKCS7::sign(crt, key, data, [], OpenSSL::PKCS7::DETACHED)
like image 713
andrykonchin Avatar asked Jan 04 '10 11:01

andrykonchin


1 Answers

Short answer

Assuming everything is defined as they were in your snippet, with a detached signature, with no certificate chain to a trusted root, certificate crt, signature signed and data data, the following should do what you want:

store = OpenSSL::X509::Store.new
p7 = OpenSSL::PKCS7.new(signed.to_der)
verified = p7.verify([crt], store, data, 
                     OpenSSL::PKCS7::DETACHED || OpenSSL::PKCS7::NOVERIFY)

(I have not tested this, YMMV)

Full story

Here's the full story of how I found this, with links to all of the sources I used, so if you need more information, you have somewhere to go to look.

Taking a look at the OpenSSL::PKCS7 documentation, we find this morsel of wisdom:

PKCS7.new => pkcs7
PKCS7.new(string) => pkcs7
Many methods in this class aren’t documented.

And a quick Google doesn't turn anything up either. That says we're going to have to take more extreme measures. Let's do do a Google code search for anyone who uses OpenSSL::PKCS7 to verify a signature.

Hmm. We find some test cases. That's good; at least it has unit tests, which can help show that the functionality does work, and provide a demonstration of how it works.

store = OpenSSL::X509::Store.new
store.add_cert(@ca_cert)
ca_certs = [@ca_cert]

data = "aaaaa\r\nbbbbb\r\nccccc\r\n"
tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs)
p7 = OpenSSL::PKCS7::PKCS7.new(tmp.to_der)
certs = p7.certificates
signers = p7.signers
assert(p7.verify([], store))
assert_equal(data, p7.data)

That's not too bad. Crate a certificate store. Sign your data, then create a new OpenSSL::PKCS7 object from the signed data. Then, you can call certificates on it to extract the certificate chain it was signed with, signers to extract the signers, and verify can be called to verify that the signature is valid. It looks like you pass your certificate store, containing your trusted CA certificates, as the second argument to verify. And you can extract the data by calling data on it.

But what does the first argument mean? No one in our test cases seems to pass anything but an empty list in for the first argument. Hmm. A mystery. We'll come back to that.

The third, optional, argument to verify looks like it's used for verifying a detached signature:

data = "aaaaa\nbbbbb\nccccc\n"
flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED
tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag)
p7 = OpenSSL::PKCS7::PKCS7.new(tmp.to_der)
a1 = OpenSSL::ASN1.decode(p7)

certs = p7.certificates
signers = p7.signers
assert(!p7.verify([], store))
assert(p7.verify([], store, data))

Back to the first argument. We found more than just the test cases when we did our code search; we also found a few other uses. In fact, the second one seems to to use the first argument:

# 'true' if signature was created using given cert, 'false' otherwise
def match?(cert)
  @p7.verify([cert.raw_cert], @store, nil, OpenSSL::PKCS7::NOVERIFY)
end

Ah, OK. It's a list of certs to check against. And now there's a fourth parameter, which appears to consist of flags. Checking the OpenSSL docs, we see that this unintuitive name (verify with a NOVERIFY flag?) means that you should only check the signature against the certs passed in and the certs embedded in the signature, and not attempt to verify the entire certificate chain against your trusted CA store.

This is all useful information, but is there anything we're missing? Thankfully, Ruby is open source software, so we can "Use the source, Luke!" After some messing around on Google code search, we find the definition of ossl_pkcs7_verify. Once you get past the somewhat cryptic names, the code is fairly straightforward to read; it's basically just converting its arguments to a format that OpenSSL can understand, and calling:

ok = PKCS7_verify(p7, x509s, x509st, in, out, flg);

So, it looks like that's where we really want to look for documentation.

DESCRIPTION

PKCS7_verify() verifies a PKCS#7 signedData structure. p7 is the PKCS7 structure to verify. certs is a set of certificates in which to search for the signer's certificate. store is a trusted certficate store (used for chain verification). indata is the signed data if the content is not present in p7 (that is it is detached). The content is written to out if it is not NULL.

flags is an optional set of flags, which can be used to modify the verify operation.

PKCS7_get0_signers() retrieves the signer's certificates from p7, it does not check their validity or whether any signatures are valid. The certs and flags parameters have the same meanings as in PKCS7_verify().

See the full man page for more details.

Oh, and as a side note, I found this warning while searching; it looks like in Ruby 1.9, and possibly in some later versions of Ruby 1.8, the class has been moved from the redundant OpenSSL::PKCS7::PKCS7 to OpenSSL::PKCS7.

warn("Warning: OpenSSL::PKCS7::PKCS7 is deprecated after Ruby 1.9; use OpenSSL::PKCS7 instead")
like image 53
Brian Campbell Avatar answered Sep 21 '22 14:09

Brian Campbell