pax_global_header00006660000000000000000000000064151160505550014515gustar00rootroot0000000000000052 comment=13b25b23600e4d08cfbcdd0d3bb5fbc19988f003 golang-github-letsencrypt-boulder-0.20251208.0+ds1/000077500000000000000000000000001511605055500214745ustar00rootroot00000000000000golang-github-letsencrypt-boulder-0.20251208.0+ds1/LICENSE.txt000066400000000000000000000406021511605055500233210ustar00rootroot00000000000000Copyright 2016 ISRG. All rights reserved. Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. golang-github-letsencrypt-boulder-0.20251208.0+ds1/README.md000066400000000000000000000254531511605055500227640ustar00rootroot00000000000000# Boulder - An ACME CA [![Build Status](https://github.com/letsencrypt/boulder/actions/workflows/boulder-ci.yml/badge.svg?branch=main)](https://github.com/letsencrypt/boulder/actions/workflows/boulder-ci.yml?query=branch%3Amain) This is an implementation of an ACME-based CA. The [ACME protocol](https://github.com/ietf-wg-acme/acme/) allows the CA to automatically verify that an applicant for a certificate actually controls an identifier, and allows subscribers to issue and revoke certificates for the identifiers they control. Boulder is the software that runs [Let's Encrypt](https://letsencrypt.org). ## Contents * [Overview](#overview) * [Setting up Boulder](#setting-up-boulder) * [Development](#development) * [Working with Certbot](#working-with-certbot) * [Working with another ACME Client](#working-with-another-acme-client) * [Production](#production) * [Contributing](#contributing) * [License](#license) ## Overview Boulder is divided into the following main components: 1. Web Front Ends (one per API version) 2. Registration Authority 3. Validation Authority 4. Certificate Authority 5. Storage Authority 6. Publisher 7. CRL Updater This component model lets us separate the function of the CA by security context. The Web Front End, Validation Authority, CRL Storer, and Publisher need access to the Internet, which puts them at greater risk of compromise. The Registration Authority can live without Internet connectivity, but still needs to talk to the Web Front End and Validation Authority. The Certificate Authority need only receive instructions from the Registration Authority. All components talk to the SA for storage, so most lines indicating SA RPCs are not shown here. ```text CA ---------> Publisher ^ | Subscriber -> WFE --> RA --> SA --> MariaDB | ^ Subscriber server <- VA <----+ | | Browser -----> S3 <----- CRL Storer/Updater ``` Internally, the logic of the system is based around five types of objects: accounts, authorizations, challenges, orders and certificates, mapping directly to the resources of the same name in ACME. Requests from ACME clients result in new objects and changes to objects. The Storage Authority maintains persistent copies of the current set of objects. Boulder uses gRPC for inter-component communication. For components that you want to be remote, it is necessary to instantiate a "client" and "server" for that component. The client implements the component's Go interface, while the server has the actual logic for the component. A high level overview for this communication model can be found in the [gRPC documentation](https://www.grpc.io/docs/). The full details of how the various ACME operations happen in Boulder are laid out in [DESIGN.md](https://github.com/letsencrypt/boulder/blob/main/docs/DESIGN.md). ## Setting up Boulder ### Development Boulder has a Dockerfile and uses Docker Compose to make it easy to install and set up all its dependencies. This is how the maintainers work on Boulder, and is our main recommended way to run it for development/experimentation. It is not suitable for use as a production environment. While we aim to make Boulder easy to setup ACME client developers may find [Pebble](https://github.com/letsencrypt/pebble), a miniature version of Boulder, to be better suited for continuous integration and quick experimentation. We recommend setting git's [fsckObjects setting](https://groups.google.com/forum/#!topic/binary-transparency/f-BI4o8HZW0/discussion) before getting a copy of Boulder to have better integrity guarantees for updates. Clone the boulder repository: ```shell git clone https://github.com/letsencrypt/boulder/ cd boulder ``` Additionally, make sure you have Docker Engine 1.13.0+ and Docker Compose 1.10.0+ installed. If you do not, you can follow Docker's [installation instructions](https://docs.docker.com/compose/install/). We recommend having **at least 2GB of RAM** available on your Docker host. In practice using less RAM may result in the MariaDB container failing in non-obvious ways. To start Boulder in a Docker container, run: ```shell docker compose up ``` To run our standard battery of tests (lints, unit, integration): ```shell ./t.sh ``` To run all unit tests: ```shell ./t.sh -u ``` To run specific unit tests (example is of the ./va directory): ```shell ./t.sh -u -p ./va ``` To run all integration tests: ```shell ./t.sh -i ``` To run unit tests and integration tests with coverage: ```shell ./t.sh -ui -c --coverage-dir=./test/coverage/mytestrun ``` To run specific integration tests (example runs TestGenerateValidity and TestWFECORS): ```shell ./t.sh -i -f TestGenerateValidity/TestWFECORS ``` To do any of the above, but using the "config-next" configuration, which represents a likely future state (e.g. including new feature flags): ```shell ./tn.sh -your -options -here ``` The configuration in docker-compose.yml mounts your boulder checkout at /boulder so you can edit code on your host and it will be immediately reflected inside the Docker containers run with `docker compose`. If you have problems with Docker, you may want to try [removing all containers and volumes](https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes). By default, Boulder uses a fake DNS resolver that resolves all hostnames to 127.0.0.1. This is suitable for running integration tests inside the Docker container. If you want Boulder to be able to communicate with a client running on your host instead, you should find your host's Docker IP with: ```shell ifconfig docker0 | grep "inet addr:" | cut -d: -f2 | awk '{ print $1}' ``` And edit docker-compose.yml to change the `FAKE_DNS` environment variable to match. This will cause Boulder's stubbed-out DNS resolver (`sd-test-srv`) to respond to all A queries with the address in `FAKE_DNS`. If you use a host-based firewall (e.g. `ufw` or `iptables`) make sure you allow connections from the Docker instance to your host on the required validation ports to your ACME client. Alternatively, you can override the docker-compose.yml default with an environmental variable using -e (replace 172.17.0.1 with the host IPv4 address found in the command above) ```shell docker compose run --use-aliases -e FAKE_DNS=172.17.0.1 --service-ports boulder ./start.py ``` Running tests without the `./test.sh` wrapper: Run unit tests locally, without docker (only works for some directories): ```shell go test ./issuance/... ``` Run all unit tests: ```shell docker compose run --use-aliases boulder go test -p 1 ./... ``` Run unit tests for a specific directory: ```shell docker compose run --use-aliases boulder go test ``` Run integration tests (omit `--filter ` to run all): ```shell docker compose run --use-aliases boulder python3 test/integration-test.py --chisel --gotest --filter ``` ### Working with Certbot Check out the Certbot client from https://github.com/certbot/certbot and follow their setup instructions. Once you've got the client set up, you'll probably want to run it against your local Boulder. There are a number of command line flags that are necessary to run the client against a local Boulder, and without root access. The simplest way to run the client locally is to use a convenient alias for certbot (`certbot_test`) with a custom `SERVER` environment variable: ```shell SERVER=http://localhost:4001/directory certbot_test certonly --standalone -d test.example.com ``` Your local Boulder instance uses a fake DNS resolver that returns 127.0.0.1 for any query, so you can use any value for the -d flag. To return an answer other than `127.0.0.1` change the Boulder `FAKE_DNS` environment variable to another IP address. ### Working with another ACME Client Once you have followed the Boulder development environment instructions and have started the containers you will find the ACME endpoints exposed to your host at the following URLs: * ACME v2, HTTP: `http://localhost:4001/directory` * ACME v2, HTTPS: `https://localhost:4431/directory` To access the HTTPS versions of the endpoints you will need to configure your ACME client software to use a CA truststore that contains the `test/certs/ipki/minica.pem` CA certificate. See [`test/certs/README.md`](https://github.com/letsencrypt/boulder/blob/main/test/certs/README.md) for more information. Your local Boulder instance uses a fake DNS resolver that returns 127.0.0.1 for any query, allowing you to issue certificates for any domain as if it resolved to your localhost. To return an answer other than `127.0.0.1` change the Boulder `FAKE_DNS` environment variable to another IP address. Most often you will want to configure `FAKE_DNS` to point to your host machine where you run an ACME client. ### Production Boulder is custom built for Let's Encrypt and is intended only to support the Web PKI and the CA/Browser forum's baseline requirements. In our experience often Boulder is not the right fit for organizations that are evaluating it for production usage. In most cases a centrally managed PKI that doesn't require domain-authorization with ACME is a better choice. For this environment we recommend evaluating a project other than Boulder. We offer a brief [deployment and implementation guide](https://github.com/letsencrypt/boulder/wiki/Deployment-&-Implementation-Guide) that describes some of the required work and security considerations involved in using Boulder in a production environment. As-is the docker based Boulder development environment is **not suitable for production usage**. It uses private key material that is publicly available, exposes debug ports and is brittle to component failure. While we are supportive of other organization's deploying Boulder in a production setting we prioritize support and development work that favors Let's Encrypt's mission. This means we may not be able to provide timely support or accept pull-requests that deviate significantly from our first line goals. If you've thoroughly evaluated the alternatives and Boulder is definitely the best fit we're happy to answer questions to the best of our ability. ## Contributing Please take a look at [CONTRIBUTING.md](https://github.com/letsencrypt/boulder/blob/main/docs/CONTRIBUTING.md) for our guidelines on submitting patches, code review process, code of conduct, and various other tips related to working on the codebase. ## Code of Conduct The code of conduct for everyone participating in this community in any capacity is available for reference [on the community forum](https://community.letsencrypt.org/guidelines). ## License This project is licensed under the Mozilla Public License 2.0, the full text of which can be found in the [LICENSE.txt](https://github.com/letsencrypt/boulder/blob/main/LICENSE.txt) file. golang-github-letsencrypt-boulder-0.20251208.0+ds1/core/000077500000000000000000000000001511605055500224245ustar00rootroot00000000000000golang-github-letsencrypt-boulder-0.20251208.0+ds1/core/util.go000066400000000000000000000232351511605055500237350ustar00rootroot00000000000000package core import ( "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" "encoding/pem" "errors" "expvar" "fmt" "io" "math/big" mrand "math/rand/v2" "os" "path" "reflect" "regexp" "sort" "strings" "time" "unicode" "github.com/go-jose/go-jose/v4" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/letsencrypt/boulder/identifier" ) const Unspecified = "Unspecified" // Package Variables Variables // BuildID is set by the compiler (using -ldflags "-X core.BuildID $(git rev-parse --short HEAD)") // and is used by GetBuildID var BuildID string // BuildHost is set by the compiler and is used by GetBuildHost var BuildHost string // BuildTime is set by the compiler and is used by GetBuildTime var BuildTime string func init() { expvar.NewString("BuildID").Set(BuildID) expvar.NewString("BuildTime").Set(BuildTime) } // Random stuff type randSource interface { Read(p []byte) (n int, err error) } // RandReader is used so that it can be replaced in tests that require // deterministic output var RandReader randSource = rand.Reader // RandomString returns a randomly generated string of the requested length. func RandomString(byteLength int) string { b := make([]byte, byteLength) _, err := io.ReadFull(RandReader, b) if err != nil { panic(fmt.Sprintf("Error reading random bytes: %s", err)) } return base64.RawURLEncoding.EncodeToString(b) } // NewToken produces a random string for Challenges, etc. func NewToken() string { return RandomString(32) } var tokenFormat = regexp.MustCompile(`^[\w-]{43}$`) // looksLikeAToken checks whether a string represents a 32-octet value in // the URL-safe base64 alphabet. func looksLikeAToken(token string) bool { return tokenFormat.MatchString(token) } // Fingerprints // Fingerprint256 produces an unpadded, URL-safe Base64-encoded SHA256 digest // of the data. func Fingerprint256(data []byte) string { d := sha256.New() _, _ = d.Write(data) // Never returns an error return base64.RawURLEncoding.EncodeToString(d.Sum(nil)) } type Sha256Digest [sha256.Size]byte // KeyDigest produces the SHA256 digest of a provided public key. func KeyDigest(key crypto.PublicKey) (Sha256Digest, error) { switch t := key.(type) { case *jose.JSONWebKey: if t == nil { return Sha256Digest{}, errors.New("cannot compute digest of nil key") } return KeyDigest(t.Key) case jose.JSONWebKey: return KeyDigest(t.Key) default: keyDER, err := x509.MarshalPKIXPublicKey(key) if err != nil { return Sha256Digest{}, err } return sha256.Sum256(keyDER), nil } } // KeyDigestB64 produces a padded, standard Base64-encoded SHA256 digest of a // provided public key. func KeyDigestB64(key crypto.PublicKey) (string, error) { digest, err := KeyDigest(key) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(digest[:]), nil } // KeyDigestEquals determines whether two public keys have the same digest. func KeyDigestEquals(j, k crypto.PublicKey) bool { digestJ, errJ := KeyDigestB64(j) digestK, errK := KeyDigestB64(k) // Keys that don't have a valid digest (due to marshalling problems) // are never equal. So, e.g. nil keys are not equal. if errJ != nil || errK != nil { return false } return digestJ == digestK } // PublicKeysEqual determines whether two public keys are identical. func PublicKeysEqual(a, b crypto.PublicKey) (bool, error) { switch ak := a.(type) { case *rsa.PublicKey: return ak.Equal(b), nil case *ecdsa.PublicKey: return ak.Equal(b), nil default: return false, fmt.Errorf("unsupported public key type %T", ak) } } // SerialToString converts a certificate serial number (big.Int) to a String // consistently. func SerialToString(serial *big.Int) string { return fmt.Sprintf("%036x", serial) } // StringToSerial converts a string into a certificate serial number (big.Int) // consistently. func StringToSerial(serial string) (*big.Int, error) { var serialNum big.Int if !ValidSerial(serial) { return &serialNum, fmt.Errorf("invalid serial number %q", serial) } _, err := fmt.Sscanf(serial, "%036x", &serialNum) return &serialNum, err } // ValidSerial tests whether the input string represents a syntactically // valid serial number, i.e., that it is a valid hex string between 32 // and 36 characters long. func ValidSerial(serial string) bool { // Originally, serial numbers were 32 hex characters long. We later increased // them to 36, but we allow the shorter ones because they exist in some // production databases. if len(serial) != 32 && len(serial) != 36 { return false } _, err := hex.DecodeString(serial) return err == nil } // GetBuildID identifies what build is running. func GetBuildID() (retID string) { retID = BuildID if retID == "" { retID = Unspecified } return } // GetBuildTime identifies when this build was made func GetBuildTime() (retID string) { retID = BuildTime if retID == "" { retID = Unspecified } return } // GetBuildHost identifies the building host func GetBuildHost() (retID string) { retID = BuildHost if retID == "" { retID = Unspecified } return } // IsAnyNilOrZero returns whether any of the supplied values are nil, or (if not) // if any of them is its type's zero-value. This is useful for validating that // all required fields on a proto message are present. func IsAnyNilOrZero(vals ...any) bool { for _, val := range vals { switch v := val.(type) { case nil: return true case bool: if !v { return true } case string: if v == "" { return true } case []string: if len(v) == 0 { return true } case byte: // Byte is an alias for uint8 and will cover that case. if v == 0 { return true } case []byte: if len(v) == 0 { return true } case int: if v == 0 { return true } case int8: if v == 0 { return true } case int16: if v == 0 { return true } case int32: if v == 0 { return true } case int64: if v == 0 { return true } case uint: if v == 0 { return true } case uint16: if v == 0 { return true } case uint32: if v == 0 { return true } case uint64: if v == 0 { return true } case float32: if v == 0 { return true } case float64: if v == 0 { return true } case time.Time: if v.IsZero() { return true } case *timestamppb.Timestamp: if v == nil || v.AsTime().IsZero() { return true } case *durationpb.Duration: if v == nil || v.AsDuration() == time.Duration(0) { return true } default: if reflect.ValueOf(v).IsZero() { return true } } } return false } // UniqueLowerNames returns the set of all unique names in the input after all // of them are lowercased. The returned names will be in their lowercased form // and sorted alphabetically. func UniqueLowerNames(names []string) (unique []string) { nameMap := make(map[string]int, len(names)) for _, name := range names { nameMap[strings.ToLower(name)] = 1 } unique = make([]string, 0, len(nameMap)) for name := range nameMap { unique = append(unique, name) } sort.Strings(unique) return } // HashIdentifiers returns a hash of the identifiers requested. This is intended // for use when interacting with the orderFqdnSets table and rate limiting. func HashIdentifiers(idents identifier.ACMEIdentifiers) []byte { var values []string for _, ident := range identifier.Normalize(idents) { values = append(values, ident.Value) } hash := sha256.Sum256([]byte(strings.Join(values, ","))) return hash[:] } // LoadCert loads a PEM certificate specified by filename or returns an error func LoadCert(filename string) (*x509.Certificate, error) { certPEM, err := os.ReadFile(filename) if err != nil { return nil, err } block, _ := pem.Decode(certPEM) if block == nil { return nil, fmt.Errorf("no data in cert PEM file %q", filename) } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, err } return cert, nil } // retryJitter is used to prevent bunched retried queries from falling into lockstep const retryJitter = 0.2 // RetryBackoff calculates a backoff time based on number of retries, will always // add jitter so requests that start in unison won't fall into lockstep. Because of // this the returned duration can always be larger than the maximum by a factor of // retryJitter. Adapted from // https://github.com/grpc/grpc-go/blob/v1.11.3/backoff.go#L77-L96 func RetryBackoff(retries int, base, max time.Duration, factor float64) time.Duration { if retries == 0 { return 0 } backoff, fMax := float64(base), float64(max) for backoff < fMax && retries > 1 { backoff *= factor retries-- } if backoff > fMax { backoff = fMax } // Randomize backoff delays so that if a cluster of requests start at // the same time, they won't operate in lockstep. backoff *= (1 - retryJitter) + 2*retryJitter*mrand.Float64() return time.Duration(backoff) } // IsASCII determines if every character in a string is encoded in // the ASCII character set. func IsASCII(str string) bool { for _, r := range str { if r > unicode.MaxASCII { return false } } return true } // IsCanceled returns true if err is non-nil and is either context.Canceled, or // has a grpc code of Canceled. This is useful because cancellations propagate // through gRPC boundaries, and if we choose to treat in-process cancellations a // certain way, we usually want to treat cross-process cancellations the same way. func IsCanceled(err error) bool { return errors.Is(err, context.Canceled) || status.Code(err) == codes.Canceled } func Command() string { return path.Base(os.Args[0]) } golang-github-letsencrypt-boulder-0.20251208.0+ds1/core/util_test.go000066400000000000000000000355621511605055500250020ustar00rootroot00000000000000package core import ( "context" "encoding/json" "errors" "fmt" "math" "math/big" "net/netip" "os" "slices" "sort" "strings" "testing" "time" "github.com/go-jose/go-jose/v4" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/letsencrypt/boulder/identifier" "github.com/letsencrypt/boulder/test" ) // challenges.go func TestNewToken(t *testing.T) { token := NewToken() fmt.Println(token) tokenLength := int(math.Ceil(32 * 8 / 6.0)) // 32 bytes, b64 encoded if len(token) != tokenLength { t.Fatalf("Expected token of length %d, got %d", tokenLength, len(token)) } collider := map[string]bool{} // Test for very blatant RNG failures: // Try 2^20 birthdays in a 2^72 search space... // our naive collision probability here is 2^-32... for range 1000000 { token = NewToken()[:12] // just sample a portion test.Assert(t, !collider[token], "Token collision!") collider[token] = true } } func TestLooksLikeAToken(t *testing.T) { test.Assert(t, !looksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOS"), "Accepted short token") test.Assert(t, !looksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOS%"), "Accepted invalid token") test.Assert(t, looksLikeAToken("R-UL_7MrV3tUUjO9v5ym2srK3dGGCwlxbVyKBdwLOSU"), "Rejected valid token") } func TestSerialUtils(t *testing.T) { serial := SerialToString(big.NewInt(100000000000000000)) test.AssertEquals(t, serial, "00000000000000000000016345785d8a0000") serialNum, err := StringToSerial("00000000000000000000016345785d8a0000") test.AssertNotError(t, err, "Couldn't convert serial number to *big.Int") if serialNum.Cmp(big.NewInt(100000000000000000)) != 0 { t.Fatalf("Incorrect conversion, got %d", serialNum) } badSerial, err := StringToSerial("doop!!!!000") test.AssertContains(t, err.Error(), "invalid serial number") fmt.Println(badSerial) } func TestBuildID(t *testing.T) { test.AssertEquals(t, Unspecified, GetBuildID()) } const JWK1JSON = `{ "kty": "RSA", "n": "vuc785P8lBj3fUxyZchF_uZw6WtbxcorqgTyq-qapF5lrO1U82Tp93rpXlmctj6fyFHBVVB5aXnUHJ7LZeVPod7Wnfl8p5OyhlHQHC8BnzdzCqCMKmWZNX5DtETDId0qzU7dPzh0LP0idt5buU7L9QNaabChw3nnaL47iu_1Di5Wp264p2TwACeedv2hfRDjDlJmaQXuS8Rtv9GnRWyC9JBu7XmGvGDziumnJH7Hyzh3VNu-kSPQD3vuAFgMZS6uUzOztCkT0fpOalZI6hqxtWLvXUMj-crXrn-Maavz8qRhpAyp5kcYk3jiHGgQIi7QSK2JIdRJ8APyX9HlmTN5AQ", "e": "AQAB" }` const JWK1Digest = `ul04Iq07ulKnnrebv2hv3yxCGgVvoHs8hjq2tVKx3mc=` const JWK2JSON = `{ "kty":"RSA", "n":"yTsLkI8n4lg9UuSKNRC0UPHsVjNdCYk8rGXIqeb_rRYaEev3D9-kxXY8HrYfGkVt5CiIVJ-n2t50BKT8oBEMuilmypSQqJw0pCgtUm-e6Z0Eg3Ly6DMXFlycyikegiZ0b-rVX7i5OCEZRDkENAYwFNX4G7NNCwEZcH7HUMUmty9dchAqDS9YWzPh_dde1A9oy9JMH07nRGDcOzIh1rCPwc71nwfPPYeeS4tTvkjanjeigOYBFkBLQuv7iBB4LPozsGF1XdoKiIIi-8ye44McdhOTPDcQp3xKxj89aO02pQhBECv61rmbPinvjMG9DYxJmZvjsKF4bN2oy0DxdC1jDw", "e":"AQAB" }` func TestKeyDigest(t *testing.T) { // Test with JWK (value, reference, and direct) var jwk jose.JSONWebKey err := json.Unmarshal([]byte(JWK1JSON), &jwk) if err != nil { t.Fatal(err) } digest, err := KeyDigestB64(jwk) test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest JWK by value") digest, err = KeyDigestB64(&jwk) test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest JWK by reference") digest, err = KeyDigestB64(jwk.Key) test.Assert(t, err == nil && digest == JWK1Digest, "Failed to digest bare key") // Test with unknown key type _, err = KeyDigestB64(struct{}{}) test.Assert(t, err != nil, "Should have rejected unknown key type") } func TestKeyDigestEquals(t *testing.T) { var jwk1, jwk2 jose.JSONWebKey err := json.Unmarshal([]byte(JWK1JSON), &jwk1) if err != nil { t.Fatal(err) } err = json.Unmarshal([]byte(JWK2JSON), &jwk2) if err != nil { t.Fatal(err) } test.Assert(t, KeyDigestEquals(jwk1, jwk1), "Key digests for same key should match") test.Assert(t, !KeyDigestEquals(jwk1, jwk2), "Key digests for different keys should not match") test.Assert(t, !KeyDigestEquals(jwk1, struct{}{}), "Unknown key types should not match anything") test.Assert(t, !KeyDigestEquals(struct{}{}, struct{}{}), "Unknown key types should not match anything") } func TestIsAnyNilOrZero(t *testing.T) { test.Assert(t, IsAnyNilOrZero(nil), "Nil seen as non-zero") test.Assert(t, IsAnyNilOrZero(false), "False bool seen as non-zero") test.Assert(t, !IsAnyNilOrZero(true), "True bool seen as zero") test.Assert(t, IsAnyNilOrZero(0), "Untyped constant zero seen as non-zero") test.Assert(t, !IsAnyNilOrZero(1), "Untyped constant 1 seen as zero") test.Assert(t, IsAnyNilOrZero(int(0)), "int(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(int(1)), "int(1) seen as zero") test.Assert(t, IsAnyNilOrZero(int8(0)), "int8(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(int8(1)), "int8(1) seen as zero") test.Assert(t, IsAnyNilOrZero(int16(0)), "int16(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(int16(1)), "int16(1) seen as zero") test.Assert(t, IsAnyNilOrZero(int32(0)), "int32(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(int32(1)), "int32(1) seen as zero") test.Assert(t, IsAnyNilOrZero(int64(0)), "int64(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(int64(1)), "int64(1) seen as zero") test.Assert(t, IsAnyNilOrZero(uint(0)), "uint(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(uint(1)), "uint(1) seen as zero") test.Assert(t, IsAnyNilOrZero(uint8(0)), "uint8(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(uint8(1)), "uint8(1) seen as zero") test.Assert(t, IsAnyNilOrZero(uint16(0)), "uint16(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(uint16(1)), "uint16(1) seen as zero") test.Assert(t, IsAnyNilOrZero(uint32(0)), "uint32(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(uint32(1)), "uint32(1) seen as zero") test.Assert(t, IsAnyNilOrZero(uint64(0)), "uint64(0) seen as non-zero") test.Assert(t, !IsAnyNilOrZero(uint64(1)), "uint64(1) seen as zero") test.Assert(t, !IsAnyNilOrZero(-12.345), "Untyped float32 seen as zero") test.Assert(t, !IsAnyNilOrZero(float32(6.66)), "Non-empty float32 seen as zero") test.Assert(t, IsAnyNilOrZero(float32(0)), "Empty float32 seen as non-zero") test.Assert(t, !IsAnyNilOrZero(float64(7.77)), "Non-empty float64 seen as zero") test.Assert(t, IsAnyNilOrZero(float64(0)), "Empty float64 seen as non-zero") test.Assert(t, IsAnyNilOrZero(""), "Empty string seen as non-zero") test.Assert(t, !IsAnyNilOrZero("string"), "Non-empty string seen as zero") test.Assert(t, IsAnyNilOrZero([]string{}), "Empty string slice seen as non-zero") test.Assert(t, !IsAnyNilOrZero([]string{"barncats"}), "Non-empty string slice seen as zero") test.Assert(t, IsAnyNilOrZero([]byte{}), "Empty byte slice seen as non-zero") test.Assert(t, !IsAnyNilOrZero([]byte("byte")), "Non-empty byte slice seen as zero") test.Assert(t, IsAnyNilOrZero(time.Time{}), "No specified time value seen as non-zero") test.Assert(t, !IsAnyNilOrZero(time.Now()), "Current time seen as zero") type Foo struct { foo int } test.Assert(t, IsAnyNilOrZero(Foo{}), "Empty struct seen as non-zero") test.Assert(t, !IsAnyNilOrZero(Foo{5}), "Non-empty struct seen as zero") var f *Foo test.Assert(t, IsAnyNilOrZero(f), "Pointer to uninitialized struct seen as non-zero") test.Assert(t, IsAnyNilOrZero(1, ""), "Mixed values seen as non-zero") test.Assert(t, IsAnyNilOrZero("", 1), "Mixed values seen as non-zero") var p *timestamppb.Timestamp test.Assert(t, IsAnyNilOrZero(p), "Pointer to uninitialized timestamppb.Timestamp seen as non-zero") test.Assert(t, IsAnyNilOrZero(timestamppb.New(time.Time{})), "*timestamppb.Timestamp containing an uninitialized inner time.Time{} is seen as non-zero") test.Assert(t, !IsAnyNilOrZero(timestamppb.Now()), "A *timestamppb.Timestamp with valid inner time is seen as zero") var d *durationpb.Duration var zeroDuration time.Duration test.Assert(t, IsAnyNilOrZero(d), "Pointer to uninitialized durationpb.Duration seen as non-zero") test.Assert(t, IsAnyNilOrZero(durationpb.New(zeroDuration)), "*durationpb.Duration containing an zero value time.Duration is seen as non-zero") test.Assert(t, !IsAnyNilOrZero(durationpb.New(666)), "A *durationpb.Duration with valid inner duration is seen as zero") } func BenchmarkIsAnyNilOrZero(b *testing.B) { var thyme *time.Time var sage *time.Duration var table = []struct { input any }{ {input: int(0)}, {input: int(1)}, {input: int8(0)}, {input: int8(1)}, {input: int16(0)}, {input: int16(1)}, {input: int32(0)}, {input: int32(1)}, {input: int64(0)}, {input: int64(1)}, {input: uint(0)}, {input: uint(1)}, {input: uint8(0)}, {input: uint8(1)}, {input: uint16(0)}, {input: uint16(1)}, {input: uint32(0)}, {input: uint32(1)}, {input: uint64(0)}, {input: uint64(1)}, {input: float32(0)}, {input: float32(0.1)}, {input: float64(0)}, {input: float64(0.1)}, {input: ""}, {input: "ahoyhoy"}, {input: []string{}}, {input: []string{""}}, {input: []string{"oodley_doodley"}}, {input: []byte{}}, {input: []byte{0}}, {input: []byte{1}}, {input: []rune{}}, {input: []rune{2}}, {input: []rune{3}}, {input: nil}, {input: false}, {input: true}, {input: thyme}, {input: time.Time{}}, {input: time.Date(2015, time.June, 04, 11, 04, 38, 0, time.UTC)}, {input: sage}, {input: time.Duration(1)}, {input: time.Duration(0)}, } for _, v := range table { b.Run(fmt.Sprintf("input_%T_%v", v.input, v.input), func(b *testing.B) { for b.Loop() { _ = IsAnyNilOrZero(v.input) } }) } } func TestUniqueLowerNames(t *testing.T) { u := UniqueLowerNames([]string{"foobar.com", "fooBAR.com", "baz.com", "foobar.com", "bar.com", "bar.com", "a.com"}) sort.Strings(u) test.AssertDeepEquals(t, []string{"a.com", "bar.com", "baz.com", "foobar.com"}, u) } func TestValidSerial(t *testing.T) { notLength32Or36 := "A" length32 := strings.Repeat("A", 32) length36 := strings.Repeat("A", 36) isValidSerial := ValidSerial(notLength32Or36) test.AssertEquals(t, isValidSerial, false) isValidSerial = ValidSerial(length32) test.AssertEquals(t, isValidSerial, true) isValidSerial = ValidSerial(length36) test.AssertEquals(t, isValidSerial, true) } func TestLoadCert(t *testing.T) { var osPathErr *os.PathError _, err := LoadCert("") test.AssertError(t, err, "Loading empty path did not error") test.AssertErrorWraps(t, err, &osPathErr) _, err = LoadCert("totally/fake/path") test.AssertError(t, err, "Loading nonexistent path did not error") test.AssertErrorWraps(t, err, &osPathErr) _, err = LoadCert("../test/hierarchy/README.md") test.AssertError(t, err, "Loading non-PEM file did not error") test.AssertContains(t, err.Error(), "no data in cert PEM file") _, err = LoadCert("../test/hierarchy/int-e1.key.pem") test.AssertError(t, err, "Loading non-cert PEM file did not error") test.AssertContains(t, err.Error(), "x509: malformed tbs certificate") cert, err := LoadCert("../test/hierarchy/int-r3.cert.pem") test.AssertNotError(t, err, "Failed to load cert PEM file") test.AssertEquals(t, cert.Subject.CommonName, "(TEST) Radical Rhino R3") } func TestRetryBackoff(t *testing.T) { assertBetween := func(a, b, c float64) { t.Helper() if a < b || a > c { t.Fatalf("%f is not between %f and %f", a, b, c) } } factor := 1.5 base := time.Minute max := 10 * time.Minute backoff := RetryBackoff(0, base, max, factor) assertBetween(float64(backoff), 0, 0) expected := base backoff = RetryBackoff(1, base, max, factor) assertBetween(float64(backoff), float64(expected)*0.8, float64(expected)*1.2) expected = time.Second * 90 backoff = RetryBackoff(2, base, max, factor) assertBetween(float64(backoff), float64(expected)*0.8, float64(expected)*1.2) expected = time.Minute * 10 // should be truncated backoff = RetryBackoff(7, base, max, factor) assertBetween(float64(backoff), float64(expected)*0.8, float64(expected)*1.2) } func TestHashIdentifiers(t *testing.T) { dns1 := identifier.NewDNS("example.com") dns1_caps := identifier.NewDNS("eXaMpLe.COM") dns2 := identifier.NewDNS("high-energy-cheese-lab.nrc-cnrc.gc.ca") dns2_caps := identifier.NewDNS("HIGH-ENERGY-CHEESE-LAB.NRC-CNRC.GC.CA") ipv4_1 := identifier.NewIP(netip.MustParseAddr("10.10.10.10")) ipv4_2 := identifier.NewIP(netip.MustParseAddr("172.16.16.16")) ipv6_1 := identifier.NewIP(netip.MustParseAddr("2001:0db8:0bad:0dab:c0ff:fee0:0007:1337")) ipv6_2 := identifier.NewIP(netip.MustParseAddr("3fff::")) testCases := []struct { Name string Idents1 identifier.ACMEIdentifiers Idents2 identifier.ACMEIdentifiers ExpectedEqual bool }{ { Name: "Deterministic for DNS", Idents1: identifier.ACMEIdentifiers{dns1}, Idents2: identifier.ACMEIdentifiers{dns1}, ExpectedEqual: true, }, { Name: "Deterministic for IPv4", Idents1: identifier.ACMEIdentifiers{ipv4_1}, Idents2: identifier.ACMEIdentifiers{ipv4_1}, ExpectedEqual: true, }, { Name: "Deterministic for IPv6", Idents1: identifier.ACMEIdentifiers{ipv6_1}, Idents2: identifier.ACMEIdentifiers{ipv6_1}, ExpectedEqual: true, }, { Name: "Differentiates for DNS", Idents1: identifier.ACMEIdentifiers{dns1}, Idents2: identifier.ACMEIdentifiers{dns2}, ExpectedEqual: false, }, { Name: "Differentiates for IPv4", Idents1: identifier.ACMEIdentifiers{ipv4_1}, Idents2: identifier.ACMEIdentifiers{ipv4_2}, ExpectedEqual: false, }, { Name: "Differentiates for IPv6", Idents1: identifier.ACMEIdentifiers{ipv6_1}, Idents2: identifier.ACMEIdentifiers{ipv6_2}, ExpectedEqual: false, }, { Name: "Not subject to ordering", Idents1: identifier.ACMEIdentifiers{ dns1, dns2, ipv4_1, ipv4_2, ipv6_1, ipv6_2, }, Idents2: identifier.ACMEIdentifiers{ ipv6_1, dns2, ipv4_2, dns1, ipv4_1, ipv6_2, }, ExpectedEqual: true, }, { Name: "Not case sensitive", Idents1: identifier.ACMEIdentifiers{ dns1, dns2, }, Idents2: identifier.ACMEIdentifiers{ dns1_caps, dns2_caps, }, ExpectedEqual: true, }, { Name: "Not subject to duplication", Idents1: identifier.ACMEIdentifiers{ dns1, dns1, }, Idents2: identifier.ACMEIdentifiers{dns1}, ExpectedEqual: true, }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { t.Parallel() h1 := HashIdentifiers(tc.Idents1) h2 := HashIdentifiers(tc.Idents2) if slices.Equal(h1, h2) != tc.ExpectedEqual { t.Errorf("Comparing hashes of idents %#v and %#v, expected equality to be %v", tc.Idents1, tc.Idents2, tc.ExpectedEqual) } }) } } func TestIsCanceled(t *testing.T) { if !IsCanceled(context.Canceled) { t.Errorf("Expected context.Canceled to be canceled, but wasn't.") } if !IsCanceled(status.Errorf(codes.Canceled, "hi")) { t.Errorf("Expected gRPC cancellation to be canceled, but wasn't.") } if IsCanceled(errors.New("hi")) { t.Errorf("Expected random error to not be canceled, but was.") } } golang-github-letsencrypt-boulder-0.20251208.0+ds1/go.mod000066400000000000000000000107111511605055500226020ustar00rootroot00000000000000module github.com/letsencrypt/boulder go 1.25.0 require ( github.com/aws/aws-sdk-go-v2 v1.39.2 github.com/aws/aws-sdk-go-v2/config v1.31.11 github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3 github.com/aws/smithy-go v1.23.0 github.com/eggsampler/acme/v3 v3.6.2 github.com/go-jose/go-jose/v4 v4.1.2 github.com/go-logr/stdr v1.2.2 github.com/go-sql-driver/mysql v1.9.1 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/google/certificate-transparency-go v1.3.2-0.20250507091337-0eddb39e94f8 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 github.com/jmhodges/clock v1.2.0 github.com/letsencrypt/borp v0.0.0-20251118150929-89c6927051ae github.com/letsencrypt/challtestsrv v1.3.3 github.com/letsencrypt/pkcs11key/v4 v4.0.0 github.com/letsencrypt/validator/v10 v10.0.0-20230215210743-a0c7dfc17158 github.com/miekg/dns v1.1.61 github.com/miekg/pkcs11 v1.1.1 github.com/nxadm/tail v1.4.11 github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_model v0.6.1 github.com/redis/go-redis/extra/redisotel/v9 v9.5.3 github.com/redis/go-redis/v9 v9.10.0 github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 github.com/weppos/publicsuffix-go v0.50.1 github.com/zmap/zcrypto v0.0.0-20250129210703-03c45d0bae98 github.com/zmap/zlint/v3 v3.6.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 go.opentelemetry.io/otel/sdk v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 golang.org/x/crypto v0.44.0 golang.org/x/net v0.47.0 golang.org/x/term v0.37.0 golang.org/x/text v0.31.0 golang.org/x/time v0.11.0 google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.8 gopkg.in/yaml.v3 v3.0.1 ) require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.15 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.29.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/poy/onpar v1.1.2 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect golang.org/x/mod v0.29.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/tools v0.38.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect ) golang-github-letsencrypt-boulder-0.20251208.0+ds1/go.sum000066400000000000000000001260071511605055500226350ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= github.com/aws/aws-sdk-go-v2/config v1.31.11 h1:6QOO1mP0MgytbfKsL/r/gE1P6/c/4pPzrrU3hKxa5fs= github.com/aws/aws-sdk-go-v2/config v1.31.11/go.mod h1:KzpDsPX/dLxaUzoqM3sN2NOhbQIW4HW/0W8rQA1YFEs= github.com/aws/aws-sdk-go-v2/credentials v1.18.15 h1:Gqy7/05KEfUSulSvwxnB7t8DuZMR3ShzNcwmTD6HOLU= github.com/aws/aws-sdk-go-v2/credentials v1.18.15/go.mod h1:VWDWSRpYHjcjURRaQ7NUzgeKFN8Iv31+EOMT/W+bFyc= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 h1:w9LnHqTq8MEdlnyhV4Bwfizd65lfNCNgdlNC6mM5paE= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9/go.mod h1:LGEP6EK4nj+bwWNdrvX/FnDTFowdBNwcSPuZu/ouFys= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9 h1:by3nYZLR9l8bUH7kgaMU4dJgYFjyRdFEfORlDpPILB4= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.9/go.mod h1:IWjQYlqw4EX9jw2g3qnEPPWvCE6bS8fKzhMed1OK7c8= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 h1:wuZ5uW2uhJR63zwNlqWH2W4aL4ZjeJP3o92/W+odDY4= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9/go.mod h1:/G58M2fGszCrOzvJUkDdY8O9kycodunH4VdT5oBAqls= github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3 h1:P18I4ipbk+b/3dZNq5YYh+Hq6XC0vp5RWkLp1tJldDA= github.com/aws/aws-sdk-go-v2/service/s3 v1.88.3/go.mod h1:Rm3gw2Jov6e6kDuamDvyIlZJDMYk97VeCZ82wz/mVZ0= github.com/aws/aws-sdk-go-v2/service/sso v1.29.5 h1:WwL5YLHabIBuAlEKRoLgqLz1LxTvCEpwsQr7MiW/vnM= github.com/aws/aws-sdk-go-v2/service/sso v1.29.5/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo= github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA= github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8= github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/eggsampler/acme/v3 v3.6.2 h1:gvyZbQ92wNQLDASVftGpHEdFwPSfg0+17P0lLt09Tp8= github.com/eggsampler/acme/v3 v3.6.2/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/certificate-transparency-go v1.3.2-0.20250507091337-0eddb39e94f8 h1:1RSWsOSxq2gk4pD/63bhsPwoOXgz2yXVadxXPbwZ0ec= github.com/google/certificate-transparency-go v1.3.2-0.20250507091337-0eddb39e94f8/go.mod h1:6Rm5w0Mlv87LyBNOCgfKYjdIBBpF42XpXGsbQvQGomQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/letsencrypt/borp v0.0.0-20251118150929-89c6927051ae h1:yFuF5yRIwaandcuNMi1A4he4FMWJsGRv38rsizIaxJA= github.com/letsencrypt/borp v0.0.0-20251118150929-89c6927051ae/go.mod h1:gMSMCNKhxox/ccR923EJsIvHeVVYfCABGbirqa0EwuM= github.com/letsencrypt/challtestsrv v1.3.3 h1:ki02PH84fo6IOe/A+zt1/kfRBp2JrtauEaa5xwjg4/Q= github.com/letsencrypt/challtestsrv v1.3.3/go.mod h1:Ur4e4FvELUXLGhkMztHOsPIsvGxD/kzSJninOrkM+zc= github.com/letsencrypt/pkcs11key/v4 v4.0.0 h1:qLc/OznH7xMr5ARJgkZCCWk+EomQkiNTOoOF5LAgagc= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= github.com/letsencrypt/validator/v10 v10.0.0-20230215210743-a0c7dfc17158 h1:HGFsIltYMUiB5eoFSowFzSoXkocM2k9ctmJ57QMGjys= github.com/letsencrypt/validator/v10 v10.0.0-20230215210743-a0c7dfc17158/go.mod h1:ZFNBS3H6OEsprCRjscty6GCBe5ZiX44x6qY4s7+bDX0= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-sqlite3 v1.14.26 h1:h72fc7d3zXGhHpwjWw+fPOBxYUupuKlbhUAQi5n6t58= github.com/mattn/go-sqlite3 v1.14.26/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v0.0.0-20200406201722-06f95a1c68e8/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3 h1:1/BDligzCa40GTllkDnY3Y5DTHuKCONbB2JcRyIfl20= github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3/go.mod h1:3dZmcLn3Qw6FLlWASn1g4y+YO9ycEFUOM+bhBmzLVKQ= github.com/redis/go-redis/extra/redisotel/v9 v9.5.3 h1:kuvuJL/+MZIEdvtb/kTBRiRgYaOmx1l+lYJyVdrRUOs= github.com/redis/go-redis/extra/redisotel/v9 v9.5.3/go.mod h1:7f/FMrf5RRRVHXgfk7CzSVzXHiWeuOQUu2bsVqWoa+g= github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs= github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/weppos/publicsuffix-go v0.40.3-0.20250127173806-e489a31678ca/go.mod h1:43Dfyxu2dpmLg56at26Q4k9gwf3yWSUiwk8kGnwzULk= github.com/weppos/publicsuffix-go v0.50.1 h1:elrBHeSkS/eIb169+DnLrknqmdP4AjT0Q0tEdytz1Og= github.com/weppos/publicsuffix-go v0.50.1/go.mod h1:znn0JVXjcR5hpUl9pbEogwH6I710rA1AX0QQPT0bf+k= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk= github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= github.com/zmap/zcrypto v0.0.0-20250129210703-03c45d0bae98 h1:Qp98bmMm9JHPPOaLi2Nb6oWoZ+1OyOMWI7PPeJrirI0= github.com/zmap/zcrypto v0.0.0-20250129210703-03c45d0bae98/go.mod h1:YTUyN/U1oJ7RzCEY5hUweYxbVUu7X+11wB7OXZT15oE= github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8= github.com/zmap/zlint/v3 v3.6.6 h1:tH7RJM9bDmh7IonlLEkFIkIn8XDYDYjehhUPgpLVqYA= github.com/zmap/zlint/v3 v3.6.6/go.mod h1:6yXG+CBOQBRpMCOnpIVPUUL296m5HYksZC9bj5LZkwE= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= golang-github-letsencrypt-boulder-0.20251208.0+ds1/goodkey/000077500000000000000000000000001511605055500231355ustar00rootroot00000000000000golang-github-letsencrypt-boulder-0.20251208.0+ds1/goodkey/good_key.go000066400000000000000000000370541511605055500252750ustar00rootroot00000000000000package goodkey import ( "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "errors" "fmt" "math/big" "sync" "github.com/letsencrypt/boulder/core" "github.com/titanous/rocacheck" ) // To generate, run: primes 2 752 | tr '\n' , var smallPrimeInts = []int64{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, } // singleton defines the object of a Singleton pattern var ( smallPrimesSingleton sync.Once smallPrimesProduct *big.Int ) type Config struct { // AllowedKeys enables or disables specific key algorithms and sizes. If // nil, defaults to just those keys allowed by the Let's Encrypt CPS. AllowedKeys *AllowedKeys // FermatRounds is an integer number of rounds of Fermat's factorization // method that should be performed to attempt to detect keys whose modulus can // be trivially factored because the two factors are very close to each other. // If this config value is empty or 0, it will default to 110 rounds. FermatRounds int } // AllowedKeys is a map of six specific key algorithm and size combinations to // booleans indicating whether keys of that type are considered good. type AllowedKeys struct { // Baseline Requirements, Section 6.1.5 requires key size >= 2048 and a multiple // of 8 bits: https://github.com/cabforum/servercert/blob/main/docs/BR.md#615-key-sizes // Baseline Requirements, Section 6.1.1.3 requires that we reject any keys which // have a known method to easily compute their private key, such as Debian Weak // Keys. Our enforcement mechanism relies on enumerating all Debian Weak Keys at // common key sizes, so we restrict all issuance to those common key sizes. RSA2048 bool RSA3072 bool RSA4096 bool // Baseline Requirements, Section 6.1.5 requires that ECDSA keys be valid // points on the NIST P-256, P-384, or P-521 elliptic curves. ECDSAP256 bool ECDSAP384 bool ECDSAP521 bool } // LetsEncryptCPS encodes the five key algorithms and sizes allowed by the Let's // Encrypt CPS CV-SSL Subscriber Certificate Profile: RSA 2048, RSA 3076, RSA // 4096, ECDSA 256 and ECDSA P384. // https://github.com/letsencrypt/cp-cps/blob/main/CP-CPS.md#dv-ssl-subscriber-certificate // If this is ever changed, the CP/CPS MUST be changed first. func LetsEncryptCPS() AllowedKeys { return AllowedKeys{ RSA2048: true, RSA3072: true, RSA4096: true, ECDSAP256: true, ECDSAP384: true, } } // ErrBadKey represents an error with a key. It is distinct from the various // ways in which an ACME request can have an erroneous key (BadPublicKeyError, // BadCSRError) because this library is used to check both JWS signing keys and // keys in CSRs. var ErrBadKey = errors.New("") func badKey(msg string, args ...any) error { return fmt.Errorf("%w%s", ErrBadKey, fmt.Errorf(msg, args...)) } // BlockedKeyCheckFunc is used to pass in the sa.BlockedKey functionality to KeyPolicy, // rather than storing a full sa.SQLStorageAuthority. This allows external // users who don’t want to import all of boulder/sa, and makes testing // significantly simpler. // On success, the function returns a boolean which is true if the key is blocked. type BlockedKeyCheckFunc func(ctx context.Context, keyHash []byte) (bool, error) // KeyPolicy determines which types of key may be used with various boulder // operations. type KeyPolicy struct { allowedKeys AllowedKeys fermatRounds int blockedCheck BlockedKeyCheckFunc } // NewPolicy returns a key policy based on the given configuration, with sane // defaults. If the config's AllowedKeys is nil, the LetsEncryptCPS AllowedKeys // is used. If the configured FermatRounds is 0, Fermat Factorization defaults to // attempting 110 rounds. func NewPolicy(config *Config, bkc BlockedKeyCheckFunc) (KeyPolicy, error) { if config == nil { config = &Config{} } kp := KeyPolicy{ blockedCheck: bkc, } if config.AllowedKeys == nil { kp.allowedKeys = LetsEncryptCPS() } else { kp.allowedKeys = *config.AllowedKeys } if config.FermatRounds == 0 { // The BRs require 100 rounds, so give ourselves a margin above that. kp.fermatRounds = 110 } else if config.FermatRounds < 100 { return KeyPolicy{}, fmt.Errorf("Fermat factorization rounds must be at least 100: %d", config.FermatRounds) } else { kp.fermatRounds = config.FermatRounds } return kp, nil } // GoodKey returns true if the key is acceptable for both TLS use and account // key use (our requirements are the same for either one), according to basic // strength and algorithm checking. GoodKey only supports pointers: *rsa.PublicKey // and *ecdsa.PublicKey. It will reject non-pointer types. // TODO: Support JSONWebKeys once go-jose migration is done. func (policy *KeyPolicy) GoodKey(ctx context.Context, key crypto.PublicKey) error { // Early rejection of unacceptable key types to guard subsequent checks. switch t := key.(type) { case *rsa.PublicKey, *ecdsa.PublicKey: break default: return badKey("unsupported key type %T", t) } if policy.blockedCheck != nil { digest, err := core.KeyDigest(key) if err != nil { return badKey("%w", err) } exists, err := policy.blockedCheck(ctx, digest[:]) if err != nil { return err } else if exists { return badKey("public key is forbidden") } } switch t := key.(type) { case *rsa.PublicKey: return policy.goodKeyRSA(t) case *ecdsa.PublicKey: return policy.goodKeyECDSA(t) default: return badKey("unsupported key type %T", key) } } // GoodKeyECDSA determines if an ECDSA pubkey meets our requirements func (policy *KeyPolicy) goodKeyECDSA(key *ecdsa.PublicKey) (err error) { // Check the curve. // // The validity of the curve is an assumption for all following tests. err = policy.goodCurve(key.Curve) if err != nil { return err } // Key validation routine adapted from NIST SP800-56A § 5.6.2.3.2. // // // Assuming a prime field since a) we are only allowing such curves and b) // crypto/elliptic only supports prime curves. Where this assumption // simplifies the code below, it is explicitly stated and explained. If ever // adapting this code to support non-prime curves, refer to NIST SP800-56A § // 5.6.2.3.2 and adapt this code appropriately. params := key.Params() // SP800-56A § 5.6.2.3.2 Step 1. // Partial check of the public key for an invalid range in the EC group: // Verify that key is not the point at infinity O. // This code assumes that the point at infinity is (0,0), which is the // case for all supported curves. if isPointAtInfinityNISTP(key.X, key.Y) { return badKey("key x, y must not be the point at infinity") } // SP800-56A § 5.6.2.3.2 Step 2. // "Verify that x_Q and y_Q are integers in the interval [0,p-1] in the // case that q is an odd prime p, or that x_Q and y_Q are bit strings // of length m bits in the case that q = 2**m." // // Prove prime field: ASSUMED. // Prove q != 2: ASSUMED. (Curve parameter. No supported curve has q == 2.) // Prime field && q != 2 => q is an odd prime p // Therefore "verify that x, y are in [0, p-1]" satisfies step 2. // // Therefore verify that both x and y of the public key point have the unique // correct representation of an element in the underlying field by verifying // that x and y are integers in [0, p-1]. if key.X.Sign() < 0 || key.Y.Sign() < 0 { return badKey("key x, y must not be negative") } if key.X.Cmp(params.P) >= 0 || key.Y.Cmp(params.P) >= 0 { return badKey("key x, y must not exceed P-1") } // SP800-56A § 5.6.2.3.2 Step 3. // "If q is an odd prime p, verify that (y_Q)**2 === (x_Q)***3 + a*x_Q + b (mod p). // If q = 2**m, verify that (y_Q)**2 + (x_Q)*(y_Q) == (x_Q)**3 + a*(x_Q)*2 + b in // the finite field of size 2**m. // (Ensures that the public key is on the correct elliptic curve.)" // // q is an odd prime p: proven/assumed above. // a = -3 for all supported curves. // // Therefore step 3 is satisfied simply by showing that // y**2 === x**3 - 3*x + B (mod P). // // This proves that the public key is on the correct elliptic curve. // But in practice, this test is provided by crypto/elliptic, so use that. if !key.Curve.IsOnCurve(key.X, key.Y) { return badKey("key point is not on the curve") } // SP800-56A § 5.6.2.3.2 Step 4. // "Verify that n*Q == Ø. // (Ensures that the public key has the correct order. Along with check 1, // ensures that the public key is in the correct range in the correct EC // subgroup, that is, it is in the correct EC subgroup and is not the // identity element.)" // // Ensure that public key has the correct order: // verify that n*Q = Ø. // // n*Q = Ø iff n*Q is the point at infinity (see step 1). ox, oy := key.Curve.ScalarMult(key.X, key.Y, params.N.Bytes()) if !isPointAtInfinityNISTP(ox, oy) { return badKey("public key does not have correct order") } // End of SP800-56A § 5.6.2.3.2 Public Key Validation Routine. // Key is valid. return nil } // Returns true iff the point (x,y) on NIST P-256, NIST P-384 or NIST P-521 is // the point at infinity. These curves all have the same point at infinity // (0,0). This function must ONLY be used on points on curves verified to have // (0,0) as their point at infinity. func isPointAtInfinityNISTP(x, y *big.Int) bool { return x.Sign() == 0 && y.Sign() == 0 } // GoodCurve determines if an elliptic curve meets our requirements. func (policy *KeyPolicy) goodCurve(c elliptic.Curve) (err error) { // Simply use a whitelist for now. params := c.Params() switch { case policy.allowedKeys.ECDSAP256 && params == elliptic.P256().Params(): return nil case policy.allowedKeys.ECDSAP384 && params == elliptic.P384().Params(): return nil case policy.allowedKeys.ECDSAP521 && params == elliptic.P521().Params(): return nil default: return badKey("ECDSA curve %v not allowed", params.Name) } } // GoodKeyRSA determines if a RSA pubkey meets our requirements func (policy *KeyPolicy) goodKeyRSA(key *rsa.PublicKey) error { modulus := key.N err := policy.goodRSABitLen(key) if err != nil { return err } // Rather than support arbitrary exponents, which significantly increases // the size of the key space we allow, we restrict E to the defacto standard // RSA exponent 65537. There is no specific standards document that specifies // 65537 as the 'best' exponent, but ITU X.509 Annex C suggests there are // notable merits for using it if using a fixed exponent. // // The CABF Baseline Requirements state: // The CA SHALL confirm that the value of the public exponent is an // odd number equal to 3 or more. Additionally, the public exponent // SHOULD be in the range between 2^16 + 1 and 2^256-1. // // By only allowing one exponent, which fits these constraints, we satisfy // these requirements. if key.E != 65537 { return badKey("key exponent must be 65537") } // The modulus SHOULD also have the following characteristics: an odd // number, not the power of a prime, and have no factors smaller than 752. // TODO: We don't yet check for "power of a prime." if checkSmallPrimes(modulus) { return badKey("key divisible by small prime") } // Check for weak keys generated by Infineon hardware // (see https://crocs.fi.muni.cz/public/papers/rsa_ccs17) if rocacheck.IsWeak(key) { return badKey("key generated by vulnerable Infineon-based hardware") } // Check if the key can be easily factored via Fermat's factorization method. err = checkPrimeFactorsTooClose(modulus, policy.fermatRounds) if err != nil { return badKey("key generated with factors too close together: %w", err) } return nil } func (policy *KeyPolicy) goodRSABitLen(key *rsa.PublicKey) error { // See comment on AllowedKeys above. modulusBitLen := key.N.BitLen() switch { case modulusBitLen == 2048 && policy.allowedKeys.RSA2048: return nil case modulusBitLen == 3072 && policy.allowedKeys.RSA3072: return nil case modulusBitLen == 4096 && policy.allowedKeys.RSA4096: return nil default: return badKey("key size not supported: %d", modulusBitLen) } } // Returns true iff integer i is divisible by any of the primes in smallPrimes. // // Short circuits; execution time is dependent on i. Do not use this on secret // values. // // Rather than checking each prime individually (invoking Mod on each), // multiply the primes together and let GCD do our work for us: if the // GCD between and is not one, we know we have // a bad key. This is substantially faster than checking each prime // individually. func checkSmallPrimes(i *big.Int) bool { smallPrimesSingleton.Do(func() { smallPrimesProduct = big.NewInt(1) for _, prime := range smallPrimeInts { smallPrimesProduct.Mul(smallPrimesProduct, big.NewInt(prime)) } }) // When the GCD is 1, i and smallPrimesProduct are coprime, meaning they // share no common factors. When the GCD is not one, it is the product of // all common factors, meaning we've identified at least one small prime // which invalidates i as a valid key. var result big.Int result.GCD(nil, nil, i, smallPrimesProduct) return result.Cmp(big.NewInt(1)) != 0 } // Returns an error if the modulus n is able to be factored into primes p and q // via Fermat's factorization method. This method relies on the two primes being // very close together, which means that they were almost certainly not picked // independently from a uniform random distribution. Basically, if we can factor // the key this easily, so can anyone else. func checkPrimeFactorsTooClose(n *big.Int, rounds int) error { // Pre-allocate some big numbers that we'll use a lot down below. one := big.NewInt(1) bb := new(big.Int) // Any odd integer is equal to a difference of squares of integers: // n = a^2 - b^2 = (a + b)(a - b) // Any RSA public key modulus is equal to a product of two primes: // n = pq // Here we try to find values for a and b, since doing so also gives us the // prime factors p = (a + b) and q = (a - b). // We start with a close to the square root of the modulus n, to start with // two candidate prime factors that are as close together as possible and // work our way out from there. Specifically, we set a = ceil(sqrt(n)), the // first integer greater than the square root of n. Unfortunately, big.Int's // built-in square root function takes the floor, so we have to add one to get // the ceil. a := new(big.Int) a.Sqrt(n).Add(a, one) // We calculate b2 to see if it is a perfect square (i.e. b^2), and therefore // b is an integer. Specifically, b2 = a^2 - n. b2 := new(big.Int) b2.Mul(a, a).Sub(b2, n) for round := range rounds { // To see if b2 is a perfect square, we take its square root, square that, // and check to see if we got the same result back. bb.Sqrt(b2).Mul(bb, bb) if b2.Cmp(bb) == 0 { // b2 is a perfect square, so we've found integer values of a and b, // and can easily compute p and q as their sum and difference. bb.Sqrt(bb) p := new(big.Int).Add(a, bb) q := new(big.Int).Sub(a, bb) return fmt.Errorf("public modulus n = pq factored in %d rounds into p: %s and q: %s", round+1, p, q) } // Set up the next iteration by incrementing a by one and recalculating b2. a.Add(a, one) b2.Mul(a, a).Sub(b2, n) } return nil } golang-github-letsencrypt-boulder-0.20251208.0+ds1/goodkey/good_key_test.go000066400000000000000000000511771511605055500263360ustar00rootroot00000000000000package goodkey import ( "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "fmt" "math/big" "testing" "github.com/letsencrypt/boulder/test" ) // testingPolicy is a simple policy which allows all of the key types, so that // the unit tests can exercise checks against all key types. var testingPolicy = &KeyPolicy{allowedKeys: AllowedKeys{ RSA2048: true, RSA3072: true, RSA4096: true, ECDSAP256: true, ECDSAP384: true, ECDSAP521: true, }} func TestUnknownKeyType(t *testing.T) { notAKey := struct{}{} err := testingPolicy.GoodKey(context.Background(), notAKey) test.AssertError(t, err, "Should have rejected a key of unknown type") test.AssertEquals(t, err.Error(), "unsupported key type struct {}") } func TestNilKey(t *testing.T) { err := testingPolicy.GoodKey(context.Background(), nil) test.AssertError(t, err, "Should have rejected a nil key") test.AssertEquals(t, err.Error(), "unsupported key type ") } func TestSmallModulus(t *testing.T) { pubKey := rsa.PublicKey{ N: big.NewInt(0), E: 65537, } // 2040 bits _, ok := pubKey.N.SetString("104192126510885102608953552259747211060428328569316484779167706297543848858189721071301121307701498317286069484848193969810800653457088975832436062805901725915630417996487259956349018066196416400386483594314258078114607080545265502078791826837453107382149801328758721235866366842649389274931060463277516954884108984101391466769505088222180613883737986792254164577832157921425082478871935498631777878563742033332460445633026471887331001305450139473524438241478798689974351175769895824322173301257621327448162705637127373457350813027123239805772024171112299987923305882261194120410409098448380641378552305583392176287", 10) if !ok { t.Errorf("error parsing pubkey modulus") } err := testingPolicy.GoodKey(context.Background(), &pubKey) test.AssertError(t, err, "Should have rejected too-short key") test.AssertEquals(t, err.Error(), "key size not supported: 2040") } func TestLargeModulus(t *testing.T) { pubKey := rsa.PublicKey{ N: big.NewInt(0), E: 65537, } // 4097 bits _, ok := pubKey.N.SetString("1528586537844618544364689295678280797814937047039447018548513699782432768815684971832418418955305671838918285565080181315448131784543332408348488544125812746629522583979538961638790013578302979210481729874191053412386396889481430969071543569003141391030053024684850548909056275565684242965892176703473950844930842702506635531145654194239072799616096020023445127233557468234181352398708456163013484600764686209741158795461806441111028922165846800488957692595308009319392149669715238691709012014980470238746838534949750493558807218940354555205690667168930634644030378921382266510932028134500172599110460167962515262077587741235811653717121760943005253103187409557573174347385738572144714188928416780963680160418832333908040737262282830643745963536624555340279793555475547508851494656512855403492456740439533790565640263514349940712999516725281940465613417922773583725174223806589481568984323871222072582132221706797917380250216291620957692131931099423995355390698925093903005385497308399692769135287821632877871068909305276870015125960884987746154344006895331078411141197233179446805991116541744285238281451294472577537413640009811940462311100056023815261650331552185459228689469446389165886801876700815724561451940764544990177661873073", 10) if !ok { t.Errorf("error parsing pubkey modulus") } err := testingPolicy.GoodKey(context.Background(), &pubKey) test.AssertError(t, err, "Should have rejected too-long key") test.AssertEquals(t, err.Error(), "key size not supported: 4097") } func TestModulusModulo8(t *testing.T) { bigOne := big.NewInt(1) key := rsa.PublicKey{ N: bigOne.Lsh(bigOne, 2048), E: 5, } err := testingPolicy.GoodKey(context.Background(), &key) test.AssertError(t, err, "Should have rejected modulus with length not divisible by 8") test.AssertEquals(t, err.Error(), "key size not supported: 2049") } var mod2048 = big.NewInt(0).Sub(big.NewInt(0).Lsh(big.NewInt(1), 2048), big.NewInt(1)) func TestNonStandardExp(t *testing.T) { evenMod := big.NewInt(0).Add(big.NewInt(1).Lsh(big.NewInt(1), 2047), big.NewInt(2)) key := rsa.PublicKey{ N: evenMod, E: (1 << 16), } err := testingPolicy.GoodKey(context.Background(), &key) test.AssertError(t, err, "Should have rejected non-standard exponent") test.AssertEquals(t, err.Error(), "key exponent must be 65537") } func TestEvenModulus(t *testing.T) { evenMod := big.NewInt(0).Add(big.NewInt(1).Lsh(big.NewInt(1), 2047), big.NewInt(2)) key := rsa.PublicKey{ N: evenMod, E: (1 << 16) + 1, } err := testingPolicy.GoodKey(context.Background(), &key) test.AssertError(t, err, "Should have rejected even modulus") test.AssertEquals(t, err.Error(), "key divisible by small prime") } func TestModulusDivisibleBySmallPrime(t *testing.T) { key := rsa.PublicKey{ N: mod2048, E: (1 << 16) + 1, } err := testingPolicy.GoodKey(context.Background(), &key) test.AssertError(t, err, "Should have rejected modulus divisible by 3") test.AssertEquals(t, err.Error(), "key divisible by small prime") } func TestROCA(t *testing.T) { n, ok := big.NewInt(1).SetString("19089470491547632015867380494603366846979936677899040455785311493700173635637619562546319438505971838982429681121352968394792665704951454132311441831732124044135181992768774222852895664400681270897445415599851900461316070972022018317962889565731866601557238345786316235456299813772607869009873279585912430769332375239444892105064608255089298943707214066350230292124208314161171265468111771687514518823144499250339825049199688099820304852696380797616737008621384107235756455735861506433065173933123259184114000282435500939123478591192413006994709825840573671701120771013072419520134975733578923370992644987545261926257", 10) if !ok { t.Fatal("failed to parse") } key := rsa.PublicKey{ N: n, E: 65537, } err := testingPolicy.GoodKey(context.Background(), &key) test.AssertError(t, err, "Should have rejected ROCA-weak key") test.AssertEquals(t, err.Error(), "key generated by vulnerable Infineon-based hardware") } func TestGoodKey(t *testing.T) { private, err := rsa.GenerateKey(rand.Reader, 2048) test.AssertNotError(t, err, "Error generating key") test.AssertNotError(t, testingPolicy.GoodKey(context.Background(), &private.PublicKey), "Should have accepted good key") } func TestECDSABadCurve(t *testing.T) { for _, curve := range invalidCurves { private, err := ecdsa.GenerateKey(curve, rand.Reader) test.AssertNotError(t, err, "Error generating key") err = testingPolicy.GoodKey(context.Background(), &private.PublicKey) test.AssertError(t, err, "Should have rejected key with unsupported curve") test.AssertEquals(t, err.Error(), fmt.Sprintf("ECDSA curve %s not allowed", curve.Params().Name)) } } var invalidCurves = []elliptic.Curve{ elliptic.P224(), } var validCurves = []elliptic.Curve{ elliptic.P256(), elliptic.P384(), elliptic.P521(), } func TestECDSAGoodKey(t *testing.T) { for _, curve := range validCurves { private, err := ecdsa.GenerateKey(curve, rand.Reader) test.AssertNotError(t, err, "Error generating key") test.AssertNotError(t, testingPolicy.GoodKey(context.Background(), &private.PublicKey), "Should have accepted good key") } } func TestECDSANotOnCurveX(t *testing.T) { for _, curve := range validCurves { // Change a public key so that it is no longer on the curve. private, err := ecdsa.GenerateKey(curve, rand.Reader) test.AssertNotError(t, err, "Error generating key") private.X.Add(private.X, big.NewInt(1)) err = testingPolicy.GoodKey(context.Background(), &private.PublicKey) test.AssertError(t, err, "Should not have accepted key not on the curve") test.AssertEquals(t, err.Error(), "key point is not on the curve") } } func TestECDSANotOnCurveY(t *testing.T) { for _, curve := range validCurves { // Again with Y. private, err := ecdsa.GenerateKey(curve, rand.Reader) test.AssertNotError(t, err, "Error generating key") // Change the public key so that it is no longer on the curve. private.Y.Add(private.Y, big.NewInt(1)) err = testingPolicy.GoodKey(context.Background(), &private.PublicKey) test.AssertError(t, err, "Should not have accepted key not on the curve") test.AssertEquals(t, err.Error(), "key point is not on the curve") } } func TestECDSANegative(t *testing.T) { for _, curve := range validCurves { // Check that negative X is not accepted. private, err := ecdsa.GenerateKey(curve, rand.Reader) test.AssertNotError(t, err, "Error generating key") private.X.Neg(private.X) err = testingPolicy.GoodKey(context.Background(), &private.PublicKey) test.AssertError(t, err, "Should not have accepted key with negative X") test.AssertEquals(t, err.Error(), "key x, y must not be negative") // Check that negative Y is not accepted. private.X.Neg(private.X) private.Y.Neg(private.Y) err = testingPolicy.GoodKey(context.Background(), &private.PublicKey) test.AssertError(t, err, "Should not have accepted key with negative Y") test.AssertEquals(t, err.Error(), "key x, y must not be negative") } } func TestECDSAXOutsideField(t *testing.T) { for _, curve := range validCurves { // Check that X outside [0, p-1] is not accepted. private, err := ecdsa.GenerateKey(curve, rand.Reader) test.AssertNotError(t, err, "Error generating key") private.X.Mul(private.X, private.Curve.Params().P) err = testingPolicy.GoodKey(context.Background(), &private.PublicKey) test.AssertError(t, err, "Should not have accepted key with a X > p-1") test.AssertEquals(t, err.Error(), "key x, y must not exceed P-1") } } func TestECDSAYOutsideField(t *testing.T) { for _, curve := range validCurves { // Check that Y outside [0, p-1] is not accepted. private, err := ecdsa.GenerateKey(curve, rand.Reader) test.AssertNotError(t, err, "Error generating key") private.X.Mul(private.Y, private.Curve.Params().P) err = testingPolicy.GoodKey(context.Background(), &private.PublicKey) test.AssertError(t, err, "Should not have accepted key with a Y > p-1") test.AssertEquals(t, err.Error(), "key x, y must not exceed P-1") } } func TestECDSAIdentity(t *testing.T) { for _, curve := range validCurves { // The point at infinity is 0,0, it should not be accepted. public := ecdsa.PublicKey{ Curve: curve, X: big.NewInt(0), Y: big.NewInt(0), } err := testingPolicy.GoodKey(context.Background(), &public) test.AssertError(t, err, "Should not have accepted key with point at infinity") test.AssertEquals(t, err.Error(), "key x, y must not be the point at infinity") } } func TestNonRefKey(t *testing.T) { private, err := rsa.GenerateKey(rand.Reader, 2048) test.AssertNotError(t, err, "Error generating key") test.AssertError(t, testingPolicy.GoodKey(context.Background(), private.PublicKey), "Accepted non-reference key") } func TestDBBlocklistAccept(t *testing.T) { for _, testCheck := range []BlockedKeyCheckFunc{ nil, func(context.Context, []byte) (bool, error) { return false, nil }, } { policy, err := NewPolicy(nil, testCheck) test.AssertNotError(t, err, "NewKeyPolicy failed") k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "ecdsa.GenerateKey failed") err = policy.GoodKey(context.Background(), k.Public()) test.AssertNotError(t, err, "GoodKey failed with a non-blocked key") } } func TestDBBlocklistReject(t *testing.T) { testCheck := func(context.Context, []byte) (bool, error) { return true, nil } policy, err := NewPolicy(nil, testCheck) test.AssertNotError(t, err, "NewKeyPolicy failed") k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "ecdsa.GenerateKey failed") err = policy.GoodKey(context.Background(), k.Public()) test.AssertError(t, err, "GoodKey didn't fail with a blocked key") test.AssertErrorIs(t, err, ErrBadKey) test.AssertEquals(t, err.Error(), "public key is forbidden") } func TestDefaultAllowedKeys(t *testing.T) { policy, err := NewPolicy(nil, nil) test.AssertNotError(t, err, "NewPolicy with nil config failed") test.Assert(t, policy.allowedKeys.RSA2048, "RSA 2048 should be allowed") test.Assert(t, policy.allowedKeys.RSA3072, "RSA 3072 should be allowed") test.Assert(t, policy.allowedKeys.RSA4096, "RSA 4096 should be allowed") test.Assert(t, policy.allowedKeys.ECDSAP256, "NIST P256 should be allowed") test.Assert(t, policy.allowedKeys.ECDSAP384, "NIST P384 should be allowed") test.Assert(t, !policy.allowedKeys.ECDSAP521, "NIST P521 should not be allowed") policy, err = NewPolicy(&Config{}, nil) test.AssertNotError(t, err, "NewPolicy with nil config.AllowedKeys failed") test.Assert(t, policy.allowedKeys.RSA2048, "RSA 2048 should be allowed") test.Assert(t, policy.allowedKeys.RSA3072, "RSA 3072 should be allowed") test.Assert(t, policy.allowedKeys.RSA4096, "RSA 4096 should be allowed") test.Assert(t, policy.allowedKeys.ECDSAP256, "NIST P256 should be allowed") test.Assert(t, policy.allowedKeys.ECDSAP384, "NIST P384 should be allowed") test.Assert(t, !policy.allowedKeys.ECDSAP521, "NIST P521 should not be allowed") } func TestRSAStrangeSize(t *testing.T) { k := &rsa.PublicKey{N: big.NewInt(10)} err := testingPolicy.GoodKey(context.Background(), k) test.AssertError(t, err, "expected GoodKey to fail") test.AssertEquals(t, err.Error(), "key size not supported: 4") } func TestCheckPrimeFactorsTooClose(t *testing.T) { type testCase struct { name string p string q string expectRounds int } testCases := []testCase{ { // The factors 59 and 101 multiply to 5959. The values a and b calculated // by Fermat's method will be 80 and 21. The ceil of the square root of // 5959 is 78. Therefore it takes 3 rounds of Fermat's method to find the // factors. name: "tiny", p: "101", q: "59", expectRounds: 3, }, { // These factors differ only in their second-to-last digit. They're so close // that a single iteration of Fermat's method is sufficient to find them. name: "very close", p: "12451309173743450529024753538187635497858772172998414407116324997634262083672423797183640278969532658774374576700091736519352600717664126766443002156788367", q: "12451309173743450529024753538187635497858772172998414407116324997634262083672423797183640278969532658774374576700091736519352600717664126766443002156788337", expectRounds: 1, }, { // These factors differ by slightly more than 2^256, which takes fourteen // rounds to factor. name: "still too close", p: "11779932606551869095289494662458707049283241949932278009554252037480401854504909149712949171865707598142483830639739537075502512627849249573564209082969463", q: "11779932606551869095289494662458707049283241949932278009554252037480401854503793357623711855670284027157475142731886267090836872063809791989556295953329083", expectRounds: 14, }, { // These factors come from a real canon printer in the wild with a broken // key generation mechanism. name: "canon printer (2048 bit, 1 round)", p: "155536235030272749691472293262418471207550926406427515178205576891522284497518443889075039382254334975506248481615035474816604875321501901699955105345417152355947783063521554077194367454070647740704883461064399268622437721385112646454393005862535727615809073410746393326688230040267160616554768771412289114449", q: "155536235030272749691472293262418471207550926406427515178205576891522284497518443889075039382254334975506248481615035474816604875321501901699955105345417152355947783063521554077194367454070647740704883461064399268622437721385112646454393005862535727615809073410746393326688230040267160616554768771412289114113", expectRounds: 1, }, { // These factors come from a real innsbruck printer in the wild with a // broken key generation mechanism. name: "innsbruck printer (4096 bit, 1 round)", p: "25868808535211632564072019392873831934145242707953960515208595626279836366691068618582894100813803673421320899654654938470888358089618966238341690624345530870988951109006149164192566967552401505863871260691612081236189439839963332690997129144163260418447718577834226720411404568398865166471102885763673744513186211985402019037772108416694793355840983833695882936201196462579254234744648546792097397517107797153785052856301942321429858537224127598198913168345965493941246097657533085617002572245972336841716321849601971924830462771411171570422802773095537171762650402420866468579928479284978914972383512240254605625661", q: "25868808535211632564072019392873831934145242707953960515208595626279836366691068618582894100813803673421320899654654938470888358089618966238341690624345530870988951109006149164192566967552401505863871260691612081236189439839963332690997129144163260418447718577834226720411404568398865166471102885763673744513186211985402019037772108416694793355840983833695882936201196462579254234744648546792097397517107797153785052856301942321429858537224127598198913168345965493941246097657533085617002572245972336841716321849601971924830462771411171570422802773095537171762650402420866468579928479284978914972383512240254605624819", expectRounds: 1, }, { // FIPS requires that |p-q| > 2^(nlen/2 - 100). For example, a 2048-bit // RSA key must have prime factors with a difference of at least 2^924. // These two factors have a difference of exactly 2^924 + 4, just *barely* // FIPS-compliant. Their first different digit is in column 52 of this // file, which makes them vastly further apart than the cases above. Their // product cannot be factored even with 100,000,000 rounds of Fermat's // Algorithm. name: "barely FIPS compliant (2048 bit)", p: "151546560166767007654995655231369126386504564489055366370313539237722892921762327477057109592614214965864835328962951695621854530739049166771701397343693962526456985866167580660948398404000483264137738772983130282095332559392185543017295488346592188097443414824871619976114874896240350402349774470198190454623", q: "151546560166767007654995655231510939369872272987323309037144546294925352276321214430320942815891873491060949332482502812040326472743233767963240491605860423063942576391584034077877871768428333113881339606298282107984376151546711223157061364850161576363709081794948857957944390170575452970542651659150041855843", expectRounds: -1, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { p, ok := new(big.Int).SetString(tc.p, 10) if !ok { t.Fatalf("failed to load prime factor p (%s)", tc.p) } q, ok := new(big.Int).SetString(tc.q, 10) if !ok { t.Fatalf("failed to load prime factor q (%s)", tc.q) } n := new(big.Int).Mul(p, q) err := checkPrimeFactorsTooClose(n, 100) if tc.expectRounds > 0 { test.AssertError(t, err, "failed to factor n") test.AssertContains(t, err.Error(), fmt.Sprintf("p: %s", tc.p)) test.AssertContains(t, err.Error(), fmt.Sprintf("q: %s", tc.q)) test.AssertContains(t, err.Error(), fmt.Sprintf("in %d rounds", tc.expectRounds)) } else { test.AssertNil(t, err, "factored the unfactorable") } }) } } func benchFermat(rounds int, b *testing.B) { n := big.NewInt(0) n.SetString("801622717394169050106926578578301725055526605503706912100006286161529273473377413824975745384114446662904851914935980611269769546695796451504160869649117000521094368058953989236438103975426680952076533198797388295193391779933559668812684470909409457778161223896975426492372231040386646816154793996920467596916193680611886097694746368434138296683172992347929528214464827172059378866098534956467670429228681248968588692628197119606249988365750115578731538804653322115223303388019261933988266126675740797091559541980722545880793708750882230374320698192373040882555154628949384420712168289605526223733016176898368282023301917856921049583659644200174763940543991507836551835324807116188739389620816364505209568211448815747330488813651206715564392791134964121857454359816296832013457790067067190116393364546525054134704119475840526673114964766611499226043189928040037210929720682839683846078550615582181112536768195193557758454282232948765374797970874053642822355832904812487562117265271449547063765654262549173209805579494164339236981348054782533307762260970390747872669357067489756517340817289701322583209366268084923373164395703994945233187987667632964509271169622904359262117908604555420100186491963838567445541249128944592555657626247", 10) for b.Loop() { if checkPrimeFactorsTooClose(n, rounds) != nil { b.Fatal("factored the unfactorable!") } } } func BenchmarkFermat1(b *testing.B) { benchFermat(1, b) } func BenchmarkFermat10(b *testing.B) { benchFermat(10, b) } func BenchmarkFermat100(b *testing.B) { benchFermat(100, b) } func BenchmarkFermat1000(b *testing.B) { benchFermat(1000, b) } func BenchmarkFermat10000(b *testing.B) { benchFermat(10000, b) } golang-github-letsencrypt-boulder-0.20251208.0+ds1/goodkey/sagoodkey/000077500000000000000000000000001511605055500251225ustar00rootroot00000000000000golang-github-letsencrypt-boulder-0.20251208.0+ds1/goodkey/sagoodkey/good_key.go000066400000000000000000000017551511605055500272610ustar00rootroot00000000000000package sagoodkey import ( "context" "google.golang.org/grpc" "github.com/letsencrypt/boulder/goodkey" sapb "github.com/letsencrypt/boulder/sa/proto" ) // BlockedKeyCheckFunc is used to pass in the sa.BlockedKey method to KeyPolicy, // rather than storing a full sa.SQLStorageAuthority. This makes testing // significantly simpler. type BlockedKeyCheckFunc func(context.Context, *sapb.SPKIHash, ...grpc.CallOption) (*sapb.Exists, error) // NewPolicy returns a KeyPolicy that uses a sa.BlockedKey method. // See goodkey.NewPolicy for more details about the policy itself. func NewPolicy(config *goodkey.Config, bkc BlockedKeyCheckFunc) (goodkey.KeyPolicy, error) { var genericCheck goodkey.BlockedKeyCheckFunc if bkc != nil { genericCheck = func(ctx context.Context, keyHash []byte) (bool, error) { exists, err := bkc(ctx, &sapb.SPKIHash{KeyHash: keyHash}) if err != nil { return false, err } return exists.Exists, nil } } return goodkey.NewPolicy(config, genericCheck) } golang-github-letsencrypt-boulder-0.20251208.0+ds1/goodkey/sagoodkey/good_key_test.go000066400000000000000000000027401511605055500303130ustar00rootroot00000000000000package sagoodkey import ( "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "testing" "google.golang.org/grpc" "github.com/letsencrypt/boulder/goodkey" sapb "github.com/letsencrypt/boulder/sa/proto" "github.com/letsencrypt/boulder/test" ) func TestDBBlocklistAccept(t *testing.T) { for _, testCheck := range []BlockedKeyCheckFunc{ nil, func(context.Context, *sapb.SPKIHash, ...grpc.CallOption) (*sapb.Exists, error) { return &sapb.Exists{Exists: false}, nil }, } { policy, err := NewPolicy(&goodkey.Config{}, testCheck) test.AssertNotError(t, err, "NewKeyPolicy failed") k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "ecdsa.GenerateKey failed") err = policy.GoodKey(context.Background(), k.Public()) test.AssertNotError(t, err, "GoodKey failed with a non-blocked key") } } func TestDBBlocklistReject(t *testing.T) { testCheck := func(context.Context, *sapb.SPKIHash, ...grpc.CallOption) (*sapb.Exists, error) { return &sapb.Exists{Exists: true}, nil } policy, err := NewPolicy(&goodkey.Config{}, testCheck) test.AssertNotError(t, err, "NewKeyPolicy failed") k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "ecdsa.GenerateKey failed") err = policy.GoodKey(context.Background(), k.Public()) test.AssertError(t, err, "GoodKey didn't fail with a blocked key") test.AssertErrorIs(t, err, goodkey.ErrBadKey) test.AssertEquals(t, err.Error(), "public key is forbidden") } golang-github-letsencrypt-boulder-0.20251208.0+ds1/test/000077500000000000000000000000001511605055500224535ustar00rootroot00000000000000golang-github-letsencrypt-boulder-0.20251208.0+ds1/test/asserts.go000066400000000000000000000221531511605055500244710ustar00rootroot00000000000000package test import ( "bytes" "encoding/base64" "encoding/json" "errors" "reflect" "slices" "strings" "testing" "time" "github.com/prometheus/client_golang/prometheus" io_prometheus_client "github.com/prometheus/client_model/go" ) // Assert a boolean func Assert(t *testing.T, result bool, message string) { t.Helper() if !result { t.Fatal(message) } } // AssertNil checks that an object is nil. Being a "boxed nil" (a nil value // wrapped in a non-nil interface type) is not good enough. func AssertNil(t *testing.T, obj any, message string) { t.Helper() if obj != nil { t.Fatal(message) } } // AssertNotNil checks an object to be non-nil. Being a "boxed nil" (a nil value // wrapped in a non-nil interface type) is not good enough. // Note that there is a gap between AssertNil and AssertNotNil. Both fail when // called with a boxed nil. This is intentional: we want to avoid boxed nils. func AssertNotNil(t *testing.T, obj any, message string) { t.Helper() if obj == nil { t.Fatal(message) } switch reflect.TypeOf(obj).Kind() { // .IsNil() only works on chan, func, interface, map, pointer, and slice. case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: if reflect.ValueOf(obj).IsNil() { t.Fatal(message) } } } // AssertBoxedNil checks that an inner object is nil. This is intentional for // testing purposes only. func AssertBoxedNil(t *testing.T, obj any, message string) { t.Helper() typ := reflect.TypeOf(obj).Kind() switch typ { // .IsNil() only works on chan, func, interface, map, pointer, and slice. case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: if !reflect.ValueOf(obj).IsNil() { t.Fatal(message) } default: t.Fatalf("Cannot check type \"%s\". Needs to be of type chan, func, interface, map, pointer, or slice.", typ) } } // AssertNotError checks that err is nil func AssertNotError(t *testing.T, err error, message string) { t.Helper() if err != nil { t.Fatalf("%s: %s", message, err) } } // AssertError checks that err is non-nil func AssertError(t *testing.T, err error, message string) { t.Helper() if err == nil { t.Fatalf("%s: expected error but received none", message) } } // AssertErrorWraps checks that err can be unwrapped into the given target. // NOTE: Has the side effect of actually performing that unwrapping. func AssertErrorWraps(t *testing.T, err error, target any) { t.Helper() if !errors.As(err, target) { t.Fatalf("error does not wrap an error of the expected type: %q !> %+T", err.Error(), target) } } // AssertErrorIs checks that err wraps the given error func AssertErrorIs(t *testing.T, err error, target error) { t.Helper() if err == nil { t.Fatal("err was unexpectedly nil and should not have been") } if !errors.Is(err, target) { t.Fatalf("error does not wrap expected error: %q !> %q", err.Error(), target.Error()) } } // AssertEquals uses the equality operator (==) to measure one and two func AssertEquals(t *testing.T, one any, two any) { t.Helper() if reflect.TypeOf(one) != reflect.TypeOf(two) { t.Fatalf("cannot test equality of different types: %T != %T", one, two) } if one != two { t.Fatalf("%#v != %#v", one, two) } } // AssertDeepEquals uses the reflect.DeepEqual method to measure one and two func AssertDeepEquals(t *testing.T, one any, two any) { t.Helper() if !reflect.DeepEqual(one, two) { t.Fatalf("[%#v] !(deep)= [%#v]", one, two) } } // AssertMarshaledEquals marshals one and two to JSON, and then uses // the equality operator to measure them func AssertMarshaledEquals(t *testing.T, one any, two any) { t.Helper() oneJSON, err := json.Marshal(one) AssertNotError(t, err, "Could not marshal 1st argument") twoJSON, err := json.Marshal(two) AssertNotError(t, err, "Could not marshal 2nd argument") if !bytes.Equal(oneJSON, twoJSON) { t.Fatalf("[%s] !(json)= [%s]", oneJSON, twoJSON) } } // AssertUnmarshaledEquals unmarshals two JSON strings (got and expected) to // a map[string]interface{} and then uses reflect.DeepEqual to check they are // the same func AssertUnmarshaledEquals(t *testing.T, got, expected string) { t.Helper() var gotMap, expectedMap map[string]any err := json.Unmarshal([]byte(got), &gotMap) AssertNotError(t, err, "Could not unmarshal 'got'") err = json.Unmarshal([]byte(expected), &expectedMap) AssertNotError(t, err, "Could not unmarshal 'expected'") if len(gotMap) != len(expectedMap) { t.Errorf("Expected %d keys, but got %d", len(expectedMap), len(gotMap)) } for k, v := range expectedMap { if !reflect.DeepEqual(v, gotMap[k]) { t.Errorf("Field %q: Expected \"%v\", got \"%v\"", k, v, gotMap[k]) } } } // AssertNotEquals uses the equality operator to measure that one and two // are different func AssertNotEquals(t *testing.T, one any, two any) { t.Helper() if one == two { t.Fatalf("%#v == %#v", one, two) } } // AssertByteEquals uses bytes.Equal to measure one and two for equality. func AssertByteEquals(t *testing.T, one []byte, two []byte) { t.Helper() if !bytes.Equal(one, two) { t.Fatalf("Byte [%s] != [%s]", base64.StdEncoding.EncodeToString(one), base64.StdEncoding.EncodeToString(two)) } } // AssertContains determines whether needle can be found in haystack func AssertContains(t *testing.T, haystack string, needle string) { t.Helper() if !strings.Contains(haystack, needle) { t.Fatalf("String [%s] does not contain [%s]", haystack, needle) } } // AssertNotContains determines if needle is not found in haystack func AssertNotContains(t *testing.T, haystack string, needle string) { t.Helper() if strings.Contains(haystack, needle) { t.Fatalf("String [%s] contains [%s]", haystack, needle) } } // AssertSliceContains determines if needle can be found in haystack func AssertSliceContains[T comparable](t *testing.T, haystack []T, needle T) { t.Helper() if slices.Contains(haystack, needle) { return } t.Fatalf("Slice %v does not contain %v", haystack, needle) } // AssertMetricWithLabelsEquals determines whether the value held by a prometheus Collector // (e.g. Gauge, Counter, CounterVec, etc) is equal to the expected float64. // In order to make useful assertions about just a subset of labels (e.g. for a // CounterVec with fields "host" and "valid", being able to assert that two // "valid": "true" increments occurred, without caring which host was tagged in // each), takes a set of labels and ignores any metrics which have different // label values. // Only works for simple metrics (Counters and Gauges), or for the *count* // (not value) of data points in a Histogram. func AssertMetricWithLabelsEquals(t *testing.T, c prometheus.Collector, l prometheus.Labels, expected float64) { t.Helper() ch := make(chan prometheus.Metric) done := make(chan struct{}) go func() { c.Collect(ch) close(done) }() var total float64 timeout := time.After(time.Second) loop: for { metric: select { case <-timeout: t.Fatal("timed out collecting metrics") case <-done: break loop case m := <-ch: var iom io_prometheus_client.Metric _ = m.Write(&iom) for _, lp := range iom.Label { // If any of the labels on this metric have the same name as but // different value than a label in `l`, skip this metric. val, ok := l[lp.GetName()] if ok && lp.GetValue() != val { break metric } } // Exactly one of the Counter, Gauge, or Histogram values will be set by // the .Write() operation, so add them all because the others will be 0. total += iom.Counter.GetValue() total += iom.Gauge.GetValue() total += float64(iom.Histogram.GetSampleCount()) } } if total != expected { t.Errorf("metric with labels %+v: got %g, want %g", l, total, expected) } } // AssertHistogramBucketCount is similar to AssertMetricWithLabelsEquals, in // that it determines whether the number of samples within a given histogram // bucket matches the expectation. The bucket to check is indicated by a single // exemplar value; whichever bucket that value falls into is the bucket whose // sample count will be compared to the expected value. func AssertHistogramBucketCount(t *testing.T, c prometheus.Collector, l prometheus.Labels, b float64, expected uint64) { t.Helper() ch := make(chan prometheus.Metric) done := make(chan struct{}) go func() { c.Collect(ch) close(done) }() var total uint64 timeout := time.After(time.Second) loop: for { metric: select { case <-timeout: t.Fatal("timed out collecting metrics") case <-done: break loop case m := <-ch: var iom io_prometheus_client.Metric _ = m.Write(&iom) for _, lp := range iom.Label { // If any of the labels on this metric have the same name as but // different value than a label in `l`, skip this metric. val, ok := l[lp.GetName()] if ok && lp.GetValue() != val { break metric } } lowerBucketsCount := uint64(0) for _, bucket := range iom.Histogram.Bucket { if b <= bucket.GetUpperBound() { total += bucket.GetCumulativeCount() - lowerBucketsCount break } else { lowerBucketsCount += bucket.GetCumulativeCount() } } } } if total != expected { t.Errorf("histogram with labels %+v at bucket %g: got %d, want %d", l, b, total, expected) } }