File
Blob: src/workerd/api/crypto/jwk.c++
| 1 | #include "jwk.h" |
| 2 | |
| 3 | #include <ncrypto.h> |
| 4 | #include <openssl/curve25519.h> |
| 5 | |
| 6 | namespace workerd::api { |
| 7 | |
| 8 | using JsonWebKey = SubtleCrypto::JsonWebKey; |
| 9 | using ncrypto::BignumPointer; |
| 10 | using ncrypto::ECKeyPointer; |
| 11 | using ncrypto::EVPKeyPointer; |
| 12 | using ncrypto::RSAPointer; |
| 13 | |
| 14 | namespace { |
| 15 | |
| 16 | kj::String getCurveName(int nid) { |
| 17 | switch (nid) { |
| 18 | case NID_X9_62_prime256v1: |
| 19 | return kj::str("P-256"); |
| 20 | case NID_secp256k1: |
| 21 | return kj::str("secp256k1"); |
| 22 | case NID_secp384r1: |
| 23 | return kj::str("P-384"); |
| 24 | case NID_secp521r1: |
| 25 | return kj::str("P-521"); |
| 26 | default: |
| 27 | return kj::String(); |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | int getCurveFromName(kj::StringPtr name) { |
| 32 | int nid = EC_curve_nist2nid(name.begin()); |
| 33 | if (nid == NID_undef) nid = OBJ_sn2nid(name.begin()); |
| 34 | return nid; |
| 35 | } |
| 36 | |
| 37 | int getOKPCurveFromName(kj::StringPtr name) { |
| 38 | int nid; |
| 39 | if (name == "Ed25519") { |
| 40 | nid = EVP_PKEY_ED25519; |
| 41 | } else if (name == "X25519") { |
| 42 | nid = EVP_PKEY_X25519; |
| 43 | } else { |
| 44 | // 448 keys are not supported by boringssl |
| 45 | nid = NID_undef; |
| 46 | } |
| 47 | return nid; |
| 48 | } |
| 49 | |
| 50 | JsonWebKey jwkFromEdKey(const EVPKeyPointer& key, KeyType keyType) { |
| 51 | KJ_REQUIRE(key, "Key must not be null"); |
| 52 | KJ_ASSERT(key.id() == EVP_PKEY_ED25519 || key.id() == EVP_PKEY_X25519, |
| 53 | "Key must be an Ed25519 or X25519 key"); |
| 54 | |
| 55 | auto pkey = key.rawPublicKey(); |
| 56 | JSG_REQUIRE(pkey, InternalDOMOperationError, "Failed to retrieve public key", |
| 57 | internalDescribeOpensslErrors()); |
| 58 | KJ_ASSERT(pkey.size() == 32); |
| 59 | kj::ArrayPtr<const kj::byte> rawPublicKey(static_cast<const kj::byte*>(pkey.get()), pkey.size()); |
| 60 | |
| 61 | JsonWebKey jwk; |
| 62 | jwk.kty = kj::str("OKP"); |
| 63 | jwk.crv = key.id() == EVP_PKEY_X25519 ? kj::str("X25519") : kj::str("Ed25519"); |
| 64 | jwk.x = fastEncodeBase64Url(rawPublicKey); |
| 65 | if (key.id() == EVP_PKEY_ED25519) { |
| 66 | jwk.alg = kj::str("EdDSA"); |
| 67 | } |
| 68 | |
| 69 | if (keyType == KeyType::PRIVATE) { |
| 70 | // Deliberately use ED25519_PUBLIC_KEY_LEN here. |
| 71 | // BoringSSL defines ED25519_PRIVATE_KEY_LEN as 64B since it stores the private key together |
| 72 | // with public key data in some functions, but in the EVP interface only the 32B private key |
| 73 | // itself is returned. |
| 74 | uint8_t rawPrivateKey[ED25519_PUBLIC_KEY_LEN]{}; |
| 75 | size_t privateKeyLen = ED25519_PUBLIC_KEY_LEN; |
| 76 | JSG_REQUIRE(1 == EVP_PKEY_get_raw_private_key(key.get(), rawPrivateKey, &privateKeyLen), |
| 77 | InternalDOMOperationError, "Failed to retrieve private key", |
| 78 | internalDescribeOpensslErrors()); |
| 79 | KJ_ASSERT(privateKeyLen == 32, privateKeyLen); |
| 80 | jwk.d = fastEncodeBase64Url(kj::arrayPtr(rawPrivateKey, privateKeyLen)); |
| 81 | } |
| 82 | |
| 83 | return jwk; |
| 84 | } |
| 85 | |
| 86 | JsonWebKey jwkFromEcKey(const EVPKeyPointer& key, KeyType keyType) { |
| 87 | KJ_REQUIRE(key, "Key must not be null"); |
| 88 | KJ_ASSERT(key.id() == EVP_PKEY_EC, "Key must be an EC key"); |
| 89 | |
| 90 | ncrypto::Ec ec = key; |
| 91 | |
| 92 | JSG_REQUIRE(ec.getX() && ec.getY(), InternalDOMOperationError, |
| 93 | "Error getting affine coordinates for export", internalDescribeOpensslErrors()); |
| 94 | |
| 95 | JSG_REQUIRE(ec.getGroup() != nullptr, DOMOperationError, "No elliptic curve group in this key", |
| 96 | tryDescribeOpensslErrors()); |
| 97 | JSG_REQUIRE(ec.getPublicKey(), DOMOperationError, "No public elliptic curve key data in this key", |
| 98 | tryDescribeOpensslErrors()); |
| 99 | |
| 100 | auto groupDegreeInBytes = integerCeilDivision(ec.getDegree(), 8U); |
| 101 | // getDegree() returns number of bits. We need this because x, y, & d need |
| 102 | // to match the group degree according to JWK. |
| 103 | |
| 104 | SubtleCrypto::JsonWebKey jwk; |
| 105 | jwk.kty = kj::str("EC"); |
| 106 | jwk.crv = getCurveName(ec.getCurve()); |
| 107 | |
| 108 | static constexpr auto handleBn = [](const BIGNUM& bn, size_t size) { |
| 109 | return JSG_REQUIRE_NONNULL(bignumToArrayPadded(bn, size), InternalDOMOperationError, |
| 110 | "Error converting EC affine co-ordinates to padded array", internalDescribeOpensslErrors()); |
| 111 | }; |
| 112 | |
| 113 | // We check that getX and getY return good values above. |
| 114 | auto xa = handleBn(*ec.getX().get(), groupDegreeInBytes); |
| 115 | jwk.x = fastEncodeBase64Url(xa); |
| 116 | |
| 117 | auto ya = handleBn(*ec.getY().get(), groupDegreeInBytes); |
| 118 | jwk.y = fastEncodeBase64Url(ya); |
| 119 | |
| 120 | if (keyType == KeyType::PRIVATE) { |
| 121 | auto privateKey = ec.getPrivateKey(); |
| 122 | JSG_REQUIRE(privateKey, InternalDOMOperationError, |
| 123 | "Error getting private key material for JSON Web Key export", |
| 124 | internalDescribeOpensslErrors()); |
| 125 | auto pk = handleBn(*privateKey, groupDegreeInBytes); |
| 126 | jwk.d = fastEncodeBase64Url(pk); |
| 127 | } |
| 128 | return jwk; |
| 129 | } |
| 130 | |
| 131 | JsonWebKey jwkFromRsaKey(const EVPKeyPointer& key, KeyType keyType) { |
| 132 | SubtleCrypto::JsonWebKey jwk; |
| 133 | jwk.kty = kj::str("RSA"); |
| 134 | |
| 135 | ncrypto::Rsa rsa = key; |
| 136 | auto publicKey = rsa.getPublicKey(); |
| 137 | |
| 138 | if (publicKey.n != nullptr) { |
| 139 | jwk.n = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*publicKey.n))); |
| 140 | } |
| 141 | if (publicKey.e != nullptr) { |
| 142 | jwk.e = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*publicKey.e))); |
| 143 | } |
| 144 | |
| 145 | if (keyType == KeyType::PRIVATE) { |
| 146 | auto privateKey = rsa.getPrivateKey(); |
| 147 | if (publicKey.d != nullptr) { |
| 148 | jwk.d = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*publicKey.d))); |
| 149 | } |
| 150 | if (privateKey.p != nullptr) { |
| 151 | jwk.p = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.p))); |
| 152 | } |
| 153 | if (privateKey.q != nullptr) { |
| 154 | jwk.q = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.q))); |
| 155 | } |
| 156 | if (privateKey.dp != nullptr) { |
| 157 | jwk.dp = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.dp))); |
| 158 | } |
| 159 | if (privateKey.dq != nullptr) { |
| 160 | jwk.dq = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.dq))); |
| 161 | } |
| 162 | if (privateKey.qi != nullptr) { |
| 163 | jwk.qi = fastEncodeBase64Url(KJ_REQUIRE_NONNULL(bignumToArray(*privateKey.qi))); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | return jwk; |
| 168 | } |
| 169 | |
| 170 | EVPKeyPointer rsaKeyFromJwk(const JsonWebKey& jwk, KeyType keyType) { |
| 171 | ncrypto::ClearErrorOnReturn clearErrorOnReturn; |
| 172 | |
| 173 | RSAPointer rsa(RSA_new()); |
| 174 | if (!rsa) return {}; |
| 175 | ncrypto::Rsa rsa_view(rsa.get()); |
| 176 | |
| 177 | auto& n = JSG_REQUIRE_NONNULL(jwk.n, Error, "RSA JWK missing n parameter"); |
| 178 | auto& e = JSG_REQUIRE_NONNULL(jwk.e, Error, "RSA JWK missing e parameter"); |
| 179 | auto N = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(n), Error, "RSA JWK invalid n parameter"); |
| 180 | auto E = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(e), Error, "RSA JWK invalid e parameter"); |
| 181 | |
| 182 | BignumPointer n_bn(N.begin(), N.size()); |
| 183 | BignumPointer e_bn(E.begin(), E.size()); |
| 184 | |
| 185 | JSG_REQUIRE( |
| 186 | rsa_view.setPublicKey(kj::mv(n_bn), kj::mv(e_bn)), Error, "RSA JWK invalid public key"); |
| 187 | |
| 188 | if (keyType == KeyType::PRIVATE) { |
| 189 | auto& d = JSG_REQUIRE_NONNULL(jwk.d, Error, "RSA JWK missing d parameter"); |
| 190 | auto& p = JSG_REQUIRE_NONNULL(jwk.p, Error, "RSA JWK missing p parameter"); |
| 191 | auto& q = JSG_REQUIRE_NONNULL(jwk.q, Error, "RSA JWK missing q parameter"); |
| 192 | auto& dp = JSG_REQUIRE_NONNULL(jwk.dp, Error, "RSA JWK missing dp parameter"); |
| 193 | auto& dq = JSG_REQUIRE_NONNULL(jwk.dq, Error, "RSA JWK missing dq parameter"); |
| 194 | auto& qi = JSG_REQUIRE_NONNULL(jwk.qi, Error, "RSA JWK missing qi parameter"); |
| 195 | |
| 196 | auto D = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(d), Error, "RSA JWK invalid d parameter"); |
| 197 | auto P = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(p), Error, "RSA JWK invalid p parameter"); |
| 198 | auto Q = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(q), Error, "RSA JWK invalid q parameter"); |
| 199 | auto DP = |
| 200 | JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(dp), Error, "RSA JWK invalid dp parameter"); |
| 201 | auto DQ = |
| 202 | JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(dq), Error, "RSA JWK invalid dq parameter"); |
| 203 | auto QI = |
| 204 | JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(qi), Error, "RSA JWK invalid qi parameter"); |
| 205 | |
| 206 | BignumPointer d_bn(D.begin(), D.size()); |
| 207 | BignumPointer p_bn(P.begin(), P.size()); |
| 208 | BignumPointer q_bn(Q.begin(), Q.size()); |
| 209 | BignumPointer dp_bn(DP.begin(), DP.size()); |
| 210 | BignumPointer dq_bn(DQ.begin(), DQ.size()); |
| 211 | BignumPointer qi_bn(QI.begin(), QI.size()); |
| 212 | |
| 213 | JSG_REQUIRE(rsa_view.setPrivateKey(kj::mv(d_bn), kj::mv(q_bn), kj::mv(p_bn), kj::mv(dp_bn), |
| 214 | kj::mv(dq_bn), kj::mv(qi_bn)), |
| 215 | Error, "RSA JWK invalid private key"); |
| 216 | } |
| 217 | |
| 218 | return EVPKeyPointer::NewRSA(std::move(rsa)); |
| 219 | } |
| 220 | |
| 221 | EVPKeyPointer ecKeyFromJwk(const JsonWebKey& jwk, KeyType keyType) { |
| 222 | int nid = getCurveFromName(JSG_REQUIRE_NONNULL(jwk.crv, Error, "EC JWK missing crv parameter")); |
| 223 | JSG_REQUIRE(nid != NID_undef, Error, "EC JWK unsupported crv parameter"); |
| 224 | |
| 225 | auto ec = ECKeyPointer::NewByCurveName(nid); |
| 226 | JSG_REQUIRE(ec, Error, "EC JWK unsupported curve"); |
| 227 | |
| 228 | auto& x = JSG_REQUIRE_NONNULL(jwk.x, Error, "EC JWK missing x parameter"); |
| 229 | auto& y = JSG_REQUIRE_NONNULL(jwk.y, Error, "EC JWK missing y parameter"); |
| 230 | |
| 231 | auto X = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(x), Error, "EC JWK invalid x parameter"); |
| 232 | auto Y = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(y), Error, "EC JWK invalid y parameter"); |
| 233 | |
| 234 | BignumPointer x_bn(X.begin(), X.size()); |
| 235 | BignumPointer y_bn(Y.begin(), Y.size()); |
| 236 | |
| 237 | JSG_REQUIRE(ec.setPublicKeyRaw(kj::mv(x_bn), kj::mv(y_bn)), Error, "EC JWK invalid public key"); |
| 238 | |
| 239 | if (keyType == KeyType::PRIVATE) { |
| 240 | auto& d = JSG_REQUIRE_NONNULL(jwk.d, Error, "EC JWK missing d parameter"); |
| 241 | auto D = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(d), Error, "EC JWK invalid d parameter"); |
| 242 | |
| 243 | BignumPointer d_bn(D.begin(), D.size()); |
| 244 | JSG_REQUIRE(ec.setPrivateKey(kj::mv(d_bn)), Error, "EW JWK invalid private key"); |
| 245 | } |
| 246 | |
| 247 | auto pkey = EVPKeyPointer::New(); |
| 248 | if (!pkey || !pkey.set(ec)) return {}; |
| 249 | return pkey; |
| 250 | } |
| 251 | |
| 252 | EVPKeyPointer edKeyFromJwk(const JsonWebKey& jwk, KeyType keyType) { |
| 253 | int nid = |
| 254 | getOKPCurveFromName(JSG_REQUIRE_NONNULL(jwk.crv, Error, "OKP JWK missing crv parameter")); |
| 255 | JSG_REQUIRE(nid != NID_undef, Error, "OKP JWK unsupported crv parameter"); |
| 256 | |
| 257 | if (keyType == KeyType::PRIVATE) { |
| 258 | auto& d = JSG_REQUIRE_NONNULL(jwk.d, Error, "OKP JWK missing d parameter"); |
| 259 | auto D = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(d), Error, "OKP JWK invalid d parameter"); |
| 260 | return EVPKeyPointer::NewRawPrivate(nid, ToNcryptoBuffer(D.asPtr().asConst())); |
| 261 | } |
| 262 | |
| 263 | auto& x = JSG_REQUIRE_NONNULL(jwk.x, Error, "OKP JWK missing x parameter"); |
| 264 | auto X = JSG_REQUIRE_NONNULL(simdutfBase64UrlDecode(x), Error, "OKP JWK invalid x parameter"); |
| 265 | return EVPKeyPointer::NewRawPublic(nid, ToNcryptoBuffer(X.asPtr().asConst())); |
| 266 | } |
| 267 | } // namespace |
| 268 | |
| 269 | JsonWebKey toJwk(const EVPKeyPointer& key, KeyType keyType) { |
| 270 | if (key) { |
| 271 | switch (key.id()) { |
| 272 | case EVP_PKEY_ED25519: |
| 273 | return jwkFromEdKey(key, keyType); |
| 274 | case EVP_PKEY_X25519: |
| 275 | return jwkFromEdKey(key, keyType); |
| 276 | case EVP_PKEY_EC: |
| 277 | return jwkFromEcKey(key, keyType); |
| 278 | case EVP_PKEY_RSA: |
| 279 | return jwkFromRsaKey(key, keyType); |
| 280 | case EVP_PKEY_RSA2: |
| 281 | return jwkFromRsaKey(key, keyType); |
| 282 | case EVP_PKEY_RSA_PSS: |
| 283 | return jwkFromRsaKey(key, keyType); |
| 284 | case EVP_PKEY_DSA: { |
| 285 | // DSA keys are not supported for JWK export. |
| 286 | break; |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | return JsonWebKey{ |
| 292 | .kty = kj::str("INVALID"), |
| 293 | }; |
| 294 | } |
| 295 | |
| 296 | EVPKeyPointer fromJwk(const JsonWebKey& jwk, KeyType keyType) { |
| 297 | if (jwk.kty == "OKP") return edKeyFromJwk(jwk, keyType); |
| 298 | if (jwk.kty == "EC") return ecKeyFromJwk(jwk, keyType); |
| 299 | if (jwk.kty == "RSA") return rsaKeyFromJwk(jwk, keyType); |
| 300 | return {}; |
| 301 | } |
| 302 | |
| 303 | } // namespace workerd::api |