/* $OpenBSD: gostr341001_pmeth.c,v 1.19 2023/07/28 15:50:33 tb Exp $ */ /* * Copyright (c) 2014 Dmitry Eremin-Solenikov * Copyright (c) 2005-2006 Cryptocom LTD * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" * * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * openssl-core@openssl.org. * * 5. Products derived from this software may not be called "OpenSSL" * nor may "OpenSSL" appear in their names without prior written * permission of the OpenSSL Project. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the OpenSSL Project * for use in the OpenSSL Toolkit (http://www.openssl.org/)" * * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== */ #include #include #ifndef OPENSSL_NO_GOST #include #include #include #include #include #include #include "ecdsa_local.h" #include "evp_local.h" #include "gost_local.h" #include "gost_asn1.h" static ECDSA_SIG * unpack_signature_cp(const unsigned char *sig, size_t siglen) { ECDSA_SIG *s; s = ECDSA_SIG_new(); if (s == NULL) { GOSTerror(ERR_R_MALLOC_FAILURE); return NULL; } BN_bin2bn(sig, siglen / 2, s->s); BN_bin2bn(sig + siglen / 2, siglen / 2, s->r); return s; } static int pack_signature_cp(ECDSA_SIG *s, int order, unsigned char *sig, size_t *siglen) { int r_len = BN_num_bytes(s->r); int s_len = BN_num_bytes(s->s); if (r_len > order || s_len > order) return 0; *siglen = 2 * order; memset(sig, 0, *siglen); BN_bn2bin(s->s, sig + order - s_len); BN_bn2bin(s->r, sig + 2 * order - r_len); ECDSA_SIG_free(s); return 1; } static ECDSA_SIG * unpack_signature_le(const unsigned char *sig, size_t siglen) { ECDSA_SIG *s; s = ECDSA_SIG_new(); if (s == NULL) { GOSTerror(ERR_R_MALLOC_FAILURE); return NULL; } GOST_le2bn(sig, siglen / 2, s->r); GOST_le2bn(sig + siglen / 2, siglen / 2, s->s); return s; } static int pack_signature_le(ECDSA_SIG *s, int order, unsigned char *sig, size_t *siglen) { *siglen = 2 * order; memset(sig, 0, *siglen); GOST_bn2le(s->r, sig, order); GOST_bn2le(s->s, sig + order, order); ECDSA_SIG_free(s); return 1; } struct gost_pmeth_data { int sign_param_nid; /* Should be set whenever parameters are filled */ int digest_nid; EVP_MD *md; unsigned char *shared_ukm; int peer_key_used; int sig_format; }; static int pkey_gost01_init(EVP_PKEY_CTX *ctx) { struct gost_pmeth_data *data; EVP_PKEY *pkey = EVP_PKEY_CTX_get0_pkey(ctx); data = calloc(1, sizeof(struct gost_pmeth_data)); if (data == NULL) return 0; if (pkey != NULL && pkey->pkey.gost != NULL) { data->sign_param_nid = EC_GROUP_get_curve_name(GOST_KEY_get0_group(pkey->pkey.gost)); data->digest_nid = GOST_KEY_get_digest(pkey->pkey.gost); } EVP_PKEY_CTX_set_data(ctx, data); return 1; } /* Copies contents of gost_pmeth_data structure */ static int pkey_gost01_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) { struct gost_pmeth_data *dst_data, *src_data; if (pkey_gost01_init(dst) == 0) return 0; src_data = EVP_PKEY_CTX_get_data(src); dst_data = EVP_PKEY_CTX_get_data(dst); *dst_data = *src_data; if (src_data->shared_ukm != NULL) dst_data->shared_ukm = NULL; return 1; } /* Frees up gost_pmeth_data structure */ static void pkey_gost01_cleanup(EVP_PKEY_CTX *ctx) { struct gost_pmeth_data *data; if ((data = EVP_PKEY_CTX_get_data(ctx)) == NULL) return; free(data->shared_ukm); free(data); } static int pkey_gost01_paramgen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) { struct gost_pmeth_data *data = EVP_PKEY_CTX_get_data(ctx); EC_GROUP *group = NULL; GOST_KEY *gost = NULL; int ret = 0; if (data->sign_param_nid == NID_undef || data->digest_nid == NID_undef) { GOSTerror(GOST_R_NO_PARAMETERS_SET); return 0; } group = EC_GROUP_new_by_curve_name(data->sign_param_nid); if (group == NULL) goto done; EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); gost = GOST_KEY_new(); if (gost == NULL) goto done; if (GOST_KEY_set_digest(gost, data->digest_nid) == 0) goto done; if (GOST_KEY_set_group(gost, group) != 0) ret = EVP_PKEY_assign_GOST(pkey, gost); done: if (ret == 0) GOST_KEY_free(gost); EC_GROUP_free(group); return ret; } static int pkey_gost01_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) { if (pkey_gost01_paramgen(ctx, pkey) == 0) return 0; return gost2001_keygen(pkey->pkey.gost) != 0; } static int pkey_gost01_sign(EVP_PKEY_CTX *ctx, unsigned char *sig, size_t *siglen, const unsigned char *tbs, size_t tbs_len) { ECDSA_SIG *unpacked_sig = NULL; EVP_PKEY *pkey = EVP_PKEY_CTX_get0_pkey(ctx); struct gost_pmeth_data *pctx = EVP_PKEY_CTX_get_data(ctx); BIGNUM *md; size_t size; int ret; if (pkey == NULL || pkey->pkey.gost == NULL) return 0; size = GOST_KEY_get_size(pkey->pkey.gost); if (siglen == NULL) return 0; if (sig == NULL) { *siglen = 2 * size; return 1; } else if (*siglen < 2 * size) { GOSTerror(EC_R_BUFFER_TOO_SMALL); return 0; } if (tbs_len != 32 && tbs_len != 64) { GOSTerror(EVP_R_BAD_BLOCK_LENGTH); return 0; } md = GOST_le2bn(tbs, tbs_len, NULL); if (md == NULL) return 0; unpacked_sig = gost2001_do_sign(md, pkey->pkey.gost); BN_free(md); if (unpacked_sig == NULL) { return 0; } switch (pctx->sig_format) { case GOST_SIG_FORMAT_SR_BE: ret = pack_signature_cp(unpacked_sig, size, sig, siglen); break; case GOST_SIG_FORMAT_RS_LE: ret = pack_signature_le(unpacked_sig, size, sig, siglen); break; default: ret = -1; break; } if (ret <= 0) ECDSA_SIG_free(unpacked_sig); return ret; } static int pkey_gost01_verify(EVP_PKEY_CTX *ctx, const unsigned char *sig, size_t siglen, const unsigned char *tbs, size_t tbs_len) { int ok = 0; EVP_PKEY *pub_key = EVP_PKEY_CTX_get0_pkey(ctx); struct gost_pmeth_data *pctx = EVP_PKEY_CTX_get_data(ctx); ECDSA_SIG *s = NULL; BIGNUM *md; if (pub_key == NULL) return 0; switch (pctx->sig_format) { case GOST_SIG_FORMAT_SR_BE: s = unpack_signature_cp(sig, siglen); break; case GOST_SIG_FORMAT_RS_LE: s = unpack_signature_le(sig, siglen); break; } if (s == NULL) return 0; md = GOST_le2bn(tbs, tbs_len, NULL); if (md == NULL) goto err; ok = gost2001_do_verify(md, s, pub_key->pkey.gost); err: BN_free(md); ECDSA_SIG_free(s); return ok; } static int gost01_VKO_key(EVP_PKEY *pub_key, EVP_PKEY *priv_key, const unsigned char *ukm, unsigned char *key) { unsigned char hashbuf[128]; int digest_nid; int ret = 0; BN_CTX *ctx = BN_CTX_new(); BIGNUM *UKM, *X, *Y; if (ctx == NULL) return 0; BN_CTX_start(ctx); if ((UKM = BN_CTX_get(ctx)) == NULL) goto err; if ((X = BN_CTX_get(ctx)) == NULL) goto err; if ((Y = BN_CTX_get(ctx)) == NULL) goto err; GOST_le2bn(ukm, 8, UKM); digest_nid = GOST_KEY_get_digest(priv_key->pkey.gost); if (VKO_compute_key(X, Y, pub_key->pkey.gost, priv_key->pkey.gost, UKM) == 0) goto err; switch (digest_nid) { case NID_id_GostR3411_94_CryptoProParamSet: GOST_bn2le(X, hashbuf, 32); GOST_bn2le(Y, hashbuf + 32, 32); GOSTR341194(hashbuf, 64, key, digest_nid); ret = 1; break; case NID_id_tc26_gost3411_2012_256: GOST_bn2le(X, hashbuf, 32); GOST_bn2le(Y, hashbuf + 32, 32); STREEBOG256(hashbuf, 64, key); ret = 1; break; case NID_id_tc26_gost3411_2012_512: GOST_bn2le(X, hashbuf, 64); GOST_bn2le(Y, hashbuf + 64, 64); STREEBOG256(hashbuf, 128, key); ret = 1; break; default: ret = -2; break; } err: BN_CTX_end(ctx); BN_CTX_free(ctx); return ret; } int pkey_gost01_decrypt(EVP_PKEY_CTX *pctx, unsigned char *key, size_t *key_len, const unsigned char *in, size_t in_len) { const unsigned char *p = in; EVP_PKEY *priv = EVP_PKEY_CTX_get0_pkey(pctx); GOST_KEY_TRANSPORT *gkt = NULL; int ret = 0; unsigned char wrappedKey[44]; unsigned char sharedKey[32]; EVP_PKEY *eph_key = NULL, *peerkey = NULL; int nid; if (key == NULL) { *key_len = 32; return 1; } gkt = d2i_GOST_KEY_TRANSPORT(NULL, (const unsigned char **)&p, in_len); if (gkt == NULL) { GOSTerror(GOST_R_ERROR_PARSING_KEY_TRANSPORT_INFO); return -1; } /* If key transport structure contains public key, use it */ eph_key = X509_PUBKEY_get(gkt->key_agreement_info->ephem_key); if (eph_key != NULL) { if (EVP_PKEY_derive_set_peer(pctx, eph_key) <= 0) { GOSTerror(GOST_R_INCOMPATIBLE_PEER_KEY); goto err; } } else { /* Set control "public key from client certificate used" */ if (EVP_PKEY_CTX_ctrl(pctx, -1, -1, EVP_PKEY_CTRL_PEER_KEY, 3, NULL) <= 0) { GOSTerror(GOST_R_CTRL_CALL_FAILED); goto err; } } peerkey = EVP_PKEY_CTX_get0_peerkey(pctx); if (peerkey == NULL) { GOSTerror(GOST_R_NO_PEER_KEY); goto err; } nid = OBJ_obj2nid(gkt->key_agreement_info->cipher); if (gkt->key_agreement_info->eph_iv->length != 8) { GOSTerror(GOST_R_INVALID_IV_LENGTH); goto err; } memcpy(wrappedKey, gkt->key_agreement_info->eph_iv->data, 8); if (gkt->key_info->encrypted_key->length != 32) { GOSTerror(EVP_R_BAD_KEY_LENGTH); goto err; } memcpy(wrappedKey + 8, gkt->key_info->encrypted_key->data, 32); if (gkt->key_info->imit->length != 4) { GOSTerror(ERR_R_INTERNAL_ERROR); goto err; } memcpy(wrappedKey + 40, gkt->key_info->imit->data, 4); if (gost01_VKO_key(peerkey, priv, wrappedKey, sharedKey) <= 0) goto err; if (gost_key_unwrap_crypto_pro(nid, sharedKey, wrappedKey, key) == 0) { GOSTerror(GOST_R_ERROR_COMPUTING_SHARED_KEY); goto err; } ret = 1; err: EVP_PKEY_free(eph_key); GOST_KEY_TRANSPORT_free(gkt); return ret; } int pkey_gost01_derive(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen) { /* * Public key of peer in the ctx field peerkey * Our private key in the ctx pkey * ukm is in the algorithm specific context data */ EVP_PKEY *my_key = EVP_PKEY_CTX_get0_pkey(ctx); EVP_PKEY *peer_key = EVP_PKEY_CTX_get0_peerkey(ctx); struct gost_pmeth_data *data = EVP_PKEY_CTX_get_data(ctx); if (data->shared_ukm == NULL) { GOSTerror(GOST_R_UKM_NOT_SET); return 0; } if (key == NULL) { *keylen = 32; return 32; } if (gost01_VKO_key(peer_key, my_key, data->shared_ukm, key) <= 0) return 0; *keylen = 32; return 1; } int pkey_gost01_encrypt(EVP_PKEY_CTX *pctx, unsigned char *out, size_t *out_len, const unsigned char *key, size_t key_len) { GOST_KEY_TRANSPORT *gkt = NULL; EVP_PKEY *pubk = EVP_PKEY_CTX_get0_pkey(pctx); struct gost_pmeth_data *data = EVP_PKEY_CTX_get_data(pctx); unsigned char ukm[8], shared_key[32], crypted_key[44]; int ret = 0; int key_is_ephemeral; EVP_PKEY *sec_key = EVP_PKEY_CTX_get0_peerkey(pctx); int nid = NID_id_Gost28147_89_CryptoPro_A_ParamSet; if (data->shared_ukm != NULL) { memcpy(ukm, data->shared_ukm, 8); } else /* if (out != NULL) */ { arc4random_buf(ukm, 8); } /* Check for private key in the peer_key of context */ if (sec_key) { key_is_ephemeral = 0; if (GOST_KEY_get0_private_key(sec_key->pkey.gost) == 0) { GOSTerror(GOST_R_NO_PRIVATE_PART_OF_NON_EPHEMERAL_KEYPAIR); goto err; } } else { key_is_ephemeral = 1; if (out != NULL) { GOST_KEY *tmp_key; sec_key = EVP_PKEY_new(); if (sec_key == NULL) goto err; tmp_key = GOST_KEY_new(); if (tmp_key == NULL) goto err; if (EVP_PKEY_assign(sec_key, EVP_PKEY_base_id(pubk), tmp_key) == 0) { GOST_KEY_free(tmp_key); goto err; } if (EVP_PKEY_copy_parameters(sec_key, pubk) == 0) goto err; if (gost2001_keygen(sec_key->pkey.gost) == 0) { goto err; } } } if (out != NULL) { if (gost01_VKO_key(pubk, sec_key, ukm, shared_key) <= 0) goto err; gost_key_wrap_crypto_pro(nid, shared_key, ukm, key, crypted_key); } gkt = GOST_KEY_TRANSPORT_new(); if (gkt == NULL) goto err; if (ASN1_OCTET_STRING_set(gkt->key_agreement_info->eph_iv, ukm, 8) == 0) goto err; if (ASN1_OCTET_STRING_set(gkt->key_info->imit, crypted_key + 40, 4) == 0) goto err; if (ASN1_OCTET_STRING_set(gkt->key_info->encrypted_key, crypted_key + 8, 32) == 0) goto err; if (key_is_ephemeral) { if (X509_PUBKEY_set(&gkt->key_agreement_info->ephem_key, out != NULL ? sec_key : pubk) == 0) { GOSTerror(GOST_R_CANNOT_PACK_EPHEMERAL_KEY); goto err; } } ASN1_OBJECT_free(gkt->key_agreement_info->cipher); gkt->key_agreement_info->cipher = OBJ_nid2obj(nid); if (key_is_ephemeral) EVP_PKEY_free(sec_key); else { /* Set control "public key from client certificate used" */ if (EVP_PKEY_CTX_ctrl(pctx, -1, -1, EVP_PKEY_CTRL_PEER_KEY, 3, NULL) <= 0) { GOSTerror(GOST_R_CTRL_CALL_FAILED); goto err; } } if ((*out_len = i2d_GOST_KEY_TRANSPORT(gkt, out ? &out : NULL)) > 0) ret = 1; GOST_KEY_TRANSPORT_free(gkt); return ret; err: if (key_is_ephemeral) EVP_PKEY_free(sec_key); GOST_KEY_TRANSPORT_free(gkt); return -1; } static int pkey_gost01_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2) { struct gost_pmeth_data *pctx = EVP_PKEY_CTX_get_data(ctx); switch (type) { case EVP_PKEY_CTRL_MD: if (EVP_MD_type(p2) != GostR3410_get_md_digest(pctx->digest_nid)) { GOSTerror(GOST_R_INVALID_DIGEST_TYPE); return 0; } pctx->md = p2; return 1; case EVP_PKEY_CTRL_PKCS7_ENCRYPT: case EVP_PKEY_CTRL_PKCS7_DECRYPT: case EVP_PKEY_CTRL_PKCS7_SIGN: case EVP_PKEY_CTRL_DIGESTINIT: return 1; case EVP_PKEY_CTRL_GOST_PARAMSET: pctx->sign_param_nid = (int)p1; return 1; case EVP_PKEY_CTRL_SET_IV: { char *ukm = malloc(p1); if (ukm == NULL) { GOSTerror(ERR_R_MALLOC_FAILURE); return 0; } memcpy(ukm, p2, p1); free(pctx->shared_ukm); pctx->shared_ukm = ukm; return 1; } case EVP_PKEY_CTRL_PEER_KEY: if (p1 == 0 || p1 == 1) /* call from EVP_PKEY_derive_set_peer */ return 1; if (p1 == 2) /* TLS: peer key used? */ return pctx->peer_key_used; if (p1 == 3) /* TLS: peer key used! */ return (pctx->peer_key_used = 1); return -2; case EVP_PKEY_CTRL_GOST_SIG_FORMAT: switch (p1) { case GOST_SIG_FORMAT_SR_BE: case GOST_SIG_FORMAT_RS_LE: pctx->sig_format = p1; return 1; default: return 0; } break; case EVP_PKEY_CTRL_GOST_SET_DIGEST: pctx->digest_nid = (int)p1; return 1; case EVP_PKEY_CTRL_GOST_GET_DIGEST: *(int *)p2 = pctx->digest_nid; return 1; default: return -2; } } static int pkey_gost01_ctrl_str(EVP_PKEY_CTX *ctx, const char *type, const char *value) { int param_nid = NID_undef; int digest_nid = NID_undef; if (strcmp(type, "paramset") == 0) { if (value == NULL) return 0; if (pkey_gost01_ctrl(ctx, EVP_PKEY_CTRL_GOST_GET_DIGEST, 0, &digest_nid) == 0) return 0; if (digest_nid == NID_id_tc26_gost3411_2012_512) param_nid = GostR3410_512_param_id(value); else param_nid = GostR3410_256_param_id(value); if (param_nid == NID_undef) param_nid = OBJ_txt2nid(value); if (param_nid == NID_undef) return 0; return pkey_gost01_ctrl(ctx, EVP_PKEY_CTRL_GOST_PARAMSET, param_nid, NULL); } if (strcmp(type, "dgst") == 0) { if (value == NULL) return 0; else if (strcmp(value, "gost94") == 0 || strcmp(value, "md_gost94") == 0) digest_nid = NID_id_GostR3411_94_CryptoProParamSet; else if (strcmp(value, "streebog256") == 0) digest_nid = NID_id_tc26_gost3411_2012_256; else if (strcmp(value, "streebog512") == 0) digest_nid = NID_id_tc26_gost3411_2012_512; if (digest_nid == NID_undef) return 0; return pkey_gost01_ctrl(ctx, EVP_PKEY_CTRL_GOST_SET_DIGEST, digest_nid, NULL); } return -2; } const EVP_PKEY_METHOD gostr01_pkey_meth = { .pkey_id = EVP_PKEY_GOSTR01, .init = pkey_gost01_init, .copy = pkey_gost01_copy, .cleanup = pkey_gost01_cleanup, .paramgen = pkey_gost01_paramgen, .keygen = pkey_gost01_keygen, .sign = pkey_gost01_sign, .verify = pkey_gost01_verify, .encrypt = pkey_gost01_encrypt, .decrypt = pkey_gost01_decrypt, .derive = pkey_gost01_derive, .ctrl = pkey_gost01_ctrl, .ctrl_str = pkey_gost01_ctrl_str, }; #endif