Skip to content

Instantly share code, notes, and snippets.

@joshuaflanagan
Created July 11, 2025 16:21
Show Gist options
  • Save joshuaflanagan/640ef8ef86769c6f2a1c4d0645079e03 to your computer and use it in GitHub Desktop.
Save joshuaflanagan/640ef8ef86769c6f2a1c4d0645079e03 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require 'base64'
require 'json'
require 'openssl'
require 'optparse'
def decode_base64url(str)
# Add padding if necessary
str += '=' * (4 - str.length % 4) if str.length % 4 != 0
Base64.urlsafe_decode64(str)
end
def convert_ecdsa_signature_to_der(signature, key_size)
# ECDSA signatures in JWT are in IEEE P1363 format (r || s)
# OpenSSL expects DER format, so we need to convert
# Determine coordinate size based on key size
coord_size = (key_size + 7) / 8 # Round up to nearest byte
# Split signature into r and s components
return nil if signature.length != coord_size * 2
r = signature[0, coord_size]
s = signature[coord_size, coord_size]
# Convert to integers
r_int = r.unpack1('H*').to_i(16)
s_int = s.unpack1('H*').to_i(16)
# Create DER-encoded signature
OpenSSL::ASN1::Sequence.new([
OpenSSL::ASN1::Integer.new(r_int),
OpenSSL::ASN1::Integer.new(s_int)
]).to_der
end
def verify_signature(header, payload, signature, public_key_path)
begin
# Read and parse header to get algorithm
header_json = JSON.parse(decode_base64url(header))
algorithm = header_json['alg']
# Read public key and create appropriate key object based on algorithm
public_key_pem = File.read(public_key_path)
# Determine expected key type based on algorithm
public_key = case algorithm
when 'RS256', 'RS384', 'RS512'
# RSA algorithms require RSA keys
OpenSSL::PKey::RSA.new(public_key_pem)
when 'ES256', 'ES384', 'ES512'
# ECDSA algorithms require EC keys
OpenSSL::PKey::EC.new(public_key_pem)
else
# Unsupported algorithm
$stderr.puts "Unsupported algorithm: #{algorithm}"
return false
end
# Prepare data to verify (header.payload)
data = "#{header}.#{payload}"
# Decode signature
decoded_signature = decode_base64url(signature)
# Verify based on algorithm
case algorithm
when 'RS256'
digest = OpenSSL::Digest::SHA256.new
return public_key.verify(digest, decoded_signature, data)
when 'RS384'
digest = OpenSSL::Digest::SHA384.new
return public_key.verify(digest, decoded_signature, data)
when 'RS512'
digest = OpenSSL::Digest::SHA512.new
return public_key.verify(digest, decoded_signature, data)
when 'ES256'
digest = OpenSSL::Digest::SHA256.new
der_signature = convert_ecdsa_signature_to_der(decoded_signature, public_key.group.degree)
return false if der_signature.nil?
return public_key.verify(digest, der_signature, data)
when 'ES384'
digest = OpenSSL::Digest::SHA384.new
der_signature = convert_ecdsa_signature_to_der(decoded_signature, public_key.group.degree)
return false if der_signature.nil?
return public_key.verify(digest, der_signature, data)
when 'ES512'
digest = OpenSSL::Digest::SHA512.new
der_signature = convert_ecdsa_signature_to_der(decoded_signature, public_key.group.degree)
return false if der_signature.nil?
return public_key.verify(digest, der_signature, data)
else
$stderr.puts "Unsupported algorithm: #{algorithm}"
return false
end
rescue
$stderr.puts "Error verifying signature: #{$!}"
return false
end
end
# Parse command line options
options = {}
OptionParser.new do |opts|
opts.on("-v", "--verify PUBLIC_KEY_PATH", "Verify JWT signature using public key file") do |path|
options[:verify] = path
end
end.parse!
# Read JWT from stdin and split once
input = ARGF.read.strip
header, payload, signature = input.split(".", 3)
# Original output - decode header and payload
puts Base64.decode64(header)
puts Base64.decode64(payload)
# Add signature verification if -v flag is provided
if options[:verify]
if File.exist?(options[:verify])
if verify_signature(header, payload, signature, options[:verify])
puts "\e[32m✓\e[0m Valid signature"
else
puts "\e[31m✗\e[0m Invalid signature"
end
else
$stderr.puts "Unable to validate signature: public key file not found at #{options[:verify]}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment