My DSA signature won't verify... :-(

Asked by cstanke

Hello,

I'm pretty sure I'm totally inept here but I can't get Sparkle to verify a DSA signed update archive. I'm running and building on 10.5.4 and using the GC dual-mode framework project from Bazaar revno: 219.

I generated the dsa keypair like the Sparkle 1.1 docs specify:

openssl dsaparam 2048 < /dev/urandom > dsaparam.pem
openssl gendsa dsaparam.pem -out dsa_priv.pem
openssl dsa -in dsa_priv.pem -pubout -out dsa_pub.pem

That's cool. I've got my keypair. Then I add the proper keys to my Info.plist and drop a copy of "dsa_pub.pem" in MyApp.app's "Resources" folder:

<key>SUExpectsDSASignature</key>
<true/>
<key>SUPublicDSAKeyFile</key>
<string>dsa_pub.pem</string>

No problems there. Next I generate a base64 encoded, dss1 signature of the sha1 hash of the MyApp.tgz archive (that's a mouthful). Then add that as a Sparkle extension attribute on the enclosure element in my appcast xml. I use the commands in the Sparkle 1.1 docs again - no sweat!

openssl dgst -sha1 -binary < ~/MyApp.tgz \
| openssl dgst -dss1 -sign ~/dsa_priv.pem \
| openssl enc -base64
MCwCFEpg2+Y/hQPeXP9j8OmNu96BgNpsAhQX6h9cZGQw8kqjlR7CxQvqD5oouQ==

sparkle:dsaSignature="MCwCFEpg2+Y/hQPeXP9j8OmNu96BgNpsAhQX6h9cZGQw8kqjlR7CxQvqD5oouQ=="

Then I update MyApp.app and Sparkle complains "The update is improperly signed."

Ahh. Damn! Ok. So I do my own verification. First I spit out the signature (without base64 encoding) to a file:

openssl dgst -sha1 -binary < ~/MyApp.tgz \
| openssl dgst -dss1 -sign ~/dsa_priv.pem > ~/sig.txt

Gotcha! Now I see if I can verify the signature with my public key:

openssl dsgt -sha1 -binary < ~/MyApp.tgz \
| openssl dgst -dss1 -verify ~/dsa_pub.pem -signature ~/sig.txt
Verification OK

Ok! So that works out ok. Now I'm stuck. I walked through the code with Sparkle Test App and the Debug framework and it failed to verify there also. I confirmed it's getting the public key and the signature and all the basic stuff. My tiny brain starts burn up when I get close to the cryptographic stuff in NSFileManager+Verification.m...

Do you see anything glaringly wrong with what I'm doing here? I'm lost on this now.

Thanks,
-Chad

Question information

Language:
English Edit question
Status:
Answered
For:
Sparkle Edit question
Assignee:
No assignee Edit question
Last query:
Last reply:
Revision history for this message
Andy Matuschak (andymatuschak) said :
#1

I performed the exact steps you just listed on Sparkle Test App, and it worked just fine (somewhat to my surprise—I don't test this too often), so it must be something a little more persnickety. A couple things to check:

1. Check your compiled .app. Is the .pem *really* in Resources?
2. Are you sure the bytes didn't get manged between ~/MyApp.tgz and the server? Long shot, I know, but try re-uploading.
3. Break in NSFileManager+Verification.m:109. Are the method parameters all non-nil? Are they what you expect them to be? Is there some kind of weird whitespace or encoding nonsense going on that's throwing it off?
4. Step through the scary function in NSFileManager+Verification.m not to see what's going on, but specifically to determine which "return NO" is being tripped.

Revision history for this message
cstanke (cstanke) said :
#2

Hi Andy,

Thanks for the response. I appreciate your time as I know it's hard to develop Sparkle on top of everything else you, then answer people's questions!

So I walked this through the debugger again paying closer attention to your long shot ("#2 Are you sure the bytes didn't get manged..."). As it turns out, I created and signed a '.tgz' file but the downloaded file in '/var/folders/5o/5oKqC0zqFFGAJye0lC2y0U+++TI/-Tmp-/MyApp 0.0 Update' has been gunzipped prior to the verification (NSFileManager+Verification.m:109). So it's just a tar file now and of course, the dsa signature from the 'tgz' file is not valid for the 'tar' file.

I set breakpoints where I thought might be likely candidates in SUUnarchiver.m and SUPipedUnarchiver.m but it's not breaking there before the verification. So, I'm probably looking in the wrong place.

So my immediate work around is to sign the '.tar' file before I 'gzip' it. I need to know though, if this is expected behavior?

Revision history for this message
cstanke (cstanke) said :
#3

Maybe the name change (and gunzip??) is happening in NSURLDownload download:decideDestinationWithSuggestedFilename: ??

If I breakpoint SUBasicUpdateDriver.m:112 the url is MyApp.tgz. Continue to a breakpoint in the download delegate method at SUBasicUpdateDriver.m:117 and passed in "name" is MyApp.tar.

Revision history for this message
cstanke (cstanke) said :
#4

Ahh! If only I'd read the documentation...

"If NSURLDownload determines that the downloaded file is in a format that it is able to decode (MacBinary, Binhex or gzip), the delegate will receive a download:shouldDecodeSourceDataOfMIMEType:. The delegate should return YES to decode the data, NO otherwise."

It seems Sparkle doesn't deal with shouldDecodeSourceDataOfMIMEType. Should this be logged as a bug or is it by design?

Revision history for this message
Andy Matuschak (andymatuschak) said :
#5

Excellent detective work, sir! You've found a bug! https://bugs.launchpad.net/sparkle/+bug/246469

What I don't understand, though, is why this doesn't break extraction. SUUnarchiver doesn't know how to extract .tars. It shouldn't be able to extract your update. Curious.

Revision history for this message
vijay_k (vijay-kanse) said :
#6

&nbsp;

Revision history for this message
Simon Pierre Desrosiers (simonpie) said :
#7

I wish to add my two cents to this.

I had the exact same problem. I generated keys and signature using the ruby script. Publish my app cast ran my test and boom : "The update is improperly signed." Darn, what did I do wrong. I followed example on this page, generating in the terminal new signatures and testing that I could sign and verify. It worked just fine.

So I decided to add some NSLogs to sparkle to see what was going on. Obviously the signature would verify, but by printing (using NSLog in - (void)downloadDidFinish:(NSURLDownload *)d method of SUBasicUpdateDriver) the downdoalded path, which looked like

/var/folders/61/61O4hMUu2RWrXE+1YsfL7U+++TI/-Tmp-/Sauvegarde 1.0.1 Update 42/Sauvegarde.zip

using the DSASignature which looked like

MEUCIHd6pYSoQiuPb0j9wypbd9m8dXqtKiVl3iT++3sCUL83AiEAsPDcPKPmU0oQ J21ktE4Y8zRV9MFJ95jfPoLOtymnQtg=

and openssl in the terminal I could still validate my signature !?!. What the hell is going on ?

I then ran "which opennssl" in the terminal and got

/opt/local/bin/openssl

HaaaHA ! That bloody port had reinstalled and superseded /usr/bin/openssl by its own version, which is incompatible with the bundled version.

Ok then, lets retry with /usr/bin/openssl to generate signature and verify them. Does not work ! Darn. I changed my environment variables to make sure that openssl calls /usr/bin/openssl. Does not work.

It turns out, I had to regenerate my keys after updating the generate_keys.rb script from

#!/usr/bin/ruby
["dsaparam.pem", "dsa_priv.pem", "dsa_pub.pem"].each do |file|
  if File.exist? file
    puts "There's already a #{file} here! Move it aside or be more careful!"
  end
end
`openssl dsaparam 2048 < /dev/urandom > dsaparam.pem`
`openssl gendsa dsaparam.pem -out dsa_priv.pem`
`openssl dsa -in dsa_priv.pem -pubout -out dsa_pub.pem`
`rm dsaparam.pem`
puts "\nGenerated private and public keys: dsa_priv.pem and dsa_pub.pem.\n
BACK UP YOUR PRIVATE KEY AND KEEP IT SAFE!\n
If you lose it, your users will be unable to upgrade!\n"

to

#!/usr/bin/ruby
["dsaparam.pem", "dsa_priv.pem", "dsa_pub.pem"].each do |file|
  if File.exist? file
    puts "There's already a #{file} here! Move it aside or be more careful!"
  end
end
`/usr/bin/openssl dsaparam 2048 < /dev/urandom > dsaparam.pem`
`/usr/bin/openssl gendsa dsaparam.pem -out dsa_priv.pem`
`/usr/bin/openssl dsa -in dsa_priv.pem -pubout -out dsa_pub.pem`
`rm dsaparam.pem`
puts "\nGenerated private and public keys: dsa_priv.pem and dsa_pub.pem.\n
BACK UP YOUR PRIVATE KEY AND KEEP IT SAFE!\n
If you lose it, your users will be unable to upgrade!\n"

I.E. just prefixing openssl with /usr/bin/ three times in the script. Now it works and my superduper app can update.

Maybe that is worth a line in the instruction.

Revision history for this message
edwin (edwincheese) said :
#8

I would like to follow Simon's notes. I have exactly the same problem. Simon's solution work fine for me.
I think if it different version of openssl with different default settings, there should be some arguments to override that settings and that is better be put in the scripts.
Thanks.

Can you help with this problem?

Provide an answer of your own, or ask cstanke for more information if necessary.

To post a message you must log in.