blob: b143641ba3794da864765cc078f9954698d6707d [file] [log] [blame]
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* 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 'weave' tool
* that generates a Weave device certificate.
*
*/
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include <stdint.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include "weave-tool.h"
using namespace nl::Weave::Profiles::Security;
using namespace nl::Weave::ASN1;
#define CMD_NAME "weave gen-device-cert"
static bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg);
static OptionDef gCmdOptionDefs[] =
{
{ "dev-id", kArgumentRequired, 'i' },
{ "key", kArgumentRequired, 'k' },
{ "ca-cert", kArgumentRequired, 'C' },
{ "ca-key", kArgumentRequired, 'K' },
{ "out", kArgumentRequired, 'o' },
{ "out-key", kArgumentRequired, 'O' },
{ "curve", kArgumentRequired, 'u' },
{ "valid-from", kArgumentRequired, 'V' },
{ "lifetime", kArgumentRequired, 'l' },
{ "sha1", kNoArgument, '1' },
{ "sha256", kNoArgument, '2' },
{ NULL }
};
static const char *const gCmdOptionHelp =
" -i, --dev-id <hex-digits>\n"
"\n"
" The device id (in hex) for which the certificate should be generated.\n"
"\n"
" -C, --ca-cert <file>\n"
"\n"
" File containing CA certificate to be used to sign the device certificate\n"
" (in PEM format).\n"
"\n"
" -K, --ca-key <file>\n"
"\n"
" File containing CA private key to be used to sign the device certificate\n"
" (in PEM format).\n"
"\n"
" -k, --key <file>\n"
"\n"
" File containing the public and private keys for the new certificate (in\n"
" PEM format). If not specified, a new key pair will be generated.\n"
"\n"
" -o, --out <file>\n"
"\n"
" File to contain the new device certificate. (Will be written in Weave base-64\n"
" format).\n"
"\n"
" -O, --out-key <file>\n"
"\n"
" File to contain the public/private for the new device certificate (in Weave\n"
" base-64 format). This option must be specified if the --key option is not.\n"
"\n"
" -V, --valid-from <YYYY>-<MM>-<DD> [ <HH>:<MM>:<SS> ]\n"
"\n"
" The start date for the certificate's validity period. If not specified,\n"
" the validity period starts on the current day.\n"
"\n"
" -l, --lifetime <days>\n"
"\n"
" The lifetime for the new certificate, in whole days.\n"
"\n"
" -u, --curve <elliptic-curve-name>\n"
"\n"
" The elliptic curve to use when generating the public/private keys. This option\n"
" must be specified if the --key option is not given.\n"
"\n"
" -1, --sha1\n"
"\n"
" Sign the certificate using a SHA-1 hash.\n"
"\n"
" -2, --sha256\n"
"\n"
" Sign the certificate using a SHA-256 hash.\n"
"\n"
;
static OptionSet gCmdOptions =
{
HandleOption,
gCmdOptionDefs,
"COMMAND OPTIONS",
gCmdOptionHelp
};
static HelpOptions gHelpOptions(
CMD_NAME,
"Usage: " CMD_NAME " [ <options...> ]\n",
WEAVE_VERSION_STRING "\n" COPYRIGHT_STRING,
"Generate a Weave device certificate"
);
static OptionSet *gCmdOptionSets[] =
{
&gCmdOptions,
&gHelpOptions,
NULL
};
static uint64_t gDevId = 0;
static const char *gCurveName = NULL;
static const char *gCACertFileName = NULL;
static const char *gCAKeyFileName = NULL;
static const char *gInCertKeyFileName = NULL;
static const char *gOutCertFileName = NULL;
static const char *gOutKeyFileName = NULL;
static int32_t gValidDays = 0;
static const EVP_MD *gSigHashAlgo = NULL;
static struct tm gValidFrom;
bool Cmd_GenDeviceCert(int argc, char *argv[])
{
bool res = true;
int curveNID = 0;
FILE *outCertFile = NULL;
FILE *outKeyFile = NULL;
X509 *caCert = NULL;
X509 *devCert = NULL;
EVP_PKEY *caKey = NULL;
EVP_PKEY *devKey = NULL;
uint8_t *weaveCert = NULL;
uint32_t weaveCertLen;
char *weaveCertB64 = NULL;
uint8_t *weaveKey = NULL;
uint32_t weaveKeyLen;
char *weaveKeyB64 = NULL;
bool certFileCreated = false;
bool keyFileCreated = false;
{
time_t now = time(NULL);
gValidFrom = *gmtime(&now);
gValidFrom.tm_hour = 0;
gValidFrom.tm_min = 0;
gValidFrom.tm_sec = 0;
}
if (argc == 1)
{
gHelpOptions.PrintBriefUsage(stderr);
ExitNow(res = true);
}
if (!ParseArgs(CMD_NAME, argc, argv, gCmdOptionSets))
{
ExitNow(res = true);
}
if (gDevId == 0)
{
fprintf(stderr, "Please specify the device id using the --dev-id option.\n");
ExitNow(res = false);
}
if (gCACertFileName == NULL)
{
fprintf(stderr, "Please specify the CA certificate file name using the --ca-cert option.\n");
ExitNow(res = false);
}
if (gCAKeyFileName == NULL)
{
fprintf(stderr, "Please specify the CA key file name using the --ca-key option.\n");
ExitNow(res = false);
}
if (gOutCertFileName == NULL)
{
fprintf(stderr, "Please specify the file name for the new device certificate using the --out option.\n");
ExitNow(res = false);
}
if (gInCertKeyFileName == NULL && gOutKeyFileName == NULL)
{
fprintf(stderr, "Please specify the file name for the new public/private key using the --out-key option.\n");
ExitNow(res = false);
}
if (gValidDays == 0)
{
fprintf(stderr, "Please specify the lifetime for the new CA certificate (in days) using the --lifetime option.\n");
ExitNow(res = false);
}
if (gInCertKeyFileName == NULL && gCurveName == NULL)
{
fprintf(stderr, "Please specify an elliptic curve using the --curve option.\n");
ExitNow(res = false);
}
if (gCurveName != NULL)
{
curveNID = OBJ_sn2nid(gCurveName);
if (curveNID == 0)
{
fprintf(stderr, "Unknown elliptic curve: %s\n", gCurveName);
ExitNow(res = false);
}
}
if (gSigHashAlgo == NULL)
{
fprintf(stderr, "Please specify a signature hash algorithm using either the --sha1 or --sha256 options.\n");
ExitNow(res = false);
}
if (access(gOutCertFileName, R_OK) == 0)
{
fprintf(stderr, "weave: ERROR: Output certificate file already exists (%s)\n"
"To replace the file, please remove it and re-run the command.\n",
gOutCertFileName);
ExitNow(res = false);
}
if (gOutKeyFileName != NULL && access(gOutKeyFileName, R_OK) == 0)
{
fprintf(stderr, "weave: ERROR: Output key file already exists (%s)\n"
"To replace the file, please remove it and re-run the command.\n",
gOutKeyFileName);
ExitNow(res = false);
}
outCertFile = fopen(gOutCertFileName, "w+");
if (outCertFile == NULL)
{
fprintf(stderr, "weave: ERROR: Unable to create output certificate file (%s)\n"
"%s.\n",
gOutCertFileName,
strerror(errno));
ExitNow(res = false);
}
certFileCreated = true;
if (gOutKeyFileName != NULL)
{
outKeyFile = fopen(gOutKeyFileName, "w+");
if (outKeyFile == NULL)
{
fprintf(stderr, "weave: ERROR: Unable to create output key file (%s)\n"
"%s.\n",
gOutKeyFileName,
strerror(errno));
ExitNow(res = false);
}
}
keyFileCreated = true;
if (!InitOpenSSL())
ExitNow(res = false);
if (!ReadCertPEM(gCACertFileName, caCert))
ExitNow(res = false);
if (gInCertKeyFileName != NULL)
{
if (!ReadPrivateKey(gInCertKeyFileName, "Enter password for private key:", devKey))
ExitNow(res = false);
// If a curve name was specified on the command line, verify that it matches the curve
// of the private key.
if (gCurveName != NULL && EC_GROUP_get_curve_name(EC_KEY_get0_group(EVP_PKEY_get1_EC_KEY(devKey))) != curveNID)
{
fprintf(stderr, "weave: ERROR: Value given for --curve option does not match specified key (%s)\n",
gCurveName);
ExitNow(res = false);
}
}
if (!ReadPrivateKey(gCAKeyFileName, "Enter password for CA certificate:", caKey))
ExitNow(res = false);
if (!MakeDeviceCert(gDevId, caCert, caKey, gCurveName, gValidFrom, gValidDays, gSigHashAlgo, devCert, devKey))
ExitNow(res = false);
if (!WeaveEncodeCert(devCert, weaveCert, weaveCertLen))
ExitNow(res = false);
if (!EncodePrivateKey(devKey, kKeyFormat_Weave_Raw, weaveKey, weaveKeyLen))
ExitNow(res = false);
weaveCertB64 = Base64Encode(weaveCert, weaveCertLen);
if (weaveCertB64 == NULL)
{
fprintf(stderr, "Memory allocation error\n");
ExitNow(res = false);
}
weaveKeyB64 = Base64Encode(weaveKey, weaveKeyLen);
if (weaveKeyB64 == NULL)
{
fprintf(stderr, "Memory allocation error\n");
ExitNow(res = false);
}
if (fputs(weaveCertB64, outCertFile) == EOF)
{
fprintf(stderr, "weave: ERROR: Unable to write output certificate file (%s)\n"
"%s.\n",
gOutCertFileName,
strerror(errno));
ExitNow(res = false);
}
if (outKeyFile != NULL && fputs(weaveKeyB64, outKeyFile) == EOF)
{
fprintf(stderr, "weave: ERROR: Unable to write output key file (%s)\n"
"%s.\n",
gOutKeyFileName,
strerror(errno));
ExitNow(res = false);
}
res = (fclose(outCertFile) != EOF);
outCertFile = NULL;
if (!res)
{
fprintf(stderr, "weave: ERROR: Unable to write output certificate file (%s)\n"
"%s.\n",
gOutCertFileName,
strerror(errno));
ExitNow();
}
if (outKeyFile != NULL)
{
res = (fclose(outKeyFile) != EOF);
outKeyFile = NULL;
if (!res)
{
fprintf(stderr, "weave: ERROR: Unable to write output key file (%s)\n"
"%s.\n",
gOutKeyFileName,
strerror(errno));
ExitNow();
}
}
exit:
if (weaveCertB64 != NULL)
free(weaveCertB64);
if (weaveKeyB64 != NULL)
free(weaveKeyB64);
if (caCert != NULL)
X509_free(caCert);
if (devCert != NULL)
X509_free(devCert);
if (caKey != NULL)
EVP_PKEY_free(caKey);
if (devKey != NULL)
EVP_PKEY_free(devKey);
if (weaveCert != NULL)
free(weaveCert);
if (weaveKey != NULL)
free(weaveKey);
if (outCertFile != NULL)
fclose(outCertFile);
if (outKeyFile != NULL)
fclose(outKeyFile);
if (gOutCertFileName != NULL && certFileCreated && !res)
unlink(gOutCertFileName);
if (gOutKeyFileName != NULL && keyFileCreated && !res)
unlink(gOutKeyFileName);
return res;
}
bool HandleOption(const char *progName, OptionSet *optSet, int id, const char *name, const char *arg)
{
switch (id)
{
case 'i':
if (!ParseEUI64(arg, gDevId))
{
PrintArgError("%s: Invalid value specified for device id: %s\n", progName, arg);
return false;
}
break;
case 'k':
gInCertKeyFileName = arg;
break;
case 'C':
gCACertFileName = arg;
break;
case 'K':
gCAKeyFileName = arg;
break;
case 'o':
gOutCertFileName = arg;
break;
case 'O':
gOutKeyFileName = arg;
break;
case 'u':
gCurveName = arg;
break;
case 'V':
if (!ParseDateTime(arg, gValidFrom))
{
PrintArgError("%s: Invalid value specified for certificate validity date: %s\n", progName, arg);
return false;
}
break;
case 'l':
if (!ParseInt(arg, gValidDays) || gValidDays < 0)
{
PrintArgError("%s: Invalid value specified for certificate lifetime: %s\n", progName, arg);
return false;
}
break;
case '1':
gSigHashAlgo = EVP_sha1();
break;
case '2':
gSigHashAlgo = EVP_sha256();
break;
default:
PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", progName, name);
return false;
}
return true;
}