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  }