diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index d7c1bafe6b4ca0..3fd0aede54f001 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -1063,6 +1063,7 @@ VendorName vendorpayload venv Verifier +Verifiers VID visualstudio vlatest diff --git a/BUILD.gn b/BUILD.gn index 0fe2b8cba7a38f..9598963d7447d0 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -112,6 +112,7 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { "${chip_root}/src/messaging/tests/echo:chip-echo-responder", "${chip_root}/src/qrcodetool", "${chip_root}/src/setup_payload", + "${chip_root}/src/tools/spake2p", ] if (chip_crypto == "openssl") { deps += [ "${chip_root}/src/tools/chip-cert" ] diff --git a/src/protocols/secure_channel/PASESession.cpp b/src/protocols/secure_channel/PASESession.cpp index 473420097f27f5..9d46995548e165 100644 --- a/src/protocols/secure_channel/PASESession.cpp +++ b/src/protocols/secure_channel/PASESession.cpp @@ -237,7 +237,7 @@ CHIP_ERROR PASESession::GeneratePASEVerifier(PASEVerifier & verifier, uint32_t p ReturnErrorOnFailure(DRBG_get_bytes(reinterpret_cast(&setupPIN), sizeof(setupPIN))); // Passcodes shall be restricted to the values 00000001 to 99999998 in decimal, see 5.1.1.6 - setupPIN = (setupPIN % 99999998) + 1; + setupPIN = (setupPIN % kSetupPINCodeMaximumValue) + 1; } return PASESession::ComputePASEVerifier(setupPIN, pbkdf2IterCount, salt, verifier); diff --git a/src/protocols/secure_channel/PASESession.h b/src/protocols/secure_channel/PASESession.h index 1d3b5b93290f60..f8229bd6bb2ad4 100644 --- a/src/protocols/secure_channel/PASESession.h +++ b/src/protocols/secure_channel/PASESession.h @@ -58,6 +58,9 @@ constexpr uint32_t kPBKDFMaximumIterations = 100000; constexpr uint32_t kPBKDFMinimumSaltLen = 16; constexpr uint32_t kPBKDFMaximumSaltLen = 32; +// Specifications section 5.1.1.6 +constexpr uint32_t kSetupPINCodeMaximumValue = 99999998; + using namespace Crypto; constexpr size_t kSpake2p_WS_Length = kP256_FE_Length + 8; diff --git a/src/tools/spake2p/BUILD.gn b/src/tools/spake2p/BUILD.gn new file mode 100644 index 00000000000000..c666da714b82b6 --- /dev/null +++ b/src/tools/spake2p/BUILD.gn @@ -0,0 +1,38 @@ +# Copyright (c) 2022 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") + +import("${chip_root}/build/chip/tools.gni") + +assert(chip_build_tools) + +executable("spake2p") { + sources = [ + "Cmd_GenVerifier.cpp", + "spake2p.cpp", + "spake2p.h", + ] + + cflags = [ "-Wconversion" ] + + public_deps = [ + "${chip_root}/src/crypto", + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/support", + "${chip_root}/src/protocols/secure_channel", + ] + + output_dir = root_out_dir +} diff --git a/src/tools/spake2p/Cmd_GenVerifier.cpp b/src/tools/spake2p/Cmd_GenVerifier.cpp new file mode 100644 index 00000000000000..8f1cd82f0280f6 --- /dev/null +++ b/src/tools/spake2p/Cmd_GenVerifier.cpp @@ -0,0 +1,313 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file implements the command handler for the 'spake2p' tool + * that generates Verifier. + * + */ + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include "spake2p.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace chip::ArgParser; + +#define CMD_NAME "spake2p gen-verifier" + +bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg); + +// clang-format off +OptionDef gCmdOptionDefs[] = +{ + { "count", kArgumentRequired, 'c' }, + { "pin-code", kArgumentRequired, 'p' }, + { "iteration-count", kArgumentRequired, 'i' }, + { "salt-len", kArgumentRequired, 'l' }, + { "salt", kArgumentRequired, 's' }, + { "out", kArgumentRequired, 'o' }, + { } +}; + +const char * const gCmdOptionHelp = + " -c, --count \n" + "\n" + " The number of pin-code/verifier parameter sets to be generated. If not specified,\n" + " one set will be generated.\n" + "\n" + " -p, --pin-code \n" + "\n" + " SPAKE2P setup PIN code. The value should be positive integer in range [1..99999998].\n" + " If not specified, the PIN code value will be randomly generated.\n" + " When count is more than one, only first set will use the specified PIN code value\n" + " and others will be randomly generated.\n" + " The following PIN codes SHALL NOT be used due to their trivial, insecure nature:\n" + " * 00000000\n" + " * 11111111\n" + " * 22222222\n" + " * 33333333\n" + " * 44444444\n" + " * 55555555\n" + " * 66666666\n" + " * 77777777\n" + " * 88888888\n" + " * 99999999\n" + " * 12345678\n" + " * 87654321\n" + "\n" + " -i, --iteration-count \n" + "\n" + " SPAKE2P PBKDF iteration count. The value should be positive integer in range [1000..100000].\n" + "\n" + " -l, --salt-len \n" + "\n" + " SPAKE2P PBKDF salt input length. The value should be in range [16..32].\n" + " If not specified, the 'salt' input should be specified and the length will be\n" + " extracted from 'salt'. When both 'salt-len' and 'salt' are specified, the length\n" + " should match the length of the specified 'salt' string.\n" + "\n" + " -s, --salt \n" + "\n" + " SPAKE2P PBKDF salt input value. Length of salt string should be in range [16..32].\n" + " If not specified, the 'salt-len' input should be specified and the 'salt' velue will be\n" + " randomly generated. When 'count' is more than one, only first set will use the specified\n" + " 'salt' value and others will be randomly generated.\n" + "\n" + " -o, --out \n" + "\n" + " File to contain the generated SPAKE2P PBKDF parameters. Specify '-' for stdout.\n" + " The format of the output file is:\n" + " Index,PIN Code,Iteration Count,Salt,Verifier\n" + " index of the parameter set in the list,'pin-code','iteration-count','salt'(Base-64 encoded),'verifier'(Base-64 encoded)\n" + " ....\n" + "\n" + ; + +OptionSet gCmdOptions = +{ + HandleOption, + gCmdOptionDefs, + "COMMAND OPTIONS", + gCmdOptionHelp +}; + +HelpOptions gHelpOptions( + CMD_NAME, + "Usage: " CMD_NAME " [ ]\n", + CHIP_VERSION_STRING "\n" COPYRIGHT_STRING, + "Generate a CHIP certificate" +); + +OptionSet *gCmdOptionSets[] = +{ + &gCmdOptions, + &gHelpOptions, + nullptr +}; +// clang-format on + +uint32_t gCount = 1; +uint32_t gPinCode = 0; +uint32_t gIterationCount = 0; +uint8_t gSaltLen = 0; +const char * gSalt = nullptr; +const char * gOutFileName = nullptr; + +bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) +{ + switch (id) + { + case 'c': + if (!ParseInt(arg, gCount) || gCount == 0) + { + PrintArgError("%s: Invalid value specified for parameter set count: %s\n", progName, arg); + return false; + } + break; + case 'p': + // Specifications sections 5.1.1.6 and 5.1.6.1 + if (!ParseInt(arg, gPinCode) || (gPinCode > chip::kSetupPINCodeMaximumValue) || (gPinCode == 0) || (gPinCode == 11111111) || + (gPinCode == 22222222) || (gPinCode == 33333333) || (gPinCode == 44444444) || (gPinCode == 55555555) || + (gPinCode == 66666666) || (gPinCode == 77777777) || (gPinCode == 88888888) || (gPinCode == 99999999) || + (gPinCode == 12345678) || (gPinCode == 87654321)) + { + PrintArgError("%s: Invalid value specified for pin-code parameter: %s\n", progName, arg); + return false; + } + break; + + case 'i': + if (!ParseInt(arg, gIterationCount) || + !(gIterationCount >= chip::kPBKDFMinimumIterations && gIterationCount <= chip::kPBKDFMaximumIterations)) + { + PrintArgError("%s: Invalid value specified for the iteration-count parameter: %s\n", progName, arg); + return false; + } + break; + + case 'l': + if (!ParseInt(arg, gSaltLen) || !(gSaltLen >= chip::kPBKDFMinimumSaltLen && gSaltLen <= chip::kPBKDFMaximumSaltLen)) + { + PrintArgError("%s: Invalid value specified for salt length parameter: %s\n", progName, arg); + return false; + } + break; + + case 's': + gSalt = arg; + if (!(strlen(gSalt) >= chip::kPBKDFMinimumSaltLen && strlen(gSalt) <= chip::kPBKDFMaximumSaltLen)) + { + fprintf(stderr, "%s: Invalid legth of the specified salt parameter: %s\n", progName, arg); + return false; + } + break; + + case 'o': + gOutFileName = arg; + break; + + default: + PrintArgError("%s: Unhandled option: %s\n", progName, name); + return false; + } + + return true; +} + +} // namespace + +bool Cmd_GenVerifier(int argc, char * argv[]) +{ + FILE * outFile = NULL; + + if (argc == 1) + { + gHelpOptions.PrintBriefUsage(stderr); + return true; + } + + bool res = ParseArgs(CMD_NAME, argc, argv, gCmdOptionSets); + VerifyOrReturnError(res, false); + + if (gIterationCount == 0) + { + fprintf(stderr, "Please specify the iteration-count parameter.\n"); + return false; + } + + if (gSalt == nullptr && gSaltLen == 0) + { + fprintf(stderr, "Please specify at least one of the 'salt' or 'salt-len' parameters.\n"); + return false; + } + else if (gSalt != nullptr && gSaltLen != 0 && gSaltLen != strlen(gSalt)) + { + fprintf(stderr, "The specified 'salt-len' doesn't match the length of 'salt' parameter.\n"); + return false; + } + else if (gSaltLen == 0) + { + gSaltLen = static_cast(strlen(gSalt)); + } + + if (gOutFileName == nullptr) + { + fprintf(stderr, "Please specify the output file name, or - for stdout.\n"); + return false; + } + + if (strcmp(gOutFileName, "-") != 0) + { + outFile = fopen(gOutFileName, "w+b"); + if (outFile == NULL) + { + fprintf(stderr, "Unable to create file %s\n%s\n", gOutFileName, strerror(errno)); + return false; + } + } + else + { + outFile = stdout; + } + + if (fprintf(outFile, "Index,PIN Code,Iteration Count,Salt,Verifier\n") < 0 || ferror(outFile)) + { + fprintf(stderr, "Error writing to output file: %s\n", strerror(errno)); + return false; + } + + for (uint32_t i = 0; i < gCount; i++) + { + uint8_t salt[chip::kPBKDFMaximumSaltLen]; + if (gSalt == nullptr) + { + CHIP_ERROR err = chip::Crypto::DRBG_get_bytes(salt, gSaltLen); + if (err != CHIP_NO_ERROR) + { + fprintf(stderr, "DRBG_get_bytes() failed.\n"); + return false; + } + } + else + { + memcpy(salt, gSalt, gSaltLen); + } + + chip::PASEVerifier verifier; + CHIP_ERROR err = chip::PASESession::GeneratePASEVerifier(verifier, gIterationCount, chip::ByteSpan(salt, gSaltLen), + (gPinCode == 0), gPinCode); + if (err != CHIP_NO_ERROR) + { + fprintf(stderr, "GeneratePASEVerifier() failed.\n"); + return false; + } + + char saltB64[BASE64_ENCODED_LEN(chip::kPBKDFMaximumSaltLen) + 1]; + uint32_t saltB64Len = chip::Base64Encode32(salt, gSaltLen, saltB64); + saltB64[saltB64Len] = 0; + + char verifierB64[BASE64_ENCODED_LEN(sizeof(chip::PASEVerifier)) + 1]; + uint32_t verifierB64Len = + chip::Base64Encode32(reinterpret_cast(&verifier), sizeof(chip::PASEVerifier), verifierB64); + verifierB64[verifierB64Len] = 0; + + if (fprintf(outFile, "%d,%08d,%d,%s,%s\n", i, gPinCode, gIterationCount, saltB64, verifierB64) < 0 || ferror(outFile)) + { + fprintf(stderr, "Error writing to output file: %s\n", strerror(errno)); + return false; + } + + // On the next iteration the PIN Code and Salt will be randomly generated. + gPinCode = 0; + gSalt = nullptr; + } + + return true; +} diff --git a/src/tools/spake2p/README.md b/src/tools/spake2p/README.md new file mode 100644 index 00000000000000..47eb56e0d59d80 --- /dev/null +++ b/src/tools/spake2p/README.md @@ -0,0 +1,33 @@ +# SPAKE2P Parameters Tool + +## Introduction + +spake2p tool provides command line interface (CLI) utility used for generating +spake parameters (PIN code and verifier) for device manufacturing provisioning. + +## Usage Examples + +Specify 'help' option for the detailed 'spake2p' tool usage instructions: + +``` +./spake2p help +``` + +Specify '--help' option for detail instructions on command usage: + +``` +./spake2p gen-verifier --help +``` + +Example command that generates spake2p verifier for a given PIN code: + +``` +./spake2p gen-verifier --pin-code 45502684 --iteration-count 1000 --salt "SPAKE2P Key Salt 1" --out spake2p-provisioning-data.csv +``` + +Example command that generates 100 sets of spake2p parameters (random PIN Codes, +random Salts and corresponding Verifiers): + +``` +./spake2p gen-verifier --count 100 --iteration-count 15000 --salt-len 32 --out spake2p-provisioning-data.csv +``` diff --git a/src/tools/spake2p/spake2p.cpp b/src/tools/spake2p/spake2p.cpp new file mode 100644 index 00000000000000..2f4890352add4e --- /dev/null +++ b/src/tools/spake2p/spake2p.cpp @@ -0,0 +1,93 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file implements the 'spake2p' command line tool. + */ + +#include "spake2p.h" + +namespace chip { +namespace Logging { +namespace Platform { + +void LogV(const char * module, uint8_t category, const char * msg, va_list v) {} + +} // namespace Platform +} // namespace Logging +} // namespace chip + +namespace { + +// clang-format off +const char * const sHelp = + "Usage: spake2p [ ]\n" + "\n" + "Commands:\n" + "\n" + " gen-verifier -- Generate SPAKE2P parameters.\n" + "\n" + " version -- Print the program version and exit.\n" + "\n"; +// clang-format on + +/** + * Print to standard output the program version information. + * + * @return Unconditionally returns true. + * + */ +bool PrintVersion(void) +{ + printf("chip " CHIP_VERSION_STRING "\n" COPYRIGHT_STRING); + + return true; +} + +} // namespace + +extern "C" int main(int argc, char * argv[]) +{ + bool res = false; + + chip::Platform::MemoryInit(); + + if (argc == 1) + { + fprintf(stderr, "Please specify a command, or 'help' for help.\n"); + } + else if (strcasecmp(argv[1], "help") == 0 || strcasecmp(argv[1], "--help") == 0 || strcasecmp(argv[1], "-h") == 0) + { + res = (fputs(sHelp, stdout) != EOF); + } + else if (strcasecmp(argv[1], "version") == 0 || strcasecmp(argv[1], "--version") == 0 || strcasecmp(argv[1], "-v") == 0) + { + res = PrintVersion(); + } + else if (strcasecmp(argv[1], "gen-verifier") == 0 || strcasecmp(argv[1], "genverifier") == 0) + { + res = Cmd_GenVerifier(argc - 1, argv + 1); + } + else + { + fprintf(stderr, "Unrecognized command: %s\n", argv[1]); + } + + return res ? 0 : -1; +} diff --git a/src/tools/spake2p/spake2p.h b/src/tools/spake2p/spake2p.h new file mode 100644 index 00000000000000..5e5f077f77c9b2 --- /dev/null +++ b/src/tools/spake2p/spake2p.h @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file defines the public API symbols, constants, and interfaces + * for the 'spakep2' command line tool. + */ + +#pragma once + +#include +#include +#include +#include + +#define COPYRIGHT_STRING "Copyright (c) 2022 Project CHIP Authors.\nAll rights reserved.\n" + +extern bool Cmd_GenVerifier(int argc, char * argv[]);