File signatures via Crystal Openssl library

Hi guys, I would like to know if with the crystal standard library, it is possible to check a file signature via the openssl library, with a public or a private key.

I need some advices as well, because I am not expert in cryptography. Basically I would like to enforce my package manager security by adding as extra of the checksum process, a signature verification process.

So if I take an example, I upload my file called package.tar.xz on my server. I guess the user should have only the public key isn’t it ?

I would like as well to know how I can investigated when my program download a file , by checking signature website and rejecting self-signed certificates ?

This is the download function. Someone from the forum did the implementation because I know nothing about networking unfortunately :face_with_diagonal_mouth: :

def downloadFile(link : String, filename : String, fileExtensionName : String)
            Ism.recordSystemCall(command: "#{{% @def.receiver %}}.#{{% @def.name %}}")

            originalLink = link
            downloaded = false
            error = String.new
            startingTime = Time.monotonic

            until downloaded
                HTTP::Client.get(link) do |response|

                    if response.status.redirection?
                        begin
                            link = response.headers["location"]
                        rescue
                            error = "#{ISM::Default::Software::DownloadSourceRedirectionErrorText1}#{response.status_code}#{ISM::Default::Software::DownloadSourceRedirectionErrorText2}"

                            Ism.notifyOfDownloadError(link, error)
                            Ism.exitProgram
                        end
                        break
                    end

                    filePath = "#{workDirectoryPathNoChroot}/#{filename+fileExtensionName}"
                    colorizedFileFullName = "#{filename}#{fileExtensionName.colorize(Colorize::ColorRGB.new(255,100,100))}"
                    colorizedLink = "#{link.colorize(:magenta)}"

                    lastSpeedUpdate = Time.monotonic
                    average = 0
                    bytesLastPeriod = 0

                    if response.status_code == 200
                        buffer = Bytes.new(65536)
                        totalRead = Int64.new(0)
                        lenght = response.headers["Content-Length"]? ? response.headers["Content-Length"].to_i32 : Int64.new(0)

                        File.open(filePath, "wb") do |data|
                            while (pos = response.body_io.read(buffer)) > 0
                                lapsed = Time.monotonic - lastSpeedUpdate

                                if lapsed.total_seconds >= 1
                                    div = lapsed.total_nanoseconds / 1_000_000_000
                                    average = (bytesLastPeriod / div).to_i32!
                                    bytesLastPeriod = 0
                                    lastSpeedUpdate = Time.monotonic
                                end

                                data.write(buffer[0...pos])
                                bytesLastPeriod += pos
                                totalRead += pos

                                if lenght > 0
                                    text = "\t#{"| ".colorize(:green)} #{colorizedFileFullName} [#{(Int64.new(totalRead*100/lenght).to_s+"%").colorize(:green)}] #{"{".colorize(:green)}#{average.humanize_bytes}/s#{"}".colorize(:green)} (#{colorizedLink})"
                                else
                                    text = "\t#{"| ".colorize(:green)} #{colorizedFileFullName} [#{"0%".colorize(:green)}] #{"{".colorize(:green)}#{average.humanize_bytes}/s#{"}".colorize(:green)} (#{colorizedLink})"
                                end

                                print text+"\r"
                            end
                        end

                        downloaded = true
                    else
                        error = "#{ISM::Default::Software::DownloadSourceCodeErrorText}#{response.status_code}"

                        Ism.notifyOfDownloadError(link, error)
                        Ism.exitProgram
                    end
                end
            end

            puts
        end

I am no more familiar with this issue than you are, but I found the following library (which you may already know about) from spider-gazelle.

I found the following issue.

it appears that there was no implementation 5 years ago. I don’t know if the situation is better now, but looking at the latest Crystal reference, it seems that there is no such class as PKey even today.

Let’s wait for someone who knows more about it.

I see. Anyway, if it’s really like this, I think I will just perform a Process call to use openssl cli.

It’s just if I can do in a more optimised way, I prefer

Thank you so much

If you’re just concerned about verifying the integrity of a file, would comparing SHA256 hashes be simpler and accomplish the same goal?

Agreed with @Blacksmoke16 here. Signatures only need encryption keys when you’re verifying that a specific entity signed it.

If you need that, use OpenSSL::HMAC. If you don’t, use Digest::SHA256.

require "digest/sha256"
require "openssl/hmac"

data = "my data"
key = "my key"

puts OpenSSL::HMAC.hexdigest(:sha256, key, data)
# => 901844ce02ca3634a38f3430eecdb8d21be863ada40c6ff01c38d85ebcbefa70

puts Digest::SHA256.hexdigest(data)
# => b2167b0aa7ef7794740b055ac7a880a52934aa67ef1ca6887ad81dccefd5b9de

I believe question was related to Pretty Good Privacy (PGP) , if that was the case then neither stdlib nor external shard provide PGP compliant implementation. For quick turnaround i would say wrapping pgp executable (available for almost every system)

for more better cases, it would be good if some shard could either implenent or integrate OpenPGP.

My two cents.