blob: e7357fd70933aacc9b89cb227756d92676a09f6a [file] [log] [blame]
/*
*
* Copyright (c) 2019 Google LLC.
* 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 a command line tool, weave-cert-prov-client, for the
* Weave Certificate Provisioning Protocol (Security Profile).
*
* The weave-cert-prov-client tool implements a facility for acting as a client
* (originator) for the certificate provisioning request, with a variety of options.
*
*/
#define __STDC_FORMAT_MACROS
#define __STDC_LIMIT_MACROS
#include <inttypes.h>
#include <limits.h>
#include <signal.h>
#include "ToolCommon.h"
#include "CertProvOptions.h"
#include <Weave/WeaveVersion.h>
#include <Weave/Core/WeaveSecurityMgr.h>
#include <Weave/Core/WeaveTLV.h>
#include <Weave/Profiles/security/WeaveSecurity.h>
#include <Weave/Profiles/security/WeaveCertProvisioning.h>
#include <Weave/Profiles/service-directory/ServiceDirectory.h>
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <Weave/Support/TimeUtils.h>
#include <Weave/Support/WeaveFaultInjection.h>
using nl::StatusReportStr;
using namespace nl::Weave::TLV;
using namespace nl::Weave::Profiles::Security;
using namespace nl::Weave::Profiles::Security::CertProvisioning;
#define TOOL_NAME "weave-cert-prov-client"
static bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg);
static bool HandleNonOptionArgs(const char *progName, int argc, char *argv[]);
static void DriveSending();
static void HandleConnectionReceived(WeaveMessageLayer *msgLayer, WeaveConnection *con);
static void ParseDestAddress();
static void BindingEventHandler(void *appState, Binding::EventType eventType, const Binding::InEventParam& inParam, Binding::OutEventParam& outParam);
const nl::Weave::ExchangeContext::Timeout kResponseTimeoutMsec = 5000;
uint32_t MaxGetCertCount = UINT32_MAX;
uint32_t GetCertInterval = 5000; // 5 sec
bool UseTCP = true;
bool Debug = false;
uint64_t DestNodeId;
const char *DestAddr = NULL;
IPAddress DestIPAddr;
uint16_t DestPort = WEAVE_PORT;
InterfaceId DestIntf = INET_NULL_INTERFACEID;
uint64_t LastGetCertTime = 0;
bool WaitingForGetCertResponse = false;
uint32_t GetCertRequestCount = 0;
uint32_t GetCertResponseCount = 0;
WeaveCertProvEngine CertProvClient;
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
bool UseWRMP = false;
#endif // WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
static OptionDef gToolOptionDefs[] =
{
{ "dest-addr", kArgumentRequired, 'D' },
{ "count", kArgumentRequired, 'c' },
{ "interval", kArgumentRequired, 'i' },
{ "tcp", kNoArgument, 't' },
{ "udp", kNoArgument, 'u' },
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
{ "wrmp", kNoArgument, 'w' },
#endif // WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
{ NULL }
};
static const char *const gToolOptionHelp =
" -D, --dest-addr <host>[:<port>][%<interface>]\n"
" Send Get Certificate Requests to a specific address rather than one\n"
" derived from the destination node id. <host> can be a hostname,\n"
" an IPv4 address or an IPv6 address. If <port> is specified, Get Certificate\n"
" Requests will be sent to the specified port. If <interface> is\n"
" specified, Get Certificate Requests will be sent over the specified local\n"
" interface.\n"
"\n"
" NOTE: When specifying a port with an IPv6 address, the IPv6 address\n"
" must be enclosed in brackets, e.g. [fd00:0:1:1::1]:11095.\n"
"\n"
" -c, --count <num>\n"
" Send the specified number of Get Certificate Requests and exit.\n"
"\n"
" -i, --interval <ms>\n"
" Send Get Certificate Requests at the specified interval in milliseconds.\n"
"\n"
" -t, --tcp\n"
" Use TCP to send Get Certificate Requests. This is the default.\n"
"\n"
" -u, --udp\n"
" Use UDP to send Get Certificate Requests.\n"
"\n"
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
" -w, --wrmp\n"
" Use UDP with Weave reliable messaging to send Get Certificate Requests.\n"
"\n"
#endif
;
static OptionSet gToolOptions =
{
HandleOption,
gToolOptionDefs,
"GENERAL OPTIONS",
gToolOptionHelp
};
static HelpOptions gHelpOptions(
TOOL_NAME,
"Usage: " TOOL_NAME " [<options...>] <dest-node-id>[@<dest-host>[:<dest-port>][%<interface>]]\n"
WEAVE_VERSION_STRING "\n" WEAVE_TOOL_COPYRIGHT,
"Send key export request and receive key export response messages.\n"
);
static OptionSet *gToolOptionSets[] =
{
&gToolOptions,
&gNetworkOptions,
&gWeaveNodeOptions,
&gWRMPOptions,
&gCASEOptions,
&gCertProvOptions,
&gDeviceDescOptions,
&gServiceDirClientOptions,
&gFaultInjectionOptions,
&gHelpOptions,
NULL
};
static void ResetTestContext(void)
{
Done = false;
WaitingForGetCertResponse = false;
GetCertRequestCount = 0;
GetCertResponseCount = 0;
}
int main(int argc, char *argv[])
{
WEAVE_ERROR err;
nl::Weave::System::Stats::Snapshot before;
nl::Weave::System::Stats::Snapshot after;
const bool printStats = true;
uint32_t iter;
#if WEAVE_CONFIG_TEST
SetupFaultInjectionContext(argc, argv);
SetSignalHandler(DoneOnHandleSIGUSR1);
#endif
{
unsigned int seed;
err = nl::Weave::Platform::Security::GetSecureRandomData((uint8_t *)&seed, sizeof(seed));
FAIL_ERROR(err, "Random number generator seeding failed");
srand(seed);
}
if (argc == 1)
{
gHelpOptions.PrintBriefUsage(stderr);
exit(EXIT_FAILURE);
}
if (!ParseArgsFromEnvVar(TOOL_NAME, TOOL_OPTIONS_ENV_VAR_NAME, gToolOptionSets, NULL, true) ||
!ParseArgs(TOOL_NAME, argc, argv, gToolOptionSets, HandleNonOptionArgs) ||
!ResolveWeaveNetworkOptions(TOOL_NAME, gWeaveNodeOptions, gNetworkOptions))
{
exit(EXIT_FAILURE);
}
InitSystemLayer();
InitNetwork();
InitWeaveStack(!UseTCP, true);
// Create a binding for the CertProvClient.
Binding *binding = ExchangeMgr.NewBinding(BindingEventHandler, NULL);
// Initialize the CertProvClient object.
err = CertProvClient.Init(binding, &gCertProvOptions, &gCertProvOptions, gCertProvOptions.CertProvClientEventHandler, &gCertProvOptions);
FAIL_ERROR(err, "WeaveCertProvEngine.Init failed");
// Release the binding (the CertProvClient retains its own reference).
binding->Release();
#if WEAVE_CONFIG_TEST
nl::Weave::Stats::UpdateSnapshot(before);
#endif
// Arrange to get called for various activities in the message layer.
MessageLayer.OnConnectionReceived = HandleConnectionReceived;
MessageLayer.OnReceiveError = HandleMessageReceiveError;
MessageLayer.OnAcceptError = HandleAcceptConnectionError;
PrintNodeConfig();
if (!UseTCP && DestAddr != NULL)
ParseDestAddress();
printf("Sending");
if (MaxGetCertCount != UINT32_MAX)
printf(" %u", MaxGetCertCount);
printf(" Get Certificate Requests via %s to node %" PRIX64, UseTCP ? "TCP" : (UseWRMP ? "UDP with WRMP" : "UDP"), DestNodeId);
if (DestAddr != NULL)
printf(" (%s)", DestAddr);
printf(" every %" PRId32 " ms\n", GetCertInterval);
#if WEAVE_CONFIG_TEST
for (iter = 0; iter < gFaultInjectionOptions.TestIterations; iter++)
{
printf("Iteration %u\n", iter);
#endif // WEAVE_CONFIG_TEST
while (!Done)
{
struct timeval sleepTime;
sleepTime.tv_sec = 0;
sleepTime.tv_usec = 100000;
ServiceNetwork(sleepTime);
if (!Done)
DriveSending();
fflush(stdout);
}
ResetTestContext();
#if WEAVE_CONFIG_TEST
if (gSigusr1Received)
{
printf("Sigusr1Received\n");
break;
}
}
#endif // WEAVE_CONFIG_TEST
CertProvClient.Shutdown();
#if WEAVE_CONFIG_TEST
ProcessStats(before, after, printStats, NULL);
PrintFaultInjectionCounters();
#endif // WEAVE_CONFIG_TEST
ShutdownWeaveStack();
ShutdownNetwork();
ShutdownSystemLayer();
return EXIT_SUCCESS;
}
bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg)
{
switch (id)
{
case 't':
UseTCP = true;
break;
case 'u':
UseTCP = false;
break;
#if WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
case 'w':
UseTCP = false;
UseWRMP = true;
break;
#endif // WEAVE_CONFIG_ENABLE_RELIABLE_MESSAGING
case 'c':
if (!ParseInt(arg, MaxGetCertCount))
{
PrintArgError("%s: Invalid value specified for send count: %s\n", progName, arg);
return false;
}
break;
case 'i':
if (!ParseInt(arg, GetCertInterval))
{
PrintArgError("%s: Invalid value specified for send interval: %s\n", progName, arg);
return false;
}
break;
case 'D':
DestAddr = arg;
break;
default:
PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", progName, name);
return false;
}
return true;
}
bool HandleNonOptionArgs(const char *progName, int argc, char *argv[])
{
if (argc > 0)
{
if (argc > 1)
{
PrintArgError("%s: Unexpected argument: %s\n", progName, argv[1]);
return false;
}
const char *nodeId = argv[0];
char *p = (char *)strchr(nodeId, '@');
if (p != NULL)
{
*p = 0;
DestAddr = p+1;
}
if (!ParseNodeId(nodeId, DestNodeId))
{
PrintArgError("%s: Invalid value specified for destination node Id: %s\n", progName, nodeId);
return false;
}
}
else
{
PrintArgError("%s: Please specify destination node Id\n", progName);
return false;
}
return true;
}
void DriveSending()
{
WEAVE_ERROR err;
if (Now() < LastGetCertTime + GetCertInterval)
return;
if (WaitingForGetCertResponse)
{
printf("No get certificate response received\n");
WaitingForGetCertResponse = false;
// Rescan interfaces to see if we got any new IP addresses
if (!UseTCP)
{
printf("Refreshing endpoints\n");
err = MessageLayer.RefreshEndpoints();
if (err != WEAVE_NO_ERROR)
printf("WeaveMessageLayer.RefreshEndpoints() failed: %s\n", ErrorStr(err));
}
}
if (MaxGetCertCount != UINT32_MAX && GetCertRequestCount >= MaxGetCertCount)
{
Done = true;
return;
}
err = CertProvClient.StartCertificateProvisioning(gCertProvOptions.RequestType, (gCertProvOptions.RequestType == WeaveCertProvEngine::kReqType_GetInitialOpDeviceCert));
LastGetCertTime = Now();
if (err == WEAVE_NO_ERROR)
{
GetCertRequestCount++;
WaitingForGetCertResponse = true;
}
else
{
printf("CertProvClient.StartCertificateProvisioning() failed: %s\n", ErrorStr(err));
}
}
void HandleConnectionReceived(WeaveMessageLayer *msgLayer, WeaveConnection *con)
{
char ipAddrStr[64];
con->PeerAddr.ToString(ipAddrStr, sizeof(ipAddrStr));
printf("Connection received from node %" PRIX64 " (%s)\n", con->PeerNodeId, ipAddrStr);
}
void ParseDestAddress()
{
// NOTE: This function is only used when communicating over UDP. Code in the WeaveConnection object handles
// parsing the destination node address for TCP connections.
WEAVE_ERROR err;
const char *addr;
uint16_t addrLen;
const char *intfName;
uint16_t intfNameLen;
err = ParseHostPortAndInterface(DestAddr, strlen(DestAddr), addr, addrLen, DestPort, intfName, intfNameLen);
if (err != INET_NO_ERROR)
{
printf("Invalid destination address: %s\n", DestAddr);
exit(EXIT_FAILURE);
}
if (!IPAddress::FromString(addr, DestIPAddr))
{
printf("Invalid destination address: %s\n", DestAddr);
exit(EXIT_FAILURE);
}
if (intfName != NULL)
{
err = InterfaceNameToId(intfName, DestIntf);
if (err != INET_NO_ERROR)
{
printf("Invalid interface name: %s\n", intfName);
exit(EXIT_FAILURE);
}
}
}
void BindingEventHandler(void *appState, Binding::EventType eventType, const Binding::InEventParam& inParam, Binding::OutEventParam& outParam)
{
switch (eventType)
{
case Binding::kEvent_PrepareRequested:
{
Binding::Configuration bindingConfig = inParam.Source->BeginConfiguration();
// Configure the target node Id.
bindingConfig.Target_NodeId(DestNodeId);
// Configure the target address.
if (DestAddr != NULL)
{
bindingConfig.TargetAddress_IP(DestIPAddr, DestPort, DestIntf);
}
// Configure the transport.
if (UseTCP)
{
bindingConfig.Transport_TCP();
}
else if (!UseWRMP)
{
bindingConfig.Transport_UDP();
}
else
{
bindingConfig.Transport_UDP_WRM();
bindingConfig.Transport_DefaultWRMPConfig(gWRMPOptions.GetWRMPConfig());
}
// Configure the security mode.
switch (gWeaveSecurityMode.SecurityMode)
{
case WeaveSecurityMode::kNone:
default:
bindingConfig.Security_None();
break;
case WeaveSecurityMode::kCASE:
bindingConfig.Security_CASESession();
break;
case WeaveSecurityMode::kCASEShared:
bindingConfig.Security_SharedCASESession();
break;
case WeaveSecurityMode::kGroupEnc:
bindingConfig.Security_Key(gGroupKeyEncOptions.GetEncKeyId());
break;
}
bindingConfig.Exchange_ResponseTimeoutMsec(kResponseTimeoutMsec);
outParam.PrepareRequested.PrepareError = bindingConfig.PrepareBinding();
break;
}
default:
Binding::DefaultEventHandler(appState, eventType, inParam, outParam);
break;
}
}