blob: 5a6f964b71344b8143baf1a5599dc5208b4dbf2d [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 an object for decoding, verifying, and
* accessing a Weave device provisioning bundle, consisting of a
* certificate, private key, and pairing (entry) code.
*
*/
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include <stdint.h>
#include <Weave/Core/WeaveCore.h>
#if WEAVE_CONFIG_ENABLE_PROVISIONING_BUNDLE_SUPPORT
#include <Weave/Core/WeaveEncoding.h>
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <Weave/Support/crypto/HMAC.h>
#include <Weave/Support/verhoeff/Verhoeff.h>
#include <Weave/Profiles/security/WeaveSecurity.h>
#include <Weave/Profiles/security/WeaveCert.h>
#include <Weave/Profiles/security/WeavePrivateKey.h>
#include <Weave/Profiles/security/WeaveProvBundle.h>
#include <Weave/Support/ASN1.h>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Support/Base64.h>
#if WEAVE_CONFIG_ENABLE_PROVISIONING_BUNDLE_SUPPORT && !WEAVE_WITH_OPENSSL
#error "INVALID WEAVE CONFIG: Provisioning bundle feature enabled but OpenSSL not available (WEAVE_CONFIG_ENABLE_PROVISIONING_BUNDLE_SUPPORT == 1 && WEAVE_WITH_OPENSSL == 0)."
#endif
#include <openssl/evp.h>
#include <openssl/err.h>
// Weave Provisioning Bundle Format
// Included
// Field Size Encrypted in MAC
//
// Version 2 Byte N Y
// Certificate Len 2 Bytes Y Y
// Private Key Len 2 Bytes Y Y
// Pairing Code Len 2 Bytes Y Y
// Device Id/MAC Addr 8 Bytes Y Y
// Certificate variable Y Y
// Private Key variable Y Y
// Pairing Code variable Y Y
// MAC 32 bytes Y N
// Encryption Padding variable Y N
// Encryption IV 32 bytes N N
//
// Encryption algorithm is AES-256-CBC with PKCS5 padding.
// MAC algorithm is HMAC-SHA256.
// Encryption and MAC keys are derived from master key using PBDKF2-SHA1, 1000 iterations with fixed salt.
// All numeric values (lengths, MAC address) are encoded little-endian.
namespace nl {
namespace Weave {
namespace Profiles {
namespace Security {
using namespace nl::Weave::Encoding;
using namespace nl::Weave::Crypto;
using namespace nl::Weave::ASN1;
enum {
kProvisioningBundleVersion = 1,
kProvisioningBundleKDFIters = 1000
};
const char gProvisioningBundleKDFSalt[] = "Weave Provisioning Bundle v1";
#define gProvisioningBundleKDFSaltLen (sizeof(gProvisioningBundleKDFSalt) - 1)
static WEAVE_ERROR TranslateOpenSSLError(WEAVE_ERROR defaultErr)
{
if (ERR_GET_REASON(ERR_get_error()) == ERR_R_MALLOC_FAILURE)
return WEAVE_ERROR_NO_MEMORY;
return defaultErr;
}
WEAVE_ERROR WeaveProvisioningBundle::Verify(uint64_t expectedDeviceId)
{
WEAVE_ERROR err = WEAVE_NO_ERROR;
WeaveCertificateSet certSet;
WeaveCertificateData *certData;
uint32_t weaveCurveId;
EncodedECPublicKey pubKey;
EncodedECPrivateKey privKey;
// Initialize the certificate set to hold a single certificate.
err = certSet.Init(1, 1024);
SuccessOrExit(err);
// Load the device certificate contained in the bundle.
err = certSet.LoadCert(Certificate, CertificateLen, 0, certData);
SuccessOrExit(err);
// Verify the certificate is indeed a device certificate.
VerifyOrExit(certData->SubjectDN.AttrOID == kOID_AttributeType_WeaveDeviceId, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Verify the certificate is identifies the expected device id.
VerifyOrExit(certData->SubjectDN.AttrValue.WeaveId == expectedDeviceId, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Verify the certificate contains a supported public key type.
VerifyOrExit(certData->PubKeyAlgoOID == kOID_PubKeyAlgo_ECPublicKey, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Decode the private key contained in the bundle.
err = DecodeWeaveECPrivateKey(PrivateKey, PrivateKeyLen, weaveCurveId, pubKey, privKey);
SuccessOrExit(err);
// Verify that the EC curve used by the public key in certificate matches that used
// by the private key.
VerifyOrExit(certData->PubKeyCurveId == weaveCurveId, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Verify that the public key in the certificate matches that in the private key.
VerifyOrExit(certData->PublicKey.EC.IsEqual(pubKey), err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Verify the check digit in the pairing code.
VerifyOrExit(Verhoeff32::ValidateCheckChar(PairingCode, PairingCodeLen) == true, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
exit:
certSet.Release();
return err;
}
WEAVE_ERROR WeaveProvisioningBundle::Decode(uint8_t *provBundleBuf, uint32_t provBundleLen, const char *masterKey, uint32_t masterKeyLen, WeaveProvisioningBundle &provBundle)
{
enum {
kEncryptKeySize = 32,
kMACKeySize = 32,
kVersionFieldSize = 2,
kFixedHeaderSize = 2 + 2 + 2 + 8, // cert len + priv key len + pairing code len + device id
kMACFieldSize = HMACSHA256::kDigestLength,
kIVSize = 32,
};
WEAVE_ERROR err = WEAVE_NO_ERROR;
EVP_CIPHER_CTX *decryptCtx;
HMACSHA256 hmac;
uint8_t expectedMAC[kMACFieldSize];
uint8_t keyMaterial[kEncryptKeySize + kMACKeySize];
uint32_t encryptedDataLen, decryptedDataLen, expectedDecryptedDataLen, decryptBufSize;
uint8_t *decryptBuf = NULL, *decodePoint = provBundleBuf;
uint16_t version;
int res, outLen;
provBundle.Clear();
decryptCtx = EVP_CIPHER_CTX_new();
VerifyOrExit(decryptCtx != NULL, err = WEAVE_ERROR_NO_MEMORY);
EVP_CIPHER_CTX_init(decryptCtx);
// Un-base64 the provisioning bundle, storing the resultant data back into the input buffer.
provBundleLen = Base64Decode((char *)provBundleBuf, (uint16_t)provBundleLen, (uint8_t *)provBundleBuf);
VerifyOrExit(provBundleLen != UINT16_MAX, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Verify the provided data is at least big enough to hold the version field and the initialization vector.
VerifyOrExit(provBundleLen > kVersionFieldSize + kIVSize, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Verify the version is supported.
version = LittleEndian::Read16(decodePoint);
VerifyOrExit(version == kProvisioningBundleVersion, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Allocate a temporary buffer to hold the decrypted data.
decryptBufSize = provBundleLen;
decryptBuf = (uint8_t *)malloc(decryptBufSize);
// Generate the encryption and MAC keys from the master key.
res = PKCS5_PBKDF2_HMAC(masterKey, (int)masterKeyLen,
(const unsigned char *)gProvisioningBundleKDFSalt, gProvisioningBundleKDFSaltLen,
kProvisioningBundleKDFIters,
EVP_sha1(),
sizeof(keyMaterial), keyMaterial);
if (res != 1)
ExitNow(err = TranslateOpenSSLError(WEAVE_ERROR_PROVISIONING_BUNDLE_DECRYPTION_ERROR));
// Initialize the OpenSSL cipher context for decryption using the generated encryption key and
// the initialization vector from the bundle.
res = EVP_CipherInit_ex(decryptCtx, EVP_aes_256_cbc(), NULL,
keyMaterial,
(uint8_t *)provBundleBuf + (provBundleLen - kIVSize),
0);
if (res != 1)
ExitNow(err = TranslateOpenSSLError(WEAVE_ERROR_PROVISIONING_BUNDLE_DECRYPTION_ERROR));
// Compute the length of the encrypted portion of the bundle.
encryptedDataLen = provBundleLen - (kVersionFieldSize + kIVSize);
// Pass the encrypted data to the decryption function, storing the output in the decryption buffer.
outLen = provBundleLen;
res = EVP_CipherUpdate(decryptCtx, decryptBuf, &outLen, decodePoint, (int)encryptedDataLen);
if (res != 1)
ExitNow(err = TranslateOpenSSLError(WEAVE_ERROR_PROVISIONING_BUNDLE_DECRYPTION_ERROR));
decryptedDataLen = outLen;
// Finalize the decryption process by decrypting and verifying the padding. Note that EVP_EncryptFinal_ex()
// does not tell us how long the padding is (although it obviously knows), so decryptedDataLen should be
// the same as encryptedDataLen.
outLen = decryptBufSize - decryptedDataLen;
res = EVP_CipherFinal_ex(decryptCtx, decryptBuf + decryptedDataLen, &outLen);
if (res != 1)
ExitNow(err = TranslateOpenSSLError(WEAVE_ERROR_PROVISIONING_BUNDLE_DECRYPTION_ERROR));
decryptedDataLen += outLen;
// Verify that the decrypted data is smaller than the encrypted data (this should always be the case because of padding).
VerifyOrExit(decryptedDataLen < encryptedDataLen, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Copy the decrypted data from the temporary buffer back into the input buffer, overwriting
// the encrypted data in the process.
memcpy(decodePoint, decryptBuf, decryptedDataLen);
// Verify that decrypted data is at least as big as the fixed portion of the header.
VerifyOrExit(decryptedDataLen > kFixedHeaderSize, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Decode the fields of the provisioning bundle into the provided object.
provBundle.CertificateLen = LittleEndian::Read16(decodePoint);
provBundle.PrivateKeyLen = LittleEndian::Read16(decodePoint);
provBundle.PairingCodeLen = LittleEndian::Read16(decodePoint);
provBundle.WeaveDeviceId = LittleEndian::Read64(decodePoint);
provBundle.Certificate = decodePoint; decodePoint += provBundle.CertificateLen;
provBundle.PrivateKey = decodePoint; decodePoint += provBundle.PrivateKeyLen;
provBundle.PairingCode = (const char *)decodePoint; decodePoint += provBundle.PairingCodeLen;
// Verify that the size of the data within the provisioning bundle matches the length of the data
// returned by decryption.
expectedDecryptedDataLen = kFixedHeaderSize + provBundle.CertificateLen + provBundle.PrivateKeyLen + provBundle.PairingCodeLen + kMACFieldSize;
VerifyOrExit(decryptedDataLen == expectedDecryptedDataLen, err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
// Recompute the MAC using the generated MAC key.
hmac.Begin(keyMaterial + kEncryptKeySize, kMACKeySize);
hmac.AddData(provBundleBuf, kVersionFieldSize + kFixedHeaderSize + provBundle.CertificateLen + provBundle.PrivateKeyLen + provBundle.PairingCodeLen);
hmac.Finish(expectedMAC);
// Fail if the MAC supplied in the bundle does not match the computed MAC.
if (!ConstantTimeCompare(decodePoint, expectedMAC, kMACFieldSize))
ExitNow(err = WEAVE_ERROR_INVALID_PROVISIONING_BUNDLE);
exit:
if (decryptBuf != NULL)
free(decryptBuf);
if (decryptCtx != NULL)
EVP_CIPHER_CTX_free(decryptCtx);
return err;
}
void WeaveProvisioningBundle::Clear()
{
WeaveDeviceId = 0;
Certificate = NULL;
CertificateLen = 0;
PrivateKey = NULL;
PrivateKeyLen = 0;
PairingCode = NULL;
PairingCodeLen = 0;
}
} // namespace Security
} // namespace Profiles
} // namespace Weave
} // namespace nl
#endif // WEAVE_CONFIG_ENABLE_PROVISIONING_BUNDLE_SUPPORT