Bug 1343202 - Utility function for decoding an InclusionProof structure; r=ckerschb,keeler

MozReview-Commit-ID: 1x2Cwan8nLL

--HG--
extra : rebase_source : 079a8945f4d04be06dd99b776246d9b96930613a
This commit is contained in:
Stephanie Ouillon 2017-08-18 09:50:49 +02:00
Родитель 3bac94ec4a
Коммит 73e9f686e8
11 изменённых файлов: 561 добавлений и 4 удалений

Просмотреть файл

@ -0,0 +1,41 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef BTInclusionProof_h
#define BTInclusionProof_h
#include "Buffer.h"
#include "mozilla/Vector.h"
namespace mozilla { namespace ct {
// Represents a Merkle inclusion proof for purposes of serialization,
// deserialization, and verification of the proof. The format for inclusion
// proofs in RFC 6962-bis is as follows:
//
// opaque LogID<2..127>;
// opaque NodeHash<32..2^8-1>;
//
// struct {
// LogID log_id;
// uint64 tree_size;
// uint64 leaf_index;
// NodeHash inclusion_path<1..2^16-1>;
// } InclusionProofDataV2;
const uint64_t kInitialPathLengthCapacity = 32;
struct InclusionProofDataV2
{
Buffer logId;
uint64_t treeSize;
uint64_t leafIndex;
Vector<Buffer, kInitialPathLengthCapacity> inclusionPath;
};
} } // namespace mozilla:ct
#endif // BTInclusionProof_h

Просмотреть файл

@ -0,0 +1,104 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BTVerifier.h"
#include "CTUtils.h"
#include "SignedCertificateTimestamp.h"
#include <stdint.h>
#include "mozilla/Assertions.h"
#include "mozilla/Move.h"
#include "mozilla/TypeTraits.h"
namespace mozilla { namespace ct {
using namespace mozilla::pkix;
typedef mozilla::pkix::Result Result;
// Members of a Inclusion Proof struct
static const size_t kLogIdPrefixLengthBytes = 1;
static const size_t kProofTreeSizeLength = 8;
static const size_t kLeafIndexLength = 8;
static const size_t kInclusionPathLengthBytes = 2;
static const size_t kNodeHashPrefixLengthBytes = 1;
Result
DecodeInclusionProof(pkix::Reader& reader, InclusionProofDataV2& output)
{
InclusionProofDataV2 result;
Input logId;
Result rv = ReadVariableBytes<kLogIdPrefixLengthBytes>(reader, logId);
if (rv != Success) {
return rv;
}
rv = ReadUint<kProofTreeSizeLength>(reader, result.treeSize);
if (rv != Success) {
return rv;
}
if (result.treeSize < 1) {
return pkix::Result::ERROR_BAD_DER;
}
rv = ReadUint<kLeafIndexLength>(reader, result.leafIndex);
if (rv != Success) {
return rv;
}
if (result.leafIndex >= result.treeSize) {
return pkix::Result::ERROR_BAD_DER;
}
Input pathInput;
rv = ReadVariableBytes<kInclusionPathLengthBytes>(reader, pathInput);
if (rv != Success) {
return rv;
}
if (pathInput.GetLength() < 1) {
return pkix::Result::ERROR_BAD_DER;
}
Reader pathReader(pathInput);
Vector<Buffer, kInitialPathLengthCapacity> inclusionPath;
while (!pathReader.AtEnd()) {
Input hash;
rv = ReadVariableBytes<kNodeHashPrefixLengthBytes>(pathReader, hash);
if (rv != Success) {
return rv;
}
Buffer hashBuffer;
rv = InputToBuffer(hash, hashBuffer);
if (rv != Success) {
return rv;
}
if (!inclusionPath.append(Move(hashBuffer))) {
return pkix::Result::FATAL_ERROR_NO_MEMORY;
}
}
if (!reader.AtEnd()){
return pkix::Result::ERROR_BAD_DER;
}
rv = InputToBuffer(logId, result.logId);
if (rv != Success) {
return rv;
}
result.inclusionPath = Move(inclusionPath);
output = Move(result);
return Success;
}
} } //namespace mozilla::ct

Просмотреть файл

@ -0,0 +1,23 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef BTVerifier_h
#define BTVerifier_h
#include "BTInclusionProof.h"
#include "pkix/Input.h"
#include "pkix/Result.h"
namespace mozilla { namespace ct {
// Decodes an Inclusion Proof (InclusionProofDataV2 as defined in RFC
// 6962-bis). This consumes the entirety of the input.
pkix::Result DecodeInclusionProof(pkix::Reader& input,
InclusionProofDataV2& output);
} } // namespace mozilla::ct
#endif // BTVerifier_h

