Class: Capsium::Package::Security

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/capsium/package/security.rb,
sig/capsium/package/security.rbs

Overview

Loads, generates and verifies security.json (ARCHITECTURE.md section 6). Checksums cover every file in the package except security.json itself and signature.sig.

Defined Under Namespace

Classes: ChecksumMismatch, IntegrityError, MissingFile, UncheckedFile

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, config = nil) ⇒ Security

Returns a new instance of Security.

Parameters:



38
39
40
41
# File 'lib/capsium/package/security.rb', line 38

def initialize(path, config = nil)
  @path = path
  @config = config || load_config
end

Instance Attribute Details

#configSecurityConfig? (readonly)

Returns the value of attribute config.

Returns:



34
35
36
# File 'lib/capsium/package/security.rb', line 34

def config
  @config
end

#pathString (readonly)

Returns the value of attribute path.

Returns:

  • (String)


34
35
36
# File 'lib/capsium/package/security.rb', line 34

def path
  @path
end

Class Method Details

.checksums_for(package_path) ⇒ Hash[String, String]

Parameters:

  • package_path (String)

Returns:

  • (Hash[String, String])


56
57
58
59
60
# File 'lib/capsium/package/security.rb', line 56

def self.checksums_for(package_path)
  package_files(package_path).sort.to_h do |relative_path|
    [relative_path, Digest::SHA256.file(File.join(package_path, relative_path)).hexdigest]
  end
end

.excluded?(relative_path) ⇒ Boolean

Parameters:

  • relative_path (String)

Returns:

  • (Boolean)


70
71
72
# File 'lib/capsium/package/security.rb', line 70

def self.excluded?(relative_path)
  [SECURITY_FILE, SIGNATURE_FILE].include?(relative_path)
end

.generate(package_path, digital_signatures: nil) ⇒ Security

Parameters:

Returns:



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/capsium/package/security.rb', line 43

def self.generate(package_path, digital_signatures: nil)
  config = SecurityConfig.new(
    security: SecurityData.new(
      integrity_checks: IntegrityChecks.new(
        checksum_algorithm: "SHA-256",
        checksums: checksums_for(package_path)
      ),
      digital_signatures: digital_signatures
    )
  )
  new(File.join(package_path, SECURITY_FILE), config)
end

.package_files(package_path) ⇒ Array[String]

Parameters:

  • package_path (String)

Returns:

  • (Array[String])


62
63
64
65
66
67
68
# File 'lib/capsium/package/security.rb', line 62

def self.package_files(package_path)
  files = Dir.glob(File.join(package_path, "**", "*")).select do |file|
    File.file?(file)
  end
  files.map { |file| file.delete_prefix("#{package_path}/") }
       .reject { |relative_path| excluded?(relative_path) }
end

Instance Method Details

#checksumsHash[String, String]

Returns:

  • (Hash[String, String])


89
90
91
# File 'lib/capsium/package/security.rb', line 89

def checksums
  present? ? config.checksums : {}
end

#digital_signaturesDigitalSignatures?

The declared digitalSignatures block, or nil when absent.

Returns:



79
80
81
82
83
# File 'lib/capsium/package/security.rb', line 79

def digital_signatures
  return unless present? && config.security

  config.security.digital_signatures
end

#present?Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/capsium/package/security.rb', line 74

def present?
  !@config.nil?
end

#save_to_file(output_path = @path) ⇒ void

This method returns an undefined value.

Parameters:

  • output_path (String) (defaults to: @path)


112
113
114
# File 'lib/capsium/package/security.rb', line 112

def save_to_file(output_path = @path)
  File.write(output_path, to_json)
end

#signed?Boolean

Returns:

  • (Boolean)


85
86
87
# File 'lib/capsium/package/security.rb', line 85

def signed?
  !digital_signatures&.signature_file.nil?
end

#to_hashHash[String, untyped]

Returns:

  • (Hash[String, untyped])


69
# File 'sig/capsium/package/security.rbs', line 69

def to_hash: () -> Hash[String, untyped]

#to_jsonString

Parameters:

  • args (Object)

Returns:

  • (String)


68
# File 'sig/capsium/package/security.rbs', line 68

def to_json: (*untyped args) -> String

#verify(package_path) ⇒ Array[ChecksumMismatch | MissingFile | UncheckedFile]

Parameters:

  • package_path (String)

Returns:



93
94
95
96
97
98
99
100
101
102
# File 'lib/capsium/package/security.rb', line 93

def verify(package_path)
  expected = checksums
  errors = verify_present_files(package_path, expected)
  expected.each do |relative_path, checksum|
    next if File.file?(File.join(package_path, relative_path))

    errors << MissingFile.new(path: relative_path, expected: checksum)
  end
  errors
end

#verify!(package_path) ⇒ void

This method returns an undefined value.

Parameters:

  • package_path (String)

Raises:



104
105
106
107
108
109
110
# File 'lib/capsium/package/security.rb', line 104

def verify!(package_path)
  errors = verify(package_path)
  return if errors.empty?

  raise IntegrityError,
        "Package integrity check failed: #{errors.map(&:path).join(', ')}"
end