Adds class iaf_file to validate the signed profile

Signed-off-by: Vasu1105 <vasundhara.jagdale@chef.io>
This commit is contained in:
Vasu1105 2022-04-27 17:31:38 +05:30
parent 89aaa621d4
commit 6b7041376a
4 changed files with 94 additions and 124 deletions

View file

@ -2,7 +2,7 @@ require "rubygems/package" unless defined?(Gem::Package)
require "pathname" unless defined?(Pathname) require "pathname" unless defined?(Pathname)
require "zlib" unless defined?(Zlib) require "zlib" unless defined?(Zlib)
require "zip" unless defined?(Zip) require "zip" unless defined?(Zip)
require "inspec/verify_signature" require "inspec/iaf_file"
module Inspec module Inspec
class FileProvider class FileProvider
@ -15,8 +15,13 @@ module Inspec
TarProvider.new(path) TarProvider.new(path)
elsif File.exist?(path) && path.end_with?(".zip") elsif File.exist?(path) && path.end_with?(".zip")
ZipProvider.new(path) ZipProvider.new(path)
elsif File.exist?(path) && path.end_with?(".iaf") && VerifySignature.valid(path) elsif File.exist?(path) && path.end_with?(".iaf")
IafProvider.new(path) iaf_file = IafFile.new(path)
if iaf_file.valid?
IafProvider.new(path)
else
raise "Artifact is invalid"
end
elsif File.exist?(path) elsif File.exist?(path)
DirProvider.new(path) DirProvider.new(path)
else else
@ -230,15 +235,15 @@ module Inspec
content = f.read content = f.read
f.close f.close
else else
key = f.readline.strip! f.readline.strip!
content = f.read content = f.read[358..content.length]
f.close f.close
end end
tmpfile = nil tmpfile = nil
Dir.mktmpdir do |workdir| Dir.mktmpdir do |workdir|
tmpfile = Pathname.new(workdir).join("artifact_to_install.tar.gz") tmpfile = Pathname.new(workdir).join("artifact_to_install.tar.gz")
File.open(tmpfile, "wb") { |fl| fl.write(content[358..content.length]) } File.open(tmpfile, "wb") { |fl| fl.write(content) }
super(tmpfile) super(tmpfile)
end end
end end

77
lib/inspec/iaf_file.rb Normal file
View file

@ -0,0 +1,77 @@
require "base64" unless defined?(Base64)
require "openssl" unless defined?(OpenSSL)
# TODO: Refactor this once the binary format work gets merged.
module Inspec
class IafFile
KEY_ALG = OpenSSL::PKey::RSA
INSPEC_PROFILE_VERSION_1 = "INSPEC-PROFILE-1".freeze
INSPEC_PROFILE_VERSION_2 = "INSPEC-PROFILE-2".freeze
ARTIFACT_DIGEST = OpenSSL::Digest::SHA512
ARTIFACT_DIGEST_NAME = "SHA512".freeze
VALID_PROFILE_VERSIONS = Set.new [INSPEC_PROFILE_VERSION_1, INSPEC_PROFILE_VERSION_2]
VALID_PROFILE_DIGESTS = Set.new [ARTIFACT_DIGEST_NAME]
def initialize(path)
@path = path
end
def valid?
header = []
valid = true
f = File.open(@path, "rb")
version = f.readline.strip!
if version == INSPEC_PROFILE_VERSION_1
header << version
header << f.readline.strip!
header << f.readline.strip!
file_sig = ""
# the signature is multi-line
while (line = f.readline) != "\n"
file_sig += line
end
header << file_sig.strip!
f.close
f = File.open(@path, "rb")
while f.readline != "\n" do end
content = f.read
f.close
elsif version == INSPEC_PROFILE_VERSION_2
header << version
header << f.readline.strip!
content = f.read
f.close
header.concat(content[0..356].unpack("h*").pack("H*").split("."))
content = content[358..content.length]
else
valid = false
end
unless File.exist?("#{header[1]}.pem.pub")
raise "Key not found"
end
unless valid_header?(header)
valid = false
end
verification_key = KEY_ALG.new File.read "#{header[1]}.pem.pub"
signature = Base64.decode64(header[3])
digest = ARTIFACT_DIGEST.new
unless verification_key.verify digest, signature, content
valid = false
end
valid
end
def valid_header?(header)
VALID_PROFILE_VERSIONS.member?(header[0]) && VALID_PROFILE_DIGESTS.member?(header[2])
end
end
end

View file

