Closed begriffs closed 7 years ago
I think the export of KeyUse
is missing in the library. This should be easy to fix.
For the other parts of your code, you can check if genJWK
does what you need. Otherwise, you need something like
-import Data.ByteString.Base64 (encode)
-
hs256jwk :: ByteString -> JWK
hs256jwk key =
fromKeyMaterial km
- & jwkUse .~ Just "sig"
+ & jwkUse .~ Just Sig -- needs hs-jose fix
& jwkAlg .~ Just (JWSAlg HS256)
where
- km = OctKeyMaterial (OctKeyParameters Oct (Base64Octets b64))
- b64 = encode key
+ km = OctKeyMaterial (OctKeyParameters (Base64Octets key)
Thanks for the super fast reply!
Another thing is I'm using jose-0.5.0.2 which is pinned there on Stackage, and not sure when a newer version of the library will be available that includes your fix. So maybe a workaround is to construct an aeson Value for this jwk and then "parse" it?
On Fri, Apr 21, 2017 at 08:19:25AM -0700, Joe Nelson wrote:
Thanks for the super fast reply!
Another thing is I'm using jose-0.5.0.2 which is pinned there on Stackage, and not sure when a newer version of the library will be available that includes your fix. So maybe a workaround is to construct an aeson Value for this jwk and then "parse" it?
I'll release a fix on the 0.5 branch, too.
Thanks for reporting; I'll get onto this in the next day or so.
-- You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub: https://github.com/frasertweedale/hs-jose/issues/42#issuecomment-296220251
@begriffs released v0.5.0.3 and v0.6.0.1 ; the former should be picked up in next lts-8 release.
Thanks for the new version! I pulled in 0.5.0.3 with stack extra-deps. I wonder if I'm still constructing the key wrong because I always get JWSInvalidSignature
when I call validateJWSJWT
with that generated key and the defaultJWTValidationSettings.
On Sat, Apr 22, 2017 at 07:11:11AM -0700, Joe Nelson wrote:
Thanks for the new version! I pulled in 0.5.0.3 with stack extra-deps. I wonder if I'm still constructing the key wrong because I always get
JWSInvalidSignature
when I callvalidateJWSJWT
with that generated key and the defaultJWTValidationSettings.Can you post a minimal reproducer? I'll take a look at it.
Cheers, Fraser
Thanks for taking a look at this, I'm getting pretty confused!
These are the steps I took in GHCI to reproduce the problem.
-- my own helper method
let k = parseJWK "safe"
Results in:
JWK {
_jwkMaterial = OctKeyMaterial (OctKeyParameters {octKty = Oct, octK = Base64Octets "safe"})
, _jwkUse = Just Sig
, _jwkKeyOps = Nothing
, _jwkAlg = Just (JWSAlg HS256)
, _jwkKid = Nothing
, _jwkX5u = Nothing
, _jwkX5c = Nothing
, _jwkX5t = Nothing
, _jwkX5tS256 = Nothing
}
Now I parse an example payload:
let payload = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0"
eJwt <- runExceptT $ decodeCompact payload :: IO (Either JWTError JWT)
The result:
Right (JWT {jwtCrypto = JWTJWS (JWS (Base64Octets "{\"role\":\"postgrest_test_author\",\"id\":\"jdoe\"}") [Signature {_protectedRaw = Just "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", _header = JWSHeader {_jwsHeaderAlg = HeaderParam Protected HS256, _jwsHeaderJku = Nothing, _jwsHeaderJwk = Nothing, _jwsHeaderKid = Nothing, _jwsHeaderX5u = Nothing, _jwsHeaderX5c = Nothing, _jwsHeaderX5t = Nothing, _jwsHeaderX5tS256 = Nothing, _jwsHeaderTyp = Just (HeaderParam Protected "JWT"), _jwsHeaderCty = Nothing, _jwsHeaderCrit = Nothing}, _signature = Base64Octets "\203\139\217\186\237]\r\220\NUL\151O\146\211C\STXEdX2Ry`\ETX\DC2\138\190\132\179\165\173[\GS"}]), jwtClaimsSet = ClaimsSet {_claimIss = Nothing, _claimSub = Nothing, _claimAud = Nothing, _claimExp = Nothing, _claimNbf = Nothing, _claimIat = Nothing, _claimJti = Nothing, _unregisteredClaims = fromList [("role",String "postgrest_test_author"),("id",String "jdoe")]}})
Now to put the two together and try to validate:
eJwt' <- (runExceptT $ do
jwt <- decodeCompact payload
validateJWSJWT defaultJWTValidationSettings k jwt
return jwt
) :: IO (Either JWTError JWT)
Sadly this produces Left (JWSError JWSInvalidSignature)
.
@begriffs This occurs because they key is too short. See https://tools.ietf.org/html/rfc7518#section-3.2
A key of the same size as the hash output (for instance, 256 bits for "HS256") or larger MUST be used with this algorithm. (This requirement is based on Section 5.3.4 (Security Effect of the HMAC Key) of NIST SP 800-117 [NIST.800-107], which states that the effective security strength is the minimum of the security strength of the key and two times the size of the internal hash value.)
What library / program did you use to produce the JWT?
In https://jwt.io/ I pasted this into the payload field
{
"role": "postgrest_test_author",
"id": "jdoe"
}
and changed the signature to safe
(with an unchecked checkbox for "secret is base64 encoded").
By "key" I am interpreting that to mean the secret passphrase for signing the jwt.
People have been providing this key via a config file parameter. If I now change the program to make it die on short keys then it will break backwards compatibility.
Then again maybe it was a bad idea for my program to allow short keys in the first place...
On Wed, May 03, 2017 at 08:30:56PM -0700, Joe Nelson wrote:
In https://jwt.io/ I pasted this into the payload field
{ "role": "postgrest_test_author", "id": "jdoe" }
and changed the signature to
safe
(with an unchecked checkbox for "secret is base64 encoded).Huh. Well, it shouldn't do that, according to the spec (unless it is doing some kind of key derivation to get a long-enough key to pass to the underlying library).
Try "reallyreallyreallyreallyverysafe"
:)
Because the failure will only happen when the message to be verified is longer than the secret, I guess there is no abstract minimum size requirement for the key. Is there a way that my code can detect this jose error to provide a friendly error to the user so that they know to change the key rather than thinking the jwk is invalid for another unknown reason?
@begriffs the failure happens when the digest size for the HMAC algorithm is longer than the key (it has nothing to do with message length). So for HMAC-SHA256, that's 32-bytes minimum key size.
It is an error to use short keys. Whatever library you are using to produce these JWTs, is violating the spec by allowing short keys to be used.
You should break compatibility in the name of security. If you want to continue to allow short keys you can use a key derivation function (e.g. PBKDF2, scrypt) to stretch a low-entropy short key into a pseudorandom key of appropriate length. Of course, this key will not work for validating tokens produced with the short key used verbatim. If your tokens are short lived, this is not really a major problem and you might wish to pursue this option so that users don't need to change their config.
@begriffs regarding detecting the problem, I'm pondering how to expose a better error, but leaning against it since the short keys should never have been used in the first place, and it is a security bug in whatever library produced the token. But there is one way to detect it in your program: use the supplied key to attempt to sign a token. If the key is too short you will get a KeySizeTooSmall
error.
See https://hackage.haskell.org/package/jose-0.5.0.3/docs/Crypto-JOSE-Error.html#t:Error
Thanks a lot for this suggestion and helping me debug! I would have been lost for a long time with this one. I've got a lot to learn about crypto. I also confirmed that my code works for the key reallyreallyreallyreallyverysafe
.
@begriffs you are welcome. Thank you for your valuable contribution!
This is technically another issue but I'll tack it on the end of the current conversation...
The following JWK gives me JWSError JWSInvalidSignature
, do you know what might be wrong with it?
{
"kty": "RSA",
"use": "sig",
"n": "AN2Vq1GNGOiCjdaiOAYcUdgu6B1RYBj2JHd_LhqtY0DUqhLyRXDfdwmJtevxu_BQBSlqsLCW91sfp28Q5-i7T-AIVCwdR9CtIO_4y5JQwB7yPMoTipb6Mr7FBT1rTcZScoeSSV75DSlf-DqNdnuvX_EArkOjaRD5fnEr1yKlGAQr",
"e": "AQAB",
"d": "D-onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS3MCyjjX2eMhu_aF5YhXBwkppwxg-EOmXeh-MzL7Zh284OuPbkglAaGhV9bb6_5CpuGb1esyPbYW-Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2Ylk",
"p": "APLCDZH_u3dDY4Tb7KjfzYIsl2uVItVE5YrBvi1vY-OFjhcDBXx3W_LRF6fFMH4rky7nu5VJMe2swrQYC0Wvzfc",
"q": "AOmr8dtDh6OtWHpWv-JOZEA7YxH9TdC3yPgXFmbcbEt4wzN4tR6dCkrua7Aj2_GnCaOUTtuoBla3wLA3CFygvm0",
"dp": "ZZ2XIpsitLyPpuiMOvBbzPavd4gY6Z8KWrfYzJoI_Q9FuBo6rKwl4BFoToD7WIUS-hpkagwWiz-6zLoX1dbOZw",
"dq": "CmH5fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU-upvDEKZsZc_UhT_SySDOxQ4G_523Y0sz_OZtSWcol_UMgQ",
"qi": "Lesy--GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aPFaFp-DyAe-b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw"
}
Actually that error happens when the key fails to validate a jwt, the key itself may be fine.
@begriffs this is also a "key too small" problem. It's a 1024-bit key, but JWA RSA signature algorithms require 2048-bit key at minimum: https://tools.ietf.org/html/rfc7518#section-3.5.
I think I have worked out how I am going to deal with this. A preliminary "check key" step, as a part of every verification, will check for simple problems like this and return the appropriate error if there is a problem. I'll open a new issue for that.
Specific error messages will be really helpful, thanks for looking into how to do that.
I'm working on code which can either load a full JWK from a file in its normal JSON format, or else construct a HMAC-SHA256 key from just a secret string. I intend this key to be used to verify a signed (not encrypted) JWT. Can't get the following snippet to compile though because I don't know how to construct the
KeyUse
(it's defined with template haskell somehow).Am I even going about this the right way?