Guest User

Untitled

a guest
May 26th, 2018
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.06 KB | None | 0 0
  1. =begin
  2. Copyright (c) 2008 GOTOU Yuuzou
  3.  
  4. The MIT License
  5.  
  6. Permission is hereby granted, free of charge, to any person obtaining a copy
  7. of this software and associated documentation files (the "Software"), to deal
  8. in the Software without restriction, including without limitation the rights
  9. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. copies of the Software, and to permit persons to whom the Software is
  11. furnished to do so, subject to the following conditions:
  12.  
  13. The above copyright notice and this permission notice shall be included in
  14. all copies or substantial portions of the Software.
  15.  
  16. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. THE SOFTWARE.
  23. =end
  24.  
  25. require "openssl"
  26. require "digest/sha1"
  27. require "digest/md5"
  28. require "enumerator"
  29. require "stringio"
  30. require "find"
  31. require "logger"
  32.  
  33. class JarSigner
  34. Error = Class.new(StandardError)
  35. InvalidFormatError = Class.new(Error)
  36. NoManifestFile = Class.new(Error)
  37. NoSignatureFile = Class.new(Error)
  38. NoPublicKeySignatureFile = Class.new(Error)
  39. NoSignature = Class.new(Error)
  40.  
  41. Logger = ::Logger.new($stderr, ::Logger::ERROR)
  42. Logger.progname = File.basename($0, ".rb")
  43. Logger.formatter = Proc.new{|level, datetime, progname, message|
  44. "%s: %s\n" % [progname, message]
  45. }
  46.  
  47. def logger=(logger)
  48. @logger = logger
  49. end
  50.  
  51. def logger
  52. @logger || Logger
  53. end
  54.  
  55. class ManifestEntry
  56. attr_accessor :params, :digest
  57. end
  58.  
  59. class Manifest
  60. attr_accessor :entries, :digest, :header, :header_digest
  61.  
  62. def initialize
  63. @header = nil
  64. @entries = Hash.new
  65. @digest = @header_digest = nil
  66. end
  67. end
  68.  
  69. class Signature
  70. attr_accessor :header, :public_key_signature, :content
  71. attr_reader :entries
  72.  
  73. def initialize
  74. @header = nil
  75. @entries = Hash.new
  76. @public_key_signature = nil
  77. @content = nil
  78. end
  79. end
  80.  
  81. class DigestCollection
  82. attr_reader :md5, :sha1
  83.  
  84. def initialize(str="")
  85. @digests = [
  86. @md5 = Digest::MD5.new,
  87. @sha1 = Digest::SHA1.new,
  88. ]
  89. update(str)
  90. end
  91.  
  92. def update(str)
  93. @digests.each{|digest| digest.update(str) }
  94. end
  95. end
  96.  
  97. module PublicKeySignatureUtilities
  98. module_function
  99.  
  100. DefaultStore = OpenSSL::X509::Store.new
  101. DefaultStore.set_default_paths
  102. DefaultStore.verify_callback = Proc.new do |preverify_ok, store_ctx|
  103. PublicKeySignatureUtilities.print_verify_info(preverify_ok, store_ctx) ||
  104. PublicKeySignatureUtilities.check_purpose(preverify_ok, store_ctx) ||
  105. false
  106. end
  107.  
  108. def check_purpose(prevefiy_ok, store_ctx)
  109. cert = store_ctx.current_cert
  110. if store_ctx.error != OpenSSL::X509::V_ERR_INVALID_PURPOSE
  111. return false
  112. elsif ext = cert.extensions.find{|ext| ext.oid == "extendedKeyUsage" }
  113. if ext.value.split(/,/).find{|val| val.strip == "Code Signing" }
  114. return true
  115. end
  116. end
  117. return false
  118. end
  119.  
  120. def print_verify_info(preverify_ok, store_ctx)
  121. cert = store_ctx.current_cert
  122. # Logger.info do
  123. # msg = [
  124. # "",
  125. # "------------------------------------------",
  126. # "depth: %d" % [store_ctx.error_depth,],
  127. # "error: %d: %s" % [store_ctx.error, store_ctx.error_string,],
  128. # "subject: %s" % [certificate_identitity(cert.subject),],
  129. # "issuer: %s" % [certificate_identitity(cert.issuer),],
  130. # "validity: %s - %s" % [cert.not_before, cert.not_after,],
  131. # "------------------------------------------",
  132. # ]
  133. # msg.join("\n")
  134. # end
  135. return preverify_ok
  136. end
  137.  
  138. def certificate_identitity(dn)
  139. dn = dn.to_a
  140. rdn = nil
  141. rdn ||= dn.find{|n| n.first=="CN"}
  142. rdn ||= dn.find{|n| n.first=="O"}
  143. rdn ||= dn.last
  144. return "%1$s=%2$s" % rdn
  145. end
  146. end
  147.  
  148. #############################################
  149.  
  150. attr_reader :path, :manifest, :signatures
  151. attr_accessor :compatible_mode
  152.  
  153. def load_manifest
  154. File.open(find_mf_file) do |stream|
  155. text, header = read_entry(stream)
  156. assume_compatible_mode(header)
  157. @manifest.header = header
  158. @manifest.digest = DigestCollection.new(text)
  159. @manifest.header_digest = entry_digest(text)
  160. while entry = read_entry(stream)
  161. text, hash = *entry
  162. unless name = hash["Name"]
  163. raise InvalidFormatError,
  164. "'Name:' must be in manifest entry: #{hash.inspect}"
  165. end
  166. manifest_entry = ManifestEntry.new
  167. manifest_entry.params = hash
  168. manifest_entry.digest = entry_digest(text)
  169. @manifest.entries[name] = manifest_entry
  170. @manifest.digest.update(text)
  171. end
  172. end
  173. end
  174.  
  175. def available_signatures
  176. list = []
  177. entries = Dir.entries("#{@path}/META-INF")
  178. entries.each do |entry|
  179. if /(.+).sf/i =~ entry
  180. list << $1
  181. end
  182. end
  183. return list.sort
  184. end
  185.  
  186. def load_signature(sign_name=nil)
  187. sign_name ||= available_signatures.first
  188. sign = Signature.new
  189. sign.content = ""
  190.  
  191. # load META-INF/XXXXXXX.SF
  192. File.open(find_sf_file(sign_name), "rb") do |stream|
  193. text, header = read_entry(stream)
  194. sign.header = header
  195. sign.content << text
  196. while entry = read_entry(stream)
  197. text, hash = *entry
  198. sign.entries[hash["Name"]] = hash
  199. sign.content << text
  200. end
  201. end
  202.  
  203. # load META-INF/XXXXXXX.RSA
  204. File.open(find_public_key_signature_file(sign_name), "rb") do |stream|
  205. sign.public_key_signature = OpenSSL::PKCS7::PKCS7.new(stream.read)
  206. end
  207.  
  208. @signatures[sign_name] = sign
  209. return true
  210. end
  211.  
  212. #############################################
  213.  
  214. def verify(sign_name=nil, store=nil)
  215. sign_name ||= available_signatures.first
  216. return verify_manifest(sign_name) &&
  217. verify_digest_manifest(sign_name) &&
  218. verify_publick_key_signature(sign_name, store)
  219. end
  220.  
  221. def verify_manifest(sign_name=nil)
  222. unsigned = []
  223. result = true
  224. verified = Hash.new(false)
  225. sign_name ||= available_signatures.first
  226.  
  227. entries = Dir.entries(@path)
  228. entries -= ["META-INF", ".", ".."]
  229. entries.collect!{|entry| File.join(@path, entry) }
  230. Find.find(*entries) do |path|
  231. next if File.directory?(path)
  232. digest = DigestCollection.new(File.open(path, "rb"){|io| io.read })
  233. name = path.gsub(@path+"/", "")
  234. m = verify_manifest_entry(digest, name)
  235. s = verify_digest_entry(sign_name, name)
  236. logger.debug("%s%s %s" % [m ? "m" : "-", s ? "s" : "-", name])
  237. result &&= m
  238. unsigned << name unless s
  239. verified[name] = true
  240. end
  241.  
  242. @manifest.entries.each do |name, entry|
  243. if !verified.key?(name)
  244. logger.info "unverified file: #{name}"
  245. result = false
  246. elsif !verified[name]
  247. logger.info "signature verification failed for a file: #{name}"
  248. result = false
  249. end
  250. end
  251.  
  252. unless unsigned.empty?
  253. logger.warn "This jar contains unsigned entries which have not been integrity-checked."
  254. unsigned.each do |name|
  255. logger.warn " unsigned: #{name}"
  256. end
  257. end
  258.  
  259. return result
  260. end
  261.  
  262. def verify_digest_manifest(sign_name)
  263. assert_signature_loaded(sign_name)
  264. sign = @signatures[sign_name]
  265. unless result =
  266. verify_digest(@manifest.digest.sha1.digest, sign.header["SHA1-Digest-Manifest"]) ||
  267. verify_digest(@manifest.header_digest.sha1.digest, sign.header["SHA1-Digest"])
  268. logger.info "digest in manifest signature not match: #{sign_name}"
  269. end
  270. return result
  271. end
  272.  
  273. def verify_publick_key_signature(sign_name, store=nil)
  274. assert_signature_loaded(sign_name)
  275. sign = @signatures[sign_name]
  276. pkcs7 = sign.public_key_signature
  277. flags = OpenSSL::PKCS7::DETACHED|OpenSSL::PKCS7::BINARY
  278. certs = pkcs7.certificates
  279. store ||= PublicKeySignatureUtilities::DefaultStore
  280. unless pkcs7.verify(certs, store, sign.content, flags)
  281. logger.info "failed to verify PKCS #7: #{sign_name}"
  282. return false
  283. end
  284. return true
  285. end
  286.  
  287. #############################################
  288.  
  289. def sign(name, pkey, certs)
  290. # not yet implemented
  291. end
  292.  
  293. #############################################
  294.  
  295. def jar?
  296. return @compatible_mode == :jar
  297. end
  298.  
  299. def xpi?
  300. return @compatible_mode == :xpi
  301. end
  302.  
  303. #############################################
  304.  
  305. private
  306.  
  307. def initialize(path)
  308. @compatible_mode = nil
  309. @path = File.expand_path(path)
  310. @manifest = Manifest.new
  311. @signatures = Hash.new
  312. end
  313.  
  314. def verify_manifest_entry(digest, name)
  315. if entry = @manifest.entries[name]
  316. if verify_digest(digest.sha1.digest, entry.params["SHA1-Digest"]) ||
  317. verify_digest(digest.md5.digest, entry.params["MD5-Digest"])
  318. return true
  319. end
  320. end
  321. return false
  322. end
  323.  
  324. def verify_digest_entry(sign_name, name)
  325. return false unless sign = @signatures[sign_name]
  326. if entry = @manifest.entries[name]
  327. if sigentry = sign.entries[name]
  328. if verify_digest(entry.digest.sha1.digest, sigentry["SHA1-Digest"]) ||
  329. verify_digest(entry.digest.md5.digest, sigentry["MD5-Digest"])
  330. return true
  331. end
  332. end
  333. end
  334. return false
  335. end
  336.  
  337. def assume_compatible_mode(hash)
  338. case hash["Created-By"]
  339. when /Sun Microsystems Inc/
  340. @compatible_mode = :jar
  341. when /Signtool/
  342. @compatible_mode = :xpi
  343. else
  344. @compatible_mode = :unknown
  345. end
  346. end
  347.  
  348. def assert_signature_loaded(sign_name)
  349. unless sign = @signatures[sign_name]
  350. raise NoSignature, "missing signature entry: #{sign_name}"
  351. end
  352. unless sign.public_key_signature
  353. raise NoSignature, "missing public key cryptgraphic signature: #{sign_name}"
  354. end
  355. end
  356.  
  357. def entry_digest(text)
  358. digest = DigestCollection.new
  359. text.each_line do |line|
  360. next if line.chomp.empty? && xpi?
  361. digest.update(line)
  362. end
  363. return digest
  364. end
  365.  
  366. def find_inf_file(basenames, exception)
  367. basenames.each do |basename|
  368. basename = basename.downcase
  369. entries = Dir.entries("#{@path}/META-INF")
  370. entries.each do |entry|
  371. if basename == entry.downcase
  372. return File.expand_path(entry, "#{@path}/META-INF")
  373. end
  374. end
  375. end
  376. raise exception, "could not find: #{basenames}"
  377. end
  378.  
  379. def find_mf_file
  380. return find_inf_file("manifest.mf", NoManifestFile)
  381. end
  382.  
  383. def find_sf_file(name)
  384. return find_inf_file("#{name}.sf", NoSignatureFile)
  385. end
  386.  
  387. def find_public_key_signature_file(name)
  388. return find_inf_file(["#{name}.rsa", "#{name}.dsa"], NoPublicKeySignatureFile)
  389. end
  390.  
  391. def verify_digest(digest, expected)
  392. if expected
  393. return digest == decode64(expected)
  394. end
  395. return false
  396. end
  397.  
  398. def encode64(str)
  399. return str ? [str].pack("m").delete("\r\n") : nil
  400. end
  401.  
  402. def decode64(str)
  403. return str ? str.unpack("m").first : nil
  404. end
  405.  
  406. def read_entry(lines)
  407. text = ""
  408. hash = Hash.new
  409. value = nil
  410. lines.each_line do |line|
  411. text << line
  412. case line.chomp
  413. when /^$/
  414. break
  415. when /^([\w-]+):\s+(.*)$/n
  416. name = $1
  417. value = $2
  418. hash[name] = value
  419. when /^\s+(.*)$/n
  420. value << $1
  421. else
  422. raise InvalidFormatError, "invalid format: #{line}"
  423. end
  424. end
  425. return hash.empty? ? nil : [text, hash]
  426. end
  427. end
  428.  
  429. if __FILE__ == $0
  430. js = JarSigner.new(ARGV[0] || ".")
  431. js.logger.level = ::Logger::DEBUG
  432. js.load_manifest
  433. js.load_signature
  434. puts "verify -> %p" % js.verify
  435.  
  436. js = JarSigner.new(ARGV[0] || ".")
  437. js.logger.level = ::Logger::ERROR
  438. js.load_manifest
  439. js.available_signatures.each do |sign_name|
  440. puts "----- %s -----" % sign_name
  441. js.load_signature(sign_name)
  442. puts "verify_manifest -> %p" % js.verify_manifest(sign_name)
  443. puts "verify_digest_manifest-> %p" % js.verify_digest_manifest(sign_name)
  444. puts "verify_publick_key_signature -> %p" % js.verify_publick_key_signature(sign_name)
  445. end
  446. end
Add Comment
Please, Sign In to add comment