lestrrat-go / jwx

Implementation of various JWx (Javascript Object Signing and Encryption/JOSE) technologies
MIT License
1.95k stars 164 forks source link

[jwe] Decryption using an algorithm that uses the "p2c" header with the WithUseNumber option fails #1140

Closed Hannes-Kunnen closed 4 months ago

Hannes-Kunnen commented 4 months ago

Describe the bug

Decrypting an encrypted payload with any algorithm that requires the p2c (PBES2 count) header using the jwe.Decrypt function will fail if the jwx.WithUseNumber(true) decoder option is set.

Go version: go version go1.22.0 linux/amd64

To Reproduce / Expected behavior

package main

import (
    "fmt"
    "github.com/lestrrat-go/jwx/v2"
    "github.com/lestrrat-go/jwx/v2/jwa"
    "github.com/lestrrat-go/jwx/v2/jwe"
    "github.com/lestrrat-go/jwx/v2/jwk"
)

func init() {
    jwx.DecoderSettings(jwx.WithUseNumber(true))
}

func main() {
    key, err := jwk.FromRaw([]byte("secure-key"))
    if err != nil {
        fmt.Println(err)
        return
    }

    var encrypted []byte
    encrypted, err = jwe.Encrypt(
        []byte("test-encryption-payload"),
        jwe.WithKey(jwa.PBES2_HS256_A128KW, key),
    )
    if err != nil {
        fmt.Println(err)
        return
    }

    _, err = jwe.Decrypt(encrypted, jwe.WithKey(jwa.PBES2_HS256_A128KW, key))
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println("success")
}

See Go Playground

Expected behavior: I expected this code to print the success message. Actual behavior: I get the following error: jwe.Decrypt: failed to decrypt any of the recipients (last error = jwe.Decrypt: tried 1 keys, but failed to match any of the keys with recipient (last error = unexpected type for 'p2c': json.Number))

Additional context I think I found the issue and a possible solution. The issue seems the be located in these lines: https://github.com/lestrrat-go/jwx/blob/611d914efbccb0babbce54e48457070e12ab6498/jwe/jwe.go#L748-L751 Because the option jwx.WithUseNumber(true) is used the type here is json.Number instead of float64. This code could be updated to support the json.Number type like this:

var countFlt float64
switch typedCount := count.(type) {
case json.Number:
    countFlt, err = typedCount.Float64()
    if err != nil {
        return nil, fmt.Errorf(`failed to parse 'p2c': %w`, err)
    }
case float64:
    countFlt = typedCount
default:
    return nil, fmt.Errorf("unexpected type for 'p2c': %T", count)
}

I am however not familiar with this code base, so it's possible that I'm missing a better solution.

lestrrat commented 4 months ago

Thank you! Nice catch. Will see how to fix...

lestrrat commented 4 months ago

@Hannes-Kunnen Thanks for the nice report and analysis! Much appreciated. Please check PR #1141 and let me know if it fixes your problem.

BTW I didn't use your suggested fix for mainly one reason: Instead of reacting to what the JSON values are, I wanted to be pedantic about what we expect based on our internal settings. That is, I wanted to only convert to json.Number when we are expecting it, not when the input somehow turned out to be json.Number -- which could potentially be by mistake.

Hannes-Kunnen commented 4 months ago

That makes more sense and will fix the issue!