Path: blob/main/sys/contrib/openzfs/tests/zfs-tests/cmd/crypto_test.c
48529 views
/*1* SPDX-License-Identifier: MIT2*3* Copyright (c) 2025, Rob Norris <[email protected]>4*5* Permission is hereby granted, free of charge, to any person obtaining a copy6* of this software and associated documentation files (the "Software"), to7* deal in the Software without restriction, including without limitation the8* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or9* sell copies of the Software, and to permit persons to whom the Software is10* furnished to do so, subject to the following conditions:11*12* The above copyright notice and this permission notice shall be included in13* all copies or substantial portions of the Software.14*15* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR16* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,17* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE18* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER19* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING20* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS21* IN THE SOFTWARE.22*/2324/*25* This is a userspace test driver for the ICP. It has two modes:26*27* "correctness" (-c <testfile>):28* Load a file full of test vectors. For each implementation of the named29* algorithm, loop over the tests, and run encrypt and decrypt with the30* provided parameters and confirm they either do (result=valid) or do not31* (result=invalid) succeed.32*33* "performance" (-p <alg>)34* For each implementation of the named algorithm, run 1000 rounds of35* encrypt() on a range of power-2 sizes of input data from 2^10 (1K) to36* 2^19 (512K).37*/3839#include <stdio.h>40#include <stdlib.h>41#include <string.h>42#include <errno.h>43#include <getopt.h>4445#include <sys/crypto/icp.h>46#include <sys/crypto/api.h>4748/* for zfs_nicenum, zfs_nicebytes */49#include <libzutil.h>5051/* ========== */5253/* types and data for both modes */5455/* valid test algorithms */56typedef enum {57ALG_NONE,58ALG_AES_GCM,59ALG_AES_CCM,60} crypto_test_alg_t;6162/*63* Generally the ICP expects zero-length data to still require a valid64* (non-NULL) pointer, even though it will never read from it. This is a65* convenient valid item for tjat case.66*/67static uint8_t val_empty[1] = {0};6869/* Strings for error returns */70static const char *crypto_errstr[] = {71[CRYPTO_SUCCESS] = "CRYPTO_SUCCESS",72[CRYPTO_HOST_MEMORY] = "CRYPTO_HOST_MEMORY",73[CRYPTO_FAILED] = "CRYPTO_FAILED",74[CRYPTO_ARGUMENTS_BAD] = "CRYPTO_ARGUMENTS_BAD",75[CRYPTO_DATA_LEN_RANGE] = "CRYPTO_DATA_LEN_RANGE",76[CRYPTO_ENCRYPTED_DATA_LEN_RANGE] = "CRYPTO_ENCRYPTED_DATA_LEN_RANGE",77[CRYPTO_KEY_SIZE_RANGE] = "CRYPTO_KEY_SIZE_RANGE",78[CRYPTO_KEY_TYPE_INCONSISTENT] = "CRYPTO_KEY_TYPE_INCONSISTENT",79[CRYPTO_MECHANISM_INVALID] = "CRYPTO_MECHANISM_INVALID",80[CRYPTO_MECHANISM_PARAM_INVALID] = "CRYPTO_MECHANISM_PARAM_INVALID",81[CRYPTO_SIGNATURE_INVALID] = "CRYPTO_SIGNATURE_INVALID",82[CRYPTO_BUFFER_TOO_SMALL] = "CRYPTO_BUFFER_TOO_SMALL",83[CRYPTO_NOT_SUPPORTED] = "CRYPTO_NOT_SUPPORTED",84[CRYPTO_INVALID_CONTEXT] = "CRYPTO_INVALID_CONTEXT",85[CRYPTO_INVALID_MAC] = "CRYPTO_INVALID_MAC",86[CRYPTO_MECH_NOT_SUPPORTED] = "CRYPTO_MECH_NOT_SUPPORTED",87[CRYPTO_INVALID_PROVIDER_ID] = "CRYPTO_INVALID_PROVIDER_ID",88[CRYPTO_BUSY] = "CRYPTO_BUSY",89[CRYPTO_UNKNOWN_PROVIDER] = "CRYPTO_UNKNOWN_PROVIDER",90};9192/* what to output; driven by -v switch */93typedef enum {94OUT_SUMMARY,95OUT_FAIL,96OUT_ALL,97} crypto_test_outmode_t;9899100/* ========== */101102/* types and data for correctness tests */103104/* most ICP inputs are separate val & len */105typedef struct {106uint8_t *val;107size_t len;108} crypto_test_val_t;109110/* tests can be expected to pass (valid) or expected to fail (invalid) */111typedef enum {112RS_NONE = 0,113RS_VALID,114RS_INVALID,115} crypto_test_result_t;116117/* a single test, loaded from the test file */118typedef struct crypto_test crypto_test_t;119struct crypto_test {120crypto_test_t *next; /* ptr to next test */121char *fileloc; /* file:line of test in file */122crypto_test_alg_t alg; /* alg, for convenience */123124/* id, comment and flags are for output */125uint64_t id;126char *comment;127char *flags;128129/*130* raw test params. these are hex strings in the test file, which131* we convert on load.132*/133crypto_test_val_t iv;134crypto_test_val_t key;135crypto_test_val_t msg;136crypto_test_val_t ct;137crypto_test_val_t aad;138crypto_test_val_t tag;139140/* expected result */141crypto_test_result_t result;142};143144/* ========== */145146/* test file loader */147148/*149* helper; split a 'key: value\n' line into separate key and value. original150* line is modified; \0 will be inserted at end of key and end of value.151*/152static boolean_t153split_kv(char *line, char **kp, char **vp)154{155char *c = strstr(line, ":");156if (c == NULL)157return (B_FALSE);158159160*c++ = '\0';161while (*c == ' ')162c++;163164char *v = c;165c = strchr(v, '\n');166if (c != NULL) {167*c++ = '\0';168if (*c != '\0')169return (B_FALSE);170}171172*kp = line;173*vp = v;174return (B_TRUE);175}176177/*178* helper; parse decimal number to uint64179*/180static boolean_t181parse_num(char *v, uint64_t *np)182{183char *c = NULL;184errno = 0;185uint64_t n = strtoull(v, &c, 10);186if (*v == '\0' || *c != '\0' || errno != 0 ||187n >= UINT32_MAX || n == 0)188return (B_FALSE);189*np = n;190return (B_TRUE);191}192193/*194* load tests from the test file. returns a linked list of tests, and the195* test algorithm in *algp.196*/197static crypto_test_t *198load_tests(const char *filepath, crypto_test_alg_t *algp)199{200crypto_test_t *tests = NULL, *tail = NULL;201char *buf = NULL;202size_t buflen = 0;203FILE *fh = NULL;204205if ((fh = fopen(filepath, "r")) == NULL) {206fprintf(stderr, "E: couldn't open %s: %s\n",207filepath, strerror(errno));208goto err;209}210211/* extract the filename part from the path, for nicer output */212const char *filename = &filepath[strlen(filepath)-1];213while (filename != filepath) {214if (*filename == '/') {215filename++;216break;217}218filename--;219}220221int lineno = 0;222223crypto_test_alg_t alg = ALG_NONE;224uint64_t ntests = 0;225crypto_test_t *test = NULL;226uint64_t ncommitted = 0;227228char *k, *v;229230ssize_t nread;231while ((nread = getline(&buf, &buflen, fh)) != -1 || errno == 0) {232/* track line number for output and for test->fileloc */233lineno++;234235if (nread < 2 && test != NULL) {236/*237* blank line or end of file; close out any test in238* progress and commit it.239*/240if (test->id == 0 ||241test->iv.val == NULL ||242test->key.val == NULL ||243test->msg.val == NULL ||244test->ct.val == NULL ||245test->aad.val == NULL ||246test->tag.val == NULL ||247test->result == RS_NONE) {248fprintf(stderr, "E: incomplete test [%s:%d]\n",249filename, lineno);250goto err;251}252253/* commit the test, ie, add it to the list */254if (tail == NULL)255tests = tail = test;256else {257tail->next = test;258tail = test;259}260ncommitted++;261262test = NULL;263}264265if (nread == -1)266/* end of file and tests finished, done */267break;268269if (nread < 2 && ncommitted == 0) {270/*271* blank line after header, make sure the header is272* complete.273*/274if (alg == ALG_NONE || ntests == 0) {275fprintf(stderr, "E: incomplete header "276"[%s:%d]\n", filename, lineno);277goto err;278}279}280281if (nread < 2) {282/*283* blank line and the header is committed, and no284* current test, so the next test will start on the285* next line.286*/287test = calloc(1, sizeof (crypto_test_t));288int len = strlen(filename) + 10;289test->fileloc = calloc(len, 1);290snprintf(test->fileloc, len, "%s:%d",291filename, lineno+1);292test->alg = alg;293continue;294}295296/*297* must be a k:v line. if there is a current test, then this298* line is part of it, otherwise it's a header line299*/300if (!split_kv(buf, &k, &v)) {301fprintf(stderr, "E: malformed line [%s:%d]\n",302filename, lineno);303goto err;304}305306if (test == NULL) {307/* no current test, so a header key */308309/*310* typical header:311*312* algorithm: AES-GCM313* tests: 316314*/315if (strcmp(k, "algorithm") == 0) {316if (alg != ALG_NONE)317goto err_dup_key;318if (strcmp(v, "AES-GCM") == 0)319alg = ALG_AES_GCM;320else if (strcmp(v, "AES-CCM") == 0)321alg = ALG_AES_CCM;322else {323fprintf(stderr,324"E: unknown algorithm [%s:%d]: "325"%s\n", filename, lineno, v);326goto err;327}328} else if (strcmp(k, "tests") == 0) {329if (ntests > 0)330goto err_dup_key;331if (!parse_num(v, &ntests)) {332fprintf(stderr,333"E: invalid number of tests "334"[%s:%d]: %s\n", filename, lineno,335v);336goto err;337}338} else {339fprintf(stderr, "E: unknown header key "340"[%s:%d]: %s\n", filename, lineno, k);341goto err;342}343continue;344}345346/* test key */347348/*349* typical test:350*351* id: 48352* comment: Flipped bit 63 in tag353* flags: ModifiedTag354* iv: 505152535455565758595a5b355* key: 000102030405060708090a0b0c0d0e0f356* msg: 202122232425262728292a2b2c2d2e2f357* ct: eb156d081ed6b6b55f4612f021d87b39358* aad:359* tag: d8847dbc326a066988c77ad3863e6083360* result: invalid361*/362if (strcmp(k, "id") == 0) {363if (test->id > 0)364goto err_dup_key;365if (!parse_num(v, &test->id)) {366fprintf(stderr,367"E: invalid test id [%s:%d]: %s\n",368filename, lineno, v);369goto err;370}371continue;372} else if (strcmp(k, "comment") == 0) {373if (test->comment != NULL)374goto err_dup_key;375test->comment = strdup(v);376continue;377} else if (strcmp(k, "flags") == 0) {378if (test->flags != NULL)379goto err_dup_key;380test->flags = strdup(v);381continue;382} else if (strcmp(k, "result") == 0) {383if (test->result != RS_NONE)384goto err_dup_key;385if (strcmp(v, "valid") == 0)386test->result = RS_VALID;387else if (strcmp(v, "invalid") == 0)388test->result = RS_INVALID;389else {390fprintf(stderr,391"E: unknown test result [%s:%d]: %s\n",392filename, lineno, v);393goto err;394}395continue;396}397398/*399* for the test param keys, we set up a pointer to the right400* field in the test struct, and then work through that401* pointer.402*/403crypto_test_val_t *vp = NULL;404if (strcmp(buf, "iv") == 0)405vp = &test->iv;406else if (strcmp(buf, "key") == 0)407vp = &test->key;408else if (strcmp(buf, "msg") == 0)409vp = &test->msg;410else if (strcmp(buf, "ct") == 0)411vp = &test->ct;412else if (strcmp(buf, "aad") == 0)413vp = &test->aad;414else if (strcmp(buf, "tag") == 0)415vp = &test->tag;416else {417fprintf(stderr, "E: unknown key [%s:%d]: %s\n",418filename, lineno, buf);419goto err;420}421422if (vp->val != NULL)423goto err_dup_key;424425/* sanity; these are hex bytes so must be two chars per byte. */426size_t vlen = strlen(v);427if ((vlen & 1) == 1) {428fprintf(stderr, "E: value length not even "429"[%s:%d]: %s\n", filename, lineno, buf);430goto err;431}432433/*434* zero-length params are allowed, but ICP requires a non-NULL435* value pointer, so we give it one and also use that as436* a marker for us to know that we've filled this value.437*/438if (vlen == 0) {439vp->val = val_empty;440continue;441}442443/*444* convert incoming value from hex to raw. allocate space445* half as long as the length, then loop the chars and446* convert from ascii to 4-bit values, shifting or or-ing447* as appropriate.448*/449vp->len = vlen/2;450vp->val = calloc(vp->len, 1);451452for (int i = 0; i < vlen; i++) {453char c = v[i];454if (!((c >= '0' && c <= '9') ||455(c >= 'a' && c <= 'f'))) {456fprintf(stderr, "E: invalid hex char "457"[%s:%d]: %c\n", filename, lineno, c);458goto err;459}460461uint8_t n = ((c <= '9') ? (c-0x30) : (c-0x57)) & 0xf;462if ((i & 1) == 0)463vp->val[i/2] = n << 4;464else465vp->val[i/2] |= n;466}467}468469if (errno != 0) {470fprintf(stderr, "E: couldn't read %s: %s\n",471filepath, strerror(errno));472goto err;473}474475free(buf);476fclose(fh);477478if (tests == NULL)479fprintf(stderr, "E: no tests in %s\n", filepath);480481*algp = alg;482return (tests);483484/*485* jump target for duplicate key error. this is so common that it's easier486* to just have a single error point.487*/488err_dup_key:489fprintf(stderr, "E: duplicate key [%s:%d]: %s\n", filename, lineno, k);490491err:492if (buf != NULL)493free(buf);494if (fh != NULL)495fclose(fh);496497/*498* XXX we should probably free all the tests here, but the test file499* is generated and this is a one-shot program, so it's really500* not worth the effort today501*/502503return (NULL);504}505506/* ========== */507508/* ICP algorithm implementation selection */509510/*511* It's currently not really possible to query the ICP for which512* implementations it supports. Also, not all GCM implementations work513* with all AES implementations. For now, we keep a hardcoded list of514* valid combinations.515*/516static const char *aes_impl[] = {517"generic",518"x86_64",519"aesni",520};521522static const char *aes_gcm_impl[][2] = {523{ "generic", "generic" },524{ "x86_64", "generic" },525{ "aesni", "generic" },526{ "generic", "pclmulqdq" },527{ "x86_64", "pclmulqdq" },528{ "aesni", "pclmulqdq" },529{ "x86_64", "avx" },530{ "aesni", "avx" },531{ "x86_64", "avx2" },532{ "aesni", "avx2" },533};534535/* signature of function to call after setting implementation params */536typedef void (*alg_cb_t)(const char *alginfo, void *arg);537538/* loop over each AES-CCM implementation */539static void540foreach_aes_ccm(alg_cb_t cb, void *arg, crypto_test_outmode_t outmode)541{542char alginfo[64];543544for (int i = 0; i < ARRAY_SIZE(aes_impl); i++) {545snprintf(alginfo, sizeof (alginfo), "AES-CCM [%s]",546aes_impl[i]);547548int err = -aes_impl_set(aes_impl[i]);549if (err != 0 && outmode != OUT_SUMMARY)550printf("W: %s couldn't enable AES impl '%s': %s\n",551alginfo, aes_impl[i], strerror(err));552553cb(alginfo, (err == 0) ? arg : NULL);554}555}556557/* loop over each AES-GCM implementation */558static void559foreach_aes_gcm(alg_cb_t cb, void *arg, crypto_test_outmode_t outmode)560{561char alginfo[64];562563for (int i = 0; i < ARRAY_SIZE(aes_gcm_impl); i++) {564const char *aes_impl = aes_gcm_impl[i][0];565const char *gcm_impl = aes_gcm_impl[i][1];566567snprintf(alginfo, sizeof (alginfo), "AES-GCM [%s+%s]",568aes_impl, gcm_impl);569570int err = -aes_impl_set(aes_impl);571if (err != 0 && outmode != OUT_SUMMARY)572printf("W: %s couldn't enable AES impl '%s': %s\n",573alginfo, aes_impl, strerror(err));574575if (err == 0) {576err = -gcm_impl_set(gcm_impl);577if (err != 0 && outmode != OUT_SUMMARY) {578printf("W: %s couldn't enable "579"GCM impl '%s': %s\n",580alginfo, gcm_impl, strerror(err));581}582}583584cb(alginfo, (err == 0) ? arg : NULL);585}586}587588/* ========== */589590/* ICP lowlevel drivers */591592/*593* initialise the mechanism (algorithm description) with the wanted parameters594* for the next operation.595*596* mech must be allocated and mech->cm_params point to space large enough597* to hold the parameters for the given algorithm.598*599* decrypt is true if setting up for decryption, false for encryption.600*/601static void602init_mech(crypto_mechanism_t *mech, crypto_test_alg_t alg,603uint8_t *iv, size_t ivlen,604uint8_t *aad, size_t aadlen,605size_t msglen, size_t taglen,606boolean_t decrypt)607{608switch (alg) {609case ALG_AES_GCM: {610mech->cm_type = crypto_mech2id(SUN_CKM_AES_GCM);611mech->cm_param_len = sizeof (CK_AES_GCM_PARAMS);612CK_AES_GCM_PARAMS *p = (CK_AES_GCM_PARAMS *)mech->cm_param;613p->pIv = (uchar_t *)iv;614p->ulIvLen = ivlen;615p->ulIvBits = ivlen << 3;616p->pAAD = aad;617p->ulAADLen = aadlen;618p->ulTagBits = taglen << 3;619break;620}621case ALG_AES_CCM: {622mech->cm_type = crypto_mech2id(SUN_CKM_AES_CCM);623mech->cm_param_len = sizeof (CK_AES_CCM_PARAMS);624CK_AES_CCM_PARAMS *p = (CK_AES_CCM_PARAMS *)mech->cm_param;625p->nonce = iv;626p->ulNonceSize = ivlen;627p->authData = aad;628p->ulAuthDataSize = aadlen;629p->ulMACSize = taglen;630/*631* ICP CCM needs the MAC len in the data size for decrypt,632* even if the buffer isn't that big.633*/634p->ulDataSize = msglen + (decrypt ? taglen : 0);635break;636}637default:638abort();639}640}641642/*643* call crypto_encrypt() with the given inputs.644*645* mech: previously initialised by init_mech646* key, keylen: raw data and length of key647* msg, msglen: raw data and length of message648* out, outlen: buffer to write output to (min msglen+taglen)649* usecp: if not NULL, recieves microseconds in crypto_encrypt()650*/651static int652encrypt_one(crypto_mechanism_t *mech,653const uint8_t *key, size_t keylen,654const uint8_t *msg, size_t msglen,655uint8_t *out, size_t outlen,656uint64_t *usecp)657{658crypto_key_t k = {659.ck_data = (uint8_t *)key,660.ck_length = keylen << 3,661};662663crypto_data_t i = {664.cd_format = CRYPTO_DATA_RAW,665.cd_offset = 0,666.cd_length = msglen,667.cd_raw = {668.iov_base = (char *)msg,669.iov_len = msglen,670},671};672673crypto_data_t o = {674.cd_format = CRYPTO_DATA_RAW,675.cd_offset = 0,676.cd_length = outlen,677.cd_raw = {678.iov_base = (char *)out,679.iov_len = outlen,680},681};682683struct timeval start, end, diff;684if (usecp != NULL)685gettimeofday(&start, NULL);686687int rv = crypto_encrypt(mech, &i, &k, NULL, &o);688689if (usecp != NULL) {690gettimeofday(&end, NULL);691timersub(&end, &start, &diff);692*usecp =693((uint64_t)diff.tv_sec) * 1000000 + (uint64_t)diff.tv_usec;694}695696return (rv);697}698699/*700* call crypto_decrypt() with the given inputs.701*702* mech: previously initialised by init_mech703* key, keylen: raw data and length of key704* ct, ctlen: raw data and length of ciphertext705* tag, taglen: raw data and length of tag (MAC)706* out, outlen: buffer to write output to (min ctlen)707* usecp: if not NULL, recieves microseconds in crypto_decrypt()708*/709static int710decrypt_one(crypto_mechanism_t *mech,711const uint8_t *key, size_t keylen,712const uint8_t *ct, size_t ctlen,713const uint8_t *tag, size_t taglen,714uint8_t *out, size_t outlen,715uint64_t *usecp)716{717uint8_t inbuf[1024];718719crypto_key_t k = {720.ck_data = (uint8_t *)key,721.ck_length = keylen << 3,722};723724memcpy(inbuf, ct, ctlen);725memcpy(inbuf + ctlen, tag, taglen);726crypto_data_t i = {727.cd_format = CRYPTO_DATA_RAW,728.cd_offset = 0,729.cd_length = ctlen + taglen,730.cd_raw = {731.iov_base = (char *)inbuf,732.iov_len = ctlen + taglen,733},734};735736crypto_data_t o = {737.cd_format = CRYPTO_DATA_RAW,738.cd_offset = 0,739.cd_length = outlen,740.cd_raw = {741.iov_base = (char *)out,742.iov_len = outlen743},744};745746struct timeval start, end, diff;747if (usecp != NULL)748gettimeofday(&start, NULL);749750int rv = crypto_decrypt(mech, &i, &k, NULL, &o);751752if (usecp != NULL) {753gettimeofday(&end, NULL);754timersub(&start, &end, &diff);755*usecp =756((uint64_t)diff.tv_sec) * 1000000 + (uint64_t)diff.tv_usec;757}758759return (rv);760}761762/* ========== */763764/* correctness tests */765766/*767* helper; dump the provided data as hex, with a string prefix768*/769static void770hexdump(const char *str, const uint8_t *src, uint_t len)771{772printf("%12s:", str);773int i = 0;774while (i < len) {775if (i % 4 == 0)776printf(" ");777printf("%02x", src[i]);778i++;779if (i % 16 == 0 && i < len) {780printf("\n");781if (i < len)782printf(" ");783}784}785printf("\n");786}787788/*789* analyse test result and on failure, print useful output for debugging.790*791* test: the test we ran792* encrypt_rv: return value from crypto_encrypt()793* encrypt_buf: the output buffer from crypto_encrypt()794* decrypt_rv: return value from crypto_decrypt()795* decrypt_buf: the output buffer from crypto_decrypt()796* outmode: output mode (summary, fail, all)797*/798static boolean_t799test_result(const crypto_test_t *test, int encrypt_rv, uint8_t *encrypt_buf,800int decrypt_rv, uint8_t *decrypt_buf, crypto_test_outmode_t outmode)801{802boolean_t ct_match = B_FALSE, tag_match = B_FALSE, msg_match = B_FALSE;803boolean_t encrypt_pass = B_FALSE, decrypt_pass = B_FALSE;804boolean_t pass = B_FALSE;805806/* check if the encrypt output matches the expected ciphertext */807if (memcmp(encrypt_buf, test->ct.val, test->msg.len) == 0)808ct_match = B_TRUE;809810/*811* check if the tag at the end of the encrypt output matches the812* expected tag813*/814if (memcmp(encrypt_buf + test->msg.len, test->tag.val,815test->tag.len) == 0)816tag_match = B_TRUE;817818/* check if the decrypt output matches the expected plaintext */819if (memcmp(decrypt_buf, test->msg.val, test->msg.len) == 0)820msg_match = B_TRUE;821822if (test->result == RS_VALID) {823/*824* a "valid" test is where the params describe an825* encrypt/decrypt cycle that should succeed. we consider826* these to have passed the test if crypto_encrypt() and827* crypto_decrypt() return success, and the output data828* matches the expected values from the test params.829*/830if (encrypt_rv == CRYPTO_SUCCESS) {831if (ct_match && tag_match)832encrypt_pass = B_TRUE;833}834if (decrypt_rv == CRYPTO_SUCCESS) {835if (msg_match)836decrypt_pass = B_TRUE;837}838} else {839/*840* an "invalid" test is where the params describe an841* encrypt/decrypt cycle that should _not_ succeed.842*843* for decrypt, we only need to check the result from844* crypto_decrypt(), because decrypt checks the the tag (MAC)845* as part of its operation.846*847* for encrypt, the tag (MAC) is an output of the encryption848* function, so if encryption succeeds, we have to check that849* the returned tag matches the expected tag.850*/851if (encrypt_rv != CRYPTO_SUCCESS || !tag_match)852encrypt_pass = B_TRUE;853if (decrypt_rv != CRYPTO_SUCCESS)854decrypt_pass = B_TRUE;855}856857/* the test as a whole passed if both encrypt and decrypt passed */858pass = (encrypt_pass && decrypt_pass);859860/* if the test passed we may not have to output anything */861if (outmode == OUT_SUMMARY || (outmode == OUT_FAIL && pass))862return (pass);863864/* print summary of test result */865printf("%s[%ju]: encrypt=%s decrypt=%s\n", test->fileloc,866(uintmax_t)test->id,867encrypt_pass ? "PASS" : "FAIL",868decrypt_pass ? "PASS" : "FAIL");869870if (!pass) {871/*872* if the test didn't pass, print any comment or flags field873* from the test params, which if present can help874* understanding what the ICP did wrong875*/876if (test->comment != NULL)877printf(" comment: %s\n", test->comment);878if (test->flags != NULL)879printf(" flags: %s\n", test->flags);880}881882if (!encrypt_pass) {883/* encrypt failed */884885/* print return value from crypto_encrypt() */886printf(" encrypt rv = 0x%02x [%s]\n", encrypt_rv,887crypto_errstr[encrypt_rv] ?888crypto_errstr[encrypt_rv] : "???");889890/* print mismatched ciphertext */891if (!ct_match) {892printf(" ciphertexts don't match:\n");893hexdump("got", encrypt_buf, test->msg.len);894hexdump("expected", test->ct.val, test->msg.len);895}896897/* print mistmatched tag (MAC) */898if (!tag_match) {899printf(" tags don't match:\n");900hexdump("got", encrypt_buf + test->msg.len,901test->tag.len);902hexdump("expected", test->tag.val, test->tag.len);903}904}905906if (!decrypt_pass) {907/* decrypt failed */908909/* print return value from crypto_decrypt() */910printf(" decrypt rv = 0x%02x [%s]\n", decrypt_rv,911crypto_errstr[decrypt_rv] ?912crypto_errstr[decrypt_rv] : "???");913914/* print mismatched plaintext */915if (!msg_match) {916printf(" plaintexts don't match:\n");917hexdump("got", decrypt_buf, test->msg.len);918hexdump("expected", test->msg.val, test->msg.len);919}920}921922if (!pass)923printf("\n");924925return (pass);926}927928/*929* run the given list of tests.930*931* alginfo: a prefix for the test summary, showing the ICP algo implementation932* in use for this run.933* tests: first test in test list934* outmode: output mode, passed to test_result()935*/936static int937run_tests(const char *alginfo, const crypto_test_t *tests,938crypto_test_outmode_t outmode)939{940int ntests = 0, npass = 0;941942/*943* allocate space for the mechanism description, and alg-specific944* params, and hook them up.945*/946crypto_mechanism_t mech = {};947union {948CK_AES_GCM_PARAMS gcm;949CK_AES_CCM_PARAMS ccm;950} params = {};951mech.cm_param = (caddr_t)¶ms;952953/* space for encrypt/decrypt output */954uint8_t encrypt_buf[1024];955uint8_t decrypt_buf[1024];956957for (const crypto_test_t *test = tests; test != NULL;958test = test->next) {959ntests++;960961/* setup mechanism description for encrypt, then encrypt */962init_mech(&mech, test->alg, test->iv.val, test->iv.len,963test->aad.val, test->aad.len, test->msg.len, test->tag.len,964B_FALSE);965int encrypt_rv = encrypt_one(&mech,966test->key.val, test->key.len,967test->msg.val, test->msg.len,968encrypt_buf, test->msg.len + test->tag.len, NULL);969970/* setup mechanism description for decrypt, then decrypt */971init_mech(&mech, test->alg, test->iv.val, test->iv.len,972test->aad.val, test->aad.len, test->msg.len, test->tag.len,973B_TRUE);974int decrypt_rv = decrypt_one(&mech,975test->key.val, test->key.len,976test->ct.val, test->ct.len,977test->tag.val, test->tag.len,978decrypt_buf, test->ct.len, NULL);979980/* consider results and if it passed, count it */981if (test_result(test, encrypt_rv, encrypt_buf,982decrypt_rv, decrypt_buf, outmode))983npass++;984}985986printf("%s: tests=%d: passed=%d failed=%d\n",987alginfo, ntests, npass, ntests-npass);988989return (ntests != npass);990}991992/* args for run_test_alg_cb */993typedef struct {994crypto_test_t *tests;995crypto_test_outmode_t outmode;996int failed;997} run_test_alg_args_t;998999/* per-alg-impl function for correctness test runs */1000static void1001run_test_alg_cb(const char *alginfo, void *arg)1002{1003if (arg == NULL) {1004printf("%s: [not supported on this platform]\n", alginfo);1005return;1006}1007run_test_alg_args_t *args = arg;1008args->failed += run_tests(alginfo, args->tests, args->outmode);1009}10101011/* main function for correctness tests */1012static int1013runtests_main(const char *filename, crypto_test_outmode_t outmode)1014{1015crypto_test_alg_t alg = ALG_NONE;1016crypto_test_t *tests = load_tests(filename, &alg);1017if (tests == NULL)1018return (1);10191020icp_init();10211022run_test_alg_args_t args = {1023.tests = tests,1024.outmode = outmode,1025.failed = 0,1026};10271028switch (alg) {1029case ALG_AES_CCM:1030foreach_aes_ccm(run_test_alg_cb, &args, outmode);1031break;1032case ALG_AES_GCM:1033foreach_aes_gcm(run_test_alg_cb, &args, outmode);1034break;1035default:1036abort();1037}10381039icp_fini();10401041return (args.failed);1042}10431044/* ========== */10451046/* performance tests */10471048/* helper; fill the given buffer with random data */1049static int1050fill_random(uint8_t *v, size_t sz)1051{1052int fd = open("/dev/urandom", O_RDONLY);1053if (fd < 0)1054return (errno);10551056while (sz > 0) {1057ssize_t r = read(fd, v, sz);1058if (r < 0) {1059close(fd);1060return (errno);1061}1062v += r;1063sz -= r;1064}10651066close(fd);10671068return (0);1069}10701071/* args for perf_alg_cb */1072typedef struct {1073crypto_test_alg_t alg;1074uint8_t *msg;1075uint8_t *out;1076uint8_t key[32];1077uint8_t iv[12];1078} perf_alg_args_t;10791080#define PERF_MSG_SHIFT_MIN (10) /* min test size 2^10 == 1K */1081#define PERF_MSG_SHIFT_MAX (19) /* max test size 2^19 == 512K */1082#define PERF_ROUNDS (1000) /* 1000 rounds per test */10831084/* per-alg-impl function for performance test runs */1085static void1086perf_alg_cb(const char *alginfo, void *arg)1087{1088char buf[10];1089printf("%-28s", alginfo);10901091if (arg == NULL) {1092printf("[not supported on this platform]\n");1093return;1094}10951096perf_alg_args_t *args = arg;10971098/* space for mechanism description */1099crypto_mechanism_t mech = {};1100union {1101CK_AES_GCM_PARAMS gcm;1102CK_AES_CCM_PARAMS ccm;1103} params = {};1104mech.cm_param = (caddr_t)¶ms;11051106/* loop for each power-2 input size */1107for (int i = PERF_MSG_SHIFT_MIN; i <= PERF_MSG_SHIFT_MAX; i++) {1108/* size of input */1109size_t sz = 1<<i;11101111/* initialise mechanism */1112init_mech(&mech, args->alg, args->iv, sizeof (args->iv),1113val_empty, 0, sz, 16, B_FALSE);11141115/* run N rounds and accumulate total time */1116uint64_t total = 0;1117for (int round = 0; round < PERF_ROUNDS; round++) {1118uint64_t usec;1119encrypt_one(&mech, args->key, sizeof (args->key),1120args->msg, sz, args->out, sz+16, &usec);1121total += usec;1122}11231124/*1125* print avg time per round. zfs_nicetime expects nanoseconds,1126* so we multiply first1127*/1128zfs_nicetime((total*1000)/PERF_ROUNDS, buf, sizeof (buf));1129printf(" %5s", buf);1130}11311132printf("\n");1133}11341135/* main function for performance tests */1136static int1137perf_main(const char *algname, crypto_test_outmode_t outmode)1138{1139perf_alg_args_t args;11401141if (strcmp(algname, "AES-CCM") == 0)1142args.alg = ALG_AES_CCM;1143else if (strcmp(algname, "AES-GCM") == 0)1144args.alg = ALG_AES_GCM;1145else {1146fprintf(stderr, "E: unknown algorithm: %s\n", algname);1147return (1);1148}11491150/*1151* test runs are often slow, but the very first ones won't be. by1152* disabling buffering, we can display results immediately, and1153* the user quickly gets an idea of what to expect1154*/1155setvbuf(stdout, NULL, _IONBF, 0);11561157/* allocate random data for encrypt input */1158size_t maxsz = (1<<PERF_MSG_SHIFT_MAX);1159args.msg = malloc(maxsz);1160VERIFY0(fill_random(args.msg, maxsz));11611162/* allocate space for output, +16 bytes for tag */1163args.out = malloc(maxsz+16);11641165/* fill key and iv */1166VERIFY0(fill_random(args.key, sizeof (args.key)));1167VERIFY0(fill_random(args.iv, sizeof (args.iv)));11681169icp_init();11701171/* print header */1172char buf[10];1173printf("avg encrypt (%4d rounds) ", PERF_ROUNDS);1174for (int i = PERF_MSG_SHIFT_MIN; i <= PERF_MSG_SHIFT_MAX; i++) {1175zfs_nicebytes(1<<i, buf, sizeof (buf));1176printf(" %5s", buf);1177}1178printf("\n");11791180/* loop over all implementations of the wanted algorithm */1181switch (args.alg) {1182case ALG_AES_CCM:1183foreach_aes_ccm(perf_alg_cb, &args, outmode);1184break;1185case ALG_AES_GCM:1186foreach_aes_gcm(perf_alg_cb, &args, outmode);1187break;1188default:1189abort();1190}11911192icp_fini();11931194return (0);1195}11961197/* ========== */11981199/* main entry */12001201static void1202usage(void)1203{1204fprintf(stderr,1205"usage: crypto_test [-v] < -c <testfile> | -p <alg> >\n");1206exit(1);1207}12081209int1210main(int argc, char **argv)1211{1212crypto_test_outmode_t outmode = OUT_SUMMARY;1213const char *filename = NULL;1214const char *algname = NULL;12151216int c;1217while ((c = getopt(argc, argv, "c:p:v")) != -1) {1218switch (c) {1219case 'c':1220filename = optarg;1221break;1222case 'p':1223algname = optarg;1224break;1225case 'v':1226outmode = (outmode == OUT_SUMMARY) ? OUT_FAIL : OUT_ALL;1227break;1228case '?':1229usage();1230}1231}12321233argc -= optind;1234argv += optind;12351236if (filename != NULL && algname != NULL) {1237fprintf(stderr, "E: can't use -c and -p together\n");1238usage();1239}12401241if (argc != 0)1242usage();12431244if (filename)1245return (runtests_main(filename, outmode));12461247return (perf_main(algname, outmode));1248}124912501251