Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
| 2 | /* |
| 3 | * SM2 asymmetric public-key algorithm |
| 4 | * as specified by OSCCA GM/T 0003.1-2012 -- 0003.5-2012 SM2 and |
| 5 | * described at https://tools.ietf.org/html/draft-shen-sm2-ecdsa-02 |
| 6 | * |
| 7 | * Copyright (c) 2020, Alibaba Group. |
| 8 | * Authors: Tianjia Zhang <tianjia.zhang@linux.alibaba.com> |
| 9 | */ |
| 10 | |
| 11 | #include <linux/module.h> |
| 12 | #include <linux/mpi.h> |
| 13 | #include <crypto/internal/akcipher.h> |
| 14 | #include <crypto/akcipher.h> |
| 15 | #include <crypto/hash.h> |
| 16 | #include <crypto/sm3_base.h> |
| 17 | #include <crypto/rng.h> |
| 18 | #include <crypto/sm2.h> |
| 19 | #include "sm2signature.asn1.h" |
| 20 | |
| 21 | #define MPI_NBYTES(m) ((mpi_get_nbits(m) + 7) / 8) |
| 22 | |
| 23 | struct ecc_domain_parms { |
| 24 | const char *desc; /* Description of the curve. */ |
| 25 | unsigned int nbits; /* Number of bits. */ |
| 26 | unsigned int fips:1; /* True if this is a FIPS140-2 approved curve */ |
| 27 | |
| 28 | /* The model describing this curve. This is mainly used to select |
| 29 | * the group equation. |
| 30 | */ |
| 31 | enum gcry_mpi_ec_models model; |
| 32 | |
| 33 | /* The actual ECC dialect used. This is used for curve specific |
| 34 | * optimizations and to select encodings etc. |
| 35 | */ |
| 36 | enum ecc_dialects dialect; |
| 37 | |
| 38 | const char *p; /* The prime defining the field. */ |
| 39 | const char *a, *b; /* The coefficients. For Twisted Edwards |
| 40 | * Curves b is used for d. For Montgomery |
| 41 | * Curves (a,b) has ((A-2)/4,B^-1). |
| 42 | */ |
| 43 | const char *n; /* The order of the base point. */ |
| 44 | const char *g_x, *g_y; /* Base point. */ |
| 45 | unsigned int h; /* Cofactor. */ |
| 46 | }; |
| 47 | |
| 48 | static const struct ecc_domain_parms sm2_ecp = { |
| 49 | .desc = "sm2p256v1", |
| 50 | .nbits = 256, |
| 51 | .fips = 0, |
| 52 | .model = MPI_EC_WEIERSTRASS, |
| 53 | .dialect = ECC_DIALECT_STANDARD, |
| 54 | .p = "0xfffffffeffffffffffffffffffffffffffffffff00000000ffffffffffffffff", |
| 55 | .a = "0xfffffffeffffffffffffffffffffffffffffffff00000000fffffffffffffffc", |
| 56 | .b = "0x28e9fa9e9d9f5e344d5a9e4bcf6509a7f39789f515ab8f92ddbcbd414d940e93", |
| 57 | .n = "0xfffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123", |
| 58 | .g_x = "0x32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7", |
| 59 | .g_y = "0xbc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0", |
| 60 | .h = 1 |
| 61 | }; |
| 62 | |
| 63 | static int sm2_ec_ctx_init(struct mpi_ec_ctx *ec) |
| 64 | { |
| 65 | const struct ecc_domain_parms *ecp = &sm2_ecp; |
| 66 | MPI p, a, b; |
| 67 | MPI x, y; |
| 68 | int rc = -EINVAL; |
| 69 | |
| 70 | p = mpi_scanval(ecp->p); |
| 71 | a = mpi_scanval(ecp->a); |
| 72 | b = mpi_scanval(ecp->b); |
| 73 | if (!p || !a || !b) |
| 74 | goto free_p; |
| 75 | |
| 76 | x = mpi_scanval(ecp->g_x); |
| 77 | y = mpi_scanval(ecp->g_y); |
| 78 | if (!x || !y) |
| 79 | goto free; |
| 80 | |
| 81 | rc = -ENOMEM; |
Hongbo Li | 5cd259c | 2021-06-04 14:30:35 +0800 | [diff] [blame^] | 82 | |
| 83 | ec->Q = mpi_point_new(0); |
| 84 | if (!ec->Q) |
| 85 | goto free; |
| 86 | |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 87 | /* mpi_ec_setup_elliptic_curve */ |
| 88 | ec->G = mpi_point_new(0); |
Hongbo Li | 5cd259c | 2021-06-04 14:30:35 +0800 | [diff] [blame^] | 89 | if (!ec->G) { |
| 90 | mpi_point_release(ec->Q); |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 91 | goto free; |
Hongbo Li | 5cd259c | 2021-06-04 14:30:35 +0800 | [diff] [blame^] | 92 | } |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 93 | |
| 94 | mpi_set(ec->G->x, x); |
| 95 | mpi_set(ec->G->y, y); |
| 96 | mpi_set_ui(ec->G->z, 1); |
| 97 | |
| 98 | rc = -EINVAL; |
| 99 | ec->n = mpi_scanval(ecp->n); |
| 100 | if (!ec->n) { |
Hongbo Li | 5cd259c | 2021-06-04 14:30:35 +0800 | [diff] [blame^] | 101 | mpi_point_release(ec->Q); |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 102 | mpi_point_release(ec->G); |
| 103 | goto free; |
| 104 | } |
| 105 | |
| 106 | ec->h = ecp->h; |
| 107 | ec->name = ecp->desc; |
| 108 | mpi_ec_init(ec, ecp->model, ecp->dialect, 0, p, a, b); |
| 109 | |
| 110 | rc = 0; |
| 111 | |
| 112 | free: |
| 113 | mpi_free(x); |
| 114 | mpi_free(y); |
| 115 | free_p: |
| 116 | mpi_free(p); |
| 117 | mpi_free(a); |
| 118 | mpi_free(b); |
| 119 | |
| 120 | return rc; |
| 121 | } |
| 122 | |
| 123 | static void sm2_ec_ctx_deinit(struct mpi_ec_ctx *ec) |
| 124 | { |
| 125 | mpi_ec_deinit(ec); |
| 126 | |
| 127 | memset(ec, 0, sizeof(*ec)); |
| 128 | } |
| 129 | |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 130 | /* RESULT must have been initialized and is set on success to the |
| 131 | * point given by VALUE. |
| 132 | */ |
| 133 | static int sm2_ecc_os2ec(MPI_POINT result, MPI value) |
| 134 | { |
| 135 | int rc; |
| 136 | size_t n; |
Tianjia Zhang | 1bc608b | 2020-10-15 17:24:41 +0800 | [diff] [blame] | 137 | unsigned char *buf; |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 138 | MPI x, y; |
| 139 | |
Tianjia Zhang | 1bc608b | 2020-10-15 17:24:41 +0800 | [diff] [blame] | 140 | n = MPI_NBYTES(value); |
| 141 | buf = kmalloc(n, GFP_KERNEL); |
| 142 | if (!buf) |
| 143 | return -ENOMEM; |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 144 | |
Tianjia Zhang | 1bc608b | 2020-10-15 17:24:41 +0800 | [diff] [blame] | 145 | rc = mpi_print(GCRYMPI_FMT_USG, buf, n, &n, value); |
| 146 | if (rc) |
| 147 | goto err_freebuf; |
| 148 | |
| 149 | rc = -EINVAL; |
| 150 | if (n < 1 || ((n - 1) % 2)) |
| 151 | goto err_freebuf; |
| 152 | /* No support for point compression */ |
| 153 | if (*buf != 0x4) |
| 154 | goto err_freebuf; |
| 155 | |
| 156 | rc = -ENOMEM; |
| 157 | n = (n - 1) / 2; |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 158 | x = mpi_read_raw_data(buf + 1, n); |
Tianjia Zhang | 1bc608b | 2020-10-15 17:24:41 +0800 | [diff] [blame] | 159 | if (!x) |
| 160 | goto err_freebuf; |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 161 | y = mpi_read_raw_data(buf + 1 + n, n); |
Tianjia Zhang | 1bc608b | 2020-10-15 17:24:41 +0800 | [diff] [blame] | 162 | if (!y) |
| 163 | goto err_freex; |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 164 | |
| 165 | mpi_normalize(x); |
| 166 | mpi_normalize(y); |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 167 | mpi_set(result->x, x); |
| 168 | mpi_set(result->y, y); |
| 169 | mpi_set_ui(result->z, 1); |
| 170 | |
Tianjia Zhang | 1bc608b | 2020-10-15 17:24:41 +0800 | [diff] [blame] | 171 | rc = 0; |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 172 | |
Tianjia Zhang | 1bc608b | 2020-10-15 17:24:41 +0800 | [diff] [blame] | 173 | mpi_free(y); |
| 174 | err_freex: |
| 175 | mpi_free(x); |
| 176 | err_freebuf: |
| 177 | kfree(buf); |
| 178 | return rc; |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 179 | } |
| 180 | |
| 181 | struct sm2_signature_ctx { |
| 182 | MPI sig_r; |
| 183 | MPI sig_s; |
| 184 | }; |
| 185 | |
| 186 | int sm2_get_signature_r(void *context, size_t hdrlen, unsigned char tag, |
| 187 | const void *value, size_t vlen) |
| 188 | { |
| 189 | struct sm2_signature_ctx *sig = context; |
| 190 | |
| 191 | if (!value || !vlen) |
| 192 | return -EINVAL; |
| 193 | |
| 194 | sig->sig_r = mpi_read_raw_data(value, vlen); |
| 195 | if (!sig->sig_r) |
| 196 | return -ENOMEM; |
| 197 | |
| 198 | return 0; |
| 199 | } |
| 200 | |
| 201 | int sm2_get_signature_s(void *context, size_t hdrlen, unsigned char tag, |
| 202 | const void *value, size_t vlen) |
| 203 | { |
| 204 | struct sm2_signature_ctx *sig = context; |
| 205 | |
| 206 | if (!value || !vlen) |
| 207 | return -EINVAL; |
| 208 | |
| 209 | sig->sig_s = mpi_read_raw_data(value, vlen); |
| 210 | if (!sig->sig_s) |
| 211 | return -ENOMEM; |
| 212 | |
| 213 | return 0; |
| 214 | } |
| 215 | |
| 216 | static int sm2_z_digest_update(struct shash_desc *desc, |
| 217 | MPI m, unsigned int pbytes) |
| 218 | { |
| 219 | static const unsigned char zero[32]; |
| 220 | unsigned char *in; |
| 221 | unsigned int inlen; |
| 222 | |
| 223 | in = mpi_get_buffer(m, &inlen, NULL); |
| 224 | if (!in) |
| 225 | return -EINVAL; |
| 226 | |
| 227 | if (inlen < pbytes) { |
| 228 | /* padding with zero */ |
| 229 | crypto_sm3_update(desc, zero, pbytes - inlen); |
| 230 | crypto_sm3_update(desc, in, inlen); |
| 231 | } else if (inlen > pbytes) { |
| 232 | /* skip the starting zero */ |
| 233 | crypto_sm3_update(desc, in + inlen - pbytes, pbytes); |
| 234 | } else { |
| 235 | crypto_sm3_update(desc, in, inlen); |
| 236 | } |
| 237 | |
| 238 | kfree(in); |
| 239 | return 0; |
| 240 | } |
| 241 | |
| 242 | static int sm2_z_digest_update_point(struct shash_desc *desc, |
| 243 | MPI_POINT point, struct mpi_ec_ctx *ec, unsigned int pbytes) |
| 244 | { |
| 245 | MPI x, y; |
| 246 | int ret = -EINVAL; |
| 247 | |
| 248 | x = mpi_new(0); |
| 249 | y = mpi_new(0); |
| 250 | |
| 251 | if (!mpi_ec_get_affine(x, y, point, ec) && |
| 252 | !sm2_z_digest_update(desc, x, pbytes) && |
| 253 | !sm2_z_digest_update(desc, y, pbytes)) |
| 254 | ret = 0; |
| 255 | |
| 256 | mpi_free(x); |
| 257 | mpi_free(y); |
| 258 | return ret; |
| 259 | } |
| 260 | |
| 261 | int sm2_compute_z_digest(struct crypto_akcipher *tfm, |
| 262 | const unsigned char *id, size_t id_len, |
| 263 | unsigned char dgst[SM3_DIGEST_SIZE]) |
| 264 | { |
| 265 | struct mpi_ec_ctx *ec = akcipher_tfm_ctx(tfm); |
| 266 | uint16_t bits_len; |
| 267 | unsigned char entl[2]; |
| 268 | SHASH_DESC_ON_STACK(desc, NULL); |
| 269 | unsigned int pbytes; |
| 270 | |
| 271 | if (id_len > (USHRT_MAX / 8) || !ec->Q) |
| 272 | return -EINVAL; |
| 273 | |
| 274 | bits_len = (uint16_t)(id_len * 8); |
| 275 | entl[0] = bits_len >> 8; |
| 276 | entl[1] = bits_len & 0xff; |
| 277 | |
| 278 | pbytes = MPI_NBYTES(ec->p); |
| 279 | |
| 280 | /* ZA = H256(ENTLA | IDA | a | b | xG | yG | xA | yA) */ |
| 281 | sm3_base_init(desc); |
| 282 | crypto_sm3_update(desc, entl, 2); |
| 283 | crypto_sm3_update(desc, id, id_len); |
| 284 | |
| 285 | if (sm2_z_digest_update(desc, ec->a, pbytes) || |
| 286 | sm2_z_digest_update(desc, ec->b, pbytes) || |
| 287 | sm2_z_digest_update_point(desc, ec->G, ec, pbytes) || |
| 288 | sm2_z_digest_update_point(desc, ec->Q, ec, pbytes)) |
| 289 | return -EINVAL; |
| 290 | |
| 291 | crypto_sm3_final(desc, dgst); |
| 292 | return 0; |
| 293 | } |
| 294 | EXPORT_SYMBOL(sm2_compute_z_digest); |
| 295 | |
| 296 | static int _sm2_verify(struct mpi_ec_ctx *ec, MPI hash, MPI sig_r, MPI sig_s) |
| 297 | { |
| 298 | int rc = -EINVAL; |
| 299 | struct gcry_mpi_point sG, tP; |
| 300 | MPI t = NULL; |
| 301 | MPI x1 = NULL, y1 = NULL; |
| 302 | |
| 303 | mpi_point_init(&sG); |
| 304 | mpi_point_init(&tP); |
| 305 | x1 = mpi_new(0); |
| 306 | y1 = mpi_new(0); |
| 307 | t = mpi_new(0); |
| 308 | |
| 309 | /* r, s in [1, n-1] */ |
| 310 | if (mpi_cmp_ui(sig_r, 1) < 0 || mpi_cmp(sig_r, ec->n) > 0 || |
| 311 | mpi_cmp_ui(sig_s, 1) < 0 || mpi_cmp(sig_s, ec->n) > 0) { |
| 312 | goto leave; |
| 313 | } |
| 314 | |
| 315 | /* t = (r + s) % n, t == 0 */ |
| 316 | mpi_addm(t, sig_r, sig_s, ec->n); |
| 317 | if (mpi_cmp_ui(t, 0) == 0) |
| 318 | goto leave; |
| 319 | |
| 320 | /* sG + tP = (x1, y1) */ |
| 321 | rc = -EBADMSG; |
| 322 | mpi_ec_mul_point(&sG, sig_s, ec->G, ec); |
| 323 | mpi_ec_mul_point(&tP, t, ec->Q, ec); |
| 324 | mpi_ec_add_points(&sG, &sG, &tP, ec); |
| 325 | if (mpi_ec_get_affine(x1, y1, &sG, ec)) |
| 326 | goto leave; |
| 327 | |
| 328 | /* R = (e + x1) % n */ |
| 329 | mpi_addm(t, hash, x1, ec->n); |
| 330 | |
| 331 | /* check R == r */ |
| 332 | rc = -EKEYREJECTED; |
| 333 | if (mpi_cmp(t, sig_r)) |
| 334 | goto leave; |
| 335 | |
| 336 | rc = 0; |
| 337 | |
| 338 | leave: |
| 339 | mpi_point_free_parts(&sG); |
| 340 | mpi_point_free_parts(&tP); |
| 341 | mpi_free(x1); |
| 342 | mpi_free(y1); |
| 343 | mpi_free(t); |
| 344 | |
| 345 | return rc; |
| 346 | } |
| 347 | |
| 348 | static int sm2_verify(struct akcipher_request *req) |
| 349 | { |
| 350 | struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req); |
| 351 | struct mpi_ec_ctx *ec = akcipher_tfm_ctx(tfm); |
| 352 | unsigned char *buffer; |
| 353 | struct sm2_signature_ctx sig; |
| 354 | MPI hash; |
| 355 | int ret; |
| 356 | |
| 357 | if (unlikely(!ec->Q)) |
| 358 | return -EINVAL; |
| 359 | |
| 360 | buffer = kmalloc(req->src_len + req->dst_len, GFP_KERNEL); |
| 361 | if (!buffer) |
| 362 | return -ENOMEM; |
| 363 | |
| 364 | sg_pcopy_to_buffer(req->src, |
| 365 | sg_nents_for_len(req->src, req->src_len + req->dst_len), |
| 366 | buffer, req->src_len + req->dst_len, 0); |
| 367 | |
| 368 | sig.sig_r = NULL; |
| 369 | sig.sig_s = NULL; |
| 370 | ret = asn1_ber_decoder(&sm2signature_decoder, &sig, |
| 371 | buffer, req->src_len); |
| 372 | if (ret) |
| 373 | goto error; |
| 374 | |
| 375 | ret = -ENOMEM; |
| 376 | hash = mpi_read_raw_data(buffer + req->src_len, req->dst_len); |
| 377 | if (!hash) |
| 378 | goto error; |
| 379 | |
| 380 | ret = _sm2_verify(ec, hash, sig.sig_r, sig.sig_s); |
| 381 | |
| 382 | mpi_free(hash); |
| 383 | error: |
| 384 | mpi_free(sig.sig_r); |
| 385 | mpi_free(sig.sig_s); |
| 386 | kfree(buffer); |
| 387 | return ret; |
| 388 | } |
| 389 | |
| 390 | static int sm2_set_pub_key(struct crypto_akcipher *tfm, |
| 391 | const void *key, unsigned int keylen) |
| 392 | { |
| 393 | struct mpi_ec_ctx *ec = akcipher_tfm_ctx(tfm); |
| 394 | MPI a; |
| 395 | int rc; |
| 396 | |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 397 | /* include the uncompressed flag '0x04' */ |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 398 | a = mpi_read_raw_data(key, keylen); |
| 399 | if (!a) |
Hongbo Li | 5cd259c | 2021-06-04 14:30:35 +0800 | [diff] [blame^] | 400 | return -ENOMEM; |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 401 | |
| 402 | mpi_normalize(a); |
| 403 | rc = sm2_ecc_os2ec(ec->Q, a); |
| 404 | mpi_free(a); |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 405 | |
Tianjia Zhang | ea7ecb6 | 2020-09-21 00:20:57 +0800 | [diff] [blame] | 406 | return rc; |
| 407 | } |
| 408 | |
| 409 | static unsigned int sm2_max_size(struct crypto_akcipher *tfm) |
| 410 | { |
| 411 | /* Unlimited max size */ |
| 412 | return PAGE_SIZE; |
| 413 | } |
| 414 | |
| 415 | static int sm2_init_tfm(struct crypto_akcipher *tfm) |
| 416 | { |
| 417 | struct mpi_ec_ctx *ec = akcipher_tfm_ctx(tfm); |
| 418 | |
| 419 | return sm2_ec_ctx_init(ec); |
| 420 | } |
| 421 | |
| 422 | static void sm2_exit_tfm(struct crypto_akcipher *tfm) |
| 423 | { |
| 424 | struct mpi_ec_ctx *ec = akcipher_tfm_ctx(tfm); |
| 425 | |
| 426 | sm2_ec_ctx_deinit(ec); |
| 427 | } |
| 428 | |
| 429 | static struct akcipher_alg sm2 = { |
| 430 | .verify = sm2_verify, |
| 431 | .set_pub_key = sm2_set_pub_key, |
| 432 | .max_size = sm2_max_size, |
| 433 | .init = sm2_init_tfm, |
| 434 | .exit = sm2_exit_tfm, |
| 435 | .base = { |
| 436 | .cra_name = "sm2", |
| 437 | .cra_driver_name = "sm2-generic", |
| 438 | .cra_priority = 100, |
| 439 | .cra_module = THIS_MODULE, |
| 440 | .cra_ctxsize = sizeof(struct mpi_ec_ctx), |
| 441 | }, |
| 442 | }; |
| 443 | |
| 444 | static int sm2_init(void) |
| 445 | { |
| 446 | return crypto_register_akcipher(&sm2); |
| 447 | } |
| 448 | |
| 449 | static void sm2_exit(void) |
| 450 | { |
| 451 | crypto_unregister_akcipher(&sm2); |
| 452 | } |
| 453 | |
| 454 | subsys_initcall(sm2_init); |
| 455 | module_exit(sm2_exit); |
| 456 | |
| 457 | MODULE_LICENSE("GPL"); |
| 458 | MODULE_AUTHOR("Tianjia Zhang <tianjia.zhang@linux.alibaba.com>"); |
| 459 | MODULE_DESCRIPTION("SM2 generic algorithm"); |
| 460 | MODULE_ALIAS_CRYPTO("sm2-generic"); |