Class: OPA::Verifier

Inherits:
Object
  • Object
show all
Defined in:
lib/opa/verifier.rb

Overview

Reads and verifies OPA archives. Supports signature verification using JAR-format signing.

Constant Summary collapse

SUPPORTED_DIGESTS =
Signer::SUPPORTED_DIGESTS
REJECTED_DIGESTS =
Signer::REJECTED_DIGESTS

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io_or_path) ⇒ Verifier

Returns a new instance of Verifier.



16
17
18
19
20
21
22
23
24
# File 'lib/opa/verifier.rb', line 16

def initialize(io_or_path)
  @raw_entries = {}
  data = if io_or_path.is_a?(String)
           File.binread(io_or_path)
         else
           io_or_path.read
         end
  read_zip(data)
end

Instance Attribute Details

#entriesObject (readonly)

Returns the value of attribute entries.



14
15
16
# File 'lib/opa/verifier.rb', line 14

def entries
  @entries
end

#manifestObject (readonly)

Returns the value of attribute manifest.



14
15
16
# File 'lib/opa/verifier.rb', line 14

def manifest
  @manifest
end

Instance Method Details

#data_entriesObject



94
95
96
97
# File 'lib/opa/verifier.rb', line 94

def data_entries
  prefix = @manifest["Data-Root"] || "data/"
  @raw_entries.select { |k, _| k.start_with?(prefix) }
end

#promptObject



84
85
86
87
# File 'lib/opa/verifier.rb', line 84

def prompt
  prompt_path = @manifest["Prompt-File"] || "prompt.md"
  @raw_entries[prompt_path]
end

#read_entry(path) ⇒ Object

Read a specific entry from the archive.



80
81
82
# File 'lib/opa/verifier.rb', line 80

def read_entry(path)
  @raw_entries[path]
end

#sessionObject



89
90
91
92
# File 'lib/opa/verifier.rb', line 89

def session
  session_path = @manifest["Session-File"] || "session/history.json"
  @raw_entries[session_path]
end

#signed?Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/opa/verifier.rb', line 26

def signed?
  @raw_entries.key?("META-INF/SIGNATURE.SF")
end

#verify!(certificate: nil) ⇒ Object

Verify the archive signature. Returns true if valid. Raises OPA::SignatureError on failure.

Raises:



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/opa/verifier.rb', line 32

def verify!(certificate: nil)
  raise SignatureError, "Archive is not signed" unless signed?

  sf_content = @raw_entries["META-INF/SIGNATURE.SF"]
  sf_attrs = parse_sf(sf_content)

  # Find and verify the signature block
  block_entry = find_signature_block
  raise SignatureError, "No signature block file found" unless block_entry

  block_data = @raw_entries[block_entry]
  verify_pkcs7_signature(block_data, sf_content, certificate)

  # Verify manifest digest
  manifest_content = @raw_entries["META-INF/MANIFEST.MF"]
  digest_name = detect_digest_algorithm(sf_attrs[:main])
  verify_digest(digest_name, manifest_content, sf_attrs[:main]["#{digest_name}-Digest-Manifest"],
                "Manifest digest mismatch")

  # Verify individual entry digests
  sf_attrs[:entries].each do |name, attrs|
    entry_digest_name = detect_digest_algorithm(attrs)
    manifest_section = extract_manifest_section(manifest_content, name)
    raise SignatureError, "No manifest section for #{name}" unless manifest_section

    verify_digest(entry_digest_name, manifest_section, attrs["#{entry_digest_name}-Digest"],
                  "Section digest mismatch for #{name}")
  end

  # Verify manifest entry digests against actual content
  @manifest.entries.each do |name, attrs|
    entry_digest_name = detect_digest_algorithm(attrs)
    next unless entry_digest_name

    expected = attrs["#{entry_digest_name}-Digest"]
    next unless expected

    actual_content = @raw_entries[name]
    raise SignatureError, "Missing entry: #{name}" unless actual_content

    verify_digest(entry_digest_name, actual_content, expected,
                  "Content digest mismatch for #{name}")
  end

  true
end