Просмотреть файл

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CTSerialization.h"
#include "CTUtils.h"
#include <stdint.h>
@ -72,7 +73,7 @@ UncheckedReadUint(size_t length, Reader& in, uint64_t& out)
// Performs overflow sanity checks and calls UncheckedReadUint.
template <size_t length, typename T>
static inline Result
Result
ReadUint(Reader& in, T& out)
{
uint64_t value;
@ -98,7 +99,7 @@ ReadFixedBytes(size_t length, Reader& in, Input& out)
// on success. |prefixLength| indicates the number of bytes needed to represent
// the length.
template <size_t prefixLength>
static inline Result
Result
ReadVariableBytes(Reader& in, Input& out)
{
size_t length;

Просмотреть файл

@ -0,0 +1,30 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef CTUtils_h
#define CTUtils_h
#include "pkix/Input.h"
#include "pkix/Result.h"
namespace mozilla { namespace ct {
// Reads a TLS-encoded variable length unsigned integer from |in|.
// The integer is expected to be in big-endian order, which is used by TLS.
// Note: checks if the output parameter overflows while reading.
// |length| indicates the size (in bytes) of the serialized integer.
template <size_t length, typename T>
pkix::Result ReadUint(Reader& in, T& out);
// Reads a length-prefixed variable amount of bytes from |in|, updating |out|
// on success. |prefixLength| indicates the number of bytes needed to represent
// the length.
template <size_t prefixLength>
pkix::Result ReadVariableBytes(Reader& in, Input& out);
} } // namespace mozilla::ct
#endif //CTUtils_h

Просмотреть файл

@ -9,6 +9,8 @@ with Files("**"):
EXPORTS += [
'BRNameMatchingPolicy.h',
'BTInclusionProof.h',
'BTVerifier.h',
'Buffer.h',
'CertVerifier.h',
'CTLog.h',
@ -21,6 +23,7 @@ EXPORTS += [
UNIFIED_SOURCES += [
'BRNameMatchingPolicy.cpp',
'BTVerifier.cpp',
'Buffer.cpp',
'CertVerifier.cpp',
'CTDiversityPolicy.cpp',

Просмотреть файл

@ -0,0 +1,160 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BTVerifier.h"
#include "CTTestUtils.h"
#include "gtest/gtest.h"
namespace mozilla { namespace ct {
using namespace pkix;
class BTSerializationTest : public ::testing::Test
{
public:
void SetUp() override
{
mTestInclusionProof = GetTestInclusionProof();
mTestInclusionProofUnexpectedData = GetTestInclusionProofUnexpectedData();
mTestInclusionProofInvalidHashSize = GetTestInclusionProofInvalidHashSize();
mTestInclusionProofInvalidHash = GetTestInclusionProofInvalidHash();
mTestInclusionProofMissingLogId = GetTestInclusionProofMissingLogId();
mTestInclusionProofNullPathLength = GetTestInclusionProofNullPathLength();
mTestInclusionProofPathLengthTooSmall = GetTestInclusionProofPathLengthTooSmall();
mTestInclusionProofPathLengthTooLarge = GetTestInclusionProofPathLengthTooLarge();
mTestInclusionProofNullTreeSize = GetTestInclusionProofNullTreeSize();
mTestInclusionProofLeafIndexOutOfBounds = GetTestInclusionProofLeafIndexOutOfBounds();
mTestInclusionProofExtraData = GetTestInclusionProofExtraData();
}
protected:
Buffer mTestInclusionProof;
Buffer mTestInclusionProofUnexpectedData;
Buffer mTestInclusionProofInvalidHashSize;
Buffer mTestInclusionProofInvalidHash;
Buffer mTestInclusionProofMissingLogId;
Buffer mTestInclusionProofNullPathLength;
Buffer mTestInclusionProofPathLengthTooSmall;
Buffer mTestInclusionProofPathLengthTooLarge;
Buffer mTestInclusionProofNullTreeSize;
Buffer mTestInclusionProofLeafIndexOutOfBounds;
Buffer mTestInclusionProofExtraData;
};
TEST_F(BTSerializationTest, DecodesInclusionProof)
{
const uint64_t expectedTreeSize = 4;
const uint64_t expectedLeafIndex = 2;
const uint64_t expectedInclusionPathElements = 2;
const uint8_t EXPECTED_LOGID[] = { 0x01, 0x00 };
Buffer expectedLogId;
MOZ_RELEASE_ASSERT(expectedLogId.append(EXPECTED_LOGID, 2));
Input encodedProofInput = InputForBuffer(mTestInclusionProof);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Success, DecodeInclusionProof(encodedProofReader, ipr));
EXPECT_EQ(expectedLogId, ipr.logId);
EXPECT_EQ(expectedTreeSize, ipr.treeSize);
EXPECT_EQ(expectedLeafIndex, ipr.leafIndex);
EXPECT_EQ(expectedInclusionPathElements, ipr.inclusionPath.length());
EXPECT_EQ(GetTestNodeHash0(), ipr.inclusionPath[0]);
EXPECT_EQ(GetTestNodeHash1(), ipr.inclusionPath[1]);
}
TEST_F(BTSerializationTest, FailsDecodingInclusionProofUnexpectedData)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofUnexpectedData);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
TEST_F(BTSerializationTest, FailsDecodingInvalidHashSize)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofInvalidHashSize);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
TEST_F(BTSerializationTest, FailsDecodingInvalidHash)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofInvalidHash);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
TEST_F(BTSerializationTest, FailsDecodingMissingLogId)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofMissingLogId);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
TEST_F(BTSerializationTest, FailsDecodingNullPathLength)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofNullPathLength);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
TEST_F(BTSerializationTest, FailsDecodingPathLengthTooSmall)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofPathLengthTooSmall);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
TEST_F(BTSerializationTest, FailsDecodingPathLengthTooLarge)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofPathLengthTooLarge);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
TEST_F(BTSerializationTest, FailsDecodingNullTreeSize)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofNullTreeSize);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
TEST_F(BTSerializationTest, FailsDecodingLeafIndexOutOfBounds)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofLeafIndexOutOfBounds);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
TEST_F(BTSerializationTest, FailsDecodingExtraData)
{
Input encodedProofInput = InputForBuffer(mTestInclusionProofExtraData);
Reader encodedProofReader(encodedProofInput);
InclusionProofDataV2 ipr;
ASSERT_EQ(Result::ERROR_BAD_DER, DecodeInclusionProof(encodedProofReader, ipr));
}
} } // namespace mozilla::ct

