github.com/letsencrypt/boulder@v0.20251208.0/core/util_test.go (about) 1 package core 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "math" 9 "math/big" 10 "net/netip" 11 "os" 12 "slices" 13 "sort" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/go-jose/go-jose/v4" 19 "google.golang.org/grpc/codes" 20 "google.golang.org/grpc/status" 21 "google.golang.org/protobuf/types/known/durationpb" 22 "google.golang.org/protobuf/types/known/timestamppb" 23 24 "github.com/letsencrypt/boulder/identifier" 25 "github.com/letsencrypt/boulder/test" 26 ) 27 28 // challenges.go 29 func TestNewToken(t *testing.T) { 30 token := NewToken() 31 fmt.Println(token) 32 tokenLength := int(math.Ceil(32 * 8 / 6.0)) // 32 bytes, b64 encoded 33 if len(token) != tokenLength { 34 t.Fatalf("Expected token of length %d, got %d", tokenLength, len(token)) 35 } 36 collider := map[string]bool{} 37 // Test for very blatant RNG failures: 38 // Try 2^20 birthdays in a 2^72 search space... 39 // our naive collision probability here is 2^-32... 40 for range 1000000 { 41 token = NewToken()[:12] // just sample a portion 42 test.Assert(t, !collider[token], "Token collision!") 43 collider[token] = true 44 } 45 } 46 47 func TestLooksLikeAToken(t *testing.T) { 48 test.Assert(t, !looksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOS"), "Accepted short token") 49 test.Assert(t, !looksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOS%"), "Accepted invalid token") 50 test.Assert(t, looksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOSU"), "Rejected valid token") 51 } 52 53 func TestSerialUtils(t *testing.T) { 54 serial := SerialToString(big.NewInt(100000000000000000)) 55 test.AssertEquals(t, serial, "00000000000000000000016345785d8a0000") 56 57 serialNum, err := StringToSerial("00000000000000000000016345785d8a0000") 58 test.AssertNotError(t, err, "Couldn't convert serial number to *big.Int") 59 if serialNum.Cmp(big.NewInt(100000000000000000)) != 0 { 60 t.Fatalf("Incorrect conversion, got %d", serialNum) 61 } 62 63 badSerial, err := StringToSerial("doop!!!!000") 64 test.AssertContains(t, err.Error(), "invalid serial number") 65 fmt.Println(badSerial) 66 } 67 68 func TestBuildID(t *testing.T) { 69 test.AssertEquals(t, Unspecified, GetBuildID()) 70 } 71 72 const JWK1JSON = `{ 73 "kty": "RSA", 74 "n": "vuc785P8lBj3fUxyZchF_uZw6WtbxcorqgTyq-qapF5lrO1U82Tp93rpXlmctj6fyFHBVVB5aXnUHJ7LZeVPod7Wnfl8p5OyhlHQHC8BnzdzCqCMKmWZNX5DtETDId0qzU7dPzh0LP0idt5buU7L9QNaabChw3nnaL47iu_1Di5Wp264p2TwACeedv2hfRDjDlJmaQXuS8Rtv9GnRWyC9JBu7XmGvGDziumnJH7Hyzh3VNu-kSPQD3vuAFgMZS6uUzOztCkT0fpOalZI6hqxtWLvXUMj-crXrn-Maavz8qRhpAyp5kcYk3jiHGgQIi7QSK2JIdRJ8APyX9HlmTN5AQ", 75 "e": "AQAB" 76 }` 77 const JWK1Digest = `ul04Iq07ulKnnrebv2hv3yxCGgVvoHs8hjq2tVKx3mc=` 78 const JWK2JSON = `{ 79 "kty":"RSA", 80 "n":"yTsLkI8n4lg9UuSKNRC0UPHsVjNdCYk8rGXIqeb_rRYaEev3D9-kxXY8HrYfGkVt5CiIVJ-n2t50BKT8oBEMuilmypSQqJw0pCgtUm-e6Z0Eg3Ly6DMXFlycyikegiZ0b-rVX7i5OCEZRDkENAYwFNX4G7NNCwEZcH7HUMUmty9dchAqDS9YWzPh_dde1A9oy9JMH07nRGDcOzIh1rCPwc71nwfPPYeeS4tTvkjanjeigOYBFkBLQuv7iBB4LPozsGF1XdoKiIIi-8ye44McdhOTPDcQp3xKxj89aO02pQhBECv61rmbPinvjMG9DYxJmZvjsKF4bN2oy0DxdC1jDw", 81 "e":"AQAB" 82 }` 83 84 func TestKeyDigest(t *testing.T) { 85 // Test with JWK (value, reference, and direct) 86 var jwk jose.JSONWebKey 87 err := json.Unmarshal([]byte(JWK1JSON), &jwk) 88 if err != nil { 89 t.Fatal(err) 90 } 91 digest, err := KeyDigestB64(jwk) 92 test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest JWK by value") 93 digest, err = KeyDigestB64(&jwk) 94 test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest JWK by reference") 95 digest, err = KeyDigestB64(jwk.Key) 96 test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest bare key") 97 98 // Test with unknown key type 99 _, err = KeyDigestB64(struct{}{}) 100 test.Assert(t, err != nil, "Should have rejected unknown key type") 101 } 102 103 func TestKeyDigestEquals(t *testing.T) { 104 var jwk1, jwk2 jose.JSONWebKey 105 err := json.Unmarshal([]byte(JWK1JSON), &jwk1) 106 if err != nil { 107 t.Fatal(err) 108 } 109 err = json.Unmarshal([]byte(JWK2JSON), &jwk2) 110 if err != nil { 111 t.Fatal(err) 112 } 113 114 test.Assert(t, KeyDigestEquals(jwk1, jwk1), "Key digests for same key should match") 115 test.Assert(t, !KeyDigestEquals(jwk1, jwk2), "Key digests for different keys should not match") 116 test.Assert(t, !KeyDigestEquals(jwk1, struct{}{}), "Unknown key types should not match anything") 117 test.Assert(t, !KeyDigestEquals(struct{}{}, struct{}{}), "Unknown key types should not match anything") 118 } 119 120 func TestIsAnyNilOrZero(t *testing.T) { 121 test.Assert(t, IsAnyNilOrZero(nil), "Nil seen as non-zero") 122 123 test.Assert(t, IsAnyNilOrZero(false), "False bool seen as non-zero") 124 test.Assert(t, !IsAnyNilOrZero(true), "True bool seen as zero") 125 126 test.Assert(t, IsAnyNilOrZero(0), "Untyped constant zero seen as non-zero") 127 test.Assert(t, !IsAnyNilOrZero(1), "Untyped constant 1 seen as zero") 128 test.Assert(t, IsAnyNilOrZero(int(0)), "int(0) seen as non-zero") 129 test.Assert(t, !IsAnyNilOrZero(int(1)), "int(1) seen as zero") 130 test.Assert(t, IsAnyNilOrZero(int8(0)), "int8(0) seen as non-zero") 131 test.Assert(t, !IsAnyNilOrZero(int8(1)), "int8(1) seen as zero") 132 test.Assert(t, IsAnyNilOrZero(int16(0)), "int16(0) seen as non-zero") 133 test.Assert(t, !IsAnyNilOrZero(int16(1)), "int16(1) seen as zero") 134 test.Assert(t, IsAnyNilOrZero(int32(0)), "int32(0) seen as non-zero") 135 test.Assert(t, !IsAnyNilOrZero(int32(1)), "int32(1) seen as zero") 136 test.Assert(t, IsAnyNilOrZero(int64(0)), "int64(0) seen as non-zero") 137 test.Assert(t, !IsAnyNilOrZero(int64(1)), "int64(1) seen as zero") 138 139 test.Assert(t, IsAnyNilOrZero(uint(0)), "uint(0) seen as non-zero") 140 test.Assert(t, !IsAnyNilOrZero(uint(1)), "uint(1) seen as zero") 141 test.Assert(t, IsAnyNilOrZero(uint8(0)), "uint8(0) seen as non-zero") 142 test.Assert(t, !IsAnyNilOrZero(uint8(1)), "uint8(1) seen as zero") 143 test.Assert(t, IsAnyNilOrZero(uint16(0)), "uint16(0) seen as non-zero") 144 test.Assert(t, !IsAnyNilOrZero(uint16(1)), "uint16(1) seen as zero") 145 test.Assert(t, IsAnyNilOrZero(uint32(0)), "uint32(0) seen as non-zero") 146 test.Assert(t, !IsAnyNilOrZero(uint32(1)), "uint32(1) seen as zero") 147 test.Assert(t, IsAnyNilOrZero(uint64(0)), "uint64(0) seen as non-zero") 148 test.Assert(t, !IsAnyNilOrZero(uint64(1)), "uint64(1) seen as zero") 149 150 test.Assert(t, !IsAnyNilOrZero(-12.345), "Untyped float32 seen as zero") 151 test.Assert(t, !IsAnyNilOrZero(float32(6.66)), "Non-empty float32 seen as zero") 152 test.Assert(t, IsAnyNilOrZero(float32(0)), "Empty float32 seen as non-zero") 153 154 test.Assert(t, !IsAnyNilOrZero(float64(7.77)), "Non-empty float64 seen as zero") 155 test.Assert(t, IsAnyNilOrZero(float64(0)), "Empty float64 seen as non-zero") 156 157 test.Assert(t, IsAnyNilOrZero(""), "Empty string seen as non-zero") 158 test.Assert(t, !IsAnyNilOrZero("string"), "Non-empty string seen as zero") 159 160 test.Assert(t, IsAnyNilOrZero([]string{}), "Empty string slice seen as non-zero") 161 test.Assert(t, !IsAnyNilOrZero([]string{"barncats"}), "Non-empty string slice seen as zero") 162 163 test.Assert(t, IsAnyNilOrZero([]byte{}), "Empty byte slice seen as non-zero") 164 test.Assert(t, !IsAnyNilOrZero([]byte("byte")), "Non-empty byte slice seen as zero") 165 166 test.Assert(t, IsAnyNilOrZero(time.Time{}), "No specified time value seen as non-zero") 167 test.Assert(t, !IsAnyNilOrZero(time.Now()), "Current time seen as zero") 168 169 type Foo struct { 170 foo int 171 } 172 test.Assert(t, IsAnyNilOrZero(Foo{}), "Empty struct seen as non-zero") 173 test.Assert(t, !IsAnyNilOrZero(Foo{5}), "Non-empty struct seen as zero") 174 var f *Foo 175 test.Assert(t, IsAnyNilOrZero(f), "Pointer to uninitialized struct seen as non-zero") 176 177 test.Assert(t, IsAnyNilOrZero(1, ""), "Mixed values seen as non-zero") 178 test.Assert(t, IsAnyNilOrZero("", 1), "Mixed values seen as non-zero") 179 180 var p *timestamppb.Timestamp 181 test.Assert(t, IsAnyNilOrZero(p), "Pointer to uninitialized timestamppb.Timestamp seen as non-zero") 182 test.Assert(t, IsAnyNilOrZero(timestamppb.New(time.Time{})), "*timestamppb.Timestamp containing an uninitialized inner time.Time{} is seen as non-zero") 183 test.Assert(t, !IsAnyNilOrZero(timestamppb.Now()), "A *timestamppb.Timestamp with valid inner time is seen as zero") 184 185 var d *durationpb.Duration 186 var zeroDuration time.Duration 187 test.Assert(t, IsAnyNilOrZero(d), "Pointer to uninitialized durationpb.Duration seen as non-zero") 188 test.Assert(t, IsAnyNilOrZero(durationpb.New(zeroDuration)), "*durationpb.Duration containing an zero value time.Duration is seen as non-zero") 189 test.Assert(t, !IsAnyNilOrZero(durationpb.New(666)), "A *durationpb.Duration with valid inner duration is seen as zero") 190 } 191 192 func BenchmarkIsAnyNilOrZero(b *testing.B) { 193 var thyme *time.Time 194 var sage *time.Duration 195 var table = []struct { 196 input any 197 }{ 198 {input: int(0)}, 199 {input: int(1)}, 200 {input: int8(0)}, 201 {input: int8(1)}, 202 {input: int16(0)}, 203 {input: int16(1)}, 204 {input: int32(0)}, 205 {input: int32(1)}, 206 {input: int64(0)}, 207 {input: int64(1)}, 208 {input: uint(0)}, 209 {input: uint(1)}, 210 {input: uint8(0)}, 211 {input: uint8(1)}, 212 {input: uint16(0)}, 213 {input: uint16(1)}, 214 {input: uint32(0)}, 215 {input: uint32(1)}, 216 {input: uint64(0)}, 217 {input: uint64(1)}, 218 {input: float32(0)}, 219 {input: float32(0.1)}, 220 {input: float64(0)}, 221 {input: float64(0.1)}, 222 {input: ""}, 223 {input: "ahoyhoy"}, 224 {input: []string{}}, 225 {input: []string{""}}, 226 {input: []string{"oodley_doodley"}}, 227 {input: []byte{}}, 228 {input: []byte{0}}, 229 {input: []byte{1}}, 230 {input: []rune{}}, 231 {input: []rune{2}}, 232 {input: []rune{3}}, 233 {input: nil}, 234 {input: false}, 235 {input: true}, 236 {input: thyme}, 237 {input: time.Time{}}, 238 {input: time.Date(2015, time.June, 04, 11, 04, 38, 0, time.UTC)}, 239 {input: sage}, 240 {input: time.Duration(1)}, 241 {input: time.Duration(0)}, 242 } 243 244 for _, v := range table { 245 b.Run(fmt.Sprintf("input_%T_%v", v.input, v.input), func(b *testing.B) { 246 for b.Loop() { 247 _ = IsAnyNilOrZero(v.input) 248 } 249 }) 250 } 251 } 252 253 func TestUniqueLowerNames(t *testing.T) { 254 u := UniqueLowerNames([]string{"foobar.com", "fooBAR.com", "baz.com", "foobar.com", "bar.com", "bar.com", "a.com"}) 255 sort.Strings(u) 256 test.AssertDeepEquals(t, []string{"a.com", "bar.com", "baz.com", "foobar.com"}, u) 257 } 258 259 func TestValidSerial(t *testing.T) { 260 notLength32Or36 := "A" 261 length32 := strings.Repeat("A", 32) 262 length36 := strings.Repeat("A", 36) 263 isValidSerial := ValidSerial(notLength32Or36) 264 test.AssertEquals(t, isValidSerial, false) 265 isValidSerial = ValidSerial(length32) 266 test.AssertEquals(t, isValidSerial, true) 267 isValidSerial = ValidSerial(length36) 268 test.AssertEquals(t, isValidSerial, true) 269 } 270 271 func TestLoadCert(t *testing.T) { 272 var osPathErr *os.PathError 273 _, err := LoadCert("") 274 test.AssertError(t, err, "Loading empty path did not error") 275 test.AssertErrorWraps(t, err, &osPathErr) 276 277 _, err = LoadCert("totally/fake/path") 278 test.AssertError(t, err, "Loading nonexistent path did not error") 279 test.AssertErrorWraps(t, err, &osPathErr) 280 281 _, err = LoadCert("../test/hierarchy/README.md") 282 test.AssertError(t, err, "Loading non-PEM file did not error") 283 test.AssertContains(t, err.Error(), "no data in cert PEM file") 284 285 _, err = LoadCert("../test/hierarchy/int-e1.key.pem") 286 test.AssertError(t, err, "Loading non-cert PEM file did not error") 287 test.AssertContains(t, err.Error(), "x509: malformed tbs certificate") 288 289 cert, err := LoadCert("../test/hierarchy/int-r3.cert.pem") 290 test.AssertNotError(t, err, "Failed to load cert PEM file") 291 test.AssertEquals(t, cert.Subject.CommonName, "(TEST) Radical Rhino R3") 292 } 293 294 func TestRetryBackoff(t *testing.T) { 295 assertBetween := func(a, b, c float64) { 296 t.Helper() 297 if a < b || a > c { 298 t.Fatalf("%f is not between %f and %f", a, b, c) 299 } 300 } 301 302 factor := 1.5 303 base := time.Minute 304 max := 10 * time.Minute 305 306 backoff := RetryBackoff(0, base, max, factor) 307 assertBetween(float64(backoff), 0, 0) 308 309 expected := base 310 backoff = RetryBackoff(1, base, max, factor) 311 assertBetween(float64(backoff), float64(expected)*0.8, float64(expected)*1.2) 312 313 expected = time.Second * 90 314 backoff = RetryBackoff(2, base, max, factor) 315 assertBetween(float64(backoff), float64(expected)*0.8, float64(expected)*1.2) 316 317 expected = time.Minute * 10 318 // should be truncated 319 backoff = RetryBackoff(7, base, max, factor) 320 assertBetween(float64(backoff), float64(expected)*0.8, float64(expected)*1.2) 321 322 } 323 324 func TestHashIdentifiers(t *testing.T) { 325 dns1 := identifier.NewDNS("example.com") 326 dns1_caps := identifier.NewDNS("eXaMpLe.COM") 327 dns2 := identifier.NewDNS("high-energy-cheese-lab.nrc-cnrc.gc.ca") 328 dns2_caps := identifier.NewDNS("HIGH-ENERGY-CHEESE-LAB.NRC-CNRC.GC.CA") 329 ipv4_1 := identifier.NewIP(netip.MustParseAddr("10.10.10.10")) 330 ipv4_2 := identifier.NewIP(netip.MustParseAddr("172.16.16.16")) 331 ipv6_1 := identifier.NewIP(netip.MustParseAddr("2001:0db8:0bad:0dab:c0ff:fee0:0007:1337")) 332 ipv6_2 := identifier.NewIP(netip.MustParseAddr("3fff::")) 333 334 testCases := []struct { 335 Name string 336 Idents1 identifier.ACMEIdentifiers 337 Idents2 identifier.ACMEIdentifiers 338 ExpectedEqual bool 339 }{ 340 { 341 Name: "Deterministic for DNS", 342 Idents1: identifier.ACMEIdentifiers{dns1}, 343 Idents2: identifier.ACMEIdentifiers{dns1}, 344 ExpectedEqual: true, 345 }, 346 { 347 Name: "Deterministic for IPv4", 348 Idents1: identifier.ACMEIdentifiers{ipv4_1}, 349 Idents2: identifier.ACMEIdentifiers{ipv4_1}, 350 ExpectedEqual: true, 351 }, 352 { 353 Name: "Deterministic for IPv6", 354 Idents1: identifier.ACMEIdentifiers{ipv6_1}, 355 Idents2: identifier.ACMEIdentifiers{ipv6_1}, 356 ExpectedEqual: true, 357 }, 358 { 359 Name: "Differentiates for DNS", 360 Idents1: identifier.ACMEIdentifiers{dns1}, 361 Idents2: identifier.ACMEIdentifiers{dns2}, 362 ExpectedEqual: false, 363 }, 364 { 365 Name: "Differentiates for IPv4", 366 Idents1: identifier.ACMEIdentifiers{ipv4_1}, 367 Idents2: identifier.ACMEIdentifiers{ipv4_2}, 368 ExpectedEqual: false, 369 }, 370 { 371 Name: "Differentiates for IPv6", 372 Idents1: identifier.ACMEIdentifiers{ipv6_1}, 373 Idents2: identifier.ACMEIdentifiers{ipv6_2}, 374 ExpectedEqual: false, 375 }, 376 { 377 Name: "Not subject to ordering", 378 Idents1: identifier.ACMEIdentifiers{ 379 dns1, dns2, ipv4_1, ipv4_2, ipv6_1, ipv6_2, 380 }, 381 Idents2: identifier.ACMEIdentifiers{ 382 ipv6_1, dns2, ipv4_2, dns1, ipv4_1, ipv6_2, 383 }, 384 ExpectedEqual: true, 385 }, 386 { 387 Name: "Not case sensitive", 388 Idents1: identifier.ACMEIdentifiers{ 389 dns1, dns2, 390 }, 391 Idents2: identifier.ACMEIdentifiers{ 392 dns1_caps, dns2_caps, 393 }, 394 ExpectedEqual: true, 395 }, 396 { 397 Name: "Not subject to duplication", 398 Idents1: identifier.ACMEIdentifiers{ 399 dns1, dns1, 400 }, 401 Idents2: identifier.ACMEIdentifiers{dns1}, 402 ExpectedEqual: true, 403 }, 404 } 405 406 for _, tc := range testCases { 407 t.Run(tc.Name, func(t *testing.T) { 408 t.Parallel() 409 h1 := HashIdentifiers(tc.Idents1) 410 h2 := HashIdentifiers(tc.Idents2) 411 if slices.Equal(h1, h2) != tc.ExpectedEqual { 412 t.Errorf("Comparing hashes of idents %#v and %#v, expected equality to be %v", tc.Idents1, tc.Idents2, tc.ExpectedEqual) 413 } 414 }) 415 } 416 } 417 418 func TestIsCanceled(t *testing.T) { 419 if !IsCanceled(context.Canceled) { 420 t.Errorf("Expected context.Canceled to be canceled, but wasn't.") 421 } 422 if !IsCanceled(status.Errorf(codes.Canceled, "hi")) { 423 t.Errorf("Expected gRPC cancellation to be canceled, but wasn't.") 424 } 425 if IsCanceled(errors.New("hi")) { 426 t.Errorf("Expected random error to not be canceled, but was.") 427 } 428 }