docs: def verify(certificate, jar_file, sf_name=None):
      """
      Verifies signature of a JAR file.
  
      Limitations:
      - diagnostic is less verbose than of jarsigner
      :return None if verification succeeds.
      :exception SignatureBlockFileVerificationError, ManifestChecksumError,
          JarChecksumError, JarSignatureMissingError
  
      Reference:
      http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#Signature_Validation
      Note that the validation is done in three steps. Failure at any step is a
      failure of the whole validation.
      """  # noqua
  
      # Step 0: get the "key alias", used also for naming of sig-related files.
      zip_file = ZipFile(jar_file)
      sf_files = [f for f in zip_file.namelist() if file_matches_sigfile(f)]
  
      if len(sf_files) == 0:
          raise JarSignatureMissingError("No .SF file in %s" % jar_file)
  
      elif len(sf_files) > 1:
          if sf_name is None:
              msg = "Multiple .SF files in %s, but SF_NAME.SF not specified" \
                  % jar_file
              raise VerificationError(msg)
  
          elif ('META-INF/' + sf_name) in sf_files:
              sf_filename = 'META-INF/' + sf_name
  
          else:
              msg = "No .SF file in %s named META-INF/%s (found %d .SF files)" \
                  % (jar_file, sf_name, len(sf_files))
              raise VerificationError(msg)
  
      elif len(sf_files) == 1:
          if sf_name is None:
              sf_filename = sf_files[0]
  
          elif sf_files[0] == 'META-INF/' + sf_name:
              sf_filename = sf_files[0]
  
          else:
              msg = "No .SF file in %s named META-INF/%s" % (jar_file, sf_name)
              raise VerificationError(msg)
  
      key_alias = sf_filename[9:-3]  # "META-INF/%s.SF"
      sf_data = zip_file.read(sf_filename)
  
      # Step 1: check the crypto part.
      file_list = zip_file.namelist()
      sig_block_filename = None
  
      # JAR specification mentions only RSA and DSA; jarsigner also has EC
      # TODO: what about "SIG-*"?
      signature_extensions = ("RSA", "DSA", "EC")
      for extension in signature_extensions:
          candidate_filename = "META-INF/%s.%s" % (key_alias, extension)
          if candidate_filename in file_list:
              sig_block_filename = candidate_filename
              break
  
      if sig_block_filename is None:
          msg = "None of %s found in JAR" % \
                ", ".join(key_alias + "." + x for x in signature_extensions)
          raise JarSignatureMissingError(msg)
  
      sig_block_data = zip_file.read(sig_block_filename)
      try:
          verify_signature_block(certificate, sf_data, sig_block_data)
  
      except SignatureBlockVerificationError as message:
          message = "Signature block verification failed: %s" % message
          raise SignatureBlockFileVerificationError(message)
  
      # KEYALIAS.SF is correctly signed.
      # Step 2: Check that it contains correct checksum of the manifest.
      signature_manifest = SignatureManifest()
      signature_manifest.parse(sf_data)
  
      jar_manifest = Manifest()
      jar_manifest.load_from_jar(jar_file)
  
      errors = signature_manifest.verify_manifest(jar_manifest)
      if len(errors) > 0:
          msg = "%s: in .SF file, section checksum(s) failed for: %s" \
                % (jar_file, ",".join(errors))
          raise ManifestChecksumError(msg)
  
      # Checksums of MANIFEST.MF itself are correct.
  
      # Step 3: Check that it contains valid checksums for each file
      # from the JAR.  NOTE: the check is done for JAR entries. If some
      # JAR entries are deleted after signing, the verification still
      # succeeds.  This seems to not follow the reference specification,
      # but that's what jarsigner does.
      errors = jar_manifest.verify_jar_checksums(jar_file)
      if len(errors) > 0:
          msg = "Checksum(s) for jar entries of jar file %s failed for: %s" \
                % (jar_file, ",".join(errors))
          raise JarChecksumError(msg)
  
      return None