@@ -169,7 +169,9 @@ namespace jwt {
169
169
get_key_failed,
170
170
write_key_failed,
171
171
write_cert_failed,
172
- convert_to_pem_failed
172
+ convert_to_pem_failed,
173
+ unknown_curve,
174
+ set_ecdsa_failed
173
175
};
174
176
/* *
175
177
* \brief Error category for ECDSA errors
@@ -194,6 +196,8 @@ namespace jwt {
194
196
case ecdsa_error::write_key_failed: return " error writing key data in PEM format" ;
195
197
case ecdsa_error::write_cert_failed: return " error writing cert data in PEM format" ;
196
198
case ecdsa_error::convert_to_pem_failed: return " failed to convert key to pem" ;
199
+ case ecdsa_error::unknown_curve: return " unknown curve" ;
200
+ case ecdsa_error::set_ecdsa_failed: return " set parameters to ECDSA failed" ;
197
201
default : return " unknown ECDSA error" ;
198
202
}
199
203
}
@@ -1085,6 +1089,242 @@ namespace jwt {
1085
1089
error::throw_if_error (ec);
1086
1090
return res;
1087
1091
}
1092
+
1093
+ #if defined(JWT_OPENSSL_3_0)
1094
+
1095
+ /* *
1096
+ * \brief Convert a curve name to a group name.
1097
+ *
1098
+ * \param curve string containing curve name
1099
+ * \param ec error_code for error_detection
1100
+ * \return group name
1101
+ */
1102
+ inline std::string curve2group (const std::string curve, std::error_code& ec) {
1103
+ if (curve == " P-256" ) {
1104
+ return " prime256v1" ;
1105
+ } else if (curve == " P-384" ) {
1106
+ return " secp384r1" ;
1107
+ } else if (curve == " P-521" ) {
1108
+ return " secp521r1" ;
1109
+ } else {
1110
+ ec = jwt::error::ecdsa_error::unknown_curve;
1111
+ return {};
1112
+ }
1113
+ }
1114
+
1115
+ #else
1116
+
1117
+ /* *
1118
+ * \brief Convert a curve name to an ID.
1119
+ *
1120
+ * \param curve string containing curve name
1121
+ * \param ec error_code for error_detection
1122
+ * \return ID
1123
+ */
1124
+ inline int curve2nid (const std::string curve, std::error_code& ec) {
1125
+ if (curve == " P-256" ) {
1126
+ return NID_X9_62_prime256v1;
1127
+ } else if (curve == " P-384" ) {
1128
+ return NID_secp384r1;
1129
+ } else if (curve == " P-521" ) {
1130
+ return NID_secp521r1;
1131
+ } else {
1132
+ ec = jwt::error::ecdsa_error::unknown_curve;
1133
+ return {};
1134
+ }
1135
+ }
1136
+
1137
+ #endif
1138
+
1139
+ /* *
1140
+ * Create public key from curve name and coordinates. This is defined in
1141
+ * [RFC 7518 Section 6.2](https://www.rfc-editor.org/rfc/rfc7518#section-6.2)
1142
+ * Using the required "crv" (Curve), "x" (X Coordinate) and "y" (Y Coordinate) Parameters.
1143
+ *
1144
+ * \tparam Decode is callable, taking a string_type and returns a string_type.
1145
+ * It should ensure the padding of the input and then base64url decode and
1146
+ * return the results.
1147
+ * \param curve string containing curve name
1148
+ * \param x string containing base64url encoded x coordinate
1149
+ * \param y string containing base64url encoded y coordinate
1150
+ * \param decode The function to decode the RSA parameters
1151
+ * \param ec error_code for error_detection (gets cleared if no error occur
1152
+ * \return public key in PEM format
1153
+ */
1154
+ template <typename Decode>
1155
+ std::string create_public_key_from_ec_components (const std::string& curve, const std::string& x,
1156
+ const std::string& y, Decode decode, std::error_code& ec) {
1157
+ ec.clear ();
1158
+ auto decoded_x = decode (x);
1159
+ auto decoded_y = decode (y);
1160
+
1161
+ #if defined(JWT_OPENSSL_3_0)
1162
+ // OpenSSL deprecated mutable keys and there is a new way for making them
1163
+ // https://mta.openssl.org/pipermail/openssl-users/2021-July/013994.html
1164
+ // https://www.openssl.org/docs/man3.1/man3/OSSL_PARAM_BLD_new.html#Example-2
1165
+ std::unique_ptr<OSSL_PARAM_BLD, decltype (&OSSL_PARAM_BLD_free)> param_bld (OSSL_PARAM_BLD_new (),
1166
+ OSSL_PARAM_BLD_free);
1167
+ if (!param_bld) {
1168
+ ec = error::ecdsa_error::create_context_failed;
1169
+ return {};
1170
+ }
1171
+
1172
+ std::string group = helper::curve2group (curve, ec);
1173
+ if (ec) return {};
1174
+
1175
+ // https://github.com/openssl/openssl/issues/16270#issuecomment-895734092
1176
+ std::string pub = std::string (" \x04 " ).append (decoded_x).append (decoded_y);
1177
+
1178
+ if (OSSL_PARAM_BLD_push_utf8_string (param_bld.get (), " group" , group.data (), group.size ()) != 1 ||
1179
+ OSSL_PARAM_BLD_push_octet_string (param_bld.get (), " pub" , pub.data (), pub.size ()) != 1 ) {
1180
+ ec = error::ecdsa_error::set_ecdsa_failed;
1181
+ return {};
1182
+ }
1183
+
1184
+ std::unique_ptr<OSSL_PARAM, decltype (&OSSL_PARAM_free)> params (OSSL_PARAM_BLD_to_param (param_bld.get ()),
1185
+ OSSL_PARAM_free);
1186
+ if (!params) {
1187
+ ec = error::ecdsa_error::set_ecdsa_failed;
1188
+ return {};
1189
+ }
1190
+
1191
+ std::unique_ptr<EVP_PKEY_CTX, decltype (&EVP_PKEY_CTX_free)> ctx (
1192
+ EVP_PKEY_CTX_new_from_name (nullptr , " EC" , nullptr ), EVP_PKEY_CTX_free);
1193
+ if (!ctx) {
1194
+ ec = error::ecdsa_error::create_context_failed;
1195
+ return {};
1196
+ }
1197
+
1198
+ // https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html#EXAMPLES
1199
+ // Error codes based on https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_fromdata_init.html#RETURN-VALUES
1200
+ EVP_PKEY* pkey = NULL ;
1201
+ if (EVP_PKEY_fromdata_init (ctx.get ()) <= 0 ||
1202
+ EVP_PKEY_fromdata (ctx.get (), &pkey, EVP_PKEY_KEYPAIR, params.get ()) <= 0 ) {
1203
+ // It's unclear if this can fail after allocating but free it anyways
1204
+ // https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html
1205
+ EVP_PKEY_free (pkey);
1206
+
1207
+ ec = error::ecdsa_error::cert_load_failed;
1208
+ return {};
1209
+ }
1210
+
1211
+ // Transfer ownership so we get ref counter and cleanup
1212
+ evp_pkey_handle ecdsa (pkey);
1213
+
1214
+ #else
1215
+ int nid = helper::curve2nid (curve, ec);
1216
+ if (ec) return {};
1217
+
1218
+ auto qx = helper::raw2bn (decoded_x, ec);
1219
+ if (ec) return {};
1220
+ auto qy = helper::raw2bn (decoded_y, ec);
1221
+ if (ec) return {};
1222
+
1223
+ std::unique_ptr<EC_GROUP, decltype (&EC_GROUP_free)> ecgroup (EC_GROUP_new_by_curve_name (nid), EC_GROUP_free);
1224
+ if (!ecgroup) {
1225
+ ec = error::ecdsa_error::set_ecdsa_failed;
1226
+ return {};
1227
+ }
1228
+
1229
+ EC_GROUP_set_asn1_flag (ecgroup.get (), OPENSSL_EC_NAMED_CURVE);
1230
+
1231
+ std::unique_ptr<EC_POINT, decltype (&EC_POINT_free)> ecpoint (EC_POINT_new (ecgroup.get ()), EC_POINT_free);
1232
+ if (!ecpoint ||
1233
+ EC_POINT_set_affine_coordinates_GFp (ecgroup.get (), ecpoint.get (), qx.get (), qy.get (), nullptr ) != 1 ) {
1234
+ ec = error::ecdsa_error::set_ecdsa_failed;
1235
+ return {};
1236
+ }
1237
+
1238
+ std::unique_ptr<EC_KEY, decltype (&EC_KEY_free)> ecdsa (EC_KEY_new (), EC_KEY_free);
1239
+ if (!ecdsa || EC_KEY_set_group (ecdsa.get (), ecgroup.get ()) != 1 ||
1240
+ EC_KEY_set_public_key (ecdsa.get (), ecpoint.get ()) != 1 ) {
1241
+ ec = error::ecdsa_error::set_ecdsa_failed;
1242
+ return {};
1243
+ }
1244
+
1245
+ #endif
1246
+
1247
+ auto pub_key_bio = make_mem_buf_bio ();
1248
+ if (!pub_key_bio) {
1249
+ ec = error::ecdsa_error::create_mem_bio_failed;
1250
+ return {};
1251
+ }
1252
+
1253
+ auto write_pem_to_bio =
1254
+ #if defined(JWT_OPENSSL_3_0)
1255
+ // https://www.openssl.org/docs/man3.1/man3/PEM_write_bio_EC_PUBKEY.html
1256
+ &PEM_write_bio_PUBKEY;
1257
+ #else
1258
+ &PEM_write_bio_EC_PUBKEY;
1259
+ #endif
1260
+ if (write_pem_to_bio (pub_key_bio.get (), ecdsa.get ()) != 1 ) {
1261
+ ec = error::ecdsa_error::load_key_bio_write;
1262
+ return {};
1263
+ }
1264
+
1265
+ return write_bio_to_string<error::ecdsa_error>(pub_key_bio, ec);
1266
+ }
1267
+
1268
+ /* *
1269
+ * Create public key from curve name and coordinates. This is defined in
1270
+ * [RFC 7518 Section 6.2](https://www.rfc-editor.org/rfc/rfc7518#section-6.2)
1271
+ * Using the required "crv" (Curve), "x" (X Coordinate) and "y" (Y Coordinate) Parameters.
1272
+ *
1273
+ * \tparam Decode is callable, taking a string_type and returns a string_type.
1274
+ * It should ensure the padding of the input and then base64url decode and
1275
+ * return the results.
1276
+ * \param curve string containing curve name
1277
+ * \param x string containing base64url encoded x coordinate
1278
+ * \param y string containing base64url encoded y coordinate
1279
+ * \param decode The function to decode the RSA parameters
1280
+ * \return public key in PEM format
1281
+ */
1282
+ template <typename Decode>
1283
+ std::string create_public_key_from_ec_components (const std::string& curve, const std::string& x,
1284
+ const std::string& y, Decode decode) {
1285
+ std::error_code ec;
1286
+ auto res = create_public_key_from_ec_components (curve, x, y, decode, ec);
1287
+ error::throw_if_error (ec);
1288
+ return res;
1289
+ }
1290
+
1291
+ #ifndef JWT_DISABLE_BASE64
1292
+ /* *
1293
+ * Create public key from curve name and coordinates. This is defined in
1294
+ * [RFC 7518 Section 6.2](https://www.rfc-editor.org/rfc/rfc7518#section-6.2)
1295
+ * Using the required "crv" (Curve), "x" (X Coordinate) and "y" (Y Coordinate) Parameters.
1296
+ *
1297
+ * \param curve string containing curve name
1298
+ * \param x string containing base64url encoded x coordinate
1299
+ * \param y string containing base64url encoded y coordinate
1300
+ * \param ec error_code for error_detection (gets cleared if no error occur
1301
+ * \return public key in PEM format
1302
+ */
1303
+ inline std::string create_public_key_from_ec_components (const std::string& curve, const std::string& x,
1304
+ const std::string& y, std::error_code& ec) {
1305
+ auto decode = [](const std::string& token) {
1306
+ return base::decode<alphabet::base64url>(base::pad<alphabet::base64url>(token));
1307
+ };
1308
+ return create_public_key_from_ec_components (curve, x, y, std::move (decode), ec);
1309
+ }
1310
+ /* *
1311
+ * Create public key from curve name and coordinates. This is defined in
1312
+ * [RFC 7518 Section 6.2](https://www.rfc-editor.org/rfc/rfc7518#section-6.2)
1313
+ * Using the required "crv" (Curve), "x" (X Coordinate) and "y" (Y Coordinate) Parameters.
1314
+ *
1315
+ * \param curve string containing curve name
1316
+ * \param x string containing base64url encoded x coordinate
1317
+ * \param y string containing base64url encoded y coordinate
1318
+ * \return public key in PEM format
1319
+ */
1320
+ inline std::string create_public_key_from_ec_components (const std::string& curve, const std::string& x,
1321
+ const std::string& y) {
1322
+ std::error_code ec;
1323
+ auto res = create_public_key_from_ec_components (curve, x, y, ec);
1324
+ error::throw_if_error (ec);
1325
+ return res;
1326
+ }
1327
+ #endif
1088
1328
} // namespace helper
1089
1329
1090
1330
/* *
0 commit comments