potatosalad / erlang-jose

JSON Object Signing and Encryption (JOSE) for Erlang and Elixir
http://hexdocs.pm/jose
MIT License
309 stars 102 forks source link

Invalid signature for PS256 #121

Open dgamidov opened 3 years ago

dgamidov commented 3 years ago

Hello! I am trying to sign JWT with RSA-2048 key using PS256, but it seems that JOSE.JWT.sign generates invalid signature.

$ elixir -v
Erlang/OTP 24 [erts-12.0.3] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit] [dtrace]

Elixir 1.12.2 (compiled with Erlang/OTP 24)

Jose version 1.11.2

private_pem = "-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCnihyP7x0Pkq0C
LRGy965jlXB84ia9fx3HXHNnQmugCYKF1aj+qpC59i5GWybSlUo6mQXY66derhUl
PiBPV/r96XcOSxcDO6tYQjGi0izH/i/pzgym6mPELWi4/aLa7Raw/tuKfcJLjJaN
dQmRE0TUsTGonuKSSJPLiRIPjYRCEzUG7yEe74ceeoRcIhJ1WofyqcqfjtUuPCs9
4CxvLvMDN5mkWzg4DTZLR32hdhMGrapTjp83XHS8Ng8ZOYj/kteu0xJe7dzMGGow
iUCbTaUVbCoLW2oKt9wph+t5u9AYI/S5HyDi1j437lY65KaERpaodtKPzVR4DFKW
QBnzl3kNAgMBAAECggEBAIn6J12uihtqK4uQPHRMoNUonHRs94S2bHrkO7J9tA8f
5dt8w+m/5OMRyLhul0F0a6ZoobUtFjDWY1nBmXBev7lfDYq68p/aA1nzYdd7CiBG
yYTfCfJpNHK7BvTbBcUQ3tAzg/DBc0Mu7EphadiNqvETYRdCisv4c6d7ImGmpZaX
zczNCM/EBcXy/xTYFPJyAndn8QjpevM6rdLUMX9adQBcESEQ1e3UPQq4JqgXRmFm
Trth63VBl/W/XGJ2KrQ+WCv0w80Nc53E5QOk9rWQ6ejXbaxlWWVtB+8iph5bh9SU
idUgDznsvgmSpuI3vtW3q+8cUo/BgYcb0j83VRmcQI0CgYEA5pLxm0H9qhehYyb6
1zPJjvMvmZSOWOEVvt7+ssF1CNzRlqggORaAu6ouwS5kIIkhjKjJEajhI68VL6ga
G6qUpuRSlpw/ygzNnpwMEqU0yIHubahT/PJBtrkcIdIYEGxS3OVcSaAZWCL1RCfx
OEqRtxSHp6t+5E8ccRsbcIwZskMCgYEAugO3hqCt+QBzZ29WE0/WqeK3x18y/alh
tQ06XoHqEKaUrURiAjwREsY4VTz8FrcRizOEa9nm1Rt+aAe2xjYTvJB6zWZYOD/+
fTPJWPW5ZQ48ju82Km/Z8IsNBOVy5M3xFCN+Sq81USugpIEGrFHulGfCq1IZgiXK
awHkbJywOm8CgYAhOBZ7Cdzj/peP03PcQWfOopa/B7oib/tUDoifdGPKLRY9IGfU
EbvmTbSCvI5MabVdsIuXcb9OBtGEljF8vuy/xrFnZYeDMqkFKvJKVWbvZFjXj6La
FIUHMFAvRzeUVibyEFFlgh1kp1SyxxZnx8aKm/iXXYvglM1CNmeTpXumRQKBgEqN
Fs70MwWq8pWv/5V52BT6cqGLPAqCClDf3lbmRoMp4EiZMZansEgP/Z/b80S/Vl6t
5RK8oFEk1cKn5M8H+V0HotAThqP0sfBIAxiOUyF3VToMEW9sqLJJaIxf0W52Q3rI
xuLz2bCQha+orp39sFr2QwUawHJy5u+AMKZ/HfotAoGBAMHcIzu84oApIooj0JrV
xfvCnS2gaAhUEYPAN2UlSTuaxeQpYkMcwiudV7KmOMzxwgZ9fSKlUaW2GwIfA3UZ
499uSmM6a0ltX3z0M7ilKspAdsux7uIzabg3aiALD9PJFSzRyqw0HhsUeVL6M2Rx
hXpb1Z9wLsBfQN3yRExcV5Vz
-----END PRIVATE KEY-----"