Просмотреть файл

@ -267,5 +267,4 @@ TEST_F(CTSerializationTest, EncodesValidSignedTreeHead)
MOZ_RELEASE_ASSERT(expectedBuffer.append(hash.begin(), hash.length()));
EXPECT_EQ(expectedBuffer, encoded);
}
} } // namespace mozilla::ct
} } // namespace mozilla::ct

Просмотреть файл

@ -9,6 +9,7 @@
#include <stdint.h>
#include <iomanip>
#include "BTInclusionProof.h"
#include "CTSerialization.h"
#include "gtest/gtest.h"
#include "mozilla/Assertions.h"
@ -326,6 +327,104 @@ const char kTestEmbeddedWithIntermediatePreCaCertData[] =
"041d31bda8e2dd6d39b3664de5ce0870f5fc7e6a00d6ed00528458d953d2"
"37586d73";
// Given the ordered set of data [ 0x00, 0x01, 0x02, deadbeef ],
// the 'inclusion proof' of the leaf of index '2' (for '0x02') is created from
// the Merkle Tree generated for that set of data.
// A Merkle inclusion proof for a leaf in a Merkle Tree is the shortest list
// of additional nodes in the Merkle Tree required to compute the Merkle Tree
// Hash (also called 'Merkle Tree head') for that tree.
// This follows the structure defined in RFC 6962-bis.
//
// https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-24#section-2.1
const char kTestInclusionProof[] =
"020100" // logId
"0000000000000004" // tree size
"0000000000000002" // leaf index
"0042" // inclusion path length
"2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
const char kTestNodeHash0[] =
"48c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729";
const char kTestNodeHash1[] =
"a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a";
const char kTestInclusionProofUnexpectedData[] = "12345678";
const char kTestInclusionProofInvalidHashSize[] =
"020100" // logId
"0000000000000004" // treesize
"0000000000000002" // leafindex
"0042" // inclusion path length
"3048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // invalid hash size
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
const char kTestInclusionProofInvalidHash[] =
"020100" // logId
"0000000000000004" // treesize
"0000000000000002" // leafindex
"0042" // inclusion path length
"2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427"; // truncated node hash 1
const char kTestInclusionProofMissingLogId[] =
"0000000000000004" // treesize
"0000000000000002" // leafindex
"0042"
"2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
const char kTestInclusionProofNullPathLength[] =
"020100"
"0000000000000004" // treesize
"0000000000000002" // leafindex
"0000"
"2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
const char kTestInclusionProofPathLengthTooSmall[] =
"020100"
"0000000000000004" // treesize
"0000000000000002" // leafindex
"0036"
"2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
const char kTestInclusionProofPathLengthTooLarge[] =
"020100"
"0000000000000004" // treesize
"0000000000000002" // leafindex
"0080"
"2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
const char kTestInclusionProofNullTreeSize[] =
"020100"
"0000000000000000" // treesize
"0000000000000002" // leafindex
"0042"
"2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
const char kTestInclusionProofLeafIndexOutOfBounds[] =
"020100"
"0000000000000004" // treesize
"0000000000000004" // leafindex
"0042"
"2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a"; // node hash 1
const char kTestInclusionProofExtraData[] =
"020100" // logId
"0000000000000004" // tree size
"0000000000000002" // leaf index
"0042" // inclusion path length
"2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729" // node hash 0
"20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a" // node hash 1
"123456"; // extra data after the proof
static uint8_t
CharToByte(char c)
{
@ -406,6 +505,84 @@ GetTestSignedCertificateTimestamp()
return HexToBytes(kTestSignedCertificateTimestamp);
}
Buffer
GetTestInclusionProof()
{
return HexToBytes(kTestInclusionProof);
}
Buffer
GetTestInclusionProofUnexpectedData()
{
return HexToBytes(kTestInclusionProofUnexpectedData);
}
Buffer
GetTestInclusionProofInvalidHashSize()
{
return HexToBytes(kTestInclusionProofInvalidHashSize);
}
Buffer
GetTestInclusionProofInvalidHash()
{
return HexToBytes(kTestInclusionProofInvalidHash);
}
Buffer
GetTestInclusionProofMissingLogId()
{
return HexToBytes(kTestInclusionProofMissingLogId);
}
Buffer
GetTestInclusionProofNullPathLength()
{
return HexToBytes(kTestInclusionProofNullPathLength);
}
Buffer
GetTestInclusionProofPathLengthTooSmall()
{
return HexToBytes(kTestInclusionProofPathLengthTooSmall);
}
Buffer
GetTestInclusionProofPathLengthTooLarge()
{
return HexToBytes(kTestInclusionProofPathLengthTooLarge);
}
Buffer
GetTestInclusionProofNullTreeSize()
{
return HexToBytes(kTestInclusionProofNullTreeSize);
}
Buffer
GetTestInclusionProofLeafIndexOutOfBounds()
{
return HexToBytes(kTestInclusionProofLeafIndexOutOfBounds);
}
Buffer
GetTestInclusionProofExtraData()
{
return HexToBytes(kTestInclusionProofExtraData);
}
Buffer
GetTestNodeHash0()
{
return HexToBytes(kTestNodeHash0);
}
Buffer
GetTestNodeHash1()
{
return HexToBytes(kTestNodeHash1);
}
Buffer
GetTestPublicKey()
{

Просмотреть файл

@ -40,6 +40,24 @@ Buffer GetTestDigitallySignedData();
// Returns the binary representation of a test serialized SCT.
Buffer GetTestSignedCertificateTimestamp();
// Returns the binary representation of a test serialized InclusionProof.
Buffer GetTestInclusionProof();
Buffer GetTestInclusionProofUnexpectedData();
Buffer GetTestInclusionProofInvalidHashSize();
Buffer GetTestInclusionProofInvalidHash();
Buffer GetTestInclusionProofMissingLogId();
Buffer GetTestInclusionProofNullPathLength();
Buffer GetTestInclusionProofPathLengthTooSmall();
Buffer GetTestInclusionProofPathLengthTooLarge();
Buffer GetTestInclusionProofNullTreeSize();
Buffer GetTestInclusionProofLeafIndexOutOfBounds();
Buffer GetTestInclusionProofExtraData();
// Returns the binary representation of test serialized node hashs from an
// inclusion proof.
Buffer GetTestNodeHash0();
Buffer GetTestNodeHash1();
// Test log key.
Buffer GetTestPublicKey();

Просмотреть файл

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
SOURCES += [
'BTSerializationTest.cpp',
'CTDiversityPolicyTest.cpp',
'CTLogVerifierTest.cpp',
'CTObjectsExtractorTest.cpp',