| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| |
| #include "pk11pub.h" |
| #include "secerr.h" |
| #include "nss.h" |
| |
| static SECStatus |
| hex_to_byteval(const char *c2, unsigned char *byteval) |
| { |
| int i; |
| unsigned char offset; |
| *byteval = 0; |
| for (i = 0; i < 2; i++) { |
| if (c2[i] >= '0' && c2[i] <= '9') { |
| offset = c2[i] - '0'; |
| *byteval |= offset << 4 * (1 - i); |
| } else if (c2[i] >= 'a' && c2[i] <= 'f') { |
| offset = c2[i] - 'a'; |
| *byteval |= (offset + 10) << 4 * (1 - i); |
| } else if (c2[i] >= 'A' && c2[i] <= 'F') { |
| offset = c2[i] - 'A'; |
| *byteval |= (offset + 10) << 4 * (1 - i); |
| } else { |
| return SECFailure; |
| } |
| } |
| return SECSuccess; |
| } |
| |
| static SECStatus |
| aes_encrypt_buf( |
| const unsigned char *key, unsigned int keysize, |
| const unsigned char *iv, unsigned int ivsize, |
| unsigned char *output, unsigned int *outputlen, unsigned int maxoutputlen, |
| const unsigned char *input, unsigned int inputlen, |
| const unsigned char *aad, unsigned int aadlen, unsigned int tagsize) |
| { |
| SECStatus rv = SECFailure; |
| SECItem key_item; |
| PK11SlotInfo *slot = NULL; |
| PK11SymKey *symKey = NULL; |
| CK_GCM_PARAMS gcm_params; |
| SECItem param; |
| |
| /* Import key into NSS. */ |
| key_item.type = siBuffer; |
| key_item.data = (unsigned char *)key; /* const cast */ |
| key_item.len = keysize; |
| slot = PK11_GetInternalSlot(); |
| symKey = PK11_ImportSymKey(slot, CKM_AES_GCM, PK11_OriginUnwrap, |
| CKA_ENCRYPT, &key_item, NULL); |
| PK11_FreeSlot(slot); |
| slot = NULL; |
| if (!symKey) { |
| fprintf(stderr, "PK11_ImportSymKey failed\n"); |
| goto loser; |
| } |
| |
| gcm_params.pIv = (unsigned char *)iv; /* const cast */ |
| gcm_params.ulIvLen = ivsize; |
| gcm_params.pAAD = (unsigned char *)aad; /* const cast */ |
| gcm_params.ulAADLen = aadlen; |
| gcm_params.ulTagBits = tagsize * 8; |
| |
| param.type = siBuffer; |
| param.data = (unsigned char *)&gcm_params; |
| param.len = sizeof(gcm_params); |
| |
| if (PK11_Encrypt(symKey, CKM_AES_GCM, ¶m, |
| output, outputlen, maxoutputlen, |
| input, inputlen) != SECSuccess) { |
| fprintf(stderr, "PK11_Encrypt failed\n"); |
| goto loser; |
| } |
| |
| rv = SECSuccess; |
| |
| loser: |
| if (symKey != NULL) { |
| PK11_FreeSymKey(symKey); |
| } |
| return rv; |
| } |
| |
| static SECStatus |
| aes_decrypt_buf( |
| const unsigned char *key, unsigned int keysize, |
| const unsigned char *iv, unsigned int ivsize, |
| unsigned char *output, unsigned int *outputlen, unsigned int maxoutputlen, |
| const unsigned char *input, unsigned int inputlen, |
| const unsigned char *aad, unsigned int aadlen, |
| const unsigned char *tag, unsigned int tagsize) |
| { |
| SECStatus rv = SECFailure; |
| unsigned char concatenated[11 * 16]; /* 1 to 11 blocks */ |
| SECItem key_item; |
| PK11SlotInfo *slot = NULL; |
| PK11SymKey *symKey = NULL; |
| CK_GCM_PARAMS gcm_params; |
| SECItem param; |
| |
| if (inputlen + tagsize > sizeof(concatenated)) { |
| fprintf(stderr, "aes_decrypt_buf: local buffer too small\n"); |
| goto loser; |
| } |
| memcpy(concatenated, input, inputlen); |
| memcpy(concatenated + inputlen, tag, tagsize); |
| |
| /* Import key into NSS. */ |
| key_item.type = siBuffer; |
| key_item.data = (unsigned char *)key; /* const cast */ |
| key_item.len = keysize; |
| slot = PK11_GetInternalSlot(); |
| symKey = PK11_ImportSymKey(slot, CKM_AES_GCM, PK11_OriginUnwrap, |
| CKA_DECRYPT, &key_item, NULL); |
| PK11_FreeSlot(slot); |
| slot = NULL; |
| if (!symKey) { |
| fprintf(stderr, "PK11_ImportSymKey failed\n"); |
| goto loser; |
| } |
| |
| gcm_params.pIv = (unsigned char *)iv; |
| gcm_params.ulIvLen = ivsize; |
| gcm_params.pAAD = (unsigned char *)aad; |
| gcm_params.ulAADLen = aadlen; |
| gcm_params.ulTagBits = tagsize * 8; |
| |
| param.type = siBuffer; |
| param.data = (unsigned char *)&gcm_params; |
| param.len = sizeof(gcm_params); |
| |
| if (PK11_Decrypt(symKey, CKM_AES_GCM, ¶m, |
| output, outputlen, maxoutputlen, |
| concatenated, inputlen + tagsize) != SECSuccess) { |
| goto loser; |
| } |
| |
| rv = SECSuccess; |
| |
| loser: |
| if (symKey != NULL) { |
| PK11_FreeSymKey(symKey); |
| } |
| return rv; |
| } |
| |
| /* |
| * Perform the AES Known Answer Test (KAT) in Galois Counter Mode (GCM). |
| * |
| * respfn is the pathname of the RESPONSE file. |
| */ |
| static void |
| aes_gcm_kat(const char *respfn) |
| { |
| char buf[512]; /* holds one line from the input REQUEST file. |
| * needs to be large enough to hold the longest |
| * line "CIPHERTEXT = <320 hex digits>\n". |
| */ |
| FILE *aesresp; /* input stream from the RESPONSE file */ |
| int i, j; |
| unsigned int test_group = 0; |
| unsigned int num_tests = 0; |
| PRBool is_encrypt; |
| unsigned char key[32]; /* 128, 192, or 256 bits */ |
| unsigned int keysize = 16; |
| unsigned char iv[10 * 16]; /* 1 to 10 blocks */ |
| unsigned int ivsize = 12; |
| unsigned char plaintext[10 * 16]; /* 1 to 10 blocks */ |
| unsigned int plaintextlen = 0; |
| unsigned char aad[10 * 16]; /* 1 to 10 blocks */ |
| unsigned int aadlen = 0; |
| unsigned char ciphertext[10 * 16]; /* 1 to 10 blocks */ |
| unsigned int ciphertextlen = 0; |
| unsigned char tag[16]; |
| unsigned int tagsize = 16; |
| unsigned char output[10 * 16]; /* 1 to 10 blocks */ |
| unsigned int outputlen = 0; |
| |
| unsigned int expected_keylen = 0; |
| unsigned int expected_ivlen = 0; |
| unsigned int expected_ptlen = 0; |
| unsigned int expected_aadlen = 0; |
| unsigned int expected_taglen = 0; |
| SECStatus rv; |
| |
| if (strstr(respfn, "Encrypt") != NULL) { |
| is_encrypt = PR_TRUE; |
| } else if (strstr(respfn, "Decrypt") != NULL) { |
| is_encrypt = PR_FALSE; |
| } else { |
| fprintf(stderr, "Input file name must contain Encrypt or Decrypt\n"); |
| exit(1); |
| } |
| aesresp = fopen(respfn, "r"); |
| if (aesresp == NULL) { |
| fprintf(stderr, "Cannot open input file %s\n", respfn); |
| exit(1); |
| } |
| while (fgets(buf, sizeof buf, aesresp) != NULL) { |
| /* a comment or blank line */ |
| if (buf[0] == '#' || buf[0] == '\n') { |
| continue; |
| } |
| /* [Keylen = ...], [IVlen = ...], etc. */ |
| if (buf[0] == '[') { |
| if (strncmp(&buf[1], "Keylen = ", 9) == 0) { |
| expected_keylen = atoi(&buf[10]); |
| } else if (strncmp(&buf[1], "IVlen = ", 8) == 0) { |
| expected_ivlen = atoi(&buf[9]); |
| } else if (strncmp(&buf[1], "PTlen = ", 8) == 0) { |
| expected_ptlen = atoi(&buf[9]); |
| } else if (strncmp(&buf[1], "AADlen = ", 9) == 0) { |
| expected_aadlen = atoi(&buf[10]); |
| } else if (strncmp(&buf[1], "Taglen = ", 9) == 0) { |
| expected_taglen = atoi(&buf[10]); |
| |
| test_group++; |
| if (test_group > 1) { |
| /* Report num_tests for the previous test group. */ |
| printf("%u tests\n", num_tests); |
| } |
| num_tests = 0; |
| printf("Keylen = %u, IVlen = %u, PTlen = %u, AADlen = %u, " |
| "Taglen = %u: ", |
| expected_keylen, expected_ivlen, |
| expected_ptlen, expected_aadlen, expected_taglen); |
| /* Convert lengths in bits to lengths in bytes. */ |
| PORT_Assert(expected_keylen % 8 == 0); |
| expected_keylen /= 8; |
| PORT_Assert(expected_ivlen % 8 == 0); |
| expected_ivlen /= 8; |
| PORT_Assert(expected_ptlen % 8 == 0); |
| expected_ptlen /= 8; |
| PORT_Assert(expected_aadlen % 8 == 0); |
| expected_aadlen /= 8; |
| PORT_Assert(expected_taglen % 8 == 0); |
| expected_taglen /= 8; |
| } else { |
| fprintf(stderr, "Unexpected input line: %s\n", buf); |
| exit(1); |
| } |
| continue; |
| } |
| /* "Count = x" begins a new data set */ |
| if (strncmp(buf, "Count", 5) == 0) { |
| /* zeroize the variables for the test with this data set */ |
| memset(key, 0, sizeof key); |
| keysize = 0; |
| memset(iv, 0, sizeof iv); |
| ivsize = 0; |
| memset(plaintext, 0, sizeof plaintext); |
| plaintextlen = 0; |
| memset(aad, 0, sizeof aad); |
| aadlen = 0; |
| memset(ciphertext, 0, sizeof ciphertext); |
| ciphertextlen = 0; |
| memset(output, 0, sizeof output); |
| outputlen = 0; |
| num_tests++; |
| continue; |
| } |
| /* Key = ... */ |
| if (strncmp(buf, "Key", 3) == 0) { |
| i = 3; |
| while (isspace(buf[i]) || buf[i] == '=') { |
| i++; |
| } |
| for (j = 0; isxdigit(buf[i]); i += 2, j++) { |
| hex_to_byteval(&buf[i], &key[j]); |
| } |
| keysize = j; |
| if (keysize != expected_keylen) { |
| fprintf(stderr, "Unexpected key length: %u vs. %u\n", |
| keysize, expected_keylen); |
| exit(1); |
| } |
| continue; |
| } |
| /* IV = ... */ |
| if (strncmp(buf, "IV", 2) == 0) { |
| i = 2; |
| while (isspace(buf[i]) || buf[i] == '=') { |
| i++; |
| } |
| for (j = 0; isxdigit(buf[i]); i += 2, j++) { |
| hex_to_byteval(&buf[i], &iv[j]); |
| } |
| ivsize = j; |
| if (ivsize != expected_ivlen) { |
| fprintf(stderr, "Unexpected IV length: %u vs. %u\n", |
| ivsize, expected_ivlen); |
| exit(1); |
| } |
| continue; |
| } |
| /* PT = ... */ |
| if (strncmp(buf, "PT", 2) == 0) { |
| i = 2; |
| while (isspace(buf[i]) || buf[i] == '=') { |
| i++; |
| } |
| for (j = 0; isxdigit(buf[i]); i += 2, j++) { |
| hex_to_byteval(&buf[i], &plaintext[j]); |
| } |
| plaintextlen = j; |
| if (plaintextlen != expected_ptlen) { |
| fprintf(stderr, "Unexpected PT length: %u vs. %u\n", |
| plaintextlen, expected_ptlen); |
| exit(1); |
| } |
| |
| if (!is_encrypt) { |
| rv = aes_decrypt_buf(key, keysize, iv, ivsize, |
| output, &outputlen, sizeof output, |
| ciphertext, ciphertextlen, aad, aadlen, tag, tagsize); |
| if (rv != SECSuccess) { |
| fprintf(stderr, "aes_decrypt_buf failed\n"); |
| goto loser; |
| } |
| if (outputlen != plaintextlen) { |
| fprintf(stderr, "aes_decrypt_buf: wrong output size\n"); |
| goto loser; |
| } |
| if (memcmp(output, plaintext, plaintextlen) != 0) { |
| fprintf(stderr, "aes_decrypt_buf: wrong plaintext\n"); |
| goto loser; |
| } |
| } |
| continue; |
| } |
| /* FAIL */ |
| if (strncmp(buf, "FAIL", 4) == 0) { |
| plaintextlen = 0; |
| |
| PORT_Assert(!is_encrypt); |
| rv = aes_decrypt_buf(key, keysize, iv, ivsize, |
| output, &outputlen, sizeof output, |
| ciphertext, ciphertextlen, aad, aadlen, tag, tagsize); |
| if (rv != SECFailure) { |
| fprintf(stderr, "aes_decrypt_buf succeeded unexpectedly\n"); |
| goto loser; |
| } |
| if (PORT_GetError() != SEC_ERROR_BAD_DATA) { |
| fprintf(stderr, "aes_decrypt_buf failed with incorrect " |
| "error code\n"); |
| goto loser; |
| } |
| continue; |
| } |
| /* AAD = ... */ |
| if (strncmp(buf, "AAD", 3) == 0) { |
| i = 3; |
| while (isspace(buf[i]) || buf[i] == '=') { |
| i++; |
| } |
| for (j = 0; isxdigit(buf[i]); i += 2, j++) { |
| hex_to_byteval(&buf[i], &aad[j]); |
| } |
| aadlen = j; |
| if (aadlen != expected_aadlen) { |
| fprintf(stderr, "Unexpected AAD length: %u vs. %u\n", |
| aadlen, expected_aadlen); |
| exit(1); |
| } |
| continue; |
| } |
| /* CT = ... */ |
| if (strncmp(buf, "CT", 2) == 0) { |
| i = 2; |
| while (isspace(buf[i]) || buf[i] == '=') { |
| i++; |
| } |
| for (j = 0; isxdigit(buf[i]); i += 2, j++) { |
| hex_to_byteval(&buf[i], &ciphertext[j]); |
| } |
| ciphertextlen = j; |
| if (ciphertextlen != expected_ptlen) { |
| fprintf(stderr, "Unexpected CT length: %u vs. %u\n", |
| ciphertextlen, expected_ptlen); |
| exit(1); |
| } |
| continue; |
| } |
| /* Tag = ... */ |
| if (strncmp(buf, "Tag", 3) == 0) { |
| i = 3; |
| while (isspace(buf[i]) || buf[i] == '=') { |
| i++; |
| } |
| for (j = 0; isxdigit(buf[i]); i += 2, j++) { |
| hex_to_byteval(&buf[i], &tag[j]); |
| } |
| tagsize = j; |
| if (tagsize != expected_taglen) { |
| fprintf(stderr, "Unexpected tag length: %u vs. %u\n", |
| tagsize, expected_taglen); |
| exit(1); |
| } |
| |
| if (is_encrypt) { |
| rv = aes_encrypt_buf(key, keysize, iv, ivsize, |
| output, &outputlen, sizeof output, |
| plaintext, plaintextlen, aad, aadlen, tagsize); |
| if (rv != SECSuccess) { |
| fprintf(stderr, "aes_encrypt_buf failed\n"); |
| goto loser; |
| } |
| if (outputlen != plaintextlen + tagsize) { |
| fprintf(stderr, "aes_encrypt_buf: wrong output size\n"); |
| goto loser; |
| } |
| if (memcmp(output, ciphertext, plaintextlen) != 0) { |
| fprintf(stderr, "aes_encrypt_buf: wrong ciphertext\n"); |
| goto loser; |
| } |
| if (memcmp(output + plaintextlen, tag, tagsize) != 0) { |
| fprintf(stderr, "aes_encrypt_buf: wrong tag\n"); |
| goto loser; |
| } |
| } |
| continue; |
| } |
| } |
| /* Report num_tests for the last test group. */ |
| printf("%u tests\n", num_tests); |
| printf("%u test groups\n", test_group); |
| printf("PASS\n"); |
| loser: |
| fclose(aesresp); |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| if (argc < 2) |
| exit(1); |
| |
| NSS_NoDB_Init(NULL); |
| |
| /*************/ |
| /* AES */ |
| /*************/ |
| if (strcmp(argv[1], "aes") == 0) { |
| /* argv[2]=kat argv[3]=gcm argv[4]=<test name>.rsp */ |
| if (strcmp(argv[2], "kat") == 0) { |
| /* Known Answer Test (KAT) */ |
| aes_gcm_kat(argv[4]); |
| } |
| } |
| |
| NSS_Shutdown(); |
| return 0; |
| } |