rsa_private_jwk = JOSE.JWK.from_pem(private_pem)
jwt = %{"test" => "ok"}

For PS256 signature is invalid (checked on on https://jwt.io):

jws = %{"typ" => "JWT", "alg" => "PS256"}
{%{alg: :jose_jws_alg_rsa_pss}, signed_jwt} = JOSE.JWT.sign(rsa_private_jwk, jws, jwt)
iex(10)> {%{alg: :jose_jws_alg_rsa_pss}, signed_jwt} = JOSE.JWT.sign(rsa_private_jwk, jws, jwt)
{%{alg: :jose_jws_alg_rsa_pss},
 %{
   "payload" => "eyJ0ZXN0Ijoib2sifQ",
   "protected" => "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9",
   "signature" => "YrR7c3-xZ_ji0bM0E9CrGJQlpbkoo4gBEF495Y75if0Wtjlnt8o6Fbiv6GTkiU5z9ULvsVqYyXJzO5tQaMgpskVUtDcLU3q7IgNxiVJ-C89paiIF7b54DD8pXcRlmKkIQz1hI3WLlsZ3-0rp3fWPKkZ2FhrS7OxJ81x4hlIwz_K0Wo-Gb1xvIekALfxKNY7TfnCifXy7impE_y4X4lQ_ibWx7FxmlfpiKBYdd6vvjZJXubVEFaMCeYX9qgxJwW8d2zlHSHvPvZLWpMMiy6nea7qAgvhmByJOeccnhTrAo_z1vsno7PTMSJusfSiUeHWNcuBXuSskXGY3T_7ei55qpg"
 }}

For RS256 signature (same private key) is valid:

jws = %{"typ" => "JWT", "alg" => "RS256"}
{%{alg: :jose_jws_alg_rsa_pkcs1_v1_5}, signed_jwt} = JOSE.JWT.sign(rsa_private_jwk, jws, jwt)
iex(15)> {%{alg: :jose_jws_alg_rsa_pkcs1_v1_5}, signed_jwt} = JOSE.JWT.sign(rsa_private_jwk, jws, jwt)
{%{alg: :jose_jws_alg_rsa_pkcs1_v1_5},
 %{
   "payload" => "eyJ0ZXN0Ijoib2sifQ",
   "protected" => "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
   "signature" => "QDeUT_90_DO8EMqWnZOxpfsDIbKCYrfQN-y0obzjNedhXCIfLpf70_4VppoQwUxFXeU3KqsTs3B5oT9MFggJPbxfhUdTYxfWsMzh2Rmx5E96Q4raxES0mKjiU70XYfZHOfoLbqH13QBx1x1JtjlOaCJZKYQc6RUIEot5tObZyl8wVdwHcANm9FSK47g8Jgo9sHrlEefOhaJQUI2BNjXUCGxZl8ftnFHuBRVCNkomGZtJ-HuuWXNDfxWFMV1BFaKYqD1u2qD1DULDLqvsq6o-b64CguEtYwS0Xso6OTxCvSMiN-0LxYuENcu3jA8Duc1IbVIAPf3T53p1kIGPKKnHFw"
 }}
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp4ocj+8dD5KtAi0Rsveu
Y5VwfOImvX8dx1xzZ0JroAmChdWo/qqQufYuRlsm0pVKOpkF2OunXq4VJT4gT1f6
/el3DksXAzurWEIxotIsx/4v6c4MpupjxC1ouP2i2u0WsP7bin3CS4yWjXUJkRNE
1LExqJ7ikkiTy4kSD42EQhM1Bu8hHu+HHnqEXCISdVqH8qnKn47VLjwrPeAsby7z
AzeZpFs4OA02S0d9oXYTBq2qU46fN1x0vDYPGTmI/5LXrtMSXu3czBhqMIlAm02l
FWwqC1tqCrfcKYfrebvQGCP0uR8g4tY+N+5WOuSmhEaWqHbSj81UeAxSlkAZ85d5
DQIDAQAB
-----END PUBLIC KEY-----
danferreira commented 3 years ago

Same problem here.

~I dont know the minimum OTP version supported by this project (base on CI tests, its probably 22?) But for OTP 21+ we could rely on crypto native implementation for RSASSA-PS since 4.1~

Edit: we are already using it via public_key module

victorolinasc commented 3 years ago

The reason here is because JOSE is not passing the saltlen options of the rsa pss padding. You can directly change that with an ultra unreliable code like this:

:ets.insert(:jose_jwa, {{:rsa_sign, :rsa_pkcs1_pss_padding}, {:public_key, [rsa_padding: :rsa_pkcs1_pss_padding, rsa_pss_saltlen: -1]}})

Then your case will pass.

This is because the "detection" algorithm used by JOSE needs to be updated. When booting up it tries to detect the available options by simply trying a few options and signing/verifying them here. That is sub-optimal because depending on the combination of OpenSSL version plus OTP you might have several more options than the ones that are tried. On my machine, for instance, I have this output for :crypto.supports():

[
  hashs: [:sha, :sha224, :sha256, :sha384, :sha512, :sha3_224, :sha3_256,
   :sha3_384, :sha3_512, :blake2b, :blake2s, :md4, :md5, :ripemd160],
  ciphers: [:aes_cbc, :aes_ccm, :aes_cfb128, :aes_cfb8, :aes_ctr, :aes_ecb,
   :aes_gcm, :chacha20, :blowfish_ecb, :blowfish_ofb64, :blowfish_cfb64,
   :blowfish_cbc, :des_ede3_cfb, :des_ede3_cbc, :des_ecb, :des_cfb, :des_cbc,
   :rc4, :rc2_cbc, :aes_128_cbc, :aes_192_cbc, :aes_256_cbc, :aes_128_cfb128,
   :aes_192_cfb128, :aes_256_cfb128, :aes_128_cfb8, :aes_192_cfb8,
   :aes_256_cfb8, :aes_128_ecb, :aes_192_ecb, :aes_256_ecb, :aes_256_ccm, 
   :aes_192_gcm, :aes_192_ccm, :aes_128_ccm, :aes_256_ctr, :aes_192_ctr,
   :aes_128_ctr, :chacha20_poly1305, :aes_256_gcm, :aes_128_gcm],
  public_keys: [:rsa, :dss, :dh, :ec_gf2m, :ecdsa, :ecdh, :eddsa, :eddh, :srp],
  macs: [:cmac, :hmac, :poly1305],
  curves: [:secp160k1, :secp160r1, :secp160r2, :secp192k1, :secp224k1,
   :secp224r1, :secp256k1, :secp384r1, :secp521r1, :secp192r1, :prime192v1,
   :prime192v2, :prime192v3, :prime239v1, :prime239v2, :prime239v3, :secp256r1,
   :prime256v1, :wtls7, :wtls9, :wtls12, :brainpoolP160r1, :brainpoolP160t1,
   :brainpoolP192r1, :brainpoolP192t1, :brainpoolP224r1, :brainpoolP224t1,
   :brainpoolP256r1, :brainpoolP256t1, :brainpoolP320r1, :brainpoolP320t1,
   :brainpoolP384r1, :brainpoolP384t1, :brainpoolP512r1, :brainpoolP512t1,
   :sect163k1, :sect163r1, :sect163r2, :sect193r1, :sect193r2, :sect233k1,
   :sect233r1, :sect239k1, :sect283k1, :sect283r1, ...],
  rsa_opts: [:rsa_pkcs1_pss_padding, :rsa_pss_saltlen, :rsa_mgf1_md,
   :rsa_pkcs1_oaep_padding, :rsa_oaep_label, :rsa_oaep_md, :signature_md,
   :rsa_pkcs1_padding, :rsa_x931_padding, :rsa_sslv23_padding, :rsa_no_padding]
]

As you can see, we should add a lot more logic on this detection algorithm I think.

utaladriz commented 2 years ago

HI the issue is still alive :-) but the unreliable solution works fine

Erlang/OTP 24 [erts-12.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

IEx 1.13.3 (compiled with Erlang/OTP 24)

{:jose, "~> 1.11"}

jkbbwr commented 1 year ago

Can confirm, same issue encountered, temp fix did resolve it.

{:jose, "~> 1.11.2"},
Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:32:32] [ds:32:32:10] [async-threads:1] [jit]