jwt / ruby-jwt

A ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.
http://ruby-jwt.org
MIT License
3.6k stars 377 forks source link

Is there a way to decode a ES256 encoded JWT with a root certificate but without a public key or a private key? #471

Closed dorianmariecom closed 2 years ago

dorianmariecom commented 2 years ago

I'm receiving JWT from Apple for In-App-Purchases and would like to verify them when I decode them.

I'm using ruby (with ruby on rails) so I do:

JWT.decode(params[:signedPayload], nil, true, { algorithm: 'ES256', root_certificates: root_certificates })

I get the root certificates from https://www.apple.com/certificateauthority

And the payload is base64 encoded JWT, but decoded it looks something like:

{
  "notificationType": "DID_CHANGE_RENEWAL_PREF",
  "subtype": "UPGRADE",
  "notificationUUID": "386fe04a-7caf-40b1-91ca-a4e29f2d135f",
  "data": {
    "bundleId": "com.socializusapp",
    "bundleVersion": "73",
    "environment": "Sandbox",
    "signedTransactionInfo": "eyJhbGciOiJFUzI1NiIsIng1Y...",
    "signedRenewalInfo": "eyJhbGciOiJFUzI1NiIsIng1Yy..."
  },
  "version": "2.0",
  "alg": "HS256"
}

How can I verify the payload is correct?

anakinj commented 2 years ago

Not totally sure about how these tokens are meant to be validated. But I guess the public parts could be located in the header of the token.

There was some features built around that pretty recently (not released yet though)

https://github.com/jwt/ruby-jwt#x509-certificates-in-x5c-header https://github.com/jwt/ruby-jwt/pull/338

amree commented 2 years ago

@dorianmariefr Maybe this well help you? https://developer.apple.com/forums/thread/691464

From the website:

def good_signature?(jws_token)
  raw = File.read "/Users/steve1/downloads/AppleRootCA-G3.cer"
  apple_root_cert = OpenSSL::X509::Certificate.new(raw)

  parts = jws_token.split(".")
  decoded_parts = parts.map { |part| Base64.decode64(part) }
  header = JSON.parse(decoded_parts[0])

  cert_chain =  header["x5c"].map { |part| OpenSSL::X509::Certificate.new(Base64.decode64(part))}
  return false unless cert_chain.last == apple_root_cert

  for n in 0..(cert_chain.count - 2)
    return false unless cert_chain[n].verify(cert_chain[n+1].public_key)
  end

  begin
    decoded_token = JWT.decode(jws_token, cert_chain[0].public_key, true, { algorithms: ['ES256'] })
    !decoded_token.nil?
  rescue JWT::JWKError
    false
  rescue JWT::DecodeError
    false
  end
end
dorianmariecom commented 2 years ago

@amree wow you are the best! thanks

ndvbd commented 1 year ago

I had to change the line return false unless cert_chain.last == apple_root_cert to return false unless cert_chain.last.to_s == apple_root_cert.to_s