1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
|
// Copyright 2024 The Tessera authors. 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.
package tessera
import (
"context"
"crypto/sha256"
"fmt"
"github.com/transparency-dev/merkle/rfc6962"
"github.com/transparency-dev/tessera/api/layout"
"github.com/transparency-dev/tessera/ctonly"
"golang.org/x/crypto/cryptobyte"
)
// NewCertificateTransparencyAppender returns a function which knows how to add a CT-specific entry type to the log.
//
// This entry point MUST ONLY be used for CT logs participating in the CT ecosystem.
// It should not be used as the basis for any other/new transparency application as this protocol:
// a) embodies some techniques which are not considered to be best practice (it does this to retain backawards-compatibility with RFC6962)
// b) is not compatible with the https://c2sp.org/tlog-tiles API which we _very strongly_ encourage you to use instead.
//
// Users of this MUST NOT call `Add` on the underlying Appender directly.
//
// Returns a future, which resolves to the assigned index in the log, or an error.
func NewCertificateTransparencyAppender(a *Appender) func(context.Context, *ctonly.Entry) IndexFuture {
return func(ctx context.Context, e *ctonly.Entry) IndexFuture {
return a.Add(ctx, convertCTEntry(e))
}
}
// convertCTEntry returns an Entry struct which will do the right thing for CT Static API logs.
//
// This MUST NOT be used for any other purpose.
func convertCTEntry(e *ctonly.Entry) *Entry {
r := &Entry{}
r.internal.Identity = e.Identity()
r.marshalForBundle = func(idx uint64) []byte {
r.internal.LeafHash = e.MerkleLeafHash(idx)
r.internal.Data = e.LeafData(idx)
return r.internal.Data
}
return r
}
// WithCTLayout instructs the underlying storage to use a Static CT API compatible scheme for layout.
func (o *AppendOptions) WithCTLayout() *AppendOptions {
o.entriesPath = ctEntriesPath
o.bundleIDHasher = ctBundleIDHasher
return o
}
// WithCTLayout instructs the underlying storage to use a Static CT API compatible scheme for layout.
func (o *MigrationOptions) WithCTLayout() *MigrationOptions {
o.entriesPath = ctEntriesPath
o.bundleIDHasher = ctBundleIDHasher
o.bundleLeafHasher = ctMerkleLeafHasher
return o
}
func ctEntriesPath(n uint64, p uint8) string {
return fmt.Sprintf("tile/data/%s", layout.NWithSuffix(0, n, p))
}
// ctBundleIDHasher knows how to calculate antispam identity hashes for entries in a Static-CT formatted entry bundle.
func ctBundleIDHasher(bundle []byte) ([][]byte, error) {
r := make([][]byte, 0, layout.EntryBundleWidth)
b := cryptobyte.String(bundle)
for i := 0; i < layout.EntryBundleWidth && !b.Empty(); i++ {
// Timestamp
if !b.Skip(8) {
return nil, fmt.Errorf("failed to read timestamp of entry index %d of bundle", i)
}
var entryType uint16
if !b.ReadUint16(&entryType) {
return nil, fmt.Errorf("failed to read entry type of entry index %d of bundle", i)
}
switch entryType {
case 0: // X509 entry
cert := cryptobyte.String{}
if !b.ReadUint24LengthPrefixed(&cert) {
return nil, fmt.Errorf("failed to read certificate at entry index %d of bundle", i)
}
// For x509 entries we hash (just) the x509 certificate for identity.
r = append(r, identityHash(cert))
// Must continue below to consume all the remaining bytes in the entry.
case 1: // Precert entry
// IssuerKeyHash
if !b.Skip(sha256.Size) {
return nil, fmt.Errorf("failed to read issuer key hash at entry index %d of bundle", i)
}
tbs := cryptobyte.String{}
if !b.ReadUint24LengthPrefixed(&tbs) {
return nil, fmt.Errorf("failed to read precert tbs at entry index %d of bundle", i)
}
default:
return nil, fmt.Errorf("unknown entry type at entry index %d of bundle", i)
}
ignore := cryptobyte.String{}
if !b.ReadUint16LengthPrefixed(&ignore) {
return nil, fmt.Errorf("failed to read SCT extensions at entry index %d of bundle", i)
}
if entryType == 1 {
precert := cryptobyte.String{}
if !b.ReadUint24LengthPrefixed(&precert) {
return nil, fmt.Errorf("failed to read precert at entry index %d of bundle", i)
}
// For Precert entries we hash (just) the full precertificate for identity.
r = append(r, identityHash(precert))
}
if !b.ReadUint16LengthPrefixed(&ignore) {
return nil, fmt.Errorf("failed to read chain fingerprints at entry index %d of bundle", i)
}
}
if !b.Empty() {
return nil, fmt.Errorf("unexpected %d bytes of trailing data in entry bundle", len(b))
}
return r, nil
}
// copyBytes copies N bytes between from and to.
func copyBytes(from *cryptobyte.String, to *cryptobyte.Builder, N int) bool {
b := make([]byte, N)
if !from.ReadBytes(&b, N) {
return false
}
to.AddBytes(b)
return true
}
// copyUint16LengthPrefixed copies a uint16 length and value between from and to.
func copyUint16LengthPrefixed(from *cryptobyte.String, to *cryptobyte.Builder) bool {
b := cryptobyte.String{}
if !from.ReadUint16LengthPrefixed(&b) {
return false
}
to.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) {
c.AddBytes(b)
})
return true
}
// copyUint24LengthPrefixed copies a uint24 length and value between from and to.
func copyUint24LengthPrefixed(from *cryptobyte.String, to *cryptobyte.Builder) bool {
b := cryptobyte.String{}
if !from.ReadUint24LengthPrefixed(&b) {
return false
}
to.AddUint24LengthPrefixed(func(c *cryptobyte.Builder) {
c.AddBytes(b)
})
return true
}
// ctMerkleLeafHasher knows how to calculate RFC6962 Merkle leaf hashes for entries in a Static-CT formatted entry bundle.
func ctMerkleLeafHasher(bundle []byte) ([][]byte, error) {
r := make([][]byte, 0, layout.EntryBundleWidth)
b := cryptobyte.String(bundle)
for i := 0; i < layout.EntryBundleWidth && !b.Empty(); i++ {
preimage := &cryptobyte.Builder{}
preimage.AddUint8(0 /* version = v1 */)
preimage.AddUint8(0 /* leaf_type = timestamped_entry */)
// Timestamp
if !copyBytes(&b, preimage, 8) {
return nil, fmt.Errorf("failed to copy timestamp of entry index %d of bundle", i)
}
var entryType uint16
if !b.ReadUint16(&entryType) {
return nil, fmt.Errorf("failed to read entry type of entry index %d of bundle", i)
}
preimage.AddUint16(entryType)
switch entryType {
case 0: // X509 entry
if !copyUint24LengthPrefixed(&b, preimage) {
return nil, fmt.Errorf("failed to copy certificate at entry index %d of bundle", i)
}
case 1: // Precert entry
// IssuerKeyHash
if !copyBytes(&b, preimage, sha256.Size) {
return nil, fmt.Errorf("failed to copy issuer key hash at entry index %d of bundle", i)
}
if !copyUint24LengthPrefixed(&b, preimage) {
return nil, fmt.Errorf("failed to copy precert tbs at entry index %d of bundle", i)
}
default:
return nil, fmt.Errorf("unknown entry type 0x%x at entry index %d of bundle", entryType, i)
}
if !copyUint16LengthPrefixed(&b, preimage) {
return nil, fmt.Errorf("failed to copy SCT extensions at entry index %d of bundle", i)
}
ignore := cryptobyte.String{}
if entryType == 1 {
if !b.ReadUint24LengthPrefixed(&ignore) {
return nil, fmt.Errorf("failed to read precert at entry index %d of bundle", i)
}
}
if !b.ReadUint16LengthPrefixed(&ignore) {
return nil, fmt.Errorf("failed to read chain fingerprints at entry index %d of bundle", i)
}
h := rfc6962.DefaultHasher.HashLeaf(preimage.BytesOrPanic())
r = append(r, h)
}
if !b.Empty() {
return nil, fmt.Errorf("unexpected %d bytes of trailing data in entry bundle", len(b))
}
return r, nil
}
|