JWK OEAP importKey fails if p < q

Number:rdar://34916499 Date Originated:2017-10-10
Status: Resolved:
Product:Safari Product Version:11.0 (13604.
Classification: Reproducible:Yes
When importing a JWK OEAP private key using crypto.subtle.importKey the import will fail if p < q

Steps to Reproduce:

In JavaScript Console

let key = {
    "alg": "RSA-OAEP",
    "d": "AEhCO0ZRRbsKTbTOzgW8_q2pET2vLQOyaLBwhWw6fK6Zz4GkKhNzcd15HpjQ5_HsqancBl6NT0h0RZ-NATzo4nTW-UL7HS3KfX4MBeun8s09LUQGFiteBbodONlnImL298JOeQnJiCtTyOPp7BokFvH5Tx0p5iSaMHQ9t6mo3CQ5EqM5tCOOAAIhIKfaTYACIA3z1ofKr45tHaqtdFNtGmEzlXjFGc_7AwK_TfaXJfcqyLaLqp7llc0c7Pktj-eSV4FI_Oysb0LXDK-L5ehJPafz6p9pLXCaQTOULMfwbvv3xKlY6Q2a9-AoBDWPPTgNzMS7DojCDguLA98-4ehjQQ",
    "dp": "YRLeNGJByvFv4Jpv2Ef-mmknlNmZdg2MCk7xsQh3-nufKxEssdN_jTF1lTR5mnYKJyaGfsuADRy-xzIuili91xx2631VXIpzO4EApt-_droErdjzwJiOVqbMQmEU9ithLWWbpa0IPm5HZCa7PD-2ctt79aMFualsmwQ2Auc2ET0",
    "dq": "KbWh0933yZ1ndCe5KW_QF0RlF57QsLL3Lc6bfOB2uY9AitUv5s4Q0BrHwdlS-0v1S0s3T_XJKjN5XRd-TEGOX0xZMRGuA99QyGm_arw4Rkrm27u3zBBUaN5Hm1rHMYugrbb4Ch1BQioCdlR1yfJwVR5w8cpp6J68jWl1ST0Oj7k",
    "e": "AQAB",
    "ext": true,
    "key_ops": [
    "kty": "RSA",
    "n": "zYn-rDpx6iI7AylGoNVh5WtdlgyQNa63JIv562B0Xh3Ye5ip3MW_ehPeEOIkTFFfigTNc2stCrTHVNRGgpny0bRsYJT6w_8PBzeCwIJ4xMy9Kzy8SGbyC8QfL0nopUJuJcIU5EuTyY4XX9p9M2FBqtjmplKGj6XsP81JrpVl1ZN5CNFW1mmpEIjdc7egzlH0CaJGum-Opr8rSw_-SQQNEDWsftAPwZktrJlk_mL068o6wuPuSgrzhhesbxBC532SsWtVkIgbJy-UlcsTI6KCSxZHQiAJ0q37bf1535IF8okrVd6iJqUjZ2XltbZpxDkAtoTKBEpH3tBiMUhiUzkOTQ",
    "p": "1blRZM5aXP7CkdOTWidcnkZQiBk8Rnpl12p2AsDgCqBWeLtGOoi8lux5zPwK07cB5Mcf0r7_uLHQt0tPHfuDKMacktLUsoCsVDbnkOPJL9aK0nWLCMzy9sgmZq4LMOChNRzPkQegq-vJxXON_Ol_3O2YGlePnpIIT79V_CzDbuU",
    "q": "9jI1BOgJNo6P3U21dgEYiMjJhF38n1xiyxRjl_dp5otzN4mkpg32sGAZAhO1rdjc9Zz2OlvuMyef6YQrtcDuVcm8Sq2YyYwhqB6dlN6dKswr9gGyBEqfcUtKoGRm7uNA1_z_N83n9h1ji06958uq3S3QvSS4_FlNlvQMsoOOw0k",
    "qi": "x9wUQzvMTSkzPKgvMnGxhltqX6Q3ruv2O5M98Vg-f_-c4O3p-bFSO94768OV7859QD_LY7J0eqjaUtvXXMcHpOH0J8xa7vgVU37vEjqEwI_zloDkyRihumwFKH6SCRTZTnIlJNgNFR_m1fFhatHdQj6VFf0HYREnTqlWd25yYRE",
    "kid": "h7bbt47gymfxtb3amihkvgu6zi"
let alg = {
	name: "RSA-OAEP",
	modulusLength: 2048,
	publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 24-bit representation of 65537
	hash: {name: "SHA-1"}
crypto.subtle.importKey("jwk", key, alg, true, ["decrypt"]);

Expected Results:

A successful cryptoky

Actual Results:

Error stating

"Unhandled Promise Rejection: DataError (DOM Exception 30): Data provided to an operation does not meet requirements"


Our (1Password's) work-around

As of April 24, 2018 our work-arounds are in place. Release notes for our web-client v481 https://app-updates.agilebits.com/product_history/B5#v481 include:

"Some users of Microsoft Edge could not sign in using Safari 11 or iOS browsers. The web app now detects when this error will occur and avoids it entirely. {3220, 4156, 4157}"

The most substantive of the work-arounds are that on import of JWK OEAP keys we check if p < q. If so, we swap p and q during the import (we do not change the stored key), swap dp and dq, and recalculated qInv. This seems simple enough, but in the process of implementing this, we encountered a difference in padding expectations between JSBN (a JavaScript BigNumber library) and SJCL(Stanford Javascript Crypto Library) that we needed identify and address.

We also now prevent the generation of keys with p < q, a behavior we have only encountered in Edge. But are running this check at key creation time everywhere. If a key is generated with p < q, we tell the browser to try again.

We still consider this a Safari bug, but as we have not heard anything from Apple about the status of this for some time, we've implemented these work-arounds.

Looking at the corecrypto source in cc_rsa_generate_key.c

/* qInv = q^(-1) mod p. This requires q to be at least as long as p with
    proper zero padding. Our caller takes care if this. Obviously qInv can
    be as big as p too. */
    if (cczp_mod_inv(zp, qinv, cczp_prime(zq)))

So if this is what is eventually called, it does have the expectation that p > q, and says it is the caller's responsibility.

May conform to RFC

There is a case that can be made that the behavior that we see as buggy may be correct behavior. RFC 3447 doesn't explicitly say that p must be greater than q. However having p < q may cause things to go against the RFC, which states.


the CRT coefficient qInv is a positive integer less than p satisfying

   q * qInv == 1 (mod p).


If p > q, the the most natural way to compute qInv will lead to one that is positive and less than p. But if p < q, the most natural way of computing qInv will lead to a negative value. Furthermore most browsers only or mostly generate OAEP private keys in which p > q. (Edge appears to be the exception.)

However, we've found that other browsers accept private keys with p < q. And arguing based on "the most natural way" to compute qInv seems like a stretch. There is also a very "natural" correction that gives you a positive qInv.

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!