I just migrated some JWK code from openssl 1.x to 3.x.

First I have to say, a lot of things got a lot easier and a lot clearer
than before.

I did see some strange behaviors with EVP_PKEY_get_params.  I see the
following statements from `man OSSL_PARAM`:

    [A] When requesting parameters, it's acceptable for data to be NULL.
    This can be used by the requestor to figure out dynamically exactly
    how much buffer space is needed to store the parameter data.  In
    this case, data_size is ignored.

    [B] If a responder finds that some data sizes are too small for the
    requested data, it must set return_size for each such OSSL_PARAM
    item to the minimum required size, and eventually return an error.

    [C] For the integer type parameters (OSSL_PARAM_UNSIGNED_INTEGER and
    OSSL_PARAM_INTEGER), a responder may choose to return an error if
    the data_size isn't a suitable size (even if data_size is bigger
    than needed).  If the responder finds the size suitable, it must
    fill all data_size bytes and ensure correct padding for the native
    endianness, and set return_size to the same value as data_size.

My understanding of [A] is that: when requesting parameters, if data is
NULL then data_size is *ignored*, meaning it does not affect any outputs
or behaviors of the function.

My understanding of [C] is that: it applies to each of the "qx", "qy",
and "priv" members of my EC-based pkey, which are all unsigned integers
according to `man EVP_PKEY-EC`.

But what I observe is that:

- [A] is not being respected, and EVP_PKEY_get_params() is returning
  errors when data_size is too small, even when data==NULL.

- In those failure cases, the return_size is not being set, which
  violates [B].

- When data is set and data_size is larger-than-necessary, qx and qy are
  behaving according to [C] but priv is not.

Reproducing code:

    #include <openssl/ec.h> #include <openssl/evp.h>

    int main(void){
        EVP_PKEY *pkey = EVP_EC_gen("P-256");
        int ret;

        OSSL_PARAM p1[] = {
            {.key="qx", .data_type=UINT, .data=NULL },
            {.key="qy", .data_type=UINT, .data=NULL },
            {.key="priv", .data_type=UINT, .data=NULL },
        ret = EVP_PKEY_get_params(pkey, p1);

        printf("ret = %d\n", ret);
        printf("xlen = %zu\n", p1[0].return_size);
        printf("ylen = %zu\n", p1[1].return_size);
        printf("dlen = %zu\n--\n", p1[2].return_size);

        // output:
        //   ret = 0      # Failure because of B, but only if you ignore A.
        //   xlen = 32
        //   ylen = 32
        //   dlen = 0     # Violates B.

        OSSL_PARAM p2[] = {
            {.key="qx", .data_type=UINT, .data=NULL, .data_size=SIZE_MAX },
            {.key="qy", .data_type=UINT, .data=NULL, .data_size=SIZE_MAX },
            {.key="priv", .data_type=UINT, .data=NULL, .data_size=SIZE_MAX },
        ret = EVP_PKEY_get_params(pkey, p2);

        printf("ret = %d\n", ret);
        printf("xlen = %zu\n", p2[0].return_size);
        printf("ylen = %zu\n", p2[1].return_size);
        printf("dlen = %zu\n--\n", p2[2].return_size);

        // output:     # What I wanted, but requires undocumented inputs.
        //   ret = 1
        //   xlen = 32
        //   ylen = 32
        //   dlen = 32

        char x[256];
        char y[256];
        char d[256];

        OSSL_PARAM p3[] = {
            {.key="qx", .data_type=UINT, .data=x, .data_size=256 },
            {.key="qy", .data_type=UINT, .data=y, .data_size=256 },
            {.key="priv", .data_type=UINT, .data=d, .data_size=256 },
        ret = EVP_PKEY_get_params(pkey, p3);

        printf("ret = %d\n", ret);
        printf("xlen = %zu\n", p3[0].return_size);
        printf("ylen = %zu\n", p3[1].return_size);
        printf("dlen = %zu\n", p3[2].return_size);

        // output:
        //   ret = 1
        //   xlen = 256  # Seems annoying, but this is what C. says.
        //   ylen = 256
        //   dlen = 32   # Seems helpful, but actually violates C.


        return 0;

Reply via email to