@@ -17,6 +17,7 @@ package certmagic
17
17
import (
18
18
"bytes"
19
19
"context"
20
+ "io/fs"
20
21
"os"
21
22
"path/filepath"
22
23
"reflect"
@@ -26,6 +27,131 @@ import (
26
27
"time"
27
28
)
28
29
30
+ // memoryStorage is an in-memory storage implementation with known contents *and* fixed iteration order for List.
31
+ type memoryStorage struct {
32
+ contents []memoryStorageItem
33
+ }
34
+
35
+ type memoryStorageItem struct {
36
+ key string
37
+ data []byte
38
+ }
39
+
40
+ func (m * memoryStorage ) lookup (_ context.Context , key string ) * memoryStorageItem {
41
+ for _ , item := range m .contents {
42
+ if item .key == key {
43
+ return & item
44
+ }
45
+ }
46
+ return nil
47
+ }
48
+ func (m * memoryStorage ) Delete (ctx context.Context , key string ) error {
49
+ for i , item := range m .contents {
50
+ if item .key == key {
51
+ m .contents = append (m .contents [:i ], m .contents [i + 1 :]... )
52
+ return nil
53
+ }
54
+ }
55
+ return fs .ErrNotExist
56
+ }
57
+ func (m * memoryStorage ) Store (ctx context.Context , key string , value []byte ) error {
58
+ m .contents = append (m .contents , memoryStorageItem {key : key , data : value })
59
+ return nil
60
+ }
61
+ func (m * memoryStorage ) Exists (ctx context.Context , key string ) bool {
62
+ return m .lookup (ctx , key ) != nil
63
+ }
64
+ func (m * memoryStorage ) List (ctx context.Context , path string , recursive bool ) ([]string , error ) {
65
+ if recursive {
66
+ panic ("unimplemented" )
67
+ }
68
+
69
+ result := []string {}
70
+ nextitem:
71
+ for _ , item := range m .contents {
72
+ if ! strings .HasPrefix (item .key , path + "/" ) {
73
+ continue
74
+ }
75
+ name := strings .TrimPrefix (item .key , path + "/" )
76
+ if i := strings .Index (name , "/" ); i >= 0 {
77
+ name = name [:i ]
78
+ }
79
+
80
+ for _ , existing := range result {
81
+ if existing == name {
82
+ continue nextitem
83
+ }
84
+ }
85
+ result = append (result , name )
86
+ }
87
+ return result , nil
88
+ }
89
+ func (m * memoryStorage ) Load (ctx context.Context , key string ) ([]byte , error ) {
90
+ if item := m .lookup (ctx , key ); item != nil {
91
+ return item .data , nil
92
+ }
93
+ return nil , fs .ErrNotExist
94
+ }
95
+ func (m * memoryStorage ) Stat (ctx context.Context , key string ) (KeyInfo , error ) {
96
+ if item := m .lookup (ctx , key ); item != nil {
97
+ return KeyInfo {Key : key , Size : int64 (len (item .data ))}, nil
98
+ }
99
+ return KeyInfo {}, fs .ErrNotExist
100
+ }
101
+ func (m * memoryStorage ) Lock (ctx context.Context , name string ) error { panic ("unimplemented" ) }
102
+ func (m * memoryStorage ) Unlock (ctx context.Context , name string ) error { panic ("unimplemented" ) }
103
+
104
+ var _ Storage = (* memoryStorage )(nil )
105
+
106
+ type recordingStorage struct {
107
+ Storage
108
+ calls []recordedCall
109
+ }
110
+
111
+ func (r * recordingStorage ) Delete (ctx context.Context , key string ) error {
112
+ r .record ("Delete" , key )
113
+ return r .Storage .Delete (ctx , key )
114
+ }
115
+ func (r * recordingStorage ) Exists (ctx context.Context , key string ) bool {
116
+ r .record ("Exists" , key )
117
+ return r .Storage .Exists (ctx , key )
118
+ }
119
+ func (r * recordingStorage ) List (ctx context.Context , path string , recursive bool ) ([]string , error ) {
120
+ r .record ("List" , path , recursive )
121
+ return r .Storage .List (ctx , path , recursive )
122
+ }
123
+ func (r * recordingStorage ) Load (ctx context.Context , key string ) ([]byte , error ) {
124
+ r .record ("Load" , key )
125
+ return r .Storage .Load (ctx , key )
126
+ }
127
+ func (r * recordingStorage ) Lock (ctx context.Context , name string ) error {
128
+ r .record ("Lock" , name )
129
+ return r .Storage .Lock (ctx , name )
130
+ }
131
+ func (r * recordingStorage ) Stat (ctx context.Context , key string ) (KeyInfo , error ) {
132
+ r .record ("Stat" , key )
133
+ return r .Storage .Stat (ctx , key )
134
+ }
135
+ func (r * recordingStorage ) Store (ctx context.Context , key string , value []byte ) error {
136
+ r .record ("Store" , key )
137
+ return r .Storage .Store (ctx , key , value )
138
+ }
139
+ func (r * recordingStorage ) Unlock (ctx context.Context , name string ) error {
140
+ r .record ("Unlock" , name )
141
+ return r .Storage .Unlock (ctx , name )
142
+ }
143
+
144
+ type recordedCall struct {
145
+ name string
146
+ args []interface {}
147
+ }
148
+
149
+ func (r * recordingStorage ) record (name string , args ... interface {}) {
150
+ r .calls = append (r .calls , recordedCall {name : name , args : args })
151
+ }
152
+
153
+ var _ Storage = (* recordingStorage )(nil )
154
+
29
155
func TestNewAccount (t * testing.T ) {
30
156
am := & ACMEIssuer {CA : dummyCA , mu : new (sync.Mutex )}
31
157
testConfig := & Config {
@@ -159,6 +285,116 @@ func TestGetAccountAlreadyExists(t *testing.T) {
159
285
}
160
286
}
161
287
288
+ func TestGetAccountAlreadyExistsSkipsBroken (t * testing.T ) {
289
+ ctx := context .Background ()
290
+
291
+ am := & ACMEIssuer {CA : dummyCA , mu : new (sync.Mutex )}
292
+ testConfig := & Config {
293
+ Issuers : []Issuer {am },
294
+ Storage : & memoryStorage {},
295
+ Logger : defaultTestLogger ,
296
+ certCache : new (Cache ),
297
+ }
298
+ am .config = testConfig
299
+
300
+
301
+
302
+ // Create a "corrupted" account
303
+ am .
config .
Storage .
Store (
ctx ,
am .
storageKeyUserReg (
am .
CA ,
"[email protected] " ), []
byte (
"this is not a valid account" ))
304
+
305
+ // Create the actual account
306
+ account , err := am .newAccount (email )
307
+ if err != nil {
308
+ t .Fatalf ("Error creating account: %v" , err )
309
+ }
310
+ err = am .saveAccount (ctx , am .CA , account )
311
+ if err != nil {
312
+ t .Fatalf ("Error saving account: %v" , err )
313
+ }
314
+
315
+ // Expect to load account from disk
316
+ keyBytes , err := PEMEncodePrivateKey (account .PrivateKey )
317
+ if err != nil {
318
+ t .Fatalf ("Error encoding private key: %v" , err )
319
+ }
320
+
321
+ loadedAccount , err := am .GetAccount (ctx , keyBytes )
322
+ if err != nil {
323
+ t .Fatalf ("Error getting account: %v" , err )
324
+ }
325
+
326
+ // Assert keys are the same
327
+ if ! privateKeysSame (account .PrivateKey , loadedAccount .PrivateKey ) {
328
+ t .Error ("Expected private key to be the same after loading, but it wasn't" )
329
+ }
330
+
331
+ // Assert emails are the same
332
+ if ! reflect .DeepEqual (account .Contact , loadedAccount .Contact ) {
333
+ t .Errorf ("Expected contacts to be equal, but was '%s' before and '%s' after loading" , account .Contact , loadedAccount .Contact )
334
+ }
335
+ }
336
+
337
+ func TestGetAccountWithEmailAlreadyExists (t * testing.T ) {
338
+ ctx := context .Background ()
339
+
340
+ am := & ACMEIssuer {CA : dummyCA , mu : new (sync.Mutex )}
341
+ testConfig := & Config {
342
+ Issuers : []Issuer {am },
343
+ Storage : & recordingStorage {Storage : & memoryStorage {}},
344
+ Logger : defaultTestLogger ,
345
+ certCache : new (Cache ),
346
+ }
347
+ am .config = testConfig
348
+
349
+
350
+
351
+ // Set up test
352
+ account , err := am .newAccount (email )
353
+ if err != nil {
354
+ t .Fatalf ("Error creating account: %v" , err )
355
+ }
356
+ err = am .saveAccount (ctx , am .CA , account )
357
+ if err != nil {
358
+ t .Fatalf ("Error saving account: %v" , err )
359
+ }
360
+
361
+ // Set the expected email:
362
+ am .Email = email
363
+ err = am .setEmail (ctx , true )
364
+ if err != nil {
365
+ t .Fatalf ("setEmail error: %v" , err )
366
+ }
367
+
368
+ // Expect to load account from disk
369
+ keyBytes , err := PEMEncodePrivateKey (account .PrivateKey )
370
+ if err != nil {
371
+ t .Fatalf ("Error encoding private key: %v" , err )
372
+ }
373
+
374
+ loadedAccount , err := am .GetAccount (ctx , keyBytes )
375
+ if err != nil {
376
+ t .Fatalf ("Error getting account: %v" , err )
377
+ }
378
+
379
+ // Assert keys are the same
380
+ if ! privateKeysSame (account .PrivateKey , loadedAccount .PrivateKey ) {
381
+ t .Error ("Expected private key to be the same after loading, but it wasn't" )
382
+ }
383
+
384
+ // Assert emails are the same
385
+ if ! reflect .DeepEqual (account .Contact , loadedAccount .Contact ) {
386
+ t .Errorf ("Expected contacts to be equal, but was '%s' before and '%s' after loading" , account .Contact , loadedAccount .Contact )
387
+ }
388
+
389
+ // Assert that this was found without listing all accounts
390
+ rs := testConfig .Storage .(* recordingStorage )
391
+ for _ , call := range rs .calls {
392
+ if call .name == "List" {
393
+ t .Error ("Unexpected List call" )
394
+ }
395
+ }
396
+ }
397
+
162
398
func TestGetEmailFromPackageDefault (t * testing.T ) {
163
399
ctx := context .Background ()
164
400
0 commit comments