Skip to content

Commit bcf07ff

Browse files
authored
Add MTCLog structures and serialization (#1079)
* Add MTCLog serialization structures and validation * comments * [MTC] Require extensions to be strictly sorted and unique in MTCLogEntry
1 parent fb252ac commit bcf07ff

4 files changed

Lines changed: 825 additions & 1 deletion

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2026 The Tessera authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package entry
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
21+
"golang.org/x/crypto/cryptobyte"
22+
)
23+
24+
// Standard log entry type constants from Section 5.2.1.
25+
const (
26+
MTCLogEntryTypeNull EntryType = 0
27+
MTCLogEntryTypeTBSCert EntryType = 1
28+
29+
// SPEC: draft-ietf-plants-merkle-tree-certs section 5.2.1.
30+
// "An MTCLogEntry's size SHOULD NOT exceed 65535 (2^16-1) bytes.
31+
// Doing so may exceed size limits in common log-serving protocols,
32+
// such as [TLOG-TILES]."
33+
MaxMTCLogEntrySize = 1<<16 - 1
34+
)
35+
36+
// MTCLogEntryExtension represents a single key-value metadata extension
37+
// appended to an MTCLogEntry.
38+
type MTCLogEntryExtension struct {
39+
Type ExtensionType // 2-byte extension identifier
40+
Data []byte // Opaque data payload
41+
}
42+
43+
// ExtensionType represents the Type field of an MTCLogentryExtension.
44+
type ExtensionType uint16
45+
46+
// EntryType represents the Type field of an MTCLogentry.
47+
type EntryType uint16
48+
49+
// MTCLogEntry represents leaf node as defined in
50+
// draft-ietf-plants-merkle-tree-certs section 5.2.1.
51+
type MTCLogEntry struct {
52+
Extensions []MTCLogEntryExtension
53+
Type EntryType // MTCLogEntryTypeNull, MTCLogEntryTypeTBSCert
54+
EntryData []byte // Raw DER bytes of TBSCertificateLogEntry if Type is TBSCert
55+
}
56+
57+
// Marshal encodes the MTCLogEntry into TLS Presentation bytes.
58+
//
59+
// Returns an error if the extensions are not specs compliant, or if the
60+
// resulting bytes do not fit in a t-log leaf.
61+
func (e *MTCLogEntry) Marshal() ([]byte, error) {
62+
if e.Type == MTCLogEntryTypeNull && len(e.EntryData) > 0 {
63+
return nil, errors.New("null entry must have empty EntryData")
64+
}
65+
// struct {} Empty;
66+
//
67+
// enum { (2^16-1) } MTCLogEntryExtensionType;
68+
//
69+
// struct {
70+
// MTCLogEntryExtensionType extension_type;
71+
// opaque extension_data<0..2^16-1>;
72+
// } MTCLogEntryExtension;
73+
//
74+
// enum {
75+
// null_entry(0), tbs_cert_entry(1), (2^16-1)
76+
// } MTCLogEntryType;
77+
//
78+
// struct {
79+
// MTCLogEntryExtension extensions<0..2^16-1>;
80+
// MTCLogEntryType type;
81+
// select (type) {
82+
// case null_entry: Empty;
83+
// case tbs_cert_entry: opaque tbs_cert_entry_data[N];
84+
// /* May be extended with future types. */
85+
// }
86+
// } MTCLogEntry;
87+
var b cryptobyte.Builder
88+
89+
// SPEC: draft-ietf-plants-merkle-tree-certs section 5.2.1.
90+
// "The extensions list MUST appear in ascending order by extension_type and
91+
// MUST NOT contain two extensions with the same extension_type."
92+
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
93+
for i, ext := range e.Extensions {
94+
if i > 0 {
95+
prevType := e.Extensions[i-1].Type
96+
if ext.Type == prevType {
97+
child.SetError(fmt.Errorf("duplicate extension type %d", ext.Type))
98+
return
99+
}
100+
if ext.Type < prevType {
101+
child.SetError(fmt.Errorf("extensions out of order: type %d appears after %d", ext.Type, prevType))
102+
return
103+
}
104+
}
105+
child.AddUint16(uint16(ext.Type))
106+
// Each extension data block has its own 16-bit length prefix.
107+
child.AddUint16LengthPrefixed(func(dataBlock *cryptobyte.Builder) {
108+
dataBlock.AddBytes(ext.Data)
109+
})
110+
}
111+
})
112+
113+
b.AddUint16(uint16(e.Type))
114+
115+
// EntryData fills up the rest of the structure, no size prefix needed.
116+
b.AddBytes(e.EntryData)
117+
118+
res, err := b.Bytes()
119+
if err != nil {
120+
return nil, err
121+
}
122+
// SPEC: draft-ietf-plants-merkle-tree-certs section 5.2.1.
123+
// "An MTCLogEntry's size SHOULD NOT exceed 65535 (2^16-1) bytes.
124+
// Doing so may exceed size limits in common log-serving protocols,
125+
// such as [TLOG-TILES]."
126+
if l := len(res); l > MaxMTCLogEntrySize {
127+
return nil, fmt.Errorf("log entry size %d exceeds tile limit %d", l, MaxMTCLogEntrySize)
128+
}
129+
return res, nil
130+
}

0 commit comments

Comments
 (0)