@ -1,62 +0,0 @@
require "base64" unless defined?(Base64)
require "openssl" unless defined?(OpenSSL)
# TODO: Refactor this once the binary format work gets merged.
module Inspec
class VerifySignature
def self.valid(path)
# Validates the file using key authentication. Currently it look for the key file in the current working directory.
f = File.open(path, "rb")
version = f.readline.strip!
header = []
header << version
if version == "INSPEC-PROFILE-1"
header << f.readline.strip!
header << f.readline.strip!
file_sig = ""
# the signature is multi-line
while (line = f.readline) != "\n"
file_sig += line
end
header << file_sig.strip!
f.close
f = File.open(path, "rb")
while f.readline != "\n" do end
content = f.read
f.close
elsif version == "INSPEC-PROFILE-2"
header << f.readline.strip!
content = f.read
f.close
header.concat(content[0..356].unpack("h*").pack("H*").split("."))
content = content[358..content.length]
else
raise "Invalid artifact version detected."
end
unless %w{INSPEC-PROFILE-1 INSPEC-PROFILE-2}.member?(header[0]) && ["SHA512"].member?(header[2])
raise "Artifact is invalid"
end
if File.exist? "#{header[1]}.pem.pub"
verification_key = OpenSSL::PKey::RSA.new File.read "#{header[1]}.pem.pub"
else
raise "Can't find #{header[1]}.pem.pub}"
end
signature = Base64.decode64(header[3])
digest = OpenSSL::Digest.new("SHA512")
if verification_key.verify(digest, signature, content)
true
else
raise "Artifact is invalid"
end
end
end
end

View file

@ -6,6 +6,7 @@ require "tempfile" unless defined?(Tempfile)
require "yaml" require "yaml"
require "inspec/dist" require "inspec/dist"
require "inspec/utils/json_profile_summary" require "inspec/utils/json_profile_summary"
require "inspec/iaf_file"
module InspecPlugins module InspecPlugins
module Sign module Sign
@ -59,7 +60,7 @@ module InspecPlugins
# convert the signature to Base64 # convert the signature to Base64
signature_base64 = Base64.encode64(signature) signature_base64 = Base64.encode64(signature)
header = "#{ARTIFACT_DIGEST_NAME}.#{signature_base64}".unpack("H*").pack("h*")+".#{content}" header = "#{ARTIFACT_DIGEST_NAME}.#{signature_base64}".unpack("H*").pack("h*") + ".#{content}"
File.open(artifact_filename, "wb") do |f| File.open(artifact_filename, "wb") do |f|
f.puts INSPEC_PROFILE_VERSION_2 f.puts INSPEC_PROFILE_VERSION_2
f.puts "#{options["keyname"]}" f.puts "#{options["keyname"]}"
@ -73,12 +74,14 @@ module InspecPlugins
end end
def self.profile_verify(options) def self.profile_verify(options)
artifact = new
file_to_verifiy = options["signed_profile"] file_to_verifiy = options["signed_profile"]
puts "Verifying #{file_to_verifiy}" puts "Verifying #{file_to_verifiy}"
artifact.verify(file_to_verifiy) do || iaf_file = Inspec::IafFile.new(file_to_verifiy)
if iaf_file.valid?
puts "Artifact is valid" puts "Artifact is valid"
else
puts "Artifact is invalid"
end end
end end
@ -116,59 +119,6 @@ module InspecPlugins
outfile_name outfile_name
end end
def verify(file_to_verifiy, &content_block)
header = []
f = File.open(file_to_verifiy, "rb")
version = f.readline.strip!
if version == INSPEC_PROFILE_VERSION_1
header << version
header << f.readline.strip!
header << f.readline.strip!
file_sig = ""
# the signature is multi-line
while (line = f.readline) != "\n"
file_sig += line
end
header << file_sig.strip!
f.close
f = File.open(file_to_verifiy, "rb")
while f.readline != "\n" do end
content = f.read
f.close
elsif version == INSPEC_PROFILE_VERSION_2
header << version
header << f.readline.strip!
content = f.read
f.close
header.concat(content[0..356].unpack("h*").pack("H*").split("."))
content = content[358..content.length]
else
raise "Invalid artifact version detected."
end
valid_header?(header)
verification_key = KEY_ALG.new File.read "#{header[1]}.pem.pub"
signature = Base64.decode64(header[3])
digest = ARTIFACT_DIGEST.new
if verification_key.verify digest, signature, content
content_block.yield(content)
else
raise "Artifact is invalid"
end
end
def valid_header?(header)
unless File.exist? "#{header[1]}.pem.pub"
raise "Can't find #{header[1]}.pem.pub}"
end
raise "Invalid artifact version detected" unless VALID_PROFILE_VERSIONS.member?(header[0])
raise "Invalid artifact digest algorithm detected" unless VALID_PROFILE_DIGESTS.member?(header[2])
end
def self.write_inspec_json(root_path, opts) def self.write_inspec_json(root_path, opts)
profile = Inspec::Profile.for_path(root_path, opts) profile = Inspec::Profile.for_path(root_path, opts)
Inspec::Utils::JsonProfileSummary.produce_json( Inspec::Utils::JsonProfileSummary.produce_json(