|
| 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