tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

BTVerifier.cpp (9615B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "BTVerifier.h"
      8 
      9 #include <stdint.h>
     10 
     11 #include "CTUtils.h"
     12 #include "SignedCertificateTimestamp.h"
     13 #include "hasht.h"
     14 #include "mozpkix/pkixnss.h"
     15 #include "mozpkix/pkixutil.h"
     16 
     17 namespace mozilla {
     18 namespace ct {
     19 
     20 using namespace mozilla::pkix;
     21 
     22 typedef mozilla::pkix::Result Result;
     23 
     24 // Common prefix lengths
     25 static const size_t kLogIdPrefixLengthBytes = 1;
     26 static const size_t kBTTreeSizeLength = 8;
     27 static const size_t kNodeHashPrefixLengthBytes = 1;
     28 
     29 // Members of a SignedTreeHeadDataV2 struct
     30 static const size_t kSTHTimestampLength = 8;
     31 static const size_t kSTHExtensionsLengthBytes = 2;
     32 static const size_t kSTHSignatureLengthBytes = 2;
     33 
     34 // Members of a Inclusion Proof struct
     35 static const size_t kLeafIndexLength = 8;
     36 static const size_t kInclusionPathLengthBytes = 2;
     37 
     38 static Result GetDigestAlgorithmLengthAndIdentifier(
     39    DigestAlgorithm digestAlgorithm,
     40    /* out */ size_t& digestAlgorithmLength,
     41    /* out */ SECOidTag& digestAlgorithmId) {
     42  switch (digestAlgorithm) {
     43    case DigestAlgorithm::sha512:
     44      digestAlgorithmLength = SHA512_LENGTH;
     45      digestAlgorithmId = SEC_OID_SHA512;
     46      return Success;
     47    case DigestAlgorithm::sha256:
     48      digestAlgorithmLength = SHA256_LENGTH;
     49      digestAlgorithmId = SEC_OID_SHA256;
     50      return Success;
     51    default:
     52      return pkix::Result::FATAL_ERROR_INVALID_ARGS;
     53  }
     54 }
     55 
     56 Result DecodeAndVerifySignedTreeHead(
     57    Input signerSubjectPublicKeyInfo, DigestAlgorithm digestAlgorithm,
     58    der::PublicKeyAlgorithm publicKeyAlgorithm, Input signedTreeHeadInput,
     59    /* out */ SignedTreeHeadDataV2& signedTreeHead) {
     60  SignedTreeHeadDataV2 result;
     61  Reader reader(signedTreeHeadInput);
     62 
     63  Input logId;
     64  Result rv = ReadVariableBytes<kLogIdPrefixLengthBytes>(reader, logId);
     65  if (rv != Success) {
     66    return rv;
     67  }
     68  InputToBuffer(logId, result.logId);
     69 
     70  // This is the beginning of the data covered by the signature.
     71  Reader::Mark signedDataMark = reader.GetMark();
     72 
     73  rv = ReadUint<kSTHTimestampLength>(reader, result.timestamp);
     74  if (rv != Success) {
     75    return rv;
     76  }
     77 
     78  rv = ReadUint<kBTTreeSizeLength>(reader, result.treeSize);
     79  if (rv != Success) {
     80    return rv;
     81  }
     82 
     83  Input hash;
     84  rv = ReadVariableBytes<kNodeHashPrefixLengthBytes>(reader, hash);
     85  if (rv != Success) {
     86    return rv;
     87  }
     88  InputToBuffer(hash, result.rootHash);
     89 
     90  // We ignore any extensions, but we have to read them.
     91  Input extensionsInput;
     92  rv = ReadVariableBytes<kSTHExtensionsLengthBytes>(reader, extensionsInput);
     93  if (rv != Success) {
     94    return rv;
     95  }
     96 
     97  Input signedDataInput;
     98  rv = reader.GetInput(signedDataMark, signedDataInput);
     99  if (rv != Success) {
    100    return rv;
    101  }
    102 
    103  Input signatureInput;
    104  rv = ReadVariableBytes<kSTHSignatureLengthBytes>(reader, signatureInput);
    105  if (rv != Success) {
    106    return rv;
    107  }
    108 
    109  switch (publicKeyAlgorithm) {
    110    case der::PublicKeyAlgorithm::ECDSA:
    111      rv = VerifyECDSASignedDataNSS(signedDataInput, digestAlgorithm,
    112                                    signatureInput, signerSubjectPublicKeyInfo,
    113                                    nullptr);
    114      break;
    115    case der::PublicKeyAlgorithm::RSA_PKCS1:
    116    default:
    117      return Result::FATAL_ERROR_INVALID_ARGS;
    118  }
    119  if (rv != Success) {
    120    return rv;
    121  }
    122 
    123  if (!reader.AtEnd()) {
    124    return pkix::Result::ERROR_BAD_DER;
    125  }
    126 
    127  signedTreeHead = std::move(result);
    128  return Success;
    129 }
    130 
    131 Result DecodeInclusionProof(Input input, InclusionProofDataV2& output) {
    132  InclusionProofDataV2 result;
    133  Reader reader(input);
    134 
    135  Input logId;
    136  Result rv = ReadVariableBytes<kLogIdPrefixLengthBytes>(reader, logId);
    137  if (rv != Success) {
    138    return rv;
    139  }
    140 
    141  rv = ReadUint<kBTTreeSizeLength>(reader, result.treeSize);
    142  if (rv != Success) {
    143    return rv;
    144  }
    145 
    146  if (result.treeSize < 1) {
    147    return pkix::Result::ERROR_BAD_DER;
    148  }
    149 
    150  rv = ReadUint<kLeafIndexLength>(reader, result.leafIndex);
    151  if (rv != Success) {
    152    return rv;
    153  }
    154 
    155  if (result.leafIndex >= result.treeSize) {
    156    return pkix::Result::ERROR_BAD_DER;
    157  }
    158 
    159  Input pathInput;
    160  rv = ReadVariableBytes<kInclusionPathLengthBytes>(reader, pathInput);
    161  if (rv != Success) {
    162    return rv;
    163  }
    164 
    165  if (pathInput.GetLength() < 1) {
    166    return pkix::Result::ERROR_BAD_DER;
    167  }
    168 
    169  Reader pathReader(pathInput);
    170  std::vector<Buffer> inclusionPath;
    171 
    172  while (!pathReader.AtEnd()) {
    173    Input hash;
    174    rv = ReadVariableBytes<kNodeHashPrefixLengthBytes>(pathReader, hash);
    175    if (rv != Success) {
    176      return rv;
    177    }
    178 
    179    Buffer hashBuffer;
    180    InputToBuffer(hash, hashBuffer);
    181 
    182    inclusionPath.push_back(std::move(hashBuffer));
    183  }
    184 
    185  if (!reader.AtEnd()) {
    186    return pkix::Result::ERROR_BAD_DER;
    187  }
    188 
    189  InputToBuffer(logId, result.logId);
    190 
    191  result.inclusionPath = std::move(inclusionPath);
    192 
    193  output = std::move(result);
    194  return Success;
    195 }
    196 
    197 static Result CommonFinishDigest(UniquePK11Context& context,
    198                                 size_t digestAlgorithmLength,
    199                                 /* out */ Buffer& outputBuffer) {
    200  uint32_t outLen = 0;
    201  outputBuffer.assign(digestAlgorithmLength, 0);
    202  if (PK11_DigestFinal(context.get(), outputBuffer.data(), &outLen,
    203                       digestAlgorithmLength) != SECSuccess) {
    204    return Result::FATAL_ERROR_LIBRARY_FAILURE;
    205  }
    206  if (outLen != digestAlgorithmLength) {
    207    return Result::FATAL_ERROR_LIBRARY_FAILURE;
    208  }
    209  return Success;
    210 }
    211 
    212 static Result LeafHash(Input leafEntry, size_t digestAlgorithmLength,
    213                       SECOidTag digestAlgorithmId,
    214                       /* out */ Buffer& calculatedHash) {
    215  UniquePK11Context context(PK11_CreateDigestContext(digestAlgorithmId));
    216  if (!context) {
    217    return Result::FATAL_ERROR_LIBRARY_FAILURE;
    218  }
    219  const unsigned char zero = 0;
    220  if (PK11_DigestOp(context.get(), &zero, 1u) != SECSuccess) {
    221    return Result::FATAL_ERROR_LIBRARY_FAILURE;
    222  }
    223  SECItem leafEntryItem = UnsafeMapInputToSECItem(leafEntry);
    224  if (PK11_DigestOp(context.get(), leafEntryItem.data, leafEntryItem.len) !=
    225      SECSuccess) {
    226    return Result::FATAL_ERROR_LIBRARY_FAILURE;
    227  }
    228  return CommonFinishDigest(context, digestAlgorithmLength, calculatedHash);
    229 }
    230 
    231 static Result NodeHash(const Buffer& left, const Buffer& right,
    232                       size_t digestAlgorithmLength,
    233                       SECOidTag digestAlgorithmId,
    234                       /* out */ Buffer& calculatedHash) {
    235  UniquePK11Context context(PK11_CreateDigestContext(digestAlgorithmId));
    236  if (!context) {
    237    return Result::FATAL_ERROR_LIBRARY_FAILURE;
    238  }
    239  const unsigned char one = 1;
    240  if (PK11_DigestOp(context.get(), &one, 1u) != SECSuccess) {
    241    return Result::FATAL_ERROR_LIBRARY_FAILURE;
    242  }
    243  if (PK11_DigestOp(context.get(), left.data(), left.size()) != SECSuccess) {
    244    return Result::FATAL_ERROR_LIBRARY_FAILURE;
    245  }
    246  if (PK11_DigestOp(context.get(), right.data(), right.size()) != SECSuccess) {
    247    return Result::FATAL_ERROR_LIBRARY_FAILURE;
    248  }
    249  return CommonFinishDigest(context, digestAlgorithmLength, calculatedHash);
    250 }
    251 
    252 // This algorithm is specified by:
    253 // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-28#section-2.1.3.2
    254 Result VerifyInclusionProof(const InclusionProofDataV2& proof, Input leafEntry,
    255                            Input expectedRootHash,
    256                            DigestAlgorithm digestAlgorithm) {
    257  if (proof.treeSize == 0) {
    258    return pkix::Result::ERROR_BAD_SIGNATURE;
    259  }
    260  size_t digestAlgorithmLength;
    261  SECOidTag digestAlgorithmId;
    262  Result rv = GetDigestAlgorithmLengthAndIdentifier(
    263      digestAlgorithm, digestAlgorithmLength, digestAlgorithmId);
    264  if (rv != Success) {
    265    return rv;
    266  }
    267  if (proof.leafIndex >= proof.treeSize) {
    268    return pkix::Result::ERROR_BAD_SIGNATURE;
    269  }
    270  if (expectedRootHash.GetLength() != digestAlgorithmLength) {
    271    return pkix::Result::ERROR_BAD_SIGNATURE;
    272  }
    273  uint64_t leafIndex = proof.leafIndex;
    274  uint64_t lastNodeIndex = proof.treeSize - 1;
    275  Buffer calculatedHash;
    276  rv = LeafHash(leafEntry, digestAlgorithmLength, digestAlgorithmId,
    277                calculatedHash);
    278  if (rv != Success) {
    279    return rv;
    280  }
    281  for (const auto& hash : proof.inclusionPath) {
    282    if (lastNodeIndex == 0) {
    283      return pkix::Result::ERROR_BAD_SIGNATURE;
    284    }
    285    if (leafIndex % 2 == 1 || leafIndex == lastNodeIndex) {
    286      rv = NodeHash(hash, calculatedHash, digestAlgorithmLength,
    287                    digestAlgorithmId, calculatedHash);
    288      if (rv != Success) {
    289        return rv;
    290      }
    291      if (leafIndex % 2 == 0) {
    292        while (leafIndex % 2 == 0 && lastNodeIndex > 0) {
    293          leafIndex >>= 1;
    294          lastNodeIndex >>= 1;
    295        }
    296      }
    297    } else {
    298      rv = NodeHash(calculatedHash, hash, digestAlgorithmLength,
    299                    digestAlgorithmId, calculatedHash);
    300      if (rv != Success) {
    301        return rv;
    302      }
    303    }
    304    leafIndex >>= 1;
    305    lastNodeIndex >>= 1;
    306  }
    307  if (lastNodeIndex != 0) {
    308    return pkix::Result::ERROR_BAD_SIGNATURE;
    309  }
    310  assert(calculatedHash.size() == digestAlgorithmLength);
    311  if (calculatedHash.size() != digestAlgorithmLength) {
    312    return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
    313  }
    314  if (memcmp(calculatedHash.data(), expectedRootHash.UnsafeGetData(),
    315             digestAlgorithmLength) != 0) {
    316    return pkix::Result::ERROR_BAD_SIGNATURE;
    317  }
    318  return Success;
    319 }
    320 
    321 }  // namespace ct
    322 }  // namespace mozilla