@@ -5,16 +5,24 @@ import (
5
5
"crypto/ecdsa"
6
6
"crypto/elliptic"
7
7
"crypto/rand"
8
+ "crypto/x509"
9
+ "encoding/pem"
10
+ "fmt"
8
11
"sync"
9
12
"testing"
10
13
11
14
"filippo.io/litetlog/internal/ctlog"
15
+ "filippo.io/litetlog/internal/tlogx"
16
+ "golang.org/x/crypto/cryptobyte"
17
+ "golang.org/x/mod/sumdb/note"
18
+ "golang.org/x/mod/sumdb/tlog"
12
19
)
13
20
14
21
type TestLog struct {
15
22
Log * ctlog.Log
16
23
Backend * MemoryBackend
17
24
Key * ecdsa.PrivateKey
25
+ t testing.TB
18
26
}
19
27
20
28
func NewEmptyTestLog (t testing.TB ) * TestLog {
@@ -23,17 +31,141 @@ func NewEmptyTestLog(t testing.TB) *TestLog {
23
31
if err != nil {
24
32
t .Fatal (err )
25
33
}
34
+ k , err := x509 .MarshalPKCS8PrivateKey (key )
35
+ if err != nil {
36
+ t .Fatal (err )
37
+ }
38
+ t .Logf ("ECDSA key: %s" , pem .EncodeToMemory (& pem.Block {Type : "PRIVATE KEY" , Bytes : k }))
26
39
log , err := ctlog .NewLog ("example.com/TestLog" , key , backend )
27
40
if err != nil {
28
41
t .Fatal (err )
29
42
}
30
- return & TestLog {
43
+ return & TestLog {t : t ,
31
44
Log : log ,
32
45
Backend : backend ,
33
46
Key : key ,
34
47
}
35
48
}
36
49
50
+ func (tl * TestLog ) CheckLog () (sthTimestamp uint64 ) {
51
+ t := tl .t
52
+
53
+ sth , err := tl .Backend .Fetch (context .Background (), "sth" )
54
+ fatalIfErr (t , err )
55
+ v , err := tlogx .NewRFC6962Verifier ("example.com/TestLog" , tl .Key .Public ())
56
+ fatalIfErr (t , err )
57
+ v .Timestamp = func (t uint64 ) { sthTimestamp = t }
58
+ n , err := note .Open (sth , note .VerifierList (v ))
59
+ fatalIfErr (t , err )
60
+ c , err := tlogx .ParseCheckpoint (n .Text )
61
+ fatalIfErr (t , err )
62
+
63
+ if c .Origin != "example.com/TestLog" {
64
+ t .Errorf ("origin line is %q" , c .Origin )
65
+ }
66
+ if c .Extension != "" {
67
+ t .Errorf ("unexpected extension %q" , c .Extension )
68
+ }
69
+
70
+ if c .N == 0 {
71
+ if c .Hash != (tlog.Hash {}) {
72
+ t .Error ("empty log should have zero hash" )
73
+ }
74
+ return
75
+ }
76
+
77
+ tree := tlog.Tree {N : c .N , Hash : c .Hash }
78
+ var indexes []int64
79
+ for n := int64 (0 ); n < c .N ; n ++ {
80
+ indexes = append (indexes , tlog .StoredHashIndex (0 , n ))
81
+ }
82
+ // tlog.TileHashReader checks the inclusion of every hash in the provided
83
+ // tree, so this checks the validity of the whole Merkle tree.
84
+ leafHashes , err := tlog .TileHashReader (tree , (* tileReader )(tl )).ReadHashes (indexes )
85
+ fatalIfErr (t , err )
86
+
87
+ lastTile := tlog .TileForIndex (tileHeight , tlog .StoredHashIndex (0 , c .N - 1 ))
88
+ lastTile .L = - 1
89
+ for n := int64 (0 ); n <= lastTile .N ; n ++ {
90
+ tile := tlog.Tile {H : tileHeight , L : - 1 , N : n , W : tileWidth }
91
+ if n == lastTile .N {
92
+ tile = lastTile
93
+ }
94
+ b , err := tl .Backend .Fetch (context .Background (), tile .Path ())
95
+ fatalIfErr (t , err )
96
+ s := cryptobyte .String (b )
97
+ for i := 0 ; i < tile .W ; i ++ {
98
+ start := len (b ) - len (s )
99
+ var timestamp uint64
100
+ var entryType uint8
101
+ var cert , extensions []byte
102
+ if ! s .ReadUint64 (& timestamp ) || ! s .ReadUint8 (& entryType ) {
103
+ t .Fatalf ("invalid data tile %v around index %d" , tile , len (b )- len (s ))
104
+ }
105
+ switch entryType {
106
+ case 0 : // x509_entry
107
+ if ! s .ReadUint24LengthPrefixed ((* cryptobyte .String )(& cert )) {
108
+ t .Fatalf ("invalid data tile %v around index %d" , tile , len (b )- len (s ))
109
+ }
110
+ case 1 : // precert_entry
111
+ panic ("unimplemented" ) // TODO
112
+ default :
113
+ t .Fatalf ("invalid data tile %v: unknown type %d" , tile , entryType )
114
+ }
115
+ if ! s .ReadUint16LengthPrefixed ((* cryptobyte .String )(& extensions )) {
116
+ t .Fatalf ("invalid data tile %v around index %d" , tile , len (b )- len (s ))
117
+ }
118
+ end := len (b ) - len (s )
119
+
120
+ idx := n * tileWidth + int64 (i )
121
+ if timestamp > sthTimestamp {
122
+ t .Errorf ("STH timestamp %d is before record %d timestamp %d" , sthTimestamp , idx , timestamp )
123
+ }
124
+ got := tlog .RecordHash (append ([]byte {0 , 0 }, b [start :end ]... ))
125
+ if exp := leafHashes [idx ]; got != exp {
126
+ t .Errorf ("tile leaf entry %d hashes to %v, level 0 hash is %v" , idx , got , exp )
127
+ }
128
+ }
129
+ if ! s .Empty () {
130
+ t .Errorf ("invalid data tile %v: trailing data" , tile )
131
+ }
132
+ }
133
+
134
+ return
135
+ }
136
+
137
+ const tileHeight = 10
138
+ const tileWidth = 1 << tileHeight
139
+
140
+ type tileReader TestLog
141
+
142
+ func (r * tileReader ) Height () int {
143
+ return tileHeight
144
+ }
145
+
146
+ func (r * tileReader ) ReadTiles (tiles []tlog.Tile ) (data [][]byte , err error ) {
147
+ for _ , t := range tiles {
148
+ b , err := r .Backend .Fetch (context .Background (), t .Path ())
149
+ if err != nil {
150
+ return nil , err
151
+ }
152
+ data = append (data , b )
153
+ }
154
+ return data , nil
155
+ }
156
+
157
+ func (r * tileReader ) SaveTiles (tiles []tlog.Tile , data [][]byte ) {}
158
+
159
+ type verifier struct {
160
+ name string
161
+ hash uint32
162
+ verify func (msg , sig []byte ) bool
163
+ }
164
+
165
+ func (v * verifier ) Name () string { return v .name }
166
+ func (v * verifier ) KeyHash () uint32 { return v .hash }
167
+ func (v * verifier ) Verify (msg , sig []byte ) bool { return v .verify (msg , sig ) }
168
+
37
169
type MemoryBackend struct {
38
170
t testing.TB
39
171
mu sync.Mutex
@@ -47,6 +179,10 @@ func NewMemoryBackend(t testing.TB) *MemoryBackend {
47
179
}
48
180
49
181
func (b * MemoryBackend ) Upload (ctx context.Context , key string , data []byte ) error {
182
+ // TODO: check key format is expected.
183
+ if len (data ) == 0 {
184
+ b .t .Errorf ("uploaded key %q with empty data" , key )
185
+ }
50
186
if err := ctx .Err (); err != nil {
51
187
return err
52
188
}
@@ -55,3 +191,23 @@ func (b *MemoryBackend) Upload(ctx context.Context, key string, data []byte) err
55
191
b .m [key ] = data
56
192
return nil
57
193
}
194
+
195
+ func (b * MemoryBackend ) Fetch (ctx context.Context , key string ) ([]byte , error ) {
196
+ if err := ctx .Err (); err != nil {
197
+ return nil , err
198
+ }
199
+ b .mu .Lock ()
200
+ defer b .mu .Unlock ()
201
+ data , ok := b .m [key ]
202
+ if ! ok {
203
+ return nil , fmt .Errorf ("key %q not found" , key )
204
+ }
205
+ return data , nil
206
+ }
207
+
208
+ func fatalIfErr (t testing.TB , err error ) {
209
+ t .Helper ()
210
+ if err != nil {
211
+ t .Fatal (err )
212
+ }
213
+ }
0 commit comments