diff --git a/cli/AGENTS.md b/cli/AGENTS.md index 8522d81..b5a5c23 100644 --- a/cli/AGENTS.md +++ b/cli/AGENTS.md @@ -2,8 +2,7 @@ `kettle` is a globally installed binary. It owns everything that used to be a Python script under `plugins/tea/skills/*/scripts/`: what an issue is, where the -store lives, who this machine is, and (once the transport lands) how issues move -to and from Gitea. +store lives, who this machine is, and how issues move to and from Gitea. The plugin keeps what only a plugin can carry — the rules an operator states and a binary cannot enforce. Everything mechanical is here. @@ -24,15 +23,37 @@ A compiled binary answers all three by construction. There is one walk (`internal/project`), it is imported rather than re-derived, and the layering rule is a build graph a test walks. -## One dependency +## Two dependencies -`gopkg.in/yaml.v3`, vendored, and that is the whole list. Everything else is the -standard library: the transport is plain `net/http` against a documented REST -API, and the CLI has no cobra — commands are values in a registry, which is what +`gopkg.in/yaml.v3` and `code.gitea.io/sdk/gitea`, both vendored, and that is the +whole list — eight modules once the SDK's own are counted, 2.3 MB of `vendor/`. +The CLI still has no cobra: commands are values in a registry, which is what lets the plugin's SKILL.md files be generated from the same struct that holds the code. -`vendor/` is committed, so a build needs no network. +`vendor/` is committed, so a build needs no network. **`go.mod` says `go 1.26`**, +which the SDK requires; that is now the minimum for anybody building this binary. + +The transport was hand-rolled `net/http` against the REST API and is now the +official SDK. What that bought: + +- **the payload shapes are one vocabulary, maintained by the people who maintain + the server.** They used to be ours, in `internal/wire`, and every field Gitea + learned was a field somebody here had to notice. +- **the server's version, for free.** The SDK negotiates it when a client is + built, which is what lets the transport answer "does this instance have issue + dependencies?" from the version instead of guessing from a status code — see + `dependenciesSince`. + +What it cost is written down where it happened: the shapes come with an HTTP +client attached (see the layering note below), a run makes one extra request +before it does anything (the version handshake), and Gitea's issue **edit** +endpoint carries no labels, so an issue whose labels changed needs a second call +— `push` makes it and says so. + +The SDK is imported as `sdk` everywhere, so one type has one spelling across the +tree. `internal/gitea` is the package named `gitea` and the SDK is `sdk` inside +it; the same alias holds in `internal/mapping` and `internal/cmd`. ## Layers @@ -50,10 +71,10 @@ internal/cmd the command tree: flags, receipts, exit codes │ │ request, pagination, payload dumps, │ │ the number -> slug ledger │ ▼ - ├────────────────────► internal/wire PROTOCOL: the JSON shapes and the - │ ▲ identifiers. Imports nothing. - │ │ - └──► internal/mapping ─────┘ BRIDGE: md <-> those shapes, pure, + ├────────────────────► internal/wire ADDRESSES: Repo and Key, and the + │ ▲ parsing that reads them. Imports + │ │ nothing. + └──► internal/mapping ─────┘ BRIDGE: md <-> the SDK's payloads, │ no I/O; label colours live here ▼ internal/issue DOMAIN what an issue is: format, taxonomy, validation, @@ -65,27 +86,49 @@ internal/project ROOT which directory is the project, and every path depends on nothing ``` +Both the transport and the bridge also import `code.gitea.io/sdk/gitea`, which +is where the payloads now live. The arrow that used to point at `internal/wire` +for them points there instead. + Read it bottom-up and each layer knows strictly less about trackers than the one -above it. Four tests hold the line, and each fails on a real mistake rather than -on a naming convention: +above it. Four rules hold the line, seven tests between them, and each fails on +a real mistake rather than on a naming convention: - `internal/issue` may import `internal/project` and the standard library, and nothing else. One test walks `go list -deps` and fails on any path with a dot - in its first element — which is also what keeps yaml out of the domain — and - another names `net/http`, `net`, `os/exec` and `encoding/json`, standard - library the first test would not catch. + in its first element — which is also what keeps yaml AND the SDK out of the + domain — and another names `net/http`, `net`, `os/exec` and `encoding/json`, + standard library the first test would not catch. **Untouched by the migration + to the SDK, and that is the point: the domain did not notice it happened.** - `internal/wire` imports only the standard library, checked the same two ways. - `internal/gitea` must not import `internal/issue` **or** `internal/mapping`: the transport knows numbers, logins, HTTP and JSON, and none of what they mean. -- `internal/mapping` performs no I/O and imports neither the transport nor the - configuration. +- `internal/mapping` reaches for nothing but the domain and the SDK — checked on + its DIRECT imports, with `os`, `net/http` and `internal/gitea` named — and a + second test greps its sources for `time.Now`. -`wire` exists because Go needs the JSON shapes to be one type. The transport and -the bridge were written in parallel and each invented its own `Issue`, `Label`, -`Milestone` and `Comment`; every command on top would then have copied fields -from one struct into the other by hand, which is two vocabularies for one thing — -exactly what this layering exists to prevent. Python did not have the problem -because it passed dicts. +**That fourth rule was stronger before the SDK, and the trade is deliberate.** +The payload shapes lived in `internal/wire`, a package that imported the +standard library and nothing else, so "the bridge cannot reach a transport" was +a fact about the import graph: there was nothing in its dependency closure that +could open a socket. `code.gitea.io/sdk/gitea` is a client and a set of types in +one package, so importing the types imports the client, and a test that walked +the closure would now be asserting something false. What is still true, and what +the test now says, is that **mapping performs no I/O** — no `os`, no `net/http`, +no transport, no configuration, no clock. `time` is allowed where it was not, +because the SDK hands over a `time.Time` and somebody has to format it back into +the string an issue file holds; the clock itself is still the caller's, and the +grep for `time.Now` is what says so. + +`wire` used to exist because Go needs the JSON shapes to be one type — the +transport and the bridge were written in parallel and each invented its own +`Issue`, `Label`, `Milestone` and `Comment`. The SDK settles that argument for +the shapes. **What survives is addressing**, which the SDK has no answer for at +all: it takes an owner, a name and an `int64`, and never parses. `42`, `#42`, +`owner/repo#42` and an issue URL are four spellings of one address, all four are +what somebody has in hand, and `wire.Key` is what the ledger is keyed by and +what the `gitea:` metadata field holds. So `wire` keeps `Repo`, `Key`, their +parsing and their tests, and lost the payloads. If a tracker concept — an issue number, a login, an HTTP call, a label colour — shows up in `internal/issue`, it is in the wrong place. @@ -153,7 +196,14 @@ instead of the tracker, which is why it lives in the domain. `.kettle/payload/` is a sibling, never a child: request bodies are debris of the transport, and a scratchpad inside a store makes `ls .kettle/issues` lie about -what exists. +what exists. It is written by an `http.RoundTripper` installed on the SDK's +client, so **every** request with a body is filed and not only the ones a call +site remembered to name — a name only decides what the file is called. A run +that sends nothing, which includes every read-only command, still leaves no +directory: the first write creates it. The dump is the same JSON the wire +carried, re-indented and with `<`, `>` and `&` left alone, because the SDK +marshals with encoding/json's escaping and a dump nobody can read is a dump +nobody reads. `kettle init` migrates older layouts in, oldest first — `tmp/issues` and then `.tea/issues` — and each is a **move**. A store left behind at an old path is a @@ -178,6 +228,12 @@ so the harness's own value would point every fixture at this repository. Anything touching credentials sets `KETTLE_CONFIG_HOME` at a temp directory, so a test run can neither read nor overwrite the developer's own tokens. +**Every fake tracker answers `/api/v1/version`**, because building a client asks +for it: the SDK will not hand one back until the instance has said what it is, +and that answer is what the dependency gate is decided on later. The fakes say +1.26.1. One says 1.19.4, and that is a test — an instance too old for the issue +dependency endpoints is answered from its version, with no request made. + ## The round trip `push` and `pull` are the two halves of one rule, and the rule is that **the @@ -185,6 +241,10 @@ store holds what has not left this machine.** A successful push deletes `.md` and every sidecar under that slug, on create and on `--update` alike, and prints the number and URL the issue now lives at. +An `--update` can take one extra request with it: Gitea's edit endpoint carries +no labels, so when the answer's label set and the issue's disagree — a label +added or removed locally, or one a create silently dropped — the whole set goes +up in a PUT, and a warning on stderr says which names moved. The deletion happens **only after a confirmed tracker response and only after the number -> slug ledger has been written** — network down, non-2xx, or an answer that does not carry the right number, and the file stays where it is while the @@ -219,13 +279,36 @@ There is also no `--login` and no `--repo` on any sync command bar `labels`. Which login a project runs under, and which repository its issues belong to, are facts about the project, stated once by `kettle init`. A cross-repository address is still an address: `kettle pull owner/repo#42` re-points the client for that -one call. +one call — `Client.For(repo)`, which is bookkeeping and not a second connection, +because the SDK takes the owner and the name per call. The credentials, the +negotiated version and the scratchpad come along. + +## Issue dependencies + +The one endpoint with a story. `depends:` becomes a native Gitea link, which is +what makes the tracker show the blocking panel and refuse to close a blocked +issue first, and it is read back the same way on a pull. + +- **Reading** goes through the SDK (`ListIssueDependencies`). +- **Writing does not.** Gitea's own `IssueMeta` is `{index, owner, repo}` and has + been since the endpoint existed; the SDK's is `{index}`, which can only ever + link inside one repository, and a `depends:` entry is allowed to live + somewhere else. So one hand-rolled POST survives, through the same HTTP client + as everything else — same payload dump, same `*APIError`. +- **The version gates both.** The routes are absent from Gitea 1.19 and present + in 1.20, checked against the release tags themselves, so an instance older + than that is answered from the version it already gave us rather than from a + 404 — which on an old server is also what "no such issue" looks like. +- **A tracker that answers with a status still means "no dependencies here"**, + because an instance that HAS the endpoint can still have them turned off for a + repository, and a pull must bring the issue back either way. A dead connection + is not that answer and never was. ## Status Done and tested: all seven packages, and the commands `init`, `auth`, `config`, `new`, `check`, `ac`, `tree`, `index`, `evict`, `pull`, `push`, `remote`, -`comment`, `close`, `labels`, `sync-evict`. 89 tests. +`comment`, `close`, `labels`, `sync-evict`. 99 tests. Not done: the plugin still ships the Python scripts and the guard hook, and still resolves `.tea/`. Rewiring `plugins/tea` onto this binary — and generating diff --git a/cli/go.mod b/cli/go.mod index 2d28d83..9c6a75b 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,5 +1,17 @@ module git.noodles.cam/claude-skills/marketplace/cli -go 1.24 +go 1.26 -require gopkg.in/yaml.v3 v3.0.1 +require ( + code.gitea.io/sdk/gitea v0.25.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/42wim/httpsig v1.2.4 // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/sys v0.43.0 // indirect +) diff --git a/cli/go.sum b/cli/go.sum index a62c313..573e791 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,3 +1,37 @@ +code.gitea.io/sdk/gitea v0.25.1 h1:yywxWwoV+SdjHtbC6unBiXojWdZOtoHuGhEazEXeWuE= +code.gitea.io/sdk/gitea v0.25.1/go.mod h1:uDFWYBU8dgZsgOHwe6C/6olxvf8FHguNB3wW1i83fgg= +github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU= +github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps= +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/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/cli/internal/cmd/close.go b/cli/internal/cmd/close.go index f502bf9..0e45b58 100644 --- a/cli/internal/cmd/close.go +++ b/cli/internal/cmd/close.go @@ -7,6 +7,8 @@ import ( "strings" "time" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/gitea" "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" "git.noodles.cam/claude-skills/marketplace/cli/internal/mapping" @@ -111,7 +113,14 @@ first, or unlink them.`, if !t.key.Repo.Zero() { c = client.For(t.key.Repo) } - req := wire.IssueRequest{State: wire.Set(state)} + // STATE AND NOTHING ELSE. Every other field of an edit is + // left at its zero value, which the SDK sends as null and + // Gitea reads as "no opinion" — bar the title, which goes + // as the empty string Gitea reads the same way. Building + // this by hand rather than through mapping.ToEdit is the + // point: a translated issue would carry its body. + want := sdk.StateType(state) + req := sdk.EditIssueOption{State: &want} got, err := c.EditIssue(t.key.Number, req, fmt.Sprintf("state-%d", t.key.Number)) if err != nil { return err @@ -120,9 +129,9 @@ first, or unlink them.`, // file is about to say something the tracker had better agree // with. An answer counts only when it is the very issue that was // patched, in the state that was asked for. - if got.Number != t.key.Number || got.State != state { + if int(got.Index) != t.key.Number || got.State != want { return Fail("%s: the %s did not go through — the tracker answered for issue #%d in state %q. Nothing local was changed.", - t.key.In(c.Repo()), verb, got.Number, got.State) + t.key.In(c.Repo()), verb, got.Index, got.State) } fmt.Printf("%-8s %-24s %-20s %s\n", past, closeName(t.id), t.key.In(c.Repo()), got.HTMLURL) @@ -297,14 +306,14 @@ func closeOne(hits []closeEntry, arg, what string) (*closeEntry, error) { // the answer that authorized this write is also the newest thing the tracker has // said about the issue, so `synced:` and `remote-updated:` are stamped from it // rather than left describing an older read. -func closeApply(root string, i *issue.Issue, state string, got *wire.Issue) (string, error) { +func closeApply(root string, i *issue.Issue, state string, got *sdk.Issue) (string, error) { i.State = state if i.Extra == nil { i.Extra = map[string]string{} } i.Extra[mapping.SyncedKey] = time.Now().UTC().Format(time.RFC3339) - if got.UpdatedAt != "" { - i.Extra[mapping.RemoteUpdatedKey] = got.UpdatedAt + if stamp := mapping.Stamp(got.Updated); stamp != "" { + i.Extra[mapping.RemoteUpdatedKey] = stamp } return issue.Save(root, i) } diff --git a/cli/internal/cmd/comment.go b/cli/internal/cmd/comment.go index abcbdf7..91af2b7 100644 --- a/cli/internal/cmd/comment.go +++ b/cli/internal/cmd/comment.go @@ -6,9 +6,10 @@ import ( "os" "strings" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" "git.noodles.cam/claude-skills/marketplace/cli/internal/mapping" - "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) func init() { @@ -81,7 +82,7 @@ comment id for that.`, // issue actually is — even when the store has ever pointed at two. client = client.For(key.Repo) - var got *wire.Comment + var got *sdk.Comment verb := "posted" if *edit != 0 { verb = "edited" diff --git a/cli/internal/cmd/evict_sync.go b/cli/internal/cmd/evict_sync.go index c667e6f..8bb56f1 100644 --- a/cli/internal/cmd/evict_sync.go +++ b/cli/internal/cmd/evict_sync.go @@ -7,6 +7,8 @@ import ( "sort" "strings" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" "git.noodles.cam/claude-skills/marketplace/cli/internal/mapping" "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" @@ -109,7 +111,7 @@ thing this command exists to fix.`, if !ok { return Fail("%s: the answer for %s does not confirm a state "+ "(issue #%d, state %q). Nothing was evicted.", - c.id, c.key, got.Number, got.State) + c.id, c.key, got.Index, got.State) } fresh[c.id] = state } @@ -216,13 +218,13 @@ func syncEvictCandidates(issues map[string]*issue.Issue, ids []string) (checkabl // of a yes here may delete a file. An answer counts only when it is about the // very issue that was asked about and names a state the domain recognizes. A // non-2xx never reaches this: the transport has already returned an error. -func syncEvictConfirms(got *wire.Issue, number int) (string, bool) { - if got == nil || got.Number != number { +func syncEvictConfirms(got *sdk.Issue, number int) (string, bool) { + if got == nil || int(got.Index) != number { return "", false } for _, s := range issue.States { - if got.State == s { - return got.State, true + if string(got.State) == s { + return s, true } } return "", false diff --git a/cli/internal/cmd/labels.go b/cli/internal/cmd/labels.go index 5e40505..8834be8 100644 --- a/cli/internal/cmd/labels.go +++ b/cli/internal/cmd/labels.go @@ -7,6 +7,8 @@ import ( "regexp" "strings" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/mapping" "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) @@ -165,8 +167,8 @@ the store and never a child.`, // taxonomy says it should be, what the repository already has under that exact // name (nil when it has nothing), and where the two disagree. type labelRow struct { - spec wire.LabelRequest - got *wire.Label + spec sdk.CreateLabelOption + got *sdk.Label drift []labelDiff } @@ -187,10 +189,10 @@ type labelLookalike struct { // In taxonomy order, because a bootstrap prints its plan in that order and a map // would shuffle it on every run — two identical runs would look like different // ones. -func labelPlan(specs []wire.LabelRequest, existing []wire.Label) ([]labelRow, []labelLookalike) { - byName := make(map[string]*wire.Label, len(existing)) - for i := range existing { - byName[existing[i].Name] = &existing[i] +func labelPlan(specs []sdk.CreateLabelOption, existing []*sdk.Label) ([]labelRow, []labelLookalike) { + byName := make(map[string]*sdk.Label, len(existing)) + for _, l := range existing { + byName[l.Name] = l } canonical := make(map[string]map[string]bool, len(specs)) @@ -205,8 +207,7 @@ func labelPlan(specs []wire.LabelRequest, existing []wire.Label) ([]labelRow, [] } var similar []labelLookalike - for i := range existing { - l := &existing[i] + for _, l := range existing { if _, exact := canonical[l.Name]; exact { continue } @@ -228,7 +229,7 @@ func labelPlan(specs []wire.LabelRequest, existing []wire.Label) ([]labelRow, [] // // Colour and `exclusive` only. A description somebody rewrote is theirs, and the // name matched exactly or this row would not exist. -func labelDrift(spec wire.LabelRequest, got *wire.Label) []labelDiff { +func labelDrift(spec sdk.CreateLabelOption, got *sdk.Label) []labelDiff { var out []labelDiff if labelHex(got.Color) != labelHex(spec.Color) { out = append(out, labelDiff{"color", labelHex(got.Color), labelHex(spec.Color)}) diff --git a/cli/internal/cmd/pull.go b/cli/internal/cmd/pull.go index 5d443fe..7e5e12f 100644 --- a/cli/internal/cmd/pull.go +++ b/cli/internal/cmd/pull.go @@ -9,6 +9,8 @@ import ( "strings" "time" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/gitea" "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" "git.noodles.cam/claude-skills/marketplace/cli/internal/mapping" @@ -172,7 +174,7 @@ for". The thread is pull-only; post with ` + "`kettle comment`" + `.`, queue, err := pullSeed(client, keys, filtered, gitea.IssueFilter{ State: *state, Labels: labels, Query: query, Milestone: *milestone, Limit: *limit, - Keep: func(p *wire.Issue) bool { + Keep: func(p *sdk.Issue) bool { return pullLandsInStore(p, dropClosed, namer) }, }) @@ -190,13 +192,18 @@ for". The thread is pull-only; post with ` + "`kettle comment`" + `.`, seen := map[int]bool{} for _, t := range queue { - seen[t.payload.Number] = true + seen[int(t.payload.Index)] = true } for len(queue) > 0 { task := queue[0] queue = queue[1:] p := task.payload + // The SDK spells an issue number `Index`, and int64. It is + // an int everywhere on this side of the transport — in the + // ledger, in a key, in `depends:` — so it is narrowed once, + // here, rather than cast at every use. + number := int(p.Index) id, err := namer.idFor(p) if err != nil { return err @@ -208,13 +215,13 @@ for". The thread is pull-only; post with ` + "`kettle comment`" + `.`, // links, and its blockers are not followed. The slug stays // unclaimed too, so no other issue ends up pointing // `depends:` at a file that is not there. - if dropClosed && p.State == "closed" && !stored { - dropped = append(dropped, p.Number) + if dropClosed && p.State == sdk.StateClosed && !stored { + dropped = append(dropped, number) continue } namer.taken[id] = true - numberOf[p.Number] = id + numberOf[number] = id // The native links, fetched ONCE for the two things they are // for: filling this issue's `depends:` and telling the walk @@ -222,7 +229,7 @@ for". The thread is pull-only; post with ` + "`kettle comment`" + `.`, // store, and only one. var blockers []int if !*noDeps { - if blockers, err = pullBlockers(client, p.Number, repo); err != nil { + if blockers, err = pullBlockers(client, number, repo); err != nil { return err } } @@ -248,10 +255,10 @@ for". The thread is pull-only; post with ` + "`kettle comment`" + `.`, if _, err := issue.Save(root, next); err != nil { return err } - if _, err := pullSyncComments(client, root, id, p.Number, p.Comments); err != nil { + if _, err := pullSyncComments(client, root, id, number, p.Comments); err != nil { return err } - ledger.Set(wire.Key{Repo: repo, Number: p.Number}, id) + ledger.Set(wire.Key{Repo: repo, Number: number}, id) written = append(written, id) pending = append(pending, unresolved{id, missing}) } @@ -320,7 +327,7 @@ for". The thread is pull-only; post with ` + "`kettle comment`" + `.`, // pullTask is one issue to walk, and how far from a seed it was found. type pullTask struct { - payload *wire.Issue + payload *sdk.Issue depth int } @@ -387,8 +394,8 @@ func pullSeed(c *gitea.Client, keys []wire.Key, filtered bool, f gitea.IssueFilt len(listing.Issues), strings.Join(what, " + "), f.State) out := make([]pullTask, 0, len(listing.Issues)) - for i := range listing.Issues { - out = append(out, pullTask{payload: &listing.Issues[i]}) + for _, p := range listing.Issues { + out = append(out, pullTask{payload: p}) } return out, nil } @@ -401,8 +408,8 @@ func pullSeed(c *gitea.Client, keys []wire.Key, filtered bool, f gitea.IssueFilt // only when the store already has it (it is refreshed, and that is a write); // anything else counts, including one --cached will skip, because a skipped // issue is still an issue the store holds when the run ends. -func pullLandsInStore(p *wire.Issue, dropClosed bool, namer *pullNamer) bool { - if !dropClosed || p.State != "closed" { +func pullLandsInStore(p *sdk.Issue, dropClosed bool, namer *pullNamer) bool { + if !dropClosed || p.State != sdk.StateClosed { return true } id, err := namer.idFor(p) @@ -423,13 +430,13 @@ func pullLandsInStore(p *wire.Issue, dropClosed bool, namer *pullNamer) bool { // either resolve to the wrong issue or invent an edge. The body still names it, // so nothing is lost. func pullBlockers(c *gitea.Client, number int, repo wire.Repo) ([]int, error) { - deps, err := c.Dependencies(number) + keys, err := c.DependencyKeys(number) if err != nil { return nil, err } var out []int - for i := range deps { - if k := deps[i].KeyIn(repo); k.Repo == repo { + for _, k := range keys { + if k.Repo == repo { out = append(out, k.Number) } } @@ -444,7 +451,7 @@ func pullBlockers(c *gitea.Client, number int, repo wire.Repo) ([]int, error) { // absence of the file is the answer, not a gap in what was asked for. func pullSyncComments(c *gitea.Client, root, id string, number, count int) (string, error) { path := commentsSidecarPath(root, id) - var thread []wire.Comment + var thread []*sdk.Comment if count > 0 { var err error if thread, err = c.ListComments(number); err != nil { @@ -530,8 +537,8 @@ type pullNamer struct { // idFor is the slug this remote issue belongs under. Three sources, in order — // see the command's own documentation for why that order and not another. -func (n *pullNamer) idFor(p *wire.Issue) (string, error) { - if got := n.ledger.Slug(wire.Key{Repo: n.repo, Number: p.Number}); got != "" { +func (n *pullNamer) idFor(p *sdk.Issue) (string, error) { + if got := n.ledger.Slug(wire.Key{Repo: n.repo, Number: int(p.Index)}); got != "" { return got, nil } marked := mapping.IDInBody(p.Body) diff --git a/cli/internal/cmd/push.go b/cli/internal/cmd/push.go index dcdf385..4ca635a 100644 --- a/cli/internal/cmd/push.go +++ b/cli/internal/cmd/push.go @@ -9,6 +9,8 @@ import ( "strings" "time" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/gitea" "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" "git.noodles.cam/claude-skills/marketplace/cli/internal/mapping" @@ -241,7 +243,7 @@ and a warning says so.`, return err } if m != nil { - msID = wire.Set(m.ID) + msID = sdk.OptionalInt64(m.ID) } milestones[i.Milestone] = msID } @@ -252,17 +254,17 @@ and a warning says so.`, opt := mapping.RequestOptions{LabelIDs: labelIDs, MilestoneID: msID} sent, synced := mapping.NumberOf(i) - var got *wire.Issue + var got *sdk.Issue verb := "created" if synced { // An edit says what state it means; a create takes the // tracker's default. opt.IncludeState = true verb = "updated" - got, err = client.EditIssue(sent, *mapping.ToRequest(i, opt), "issue-"+id) + got, err = client.EditIssue(sent, mapping.ToEdit(i, opt), "issue-"+id) } else { sent = 0 - got, err = client.CreateIssue(*mapping.ToRequest(i, opt), "issue-"+id) + got, err = client.CreateIssue(mapping.ToCreate(i, opt), "issue-"+id) } // THE GATE. Below this line a local file is going to be // deleted, so anything short of a confirmed write stops the @@ -274,7 +276,7 @@ and a warning says so.`, if !confirmed { return Fail("%s: the tracker's answer does not confirm the write "+ "(it carries number %d) — %s is untouched, nothing was deleted", - id, got.Number, issue.PathOf(root, id)) + id, got.Index, issue.PathOf(root, id)) } // The number is confirmed, so the ledger learns it NOW — @@ -290,35 +292,30 @@ and a warning says so.`, "written (%v) — %s is untouched", id, number, err, issue.PathOf(root, id)) } - // Gitea occasionally drops labels handed to it on create, so - // the echo is checked and the set re-applied rather than - // trusted. A failure here is a warning and not an abort: the - // issue IS in the tracker, and a run that stopped now would - // leave a file whose `gitea:` field was never written — which - // the next push would file all over again as a new issue. - applied := map[string]bool{} - for _, l := range got.Labels { - applied[l.Name] = true - } - var ids []int64 - var missing []string - for _, name := range i.Labels { - lid, in := labelIDs[name] - if !in { - continue - } - ids = append(ids, lid) - if !applied[name] { - missing = append(missing, name) - } - } - if len(missing) > 0 { - if _, err := client.SetLabels(number, ids, "labels-"+id); err != nil { - fmt.Fprintf(os.Stderr, "warning: %s: could not re-apply labels (%s): %v\n", - id, strings.Join(missing, ", "), err) + // THE LABELS THE TRACKER ENDED UP WITH HAVE TO BE THE + // ISSUE'S, and two different things leave them disagreeing: + // + // - a create can DROP labels handed to it, which Gitea has + // been seen to do, so the echo is checked rather than + // trusted; + // - an edit cannot carry labels AT ALL — Gitea's PATCH + // takes none — so a label added or removed locally is + // not in the answer either way. + // + // One answer to both: compare the set the tracker echoed + // with the set this issue wants, and PUT the whole list when + // they differ. A failure here is a warning and not an abort: + // the issue IS in the tracker, and a run that stopped now + // would leave a file whose `gitea:` field was never written + // — which the next push would file all over again as a new + // issue. + if drift := pushLabelDrift(got.Labels, i.Labels, labelIDs); len(drift) > 0 { + if _, err := client.SetLabels(number, mapping.LabelIDsFor(i, opt), "labels-"+id); err != nil { + fmt.Fprintf(os.Stderr, "warning: %s: could not set the labels (%s): %v\n", + id, strings.Join(drift, ", "), err) } else { - fmt.Fprintf(os.Stderr, "warning: %s: labels re-applied via PUT (%s)\n", - id, strings.Join(missing, ", ")) + fmt.Fprintf(os.Stderr, "%s: labels set via PUT (%s)\n", + id, strings.Join(drift, ", ")) } } @@ -454,6 +451,44 @@ func pushLabelIDs(c *gitea.Client, names []string) (map[string]int64, error) { return out, nil } +// pushLabelDrift is the names on which the tracker's answer and the issue +// disagree — what a receipt says when the PUT that follows is made. +// +// BOTH DIRECTIONS. A wanted label the answer does not carry is one Gitea +// dropped or one an edit could not send; a label the answer carries that the +// issue no longer wants is one somebody removed locally, and leaving it would +// make `push --update` a command that can add a label but never take one off. +// +// Only labels this run resolved to an id count as wanted: an unknown name was +// already left off deliberately, and one the repository holds but the taxonomy +// does not know is not this command's to remove either — which is why the +// comparison is against the resolved set and not against `labels:` as written. +func pushLabelDrift(got []*sdk.Label, want []string, ids map[string]int64) []string { + have := map[string]bool{} + for _, l := range got { + if l != nil { + have[l.Name] = true + } + } + wanted := map[string]bool{} + var drift []string + for _, name := range want { + if _, resolved := ids[name]; !resolved { + continue + } + wanted[name] = true + if !have[name] { + drift = append(drift, name) + } + } + for _, l := range got { + if l != nil && !wanted[l.Name] { + drift = append(drift, "-"+l.Name) + } + } + return drift +} + // pushDep is what one `depends:` entry is, as far as linking is concerned. type pushDep struct { // Slug is the dependency as `depends:` spells it. @@ -615,14 +650,15 @@ func pushLedgerKeys(m gitea.RemoteMap, repo wire.Repo) map[string]wire.Key { // because none of it gets this far: a non-2xx answer, a body that is not the JSON // expected, or a `number` that is not a number — the transport fails all three // before returning, and the file survives by never reaching the delete. -func pushConfirmedNumber(got *wire.Issue, sent int) (int, bool) { - if got == nil || got.Number <= 0 { +func pushConfirmedNumber(got *sdk.Issue, sent int) (int, bool) { + if got == nil || got.Index <= 0 { return 0, false } - if sent != 0 && got.Number != sent { + number := int(got.Index) + if sent != 0 && number != sent { return 0, false } - return got.Number, true + return number, true } // pushGitBranch is the branch HEAD is on, or "". diff --git a/cli/internal/cmd/remote.go b/cli/internal/cmd/remote.go index 8d9b777..2bfd38f 100644 --- a/cli/internal/cmd/remote.go +++ b/cli/internal/cmd/remote.go @@ -6,6 +6,7 @@ import ( "strings" "git.noodles.cam/claude-skills/marketplace/cli/internal/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/mapping" "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) @@ -80,10 +81,9 @@ milestones or labels, or the web UI.`, ledger := gitea.LoadRemoteMap(root) repo := client.Repo() - for i := range listing.Issues { - p := &listing.Issues[i] + for _, p := range listing.Issues { labels := "-" - if names := p.LabelNames(); len(names) > 0 { + if names := mapping.LabelNames(p); len(names) > 0 { labels = strings.Join(names, ", ") } // One line per issue is the whole point; a repository that @@ -91,8 +91,8 @@ milestones or labels, or the web UI.`, if len(labels) > labelColumn { labels = labels[:labelColumn] } - fmt.Printf("#%-5d %-7s %-38s %s\n", p.Number, p.State, labels, p.Title) - if local := ledger.Slug(wire.Key{Repo: repo, Number: p.Number}); local != "" { + fmt.Printf("#%-5d %-7s %-38s %s\n", p.Index, p.State, labels, p.Title) + if local := ledger.Slug(wire.Key{Repo: repo, Number: int(p.Index)}); local != "" { fmt.Printf("%13s└─ local: %s\n", "", local) } } diff --git a/cli/internal/cmd/sync_pull_test.go b/cli/internal/cmd/sync_pull_test.go index ef76ef9..879b2c3 100644 --- a/cli/internal/cmd/sync_pull_test.go +++ b/cli/internal/cmd/sync_pull_test.go @@ -13,10 +13,17 @@ package cmd_test // KETTLE_URL / KETTLE_TOKEN / KETTLE_REPO, which is also what a CI run does. // KETTLE_CONFIG_HOME points at a temp directory so no fixture can read or // overwrite the developer's own tokens. +// +// The fake answers /api/v1/version before anything else: the SDK asks an +// instance what it is before it hands back a client, so a fake that did not +// answer would fail every command at startup — and it is that answer the +// dependency gate is decided on, which is why it says a version new enough to +// have them. import ( "encoding/json" "fmt" + "io" "net/http" "net/http/httptest" "os" @@ -27,11 +34,27 @@ import ( "strings" "sync" "testing" + "time" + + sdk "code.gitea.io/sdk/gitea" "git.noodles.cam/claude-skills/marketplace/cli/internal/config" - "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) +// pullGiteaVersion is what both fakes in this package claim to be: new enough +// for the issue-dependency endpoints, which is what the transport gates on. +const pullGiteaVersion = "1.26.1" + +// pullVersionRoute answers the version handshake and reports whether it did. +func pullVersionRoute(w http.ResponseWriter, r *http.Request) bool { + if r.URL.Path != "/api/v1/version" { + return false + } + w.Header().Set("Content-Type", "application/json") + _, _ = io.WriteString(w, `{"version":"`+pullGiteaVersion+`"}`) + return true +} + // -------------------------------------------------------------------------- // the fake tracker // -------------------------------------------------------------------------- @@ -39,9 +62,9 @@ import ( // pullFakeGitea is a Gitea instance with one repository in it, owner/repo. type pullFakeGitea struct { mu sync.Mutex - issues map[int]*wire.Issue + issues map[int]*sdk.Issue deps map[int][]int - comments map[int][]wire.Comment + comments map[int][]sdk.Comment labels map[string]int64 next int @@ -52,35 +75,35 @@ type pullFakeGitea struct { func pullNewGitea() *pullFakeGitea { return &pullFakeGitea{ - issues: map[int]*wire.Issue{}, + issues: map[int]*sdk.Issue{}, deps: map[int][]int{}, - comments: map[int][]wire.Comment{}, + comments: map[int][]sdk.Comment{}, labels: map[string]int64{}, } } // pullAdd puts an issue in the tracker the way the web UI would: it is there // before this project ever hears about it. -func (g *pullFakeGitea) pullAdd(p wire.Issue) { +func (g *pullFakeGitea) pullAdd(p sdk.Issue) { g.mu.Lock() defer g.mu.Unlock() if p.State == "" { - p.State = "open" + p.State = sdk.StateOpen } - p.HTMLURL = pullURL(p.Number) - g.issues[p.Number] = &p - if p.Number > g.next { - g.next = p.Number + p.HTMLURL = pullURL(int(p.Index)) + g.issues[int(p.Index)] = &p + if int(p.Index) > g.next { + g.next = int(p.Index) } } -func (g *pullFakeGitea) pullIssue(n int) wire.Issue { +func (g *pullFakeGitea) pullIssue(n int) sdk.Issue { g.mu.Lock() defer g.mu.Unlock() if p := g.issues[n]; p != nil { return *p } - return wire.Issue{} + return sdk.Issue{} } func (g *pullFakeGitea) pullRetitle(n int, title string) { @@ -108,6 +131,9 @@ func (g *pullFakeGitea) ServeHTTP(w http.ResponseWriter, r *http.Request) { g.mu.Lock() defer g.mu.Unlock() + if pullVersionRoute(w, r) { + return + } path, ok := strings.CutPrefix(r.URL.Path, "/api/v1/repos/owner/repo/") if !ok { http.Error(w, `{"message":"no such repository"}`, http.StatusNotFound) @@ -116,36 +142,36 @@ func (g *pullFakeGitea) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch { case path == "labels" && r.Method == http.MethodGet: - out := []wire.Label{} + out := []sdk.Label{} for name, id := range g.labels { - out = append(out, wire.Label{ID: id, Name: name}) + out = append(out, sdk.Label{ID: id, Name: name}) } sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) pullJSON(w, out) case path == "labels" && r.Method == http.MethodPost: - var req wire.LabelRequest + var req sdk.CreateLabelOption pullDecode(r, &req) id := int64(1000 + len(g.labels)) g.labels[req.Name] = id - pullJSON(w, wire.Label{ID: id, Name: req.Name, Color: req.Color, Exclusive: req.Exclusive}) + pullJSON(w, sdk.Label{ID: id, Name: req.Name, Color: req.Color, Exclusive: req.Exclusive}) case path == "milestones" && r.Method == http.MethodGet: - pullJSON(w, []wire.Milestone{}) + pullJSON(w, []sdk.Milestone{}) case path == "issues" && r.Method == http.MethodPost: if g.writesFail { http.Error(w, `{"message":"the tracker is having a bad day"}`, http.StatusInternalServerError) return } - var req wire.IssueRequest + var req sdk.CreateIssueOption pullDecode(r, &req) g.next++ - p := &wire.Issue{ - Number: g.next, Title: pullStr(req.Title), Body: pullStr(req.Body), - State: "open", HTMLURL: pullURL(g.next), Labels: g.pullLabelsFor(req.Labels), + p := &sdk.Issue{ + Index: int64(g.next), Title: req.Title, Body: req.Body, + State: sdk.StateOpen, HTMLURL: pullURL(g.next), Labels: g.pullLabelsFor(req.Labels), } - g.issues[p.Number] = p + g.issues[g.next] = p pullJSON(w, p) case path == "issues" && r.Method == http.MethodGet: @@ -163,10 +189,12 @@ func (g *pullFakeGitea) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, `{"message":"the tracker is having a bad day"}`, http.StatusInternalServerError) return } - var req wire.IssueRequest + var req sdk.EditIssueOption pullDecode(r, &req) - if req.Title != nil { - p.Title = *req.Title + // An empty title is Gitea's "leave it alone" — the one field of an + // edit that says so with a zero value rather than with null. + if req.Title != "" { + p.Title = req.Title } if req.Body != nil { p.Body = *req.Body @@ -174,9 +202,10 @@ func (g *pullFakeGitea) ServeHTTP(w http.ResponseWriter, r *http.Request) { if req.State != nil { p.State = *req.State } - if req.Labels != nil { - p.Labels = g.pullLabelsFor(req.Labels) - } + // No labels here on purpose: Gitea's edit endpoint takes none, so + // an issue whose labels changed gets them through PUT ./labels + // below, and a fake that quietly accepted them would hide a push + // that never sent them. } pullJSON(w, p) @@ -185,7 +214,7 @@ func (g *pullFakeGitea) ServeHTTP(w http.ResponseWriter, r *http.Request) { n, _ := strconv.Atoi(m[1]) switch { case m[2] == "dependencies" && r.Method == http.MethodGet: - out := []wire.Issue{} + out := []sdk.Issue{} for _, d := range g.deps[n] { if p := g.issues[d]; p != nil { out = append(out, *p) @@ -202,15 +231,13 @@ func (g *pullFakeGitea) ServeHTTP(w http.ResponseWriter, r *http.Request) { case m[2] == "comments" && r.Method == http.MethodGet: out := g.comments[n] if out == nil { - out = []wire.Comment{} + out = []sdk.Comment{} } pullJSON(w, out) case m[2] == "labels" && r.Method == http.MethodPut: - var req struct { - Labels []int64 `json:"labels"` - } + var req sdk.IssueLabelsOption pullDecode(r, &req) - g.issues[n].Labels = g.pullLabelsFor(&req.Labels) + g.issues[n].Labels = g.pullLabelsFor(req.Labels) pullJSON(w, g.issues[n].Labels) default: http.Error(w, `{"message":"not implemented"}`, http.StatusNotFound) @@ -242,10 +269,10 @@ func (g *pullFakeGitea) pullList(w http.ResponseWriter, r *http.Request) { } sort.Ints(numbers) - out := []wire.Issue{} + out := []sdk.Issue{} for _, n := range numbers { p := g.issues[n] - if state != "" && state != "all" && p.State != state { + if state != "" && state != "all" && string(p.State) != state { continue } has := map[string]bool{} @@ -273,7 +300,7 @@ func (g *pullFakeGitea) pullList(w http.ResponseWriter, r *http.Request) { pullJSON(w, out[start:end]) } -func (g *pullFakeGitea) pullLabelsFor(ids *[]int64) []wire.Label { +func (g *pullFakeGitea) pullLabelsFor(ids []int64) []*sdk.Label { if ids == nil { return nil } @@ -281,10 +308,10 @@ func (g *pullFakeGitea) pullLabelsFor(ids *[]int64) []wire.Label { for name, id := range g.labels { byID[id] = name } - var out []wire.Label - for _, id := range *ids { + var out []*sdk.Label + for _, id := range ids { if name, ok := byID[id]; ok { - out = append(out, wire.Label{ID: id, Name: name}) + out = append(out, &sdk.Label{ID: id, Name: name}) } } return out @@ -304,13 +331,6 @@ func pullJSON(w http.ResponseWriter, v any) { _ = json.NewEncoder(w).Encode(v) } -func pullStr(p *string) string { - if p == nil { - return "" - } - return *p -} - // pullEnv starts the fake and returns the environment that points the binary at // it. The credential home is a temp directory: a test run may neither read nor // overwrite the developer's own tokens. @@ -430,9 +450,9 @@ func TestPushLeavesTheFileWhenTheTrackerRefuses(t *testing.T) { func TestPullByNumberFetchesAClosedIssue(t *testing.T) { dir := newProject(t) g := pullNewGitea() - g.pullAdd(wire.Issue{ - Number: 7, Title: "Closed but addressable", State: "closed", - Body: "## Summary\nДело сделано.\n", UpdatedAt: "2026-08-01T10:00:00Z", + g.pullAdd(sdk.Issue{ + Index: 7, Title: "Closed but addressable", State: sdk.StateClosed, + Body: "## Summary\nДело сделано.\n", Updated: time.Date(2026, 8, 1, 10, 0, 0, 0, time.UTC), }) env := pullEnv(t, g) @@ -456,8 +476,8 @@ func TestPullByNumberFetchesAClosedIssue(t *testing.T) { // --no-deps is how you ask for one row of it. func TestPullBringsTheBlockerDownWithIt(t *testing.T) { g := pullNewGitea() - g.pullAdd(wire.Issue{Number: 1, Title: "Migrate the schema", Body: "## Summary\nx\n"}) - g.pullAdd(wire.Issue{Number: 2, Title: "Wire sqlc into the layer", Body: "## Summary\nx\n"}) + g.pullAdd(sdk.Issue{Index: 1, Title: "Migrate the schema", Body: "## Summary\nx\n"}) + g.pullAdd(sdk.Issue{Index: 2, Title: "Wire sqlc into the layer", Body: "## Summary\nx\n"}) g.pullBlocks(2, 1) env := pullEnv(t, g) @@ -549,9 +569,9 @@ func TestAPushedIssueComesBackUnderItsOriginalSlug(t *testing.T) { // it. func TestPullFilterModeLeavesClosedIssuesOut(t *testing.T) { g := pullNewGitea() - bug := []wire.Label{{ID: 1, Name: "type/bug"}} - g.pullAdd(wire.Issue{Number: 1, Title: "Still broken", Body: "## Summary\nx\n", Labels: bug}) - g.pullAdd(wire.Issue{Number: 2, Title: "Fixed last week", State: "closed", + bug := []*sdk.Label{{ID: 1, Name: "type/bug"}} + g.pullAdd(sdk.Issue{Index: 1, Title: "Still broken", Body: "## Summary\nx\n", Labels: bug}) + g.pullAdd(sdk.Issue{Index: 2, Title: "Fixed last week", State: sdk.StateClosed, Body: "## Summary\nx\n", Labels: bug}) env := pullEnv(t, g) @@ -581,10 +601,10 @@ func TestPullFilterModeLeavesClosedIssuesOut(t *testing.T) { func TestPushUpdateDropsTheLocalCopyAsWell(t *testing.T) { dir := newProject(t) g := pullNewGitea() - g.pullAdd(wire.Issue{ - Number: 3, Title: "Came down and went back up", + g.pullAdd(sdk.Issue{ + Index: 3, Title: "Came down and went back up", Body: "## Summary\nx\n\n## Spec\nnone\n\n## Acceptance criteria\n- [ ] x\n", - Labels: []wire.Label{{ID: 1, Name: "type/task"}}, + Labels: []*sdk.Label{{ID: 1, Name: "type/task"}}, }) env := pullEnv(t, g) @@ -637,8 +657,8 @@ func TestPushDryRunNeedsNoCredential(t *testing.T) { func TestRemoteListsWithoutWritingAnything(t *testing.T) { dir := newProject(t) g := pullNewGitea() - g.pullAdd(wire.Issue{Number: 4, Title: "Something open", Body: "x"}) - g.pullAdd(wire.Issue{Number: 5, Title: "Something closed", State: "closed", Body: "x"}) + g.pullAdd(sdk.Issue{Index: 4, Title: "Something open", Body: "x"}) + g.pullAdd(sdk.Issue{Index: 5, Title: "Something closed", State: sdk.StateClosed, Body: "x"}) env := pullEnv(t, g) r := runWith(t, dir, env, "", "remote") diff --git a/cli/internal/cmd/sync_write_test.go b/cli/internal/cmd/sync_write_test.go index a7e5b67..dc237ac 100644 --- a/cli/internal/cmd/sync_write_test.go +++ b/cli/internal/cmd/sync_write_test.go @@ -97,6 +97,13 @@ func wrNewTracker() *wrTracker { func (tr *wrTracker) ServeHTTP(w http.ResponseWriter, r *http.Request) { tr.mu.Lock() defer tr.mu.Unlock() + + // The version handshake, answered before the log: it is not a call any + // command made, and counting it would make every "how many requests did + // that send" assertion in this file one out. + if pullVersionRoute(w, r) { + return + } tr.calls = append(tr.calls, r.Method+" "+r.URL.Path) // The scheme Gitea uses and the client sends: the word `token`. @@ -150,13 +157,21 @@ func (tr *wrTracker) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodPatch { var req struct { State *string `json:"state"` - Title *string `json:"title"` + Title string `json:"title"` + Body *string `json:"body"` } wrDecode(r, &req) - // A close is state and nothing else; a title arriving here would be - // the command editing an issue it was only asked to close. - if req.Title != nil { - http.Error(w, `{"message":"close sent a title"}`, http.StatusUnprocessableEntity) + // A close is state and nothing else; a title or a body arriving + // here would be the command editing an issue it was only asked to + // close. + // + // A title of "" is not one. The SDK's edit body has no pointer + // there and sends the key whatever happens, so an empty string is + // how "no opinion" is spelled for this one field — which is also + // how Gitea itself reads it, and Gitea is what this stands in for. + // Everything else is a pointer and must arrive as null. + if req.Title != "" || req.Body != nil { + http.Error(w, `{"message":"close sent more than a state"}`, http.StatusUnprocessableEntity) return } if req.State != nil { diff --git a/cli/internal/gitea/client.go b/cli/internal/gitea/client.go index d6ff2a5..27af8e9 100644 --- a/cli/internal/gitea/client.go +++ b/cli/internal/gitea/client.go @@ -10,17 +10,25 @@ // that layer is not imported here either: it sits above this package, not // beside it. // -// The JSON shapes and the issue keys are internal/wire's. They are not this -// package's to own, because the bridge needs exactly the same vocabulary and -// cannot import a transport to get it; a copy on each side is two structs that -// drift and a command that copies fields between them by hand. +// The payload shapes are code.gitea.io/sdk/gitea's, aliased `sdk` everywhere it +// is imported so that one type has one spelling across the tree. They are not +// this package's to own and never were: the bridge needs exactly the same +// vocabulary and cannot import a transport to get it, and a copy on each side +// is two structs that drift and a command that copies fields between them by +// hand. The issue KEYS are still internal/wire's — the SDK addresses an issue +// as (owner, repo, int64) and never parses `owner/repo#42` out of anything. // -// Every request goes through Call. One place sets the header, one place reads -// a status code, one place files the request body. When this was a Python -// module shelling out to `tea api`, "why did that fail" meant reading a -// subprocess's stderr and guessing; here a failure is an *APIError carrying the -// status AND the body the server actually sent, because "500" on its own has -// never helped anybody. +// WHAT THIS PACKAGE IS, NOW THAT THE SDK EXISTS: the one place that holds the +// credentials, the scratchpad and the repository this project points at, so +// that no command has to. Every method here is a thin wrapper, and the three +// things the wrapping is for are the three things the SDK does not do: +// +// - every request body is filed under `.kettle/payload/` by a RoundTripper, +// so a retry or a post-mortem has the bytes that went out; +// - every failure comes back as an *APIError carrying the status AND what the +// server said, because "500" on its own has never helped anybody; +// - a listing stops when the caller has what it asked for, which a client +// that fetches whole pages into a slice cannot do. package gitea import ( @@ -30,21 +38,21 @@ import ( "fmt" "io" "net/http" - "net/url" "os" "path/filepath" "strings" + "sync" "time" "unicode/utf8" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/config" "git.noodles.cam/claude-skills/marketplace/cli/internal/project" "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) const ( - // apiPrefix is where every Gitea instance puts its REST API. - apiPrefix = "/api/v1" // userAgent names this binary in the server's log. A tracker admin looking // at a burst of requests should be able to tell what made them. userAgent = "kettle" @@ -59,25 +67,28 @@ const ( // Client talks to one repository on one Gitea instance. type Client struct { - // HTTP is the transport, exported so a caller can change the timeout or - // hand in an instrumented one. Never nil after New. - HTTP *http.Client + // api is the SDK client: one per run, shared by every copy For makes. + api *sdk.Client + // http is the SDK's transport, kept because AddDependency still sends one + // request by hand — see there. + http *http.Client + // dump is the RoundTripper that files request bodies. Shared with every + // copy For makes, because the scratchpad is one directory per run. + dump *dumper - base string // instance URL with the API prefix, no trailing slash + base string // instance URL, no API prefix and no trailing slash token string repo wire.Repo - - // payloadRoot is resolved once, by New, and is never taken from a caller. - // The one time where a request body lands was an argument, it got pointed - // at the issue store — see writePayload. - payloadRoot string } // New builds a client for the repository this project points at. // // It refuses a half-filled configuration instead of letting the first call come // back 401 or 404: those answers name nothing an operator can act on, and every -// field missing here has exactly one command that supplies it. +// field missing here has exactly one command that supplies it. That check comes +// first because building the client now DIALS — the SDK asks the instance for +// its version before it hands one back — and a missing token reported as a +// connection failure sends the operator to the wrong place. func New(cfg *config.Resolved) (*Client, error) { if cfg == nil { return nil, errors.New("no resolved configuration — call config.Require first") @@ -95,98 +106,61 @@ func New(cfg *config.Resolved) (*Client, error) { if len(missing) > 0 { return nil, fmt.Errorf("this project has no %s", strings.Join(missing, ", and no ")) } + + base := strings.TrimRight(cfg.URL, "/") + dump := &dumper{next: http.DefaultTransport, root: project.PayloadRoot("")} + hc := &http.Client{Timeout: requestTimeout, Transport: dump} + + api, err := sdk.NewClient(base, + sdk.SetToken(cfg.Token), + sdk.SetHTTPClient(hc), + sdk.SetUserAgent(userAgent)) + if err != nil { + // A version string the SDK cannot parse is not a cosmetic failure, and + // it is refused here rather than shrugged off: the SDK hands back a + // usable-looking client that has quietly decided the server is Gitea + // 1.11, and its 1.11 compatibility path rewrites an issue's URL from a + // `repository` field a modern payload need not carry — a nil + // dereference on the first issue read. Saying so at the handshake beats + // crashing three calls later. + if errors.Is(err, &sdk.ErrUnknownVersion{}) { + return nil, fmt.Errorf("%s did not answer with a version this can read (%w)"+ + " — check that %s points at a Gitea instance", base, err, config.EnvURL) + } + return nil, fmt.Errorf("cannot reach the Gitea instance at %s: %w", base, err) + } return &Client{ - HTTP: &http.Client{Timeout: requestTimeout}, - base: strings.TrimRight(cfg.URL, "/") + apiPrefix, - token: cfg.Token, - repo: wire.Repo{Owner: cfg.Owner, Name: cfg.Repo}, - payloadRoot: project.PayloadRoot(""), + api: api, + http: hc, + dump: dump, + base: base, + token: cfg.Token, + repo: wire.Repo{Owner: cfg.Owner, Name: cfg.Repo}, }, nil } -// Repo is the repository every path is built against. +// Repo is the repository every call is made against. func (c *Client) Repo() wire.Repo { return c.repo } // For returns a copy of this client pointed at another repository, for the run -// that was given an explicit owner/name. The credentials and the scratchpad -// come along; only the paths change. +// that was given an explicit owner/name. +// +// Bookkeeping and not a second connection: the SDK takes the owner and the name +// per call, so what changes is which pair this client passes. The credentials, +// the negotiated server version and the scratchpad are all shared, which is +// what makes `kettle pull owner/repo#42` cost nothing extra. func (c *Client) For(r wire.Repo) *Client { out := *c out.repo = r return &out } -// Body is a request payload and the name its dump is filed under. -// -// The name is the caller's label for this call, not a path: it becomes -// `.json` in the scratchpad, and something that identifies the call in a -// post-mortem — an issue's slug, a label's name — is worth more there than a -// serial number. -type Body struct { - Name string - Data any -} +// owned is the owner and name every SDK call takes, escaped by the SDK itself. +func (c *Client) owned() (string, string) { return c.repo.Owner, c.repo.Name } -// Call makes one request and decodes the answer into out, which may be nil when -// there is nothing to read. -// -// body may be nil. When it is not, its Data is marshalled once: the bytes filed -// in the scratchpad and the bytes on the wire are the same bytes, so a retry -// from the file sends what this call sent. -// -// An empty response body leaves out untouched — a 204 from a PATCH is a -// success, not a decode failure. -func (c *Client) Call(method, path string, body *Body, out any) error { - var payload []byte - if body != nil { - var err error - if payload, err = c.writePayload(body); err != nil { - return err - } - } - - endpoint := c.base + "/" + strings.TrimLeft(path, "/") - var reader io.Reader - if payload != nil { - reader = bytes.NewReader(payload) - } - req, err := http.NewRequest(method, endpoint, reader) - if err != nil { - return fmt.Errorf("%s %s: %w", method, endpoint, err) - } - // Gitea's own scheme, and what the `tea` CLI this replaces sent: the word - // `token`, not `Bearer`. An instance answers 401 to the other spelling. - req.Header.Set("Authorization", "token "+c.token) - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", userAgent) - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - resp, err := c.HTTP.Do(req) - if err != nil { - // The token travels in a header and never in the URL, so an error is - // free to quote the URL in full. - return fmt.Errorf("%s %s: %w", method, endpoint, err) - } - defer resp.Body.Close() - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("%s %s: reading the response: %w", method, endpoint, err) - } - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return &APIError{Method: method, URL: endpoint, Status: resp.StatusCode, Body: string(raw)} - } - if out == nil || len(bytes.TrimSpace(raw)) == 0 { - return nil - } - if err := json.Unmarshal(raw, out); err != nil { - return fmt.Errorf("%s %s: %d answered with what is not the JSON expected (%w): %s", - method, endpoint, resp.StatusCode, err, truncate(string(raw))) - } - return nil -} +// -------------------------------------------------------------------------- +// what a failure says +// -------------------------------------------------------------------------- // APIError is a non-2xx answer, carrying both halves of what happened. // @@ -223,6 +197,36 @@ func StatusIs(err error, status int) bool { return errors.As(err, &apiErr) && apiErr.Status == status } +// fail turns one SDK call's (response, error) pair into this package's error. +// +// BOTH HALVES OR NEITHER. The SDK reads the response body to build its error +// and then closes it, so the body is only ever available through err; the +// status and the request line are only ever available through resp. Neither is +// a diagnosis on its own, and dropping either is how "the tracker said no" +// becomes a message nobody can act on. +// +// A 2xx that still errored is a decode failure, not an answer the server +// refused: it keeps the status out of the message and the shape out of +// StatusIs, because a caller asking "was that a 409" must not be told yes by a +// body it could not parse. +func fail(resp *sdk.Response, err error) error { + if err == nil { + return nil + } + if resp == nil || resp.Response == nil { + return err // never reached the server; the URL is already in the error + } + method, endpoint := "", "" + if r := resp.Request; r != nil { + method, endpoint = r.Method, r.URL.String() + } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return &APIError{Method: method, URL: endpoint, Status: resp.StatusCode, Body: err.Error()} + } + return fmt.Errorf("%s %s: %d answered with what is not the JSON expected: %w", + method, endpoint, resp.StatusCode, err) +} + func truncate(s string) string { if len(s) <= maxErrorBody { return s @@ -240,11 +244,17 @@ func truncate(s string) string { // where request bodies land // -------------------------------------------------------------------------- -// writePayload marshals a request body, files a copy under `.kettle/payload/`, -// and returns the bytes to send. +// dumper is the RoundTripper that files a copy of every request body under +// `.kettle/payload/`. // // The file survives the call, for a retry or a post-mortem. // +// A RoundTripper and not a call site's decision, because a call site can forget +// and a RoundTripper cannot: it sees every request the SDK builds, including +// the ones no method of this package spelled out. What a call site still +// supplies is the NAME — see label — because an issue's slug identifies the +// call in a post-mortem and a serial number does not. +// // WHERE IT LANDS IS NOT THE CALLER'S BUSINESS, and never was. The directory is // this package's scratchpad — a SIBLING of the issue store under the same // marker, resolved by the same walk, so which command wrote a body cannot @@ -255,58 +265,179 @@ func truncate(s string) string { // are debris of the transport, and when they share a path `ls` starts lying // about what the project holds. // -// It is created lazily, by the first write of a run and only then, so a dry run -// or a run with nothing to send leaves no directory behind. -func (c *Client) writePayload(b *Body) ([]byte, error) { - if c.payloadRoot == "" { - return nil, project.NotFoundError("") +// It is created lazily, by the first write of a run and only then, so a run +// with nothing to send — every read-only command, and the version handshake +// every run opens with — leaves no directory behind. +type dumper struct { + next http.RoundTripper + // root is resolved once, by New, and is never taken from a caller. + root string + + mu sync.Mutex + name string +} + +// label names the file the next request body lands in. One label serves one +// request: it is taken, not read, so a request the SDK makes on its own account +// cannot end up filed under the last thing a command was doing. +func (d *dumper) label(name string) { + d.mu.Lock() + d.name = name + d.mu.Unlock() +} + +func (d *dumper) take() string { + d.mu.Lock() + defer d.mu.Unlock() + name := d.name + d.name = "" + return name +} + +// RoundTrip files the body and then sends the request. +// +// A dump that cannot be written fails the call before it is made, which is the +// order the old hand-rolled client had and worth keeping: the point of the file +// is to hold what was sent, and one that does not exist for a request that did +// is worse than not having sent it. +func (d *dumper) RoundTrip(req *http.Request) (*http.Response, error) { + if err := d.file(req); err != nil { + return nil, err + } + return d.next.RoundTrip(req) +} + +func (d *dumper) file(req *http.Request) error { + name := d.take() + if req.Body == nil || req.GetBody == nil { + return nil // a read: nothing to file + } + body, err := req.GetBody() + if err != nil { + return err + } + defer body.Close() + raw, err := io.ReadAll(body) + if err != nil { + return err + } + if len(bytes.TrimSpace(raw)) == 0 { + return nil + } + if d.root == "" { + return project.NotFoundError("") + } + if name == "" { + name = derivedName(req) } + if err := os.MkdirAll(d.root, 0o755); err != nil { + return err + } + return os.WriteFile(filepath.Join(d.root, safeName(name)+".json"), readable(raw), 0o644) +} + +// readable is the request body as a person reads it: indented, and with the +// markup left alone. +// +// The SDK marshals with encoding/json's defaults, which escape `<`, `>` and `&` +// into their \u00xx spellings. An issue body carries `` markers and +// prose full of `&`, and a dump escaped that way is unreadable exactly when +// somebody is reading it because something went wrong. +// +// So the bytes are re-encoded rather than filed verbatim: same JSON VALUE, and +// numbers verbatim (UseNumber, so an id is not rounded through a float), but +// not the same bytes. What that costs is byte-for-byte fidelity with the wire — +// what it buys is a file anybody can read and re-POST. Anything that will not +// parse is filed as it came, because a dump of something surprising is exactly +// the dump worth having. +func readable(raw []byte) []byte { + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.UseNumber() + var v any + if err := dec.Decode(&v); err != nil { + return raw + } buf := &bytes.Buffer{} enc := json.NewEncoder(buf) enc.SetIndent("", " ") - // An issue body carries `` markers and prose full of `&`. - // Escaping those to < would make the dump unreadable exactly when - // somebody is reading it because something went wrong. enc.SetEscapeHTML(false) - if err := enc.Encode(b.Data); err != nil { - return nil, fmt.Errorf("encoding the %s request body: %w", b.name(), err) + if err := enc.Encode(v); err != nil { + return raw } - raw := buf.Bytes() - - if err := os.MkdirAll(c.payloadRoot, 0o755); err != nil { - return nil, err - } - path := filepath.Join(c.payloadRoot, b.name()+".json") - if err := os.WriteFile(path, raw, 0o644); err != nil { - return nil, err - } - return raw, nil + return buf.Bytes() } -// name is the file stem, with everything that is not plainly a file name folded -// away. +// derivedName is what an unnamed request is filed under: its method and its +// path. Nobody has to remember to name a call for its body to be kept — a name +// only makes the file easier to find. +func derivedName(req *http.Request) string { + return strings.ToLower(req.Method) + "-" + strings.TrimPrefix(req.URL.Path, "/api/v1/") +} + +// safeName is the file stem, with everything that is not plainly a file name +// folded away. // // Sanitizing here rather than trusting callers: label names are namespaced // (`type/bug`), and a name passed straight through would write outside the // scratchpad — which is the one thing this directory exists to prevent. -func (b *Body) name() string { - if b.Name == "" { - return "request" - } +func safeName(name string) string { safe := strings.Map(func(r rune) rune { switch { case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '-', r == '_': return r } return '-' - }, b.Name) + }, name) if safe = strings.Trim(safe, "-"); safe == "" { return "request" } return safe } +// -------------------------------------------------------------------------- +// the one request the SDK cannot express +// -------------------------------------------------------------------------- + +// post sends one JSON body to a path under this instance's API and ignores +// whatever comes back. +// +// It exists for AddDependency and for nothing else — see there for what the SDK +// leaves out. It goes through the same http.Client, so the body is filed and a +// failure carries the status and the server's words exactly as every other call +// in this package does. +func (c *Client) post(path string, body any, name string) error { + raw, err := json.Marshal(body) + if err != nil { + return err + } + endpoint := c.base + "/api/v1/" + strings.TrimLeft(path, "/") + req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw)) + if err != nil { + return err + } + // Gitea's own scheme, and what the `tea` CLI this replaces sent: the word + // `token`, not `Bearer`. An instance answers 401 to the other spelling. + req.Header.Set("Authorization", "token "+c.token) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", userAgent) + + c.dump.label(name) + resp, err := c.http.Do(req) + if err != nil { + // The token travels in a header and never in the URL, so an error is + // free to quote the URL in full. + return err + } + defer resp.Body.Close() + answer, _ := io.ReadAll(resp.Body) + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return &APIError{Method: http.MethodPost, URL: endpoint, Status: resp.StatusCode, Body: string(answer)} + } + return nil +} + // -------------------------------------------------------------------------- // pagination // -------------------------------------------------------------------------- @@ -327,21 +458,19 @@ const ( PageSlack = 4 ) -// pages GETs a list endpoint page by page and hands each page to each as it -// arrives, stopping when each returns false, when a short page says the list is +// pages calls fetch page by page and hands each page to each as it arrives, +// stopping when each returns false, when a short page says the list is // exhausted, or when budget pages have been read. // // A callback rather than a slice, because a caller whose budget is spent on // what it KEEPS cannot be served by a function that fetches everything first: // the page after the one that completed the budget must never be requested. -func pages[T any](c *Client, path string, limit, budget int, each func([]T) (bool, error)) error { - sep := "?" - if strings.Contains(path, "?") { - sep = "&" - } +// That is the one thing the SDK's own list options cannot do — they describe a +// page, and this describes when to stop asking for another. +func pages[T any](fetch func(page, limit int) ([]T, error), limit, budget int, each func([]T) (bool, error)) error { for page := 1; page <= budget; page++ { - var batch []T - if err := c.Call(http.MethodGet, fmt.Sprintf("%s%spage=%d&limit=%d", path, sep, page, limit), nil, &batch); err != nil { + batch, err := fetch(page, limit) + if err != nil { return err } if len(batch) == 0 { @@ -359,23 +488,16 @@ func pages[T any](c *Client, path string, limit, budget int, each func([]T) (boo } // paginate follows a list endpoint to exhaustion and returns the whole list. -func paginate[T any](c *Client, path string, limit int) ([]T, error) { +func paginate[T any](fetch func(page, limit int) ([]T, error), limit int) ([]T, error) { var out []T - err := pages(c, path, limit, maxPages, func(batch []T) (bool, error) { + err := pages(fetch, limit, maxPages, func(batch []T) (bool, error) { out = append(out, batch...) return true, nil }) return out, err } -// repoPath builds an endpoint under this client's repository. Owner and name -// are escaped: they arrive from a config file, and a file is a thing people -// type into. -func (c *Client) repoPath(suffix string) string { - return "repos/" + url.PathEscape(c.repo.Owner) + "/" + url.PathEscape(c.repo.Name) + "/" + suffix -} - -// repoPathf is repoPath with the issue or label number formatted in. -func (c *Client) repoPathf(format string, args ...any) string { - return c.repoPath(fmt.Sprintf(format, args...)) +// listOptions is one page, as the SDK asks for it. +func listOptions(page, limit int) sdk.ListOptions { + return sdk.ListOptions{Page: page, PageSize: limit} } diff --git a/cli/internal/gitea/client_test.go b/cli/internal/gitea/client_test.go index 85900b7..4cdaf44 100644 --- a/cli/internal/gitea/client_test.go +++ b/cli/internal/gitea/client_test.go @@ -9,6 +9,12 @@ package gitea_test // — and a request dump would land in the developer's own project. Nothing here // reads a login file either, but KETTLE_CONFIG_HOME is redirected all the same, // so a run can neither read nor overwrite the developer's own tokens. +// +// EVERY FAKE ANSWERS /api/v1/version, because building a client is now a +// request: the SDK asks the instance what it is before it hands one back, and +// that answer is what the dependency gate is decided on later. A fake that did +// not answer it would be a fake no client can be built against — see +// versionRoute. import ( "encoding/json" @@ -19,15 +25,22 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "strconv" "strings" "testing" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/config" "git.noodles.cam/claude-skills/marketplace/cli/internal/gitea" "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) +// modernGitea is what a fake says it is: new enough for everything this +// transport asks for, dependency endpoints included. +const modernGitea = "1.26.1" + // newProject makes an initialized project and points the walk at it. Returns // the project root. func newProject(t *testing.T) string { @@ -41,6 +54,31 @@ func newProject(t *testing.T) string { return dir } +// versionRoute answers the SDK's version handshake and reports whether it did, +// so every other handler can be written as though the request were not there. +func versionRoute(w http.ResponseWriter, r *http.Request, version string) bool { + if r.URL.Path != "/api/v1/version" { + return false + } + w.Header().Set("Content-Type", "application/json") + _, _ = io.WriteString(w, `{"version":"`+version+`"}`) + return true +} + +// serve is an httptest server that speaks the handshake and hands everything +// else to next. +func serve(t *testing.T, version string, next http.HandlerFunc) *httptest.Server { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if versionRoute(w, r, version) { + return + } + next(w, r) + })) + t.Cleanup(srv.Close) + return srv +} + func newClient(t *testing.T, url string) *gitea.Client { t.Helper() c, err := gitea.New(&config.Resolved{URL: url, Token: "s3cret", Owner: "acme", Repo: "widgets"}) @@ -65,7 +103,7 @@ func TestPaginationFollowsToTheLastPage(t *testing.T) { var asked []string var auth string - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := serve(t, modernGitea, func(w http.ResponseWriter, r *http.Request) { asked = append(asked, r.URL.RequestURI()) auth = r.Header.Get("Authorization") @@ -82,8 +120,7 @@ func TestPaginationFollowsToTheLastPage(t *testing.T) { out = append(out, map[string]any{"id": (page-1)*limit + i + 1, "body": "hello"}) } writeJSON(t, w, out) - })) - defer srv.Close() + }) got, err := newClient(t, srv.URL).ListComments(42) if err != nil { @@ -102,7 +139,7 @@ func TestPaginationFollowsToTheLastPage(t *testing.T) { if auth != "token s3cret" { t.Errorf("Authorization was %q, want %q", auth, "token s3cret") } - want := "/api/v1/repos/acme/widgets/issues/42/comments?page=1&limit=50" + want := "/api/v1/repos/acme/widgets/issues/42/comments?limit=50&page=1" if asked[0] != want { t.Errorf("first request was %s, want %s", asked[0], want) } @@ -113,11 +150,10 @@ func TestPaginationFollowsToTheLastPage(t *testing.T) { func TestErrorNamesTheStatusAndTheBody(t *testing.T) { newProject(t) - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := serve(t, modernGitea, func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnprocessableEntity) io.WriteString(w, `{"message":"label already exists","url":"https://example.test/docs"}`) - })) - defer srv.Close() + }) _, err := newClient(t, srv.URL).GetIssue(7) if err == nil { @@ -152,21 +188,20 @@ func TestPayloadLandsBesideTheStoreAndNeverInIt(t *testing.T) { root := newProject(t) var sent []byte - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := serve(t, modernGitea, func(w http.ResponseWriter, r *http.Request) { sent, _ = io.ReadAll(r.Body) writeJSON(t, w, map[string]any{ "number": 42, "id": 5, "html_url": "https://example.test/acme/widgets/issues/42"}) - })) - defer srv.Close() + }) body := " a & b" got, err := newClient(t, srv.URL).CreateIssue( - wire.IssueRequest{Title: wire.Set("wire sqlc"), Body: wire.Set(body)}, "issue-wire-sqlc") + sdk.CreateIssueOption{Title: "wire sqlc", Body: body}, "issue-wire-sqlc") if err != nil { t.Fatalf("CreateIssue: %v", err) } - if got.Number != 42 { - t.Errorf("got issue #%d, want #42", got.Number) + if got.Index != 42 { + t.Errorf("got issue #%d, want #42", got.Index) } path := filepath.Join(root, ".kettle", "payload", "issue-wire-sqlc.json") @@ -174,19 +209,33 @@ func TestPayloadLandsBesideTheStoreAndNeverInIt(t *testing.T) { if err != nil { t.Fatalf("the request body was not filed at %s: %v", path, err) } - if string(raw) != string(sent) { + // The same JSON VALUE as went out, and not the same bytes: the SDK marshals + // with encoding/json's defaults, so what is on the wire has its markup + // escaped and no indentation. A dump nobody can read is a dump nobody + // reads, and a re-POST of this file sends what this call sent. + var filed, onTheWire any + if err := json.Unmarshal(raw, &filed); err != nil { + t.Fatalf("the filed body is not JSON: %v\n%s", err, raw) + } + if err := json.Unmarshal(sent, &onTheWire); err != nil { + t.Fatalf("what was sent is not JSON: %v\n%s", err, sent) + } + if !reflect.DeepEqual(filed, onTheWire) { t.Errorf("the filed body is not the body that was sent:\nfiled: %s\nsent: %s", raw, sent) } // A dump escaped to \u003c is unreadable exactly when it is being read. if !strings.Contains(string(raw), " a & b") { t.Errorf("the dump escaped the markup it was meant to preserve:\n%s", raw) } + if !strings.Contains(string(raw), "\n \"") { + t.Errorf("the dump is not indented:\n%s", raw) + } // The whole reason the scratchpad is a sibling. if _, err := os.Stat(filepath.Join(root, ".kettle", "issues")); !os.IsNotExist(err) { t.Errorf("writing a request body materialized the issue store (%v)", err) } // A namespaced name must not climb out of the scratchpad. - if _, err := newClient(t, srv.URL).CreateLabel(wire.LabelRequest{Name: "type/bug", Color: "#ee0701"}); err != nil { + if _, err := newClient(t, srv.URL).CreateLabel(sdk.CreateLabelOption{Name: "type/bug", Color: "#ee0701"}); err != nil { t.Fatalf("CreateLabel: %v", err) } if _, err := os.Stat(filepath.Join(root, ".kettle", "payload", "label-type-bug.json")); err != nil { @@ -198,14 +247,14 @@ func TestPayloadLandsBesideTheStoreAndNeverInIt(t *testing.T) { } // A run that sends no body leaves no directory behind — the scratchpad is -// created by the first write and only then. +// created by the first write and only then. The version handshake every client +// opens with is a read, so building one is not "a run that sent something". func TestAReadOnlyCallCreatesNoScratchpad(t *testing.T) { root := newProject(t) - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := serve(t, modernGitea, func(w http.ResponseWriter, r *http.Request) { writeJSON(t, w, map[string]any{"number": 42}) - })) - defer srv.Close() + }) if _, err := newClient(t, srv.URL).GetIssue(42); err != nil { t.Fatalf("GetIssue: %v", err) @@ -221,7 +270,7 @@ func TestAReadOnlyCallCreatesNoScratchpad(t *testing.T) { func TestListIssuesRechecksWhatTheServerIgnored(t *testing.T) { newProject(t) - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := serve(t, modernGitea, func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.Path, "/milestones") { writeJSON(t, w, []map[string]any{{"id": 3, "title": "v1"}, {"id": 9, "title": "later"}}) return @@ -237,8 +286,7 @@ func TestListIssuesRechecksWhatTheServerIgnored(t *testing.T) { {"number": 4, "title": "a pull request", "milestone": map[string]any{"id": 3, "title": "v1"}, "pull_request": map[string]any{"merged": false}}, }) - })) - defer srv.Close() + }) got, err := newClient(t, srv.URL).ListIssues(gitea.IssueFilter{Milestone: "v1", Limit: 50}) if err != nil { @@ -247,7 +295,7 @@ func TestListIssuesRechecksWhatTheServerIgnored(t *testing.T) { if got.Milestone != "v1" { t.Errorf("resolved milestone is %q, want v1", got.Milestone) } - if len(got.Issues) != 1 || got.Issues[0].Number != 1 { + if len(got.Issues) != 1 || got.Issues[0].Index != 1 { t.Fatalf("got %d issue(s) %v, want only #1 — the backlog was not re-filtered", len(got.Issues), got.Issues) } @@ -265,7 +313,7 @@ func TestListIssuesStopsAtThePageBudget(t *testing.T) { newProject(t) pages := 0 - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := serve(t, modernGitea, func(w http.ResponseWriter, r *http.Request) { pages++ limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) out := []map[string]any{} @@ -273,12 +321,11 @@ func TestListIssuesStopsAtThePageBudget(t *testing.T) { out = append(out, map[string]any{"number": pages*100 + i, "state": "closed"}) } writeJSON(t, w, out) - })) - defer srv.Close() + }) got, err := newClient(t, srv.URL).ListIssues(gitea.IssueFilter{ Limit: 2, - Keep: func(i *wire.Issue) bool { return i.State == "open" }, + Keep: func(i *sdk.Issue) bool { return i.State == sdk.StateOpen }, }) if err != nil { t.Fatalf("ListIssues: %v", err) @@ -302,7 +349,7 @@ func TestListIssuesStopsAtTheLimit(t *testing.T) { newProject(t) pages := 0 - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := serve(t, modernGitea, func(w http.ResponseWriter, r *http.Request) { pages++ limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) out := []map[string]any{} @@ -310,12 +357,11 @@ func TestListIssuesStopsAtTheLimit(t *testing.T) { out = append(out, map[string]any{"number": pages*100 + i, "state": "open"}) } writeJSON(t, w, out) - })) - defer srv.Close() + }) got, err := newClient(t, srv.URL).ListIssues(gitea.IssueFilter{ Limit: 2, - Keep: func(i *wire.Issue) bool { return i.State == "open" }, + Keep: func(i *sdk.Issue) bool { return i.State == sdk.StateOpen }, }) if err != nil { t.Fatalf("ListIssues: %v", err) @@ -328,29 +374,122 @@ func TestListIssuesStopsAtTheLimit(t *testing.T) { } } -// A dependency endpoint the instance does not have is "no dependencies", not a -// failed pull. A dead connection still is one. +// A dependency endpoint the instance has but this repository does not is "no +// dependencies", not a failed pull. A dead connection still is one. func TestDependenciesToleratesAnInstanceWithoutThem(t *testing.T) { newProject(t) - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := serve(t, modernGitea, func(w http.ResponseWriter, r *http.Request) { http.Error(w, "not implemented", http.StatusNotImplemented) - })) - defer srv.Close() + }) - got, err := newClient(t, srv.URL).DependencyKeys(42) + // Both clients are built while the server is up, because building one is + // itself a request now — and the question this asks is what a call does + // when the connection dies UNDER it, which is a different failure from a + // tracker that was never there. + live := newClient(t, srv.URL) + dead := newClient(t, srv.URL) + + got, err := live.DependencyKeys(42) if err != nil || len(got) != 0 { t.Errorf("DependencyKeys = %v, %v; want no keys and no error", got, err) } srv.Close() - if _, err := newClient(t, srv.URL).Dependencies(42); err == nil { + if _, err := dead.Dependencies(42); err == nil { t.Error("a dead connection was reported as an instance without dependency support") } } +// The gate the SDK made possible: an instance too old to have the endpoint at +// all is answered from its version, without a request. +// +// It matters because the alternative is guessing from a status code. An old +// Gitea answers a route it does not have with the same 404 it answers for an +// issue that does not exist, and a pull that read the second as "no blockers" +// would quietly drop half the unit of work. +func TestDependenciesAreNotAskedForOnAnInstanceTooOldToHaveThem(t *testing.T) { + newProject(t) + + asked := 0 + srv := serve(t, "1.19.4", func(w http.ResponseWriter, r *http.Request) { + asked++ + writeJSON(t, w, []map[string]any{{"number": 7}}) + }) + + c := newClient(t, srv.URL) + got, err := c.Dependencies(42) + if err != nil || len(got) != 0 { + t.Errorf("Dependencies = %v, %v; want none and no error", got, err) + } + if asked != 0 { + t.Errorf("%d request(s) went out — the version had already answered", asked) + } + + // Writing one says so out loud instead: push reports it beside the issue it + // could not link, and a warning naming the version is something an operator + // can act on. + err = c.AddDependency(42, wire.Key{Repo: wire.Repo{Owner: "acme", Name: "widgets"}, Number: 7}) + if err == nil { + t.Fatal("a link was attempted against an instance that has no endpoint for it") + } + if !strings.Contains(err.Error(), "1.20.0") { + t.Errorf("the refusal does not name the version that would work: %v", err) + } + if asked != 0 { + t.Errorf("%d request(s) went out for a write the instance cannot take", asked) + } +} + +// And the other side of the same gate: a modern instance is asked, and the +// answer comes back as cross-repo keys — a dependency is allowed to live +// somewhere else, and a bare number would not say where. +func TestDependenciesAreAskedForOnAnInstanceThatHasThem(t *testing.T) { + newProject(t) + + var body string + srv := serve(t, modernGitea, func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + raw, _ := io.ReadAll(r.Body) + body = string(raw) + w.WriteHeader(http.StatusCreated) + return + } + writeJSON(t, w, []map[string]any{ + {"number": 7}, + {"number": 3, "repository": map[string]any{"full_name": "other/repo"}}, + }) + }) + + c := newClient(t, srv.URL) + got, err := c.DependencyKeys(42) + if err != nil { + t.Fatalf("DependencyKeys: %v", err) + } + want := []wire.Key{ + {Repo: wire.Repo{Owner: "acme", Name: "widgets"}, Number: 7}, + {Repo: wire.Repo{Owner: "other", Name: "repo"}, Number: 3}, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("keys = %v, want %v", got, want) + } + + // The one endpoint left with a hand-rolled request, because the SDK's + // IssueMeta carries an index and nothing else: a link to another repository + // needs the owner and the name with it. + if err := c.AddDependency(42, want[1]); err != nil { + t.Fatalf("AddDependency: %v", err) + } + for _, part := range []string{`"index":3`, `"owner":"other"`, `"repo":"repo"`} { + if !strings.Contains(body, part) { + t.Errorf("the link body does not carry %s: %s", part, body) + } + } +} + // A half-filled configuration is refused here rather than at the first 401, -// because a 401 names nothing an operator can act on. +// because a 401 names nothing an operator can act on — and before the client is +// built at all, because building one dials. func TestNewRefusesAHalfFilledConfiguration(t *testing.T) { for _, tc := range []struct { what string @@ -372,14 +511,54 @@ func TestNewRefusesAHalfFilledConfiguration(t *testing.T) { } } +// An instance nobody can reach is named as one. The handshake is the first +// request of every run, so this is the failure an operator meets when the URL +// is wrong or the tracker is down, and it has to say which instance. +func TestNewSaysWhichInstanceItCouldNotReach(t *testing.T) { + newProject(t) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + srv.Close() + + _, err := gitea.New(&config.Resolved{URL: srv.URL, Token: "t", Owner: "a", Repo: "b"}) + if err == nil { + t.Fatal("a client was built against a tracker that is not there") + } + if !strings.Contains(err.Error(), srv.URL) { + t.Errorf("the error does not name the instance: %v", err) + } +} + +// A version nobody can parse is refused at the handshake, not carried. +// +// The SDK hands back a client that has silently decided the server is 1.11 and +// then rewrites issue URLs from a field a modern payload need not carry, which +// is a nil dereference on the first issue read. A refusal that names the +// instance is the answer an operator can act on. +func TestNewRefusesAnInstanceWhoseVersionIsNotOne(t *testing.T) { + newProject(t) + + srv := serve(t, "not-a-version", func(w http.ResponseWriter, r *http.Request) { + t.Errorf("a call went out to %s after the handshake had already failed", r.URL.Path) + }) + + _, err := gitea.New(&config.Resolved{URL: srv.URL, Token: "t", Owner: "a", Repo: "b"}) + if err == nil { + t.Fatal("an unreadable version was accepted — the first issue read would panic inside the SDK") + } + if !strings.Contains(err.Error(), srv.URL) || !strings.Contains(err.Error(), config.EnvURL) { + t.Errorf("the refusal names neither the instance nor the setting that points at it: %v", err) + } +} + // The layering rule, from this side. The transport knows numbers, logins, HTTP // and JSON; the domain knows none of those, and neither may reach the other. // // The bridge is out too, and for a reason of its own: it is the layer that // translates between the two, so it sits ABOVE both. A transport that imported // it would be a transport that knows what an issue is, one indirection later — -// and the protocol both of them share, internal/wire, exists precisely so that -// neither has to reach for the other to name a payload. +// and the vocabulary both of them share, the SDK's payloads, exists precisely +// so that neither has to reach for the other to name one. func TestTransportDoesNotImportTheDomain(t *testing.T) { out, err := exec.Command("go", "list", "-deps", ".").Output() if err != nil { diff --git a/cli/internal/gitea/issues.go b/cli/internal/gitea/issues.go index 1629eb9..d1a70b0 100644 --- a/cli/internal/gitea/issues.go +++ b/cli/internal/gitea/issues.go @@ -3,10 +3,11 @@ package gitea import ( "errors" "fmt" - "net/http" "net/url" "strings" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) @@ -14,86 +15,98 @@ import ( // // A number is an address, not a query: this answers for a closed issue exactly // as it does for an open one. -func (c *Client) GetIssue(number int) (*wire.Issue, error) { - var got wire.Issue - if err := c.Call(http.MethodGet, c.repoPathf("issues/%d", number), nil, &got); err != nil { +func (c *Client) GetIssue(number int) (*sdk.Issue, error) { + owner, repo := c.owned() + got, resp, err := c.api.GetIssue(owner, repo, int64(number)) + if err := fail(resp, err); err != nil { return nil, err } // A 200 that carries no number is not this issue. Gitea has answered that // way for a repository whose issue tracker is disabled. - if got.Number == 0 { + if got == nil || got.Index == 0 { return nil, fmt.Errorf("issue #%d not found in %s", number, c.repo) } - return &got, nil + return got, nil } // CreateIssue files a new issue. name labels the request body in the // scratchpad; the issue's slug is what makes that dump worth keeping. -func (c *Client) CreateIssue(req wire.IssueRequest, name string) (*wire.Issue, error) { - var got wire.Issue - body := &Body{Name: name, Data: req} - if err := c.Call(http.MethodPost, c.repoPath("issues"), body, &got); err != nil { +func (c *Client) CreateIssue(opt sdk.CreateIssueOption, name string) (*sdk.Issue, error) { + owner, repo := c.owned() + c.dump.label(name) + got, resp, err := c.api.CreateIssue(owner, repo, opt) + if err := fail(resp, err); err != nil { return nil, err } - return &got, nil + return got, nil } -// EditIssue patches an existing issue. Only the fields set on req are sent. -func (c *Client) EditIssue(number int, req wire.IssueRequest, name string) (*wire.Issue, error) { - var got wire.Issue - body := &Body{Name: name, Data: req} - if err := c.Call(http.MethodPatch, c.repoPathf("issues/%d", number), body, &got); err != nil { +// EditIssue patches an existing issue. Only the fields set on opt are sent. +// +// LABELS DO NOT GO THROUGH HERE. Gitea's edit endpoint takes no label list and +// neither does the SDK's EditIssueOption, so an issue whose labels changed +// needs SetLabels after this — push does exactly that, and says so. +func (c *Client) EditIssue(number int, opt sdk.EditIssueOption, name string) (*sdk.Issue, error) { + owner, repo := c.owned() + c.dump.label(name) + got, resp, err := c.api.EditIssue(owner, repo, int64(number), opt) + if err := fail(resp, err); err != nil { return nil, err } - return &got, nil + return got, nil } // SetLabels replaces an issue's labels with exactly these ids. // // It exists because Gitea occasionally drops labels handed to it on create, and -// the answer to that is to re-apply them rather than to trust the echo. -func (c *Client) SetLabels(number int, ids []int64, name string) ([]wire.Label, error) { +// the answer to that is to re-apply them rather than to trust the echo. It is +// also the only way to change the labels of an issue that already exists — see +// EditIssue. +func (c *Client) SetLabels(number int, ids []int64, name string) ([]*sdk.Label, error) { if ids == nil { ids = []int64{} } - var got []wire.Label - body := &Body{Name: name, Data: struct { - Labels []int64 `json:"labels"` - }{ids}} - if err := c.Call(http.MethodPut, c.repoPathf("issues/%d/labels", number), body, &got); err != nil { + owner, repo := c.owned() + c.dump.label(name) + got, resp, err := c.api.ReplaceIssueLabels(owner, repo, int64(number), sdk.IssueLabelsOption{Labels: ids}) + if err := fail(resp, err); err != nil { return nil, err } return got, nil } // ListComments is an issue's whole thread, every page of it. -func (c *Client) ListComments(number int) ([]wire.Comment, error) { - return paginate[wire.Comment](c, c.repoPathf("issues/%d/comments", number), pageLimit) +func (c *Client) ListComments(number int) ([]*sdk.Comment, error) { + owner, repo := c.owned() + return paginate(func(page, limit int) ([]*sdk.Comment, error) { + got, resp, err := c.api.ListIssueComments(owner, repo, int64(number), + sdk.ListIssueCommentOptions{ListOptions: listOptions(page, limit)}) + return got, fail(resp, err) + }, pageLimit) } // CreateComment posts a comment on an issue. -func (c *Client) CreateComment(number int, text, name string) (*wire.Comment, error) { - var got wire.Comment - body := &Body{Name: name, Data: commentBody{Body: text}} - if err := c.Call(http.MethodPost, c.repoPathf("issues/%d/comments", number), body, &got); err != nil { +func (c *Client) CreateComment(number int, text, name string) (*sdk.Comment, error) { + owner, repo := c.owned() + c.dump.label(name) + got, resp, err := c.api.CreateIssueComment(owner, repo, int64(number), + sdk.CreateIssueCommentOption{Body: text}) + if err := fail(resp, err); err != nil { return nil, err } - return &got, nil + return got, nil } // EditComment rewrites one comment, addressed by its own id and not by the // issue it is on — which is how Gitea addresses it. -func (c *Client) EditComment(id int64, text, name string) (*wire.Comment, error) { - var got wire.Comment - body := &Body{Name: name, Data: commentBody{Body: text}} - if err := c.Call(http.MethodPatch, c.repoPathf("issues/comments/%d", id), body, &got); err != nil { +func (c *Client) EditComment(id int64, text, name string) (*sdk.Comment, error) { + owner, repo := c.owned() + c.dump.label(name) + got, resp, err := c.api.EditIssueComment(owner, repo, id, sdk.EditIssueCommentOption{Body: text}) + if err := fail(resp, err); err != nil { return nil, err } - return &got, nil -} - -type commentBody struct { - Body string `json:"body"` + return got, nil } // -------------------------------------------------------------------------- @@ -121,13 +134,13 @@ type IssueFilter struct { // something to say about them ("11 closed, not stored") still can. // // What Keep means is the caller's business; this package only counts. - Keep func(*wire.Issue) bool + Keep func(*sdk.Issue) bool } // IssueListing is what a filtered read found. type IssueListing struct { // Issues are every payload that passed the filter, kept or not. - Issues []wire.Issue + Issues []*sdk.Issue // Milestone is the resolved milestone title, for a receipt. Milestone string // Warning is set when a Keep-bounded read ran out of page budget with the @@ -167,23 +180,27 @@ func (c *Client) ListIssues(f IssueFilter) (*IssueListing, error) { milestoneID, out.Milestone = ms.ID, ms.Title } - params := url.Values{} state := f.State if state == "" { state = "open" } - params.Set("state", state) - params.Set("type", "issues") - if len(f.Labels) > 0 { - params.Set("labels", strings.Join(f.Labels, ",")) + owner, repo := c.owned() + fetch := func(page, limit int) ([]*sdk.Issue, error) { + opt := sdk.ListIssueOption{ + ListOptions: listOptions(page, limit), + State: sdk.StateType(state), + // Issues and not pull requests. The server has been known to + // ignore this, which is why matches re-checks it. + Type: sdk.IssueTypeIssue, + Labels: f.Labels, + KeyWord: f.Query, + } + if out.Milestone != "" { + opt.Milestones = []string{out.Milestone} + } + got, resp, err := c.api.ListRepoIssues(owner, repo, opt) + return got, fail(resp, err) } - if f.Query != "" { - params.Set("q", f.Query) - } - if out.Milestone != "" { - params.Set("milestones", out.Milestone) - } - path := c.repoPath("issues?" + params.Encode()) perPage := min(f.Limit, pageLimit) ideal := max(1, (f.Limit+perPage-1)/perPage) @@ -193,15 +210,14 @@ func (c *Client) ListIssues(f IssueFilter) (*IssueListing, error) { } kept, seen, lastFull := 0, 0, false - err := pages(c, path, perPage, budget, func(batch []wire.Issue) (bool, error) { + err := pages(fetch, perPage, budget, func(batch []*sdk.Issue) (bool, error) { seen++ lastFull = len(batch) == perPage - for i := range batch { - p := &batch[i] + for _, p := range batch { if !matches(p, milestoneID, f.Labels) { continue } - out.Issues = append(out.Issues, *p) + out.Issues = append(out.Issues, p) if f.Keep == nil || f.Keep(p) { kept++ if kept >= f.Limit { @@ -230,10 +246,10 @@ func (c *Client) ListIssues(f IssueFilter) (*IssueListing, error) { // matters most — a pull request rendered as a unit of work is not a bug the // operator can see until it is in the store. // -// A function and not a method: the payload is the protocol's, and re-checking a -// filter the server ignored is this package's business, not the protocol's. -func matches(i *wire.Issue, milestoneID int64, labels []string) bool { - if i.IsPullRequest() { +// A function and not a method: the payload is the SDK's, and re-checking a +// filter the server ignored is this package's business, not the payload's. +func matches(i *sdk.Issue, milestoneID int64, labels []string) bool { + if i.PullRequest != nil { return false } if milestoneID != 0 && (i.Milestone == nil || i.Milestone.ID != milestoneID) { @@ -255,28 +271,46 @@ func matches(i *wire.Issue, milestoneID int64, labels []string) bool { // dependencies // -------------------------------------------------------------------------- -// issueMeta is Gitea's IssueMeta: how a dependency names another issue. -type issueMeta struct { - Index int `json:"index"` - Owner string `json:"owner"` - Repo string `json:"repo"` +// dependenciesSince is the first Gitea release that answers at +// /issues/{index}/dependencies at all. +// +// Checked against the release tags themselves and not guessed: the routes are +// absent from routers/api/v1/api.go through 1.19 and present in 1.20. Asking +// the version rather than the endpoint is what turns "some error came back" +// into an answer — and it costs no request, because the SDK already negotiated +// the version when the client was built. +const dependenciesSince = ">= 1.20.0" + +// hasDependencies reports whether this instance is new enough to have the +// dependency endpoints. +func (c *Client) hasDependencies() bool { + return c.api.CheckServerVersionConstraint(dependenciesSince) == nil } // Dependencies are the issues that block this one — Gitea's own dependency // links, read in the direction AddDependency writes them. // -// An instance that does not have the endpoint, or has dependencies turned off -// for this repository, answers with a status rather than a list. That is -// reported as "no dependencies" and not as a failure: a pull must still bring -// the issue itself back from a tracker whose dependency support is off. +// TWO WAYS FOR THERE TO BE NO ANSWER, and both are reported as "no +// dependencies" rather than as a failure, because a pull must still bring the +// issue itself back: +// +// - the instance predates the endpoint, which the version says before a +// request is made; +// - the instance has it but this repository does not — dependencies turned +// off, a tracker disabled — which only the tracker's own answer can say. // // Deliberately narrower than the Python it replaces, which swallowed every // failure here including a dead connection. "The server said no" and "there was // no server" are different answers, and only the first one means the feature is // missing. -func (c *Client) Dependencies(number int) ([]wire.Issue, error) { - var got []wire.Issue - err := c.Call(http.MethodGet, c.repoPathf("issues/%d/dependencies", number), nil, &got) +func (c *Client) Dependencies(number int) ([]*sdk.Issue, error) { + if !c.hasDependencies() { + return nil, nil + } + owner, repo := c.owned() + got, resp, err := c.api.ListIssueDependencies(owner, repo, int64(number), + sdk.ListIssueDependenciesOptions{ListOptions: listOptions(1, pageLimit)}) + err = fail(resp, err) var apiErr *APIError if errors.As(err, &apiErr) { return nil, nil @@ -298,12 +332,38 @@ func (c *Client) DependencyKeys(number int) ([]wire.Key, error) { return nil, err } out := make([]wire.Key, 0, len(deps)) - for i := range deps { - out = append(out, deps[i].KeyIn(c.repo)) + for _, d := range deps { + out = append(out, keyIn(d, c.repo)) } return out, nil } +// keyIn is a payload's cross-repo handle. The payload's own repository wins +// when it carries one — a dependency listing answers with issues from other +// repositories — and fallback is the repository that was asked. +func keyIn(p *sdk.Issue, fallback wire.Repo) wire.Key { + repo := fallback + if p.Repository != nil { + if r, err := wire.ParseRepo(p.Repository.FullName); err == nil { + repo = r + } + } + return wire.Key{Repo: repo, Number: int(p.Index)} +} + +// issueMeta is Gitea's own IssueMeta: how a dependency names another issue. +// +// The SDK has a type of this name too and it carries only `index`, so it can +// only ever link inside one repository. Gitea's has taken an owner and a repo +// since the endpoint existed, and a `depends:` entry is allowed to live +// somewhere else — so this one struct and the raw POST that sends it are all +// that is left of the hand-rolled client. +type issueMeta struct { + Index int `json:"index"` + Owner string `json:"owner"` + Repo string `json:"repo"` +} + // AddDependency makes issue number depend on dep. // // Confirmed against an instance's own swagger.v1.json (Gitea 1.26.1): @@ -324,9 +384,16 @@ func (c *Client) AddDependency(number int, dep wire.Key) error { if dep.Number < 1 { return fmt.Errorf("dependency %s names no issue number", dep) } - body := &Body{ - Name: fmt.Sprintf("dep-%d-%d", number, dep.Number), - Data: issueMeta{Index: dep.Number, Owner: dep.Repo.Owner, Repo: dep.Repo.Name}, + if !c.hasDependencies() { + return fmt.Errorf("this Gitea has no issue-dependency API (it is not %s) — link #%d -> %s by hand", + strings.TrimPrefix(dependenciesSince, ">= "), number, dep) } - return c.Call(http.MethodPost, c.repoPathf("issues/%d/dependencies", number), body, nil) + // Owner and name are escaped, the way the SDK escapes them for every other + // call: they arrive from a config file, and a file is a thing people type + // into. + return c.post( + fmt.Sprintf("repos/%s/%s/issues/%d/dependencies", + url.PathEscape(c.repo.Owner), url.PathEscape(c.repo.Name), number), + issueMeta{Index: dep.Number, Owner: dep.Repo.Owner, Repo: dep.Repo.Name}, + fmt.Sprintf("dep-%d-%d", number, dep.Number)) } diff --git a/cli/internal/gitea/labels.go b/cli/internal/gitea/labels.go index 33effa6..831debd 100644 --- a/cli/internal/gitea/labels.go +++ b/cli/internal/gitea/labels.go @@ -2,11 +2,10 @@ package gitea import ( "fmt" - "net/http" "strconv" "strings" - "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" + sdk "code.gitea.io/sdk/gitea" ) // ListLabels is every label in the repository, every page of it. @@ -14,8 +13,13 @@ import ( // A bootstrap decides its plan against this and never against a cache: a cache // answers "what did we create last time", and the question is "what does the // repository have right now". -func (c *Client) ListLabels() ([]wire.Label, error) { - return paginate[wire.Label](c, c.repoPath("labels"), 100) +func (c *Client) ListLabels() ([]*sdk.Label, error) { + owner, repo := c.owned() + return paginate(func(page, limit int) ([]*sdk.Label, error) { + got, resp, err := c.api.ListRepoLabels(owner, repo, + sdk.ListLabelsOptions{ListOptions: listOptions(page, limit)}) + return got, fail(resp, err) + }, 100) } // CreateLabel adds a label to the repository. @@ -26,26 +30,40 @@ func (c *Client) ListLabels() ([]wire.Label, error) { // // What a label MEANS is not decided here either: this creates what it is // handed. -func (c *Client) CreateLabel(req wire.LabelRequest) (*wire.Label, error) { - var got wire.Label - body := &Body{Name: "label-" + req.Name, Data: req} - if err := c.Call(http.MethodPost, c.repoPath("labels"), body, &got); err != nil { +func (c *Client) CreateLabel(opt sdk.CreateLabelOption) (*sdk.Label, error) { + owner, repo := c.owned() + c.dump.label("label-" + opt.Name) + got, resp, err := c.api.CreateLabel(owner, repo, opt) + if err := fail(resp, err); err != nil { return nil, err } - if got.ID == 0 { - return nil, fmt.Errorf("creating label %q: the tracker's answer carries no id", req.Name) + if got == nil || got.ID == 0 { + return nil, fmt.Errorf("creating label %q: the tracker's answer carries no id", opt.Name) } - return &got, nil + return got, nil } // EditLabel patches an existing label by id. -func (c *Client) EditLabel(id int64, req wire.LabelRequest) (*wire.Label, error) { - var got wire.Label - body := &Body{Name: "label-" + req.Name, Data: req} - if err := c.Call(http.MethodPatch, c.repoPathf("labels/%d", id), body, &got); err != nil { +// +// It takes the same spec a create takes, and sends every field of it. The SDK +// spells an edit with pointers, where nil means "leave it alone" — but a label +// edit is rare enough that sending the unchanged name and description along +// costs nothing and removes a way to lose them on a server that reads an absent +// field as empty. The caller decides what the label should BE; this makes the +// tracker say that and nothing less. +func (c *Client) EditLabel(id int64, opt sdk.CreateLabelOption) (*sdk.Label, error) { + owner, repo := c.owned() + c.dump.label("label-" + opt.Name) + got, resp, err := c.api.EditLabel(owner, repo, id, sdk.EditLabelOption{ + Name: &opt.Name, + Color: &opt.Color, + Description: &opt.Description, + Exclusive: &opt.Exclusive, + }) + if err := fail(resp, err); err != nil { return nil, err } - return &got, nil + return got, nil } // ListMilestones is every milestone in the repository, open and closed. @@ -53,8 +71,15 @@ func (c *Client) EditLabel(id int64, req wire.LabelRequest) (*wire.Label, error) // Both states, always: a milestone is closed the moment its work is done, and a // listing that hid those would fail to resolve exactly the filter somebody // types when they want to see what was in it. -func (c *Client) ListMilestones() ([]wire.Milestone, error) { - return paginate[wire.Milestone](c, c.repoPath("milestones?state=all"), 100) +func (c *Client) ListMilestones() ([]*sdk.Milestone, error) { + owner, repo := c.owned() + return paginate(func(page, limit int) ([]*sdk.Milestone, error) { + got, resp, err := c.api.ListRepoMilestones(owner, repo, sdk.ListMilestoneOption{ + ListOptions: listOptions(page, limit), + State: sdk.StateAll, + }) + return got, fail(resp, err) + }, 100) } // ResolveMilestone finds a milestone by id or by title, and fails when there is @@ -64,14 +89,19 @@ func (c *Client) ListMilestones() ([]wire.Milestone, error) { // Gitea silently ignores a `milestones=` filter it cannot resolve and answers // with the entire backlog. A typo in a milestone name would otherwise read as // "your milestone has 300 issues in it". -func (c *Client) ResolveMilestone(value string) (*wire.Milestone, error) { +// +// Against the whole listing rather than the SDK's GetMilestoneByName, because a +// failure has to say what the repository actually HAS — and because that helper +// matches case-insensitively, which would resolve two different milestones to +// one on a repository that has both. +func (c *Client) ResolveMilestone(value string) (*sdk.Milestone, error) { got, err := c.ListMilestones() if err != nil { return nil, err } - for i := range got { - if got[i].Title == value || strconv.FormatInt(got[i].ID, 10) == value { - return &got[i], nil + for _, m := range got { + if m.Title == value || strconv.FormatInt(m.ID, 10) == value { + return m, nil } } have := make([]string, 0, len(got)) @@ -91,7 +121,7 @@ func (c *Client) ResolveMilestone(value string) (*wire.Milestone, error) { // milestone the tracker does not have is filed without one, because refusing // the whole push over a field the tracker will happily accept as empty helps // nobody. "none" and "" are both "no milestone". -func (c *Client) FindMilestone(title string) (*wire.Milestone, error) { +func (c *Client) FindMilestone(title string) (*sdk.Milestone, error) { if title == "" || title == "none" { return nil, nil } @@ -99,9 +129,9 @@ func (c *Client) FindMilestone(title string) (*wire.Milestone, error) { if err != nil { return nil, err } - for i := range got { - if got[i].Title == title { - return &got[i], nil + for _, m := range got { + if m.Title == title { + return m, nil } } return nil, nil diff --git a/cli/internal/mapping/frompayload.go b/cli/internal/mapping/frompayload.go index fd1d3f5..841c3d3 100644 --- a/cli/internal/mapping/frompayload.go +++ b/cli/internal/mapping/frompayload.go @@ -4,6 +4,9 @@ import ( "slices" "strconv" "strings" + "time" + + sdk "code.gitea.io/sdk/gitea" "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" @@ -39,7 +42,7 @@ type PayloadOptions struct { // transport bookkeeping, and the caller has already read the slug off it to // decide which id to pass. Everything downstream — checkboxes, `#N` references, // what lands on disk — sees the body the author wrote. -func FromPayload(p *wire.Issue, id string, repo wire.Repo, opt PayloadOptions) (*issue.Issue, []int) { +func FromPayload(p *sdk.Issue, id string, repo wire.Repo, opt PayloadOptions) (*issue.Issue, []int) { body := MergeCheckboxState(StripIDMarker(strings.TrimSpace(p.Body)), opt.LocalBody) numbers := NumbersInBody(body) @@ -68,15 +71,15 @@ func FromPayload(p *wire.Issue, id string, repo wire.Repo, opt PayloadOptions) ( // dependency listing answers with issues from elsewhere, and this is the // handle for the copy landing in THIS store. extra := map[string]string{ - GiteaKey: wire.Key{Repo: repo, Number: p.Number}.String(), + GiteaKey: wire.Key{Repo: repo, Number: int(p.Index)}.String(), URLKey: p.HTMLURL, SyncedKey: opt.Synced, } if p.Ref != "" { extra[BranchKey] = p.Ref } - if p.UpdatedAt != "" { - extra[RemoteUpdatedKey] = p.UpdatedAt + if stamp := Stamp(p.Updated); stamp != "" { + extra[RemoteUpdatedKey] = stamp } // Zero comments is not a fact worth a line in the file — every issue that // has never been discussed would carry one. @@ -84,41 +87,84 @@ func FromPayload(p *wire.Issue, id string, repo wire.Repo, opt PayloadOptions) ( extra[CommentsKey] = strconv.Itoa(p.Comments) } - state := p.State + state := string(p.State) if state == "" { state = "open" } - // Appended into nil slices, so an issue with no labels is the same value as - // one loaded from a file — the store's own parser yields nothing, not an - // empty list, and two spellings of "none" is a comparison bug waiting. - var labels []string - for _, l := range p.Labels { - labels = append(labels, l.Name) - } - var assignees []string - for _, a := range p.Assignees { - assignees = append(assignees, a.Login) - } - milestone := "" - if p.Milestone != nil { - milestone = p.Milestone.Title - } - return &issue.Issue{ ID: id, Title: p.Title, Body: body, State: state, - Labels: labels, - Assignees: assignees, - Milestone: milestone, + Labels: LabelNames(p), + Assignees: AssigneeLogins(p), + Milestone: MilestoneTitle(p), Depends: deps, Origin: Origin, Extra: extra, }, unresolved } +// LabelNames are a payload's label names, in the order the tracker listed them. +// +// Appended into a nil slice, so an issue with no labels is the same value as +// one loaded from a file — the store's own parser yields nothing, not an empty +// list, and two spellings of "none" is a comparison bug waiting. The same goes +// for the two below. +func LabelNames(p *sdk.Issue) []string { + var out []string + for _, l := range p.Labels { + if l != nil { + out = append(out, l.Name) + } + } + return out +} + +// AssigneeLogins are a payload's assignees, as logins. +// +// Only the login crosses this boundary — it is the one field of a Gitea user +// that means anything to a command, it is what `assignees:` holds, and a +// display name is not an identity anything can be pushed against. +func AssigneeLogins(p *sdk.Issue) []string { + var out []string + for _, a := range p.Assignees { + if a != nil { + out = append(out, a.UserName) + } + } + return out +} + +// MilestoneTitle is a payload's milestone title, or "" when it has none. The +// domain carries the title; the id exists only long enough to be sent back. +func MilestoneTitle(p *sdk.Issue) string { + if p.Milestone == nil { + return "" + } + return p.Milestone.Title +} + +// Stamp is how a tracker timestamp is written into an issue's metadata, and "" +// for a time the payload did not carry. +// +// The zero time is not a date: an issue whose `updated_at` was absent would +// otherwise be stamped `0001-01-01`, which reads as a fact and is not one. +// +// RFC3339 both ways. These values are written into a file, compared as opaque +// strings and handed back; the SDK parses them into a time.Time on the way in, +// so something has to spell them out again, and the format Gitea sends is the +// format they go back out in. What was true when this was a string end to end — +// that no round trip could change the spelling — is not any more: a timestamp +// with a fraction of a second in it comes back without one. +func Stamp(t time.Time) string { + if t.IsZero() { + return "" + } + return t.Format(time.RFC3339) +} + // NumbersInBody is every `#N` referenced from the body's dependency sections. // Used only to seed `depends:` on the first pull — after that the metadata // field is the graph and the prose is prose. @@ -188,19 +234,26 @@ func MergeCheckboxState(remoteBody, localBody string) string { // RenderComments flattens a comment thread to markdown. Read-only: nothing // writes it back, which is why it may be as lossy as a reader needs. -func RenderComments(comments []wire.Comment) string { +func RenderComments(comments []*sdk.Comment) string { var out []string for _, c := range comments { - day := c.CreatedAt + if c == nil { + continue + } + day := Stamp(c.Created) if len(day) > 10 { day = day[:10] } + who := "" + if c.Poster != nil { + who = c.Poster.UserName + } body := strings.TrimSpace(c.Body) if body == "" { body = "(empty)" } out = append(out, - "## comment "+strconv.FormatInt(c.ID, 10)+" — "+c.User.Login+" — "+day, + "## comment "+strconv.FormatInt(c.ID, 10)+" — "+who+" — "+day, "", body, "") } return strings.Join(out, "\n") diff --git a/cli/internal/mapping/labels.go b/cli/internal/mapping/labels.go index deda354..bcb31cf 100644 --- a/cli/internal/mapping/labels.go +++ b/cli/internal/mapping/labels.go @@ -3,8 +3,9 @@ package mapping import ( "strings" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" - "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) // How the taxonomy is painted in Gitea's UI. A hex code says nothing about what @@ -43,20 +44,22 @@ func LabelColor(name string) string { // LabelSpecs is the request body for each name, in the order given. // -// A wire.LabelRequest and not a shape of this package's own: it is field for -// field what a label create takes, and a second spelling of it would mean the -// bootstrap command copying four fields across on its way to the transport. +// The SDK's own CreateLabelOption and not a shape of this package's own: it is +// field for field what a label create takes, and a second spelling of it would +// mean the bootstrap command copying four fields across on its way to the +// transport. It is what an EDIT is built from too — see Client.EditLabel — so +// one value says what a label should be, whether or not it exists yet. // Exclusivity and meaning come from the domain taxonomy; only the color is // decided here. // // A slice and not a map: the order is the taxonomy's, and a bootstrap prints // its plan in that order — a map would shuffle the plan on every run and make // two identical runs look like different ones. -func LabelSpecs(names []string) []wire.LabelRequest { +func LabelSpecs(names []string) []sdk.CreateLabelOption { ns := exclusiveNamespaces() - out := make([]wire.LabelRequest, 0, len(names)) + out := make([]sdk.CreateLabelOption, 0, len(names)) for _, name := range names { - out = append(out, wire.LabelRequest{ + out = append(out, sdk.CreateLabelOption{ Name: name, Color: LabelColor(name), Description: typeMeaning(name), @@ -72,7 +75,7 @@ func LabelSpecs(names []string) []wire.LabelRequest { // Derived from the domain's own list rather than restated: add a type over in // the taxonomy and the next bootstrap creates it, with no line changing here // except the color it is painted with. -func CanonicalLabelSpecs() []wire.LabelRequest { return LabelSpecs(issue.CanonicalLabels()) } +func CanonicalLabelSpecs() []sdk.CreateLabelOption { return LabelSpecs(issue.CanonicalLabels()) } // exclusiveNamespaces are the namespaces at most one label may come from, read // off the canonical set rather than listed again — the domain publishes exactly diff --git a/cli/internal/mapping/layering_test.go b/cli/internal/mapping/layering_test.go index 8d74f8a..43045c9 100644 --- a/cli/internal/mapping/layering_test.go +++ b/cli/internal/mapping/layering_test.go @@ -1,26 +1,35 @@ package mapping import ( + "os" "os/exec" + "path/filepath" "strings" "testing" ) +// sdkPath is the one third-party import this package is allowed: the payload +// shapes it translates to and from. +const sdkPath = "code.gitea.io/sdk/gitea" + // The bridge translates values and nothing else: no network, no filesystem, no // clock, no configuration. Every one of those is a caller's to supply, which is // what lets this package be reasoned about and tested without a Gitea anywhere. // -// Two imports and no more: internal/issue for what an issue is, and -// internal/wire for the shapes on the other side. wire is allowed precisely -// because it is inert — shapes and identifiers over the standard library, with -// a layering test of its own — so naming a payload here costs nothing and -// reaches nowhere. +// THE RULE THIS TEST USED TO MAKE was stronger and is no longer true. The +// shapes lived in internal/wire, which imported the standard library and +// nothing else, so "the bridge cannot reach a transport" held by construction: +// there was nothing in its dependency graph that could open a socket. The SDK's +// types come with the SDK's client attached, so the graph now contains an HTTP +// client whatever this package does with it — and a test that claimed otherwise +// would be a test that lies. // -// DIRECT imports, not the dependency walk internal/issue does. The domain -// reaches os through internal/project and that is the domain's business; what -// this test is about is what this package itself reaches for. A transport that -// grew a helper here — or a lookup that quietly opened a config file — is what -// it catches. +// So it asserts the part that survives, which is also the part that catches a +// real mistake: what THIS package reaches for. DIRECT imports, not the +// dependency walk internal/issue does — the domain reaches os through +// internal/project and that is the domain's business. A transport that grew a +// helper here, or a lookup that quietly opened a config file, is what this +// catches, and it still fails on `os`, on `net/http` and on internal/gitea. func TestTheBridgeTranslatesAndNothingElse(t *testing.T) { forbidden := map[string]string{ "net/http": "an HTTP call belongs in the transport", @@ -28,19 +37,78 @@ func TestTheBridgeTranslatesAndNothingElse(t *testing.T) { "os": "a pure function reads no file and no environment", "os/exec": "nothing here shells out", "io/ioutil": "a pure function reads no file", - "time": "the clock is the caller's; a timestamp arrives as a string", "git.noodles.cam/claude-skills/marketplace/cli/internal/gitea": "the transport imports this package, never the reverse", "git.noodles.cam/claude-skills/marketplace/cli/internal/config": "credentials and repositories are the transport's", "git.noodles.cam/claude-skills/marketplace/cli/internal/project": "nothing here resolves a path", } + allowed := map[string]bool{ + sdkPath: true, + "git.noodles.cam/claude-skills/marketplace/cli/internal/issue": true, + "git.noodles.cam/claude-skills/marketplace/cli/internal/wire": true, + } + for _, dep := range directImports(t) { + if why, bad := forbidden[dep]; bad { + t.Errorf("mapping imports %s — %s", dep, why) + continue + } + // A standard-library import path has no dot in its first element, + // because it has no domain name in front of it. Everything else has to + // be named above: one third party is a decision, two is a habit. + first, _, _ := strings.Cut(dep, "/") + if strings.Contains(first, ".") && !allowed[dep] { + t.Errorf("mapping imports %s — the only payload vocabulary here is %s", dep, sdkPath) + } + } +} + +// The clock is the caller's, and `time` alone can no longer say so: the SDK +// hands over a time.Time, so this package imports the package to format one +// back into the string an issue file holds. What it must never do is ASK what +// time it is — a `synced:` stamped here would be stamped at translation rather +// than at the write it describes, and two issues pushed in one run would carry +// two different times for one run. +// +// The source, then, rather than the import graph: the difference between +// formatting a timestamp and having a clock is not visible in `go list`. +func TestTheBridgeHasNoClock(t *testing.T) { + for _, path := range sourceFiles(t) { + raw, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + for _, banned := range []string{"time.Now(", "time.Since(", "time.Until("} { + if strings.Contains(string(raw), banned) { + t.Errorf("%s calls %s) — the clock belongs to the caller", filepath.Base(path), banned) + } + } + } +} + +func directImports(t *testing.T) []string { + t.Helper() out, err := exec.Command("go", "list", "-f", `{{join .Imports "\n"}}`, ".").Output() if err != nil { t.Fatalf("go list: %v", err) } - for _, dep := range strings.Fields(string(out)) { - if why, bad := forbidden[dep]; bad { - t.Errorf("mapping imports %s — %s", dep, why) - } - } + return strings.Fields(string(out)) +} + +// sourceFiles is this package's own .go files, tests excluded: a test may look +// at a clock, and one of them does. +func sourceFiles(t *testing.T) []string { + t.Helper() + out, err := exec.Command("go", "list", "-f", `{{range .GoFiles}}{{.}} +{{end}}`, ".").Output() + if err != nil { + t.Fatalf("go list: %v", err) + } + var paths []string + for _, name := range strings.Fields(string(out)) { + paths = append(paths, name) + } + if len(paths) == 0 { + t.Fatal("go list named no source files — this test would pass on an empty package") + } + return paths } diff --git a/cli/internal/mapping/mapping.go b/cli/internal/mapping/mapping.go index f67c72c..08641fc 100644 --- a/cli/internal/mapping/mapping.go +++ b/cli/internal/mapping/mapping.go @@ -1,5 +1,5 @@ -// Package mapping is md <-> Gitea JSON. The whole translation, and only the -// translation. +// Package mapping is md <-> Gitea's payloads. The whole translation, and only +// the translation. // // Pure functions: no network, no filesystem, no flags, no clock. Give it a // payload and it hands back a domain issue; give it an issue and it hands back @@ -7,13 +7,21 @@ // tested without a Gitea anywhere, and it is the one package to open when the // two representations disagree. // -// Direction of knowledge: this package imports the domain and the protocol -// (internal/wire), and nothing imports it but the command layer. The domain -// never imports it, and TestDomainDependsOnNothing over in internal/issue fails -// the moment it does; the transport never imports it either, and +// Direction of knowledge: this package imports the domain and the Gitea SDK, +// and nothing imports it but the command layer. The domain never imports it, +// and TestDomainDependsOnNothing over in internal/issue fails the moment it +// does; the transport never imports it either, and // TestTransportDoesNotImportTheDomain over in internal/gitea says so. Both -// sides speak wire's shapes, which is what lets the two meet without either one -// reaching into the other. +// sides speak the SDK's shapes, which is what lets the two meet without either +// one reaching into the other. +// +// WHAT THAT COSTS, SAID OUT LOUD. The shapes used to be internal/wire's, a +// package that imported the standard library and nothing else, so "the bridge +// cannot reach the network" was a fact about the import graph. code.gitea.io/ +// sdk/gitea carries an HTTP client, so it is not any more. What is still true +// is that nothing HERE does I/O, and layering_test.go asserts the version of +// the rule that can still be checked: no os, no net/http, no transport, no +// configuration, no clock, and no third party but the SDK. // // What crosses the boundary, and what does not: // diff --git a/cli/internal/mapping/mapping_test.go b/cli/internal/mapping/mapping_test.go index aff7aa9..ebb184e 100644 --- a/cli/internal/mapping/mapping_test.go +++ b/cli/internal/mapping/mapping_test.go @@ -5,6 +5,9 @@ import ( "reflect" "strings" "testing" + "time" + + sdk "code.gitea.io/sdk/gitea" "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" @@ -14,6 +17,17 @@ import ( // handle in `gitea:` is a key, and a key is a repository and a number. var tea = wire.Repo{Owner: "claude-skills", Name: "tea"} +// when parses a tracker timestamp the way the SDK hands one over, so a fixture +// can be written in the spelling Gitea actually sends. +func when(t *testing.T, s string) time.Time { + t.Helper() + got, err := time.Parse(time.RFC3339, s) + if err != nil { + t.Fatalf("parsing %q: %v", s, err) + } + return got +} + // A file exactly as the store holds it: domain fields, then the sync fields the // domain carries and never reads. const stored = `--- @@ -58,51 +72,60 @@ func roundTripOptions() RequestOptions { // preserved comes back, and the body comes back byte for byte. func TestRoundTripPreservesEveryFieldTheFormatKeeps(t *testing.T) { local := issue.FromText(stored, "wire-sqlc-appclick") - req := ToRequest(local, roundTripOptions()) + req := ToCreate(local, roundTripOptions()) - if req.Title == nil || *req.Title != local.Title { - t.Errorf("title = %v, want %q", req.Title, local.Title) + if req.Title != local.Title { + t.Errorf("title = %q, want %q", req.Title, local.Title) } - if req.Body == nil { + if req.Body == "" { t.Fatal("the request carries no body — a create would file an empty issue") } - if got := IDInBody(*req.Body); got != local.ID { + if got := IDInBody(req.Body); got != local.ID { t.Errorf("the request body does not claim the slug: %q", got) } - if got := StripIDMarker(*req.Body); got != strings.TrimSpace(local.Body) { + if got := StripIDMarker(req.Body); got != strings.TrimSpace(local.Body) { t.Errorf("the prose was rewritten on the way up:\n--- got ---\n%s\n--- want ---\n%s", got, strings.TrimSpace(local.Body)) } - if want := []int64{11, 12}; req.Labels == nil || !reflect.DeepEqual(*req.Labels, want) { + if want := []int64{11, 12}; !reflect.DeepEqual(req.Labels, want) { t.Errorf("labels = %v, want %v", req.Labels, want) } - if want := []string{"naudachu"}; req.Assignees == nil || !reflect.DeepEqual(*req.Assignees, want) { + if want := []string{"naudachu"}; !reflect.DeepEqual(req.Assignees, want) { t.Errorf("assignees = %v, want %v", req.Assignees, want) } - if req.Milestone == nil || *req.Milestone != 5 { + if req.Milestone != 5 { t.Errorf("milestone = %v, want 5", req.Milestone) } - if req.State == nil || *req.State != "open" { - t.Errorf("state = %v", req.State) + if req.Ref != "feat/wire-sqlc" { + t.Errorf("ref = %q — branch: is a sync field and must ride along", req.Ref) } - if req.Ref == nil || *req.Ref != "feat/wire-sqlc" { - t.Errorf("ref = %v — branch: is a sync field and must ride along", req.Ref) + // An edit is the other half of the same translation, and the two must not + // disagree about the issue they describe. + edit := ToEdit(local, roundTripOptions()) + if edit.Body == nil || *edit.Body != req.Body || edit.Title != req.Title { + t.Errorf("a create and an edit describe different issues: %q / %v", edit.Title, edit.Body) + } + if edit.State == nil || *edit.State != sdk.StateOpen { + t.Errorf("state = %v", edit.State) + } + if edit.Ref == nil || *edit.Ref != "feat/wire-sqlc" { + t.Errorf("ref = %v — branch: is a sync field and must ride along", edit.Ref) } // What the tracker hands back is the body it was given, plus its own // bookkeeping. - echo := &wire.Issue{ - Number: 42, - Title: *req.Title, - Body: *req.Body, - State: "open", + echo := &sdk.Issue{ + Index: 42, + Title: req.Title, + Body: req.Body, + State: sdk.StateOpen, HTMLURL: "https://git.noodles.cam/claude-skills/tea/issues/42", - UpdatedAt: "2026-08-09T18:24:01Z", - Ref: *req.Ref, + Updated: when(t, "2026-08-09T18:24:01Z"), + Ref: req.Ref, Comments: 3, - Labels: []wire.Label{{Name: "type/task"}, {Name: "tech/sql"}}, - Assignees: []wire.User{{Login: "naudachu"}}, - Milestone: &wire.Milestone{ID: 5, Title: "v0.2"}, + Labels: []*sdk.Label{{Name: "type/task"}, {Name: "tech/sql"}}, + Assignees: []*sdk.User{{UserName: "naudachu"}}, + Milestone: &sdk.Milestone{ID: 5, Title: "v0.2"}, } back, unresolved := FromPayload(echo, local.ID, tea, PayloadOptions{ IDForNumber: map[int]string{7: "migrate-schema"}, @@ -153,59 +176,72 @@ func TestRoundTripPreservesEveryFieldTheFormatKeeps(t *testing.T) { // And the strongest form of "no churn": pushing what came back sends // exactly what was sent the first time. - if again := ToRequest(back, roundTripOptions()); !reflect.DeepEqual(again, req) { + if again := ToCreate(back, roundTripOptions()); !reflect.DeepEqual(again, req) { t.Errorf("a second push differs from the first:\n--- again ---\n%+v\n--- first ---\n%+v", again, req) } } // The other shape an issue comes in: nothing scheduled, nobody assigned. +// +// On an EDIT that is a statement and not an absence, which is why this asserts +// on the bytes. Gitea reads a null as "no opinion" and a value as "make it +// this", and the SDK's edit body sends every key — so `"assignees":null` is the +// spelling that leaves the tracker's assignees alone, and `"assignees":[]` +// would clear them. func TestNoMilestoneAndNoAssignees(t *testing.T) { local := issue.FromText("---\nid: lone\nstate: open\nlabels: [type/task]\n"+ "assignees: []\nmilestone: none\ndepends: []\norigin: local\n---\n"+ "# A lone issue\n\n## Summary\nОдин.\n", "lone") - req := ToRequest(local, RequestOptions{LabelIDs: map[string]int64{"type/task": 11}}) - if req.Assignees != nil { - t.Errorf("assignees = %v — an empty list would clear whoever the tracker has", req.Assignees) + opt := RequestOptions{LabelIDs: map[string]int64{"type/task": 11}} + edit := ToEdit(local, opt) + if edit.Assignees != nil { + t.Errorf("assignees = %v — an empty list would clear whoever the tracker has", edit.Assignees) } - if req.Milestone != nil { - t.Errorf("milestone = %v — a missing milestone is no opinion, not a detach", req.Milestone) + if edit.Milestone != nil { + t.Errorf("milestone = %v — a missing milestone is no opinion, not a detach", edit.Milestone) } - raw, err := json.Marshal(req) + raw, err := json.Marshal(edit) if err != nil { t.Fatalf("marshal: %v", err) } body := string(raw) - for _, key := range []string{`"assignees"`, `"milestone"`, `"state"`, `"ref"`} { - if strings.Contains(body, key) { - t.Errorf("%s is in the request body; on a PATCH that overwrites what the tracker holds: %s", key, body) + for _, key := range []string{`"assignees":null`, `"milestone":null`, `"state":null`, `"ref":null`} { + if !strings.Contains(body, key) { + t.Errorf("%s is not in the edit body; anything else there overwrites what the tracker holds: %s", key, body) } } - // A resolved-but-empty label set is the opposite statement and must be sent. - if !strings.Contains(body, `"labels":[11]`) { - t.Errorf("labels missing from %s", body) - } - empty, err := json.Marshal(ToRequest(local, RequestOptions{LabelIDs: map[string]int64{}})) - if err != nil { - t.Fatalf("marshal: %v", err) - } - if !strings.Contains(string(empty), `"labels":[]`) { - t.Errorf("a resolved label set that matched nothing must still be sent as []: %s", empty) - } - silent, err := json.Marshal(ToRequest(local, RequestOptions{})) - if err != nil { - t.Fatalf("marshal: %v", err) - } - if strings.Contains(string(silent), `"labels"`) { - t.Errorf("a caller that resolved no ids must not clear the tracker's labels: %s", silent) + // The title is the one field of an edit that is not a pointer. Gitea reads + // an empty one as "leave it alone" too, but this issue has a title and it + // has to go up. + if !strings.Contains(body, `"title":"A lone issue"`) { + t.Errorf("the title is missing from %s", body) } - back, unresolved := FromPayload(&wire.Issue{ - Number: 9, + // A create is the opposite: there is nothing on the tracker's side to + // overwrite, so the resolved label ids are sent as they stand. + created, err := json.Marshal(ToCreate(local, opt)) + if err != nil { + t.Fatalf("marshal: %v", err) + } + if !strings.Contains(string(created), `"labels":[11]`) { + t.Errorf("labels missing from %s", created) + } + // A resolved label set that matched nothing is still an answer, and it is + // the same answer a PUT sends at an issue that already exists. + if got := LabelIDsFor(local, RequestOptions{LabelIDs: map[string]int64{}}); got == nil || len(got) != 0 { + t.Errorf("a resolved label set that matched nothing must be an empty list, got %v", got) + } + if got := LabelIDsFor(local, RequestOptions{}); got != nil { + t.Errorf("a caller that resolved no ids has no opinion about labels, got %v", got) + } + + back, unresolved := FromPayload(&sdk.Issue{ + Index: 9, Title: "A lone issue", Body: WithIDMarker("## Summary\nОдин.", "lone"), - State: "open", + State: sdk.StateOpen, HTMLURL: "https://git.noodles.cam/claude-skills/tea/issues/9", }, "lone", tea, PayloadOptions{Synced: "2026-08-11T10:00:00Z"}) @@ -231,7 +267,7 @@ func TestNoMilestoneAndNoAssignees(t *testing.T) { // a made-up slug is an edge to a file that does not exist. func TestUnresolvedNumbersAreReportedNotInvented(t *testing.T) { body := "## Summary\nx\n\n## Depends on\n- #7\n- #8\n" - back, unresolved := FromPayload(&wire.Issue{Number: 1, Title: "T", Body: body}, + back, unresolved := FromPayload(&sdk.Issue{Index: 1, Title: "T", Body: body}, "here", wire.Repo{Owner: "o", Name: "r"}, PayloadOptions{IDForNumber: map[int]string{7: "known"}}) if want := []string{"known"}; !reflect.DeepEqual(back.Depends, want) { @@ -247,7 +283,7 @@ func TestUnresolvedNumbersAreReportedNotInvented(t *testing.T) { func TestExtraNumbersJoinTheOnesTheBodyNames(t *testing.T) { body := "## Summary\nx\n\n## Depends on\n- #7\n" - back, _ := FromPayload(&wire.Issue{Number: 1, Title: "T", Body: body}, "here", wire.Repo{Owner: "o", Name: "r"}, + back, _ := FromPayload(&sdk.Issue{Index: 1, Title: "T", Body: body}, "here", wire.Repo{Owner: "o", Name: "r"}, PayloadOptions{ IDForNumber: map[int]string{7: "seven", 9: "nine"}, ExtraNumbers: []int{7, 9}, @@ -352,10 +388,10 @@ func TestNumberOf(t *testing.T) { func TestApplyRemoteStampsTheSyncFields(t *testing.T) { local := &issue.Issue{ID: "x", Origin: issue.Local} - ApplyRemote(local, &wire.Issue{ - Number: 42, - HTMLURL: "https://git.noodles.cam/o/r/issues/42", - UpdatedAt: "2026-08-09T18:24:01Z", + ApplyRemote(local, &sdk.Issue{ + Index: 42, + HTMLURL: "https://git.noodles.cam/o/r/issues/42", + Updated: when(t, "2026-08-09T18:24:01Z"), }, wire.Repo{Owner: "o", Name: "r"}, "2026-08-11T10:00:00Z") if local.IsLocal() { @@ -368,13 +404,16 @@ func TestApplyRemoteStampsTheSyncFields(t *testing.T) { } } +// A thread is flattened for a reader, so it may be as lossy as a reader needs — +// but a payload that carries no date and no author still renders, because a +// comment that is there is worth showing whatever the tracker left out of it. func TestRenderComments(t *testing.T) { - got := RenderComments([]wire.Comment{ - {ID: 1, User: wire.User{Login: "naudachu"}, CreatedAt: "2026-08-09T18:24:01Z", Body: " привет "}, - {ID: 2, User: wire.User{Login: "bot"}, CreatedAt: "", Body: ""}, + got := RenderComments([]*sdk.Comment{ + {ID: 1, Poster: &sdk.User{UserName: "naudachu"}, Created: when(t, "2026-08-09T18:24:01Z"), Body: " привет "}, + {ID: 2, Poster: nil, Body: ""}, }) want := "## comment 1 — naudachu — 2026-08-09\n\nпривет\n\n" + - "## comment 2 — bot — \n\n(empty)\n" + "## comment 2 — — \n\n(empty)\n" if got != want { t.Errorf("got:\n%q\nwant:\n%q", got, want) } diff --git a/cli/internal/mapping/marker_test.go b/cli/internal/mapping/marker_test.go index bdba674..cfd53d6 100644 --- a/cli/internal/mapping/marker_test.go +++ b/cli/internal/mapping/marker_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" + sdk "code.gitea.io/sdk/gitea" ) func TestIDInBodyReadsBothSpellings(t *testing.T) { @@ -44,7 +44,7 @@ func TestAnIssuePushedByTheOldNameStillResolves(t *testing.T) { t.Fatalf("IDInBody = %q — every issue pushed under the old name would be orphaned", id) } - iss, _ := FromPayload(&wire.Issue{Number: 42, Title: "Wire sqlc", Body: inTracker}, + iss, _ := FromPayload(&sdk.Issue{Index: 42, Title: "Wire sqlc", Body: inTracker}, id, tea, PayloadOptions{}) if strings.Contains(iss.Body, "tea:id") { t.Errorf("the old marker reached the local copy: %q", iss.Body) @@ -55,7 +55,7 @@ func TestAnIssuePushedByTheOldNameStillResolves(t *testing.T) { // And the next push rewrites it into the current spelling, without ever // having two. - up := *ToRequest(iss, RequestOptions{}).Body + up := ToCreate(iss, RequestOptions{}).Body if !strings.HasPrefix(up, "\n\n") { t.Errorf("the marker was not rewritten: %q", up) } diff --git a/cli/internal/mapping/torequest.go b/cli/internal/mapping/torequest.go index eb91e81..bedb491 100644 --- a/cli/internal/mapping/torequest.go +++ b/cli/internal/mapping/torequest.go @@ -4,6 +4,8 @@ import ( "slices" "strings" + sdk "code.gitea.io/sdk/gitea" + "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) @@ -17,21 +19,24 @@ import ( // here — this package never learns which repository it is translating for // beyond the name it is handed. type RequestOptions struct { - // LabelIDs is name -> id for the labels this repository holds. A nil map - // leaves `labels` out of the request; a non-nil one sends the list, empty - // included, and a label the repository does not have is silently left off - // rather than failing the write — an unknown label is a bootstrap that has - // not run, not a reason to lose the issue. + // LabelIDs is name -> id for the labels this repository holds. A label the + // repository does not have is silently left off rather than failing the + // write — an unknown label is a bootstrap that has not run, not a reason to + // lose the issue. + // + // It is read by ToCreate and ignored by ToEdit, because Gitea's edit + // endpoint carries no labels at all. Changing them on an issue that exists + // is a PUT of its own; push makes it. LabelIDs map[string]int64 // MilestoneID is the resolved milestone. nil leaves the key out, which on // an edit means "leave whatever is attached alone". MilestoneID *int64 - // IncludeState sends `state`. An edit that means to open or close says so; - // a create takes the tracker's default. + // IncludeState sends `state` on an edit. An edit that means to open or + // close says so; a create takes the tracker's default. IncludeState bool } -// ToRequest is the request body for creating or editing an issue. +// ToCreate is the body of a create. // // The prose is sent verbatim — see the package doc on why slugs in // `## Depends on` are not rewritten to `#N`. The one addition is the id marker, @@ -39,59 +44,100 @@ type RequestOptions struct { // deleted the local file. FromPayload takes it straight back off, so the body // still round-trips byte for byte. // -// A create needs a title and a body, so those two are always filled. Every -// other key is left out unless the caller has an opinion about it: on a PATCH -// an absent key leaves the tracker's value alone, and a present one overwrites -// it — see wire.IssueRequest for what each of them clears when it is sent -// empty. -func ToRequest(i *issue.Issue, opt RequestOptions) *wire.IssueRequest { - r := &wire.IssueRequest{ - Title: wire.Set(i.Title), - Body: wire.Set(WithIDMarker(strings.TrimSpace(i.Body), i.ID)), - } - if opt.LabelIDs != nil { - ids := []int64{} - for _, name := range i.Labels { - if id, ok := opt.LabelIDs[name]; ok { - ids = append(ids, id) - } - } - r.Labels = &ids - } - // Copied, so the request body and the issue it came from cannot alias one - // slice: whatever a caller does to either afterwards is not a change to - // what was sent. - if len(i.Assignees) > 0 { - r.Assignees = wire.Set(slices.Clone(i.Assignees)) +// Every field of a create is a value and every one of them is sent, which is +// safe in a way an edit is not: there is nothing on the tracker's side yet for +// an empty field to overwrite. +func ToCreate(i *issue.Issue, opt RequestOptions) sdk.CreateIssueOption { + out := sdk.CreateIssueOption{ + Title: i.Title, + Body: WithIDMarker(strings.TrimSpace(i.Body), i.ID), + // Copied, so the request body and the issue it came from cannot alias + // one slice: whatever a caller does to either afterwards is not a + // change to what was sent. + Assignees: slices.Clone(i.Assignees), + Labels: LabelIDsFor(i, opt), + Ref: strings.TrimSpace(i.Extra[BranchKey]), } if opt.MilestoneID != nil { - r.Milestone = opt.MilestoneID + out.Milestone = *opt.MilestoneID + } + return out +} + +// ToEdit is the body of an edit. +// +// EVERY FIELD IS A POINTER AND MOST OF THEM ARE LEFT NIL, because Gitea reads +// an absent value as "no opinion" and a present one as "make it this", and the +// difference is not academic: an empty `ref` CLEARS the branch an issue is +// pinned to, and an empty `assignees` clears its assignees. A caller meaning to +// change only the state would do both by accident with plain zero values. +// +// The exception is the title, which the SDK spells as a plain string and always +// sends. Gitea reads an EMPTY title as "leave it alone" — it is the one field +// of an edit where the zero value already means no opinion — so a caller that +// wants to rename says so by filling it, and `kettle close` stays a change of +// state and nothing else. +func ToEdit(i *issue.Issue, opt RequestOptions) sdk.EditIssueOption { + out := sdk.EditIssueOption{ + Title: i.Title, + Body: sdk.OptionalString(WithIDMarker(strings.TrimSpace(i.Body), i.ID)), + Milestone: opt.MilestoneID, + } + if len(i.Assignees) > 0 { + out.Assignees = slices.Clone(i.Assignees) } if opt.IncludeState { - r.State = wire.Set(i.State) + state := sdk.StateType(i.State) + out.State = &state } // An empty `branch:` is "no opinion", not "no branch": sending ref="" would // clear whatever is set on the Gitea side, so the key is left out instead. if branch := strings.TrimSpace(i.Extra[BranchKey]); branch != "" { - r.Ref = wire.Set(branch) + out.Ref = sdk.OptionalString(branch) } - return r + return out +} + +// LabelIDsFor is the ids this issue's labels resolve to, in the issue's own +// order — what a create sends, and what a push PUTs at an issue whose labels +// have to be made to match afterwards. +// +// One answer to one question, exported so those two cannot derive it +// differently: an edit carries no labels at all, so every issue that already +// exists gets its label set through a PUT, and a PUT that disagreed with what a +// create would have sent would make a pushed issue and a re-pushed one two +// different things. +// +// nil for a caller that resolved no ids, and an empty list — never nil — for +// one that resolved some and matched none. The difference is a statement to the +// tracker: `[]` clears every label on the issue. +func LabelIDsFor(i *issue.Issue, opt RequestOptions) []int64 { + if opt.LabelIDs == nil { + return nil + } + ids := []int64{} + for _, name := range i.Labels { + if id, ok := opt.LabelIDs[name]; ok { + ids = append(ids, id) + } + } + return ids } // ApplyRemote stamps the sync-owned fields onto an issue after a successful // write. Mutates and returns it; `origin` is the one domain field this touches, // and it touches it because "this work exists somewhere else now" is exactly // what has just become true. -func ApplyRemote(i *issue.Issue, p *wire.Issue, repo wire.Repo, synced string) *issue.Issue { +func ApplyRemote(i *issue.Issue, p *sdk.Issue, repo wire.Repo, synced string) *issue.Issue { if i.Extra == nil { i.Extra = map[string]string{} } i.Origin = Origin - i.Extra[GiteaKey] = wire.Key{Repo: repo, Number: p.Number}.String() + i.Extra[GiteaKey] = wire.Key{Repo: repo, Number: int(p.Index)}.String() i.Extra[URLKey] = p.HTMLURL i.Extra[SyncedKey] = synced - if p.UpdatedAt != "" { - i.Extra[RemoteUpdatedKey] = p.UpdatedAt + if stamp := Stamp(p.Updated); stamp != "" { + i.Extra[RemoteUpdatedKey] = stamp } return i } diff --git a/cli/internal/wire/key.go b/cli/internal/wire/key.go index eec8062..191156e 100644 --- a/cli/internal/wire/key.go +++ b/cli/internal/wire/key.go @@ -1,3 +1,25 @@ +// Package wire is the protocol's identifiers: the way this project addresses +// one repository and one issue, and nothing else. +// +// The JSON shapes used to live here too, because the transport and the bridge +// both had to name a Gitea issue and neither may import the other. They are +// code.gitea.io/sdk/gitea's now — one vocabulary, maintained by the people who +// maintain the server — and the reason the shapes were lifted out of the +// transport in the first place still holds: two structs for one payload drift, +// and the first field only one of them learns is a field the other silently +// drops. +// +// WHAT THE SDK HAS NO ANSWER FOR IS ADDRESSING. `42`, `#42`, `owner/repo#42` +// and an issue URL are four spellings of one thing, all four are what somebody +// has in hand, and the SDK takes an owner, a name and an int64 — it never +// parses. So the parsing stays, and so does the pair of types it produces: Repo +// and Key, the values that go into the ledger, into the `gitea:` metadata field +// and into every receipt. +// +// THIS PACKAGE IMPORTS THE STANDARD LIBRARY AND NOTHING ELSE — no HTTP, no +// filesystem, no configuration, no SDK, and above all not internal/issue. An +// identifier that reached for any of those would drag every user of it into +// that layer. layering_test.go fails the moment it stops being true. package wire import ( diff --git a/cli/internal/wire/layering_test.go b/cli/internal/wire/layering_test.go index d8399e9..ff295b8 100644 --- a/cli/internal/wire/layering_test.go +++ b/cli/internal/wire/layering_test.go @@ -6,12 +6,12 @@ import ( "testing" ) -// The protocol is shared by two layers that may not import each other, and it -// can only be shared because it reaches for nothing itself: no domain, no -// configuration, no path resolution, no third party. One import from any of -// those would drag every user of this package into that layer — which is the -// whole reason these shapes were lifted out of the transport rather than left -// there for the bridge to reimplement. +// The identifiers are shared by two layers that may not import each other, and +// they can only be shared because they reach for nothing themselves: no domain, +// no configuration, no path resolution, no third party — the Gitea SDK +// included. One import from any of those would drag every user of this package +// into that layer, which is the whole reason an issue key is parsed here rather +// than wherever it is first needed. // // The dependency walk, so a helper pulled in three packages deep is caught as // the same violation as one written at the top of a file. @@ -28,7 +28,7 @@ func TestWireDependsOnNothing(t *testing.T) { // because it has no domain name in front of it. first, _, _ := strings.Cut(dep, "/") if strings.Contains(first, ".") { - t.Errorf("the protocol imports %s — these are shapes and identifiers, and nothing else belongs here", dep) + t.Errorf("the protocol imports %s — these are identifiers, and nothing else belongs here", dep) } } } @@ -43,10 +43,10 @@ func TestWireReachesNeitherTheNetworkNorTheDisk(t *testing.T) { forbidden := map[string]string{ "net/http": "an HTTP call belongs in the transport", "net": "an HTTP call belongs in the transport", - "os": "a shape reads no file and no environment", + "os": "an identifier reads no file and no environment", "os/exec": "nothing here shells out", "io": "nothing here is a stream", - "time": "a timestamp crosses as the string the tracker sent", + "time": "an address has no timestamp in it", } out, err := exec.Command("go", "list", "-f", `{{join .Imports "\n"}}`, ".").Output() diff --git a/cli/internal/wire/request.go b/cli/internal/wire/request.go deleted file mode 100644 index d96230a..0000000 --- a/cli/internal/wire/request.go +++ /dev/null @@ -1,59 +0,0 @@ -package wire - -// The bodies that go up, and the shorthand that fills them. -// -// The omitted keys carry meaning of their own on a PATCH: a key that is absent -// leaves the tracker's value alone, and a key that is present overwrites it. So -// "no opinion" and "empty" must not marshal the same way, which is what every -// pointer and every omitempty below is for. - -// IssueRequest is the body of a create or an edit. -// -// Every field is a pointer because Gitea reads an absent key as "no opinion" -// and a present one as "make it this", and the difference is not academic: an -// empty `ref` CLEARS the branch an issue is pinned to, and an empty `labels` -// clears its labels. A caller meaning to change only the state would do both by -// accident with plain zero values. Set fills a field; leaving it nil leaves the -// tracker's copy alone. -type IssueRequest struct { - Title *string `json:"title,omitempty"` - Body *string `json:"body,omitempty"` - // Labels is a pointer because `[]` is a statement — it clears every label - // on the issue — while a caller that has not resolved label ids at all has - // no business making it. A plain slice with omitempty cannot say both. - Labels *[]int64 `json:"labels,omitempty"` - Assignees *[]string `json:"assignees,omitempty"` - // Milestone is a pointer for the same reason, and because 0 is Gitea's - // "detach from its milestone" — a value somebody may well mean. - Milestone *int64 `json:"milestone,omitempty"` - State *string `json:"state,omitempty"` - Ref *string `json:"ref,omitempty"` -} - -// LabelRequest is the body of a label create or edit — everything a repository -// needs to make one label. -// -// Value fields, not pointers, and every one of them is sent: Gitea 1.26 patches -// only what it is given, but an older server reads an absent field as empty and -// blanks it. A label edit is rare enough that sending the unchanged name and -// description along costs nothing and removes a way to lose them. -// -// It goes up as a request body of its own because `tea labels create` could not -// set `exclusive` — the flag that makes `type/*` behave like a single choice — -// which is the whole reason label creation went through the API rather than a -// CLI wrapper. -// -// What a label MEANS — which namespaces are exclusive, what colour a severity -// is — is not decided here. This is the shape; the taxonomy is the domain's and -// the palette is the bridge's. -type LabelRequest struct { - Name string `json:"name"` - Color string `json:"color"` - Description string `json:"description"` - Exclusive bool `json:"exclusive"` -} - -// Set is a pointer to v, for filling the optional fields of a request. Gitea -// reads an absent key as "no opinion" and a present one as "make it this", so -// those fields are pointers and this is the shorthand that fills them. -func Set[T any](v T) *T { return &v } diff --git a/cli/internal/wire/wire.go b/cli/internal/wire/wire.go deleted file mode 100644 index 0e1950f..0000000 --- a/cli/internal/wire/wire.go +++ /dev/null @@ -1,155 +0,0 @@ -// Package wire is the protocol: the JSON shapes a Gitea instance sends and -// takes, the identifiers that address them, and nothing else. -// -// It is a package because two layers need the same vocabulary and neither may -// import the other. internal/gitea is the transport — HTTP verbs, pagination, -// status codes, credentials — and internal/mapping is the bridge — md <-> JSON, -// pure functions, no network. Both have to name a Gitea issue, and when each -// named it with a struct of its own, every command written on top of the two -// would have had to copy a payload field by field from one spelling into the -// other. Two copies of a shape also drift: the first field only one of them -// learns is a field the other silently drops. -// -// THIS PACKAGE IMPORTS THE STANDARD LIBRARY AND NOTHING ELSE — no HTTP, no -// filesystem, no configuration, and above all not internal/issue. That is what -// lets the transport and the bridge share it without either one landing inside -// the other's layer, and layering_test.go fails the moment it stops being true. -// -// Structs and not map[string]any, because the two representations disagreeing -// is the failure this vocabulary exists to make debuggable: a typo in a key is -// a compile error here and a silently dropped field there. Anything Gitea sends -// that is not named below is not read by anybody — decoding is lossy on -// purpose, since the tracker is not the record for anything the domain owns. -package wire - -// User is whoever wrote or was assigned something. -// -// Only the login crosses this boundary — it is the one field of a Gitea user -// that means anything to a command, it is what `assignees:` holds, and a -// display name is not an identity anything can be pushed against. A transport -// that carries the rest invites somebody to use it. -type User struct { - Login string `json:"login"` -} - -// Label as the tracker holds it. -// -// Color is hex. Gitea returns it without the leading `#` (`ee0701`) and accepts -// it either way; both spellings are the same color, so a comparison has to -// strip before it compares. -type Label struct { - ID int64 `json:"id"` - Name string `json:"name"` - Color string `json:"color"` - Description string `json:"description"` - Exclusive bool `json:"exclusive"` -} - -// Milestone as the tracker holds it. The domain carries its title; the id -// exists only long enough to be sent back. -type Milestone struct { - ID int64 `json:"id"` - Title string `json:"title"` - State string `json:"state"` - Description string `json:"description"` -} - -// RepoRef is the repository an issue payload says it belongs to. Present on a -// dependency listing, where the answer may well be another repository. -type RepoRef struct { - Owner string `json:"owner"` - Name string `json:"name"` - FullName string `json:"full_name"` -} - -// PullRequest is non-nil on a row that is a pull request rather than an issue. -// Gitea's issue endpoints return both, and `type=issues` is a filter the server -// has been known to ignore — which is why every listing re-checks it. -type PullRequest struct { - Merged bool `json:"merged"` - HTMLURL string `json:"html_url"` -} - -// Issue is a tracker row: a Gitea issue as the API reports it. -// -// Timestamps stay strings. They are written into an issue's metadata verbatim -// and compared as opaque values; parsing them here would mean formatting them -// back, and a round trip through a time package is a chance to hand the store a -// different string than the tracker sent. -type Issue struct { - Number int `json:"number"` - Title string `json:"title"` - Body string `json:"body"` - State string `json:"state"` - // Ref is the branch the issue is pinned to. - Ref string `json:"ref"` - // HTMLURL and UpdatedAt are the tracker's own bookkeeping and land in the - // domain's Extra untouched. - HTMLURL string `json:"html_url"` - // Comments is a count, not a thread: the thread is fetched separately and - // parked beside the issue as a sidecar. - Comments int `json:"comments"` - Labels []Label `json:"labels"` - Assignees []User `json:"assignees"` - Milestone *Milestone `json:"milestone"` - Repository *RepoRef `json:"repository"` - PullRequest *PullRequest `json:"pull_request"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` -} - -// IsPullRequest reports whether this row is a pull request. -func (i *Issue) IsPullRequest() bool { return i.PullRequest != nil } - -// LabelNames are the label names, in the order the tracker listed them. -func (i *Issue) LabelNames() []string { - out := make([]string, 0, len(i.Labels)) - for _, l := range i.Labels { - out = append(out, l.Name) - } - return out -} - -// AssigneeLogins are the assignees, as logins. -func (i *Issue) AssigneeLogins() []string { - out := make([]string, 0, len(i.Assignees)) - for _, a := range i.Assignees { - out = append(out, a.Login) - } - return out -} - -// MilestoneTitle is the milestone's title, or "" when there is none. -func (i *Issue) MilestoneTitle() string { - if i.Milestone == nil { - return "" - } - return i.Milestone.Title -} - -// KeyIn is this issue's cross-repo handle. The payload's own repository wins -// when it carries one — a dependency listing answers with issues from other -// repositories — and fallback is the repository that was asked. -func (i *Issue) KeyIn(fallback Repo) Key { - repo := fallback - if i.Repository != nil { - if r, err := ParseRepo(i.Repository.FullName); err == nil { - repo = r - } - } - return Key{Repo: repo, Number: i.Number} -} - -// Comment is one entry in an issue's thread. -// -// Read only, in practice: a thread is flattened to markdown for a reader and -// nothing writes that markdown back, which is why the rendering may be as lossy -// as a reader needs. -type Comment struct { - ID int64 `json:"id"` - Body string `json:"body"` - HTMLURL string `json:"html_url"` - User User `json:"user"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` -} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/LICENSE b/cli/vendor/code.gitea.io/sdk/gitea/LICENSE new file mode 100644 index 0000000..10aeba4 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 The Gitea Authors +Copyright (c) 2014 The Gogs Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/cli/vendor/code.gitea.io/sdk/gitea/action_run.go b/cli/vendor/code.gitea.io/sdk/gitea/action_run.go new file mode 100644 index 0000000..91e5bc3 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/action_run.go @@ -0,0 +1,321 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "time" +) + +// ActionTask represents a workflow run task (from /actions/tasks endpoint) +// This is the format returned by older Gitea versions +type ActionTask struct { + ID int64 `json:"id"` + Name string `json:"name"` // Workflow name + HeadBranch string `json:"head_branch"` + HeadSHA string `json:"head_sha"` + RunNumber int64 `json:"run_number"` + Event string `json:"event"` + DisplayTitle string `json:"display_title"` // PR title or commit message + Status string `json:"status"` + WorkflowID string `json:"workflow_id"` // e.g. "ci.yml" + URL string `json:"url"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + RunStartedAt time.Time `json:"run_started_at"` +} + +// ActionTaskResponse holds the response for listing action tasks +type ActionTaskResponse struct { + TotalCount int64 `json:"total_count"` + WorkflowRuns []*ActionTask `json:"workflow_runs"` +} + +// ActionWorkflowRun represents a workflow run (from /actions/runs endpoint) +// This is the format returned by newer Gitea versions +type ActionWorkflowRun struct { + ID int64 `json:"id"` + DisplayTitle string `json:"display_title"` + Event string `json:"event"` + HeadBranch string `json:"head_branch,omitempty"` + HeadSha string `json:"head_sha"` + Path string `json:"path"` + RunAttempt int64 `json:"run_attempt"` + RunNumber int64 `json:"run_number"` + Status string `json:"status"` + Conclusion string `json:"conclusion,omitempty"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + StartedAt time.Time `json:"started_at"` + CompletedAt time.Time `json:"completed_at"` + Actor *User `json:"actor,omitempty"` + TriggerActor *User `json:"trigger_actor,omitempty"` + Repository *Repository `json:"repository,omitempty"` + HeadRepository *Repository `json:"head_repository,omitempty"` + RepositoryID int64 `json:"repository_id,omitempty"` +} + +// ActionWorkflowRunsResponse holds the response for listing workflow runs +type ActionWorkflowRunsResponse struct { + TotalCount int64 `json:"total_count"` + WorkflowRuns []*ActionWorkflowRun `json:"workflow_runs"` +} + +// ActionWorkflowJob represents a job within a workflow run +type ActionWorkflowJob struct { + ID int64 `json:"id"` + RunID int64 `json:"run_id"` + RunURL string `json:"run_url"` + RunAttempt int64 `json:"run_attempt"` + Name string `json:"name"` + HeadBranch string `json:"head_branch,omitempty"` + HeadSha string `json:"head_sha"` + Status string `json:"status"` + Conclusion string `json:"conclusion,omitempty"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + CreatedAt time.Time `json:"created_at"` + StartedAt time.Time `json:"started_at"` + CompletedAt time.Time `json:"completed_at"` + RunnerID int64 `json:"runner_id,omitempty"` + RunnerName string `json:"runner_name,omitempty"` + Labels []string `json:"labels"` + Steps []*ActionWorkflowStep `json:"steps"` +} + +// ActionWorkflowJobsResponse holds the response for listing workflow jobs +type ActionWorkflowJobsResponse struct { + TotalCount int64 `json:"total_count"` + Jobs []*ActionWorkflowJob `json:"jobs"` +} + +// ActionWorkflowStep represents a step within a job +type ActionWorkflowStep struct { + Name string `json:"name"` + Number int64 `json:"number"` + Status string `json:"status"` + Conclusion string `json:"conclusion,omitempty"` + StartedAt time.Time `json:"started_at"` + CompletedAt time.Time `json:"completed_at"` +} + +// ListRepoActionRunsOptions options for listing repository action runs +type ListRepoActionRunsOptions struct { + ListOptions + Branch string // Filter by branch + Event string // Filter by triggering event + Status string // Filter by status (pending, queued, in_progress, failure, success, skipped) + Actor string // Filter by actor (user who triggered the run) + HeadSHA string // Filter by the SHA of the head commit +} + +// QueryEncode encodes the options to URL query parameters +func (opt *ListRepoActionRunsOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.Branch != "" { + query.Add("branch", opt.Branch) + } + if opt.Event != "" { + query.Add("event", opt.Event) + } + if opt.Status != "" { + query.Add("status", opt.Status) + } + if opt.Actor != "" { + query.Add("actor", opt.Actor) + } + if opt.HeadSHA != "" { + query.Add("head_sha", opt.HeadSHA) + } + return query.Encode() +} + +// ListRepoActionJobsOptions options for listing repository action jobs +type ListRepoActionJobsOptions struct { + ListOptions + Status string // Filter by status (pending, queued, in_progress, failure, success, skipped) +} + +// QueryEncode encodes the options to URL query parameters +func (opt *ListRepoActionJobsOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.Status != "" { + query.Add("status", opt.Status) + } + return query.Encode() +} + +// ListRepoActionRuns lists workflow runs for a repository. +// Requires Gitea 1.26.0 or later. For older versions, use ListRepoActionTasks. +func (c *Client) ListRepoActionRuns(owner, repo string, opt ListRepoActionRunsOptions) (*ActionWorkflowRunsResponse, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/runs", owner, repo)) + link.RawQuery = opt.QueryEncode() + + resp := new(ActionWorkflowRunsResponse) + response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp) + return resp, response, err +} + +// GetRepoActionRun gets a single workflow run. +// Requires Gitea 1.26.0 or later. +func (c *Client) GetRepoActionRun(owner, repo string, runID int64) (*ActionWorkflowRun, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + run := new(ActionWorkflowRun) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/runs/%d", owner, repo, runID), jsonHeader, nil, run) + return run, resp, err +} + +// ListRepoActionRunJobs lists jobs for a workflow run. +// Requires Gitea 1.26.0 or later. +func (c *Client) ListRepoActionRunJobs(owner, repo string, runID int64, opt ListRepoActionJobsOptions) (*ActionWorkflowJobsResponse, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/runs/%d/jobs", owner, repo, runID)) + link.RawQuery = opt.QueryEncode() + + resp := new(ActionWorkflowJobsResponse) + response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp) + return resp, response, err +} + +// ListRepoActionJobs lists all jobs for a repository. +// Requires Gitea 1.26.0 or later. +func (c *Client) ListRepoActionJobs(owner, repo string, opt ListRepoActionJobsOptions) (*ActionWorkflowJobsResponse, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/jobs", owner, repo)) + link.RawQuery = opt.QueryEncode() + + resp := new(ActionWorkflowJobsResponse) + response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp) + return resp, response, err +} + +// GetRepoActionJob gets a single job. +// Requires Gitea 1.26.0 or later. +func (c *Client) GetRepoActionJob(owner, repo string, jobID int64) (*ActionWorkflowJob, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + job := new(ActionWorkflowJob) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/jobs/%d", owner, repo, jobID), jsonHeader, nil, job) + return job, resp, err +} + +// GetRepoActionJobLogs gets the logs for a specific job. +// Requires Gitea 1.26.0 or later. +func (c *Client) GetRepoActionJobLogs(owner, repo string, jobID int64) ([]byte, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/jobs/%d/logs", owner, repo, jobID), nil, nil) +} + +// ListRepoActionTasks lists workflow tasks for a repository (Gitea 1.24.x and earlier) +// Use this for older Gitea versions that don't have /actions/runs endpoint +func (c *Client) ListRepoActionTasks(owner, repo string, opt ListOptions) (*ActionTaskResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/tasks", owner, repo)) + link.RawQuery = opt.getURLQuery().Encode() + + resp := new(ActionTaskResponse) + response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp) + return resp, response, err +} + +// DeleteRepoActionRun deletes a workflow run. +// Requires Gitea 1.26.0 or later. +func (c *Client) DeleteRepoActionRun(owner, repo string, runID int64) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/actions/runs/%d", owner, repo, runID), jsonHeader, nil) +} + +// RerunRepoActionRun reruns an entire workflow run. +// Requires Gitea 1.26.0 or later. +func (c *Client) RerunRepoActionRun(owner, repo string, runID int64) (*ActionWorkflowRun, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + run := new(ActionWorkflowRun) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/actions/runs/%d/rerun", owner, repo, runID), jsonHeader, nil, run) + return run, resp, err +} + +// RerunRepoActionRunFailedJobs reruns all failed jobs in a workflow run. +// Requires Gitea 1.26.0 or later. +func (c *Client) RerunRepoActionRunFailedJobs(owner, repo string, runID int64) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/actions/runs/%d/rerun-failed-jobs", owner, repo, runID), jsonHeader, nil) +} + +// RerunRepoActionJob reruns a specific workflow job in a run. +// Requires Gitea 1.26.0 or later. +func (c *Client) RerunRepoActionJob(owner, repo string, runID, jobID int64) (*ActionWorkflowJob, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + job := new(ActionWorkflowJob) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/actions/runs/%d/jobs/%d/rerun", owner, repo, runID, jobID), jsonHeader, nil, job) + return job, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/action_shared.go b/cli/vendor/code.gitea.io/sdk/gitea/action_shared.go new file mode 100644 index 0000000..c967042 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/action_shared.go @@ -0,0 +1,270 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/url" + "time" +) + +// RegistrationToken is returned when creating an Actions runner registration token. +type RegistrationToken struct { + Token string `json:"token"` +} + +// CreateOrUpdateSecretOption contains the data for creating or updating an Actions secret. +type CreateOrUpdateSecretOption struct { + Data string `json:"data"` + Description string `json:"description"` +} + +// Validate checks whether a secret payload can be sent to the API. +func (opt CreateOrUpdateSecretOption) Validate() error { + if len(opt.Data) == 0 { + return errors.New("empty Data field") + } + return nil +} + +// ActionVariable represents an Actions variable. +type ActionVariable struct { + OwnerID int64 `json:"owner_id"` + RepoID int64 `json:"repo_id"` + Name string `json:"name"` + Data string `json:"data"` + Description string `json:"description"` +} + +// CreateActionVariableOption is used to create an Actions variable. +type CreateActionVariableOption struct { + Value string `json:"value"` + Description string `json:"description"` +} + +// Validate checks whether the variable create payload is valid. +func (opt CreateActionVariableOption) Validate() error { + if len(opt.Value) == 0 { + return errors.New("empty Value field") + } + return nil +} + +// UpdateActionVariableOption is used to update an Actions variable. +type UpdateActionVariableOption struct { + Name string `json:"name"` + Value string `json:"value"` + Description string `json:"description"` +} + +// Validate checks whether the variable update payload is valid. +func (opt UpdateActionVariableOption) Validate() error { + if len(opt.Value) == 0 { + return errors.New("empty Value field") + } + return nil +} + +// ListActionRunnersOptions controls runner listing requests. +type ListActionRunnersOptions struct { + ListOptions + Disabled *bool +} + +// QueryEncode turns the runner list options into a query string. +func (opt *ListActionRunnersOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.Disabled != nil { + query.Add("disabled", fmt.Sprintf("%t", *opt.Disabled)) + } + return query.Encode() +} + +// ActionRunnerLabel represents a runner label. +type ActionRunnerLabel struct { + ID int64 `json:"id"` + Name string `json:"name"` + Type string `json:"type"` +} + +// ActionRunner represents an Actions runner. +type ActionRunner struct { + ID int64 `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Busy bool `json:"busy"` + Disabled bool `json:"disabled"` + Ephemeral bool `json:"ephemeral"` + Labels []*ActionRunnerLabel `json:"labels"` +} + +// EditActionRunnerOption contains editable runner fields. +type EditActionRunnerOption struct { + Disabled *bool `json:"disabled"` +} + +// Validate checks whether the runner update payload is valid. +func (opt EditActionRunnerOption) Validate() error { + if opt.Disabled == nil { + return errors.New("nil Disabled field") + } + return nil +} + +// ActionRunnersResponse contains a page of runners. +type ActionRunnersResponse struct { + Runners []*ActionRunner `json:"runners"` + TotalCount int64 `json:"total_count"` +} + +// ActionWorkflow represents a repository workflow definition. +type ActionWorkflow struct { + ID string `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + State string `json:"state"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + BadgeURL string `json:"badge_url"` + DeletedAt time.Time `json:"deleted_at"` +} + +// ActionWorkflowResponse contains a workflow list response. +type ActionWorkflowResponse struct { + Workflows []*ActionWorkflow `json:"workflows"` + TotalCount int64 `json:"total_count"` +} + +// CreateActionWorkflowDispatchOption triggers a workflow_dispatch event. +type CreateActionWorkflowDispatchOption struct { + Ref string `json:"ref"` + Inputs map[string]string `json:"inputs,omitempty"` +} + +// Validate checks whether the dispatch payload is valid. +func (opt CreateActionWorkflowDispatchOption) Validate() error { + if len(opt.Ref) == 0 { + return errors.New("empty Ref field") + } + return nil +} + +// RunDetails contains the workflow run identifiers returned by workflow dispatch. +type RunDetails struct { + WorkflowRunID int64 `json:"workflow_run_id"` + RunURL string `json:"run_url"` + HTMLURL string `json:"html_url"` +} + +// ActionArtifact represents an Actions artifact. +type ActionArtifact struct { + ID int64 `json:"id"` + Name string `json:"name"` + SizeInBytes int64 `json:"size_in_bytes"` + URL string `json:"url"` + ArchiveDownloadURL string `json:"archive_download_url"` + Expired bool `json:"expired"` + WorkflowRun *ActionWorkflowRun `json:"workflow_run"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ExpiresAt time.Time `json:"expires_at"` +} + +// ActionArtifactsResponse contains a page of artifacts. +type ActionArtifactsResponse struct { + Artifacts []*ActionArtifact `json:"artifacts"` + TotalCount int64 `json:"total_count"` +} + +// ListActionArtifactsOptions controls artifact listing requests. +type ListActionArtifactsOptions struct { + ListOptions + Name string +} + +// QueryEncode turns the artifact list options into a query string. +func (opt *ListActionArtifactsOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.Name != "" { + query.Add("name", opt.Name) + } + return query.Encode() +} + +func (c *Client) createActionRegistrationToken(path string) (*RegistrationToken, *Response, error) { + token := new(RegistrationToken) + resp, err := c.getParsedResponse("POST", path, nil, nil, token) + return token, resp, err +} + +func (c *Client) listActionRuns(path string, opt ListRepoActionRunsOptions) (*ActionWorkflowRunsResponse, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + opt.setDefaults() + link, _ := url.Parse(path) + link.RawQuery = opt.QueryEncode() + + resp := new(ActionWorkflowRunsResponse) + response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp) + return resp, response, err +} + +func (c *Client) listActionJobs(path string, opt ListRepoActionJobsOptions) (*ActionWorkflowJobsResponse, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_26_0); err != nil { + return nil, nil, err + } + opt.setDefaults() + link, _ := url.Parse(path) + link.RawQuery = opt.QueryEncode() + + resp := new(ActionWorkflowJobsResponse) + response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp) + return resp, response, err +} + +func (c *Client) listActionRunners(path string, opt ListActionRunnersOptions) (*ActionRunnersResponse, *Response, error) { + opt.setDefaults() + link, _ := url.Parse(path) + link.RawQuery = opt.QueryEncode() + + resp := new(ActionRunnersResponse) + response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp) + return resp, response, err +} + +func (c *Client) getActionRunner(path string) (*ActionRunner, *Response, error) { + runner := new(ActionRunner) + resp, err := c.getParsedResponse("GET", path, jsonHeader, nil, runner) + return runner, resp, err +} + +func (c *Client) updateActionRunner(path string, opt EditActionRunnerOption) (*ActionRunner, *Response, error) { + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + runner := new(ActionRunner) + resp, err := c.getParsedResponse("PATCH", path, jsonHeader, bytes.NewReader(body), runner) + return runner, resp, err +} + +func (c *Client) listActionArtifacts(path string, opt ListActionArtifactsOptions) (*ActionArtifactsResponse, *Response, error) { + opt.setDefaults() + link, _ := url.Parse(path) + link.RawQuery = opt.QueryEncode() + + resp := new(ActionArtifactsResponse) + response, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, resp) + return resp, response, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/activity.go b/cli/vendor/code.gitea.io/sdk/gitea/activity.go new file mode 100644 index 0000000..01dbf9f --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/activity.go @@ -0,0 +1,24 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import "time" + +// Activity represents a user or organization activity +type Activity struct { + ID int64 `json:"id"` + ActUserID int64 `json:"act_user_id"` + ActUser *User `json:"act_user"` + OpType string `json:"op_type"` + Content string `json:"content"` + RepoID int64 `json:"repo_id"` + Repo *Repository `json:"repo"` + CommentID int64 `json:"comment_id"` + Comment *Comment `json:"comment"` + RefName string `json:"ref_name"` + IsPrivate bool `json:"is_private"` + UserID int64 `json:"user_id"` + Created time.Time `json:"created"` +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/activitypub.go b/cli/vendor/code.gitea.io/sdk/gitea/activitypub.go new file mode 100644 index 0000000..b8a5991 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/activitypub.go @@ -0,0 +1,49 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +// ActivityPub represents an ActivityPub object +type ActivityPub map[string]interface{} + +// GetActivityPubPerson returns the Person actor for a user +func (c *Client) GetActivityPubPerson(userID int64) (ActivityPub, *Response, error) { + result := make(ActivityPub) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/activitypub/user-id/%d", userID), + jsonHeader, nil, &result) + return result, resp, err +} + +// SendActivityPubInbox sends an ActivityPub message to a user's inbox +func (c *Client) SendActivityPubInbox(userID int64, activity ActivityPub) (*Response, error) { + body, err := json.Marshal(activity) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", + fmt.Sprintf("/activitypub/user-id/%d/inbox", userID), + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// GetActivityPubPersonResponse returns the raw ActivityPub Person response +func (c *Client) GetActivityPubPersonResponse(userID int64) ([]byte, *Response, error) { + return c.getResponse("GET", + fmt.Sprintf("/activitypub/user-id/%d", userID), + jsonHeader, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/admin_action.go b/cli/vendor/code.gitea.io/sdk/gitea/admin_action.go new file mode 100644 index 0000000..45884fa --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/admin_action.go @@ -0,0 +1,57 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import "fmt" + +// ListAdminActionJobs lists all admin-scope Actions jobs. +func (c *Client) ListAdminActionJobs(opt ListRepoActionJobsOptions) (*ActionWorkflowJobsResponse, *Response, error) { + return c.listActionJobs("/admin/actions/jobs", opt) +} + +// ListAdminActionRuns lists all admin-scope Actions workflow runs. +func (c *Client) ListAdminActionRuns(opt ListRepoActionRunsOptions) (*ActionWorkflowRunsResponse, *Response, error) { + return c.listActionRuns("/admin/actions/runs", opt) +} + +// ListAdminActionRunners lists all admin-scope Actions runners. +func (c *Client) ListAdminActionRunners(opt ListActionRunnersOptions) (*ActionRunnersResponse, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.listActionRunners("/admin/actions/runners", opt) +} + +// GetAdminActionRunner gets one admin-scope Actions runner. +func (c *Client) GetAdminActionRunner(runnerID int64) (*ActionRunner, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.getActionRunner(fmt.Sprintf("/admin/actions/runners/%d", runnerID)) +} + +// DeleteAdminActionRunner deletes one admin-scope Actions runner. +func (c *Client) DeleteAdminActionRunner(runnerID int64) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/admin/actions/runners/%d", runnerID), nil, nil) +} + +// UpdateAdminActionRunner updates one admin-scope Actions runner. +func (c *Client) UpdateAdminActionRunner(runnerID int64, opt EditActionRunnerOption) (*ActionRunner, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.updateActionRunner(fmt.Sprintf("/admin/actions/runners/%d", runnerID), opt) +} + +// CreateAdminActionRunnerRegistrationToken creates an admin-scope runner registration token. +func (c *Client) CreateAdminActionRunnerRegistrationToken() (*RegistrationToken, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, nil, err + } + return c.createActionRegistrationToken("/admin/actions/runners/registration-token") +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/admin_badges.go b/cli/vendor/code.gitea.io/sdk/gitea/admin_badges.go new file mode 100644 index 0000000..f9f2cc1 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/admin_badges.go @@ -0,0 +1,79 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +// Badge represents a user badge +type Badge struct { + ID int64 `json:"id"` + Slug string `json:"slug"` + Description string `json:"description"` + ImageURL string `json:"image_url"` +} + +// ListUserBadges lists badges of a user +func (c *Client) ListUserBadges(username string) ([]*Badge, *Response, error) { + if err := escapeValidatePathSegments(&username); err != nil { + return nil, nil, err + } + badges := make([]*Badge, 0, 5) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/admin/users/%s/badges", username), + jsonHeader, nil, &badges) + return badges, resp, err +} + +// UserBadgeOption represents options for adding badges to a user +type UserBadgeOption struct { + BadgeSlugs []string `json:"badge_slugs"` +} + +// AddUserBadges adds badges to a user by their slugs +func (c *Client) AddUserBadges(username string, opt UserBadgeOption) (*Response, error) { + if err := escapeValidatePathSegments(&username); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", + fmt.Sprintf("/admin/users/%s/badges", username), + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent && status != http.StatusCreated { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// DeleteUserBadge deletes a user's badge +func (c *Client) DeleteUserBadge(username string, opt UserBadgeOption) (*Response, error) { + if err := escapeValidatePathSegments(&username); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", + fmt.Sprintf("/admin/users/%s/badges", username), + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/admin_cron.go b/cli/vendor/code.gitea.io/sdk/gitea/admin_cron.go new file mode 100644 index 0000000..de05ccb --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/admin_cron.go @@ -0,0 +1,46 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "time" +) + +// CronTask represents a Cron task +type CronTask struct { + Name string `json:"name"` + Schedule string `json:"schedule"` + Next time.Time `json:"next"` + Prev time.Time `json:"prev"` + ExecTimes int64 `json:"exec_times"` +} + +// ListCronTaskOptions list options for ListCronTasks +type ListCronTaskOptions struct { + ListOptions +} + +// ListCronTasks list available cron tasks +func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + opt.setDefaults() + ct := make([]*CronTask, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/cron?%s", opt.getURLQuery().Encode()), jsonHeader, nil, &ct) + return ct, resp, err +} + +// RunCronTasks run a cron task +func (c *Client) RunCronTasks(task string) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&task); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/admin_email.go b/cli/vendor/code.gitea.io/sdk/gitea/admin_email.go new file mode 100644 index 0000000..063adb4 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/admin_email.go @@ -0,0 +1,48 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "net/url" +) + +// ListAdminEmailsOptions options for listing all emails +type ListAdminEmailsOptions struct { + ListOptions +} + +// ListAdminEmails lists all email addresses +func (c *Client) ListAdminEmails(opt ListAdminEmailsOptions) ([]*Email, *Response, error) { + opt.setDefaults() + + link, _ := url.Parse("/admin/emails") + link.RawQuery = opt.getURLQuery().Encode() + + emails := make([]*Email, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &emails) + return emails, resp, err +} + +// SearchAdminEmailsOptions options for searching emails +type SearchAdminEmailsOptions struct { + ListOptions + Query string `json:"q,omitempty"` +} + +// SearchAdminEmails searches email addresses +func (c *Client) SearchAdminEmails(opt SearchAdminEmailsOptions) ([]*Email, *Response, error) { + opt.setDefaults() + + link, _ := url.Parse("/admin/emails/search") + query := opt.getURLQuery() + if opt.Query != "" { + query.Add("q", opt.Query) + } + link.RawQuery = query.Encode() + + emails := make([]*Email, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &emails) + return emails, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/admin_hooks.go b/cli/vendor/code.gitea.io/sdk/gitea/admin_hooks.go new file mode 100644 index 0000000..aff01e7 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/admin_hooks.go @@ -0,0 +1,80 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +// ListAdminHooksOptions options for listing admin hooks +type ListAdminHooksOptions struct { + ListOptions + // Type of hooks to list: system, default, or all + Type string `json:"type,omitempty"` +} + +// ListAdminHooks lists all system webhooks +func (c *Client) ListAdminHooks(opt ListAdminHooksOptions) ([]*Hook, *Response, error) { + opt.setDefaults() + + link, _ := url.Parse("/admin/hooks") + query := opt.getURLQuery() + if opt.Type != "" { + query.Add("type", opt.Type) + } + link.RawQuery = query.Encode() + + hooks := make([]*Hook, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &hooks) + return hooks, resp, err +} + +// CreateAdminHook creates a system webhook +func (c *Client) CreateAdminHook(opt CreateHookOption) (*Hook, *Response, error) { + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + hook := new(Hook) + resp, err := c.getParsedResponse("POST", "/admin/hooks", jsonHeader, bytes.NewReader(body), hook) + return hook, resp, err +} + +// GetAdminHook gets a system webhook by ID +func (c *Client) GetAdminHook(id int64) (*Hook, *Response, error) { + hook := new(Hook) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/hooks/%d", id), jsonHeader, nil, hook) + return hook, resp, err +} + +// EditAdminHook edits a system webhook +func (c *Client) EditAdminHook(id int64, opt EditHookOption) (*Hook, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + hook := new(Hook) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/admin/hooks/%d", id), jsonHeader, bytes.NewReader(body), hook) + return hook, resp, err +} + +// DeleteAdminHook deletes a system webhook +func (c *Client) DeleteAdminHook(id int64) (*Response, error) { + status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/admin/hooks/%d", id), jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/admin_org.go b/cli/vendor/code.gitea.io/sdk/gitea/admin_org.go new file mode 100644 index 0000000..26bf81f --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/admin_org.go @@ -0,0 +1,39 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// AdminListOrgsOptions options for listing admin's organizations +type AdminListOrgsOptions struct { + ListOptions +} + +// AdminListOrgs lists all orgs +func (c *Client) AdminListOrgs(opt AdminListOrgsOptions) ([]*Organization, *Response, error) { + opt.setDefaults() + orgs := make([]*Organization, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs) + return orgs, resp, err +} + +// AdminCreateOrg create an organization +func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + org := new(Organization) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/orgs", user), jsonHeader, bytes.NewReader(body), org) + return org, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/admin_repo.go b/cli/vendor/code.gitea.io/sdk/gitea/admin_repo.go new file mode 100644 index 0000000..6f8fe29 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/admin_repo.go @@ -0,0 +1,68 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" +) + +// AdminCreateRepo create a repo +func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/repos", user), jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} + +// ListUnadoptedReposOptions options for listing unadopted repositories +type ListUnadoptedReposOptions struct { + ListOptions + Pattern string `json:"pattern,omitempty"` +} + +// ListUnadoptedRepos lists unadopted repositories +func (c *Client) ListUnadoptedRepos(opt ListUnadoptedReposOptions) ([]string, *Response, error) { + opt.setDefaults() + + link, _ := url.Parse("/admin/unadopted") + query := opt.getURLQuery() + if opt.Pattern != "" { + query.Add("pattern", opt.Pattern) + } + link.RawQuery = query.Encode() + + repos := make([]string, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &repos) + return repos, resp, err +} + +// AdoptUnadoptedRepo adopts an unadopted repository +func (c *Client) AdoptUnadoptedRepo(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", + fmt.Sprintf("/admin/unadopted/%s/%s", owner, repo), + jsonHeader, nil) +} + +// DeleteUnadoptedRepo deletes an unadopted repository +func (c *Client) DeleteUnadoptedRepo(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/admin/unadopted/%s/%s", owner, repo), + jsonHeader, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/admin_user.go b/cli/vendor/code.gitea.io/sdk/gitea/admin_user.go new file mode 100644 index 0000000..4741d2b --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/admin_user.go @@ -0,0 +1,196 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// AdminListUsersOptions options for listing admin users +type AdminListUsersOptions struct { + ListOptions + SourceID int64 + LoginName string + Query string + Sort string // "name", "created", "updated", "id" + Order string // "asc", "desc" + Visibility string + IsActive *bool + IsAdmin *bool + IsRestricted *bool + Is2FAEnabled *bool + IsProhibitLogin *bool +} + +// QueryEncode turns options into querystring argument +func (opt *AdminListUsersOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.SourceID > 0 { + query.Add("source_id", fmt.Sprintf("%d", opt.SourceID)) + } + if opt.LoginName != "" { + query.Add("login_name", opt.LoginName) + } + if opt.Query != "" { + query.Add("q", opt.Query) + } + if opt.Sort != "" { + query.Add("sort", opt.Sort) + } + if opt.Order != "" { + query.Add("order", opt.Order) + } + if opt.Visibility != "" { + query.Add("visibility", opt.Visibility) + } + if opt.IsActive != nil { + query.Add("is_active", fmt.Sprintf("%t", *opt.IsActive)) + } + if opt.IsAdmin != nil { + query.Add("is_admin", fmt.Sprintf("%t", *opt.IsAdmin)) + } + if opt.IsRestricted != nil { + query.Add("is_restricted", fmt.Sprintf("%t", *opt.IsRestricted)) + } + if opt.Is2FAEnabled != nil { + query.Add("is_2fa_enabled", fmt.Sprintf("%t", *opt.Is2FAEnabled)) + } + if opt.IsProhibitLogin != nil { + query.Add("is_prohibit_login", fmt.Sprintf("%t", *opt.IsProhibitLogin)) + } + return query.Encode() +} + +// AdminListUsers lists all users +func (c *Client) AdminListUsers(opt AdminListUsersOptions) ([]*User, *Response, error) { + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/users?%s", opt.QueryEncode()), nil, nil, &users) + return users, resp, err +} + +// CreateUserOption create user options +type CreateUserOption struct { + SourceID int64 `json:"source_id"` + LoginName string `json:"login_name"` + Username string `json:"username"` + FullName string `json:"full_name"` + Email string `json:"email"` + Password string `json:"password"` + MustChangePassword *bool `json:"must_change_password"` + SendNotify bool `json:"send_notify"` + Visibility *VisibleType `json:"visibility"` +} + +// Validate the CreateUserOption struct +func (opt CreateUserOption) Validate() error { + if len(opt.Email) == 0 { + return fmt.Errorf("email is empty") + } + if len(opt.Username) == 0 { + return fmt.Errorf("username is empty") + } + return nil +} + +// AdminCreateUser create a user +func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error) { + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + user := new(User) + resp, err := c.getParsedResponse("POST", "/admin/users", jsonHeader, bytes.NewReader(body), user) + return user, resp, err +} + +// EditUserOption edit user options +type EditUserOption struct { + SourceID int64 `json:"source_id"` + LoginName string `json:"login_name"` + Email *string `json:"email"` + FullName *string `json:"full_name"` + Password string `json:"password"` + Description *string `json:"description"` + MustChangePassword *bool `json:"must_change_password"` + Website *string `json:"website"` + Location *string `json:"location"` + Active *bool `json:"active"` + Admin *bool `json:"admin"` + AllowGitHook *bool `json:"allow_git_hook"` + AllowImportLocal *bool `json:"allow_import_local"` + MaxRepoCreation *int `json:"max_repo_creation"` + ProhibitLogin *bool `json:"prohibit_login"` + AllowCreateOrganization *bool `json:"allow_create_organization"` + Restricted *bool `json:"restricted"` + Visibility *VisibleType `json:"visibility"` +} + +// AdminEditUser modify user informations +func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PATCH", fmt.Sprintf("/admin/users/%s", user), jsonHeader, bytes.NewReader(body)) +} + +// AdminDeleteUser delete one user according name +func (c *Client) AdminDeleteUser(user string) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/admin/users/%s", user), nil, nil) +} + +// AdminCreateUserPublicKey adds a public key for the user +func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*PublicKey, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + key := new(PublicKey) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/keys", user), jsonHeader, bytes.NewReader(body), key) + return key, resp, err +} + +// AdminDeleteUserPublicKey deletes a user's public key +func (c *Client) AdminDeleteUserPublicKey(user string, keyID int) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/admin/users/%s/keys/%d", user, keyID), nil, nil) +} + +// RenameUserOption options for renaming a user +type RenameUserOption struct { + NewUsername string `json:"new_username"` +} + +// AdminRenameUser renames a user +func (c *Client) AdminRenameUser(username string, opt RenameUserOption) (*Response, error) { + if err := escapeValidatePathSegments(&username); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", + fmt.Sprintf("/admin/users/%s/rename", username), + jsonHeader, bytes.NewReader(body)) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/agent.go b/cli/vendor/code.gitea.io/sdk/gitea/agent.go new file mode 100644 index 0000000..7622ece --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/agent.go @@ -0,0 +1,38 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build !windows + +package gitea + +import ( + "fmt" + "net" + "os" + + "golang.org/x/crypto/ssh/agent" +) + +// hasAgent returns true if the ssh agent is available +func hasAgent() bool { + if _, err := os.Stat(os.Getenv("SSH_AUTH_SOCK")); err != nil { + return false + } + + return true +} + +// GetAgent returns a ssh agent +func GetAgent() (agent.Agent, error) { + if !hasAgent() { + return nil, fmt.Errorf("no ssh agent available") + } + + sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")) + if err != nil { + return nil, err + } + + return agent.NewClient(sshAgent), nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/agent_windows.go b/cli/vendor/code.gitea.io/sdk/gitea/agent_windows.go new file mode 100644 index 0000000..135dc4f --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/agent_windows.go @@ -0,0 +1,28 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build windows + +package gitea + +import ( + "fmt" + + "github.com/davidmz/go-pageant" + "golang.org/x/crypto/ssh/agent" +) + +// hasAgent returns true if pageant is available +func hasAgent() bool { + return pageant.Available() +} + +// GetAgent returns a ssh agent +func GetAgent() (agent.Agent, error) { + if !hasAgent() { + return nil, fmt.Errorf("no pageant available") + } + + return pageant.New(), nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/attachment.go b/cli/vendor/code.gitea.io/sdk/gitea/attachment.go new file mode 100644 index 0000000..89c3a50 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/attachment.go @@ -0,0 +1,110 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea // import "code.gitea.io/sdk/gitea" +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "time" +) + +// Attachment a generic attachment +type Attachment struct { + ID int64 `json:"id"` + Name string `json:"name"` + Size int64 `json:"size"` + DownloadCount int64 `json:"download_count"` + Created time.Time `json:"created_at"` + UUID string `json:"uuid"` + DownloadURL string `json:"browser_download_url"` +} + +// ListReleaseAttachmentsOptions options for listing release's attachments +type ListReleaseAttachmentsOptions struct { + ListOptions +} + +// ListReleaseAttachments list release's attachments +func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt ListReleaseAttachmentsOptions) ([]*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + attachments := make([]*Attachment, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/%d/assets?%s", user, repo, release, opt.getURLQuery().Encode()), + nil, nil, &attachments) + return attachments, resp, err +} + +// GetReleaseAttachment returns the requested attachment +func (c *Client) GetReleaseAttachment(user, repo string, release, id int64) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + a := new(Attachment) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), + nil, nil, &a) + return a, resp, err +} + +// CreateReleaseAttachment creates an attachment for the given release +func (c *Client) CreateReleaseAttachment(user, repo string, release int64, file io.Reader, filename string) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + // Write file to body + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("attachment", filename) + if err != nil { + return nil, nil, err + } + + if _, err = io.Copy(part, file); err != nil { + return nil, nil, err + } + if err = writer.Close(); err != nil { + return nil, nil, err + } + + // Send request + attachment := new(Attachment) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/releases/%d/assets", user, repo, release), + http.Header{"Content-Type": []string{writer.FormDataContentType()}}, body, &attachment) + return attachment, resp, err +} + +// EditAttachmentOptions options for editing attachments +type EditAttachmentOptions struct { + Name string `json:"name"` +} + +// EditReleaseAttachment updates the given attachment with the given options +func (c *Client) EditReleaseAttachment(user, repo string, release, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&form) + if err != nil { + return nil, nil, err + } + attach := new(Attachment) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, attachment), jsonHeader, bytes.NewReader(body), attach) + return attach, resp, err +} + +// DeleteReleaseAttachment deletes the given attachment including the uploaded file +func (c *Client) DeleteReleaseAttachment(user, repo string, release, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/client.go b/cli/vendor/code.gitea.io/sdk/gitea/client.go new file mode 100644 index 0000000..5bf19c3 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/client.go @@ -0,0 +1,536 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + + version "github.com/hashicorp/go-version" +) + +var jsonHeader = http.Header{"content-type": []string{"application/json"}} + +// Version return the library version +func Version() string { + return "0.16.0" +} + +// Client represents a thread-safe Gitea API client. +type Client struct { + url string + accessToken string + username string + password string + otp string + sudo string + userAgent string + debug bool + httpsigner *HTTPSign + client *http.Client + ctx context.Context + mutex sync.RWMutex + serverVersion *version.Version + getVersionOnce sync.Once + ignoreVersion bool // only set by SetGiteaVersion so don't need a mutex lock +} + +// Response represents the gitea response +type Response struct { + *http.Response + + FirstPage int + PrevPage int + NextPage int + LastPage int +} + +// ClientOption are functions used to init a new client +type ClientOption func(*Client) error + +// NewClient initializes and returns a API client. +// Usage of all gitea.Client methods is concurrency-safe. +func NewClient(url string, options ...ClientOption) (*Client, error) { + client := &Client{ + url: strings.TrimSuffix(url, "/"), + client: &http.Client{}, + ctx: context.Background(), + } + for _, opt := range options { + if err := opt(client); err != nil { + return nil, err + } + } + if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil { + if errors.Is(err, &ErrUnknownVersion{}) { + return client, err + } + return nil, err + } + + return client, nil +} + +// NewClientWithHTTP creates an API client with a custom http client +// Deprecated use SetHTTPClient option +func NewClientWithHTTP(url string, httpClient *http.Client) *Client { + client, _ := NewClient(url, SetHTTPClient(httpClient)) + return client +} + +// SetHTTPClient is an option for NewClient to set custom http client +func SetHTTPClient(httpClient *http.Client) ClientOption { + return func(client *Client) error { + client.SetHTTPClient(httpClient) + return nil + } +} + +// SetHTTPClient replaces default http.Client with user given one. +func (c *Client) SetHTTPClient(client *http.Client) { + c.mutex.Lock() + c.client = client + c.mutex.Unlock() +} + +// SetToken is an option for NewClient to set token +func SetToken(token string) ClientOption { + return func(client *Client) error { + client.mutex.Lock() + client.accessToken = token + client.mutex.Unlock() + return nil + } +} + +// SetBasicAuth is an option for NewClient to set username and password +func SetBasicAuth(username, password string) ClientOption { + return func(client *Client) error { + client.SetBasicAuth(username, password) + return nil + } +} + +// UseSSHCert is an option for NewClient to enable SSH certificate authentication via HTTPSign +// If you want to auth against the ssh-agent you'll need to set a principal, if you want to +// use a file on disk you'll need to specify sshKey. +// If you have an encrypted sshKey you'll need to also set the passphrase. +func UseSSHCert(principal, sshKey, passphrase string) ClientOption { + return func(client *Client) error { + if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil { + return err + } + + client.mutex.Lock() + defer client.mutex.Unlock() + + var err error + client.httpsigner, err = NewHTTPSignWithCert(principal, sshKey, passphrase) + if err != nil { + return err + } + + return nil + } +} + +// UseSSHPubkey is an option for NewClient to enable SSH pubkey authentication via HTTPSign +// If you want to auth against the ssh-agent you'll need to set a fingerprint, if you want to +// use a file on disk you'll need to specify sshKey. +// If you have an encrypted sshKey you'll need to also set the passphrase. +func UseSSHPubkey(fingerprint, sshKey, passphrase string) ClientOption { + return func(client *Client) error { + if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil { + return err + } + + client.mutex.Lock() + defer client.mutex.Unlock() + + var err error + client.httpsigner, err = NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase) + if err != nil { + return err + } + + return nil + } +} + +// SetBasicAuth sets username and password +func (c *Client) SetBasicAuth(username, password string) { + c.mutex.Lock() + c.username, c.password = username, password + c.mutex.Unlock() +} + +// SetOTP is an option for NewClient to set OTP for 2FA +func SetOTP(otp string) ClientOption { + return func(client *Client) error { + client.SetOTP(otp) + return nil + } +} + +// SetOTP sets OTP for 2FA +func (c *Client) SetOTP(otp string) { + c.mutex.Lock() + c.otp = otp + c.mutex.Unlock() +} + +// SetContext is an option for NewClient to set the default context +func SetContext(ctx context.Context) ClientOption { + return func(client *Client) error { + client.SetContext(ctx) + return nil + } +} + +// SetContext set default context witch is used for http requests +func (c *Client) SetContext(ctx context.Context) { + c.mutex.Lock() + c.ctx = ctx + c.mutex.Unlock() +} + +// SetSudo is an option for NewClient to set sudo header +func SetSudo(sudo string) ClientOption { + return func(client *Client) error { + client.SetSudo(sudo) + return nil + } +} + +// SetSudo sets username to impersonate. +func (c *Client) SetSudo(sudo string) { + c.mutex.Lock() + c.sudo = sudo + c.mutex.Unlock() +} + +// SetUserAgent is an option for NewClient to set user-agent header +func SetUserAgent(userAgent string) ClientOption { + return func(client *Client) error { + client.SetUserAgent(userAgent) + return nil + } +} + +// SetUserAgent sets the user-agent to send with every request. +func (c *Client) SetUserAgent(userAgent string) { + c.mutex.Lock() + c.userAgent = userAgent + c.mutex.Unlock() +} + +// SetDebugMode is an option for NewClient to enable debug mode +func SetDebugMode() ClientOption { + return func(client *Client) error { + client.mutex.Lock() + client.debug = true + client.mutex.Unlock() + return nil + } +} + +func newResponse(r *http.Response) *Response { + response := &Response{Response: r} + response.parseLinkHeader() + + return response +} + +func (r *Response) parseLinkHeader() { + link := r.Header.Get("Link") + if link == "" { + return + } + + links := strings.Split(link, ",") + for _, l := range links { + u, param, ok := strings.Cut(l, ";") + if !ok { + continue + } + u = strings.Trim(u, " <>") + + key, value, ok := strings.Cut(strings.TrimSpace(param), "=") + if !ok || key != "rel" { + continue + } + + value = strings.Trim(value, "\"") + + parsed, err := url.Parse(u) + if err != nil { + continue + } + + page := parsed.Query().Get("page") + if page == "" { + continue + } + + switch value { + case "first": + r.FirstPage, _ = strconv.Atoi(page) + case "prev": + r.PrevPage, _ = strconv.Atoi(page) + case "next": + r.NextPage, _ = strconv.Atoi(page) + case "last": + r.LastPage, _ = strconv.Atoi(page) + } + } +} + +func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) { + c.mutex.RLock() + debug := c.debug + if debug { + fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body) + } + req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body) + + client := c.client // client ref can change from this point on so safe it + c.mutex.RUnlock() + + if err != nil { + return nil, nil, err + } + + resp, err := client.Do(req) + if err != nil { + return nil, nil, err + } + + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil && err == nil { + err = closeErr + } + }() + + data, err := io.ReadAll(resp.Body) + if debug { + fmt.Printf("Response: %v\n\n", resp) + } + + return data, newResponse(resp), err +} + +func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) { + c.mutex.RLock() + debug := c.debug + if debug { + var bodyStr string + if body != nil { + bs, _ := io.ReadAll(body) + body = bytes.NewReader(bs) + bodyStr = string(bs) + } + fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, bodyStr) + } + req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body) + if err != nil { + c.mutex.RUnlock() + return nil, err + } + if len(c.accessToken) != 0 { + req.Header.Set("Authorization", "token "+c.accessToken) + } + if len(c.otp) != 0 { + req.Header.Set("X-GITEA-OTP", c.otp) + } + if len(c.username) != 0 { + req.SetBasicAuth(c.username, c.password) + } + if len(c.sudo) != 0 { + req.Header.Set("Sudo", c.sudo) + } + if len(c.userAgent) != 0 { + req.Header.Set("User-Agent", c.userAgent) + } + + client := c.client // client ref can change from this point on so safe it + c.mutex.RUnlock() + + for k, v := range header { + req.Header[k] = v + } + + if c.httpsigner != nil { + err = c.SignRequest(req) + if err != nil { + return nil, err + } + } + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if debug { + fmt.Printf("Response: %v\n\n", resp) + } + + return newResponse(resp), nil +} + +// Converts a response for a HTTP status code indicating an error condition +// (non-2XX) to a well-known error value and response body. For non-problematic +// (2XX) status codes nil will be returned. Note that on a non-2XX response, the +// response body stream will have been read and, hence, is closed on return. +func statusCodeToErr(resp *Response) (body []byte, err error) { + // no error + if resp.StatusCode/100 == 2 { + return nil, nil + } + + // + // error: body will be read for details + // + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil && err == nil { + err = closeErr + } + }() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("body read on HTTP error %d: %v", resp.StatusCode, err) + } + + // Try to unmarshal and get an error message + errMap := make(map[string]interface{}) + if err = json.Unmarshal(data, &errMap); err != nil { + // when the JSON can't be parsed, data was probably empty or a + // plain string, so we try to return a helpful error anyway + path := resp.Request.URL.Path + method := resp.Request.Method + return data, fmt.Errorf("unknown API error: %d\nRequest: '%s' with '%s' method and '%s' body", resp.StatusCode, path, method, string(data)) + } + + if msg, ok := errMap["message"]; ok { + return data, fmt.Errorf("%v", msg) + } + + // If no error message, at least give status and data + return data, fmt.Errorf("%s: %s", resp.Status, string(data)) +} + +func (c *Client) getResponseReader(method, path string, header http.Header, body io.Reader) (io.ReadCloser, *Response, error) { + resp, err := c.doRequest(method, path, header, body) + if err != nil { + return nil, resp, err + } + + // check for errors + data, err := statusCodeToErr(resp) + if err != nil { + return io.NopCloser(bytes.NewReader(data)), resp, err + } + + return resp.Body, resp, nil +} + +func (c *Client) doRequestWithStatusHandle(method, path string, header http.Header, body io.Reader) (*Response, error) { + resp, err := c.doRequest(method, path, header, body) + if err != nil { + return resp, err + } + + // check for errors + if _, err = statusCodeToErr(resp); err != nil { + // resp.Body has already been closed in statusCodeToErr + return resp, err + } + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil && err == nil { + err = closeErr + } + }() + + return resp, err +} + +func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) { + resp, err := c.doRequest(method, path, header, body) + if err != nil { + return nil, resp, err + } + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil && err == nil { + err = closeErr + } + }() + + // check for errors + data, err := statusCodeToErr(resp) + if err != nil { + return data, resp, err + } + + // success (2XX), read body + data, err = io.ReadAll(resp.Body) + if err != nil { + return nil, resp, err + } + + return data, resp, nil +} + +func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) (*Response, error) { + data, resp, err := c.getResponse(method, path, header, body) + if err != nil { + return resp, err + } + return resp, json.Unmarshal(data, obj) +} + +func (c *Client) getStatusCode(method, path string, header http.Header, body io.Reader) (int, *Response, error) { + resp, err := c.doRequest(method, path, header, body) + if err != nil { + return -1, resp, err + } + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil && err == nil { + err = closeErr + } + }() + + return resp.StatusCode, resp, nil +} + +// pathEscapeSegments escapes segments of a path while not escaping forward slash +func pathEscapeSegments(path string) string { + slice := strings.Split(path, "/") + for index := range slice { + slice[index] = url.PathEscape(slice[index]) + } + escapedPath := strings.Join(slice, "/") + return escapedPath +} + +// escapeValidatePathSegments is a help function to validate and encode url path segments +func escapeValidatePathSegments(seg ...*string) error { + for i := range seg { + if seg[i] == nil || len(*seg[i]) == 0 { + return fmt.Errorf("path segment [%d] is empty", i) + } + *seg[i] = url.PathEscape(*seg[i]) + } + return nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/doc.go b/cli/vendor/code.gitea.io/sdk/gitea/doc.go new file mode 100644 index 0000000..777ad2f --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/doc.go @@ -0,0 +1,9 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +// Package gitea implements a client for the Gitea API. +// The version corresponds to the highest supported version +// of the gitea API, but backwards-compatibility is mostly +// given. +package gitea // import "code.gitea.io/sdk/gitea" diff --git a/cli/vendor/code.gitea.io/sdk/gitea/fork.go b/cli/vendor/code.gitea.io/sdk/gitea/fork.go new file mode 100644 index 0000000..0373c35 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/fork.go @@ -0,0 +1,51 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// ListForksOptions options for listing repository's forks +type ListForksOptions struct { + ListOptions +} + +// ListForks list a repository's forks +func (c *Client) ListForks(user, repo string, opt ListForksOptions) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + forks := make([]*Repository, opt.PageSize) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/forks?%s", user, repo, opt.getURLQuery().Encode()), + nil, nil, &forks) + return forks, resp, err +} + +// CreateForkOption options for creating a fork +type CreateForkOption struct { + // organization name, if forking into an organization + Organization *string `json:"organization"` + // name of the forked repository + Name *string `json:"name"` +} + +// CreateFork create a fork of a repository +func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(form) + if err != nil { + return nil, nil, err + } + fork := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/forks", user, repo), jsonHeader, bytes.NewReader(body), &fork) + return fork, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/git_blob.go b/cli/vendor/code.gitea.io/sdk/gitea/git_blob.go new file mode 100644 index 0000000..7668672 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/git_blob.go @@ -0,0 +1,28 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" +) + +// GitBlobResponse represents a git blob +type GitBlobResponse struct { + Content string `json:"content"` + Encoding string `json:"encoding"` + URL string `json:"url"` + SHA string `json:"sha"` + Size int64 `json:"size"` +} + +// GetBlob get the blob of a repository file +func (c *Client) GetBlob(user, repo, sha string) (*GitBlobResponse, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil { + return nil, nil, err + } + blob := new(GitBlobResponse) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/blobs/%s", user, repo, sha), nil, nil, blob) + return blob, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/git_hook.go b/cli/vendor/code.gitea.io/sdk/gitea/git_hook.go new file mode 100644 index 0000000..6b1f042 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/git_hook.go @@ -0,0 +1,69 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// GitHook represents a Git repository hook +type GitHook struct { + Name string `json:"name"` + IsActive bool `json:"is_active"` + Content string `json:"content,omitempty"` +} + +// ListRepoGitHooksOptions options for listing repository's githooks +type ListRepoGitHooksOptions struct { + ListOptions +} + +// ListRepoGitHooks list all the Git hooks of one repository +func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions) ([]*GitHook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + hooks := make([]*GitHook, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks) + return hooks, resp, err +} + +// GetRepoGitHook get a Git hook of a repository +func (c *Client) GetRepoGitHook(user, repo, id string) (*GitHook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &id); err != nil { + return nil, nil, err + } + h := new(GitHook) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil, h) + return h, resp, err +} + +// EditGitHookOption options when modifying one Git hook +type EditGitHookOption struct { + Content string `json:"content"` +} + +// EditRepoGitHook modify one Git hook of a repository +func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &id); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), jsonHeader, bytes.NewReader(body)) +} + +// DeleteRepoGitHook delete one Git hook from a repository +func (c *Client) DeleteRepoGitHook(user, repo, id string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &id); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/helper.go b/cli/vendor/code.gitea.io/sdk/gitea/helper.go new file mode 100644 index 0000000..ff8038b --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/helper.go @@ -0,0 +1,20 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +// OptionalBool convert a bool to a bool reference +func OptionalBool(v bool) *bool { + return &v +} + +// OptionalString convert a string to a string reference +func OptionalString(v string) *string { + return &v +} + +// OptionalInt64 convert a int64 to a int64 reference +func OptionalInt64(v int64) *int64 { + return &v +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/hook.go b/cli/vendor/code.gitea.io/sdk/gitea/hook.go new file mode 100644 index 0000000..a18635c --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/hook.go @@ -0,0 +1,237 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// Hook a hook is a web hook when one repository changed +type Hook struct { + ID int64 `json:"id"` + Type string `json:"type"` + URL string `json:"-"` + BranchFilter string `json:"branch_filter"` + Config map[string]string `json:"config"` + Events []string `json:"events"` + AuthorizationHeader string `json:"authorization_header"` + Active bool `json:"active"` + Updated time.Time `json:"updated_at"` + Created time.Time `json:"created_at"` +} + +// HookType represent all webhook types gitea currently offer +type HookType string + +const ( + // HookTypeDingtalk webhook that dingtalk understand + HookTypeDingtalk HookType = "dingtalk" + // HookTypeDiscord webhook that discord understand + HookTypeDiscord HookType = "discord" + // HookTypeGitea webhook that gitea understand + HookTypeGitea HookType = "gitea" + // HookTypeGogs webhook that gogs understand + HookTypeGogs HookType = "gogs" + // HookTypeMsteams webhook that msteams understand + HookTypeMsteams HookType = "msteams" + // HookTypeSlack webhook that slack understand + HookTypeSlack HookType = "slack" + // HookTypeTelegram webhook that telegram understand + HookTypeTelegram HookType = "telegram" + // HookTypeFeishu webhook that feishu understand + HookTypeFeishu HookType = "feishu" +) + +// ListHooksOptions options for listing hooks +type ListHooksOptions struct { + ListOptions +} + +// ListOrgHooks list all the hooks of one organization +func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + hooks := make([]*Hook, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks?%s", org, opt.getURLQuery().Encode()), nil, nil, &hooks) + return hooks, resp, err +} + +// ListMyHooks list all the hooks of the authenticated user +func (c *Client) ListMyHooks(opt ListHooksOptions) ([]*Hook, *Response, error) { + opt.setDefaults() + hooks := make([]*Hook, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/hooks?%s", opt.getURLQuery().Encode()), nil, nil, &hooks) + return hooks, resp, err +} + +// ListRepoHooks list all the hooks of one repository +func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + hooks := make([]*Hook, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks) + return hooks, resp, err +} + +// GetOrgHook get a hook of an organization +func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h) + return h, resp, err +} + +// GetMyHook get a hook of the authenticated user +func (c *Client) GetMyHook(id int64) (*Hook, *Response, error) { + h := new(Hook) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/hooks/%d", id), nil, nil, h) + return h, resp, err +} + +// GetRepoHook get a hook of a repository +func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h) + return h, resp, err +} + +// CreateHookOption options when create a hook +type CreateHookOption struct { + Type HookType `json:"type"` + Config map[string]string `json:"config"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter"` + Active bool `json:"active"` + AuthorizationHeader string `json:"authorization_header"` +} + +// Validate the CreateHookOption struct +func (opt CreateHookOption) Validate() error { + if len(opt.Type) == 0 { + return fmt.Errorf("hook type needed") + } + return nil +} + +// CreateOrgHook create one hook for an organization, with options +func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/hooks", org), jsonHeader, bytes.NewReader(body), h) + return h, resp, err +} + +// CreateMyHook create one hook for the authenticated user, with options +func (c *Client) CreateMyHook(opt CreateHookOption) (*Hook, *Response, error) { + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := c.getParsedResponse("POST", "/user/hooks", jsonHeader, bytes.NewReader(body), h) + return h, resp, err +} + +// CreateRepoHook create one hook for a repository, with options +func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/hooks", user, repo), jsonHeader, bytes.NewReader(body), h) + return h, resp, err +} + +// EditHookOption options when modify one hook +type EditHookOption struct { + Config map[string]string `json:"config"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter"` + Active *bool `json:"active"` + AuthorizationHeader string `json:"authorization_header"` +} + +// EditOrgHook modify one hook of an organization, with hook id and options +func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PATCH", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), jsonHeader, bytes.NewReader(body)) +} + +// EditMyHook modify one hook of the authenticated user, with hook id and options +func (c *Client) EditMyHook(id int64, opt EditHookOption) (*Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PATCH", fmt.Sprintf("/user/hooks/%d", id), jsonHeader, bytes.NewReader(body)) +} + +// EditRepoHook modify one hook of a repository, with hook id and options +func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), jsonHeader, bytes.NewReader(body)) +} + +// DeleteOrgHook delete one hook from an organization, with hook id +func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil) +} + +// DeleteMyHook delete one hook from the authenticated user, with hook id +func (c *Client) DeleteMyHook(id int64) (*Response, error) { + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/user/hooks/%d", id), nil, nil) +} + +// DeleteRepoHook delete one hook from a repository, with hook id +func (c *Client) DeleteRepoHook(user, repo string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/hook_validate.go b/cli/vendor/code.gitea.io/sdk/gitea/hook_validate.go new file mode 100644 index 0000000..5ef6149 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/hook_validate.go @@ -0,0 +1,59 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "io" + "net/http" +) + +// VerifyWebhookSignature verifies that a payload matches the X-Gitea-Signature based on a secret +func VerifyWebhookSignature(secret, expected string, payload []byte) (bool, error) { + hash := hmac.New(sha256.New, []byte(secret)) + if _, err := hash.Write(payload); err != nil { + return false, err + } + expectedSum, err := hex.DecodeString(expected) + if err != nil { + return false, err + } + return hmac.Equal(hash.Sum(nil), expectedSum), nil +} + +// VerifyWebhookSignatureMiddleware is a http.Handler for verifying X-Gitea-Signature on incoming webhooks +func VerifyWebhookSignatureMiddleware(secret string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var b bytes.Buffer + if _, err := io.Copy(&b, r.Body); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + expected := r.Header.Get("X-Gitea-Signature") + if expected == "" { + http.Error(w, "no signature found", http.StatusBadRequest) + return + } + + ok, err := VerifyWebhookSignature(secret, expected, b.Bytes()) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + if !ok { + http.Error(w, "invalid payload", http.StatusUnauthorized) + return + } + + r.Body = io.NopCloser(&b) + next.ServeHTTP(w, r) + }) + } +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/httpsign.go b/cli/vendor/code.gitea.io/sdk/gitea/httpsign.go new file mode 100644 index 0000000..65d74b4 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/httpsign.go @@ -0,0 +1,277 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "crypto" + "encoding/base64" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" + + "github.com/42wim/httpsig" + legacyhttpsig "github.com/go-fed/httpsig" + "golang.org/x/crypto/ssh" +) + +// HTTPSign contains the signer used for signing requests +type HTTPSign struct { + ssh.Signer + cert bool +} + +// HTTPSignConfig contains the configuration for creating a HTTPSign +type HTTPSignConfig struct { + fingerprint string + principal string + pubkey bool + cert bool + sshKey string + passphrase string +} + +// NewHTTPSignWithPubkey can be used to create a HTTPSign with a public key +// if no fingerprint is specified it returns the first public key found +func NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase string) (*HTTPSign, error) { + return newHTTPSign(&HTTPSignConfig{ + fingerprint: fingerprint, + pubkey: true, + sshKey: sshKey, + passphrase: passphrase, + }) +} + +// NewHTTPSignWithCert can be used to create a HTTPSign with a certificate +// if no principal is specified it returns the first certificate found +func NewHTTPSignWithCert(principal, sshKey, passphrase string) (*HTTPSign, error) { + return newHTTPSign(&HTTPSignConfig{ + principal: principal, + cert: true, + sshKey: sshKey, + passphrase: passphrase, + }) +} + +// NewHTTPSign returns a new HTTPSign +// It will check the ssh-agent or a local file is config.sshKey is set. +// Depending on the configuration it will either use a certificate or a public key +func newHTTPSign(config *HTTPSignConfig) (*HTTPSign, error) { + var signer ssh.Signer + + if config.sshKey != "" { + priv, err := os.ReadFile(config.sshKey) + if err != nil { + return nil, err + } + + if config.passphrase == "" { + signer, err = ssh.ParsePrivateKey(priv) + if err != nil { + return nil, err + } + } else { + signer, err = ssh.ParsePrivateKeyWithPassphrase(priv, []byte(config.passphrase)) + if err != nil { + return nil, err + } + } + + if config.cert { + certbytes, err := os.ReadFile(config.sshKey + "-cert.pub") + if err != nil { + return nil, err + } + + pub, _, _, _, err := ssh.ParseAuthorizedKey(certbytes) + if err != nil { + return nil, err + } + + cert, ok := pub.(*ssh.Certificate) + if !ok { + return nil, fmt.Errorf("failed to parse certificate") + } + + signer, err = ssh.NewCertSigner(cert, signer) + if err != nil { + return nil, err + } + } + } else { + // if no sshKey is specified, check if we have a ssh-agent and use it + agent, err := GetAgent() + if err != nil { + return nil, err + } + + signers, err := agent.Signers() + if err != nil { + return nil, err + } + + if len(signers) == 0 { + return nil, fmt.Errorf("no signers found") + } + + if config.cert { + signer = findCertSigner(signers, config.principal) + if signer == nil { + return nil, fmt.Errorf("no certificate found for %s", config.principal) + } + } + + if config.pubkey { + signer = findPubkeySigner(signers, config.fingerprint) + if signer == nil { + return nil, fmt.Errorf("no public key found for %s", config.fingerprint) + } + } + } + + return &HTTPSign{ + Signer: signer, + cert: config.cert, + }, nil +} + +// SignWithAlgorithm implements ssh.AlgorithmSigner, required by 42wim/httpsig v1.2.4+ +// when signing with RSA keys. +func (h *HTTPSign) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*ssh.Signature, error) { + if as, ok := h.Signer.(ssh.AlgorithmSigner); ok { + return as.SignWithAlgorithm(rand, data, algorithm) + } + return h.Sign(rand, data) +} + +// SignRequest signs a HTTP request +func (c *Client) SignRequest(r *http.Request) error { + var contents []byte + + headersToSign := []string{httpsig.RequestTarget, "(created)", "(expires)"} + + if c.httpsigner.cert { + // add our certificate to the headers to sign + pubkey, _ := ssh.ParsePublicKey(c.httpsigner.PublicKey().Marshal()) + if cert, ok := pubkey.(*ssh.Certificate); ok { + certString := base64.RawStdEncoding.EncodeToString(cert.Marshal()) + r.Header.Add("x-ssh-certificate", certString) + + headersToSign = append(headersToSign, "x-ssh-certificate") + } else { + return fmt.Errorf("no ssh certificate found") + } + } + + // if we have a body, the Digest header will be added and we'll include this also in + // our signature. + if r.Body != nil { + body, err := r.GetBody() + if err != nil { + return fmt.Errorf("getBody() failed: %s", err) + } + + contents, err = io.ReadAll(body) + if err != nil { + return fmt.Errorf("failed reading body: %s", err) + } + + headersToSign = append(headersToSign, "Digest") + } + + // create a signer for the request and headers, the signature will be valid for 10 seconds + var err error + + // use legacyhttpsig to sign with RSA-SHA1 on older gitea releases + if err = c.checkServerVersionGreaterThanOrEqual(version1_23_0); err != nil { + // Legacy signer + legacySigner, _, err := legacyhttpsig.NewSSHSigner(c.httpsigner, httpsig.DigestSha512, headersToSign, legacyhttpsig.Signature, 10) + if err != nil { + return fmt.Errorf("legacy httpsig.NewSSHSigner failed: %s", err) + } + + // sign the request, use the fingerprint if we don't have a certificate + keyID := "gitea" + if !c.httpsigner.cert { + keyID = ssh.FingerprintSHA256(c.httpsigner.PublicKey()) + } + + return legacySigner.SignRequest(keyID, r, contents) + } + // Modern signer + modernSigner, _, err := httpsig.NewSSHSigner(c.httpsigner, httpsig.DigestSha512, headersToSign, httpsig.Signature, 10) + if err != nil { + return fmt.Errorf("httpsig.NewSSHSigner failed: %s", err) + } + + // sign the request, use the fingerprint if we don't have a certificate + keyID := "gitea" + if !c.httpsigner.cert { + keyID = ssh.FingerprintSHA256(c.httpsigner.PublicKey()) + } + + return modernSigner.SignRequest(keyID, r, contents) +} + +// findCertSigner returns the Signer containing a valid certificate +// if no principal is specified it returns the first certificate found +func findCertSigner(sshsigners []ssh.Signer, principal string) ssh.Signer { + for _, s := range sshsigners { + // Check if the key is a certificate + if !strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") { + continue + } + + // convert the ssh.Signer to a ssh.Certificate + mpubkey, _ := ssh.ParsePublicKey(s.PublicKey().Marshal()) + cryptopub := mpubkey.(crypto.PublicKey) + cert := cryptopub.(*ssh.Certificate) + t := time.Unix(int64(cert.ValidBefore), 0) + + // make sure the certificate is at least 10 seconds valid + if time.Until(t) <= time.Second*10 { + continue + } + + if principal == "" { + return s + } + + for _, p := range cert.ValidPrincipals { + if p == principal { + return s + } + } + } + + return nil +} + +// findPubkeySigner returns the Signer containing a valid public key +// if no fingerprint is specified it returns the first public key found +func findPubkeySigner(sshsigners []ssh.Signer, fingerprint string) ssh.Signer { + for _, s := range sshsigners { + // Check if the key is a certificate + if strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") { + continue + } + + if fingerprint == "" { + return s + } + + if strings.TrimSpace(string(ssh.MarshalAuthorizedKey(s.PublicKey()))) == fingerprint { + return s + } + + if ssh.FingerprintSHA256(s.PublicKey()) == fingerprint { + return s + } + } + + return nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue.go b/cli/vendor/code.gitea.io/sdk/gitea/issue.go new file mode 100644 index 0000000..19e2035 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue.go @@ -0,0 +1,308 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" +) + +// PullRequestMeta PR info if an issue is a PR +type PullRequestMeta struct { + HasMerged bool `json:"merged"` + Merged *time.Time `json:"merged_at"` +} + +// RepositoryMeta basic repository information +type RepositoryMeta struct { + ID int64 `json:"id"` + Name string `json:"name"` + Owner string `json:"owner"` + FullName string `json:"full_name"` +} + +// Issue represents an issue in a repository +type Issue struct { + ID int64 `json:"id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Index int64 `json:"number"` + Poster *User `json:"user"` + OriginalAuthor string `json:"original_author"` + OriginalAuthorID int64 `json:"original_author_id"` + Title string `json:"title"` + Body string `json:"body"` + Ref string `json:"ref"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + Assignees []*User `json:"assignees"` + // Whether the issue is open or closed + State StateType `json:"state"` + IsLocked bool `json:"is_locked"` + Comments int `json:"comments"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` + Deadline *time.Time `json:"due_date"` + PullRequest *PullRequestMeta `json:"pull_request"` + Repository *RepositoryMeta `json:"repository"` +} + +// ListIssueOption list issue options +type ListIssueOption struct { + ListOptions + State StateType + Type IssueType + Labels []string + Milestones []string + KeyWord string + Since time.Time + Before time.Time + // filter by created by username + CreatedBy string + // filter by assigned to username + AssignedBy string + // filter by username mentioned + MentionedBy string + // filter by owner (only works on ListIssues on User) + Owner string + // filter by team (requires organization owner parameter to be provided and only works on ListIssues on User) + Team string +} + +// StateType issue state type +type StateType string + +const ( + // StateOpen pr/issue is opend + StateOpen StateType = "open" + // StateClosed pr/issue is closed + StateClosed StateType = "closed" + // StateAll is all + StateAll StateType = "all" +) + +// IssueType is issue a pull or only an issue +type IssueType string + +const ( + // IssueTypeAll pr and issue + IssueTypeAll IssueType = "" + // IssueTypeIssue only issues + IssueTypeIssue IssueType = "issues" + // IssueTypePull only pulls + IssueTypePull IssueType = "pulls" +) + +// QueryEncode turns options into querystring argument +func (opt *ListIssueOption) QueryEncode() string { + query := opt.getURLQuery() + + if len(opt.State) > 0 { + query.Add("state", string(opt.State)) + } + + if len(opt.Labels) > 0 { + query.Add("labels", strings.Join(opt.Labels, ",")) + } + + if len(opt.KeyWord) > 0 { + query.Add("q", opt.KeyWord) + } + + query.Add("type", string(opt.Type)) + + if len(opt.Milestones) > 0 { + query.Add("milestones", strings.Join(opt.Milestones, ",")) + } + + if !opt.Since.IsZero() { + query.Add("since", opt.Since.Format(time.RFC3339)) + } + if !opt.Before.IsZero() { + query.Add("before", opt.Before.Format(time.RFC3339)) + } + + if len(opt.CreatedBy) > 0 { + query.Add("created_by", opt.CreatedBy) + } + if len(opt.AssignedBy) > 0 { + query.Add("assigned_by", opt.AssignedBy) + } + if len(opt.MentionedBy) > 0 { + query.Add("mentioned_by", opt.MentionedBy) + } + if len(opt.Owner) > 0 { + query.Add("owner", opt.Owner) + } + if len(opt.Team) > 0 { + query.Add("team", opt.Team) + } + + return query.Encode() +} + +// ListIssues returns all issues assigned the authenticated user +func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) { + opt.setDefaults() + issues := make([]*Issue, 0, opt.PageSize) + + link, _ := url.Parse("/repos/issues/search") + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues) + if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil { + for i := 0; i < len(issues); i++ { + if issues[i].Repository != nil { + issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0] + } + } + } + for i := range issues { + c.issueBackwardsCompatibility(issues[i]) + } + return issues, resp, err +} + +// ListRepoIssues returns all issues for a given repository +func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + issues := make([]*Issue, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues", owner, repo)) + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues) + if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil { + for i := 0; i < len(issues); i++ { + if issues[i].Repository != nil { + issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0] + } + } + } + for i := range issues { + c.issueBackwardsCompatibility(issues[i]) + } + return issues, resp, err +} + +// GetIssue returns a single issue for a given repository +func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue) + if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil && issue.Repository != nil { + issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0] + } + c.issueBackwardsCompatibility(issue) + return issue, resp, err +} + +// CreateIssueOption options to create one issue +type CreateIssueOption struct { + Title string `json:"title"` + Body string `json:"body"` + Ref string `json:"ref"` + Assignees []string `json:"assignees"` + Deadline *time.Time `json:"due_date"` + // milestone id + Milestone int64 `json:"milestone"` + // list of label ids + Labels []int64 `json:"labels"` + Closed bool `json:"closed"` +} + +// Validate the CreateIssueOption struct +func (opt CreateIssueOption) Validate() error { + if len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// CreateIssue create a new issue for a given repository +func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo), + jsonHeader, bytes.NewReader(body), issue) + c.issueBackwardsCompatibility(issue) + return issue, resp, err +} + +// EditIssueOption options for editing an issue +type EditIssueOption struct { + Title string `json:"title"` + Body *string `json:"body"` + Ref *string `json:"ref"` + Assignees []string `json:"assignees"` + Milestone *int64 `json:"milestone"` + State *StateType `json:"state"` + Deadline *time.Time `json:"due_date"` + RemoveDeadline *bool `json:"unset_due_date"` +} + +// Validate the EditIssueOption struct +func (opt EditIssueOption) Validate() error { + if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// EditIssue modify an existing issue for a given repository +func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), + jsonHeader, bytes.NewReader(body), issue) + c.issueBackwardsCompatibility(issue) + return issue, resp, err +} + +// DeleteIssue delete a issue from a repository +func (c *Client) DeleteIssue(user, repo string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/repos/%s/%s/issues/%d", user, repo, id), + nil, nil) +} + +func (c *Client) issueBackwardsCompatibility(issue *Issue) { + if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil { + c.mutex.RLock() + issue.HTMLURL = fmt.Sprintf("%s/%s/issues/%d", c.url, issue.Repository.FullName, issue.Index) + c.mutex.RUnlock() + } +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_attachment.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_attachment.go new file mode 100644 index 0000000..5455106 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_attachment.go @@ -0,0 +1,91 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" +) + +// ListIssueAttachments lists all attachments for an issue. +func (c *Client) ListIssueAttachments(owner, repo string, index int64) ([]*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + attachments := make([]*Attachment, 0, 10) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/issues/%d/assets", owner, repo, index), + nil, nil, &attachments) + return attachments, resp, err +} + +// GetIssueAttachment gets an issue attachment. +func (c *Client) GetIssueAttachment(owner, repo string, index, attachmentID int64) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + attachment := new(Attachment) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/issues/%d/assets/%d", owner, repo, index, attachmentID), + nil, nil, attachment) + return attachment, resp, err +} + +// CreateIssueAttachment uploads an attachment for an issue. +func (c *Client) CreateIssueAttachment(owner, repo string, index int64, file io.Reader, filename string) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("attachment", filename) + if err != nil { + return nil, nil, err + } + if _, err = io.Copy(part, file); err != nil { + return nil, nil, err + } + if err = writer.Close(); err != nil { + return nil, nil, err + } + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/assets", owner, repo, index)) + link.RawQuery = url.Values{"name": []string{filename}}.Encode() + + attachment := new(Attachment) + resp, err := c.getParsedResponse("POST", link.String(), http.Header{"Content-Type": []string{writer.FormDataContentType()}}, body, attachment) + return attachment, resp, err +} + +// EditIssueAttachment updates an issue attachment. +func (c *Client) EditIssueAttachment(owner, repo string, index, attachmentID int64, form EditAttachmentOptions) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&form) + if err != nil { + return nil, nil, err + } + attachment := new(Attachment) + resp, err := c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/issues/%d/assets/%d", owner, repo, index, attachmentID), + jsonHeader, bytes.NewReader(body), attachment) + return attachment, resp, err +} + +// DeleteIssueAttachment deletes an issue attachment. +func (c *Client) DeleteIssueAttachment(owner, repo string, index, attachmentID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/repos/%s/%s/issues/%d/assets/%d", owner, repo, index, attachmentID), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_comment.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_comment.go new file mode 100644 index 0000000..acaa4b5 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_comment.go @@ -0,0 +1,233 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "time" +) + +// Comment represents a comment on a commit or issue +type Comment struct { + ID int64 `json:"id"` + HTMLURL string `json:"html_url"` + PRURL string `json:"pull_request_url"` + IssueURL string `json:"issue_url"` + Poster *User `json:"user"` + OriginalAuthor string `json:"original_author"` + OriginalAuthorID int64 `json:"original_author_id"` + Body string `json:"body"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + Attachments []*Attachment `json:"assets"` +} + +// ListIssueCommentOptions list comment options +type ListIssueCommentOptions struct { + ListOptions + Since time.Time + Before time.Time +} + +// QueryEncode turns options into querystring argument +func (opt *ListIssueCommentOptions) QueryEncode() string { + query := opt.getURLQuery() + if !opt.Since.IsZero() { + query.Add("since", opt.Since.Format(time.RFC3339)) + } + if !opt.Before.IsZero() { + query.Add("before", opt.Before.Format(time.RFC3339)) + } + return query.Encode() +} + +// ListIssueComments list comments on an issue. +func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index)) + link.RawQuery = opt.QueryEncode() + comments := make([]*Comment, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments) + return comments, resp, err +} + +// ListRepoIssueComments list comments for a given repo. +func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentOptions) ([]*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo)) + link.RawQuery = opt.QueryEncode() + comments := make([]*Comment, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments) + return comments, resp, err +} + +// GetIssueComment get a comment for a given repo by id. +func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + comment := new(Comment) + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return comment, nil, err + } + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, id), nil, nil, &comment) + return comment, resp, err +} + +// CreateIssueCommentOption options for creating a comment on an issue +type CreateIssueCommentOption struct { + Body string `json:"body"` +} + +// Validate the CreateIssueCommentOption struct +func (opt CreateIssueCommentOption) Validate() error { + if len(opt.Body) == 0 { + return fmt.Errorf("body is empty") + } + return nil +} + +// CreateIssueComment create comment on an issue. +func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + comment := new(Comment) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment) + return comment, resp, err +} + +// EditIssueCommentOption options for editing a comment +type EditIssueCommentOption struct { + Body string `json:"body"` +} + +// Validate the EditIssueCommentOption struct +func (opt EditIssueCommentOption) Validate() error { + if len(opt.Body) == 0 { + return fmt.Errorf("body is empty") + } + return nil +} + +// EditIssueComment edits an issue comment. +func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditIssueCommentOption) (*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + comment := new(Comment) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), jsonHeader, bytes.NewReader(body), comment) + return comment, resp, err +} + +// DeleteIssueComment deletes an issue comment. +func (c *Client) DeleteIssueComment(owner, repo string, commentID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), nil, nil) +} + +// ListIssueCommentAttachments lists all attachments for a comment +func (c *Client) ListIssueCommentAttachments(owner, repo string, commentID int64) ([]*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + attachments := make([]*Attachment, 0, 10) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/issues/comments/%d/assets", owner, repo, commentID), + nil, nil, &attachments) + return attachments, resp, err +} + +// CreateIssueCommentAttachment uploads an attachment for a comment. +func (c *Client) CreateIssueCommentAttachment(owner, repo string, commentID int64, file io.Reader, filename string) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("attachment", filename) + if err != nil { + return nil, nil, err + } + if _, err = io.Copy(part, file); err != nil { + return nil, nil, err + } + if err = writer.Close(); err != nil { + return nil, nil, err + } + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments/%d/assets", owner, repo, commentID)) + link.RawQuery = url.Values{"name": []string{filename}}.Encode() + + attachment := new(Attachment) + resp, err := c.getParsedResponse("POST", link.String(), http.Header{"Content-Type": []string{writer.FormDataContentType()}}, body, attachment) + return attachment, resp, err +} + +// GetIssueCommentAttachment gets a comment attachment +func (c *Client) GetIssueCommentAttachment(owner, repo string, commentID, attachmentID int64) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + attachment := new(Attachment) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/issues/comments/%d/assets/%d", owner, repo, commentID, attachmentID), + nil, nil, &attachment) + return attachment, resp, err +} + +// EditIssueCommentAttachment updates a comment attachment +func (c *Client) EditIssueCommentAttachment(owner, repo string, commentID, attachmentID int64, form EditAttachmentOptions) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&form) + if err != nil { + return nil, nil, err + } + attachment := new(Attachment) + resp, err := c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/issues/comments/%d/assets/%d", owner, repo, commentID, attachmentID), + jsonHeader, bytes.NewReader(body), attachment) + return attachment, resp, err +} + +// DeleteIssueCommentAttachment deletes a comment attachment +func (c *Client) DeleteIssueCommentAttachment(owner, repo string, commentID, attachmentID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/repos/%s/%s/issues/comments/%d/assets/%d", owner, repo, commentID, attachmentID), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_ext.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_ext.go new file mode 100644 index 0000000..78c58d5 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_ext.go @@ -0,0 +1,191 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" +) + +// IssueBlockedBy represents an issue that blocks another issue +type IssueBlockedBy struct { + Index int64 `json:"index"` + Title string `json:"title"` + State string `json:"state"` + CreatedAt time.Time `json:"created_at"` +} + +// ListIssueBlocksOptions options for listing issue blocks +type ListIssueBlocksOptions struct { + ListOptions +} + +// ListIssueBlocks lists issues that are blocked by the specified issue with pagination +func (c *Client) ListIssueBlocks(owner, repo string, index int64, opt ListIssueBlocksOptions) ([]*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/blocks", owner, repo, index)) + opt.setDefaults() + link.RawQuery = opt.getURLQuery().Encode() + issues := make([]*Issue, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues) + return issues, resp, err +} + +// IssueMeta represents issue reference for blocking/dependency operations +type IssueMeta struct { + Index int64 `json:"index"` +} + +// CreateIssueBlocking blocks an issue with another issue +func (c *Client) CreateIssueBlocking(owner, repo string, index int64, opt IssueMeta) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/issues/%d/blocks", owner, repo, index), + jsonHeader, bytes.NewReader(body), &issue) + return issue, resp, err +} + +// RemoveIssueBlocking removes an issue block +func (c *Client) RemoveIssueBlocking(owner, repo string, index int64, opt IssueMeta) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/issues/%d/blocks", owner, repo, index), + jsonHeader, bytes.NewReader(body), &issue) + return issue, resp, err +} + +// ListIssueDependenciesOptions options for listing issue dependencies +type ListIssueDependenciesOptions struct { + ListOptions +} + +// ListIssueDependencies lists issues that block the specified issue (its dependencies) with pagination +func (c *Client) ListIssueDependencies(owner, repo string, index int64, opt ListIssueDependenciesOptions) ([]*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/dependencies", owner, repo, index)) + opt.setDefaults() + link.RawQuery = opt.getURLQuery().Encode() + issues := make([]*Issue, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues) + return issues, resp, err +} + +// CreateIssueDependency creates a new issue dependency +func (c *Client) CreateIssueDependency(owner, repo string, index int64, opt IssueMeta) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/issues/%d/dependencies", owner, repo, index), + jsonHeader, bytes.NewReader(body), &issue) + return issue, resp, err +} + +// RemoveIssueDependency removes an issue dependency +func (c *Client) RemoveIssueDependency(owner, repo string, index int64, opt IssueMeta) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/issues/%d/dependencies", owner, repo, index), + jsonHeader, bytes.NewReader(body), &issue) + return issue, resp, err +} + +// LockIssueOption represents options for locking an issue +type LockIssueOption struct { + LockReason string `json:"lock_reason"` +} + +// LockIssue locks an issue +func (c *Client) LockIssue(owner, repo string, index int64, opt LockIssueOption) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("PUT", + fmt.Sprintf("/repos/%s/%s/issues/%d/lock", owner, repo, index), + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// UnlockIssue unlocks an issue +func (c *Client) UnlockIssue(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", + fmt.Sprintf("/repos/%s/%s/issues/%d/lock", owner, repo, index), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// EditDeadlineOption represents options for updating issue deadline +type EditDeadlineOption struct { + Deadline *time.Time `json:"due_date"` +} + +// UpdateIssueDeadline updates an issue's deadline +func (c *Client) UpdateIssueDeadline(owner, repo string, index int64, opt EditDeadlineOption) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/issues/%d/deadline", owner, repo, index), + jsonHeader, bytes.NewReader(body), &issue) + return issue, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_label.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_label.go new file mode 100644 index 0000000..1868cd0 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_label.go @@ -0,0 +1,72 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// GetIssueLabels get labels of one issue via issue id +func (c *Client) GetIssueLabels(owner, repo string, index int64, opts ListLabelsOptions) ([]*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + labels := make([]*Label, 0, 5) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/labels?%s", owner, repo, index, opts.getURLQuery().Encode()), nil, nil, &labels) + return labels, resp, err +} + +// IssueLabelsOption a collection of labels +type IssueLabelsOption struct { + // list of label IDs + Labels []int64 `json:"labels"` +} + +// AddIssueLabels add one or more labels to one issue +func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + var labels []*Label + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) + return labels, resp, err +} + +// ReplaceIssueLabels replace old labels of issue with new labels +func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + var labels []*Label + resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) + return labels, resp, err +} + +// DeleteIssueLabel delete one label of one issue by issue id and label id +// TODO: maybe we need delete by label name and issue id +func (c *Client) DeleteIssueLabel(owner, repo string, index, label int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, label), nil, nil) +} + +// ClearIssueLabels delete all the labels of one issue. +func (c *Client) ClearIssueLabels(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_milestone.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_milestone.go new file mode 100644 index 0000000..87fd493 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_milestone.go @@ -0,0 +1,235 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" +) + +// Milestone milestone is a collection of issues on one repository +type Milestone struct { + ID int64 `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + State StateType `json:"state"` + OpenIssues int `json:"open_issues"` + ClosedIssues int `json:"closed_issues"` + Created time.Time `json:"created_at"` + Updated *time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` + Deadline *time.Time `json:"due_on"` +} + +// ListMilestoneOption list milestone options +type ListMilestoneOption struct { + ListOptions + // open, closed, all + State StateType + Name string +} + +// QueryEncode turns options into querystring argument +func (opt *ListMilestoneOption) QueryEncode() string { + query := opt.getURLQuery() + if opt.State != "" { + query.Add("state", string(opt.State)) + } + if len(opt.Name) != 0 { + query.Add("name", opt.Name) + } + return query.Encode() +} + +// ListRepoMilestones list all the milestones of one repository +func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption) ([]*Milestone, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + milestones := make([]*Milestone, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/milestones", owner, repo)) + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &milestones) + return milestones, resp, err +} + +// GetMilestone get one milestone by repo name and milestone id +func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone) + return milestone, resp, err +} + +// GetMilestoneByName get one milestone by repo and milestone name +func (c *Client) GetMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) { + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + // backwards compatibility mode + m, resp, err := c.resolveMilestoneByName(owner, repo, name) + return m, resp, err + } + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone) + return milestone, resp, err +} + +// CreateMilestoneOption options for creating a milestone +type CreateMilestoneOption struct { + Title string `json:"title"` + Description string `json:"description"` + State StateType `json:"state"` + Deadline *time.Time `json:"due_on"` +} + +// Validate the CreateMilestoneOption struct +func (opt CreateMilestoneOption) Validate() error { + if len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// CreateMilestone create one milestone with options +func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/milestones", owner, repo), jsonHeader, bytes.NewReader(body), milestone) + + // make creating closed milestones need gitea >= v1.13.0 + // this make it backwards compatible + if err == nil && opt.State == StateClosed && milestone.State != StateClosed { + closed := StateClosed + return c.EditMilestone(owner, repo, milestone.ID, EditMilestoneOption{ + State: &closed, + }) + } + + return milestone, resp, err +} + +// EditMilestoneOption options for editing a milestone +type EditMilestoneOption struct { + Title string `json:"title"` + Description *string `json:"description"` + State *StateType `json:"state"` + Deadline *time.Time `json:"due_on"` +} + +// Validate the EditMilestoneOption struct +func (opt EditMilestoneOption) Validate() error { + if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// EditMilestone modify milestone with options +func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), milestone) + return milestone, resp, err +} + +// EditMilestoneByName modify milestone with options +func (c *Client) EditMilestoneByName(owner, repo, name string, opt EditMilestoneOption) (*Milestone, *Response, error) { + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + // backwards compatibility mode + m, _, err := c.resolveMilestoneByName(owner, repo, name) + if err != nil { + return nil, nil, err + } + return c.EditMilestone(owner, repo, m.ID, opt) + } + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), milestone) + return milestone, resp, err +} + +// DeleteMilestone delete one milestone by id +func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil) +} + +// DeleteMilestoneByName delete one milestone by name +func (c *Client) DeleteMilestoneByName(owner, repo, name string) (*Response, error) { + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + // backwards compatibility mode + m, _, err := c.resolveMilestoneByName(owner, repo, name) + if err != nil { + return nil, err + } + return c.DeleteMilestone(owner, repo, m.ID) + } + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil) +} + +// resolveMilestoneByName is a fallback method to find milestone id by name +func (c *Client) resolveMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) { + for i := 1; ; i++ { + miles, resp, err := c.ListRepoMilestones(owner, repo, ListMilestoneOption{ + ListOptions: ListOptions{ + Page: i, + }, + State: "all", + }) + if err != nil { + return nil, nil, err + } + if len(miles) == 0 { + return nil, nil, fmt.Errorf("milestone '%s' do not exist", name) + } + for _, m := range miles { + if strings.EqualFold(strings.TrimSpace(m.Title), strings.TrimSpace(name)) { + return m, resp, nil + } + } + } +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_pin.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_pin.go new file mode 100644 index 0000000..bd5d426 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_pin.go @@ -0,0 +1,73 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" +) + +// ListRepoPinnedIssues lists a repo's pinned issues +func (c *Client) ListRepoPinnedIssues(owner, repo string) ([]*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + issues := make([]*Issue, 0, 5) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/issues/pinned", owner, repo), + jsonHeader, nil, &issues) + return issues, resp, err +} + +// PinIssue pins an issue +func (c *Client) PinIssue(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", + fmt.Sprintf("/repos/%s/%s/issues/%d/pin", owner, repo, index), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// UnpinIssue unpins an issue +func (c *Client) UnpinIssue(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", + fmt.Sprintf("/repos/%s/%s/issues/%d/pin", owner, repo, index), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// MoveIssuePin moves a pinned issue to the given position +func (c *Client) MoveIssuePin(owner, repo string, index, position int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("PATCH", + fmt.Sprintf("/repos/%s/%s/issues/%d/pin/%d", owner, repo, index, position), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_reaction.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_reaction.go new file mode 100644 index 0000000..ed7eb21 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_reaction.go @@ -0,0 +1,118 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// Reaction contain one reaction +type Reaction struct { + User *User `json:"user"` + Reaction string `json:"content"` + Created time.Time `json:"created_at"` +} + +// ListIssueReactionsOptions options for listing issue reactions +type ListIssueReactionsOptions struct { + ListOptions +} + +// GetIssueReactions get a list reactions of an issue +// +// Deprecated: Use ListIssueReactions instead, which supports pagination. +func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction, *Response, error) { + return c.ListIssueReactions(owner, repo, index, ListIssueReactionsOptions{}) +} + +// ListIssueReactions get a list of reactions for an issue with pagination +func (c *Client) ListIssueReactions(owner, repo string, index int64, opt ListIssueReactionsOptions) ([]*Reaction, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index)) + opt.setDefaults() + link.RawQuery = opt.getURLQuery().Encode() + reactions := make([]*Reaction, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &reactions) + return reactions, resp, err +} + +// GetIssueCommentReactions get a list of reactions from a comment of an issue +func (c *Client) GetIssueCommentReactions(owner, repo string, commentID int64) ([]*Reaction, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + reactions := make([]*Reaction, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), nil, nil, &reactions) + return reactions, resp, err +} + +// editReactionOption contain the reaction type +type editReactionOption struct { + Reaction string `json:"content"` +} + +// PostIssueReaction add a reaction to an issue +func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction string) (*Reaction, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + reactionResponse := new(Reaction) + body, err := json.Marshal(&editReactionOption{Reaction: reaction}) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), + jsonHeader, bytes.NewReader(body), reactionResponse) + return reactionResponse, resp, err +} + +// DeleteIssueReaction remove a reaction from an issue +func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + body, err := json.Marshal(&editReactionOption{Reaction: reaction}) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), jsonHeader, bytes.NewReader(body)) +} + +// PostIssueCommentReaction add a reaction to a comment of an issue +func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Reaction, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + reactionResponse := new(Reaction) + body, err := json.Marshal(&editReactionOption{Reaction: reaction}) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), + jsonHeader, bytes.NewReader(body), reactionResponse) + return reactionResponse, resp, err +} + +// DeleteIssueCommentReaction remove a reaction from a comment of an issue +func (c *Client) DeleteIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + body, err := json.Marshal(&editReactionOption{Reaction: reaction}) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), + jsonHeader, bytes.NewReader(body)) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_stopwatch.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_stopwatch.go new file mode 100644 index 0000000..51d39f4 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_stopwatch.go @@ -0,0 +1,70 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "time" +) + +// StopWatch represents a running stopwatch of an issue / pr +type StopWatch struct { + Created time.Time `json:"created"` + Seconds int64 `json:"seconds"` + Duration string `json:"duration"` + IssueIndex int64 `json:"issue_index"` + IssueTitle string `json:"issue_title"` + RepoOwnerName string `json:"repo_owner_name"` + RepoName string `json:"repo_name"` +} + +// ListStopwatchesOptions options for listing stopwatches +type ListStopwatchesOptions struct { + ListOptions +} + +// GetMyStopwatches list all stopwatches +// +// Deprecated: Use ListMyStopwatches instead, which supports pagination. +func (c *Client) GetMyStopwatches() ([]*StopWatch, *Response, error) { + return c.ListMyStopwatches(ListStopwatchesOptions{}) +} + +// ListMyStopwatches list all stopwatches with pagination +func (c *Client) ListMyStopwatches(opt ListStopwatchesOptions) ([]*StopWatch, *Response, error) { + link, _ := url.Parse("/user/stopwatches") + opt.setDefaults() + link.RawQuery = opt.getURLQuery().Encode() + stopwatches := make([]*StopWatch, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &stopwatches) + return stopwatches, resp, err +} + +// DeleteIssueStopwatch delete / cancel a specific stopwatch +func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/delete", owner, repo, index), nil, nil) +} + +// StartIssueStopWatch starts a stopwatch for an existing issue for a given +// repository +func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index), nil, nil) +} + +// StopIssueStopWatch stops an existing stopwatch for an issue in a given +// repository +func (c *Client) StopIssueStopWatch(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_subscription.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_subscription.go new file mode 100644 index 0000000..e19ca88 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_subscription.go @@ -0,0 +1,103 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" + "net/url" +) + +// ListIssueSubscribersOptions options for listing issue subscribers +type ListIssueSubscribersOptions struct { + ListOptions +} + +// GetIssueSubscribers get list of users who subscribed on an issue +// +// Deprecated: Use ListIssueSubscribers instead, which supports pagination. +func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User, *Response, error) { + return c.ListIssueSubscribers(owner, repo, index, ListIssueSubscribersOptions{}) +} + +// ListIssueSubscribers get list of users who subscribed on an issue with pagination +func (c *Client) ListIssueSubscribers(owner, repo string, index int64, opt ListIssueSubscribersOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions", owner, repo, index)) + opt.setDefaults() + link.RawQuery = opt.getURLQuery().Encode() + subscribers := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &subscribers) + return subscribers, resp, err +} + +// AddIssueSubscription Subscribe user to issue +func (c *Client) AddIssueSubscription(owner, repo string, index int64, user string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil) + if err != nil { + return resp, err + } + if status == http.StatusCreated { + return resp, nil + } + if status == http.StatusOK { + return resp, fmt.Errorf("already subscribed") + } + return resp, fmt.Errorf("unexpected Status: %d", status) +} + +// DeleteIssueSubscription unsubscribe user from issue +func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil) + if err != nil { + return resp, err + } + if status == http.StatusCreated { + return resp, nil + } + if status == http.StatusOK { + return resp, fmt.Errorf("already unsubscribed") + } + return resp, fmt.Errorf("unexpected Status: %d", status) +} + +// CheckIssueSubscription check if current user is subscribed to an issue +func (c *Client) CheckIssueSubscription(owner, repo string, index int64) (*WatchInfo, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + wi := new(WatchInfo) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/check", owner, repo, index), nil, nil, wi) + return wi, resp, err +} + +// IssueSubscribe subscribe current user to an issue +func (c *Client) IssueSubscribe(owner, repo string, index int64) (*Response, error) { + u, _, err := c.GetMyUserInfo() + if err != nil { + return nil, err + } + return c.AddIssueSubscription(owner, repo, index, u.UserName) +} + +// IssueUnSubscribe unsubscribe current user from an issue +func (c *Client) IssueUnSubscribe(owner, repo string, index int64) (*Response, error) { + u, _, err := c.GetMyUserInfo() + if err != nil { + return nil, err + } + return c.DeleteIssueSubscription(owner, repo, index, u.UserName) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_template.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_template.go new file mode 100644 index 0000000..e6ac1e9 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_template.go @@ -0,0 +1,97 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" +) + +// IssueTemplate provides metadata and content on an issue template. +// There are two types of issue templates: .Markdown- and .Form-based. +type IssueTemplate struct { + Name string `json:"name"` + About string `json:"about"` + Filename string `json:"file_name"` + IssueTitle string `json:"title"` + IssueLabels []string `json:"labels"` + IssueRef string `json:"ref"` + // If non-nil, this is a form-based template + Form []IssueFormElement `json:"body"` + // Should only be used when .Form is nil. + MarkdownContent string `json:"content"` +} + +// IssueFormElement describes a part of a IssueTemplate form +type IssueFormElement struct { + ID string `json:"id"` + Type IssueFormElementType `json:"type"` + Attributes IssueFormElementAttributes `json:"attributes"` + Validations IssueFormElementValidations `json:"validations"` +} + +// IssueFormElementAttributes contains the combined set of attributes available on all element types. +type IssueFormElementAttributes struct { + // required for all element types. + // A brief description of the expected user input, which is also displayed in the form. + Label string `json:"label"` + // required for element types "dropdown", "checkboxes" + // for dropdown, contains the available options + Options []string `json:"options"` + // for element types "markdown", "textarea", "input" + // Text that is pre-filled in the input + Value string `json:"value"` + // for element types "textarea", "input", "dropdown", "checkboxes" + // A description of the text area to provide context or guidance, which is displayed in the form. + Description string `json:"description"` + // for element types "textarea", "input" + // A semi-opaque placeholder that renders in the text area when empty. + Placeholder string `json:"placeholder"` + // for element types "textarea" + // A language specifier. If set, the input is rendered as codeblock with syntax highlighting. + SyntaxHighlighting string `json:"render"` + // for element types "dropdown" + Multiple bool `json:"multiple"` +} + +// IssueFormElementValidations contains the combined set of validations available on all element types. +type IssueFormElementValidations struct { + // for all element types + Required bool `json:"required"` + // for element types "input" + IsNumber bool `json:"is_number"` + // for element types "input" + Regex string `json:"regex"` +} + +// IssueFormElementType is an enum +type IssueFormElementType string + +const ( + // IssueFormElementMarkdown is markdown rendered to the form for context, but omitted in the resulting issue + IssueFormElementMarkdown IssueFormElementType = "markdown" + // IssueFormElementTextarea is a multi line input + IssueFormElementTextarea IssueFormElementType = "textarea" + // IssueFormElementInput is a single line input + IssueFormElementInput IssueFormElementType = "input" + // IssueFormElementDropdown is a select form + IssueFormElementDropdown IssueFormElementType = "dropdown" + // IssueFormElementCheckboxes are a multi checkbox input + IssueFormElementCheckboxes IssueFormElementType = "checkboxes" +) + +// GetIssueTemplates lists all issue templates of the repository +func (c *Client) GetIssueTemplates(owner, repo string) ([]*IssueTemplate, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + templates := new([]*IssueTemplate) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issue_templates", owner, repo), nil, nil, templates) + return *templates, resp, err +} + +// IsForm tells if this template is a form instead of a markdown-based template. +func (t IssueTemplate) IsForm() bool { + return t.Form != nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_timeline.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_timeline.go new file mode 100644 index 0000000..586087d --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_timeline.go @@ -0,0 +1,44 @@ +// Copyright 2025 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "time" +) + +// Comment represents a comment on a commit or issue +type TimelineComment struct { + ID int64 `json:"id"` + HTMLURL string `json:"html_url"` + PRURL string `json:"pull_request_url"` + IssueURL string `json:"issue_url"` + Poster *User `json:"user"` + OriginalAuthor string `json:"original_author"` + OriginalAuthorID int64 `json:"original_author_id"` + Body string `json:"body"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + Type string `json:"type"` + Label []*Label `json:"label"` + NewMilestone *Milestone `json:"milestone"` + OldMilestone *Milestone `json:"old_milestone"` + NewTitle string `json:"new_title"` + OldTitle string `json:"old_title"` +} + +// ListIssueTimeline list timeline on an issue. +func (c *Client) ListIssueTimeline(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*TimelineComment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/timeline", owner, repo, index)) + link.RawQuery = opt.QueryEncode() + timelineComments := make([]*TimelineComment, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &timelineComments) + return timelineComments, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go b/cli/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go new file mode 100644 index 0000000..286430d --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go @@ -0,0 +1,150 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// TrackedTime worked time for an issue / pr +type TrackedTime struct { + ID int64 `json:"id"` + Created time.Time `json:"created"` + // Time in seconds + Time int64 `json:"time"` + // deprecated (only for backwards compatibility) + UserID int64 `json:"user_id"` + UserName string `json:"user_name"` + // deprecated (only for backwards compatibility) + IssueID int64 `json:"issue_id"` + Issue *Issue `json:"issue"` +} + +// ListTrackedTimesOptions options for listing repository's tracked times +type ListTrackedTimesOptions struct { + ListOptions + Since time.Time + Before time.Time + // User filter is only used by ListRepoTrackedTimes !!! + User string +} + +// QueryEncode turns options into querystring argument +func (opt *ListTrackedTimesOptions) QueryEncode() string { + query := opt.getURLQuery() + + if !opt.Since.IsZero() { + query.Add("since", opt.Since.Format(time.RFC3339)) + } + if !opt.Before.IsZero() { + query.Add("before", opt.Before.Format(time.RFC3339)) + } + + if len(opt.User) != 0 { + query.Add("user", opt.User) + } + + return query.Encode() +} + +// ListRepoTrackedTimes list tracked times of a repository +func (c *Client) ListRepoTrackedTimes(owner, repo string, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/times", owner, repo)) + opt.setDefaults() + link.RawQuery = opt.QueryEncode() + times := make([]*TrackedTime, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ×) + return times, resp, err +} + +// GetMyTrackedTimes list tracked times of the current user +// +// Deprecated: Use ListMyTrackedTimes instead, which supports pagination and filtering. +func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, *Response, error) { + return c.ListMyTrackedTimes(ListTrackedTimesOptions{}) +} + +// ListMyTrackedTimes list tracked times of the current user with pagination and filtering +func (c *Client) ListMyTrackedTimes(opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) { + link, _ := url.Parse("/user/times") + opt.setDefaults() + link.RawQuery = opt.QueryEncode() + times := make([]*TrackedTime, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ×) + return times, resp, err +} + +// AddTimeOption options for adding time to an issue +type AddTimeOption struct { + // time in seconds + Time int64 `json:"time"` + // optional + Created time.Time `json:"created"` + // optional + User string `json:"user_name"` +} + +// Validate the AddTimeOption struct +func (opt AddTimeOption) Validate() error { + if opt.Time == 0 { + return fmt.Errorf("no time to add") + } + return nil +} + +// AddTime adds time to issue with the given index +func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + t := new(TrackedTime) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), + jsonHeader, bytes.NewReader(body), t) + return t, resp, err +} + +// ListIssueTrackedTimes list tracked times of a single issue for a given repository +func (c *Client) ListIssueTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index)) + opt.setDefaults() + link.RawQuery = opt.QueryEncode() + times := make([]*TrackedTime, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ×) + return times, resp, err +} + +// ResetIssueTime reset tracked time of a single issue for a given repository +func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), jsonHeader, nil) +} + +// DeleteTime delete a specific tracked time by id of a single issue for a given repository +func (c *Client) DeleteTime(owner, repo string, index, timeID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), jsonHeader, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/list_options.go b/cli/vendor/code.gitea.io/sdk/gitea/list_options.go new file mode 100644 index 0000000..fb1aff4 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/list_options.go @@ -0,0 +1,40 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" +) + +// ListOptions options for using Gitea's API pagination +type ListOptions struct { + // Setting Page to -1 disables pagination on endpoints that support it. + // Page numbering starts at 1. + Page int + // The default value depends on the server config DEFAULT_PAGING_NUM + // The highest valid value depends on the server config MAX_RESPONSE_ITEMS + PageSize int +} + +func (o ListOptions) getURLQuery() url.Values { + query := make(url.Values) + query.Add("page", fmt.Sprintf("%d", o.Page)) + query.Add("limit", fmt.Sprintf("%d", o.PageSize)) + + return query +} + +// setDefaults applies default pagination options. +// If .Page is set to -1, it will disable pagination. +// WARNING: This function is not idempotent, make sure to never call this method twice! +func (o *ListOptions) setDefaults() { + if o.Page < 0 { + o.Page, o.PageSize = 0, 0 + return + } else if o.Page == 0 { + o.Page = 1 + } +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/miscellaneous.go b/cli/vendor/code.gitea.io/sdk/gitea/miscellaneous.go new file mode 100644 index 0000000..a04e00a --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/miscellaneous.go @@ -0,0 +1,215 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// GitignoreTemplateInfo represents a gitignore template +type GitignoreTemplateInfo struct { + Name string `json:"name"` + Source string `json:"source"` +} + +// LabelTemplate represents a label template +type LabelTemplate struct { + Name string `json:"name"` + Color string `json:"color"` + Description string `json:"description"` + Exclusive bool `json:"exclusive"` +} + +// LicensesTemplateListEntry represents a license in the list +type LicensesTemplateListEntry struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` +} + +// LicenseTemplateInfo represents a license template +type LicenseTemplateInfo struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` + Body string `json:"body"` + Implementation string `json:"implementation"` +} + +// MarkdownOption represents options for rendering markdown +type MarkdownOption struct { + Text string `json:"Text"` + Mode string `json:"Mode"` + Context string `json:"Context"` + Wiki bool `json:"Wiki"` +} + +// MarkupOption represents options for rendering markup +type MarkupOption struct { + Text string `json:"Text"` + Mode string `json:"Mode"` + Context string `json:"Context"` + FilePath string `json:"FilePath"` + Wiki bool `json:"Wiki"` +} + +// NodeInfo represents nodeinfo about the server +type NodeInfo struct { + Version string `json:"version"` + Software NodeInfoSoftware `json:"software"` + Protocols []string `json:"protocols"` + Services NodeInfoServices `json:"services"` + OpenRegistrations bool `json:"openRegistrations"` + Usage NodeInfoUsage `json:"usage"` + Metadata map[string]interface{} `json:"metadata"` +} + +// NodeInfoSoftware represents software information +type NodeInfoSoftware struct { + Name string `json:"name"` + Version string `json:"version"` + Repository string `json:"repository"` + Homepage string `json:"homepage"` +} + +// NodeInfoServices represents third party services +type NodeInfoServices struct { + Inbound []string `json:"inbound"` + Outbound []string `json:"outbound"` +} + +// NodeInfoUsage represents usage statistics +type NodeInfoUsage struct { + Users NodeInfoUsageUsers `json:"users"` + LocalPosts int64 `json:"localPosts"` + LocalComments int64 `json:"localComments"` +} + +// NodeInfoUsageUsers represents user statistics +type NodeInfoUsageUsers struct { + Total int64 `json:"total"` + ActiveHalfyear int64 `json:"activeHalfyear"` + ActiveMonth int64 `json:"activeMonth"` +} + +// ListGitignoresTemplates lists all gitignore templates +func (c *Client) ListGitignoresTemplates() ([]string, *Response, error) { + templates := make([]string, 0, 10) + resp, err := c.getParsedResponse("GET", "/gitignore/templates", jsonHeader, nil, &templates) + return templates, resp, err +} + +// GetGitignoreTemplateInfo gets information about a gitignore template +func (c *Client) GetGitignoreTemplateInfo(name string) (*GitignoreTemplateInfo, *Response, error) { + if err := escapeValidatePathSegments(&name); err != nil { + return nil, nil, err + } + template := new(GitignoreTemplateInfo) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/gitignore/templates/%s", name), + jsonHeader, nil, &template) + return template, resp, err +} + +// ListLabelTemplates lists all label templates +func (c *Client) ListLabelTemplates() ([]string, *Response, error) { + templates := make([]string, 0, 10) + resp, err := c.getParsedResponse("GET", "/label/templates", jsonHeader, nil, &templates) + return templates, resp, err +} + +// GetLabelTemplate gets all labels in a template +func (c *Client) GetLabelTemplate(name string) ([]*LabelTemplate, *Response, error) { + if err := escapeValidatePathSegments(&name); err != nil { + return nil, nil, err + } + labels := make([]*LabelTemplate, 0, 10) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/label/templates/%s", name), + jsonHeader, nil, &labels) + return labels, resp, err +} + +// ListLicenseTemplates lists all license templates +func (c *Client) ListLicenseTemplates() ([]*LicensesTemplateListEntry, *Response, error) { + licenses := make([]*LicensesTemplateListEntry, 0, 10) + resp, err := c.getParsedResponse("GET", "/licenses", jsonHeader, nil, &licenses) + return licenses, resp, err +} + +// GetLicenseTemplateInfo gets information about a license template +func (c *Client) GetLicenseTemplateInfo(name string) (*LicenseTemplateInfo, *Response, error) { + if err := escapeValidatePathSegments(&name); err != nil { + return nil, nil, err + } + license := new(LicenseTemplateInfo) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/licenses/%s", name), + jsonHeader, nil, &license) + return license, resp, err +} + +// RenderMarkdown renders a markdown document as HTML +func (c *Client) RenderMarkdown(opt MarkdownOption) (string, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return "", nil, err + } + + html, resp, err := c.getResponse("POST", "/markdown", jsonHeader, bytes.NewReader(body)) + return string(html), resp, err +} + +// RenderMarkdownRaw renders raw markdown as HTML +func (c *Client) RenderMarkdownRaw(markdown string) (string, *Response, error) { + html, resp, err := c.getResponse("POST", "/markdown/raw", + map[string][]string{"Content-Type": {"text/plain"}}, + bytes.NewReader([]byte(markdown))) + if err != nil { + return "", resp, err + } + return string(html), resp, err +} + +// RenderMarkup renders a markup document as HTML +func (c *Client) RenderMarkup(opt MarkupOption) (string, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return "", nil, err + } + + html, resp, err := c.getResponse("POST", "/markup", jsonHeader, bytes.NewReader(body)) + if err != nil { + return "", resp, err + } + return string(html), resp, err +} + +// GetNodeInfo gets the nodeinfo of the Gitea application +func (c *Client) GetNodeInfo() (*NodeInfo, *Response, error) { + nodeInfo := new(NodeInfo) + resp, err := c.getParsedResponse("GET", "/nodeinfo", jsonHeader, nil, &nodeInfo) + return nodeInfo, resp, err +} + +// GetSigningKeyGPG gets the default GPG signing key +func (c *Client) GetSigningKeyGPG() (string, *Response, error) { + key, resp, err := c.getResponse("GET", "/signing-key.gpg", nil, nil) + if err != nil { + return "", resp, err + } + return string(key), resp, err +} + +// GetSigningKeySSH gets the default SSH signing key +func (c *Client) GetSigningKeySSH() (string, *Response, error) { + key, resp, err := c.getResponse("GET", "/signing-key.pub", nil, nil) + if err != nil { + return "", resp, err + } + return string(key), resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/notifications.go b/cli/vendor/code.gitea.io/sdk/gitea/notifications.go new file mode 100644 index 0000000..a75a41e --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/notifications.go @@ -0,0 +1,257 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "time" +) + +// NotificationThread expose Notification on API +type NotificationThread struct { + ID int64 `json:"id"` + Repository *Repository `json:"repository"` + Subject *NotificationSubject `json:"subject"` + Unread bool `json:"unread"` + Pinned bool `json:"pinned"` + UpdatedAt time.Time `json:"updated_at"` + URL string `json:"url"` +} + +// NotificationSubject contains the notification subject (Issue/Pull/Commit) +type NotificationSubject struct { + Title string `json:"title"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + LatestCommentURL string `json:"latest_comment_url"` + LatestCommentHTMLURL string `json:"latest_comment_html_url"` + Type NotifySubjectType `json:"type"` + State NotifySubjectState `json:"state"` +} + +// NotifyStatus notification status type +type NotifyStatus string + +const ( + // NotifyStatusUnread was not read + NotifyStatusUnread NotifyStatus = "unread" + // NotifyStatusRead was already read by user + NotifyStatusRead NotifyStatus = "read" + // NotifyStatusPinned notification is pinned by user + NotifyStatusPinned NotifyStatus = "pinned" +) + +// NotifySubjectType represent type of notification subject +type NotifySubjectType string + +const ( + // NotifySubjectIssue an issue is subject of an notification + NotifySubjectIssue NotifySubjectType = "Issue" + // NotifySubjectPull an pull is subject of an notification + NotifySubjectPull NotifySubjectType = "Pull" + // NotifySubjectCommit an commit is subject of an notification + NotifySubjectCommit NotifySubjectType = "Commit" + // NotifySubjectRepository an repository is subject of an notification + NotifySubjectRepository NotifySubjectType = "Repository" +) + +// NotifySubjectState reflect state of notification subject +type NotifySubjectState string + +const ( + // NotifySubjectOpen if subject is a pull/issue and is open at the moment + NotifySubjectOpen NotifySubjectState = "open" + // NotifySubjectClosed if subject is a pull/issue and is closed at the moment + NotifySubjectClosed NotifySubjectState = "closed" + // NotifySubjectMerged if subject is a pull and got merged + NotifySubjectMerged NotifySubjectState = "merged" +) + +// ListNotificationOptions represents the filter options +type ListNotificationOptions struct { + ListOptions + Since time.Time + Before time.Time + Status []NotifyStatus + SubjectTypes []NotifySubjectType +} + +// MarkNotificationOptions represents the filter & modify options +type MarkNotificationOptions struct { + LastReadAt time.Time + Status []NotifyStatus + ToStatus NotifyStatus +} + +// QueryEncode encode options to url query +func (opt *ListNotificationOptions) QueryEncode() string { + query := opt.getURLQuery() + if !opt.Since.IsZero() { + query.Add("since", opt.Since.Format(time.RFC3339)) + } + if !opt.Before.IsZero() { + query.Add("before", opt.Before.Format(time.RFC3339)) + } + for _, s := range opt.Status { + query.Add("status-types", string(s)) + } + for _, s := range opt.SubjectTypes { + query.Add("subject-type", string(s)) + } + return query.Encode() +} + +// Validate the CreateUserOption struct +func (opt ListNotificationOptions) Validate(c *Client) error { + if len(opt.Status) != 0 { + return c.checkServerVersionGreaterThanOrEqual(version1_12_3) + } + return nil +} + +// QueryEncode encode options to url query +func (opt *MarkNotificationOptions) QueryEncode() string { + query := make(url.Values) + if !opt.LastReadAt.IsZero() { + query.Add("last_read_at", opt.LastReadAt.Format(time.RFC3339)) + } + for _, s := range opt.Status { + query.Add("status-types", string(s)) + } + if len(opt.ToStatus) != 0 { + query.Add("to-status", string(opt.ToStatus)) + } + return query.Encode() +} + +// Validate the CreateUserOption struct +func (opt MarkNotificationOptions) Validate(c *Client) error { + if len(opt.Status) != 0 || len(opt.ToStatus) != 0 { + return c.checkServerVersionGreaterThanOrEqual(version1_12_3) + } + return nil +} + +// CheckNotifications list users's notification threads +func (c *Client) CheckNotifications() (int64, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return 0, nil, err + } + new := struct { + New int64 `json:"new"` + }{} + + resp, err := c.getParsedResponse("GET", "/notifications/new", jsonHeader, nil, &new) + return new.New, resp, err +} + +// GetNotification get notification thread by ID +func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + thread := new(NotificationThread) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/notifications/threads/%d", id), nil, nil, thread) + return thread, resp, err +} + +// ReadNotification mark notification thread as read by ID +// It optionally takes a second argument if status has to be set other than 'read' +// The relevant notification will be returned as the first parameter when the Gitea server is 1.16.0 or higher. +func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*NotificationThread, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + link := fmt.Sprintf("/notifications/threads/%d", id) + if len(status) != 0 { + link += fmt.Sprintf("?to-status=%s", status[0]) + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil { + thread := &NotificationThread{} + resp, err := c.getParsedResponse("PATCH", link, nil, nil, thread) + return thread, resp, err + } + resp, err := c.doRequestWithStatusHandle("PATCH", link, nil, nil) + return nil, resp, err +} + +// ListNotifications list users's notification threads +func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*NotificationThread, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + link, _ := url.Parse("/notifications") + link.RawQuery = opt.QueryEncode() + threads := make([]*NotificationThread, 0, 10) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads) + return threads, resp, err +} + +// ReadNotifications mark notification threads as read +// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher. +func (c *Client) ReadNotifications(opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + link, _ := url.Parse("/notifications") + link.RawQuery = opt.QueryEncode() + + if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil { + threads := make([]*NotificationThread, 0, 10) + resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads) + return threads, resp, err + } + resp, err := c.doRequestWithStatusHandle("PUT", link.String(), nil, nil) + return nil, resp, err +} + +// ListRepoNotifications list users's notification threads on a specific repo +func (c *Client) ListRepoNotifications(owner, repo string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo)) + link.RawQuery = opt.QueryEncode() + threads := make([]*NotificationThread, 0, 10) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads) + return threads, resp, err +} + +// ReadRepoNotifications mark notification threads as read on a specific repo +// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher. +func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo)) + link.RawQuery = opt.QueryEncode() + + if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil { + threads := make([]*NotificationThread, 0, 10) + resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads) + return threads, resp, err + } + resp, err := c.doRequestWithStatusHandle("PUT", link.String(), nil, nil) + return nil, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/oauth2.go b/cli/vendor/code.gitea.io/sdk/gitea/oauth2.go new file mode 100644 index 0000000..7dfa14c --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/oauth2.go @@ -0,0 +1,93 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// Oauth2 represents an Oauth2 Application +type Oauth2 struct { + ID int64 `json:"id"` + Name string `json:"name"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RedirectURIs []string `json:"redirect_uris"` + ConfidentialClient bool `json:"confidential_client"` + Created time.Time `json:"created"` +} + +// ListOauth2Option for listing Oauth2 Applications +type ListOauth2Option struct { + ListOptions +} + +// CreateOauth2Option required options for creating an Application +type CreateOauth2Option struct { + Name string `json:"name"` + ConfidentialClient bool `json:"confidential_client"` + RedirectURIs []string `json:"redirect_uris"` +} + +// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object. +func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + oauth := new(Oauth2) + resp, err := c.getParsedResponse("POST", "/user/applications/oauth2", jsonHeader, bytes.NewReader(body), oauth) + return oauth, resp, err +} + +// UpdateOauth2 a specific Oauth2 Application by ID and return a completed Oauth2 object. +func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + oauth := new(Oauth2) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), jsonHeader, bytes.NewReader(body), oauth) + return oauth, resp, err +} + +// GetOauth2 a specific Oauth2 Application by ID. +func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + oauth2s := &Oauth2{} + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil, &oauth2s) + return oauth2s, resp, err +} + +// ListOauth2 all of your Oauth2 Applications. +func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + opt.setDefaults() + oauth2s := make([]*Oauth2, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2?%s", opt.getURLQuery().Encode()), nil, nil, &oauth2s) + return oauth2s, resp, err +} + +// DeleteOauth2 delete an Oauth2 application by ID +func (c *Client) DeleteOauth2(oauth2id int64) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, err + } + resp, err := c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil) + return resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/org.go b/cli/vendor/code.gitea.io/sdk/gitea/org.go new file mode 100644 index 0000000..59d143b --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/org.go @@ -0,0 +1,171 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" +) + +// Organization represents an organization +type Organization struct { + ID int64 `json:"id"` + Name string `json:"name"` + // Deprecated: Use Name instead. See https://github.com/go-gitea/gitea/blob/main/modules/structs/org.go#L29 + UserName string `json:"username"` + FullName string `json:"full_name"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` + Visibility string `json:"visibility"` + RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` +} + +// VisibleType defines the visibility +type VisibleType string + +const ( + // VisibleTypePublic Visible for everyone + VisibleTypePublic VisibleType = "public" + + // VisibleTypeLimited Visible for every connected user + VisibleTypeLimited VisibleType = "limited" + + // VisibleTypePrivate Visible only for organization's members + VisibleTypePrivate VisibleType = "private" +) + +// ListOrgsOptions options for listing organizations +type ListOrgsOptions struct { + ListOptions +} + +// ListOrgs lists all public organizations +func (c *Client) ListOrgs(opt ListOrgsOptions) ([]*Organization, *Response, error) { + opt.setDefaults() + link, _ := url.Parse("/orgs") + link.RawQuery = opt.getURLQuery().Encode() + orgs := make([]*Organization, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &orgs) + return orgs, resp, err +} + +// ListMyOrgs list all of current user's organizations +func (c *Client) ListMyOrgs(opt ListOrgsOptions) ([]*Organization, *Response, error) { + opt.setDefaults() + orgs := make([]*Organization, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs) + return orgs, resp, err +} + +// ListUserOrgs list all of some user's organizations +func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + orgs := make([]*Organization, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs?%s", user, opt.getURLQuery().Encode()), nil, nil, &orgs) + return orgs, resp, err +} + +// GetOrg get one organization by name +func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) { + if err := escapeValidatePathSegments(&orgname); err != nil { + return nil, nil, err + } + org := new(Organization) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org) + return org, resp, err +} + +// CreateOrgOption options for creating an organization +type CreateOrgOption struct { + Name string `json:"username"` + FullName string `json:"full_name"` + Email string `json:"email"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` + Visibility VisibleType `json:"visibility"` + RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` +} + +// checkVisibilityOpt check if mode exist +func checkVisibilityOpt(v VisibleType) bool { + return v == VisibleTypePublic || v == VisibleTypeLimited || v == VisibleTypePrivate +} + +// Validate the CreateOrgOption struct +func (opt CreateOrgOption) Validate() error { + if len(opt.Name) == 0 { + return fmt.Errorf("empty org name") + } + if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) { + return fmt.Errorf("invalid visibility option") + } + return nil +} + +// CreateOrg creates an organization +func (c *Client) CreateOrg(opt CreateOrgOption) (*Organization, *Response, error) { + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + org := new(Organization) + resp, err := c.getParsedResponse("POST", "/orgs", jsonHeader, bytes.NewReader(body), org) + return org, resp, err +} + +// EditOrgOption options for editing an organization +type EditOrgOption struct { + FullName string `json:"full_name"` + Email string `json:"email"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` + Visibility VisibleType `json:"visibility"` + RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"` +} + +// Validate the EditOrgOption struct +func (opt EditOrgOption) Validate() error { + if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) { + return fmt.Errorf("invalid visibility option") + } + return nil +} + +// EditOrg modify one organization via options +func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) { + if err := escapeValidatePathSegments(&orgname); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PATCH", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, bytes.NewReader(body)) +} + +// DeleteOrg deletes an organization +func (c *Client) DeleteOrg(orgname string) (*Response, error) { + if err := escapeValidatePathSegments(&orgname); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/org_action.go b/cli/vendor/code.gitea.io/sdk/gitea/org_action.go new file mode 100644 index 0000000..c14003d --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/org_action.go @@ -0,0 +1,203 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" +) + +// ListOrgActionSecretOption list OrgActionSecret options +type ListOrgActionSecretOption struct { + ListOptions +} + +// ListOrgActionSecret list an organization's secrets +func (c *Client) ListOrgActionSecret(org string, opt ListOrgActionSecretOption) ([]*Secret, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + secrets := make([]*Secret, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/orgs/%s/actions/secrets", org)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets) + return secrets, resp, err +} + +// ListOrgActionVariableOption lists ActionVariable options +type ListOrgActionVariableOption struct { + ListOptions +} + +// ListOrgActionVariable lists an organization's action variables +func (c *Client) ListOrgActionVariable(org string, opt ListOrgActionVariableOption) ([]*ActionVariable, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + variables := make([]*ActionVariable, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/orgs/%s/actions/variables", org)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &variables) + return variables, resp, err +} + +// GetOrgActionVariable gets a single organization's action variable by name +func (c *Client) GetOrgActionVariable(org, name string) (*ActionVariable, *Response, error) { + if err := escapeValidatePathSegments(&org, &name); err != nil { + return nil, nil, err + } + var variable ActionVariable + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/orgs/%s/actions/variables/%s", org, name), + jsonHeader, nil, &variable) + if err != nil { + return nil, resp, err + } + return &variable, resp, nil +} + +// CreateOrgActionVariable creates a variable for the specified organization in the Gitea Actions. +func (c *Client) CreateOrgActionVariable(org, name string, opt CreateActionVariableOption) (*Response, error) { + if err := escapeValidatePathSegments(&org, &name); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/orgs/%s/actions/variables/%s", org, name), jsonHeader, bytes.NewReader(body)) +} + +// UpdateOrgActionVariable updates a variable for the specified organization in the Gitea Actions. +func (c *Client) UpdateOrgActionVariable(org, name string, opt UpdateActionVariableOption) (*Response, error) { + if err := escapeValidatePathSegments(&org, &name); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/orgs/%s/actions/variables/%s", org, name), jsonHeader, bytes.NewReader(body)) +} + +// CreateOrgActionSecret creates a secret for the specified organization in the Gitea Actions. +func (c *Client) CreateOrgActionSecret(org, secretName string, opt CreateOrUpdateSecretOption) (*Response, error) { + if err := escapeValidatePathSegments(&org, &secretName); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/orgs/%s/actions/secrets/%s", org, secretName), jsonHeader, bytes.NewReader(body)) +} + +// DeleteOrgActionSecret deletes an organization's Actions secret. +func (c *Client) DeleteOrgActionSecret(org, secretName string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &secretName); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/orgs/%s/actions/secrets/%s", org, secretName), nil, nil) +} + +// DeleteOrgActionVariable deletes an organization's Actions variable. +func (c *Client) DeleteOrgActionVariable(org, name string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &name); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/orgs/%s/actions/variables/%s", org, name), nil, nil) +} + +// CreateOrgActionRunnerRegistrationToken creates an organization runner registration token. +func (c *Client) CreateOrgActionRunnerRegistrationToken(org string) (*RegistrationToken, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, nil, err + } + return c.createActionRegistrationToken(fmt.Sprintf("/orgs/%s/actions/runners/registration-token", org)) +} + +// ListOrgActionRunners lists organization-scoped Actions runners. +func (c *Client) ListOrgActionRunners(org string, opt ListActionRunnersOptions) (*ActionRunnersResponse, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.listActionRunners(fmt.Sprintf("/orgs/%s/actions/runners", org), opt) +} + +// GetOrgActionRunner gets one organization-scoped Actions runner. +func (c *Client) GetOrgActionRunner(org string, runnerID int64) (*ActionRunner, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.getActionRunner(fmt.Sprintf("/orgs/%s/actions/runners/%d", org, runnerID)) +} + +// DeleteOrgActionRunner deletes one organization-scoped Actions runner. +func (c *Client) DeleteOrgActionRunner(org string, runnerID int64) (*Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/orgs/%s/actions/runners/%d", org, runnerID), nil, nil) +} + +// UpdateOrgActionRunner updates one organization-scoped Actions runner. +func (c *Client) UpdateOrgActionRunner(org string, runnerID int64, opt EditActionRunnerOption) (*ActionRunner, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.updateActionRunner(fmt.Sprintf("/orgs/%s/actions/runners/%d", org, runnerID), opt) +} + +// ListOrgActionJobs lists organization-scoped Actions jobs. +func (c *Client) ListOrgActionJobs(org string, opt ListRepoActionJobsOptions) (*ActionWorkflowJobsResponse, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + return c.listActionJobs(fmt.Sprintf("/orgs/%s/actions/jobs", org), opt) +} + +// ListOrgActionRuns lists organization-scoped Actions workflow runs. +func (c *Client) ListOrgActionRuns(org string, opt ListRepoActionRunsOptions) (*ActionWorkflowRunsResponse, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + return c.listActionRuns(fmt.Sprintf("/orgs/%s/actions/runs", org), opt) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/org_block.go b/cli/vendor/code.gitea.io/sdk/gitea/org_block.go new file mode 100644 index 0000000..4261ec8 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/org_block.go @@ -0,0 +1,79 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" + "net/url" +) + +// ListOrgBlocksOptions options for listing organization blocks +type ListOrgBlocksOptions struct { + ListOptions +} + +// ListOrgBlocks lists users blocked by the organization +func (c *Client) ListOrgBlocks(org string, opt ListOrgBlocksOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/orgs/%s/blocks", org)) + link.RawQuery = opt.getURLQuery().Encode() + + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) + return users, resp, err +} + +// CheckOrgBlock checks if a user is blocked by the organization +func (c *Client) CheckOrgBlock(org, username string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&org, &username); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", + fmt.Sprintf("/orgs/%s/blocks/%s", org, username), + jsonHeader, nil) + if err != nil { + return false, resp, err + } + return status == http.StatusNoContent, resp, nil +} + +// BlockOrgUser blocks a user from the organization +func (c *Client) BlockOrgUser(org, username string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &username); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("PUT", + fmt.Sprintf("/orgs/%s/blocks/%s", org, username), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// UnblockOrgUser unblocks a user from the organization +func (c *Client) UnblockOrgUser(org, username string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &username); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", + fmt.Sprintf("/orgs/%s/blocks/%s", org, username), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/org_label.go b/cli/vendor/code.gitea.io/sdk/gitea/org_label.go new file mode 100644 index 0000000..c524235 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/org_label.go @@ -0,0 +1,116 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "regexp" + "strings" +) + +// ListOrgLabelsOptions options for listing organization labels +type ListOrgLabelsOptions struct { + ListOptions +} + +// ListOrgLabels returns the labels defined at the org level +func (c *Client) ListOrgLabels(orgName string, opt ListOrgLabelsOptions) ([]*Label, *Response, error) { + if err := escapeValidatePathSegments(&orgName); err != nil { + return nil, nil, err + } + opt.setDefaults() + labels := make([]*Label, 0, opt.PageSize) + link, _ := url.Parse(fmt.Sprintf("/orgs/%s/labels", orgName)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &labels) + return labels, resp, err +} + +type CreateOrgLabelOption struct { + // Name of the label + Name string `json:"name"` + // Color of the label in hex format without # + Color string `json:"color"` + // Description of the label + Description string `json:"description"` + // Whether this is an exclusive label + Exclusive bool `json:"exclusive"` +} + +// Validate the CreateLabelOption struct +func (opt CreateOrgLabelOption) Validate() error { + aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", opt.Color) + if err != nil { + return err + } + if !aw { + return fmt.Errorf("invalid color format") + } + if len(strings.TrimSpace(opt.Name)) == 0 { + return fmt.Errorf("empty name not allowed") + } + return nil +} + +// CreateOrgLabel creates a new label under an organization +func (c *Client) CreateOrgLabel(orgName string, opt CreateOrgLabelOption) (*Label, *Response, error) { + if err := escapeValidatePathSegments(&orgName); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + label := new(Label) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/labels", orgName), jsonHeader, bytes.NewReader(body), label) + return label, resp, err +} + +// GetOrgLabel get one label of organization by org it +func (c *Client) GetOrgLabel(orgName string, labelID int64) (*Label, *Response, error) { + if err := escapeValidatePathSegments(&orgName); err != nil { + return nil, nil, err + } + label := new(Label) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/labels/%d", orgName, labelID), nil, nil, label) + return label, resp, err +} + +type EditOrgLabelOption struct { + // New name of the label + Name *string `json:"name"` + // New color of the label in hex format without # + Color *string `json:"color"` + // New description of the label + Description *string `json:"description"` + // Whether this is an exclusive label + Exclusive *bool `json:"exclusive,omitempty"` +} + +// EditOrgLabel edits an existing org-level label by ID +func (c *Client) EditOrgLabel(orgName string, labelID int64, opt EditOrgLabelOption) (*Label, *Response, error) { + if err := escapeValidatePathSegments(&orgName); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + label := new(Label) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/orgs/%s/labels/%d", orgName, labelID), jsonHeader, bytes.NewReader(body), label) + return label, resp, err +} + +// DeleteOrgLabel deletes a org label by ID +func (c *Client) DeleteOrgLabel(orgName string, labelID int64) (*Response, error) { + if err := escapeValidatePathSegments(&orgName); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/labels/%d", orgName, labelID), jsonHeader, nil) + return resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/org_member.go b/cli/vendor/code.gitea.io/sdk/gitea/org_member.go new file mode 100644 index 0000000..91ac44a --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/org_member.go @@ -0,0 +1,141 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" + "net/url" +) + +// DeleteOrgMembership remove a member from an organization +func (c *Client) DeleteOrgMembership(org, user string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &user); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil) +} + +// ListOrgMembershipOption list OrgMembership options +type ListOrgMembershipOption struct { + ListOptions +} + +// ListOrgMembership list an organization's members +func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", org)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) + return users, resp, err +} + +// ListPublicOrgMembership list an organization's members +func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", org)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) + return users, resp, err +} + +// CheckOrgMembership Check if a user is a member of an organization +func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&org, &user); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil) + if err != nil { + return false, resp, err + } + switch status { + case http.StatusNoContent: + return true, resp, nil + case http.StatusNotFound: + return false, resp, nil + default: + return false, resp, fmt.Errorf("unexpected Status: %d", status) + } +} + +// CheckPublicOrgMembership Check if a user is a member of an organization +func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&org, &user); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil) + if err != nil { + return false, resp, err + } + switch status { + case http.StatusNoContent: + return true, resp, nil + case http.StatusNotFound: + return false, resp, nil + default: + return false, resp, fmt.Errorf("unexpected Status: %d", status) + } +} + +// SetPublicOrgMembership publicize/conceal a user's membership +func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) { + if err := escapeValidatePathSegments(&org, &user); err != nil { + return nil, err + } + var ( + status int + err error + resp *Response + ) + if visible { + status, resp, err = c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil) + } else { + status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil) + } + if err != nil { + return resp, err + } + switch status { + case http.StatusNoContent: + return resp, nil + case http.StatusNotFound: + return resp, fmt.Errorf("forbidden") + default: + return resp, fmt.Errorf("unexpected Status: %d", status) + } +} + +// OrgPermissions represents the permissions for an user in an organization +type OrgPermissions struct { + CanCreateRepository bool `json:"can_create_repository"` + CanRead bool `json:"can_read"` + CanWrite bool `json:"can_write"` + IsAdmin bool `json:"is_admin"` + IsOwner bool `json:"is_owner"` +} + +// GetOrgPermissions returns user permissions for specific organization. +func (c *Client) GetOrgPermissions(org, user string) (*OrgPermissions, *Response, error) { + if err := escapeValidatePathSegments(&org, &user); err != nil { + return nil, nil, err + } + + perm := &OrgPermissions{} + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs/%s/permissions", user, org), jsonHeader, nil, &perm) + if err != nil { + return nil, resp, err + } + return perm, resp, nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/org_social.go b/cli/vendor/code.gitea.io/sdk/gitea/org_social.go new file mode 100644 index 0000000..430fe94 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/org_social.go @@ -0,0 +1,124 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +// UpdateOrgAvatar updates the organization's avatar +func (c *Client) UpdateOrgAvatar(org string, opt UpdateUserAvatarOption) (*Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", + fmt.Sprintf("/orgs/%s/avatar", org), + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// DeleteOrgAvatar deletes the organization's avatar +func (c *Client) DeleteOrgAvatar(org string) (*Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", + fmt.Sprintf("/orgs/%s/avatar", org), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// RenameOrgOption options for renaming an organization +type RenameOrgOption struct { + NewName string `json:"new_name"` +} + +// RenameOrg renames an organization +func (c *Client) RenameOrg(org string, opt RenameOrgOption) (*Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", + fmt.Sprintf("/orgs/%s/rename", org), + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// ListOrgActivityFeedsOptions options for listing organization activity feeds +type ListOrgActivityFeedsOptions struct { + ListOptions + Date string `json:"date,omitempty"` +} + +// ListOrgActivityFeeds lists the organization's activity feeds +func (c *Client) ListOrgActivityFeeds(org string, opt ListOrgActivityFeedsOptions) ([]*Activity, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/orgs/%s/activities/feeds", org)) + query := opt.getURLQuery() + if opt.Date != "" { + query.Add("date", opt.Date) + } + link.RawQuery = query.Encode() + + activities := make([]*Activity, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &activities) + return activities, resp, err +} + +// ListTeamActivityFeedsOptions options for listing team activity feeds +type ListTeamActivityFeedsOptions struct { + ListOptions + Date string `json:"date,omitempty"` +} + +// ListTeamActivityFeeds lists the team's activity feeds +func (c *Client) ListTeamActivityFeeds(teamID int64, opt ListTeamActivityFeedsOptions) ([]*Activity, *Response, error) { + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/teams/%d/activities/feeds", teamID)) + query := opt.getURLQuery() + if opt.Date != "" { + query.Add("date", opt.Date) + } + link.RawQuery = query.Encode() + + activities := make([]*Activity, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &activities) + return activities, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/org_team.go b/cli/vendor/code.gitea.io/sdk/gitea/org_team.go new file mode 100644 index 0000000..71a6b35 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/org_team.go @@ -0,0 +1,292 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" +) + +// Team represents a team in an organization +type Team struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Organization *Organization `json:"organization"` + Permission AccessMode `json:"permission"` + CanCreateOrgRepo bool `json:"can_create_org_repo"` + IncludesAllRepositories bool `json:"includes_all_repositories"` + Units []RepoUnitType `json:"units"` + UnitsMap map[string]string `json:"units_map"` +} + +// RepoUnitType represent all unit types of a repo gitea currently offer +type RepoUnitType string + +const ( + // RepoUnitCode represent file view of a repository + RepoUnitCode RepoUnitType = "repo.code" + // RepoUnitIssues represent issues of a repository + RepoUnitIssues RepoUnitType = "repo.issues" + // RepoUnitPulls represent pulls of a repository + RepoUnitPulls RepoUnitType = "repo.pulls" + // RepoUnitExtIssues represent external issues of a repository + RepoUnitExtIssues RepoUnitType = "repo.ext_issues" + // RepoUnitWiki represent wiki of a repository + RepoUnitWiki RepoUnitType = "repo.wiki" + // RepoUnitExtWiki represent external wiki of a repository + RepoUnitExtWiki RepoUnitType = "repo.ext_wiki" + // RepoUnitReleases represent releases of a repository + RepoUnitReleases RepoUnitType = "repo.releases" + // RepoUnitProjects represent projects of a repository + RepoUnitProjects RepoUnitType = "repo.projects" + // RepoUnitPackages represents packages of a repository + RepoUnitPackages RepoUnitType = "repo.packages" + // RepoUnitActions represents actions of a repository + RepoUnitActions RepoUnitType = "repo.actions" +) + +// ListTeamsOptions options for listing teams +type ListTeamsOptions struct { + ListOptions +} + +// ListOrgTeams lists all teams of an organization +func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + teams := make([]*Team, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams?%s", org, opt.getURLQuery().Encode()), nil, nil, &teams) + return teams, resp, err +} + +// ListMyTeams lists all the teams of the current user +func (c *Client) ListMyTeams(opt *ListTeamsOptions) ([]*Team, *Response, error) { + opt.setDefaults() + teams := make([]*Team, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/teams?%s", opt.getURLQuery().Encode()), nil, nil, &teams) + return teams, resp, err +} + +// GetTeam gets a team by ID +func (c *Client) GetTeam(id int64) (*Team, *Response, error) { + t := new(Team) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d", id), nil, nil, t) + return t, resp, err +} + +// SearchTeamsOptions options for searching teams. +type SearchTeamsOptions struct { + ListOptions + Query string + IncludeDescription bool +} + +func (o SearchTeamsOptions) getURLQuery() url.Values { + query := make(url.Values) + query.Add("page", fmt.Sprintf("%d", o.Page)) + query.Add("limit", fmt.Sprintf("%d", o.PageSize)) + query.Add("q", o.Query) + query.Add("include_desc", fmt.Sprintf("%t", o.IncludeDescription)) + + return query +} + +// TeamSearchResults is the JSON struct that is returned from Team search API. +type TeamSearchResults struct { + OK bool `json:"ok"` + Error string `json:"error"` + Data []*Team `json:"data"` +} + +// SearchOrgTeams search for teams in a org. +func (c *Client) SearchOrgTeams(org string, opt *SearchTeamsOptions) ([]*Team, *Response, error) { + responseBody := TeamSearchResults{} + opt.setDefaults() + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams/search?%s", org, opt.getURLQuery().Encode()), nil, nil, &responseBody) + if err != nil { + return nil, resp, err + } + if !responseBody.OK { + return nil, resp, fmt.Errorf("gitea error: %v", responseBody.Error) + } + return responseBody.Data, resp, err +} + +// CreateTeamOption options for creating a team +type CreateTeamOption struct { + Name string `json:"name"` + Description string `json:"description"` + Permission AccessMode `json:"permission"` + CanCreateOrgRepo bool `json:"can_create_org_repo"` + IncludesAllRepositories bool `json:"includes_all_repositories"` + Units []RepoUnitType `json:"units"` + UnitsMap map[string]string `json:"units_map"` +} + +// Validate the CreateTeamOption struct +func (opt *CreateTeamOption) Validate() error { + if opt.Permission == AccessModeOwner { + opt.Permission = AccessModeAdmin + } else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin { + return fmt.Errorf("permission mode invalid") + } + if len(opt.Name) == 0 { + return fmt.Errorf("name required") + } + if len(opt.Name) > 255 { + return fmt.Errorf("name too long") + } + if len(opt.Description) > 255 { + return fmt.Errorf("description too long") + } + return nil +} + +// CreateTeam creates a team for an organization +func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := (&opt).Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + t := new(Team) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/teams", org), jsonHeader, bytes.NewReader(body), t) + return t, resp, err +} + +// EditTeamOption options for editing a team +type EditTeamOption struct { + Name string `json:"name"` + Description *string `json:"description"` + Permission AccessMode `json:"permission"` + CanCreateOrgRepo *bool `json:"can_create_org_repo"` + IncludesAllRepositories *bool `json:"includes_all_repositories"` + Units []RepoUnitType `json:"units"` + UnitsMap map[string]string `json:"units_map"` +} + +// Validate the EditTeamOption struct +func (opt *EditTeamOption) Validate() error { + if opt.Permission == AccessModeOwner { + opt.Permission = AccessModeAdmin + } else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin { + return fmt.Errorf("permission mode invalid") + } + if len(opt.Name) == 0 { + return fmt.Errorf("name required") + } + if len(opt.Name) > 30 { + return fmt.Errorf("name to long") + } + if opt.Description != nil && len(*opt.Description) > 255 { + return fmt.Errorf("description to long") + } + return nil +} + +// EditTeam edits a team of an organization +func (c *Client) EditTeam(id int64, opt EditTeamOption) (*Response, error) { + if err := (&opt).Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PATCH", fmt.Sprintf("/teams/%d", id), jsonHeader, bytes.NewReader(body)) +} + +// DeleteTeam deletes a team of an organization +func (c *Client) DeleteTeam(id int64) (*Response, error) { + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/teams/%d", id), nil, nil) +} + +// ListTeamMembersOptions options for listing team's members +type ListTeamMembersOptions struct { + ListOptions +} + +// ListTeamMembers lists all members of a team +func (c *Client) ListTeamMembers(id int64, opt ListTeamMembersOptions) ([]*User, *Response, error) { + opt.setDefaults() + members := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members?%s", id, opt.getURLQuery().Encode()), nil, nil, &members) + return members, resp, err +} + +// GetTeamMember gets a member of a team +func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + m := new(User) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m) + return m, resp, err +} + +// AddTeamMember adds a member to a team +func (c *Client) AddTeamMember(id int64, user string) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil) +} + +// RemoveTeamMember removes a member from a team +func (c *Client) RemoveTeamMember(id int64, user string) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil) +} + +// ListTeamRepositoriesOptions options for listing team's repositories +type ListTeamRepositoriesOptions struct { + ListOptions +} + +// ListTeamRepositories lists all repositories of a team +func (c *Client) ListTeamRepositories(id int64, opt ListTeamRepositoriesOptions) ([]*Repository, *Response, error) { + opt.setDefaults() + repos := make([]*Repository, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/repos?%s", id, opt.getURLQuery().Encode()), nil, nil, &repos) + return repos, resp, err +} + +// GetTeamRepository gets a repository that belongs to a team. +func (c *Client) GetTeamRepository(id int64, org, repo string) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&org, &repo); err != nil { + return nil, nil, err + } + result := new(Repository) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil, result) + return result, resp, err +} + +// AddTeamRepository adds a repository to a team +func (c *Client) AddTeamRepository(id int64, org, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil) +} + +// RemoveTeamRepository removes a repository from a team +func (c *Client) RemoveTeamRepository(id int64, org, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/package.go b/cli/vendor/code.gitea.io/sdk/gitea/package.go new file mode 100644 index 0000000..9bcb156 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/package.go @@ -0,0 +1,146 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "time" +) + +// Package represents a package +type Package struct { + // the package's id + ID int64 `json:"id"` + // the package's owner + Owner *User `json:"owner"` + // the repo this package belongs to (if any) + Repository *Repository `json:"repository"` + // the package's creator + Creator *User `json:"creator"` + // the type of package: + Type string `json:"type"` + // the name of the package + Name string `json:"name"` + // the version of the package + Version string `json:"version"` + // the HTML URL for viewing the package + HTMLURL string `json:"html_url"` + // the date the package was uploaded + CreatedAt time.Time `json:"created_at"` +} + +// PackageFile represents a file from a package +type PackageFile struct { + // the file's ID + ID int64 `json:"id"` + // the size of the file in bytes + Size int64 `json:"size"` + // the name of the file + Name string `json:"name"` + // the md5 hash of the file + MD5 string `json:"md5"` + // the sha1 hash of the file + SHA1 string `json:"sha1"` + // the sha256 hash of the file + SHA256 string `json:"sha256"` + // the sha512 hash of the file + SHA512 string `json:"sha512"` +} + +// ListPackagesOptions options for listing packages +type ListPackagesOptions struct { + ListOptions + // type, and q are only used for ListPackages, not ListPackageVersions + Type string + Q string +} + +func (opt ListPackagesOptions) getURLQuery() url.Values { + query := opt.ListOptions.getURLQuery() + if opt.Type != "" { + query.Set("type", opt.Type) + } + if opt.Q != "" { + query.Set("q", opt.Q) + } + return query +} + +// ListPackages lists all the packages owned by a given owner (user, organisation) +func (c *Client) ListPackages(owner string, opt ListPackagesOptions) ([]*Package, *Response, error) { + if err := escapeValidatePathSegments(&owner); err != nil { + return nil, nil, err + } + opt.setDefaults() + packages := make([]*Package, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s?%s", owner, opt.getURLQuery().Encode()), nil, nil, &packages) + return packages, resp, err +} + +// ListPackageVersions lists all versions of a package. +func (c *Client) ListPackageVersions(owner, packageType, name string, opt ListPackagesOptions) ([]*Package, *Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name); err != nil { + return nil, nil, err + } + opt.setDefaults() + packages := make([]*Package, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s?%s", owner, packageType, name, opt.ListOptions.getURLQuery().Encode()), nil, nil, &packages) + return packages, resp, err +} + +// GetPackage gets the details of a specific package version +func (c *Client) GetPackage(owner, packageType, name, version string) (*Package, *Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil { + return nil, nil, err + } + foundPackage := new(Package) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil, foundPackage) + return foundPackage, resp, err +} + +// DeletePackage deletes a specific package version +func (c *Client) DeletePackage(owner, packageType, name, version string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil) +} + +// ListPackageFiles lists the files within a package +func (c *Client) ListPackageFiles(owner, packageType, name, version string) ([]*PackageFile, *Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil { + return nil, nil, err + } + packageFiles := make([]*PackageFile, 0) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s/files", owner, packageType, name, version), nil, nil, &packageFiles) + return packageFiles, resp, err +} + +// GetLatestPackage gets the details of the latest version of a package +func (c *Client) GetLatestPackage(owner, packageType, name string) (*Package, *Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name); err != nil { + return nil, nil, err + } + foundPackage := new(Package) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/-/latest", owner, packageType, name), nil, nil, foundPackage) + return foundPackage, resp, err +} + +// LinkPackage links a package to a repository +func (c *Client) LinkPackage(owner, packageType, name, repoName string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name, &repoName); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/packages/%s/%s/%s/-/link/%s", owner, packageType, name, repoName), nil, nil) +} + +// UnlinkPackage unlinks a package from a repository +func (c *Client) UnlinkPackage(owner, packageType, name string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &packageType, &name); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/packages/%s/%s/%s/-/unlink", owner, packageType, name), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/pull.go b/cli/vendor/code.gitea.io/sdk/gitea/pull.go new file mode 100644 index 0000000..a466d35 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/pull.go @@ -0,0 +1,428 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" +) + +// PRBranchInfo information about a branch +type PRBranchInfo struct { + Name string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + RepoID int64 `json:"repo_id"` + Repository *Repository `json:"repo"` +} + +// PullRequest represents a pull request +type PullRequest struct { + ID int64 `json:"id"` + URL string `json:"url"` + Index int64 `json:"number"` + Poster *User `json:"user"` + Title string `json:"title"` + Body string `json:"body"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + Assignee *User `json:"assignee"` + Assignees []*User `json:"assignees"` + RequestedReviewers []*User `json:"requested_reviewers"` + RequestedReviewersTeams []*Team `json:"requested_reviewers_teams"` + State StateType `json:"state"` + Draft bool `json:"draft"` + IsLocked bool `json:"is_locked"` + Comments int `json:"comments"` + ReviewComments int `json:"review_comments,omitempty"` + + HTMLURL string `json:"html_url"` + DiffURL string `json:"diff_url"` + PatchURL string `json:"patch_url"` + + Mergeable bool `json:"mergeable"` + HasMerged bool `json:"merged"` + Merged *time.Time `json:"merged_at"` + MergedCommitID *string `json:"merge_commit_sha"` + MergedBy *User `json:"merged_by"` + AllowMaintainerEdit bool `json:"allow_maintainer_edit"` + + Base *PRBranchInfo `json:"base"` + Head *PRBranchInfo `json:"head"` + MergeBase string `json:"merge_base"` + + Deadline *time.Time `json:"due_date"` + Created *time.Time `json:"created_at"` + Updated *time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` + + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + ChangedFiles *int `json:"changed_files,omitempty"` + PinOrder int `json:"pin_order"` +} + +// ChangedFile is a changed file in a diff +type ChangedFile struct { + Filename string `json:"filename"` + PreviousFilename string `json:"previous_filename"` + Status string `json:"status"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` + Changes int `json:"changes"` + HTMLURL string `json:"html_url"` + ContentsURL string `json:"contents_url"` + RawURL string `json:"raw_url"` +} + +// ListPullRequestsOptions options for listing pull requests +type ListPullRequestsOptions struct { + ListOptions + State StateType `json:"state"` + // oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority + Sort string + Milestone int64 +} + +// MergeStyle is used specify how a pull is merged +type MergeStyle string + +const ( + // MergeStyleMerge merge pull as usual + MergeStyleMerge MergeStyle = "merge" + // MergeStyleRebase rebase pull + MergeStyleRebase MergeStyle = "rebase" + // MergeStyleRebaseMerge rebase and merge pull + MergeStyleRebaseMerge MergeStyle = "rebase-merge" + // MergeStyleSquash squash and merge pull + MergeStyleSquash MergeStyle = "squash" + // MergeStyleFastForwardOnly fast-forward merge + MergeStyleFastForwardOnly MergeStyle = "fast-forward-only" + // MergeStyleManuallyMerged manually merged + MergeStyleManuallyMerged MergeStyle = "manually-merged" +) + +// QueryEncode turns options into querystring argument +func (opt *ListPullRequestsOptions) QueryEncode() string { + query := opt.getURLQuery() + if len(opt.State) > 0 { + query.Add("state", string(opt.State)) + } + if len(opt.Sort) > 0 { + query.Add("sort", opt.Sort) + } + if opt.Milestone > 0 { + query.Add("milestone", fmt.Sprintf("%d", opt.Milestone)) + } + return query.Encode() +} + +// ListRepoPullRequests list PRs of one repository +func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + prs := make([]*PullRequest, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", owner, repo)) + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &prs) + if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { + for i := range prs { + if err := fixPullHeadSha(c, prs[i]); err != nil { + return prs, resp, err + } + } + } + return prs, resp, err +} + +// GetPullRequest get information of one PR +func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + pr := new(PullRequest) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr) + if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { + if err := fixPullHeadSha(c, pr); err != nil { + return pr, resp, err + } + } + return pr, resp, err +} + +// GetPullRequestByBaseHead gets a pull request by its base and head branches. +func (c *Client) GetPullRequestByBaseHead(owner, repo, base, head string) (*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &base); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, nil, err + } + head = pathEscapeSegments(head) + pr := new(PullRequest) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%s/%s", owner, repo, base, head), nil, nil, pr) + if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { + if err := fixPullHeadSha(c, pr); err != nil { + return pr, resp, err + } + } + return pr, resp, err +} + +// CreatePullRequestOption options when creating a pull request +type CreatePullRequestOption struct { + Head string `json:"head"` + Base string `json:"base"` + Title string `json:"title"` + Body string `json:"body"` + Assignee string `json:"assignee"` + Assignees []string `json:"assignees"` + Reviewers []string `json:"reviewers"` + TeamReviewers []string `json:"team_reviewers"` + Milestone int64 `json:"milestone"` + Labels []int64 `json:"labels"` + Deadline *time.Time `json:"due_date"` +} + +// CreatePullRequest create pull request with options +func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + pr := new(PullRequest) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls", owner, repo), + jsonHeader, bytes.NewReader(body), pr) + return pr, resp, err +} + +// EditPullRequestOption options when modify pull request +type EditPullRequestOption struct { + Title string `json:"title"` + Body *string `json:"body"` + Base string `json:"base"` + Assignee string `json:"assignee"` + Assignees []string `json:"assignees"` + Milestone int64 `json:"milestone"` + Labels []int64 `json:"labels"` + State *StateType `json:"state"` + Deadline *time.Time `json:"due_date"` + RemoveDeadline *bool `json:"unset_due_date"` + AllowMaintainerEdit *bool `json:"allow_maintainer_edit"` +} + +// Validate the EditPullRequestOption struct +func (opt EditPullRequestOption) Validate(c *Client) error { + if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + if len(opt.Base) != 0 { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return fmt.Errorf("can not change base gitea to old") + } + } + return nil +} + +// EditPullRequest modify pull request with PR id and options +func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRequestOption) (*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + pr := new(PullRequest) + resp, err := c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), + jsonHeader, bytes.NewReader(body), pr) + return pr, resp, err +} + +// MergePullRequestOption options when merging a pull request +type MergePullRequestOption struct { + Style MergeStyle `json:"Do"` + MergeCommitID string `json:"MergeCommitID"` + Title string `json:"MergeTitleField"` + Message string `json:"MergeMessageField"` + DeleteBranchAfterMerge *bool `json:"delete_branch_after_merge,omitempty"` + ForceMerge bool `json:"force_merge"` + HeadCommitId string `json:"head_commit_id"` + MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed"` +} + +// Validate the MergePullRequestOption struct +func (opt MergePullRequestOption) Validate(c *Client) error { + if opt.Style == MergeStyleSquash { + if err := c.checkServerVersionGreaterThanOrEqual(version1_11_5); err != nil { + return err + } + } + return nil +} + +// MergePullRequest merge a PR to repository by PR id +func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePullRequestOption) (bool, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return false, nil, err + } + if err := opt.Validate(c); err != nil { + return false, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("POST", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), jsonHeader, bytes.NewReader(body)) + if err != nil { + return false, resp, err + } + return status == 200 || status == 201, resp, nil +} + +// IsPullRequestMerged test if one PR is merged to one repository +func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil) + if err != nil { + return false, resp, err + } + + return status == 204, resp, nil +} + +// PullRequestDiffOptions options for GET /repos///pulls/.[diff|patch] +type PullRequestDiffOptions struct { + // Include binary file changes when requesting a .diff + Binary bool +} + +// QueryEncode converts the options to a query string +func (o PullRequestDiffOptions) QueryEncode() string { + query := make(url.Values) + query.Add("binary", fmt.Sprintf("%v", o.Binary)) + return query.Encode() +} + +type pullRequestDiffType string + +const ( + pullRequestDiffTypeDiff pullRequestDiffType = "diff" + pullRequestDiffTypePatch pullRequestDiffType = "patch" +) + +// getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR +func (c *Client) getPullRequestDiffOrPatch(owner, repo string, kind pullRequestDiffType, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + r, _, err2 := c.GetRepo(owner, repo) + if err2 != nil { + return nil, nil, err + } + if r.Private { + return nil, nil, err + } + url := fmt.Sprintf("/%s/%s/pulls/%d.%s?%s", owner, repo, index, kind, opts.QueryEncode()) + return c.getWebResponse("GET", url, nil) + } + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil) +} + +// GetPullRequestPatch gets the git patchset of a PR +func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) { + return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypePatch, index, PullRequestDiffOptions{}) +} + +// GetPullRequestDiff gets the diff of a PR. For Gitea >= 1.16, you must set includeBinary to get an applicable diff +func (c *Client) GetPullRequestDiff(owner, repo string, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) { + return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypeDiff, index, opts) +} + +// CancelScheduledAutoMerge cancels a scheduled automatic merge for a pull request. +func (c *Client) CancelScheduledAutoMerge(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_18_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil) +} + +// ListPullRequestCommitsOptions options for listing pull requests +type ListPullRequestCommitsOptions struct { + ListOptions +} + +// ListPullRequestCommits list commits for a pull request +func (c *Client) ListPullRequestCommits(owner, repo string, index int64, opt ListPullRequestCommitsOptions) ([]*Commit, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/commits", owner, repo, index)) + opt.setDefaults() + commits := make([]*Commit, 0, opt.PageSize) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits) + return commits, resp, err +} + +// fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675 +// When no head sha is available, this is because the branch got deleted in the base repo. +// pr.Head.Ref points in this case not to the head repo branch name, but the base repo ref, +// which stays available to resolve the commit sha. This is fixed for gitea >= 1.14.0 +func fixPullHeadSha(client *Client, pr *PullRequest) error { + if pr.Base != nil && pr.Base.Repository != nil && pr.Base.Repository.Owner != nil && + pr.Head != nil && pr.Head.Ref != "" && pr.Head.Sha == "" { + owner := pr.Base.Repository.Owner.UserName + repo := pr.Base.Repository.Name + refs, _, err := client.GetRepoRefs(owner, repo, pr.Head.Ref) + if err != nil { + return err + } else if len(refs) == 0 { + return fmt.Errorf("unable to resolve PR ref '%s'", pr.Head.Ref) + } + pr.Head.Sha = refs[0].Object.SHA + } + return nil +} + +// ListPullRequestFilesOptions options for listing pull request files +type ListPullRequestFilesOptions struct { + ListOptions +} + +// ListPullRequestFiles list changed files for a pull request +func (c *Client) ListPullRequestFiles(owner, repo string, index int64, opt ListPullRequestFilesOptions) ([]*ChangedFile, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/files", owner, repo, index)) + opt.setDefaults() + files := make([]*ChangedFile, 0, opt.PageSize) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &files) + return files, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/pull_review.go b/cli/vendor/code.gitea.io/sdk/gitea/pull_review.go new file mode 100644 index 0000000..d241712 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/pull_review.go @@ -0,0 +1,375 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" +) + +// ReviewStateType review state type +type ReviewStateType string + +const ( + // ReviewStateApproved pr is approved + ReviewStateApproved ReviewStateType = "APPROVED" + // ReviewStatePending pr state is pending + ReviewStatePending ReviewStateType = "PENDING" + // ReviewStateComment is a comment review + ReviewStateComment ReviewStateType = "COMMENT" + // ReviewStateRequestChanges changes for pr are requested + ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES" + // ReviewStateRequestReview review is requested from user + ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW" + // ReviewStateUnknown state of pr is unknown + ReviewStateUnknown ReviewStateType = "" +) + +// PullReview represents a pull request review +type PullReview struct { + ID int64 `json:"id"` + Reviewer *User `json:"user"` + ReviewerTeam *Team `json:"team"` + State ReviewStateType `json:"state"` + Body string `json:"body"` + CommitID string `json:"commit_id"` + // Stale indicates if the pull has changed since the review + Stale bool `json:"stale"` + // Official indicates if the review counts towards the required approval limit, if PR base is a protected branch + Official bool `json:"official"` + Dismissed bool `json:"dismissed"` + CodeCommentsCount int `json:"comments_count"` + Submitted time.Time `json:"submitted_at"` + + HTMLURL string `json:"html_url"` + HTMLPullURL string `json:"pull_request_url"` +} + +// PullReviewComment represents a comment on a pull request review +type PullReviewComment struct { + ID int64 `json:"id"` + Body string `json:"body"` + Reviewer *User `json:"user"` + ReviewID int64 `json:"pull_request_review_id"` + Resolver *User `json:"resolver"` + + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + + Path string `json:"path"` + CommitID string `json:"commit_id"` + OrigCommitID string `json:"original_commit_id"` + DiffHunk string `json:"diff_hunk"` + LineNum uint64 `json:"position"` + OldLineNum uint64 `json:"original_position"` + + HTMLURL string `json:"html_url"` + HTMLPullURL string `json:"pull_request_url"` +} + +// CreatePullReviewOptions are options to create a pull review +type CreatePullReviewOptions struct { + State ReviewStateType `json:"event"` + Body string `json:"body"` + CommitID string `json:"commit_id"` + Comments []CreatePullReviewComment `json:"comments"` +} + +// CreatePullReviewComment represent a review comment for creation api +type CreatePullReviewComment struct { + // the tree path + Path string `json:"path"` + Body string `json:"body"` + // if comment to old file line or 0 + OldLineNum int64 `json:"old_position"` + // if comment to new file line or 0 + NewLineNum int64 `json:"new_position"` +} + +// CreatePullReviewCommentReplyOptions are options to reply to a pull request review comment. +type CreatePullReviewCommentReplyOptions struct { + Body string `json:"body"` +} + +// SubmitPullReviewOptions are options to submit a pending pull review +type SubmitPullReviewOptions struct { + State ReviewStateType `json:"event"` + Body string `json:"body"` +} + +// DismissPullReviewOptions are options to dismiss a pull review +type DismissPullReviewOptions struct { + Message string `json:"message"` +} + +// PullReviewRequestOptions are options to add or remove pull review requests +type PullReviewRequestOptions struct { + Reviewers []string `json:"reviewers"` + TeamReviewers []string `json:"team_reviewers"` +} + +// ListPullReviewsOptions options for listing PullReviews +type ListPullReviewsOptions struct { + ListOptions +} + +// Validate the CreatePullReviewOptions struct +func (opt CreatePullReviewOptions) Validate() error { + if opt.State != ReviewStateApproved && len(opt.Comments) == 0 && len(strings.TrimSpace(opt.Body)) == 0 { + return fmt.Errorf("body is empty") + } + for i := range opt.Comments { + if err := opt.Comments[i].Validate(); err != nil { + return err + } + } + return nil +} + +// Validate the SubmitPullReviewOptions struct +func (opt SubmitPullReviewOptions) Validate() error { + if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 { + return fmt.Errorf("body is empty") + } + return nil +} + +// Validate the CreatePullReviewComment struct +func (opt CreatePullReviewComment) Validate() error { + if len(strings.TrimSpace(opt.Body)) == 0 { + return fmt.Errorf("body is empty") + } + if opt.NewLineNum != 0 && opt.OldLineNum != 0 { + return fmt.Errorf("old and new line num are set, cant identify the code comment position") + } + return nil +} + +// Validate the CreatePullReviewCommentReplyOptions struct. +func (opt CreatePullReviewCommentReplyOptions) Validate() error { + if len(strings.TrimSpace(opt.Body)) == 0 { + return fmt.Errorf("body is empty") + } + return nil +} + +// ListPullReviews lists all reviews of a pull request +func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullReviewsOptions) ([]*PullReview, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + opt.setDefaults() + rs := make([]*PullReview, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index)) + link.RawQuery = opt.ListOptions.getURLQuery().Encode() + + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rs) + return rs, resp, err +} + +// GetPullReview gets a specific review of a pull request +func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + + r := new(PullReview) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil, &r) + return r, resp, err +} + +// ListPullReviewComments lists all comments of a pull request review +func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]*PullReviewComment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + rcl := make([]*PullReviewComment, 0, 4) + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/comments", owner, repo, index, id)) + + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rcl) + return rcl, resp, err +} + +// DeletePullReview delete a specific review from a pull request +func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil) +} + +// CreatePullReview create a review to an pull request +func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePullReviewOptions) (*PullReview, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + r := new(PullReview) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index), + jsonHeader, bytes.NewReader(body), r) + return r, resp, err +} + +// CreatePullReviewCommentReply replies to a pull request review comment. +// Available on Gitea main/nightly; first released version not assigned yet. +// Upstream: gitea/gitea#36683 (331450b17a025ce78b5bf9405ca8d684607680ef). +func (c *Client) CreatePullReviewCommentReply(owner, repo string, index, id int64, opt CreatePullReviewCommentReplyOptions) (*PullReviewComment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + r := new(PullReviewComment) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/comments/%d/replies", owner, repo, index, id), + jsonHeader, bytes.NewReader(body), r) + return r, resp, err +} + +// SubmitPullReview submit a pending review to an pull request +func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt SubmitPullReviewOptions) (*PullReview, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + r := new(PullReview) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), + jsonHeader, bytes.NewReader(body), r) + return r, resp, err +} + +// CreateReviewRequests create review requests to an pull request +func (c *Client) CreateReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index), + jsonHeader, bytes.NewReader(body)) +} + +// DeleteReviewRequests delete review requests to an pull request +func (c *Client) DeleteReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index), + jsonHeader, bytes.NewReader(body)) +} + +// DismissPullReview dismiss a review for a pull request +func (c *Client) DismissPullReview(owner, repo string, index, id int64, opt DismissPullReviewOptions) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/dismissals", owner, repo, index, id), + jsonHeader, bytes.NewReader(body)) +} + +// UnDismissPullReview cancel to dismiss a review for a pull request +func (c *Client) UnDismissPullReview(owner, repo string, index, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, id), + jsonHeader, nil) +} + +// ResolvePullReviewComment resolves a pull-request review comment conversation. +// Available on Gitea main/nightly; first released version not assigned yet. +// Upstream: gitea/gitea#36441 (c2dea22926f9e7a40aa47296e7b9bc3d1c5b039e). +func (c *Client) ResolvePullReviewComment(owner, repo string, commentID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/pulls/comments/%d/resolve", owner, repo, commentID), nil, nil) +} + +// UnresolvePullReviewComment unresolves a pull-request review comment conversation. +// Available on Gitea main/nightly; first released version not assigned yet. +// Upstream: gitea/gitea#36441 (c2dea22926f9e7a40aa47296e7b9bc3d1c5b039e). +func (c *Client) UnresolvePullReviewComment(owner, repo string, commentID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/pulls/comments/%d/unresolve", owner, repo, commentID), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/release.go b/cli/vendor/code.gitea.io/sdk/gitea/release.go new file mode 100644 index 0000000..d53be26 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/release.go @@ -0,0 +1,212 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" +) + +// Release represents a repository release +type Release struct { + ID int64 `json:"id"` + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + TarURL string `json:"tarball_url"` + ZipURL string `json:"zipball_url"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` + CreatedAt time.Time `json:"created_at"` + PublishedAt time.Time `json:"published_at"` + Publisher *User `json:"author"` + Attachments []*Attachment `json:"assets"` +} + +// ListReleasesOptions options for listing repository's releases +type ListReleasesOptions struct { + ListOptions + IsDraft *bool + IsPreRelease *bool +} + +// QueryEncode turns options into querystring argument +func (opt *ListReleasesOptions) QueryEncode() string { + query := opt.getURLQuery() + + if opt.IsDraft != nil { + query.Add("draft", fmt.Sprintf("%t", *opt.IsDraft)) + } + if opt.IsPreRelease != nil { + query.Add("pre-release", fmt.Sprintf("%t", *opt.IsPreRelease)) + } + + return query.Encode() +} + +// ListReleases list releases of a repository +func (c *Client) ListReleases(owner, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + releases := make([]*Release, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases?%s", owner, repo, opt.QueryEncode()), + nil, nil, &releases) + return releases, resp, err +} + +// GetRelease get a release of a repository by id +func (c *Client) GetRelease(owner, repo string, id int64) (*Release, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + r := new(Release) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id), + jsonHeader, nil, &r) + return r, resp, err +} + +// GetLatestRelease get the latest release of a repository +func (c *Client) GetLatestRelease(owner, repo string) (*Release, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + r := new(Release) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/latest", owner, repo), + jsonHeader, nil, &r) + return r, resp, err +} + +// GetReleaseByTag get a release of a repository by tag +func (c *Client) GetReleaseByTag(owner, repo, tag string) (*Release, *Response, error) { + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + return c.fallbackGetReleaseByTag(owner, repo, tag) + } + if err := escapeValidatePathSegments(&owner, &repo, &tag); err != nil { + return nil, nil, err + } + r := new(Release) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/tags/%s", owner, repo, tag), + nil, nil, &r) + return r, resp, err +} + +// CreateReleaseOption options when creating a release +type CreateReleaseOption struct { + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` +} + +// Validate the CreateReleaseOption struct +func (opt CreateReleaseOption) Validate() error { + if len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// CreateRelease create a release +func (c *Client) CreateRelease(owner, repo string, opt CreateReleaseOption) (*Release, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(opt) + if err != nil { + return nil, nil, err + } + r := new(Release) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/releases", owner, repo), + jsonHeader, bytes.NewReader(body), r) + return r, resp, err +} + +// EditReleaseOption options when editing a release +type EditReleaseOption struct { + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft *bool `json:"draft"` + IsPrerelease *bool `json:"prerelease"` +} + +// EditRelease edit a release +func (c *Client) EditRelease(owner, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(form) + if err != nil { + return nil, nil, err + } + r := new(Release) + resp, err := c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id), + jsonHeader, bytes.NewReader(body), r) + return r, resp, err +} + +// DeleteRelease delete a release from a repository, keeping its tag +func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), + nil, nil) +} + +// DeleteReleaseByTag deletes a release frm a repository by tag +func (c *Client) DeleteReleaseByTag(user, repo, tag string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag), + nil, nil) +} + +// fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 ) +func (c *Client) fallbackGetReleaseByTag(owner, repo, tag string) (*Release, *Response, error) { + for i := 1; ; i++ { + rl, resp, err := c.ListReleases(owner, repo, ListReleasesOptions{ListOptions: ListOptions{Page: i}}) + if err != nil { + return nil, resp, err + } + if len(rl) == 0 { + return nil, + newResponse(&http.Response{StatusCode: 404}), + fmt.Errorf("release with tag '%s' not found", tag) + } + for _, r := range rl { + if r.TagName == tag { + return r, resp, nil + } + } + } +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo.go b/cli/vendor/code.gitea.io/sdk/gitea/repo.go new file mode 100644 index 0000000..466a4b4 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo.go @@ -0,0 +1,629 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" +) + +// Permission represents a set of permissions +type Permission struct { + Admin bool `json:"admin"` + Push bool `json:"push"` + Pull bool `json:"pull"` +} + +// InternalTracker represents settings for internal tracker +type InternalTracker struct { + // Enable time tracking (Built-in issue tracker) + EnableTimeTracker bool `json:"enable_time_tracker"` + // Let only contributors track time (Built-in issue tracker) + AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"` + // Enable dependencies for issues and pull requests (Built-in issue tracker) + EnableIssueDependencies bool `json:"enable_issue_dependencies"` +} + +// ExternalTracker represents settings for external tracker +type ExternalTracker struct { + // URL of external issue tracker. + ExternalTrackerURL string `json:"external_tracker_url"` + // External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index. + ExternalTrackerFormat string `json:"external_tracker_format"` + // External Issue Tracker Number Format, either `numeric` or `alphanumeric` + ExternalTrackerStyle string `json:"external_tracker_style"` +} + +// ExternalWiki represents setting for external wiki +type ExternalWiki struct { + // URL of external wiki. + ExternalWikiURL string `json:"external_wiki_url"` +} + +// ProjectsMode is used specify which kinds of projects to show for a repository +type ProjectsMode string + +const ( + // ProjectsModeRepo only allow repo-level projects + ProjectsModeRepo ProjectsMode = "repo" + // ProjectsModeOwner only allow owner projects + ProjectsModeOwner ProjectsMode = "owner" + // ProjectsModeAll only allow all projects + ProjectsModeAll ProjectsMode = "all" +) + +// RepoTransfer represents a pending repository transfer +type RepoTransfer struct { + Doer *User `json:"doer"` + Recipient *User `json:"recipient"` + Teams []*Team `json:"teams"` +} + +// Repository represents a repository +type Repository struct { + ID int64 `json:"id"` + Owner *User `json:"owner"` + Name string `json:"name"` + FullName string `json:"full_name"` + Description string `json:"description"` + Empty bool `json:"empty"` + Private bool `json:"private"` + Fork bool `json:"fork"` + Template bool `json:"template"` + Parent *Repository `json:"parent"` + Mirror bool `json:"mirror"` + Size int `json:"size"` + Language string `json:"language"` + LanguagesURL string `json:"languages_url"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Link string `json:"link"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + OriginalURL string `json:"original_url"` + Website string `json:"website"` + Stars int `json:"stars_count"` + Forks int `json:"forks_count"` + Watchers int `json:"watchers_count"` + OpenIssues int `json:"open_issues_count"` + OpenPulls int `json:"open_pr_counter"` + Releases int `json:"release_counter"` + DefaultBranch string `json:"default_branch"` + Archived bool `json:"archived"` + ArchivedAt time.Time `json:"archived_at"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + Permissions *Permission `json:"permissions,omitempty"` + HasIssues bool `json:"has_issues"` + HasCode bool `json:"has_code"` + InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` + ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` + HasWiki bool `json:"has_wiki"` + ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` + HasPullRequests bool `json:"has_pull_requests"` + HasProjects bool `json:"has_projects"` + HasReleases bool `json:"has_releases,omitempty"` + HasPackages bool `json:"has_packages,omitempty"` + HasActions bool `json:"has_actions,omitempty"` + IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"` + AllowFastForwardOnlyMerge bool `json:"allow_fast_forward_only_merge"` + AllowMerge bool `json:"allow_merge_commits"` + AllowRebase bool `json:"allow_rebase"` + AllowRebaseMerge bool `json:"allow_rebase_explicit"` + AllowRebaseUpdate bool `json:"allow_rebase_update"` + AllowSquash bool `json:"allow_squash_merge"` + DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"` + AvatarURL string `json:"avatar_url"` + Internal bool `json:"internal"` + MirrorInterval string `json:"mirror_interval"` + MirrorUpdated time.Time `json:"mirror_updated,omitempty"` + DefaultMergeStyle MergeStyle `json:"default_merge_style"` + ProjectsMode *ProjectsMode `json:"projects_mode"` + DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"` + ObjectFormatName string `json:"object_format_name"` + Topics []string `json:"topics"` + Licenses []string `json:"licenses"` + RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"` +} + +// RepoType represent repo type +type RepoType string + +const ( + // RepoTypeNone dont specify a type + RepoTypeNone RepoType = "" + // RepoTypeSource is the default repo type + RepoTypeSource RepoType = "source" + // RepoTypeFork is a repo witch was forked from an other one + RepoTypeFork RepoType = "fork" + // RepoTypeMirror represents an mirror repo + RepoTypeMirror RepoType = "mirror" +) + +// TrustModel represent how git signatures are handled in a repository +type TrustModel string + +const ( + // TrustModelDefault use TM set by global config + TrustModelDefault TrustModel = "default" + // TrustModelCollaborator gpg signature has to be owned by a repo collaborator + TrustModelCollaborator TrustModel = "collaborator" + // TrustModelCommitter gpg signature has to match committer + TrustModelCommitter TrustModel = "committer" + // TrustModelCollaboratorCommitter gpg signature has to match committer and owned by a repo collaborator + TrustModelCollaboratorCommitter TrustModel = "collaboratorcommitter" +) + +// ListReposOptions options for listing repositories +type ListReposOptions struct { + ListOptions +} + +// ListMyRepos lists all repositories for the authenticated user that has access to. +func (c *Client) ListMyRepos(opt ListReposOptions) ([]*Repository, *Response, error) { + opt.setDefaults() + repos := make([]*Repository, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/repos?%s", opt.getURLQuery().Encode()), nil, nil, &repos) + return repos, resp, err +} + +// ListUserRepos list all repositories of one user by user's name +func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + repos := make([]*Repository, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos?%s", user, opt.getURLQuery().Encode()), nil, nil, &repos) + return repos, resp, err +} + +// ListOrgReposOptions options for a organization's repositories +type ListOrgReposOptions struct { + ListOptions +} + +// ListOrgRepos list all repositories of one organization by organization's name +func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + repos := make([]*Repository, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos?%s", org, opt.getURLQuery().Encode()), nil, nil, &repos) + return repos, resp, err +} + +// SearchRepoOptions options for searching repositories +type SearchRepoOptions struct { + ListOptions + + // The keyword to query + Keyword string + // Limit search to repositories with keyword as topic + KeywordIsTopic bool + // Include search of keyword within repository description + KeywordInDescription bool + + /* + User Filter + */ + + // Repo Owner + OwnerID int64 + // Stared By UserID + StarredByUserID int64 + + /* + Repo Attributes + */ + + // pubic, private or all repositories (defaults to all) + IsPrivate *bool + // archived, non-archived or all repositories (defaults to all) + IsArchived *bool + // Exclude template repos from search + ExcludeTemplate bool + // Filter by "fork", "source", "mirror" + Type RepoType + + /* + Sort Filters + */ + + // sort repos by attribute. Supported values are "alpha", "created", "updated", "size", and "id". Default is "alpha" + Sort string + // sort order, either "asc" (ascending) or "desc" (descending). Default is "asc", ignored if "sort" is not specified. + Order string + // Repo owner to prioritize in the results + PrioritizedByOwnerID int64 + + /* + Cover EdgeCases + */ + // if set all other options are ignored and this string is used as query + RawQuery string +} + +// QueryEncode turns options into querystring argument +func (opt *SearchRepoOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.Keyword != "" { + query.Add("q", opt.Keyword) + } + if opt.KeywordIsTopic { + query.Add("topic", "true") + } + if opt.KeywordInDescription { + query.Add("includeDesc", "true") + } + + // User Filter + if opt.OwnerID > 0 { + query.Add("uid", fmt.Sprintf("%d", opt.OwnerID)) + query.Add("exclusive", "true") + } + if opt.StarredByUserID > 0 { + query.Add("starredBy", fmt.Sprintf("%d", opt.StarredByUserID)) + } + + // Repo Attributes + if opt.IsPrivate != nil { + query.Add("is_private", fmt.Sprintf("%t", *opt.IsPrivate)) + } + if opt.IsArchived != nil { + query.Add("archived", fmt.Sprintf("%t", *opt.IsArchived)) + } + if opt.ExcludeTemplate { + query.Add("template", "false") + } + if len(opt.Type) != 0 { + query.Add("mode", string(opt.Type)) + } + + // Sort Filters + if opt.Sort != "" { + query.Add("sort", opt.Sort) + } + if opt.PrioritizedByOwnerID > 0 { + query.Add("priority_owner_id", fmt.Sprintf("%d", opt.PrioritizedByOwnerID)) + } + if opt.Order != "" { + query.Add("order", opt.Order) + } + + return query.Encode() +} + +type searchRepoResponse struct { + Repos []*Repository `json:"data"` +} + +// SearchRepos searches for repositories matching the given filters +func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, error) { + opt.setDefaults() + repos := new(searchRepoResponse) + + link, _ := url.Parse("/repos/search") + + if len(opt.RawQuery) != 0 { + link.RawQuery = opt.RawQuery + } else { + link.RawQuery = opt.QueryEncode() + // IsPrivate only works on gitea >= 1.12.0 + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil && opt.IsPrivate != nil { + if *opt.IsPrivate { + // private repos only not supported on gitea <= 1.11.x + return nil, nil, err + } + newQuery := link.Query() + newQuery.Add("private", "false") + link.RawQuery = newQuery.Encode() + } + } + + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &repos) + return repos.Repos, resp, err +} + +// CreateRepoOption options when creating repository +type CreateRepoOption struct { + // Name of the repository to create + Name string `json:"name"` + // Description of the repository to create + Description string `json:"description"` + // Whether the repository is private + Private bool `json:"private"` + // Issue Label set to use + IssueLabels string `json:"issue_labels"` + // Whether the repository should be auto-intialized? + AutoInit bool `json:"auto_init"` + // Whether the repository is template + Template bool `json:"template"` + // Gitignores to use + Gitignores string `json:"gitignores"` + // License to use + License string `json:"license"` + // Readme of the repository to create + Readme string `json:"readme"` + // DefaultBranch of the repository (used when initializes and in template) + DefaultBranch string `json:"default_branch"` + // TrustModel of the repository + TrustModel TrustModel `json:"trust_model"` + // ObjectFormatName of the repository, could be sha1 or sha256, depends on Gitea version + ObjectFormatName string `json:"object_format_name"` +} + +// Validate the CreateRepoOption struct +func (opt CreateRepoOption) Validate(c *Client) error { + if len(strings.TrimSpace(opt.Name)) == 0 { + return fmt.Errorf("name is empty") + } + if len(opt.Name) > 100 { + return fmt.Errorf("name has more than 100 chars") + } + if len(opt.Description) > 2048 { + return fmt.Errorf("description has more than 2048 chars") + } + if len(opt.DefaultBranch) > 100 { + return fmt.Errorf("default branch name has more than 100 chars") + } + if len(opt.TrustModel) != 0 { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return err + } + } + if len(opt.ObjectFormatName) != 0 { + if opt.ObjectFormatName != "sha1" && opt.ObjectFormatName != "sha256" { + return fmt.Errorf("object format must be sha1 or sha256") + } + } + return nil +} + +// CreateRepo creates a repository for authenticated user. +func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) { + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + // object_format_name is only supported on gitea >= 1.22.0 + if c.checkServerVersionGreaterThanOrEqual(version1_22_0) != nil { + opt.ObjectFormatName = "" + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", "/user/repos", jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} + +// CreateOrgRepo creates an organization repository for authenticated user. +func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/repos", org), jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} + +// GetRepo returns information of a repository of given owner. +func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&owner, &reponame); err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo) + return repo, resp, err +} + +// GetRepoByID returns information of a repository by a giver repository ID. +func (c *Client) GetRepoByID(id int64) (*Repository, *Response, error) { + repo := new(Repository) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repositories/%d", id), nil, nil, repo) + return repo, resp, err +} + +// EditRepoOption options when editing a repository's properties +type EditRepoOption struct { + // name of the repository + Name *string `json:"name,omitempty"` + // a short description of the repository. + Description *string `json:"description,omitempty"` + // a URL with more information about the repository. + Website *string `json:"website,omitempty"` + // either `true` to make the repository private or `false` to make it public. + // Note: you will get a 422 error if the organization restricts changing repository visibility to organization + // owners and a non-owner tries to change the value of private. + Private *bool `json:"private,omitempty"` + // either `true` to make this repository a template or `false` to make it a normal repository + Template *bool `json:"template,omitempty"` + // either `true` to enable issues for this repository or `false` to disable them. + HasIssues *bool `json:"has_issues,omitempty"` + // set this structure to configure internal issue tracker (requires has_issues) + InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` + // set this structure to use external issue tracker (requires has_issues) + ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` + // either `true` to enable the wiki for this repository or `false` to disable it. + HasWiki *bool `json:"has_wiki,omitempty"` + // set this structure to use external wiki instead of internal (requires has_wiki) + ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` + // sets the default branch for this repository. + DefaultBranch *string `json:"default_branch,omitempty"` + // either `true` to allow pull requests, or `false` to prevent pull request. + HasPullRequests *bool `json:"has_pull_requests,omitempty"` + // either `true` to enable project unit, or `false` to disable them. + HasProjects *bool `json:"has_projects,omitempty"` + // either `true` to enable release, or `false` to disable them. + HasReleases *bool `json:"has_releases,omitempty"` + // either `true` to enable packages, or `false` to disable them. + HasPackages *bool `json:"has_packages,omitempty"` + // either `true` to enable actions, or `false` to disable them. + HasActions *bool `json:"has_actions,omitempty"` + // either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`. + IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"` + // either `true` to allow merging pull requests with fast-forward only strategy, or `false` to prevent merging pull requests with a fast-forward only strategy. `has_pull_requests` must be `true`. + AllowFastForwardOnlyMerge *bool `json:"allow_fast_forward_only_merge"` + // either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`. + AllowMerge *bool `json:"allow_merge_commits,omitempty"` + // either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging. `has_pull_requests` must be `true`. + AllowRebase *bool `json:"allow_rebase,omitempty"` + // either `true` to allow rebase with explicit merge commits (--no-ff), or `false` to prevent rebase with explicit merge commits. `has_pull_requests` must be `true`. + AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"` + // either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. `has_pull_requests` must be `true`. + AllowSquash *bool `json:"allow_squash_merge,omitempty"` + // set to `true` to archive this repository. + Archived *bool `json:"archived,omitempty"` + // set to a string like `8h30m0s` to set the mirror interval time + MirrorInterval *string `json:"mirror_interval,omitempty"` + // either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`. + AllowManualMerge *bool `json:"allow_manual_merge,omitempty"` + // either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur. + AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"` + // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". `has_pull_requests` must be `true`. + DefaultMergeStyle *MergeStyle `json:"default_merge_style,omitempty"` + // set to a projects mode to be used by this repository, to specify which kinds of projects to show: "repo" to only allow repo-level projects, "owner" to only allow owner projects, "all" to allow all projects. `has_projects` must be `true`. + ProjectsMode *ProjectsMode `json:"projects_mode"` + // set to `true` to delete the pull request branch after merge by default. `has_pull_requests` must be `true`. + DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge"` +} + +// EditRepo edit the properties of a repository +func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&owner, &reponame); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s", owner, reponame), jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} + +// DeleteRepo deletes a repository of user or organization. +func (c *Client) DeleteRepo(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil) +} + +// MirrorSync adds a mirrored repository to the mirror sync queue. +func (c *Client) MirrorSync(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil) +} + +// GetRepoLanguages return language stats of a repo +func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + langMap := make(map[string]int64) + + data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil) + if err != nil { + return nil, resp, err + } + if err = json.Unmarshal(data, &langMap); err != nil { + return nil, resp, err + } + return langMap, resp, nil +} + +// ArchiveType represent supported archive formats by gitea +type ArchiveType string + +const ( + // ZipArchive represent zip format + ZipArchive ArchiveType = ".zip" + // TarGZArchive represent tar.gz format + TarGZArchive ArchiveType = ".tar.gz" +) + +// GetArchive get an archive of a repository by git reference +// e.g.: ref -> master, 70b7c74b33, v1.2.1, ... +func (c *Client) GetArchive(owner, repo, ref string, ext ArchiveType) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + ref = pathEscapeSegments(ref) + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil) +} + +// GetArchiveReader gets a `git archive` for a particular tree-ish git reference +// such as a branch name (`master`), a commit hash (`70b7c74b33`), a tag +// (`v1.2.1`). The archive is returned as a byte stream in a ReadCloser. It is +// the responsibility of the client to close the reader. +func (c *Client) GetArchiveReader(owner, repo, ref string, ext ArchiveType) (io.ReadCloser, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + ref = pathEscapeSegments(ref) + return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil) +} + +// UpdateRepoAvatarOption options for updating repository avatar +type UpdateRepoAvatarOption struct { + Image string `json:"image"` // base64 encoded image +} + +// UpdateRepoAvatar updates a repository's avatar +func (c *Client) UpdateRepoAvatar(owner, repo string, opt UpdateRepoAvatarOption) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", + fmt.Sprintf("/repos/%s/%s/avatar", owner, repo), + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// DeleteRepoAvatar deletes a repository's avatar +func (c *Client) DeleteRepoAvatar(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", + fmt.Sprintf("/repos/%s/%s/avatar", owner, repo), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_action.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_action.go new file mode 100644 index 0000000..68ff96a --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_action.go @@ -0,0 +1,151 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" +) + +// ListRepoActionSecretOption list RepoActionSecret options +type ListRepoActionSecretOption struct { + ListOptions +} + +// CreateActionsVariable represents body for creating a action variable. +type CreateRepoActionsVariable struct { + Value string `json:"value"` +} + +// PutActionsVariable represents body for updating a action variable. +type PutRepoActionsVariable struct { + Value string `json:"value"` + Name string `json:"name"` +} + +// ListRepoActionSecret list a repository's secrets +func (c *Client) ListRepoActionSecret(user, repo string, opt ListRepoActionSecretOption) ([]*Secret, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + secrets := make([]*Secret, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/secrets", user, repo)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets) + return secrets, resp, err +} + +// ListRepoActionVariableOption lists RepoActionVariable options +type ListRepoActionVariableOption struct { + ListOptions +} + +// ListRepoActionVariable lists a repository's action variables +func (c *Client) ListRepoActionVariable(user, repo string, opt ListRepoActionVariableOption) ([]*RepoActionVariable, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + variables := make([]*RepoActionVariable, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/variables", user, repo)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &variables) + return variables, resp, err +} + +// CreateRepoActionSecret creates a secret for the specified repository in the Gitea Actions. +func (c *Client) CreateRepoActionSecret(user, repo, secretName string, opt CreateOrUpdateSecretOption) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &secretName); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/repos/%s/%s/actions/secrets/%s", user, repo, secretName), jsonHeader, bytes.NewReader(body)) +} + +// DeleteRepoActionSecret deletes a secret from the Gitea Actions. +// It takes the repository owner, name and the secret name as parameters. +// The function returns the HTTP response and an error, if any. +func (c *Client) DeleteRepoActionSecret(user, repo, secretName string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/actions/secrets/%s", user, repo, secretName), nil, nil) +} + +// GetRepoActionVariable returns a repository variable in the Gitea Actions. +// It takes the repository owner, name and the variable name as parameters. +// The function returns the HTTP response and an error, if any. +func (c *Client) GetRepoActionVariable(user, repo, variableName string) (*RepoActionVariable, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + variable := new(RepoActionVariable) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/variables/%s", user, repo, variableName), nil, nil, variable) + return variable, resp, err +} + +// CreateRepoActionVariable creates a repository variable in the Gitea Actions. +// It takes the repository owner, name, variable name and the variable value as parameters. +// The function returns the HTTP response and an error, if any. +func (c *Client) CreateRepoActionVariable(user, repo, variableName, value string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + + create := CreateRepoActionsVariable{ + Value: value, + } + + body, err := json.Marshal(&create) + if err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/actions/variables/%s", user, repo, variableName), jsonHeader, bytes.NewReader(body)) +} + +// UpdateRepoActionVariable updates a repository variable in the Gitea Actions. +// It takes the repository owner, name, variable name and the variable value as parameters. +// The function returns the HTTP response and an error, if any. +func (c *Client) UpdateRepoActionVariable(user, repo, variableName, value string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + + update := PutRepoActionsVariable{ + Value: value, + Name: variableName, + } + + body, err := json.Marshal(&update) + if err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/repos/%s/%s/actions/variables/%s", user, repo, variableName), jsonHeader, bytes.NewReader(body)) +} + +// DeleteRepoActionVariable deletes a repository variable in the Gitea Actions. +// It takes the repository owner, name and the variable name as parameters. +// The function returns the HTTP response and an error, if any. +func (c *Client) DeleteRepoActionVariable(user, reponame, variableName string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &reponame); err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/actions/variables/%s", user, reponame, variableName), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_action_extra.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_action_extra.go new file mode 100644 index 0000000..b20e090 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_action_extra.go @@ -0,0 +1,212 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/url" +) + +// CreateRepoActionRunnerRegistrationToken creates a repository-scope runner registration token. +func (c *Client) CreateRepoActionRunnerRegistrationToken(owner, repo string) (*RegistrationToken, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, nil, err + } + return c.createActionRegistrationToken(fmt.Sprintf("/repos/%s/%s/actions/runners/registration-token", owner, repo)) +} + +// ListRepoActionRunners lists repository-scope Actions runners. +func (c *Client) ListRepoActionRunners(owner, repo string, opt ListActionRunnersOptions) (*ActionRunnersResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.listActionRunners(fmt.Sprintf("/repos/%s/%s/actions/runners", owner, repo), opt) +} + +// GetRepoActionRunner gets one repository-scope Actions runner. +func (c *Client) GetRepoActionRunner(owner, repo string, runnerID int64) (*ActionRunner, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.getActionRunner(fmt.Sprintf("/repos/%s/%s/actions/runners/%d", owner, repo, runnerID)) +} + +// DeleteRepoActionRunner deletes one repository-scope Actions runner. +func (c *Client) DeleteRepoActionRunner(owner, repo string, runnerID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/actions/runners/%d", owner, repo, runnerID), nil, nil) +} + +// UpdateRepoActionRunner updates one repository-scope Actions runner. +func (c *Client) UpdateRepoActionRunner(owner, repo string, runnerID int64, opt EditActionRunnerOption) (*ActionRunner, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.updateActionRunner(fmt.Sprintf("/repos/%s/%s/actions/runners/%d", owner, repo, runnerID), opt) +} + +// ListRepoActionWorkflows lists repository workflows. +func (c *Client) ListRepoActionWorkflows(owner, repo string) (*ActionWorkflowResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + workflows := new(ActionWorkflowResponse) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/workflows", owner, repo), jsonHeader, nil, workflows) + return workflows, resp, err +} + +// GetRepoActionWorkflow gets one repository workflow. +func (c *Client) GetRepoActionWorkflow(owner, repo, workflowID string) (*ActionWorkflow, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &workflowID); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + workflow := new(ActionWorkflow) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/workflows/%s", owner, repo, workflowID), jsonHeader, nil, workflow) + return workflow, resp, err +} + +// DisableRepoActionWorkflow disables one repository workflow. +func (c *Client) DisableRepoActionWorkflow(owner, repo, workflowID string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &workflowID); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/repos/%s/%s/actions/workflows/%s/disable", owner, repo, workflowID), nil, nil) +} + +// EnableRepoActionWorkflow enables one repository workflow. +func (c *Client) EnableRepoActionWorkflow(owner, repo, workflowID string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &workflowID); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/repos/%s/%s/actions/workflows/%s/enable", owner, repo, workflowID), nil, nil) +} + +// DispatchRepoActionWorkflow dispatches one repository workflow. +func (c *Client) DispatchRepoActionWorkflow(owner, repo, workflowID string, opt CreateActionWorkflowDispatchOption, returnRunDetails bool) (*RunDetails, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &workflowID); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/workflows/%s/dispatches", owner, repo, workflowID)) + if returnRunDetails { + link.RawQuery = url.Values{"return_run_details": []string{"true"}}.Encode() + details := new(RunDetails) + resp, err := c.getParsedResponse("POST", link.String(), jsonHeader, bytes.NewReader(body), details) + return details, resp, err + } + + resp, err := c.doRequestWithStatusHandle("POST", link.String(), jsonHeader, bytes.NewReader(body)) + return nil, resp, err +} + +// ListRepoActionRunArtifacts lists artifacts for one workflow run. +func (c *Client) ListRepoActionRunArtifacts(owner, repo string, runID int64, opt ListActionArtifactsOptions) (*ActionArtifactsResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.listActionArtifacts(fmt.Sprintf("/repos/%s/%s/actions/runs/%d/artifacts", owner, repo, runID), opt) +} + +// ListRepoActionArtifacts lists repository artifacts. +func (c *Client) ListRepoActionArtifacts(owner, repo string, opt ListActionArtifactsOptions) (*ActionArtifactsResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.listActionArtifacts(fmt.Sprintf("/repos/%s/%s/actions/artifacts", owner, repo), opt) +} + +// GetRepoActionArtifact gets one repository artifact. +func (c *Client) GetRepoActionArtifact(owner, repo string, artifactID int64) (*ActionArtifact, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + artifact := new(ActionArtifact) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/artifacts/%d", owner, repo, artifactID), jsonHeader, nil, artifact) + return artifact, resp, err +} + +// DeleteRepoActionArtifact deletes one repository artifact. +func (c *Client) DeleteRepoActionArtifact(owner, repo string, artifactID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/actions/artifacts/%d", owner, repo, artifactID), nil, nil) +} + +// GetRepoActionArtifactArchive downloads one repository artifact zip archive. +func (c *Client) GetRepoActionArtifactArchive(owner, repo string, artifactID int64) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/actions/artifacts/%d/zip", owner, repo, artifactID), nil, nil) +} + +// GetRepoActionArtifactArchiveReader returns a reader for one repository artifact zip archive. +func (c *Client) GetRepoActionArtifactArchiveReader(owner, repo string, artifactID int64) (io.ReadCloser, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/actions/artifacts/%d/zip", owner, repo, artifactID), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_action_variable.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_action_variable.go new file mode 100644 index 0000000..aedc0f4 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_action_variable.go @@ -0,0 +1,13 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +// RepoActionVariable represents a action variable +type RepoActionVariable struct { + OwnerID int64 `json:"owner_id"` + RepoID int64 `json:"repo_id"` + Name string `json:"name"` + Value string `json:"data"` +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_branch.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_branch.go new file mode 100644 index 0000000..9ca8fdd --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_branch.go @@ -0,0 +1,233 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "time" +) + +// PayloadUser represents the author or committer of a commit +type PayloadUser struct { + // Full name of the commit author + Name string `json:"name"` + Email string `json:"email"` + UserName string `json:"username"` +} + +// PayloadCommit represents a commit +type PayloadCommit struct { + // sha1 hash of the commit + ID string `json:"id"` + Message string `json:"message"` + URL string `json:"url"` + Author *PayloadUser `json:"author"` + Committer *PayloadUser `json:"committer"` + Verification *PayloadCommitVerification `json:"verification"` + Timestamp time.Time `json:"timestamp"` + Added []string `json:"added"` + Removed []string `json:"removed"` + Modified []string `json:"modified"` +} + +// PayloadCommitVerification represents the GPG verification of a commit +type PayloadCommitVerification struct { + Verified bool `json:"verified"` + Reason string `json:"reason"` + Signature string `json:"signature"` + Payload string `json:"payload"` +} + +// Branch represents a repository branch +type Branch struct { + Name string `json:"name"` + Commit *PayloadCommit `json:"commit"` + Protected bool `json:"protected"` + RequiredApprovals int64 `json:"required_approvals"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + UserCanPush bool `json:"user_can_push"` + UserCanMerge bool `json:"user_can_merge"` + EffectiveBranchProtectionName string `json:"effective_branch_protection_name"` +} + +// ListRepoBranchesOptions options for listing a repository's branches +type ListRepoBranchesOptions struct { + ListOptions +} + +// ListRepoBranches list all the branches of one repository +func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions) ([]*Branch, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + branches := make([]*Branch, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &branches) + return branches, resp, err +} + +// GetRepoBranch get one branch's information of one repository +func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + branch = pathEscapeSegments(branch) + b := new(Branch) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b) + if err != nil { + return nil, resp, err + } + return b, resp, nil +} + +// DeleteRepoBranch delete a branch in a repository +func (c *Client) DeleteRepoBranch(user, repo, branch string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return false, nil, err + } + branch = pathEscapeSegments(branch) + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil) + if err != nil { + return false, resp, err + } + return status == 204, resp, nil +} + +type UpdateRepoBranchOption struct { + Name string `json:"name"` +} + +func (opt UpdateRepoBranchOption) Validate() error { + if len(opt.Name) == 0 { + return errors.New("empty Name field") + } + + return nil +} + +// RenameRepoBranchOption renames a repository branch. +type RenameRepoBranchOption struct { + Name string `json:"name"` +} + +// Validate checks whether the rename payload is valid. +func (opt RenameRepoBranchOption) Validate() error { + if len(opt.Name) == 0 { + return errors.New("empty Name field") + } + return nil +} + +// UpdateRepoBranchRefOption updates a branch ref to point to a new commit. +type UpdateRepoBranchRefOption struct { + NewCommitID string `json:"new_commit_id"` + OldCommitID string `json:"old_commit_id"` + Force bool `json:"force"` +} + +// Validate checks whether the branch update payload is valid. +func (opt UpdateRepoBranchRefOption) Validate() error { + if len(opt.NewCommitID) == 0 { + return errors.New("empty NewCommitID field") + } + return nil +} + +// RenameRepoBranch renames a branch in a repository. +func (c *Client) RenameRepoBranch(user, repo, branch string, opt RenameRepoBranchOption) (successful bool, resp *Response, err error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return false, nil, err + } + branch = pathEscapeSegments(branch) + if err := c.checkServerVersionGreaterThanOrEqual(version1_24_0); err != nil { + return false, nil, err + } + if err := opt.Validate(); err != nil { + return false, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("PATCH", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), jsonHeader, bytes.NewReader(body)) + return status == 204, resp, err +} + +// UpdateRepoBranchRef updates the commit a branch points to. +// Available on Gitea main/nightly; first released version not assigned yet. +// Upstream: gitea/gitea#35951 (a440116a16c42956f21031bea8422ffbb003c732). +func (c *Client) UpdateRepoBranchRef(user, repo, branch string, opt UpdateRepoBranchRefOption) (successful bool, resp *Response, err error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return false, nil, err + } + branch = pathEscapeSegments(branch) + if err := opt.Validate(); err != nil { + return false, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), jsonHeader, bytes.NewReader(body)) + return status == 204, resp, err +} + +// UpdateRepoBranch renames a branch in a repository. +// +// Deprecated: Use RenameRepoBranch instead (identical behavior, clearer name). +// For updating the commit a branch points to, use UpdateRepoBranchRef (PUT). +func (c *Client) UpdateRepoBranch(user, repo, branch string, opt UpdateRepoBranchOption) (sucessful bool, resp *Response, err error) { + return c.RenameRepoBranch(user, repo, branch, RenameRepoBranchOption(opt)) +} + +// CreateBranchOption options when creating a branch in a repository +type CreateBranchOption struct { + // Name of the branch to create + BranchName string `json:"new_branch_name"` + // Name of the old branch to create from (optional) + OldBranchName string `json:"old_branch_name"` +} + +// Validate the CreateBranchOption struct +func (opt CreateBranchOption) Validate() error { + if len(opt.BranchName) == 0 { + return fmt.Errorf("BranchName is empty") + } + if len(opt.BranchName) > 100 { + return fmt.Errorf("BranchName to long") + } + if len(opt.OldBranchName) > 100 { + return fmt.Errorf("OldBranchName to long") + } + return nil +} + +// CreateBranch creates a branch for a user's repository +func (c *Client) CreateBranch(owner, repo string, opt CreateBranchOption) (*Branch, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + branch := new(Branch) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branches", owner, repo), jsonHeader, bytes.NewReader(body), branch) + return branch, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go new file mode 100644 index 0000000..7c85b1f --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go @@ -0,0 +1,175 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// BranchProtection represents a branch protection for a repository +type BranchProtection struct { + BranchName string `json:"branch_name"` + RuleName string `json:"rule_name"` + EnablePush bool `json:"enable_push"` + EnablePushWhitelist bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` + EnableMergeWhitelist bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals int64 `json:"required_approvals"` + EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` + BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` + BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` + DismissStaleApprovals bool `json:"dismiss_stale_approvals"` + RequireSignedCommits bool `json:"require_signed_commits"` + ProtectedFilePatterns string `json:"protected_file_patterns"` + UnprotectedFilePatterns string `json:"unprotected_file_patterns"` + BlockAdminMergeOverride bool `json:"block_admin_merge_override"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CreateBranchProtectionOption options for creating a branch protection +type CreateBranchProtectionOption struct { + BranchName string `json:"branch_name"` + RuleName string `json:"rule_name"` + EnablePush bool `json:"enable_push"` + EnablePushWhitelist bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` + EnableMergeWhitelist bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals int64 `json:"required_approvals"` + EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` + BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` + BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` + DismissStaleApprovals bool `json:"dismiss_stale_approvals"` + RequireSignedCommits bool `json:"require_signed_commits"` + ProtectedFilePatterns string `json:"protected_file_patterns"` + UnprotectedFilePatterns string `json:"unprotected_file_patterns"` + BlockAdminMergeOverride bool `json:"block_admin_merge_override"` +} + +// EditBranchProtectionOption options for editing a branch protection +type EditBranchProtectionOption struct { + EnablePush *bool `json:"enable_push"` + EnablePushWhitelist *bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"` + EnableMergeWhitelist *bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck *bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals *int64 `json:"required_approvals"` + EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"` + BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"` + BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"` + DismissStaleApprovals *bool `json:"dismiss_stale_approvals"` + RequireSignedCommits *bool `json:"require_signed_commits"` + ProtectedFilePatterns *string `json:"protected_file_patterns"` + UnprotectedFilePatterns *string `json:"unprotected_file_patterns"` + BlockAdminMergeOverride *bool `json:"block_admin_merge_override"` +} + +// ListBranchProtectionsOptions list branch protection options +type ListBranchProtectionsOptions struct { + ListOptions +} + +// ListBranchProtections list branch protections for a repo +func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + bps := make([]*BranchProtection, 0, opt.PageSize) + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &bps) + return bps, resp, err +} + +// GetBranchProtection gets a branch protection +func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + bp := new(BranchProtection) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil, bp) + return bp, resp, err +} + +// CreateBranchProtection creates a branch protection for a repo +func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + bp := new(BranchProtection) + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo), jsonHeader, bytes.NewReader(body), bp) + return bp, resp, err +} + +// EditBranchProtection edits a branch protection for a repo +func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + bp := new(BranchProtection) + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), bp) + return bp, resp, err +} + +// DeleteBranchProtection deletes a branch protection for a repo +func (c *Client) DeleteBranchProtection(owner, repo, name string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_collaborator.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_collaborator.go new file mode 100644 index 0000000..2825a3e --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_collaborator.go @@ -0,0 +1,161 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// ListCollaboratorsOptions options for listing a repository's collaborators +type ListCollaboratorsOptions struct { + ListOptions +} + +// CollaboratorPermissionResult result type for CollaboratorPermission +type CollaboratorPermissionResult struct { + Permission AccessMode `json:"permission"` + Role string `json:"role_name"` + User *User `json:"user"` +} + +// ListCollaborators list a repository's collaborators +func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + collaborators := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/collaborators?%s", user, repo, opt.getURLQuery().Encode()), + nil, nil, &collaborators) + return collaborators, resp, err +} + +// IsCollaborator check if a user is a collaborator of a repository +func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil) + if err != nil { + return false, resp, err + } + if status == 204 { + return true, resp, nil + } + return false, resp, nil +} + +// CollaboratorPermission gets collaborator permission of a repository +func (c *Client) CollaboratorPermission(user, repo, collaborator string) (*CollaboratorPermissionResult, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil { + return nil, nil, err + } + rv := new(CollaboratorPermissionResult) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", user, repo, collaborator), + nil, + nil, + rv) + if err != nil { + return nil, resp, err + } + if resp.StatusCode != 200 { + rv = nil + } + return rv, resp, nil +} + +// AddCollaboratorOption options when adding a user as a collaborator of a repository +type AddCollaboratorOption struct { + Permission *AccessMode `json:"permission"` +} + +// AccessMode represent the grade of access you have to something +type AccessMode string + +const ( + // AccessModeNone no access + AccessModeNone AccessMode = "none" + // AccessModeRead read access + AccessModeRead AccessMode = "read" + // AccessModeWrite write access + AccessModeWrite AccessMode = "write" + // AccessModeAdmin admin access + AccessModeAdmin AccessMode = "admin" + // AccessModeOwner owner + AccessModeOwner AccessMode = "owner" +) + +// Validate the AddCollaboratorOption struct +func (opt *AddCollaboratorOption) Validate() error { + if opt.Permission != nil { + if *opt.Permission == AccessModeOwner { + *opt.Permission = AccessModeAdmin + return nil + } + if *opt.Permission == AccessModeNone { + opt.Permission = nil + return nil + } + if *opt.Permission != AccessModeRead && *opt.Permission != AccessModeWrite && *opt.Permission != AccessModeAdmin { + return fmt.Errorf("permission mode invalid") + } + } + return nil +} + +// AddCollaborator add some user as a collaborator of a repository +func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollaboratorOption) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil { + return nil, err + } + if err := (&opt).Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), jsonHeader, bytes.NewReader(body)) +} + +// DeleteCollaborator remove a collaborator from a repository +func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil) +} + +// GetReviewers return all users that can be requested to review in this repo +func (c *Client) GetReviewers(user, repo string) ([]*User, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + reviewers := make([]*User, 0, 5) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/reviewers", user, repo), nil, nil, &reviewers) + return reviewers, resp, err +} + +// GetAssignees return all users that have write access and can be assigned to issues +func (c *Client) GetAssignees(user, repo string) ([]*User, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + assignees := make([]*User, 0, 5) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/assignees", user, repo), nil, nil, &assignees) + return assignees, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_commit.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_commit.go new file mode 100644 index 0000000..962c25f --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_commit.go @@ -0,0 +1,156 @@ +// Copyright 2018 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "strconv" + "time" +) + +// Identity for a person's identity like an author or committer +type Identity struct { + Name string `json:"name"` + Email string `json:"email"` +} + +// CommitMeta contains meta information of a commit in terms of API. +type CommitMeta struct { + URL string `json:"url"` + SHA string `json:"sha"` + Created time.Time `json:"created"` +} + +// CommitUser contains information of a user in the context of a commit. +type CommitUser struct { + Identity + Date string `json:"date"` +} + +// RepoCommit contains information of a commit in the context of a repository. +type RepoCommit struct { + URL string `json:"url"` + Author *CommitUser `json:"author"` + Committer *CommitUser `json:"committer"` + Message string `json:"message"` + Tree *CommitMeta `json:"tree"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// CommitStats contains stats from a Git commit +type CommitStats struct { + Total int `json:"total"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` +} + +// Commit contains information generated from a Git commit. +type Commit struct { + *CommitMeta + HTMLURL string `json:"html_url"` + RepoCommit *RepoCommit `json:"commit"` + Author *User `json:"author"` + Committer *User `json:"committer"` + Parents []*CommitMeta `json:"parents"` + Files []*CommitAffectedFiles `json:"files"` + Stats *CommitStats `json:"stats"` +} + +// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE +type CommitDateOptions struct { + Author time.Time `json:"author"` + Committer time.Time `json:"committer"` +} + +// CommitAffectedFiles store information about files affected by the commit +type CommitAffectedFiles struct { + Filename string `json:"filename"` +} + +// GetSingleCommit returns a single commit +func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &commitID); err != nil { + return nil, nil, err + } + commit := new(Commit) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s", user, repo, commitID), nil, nil, &commit) + return commit, resp, err +} + +// ListCommitOptions list commit options +type ListCommitOptions struct { + ListOptions + // SHA or branch to start listing commits from (usually 'master') + SHA string + // Path indicates that only commits that include the path's file/dir should be returned. + Path string + // Stat includes diff stats for every commit (disable for speedup) + Stat bool + // Verification includes verification for every commit (disable for speedup) + Verification bool + // Files includes a list of affected files for every commit (disable for speedup) + Files bool + // Not is a string used such that commits that match the given specifier will not be listed. + Not string +} + +// QueryEncode turns options into querystring argument +func (opt *ListCommitOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.SHA != "" { + query.Add("sha", opt.SHA) + } + if opt.Path != "" { + query.Add("path", opt.Path) + } + query.Add("stat", strconv.FormatBool(opt.Stat)) + query.Add("verification", strconv.FormatBool(opt.Verification)) + query.Add("files", strconv.FormatBool(opt.Files)) + if opt.Not != "" { + query.Add("not", opt.Not) + } + return query.Encode() +} + +// ListRepoCommits return list of commits from a repo +func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo)) + opt.setDefaults() + commits := make([]*Commit, 0, opt.PageSize) + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits) + return commits, resp, err +} + +// GetCommitDiff returns the commit's raw diff. +func (c *Client) GetCommitDiff(user, repo, commitID string) ([]byte, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil { + return nil, nil, err + } + + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypeDiff), nil, nil) +} + +// GetCommitPatch returns the commit's raw patch. +func (c *Client) GetCommitPatch(user, repo, commitID string) ([]byte, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil { + return nil, nil, err + } + + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypePatch), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_compare.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_compare.go new file mode 100644 index 0000000..80a0663 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_compare.go @@ -0,0 +1,33 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import "fmt" + +// Compare represents a comparison between two commits. +type Compare struct { + TotalCommits int `json:"total_commits"` // Total number of commits in the comparison. + Commits []*Commit `json:"commits"` // List of commits in the comparison. +} + +// CompareCommits compares two commits in a repository. +func (c *Client) CompareCommits(user, repo, prev, current string) (*Compare, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &prev, ¤t); err != nil { + return nil, nil, err + } + + basehead := fmt.Sprintf("%s...%s", prev, current) + + apiResp := new(Compare) + resp, err := c.getParsedResponse( + "GET", + fmt.Sprintf("/repos/%s/%s/compare/%s", user, repo, basehead), + nil, nil, apiResp, + ) + return apiResp, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_ext.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_ext.go new file mode 100644 index 0000000..8f65374 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_ext.go @@ -0,0 +1,342 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" +) + +// --- Pinned pull requests --- + +// ListRepoPinnedPullRequests lists a repo's pinned pull requests +func (c *Client) ListRepoPinnedPullRequests(owner, repo string) ([]*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + prs := make([]*PullRequest, 0, 5) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/pulls/pinned", owner, repo), + jsonHeader, nil, &prs) + return prs, resp, err +} + +// --- Webhooks --- + +// TestWebhook tests a webhook +func (c *Client) TestWebhook(owner, repo string, hookID int64, ref string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + opt := map[string]string{"ref": ref} + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", + fmt.Sprintf("/repos/%s/%s/hooks/%d/tests", owner, repo, hookID), + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// --- Merge upstream --- + +// MergeUpstreamRequest options for merging upstream +type MergeUpstreamRequest struct { + Branch string `json:"branch"` + FfOnly bool `json:"ff_only"` +} + +// MergeUpstreamResponse represents the response from merging upstream +type MergeUpstreamResponse struct { + MergeStyle string `json:"merge_type"` +} + +// MergeUpstream merges upstream into a forked repository +func (c *Client) MergeUpstream(owner, repo string, opt MergeUpstreamRequest) (*MergeUpstreamResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + result := new(MergeUpstreamResponse) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/merge-upstream", owner, repo), + jsonHeader, bytes.NewReader(body), result) + return result, resp, err +} + +// --- Pin allowed --- + +// NewIssuePinsAllowed represents whether new issue/PR pins are allowed +type NewIssuePinsAllowed struct { + Issues bool `json:"issues"` + PullRequests bool `json:"pull_requests"` +} + +// CheckPinAllowed checks if the current user can pin issues or PRs +func (c *Client) CheckPinAllowed(owner, repo string) (*NewIssuePinsAllowed, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + result := new(NewIssuePinsAllowed) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/new_pin_allowed", owner, repo), + jsonHeader, nil, result) + return result, resp, err +} + +// --- Batch file operations --- + +// ChangeFilesOptions options for batch file operations +type ChangeFilesOptions struct { + Files []*ChangeFileOperation `json:"files"` + Message string `json:"message"` + Branch string `json:"branch,omitempty"` + NewBranch string `json:"new_branch,omitempty"` + ForcePush bool `json:"force_push,omitempty"` + Author Identity `json:"author"` + Committer Identity `json:"committer"` + Dates CommitDateOptions `json:"dates"` + Signoff bool `json:"signoff,omitempty"` +} + +// ChangeFileOperation represents a file operation in batch +type ChangeFileOperation struct { + Operation string `json:"operation"` // create, update, upload, rename, delete + Path string `json:"path"` + Content string `json:"content"` // base64 encoded for create/update + SHA string `json:"sha,omitempty"` // required for update/delete + FromPath string `json:"from_path,omitempty"` // for rename +} + +// ChangeFiles creates, updates, or deletes multiple files +func (c *Client) ChangeFiles(owner, repo string, opt ChangeFilesOptions) (*FileResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + result := new(FileResponse) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/contents", owner, repo), + jsonHeader, bytes.NewReader(body), &result) + return result, resp, err +} + +// --- Branch protection priorities --- + +// UpdateBranchProtectionPriorities updates the priorities of branch protection rules +func (c *Client) UpdateBranchProtectionPriorities(owner, repo string, ids []int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + opt := map[string][]int64{"ids": ids} + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", + fmt.Sprintf("/repos/%s/%s/branch_protections/priority", owner, repo), + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// --- File contents (batch lookup) --- + +// GetFilesOptions controls batch file-content lookup requests. +type GetFilesOptions struct { + Files []string `json:"files"` +} + +// Validate checks whether the batch file lookup request is valid. +func (opt GetFilesOptions) Validate() error { + if len(opt.Files) == 0 { + return errors.New("empty Files field") + } + return nil +} + +// GetRepoFileContents fetches metadata and contents for multiple files through the GET endpoint. +// The file list is JSON-encoded in the "body" query parameter; for large file lists prefer +// PostRepoFileContents to avoid URL length limitations. +func (c *Client) GetRepoFileContents(owner, repo, ref string, opt GetFilesOptions) ([]*ContentsResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/file-contents", owner, repo)) + query := link.Query() + if ref != "" { + query.Add("ref", ref) + } + query.Add("body", string(body)) + link.RawQuery = query.Encode() + + contents := make([]*ContentsResponse, 0, len(opt.Files)) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &contents) + return contents, resp, err +} + +// PostRepoFileContents fetches metadata and contents for multiple files through the POST endpoint. +func (c *Client) PostRepoFileContents(owner, repo, ref string, opt GetFilesOptions) ([]*ContentsResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/file-contents", owner, repo)) + if ref != "" { + link.RawQuery = url.Values{"ref": []string{ref}}.Encode() + } + + contents := make([]*ContentsResponse, 0, len(opt.Files)) + resp, err := c.getParsedResponse("POST", link.String(), jsonHeader, bytes.NewReader(body), &contents) + return contents, resp, err +} + +// --- Issue config --- + +// IssueConfigContactLink represents an issue config contact link. +type IssueConfigContactLink struct { + Name string `json:"name"` + URL string `json:"url"` + About string `json:"about"` +} + +// IssueConfig represents the parsed issue config for a repository. +type IssueConfig struct { + BlankIssuesEnabled bool `json:"blank_issues_enabled"` + ContactLinks []IssueConfigContactLink `json:"contact_links"` +} + +// GetIssueConfig gets the issue config for a repository. +func (c *Client) GetIssueConfig(owner, repo string) (*IssueConfig, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + config := new(IssueConfig) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issue_config", owner, repo), jsonHeader, nil, config) + return config, resp, err +} + +// --- Licenses --- + +// GetRepoLicenses gets detected licenses for a repository. +func (c *Client) GetRepoLicenses(owner, repo string) ([]string, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + licenses := make([]string, 0, 2) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/licenses", owner, repo), jsonHeader, nil, &licenses) + return licenses, resp, err +} + +// --- Signing keys --- + +// GetRepoSigningKeyGPG gets the repository signing GPG public key. +func (c *Client) GetRepoSigningKeyGPG(owner, repo string) (string, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return "", nil, err + } + key, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/signing-key.gpg", owner, repo), nil, nil) + return string(key), resp, err +} + +// GetRepoSigningKeySSH gets the repository signing SSH public key. +func (c *Client) GetRepoSigningKeySSH(owner, repo string) (string, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return "", nil, err + } + key, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/signing-key.pub", owner, repo), nil, nil) + return string(key), resp, err +} + +// --- Subscribers --- + +// ListRepoSubscribers lists repository watchers. +func (c *Client) ListRepoSubscribers(owner, repo string, opt ListOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + subscribers := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/subscribers?%s", owner, repo, opt.getURLQuery().Encode()), jsonHeader, nil, &subscribers) + return subscribers, resp, err +} + +// --- Diff patch --- + +// ApplyDiffPatchFileOptions applies a patch against repository contents. +type ApplyDiffPatchFileOptions struct { + FileOptions + Content string `json:"content"` +} + +// Validate checks whether the patch payload is valid. +func (opt ApplyDiffPatchFileOptions) Validate() error { + if len(opt.Content) == 0 { + return errors.New("empty Content field") + } + return nil +} + +// ApplyRepoDiffPatch applies a patch to repository contents. +func (c *Client) ApplyRepoDiffPatch(owner, repo string, opt ApplyDiffPatchFileOptions) (*FileResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + result := new(FileResponse) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/diffpatch", owner, repo), jsonHeader, bytes.NewReader(body), result) + return result, resp, err +} + +// --- Push mirrors --- + +// TriggerPushMirrorsSync triggers push-mirror syncing for a repository. +func (c *Client) TriggerPushMirrorsSync(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/repos/%s/%s/push_mirrors-sync", owner, repo), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_file.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_file.go new file mode 100644 index 0000000..ae07b52 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_file.go @@ -0,0 +1,292 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/url" + "strings" + "time" +) + +// FileOptions options for all file APIs +type FileOptions struct { + // message (optional) for the commit of this file. if not supplied, a default message will be used + Message string `json:"message"` + // branch (optional) to base this file from. if not given, the default branch is used + BranchName string `json:"branch"` + // new_branch (optional) will make a new branch from `branch` before creating the file + NewBranchName string `json:"new_branch"` + // force_push (optional) will do a force-push if the new branch already exists + ForcePush bool `json:"force_push"` + // `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) + Author Identity `json:"author"` + Committer Identity `json:"committer"` + Dates CommitDateOptions `json:"dates"` + // Add a Signed-off-by trailer by the committer at the end of the commit log message. + Signoff bool `json:"signoff"` +} + +// CreateFileOptions options for creating files +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type CreateFileOptions struct { + FileOptions + // content must be base64 encoded + // required: true + Content string `json:"content"` +} + +// DeleteFileOptions options for deleting files (used for other File structs below) +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type DeleteFileOptions struct { + FileOptions + // sha is the SHA for the file that already exists + // required: true + SHA string `json:"sha"` +} + +// UpdateFileOptions options for updating files +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type UpdateFileOptions struct { + FileOptions + // sha is the SHA for the file that already exists + // required: true + SHA string `json:"sha"` + // content must be base64 encoded + // required: true + Content string `json:"content"` + // from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL + FromPath string `json:"from_path"` +} + +// FileLinksResponse contains the links for a repo's file +type FileLinksResponse struct { + Self *string `json:"self"` + GitURL *string `json:"git"` + HTMLURL *string `json:"html"` +} + +// ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content +type ContentsResponse struct { + Name string `json:"name"` + Path string `json:"path"` + SHA string `json:"sha"` + LastCommitSha *string `json:"last_commit_sha,omitempty"` + // swagger:strfmt date-time + LastCommitterDate *time.Time `json:"last_committer_date,omitempty"` + // swagger:strfmt date-time + LastAuthorDate *time.Time `json:"last_author_date,omitempty"` + LastCommitMessage *string `json:"last_commit_message,omitempty"` + // `type` will be `file`, `dir`, `symlink`, or `submodule` + Type string `json:"type"` + Size int64 `json:"size"` + // `encoding` is populated when `type` is `file`, otherwise null + Encoding *string `json:"encoding"` + // `content` is populated when `type` is `file`, otherwise null + Content *string `json:"content"` + // `target` is populated when `type` is `symlink`, otherwise null + Target *string `json:"target"` + URL *string `json:"url"` + HTMLURL *string `json:"html_url"` + GitURL *string `json:"git_url"` + DownloadURL *string `json:"download_url"` + // `submodule_git_url` is populated when `type` is `submodule`, otherwise null + SubmoduleGitURL *string `json:"submodule_git_url"` + Links *FileLinksResponse `json:"_links"` + LfsOid *string `json:"lfs_oid,omitempty"` + LfsSize *int64 `json:"lfs_size,omitempty"` +} + +// FileCommitResponse contains information generated from a Git commit for a repo's file. +type FileCommitResponse struct { + CommitMeta + HTMLURL string `json:"html_url"` + Author *CommitUser `json:"author"` + Committer *CommitUser `json:"committer"` + Parents []*CommitMeta `json:"parents"` + Message string `json:"message"` + Tree *CommitMeta `json:"tree"` +} + +// FileResponse contains information about a repo's file +type FileResponse struct { + Content *ContentsResponse `json:"content"` + Commit *FileCommitResponse `json:"commit"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// FileDeleteResponse contains information about a repo's file that was deleted +type FileDeleteResponse struct { + Content interface{} `json:"content"` // to be set to nil + Commit *FileCommitResponse `json:"commit"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// GetFile downloads a file of repository, ref can be branch/tag/commit. +// it optional can resolve lfs pointers and server the file instead +// e.g.: ref -> master, filepath -> README.md (no leading slash) +func (c *Client) GetFile(owner, repo, ref, filepath string, resolveLFS ...bool) ([]byte, *Response, error) { + reader, resp, err := c.GetFileReader(owner, repo, ref, filepath, resolveLFS...) + if reader == nil { + return nil, resp, err + } + defer func() { + if closeErr := reader.Close(); closeErr != nil && err == nil { + err = closeErr + } + }() + + data, err2 := io.ReadAll(reader) + if err2 != nil { + return nil, resp, err2 + } + + return data, resp, err +} + +// GetFileReader return reader for download a file of repository, ref can be branch/tag/commit. +// it optional can resolve lfs pointers and server the file instead +// e.g.: ref -> master, filepath -> README.md (no leading slash) +func (c *Client) GetFileReader(owner, repo, ref, filepath string, resolveLFS ...bool) (io.ReadCloser, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + // resolve lfs + if len(resolveLFS) != 0 && resolveLFS[0] { + if err := c.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil { + return nil, nil, err + } + return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/media/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil) + } + + // normal get + filepath = pathEscapeSegments(filepath) + if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { + ref = pathEscapeSegments(ref) + return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", owner, repo, ref, filepath), nil, nil) + } + return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil) +} + +// GetContents get the metadata and contents of a file in a repository +// ref is optional +func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) { + data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath) + if err != nil { + return nil, resp, err + } + cr := new(ContentsResponse) + if json.Unmarshal(data, &cr) != nil { + return nil, resp, fmt.Errorf("expect file, got directory") + } + return cr, resp, err +} + +// ListContents gets a list of entries in a dir +// ref is optional +func (c *Client) ListContents(owner, repo, ref, filepath string) ([]*ContentsResponse, *Response, error) { + data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath) + if err != nil { + return nil, resp, err + } + crl := make([]*ContentsResponse, 0) + if json.Unmarshal(data, &crl) != nil { + return nil, resp, fmt.Errorf("expect directory, got file") + } + return crl, resp, err +} + +func (c *Client) getDirOrFileContents(owner, repo, ref, filepath string) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + filepath = pathEscapeSegments(strings.TrimPrefix(filepath, "/")) + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), jsonHeader, nil) +} + +// CreateFile create a file in a repository +func (c *Client) CreateFile(owner, repo, filepath string, opt CreateFileOptions) (*FileResponse, *Response, error) { + var err error + if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + filepath = pathEscapeSegments(filepath) + + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + fr := new(FileResponse) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr) + return fr, resp, err +} + +// UpdateFile update a file in a repository +func (c *Client) UpdateFile(owner, repo, filepath string, opt UpdateFileOptions) (*FileResponse, *Response, error) { + var err error + if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { + return nil, nil, err + } + + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + filepath = pathEscapeSegments(filepath) + + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + fr := new(FileResponse) + resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr) + return fr, resp, err +} + +// DeleteFile delete a file from repository +func (c *Client) DeleteFile(owner, repo, filepath string, opt DeleteFileOptions) (*Response, error) { + var err error + if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + filepath = pathEscapeSegments(filepath) + + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != 200 && status != 204 { + return resp, fmt.Errorf("unexpected Status: %d", status) + } + return resp, nil +} + +func (c *Client) setDefaultBranchForOldVersions(owner, repo, branch string) (string, error) { + if len(branch) == 0 { + // Gitea >= 1.12.0 Use DefaultBranch on "", mimic this for older versions + if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil { + r, _, err := c.GetRepo(owner, repo) + if err != nil { + return "", err + } + return r.DefaultBranch, nil + } + } + return branch, nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_file_ext.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_file_ext.go new file mode 100644 index 0000000..c5a8f4d --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_file_ext.go @@ -0,0 +1,102 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" +) + +// ContentsExtResponse contains extended information about a repo's contents +type ContentsExtResponse struct { + DirContents []*ContentsResponse `json:"dir_contents,omitempty"` + FileContents *ContentsResponse `json:"file_contents,omitempty"` +} + +// GetContentsExtOptions options for getting extended contents +type GetContentsExtOptions struct { + // The name of the commit/branch/tag. Default to the repository's default branch + Ref string `json:"ref,omitempty"` + // Comma-separated includes options: file_content, lfs_metadata, commit_metadata, commit_message + Includes string `json:"includes,omitempty"` +} + +// GetContentsExt gets extended file metadata and/or content from a repository +// The extended "contents" API, to get file metadata and/or content, or list a directory +func (c *Client) GetContentsExt(owner, repo, filepath string, opt GetContentsExtOptions) (*ContentsExtResponse, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + // filepath doesn't need escaping since it's already part of the path + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/contents-ext/%s", owner, repo, filepath)) + query := link.Query() + + if opt.Ref != "" { + query.Add("ref", opt.Ref) + } + if opt.Includes != "" { + query.Add("includes", opt.Includes) + } + + link.RawQuery = query.Encode() + + result := new(ContentsExtResponse) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, result) + return result, resp, err +} + +// GetEditorConfig gets the EditorConfig definitions of a file in a repository +func (c *Client) GetEditorConfig(owner, repo, filepath string, ref ...string) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/editorconfig/%s", owner, repo, filepath)) + + if len(ref) > 0 && ref[0] != "" { + query := link.Query() + query.Add("ref", ref[0]) + link.RawQuery = query.Encode() + } + + return c.getResponse("GET", link.String(), nil, nil) +} + +// GetRawFileOrLFS gets a file or its LFS object from a repository +// This endpoint resolves LFS pointers and returns actual LFS objects +func (c *Client) GetRawFileOrLFS(owner, repo, filepath string, ref ...string) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/media/%s", owner, repo, filepath)) + + if len(ref) > 0 && ref[0] != "" { + query := link.Query() + query.Add("ref", ref[0]) + link.RawQuery = query.Encode() + } + + return c.getResponse("GET", link.String(), nil, nil) +} + +// GetRawFile gets a file from a repository +// Unlike GetRawFileOrLFS, this does NOT resolve LFS pointers +func (c *Client) GetRawFile(owner, repo, filepath string, ref ...string) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/raw/%s", owner, repo, filepath)) + + if len(ref) > 0 && ref[0] != "" { + query := link.Query() + query.Add("ref", ref[0]) + link.RawQuery = query.Encode() + } + + return c.getResponse("GET", link.String(), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_git_notes.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_git_notes.go new file mode 100644 index 0000000..fdc03ce --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_git_notes.go @@ -0,0 +1,56 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" +) + +// Note represents a git note +type Note struct { + Message string `json:"message"` + Commit *Commit `json:"commit"` +} + +// GetRepoNoteOptions options for getting a note +type GetRepoNoteOptions struct { + // include verification for every commit (disable for speedup, default 'true') + Verification *bool `json:"verification,omitempty"` + // include a list of affected files for every commit (disable for speedup, default 'true') + Files *bool `json:"files,omitempty"` +} + +// GetRepoNote gets a note corresponding to a single commit from a repository +func (c *Client) GetRepoNote(owner, repo, sha string, opt GetRepoNoteOptions) (*Note, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &sha); err != nil { + return nil, nil, err + } + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/git/notes/%s", owner, repo, sha)) + query := link.Query() + + if opt.Verification != nil { + if *opt.Verification { + query.Add("verification", "true") + } else { + query.Add("verification", "false") + } + } + + if opt.Files != nil { + if *opt.Files { + query.Add("files", "true") + } else { + query.Add("files", "false") + } + } + + link.RawQuery = query.Encode() + + note := new(Note) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ¬e) + return note, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_helpers.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_helpers.go new file mode 100644 index 0000000..39fe38b --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_helpers.go @@ -0,0 +1,51 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" +) + +// GetCommitPullRequest gets the pull request associated with a commit SHA +func (c *Client) GetCommitPullRequest(owner, repo, sha string) (*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &sha); err != nil { + return nil, nil, err + } + pr := new(PullRequest) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/commits/%s/pull", owner, repo, sha), + jsonHeader, nil, &pr) + return pr, resp, err +} + +// UpdatePullRequest updates a pull request with new commits from the base branch +func (c *Client) UpdatePullRequest(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/update", owner, repo, index), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusOK { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// GetUserTrackedTimes gets all tracked times for a user in a repository +func (c *Client) GetUserTrackedTimes(owner, repo, user string) ([]*TrackedTime, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil { + return nil, nil, err + } + times := make([]*TrackedTime, 0, 10) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/times/%s", owner, repo, user), + jsonHeader, nil, ×) + return times, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_key.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_key.go new file mode 100644 index 0000000..b9964fb --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_key.go @@ -0,0 +1,90 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// DeployKey a deploy key +type DeployKey struct { + ID int64 `json:"id"` + KeyID int64 `json:"key_id"` + Key string `json:"key"` + URL string `json:"url"` + Title string `json:"title"` + Fingerprint string `json:"fingerprint"` + Created time.Time `json:"created_at"` + ReadOnly bool `json:"read_only"` + Repository *Repository `json:"repository,omitempty"` +} + +// ListDeployKeysOptions options for listing a repository's deploy keys +type ListDeployKeysOptions struct { + ListOptions + KeyID int64 + Fingerprint string +} + +// QueryEncode turns options into querystring argument +func (opt *ListDeployKeysOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.KeyID > 0 { + query.Add("key_id", fmt.Sprintf("%d", opt.KeyID)) + } + if len(opt.Fingerprint) > 0 { + query.Add("fingerprint", opt.Fingerprint) + } + return query.Encode() +} + +// ListDeployKeys list all the deploy keys of one repository +func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([]*DeployKey, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/keys", user, repo)) + opt.setDefaults() + link.RawQuery = opt.QueryEncode() + keys := make([]*DeployKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &keys) + return keys, resp, err +} + +// GetDeployKey get one deploy key with key id +func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + key := new(DeployKey) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key) + return key, resp, err +} + +// CreateDeployKey options when create one deploy key +func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + key := new(DeployKey) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/keys", user, repo), jsonHeader, bytes.NewReader(body), key) + return key, resp, err +} + +// DeleteDeployKey delete deploy key with key id +func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_label.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_label.go new file mode 100644 index 0000000..2cee0be --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_label.go @@ -0,0 +1,153 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "regexp" + "strings" +) + +// Label a label to an issue or a pr +type Label struct { + ID int64 `json:"id"` + Name string `json:"name"` + // example: 00aabb + Color string `json:"color"` + Description string `json:"description"` + Exclusive bool `json:"exclusive"` + IsArchived bool `json:"is_archived"` + URL string `json:"url"` +} + +// ListLabelsOptions options for listing repository's labels +type ListLabelsOptions struct { + ListOptions +} + +// ListRepoLabels list labels of one repository +func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + labels := make([]*Label, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels?%s", owner, repo, opt.getURLQuery().Encode()), nil, nil, &labels) + return labels, resp, err +} + +// GetRepoLabel get one label of repository by repo it +func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + label := new(Label) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label) + return label, resp, err +} + +// CreateLabelOption options for creating a label +type CreateLabelOption struct { + Name string `json:"name"` + // example: #00aabb + Color string `json:"color"` + Description string `json:"description"` + Exclusive bool `json:"exclusive"` + IsArchived bool `json:"is_archived"` +} + +// Validate the CreateLabelOption struct +func (opt CreateLabelOption) Validate() error { + aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", opt.Color) + if err != nil { + return err + } + if !aw { + return fmt.Errorf("invalid color format") + } + if len(strings.TrimSpace(opt.Name)) == 0 { + return fmt.Errorf("empty name not allowed") + } + return nil +} + +// CreateLabel create one label of repository +func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + if len(opt.Color) == 6 { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + opt.Color = "#" + opt.Color + } + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + label := new(Label) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/labels", owner, repo), + jsonHeader, bytes.NewReader(body), label) + return label, resp, err +} + +// EditLabelOption options for editing a label +type EditLabelOption struct { + Name *string `json:"name"` + Color *string `json:"color"` + Description *string `json:"description"` + Exclusive *bool `json:"exclusive"` + IsArchived *bool `json:"is_archived"` +} + +// Validate the EditLabelOption struct +func (opt EditLabelOption) Validate() error { + if opt.Color != nil { + aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", *opt.Color) + if err != nil { + return err + } + if !aw { + return fmt.Errorf("invalid color format") + } + } + if opt.Name != nil { + if len(strings.TrimSpace(*opt.Name)) == 0 { + return fmt.Errorf("empty name not allowed") + } + } + return nil +} + +// EditLabel modify one label with options +func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + label := new(Label) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), label) + return label, resp, err +} + +// DeleteLabel delete one label of repository by id +func (c *Client) DeleteLabel(owner, repo string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_migrate.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_migrate.go new file mode 100644 index 0000000..73a04bc --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_migrate.go @@ -0,0 +1,132 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// GitServiceType represents a git service +type GitServiceType string + +const ( + // GitServicePlain represents a plain git service + GitServicePlain GitServiceType = "git" + // GitServiceGithub represents github.com + GitServiceGithub GitServiceType = "github" + // GitServiceGitlab represents a gitlab service + GitServiceGitlab GitServiceType = "gitlab" + // GitServiceGitea represents a gitea service + GitServiceGitea GitServiceType = "gitea" + // GitServiceGogs represents a gogs service + GitServiceGogs GitServiceType = "gogs" +) + +// MigrateRepoOption options for migrating a repository from an external service +type MigrateRepoOption struct { + RepoName string `json:"repo_name"` + RepoOwner string `json:"repo_owner"` + // deprecated use RepoOwner + RepoOwnerID int64 `json:"uid"` + CloneAddr string `json:"clone_addr"` + Service GitServiceType `json:"service"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` + AuthToken string `json:"auth_token"` + Mirror bool `json:"mirror"` + Private bool `json:"private"` + Description string `json:"description"` + Wiki bool `json:"wiki"` + Milestones bool `json:"milestones"` + Labels bool `json:"labels"` + Issues bool `json:"issues"` + PullRequests bool `json:"pull_requests"` + Releases bool `json:"releases"` + MirrorInterval string `json:"mirror_interval"` + LFS bool `json:"lfs"` + LFSEndpoint string `json:"lfs_endpoint"` +} + +// Validate the MigrateRepoOption struct +func (opt *MigrateRepoOption) Validate(c *Client) error { + // check user options + if len(opt.CloneAddr) == 0 { + return fmt.Errorf("clone addr required") + } + if len(opt.RepoName) == 0 { + return fmt.Errorf("repo name required") + } else if len(opt.RepoName) > 100 { + return fmt.Errorf("repo name too long") + } + if len(opt.Description) > 2048 { + return fmt.Errorf("description too long") + } + switch opt.Service { + case GitServiceGithub: + if len(opt.AuthToken) == 0 { + return fmt.Errorf("github requires token authentication") + } + case GitServiceGitlab, GitServiceGitea: + if len(opt.AuthToken) == 0 { + return fmt.Errorf("%s requires token authentication", opt.Service) + } + // Gitlab is supported since 1.12.0 but api cant handle it until 1.13.0 + // https://github.com/go-gitea/gitea/pull/12672 + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + return fmt.Errorf("migrate from service %s need gitea >= 1.13.0", opt.Service) + } + case GitServiceGogs: + if len(opt.AuthToken) == 0 { + return fmt.Errorf("gogs requires token authentication") + } + if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { + return fmt.Errorf("migrate from service gogs need gitea >= 1.14.0") + } + } + return nil +} + +// MigrateRepo migrates a repository from other Git hosting sources for the authenticated user. +// +// To migrate a repository for a organization, the authenticated user must be a +// owner of the specified organization. +func (c *Client) MigrateRepo(opt MigrateRepoOption) (*Repository, *Response, error) { + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + if len(opt.AuthToken) != 0 { + // gitea <= 1.12 dont understand AuthToken + opt.AuthUsername = opt.AuthToken + opt.AuthPassword, opt.AuthToken = "", "" + } + if len(opt.RepoOwner) != 0 { + // gitea <= 1.12 dont understand RepoOwner + u, _, err := c.GetUserInfo(opt.RepoOwner) + if err != nil { + return nil, nil, err + } + opt.RepoOwnerID = u.ID + } else if opt.RepoOwnerID == 0 { + // gitea <= 1.12 require RepoOwnerID + u, _, err := c.GetMyUserInfo() + if err != nil { + return nil, nil, err + } + opt.RepoOwnerID = u.ID + } + } + + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", "/repos/migrate", jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_mirror.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_mirror.go new file mode 100644 index 0000000..2ee806f --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_mirror.go @@ -0,0 +1,76 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +type CreatePushMirrorOption struct { + Interval string `json:"interval"` + RemoteAddress string `json:"remote_address"` + RemotePassword string `json:"remote_password"` + RemoteUsername string `json:"remote_username"` + SyncONCommit bool `json:"sync_on_commit"` +} + +// PushMirrorResponse returns a git push mirror +type PushMirrorResponse struct { + Created string `json:"created"` + Interval string `json:"interval"` + LastError string `json:"last_error"` + LastUpdate string `json:"last_update"` + RemoteAddress string `json:"remote_address"` + RemoteName string `json:"remote_name"` + RepoName string `json:"repo_name"` + SyncONCommit bool `json:"sync_on_commit"` +} + +// PushMirrors add a push mirror to the repository +func (c *Client) PushMirrors(user, repo string, opt CreatePushMirrorOption) (*PushMirrorResponse, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(opt) + if err != nil { + return nil, nil, err + } + pm := new(PushMirrorResponse) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/push_mirrors", user, repo), jsonHeader, bytes.NewReader(body), &pm) + return pm, resp, err +} + +// ListPushMirrors gets all push mirrors of a repository +func (c *Client) ListPushMirrors(user, repo string, opt ListOptions) ([]*PushMirrorResponse, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + pms := make([]*PushMirrorResponse, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/push_mirrors?%s", user, repo, opt.getURLQuery().Encode()), + nil, nil, &pms) + return pms, resp, err +} + +// GetPushMirrorByRemoteName get a push mirror of the repository by remote name +func (c *Client) GetPushMirrorByRemoteName(user, repo, remoteName string) (*PushMirrorResponse, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &remoteName); err != nil { + return nil, nil, err + } + pm := new(PushMirrorResponse) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/push_mirrors/%s", user, repo, remoteName), nil, nil, &pm) + return pm, resp, err +} + +// DeletePushMirror deletes a push mirror from a repository by remote name +func (c *Client) DeletePushMirror(user, repo, remoteName string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &remoteName); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/push_mirrors/%s", user, repo, remoteName), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_misc.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_misc.go new file mode 100644 index 0000000..00b1236 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_misc.go @@ -0,0 +1,90 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "time" +) + +// ListRepoActivityFeedsOptions options for listing repository activity feeds +type ListRepoActivityFeedsOptions struct { + ListOptions + Date string `json:"date"` // the date of the activities to be found (format: YYYY-MM-DD) +} + +// ListRepoActivityFeeds lists activity feeds for a repository +func (c *Client) ListRepoActivityFeeds(owner, repo string, opt ListRepoActivityFeedsOptions) ([]*Activity, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/activities/feeds", owner, repo)) + query := opt.getURLQuery() + if opt.Date != "" { + query.Add("date", opt.Date) + } + link.RawQuery = query.Encode() + + feeds := make([]*Activity, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &feeds) + return feeds, resp, err +} + +// IssueConfigValidation represents the validation result for issue config +type IssueConfigValidation struct { + Valid bool `json:"valid"` + Message string `json:"message"` +} + +// ValidateIssueConfig validates the issue config file for a repository +func (c *Client) ValidateIssueConfig(owner, repo string) (*IssueConfigValidation, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + result := new(IssueConfigValidation) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/issue_config/validate", owner, repo), + jsonHeader, nil, &result) + return result, resp, err +} + +// TopicSearchOptions options for searching topics +type TopicSearchOptions struct { + ListOptions + Query string `json:"q"` // query string +} + +// TopicSearchResult represents a topic search result +type TopicSearchResult struct { + Topics []*TopicResponse `json:"topics"` +} + +// TopicResponse represents a topic +type TopicResponse struct { + ID int64 `json:"id"` + Name string `json:"topic_name"` + RepoCount int `json:"repo_count"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` +} + +// SearchTopics searches for topics +func (c *Client) SearchTopics(opt TopicSearchOptions) (*TopicSearchResult, *Response, error) { + opt.setDefaults() + + link, _ := url.Parse("/topics/search") + query := opt.getURLQuery() + if opt.Query != "" { + query.Add("q", opt.Query) + } + link.RawQuery = query.Encode() + + result := new(TopicSearchResult) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &result) + return result, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_refs.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_refs.go new file mode 100644 index 0000000..0707888 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_refs.go @@ -0,0 +1,92 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +// Reference represents a Git reference. +type Reference struct { + Ref string `json:"ref"` + URL string `json:"url"` + Object *GitObject `json:"object"` +} + +// GitObject represents a Git object. +type GitObject struct { + Type string `json:"type"` + SHA string `json:"sha"` + URL string `json:"url"` +} + +// GetRepoRef gets one exact ref from a repository. +// +// The underlying API returns a filtered list for /git/refs/{ref}, so this +// method resolves the exact ref from that list. It may return HTTP errors from +// the underlying API call, or an error when the server response only contains +// partial matches. +func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, error) { + refs, resp, err := c.GetRepoRefs(user, repo, ref) + if err != nil { + return nil, resp, err + } + + normalizedRef := "refs/" + strings.TrimPrefix(ref, "refs/") + for _, repoRef := range refs { + if repoRef == nil || repoRef.Ref != normalizedRef { + continue + } + return repoRef, resp, nil + } + + return nil, resp, errors.New("no exact match found for this ref") +} + +// GetRepoRefs gets the refs from a repository that match a partial or full ref. +func (c *Client) GetRepoRefs(user, repo, ref string) ([]*Reference, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + ref = strings.TrimPrefix(ref, "refs/") + ref = pathEscapeSegments(ref) + + data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil) + if err != nil { + return nil, resp, err + } + + // Attempt to unmarshal single returned ref. + r := new(Reference) + refErr := json.Unmarshal(data, r) + if refErr == nil { + return []*Reference{r}, resp, nil + } + + // Attempt to unmarshal multiple refs. + var rs []*Reference + refsErr := json.Unmarshal(data, &rs) + if refsErr == nil { + if len(rs) == 0 { + return nil, resp, errors.New("unexpected response: an array of refs with length 0") + } + return rs, resp, nil + } + + return nil, resp, fmt.Errorf("unmarshalling failed for both single and multiple refs: %s and %s", refErr, refsErr) +} + +// ListAllGitRefs gets all refs from a repository without filtering. +func (c *Client) ListAllGitRefs(owner, repo string) ([]*Reference, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + refs := make([]*Reference, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs", owner, repo), nil, nil, &refs) + return refs, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_stars.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_stars.go new file mode 100644 index 0000000..45488eb --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_stars.go @@ -0,0 +1,96 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" +) + +// ListStargazersOptions options for listing a repository's stargazers +type ListStargazersOptions struct { + ListOptions +} + +// ListRepoStargazers list a repository's stargazers +func (c *Client) ListRepoStargazers(user, repo string, opt ListStargazersOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + stargazers := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/stargazers?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &stargazers) + return stargazers, resp, err +} + +// GetStarredRepos returns the repos that the given user has starred +func (c *Client) GetStarredRepos(user string) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + repos := make([]*Repository, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/starred", user), jsonHeader, nil, &repos) + return repos, resp, err +} + +// GetMyStarredRepos returns the repos that the authenticated user has starred +func (c *Client) GetMyStarredRepos() ([]*Repository, *Response, error) { + repos := make([]*Repository, 0, 10) + resp, err := c.getParsedResponse("GET", "/user/starred", jsonHeader, nil, &repos) + return repos, resp, err +} + +// IsRepoStarring returns whether the authenticated user has starred the repo or not +func (c *Client) IsRepoStarring(user, repo string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return false, nil, err + } + resp, err := c.doRequestWithStatusHandle("GET", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil) + if resp != nil { + switch resp.StatusCode { + case http.StatusNotFound: + return false, resp, nil + case http.StatusNoContent: + return true, resp, nil + default: + return false, resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode) + } + } + return false, nil, err +} + +// StarRepo star specified repo as the authenticated user +func (c *Client) StarRepo(user, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + resp, err := c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil) + if resp != nil { + switch resp.StatusCode { + case http.StatusNoContent: + return resp, nil + default: + return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode) + } + } + return nil, err +} + +// UnStarRepo remove star to specified repo as the authenticated user +func (c *Client) UnStarRepo(user, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + resp, err := c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil) + if resp != nil { + switch resp.StatusCode { + case http.StatusNoContent: + return resp, nil + default: + return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode) + } + } + return nil, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_tag.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_tag.go new file mode 100644 index 0000000..c0dcfe8 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_tag.go @@ -0,0 +1,129 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// Tag represents a repository tag +type Tag struct { + Name string `json:"name"` + Message string `json:"message"` + ID string `json:"id"` + Commit *CommitMeta `json:"commit"` + ZipballURL string `json:"zipball_url"` + TarballURL string `json:"tarball_url"` +} + +// AnnotatedTag represents an annotated tag +type AnnotatedTag struct { + Tag string `json:"tag"` + SHA string `json:"sha"` + URL string `json:"url"` + Message string `json:"message"` + Tagger *CommitUser `json:"tagger"` + Object *AnnotatedTagObject `json:"object"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// AnnotatedTagObject contains meta information of the tag object +type AnnotatedTagObject struct { + Type string `json:"type"` + URL string `json:"url"` + SHA string `json:"sha"` +} + +// ListRepoTagsOptions options for listing a repository's tags +type ListRepoTagsOptions struct { + ListOptions +} + +// ListRepoTags list all the branches of one repository +func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Tag, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + tags := make([]*Tag, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &tags) + return tags, resp, err +} + +// GetTag get the tag of a repository +func (c *Client) GetTag(user, repo, tag string) (*Tag, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil { + return nil, nil, err + } + t := new(Tag) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag), nil, nil, &t) + return t, resp, err +} + +// GetAnnotatedTag get the tag object of an annotated tag (not lightweight tags) of a repository +func (c *Client) GetAnnotatedTag(user, repo, sha string) (*AnnotatedTag, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil { + return nil, nil, err + } + t := new(AnnotatedTag) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/tags/%s", user, repo, sha), nil, nil, &t) + return t, resp, err +} + +// CreateTagOption options when creating a tag +type CreateTagOption struct { + TagName string `json:"tag_name"` + Message string `json:"message"` + Target string `json:"target"` +} + +// Validate validates CreateTagOption +func (opt CreateTagOption) Validate() error { + if len(opt.TagName) == 0 { + return fmt.Errorf("TagName is required") + } + return nil +} + +// CreateTag create a new git tag in a repository +func (c *Client) CreateTag(user, repo string, opt CreateTagOption) (*Tag, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(opt) + if err != nil { + return nil, nil, err + } + t := new(Tag) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/tags", user, repo), jsonHeader, bytes.NewReader(body), &t) + return t, resp, err +} + +// DeleteTag deletes a tag from a repository, if no release refers to it +func (c *Client) DeleteTag(user, repo, tag string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", + fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag), + nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_tag_protection.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_tag_protection.go new file mode 100644 index 0000000..4304662 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_tag_protection.go @@ -0,0 +1,124 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strconv" + "time" +) + +// TagProtection represents a tag protection for a repository +type TagProtection struct { + ID int64 `json:"id"` + NamePattern string `json:"name_pattern"` + WhitelistUsernames []string `json:"whitelist_usernames"` + WhitelistTeams []string `json:"whitelist_teams"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CreateTagProtectionOption options for creating a tag protection +type CreateTagProtectionOption struct { + NamePattern string `json:"name_pattern"` + WhitelistUsernames []string `json:"whitelist_usernames"` + WhitelistTeams []string `json:"whitelist_teams"` +} + +// EditTagProtectionOption options for editing a tag protection +type EditTagProtectionOption struct { + NamePattern *string `json:"name_pattern"` + WhitelistUsernames []string `json:"whitelist_usernames"` + WhitelistTeams []string `json:"whitelist_teams"` +} + +// ListRepoTagsOptions options for listing a repository's tags +type ListRepoTagProtectionsOptions struct { + ListOptions +} + +// ListTagProtection list tag protections for a repository +func (c *Client) ListTagProtection(owner, repo string, opt ListRepoTagProtectionsOptions) ([]*TagProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_23_0); err != nil { + return nil, nil, err + } + + tps := make([]*TagProtection, 0, opt.PageSize) + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/tag_protections", owner, repo)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &tps) + return tps, resp, err +} + +// GetTagProtection gets a tag protection +func (c *Client) GetTagProtection(owner, repo string, id int64) (*TagProtection, *Response, error) { + sid := strconv.FormatInt(id, 10) + if err := escapeValidatePathSegments(&owner, &repo, &sid); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_23_0); err != nil { + return nil, nil, err + } + tp := new(TagProtection) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tag_protections/%d", owner, repo, id), jsonHeader, nil, tp) + return tp, resp, err +} + +// CreateTagProtection creates a tag protection for a repo +func (c *Client) CreateTagProtection(owner, repo string, opt CreateTagProtectionOption) (*TagProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_23_0); err != nil { + return nil, nil, err + } + + tp := new(TagProtection) + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/tag_protections", owner, repo), jsonHeader, bytes.NewReader(body), tp) + return tp, resp, err +} + +// EditTagProtection edits a tag protection for a repo +func (c *Client) EditTagProtection(owner, repo string, id int64, opt EditTagProtectionOption) (*TagProtection, *Response, error) { + sid := strconv.FormatInt(id, 10) + if err := escapeValidatePathSegments(&owner, &repo, &sid); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_23_0); err != nil { + return nil, nil, err + } + + tp := new(TagProtection) + + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/tag_protections/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), tp) + return tp, resp, err +} + +// DeleteTagProtection deletes a tag protection for a repo +func (c *Client) DeleteTagProtection(owner, repo string, id int64) (*Response, error) { + sid := strconv.FormatInt(id, 10) + if err := escapeValidatePathSegments(&owner, &repo, &sid); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_23_0); err != nil { + return nil, err + } + + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/tag_protections/%d", owner, repo, id), jsonHeader, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_team.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_team.go new file mode 100644 index 0000000..36e0863 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_team.go @@ -0,0 +1,63 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" +) + +// GetRepoTeams return teams from a repository +func (c *Client) GetRepoTeams(user, repo string) ([]*Team, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + teams := make([]*Team, 0, 5) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams", user, repo), nil, nil, &teams) + return teams, resp, err +} + +// AddRepoTeam add a team to a repository +func (c *Client) AddRepoTeam(user, repo, team string) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &team); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil) +} + +// RemoveRepoTeam delete a team from a repository +func (c *Client) RemoveRepoTeam(user, repo, team string) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &team); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil) +} + +// CheckRepoTeam check if team is assigned to repo by name and return it. +// If not assigned, it will return nil. +func (c *Client) CheckRepoTeam(user, repo, team string) (*Team, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &team); err != nil { + return nil, nil, err + } + t := new(Team) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil, &t) + if resp != nil && resp.StatusCode == http.StatusNotFound { + // if not found it's not an error, it indicates it's not assigned + return nil, resp, nil + } + return t, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_template.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_template.go new file mode 100644 index 0000000..8b689be --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_template.go @@ -0,0 +1,65 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// CreateRepoFromTemplateOption options when creating repository using a template +type CreateRepoFromTemplateOption struct { + // Owner is the organization or person who will own the new repository + Owner string `json:"owner"` + // Name of the repository to create + Name string `json:"name"` + // Description of the repository to create + Description string `json:"description"` + // Private is whether the repository is private + Private bool `json:"private"` + // GitContent include git content of default branch in template repo + GitContent bool `json:"git_content"` + // Topics include topics of template repo + Topics bool `json:"topics"` + // GitHooks include git hooks of template repo + GitHooks bool `json:"git_hooks"` + // Webhooks include webhooks of template repo + Webhooks bool `json:"webhooks"` + // Avatar include avatar of the template repo + Avatar bool `json:"avatar"` + // Labels include labels of template repo + Labels bool `json:"labels"` +} + +// Validate validates CreateRepoFromTemplateOption +func (opt CreateRepoFromTemplateOption) Validate() error { + if len(opt.Owner) == 0 { + return fmt.Errorf("field Owner is required") + } + if len(opt.Name) == 0 { + return fmt.Errorf("field Name is required") + } + return nil +} + +// CreateRepoFromTemplate create a repository using a template +func (c *Client) CreateRepoFromTemplate(templateOwner, templateRepo string, opt CreateRepoFromTemplateOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&templateOwner, &templateRepo); err != nil { + return nil, nil, err + } + + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/generate", templateOwner, templateRepo), jsonHeader, bytes.NewReader(body), &repo) + return repo, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_topics.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_topics.go new file mode 100644 index 0000000..9a105f2 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_topics.go @@ -0,0 +1,65 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// ListRepoTopicsOptions options for listing repo's topics +type ListRepoTopicsOptions struct { + ListOptions +} + +// topicsList represents a list of repo's topics +type topicsList struct { + Topics []string `json:"topics"` +} + +// ListRepoTopics list all repository's topics +func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([]string, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + + list := new(topicsList) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/topics?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, list) + if err != nil { + return nil, resp, err + } + return list.Topics, resp, nil +} + +// SetRepoTopics replaces the list of repo's topics +func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + l := topicsList{Topics: list} + body, err := json.Marshal(&l) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/repos/%s/%s/topics", user, repo), jsonHeader, bytes.NewReader(body)) +} + +// AddRepoTopic adds a topic to a repo's topics list +func (c *Client) AddRepoTopic(user, repo, topic string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil) +} + +// DeleteRepoTopic deletes a topic from repo's topics list +func (c *Client) DeleteRepoTopic(user, repo, topic string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_transfer.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_transfer.go new file mode 100644 index 0000000..534a6e4 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_transfer.go @@ -0,0 +1,62 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// TransferRepoOption options when transfer a repository's ownership +type TransferRepoOption struct { + // required: true + NewOwner string `json:"new_owner"` + // ID of the team or teams to add to the repository. Teams can only be added to organization-owned repositories. + TeamIDs *[]int64 `json:"team_ids"` +} + +// TransferRepo transfers the ownership of a repository +func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&owner, &reponame); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer", owner, reponame), jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} + +// AcceptRepoTransfer accepts a repo transfer. +func (c *Client) AcceptRepoTransfer(owner, reponame string) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&owner, &reponame); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/accept", owner, reponame), jsonHeader, nil, repo) + return repo, resp, err +} + +// RejectRepoTransfer rejects a repo transfer. +func (c *Client) RejectRepoTransfer(owner, reponame string) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&owner, &reponame); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/reject", owner, reponame), jsonHeader, nil, repo) + return repo, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_tree.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_tree.go new file mode 100644 index 0000000..7defbb3 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_tree.go @@ -0,0 +1,54 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" +) + +// GitEntry represents a git tree +type GitEntry struct { + Path string `json:"path"` + Mode string `json:"mode"` + Type string `json:"type"` + Size int64 `json:"size"` + SHA string `json:"sha"` + URL string `json:"url"` +} + +// GitTreeResponse returns a git tree +type GitTreeResponse struct { + SHA string `json:"sha"` + URL string `json:"url"` + Entries []GitEntry `json:"tree"` + Truncated bool `json:"truncated"` + Page int `json:"page"` + TotalCount int `json:"total_count"` +} + +type ListTreeOptions struct { + ListOptions + // Ref can be branch/tag/commit. required + // e.g.: "master" + Ref string + // Recursive if true will return the tree in a recursive fashion + Recursive bool +} + +// GetTrees get trees of repository, +func (c *Client) GetTrees(user, repo string, opt ListTreeOptions) (*GitTreeResponse, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &opt.Ref); err != nil { + return nil, nil, err + } + trees := new(GitTreeResponse) + opt.setDefaults() + path := fmt.Sprintf("/repos/%s/%s/git/trees/%s?%s", user, repo, opt.Ref, opt.getURLQuery().Encode()) + + if opt.Recursive { + path += "&recursive=1" + } + resp, err := c.getParsedResponse("GET", path, nil, nil, trees) + return trees, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_watch.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_watch.go new file mode 100644 index 0000000..c36ca64 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_watch.go @@ -0,0 +1,87 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" + "time" +) + +// WatchInfo represents an API watch status of one repository +type WatchInfo struct { + Subscribed bool `json:"subscribed"` + Ignored bool `json:"ignored"` + Reason interface{} `json:"reason"` + CreatedAt time.Time `json:"created_at"` + URL string `json:"url"` + RepositoryURL string `json:"repository_url"` +} + +// GetWatchedRepos list all the watched repos of user +func (c *Client) GetWatchedRepos(user string) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + repos := make([]*Repository, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/subscriptions", user), nil, nil, &repos) + return repos, resp, err +} + +// GetMyWatchedRepos list repositories watched by the authenticated user +func (c *Client) GetMyWatchedRepos() ([]*Repository, *Response, error) { + repos := make([]*Repository, 0, 10) + resp, err := c.getParsedResponse("GET", "/user/subscriptions", nil, nil, &repos) + return repos, resp, err +} + +// CheckRepoWatch check if the current user is watching a repo +func (c *Client) CheckRepoWatch(owner, repo string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil) + if err != nil { + return false, resp, err + } + switch status { + case http.StatusNotFound: + return false, resp, nil + case http.StatusOK: + return true, resp, nil + default: + return false, resp, fmt.Errorf("unexpected Status: %d", status) + } +} + +// WatchRepo start to watch a repository +func (c *Client) WatchRepo(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil) + if err != nil { + return resp, err + } + if status == http.StatusOK { + return resp, nil + } + return resp, fmt.Errorf("unexpected Status: %d", status) +} + +// UnWatchRepo stop to watch a repository +func (c *Client) UnWatchRepo(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil) + if err != nil { + return resp, err + } + if status == http.StatusNoContent { + return resp, nil + } + return resp, fmt.Errorf("unexpected Status: %d", status) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/repo_wiki.go b/cli/vendor/code.gitea.io/sdk/gitea/repo_wiki.go new file mode 100644 index 0000000..6346f19 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/repo_wiki.go @@ -0,0 +1,164 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +// WikiPage represents a wiki page +type WikiPage struct { + Title string `json:"title"` + ContentBase64 string `json:"content_base64"` + CommitCount int64 `json:"commit_count"` + Sidebar string `json:"sidebar"` + Footer string `json:"footer"` + HTMLURL string `json:"html_url"` + SubURL string `json:"sub_url"` + LastCommit *WikiCommit `json:"last_commit,omitempty"` +} + +// WikiPageMetaData represents metadata for a wiki page (without content) +type WikiPageMetaData struct { + Title string `json:"title"` + HTMLURL string `json:"html_url"` + SubURL string `json:"sub_url"` + LastCommit *WikiCommit `json:"last_commit,omitempty"` +} + +// WikiCommit represents a wiki commit/revision +type WikiCommit struct { + ID string `json:"sha"` + Message string `json:"message"` + Author *CommitUser `json:"author,omitempty"` + Commiter *CommitUser `json:"commiter,omitempty"` +} + +// WikiCommitList represents a list of wiki commits +type WikiCommitList struct { + WikiCommits []*WikiCommit `json:"commits"` + Count int64 `json:"count"` +} + +// CreateWikiPageOptions options for creating or editing a wiki page +type CreateWikiPageOptions struct { + Title string `json:"title,omitempty"` + ContentBase64 string `json:"content_base64,omitempty"` + Message string `json:"message,omitempty"` +} + +// ListWikiPagesOptions options for listing wiki pages +type ListWikiPagesOptions struct { + ListOptions +} + +// ListWikiPageRevisionsOptions options for listing wiki page revisions +type ListWikiPageRevisionsOptions struct { + Page int `json:"page,omitempty"` +} + +// CreateWikiPage creates a new wiki page +func (c *Client) CreateWikiPage(owner, repo string, opt CreateWikiPageOptions) (*WikiPage, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + page := new(WikiPage) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/wiki/new", owner, repo), + jsonHeader, bytes.NewReader(body), &page) + return page, resp, err +} + +// GetWikiPage gets a wiki page by name +func (c *Client) GetWikiPage(owner, repo, pageName string) (*WikiPage, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &pageName); err != nil { + return nil, nil, err + } + + page := new(WikiPage) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/wiki/page/%s", owner, repo, pageName), + jsonHeader, nil, &page) + return page, resp, err +} + +// EditWikiPage edits an existing wiki page +func (c *Client) EditWikiPage(owner, repo, pageName string, opt CreateWikiPageOptions) (*WikiPage, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &pageName); err != nil { + return nil, nil, err + } + + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + page := new(WikiPage) + resp, err := c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/wiki/page/%s", owner, repo, pageName), + jsonHeader, bytes.NewReader(body), &page) + return page, resp, err +} + +// DeleteWikiPage deletes a wiki page +func (c *Client) DeleteWikiPage(owner, repo, pageName string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &pageName); err != nil { + return nil, err + } + + status, resp, err := c.getStatusCode("DELETE", + fmt.Sprintf("/repos/%s/%s/wiki/page/%s", owner, repo, pageName), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// ListWikiPages lists all wiki pages in a repository +func (c *Client) ListWikiPages(owner, repo string, opt ListWikiPagesOptions) ([]*WikiPageMetaData, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/wiki/pages", owner, repo)) + link.RawQuery = opt.getURLQuery().Encode() + + pages := make([]*WikiPageMetaData, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &pages) + return pages, resp, err +} + +// GetWikiPageRevisions gets all revisions of a wiki page +func (c *Client) GetWikiPageRevisions(owner, repo, pageName string, opt ListWikiPageRevisionsOptions) (*WikiCommitList, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &pageName); err != nil { + return nil, nil, err + } + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/wiki/revisions/%s", owner, repo, pageName)) + if opt.Page > 0 { + query := link.Query() + query.Add("page", fmt.Sprintf("%d", opt.Page)) + link.RawQuery = query.Encode() + } + + commitList := new(WikiCommitList) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &commitList) + return commitList, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/secret.go b/cli/vendor/code.gitea.io/sdk/gitea/secret.go new file mode 100644 index 0000000..ea11ed0 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/secret.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import "time" + +type Secret struct { + // the secret's name + Name string `json:"name"` + // the secret's data + Data string `json:"data"` + // the secret's description + Description string `json:"description"` + // Date and Time of secret creation + Created time.Time `json:"created_at"` +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/settings.go b/cli/vendor/code.gitea.io/sdk/gitea/settings.go new file mode 100644 index 0000000..fb94248 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/settings.go @@ -0,0 +1,78 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +// GlobalUISettings represent the global ui settings of a gitea instance witch is exposed by API +type GlobalUISettings struct { + DefaultTheme string `json:"default_theme"` + AllowedReactions []string `json:"allowed_reactions"` + CustomEmojis []string `json:"custom_emojis"` +} + +// GlobalRepoSettings represent the global repository settings of a gitea instance witch is exposed by API +type GlobalRepoSettings struct { + MirrorsDisabled bool `json:"mirrors_disabled"` + HTTPGitDisabled bool `json:"http_git_disabled"` + MigrationsDisabled bool `json:"migrations_disabled"` + StarsDisabled bool `json:"stars_disabled"` + TimeTrackingDisabled bool `json:"time_tracking_disabled"` + LFSDisabled bool `json:"lfs_disabled"` +} + +// GlobalAPISettings contains global api settings exposed by it +type GlobalAPISettings struct { + MaxResponseItems int `json:"max_response_items"` + DefaultPagingNum int `json:"default_paging_num"` + DefaultGitTreesPerPage int `json:"default_git_trees_per_page"` + DefaultMaxBlobSize int64 `json:"default_max_blob_size"` +} + +// GlobalAttachmentSettings contains global Attachment settings exposed by API +type GlobalAttachmentSettings struct { + Enabled bool `json:"enabled"` + AllowedTypes string `json:"allowed_types"` + MaxSize int64 `json:"max_size"` + MaxFiles int `json:"max_files"` +} + +// GetGlobalUISettings get global ui settings witch are exposed by API +func (c *Client) GetGlobalUISettings() (*GlobalUISettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + conf := new(GlobalUISettings) + resp, err := c.getParsedResponse("GET", "/settings/ui", jsonHeader, nil, &conf) + return conf, resp, err +} + +// GetGlobalRepoSettings get global repository settings witch are exposed by API +func (c *Client) GetGlobalRepoSettings() (*GlobalRepoSettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + conf := new(GlobalRepoSettings) + resp, err := c.getParsedResponse("GET", "/settings/repository", jsonHeader, nil, &conf) + return conf, resp, err +} + +// GetGlobalAPISettings get global api settings witch are exposed by it +func (c *Client) GetGlobalAPISettings() (*GlobalAPISettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + conf := new(GlobalAPISettings) + resp, err := c.getParsedResponse("GET", "/settings/api", jsonHeader, nil, &conf) + return conf, resp, err +} + +// GetGlobalAttachmentSettings get global repository settings witch are exposed by API +func (c *Client) GetGlobalAttachmentSettings() (*GlobalAttachmentSettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + conf := new(GlobalAttachmentSettings) + resp, err := c.getParsedResponse("GET", "/settings/attachment", jsonHeader, nil, &conf) + return conf, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/status.go b/cli/vendor/code.gitea.io/sdk/gitea/status.go new file mode 100644 index 0000000..fe5d971 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/status.go @@ -0,0 +1,108 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// StatusState holds the state of a Status +// It can be "pending", "success", "error", "failure", and "warning" +type StatusState string + +const ( + // StatusPending is for when the Status is Pending + StatusPending StatusState = "pending" + // StatusSuccess is for when the Status is Success + StatusSuccess StatusState = "success" + // StatusError is for when the Status is Error + StatusError StatusState = "error" + // StatusFailure is for when the Status is Failure + StatusFailure StatusState = "failure" + // StatusWarning is for when the Status is Warning + StatusWarning StatusState = "warning" +) + +// Status holds a single Status of a single Commit +type Status struct { + ID int64 `json:"id"` + State StatusState `json:"status"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + URL string `json:"url"` + Context string `json:"context"` + Creator *User `json:"creator"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CreateStatusOption holds the information needed to create a new Status for a Commit +type CreateStatusOption struct { + State StatusState `json:"state"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + Context string `json:"context"` +} + +// CreateStatus creates a new Status for a given Commit +func (c *Client) CreateStatus(owner, repo, sha string, opts CreateStatusOption) (*Status, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opts) + if err != nil { + return nil, nil, err + } + status := new(Status) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/statuses/%s", owner, repo, url.QueryEscape(sha)), jsonHeader, bytes.NewReader(body), status) + return status, resp, err +} + +// ListStatusesOption options for listing a repository's commit's statuses +type ListStatusesOption struct { + ListOptions +} + +// ListStatuses returns all statuses for a given Commit by ref +func (c *Client) ListStatuses(owner, repo, ref string, opt ListStatusesOption) ([]*Status, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &ref); err != nil { + return nil, nil, err + } + opt.setDefaults() + statuses := make([]*Status, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/statuses?%s", owner, repo, ref, opt.getURLQuery().Encode()), jsonHeader, nil, &statuses) + return statuses, resp, err +} + +// CombinedStatus holds the combined state of several statuses for a single commit +type CombinedStatus struct { + State StatusState `json:"state"` + SHA string `json:"sha"` + TotalCount int `json:"total_count"` + Statuses []*Status `json:"statuses"` + Repository *Repository `json:"repository"` + CommitURL string `json:"commit_url"` + URL string `json:"url"` +} + +// GetCombinedStatus returns the CombinedStatus for a given Commit +func (c *Client) GetCombinedStatus(owner, repo, ref string) (*CombinedStatus, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &ref); err != nil { + return nil, nil, err + } + status := new(CombinedStatus) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/status", owner, repo, ref), jsonHeader, nil, status) + + // gitea api return empty body if nothing here jet + if resp != nil && resp.StatusCode == 200 && err != nil { + return status, resp, nil + } + + return status, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user.go b/cli/vendor/code.gitea.io/sdk/gitea/user.go new file mode 100644 index 0000000..ce9f85e --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user.go @@ -0,0 +1,95 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "strconv" + "time" +) + +// User represents a user +type User struct { + // the user's id + ID int64 `json:"id"` + // the user's username + UserName string `json:"login"` + // The login_name of non local users (e.g. LDAP / OAuth / SMTP) + LoginName string `json:"login_name"` + // The ID of the Authentication Source for non local users. + SourceID int64 `json:"source_id"` + // the user's full name + FullName string `json:"full_name"` + Email string `json:"email"` + // URL to the user's avatar + AvatarURL string `json:"avatar_url"` + // URL to the user's profile + HTMLURL string `json:"html_url"` + // User locale + Language string `json:"language"` + // Is the user an administrator + IsAdmin bool `json:"is_admin"` + // Date and Time of last login + LastLogin time.Time `json:"last_login"` + // Date and Time of user creation + Created time.Time `json:"created"` + // Is user restricted + Restricted bool `json:"restricted"` + // Is user active + IsActive bool `json:"active"` + // Is user login prohibited + ProhibitLogin bool `json:"prohibit_login"` + // the user's location + Location string `json:"location"` + // the user's website + Website string `json:"website"` + // the user's description + Description string `json:"description"` + // User visibility level option + Visibility VisibleType `json:"visibility"` + + // user counts + FollowerCount int `json:"followers_count"` + FollowingCount int `json:"following_count"` + StarredRepoCount int `json:"starred_repos_count"` +} + +// GetUserInfo get user info by user's name +func (c *Client) GetUserInfo(user string) (*User, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + u := new(User) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s", user), nil, nil, u) + return u, resp, err +} + +// GetMyUserInfo get user info of current user +func (c *Client) GetMyUserInfo() (*User, *Response, error) { + u := new(User) + resp, err := c.getParsedResponse("GET", "/user", nil, nil, u) + return u, resp, err +} + +// GetUserByID returns user by a given user ID +func (c *Client) GetUserByID(id int64) (*User, *Response, error) { + if id < 0 { + return nil, nil, fmt.Errorf("invalid user id %d", id) + } + + query := make(url.Values) + query.Add("uid", strconv.FormatInt(id, 10)) + users, resp, err := c.searchUsers(query.Encode()) + if err != nil { + return nil, resp, err + } + + if len(users) == 1 { + return users[0], resp, err + } + + return nil, resp, fmt.Errorf("user not found with id %d", id) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_action.go b/cli/vendor/code.gitea.io/sdk/gitea/user_action.go new file mode 100644 index 0000000..aea2b5e --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_action.go @@ -0,0 +1,161 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// CreateUserActionSecret creates or updates a user-scope Actions secret. +func (c *Client) CreateUserActionSecret(secretName string, opt CreateOrUpdateSecretOption) (*Response, error) { + if err := escapeValidatePathSegments(&secretName); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_21_0); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/user/actions/secrets/%s", secretName), jsonHeader, bytes.NewReader(body)) +} + +// DeleteUserActionSecret deletes a user-scope Actions secret. +func (c *Client) DeleteUserActionSecret(secretName string) (*Response, error) { + if err := escapeValidatePathSegments(&secretName); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_21_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/user/actions/secrets/%s", secretName), nil, nil) +} + +// ListUserActionVariable lists user-scope Actions variables. +func (c *Client) ListUserActionVariable(opt ListOptions) ([]*ActionVariable, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, nil, err + } + opt.setDefaults() + variables := make([]*ActionVariable, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/actions/variables?%s", opt.getURLQuery().Encode()), jsonHeader, nil, &variables) + return variables, resp, err +} + +// GetUserActionVariable gets one user-scope Actions variable. +func (c *Client) GetUserActionVariable(variableName string) (*ActionVariable, *Response, error) { + if err := escapeValidatePathSegments(&variableName); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, nil, err + } + variable := new(ActionVariable) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/actions/variables/%s", variableName), jsonHeader, nil, variable) + return variable, resp, err +} + +// CreateUserActionVariable creates one user-scope Actions variable. +func (c *Client) CreateUserActionVariable(variableName string, opt CreateActionVariableOption) (*Response, error) { + if err := escapeValidatePathSegments(&variableName); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("POST", fmt.Sprintf("/user/actions/variables/%s", variableName), jsonHeader, bytes.NewReader(body)) +} + +// UpdateUserActionVariable updates one user-scope Actions variable. +func (c *Client) UpdateUserActionVariable(variableName string, opt UpdateActionVariableOption) (*Response, error) { + if err := escapeValidatePathSegments(&variableName); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/user/actions/variables/%s", variableName), jsonHeader, bytes.NewReader(body)) +} + +// DeleteUserActionVariable deletes one user-scope Actions variable. +func (c *Client) DeleteUserActionVariable(variableName string) (*Response, error) { + if err := escapeValidatePathSegments(&variableName); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/user/actions/variables/%s", variableName), nil, nil) +} + +// ListUserActionRuns lists user-scope Actions workflow runs. +func (c *Client) ListUserActionRuns(opt ListRepoActionRunsOptions) (*ActionWorkflowRunsResponse, *Response, error) { + return c.listActionRuns("/user/actions/runs", opt) +} + +// ListUserActionJobs lists user-scope Actions jobs. +func (c *Client) ListUserActionJobs(opt ListRepoActionJobsOptions) (*ActionWorkflowJobsResponse, *Response, error) { + return c.listActionJobs("/user/actions/jobs", opt) +} + +// CreateUserActionRunnerRegistrationToken creates a user-scope runner registration token. +func (c *Client) CreateUserActionRunnerRegistrationToken() (*RegistrationToken, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil { + return nil, nil, err + } + return c.createActionRegistrationToken("/user/actions/runners/registration-token") +} + +// ListUserActionRunners lists user-scope Actions runners. +func (c *Client) ListUserActionRunners(opt ListActionRunnersOptions) (*ActionRunnersResponse, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.listActionRunners("/user/actions/runners", opt) +} + +// GetUserActionRunner gets one user-scope Actions runner. +func (c *Client) GetUserActionRunner(runnerID int64) (*ActionRunner, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.getActionRunner(fmt.Sprintf("/user/actions/runners/%d", runnerID)) +} + +// DeleteUserActionRunner deletes one user-scope Actions runner. +func (c *Client) DeleteUserActionRunner(runnerID int64) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/user/actions/runners/%d", runnerID), nil, nil) +} + +// UpdateUserActionRunner updates one user-scope Actions runner. +func (c *Client) UpdateUserActionRunner(runnerID int64, opt EditActionRunnerOption) (*ActionRunner, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_25_0); err != nil { + return nil, nil, err + } + return c.updateActionRunner(fmt.Sprintf("/user/actions/runners/%d", runnerID), opt) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_app.go b/cli/vendor/code.gitea.io/sdk/gitea/user_app.go new file mode 100644 index 0000000..bd5b1f8 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_app.go @@ -0,0 +1,145 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "reflect" + "time" +) + +// AccessTokenScope represents the scope for an access token. +type AccessTokenScope string + +const ( + AccessTokenScopeAll AccessTokenScope = "all" + + AccessTokenScopeRepo AccessTokenScope = "repo" + AccessTokenScopeRepoStatus AccessTokenScope = "repo:status" + AccessTokenScopePublicRepo AccessTokenScope = "public_repo" + + AccessTokenScopeAdminOrg AccessTokenScope = "admin:org" + AccessTokenScopeWriteOrg AccessTokenScope = "write:org" + AccessTokenScopeReadOrg AccessTokenScope = "read:org" + + AccessTokenScopeAdminPublicKey AccessTokenScope = "admin:public_key" + AccessTokenScopeWritePublicKey AccessTokenScope = "write:public_key" + AccessTokenScopeReadPublicKey AccessTokenScope = "read:public_key" + + AccessTokenScopeAdminRepoHook AccessTokenScope = "admin:repo_hook" + AccessTokenScopeWriteRepoHook AccessTokenScope = "write:repo_hook" + AccessTokenScopeReadRepoHook AccessTokenScope = "read:repo_hook" + + AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook" + + AccessTokenScopeAdminUserHook AccessTokenScope = "admin:user_hook" + + AccessTokenScopeNotification AccessTokenScope = "notification" + + AccessTokenScopeUser AccessTokenScope = "user" + AccessTokenScopeReadUser AccessTokenScope = "read:user" + AccessTokenScopeUserEmail AccessTokenScope = "user:email" + AccessTokenScopeUserFollow AccessTokenScope = "user:follow" + + AccessTokenScopeDeleteRepo AccessTokenScope = "delete_repo" + + AccessTokenScopePackage AccessTokenScope = "package" + AccessTokenScopeWritePackage AccessTokenScope = "write:package" + AccessTokenScopeReadPackage AccessTokenScope = "read:package" + AccessTokenScopeDeletePackage AccessTokenScope = "delete:package" + + AccessTokenScopeAdminGPGKey AccessTokenScope = "admin:gpg_key" + AccessTokenScopeWriteGPGKey AccessTokenScope = "write:gpg_key" + AccessTokenScopeReadGPGKey AccessTokenScope = "read:gpg_key" + + AccessTokenScopeAdminApplication AccessTokenScope = "admin:application" + AccessTokenScopeWriteApplication AccessTokenScope = "write:application" + AccessTokenScopeReadApplication AccessTokenScope = "read:application" + + AccessTokenScopeSudo AccessTokenScope = "sudo" +) + +// AccessToken represents an API access token. +type AccessToken struct { + ID int64 `json:"id"` + Name string `json:"name"` + Token string `json:"sha1"` + TokenLastEight string `json:"token_last_eight"` + Scopes []AccessTokenScope `json:"scopes"` + Created time.Time `json:"created_at,omitempty"` + Updated time.Time `json:"last_used_at,omitempty"` +} + +// ListAccessTokensOptions options for listing a users's access tokens +type ListAccessTokensOptions struct { + ListOptions +} + +// ListAccessTokens lists all the access tokens of user +func (c *Client) ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, *Response, error) { + c.mutex.RLock() + username := c.username + c.mutex.RUnlock() + if len(username) == 0 { + return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") + } + opts.setDefaults() + tokens := make([]*AccessToken, 0, opts.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/tokens?%s", url.PathEscape(username), opts.getURLQuery().Encode()), jsonHeader, nil, &tokens) + return tokens, resp, err +} + +// CreateAccessTokenOption options when create access token +type CreateAccessTokenOption struct { + Name string `json:"name"` + Scopes []AccessTokenScope `json:"scopes"` +} + +// CreateAccessToken create one access token with options +func (c *Client) CreateAccessToken(opt CreateAccessTokenOption) (*AccessToken, *Response, error) { + c.mutex.RLock() + username := c.username + c.mutex.RUnlock() + if len(username) == 0 { + return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + t := new(AccessToken) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/users/%s/tokens", url.PathEscape(username)), jsonHeader, bytes.NewReader(body), t) + return t, resp, err +} + +// DeleteAccessToken delete token, identified by ID and if not available by name +func (c *Client) DeleteAccessToken(value interface{}) (*Response, error) { + c.mutex.RLock() + username := c.username + c.mutex.RUnlock() + if len(username) == 0 { + return nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") + } + + token := "" + + switch reflect.ValueOf(value).Kind() { + case reflect.Int64: + token = fmt.Sprintf("%d", value.(int64)) + case reflect.String: + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, err + } + token = value.(string) + default: + return nil, fmt.Errorf("only string and int64 supported") + } + + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/users/%s/tokens/%s", url.PathEscape(username), url.PathEscape(token)), jsonHeader, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_block.go b/cli/vendor/code.gitea.io/sdk/gitea/user_block.go new file mode 100644 index 0000000..011a520 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_block.go @@ -0,0 +1,76 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" + "net/url" +) + +// ListUserBlocksOptions options for listing user blocks +type ListUserBlocksOptions struct { + ListOptions +} + +// ListMyBlocks lists users blocked by the authenticated user +func (c *Client) ListMyBlocks(opt ListUserBlocksOptions) ([]*User, *Response, error) { + opt.setDefaults() + + link, _ := url.Parse("/user/blocks") + link.RawQuery = opt.getURLQuery().Encode() + + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) + return users, resp, err +} + +// CheckUserBlock checks if a user is blocked by the authenticated user +func (c *Client) CheckUserBlock(username string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&username); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", + fmt.Sprintf("/user/blocks/%s", username), + jsonHeader, nil) + if err != nil { + return false, resp, err + } + return status == http.StatusNoContent, resp, nil +} + +// BlockUser blocks a user +func (c *Client) BlockUser(username string) (*Response, error) { + if err := escapeValidatePathSegments(&username); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("PUT", + fmt.Sprintf("/user/blocks/%s", username), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// UnblockUser unblocks a user +func (c *Client) UnblockUser(username string) (*Response, error) { + if err := escapeValidatePathSegments(&username); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", + fmt.Sprintf("/user/blocks/%s", username), + jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_email.go b/cli/vendor/code.gitea.io/sdk/gitea/user_email.go new file mode 100644 index 0000000..ca9c039 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_email.go @@ -0,0 +1,65 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// Email an email address belonging to a user +type Email struct { + Email string `json:"email"` + Verified bool `json:"verified"` + Primary bool `json:"primary"` + UserID int64 `json:"user_id,omitempty"` + Username string `json:"username,omitempty"` +} + +// ListEmailsOptions options for listing current's user emails +type ListEmailsOptions struct { + ListOptions +} + +// ListEmails all the email addresses of user +func (c *Client) ListEmails(opt ListEmailsOptions) ([]*Email, *Response, error) { + opt.setDefaults() + emails := make([]*Email, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/emails?%s", opt.getURLQuery().Encode()), nil, nil, &emails) + return emails, resp, err +} + +// CreateEmailOption options when creating email addresses +type CreateEmailOption struct { + // email addresses to add + Emails []string `json:"emails"` +} + +// AddEmail add one email to current user with options +func (c *Client) AddEmail(opt CreateEmailOption) ([]*Email, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + emails := make([]*Email, 0, 3) + resp, err := c.getParsedResponse("POST", "/user/emails", jsonHeader, bytes.NewReader(body), &emails) + return emails, resp, err +} + +// DeleteEmailOption options when deleting email addresses +type DeleteEmailOption struct { + // email addresses to delete + Emails []string `json:"emails"` +} + +// DeleteEmail delete one email of current users' +func (c *Client) DeleteEmail(opt DeleteEmailOption) (*Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + return c.doRequestWithStatusHandle("DELETE", "/user/emails", jsonHeader, bytes.NewReader(body)) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_ext.go b/cli/vendor/code.gitea.io/sdk/gitea/user_ext.go new file mode 100644 index 0000000..36ef735 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_ext.go @@ -0,0 +1,58 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" +) + +// UserHeatmapData represents the data needed to create a heatmap +type UserHeatmapData struct { + Timestamp int64 `json:"timestamp"` + Contributions int64 `json:"contributions"` +} + +// GetUserHeatmap gets a user's heatmap data +func (c *Client) GetUserHeatmap(username string) ([]*UserHeatmapData, *Response, error) { + if err := escapeValidatePathSegments(&username); err != nil { + return nil, nil, err + } + + heatmap := make([]*UserHeatmapData, 0, 365) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/users/%s/heatmap", username), + jsonHeader, nil, &heatmap) + return heatmap, resp, err +} + +// ListUserActivityFeedsOptions options for listing user activity feeds +type ListUserActivityFeedsOptions struct { + ListOptions + OnlyPerformedBy bool `json:"only-performed-by,omitempty"` + Date string `json:"date,omitempty"` +} + +// ListUserActivityFeeds lists a user's activity feeds +func (c *Client) ListUserActivityFeeds(username string, opt ListUserActivityFeedsOptions) ([]*Activity, *Response, error) { + if err := escapeValidatePathSegments(&username); err != nil { + return nil, nil, err + } + opt.setDefaults() + + link, _ := url.Parse(fmt.Sprintf("/users/%s/activities/feeds", username)) + query := opt.getURLQuery() + if opt.OnlyPerformedBy { + query.Add("only-performed-by", "true") + } + if opt.Date != "" { + query.Add("date", opt.Date) + } + link.RawQuery = query.Encode() + + activities := make([]*Activity, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &activities) + return activities, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_follow.go b/cli/vendor/code.gitea.io/sdk/gitea/user_follow.go new file mode 100644 index 0000000..40072aa --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_follow.go @@ -0,0 +1,93 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import "fmt" + +// ListFollowersOptions options for listing followers +type ListFollowersOptions struct { + ListOptions +} + +// ListMyFollowers list all the followers of current user +func (c *Client) ListMyFollowers(opt ListFollowersOptions) ([]*User, *Response, error) { + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/followers?%s", opt.getURLQuery().Encode()), nil, nil, &users) + return users, resp, err +} + +// ListFollowers list all the followers of one user +func (c *Client) ListFollowers(user string, opt ListFollowersOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/followers?%s", user, opt.getURLQuery().Encode()), nil, nil, &users) + return users, resp, err +} + +// ListFollowingOptions options for listing a user's users being followed +type ListFollowingOptions struct { + ListOptions +} + +// ListMyFollowing list all the users current user followed +func (c *Client) ListMyFollowing(opt ListFollowingOptions) ([]*User, *Response, error) { + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/following?%s", opt.getURLQuery().Encode()), nil, nil, &users) + return users, resp, err +} + +// ListFollowing list all the users the user followed +func (c *Client) ListFollowing(user string, opt ListFollowingOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/following?%s", user, opt.getURLQuery().Encode()), nil, nil, &users) + return users, resp, err +} + +// IsFollowing if current user followed the target +func (c *Client) IsFollowing(target string) (bool, *Response) { + if err := escapeValidatePathSegments(&target); err != nil { + // ToDo return err + return false, nil + } + resp, err := c.doRequestWithStatusHandle("GET", fmt.Sprintf("/user/following/%s", target), nil, nil) + return err == nil, resp +} + +// IsUserFollowing if the user followed the target +func (c *Client) IsUserFollowing(user, target string) (bool, *Response) { + if err := escapeValidatePathSegments(&user, &target); err != nil { + // ToDo return err + return false, nil + } + resp, err := c.doRequestWithStatusHandle("GET", fmt.Sprintf("/users/%s/following/%s", user, target), nil, nil) + return err == nil, resp +} + +// Follow set current user follow the target +func (c *Client) Follow(target string) (*Response, error) { + if err := escapeValidatePathSegments(&target); err != nil { + return nil, err + } + resp, err := c.doRequestWithStatusHandle("PUT", fmt.Sprintf("/user/following/%s", target), nil, nil) + return resp, err +} + +// Unfollow set current user unfollow the target +func (c *Client) Unfollow(target string) (*Response, error) { + if err := escapeValidatePathSegments(&target); err != nil { + return nil, err + } + resp, err := c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/user/following/%s", target), nil, nil) + return resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go b/cli/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go new file mode 100644 index 0000000..95070ca --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go @@ -0,0 +1,120 @@ +// Copyright 2017 Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// GPGKey a user GPG key to sign commit and tag in repository +type GPGKey struct { + ID int64 `json:"id"` + PrimaryKeyID string `json:"primary_key_id"` + KeyID string `json:"key_id"` + PublicKey string `json:"public_key"` + Emails []*GPGKeyEmail `json:"emails"` + SubsKey []*GPGKey `json:"subkeys"` + CanSign bool `json:"can_sign"` + CanEncryptComms bool `json:"can_encrypt_comms"` + CanEncryptStorage bool `json:"can_encrypt_storage"` + CanCertify bool `json:"can_certify"` + Verified bool `json:"verified"` + Created time.Time `json:"created_at,omitempty"` + Expires time.Time `json:"expires_at,omitempty"` +} + +// GPGKeyEmail an email attached to a GPGKey +type GPGKeyEmail struct { + Email string `json:"email"` + Verified bool `json:"verified"` +} + +// ListGPGKeysOptions options for listing a user's GPGKeys +type ListGPGKeysOptions struct { + ListOptions +} + +// ListGPGKeys list all the GPG keys of the user +func (c *Client) ListGPGKeys(user string, opt ListGPGKeysOptions) ([]*GPGKey, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + keys := make([]*GPGKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/gpg_keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys) + return keys, resp, err +} + +// ListMyGPGKeys list all the GPG keys of current user +func (c *Client) ListMyGPGKeys(opt *ListGPGKeysOptions) ([]*GPGKey, *Response, error) { + opt.setDefaults() + keys := make([]*GPGKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys) + return keys, resp, err +} + +// GetGPGKey get current user's GPG key by key id +func (c *Client) GetGPGKey(keyID int64) (*GPGKey, *Response, error) { + key := new(GPGKey) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil, &key) + return key, resp, err +} + +// CreateGPGKeyOption options create user GPG key +type CreateGPGKeyOption struct { + // An armored GPG key to add + ArmoredKey string `json:"armored_public_key"` + // An optional armored signature for the GPG key + Signature string `json:"armored_signature,omitempty"` +} + +// CreateGPGKey create GPG key with options +func (c *Client) CreateGPGKey(opt CreateGPGKeyOption) (*GPGKey, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + key := new(GPGKey) + resp, err := c.getParsedResponse("POST", "/user/gpg_keys", jsonHeader, bytes.NewReader(body), key) + return key, resp, err +} + +// DeleteGPGKey delete GPG key with key id +func (c *Client) DeleteGPGKey(keyID int64) (*Response, error) { + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil) +} + +// GetGPGKeyVerificationToken gets a verification token for adding a GPG key. +// Returns the token as a plain string (API returns text/plain, not JSON). +// The user should sign this token with their GPG key and submit via VerifyGPGKey. +func (c *Client) GetGPGKeyVerificationToken() (string, *Response, error) { + body, resp, err := c.getResponse("GET", "/user/gpg_key_token", nil, nil) + return string(body), resp, err +} + +// VerifyGPGKeyOption options for verifying a GPG key +type VerifyGPGKeyOption struct { + // KeyID is the GPG key ID to verify + KeyID string `json:"key_id"` + // Signature is the ASCII-armored signature of the verification token + Signature string `json:"armored_signature"` +} + +// VerifyGPGKey verifies a GPG key by submitting a signed verification token. +// First call GetGPGKeyVerificationToken to get the token, sign it with the GPG key, +// then call this with the key ID and armored signature. +func (c *Client) VerifyGPGKey(opt VerifyGPGKeyOption) (*GPGKey, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + key := new(GPGKey) + resp, err := c.getParsedResponse("POST", "/user/gpg_key_verify", + jsonHeader, bytes.NewReader(body), key) + return key, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_key.go b/cli/vendor/code.gitea.io/sdk/gitea/user_key.go new file mode 100644 index 0000000..d016276 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_key.go @@ -0,0 +1,83 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// PublicKey publickey is a user key to push code to repository +type PublicKey struct { + ID int64 `json:"id"` + Key string `json:"key"` + URL string `json:"url,omitempty"` + Title string `json:"title,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + Created time.Time `json:"created_at,omitempty"` + Updated time.Time `json:"last_used_at,omitempty"` + Owner *User `json:"user,omitempty"` + ReadOnly bool `json:"read_only,omitempty"` + KeyType string `json:"key_type,omitempty"` +} + +// ListPublicKeysOptions options for listing a user's PublicKeys +type ListPublicKeysOptions struct { + ListOptions +} + +// ListPublicKeys list all the public keys of the user +func (c *Client) ListPublicKeys(user string, opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + keys := make([]*PublicKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys) + return keys, resp, err +} + +// ListMyPublicKeys list all the public keys of current user +func (c *Client) ListMyPublicKeys(opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) { + opt.setDefaults() + keys := make([]*PublicKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys) + return keys, resp, err +} + +// GetPublicKey get current user's public key by key id +func (c *Client) GetPublicKey(keyID int64) (*PublicKey, *Response, error) { + key := new(PublicKey) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys/%d", keyID), nil, nil, &key) + return key, resp, err +} + +// CreateKeyOption options when creating a key +type CreateKeyOption struct { + // Title of the key to add + Title string `json:"title"` + // An armored SSH key to add + Key string `json:"key"` + // Describe if the key has only read access or read/write + ReadOnly bool `json:"read_only"` +} + +// CreatePublicKey create public key with options +func (c *Client) CreatePublicKey(opt CreateKeyOption) (*PublicKey, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + key := new(PublicKey) + resp, err := c.getParsedResponse("POST", "/user/keys", jsonHeader, bytes.NewReader(body), key) + return key, resp, err +} + +// DeletePublicKey delete public key with key id +func (c *Client) DeletePublicKey(keyID int64) (*Response, error) { + return c.doRequestWithStatusHandle("DELETE", fmt.Sprintf("/user/keys/%d", keyID), nil, nil) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_search.go b/cli/vendor/code.gitea.io/sdk/gitea/user_search.go new file mode 100644 index 0000000..0e042a5 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_search.go @@ -0,0 +1,52 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" +) + +type searchUsersResponse struct { + Users []*User `json:"data"` +} + +// SearchUsersOption options for SearchUsers +type SearchUsersOption struct { + ListOptions + KeyWord string + UID int64 +} + +// QueryEncode turns options into querystring argument +func (opt *SearchUsersOption) QueryEncode() string { + query := make(url.Values) + if opt.Page > 0 { + query.Add("page", fmt.Sprintf("%d", opt.Page)) + } + if opt.PageSize > 0 { + query.Add("limit", fmt.Sprintf("%d", opt.PageSize)) + } + if len(opt.KeyWord) > 0 { + query.Add("q", opt.KeyWord) + } + if opt.UID > 0 { + query.Add("uid", fmt.Sprintf("%d", opt.UID)) + } + return query.Encode() +} + +func (c *Client) searchUsers(rawQuery string) ([]*User, *Response, error) { + link, _ := url.Parse("/users/search") + link.RawQuery = rawQuery + userResp := new(searchUsersResponse) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &userResp) + return userResp.Users, resp, err +} + +// SearchUsers finds users by query +func (c *Client) SearchUsers(opt SearchUsersOption) ([]*User, *Response, error) { + return c.searchUsers(opt.QueryEncode()) +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_settings.go b/cli/vendor/code.gitea.io/sdk/gitea/user_settings.go new file mode 100644 index 0000000..494cab3 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_settings.go @@ -0,0 +1,62 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" +) + +// UserSettings represents user settings +type UserSettings struct { + FullName string `json:"full_name"` + Website string `json:"website"` + Description string `json:"description"` + Location string `json:"location"` + Language string `json:"language"` + Theme string `json:"theme"` + DiffViewStyle string `json:"diff_view_style"` + // Privacy + HideEmail bool `json:"hide_email"` + HideActivity bool `json:"hide_activity"` +} + +// UserSettingsOptions represents options to change user settings +type UserSettingsOptions struct { + FullName *string `json:"full_name,omitempty"` + Website *string `json:"website,omitempty"` + Description *string `json:"description,omitempty"` + Location *string `json:"location,omitempty"` + Language *string `json:"language,omitempty"` + Theme *string `json:"theme,omitempty"` + DiffViewStyle *string `json:"diff_view_style,omitempty"` + // Privacy + HideEmail *bool `json:"hide_email,omitempty"` + HideActivity *bool `json:"hide_activity,omitempty"` +} + +// GetUserSettings returns user settings +func (c *Client) GetUserSettings() (*UserSettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + userConfig := new(UserSettings) + resp, err := c.getParsedResponse("GET", "/user/settings", nil, nil, userConfig) + return userConfig, resp, err +} + +// UpdateUserSettings returns user settings +func (c *Client) UpdateUserSettings(opt UserSettingsOptions) (*UserSettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + userConfig := new(UserSettings) + resp, err := c.getParsedResponse("PATCH", "/user/settings", jsonHeader, bytes.NewReader(body), userConfig) + return userConfig, resp, err +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/user_social.go b/cli/vendor/code.gitea.io/sdk/gitea/user_social.go new file mode 100644 index 0000000..eef284d --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/user_social.go @@ -0,0 +1,46 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +// UpdateUserAvatarOption options for updating user avatar +type UpdateUserAvatarOption struct { + Image string `json:"image"` // base64 encoded image +} + +// UpdateUserAvatar updates the authenticated user's avatar +func (c *Client) UpdateUserAvatar(opt UpdateUserAvatarOption) (*Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("POST", "/user/avatar", + jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} + +// DeleteUserAvatar deletes the authenticated user's avatar +func (c *Client) DeleteUserAvatar() (*Response, error) { + status, resp, err := c.getStatusCode("DELETE", "/user/avatar", jsonHeader, nil) + if err != nil { + return resp, err + } + if status != http.StatusNoContent { + return resp, fmt.Errorf("unexpected status: %d", status) + } + return resp, nil +} diff --git a/cli/vendor/code.gitea.io/sdk/gitea/version.go b/cli/vendor/code.gitea.io/sdk/gitea/version.go new file mode 100644 index 0000000..ffac143 --- /dev/null +++ b/cli/vendor/code.gitea.io/sdk/gitea/version.go @@ -0,0 +1,132 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "strings" + + version "github.com/hashicorp/go-version" +) + +// ServerVersion returns the version of the server +func (c *Client) ServerVersion() (string, *Response, error) { + v := struct { + Version string `json:"version"` + }{} + resp, err := c.getParsedResponse("GET", "/version", nil, nil, &v) + return v.Version, resp, err +} + +// CheckServerVersionConstraint validates that the login's server satisfies a +// given version constraint such as ">= 1.11.0+dev" +func (c *Client) CheckServerVersionConstraint(constraint string) error { + if err := c.loadServerVersion(); err != nil { + return err + } + + check, err := version.NewConstraint(constraint) + if err != nil { + return err + } + if !check.Check(c.serverVersion) { + c.mutex.RLock() + url := c.url + c.mutex.RUnlock() + return fmt.Errorf("gitea server at %s does not satisfy version constraint %s", url, constraint) + } + return nil +} + +// SetGiteaVersion configures the Client to assume the given version of the +// Gitea server, instead of querying the server for it when initializing. +// Use "" to skip all canonical ways in the SDK to check for versions +func SetGiteaVersion(v string) ClientOption { + if v == "" { + return func(c *Client) error { + c.ignoreVersion = true + return nil + } + } + return func(c *Client) (err error) { + c.getVersionOnce.Do(func() { + c.serverVersion, err = version.NewVersion(v) + }) + return err + } +} + +// predefined versions only have to be parsed by library once +var ( + version1_11_0 = version.Must(version.NewVersion("1.11.0")) + version1_11_5 = version.Must(version.NewVersion("1.11.5")) + version1_12_0 = version.Must(version.NewVersion("1.12.0")) + version1_12_3 = version.Must(version.NewVersion("1.12.3")) + version1_13_0 = version.Must(version.NewVersion("1.13.0")) + version1_14_0 = version.Must(version.NewVersion("1.14.0")) + version1_15_0 = version.Must(version.NewVersion("1.15.0")) + version1_16_0 = version.Must(version.NewVersion("1.16.0")) + version1_17_0 = version.Must(version.NewVersion("1.17.0")) + version1_18_0 = version.Must(version.NewVersion("1.18.0")) + version1_21_0 = version.Must(version.NewVersion("1.21.0")) + version1_22_0 = version.Must(version.NewVersion("1.22.0")) + version1_23_0 = version.Must(version.NewVersion("1.23.0")) + version1_24_0 = version.Must(version.NewVersion("1.24.0")) + version1_25_0 = version.Must(version.NewVersion("1.25.0")) + version1_26_0 = version.Must(version.NewVersion("1.26.0")) +) + +// ErrUnknownVersion is an unknown version from the API +type ErrUnknownVersion struct { + raw string +} + +// Error fulfills error +func (e *ErrUnknownVersion) Error() string { + return fmt.Sprintf("unknown version: %s", e.raw) +} + +func (*ErrUnknownVersion) Is(target error) bool { + _, ok := target.(*ErrUnknownVersion) + return ok +} + +// checkServerVersionGreaterThanOrEqual is the canonical way in the SDK to check for versions for API compatibility reasons +func (c *Client) checkServerVersionGreaterThanOrEqual(v *version.Version) error { + if c.ignoreVersion { + return nil + } + if err := c.loadServerVersion(); err != nil { + return err + } + + if !c.serverVersion.GreaterThanOrEqual(v) { + c.mutex.RLock() + url := c.url + c.mutex.RUnlock() + return fmt.Errorf("gitea server at %s is older than %s", url, v.Original()) + } + return nil +} + +// loadServerVersion init the serverVersion variable +func (c *Client) loadServerVersion() (err error) { + c.getVersionOnce.Do(func() { + raw, _, err2 := c.ServerVersion() + if err2 != nil { + err = err2 + return + } + if c.serverVersion, err = version.NewVersion(raw); err != nil { + if strings.TrimSpace(raw) != "" { + // Version was something, just not recognized + c.serverVersion = version1_11_0 + err = &ErrUnknownVersion{raw: raw} + } + return + } + }) + return err +} diff --git a/cli/vendor/github.com/42wim/httpsig/.travis.yml b/cli/vendor/github.com/42wim/httpsig/.travis.yml new file mode 100644 index 0000000..2c0c604 --- /dev/null +++ b/cli/vendor/github.com/42wim/httpsig/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - 1.15 diff --git a/cli/vendor/github.com/42wim/httpsig/LICENSE b/cli/vendor/github.com/42wim/httpsig/LICENSE new file mode 100644 index 0000000..a9e8aef --- /dev/null +++ b/cli/vendor/github.com/42wim/httpsig/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, go-fed +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cli/vendor/github.com/42wim/httpsig/README.md b/cli/vendor/github.com/42wim/httpsig/README.md new file mode 100644 index 0000000..7bd87ed --- /dev/null +++ b/cli/vendor/github.com/42wim/httpsig/README.md @@ -0,0 +1,115 @@ +# httpsig + +Forked from + +> HTTP Signatures made simple + +[![Build Status][Build-Status-Image]][Build-Status-Url] [![Go Reference][Go-Reference-Image]][Go-Reference-Url] +[![Go Report Card][Go-Report-Card-Image]][Go-Report-Card-Url] [![License][License-Image]][License-Url] +[![Chat][Chat-Image]][Chat-Url] [![OpenCollective][OpenCollective-Image]][OpenCollective-Url] + +`go get github.com/42wim/httpsig` + +Implementation of [HTTP Signatures](https://tools.ietf.org/html/draft-cavage-http-signatures). + +Supports many different combinations of MAC, HMAC signing of hash, or RSA +signing of hash schemes. Its goals are: + +* Have a very simple interface for signing and validating +* Support a variety of signing algorithms and combinations +* Support setting either headers (`Authorization` or `Signature`) +* Remaining flexible with headers included in the signing string +* Support both HTTP requests and responses +* Explicitly not support known-cryptographically weak algorithms +* Support automatic signing and validating Digest headers + +## How to use + +`import "github.com/42wim/httpsig"` + +### Signing + +Signing a request or response requires creating a new `Signer` and using it: + +``` +func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error { + prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256} + digestAlgorithm := DigestSha256 + // The "Date" and "Digest" headers must already be set on r, as well as r.URL. + headersToSign := []string{httpsig.RequestTarget, "date", "digest"} + signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature) + if err != nil { + return err + } + // To sign the digest, we need to give the signer a copy of the body... + // ...but it is optional, no digest will be signed if given "nil" + body := ... + // If r were a http.ResponseWriter, call SignResponse instead. + return signer.SignRequest(privateKey, pubKeyId, r, body) +} +``` + +`Signer`s are not safe for concurrent use by goroutines, so be sure to guard +access: + +``` +type server struct { + signer httpsig.Signer + mu *sync.Mutex +} + +func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) { + privateKey := ... + pubKeyId := ... + // Set headers and such on w + s.mu.Lock() + defer s.mu.Unlock() + // To sign the digest, we need to give the signer a copy of the response body... + // ...but it is optional, no digest will be signed if given "nil" + body := ... + err := s.signer.SignResponse(privateKey, pubKeyId, w, body) + if err != nil { + ... + } + ... +} +``` + +The `pubKeyId` will be used at verification time. + +### Verifying + +Verifying requires an application to use the `pubKeyId` to both retrieve the key +needed for verification as well as determine the algorithm to use. Use a +`Verifier`: + +``` +func verify(r *http.Request) error { + verifier, err := httpsig.NewVerifier(r) + if err != nil { + return err + } + pubKeyId := verifier.KeyId() + var algo httpsig.Algorithm = ... + var pubKey crypto.PublicKey = ... + // The verifier will verify the Digest in addition to the HTTP signature + return verifier.Verify(pubKey, algo) +} +``` + +`Verifier`s are not safe for concurrent use by goroutines, but since they are +constructed on a per-request or per-response basis it should not be a common +restriction. + +[Build-Status-Image]: https://travis-ci.org/42wim/httpsig.svg?branch=master +[Build-Status-Url]: https://travis-ci.org/42wim/httpsig +[Go-Reference-Image]: https://pkg.go.dev/badge/github.com/42wim/httpsig +[Go-Reference-Url]: https://pkg.go.dev/github.com/42wim/httpsig +[Go-Report-Card-Image]: https://goreportcard.com/badge/github.com/42wim/httpsig +[Go-Report-Card-Url]: https://goreportcard.com/report/github.com/42wim/httpsig +[License-Image]: https://img.shields.io/github/license/42wim/httpsig?color=blue +[License-Url]: https://opensource.org/licenses/BSD-3-Clause +[Chat-Image]: https://img.shields.io/matrix/42wim:feneas.org?server_fqdn=matrix.org +[Chat-Url]: https://matrix.to/#/!BLOSvIyKTDLIVjRKSc:feneas.org?via=feneas.org&via=matrix.org +[OpenCollective-Image]: https://img.shields.io/opencollective/backers/42wim-activitypub-labs +[OpenCollective-Url]: https://opencollective.com/42wim-activitypub-labs diff --git a/cli/vendor/github.com/42wim/httpsig/algorithms.go b/cli/vendor/github.com/42wim/httpsig/algorithms.go new file mode 100644 index 0000000..4e5e191 --- /dev/null +++ b/cli/vendor/github.com/42wim/httpsig/algorithms.go @@ -0,0 +1,550 @@ +package httpsig + +import ( + "crypto" + "crypto/ecdsa" + "crypto/hmac" + "crypto/rsa" + "crypto/sha256" + "crypto/sha512" + "crypto/subtle" // Use should trigger great care + "encoding/asn1" + "errors" + "fmt" + "hash" + "io" + "math/big" + "strings" + + "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/blake2s" + "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/sha3" + "golang.org/x/crypto/ssh" +) + +const ( + hmacPrefix = "hmac" + rsaPrefix = "rsa" + sshPrefix = "ssh" + ecdsaPrefix = "ecdsa" + ed25519Prefix = "ed25519" + sha224String = "sha224" + sha256String = "sha256" + sha384String = "sha384" + sha512String = "sha512" + sha3_224String = "sha3-224" + sha3_256String = "sha3-256" + sha3_384String = "sha3-384" + sha3_512String = "sha3-512" + sha512_224String = "sha512-224" + sha512_256String = "sha512-256" + blake2s_256String = "blake2s-256" + blake2b_256String = "blake2b-256" + blake2b_384String = "blake2b-384" + blake2b_512String = "blake2b-512" +) + +var blake2Algorithms = map[crypto.Hash]bool{ + crypto.BLAKE2s_256: true, + crypto.BLAKE2b_256: true, + crypto.BLAKE2b_384: true, + crypto.BLAKE2b_512: true, +} + +var hashToDef = map[crypto.Hash]struct { + name string + new func(key []byte) (hash.Hash, error) // Only MACers will accept a key +}{ + // Which standard names these? + // The spec lists the following as a canonical reference, which is dead: + // http://www.iana.org/assignments/signature-algorithms + // + // Note that the forbidden hashes have an invalid 'new' function. + crypto.SHA224: {sha224String, func(key []byte) (hash.Hash, error) { return sha256.New224(), nil }}, + crypto.SHA256: {sha256String, func(key []byte) (hash.Hash, error) { return sha256.New(), nil }}, + crypto.SHA384: {sha384String, func(key []byte) (hash.Hash, error) { return sha512.New384(), nil }}, + crypto.SHA512: {sha512String, func(key []byte) (hash.Hash, error) { return sha512.New(), nil }}, + crypto.SHA3_224: {sha3_224String, func(key []byte) (hash.Hash, error) { return sha3.New224(), nil }}, + crypto.SHA3_256: {sha3_256String, func(key []byte) (hash.Hash, error) { return sha3.New256(), nil }}, + crypto.SHA3_384: {sha3_384String, func(key []byte) (hash.Hash, error) { return sha3.New384(), nil }}, + crypto.SHA3_512: {sha3_512String, func(key []byte) (hash.Hash, error) { return sha3.New512(), nil }}, + crypto.SHA512_224: {sha512_224String, func(key []byte) (hash.Hash, error) { return sha512.New512_224(), nil }}, + crypto.SHA512_256: {sha512_256String, func(key []byte) (hash.Hash, error) { return sha512.New512_256(), nil }}, + crypto.BLAKE2s_256: {blake2s_256String, func(key []byte) (hash.Hash, error) { return blake2s.New256(key) }}, + crypto.BLAKE2b_256: {blake2b_256String, func(key []byte) (hash.Hash, error) { return blake2b.New256(key) }}, + crypto.BLAKE2b_384: {blake2b_384String, func(key []byte) (hash.Hash, error) { return blake2b.New384(key) }}, + crypto.BLAKE2b_512: {blake2b_512String, func(key []byte) (hash.Hash, error) { return blake2b.New512(key) }}, +} + +var stringToHash map[string]crypto.Hash + +const ( + defaultAlgorithm = RSA_SHA256 + defaultAlgorithmHashing = sha256String +) + +func init() { + stringToHash = make(map[string]crypto.Hash, len(hashToDef)) + for k, v := range hashToDef { + stringToHash[v.name] = k + } + // This should guarantee that at runtime the defaultAlgorithm will not + // result in errors when fetching a macer or signer (see algorithms.go) + if ok, err := isAvailable(string(defaultAlgorithmHashing)); err != nil { + panic(err) + } else if !ok { + panic(fmt.Sprintf("the default httpsig algorithm is unavailable: %q", defaultAlgorithm)) + } +} + +func isForbiddenHash(h crypto.Hash) bool { + return false +} + +// signer is an internally public type. +type signer interface { + Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) + Verify(pub crypto.PublicKey, toHash, signature []byte) error + String() string +} + +// macer is an internally public type. +type macer interface { + Sign(sig, key []byte) ([]byte, error) + Equal(sig, actualMAC, key []byte) (bool, error) + String() string +} + +var _ macer = &hmacAlgorithm{} + +type hmacAlgorithm struct { + fn func(key []byte) (hash.Hash, error) + kind crypto.Hash +} + +func (h *hmacAlgorithm) Sign(sig, key []byte) ([]byte, error) { + hs, err := h.fn(key) + if err != nil { + return nil, err + } + if err = setSig(hs, sig); err != nil { + return nil, err + } + return hs.Sum(nil), nil +} + +func (h *hmacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { + hs, err := h.fn(key) + if err != nil { + return false, err + } + defer hs.Reset() + err = setSig(hs, sig) + if err != nil { + return false, err + } + expected := hs.Sum(nil) + return hmac.Equal(actualMAC, expected), nil +} + +func (h *hmacAlgorithm) String() string { + return fmt.Sprintf("%s-%s", hmacPrefix, hashToDef[h.kind].name) +} + +var _ signer = &rsaAlgorithm{} + +type rsaAlgorithm struct { + hash.Hash + kind crypto.Hash + sshSigner ssh.Signer +} + +func (r *rsaAlgorithm) setSig(b []byte) error { + n, err := r.Write(b) + if err != nil { + r.Reset() + return err + } else if n != len(b) { + r.Reset() + return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) + } + return nil +} + +// Code from https://github.com/cloudtools/ssh-cert-authority/pull/49/files +// This interface provides a way to reach the exported, but not accessible SignWithOpts() method +// in x/crypto/ssh/agent. Access to this is needed to sign with more secure signing algorithms +type agentKeyringSigner interface { + SignWithOpts(rand io.Reader, data []byte, opts crypto.SignerOpts) (*ssh.Signature, error) +} + +// A struct to wrap an SSH Signer with one that will switch to SHA256 Signatures. +// Replaces the call to Sign() with a call to SignWithOpts using HashFunc() algorithm. +type Sha256Signer struct { + ssh.Signer +} + +func (s Sha256Signer) HashFunc() crypto.Hash { + return crypto.SHA256 +} + +func (s Sha256Signer) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { + if aks, ok := s.Signer.(agentKeyringSigner); !ok { + return nil, fmt.Errorf("ssh: can't wrap a non ssh agentKeyringSigner") + } else { + return aks.SignWithOpts(rand, data, s) + } +} + +func (r *rsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { + if r.sshSigner != nil { + var ( + sshsig *ssh.Signature + err error + ) + // are we using an SSH Agent + if _, ok := r.sshSigner.(agentKeyringSigner); ok { + signer := Sha256Signer{r.sshSigner} + sshsig, err = signer.Sign(rand, sig) + if err != nil { + return nil, err + } + } else { + sshsig, err = r.sshSigner.(ssh.AlgorithmSigner).SignWithAlgorithm(rand, sig, ssh.SigAlgoRSASHA2256) + if err != nil { + return nil, err + } + } + + return sshsig.Blob, nil + } + defer r.Reset() + + if err := r.setSig(sig); err != nil { + return nil, err + } + rsaK, ok := p.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("crypto.PrivateKey is not *rsa.PrivateKey") + } + return rsa.SignPKCS1v15(rand, rsaK, r.kind, r.Sum(nil)) +} + +func (r *rsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { + defer r.Reset() + rsaK, ok := pub.(*rsa.PublicKey) + if !ok { + return errors.New("crypto.PublicKey is not *rsa.PublicKey") + } + if err := r.setSig(toHash); err != nil { + return err + } + return rsa.VerifyPKCS1v15(rsaK, r.kind, r.Sum(nil), signature) +} + +func (r *rsaAlgorithm) String() string { + return fmt.Sprintf("%s-%s", rsaPrefix, hashToDef[r.kind].name) +} + +var _ signer = &ed25519Algorithm{} + +type ed25519Algorithm struct { + sshSigner ssh.Signer +} + +func (r *ed25519Algorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { + if r.sshSigner != nil { + sshsig, err := r.sshSigner.Sign(rand, sig) + if err != nil { + return nil, err + } + + return sshsig.Blob, nil + } + ed25519K, ok := p.(ed25519.PrivateKey) + if !ok { + return nil, errors.New("crypto.PrivateKey is not ed25519.PrivateKey") + } + return ed25519.Sign(ed25519K, sig), nil +} + +func (r *ed25519Algorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { + ed25519K, ok := pub.(ed25519.PublicKey) + if !ok { + return errors.New("crypto.PublicKey is not ed25519.PublicKey") + } + + if ed25519.Verify(ed25519K, toHash, signature) { + return nil + } + + return errors.New("ed25519 verify failed") +} + +func (r *ed25519Algorithm) String() string { + return fmt.Sprintf("%s", ed25519Prefix) +} + +var _ signer = &ecdsaAlgorithm{} + +type ecdsaAlgorithm struct { + hash.Hash + kind crypto.Hash +} + +func (r *ecdsaAlgorithm) setSig(b []byte) error { + n, err := r.Write(b) + if err != nil { + r.Reset() + return err + } else if n != len(b) { + r.Reset() + return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) + } + return nil +} + +type ECDSASignature struct { + R, S *big.Int +} + +func (r *ecdsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { + defer r.Reset() + if err := r.setSig(sig); err != nil { + return nil, err + } + ecdsaK, ok := p.(*ecdsa.PrivateKey) + if !ok { + return nil, errors.New("crypto.PrivateKey is not *ecdsa.PrivateKey") + } + R, S, err := ecdsa.Sign(rand, ecdsaK, r.Sum(nil)) + if err != nil { + return nil, err + } + + signature := ECDSASignature{R: R, S: S} + bytes, err := asn1.Marshal(signature) + + return bytes, err +} + +func (r *ecdsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { + defer r.Reset() + ecdsaK, ok := pub.(*ecdsa.PublicKey) + if !ok { + return errors.New("crypto.PublicKey is not *ecdsa.PublicKey") + } + if err := r.setSig(toHash); err != nil { + return err + } + + sig := new(ECDSASignature) + _, err := asn1.Unmarshal(signature, sig) + if err != nil { + return err + } + + if ecdsa.Verify(ecdsaK, r.Sum(nil), sig.R, sig.S) { + return nil + } else { + return errors.New("Invalid signature") + } +} + +func (r *ecdsaAlgorithm) String() string { + return fmt.Sprintf("%s-%s", ecdsaPrefix, hashToDef[r.kind].name) +} + +var _ macer = &blakeMacAlgorithm{} + +type blakeMacAlgorithm struct { + fn func(key []byte) (hash.Hash, error) + kind crypto.Hash +} + +func (r *blakeMacAlgorithm) Sign(sig, key []byte) ([]byte, error) { + hs, err := r.fn(key) + if err != nil { + return nil, err + } + if err = setSig(hs, sig); err != nil { + return nil, err + } + return hs.Sum(nil), nil +} + +func (r *blakeMacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { + hs, err := r.fn(key) + if err != nil { + return false, err + } + defer hs.Reset() + err = setSig(hs, sig) + if err != nil { + return false, err + } + expected := hs.Sum(nil) + return subtle.ConstantTimeCompare(actualMAC, expected) == 1, nil +} + +func (r *blakeMacAlgorithm) String() string { + return fmt.Sprintf("%s", hashToDef[r.kind].name) +} + +func setSig(a hash.Hash, b []byte) error { + n, err := a.Write(b) + if err != nil { + a.Reset() + return err + } else if n != len(b) { + a.Reset() + return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) + } + return nil +} + +// IsSupportedHttpSigAlgorithm returns true if the string is supported by this +// library, is not a hash known to be weak, and is supported by the hardware. +func IsSupportedHttpSigAlgorithm(algo string) bool { + a, err := isAvailable(algo) + return a && err == nil +} + +// isAvailable is an internally public function +func isAvailable(algo string) (bool, error) { + c, ok := stringToHash[algo] + if !ok { + return false, fmt.Errorf("no match for %q", algo) + } + if isForbiddenHash(c) { + return false, fmt.Errorf("forbidden hash type in %q", algo) + } + return c.Available(), nil +} + +func newAlgorithmConstructor(algo string) (fn func(k []byte) (hash.Hash, error), c crypto.Hash, e error) { + ok := false + c, ok = stringToHash[algo] + if !ok { + e = fmt.Errorf("no match for %q", algo) + return + } + if isForbiddenHash(c) { + e = fmt.Errorf("forbidden hash type in %q", algo) + return + } + algoDef, ok := hashToDef[c] + if !ok { + e = fmt.Errorf("have crypto.Hash %v but no definition", c) + return + } + fn = func(key []byte) (hash.Hash, error) { + h, err := algoDef.new(key) + if err != nil { + return nil, err + } + return h, nil + } + return +} + +func newAlgorithm(algo string, key []byte) (hash.Hash, crypto.Hash, error) { + fn, c, err := newAlgorithmConstructor(algo) + if err != nil { + return nil, c, err + } + h, err := fn(key) + return h, c, err +} + +func signerFromSSHSigner(sshSigner ssh.Signer, s string) (signer, error) { + switch { + case strings.HasPrefix(s, rsaPrefix): + return &rsaAlgorithm{ + sshSigner: sshSigner, + }, nil + case strings.HasPrefix(s, ed25519Prefix): + return &ed25519Algorithm{ + sshSigner: sshSigner, + }, nil + default: + return nil, fmt.Errorf("no signer matching %q", s) + } +} + +// signerFromString is an internally public method constructor +func signerFromString(s string) (signer, error) { + s = strings.ToLower(s) + isEcdsa := false + isEd25519 := false + var algo string = "" + if strings.HasPrefix(s, ecdsaPrefix) { + algo = strings.TrimPrefix(s, ecdsaPrefix+"-") + isEcdsa = true + } else if strings.HasPrefix(s, rsaPrefix) { + algo = strings.TrimPrefix(s, rsaPrefix+"-") + } else if strings.HasPrefix(s, ed25519Prefix) { + isEd25519 = true + algo = "sha512" + } else { + return nil, fmt.Errorf("no signer matching %q", s) + } + hash, cHash, err := newAlgorithm(algo, nil) + if err != nil { + return nil, err + } + if isEd25519 { + return &ed25519Algorithm{}, nil + } + if isEcdsa { + return &ecdsaAlgorithm{ + Hash: hash, + kind: cHash, + }, nil + } + return &rsaAlgorithm{ + Hash: hash, + kind: cHash, + }, nil +} + +// macerFromString is an internally public method constructor +func macerFromString(s string) (macer, error) { + s = strings.ToLower(s) + if strings.HasPrefix(s, hmacPrefix) { + algo := strings.TrimPrefix(s, hmacPrefix+"-") + hashFn, cHash, err := newAlgorithmConstructor(algo) + if err != nil { + return nil, err + } + // Ensure below does not panic + _, err = hashFn(nil) + if err != nil { + return nil, err + } + return &hmacAlgorithm{ + fn: func(key []byte) (hash.Hash, error) { + return hmac.New(func() hash.Hash { + h, e := hashFn(nil) + if e != nil { + panic(e) + } + return h + }, key), nil + }, + kind: cHash, + }, nil + } else if bl, ok := stringToHash[s]; ok && blake2Algorithms[bl] { + hashFn, cHash, err := newAlgorithmConstructor(s) + if err != nil { + return nil, err + } + return &blakeMacAlgorithm{ + fn: hashFn, + kind: cHash, + }, nil + } else { + return nil, fmt.Errorf("no MACer matching %q", s) + } +} diff --git a/cli/vendor/github.com/42wim/httpsig/digest.go b/cli/vendor/github.com/42wim/httpsig/digest.go new file mode 100644 index 0000000..bf9e3a9 --- /dev/null +++ b/cli/vendor/github.com/42wim/httpsig/digest.go @@ -0,0 +1,120 @@ +package httpsig + +import ( + "bytes" + "crypto" + "encoding/base64" + "fmt" + "hash" + "net/http" + "strings" +) + +type DigestAlgorithm string + +const ( + DigestSha256 DigestAlgorithm = "SHA-256" + DigestSha512 = "SHA-512" +) + +var digestToDef = map[DigestAlgorithm]crypto.Hash{ + DigestSha256: crypto.SHA256, + DigestSha512: crypto.SHA512, +} + +// IsSupportedDigestAlgorithm returns true if hte string is supported by this +// library, is not a hash known to be weak, and is supported by the hardware. +func IsSupportedDigestAlgorithm(algo string) bool { + uc := DigestAlgorithm(strings.ToUpper(algo)) + c, ok := digestToDef[uc] + return ok && c.Available() +} + +func getHash(alg DigestAlgorithm) (h hash.Hash, toUse DigestAlgorithm, err error) { + upper := DigestAlgorithm(strings.ToUpper(string(alg))) + c, ok := digestToDef[upper] + if !ok { + err = fmt.Errorf("unknown or unsupported Digest algorithm: %s", alg) + } else if !c.Available() { + err = fmt.Errorf("unavailable Digest algorithm: %s", alg) + } else { + h = c.New() + toUse = upper + } + return +} + +const ( + digestHeader = "Digest" + digestDelim = "=" +) + +func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) { + _, ok := r.Header[digestHeader] + if ok { + err = fmt.Errorf("cannot add Digest: Digest is already set") + return + } + var h hash.Hash + var a DigestAlgorithm + h, a, err = getHash(algo) + if err != nil { + return + } + h.Write(b) + sum := h.Sum(nil) + r.Header.Add(digestHeader, + fmt.Sprintf("%s%s%s", + a, + digestDelim, + base64.StdEncoding.EncodeToString(sum[:]))) + return +} + +func addDigestResponse(r http.ResponseWriter, algo DigestAlgorithm, b []byte) (err error) { + _, ok := r.Header()[digestHeader] + if ok { + err = fmt.Errorf("cannot add Digest: Digest is already set") + return + } + var h hash.Hash + var a DigestAlgorithm + h, a, err = getHash(algo) + if err != nil { + return + } + h.Write(b) + sum := h.Sum(nil) + r.Header().Add(digestHeader, + fmt.Sprintf("%s%s%s", + a, + digestDelim, + base64.StdEncoding.EncodeToString(sum[:]))) + return +} + +func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) { + d := r.Header.Get(digestHeader) + if len(d) == 0 { + err = fmt.Errorf("cannot verify Digest: request has no Digest header") + return + } + elem := strings.SplitN(d, digestDelim, 2) + if len(elem) != 2 { + err = fmt.Errorf("cannot verify Digest: malformed Digest: %s", d) + return + } + var h hash.Hash + h, _, err = getHash(DigestAlgorithm(elem[0])) + if err != nil { + return + } + h.Write(body.Bytes()) + sum := h.Sum(nil) + encSum := base64.StdEncoding.EncodeToString(sum[:]) + if encSum != elem[1] { + err = fmt.Errorf("cannot verify Digest: header Digest does not match the digest of the request body") + return + } + return +} diff --git a/cli/vendor/github.com/42wim/httpsig/httpsig.go b/cli/vendor/github.com/42wim/httpsig/httpsig.go new file mode 100644 index 0000000..c9c38e8 --- /dev/null +++ b/cli/vendor/github.com/42wim/httpsig/httpsig.go @@ -0,0 +1,356 @@ +// Implements HTTP request and response signing and verification. Supports the +// major MAC and asymmetric key signature algorithms. It has several safety +// restrictions: One, none of the widely known non-cryptographically safe +// algorithms are permitted; Two, the RSA SHA256 algorithms must be available in +// the binary (and it should, barring export restrictions); Finally, the library +// assumes either the 'Authorizationn' or 'Signature' headers are to be set (but +// not both). +package httpsig + +import ( + "crypto" + "fmt" + "net/http" + "strings" + "time" + + "golang.org/x/crypto/ssh" +) + +// Algorithm specifies a cryptography secure algorithm for signing HTTP requests +// and responses. +type Algorithm string + +const ( + // MAC-based algoirthms. + HMAC_SHA224 Algorithm = hmacPrefix + "-" + sha224String + HMAC_SHA256 Algorithm = hmacPrefix + "-" + sha256String + HMAC_SHA384 Algorithm = hmacPrefix + "-" + sha384String + HMAC_SHA512 Algorithm = hmacPrefix + "-" + sha512String + HMAC_SHA3_224 Algorithm = hmacPrefix + "-" + sha3_224String + HMAC_SHA3_256 Algorithm = hmacPrefix + "-" + sha3_256String + HMAC_SHA3_384 Algorithm = hmacPrefix + "-" + sha3_384String + HMAC_SHA3_512 Algorithm = hmacPrefix + "-" + sha3_512String + HMAC_SHA512_224 Algorithm = hmacPrefix + "-" + sha512_224String + HMAC_SHA512_256 Algorithm = hmacPrefix + "-" + sha512_256String + HMAC_BLAKE2S_256 Algorithm = hmacPrefix + "-" + blake2s_256String + HMAC_BLAKE2B_256 Algorithm = hmacPrefix + "-" + blake2b_256String + HMAC_BLAKE2B_384 Algorithm = hmacPrefix + "-" + blake2b_384String + HMAC_BLAKE2B_512 Algorithm = hmacPrefix + "-" + blake2b_512String + BLAKE2S_256 Algorithm = blake2s_256String + BLAKE2B_256 Algorithm = blake2b_256String + BLAKE2B_384 Algorithm = blake2b_384String + BLAKE2B_512 Algorithm = blake2b_512String + // RSA-based algorithms. + RSA_SHA224 Algorithm = rsaPrefix + "-" + sha224String + // RSA_SHA256 is the default algorithm. + RSA_SHA256 Algorithm = rsaPrefix + "-" + sha256String + RSA_SHA384 Algorithm = rsaPrefix + "-" + sha384String + RSA_SHA512 Algorithm = rsaPrefix + "-" + sha512String + // ECDSA algorithms + ECDSA_SHA224 Algorithm = ecdsaPrefix + "-" + sha224String + ECDSA_SHA256 Algorithm = ecdsaPrefix + "-" + sha256String + ECDSA_SHA384 Algorithm = ecdsaPrefix + "-" + sha384String + ECDSA_SHA512 Algorithm = ecdsaPrefix + "-" + sha512String + // ED25519 algorithms + // can only be SHA512 + ED25519 Algorithm = ed25519Prefix + + // Just because you can glue things together, doesn't mean they will + // work. The following options are not supported. + rsa_SHA3_224 Algorithm = rsaPrefix + "-" + sha3_224String + rsa_SHA3_256 Algorithm = rsaPrefix + "-" + sha3_256String + rsa_SHA3_384 Algorithm = rsaPrefix + "-" + sha3_384String + rsa_SHA3_512 Algorithm = rsaPrefix + "-" + sha3_512String + rsa_SHA512_224 Algorithm = rsaPrefix + "-" + sha512_224String + rsa_SHA512_256 Algorithm = rsaPrefix + "-" + sha512_256String + rsa_BLAKE2S_256 Algorithm = rsaPrefix + "-" + blake2s_256String + rsa_BLAKE2B_256 Algorithm = rsaPrefix + "-" + blake2b_256String + rsa_BLAKE2B_384 Algorithm = rsaPrefix + "-" + blake2b_384String + rsa_BLAKE2B_512 Algorithm = rsaPrefix + "-" + blake2b_512String +) + +// HTTP Signatures can be applied to different HTTP headers, depending on the +// expected application behavior. +type SignatureScheme string + +const ( + // Signature will place the HTTP Signature into the 'Signature' HTTP + // header. + Signature SignatureScheme = "Signature" + // Authorization will place the HTTP Signature into the 'Authorization' + // HTTP header. + Authorization SignatureScheme = "Authorization" +) + +const ( + // The HTTP Signatures specification uses the "Signature" auth-scheme + // for the Authorization header. This is coincidentally named, but not + // semantically the same, as the "Signature" HTTP header value. + signatureAuthScheme = "Signature" +) + +// There are subtle differences to the values in the header. The Authorization +// header has an 'auth-scheme' value that must be prefixed to the rest of the +// key and values. +func (s SignatureScheme) authScheme() string { + switch s { + case Authorization: + return signatureAuthScheme + default: + return "" + } +} + +// Signers will sign HTTP requests or responses based on the algorithms and +// headers selected at creation time. +// +// Signers are not safe to use between multiple goroutines. +// +// Note that signatures do set the deprecated 'algorithm' parameter for +// backwards compatibility. +type Signer interface { + // SignRequest signs the request using a private key. The public key id + // is used by the HTTP server to identify which key to use to verify the + // signature. + // + // If the Signer was created using a MAC based algorithm, then the key + // is expected to be of type []byte. If the Signer was created using an + // RSA based algorithm, then the private key is expected to be of type + // *rsa.PrivateKey. + // + // A Digest (RFC 3230) will be added to the request. The body provided + // must match the body used in the request, and is allowed to be nil. + // The Digest ensures the request body is not tampered with in flight, + // and if the signer is created to also sign the "Digest" header, the + // HTTP Signature will then ensure both the Digest and body are not both + // modified to maliciously represent different content. + SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error + // SignResponse signs the response using a private key. The public key + // id is used by the HTTP client to identify which key to use to verify + // the signature. + // + // If the Signer was created using a MAC based algorithm, then the key + // is expected to be of type []byte. If the Signer was created using an + // RSA based algorithm, then the private key is expected to be of type + // *rsa.PrivateKey. + // + // A Digest (RFC 3230) will be added to the response. The body provided + // must match the body written in the response, and is allowed to be + // nil. The Digest ensures the response body is not tampered with in + // flight, and if the signer is created to also sign the "Digest" + // header, the HTTP Signature will then ensure both the Digest and body + // are not both modified to maliciously represent different content. + SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error +} + +// NewSigner creates a new Signer with the provided algorithm preferences to +// make HTTP signatures. Only the first available algorithm will be used, which +// is returned by this function along with the Signer. If none of the preferred +// algorithms were available, then the default algorithm is used. The headers +// specified will be included into the HTTP signatures. +// +// The Digest will also be calculated on a request's body using the provided +// digest algorithm, if "Digest" is one of the headers listed. +// +// The provided scheme determines which header is populated with the HTTP +// Signature. +// +// An error is returned if an unknown or a known cryptographically insecure +// Algorithm is provided. +func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, Algorithm, error) { + for _, pref := range prefs { + s, err := newSigner(pref, dAlgo, headers, scheme, expiresIn) + if err != nil { + continue + } + return s, pref, err + } + s, err := newSigner(defaultAlgorithm, dAlgo, headers, scheme, expiresIn) + return s, defaultAlgorithm, err +} + +// Signers will sign HTTP requests or responses based on the algorithms and +// headers selected at creation time. +// +// Signers are not safe to use between multiple goroutines. +// +// Note that signatures do set the deprecated 'algorithm' parameter for +// backwards compatibility. +type SSHSigner interface { + // SignRequest signs the request using ssh.Signer. + // The public key id is used by the HTTP server to identify which key to use + // to verify the signature. + // + // A Digest (RFC 3230) will be added to the request. The body provided + // must match the body used in the request, and is allowed to be nil. + // The Digest ensures the request body is not tampered with in flight, + // and if the signer is created to also sign the "Digest" header, the + // HTTP Signature will then ensure both the Digest and body are not both + // modified to maliciously represent different content. + SignRequest(pubKeyId string, r *http.Request, body []byte) error + // SignResponse signs the response using ssh.Signer. The public key + // id is used by the HTTP client to identify which key to use to verify + // the signature. + // + // A Digest (RFC 3230) will be added to the response. The body provided + // must match the body written in the response, and is allowed to be + // nil. The Digest ensures the response body is not tampered with in + // flight, and if the signer is created to also sign the "Digest" + // header, the HTTP Signature will then ensure both the Digest and body + // are not both modified to maliciously represent different content. + SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error +} + +// NewwSSHSigner creates a new Signer using the specified ssh.Signer +// At the moment only ed25519 ssh keys are supported. +// The headers specified will be included into the HTTP signatures. +// +// The Digest will also be calculated on a request's body using the provided +// digest algorithm, if "Digest" is one of the headers listed. +// +// The provided scheme determines which header is populated with the HTTP +// Signature. +func NewSSHSigner(s ssh.Signer, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, Algorithm, error) { + sshAlgo := getSSHAlgorithm(s.PublicKey().Type()) + if sshAlgo == "" { + return nil, "", fmt.Errorf("key type: %s not supported yet.", s.PublicKey().Type()) + } + + signer, err := newSSHSigner(s, sshAlgo, dAlgo, headers, scheme, expiresIn) + if err != nil { + return nil, "", err + } + + return signer, sshAlgo, nil +} + +func getSSHAlgorithm(pkType string) Algorithm { + switch { + case strings.HasPrefix(pkType, sshPrefix+"-"+ed25519Prefix): + return ED25519 + case strings.HasPrefix(pkType, sshPrefix+"-"+rsaPrefix): + return RSA_SHA256 + } + + return "" +} + +// Verifier verifies HTTP Signatures. +// +// It will determine which of the supported headers has the parameters +// that define the signature. +// +// Verifiers are not safe to use between multiple goroutines. +// +// Note that verification ignores the deprecated 'algorithm' parameter. +type Verifier interface { + // KeyId gets the public key id that the signature is signed with. + // + // Note that the application is expected to determine the algorithm + // used based on metadata or out-of-band information for this key id. + KeyId() string + // Verify accepts the public key specified by KeyId and returns an + // error if verification fails or if the signature is malformed. The + // algorithm must be the one used to create the signature in order to + // pass verification. The algorithm is determined based on metadata or + // out-of-band information for the key id. + // + // If the signature was created using a MAC based algorithm, then the + // key is expected to be of type []byte. If the signature was created + // using an RSA based algorithm, then the public key is expected to be + // of type *rsa.PublicKey. + Verify(pKey crypto.PublicKey, algo Algorithm) error +} + +const ( + // host is treated specially because golang may not include it in the + // request header map on the server side of a request. + hostHeader = "Host" +) + +// NewVerifier verifies the given request. It returns an error if the HTTP +// Signature parameters are not present in any headers, are present in more than +// one header, are malformed, or are missing required parameters. It ignores +// unknown HTTP Signature parameters. +func NewVerifier(r *http.Request) (Verifier, error) { + h := r.Header + if _, hasHostHeader := h[hostHeader]; len(r.Host) > 0 && !hasHostHeader { + h[hostHeader] = []string{r.Host} + } + return newVerifier(h, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) { + return signatureString(h, toInclude, addRequestTarget(r), created, expires) + }) +} + +// NewResponseVerifier verifies the given response. It returns errors under the +// same conditions as NewVerifier. +func NewResponseVerifier(r *http.Response) (Verifier, error) { + return newVerifier(r.Header, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) { + return signatureString(h, toInclude, requestTargetNotPermitted, created, expires) + }) +} + +func newSSHSigner(sshSigner ssh.Signer, algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, error) { + var expires, created int64 = 0, 0 + + if expiresIn != 0 { + created = time.Now().Unix() + expires = created + expiresIn + } + + s, err := signerFromSSHSigner(sshSigner, string(algo)) + if err != nil { + return nil, fmt.Errorf("no crypto implementation available for ssh algo %q: %s", algo, err) + } + + a := &asymmSSHSigner{ + asymmSigner: &asymmSigner{ + s: s, + dAlgo: dAlgo, + headers: headers, + targetHeader: scheme, + prefix: scheme.authScheme(), + created: created, + expires: expires, + }, + } + + return a, nil +} + +func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, error) { + var expires, created int64 = 0, 0 + if expiresIn != 0 { + created = time.Now().Unix() + expires = created + expiresIn + } + + s, err := signerFromString(string(algo)) + if err == nil { + a := &asymmSigner{ + s: s, + dAlgo: dAlgo, + headers: headers, + targetHeader: scheme, + prefix: scheme.authScheme(), + created: created, + expires: expires, + } + return a, nil + } + m, err := macerFromString(string(algo)) + if err != nil { + return nil, fmt.Errorf("no crypto implementation available for %q: %s", algo, err) + } + c := &macSigner{ + m: m, + dAlgo: dAlgo, + headers: headers, + targetHeader: scheme, + prefix: scheme.authScheme(), + created: created, + expires: expires, + } + return c, nil +} diff --git a/cli/vendor/github.com/42wim/httpsig/signing.go b/cli/vendor/github.com/42wim/httpsig/signing.go new file mode 100644 index 0000000..e18db41 --- /dev/null +++ b/cli/vendor/github.com/42wim/httpsig/signing.go @@ -0,0 +1,334 @@ +package httpsig + +import ( + "bytes" + "crypto" + "crypto/rand" + "encoding/base64" + "fmt" + "net/http" + "net/textproto" + "strconv" + "strings" +) + +const ( + // Signature Parameters + keyIdParameter = "keyId" + algorithmParameter = "algorithm" + headersParameter = "headers" + signatureParameter = "signature" + prefixSeparater = " " + parameterKVSeparater = "=" + parameterValueDelimiter = "\"" + parameterSeparater = "," + headerParameterValueDelim = " " + // RequestTarget specifies to include the http request method and + // entire URI in the signature. Pass it as a header to NewSigner. + RequestTarget = "(request-target)" + createdKey = "created" + expiresKey = "expires" + dateHeader = "date" + + // Signature String Construction + headerFieldDelimiter = ": " + headersDelimiter = "\n" + headerValueDelimiter = ", " + requestTargetSeparator = " " +) + +var defaultHeaders = []string{dateHeader} + +var _ Signer = &macSigner{} + +type macSigner struct { + m macer + makeDigest bool + dAlgo DigestAlgorithm + headers []string + targetHeader SignatureScheme + prefix string + created int64 + expires int64 +} + +func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { + if body != nil { + err := addDigest(r, m.dAlgo, body) + if err != nil { + return err + } + } + s, err := m.signatureString(r) + if err != nil { + return err + } + enc, err := m.signSignature(pKey, s) + if err != nil { + return err + } + setSignatureHeader(r.Header, string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) + return nil +} + +func (m *macSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { + if body != nil { + err := addDigestResponse(r, m.dAlgo, body) + if err != nil { + return err + } + } + s, err := m.signatureStringResponse(r) + if err != nil { + return err + } + enc, err := m.signSignature(pKey, s) + if err != nil { + return err + } + setSignatureHeader(r.Header(), string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) + return nil +} + +func (m *macSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { + pKeyBytes, ok := pKey.([]byte) + if !ok { + return "", fmt.Errorf("private key for MAC signing must be of type []byte") + } + sig, err := m.m.Sign([]byte(s), pKeyBytes) + if err != nil { + return "", err + } + enc := base64.StdEncoding.EncodeToString(sig) + return enc, nil +} + +func (m *macSigner) signatureString(r *http.Request) (string, error) { + return signatureString(r.Header, m.headers, addRequestTarget(r), m.created, m.expires) +} + +func (m *macSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { + return signatureString(r.Header(), m.headers, requestTargetNotPermitted, m.created, m.expires) +} + +var _ Signer = &asymmSigner{} + +type asymmSigner struct { + s signer + makeDigest bool + dAlgo DigestAlgorithm + headers []string + targetHeader SignatureScheme + prefix string + created int64 + expires int64 +} + +func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { + if body != nil { + err := addDigest(r, a.dAlgo, body) + if err != nil { + return err + } + } + s, err := a.signatureString(r) + if err != nil { + return err + } + enc, err := a.signSignature(pKey, s) + if err != nil { + return err + } + setSignatureHeader(r.Header, string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) + return nil +} + +func (a *asymmSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { + if body != nil { + err := addDigestResponse(r, a.dAlgo, body) + if err != nil { + return err + } + } + s, err := a.signatureStringResponse(r) + if err != nil { + return err + } + enc, err := a.signSignature(pKey, s) + if err != nil { + return err + } + setSignatureHeader(r.Header(), string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) + return nil +} + +func (a *asymmSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { + sig, err := a.s.Sign(rand.Reader, pKey, []byte(s)) + if err != nil { + return "", err + } + enc := base64.StdEncoding.EncodeToString(sig) + return enc, nil +} + +func (a *asymmSigner) signatureString(r *http.Request) (string, error) { + return signatureString(r.Header, a.headers, addRequestTarget(r), a.created, a.expires) +} + +func (a *asymmSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { + return signatureString(r.Header(), a.headers, requestTargetNotPermitted, a.created, a.expires) +} + +var _ SSHSigner = &asymmSSHSigner{} + +type asymmSSHSigner struct { + *asymmSigner +} + +func (a *asymmSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error { + return a.asymmSigner.SignRequest(nil, pubKeyId, r, body) +} + +func (a *asymmSSHSigner) SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error { + return a.asymmSigner.SignResponse(nil, pubKeyId, r, body) +} + +func setSignatureHeader(h http.Header, targetHeader, prefix, pubKeyId, algo, enc string, headers []string, created int64, expires int64) { + if len(headers) == 0 { + headers = defaultHeaders + } + var b bytes.Buffer + // KeyId + b.WriteString(prefix) + if len(prefix) > 0 { + b.WriteString(prefixSeparater) + } + b.WriteString(keyIdParameter) + b.WriteString(parameterKVSeparater) + b.WriteString(parameterValueDelimiter) + b.WriteString(pubKeyId) + b.WriteString(parameterValueDelimiter) + b.WriteString(parameterSeparater) + // Algorithm + b.WriteString(algorithmParameter) + b.WriteString(parameterKVSeparater) + b.WriteString(parameterValueDelimiter) + b.WriteString("hs2019") //real algorithm is hidden, see newest version of spec draft + b.WriteString(parameterValueDelimiter) + b.WriteString(parameterSeparater) + + hasCreated := false + hasExpires := false + for _, h := range headers { + val := strings.ToLower(h) + if val == "("+createdKey+")" { + hasCreated = true + } else if val == "("+expiresKey+")" { + hasExpires = true + } + } + + // Created + if hasCreated == true { + b.WriteString(createdKey) + b.WriteString(parameterKVSeparater) + b.WriteString(strconv.FormatInt(created, 10)) + b.WriteString(parameterSeparater) + } + + // Expires + if hasExpires == true { + b.WriteString(expiresKey) + b.WriteString(parameterKVSeparater) + b.WriteString(strconv.FormatInt(expires, 10)) + b.WriteString(parameterSeparater) + } + + // Headers + b.WriteString(headersParameter) + b.WriteString(parameterKVSeparater) + b.WriteString(parameterValueDelimiter) + for i, h := range headers { + b.WriteString(strings.ToLower(h)) + if i != len(headers)-1 { + b.WriteString(headerParameterValueDelim) + } + } + b.WriteString(parameterValueDelimiter) + b.WriteString(parameterSeparater) + // Signature + b.WriteString(signatureParameter) + b.WriteString(parameterKVSeparater) + b.WriteString(parameterValueDelimiter) + b.WriteString(enc) + b.WriteString(parameterValueDelimiter) + h.Add(targetHeader, b.String()) +} + +func requestTargetNotPermitted(b *bytes.Buffer) error { + return fmt.Errorf("cannot sign with %q on anything other than an http request", RequestTarget) +} + +func addRequestTarget(r *http.Request) func(b *bytes.Buffer) error { + return func(b *bytes.Buffer) error { + b.WriteString(RequestTarget) + b.WriteString(headerFieldDelimiter) + b.WriteString(strings.ToLower(r.Method)) + b.WriteString(requestTargetSeparator) + b.WriteString(r.URL.Path) + + if r.URL.RawQuery != "" { + b.WriteString("?") + b.WriteString(r.URL.RawQuery) + } + + return nil + } +} + +func signatureString(values http.Header, include []string, requestTargetFn func(b *bytes.Buffer) error, created int64, expires int64) (string, error) { + if len(include) == 0 { + include = defaultHeaders + } + var b bytes.Buffer + for n, i := range include { + i := strings.ToLower(i) + if i == RequestTarget { + err := requestTargetFn(&b) + if err != nil { + return "", err + } + } else if i == "("+expiresKey+")" { + if expires == 0 { + return "", fmt.Errorf("missing expires value") + } + b.WriteString(i) + b.WriteString(headerFieldDelimiter) + b.WriteString(strconv.FormatInt(expires, 10)) + } else if i == "("+createdKey+")" { + if created == 0 { + return "", fmt.Errorf("missing created value") + } + b.WriteString(i) + b.WriteString(headerFieldDelimiter) + b.WriteString(strconv.FormatInt(created, 10)) + } else { + hv, ok := values[textproto.CanonicalMIMEHeaderKey(i)] + if !ok { + return "", fmt.Errorf("missing header %q", i) + } + b.WriteString(i) + b.WriteString(headerFieldDelimiter) + for i, v := range hv { + b.WriteString(strings.TrimSpace(v)) + if i < len(hv)-1 { + b.WriteString(headerValueDelimiter) + } + } + } + if n < len(include)-1 { + b.WriteString(headersDelimiter) + } + } + return b.String(), nil +} diff --git a/cli/vendor/github.com/42wim/httpsig/verifying.go b/cli/vendor/github.com/42wim/httpsig/verifying.go new file mode 100644 index 0000000..e39b9dc --- /dev/null +++ b/cli/vendor/github.com/42wim/httpsig/verifying.go @@ -0,0 +1,184 @@ +package httpsig + +import ( + "crypto" + "encoding/base64" + "errors" + "fmt" + "net/http" + "strconv" + "strings" + "time" +) + +var _ Verifier = &verifier{} + +type verifier struct { + header http.Header + kId string + signature string + created int64 + expires int64 + headers []string + sigStringFn func(http.Header, []string, int64, int64) (string, error) +} + +func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64) (string, error)) (*verifier, error) { + scheme, s, err := getSignatureScheme(h) + if err != nil { + return nil, err + } + kId, sig, headers, created, expires, err := getSignatureComponents(scheme, s) + if created != 0 { + //check if created is not in the future, we assume a maximum clock offset of 10 seconds + now := time.Now().Unix() + if created-now > 10 { + return nil, errors.New("created is in the future") + } + } + if expires != 0 { + //check if expires is in the past, we assume a maximum clock offset of 10 seconds + now := time.Now().Unix() + if now-expires > 10 { + return nil, errors.New("signature expired") + } + } + if err != nil { + return nil, err + } + return &verifier{ + header: h, + kId: kId, + signature: sig, + created: created, + expires: expires, + headers: headers, + sigStringFn: sigStringFn, + }, nil +} + +func (v *verifier) KeyId() string { + return v.kId +} + +func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error { + s, err := signerFromString(string(algo)) + if err == nil { + return v.asymmVerify(s, pKey) + } + m, err := macerFromString(string(algo)) + if err == nil { + return v.macVerify(m, pKey) + } + return fmt.Errorf("no crypto implementation available for %q: %s", algo, err) +} + +func (v *verifier) macVerify(m macer, pKey crypto.PublicKey) error { + key, ok := pKey.([]byte) + if !ok { + return fmt.Errorf("public key for MAC verifying must be of type []byte") + } + signature, err := v.sigStringFn(v.header, v.headers, v.created, v.expires) + if err != nil { + return err + } + actualMAC, err := base64.StdEncoding.DecodeString(v.signature) + if err != nil { + return err + } + ok, err = m.Equal([]byte(signature), actualMAC, key) + if err != nil { + return err + } else if !ok { + return fmt.Errorf("invalid http signature") + } + return nil +} + +func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey) error { + toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires) + if err != nil { + return err + } + signature, err := base64.StdEncoding.DecodeString(v.signature) + if err != nil { + return err + } + err = s.Verify(pKey, []byte(toHash), signature) + if err != nil { + return err + } + return nil +} + +func getSignatureScheme(h http.Header) (scheme SignatureScheme, val string, err error) { + s := h.Get(string(Signature)) + sigHasAll := strings.Contains(s, keyIdParameter) || + strings.Contains(s, headersParameter) || + strings.Contains(s, signatureParameter) + a := h.Get(string(Authorization)) + authHasAll := strings.Contains(a, keyIdParameter) || + strings.Contains(a, headersParameter) || + strings.Contains(a, signatureParameter) + if sigHasAll && authHasAll { + err = fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization) + return + } else if !sigHasAll && !authHasAll { + err = fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization) + return + } else if sigHasAll { + val = s + scheme = Signature + return + } else { // authHasAll + val = a + scheme = Authorization + return + } +} + +func getSignatureComponents(scheme SignatureScheme, s string) (kId, sig string, headers []string, created int64, expires int64, err error) { + if as := scheme.authScheme(); len(as) > 0 { + s = strings.TrimPrefix(s, as+prefixSeparater) + } + params := strings.Split(s, parameterSeparater) + for _, p := range params { + kv := strings.SplitN(p, parameterKVSeparater, 2) + if len(kv) != 2 { + err = fmt.Errorf("malformed http signature parameter: %v", kv) + return + } + k := kv[0] + v := strings.Trim(kv[1], parameterValueDelimiter) + switch k { + case keyIdParameter: + kId = v + case createdKey: + created, err = strconv.ParseInt(v, 10, 64) + if err != nil { + return + } + case expiresKey: + expires, err = strconv.ParseInt(v, 10, 64) + if err != nil { + return + } + case algorithmParameter: + // Deprecated, ignore + case headersParameter: + headers = strings.Split(v, headerParameterValueDelim) + case signatureParameter: + sig = v + default: + // Ignore unrecognized parameters + } + } + if len(kId) == 0 { + err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter) + } else if len(sig) == 0 { + err = fmt.Errorf("missing %q parameter in http signature", signatureParameter) + } else if len(headers) == 0 { // Optional + headers = defaultHeaders + } + return +} diff --git a/cli/vendor/github.com/davidmz/go-pageant/LICENSE.txt b/cli/vendor/github.com/davidmz/go-pageant/LICENSE.txt new file mode 100644 index 0000000..bbe93b8 --- /dev/null +++ b/cli/vendor/github.com/davidmz/go-pageant/LICENSE.txt @@ -0,0 +1,16 @@ +Copyright (c) 2014 David Mzareulyan + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/cli/vendor/github.com/davidmz/go-pageant/README.md b/cli/vendor/github.com/davidmz/go-pageant/README.md new file mode 100644 index 0000000..23a352e --- /dev/null +++ b/cli/vendor/github.com/davidmz/go-pageant/README.md @@ -0,0 +1,9 @@ +[![GoDoc](https://godoc.org/github.com/davidmz/go-pageant?status.svg)](https://godoc.org/github.com/davidmz/go-pageant) + +Package pageant provides an interface to PyTTY pageant.exe utility. + +This package is windows-only. + +See documentation on [GoDoc](http://godoc.org/github.com/davidmz/go-pageant) + +License: MIT \ No newline at end of file diff --git a/cli/vendor/github.com/davidmz/go-pageant/io_windows.go b/cli/vendor/github.com/davidmz/go-pageant/io_windows.go new file mode 100644 index 0000000..8f37551 --- /dev/null +++ b/cli/vendor/github.com/davidmz/go-pageant/io_windows.go @@ -0,0 +1,42 @@ +package pageant + +import ( + "io" + "sync" + + "golang.org/x/crypto/ssh/agent" +) + +// New returns new ssh-agent instance (see http://golang.org/x/crypto/ssh/agent) +func New() agent.Agent { + return agent.NewClient(&conn{}) +} + +type conn struct { + sync.Mutex + buf []byte +} + +func (c *conn) Write(p []byte) (int, error) { + c.Lock() + defer c.Unlock() + + resp, err := query(p) + if err != nil { + return 0, err + } + c.buf = append(c.buf, resp...) + return len(p), nil +} + +func (c *conn) Read(p []byte) (int, error) { + c.Lock() + defer c.Unlock() + + if len(c.buf) == 0 { + return 0, io.EOF + } + n := copy(p, c.buf) + c.buf = c.buf[n:] + return n, nil +} diff --git a/cli/vendor/github.com/davidmz/go-pageant/pageant_windows.go b/cli/vendor/github.com/davidmz/go-pageant/pageant_windows.go new file mode 100644 index 0000000..520e8f4 --- /dev/null +++ b/cli/vendor/github.com/davidmz/go-pageant/pageant_windows.go @@ -0,0 +1,134 @@ +// Package pageant provides an interface to PyTTY pageant.exe utility. +// This package is windows-only +package pageant + +// see https://github.com/Yasushi/putty/blob/master/windows/winpgntc.c#L155 +// see https://github.com/paramiko/paramiko/blob/master/paramiko/win_pageant.py + +import ( + "encoding/binary" + "errors" + "fmt" + "sync" + "syscall" + "unsafe" +) + +// MaxMessageLen defines maximum size of message can be sent to pageant +const MaxMessageLen = 8192 + +var ( + // ErrPageantNotFound returns when pageant process not found + ErrPageantNotFound = errors.New("pageant process not found") + // ErrSendMessage returns when message to pageant cannt be sent + ErrSendMessage = errors.New("error sending message") + + // ErrMessageTooLong returns when message is too long (see MaxMessageLen) + ErrMessageTooLong = errors.New("message too long") + // ErrInvalidMessageFormat returns when message have invalid fomat + ErrInvalidMessageFormat = errors.New("invalid message format") + // ErrResponseTooLong returns when response from pageant is too long + ErrResponseTooLong = errors.New("response too long") +) + +///////////////////////// + +const ( + agentCopydataID = 0x804e50ba + wmCopydata = 74 +) + +type copyData struct { + dwData uintptr + cbData uint32 + lpData unsafe.Pointer +} + +var ( + lock sync.Mutex + + winFindWindow = winAPI("user32.dll", "FindWindowW") + winGetCurrentThreadID = winAPI("kernel32.dll", "GetCurrentThreadId") + winSendMessage = winAPI("user32.dll", "SendMessageW") +) + +func winAPI(dllName, funcName string) func(...uintptr) (uintptr, uintptr, error) { + proc := syscall.MustLoadDLL(dllName).MustFindProc(funcName) + return func(a ...uintptr) (uintptr, uintptr, error) { return proc.Call(a...) } +} + +// Available returns true if Pageant is started +func Available() bool { return pageantWindow() != 0 } + +// Query sends message msg to Pageant and returns response or error. +// 'msg' is raw agent request with length prefix +// Response is raw agent response with length prefix +func query(msg []byte) ([]byte, error) { + if len(msg) > MaxMessageLen { + return nil, ErrMessageTooLong + } + + msgLen := binary.BigEndian.Uint32(msg[:4]) + if len(msg) != int(msgLen)+4 { + return nil, ErrInvalidMessageFormat + } + + lock.Lock() + defer lock.Unlock() + + paWin := pageantWindow() + + if paWin == 0 { + return nil, ErrPageantNotFound + } + + thID, _, _ := winGetCurrentThreadID() + mapName := fmt.Sprintf("PageantRequest%08x", thID) + pMapName, _ := syscall.UTF16PtrFromString(mapName) + + mmap, err := syscall.CreateFileMapping(syscall.InvalidHandle, nil, syscall.PAGE_READWRITE, 0, MaxMessageLen+4, pMapName) + if err != nil { + return nil, err + } + defer syscall.CloseHandle(mmap) + + ptr, err := syscall.MapViewOfFile(mmap, syscall.FILE_MAP_WRITE, 0, 0, 0) + if err != nil { + return nil, err + } + defer syscall.UnmapViewOfFile(ptr) + + mmSlice := (*(*[MaxMessageLen]byte)(unsafe.Pointer(ptr)))[:] + + copy(mmSlice, msg) + + mapNameBytesZ := append([]byte(mapName), 0) + + cds := copyData{ + dwData: agentCopydataID, + cbData: uint32(len(mapNameBytesZ)), + lpData: unsafe.Pointer(&(mapNameBytesZ[0])), + } + + resp, _, _ := winSendMessage(paWin, wmCopydata, 0, uintptr(unsafe.Pointer(&cds))) + + if resp == 0 { + return nil, ErrSendMessage + } + + respLen := binary.BigEndian.Uint32(mmSlice[:4]) + if respLen > MaxMessageLen-4 { + return nil, ErrResponseTooLong + } + + respData := make([]byte, respLen+4) + copy(respData, mmSlice) + + return respData, nil +} + +func pageantWindow() uintptr { + nameP, _ := syscall.UTF16PtrFromString("Pageant") + h, _, _ := winFindWindow(uintptr(unsafe.Pointer(nameP)), uintptr(unsafe.Pointer(nameP))) + return h +} diff --git a/cli/vendor/github.com/go-fed/httpsig/LICENSE b/cli/vendor/github.com/go-fed/httpsig/LICENSE new file mode 100644 index 0000000..a9e8aef --- /dev/null +++ b/cli/vendor/github.com/go-fed/httpsig/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, go-fed +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cli/vendor/github.com/go-fed/httpsig/README.md b/cli/vendor/github.com/go-fed/httpsig/README.md new file mode 100644 index 0000000..3b83854 --- /dev/null +++ b/cli/vendor/github.com/go-fed/httpsig/README.md @@ -0,0 +1,94 @@ +# httpsig + +`go get github.com/go-fed/httpsig` + +Implementation of [HTTP Signatures](https://tools.ietf.org/html/draft-cavage-http-signatures). + +Supports many different combinations of MAC, HMAC signing of hash, or RSA +signing of hash schemes. Its goals are: + +* Have a very simple interface for signing and validating +* Support a variety of signing algorithms and combinations +* Support setting either headers (`Authorization` or `Signature`) +* Remaining flexible with headers included in the signing string +* Support both HTTP requests and responses +* Explicitly not support known-cryptographically weak algorithms +* Support automatic signing and validating Digest headers + +## How to use + +`import "github.com/go-fed/httpsig"` + +### Signing + +Signing a request or response requires creating a new `Signer` and using it: + +``` +func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error { + prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256} + digestAlgorithm := DigestSha256 + // The "Date" and "Digest" headers must already be set on r, as well as r.URL. + headersToSign := []string{httpsig.RequestTarget, "date", "digest"} + signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature) + if err != nil { + return err + } + // To sign the digest, we need to give the signer a copy of the body... + // ...but it is optional, no digest will be signed if given "nil" + body := ... + // If r were a http.ResponseWriter, call SignResponse instead. + return signer.SignRequest(privateKey, pubKeyId, r, body) +} +``` + +`Signer`s are not safe for concurrent use by goroutines, so be sure to guard +access: + +``` +type server struct { + signer httpsig.Signer + mu *sync.Mutex +} + +func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) { + privateKey := ... + pubKeyId := ... + // Set headers and such on w + s.mu.Lock() + defer s.mu.Unlock() + // To sign the digest, we need to give the signer a copy of the response body... + // ...but it is optional, no digest will be signed if given "nil" + body := ... + err := s.signer.SignResponse(privateKey, pubKeyId, w, body) + if err != nil { + ... + } + ... +} +``` + +The `pubKeyId` will be used at verification time. + +### Verifying + +Verifying requires an application to use the `pubKeyId` to both retrieve the key +needed for verification as well as determine the algorithm to use. Use a +`Verifier`: + +``` +func verify(r *http.Request) error { + verifier, err := httpsig.NewVerifier(r) + if err != nil { + return err + } + pubKeyId := verifier.KeyId() + var algo httpsig.Algorithm = ... + var pubKey crypto.PublicKey = ... + // The verifier will verify the Digest in addition to the HTTP signature + return verifier.Verify(pubKey, algo) +} +``` + +`Verifier`s are not safe for concurrent use by goroutines, but since they are +constructed on a per-request or per-response basis it should not be a common +restriction. diff --git a/cli/vendor/github.com/go-fed/httpsig/algorithms.go b/cli/vendor/github.com/go-fed/httpsig/algorithms.go new file mode 100644 index 0000000..9595941 --- /dev/null +++ b/cli/vendor/github.com/go-fed/httpsig/algorithms.go @@ -0,0 +1,532 @@ +package httpsig + +import ( + "crypto" + "crypto/ecdsa" + "crypto/hmac" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "crypto/subtle" // Use should trigger great care + "encoding/asn1" + "errors" + "fmt" + "hash" + "io" + "math/big" + "strings" + + "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/blake2s" + "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/ripemd160" + "golang.org/x/crypto/sha3" + "golang.org/x/crypto/ssh" +) + +const ( + hmacPrefix = "hmac" + rsaPrefix = "rsa" + sshPrefix = "ssh" + ecdsaPrefix = "ecdsa" + ed25519Prefix = "ed25519" + md4String = "md4" + md5String = "md5" + sha1String = "sha1" + sha224String = "sha224" + sha256String = "sha256" + sha384String = "sha384" + sha512String = "sha512" + md5sha1String = "md5sha1" + ripemd160String = "ripemd160" + sha3_224String = "sha3-224" + sha3_256String = "sha3-256" + sha3_384String = "sha3-384" + sha3_512String = "sha3-512" + sha512_224String = "sha512-224" + sha512_256String = "sha512-256" + blake2s_256String = "blake2s-256" + blake2b_256String = "blake2b-256" + blake2b_384String = "blake2b-384" + blake2b_512String = "blake2b-512" +) + +var blake2Algorithms = map[crypto.Hash]bool{ + crypto.BLAKE2s_256: true, + crypto.BLAKE2b_256: true, + crypto.BLAKE2b_384: true, + crypto.BLAKE2b_512: true, +} + +var hashToDef = map[crypto.Hash]struct { + name string + new func(key []byte) (hash.Hash, error) // Only MACers will accept a key +}{ + // Which standard names these? + // The spec lists the following as a canonical reference, which is dead: + // http://www.iana.org/assignments/signature-algorithms + // + // Note that the forbidden hashes have an invalid 'new' function. + crypto.MD4: {md4String, func(key []byte) (hash.Hash, error) { return nil, nil }}, + crypto.MD5: {md5String, func(key []byte) (hash.Hash, error) { return nil, nil }}, + // Temporarily enable SHA1 because of issue https://github.com/golang/go/issues/37278 + crypto.SHA1: {sha1String, func(key []byte) (hash.Hash, error) { return sha1.New(), nil }}, + crypto.SHA224: {sha224String, func(key []byte) (hash.Hash, error) { return sha256.New224(), nil }}, + crypto.SHA256: {sha256String, func(key []byte) (hash.Hash, error) { return sha256.New(), nil }}, + crypto.SHA384: {sha384String, func(key []byte) (hash.Hash, error) { return sha512.New384(), nil }}, + crypto.SHA512: {sha512String, func(key []byte) (hash.Hash, error) { return sha512.New(), nil }}, + crypto.MD5SHA1: {md5sha1String, func(key []byte) (hash.Hash, error) { return nil, nil }}, + crypto.RIPEMD160: {ripemd160String, func(key []byte) (hash.Hash, error) { return ripemd160.New(), nil }}, + crypto.SHA3_224: {sha3_224String, func(key []byte) (hash.Hash, error) { return sha3.New224(), nil }}, + crypto.SHA3_256: {sha3_256String, func(key []byte) (hash.Hash, error) { return sha3.New256(), nil }}, + crypto.SHA3_384: {sha3_384String, func(key []byte) (hash.Hash, error) { return sha3.New384(), nil }}, + crypto.SHA3_512: {sha3_512String, func(key []byte) (hash.Hash, error) { return sha3.New512(), nil }}, + crypto.SHA512_224: {sha512_224String, func(key []byte) (hash.Hash, error) { return sha512.New512_224(), nil }}, + crypto.SHA512_256: {sha512_256String, func(key []byte) (hash.Hash, error) { return sha512.New512_256(), nil }}, + crypto.BLAKE2s_256: {blake2s_256String, func(key []byte) (hash.Hash, error) { return blake2s.New256(key) }}, + crypto.BLAKE2b_256: {blake2b_256String, func(key []byte) (hash.Hash, error) { return blake2b.New256(key) }}, + crypto.BLAKE2b_384: {blake2b_384String, func(key []byte) (hash.Hash, error) { return blake2b.New384(key) }}, + crypto.BLAKE2b_512: {blake2b_512String, func(key []byte) (hash.Hash, error) { return blake2b.New512(key) }}, +} + +var stringToHash map[string]crypto.Hash + +const ( + defaultAlgorithm = RSA_SHA256 + defaultAlgorithmHashing = sha256String +) + +func init() { + stringToHash = make(map[string]crypto.Hash, len(hashToDef)) + for k, v := range hashToDef { + stringToHash[v.name] = k + } + // This should guarantee that at runtime the defaultAlgorithm will not + // result in errors when fetching a macer or signer (see algorithms.go) + if ok, err := isAvailable(string(defaultAlgorithmHashing)); err != nil { + panic(err) + } else if !ok { + panic(fmt.Sprintf("the default httpsig algorithm is unavailable: %q", defaultAlgorithm)) + } +} + +func isForbiddenHash(h crypto.Hash) bool { + switch h { + // Not actually cryptographically secure + case crypto.MD4: + fallthrough + case crypto.MD5: + fallthrough + case crypto.MD5SHA1: // shorthand for crypto/tls, not actually implemented + return true + } + // Still cryptographically secure + return false +} + +// signer is an internally public type. +type signer interface { + Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) + Verify(pub crypto.PublicKey, toHash, signature []byte) error + String() string +} + +// macer is an internally public type. +type macer interface { + Sign(sig, key []byte) ([]byte, error) + Equal(sig, actualMAC, key []byte) (bool, error) + String() string +} + +var _ macer = &hmacAlgorithm{} + +type hmacAlgorithm struct { + fn func(key []byte) (hash.Hash, error) + kind crypto.Hash +} + +func (h *hmacAlgorithm) Sign(sig, key []byte) ([]byte, error) { + hs, err := h.fn(key) + if err = setSig(hs, sig); err != nil { + return nil, err + } + return hs.Sum(nil), nil +} + +func (h *hmacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { + hs, err := h.fn(key) + if err != nil { + return false, err + } + defer hs.Reset() + err = setSig(hs, sig) + if err != nil { + return false, err + } + expected := hs.Sum(nil) + return hmac.Equal(actualMAC, expected), nil +} + +func (h *hmacAlgorithm) String() string { + return fmt.Sprintf("%s-%s", hmacPrefix, hashToDef[h.kind].name) +} + +var _ signer = &rsaAlgorithm{} + +type rsaAlgorithm struct { + hash.Hash + kind crypto.Hash + sshSigner ssh.Signer +} + +func (r *rsaAlgorithm) setSig(b []byte) error { + n, err := r.Write(b) + if err != nil { + r.Reset() + return err + } else if n != len(b) { + r.Reset() + return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) + } + return nil +} + +func (r *rsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { + if r.sshSigner != nil { + sshsig, err := r.sshSigner.Sign(rand, sig) + if err != nil { + return nil, err + } + + return sshsig.Blob, nil + } + defer r.Reset() + + if err := r.setSig(sig); err != nil { + return nil, err + } + rsaK, ok := p.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("crypto.PrivateKey is not *rsa.PrivateKey") + } + return rsa.SignPKCS1v15(rand, rsaK, r.kind, r.Sum(nil)) +} + +func (r *rsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { + defer r.Reset() + rsaK, ok := pub.(*rsa.PublicKey) + if !ok { + return errors.New("crypto.PublicKey is not *rsa.PublicKey") + } + if err := r.setSig(toHash); err != nil { + return err + } + return rsa.VerifyPKCS1v15(rsaK, r.kind, r.Sum(nil), signature) +} + +func (r *rsaAlgorithm) String() string { + return fmt.Sprintf("%s-%s", rsaPrefix, hashToDef[r.kind].name) +} + +var _ signer = &ed25519Algorithm{} + +type ed25519Algorithm struct { + sshSigner ssh.Signer +} + +func (r *ed25519Algorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { + if r.sshSigner != nil { + sshsig, err := r.sshSigner.Sign(rand, sig) + if err != nil { + return nil, err + } + + return sshsig.Blob, nil + } + ed25519K, ok := p.(ed25519.PrivateKey) + if !ok { + return nil, errors.New("crypto.PrivateKey is not ed25519.PrivateKey") + } + return ed25519.Sign(ed25519K, sig), nil +} + +func (r *ed25519Algorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { + ed25519K, ok := pub.(ed25519.PublicKey) + if !ok { + return errors.New("crypto.PublicKey is not ed25519.PublicKey") + } + + if ed25519.Verify(ed25519K, toHash, signature) { + return nil + } + + return errors.New("ed25519 verify failed") +} + +func (r *ed25519Algorithm) String() string { + return fmt.Sprintf("%s", ed25519Prefix) +} + +var _ signer = &ecdsaAlgorithm{} + +type ecdsaAlgorithm struct { + hash.Hash + kind crypto.Hash +} + +func (r *ecdsaAlgorithm) setSig(b []byte) error { + n, err := r.Write(b) + if err != nil { + r.Reset() + return err + } else if n != len(b) { + r.Reset() + return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) + } + return nil +} + +type ECDSASignature struct { + R, S *big.Int +} + +func (r *ecdsaAlgorithm) Sign(rand io.Reader, p crypto.PrivateKey, sig []byte) ([]byte, error) { + defer r.Reset() + if err := r.setSig(sig); err != nil { + return nil, err + } + ecdsaK, ok := p.(*ecdsa.PrivateKey) + if !ok { + return nil, errors.New("crypto.PrivateKey is not *ecdsa.PrivateKey") + } + R, S, err := ecdsa.Sign(rand, ecdsaK, r.Sum(nil)) + if err != nil { + return nil, err + } + + signature := ECDSASignature{R: R, S: S} + bytes, err := asn1.Marshal(signature) + + return bytes, err +} + +func (r *ecdsaAlgorithm) Verify(pub crypto.PublicKey, toHash, signature []byte) error { + defer r.Reset() + ecdsaK, ok := pub.(*ecdsa.PublicKey) + if !ok { + return errors.New("crypto.PublicKey is not *ecdsa.PublicKey") + } + if err := r.setSig(toHash); err != nil { + return err + } + + sig := new(ECDSASignature) + _, err := asn1.Unmarshal(signature, sig) + if err != nil { + return err + } + + if ecdsa.Verify(ecdsaK, r.Sum(nil), sig.R, sig.S) { + return nil + } else { + return errors.New("Invalid signature") + } +} + +func (r *ecdsaAlgorithm) String() string { + return fmt.Sprintf("%s-%s", ecdsaPrefix, hashToDef[r.kind].name) +} + +var _ macer = &blakeMacAlgorithm{} + +type blakeMacAlgorithm struct { + fn func(key []byte) (hash.Hash, error) + kind crypto.Hash +} + +func (r *blakeMacAlgorithm) Sign(sig, key []byte) ([]byte, error) { + hs, err := r.fn(key) + if err != nil { + return nil, err + } + if err = setSig(hs, sig); err != nil { + return nil, err + } + return hs.Sum(nil), nil +} + +func (r *blakeMacAlgorithm) Equal(sig, actualMAC, key []byte) (bool, error) { + hs, err := r.fn(key) + if err != nil { + return false, err + } + defer hs.Reset() + err = setSig(hs, sig) + if err != nil { + return false, err + } + expected := hs.Sum(nil) + return subtle.ConstantTimeCompare(actualMAC, expected) == 1, nil +} + +func (r *blakeMacAlgorithm) String() string { + return fmt.Sprintf("%s", hashToDef[r.kind].name) +} + +func setSig(a hash.Hash, b []byte) error { + n, err := a.Write(b) + if err != nil { + a.Reset() + return err + } else if n != len(b) { + a.Reset() + return fmt.Errorf("could only write %d of %d bytes of signature to hash", n, len(b)) + } + return nil +} + +// IsSupportedHttpSigAlgorithm returns true if the string is supported by this +// library, is not a hash known to be weak, and is supported by the hardware. +func IsSupportedHttpSigAlgorithm(algo string) bool { + a, err := isAvailable(algo) + return a && err == nil +} + +// isAvailable is an internally public function +func isAvailable(algo string) (bool, error) { + c, ok := stringToHash[algo] + if !ok { + return false, fmt.Errorf("no match for %q", algo) + } + if isForbiddenHash(c) { + return false, fmt.Errorf("forbidden hash type in %q", algo) + } + return c.Available(), nil +} + +func newAlgorithmConstructor(algo string) (fn func(k []byte) (hash.Hash, error), c crypto.Hash, e error) { + ok := false + c, ok = stringToHash[algo] + if !ok { + e = fmt.Errorf("no match for %q", algo) + return + } + if isForbiddenHash(c) { + e = fmt.Errorf("forbidden hash type in %q", algo) + return + } + algoDef, ok := hashToDef[c] + if !ok { + e = fmt.Errorf("have crypto.Hash %v but no definition", c) + return + } + fn = func(key []byte) (hash.Hash, error) { + h, err := algoDef.new(key) + if err != nil { + return nil, err + } + return h, nil + } + return +} + +func newAlgorithm(algo string, key []byte) (hash.Hash, crypto.Hash, error) { + fn, c, err := newAlgorithmConstructor(algo) + if err != nil { + return nil, c, err + } + h, err := fn(key) + return h, c, err +} + +func signerFromSSHSigner(sshSigner ssh.Signer, s string) (signer, error) { + switch { + case strings.HasPrefix(s, rsaPrefix): + return &rsaAlgorithm{ + sshSigner: sshSigner, + }, nil + case strings.HasPrefix(s, ed25519Prefix): + return &ed25519Algorithm{ + sshSigner: sshSigner, + }, nil + default: + return nil, fmt.Errorf("no signer matching %q", s) + } +} + +// signerFromString is an internally public method constructor +func signerFromString(s string) (signer, error) { + s = strings.ToLower(s) + isEcdsa := false + isEd25519 := false + var algo string = "" + if strings.HasPrefix(s, ecdsaPrefix) { + algo = strings.TrimPrefix(s, ecdsaPrefix+"-") + isEcdsa = true + } else if strings.HasPrefix(s, rsaPrefix) { + algo = strings.TrimPrefix(s, rsaPrefix+"-") + } else if strings.HasPrefix(s, ed25519Prefix) { + isEd25519 = true + algo = "sha512" + } else { + return nil, fmt.Errorf("no signer matching %q", s) + } + hash, cHash, err := newAlgorithm(algo, nil) + if err != nil { + return nil, err + } + if isEd25519 { + return &ed25519Algorithm{}, nil + } + if isEcdsa { + return &ecdsaAlgorithm{ + Hash: hash, + kind: cHash, + }, nil + } + return &rsaAlgorithm{ + Hash: hash, + kind: cHash, + }, nil +} + +// macerFromString is an internally public method constructor +func macerFromString(s string) (macer, error) { + s = strings.ToLower(s) + if strings.HasPrefix(s, hmacPrefix) { + algo := strings.TrimPrefix(s, hmacPrefix+"-") + hashFn, cHash, err := newAlgorithmConstructor(algo) + if err != nil { + return nil, err + } + // Ensure below does not panic + _, err = hashFn(nil) + if err != nil { + return nil, err + } + return &hmacAlgorithm{ + fn: func(key []byte) (hash.Hash, error) { + return hmac.New(func() hash.Hash { + h, e := hashFn(nil) + if e != nil { + panic(e) + } + return h + }, key), nil + }, + kind: cHash, + }, nil + } else if bl, ok := stringToHash[s]; ok && blake2Algorithms[bl] { + hashFn, cHash, err := newAlgorithmConstructor(s) + if err != nil { + return nil, err + } + return &blakeMacAlgorithm{ + fn: hashFn, + kind: cHash, + }, nil + } else { + return nil, fmt.Errorf("no MACer matching %q", s) + } +} diff --git a/cli/vendor/github.com/go-fed/httpsig/digest.go b/cli/vendor/github.com/go-fed/httpsig/digest.go new file mode 100644 index 0000000..bf9e3a9 --- /dev/null +++ b/cli/vendor/github.com/go-fed/httpsig/digest.go @@ -0,0 +1,120 @@ +package httpsig + +import ( + "bytes" + "crypto" + "encoding/base64" + "fmt" + "hash" + "net/http" + "strings" +) + +type DigestAlgorithm string + +const ( + DigestSha256 DigestAlgorithm = "SHA-256" + DigestSha512 = "SHA-512" +) + +var digestToDef = map[DigestAlgorithm]crypto.Hash{ + DigestSha256: crypto.SHA256, + DigestSha512: crypto.SHA512, +} + +// IsSupportedDigestAlgorithm returns true if hte string is supported by this +// library, is not a hash known to be weak, and is supported by the hardware. +func IsSupportedDigestAlgorithm(algo string) bool { + uc := DigestAlgorithm(strings.ToUpper(algo)) + c, ok := digestToDef[uc] + return ok && c.Available() +} + +func getHash(alg DigestAlgorithm) (h hash.Hash, toUse DigestAlgorithm, err error) { + upper := DigestAlgorithm(strings.ToUpper(string(alg))) + c, ok := digestToDef[upper] + if !ok { + err = fmt.Errorf("unknown or unsupported Digest algorithm: %s", alg) + } else if !c.Available() { + err = fmt.Errorf("unavailable Digest algorithm: %s", alg) + } else { + h = c.New() + toUse = upper + } + return +} + +const ( + digestHeader = "Digest" + digestDelim = "=" +) + +func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) { + _, ok := r.Header[digestHeader] + if ok { + err = fmt.Errorf("cannot add Digest: Digest is already set") + return + } + var h hash.Hash + var a DigestAlgorithm + h, a, err = getHash(algo) + if err != nil { + return + } + h.Write(b) + sum := h.Sum(nil) + r.Header.Add(digestHeader, + fmt.Sprintf("%s%s%s", + a, + digestDelim, + base64.StdEncoding.EncodeToString(sum[:]))) + return +} + +func addDigestResponse(r http.ResponseWriter, algo DigestAlgorithm, b []byte) (err error) { + _, ok := r.Header()[digestHeader] + if ok { + err = fmt.Errorf("cannot add Digest: Digest is already set") + return + } + var h hash.Hash + var a DigestAlgorithm + h, a, err = getHash(algo) + if err != nil { + return + } + h.Write(b) + sum := h.Sum(nil) + r.Header().Add(digestHeader, + fmt.Sprintf("%s%s%s", + a, + digestDelim, + base64.StdEncoding.EncodeToString(sum[:]))) + return +} + +func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) { + d := r.Header.Get(digestHeader) + if len(d) == 0 { + err = fmt.Errorf("cannot verify Digest: request has no Digest header") + return + } + elem := strings.SplitN(d, digestDelim, 2) + if len(elem) != 2 { + err = fmt.Errorf("cannot verify Digest: malformed Digest: %s", d) + return + } + var h hash.Hash + h, _, err = getHash(DigestAlgorithm(elem[0])) + if err != nil { + return + } + h.Write(body.Bytes()) + sum := h.Sum(nil) + encSum := base64.StdEncoding.EncodeToString(sum[:]) + if encSum != elem[1] { + err = fmt.Errorf("cannot verify Digest: header Digest does not match the digest of the request body") + return + } + return +} diff --git a/cli/vendor/github.com/go-fed/httpsig/httpsig.go b/cli/vendor/github.com/go-fed/httpsig/httpsig.go new file mode 100644 index 0000000..1a6b954 --- /dev/null +++ b/cli/vendor/github.com/go-fed/httpsig/httpsig.go @@ -0,0 +1,361 @@ +// Implements HTTP request and response signing and verification. Supports the +// major MAC and asymmetric key signature algorithms. It has several safety +// restrictions: One, none of the widely known non-cryptographically safe +// algorithms are permitted; Two, the RSA SHA256 algorithms must be available in +// the binary (and it should, barring export restrictions); Finally, the library +// assumes either the 'Authorizationn' or 'Signature' headers are to be set (but +// not both). +package httpsig + +import ( + "crypto" + "fmt" + "net/http" + "strings" + "time" + + "golang.org/x/crypto/ssh" +) + +// Algorithm specifies a cryptography secure algorithm for signing HTTP requests +// and responses. +type Algorithm string + +const ( + // MAC-based algoirthms. + HMAC_SHA224 Algorithm = hmacPrefix + "-" + sha224String + HMAC_SHA256 Algorithm = hmacPrefix + "-" + sha256String + HMAC_SHA384 Algorithm = hmacPrefix + "-" + sha384String + HMAC_SHA512 Algorithm = hmacPrefix + "-" + sha512String + HMAC_RIPEMD160 Algorithm = hmacPrefix + "-" + ripemd160String + HMAC_SHA3_224 Algorithm = hmacPrefix + "-" + sha3_224String + HMAC_SHA3_256 Algorithm = hmacPrefix + "-" + sha3_256String + HMAC_SHA3_384 Algorithm = hmacPrefix + "-" + sha3_384String + HMAC_SHA3_512 Algorithm = hmacPrefix + "-" + sha3_512String + HMAC_SHA512_224 Algorithm = hmacPrefix + "-" + sha512_224String + HMAC_SHA512_256 Algorithm = hmacPrefix + "-" + sha512_256String + HMAC_BLAKE2S_256 Algorithm = hmacPrefix + "-" + blake2s_256String + HMAC_BLAKE2B_256 Algorithm = hmacPrefix + "-" + blake2b_256String + HMAC_BLAKE2B_384 Algorithm = hmacPrefix + "-" + blake2b_384String + HMAC_BLAKE2B_512 Algorithm = hmacPrefix + "-" + blake2b_512String + BLAKE2S_256 Algorithm = blake2s_256String + BLAKE2B_256 Algorithm = blake2b_256String + BLAKE2B_384 Algorithm = blake2b_384String + BLAKE2B_512 Algorithm = blake2b_512String + // RSA-based algorithms. + RSA_SHA1 Algorithm = rsaPrefix + "-" + sha1String + RSA_SHA224 Algorithm = rsaPrefix + "-" + sha224String + // RSA_SHA256 is the default algorithm. + RSA_SHA256 Algorithm = rsaPrefix + "-" + sha256String + RSA_SHA384 Algorithm = rsaPrefix + "-" + sha384String + RSA_SHA512 Algorithm = rsaPrefix + "-" + sha512String + RSA_RIPEMD160 Algorithm = rsaPrefix + "-" + ripemd160String + // ECDSA algorithms + ECDSA_SHA224 Algorithm = ecdsaPrefix + "-" + sha224String + ECDSA_SHA256 Algorithm = ecdsaPrefix + "-" + sha256String + ECDSA_SHA384 Algorithm = ecdsaPrefix + "-" + sha384String + ECDSA_SHA512 Algorithm = ecdsaPrefix + "-" + sha512String + ECDSA_RIPEMD160 Algorithm = ecdsaPrefix + "-" + ripemd160String + // ED25519 algorithms + // can only be SHA512 + ED25519 Algorithm = ed25519Prefix + + // Just because you can glue things together, doesn't mean they will + // work. The following options are not supported. + rsa_SHA3_224 Algorithm = rsaPrefix + "-" + sha3_224String + rsa_SHA3_256 Algorithm = rsaPrefix + "-" + sha3_256String + rsa_SHA3_384 Algorithm = rsaPrefix + "-" + sha3_384String + rsa_SHA3_512 Algorithm = rsaPrefix + "-" + sha3_512String + rsa_SHA512_224 Algorithm = rsaPrefix + "-" + sha512_224String + rsa_SHA512_256 Algorithm = rsaPrefix + "-" + sha512_256String + rsa_BLAKE2S_256 Algorithm = rsaPrefix + "-" + blake2s_256String + rsa_BLAKE2B_256 Algorithm = rsaPrefix + "-" + blake2b_256String + rsa_BLAKE2B_384 Algorithm = rsaPrefix + "-" + blake2b_384String + rsa_BLAKE2B_512 Algorithm = rsaPrefix + "-" + blake2b_512String +) + +// HTTP Signatures can be applied to different HTTP headers, depending on the +// expected application behavior. +type SignatureScheme string + +const ( + // Signature will place the HTTP Signature into the 'Signature' HTTP + // header. + Signature SignatureScheme = "Signature" + // Authorization will place the HTTP Signature into the 'Authorization' + // HTTP header. + Authorization SignatureScheme = "Authorization" +) + +const ( + // The HTTP Signatures specification uses the "Signature" auth-scheme + // for the Authorization header. This is coincidentally named, but not + // semantically the same, as the "Signature" HTTP header value. + signatureAuthScheme = "Signature" +) + +// There are subtle differences to the values in the header. The Authorization +// header has an 'auth-scheme' value that must be prefixed to the rest of the +// key and values. +func (s SignatureScheme) authScheme() string { + switch s { + case Authorization: + return signatureAuthScheme + default: + return "" + } +} + +// Signers will sign HTTP requests or responses based on the algorithms and +// headers selected at creation time. +// +// Signers are not safe to use between multiple goroutines. +// +// Note that signatures do set the deprecated 'algorithm' parameter for +// backwards compatibility. +type Signer interface { + // SignRequest signs the request using a private key. The public key id + // is used by the HTTP server to identify which key to use to verify the + // signature. + // + // If the Signer was created using a MAC based algorithm, then the key + // is expected to be of type []byte. If the Signer was created using an + // RSA based algorithm, then the private key is expected to be of type + // *rsa.PrivateKey. + // + // A Digest (RFC 3230) will be added to the request. The body provided + // must match the body used in the request, and is allowed to be nil. + // The Digest ensures the request body is not tampered with in flight, + // and if the signer is created to also sign the "Digest" header, the + // HTTP Signature will then ensure both the Digest and body are not both + // modified to maliciously represent different content. + SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error + // SignResponse signs the response using a private key. The public key + // id is used by the HTTP client to identify which key to use to verify + // the signature. + // + // If the Signer was created using a MAC based algorithm, then the key + // is expected to be of type []byte. If the Signer was created using an + // RSA based algorithm, then the private key is expected to be of type + // *rsa.PrivateKey. + // + // A Digest (RFC 3230) will be added to the response. The body provided + // must match the body written in the response, and is allowed to be + // nil. The Digest ensures the response body is not tampered with in + // flight, and if the signer is created to also sign the "Digest" + // header, the HTTP Signature will then ensure both the Digest and body + // are not both modified to maliciously represent different content. + SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error +} + +// NewSigner creates a new Signer with the provided algorithm preferences to +// make HTTP signatures. Only the first available algorithm will be used, which +// is returned by this function along with the Signer. If none of the preferred +// algorithms were available, then the default algorithm is used. The headers +// specified will be included into the HTTP signatures. +// +// The Digest will also be calculated on a request's body using the provided +// digest algorithm, if "Digest" is one of the headers listed. +// +// The provided scheme determines which header is populated with the HTTP +// Signature. +// +// An error is returned if an unknown or a known cryptographically insecure +// Algorithm is provided. +func NewSigner(prefs []Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, Algorithm, error) { + for _, pref := range prefs { + s, err := newSigner(pref, dAlgo, headers, scheme, expiresIn) + if err != nil { + continue + } + return s, pref, err + } + s, err := newSigner(defaultAlgorithm, dAlgo, headers, scheme, expiresIn) + return s, defaultAlgorithm, err +} + +// Signers will sign HTTP requests or responses based on the algorithms and +// headers selected at creation time. +// +// Signers are not safe to use between multiple goroutines. +// +// Note that signatures do set the deprecated 'algorithm' parameter for +// backwards compatibility. +type SSHSigner interface { + // SignRequest signs the request using ssh.Signer. + // The public key id is used by the HTTP server to identify which key to use + // to verify the signature. + // + // A Digest (RFC 3230) will be added to the request. The body provided + // must match the body used in the request, and is allowed to be nil. + // The Digest ensures the request body is not tampered with in flight, + // and if the signer is created to also sign the "Digest" header, the + // HTTP Signature will then ensure both the Digest and body are not both + // modified to maliciously represent different content. + SignRequest(pubKeyId string, r *http.Request, body []byte) error + // SignResponse signs the response using ssh.Signer. The public key + // id is used by the HTTP client to identify which key to use to verify + // the signature. + // + // A Digest (RFC 3230) will be added to the response. The body provided + // must match the body written in the response, and is allowed to be + // nil. The Digest ensures the response body is not tampered with in + // flight, and if the signer is created to also sign the "Digest" + // header, the HTTP Signature will then ensure both the Digest and body + // are not both modified to maliciously represent different content. + SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error +} + +// NewwSSHSigner creates a new Signer using the specified ssh.Signer +// At the moment only ed25519 ssh keys are supported. +// The headers specified will be included into the HTTP signatures. +// +// The Digest will also be calculated on a request's body using the provided +// digest algorithm, if "Digest" is one of the headers listed. +// +// The provided scheme determines which header is populated with the HTTP +// Signature. +func NewSSHSigner(s ssh.Signer, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, Algorithm, error) { + sshAlgo := getSSHAlgorithm(s.PublicKey().Type()) + if sshAlgo == "" { + return nil, "", fmt.Errorf("key type: %s not supported yet.", s.PublicKey().Type()) + } + + signer, err := newSSHSigner(s, sshAlgo, dAlgo, headers, scheme, expiresIn) + if err != nil { + return nil, "", err + } + + return signer, sshAlgo, nil +} + +func getSSHAlgorithm(pkType string) Algorithm { + switch { + case strings.HasPrefix(pkType, sshPrefix+"-"+ed25519Prefix): + return ED25519 + case strings.HasPrefix(pkType, sshPrefix+"-"+rsaPrefix): + return RSA_SHA1 + } + + return "" +} + +// Verifier verifies HTTP Signatures. +// +// It will determine which of the supported headers has the parameters +// that define the signature. +// +// Verifiers are not safe to use between multiple goroutines. +// +// Note that verification ignores the deprecated 'algorithm' parameter. +type Verifier interface { + // KeyId gets the public key id that the signature is signed with. + // + // Note that the application is expected to determine the algorithm + // used based on metadata or out-of-band information for this key id. + KeyId() string + // Verify accepts the public key specified by KeyId and returns an + // error if verification fails or if the signature is malformed. The + // algorithm must be the one used to create the signature in order to + // pass verification. The algorithm is determined based on metadata or + // out-of-band information for the key id. + // + // If the signature was created using a MAC based algorithm, then the + // key is expected to be of type []byte. If the signature was created + // using an RSA based algorithm, then the public key is expected to be + // of type *rsa.PublicKey. + Verify(pKey crypto.PublicKey, algo Algorithm) error +} + +const ( + // host is treated specially because golang may not include it in the + // request header map on the server side of a request. + hostHeader = "Host" +) + +// NewVerifier verifies the given request. It returns an error if the HTTP +// Signature parameters are not present in any headers, are present in more than +// one header, are malformed, or are missing required parameters. It ignores +// unknown HTTP Signature parameters. +func NewVerifier(r *http.Request) (Verifier, error) { + h := r.Header + if _, hasHostHeader := h[hostHeader]; len(r.Host) > 0 && !hasHostHeader { + h[hostHeader] = []string{r.Host} + } + return newVerifier(h, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) { + return signatureString(h, toInclude, addRequestTarget(r), created, expires) + }) +} + +// NewResponseVerifier verifies the given response. It returns errors under the +// same conditions as NewVerifier. +func NewResponseVerifier(r *http.Response) (Verifier, error) { + return newVerifier(r.Header, func(h http.Header, toInclude []string, created int64, expires int64) (string, error) { + return signatureString(h, toInclude, requestTargetNotPermitted, created, expires) + }) +} + +func newSSHSigner(sshSigner ssh.Signer, algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, error) { + var expires, created int64 = 0, 0 + + if expiresIn != 0 { + created = time.Now().Unix() + expires = created + expiresIn + } + + s, err := signerFromSSHSigner(sshSigner, string(algo)) + if err != nil { + return nil, fmt.Errorf("no crypto implementation available for ssh algo %q", algo) + } + + a := &asymmSSHSigner{ + asymmSigner: &asymmSigner{ + s: s, + dAlgo: dAlgo, + headers: headers, + targetHeader: scheme, + prefix: scheme.authScheme(), + created: created, + expires: expires, + }, + } + + return a, nil +} + +func newSigner(algo Algorithm, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (Signer, error) { + + var expires, created int64 = 0, 0 + if expiresIn != 0 { + created = time.Now().Unix() + expires = created + expiresIn + } + + s, err := signerFromString(string(algo)) + if err == nil { + a := &asymmSigner{ + s: s, + dAlgo: dAlgo, + headers: headers, + targetHeader: scheme, + prefix: scheme.authScheme(), + created: created, + expires: expires, + } + return a, nil + } + m, err := macerFromString(string(algo)) + if err != nil { + return nil, fmt.Errorf("no crypto implementation available for %q", algo) + } + c := &macSigner{ + m: m, + dAlgo: dAlgo, + headers: headers, + targetHeader: scheme, + prefix: scheme.authScheme(), + created: created, + expires: expires, + } + return c, nil +} diff --git a/cli/vendor/github.com/go-fed/httpsig/signing.go b/cli/vendor/github.com/go-fed/httpsig/signing.go new file mode 100644 index 0000000..e18db41 --- /dev/null +++ b/cli/vendor/github.com/go-fed/httpsig/signing.go @@ -0,0 +1,334 @@ +package httpsig + +import ( + "bytes" + "crypto" + "crypto/rand" + "encoding/base64" + "fmt" + "net/http" + "net/textproto" + "strconv" + "strings" +) + +const ( + // Signature Parameters + keyIdParameter = "keyId" + algorithmParameter = "algorithm" + headersParameter = "headers" + signatureParameter = "signature" + prefixSeparater = " " + parameterKVSeparater = "=" + parameterValueDelimiter = "\"" + parameterSeparater = "," + headerParameterValueDelim = " " + // RequestTarget specifies to include the http request method and + // entire URI in the signature. Pass it as a header to NewSigner. + RequestTarget = "(request-target)" + createdKey = "created" + expiresKey = "expires" + dateHeader = "date" + + // Signature String Construction + headerFieldDelimiter = ": " + headersDelimiter = "\n" + headerValueDelimiter = ", " + requestTargetSeparator = " " +) + +var defaultHeaders = []string{dateHeader} + +var _ Signer = &macSigner{} + +type macSigner struct { + m macer + makeDigest bool + dAlgo DigestAlgorithm + headers []string + targetHeader SignatureScheme + prefix string + created int64 + expires int64 +} + +func (m *macSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { + if body != nil { + err := addDigest(r, m.dAlgo, body) + if err != nil { + return err + } + } + s, err := m.signatureString(r) + if err != nil { + return err + } + enc, err := m.signSignature(pKey, s) + if err != nil { + return err + } + setSignatureHeader(r.Header, string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) + return nil +} + +func (m *macSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { + if body != nil { + err := addDigestResponse(r, m.dAlgo, body) + if err != nil { + return err + } + } + s, err := m.signatureStringResponse(r) + if err != nil { + return err + } + enc, err := m.signSignature(pKey, s) + if err != nil { + return err + } + setSignatureHeader(r.Header(), string(m.targetHeader), m.prefix, pubKeyId, m.m.String(), enc, m.headers, m.created, m.expires) + return nil +} + +func (m *macSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { + pKeyBytes, ok := pKey.([]byte) + if !ok { + return "", fmt.Errorf("private key for MAC signing must be of type []byte") + } + sig, err := m.m.Sign([]byte(s), pKeyBytes) + if err != nil { + return "", err + } + enc := base64.StdEncoding.EncodeToString(sig) + return enc, nil +} + +func (m *macSigner) signatureString(r *http.Request) (string, error) { + return signatureString(r.Header, m.headers, addRequestTarget(r), m.created, m.expires) +} + +func (m *macSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { + return signatureString(r.Header(), m.headers, requestTargetNotPermitted, m.created, m.expires) +} + +var _ Signer = &asymmSigner{} + +type asymmSigner struct { + s signer + makeDigest bool + dAlgo DigestAlgorithm + headers []string + targetHeader SignatureScheme + prefix string + created int64 + expires int64 +} + +func (a *asymmSigner) SignRequest(pKey crypto.PrivateKey, pubKeyId string, r *http.Request, body []byte) error { + if body != nil { + err := addDigest(r, a.dAlgo, body) + if err != nil { + return err + } + } + s, err := a.signatureString(r) + if err != nil { + return err + } + enc, err := a.signSignature(pKey, s) + if err != nil { + return err + } + setSignatureHeader(r.Header, string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) + return nil +} + +func (a *asymmSigner) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error { + if body != nil { + err := addDigestResponse(r, a.dAlgo, body) + if err != nil { + return err + } + } + s, err := a.signatureStringResponse(r) + if err != nil { + return err + } + enc, err := a.signSignature(pKey, s) + if err != nil { + return err + } + setSignatureHeader(r.Header(), string(a.targetHeader), a.prefix, pubKeyId, a.s.String(), enc, a.headers, a.created, a.expires) + return nil +} + +func (a *asymmSigner) signSignature(pKey crypto.PrivateKey, s string) (string, error) { + sig, err := a.s.Sign(rand.Reader, pKey, []byte(s)) + if err != nil { + return "", err + } + enc := base64.StdEncoding.EncodeToString(sig) + return enc, nil +} + +func (a *asymmSigner) signatureString(r *http.Request) (string, error) { + return signatureString(r.Header, a.headers, addRequestTarget(r), a.created, a.expires) +} + +func (a *asymmSigner) signatureStringResponse(r http.ResponseWriter) (string, error) { + return signatureString(r.Header(), a.headers, requestTargetNotPermitted, a.created, a.expires) +} + +var _ SSHSigner = &asymmSSHSigner{} + +type asymmSSHSigner struct { + *asymmSigner +} + +func (a *asymmSSHSigner) SignRequest(pubKeyId string, r *http.Request, body []byte) error { + return a.asymmSigner.SignRequest(nil, pubKeyId, r, body) +} + +func (a *asymmSSHSigner) SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error { + return a.asymmSigner.SignResponse(nil, pubKeyId, r, body) +} + +func setSignatureHeader(h http.Header, targetHeader, prefix, pubKeyId, algo, enc string, headers []string, created int64, expires int64) { + if len(headers) == 0 { + headers = defaultHeaders + } + var b bytes.Buffer + // KeyId + b.WriteString(prefix) + if len(prefix) > 0 { + b.WriteString(prefixSeparater) + } + b.WriteString(keyIdParameter) + b.WriteString(parameterKVSeparater) + b.WriteString(parameterValueDelimiter) + b.WriteString(pubKeyId) + b.WriteString(parameterValueDelimiter) + b.WriteString(parameterSeparater) + // Algorithm + b.WriteString(algorithmParameter) + b.WriteString(parameterKVSeparater) + b.WriteString(parameterValueDelimiter) + b.WriteString("hs2019") //real algorithm is hidden, see newest version of spec draft + b.WriteString(parameterValueDelimiter) + b.WriteString(parameterSeparater) + + hasCreated := false + hasExpires := false + for _, h := range headers { + val := strings.ToLower(h) + if val == "("+createdKey+")" { + hasCreated = true + } else if val == "("+expiresKey+")" { + hasExpires = true + } + } + + // Created + if hasCreated == true { + b.WriteString(createdKey) + b.WriteString(parameterKVSeparater) + b.WriteString(strconv.FormatInt(created, 10)) + b.WriteString(parameterSeparater) + } + + // Expires + if hasExpires == true { + b.WriteString(expiresKey) + b.WriteString(parameterKVSeparater) + b.WriteString(strconv.FormatInt(expires, 10)) + b.WriteString(parameterSeparater) + } + + // Headers + b.WriteString(headersParameter) + b.WriteString(parameterKVSeparater) + b.WriteString(parameterValueDelimiter) + for i, h := range headers { + b.WriteString(strings.ToLower(h)) + if i != len(headers)-1 { + b.WriteString(headerParameterValueDelim) + } + } + b.WriteString(parameterValueDelimiter) + b.WriteString(parameterSeparater) + // Signature + b.WriteString(signatureParameter) + b.WriteString(parameterKVSeparater) + b.WriteString(parameterValueDelimiter) + b.WriteString(enc) + b.WriteString(parameterValueDelimiter) + h.Add(targetHeader, b.String()) +} + +func requestTargetNotPermitted(b *bytes.Buffer) error { + return fmt.Errorf("cannot sign with %q on anything other than an http request", RequestTarget) +} + +func addRequestTarget(r *http.Request) func(b *bytes.Buffer) error { + return func(b *bytes.Buffer) error { + b.WriteString(RequestTarget) + b.WriteString(headerFieldDelimiter) + b.WriteString(strings.ToLower(r.Method)) + b.WriteString(requestTargetSeparator) + b.WriteString(r.URL.Path) + + if r.URL.RawQuery != "" { + b.WriteString("?") + b.WriteString(r.URL.RawQuery) + } + + return nil + } +} + +func signatureString(values http.Header, include []string, requestTargetFn func(b *bytes.Buffer) error, created int64, expires int64) (string, error) { + if len(include) == 0 { + include = defaultHeaders + } + var b bytes.Buffer + for n, i := range include { + i := strings.ToLower(i) + if i == RequestTarget { + err := requestTargetFn(&b) + if err != nil { + return "", err + } + } else if i == "("+expiresKey+")" { + if expires == 0 { + return "", fmt.Errorf("missing expires value") + } + b.WriteString(i) + b.WriteString(headerFieldDelimiter) + b.WriteString(strconv.FormatInt(expires, 10)) + } else if i == "("+createdKey+")" { + if created == 0 { + return "", fmt.Errorf("missing created value") + } + b.WriteString(i) + b.WriteString(headerFieldDelimiter) + b.WriteString(strconv.FormatInt(created, 10)) + } else { + hv, ok := values[textproto.CanonicalMIMEHeaderKey(i)] + if !ok { + return "", fmt.Errorf("missing header %q", i) + } + b.WriteString(i) + b.WriteString(headerFieldDelimiter) + for i, v := range hv { + b.WriteString(strings.TrimSpace(v)) + if i < len(hv)-1 { + b.WriteString(headerValueDelimiter) + } + } + } + if n < len(include)-1 { + b.WriteString(headersDelimiter) + } + } + return b.String(), nil +} diff --git a/cli/vendor/github.com/go-fed/httpsig/verifying.go b/cli/vendor/github.com/go-fed/httpsig/verifying.go new file mode 100644 index 0000000..6d8df02 --- /dev/null +++ b/cli/vendor/github.com/go-fed/httpsig/verifying.go @@ -0,0 +1,184 @@ +package httpsig + +import ( + "crypto" + "encoding/base64" + "errors" + "fmt" + "net/http" + "strconv" + "strings" + "time" +) + +var _ Verifier = &verifier{} + +type verifier struct { + header http.Header + kId string + signature string + created int64 + expires int64 + headers []string + sigStringFn func(http.Header, []string, int64, int64) (string, error) +} + +func newVerifier(h http.Header, sigStringFn func(http.Header, []string, int64, int64) (string, error)) (*verifier, error) { + scheme, s, err := getSignatureScheme(h) + if err != nil { + return nil, err + } + kId, sig, headers, created, expires, err := getSignatureComponents(scheme, s) + if created != 0 { + //check if created is not in the future, we assume a maximum clock offset of 10 seconds + now := time.Now().Unix() + if created-now > 10 { + return nil, errors.New("created is in the future") + } + } + if expires != 0 { + //check if expires is in the past, we assume a maximum clock offset of 10 seconds + now := time.Now().Unix() + if now-expires > 10 { + return nil, errors.New("signature expired") + } + } + if err != nil { + return nil, err + } + return &verifier{ + header: h, + kId: kId, + signature: sig, + created: created, + expires: expires, + headers: headers, + sigStringFn: sigStringFn, + }, nil +} + +func (v *verifier) KeyId() string { + return v.kId +} + +func (v *verifier) Verify(pKey crypto.PublicKey, algo Algorithm) error { + s, err := signerFromString(string(algo)) + if err == nil { + return v.asymmVerify(s, pKey) + } + m, err := macerFromString(string(algo)) + if err == nil { + return v.macVerify(m, pKey) + } + return fmt.Errorf("no crypto implementation available for %q", algo) +} + +func (v *verifier) macVerify(m macer, pKey crypto.PublicKey) error { + key, ok := pKey.([]byte) + if !ok { + return fmt.Errorf("public key for MAC verifying must be of type []byte") + } + signature, err := v.sigStringFn(v.header, v.headers, v.created, v.expires) + if err != nil { + return err + } + actualMAC, err := base64.StdEncoding.DecodeString(v.signature) + if err != nil { + return err + } + ok, err = m.Equal([]byte(signature), actualMAC, key) + if err != nil { + return err + } else if !ok { + return fmt.Errorf("invalid http signature") + } + return nil +} + +func (v *verifier) asymmVerify(s signer, pKey crypto.PublicKey) error { + toHash, err := v.sigStringFn(v.header, v.headers, v.created, v.expires) + if err != nil { + return err + } + signature, err := base64.StdEncoding.DecodeString(v.signature) + if err != nil { + return err + } + err = s.Verify(pKey, []byte(toHash), signature) + if err != nil { + return err + } + return nil +} + +func getSignatureScheme(h http.Header) (scheme SignatureScheme, val string, err error) { + s := h.Get(string(Signature)) + sigHasAll := strings.Contains(s, keyIdParameter) || + strings.Contains(s, headersParameter) || + strings.Contains(s, signatureParameter) + a := h.Get(string(Authorization)) + authHasAll := strings.Contains(a, keyIdParameter) || + strings.Contains(a, headersParameter) || + strings.Contains(a, signatureParameter) + if sigHasAll && authHasAll { + err = fmt.Errorf("both %q and %q have signature parameters", Signature, Authorization) + return + } else if !sigHasAll && !authHasAll { + err = fmt.Errorf("neither %q nor %q have signature parameters", Signature, Authorization) + return + } else if sigHasAll { + val = s + scheme = Signature + return + } else { // authHasAll + val = a + scheme = Authorization + return + } +} + +func getSignatureComponents(scheme SignatureScheme, s string) (kId, sig string, headers []string, created int64, expires int64, err error) { + if as := scheme.authScheme(); len(as) > 0 { + s = strings.TrimPrefix(s, as+prefixSeparater) + } + params := strings.Split(s, parameterSeparater) + for _, p := range params { + kv := strings.SplitN(p, parameterKVSeparater, 2) + if len(kv) != 2 { + err = fmt.Errorf("malformed http signature parameter: %v", kv) + return + } + k := kv[0] + v := strings.Trim(kv[1], parameterValueDelimiter) + switch k { + case keyIdParameter: + kId = v + case createdKey: + created, err = strconv.ParseInt(v, 10, 64) + if err != nil { + return + } + case expiresKey: + expires, err = strconv.ParseInt(v, 10, 64) + if err != nil { + return + } + case algorithmParameter: + // Deprecated, ignore + case headersParameter: + headers = strings.Split(v, headerParameterValueDelim) + case signatureParameter: + sig = v + default: + // Ignore unrecognized parameters + } + } + if len(kId) == 0 { + err = fmt.Errorf("missing %q parameter in http signature", keyIdParameter) + } else if len(sig) == 0 { + err = fmt.Errorf("missing %q parameter in http signature", signatureParameter) + } else if len(headers) == 0 { // Optional + headers = defaultHeaders + } + return +} diff --git a/cli/vendor/github.com/hashicorp/go-version/CHANGELOG.md b/cli/vendor/github.com/hashicorp/go-version/CHANGELOG.md new file mode 100644 index 0000000..81b4231 --- /dev/null +++ b/cli/vendor/github.com/hashicorp/go-version/CHANGELOG.md @@ -0,0 +1,102 @@ +# 1.9.0 (Mar 30, 2026) + +ENHANCEMENTS: + +Support parsing versions with custom prefixes via opt-in option in https://github.com/hashicorp/go-version/pull/79 + +INTERNAL: + +- Bump the github-actions-backward-compatible group across 1 directory with 2 updates in https://github.com/hashicorp/go-version/pull/179 +- Bump the github-actions-breaking group with 4 updates in https://github.com/hashicorp/go-version/pull/180 +- Bump the github-actions-backward-compatible group with 3 updates in https://github.com/hashicorp/go-version/pull/182 +- Update GitHub Actions to trigger on pull requests and update go version in https://github.com/hashicorp/go-version/pull/185 +- Bump actions/upload-artifact from 6.0.0 to 7.0.0 in the github-actions-breaking group across 1 directory in https://github.com/hashicorp/go-version/pull/183 +- Bump the github-actions-backward-compatible group across 1 directory with 2 updates in https://github.com/hashicorp/go-version/pull/186 + +# 1.8.0 (Nov 28, 2025) + +ENHANCEMENTS: + +- Add benchmark test for version.String() in https://github.com/hashicorp/go-version/pull/159 +- Bytes implementation in https://github.com/hashicorp/go-version/pull/161 + +INTERNAL: + +- Add CODEOWNERS file in .github/CODEOWNERS in https://github.com/hashicorp/go-version/pull/145 +- Linting in https://github.com/hashicorp/go-version/pull/151 +- Correct typos in comments in https://github.com/hashicorp/go-version/pull/134 +- Migrate GitHub Actions updates from TSCCR to Dependabot in https://github.com/hashicorp/go-version/pull/155 +- Bump the github-actions-backward-compatible group with 2 updates in https://github.com/hashicorp/go-version/pull/157 +- Update doc reference in README in https://github.com/hashicorp/go-version/pull/135 +- Bump the github-actions-breaking group with 3 updates in https://github.com/hashicorp/go-version/pull/156 +- [Compliance] - PR Template Changes Required in https://github.com/hashicorp/go-version/pull/158 +- Bump actions/cache from 4.2.3 to 4.2.4 in the github-actions-backward-compatible group in https://github.com/hashicorp/go-version/pull/167 +- Bump actions/checkout from 4.2.2 to 5.0.0 in the github-actions-breaking group in https://github.com/hashicorp/go-version/pull/166 +- Bump the github-actions-breaking group across 1 directory with 2 updates in https://github.com/hashicorp/go-version/pull/171 +- [IND-4226] [COMPLIANCE] Update Copyright Headers in https://github.com/hashicorp/go-version/pull/172 +- drop init() in https://github.com/hashicorp/go-version/pull/175 + +# 1.7.0 (May 24, 2024) + +ENHANCEMENTS: + +- Remove `reflect` dependency ([#91](https://github.com/hashicorp/go-version/pull/91)) +- Implement the `database/sql.Scanner` and `database/sql/driver.Value` interfaces for `Version` ([#133](https://github.com/hashicorp/go-version/pull/133)) + +INTERNAL: + +- [COMPLIANCE] Add Copyright and License Headers ([#115](https://github.com/hashicorp/go-version/pull/115)) +- [COMPLIANCE] Update MPL-2.0 LICENSE ([#105](https://github.com/hashicorp/go-version/pull/105)) +- Bump actions/cache from 3.0.11 to 3.2.5 ([#116](https://github.com/hashicorp/go-version/pull/116)) +- Bump actions/checkout from 3.2.0 to 3.3.0 ([#111](https://github.com/hashicorp/go-version/pull/111)) +- Bump actions/upload-artifact from 3.1.1 to 3.1.2 ([#112](https://github.com/hashicorp/go-version/pull/112)) +- GHA Migration ([#103](https://github.com/hashicorp/go-version/pull/103)) +- github: Pin external GitHub Actions to hashes ([#107](https://github.com/hashicorp/go-version/pull/107)) +- SEC-090: Automated trusted workflow pinning (2023-04-05) ([#124](https://github.com/hashicorp/go-version/pull/124)) +- update readme ([#104](https://github.com/hashicorp/go-version/pull/104)) + +# 1.6.0 (June 28, 2022) + +FEATURES: + +- Add `Prerelease` function to `Constraint` to return true if the version includes a prerelease field ([#100](https://github.com/hashicorp/go-version/pull/100)) + +# 1.5.0 (May 18, 2022) + +FEATURES: + +- Use `encoding` `TextMarshaler` & `TextUnmarshaler` instead of JSON equivalents ([#95](https://github.com/hashicorp/go-version/pull/95)) +- Add JSON handlers to allow parsing from/to JSON ([#93](https://github.com/hashicorp/go-version/pull/93)) + +# 1.4.0 (January 5, 2022) + +FEATURES: + + - Introduce `MustConstraints()` ([#87](https://github.com/hashicorp/go-version/pull/87)) + - `Constraints`: Introduce `Equals()` and `sort.Interface` methods ([#88](https://github.com/hashicorp/go-version/pull/88)) + +# 1.3.0 (March 31, 2021) + +Please note that CHANGELOG.md does not exist in the source code prior to this release. + +FEATURES: + - Add `Core` function to return a version without prerelease or metadata ([#85](https://github.com/hashicorp/go-version/pull/85)) + +# 1.2.1 (June 17, 2020) + +BUG FIXES: + - Prevent `Version.Equal` method from panicking on `nil` encounter ([#73](https://github.com/hashicorp/go-version/pull/73)) + +# 1.2.0 (April 23, 2019) + +FEATURES: + - Add `GreaterThanOrEqual` and `LessThanOrEqual` helper methods ([#53](https://github.com/hashicorp/go-version/pull/53)) + +# 1.1.0 (Jan 07, 2019) + +FEATURES: + - Add `NewSemver` constructor ([#45](https://github.com/hashicorp/go-version/pull/45)) + +# 1.0.0 (August 24, 2018) + +Initial release. diff --git a/cli/vendor/github.com/hashicorp/go-version/LICENSE b/cli/vendor/github.com/hashicorp/go-version/LICENSE new file mode 100644 index 0000000..bb1e9a4 --- /dev/null +++ b/cli/vendor/github.com/hashicorp/go-version/LICENSE @@ -0,0 +1,356 @@ +Copyright IBM Corp. 2014, 2025 + +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. + diff --git a/cli/vendor/github.com/hashicorp/go-version/README.md b/cli/vendor/github.com/hashicorp/go-version/README.md new file mode 100644 index 0000000..5528960 --- /dev/null +++ b/cli/vendor/github.com/hashicorp/go-version/README.md @@ -0,0 +1,93 @@ +# Versioning Library for Go + +![Build Status](https://github.com/hashicorp/go-version/actions/workflows/go-tests.yml/badge.svg) +[![Go Reference](https://pkg.go.dev/badge/github.com/hashicorp/go-version.svg)](https://pkg.go.dev/github.com/hashicorp/go-version) + +go-version is a library for parsing versions and version constraints, +and verifying versions against a set of constraints. go-version +can sort a collection of versions properly, handles prerelease/beta +versions, can increment versions, etc. + +Versions used with go-version must follow [SemVer](http://semver.org/). + +## Installation and Usage + +Package documentation can be found on +[Go Reference](https://pkg.go.dev/github.com/hashicorp/go-version). + +Installation can be done with a normal `go get`: + +``` +$ go get github.com/hashicorp/go-version +``` + +#### Version Parsing and Comparison + +```go +v1, err := version.NewVersion("1.2") +v2, err := version.NewVersion("1.5+metadata") + +// Comparison example. There is also GreaterThan, Equal, and just +// a simple Compare that returns an int allowing easy >=, <=, etc. +if v1.LessThan(v2) { + fmt.Printf("%s is less than %s", v1, v2) +} +``` + +#### Version Parsing and Comparison with Prefixes + +The library also supports parsing versions with a custom prefix. +Using the `WithPrefix` option, you can specify a prefix to strip +before parsing the version. + +Use `WithPrefix` when your input strings carry a known release prefix such as +`deployment-`, `controller-`, etc. + +After parsing, the prefix is not part of the canonical version value. This +means the regular comparison methods such as `Compare`, `LessThan`, `Equal`, +and `GreaterThan` compare only the stripped version. If you compare versions +from different prefixes with these methods, the prefixes are ignored. If you +need to reject cross-prefix comparisons, inspect the parsed prefixes before +comparing the versions. + +```go +v1, _ := version.NewVersion("deployment-v1.2.3-beta+metadata", version.WithPrefix("deployment-")) +v2, _ := version.NewVersion("deployment-v1.2.4", version.WithPrefix("deployment-")) + +if v1.LessThan(v2) { + fmt.Printf("%s (%s) is less than %s (%s)\n", v1, v1.Original(), v2, v2.Original()) + // Outputs: 1.2.3-beta+metadata (deployment-v1.2.3-beta+metadata) is less than 1.2.4 (deployment-v1.2.4) +} +``` + +#### Version Constraints + +```go +v1, err := version.NewVersion("1.2") + +// Constraints example. +constraints, err := version.NewConstraint(">= 1.0, < 1.4") +if constraints.Check(v1) { + fmt.Printf("%s satisfies constraints %s", v1, constraints) +} +``` + +#### Version Sorting + +```go +versionsRaw := []string{"1.1", "0.7.1", "1.4-beta", "1.4", "2"} +versions := make([]*version.Version, len(versionsRaw)) +for i, raw := range versionsRaw { + v, _ := version.NewVersion(raw) + versions[i] = v +} + +// After this, the versions are properly sorted +sort.Sort(version.Collection(versions)) +``` + +## Issues and Contributing + +If you find an issue with this library, please report an issue. If you'd +like, we welcome any contributions. Fork this library and submit a pull +request. diff --git a/cli/vendor/github.com/hashicorp/go-version/constraint.go b/cli/vendor/github.com/hashicorp/go-version/constraint.go new file mode 100644 index 0000000..3964da0 --- /dev/null +++ b/cli/vendor/github.com/hashicorp/go-version/constraint.go @@ -0,0 +1,307 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package version + +import ( + "fmt" + "regexp" + "sort" + "strings" + "sync" +) + +var ( + constraintRegexp *regexp.Regexp + constraintRegexpOnce sync.Once +) + +func getConstraintRegexp() *regexp.Regexp { + constraintRegexpOnce.Do(func() { + // This heavy lifting only happens the first time this function is called + constraintRegexp = regexp.MustCompile(fmt.Sprintf( + `^\s*(%s)\s*(%s)\s*$`, + `<=|>=|!=|~>|<|>|=|`, + VersionRegexpRaw, + )) + }) + return constraintRegexp +} + +// Constraint represents a single constraint for a version, such as +// ">= 1.0". +type Constraint struct { + f constraintFunc + op operator + check *Version + original string +} + +func (c *Constraint) Equals(con *Constraint) bool { + return c.op == con.op && c.check.Equal(con.check) +} + +// Constraints is a slice of constraints. We make a custom type so that +// we can add methods to it. +type Constraints []*Constraint + +type constraintFunc func(v, c *Version) bool + +type constraintOperation struct { + op operator + f constraintFunc +} + +// NewConstraint will parse one or more constraints from the given +// constraint string. The string must be a comma-separated list of +// constraints. +func NewConstraint(v string) (Constraints, error) { + vs := strings.Split(v, ",") + result := make([]*Constraint, len(vs)) + for i, single := range vs { + c, err := parseSingle(single) + if err != nil { + return nil, err + } + + result[i] = c + } + + return Constraints(result), nil +} + +// MustConstraints is a helper that wraps a call to a function +// returning (Constraints, error) and panics if error is non-nil. +func MustConstraints(c Constraints, err error) Constraints { + if err != nil { + panic(err) + } + + return c +} + +// Check tests if a version satisfies all the constraints. +func (cs Constraints) Check(v *Version) bool { + for _, c := range cs { + if !c.Check(v) { + return false + } + } + + return true +} + +// Equals compares Constraints with other Constraints +// for equality. This may not represent logical equivalence +// of compared constraints. +// e.g. even though '>0.1,>0.2' is logically equivalent +// to '>0.2' it is *NOT* treated as equal. +// +// Missing operator is treated as equal to '=', whitespaces +// are ignored and constraints are sorted before comparison. +func (cs Constraints) Equals(c Constraints) bool { + if len(cs) != len(c) { + return false + } + + // make copies to retain order of the original slices + left := make(Constraints, len(cs)) + copy(left, cs) + sort.Stable(left) + right := make(Constraints, len(c)) + copy(right, c) + sort.Stable(right) + + // compare sorted slices + for i, con := range left { + if !con.Equals(right[i]) { + return false + } + } + + return true +} + +func (cs Constraints) Len() int { + return len(cs) +} + +func (cs Constraints) Less(i, j int) bool { + if cs[i].op < cs[j].op { + return true + } + if cs[i].op > cs[j].op { + return false + } + + return cs[i].check.LessThan(cs[j].check) +} + +func (cs Constraints) Swap(i, j int) { + cs[i], cs[j] = cs[j], cs[i] +} + +// Returns the string format of the constraints +func (cs Constraints) String() string { + csStr := make([]string, len(cs)) + for i, c := range cs { + csStr[i] = c.String() + } + + return strings.Join(csStr, ",") +} + +// Check tests if a constraint is validated by the given version. +func (c *Constraint) Check(v *Version) bool { + return c.f(v, c.check) +} + +// Prerelease returns true if the version underlying this constraint +// contains a prerelease field. +func (c *Constraint) Prerelease() bool { + return len(c.check.Prerelease()) > 0 +} + +func (c *Constraint) String() string { + return c.original +} + +func parseSingle(v string) (*Constraint, error) { + matches := getConstraintRegexp().FindStringSubmatch(v) + if matches == nil { + return nil, fmt.Errorf("malformed constraint: %s", v) + } + + check, err := NewVersion(matches[2]) + if err != nil { + return nil, err + } + + var cop constraintOperation + switch matches[1] { + case "=": + cop = constraintOperation{op: equal, f: constraintEqual} + case "!=": + cop = constraintOperation{op: notEqual, f: constraintNotEqual} + case ">": + cop = constraintOperation{op: greaterThan, f: constraintGreaterThan} + case "<": + cop = constraintOperation{op: lessThan, f: constraintLessThan} + case ">=": + cop = constraintOperation{op: greaterThanEqual, f: constraintGreaterThanEqual} + case "<=": + cop = constraintOperation{op: lessThanEqual, f: constraintLessThanEqual} + case "~>": + cop = constraintOperation{op: pessimistic, f: constraintPessimistic} + default: + cop = constraintOperation{op: equal, f: constraintEqual} + } + + return &Constraint{ + f: cop.f, + op: cop.op, + check: check, + original: v, + }, nil +} + +func prereleaseCheck(v, c *Version) bool { + switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; { + case cPre && vPre: + // A constraint with a pre-release can only match a pre-release version + // with the same base segments. + return v.equalSegments(c) + + case !cPre && vPre: + // A constraint without a pre-release can only match a version without a + // pre-release. + return false + + case cPre && !vPre: + // OK, except with the pessimistic operator + case !cPre && !vPre: + // OK + } + return true +} + +//------------------------------------------------------------------- +// Constraint functions +//------------------------------------------------------------------- + +type operator rune + +const ( + equal operator = '=' + notEqual operator = '≠' + greaterThan operator = '>' + lessThan operator = '<' + greaterThanEqual operator = '≥' + lessThanEqual operator = '≤' + pessimistic operator = '~' +) + +func constraintEqual(v, c *Version) bool { + return v.Equal(c) +} + +func constraintNotEqual(v, c *Version) bool { + return !v.Equal(c) +} + +func constraintGreaterThan(v, c *Version) bool { + return prereleaseCheck(v, c) && v.Compare(c) == 1 +} + +func constraintLessThan(v, c *Version) bool { + return prereleaseCheck(v, c) && v.Compare(c) == -1 +} + +func constraintGreaterThanEqual(v, c *Version) bool { + return prereleaseCheck(v, c) && v.Compare(c) >= 0 +} + +func constraintLessThanEqual(v, c *Version) bool { + return prereleaseCheck(v, c) && v.Compare(c) <= 0 +} + +func constraintPessimistic(v, c *Version) bool { + // Using a pessimistic constraint with a pre-release, restricts versions to pre-releases + if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") { + return false + } + + // If the version being checked is naturally less than the constraint, then there + // is no way for the version to be valid against the constraint + if v.LessThan(c) { + return false + } + // We'll use this more than once, so grab the length now so it's a little cleaner + // to write the later checks + cs := len(c.segments) + + // If the version being checked has less specificity than the constraint, then there + // is no way for the version to be valid against the constraint + if cs > len(v.segments) { + return false + } + + // Check the segments in the constraint against those in the version. If the version + // being checked, at any point, does not have the same values in each index of the + // constraints segments, then it cannot be valid against the constraint. + for i := 0; i < c.si-1; i++ { + if v.segments[i] != c.segments[i] { + return false + } + } + + // Check the last part of the segment in the constraint. If the version segment at + // this index is less than the constraints segment at this index, then it cannot + // be valid against the constraint + if c.segments[cs-1] > v.segments[cs-1] { + return false + } + + // If nothing has rejected the version by now, it's valid + return true +} diff --git a/cli/vendor/github.com/hashicorp/go-version/version.go b/cli/vendor/github.com/hashicorp/go-version/version.go new file mode 100644 index 0000000..b95503d --- /dev/null +++ b/cli/vendor/github.com/hashicorp/go-version/version.go @@ -0,0 +1,505 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package version + +import ( + "database/sql/driver" + "fmt" + "regexp" + "strconv" + "strings" + "sync" +) + +// The compiled regular expression used to test the validity of a version. +var ( + versionRegexp *regexp.Regexp + versionRegexpOnce sync.Once + semverRegexp *regexp.Regexp + semverRegexpOnce sync.Once +) + +func getVersionRegexp() *regexp.Regexp { + versionRegexpOnce.Do(func() { + versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$") + }) + return versionRegexp +} + +func getSemverRegexp() *regexp.Regexp { + semverRegexpOnce.Do(func() { + semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$") + }) + return semverRegexp +} + +// The raw regular expression string used for testing the validity +// of a version. +const ( + VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` + + `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` + + `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + + `?` + + // SemverRegexpRaw requires a separator between version and prerelease + SemverRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` + + `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` + + `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + + `?` +) + +// Optional options for NewVersion function. +type options struct { + // If set, this prefix will be trimmed from the version string before parsing. + prefix string +} + +// Option is a functional option for NewVersion. +type Option func(*options) + +// WithPrefix is a functional option that sets a prefix to be removed from the +// version string before parsing. +func WithPrefix(prefix string) Option { + return func(o *options) { + o.prefix = prefix + } +} + +// Version represents a single version. +type Version struct { + metadata string + pre string + segments []int64 + si int + original string + prefix string +} + +// NewVersion parses the given version and returns a new Version. +// +// Optional parsing behavior can be enabled with Option values such as +// WithPrefix, which validates and strips an expected prefix before parsing. +func NewVersion(v string, opts ...Option) (*Version, error) { + options := &options{} + for _, opt := range opts { + if opt != nil { + opt(options) + } + } + + vToParse := v + if options.prefix != "" { + if !strings.HasPrefix(v, options.prefix) { + return nil, fmt.Errorf("version %q does not have prefix %q", v, options.prefix) + } + vToParse = strings.TrimPrefix(v, options.prefix) + } + + ver, err := newVersion(vToParse, getVersionRegexp()) + if err != nil { + return nil, err + } + ver.prefix = options.prefix + ver.original = v + return ver, nil +} + +// NewSemver parses the given version and returns a new +// Version that adheres strictly to SemVer specs +// https://semver.org/ +func NewSemver(v string) (*Version, error) { + return newVersion(v, getSemverRegexp()) +} + +func newVersion(v string, pattern *regexp.Regexp) (*Version, error) { + matches := pattern.FindStringSubmatch(v) + if matches == nil { + return nil, fmt.Errorf("malformed version: %s", v) + } + segmentsStr := strings.Split(matches[1], ".") + segments := make([]int64, len(segmentsStr)) + for i, str := range segmentsStr { + val, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return nil, fmt.Errorf( + "error parsing version: %s", err) + } + + segments[i] = val + } + + // Even though we could support more than three segments, if we + // got less than three, pad it with 0s. This is to cover the basic + // default usecase of semver, which is MAJOR.MINOR.PATCH at the minimum + for i := len(segments); i < 3; i++ { + segments = append(segments, 0) + } + + pre := matches[7] + if pre == "" { + pre = matches[4] + } + + return &Version{ + metadata: matches[10], + pre: pre, + segments: segments, + si: len(segmentsStr), + original: v, + }, nil +} + +// Must is a helper that wraps a call to a function returning (*Version, error) +// and panics if error is non-nil. +func Must(v *Version, err error) *Version { + if err != nil { + panic(err) + } + + return v +} + +// Compare compares this version to another version. This +// returns -1, 0, or 1 if this version is smaller, equal, +// or larger than the other version, respectively. +// +// If you want boolean results, use the LessThan, Equal, +// GreaterThan, GreaterThanOrEqual or LessThanOrEqual methods. +func (v *Version) Compare(other *Version) int { + // A quick, efficient equality check + if v.String() == other.String() { + return 0 + } + + // If the segments are the same, we must compare on prerelease info + if v.equalSegments(other) { + preSelf := v.Prerelease() + preOther := other.Prerelease() + if preSelf == "" && preOther == "" { + return 0 + } + if preSelf == "" { + return 1 + } + if preOther == "" { + return -1 + } + + return comparePrereleases(preSelf, preOther) + } + + segmentsSelf := v.Segments64() + segmentsOther := other.Segments64() + // Get the highest specificity (hS), or if they're equal, just use segmentSelf length + lenSelf := len(segmentsSelf) + lenOther := len(segmentsOther) + hS := lenSelf + if lenSelf < lenOther { + hS = lenOther + } + // Compare the segments + // Because a constraint could have more/less specificity than the version it's + // checking, we need to account for a lopsided or jagged comparison + for i := 0; i < hS; i++ { + if i > lenSelf-1 { + // This means Self had the lower specificity + // Check to see if the remaining segments in Other are all zeros + if !allZero(segmentsOther[i:]) { + // if not, it means that Other has to be greater than Self + return -1 + } + break + } else if i > lenOther-1 { + // this means Other had the lower specificity + // Check to see if the remaining segments in Self are all zeros - + if !allZero(segmentsSelf[i:]) { + // if not, it means that Self has to be greater than Other + return 1 + } + break + } + lhs := segmentsSelf[i] + rhs := segmentsOther[i] + if lhs == rhs { + continue + } else if lhs < rhs { + return -1 + } + // Otherwise, rhs was > lhs, they're not equal + return 1 + } + + // if we got this far, they're equal + return 0 +} + +func (v *Version) equalSegments(other *Version) bool { + segmentsSelf := v.Segments64() + segmentsOther := other.Segments64() + + if len(segmentsSelf) != len(segmentsOther) { + return false + } + for i, v := range segmentsSelf { + if v != segmentsOther[i] { + return false + } + } + return true +} + +func allZero(segs []int64) bool { + for _, s := range segs { + if s != 0 { + return false + } + } + return true +} + +func comparePart(preSelf string, preOther string) int { + if preSelf == preOther { + return 0 + } + + var selfInt int64 + selfNumeric := true + selfInt, err := strconv.ParseInt(preSelf, 10, 64) + if err != nil { + selfNumeric = false + } + + var otherInt int64 + otherNumeric := true + otherInt, err = strconv.ParseInt(preOther, 10, 64) + if err != nil { + otherNumeric = false + } + + // if a part is empty, we use the other to decide + if preSelf == "" { + if otherNumeric { + return -1 + } + return 1 + } + + if preOther == "" { + if selfNumeric { + return 1 + } + return -1 + } + + if selfNumeric && !otherNumeric { + return -1 + } else if !selfNumeric && otherNumeric { + return 1 + } else if !selfNumeric && !otherNumeric && preSelf > preOther { + return 1 + } else if selfInt > otherInt { + return 1 + } + + return -1 +} + +func comparePrereleases(v string, other string) int { + // the same pre release! + if v == other { + return 0 + } + + // split both pre releases for analyse their parts + selfPreReleaseMeta := strings.Split(v, ".") + otherPreReleaseMeta := strings.Split(other, ".") + + selfPreReleaseLen := len(selfPreReleaseMeta) + otherPreReleaseLen := len(otherPreReleaseMeta) + + biggestLen := otherPreReleaseLen + if selfPreReleaseLen > otherPreReleaseLen { + biggestLen = selfPreReleaseLen + } + + // loop for parts to find the first difference + for i := 0; i < biggestLen; i = i + 1 { + partSelfPre := "" + if i < selfPreReleaseLen { + partSelfPre = selfPreReleaseMeta[i] + } + + partOtherPre := "" + if i < otherPreReleaseLen { + partOtherPre = otherPreReleaseMeta[i] + } + + compare := comparePart(partSelfPre, partOtherPre) + // if parts are equals, continue the loop + if compare != 0 { + return compare + } + } + + return 0 +} + +// Core returns a new version constructed from only the MAJOR.MINOR.PATCH +// segments of the version, without prerelease or metadata. +func (v *Version) Core() *Version { + segments := v.Segments64() + segmentsOnly := fmt.Sprintf("%d.%d.%d", segments[0], segments[1], segments[2]) + return Must(NewVersion(segmentsOnly)) +} + +// Equal tests if two versions are equal. +func (v *Version) Equal(o *Version) bool { + if v == nil || o == nil { + return v == o + } + + return v.Compare(o) == 0 +} + +// GreaterThan tests if this version is greater than another version. +func (v *Version) GreaterThan(o *Version) bool { + return v.Compare(o) > 0 +} + +// GreaterThanOrEqual tests if this version is greater than or equal to another version. +func (v *Version) GreaterThanOrEqual(o *Version) bool { + return v.Compare(o) >= 0 +} + +// LessThan tests if this version is less than another version. +func (v *Version) LessThan(o *Version) bool { + return v.Compare(o) < 0 +} + +// LessThanOrEqual tests if this version is less than or equal to another version. +func (v *Version) LessThanOrEqual(o *Version) bool { + return v.Compare(o) <= 0 +} + +// Metadata returns any metadata that was part of the version +// string. +// +// Metadata is anything that comes after the "+" in the version. +// For example, with "1.2.3+beta", the metadata is "beta". +func (v *Version) Metadata() string { + return v.metadata +} + +// Prerelease returns any prerelease data that is part of the version, +// or blank if there is no prerelease data. +// +// Prerelease information is anything that comes after the "-" in the +// version (but before any metadata). For example, with "1.2.3-beta", +// the prerelease information is "beta". +func (v *Version) Prerelease() string { + return v.pre +} + +// Segments returns the numeric segments of the version as a slice of ints. +// +// This excludes any metadata or pre-release information. For example, +// for a version "1.2.3-beta", segments will return a slice of +// 1, 2, 3. +func (v *Version) Segments() []int { + segmentSlice := make([]int, len(v.segments)) + for i, v := range v.segments { + segmentSlice[i] = int(v) + } + return segmentSlice +} + +// Segments64 returns the numeric segments of the version as a slice of int64s. +// +// This excludes any metadata or pre-release information. For example, +// for a version "1.2.3-beta", segments will return a slice of +// 1, 2, 3. +func (v *Version) Segments64() []int64 { + result := make([]int64, len(v.segments)) + copy(result, v.segments) + return result +} + +// String returns the full version string included pre-release +// and metadata information. +// +// This value is rebuilt according to the parsed segments and other +// information. Therefore, ambiguities in the version string such as +// prefixed zeroes (1.04.0 => 1.4.0), `v` prefix (v1.0.0 => 1.0.0), and +// missing parts (1.0 => 1.0.0) will be made into a canonicalized form +// as shown in the parenthesized examples. +func (v *Version) String() string { + return string(v.bytes()) +} + +func (v *Version) bytes() []byte { + var buf []byte + for i, s := range v.segments { + if i > 0 { + buf = append(buf, '.') + } + buf = strconv.AppendInt(buf, s, 10) + } + + if v.pre != "" { + buf = append(buf, '-') + buf = append(buf, v.pre...) + } + + if v.metadata != "" { + buf = append(buf, '+') + buf = append(buf, v.metadata...) + } + + return buf +} + +// Original returns the original parsed version as-is, including any +// potential whitespace, `v` prefix, etc. +func (v *Version) Original() string { + return v.original +} + +// Prefix returns the explicit prefix used with WithPrefix, if any. +func (v *Version) Prefix() string { + return v.prefix +} + +// UnmarshalText implements encoding.TextUnmarshaler interface. +func (v *Version) UnmarshalText(b []byte) error { + temp, err := NewVersion(string(b)) + if err != nil { + return err + } + + *v = *temp + + return nil +} + +// MarshalText implements encoding.TextMarshaler interface. +func (v *Version) MarshalText() ([]byte, error) { + return []byte(v.String()), nil +} + +// Scan implements the sql.Scanner interface. +func (v *Version) Scan(src interface{}) error { + switch src := src.(type) { + case string: + return v.UnmarshalText([]byte(src)) + case nil: + return nil + default: + return fmt.Errorf("cannot scan %T as Version", src) + } +} + +// Value implements the driver.Valuer interface. +func (v *Version) Value() (driver.Value, error) { + return v.String(), nil +} diff --git a/cli/vendor/github.com/hashicorp/go-version/version_collection.go b/cli/vendor/github.com/hashicorp/go-version/version_collection.go new file mode 100644 index 0000000..11bc8b1 --- /dev/null +++ b/cli/vendor/github.com/hashicorp/go-version/version_collection.go @@ -0,0 +1,20 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package version + +// Collection is a type that implements the sort.Interface interface +// so that versions can be sorted. +type Collection []*Version + +func (v Collection) Len() int { + return len(v) +} + +func (v Collection) Less(i, j int) bool { + return v[i].LessThan(v[j]) +} + +func (v Collection) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} diff --git a/cli/vendor/golang.org/x/crypto/LICENSE b/cli/vendor/golang.org/x/crypto/LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cli/vendor/golang.org/x/crypto/PATENTS b/cli/vendor/golang.org/x/crypto/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/cli/vendor/golang.org/x/crypto/blake2b/blake2b.go b/cli/vendor/golang.org/x/crypto/blake2b/blake2b.go new file mode 100644 index 0000000..d2e98d4 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2b/blake2b.go @@ -0,0 +1,291 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blake2b implements the BLAKE2b hash algorithm defined by RFC 7693 +// and the extendable output function (XOF) BLAKE2Xb. +// +// BLAKE2b is optimized for 64-bit platforms—including NEON-enabled ARMs—and +// produces digests of any size between 1 and 64 bytes. +// For a detailed specification of BLAKE2b see https://blake2.net/blake2.pdf +// and for BLAKE2Xb see https://blake2.net/blake2x.pdf +// +// If you aren't sure which function you need, use BLAKE2b (Sum512 or New512). +// If you need a secret-key MAC (message authentication code), use the New512 +// function with a non-nil key. +// +// BLAKE2X is a construction to compute hash values larger than 64 bytes. It +// can produce hash values between 0 and 4 GiB. +package blake2b + +import ( + "encoding/binary" + "errors" + "hash" +) + +const ( + // The blocksize of BLAKE2b in bytes. + BlockSize = 128 + // The hash size of BLAKE2b-512 in bytes. + Size = 64 + // The hash size of BLAKE2b-384 in bytes. + Size384 = 48 + // The hash size of BLAKE2b-256 in bytes. + Size256 = 32 +) + +var ( + useAVX2 bool + useAVX bool + useSSE4 bool +) + +var ( + errKeySize = errors.New("blake2b: invalid key size") + errHashSize = errors.New("blake2b: invalid hash size") +) + +var iv = [8]uint64{ + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, +} + +// Sum512 returns the BLAKE2b-512 checksum of the data. +func Sum512(data []byte) [Size]byte { + var sum [Size]byte + checkSum(&sum, Size, data) + return sum +} + +// Sum384 returns the BLAKE2b-384 checksum of the data. +func Sum384(data []byte) [Size384]byte { + var sum [Size]byte + var sum384 [Size384]byte + checkSum(&sum, Size384, data) + copy(sum384[:], sum[:Size384]) + return sum384 +} + +// Sum256 returns the BLAKE2b-256 checksum of the data. +func Sum256(data []byte) [Size256]byte { + var sum [Size]byte + var sum256 [Size256]byte + checkSum(&sum, Size256, data) + copy(sum256[:], sum[:Size256]) + return sum256 +} + +// New512 returns a new hash.Hash computing the BLAKE2b-512 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New512(key []byte) (hash.Hash, error) { return newDigest(Size, key) } + +// New384 returns a new hash.Hash computing the BLAKE2b-384 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New384(key []byte) (hash.Hash, error) { return newDigest(Size384, key) } + +// New256 returns a new hash.Hash computing the BLAKE2b-256 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New256(key []byte) (hash.Hash, error) { return newDigest(Size256, key) } + +// New returns a new hash.Hash computing the BLAKE2b checksum with a custom length. +// A non-nil key turns the hash into a MAC. The key must be between zero and 64 bytes long. +// The hash size can be a value between 1 and 64 but it is highly recommended to use +// values equal or greater than: +// - 32 if BLAKE2b is used as a hash function (The key is zero bytes long). +// - 16 if BLAKE2b is used as a MAC function (The key is at least 16 bytes long). +// When the key is nil, the returned hash.Hash implements BinaryMarshaler +// and BinaryUnmarshaler for state (de)serialization as documented by hash.Hash. +func New(size int, key []byte) (hash.Hash, error) { return newDigest(size, key) } + +func newDigest(hashSize int, key []byte) (*digest, error) { + if hashSize < 1 || hashSize > Size { + return nil, errHashSize + } + if len(key) > Size { + return nil, errKeySize + } + d := &digest{ + size: hashSize, + keyLen: len(key), + } + copy(d.key[:], key) + d.Reset() + return d, nil +} + +func checkSum(sum *[Size]byte, hashSize int, data []byte) { + h := iv + h[0] ^= uint64(hashSize) | (1 << 16) | (1 << 24) + var c [2]uint64 + + if length := len(data); length > BlockSize { + n := length &^ (BlockSize - 1) + if length == n { + n -= BlockSize + } + hashBlocks(&h, &c, 0, data[:n]) + data = data[n:] + } + + var block [BlockSize]byte + offset := copy(block[:], data) + remaining := uint64(BlockSize - offset) + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) + + for i, v := range h[:(hashSize+7)/8] { + binary.LittleEndian.PutUint64(sum[8*i:], v) + } +} + +type digest struct { + h [8]uint64 + c [2]uint64 + size int + block [BlockSize]byte + offset int + + key [BlockSize]byte + keyLen int +} + +const ( + magic = "b2b" + marshaledSize = len(magic) + 8*8 + 2*8 + 1 + BlockSize + 1 +) + +func (d *digest) MarshalBinary() ([]byte, error) { + if d.keyLen != 0 { + return nil, errors.New("crypto/blake2b: cannot marshal MACs") + } + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + for i := 0; i < 8; i++ { + b = appendUint64(b, d.h[i]) + } + b = appendUint64(b, d.c[0]) + b = appendUint64(b, d.c[1]) + // Maximum value for size is 64 + b = append(b, byte(d.size)) + b = append(b, d.block[:]...) + b = append(b, byte(d.offset)) + return b, nil +} + +func (d *digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("crypto/blake2b: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/blake2b: invalid hash state size") + } + b = b[len(magic):] + for i := 0; i < 8; i++ { + b, d.h[i] = consumeUint64(b) + } + b, d.c[0] = consumeUint64(b) + b, d.c[1] = consumeUint64(b) + d.size = int(b[0]) + b = b[1:] + copy(d.block[:], b[:BlockSize]) + b = b[BlockSize:] + d.offset = int(b[0]) + return nil +} + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Size() int { return d.size } + +func (d *digest) Reset() { + d.h = iv + d.h[0] ^= uint64(d.size) | (uint64(d.keyLen) << 8) | (1 << 16) | (1 << 24) + d.offset, d.c[0], d.c[1] = 0, 0, 0 + if d.keyLen > 0 { + d.block = d.key + d.offset = BlockSize + } +} + +func (d *digest) Write(p []byte) (n int, err error) { + n = len(p) + + if d.offset > 0 { + remaining := BlockSize - d.offset + if n <= remaining { + d.offset += copy(d.block[d.offset:], p) + return + } + copy(d.block[d.offset:], p[:remaining]) + hashBlocks(&d.h, &d.c, 0, d.block[:]) + d.offset = 0 + p = p[remaining:] + } + + if length := len(p); length > BlockSize { + nn := length &^ (BlockSize - 1) + if length == nn { + nn -= BlockSize + } + hashBlocks(&d.h, &d.c, 0, p[:nn]) + p = p[nn:] + } + + if len(p) > 0 { + d.offset += copy(d.block[:], p) + } + + return +} + +func (d *digest) Sum(sum []byte) []byte { + var hash [Size]byte + d.finalize(&hash) + return append(sum, hash[:d.size]...) +} + +func (d *digest) finalize(hash *[Size]byte) { + var block [BlockSize]byte + copy(block[:], d.block[:d.offset]) + remaining := uint64(BlockSize - d.offset) + + c := d.c + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + h := d.h + hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) + + for i, v := range h { + binary.LittleEndian.PutUint64(hash[8*i:], v) + } +} + +func appendUint64(b []byte, x uint64) []byte { + var a [8]byte + binary.BigEndian.PutUint64(a[:], x) + return append(b, a[:]...) +} + +func appendUint32(b []byte, x uint32) []byte { + var a [4]byte + binary.BigEndian.PutUint32(a[:], x) + return append(b, a[:]...) +} + +func consumeUint64(b []byte) ([]byte, uint64) { + x := binary.BigEndian.Uint64(b) + return b[8:], x +} + +func consumeUint32(b []byte) ([]byte, uint32) { + x := binary.BigEndian.Uint32(b) + return b[4:], x +} diff --git a/cli/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go b/cli/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go new file mode 100644 index 0000000..199c21d --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go @@ -0,0 +1,37 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build amd64 && gc && !purego + +package blake2b + +import "golang.org/x/sys/cpu" + +func init() { + useAVX2 = cpu.X86.HasAVX2 + useAVX = cpu.X86.HasAVX + useSSE4 = cpu.X86.HasSSE41 +} + +//go:noescape +func hashBlocksAVX2(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) + +//go:noescape +func hashBlocksAVX(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) + +//go:noescape +func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) + +func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + switch { + case useAVX2: + hashBlocksAVX2(h, c, flag, blocks) + case useAVX: + hashBlocksAVX(h, c, flag, blocks) + case useSSE4: + hashBlocksSSE4(h, c, flag, blocks) + default: + hashBlocksGeneric(h, c, flag, blocks) + } +} diff --git a/cli/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s b/cli/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s new file mode 100644 index 0000000..f75162e --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s @@ -0,0 +1,4559 @@ +// Code generated by command: go run blake2bAVX2_amd64_asm.go -out ../../blake2bAVX2_amd64.s -pkg blake2b. DO NOT EDIT. + +//go:build amd64 && gc && !purego + +#include "textflag.h" + +// func hashBlocksAVX2(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) +// Requires: AVX, AVX2 +TEXT ·hashBlocksAVX2(SB), NOSPLIT, $320-48 + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVQ flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DI + MOVQ SP, DX + ADDQ $+31, DX + ANDQ $-32, DX + MOVQ CX, 16(DX) + XORQ CX, CX + MOVQ CX, 24(DX) + VMOVDQU ·AVX2_c40<>+0(SB), Y4 + VMOVDQU ·AVX2_c48<>+0(SB), Y5 + VMOVDQU (AX), Y8 + VMOVDQU 32(AX), Y9 + VMOVDQU ·AVX2_iv0<>+0(SB), Y6 + VMOVDQU ·AVX2_iv1<>+0(SB), Y7 + MOVQ (BX), R8 + MOVQ 8(BX), R9 + MOVQ R9, 8(DX) + +loop: + ADDQ $0x80, R8 + MOVQ R8, (DX) + CMPQ R8, $0x80 + JGE noinc + INCQ R9 + MOVQ R9, 8(DX) + +noinc: + VMOVDQA Y8, Y0 + VMOVDQA Y9, Y1 + VMOVDQA Y6, Y2 + VPXOR (DX), Y7, Y3 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x26 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x20 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x30 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x08 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x28 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x38 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x40 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x70 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x78 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VMOVDQA Y12, 32(DX) + VMOVDQA Y13, 64(DX) + VMOVDQA Y14, 96(DX) + VMOVDQA Y15, 128(DX) + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x48 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x68 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x78 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x30 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x58 + VPSHUFD $0x4e, (SI), X14 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x28 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x38 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x18 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VMOVDQA Y12, 160(DX) + VMOVDQA Y13, 192(DX) + VMOVDQA Y14, 224(DX) + VMOVDQA Y15, 256(DX) + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x28 + VMOVDQU 88(SI), X12 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x78 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x40 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x10 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x2e + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x68 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x38 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x48 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x08 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x20 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x58 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x70 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x20 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x78 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x30 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x1e + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x40 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x10 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x50 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x2e + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x20 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x78 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x30 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x18 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x08 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x40 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x60 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x68 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x1e + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x40 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x58 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x18 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x20 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x78 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x08 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x70 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x48 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x70 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x20 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x28 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x50 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x36 + VPSHUFD $0x4e, 64(SI), X11 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x30 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x10 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x58 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x18 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x58 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x08 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x48 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x28 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x40 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x10 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x3e + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x30 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x50 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x30 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x58 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x1e + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x78 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x18 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x40 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x08 + VMOVDQU 96(SI), X14 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x50 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x10 + VMOVDQU 32(SI), X11 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x38 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x38 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x08 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x10 + VPSHUFD $0x4e, 40(SI), X11 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x20 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x78 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x18 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x68 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x58 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x1e + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + VPADDQ 32(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ 64(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ 96(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ 128(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + VPADDQ 160(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ 192(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ 224(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ 256(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + VPXOR Y0, Y8, Y8 + VPXOR Y1, Y9, Y9 + VPXOR Y2, Y8, Y8 + VPXOR Y3, Y9, Y9 + LEAQ 128(SI), SI + SUBQ $0x80, DI + JNE loop + MOVQ R8, (BX) + MOVQ R9, 8(BX) + VMOVDQU Y8, (AX) + VMOVDQU Y9, 32(AX) + VZEROUPPER + RET + +DATA ·AVX2_c40<>+0(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+8(SB)/8, $0x0a09080f0e0d0c0b +DATA ·AVX2_c40<>+16(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+24(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX2_c40<>(SB), RODATA|NOPTR, $32 + +DATA ·AVX2_c48<>+0(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+8(SB)/8, $0x09080f0e0d0c0b0a +DATA ·AVX2_c48<>+16(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+24(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX2_c48<>(SB), RODATA|NOPTR, $32 + +DATA ·AVX2_iv0<>+0(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX2_iv0<>+8(SB)/8, $0xbb67ae8584caa73b +DATA ·AVX2_iv0<>+16(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX2_iv0<>+24(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX2_iv0<>(SB), RODATA|NOPTR, $32 + +DATA ·AVX2_iv1<>+0(SB)/8, $0x510e527fade682d1 +DATA ·AVX2_iv1<>+8(SB)/8, $0x9b05688c2b3e6c1f +DATA ·AVX2_iv1<>+16(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX2_iv1<>+24(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX2_iv1<>(SB), RODATA|NOPTR, $32 + +// func hashBlocksAVX(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) +// Requires: AVX, SSE2 +TEXT ·hashBlocksAVX(SB), NOSPLIT, $288-48 + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVQ flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DI + MOVQ SP, R10 + ADDQ $0x0f, R10 + ANDQ $-16, R10 + VMOVDQU ·AVX_c40<>+0(SB), X0 + VMOVDQU ·AVX_c48<>+0(SB), X1 + VMOVDQA X0, X8 + VMOVDQA X1, X9 + VMOVDQU ·AVX_iv3<>+0(SB), X0 + VMOVDQA X0, (R10) + XORQ CX, (R10) + VMOVDQU (AX), X10 + VMOVDQU 16(AX), X11 + VMOVDQU 32(AX), X2 + VMOVDQU 48(AX), X3 + MOVQ (BX), R8 + MOVQ 8(BX), R9 + +loop: + ADDQ $0x80, R8 + CMPQ R8, $0x80 + JGE noinc + INCQ R9 + +noinc: + BYTE $0xc4 + BYTE $0x41 + BYTE $0xf9 + BYTE $0x6e + BYTE $0xf8 + BYTE $0xc4 + BYTE $0x43 + BYTE $0x81 + BYTE $0x22 + BYTE $0xf9 + BYTE $0x01 + VMOVDQA X10, X0 + VMOVDQA X11, X1 + VMOVDQU ·AVX_iv0<>+0(SB), X4 + VMOVDQU ·AVX_iv1<>+0(SB), X5 + VMOVDQU ·AVX_iv2<>+0(SB), X6 + VPXOR X15, X6, X6 + VMOVDQA (R10), X7 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x26 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x20 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x08 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x28 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x38 + BYTE $0x01 + VMOVDQA X12, 16(R10) + VMOVDQA X13, 32(R10) + VMOVDQA X14, 48(R10) + VMOVDQA X15, 64(R10) + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x40 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x78 + BYTE $0x01 + VMOVDQA X12, 80(R10) + VMOVDQA X13, 96(R10) + VMOVDQA X14, 112(R10) + VMOVDQA X15, 128(R10) + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x78 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x68 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x30 + BYTE $0x01 + VMOVDQA X12, 144(R10) + VMOVDQA X13, 160(R10) + VMOVDQA X14, 176(R10) + VMOVDQA X15, 192(R10) + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + VPSHUFD $0x4e, (SI), X12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x58 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x38 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x18 + BYTE $0x01 + VMOVDQA X12, 208(R10) + VMOVDQA X13, 224(R10) + VMOVDQA X14, 240(R10) + VMOVDQA X15, 256(R10) + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + VMOVDQU 88(SI), X12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x28 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x40 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x10 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x36 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x68 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x08 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x20 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x70 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x20 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x30 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x3e + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x40 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x36 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x20 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x78 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x30 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x08 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x40 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x60 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x68 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x2e + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x58 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x18 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x20 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x78 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x70 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x48 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x28 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x50 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + MOVQ (SI), X12 + VPSHUFD $0x4e, 64(SI), X13 + MOVQ 56(SI), X14 + MOVQ 16(SI), X15 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x58 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x58 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x08 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x48 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + MOVQ 40(SI), X12 + MOVQ 64(SI), X13 + MOVQ (SI), X14 + MOVQ 48(SI), X15 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x50 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + MOVQ 48(SI), X12 + MOVQ 88(SI), X13 + MOVQ 120(SI), X14 + MOVQ 24(SI), X15 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x2e + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x40 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + VMOVDQU 96(SI), X12 + MOVQ 8(SI), X13 + MOVQ 16(SI), X14 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x38 + BYTE $0x01 + VMOVDQU 32(SI), X15 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x30 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x28 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + MOVQ 120(SI), X12 + MOVQ 24(SI), X13 + MOVQ 88(SI), X14 + MOVQ 96(SI), X15 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x68 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x3e + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + VPADDQ 16(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 32(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ 48(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 64(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + VPADDQ 80(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 96(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ 112(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 128(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + VPADDQ 144(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 160(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ 176(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 192(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + VPADDQ 208(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 224(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ 240(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 256(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + VMOVDQU 32(AX), X14 + VMOVDQU 48(AX), X15 + VPXOR X0, X10, X10 + VPXOR X1, X11, X11 + VPXOR X2, X14, X14 + VPXOR X3, X15, X15 + VPXOR X4, X10, X10 + VPXOR X5, X11, X11 + VPXOR X6, X14, X2 + VPXOR X7, X15, X3 + VMOVDQU X2, 32(AX) + VMOVDQU X3, 48(AX) + LEAQ 128(SI), SI + SUBQ $0x80, DI + JNE loop + VMOVDQU X10, (AX) + VMOVDQU X11, 16(AX) + MOVQ R8, (BX) + MOVQ R9, 8(BX) + VZEROUPPER + RET + +DATA ·AVX_c40<>+0(SB)/8, $0x0201000706050403 +DATA ·AVX_c40<>+8(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX_c40<>(SB), RODATA|NOPTR, $16 + +DATA ·AVX_c48<>+0(SB)/8, $0x0100070605040302 +DATA ·AVX_c48<>+8(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX_c48<>(SB), RODATA|NOPTR, $16 + +DATA ·AVX_iv3<>+0(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX_iv3<>+8(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX_iv3<>(SB), RODATA|NOPTR, $16 + +DATA ·AVX_iv0<>+0(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX_iv0<>+8(SB)/8, $0xbb67ae8584caa73b +GLOBL ·AVX_iv0<>(SB), RODATA|NOPTR, $16 + +DATA ·AVX_iv1<>+0(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX_iv1<>+8(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX_iv1<>(SB), RODATA|NOPTR, $16 + +DATA ·AVX_iv2<>+0(SB)/8, $0x510e527fade682d1 +DATA ·AVX_iv2<>+8(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·AVX_iv2<>(SB), RODATA|NOPTR, $16 diff --git a/cli/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s b/cli/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s new file mode 100644 index 0000000..9a0ce21 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s @@ -0,0 +1,1441 @@ +// Code generated by command: go run blake2b_amd64_asm.go -out ../../blake2b_amd64.s -pkg blake2b. DO NOT EDIT. + +//go:build amd64 && gc && !purego + +#include "textflag.h" + +// func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) +// Requires: SSE2, SSE4.1, SSSE3 +TEXT ·hashBlocksSSE4(SB), NOSPLIT, $288-48 + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVQ flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DI + MOVQ SP, R10 + ADDQ $0x0f, R10 + ANDQ $-16, R10 + MOVOU ·iv3<>+0(SB), X0 + MOVO X0, (R10) + XORQ CX, (R10) + MOVOU ·c40<>+0(SB), X13 + MOVOU ·c48<>+0(SB), X14 + MOVOU (AX), X12 + MOVOU 16(AX), X15 + MOVQ (BX), R8 + MOVQ 8(BX), R9 + +loop: + ADDQ $0x80, R8 + CMPQ R8, $0x80 + JGE noinc + INCQ R9 + +noinc: + MOVQ R8, X8 + PINSRQ $0x01, R9, X8 + MOVO X12, X0 + MOVO X15, X1 + MOVOU 32(AX), X2 + MOVOU 48(AX), X3 + MOVOU ·iv0<>+0(SB), X4 + MOVOU ·iv1<>+0(SB), X5 + MOVOU ·iv2<>+0(SB), X6 + PXOR X8, X6 + MOVO (R10), X7 + MOVQ (SI), X8 + PINSRQ $0x01, 16(SI), X8 + MOVQ 32(SI), X9 + PINSRQ $0x01, 48(SI), X9 + MOVQ 8(SI), X10 + PINSRQ $0x01, 24(SI), X10 + MOVQ 40(SI), X11 + PINSRQ $0x01, 56(SI), X11 + MOVO X8, 16(R10) + MOVO X9, 32(R10) + MOVO X10, 48(R10) + MOVO X11, 64(R10) + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 64(SI), X8 + PINSRQ $0x01, 80(SI), X8 + MOVQ 96(SI), X9 + PINSRQ $0x01, 112(SI), X9 + MOVQ 72(SI), X10 + PINSRQ $0x01, 88(SI), X10 + MOVQ 104(SI), X11 + PINSRQ $0x01, 120(SI), X11 + MOVO X8, 80(R10) + MOVO X9, 96(R10) + MOVO X10, 112(R10) + MOVO X11, 128(R10) + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 112(SI), X8 + PINSRQ $0x01, 32(SI), X8 + MOVQ 72(SI), X9 + PINSRQ $0x01, 104(SI), X9 + MOVQ 80(SI), X10 + PINSRQ $0x01, 64(SI), X10 + MOVQ 120(SI), X11 + PINSRQ $0x01, 48(SI), X11 + MOVO X8, 144(R10) + MOVO X9, 160(R10) + MOVO X10, 176(R10) + MOVO X11, 192(R10) + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 8(SI), X8 + PINSRQ $0x01, (SI), X8 + MOVQ 88(SI), X9 + PINSRQ $0x01, 40(SI), X9 + MOVQ 96(SI), X10 + PINSRQ $0x01, 16(SI), X10 + MOVQ 56(SI), X11 + PINSRQ $0x01, 24(SI), X11 + MOVO X8, 208(R10) + MOVO X9, 224(R10) + MOVO X10, 240(R10) + MOVO X11, 256(R10) + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 88(SI), X8 + PINSRQ $0x01, 96(SI), X8 + MOVQ 40(SI), X9 + PINSRQ $0x01, 120(SI), X9 + MOVQ 64(SI), X10 + PINSRQ $0x01, (SI), X10 + MOVQ 16(SI), X11 + PINSRQ $0x01, 104(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 80(SI), X8 + PINSRQ $0x01, 24(SI), X8 + MOVQ 56(SI), X9 + PINSRQ $0x01, 72(SI), X9 + MOVQ 112(SI), X10 + PINSRQ $0x01, 48(SI), X10 + MOVQ 8(SI), X11 + PINSRQ $0x01, 32(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 56(SI), X8 + PINSRQ $0x01, 24(SI), X8 + MOVQ 104(SI), X9 + PINSRQ $0x01, 88(SI), X9 + MOVQ 72(SI), X10 + PINSRQ $0x01, 8(SI), X10 + MOVQ 96(SI), X11 + PINSRQ $0x01, 112(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 16(SI), X8 + PINSRQ $0x01, 40(SI), X8 + MOVQ 32(SI), X9 + PINSRQ $0x01, 120(SI), X9 + MOVQ 48(SI), X10 + PINSRQ $0x01, 80(SI), X10 + MOVQ (SI), X11 + PINSRQ $0x01, 64(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 72(SI), X8 + PINSRQ $0x01, 40(SI), X8 + MOVQ 16(SI), X9 + PINSRQ $0x01, 80(SI), X9 + MOVQ (SI), X10 + PINSRQ $0x01, 56(SI), X10 + MOVQ 32(SI), X11 + PINSRQ $0x01, 120(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 112(SI), X8 + PINSRQ $0x01, 88(SI), X8 + MOVQ 48(SI), X9 + PINSRQ $0x01, 24(SI), X9 + MOVQ 8(SI), X10 + PINSRQ $0x01, 96(SI), X10 + MOVQ 64(SI), X11 + PINSRQ $0x01, 104(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 16(SI), X8 + PINSRQ $0x01, 48(SI), X8 + MOVQ (SI), X9 + PINSRQ $0x01, 64(SI), X9 + MOVQ 96(SI), X10 + PINSRQ $0x01, 80(SI), X10 + MOVQ 88(SI), X11 + PINSRQ $0x01, 24(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 32(SI), X8 + PINSRQ $0x01, 56(SI), X8 + MOVQ 120(SI), X9 + PINSRQ $0x01, 8(SI), X9 + MOVQ 104(SI), X10 + PINSRQ $0x01, 40(SI), X10 + MOVQ 112(SI), X11 + PINSRQ $0x01, 72(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 96(SI), X8 + PINSRQ $0x01, 8(SI), X8 + MOVQ 112(SI), X9 + PINSRQ $0x01, 32(SI), X9 + MOVQ 40(SI), X10 + PINSRQ $0x01, 120(SI), X10 + MOVQ 104(SI), X11 + PINSRQ $0x01, 80(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ (SI), X8 + PINSRQ $0x01, 48(SI), X8 + MOVQ 72(SI), X9 + PINSRQ $0x01, 64(SI), X9 + MOVQ 56(SI), X10 + PINSRQ $0x01, 24(SI), X10 + MOVQ 16(SI), X11 + PINSRQ $0x01, 88(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 104(SI), X8 + PINSRQ $0x01, 56(SI), X8 + MOVQ 96(SI), X9 + PINSRQ $0x01, 24(SI), X9 + MOVQ 88(SI), X10 + PINSRQ $0x01, 112(SI), X10 + MOVQ 8(SI), X11 + PINSRQ $0x01, 72(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 40(SI), X8 + PINSRQ $0x01, 120(SI), X8 + MOVQ 64(SI), X9 + PINSRQ $0x01, 16(SI), X9 + MOVQ (SI), X10 + PINSRQ $0x01, 32(SI), X10 + MOVQ 48(SI), X11 + PINSRQ $0x01, 80(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 48(SI), X8 + PINSRQ $0x01, 112(SI), X8 + MOVQ 88(SI), X9 + PINSRQ $0x01, (SI), X9 + MOVQ 120(SI), X10 + PINSRQ $0x01, 72(SI), X10 + MOVQ 24(SI), X11 + PINSRQ $0x01, 64(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 96(SI), X8 + PINSRQ $0x01, 104(SI), X8 + MOVQ 8(SI), X9 + PINSRQ $0x01, 80(SI), X9 + MOVQ 16(SI), X10 + PINSRQ $0x01, 56(SI), X10 + MOVQ 32(SI), X11 + PINSRQ $0x01, 40(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 80(SI), X8 + PINSRQ $0x01, 64(SI), X8 + MOVQ 56(SI), X9 + PINSRQ $0x01, 8(SI), X9 + MOVQ 16(SI), X10 + PINSRQ $0x01, 32(SI), X10 + MOVQ 48(SI), X11 + PINSRQ $0x01, 40(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 120(SI), X8 + PINSRQ $0x01, 72(SI), X8 + MOVQ 24(SI), X9 + PINSRQ $0x01, 104(SI), X9 + MOVQ 88(SI), X10 + PINSRQ $0x01, 112(SI), X10 + MOVQ 96(SI), X11 + PINSRQ $0x01, (SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + PADDQ 16(R10), X0 + PADDQ 32(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ 48(R10), X0 + PADDQ 64(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + PADDQ 80(R10), X0 + PADDQ 96(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ 112(R10), X0 + PADDQ 128(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + PADDQ 144(R10), X0 + PADDQ 160(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ 176(R10), X0 + PADDQ 192(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + PADDQ 208(R10), X0 + PADDQ 224(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ 240(R10), X0 + PADDQ 256(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU 32(AX), X10 + MOVOU 48(AX), X11 + PXOR X0, X12 + PXOR X1, X15 + PXOR X2, X10 + PXOR X3, X11 + PXOR X4, X12 + PXOR X5, X15 + PXOR X6, X10 + PXOR X7, X11 + MOVOU X10, 32(AX) + MOVOU X11, 48(AX) + LEAQ 128(SI), SI + SUBQ $0x80, DI + JNE loop + MOVOU X12, (AX) + MOVOU X15, 16(AX) + MOVQ R8, (BX) + MOVQ R9, 8(BX) + RET + +DATA ·iv3<>+0(SB)/8, $0x1f83d9abfb41bd6b +DATA ·iv3<>+8(SB)/8, $0x5be0cd19137e2179 +GLOBL ·iv3<>(SB), RODATA|NOPTR, $16 + +DATA ·c40<>+0(SB)/8, $0x0201000706050403 +DATA ·c40<>+8(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·c40<>(SB), RODATA|NOPTR, $16 + +DATA ·c48<>+0(SB)/8, $0x0100070605040302 +DATA ·c48<>+8(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·c48<>(SB), RODATA|NOPTR, $16 + +DATA ·iv0<>+0(SB)/8, $0x6a09e667f3bcc908 +DATA ·iv0<>+8(SB)/8, $0xbb67ae8584caa73b +GLOBL ·iv0<>(SB), RODATA|NOPTR, $16 + +DATA ·iv1<>+0(SB)/8, $0x3c6ef372fe94f82b +DATA ·iv1<>+8(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·iv1<>(SB), RODATA|NOPTR, $16 + +DATA ·iv2<>+0(SB)/8, $0x510e527fade682d1 +DATA ·iv2<>+8(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·iv2<>(SB), RODATA|NOPTR, $16 diff --git a/cli/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go b/cli/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go new file mode 100644 index 0000000..3168a8a --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go @@ -0,0 +1,182 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "encoding/binary" + "math/bits" +) + +// the precomputed values for BLAKE2b +// there are 12 16-byte arrays - one for each round +// the entries are calculated from the sigma constants. +var precomputed = [12][16]byte{ + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, + {11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4}, + {7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8}, + {9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13}, + {2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9}, + {12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11}, + {13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10}, + {6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5}, + {10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0}, + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, // equal to the first + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, // equal to the second +} + +func hashBlocksGeneric(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + var m [16]uint64 + c0, c1 := c[0], c[1] + + for i := 0; i < len(blocks); { + c0 += BlockSize + if c0 < BlockSize { + c1++ + } + + v0, v1, v2, v3, v4, v5, v6, v7 := h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7] + v8, v9, v10, v11, v12, v13, v14, v15 := iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7] + v12 ^= c0 + v13 ^= c1 + v14 ^= flag + + for j := range m { + m[j] = binary.LittleEndian.Uint64(blocks[i:]) + i += 8 + } + + for j := range precomputed { + s := &(precomputed[j]) + + v0 += m[s[0]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -32) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -24) + v1 += m[s[1]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -32) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -24) + v2 += m[s[2]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -32) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -24) + v3 += m[s[3]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -32) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -24) + + v0 += m[s[4]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -16) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -63) + v1 += m[s[5]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -16) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -63) + v2 += m[s[6]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -16) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -63) + v3 += m[s[7]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -16) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -63) + + v0 += m[s[8]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -32) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -24) + v1 += m[s[9]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -32) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -24) + v2 += m[s[10]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -32) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -24) + v3 += m[s[11]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -32) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -24) + + v0 += m[s[12]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -16) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -63) + v1 += m[s[13]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -16) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -63) + v2 += m[s[14]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -16) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -63) + v3 += m[s[15]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -16) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -63) + + } + + h[0] ^= v0 ^ v8 + h[1] ^= v1 ^ v9 + h[2] ^= v2 ^ v10 + h[3] ^= v3 ^ v11 + h[4] ^= v4 ^ v12 + h[5] ^= v5 ^ v13 + h[6] ^= v6 ^ v14 + h[7] ^= v7 ^ v15 + } + c[0], c[1] = c0, c1 +} diff --git a/cli/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go b/cli/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go new file mode 100644 index 0000000..6e28668 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go @@ -0,0 +1,11 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !amd64 || purego || !gc + +package blake2b + +func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + hashBlocksGeneric(h, c, flag, blocks) +} diff --git a/cli/vendor/golang.org/x/crypto/blake2b/blake2x.go b/cli/vendor/golang.org/x/crypto/blake2b/blake2x.go new file mode 100644 index 0000000..7692bb3 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2b/blake2x.go @@ -0,0 +1,185 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "encoding/binary" + "errors" + "io" +) + +// XOF defines the interface to hash functions that +// support arbitrary-length output. +// +// New callers should prefer the standard library [hash.XOF]. +type XOF interface { + // Write absorbs more data into the hash's state. It panics if called + // after Read. + io.Writer + + // Read reads more output from the hash. It returns io.EOF if the limit + // has been reached. + io.Reader + + // Clone returns a copy of the XOF in its current state. + Clone() XOF + + // Reset resets the XOF to its initial state. + Reset() +} + +// OutputLengthUnknown can be used as the size argument to NewXOF to indicate +// the length of the output is not known in advance. +const OutputLengthUnknown = 0 + +// magicUnknownOutputLength is a magic value for the output size that indicates +// an unknown number of output bytes. +const magicUnknownOutputLength = (1 << 32) - 1 + +// maxOutputLength is the absolute maximum number of bytes to produce when the +// number of output bytes is unknown. +const maxOutputLength = (1 << 32) * 64 + +// NewXOF creates a new variable-output-length hash. The hash either produce a +// known number of bytes (1 <= size < 2**32-1), or an unknown number of bytes +// (size == OutputLengthUnknown). In the latter case, an absolute limit of +// 256GiB applies. +// +// A non-nil key turns the hash into a MAC. The key must between +// zero and 32 bytes long. +// +// The result can be safely interface-upgraded to [hash.XOF]. +func NewXOF(size uint32, key []byte) (XOF, error) { + if len(key) > Size { + return nil, errKeySize + } + if size == magicUnknownOutputLength { + // 2^32-1 indicates an unknown number of bytes and thus isn't a + // valid length. + return nil, errors.New("blake2b: XOF length too large") + } + if size == OutputLengthUnknown { + size = magicUnknownOutputLength + } + x := &xof{ + d: digest{ + size: Size, + keyLen: len(key), + }, + length: size, + } + copy(x.d.key[:], key) + x.Reset() + return x, nil +} + +type xof struct { + d digest + length uint32 + remaining uint64 + cfg, root, block [Size]byte + offset int + nodeOffset uint32 + readMode bool +} + +func (x *xof) Write(p []byte) (n int, err error) { + if x.readMode { + panic("blake2b: write to XOF after read") + } + return x.d.Write(p) +} + +func (x *xof) Clone() XOF { + clone := *x + return &clone +} + +func (x *xof) BlockSize() int { + return x.d.BlockSize() +} + +func (x *xof) Reset() { + x.cfg[0] = byte(Size) + binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length + binary.LittleEndian.PutUint32(x.cfg[12:], x.length) // XOF length + x.cfg[17] = byte(Size) // inner hash size + + x.d.Reset() + x.d.h[1] ^= uint64(x.length) << 32 + + x.remaining = uint64(x.length) + if x.remaining == magicUnknownOutputLength { + x.remaining = maxOutputLength + } + x.offset, x.nodeOffset = 0, 0 + x.readMode = false +} + +func (x *xof) Read(p []byte) (n int, err error) { + if !x.readMode { + x.d.finalize(&x.root) + x.readMode = true + } + + if x.remaining == 0 { + return 0, io.EOF + } + + n = len(p) + if uint64(n) > x.remaining { + n = int(x.remaining) + p = p[:n] + } + + if x.offset > 0 { + blockRemaining := Size - x.offset + if n < blockRemaining { + x.offset += copy(p, x.block[x.offset:]) + x.remaining -= uint64(n) + return + } + copy(p, x.block[x.offset:]) + p = p[blockRemaining:] + x.offset = 0 + x.remaining -= uint64(blockRemaining) + } + + for len(p) >= Size { + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + copy(p, x.block[:]) + p = p[Size:] + x.remaining -= uint64(Size) + } + + if todo := len(p); todo > 0 { + if x.remaining < uint64(Size) { + x.cfg[0] = byte(x.remaining) + } + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + x.offset = copy(p, x.block[:todo]) + x.remaining -= uint64(todo) + } + return +} + +func (d *digest) initConfig(cfg *[Size]byte) { + d.offset, d.c[0], d.c[1] = 0, 0, 0 + for i := range d.h { + d.h[i] = iv[i] ^ binary.LittleEndian.Uint64(cfg[i*8:]) + } +} diff --git a/cli/vendor/golang.org/x/crypto/blake2b/go125.go b/cli/vendor/golang.org/x/crypto/blake2b/go125.go new file mode 100644 index 0000000..67e990b --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2b/go125.go @@ -0,0 +1,11 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.25 + +package blake2b + +import "hash" + +var _ hash.XOF = (*xof)(nil) diff --git a/cli/vendor/golang.org/x/crypto/blake2b/register.go b/cli/vendor/golang.org/x/crypto/blake2b/register.go new file mode 100644 index 0000000..54e446e --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2b/register.go @@ -0,0 +1,30 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "crypto" + "hash" +) + +func init() { + newHash256 := func() hash.Hash { + h, _ := New256(nil) + return h + } + newHash384 := func() hash.Hash { + h, _ := New384(nil) + return h + } + + newHash512 := func() hash.Hash { + h, _ := New512(nil) + return h + } + + crypto.RegisterHash(crypto.BLAKE2b_256, newHash256) + crypto.RegisterHash(crypto.BLAKE2b_384, newHash384) + crypto.RegisterHash(crypto.BLAKE2b_512, newHash512) +} diff --git a/cli/vendor/golang.org/x/crypto/blake2s/blake2s.go b/cli/vendor/golang.org/x/crypto/blake2s/blake2s.go new file mode 100644 index 0000000..c25d07d --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2s/blake2s.go @@ -0,0 +1,254 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blake2s implements the BLAKE2s hash algorithm defined by RFC 7693 +// and the extendable output function (XOF) BLAKE2Xs. +// +// BLAKE2s is optimized for 8- to 32-bit platforms and produces digests of any +// size between 1 and 32 bytes. +// For a detailed specification of BLAKE2s see https://blake2.net/blake2.pdf +// and for BLAKE2Xs see https://blake2.net/blake2x.pdf +// +// If you aren't sure which function you need, use BLAKE2s (Sum256 or New256). +// If you need a secret-key MAC (message authentication code), use the New256 +// function with a non-nil key. +// +// BLAKE2X is a construction to compute hash values larger than 32 bytes. It +// can produce hash values between 0 and 65535 bytes. +package blake2s + +import ( + "crypto" + "encoding/binary" + "errors" + "hash" +) + +const ( + // The blocksize of BLAKE2s in bytes. + BlockSize = 64 + + // The hash size of BLAKE2s-256 in bytes. + Size = 32 + + // The hash size of BLAKE2s-128 in bytes. + Size128 = 16 +) + +var errKeySize = errors.New("blake2s: invalid key size") + +var iv = [8]uint32{ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +} + +// Sum256 returns the BLAKE2s-256 checksum of the data. +func Sum256(data []byte) [Size]byte { + var sum [Size]byte + checkSum(&sum, Size, data) + return sum +} + +// New256 returns a new hash.Hash computing the BLAKE2s-256 checksum. A non-nil +// key turns the hash into a MAC. The key must between zero and 32 bytes long. +// When the key is nil, the returned hash.Hash implements BinaryMarshaler +// and BinaryUnmarshaler for state (de)serialization as documented by hash.Hash. +func New256(key []byte) (hash.Hash, error) { return newDigest(Size, key) } + +func init() { + crypto.RegisterHash(crypto.BLAKE2s_256, func() hash.Hash { + h, _ := New256(nil) + return h + }) +} + +// New128 returns a new hash.Hash computing the BLAKE2s-128 checksum given a +// non-empty key. Note that a 128-bit digest is too small to be secure as a +// cryptographic hash and should only be used as a MAC, thus the key argument +// is not optional. +func New128(key []byte) (hash.Hash, error) { + if len(key) == 0 { + return nil, errors.New("blake2s: a key is required for a 128-bit hash") + } + return newDigest(Size128, key) +} + +func newDigest(hashSize int, key []byte) (*digest, error) { + if len(key) > Size { + return nil, errKeySize + } + d := &digest{ + size: hashSize, + keyLen: len(key), + } + copy(d.key[:], key) + d.Reset() + return d, nil +} + +func checkSum(sum *[Size]byte, hashSize int, data []byte) { + var ( + h [8]uint32 + c [2]uint32 + ) + + h = iv + h[0] ^= uint32(hashSize) | (1 << 16) | (1 << 24) + + if length := len(data); length > BlockSize { + n := length &^ (BlockSize - 1) + if length == n { + n -= BlockSize + } + hashBlocks(&h, &c, 0, data[:n]) + data = data[n:] + } + + var block [BlockSize]byte + offset := copy(block[:], data) + remaining := uint32(BlockSize - offset) + + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + hashBlocks(&h, &c, 0xFFFFFFFF, block[:]) + + for i, v := range h { + binary.LittleEndian.PutUint32(sum[4*i:], v) + } +} + +type digest struct { + h [8]uint32 + c [2]uint32 + size int + block [BlockSize]byte + offset int + + key [BlockSize]byte + keyLen int +} + +const ( + magic = "b2s" + marshaledSize = len(magic) + 8*4 + 2*4 + 1 + BlockSize + 1 +) + +func (d *digest) MarshalBinary() ([]byte, error) { + if d.keyLen != 0 { + return nil, errors.New("crypto/blake2s: cannot marshal MACs") + } + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + for i := 0; i < 8; i++ { + b = appendUint32(b, d.h[i]) + } + b = appendUint32(b, d.c[0]) + b = appendUint32(b, d.c[1]) + // Maximum value for size is 32 + b = append(b, byte(d.size)) + b = append(b, d.block[:]...) + b = append(b, byte(d.offset)) + return b, nil +} + +func (d *digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("crypto/blake2s: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/blake2s: invalid hash state size") + } + b = b[len(magic):] + for i := 0; i < 8; i++ { + b, d.h[i] = consumeUint32(b) + } + b, d.c[0] = consumeUint32(b) + b, d.c[1] = consumeUint32(b) + d.size = int(b[0]) + b = b[1:] + copy(d.block[:], b[:BlockSize]) + b = b[BlockSize:] + d.offset = int(b[0]) + return nil +} + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Size() int { return d.size } + +func (d *digest) Reset() { + d.h = iv + d.h[0] ^= uint32(d.size) | (uint32(d.keyLen) << 8) | (1 << 16) | (1 << 24) + d.offset, d.c[0], d.c[1] = 0, 0, 0 + if d.keyLen > 0 { + d.block = d.key + d.offset = BlockSize + } +} + +func (d *digest) Write(p []byte) (n int, err error) { + n = len(p) + + if d.offset > 0 { + remaining := BlockSize - d.offset + if n <= remaining { + d.offset += copy(d.block[d.offset:], p) + return + } + copy(d.block[d.offset:], p[:remaining]) + hashBlocks(&d.h, &d.c, 0, d.block[:]) + d.offset = 0 + p = p[remaining:] + } + + if length := len(p); length > BlockSize { + nn := length &^ (BlockSize - 1) + if length == nn { + nn -= BlockSize + } + hashBlocks(&d.h, &d.c, 0, p[:nn]) + p = p[nn:] + } + + d.offset += copy(d.block[:], p) + return +} + +func (d *digest) Sum(sum []byte) []byte { + var hash [Size]byte + d.finalize(&hash) + return append(sum, hash[:d.size]...) +} + +func (d *digest) finalize(hash *[Size]byte) { + var block [BlockSize]byte + h := d.h + c := d.c + + copy(block[:], d.block[:d.offset]) + remaining := uint32(BlockSize - d.offset) + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + hashBlocks(&h, &c, 0xFFFFFFFF, block[:]) + for i, v := range h { + binary.LittleEndian.PutUint32(hash[4*i:], v) + } +} + +func appendUint32(b []byte, x uint32) []byte { + var a [4]byte + binary.BigEndian.PutUint32(a[:], x) + return append(b, a[:]...) +} + +func consumeUint32(b []byte) ([]byte, uint32) { + x := binary.BigEndian.Uint32(b) + return b[4:], x +} diff --git a/cli/vendor/golang.org/x/crypto/blake2s/blake2s_386.go b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_386.go new file mode 100644 index 0000000..97f6296 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_386.go @@ -0,0 +1,32 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build 386 && gc && !purego + +package blake2s + +import "golang.org/x/sys/cpu" + +var ( + useSSE4 = false + useSSSE3 = cpu.X86.HasSSSE3 + useSSE2 = cpu.X86.HasSSE2 +) + +//go:noescape +func hashBlocksSSE2(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) + +//go:noescape +func hashBlocksSSSE3(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) + +func hashBlocks(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) { + switch { + case useSSSE3: + hashBlocksSSSE3(h, c, flag, blocks) + case useSSE2: + hashBlocksSSE2(h, c, flag, blocks) + default: + hashBlocksGeneric(h, c, flag, blocks) + } +} diff --git a/cli/vendor/golang.org/x/crypto/blake2s/blake2s_386.s b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_386.s new file mode 100644 index 0000000..919c026 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_386.s @@ -0,0 +1,429 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build 386 && gc && !purego + +#include "textflag.h" + +DATA iv0<>+0x00(SB)/4, $0x6a09e667 +DATA iv0<>+0x04(SB)/4, $0xbb67ae85 +DATA iv0<>+0x08(SB)/4, $0x3c6ef372 +DATA iv0<>+0x0c(SB)/4, $0xa54ff53a +GLOBL iv0<>(SB), (NOPTR+RODATA), $16 + +DATA iv1<>+0x00(SB)/4, $0x510e527f +DATA iv1<>+0x04(SB)/4, $0x9b05688c +DATA iv1<>+0x08(SB)/4, $0x1f83d9ab +DATA iv1<>+0x0c(SB)/4, $0x5be0cd19 +GLOBL iv1<>(SB), (NOPTR+RODATA), $16 + +DATA rol16<>+0x00(SB)/8, $0x0504070601000302 +DATA rol16<>+0x08(SB)/8, $0x0D0C0F0E09080B0A +GLOBL rol16<>(SB), (NOPTR+RODATA), $16 + +DATA rol8<>+0x00(SB)/8, $0x0407060500030201 +DATA rol8<>+0x08(SB)/8, $0x0C0F0E0D080B0A09 +GLOBL rol8<>(SB), (NOPTR+RODATA), $16 + +DATA counter<>+0x00(SB)/8, $0x40 +DATA counter<>+0x08(SB)/8, $0x0 +GLOBL counter<>(SB), (NOPTR+RODATA), $16 + +#define ROTL_SSE2(n, t, v) \ + MOVO v, t; \ + PSLLL $n, t; \ + PSRLL $(32-n), v; \ + PXOR t, v + +#define ROTL_SSSE3(c, v) \ + PSHUFB c, v + +#define ROUND_SSE2(v0, v1, v2, v3, m0, m1, m2, m3, t) \ + PADDL m0, v0; \ + PADDL v1, v0; \ + PXOR v0, v3; \ + ROTL_SSE2(16, t, v3); \ + PADDL v3, v2; \ + PXOR v2, v1; \ + ROTL_SSE2(20, t, v1); \ + PADDL m1, v0; \ + PADDL v1, v0; \ + PXOR v0, v3; \ + ROTL_SSE2(24, t, v3); \ + PADDL v3, v2; \ + PXOR v2, v1; \ + ROTL_SSE2(25, t, v1); \ + PSHUFL $0x39, v1, v1; \ + PSHUFL $0x4E, v2, v2; \ + PSHUFL $0x93, v3, v3; \ + PADDL m2, v0; \ + PADDL v1, v0; \ + PXOR v0, v3; \ + ROTL_SSE2(16, t, v3); \ + PADDL v3, v2; \ + PXOR v2, v1; \ + ROTL_SSE2(20, t, v1); \ + PADDL m3, v0; \ + PADDL v1, v0; \ + PXOR v0, v3; \ + ROTL_SSE2(24, t, v3); \ + PADDL v3, v2; \ + PXOR v2, v1; \ + ROTL_SSE2(25, t, v1); \ + PSHUFL $0x39, v3, v3; \ + PSHUFL $0x4E, v2, v2; \ + PSHUFL $0x93, v1, v1 + +#define ROUND_SSSE3(v0, v1, v2, v3, m0, m1, m2, m3, t, c16, c8) \ + PADDL m0, v0; \ + PADDL v1, v0; \ + PXOR v0, v3; \ + ROTL_SSSE3(c16, v3); \ + PADDL v3, v2; \ + PXOR v2, v1; \ + ROTL_SSE2(20, t, v1); \ + PADDL m1, v0; \ + PADDL v1, v0; \ + PXOR v0, v3; \ + ROTL_SSSE3(c8, v3); \ + PADDL v3, v2; \ + PXOR v2, v1; \ + ROTL_SSE2(25, t, v1); \ + PSHUFL $0x39, v1, v1; \ + PSHUFL $0x4E, v2, v2; \ + PSHUFL $0x93, v3, v3; \ + PADDL m2, v0; \ + PADDL v1, v0; \ + PXOR v0, v3; \ + ROTL_SSSE3(c16, v3); \ + PADDL v3, v2; \ + PXOR v2, v1; \ + ROTL_SSE2(20, t, v1); \ + PADDL m3, v0; \ + PADDL v1, v0; \ + PXOR v0, v3; \ + ROTL_SSSE3(c8, v3); \ + PADDL v3, v2; \ + PXOR v2, v1; \ + ROTL_SSE2(25, t, v1); \ + PSHUFL $0x39, v3, v3; \ + PSHUFL $0x4E, v2, v2; \ + PSHUFL $0x93, v1, v1 + +#define PRECOMPUTE(dst, off, src, t) \ + MOVL 0*4(src), t; \ + MOVL t, 0*4+off+0(dst); \ + MOVL t, 9*4+off+64(dst); \ + MOVL t, 5*4+off+128(dst); \ + MOVL t, 14*4+off+192(dst); \ + MOVL t, 4*4+off+256(dst); \ + MOVL t, 2*4+off+320(dst); \ + MOVL t, 8*4+off+384(dst); \ + MOVL t, 12*4+off+448(dst); \ + MOVL t, 3*4+off+512(dst); \ + MOVL t, 15*4+off+576(dst); \ + MOVL 1*4(src), t; \ + MOVL t, 4*4+off+0(dst); \ + MOVL t, 8*4+off+64(dst); \ + MOVL t, 14*4+off+128(dst); \ + MOVL t, 5*4+off+192(dst); \ + MOVL t, 12*4+off+256(dst); \ + MOVL t, 11*4+off+320(dst); \ + MOVL t, 1*4+off+384(dst); \ + MOVL t, 6*4+off+448(dst); \ + MOVL t, 10*4+off+512(dst); \ + MOVL t, 3*4+off+576(dst); \ + MOVL 2*4(src), t; \ + MOVL t, 1*4+off+0(dst); \ + MOVL t, 13*4+off+64(dst); \ + MOVL t, 6*4+off+128(dst); \ + MOVL t, 8*4+off+192(dst); \ + MOVL t, 2*4+off+256(dst); \ + MOVL t, 0*4+off+320(dst); \ + MOVL t, 14*4+off+384(dst); \ + MOVL t, 11*4+off+448(dst); \ + MOVL t, 12*4+off+512(dst); \ + MOVL t, 4*4+off+576(dst); \ + MOVL 3*4(src), t; \ + MOVL t, 5*4+off+0(dst); \ + MOVL t, 15*4+off+64(dst); \ + MOVL t, 9*4+off+128(dst); \ + MOVL t, 1*4+off+192(dst); \ + MOVL t, 11*4+off+256(dst); \ + MOVL t, 7*4+off+320(dst); \ + MOVL t, 13*4+off+384(dst); \ + MOVL t, 3*4+off+448(dst); \ + MOVL t, 6*4+off+512(dst); \ + MOVL t, 10*4+off+576(dst); \ + MOVL 4*4(src), t; \ + MOVL t, 2*4+off+0(dst); \ + MOVL t, 1*4+off+64(dst); \ + MOVL t, 15*4+off+128(dst); \ + MOVL t, 10*4+off+192(dst); \ + MOVL t, 6*4+off+256(dst); \ + MOVL t, 8*4+off+320(dst); \ + MOVL t, 3*4+off+384(dst); \ + MOVL t, 13*4+off+448(dst); \ + MOVL t, 14*4+off+512(dst); \ + MOVL t, 5*4+off+576(dst); \ + MOVL 5*4(src), t; \ + MOVL t, 6*4+off+0(dst); \ + MOVL t, 11*4+off+64(dst); \ + MOVL t, 2*4+off+128(dst); \ + MOVL t, 9*4+off+192(dst); \ + MOVL t, 1*4+off+256(dst); \ + MOVL t, 13*4+off+320(dst); \ + MOVL t, 4*4+off+384(dst); \ + MOVL t, 8*4+off+448(dst); \ + MOVL t, 15*4+off+512(dst); \ + MOVL t, 7*4+off+576(dst); \ + MOVL 6*4(src), t; \ + MOVL t, 3*4+off+0(dst); \ + MOVL t, 7*4+off+64(dst); \ + MOVL t, 13*4+off+128(dst); \ + MOVL t, 12*4+off+192(dst); \ + MOVL t, 10*4+off+256(dst); \ + MOVL t, 1*4+off+320(dst); \ + MOVL t, 9*4+off+384(dst); \ + MOVL t, 14*4+off+448(dst); \ + MOVL t, 0*4+off+512(dst); \ + MOVL t, 6*4+off+576(dst); \ + MOVL 7*4(src), t; \ + MOVL t, 7*4+off+0(dst); \ + MOVL t, 14*4+off+64(dst); \ + MOVL t, 10*4+off+128(dst); \ + MOVL t, 0*4+off+192(dst); \ + MOVL t, 5*4+off+256(dst); \ + MOVL t, 9*4+off+320(dst); \ + MOVL t, 12*4+off+384(dst); \ + MOVL t, 1*4+off+448(dst); \ + MOVL t, 13*4+off+512(dst); \ + MOVL t, 2*4+off+576(dst); \ + MOVL 8*4(src), t; \ + MOVL t, 8*4+off+0(dst); \ + MOVL t, 5*4+off+64(dst); \ + MOVL t, 4*4+off+128(dst); \ + MOVL t, 15*4+off+192(dst); \ + MOVL t, 14*4+off+256(dst); \ + MOVL t, 3*4+off+320(dst); \ + MOVL t, 11*4+off+384(dst); \ + MOVL t, 10*4+off+448(dst); \ + MOVL t, 7*4+off+512(dst); \ + MOVL t, 1*4+off+576(dst); \ + MOVL 9*4(src), t; \ + MOVL t, 12*4+off+0(dst); \ + MOVL t, 2*4+off+64(dst); \ + MOVL t, 11*4+off+128(dst); \ + MOVL t, 4*4+off+192(dst); \ + MOVL t, 0*4+off+256(dst); \ + MOVL t, 15*4+off+320(dst); \ + MOVL t, 10*4+off+384(dst); \ + MOVL t, 7*4+off+448(dst); \ + MOVL t, 5*4+off+512(dst); \ + MOVL t, 9*4+off+576(dst); \ + MOVL 10*4(src), t; \ + MOVL t, 9*4+off+0(dst); \ + MOVL t, 4*4+off+64(dst); \ + MOVL t, 8*4+off+128(dst); \ + MOVL t, 13*4+off+192(dst); \ + MOVL t, 3*4+off+256(dst); \ + MOVL t, 5*4+off+320(dst); \ + MOVL t, 7*4+off+384(dst); \ + MOVL t, 15*4+off+448(dst); \ + MOVL t, 11*4+off+512(dst); \ + MOVL t, 0*4+off+576(dst); \ + MOVL 11*4(src), t; \ + MOVL t, 13*4+off+0(dst); \ + MOVL t, 10*4+off+64(dst); \ + MOVL t, 0*4+off+128(dst); \ + MOVL t, 3*4+off+192(dst); \ + MOVL t, 9*4+off+256(dst); \ + MOVL t, 6*4+off+320(dst); \ + MOVL t, 15*4+off+384(dst); \ + MOVL t, 4*4+off+448(dst); \ + MOVL t, 2*4+off+512(dst); \ + MOVL t, 12*4+off+576(dst); \ + MOVL 12*4(src), t; \ + MOVL t, 10*4+off+0(dst); \ + MOVL t, 12*4+off+64(dst); \ + MOVL t, 1*4+off+128(dst); \ + MOVL t, 6*4+off+192(dst); \ + MOVL t, 13*4+off+256(dst); \ + MOVL t, 4*4+off+320(dst); \ + MOVL t, 0*4+off+384(dst); \ + MOVL t, 2*4+off+448(dst); \ + MOVL t, 8*4+off+512(dst); \ + MOVL t, 14*4+off+576(dst); \ + MOVL 13*4(src), t; \ + MOVL t, 14*4+off+0(dst); \ + MOVL t, 3*4+off+64(dst); \ + MOVL t, 7*4+off+128(dst); \ + MOVL t, 2*4+off+192(dst); \ + MOVL t, 15*4+off+256(dst); \ + MOVL t, 12*4+off+320(dst); \ + MOVL t, 6*4+off+384(dst); \ + MOVL t, 0*4+off+448(dst); \ + MOVL t, 9*4+off+512(dst); \ + MOVL t, 11*4+off+576(dst); \ + MOVL 14*4(src), t; \ + MOVL t, 11*4+off+0(dst); \ + MOVL t, 0*4+off+64(dst); \ + MOVL t, 12*4+off+128(dst); \ + MOVL t, 7*4+off+192(dst); \ + MOVL t, 8*4+off+256(dst); \ + MOVL t, 14*4+off+320(dst); \ + MOVL t, 2*4+off+384(dst); \ + MOVL t, 5*4+off+448(dst); \ + MOVL t, 1*4+off+512(dst); \ + MOVL t, 13*4+off+576(dst); \ + MOVL 15*4(src), t; \ + MOVL t, 15*4+off+0(dst); \ + MOVL t, 6*4+off+64(dst); \ + MOVL t, 3*4+off+128(dst); \ + MOVL t, 11*4+off+192(dst); \ + MOVL t, 7*4+off+256(dst); \ + MOVL t, 10*4+off+320(dst); \ + MOVL t, 5*4+off+384(dst); \ + MOVL t, 9*4+off+448(dst); \ + MOVL t, 4*4+off+512(dst); \ + MOVL t, 8*4+off+576(dst) + +// func hashBlocksSSE2(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) +TEXT ·hashBlocksSSE2(SB), 0, $672-24 // frame = 656 + 16 byte alignment + MOVL h+0(FP), AX + MOVL c+4(FP), BX + MOVL flag+8(FP), CX + MOVL blocks_base+12(FP), SI + MOVL blocks_len+16(FP), DX + + MOVL SP, DI + ADDL $15, DI + ANDL $~15, DI + + MOVL CX, 8(DI) + MOVL 0(BX), CX + MOVL CX, 0(DI) + MOVL 4(BX), CX + MOVL CX, 4(DI) + XORL CX, CX + MOVL CX, 12(DI) + + MOVOU 0(AX), X0 + MOVOU 16(AX), X1 + MOVOU counter<>(SB), X2 + +loop: + MOVO X0, X4 + MOVO X1, X5 + MOVOU iv0<>(SB), X6 + MOVOU iv1<>(SB), X7 + + MOVO 0(DI), X3 + PADDQ X2, X3 + PXOR X3, X7 + MOVO X3, 0(DI) + + PRECOMPUTE(DI, 16, SI, CX) + ROUND_SSE2(X4, X5, X6, X7, 16(DI), 32(DI), 48(DI), 64(DI), X3) + ROUND_SSE2(X4, X5, X6, X7, 16+64(DI), 32+64(DI), 48+64(DI), 64+64(DI), X3) + ROUND_SSE2(X4, X5, X6, X7, 16+128(DI), 32+128(DI), 48+128(DI), 64+128(DI), X3) + ROUND_SSE2(X4, X5, X6, X7, 16+192(DI), 32+192(DI), 48+192(DI), 64+192(DI), X3) + ROUND_SSE2(X4, X5, X6, X7, 16+256(DI), 32+256(DI), 48+256(DI), 64+256(DI), X3) + ROUND_SSE2(X4, X5, X6, X7, 16+320(DI), 32+320(DI), 48+320(DI), 64+320(DI), X3) + ROUND_SSE2(X4, X5, X6, X7, 16+384(DI), 32+384(DI), 48+384(DI), 64+384(DI), X3) + ROUND_SSE2(X4, X5, X6, X7, 16+448(DI), 32+448(DI), 48+448(DI), 64+448(DI), X3) + ROUND_SSE2(X4, X5, X6, X7, 16+512(DI), 32+512(DI), 48+512(DI), 64+512(DI), X3) + ROUND_SSE2(X4, X5, X6, X7, 16+576(DI), 32+576(DI), 48+576(DI), 64+576(DI), X3) + + PXOR X4, X0 + PXOR X5, X1 + PXOR X6, X0 + PXOR X7, X1 + + LEAL 64(SI), SI + SUBL $64, DX + JNE loop + + MOVL 0(DI), CX + MOVL CX, 0(BX) + MOVL 4(DI), CX + MOVL CX, 4(BX) + + MOVOU X0, 0(AX) + MOVOU X1, 16(AX) + + RET + +// func hashBlocksSSSE3(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) +TEXT ·hashBlocksSSSE3(SB), 0, $704-24 // frame = 688 + 16 byte alignment + MOVL h+0(FP), AX + MOVL c+4(FP), BX + MOVL flag+8(FP), CX + MOVL blocks_base+12(FP), SI + MOVL blocks_len+16(FP), DX + + MOVL SP, DI + ADDL $15, DI + ANDL $~15, DI + + MOVL CX, 8(DI) + MOVL 0(BX), CX + MOVL CX, 0(DI) + MOVL 4(BX), CX + MOVL CX, 4(DI) + XORL CX, CX + MOVL CX, 12(DI) + + MOVOU 0(AX), X0 + MOVOU 16(AX), X1 + MOVOU counter<>(SB), X2 + +loop: + MOVO X0, 656(DI) + MOVO X1, 672(DI) + MOVO X0, X4 + MOVO X1, X5 + MOVOU iv0<>(SB), X6 + MOVOU iv1<>(SB), X7 + + MOVO 0(DI), X3 + PADDQ X2, X3 + PXOR X3, X7 + MOVO X3, 0(DI) + + MOVOU rol16<>(SB), X0 + MOVOU rol8<>(SB), X1 + + PRECOMPUTE(DI, 16, SI, CX) + ROUND_SSSE3(X4, X5, X6, X7, 16(DI), 32(DI), 48(DI), 64(DI), X3, X0, X1) + ROUND_SSSE3(X4, X5, X6, X7, 16+64(DI), 32+64(DI), 48+64(DI), 64+64(DI), X3, X0, X1) + ROUND_SSSE3(X4, X5, X6, X7, 16+128(DI), 32+128(DI), 48+128(DI), 64+128(DI), X3, X0, X1) + ROUND_SSSE3(X4, X5, X6, X7, 16+192(DI), 32+192(DI), 48+192(DI), 64+192(DI), X3, X0, X1) + ROUND_SSSE3(X4, X5, X6, X7, 16+256(DI), 32+256(DI), 48+256(DI), 64+256(DI), X3, X0, X1) + ROUND_SSSE3(X4, X5, X6, X7, 16+320(DI), 32+320(DI), 48+320(DI), 64+320(DI), X3, X0, X1) + ROUND_SSSE3(X4, X5, X6, X7, 16+384(DI), 32+384(DI), 48+384(DI), 64+384(DI), X3, X0, X1) + ROUND_SSSE3(X4, X5, X6, X7, 16+448(DI), 32+448(DI), 48+448(DI), 64+448(DI), X3, X0, X1) + ROUND_SSSE3(X4, X5, X6, X7, 16+512(DI), 32+512(DI), 48+512(DI), 64+512(DI), X3, X0, X1) + ROUND_SSSE3(X4, X5, X6, X7, 16+576(DI), 32+576(DI), 48+576(DI), 64+576(DI), X3, X0, X1) + + MOVO 656(DI), X0 + MOVO 672(DI), X1 + PXOR X4, X0 + PXOR X5, X1 + PXOR X6, X0 + PXOR X7, X1 + + LEAL 64(SI), SI + SUBL $64, DX + JNE loop + + MOVL 0(DI), CX + MOVL CX, 0(BX) + MOVL 4(DI), CX + MOVL CX, 4(BX) + + MOVOU X0, 0(AX) + MOVOU X1, 16(AX) + + RET diff --git a/cli/vendor/golang.org/x/crypto/blake2s/blake2s_amd64.go b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_amd64.go new file mode 100644 index 0000000..8a73102 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_amd64.go @@ -0,0 +1,37 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build amd64 && gc && !purego + +package blake2s + +import "golang.org/x/sys/cpu" + +var ( + useSSE4 = cpu.X86.HasSSE41 + useSSSE3 = cpu.X86.HasSSSE3 + useSSE2 = cpu.X86.HasSSE2 +) + +//go:noescape +func hashBlocksSSE2(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) + +//go:noescape +func hashBlocksSSSE3(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) + +//go:noescape +func hashBlocksSSE4(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) + +func hashBlocks(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) { + switch { + case useSSE4: + hashBlocksSSE4(h, c, flag, blocks) + case useSSSE3: + hashBlocksSSSE3(h, c, flag, blocks) + case useSSE2: + hashBlocksSSE2(h, c, flag, blocks) + default: + hashBlocksGeneric(h, c, flag, blocks) + } +} diff --git a/cli/vendor/golang.org/x/crypto/blake2s/blake2s_amd64.s b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_amd64.s new file mode 100644 index 0000000..57d510f --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_amd64.s @@ -0,0 +1,2173 @@ +// Code generated by command: go run blake2s_amd64_asm.go -out ../blake2s_amd64.s -pkg blake2s. DO NOT EDIT. + +//go:build amd64 && gc && !purego + +#include "textflag.h" + +// func hashBlocksSSE2(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) +// Requires: SSE2 +TEXT ·hashBlocksSSE2(SB), $672-48 + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVL flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DX + MOVQ SP, BP + ADDQ $0x0f, BP + ANDQ $-16, BP + MOVQ (BX), R9 + MOVQ R9, (BP) + MOVQ CX, 8(BP) + MOVOU (AX), X0 + MOVOU 16(AX), X1 + MOVOU iv0<>+0(SB), X2 + MOVOU iv1<>+0(SB), X3 + MOVOU counter<>+0(SB), X12 + MOVOU rol16<>+0(SB), X13 + MOVOU rol8<>+0(SB), X14 + MOVO (BP), X15 + +loop: + MOVO X0, X4 + MOVO X1, X5 + MOVO X2, X6 + MOVO X3, X7 + PADDQ X12, X15 + PXOR X15, X7 + MOVQ (SI), R8 + MOVQ 8(SI), R9 + MOVQ 16(SI), R10 + MOVQ 24(SI), R11 + MOVQ 32(SI), R12 + MOVQ 40(SI), R13 + MOVQ 48(SI), R14 + MOVQ 56(SI), R15 + MOVL R8, 16(BP) + MOVL R8, 116(BP) + MOVL R8, 164(BP) + MOVL R8, 264(BP) + MOVL R8, 288(BP) + MOVL R8, 344(BP) + MOVL R8, 432(BP) + MOVL R8, 512(BP) + MOVL R8, 540(BP) + MOVL R8, 652(BP) + SHRQ $0x20, R8 + MOVL R8, 32(BP) + MOVL R8, 112(BP) + MOVL R8, 200(BP) + MOVL R8, 228(BP) + MOVL R8, 320(BP) + MOVL R8, 380(BP) + MOVL R8, 404(BP) + MOVL R8, 488(BP) + MOVL R8, 568(BP) + MOVL R8, 604(BP) + MOVL R9, 20(BP) + MOVL R9, 132(BP) + MOVL R9, 168(BP) + MOVL R9, 240(BP) + MOVL R9, 280(BP) + MOVL R9, 336(BP) + MOVL R9, 456(BP) + MOVL R9, 508(BP) + MOVL R9, 576(BP) + MOVL R9, 608(BP) + SHRQ $0x20, R9 + MOVL R9, 36(BP) + MOVL R9, 140(BP) + MOVL R9, 180(BP) + MOVL R9, 212(BP) + MOVL R9, 316(BP) + MOVL R9, 364(BP) + MOVL R9, 452(BP) + MOVL R9, 476(BP) + MOVL R9, 552(BP) + MOVL R9, 632(BP) + MOVL R10, 24(BP) + MOVL R10, 84(BP) + MOVL R10, 204(BP) + MOVL R10, 248(BP) + MOVL R10, 296(BP) + MOVL R10, 368(BP) + MOVL R10, 412(BP) + MOVL R10, 516(BP) + MOVL R10, 584(BP) + MOVL R10, 612(BP) + SHRQ $0x20, R10 + MOVL R10, 40(BP) + MOVL R10, 124(BP) + MOVL R10, 152(BP) + MOVL R10, 244(BP) + MOVL R10, 276(BP) + MOVL R10, 388(BP) + MOVL R10, 416(BP) + MOVL R10, 496(BP) + MOVL R10, 588(BP) + MOVL R10, 620(BP) + MOVL R11, 28(BP) + MOVL R11, 108(BP) + MOVL R11, 196(BP) + MOVL R11, 256(BP) + MOVL R11, 312(BP) + MOVL R11, 340(BP) + MOVL R11, 436(BP) + MOVL R11, 520(BP) + MOVL R11, 528(BP) + MOVL R11, 616(BP) + SHRQ $0x20, R11 + MOVL R11, 44(BP) + MOVL R11, 136(BP) + MOVL R11, 184(BP) + MOVL R11, 208(BP) + MOVL R11, 292(BP) + MOVL R11, 372(BP) + MOVL R11, 448(BP) + MOVL R11, 468(BP) + MOVL R11, 580(BP) + MOVL R11, 600(BP) + MOVL R12, 48(BP) + MOVL R12, 100(BP) + MOVL R12, 160(BP) + MOVL R12, 268(BP) + MOVL R12, 328(BP) + MOVL R12, 348(BP) + MOVL R12, 444(BP) + MOVL R12, 504(BP) + MOVL R12, 556(BP) + MOVL R12, 596(BP) + SHRQ $0x20, R12 + MOVL R12, 64(BP) + MOVL R12, 88(BP) + MOVL R12, 188(BP) + MOVL R12, 224(BP) + MOVL R12, 272(BP) + MOVL R12, 396(BP) + MOVL R12, 440(BP) + MOVL R12, 492(BP) + MOVL R12, 548(BP) + MOVL R12, 628(BP) + MOVL R13, 52(BP) + MOVL R13, 96(BP) + MOVL R13, 176(BP) + MOVL R13, 260(BP) + MOVL R13, 284(BP) + MOVL R13, 356(BP) + MOVL R13, 428(BP) + MOVL R13, 524(BP) + MOVL R13, 572(BP) + MOVL R13, 592(BP) + SHRQ $0x20, R13 + MOVL R13, 68(BP) + MOVL R13, 120(BP) + MOVL R13, 144(BP) + MOVL R13, 220(BP) + MOVL R13, 308(BP) + MOVL R13, 360(BP) + MOVL R13, 460(BP) + MOVL R13, 480(BP) + MOVL R13, 536(BP) + MOVL R13, 640(BP) + MOVL R14, 56(BP) + MOVL R14, 128(BP) + MOVL R14, 148(BP) + MOVL R14, 232(BP) + MOVL R14, 324(BP) + MOVL R14, 352(BP) + MOVL R14, 400(BP) + MOVL R14, 472(BP) + MOVL R14, 560(BP) + MOVL R14, 648(BP) + SHRQ $0x20, R14 + MOVL R14, 72(BP) + MOVL R14, 92(BP) + MOVL R14, 172(BP) + MOVL R14, 216(BP) + MOVL R14, 332(BP) + MOVL R14, 384(BP) + MOVL R14, 424(BP) + MOVL R14, 464(BP) + MOVL R14, 564(BP) + MOVL R14, 636(BP) + MOVL R15, 60(BP) + MOVL R15, 80(BP) + MOVL R15, 192(BP) + MOVL R15, 236(BP) + MOVL R15, 304(BP) + MOVL R15, 392(BP) + MOVL R15, 408(BP) + MOVL R15, 484(BP) + MOVL R15, 532(BP) + MOVL R15, 644(BP) + SHRQ $0x20, R15 + MOVL R15, 76(BP) + MOVL R15, 104(BP) + MOVL R15, 156(BP) + MOVL R15, 252(BP) + MOVL R15, 300(BP) + MOVL R15, 376(BP) + MOVL R15, 420(BP) + MOVL R15, 500(BP) + MOVL R15, 544(BP) + MOVL R15, 624(BP) + PADDL 16(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 32(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 48(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 64(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 80(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 96(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 112(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 128(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 144(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 160(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 176(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 192(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 208(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 224(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 240(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 256(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 272(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 288(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 304(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 320(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 336(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 352(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 368(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 384(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 400(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 416(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 432(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 448(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 464(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 480(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 496(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 512(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 528(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 544(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 560(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 576(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 592(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 608(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 624(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x10, X8 + PSRLL $0x10, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 640(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + MOVO X7, X8 + PSLLL $0x18, X8 + PSRLL $0x08, X7 + PXOR X8, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PXOR X4, X0 + PXOR X5, X1 + PXOR X6, X0 + PXOR X7, X1 + LEAQ 64(SI), SI + SUBQ $0x40, DX + JNE loop + MOVO X15, (BP) + MOVQ (BP), R9 + MOVQ R9, (BX) + MOVOU X0, (AX) + MOVOU X1, 16(AX) + RET + +DATA iv0<>+0(SB)/4, $0x6a09e667 +DATA iv0<>+4(SB)/4, $0xbb67ae85 +DATA iv0<>+8(SB)/4, $0x3c6ef372 +DATA iv0<>+12(SB)/4, $0xa54ff53a +GLOBL iv0<>(SB), RODATA|NOPTR, $16 + +DATA iv1<>+0(SB)/4, $0x510e527f +DATA iv1<>+4(SB)/4, $0x9b05688c +DATA iv1<>+8(SB)/4, $0x1f83d9ab +DATA iv1<>+12(SB)/4, $0x5be0cd19 +GLOBL iv1<>(SB), RODATA|NOPTR, $16 + +DATA counter<>+0(SB)/8, $0x0000000000000040 +DATA counter<>+8(SB)/8, $0x0000000000000000 +GLOBL counter<>(SB), RODATA|NOPTR, $16 + +DATA rol16<>+0(SB)/8, $0x0504070601000302 +DATA rol16<>+8(SB)/8, $0x0d0c0f0e09080b0a +GLOBL rol16<>(SB), RODATA|NOPTR, $16 + +DATA rol8<>+0(SB)/8, $0x0407060500030201 +DATA rol8<>+8(SB)/8, $0x0c0f0e0d080b0a09 +GLOBL rol8<>(SB), RODATA|NOPTR, $16 + +// func hashBlocksSSSE3(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) +// Requires: SSE2, SSSE3 +TEXT ·hashBlocksSSSE3(SB), $672-48 + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVL flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DX + MOVQ SP, BP + ADDQ $0x0f, BP + ANDQ $-16, BP + MOVQ (BX), R9 + MOVQ R9, (BP) + MOVQ CX, 8(BP) + MOVOU (AX), X0 + MOVOU 16(AX), X1 + MOVOU iv0<>+0(SB), X2 + MOVOU iv1<>+0(SB), X3 + MOVOU counter<>+0(SB), X12 + MOVOU rol16<>+0(SB), X13 + MOVOU rol8<>+0(SB), X14 + MOVO (BP), X15 + +loop: + MOVO X0, X4 + MOVO X1, X5 + MOVO X2, X6 + MOVO X3, X7 + PADDQ X12, X15 + PXOR X15, X7 + MOVQ (SI), R8 + MOVQ 8(SI), R9 + MOVQ 16(SI), R10 + MOVQ 24(SI), R11 + MOVQ 32(SI), R12 + MOVQ 40(SI), R13 + MOVQ 48(SI), R14 + MOVQ 56(SI), R15 + MOVL R8, 16(BP) + MOVL R8, 116(BP) + MOVL R8, 164(BP) + MOVL R8, 264(BP) + MOVL R8, 288(BP) + MOVL R8, 344(BP) + MOVL R8, 432(BP) + MOVL R8, 512(BP) + MOVL R8, 540(BP) + MOVL R8, 652(BP) + SHRQ $0x20, R8 + MOVL R8, 32(BP) + MOVL R8, 112(BP) + MOVL R8, 200(BP) + MOVL R8, 228(BP) + MOVL R8, 320(BP) + MOVL R8, 380(BP) + MOVL R8, 404(BP) + MOVL R8, 488(BP) + MOVL R8, 568(BP) + MOVL R8, 604(BP) + MOVL R9, 20(BP) + MOVL R9, 132(BP) + MOVL R9, 168(BP) + MOVL R9, 240(BP) + MOVL R9, 280(BP) + MOVL R9, 336(BP) + MOVL R9, 456(BP) + MOVL R9, 508(BP) + MOVL R9, 576(BP) + MOVL R9, 608(BP) + SHRQ $0x20, R9 + MOVL R9, 36(BP) + MOVL R9, 140(BP) + MOVL R9, 180(BP) + MOVL R9, 212(BP) + MOVL R9, 316(BP) + MOVL R9, 364(BP) + MOVL R9, 452(BP) + MOVL R9, 476(BP) + MOVL R9, 552(BP) + MOVL R9, 632(BP) + MOVL R10, 24(BP) + MOVL R10, 84(BP) + MOVL R10, 204(BP) + MOVL R10, 248(BP) + MOVL R10, 296(BP) + MOVL R10, 368(BP) + MOVL R10, 412(BP) + MOVL R10, 516(BP) + MOVL R10, 584(BP) + MOVL R10, 612(BP) + SHRQ $0x20, R10 + MOVL R10, 40(BP) + MOVL R10, 124(BP) + MOVL R10, 152(BP) + MOVL R10, 244(BP) + MOVL R10, 276(BP) + MOVL R10, 388(BP) + MOVL R10, 416(BP) + MOVL R10, 496(BP) + MOVL R10, 588(BP) + MOVL R10, 620(BP) + MOVL R11, 28(BP) + MOVL R11, 108(BP) + MOVL R11, 196(BP) + MOVL R11, 256(BP) + MOVL R11, 312(BP) + MOVL R11, 340(BP) + MOVL R11, 436(BP) + MOVL R11, 520(BP) + MOVL R11, 528(BP) + MOVL R11, 616(BP) + SHRQ $0x20, R11 + MOVL R11, 44(BP) + MOVL R11, 136(BP) + MOVL R11, 184(BP) + MOVL R11, 208(BP) + MOVL R11, 292(BP) + MOVL R11, 372(BP) + MOVL R11, 448(BP) + MOVL R11, 468(BP) + MOVL R11, 580(BP) + MOVL R11, 600(BP) + MOVL R12, 48(BP) + MOVL R12, 100(BP) + MOVL R12, 160(BP) + MOVL R12, 268(BP) + MOVL R12, 328(BP) + MOVL R12, 348(BP) + MOVL R12, 444(BP) + MOVL R12, 504(BP) + MOVL R12, 556(BP) + MOVL R12, 596(BP) + SHRQ $0x20, R12 + MOVL R12, 64(BP) + MOVL R12, 88(BP) + MOVL R12, 188(BP) + MOVL R12, 224(BP) + MOVL R12, 272(BP) + MOVL R12, 396(BP) + MOVL R12, 440(BP) + MOVL R12, 492(BP) + MOVL R12, 548(BP) + MOVL R12, 628(BP) + MOVL R13, 52(BP) + MOVL R13, 96(BP) + MOVL R13, 176(BP) + MOVL R13, 260(BP) + MOVL R13, 284(BP) + MOVL R13, 356(BP) + MOVL R13, 428(BP) + MOVL R13, 524(BP) + MOVL R13, 572(BP) + MOVL R13, 592(BP) + SHRQ $0x20, R13 + MOVL R13, 68(BP) + MOVL R13, 120(BP) + MOVL R13, 144(BP) + MOVL R13, 220(BP) + MOVL R13, 308(BP) + MOVL R13, 360(BP) + MOVL R13, 460(BP) + MOVL R13, 480(BP) + MOVL R13, 536(BP) + MOVL R13, 640(BP) + MOVL R14, 56(BP) + MOVL R14, 128(BP) + MOVL R14, 148(BP) + MOVL R14, 232(BP) + MOVL R14, 324(BP) + MOVL R14, 352(BP) + MOVL R14, 400(BP) + MOVL R14, 472(BP) + MOVL R14, 560(BP) + MOVL R14, 648(BP) + SHRQ $0x20, R14 + MOVL R14, 72(BP) + MOVL R14, 92(BP) + MOVL R14, 172(BP) + MOVL R14, 216(BP) + MOVL R14, 332(BP) + MOVL R14, 384(BP) + MOVL R14, 424(BP) + MOVL R14, 464(BP) + MOVL R14, 564(BP) + MOVL R14, 636(BP) + MOVL R15, 60(BP) + MOVL R15, 80(BP) + MOVL R15, 192(BP) + MOVL R15, 236(BP) + MOVL R15, 304(BP) + MOVL R15, 392(BP) + MOVL R15, 408(BP) + MOVL R15, 484(BP) + MOVL R15, 532(BP) + MOVL R15, 644(BP) + SHRQ $0x20, R15 + MOVL R15, 76(BP) + MOVL R15, 104(BP) + MOVL R15, 156(BP) + MOVL R15, 252(BP) + MOVL R15, 300(BP) + MOVL R15, 376(BP) + MOVL R15, 420(BP) + MOVL R15, 500(BP) + MOVL R15, 544(BP) + MOVL R15, 624(BP) + PADDL 16(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 32(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 48(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 64(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 80(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 96(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 112(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 128(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 144(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 160(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 176(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 192(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 208(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 224(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 240(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 256(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 272(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 288(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 304(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 320(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 336(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 352(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 368(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 384(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 400(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 416(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 432(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 448(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 464(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 480(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 496(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 512(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 528(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 544(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 560(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 576(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PADDL 592(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 608(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL 624(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL 640(BP), X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PXOR X4, X0 + PXOR X5, X1 + PXOR X6, X0 + PXOR X7, X1 + LEAQ 64(SI), SI + SUBQ $0x40, DX + JNE loop + MOVO X15, (BP) + MOVQ (BP), R9 + MOVQ R9, (BX) + MOVOU X0, (AX) + MOVOU X1, 16(AX) + RET + +// func hashBlocksSSE4(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) +// Requires: SSE2, SSE4.1, SSSE3 +TEXT ·hashBlocksSSE4(SB), $32-48 + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVL flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DX + MOVQ SP, BP + ADDQ $0x0f, BP + ANDQ $-16, BP + MOVQ (BX), R9 + MOVQ R9, (BP) + MOVQ CX, 8(BP) + MOVOU (AX), X0 + MOVOU 16(AX), X1 + MOVOU iv0<>+0(SB), X2 + MOVOU iv1<>+0(SB), X3 + MOVOU counter<>+0(SB), X12 + MOVOU rol16<>+0(SB), X13 + MOVOU rol8<>+0(SB), X14 + MOVO (BP), X15 + +loop: + MOVO X0, X4 + MOVO X1, X5 + MOVO X2, X6 + MOVO X3, X7 + PADDQ X12, X15 + PXOR X15, X7 + MOVL (SI), X8 + PINSRD $0x01, 8(SI), X8 + PINSRD $0x02, 16(SI), X8 + PINSRD $0x03, 24(SI), X8 + MOVL 4(SI), X9 + PINSRD $0x01, 12(SI), X9 + PINSRD $0x02, 20(SI), X9 + PINSRD $0x03, 28(SI), X9 + MOVL 32(SI), X10 + PINSRD $0x01, 40(SI), X10 + PINSRD $0x02, 48(SI), X10 + PINSRD $0x03, 56(SI), X10 + MOVL 36(SI), X11 + PINSRD $0x01, 44(SI), X11 + PINSRD $0x02, 52(SI), X11 + PINSRD $0x03, 60(SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + MOVL 56(SI), X8 + PINSRD $0x01, 16(SI), X8 + PINSRD $0x02, 36(SI), X8 + PINSRD $0x03, 52(SI), X8 + MOVL 40(SI), X9 + PINSRD $0x01, 32(SI), X9 + PINSRD $0x02, 60(SI), X9 + PINSRD $0x03, 24(SI), X9 + MOVL 4(SI), X10 + PINSRD $0x01, (SI), X10 + PINSRD $0x02, 44(SI), X10 + PINSRD $0x03, 20(SI), X10 + MOVL 48(SI), X11 + PINSRD $0x01, 8(SI), X11 + PINSRD $0x02, 28(SI), X11 + PINSRD $0x03, 12(SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + MOVL 44(SI), X8 + PINSRD $0x01, 48(SI), X8 + PINSRD $0x02, 20(SI), X8 + PINSRD $0x03, 60(SI), X8 + MOVL 32(SI), X9 + PINSRD $0x01, (SI), X9 + PINSRD $0x02, 8(SI), X9 + PINSRD $0x03, 52(SI), X9 + MOVL 40(SI), X10 + PINSRD $0x01, 12(SI), X10 + PINSRD $0x02, 28(SI), X10 + PINSRD $0x03, 36(SI), X10 + MOVL 56(SI), X11 + PINSRD $0x01, 24(SI), X11 + PINSRD $0x02, 4(SI), X11 + PINSRD $0x03, 16(SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + MOVL 28(SI), X8 + PINSRD $0x01, 12(SI), X8 + PINSRD $0x02, 52(SI), X8 + PINSRD $0x03, 44(SI), X8 + MOVL 36(SI), X9 + PINSRD $0x01, 4(SI), X9 + PINSRD $0x02, 48(SI), X9 + PINSRD $0x03, 56(SI), X9 + MOVL 8(SI), X10 + PINSRD $0x01, 20(SI), X10 + PINSRD $0x02, 16(SI), X10 + PINSRD $0x03, 60(SI), X10 + MOVL 24(SI), X11 + PINSRD $0x01, 40(SI), X11 + PINSRD $0x02, (SI), X11 + PINSRD $0x03, 32(SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + MOVL 36(SI), X8 + PINSRD $0x01, 20(SI), X8 + PINSRD $0x02, 8(SI), X8 + PINSRD $0x03, 40(SI), X8 + MOVL (SI), X9 + PINSRD $0x01, 28(SI), X9 + PINSRD $0x02, 16(SI), X9 + PINSRD $0x03, 60(SI), X9 + MOVL 56(SI), X10 + PINSRD $0x01, 44(SI), X10 + PINSRD $0x02, 24(SI), X10 + PINSRD $0x03, 12(SI), X10 + MOVL 4(SI), X11 + PINSRD $0x01, 48(SI), X11 + PINSRD $0x02, 32(SI), X11 + PINSRD $0x03, 52(SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + MOVL 8(SI), X8 + PINSRD $0x01, 24(SI), X8 + PINSRD $0x02, (SI), X8 + PINSRD $0x03, 32(SI), X8 + MOVL 48(SI), X9 + PINSRD $0x01, 40(SI), X9 + PINSRD $0x02, 44(SI), X9 + PINSRD $0x03, 12(SI), X9 + MOVL 16(SI), X10 + PINSRD $0x01, 28(SI), X10 + PINSRD $0x02, 60(SI), X10 + PINSRD $0x03, 4(SI), X10 + MOVL 52(SI), X11 + PINSRD $0x01, 20(SI), X11 + PINSRD $0x02, 56(SI), X11 + PINSRD $0x03, 36(SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + MOVL 48(SI), X8 + PINSRD $0x01, 4(SI), X8 + PINSRD $0x02, 56(SI), X8 + PINSRD $0x03, 16(SI), X8 + MOVL 20(SI), X9 + PINSRD $0x01, 60(SI), X9 + PINSRD $0x02, 52(SI), X9 + PINSRD $0x03, 40(SI), X9 + MOVL (SI), X10 + PINSRD $0x01, 24(SI), X10 + PINSRD $0x02, 36(SI), X10 + PINSRD $0x03, 32(SI), X10 + MOVL 28(SI), X11 + PINSRD $0x01, 12(SI), X11 + PINSRD $0x02, 8(SI), X11 + PINSRD $0x03, 44(SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + MOVL 52(SI), X8 + PINSRD $0x01, 28(SI), X8 + PINSRD $0x02, 48(SI), X8 + PINSRD $0x03, 12(SI), X8 + MOVL 44(SI), X9 + PINSRD $0x01, 56(SI), X9 + PINSRD $0x02, 4(SI), X9 + PINSRD $0x03, 36(SI), X9 + MOVL 20(SI), X10 + PINSRD $0x01, 60(SI), X10 + PINSRD $0x02, 32(SI), X10 + PINSRD $0x03, 8(SI), X10 + MOVL (SI), X11 + PINSRD $0x01, 16(SI), X11 + PINSRD $0x02, 24(SI), X11 + PINSRD $0x03, 40(SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + MOVL 24(SI), X8 + PINSRD $0x01, 56(SI), X8 + PINSRD $0x02, 44(SI), X8 + PINSRD $0x03, (SI), X8 + MOVL 60(SI), X9 + PINSRD $0x01, 36(SI), X9 + PINSRD $0x02, 12(SI), X9 + PINSRD $0x03, 32(SI), X9 + MOVL 48(SI), X10 + PINSRD $0x01, 52(SI), X10 + PINSRD $0x02, 4(SI), X10 + PINSRD $0x03, 40(SI), X10 + MOVL 8(SI), X11 + PINSRD $0x01, 28(SI), X11 + PINSRD $0x02, 16(SI), X11 + PINSRD $0x03, 20(SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + MOVL 40(SI), X8 + PINSRD $0x01, 32(SI), X8 + PINSRD $0x02, 28(SI), X8 + PINSRD $0x03, 4(SI), X8 + MOVL 8(SI), X9 + PINSRD $0x01, 16(SI), X9 + PINSRD $0x02, 24(SI), X9 + PINSRD $0x03, 20(SI), X9 + MOVL 60(SI), X10 + PINSRD $0x01, 36(SI), X10 + PINSRD $0x02, 12(SI), X10 + PINSRD $0x03, 52(SI), X10 + MOVL 44(SI), X11 + PINSRD $0x01, 56(SI), X11 + PINSRD $0x02, 48(SI), X11 + PINSRD $0x03, (SI), X11 + PADDL X8, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X9, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X5, X5 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X7, X7 + PADDL X10, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X13, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x14, X8 + PSRLL $0x0c, X5 + PXOR X8, X5 + PADDL X11, X4 + PADDL X5, X4 + PXOR X4, X7 + PSHUFB X14, X7 + PADDL X7, X6 + PXOR X6, X5 + MOVO X5, X8 + PSLLL $0x19, X8 + PSRLL $0x07, X5 + PXOR X8, X5 + PSHUFL $0x39, X7, X7 + PSHUFL $0x4e, X6, X6 + PSHUFL $0x93, X5, X5 + PXOR X4, X0 + PXOR X5, X1 + PXOR X6, X0 + PXOR X7, X1 + LEAQ 64(SI), SI + SUBQ $0x40, DX + JNE loop + MOVO X15, (BP) + MOVQ (BP), R9 + MOVQ R9, (BX) + MOVOU X0, (AX) + MOVOU X1, 16(AX) + RET diff --git a/cli/vendor/golang.org/x/crypto/blake2s/blake2s_generic.go b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_generic.go new file mode 100644 index 0000000..24a1ff2 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_generic.go @@ -0,0 +1,178 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2s + +import ( + "math/bits" +) + +// the precomputed values for BLAKE2s +// there are 10 16-byte arrays - one for each round +// the entries are calculated from the sigma constants. +var precomputed = [10][16]byte{ + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, + {11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4}, + {7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8}, + {9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13}, + {2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9}, + {12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11}, + {13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10}, + {6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5}, + {10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0}, +} + +func hashBlocksGeneric(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) { + var m [16]uint32 + c0, c1 := c[0], c[1] + + for i := 0; i < len(blocks); { + c0 += BlockSize + if c0 < BlockSize { + c1++ + } + + v0, v1, v2, v3, v4, v5, v6, v7 := h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7] + v8, v9, v10, v11, v12, v13, v14, v15 := iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7] + v12 ^= c0 + v13 ^= c1 + v14 ^= flag + + for j := range m { + m[j] = uint32(blocks[i]) | uint32(blocks[i+1])<<8 | uint32(blocks[i+2])<<16 | uint32(blocks[i+3])<<24 + i += 4 + } + + for k := range precomputed { + s := &(precomputed[k]) + + v0 += m[s[0]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft32(v12, -16) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft32(v4, -12) + v1 += m[s[1]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft32(v13, -16) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft32(v5, -12) + v2 += m[s[2]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft32(v14, -16) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft32(v6, -12) + v3 += m[s[3]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft32(v15, -16) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft32(v7, -12) + + v0 += m[s[4]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft32(v12, -8) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft32(v4, -7) + v1 += m[s[5]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft32(v13, -8) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft32(v5, -7) + v2 += m[s[6]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft32(v14, -8) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft32(v6, -7) + v3 += m[s[7]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft32(v15, -8) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft32(v7, -7) + + v0 += m[s[8]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft32(v15, -16) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft32(v5, -12) + v1 += m[s[9]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft32(v12, -16) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft32(v6, -12) + v2 += m[s[10]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft32(v13, -16) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft32(v7, -12) + v3 += m[s[11]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft32(v14, -16) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft32(v4, -12) + + v0 += m[s[12]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft32(v15, -8) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft32(v5, -7) + v1 += m[s[13]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft32(v12, -8) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft32(v6, -7) + v2 += m[s[14]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft32(v13, -8) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft32(v7, -7) + v3 += m[s[15]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft32(v14, -8) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft32(v4, -7) + } + + h[0] ^= v0 ^ v8 + h[1] ^= v1 ^ v9 + h[2] ^= v2 ^ v10 + h[3] ^= v3 ^ v11 + h[4] ^= v4 ^ v12 + h[5] ^= v5 ^ v13 + h[6] ^= v6 ^ v14 + h[7] ^= v7 ^ v15 + } + c[0], c[1] = c0, c1 +} diff --git a/cli/vendor/golang.org/x/crypto/blake2s/blake2s_ref.go b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_ref.go new file mode 100644 index 0000000..38ce8e2 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2s/blake2s_ref.go @@ -0,0 +1,17 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!amd64 && !386) || !gc || purego + +package blake2s + +var ( + useSSE4 = false + useSSSE3 = false + useSSE2 = false +) + +func hashBlocks(h *[8]uint32, c *[2]uint32, flag uint32, blocks []byte) { + hashBlocksGeneric(h, c, flag, blocks) +} diff --git a/cli/vendor/golang.org/x/crypto/blake2s/blake2x.go b/cli/vendor/golang.org/x/crypto/blake2s/blake2x.go new file mode 100644 index 0000000..828749f --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blake2s/blake2x.go @@ -0,0 +1,178 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2s + +import ( + "encoding/binary" + "errors" + "io" +) + +// XOF defines the interface to hash functions that +// support arbitrary-length output. +type XOF interface { + // Write absorbs more data into the hash's state. It panics if called + // after Read. + io.Writer + + // Read reads more output from the hash. It returns io.EOF if the limit + // has been reached. + io.Reader + + // Clone returns a copy of the XOF in its current state. + Clone() XOF + + // Reset resets the XOF to its initial state. + Reset() +} + +// OutputLengthUnknown can be used as the size argument to NewXOF to indicate +// the length of the output is not known in advance. +const OutputLengthUnknown = 0 + +// magicUnknownOutputLength is a magic value for the output size that indicates +// an unknown number of output bytes. +const magicUnknownOutputLength = 65535 + +// maxOutputLength is the absolute maximum number of bytes to produce when the +// number of output bytes is unknown. +const maxOutputLength = (1 << 32) * 32 + +// NewXOF creates a new variable-output-length hash. The hash either produce a +// known number of bytes (1 <= size < 65535), or an unknown number of bytes +// (size == OutputLengthUnknown). In the latter case, an absolute limit of +// 128GiB applies. +// +// A non-nil key turns the hash into a MAC. The key must between +// zero and 32 bytes long. +func NewXOF(size uint16, key []byte) (XOF, error) { + if len(key) > Size { + return nil, errKeySize + } + if size == magicUnknownOutputLength { + // 2^16-1 indicates an unknown number of bytes and thus isn't a + // valid length. + return nil, errors.New("blake2s: XOF length too large") + } + if size == OutputLengthUnknown { + size = magicUnknownOutputLength + } + x := &xof{ + d: digest{ + size: Size, + keyLen: len(key), + }, + length: size, + } + copy(x.d.key[:], key) + x.Reset() + return x, nil +} + +type xof struct { + d digest + length uint16 + remaining uint64 + cfg, root, block [Size]byte + offset int + nodeOffset uint32 + readMode bool +} + +func (x *xof) Write(p []byte) (n int, err error) { + if x.readMode { + panic("blake2s: write to XOF after read") + } + return x.d.Write(p) +} + +func (x *xof) Clone() XOF { + clone := *x + return &clone +} + +func (x *xof) Reset() { + x.cfg[0] = byte(Size) + binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length + binary.LittleEndian.PutUint16(x.cfg[12:], x.length) // XOF length + x.cfg[15] = byte(Size) // inner hash size + + x.d.Reset() + x.d.h[3] ^= uint32(x.length) + + x.remaining = uint64(x.length) + if x.remaining == magicUnknownOutputLength { + x.remaining = maxOutputLength + } + x.offset, x.nodeOffset = 0, 0 + x.readMode = false +} + +func (x *xof) Read(p []byte) (n int, err error) { + if !x.readMode { + x.d.finalize(&x.root) + x.readMode = true + } + + if x.remaining == 0 { + return 0, io.EOF + } + + n = len(p) + if uint64(n) > x.remaining { + n = int(x.remaining) + p = p[:n] + } + + if x.offset > 0 { + blockRemaining := Size - x.offset + if n < blockRemaining { + x.offset += copy(p, x.block[x.offset:]) + x.remaining -= uint64(n) + return + } + copy(p, x.block[x.offset:]) + p = p[blockRemaining:] + x.offset = 0 + x.remaining -= uint64(blockRemaining) + } + + for len(p) >= Size { + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + copy(p, x.block[:]) + p = p[Size:] + x.remaining -= uint64(Size) + } + + if todo := len(p); todo > 0 { + if x.remaining < uint64(Size) { + x.cfg[0] = byte(x.remaining) + } + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + x.offset = copy(p, x.block[:todo]) + x.remaining -= uint64(todo) + } + + return +} + +func (d *digest) initConfig(cfg *[Size]byte) { + d.offset, d.c[0], d.c[1] = 0, 0, 0 + for i := range d.h { + d.h[i] = iv[i] ^ binary.LittleEndian.Uint32(cfg[i*4:]) + } +} diff --git a/cli/vendor/golang.org/x/crypto/blowfish/block.go b/cli/vendor/golang.org/x/crypto/blowfish/block.go new file mode 100644 index 0000000..9d80f19 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blowfish/block.go @@ -0,0 +1,159 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blowfish + +// getNextWord returns the next big-endian uint32 value from the byte slice +// at the given position in a circular manner, updating the position. +func getNextWord(b []byte, pos *int) uint32 { + var w uint32 + j := *pos + for i := 0; i < 4; i++ { + w = w<<8 | uint32(b[j]) + j++ + if j >= len(b) { + j = 0 + } + } + *pos = j + return w +} + +// ExpandKey performs a key expansion on the given *Cipher. Specifically, it +// performs the Blowfish algorithm's key schedule which sets up the *Cipher's +// pi and substitution tables for calls to Encrypt. This is used, primarily, +// by the bcrypt package to reuse the Blowfish key schedule during its +// set up. It's unlikely that you need to use this directly. +func ExpandKey(key []byte, c *Cipher) { + j := 0 + for i := 0; i < 18; i++ { + // Using inlined getNextWord for performance. + var d uint32 + for k := 0; k < 4; k++ { + d = d<<8 | uint32(key[j]) + j++ + if j >= len(key) { + j = 0 + } + } + c.p[i] ^= d + } + + var l, r uint32 + for i := 0; i < 18; i += 2 { + l, r = encryptBlock(l, r, c) + c.p[i], c.p[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s0[i], c.s0[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s1[i], c.s1[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s2[i], c.s2[i+1] = l, r + } + for i := 0; i < 256; i += 2 { + l, r = encryptBlock(l, r, c) + c.s3[i], c.s3[i+1] = l, r + } +} + +// This is similar to ExpandKey, but folds the salt during the key +// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero +// salt passed in, reusing ExpandKey turns out to be a place of inefficiency +// and specializing it here is useful. +func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) { + j := 0 + for i := 0; i < 18; i++ { + c.p[i] ^= getNextWord(key, &j) + } + + j = 0 + var l, r uint32 + for i := 0; i < 18; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.p[i], c.p[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s0[i], c.s0[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s1[i], c.s1[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s2[i], c.s2[i+1] = l, r + } + + for i := 0; i < 256; i += 2 { + l ^= getNextWord(salt, &j) + r ^= getNextWord(salt, &j) + l, r = encryptBlock(l, r, c) + c.s3[i], c.s3[i+1] = l, r + } +} + +func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { + xl, xr := l, r + xl ^= c.p[0] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16] + xr ^= c.p[17] + return xr, xl +} + +func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { + xl, xr := l, r + xl ^= c.p[17] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3] + xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2] + xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1] + xr ^= c.p[0] + return xr, xl +} diff --git a/cli/vendor/golang.org/x/crypto/blowfish/cipher.go b/cli/vendor/golang.org/x/crypto/blowfish/cipher.go new file mode 100644 index 0000000..0898956 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blowfish/cipher.go @@ -0,0 +1,99 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm. +// +// Blowfish is a legacy cipher and its short block size makes it vulnerable to +// birthday bound attacks (see https://sweet32.info). It should only be used +// where compatibility with legacy systems, not security, is the goal. +// +// Deprecated: any new system should use AES (from crypto/aes, if necessary in +// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from +// golang.org/x/crypto/chacha20poly1305). +package blowfish + +// The code is a port of Bruce Schneier's C implementation. +// See https://www.schneier.com/blowfish.html. + +import "strconv" + +// The Blowfish block size in bytes. +const BlockSize = 8 + +// A Cipher is an instance of Blowfish encryption using a particular key. +type Cipher struct { + p [18]uint32 + s0, s1, s2, s3 [256]uint32 +} + +type KeySizeError int + +func (k KeySizeError) Error() string { + return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k)) +} + +// NewCipher creates and returns a Cipher. +// The key argument should be the Blowfish key, from 1 to 56 bytes. +func NewCipher(key []byte) (*Cipher, error) { + var result Cipher + if k := len(key); k < 1 || k > 56 { + return nil, KeySizeError(k) + } + initCipher(&result) + ExpandKey(key, &result) + return &result, nil +} + +// NewSaltedCipher creates a returns a Cipher that folds a salt into its key +// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is +// sufficient and desirable. For bcrypt compatibility, the key can be over 56 +// bytes. +func NewSaltedCipher(key, salt []byte) (*Cipher, error) { + if len(salt) == 0 { + return NewCipher(key) + } + var result Cipher + if k := len(key); k < 1 { + return nil, KeySizeError(k) + } + initCipher(&result) + expandKeyWithSalt(key, salt, &result) + return &result, nil +} + +// BlockSize returns the Blowfish block size, 8 bytes. +// It is necessary to satisfy the Block interface in the +// package "crypto/cipher". +func (c *Cipher) BlockSize() int { return BlockSize } + +// Encrypt encrypts the 8-byte buffer src using the key k +// and stores the result in dst. +// Note that for amounts of data larger than a block, +// it is not safe to just call Encrypt on successive blocks; +// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go). +func (c *Cipher) Encrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + l, r = encryptBlock(l, r, c) + dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) + dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) +} + +// Decrypt decrypts the 8-byte buffer src using the key k +// and stores the result in dst. +func (c *Cipher) Decrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + l, r = decryptBlock(l, r, c) + dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) + dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) +} + +func initCipher(c *Cipher) { + copy(c.p[0:], p[0:]) + copy(c.s0[0:], s0[0:]) + copy(c.s1[0:], s1[0:]) + copy(c.s2[0:], s2[0:]) + copy(c.s3[0:], s3[0:]) +} diff --git a/cli/vendor/golang.org/x/crypto/blowfish/const.go b/cli/vendor/golang.org/x/crypto/blowfish/const.go new file mode 100644 index 0000000..d040775 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/blowfish/const.go @@ -0,0 +1,199 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The startup permutation array and substitution boxes. +// They are the hexadecimal digits of PI; see: +// https://www.schneier.com/code/constants.txt. + +package blowfish + +var s0 = [256]uint32{ + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, + 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, + 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, + 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, + 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, + 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, + 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, + 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, + 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, + 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, + 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, + 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, + 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, + 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, + 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, + 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, + 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, + 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, + 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, + 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, + 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, + 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, +} + +var s1 = [256]uint32{ + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, + 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, + 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, + 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, + 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, + 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, + 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, + 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, + 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, + 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, + 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, + 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, + 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, + 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, + 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, + 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, + 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, + 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, + 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, + 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, + 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, + 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, +} + +var s2 = [256]uint32{ + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, + 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, + 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, + 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, + 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, + 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, + 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, + 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, + 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, + 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, + 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, + 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, + 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, + 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, + 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, + 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, + 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, + 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, + 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, + 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, + 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, + 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, +} + +var s3 = [256]uint32{ + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, + 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, + 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, + 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, + 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, + 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, + 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, + 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, + 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, + 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, + 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, + 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, + 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, + 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, + 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, + 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, + 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, + 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, + 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, + 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, + 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, + 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6, +} + +var p = [18]uint32{ + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, + 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b, +} diff --git a/cli/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go b/cli/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go new file mode 100644 index 0000000..661ea13 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/chacha20/chacha_arm64.go @@ -0,0 +1,16 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego + +package chacha20 + +const bufSize = 256 + +//go:noescape +func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32) + +func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) { + xorKeyStreamVX(dst, src, &c.key, &c.nonce, &c.counter) +} diff --git a/cli/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s b/cli/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s new file mode 100644 index 0000000..769af38 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s @@ -0,0 +1,307 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego + +#include "textflag.h" + +#define NUM_ROUNDS 10 + +// func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32) +TEXT ·xorKeyStreamVX(SB), NOSPLIT, $0 + MOVD dst+0(FP), R1 + MOVD src+24(FP), R2 + MOVD src_len+32(FP), R3 + MOVD key+48(FP), R4 + MOVD nonce+56(FP), R6 + MOVD counter+64(FP), R7 + + MOVD $·constants(SB), R10 + MOVD $·incRotMatrix(SB), R11 + + MOVW (R7), R20 + + AND $~255, R3, R13 + ADD R2, R13, R12 // R12 for block end + AND $255, R3, R13 +loop: + MOVD $NUM_ROUNDS, R21 + VLD1 (R11), [V30.S4, V31.S4] + + // load constants + // VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4] + WORD $0x4D60E940 + + // load keys + // VLD4R 16(R4), [V4.S4, V5.S4, V6.S4, V7.S4] + WORD $0x4DFFE884 + // VLD4R 16(R4), [V8.S4, V9.S4, V10.S4, V11.S4] + WORD $0x4DFFE888 + SUB $32, R4 + + // load counter + nonce + // VLD1R (R7), [V12.S4] + WORD $0x4D40C8EC + + // VLD3R (R6), [V13.S4, V14.S4, V15.S4] + WORD $0x4D40E8CD + + // update counter + VADD V30.S4, V12.S4, V12.S4 + +chacha: + // V0..V3 += V4..V7 + // V12..V15 <<<= ((V12..V15 XOR V0..V3), 16) + VADD V0.S4, V4.S4, V0.S4 + VADD V1.S4, V5.S4, V1.S4 + VADD V2.S4, V6.S4, V2.S4 + VADD V3.S4, V7.S4, V3.S4 + VEOR V12.B16, V0.B16, V12.B16 + VEOR V13.B16, V1.B16, V13.B16 + VEOR V14.B16, V2.B16, V14.B16 + VEOR V15.B16, V3.B16, V15.B16 + VREV32 V12.H8, V12.H8 + VREV32 V13.H8, V13.H8 + VREV32 V14.H8, V14.H8 + VREV32 V15.H8, V15.H8 + // V8..V11 += V12..V15 + // V4..V7 <<<= ((V4..V7 XOR V8..V11), 12) + VADD V8.S4, V12.S4, V8.S4 + VADD V9.S4, V13.S4, V9.S4 + VADD V10.S4, V14.S4, V10.S4 + VADD V11.S4, V15.S4, V11.S4 + VEOR V8.B16, V4.B16, V16.B16 + VEOR V9.B16, V5.B16, V17.B16 + VEOR V10.B16, V6.B16, V18.B16 + VEOR V11.B16, V7.B16, V19.B16 + VSHL $12, V16.S4, V4.S4 + VSHL $12, V17.S4, V5.S4 + VSHL $12, V18.S4, V6.S4 + VSHL $12, V19.S4, V7.S4 + VSRI $20, V16.S4, V4.S4 + VSRI $20, V17.S4, V5.S4 + VSRI $20, V18.S4, V6.S4 + VSRI $20, V19.S4, V7.S4 + + // V0..V3 += V4..V7 + // V12..V15 <<<= ((V12..V15 XOR V0..V3), 8) + VADD V0.S4, V4.S4, V0.S4 + VADD V1.S4, V5.S4, V1.S4 + VADD V2.S4, V6.S4, V2.S4 + VADD V3.S4, V7.S4, V3.S4 + VEOR V12.B16, V0.B16, V12.B16 + VEOR V13.B16, V1.B16, V13.B16 + VEOR V14.B16, V2.B16, V14.B16 + VEOR V15.B16, V3.B16, V15.B16 + VTBL V31.B16, [V12.B16], V12.B16 + VTBL V31.B16, [V13.B16], V13.B16 + VTBL V31.B16, [V14.B16], V14.B16 + VTBL V31.B16, [V15.B16], V15.B16 + + // V8..V11 += V12..V15 + // V4..V7 <<<= ((V4..V7 XOR V8..V11), 7) + VADD V12.S4, V8.S4, V8.S4 + VADD V13.S4, V9.S4, V9.S4 + VADD V14.S4, V10.S4, V10.S4 + VADD V15.S4, V11.S4, V11.S4 + VEOR V8.B16, V4.B16, V16.B16 + VEOR V9.B16, V5.B16, V17.B16 + VEOR V10.B16, V6.B16, V18.B16 + VEOR V11.B16, V7.B16, V19.B16 + VSHL $7, V16.S4, V4.S4 + VSHL $7, V17.S4, V5.S4 + VSHL $7, V18.S4, V6.S4 + VSHL $7, V19.S4, V7.S4 + VSRI $25, V16.S4, V4.S4 + VSRI $25, V17.S4, V5.S4 + VSRI $25, V18.S4, V6.S4 + VSRI $25, V19.S4, V7.S4 + + // V0..V3 += V5..V7, V4 + // V15,V12-V14 <<<= ((V15,V12-V14 XOR V0..V3), 16) + VADD V0.S4, V5.S4, V0.S4 + VADD V1.S4, V6.S4, V1.S4 + VADD V2.S4, V7.S4, V2.S4 + VADD V3.S4, V4.S4, V3.S4 + VEOR V15.B16, V0.B16, V15.B16 + VEOR V12.B16, V1.B16, V12.B16 + VEOR V13.B16, V2.B16, V13.B16 + VEOR V14.B16, V3.B16, V14.B16 + VREV32 V12.H8, V12.H8 + VREV32 V13.H8, V13.H8 + VREV32 V14.H8, V14.H8 + VREV32 V15.H8, V15.H8 + + // V10 += V15; V5 <<<= ((V10 XOR V5), 12) + // ... + VADD V15.S4, V10.S4, V10.S4 + VADD V12.S4, V11.S4, V11.S4 + VADD V13.S4, V8.S4, V8.S4 + VADD V14.S4, V9.S4, V9.S4 + VEOR V10.B16, V5.B16, V16.B16 + VEOR V11.B16, V6.B16, V17.B16 + VEOR V8.B16, V7.B16, V18.B16 + VEOR V9.B16, V4.B16, V19.B16 + VSHL $12, V16.S4, V5.S4 + VSHL $12, V17.S4, V6.S4 + VSHL $12, V18.S4, V7.S4 + VSHL $12, V19.S4, V4.S4 + VSRI $20, V16.S4, V5.S4 + VSRI $20, V17.S4, V6.S4 + VSRI $20, V18.S4, V7.S4 + VSRI $20, V19.S4, V4.S4 + + // V0 += V5; V15 <<<= ((V0 XOR V15), 8) + // ... + VADD V5.S4, V0.S4, V0.S4 + VADD V6.S4, V1.S4, V1.S4 + VADD V7.S4, V2.S4, V2.S4 + VADD V4.S4, V3.S4, V3.S4 + VEOR V0.B16, V15.B16, V15.B16 + VEOR V1.B16, V12.B16, V12.B16 + VEOR V2.B16, V13.B16, V13.B16 + VEOR V3.B16, V14.B16, V14.B16 + VTBL V31.B16, [V12.B16], V12.B16 + VTBL V31.B16, [V13.B16], V13.B16 + VTBL V31.B16, [V14.B16], V14.B16 + VTBL V31.B16, [V15.B16], V15.B16 + + // V10 += V15; V5 <<<= ((V10 XOR V5), 7) + // ... + VADD V15.S4, V10.S4, V10.S4 + VADD V12.S4, V11.S4, V11.S4 + VADD V13.S4, V8.S4, V8.S4 + VADD V14.S4, V9.S4, V9.S4 + VEOR V10.B16, V5.B16, V16.B16 + VEOR V11.B16, V6.B16, V17.B16 + VEOR V8.B16, V7.B16, V18.B16 + VEOR V9.B16, V4.B16, V19.B16 + VSHL $7, V16.S4, V5.S4 + VSHL $7, V17.S4, V6.S4 + VSHL $7, V18.S4, V7.S4 + VSHL $7, V19.S4, V4.S4 + VSRI $25, V16.S4, V5.S4 + VSRI $25, V17.S4, V6.S4 + VSRI $25, V18.S4, V7.S4 + VSRI $25, V19.S4, V4.S4 + + SUB $1, R21 + CBNZ R21, chacha + + // VLD4R (R10), [V16.S4, V17.S4, V18.S4, V19.S4] + WORD $0x4D60E950 + + // VLD4R 16(R4), [V20.S4, V21.S4, V22.S4, V23.S4] + WORD $0x4DFFE894 + VADD V30.S4, V12.S4, V12.S4 + VADD V16.S4, V0.S4, V0.S4 + VADD V17.S4, V1.S4, V1.S4 + VADD V18.S4, V2.S4, V2.S4 + VADD V19.S4, V3.S4, V3.S4 + // VLD4R 16(R4), [V24.S4, V25.S4, V26.S4, V27.S4] + WORD $0x4DFFE898 + // restore R4 + SUB $32, R4 + + // load counter + nonce + // VLD1R (R7), [V28.S4] + WORD $0x4D40C8FC + // VLD3R (R6), [V29.S4, V30.S4, V31.S4] + WORD $0x4D40E8DD + + VADD V20.S4, V4.S4, V4.S4 + VADD V21.S4, V5.S4, V5.S4 + VADD V22.S4, V6.S4, V6.S4 + VADD V23.S4, V7.S4, V7.S4 + VADD V24.S4, V8.S4, V8.S4 + VADD V25.S4, V9.S4, V9.S4 + VADD V26.S4, V10.S4, V10.S4 + VADD V27.S4, V11.S4, V11.S4 + VADD V28.S4, V12.S4, V12.S4 + VADD V29.S4, V13.S4, V13.S4 + VADD V30.S4, V14.S4, V14.S4 + VADD V31.S4, V15.S4, V15.S4 + + VZIP1 V1.S4, V0.S4, V16.S4 + VZIP2 V1.S4, V0.S4, V17.S4 + VZIP1 V3.S4, V2.S4, V18.S4 + VZIP2 V3.S4, V2.S4, V19.S4 + VZIP1 V5.S4, V4.S4, V20.S4 + VZIP2 V5.S4, V4.S4, V21.S4 + VZIP1 V7.S4, V6.S4, V22.S4 + VZIP2 V7.S4, V6.S4, V23.S4 + VZIP1 V9.S4, V8.S4, V24.S4 + VZIP2 V9.S4, V8.S4, V25.S4 + VZIP1 V11.S4, V10.S4, V26.S4 + VZIP2 V11.S4, V10.S4, V27.S4 + VZIP1 V13.S4, V12.S4, V28.S4 + VZIP2 V13.S4, V12.S4, V29.S4 + VZIP1 V15.S4, V14.S4, V30.S4 + VZIP2 V15.S4, V14.S4, V31.S4 + VZIP1 V18.D2, V16.D2, V0.D2 + VZIP2 V18.D2, V16.D2, V4.D2 + VZIP1 V19.D2, V17.D2, V8.D2 + VZIP2 V19.D2, V17.D2, V12.D2 + VLD1.P 64(R2), [V16.B16, V17.B16, V18.B16, V19.B16] + + VZIP1 V22.D2, V20.D2, V1.D2 + VZIP2 V22.D2, V20.D2, V5.D2 + VZIP1 V23.D2, V21.D2, V9.D2 + VZIP2 V23.D2, V21.D2, V13.D2 + VLD1.P 64(R2), [V20.B16, V21.B16, V22.B16, V23.B16] + VZIP1 V26.D2, V24.D2, V2.D2 + VZIP2 V26.D2, V24.D2, V6.D2 + VZIP1 V27.D2, V25.D2, V10.D2 + VZIP2 V27.D2, V25.D2, V14.D2 + VLD1.P 64(R2), [V24.B16, V25.B16, V26.B16, V27.B16] + VZIP1 V30.D2, V28.D2, V3.D2 + VZIP2 V30.D2, V28.D2, V7.D2 + VZIP1 V31.D2, V29.D2, V11.D2 + VZIP2 V31.D2, V29.D2, V15.D2 + VLD1.P 64(R2), [V28.B16, V29.B16, V30.B16, V31.B16] + VEOR V0.B16, V16.B16, V16.B16 + VEOR V1.B16, V17.B16, V17.B16 + VEOR V2.B16, V18.B16, V18.B16 + VEOR V3.B16, V19.B16, V19.B16 + VST1.P [V16.B16, V17.B16, V18.B16, V19.B16], 64(R1) + VEOR V4.B16, V20.B16, V20.B16 + VEOR V5.B16, V21.B16, V21.B16 + VEOR V6.B16, V22.B16, V22.B16 + VEOR V7.B16, V23.B16, V23.B16 + VST1.P [V20.B16, V21.B16, V22.B16, V23.B16], 64(R1) + VEOR V8.B16, V24.B16, V24.B16 + VEOR V9.B16, V25.B16, V25.B16 + VEOR V10.B16, V26.B16, V26.B16 + VEOR V11.B16, V27.B16, V27.B16 + VST1.P [V24.B16, V25.B16, V26.B16, V27.B16], 64(R1) + VEOR V12.B16, V28.B16, V28.B16 + VEOR V13.B16, V29.B16, V29.B16 + VEOR V14.B16, V30.B16, V30.B16 + VEOR V15.B16, V31.B16, V31.B16 + VST1.P [V28.B16, V29.B16, V30.B16, V31.B16], 64(R1) + + ADD $4, R20 + MOVW R20, (R7) // update counter + + CMP R2, R12 + BGT loop + + RET + + +DATA ·constants+0x00(SB)/4, $0x61707865 +DATA ·constants+0x04(SB)/4, $0x3320646e +DATA ·constants+0x08(SB)/4, $0x79622d32 +DATA ·constants+0x0c(SB)/4, $0x6b206574 +GLOBL ·constants(SB), NOPTR|RODATA, $32 + +DATA ·incRotMatrix+0x00(SB)/4, $0x00000000 +DATA ·incRotMatrix+0x04(SB)/4, $0x00000001 +DATA ·incRotMatrix+0x08(SB)/4, $0x00000002 +DATA ·incRotMatrix+0x0c(SB)/4, $0x00000003 +DATA ·incRotMatrix+0x10(SB)/4, $0x02010003 +DATA ·incRotMatrix+0x14(SB)/4, $0x06050407 +DATA ·incRotMatrix+0x18(SB)/4, $0x0A09080B +DATA ·incRotMatrix+0x1c(SB)/4, $0x0E0D0C0F +GLOBL ·incRotMatrix(SB), NOPTR|RODATA, $32 diff --git a/cli/vendor/golang.org/x/crypto/chacha20/chacha_generic.go b/cli/vendor/golang.org/x/crypto/chacha20/chacha_generic.go new file mode 100644 index 0000000..93eb5ae --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/chacha20/chacha_generic.go @@ -0,0 +1,398 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package chacha20 implements the ChaCha20 and XChaCha20 encryption algorithms +// as specified in RFC 8439 and draft-irtf-cfrg-xchacha-01. +package chacha20 + +import ( + "crypto/cipher" + "encoding/binary" + "errors" + "math/bits" + + "golang.org/x/crypto/internal/alias" +) + +const ( + // KeySize is the size of the key used by this cipher, in bytes. + KeySize = 32 + + // NonceSize is the size of the nonce used with the standard variant of this + // cipher, in bytes. + // + // Note that this is too short to be safely generated at random if the same + // key is reused more than 2³² times. + NonceSize = 12 + + // NonceSizeX is the size of the nonce used with the XChaCha20 variant of + // this cipher, in bytes. + NonceSizeX = 24 +) + +// Cipher is a stateful instance of ChaCha20 or XChaCha20 using a particular key +// and nonce. A *Cipher implements the cipher.Stream interface. +type Cipher struct { + // The ChaCha20 state is 16 words: 4 constant, 8 of key, 1 of counter + // (incremented after each block), and 3 of nonce. + key [8]uint32 + counter uint32 + nonce [3]uint32 + + // The last len bytes of buf are leftover key stream bytes from the previous + // XORKeyStream invocation. The size of buf depends on how many blocks are + // computed at a time by xorKeyStreamBlocks. + buf [bufSize]byte + len int + + // overflow is set when the counter overflowed, no more blocks can be + // generated, and the next XORKeyStream call should panic. + overflow bool + + // The counter-independent results of the first round are cached after they + // are computed the first time. + precompDone bool + p1, p5, p9, p13 uint32 + p2, p6, p10, p14 uint32 + p3, p7, p11, p15 uint32 +} + +var _ cipher.Stream = (*Cipher)(nil) + +// NewUnauthenticatedCipher creates a new ChaCha20 stream cipher with the given +// 32 bytes key and a 12 or 24 bytes nonce. If a nonce of 24 bytes is provided, +// the XChaCha20 construction will be used. It returns an error if key or nonce +// have any other length. +// +// Note that ChaCha20, like all stream ciphers, is not authenticated and allows +// attackers to silently tamper with the plaintext. For this reason, it is more +// appropriate as a building block than as a standalone encryption mechanism. +// Instead, consider using package golang.org/x/crypto/chacha20poly1305. +func NewUnauthenticatedCipher(key, nonce []byte) (*Cipher, error) { + // This function is split into a wrapper so that the Cipher allocation will + // be inlined, and depending on how the caller uses the return value, won't + // escape to the heap. + c := &Cipher{} + return newUnauthenticatedCipher(c, key, nonce) +} + +func newUnauthenticatedCipher(c *Cipher, key, nonce []byte) (*Cipher, error) { + if len(key) != KeySize { + return nil, errors.New("chacha20: wrong key size") + } + if len(nonce) == NonceSizeX { + // XChaCha20 uses the ChaCha20 core to mix 16 bytes of the nonce into a + // derived key, allowing it to operate on a nonce of 24 bytes. See + // draft-irtf-cfrg-xchacha-01, Section 2.3. + key, _ = HChaCha20(key, nonce[0:16]) + cNonce := make([]byte, NonceSize) + copy(cNonce[4:12], nonce[16:24]) + nonce = cNonce + } else if len(nonce) != NonceSize { + return nil, errors.New("chacha20: wrong nonce size") + } + + key, nonce = key[:KeySize], nonce[:NonceSize] // bounds check elimination hint + c.key = [8]uint32{ + binary.LittleEndian.Uint32(key[0:4]), + binary.LittleEndian.Uint32(key[4:8]), + binary.LittleEndian.Uint32(key[8:12]), + binary.LittleEndian.Uint32(key[12:16]), + binary.LittleEndian.Uint32(key[16:20]), + binary.LittleEndian.Uint32(key[20:24]), + binary.LittleEndian.Uint32(key[24:28]), + binary.LittleEndian.Uint32(key[28:32]), + } + c.nonce = [3]uint32{ + binary.LittleEndian.Uint32(nonce[0:4]), + binary.LittleEndian.Uint32(nonce[4:8]), + binary.LittleEndian.Uint32(nonce[8:12]), + } + return c, nil +} + +// The constant first 4 words of the ChaCha20 state. +const ( + j0 uint32 = 0x61707865 // expa + j1 uint32 = 0x3320646e // nd 3 + j2 uint32 = 0x79622d32 // 2-by + j3 uint32 = 0x6b206574 // te k +) + +const blockSize = 64 + +// quarterRound is the core of ChaCha20. It shuffles the bits of 4 state words. +// It's executed 4 times for each of the 20 ChaCha20 rounds, operating on all 16 +// words each round, in columnar or diagonal groups of 4 at a time. +func quarterRound(a, b, c, d uint32) (uint32, uint32, uint32, uint32) { + a += b + d ^= a + d = bits.RotateLeft32(d, 16) + c += d + b ^= c + b = bits.RotateLeft32(b, 12) + a += b + d ^= a + d = bits.RotateLeft32(d, 8) + c += d + b ^= c + b = bits.RotateLeft32(b, 7) + return a, b, c, d +} + +// SetCounter sets the Cipher counter. The next invocation of XORKeyStream will +// behave as if (64 * counter) bytes had been encrypted so far. +// +// To prevent accidental counter reuse, SetCounter panics if counter is less +// than the current value. +// +// Note that the execution time of XORKeyStream is not independent of the +// counter value. +func (s *Cipher) SetCounter(counter uint32) { + // Internally, s may buffer multiple blocks, which complicates this + // implementation slightly. When checking whether the counter has rolled + // back, we must use both s.counter and s.len to determine how many blocks + // we have already output. + outputCounter := s.counter - uint32(s.len)/blockSize + if s.overflow || counter < outputCounter { + panic("chacha20: SetCounter attempted to rollback counter") + } + + // In the general case, we set the new counter value and reset s.len to 0, + // causing the next call to XORKeyStream to refill the buffer. However, if + // we're advancing within the existing buffer, we can save work by simply + // setting s.len. + if counter < s.counter { + s.len = int(s.counter-counter) * blockSize + } else { + s.counter = counter + s.len = 0 + } +} + +// XORKeyStream XORs each byte in the given slice with a byte from the +// cipher's key stream. Dst and src must overlap entirely or not at all. +// +// If len(dst) < len(src), XORKeyStream will panic. It is acceptable +// to pass a dst bigger than src, and in that case, XORKeyStream will +// only update dst[:len(src)] and will not touch the rest of dst. +// +// Multiple calls to XORKeyStream behave as if the concatenation of +// the src buffers was passed in a single run. That is, Cipher +// maintains state and does not reset at each XORKeyStream call. +func (s *Cipher) XORKeyStream(dst, src []byte) { + if len(src) == 0 { + return + } + if len(dst) < len(src) { + panic("chacha20: output smaller than input") + } + dst = dst[:len(src)] + if alias.InexactOverlap(dst, src) { + panic("chacha20: invalid buffer overlap") + } + + // First, drain any remaining key stream from a previous XORKeyStream. + if s.len != 0 { + keyStream := s.buf[bufSize-s.len:] + if len(src) < len(keyStream) { + keyStream = keyStream[:len(src)] + } + _ = src[len(keyStream)-1] // bounds check elimination hint + for i, b := range keyStream { + dst[i] = src[i] ^ b + } + s.len -= len(keyStream) + dst, src = dst[len(keyStream):], src[len(keyStream):] + } + if len(src) == 0 { + return + } + + // If we'd need to let the counter overflow and keep generating output, + // panic immediately. If instead we'd only reach the last block, remember + // not to generate any more output after the buffer is drained. + numBlocks := (uint64(len(src)) + blockSize - 1) / blockSize + if s.overflow || uint64(s.counter)+numBlocks > 1<<32 { + panic("chacha20: counter overflow") + } else if uint64(s.counter)+numBlocks == 1<<32 { + s.overflow = true + } + + // xorKeyStreamBlocks implementations expect input lengths that are a + // multiple of bufSize. Platform-specific ones process multiple blocks at a + // time, so have bufSizes that are a multiple of blockSize. + + full := len(src) - len(src)%bufSize + if full > 0 { + s.xorKeyStreamBlocks(dst[:full], src[:full]) + } + dst, src = dst[full:], src[full:] + + // If using a multi-block xorKeyStreamBlocks would overflow, use the generic + // one that does one block at a time. + const blocksPerBuf = bufSize / blockSize + if uint64(s.counter)+blocksPerBuf > 1<<32 { + s.buf = [bufSize]byte{} + numBlocks := (len(src) + blockSize - 1) / blockSize + buf := s.buf[bufSize-numBlocks*blockSize:] + copy(buf, src) + s.xorKeyStreamBlocksGeneric(buf, buf) + s.len = len(buf) - copy(dst, buf) + return + } + + // If we have a partial (multi-)block, pad it for xorKeyStreamBlocks, and + // keep the leftover keystream for the next XORKeyStream invocation. + if len(src) > 0 { + s.buf = [bufSize]byte{} + copy(s.buf[:], src) + s.xorKeyStreamBlocks(s.buf[:], s.buf[:]) + s.len = bufSize - copy(dst, s.buf[:]) + } +} + +func (s *Cipher) xorKeyStreamBlocksGeneric(dst, src []byte) { + if len(dst) != len(src) || len(dst)%blockSize != 0 { + panic("chacha20: internal error: wrong dst and/or src length") + } + + // To generate each block of key stream, the initial cipher state + // (represented below) is passed through 20 rounds of shuffling, + // alternatively applying quarterRounds by columns (like 1, 5, 9, 13) + // or by diagonals (like 1, 6, 11, 12). + // + // 0:cccccccc 1:cccccccc 2:cccccccc 3:cccccccc + // 4:kkkkkkkk 5:kkkkkkkk 6:kkkkkkkk 7:kkkkkkkk + // 8:kkkkkkkk 9:kkkkkkkk 10:kkkkkkkk 11:kkkkkkkk + // 12:bbbbbbbb 13:nnnnnnnn 14:nnnnnnnn 15:nnnnnnnn + // + // c=constant k=key b=blockcount n=nonce + var ( + c0, c1, c2, c3 = j0, j1, j2, j3 + c4, c5, c6, c7 = s.key[0], s.key[1], s.key[2], s.key[3] + c8, c9, c10, c11 = s.key[4], s.key[5], s.key[6], s.key[7] + _, c13, c14, c15 = s.counter, s.nonce[0], s.nonce[1], s.nonce[2] + ) + + // Three quarters of the first round don't depend on the counter, so we can + // calculate them here, and reuse them for multiple blocks in the loop, and + // for future XORKeyStream invocations. + if !s.precompDone { + s.p1, s.p5, s.p9, s.p13 = quarterRound(c1, c5, c9, c13) + s.p2, s.p6, s.p10, s.p14 = quarterRound(c2, c6, c10, c14) + s.p3, s.p7, s.p11, s.p15 = quarterRound(c3, c7, c11, c15) + s.precompDone = true + } + + // A condition of len(src) > 0 would be sufficient, but this also + // acts as a bounds check elimination hint. + for len(src) >= 64 && len(dst) >= 64 { + // The remainder of the first column round. + fcr0, fcr4, fcr8, fcr12 := quarterRound(c0, c4, c8, s.counter) + + // The second diagonal round. + x0, x5, x10, x15 := quarterRound(fcr0, s.p5, s.p10, s.p15) + x1, x6, x11, x12 := quarterRound(s.p1, s.p6, s.p11, fcr12) + x2, x7, x8, x13 := quarterRound(s.p2, s.p7, fcr8, s.p13) + x3, x4, x9, x14 := quarterRound(s.p3, fcr4, s.p9, s.p14) + + // The remaining 18 rounds. + for i := 0; i < 9; i++ { + // Column round. + x0, x4, x8, x12 = quarterRound(x0, x4, x8, x12) + x1, x5, x9, x13 = quarterRound(x1, x5, x9, x13) + x2, x6, x10, x14 = quarterRound(x2, x6, x10, x14) + x3, x7, x11, x15 = quarterRound(x3, x7, x11, x15) + + // Diagonal round. + x0, x5, x10, x15 = quarterRound(x0, x5, x10, x15) + x1, x6, x11, x12 = quarterRound(x1, x6, x11, x12) + x2, x7, x8, x13 = quarterRound(x2, x7, x8, x13) + x3, x4, x9, x14 = quarterRound(x3, x4, x9, x14) + } + + // Add back the initial state to generate the key stream, then + // XOR the key stream with the source and write out the result. + addXor(dst[0:4], src[0:4], x0, c0) + addXor(dst[4:8], src[4:8], x1, c1) + addXor(dst[8:12], src[8:12], x2, c2) + addXor(dst[12:16], src[12:16], x3, c3) + addXor(dst[16:20], src[16:20], x4, c4) + addXor(dst[20:24], src[20:24], x5, c5) + addXor(dst[24:28], src[24:28], x6, c6) + addXor(dst[28:32], src[28:32], x7, c7) + addXor(dst[32:36], src[32:36], x8, c8) + addXor(dst[36:40], src[36:40], x9, c9) + addXor(dst[40:44], src[40:44], x10, c10) + addXor(dst[44:48], src[44:48], x11, c11) + addXor(dst[48:52], src[48:52], x12, s.counter) + addXor(dst[52:56], src[52:56], x13, c13) + addXor(dst[56:60], src[56:60], x14, c14) + addXor(dst[60:64], src[60:64], x15, c15) + + s.counter += 1 + + src, dst = src[blockSize:], dst[blockSize:] + } +} + +// HChaCha20 uses the ChaCha20 core to generate a derived key from a 32 bytes +// key and a 16 bytes nonce. It returns an error if key or nonce have any other +// length. It is used as part of the XChaCha20 construction. +func HChaCha20(key, nonce []byte) ([]byte, error) { + // This function is split into a wrapper so that the slice allocation will + // be inlined, and depending on how the caller uses the return value, won't + // escape to the heap. + out := make([]byte, 32) + return hChaCha20(out, key, nonce) +} + +func hChaCha20(out, key, nonce []byte) ([]byte, error) { + if len(key) != KeySize { + return nil, errors.New("chacha20: wrong HChaCha20 key size") + } + if len(nonce) != 16 { + return nil, errors.New("chacha20: wrong HChaCha20 nonce size") + } + + x0, x1, x2, x3 := j0, j1, j2, j3 + x4 := binary.LittleEndian.Uint32(key[0:4]) + x5 := binary.LittleEndian.Uint32(key[4:8]) + x6 := binary.LittleEndian.Uint32(key[8:12]) + x7 := binary.LittleEndian.Uint32(key[12:16]) + x8 := binary.LittleEndian.Uint32(key[16:20]) + x9 := binary.LittleEndian.Uint32(key[20:24]) + x10 := binary.LittleEndian.Uint32(key[24:28]) + x11 := binary.LittleEndian.Uint32(key[28:32]) + x12 := binary.LittleEndian.Uint32(nonce[0:4]) + x13 := binary.LittleEndian.Uint32(nonce[4:8]) + x14 := binary.LittleEndian.Uint32(nonce[8:12]) + x15 := binary.LittleEndian.Uint32(nonce[12:16]) + + for i := 0; i < 10; i++ { + // Diagonal round. + x0, x4, x8, x12 = quarterRound(x0, x4, x8, x12) + x1, x5, x9, x13 = quarterRound(x1, x5, x9, x13) + x2, x6, x10, x14 = quarterRound(x2, x6, x10, x14) + x3, x7, x11, x15 = quarterRound(x3, x7, x11, x15) + + // Column round. + x0, x5, x10, x15 = quarterRound(x0, x5, x10, x15) + x1, x6, x11, x12 = quarterRound(x1, x6, x11, x12) + x2, x7, x8, x13 = quarterRound(x2, x7, x8, x13) + x3, x4, x9, x14 = quarterRound(x3, x4, x9, x14) + } + + _ = out[31] // bounds check elimination hint + binary.LittleEndian.PutUint32(out[0:4], x0) + binary.LittleEndian.PutUint32(out[4:8], x1) + binary.LittleEndian.PutUint32(out[8:12], x2) + binary.LittleEndian.PutUint32(out[12:16], x3) + binary.LittleEndian.PutUint32(out[16:20], x12) + binary.LittleEndian.PutUint32(out[20:24], x13) + binary.LittleEndian.PutUint32(out[24:28], x14) + binary.LittleEndian.PutUint32(out[28:32], x15) + return out, nil +} diff --git a/cli/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go b/cli/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go new file mode 100644 index 0000000..c709b72 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/chacha20/chacha_noasm.go @@ -0,0 +1,13 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!arm64 && !s390x && !ppc64 && !ppc64le) || !gc || purego + +package chacha20 + +const bufSize = blockSize + +func (s *Cipher) xorKeyStreamBlocks(dst, src []byte) { + s.xorKeyStreamBlocksGeneric(dst, src) +} diff --git a/cli/vendor/golang.org/x/crypto/chacha20/chacha_ppc64x.go b/cli/vendor/golang.org/x/crypto/chacha20/chacha_ppc64x.go new file mode 100644 index 0000000..bd183d9 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/chacha20/chacha_ppc64x.go @@ -0,0 +1,16 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego && (ppc64 || ppc64le) + +package chacha20 + +const bufSize = 256 + +//go:noescape +func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32) + +func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) { + chaCha20_ctr32_vsx(&dst[0], &src[0], len(src), &c.key, &c.counter) +} diff --git a/cli/vendor/golang.org/x/crypto/chacha20/chacha_ppc64x.s b/cli/vendor/golang.org/x/crypto/chacha20/chacha_ppc64x.s new file mode 100644 index 0000000..a660b41 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/chacha20/chacha_ppc64x.s @@ -0,0 +1,501 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Based on CRYPTOGAMS code with the following comment: +// # ==================================================================== +// # Written by Andy Polyakov for the OpenSSL +// # project. The module is, however, dual licensed under OpenSSL and +// # CRYPTOGAMS licenses depending on where you obtain it. For further +// # details see http://www.openssl.org/~appro/cryptogams/. +// # ==================================================================== + +// Code for the perl script that generates the ppc64 assembler +// can be found in the cryptogams repository at the link below. It is based on +// the original from openssl. + +// https://github.com/dot-asm/cryptogams/commit/a60f5b50ed908e91 + +// The differences in this and the original implementation are +// due to the calling conventions and initialization of constants. + +//go:build gc && !purego && (ppc64 || ppc64le) + +#include "textflag.h" + +#define OUT R3 +#define INP R4 +#define LEN R5 +#define KEY R6 +#define CNT R7 +#define TMP R15 + +#define CONSTBASE R16 +#define BLOCKS R17 + +// for VPERMXOR +#define MASK R18 + +DATA consts<>+0x00(SB)/4, $0x61707865 +DATA consts<>+0x04(SB)/4, $0x3320646e +DATA consts<>+0x08(SB)/4, $0x79622d32 +DATA consts<>+0x0c(SB)/4, $0x6b206574 +DATA consts<>+0x10(SB)/4, $0x00000001 +DATA consts<>+0x14(SB)/4, $0x00000000 +DATA consts<>+0x18(SB)/4, $0x00000000 +DATA consts<>+0x1c(SB)/4, $0x00000000 +DATA consts<>+0x20(SB)/4, $0x00000004 +DATA consts<>+0x24(SB)/4, $0x00000000 +DATA consts<>+0x28(SB)/4, $0x00000000 +DATA consts<>+0x2c(SB)/4, $0x00000000 +DATA consts<>+0x30(SB)/4, $0x0e0f0c0d +DATA consts<>+0x34(SB)/4, $0x0a0b0809 +DATA consts<>+0x38(SB)/4, $0x06070405 +DATA consts<>+0x3c(SB)/4, $0x02030001 +DATA consts<>+0x40(SB)/4, $0x0d0e0f0c +DATA consts<>+0x44(SB)/4, $0x090a0b08 +DATA consts<>+0x48(SB)/4, $0x05060704 +DATA consts<>+0x4c(SB)/4, $0x01020300 +DATA consts<>+0x50(SB)/4, $0x61707865 +DATA consts<>+0x54(SB)/4, $0x61707865 +DATA consts<>+0x58(SB)/4, $0x61707865 +DATA consts<>+0x5c(SB)/4, $0x61707865 +DATA consts<>+0x60(SB)/4, $0x3320646e +DATA consts<>+0x64(SB)/4, $0x3320646e +DATA consts<>+0x68(SB)/4, $0x3320646e +DATA consts<>+0x6c(SB)/4, $0x3320646e +DATA consts<>+0x70(SB)/4, $0x79622d32 +DATA consts<>+0x74(SB)/4, $0x79622d32 +DATA consts<>+0x78(SB)/4, $0x79622d32 +DATA consts<>+0x7c(SB)/4, $0x79622d32 +DATA consts<>+0x80(SB)/4, $0x6b206574 +DATA consts<>+0x84(SB)/4, $0x6b206574 +DATA consts<>+0x88(SB)/4, $0x6b206574 +DATA consts<>+0x8c(SB)/4, $0x6b206574 +DATA consts<>+0x90(SB)/4, $0x00000000 +DATA consts<>+0x94(SB)/4, $0x00000001 +DATA consts<>+0x98(SB)/4, $0x00000002 +DATA consts<>+0x9c(SB)/4, $0x00000003 +DATA consts<>+0xa0(SB)/4, $0x11223300 +DATA consts<>+0xa4(SB)/4, $0x55667744 +DATA consts<>+0xa8(SB)/4, $0x99aabb88 +DATA consts<>+0xac(SB)/4, $0xddeeffcc +DATA consts<>+0xb0(SB)/4, $0x22330011 +DATA consts<>+0xb4(SB)/4, $0x66774455 +DATA consts<>+0xb8(SB)/4, $0xaabb8899 +DATA consts<>+0xbc(SB)/4, $0xeeffccdd +GLOBL consts<>(SB), RODATA, $0xc0 + +#ifdef GOARCH_ppc64 +#define BE_XXBRW_INIT() \ + LVSL (R0)(R0), V24 \ + VSPLTISB $3, V25 \ + VXOR V24, V25, V24 \ + +#define BE_XXBRW(vr) VPERM vr, vr, V24, vr +#else +#define BE_XXBRW_INIT() +#define BE_XXBRW(vr) +#endif + +//func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32) +TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40 + MOVD out+0(FP), OUT + MOVD inp+8(FP), INP + MOVD len+16(FP), LEN + MOVD key+24(FP), KEY + MOVD counter+32(FP), CNT + + // Addressing for constants + MOVD $consts<>+0x00(SB), CONSTBASE + MOVD $16, R8 + MOVD $32, R9 + MOVD $48, R10 + MOVD $64, R11 + SRD $6, LEN, BLOCKS + // for VPERMXOR + MOVD $consts<>+0xa0(SB), MASK + MOVD $16, R20 + // V16 + LXVW4X (CONSTBASE)(R0), VS48 + ADD $80,CONSTBASE + + // Load key into V17,V18 + LXVW4X (KEY)(R0), VS49 + LXVW4X (KEY)(R8), VS50 + + // Load CNT, NONCE into V19 + LXVW4X (CNT)(R0), VS51 + + // Clear V27 + VXOR V27, V27, V27 + + BE_XXBRW_INIT() + + // V28 + LXVW4X (CONSTBASE)(R11), VS60 + + // Load mask constants for VPERMXOR + LXVW4X (MASK)(R0), V20 + LXVW4X (MASK)(R20), V21 + + // splat slot from V19 -> V26 + VSPLTW $0, V19, V26 + + VSLDOI $4, V19, V27, V19 + VSLDOI $12, V27, V19, V19 + + VADDUWM V26, V28, V26 + + MOVD $10, R14 + MOVD R14, CTR + PCALIGN $16 +loop_outer_vsx: + // V0, V1, V2, V3 + LXVW4X (R0)(CONSTBASE), VS32 + LXVW4X (R8)(CONSTBASE), VS33 + LXVW4X (R9)(CONSTBASE), VS34 + LXVW4X (R10)(CONSTBASE), VS35 + + // splat values from V17, V18 into V4-V11 + VSPLTW $0, V17, V4 + VSPLTW $1, V17, V5 + VSPLTW $2, V17, V6 + VSPLTW $3, V17, V7 + VSPLTW $0, V18, V8 + VSPLTW $1, V18, V9 + VSPLTW $2, V18, V10 + VSPLTW $3, V18, V11 + + // VOR + VOR V26, V26, V12 + + // splat values from V19 -> V13, V14, V15 + VSPLTW $1, V19, V13 + VSPLTW $2, V19, V14 + VSPLTW $3, V19, V15 + + // splat const values + VSPLTISW $-16, V27 + VSPLTISW $12, V28 + VSPLTISW $8, V29 + VSPLTISW $7, V30 + PCALIGN $16 +loop_vsx: + VADDUWM V0, V4, V0 + VADDUWM V1, V5, V1 + VADDUWM V2, V6, V2 + VADDUWM V3, V7, V3 + + VPERMXOR V12, V0, V21, V12 + VPERMXOR V13, V1, V21, V13 + VPERMXOR V14, V2, V21, V14 + VPERMXOR V15, V3, V21, V15 + + VADDUWM V8, V12, V8 + VADDUWM V9, V13, V9 + VADDUWM V10, V14, V10 + VADDUWM V11, V15, V11 + + VXOR V4, V8, V4 + VXOR V5, V9, V5 + VXOR V6, V10, V6 + VXOR V7, V11, V7 + + VRLW V4, V28, V4 + VRLW V5, V28, V5 + VRLW V6, V28, V6 + VRLW V7, V28, V7 + + VADDUWM V0, V4, V0 + VADDUWM V1, V5, V1 + VADDUWM V2, V6, V2 + VADDUWM V3, V7, V3 + + VPERMXOR V12, V0, V20, V12 + VPERMXOR V13, V1, V20, V13 + VPERMXOR V14, V2, V20, V14 + VPERMXOR V15, V3, V20, V15 + + VADDUWM V8, V12, V8 + VADDUWM V9, V13, V9 + VADDUWM V10, V14, V10 + VADDUWM V11, V15, V11 + + VXOR V4, V8, V4 + VXOR V5, V9, V5 + VXOR V6, V10, V6 + VXOR V7, V11, V7 + + VRLW V4, V30, V4 + VRLW V5, V30, V5 + VRLW V6, V30, V6 + VRLW V7, V30, V7 + + VADDUWM V0, V5, V0 + VADDUWM V1, V6, V1 + VADDUWM V2, V7, V2 + VADDUWM V3, V4, V3 + + VPERMXOR V15, V0, V21, V15 + VPERMXOR V12, V1, V21, V12 + VPERMXOR V13, V2, V21, V13 + VPERMXOR V14, V3, V21, V14 + + VADDUWM V10, V15, V10 + VADDUWM V11, V12, V11 + VADDUWM V8, V13, V8 + VADDUWM V9, V14, V9 + + VXOR V5, V10, V5 + VXOR V6, V11, V6 + VXOR V7, V8, V7 + VXOR V4, V9, V4 + + VRLW V5, V28, V5 + VRLW V6, V28, V6 + VRLW V7, V28, V7 + VRLW V4, V28, V4 + + VADDUWM V0, V5, V0 + VADDUWM V1, V6, V1 + VADDUWM V2, V7, V2 + VADDUWM V3, V4, V3 + + VPERMXOR V15, V0, V20, V15 + VPERMXOR V12, V1, V20, V12 + VPERMXOR V13, V2, V20, V13 + VPERMXOR V14, V3, V20, V14 + + VADDUWM V10, V15, V10 + VADDUWM V11, V12, V11 + VADDUWM V8, V13, V8 + VADDUWM V9, V14, V9 + + VXOR V5, V10, V5 + VXOR V6, V11, V6 + VXOR V7, V8, V7 + VXOR V4, V9, V4 + + VRLW V5, V30, V5 + VRLW V6, V30, V6 + VRLW V7, V30, V7 + VRLW V4, V30, V4 + BDNZ loop_vsx + + VADDUWM V12, V26, V12 + + VMRGEW V0, V1, V27 + VMRGEW V2, V3, V28 + + VMRGOW V0, V1, V0 + VMRGOW V2, V3, V2 + + VMRGEW V4, V5, V29 + VMRGEW V6, V7, V30 + + XXPERMDI VS32, VS34, $0, VS33 + XXPERMDI VS32, VS34, $3, VS35 + XXPERMDI VS59, VS60, $0, VS32 + XXPERMDI VS59, VS60, $3, VS34 + + VMRGOW V4, V5, V4 + VMRGOW V6, V7, V6 + + VMRGEW V8, V9, V27 + VMRGEW V10, V11, V28 + + XXPERMDI VS36, VS38, $0, VS37 + XXPERMDI VS36, VS38, $3, VS39 + XXPERMDI VS61, VS62, $0, VS36 + XXPERMDI VS61, VS62, $3, VS38 + + VMRGOW V8, V9, V8 + VMRGOW V10, V11, V10 + + VMRGEW V12, V13, V29 + VMRGEW V14, V15, V30 + + XXPERMDI VS40, VS42, $0, VS41 + XXPERMDI VS40, VS42, $3, VS43 + XXPERMDI VS59, VS60, $0, VS40 + XXPERMDI VS59, VS60, $3, VS42 + + VMRGOW V12, V13, V12 + VMRGOW V14, V15, V14 + + VSPLTISW $4, V27 + VADDUWM V26, V27, V26 + + XXPERMDI VS44, VS46, $0, VS45 + XXPERMDI VS44, VS46, $3, VS47 + XXPERMDI VS61, VS62, $0, VS44 + XXPERMDI VS61, VS62, $3, VS46 + + VADDUWM V0, V16, V0 + VADDUWM V4, V17, V4 + VADDUWM V8, V18, V8 + VADDUWM V12, V19, V12 + + BE_XXBRW(V0) + BE_XXBRW(V4) + BE_XXBRW(V8) + BE_XXBRW(V12) + + CMPU LEN, $64 + BLT tail_vsx + + // Bottom of loop + LXVW4X (INP)(R0), VS59 + LXVW4X (INP)(R8), VS60 + LXVW4X (INP)(R9), VS61 + LXVW4X (INP)(R10), VS62 + + VXOR V27, V0, V27 + VXOR V28, V4, V28 + VXOR V29, V8, V29 + VXOR V30, V12, V30 + + STXVW4X VS59, (OUT)(R0) + STXVW4X VS60, (OUT)(R8) + ADD $64, INP + STXVW4X VS61, (OUT)(R9) + ADD $-64, LEN + STXVW4X VS62, (OUT)(R10) + ADD $64, OUT + BEQ done_vsx + + VADDUWM V1, V16, V0 + VADDUWM V5, V17, V4 + VADDUWM V9, V18, V8 + VADDUWM V13, V19, V12 + + BE_XXBRW(V0) + BE_XXBRW(V4) + BE_XXBRW(V8) + BE_XXBRW(V12) + + CMPU LEN, $64 + BLT tail_vsx + + LXVW4X (INP)(R0), VS59 + LXVW4X (INP)(R8), VS60 + LXVW4X (INP)(R9), VS61 + LXVW4X (INP)(R10), VS62 + + VXOR V27, V0, V27 + VXOR V28, V4, V28 + VXOR V29, V8, V29 + VXOR V30, V12, V30 + + STXVW4X VS59, (OUT)(R0) + STXVW4X VS60, (OUT)(R8) + ADD $64, INP + STXVW4X VS61, (OUT)(R9) + ADD $-64, LEN + STXVW4X VS62, (OUT)(V10) + ADD $64, OUT + BEQ done_vsx + + VADDUWM V2, V16, V0 + VADDUWM V6, V17, V4 + VADDUWM V10, V18, V8 + VADDUWM V14, V19, V12 + + BE_XXBRW(V0) + BE_XXBRW(V4) + BE_XXBRW(V8) + BE_XXBRW(V12) + + CMPU LEN, $64 + BLT tail_vsx + + LXVW4X (INP)(R0), VS59 + LXVW4X (INP)(R8), VS60 + LXVW4X (INP)(R9), VS61 + LXVW4X (INP)(R10), VS62 + + VXOR V27, V0, V27 + VXOR V28, V4, V28 + VXOR V29, V8, V29 + VXOR V30, V12, V30 + + STXVW4X VS59, (OUT)(R0) + STXVW4X VS60, (OUT)(R8) + ADD $64, INP + STXVW4X VS61, (OUT)(R9) + ADD $-64, LEN + STXVW4X VS62, (OUT)(R10) + ADD $64, OUT + BEQ done_vsx + + VADDUWM V3, V16, V0 + VADDUWM V7, V17, V4 + VADDUWM V11, V18, V8 + VADDUWM V15, V19, V12 + + BE_XXBRW(V0) + BE_XXBRW(V4) + BE_XXBRW(V8) + BE_XXBRW(V12) + + CMPU LEN, $64 + BLT tail_vsx + + LXVW4X (INP)(R0), VS59 + LXVW4X (INP)(R8), VS60 + LXVW4X (INP)(R9), VS61 + LXVW4X (INP)(R10), VS62 + + VXOR V27, V0, V27 + VXOR V28, V4, V28 + VXOR V29, V8, V29 + VXOR V30, V12, V30 + + STXVW4X VS59, (OUT)(R0) + STXVW4X VS60, (OUT)(R8) + ADD $64, INP + STXVW4X VS61, (OUT)(R9) + ADD $-64, LEN + STXVW4X VS62, (OUT)(R10) + ADD $64, OUT + + MOVD $10, R14 + MOVD R14, CTR + BNE loop_outer_vsx + +done_vsx: + // Increment counter by number of 64 byte blocks + MOVWZ (CNT), R14 + ADD BLOCKS, R14 + MOVWZ R14, (CNT) + RET + +tail_vsx: + ADD $32, R1, R11 + MOVD LEN, CTR + + // Save values on stack to copy from + STXVW4X VS32, (R11)(R0) + STXVW4X VS36, (R11)(R8) + STXVW4X VS40, (R11)(R9) + STXVW4X VS44, (R11)(R10) + ADD $-1, R11, R12 + ADD $-1, INP + ADD $-1, OUT + PCALIGN $16 +looptail_vsx: + // Copying the result to OUT + // in bytes. + MOVBZU 1(R12), KEY + MOVBZU 1(INP), TMP + XOR KEY, TMP, KEY + MOVBU KEY, 1(OUT) + BDNZ looptail_vsx + + // Clear the stack values + STXVW4X VS48, (R11)(R0) + STXVW4X VS48, (R11)(R8) + STXVW4X VS48, (R11)(R9) + STXVW4X VS48, (R11)(R10) + BR done_vsx diff --git a/cli/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go b/cli/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go new file mode 100644 index 0000000..683ccfd --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/chacha20/chacha_s390x.go @@ -0,0 +1,27 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego + +package chacha20 + +import "golang.org/x/sys/cpu" + +var haveAsm = cpu.S390X.HasVX + +const bufSize = 256 + +// xorKeyStreamVX is an assembly implementation of XORKeyStream. It must only +// be called when the vector facility is available. Implementation in asm_s390x.s. +// +//go:noescape +func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32) + +func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) { + if cpu.S390X.HasVX { + xorKeyStreamVX(dst, src, &c.key, &c.nonce, &c.counter) + } else { + c.xorKeyStreamBlocksGeneric(dst, src) + } +} diff --git a/cli/vendor/golang.org/x/crypto/chacha20/chacha_s390x.s b/cli/vendor/golang.org/x/crypto/chacha20/chacha_s390x.s new file mode 100644 index 0000000..1eda91a --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/chacha20/chacha_s390x.s @@ -0,0 +1,224 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego + +#include "go_asm.h" +#include "textflag.h" + +// This is an implementation of the ChaCha20 encryption algorithm as +// specified in RFC 7539. It uses vector instructions to compute +// 4 keystream blocks in parallel (256 bytes) which are then XORed +// with the bytes in the input slice. + +GLOBL ·constants<>(SB), RODATA|NOPTR, $32 +// BSWAP: swap bytes in each 4-byte element +DATA ·constants<>+0x00(SB)/4, $0x03020100 +DATA ·constants<>+0x04(SB)/4, $0x07060504 +DATA ·constants<>+0x08(SB)/4, $0x0b0a0908 +DATA ·constants<>+0x0c(SB)/4, $0x0f0e0d0c +// J0: [j0, j1, j2, j3] +DATA ·constants<>+0x10(SB)/4, $0x61707865 +DATA ·constants<>+0x14(SB)/4, $0x3320646e +DATA ·constants<>+0x18(SB)/4, $0x79622d32 +DATA ·constants<>+0x1c(SB)/4, $0x6b206574 + +#define BSWAP V5 +#define J0 V6 +#define KEY0 V7 +#define KEY1 V8 +#define NONCE V9 +#define CTR V10 +#define M0 V11 +#define M1 V12 +#define M2 V13 +#define M3 V14 +#define INC V15 +#define X0 V16 +#define X1 V17 +#define X2 V18 +#define X3 V19 +#define X4 V20 +#define X5 V21 +#define X6 V22 +#define X7 V23 +#define X8 V24 +#define X9 V25 +#define X10 V26 +#define X11 V27 +#define X12 V28 +#define X13 V29 +#define X14 V30 +#define X15 V31 + +#define NUM_ROUNDS 20 + +#define ROUND4(a0, a1, a2, a3, b0, b1, b2, b3, c0, c1, c2, c3, d0, d1, d2, d3) \ + VAF a1, a0, a0 \ + VAF b1, b0, b0 \ + VAF c1, c0, c0 \ + VAF d1, d0, d0 \ + VX a0, a2, a2 \ + VX b0, b2, b2 \ + VX c0, c2, c2 \ + VX d0, d2, d2 \ + VERLLF $16, a2, a2 \ + VERLLF $16, b2, b2 \ + VERLLF $16, c2, c2 \ + VERLLF $16, d2, d2 \ + VAF a2, a3, a3 \ + VAF b2, b3, b3 \ + VAF c2, c3, c3 \ + VAF d2, d3, d3 \ + VX a3, a1, a1 \ + VX b3, b1, b1 \ + VX c3, c1, c1 \ + VX d3, d1, d1 \ + VERLLF $12, a1, a1 \ + VERLLF $12, b1, b1 \ + VERLLF $12, c1, c1 \ + VERLLF $12, d1, d1 \ + VAF a1, a0, a0 \ + VAF b1, b0, b0 \ + VAF c1, c0, c0 \ + VAF d1, d0, d0 \ + VX a0, a2, a2 \ + VX b0, b2, b2 \ + VX c0, c2, c2 \ + VX d0, d2, d2 \ + VERLLF $8, a2, a2 \ + VERLLF $8, b2, b2 \ + VERLLF $8, c2, c2 \ + VERLLF $8, d2, d2 \ + VAF a2, a3, a3 \ + VAF b2, b3, b3 \ + VAF c2, c3, c3 \ + VAF d2, d3, d3 \ + VX a3, a1, a1 \ + VX b3, b1, b1 \ + VX c3, c1, c1 \ + VX d3, d1, d1 \ + VERLLF $7, a1, a1 \ + VERLLF $7, b1, b1 \ + VERLLF $7, c1, c1 \ + VERLLF $7, d1, d1 + +#define PERMUTE(mask, v0, v1, v2, v3) \ + VPERM v0, v0, mask, v0 \ + VPERM v1, v1, mask, v1 \ + VPERM v2, v2, mask, v2 \ + VPERM v3, v3, mask, v3 + +#define ADDV(x, v0, v1, v2, v3) \ + VAF x, v0, v0 \ + VAF x, v1, v1 \ + VAF x, v2, v2 \ + VAF x, v3, v3 + +#define XORV(off, dst, src, v0, v1, v2, v3) \ + VLM off(src), M0, M3 \ + PERMUTE(BSWAP, v0, v1, v2, v3) \ + VX v0, M0, M0 \ + VX v1, M1, M1 \ + VX v2, M2, M2 \ + VX v3, M3, M3 \ + VSTM M0, M3, off(dst) + +#define SHUFFLE(a, b, c, d, t, u, v, w) \ + VMRHF a, c, t \ // t = {a[0], c[0], a[1], c[1]} + VMRHF b, d, u \ // u = {b[0], d[0], b[1], d[1]} + VMRLF a, c, v \ // v = {a[2], c[2], a[3], c[3]} + VMRLF b, d, w \ // w = {b[2], d[2], b[3], d[3]} + VMRHF t, u, a \ // a = {a[0], b[0], c[0], d[0]} + VMRLF t, u, b \ // b = {a[1], b[1], c[1], d[1]} + VMRHF v, w, c \ // c = {a[2], b[2], c[2], d[2]} + VMRLF v, w, d // d = {a[3], b[3], c[3], d[3]} + +// func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32) +TEXT ·xorKeyStreamVX(SB), NOSPLIT, $0 + MOVD $·constants<>(SB), R1 + MOVD dst+0(FP), R2 // R2=&dst[0] + LMG src+24(FP), R3, R4 // R3=&src[0] R4=len(src) + MOVD key+48(FP), R5 // R5=key + MOVD nonce+56(FP), R6 // R6=nonce + MOVD counter+64(FP), R7 // R7=counter + + // load BSWAP and J0 + VLM (R1), BSWAP, J0 + + // setup + MOVD $95, R0 + VLM (R5), KEY0, KEY1 + VLL R0, (R6), NONCE + VZERO M0 + VLEIB $7, $32, M0 + VSRLB M0, NONCE, NONCE + + // initialize counter values + VLREPF (R7), CTR + VZERO INC + VLEIF $1, $1, INC + VLEIF $2, $2, INC + VLEIF $3, $3, INC + VAF INC, CTR, CTR + VREPIF $4, INC + +chacha: + VREPF $0, J0, X0 + VREPF $1, J0, X1 + VREPF $2, J0, X2 + VREPF $3, J0, X3 + VREPF $0, KEY0, X4 + VREPF $1, KEY0, X5 + VREPF $2, KEY0, X6 + VREPF $3, KEY0, X7 + VREPF $0, KEY1, X8 + VREPF $1, KEY1, X9 + VREPF $2, KEY1, X10 + VREPF $3, KEY1, X11 + VLR CTR, X12 + VREPF $1, NONCE, X13 + VREPF $2, NONCE, X14 + VREPF $3, NONCE, X15 + + MOVD $(NUM_ROUNDS/2), R1 + +loop: + ROUND4(X0, X4, X12, X8, X1, X5, X13, X9, X2, X6, X14, X10, X3, X7, X15, X11) + ROUND4(X0, X5, X15, X10, X1, X6, X12, X11, X2, X7, X13, X8, X3, X4, X14, X9) + + ADD $-1, R1 + BNE loop + + // decrement length + ADD $-256, R4 + + // rearrange vectors + SHUFFLE(X0, X1, X2, X3, M0, M1, M2, M3) + ADDV(J0, X0, X1, X2, X3) + SHUFFLE(X4, X5, X6, X7, M0, M1, M2, M3) + ADDV(KEY0, X4, X5, X6, X7) + SHUFFLE(X8, X9, X10, X11, M0, M1, M2, M3) + ADDV(KEY1, X8, X9, X10, X11) + VAF CTR, X12, X12 + SHUFFLE(X12, X13, X14, X15, M0, M1, M2, M3) + ADDV(NONCE, X12, X13, X14, X15) + + // increment counters + VAF INC, CTR, CTR + + // xor keystream with plaintext + XORV(0*64, R2, R3, X0, X4, X8, X12) + XORV(1*64, R2, R3, X1, X5, X9, X13) + XORV(2*64, R2, R3, X2, X6, X10, X14) + XORV(3*64, R2, R3, X3, X7, X11, X15) + + // increment pointers + MOVD $256(R2), R2 + MOVD $256(R3), R3 + + CMPBNE R4, $0, chacha + + VSTEF $0, CTR, (R7) + RET diff --git a/cli/vendor/golang.org/x/crypto/chacha20/xor.go b/cli/vendor/golang.org/x/crypto/chacha20/xor.go new file mode 100644 index 0000000..c2d0485 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/chacha20/xor.go @@ -0,0 +1,42 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found src the LICENSE file. + +package chacha20 + +import "runtime" + +// Platforms that have fast unaligned 32-bit little endian accesses. +const unaligned = runtime.GOARCH == "386" || + runtime.GOARCH == "amd64" || + runtime.GOARCH == "arm64" || + runtime.GOARCH == "ppc64le" || + runtime.GOARCH == "s390x" + +// addXor reads a little endian uint32 from src, XORs it with (a + b) and +// places the result in little endian byte order in dst. +func addXor(dst, src []byte, a, b uint32) { + _, _ = src[3], dst[3] // bounds check elimination hint + if unaligned { + // The compiler should optimize this code into + // 32-bit unaligned little endian loads and stores. + // TODO: delete once the compiler does a reliably + // good job with the generic code below. + // See issue #25111 for more details. + v := uint32(src[0]) + v |= uint32(src[1]) << 8 + v |= uint32(src[2]) << 16 + v |= uint32(src[3]) << 24 + v ^= a + b + dst[0] = byte(v) + dst[1] = byte(v >> 8) + dst[2] = byte(v >> 16) + dst[3] = byte(v >> 24) + } else { + a += b + dst[0] = src[0] ^ byte(a) + dst[1] = src[1] ^ byte(a>>8) + dst[2] = src[2] ^ byte(a>>16) + dst[3] = src[3] ^ byte(a>>24) + } +} diff --git a/cli/vendor/golang.org/x/crypto/curve25519/curve25519.go b/cli/vendor/golang.org/x/crypto/curve25519/curve25519.go new file mode 100644 index 0000000..048faef --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/curve25519/curve25519.go @@ -0,0 +1,93 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package curve25519 provides an implementation of the X25519 function, which +// performs scalar multiplication on the elliptic curve known as Curve25519 +// according to [RFC 7748]. +// +// The curve25519 package is a wrapper for the X25519 implementation in the +// crypto/ecdh package. It is [frozen] and is not accepting new features. +// +// [RFC 7748]: https://datatracker.ietf.org/doc/html/rfc7748 +// [frozen]: https://go.dev/wiki/Frozen +package curve25519 + +import "crypto/ecdh" + +// ScalarMult sets dst to the product scalar * point. +// +// Deprecated: when provided a low-order point, ScalarMult will set dst to all +// zeroes, irrespective of the scalar. Instead, use the X25519 function, which +// will return an error. +func ScalarMult(dst, scalar, point *[32]byte) { + if _, err := x25519(dst, scalar[:], point[:]); err != nil { + // The only error condition for x25519 when the inputs are 32 bytes long + // is if the output would have been the all-zero value. + for i := range dst { + dst[i] = 0 + } + } +} + +// ScalarBaseMult sets dst to the product scalar * base where base is the +// standard generator. +// +// It is recommended to use the X25519 function with Basepoint instead, as +// copying into fixed size arrays can lead to unexpected bugs. +func ScalarBaseMult(dst, scalar *[32]byte) { + curve := ecdh.X25519() + priv, err := curve.NewPrivateKey(scalar[:]) + if err != nil { + panic("curve25519: " + err.Error()) + } + copy(dst[:], priv.PublicKey().Bytes()) +} + +const ( + // ScalarSize is the size of the scalar input to X25519. + ScalarSize = 32 + // PointSize is the size of the point input to X25519. + PointSize = 32 +) + +// Basepoint is the canonical Curve25519 generator. +var Basepoint []byte + +var basePoint = [32]byte{9} + +func init() { Basepoint = basePoint[:] } + +// X25519 returns the result of the scalar multiplication (scalar * point), +// according to RFC 7748, Section 5. scalar, point and the return value are +// slices of 32 bytes. +// +// scalar can be generated at random, for example with crypto/rand. point should +// be either Basepoint or the output of another X25519 call. +// +// If point is Basepoint (but not if it's a different slice with the same +// contents) a precomputed implementation might be used for performance. +func X25519(scalar, point []byte) ([]byte, error) { + // Outline the body of function, to let the allocation be inlined in the + // caller, and possibly avoid escaping to the heap. + var dst [32]byte + return x25519(&dst, scalar, point) +} + +func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) { + curve := ecdh.X25519() + pub, err := curve.NewPublicKey(point) + if err != nil { + return nil, err + } + priv, err := curve.NewPrivateKey(scalar) + if err != nil { + return nil, err + } + out, err := priv.ECDH(pub) + if err != nil { + return nil, err + } + copy(dst[:], out) + return dst[:], nil +} diff --git a/cli/vendor/golang.org/x/crypto/ed25519/ed25519.go b/cli/vendor/golang.org/x/crypto/ed25519/ed25519.go new file mode 100644 index 0000000..df453dc --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ed25519/ed25519.go @@ -0,0 +1,72 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ed25519 implements the Ed25519 signature algorithm. +// +// These functions are also compatible with the “Ed25519” function defined in +// [RFC 8032]. However, unlike RFC 8032's formulation, this package's private key +// representation includes a public key suffix to make multiple signing +// operations with the same key more efficient. This package refers to the RFC +// 8032 private key as the “seed”. +// +// The ed25519 package is a wrapper for the Ed25519 implementation in the +// crypto/ed25519 package. It is [frozen] and is not accepting new features. +// +// [RFC 8032]: https://datatracker.ietf.org/doc/html/rfc8032 +// [frozen]: https://go.dev/wiki/Frozen +package ed25519 + +import ( + "crypto/ed25519" + "io" +) + +const ( + // PublicKeySize is the size, in bytes, of public keys as used in this package. + PublicKeySize = 32 + // PrivateKeySize is the size, in bytes, of private keys as used in this package. + PrivateKeySize = 64 + // SignatureSize is the size, in bytes, of signatures generated and verified by this package. + SignatureSize = 64 + // SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032. + SeedSize = 32 +) + +// PublicKey is the type of Ed25519 public keys. +// +// This type is an alias for crypto/ed25519's PublicKey type. +// See the crypto/ed25519 package for the methods on this type. +type PublicKey = ed25519.PublicKey + +// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer. +// +// This type is an alias for crypto/ed25519's PrivateKey type. +// See the crypto/ed25519 package for the methods on this type. +type PrivateKey = ed25519.PrivateKey + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, crypto/rand.Reader will be used. +func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) { + return ed25519.GenerateKey(rand) +} + +// NewKeyFromSeed calculates a private key from a seed. It will panic if +// len(seed) is not SeedSize. This function is provided for interoperability +// with RFC 8032. RFC 8032's private keys correspond to seeds in this +// package. +func NewKeyFromSeed(seed []byte) PrivateKey { + return ed25519.NewKeyFromSeed(seed) +} + +// Sign signs the message with privateKey and returns a signature. It will +// panic if len(privateKey) is not PrivateKeySize. +func Sign(privateKey PrivateKey, message []byte) []byte { + return ed25519.Sign(privateKey, message) +} + +// Verify reports whether sig is a valid signature of message by publicKey. It +// will panic if len(publicKey) is not PublicKeySize. +func Verify(publicKey PublicKey, message, sig []byte) bool { + return ed25519.Verify(publicKey, message, sig) +} diff --git a/cli/vendor/golang.org/x/crypto/internal/alias/alias.go b/cli/vendor/golang.org/x/crypto/internal/alias/alias.go new file mode 100644 index 0000000..551ff0c --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/alias/alias.go @@ -0,0 +1,31 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +// Package alias implements memory aliasing tests. +package alias + +import "unsafe" + +// AnyOverlap reports whether x and y share memory at any (not necessarily +// corresponding) index. The memory beyond the slice length is ignored. +func AnyOverlap(x, y []byte) bool { + return len(x) > 0 && len(y) > 0 && + uintptr(unsafe.Pointer(&x[0])) <= uintptr(unsafe.Pointer(&y[len(y)-1])) && + uintptr(unsafe.Pointer(&y[0])) <= uintptr(unsafe.Pointer(&x[len(x)-1])) +} + +// InexactOverlap reports whether x and y share memory at any non-corresponding +// index. The memory beyond the slice length is ignored. Note that x and y can +// have different lengths and still not have any inexact overlap. +// +// InexactOverlap can be used to implement the requirements of the crypto/cipher +// AEAD, Block, BlockMode and Stream interfaces. +func InexactOverlap(x, y []byte) bool { + if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] { + return false + } + return AnyOverlap(x, y) +} diff --git a/cli/vendor/golang.org/x/crypto/internal/alias/alias_purego.go b/cli/vendor/golang.org/x/crypto/internal/alias/alias_purego.go new file mode 100644 index 0000000..6fe61b5 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/alias/alias_purego.go @@ -0,0 +1,34 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build purego + +// Package alias implements memory aliasing tests. +package alias + +// This is the Google App Engine standard variant based on reflect +// because the unsafe package and cgo are disallowed. + +import "reflect" + +// AnyOverlap reports whether x and y share memory at any (not necessarily +// corresponding) index. The memory beyond the slice length is ignored. +func AnyOverlap(x, y []byte) bool { + return len(x) > 0 && len(y) > 0 && + reflect.ValueOf(&x[0]).Pointer() <= reflect.ValueOf(&y[len(y)-1]).Pointer() && + reflect.ValueOf(&y[0]).Pointer() <= reflect.ValueOf(&x[len(x)-1]).Pointer() +} + +// InexactOverlap reports whether x and y share memory at any non-corresponding +// index. The memory beyond the slice length is ignored. Note that x and y can +// have different lengths and still not have any inexact overlap. +// +// InexactOverlap can be used to implement the requirements of the crypto/cipher +// AEAD, Block, BlockMode and Stream interfaces. +func InexactOverlap(x, y []byte) bool { + if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] { + return false + } + return AnyOverlap(x, y) +} diff --git a/cli/vendor/golang.org/x/crypto/internal/poly1305/mac_noasm.go b/cli/vendor/golang.org/x/crypto/internal/poly1305/mac_noasm.go new file mode 100644 index 0000000..8d99551 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/poly1305/mac_noasm.go @@ -0,0 +1,9 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!amd64 && !loong64 && !ppc64le && !ppc64 && !s390x) || !gc || purego + +package poly1305 + +type mac struct{ macGeneric } diff --git a/cli/vendor/golang.org/x/crypto/internal/poly1305/poly1305.go b/cli/vendor/golang.org/x/crypto/internal/poly1305/poly1305.go new file mode 100644 index 0000000..4aaea81 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/poly1305/poly1305.go @@ -0,0 +1,99 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package poly1305 implements Poly1305 one-time message authentication code as +// specified in https://cr.yp.to/mac/poly1305-20050329.pdf. +// +// Poly1305 is a fast, one-time authentication function. It is infeasible for an +// attacker to generate an authenticator for a message without the key. However, a +// key must only be used for a single message. Authenticating two different +// messages with the same key allows an attacker to forge authenticators for other +// messages with the same key. +// +// Poly1305 was originally coupled with AES in order to make Poly1305-AES. AES was +// used with a fixed key in order to generate one-time keys from an nonce. +// However, in this package AES isn't used and the one-time key is specified +// directly. +package poly1305 + +import "crypto/subtle" + +// TagSize is the size, in bytes, of a poly1305 authenticator. +const TagSize = 16 + +// Sum generates an authenticator for msg using a one-time key and puts the +// 16-byte result into out. Authenticating two different messages with the same +// key allows an attacker to forge messages at will. +func Sum(out *[16]byte, m []byte, key *[32]byte) { + h := New(key) + h.Write(m) + h.Sum(out[:0]) +} + +// Verify returns true if mac is a valid authenticator for m with the given key. +func Verify(mac *[16]byte, m []byte, key *[32]byte) bool { + var tmp [16]byte + Sum(&tmp, m, key) + return subtle.ConstantTimeCompare(tmp[:], mac[:]) == 1 +} + +// New returns a new MAC computing an authentication +// tag of all data written to it with the given key. +// This allows writing the message progressively instead +// of passing it as a single slice. Common users should use +// the Sum function instead. +// +// The key must be unique for each message, as authenticating +// two different messages with the same key allows an attacker +// to forge messages at will. +func New(key *[32]byte) *MAC { + m := &MAC{} + initialize(key, &m.macState) + return m +} + +// MAC is an io.Writer computing an authentication tag +// of the data written to it. +// +// MAC cannot be used like common hash.Hash implementations, +// because using a poly1305 key twice breaks its security. +// Therefore writing data to a running MAC after calling +// Sum or Verify causes it to panic. +type MAC struct { + mac // platform-dependent implementation + + finalized bool +} + +// Size returns the number of bytes Sum will return. +func (h *MAC) Size() int { return TagSize } + +// Write adds more data to the running message authentication code. +// It never returns an error. +// +// It must not be called after the first call of Sum or Verify. +func (h *MAC) Write(p []byte) (n int, err error) { + if h.finalized { + panic("poly1305: write to MAC after Sum or Verify") + } + return h.mac.Write(p) +} + +// Sum computes the authenticator of all data written to the +// message authentication code. +func (h *MAC) Sum(b []byte) []byte { + var mac [TagSize]byte + h.mac.Sum(&mac) + h.finalized = true + return append(b, mac[:]...) +} + +// Verify returns whether the authenticator of all data written to +// the message authentication code matches the expected value. +func (h *MAC) Verify(expected []byte) bool { + var mac [TagSize]byte + h.mac.Sum(&mac) + h.finalized = true + return subtle.ConstantTimeCompare(expected, mac[:]) == 1 +} diff --git a/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_amd64.s b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_amd64.s new file mode 100644 index 0000000..1337573 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_amd64.s @@ -0,0 +1,93 @@ +// Code generated by command: go run sum_amd64_asm.go -out ../sum_amd64.s -pkg poly1305. DO NOT EDIT. + +//go:build gc && !purego + +// func update(state *macState, msg []byte) +TEXT ·update(SB), $0-32 + MOVQ state+0(FP), DI + MOVQ msg_base+8(FP), SI + MOVQ msg_len+16(FP), R15 + MOVQ (DI), R8 + MOVQ 8(DI), R9 + MOVQ 16(DI), R10 + MOVQ 24(DI), R11 + MOVQ 32(DI), R12 + CMPQ R15, $0x10 + JB bytes_between_0_and_15 + +loop: + ADDQ (SI), R8 + ADCQ 8(SI), R9 + ADCQ $0x01, R10 + LEAQ 16(SI), SI + +multiply: + MOVQ R11, AX + MULQ R8 + MOVQ AX, BX + MOVQ DX, CX + MOVQ R11, AX + MULQ R9 + ADDQ AX, CX + ADCQ $0x00, DX + MOVQ R11, R13 + IMULQ R10, R13 + ADDQ DX, R13 + MOVQ R12, AX + MULQ R8 + ADDQ AX, CX + ADCQ $0x00, DX + MOVQ DX, R8 + MOVQ R12, R14 + IMULQ R10, R14 + MOVQ R12, AX + MULQ R9 + ADDQ AX, R13 + ADCQ DX, R14 + ADDQ R8, R13 + ADCQ $0x00, R14 + MOVQ BX, R8 + MOVQ CX, R9 + MOVQ R13, R10 + ANDQ $0x03, R10 + MOVQ R13, BX + ANDQ $-4, BX + ADDQ BX, R8 + ADCQ R14, R9 + ADCQ $0x00, R10 + SHRQ $0x02, R14, R13 + SHRQ $0x02, R14 + ADDQ R13, R8 + ADCQ R14, R9 + ADCQ $0x00, R10 + SUBQ $0x10, R15 + CMPQ R15, $0x10 + JAE loop + +bytes_between_0_and_15: + TESTQ R15, R15 + JZ done + MOVQ $0x00000001, BX + XORQ CX, CX + XORQ R13, R13 + ADDQ R15, SI + +flush_buffer: + SHLQ $0x08, BX, CX + SHLQ $0x08, BX + MOVB -1(SI), R13 + XORQ R13, BX + DECQ SI + DECQ R15 + JNZ flush_buffer + ADDQ BX, R8 + ADCQ CX, R9 + ADCQ $0x00, R10 + MOVQ $0x00000010, R15 + JMP multiply + +done: + MOVQ R8, (DI) + MOVQ R9, 8(DI) + MOVQ R10, 16(DI) + RET diff --git a/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_asm.go b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_asm.go new file mode 100644 index 0000000..315b84a --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_asm.go @@ -0,0 +1,47 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego && (amd64 || loong64 || ppc64 || ppc64le) + +package poly1305 + +//go:noescape +func update(state *macState, msg []byte) + +// mac is a wrapper for macGeneric that redirects calls that would have gone to +// updateGeneric to update. +// +// Its Write and Sum methods are otherwise identical to the macGeneric ones, but +// using function pointers would carry a major performance cost. +type mac struct{ macGeneric } + +func (h *mac) Write(p []byte) (int, error) { + nn := len(p) + if h.offset > 0 { + n := copy(h.buffer[h.offset:], p) + if h.offset+n < TagSize { + h.offset += n + return nn, nil + } + p = p[n:] + h.offset = 0 + update(&h.macState, h.buffer[:]) + } + if n := len(p) - (len(p) % TagSize); n > 0 { + update(&h.macState, p[:n]) + p = p[n:] + } + if len(p) > 0 { + h.offset += copy(h.buffer[h.offset:], p) + } + return nn, nil +} + +func (h *mac) Sum(out *[16]byte) { + state := h.macState + if h.offset > 0 { + update(&state, h.buffer[:h.offset]) + } + finalize(out, &state.h, &state.s) +} diff --git a/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_generic.go b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_generic.go new file mode 100644 index 0000000..ec2202b --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_generic.go @@ -0,0 +1,312 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file provides the generic implementation of Sum and MAC. Other files +// might provide optimized assembly implementations of some of this code. + +package poly1305 + +import ( + "encoding/binary" + "math/bits" +) + +// Poly1305 [RFC 7539] is a relatively simple algorithm: the authentication tag +// for a 64 bytes message is approximately +// +// s + m[0:16] * r⁴ + m[16:32] * r³ + m[32:48] * r² + m[48:64] * r mod 2¹³⁰ - 5 +// +// for some secret r and s. It can be computed sequentially like +// +// for len(msg) > 0: +// h += read(msg, 16) +// h *= r +// h %= 2¹³⁰ - 5 +// return h + s +// +// All the complexity is about doing performant constant-time math on numbers +// larger than any available numeric type. + +func sumGeneric(out *[TagSize]byte, msg []byte, key *[32]byte) { + h := newMACGeneric(key) + h.Write(msg) + h.Sum(out) +} + +func newMACGeneric(key *[32]byte) macGeneric { + m := macGeneric{} + initialize(key, &m.macState) + return m +} + +// macState holds numbers in saturated 64-bit little-endian limbs. That is, +// the value of [x0, x1, x2] is x[0] + x[1] * 2⁶⁴ + x[2] * 2¹²⁸. +type macState struct { + // h is the main accumulator. It is to be interpreted modulo 2¹³⁰ - 5, but + // can grow larger during and after rounds. It must, however, remain below + // 2 * (2¹³⁰ - 5). + h [3]uint64 + // r and s are the private key components. + r [2]uint64 + s [2]uint64 +} + +type macGeneric struct { + macState + + buffer [TagSize]byte + offset int +} + +// Write splits the incoming message into TagSize chunks, and passes them to +// update. It buffers incomplete chunks. +func (h *macGeneric) Write(p []byte) (int, error) { + nn := len(p) + if h.offset > 0 { + n := copy(h.buffer[h.offset:], p) + if h.offset+n < TagSize { + h.offset += n + return nn, nil + } + p = p[n:] + h.offset = 0 + updateGeneric(&h.macState, h.buffer[:]) + } + if n := len(p) - (len(p) % TagSize); n > 0 { + updateGeneric(&h.macState, p[:n]) + p = p[n:] + } + if len(p) > 0 { + h.offset += copy(h.buffer[h.offset:], p) + } + return nn, nil +} + +// Sum flushes the last incomplete chunk from the buffer, if any, and generates +// the MAC output. It does not modify its state, in order to allow for multiple +// calls to Sum, even if no Write is allowed after Sum. +func (h *macGeneric) Sum(out *[TagSize]byte) { + state := h.macState + if h.offset > 0 { + updateGeneric(&state, h.buffer[:h.offset]) + } + finalize(out, &state.h, &state.s) +} + +// [rMask0, rMask1] is the specified Poly1305 clamping mask in little-endian. It +// clears some bits of the secret coefficient to make it possible to implement +// multiplication more efficiently. +const ( + rMask0 = 0x0FFFFFFC0FFFFFFF + rMask1 = 0x0FFFFFFC0FFFFFFC +) + +// initialize loads the 256-bit key into the two 128-bit secret values r and s. +func initialize(key *[32]byte, m *macState) { + m.r[0] = binary.LittleEndian.Uint64(key[0:8]) & rMask0 + m.r[1] = binary.LittleEndian.Uint64(key[8:16]) & rMask1 + m.s[0] = binary.LittleEndian.Uint64(key[16:24]) + m.s[1] = binary.LittleEndian.Uint64(key[24:32]) +} + +// uint128 holds a 128-bit number as two 64-bit limbs, for use with the +// bits.Mul64 and bits.Add64 intrinsics. +type uint128 struct { + lo, hi uint64 +} + +func mul64(a, b uint64) uint128 { + hi, lo := bits.Mul64(a, b) + return uint128{lo, hi} +} + +func add128(a, b uint128) uint128 { + lo, c := bits.Add64(a.lo, b.lo, 0) + hi, c := bits.Add64(a.hi, b.hi, c) + if c != 0 { + panic("poly1305: unexpected overflow") + } + return uint128{lo, hi} +} + +func shiftRightBy2(a uint128) uint128 { + a.lo = a.lo>>2 | (a.hi&3)<<62 + a.hi = a.hi >> 2 + return a +} + +// updateGeneric absorbs msg into the state.h accumulator. For each chunk m of +// 128 bits of message, it computes +// +// h₊ = (h + m) * r mod 2¹³⁰ - 5 +// +// If the msg length is not a multiple of TagSize, it assumes the last +// incomplete chunk is the final one. +func updateGeneric(state *macState, msg []byte) { + h0, h1, h2 := state.h[0], state.h[1], state.h[2] + r0, r1 := state.r[0], state.r[1] + + for len(msg) > 0 { + var c uint64 + + // For the first step, h + m, we use a chain of bits.Add64 intrinsics. + // The resulting value of h might exceed 2¹³⁰ - 5, but will be partially + // reduced at the end of the multiplication below. + // + // The spec requires us to set a bit just above the message size, not to + // hide leading zeroes. For full chunks, that's 1 << 128, so we can just + // add 1 to the most significant (2¹²⁸) limb, h2. + if len(msg) >= TagSize { + h0, c = bits.Add64(h0, binary.LittleEndian.Uint64(msg[0:8]), 0) + h1, c = bits.Add64(h1, binary.LittleEndian.Uint64(msg[8:16]), c) + h2 += c + 1 + + msg = msg[TagSize:] + } else { + var buf [TagSize]byte + copy(buf[:], msg) + buf[len(msg)] = 1 + + h0, c = bits.Add64(h0, binary.LittleEndian.Uint64(buf[0:8]), 0) + h1, c = bits.Add64(h1, binary.LittleEndian.Uint64(buf[8:16]), c) + h2 += c + + msg = nil + } + + // Multiplication of big number limbs is similar to elementary school + // columnar multiplication. Instead of digits, there are 64-bit limbs. + // + // We are multiplying a 3 limbs number, h, by a 2 limbs number, r. + // + // h2 h1 h0 x + // r1 r0 = + // ---------------- + // h2r0 h1r0 h0r0 <-- individual 128-bit products + // + h2r1 h1r1 h0r1 + // ------------------------ + // m3 m2 m1 m0 <-- result in 128-bit overlapping limbs + // ------------------------ + // m3.hi m2.hi m1.hi m0.hi <-- carry propagation + // + m3.lo m2.lo m1.lo m0.lo + // ------------------------------- + // t4 t3 t2 t1 t0 <-- final result in 64-bit limbs + // + // The main difference from pen-and-paper multiplication is that we do + // carry propagation in a separate step, as if we wrote two digit sums + // at first (the 128-bit limbs), and then carried the tens all at once. + + h0r0 := mul64(h0, r0) + h1r0 := mul64(h1, r0) + h2r0 := mul64(h2, r0) + h0r1 := mul64(h0, r1) + h1r1 := mul64(h1, r1) + h2r1 := mul64(h2, r1) + + // Since h2 is known to be at most 7 (5 + 1 + 1), and r0 and r1 have their + // top 4 bits cleared by rMask{0,1}, we know that their product is not going + // to overflow 64 bits, so we can ignore the high part of the products. + // + // This also means that the product doesn't have a fifth limb (t4). + if h2r0.hi != 0 { + panic("poly1305: unexpected overflow") + } + if h2r1.hi != 0 { + panic("poly1305: unexpected overflow") + } + + m0 := h0r0 + m1 := add128(h1r0, h0r1) // These two additions don't overflow thanks again + m2 := add128(h2r0, h1r1) // to the 4 masked bits at the top of r0 and r1. + m3 := h2r1 + + t0 := m0.lo + t1, c := bits.Add64(m1.lo, m0.hi, 0) + t2, c := bits.Add64(m2.lo, m1.hi, c) + t3, _ := bits.Add64(m3.lo, m2.hi, c) + + // Now we have the result as 4 64-bit limbs, and we need to reduce it + // modulo 2¹³⁰ - 5. The special shape of this Crandall prime lets us do + // a cheap partial reduction according to the reduction identity + // + // c * 2¹³⁰ + n = c * 5 + n mod 2¹³⁰ - 5 + // + // because 2¹³⁰ = 5 mod 2¹³⁰ - 5. Partial reduction since the result is + // likely to be larger than 2¹³⁰ - 5, but still small enough to fit the + // assumptions we make about h in the rest of the code. + // + // See also https://speakerdeck.com/gtank/engineering-prime-numbers?slide=23 + + // We split the final result at the 2¹³⁰ mark into h and cc, the carry. + // Note that the carry bits are effectively shifted left by 2, in other + // words, cc = c * 4 for the c in the reduction identity. + h0, h1, h2 = t0, t1, t2&maskLow2Bits + cc := uint128{t2 & maskNotLow2Bits, t3} + + // To add c * 5 to h, we first add cc = c * 4, and then add (cc >> 2) = c. + + h0, c = bits.Add64(h0, cc.lo, 0) + h1, c = bits.Add64(h1, cc.hi, c) + h2 += c + + cc = shiftRightBy2(cc) + + h0, c = bits.Add64(h0, cc.lo, 0) + h1, c = bits.Add64(h1, cc.hi, c) + h2 += c + + // h2 is at most 3 + 1 + 1 = 5, making the whole of h at most + // + // 5 * 2¹²⁸ + (2¹²⁸ - 1) = 6 * 2¹²⁸ - 1 + } + + state.h[0], state.h[1], state.h[2] = h0, h1, h2 +} + +const ( + maskLow2Bits uint64 = 0x0000000000000003 + maskNotLow2Bits uint64 = ^maskLow2Bits +) + +// select64 returns x if v == 1 and y if v == 0, in constant time. +func select64(v, x, y uint64) uint64 { return ^(v-1)&x | (v-1)&y } + +// [p0, p1, p2] is 2¹³⁰ - 5 in little endian order. +const ( + p0 = 0xFFFFFFFFFFFFFFFB + p1 = 0xFFFFFFFFFFFFFFFF + p2 = 0x0000000000000003 +) + +// finalize completes the modular reduction of h and computes +// +// out = h + s mod 2¹²⁸ +func finalize(out *[TagSize]byte, h *[3]uint64, s *[2]uint64) { + h0, h1, h2 := h[0], h[1], h[2] + + // After the partial reduction in updateGeneric, h might be more than + // 2¹³⁰ - 5, but will be less than 2 * (2¹³⁰ - 5). To complete the reduction + // in constant time, we compute t = h - (2¹³⁰ - 5), and select h as the + // result if the subtraction underflows, and t otherwise. + + hMinusP0, b := bits.Sub64(h0, p0, 0) + hMinusP1, b := bits.Sub64(h1, p1, b) + _, b = bits.Sub64(h2, p2, b) + + // h = h if h < p else h - p + h0 = select64(b, h0, hMinusP0) + h1 = select64(b, h1, hMinusP1) + + // Finally, we compute the last Poly1305 step + // + // tag = h + s mod 2¹²⁸ + // + // by just doing a wide addition with the 128 low bits of h and discarding + // the overflow. + h0, c := bits.Add64(h0, s[0], 0) + h1, _ = bits.Add64(h1, s[1], c) + + binary.LittleEndian.PutUint64(out[0:8], h0) + binary.LittleEndian.PutUint64(out[8:16], h1) +} diff --git a/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_loong64.s b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_loong64.s new file mode 100644 index 0000000..bc8361d --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_loong64.s @@ -0,0 +1,123 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego + +// func update(state *macState, msg []byte) +TEXT ·update(SB), $0-32 + MOVV state+0(FP), R4 + MOVV msg_base+8(FP), R5 + MOVV msg_len+16(FP), R6 + + MOVV $0x10, R7 + + MOVV (R4), R8 // h0 + MOVV 8(R4), R9 // h1 + MOVV 16(R4), R10 // h2 + MOVV 24(R4), R11 // r0 + MOVV 32(R4), R12 // r1 + + BLT R6, R7, bytes_between_0_and_15 + +loop: + MOVV (R5), R14 // msg[0:8] + MOVV 8(R5), R16 // msg[8:16] + ADDV R14, R8, R8 // h0 (x1 + y1 = z1', if z1' < x1 then z1' overflow) + ADDV R16, R9, R27 + SGTU R14, R8, R24 // h0.carry + SGTU R9, R27, R28 + ADDV R27, R24, R9 // h1 + SGTU R27, R9, R24 + OR R24, R28, R24 // h1.carry + ADDV $0x01, R24, R24 + ADDV R10, R24, R10 // h2 + + ADDV $16, R5, R5 // msg = msg[16:] + +multiply: + MULV R8, R11, R14 // h0r0.lo + MULHVU R8, R11, R15 // h0r0.hi + MULV R9, R11, R13 // h1r0.lo + MULHVU R9, R11, R16 // h1r0.hi + ADDV R13, R15, R15 + SGTU R13, R15, R24 + ADDV R24, R16, R16 + MULV R10, R11, R25 + ADDV R16, R25, R25 + MULV R8, R12, R13 // h0r1.lo + MULHVU R8, R12, R16 // h0r1.hi + ADDV R13, R15, R15 + SGTU R13, R15, R24 + ADDV R24, R16, R16 + MOVV R16, R8 + MULV R10, R12, R26 // h2r1 + MULV R9, R12, R13 // h1r1.lo + MULHVU R9, R12, R16 // h1r1.hi + ADDV R13, R25, R25 + ADDV R16, R26, R27 + SGTU R13, R25, R24 + ADDV R27, R24, R26 + ADDV R8, R25, R25 + SGTU R8, R25, R24 + ADDV R24, R26, R26 + AND $3, R25, R10 + AND $-4, R25, R17 + ADDV R17, R14, R8 + ADDV R26, R15, R27 + SGTU R17, R8, R24 + SGTU R26, R27, R28 + ADDV R27, R24, R9 + SGTU R27, R9, R24 + OR R24, R28, R24 + ADDV R24, R10, R10 + SLLV $62, R26, R27 + SRLV $2, R25, R28 + SRLV $2, R26, R26 + OR R27, R28, R25 + ADDV R25, R8, R8 + ADDV R26, R9, R27 + SGTU R25, R8, R24 + SGTU R26, R27, R28 + ADDV R27, R24, R9 + SGTU R27, R9, R24 + OR R24, R28, R24 + ADDV R24, R10, R10 + + SUBV $16, R6, R6 + BGE R6, R7, loop + +bytes_between_0_and_15: + BEQ R6, R0, done + MOVV $1, R14 + XOR R15, R15 + ADDV R6, R5, R5 + +flush_buffer: + MOVBU -1(R5), R25 + SRLV $56, R14, R24 + SLLV $8, R15, R28 + SLLV $8, R14, R14 + OR R24, R28, R15 + XOR R25, R14, R14 + SUBV $1, R6, R6 + SUBV $1, R5, R5 + BNE R6, R0, flush_buffer + + ADDV R14, R8, R8 + SGTU R14, R8, R24 + ADDV R15, R9, R27 + SGTU R15, R27, R28 + ADDV R27, R24, R9 + SGTU R27, R9, R24 + OR R24, R28, R24 + ADDV R10, R24, R10 + + MOVV $16, R6 + JMP multiply + +done: + MOVV R8, (R4) + MOVV R9, 8(R4) + MOVV R10, 16(R4) + RET diff --git a/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64x.s b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64x.s new file mode 100644 index 0000000..6899a1d --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64x.s @@ -0,0 +1,187 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego && (ppc64 || ppc64le) + +#include "textflag.h" + +// This was ported from the amd64 implementation. + +#ifdef GOARCH_ppc64le +#define LE_MOVD MOVD +#define LE_MOVWZ MOVWZ +#define LE_MOVHZ MOVHZ +#else +#define LE_MOVD MOVDBR +#define LE_MOVWZ MOVWBR +#define LE_MOVHZ MOVHBR +#endif + +#define POLY1305_ADD(msg, h0, h1, h2, t0, t1, t2) \ + LE_MOVD (msg)( R0), t0; \ + LE_MOVD (msg)(R24), t1; \ + MOVD $1, t2; \ + ADDC t0, h0, h0; \ + ADDE t1, h1, h1; \ + ADDE t2, h2; \ + ADD $16, msg + +#define POLY1305_MUL(h0, h1, h2, r0, r1, t0, t1, t2, t3, t4, t5) \ + MULLD r0, h0, t0; \ + MULHDU r0, h0, t1; \ + MULLD r0, h1, t4; \ + MULHDU r0, h1, t5; \ + ADDC t4, t1, t1; \ + MULLD r0, h2, t2; \ + MULHDU r1, h0, t4; \ + MULLD r1, h0, h0; \ + ADDE t5, t2, t2; \ + ADDC h0, t1, t1; \ + MULLD h2, r1, t3; \ + ADDZE t4, h0; \ + MULHDU r1, h1, t5; \ + MULLD r1, h1, t4; \ + ADDC t4, t2, t2; \ + ADDE t5, t3, t3; \ + ADDC h0, t2, t2; \ + MOVD $-4, t4; \ + ADDZE t3; \ + RLDICL $0, t2, $62, h2; \ + AND t2, t4, h0; \ + ADDC t0, h0, h0; \ + ADDE t3, t1, h1; \ + SLD $62, t3, t4; \ + SRD $2, t2; \ + ADDZE h2; \ + OR t4, t2, t2; \ + SRD $2, t3; \ + ADDC t2, h0, h0; \ + ADDE t3, h1, h1; \ + ADDZE h2 + +// func update(state *[7]uint64, msg []byte) +TEXT ·update(SB), $0-32 + MOVD state+0(FP), R3 + MOVD msg_base+8(FP), R4 + MOVD msg_len+16(FP), R5 + + MOVD 0(R3), R8 // h0 + MOVD 8(R3), R9 // h1 + MOVD 16(R3), R10 // h2 + MOVD 24(R3), R11 // r0 + MOVD 32(R3), R12 // r1 + + MOVD $8, R24 + + CMP R5, $16 + BLT bytes_between_0_and_15 + +loop: + POLY1305_ADD(R4, R8, R9, R10, R20, R21, R22) + + PCALIGN $16 +multiply: + POLY1305_MUL(R8, R9, R10, R11, R12, R16, R17, R18, R14, R20, R21) + ADD $-16, R5 + CMP R5, $16 + BGE loop + +bytes_between_0_and_15: + CMP R5, $0 + BEQ done + MOVD $0, R16 // h0 + MOVD $0, R17 // h1 + +flush_buffer: + CMP R5, $8 + BLE just1 + + MOVD $8, R21 + SUB R21, R5, R21 + + // Greater than 8 -- load the rightmost remaining bytes in msg + // and put into R17 (h1) + LE_MOVD (R4)(R21), R17 + MOVD $16, R22 + + // Find the offset to those bytes + SUB R5, R22, R22 + SLD $3, R22 + + // Shift to get only the bytes in msg + SRD R22, R17, R17 + + // Put 1 at high end + MOVD $1, R23 + SLD $3, R21 + SLD R21, R23, R23 + OR R23, R17, R17 + + // Remainder is 8 + MOVD $8, R5 + +just1: + CMP R5, $8 + BLT less8 + + // Exactly 8 + LE_MOVD (R4), R16 + + CMP R17, $0 + + // Check if we've already set R17; if not + // set 1 to indicate end of msg. + BNE carry + MOVD $1, R17 + BR carry + +less8: + MOVD $0, R16 // h0 + MOVD $0, R22 // shift count + CMP R5, $4 + BLT less4 + LE_MOVWZ (R4), R16 + ADD $4, R4 + ADD $-4, R5 + MOVD $32, R22 + +less4: + CMP R5, $2 + BLT less2 + LE_MOVHZ (R4), R21 + SLD R22, R21, R21 + OR R16, R21, R16 + ADD $16, R22 + ADD $-2, R5 + ADD $2, R4 + +less2: + CMP R5, $0 + BEQ insert1 + MOVBZ (R4), R21 + SLD R22, R21, R21 + OR R16, R21, R16 + ADD $8, R22 + +insert1: + // Insert 1 at end of msg + MOVD $1, R21 + SLD R22, R21, R21 + OR R16, R21, R16 + +carry: + // Add new values to h0, h1, h2 + ADDC R16, R8 + ADDE R17, R9 + ADDZE R10, R10 + MOVD $16, R5 + ADD R5, R4 + BR multiply + +done: + // Save h0, h1, h2 in state + MOVD R8, 0(R3) + MOVD R9, 8(R3) + MOVD R10, 16(R3) + RET diff --git a/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_s390x.go b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_s390x.go new file mode 100644 index 0000000..e1d033a --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_s390x.go @@ -0,0 +1,76 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego + +package poly1305 + +import ( + "golang.org/x/sys/cpu" +) + +// updateVX is an assembly implementation of Poly1305 that uses vector +// instructions. It must only be called if the vector facility (vx) is +// available. +// +//go:noescape +func updateVX(state *macState, msg []byte) + +// mac is a replacement for macGeneric that uses a larger buffer and redirects +// calls that would have gone to updateGeneric to updateVX if the vector +// facility is installed. +// +// A larger buffer is required for good performance because the vector +// implementation has a higher fixed cost per call than the generic +// implementation. +type mac struct { + macState + + buffer [16 * TagSize]byte // size must be a multiple of block size (16) + offset int +} + +func (h *mac) Write(p []byte) (int, error) { + nn := len(p) + if h.offset > 0 { + n := copy(h.buffer[h.offset:], p) + if h.offset+n < len(h.buffer) { + h.offset += n + return nn, nil + } + p = p[n:] + h.offset = 0 + if cpu.S390X.HasVX { + updateVX(&h.macState, h.buffer[:]) + } else { + updateGeneric(&h.macState, h.buffer[:]) + } + } + + tail := len(p) % len(h.buffer) // number of bytes to copy into buffer + body := len(p) - tail // number of bytes to process now + if body > 0 { + if cpu.S390X.HasVX { + updateVX(&h.macState, p[:body]) + } else { + updateGeneric(&h.macState, p[:body]) + } + } + h.offset = copy(h.buffer[:], p[body:]) // copy tail bytes - can be 0 + return nn, nil +} + +func (h *mac) Sum(out *[TagSize]byte) { + state := h.macState + remainder := h.buffer[:h.offset] + + // Use the generic implementation if we have 2 or fewer blocks left + // to sum. The vector implementation has a higher startup time. + if cpu.S390X.HasVX && len(remainder) > 2*TagSize { + updateVX(&state, remainder) + } else if len(remainder) > 0 { + updateGeneric(&state, remainder) + } + finalize(out, &state.h, &state.s) +} diff --git a/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_s390x.s b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_s390x.s new file mode 100644 index 0000000..0fe3a7c --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/internal/poly1305/sum_s390x.s @@ -0,0 +1,503 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc && !purego + +#include "textflag.h" + +// This implementation of Poly1305 uses the vector facility (vx) +// to process up to 2 blocks (32 bytes) per iteration using an +// algorithm based on the one described in: +// +// NEON crypto, Daniel J. Bernstein & Peter Schwabe +// https://cryptojedi.org/papers/neoncrypto-20120320.pdf +// +// This algorithm uses 5 26-bit limbs to represent a 130-bit +// value. These limbs are, for the most part, zero extended and +// placed into 64-bit vector register elements. Each vector +// register is 128-bits wide and so holds 2 of these elements. +// Using 26-bit limbs allows us plenty of headroom to accommodate +// accumulations before and after multiplication without +// overflowing either 32-bits (before multiplication) or 64-bits +// (after multiplication). +// +// In order to parallelise the operations required to calculate +// the sum we use two separate accumulators and then sum those +// in an extra final step. For compatibility with the generic +// implementation we perform this summation at the end of every +// updateVX call. +// +// To use two accumulators we must multiply the message blocks +// by r² rather than r. Only the final message block should be +// multiplied by r. +// +// Example: +// +// We want to calculate the sum (h) for a 64 byte message (m): +// +// h = m[0:16]r⁴ + m[16:32]r³ + m[32:48]r² + m[48:64]r +// +// To do this we split the calculation into the even indices +// and odd indices of the message. These form our SIMD 'lanes': +// +// h = m[ 0:16]r⁴ + m[32:48]r² + <- lane 0 +// m[16:32]r³ + m[48:64]r <- lane 1 +// +// To calculate this iteratively we refactor so that both lanes +// are written in terms of r² and r: +// +// h = (m[ 0:16]r² + m[32:48])r² + <- lane 0 +// (m[16:32]r² + m[48:64])r <- lane 1 +// ^ ^ +// | coefficients for second iteration +// coefficients for first iteration +// +// So in this case we would have two iterations. In the first +// both lanes are multiplied by r². In the second only the +// first lane is multiplied by r² and the second lane is +// instead multiplied by r. This gives use the odd and even +// powers of r that we need from the original equation. +// +// Notation: +// +// h - accumulator +// r - key +// m - message +// +// [a, b] - SIMD register holding two 64-bit values +// [a, b, c, d] - SIMD register holding four 32-bit values +// xᵢ[n] - limb n of variable x with bit width i +// +// Limbs are expressed in little endian order, so for 26-bit +// limbs x₂₆[4] will be the most significant limb and x₂₆[0] +// will be the least significant limb. + +// masking constants +#define MOD24 V0 // [0x0000000000ffffff, 0x0000000000ffffff] - mask low 24-bits +#define MOD26 V1 // [0x0000000003ffffff, 0x0000000003ffffff] - mask low 26-bits + +// expansion constants (see EXPAND macro) +#define EX0 V2 +#define EX1 V3 +#define EX2 V4 + +// key (r², r or 1 depending on context) +#define R_0 V5 +#define R_1 V6 +#define R_2 V7 +#define R_3 V8 +#define R_4 V9 + +// precalculated coefficients (5r², 5r or 0 depending on context) +#define R5_1 V10 +#define R5_2 V11 +#define R5_3 V12 +#define R5_4 V13 + +// message block (m) +#define M_0 V14 +#define M_1 V15 +#define M_2 V16 +#define M_3 V17 +#define M_4 V18 + +// accumulator (h) +#define H_0 V19 +#define H_1 V20 +#define H_2 V21 +#define H_3 V22 +#define H_4 V23 + +// temporary registers (for short-lived values) +#define T_0 V24 +#define T_1 V25 +#define T_2 V26 +#define T_3 V27 +#define T_4 V28 + +GLOBL ·constants<>(SB), RODATA, $0x30 +// EX0 +DATA ·constants<>+0x00(SB)/8, $0x0006050403020100 +DATA ·constants<>+0x08(SB)/8, $0x1016151413121110 +// EX1 +DATA ·constants<>+0x10(SB)/8, $0x060c0b0a09080706 +DATA ·constants<>+0x18(SB)/8, $0x161c1b1a19181716 +// EX2 +DATA ·constants<>+0x20(SB)/8, $0x0d0d0d0d0d0f0e0d +DATA ·constants<>+0x28(SB)/8, $0x1d1d1d1d1d1f1e1d + +// MULTIPLY multiplies each lane of f and g, partially reduced +// modulo 2¹³⁰ - 5. The result, h, consists of partial products +// in each lane that need to be reduced further to produce the +// final result. +// +// h₁₃₀ = (f₁₃₀g₁₃₀) % 2¹³⁰ + (5f₁₃₀g₁₃₀) / 2¹³⁰ +// +// Note that the multiplication by 5 of the high bits is +// achieved by precalculating the multiplication of four of the +// g coefficients by 5. These are g51-g54. +#define MULTIPLY(f0, f1, f2, f3, f4, g0, g1, g2, g3, g4, g51, g52, g53, g54, h0, h1, h2, h3, h4) \ + VMLOF f0, g0, h0 \ + VMLOF f0, g3, h3 \ + VMLOF f0, g1, h1 \ + VMLOF f0, g4, h4 \ + VMLOF f0, g2, h2 \ + VMLOF f1, g54, T_0 \ + VMLOF f1, g2, T_3 \ + VMLOF f1, g0, T_1 \ + VMLOF f1, g3, T_4 \ + VMLOF f1, g1, T_2 \ + VMALOF f2, g53, h0, h0 \ + VMALOF f2, g1, h3, h3 \ + VMALOF f2, g54, h1, h1 \ + VMALOF f2, g2, h4, h4 \ + VMALOF f2, g0, h2, h2 \ + VMALOF f3, g52, T_0, T_0 \ + VMALOF f3, g0, T_3, T_3 \ + VMALOF f3, g53, T_1, T_1 \ + VMALOF f3, g1, T_4, T_4 \ + VMALOF f3, g54, T_2, T_2 \ + VMALOF f4, g51, h0, h0 \ + VMALOF f4, g54, h3, h3 \ + VMALOF f4, g52, h1, h1 \ + VMALOF f4, g0, h4, h4 \ + VMALOF f4, g53, h2, h2 \ + VAG T_0, h0, h0 \ + VAG T_3, h3, h3 \ + VAG T_1, h1, h1 \ + VAG T_4, h4, h4 \ + VAG T_2, h2, h2 + +// REDUCE performs the following carry operations in four +// stages, as specified in Bernstein & Schwabe: +// +// 1: h₂₆[0]->h₂₆[1] h₂₆[3]->h₂₆[4] +// 2: h₂₆[1]->h₂₆[2] h₂₆[4]->h₂₆[0] +// 3: h₂₆[0]->h₂₆[1] h₂₆[2]->h₂₆[3] +// 4: h₂₆[3]->h₂₆[4] +// +// The result is that all of the limbs are limited to 26-bits +// except for h₂₆[1] and h₂₆[4] which are limited to 27-bits. +// +// Note that although each limb is aligned at 26-bit intervals +// they may contain values that exceed 2²⁶ - 1, hence the need +// to carry the excess bits in each limb. +#define REDUCE(h0, h1, h2, h3, h4) \ + VESRLG $26, h0, T_0 \ + VESRLG $26, h3, T_1 \ + VN MOD26, h0, h0 \ + VN MOD26, h3, h3 \ + VAG T_0, h1, h1 \ + VAG T_1, h4, h4 \ + VESRLG $26, h1, T_2 \ + VESRLG $26, h4, T_3 \ + VN MOD26, h1, h1 \ + VN MOD26, h4, h4 \ + VESLG $2, T_3, T_4 \ + VAG T_3, T_4, T_4 \ + VAG T_2, h2, h2 \ + VAG T_4, h0, h0 \ + VESRLG $26, h2, T_0 \ + VESRLG $26, h0, T_1 \ + VN MOD26, h2, h2 \ + VN MOD26, h0, h0 \ + VAG T_0, h3, h3 \ + VAG T_1, h1, h1 \ + VESRLG $26, h3, T_2 \ + VN MOD26, h3, h3 \ + VAG T_2, h4, h4 + +// EXPAND splits the 128-bit little-endian values in0 and in1 +// into 26-bit big-endian limbs and places the results into +// the first and second lane of d₂₆[0:4] respectively. +// +// The EX0, EX1 and EX2 constants are arrays of byte indices +// for permutation. The permutation both reverses the bytes +// in the input and ensures the bytes are copied into the +// destination limb ready to be shifted into their final +// position. +#define EXPAND(in0, in1, d0, d1, d2, d3, d4) \ + VPERM in0, in1, EX0, d0 \ + VPERM in0, in1, EX1, d2 \ + VPERM in0, in1, EX2, d4 \ + VESRLG $26, d0, d1 \ + VESRLG $30, d2, d3 \ + VESRLG $4, d2, d2 \ + VN MOD26, d0, d0 \ // [in0₂₆[0], in1₂₆[0]] + VN MOD26, d3, d3 \ // [in0₂₆[3], in1₂₆[3]] + VN MOD26, d1, d1 \ // [in0₂₆[1], in1₂₆[1]] + VN MOD24, d4, d4 \ // [in0₂₆[4], in1₂₆[4]] + VN MOD26, d2, d2 // [in0₂₆[2], in1₂₆[2]] + +// func updateVX(state *macState, msg []byte) +TEXT ·updateVX(SB), NOSPLIT, $0 + MOVD state+0(FP), R1 + LMG msg+8(FP), R2, R3 // R2=msg_base, R3=msg_len + + // load EX0, EX1 and EX2 + MOVD $·constants<>(SB), R5 + VLM (R5), EX0, EX2 + + // generate masks + VGMG $(64-24), $63, MOD24 // [0x00ffffff, 0x00ffffff] + VGMG $(64-26), $63, MOD26 // [0x03ffffff, 0x03ffffff] + + // load h (accumulator) and r (key) from state + VZERO T_1 // [0, 0] + VL 0(R1), T_0 // [h₆₄[0], h₆₄[1]] + VLEG $0, 16(R1), T_1 // [h₆₄[2], 0] + VL 24(R1), T_2 // [r₆₄[0], r₆₄[1]] + VPDI $0, T_0, T_2, T_3 // [h₆₄[0], r₆₄[0]] + VPDI $5, T_0, T_2, T_4 // [h₆₄[1], r₆₄[1]] + + // unpack h and r into 26-bit limbs + // note: h₆₄[2] may have the low 3 bits set, so h₂₆[4] is a 27-bit value + VN MOD26, T_3, H_0 // [h₂₆[0], r₂₆[0]] + VZERO H_1 // [0, 0] + VZERO H_3 // [0, 0] + VGMG $(64-12-14), $(63-12), T_0 // [0x03fff000, 0x03fff000] - 26-bit mask with low 12 bits masked out + VESLG $24, T_1, T_1 // [h₆₄[2]<<24, 0] + VERIMG $-26&63, T_3, MOD26, H_1 // [h₂₆[1], r₂₆[1]] + VESRLG $+52&63, T_3, H_2 // [h₂₆[2], r₂₆[2]] - low 12 bits only + VERIMG $-14&63, T_4, MOD26, H_3 // [h₂₆[1], r₂₆[1]] + VESRLG $40, T_4, H_4 // [h₂₆[4], r₂₆[4]] - low 24 bits only + VERIMG $+12&63, T_4, T_0, H_2 // [h₂₆[2], r₂₆[2]] - complete + VO T_1, H_4, H_4 // [h₂₆[4], r₂₆[4]] - complete + + // replicate r across all 4 vector elements + VREPF $3, H_0, R_0 // [r₂₆[0], r₂₆[0], r₂₆[0], r₂₆[0]] + VREPF $3, H_1, R_1 // [r₂₆[1], r₂₆[1], r₂₆[1], r₂₆[1]] + VREPF $3, H_2, R_2 // [r₂₆[2], r₂₆[2], r₂₆[2], r₂₆[2]] + VREPF $3, H_3, R_3 // [r₂₆[3], r₂₆[3], r₂₆[3], r₂₆[3]] + VREPF $3, H_4, R_4 // [r₂₆[4], r₂₆[4], r₂₆[4], r₂₆[4]] + + // zero out lane 1 of h + VLEIG $1, $0, H_0 // [h₂₆[0], 0] + VLEIG $1, $0, H_1 // [h₂₆[1], 0] + VLEIG $1, $0, H_2 // [h₂₆[2], 0] + VLEIG $1, $0, H_3 // [h₂₆[3], 0] + VLEIG $1, $0, H_4 // [h₂₆[4], 0] + + // calculate 5r (ignore least significant limb) + VREPIF $5, T_0 + VMLF T_0, R_1, R5_1 // [5r₂₆[1], 5r₂₆[1], 5r₂₆[1], 5r₂₆[1]] + VMLF T_0, R_2, R5_2 // [5r₂₆[2], 5r₂₆[2], 5r₂₆[2], 5r₂₆[2]] + VMLF T_0, R_3, R5_3 // [5r₂₆[3], 5r₂₆[3], 5r₂₆[3], 5r₂₆[3]] + VMLF T_0, R_4, R5_4 // [5r₂₆[4], 5r₂₆[4], 5r₂₆[4], 5r₂₆[4]] + + // skip r² calculation if we are only calculating one block + CMPBLE R3, $16, skip + + // calculate r² + MULTIPLY(R_0, R_1, R_2, R_3, R_4, R_0, R_1, R_2, R_3, R_4, R5_1, R5_2, R5_3, R5_4, M_0, M_1, M_2, M_3, M_4) + REDUCE(M_0, M_1, M_2, M_3, M_4) + VGBM $0x0f0f, T_0 + VERIMG $0, M_0, T_0, R_0 // [r₂₆[0], r²₂₆[0], r₂₆[0], r²₂₆[0]] + VERIMG $0, M_1, T_0, R_1 // [r₂₆[1], r²₂₆[1], r₂₆[1], r²₂₆[1]] + VERIMG $0, M_2, T_0, R_2 // [r₂₆[2], r²₂₆[2], r₂₆[2], r²₂₆[2]] + VERIMG $0, M_3, T_0, R_3 // [r₂₆[3], r²₂₆[3], r₂₆[3], r²₂₆[3]] + VERIMG $0, M_4, T_0, R_4 // [r₂₆[4], r²₂₆[4], r₂₆[4], r²₂₆[4]] + + // calculate 5r² (ignore least significant limb) + VREPIF $5, T_0 + VMLF T_0, R_1, R5_1 // [5r₂₆[1], 5r²₂₆[1], 5r₂₆[1], 5r²₂₆[1]] + VMLF T_0, R_2, R5_2 // [5r₂₆[2], 5r²₂₆[2], 5r₂₆[2], 5r²₂₆[2]] + VMLF T_0, R_3, R5_3 // [5r₂₆[3], 5r²₂₆[3], 5r₂₆[3], 5r²₂₆[3]] + VMLF T_0, R_4, R5_4 // [5r₂₆[4], 5r²₂₆[4], 5r₂₆[4], 5r²₂₆[4]] + +loop: + CMPBLE R3, $32, b2 // 2 or fewer blocks remaining, need to change key coefficients + + // load next 2 blocks from message + VLM (R2), T_0, T_1 + + // update message slice + SUB $32, R3 + MOVD $32(R2), R2 + + // unpack message blocks into 26-bit big-endian limbs + EXPAND(T_0, T_1, M_0, M_1, M_2, M_3, M_4) + + // add 2¹²⁸ to each message block value + VLEIB $4, $1, M_4 + VLEIB $12, $1, M_4 + +multiply: + // accumulate the incoming message + VAG H_0, M_0, M_0 + VAG H_3, M_3, M_3 + VAG H_1, M_1, M_1 + VAG H_4, M_4, M_4 + VAG H_2, M_2, M_2 + + // multiply the accumulator by the key coefficient + MULTIPLY(M_0, M_1, M_2, M_3, M_4, R_0, R_1, R_2, R_3, R_4, R5_1, R5_2, R5_3, R5_4, H_0, H_1, H_2, H_3, H_4) + + // carry and partially reduce the partial products + REDUCE(H_0, H_1, H_2, H_3, H_4) + + CMPBNE R3, $0, loop + +finish: + // sum lane 0 and lane 1 and put the result in lane 1 + VZERO T_0 + VSUMQG H_0, T_0, H_0 + VSUMQG H_3, T_0, H_3 + VSUMQG H_1, T_0, H_1 + VSUMQG H_4, T_0, H_4 + VSUMQG H_2, T_0, H_2 + + // reduce again after summation + // TODO(mundaym): there might be a more efficient way to do this + // now that we only have 1 active lane. For example, we could + // simultaneously pack the values as we reduce them. + REDUCE(H_0, H_1, H_2, H_3, H_4) + + // carry h[1] through to h[4] so that only h[4] can exceed 2²⁶ - 1 + // TODO(mundaym): in testing this final carry was unnecessary. + // Needs a proof before it can be removed though. + VESRLG $26, H_1, T_1 + VN MOD26, H_1, H_1 + VAQ T_1, H_2, H_2 + VESRLG $26, H_2, T_2 + VN MOD26, H_2, H_2 + VAQ T_2, H_3, H_3 + VESRLG $26, H_3, T_3 + VN MOD26, H_3, H_3 + VAQ T_3, H_4, H_4 + + // h is now < 2(2¹³⁰ - 5) + // Pack each lane in h₂₆[0:4] into h₁₂₈[0:1]. + VESLG $26, H_1, H_1 + VESLG $26, H_3, H_3 + VO H_0, H_1, H_0 + VO H_2, H_3, H_2 + VESLG $4, H_2, H_2 + VLEIB $7, $48, H_1 + VSLB H_1, H_2, H_2 + VO H_0, H_2, H_0 + VLEIB $7, $104, H_1 + VSLB H_1, H_4, H_3 + VO H_3, H_0, H_0 + VLEIB $7, $24, H_1 + VSRLB H_1, H_4, H_1 + + // update state + VSTEG $1, H_0, 0(R1) + VSTEG $0, H_0, 8(R1) + VSTEG $1, H_1, 16(R1) + RET + +b2: // 2 or fewer blocks remaining + CMPBLE R3, $16, b1 + + // Load the 2 remaining blocks (17-32 bytes remaining). + MOVD $-17(R3), R0 // index of final byte to load modulo 16 + VL (R2), T_0 // load full 16 byte block + VLL R0, 16(R2), T_1 // load final (possibly partial) block and pad with zeros to 16 bytes + + // The Poly1305 algorithm requires that a 1 bit be appended to + // each message block. If the final block is less than 16 bytes + // long then it is easiest to insert the 1 before the message + // block is split into 26-bit limbs. If, on the other hand, the + // final message block is 16 bytes long then we append the 1 bit + // after expansion as normal. + MOVBZ $1, R0 + MOVD $-16(R3), R3 // index of byte in last block to insert 1 at (could be 16) + CMPBEQ R3, $16, 2(PC) // skip the insertion if the final block is 16 bytes long + VLVGB R3, R0, T_1 // insert 1 into the byte at index R3 + + // Split both blocks into 26-bit limbs in the appropriate lanes. + EXPAND(T_0, T_1, M_0, M_1, M_2, M_3, M_4) + + // Append a 1 byte to the end of the second to last block. + VLEIB $4, $1, M_4 + + // Append a 1 byte to the end of the last block only if it is a + // full 16 byte block. + CMPBNE R3, $16, 2(PC) + VLEIB $12, $1, M_4 + + // Finally, set up the coefficients for the final multiplication. + // We have previously saved r and 5r in the 32-bit even indexes + // of the R_[0-4] and R5_[1-4] coefficient registers. + // + // We want lane 0 to be multiplied by r² so that can be kept the + // same. We want lane 1 to be multiplied by r so we need to move + // the saved r value into the 32-bit odd index in lane 1 by + // rotating the 64-bit lane by 32. + VGBM $0x00ff, T_0 // [0, 0xffffffffffffffff] - mask lane 1 only + VERIMG $32, R_0, T_0, R_0 // [_, r²₂₆[0], _, r₂₆[0]] + VERIMG $32, R_1, T_0, R_1 // [_, r²₂₆[1], _, r₂₆[1]] + VERIMG $32, R_2, T_0, R_2 // [_, r²₂₆[2], _, r₂₆[2]] + VERIMG $32, R_3, T_0, R_3 // [_, r²₂₆[3], _, r₂₆[3]] + VERIMG $32, R_4, T_0, R_4 // [_, r²₂₆[4], _, r₂₆[4]] + VERIMG $32, R5_1, T_0, R5_1 // [_, 5r²₂₆[1], _, 5r₂₆[1]] + VERIMG $32, R5_2, T_0, R5_2 // [_, 5r²₂₆[2], _, 5r₂₆[2]] + VERIMG $32, R5_3, T_0, R5_3 // [_, 5r²₂₆[3], _, 5r₂₆[3]] + VERIMG $32, R5_4, T_0, R5_4 // [_, 5r²₂₆[4], _, 5r₂₆[4]] + + MOVD $0, R3 + BR multiply + +skip: + CMPBEQ R3, $0, finish + +b1: // 1 block remaining + + // Load the final block (1-16 bytes). This will be placed into + // lane 0. + MOVD $-1(R3), R0 + VLL R0, (R2), T_0 // pad to 16 bytes with zeros + + // The Poly1305 algorithm requires that a 1 bit be appended to + // each message block. If the final block is less than 16 bytes + // long then it is easiest to insert the 1 before the message + // block is split into 26-bit limbs. If, on the other hand, the + // final message block is 16 bytes long then we append the 1 bit + // after expansion as normal. + MOVBZ $1, R0 + CMPBEQ R3, $16, 2(PC) + VLVGB R3, R0, T_0 + + // Set the message block in lane 1 to the value 0 so that it + // can be accumulated without affecting the final result. + VZERO T_1 + + // Split the final message block into 26-bit limbs in lane 0. + // Lane 1 will be contain 0. + EXPAND(T_0, T_1, M_0, M_1, M_2, M_3, M_4) + + // Append a 1 byte to the end of the last block only if it is a + // full 16 byte block. + CMPBNE R3, $16, 2(PC) + VLEIB $4, $1, M_4 + + // We have previously saved r and 5r in the 32-bit even indexes + // of the R_[0-4] and R5_[1-4] coefficient registers. + // + // We want lane 0 to be multiplied by r so we need to move the + // saved r value into the 32-bit odd index in lane 0. We want + // lane 1 to be set to the value 1. This makes multiplication + // a no-op. We do this by setting lane 1 in every register to 0 + // and then just setting the 32-bit index 3 in R_0 to 1. + VZERO T_0 + MOVD $0, R0 + MOVD $0x10111213, R12 + VLVGP R12, R0, T_1 // [_, 0x10111213, _, 0x00000000] + VPERM T_0, R_0, T_1, R_0 // [_, r₂₆[0], _, 0] + VPERM T_0, R_1, T_1, R_1 // [_, r₂₆[1], _, 0] + VPERM T_0, R_2, T_1, R_2 // [_, r₂₆[2], _, 0] + VPERM T_0, R_3, T_1, R_3 // [_, r₂₆[3], _, 0] + VPERM T_0, R_4, T_1, R_4 // [_, r₂₆[4], _, 0] + VPERM T_0, R5_1, T_1, R5_1 // [_, 5r₂₆[1], _, 0] + VPERM T_0, R5_2, T_1, R5_2 // [_, 5r₂₆[2], _, 0] + VPERM T_0, R5_3, T_1, R5_3 // [_, 5r₂₆[3], _, 0] + VPERM T_0, R5_4, T_1, R5_4 // [_, 5r₂₆[4], _, 0] + + // Set the value of lane 1 to be 1. + VLEIF $3, $1, R_0 // [_, r₂₆[0], _, 1] + + MOVD $0, R3 + BR multiply diff --git a/cli/vendor/golang.org/x/crypto/ripemd160/ripemd160.go b/cli/vendor/golang.org/x/crypto/ripemd160/ripemd160.go new file mode 100644 index 0000000..b6d33ef --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ripemd160/ripemd160.go @@ -0,0 +1,124 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ripemd160 implements the RIPEMD-160 hash algorithm. +// +// Deprecated: RIPEMD-160 is a legacy hash and should not be used for new +// applications. Also, this package does not and will not provide an optimized +// implementation. Instead, use a modern hash like SHA-256 (from crypto/sha256). +package ripemd160 + +// RIPEMD-160 is designed by Hans Dobbertin, Antoon Bosselaers, and Bart +// Preneel with specifications available at: +// http://homes.esat.kuleuven.be/~cosicart/pdf/AB-9601/AB-9601.pdf. + +import ( + "crypto" + "hash" +) + +func init() { + crypto.RegisterHash(crypto.RIPEMD160, New) +} + +// The size of the checksum in bytes. +const Size = 20 + +// The block size of the hash algorithm in bytes. +const BlockSize = 64 + +const ( + _s0 = 0x67452301 + _s1 = 0xefcdab89 + _s2 = 0x98badcfe + _s3 = 0x10325476 + _s4 = 0xc3d2e1f0 +) + +// digest represents the partial evaluation of a checksum. +type digest struct { + s [5]uint32 // running context + x [BlockSize]byte // temporary buffer + nx int // index into x + tc uint64 // total count of bytes processed +} + +func (d *digest) Reset() { + d.s[0], d.s[1], d.s[2], d.s[3], d.s[4] = _s0, _s1, _s2, _s3, _s4 + d.nx = 0 + d.tc = 0 +} + +// New returns a new hash.Hash computing the checksum. +func New() hash.Hash { + result := new(digest) + result.Reset() + return result +} + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.tc += uint64(nn) + if d.nx > 0 { + n := len(p) + if n > BlockSize-d.nx { + n = BlockSize - d.nx + } + for i := 0; i < n; i++ { + d.x[d.nx+i] = p[i] + } + d.nx += n + if d.nx == BlockSize { + _Block(d, d.x[0:]) + d.nx = 0 + } + p = p[n:] + } + n := _Block(d, p) + p = p[n:] + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d0 *digest) Sum(in []byte) []byte { + // Make a copy of d0 so that caller can keep writing and summing. + d := *d0 + + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + tc := d.tc + var tmp [64]byte + tmp[0] = 0x80 + if tc%64 < 56 { + d.Write(tmp[0 : 56-tc%64]) + } else { + d.Write(tmp[0 : 64+56-tc%64]) + } + + // Length in bits. + tc <<= 3 + for i := uint(0); i < 8; i++ { + tmp[i] = byte(tc >> (8 * i)) + } + d.Write(tmp[0:8]) + + if d.nx != 0 { + panic("d.nx != 0") + } + + var digest [Size]byte + for i, s := range d.s { + digest[i*4] = byte(s) + digest[i*4+1] = byte(s >> 8) + digest[i*4+2] = byte(s >> 16) + digest[i*4+3] = byte(s >> 24) + } + + return append(in, digest[:]...) +} diff --git a/cli/vendor/golang.org/x/crypto/ripemd160/ripemd160block.go b/cli/vendor/golang.org/x/crypto/ripemd160/ripemd160block.go new file mode 100644 index 0000000..e0edc02 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ripemd160/ripemd160block.go @@ -0,0 +1,165 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// RIPEMD-160 block step. +// In its own file so that a faster assembly or C version +// can be substituted easily. + +package ripemd160 + +import ( + "math/bits" +) + +// work buffer indices and roll amounts for one line +var _n = [80]uint{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, +} + +var _r = [80]uint{ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, +} + +// same for the other parallel one +var n_ = [80]uint{ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, +} + +var r_ = [80]uint{ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, +} + +func _Block(md *digest, p []byte) int { + n := 0 + var x [16]uint32 + var alpha, beta uint32 + for len(p) >= BlockSize { + a, b, c, d, e := md.s[0], md.s[1], md.s[2], md.s[3], md.s[4] + aa, bb, cc, dd, ee := a, b, c, d, e + j := 0 + for i := 0; i < 16; i++ { + x[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24 + j += 4 + } + + // round 1 + i := 0 + for i < 16 { + alpha = a + (b ^ c ^ d) + x[_n[i]] + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 2 + for i < 32 { + alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999 + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 3 + for i < 48 { + alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1 + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 4 + for i < 64 { + alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 5 + for i < 80 { + alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ cc ^ dd) + x[n_[i]] + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // combine results + dd += c + md.s[1] + md.s[1] = md.s[2] + d + ee + md.s[2] = md.s[3] + e + aa + md.s[3] = md.s[4] + a + bb + md.s[4] = md.s[0] + b + cc + md.s[0] = dd + + p = p[BlockSize:] + n += BlockSize + } + return n +} diff --git a/cli/vendor/golang.org/x/crypto/sha3/hashes.go b/cli/vendor/golang.org/x/crypto/sha3/hashes.go new file mode 100644 index 0000000..a51269d --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/sha3/hashes.go @@ -0,0 +1,95 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sha3 implements the SHA-3 hash algorithms and the SHAKE extendable +// output functions defined in FIPS 202. +// +// Most of this package is a wrapper around the crypto/sha3 package in the +// standard library. The only exception is the legacy Keccak hash functions. +package sha3 + +import ( + "crypto/sha3" + "hash" +) + +// New224 creates a new SHA3-224 hash. +// Its generic security strength is 224 bits against preimage attacks, +// and 112 bits against collision attacks. +// +// It is a wrapper for the [sha3.New224] function in the standard library. +// +//go:fix inline +func New224() hash.Hash { + return sha3.New224() +} + +// New256 creates a new SHA3-256 hash. +// Its generic security strength is 256 bits against preimage attacks, +// and 128 bits against collision attacks. +// +// It is a wrapper for the [sha3.New256] function in the standard library. +// +//go:fix inline +func New256() hash.Hash { + return sha3.New256() +} + +// New384 creates a new SHA3-384 hash. +// Its generic security strength is 384 bits against preimage attacks, +// and 192 bits against collision attacks. +// +// It is a wrapper for the [sha3.New384] function in the standard library. +// +//go:fix inline +func New384() hash.Hash { + return sha3.New384() +} + +// New512 creates a new SHA3-512 hash. +// Its generic security strength is 512 bits against preimage attacks, +// and 256 bits against collision attacks. +// +// It is a wrapper for the [sha3.New512] function in the standard library. +// +//go:fix inline +func New512() hash.Hash { + return sha3.New512() +} + +// Sum224 returns the SHA3-224 digest of the data. +// +// It is a wrapper for the [sha3.Sum224] function in the standard library. +// +//go:fix inline +func Sum224(data []byte) [28]byte { + return sha3.Sum224(data) +} + +// Sum256 returns the SHA3-256 digest of the data. +// +// It is a wrapper for the [sha3.Sum256] function in the standard library. +// +//go:fix inline +func Sum256(data []byte) [32]byte { + return sha3.Sum256(data) +} + +// Sum384 returns the SHA3-384 digest of the data. +// +// It is a wrapper for the [sha3.Sum384] function in the standard library. +// +//go:fix inline +func Sum384(data []byte) [48]byte { + return sha3.Sum384(data) +} + +// Sum512 returns the SHA3-512 digest of the data. +// +// It is a wrapper for the [sha3.Sum512] function in the standard library. +// +//go:fix inline +func Sum512(data []byte) [64]byte { + return sha3.Sum512(data) +} diff --git a/cli/vendor/golang.org/x/crypto/sha3/legacy_hash.go b/cli/vendor/golang.org/x/crypto/sha3/legacy_hash.go new file mode 100644 index 0000000..b878453 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/sha3/legacy_hash.go @@ -0,0 +1,263 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// This implementation is only used for NewLegacyKeccak256 and +// NewLegacyKeccak512, which are not implemented by crypto/sha3. +// All other functions in this package are wrappers around crypto/sha3. + +import ( + "crypto/subtle" + "encoding/binary" + "errors" + "hash" + "unsafe" + + "golang.org/x/sys/cpu" +) + +const ( + dsbyteKeccak = 0b00000001 + + // rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in + // bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits. + rateK256 = (1600 - 256) / 8 + rateK512 = (1600 - 512) / 8 + rateK1024 = (1600 - 1024) / 8 +) + +// NewLegacyKeccak256 creates a new Keccak-256 hash. +// +// Only use this function if you require compatibility with an existing cryptosystem +// that uses non-standard padding. All other users should use New256 instead. +func NewLegacyKeccak256() hash.Hash { + return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak} +} + +// NewLegacyKeccak512 creates a new Keccak-512 hash. +// +// Only use this function if you require compatibility with an existing cryptosystem +// that uses non-standard padding. All other users should use New512 instead. +func NewLegacyKeccak512() hash.Hash { + return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak} +} + +// spongeDirection indicates the direction bytes are flowing through the sponge. +type spongeDirection int + +const ( + // spongeAbsorbing indicates that the sponge is absorbing input. + spongeAbsorbing spongeDirection = iota + // spongeSqueezing indicates that the sponge is being squeezed. + spongeSqueezing +) + +type state struct { + a [1600 / 8]byte // main state of the hash + + // a[n:rate] is the buffer. If absorbing, it's the remaining space to XOR + // into before running the permutation. If squeezing, it's the remaining + // output to produce before running the permutation. + n, rate int + + // dsbyte contains the "domain separation" bits and the first bit of + // the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the + // SHA-3 and SHAKE functions by appending bitstrings to the message. + // Using a little-endian bit-ordering convention, these are "01" for SHA-3 + // and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the + // padding rule from section 5.1 is applied to pad the message to a multiple + // of the rate, which involves adding a "1" bit, zero or more "0" bits, and + // a final "1" bit. We merge the first "1" bit from the padding into dsbyte, + // giving 00000110b (0x06) and 00011111b (0x1f). + // [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf + // "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and + // Extendable-Output Functions (May 2014)" + dsbyte byte + + outputLen int // the default output size in bytes + state spongeDirection // whether the sponge is absorbing or squeezing +} + +// BlockSize returns the rate of sponge underlying this hash function. +func (d *state) BlockSize() int { return d.rate } + +// Size returns the output size of the hash function in bytes. +func (d *state) Size() int { return d.outputLen } + +// Reset clears the internal state by zeroing the sponge state and +// the buffer indexes, and setting Sponge.state to absorbing. +func (d *state) Reset() { + // Zero the permutation's state. + for i := range d.a { + d.a[i] = 0 + } + d.state = spongeAbsorbing + d.n = 0 +} + +func (d *state) clone() *state { + ret := *d + return &ret +} + +// permute applies the KeccakF-1600 permutation. +func (d *state) permute() { + var a *[25]uint64 + if cpu.IsBigEndian { + a = new([25]uint64) + for i := range a { + a[i] = binary.LittleEndian.Uint64(d.a[i*8:]) + } + } else { + a = (*[25]uint64)(unsafe.Pointer(&d.a)) + } + + keccakF1600(a) + d.n = 0 + + if cpu.IsBigEndian { + for i := range a { + binary.LittleEndian.PutUint64(d.a[i*8:], a[i]) + } + } +} + +// pads appends the domain separation bits in dsbyte, applies +// the multi-bitrate 10..1 padding rule, and permutes the state. +func (d *state) padAndPermute() { + // Pad with this instance's domain-separator bits. We know that there's + // at least one byte of space in the sponge because, if it were full, + // permute would have been called to empty it. dsbyte also contains the + // first one bit for the padding. See the comment in the state struct. + d.a[d.n] ^= d.dsbyte + // This adds the final one bit for the padding. Because of the way that + // bits are numbered from the LSB upwards, the final bit is the MSB of + // the last byte. + d.a[d.rate-1] ^= 0x80 + // Apply the permutation + d.permute() + d.state = spongeSqueezing +} + +// Write absorbs more data into the hash's state. It panics if any +// output has already been read. +func (d *state) Write(p []byte) (n int, err error) { + if d.state != spongeAbsorbing { + panic("sha3: Write after Read") + } + + n = len(p) + + for len(p) > 0 { + x := subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p) + d.n += x + p = p[x:] + + // If the sponge is full, apply the permutation. + if d.n == d.rate { + d.permute() + } + } + + return +} + +// Read squeezes an arbitrary number of bytes from the sponge. +func (d *state) Read(out []byte) (n int, err error) { + // If we're still absorbing, pad and apply the permutation. + if d.state == spongeAbsorbing { + d.padAndPermute() + } + + n = len(out) + + // Now, do the squeezing. + for len(out) > 0 { + // Apply the permutation if we've squeezed the sponge dry. + if d.n == d.rate { + d.permute() + } + + x := copy(out, d.a[d.n:d.rate]) + d.n += x + out = out[x:] + } + + return +} + +// Sum applies padding to the hash state and then squeezes out the desired +// number of output bytes. It panics if any output has already been read. +func (d *state) Sum(in []byte) []byte { + if d.state != spongeAbsorbing { + panic("sha3: Sum after Read") + } + + // Make a copy of the original hash so that caller can keep writing + // and summing. + dup := d.clone() + hash := make([]byte, dup.outputLen, 64) // explicit cap to allow stack allocation + dup.Read(hash) + return append(in, hash...) +} + +const ( + magicKeccak = "sha\x0b" + // magic || rate || main state || n || sponge direction + marshaledSize = len(magicKeccak) + 1 + 200 + 1 + 1 +) + +func (d *state) MarshalBinary() ([]byte, error) { + return d.AppendBinary(make([]byte, 0, marshaledSize)) +} + +func (d *state) AppendBinary(b []byte) ([]byte, error) { + switch d.dsbyte { + case dsbyteKeccak: + b = append(b, magicKeccak...) + default: + panic("unknown dsbyte") + } + // rate is at most 168, and n is at most rate. + b = append(b, byte(d.rate)) + b = append(b, d.a[:]...) + b = append(b, byte(d.n), byte(d.state)) + return b, nil +} + +func (d *state) UnmarshalBinary(b []byte) error { + if len(b) != marshaledSize { + return errors.New("sha3: invalid hash state") + } + + magic := string(b[:len(magicKeccak)]) + b = b[len(magicKeccak):] + switch { + case magic == magicKeccak && d.dsbyte == dsbyteKeccak: + default: + return errors.New("sha3: invalid hash state identifier") + } + + rate := int(b[0]) + b = b[1:] + if rate != d.rate { + return errors.New("sha3: invalid hash state function") + } + + copy(d.a[:], b) + b = b[len(d.a):] + + n, state := int(b[0]), spongeDirection(b[1]) + if n > d.rate { + return errors.New("sha3: invalid hash state") + } + d.n = n + if state != spongeAbsorbing && state != spongeSqueezing { + return errors.New("sha3: invalid hash state") + } + d.state = state + + return nil +} diff --git a/cli/vendor/golang.org/x/crypto/sha3/legacy_keccakf.go b/cli/vendor/golang.org/x/crypto/sha3/legacy_keccakf.go new file mode 100644 index 0000000..101588c --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/sha3/legacy_keccakf.go @@ -0,0 +1,416 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// This implementation is only used for NewLegacyKeccak256 and +// NewLegacyKeccak512, which are not implemented by crypto/sha3. +// All other functions in this package are wrappers around crypto/sha3. + +import "math/bits" + +// rc stores the round constants for use in the ι step. +var rc = [24]uint64{ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +} + +// keccakF1600 applies the Keccak permutation to a 1600b-wide +// state represented as a slice of 25 uint64s. +func keccakF1600(a *[25]uint64) { + // Implementation translated from Keccak-inplace.c + // in the keccak reference code. + var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 + + for i := 0; i < 24; i += 4 { + // Combines the 5 steps in each round into 2 steps. + // Unrolls 4 rounds per loop and spreads some steps across rounds. + + // Round 1 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[6] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[12] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[18] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[24] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i] + a[6] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[16] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[22] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[3] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[10] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[1] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[7] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[19] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[20] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[11] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[23] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[4] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[5] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[2] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[8] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[14] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[15] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + // Round 2 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[16] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[7] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[23] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[14] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1] + a[16] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[11] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[2] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[18] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[20] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[6] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[22] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[4] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[15] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[1] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[8] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[24] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[10] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[12] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[3] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[19] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[5] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + // Round 3 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[11] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[22] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[8] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[19] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2] + a[11] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[1] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[12] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[23] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[15] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[16] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[2] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[24] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[5] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[6] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[3] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[14] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[20] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[7] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[18] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[4] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[10] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + // Round 4 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[1] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[2] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[3] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[4] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3] + a[1] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[6] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[7] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[8] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[5] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[11] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[12] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[14] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[10] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[16] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[18] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[19] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[15] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[22] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[23] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[24] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[20] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + } +} diff --git a/cli/vendor/golang.org/x/crypto/sha3/shake.go b/cli/vendor/golang.org/x/crypto/sha3/shake.go new file mode 100644 index 0000000..6f3f70c --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/sha3/shake.go @@ -0,0 +1,119 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +import ( + "crypto/sha3" + "hash" + "io" +) + +// ShakeHash defines the interface to hash functions that support +// arbitrary-length output. When used as a plain [hash.Hash], it +// produces minimum-length outputs that provide full-strength generic +// security. +type ShakeHash interface { + hash.Hash + + // Read reads more output from the hash; reading affects the hash's + // state. (ShakeHash.Read is thus very different from Hash.Sum.) + // It never returns an error, but subsequent calls to Write or Sum + // will panic. + io.Reader + + // Clone returns a copy of the ShakeHash in its current state. + Clone() ShakeHash +} + +// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash. +// Its generic security strength is 128 bits against all attacks if at +// least 32 bytes of its output are used. +func NewShake128() ShakeHash { + return &shakeWrapper{sha3.NewSHAKE128(), 32, false, sha3.NewSHAKE128} +} + +// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash. +// Its generic security strength is 256 bits against all attacks if +// at least 64 bytes of its output are used. +func NewShake256() ShakeHash { + return &shakeWrapper{sha3.NewSHAKE256(), 64, false, sha3.NewSHAKE256} +} + +// NewCShake128 creates a new instance of cSHAKE128 variable-output-length ShakeHash, +// a customizable variant of SHAKE128. +// N is used to define functions based on cSHAKE, it can be empty when plain cSHAKE is +// desired. S is a customization byte string used for domain separation - two cSHAKE +// computations on same input with different S yield unrelated outputs. +// When N and S are both empty, this is equivalent to NewShake128. +func NewCShake128(N, S []byte) ShakeHash { + return &shakeWrapper{sha3.NewCSHAKE128(N, S), 32, false, func() *sha3.SHAKE { + return sha3.NewCSHAKE128(N, S) + }} +} + +// NewCShake256 creates a new instance of cSHAKE256 variable-output-length ShakeHash, +// a customizable variant of SHAKE256. +// N is used to define functions based on cSHAKE, it can be empty when plain cSHAKE is +// desired. S is a customization byte string used for domain separation - two cSHAKE +// computations on same input with different S yield unrelated outputs. +// When N and S are both empty, this is equivalent to NewShake256. +func NewCShake256(N, S []byte) ShakeHash { + return &shakeWrapper{sha3.NewCSHAKE256(N, S), 64, false, func() *sha3.SHAKE { + return sha3.NewCSHAKE256(N, S) + }} +} + +// ShakeSum128 writes an arbitrary-length digest of data into hash. +func ShakeSum128(hash, data []byte) { + h := NewShake128() + h.Write(data) + h.Read(hash) +} + +// ShakeSum256 writes an arbitrary-length digest of data into hash. +func ShakeSum256(hash, data []byte) { + h := NewShake256() + h.Write(data) + h.Read(hash) +} + +// shakeWrapper adds the Size, Sum, and Clone methods to a sha3.SHAKE +// to implement the ShakeHash interface. +type shakeWrapper struct { + *sha3.SHAKE + outputLen int + squeezing bool + newSHAKE func() *sha3.SHAKE +} + +func (w *shakeWrapper) Read(p []byte) (n int, err error) { + w.squeezing = true + return w.SHAKE.Read(p) +} + +func (w *shakeWrapper) Clone() ShakeHash { + s := w.newSHAKE() + b, err := w.MarshalBinary() + if err != nil { + panic(err) // unreachable + } + if err := s.UnmarshalBinary(b); err != nil { + panic(err) // unreachable + } + return &shakeWrapper{s, w.outputLen, w.squeezing, w.newSHAKE} +} + +func (w *shakeWrapper) Size() int { return w.outputLen } + +func (w *shakeWrapper) Sum(b []byte) []byte { + if w.squeezing { + panic("sha3: Sum after Read") + } + out := make([]byte, w.outputLen) + // Clone the state so that we don't affect future Write calls. + s := w.Clone() + s.Read(out) + return append(b, out...) +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/agent/client.go b/cli/vendor/golang.org/x/crypto/ssh/agent/client.go new file mode 100644 index 0000000..b357e18 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/agent/client.go @@ -0,0 +1,856 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package agent implements the ssh-agent protocol, and provides both +// a client and a server. The client can talk to a standard ssh-agent +// that uses UNIX sockets, and one could implement an alternative +// ssh-agent process using the sample server. +// +// References: +// +// [PROTOCOL.agent]: https://tools.ietf.org/html/draft-miller-ssh-agent-00 +package agent + +import ( + "bytes" + "crypto/dsa" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + "sync" + + "golang.org/x/crypto/ssh" +) + +// SignatureFlags represent additional flags that can be passed to the signature +// requests an defined in [PROTOCOL.agent] section 4.5.1. +type SignatureFlags uint32 + +// SignatureFlag values as defined in [PROTOCOL.agent] section 5.3. +const ( + SignatureFlagReserved SignatureFlags = 1 << iota + SignatureFlagRsaSha256 + SignatureFlagRsaSha512 +) + +// Agent represents the capabilities of an ssh-agent. +type Agent interface { + // List returns the identities known to the agent. + List() ([]*Key, error) + + // Sign has the agent sign the data using a protocol 2 key as defined + // in [PROTOCOL.agent] section 2.6.2. + Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) + + // Add adds a private key to the agent. + Add(key AddedKey) error + + // Remove removes all identities with the given public key. + Remove(key ssh.PublicKey) error + + // RemoveAll removes all identities. + RemoveAll() error + + // Lock locks the agent. Sign and Remove will fail, and List will empty an empty list. + Lock(passphrase []byte) error + + // Unlock undoes the effect of Lock + Unlock(passphrase []byte) error + + // Signers returns signers for all the known keys. + Signers() ([]ssh.Signer, error) +} + +type ExtendedAgent interface { + Agent + + // SignWithFlags signs like Sign, but allows for additional flags to be sent/received + SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error) + + // Extension processes a custom extension request. Standard-compliant agents are not + // required to support any extensions, but this method allows agents to implement + // vendor-specific methods or add experimental features. See [PROTOCOL.agent] section 4.7. + // If agent extensions are unsupported entirely this method MUST return an + // ErrExtensionUnsupported error. Similarly, if just the specific extensionType in + // the request is unsupported by the agent then ErrExtensionUnsupported MUST be + // returned. + // + // In the case of success, since [PROTOCOL.agent] section 4.7 specifies that the contents + // of the response are unspecified (including the type of the message), the complete + // response will be returned as a []byte slice, including the "type" byte of the message. + Extension(extensionType string, contents []byte) ([]byte, error) +} + +// ConstraintExtension describes an optional constraint defined by users. +type ConstraintExtension struct { + // ExtensionName consist of a UTF-8 string suffixed by the + // implementation domain following the naming scheme defined + // in Section 4.2 of RFC 4251, e.g. "foo@example.com". + ExtensionName string + // ExtensionDetails contains the actual content of the extended + // constraint. + ExtensionDetails []byte +} + +// AddedKey describes an SSH key to be added to an Agent. +type AddedKey struct { + // PrivateKey must be a *rsa.PrivateKey, *dsa.PrivateKey, + // ed25519.PrivateKey or *ecdsa.PrivateKey, which will be inserted into the + // agent. + PrivateKey interface{} + // Certificate, if not nil, is communicated to the agent and will be + // stored with the key. + Certificate *ssh.Certificate + // Comment is an optional, free-form string. + Comment string + // LifetimeSecs, if not zero, is the number of seconds that the + // agent will store the key for. + LifetimeSecs uint32 + // ConfirmBeforeUse, if true, requests that the agent confirm with the + // user before each use of this key. + ConfirmBeforeUse bool + // ConstraintExtensions are the experimental or private-use constraints + // defined by users. + ConstraintExtensions []ConstraintExtension +} + +// See [PROTOCOL.agent], section 3. +const ( + agentRequestV1Identities = 1 + agentRemoveAllV1Identities = 9 + + // 3.2 Requests from client to agent for protocol 2 key operations + agentAddIdentity = 17 + agentRemoveIdentity = 18 + agentRemoveAllIdentities = 19 + agentAddIDConstrained = 25 + + // 3.3 Key-type independent requests from client to agent + agentAddSmartcardKey = 20 + agentRemoveSmartcardKey = 21 + agentLock = 22 + agentUnlock = 23 + agentAddSmartcardKeyConstrained = 26 + + // 3.7 Key constraint identifiers + agentConstrainLifetime = 1 + agentConstrainConfirm = 2 + // Constraint extension identifier up to version 2 of the protocol. A + // backward incompatible change will be required if we want to add support + // for SSH_AGENT_CONSTRAIN_MAXSIGN which uses the same ID. + agentConstrainExtensionV00 = 3 + // Constraint extension identifier in version 3 and later of the protocol. + agentConstrainExtension = 255 +) + +// maxAgentResponseBytes is the maximum agent reply size that is accepted. This +// is a sanity check, not a limit in the spec. +const maxAgentResponseBytes = 16 << 20 + +// Agent messages: +// These structures mirror the wire format of the corresponding ssh agent +// messages found in [PROTOCOL.agent]. + +// 3.4 Generic replies from agent to client +const agentFailure = 5 + +type failureAgentMsg struct{} + +const agentSuccess = 6 + +type successAgentMsg struct{} + +// See [PROTOCOL.agent], section 2.5.2. +const agentRequestIdentities = 11 + +type requestIdentitiesAgentMsg struct{} + +// See [PROTOCOL.agent], section 2.5.2. +const agentIdentitiesAnswer = 12 + +type identitiesAnswerAgentMsg struct { + NumKeys uint32 `sshtype:"12"` + Keys []byte `ssh:"rest"` +} + +// See [PROTOCOL.agent], section 2.6.2. +const agentSignRequest = 13 + +type signRequestAgentMsg struct { + KeyBlob []byte `sshtype:"13"` + Data []byte + Flags uint32 +} + +// See [PROTOCOL.agent], section 2.6.2. + +// 3.6 Replies from agent to client for protocol 2 key operations +const agentSignResponse = 14 + +type signResponseAgentMsg struct { + SigBlob []byte `sshtype:"14"` +} + +type publicKey struct { + Format string + Rest []byte `ssh:"rest"` +} + +// 3.7 Key constraint identifiers +type constrainLifetimeAgentMsg struct { + LifetimeSecs uint32 `sshtype:"1"` +} + +type constrainExtensionAgentMsg struct { + ExtensionName string `sshtype:"255|3"` + ExtensionDetails []byte + + // Rest is a field used for parsing, not part of message + Rest []byte `ssh:"rest"` +} + +// See [PROTOCOL.agent], section 4.7 +const agentExtension = 27 +const agentExtensionFailure = 28 + +// ErrExtensionUnsupported indicates that an extension defined in +// [PROTOCOL.agent] section 4.7 is unsupported by the agent. Specifically this +// error indicates that the agent returned a standard SSH_AGENT_FAILURE message +// as the result of a SSH_AGENTC_EXTENSION request. Note that the protocol +// specification (and therefore this error) does not distinguish between a +// specific extension being unsupported and extensions being unsupported entirely. +var ErrExtensionUnsupported = errors.New("agent: extension unsupported") + +type extensionAgentMsg struct { + ExtensionType string `sshtype:"27"` + // NOTE: this matches OpenSSH's PROTOCOL.agent, not the IETF draft [PROTOCOL.agent], + // so that it matches what OpenSSH actually implements in the wild. + Contents []byte `ssh:"rest"` +} + +// Key represents a protocol 2 public key as defined in +// [PROTOCOL.agent], section 2.5.2. +type Key struct { + Format string + Blob []byte + Comment string +} + +func clientErr(err error) error { + return fmt.Errorf("agent: client error: %v", err) +} + +// String returns the storage form of an agent key with the format, base64 +// encoded serialized key, and the comment if it is not empty. +func (k *Key) String() string { + s := string(k.Format) + " " + base64.StdEncoding.EncodeToString(k.Blob) + + if k.Comment != "" { + s += " " + k.Comment + } + + return s +} + +// Type returns the public key type. +func (k *Key) Type() string { + return k.Format +} + +// Marshal returns key blob to satisfy the ssh.PublicKey interface. +func (k *Key) Marshal() []byte { + return k.Blob +} + +// Verify satisfies the ssh.PublicKey interface. +func (k *Key) Verify(data []byte, sig *ssh.Signature) error { + pubKey, err := ssh.ParsePublicKey(k.Blob) + if err != nil { + return fmt.Errorf("agent: bad public key: %v", err) + } + return pubKey.Verify(data, sig) +} + +type wireKey struct { + Format string + Rest []byte `ssh:"rest"` +} + +func parseKey(in []byte) (out *Key, rest []byte, err error) { + var record struct { + Blob []byte + Comment string + Rest []byte `ssh:"rest"` + } + + if err := ssh.Unmarshal(in, &record); err != nil { + return nil, nil, err + } + + var wk wireKey + if err := ssh.Unmarshal(record.Blob, &wk); err != nil { + return nil, nil, err + } + + return &Key{ + Format: wk.Format, + Blob: record.Blob, + Comment: record.Comment, + }, record.Rest, nil +} + +// client is a client for an ssh-agent process. +type client struct { + // conn is typically a *net.UnixConn + conn io.ReadWriter + // mu is used to prevent concurrent access to the agent + mu sync.Mutex +} + +// NewClient returns an Agent that talks to an ssh-agent process over +// the given connection. +func NewClient(rw io.ReadWriter) ExtendedAgent { + return &client{conn: rw} +} + +// call sends an RPC to the agent. On success, the reply is +// unmarshaled into reply and replyType is set to the first byte of +// the reply, which contains the type of the message. +func (c *client) call(req []byte) (reply interface{}, err error) { + buf, err := c.callRaw(req) + if err != nil { + return nil, err + } + reply, err = unmarshal(buf) + if err != nil { + return nil, clientErr(err) + } + return reply, nil +} + +// callRaw sends an RPC to the agent. On success, the raw +// bytes of the response are returned; no unmarshalling is +// performed on the response. +func (c *client) callRaw(req []byte) (reply []byte, err error) { + c.mu.Lock() + defer c.mu.Unlock() + + msg := make([]byte, 4+len(req)) + binary.BigEndian.PutUint32(msg, uint32(len(req))) + copy(msg[4:], req) + if _, err = c.conn.Write(msg); err != nil { + return nil, clientErr(err) + } + + var respSizeBuf [4]byte + if _, err = io.ReadFull(c.conn, respSizeBuf[:]); err != nil { + return nil, clientErr(err) + } + respSize := binary.BigEndian.Uint32(respSizeBuf[:]) + if respSize > maxAgentResponseBytes { + return nil, clientErr(errors.New("response too large")) + } + + buf := make([]byte, respSize) + if _, err = io.ReadFull(c.conn, buf); err != nil { + return nil, clientErr(err) + } + return buf, nil +} + +func (c *client) simpleCall(req []byte) error { + resp, err := c.call(req) + if err != nil { + return err + } + if _, ok := resp.(*successAgentMsg); ok { + return nil + } + return errors.New("agent: failure") +} + +func (c *client) RemoveAll() error { + return c.simpleCall([]byte{agentRemoveAllIdentities}) +} + +func (c *client) Remove(key ssh.PublicKey) error { + req := ssh.Marshal(&agentRemoveIdentityMsg{ + KeyBlob: key.Marshal(), + }) + return c.simpleCall(req) +} + +func (c *client) Lock(passphrase []byte) error { + req := ssh.Marshal(&agentLockMsg{ + Passphrase: passphrase, + }) + return c.simpleCall(req) +} + +func (c *client) Unlock(passphrase []byte) error { + req := ssh.Marshal(&agentUnlockMsg{ + Passphrase: passphrase, + }) + return c.simpleCall(req) +} + +// List returns the identities known to the agent. +func (c *client) List() ([]*Key, error) { + // see [PROTOCOL.agent] section 2.5.2. + req := []byte{agentRequestIdentities} + + msg, err := c.call(req) + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *identitiesAnswerAgentMsg: + if msg.NumKeys > maxAgentResponseBytes/8 { + return nil, errors.New("agent: too many keys in agent reply") + } + keys := make([]*Key, msg.NumKeys) + data := msg.Keys + for i := uint32(0); i < msg.NumKeys; i++ { + var key *Key + var err error + if key, data, err = parseKey(data); err != nil { + return nil, err + } + keys[i] = key + } + return keys, nil + case *failureAgentMsg: + return nil, errors.New("agent: failed to list keys") + default: + return nil, fmt.Errorf("agent: failed to list keys, unexpected message type %T", msg) + } +} + +// Sign has the agent sign the data using a protocol 2 key as defined +// in [PROTOCOL.agent] section 2.6.2. +func (c *client) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { + return c.SignWithFlags(key, data, 0) +} + +func (c *client) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error) { + req := ssh.Marshal(signRequestAgentMsg{ + KeyBlob: key.Marshal(), + Data: data, + Flags: uint32(flags), + }) + + msg, err := c.call(req) + if err != nil { + return nil, err + } + + switch msg := msg.(type) { + case *signResponseAgentMsg: + var sig ssh.Signature + if err := ssh.Unmarshal(msg.SigBlob, &sig); err != nil { + return nil, err + } + + return &sig, nil + case *failureAgentMsg: + return nil, errors.New("agent: failed to sign challenge") + default: + return nil, fmt.Errorf("agent: failed to sign challenge, unexpected message type %T", msg) + } +} + +// unmarshal parses an agent message in packet, returning the parsed +// form and the message type of packet. +func unmarshal(packet []byte) (interface{}, error) { + if len(packet) < 1 { + return nil, errors.New("agent: empty packet") + } + var msg interface{} + switch packet[0] { + case agentFailure: + return new(failureAgentMsg), nil + case agentSuccess: + return new(successAgentMsg), nil + case agentIdentitiesAnswer: + msg = new(identitiesAnswerAgentMsg) + case agentSignResponse: + msg = new(signResponseAgentMsg) + case agentV1IdentitiesAnswer: + msg = new(agentV1IdentityMsg) + default: + return nil, fmt.Errorf("agent: unknown type tag %d", packet[0]) + } + if err := ssh.Unmarshal(packet, msg); err != nil { + return nil, err + } + return msg, nil +} + +type rsaKeyMsg struct { + Type string `sshtype:"17|25"` + N *big.Int + E *big.Int + D *big.Int + Iqmp *big.Int // IQMP = Inverse Q Mod P + P *big.Int + Q *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type dsaKeyMsg struct { + Type string `sshtype:"17|25"` + P *big.Int + Q *big.Int + G *big.Int + Y *big.Int + X *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type ecdsaKeyMsg struct { + Type string `sshtype:"17|25"` + Curve string + KeyBytes []byte + D *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type ed25519KeyMsg struct { + Type string `sshtype:"17|25"` + Pub []byte + Priv []byte + Comments string + Constraints []byte `ssh:"rest"` +} + +// Insert adds a private key to the agent. +func (c *client) insertKey(s interface{}, comment string, constraints []byte) error { + var req []byte + switch k := s.(type) { + case *rsa.PrivateKey: + if len(k.Primes) != 2 { + return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) + } + k.Precompute() + req = ssh.Marshal(rsaKeyMsg{ + Type: ssh.KeyAlgoRSA, + N: k.N, + E: big.NewInt(int64(k.E)), + D: k.D, + Iqmp: k.Precomputed.Qinv, + P: k.Primes[0], + Q: k.Primes[1], + Comments: comment, + Constraints: constraints, + }) + case *dsa.PrivateKey: + req = ssh.Marshal(dsaKeyMsg{ + Type: ssh.InsecureKeyAlgoDSA, + P: k.P, + Q: k.Q, + G: k.G, + Y: k.Y, + X: k.X, + Comments: comment, + Constraints: constraints, + }) + case *ecdsa.PrivateKey: + nistID := fmt.Sprintf("nistp%d", k.Params().BitSize) + req = ssh.Marshal(ecdsaKeyMsg{ + Type: "ecdsa-sha2-" + nistID, + Curve: nistID, + KeyBytes: elliptic.Marshal(k.Curve, k.X, k.Y), + D: k.D, + Comments: comment, + Constraints: constraints, + }) + case ed25519.PrivateKey: + req = ssh.Marshal(ed25519KeyMsg{ + Type: ssh.KeyAlgoED25519, + Pub: []byte(k)[32:], + Priv: []byte(k), + Comments: comment, + Constraints: constraints, + }) + // This function originally supported only *ed25519.PrivateKey, however the + // general idiom is to pass ed25519.PrivateKey by value, not by pointer. + // We still support the pointer variant for backwards compatibility. + case *ed25519.PrivateKey: + req = ssh.Marshal(ed25519KeyMsg{ + Type: ssh.KeyAlgoED25519, + Pub: []byte(*k)[32:], + Priv: []byte(*k), + Comments: comment, + Constraints: constraints, + }) + default: + return fmt.Errorf("agent: unsupported key type %T", s) + } + + // if constraints are present then the message type needs to be changed. + if len(constraints) != 0 { + req[0] = agentAddIDConstrained + } + + resp, err := c.call(req) + if err != nil { + return err + } + if _, ok := resp.(*successAgentMsg); ok { + return nil + } + return errors.New("agent: failure") +} + +type rsaCertMsg struct { + Type string `sshtype:"17|25"` + CertBytes []byte + D *big.Int + Iqmp *big.Int // IQMP = Inverse Q Mod P + P *big.Int + Q *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type dsaCertMsg struct { + Type string `sshtype:"17|25"` + CertBytes []byte + X *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type ecdsaCertMsg struct { + Type string `sshtype:"17|25"` + CertBytes []byte + D *big.Int + Comments string + Constraints []byte `ssh:"rest"` +} + +type ed25519CertMsg struct { + Type string `sshtype:"17|25"` + CertBytes []byte + Pub []byte + Priv []byte + Comments string + Constraints []byte `ssh:"rest"` +} + +// Add adds a private key to the agent. If a certificate is given, +// that certificate is added instead as public key. +func (c *client) Add(key AddedKey) error { + var constraints []byte + + if secs := key.LifetimeSecs; secs != 0 { + constraints = append(constraints, ssh.Marshal(constrainLifetimeAgentMsg{secs})...) + } + + if key.ConfirmBeforeUse { + constraints = append(constraints, agentConstrainConfirm) + } + + cert := key.Certificate + if cert == nil { + return c.insertKey(key.PrivateKey, key.Comment, constraints) + } + return c.insertCert(key.PrivateKey, cert, key.Comment, constraints) +} + +func (c *client) insertCert(s interface{}, cert *ssh.Certificate, comment string, constraints []byte) error { + var req []byte + switch k := s.(type) { + case *rsa.PrivateKey: + if len(k.Primes) != 2 { + return fmt.Errorf("agent: unsupported RSA key with %d primes", len(k.Primes)) + } + k.Precompute() + req = ssh.Marshal(rsaCertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + D: k.D, + Iqmp: k.Precomputed.Qinv, + P: k.Primes[0], + Q: k.Primes[1], + Comments: comment, + Constraints: constraints, + }) + case *dsa.PrivateKey: + req = ssh.Marshal(dsaCertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + X: k.X, + Comments: comment, + Constraints: constraints, + }) + case *ecdsa.PrivateKey: + req = ssh.Marshal(ecdsaCertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + D: k.D, + Comments: comment, + Constraints: constraints, + }) + case ed25519.PrivateKey: + req = ssh.Marshal(ed25519CertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + Pub: []byte(k)[32:], + Priv: []byte(k), + Comments: comment, + Constraints: constraints, + }) + // This function originally supported only *ed25519.PrivateKey, however the + // general idiom is to pass ed25519.PrivateKey by value, not by pointer. + // We still support the pointer variant for backwards compatibility. + case *ed25519.PrivateKey: + req = ssh.Marshal(ed25519CertMsg{ + Type: cert.Type(), + CertBytes: cert.Marshal(), + Pub: []byte(*k)[32:], + Priv: []byte(*k), + Comments: comment, + Constraints: constraints, + }) + default: + return fmt.Errorf("agent: unsupported key type %T", s) + } + + // if constraints are present then the message type needs to be changed. + if len(constraints) != 0 { + req[0] = agentAddIDConstrained + } + + signer, err := ssh.NewSignerFromKey(s) + if err != nil { + return err + } + if !bytes.Equal(cert.Key.Marshal(), signer.PublicKey().Marshal()) { + return errors.New("agent: signer and cert have different public key") + } + + resp, err := c.call(req) + if err != nil { + return err + } + if _, ok := resp.(*successAgentMsg); ok { + return nil + } + return errors.New("agent: failure") +} + +// Signers provides a callback for client authentication. +func (c *client) Signers() ([]ssh.Signer, error) { + keys, err := c.List() + if err != nil { + return nil, err + } + + var result []ssh.Signer + for _, k := range keys { + result = append(result, &agentKeyringSigner{c, k}) + } + return result, nil +} + +type agentKeyringSigner struct { + agent *client + pub ssh.PublicKey +} + +func (s *agentKeyringSigner) PublicKey() ssh.PublicKey { + return s.pub +} + +func (s *agentKeyringSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { + // The agent has its own entropy source, so the rand argument is ignored. + return s.agent.Sign(s.pub, data) +} + +func (s *agentKeyringSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*ssh.Signature, error) { + if algorithm == "" || algorithm == underlyingAlgo(s.pub.Type()) { + return s.Sign(rand, data) + } + + var flags SignatureFlags + switch algorithm { + case ssh.KeyAlgoRSASHA256: + flags = SignatureFlagRsaSha256 + case ssh.KeyAlgoRSASHA512: + flags = SignatureFlagRsaSha512 + default: + return nil, fmt.Errorf("agent: unsupported algorithm %q", algorithm) + } + + return s.agent.SignWithFlags(s.pub, data, flags) +} + +var _ ssh.AlgorithmSigner = &agentKeyringSigner{} + +// certKeyAlgoNames is a mapping from known certificate algorithm names to the +// corresponding public key signature algorithm. +// +// This map must be kept in sync with the one in certs.go. +var certKeyAlgoNames = map[string]string{ + ssh.CertAlgoRSAv01: ssh.KeyAlgoRSA, + ssh.CertAlgoRSASHA256v01: ssh.KeyAlgoRSASHA256, + ssh.CertAlgoRSASHA512v01: ssh.KeyAlgoRSASHA512, + ssh.InsecureCertAlgoDSAv01: ssh.InsecureKeyAlgoDSA, + ssh.CertAlgoECDSA256v01: ssh.KeyAlgoECDSA256, + ssh.CertAlgoECDSA384v01: ssh.KeyAlgoECDSA384, + ssh.CertAlgoECDSA521v01: ssh.KeyAlgoECDSA521, + ssh.CertAlgoSKECDSA256v01: ssh.KeyAlgoSKECDSA256, + ssh.CertAlgoED25519v01: ssh.KeyAlgoED25519, + ssh.CertAlgoSKED25519v01: ssh.KeyAlgoSKED25519, +} + +// underlyingAlgo returns the signature algorithm associated with algo (which is +// an advertised or negotiated public key or host key algorithm). These are +// usually the same, except for certificate algorithms. +func underlyingAlgo(algo string) string { + if a, ok := certKeyAlgoNames[algo]; ok { + return a + } + return algo +} + +// Calls an extension method. It is up to the agent implementation as to whether or not +// any particular extension is supported and may always return an error. Because the +// type of the response is up to the implementation, this returns the bytes of the +// response and does not attempt any type of unmarshalling. +func (c *client) Extension(extensionType string, contents []byte) ([]byte, error) { + req := ssh.Marshal(extensionAgentMsg{ + ExtensionType: extensionType, + Contents: contents, + }) + buf, err := c.callRaw(req) + if err != nil { + return nil, err + } + if len(buf) == 0 { + return nil, errors.New("agent: failure; empty response") + } + // [PROTOCOL.agent] section 4.7 indicates that an SSH_AGENT_FAILURE message + // represents an agent that does not support the extension + if buf[0] == agentFailure { + return nil, ErrExtensionUnsupported + } + if buf[0] == agentExtensionFailure { + return nil, errors.New("agent: generic extension failure") + } + + return buf, nil +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/agent/forward.go b/cli/vendor/golang.org/x/crypto/ssh/agent/forward.go new file mode 100644 index 0000000..fd24ba9 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/agent/forward.go @@ -0,0 +1,103 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "errors" + "io" + "net" + "sync" + + "golang.org/x/crypto/ssh" +) + +// RequestAgentForwarding sets up agent forwarding for the session. +// ForwardToAgent or ForwardToRemote should be called to route +// the authentication requests. +func RequestAgentForwarding(session *ssh.Session) error { + ok, err := session.SendRequest("auth-agent-req@openssh.com", true, nil) + if err != nil { + return err + } + if !ok { + return errors.New("forwarding request denied") + } + return nil +} + +// ForwardToAgent routes authentication requests to the given keyring. +func ForwardToAgent(client *ssh.Client, keyring Agent) error { + channels := client.HandleChannelOpen(channelType) + if channels == nil { + return errors.New("agent: already have handler for " + channelType) + } + + go func() { + for ch := range channels { + channel, reqs, err := ch.Accept() + if err != nil { + continue + } + go ssh.DiscardRequests(reqs) + go func() { + ServeAgent(keyring, channel) + channel.Close() + }() + } + }() + return nil +} + +const channelType = "auth-agent@openssh.com" + +// ForwardToRemote routes authentication requests to the ssh-agent +// process serving on the given unix socket. +func ForwardToRemote(client *ssh.Client, addr string) error { + channels := client.HandleChannelOpen(channelType) + if channels == nil { + return errors.New("agent: already have handler for " + channelType) + } + conn, err := net.Dial("unix", addr) + if err != nil { + return err + } + conn.Close() + + go func() { + for ch := range channels { + channel, reqs, err := ch.Accept() + if err != nil { + continue + } + go ssh.DiscardRequests(reqs) + go forwardUnixSocket(channel, addr) + } + }() + return nil +} + +func forwardUnixSocket(channel ssh.Channel, addr string) { + conn, err := net.Dial("unix", addr) + if err != nil { + return + } + + var wg sync.WaitGroup + wg.Add(2) + go func() { + io.Copy(conn, channel) + conn.(*net.UnixConn).CloseWrite() + wg.Done() + }() + go func() { + io.Copy(channel, conn) + channel.CloseWrite() + wg.Done() + }() + + wg.Wait() + conn.Close() + channel.Close() +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/agent/keyring.go b/cli/vendor/golang.org/x/crypto/ssh/agent/keyring.go new file mode 100644 index 0000000..d129875 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/agent/keyring.go @@ -0,0 +1,250 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "bytes" + "crypto/rand" + "crypto/subtle" + "errors" + "fmt" + "sync" + "time" + + "golang.org/x/crypto/ssh" +) + +type privKey struct { + signer ssh.Signer + comment string + expire *time.Time +} + +type keyring struct { + mu sync.Mutex + keys []privKey + + locked bool + passphrase []byte +} + +var errLocked = errors.New("agent: locked") + +// NewKeyring returns an Agent that holds keys in memory. It is safe +// for concurrent use by multiple goroutines. +func NewKeyring() Agent { + return &keyring{} +} + +// RemoveAll removes all identities. +func (r *keyring) RemoveAll() error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + + r.keys = nil + return nil +} + +// removeLocked does the actual key removal. The caller must already be holding the +// keyring mutex. +func (r *keyring) removeLocked(want []byte) error { + found := false + for i := 0; i < len(r.keys); { + if bytes.Equal(r.keys[i].signer.PublicKey().Marshal(), want) { + found = true + r.keys[i] = r.keys[len(r.keys)-1] + r.keys = r.keys[:len(r.keys)-1] + continue + } else { + i++ + } + } + + if !found { + return errors.New("agent: key not found") + } + return nil +} + +// Remove removes all identities with the given public key. +func (r *keyring) Remove(key ssh.PublicKey) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + + return r.removeLocked(key.Marshal()) +} + +// Lock locks the agent. Sign and Remove will fail, and List will return an empty list. +func (r *keyring) Lock(passphrase []byte) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + + r.locked = true + r.passphrase = passphrase + return nil +} + +// Unlock undoes the effect of Lock +func (r *keyring) Unlock(passphrase []byte) error { + r.mu.Lock() + defer r.mu.Unlock() + if !r.locked { + return errors.New("agent: not locked") + } + if 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) { + return fmt.Errorf("agent: incorrect passphrase") + } + + r.locked = false + r.passphrase = nil + return nil +} + +// expireKeysLocked removes expired keys from the keyring. If a key was added +// with a lifetimesecs constraint and seconds >= lifetimesecs seconds have +// elapsed, it is removed. The caller *must* be holding the keyring mutex. +func (r *keyring) expireKeysLocked() { + for _, k := range r.keys { + if k.expire != nil && time.Now().After(*k.expire) { + r.removeLocked(k.signer.PublicKey().Marshal()) + } + } +} + +// List returns the identities known to the agent. +func (r *keyring) List() ([]*Key, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + // section 2.7: locked agents return empty. + return nil, nil + } + + r.expireKeysLocked() + var ids []*Key + for _, k := range r.keys { + pub := k.signer.PublicKey() + ids = append(ids, &Key{ + Format: pub.Type(), + Blob: pub.Marshal(), + Comment: k.comment}) + } + return ids, nil +} + +// Insert adds a private key to the keyring. If a certificate +// is given, that certificate is added as public key. Note that +// any constraints given are ignored. +func (r *keyring) Add(key AddedKey) error { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return errLocked + } + signer, err := ssh.NewSignerFromKey(key.PrivateKey) + + if err != nil { + return err + } + + if cert := key.Certificate; cert != nil { + signer, err = ssh.NewCertSigner(cert, signer) + if err != nil { + return err + } + } + + p := privKey{ + signer: signer, + comment: key.Comment, + } + + if key.LifetimeSecs > 0 { + t := time.Now().Add(time.Duration(key.LifetimeSecs) * time.Second) + p.expire = &t + } + + // If we already have a Signer with the same public key, replace it with the + // new one. + for idx, k := range r.keys { + if bytes.Equal(k.signer.PublicKey().Marshal(), p.signer.PublicKey().Marshal()) { + r.keys[idx] = p + return nil + } + } + + r.keys = append(r.keys, p) + + return nil +} + +// Sign returns a signature for the data. +func (r *keyring) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) { + return r.SignWithFlags(key, data, 0) +} + +func (r *keyring) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFlags) (*ssh.Signature, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return nil, errLocked + } + + r.expireKeysLocked() + wanted := key.Marshal() + for _, k := range r.keys { + if bytes.Equal(k.signer.PublicKey().Marshal(), wanted) { + if flags == 0 { + return k.signer.Sign(rand.Reader, data) + } else { + if algorithmSigner, ok := k.signer.(ssh.AlgorithmSigner); !ok { + return nil, fmt.Errorf("agent: signature does not support non-default signature algorithm: %T", k.signer) + } else { + var algorithm string + switch flags { + case SignatureFlagRsaSha256: + algorithm = ssh.KeyAlgoRSASHA256 + case SignatureFlagRsaSha512: + algorithm = ssh.KeyAlgoRSASHA512 + default: + return nil, fmt.Errorf("agent: unsupported signature flags: %d", flags) + } + return algorithmSigner.SignWithAlgorithm(rand.Reader, data, algorithm) + } + } + } + } + return nil, errors.New("not found") +} + +// Signers returns signers for all the known keys. +func (r *keyring) Signers() ([]ssh.Signer, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.locked { + return nil, errLocked + } + + r.expireKeysLocked() + s := make([]ssh.Signer, 0, len(r.keys)) + for _, k := range r.keys { + s = append(s, k.signer) + } + return s, nil +} + +// The keyring does not support any extensions +func (r *keyring) Extension(extensionType string, contents []byte) ([]byte, error) { + return nil, ErrExtensionUnsupported +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/agent/server.go b/cli/vendor/golang.org/x/crypto/ssh/agent/server.go new file mode 100644 index 0000000..2a7658c --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/agent/server.go @@ -0,0 +1,573 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package agent + +import ( + "crypto/dsa" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" + "encoding/binary" + "errors" + "fmt" + "io" + "log" + "math/big" + + "golang.org/x/crypto/ssh" +) + +// server wraps an Agent and uses it to implement the agent side of +// the SSH-agent, wire protocol. +type server struct { + agent Agent +} + +func (s *server) processRequestBytes(reqData []byte) []byte { + rep, err := s.processRequest(reqData) + if err != nil { + if err != errLocked { + // TODO(hanwen): provide better logging interface? + log.Printf("agent %d: %v", reqData[0], err) + } + return []byte{agentFailure} + } + + if rep == nil { + return []byte{agentSuccess} + } + + return ssh.Marshal(rep) +} + +func marshalKey(k *Key) []byte { + var record struct { + Blob []byte + Comment string + } + record.Blob = k.Marshal() + record.Comment = k.Comment + + return ssh.Marshal(&record) +} + +// See [PROTOCOL.agent], section 2.5.1. +const agentV1IdentitiesAnswer = 2 + +type agentV1IdentityMsg struct { + Numkeys uint32 `sshtype:"2"` +} + +type agentRemoveIdentityMsg struct { + KeyBlob []byte `sshtype:"18"` +} + +type agentLockMsg struct { + Passphrase []byte `sshtype:"22"` +} + +type agentUnlockMsg struct { + Passphrase []byte `sshtype:"23"` +} + +func (s *server) processRequest(data []byte) (interface{}, error) { + switch data[0] { + case agentRequestV1Identities: + return &agentV1IdentityMsg{0}, nil + + case agentRemoveAllV1Identities: + return nil, nil + + case agentRemoveIdentity: + var req agentRemoveIdentityMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + + var wk wireKey + if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil { + return nil, err + } + + return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob}) + + case agentRemoveAllIdentities: + return nil, s.agent.RemoveAll() + + case agentLock: + var req agentLockMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + + return nil, s.agent.Lock(req.Passphrase) + + case agentUnlock: + var req agentUnlockMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + return nil, s.agent.Unlock(req.Passphrase) + + case agentSignRequest: + var req signRequestAgentMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + + var wk wireKey + if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil { + return nil, err + } + + k := &Key{ + Format: wk.Format, + Blob: req.KeyBlob, + } + + var sig *ssh.Signature + var err error + if extendedAgent, ok := s.agent.(ExtendedAgent); ok { + sig, err = extendedAgent.SignWithFlags(k, req.Data, SignatureFlags(req.Flags)) + } else { + sig, err = s.agent.Sign(k, req.Data) + } + + if err != nil { + return nil, err + } + return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil + + case agentRequestIdentities: + keys, err := s.agent.List() + if err != nil { + return nil, err + } + + rep := identitiesAnswerAgentMsg{ + NumKeys: uint32(len(keys)), + } + for _, k := range keys { + rep.Keys = append(rep.Keys, marshalKey(k)...) + } + return rep, nil + + case agentAddIDConstrained, agentAddIdentity: + return nil, s.insertIdentity(data) + + case agentExtension: + // Return a stub object where the whole contents of the response gets marshaled. + var responseStub struct { + Rest []byte `ssh:"rest"` + } + + if extendedAgent, ok := s.agent.(ExtendedAgent); !ok { + // If this agent doesn't implement extensions, [PROTOCOL.agent] section 4.7 + // requires that we return a standard SSH_AGENT_FAILURE message. + responseStub.Rest = []byte{agentFailure} + } else { + var req extensionAgentMsg + if err := ssh.Unmarshal(data, &req); err != nil { + return nil, err + } + res, err := extendedAgent.Extension(req.ExtensionType, req.Contents) + if err != nil { + // If agent extensions are unsupported, return a standard SSH_AGENT_FAILURE + // message as required by [PROTOCOL.agent] section 4.7. + if err == ErrExtensionUnsupported { + responseStub.Rest = []byte{agentFailure} + } else { + // As the result of any other error processing an extension request, + // [PROTOCOL.agent] section 4.7 requires that we return a + // SSH_AGENT_EXTENSION_FAILURE code. + responseStub.Rest = []byte{agentExtensionFailure} + } + } else { + if len(res) == 0 { + return nil, nil + } + responseStub.Rest = res + } + } + + return responseStub, nil + } + + return nil, fmt.Errorf("unknown opcode %d", data[0]) +} + +func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse bool, extensions []ConstraintExtension, err error) { + for len(constraints) != 0 { + switch constraints[0] { + case agentConstrainLifetime: + if len(constraints) < 5 { + return 0, false, nil, io.ErrUnexpectedEOF + } + lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5]) + constraints = constraints[5:] + case agentConstrainConfirm: + confirmBeforeUse = true + constraints = constraints[1:] + case agentConstrainExtension, agentConstrainExtensionV00: + var msg constrainExtensionAgentMsg + if err = ssh.Unmarshal(constraints, &msg); err != nil { + return 0, false, nil, err + } + extensions = append(extensions, ConstraintExtension{ + ExtensionName: msg.ExtensionName, + ExtensionDetails: msg.ExtensionDetails, + }) + constraints = msg.Rest + default: + return 0, false, nil, fmt.Errorf("unknown constraint type: %d", constraints[0]) + } + } + return +} + +func setConstraints(key *AddedKey, constraintBytes []byte) error { + lifetimeSecs, confirmBeforeUse, constraintExtensions, err := parseConstraints(constraintBytes) + if err != nil { + return err + } + + key.LifetimeSecs = lifetimeSecs + key.ConfirmBeforeUse = confirmBeforeUse + key.ConstraintExtensions = constraintExtensions + return nil +} + +func parseRSAKey(req []byte) (*AddedKey, error) { + var k rsaKeyMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + if k.E.BitLen() > 30 { + return nil, errors.New("agent: RSA public exponent too large") + } + priv := &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + E: int(k.E.Int64()), + N: k.N, + }, + D: k.D, + Primes: []*big.Int{k.P, k.Q}, + } + priv.Precompute() + + addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseEd25519Key(req []byte) (*AddedKey, error) { + var k ed25519KeyMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + priv := ed25519.PrivateKey(k.Priv) + + addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseDSAKey(req []byte) (*AddedKey, error) { + var k dsaKeyMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + priv := &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: k.P, + Q: k.Q, + G: k.G, + }, + Y: k.Y, + }, + X: k.X, + } + + addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) { + priv = &ecdsa.PrivateKey{ + D: privScalar, + } + + switch curveName { + case "nistp256": + priv.Curve = elliptic.P256() + case "nistp384": + priv.Curve = elliptic.P384() + case "nistp521": + priv.Curve = elliptic.P521() + default: + return nil, fmt.Errorf("agent: unknown curve %q", curveName) + } + + priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes) + if priv.X == nil || priv.Y == nil { + return nil, errors.New("agent: point not on curve") + } + + return priv, nil +} + +func parseEd25519Cert(req []byte) (*AddedKey, error) { + var k ed25519CertMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + pubKey, err := ssh.ParsePublicKey(k.CertBytes) + if err != nil { + return nil, err + } + priv := ed25519.PrivateKey(k.Priv) + cert, ok := pubKey.(*ssh.Certificate) + if !ok { + return nil, errors.New("agent: bad ED25519 certificate") + } + + addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseECDSAKey(req []byte) (*AddedKey, error) { + var k ecdsaKeyMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + + priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D) + if err != nil { + return nil, err + } + + addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseRSACert(req []byte) (*AddedKey, error) { + var k rsaCertMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + + pubKey, err := ssh.ParsePublicKey(k.CertBytes) + if err != nil { + return nil, err + } + + cert, ok := pubKey.(*ssh.Certificate) + if !ok { + return nil, errors.New("agent: bad RSA certificate") + } + + // An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go + var rsaPub struct { + Name string + E *big.Int + N *big.Int + } + if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil { + return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err) + } + + if rsaPub.E.BitLen() > 30 { + return nil, errors.New("agent: RSA public exponent too large") + } + + priv := rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + E: int(rsaPub.E.Int64()), + N: rsaPub.N, + }, + D: k.D, + Primes: []*big.Int{k.Q, k.P}, + } + priv.Precompute() + + addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseDSACert(req []byte) (*AddedKey, error) { + var k dsaCertMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + pubKey, err := ssh.ParsePublicKey(k.CertBytes) + if err != nil { + return nil, err + } + cert, ok := pubKey.(*ssh.Certificate) + if !ok { + return nil, errors.New("agent: bad DSA certificate") + } + + // A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go + var w struct { + Name string + P, Q, G, Y *big.Int + } + if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil { + return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err) + } + + priv := &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: w.P, + Q: w.Q, + G: w.G, + }, + Y: w.Y, + }, + X: k.X, + } + + addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func parseECDSACert(req []byte) (*AddedKey, error) { + var k ecdsaCertMsg + if err := ssh.Unmarshal(req, &k); err != nil { + return nil, err + } + + pubKey, err := ssh.ParsePublicKey(k.CertBytes) + if err != nil { + return nil, err + } + cert, ok := pubKey.(*ssh.Certificate) + if !ok { + return nil, errors.New("agent: bad ECDSA certificate") + } + + // An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go + var ecdsaPub struct { + Name string + ID string + Key []byte + } + if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil { + return nil, err + } + + priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D) + if err != nil { + return nil, err + } + + addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments} + if err := setConstraints(addedKey, k.Constraints); err != nil { + return nil, err + } + return addedKey, nil +} + +func (s *server) insertIdentity(req []byte) error { + var record struct { + Type string `sshtype:"17|25"` + Rest []byte `ssh:"rest"` + } + + if err := ssh.Unmarshal(req, &record); err != nil { + return err + } + + var addedKey *AddedKey + var err error + + switch record.Type { + case ssh.KeyAlgoRSA: + addedKey, err = parseRSAKey(req) + case ssh.InsecureKeyAlgoDSA: + addedKey, err = parseDSAKey(req) + case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521: + addedKey, err = parseECDSAKey(req) + case ssh.KeyAlgoED25519: + addedKey, err = parseEd25519Key(req) + case ssh.CertAlgoRSAv01: + addedKey, err = parseRSACert(req) + case ssh.InsecureCertAlgoDSAv01: + addedKey, err = parseDSACert(req) + case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01: + addedKey, err = parseECDSACert(req) + case ssh.CertAlgoED25519v01: + addedKey, err = parseEd25519Cert(req) + default: + return fmt.Errorf("agent: not implemented: %q", record.Type) + } + + if err != nil { + return err + } + return s.agent.Add(*addedKey) +} + +// ServeAgent serves the agent protocol on the given connection. It +// returns when an I/O error occurs. +func ServeAgent(agent Agent, c io.ReadWriter) error { + s := &server{agent} + + var length [4]byte + for { + if _, err := io.ReadFull(c, length[:]); err != nil { + return err + } + l := binary.BigEndian.Uint32(length[:]) + if l == 0 { + return fmt.Errorf("agent: request size is 0") + } + if l > maxAgentResponseBytes { + // We also cap requests. + return fmt.Errorf("agent: request too large: %d", l) + } + + req := make([]byte, l) + if _, err := io.ReadFull(c, req); err != nil { + return err + } + + repData := s.processRequestBytes(req) + if len(repData) > maxAgentResponseBytes { + return fmt.Errorf("agent: reply too large: %d bytes", len(repData)) + } + + binary.BigEndian.PutUint32(length[:], uint32(len(repData))) + if _, err := c.Write(length[:]); err != nil { + return err + } + if _, err := c.Write(repData); err != nil { + return err + } + } +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/buffer.go b/cli/vendor/golang.org/x/crypto/ssh/buffer.go new file mode 100644 index 0000000..1ab07d0 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/buffer.go @@ -0,0 +1,97 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "io" + "sync" +) + +// buffer provides a linked list buffer for data exchange +// between producer and consumer. Theoretically the buffer is +// of unlimited capacity as it does no allocation of its own. +type buffer struct { + // protects concurrent access to head, tail and closed + *sync.Cond + + head *element // the buffer that will be read first + tail *element // the buffer that will be read last + + closed bool +} + +// An element represents a single link in a linked list. +type element struct { + buf []byte + next *element +} + +// newBuffer returns an empty buffer that is not closed. +func newBuffer() *buffer { + e := new(element) + b := &buffer{ + Cond: newCond(), + head: e, + tail: e, + } + return b +} + +// write makes buf available for Read to receive. +// buf must not be modified after the call to write. +func (b *buffer) write(buf []byte) { + b.Cond.L.Lock() + e := &element{buf: buf} + b.tail.next = e + b.tail = e + b.Cond.Signal() + b.Cond.L.Unlock() +} + +// eof closes the buffer. Reads from the buffer once all +// the data has been consumed will receive io.EOF. +func (b *buffer) eof() { + b.Cond.L.Lock() + b.closed = true + b.Cond.Signal() + b.Cond.L.Unlock() +} + +// Read reads data from the internal buffer in buf. Reads will block +// if no data is available, or until the buffer is closed. +func (b *buffer) Read(buf []byte) (n int, err error) { + b.Cond.L.Lock() + defer b.Cond.L.Unlock() + + for len(buf) > 0 { + // if there is data in b.head, copy it + if len(b.head.buf) > 0 { + r := copy(buf, b.head.buf) + buf, b.head.buf = buf[r:], b.head.buf[r:] + n += r + continue + } + // if there is a next buffer, make it the head + if len(b.head.buf) == 0 && b.head != b.tail { + b.head = b.head.next + continue + } + + // if at least one byte has been copied, return + if n > 0 { + break + } + + // if nothing was read, and there is nothing outstanding + // check to see if the buffer is closed. + if b.closed { + err = io.EOF + break + } + // out of buffers, wait for producer + b.Cond.Wait() + } + return +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/certs.go b/cli/vendor/golang.org/x/crypto/ssh/certs.go new file mode 100644 index 0000000..139fa31 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/certs.go @@ -0,0 +1,624 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "sort" + "time" +) + +// Certificate algorithm names from [PROTOCOL.certkeys]. These values can appear +// in Certificate.Type, PublicKey.Type, and ClientConfig.HostKeyAlgorithms. +// Unlike key algorithm names, these are not passed to AlgorithmSigner nor +// returned by MultiAlgorithmSigner and don't appear in the Signature.Format +// field. +const ( + CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com" + // Deprecated: DSA is only supported at insecure key sizes, and was removed + // from major implementations. + CertAlgoDSAv01 = InsecureCertAlgoDSAv01 + // Deprecated: DSA is only supported at insecure key sizes, and was removed + // from major implementations. + InsecureCertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com" + CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com" + CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com" + CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com" + CertAlgoSKECDSA256v01 = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com" + CertAlgoED25519v01 = "ssh-ed25519-cert-v01@openssh.com" + CertAlgoSKED25519v01 = "sk-ssh-ed25519-cert-v01@openssh.com" + + // CertAlgoRSASHA256v01 and CertAlgoRSASHA512v01 can't appear as a + // Certificate.Type (or PublicKey.Type), but only in + // ClientConfig.HostKeyAlgorithms. + CertAlgoRSASHA256v01 = "rsa-sha2-256-cert-v01@openssh.com" + CertAlgoRSASHA512v01 = "rsa-sha2-512-cert-v01@openssh.com" +) + +const ( + // Deprecated: use CertAlgoRSAv01. + CertSigAlgoRSAv01 = CertAlgoRSAv01 + // Deprecated: use CertAlgoRSASHA256v01. + CertSigAlgoRSASHA2256v01 = CertAlgoRSASHA256v01 + // Deprecated: use CertAlgoRSASHA512v01. + CertSigAlgoRSASHA2512v01 = CertAlgoRSASHA512v01 +) + +// Certificate types distinguish between host and user +// certificates. The values can be set in the CertType field of +// Certificate. +const ( + UserCert = 1 + HostCert = 2 +) + +// Signature represents a cryptographic signature. +type Signature struct { + Format string + Blob []byte + Rest []byte `ssh:"rest"` +} + +// CertTimeInfinity can be used for OpenSSHCertV01.ValidBefore to indicate that +// a certificate does not expire. +const CertTimeInfinity = 1<<64 - 1 + +// An Certificate represents an OpenSSH certificate as defined in +// [PROTOCOL.certkeys]?rev=1.8. The Certificate type implements the +// PublicKey interface, so it can be unmarshaled using +// ParsePublicKey. +type Certificate struct { + Nonce []byte + Key PublicKey + Serial uint64 + CertType uint32 + KeyId string + ValidPrincipals []string + ValidAfter uint64 + ValidBefore uint64 + Permissions + Reserved []byte + SignatureKey PublicKey + Signature *Signature +} + +// genericCertData holds the key-independent part of the certificate data. +// Overall, certificates contain an nonce, public key fields and +// key-independent fields. +type genericCertData struct { + Serial uint64 + CertType uint32 + KeyId string + ValidPrincipals []byte + ValidAfter uint64 + ValidBefore uint64 + CriticalOptions []byte + Extensions []byte + Reserved []byte + SignatureKey []byte + Signature []byte +} + +func marshalStringList(namelist []string) []byte { + var to []byte + for _, name := range namelist { + s := struct{ N string }{name} + to = append(to, Marshal(&s)...) + } + return to +} + +type optionsTuple struct { + Key string + Value []byte +} + +type optionsTupleValue struct { + Value string +} + +// serialize a map of critical options or extensions +// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation, +// we need two length prefixes for a non-empty string value +func marshalTuples(tups map[string]string) []byte { + keys := make([]string, 0, len(tups)) + for key := range tups { + keys = append(keys, key) + } + sort.Strings(keys) + + var ret []byte + for _, key := range keys { + s := optionsTuple{Key: key} + if value := tups[key]; len(value) > 0 { + s.Value = Marshal(&optionsTupleValue{value}) + } + ret = append(ret, Marshal(&s)...) + } + return ret +} + +// issue #10569 - per [PROTOCOL.certkeys] and SSH implementation, +// we need two length prefixes for a non-empty option value +func parseTuples(in []byte) (map[string]string, error) { + tups := map[string]string{} + var lastKey string + var haveLastKey bool + + for len(in) > 0 { + var key, val, extra []byte + var ok bool + + if key, in, ok = parseString(in); !ok { + return nil, errShortRead + } + keyStr := string(key) + // according to [PROTOCOL.certkeys], the names must be in + // lexical order. + if haveLastKey && keyStr <= lastKey { + return nil, fmt.Errorf("ssh: certificate options are not in lexical order") + } + lastKey, haveLastKey = keyStr, true + // the next field is a data field, which if non-empty has a string embedded + if val, in, ok = parseString(in); !ok { + return nil, errShortRead + } + if len(val) > 0 { + val, extra, ok = parseString(val) + if !ok { + return nil, errShortRead + } + if len(extra) > 0 { + return nil, fmt.Errorf("ssh: unexpected trailing data after certificate option value") + } + tups[keyStr] = string(val) + } else { + tups[keyStr] = "" + } + } + return tups, nil +} + +func parseCert(in []byte, privAlgo string) (*Certificate, error) { + nonce, rest, ok := parseString(in) + if !ok { + return nil, errShortRead + } + + key, rest, err := parsePubKey(rest, privAlgo) + if err != nil { + return nil, err + } + + var g genericCertData + if err := Unmarshal(rest, &g); err != nil { + return nil, err + } + + c := &Certificate{ + Nonce: nonce, + Key: key, + Serial: g.Serial, + CertType: g.CertType, + KeyId: g.KeyId, + ValidAfter: g.ValidAfter, + ValidBefore: g.ValidBefore, + } + + for principals := g.ValidPrincipals; len(principals) > 0; { + principal, rest, ok := parseString(principals) + if !ok { + return nil, errShortRead + } + c.ValidPrincipals = append(c.ValidPrincipals, string(principal)) + principals = rest + } + + c.CriticalOptions, err = parseTuples(g.CriticalOptions) + if err != nil { + return nil, err + } + c.Extensions, err = parseTuples(g.Extensions) + if err != nil { + return nil, err + } + c.Reserved = g.Reserved + k, err := ParsePublicKey(g.SignatureKey) + if err != nil { + return nil, err + } + // The Type() function is intended to return only certificate key types, but + // we use certKeyAlgoNames anyway for safety, to match [Certificate.Type]. + if _, ok := certKeyAlgoNames[k.Type()]; ok { + return nil, fmt.Errorf("ssh: the signature key type %q is invalid for certificates", k.Type()) + } + c.SignatureKey = k + c.Signature, rest, ok = parseSignatureBody(g.Signature) + if !ok || len(rest) > 0 { + return nil, errors.New("ssh: signature parse error") + } + + return c, nil +} + +type openSSHCertSigner struct { + pub *Certificate + signer Signer +} + +type algorithmOpenSSHCertSigner struct { + *openSSHCertSigner + algorithmSigner AlgorithmSigner +} + +// NewCertSigner returns a Signer that signs with the given Certificate, whose +// private key is held by signer. It returns an error if the public key in cert +// doesn't match the key used by signer. +func NewCertSigner(cert *Certificate, signer Signer) (Signer, error) { + if !bytes.Equal(cert.Key.Marshal(), signer.PublicKey().Marshal()) { + return nil, errors.New("ssh: signer and cert have different public key") + } + + switch s := signer.(type) { + case MultiAlgorithmSigner: + return &multiAlgorithmSigner{ + AlgorithmSigner: &algorithmOpenSSHCertSigner{ + &openSSHCertSigner{cert, signer}, s}, + supportedAlgorithms: s.Algorithms(), + }, nil + case AlgorithmSigner: + return &algorithmOpenSSHCertSigner{ + &openSSHCertSigner{cert, signer}, s}, nil + default: + return &openSSHCertSigner{cert, signer}, nil + } +} + +func (s *openSSHCertSigner) Sign(rand io.Reader, data []byte) (*Signature, error) { + return s.signer.Sign(rand, data) +} + +func (s *openSSHCertSigner) PublicKey() PublicKey { + return s.pub +} + +func (s *algorithmOpenSSHCertSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) { + return s.algorithmSigner.SignWithAlgorithm(rand, data, algorithm) +} + +const sourceAddressCriticalOption = "source-address" + +// CertChecker does the work of verifying a certificate. Its methods +// can be plugged into ClientConfig.HostKeyCallback and +// ServerConfig.PublicKeyCallback. For the CertChecker to work, +// minimally, the IsAuthority callback should be set. +type CertChecker struct { + // SupportedCriticalOptions lists the CriticalOptions that the + // server application layer understands. These are only used + // for user certificates. + SupportedCriticalOptions []string + + // IsUserAuthority should return true if the key is recognized as an + // authority for user certificate. This must be set if this CertChecker + // will be checking user certificates. + IsUserAuthority func(auth PublicKey) bool + + // IsHostAuthority should report whether the key is recognized as + // an authority for this host. This must be set if this CertChecker + // will be checking host certificates. + IsHostAuthority func(auth PublicKey, address string) bool + + // Clock is used for verifying time stamps. If nil, time.Now + // is used. + Clock func() time.Time + + // UserKeyFallback is called when CertChecker.Authenticate encounters a + // public key that is not a certificate. It must implement validation + // of user keys or else, if nil, all such keys are rejected. + UserKeyFallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) + + // HostKeyFallback is called when CertChecker.CheckHostKey encounters a + // public key that is not a certificate. It must implement host key + // validation or else, if nil, all such keys are rejected. + HostKeyFallback HostKeyCallback + + // IsRevoked is called for each certificate so that revocation checking + // can be implemented. It should return true if the given certificate + // is revoked and false otherwise. If nil, no certificates are + // considered to have been revoked. + IsRevoked func(cert *Certificate) bool +} + +// CheckHostKey checks a host key certificate. This method can be +// plugged into ClientConfig.HostKeyCallback. +func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey) error { + cert, ok := key.(*Certificate) + if !ok { + if c.HostKeyFallback != nil { + return c.HostKeyFallback(addr, remote, key) + } + return errors.New("ssh: non-certificate host key") + } + if cert.CertType != HostCert { + return fmt.Errorf("ssh: certificate presented as a host key has type %d", cert.CertType) + } + if !c.IsHostAuthority(cert.SignatureKey, addr) { + return fmt.Errorf("ssh: no authorities for hostname: %v", addr) + } + + hostname, _, err := net.SplitHostPort(addr) + if err != nil { + return err + } + + // Pass hostname only as principal for host certificates (consistent with OpenSSH) + return c.CheckCert(hostname, cert) +} + +// Authenticate checks a user certificate. Authenticate can be used as +// a value for ServerConfig.PublicKeyCallback. +func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) { + cert, ok := pubKey.(*Certificate) + if !ok { + if c.UserKeyFallback != nil { + return c.UserKeyFallback(conn, pubKey) + } + return nil, errors.New("ssh: normal key pairs not accepted") + } + + if cert.CertType != UserCert { + return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType) + } + if !c.IsUserAuthority(cert.SignatureKey) { + return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority") + } + + if err := c.CheckCert(conn.User(), cert); err != nil { + return nil, err + } + + return &cert.Permissions, nil +} + +// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and +// the signature of the certificate. +func (c *CertChecker) CheckCert(principal string, cert *Certificate) error { + if c.IsRevoked != nil && c.IsRevoked(cert) { + return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial) + } + + for opt := range cert.CriticalOptions { + // sourceAddressCriticalOption will be enforced by + // serverAuthenticate + if opt == sourceAddressCriticalOption { + continue + } + + found := false + for _, supp := range c.SupportedCriticalOptions { + if supp == opt { + found = true + break + } + } + if !found { + return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt) + } + } + + if len(cert.ValidPrincipals) > 0 { + // By default, certs are valid for all users/hosts. + found := false + for _, p := range cert.ValidPrincipals { + if p == principal { + found = true + break + } + } + if !found { + return fmt.Errorf("ssh: principal %q not in the set of valid principals for given certificate: %q", principal, cert.ValidPrincipals) + } + } + + clock := c.Clock + if clock == nil { + clock = time.Now + } + + unixNow := clock().Unix() + if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) { + return fmt.Errorf("ssh: cert is not yet valid") + } + if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) { + return fmt.Errorf("ssh: cert has expired") + } + if err := cert.SignatureKey.Verify(cert.bytesForSigning(), cert.Signature); err != nil { + return fmt.Errorf("ssh: certificate signature does not verify") + } + + return nil +} + +// SignCert signs the certificate with an authority, setting the Nonce, +// SignatureKey, and Signature fields. If the authority implements the +// MultiAlgorithmSigner interface the first algorithm in the list is used. This +// is useful if you want to sign with a specific algorithm. As specified in +// [SSH-CERTS], Section 2.1.1, authority can't be a [Certificate]. +func (c *Certificate) SignCert(rand io.Reader, authority Signer) error { + c.Nonce = make([]byte, 32) + if _, err := io.ReadFull(rand, c.Nonce); err != nil { + return err + } + // The Type() function is intended to return only certificate key types, but + // we use certKeyAlgoNames anyway for safety, to match [Certificate.Type]. + if _, ok := certKeyAlgoNames[authority.PublicKey().Type()]; ok { + return fmt.Errorf("ssh: certificates cannot be used as authority (public key type %q)", + authority.PublicKey().Type()) + } + c.SignatureKey = authority.PublicKey() + + if v, ok := authority.(MultiAlgorithmSigner); ok { + if len(v.Algorithms()) == 0 { + return errors.New("the provided authority has no signature algorithm") + } + // Use the first algorithm in the list. + sig, err := v.SignWithAlgorithm(rand, c.bytesForSigning(), v.Algorithms()[0]) + if err != nil { + return err + } + c.Signature = sig + return nil + } else if v, ok := authority.(AlgorithmSigner); ok && v.PublicKey().Type() == KeyAlgoRSA { + // Default to KeyAlgoRSASHA512 for ssh-rsa signers. + // TODO: consider using KeyAlgoRSASHA256 as default. + sig, err := v.SignWithAlgorithm(rand, c.bytesForSigning(), KeyAlgoRSASHA512) + if err != nil { + return err + } + c.Signature = sig + return nil + } + + sig, err := authority.Sign(rand, c.bytesForSigning()) + if err != nil { + return err + } + c.Signature = sig + return nil +} + +// certKeyAlgoNames is a mapping from known certificate algorithm names to the +// corresponding public key signature algorithm. +// +// This map must be kept in sync with the one in agent/client.go. +var certKeyAlgoNames = map[string]string{ + CertAlgoRSAv01: KeyAlgoRSA, + CertAlgoRSASHA256v01: KeyAlgoRSASHA256, + CertAlgoRSASHA512v01: KeyAlgoRSASHA512, + InsecureCertAlgoDSAv01: InsecureKeyAlgoDSA, + CertAlgoECDSA256v01: KeyAlgoECDSA256, + CertAlgoECDSA384v01: KeyAlgoECDSA384, + CertAlgoECDSA521v01: KeyAlgoECDSA521, + CertAlgoSKECDSA256v01: KeyAlgoSKECDSA256, + CertAlgoED25519v01: KeyAlgoED25519, + CertAlgoSKED25519v01: KeyAlgoSKED25519, +} + +// underlyingAlgo returns the signature algorithm associated with algo (which is +// an advertised or negotiated public key or host key algorithm). These are +// usually the same, except for certificate algorithms. +func underlyingAlgo(algo string) string { + if a, ok := certKeyAlgoNames[algo]; ok { + return a + } + return algo +} + +// certificateAlgo returns the certificate algorithms that uses the provided +// underlying signature algorithm. +func certificateAlgo(algo string) (certAlgo string, ok bool) { + for certName, algoName := range certKeyAlgoNames { + if algoName == algo { + return certName, true + } + } + return "", false +} + +func (cert *Certificate) bytesForSigning() []byte { + c2 := *cert + c2.Signature = nil + out := c2.Marshal() + // Drop trailing signature length. + return out[:len(out)-4] +} + +// Marshal serializes c into OpenSSH's wire format. It is part of the +// PublicKey interface. +func (c *Certificate) Marshal() []byte { + generic := genericCertData{ + Serial: c.Serial, + CertType: c.CertType, + KeyId: c.KeyId, + ValidPrincipals: marshalStringList(c.ValidPrincipals), + ValidAfter: uint64(c.ValidAfter), + ValidBefore: uint64(c.ValidBefore), + CriticalOptions: marshalTuples(c.CriticalOptions), + Extensions: marshalTuples(c.Extensions), + Reserved: c.Reserved, + SignatureKey: c.SignatureKey.Marshal(), + } + if c.Signature != nil { + generic.Signature = Marshal(c.Signature) + } + genericBytes := Marshal(&generic) + keyBytes := c.Key.Marshal() + _, keyBytes, _ = parseString(keyBytes) + prefix := Marshal(&struct { + Name string + Nonce []byte + Key []byte `ssh:"rest"` + }{c.Type(), c.Nonce, keyBytes}) + + result := make([]byte, 0, len(prefix)+len(genericBytes)) + result = append(result, prefix...) + result = append(result, genericBytes...) + return result +} + +// Type returns the certificate algorithm name. It is part of the PublicKey interface. +func (c *Certificate) Type() string { + certName, ok := certificateAlgo(c.Key.Type()) + if !ok { + panic("unknown certificate type for key type " + c.Key.Type()) + } + return certName +} + +// Verify verifies a signature against the certificate's public +// key. It is part of the PublicKey interface. +func (c *Certificate) Verify(data []byte, sig *Signature) error { + return c.Key.Verify(data, sig) +} + +func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) { + format, in, ok := parseString(in) + if !ok { + return + } + + out = &Signature{ + Format: string(format), + } + + if out.Blob, in, ok = parseString(in); !ok { + return + } + + switch out.Format { + case KeyAlgoSKECDSA256, CertAlgoSKECDSA256v01, KeyAlgoSKED25519, CertAlgoSKED25519v01: + out.Rest = in + return out, nil, ok + } + + return out, in, ok +} + +func parseSignature(in []byte) (out *Signature, rest []byte, ok bool) { + sigBytes, rest, ok := parseString(in) + if !ok { + return + } + + out, trailing, ok := parseSignatureBody(sigBytes) + if !ok || len(trailing) > 0 { + return nil, nil, false + } + return +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/channel.go b/cli/vendor/golang.org/x/crypto/ssh/channel.go new file mode 100644 index 0000000..cc0bb7a --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/channel.go @@ -0,0 +1,645 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "log" + "sync" +) + +const ( + minPacketLength = 9 + // channelMaxPacket contains the maximum number of bytes that will be + // sent in a single packet. As per RFC 4253, section 6.1, 32k is also + // the minimum. + channelMaxPacket = 1 << 15 + // We follow OpenSSH here. + channelWindowSize = 64 * channelMaxPacket +) + +// NewChannel represents an incoming request to a channel. It must either be +// accepted for use by calling Accept, or rejected by calling Reject. +type NewChannel interface { + // Accept accepts the channel creation request. It returns the Channel + // and a Go channel containing SSH requests. The Go channel must be + // serviced otherwise the Channel will hang. + Accept() (Channel, <-chan *Request, error) + + // Reject rejects the channel creation request. After calling + // this, no other methods on the Channel may be called. + Reject(reason RejectionReason, message string) error + + // ChannelType returns the type of the channel, as supplied by the + // client. + ChannelType() string + + // ExtraData returns the arbitrary payload for this channel, as supplied + // by the client. This data is specific to the channel type. + ExtraData() []byte +} + +// A Channel is an ordered, reliable, flow-controlled, duplex stream +// that is multiplexed over an SSH connection. +type Channel interface { + // Read reads up to len(data) bytes from the channel. + Read(data []byte) (int, error) + + // Write writes len(data) bytes to the channel. + Write(data []byte) (int, error) + + // Close signals end of channel use. No data may be sent after this + // call. + Close() error + + // CloseWrite signals the end of sending in-band + // data. Requests may still be sent, and the other side may + // still send data + CloseWrite() error + + // SendRequest sends a channel request. If wantReply is true, + // it will wait for a reply and return the result as a + // boolean, otherwise the return value will be false. Channel + // requests are out-of-band messages so they may be sent even + // if the data stream is closed or blocked by flow control. + // If the channel is closed before a reply is returned, io.EOF + // is returned. + SendRequest(name string, wantReply bool, payload []byte) (bool, error) + + // Stderr returns an io.ReadWriter that writes to this channel + // with the extended data type set to stderr. Stderr may + // safely be read and written from a different goroutine than + // Read and Write respectively. + Stderr() io.ReadWriter +} + +// Request is a request sent outside of the normal stream of +// data. Requests can either be specific to an SSH channel, or they +// can be global. +type Request struct { + Type string + WantReply bool + Payload []byte + + ch *channel + mux *mux +} + +// Reply sends a response to a request. It must be called for all requests +// where WantReply is true and is a no-op otherwise. The payload argument is +// ignored for replies to channel-specific requests. +func (r *Request) Reply(ok bool, payload []byte) error { + if !r.WantReply { + return nil + } + + if r.ch == nil { + return r.mux.ackRequest(ok, payload) + } + + return r.ch.ackRequest(ok) +} + +// RejectionReason is an enumeration used when rejecting channel creation +// requests. See RFC 4254, section 5.1. +type RejectionReason uint32 + +const ( + Prohibited RejectionReason = iota + 1 + ConnectionFailed + UnknownChannelType + ResourceShortage +) + +// String converts the rejection reason to human readable form. +func (r RejectionReason) String() string { + switch r { + case Prohibited: + return "administratively prohibited" + case ConnectionFailed: + return "connect failed" + case UnknownChannelType: + return "unknown channel type" + case ResourceShortage: + return "resource shortage" + } + return fmt.Sprintf("unknown reason %d", int(r)) +} + +func min(a uint32, b int) uint32 { + if a < uint32(b) { + return a + } + return uint32(b) +} + +type channelDirection uint8 + +const ( + channelInbound channelDirection = iota + channelOutbound +) + +// channel is an implementation of the Channel interface that works +// with the mux class. +type channel struct { + // R/O after creation + chanType string + extraData []byte + localId, remoteId uint32 + + // maxIncomingPayload and maxRemotePayload are the maximum + // payload sizes of normal and extended data packets for + // receiving and sending, respectively. The wire packet will + // be 9 or 13 bytes larger (excluding encryption overhead). + maxIncomingPayload uint32 + maxRemotePayload uint32 + + mux *mux + + // decided is set to true if an accept or reject message has been sent + // (for outbound channels) or received (for inbound channels). + decided bool + + // direction contains either channelOutbound, for channels created + // locally, or channelInbound, for channels created by the peer. + direction channelDirection + + // Pending internal channel messages. + msg chan interface{} + + // Since requests have no ID, there can be only one request + // with WantReply=true outstanding. This lock is held by a + // goroutine that has such an outgoing request pending. + sentRequestMu sync.Mutex + + incomingRequests chan *Request + + sentEOF bool + + // thread-safe data + remoteWin window + pending *buffer + extPending *buffer + + // windowMu protects myWindow, the flow-control window, and myConsumed, + // the number of bytes consumed since we last increased myWindow + windowMu sync.Mutex + myWindow uint32 + myConsumed uint32 + + // writeMu serializes calls to mux.conn.writePacket() and + // protects sentClose and packetPool. This mutex must be + // different from windowMu, as writePacket can block if there + // is a key exchange pending. + writeMu sync.Mutex + sentClose bool + + // packetPool has a buffer for each extended channel ID to + // save allocations during writes. + packetPool map[uint32][]byte +} + +// writePacket sends a packet. If the packet is a channel close, it updates +// sentClose. This method takes the lock c.writeMu. +func (ch *channel) writePacket(packet []byte) error { + ch.writeMu.Lock() + if ch.sentClose { + ch.writeMu.Unlock() + return io.EOF + } + ch.sentClose = (packet[0] == msgChannelClose) + err := ch.mux.conn.writePacket(packet) + ch.writeMu.Unlock() + return err +} + +func (ch *channel) sendMessage(msg interface{}) error { + if debugMux { + log.Printf("send(%d): %#v", ch.mux.chanList.offset, msg) + } + + p := Marshal(msg) + binary.BigEndian.PutUint32(p[1:], ch.remoteId) + return ch.writePacket(p) +} + +// WriteExtended writes data to a specific extended stream. These streams are +// used, for example, for stderr. +func (ch *channel) WriteExtended(data []byte, extendedCode uint32) (n int, err error) { + if ch.sentEOF { + return 0, io.EOF + } + // 1 byte message type, 4 bytes remoteId, 4 bytes data length + opCode := byte(msgChannelData) + headerLength := uint32(9) + if extendedCode > 0 { + headerLength += 4 + opCode = msgChannelExtendedData + } + + ch.writeMu.Lock() + packet := ch.packetPool[extendedCode] + // We don't remove the buffer from packetPool, so + // WriteExtended calls from different goroutines will be + // flagged as errors by the race detector. + ch.writeMu.Unlock() + + for len(data) > 0 { + space := min(ch.maxRemotePayload, len(data)) + if space, err = ch.remoteWin.reserve(space); err != nil { + return n, err + } + if want := headerLength + space; uint32(cap(packet)) < want { + packet = make([]byte, want) + } else { + packet = packet[:want] + } + + todo := data[:space] + + packet[0] = opCode + binary.BigEndian.PutUint32(packet[1:], ch.remoteId) + if extendedCode > 0 { + binary.BigEndian.PutUint32(packet[5:], uint32(extendedCode)) + } + binary.BigEndian.PutUint32(packet[headerLength-4:], uint32(len(todo))) + copy(packet[headerLength:], todo) + if err = ch.writePacket(packet); err != nil { + return n, err + } + + n += len(todo) + data = data[len(todo):] + } + + ch.writeMu.Lock() + ch.packetPool[extendedCode] = packet + ch.writeMu.Unlock() + + return n, err +} + +func (ch *channel) handleData(packet []byte) error { + headerLen := 9 + isExtendedData := packet[0] == msgChannelExtendedData + if isExtendedData { + headerLen = 13 + } + if len(packet) < headerLen { + // malformed data packet + return parseError(packet[0]) + } + + var extended uint32 + if isExtendedData { + extended = binary.BigEndian.Uint32(packet[5:]) + } + + length := binary.BigEndian.Uint32(packet[headerLen-4 : headerLen]) + if length == 0 { + return nil + } + if length > ch.maxIncomingPayload { + // TODO(hanwen): should send Disconnect? + return errors.New("ssh: incoming packet exceeds maximum payload size") + } + + data := packet[headerLen:] + if length != uint32(len(data)) { + return errors.New("ssh: wrong packet length") + } + + ch.windowMu.Lock() + if ch.myWindow < length { + ch.windowMu.Unlock() + // TODO(hanwen): should send Disconnect with reason? + return errors.New("ssh: remote side wrote too much") + } + ch.myWindow -= length + ch.windowMu.Unlock() + + if extended == 1 { + ch.extPending.write(data) + } else if extended > 0 { + // discard other extended data. + } else { + ch.pending.write(data) + } + return nil +} + +func (c *channel) adjustWindow(adj uint32) error { + c.windowMu.Lock() + // Since myConsumed and myWindow are managed on our side, and can never + // exceed the initial window setting, we don't worry about overflow. + c.myConsumed += adj + var sendAdj uint32 + if (channelWindowSize-c.myWindow > 3*c.maxIncomingPayload) || + (c.myWindow < channelWindowSize/2) { + sendAdj = c.myConsumed + c.myConsumed = 0 + c.myWindow += sendAdj + } + c.windowMu.Unlock() + if sendAdj == 0 { + return nil + } + return c.sendMessage(windowAdjustMsg{ + AdditionalBytes: sendAdj, + }) +} + +func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error) { + switch extended { + case 1: + n, err = c.extPending.Read(data) + case 0: + n, err = c.pending.Read(data) + default: + return 0, fmt.Errorf("ssh: extended code %d unimplemented", extended) + } + + if n > 0 { + err = c.adjustWindow(uint32(n)) + // sendWindowAdjust can return io.EOF if the remote + // peer has closed the connection, however we want to + // defer forwarding io.EOF to the caller of Read until + // the buffer has been drained. + if n > 0 && err == io.EOF { + err = nil + } + } + + return n, err +} + +func (c *channel) close() { + c.pending.eof() + c.extPending.eof() + close(c.msg) + close(c.incomingRequests) + c.writeMu.Lock() + // This is not necessary for a normal channel teardown, but if + // there was another error, it is. + c.sentClose = true + c.writeMu.Unlock() + // Unblock writers. + c.remoteWin.close() +} + +// responseMessageReceived is called when a success or failure message is +// received on a channel to check that such a message is reasonable for the +// given channel. +func (ch *channel) responseMessageReceived() error { + if ch.direction == channelInbound { + return errors.New("ssh: channel response message received on inbound channel") + } + if ch.decided { + return errors.New("ssh: duplicate response received for channel") + } + ch.decided = true + return nil +} + +func (ch *channel) handlePacket(packet []byte) error { + switch packet[0] { + case msgChannelData, msgChannelExtendedData: + return ch.handleData(packet) + case msgChannelClose: + ch.sendMessage(channelCloseMsg{PeersID: ch.remoteId}) + ch.mux.chanList.remove(ch.localId) + ch.close() + return nil + case msgChannelEOF: + // RFC 4254 is mute on how EOF affects dataExt messages but + // it is logical to signal EOF at the same time. + ch.extPending.eof() + ch.pending.eof() + return nil + } + + decoded, err := decode(packet) + if err != nil { + return err + } + + switch msg := decoded.(type) { + case *channelOpenFailureMsg: + if err := ch.responseMessageReceived(); err != nil { + return err + } + ch.mux.chanList.remove(msg.PeersID) + ch.msg <- msg + case *channelOpenConfirmMsg: + if err := ch.responseMessageReceived(); err != nil { + return err + } + if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { + return fmt.Errorf("ssh: invalid MaxPacketSize %d from peer", msg.MaxPacketSize) + } + ch.remoteId = msg.MyID + ch.maxRemotePayload = msg.MaxPacketSize + ch.remoteWin.add(msg.MyWindow) + ch.msg <- msg + case *windowAdjustMsg: + if !ch.remoteWin.add(msg.AdditionalBytes) { + return fmt.Errorf("ssh: invalid window update for %d bytes", msg.AdditionalBytes) + } + case *channelRequestMsg: + req := Request{ + Type: msg.Request, + WantReply: msg.WantReply, + Payload: msg.RequestSpecificData, + ch: ch, + } + + ch.incomingRequests <- &req + default: + ch.msg <- msg + } + return nil +} + +func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel { + ch := &channel{ + remoteWin: window{Cond: newCond()}, + myWindow: channelWindowSize, + pending: newBuffer(), + extPending: newBuffer(), + direction: direction, + incomingRequests: make(chan *Request, chanSize), + msg: make(chan interface{}, chanSize), + chanType: chanType, + extraData: extraData, + mux: m, + packetPool: make(map[uint32][]byte), + } + ch.localId = m.chanList.add(ch) + return ch +} + +var errUndecided = errors.New("ssh: must Accept or Reject channel") +var errDecidedAlready = errors.New("ssh: can call Accept or Reject only once") + +type extChannel struct { + code uint32 + ch *channel +} + +func (e *extChannel) Write(data []byte) (n int, err error) { + return e.ch.WriteExtended(data, e.code) +} + +func (e *extChannel) Read(data []byte) (n int, err error) { + return e.ch.ReadExtended(data, e.code) +} + +func (ch *channel) Accept() (Channel, <-chan *Request, error) { + if ch.decided { + return nil, nil, errDecidedAlready + } + ch.maxIncomingPayload = channelMaxPacket + confirm := channelOpenConfirmMsg{ + PeersID: ch.remoteId, + MyID: ch.localId, + MyWindow: ch.myWindow, + MaxPacketSize: ch.maxIncomingPayload, + } + ch.decided = true + if err := ch.sendMessage(confirm); err != nil { + return nil, nil, err + } + + return ch, ch.incomingRequests, nil +} + +func (ch *channel) Reject(reason RejectionReason, message string) error { + if ch.decided { + return errDecidedAlready + } + reject := channelOpenFailureMsg{ + PeersID: ch.remoteId, + Reason: reason, + Message: message, + Language: "en", + } + ch.decided = true + return ch.sendMessage(reject) +} + +func (ch *channel) Read(data []byte) (int, error) { + if !ch.decided { + return 0, errUndecided + } + return ch.ReadExtended(data, 0) +} + +func (ch *channel) Write(data []byte) (int, error) { + if !ch.decided { + return 0, errUndecided + } + return ch.WriteExtended(data, 0) +} + +func (ch *channel) CloseWrite() error { + if !ch.decided { + return errUndecided + } + ch.sentEOF = true + return ch.sendMessage(channelEOFMsg{ + PeersID: ch.remoteId}) +} + +func (ch *channel) Close() error { + if !ch.decided { + return errUndecided + } + + return ch.sendMessage(channelCloseMsg{ + PeersID: ch.remoteId}) +} + +// Extended returns an io.ReadWriter that sends and receives data on the given, +// SSH extended stream. Such streams are used, for example, for stderr. +func (ch *channel) Extended(code uint32) io.ReadWriter { + if !ch.decided { + return nil + } + return &extChannel{code, ch} +} + +func (ch *channel) Stderr() io.ReadWriter { + return ch.Extended(1) +} + +func (ch *channel) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { + if !ch.decided { + return false, errUndecided + } + + if wantReply { + ch.sentRequestMu.Lock() + defer ch.sentRequestMu.Unlock() + } + + msg := channelRequestMsg{ + PeersID: ch.remoteId, + Request: name, + WantReply: wantReply, + RequestSpecificData: payload, + } + + if err := ch.sendMessage(msg); err != nil { + return false, err + } + + if wantReply { + m, ok := (<-ch.msg) + if !ok { + return false, io.EOF + } + switch m.(type) { + case *channelRequestFailureMsg: + return false, nil + case *channelRequestSuccessMsg: + return true, nil + default: + return false, fmt.Errorf("ssh: unexpected response to channel request: %#v", m) + } + } + + return false, nil +} + +// ackRequest either sends an ack or nack to the channel request. +func (ch *channel) ackRequest(ok bool) error { + if !ch.decided { + return errUndecided + } + + var msg interface{} + if !ok { + msg = channelRequestFailureMsg{ + PeersID: ch.remoteId, + } + } else { + msg = channelRequestSuccessMsg{ + PeersID: ch.remoteId, + } + } + return ch.sendMessage(msg) +} + +func (ch *channel) ChannelType() string { + return ch.chanType +} + +func (ch *channel) ExtraData() []byte { + return ch.extraData +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/cipher.go b/cli/vendor/golang.org/x/crypto/ssh/cipher.go new file mode 100644 index 0000000..ad2b370 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/cipher.go @@ -0,0 +1,789 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/fips140" + "crypto/rc4" + "crypto/subtle" + "encoding/binary" + "errors" + "fmt" + "hash" + "io" + "slices" + + "golang.org/x/crypto/chacha20" + "golang.org/x/crypto/internal/poly1305" +) + +const ( + packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher. + + // RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations + // MUST be able to process (plus a few more kilobytes for padding and mac). The RFC + // indicates implementations SHOULD be able to handle larger packet sizes, but then + // waffles on about reasonable limits. + // + // OpenSSH caps their maxPacket at 256kB so we choose to do + // the same. maxPacket is also used to ensure that uint32 + // length fields do not overflow, so it should remain well + // below 4G. + maxPacket = 256 * 1024 +) + +// noneCipher implements cipher.Stream and provides no encryption. It is used +// by the transport before the first key-exchange. +type noneCipher struct{} + +func (c noneCipher) XORKeyStream(dst, src []byte) { + copy(dst, src) +} + +func newAESCTR(key, iv []byte) (cipher.Stream, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewCTR(c, iv), nil +} + +func newRC4(key, iv []byte) (cipher.Stream, error) { + return rc4.NewCipher(key) +} + +type cipherMode struct { + keySize int + ivSize int + create func(key, iv []byte, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) +} + +func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream, error)) func(key, iv []byte, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) { + return func(key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) { + stream, err := createFunc(key, iv) + if err != nil { + return nil, err + } + + var streamDump []byte + if skip > 0 { + streamDump = make([]byte, 512) + } + + for remainingToDump := skip; remainingToDump > 0; { + dumpThisTime := remainingToDump + if dumpThisTime > len(streamDump) { + dumpThisTime = len(streamDump) + } + stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime]) + remainingToDump -= dumpThisTime + } + + mac := macModes[algs.MAC].new(macKey) + return &streamPacketCipher{ + mac: mac, + etm: macModes[algs.MAC].etm, + macResult: make([]byte, mac.Size()), + cipher: stream, + }, nil + } +} + +// cipherModes documents properties of supported ciphers. Ciphers not included +// are not supported and will not be negotiated, even if explicitly configured. +// When FIPS mode is enabled, only FIPS-approved algorithms are included. +var cipherModes = map[string]*cipherMode{} + +func init() { + cipherModes[CipherAES128CTR] = &cipherMode{16, aes.BlockSize, streamCipherMode(0, newAESCTR)} + cipherModes[CipherAES192CTR] = &cipherMode{24, aes.BlockSize, streamCipherMode(0, newAESCTR)} + cipherModes[CipherAES256CTR] = &cipherMode{32, aes.BlockSize, streamCipherMode(0, newAESCTR)} + // Use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, + // we'll wire it up to NewGCMForSSH in Go 1.26. + // + // For now it means we'll work with fips140=on but not fips140=only. + cipherModes[CipherAES128GCM] = &cipherMode{16, 12, newGCMCipher} + cipherModes[CipherAES256GCM] = &cipherMode{32, 12, newGCMCipher} + + if fips140.Enabled() { + defaultCiphers = slices.DeleteFunc(defaultCiphers, func(algo string) bool { + _, ok := cipherModes[algo] + return !ok + }) + return + } + + cipherModes[CipherChaCha20Poly1305] = &cipherMode{64, 0, newChaCha20Cipher} + // Insecure ciphers not included in the default configuration. + cipherModes[InsecureCipherRC4128] = &cipherMode{16, 0, streamCipherMode(1536, newRC4)} + cipherModes[InsecureCipherRC4256] = &cipherMode{32, 0, streamCipherMode(1536, newRC4)} + cipherModes[InsecureCipherRC4] = &cipherMode{16, 0, streamCipherMode(0, newRC4)} + // CBC mode is insecure and so is not included in the default config. + // (See https://www.ieee-security.org/TC/SP2013/papers/4977a526.pdf). If absolutely + // needed, it's possible to specify a custom Config to enable it. + // You should expect that an active attacker can recover plaintext if + // you do. + cipherModes[InsecureCipherAES128CBC] = &cipherMode{16, aes.BlockSize, newAESCBCCipher} + cipherModes[InsecureCipherTripleDESCBC] = &cipherMode{24, des.BlockSize, newTripleDESCBCCipher} +} + +// prefixLen is the length of the packet prefix that contains the packet length +// and number of padding bytes. +const prefixLen = 5 + +// streamPacketCipher is a packetCipher using a stream cipher. +type streamPacketCipher struct { + mac hash.Hash + cipher cipher.Stream + etm bool + + // The following members are to avoid per-packet allocations. + prefix [prefixLen]byte + seqNumBytes [4]byte + padding [2 * packetSizeMultiple]byte + packetData []byte + macResult []byte +} + +// readCipherPacket reads and decrypt a single packet from the reader argument. +func (s *streamPacketCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error) { + if _, err := io.ReadFull(r, s.prefix[:]); err != nil { + return nil, err + } + + var encryptedPaddingLength [1]byte + if s.mac != nil && s.etm { + copy(encryptedPaddingLength[:], s.prefix[4:5]) + s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5]) + } else { + s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) + } + + length := binary.BigEndian.Uint32(s.prefix[0:4]) + paddingLength := uint32(s.prefix[4]) + + var macSize uint32 + if s.mac != nil { + s.mac.Reset() + binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) + s.mac.Write(s.seqNumBytes[:]) + if s.etm { + s.mac.Write(s.prefix[:4]) + s.mac.Write(encryptedPaddingLength[:]) + } else { + s.mac.Write(s.prefix[:]) + } + macSize = uint32(s.mac.Size()) + } + + if length <= paddingLength+1 { + return nil, errors.New("ssh: invalid packet length, packet too small") + } + + if length > maxPacket { + return nil, errors.New("ssh: invalid packet length, packet too large") + } + + // the maxPacket check above ensures that length-1+macSize + // does not overflow. + if uint32(cap(s.packetData)) < length-1+macSize { + s.packetData = make([]byte, length-1+macSize) + } else { + s.packetData = s.packetData[:length-1+macSize] + } + + if _, err := io.ReadFull(r, s.packetData); err != nil { + return nil, err + } + mac := s.packetData[length-1:] + data := s.packetData[:length-1] + + if s.mac != nil && s.etm { + s.mac.Write(data) + } + + s.cipher.XORKeyStream(data, data) + + if s.mac != nil { + if !s.etm { + s.mac.Write(data) + } + s.macResult = s.mac.Sum(s.macResult[:0]) + if subtle.ConstantTimeCompare(s.macResult, mac) != 1 { + return nil, errors.New("ssh: MAC failure") + } + } + + return s.packetData[:length-paddingLength-1], nil +} + +// writeCipherPacket encrypts and sends a packet of data to the writer argument +func (s *streamPacketCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + if len(packet) > maxPacket { + return errors.New("ssh: packet too large") + } + + aadlen := 0 + if s.mac != nil && s.etm { + // packet length is not encrypted for EtM modes + aadlen = 4 + } + + paddingLength := packetSizeMultiple - (prefixLen+len(packet)-aadlen)%packetSizeMultiple + if paddingLength < 4 { + paddingLength += packetSizeMultiple + } + + length := len(packet) + 1 + paddingLength + binary.BigEndian.PutUint32(s.prefix[:], uint32(length)) + s.prefix[4] = byte(paddingLength) + padding := s.padding[:paddingLength] + if _, err := io.ReadFull(rand, padding); err != nil { + return err + } + + if s.mac != nil { + s.mac.Reset() + binary.BigEndian.PutUint32(s.seqNumBytes[:], seqNum) + s.mac.Write(s.seqNumBytes[:]) + + if s.etm { + // For EtM algorithms, the packet length must stay unencrypted, + // but the following data (padding length) must be encrypted + s.cipher.XORKeyStream(s.prefix[4:5], s.prefix[4:5]) + } + + s.mac.Write(s.prefix[:]) + + if !s.etm { + // For non-EtM algorithms, the algorithm is applied on unencrypted data + s.mac.Write(packet) + s.mac.Write(padding) + } + } + + if !(s.mac != nil && s.etm) { + // For EtM algorithms, the padding length has already been encrypted + // and the packet length must remain unencrypted + s.cipher.XORKeyStream(s.prefix[:], s.prefix[:]) + } + + s.cipher.XORKeyStream(packet, packet) + s.cipher.XORKeyStream(padding, padding) + + if s.mac != nil && s.etm { + // For EtM algorithms, packet and padding must be encrypted + s.mac.Write(packet) + s.mac.Write(padding) + } + + if _, err := w.Write(s.prefix[:]); err != nil { + return err + } + if _, err := w.Write(packet); err != nil { + return err + } + if _, err := w.Write(padding); err != nil { + return err + } + + if s.mac != nil { + s.macResult = s.mac.Sum(s.macResult[:0]) + if _, err := w.Write(s.macResult); err != nil { + return err + } + } + + return nil +} + +type gcmCipher struct { + aead cipher.AEAD + prefix [4]byte + iv []byte + buf []byte +} + +func newGCMCipher(key, iv, unusedMacKey []byte, unusedAlgs DirectionAlgorithms) (packetCipher, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + aead, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + return &gcmCipher{ + aead: aead, + iv: iv, + }, nil +} + +const gcmTagSize = 16 + +func (c *gcmCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + // Pad out to multiple of 16 bytes. This is different from the + // stream cipher because that encrypts the length too. + padding := byte(packetSizeMultiple - (1+len(packet))%packetSizeMultiple) + if padding < 4 { + padding += packetSizeMultiple + } + + length := uint32(len(packet) + int(padding) + 1) + binary.BigEndian.PutUint32(c.prefix[:], length) + if _, err := w.Write(c.prefix[:]); err != nil { + return err + } + + if cap(c.buf) < int(length) { + c.buf = make([]byte, length) + } else { + c.buf = c.buf[:length] + } + + c.buf[0] = padding + copy(c.buf[1:], packet) + if _, err := io.ReadFull(rand, c.buf[1+len(packet):]); err != nil { + return err + } + c.buf = c.aead.Seal(c.buf[:0], c.iv, c.buf, c.prefix[:]) + if _, err := w.Write(c.buf); err != nil { + return err + } + c.incIV() + + return nil +} + +func (c *gcmCipher) incIV() { + for i := 4 + 7; i >= 4; i-- { + c.iv[i]++ + if c.iv[i] != 0 { + break + } + } +} + +func (c *gcmCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error) { + if _, err := io.ReadFull(r, c.prefix[:]); err != nil { + return nil, err + } + length := binary.BigEndian.Uint32(c.prefix[:]) + if length > maxPacket { + return nil, errors.New("ssh: max packet length exceeded") + } + + if cap(c.buf) < int(length+gcmTagSize) { + c.buf = make([]byte, length+gcmTagSize) + } else { + c.buf = c.buf[:length+gcmTagSize] + } + + if _, err := io.ReadFull(r, c.buf); err != nil { + return nil, err + } + + plain, err := c.aead.Open(c.buf[:0], c.iv, c.buf, c.prefix[:]) + if err != nil { + return nil, err + } + c.incIV() + + if len(plain) == 0 { + return nil, errors.New("ssh: empty packet") + } + + padding := plain[0] + if padding < 4 { + // padding is a byte, so it automatically satisfies + // the maximum size, which is 255. + return nil, fmt.Errorf("ssh: illegal padding %d", padding) + } + + if int(padding+1) >= len(plain) { + return nil, fmt.Errorf("ssh: padding %d too large", padding) + } + plain = plain[1 : length-uint32(padding)] + return plain, nil +} + +// cbcCipher implements aes128-cbc cipher defined in RFC 4253 section 6.1 +type cbcCipher struct { + mac hash.Hash + macSize uint32 + decrypter cipher.BlockMode + encrypter cipher.BlockMode + + // The following members are to avoid per-packet allocations. + seqNumBytes [4]byte + packetData []byte + macResult []byte + + // Amount of data we should still read to hide which + // verification error triggered. + oracleCamouflage uint32 +} + +func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) { + cbc := &cbcCipher{ + mac: macModes[algs.MAC].new(macKey), + decrypter: cipher.NewCBCDecrypter(c, iv), + encrypter: cipher.NewCBCEncrypter(c, iv), + packetData: make([]byte, 1024), + } + if cbc.mac != nil { + cbc.macSize = uint32(cbc.mac.Size()) + } + + return cbc, nil +} + +func newAESCBCCipher(key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + cbc, err := newCBCCipher(c, key, iv, macKey, algs) + if err != nil { + return nil, err + } + + return cbc, nil +} + +func newTripleDESCBCCipher(key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) { + c, err := des.NewTripleDESCipher(key) + if err != nil { + return nil, err + } + + cbc, err := newCBCCipher(c, key, iv, macKey, algs) + if err != nil { + return nil, err + } + + return cbc, nil +} + +func maxUInt32(a, b int) uint32 { + if a > b { + return uint32(a) + } + return uint32(b) +} + +const ( + cbcMinPacketSizeMultiple = 8 + cbcMinPacketSize = 16 + cbcMinPaddingSize = 4 +) + +// cbcError represents a verification error that may leak information. +type cbcError string + +func (e cbcError) Error() string { return string(e) } + +func (c *cbcCipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error) { + p, err := c.readCipherPacketLeaky(seqNum, r) + if err != nil { + if _, ok := err.(cbcError); ok { + // Verification error: read a fixed amount of + // data, to make distinguishing between + // failing MAC and failing length check more + // difficult. + io.CopyN(io.Discard, r, int64(c.oracleCamouflage)) + } + } + return p, err +} + +func (c *cbcCipher) readCipherPacketLeaky(seqNum uint32, r io.Reader) ([]byte, error) { + blockSize := c.decrypter.BlockSize() + + // Read the header, which will include some of the subsequent data in the + // case of block ciphers - this is copied back to the payload later. + // How many bytes of payload/padding will be read with this first read. + firstBlockLength := uint32((prefixLen + blockSize - 1) / blockSize * blockSize) + firstBlock := c.packetData[:firstBlockLength] + if _, err := io.ReadFull(r, firstBlock); err != nil { + return nil, err + } + + c.oracleCamouflage = maxPacket + 4 + c.macSize - firstBlockLength + + c.decrypter.CryptBlocks(firstBlock, firstBlock) + length := binary.BigEndian.Uint32(firstBlock[:4]) + if length > maxPacket { + return nil, cbcError("ssh: packet too large") + } + if length+4 < maxUInt32(cbcMinPacketSize, blockSize) { + // The minimum size of a packet is 16 (or the cipher block size, whichever + // is larger) bytes. + return nil, cbcError("ssh: packet too small") + } + // The length of the packet (including the length field but not the MAC) must + // be a multiple of the block size or 8, whichever is larger. + if (length+4)%maxUInt32(cbcMinPacketSizeMultiple, blockSize) != 0 { + return nil, cbcError("ssh: invalid packet length multiple") + } + + paddingLength := uint32(firstBlock[4]) + if paddingLength < cbcMinPaddingSize || length <= paddingLength+1 { + return nil, cbcError("ssh: invalid packet length") + } + + // Positions within the c.packetData buffer: + macStart := 4 + length + paddingStart := macStart - paddingLength + + // Entire packet size, starting before length, ending at end of mac. + entirePacketSize := macStart + c.macSize + + // Ensure c.packetData is large enough for the entire packet data. + if uint32(cap(c.packetData)) < entirePacketSize { + // Still need to upsize and copy, but this should be rare at runtime, only + // on upsizing the packetData buffer. + c.packetData = make([]byte, entirePacketSize) + copy(c.packetData, firstBlock) + } else { + c.packetData = c.packetData[:entirePacketSize] + } + + n, err := io.ReadFull(r, c.packetData[firstBlockLength:]) + if err != nil { + return nil, err + } + c.oracleCamouflage -= uint32(n) + + remainingCrypted := c.packetData[firstBlockLength:macStart] + c.decrypter.CryptBlocks(remainingCrypted, remainingCrypted) + + mac := c.packetData[macStart:] + if c.mac != nil { + c.mac.Reset() + binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum) + c.mac.Write(c.seqNumBytes[:]) + c.mac.Write(c.packetData[:macStart]) + c.macResult = c.mac.Sum(c.macResult[:0]) + if subtle.ConstantTimeCompare(c.macResult, mac) != 1 { + return nil, cbcError("ssh: MAC failure") + } + } + + return c.packetData[prefixLen:paddingStart], nil +} + +func (c *cbcCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader, packet []byte) error { + effectiveBlockSize := maxUInt32(cbcMinPacketSizeMultiple, c.encrypter.BlockSize()) + + // Length of encrypted portion of the packet (header, payload, padding). + // Enforce minimum padding and packet size. + encLength := maxUInt32(prefixLen+len(packet)+cbcMinPaddingSize, cbcMinPacketSize) + // Enforce block size. + encLength = (encLength + effectiveBlockSize - 1) / effectiveBlockSize * effectiveBlockSize + + length := encLength - 4 + paddingLength := int(length) - (1 + len(packet)) + + // Overall buffer contains: header, payload, padding, mac. + // Space for the MAC is reserved in the capacity but not the slice length. + bufferSize := encLength + c.macSize + if uint32(cap(c.packetData)) < bufferSize { + c.packetData = make([]byte, encLength, bufferSize) + } else { + c.packetData = c.packetData[:encLength] + } + + p := c.packetData + + // Packet header. + binary.BigEndian.PutUint32(p, length) + p = p[4:] + p[0] = byte(paddingLength) + + // Payload. + p = p[1:] + copy(p, packet) + + // Padding. + p = p[len(packet):] + if _, err := io.ReadFull(rand, p); err != nil { + return err + } + + if c.mac != nil { + c.mac.Reset() + binary.BigEndian.PutUint32(c.seqNumBytes[:], seqNum) + c.mac.Write(c.seqNumBytes[:]) + c.mac.Write(c.packetData) + // The MAC is now appended into the capacity reserved for it earlier. + c.packetData = c.mac.Sum(c.packetData) + } + + c.encrypter.CryptBlocks(c.packetData[:encLength], c.packetData[:encLength]) + + if _, err := w.Write(c.packetData); err != nil { + return err + } + + return nil +} + +// chacha20Poly1305Cipher implements the chacha20-poly1305@openssh.com +// AEAD, which is described here: +// +// https://tools.ietf.org/html/draft-josefsson-ssh-chacha20-poly1305-openssh-00 +// +// the methods here also implement padding, which RFC 4253 Section 6 +// also requires of stream ciphers. +type chacha20Poly1305Cipher struct { + lengthKey [32]byte + contentKey [32]byte + buf []byte +} + +func newChaCha20Cipher(key, unusedIV, unusedMACKey []byte, unusedAlgs DirectionAlgorithms) (packetCipher, error) { + if len(key) != 64 { + panic(len(key)) + } + + c := &chacha20Poly1305Cipher{ + buf: make([]byte, 256), + } + + copy(c.contentKey[:], key[:32]) + copy(c.lengthKey[:], key[32:]) + return c, nil +} + +func (c *chacha20Poly1305Cipher) readCipherPacket(seqNum uint32, r io.Reader) ([]byte, error) { + nonce := make([]byte, 12) + binary.BigEndian.PutUint32(nonce[8:], seqNum) + s, err := chacha20.NewUnauthenticatedCipher(c.contentKey[:], nonce) + if err != nil { + return nil, err + } + var polyKey, discardBuf [32]byte + s.XORKeyStream(polyKey[:], polyKey[:]) + s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes + + encryptedLength := c.buf[:4] + if _, err := io.ReadFull(r, encryptedLength); err != nil { + return nil, err + } + + var lenBytes [4]byte + ls, err := chacha20.NewUnauthenticatedCipher(c.lengthKey[:], nonce) + if err != nil { + return nil, err + } + ls.XORKeyStream(lenBytes[:], encryptedLength) + + length := binary.BigEndian.Uint32(lenBytes[:]) + if length > maxPacket { + return nil, errors.New("ssh: invalid packet length, packet too large") + } + + contentEnd := 4 + length + packetEnd := contentEnd + poly1305.TagSize + if uint32(cap(c.buf)) < packetEnd { + c.buf = make([]byte, packetEnd) + copy(c.buf[:], encryptedLength) + } else { + c.buf = c.buf[:packetEnd] + } + + if _, err := io.ReadFull(r, c.buf[4:packetEnd]); err != nil { + return nil, err + } + + var mac [poly1305.TagSize]byte + copy(mac[:], c.buf[contentEnd:packetEnd]) + if !poly1305.Verify(&mac, c.buf[:contentEnd], &polyKey) { + return nil, errors.New("ssh: MAC failure") + } + + plain := c.buf[4:contentEnd] + s.XORKeyStream(plain, plain) + + if len(plain) == 0 { + return nil, errors.New("ssh: empty packet") + } + + padding := plain[0] + if padding < 4 { + // padding is a byte, so it automatically satisfies + // the maximum size, which is 255. + return nil, fmt.Errorf("ssh: illegal padding %d", padding) + } + + if int(padding)+1 >= len(plain) { + return nil, fmt.Errorf("ssh: padding %d too large", padding) + } + + plain = plain[1 : len(plain)-int(padding)] + + return plain, nil +} + +func (c *chacha20Poly1305Cipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader, payload []byte) error { + nonce := make([]byte, 12) + binary.BigEndian.PutUint32(nonce[8:], seqNum) + s, err := chacha20.NewUnauthenticatedCipher(c.contentKey[:], nonce) + if err != nil { + return err + } + var polyKey, discardBuf [32]byte + s.XORKeyStream(polyKey[:], polyKey[:]) + s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes + + // There is no blocksize, so fall back to multiple of 8 byte + // padding, as described in RFC 4253, Sec 6. + const packetSizeMultiple = 8 + + padding := packetSizeMultiple - (1+len(payload))%packetSizeMultiple + if padding < 4 { + padding += packetSizeMultiple + } + + // size (4 bytes), padding (1), payload, padding, tag. + totalLength := 4 + 1 + len(payload) + padding + poly1305.TagSize + if cap(c.buf) < totalLength { + c.buf = make([]byte, totalLength) + } else { + c.buf = c.buf[:totalLength] + } + + binary.BigEndian.PutUint32(c.buf, uint32(1+len(payload)+padding)) + ls, err := chacha20.NewUnauthenticatedCipher(c.lengthKey[:], nonce) + if err != nil { + return err + } + ls.XORKeyStream(c.buf, c.buf[:4]) + c.buf[4] = byte(padding) + copy(c.buf[5:], payload) + packetEnd := 5 + len(payload) + padding + if _, err := io.ReadFull(rand, c.buf[5+len(payload):packetEnd]); err != nil { + return err + } + + s.XORKeyStream(c.buf[4:], c.buf[4:packetEnd]) + + var mac [poly1305.TagSize]byte + poly1305.Sum(&mac, c.buf[:packetEnd], &polyKey) + + copy(c.buf[packetEnd:], mac[:]) + + if _, err := w.Write(c.buf); err != nil { + return err + } + return nil +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/client.go b/cli/vendor/golang.org/x/crypto/ssh/client.go new file mode 100644 index 0000000..3307978 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/client.go @@ -0,0 +1,283 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "net" + "os" + "sync" + "time" +) + +// Client implements a traditional SSH client that supports shells, +// subprocesses, TCP port/streamlocal forwarding and tunneled dialing. +type Client struct { + Conn + + handleForwardsOnce sync.Once // guards calling (*Client).handleForwards + + forwards forwardList // forwarded tcpip connections from the remote side + mu sync.Mutex + channelHandlers map[string]chan NewChannel +} + +// HandleChannelOpen returns a channel on which NewChannel requests +// for the given type are sent. If the type already is being handled, +// nil is returned. The channel is closed when the connection is closed. +func (c *Client) HandleChannelOpen(channelType string) <-chan NewChannel { + c.mu.Lock() + defer c.mu.Unlock() + if c.channelHandlers == nil { + // The SSH channel has been closed. + c := make(chan NewChannel) + close(c) + return c + } + + ch := c.channelHandlers[channelType] + if ch != nil { + return nil + } + + ch = make(chan NewChannel, chanSize) + c.channelHandlers[channelType] = ch + return ch +} + +// NewClient creates a Client on top of the given connection. +func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client { + conn := &Client{ + Conn: c, + channelHandlers: make(map[string]chan NewChannel, 1), + } + + go conn.handleGlobalRequests(reqs) + go conn.handleChannelOpens(chans) + go func() { + conn.Wait() + conn.forwards.closeAll() + }() + return conn +} + +// NewClientConn establishes an authenticated SSH connection using c +// as the underlying transport. The Request and NewChannel channels +// must be serviced or the connection will hang. +func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) { + fullConf := *config + fullConf.SetDefaults() + if fullConf.HostKeyCallback == nil { + c.Close() + return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback") + } + + conn := &connection{ + sshConn: sshConn{conn: c, user: fullConf.User}, + } + + if err := conn.clientHandshake(addr, &fullConf); err != nil { + c.Close() + return nil, nil, nil, fmt.Errorf("ssh: handshake failed: %w", err) + } + conn.mux = newMux(conn.transport) + return conn, conn.mux.incomingChannels, conn.mux.incomingRequests, nil +} + +// clientHandshake performs the client side key exchange. See RFC 4253 Section +// 7. +func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) error { + if config.ClientVersion != "" { + c.clientVersion = []byte(config.ClientVersion) + } else { + c.clientVersion = []byte(packageVersion) + } + var err error + c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion) + if err != nil { + return err + } + + c.transport = newClientTransport( + newTransport(c.sshConn.conn, config.Rand, true /* is client */), + c.clientVersion, c.serverVersion, config, dialAddress, c.sshConn.RemoteAddr()) + if err := c.transport.waitSession(); err != nil { + return err + } + + c.sessionID = c.transport.getSessionID() + c.algorithms = c.transport.getAlgorithms() + return c.clientAuthenticate(config) +} + +// verifyHostKeySignature verifies the host key obtained in the key exchange. +// algo is the negotiated algorithm, and may be a certificate type. +func verifyHostKeySignature(hostKey PublicKey, algo string, result *kexResult) error { + sig, rest, ok := parseSignatureBody(result.Signature) + if len(rest) > 0 || !ok { + return errors.New("ssh: signature parse error") + } + + if a := underlyingAlgo(algo); sig.Format != a { + return fmt.Errorf("ssh: invalid signature algorithm %q, expected %q", sig.Format, a) + } + + return hostKey.Verify(result.H, sig) +} + +// NewSession opens a new Session for this client. (A session is a remote +// execution of a program.) +func (c *Client) NewSession() (*Session, error) { + ch, in, err := c.OpenChannel("session", nil) + if err != nil { + return nil, err + } + return newSession(ch, in) +} + +func (c *Client) handleGlobalRequests(incoming <-chan *Request) { + for r := range incoming { + // This handles keepalive messages and matches + // the behaviour of OpenSSH. + r.Reply(false, nil) + } +} + +// handleChannelOpens channel open messages from the remote side. +func (c *Client) handleChannelOpens(in <-chan NewChannel) { + for ch := range in { + c.mu.Lock() + handler := c.channelHandlers[ch.ChannelType()] + c.mu.Unlock() + + if handler != nil { + handler <- ch + } else { + ch.Reject(UnknownChannelType, fmt.Sprintf("unknown channel type: %v", ch.ChannelType())) + } + } + + c.mu.Lock() + for _, ch := range c.channelHandlers { + close(ch) + } + c.channelHandlers = nil + c.mu.Unlock() +} + +// Dial starts a client connection to the given SSH server. It is a +// convenience function that connects to the given network address, +// initiates the SSH handshake, and then sets up a Client. For access +// to incoming channels and requests, use net.Dial with NewClientConn +// instead. +func Dial(network, addr string, config *ClientConfig) (*Client, error) { + conn, err := net.DialTimeout(network, addr, config.Timeout) + if err != nil { + return nil, err + } + c, chans, reqs, err := NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + return NewClient(c, chans, reqs), nil +} + +// HostKeyCallback is the function type used for verifying server +// keys. A HostKeyCallback must return nil if the host key is OK, or +// an error to reject it. It receives the hostname as passed to Dial +// or NewClientConn. The remote address is the RemoteAddr of the +// net.Conn underlying the SSH connection. +type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error + +// BannerCallback is the function type used for treat the banner sent by +// the server. A BannerCallback receives the message sent by the remote server. +type BannerCallback func(message string) error + +// A ClientConfig structure is used to configure a Client. It must not be +// modified after having been passed to an SSH function. +type ClientConfig struct { + // Config contains configuration that is shared between clients and + // servers. + Config + + // User contains the username to authenticate as. + User string + + // Auth contains possible authentication methods to use with the + // server. Only the first instance of a particular RFC 4252 method will + // be used during authentication. + Auth []AuthMethod + + // HostKeyCallback is called during the cryptographic + // handshake to validate the server's host key. The client + // configuration must supply this callback for the connection + // to succeed. The functions InsecureIgnoreHostKey or + // FixedHostKey can be used for simplistic host key checks. + HostKeyCallback HostKeyCallback + + // BannerCallback is called during the SSH dance to display a custom + // server's message. The client configuration can supply this callback to + // handle it as wished. The function BannerDisplayStderr can be used for + // simplistic display on Stderr. + BannerCallback BannerCallback + + // ClientVersion contains the version identification string that will + // be used for the connection. If empty, a reasonable default is used. + ClientVersion string + + // HostKeyAlgorithms lists the public key algorithms that the client will + // accept from the server for host key authentication, in order of + // preference. If empty, a reasonable default is used. Any + // string returned from a PublicKey.Type method may be used, or + // any of the CertAlgo and KeyAlgo constants. + HostKeyAlgorithms []string + + // Timeout is the maximum amount of time for the TCP connection to establish. + // + // A Timeout of zero means no timeout. + Timeout time.Duration +} + +// InsecureIgnoreHostKey returns a function that can be used for +// ClientConfig.HostKeyCallback to accept any host key. It should +// not be used for production code. +func InsecureIgnoreHostKey() HostKeyCallback { + return func(hostname string, remote net.Addr, key PublicKey) error { + return nil + } +} + +type fixedHostKey struct { + key PublicKey +} + +func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error { + if f.key == nil { + return fmt.Errorf("ssh: required host key was nil") + } + if !bytes.Equal(key.Marshal(), f.key.Marshal()) { + return fmt.Errorf("ssh: host key mismatch") + } + return nil +} + +// FixedHostKey returns a function for use in +// ClientConfig.HostKeyCallback to accept only a specific host key. +func FixedHostKey(key PublicKey) HostKeyCallback { + hk := &fixedHostKey{key} + return hk.check +} + +// BannerDisplayStderr returns a function that can be used for +// ClientConfig.BannerCallback to display banners on os.Stderr. +func BannerDisplayStderr() BannerCallback { + return func(banner string) error { + _, err := os.Stderr.WriteString(banner) + + return err + } +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/client_auth.go b/cli/vendor/golang.org/x/crypto/ssh/client_auth.go new file mode 100644 index 0000000..4f2f75c --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/client_auth.go @@ -0,0 +1,792 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" + "slices" + "strings" +) + +type authResult int + +const ( + authFailure authResult = iota + authPartialSuccess + authSuccess +) + +// clientAuthenticate authenticates with the remote server. See RFC 4252. +func (c *connection) clientAuthenticate(config *ClientConfig) error { + // initiate user auth session + if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil { + return err + } + packet, err := c.transport.readPacket() + if err != nil { + return err + } + // The server may choose to send a SSH_MSG_EXT_INFO at this point (if we + // advertised willingness to receive one, which we always do) or not. See + // RFC 8308, Section 2.4. + extensions := make(map[string][]byte) + if len(packet) > 0 && packet[0] == msgExtInfo { + var extInfo extInfoMsg + if err := Unmarshal(packet, &extInfo); err != nil { + return err + } + payload := extInfo.Payload + for i := uint32(0); i < extInfo.NumExtensions; i++ { + name, rest, ok := parseString(payload) + if !ok { + return parseError(msgExtInfo) + } + value, rest, ok := parseString(rest) + if !ok { + return parseError(msgExtInfo) + } + extensions[string(name)] = value + payload = rest + } + packet, err = c.transport.readPacket() + if err != nil { + return err + } + } + var serviceAccept serviceAcceptMsg + if err := Unmarshal(packet, &serviceAccept); err != nil { + return err + } + + // during the authentication phase the client first attempts the "none" method + // then any untried methods suggested by the server. + var tried []string + var lastMethods []string + + sessionID := c.transport.getSessionID() + for auth := AuthMethod(new(noneAuth)); auth != nil; { + ok, methods, err := auth.auth(sessionID, config.User, c.transport, config.Rand, extensions) + if err != nil { + // On disconnect, return error immediately + if _, ok := err.(*disconnectMsg); ok { + return err + } + // We return the error later if there is no other method left to + // try. + ok = authFailure + } + if ok == authSuccess { + // success + return nil + } else if ok == authFailure { + if m := auth.method(); !slices.Contains(tried, m) { + tried = append(tried, m) + } + } + if methods == nil { + methods = lastMethods + } + lastMethods = methods + + auth = nil + + findNext: + for _, a := range config.Auth { + candidateMethod := a.method() + if slices.Contains(tried, candidateMethod) { + continue + } + for _, meth := range methods { + if meth == candidateMethod { + auth = a + break findNext + } + } + } + + if auth == nil && err != nil { + // We have an error and there are no other authentication methods to + // try, so we return it. + return err + } + } + return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", tried) +} + +// An AuthMethod represents an instance of an RFC 4252 authentication method. +type AuthMethod interface { + // auth authenticates user over transport t. + // Returns true if authentication is successful. + // If authentication is not successful, a []string of alternative + // method names is returned. If the slice is nil, it will be ignored + // and the previous set of possible methods will be reused. + auth(session []byte, user string, p packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error) + + // method returns the RFC 4252 method name. + method() string +} + +// "none" authentication, RFC 4252 section 5.2. +type noneAuth int + +func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) { + if err := c.writePacket(Marshal(&userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: "none", + })); err != nil { + return authFailure, nil, err + } + + return handleAuthResponse(c) +} + +func (n *noneAuth) method() string { + return "none" +} + +// passwordCallback is an AuthMethod that fetches the password through +// a function call, e.g. by prompting the user. +type passwordCallback func() (password string, err error) + +func (cb passwordCallback) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) { + type passwordAuthMsg struct { + User string `sshtype:"50"` + Service string + Method string + Reply bool + Password string + } + + pw, err := cb() + // REVIEW NOTE: is there a need to support skipping a password attempt? + // The program may only find out that the user doesn't have a password + // when prompting. + if err != nil { + return authFailure, nil, err + } + + if err := c.writePacket(Marshal(&passwordAuthMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + Reply: false, + Password: pw, + })); err != nil { + return authFailure, nil, err + } + + return handleAuthResponse(c) +} + +func (cb passwordCallback) method() string { + return "password" +} + +// Password returns an AuthMethod using the given password. +func Password(secret string) AuthMethod { + return passwordCallback(func() (string, error) { return secret, nil }) +} + +// PasswordCallback returns an AuthMethod that uses a callback for +// fetching a password. +func PasswordCallback(prompt func() (secret string, err error)) AuthMethod { + return passwordCallback(prompt) +} + +type publickeyAuthMsg struct { + User string `sshtype:"50"` + Service string + Method string + // HasSig indicates to the receiver packet that the auth request is signed and + // should be used for authentication of the request. + HasSig bool + Algoname string + PubKey []byte + // Sig is tagged with "rest" so Marshal will exclude it during + // validateKey + Sig []byte `ssh:"rest"` +} + +// publicKeyCallback is an AuthMethod that uses a set of key +// pairs for authentication. +type publicKeyCallback func() ([]Signer, error) + +func (cb publicKeyCallback) method() string { + return "publickey" +} + +func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiAlgorithmSigner, string, error) { + var as MultiAlgorithmSigner + keyFormat := signer.PublicKey().Type() + + // If the signer implements MultiAlgorithmSigner we use the algorithms it + // support, if it implements AlgorithmSigner we assume it supports all + // algorithms, otherwise only the key format one. + switch s := signer.(type) { + case MultiAlgorithmSigner: + as = s + case AlgorithmSigner: + as = &multiAlgorithmSigner{ + AlgorithmSigner: s, + supportedAlgorithms: algorithmsForKeyFormat(underlyingAlgo(keyFormat)), + } + default: + as = &multiAlgorithmSigner{ + AlgorithmSigner: algorithmSignerWrapper{signer}, + supportedAlgorithms: []string{underlyingAlgo(keyFormat)}, + } + } + + getFallbackAlgo := func() (string, error) { + // Fallback to use if there is no "server-sig-algs" extension or a + // common algorithm cannot be found. We use the public key format if the + // MultiAlgorithmSigner supports it, otherwise we return an error. + if !slices.Contains(as.Algorithms(), underlyingAlgo(keyFormat)) { + return "", fmt.Errorf("ssh: no common public key signature algorithm, server only supports %q for key type %q, signer only supports %v", + underlyingAlgo(keyFormat), keyFormat, as.Algorithms()) + } + return keyFormat, nil + } + + extPayload, ok := extensions["server-sig-algs"] + if !ok { + // If there is no "server-sig-algs" extension use the fallback + // algorithm. + algo, err := getFallbackAlgo() + return as, algo, err + } + + // The server-sig-algs extension only carries underlying signature + // algorithm, but we are trying to select a protocol-level public key + // algorithm, which might be a certificate type. Extend the list of server + // supported algorithms to include the corresponding certificate algorithms. + serverAlgos := strings.Split(string(extPayload), ",") + for _, algo := range serverAlgos { + if certAlgo, ok := certificateAlgo(algo); ok { + serverAlgos = append(serverAlgos, certAlgo) + } + } + + // Filter algorithms based on those supported by MultiAlgorithmSigner. + // Iterate over the signer's algorithms first to preserve its preference order. + supportedKeyAlgos := algorithmsForKeyFormat(keyFormat) + var keyAlgos []string + for _, signerAlgo := range as.Algorithms() { + if idx := slices.IndexFunc(supportedKeyAlgos, func(algo string) bool { + return underlyingAlgo(algo) == signerAlgo + }); idx >= 0 { + keyAlgos = append(keyAlgos, supportedKeyAlgos[idx]) + } + } + + algo, err := findCommon("public key signature algorithm", keyAlgos, serverAlgos, true) + if err != nil { + // If there is no overlap, return the fallback algorithm to support + // servers that fail to list all supported algorithms. + algo, err := getFallbackAlgo() + return as, algo, err + } + return as, algo, nil +} + +func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error) { + // Authentication is performed by sending an enquiry to test if a key is + // acceptable to the remote. If the key is acceptable, the client will + // attempt to authenticate with the valid key. If not the client will repeat + // the process with the remaining keys. + + signers, err := cb() + if err != nil { + return authFailure, nil, err + } + var methods []string + var errSigAlgo error + + origSignersLen := len(signers) + for idx := 0; idx < len(signers); idx++ { + signer := signers[idx] + pub := signer.PublicKey() + as, algo, err := pickSignatureAlgorithm(signer, extensions) + if err != nil && errSigAlgo == nil { + // If we cannot negotiate a signature algorithm store the first + // error so we can return it to provide a more meaningful message if + // no other signers work. + errSigAlgo = err + continue + } + ok, err := validateKey(pub, algo, user, c) + if err != nil { + return authFailure, nil, err + } + // OpenSSH 7.2-7.7 advertises support for rsa-sha2-256 and rsa-sha2-512 + // in the "server-sig-algs" extension but doesn't support these + // algorithms for certificate authentication, so if the server rejects + // the key try to use the obtained algorithm as if "server-sig-algs" had + // not been implemented if supported from the algorithm signer. + if !ok && idx < origSignersLen && isRSACert(algo) && algo != CertAlgoRSAv01 { + if slices.Contains(as.Algorithms(), KeyAlgoRSA) { + // We retry using the compat algorithm after all signers have + // been tried normally. + signers = append(signers, &multiAlgorithmSigner{ + AlgorithmSigner: as, + supportedAlgorithms: []string{KeyAlgoRSA}, + }) + } + } + if !ok { + continue + } + + pubKey := pub.Marshal() + data := buildDataSignedForAuth(session, userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + }, algo, pubKey) + sign, err := as.SignWithAlgorithm(rand, data, underlyingAlgo(algo)) + if err != nil { + return authFailure, nil, err + } + + // manually wrap the serialized signature in a string + s := Marshal(sign) + sig := make([]byte, stringLength(len(s))) + marshalString(sig, s) + msg := publickeyAuthMsg{ + User: user, + Service: serviceSSH, + Method: cb.method(), + HasSig: true, + Algoname: algo, + PubKey: pubKey, + Sig: sig, + } + p := Marshal(&msg) + if err := c.writePacket(p); err != nil { + return authFailure, nil, err + } + var success authResult + success, methods, err = handleAuthResponse(c) + if err != nil { + return authFailure, nil, err + } + + // If authentication succeeds or the list of available methods does not + // contain the "publickey" method, do not attempt to authenticate with any + // other keys. According to RFC 4252 Section 7, the latter can occur when + // additional authentication methods are required. + if success == authSuccess || !slices.Contains(methods, cb.method()) { + return success, methods, err + } + } + + return authFailure, methods, errSigAlgo +} + +// validateKey validates the key provided is acceptable to the server. +func validateKey(key PublicKey, algo string, user string, c packetConn) (bool, error) { + pubKey := key.Marshal() + msg := publickeyAuthMsg{ + User: user, + Service: serviceSSH, + Method: "publickey", + HasSig: false, + Algoname: algo, + PubKey: pubKey, + } + if err := c.writePacket(Marshal(&msg)); err != nil { + return false, err + } + + return confirmKeyAck(key, c) +} + +func confirmKeyAck(key PublicKey, c packetConn) (bool, error) { + pubKey := key.Marshal() + + for { + packet, err := c.readPacket() + if err != nil { + return false, err + } + switch packet[0] { + case msgUserAuthBanner: + if err := handleBannerResponse(c, packet); err != nil { + return false, err + } + case msgUserAuthPubKeyOk: + var msg userAuthPubKeyOkMsg + if err := Unmarshal(packet, &msg); err != nil { + return false, err + } + // According to RFC 4252 Section 7 the algorithm in + // SSH_MSG_USERAUTH_PK_OK should match that of the request but some + // servers send the key type instead. OpenSSH allows any algorithm + // that matches the public key, so we do the same. + // https://github.com/openssh/openssh-portable/blob/86bdd385/sshconnect2.c#L709 + if !slices.Contains(algorithmsForKeyFormat(key.Type()), msg.Algo) { + return false, nil + } + if !bytes.Equal(msg.PubKey, pubKey) { + return false, nil + } + return true, nil + case msgUserAuthFailure: + return false, nil + default: + return false, unexpectedMessageError(msgUserAuthPubKeyOk, packet[0]) + } + } +} + +// PublicKeys returns an AuthMethod that uses the given key +// pairs. +func PublicKeys(signers ...Signer) AuthMethod { + return publicKeyCallback(func() ([]Signer, error) { return signers, nil }) +} + +// PublicKeysCallback returns an AuthMethod that runs the given +// function to obtain a list of key pairs. +func PublicKeysCallback(getSigners func() (signers []Signer, err error)) AuthMethod { + return publicKeyCallback(getSigners) +} + +// handleAuthResponse returns whether the preceding authentication request succeeded +// along with a list of remaining authentication methods to try next and +// an error if an unexpected response was received. +func handleAuthResponse(c packetConn) (authResult, []string, error) { + gotMsgExtInfo := false + for { + packet, err := c.readPacket() + if err != nil { + return authFailure, nil, err + } + + switch packet[0] { + case msgUserAuthBanner: + if err := handleBannerResponse(c, packet); err != nil { + return authFailure, nil, err + } + case msgExtInfo: + // Ignore post-authentication RFC 8308 extensions, once. + if gotMsgExtInfo { + return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0]) + } + gotMsgExtInfo = true + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return authFailure, nil, err + } + if msg.PartialSuccess { + return authPartialSuccess, msg.Methods, nil + } + return authFailure, msg.Methods, nil + case msgUserAuthSuccess: + return authSuccess, nil, nil + default: + return authFailure, nil, unexpectedMessageError(msgUserAuthSuccess, packet[0]) + } + } +} + +func handleBannerResponse(c packetConn, packet []byte) error { + var msg userAuthBannerMsg + if err := Unmarshal(packet, &msg); err != nil { + return err + } + + transport, ok := c.(*handshakeTransport) + if !ok { + return nil + } + + if transport.bannerCallback != nil { + return transport.bannerCallback(msg.Message) + } + + return nil +} + +// KeyboardInteractiveChallenge should print questions, optionally +// disabling echoing (e.g. for passwords), and return all the answers. +// Challenge may be called multiple times in a single session. After +// successful authentication, the server may send a challenge with no +// questions, for which the name and instruction messages should be +// printed. RFC 4256 section 3.3 details how the UI should behave for +// both CLI and GUI environments. +type KeyboardInteractiveChallenge func(name, instruction string, questions []string, echos []bool) (answers []string, err error) + +// KeyboardInteractive returns an AuthMethod using a prompt/response +// sequence controlled by the server. +func KeyboardInteractive(challenge KeyboardInteractiveChallenge) AuthMethod { + return challenge +} + +func (cb KeyboardInteractiveChallenge) method() string { + return "keyboard-interactive" +} + +func (cb KeyboardInteractiveChallenge) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) { + type initiateMsg struct { + User string `sshtype:"50"` + Service string + Method string + Language string + Submethods string + } + + if err := c.writePacket(Marshal(&initiateMsg{ + User: user, + Service: serviceSSH, + Method: "keyboard-interactive", + })); err != nil { + return authFailure, nil, err + } + + gotMsgExtInfo := false + gotUserAuthInfoRequest := false + for { + packet, err := c.readPacket() + if err != nil { + return authFailure, nil, err + } + + // like handleAuthResponse, but with less options. + switch packet[0] { + case msgUserAuthBanner: + if err := handleBannerResponse(c, packet); err != nil { + return authFailure, nil, err + } + continue + case msgExtInfo: + // Ignore post-authentication RFC 8308 extensions, once. + if gotMsgExtInfo { + return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0]) + } + gotMsgExtInfo = true + continue + case msgUserAuthInfoRequest: + // OK + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return authFailure, nil, err + } + if msg.PartialSuccess { + return authPartialSuccess, msg.Methods, nil + } + if !gotUserAuthInfoRequest { + return authFailure, msg.Methods, unexpectedMessageError(msgUserAuthInfoRequest, packet[0]) + } + return authFailure, msg.Methods, nil + case msgUserAuthSuccess: + return authSuccess, nil, nil + default: + return authFailure, nil, unexpectedMessageError(msgUserAuthInfoRequest, packet[0]) + } + + var msg userAuthInfoRequestMsg + if err := Unmarshal(packet, &msg); err != nil { + return authFailure, nil, err + } + gotUserAuthInfoRequest = true + + // Manually unpack the prompt/echo pairs. + rest := msg.Prompts + var prompts []string + var echos []bool + for i := 0; i < int(msg.NumPrompts); i++ { + prompt, r, ok := parseString(rest) + if !ok || len(r) == 0 { + return authFailure, nil, errors.New("ssh: prompt format error") + } + prompts = append(prompts, string(prompt)) + echos = append(echos, r[0] != 0) + rest = r[1:] + } + + if len(rest) != 0 { + return authFailure, nil, errors.New("ssh: extra data following keyboard-interactive pairs") + } + + answers, err := cb(msg.Name, msg.Instruction, prompts, echos) + if err != nil { + return authFailure, nil, err + } + + if len(answers) != len(prompts) { + return authFailure, nil, fmt.Errorf("ssh: incorrect number of answers from keyboard-interactive callback %d (expected %d)", len(answers), len(prompts)) + } + responseLength := 1 + 4 + for _, a := range answers { + responseLength += stringLength(len(a)) + } + serialized := make([]byte, responseLength) + p := serialized + p[0] = msgUserAuthInfoResponse + p = p[1:] + p = marshalUint32(p, uint32(len(answers))) + for _, a := range answers { + p = marshalString(p, []byte(a)) + } + + if err := c.writePacket(serialized); err != nil { + return authFailure, nil, err + } + } +} + +type retryableAuthMethod struct { + authMethod AuthMethod + maxTries int +} + +func (r *retryableAuthMethod) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (ok authResult, methods []string, err error) { + for i := 0; r.maxTries <= 0 || i < r.maxTries; i++ { + ok, methods, err = r.authMethod.auth(session, user, c, rand, extensions) + if ok != authFailure || err != nil { // either success, partial success or error terminate + return ok, methods, err + } + } + return ok, methods, err +} + +func (r *retryableAuthMethod) method() string { + return r.authMethod.method() +} + +// RetryableAuthMethod is a decorator for other auth methods enabling them to +// be retried up to maxTries before considering that AuthMethod itself failed. +// If maxTries is <= 0, will retry indefinitely +// +// This is useful for interactive clients using challenge/response type +// authentication (e.g. Keyboard-Interactive, Password, etc) where the user +// could mistype their response resulting in the server issuing a +// SSH_MSG_USERAUTH_FAILURE (rfc4252 #8 [password] and rfc4256 #3.4 +// [keyboard-interactive]); Without this decorator, the non-retryable +// AuthMethod would be removed from future consideration, and never tried again +// (and so the user would never be able to retry their entry). +func RetryableAuthMethod(auth AuthMethod, maxTries int) AuthMethod { + return &retryableAuthMethod{authMethod: auth, maxTries: maxTries} +} + +// GSSAPIWithMICAuthMethod is an AuthMethod with "gssapi-with-mic" authentication. +// See RFC 4462 section 3 +// gssAPIClient is implementation of the GSSAPIClient interface, see the definition of the interface for details. +// target is the server host you want to log in to. +func GSSAPIWithMICAuthMethod(gssAPIClient GSSAPIClient, target string) AuthMethod { + if gssAPIClient == nil { + panic("gss-api client must be not nil with enable gssapi-with-mic") + } + return &gssAPIWithMICCallback{gssAPIClient: gssAPIClient, target: target} +} + +type gssAPIWithMICCallback struct { + gssAPIClient GSSAPIClient + target string +} + +func (g *gssAPIWithMICCallback) auth(session []byte, user string, c packetConn, rand io.Reader, _ map[string][]byte) (authResult, []string, error) { + m := &userAuthRequestMsg{ + User: user, + Service: serviceSSH, + Method: g.method(), + } + // The GSS-API authentication method is initiated when the client sends an SSH_MSG_USERAUTH_REQUEST. + // See RFC 4462 section 3.2. + m.Payload = appendU32(m.Payload, 1) + m.Payload = appendString(m.Payload, string(krb5OID)) + if err := c.writePacket(Marshal(m)); err != nil { + return authFailure, nil, err + } + // The server responds to the SSH_MSG_USERAUTH_REQUEST with either an + // SSH_MSG_USERAUTH_FAILURE if none of the mechanisms are supported or + // with an SSH_MSG_USERAUTH_GSSAPI_RESPONSE. + // See RFC 4462 section 3.3. + // OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication,so I don't want to check + // selected mech if it is valid. + packet, err := c.readPacket() + if err != nil { + return authFailure, nil, err + } + userAuthGSSAPIResp := &userAuthGSSAPIResponse{} + if err := Unmarshal(packet, userAuthGSSAPIResp); err != nil { + return authFailure, nil, err + } + // Start the loop into the exchange token. + // See RFC 4462 section 3.4. + var token []byte + defer g.gssAPIClient.DeleteSecContext() + for { + // Initiates the establishment of a security context between the application and a remote peer. + nextToken, needContinue, err := g.gssAPIClient.InitSecContext("host@"+g.target, token, false) + if err != nil { + return authFailure, nil, err + } + if len(nextToken) > 0 { + if err := c.writePacket(Marshal(&userAuthGSSAPIToken{ + Token: nextToken, + })); err != nil { + return authFailure, nil, err + } + } + if !needContinue { + break + } + packet, err = c.readPacket() + if err != nil { + return authFailure, nil, err + } + switch packet[0] { + case msgUserAuthFailure: + var msg userAuthFailureMsg + if err := Unmarshal(packet, &msg); err != nil { + return authFailure, nil, err + } + if msg.PartialSuccess { + return authPartialSuccess, msg.Methods, nil + } + return authFailure, msg.Methods, nil + case msgUserAuthGSSAPIError: + userAuthGSSAPIErrorResp := &userAuthGSSAPIError{} + if err := Unmarshal(packet, userAuthGSSAPIErrorResp); err != nil { + return authFailure, nil, err + } + return authFailure, nil, fmt.Errorf("GSS-API Error:\n"+ + "Major Status: %d\n"+ + "Minor Status: %d\n"+ + "Error Message: %s\n", userAuthGSSAPIErrorResp.MajorStatus, userAuthGSSAPIErrorResp.MinorStatus, + userAuthGSSAPIErrorResp.Message) + case msgUserAuthGSSAPIToken: + userAuthGSSAPITokenReq := &userAuthGSSAPIToken{} + if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil { + return authFailure, nil, err + } + token = userAuthGSSAPITokenReq.Token + } + } + // Binding Encryption Keys. + // See RFC 4462 section 3.5. + micField := buildMIC(string(session), user, "ssh-connection", "gssapi-with-mic") + micToken, err := g.gssAPIClient.GetMIC(micField) + if err != nil { + return authFailure, nil, err + } + if err := c.writePacket(Marshal(&userAuthGSSAPIMIC{ + MIC: micToken, + })); err != nil { + return authFailure, nil, err + } + return handleAuthResponse(c) +} + +func (g *gssAPIWithMICCallback) method() string { + return "gssapi-with-mic" +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/common.go b/cli/vendor/golang.org/x/crypto/ssh/common.go new file mode 100644 index 0000000..2e44e9c --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/common.go @@ -0,0 +1,727 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto" + "crypto/fips140" + "crypto/rand" + "fmt" + "io" + "math" + "slices" + "sync" + + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" +) + +// These are string constants in the SSH protocol. +const ( + compressionNone = "none" + serviceUserAuth = "ssh-userauth" + serviceSSH = "ssh-connection" +) + +// The ciphers currently or previously implemented by this library, to use in +// [Config.Ciphers]. For a list, see the [Algorithms.Ciphers] returned by +// [SupportedAlgorithms] or [InsecureAlgorithms]. +const ( + CipherAES128GCM = "aes128-gcm@openssh.com" + CipherAES256GCM = "aes256-gcm@openssh.com" + CipherChaCha20Poly1305 = "chacha20-poly1305@openssh.com" + CipherAES128CTR = "aes128-ctr" + CipherAES192CTR = "aes192-ctr" + CipherAES256CTR = "aes256-ctr" + InsecureCipherAES128CBC = "aes128-cbc" + InsecureCipherTripleDESCBC = "3des-cbc" + InsecureCipherRC4 = "arcfour" + InsecureCipherRC4128 = "arcfour128" + InsecureCipherRC4256 = "arcfour256" +) + +// The key exchanges currently or previously implemented by this library, to use +// in [Config.KeyExchanges]. For a list, see the +// [Algorithms.KeyExchanges] returned by [SupportedAlgorithms] or +// [InsecureAlgorithms]. +const ( + InsecureKeyExchangeDH1SHA1 = "diffie-hellman-group1-sha1" + InsecureKeyExchangeDH14SHA1 = "diffie-hellman-group14-sha1" + KeyExchangeDH14SHA256 = "diffie-hellman-group14-sha256" + KeyExchangeDH16SHA512 = "diffie-hellman-group16-sha512" + KeyExchangeECDHP256 = "ecdh-sha2-nistp256" + KeyExchangeECDHP384 = "ecdh-sha2-nistp384" + KeyExchangeECDHP521 = "ecdh-sha2-nistp521" + KeyExchangeCurve25519 = "curve25519-sha256" + InsecureKeyExchangeDHGEXSHA1 = "diffie-hellman-group-exchange-sha1" + KeyExchangeDHGEXSHA256 = "diffie-hellman-group-exchange-sha256" + // KeyExchangeMLKEM768X25519 is supported from Go 1.24. + KeyExchangeMLKEM768X25519 = "mlkem768x25519-sha256" + + // An alias for KeyExchangeCurve25519SHA256. This kex ID will be added if + // KeyExchangeCurve25519SHA256 is requested for backward compatibility with + // OpenSSH versions up to 7.2. + keyExchangeCurve25519LibSSH = "curve25519-sha256@libssh.org" +) + +// The message authentication code (MAC) currently or previously implemented by +// this library, to use in [Config.MACs]. For a list, see the +// [Algorithms.MACs] returned by [SupportedAlgorithms] or +// [InsecureAlgorithms]. +const ( + HMACSHA256ETM = "hmac-sha2-256-etm@openssh.com" + HMACSHA512ETM = "hmac-sha2-512-etm@openssh.com" + HMACSHA256 = "hmac-sha2-256" + HMACSHA512 = "hmac-sha2-512" + HMACSHA1 = "hmac-sha1" + InsecureHMACSHA196 = "hmac-sha1-96" +) + +var ( + // supportedKexAlgos specifies key-exchange algorithms implemented by this + // package in preference order, excluding those with security issues. + supportedKexAlgos = []string{ + KeyExchangeMLKEM768X25519, + KeyExchangeCurve25519, + KeyExchangeECDHP256, + KeyExchangeECDHP384, + KeyExchangeECDHP521, + KeyExchangeDH14SHA256, + KeyExchangeDH16SHA512, + KeyExchangeDHGEXSHA256, + } + // defaultKexAlgos specifies the default preference for key-exchange + // algorithms in preference order. + defaultKexAlgos = []string{ + KeyExchangeMLKEM768X25519, + KeyExchangeCurve25519, + KeyExchangeECDHP256, + KeyExchangeECDHP384, + KeyExchangeECDHP521, + KeyExchangeDH14SHA256, + InsecureKeyExchangeDH14SHA1, + } + // insecureKexAlgos specifies key-exchange algorithms implemented by this + // package and which have security issues. + insecureKexAlgos = []string{ + InsecureKeyExchangeDH14SHA1, + InsecureKeyExchangeDH1SHA1, + InsecureKeyExchangeDHGEXSHA1, + } + // supportedCiphers specifies cipher algorithms implemented by this package + // in preference order, excluding those with security issues. + supportedCiphers = []string{ + CipherAES128GCM, + CipherAES256GCM, + CipherChaCha20Poly1305, + CipherAES128CTR, + CipherAES192CTR, + CipherAES256CTR, + } + // defaultCiphers specifies the default preference for ciphers algorithms + // in preference order. + defaultCiphers = supportedCiphers + // insecureCiphers specifies cipher algorithms implemented by this + // package and which have security issues. + insecureCiphers = []string{ + InsecureCipherAES128CBC, + InsecureCipherTripleDESCBC, + InsecureCipherRC4256, + InsecureCipherRC4128, + InsecureCipherRC4, + } + // supportedMACs specifies MAC algorithms implemented by this package in + // preference order, excluding those with security issues. + supportedMACs = []string{ + HMACSHA256ETM, + HMACSHA512ETM, + HMACSHA256, + HMACSHA512, + HMACSHA1, + } + // defaultMACs specifies the default preference for MAC algorithms in + // preference order. + defaultMACs = []string{ + HMACSHA256ETM, + HMACSHA512ETM, + HMACSHA256, + HMACSHA512, + HMACSHA1, + InsecureHMACSHA196, + } + // insecureMACs specifies MAC algorithms implemented by this + // package and which have security issues. + insecureMACs = []string{ + InsecureHMACSHA196, + } + // supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. + // methods of authenticating servers) implemented by this package in + // preference order, excluding those with security issues. + supportedHostKeyAlgos = []string{ + CertAlgoRSASHA256v01, + CertAlgoRSASHA512v01, + CertAlgoECDSA256v01, + CertAlgoECDSA384v01, + CertAlgoECDSA521v01, + CertAlgoED25519v01, + KeyAlgoRSASHA256, + KeyAlgoRSASHA512, + KeyAlgoECDSA256, + KeyAlgoECDSA384, + KeyAlgoECDSA521, + KeyAlgoED25519, + } + // defaultHostKeyAlgos specifies the default preference for host-key + // algorithms in preference order. + defaultHostKeyAlgos = []string{ + CertAlgoRSASHA256v01, + CertAlgoRSASHA512v01, + CertAlgoRSAv01, + InsecureCertAlgoDSAv01, + CertAlgoECDSA256v01, + CertAlgoECDSA384v01, + CertAlgoECDSA521v01, + CertAlgoED25519v01, + KeyAlgoECDSA256, + KeyAlgoECDSA384, + KeyAlgoECDSA521, + KeyAlgoRSASHA256, + KeyAlgoRSASHA512, + KeyAlgoRSA, + InsecureKeyAlgoDSA, + KeyAlgoED25519, + } + // insecureHostKeyAlgos specifies host-key algorithms implemented by this + // package and which have security issues. + insecureHostKeyAlgos = []string{ + KeyAlgoRSA, + InsecureKeyAlgoDSA, + CertAlgoRSAv01, + InsecureCertAlgoDSAv01, + } + // supportedPubKeyAuthAlgos specifies the supported client public key + // authentication algorithms. Note that this doesn't include certificate + // types since those use the underlying algorithm. Order is irrelevant. + supportedPubKeyAuthAlgos = []string{ + KeyAlgoED25519, + KeyAlgoSKED25519, + KeyAlgoSKECDSA256, + KeyAlgoECDSA256, + KeyAlgoECDSA384, + KeyAlgoECDSA521, + KeyAlgoRSASHA256, + KeyAlgoRSASHA512, + } + + // defaultPubKeyAuthAlgos specifies the preferred client public key + // authentication algorithms. This list is sent to the client if it supports + // the server-sig-algs extension. Order is irrelevant. + defaultPubKeyAuthAlgos = []string{ + KeyAlgoED25519, + KeyAlgoSKED25519, + KeyAlgoSKECDSA256, + KeyAlgoECDSA256, + KeyAlgoECDSA384, + KeyAlgoECDSA521, + KeyAlgoRSASHA256, + KeyAlgoRSASHA512, + KeyAlgoRSA, + InsecureKeyAlgoDSA, + } + // insecurePubKeyAuthAlgos specifies client public key authentication + // algorithms implemented by this package and which have security issues. + insecurePubKeyAuthAlgos = []string{ + KeyAlgoRSA, + InsecureKeyAlgoDSA, + } +) + +// NegotiatedAlgorithms defines algorithms negotiated between client and server. +type NegotiatedAlgorithms struct { + KeyExchange string + HostKey string + Read DirectionAlgorithms + Write DirectionAlgorithms +} + +// Algorithms defines a set of algorithms that can be configured in the client +// or server config for negotiation during a handshake. +type Algorithms struct { + KeyExchanges []string + Ciphers []string + MACs []string + HostKeys []string + PublicKeyAuths []string +} + +func init() { + if fips140.Enabled() { + defaultHostKeyAlgos = slices.DeleteFunc(defaultHostKeyAlgos, func(algo string) bool { + _, err := hashFunc(underlyingAlgo(algo)) + return err != nil + }) + defaultPubKeyAuthAlgos = slices.DeleteFunc(defaultPubKeyAuthAlgos, func(algo string) bool { + _, err := hashFunc(underlyingAlgo(algo)) + return err != nil + }) + } +} + +func hashFunc(format string) (crypto.Hash, error) { + switch format { + case KeyAlgoRSASHA256, KeyAlgoECDSA256, KeyAlgoSKED25519, KeyAlgoSKECDSA256: + return crypto.SHA256, nil + case KeyAlgoECDSA384: + return crypto.SHA384, nil + case KeyAlgoRSASHA512, KeyAlgoECDSA521: + return crypto.SHA512, nil + case KeyAlgoED25519: + // KeyAlgoED25519 doesn't pre-hash. + return 0, nil + case KeyAlgoRSA, InsecureKeyAlgoDSA: + if fips140.Enabled() { + return 0, fmt.Errorf("ssh: hash algorithm for format %q not allowed in FIPS 140 mode", format) + } + return crypto.SHA1, nil + default: + return 0, fmt.Errorf("ssh: hash algorithm for format %q not mapped", format) + } +} + +// SupportedAlgorithms returns algorithms currently implemented by this package, +// excluding those with security issues, which are returned by +// InsecureAlgorithms. The algorithms listed here are in preference order. +func SupportedAlgorithms() Algorithms { + return Algorithms{ + Ciphers: slices.Clone(supportedCiphers), + MACs: slices.Clone(supportedMACs), + KeyExchanges: slices.Clone(supportedKexAlgos), + HostKeys: slices.Clone(supportedHostKeyAlgos), + PublicKeyAuths: slices.Clone(supportedPubKeyAuthAlgos), + } +} + +// InsecureAlgorithms returns algorithms currently implemented by this package +// and which have security issues. +func InsecureAlgorithms() Algorithms { + return Algorithms{ + KeyExchanges: slices.Clone(insecureKexAlgos), + Ciphers: slices.Clone(insecureCiphers), + MACs: slices.Clone(insecureMACs), + HostKeys: slices.Clone(insecureHostKeyAlgos), + PublicKeyAuths: slices.Clone(insecurePubKeyAuthAlgos), + } +} + +var supportedCompressions = []string{compressionNone} + +// algorithmsForKeyFormat returns the supported signature algorithms for a given +// public key format (PublicKey.Type), in order of preference. See RFC 8332, +// Section 2. See also the note in sendKexInit on backwards compatibility. +func algorithmsForKeyFormat(keyFormat string) []string { + switch keyFormat { + case KeyAlgoRSA: + return []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512, KeyAlgoRSA} + case CertAlgoRSAv01: + return []string{CertAlgoRSASHA256v01, CertAlgoRSASHA512v01, CertAlgoRSAv01} + default: + return []string{keyFormat} + } +} + +// keyFormatForAlgorithm returns the key format corresponding to the given +// signature algorithm. It returns an empty string if the signature algorithm is +// invalid or unsupported. +func keyFormatForAlgorithm(sigAlgo string) string { + switch sigAlgo { + case KeyAlgoRSA, KeyAlgoRSASHA256, KeyAlgoRSASHA512: + return KeyAlgoRSA + case CertAlgoRSAv01, CertAlgoRSASHA256v01, CertAlgoRSASHA512v01: + return CertAlgoRSAv01 + case KeyAlgoED25519, + KeyAlgoSKED25519, + KeyAlgoSKECDSA256, + KeyAlgoECDSA256, + KeyAlgoECDSA384, + KeyAlgoECDSA521, + InsecureKeyAlgoDSA, + InsecureCertAlgoDSAv01, + CertAlgoECDSA256v01, + CertAlgoECDSA384v01, + CertAlgoECDSA521v01, + CertAlgoSKECDSA256v01, + CertAlgoED25519v01, + CertAlgoSKED25519v01: + return sigAlgo + default: + return "" + } +} + +// isRSA returns whether algo is a supported RSA algorithm, including certificate +// algorithms. +func isRSA(algo string) bool { + algos := algorithmsForKeyFormat(KeyAlgoRSA) + return slices.Contains(algos, underlyingAlgo(algo)) +} + +func isRSACert(algo string) bool { + _, ok := certKeyAlgoNames[algo] + if !ok { + return false + } + return isRSA(algo) +} + +// unexpectedMessageError results when the SSH message that we received didn't +// match what we wanted. +func unexpectedMessageError(expected, got uint8) error { + return fmt.Errorf("ssh: unexpected message type %d (expected %d)", got, expected) +} + +// parseError results from a malformed SSH message. +func parseError(tag uint8) error { + return fmt.Errorf("ssh: parse error in message type %d", tag) +} + +func findCommon(what string, client []string, server []string, isClient bool) (string, error) { + for _, c := range client { + for _, s := range server { + if c == s { + return c, nil + } + } + } + err := &AlgorithmNegotiationError{ + What: what, + } + if isClient { + err.SupportedAlgorithms = client + err.RequestedAlgorithms = server + } else { + err.SupportedAlgorithms = server + err.RequestedAlgorithms = client + } + return "", err +} + +// AlgorithmNegotiationError defines the error returned if the client and the +// server cannot agree on an algorithm for key exchange, host key, cipher, MAC. +type AlgorithmNegotiationError struct { + What string + // RequestedAlgorithms lists the algorithms supported by the peer. + RequestedAlgorithms []string + // SupportedAlgorithms lists the algorithms supported on our side. + SupportedAlgorithms []string +} + +func (a *AlgorithmNegotiationError) Error() string { + return fmt.Sprintf("ssh: no common algorithm for %s; we offered: %v, peer offered: %v", + a.What, a.SupportedAlgorithms, a.RequestedAlgorithms) +} + +// DirectionAlgorithms defines the algorithms negotiated in one direction +// (either read or write). +type DirectionAlgorithms struct { + Cipher string + MAC string + compression string +} + +// rekeyBytes returns a rekeying intervals in bytes. +func (a *DirectionAlgorithms) rekeyBytes() int64 { + // According to RFC 4344 block ciphers should rekey after + // 2^(BLOCKSIZE/4) blocks. For all AES flavors BLOCKSIZE is + // 128. + switch a.Cipher { + case CipherAES128CTR, CipherAES192CTR, CipherAES256CTR, CipherAES128GCM, CipherAES256GCM, InsecureCipherAES128CBC: + return 16 * (1 << 32) + + } + + // For others, stick with RFC 4253 recommendation to rekey after 1 Gb of data. + return 1 << 30 +} + +var aeadCiphers = map[string]bool{ + CipherAES128GCM: true, + CipherAES256GCM: true, + CipherChaCha20Poly1305: true, +} + +func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMsg) (algs *NegotiatedAlgorithms, err error) { + result := &NegotiatedAlgorithms{} + + result.KeyExchange, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos, isClient) + if err != nil { + return + } + + result.HostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos, isClient) + if err != nil { + return + } + + stoc, ctos := &result.Write, &result.Read + if isClient { + ctos, stoc = stoc, ctos + } + + ctos.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer, isClient) + if err != nil { + return + } + + stoc.Cipher, err = findCommon("server to client cipher", clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient, isClient) + if err != nil { + return + } + + if !aeadCiphers[ctos.Cipher] { + ctos.MAC, err = findCommon("client to server MAC", clientKexInit.MACsClientServer, serverKexInit.MACsClientServer, isClient) + if err != nil { + return + } + } + + if !aeadCiphers[stoc.Cipher] { + stoc.MAC, err = findCommon("server to client MAC", clientKexInit.MACsServerClient, serverKexInit.MACsServerClient, isClient) + if err != nil { + return + } + } + + ctos.compression, err = findCommon("client to server compression", clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer, isClient) + if err != nil { + return + } + + stoc.compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient, isClient) + if err != nil { + return + } + + return result, nil +} + +// If rekeythreshold is too small, we can't make any progress sending +// stuff. +const minRekeyThreshold uint64 = 256 + +// Config contains configuration data common to both ServerConfig and +// ClientConfig. +type Config struct { + // Rand provides the source of entropy for cryptographic + // primitives. If Rand is nil, the cryptographic random reader + // in package crypto/rand will be used. + Rand io.Reader + + // The maximum number of bytes sent or received after which a + // new key is negotiated. It must be at least 256. If + // unspecified, a size suitable for the chosen cipher is used. + RekeyThreshold uint64 + + // The allowed key exchanges algorithms. If unspecified then a default set + // of algorithms is used. Unsupported values are silently ignored. + KeyExchanges []string + + // The allowed cipher algorithms. If unspecified then a sensible default is + // used. Unsupported values are silently ignored. + Ciphers []string + + // The allowed MAC algorithms. If unspecified then a sensible default is + // used. Unsupported values are silently ignored. + MACs []string +} + +// SetDefaults sets sensible values for unset fields in config. This is +// exported for testing: Configs passed to SSH functions are copied and have +// default values set automatically. +func (c *Config) SetDefaults() { + if c.Rand == nil { + c.Rand = rand.Reader + } + if c.Ciphers == nil { + c.Ciphers = defaultCiphers + } + var ciphers []string + for _, c := range c.Ciphers { + if cipherModes[c] != nil { + // Ignore the cipher if we have no cipherModes definition. + ciphers = append(ciphers, c) + } + } + c.Ciphers = ciphers + + if c.KeyExchanges == nil { + c.KeyExchanges = defaultKexAlgos + } + var kexs []string + for _, k := range c.KeyExchanges { + if kexAlgoMap[k] != nil { + // Ignore the KEX if we have no kexAlgoMap definition. + kexs = append(kexs, k) + if k == KeyExchangeCurve25519 && !slices.Contains(c.KeyExchanges, keyExchangeCurve25519LibSSH) { + kexs = append(kexs, keyExchangeCurve25519LibSSH) + } + } + } + c.KeyExchanges = kexs + + if c.MACs == nil { + c.MACs = defaultMACs + } + var macs []string + for _, m := range c.MACs { + if macModes[m] != nil { + // Ignore the MAC if we have no macModes definition. + macs = append(macs, m) + } + } + c.MACs = macs + + if c.RekeyThreshold == 0 { + // cipher specific default + } else if c.RekeyThreshold < minRekeyThreshold { + c.RekeyThreshold = minRekeyThreshold + } else if c.RekeyThreshold >= math.MaxInt64 { + // Avoid weirdness if somebody uses -1 as a threshold. + c.RekeyThreshold = math.MaxInt64 + } +} + +// buildDataSignedForAuth returns the data that is signed in order to prove +// possession of a private key. See RFC 4252, section 7. algo is the advertised +// algorithm, and may be a certificate type. +func buildDataSignedForAuth(sessionID []byte, req userAuthRequestMsg, algo string, pubKey []byte) []byte { + data := struct { + Session []byte + Type byte + User string + Service string + Method string + Sign bool + Algo string + PubKey []byte + }{ + sessionID, + msgUserAuthRequest, + req.User, + req.Service, + req.Method, + true, + algo, + pubKey, + } + return Marshal(data) +} + +func appendU16(buf []byte, n uint16) []byte { + return append(buf, byte(n>>8), byte(n)) +} + +func appendU32(buf []byte, n uint32) []byte { + return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) +} + +func appendU64(buf []byte, n uint64) []byte { + return append(buf, + byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32), + byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) +} + +func appendInt(buf []byte, n int) []byte { + return appendU32(buf, uint32(n)) +} + +func appendString(buf []byte, s string) []byte { + buf = appendU32(buf, uint32(len(s))) + buf = append(buf, s...) + return buf +} + +func appendBool(buf []byte, b bool) []byte { + if b { + return append(buf, 1) + } + return append(buf, 0) +} + +// newCond is a helper to hide the fact that there is no usable zero +// value for sync.Cond. +func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) } + +// window represents the buffer available to clients +// wishing to write to a channel. +type window struct { + *sync.Cond + win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1 + writeWaiters int + closed bool +} + +// add adds win to the amount of window available +// for consumers. +func (w *window) add(win uint32) bool { + // a zero sized window adjust is a noop. + if win == 0 { + return true + } + w.L.Lock() + if w.win+win < win { + w.L.Unlock() + return false + } + w.win += win + // It is unusual that multiple goroutines would be attempting to reserve + // window space, but not guaranteed. Use broadcast to notify all waiters + // that additional window is available. + w.Broadcast() + w.L.Unlock() + return true +} + +// close sets the window to closed, so all reservations fail +// immediately. +func (w *window) close() { + w.L.Lock() + w.closed = true + w.Broadcast() + w.L.Unlock() +} + +// reserve reserves win from the available window capacity. +// If no capacity remains, reserve will block. reserve may +// return less than requested. +func (w *window) reserve(win uint32) (uint32, error) { + var err error + w.L.Lock() + w.writeWaiters++ + w.Broadcast() + for w.win == 0 && !w.closed { + w.Wait() + } + w.writeWaiters-- + if w.win < win { + win = w.win + } + w.win -= win + if w.closed { + err = io.EOF + } + w.L.Unlock() + return win, err +} + +// waitWriterBlocked waits until some goroutine is blocked for further +// writes. It is used in tests only. +func (w *window) waitWriterBlocked() { + w.Cond.L.Lock() + for w.writeWaiters == 0 { + w.Cond.Wait() + } + w.Cond.L.Unlock() +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/connection.go b/cli/vendor/golang.org/x/crypto/ssh/connection.go new file mode 100644 index 0000000..613a71a --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/connection.go @@ -0,0 +1,155 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "fmt" + "net" +) + +// OpenChannelError is returned if the other side rejects an +// OpenChannel request. +type OpenChannelError struct { + Reason RejectionReason + Message string +} + +func (e *OpenChannelError) Error() string { + return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message) +} + +// ConnMetadata holds metadata for the connection. +type ConnMetadata interface { + // User returns the user ID for this connection. + User() string + + // SessionID returns the session hash, also denoted by H. + SessionID() []byte + + // ClientVersion returns the client's version string as hashed + // into the session ID. + ClientVersion() []byte + + // ServerVersion returns the server's version string as hashed + // into the session ID. + ServerVersion() []byte + + // RemoteAddr returns the remote address for this connection. + RemoteAddr() net.Addr + + // LocalAddr returns the local address for this connection. + LocalAddr() net.Addr +} + +// Conn represents an SSH connection for both server and client roles. +// Conn is the basis for implementing an application layer, such +// as ClientConn, which implements the traditional shell access for +// clients. +type Conn interface { + ConnMetadata + + // SendRequest sends a global request, and returns the + // reply. If wantReply is true, it returns the response status + // and payload. See also RFC 4254, section 4. + SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) + + // OpenChannel tries to open an channel. If the request is + // rejected, it returns *OpenChannelError. On success it returns + // the SSH Channel and a Go channel for incoming, out-of-band + // requests. The Go channel must be serviced, or the + // connection will hang. + OpenChannel(name string, data []byte) (Channel, <-chan *Request, error) + + // Close closes the underlying network connection + Close() error + + // Wait blocks until the connection has shut down, and returns the + // error causing the shutdown. + Wait() error + + // TODO(hanwen): consider exposing: + // RequestKeyChange + // Disconnect +} + +// AlgorithmsConnMetadata is a ConnMetadata that can return the algorithms +// negotiated between client and server. +type AlgorithmsConnMetadata interface { + ConnMetadata + Algorithms() NegotiatedAlgorithms +} + +// DiscardRequests consumes and rejects all requests from the +// passed-in channel. +func DiscardRequests(in <-chan *Request) { + for req := range in { + if req.WantReply { + req.Reply(false, nil) + } + } +} + +// A connection represents an incoming connection. +type connection struct { + transport *handshakeTransport + sshConn + + // The connection protocol. + *mux +} + +func (c *connection) Close() error { + return c.sshConn.conn.Close() +} + +// sshConn provides net.Conn metadata, but disallows direct reads and +// writes. +type sshConn struct { + conn net.Conn + + user string + sessionID []byte + clientVersion []byte + serverVersion []byte + algorithms NegotiatedAlgorithms +} + +func dup(src []byte) []byte { + dst := make([]byte, len(src)) + copy(dst, src) + return dst +} + +func (c *sshConn) User() string { + return c.user +} + +func (c *sshConn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +func (c *sshConn) Close() error { + return c.conn.Close() +} + +func (c *sshConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +func (c *sshConn) SessionID() []byte { + return dup(c.sessionID) +} + +func (c *sshConn) ClientVersion() []byte { + return dup(c.clientVersion) +} + +func (c *sshConn) ServerVersion() []byte { + return dup(c.serverVersion) +} + +func (c *sshConn) Algorithms() NegotiatedAlgorithms { + return c.algorithms +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/doc.go b/cli/vendor/golang.org/x/crypto/ssh/doc.go new file mode 100644 index 0000000..5b4de9e --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/doc.go @@ -0,0 +1,34 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package ssh implements an SSH client and server. + +SSH is a transport security protocol, an authentication protocol and a +family of application protocols. The most typical application level +protocol is a remote shell and this is specifically implemented. However, +the multiplexed nature of SSH is exposed to users that wish to support +others. + +References: + + [PROTOCOL]: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL?rev=HEAD + [PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD + [SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1 + [SSH-CERTS]: https://datatracker.ietf.org/doc/html/draft-miller-ssh-cert-01 + [FIPS 140-3 mode]: https://go.dev/doc/security/fips140 + +This package does not fall under the stability promise of the Go language itself, +so its API may be changed when pressing needs arise. + +# FIPS 140-3 mode + +When the program is in [FIPS 140-3 mode], this package behaves as if only SP +800-140C and SP 800-140D approved cipher suites, signature algorithms, +certificate public key types and sizes, and key exchange and derivation +algorithms were implemented. Others are silently ignored and not negotiated, or +rejected. This set may depend on the algorithms supported by the FIPS 140-3 Go +Cryptographic Module selected with GOFIPS140, and may change across Go versions. +*/ +package ssh diff --git a/cli/vendor/golang.org/x/crypto/ssh/handshake.go b/cli/vendor/golang.org/x/crypto/ssh/handshake.go new file mode 100644 index 0000000..4be3cbb --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/handshake.go @@ -0,0 +1,847 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "errors" + "fmt" + "io" + "log" + "net" + "slices" + "strings" + "sync" +) + +// debugHandshake, if set, prints messages sent and received. Key +// exchange messages are printed as if DH were used, so the debug +// messages are wrong when using ECDH. +const debugHandshake = false + +// chanSize sets the amount of buffering SSH connections. This is +// primarily for testing: setting chanSize=0 uncovers deadlocks more +// quickly. +const chanSize = 16 + +// maxPendingPackets sets the maximum number of packets to queue while waiting +// for KEX to complete. This limits the total pending data to maxPendingPackets +// * maxPacket bytes, which is ~16.8MB. +const maxPendingPackets = 64 + +// keyingTransport is a packet based transport that supports key +// changes. It need not be thread-safe. It should pass through +// msgNewKeys in both directions. +type keyingTransport interface { + packetConn + + // prepareKeyChange sets up a key change. The key change for a + // direction will be effected if a msgNewKeys message is sent + // or received. + prepareKeyChange(*NegotiatedAlgorithms, *kexResult) error + + // setStrictMode sets the strict KEX mode, notably triggering + // sequence number resets on sending or receiving msgNewKeys. + // If the sequence number is already > 1 when setStrictMode + // is called, an error is returned. + setStrictMode() error + + // setInitialKEXDone indicates to the transport that the initial key exchange + // was completed + setInitialKEXDone() +} + +// handshakeTransport implements rekeying on top of a keyingTransport +// and offers a thread-safe writePacket() interface. +type handshakeTransport struct { + conn keyingTransport + config *Config + + serverVersion []byte + clientVersion []byte + + // hostKeys is non-empty if we are the server. In that case, + // it contains all host keys that can be used to sign the + // connection. + hostKeys []Signer + + // publicKeyAuthAlgorithms is non-empty if we are the server. In that case, + // it contains the supported client public key authentication algorithms. + publicKeyAuthAlgorithms []string + + // hostKeyAlgorithms is non-empty if we are the client. In that case, + // we accept these key types from the server as host key. + hostKeyAlgorithms []string + + // On read error, incoming is closed, and readError is set. + incoming chan []byte + readError error + + mu sync.Mutex + // Condition for the above mutex. It is used to notify a completed key + // exchange or a write failure. Writes can wait for this condition while a + // key exchange is in progress. + writeCond *sync.Cond + writeError error + sentInitPacket []byte + sentInitMsg *kexInitMsg + // Used to queue writes when a key exchange is in progress. The length is + // limited by pendingPacketsSize. Once full, writes will block until the key + // exchange is completed or an error occurs. If not empty, it is emptied + // all at once when the key exchange is completed in kexLoop. + pendingPackets [][]byte + writePacketsLeft uint32 + writeBytesLeft int64 + userAuthComplete bool // whether the user authentication phase is complete + + // If the read loop wants to schedule a kex, it pings this + // channel, and the write loop will send out a kex + // message. + requestKex chan struct{} + + // If the other side requests or confirms a kex, its kexInit + // packet is sent here for the write loop to find it. + startKex chan *pendingKex + kexLoopDone chan struct{} // closed (with writeError non-nil) when kexLoop exits + + // data for host key checking + hostKeyCallback HostKeyCallback + dialAddress string + remoteAddr net.Addr + + // bannerCallback is non-empty if we are the client and it has been set in + // ClientConfig. In that case it is called during the user authentication + // dance to handle a custom server's message. + bannerCallback BannerCallback + + // Algorithms agreed in the last key exchange. + algorithms *NegotiatedAlgorithms + + // Counters exclusively owned by readLoop. + readPacketsLeft uint32 + readBytesLeft int64 + + // The session ID or nil if first kex did not complete yet. + sessionID []byte + + // strictMode indicates if the other side of the handshake indicated + // that we should be following the strict KEX protocol restrictions. + strictMode bool +} + +type pendingKex struct { + otherInit []byte + done chan error +} + +func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, serverVersion []byte) *handshakeTransport { + t := &handshakeTransport{ + conn: conn, + serverVersion: serverVersion, + clientVersion: clientVersion, + incoming: make(chan []byte, chanSize), + requestKex: make(chan struct{}, 1), + startKex: make(chan *pendingKex), + kexLoopDone: make(chan struct{}), + + config: config, + } + t.writeCond = sync.NewCond(&t.mu) + t.resetReadThresholds() + t.resetWriteThresholds() + + // We always start with a mandatory key exchange. + t.requestKex <- struct{}{} + return t +} + +func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ClientConfig, dialAddr string, addr net.Addr) *handshakeTransport { + t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion) + t.dialAddress = dialAddr + t.remoteAddr = addr + t.hostKeyCallback = config.HostKeyCallback + t.bannerCallback = config.BannerCallback + if config.HostKeyAlgorithms != nil { + t.hostKeyAlgorithms = config.HostKeyAlgorithms + } else { + t.hostKeyAlgorithms = defaultHostKeyAlgos + } + go t.readLoop() + go t.kexLoop() + return t +} + +func newServerTransport(conn keyingTransport, clientVersion, serverVersion []byte, config *ServerConfig) *handshakeTransport { + t := newHandshakeTransport(conn, &config.Config, clientVersion, serverVersion) + t.hostKeys = config.hostKeys + t.publicKeyAuthAlgorithms = config.PublicKeyAuthAlgorithms + go t.readLoop() + go t.kexLoop() + return t +} + +func (t *handshakeTransport) getSessionID() []byte { + return t.sessionID +} + +func (t *handshakeTransport) getAlgorithms() NegotiatedAlgorithms { + return *t.algorithms +} + +// waitSession waits for the session to be established. This should be +// the first thing to call after instantiating handshakeTransport. +func (t *handshakeTransport) waitSession() error { + p, err := t.readPacket() + if err != nil { + return err + } + if p[0] != msgNewKeys { + return fmt.Errorf("ssh: first packet should be msgNewKeys") + } + + return nil +} + +func (t *handshakeTransport) id() string { + if len(t.hostKeys) > 0 { + return "server" + } + return "client" +} + +func (t *handshakeTransport) printPacket(p []byte, write bool) { + action := "got" + if write { + action = "sent" + } + + if p[0] == msgChannelData || p[0] == msgChannelExtendedData { + log.Printf("%s %s data (packet %d bytes)", t.id(), action, len(p)) + } else { + msg, err := decode(p) + log.Printf("%s %s %T %v (%v)", t.id(), action, msg, msg, err) + } +} + +func (t *handshakeTransport) readPacket() ([]byte, error) { + p, ok := <-t.incoming + if !ok { + return nil, t.readError + } + return p, nil +} + +func (t *handshakeTransport) readLoop() { + first := true + for { + p, err := t.readOnePacket(first) + first = false + if err != nil { + t.readError = err + close(t.incoming) + break + } + // If this is the first kex, and strict KEX mode is enabled, + // we don't ignore any messages, as they may be used to manipulate + // the packet sequence numbers. + if !(t.sessionID == nil && t.strictMode) && (p[0] == msgIgnore || p[0] == msgDebug) { + continue + } + t.incoming <- p + } + + // Stop writers too. + t.recordWriteError(t.readError) + + // Unblock the writer should it wait for this. + close(t.startKex) + + // Don't close t.requestKex; it's also written to from writePacket. +} + +func (t *handshakeTransport) pushPacket(p []byte) error { + if debugHandshake { + t.printPacket(p, true) + } + return t.conn.writePacket(p) +} + +func (t *handshakeTransport) getWriteError() error { + t.mu.Lock() + defer t.mu.Unlock() + return t.writeError +} + +func (t *handshakeTransport) recordWriteError(err error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.writeError == nil && err != nil { + t.writeError = err + t.writeCond.Broadcast() + } +} + +func (t *handshakeTransport) requestKeyExchange() { + select { + case t.requestKex <- struct{}{}: + default: + // something already requested a kex, so do nothing. + } +} + +func (t *handshakeTransport) resetWriteThresholds() { + t.writePacketsLeft = packetRekeyThreshold + if t.config.RekeyThreshold > 0 { + t.writeBytesLeft = int64(t.config.RekeyThreshold) + } else if t.algorithms != nil { + t.writeBytesLeft = t.algorithms.Write.rekeyBytes() + } else { + t.writeBytesLeft = 1 << 30 + } +} + +func (t *handshakeTransport) kexLoop() { + +write: + for t.getWriteError() == nil { + var request *pendingKex + var sent bool + + for request == nil || !sent { + var ok bool + select { + case request, ok = <-t.startKex: + if !ok { + break write + } + case <-t.requestKex: + break + } + + if !sent { + if err := t.sendKexInit(); err != nil { + t.recordWriteError(err) + break + } + sent = true + } + } + + if err := t.getWriteError(); err != nil { + if request != nil { + request.done <- err + } + break + } + + // We're not servicing t.requestKex, but that is OK: + // we never block on sending to t.requestKex. + + // We're not servicing t.startKex, but the remote end + // has just sent us a kexInitMsg, so it can't send + // another key change request, until we close the done + // channel on the pendingKex request. + + err := t.enterKeyExchange(request.otherInit) + + t.mu.Lock() + t.writeError = err + t.sentInitPacket = nil + t.sentInitMsg = nil + + t.resetWriteThresholds() + + // we have completed the key exchange. Since the + // reader is still blocked, it is safe to clear out + // the requestKex channel. This avoids the situation + // where: 1) we consumed our own request for the + // initial kex, and 2) the kex from the remote side + // caused another send on the requestKex channel, + clear: + for { + select { + case <-t.requestKex: + // + default: + break clear + } + } + + request.done <- t.writeError + + // kex finished. Push packets that we received while + // the kex was in progress. Don't look at t.startKex + // and don't increment writtenSinceKex: if we trigger + // another kex while we are still busy with the last + // one, things will become very confusing. + for _, p := range t.pendingPackets { + t.writeError = t.pushPacket(p) + if t.writeError != nil { + break + } + } + t.pendingPackets = t.pendingPackets[:0] + // Unblock writePacket if waiting for KEX. + t.writeCond.Broadcast() + t.mu.Unlock() + } + + // Unblock reader. + t.conn.Close() + + // drain startKex channel. We don't service t.requestKex + // because nobody does blocking sends there. + for request := range t.startKex { + request.done <- t.getWriteError() + } + + // Mark that the loop is done so that Close can return. + close(t.kexLoopDone) +} + +// The protocol uses uint32 for packet counters, so we can't let them +// reach 1<<32. We will actually read and write more packets than +// this, though: the other side may send more packets, and after we +// hit this limit on writing we will send a few more packets for the +// key exchange itself. +const packetRekeyThreshold = (1 << 31) + +func (t *handshakeTransport) resetReadThresholds() { + t.readPacketsLeft = packetRekeyThreshold + if t.config.RekeyThreshold > 0 { + t.readBytesLeft = int64(t.config.RekeyThreshold) + } else if t.algorithms != nil { + t.readBytesLeft = t.algorithms.Read.rekeyBytes() + } else { + t.readBytesLeft = 1 << 30 + } +} + +func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) { + p, err := t.conn.readPacket() + if err != nil { + return nil, err + } + + if t.readPacketsLeft > 0 { + t.readPacketsLeft-- + } else { + t.requestKeyExchange() + } + + if t.readBytesLeft > 0 { + t.readBytesLeft -= int64(len(p)) + } else { + t.requestKeyExchange() + } + + if debugHandshake { + t.printPacket(p, false) + } + + if first && p[0] != msgKexInit { + return nil, fmt.Errorf("ssh: first packet should be msgKexInit") + } + + if p[0] != msgKexInit { + return p, nil + } + + firstKex := t.sessionID == nil + + kex := pendingKex{ + done: make(chan error, 1), + otherInit: p, + } + t.startKex <- &kex + err = <-kex.done + + if debugHandshake { + log.Printf("%s exited key exchange (first %v), err %v", t.id(), firstKex, err) + } + + if err != nil { + return nil, err + } + + t.resetReadThresholds() + + // By default, a key exchange is hidden from higher layers by + // translating it into msgIgnore. + successPacket := []byte{msgIgnore} + if firstKex { + // sendKexInit() for the first kex waits for + // msgNewKeys so the authentication process is + // guaranteed to happen over an encrypted transport. + successPacket = []byte{msgNewKeys} + } + + return successPacket, nil +} + +const ( + kexStrictClient = "kex-strict-c-v00@openssh.com" + kexStrictServer = "kex-strict-s-v00@openssh.com" +) + +// sendKexInit sends a key change message. +func (t *handshakeTransport) sendKexInit() error { + t.mu.Lock() + defer t.mu.Unlock() + if t.sentInitMsg != nil { + // kexInits may be sent either in response to the other side, + // or because our side wants to initiate a key change, so we + // may have already sent a kexInit. In that case, don't send a + // second kexInit. + return nil + } + + msg := &kexInitMsg{ + CiphersClientServer: t.config.Ciphers, + CiphersServerClient: t.config.Ciphers, + MACsClientServer: t.config.MACs, + MACsServerClient: t.config.MACs, + CompressionClientServer: supportedCompressions, + CompressionServerClient: supportedCompressions, + } + io.ReadFull(t.config.Rand, msg.Cookie[:]) + + // We mutate the KexAlgos slice, in order to add the kex-strict extension algorithm, + // and possibly to add the ext-info extension algorithm. Since the slice may be the + // user owned KeyExchanges, we create our own slice in order to avoid using user + // owned memory by mistake. + msg.KexAlgos = make([]string, 0, len(t.config.KeyExchanges)+2) // room for kex-strict and ext-info + msg.KexAlgos = append(msg.KexAlgos, t.config.KeyExchanges...) + + isServer := len(t.hostKeys) > 0 + if isServer { + for _, k := range t.hostKeys { + // If k is a MultiAlgorithmSigner, we restrict the signature + // algorithms. If k is a AlgorithmSigner, presume it supports all + // signature algorithms associated with the key format. If k is not + // an AlgorithmSigner, we can only assume it only supports the + // algorithms that matches the key format. (This means that Sign + // can't pick a different default). + keyFormat := k.PublicKey().Type() + + switch s := k.(type) { + case MultiAlgorithmSigner: + for _, algo := range algorithmsForKeyFormat(keyFormat) { + if slices.Contains(s.Algorithms(), underlyingAlgo(algo)) { + msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algo) + } + } + case AlgorithmSigner: + msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algorithmsForKeyFormat(keyFormat)...) + default: + msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, keyFormat) + } + } + + if t.sessionID == nil { + msg.KexAlgos = append(msg.KexAlgos, kexStrictServer) + } + } else { + msg.ServerHostKeyAlgos = t.hostKeyAlgorithms + + // As a client we opt in to receiving SSH_MSG_EXT_INFO so we know what + // algorithms the server supports for public key authentication. See RFC + // 8308, Section 2.1. + // + // We also send the strict KEX mode extension algorithm, in order to opt + // into the strict KEX mode. + if firstKeyExchange := t.sessionID == nil; firstKeyExchange { + msg.KexAlgos = append(msg.KexAlgos, "ext-info-c") + msg.KexAlgos = append(msg.KexAlgos, kexStrictClient) + } + + } + + packet := Marshal(msg) + + // writePacket destroys the contents, so save a copy. + packetCopy := make([]byte, len(packet)) + copy(packetCopy, packet) + + if err := t.pushPacket(packetCopy); err != nil { + return err + } + + t.sentInitMsg = msg + t.sentInitPacket = packet + + return nil +} + +var errSendBannerPhase = errors.New("ssh: SendAuthBanner outside of authentication phase") + +func (t *handshakeTransport) writePacket(p []byte) error { + t.mu.Lock() + defer t.mu.Unlock() + + switch p[0] { + case msgKexInit: + return errors.New("ssh: only handshakeTransport can send kexInit") + case msgNewKeys: + return errors.New("ssh: only handshakeTransport can send newKeys") + case msgUserAuthBanner: + if t.userAuthComplete { + return errSendBannerPhase + } + case msgUserAuthSuccess: + t.userAuthComplete = true + } + + if t.writeError != nil { + return t.writeError + } + + if t.sentInitMsg != nil { + if len(t.pendingPackets) < maxPendingPackets { + // Copy the packet so the writer can reuse the buffer. + cp := make([]byte, len(p)) + copy(cp, p) + t.pendingPackets = append(t.pendingPackets, cp) + return nil + } + for t.sentInitMsg != nil { + // Block and wait for KEX to complete or an error. + t.writeCond.Wait() + if t.writeError != nil { + return t.writeError + } + } + } + + if t.writeBytesLeft > 0 { + t.writeBytesLeft -= int64(len(p)) + } else { + t.requestKeyExchange() + } + + if t.writePacketsLeft > 0 { + t.writePacketsLeft-- + } else { + t.requestKeyExchange() + } + + if err := t.pushPacket(p); err != nil { + t.writeError = err + t.writeCond.Broadcast() + } + + return nil +} + +func (t *handshakeTransport) Close() error { + // Close the connection. This should cause the readLoop goroutine to wake up + // and close t.startKex, which will shut down kexLoop if running. + err := t.conn.Close() + + // Wait for the kexLoop goroutine to complete. + // At that point we know that the readLoop goroutine is complete too, + // because kexLoop itself waits for readLoop to close the startKex channel. + <-t.kexLoopDone + + return err +} + +func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error { + if debugHandshake { + log.Printf("%s entered key exchange", t.id()) + } + + otherInit := &kexInitMsg{} + if err := Unmarshal(otherInitPacket, otherInit); err != nil { + return err + } + + magics := handshakeMagics{ + clientVersion: t.clientVersion, + serverVersion: t.serverVersion, + clientKexInit: otherInitPacket, + serverKexInit: t.sentInitPacket, + } + + clientInit := otherInit + serverInit := t.sentInitMsg + isClient := len(t.hostKeys) == 0 + if isClient { + clientInit, serverInit = serverInit, clientInit + + magics.clientKexInit = t.sentInitPacket + magics.serverKexInit = otherInitPacket + } + + var err error + t.algorithms, err = findAgreedAlgorithms(isClient, clientInit, serverInit) + if err != nil { + return err + } + + if t.sessionID == nil && ((isClient && slices.Contains(serverInit.KexAlgos, kexStrictServer)) || (!isClient && slices.Contains(clientInit.KexAlgos, kexStrictClient))) { + t.strictMode = true + if err := t.conn.setStrictMode(); err != nil { + return err + } + } + + // We don't send FirstKexFollows, but we handle receiving it. + // + // RFC 4253 section 7 defines the kex and the agreement method for + // first_kex_packet_follows. It states that the guessed packet + // should be ignored if the "kex algorithm and/or the host + // key algorithm is guessed wrong (server and client have + // different preferred algorithm), or if any of the other + // algorithms cannot be agreed upon". The other algorithms have + // already been checked above so the kex algorithm and host key + // algorithm are checked here. + if otherInit.FirstKexFollows && (clientInit.KexAlgos[0] != serverInit.KexAlgos[0] || clientInit.ServerHostKeyAlgos[0] != serverInit.ServerHostKeyAlgos[0]) { + // other side sent a kex message for the wrong algorithm, + // which we have to ignore. + if _, err := t.conn.readPacket(); err != nil { + return err + } + } + + kex, ok := kexAlgoMap[t.algorithms.KeyExchange] + if !ok { + return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.KeyExchange) + } + + var result *kexResult + if len(t.hostKeys) > 0 { + result, err = t.server(kex, &magics) + } else { + result, err = t.client(kex, &magics) + } + + if err != nil { + return err + } + + firstKeyExchange := t.sessionID == nil + if firstKeyExchange { + t.sessionID = result.H + } + result.SessionID = t.sessionID + + if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil { + return err + } + if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil { + return err + } + + // On the server side, after the first SSH_MSG_NEWKEYS, send a SSH_MSG_EXT_INFO + // message with the server-sig-algs extension if the client supports it. See + // RFC 8308, Sections 2.4 and 3.1, and [PROTOCOL], Section 1.9. + if !isClient && firstKeyExchange && slices.Contains(clientInit.KexAlgos, "ext-info-c") { + supportedPubKeyAuthAlgosList := strings.Join(t.publicKeyAuthAlgorithms, ",") + extInfo := &extInfoMsg{ + NumExtensions: 2, + Payload: make([]byte, 0, 4+15+4+len(supportedPubKeyAuthAlgosList)+4+16+4+1), + } + extInfo.Payload = appendInt(extInfo.Payload, len("server-sig-algs")) + extInfo.Payload = append(extInfo.Payload, "server-sig-algs"...) + extInfo.Payload = appendInt(extInfo.Payload, len(supportedPubKeyAuthAlgosList)) + extInfo.Payload = append(extInfo.Payload, supportedPubKeyAuthAlgosList...) + extInfo.Payload = appendInt(extInfo.Payload, len("ping@openssh.com")) + extInfo.Payload = append(extInfo.Payload, "ping@openssh.com"...) + extInfo.Payload = appendInt(extInfo.Payload, 1) + extInfo.Payload = append(extInfo.Payload, "0"...) + if err := t.conn.writePacket(Marshal(extInfo)); err != nil { + return err + } + } + + if packet, err := t.conn.readPacket(); err != nil { + return err + } else if packet[0] != msgNewKeys { + return unexpectedMessageError(msgNewKeys, packet[0]) + } + + if firstKeyExchange { + // Indicates to the transport that the first key exchange is completed + // after receiving SSH_MSG_NEWKEYS. + t.conn.setInitialKEXDone() + } + + return nil +} + +// algorithmSignerWrapper is an AlgorithmSigner that only supports the default +// key format algorithm. +// +// This is technically a violation of the AlgorithmSigner interface, but it +// should be unreachable given where we use this. Anyway, at least it returns an +// error instead of panicing or producing an incorrect signature. +type algorithmSignerWrapper struct { + Signer +} + +func (a algorithmSignerWrapper) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) { + if algorithm != underlyingAlgo(a.PublicKey().Type()) { + return nil, errors.New("ssh: internal error: algorithmSignerWrapper invoked with non-default algorithm") + } + return a.Sign(rand, data) +} + +func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner { + for _, k := range hostKeys { + if s, ok := k.(MultiAlgorithmSigner); ok { + if !slices.Contains(s.Algorithms(), underlyingAlgo(algo)) { + continue + } + } + + if algo == k.PublicKey().Type() { + return algorithmSignerWrapper{k} + } + + k, ok := k.(AlgorithmSigner) + if !ok { + continue + } + for _, a := range algorithmsForKeyFormat(k.PublicKey().Type()) { + if algo == a { + return k + } + } + } + return nil +} + +func (t *handshakeTransport) server(kex kexAlgorithm, magics *handshakeMagics) (*kexResult, error) { + hostKey := pickHostKey(t.hostKeys, t.algorithms.HostKey) + if hostKey == nil { + return nil, errors.New("ssh: internal error: negotiated unsupported signature type") + } + + r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey, t.algorithms.HostKey) + return r, err +} + +func (t *handshakeTransport) client(kex kexAlgorithm, magics *handshakeMagics) (*kexResult, error) { + result, err := kex.Client(t.conn, t.config.Rand, magics) + if err != nil { + return nil, err + } + + hostKey, err := ParsePublicKey(result.HostKey) + if err != nil { + return nil, err + } + + if err := verifyHostKeySignature(hostKey, t.algorithms.HostKey, result); err != nil { + return nil, err + } + + err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go b/cli/vendor/golang.org/x/crypto/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go new file mode 100644 index 0000000..af81d26 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go @@ -0,0 +1,93 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bcrypt_pbkdf implements bcrypt_pbkdf(3) from OpenBSD. +// +// See https://flak.tedunangst.com/post/bcrypt-pbkdf and +// https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libutil/bcrypt_pbkdf.c. +package bcrypt_pbkdf + +import ( + "crypto/sha512" + "errors" + "golang.org/x/crypto/blowfish" +) + +const blockSize = 32 + +// Key derives a key from the password, salt and rounds count, returning a +// []byte of length keyLen that can be used as cryptographic key. +func Key(password, salt []byte, rounds, keyLen int) ([]byte, error) { + if rounds < 1 { + return nil, errors.New("bcrypt_pbkdf: number of rounds is too small") + } + if len(password) == 0 { + return nil, errors.New("bcrypt_pbkdf: empty password") + } + if len(salt) == 0 || len(salt) > 1<<20 { + return nil, errors.New("bcrypt_pbkdf: bad salt length") + } + if keyLen > 1024 { + return nil, errors.New("bcrypt_pbkdf: keyLen is too large") + } + + numBlocks := (keyLen + blockSize - 1) / blockSize + key := make([]byte, numBlocks*blockSize) + + h := sha512.New() + h.Write(password) + shapass := h.Sum(nil) + + shasalt := make([]byte, 0, sha512.Size) + cnt, tmp := make([]byte, 4), make([]byte, blockSize) + for block := 1; block <= numBlocks; block++ { + h.Reset() + h.Write(salt) + cnt[0] = byte(block >> 24) + cnt[1] = byte(block >> 16) + cnt[2] = byte(block >> 8) + cnt[3] = byte(block) + h.Write(cnt) + bcryptHash(tmp, shapass, h.Sum(shasalt)) + + out := make([]byte, blockSize) + copy(out, tmp) + for i := 2; i <= rounds; i++ { + h.Reset() + h.Write(tmp) + bcryptHash(tmp, shapass, h.Sum(shasalt)) + for j := 0; j < len(out); j++ { + out[j] ^= tmp[j] + } + } + + for i, v := range out { + key[i*numBlocks+(block-1)] = v + } + } + return key[:keyLen], nil +} + +var magic = []byte("OxychromaticBlowfishSwatDynamite") + +func bcryptHash(out, shapass, shasalt []byte) { + c, err := blowfish.NewSaltedCipher(shapass, shasalt) + if err != nil { + panic(err) + } + for i := 0; i < 64; i++ { + blowfish.ExpandKey(shasalt, c) + blowfish.ExpandKey(shapass, c) + } + copy(out, magic) + for i := 0; i < 32; i += 8 { + for j := 0; j < 64; j++ { + c.Encrypt(out[i:i+8], out[i:i+8]) + } + } + // Swap bytes due to different endianness. + for i := 0; i < 32; i += 4 { + out[i+3], out[i+2], out[i+1], out[i] = out[i], out[i+1], out[i+2], out[i+3] + } +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/kex.go b/cli/vendor/golang.org/x/crypto/ssh/kex.go new file mode 100644 index 0000000..5f7fdd8 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/kex.go @@ -0,0 +1,807 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/fips140" + "crypto/rand" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + "slices" + + "golang.org/x/crypto/curve25519" +) + +const ( + // This is the group called diffie-hellman-group1-sha1 in RFC 4253 and + // Oakley Group 2 in RFC 2409. + oakleyGroup2 = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF" + // This is the group called diffie-hellman-group14-sha1 in RFC 4253 and + // Oakley Group 14 in RFC 3526. + oakleyGroup14 = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF" + // This is the group called diffie-hellman-group15-sha512 in RFC 8268 and + // Oakley Group 15 in RFC 3526. + oakleyGroup15 = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF" + // This is the group called diffie-hellman-group16-sha512 in RFC 8268 and + // Oakley Group 16 in RFC 3526. + oakleyGroup16 = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF" +) + +// kexResult captures the outcome of a key exchange. +type kexResult struct { + // Session hash. See also RFC 4253, section 8. + H []byte + + // Shared secret. See also RFC 4253, section 8. + K []byte + + // Host key as hashed into H. + HostKey []byte + + // Signature of H. + Signature []byte + + // A cryptographic hash function that matches the security + // level of the key exchange algorithm. It is used for + // calculating H, and for deriving keys from H and K. + Hash crypto.Hash + + // The session ID, which is the first H computed. This is used + // to derive key material inside the transport. + SessionID []byte +} + +// handshakeMagics contains data that is always included in the +// session hash. +type handshakeMagics struct { + clientVersion, serverVersion []byte + clientKexInit, serverKexInit []byte +} + +func (m *handshakeMagics) write(w io.Writer) { + writeString(w, m.clientVersion) + writeString(w, m.serverVersion) + writeString(w, m.clientKexInit) + writeString(w, m.serverKexInit) +} + +// kexAlgorithm abstracts different key exchange algorithms. +type kexAlgorithm interface { + // Server runs server-side key agreement, signing the result + // with a hostkey. algo is the negotiated algorithm, and may + // be a certificate type. + Server(p packetConn, rand io.Reader, magics *handshakeMagics, s AlgorithmSigner, algo string) (*kexResult, error) + + // Client runs the client-side key agreement. Caller is + // responsible for verifying the host key signature. + Client(p packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) +} + +// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement. +type dhGroup struct { + g, p, pMinus1 *big.Int + hashFunc crypto.Hash +} + +func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) { + if theirPublic.Cmp(bigOne) <= 0 || theirPublic.Cmp(group.pMinus1) >= 0 { + return nil, errors.New("ssh: DH parameter out of bounds") + } + return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil +} + +func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) { + var x *big.Int + for { + var err error + if x, err = rand.Int(randSource, group.pMinus1); err != nil { + return nil, err + } + if x.Sign() > 0 { + break + } + } + + X := new(big.Int).Exp(group.g, x, group.p) + kexDHInit := kexDHInitMsg{ + X: X, + } + if err := c.writePacket(Marshal(&kexDHInit)); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var kexDHReply kexDHReplyMsg + if err = Unmarshal(packet, &kexDHReply); err != nil { + return nil, err + } + + ki, err := group.diffieHellman(kexDHReply.Y, x) + if err != nil { + return nil, err + } + + h := group.hashFunc.New() + magics.write(h) + writeString(h, kexDHReply.HostKey) + writeInt(h, X) + writeInt(h, kexDHReply.Y) + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: kexDHReply.HostKey, + Signature: kexDHReply.Signature, + Hash: group.hashFunc, + }, nil +} + +func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (result *kexResult, err error) { + packet, err := c.readPacket() + if err != nil { + return + } + var kexDHInit kexDHInitMsg + if err = Unmarshal(packet, &kexDHInit); err != nil { + return + } + + var y *big.Int + for { + if y, err = rand.Int(randSource, group.pMinus1); err != nil { + return + } + if y.Sign() > 0 { + break + } + } + + Y := new(big.Int).Exp(group.g, y, group.p) + ki, err := group.diffieHellman(kexDHInit.X, y) + if err != nil { + return nil, err + } + + hostKeyBytes := priv.PublicKey().Marshal() + + h := group.hashFunc.New() + magics.write(h) + writeString(h, hostKeyBytes) + writeInt(h, kexDHInit.X) + writeInt(h, Y) + + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + H := h.Sum(nil) + + // H is already a hash, but the hostkey signing will apply its + // own key-specific hash algorithm. + sig, err := signAndMarshal(priv, randSource, H, algo) + if err != nil { + return nil, err + } + + kexDHReply := kexDHReplyMsg{ + HostKey: hostKeyBytes, + Y: Y, + Signature: sig, + } + packet = Marshal(&kexDHReply) + + err = c.writePacket(packet) + return &kexResult{ + H: H, + K: K, + HostKey: hostKeyBytes, + Signature: sig, + Hash: group.hashFunc, + }, err +} + +// ecdh performs Elliptic Curve Diffie-Hellman key exchange as +// described in RFC 5656, section 4. +type ecdh struct { + curve elliptic.Curve +} + +func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) { + ephKey, err := ecdsa.GenerateKey(kex.curve, rand) + if err != nil { + return nil, err + } + + kexInit := kexECDHInitMsg{ + ClientPubKey: elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y), + } + + serialized := Marshal(&kexInit) + if err := c.writePacket(serialized); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var reply kexECDHReplyMsg + if err = Unmarshal(packet, &reply); err != nil { + return nil, err + } + + x, y, err := unmarshalECKey(kex.curve, reply.EphemeralPubKey) + if err != nil { + return nil, err + } + + // generate shared secret + secret, _ := kex.curve.ScalarMult(x, y, ephKey.D.Bytes()) + + h := ecHash(kex.curve).New() + magics.write(h) + writeString(h, reply.HostKey) + writeString(h, kexInit.ClientPubKey) + writeString(h, reply.EphemeralPubKey) + K := make([]byte, intLength(secret)) + marshalInt(K, secret) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: reply.HostKey, + Signature: reply.Signature, + Hash: ecHash(kex.curve), + }, nil +} + +// unmarshalECKey parses and checks an EC key. +func unmarshalECKey(curve elliptic.Curve, pubkey []byte) (x, y *big.Int, err error) { + x, y = elliptic.Unmarshal(curve, pubkey) + if x == nil { + return nil, nil, errors.New("ssh: elliptic.Unmarshal failure") + } + if !validateECPublicKey(curve, x, y) { + return nil, nil, errors.New("ssh: public key not on curve") + } + return x, y, nil +} + +// validateECPublicKey checks that the point is a valid public key for +// the given curve. See [SEC1], 3.2.2 +func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool { + if x.Sign() == 0 && y.Sign() == 0 { + return false + } + + if x.Cmp(curve.Params().P) >= 0 { + return false + } + + if y.Cmp(curve.Params().P) >= 0 { + return false + } + + if !curve.IsOnCurve(x, y) { + return false + } + + // We don't check if N * PubKey == 0, since + // + // - the NIST curves have cofactor = 1, so this is implicit. + // (We don't foresee an implementation that supports non NIST + // curves) + // + // - for ephemeral keys, we don't need to worry about small + // subgroup attacks. + return true +} + +func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (result *kexResult, err error) { + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var kexECDHInit kexECDHInitMsg + if err = Unmarshal(packet, &kexECDHInit); err != nil { + return nil, err + } + + clientX, clientY, err := unmarshalECKey(kex.curve, kexECDHInit.ClientPubKey) + if err != nil { + return nil, err + } + + // We could cache this key across multiple users/multiple + // connection attempts, but the benefit is small. OpenSSH + // generates a new key for each incoming connection. + ephKey, err := ecdsa.GenerateKey(kex.curve, rand) + if err != nil { + return nil, err + } + + hostKeyBytes := priv.PublicKey().Marshal() + + serializedEphKey := elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y) + + // generate shared secret + secret, _ := kex.curve.ScalarMult(clientX, clientY, ephKey.D.Bytes()) + + h := ecHash(kex.curve).New() + magics.write(h) + writeString(h, hostKeyBytes) + writeString(h, kexECDHInit.ClientPubKey) + writeString(h, serializedEphKey) + + K := make([]byte, intLength(secret)) + marshalInt(K, secret) + h.Write(K) + + H := h.Sum(nil) + + // H is already a hash, but the hostkey signing will apply its + // own key-specific hash algorithm. + sig, err := signAndMarshal(priv, rand, H, algo) + if err != nil { + return nil, err + } + + reply := kexECDHReplyMsg{ + EphemeralPubKey: serializedEphKey, + HostKey: hostKeyBytes, + Signature: sig, + } + + serialized := Marshal(&reply) + if err := c.writePacket(serialized); err != nil { + return nil, err + } + + return &kexResult{ + H: H, + K: K, + HostKey: reply.HostKey, + Signature: sig, + Hash: ecHash(kex.curve), + }, nil +} + +// ecHash returns the hash to match the given elliptic curve, see RFC +// 5656, section 6.2.1 +func ecHash(curve elliptic.Curve) crypto.Hash { + bitSize := curve.Params().BitSize + switch { + case bitSize <= 256: + return crypto.SHA256 + case bitSize <= 384: + return crypto.SHA384 + } + return crypto.SHA512 +} + +// kexAlgoMap defines the supported KEXs. KEXs not included are not supported +// and will not be negotiated, even if explicitly configured. When FIPS mode is +// enabled, only FIPS-approved algorithms are included. +var kexAlgoMap = map[string]kexAlgorithm{} + +func init() { + // mlkem768x25519-sha256 we'll work with fips140=on but not fips140=only + // until Go 1.26. + kexAlgoMap[KeyExchangeMLKEM768X25519] = &mlkem768WithCurve25519sha256{} + kexAlgoMap[KeyExchangeECDHP521] = &ecdh{elliptic.P521()} + kexAlgoMap[KeyExchangeECDHP384] = &ecdh{elliptic.P384()} + kexAlgoMap[KeyExchangeECDHP256] = &ecdh{elliptic.P256()} + + if fips140.Enabled() { + defaultKexAlgos = slices.DeleteFunc(defaultKexAlgos, func(algo string) bool { + _, ok := kexAlgoMap[algo] + return !ok + }) + return + } + + p, _ := new(big.Int).SetString(oakleyGroup2, 16) + kexAlgoMap[InsecureKeyExchangeDH1SHA1] = &dhGroup{ + g: new(big.Int).SetInt64(2), + p: p, + pMinus1: new(big.Int).Sub(p, bigOne), + hashFunc: crypto.SHA1, + } + + p, _ = new(big.Int).SetString(oakleyGroup14, 16) + group14 := &dhGroup{ + g: new(big.Int).SetInt64(2), + p: p, + pMinus1: new(big.Int).Sub(p, bigOne), + } + + kexAlgoMap[InsecureKeyExchangeDH14SHA1] = &dhGroup{ + g: group14.g, p: group14.p, pMinus1: group14.pMinus1, + hashFunc: crypto.SHA1, + } + kexAlgoMap[KeyExchangeDH14SHA256] = &dhGroup{ + g: group14.g, p: group14.p, pMinus1: group14.pMinus1, + hashFunc: crypto.SHA256, + } + + p, _ = new(big.Int).SetString(oakleyGroup16, 16) + + kexAlgoMap[KeyExchangeDH16SHA512] = &dhGroup{ + g: new(big.Int).SetInt64(2), + p: p, + pMinus1: new(big.Int).Sub(p, bigOne), + hashFunc: crypto.SHA512, + } + + kexAlgoMap[KeyExchangeCurve25519] = &curve25519sha256{} + kexAlgoMap[keyExchangeCurve25519LibSSH] = &curve25519sha256{} + kexAlgoMap[InsecureKeyExchangeDHGEXSHA1] = &dhGEXSHA{hashFunc: crypto.SHA1} + kexAlgoMap[KeyExchangeDHGEXSHA256] = &dhGEXSHA{hashFunc: crypto.SHA256} +} + +// curve25519sha256 implements the curve25519-sha256 (formerly known as +// curve25519-sha256@libssh.org) key exchange method, as described in RFC 8731. +type curve25519sha256 struct{} + +type curve25519KeyPair struct { + priv [32]byte + pub [32]byte +} + +func (kp *curve25519KeyPair) generate(rand io.Reader) error { + if _, err := io.ReadFull(rand, kp.priv[:]); err != nil { + return err + } + p, err := curve25519.X25519(kp.priv[:], curve25519.Basepoint) + if err != nil { + return fmt.Errorf("curve25519: %w", err) + } + if len(p) != 32 { + return fmt.Errorf("curve25519: internal error: X25519 returned %d bytes, expected 32", len(p)) + } + copy(kp.pub[:], p) + return nil +} + +func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) { + var kp curve25519KeyPair + if err := kp.generate(rand); err != nil { + return nil, err + } + if err := c.writePacket(Marshal(&kexECDHInitMsg{kp.pub[:]})); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var reply kexECDHReplyMsg + if err = Unmarshal(packet, &reply); err != nil { + return nil, err + } + if len(reply.EphemeralPubKey) != 32 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong length") + } + + secret, err := curve25519.X25519(kp.priv[:], reply.EphemeralPubKey) + if err != nil { + return nil, fmt.Errorf("ssh: peer's curve25519 public value is not valid: %w", err) + } + + h := crypto.SHA256.New() + magics.write(h) + writeString(h, reply.HostKey) + writeString(h, kp.pub[:]) + writeString(h, reply.EphemeralPubKey) + + ki := new(big.Int).SetBytes(secret[:]) + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: reply.HostKey, + Signature: reply.Signature, + Hash: crypto.SHA256, + }, nil +} + +func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (result *kexResult, err error) { + packet, err := c.readPacket() + if err != nil { + return + } + var kexInit kexECDHInitMsg + if err = Unmarshal(packet, &kexInit); err != nil { + return + } + + if len(kexInit.ClientPubKey) != 32 { + return nil, errors.New("ssh: peer's curve25519 public value has wrong length") + } + + var kp curve25519KeyPair + if err := kp.generate(rand); err != nil { + return nil, err + } + + secret, err := curve25519.X25519(kp.priv[:], kexInit.ClientPubKey) + if err != nil { + return nil, fmt.Errorf("ssh: peer's curve25519 public value is not valid: %w", err) + } + + hostKeyBytes := priv.PublicKey().Marshal() + + h := crypto.SHA256.New() + magics.write(h) + writeString(h, hostKeyBytes) + writeString(h, kexInit.ClientPubKey) + writeString(h, kp.pub[:]) + + ki := new(big.Int).SetBytes(secret[:]) + K := make([]byte, intLength(ki)) + marshalInt(K, ki) + h.Write(K) + + H := h.Sum(nil) + + sig, err := signAndMarshal(priv, rand, H, algo) + if err != nil { + return nil, err + } + + reply := kexECDHReplyMsg{ + EphemeralPubKey: kp.pub[:], + HostKey: hostKeyBytes, + Signature: sig, + } + if err := c.writePacket(Marshal(&reply)); err != nil { + return nil, err + } + return &kexResult{ + H: H, + K: K, + HostKey: hostKeyBytes, + Signature: sig, + Hash: crypto.SHA256, + }, nil +} + +// dhGEXSHA implements the diffie-hellman-group-exchange-sha1 and +// diffie-hellman-group-exchange-sha256 key agreement protocols, +// as described in RFC 4419 +type dhGEXSHA struct { + hashFunc crypto.Hash +} + +const ( + dhGroupExchangeMinimumBits = 2048 + dhGroupExchangePreferredBits = 2048 + dhGroupExchangeMaximumBits = 8192 +) + +func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) { + // Send GexRequest + kexDHGexRequest := kexDHGexRequestMsg{ + MinBits: dhGroupExchangeMinimumBits, + PreferredBits: dhGroupExchangePreferredBits, + MaxBits: dhGroupExchangeMaximumBits, + } + if err := c.writePacket(Marshal(&kexDHGexRequest)); err != nil { + return nil, err + } + + // Receive GexGroup + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var msg kexDHGexGroupMsg + if err = Unmarshal(packet, &msg); err != nil { + return nil, err + } + + // reject if p's bit length < dhGroupExchangeMinimumBits or > dhGroupExchangeMaximumBits + if msg.P.BitLen() < dhGroupExchangeMinimumBits || msg.P.BitLen() > dhGroupExchangeMaximumBits { + return nil, fmt.Errorf("ssh: server-generated gex p is out of range (%d bits)", msg.P.BitLen()) + } + + // Check if g is safe by verifying that 1 < g < p-1 + pMinusOne := new(big.Int).Sub(msg.P, bigOne) + if msg.G.Cmp(bigOne) <= 0 || msg.G.Cmp(pMinusOne) >= 0 { + return nil, fmt.Errorf("ssh: server provided gex g is not safe") + } + + // Send GexInit + pHalf := new(big.Int).Rsh(msg.P, 1) + x, err := rand.Int(randSource, pHalf) + if err != nil { + return nil, err + } + X := new(big.Int).Exp(msg.G, x, msg.P) + kexDHGexInit := kexDHGexInitMsg{ + X: X, + } + if err := c.writePacket(Marshal(&kexDHGexInit)); err != nil { + return nil, err + } + + // Receive GexReply + packet, err = c.readPacket() + if err != nil { + return nil, err + } + + var kexDHGexReply kexDHGexReplyMsg + if err = Unmarshal(packet, &kexDHGexReply); err != nil { + return nil, err + } + + if kexDHGexReply.Y.Cmp(bigOne) <= 0 || kexDHGexReply.Y.Cmp(pMinusOne) >= 0 { + return nil, errors.New("ssh: DH parameter out of bounds") + } + kInt := new(big.Int).Exp(kexDHGexReply.Y, x, msg.P) + + // Check if k is safe by verifying that k > 1 and k < p - 1 + if kInt.Cmp(bigOne) <= 0 || kInt.Cmp(pMinusOne) >= 0 { + return nil, fmt.Errorf("ssh: derived k is not safe") + } + + h := gex.hashFunc.New() + magics.write(h) + writeString(h, kexDHGexReply.HostKey) + binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMinimumBits)) + binary.Write(h, binary.BigEndian, uint32(dhGroupExchangePreferredBits)) + binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMaximumBits)) + writeInt(h, msg.P) + writeInt(h, msg.G) + writeInt(h, X) + writeInt(h, kexDHGexReply.Y) + K := make([]byte, intLength(kInt)) + marshalInt(K, kInt) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: kexDHGexReply.HostKey, + Signature: kexDHGexReply.Signature, + Hash: gex.hashFunc, + }, nil +} + +// Server half implementation of the Diffie Hellman Key Exchange with SHA1 and SHA256. +func (gex *dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (result *kexResult, err error) { + // Receive GexRequest + packet, err := c.readPacket() + if err != nil { + return + } + var kexDHGexRequest kexDHGexRequestMsg + if err = Unmarshal(packet, &kexDHGexRequest); err != nil { + return + } + // We check that the request received is valid and that the MaxBits + // requested are at least equal to our supported minimum. This is the same + // check done in OpenSSH: + // https://github.com/openssh/openssh-portable/blob/80a2f64b/kexgexs.c#L94 + // + // Furthermore, we also check that the required MinBits are less than or + // equal to 4096 because we can use up to Oakley Group 16. + if kexDHGexRequest.MaxBits < kexDHGexRequest.MinBits || kexDHGexRequest.PreferredBits < kexDHGexRequest.MinBits || + kexDHGexRequest.MaxBits < kexDHGexRequest.PreferredBits || kexDHGexRequest.MaxBits < dhGroupExchangeMinimumBits || + kexDHGexRequest.MinBits > 4096 { + return nil, fmt.Errorf("ssh: DH GEX request out of range, min: %d, max: %d, preferred: %d", kexDHGexRequest.MinBits, + kexDHGexRequest.MaxBits, kexDHGexRequest.PreferredBits) + } + + var p *big.Int + // We hardcode sending Oakley Group 14 (2048 bits), Oakley Group 15 (3072 + // bits) or Oakley Group 16 (4096 bits), based on the requested max size. + if kexDHGexRequest.MaxBits < 3072 { + p, _ = new(big.Int).SetString(oakleyGroup14, 16) + } else if kexDHGexRequest.MaxBits < 4096 { + p, _ = new(big.Int).SetString(oakleyGroup15, 16) + } else { + p, _ = new(big.Int).SetString(oakleyGroup16, 16) + } + + g := big.NewInt(2) + msg := &kexDHGexGroupMsg{ + P: p, + G: g, + } + if err := c.writePacket(Marshal(msg)); err != nil { + return nil, err + } + + // Receive GexInit + packet, err = c.readPacket() + if err != nil { + return + } + var kexDHGexInit kexDHGexInitMsg + if err = Unmarshal(packet, &kexDHGexInit); err != nil { + return + } + + pHalf := new(big.Int).Rsh(p, 1) + + y, err := rand.Int(randSource, pHalf) + if err != nil { + return + } + Y := new(big.Int).Exp(g, y, p) + + pMinusOne := new(big.Int).Sub(p, bigOne) + if kexDHGexInit.X.Cmp(bigOne) <= 0 || kexDHGexInit.X.Cmp(pMinusOne) >= 0 { + return nil, errors.New("ssh: DH parameter out of bounds") + } + kInt := new(big.Int).Exp(kexDHGexInit.X, y, p) + + hostKeyBytes := priv.PublicKey().Marshal() + + h := gex.hashFunc.New() + magics.write(h) + writeString(h, hostKeyBytes) + binary.Write(h, binary.BigEndian, kexDHGexRequest.MinBits) + binary.Write(h, binary.BigEndian, kexDHGexRequest.PreferredBits) + binary.Write(h, binary.BigEndian, kexDHGexRequest.MaxBits) + writeInt(h, p) + writeInt(h, g) + writeInt(h, kexDHGexInit.X) + writeInt(h, Y) + + K := make([]byte, intLength(kInt)) + marshalInt(K, kInt) + h.Write(K) + + H := h.Sum(nil) + + // H is already a hash, but the hostkey signing will apply its + // own key-specific hash algorithm. + sig, err := signAndMarshal(priv, randSource, H, algo) + if err != nil { + return nil, err + } + + kexDHGexReply := kexDHGexReplyMsg{ + HostKey: hostKeyBytes, + Y: Y, + Signature: sig, + } + packet = Marshal(&kexDHGexReply) + + err = c.writePacket(packet) + + return &kexResult{ + H: H, + K: K, + HostKey: hostKeyBytes, + Signature: sig, + Hash: gex.hashFunc, + }, err +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/keys.go b/cli/vendor/golang.org/x/crypto/ssh/keys.go new file mode 100644 index 0000000..47a0753 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/keys.go @@ -0,0 +1,1823 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/dsa" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/md5" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "encoding/pem" + "errors" + "fmt" + "io" + "math/big" + "slices" + "strings" + + "golang.org/x/crypto/ssh/internal/bcrypt_pbkdf" +) + +// Public key algorithms names. These values can appear in PublicKey.Type, +// ClientConfig.HostKeyAlgorithms, Signature.Format, or as AlgorithmSigner +// arguments. +const ( + KeyAlgoRSA = "ssh-rsa" + // Deprecated: DSA is only supported at insecure key sizes, and was removed + // from major implementations. + KeyAlgoDSA = InsecureKeyAlgoDSA + // Deprecated: DSA is only supported at insecure key sizes, and was removed + // from major implementations. + InsecureKeyAlgoDSA = "ssh-dss" + KeyAlgoECDSA256 = "ecdsa-sha2-nistp256" + KeyAlgoSKECDSA256 = "sk-ecdsa-sha2-nistp256@openssh.com" + KeyAlgoECDSA384 = "ecdsa-sha2-nistp384" + KeyAlgoECDSA521 = "ecdsa-sha2-nistp521" + KeyAlgoED25519 = "ssh-ed25519" + KeyAlgoSKED25519 = "sk-ssh-ed25519@openssh.com" + + // KeyAlgoRSASHA256 and KeyAlgoRSASHA512 are only public key algorithms, not + // public key formats, so they can't appear as a PublicKey.Type. The + // corresponding PublicKey.Type is KeyAlgoRSA. See RFC 8332, Section 2. + KeyAlgoRSASHA256 = "rsa-sha2-256" + KeyAlgoRSASHA512 = "rsa-sha2-512" +) + +const ( + // Deprecated: use KeyAlgoRSA. + SigAlgoRSA = KeyAlgoRSA + // Deprecated: use KeyAlgoRSASHA256. + SigAlgoRSASHA2256 = KeyAlgoRSASHA256 + // Deprecated: use KeyAlgoRSASHA512. + SigAlgoRSASHA2512 = KeyAlgoRSASHA512 +) + +// parsePubKey parses a public key of the given algorithm. +// Use ParsePublicKey for keys with prepended algorithm. +func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err error) { + switch algo { + case KeyAlgoRSA: + return parseRSA(in) + case InsecureKeyAlgoDSA: + return parseDSA(in) + case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521: + return parseECDSA(in) + case KeyAlgoSKECDSA256: + return parseSKECDSA(in) + case KeyAlgoED25519: + return parseED25519(in) + case KeyAlgoSKED25519: + return parseSKEd25519(in) + case CertAlgoRSAv01, InsecureCertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01: + cert, err := parseCert(in, certKeyAlgoNames[algo]) + if err != nil { + return nil, nil, err + } + return cert, nil, nil + } + if keyFormat := keyFormatForAlgorithm(algo); keyFormat != "" { + return nil, nil, fmt.Errorf("ssh: signature algorithm %q isn't a key format; key is malformed and should be re-encoded with type %q", + algo, keyFormat) + } + + return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo) +} + +// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format +// (see sshd(8) manual page) once the options and key type fields have been +// removed. +func parseAuthorizedKey(in []byte) (out PublicKey, comment string, err error) { + in = bytes.TrimSpace(in) + + i := bytes.IndexAny(in, " \t") + if i == -1 { + i = len(in) + } + base64Key := in[:i] + + key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key))) + n, err := base64.StdEncoding.Decode(key, base64Key) + if err != nil { + return nil, "", err + } + key = key[:n] + out, err = ParsePublicKey(key) + if err != nil { + return nil, "", err + } + comment = string(bytes.TrimSpace(in[i:])) + return out, comment, nil +} + +// ParseKnownHosts parses an entry in the format of the known_hosts file. +// +// The known_hosts format is documented in the sshd(8) manual page. This +// function will parse a single entry from in. On successful return, marker +// will contain the optional marker value (i.e. "cert-authority" or "revoked") +// or else be empty, hosts will contain the hosts that this entry matches, +// pubKey will contain the public key and comment will contain any trailing +// comment at the end of the line. See the sshd(8) manual page for the various +// forms that a host string can take. +// +// The unparsed remainder of the input will be returned in rest. This function +// can be called repeatedly to parse multiple entries. +// +// If no entries were found in the input then err will be io.EOF. Otherwise a +// non-nil err value indicates a parse error. +func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey, comment string, rest []byte, err error) { + for len(in) > 0 { + end := bytes.IndexByte(in, '\n') + if end != -1 { + rest = in[end+1:] + in = in[:end] + } else { + rest = nil + } + + end = bytes.IndexByte(in, '\r') + if end != -1 { + in = in[:end] + } + + in = bytes.TrimSpace(in) + if len(in) == 0 || in[0] == '#' { + in = rest + continue + } + + i := bytes.IndexAny(in, " \t") + if i == -1 { + in = rest + continue + } + + // Strip out the beginning of the known_host key. + // This is either an optional marker or a (set of) hostname(s). + keyFields := bytes.Fields(in) + if len(keyFields) < 3 || len(keyFields) > 5 { + return "", nil, nil, "", nil, errors.New("ssh: invalid entry in known_hosts data") + } + + // keyFields[0] is either "@cert-authority", "@revoked" or a comma separated + // list of hosts + marker := "" + if keyFields[0][0] == '@' { + marker = string(keyFields[0][1:]) + keyFields = keyFields[1:] + } + + hosts := string(keyFields[0]) + // keyFields[1] contains the key type (e.g. “ssh-rsa”). + // However, that information is duplicated inside the + // base64-encoded key and so is ignored here. + + key := bytes.Join(keyFields[2:], []byte(" ")) + if pubKey, comment, err = parseAuthorizedKey(key); err != nil { + return "", nil, nil, "", nil, err + } + + return marker, strings.Split(hosts, ","), pubKey, comment, rest, nil + } + + return "", nil, nil, "", nil, io.EOF +} + +// ParseAuthorizedKey parses a public key from an authorized_keys file used in +// OpenSSH according to the sshd(8) manual page. Invalid lines are ignored. +func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) { + var lastErr error + for len(in) > 0 { + end := bytes.IndexByte(in, '\n') + if end != -1 { + rest = in[end+1:] + in = in[:end] + } else { + rest = nil + } + + end = bytes.IndexByte(in, '\r') + if end != -1 { + in = in[:end] + } + + in = bytes.TrimSpace(in) + if len(in) == 0 || in[0] == '#' { + in = rest + continue + } + + i := bytes.IndexAny(in, " \t") + if i == -1 { + in = rest + continue + } + + if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { + return out, comment, options, rest, nil + } else { + lastErr = err + } + + // No key type recognised. Maybe there's an options field at + // the beginning. + var b byte + inQuote := false + var candidateOptions []string + optionStart := 0 + for i, b = range in { + isEnd := !inQuote && (b == ' ' || b == '\t') + if (b == ',' && !inQuote) || isEnd { + if i-optionStart > 0 { + candidateOptions = append(candidateOptions, string(in[optionStart:i])) + } + optionStart = i + 1 + } + if isEnd { + break + } + if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) { + inQuote = !inQuote + } + } + for i < len(in) && (in[i] == ' ' || in[i] == '\t') { + i++ + } + if i == len(in) { + // Invalid line: unmatched quote + in = rest + continue + } + + in = in[i:] + i = bytes.IndexAny(in, " \t") + if i == -1 { + in = rest + continue + } + + if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { + options = candidateOptions + return out, comment, options, rest, nil + } else { + lastErr = err + } + + in = rest + continue + } + + if lastErr != nil { + return nil, "", nil, nil, fmt.Errorf("ssh: no key found; last parsing error for ignored line: %w", lastErr) + } + + return nil, "", nil, nil, errors.New("ssh: no key found") +} + +// ParsePublicKey parses an SSH public key or certificate formatted for use in +// the SSH wire protocol according to RFC 4253, section 6.6. +func ParsePublicKey(in []byte) (out PublicKey, err error) { + algo, in, ok := parseString(in) + if !ok { + return nil, errShortRead + } + var rest []byte + out, rest, err = parsePubKey(in, string(algo)) + if len(rest) > 0 { + return nil, errors.New("ssh: trailing junk in public key") + } + + return out, err +} + +// MarshalAuthorizedKey serializes key for inclusion in an OpenSSH +// authorized_keys file. The return value ends with newline. +func MarshalAuthorizedKey(key PublicKey) []byte { + b := &bytes.Buffer{} + b.WriteString(key.Type()) + b.WriteByte(' ') + e := base64.NewEncoder(base64.StdEncoding, b) + e.Write(key.Marshal()) + e.Close() + b.WriteByte('\n') + return b.Bytes() +} + +// MarshalPrivateKey returns a PEM block with the private key serialized in the +// OpenSSH format. +func MarshalPrivateKey(key crypto.PrivateKey, comment string) (*pem.Block, error) { + return marshalOpenSSHPrivateKey(key, comment, unencryptedOpenSSHMarshaler) +} + +// MarshalPrivateKeyWithPassphrase returns a PEM block holding the encrypted +// private key serialized in the OpenSSH format. +func MarshalPrivateKeyWithPassphrase(key crypto.PrivateKey, comment string, passphrase []byte) (*pem.Block, error) { + return marshalOpenSSHPrivateKey(key, comment, passphraseProtectedOpenSSHMarshaler(passphrase)) +} + +// PublicKey represents a public key using an unspecified algorithm. +// +// Some PublicKeys provided by this package also implement CryptoPublicKey. +type PublicKey interface { + // Type returns the key format name, e.g. "ssh-rsa". + Type() string + + // Marshal returns the serialized key data in SSH wire format, with the name + // prefix. To unmarshal the returned data, use the ParsePublicKey function. + Marshal() []byte + + // Verify that sig is a signature on the given data using this key. This + // method will hash the data appropriately first. sig.Format is allowed to + // be any signature algorithm compatible with the key type, the caller + // should check if it has more stringent requirements. + Verify(data []byte, sig *Signature) error +} + +// CryptoPublicKey, if implemented by a PublicKey, +// returns the underlying crypto.PublicKey form of the key. +type CryptoPublicKey interface { + CryptoPublicKey() crypto.PublicKey +} + +// A Signer can create signatures that verify against a public key. +// +// Some Signers provided by this package also implement MultiAlgorithmSigner. +type Signer interface { + // PublicKey returns the associated PublicKey. + PublicKey() PublicKey + + // Sign returns a signature for the given data. This method will hash the + // data appropriately first. The signature algorithm is expected to match + // the key format returned by the PublicKey.Type method (and not to be any + // alternative algorithm supported by the key format). + Sign(rand io.Reader, data []byte) (*Signature, error) +} + +// An AlgorithmSigner is a Signer that also supports specifying an algorithm to +// use for signing. +// +// An AlgorithmSigner can't advertise the algorithms it supports, unless it also +// implements MultiAlgorithmSigner, so it should be prepared to be invoked with +// every algorithm supported by the public key format. +type AlgorithmSigner interface { + Signer + + // SignWithAlgorithm is like Signer.Sign, but allows specifying a desired + // signing algorithm. Callers may pass an empty string for the algorithm in + // which case the AlgorithmSigner will use a default algorithm. This default + // doesn't currently control any behavior in this package. + SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) +} + +// MultiAlgorithmSigner is an AlgorithmSigner that also reports the algorithms +// supported by that signer. +type MultiAlgorithmSigner interface { + AlgorithmSigner + + // Algorithms returns the available algorithms in preference order. The list + // must not be empty, and it must not include certificate types. + Algorithms() []string +} + +// NewSignerWithAlgorithms returns a signer restricted to the specified +// algorithms. The algorithms must be set in preference order. The list must not +// be empty, and it must not include certificate types. An error is returned if +// the specified algorithms are incompatible with the public key type. +func NewSignerWithAlgorithms(signer AlgorithmSigner, algorithms []string) (MultiAlgorithmSigner, error) { + if len(algorithms) == 0 { + return nil, errors.New("ssh: please specify at least one valid signing algorithm") + } + var signerAlgos []string + supportedAlgos := algorithmsForKeyFormat(underlyingAlgo(signer.PublicKey().Type())) + if s, ok := signer.(*multiAlgorithmSigner); ok { + signerAlgos = s.Algorithms() + } else { + signerAlgos = supportedAlgos + } + + for _, algo := range algorithms { + if !slices.Contains(supportedAlgos, algo) { + return nil, fmt.Errorf("ssh: algorithm %q is not supported for key type %q", + algo, signer.PublicKey().Type()) + } + if !slices.Contains(signerAlgos, algo) { + return nil, fmt.Errorf("ssh: algorithm %q is restricted for the provided signer", algo) + } + } + return &multiAlgorithmSigner{ + AlgorithmSigner: signer, + supportedAlgorithms: algorithms, + }, nil +} + +type multiAlgorithmSigner struct { + AlgorithmSigner + supportedAlgorithms []string +} + +func (s *multiAlgorithmSigner) Algorithms() []string { + return s.supportedAlgorithms +} + +func (s *multiAlgorithmSigner) isAlgorithmSupported(algorithm string) bool { + if algorithm == "" { + algorithm = underlyingAlgo(s.PublicKey().Type()) + } + for _, algo := range s.supportedAlgorithms { + if algorithm == algo { + return true + } + } + return false +} + +func (s *multiAlgorithmSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) { + if !s.isAlgorithmSupported(algorithm) { + return nil, fmt.Errorf("ssh: algorithm %q is not supported: %v", algorithm, s.supportedAlgorithms) + } + return s.AlgorithmSigner.SignWithAlgorithm(rand, data, algorithm) +} + +type rsaPublicKey rsa.PublicKey + +func (r *rsaPublicKey) Type() string { + return "ssh-rsa" +} + +// parseRSA parses an RSA key according to RFC 4253, section 6.6. +func parseRSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + E *big.Int + N *big.Int + Rest []byte `ssh:"rest"` + } + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + if w.E.BitLen() > 24 { + return nil, nil, errors.New("ssh: exponent too large") + } + e := w.E.Int64() + if e < 3 || e&1 == 0 { + return nil, nil, errors.New("ssh: incorrect exponent") + } + + var key rsa.PublicKey + key.E = int(e) + key.N = w.N + return (*rsaPublicKey)(&key), w.Rest, nil +} + +func (r *rsaPublicKey) Marshal() []byte { + e := new(big.Int).SetInt64(int64(r.E)) + // RSA publickey struct layout should match the struct used by + // parseRSACert in the x/crypto/ssh/agent package. + wirekey := struct { + Name string + E *big.Int + N *big.Int + }{ + KeyAlgoRSA, + e, + r.N, + } + return Marshal(&wirekey) +} + +func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error { + supportedAlgos := algorithmsForKeyFormat(r.Type()) + if !slices.Contains(supportedAlgos, sig.Format) { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type()) + } + hash, err := hashFunc(sig.Format) + if err != nil { + return err + } + h := hash.New() + h.Write(data) + digest := h.Sum(nil) + + // Signatures in PKCS1v15 must match the key's modulus in + // length. However with SSH, some signers provide RSA + // signatures which are missing the MSB 0's of the bignum + // represented. With ssh-rsa signatures, this is encouraged by + // the spec (even though e.g. OpenSSH will give the full + // length unconditionally). With rsa-sha2-* signatures, the + // verifier is allowed to support these, even though they are + // out of spec. See RFC 4253 Section 6.6 for ssh-rsa and RFC + // 8332 Section 3 for rsa-sha2-* details. + // + // In practice: + // * OpenSSH always allows "short" signatures: + // https://github.com/openssh/openssh-portable/blob/V_9_8_P1/ssh-rsa.c#L526 + // but always generates padded signatures: + // https://github.com/openssh/openssh-portable/blob/V_9_8_P1/ssh-rsa.c#L439 + // + // * PuTTY versions 0.81 and earlier will generate short + // signatures for all RSA signature variants. Note that + // PuTTY is embedded in other software, such as WinSCP and + // FileZilla. At the time of writing, a patch has been + // applied to PuTTY to generate padded signatures for + // rsa-sha2-*, but not yet released: + // https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=a5bcf3d384e1bf15a51a6923c3724cbbee022d8e + // + // * SSH.NET versions 2024.0.0 and earlier will generate short + // signatures for all RSA signature variants, fixed in 2024.1.0: + // https://github.com/sshnet/SSH.NET/releases/tag/2024.1.0 + // + // As a result, we pad these up to the key size by inserting + // leading 0's. + // + // Note that support for short signatures with rsa-sha2-* may + // be removed in the future due to such signatures not being + // allowed by the spec. + blob := sig.Blob + keySize := (*rsa.PublicKey)(r).Size() + if len(blob) < keySize { + padded := make([]byte, keySize) + copy(padded[keySize-len(blob):], blob) + blob = padded + } + return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), hash, digest, blob) +} + +func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return (*rsa.PublicKey)(r) +} + +type dsaPublicKey dsa.PublicKey + +func (k *dsaPublicKey) Type() string { + return "ssh-dss" +} + +func checkDSAParams(param *dsa.Parameters) error { + // SSH specifies FIPS 186-2, which only provided a single size + // (1024 bits) DSA key. FIPS 186-3 allows for larger key + // sizes, which would confuse SSH. + if l := param.P.BitLen(); l != 1024 { + return fmt.Errorf("ssh: unsupported DSA key size %d", l) + } + + return nil +} + +// parseDSA parses an DSA key according to RFC 4253, section 6.6. +func parseDSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + P, Q, G, Y *big.Int + Rest []byte `ssh:"rest"` + } + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + param := dsa.Parameters{ + P: w.P, + Q: w.Q, + G: w.G, + } + if err := checkDSAParams(¶m); err != nil { + return nil, nil, err + } + + key := &dsaPublicKey{ + Parameters: param, + Y: w.Y, + } + return key, w.Rest, nil +} + +func (k *dsaPublicKey) Marshal() []byte { + // DSA publickey struct layout should match the struct used by + // parseDSACert in the x/crypto/ssh/agent package. + w := struct { + Name string + P, Q, G, Y *big.Int + }{ + k.Type(), + k.P, + k.Q, + k.G, + k.Y, + } + + return Marshal(&w) +} + +func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + hash, err := hashFunc(sig.Format) + if err != nil { + return err + } + h := hash.New() + h.Write(data) + digest := h.Sum(nil) + + // Per RFC 4253, section 6.6, + // The value for 'dss_signature_blob' is encoded as a string containing + // r, followed by s (which are 160-bit integers, without lengths or + // padding, unsigned, and in network byte order). + // For DSS purposes, sig.Blob should be exactly 40 bytes in length. + if len(sig.Blob) != 40 { + return errors.New("ssh: DSA signature parse error") + } + r := new(big.Int).SetBytes(sig.Blob[:20]) + s := new(big.Int).SetBytes(sig.Blob[20:]) + if dsa.Verify((*dsa.PublicKey)(k), digest, r, s) { + return nil + } + return errors.New("ssh: signature did not verify") +} + +func (k *dsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return (*dsa.PublicKey)(k) +} + +type dsaPrivateKey struct { + *dsa.PrivateKey +} + +func (k *dsaPrivateKey) PublicKey() PublicKey { + return (*dsaPublicKey)(&k.PrivateKey.PublicKey) +} + +func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) { + return k.SignWithAlgorithm(rand, data, k.PublicKey().Type()) +} + +func (k *dsaPrivateKey) Algorithms() []string { + return []string{k.PublicKey().Type()} +} + +func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) { + if algorithm != "" && algorithm != k.PublicKey().Type() { + return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm) + } + + hash, err := hashFunc(k.PublicKey().Type()) + if err != nil { + return nil, err + } + h := hash.New() + h.Write(data) + digest := h.Sum(nil) + r, s, err := dsa.Sign(rand, k.PrivateKey, digest) + if err != nil { + return nil, err + } + + sig := make([]byte, 40) + rb := r.Bytes() + sb := s.Bytes() + + copy(sig[20-len(rb):20], rb) + copy(sig[40-len(sb):], sb) + + return &Signature{ + Format: k.PublicKey().Type(), + Blob: sig, + }, nil +} + +type ecdsaPublicKey ecdsa.PublicKey + +func (k *ecdsaPublicKey) Type() string { + return "ecdsa-sha2-" + k.nistID() +} + +func (k *ecdsaPublicKey) nistID() string { + switch k.Params().BitSize { + case 256: + return "nistp256" + case 384: + return "nistp384" + case 521: + return "nistp521" + } + panic("ssh: unsupported ecdsa key size") +} + +type ed25519PublicKey ed25519.PublicKey + +func (k ed25519PublicKey) Type() string { + return KeyAlgoED25519 +} + +func parseED25519(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + KeyBytes []byte + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + if l := len(w.KeyBytes); l != ed25519.PublicKeySize { + return nil, nil, fmt.Errorf("invalid size %d for Ed25519 public key", l) + } + + return ed25519PublicKey(w.KeyBytes), w.Rest, nil +} + +func (k ed25519PublicKey) Marshal() []byte { + w := struct { + Name string + KeyBytes []byte + }{ + KeyAlgoED25519, + []byte(k), + } + return Marshal(&w) +} + +func (k ed25519PublicKey) Verify(b []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + if l := len(k); l != ed25519.PublicKeySize { + return fmt.Errorf("ssh: invalid size %d for Ed25519 public key", l) + } + + if ok := ed25519.Verify(ed25519.PublicKey(k), b, sig.Blob); !ok { + return errors.New("ssh: signature did not verify") + } + + return nil +} + +func (k ed25519PublicKey) CryptoPublicKey() crypto.PublicKey { + return ed25519.PublicKey(k) +} + +func supportedEllipticCurve(curve elliptic.Curve) bool { + return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521() +} + +// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1. +func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + Curve string + KeyBytes []byte + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + key := new(ecdsa.PublicKey) + + switch w.Curve { + case "nistp256": + key.Curve = elliptic.P256() + case "nistp384": + key.Curve = elliptic.P384() + case "nistp521": + key.Curve = elliptic.P521() + default: + return nil, nil, errors.New("ssh: unsupported curve") + } + + key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes) + if key.X == nil || key.Y == nil { + return nil, nil, errors.New("ssh: invalid curve point") + } + return (*ecdsaPublicKey)(key), w.Rest, nil +} + +func (k *ecdsaPublicKey) Marshal() []byte { + // See RFC 5656, section 3.1. + keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y) + // ECDSA publickey struct layout should match the struct used by + // parseECDSACert in the x/crypto/ssh/agent package. + w := struct { + Name string + ID string + Key []byte + }{ + k.Type(), + k.nistID(), + keyBytes, + } + + return Marshal(&w) +} + +func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + hash, err := hashFunc(sig.Format) + if err != nil { + return err + } + h := hash.New() + h.Write(data) + digest := h.Sum(nil) + + // Per RFC 5656, section 3.1.2, + // The ecdsa_signature_blob value has the following specific encoding: + // mpint r + // mpint s + var ecSig struct { + R *big.Int + S *big.Int + } + + if err := Unmarshal(sig.Blob, &ecSig); err != nil { + return err + } + + if ecdsa.Verify((*ecdsa.PublicKey)(k), digest, ecSig.R, ecSig.S) { + return nil + } + return errors.New("ssh: signature did not verify") +} + +func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey { + return (*ecdsa.PublicKey)(k) +} + +// skFields holds the additional fields present in U2F/FIDO2 signatures. +// See openssh/PROTOCOL.u2f 'SSH U2F Signatures' for details. +type skFields struct { + // Flags contains U2F/FIDO2 flags such as 'user present' + Flags byte + // Counter is a monotonic signature counter which can be + // used to detect concurrent use of a private key, should + // it be extracted from hardware. + Counter uint32 +} + +type skECDSAPublicKey struct { + // application is a URL-like string, typically "ssh:" for SSH. + // see openssh/PROTOCOL.u2f for details. + application string + ecdsa.PublicKey +} + +func (k *skECDSAPublicKey) Type() string { + return KeyAlgoSKECDSA256 +} + +func (k *skECDSAPublicKey) nistID() string { + return "nistp256" +} + +func parseSKECDSA(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + Curve string + KeyBytes []byte + Application string + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + key := new(skECDSAPublicKey) + key.application = w.Application + + if w.Curve != "nistp256" { + return nil, nil, errors.New("ssh: unsupported curve") + } + key.Curve = elliptic.P256() + + key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes) + if key.X == nil || key.Y == nil { + return nil, nil, errors.New("ssh: invalid curve point") + } + + return key, w.Rest, nil +} + +func (k *skECDSAPublicKey) Marshal() []byte { + // See RFC 5656, section 3.1. + keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y) + w := struct { + Name string + ID string + Key []byte + Application string + }{ + k.Type(), + k.nistID(), + keyBytes, + k.application, + } + + return Marshal(&w) +} + +func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + hash, err := hashFunc(sig.Format) + if err != nil { + return err + } + h := hash.New() + h.Write([]byte(k.application)) + appDigest := h.Sum(nil) + + h.Reset() + h.Write(data) + dataDigest := h.Sum(nil) + + var ecSig struct { + R *big.Int + S *big.Int + } + if err := Unmarshal(sig.Blob, &ecSig); err != nil { + return err + } + + var skf skFields + if err := Unmarshal(sig.Rest, &skf); err != nil { + return err + } + + blob := struct { + ApplicationDigest []byte `ssh:"rest"` + Flags byte + Counter uint32 + MessageDigest []byte `ssh:"rest"` + }{ + appDigest, + skf.Flags, + skf.Counter, + dataDigest, + } + + original := Marshal(blob) + + h.Reset() + h.Write(original) + digest := h.Sum(nil) + + if ecdsa.Verify((*ecdsa.PublicKey)(&k.PublicKey), digest, ecSig.R, ecSig.S) { + return nil + } + return errors.New("ssh: signature did not verify") +} + +func (k *skECDSAPublicKey) CryptoPublicKey() crypto.PublicKey { + return &k.PublicKey +} + +type skEd25519PublicKey struct { + // application is a URL-like string, typically "ssh:" for SSH. + // see openssh/PROTOCOL.u2f for details. + application string + ed25519.PublicKey +} + +func (k *skEd25519PublicKey) Type() string { + return KeyAlgoSKED25519 +} + +func parseSKEd25519(in []byte) (out PublicKey, rest []byte, err error) { + var w struct { + KeyBytes []byte + Application string + Rest []byte `ssh:"rest"` + } + + if err := Unmarshal(in, &w); err != nil { + return nil, nil, err + } + + if l := len(w.KeyBytes); l != ed25519.PublicKeySize { + return nil, nil, fmt.Errorf("invalid size %d for Ed25519 public key", l) + } + + key := new(skEd25519PublicKey) + key.application = w.Application + key.PublicKey = ed25519.PublicKey(w.KeyBytes) + + return key, w.Rest, nil +} + +func (k *skEd25519PublicKey) Marshal() []byte { + w := struct { + Name string + KeyBytes []byte + Application string + }{ + KeyAlgoSKED25519, + []byte(k.PublicKey), + k.application, + } + return Marshal(&w) +} + +func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error { + if sig.Format != k.Type() { + return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) + } + if l := len(k.PublicKey); l != ed25519.PublicKeySize { + return fmt.Errorf("invalid size %d for Ed25519 public key", l) + } + + hash, err := hashFunc(sig.Format) + if err != nil { + return err + } + h := hash.New() + h.Write([]byte(k.application)) + appDigest := h.Sum(nil) + + h.Reset() + h.Write(data) + dataDigest := h.Sum(nil) + + var edSig struct { + Signature []byte `ssh:"rest"` + } + + if err := Unmarshal(sig.Blob, &edSig); err != nil { + return err + } + + var skf skFields + if err := Unmarshal(sig.Rest, &skf); err != nil { + return err + } + + blob := struct { + ApplicationDigest []byte `ssh:"rest"` + Flags byte + Counter uint32 + MessageDigest []byte `ssh:"rest"` + }{ + appDigest, + skf.Flags, + skf.Counter, + dataDigest, + } + + original := Marshal(blob) + + if ok := ed25519.Verify(k.PublicKey, original, edSig.Signature); !ok { + return errors.New("ssh: signature did not verify") + } + + return nil +} + +func (k *skEd25519PublicKey) CryptoPublicKey() crypto.PublicKey { + return k.PublicKey +} + +// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey, +// *ecdsa.PrivateKey or any other crypto.Signer and returns a +// corresponding Signer instance. ECDSA keys must use P-256, P-384 or +// P-521. DSA keys must use parameter size L1024N160. +func NewSignerFromKey(key interface{}) (Signer, error) { + switch key := key.(type) { + case crypto.Signer: + return NewSignerFromSigner(key) + case *dsa.PrivateKey: + return newDSAPrivateKey(key) + default: + return nil, fmt.Errorf("ssh: unsupported key type %T", key) + } +} + +func newDSAPrivateKey(key *dsa.PrivateKey) (Signer, error) { + if err := checkDSAParams(&key.PublicKey.Parameters); err != nil { + return nil, err + } + + return &dsaPrivateKey{key}, nil +} + +type wrappedSigner struct { + signer crypto.Signer + pubKey PublicKey +} + +// NewSignerFromSigner takes any crypto.Signer implementation and +// returns a corresponding Signer interface. This can be used, for +// example, with keys kept in hardware modules. +func NewSignerFromSigner(signer crypto.Signer) (Signer, error) { + pubKey, err := NewPublicKey(signer.Public()) + if err != nil { + return nil, err + } + + return &wrappedSigner{signer, pubKey}, nil +} + +func (s *wrappedSigner) PublicKey() PublicKey { + return s.pubKey +} + +func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) { + return s.SignWithAlgorithm(rand, data, s.pubKey.Type()) +} + +func (s *wrappedSigner) Algorithms() []string { + return algorithmsForKeyFormat(s.pubKey.Type()) +} + +func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) { + if algorithm == "" { + algorithm = s.pubKey.Type() + } + + if !slices.Contains(s.Algorithms(), algorithm) { + return nil, fmt.Errorf("ssh: unsupported signature algorithm %q for key format %q", algorithm, s.pubKey.Type()) + } + + hashFunc, err := hashFunc(algorithm) + if err != nil { + return nil, err + } + var digest []byte + if hashFunc != 0 { + h := hashFunc.New() + h.Write(data) + digest = h.Sum(nil) + } else { + digest = data + } + + signature, err := s.signer.Sign(rand, digest, hashFunc) + if err != nil { + return nil, err + } + + // crypto.Signer.Sign is expected to return an ASN.1-encoded signature + // for ECDSA and DSA, but that's not the encoding expected by SSH, so + // re-encode. + switch s.pubKey.(type) { + case *ecdsaPublicKey, *dsaPublicKey: + type asn1Signature struct { + R, S *big.Int + } + asn1Sig := new(asn1Signature) + _, err := asn1.Unmarshal(signature, asn1Sig) + if err != nil { + return nil, err + } + + switch s.pubKey.(type) { + case *ecdsaPublicKey: + signature = Marshal(asn1Sig) + + case *dsaPublicKey: + signature = make([]byte, 40) + r := asn1Sig.R.Bytes() + s := asn1Sig.S.Bytes() + copy(signature[20-len(r):20], r) + copy(signature[40-len(s):40], s) + } + } + + return &Signature{ + Format: algorithm, + Blob: signature, + }, nil +} + +// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey, +// or ed25519.PublicKey returns a corresponding PublicKey instance. +// ECDSA keys must use P-256, P-384 or P-521. +func NewPublicKey(key interface{}) (PublicKey, error) { + switch key := key.(type) { + case *rsa.PublicKey: + return (*rsaPublicKey)(key), nil + case *ecdsa.PublicKey: + if !supportedEllipticCurve(key.Curve) { + return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported") + } + return (*ecdsaPublicKey)(key), nil + case *dsa.PublicKey: + return (*dsaPublicKey)(key), nil + case ed25519.PublicKey: + if l := len(key); l != ed25519.PublicKeySize { + return nil, fmt.Errorf("ssh: invalid size %d for Ed25519 public key", l) + } + return ed25519PublicKey(key), nil + default: + return nil, fmt.Errorf("ssh: unsupported key type %T", key) + } +} + +// ParsePrivateKey returns a Signer from a PEM encoded private key. It supports +// the same keys as ParseRawPrivateKey. If the private key is encrypted, it +// will return a PassphraseMissingError. +func ParsePrivateKey(pemBytes []byte) (Signer, error) { + key, err := ParseRawPrivateKey(pemBytes) + if err != nil { + return nil, err + } + + return NewSignerFromKey(key) +} + +// ParsePrivateKeyWithPassphrase returns a Signer from a PEM encoded private +// key and passphrase. It supports the same keys as +// ParseRawPrivateKeyWithPassphrase. +func ParsePrivateKeyWithPassphrase(pemBytes, passphrase []byte) (Signer, error) { + key, err := ParseRawPrivateKeyWithPassphrase(pemBytes, passphrase) + if err != nil { + return nil, err + } + + return NewSignerFromKey(key) +} + +// encryptedBlock tells whether a private key is +// encrypted by examining its Proc-Type header +// for a mention of ENCRYPTED +// according to RFC 1421 Section 4.6.1.1. +func encryptedBlock(block *pem.Block) bool { + return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED") +} + +// A PassphraseMissingError indicates that parsing this private key requires a +// passphrase. Use ParsePrivateKeyWithPassphrase. +type PassphraseMissingError struct { + // PublicKey will be set if the private key format includes an unencrypted + // public key along with the encrypted private key. + PublicKey PublicKey +} + +func (*PassphraseMissingError) Error() string { + return "ssh: this private key is passphrase protected" +} + +// ParseRawPrivateKey returns a private key from a PEM encoded private key. It supports +// RSA, DSA, ECDSA, and Ed25519 private keys in PKCS#1, PKCS#8, OpenSSL, and OpenSSH +// formats. If the private key is encrypted, it will return a PassphraseMissingError. +func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, errors.New("ssh: no key found") + } + + if encryptedBlock(block) { + return nil, &PassphraseMissingError{} + } + + switch block.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(block.Bytes) + // RFC5208 - https://tools.ietf.org/html/rfc5208 + case "PRIVATE KEY": + return x509.ParsePKCS8PrivateKey(block.Bytes) + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(block.Bytes) + case "DSA PRIVATE KEY": + return ParseDSAPrivateKey(block.Bytes) + case "OPENSSH PRIVATE KEY": + return parseOpenSSHPrivateKey(block.Bytes, unencryptedOpenSSHKey) + default: + return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) + } +} + +// ParseRawPrivateKeyWithPassphrase returns a private key decrypted with +// passphrase from a PEM encoded private key. If the passphrase is wrong, it +// will return x509.IncorrectPasswordError. +func ParseRawPrivateKeyWithPassphrase(pemBytes, passphrase []byte) (interface{}, error) { + block, _ := pem.Decode(pemBytes) + if block == nil { + return nil, errors.New("ssh: no key found") + } + + if block.Type == "OPENSSH PRIVATE KEY" { + return parseOpenSSHPrivateKey(block.Bytes, passphraseProtectedOpenSSHKey(passphrase)) + } + + if !encryptedBlock(block) || !x509.IsEncryptedPEMBlock(block) { + return nil, errors.New("ssh: not an encrypted key") + } + + buf, err := x509.DecryptPEMBlock(block, passphrase) + if err != nil { + if err == x509.IncorrectPasswordError { + return nil, err + } + return nil, fmt.Errorf("ssh: cannot decode encrypted private keys: %v", err) + } + + var result interface{} + + switch block.Type { + case "RSA PRIVATE KEY": + result, err = x509.ParsePKCS1PrivateKey(buf) + case "EC PRIVATE KEY": + result, err = x509.ParseECPrivateKey(buf) + case "DSA PRIVATE KEY": + result, err = ParseDSAPrivateKey(buf) + default: + err = fmt.Errorf("ssh: unsupported key type %q", block.Type) + } + // Because of deficiencies in the format, DecryptPEMBlock does not always + // detect an incorrect password. In these cases decrypted DER bytes is + // random noise. If the parsing of the key returns an asn1.StructuralError + // we return x509.IncorrectPasswordError. + if _, ok := err.(asn1.StructuralError); ok { + return nil, x509.IncorrectPasswordError + } + + return result, err +} + +// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as +// specified by the OpenSSL DSA man page. +func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) { + var k struct { + Version int + P *big.Int + Q *big.Int + G *big.Int + Pub *big.Int + Priv *big.Int + } + rest, err := asn1.Unmarshal(der, &k) + if err != nil { + return nil, errors.New("ssh: failed to parse DSA key: " + err.Error()) + } + if len(rest) > 0 { + return nil, errors.New("ssh: garbage after DSA key") + } + + return &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: k.P, + Q: k.Q, + G: k.G, + }, + Y: k.Pub, + }, + X: k.Priv, + }, nil +} + +func unencryptedOpenSSHKey(cipherName, kdfName, kdfOpts string, privKeyBlock []byte) ([]byte, error) { + if kdfName != "none" || cipherName != "none" { + return nil, &PassphraseMissingError{} + } + if kdfOpts != "" { + return nil, errors.New("ssh: invalid openssh private key") + } + return privKeyBlock, nil +} + +func passphraseProtectedOpenSSHKey(passphrase []byte) openSSHDecryptFunc { + return func(cipherName, kdfName, kdfOpts string, privKeyBlock []byte) ([]byte, error) { + if kdfName == "none" || cipherName == "none" { + return nil, errors.New("ssh: key is not password protected") + } + if kdfName != "bcrypt" { + return nil, fmt.Errorf("ssh: unknown KDF %q, only supports %q", kdfName, "bcrypt") + } + + var opts struct { + Salt string + Rounds uint32 + } + if err := Unmarshal([]byte(kdfOpts), &opts); err != nil { + return nil, err + } + + k, err := bcrypt_pbkdf.Key(passphrase, []byte(opts.Salt), int(opts.Rounds), 32+16) + if err != nil { + return nil, err + } + key, iv := k[:32], k[32:] + + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + switch cipherName { + case "aes256-ctr": + ctr := cipher.NewCTR(c, iv) + ctr.XORKeyStream(privKeyBlock, privKeyBlock) + case "aes256-cbc": + if len(privKeyBlock)%c.BlockSize() != 0 { + return nil, fmt.Errorf("ssh: invalid encrypted private key length, not a multiple of the block size") + } + cbc := cipher.NewCBCDecrypter(c, iv) + cbc.CryptBlocks(privKeyBlock, privKeyBlock) + default: + return nil, fmt.Errorf("ssh: unknown cipher %q, only supports %q or %q", cipherName, "aes256-ctr", "aes256-cbc") + } + + return privKeyBlock, nil + } +} + +func unencryptedOpenSSHMarshaler(privKeyBlock []byte) ([]byte, string, string, string, error) { + key := generateOpenSSHPadding(privKeyBlock, 8) + return key, "none", "none", "", nil +} + +func passphraseProtectedOpenSSHMarshaler(passphrase []byte) openSSHEncryptFunc { + return func(privKeyBlock []byte) ([]byte, string, string, string, error) { + salt := make([]byte, 16) + if _, err := rand.Read(salt); err != nil { + return nil, "", "", "", err + } + + opts := struct { + Salt []byte + Rounds uint32 + }{salt, 16} + + // Derive key to encrypt the private key block. + k, err := bcrypt_pbkdf.Key(passphrase, salt, int(opts.Rounds), 32+aes.BlockSize) + if err != nil { + return nil, "", "", "", err + } + + // Add padding matching the block size of AES. + keyBlock := generateOpenSSHPadding(privKeyBlock, aes.BlockSize) + + // Encrypt the private key using the derived secret. + + dst := make([]byte, len(keyBlock)) + key, iv := k[:32], k[32:] + block, err := aes.NewCipher(key) + if err != nil { + return nil, "", "", "", err + } + + stream := cipher.NewCTR(block, iv) + stream.XORKeyStream(dst, keyBlock) + + return dst, "aes256-ctr", "bcrypt", string(Marshal(opts)), nil + } +} + +const privateKeyAuthMagic = "openssh-key-v1\x00" + +type openSSHDecryptFunc func(CipherName, KdfName, KdfOpts string, PrivKeyBlock []byte) ([]byte, error) +type openSSHEncryptFunc func(PrivKeyBlock []byte) (ProtectedKeyBlock []byte, cipherName, kdfName, kdfOptions string, err error) + +type openSSHEncryptedPrivateKey struct { + CipherName string + KdfName string + KdfOpts string + NumKeys uint32 + PubKey []byte + PrivKeyBlock []byte + Rest []byte `ssh:"rest"` +} + +type openSSHPrivateKey struct { + Check1 uint32 + Check2 uint32 + Keytype string + Rest []byte `ssh:"rest"` +} + +type openSSHRSAPrivateKey struct { + N *big.Int + E *big.Int + D *big.Int + Iqmp *big.Int + P *big.Int + Q *big.Int + Comment string + Pad []byte `ssh:"rest"` +} + +type openSSHEd25519PrivateKey struct { + Pub []byte + Priv []byte + Comment string + Pad []byte `ssh:"rest"` +} + +type openSSHECDSAPrivateKey struct { + Curve string + Pub []byte + D *big.Int + Comment string + Pad []byte `ssh:"rest"` +} + +// parseOpenSSHPrivateKey parses an OpenSSH private key, using the decrypt +// function to unwrap the encrypted portion. unencryptedOpenSSHKey can be used +// as the decrypt function to parse an unencrypted private key. See +// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key. +func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.PrivateKey, error) { + if len(key) < len(privateKeyAuthMagic) || string(key[:len(privateKeyAuthMagic)]) != privateKeyAuthMagic { + return nil, errors.New("ssh: invalid openssh private key format") + } + remaining := key[len(privateKeyAuthMagic):] + + var w openSSHEncryptedPrivateKey + if err := Unmarshal(remaining, &w); err != nil { + return nil, err + } + if w.NumKeys != 1 { + // We only support single key files, and so does OpenSSH. + // https://github.com/openssh/openssh-portable/blob/4103a3ec7/sshkey.c#L4171 + return nil, errors.New("ssh: multi-key files are not supported") + } + + privKeyBlock, err := decrypt(w.CipherName, w.KdfName, w.KdfOpts, w.PrivKeyBlock) + if err != nil { + if err, ok := err.(*PassphraseMissingError); ok { + pub, errPub := ParsePublicKey(w.PubKey) + if errPub != nil { + return nil, fmt.Errorf("ssh: failed to parse embedded public key: %v", errPub) + } + err.PublicKey = pub + } + return nil, err + } + + var pk1 openSSHPrivateKey + if err := Unmarshal(privKeyBlock, &pk1); err != nil || pk1.Check1 != pk1.Check2 { + if w.CipherName != "none" { + return nil, x509.IncorrectPasswordError + } + return nil, errors.New("ssh: malformed OpenSSH key") + } + + switch pk1.Keytype { + case KeyAlgoRSA: + var key openSSHRSAPrivateKey + if err := Unmarshal(pk1.Rest, &key); err != nil { + return nil, err + } + + if err := checkOpenSSHKeyPadding(key.Pad); err != nil { + return nil, err + } + + pk := &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: key.N, + E: int(key.E.Int64()), + }, + D: key.D, + Primes: []*big.Int{key.P, key.Q}, + } + + if err := pk.Validate(); err != nil { + return nil, err + } + + pk.Precompute() + + return pk, nil + case KeyAlgoED25519: + var key openSSHEd25519PrivateKey + if err := Unmarshal(pk1.Rest, &key); err != nil { + return nil, err + } + + if len(key.Priv) != ed25519.PrivateKeySize { + return nil, errors.New("ssh: private key unexpected length") + } + + if err := checkOpenSSHKeyPadding(key.Pad); err != nil { + return nil, err + } + + pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize)) + copy(pk, key.Priv) + return &pk, nil + case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521: + var key openSSHECDSAPrivateKey + if err := Unmarshal(pk1.Rest, &key); err != nil { + return nil, err + } + + if err := checkOpenSSHKeyPadding(key.Pad); err != nil { + return nil, err + } + + var curve elliptic.Curve + switch key.Curve { + case "nistp256": + curve = elliptic.P256() + case "nistp384": + curve = elliptic.P384() + case "nistp521": + curve = elliptic.P521() + default: + return nil, errors.New("ssh: unhandled elliptic curve: " + key.Curve) + } + + X, Y := elliptic.Unmarshal(curve, key.Pub) + if X == nil || Y == nil { + return nil, errors.New("ssh: failed to unmarshal public key") + } + + if key.D.Cmp(curve.Params().N) >= 0 { + return nil, errors.New("ssh: scalar is out of range") + } + + x, y := curve.ScalarBaseMult(key.D.Bytes()) + if x.Cmp(X) != 0 || y.Cmp(Y) != 0 { + return nil, errors.New("ssh: public key does not match private key") + } + + return &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: curve, + X: X, + Y: Y, + }, + D: key.D, + }, nil + default: + return nil, errors.New("ssh: unhandled key type") + } +} + +func marshalOpenSSHPrivateKey(key crypto.PrivateKey, comment string, encrypt openSSHEncryptFunc) (*pem.Block, error) { + var w openSSHEncryptedPrivateKey + var pk1 openSSHPrivateKey + + // Random check bytes. + var check uint32 + if err := binary.Read(rand.Reader, binary.BigEndian, &check); err != nil { + return nil, err + } + + pk1.Check1 = check + pk1.Check2 = check + w.NumKeys = 1 + + // Use a []byte directly on ed25519 keys. + if k, ok := key.(*ed25519.PrivateKey); ok { + key = *k + } + + switch k := key.(type) { + case *rsa.PrivateKey: + E := new(big.Int).SetInt64(int64(k.PublicKey.E)) + // Marshal public key: + // E and N are in reversed order in the public and private key. + pubKey := struct { + KeyType string + E *big.Int + N *big.Int + }{ + KeyAlgoRSA, + E, k.PublicKey.N, + } + w.PubKey = Marshal(pubKey) + + // Marshal private key. + key := openSSHRSAPrivateKey{ + N: k.PublicKey.N, + E: E, + D: k.D, + Iqmp: k.Precomputed.Qinv, + P: k.Primes[0], + Q: k.Primes[1], + Comment: comment, + } + pk1.Keytype = KeyAlgoRSA + pk1.Rest = Marshal(key) + case ed25519.PrivateKey: + pub := make([]byte, ed25519.PublicKeySize) + priv := make([]byte, ed25519.PrivateKeySize) + copy(pub, k[32:]) + copy(priv, k) + + // Marshal public key. + pubKey := struct { + KeyType string + Pub []byte + }{ + KeyAlgoED25519, pub, + } + w.PubKey = Marshal(pubKey) + + // Marshal private key. + key := openSSHEd25519PrivateKey{ + Pub: pub, + Priv: priv, + Comment: comment, + } + pk1.Keytype = KeyAlgoED25519 + pk1.Rest = Marshal(key) + case *ecdsa.PrivateKey: + var curve, keyType string + switch name := k.Curve.Params().Name; name { + case "P-256": + curve = "nistp256" + keyType = KeyAlgoECDSA256 + case "P-384": + curve = "nistp384" + keyType = KeyAlgoECDSA384 + case "P-521": + curve = "nistp521" + keyType = KeyAlgoECDSA521 + default: + return nil, errors.New("ssh: unhandled elliptic curve " + name) + } + + pub := elliptic.Marshal(k.Curve, k.PublicKey.X, k.PublicKey.Y) + + // Marshal public key. + pubKey := struct { + KeyType string + Curve string + Pub []byte + }{ + keyType, curve, pub, + } + w.PubKey = Marshal(pubKey) + + // Marshal private key. + key := openSSHECDSAPrivateKey{ + Curve: curve, + Pub: pub, + D: k.D, + Comment: comment, + } + pk1.Keytype = keyType + pk1.Rest = Marshal(key) + default: + return nil, fmt.Errorf("ssh: unsupported key type %T", k) + } + + var err error + // Add padding and encrypt the key if necessary. + w.PrivKeyBlock, w.CipherName, w.KdfName, w.KdfOpts, err = encrypt(Marshal(pk1)) + if err != nil { + return nil, err + } + + b := Marshal(w) + block := &pem.Block{ + Type: "OPENSSH PRIVATE KEY", + Bytes: append([]byte(privateKeyAuthMagic), b...), + } + return block, nil +} + +func checkOpenSSHKeyPadding(pad []byte) error { + for i, b := range pad { + if int(b) != i+1 { + return errors.New("ssh: padding not as expected") + } + } + return nil +} + +func generateOpenSSHPadding(block []byte, blockSize int) []byte { + for i, l := 0, len(block); (l+i)%blockSize != 0; i++ { + block = append(block, byte(i+1)) + } + return block +} + +// FingerprintLegacyMD5 returns the user presentation of the key's +// fingerprint as described by RFC 4716 section 4. +func FingerprintLegacyMD5(pubKey PublicKey) string { + md5sum := md5.Sum(pubKey.Marshal()) + hexarray := make([]string, len(md5sum)) + for i, c := range md5sum { + hexarray[i] = hex.EncodeToString([]byte{c}) + } + return strings.Join(hexarray, ":") +} + +// FingerprintSHA256 returns the user presentation of the key's +// fingerprint as unpadded base64 encoded sha256 hash. +// This format was introduced from OpenSSH 6.8. +// https://www.openssh.com/txt/release-6.8 +// https://tools.ietf.org/html/rfc4648#section-3.2 (unpadded base64 encoding) +func FingerprintSHA256(pubKey PublicKey) string { + sha256sum := sha256.Sum256(pubKey.Marshal()) + hash := base64.RawStdEncoding.EncodeToString(sha256sum[:]) + return "SHA256:" + hash +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/mac.go b/cli/vendor/golang.org/x/crypto/ssh/mac.go new file mode 100644 index 0000000..87d626f --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/mac.go @@ -0,0 +1,84 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +// Message authentication support + +import ( + "crypto/fips140" + "crypto/hmac" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "hash" + "slices" +) + +type macMode struct { + keySize int + etm bool + new func(key []byte) hash.Hash +} + +// truncatingMAC wraps around a hash.Hash and truncates the output digest to +// a given size. +type truncatingMAC struct { + length int + hmac hash.Hash +} + +func (t truncatingMAC) Write(data []byte) (int, error) { + return t.hmac.Write(data) +} + +func (t truncatingMAC) Sum(in []byte) []byte { + out := t.hmac.Sum(in) + return out[:len(in)+t.length] +} + +func (t truncatingMAC) Reset() { + t.hmac.Reset() +} + +func (t truncatingMAC) Size() int { + return t.length +} + +func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() } + +// macModes defines the supported MACs. MACs not included are not supported +// and will not be negotiated, even if explicitly configured. When FIPS mode is +// enabled, only FIPS-approved algorithms are included. +var macModes = map[string]*macMode{} + +func init() { + macModes[HMACSHA512ETM] = &macMode{64, true, func(key []byte) hash.Hash { + return hmac.New(sha512.New, key) + }} + macModes[HMACSHA256ETM] = &macMode{32, true, func(key []byte) hash.Hash { + return hmac.New(sha256.New, key) + }} + macModes[HMACSHA512] = &macMode{64, false, func(key []byte) hash.Hash { + return hmac.New(sha512.New, key) + }} + macModes[HMACSHA256] = &macMode{32, false, func(key []byte) hash.Hash { + return hmac.New(sha256.New, key) + }} + + if fips140.Enabled() { + defaultMACs = slices.DeleteFunc(defaultMACs, func(algo string) bool { + _, ok := macModes[algo] + return !ok + }) + return + } + + macModes[HMACSHA1] = &macMode{20, false, func(key []byte) hash.Hash { + return hmac.New(sha1.New, key) + }} + macModes[InsecureHMACSHA196] = &macMode{20, false, func(key []byte) hash.Hash { + return truncatingMAC{12, hmac.New(sha1.New, key)} + }} +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/messages.go b/cli/vendor/golang.org/x/crypto/ssh/messages.go new file mode 100644 index 0000000..ab22c3d --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/messages.go @@ -0,0 +1,893 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + "reflect" + "strconv" + "strings" +) + +// These are SSH message type numbers. They are scattered around several +// documents but many were taken from [SSH-PARAMETERS]. +const ( + msgIgnore = 2 + msgUnimplemented = 3 + msgDebug = 4 + msgNewKeys = 21 +) + +// SSH messages: +// +// These structures mirror the wire format of the corresponding SSH messages. +// They are marshaled using reflection with the marshal and unmarshal functions +// in this file. The only wrinkle is that a final member of type []byte with a +// ssh tag of "rest" receives the remainder of a packet when unmarshaling. + +// See RFC 4253, section 11.1. +const msgDisconnect = 1 + +// disconnectMsg is the message that signals a disconnect. It is also +// the error type returned from mux.Wait() +type disconnectMsg struct { + Reason uint32 `sshtype:"1"` + Message string + Language string +} + +func (d *disconnectMsg) Error() string { + return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message) +} + +// See RFC 4253, section 7.1. +const msgKexInit = 20 + +type kexInitMsg struct { + Cookie [16]byte `sshtype:"20"` + KexAlgos []string + ServerHostKeyAlgos []string + CiphersClientServer []string + CiphersServerClient []string + MACsClientServer []string + MACsServerClient []string + CompressionClientServer []string + CompressionServerClient []string + LanguagesClientServer []string + LanguagesServerClient []string + FirstKexFollows bool + Reserved uint32 +} + +// See RFC 4253, section 8. + +// Diffie-Hellman +const msgKexDHInit = 30 + +type kexDHInitMsg struct { + X *big.Int `sshtype:"30"` +} + +const msgKexECDHInit = 30 + +type kexECDHInitMsg struct { + ClientPubKey []byte `sshtype:"30"` +} + +const msgKexECDHReply = 31 + +type kexECDHReplyMsg struct { + HostKey []byte `sshtype:"31"` + EphemeralPubKey []byte + Signature []byte +} + +const msgKexDHReply = 31 + +type kexDHReplyMsg struct { + HostKey []byte `sshtype:"31"` + Y *big.Int + Signature []byte +} + +// See RFC 4419, section 5. +const msgKexDHGexGroup = 31 + +type kexDHGexGroupMsg struct { + P *big.Int `sshtype:"31"` + G *big.Int +} + +const msgKexDHGexInit = 32 + +type kexDHGexInitMsg struct { + X *big.Int `sshtype:"32"` +} + +const msgKexDHGexReply = 33 + +type kexDHGexReplyMsg struct { + HostKey []byte `sshtype:"33"` + Y *big.Int + Signature []byte +} + +const msgKexDHGexRequest = 34 + +type kexDHGexRequestMsg struct { + MinBits uint32 `sshtype:"34"` + PreferredBits uint32 + MaxBits uint32 +} + +// See RFC 4253, section 10. +const msgServiceRequest = 5 + +type serviceRequestMsg struct { + Service string `sshtype:"5"` +} + +// See RFC 4253, section 10. +const msgServiceAccept = 6 + +type serviceAcceptMsg struct { + Service string `sshtype:"6"` +} + +// See RFC 8308, section 2.3 +const msgExtInfo = 7 + +type extInfoMsg struct { + NumExtensions uint32 `sshtype:"7"` + Payload []byte `ssh:"rest"` +} + +// See RFC 4252, section 5. +const msgUserAuthRequest = 50 + +type userAuthRequestMsg struct { + User string `sshtype:"50"` + Service string + Method string + Payload []byte `ssh:"rest"` +} + +// Used for debug printouts of packets. +type userAuthSuccessMsg struct { +} + +// See RFC 4252, section 5.1 +const msgUserAuthFailure = 51 + +type userAuthFailureMsg struct { + Methods []string `sshtype:"51"` + PartialSuccess bool +} + +// See RFC 4252, section 5.1 +const msgUserAuthSuccess = 52 + +// See RFC 4252, section 5.4 +const msgUserAuthBanner = 53 + +type userAuthBannerMsg struct { + Message string `sshtype:"53"` + // unused, but required to allow message parsing + Language string +} + +// See RFC 4256, section 3.2 +const msgUserAuthInfoRequest = 60 +const msgUserAuthInfoResponse = 61 + +type userAuthInfoRequestMsg struct { + Name string `sshtype:"60"` + Instruction string + Language string + NumPrompts uint32 + Prompts []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.1. +const msgChannelOpen = 90 + +type channelOpenMsg struct { + ChanType string `sshtype:"90"` + PeersID uint32 + PeersWindow uint32 + MaxPacketSize uint32 + TypeSpecificData []byte `ssh:"rest"` +} + +const msgChannelExtendedData = 95 +const msgChannelData = 94 + +// Used for debug print outs of packets. +type channelDataMsg struct { + PeersID uint32 `sshtype:"94"` + Length uint32 + Rest []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.1. +const msgChannelOpenConfirm = 91 + +type channelOpenConfirmMsg struct { + PeersID uint32 `sshtype:"91"` + MyID uint32 + MyWindow uint32 + MaxPacketSize uint32 + TypeSpecificData []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.1. +const msgChannelOpenFailure = 92 + +type channelOpenFailureMsg struct { + PeersID uint32 `sshtype:"92"` + Reason RejectionReason + Message string + Language string +} + +const msgChannelRequest = 98 + +type channelRequestMsg struct { + PeersID uint32 `sshtype:"98"` + Request string + WantReply bool + RequestSpecificData []byte `ssh:"rest"` +} + +// See RFC 4254, section 5.4. +const msgChannelSuccess = 99 + +type channelRequestSuccessMsg struct { + PeersID uint32 `sshtype:"99"` +} + +// See RFC 4254, section 5.4. +const msgChannelFailure = 100 + +type channelRequestFailureMsg struct { + PeersID uint32 `sshtype:"100"` +} + +// See RFC 4254, section 5.3 +const msgChannelClose = 97 + +type channelCloseMsg struct { + PeersID uint32 `sshtype:"97"` +} + +// See RFC 4254, section 5.3 +const msgChannelEOF = 96 + +type channelEOFMsg struct { + PeersID uint32 `sshtype:"96"` +} + +// See RFC 4254, section 4 +const msgGlobalRequest = 80 + +type globalRequestMsg struct { + Type string `sshtype:"80"` + WantReply bool + Data []byte `ssh:"rest"` +} + +// See RFC 4254, section 4 +const msgRequestSuccess = 81 + +type globalRequestSuccessMsg struct { + Data []byte `ssh:"rest" sshtype:"81"` +} + +// See RFC 4254, section 4 +const msgRequestFailure = 82 + +type globalRequestFailureMsg struct { + Data []byte `ssh:"rest" sshtype:"82"` +} + +// See RFC 4254, section 5.2 +const msgChannelWindowAdjust = 93 + +type windowAdjustMsg struct { + PeersID uint32 `sshtype:"93"` + AdditionalBytes uint32 +} + +// See RFC 4252, section 7 +const msgUserAuthPubKeyOk = 60 + +type userAuthPubKeyOkMsg struct { + Algo string `sshtype:"60"` + PubKey []byte +} + +// See RFC 4462, section 3 +const msgUserAuthGSSAPIResponse = 60 + +type userAuthGSSAPIResponse struct { + SupportMech []byte `sshtype:"60"` +} + +const msgUserAuthGSSAPIToken = 61 + +type userAuthGSSAPIToken struct { + Token []byte `sshtype:"61"` +} + +const msgUserAuthGSSAPIMIC = 66 + +type userAuthGSSAPIMIC struct { + MIC []byte `sshtype:"66"` +} + +// See RFC 4462, section 3.9 +const msgUserAuthGSSAPIErrTok = 64 + +type userAuthGSSAPIErrTok struct { + ErrorToken []byte `sshtype:"64"` +} + +// See RFC 4462, section 3.8 +const msgUserAuthGSSAPIError = 65 + +type userAuthGSSAPIError struct { + MajorStatus uint32 `sshtype:"65"` + MinorStatus uint32 + Message string + LanguageTag string +} + +// Transport layer OpenSSH extension. See [PROTOCOL], section 1.9 +const msgPing = 192 + +type pingMsg struct { + Data string `sshtype:"192"` +} + +// Transport layer OpenSSH extension. See [PROTOCOL], section 1.9 +const msgPong = 193 + +type pongMsg struct { + Data string `sshtype:"193"` +} + +// typeTags returns the possible type bytes for the given reflect.Type, which +// should be a struct. The possible values are separated by a '|' character. +func typeTags(structType reflect.Type) (tags []byte) { + tagStr := structType.Field(0).Tag.Get("sshtype") + + for _, tag := range strings.Split(tagStr, "|") { + i, err := strconv.Atoi(tag) + if err == nil { + tags = append(tags, byte(i)) + } + } + + return tags +} + +func fieldError(t reflect.Type, field int, problem string) error { + if problem != "" { + problem = ": " + problem + } + return fmt.Errorf("ssh: unmarshal error for field %s of type %s%s", t.Field(field).Name, t.Name(), problem) +} + +var errShortRead = errors.New("ssh: short read") + +// Unmarshal parses data in SSH wire format into a structure. The out +// argument should be a pointer to struct. If the first member of the +// struct has the "sshtype" tag set to a '|'-separated set of numbers +// in decimal, the packet must start with one of those numbers. In +// case of error, Unmarshal returns a ParseError or +// UnexpectedMessageError. +func Unmarshal(data []byte, out interface{}) error { + v := reflect.ValueOf(out).Elem() + structType := v.Type() + expectedTypes := typeTags(structType) + + var expectedType byte + if len(expectedTypes) > 0 { + expectedType = expectedTypes[0] + } + + if len(data) == 0 { + return parseError(expectedType) + } + + if len(expectedTypes) > 0 { + goodType := false + for _, e := range expectedTypes { + if e > 0 && data[0] == e { + goodType = true + break + } + } + if !goodType { + return fmt.Errorf("ssh: unexpected message type %d (expected one of %v)", data[0], expectedTypes) + } + data = data[1:] + } + + var ok bool + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + t := field.Type() + switch t.Kind() { + case reflect.Bool: + if len(data) < 1 { + return errShortRead + } + field.SetBool(data[0] != 0) + data = data[1:] + case reflect.Array: + if t.Elem().Kind() != reflect.Uint8 { + return fieldError(structType, i, "array of unsupported type") + } + if len(data) < t.Len() { + return errShortRead + } + for j, n := 0, t.Len(); j < n; j++ { + field.Index(j).Set(reflect.ValueOf(data[j])) + } + data = data[t.Len():] + case reflect.Uint64: + var u64 uint64 + if u64, data, ok = parseUint64(data); !ok { + return errShortRead + } + field.SetUint(u64) + case reflect.Uint32: + var u32 uint32 + if u32, data, ok = parseUint32(data); !ok { + return errShortRead + } + field.SetUint(uint64(u32)) + case reflect.Uint8: + if len(data) < 1 { + return errShortRead + } + field.SetUint(uint64(data[0])) + data = data[1:] + case reflect.String: + var s []byte + if s, data, ok = parseString(data); !ok { + return fieldError(structType, i, "") + } + field.SetString(string(s)) + case reflect.Slice: + switch t.Elem().Kind() { + case reflect.Uint8: + if structType.Field(i).Tag.Get("ssh") == "rest" { + field.Set(reflect.ValueOf(data)) + data = nil + } else { + var s []byte + if s, data, ok = parseString(data); !ok { + return errShortRead + } + field.Set(reflect.ValueOf(s)) + } + case reflect.String: + var nl []string + if nl, data, ok = parseNameList(data); !ok { + return errShortRead + } + field.Set(reflect.ValueOf(nl)) + default: + return fieldError(structType, i, "slice of unsupported type") + } + case reflect.Ptr: + if t == bigIntType { + var n *big.Int + if n, data, ok = parseInt(data); !ok { + return errShortRead + } + field.Set(reflect.ValueOf(n)) + } else { + return fieldError(structType, i, "pointer to unsupported type") + } + default: + return fieldError(structType, i, fmt.Sprintf("unsupported type: %v", t)) + } + } + + if len(data) != 0 { + return parseError(expectedType) + } + + return nil +} + +// Marshal serializes the message in msg to SSH wire format. The msg +// argument should be a struct or pointer to struct. If the first +// member has the "sshtype" tag set to a number in decimal, that +// number is prepended to the result. If the last of member has the +// "ssh" tag set to "rest", its contents are appended to the output. +func Marshal(msg interface{}) []byte { + out := make([]byte, 0, 64) + return marshalStruct(out, msg) +} + +func marshalStruct(out []byte, msg interface{}) []byte { + v := reflect.Indirect(reflect.ValueOf(msg)) + msgTypes := typeTags(v.Type()) + if len(msgTypes) > 0 { + out = append(out, msgTypes[0]) + } + + for i, n := 0, v.NumField(); i < n; i++ { + field := v.Field(i) + switch t := field.Type(); t.Kind() { + case reflect.Bool: + var v uint8 + if field.Bool() { + v = 1 + } + out = append(out, v) + case reflect.Array: + if t.Elem().Kind() != reflect.Uint8 { + panic(fmt.Sprintf("array of non-uint8 in field %d: %T", i, field.Interface())) + } + for j, l := 0, t.Len(); j < l; j++ { + out = append(out, uint8(field.Index(j).Uint())) + } + case reflect.Uint32: + out = appendU32(out, uint32(field.Uint())) + case reflect.Uint64: + out = appendU64(out, uint64(field.Uint())) + case reflect.Uint8: + out = append(out, uint8(field.Uint())) + case reflect.String: + s := field.String() + out = appendInt(out, len(s)) + out = append(out, s...) + case reflect.Slice: + switch t.Elem().Kind() { + case reflect.Uint8: + if v.Type().Field(i).Tag.Get("ssh") != "rest" { + out = appendInt(out, field.Len()) + } + out = append(out, field.Bytes()...) + case reflect.String: + offset := len(out) + out = appendU32(out, 0) + if n := field.Len(); n > 0 { + for j := 0; j < n; j++ { + f := field.Index(j) + if j != 0 { + out = append(out, ',') + } + out = append(out, f.String()...) + } + // overwrite length value + binary.BigEndian.PutUint32(out[offset:], uint32(len(out)-offset-4)) + } + default: + panic(fmt.Sprintf("slice of unknown type in field %d: %T", i, field.Interface())) + } + case reflect.Ptr: + if t == bigIntType { + var n *big.Int + nValue := reflect.ValueOf(&n) + nValue.Elem().Set(field) + needed := intLength(n) + oldLength := len(out) + + if cap(out)-len(out) < needed { + newOut := make([]byte, len(out), 2*(len(out)+needed)) + copy(newOut, out) + out = newOut + } + out = out[:oldLength+needed] + marshalInt(out[oldLength:], n) + } else { + panic(fmt.Sprintf("pointer to unknown type in field %d: %T", i, field.Interface())) + } + } + } + + return out +} + +var bigOne = big.NewInt(1) + +func parseString(in []byte) (out, rest []byte, ok bool) { + if len(in) < 4 { + return + } + length := binary.BigEndian.Uint32(in) + in = in[4:] + if uint32(len(in)) < length { + return + } + out = in[:length] + rest = in[length:] + ok = true + return +} + +var ( + comma = []byte{','} + emptyNameList = []string{} +) + +func parseNameList(in []byte) (out []string, rest []byte, ok bool) { + contents, rest, ok := parseString(in) + if !ok { + return + } + if len(contents) == 0 { + out = emptyNameList + return + } + parts := bytes.Split(contents, comma) + out = make([]string, len(parts)) + for i, part := range parts { + out[i] = string(part) + } + return +} + +func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) { + contents, rest, ok := parseString(in) + if !ok { + return + } + out = new(big.Int) + + if len(contents) > 0 && contents[0]&0x80 == 0x80 { + // This is a negative number + notBytes := make([]byte, len(contents)) + for i := range notBytes { + notBytes[i] = ^contents[i] + } + out.SetBytes(notBytes) + out.Add(out, bigOne) + out.Neg(out) + } else { + // Positive number + out.SetBytes(contents) + } + ok = true + return +} + +func parseUint32(in []byte) (uint32, []byte, bool) { + if len(in) < 4 { + return 0, nil, false + } + return binary.BigEndian.Uint32(in), in[4:], true +} + +func parseUint64(in []byte) (uint64, []byte, bool) { + if len(in) < 8 { + return 0, nil, false + } + return binary.BigEndian.Uint64(in), in[8:], true +} + +func intLength(n *big.Int) int { + length := 4 /* length bytes */ + if n.Sign() < 0 { + nMinus1 := new(big.Int).Neg(n) + nMinus1.Sub(nMinus1, bigOne) + bitLen := nMinus1.BitLen() + if bitLen%8 == 0 { + // The number will need 0xff padding + length++ + } + length += (bitLen + 7) / 8 + } else if n.Sign() == 0 { + // A zero is the zero length string + } else { + bitLen := n.BitLen() + if bitLen%8 == 0 { + // The number will need 0x00 padding + length++ + } + length += (bitLen + 7) / 8 + } + + return length +} + +func marshalUint32(to []byte, n uint32) []byte { + binary.BigEndian.PutUint32(to, n) + return to[4:] +} + +func marshalUint64(to []byte, n uint64) []byte { + binary.BigEndian.PutUint64(to, n) + return to[8:] +} + +func marshalInt(to []byte, n *big.Int) []byte { + lengthBytes := to + to = to[4:] + length := 0 + + if n.Sign() < 0 { + // A negative number has to be converted to two's-complement + // form. So we'll subtract 1 and invert. If the + // most-significant-bit isn't set then we'll need to pad the + // beginning with 0xff in order to keep the number negative. + nMinus1 := new(big.Int).Neg(n) + nMinus1.Sub(nMinus1, bigOne) + bytes := nMinus1.Bytes() + for i := range bytes { + bytes[i] ^= 0xff + } + if len(bytes) == 0 || bytes[0]&0x80 == 0 { + to[0] = 0xff + to = to[1:] + length++ + } + nBytes := copy(to, bytes) + to = to[nBytes:] + length += nBytes + } else if n.Sign() == 0 { + // A zero is the zero length string + } else { + bytes := n.Bytes() + if len(bytes) > 0 && bytes[0]&0x80 != 0 { + // We'll have to pad this with a 0x00 in order to + // stop it looking like a negative number. + to[0] = 0 + to = to[1:] + length++ + } + nBytes := copy(to, bytes) + to = to[nBytes:] + length += nBytes + } + + lengthBytes[0] = byte(length >> 24) + lengthBytes[1] = byte(length >> 16) + lengthBytes[2] = byte(length >> 8) + lengthBytes[3] = byte(length) + return to +} + +func writeInt(w io.Writer, n *big.Int) { + length := intLength(n) + buf := make([]byte, length) + marshalInt(buf, n) + w.Write(buf) +} + +func writeString(w io.Writer, s []byte) { + var lengthBytes [4]byte + lengthBytes[0] = byte(len(s) >> 24) + lengthBytes[1] = byte(len(s) >> 16) + lengthBytes[2] = byte(len(s) >> 8) + lengthBytes[3] = byte(len(s)) + w.Write(lengthBytes[:]) + w.Write(s) +} + +func stringLength(n int) int { + return 4 + n +} + +func marshalString(to []byte, s []byte) []byte { + to[0] = byte(len(s) >> 24) + to[1] = byte(len(s) >> 16) + to[2] = byte(len(s) >> 8) + to[3] = byte(len(s)) + to = to[4:] + copy(to, s) + return to[len(s):] +} + +var bigIntType = reflect.TypeFor[*big.Int]() + +// Decode a packet into its corresponding message. +func decode(packet []byte) (interface{}, error) { + var msg interface{} + switch packet[0] { + case msgDisconnect: + msg = new(disconnectMsg) + case msgServiceRequest: + msg = new(serviceRequestMsg) + case msgServiceAccept: + msg = new(serviceAcceptMsg) + case msgExtInfo: + msg = new(extInfoMsg) + case msgKexInit: + msg = new(kexInitMsg) + case msgKexDHInit: + msg = new(kexDHInitMsg) + case msgKexDHReply: + msg = new(kexDHReplyMsg) + case msgUserAuthRequest: + msg = new(userAuthRequestMsg) + case msgUserAuthSuccess: + return new(userAuthSuccessMsg), nil + case msgUserAuthFailure: + msg = new(userAuthFailureMsg) + case msgUserAuthBanner: + msg = new(userAuthBannerMsg) + case msgUserAuthPubKeyOk: + msg = new(userAuthPubKeyOkMsg) + case msgGlobalRequest: + msg = new(globalRequestMsg) + case msgRequestSuccess: + msg = new(globalRequestSuccessMsg) + case msgRequestFailure: + msg = new(globalRequestFailureMsg) + case msgChannelOpen: + msg = new(channelOpenMsg) + case msgChannelData: + msg = new(channelDataMsg) + case msgChannelOpenConfirm: + msg = new(channelOpenConfirmMsg) + case msgChannelOpenFailure: + msg = new(channelOpenFailureMsg) + case msgChannelWindowAdjust: + msg = new(windowAdjustMsg) + case msgChannelEOF: + msg = new(channelEOFMsg) + case msgChannelClose: + msg = new(channelCloseMsg) + case msgChannelRequest: + msg = new(channelRequestMsg) + case msgChannelSuccess: + msg = new(channelRequestSuccessMsg) + case msgChannelFailure: + msg = new(channelRequestFailureMsg) + case msgUserAuthGSSAPIToken: + msg = new(userAuthGSSAPIToken) + case msgUserAuthGSSAPIMIC: + msg = new(userAuthGSSAPIMIC) + case msgUserAuthGSSAPIErrTok: + msg = new(userAuthGSSAPIErrTok) + case msgUserAuthGSSAPIError: + msg = new(userAuthGSSAPIError) + default: + return nil, unexpectedMessageError(0, packet[0]) + } + if err := Unmarshal(packet, msg); err != nil { + return nil, err + } + return msg, nil +} + +var packetTypeNames = map[byte]string{ + msgDisconnect: "disconnectMsg", + msgServiceRequest: "serviceRequestMsg", + msgServiceAccept: "serviceAcceptMsg", + msgExtInfo: "extInfoMsg", + msgKexInit: "kexInitMsg", + msgKexDHInit: "kexDHInitMsg", + msgKexDHReply: "kexDHReplyMsg", + msgUserAuthRequest: "userAuthRequestMsg", + msgUserAuthSuccess: "userAuthSuccessMsg", + msgUserAuthFailure: "userAuthFailureMsg", + msgUserAuthPubKeyOk: "userAuthPubKeyOkMsg", + msgGlobalRequest: "globalRequestMsg", + msgRequestSuccess: "globalRequestSuccessMsg", + msgRequestFailure: "globalRequestFailureMsg", + msgChannelOpen: "channelOpenMsg", + msgChannelData: "channelDataMsg", + msgChannelOpenConfirm: "channelOpenConfirmMsg", + msgChannelOpenFailure: "channelOpenFailureMsg", + msgChannelWindowAdjust: "windowAdjustMsg", + msgChannelEOF: "channelEOFMsg", + msgChannelClose: "channelCloseMsg", + msgChannelRequest: "channelRequestMsg", + msgChannelSuccess: "channelRequestSuccessMsg", + msgChannelFailure: "channelRequestFailureMsg", +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/mlkem.go b/cli/vendor/golang.org/x/crypto/ssh/mlkem.go new file mode 100644 index 0000000..ddc0ed1 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/mlkem.go @@ -0,0 +1,168 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "crypto" + "crypto/mlkem" + "crypto/sha256" + "errors" + "fmt" + "io" + + "golang.org/x/crypto/curve25519" +) + +// mlkem768WithCurve25519sha256 implements the hybrid ML-KEM768 with +// curve25519-sha256 key exchange method, as described by +// draft-kampanakis-curdle-ssh-pq-ke-05 section 2.3.3. +type mlkem768WithCurve25519sha256 struct{} + +func (kex *mlkem768WithCurve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) { + var c25519kp curve25519KeyPair + if err := c25519kp.generate(rand); err != nil { + return nil, err + } + + seed := make([]byte, mlkem.SeedSize) + if _, err := io.ReadFull(rand, seed); err != nil { + return nil, err + } + + mlkemDk, err := mlkem.NewDecapsulationKey768(seed) + if err != nil { + return nil, err + } + + hybridKey := append(mlkemDk.EncapsulationKey().Bytes(), c25519kp.pub[:]...) + if err := c.writePacket(Marshal(&kexECDHInitMsg{hybridKey})); err != nil { + return nil, err + } + + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var reply kexECDHReplyMsg + if err = Unmarshal(packet, &reply); err != nil { + return nil, err + } + + if len(reply.EphemeralPubKey) != mlkem.CiphertextSize768+32 { + return nil, errors.New("ssh: peer's mlkem768x25519 public value has wrong length") + } + + // Perform KEM decapsulate operation to obtain shared key from ML-KEM. + mlkem768Secret, err := mlkemDk.Decapsulate(reply.EphemeralPubKey[:mlkem.CiphertextSize768]) + if err != nil { + return nil, err + } + + // Complete Curve25519 ECDH to obtain its shared key. + c25519Secret, err := curve25519.X25519(c25519kp.priv[:], reply.EphemeralPubKey[mlkem.CiphertextSize768:]) + if err != nil { + return nil, fmt.Errorf("ssh: peer's mlkem768x25519 public value is not valid: %w", err) + } + // Compute actual shared key. + h := sha256.New() + h.Write(mlkem768Secret) + h.Write(c25519Secret) + secret := h.Sum(nil) + + h.Reset() + magics.write(h) + writeString(h, reply.HostKey) + writeString(h, hybridKey) + writeString(h, reply.EphemeralPubKey) + + K := make([]byte, stringLength(len(secret))) + marshalString(K, secret) + h.Write(K) + + return &kexResult{ + H: h.Sum(nil), + K: K, + HostKey: reply.HostKey, + Signature: reply.Signature, + Hash: crypto.SHA256, + }, nil +} + +func (kex *mlkem768WithCurve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (*kexResult, error) { + packet, err := c.readPacket() + if err != nil { + return nil, err + } + + var kexInit kexECDHInitMsg + if err = Unmarshal(packet, &kexInit); err != nil { + return nil, err + } + + if len(kexInit.ClientPubKey) != mlkem.EncapsulationKeySize768+32 { + return nil, errors.New("ssh: peer's ML-KEM768/curve25519 public value has wrong length") + } + + encapsulationKey, err := mlkem.NewEncapsulationKey768(kexInit.ClientPubKey[:mlkem.EncapsulationKeySize768]) + if err != nil { + return nil, fmt.Errorf("ssh: peer's ML-KEM768 encapsulation key is not valid: %w", err) + } + // Perform KEM encapsulate operation to obtain ciphertext and shared key. + mlkem768Secret, mlkem768Ciphertext := encapsulationKey.Encapsulate() + + // Perform server side of Curve25519 ECDH to obtain server public value and + // shared key. + var c25519kp curve25519KeyPair + if err := c25519kp.generate(rand); err != nil { + return nil, err + } + c25519Secret, err := curve25519.X25519(c25519kp.priv[:], kexInit.ClientPubKey[mlkem.EncapsulationKeySize768:]) + if err != nil { + return nil, fmt.Errorf("ssh: peer's ML-KEM768/curve25519 public value is not valid: %w", err) + } + hybridKey := append(mlkem768Ciphertext, c25519kp.pub[:]...) + + // Compute actual shared key. + h := sha256.New() + h.Write(mlkem768Secret) + h.Write(c25519Secret) + secret := h.Sum(nil) + + hostKeyBytes := priv.PublicKey().Marshal() + + h.Reset() + magics.write(h) + writeString(h, hostKeyBytes) + writeString(h, kexInit.ClientPubKey) + writeString(h, hybridKey) + + K := make([]byte, stringLength(len(secret))) + marshalString(K, secret) + h.Write(K) + + H := h.Sum(nil) + + sig, err := signAndMarshal(priv, rand, H, algo) + if err != nil { + return nil, err + } + + reply := kexECDHReplyMsg{ + EphemeralPubKey: hybridKey, + HostKey: hostKeyBytes, + Signature: sig, + } + if err := c.writePacket(Marshal(&reply)); err != nil { + return nil, err + } + return &kexResult{ + H: H, + K: K, + HostKey: hostKeyBytes, + Signature: sig, + Hash: crypto.SHA256, + }, nil +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/mux.go b/cli/vendor/golang.org/x/crypto/ssh/mux.go new file mode 100644 index 0000000..d2d24c6 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/mux.go @@ -0,0 +1,357 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/binary" + "fmt" + "io" + "log" + "sync" + "sync/atomic" +) + +// debugMux, if set, causes messages in the connection protocol to be +// logged. +const debugMux = false + +// chanList is a thread safe channel list. +type chanList struct { + // protects concurrent access to chans + sync.Mutex + + // chans are indexed by the local id of the channel, which the + // other side should send in the PeersId field. + chans []*channel + + // This is a debugging aid: it offsets all IDs by this + // amount. This helps distinguish otherwise identical + // server/client muxes + offset uint32 +} + +// Assigns a channel ID to the given channel. +func (c *chanList) add(ch *channel) uint32 { + c.Lock() + defer c.Unlock() + for i := range c.chans { + if c.chans[i] == nil { + c.chans[i] = ch + return uint32(i) + c.offset + } + } + c.chans = append(c.chans, ch) + return uint32(len(c.chans)-1) + c.offset +} + +// getChan returns the channel for the given ID. +func (c *chanList) getChan(id uint32) *channel { + id -= c.offset + + c.Lock() + defer c.Unlock() + if id < uint32(len(c.chans)) { + return c.chans[id] + } + return nil +} + +func (c *chanList) remove(id uint32) { + id -= c.offset + c.Lock() + if id < uint32(len(c.chans)) { + c.chans[id] = nil + } + c.Unlock() +} + +// dropAll forgets all channels it knows, returning them in a slice. +func (c *chanList) dropAll() []*channel { + c.Lock() + defer c.Unlock() + var r []*channel + + for _, ch := range c.chans { + if ch == nil { + continue + } + r = append(r, ch) + } + c.chans = nil + return r +} + +// mux represents the state for the SSH connection protocol, which +// multiplexes many channels onto a single packet transport. +type mux struct { + conn packetConn + chanList chanList + + incomingChannels chan NewChannel + + globalSentMu sync.Mutex + globalResponses chan interface{} + incomingRequests chan *Request + + errCond *sync.Cond + err error +} + +// When debugging, each new chanList instantiation has a different +// offset. +var globalOff uint32 + +func (m *mux) Wait() error { + m.errCond.L.Lock() + defer m.errCond.L.Unlock() + for m.err == nil { + m.errCond.Wait() + } + return m.err +} + +// newMux returns a mux that runs over the given connection. +func newMux(p packetConn) *mux { + m := &mux{ + conn: p, + incomingChannels: make(chan NewChannel, chanSize), + globalResponses: make(chan interface{}, 1), + incomingRequests: make(chan *Request, chanSize), + errCond: newCond(), + } + if debugMux { + m.chanList.offset = atomic.AddUint32(&globalOff, 1) + } + + go m.loop() + return m +} + +func (m *mux) sendMessage(msg interface{}) error { + p := Marshal(msg) + if debugMux { + log.Printf("send global(%d): %#v", m.chanList.offset, msg) + } + return m.conn.writePacket(p) +} + +func (m *mux) SendRequest(name string, wantReply bool, payload []byte) (bool, []byte, error) { + if wantReply { + m.globalSentMu.Lock() + defer m.globalSentMu.Unlock() + } + + if err := m.sendMessage(globalRequestMsg{ + Type: name, + WantReply: wantReply, + Data: payload, + }); err != nil { + return false, nil, err + } + + if !wantReply { + return false, nil, nil + } + + msg, ok := <-m.globalResponses + if !ok { + return false, nil, io.EOF + } + switch msg := msg.(type) { + case *globalRequestFailureMsg: + return false, msg.Data, nil + case *globalRequestSuccessMsg: + return true, msg.Data, nil + default: + return false, nil, fmt.Errorf("ssh: unexpected response to request: %#v", msg) + } +} + +// ackRequest must be called after processing a global request that +// has WantReply set. +func (m *mux) ackRequest(ok bool, data []byte) error { + if ok { + return m.sendMessage(globalRequestSuccessMsg{Data: data}) + } + return m.sendMessage(globalRequestFailureMsg{Data: data}) +} + +func (m *mux) Close() error { + return m.conn.Close() +} + +// loop runs the connection machine. It will process packets until an +// error is encountered. To synchronize on loop exit, use mux.Wait. +func (m *mux) loop() { + var err error + for err == nil { + err = m.onePacket() + } + + for _, ch := range m.chanList.dropAll() { + ch.close() + } + + close(m.incomingChannels) + close(m.incomingRequests) + close(m.globalResponses) + + m.conn.Close() + + m.errCond.L.Lock() + m.err = err + m.errCond.Broadcast() + m.errCond.L.Unlock() + + if debugMux { + log.Println("loop exit", err) + } +} + +// onePacket reads and processes one packet. +func (m *mux) onePacket() error { + packet, err := m.conn.readPacket() + if err != nil { + return err + } + + if debugMux { + if packet[0] == msgChannelData || packet[0] == msgChannelExtendedData { + log.Printf("decoding(%d): data packet - %d bytes", m.chanList.offset, len(packet)) + } else { + p, _ := decode(packet) + log.Printf("decoding(%d): %d %#v - %d bytes", m.chanList.offset, packet[0], p, len(packet)) + } + } + + switch packet[0] { + case msgChannelOpen: + return m.handleChannelOpen(packet) + case msgGlobalRequest, msgRequestSuccess, msgRequestFailure: + return m.handleGlobalPacket(packet) + case msgPing: + var msg pingMsg + if err := Unmarshal(packet, &msg); err != nil { + return fmt.Errorf("failed to unmarshal ping@openssh.com message: %w", err) + } + return m.sendMessage(pongMsg(msg)) + } + + // assume a channel packet. + if len(packet) < 5 { + return parseError(packet[0]) + } + id := binary.BigEndian.Uint32(packet[1:]) + ch := m.chanList.getChan(id) + if ch == nil { + return m.handleUnknownChannelPacket(id, packet) + } + + return ch.handlePacket(packet) +} + +func (m *mux) handleGlobalPacket(packet []byte) error { + msg, err := decode(packet) + if err != nil { + return err + } + + switch msg := msg.(type) { + case *globalRequestMsg: + m.incomingRequests <- &Request{ + Type: msg.Type, + WantReply: msg.WantReply, + Payload: msg.Data, + mux: m, + } + case *globalRequestSuccessMsg, *globalRequestFailureMsg: + m.globalResponses <- msg + default: + panic(fmt.Sprintf("not a global message %#v", msg)) + } + + return nil +} + +// handleChannelOpen schedules a channel to be Accept()ed. +func (m *mux) handleChannelOpen(packet []byte) error { + var msg channelOpenMsg + if err := Unmarshal(packet, &msg); err != nil { + return err + } + + if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 { + failMsg := channelOpenFailureMsg{ + PeersID: msg.PeersID, + Reason: ConnectionFailed, + Message: "invalid request", + Language: "en_US.UTF-8", + } + return m.sendMessage(failMsg) + } + + c := m.newChannel(msg.ChanType, channelInbound, msg.TypeSpecificData) + c.remoteId = msg.PeersID + c.maxRemotePayload = msg.MaxPacketSize + c.remoteWin.add(msg.PeersWindow) + m.incomingChannels <- c + return nil +} + +func (m *mux) OpenChannel(chanType string, extra []byte) (Channel, <-chan *Request, error) { + ch, err := m.openChannel(chanType, extra) + if err != nil { + return nil, nil, err + } + + return ch, ch.incomingRequests, nil +} + +func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) { + ch := m.newChannel(chanType, channelOutbound, extra) + + ch.maxIncomingPayload = channelMaxPacket + + open := channelOpenMsg{ + ChanType: chanType, + PeersWindow: ch.myWindow, + MaxPacketSize: ch.maxIncomingPayload, + TypeSpecificData: extra, + PeersID: ch.localId, + } + if err := m.sendMessage(open); err != nil { + return nil, err + } + + switch msg := (<-ch.msg).(type) { + case *channelOpenConfirmMsg: + return ch, nil + case *channelOpenFailureMsg: + return nil, &OpenChannelError{msg.Reason, msg.Message} + default: + return nil, fmt.Errorf("ssh: unexpected packet in response to channel open: %T", msg) + } +} + +func (m *mux) handleUnknownChannelPacket(id uint32, packet []byte) error { + msg, err := decode(packet) + if err != nil { + return err + } + + switch msg := msg.(type) { + // RFC 4254 section 5.4 says unrecognized channel requests should + // receive a failure response. + case *channelRequestMsg: + if msg.WantReply { + return m.sendMessage(channelRequestFailureMsg{ + PeersID: msg.PeersID, + }) + } + return nil + default: + return fmt.Errorf("ssh: invalid channel %d", id) + } +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/server.go b/cli/vendor/golang.org/x/crypto/ssh/server.go new file mode 100644 index 0000000..064dcba --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/server.go @@ -0,0 +1,955 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "slices" + "strings" +) + +// The Permissions type holds fine-grained permissions that are +// specific to a user or a specific authentication method for a user. +// The Permissions value for a successful authentication attempt is +// available in ServerConn, so it can be used to pass information from +// the user-authentication phase to the application layer. +type Permissions struct { + // CriticalOptions indicate restrictions to the default + // permissions, and are typically used in conjunction with + // user certificates. The standard for SSH certificates + // defines "force-command" (only allow the given command to + // execute) and "source-address" (only allow connections from + // the given address). The SSH package currently only enforces + // the "source-address" critical option. It is up to server + // implementations to enforce other critical options, such as + // "force-command", by checking them after the SSH handshake + // is successful. In general, SSH servers should reject + // connections that specify critical options that are unknown + // or not supported. + CriticalOptions map[string]string + + // Extensions are extra functionality that the server may + // offer on authenticated connections. Lack of support for an + // extension does not preclude authenticating a user. Common + // extensions are "permit-agent-forwarding", + // "permit-X11-forwarding". The Go SSH library currently does + // not act on any extension, and it is up to server + // implementations to honor them. Extensions can be used to + // pass data from the authentication callbacks to the server + // application layer. + Extensions map[string]string + + // ExtraData allows to store user defined data. + ExtraData map[any]any +} + +type GSSAPIWithMICConfig struct { + // AllowLogin, must be set, is called when gssapi-with-mic + // authentication is selected (RFC 4462 section 3). The srcName is from the + // results of the GSS-API authentication. The format is username@DOMAIN. + // GSSAPI just guarantees to the server who the user is, but not if they can log in, and with what permissions. + // This callback is called after the user identity is established with GSSAPI to decide if the user can login with + // which permissions. If the user is allowed to login, it should return a nil error. + AllowLogin func(conn ConnMetadata, srcName string) (*Permissions, error) + + // Server must be set. It's the implementation + // of the GSSAPIServer interface. See GSSAPIServer interface for details. + Server GSSAPIServer +} + +// SendAuthBanner implements [ServerPreAuthConn]. +func (s *connection) SendAuthBanner(msg string) error { + return s.transport.writePacket(Marshal(&userAuthBannerMsg{ + Message: msg, + })) +} + +func (*connection) unexportedMethodForFutureProofing() {} + +// ServerPreAuthConn is the interface available on an incoming server +// connection before authentication has completed. +type ServerPreAuthConn interface { + unexportedMethodForFutureProofing() // permits growing ServerPreAuthConn safely later, ala testing.TB + + ConnMetadata + + // SendAuthBanner sends a banner message to the client. + // It returns an error once the authentication phase has ended. + SendAuthBanner(string) error +} + +// ServerConfig holds server specific configuration data. +type ServerConfig struct { + // Config contains configuration shared between client and server. + Config + + // PublicKeyAuthAlgorithms specifies the supported client public key + // authentication algorithms. Note that this should not include certificate + // types since those use the underlying algorithm. This list is sent to the + // client if it supports the server-sig-algs extension. Order is irrelevant. + // If unspecified then a default set of algorithms is used. + PublicKeyAuthAlgorithms []string + + hostKeys []Signer + + // NoClientAuth is true if clients are allowed to connect without + // authenticating. + // To determine NoClientAuth at runtime, set NoClientAuth to true + // and the optional NoClientAuthCallback to a non-nil value. + NoClientAuth bool + + // NoClientAuthCallback, if non-nil, is called when a user + // attempts to authenticate with auth method "none". + // NoClientAuth must also be set to true for this be used, or + // this func is unused. + NoClientAuthCallback func(ConnMetadata) (*Permissions, error) + + // MaxAuthTries specifies the maximum number of authentication attempts + // permitted per connection. If set to a negative number, the number of + // attempts are unlimited. If set to zero, the number of attempts are limited + // to 6. + MaxAuthTries int + + // PasswordCallback, if non-nil, is called when a user + // attempts to authenticate using a password. + PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) + + // PublicKeyCallback, if non-nil, is called when a client + // offers a public key for authentication. It must return a nil error + // if the given public key can be used to authenticate the + // given user. For example, see CertChecker.Authenticate. A + // call to this function does not guarantee that the key + // offered is in fact used to authenticate. To record any data + // depending on the public key, store it inside a + // Permissions.Extensions entry. + PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) + + // VerifiedPublicKeyCallback, if non-nil, is called after a client + // successfully confirms having control over a key that was previously + // approved by PublicKeyCallback. The permissions object passed to the + // callback is the one returned by PublicKeyCallback for the given public + // key and its ownership is transferred to the callback. The returned + // Permissions object can be the same object, optionally modified, or a + // completely new object. If VerifiedPublicKeyCallback is non-nil, + // PublicKeyCallback is not allowed to return a PartialSuccessError, which + // can instead be returned by VerifiedPublicKeyCallback. + // + // VerifiedPublicKeyCallback does not affect which authentication methods + // are included in the list of methods that can be attempted by the client. + VerifiedPublicKeyCallback func(conn ConnMetadata, key PublicKey, permissions *Permissions, + signatureAlgorithm string) (*Permissions, error) + + // KeyboardInteractiveCallback, if non-nil, is called when + // keyboard-interactive authentication is selected (RFC + // 4256). The client object's Challenge function should be + // used to query the user. The callback may offer multiple + // Challenge rounds. To avoid information leaks, the client + // should be presented a challenge even if the user is + // unknown. + KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) + + // AuthLogCallback, if non-nil, is called to log all authentication + // attempts. + AuthLogCallback func(conn ConnMetadata, method string, err error) + + // PreAuthConnCallback, if non-nil, is called upon receiving a new connection + // before any authentication has started. The provided ServerPreAuthConn + // can be used at any time before authentication is complete, including + // after this callback has returned. + PreAuthConnCallback func(ServerPreAuthConn) + + // ServerVersion is the version identification string to announce in + // the public handshake. + // If empty, a reasonable default is used. + // Note that RFC 4253 section 4.2 requires that this string start with + // "SSH-2.0-". + ServerVersion string + + // BannerCallback, if present, is called and the return string is sent to + // the client after key exchange completed but before authentication. + BannerCallback func(conn ConnMetadata) string + + // GSSAPIWithMICConfig includes gssapi server and callback, which if both non-nil, is used + // when gssapi-with-mic authentication is selected (RFC 4462 section 3). + GSSAPIWithMICConfig *GSSAPIWithMICConfig +} + +// AddHostKey adds a private key as a host key. If an existing host +// key exists with the same public key format, it is replaced. Each server +// config must have at least one host key. +func (s *ServerConfig) AddHostKey(key Signer) { + for i, k := range s.hostKeys { + if k.PublicKey().Type() == key.PublicKey().Type() { + s.hostKeys[i] = key + return + } + } + + s.hostKeys = append(s.hostKeys, key) +} + +// cachedPubKey contains the results of querying whether a public key is +// acceptable for a user. This is a FIFO cache. +type cachedPubKey struct { + user string + pubKeyData []byte + result error + perms *Permissions +} + +// maxCachedPubKeys is the number of cache entries we store. +// +// Due to consistent misuse of the PublicKeyCallback API, we have reduced this +// to 1, such that the only key in the cache is the most recently seen one. This +// forces the behavior that the last call to PublicKeyCallback will always be +// with the key that is used for authentication. +const maxCachedPubKeys = 1 + +// pubKeyCache caches tests for public keys. Since SSH clients +// will query whether a public key is acceptable before attempting to +// authenticate with it, we end up with duplicate queries for public +// key validity. The cache only applies to a single ServerConn. +type pubKeyCache struct { + keys []cachedPubKey +} + +// get returns the result for a given user/algo/key tuple. +func (c *pubKeyCache) get(user string, pubKeyData []byte) (cachedPubKey, bool) { + for _, k := range c.keys { + if k.user == user && bytes.Equal(k.pubKeyData, pubKeyData) { + return k, true + } + } + return cachedPubKey{}, false +} + +// add adds the given tuple to the cache. +func (c *pubKeyCache) add(candidate cachedPubKey) { + if len(c.keys) >= maxCachedPubKeys { + c.keys = c.keys[1:] + } + c.keys = append(c.keys, candidate) +} + +// ServerConn is an authenticated SSH connection, as seen from the +// server +type ServerConn struct { + Conn + + // If the succeeding authentication callback returned a + // non-nil Permissions pointer, it is stored here. + Permissions *Permissions +} + +// NewServerConn starts a new SSH server with c as the underlying +// transport. It starts with a handshake and, if the handshake is +// unsuccessful, it closes the connection and returns an error. The +// Request and NewChannel channels must be serviced, or the connection +// will hang. +// +// The returned error may be of type *ServerAuthError for +// authentication errors. +func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { + fullConf := *config + fullConf.SetDefaults() + if fullConf.MaxAuthTries == 0 { + fullConf.MaxAuthTries = 6 + } + if len(fullConf.PublicKeyAuthAlgorithms) == 0 { + fullConf.PublicKeyAuthAlgorithms = defaultPubKeyAuthAlgos + } else { + for _, algo := range fullConf.PublicKeyAuthAlgorithms { + if !slices.Contains(SupportedAlgorithms().PublicKeyAuths, algo) && !slices.Contains(InsecureAlgorithms().PublicKeyAuths, algo) { + c.Close() + return nil, nil, nil, fmt.Errorf("ssh: unsupported public key authentication algorithm %s", algo) + } + } + } + + s := &connection{ + sshConn: sshConn{conn: c}, + } + perms, err := s.serverHandshake(&fullConf) + if err != nil { + c.Close() + return nil, nil, nil, err + } + return &ServerConn{s, perms}, s.mux.incomingChannels, s.mux.incomingRequests, nil +} + +// signAndMarshal signs the data with the appropriate algorithm, +// and serializes the result in SSH wire format. algo is the negotiate +// algorithm and may be a certificate type. +func signAndMarshal(k AlgorithmSigner, rand io.Reader, data []byte, algo string) ([]byte, error) { + sig, err := k.SignWithAlgorithm(rand, data, underlyingAlgo(algo)) + if err != nil { + return nil, err + } + + return Marshal(sig), nil +} + +// handshake performs key exchange and user authentication. +func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) { + if len(config.hostKeys) == 0 { + return nil, errors.New("ssh: server has no host keys") + } + + if !config.NoClientAuth && config.PasswordCallback == nil && config.PublicKeyCallback == nil && + config.KeyboardInteractiveCallback == nil && (config.GSSAPIWithMICConfig == nil || + config.GSSAPIWithMICConfig.AllowLogin == nil || config.GSSAPIWithMICConfig.Server == nil) { + return nil, errors.New("ssh: no authentication methods configured but NoClientAuth is also false") + } + + if config.ServerVersion != "" { + s.serverVersion = []byte(config.ServerVersion) + } else { + s.serverVersion = []byte(packageVersion) + } + var err error + s.clientVersion, err = exchangeVersions(s.sshConn.conn, s.serverVersion) + if err != nil { + return nil, err + } + + tr := newTransport(s.sshConn.conn, config.Rand, false /* not client */) + s.transport = newServerTransport(tr, s.clientVersion, s.serverVersion, config) + + if err := s.transport.waitSession(); err != nil { + return nil, err + } + + // We just did the key change, so the session ID is established. + s.sessionID = s.transport.getSessionID() + s.algorithms = s.transport.getAlgorithms() + + var packet []byte + if packet, err = s.transport.readPacket(); err != nil { + return nil, err + } + + var serviceRequest serviceRequestMsg + if err = Unmarshal(packet, &serviceRequest); err != nil { + return nil, err + } + if serviceRequest.Service != serviceUserAuth { + return nil, errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating") + } + serviceAccept := serviceAcceptMsg{ + Service: serviceUserAuth, + } + if err := s.transport.writePacket(Marshal(&serviceAccept)); err != nil { + return nil, err + } + + perms, err := s.serverAuthenticate(config) + if err != nil { + return nil, err + } + s.mux = newMux(s.transport) + return perms, err +} + +func checkSourceAddress(addr net.Addr, sourceAddrs string) error { + if addr == nil { + return errors.New("ssh: no address known for client, but source-address match required") + } + + tcpAddr, ok := addr.(*net.TCPAddr) + if !ok { + return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr) + } + + for _, sourceAddr := range strings.Split(sourceAddrs, ",") { + if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil { + if allowedIP.Equal(tcpAddr.IP) { + return nil + } + } else { + _, ipNet, err := net.ParseCIDR(sourceAddr) + if err != nil { + return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err) + } + + if ipNet.Contains(tcpAddr.IP) { + return nil + } + } + } + + return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr) +} + +func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, token []byte, s *connection, + sessionID []byte, userAuthReq userAuthRequestMsg) (authErr error, perms *Permissions, err error) { + gssAPIServer := gssapiConfig.Server + defer gssAPIServer.DeleteSecContext() + var srcName string + for { + var ( + outToken []byte + needContinue bool + ) + outToken, srcName, needContinue, err = gssAPIServer.AcceptSecContext(token) + if err != nil { + return err, nil, nil + } + if len(outToken) != 0 { + if err := s.transport.writePacket(Marshal(&userAuthGSSAPIToken{ + Token: outToken, + })); err != nil { + return nil, nil, err + } + } + if !needContinue { + break + } + packet, err := s.transport.readPacket() + if err != nil { + return nil, nil, err + } + userAuthGSSAPITokenReq := &userAuthGSSAPIToken{} + if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil { + return nil, nil, err + } + token = userAuthGSSAPITokenReq.Token + } + packet, err := s.transport.readPacket() + if err != nil { + return nil, nil, err + } + userAuthGSSAPIMICReq := &userAuthGSSAPIMIC{} + if err := Unmarshal(packet, userAuthGSSAPIMICReq); err != nil { + return nil, nil, err + } + mic := buildMIC(string(sessionID), userAuthReq.User, userAuthReq.Service, userAuthReq.Method) + if err := gssAPIServer.VerifyMIC(mic, userAuthGSSAPIMICReq.MIC); err != nil { + return err, nil, nil + } + perms, authErr = gssapiConfig.AllowLogin(s, srcName) + return authErr, perms, nil +} + +// isAlgoCompatible checks if the signature format is compatible with the +// selected algorithm taking into account edge cases that occur with old +// clients. +func isAlgoCompatible(algo, sigFormat string) bool { + // Compatibility for old clients. + // + // For certificate authentication with OpenSSH 7.2-7.7 signature format can + // be rsa-sha2-256 or rsa-sha2-512 for the algorithm + // ssh-rsa-cert-v01@openssh.com. + // + // With gpg-agent < 2.2.6 the algorithm can be rsa-sha2-256 or rsa-sha2-512 + // for signature format ssh-rsa. + if isRSA(algo) && isRSA(sigFormat) { + return true + } + // Standard case: the underlying algorithm must match the signature format. + return underlyingAlgo(algo) == sigFormat +} + +// ServerAuthError represents server authentication errors and is +// sometimes returned by NewServerConn. It appends any authentication +// errors that may occur, and is returned if all of the authentication +// methods provided by the user failed to authenticate. +type ServerAuthError struct { + // Errors contains authentication errors returned by the authentication + // callback methods. The first entry is typically ErrNoAuth. + Errors []error +} + +func (l ServerAuthError) Error() string { + var errs []string + for _, err := range l.Errors { + errs = append(errs, err.Error()) + } + return "[" + strings.Join(errs, ", ") + "]" +} + +// ServerAuthCallbacks defines server-side authentication callbacks. +type ServerAuthCallbacks struct { + // PasswordCallback behaves like [ServerConfig.PasswordCallback]. + PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) + + // PublicKeyCallback behaves like [ServerConfig.PublicKeyCallback]. + PublicKeyCallback func(conn ConnMetadata, key PublicKey) (*Permissions, error) + + // KeyboardInteractiveCallback behaves like [ServerConfig.KeyboardInteractiveCallback]. + KeyboardInteractiveCallback func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) + + // GSSAPIWithMICConfig behaves like [ServerConfig.GSSAPIWithMICConfig]. + GSSAPIWithMICConfig *GSSAPIWithMICConfig +} + +// PartialSuccessError can be returned by any of the [ServerConfig] +// authentication callbacks to indicate to the client that authentication has +// partially succeeded, but further steps are required. +type PartialSuccessError struct { + // Next defines the authentication callbacks to apply to further steps. The + // available methods communicated to the client are based on the non-nil + // ServerAuthCallbacks fields. + Next ServerAuthCallbacks +} + +func (p *PartialSuccessError) Error() string { + return "ssh: authenticated with partial success" +} + +// ErrNoAuth is the error value returned if no +// authentication method has been passed yet. This happens as a normal +// part of the authentication loop, since the client first tries +// 'none' authentication to discover available methods. +// It is returned in ServerAuthError.Errors from NewServerConn. +var ErrNoAuth = errors.New("ssh: no auth passed yet") + +// BannerError is an error that can be returned by authentication handlers in +// ServerConfig to send a banner message to the client. +type BannerError struct { + Err error + Message string +} + +func (b *BannerError) Unwrap() error { + return b.Err +} + +func (b *BannerError) Error() string { + if b.Err == nil { + return b.Message + } + return b.Err.Error() +} + +func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, error) { + if config.PreAuthConnCallback != nil { + config.PreAuthConnCallback(s) + } + + sessionID := s.transport.getSessionID() + var cache pubKeyCache + var perms *Permissions + + authFailures := 0 + noneAuthCount := 0 + var authErrs []error + var calledBannerCallback bool + partialSuccessReturned := false + // Set the initial authentication callbacks from the config. They can be + // changed if a PartialSuccessError is returned. + authConfig := ServerAuthCallbacks{ + PasswordCallback: config.PasswordCallback, + PublicKeyCallback: config.PublicKeyCallback, + KeyboardInteractiveCallback: config.KeyboardInteractiveCallback, + GSSAPIWithMICConfig: config.GSSAPIWithMICConfig, + } + +userAuthLoop: + for { + if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 { + discMsg := &disconnectMsg{ + Reason: 2, + Message: "too many authentication failures", + } + + if err := s.transport.writePacket(Marshal(discMsg)); err != nil { + return nil, err + } + authErrs = append(authErrs, discMsg) + return nil, &ServerAuthError{Errors: authErrs} + } + + var userAuthReq userAuthRequestMsg + if packet, err := s.transport.readPacket(); err != nil { + if err == io.EOF { + return nil, &ServerAuthError{Errors: authErrs} + } + return nil, err + } else if err = Unmarshal(packet, &userAuthReq); err != nil { + return nil, err + } + + if userAuthReq.Service != serviceSSH { + return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service) + } + + if s.user != userAuthReq.User && partialSuccessReturned { + return nil, fmt.Errorf("ssh: client changed the user after a partial success authentication, previous user %q, current user %q", + s.user, userAuthReq.User) + } + + s.user = userAuthReq.User + + if !calledBannerCallback && config.BannerCallback != nil { + calledBannerCallback = true + if msg := config.BannerCallback(s); msg != "" { + if err := s.SendAuthBanner(msg); err != nil { + return nil, err + } + } + } + + perms = nil + authErr := ErrNoAuth + + switch userAuthReq.Method { + case "none": + noneAuthCount++ + // We don't allow none authentication after a partial success + // response. + if config.NoClientAuth && !partialSuccessReturned { + if config.NoClientAuthCallback != nil { + perms, authErr = config.NoClientAuthCallback(s) + } else { + authErr = nil + } + } + case "password": + if authConfig.PasswordCallback == nil { + authErr = errors.New("ssh: password auth not configured") + break + } + payload := userAuthReq.Payload + if len(payload) < 1 || payload[0] != 0 { + return nil, parseError(msgUserAuthRequest) + } + payload = payload[1:] + password, payload, ok := parseString(payload) + if !ok || len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + + perms, authErr = authConfig.PasswordCallback(s, password) + case "keyboard-interactive": + if authConfig.KeyboardInteractiveCallback == nil { + authErr = errors.New("ssh: keyboard-interactive auth not configured") + break + } + + prompter := &sshClientKeyboardInteractive{s} + perms, authErr = authConfig.KeyboardInteractiveCallback(s, prompter.Challenge) + case "publickey": + if authConfig.PublicKeyCallback == nil { + authErr = errors.New("ssh: publickey auth not configured") + break + } + payload := userAuthReq.Payload + if len(payload) < 1 { + return nil, parseError(msgUserAuthRequest) + } + isQuery := payload[0] == 0 + payload = payload[1:] + algoBytes, payload, ok := parseString(payload) + if !ok { + return nil, parseError(msgUserAuthRequest) + } + algo := string(algoBytes) + if !slices.Contains(config.PublicKeyAuthAlgorithms, underlyingAlgo(algo)) { + authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo) + break + } + + pubKeyData, payload, ok := parseString(payload) + if !ok { + return nil, parseError(msgUserAuthRequest) + } + + pubKey, err := ParsePublicKey(pubKeyData) + if err != nil { + return nil, err + } + + candidate, ok := cache.get(s.user, pubKeyData) + if !ok { + candidate.user = s.user + candidate.pubKeyData = pubKeyData + candidate.perms, candidate.result = authConfig.PublicKeyCallback(s, pubKey) + _, isPartialSuccessError := candidate.result.(*PartialSuccessError) + if isPartialSuccessError && config.VerifiedPublicKeyCallback != nil { + return nil, errors.New("ssh: invalid library usage: PublicKeyCallback must not return partial success when VerifiedPublicKeyCallback is defined") + } + + if (candidate.result == nil || isPartialSuccessError) && + candidate.perms != nil && + candidate.perms.CriticalOptions != nil && + candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" { + if err := checkSourceAddress( + s.RemoteAddr(), + candidate.perms.CriticalOptions[sourceAddressCriticalOption]); err != nil { + candidate.result = err + } + } + cache.add(candidate) + } + + if isQuery { + // The client can query if the given public key + // would be okay. + + if len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + _, isPartialSuccessError := candidate.result.(*PartialSuccessError) + if candidate.result == nil || isPartialSuccessError { + okMsg := userAuthPubKeyOkMsg{ + Algo: algo, + PubKey: pubKeyData, + } + if err = s.transport.writePacket(Marshal(&okMsg)); err != nil { + return nil, err + } + continue userAuthLoop + } + authErr = candidate.result + } else { + sig, payload, ok := parseSignature(payload) + if !ok || len(payload) > 0 { + return nil, parseError(msgUserAuthRequest) + } + // Ensure the declared public key algo is compatible with the + // decoded one. This check will ensure we don't accept e.g. + // ssh-rsa-cert-v01@openssh.com algorithm with ssh-rsa public + // key type. The algorithm and public key type must be + // consistent: both must be certificate algorithms, or neither. + if !slices.Contains(algorithmsForKeyFormat(pubKey.Type()), algo) { + authErr = fmt.Errorf("ssh: public key type %q not compatible with selected algorithm %q", + pubKey.Type(), algo) + break + } + // Ensure the public key algo and signature algo + // are supported. Compare the private key + // algorithm name that corresponds to algo with + // sig.Format. This is usually the same, but + // for certs, the names differ. + if !slices.Contains(config.PublicKeyAuthAlgorithms, sig.Format) { + authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format) + break + } + if !isAlgoCompatible(algo, sig.Format) { + authErr = fmt.Errorf("ssh: signature %q not compatible with selected algorithm %q", sig.Format, algo) + break + } + + signedData := buildDataSignedForAuth(sessionID, userAuthReq, algo, pubKeyData) + + if err := pubKey.Verify(signedData, sig); err != nil { + return nil, err + } + + authErr = candidate.result + perms = candidate.perms + if authErr == nil && config.VerifiedPublicKeyCallback != nil { + // Only call VerifiedPublicKeyCallback after the key has been accepted + // and successfully verified. If authErr is non-nil, the key is not + // considered verified and the callback must not run. + perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, algo) + } + } + case "gssapi-with-mic": + if authConfig.GSSAPIWithMICConfig == nil { + authErr = errors.New("ssh: gssapi-with-mic auth not configured") + break + } + gssapiConfig := authConfig.GSSAPIWithMICConfig + userAuthRequestGSSAPI, err := parseGSSAPIPayload(userAuthReq.Payload) + if err != nil { + return nil, parseError(msgUserAuthRequest) + } + // OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication. + if userAuthRequestGSSAPI.N == 0 { + authErr = fmt.Errorf("ssh: Mechanism negotiation is not supported") + break + } + var i uint32 + present := false + for i = 0; i < userAuthRequestGSSAPI.N; i++ { + if userAuthRequestGSSAPI.OIDS[i].Equal(krb5Mesh) { + present = true + break + } + } + if !present { + authErr = fmt.Errorf("ssh: GSSAPI authentication must use the Kerberos V5 mechanism") + break + } + // Initial server response, see RFC 4462 section 3.3. + if err := s.transport.writePacket(Marshal(&userAuthGSSAPIResponse{ + SupportMech: krb5OID, + })); err != nil { + return nil, err + } + // Exchange token, see RFC 4462 section 3.4. + packet, err := s.transport.readPacket() + if err != nil { + return nil, err + } + userAuthGSSAPITokenReq := &userAuthGSSAPIToken{} + if err := Unmarshal(packet, userAuthGSSAPITokenReq); err != nil { + return nil, err + } + authErr, perms, err = gssExchangeToken(gssapiConfig, userAuthGSSAPITokenReq.Token, s, sessionID, + userAuthReq) + if err != nil { + return nil, err + } + default: + authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method) + } + + authErrs = append(authErrs, authErr) + + if config.AuthLogCallback != nil { + config.AuthLogCallback(s, userAuthReq.Method, authErr) + } + + var bannerErr *BannerError + if errors.As(authErr, &bannerErr) { + if bannerErr.Message != "" { + if err := s.SendAuthBanner(bannerErr.Message); err != nil { + return nil, err + } + } + } + + if authErr == nil { + break userAuthLoop + } + + var failureMsg userAuthFailureMsg + + if partialSuccess, ok := authErr.(*PartialSuccessError); ok { + // After a partial success error we don't allow changing the user + // name and execute the NoClientAuthCallback. + partialSuccessReturned = true + + // In case a partial success is returned, the server may send + // a new set of authentication methods. + authConfig = partialSuccess.Next + + // Reset pubkey cache, as the new PublicKeyCallback might + // accept a different set of public keys. + cache = pubKeyCache{} + + // Send back a partial success message to the user. + failureMsg.PartialSuccess = true + } else { + // Allow initial attempt of 'none' without penalty. + if authFailures > 0 || userAuthReq.Method != "none" || noneAuthCount != 1 { + authFailures++ + } + if config.MaxAuthTries > 0 && authFailures >= config.MaxAuthTries { + // If we have hit the max attempts, don't bother sending the + // final SSH_MSG_USERAUTH_FAILURE message, since there are + // no more authentication methods which can be attempted, + // and this message may cause the client to re-attempt + // authentication while we send the disconnect message. + // Continue, and trigger the disconnect at the start of + // the loop. + // + // The SSH specification is somewhat confusing about this, + // RFC 4252 Section 5.1 requires each authentication failure + // be responded to with a respective SSH_MSG_USERAUTH_FAILURE + // message, but Section 4 says the server should disconnect + // after some number of attempts, but it isn't explicit which + // message should take precedence (i.e. should there be a failure + // message than a disconnect message, or if we are going to + // disconnect, should we only send that message.) + // + // Either way, OpenSSH disconnects immediately after the last + // failed authentication attempt, and given they are typically + // considered the golden implementation it seems reasonable + // to match that behavior. + continue + } + } + + if authConfig.PasswordCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "password") + } + if authConfig.PublicKeyCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "publickey") + } + if authConfig.KeyboardInteractiveCallback != nil { + failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive") + } + if authConfig.GSSAPIWithMICConfig != nil && authConfig.GSSAPIWithMICConfig.Server != nil && + authConfig.GSSAPIWithMICConfig.AllowLogin != nil { + failureMsg.Methods = append(failureMsg.Methods, "gssapi-with-mic") + } + + if len(failureMsg.Methods) == 0 { + return nil, errors.New("ssh: no authentication methods available") + } + + if err := s.transport.writePacket(Marshal(&failureMsg)); err != nil { + return nil, err + } + } + + if err := s.transport.writePacket([]byte{msgUserAuthSuccess}); err != nil { + return nil, err + } + return perms, nil +} + +// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by +// asking the client on the other side of a ServerConn. +type sshClientKeyboardInteractive struct { + *connection +} + +func (c *sshClientKeyboardInteractive) Challenge(name, instruction string, questions []string, echos []bool) (answers []string, err error) { + if len(questions) != len(echos) { + return nil, errors.New("ssh: echos and questions must have equal length") + } + + var prompts []byte + for i := range questions { + prompts = appendString(prompts, questions[i]) + prompts = appendBool(prompts, echos[i]) + } + + if err := c.transport.writePacket(Marshal(&userAuthInfoRequestMsg{ + Name: name, + Instruction: instruction, + NumPrompts: uint32(len(questions)), + Prompts: prompts, + })); err != nil { + return nil, err + } + + packet, err := c.transport.readPacket() + if err != nil { + return nil, err + } + if packet[0] != msgUserAuthInfoResponse { + return nil, unexpectedMessageError(msgUserAuthInfoResponse, packet[0]) + } + packet = packet[1:] + + n, packet, ok := parseUint32(packet) + if !ok || int(n) != len(questions) { + return nil, parseError(msgUserAuthInfoResponse) + } + + for i := uint32(0); i < n; i++ { + ans, rest, ok := parseString(packet) + if !ok { + return nil, parseError(msgUserAuthInfoResponse) + } + + answers = append(answers, string(ans)) + packet = rest + } + if len(packet) != 0 { + return nil, errors.New("ssh: junk at end of message") + } + + return answers, nil +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/session.go b/cli/vendor/golang.org/x/crypto/ssh/session.go new file mode 100644 index 0000000..acef622 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/session.go @@ -0,0 +1,647 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +// Session implements an interactive session described in +// "RFC 4254, section 6". + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "sync" +) + +type Signal string + +// POSIX signals as listed in RFC 4254 Section 6.10. +const ( + SIGABRT Signal = "ABRT" + SIGALRM Signal = "ALRM" + SIGFPE Signal = "FPE" + SIGHUP Signal = "HUP" + SIGILL Signal = "ILL" + SIGINT Signal = "INT" + SIGKILL Signal = "KILL" + SIGPIPE Signal = "PIPE" + SIGQUIT Signal = "QUIT" + SIGSEGV Signal = "SEGV" + SIGTERM Signal = "TERM" + SIGUSR1 Signal = "USR1" + SIGUSR2 Signal = "USR2" +) + +var signals = map[Signal]int{ + SIGABRT: 6, + SIGALRM: 14, + SIGFPE: 8, + SIGHUP: 1, + SIGILL: 4, + SIGINT: 2, + SIGKILL: 9, + SIGPIPE: 13, + SIGQUIT: 3, + SIGSEGV: 11, + SIGTERM: 15, +} + +type TerminalModes map[uint8]uint32 + +// POSIX terminal mode flags as listed in RFC 4254 Section 8. +const ( + tty_OP_END = 0 + VINTR = 1 + VQUIT = 2 + VERASE = 3 + VKILL = 4 + VEOF = 5 + VEOL = 6 + VEOL2 = 7 + VSTART = 8 + VSTOP = 9 + VSUSP = 10 + VDSUSP = 11 + VREPRINT = 12 + VWERASE = 13 + VLNEXT = 14 + VFLUSH = 15 + VSWTCH = 16 + VSTATUS = 17 + VDISCARD = 18 + IGNPAR = 30 + PARMRK = 31 + INPCK = 32 + ISTRIP = 33 + INLCR = 34 + IGNCR = 35 + ICRNL = 36 + IUCLC = 37 + IXON = 38 + IXANY = 39 + IXOFF = 40 + IMAXBEL = 41 + IUTF8 = 42 // RFC 8160 + ISIG = 50 + ICANON = 51 + XCASE = 52 + ECHO = 53 + ECHOE = 54 + ECHOK = 55 + ECHONL = 56 + NOFLSH = 57 + TOSTOP = 58 + IEXTEN = 59 + ECHOCTL = 60 + ECHOKE = 61 + PENDIN = 62 + OPOST = 70 + OLCUC = 71 + ONLCR = 72 + OCRNL = 73 + ONOCR = 74 + ONLRET = 75 + CS7 = 90 + CS8 = 91 + PARENB = 92 + PARODD = 93 + TTY_OP_ISPEED = 128 + TTY_OP_OSPEED = 129 +) + +// A Session represents a connection to a remote command or shell. +type Session struct { + // Stdin specifies the remote process's standard input. + // If Stdin is nil, the remote process reads from an empty + // bytes.Buffer. + Stdin io.Reader + + // Stdout and Stderr specify the remote process's standard + // output and error. + // + // If either is nil, Run connects the corresponding file + // descriptor to an instance of io.Discard. There is a + // fixed amount of buffering that is shared for the two streams. + // If either blocks it may eventually cause the remote + // command to block. + Stdout io.Writer + Stderr io.Writer + + ch Channel // the channel backing this session + started bool // true once Start, Run or Shell is invoked. + copyFuncs []func() error + errors chan error // one send per copyFunc + + // true if pipe method is active + stdinpipe, stdoutpipe, stderrpipe bool + + // stdinPipeWriter is non-nil if StdinPipe has not been called + // and Stdin was specified by the user; it is the write end of + // a pipe connecting Session.Stdin to the stdin channel. + stdinPipeWriter io.WriteCloser + + exitStatus chan error +} + +// SendRequest sends an out-of-band channel request on the SSH channel +// underlying the session. +func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { + return s.ch.SendRequest(name, wantReply, payload) +} + +func (s *Session) Close() error { + return s.ch.Close() +} + +// RFC 4254 Section 6.4. +type setenvRequest struct { + Name string + Value string +} + +// Setenv sets an environment variable that will be applied to any +// command executed by Shell or Run. +func (s *Session) Setenv(name, value string) error { + msg := setenvRequest{ + Name: name, + Value: value, + } + ok, err := s.ch.SendRequest("env", true, Marshal(&msg)) + if err == nil && !ok { + err = errors.New("ssh: setenv failed") + } + return err +} + +// RFC 4254 Section 6.2. +type ptyRequestMsg struct { + Term string + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 + Modelist string +} + +// RequestPty requests the association of a pty with the session on the remote host. +func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error { + var tm []byte + for k, v := range termmodes { + kv := struct { + Key byte + Val uint32 + }{k, v} + + tm = append(tm, Marshal(&kv)...) + } + tm = append(tm, tty_OP_END) + req := ptyRequestMsg{ + Term: term, + Columns: uint32(w), + Rows: uint32(h), + Width: uint32(w * 8), + Height: uint32(h * 8), + Modelist: string(tm), + } + ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req)) + if err == nil && !ok { + err = errors.New("ssh: pty-req failed") + } + return err +} + +// RFC 4254 Section 6.5. +type subsystemRequestMsg struct { + Subsystem string +} + +// RequestSubsystem requests the association of a subsystem with the session on the remote host. +// A subsystem is a predefined command that runs in the background when the ssh session is initiated +func (s *Session) RequestSubsystem(subsystem string) error { + msg := subsystemRequestMsg{ + Subsystem: subsystem, + } + ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg)) + if err == nil && !ok { + err = errors.New("ssh: subsystem request failed") + } + return err +} + +// RFC 4254 Section 6.7. +type ptyWindowChangeMsg struct { + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 +} + +// WindowChange informs the remote host about a terminal window dimension change to h rows and w columns. +func (s *Session) WindowChange(h, w int) error { + req := ptyWindowChangeMsg{ + Columns: uint32(w), + Rows: uint32(h), + Width: uint32(w * 8), + Height: uint32(h * 8), + } + _, err := s.ch.SendRequest("window-change", false, Marshal(&req)) + return err +} + +// RFC 4254 Section 6.9. +type signalMsg struct { + Signal string +} + +// Signal sends the given signal to the remote process. +// sig is one of the SIG* constants. +func (s *Session) Signal(sig Signal) error { + msg := signalMsg{ + Signal: string(sig), + } + + _, err := s.ch.SendRequest("signal", false, Marshal(&msg)) + return err +} + +// RFC 4254 Section 6.5. +type execMsg struct { + Command string +} + +// Start runs cmd on the remote host. Typically, the remote +// server passes cmd to the shell for interpretation. +// A Session only accepts one call to Run, Start or Shell. +func (s *Session) Start(cmd string) error { + if s.started { + return errors.New("ssh: session already started") + } + req := execMsg{ + Command: cmd, + } + + ok, err := s.ch.SendRequest("exec", true, Marshal(&req)) + if err == nil && !ok { + err = fmt.Errorf("ssh: command %v failed", cmd) + } + if err != nil { + return err + } + return s.start() +} + +// Run runs cmd on the remote host. Typically, the remote +// server passes cmd to the shell for interpretation. +// A Session only accepts one call to Run, Start, Shell, Output, +// or CombinedOutput. +// +// The returned error is nil if the command runs, has no problems +// copying stdin, stdout, and stderr, and exits with a zero exit +// status. +// +// If the remote server does not send an exit status, an error of type +// *ExitMissingError is returned. If the command completes +// unsuccessfully or is interrupted by a signal, the error is of type +// *ExitError. Other error types may be returned for I/O problems. +func (s *Session) Run(cmd string) error { + err := s.Start(cmd) + if err != nil { + return err + } + return s.Wait() +} + +// Output runs cmd on the remote host and returns its standard output. +func (s *Session) Output(cmd string) ([]byte, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + var b bytes.Buffer + s.Stdout = &b + err := s.Run(cmd) + return b.Bytes(), err +} + +type singleWriter struct { + b bytes.Buffer + mu sync.Mutex +} + +func (w *singleWriter) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + return w.b.Write(p) +} + +// CombinedOutput runs cmd on the remote host and returns its combined +// standard output and standard error. +func (s *Session) CombinedOutput(cmd string) ([]byte, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + if s.Stderr != nil { + return nil, errors.New("ssh: Stderr already set") + } + var b singleWriter + s.Stdout = &b + s.Stderr = &b + err := s.Run(cmd) + return b.b.Bytes(), err +} + +// Shell starts a login shell on the remote host. A Session only +// accepts one call to Run, Start, Shell, Output, or CombinedOutput. +func (s *Session) Shell() error { + if s.started { + return errors.New("ssh: session already started") + } + + ok, err := s.ch.SendRequest("shell", true, nil) + if err == nil && !ok { + return errors.New("ssh: could not start shell") + } + if err != nil { + return err + } + return s.start() +} + +func (s *Session) start() error { + s.started = true + + type F func(*Session) + for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { + setupFd(s) + } + + s.errors = make(chan error, len(s.copyFuncs)) + for _, fn := range s.copyFuncs { + go func(fn func() error) { + s.errors <- fn() + }(fn) + } + return nil +} + +// Wait waits for the remote command to exit. +// +// The returned error is nil if the command runs, has no problems +// copying stdin, stdout, and stderr, and exits with a zero exit +// status. +// +// If the remote server does not send an exit status, an error of type +// *ExitMissingError is returned. If the command completes +// unsuccessfully or is interrupted by a signal, the error is of type +// *ExitError. Other error types may be returned for I/O problems. +func (s *Session) Wait() error { + if !s.started { + return errors.New("ssh: session not started") + } + waitErr := <-s.exitStatus + + if s.stdinPipeWriter != nil { + s.stdinPipeWriter.Close() + } + var copyError error + for range s.copyFuncs { + if err := <-s.errors; err != nil && copyError == nil { + copyError = err + } + } + if waitErr != nil { + return waitErr + } + return copyError +} + +func (s *Session) wait(reqs <-chan *Request) error { + wm := Waitmsg{status: -1} + // Wait for msg channel to be closed before returning. + for msg := range reqs { + switch msg.Type { + case "exit-status": + wm.status = int(binary.BigEndian.Uint32(msg.Payload)) + case "exit-signal": + var sigval struct { + Signal string + CoreDumped bool + Error string + Lang string + } + if err := Unmarshal(msg.Payload, &sigval); err != nil { + return err + } + + // Must sanitize strings? + wm.signal = sigval.Signal + wm.msg = sigval.Error + wm.lang = sigval.Lang + default: + // This handles keepalives and matches + // OpenSSH's behaviour. + if msg.WantReply { + msg.Reply(false, nil) + } + } + } + if wm.status == 0 { + return nil + } + if wm.status == -1 { + // exit-status was never sent from server + if wm.signal == "" { + // signal was not sent either. RFC 4254 + // section 6.10 recommends against this + // behavior, but it is allowed, so we let + // clients handle it. + return &ExitMissingError{} + } + wm.status = 128 + if _, ok := signals[Signal(wm.signal)]; ok { + wm.status += signals[Signal(wm.signal)] + } + } + + return &ExitError{wm} +} + +// ExitMissingError is returned if a session is torn down cleanly, but +// the server sends no confirmation of the exit status. +type ExitMissingError struct{} + +func (e *ExitMissingError) Error() string { + return "wait: remote command exited without exit status or exit signal" +} + +func (s *Session) stdin() { + if s.stdinpipe { + return + } + var stdin io.Reader + if s.Stdin == nil { + stdin = new(bytes.Buffer) + } else { + r, w := io.Pipe() + go func() { + _, err := io.Copy(w, s.Stdin) + w.CloseWithError(err) + }() + stdin, s.stdinPipeWriter = r, w + } + s.copyFuncs = append(s.copyFuncs, func() error { + _, err := io.Copy(s.ch, stdin) + if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { + err = err1 + } + return err + }) +} + +func (s *Session) stdout() { + if s.stdoutpipe { + return + } + if s.Stdout == nil { + s.Stdout = io.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + _, err := io.Copy(s.Stdout, s.ch) + return err + }) +} + +func (s *Session) stderr() { + if s.stderrpipe { + return + } + if s.Stderr == nil { + s.Stderr = io.Discard + } + s.copyFuncs = append(s.copyFuncs, func() error { + _, err := io.Copy(s.Stderr, s.ch.Stderr()) + return err + }) +} + +// sessionStdin reroutes Close to CloseWrite. +type sessionStdin struct { + io.Writer + ch Channel +} + +func (s *sessionStdin) Close() error { + return s.ch.CloseWrite() +} + +// StdinPipe returns a pipe that will be connected to the +// remote command's standard input when the command starts. +func (s *Session) StdinPipe() (io.WriteCloser, error) { + if s.Stdin != nil { + return nil, errors.New("ssh: Stdin already set") + } + if s.started { + return nil, errors.New("ssh: StdinPipe after process started") + } + s.stdinpipe = true + return &sessionStdin{s.ch, s.ch}, nil +} + +// StdoutPipe returns a pipe that will be connected to the +// remote command's standard output when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StdoutPipe reader is +// not serviced fast enough it may eventually cause the +// remote command to block. +func (s *Session) StdoutPipe() (io.Reader, error) { + if s.Stdout != nil { + return nil, errors.New("ssh: Stdout already set") + } + if s.started { + return nil, errors.New("ssh: StdoutPipe after process started") + } + s.stdoutpipe = true + return s.ch, nil +} + +// StderrPipe returns a pipe that will be connected to the +// remote command's standard error when the command starts. +// There is a fixed amount of buffering that is shared between +// stdout and stderr streams. If the StderrPipe reader is +// not serviced fast enough it may eventually cause the +// remote command to block. +func (s *Session) StderrPipe() (io.Reader, error) { + if s.Stderr != nil { + return nil, errors.New("ssh: Stderr already set") + } + if s.started { + return nil, errors.New("ssh: StderrPipe after process started") + } + s.stderrpipe = true + return s.ch.Stderr(), nil +} + +// newSession returns a new interactive session on the remote host. +func newSession(ch Channel, reqs <-chan *Request) (*Session, error) { + s := &Session{ + ch: ch, + } + s.exitStatus = make(chan error, 1) + go func() { + s.exitStatus <- s.wait(reqs) + }() + + return s, nil +} + +// An ExitError reports unsuccessful completion of a remote command. +type ExitError struct { + Waitmsg +} + +func (e *ExitError) Error() string { + return e.Waitmsg.String() +} + +// Waitmsg stores the information about an exited remote command +// as reported by Wait. +type Waitmsg struct { + status int + signal string + msg string + lang string +} + +// ExitStatus returns the exit status of the remote command. +func (w Waitmsg) ExitStatus() int { + return w.status +} + +// Signal returns the exit signal of the remote command if +// it was terminated violently. +func (w Waitmsg) Signal() string { + return w.signal +} + +// Msg returns the exit message given by the remote command +func (w Waitmsg) Msg() string { + return w.msg +} + +// Lang returns the language tag. See RFC 3066 +func (w Waitmsg) Lang() string { + return w.lang +} + +func (w Waitmsg) String() string { + str := fmt.Sprintf("Process exited with status %v", w.status) + if w.signal != "" { + str += fmt.Sprintf(" from signal %v", w.signal) + } + if w.msg != "" { + str += fmt.Sprintf(". Reason was: %v", w.msg) + } + return str +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/ssh_gss.go b/cli/vendor/golang.org/x/crypto/ssh/ssh_gss.go new file mode 100644 index 0000000..a6249a1 --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/ssh_gss.go @@ -0,0 +1,145 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "encoding/asn1" + "errors" +) + +var krb5OID []byte + +func init() { + krb5OID, _ = asn1.Marshal(krb5Mesh) +} + +// GSSAPIClient provides the API to plug-in GSSAPI authentication for client logins. +type GSSAPIClient interface { + // InitSecContext initiates the establishment of a security context for GSS-API between the + // ssh client and ssh server. Initially the token parameter should be specified as nil. + // The routine may return a outputToken which should be transferred to + // the ssh server, where the ssh server will present it to + // AcceptSecContext. If no token need be sent, InitSecContext will indicate this by setting + // needContinue to false. To complete the context + // establishment, one or more reply tokens may be required from the ssh + // server;if so, InitSecContext will return a needContinue which is true. + // In this case, InitSecContext should be called again when the + // reply token is received from the ssh server, passing the reply + // token to InitSecContext via the token parameters. + // See RFC 2743 section 2.2.1 and RFC 4462 section 3.4. + InitSecContext(target string, token []byte, isGSSDelegCreds bool) (outputToken []byte, needContinue bool, err error) + // GetMIC generates a cryptographic MIC for the SSH2 message, and places + // the MIC in a token for transfer to the ssh server. + // The contents of the MIC field are obtained by calling GSS_GetMIC() + // over the following, using the GSS-API context that was just + // established: + // string session identifier + // byte SSH_MSG_USERAUTH_REQUEST + // string user name + // string service + // string "gssapi-with-mic" + // See RFC 2743 section 2.3.1 and RFC 4462 3.5. + GetMIC(micFiled []byte) ([]byte, error) + // Whenever possible, it should be possible for + // DeleteSecContext() calls to be successfully processed even + // if other calls cannot succeed, thereby enabling context-related + // resources to be released. + // In addition to deleting established security contexts, + // gss_delete_sec_context must also be able to delete "half-built" + // security contexts resulting from an incomplete sequence of + // InitSecContext()/AcceptSecContext() calls. + // See RFC 2743 section 2.2.3. + DeleteSecContext() error +} + +// GSSAPIServer provides the API to plug in GSSAPI authentication for server logins. +type GSSAPIServer interface { + // AcceptSecContext allows a remotely initiated security context between the application + // and a remote peer to be established by the ssh client. The routine may return a + // outputToken which should be transferred to the ssh client, + // where the ssh client will present it to InitSecContext. + // If no token need be sent, AcceptSecContext will indicate this + // by setting the needContinue to false. To + // complete the context establishment, one or more reply tokens may be + // required from the ssh client. if so, AcceptSecContext + // will return a needContinue which is true, in which case it + // should be called again when the reply token is received from the ssh + // client, passing the token to AcceptSecContext via the + // token parameters. + // The srcName return value is the authenticated username. + // See RFC 2743 section 2.2.2 and RFC 4462 section 3.4. + AcceptSecContext(token []byte) (outputToken []byte, srcName string, needContinue bool, err error) + // VerifyMIC verifies that a cryptographic MIC, contained in the token parameter, + // fits the supplied message is received from the ssh client. + // See RFC 2743 section 2.3.2. + VerifyMIC(micField []byte, micToken []byte) error + // Whenever possible, it should be possible for + // DeleteSecContext() calls to be successfully processed even + // if other calls cannot succeed, thereby enabling context-related + // resources to be released. + // In addition to deleting established security contexts, + // gss_delete_sec_context must also be able to delete "half-built" + // security contexts resulting from an incomplete sequence of + // InitSecContext()/AcceptSecContext() calls. + // See RFC 2743 section 2.2.3. + DeleteSecContext() error +} + +var ( + // OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication, + // so we also support the krb5 mechanism only. + // See RFC 1964 section 1. + krb5Mesh = asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2} +) + +// The GSS-API authentication method is initiated when the client sends an SSH_MSG_USERAUTH_REQUEST +// See RFC 4462 section 3.2. +type userAuthRequestGSSAPI struct { + N uint32 + OIDS []asn1.ObjectIdentifier +} + +func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) { + n, rest, ok := parseUint32(payload) + if !ok { + return nil, errors.New("parse uint32 failed") + } + // Each ASN.1 encoded OID must have a minimum + // of 2 bytes; 64 maximum mechanisms is an + // arbitrary, but reasonable ceiling. + const maxMechs = 64 + if n > maxMechs || int(n)*2 > len(rest) { + return nil, errors.New("invalid mechanism count") + } + s := &userAuthRequestGSSAPI{ + N: n, + OIDS: make([]asn1.ObjectIdentifier, n), + } + for i := 0; i < int(n); i++ { + var ( + desiredMech []byte + err error + ) + desiredMech, rest, ok = parseString(rest) + if !ok { + return nil, errors.New("parse string failed") + } + if rest, err = asn1.Unmarshal(desiredMech, &s.OIDS[i]); err != nil { + return nil, err + } + } + return s, nil +} + +// See RFC 4462 section 3.6. +func buildMIC(sessionID string, username string, service string, authMethod string) []byte { + out := make([]byte, 0, 0) + out = appendString(out, sessionID) + out = append(out, msgUserAuthRequest) + out = appendString(out, username) + out = appendString(out, service) + out = appendString(out, authMethod) + return out +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/streamlocal.go b/cli/vendor/golang.org/x/crypto/ssh/streamlocal.go new file mode 100644 index 0000000..152470f --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/streamlocal.go @@ -0,0 +1,116 @@ +package ssh + +import ( + "errors" + "io" + "net" +) + +// streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message +// with "direct-streamlocal@openssh.com" string. +// +// See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding +// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235 +type streamLocalChannelOpenDirectMsg struct { + socketPath string + reserved0 string + reserved1 uint32 +} + +// forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message +// with "forwarded-streamlocal@openssh.com" string. +type forwardedStreamLocalPayload struct { + SocketPath string + Reserved0 string +} + +// streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message +// with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string. +type streamLocalChannelForwardMsg struct { + socketPath string +} + +// ListenUnix is similar to ListenTCP but uses a Unix domain socket. +func (c *Client) ListenUnix(socketPath string) (net.Listener, error) { + c.handleForwardsOnce.Do(c.handleForwards) + m := streamLocalChannelForwardMsg{ + socketPath, + } + // send message + ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m)) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer") + } + ch := c.forwards.add("unix", socketPath) + + return &unixListener{socketPath, c, ch}, nil +} + +func (c *Client) dialStreamLocal(socketPath string) (Channel, error) { + msg := streamLocalChannelOpenDirectMsg{ + socketPath: socketPath, + } + ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg)) + if err != nil { + return nil, err + } + go DiscardRequests(in) + return ch, err +} + +type unixListener struct { + socketPath string + + conn *Client + in <-chan forward +} + +// Accept waits for and returns the next connection to the listener. +func (l *unixListener) Accept() (net.Conn, error) { + s, ok := <-l.in + if !ok { + return nil, io.EOF + } + ch, incoming, err := s.newCh.Accept() + if err != nil { + return nil, err + } + go DiscardRequests(incoming) + + return &chanConn{ + Channel: ch, + laddr: &net.UnixAddr{ + Name: l.socketPath, + Net: "unix", + }, + raddr: &net.UnixAddr{ + Name: "@", + Net: "unix", + }, + }, nil +} + +// Close closes the listener. +func (l *unixListener) Close() error { + // this also closes the listener. + l.conn.forwards.remove("unix", l.socketPath) + m := streamLocalChannelForwardMsg{ + l.socketPath, + } + ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m)) + if err == nil && !ok { + err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed") + } + return err +} + +// Addr returns the listener's network address. +func (l *unixListener) Addr() net.Addr { + return &net.UnixAddr{ + Name: l.socketPath, + Net: "unix", + } +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/tcpip.go b/cli/vendor/golang.org/x/crypto/ssh/tcpip.go new file mode 100644 index 0000000..78c41fe --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/tcpip.go @@ -0,0 +1,545 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "context" + "errors" + "fmt" + "io" + "math/rand" + "net" + "net/netip" + "strconv" + "strings" + "sync" + "time" +) + +// Listen requests the remote peer open a listening socket on +// addr. Incoming connections will be available by calling Accept on +// the returned net.Listener. The listener must be serviced, or the +// SSH connection may hang. +// N must be "tcp", "tcp4", "tcp6", or "unix". +// +// If the address is a hostname, it is sent to the remote peer as-is, without +// being resolved locally, and the Listener Addr method will return a zero IP. +func (c *Client) Listen(n, addr string) (net.Listener, error) { + switch n { + case "tcp", "tcp4", "tcp6": + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + port, err := strconv.ParseInt(portStr, 10, 32) + if err != nil { + return nil, err + } + return c.listenTCPInternal(host, int(port)) + case "unix": + return c.ListenUnix(addr) + default: + return nil, fmt.Errorf("ssh: unsupported protocol: %s", n) + } +} + +// Automatic port allocation is broken with OpenSSH before 6.0. See +// also https://bugzilla.mindrot.org/show_bug.cgi?id=2017. In +// particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0, +// rather than the actual port number. This means you can never open +// two different listeners with auto allocated ports. We work around +// this by trying explicit ports until we succeed. + +const openSSHPrefix = "OpenSSH_" + +var portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano())) + +// isBrokenOpenSSHVersion returns true if the given version string +// specifies a version of OpenSSH that is known to have a bug in port +// forwarding. +func isBrokenOpenSSHVersion(versionStr string) bool { + i := strings.Index(versionStr, openSSHPrefix) + if i < 0 { + return false + } + i += len(openSSHPrefix) + j := i + for ; j < len(versionStr); j++ { + if versionStr[j] < '0' || versionStr[j] > '9' { + break + } + } + version, _ := strconv.Atoi(versionStr[i:j]) + return version < 6 +} + +// autoPortListenWorkaround simulates automatic port allocation by +// trying random ports repeatedly. +func (c *Client) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) { + var sshListener net.Listener + var err error + const tries = 10 + for i := 0; i < tries; i++ { + addr := *laddr + addr.Port = 1024 + portRandomizer.Intn(60000) + sshListener, err = c.ListenTCP(&addr) + if err == nil { + laddr.Port = addr.Port + return sshListener, err + } + } + return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err) +} + +// RFC 4254 7.1 +type channelForwardMsg struct { + addr string + rport uint32 +} + +// handleForwards starts goroutines handling forwarded connections. +// It's called on first use by (*Client).ListenTCP to not launch +// goroutines until needed. +func (c *Client) handleForwards() { + go c.forwards.handleChannels(c.HandleChannelOpen("forwarded-tcpip")) + go c.forwards.handleChannels(c.HandleChannelOpen("forwarded-streamlocal@openssh.com")) +} + +// ListenTCP requests the remote peer open a listening socket +// on laddr. Incoming connections will be available by calling +// Accept on the returned net.Listener. +// +// ListenTCP accepts an IP address, to provide a hostname use [Client.Listen] +// with "tcp", "tcp4", or "tcp6" network instead. +func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { + c.handleForwardsOnce.Do(c.handleForwards) + if laddr.Port == 0 && isBrokenOpenSSHVersion(string(c.ServerVersion())) { + return c.autoPortListenWorkaround(laddr) + } + + return c.listenTCPInternal(laddr.IP.String(), laddr.Port) +} + +func (c *Client) listenTCPInternal(host string, port int) (net.Listener, error) { + c.handleForwardsOnce.Do(c.handleForwards) + + m := channelForwardMsg{ + host, + uint32(port), + } + // send message + ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m)) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.New("ssh: tcpip-forward request denied by peer") + } + + // If the original port was 0, then the remote side will + // supply a real port number in the response. + if port == 0 { + var p struct { + Port uint32 + } + if err := Unmarshal(resp, &p); err != nil { + return nil, err + } + port = int(p.Port) + } + // Construct a local address placeholder for the remote listener. If the + // original host is an IP address, preserve it so that Listener.Addr() + // reports the same IP. If the host is a hostname or cannot be parsed as an + // IP, fall back to IPv4zero. The port field is always set, even if the + // original port was 0, because in that case the remote server will assign + // one, allowing callers to determine which port was selected. + ip := net.IPv4zero + if parsed, err := netip.ParseAddr(host); err == nil { + ip = net.IP(parsed.AsSlice()) + } + laddr := &net.TCPAddr{ + IP: ip, + Port: port, + } + addr := net.JoinHostPort(host, strconv.FormatInt(int64(port), 10)) + ch := c.forwards.add("tcp", addr) + + return &tcpListener{laddr, addr, c, ch}, nil +} + +// forwardList stores a mapping between remote +// forward requests and the tcpListeners. +type forwardList struct { + sync.Mutex + entries []forwardEntry +} + +// forwardEntry represents an established mapping of a laddr on a +// remote ssh server to a channel connected to a tcpListener. +type forwardEntry struct { + addr string // host:port or socket path + network string // tcp or unix + c chan forward +} + +// forward represents an incoming forwarded tcpip connection. The +// arguments to add/remove/lookup should be address as specified in +// the original forward-request. +type forward struct { + newCh NewChannel // the ssh client channel underlying this forward + raddr net.Addr // the raddr of the incoming connection +} + +func (l *forwardList) add(n, addr string) chan forward { + l.Lock() + defer l.Unlock() + f := forwardEntry{ + addr: addr, + network: n, + c: make(chan forward, 1), + } + l.entries = append(l.entries, f) + return f.c +} + +// See RFC 4254, section 7.2 +type forwardedTCPPayload struct { + Addr string + Port uint32 + OriginAddr string + OriginPort uint32 +} + +// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr. +func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) { + if port == 0 || port > 65535 { + return nil, fmt.Errorf("ssh: port number out of range: %d", port) + } + ip, err := netip.ParseAddr(addr) + if err != nil { + return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr) + } + return &net.TCPAddr{IP: net.IP(ip.AsSlice()), Port: int(port)}, nil +} + +func (l *forwardList) handleChannels(in <-chan NewChannel) { + for ch := range in { + var ( + addr string + network string + raddr net.Addr + err error + ) + switch channelType := ch.ChannelType(); channelType { + case "forwarded-tcpip": + var payload forwardedTCPPayload + if err = Unmarshal(ch.ExtraData(), &payload); err != nil { + ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error()) + continue + } + + // RFC 4254 section 7.2 specifies that incoming addresses should + // list the address that was connected, in string format. It is the + // same address used in the tcpip-forward request. The originator + // address is an IP address instead. + addr = net.JoinHostPort(payload.Addr, strconv.FormatUint(uint64(payload.Port), 10)) + + raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort) + if err != nil { + ch.Reject(ConnectionFailed, err.Error()) + continue + } + network = "tcp" + case "forwarded-streamlocal@openssh.com": + var payload forwardedStreamLocalPayload + if err = Unmarshal(ch.ExtraData(), &payload); err != nil { + ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error()) + continue + } + addr = payload.SocketPath + raddr = &net.UnixAddr{ + Name: "@", + Net: "unix", + } + network = "unix" + default: + panic(fmt.Errorf("ssh: unknown channel type %s", channelType)) + } + if ok := l.forward(network, addr, raddr, ch); !ok { + // Section 7.2, implementations MUST reject spurious incoming + // connections. + ch.Reject(Prohibited, "no forward for address") + continue + } + + } +} + +// remove removes the forward entry, and the channel feeding its +// listener. +func (l *forwardList) remove(n, addr string) { + l.Lock() + defer l.Unlock() + for i, f := range l.entries { + if n == f.network && addr == f.addr { + l.entries = append(l.entries[:i], l.entries[i+1:]...) + close(f.c) + return + } + } +} + +// closeAll closes and clears all forwards. +func (l *forwardList) closeAll() { + l.Lock() + defer l.Unlock() + for _, f := range l.entries { + close(f.c) + } + l.entries = nil +} + +func (l *forwardList) forward(n, addr string, raddr net.Addr, ch NewChannel) bool { + l.Lock() + defer l.Unlock() + for _, f := range l.entries { + if n == f.network && addr == f.addr { + f.c <- forward{newCh: ch, raddr: raddr} + return true + } + } + return false +} + +type tcpListener struct { + laddr *net.TCPAddr + addr string + + conn *Client + in <-chan forward +} + +// Accept waits for and returns the next connection to the listener. +func (l *tcpListener) Accept() (net.Conn, error) { + s, ok := <-l.in + if !ok { + return nil, io.EOF + } + ch, incoming, err := s.newCh.Accept() + if err != nil { + return nil, err + } + go DiscardRequests(incoming) + + return &chanConn{ + Channel: ch, + laddr: l.laddr, + raddr: s.raddr, + }, nil +} + +// Close closes the listener. +func (l *tcpListener) Close() error { + host, port, err := net.SplitHostPort(l.addr) + if err != nil { + return err + } + rport, err := strconv.ParseUint(port, 10, 32) + if err != nil { + return err + } + m := channelForwardMsg{ + host, + uint32(rport), + } + + // this also closes the listener. + l.conn.forwards.remove("tcp", l.addr) + ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m)) + if err == nil && !ok { + err = errors.New("ssh: cancel-tcpip-forward failed") + } + return err +} + +// Addr returns the listener's network address. +func (l *tcpListener) Addr() net.Addr { + return l.laddr +} + +// DialContext initiates a connection to the addr from the remote host. +// +// The provided Context must be non-nil. If the context expires before the +// connection is complete, an error is returned. Once successfully connected, +// any expiration of the context will not affect the connection. +// +// See func Dial for additional information. +func (c *Client) DialContext(ctx context.Context, n, addr string) (net.Conn, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + type connErr struct { + conn net.Conn + err error + } + ch := make(chan connErr) + go func() { + conn, err := c.Dial(n, addr) + select { + case ch <- connErr{conn, err}: + case <-ctx.Done(): + if conn != nil { + conn.Close() + } + } + }() + select { + case res := <-ch: + return res.conn, res.err + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +// Dial initiates a connection to the addr from the remote host. +// The resulting connection has a zero LocalAddr() and RemoteAddr(). +func (c *Client) Dial(n, addr string) (net.Conn, error) { + var ch Channel + switch n { + case "tcp", "tcp4", "tcp6": + // Parse the address into host and numeric port. + host, portString, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + port, err := strconv.ParseUint(portString, 10, 16) + if err != nil { + return nil, err + } + ch, err = c.dial(net.IPv4zero.String(), 0, host, int(port)) + if err != nil { + return nil, err + } + // Use a zero address for local and remote address. + zeroAddr := &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, + } + return &chanConn{ + Channel: ch, + laddr: zeroAddr, + raddr: zeroAddr, + }, nil + case "unix": + var err error + ch, err = c.dialStreamLocal(addr) + if err != nil { + return nil, err + } + return &chanConn{ + Channel: ch, + laddr: &net.UnixAddr{ + Name: "@", + Net: "unix", + }, + raddr: &net.UnixAddr{ + Name: addr, + Net: "unix", + }, + }, nil + default: + return nil, fmt.Errorf("ssh: unsupported protocol: %s", n) + } +} + +// DialTCP connects to the remote address raddr on the network net, +// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used +// as the local address for the connection. +func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) { + if laddr == nil { + laddr = &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, + } + } + ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port) + if err != nil { + return nil, err + } + return &chanConn{ + Channel: ch, + laddr: laddr, + raddr: raddr, + }, nil +} + +// RFC 4254 7.2 +type channelOpenDirectMsg struct { + raddr string + rport uint32 + laddr string + lport uint32 +} + +func (c *Client) dial(laddr string, lport int, raddr string, rport int) (Channel, error) { + msg := channelOpenDirectMsg{ + raddr: raddr, + rport: uint32(rport), + laddr: laddr, + lport: uint32(lport), + } + ch, in, err := c.OpenChannel("direct-tcpip", Marshal(&msg)) + if err != nil { + return nil, err + } + go DiscardRequests(in) + return ch, nil +} + +type tcpChan struct { + Channel // the backing channel +} + +// chanConn fulfills the net.Conn interface without +// the tcpChan having to hold laddr or raddr directly. +type chanConn struct { + Channel + laddr, raddr net.Addr +} + +// LocalAddr returns the local network address. +func (t *chanConn) LocalAddr() net.Addr { + return t.laddr +} + +// RemoteAddr returns the remote network address. +func (t *chanConn) RemoteAddr() net.Addr { + return t.raddr +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. +func (t *chanConn) SetDeadline(deadline time.Time) error { + if err := t.SetReadDeadline(deadline); err != nil { + return err + } + return t.SetWriteDeadline(deadline) +} + +// SetReadDeadline sets the read deadline. +// A zero value for t means Read will not time out. +// After the deadline, the error from Read will implement net.Error +// with Timeout() == true. +func (t *chanConn) SetReadDeadline(deadline time.Time) error { + // for compatibility with previous version, + // the error message contains "tcpChan" + return errors.New("ssh: tcpChan: deadline not supported") +} + +// SetWriteDeadline exists to satisfy the net.Conn interface +// but is not implemented by this type. It always returns an error. +func (t *chanConn) SetWriteDeadline(deadline time.Time) error { + return errors.New("ssh: tcpChan: deadline not supported") +} diff --git a/cli/vendor/golang.org/x/crypto/ssh/transport.go b/cli/vendor/golang.org/x/crypto/ssh/transport.go new file mode 100644 index 0000000..fa3dd6a --- /dev/null +++ b/cli/vendor/golang.org/x/crypto/ssh/transport.go @@ -0,0 +1,377 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssh + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "log" +) + +// debugTransport if set, will print packet types as they go over the +// wire. No message decoding is done, to minimize the impact on timing. +const debugTransport = false + +// packetConn represents a transport that implements packet based +// operations. +type packetConn interface { + // Encrypt and send a packet of data to the remote peer. + writePacket(packet []byte) error + + // Read a packet from the connection. The read is blocking, + // i.e. if error is nil, then the returned byte slice is + // always non-empty. + readPacket() ([]byte, error) + + // Close closes the write-side of the connection. + Close() error +} + +// transport is the keyingTransport that implements the SSH packet +// protocol. +type transport struct { + reader connectionState + writer connectionState + + bufReader *bufio.Reader + bufWriter *bufio.Writer + rand io.Reader + isClient bool + io.Closer + + strictMode bool + initialKEXDone bool +} + +// packetCipher represents a combination of SSH encryption/MAC +// protocol. A single instance should be used for one direction only. +type packetCipher interface { + // writeCipherPacket encrypts the packet and writes it to w. The + // contents of the packet are generally scrambled. + writeCipherPacket(seqnum uint32, w io.Writer, rand io.Reader, packet []byte) error + + // readCipherPacket reads and decrypts a packet of data. The + // returned packet may be overwritten by future calls of + // readPacket. + readCipherPacket(seqnum uint32, r io.Reader) ([]byte, error) +} + +// connectionState represents one side (read or write) of the +// connection. This is necessary because each direction has its own +// keys, and can even have its own algorithms +type connectionState struct { + packetCipher + seqNum uint32 + dir direction + pendingKeyChange chan packetCipher +} + +func (t *transport) setStrictMode() error { + if t.reader.seqNum != 1 { + return errors.New("ssh: sequence number != 1 when strict KEX mode requested") + } + t.strictMode = true + return nil +} + +func (t *transport) setInitialKEXDone() { + t.initialKEXDone = true +} + +// prepareKeyChange sets up key material for a keychange. The key changes in +// both directions are triggered by reading and writing a msgNewKey packet +// respectively. +func (t *transport) prepareKeyChange(algs *NegotiatedAlgorithms, kexResult *kexResult) error { + ciph, err := newPacketCipher(t.reader.dir, algs.Read, kexResult) + if err != nil { + return err + } + t.reader.pendingKeyChange <- ciph + + ciph, err = newPacketCipher(t.writer.dir, algs.Write, kexResult) + if err != nil { + return err + } + t.writer.pendingKeyChange <- ciph + + return nil +} + +func (t *transport) printPacket(p []byte, write bool) { + if len(p) == 0 { + return + } + who := "server" + if t.isClient { + who = "client" + } + what := "read" + if write { + what = "write" + } + + log.Println(what, who, p[0]) +} + +// Read and decrypt next packet. +func (t *transport) readPacket() (p []byte, err error) { + for { + p, err = t.reader.readPacket(t.bufReader, t.strictMode) + if err != nil { + break + } + // in strict mode we pass through DEBUG and IGNORE packets only during the initial KEX + if len(p) == 0 || (t.strictMode && !t.initialKEXDone) || (p[0] != msgIgnore && p[0] != msgDebug) { + break + } + } + if debugTransport { + t.printPacket(p, false) + } + + return p, err +} + +func (s *connectionState) readPacket(r *bufio.Reader, strictMode bool) ([]byte, error) { + packet, err := s.packetCipher.readCipherPacket(s.seqNum, r) + s.seqNum++ + if err == nil && len(packet) == 0 { + err = errors.New("ssh: zero length packet") + } + + if len(packet) > 0 { + switch packet[0] { + case msgNewKeys: + select { + case cipher := <-s.pendingKeyChange: + s.packetCipher = cipher + if strictMode { + s.seqNum = 0 + } + default: + return nil, errors.New("ssh: got bogus newkeys message") + } + + case msgDisconnect: + // Transform a disconnect message into an + // error. Since this is lowest level at which + // we interpret message types, doing it here + // ensures that we don't have to handle it + // elsewhere. + var msg disconnectMsg + if err := Unmarshal(packet, &msg); err != nil { + return nil, err + } + return nil, &msg + } + } + + // The packet may point to an internal buffer, so copy the + // packet out here. + fresh := make([]byte, len(packet)) + copy(fresh, packet) + + return fresh, err +} + +func (t *transport) writePacket(packet []byte) error { + if debugTransport { + t.printPacket(packet, true) + } + return t.writer.writePacket(t.bufWriter, t.rand, packet, t.strictMode) +} + +func (s *connectionState) writePacket(w *bufio.Writer, rand io.Reader, packet []byte, strictMode bool) error { + changeKeys := len(packet) > 0 && packet[0] == msgNewKeys + + err := s.packetCipher.writeCipherPacket(s.seqNum, w, rand, packet) + if err != nil { + return err + } + if err = w.Flush(); err != nil { + return err + } + s.seqNum++ + if changeKeys { + select { + case cipher := <-s.pendingKeyChange: + s.packetCipher = cipher + if strictMode { + s.seqNum = 0 + } + default: + panic("ssh: no key material for msgNewKeys") + } + } + return err +} + +func newTransport(rwc io.ReadWriteCloser, rand io.Reader, isClient bool) *transport { + t := &transport{ + bufReader: bufio.NewReader(rwc), + bufWriter: bufio.NewWriter(rwc), + rand: rand, + reader: connectionState{ + packetCipher: &streamPacketCipher{cipher: noneCipher{}}, + pendingKeyChange: make(chan packetCipher, 1), + }, + writer: connectionState{ + packetCipher: &streamPacketCipher{cipher: noneCipher{}}, + pendingKeyChange: make(chan packetCipher, 1), + }, + Closer: rwc, + } + t.isClient = isClient + + if isClient { + t.reader.dir = serverKeys + t.writer.dir = clientKeys + } else { + t.reader.dir = clientKeys + t.writer.dir = serverKeys + } + + return t +} + +type direction struct { + ivTag []byte + keyTag []byte + macKeyTag []byte +} + +var ( + serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}} + clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}} +) + +// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as +// described in RFC 4253, section 6.4. direction should either be serverKeys +// (to setup server->client keys) or clientKeys (for client->server keys). +func newPacketCipher(d direction, algs DirectionAlgorithms, kex *kexResult) (packetCipher, error) { + cipherMode := cipherModes[algs.Cipher] + if cipherMode == nil { + return nil, fmt.Errorf("ssh: unsupported cipher %v", algs.Cipher) + } + + iv := make([]byte, cipherMode.ivSize) + key := make([]byte, cipherMode.keySize) + + generateKeyMaterial(iv, d.ivTag, kex) + generateKeyMaterial(key, d.keyTag, kex) + + var macKey []byte + if !aeadCiphers[algs.Cipher] { + macMode := macModes[algs.MAC] + macKey = make([]byte, macMode.keySize) + generateKeyMaterial(macKey, d.macKeyTag, kex) + } + + return cipherModes[algs.Cipher].create(key, iv, macKey, algs) +} + +// generateKeyMaterial fills out with key material generated from tag, K, H +// and sessionId, as specified in RFC 4253, section 7.2. +func generateKeyMaterial(out, tag []byte, r *kexResult) { + var digestsSoFar []byte + + h := r.Hash.New() + for len(out) > 0 { + h.Reset() + h.Write(r.K) + h.Write(r.H) + + if len(digestsSoFar) == 0 { + h.Write(tag) + h.Write(r.SessionID) + } else { + h.Write(digestsSoFar) + } + + digest := h.Sum(nil) + n := copy(out, digest) + out = out[n:] + if len(out) > 0 { + digestsSoFar = append(digestsSoFar, digest...) + } + } +} + +const packageVersion = "SSH-2.0-Go" + +// Sends and receives a version line. The versionLine string should +// be US ASCII, start with "SSH-2.0-", and should not include a +// newline. exchangeVersions returns the other side's version line. +func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) { + // Contrary to the RFC, we do not ignore lines that don't + // start with "SSH-2.0-" to make the library usable with + // nonconforming servers. + for _, c := range versionLine { + // The spec disallows non US-ASCII chars, and + // specifically forbids null chars. + if c < 32 { + return nil, errors.New("ssh: junk character in version line") + } + } + if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil { + return + } + + them, err = readVersion(rw) + return them, err +} + +// maxVersionStringBytes is the maximum number of bytes that we'll +// accept as a version string. RFC 4253 section 4.2 limits this at 255 +// chars +const maxVersionStringBytes = 255 + +// Read version string as specified by RFC 4253, section 4.2. +func readVersion(r io.Reader) ([]byte, error) { + versionString := make([]byte, 0, 64) + var ok bool + var buf [1]byte + + for length := 0; length < maxVersionStringBytes; length++ { + _, err := io.ReadFull(r, buf[:]) + if err != nil { + return nil, err + } + // The RFC says that the version should be terminated with \r\n + // but several SSH servers actually only send a \n. + if buf[0] == '\n' { + if !bytes.HasPrefix(versionString, []byte("SSH-")) { + // RFC 4253 says we need to ignore all version string lines + // except the one containing the SSH version (provided that + // all the lines do not exceed 255 bytes in total). + versionString = versionString[:0] + continue + } + ok = true + break + } + + // non ASCII chars are disallowed, but we are lenient, + // since Go doesn't use null-terminated strings. + + // The RFC allows a comment after a space, however, + // all of it (version and comments) goes into the + // session hash. + versionString = append(versionString, buf[0]) + } + + if !ok { + return nil, errors.New("ssh: overflow reading version string") + } + + // There might be a '\r' on the end which we should remove. + if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' { + versionString = versionString[:len(versionString)-1] + } + return versionString, nil +} diff --git a/cli/vendor/golang.org/x/sys/LICENSE b/cli/vendor/golang.org/x/sys/LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cli/vendor/golang.org/x/sys/PATENTS b/cli/vendor/golang.org/x/sys/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/cli/vendor/golang.org/x/sys/cpu/asm_aix_ppc64.s b/cli/vendor/golang.org/x/sys/cpu/asm_aix_ppc64.s new file mode 100644 index 0000000..269e173 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/asm_aix_ppc64.s @@ -0,0 +1,17 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc + +#include "textflag.h" + +// +// System calls for ppc64, AIX are implemented in runtime/syscall_aix.go +// + +TEXT ·syscall6(SB),NOSPLIT,$0-88 + JMP syscall·syscall6(SB) + +TEXT ·rawSyscall6(SB),NOSPLIT,$0-88 + JMP syscall·rawSyscall6(SB) diff --git a/cli/vendor/golang.org/x/sys/cpu/asm_darwin_arm64_gc.s b/cli/vendor/golang.org/x/sys/cpu/asm_darwin_arm64_gc.s new file mode 100644 index 0000000..e07fa75 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/asm_darwin_arm64_gc.s @@ -0,0 +1,12 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin && arm64 && gc + +#include "textflag.h" + +TEXT libc_sysctlbyname_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_sysctlbyname(SB) +GLOBL ·libc_sysctlbyname_trampoline_addr(SB), RODATA, $8 +DATA ·libc_sysctlbyname_trampoline_addr(SB)/8, $libc_sysctlbyname_trampoline<>(SB) diff --git a/cli/vendor/golang.org/x/sys/cpu/asm_darwin_x86_gc.s b/cli/vendor/golang.org/x/sys/cpu/asm_darwin_x86_gc.s new file mode 100644 index 0000000..ec2acfe --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/asm_darwin_x86_gc.s @@ -0,0 +1,17 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin && amd64 && gc + +#include "textflag.h" + +TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_sysctl(SB) +GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8 +DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB) + +TEXT libc_sysctlbyname_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_sysctlbyname(SB) +GLOBL ·libc_sysctlbyname_trampoline_addr(SB), RODATA, $8 +DATA ·libc_sysctlbyname_trampoline_addr(SB)/8, $libc_sysctlbyname_trampoline<>(SB) diff --git a/cli/vendor/golang.org/x/sys/cpu/byteorder.go b/cli/vendor/golang.org/x/sys/cpu/byteorder.go new file mode 100644 index 0000000..271055b --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/byteorder.go @@ -0,0 +1,66 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import ( + "runtime" +) + +// byteOrder is a subset of encoding/binary.ByteOrder. +type byteOrder interface { + Uint32([]byte) uint32 + Uint64([]byte) uint64 +} + +type littleEndian struct{} +type bigEndian struct{} + +func (littleEndian) Uint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +func (littleEndian) Uint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + +func (bigEndian) Uint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} + +func (bigEndian) Uint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +// hostByteOrder returns littleEndian on little-endian machines and +// bigEndian on big-endian machines. +func hostByteOrder() byteOrder { + switch runtime.GOARCH { + case "386", "amd64", "amd64p32", + "alpha", + "arm", "arm64", + "loong64", + "mipsle", "mips64le", "mips64p32le", + "nios2", + "ppc64le", + "riscv", "riscv64", + "sh": + return littleEndian{} + case "armbe", "arm64be", + "m68k", + "mips", "mips64", "mips64p32", + "ppc", "ppc64", + "s390", "s390x", + "shbe", + "sparc", "sparc64": + return bigEndian{} + } + panic("unknown architecture") +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu.go b/cli/vendor/golang.org/x/sys/cpu/cpu.go new file mode 100644 index 0000000..6354199 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu.go @@ -0,0 +1,338 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package cpu implements processor feature detection for +// various CPU architectures. +package cpu + +import ( + "os" + "strings" +) + +// Initialized reports whether the CPU features were initialized. +// +// For some GOOS/GOARCH combinations initialization of the CPU features depends +// on reading an operating specific file, e.g. /proc/self/auxv on linux/arm +// Initialized will report false if reading the file fails. +var Initialized bool + +// CacheLinePad is used to pad structs to avoid false sharing. +type CacheLinePad struct{ _ [cacheLineSize]byte } + +// X86 contains the supported CPU features of the +// current X86/AMD64 platform. If the current platform +// is not X86/AMD64 then all feature flags are false. +// +// X86 is padded to avoid false sharing. Further the HasAVX +// and HasAVX2 are only set if the OS supports XMM and YMM +// registers in addition to the CPUID feature bit being set. +var X86 struct { + _ CacheLinePad + HasAES bool // AES hardware implementation (AES NI) + HasADX bool // Multi-precision add-carry instruction extensions + HasAVX bool // Advanced vector extension + HasAVX2 bool // Advanced vector extension 2 + HasAVX512 bool // Advanced vector extension 512 + HasAVX512F bool // Advanced vector extension 512 Foundation Instructions + HasAVX512CD bool // Advanced vector extension 512 Conflict Detection Instructions + HasAVX512ER bool // Advanced vector extension 512 Exponential and Reciprocal Instructions + HasAVX512PF bool // Advanced vector extension 512 Prefetch Instructions + HasAVX512VL bool // Advanced vector extension 512 Vector Length Extensions + HasAVX512BW bool // Advanced vector extension 512 Byte and Word Instructions + HasAVX512DQ bool // Advanced vector extension 512 Doubleword and Quadword Instructions + HasAVX512IFMA bool // Advanced vector extension 512 Integer Fused Multiply Add + HasAVX512VBMI bool // Advanced vector extension 512 Vector Byte Manipulation Instructions + HasAVX5124VNNIW bool // Advanced vector extension 512 Vector Neural Network Instructions Word variable precision + HasAVX5124FMAPS bool // Advanced vector extension 512 Fused Multiply Accumulation Packed Single precision + HasAVX512VPOPCNTDQ bool // Advanced vector extension 512 Double and quad word population count instructions + HasAVX512VPCLMULQDQ bool // Advanced vector extension 512 Vector carry-less multiply operations + HasAVX512VNNI bool // Advanced vector extension 512 Vector Neural Network Instructions + HasAVX512GFNI bool // Advanced vector extension 512 Galois field New Instructions + HasAVX512VAES bool // Advanced vector extension 512 Vector AES instructions + HasAVX512VBMI2 bool // Advanced vector extension 512 Vector Byte Manipulation Instructions 2 + HasAVX512BITALG bool // Advanced vector extension 512 Bit Algorithms + HasAVX512BF16 bool // Advanced vector extension 512 BFloat16 Instructions + HasAMXTile bool // Advanced Matrix Extension Tile instructions + HasAMXInt8 bool // Advanced Matrix Extension Int8 instructions + HasAMXBF16 bool // Advanced Matrix Extension BFloat16 instructions + HasBMI1 bool // Bit manipulation instruction set 1 + HasBMI2 bool // Bit manipulation instruction set 2 + HasCX16 bool // Compare and exchange 16 Bytes + HasERMS bool // Enhanced REP for MOVSB and STOSB + HasFMA bool // Fused-multiply-add instructions + HasOSXSAVE bool // OS supports XSAVE/XRESTOR for saving/restoring XMM registers. + HasPCLMULQDQ bool // PCLMULQDQ instruction - most often used for AES-GCM + HasPOPCNT bool // Hamming weight instruction POPCNT. + HasRDRAND bool // RDRAND instruction (on-chip random number generator) + HasRDSEED bool // RDSEED instruction (on-chip random number generator) + HasSSE2 bool // Streaming SIMD extension 2 (always available on amd64) + HasSSE3 bool // Streaming SIMD extension 3 + HasSSSE3 bool // Supplemental streaming SIMD extension 3 + HasSSE41 bool // Streaming SIMD extension 4 and 4.1 + HasSSE42 bool // Streaming SIMD extension 4 and 4.2 + HasAVXIFMA bool // Advanced vector extension Integer Fused Multiply Add + HasAVXVNNI bool // Advanced vector extension Vector Neural Network Instructions + HasAVXVNNIInt8 bool // Advanced vector extension Vector Neural Network Int8 instructions + _ CacheLinePad +} + +// ARM64 contains the supported CPU features of the +// current ARMv8(aarch64) platform. If the current platform +// is not arm64 then all feature flags are false. +var ARM64 struct { + _ CacheLinePad + HasFP bool // Floating-point instruction set (always available) + HasASIMD bool // Advanced SIMD (always available) + HasEVTSTRM bool // Event stream support + HasAES bool // AES hardware implementation + HasPMULL bool // Polynomial multiplication instruction set + HasSHA1 bool // SHA1 hardware implementation + HasSHA2 bool // SHA2 hardware implementation + HasCRC32 bool // CRC32 hardware implementation + HasATOMICS bool // Atomic memory operation instruction set + HasFPHP bool // Half precision floating-point instruction set + HasASIMDHP bool // Advanced SIMD half precision instruction set + HasCPUID bool // CPUID identification scheme registers + HasASIMDRDM bool // Rounding double multiply add/subtract instruction set + HasJSCVT bool // Javascript conversion from floating-point to integer + HasFCMA bool // Floating-point multiplication and addition of complex numbers + HasLRCPC bool // Release Consistent processor consistent support + HasDCPOP bool // Persistent memory support + HasSHA3 bool // SHA3 hardware implementation + HasSM3 bool // SM3 hardware implementation + HasSM4 bool // SM4 hardware implementation + HasASIMDDP bool // Advanced SIMD double precision instruction set + HasSHA512 bool // SHA512 hardware implementation + HasSVE bool // Scalable Vector Extensions + HasSVE2 bool // Scalable Vector Extensions 2 + HasASIMDFHM bool // Advanced SIMD multiplication FP16 to FP32 + HasDIT bool // Data Independent Timing support + HasI8MM bool // Advanced SIMD Int8 matrix multiplication instructions + _ CacheLinePad +} + +// ARM contains the supported CPU features of the current ARM (32-bit) platform. +// All feature flags are false if: +// 1. the current platform is not arm, or +// 2. the current operating system is not Linux. +var ARM struct { + _ CacheLinePad + HasSWP bool // SWP instruction support + HasHALF bool // Half-word load and store support + HasTHUMB bool // ARM Thumb instruction set + Has26BIT bool // Address space limited to 26-bits + HasFASTMUL bool // 32-bit operand, 64-bit result multiplication support + HasFPA bool // Floating point arithmetic support + HasVFP bool // Vector floating point support + HasEDSP bool // DSP Extensions support + HasJAVA bool // Java instruction set + HasIWMMXT bool // Intel Wireless MMX technology support + HasCRUNCH bool // MaverickCrunch context switching and handling + HasTHUMBEE bool // Thumb EE instruction set + HasNEON bool // NEON instruction set + HasVFPv3 bool // Vector floating point version 3 support + HasVFPv3D16 bool // Vector floating point version 3 D8-D15 + HasTLS bool // Thread local storage support + HasVFPv4 bool // Vector floating point version 4 support + HasIDIVA bool // Integer divide instruction support in ARM mode + HasIDIVT bool // Integer divide instruction support in Thumb mode + HasVFPD32 bool // Vector floating point version 3 D15-D31 + HasLPAE bool // Large Physical Address Extensions + HasEVTSTRM bool // Event stream support + HasAES bool // AES hardware implementation + HasPMULL bool // Polynomial multiplication instruction set + HasSHA1 bool // SHA1 hardware implementation + HasSHA2 bool // SHA2 hardware implementation + HasCRC32 bool // CRC32 hardware implementation + _ CacheLinePad +} + +// The booleans in Loong64 contain the correspondingly named cpu feature bit. +// The struct is padded to avoid false sharing. +var Loong64 struct { + _ CacheLinePad + HasLSX bool // support 128-bit vector extension + HasLASX bool // support 256-bit vector extension + HasCRC32 bool // support CRC instruction + HasLAM_BH bool // support AM{SWAP/ADD}[_DB].{B/H} instruction + HasLAMCAS bool // support AMCAS[_DB].{B/H/W/D} instruction + _ CacheLinePad +} + +// MIPS64X contains the supported CPU features of the current mips64/mips64le +// platforms. If the current platform is not mips64/mips64le or the current +// operating system is not Linux then all feature flags are false. +var MIPS64X struct { + _ CacheLinePad + HasMSA bool // MIPS SIMD architecture + _ CacheLinePad +} + +// PPC64 contains the supported CPU features of the current ppc64/ppc64le platforms. +// If the current platform is not ppc64/ppc64le then all feature flags are false. +// +// For ppc64/ppc64le, it is safe to check only for ISA level starting on ISA v3.00, +// since there are no optional categories. There are some exceptions that also +// require kernel support to work (DARN, SCV), so there are feature bits for +// those as well. The struct is padded to avoid false sharing. +var PPC64 struct { + _ CacheLinePad + HasDARN bool // Hardware random number generator (requires kernel enablement) + HasSCV bool // Syscall vectored (requires kernel enablement) + IsPOWER8 bool // ISA v2.07 (POWER8) + IsPOWER9 bool // ISA v3.00 (POWER9), implies IsPOWER8 + _ CacheLinePad +} + +// S390X contains the supported CPU features of the current IBM Z +// (s390x) platform. If the current platform is not IBM Z then all +// feature flags are false. +// +// S390X is padded to avoid false sharing. Further HasVX is only set +// if the OS supports vector registers in addition to the STFLE +// feature bit being set. +var S390X struct { + _ CacheLinePad + HasZARCH bool // z/Architecture mode is active [mandatory] + HasSTFLE bool // store facility list extended + HasLDISP bool // long (20-bit) displacements + HasEIMM bool // 32-bit immediates + HasDFP bool // decimal floating point + HasETF3EH bool // ETF-3 enhanced + HasMSA bool // message security assist (CPACF) + HasAES bool // KM-AES{128,192,256} functions + HasAESCBC bool // KMC-AES{128,192,256} functions + HasAESCTR bool // KMCTR-AES{128,192,256} functions + HasAESGCM bool // KMA-GCM-AES{128,192,256} functions + HasGHASH bool // KIMD-GHASH function + HasSHA1 bool // K{I,L}MD-SHA-1 functions + HasSHA256 bool // K{I,L}MD-SHA-256 functions + HasSHA512 bool // K{I,L}MD-SHA-512 functions + HasSHA3 bool // K{I,L}MD-SHA3-{224,256,384,512} and K{I,L}MD-SHAKE-{128,256} functions + HasVX bool // vector facility + HasVXE bool // vector-enhancements facility 1 + _ CacheLinePad +} + +// RISCV64 contains the supported CPU features and performance characteristics for riscv64 +// platforms. The booleans in RISCV64, with the exception of HasFastMisaligned, indicate +// the presence of RISC-V extensions. +// +// It is safe to assume that all the RV64G extensions are supported and so they are omitted from +// this structure. As riscv64 Go programs require at least RV64G, the code that populates +// this structure cannot run successfully if some of the RV64G extensions are missing. +// The struct is padded to avoid false sharing. +var RISCV64 struct { + _ CacheLinePad + HasFastMisaligned bool // Fast misaligned accesses + HasC bool // Compressed instruction-set extension + HasV bool // Vector extension compatible with RVV 1.0 + HasZba bool // Address generation instructions extension + HasZbb bool // Basic bit-manipulation extension + HasZbs bool // Single-bit instructions extension + HasZvbb bool // Vector Basic Bit-manipulation + HasZvbc bool // Vector Carryless Multiplication + HasZvkb bool // Vector Cryptography Bit-manipulation + HasZvkt bool // Vector Data-Independent Execution Latency + HasZvkg bool // Vector GCM/GMAC + HasZvkn bool // NIST Algorithm Suite (AES/SHA256/SHA512) + HasZvknc bool // NIST Algorithm Suite with carryless multiply + HasZvkng bool // NIST Algorithm Suite with GCM + HasZvks bool // ShangMi Algorithm Suite + HasZvksc bool // ShangMi Algorithm Suite with carryless multiplication + HasZvksg bool // ShangMi Algorithm Suite with GCM + _ CacheLinePad +} + +func init() { + archInit() + initOptions() + processOptions() +} + +// options contains the cpu debug options that can be used in GODEBUG. +// Options are arch dependent and are added by the arch specific initOptions functions. +// Features that are mandatory for the specific GOARCH should have the Required field set +// (e.g. SSE2 on amd64). +var options []option + +// Option names should be lower case. e.g. avx instead of AVX. +type option struct { + Name string + Feature *bool + Specified bool // whether feature value was specified in GODEBUG + Enable bool // whether feature should be enabled + Required bool // whether feature is mandatory and can not be disabled +} + +func processOptions() { + env := os.Getenv("GODEBUG") +field: + for env != "" { + field := "" + i := strings.IndexByte(env, ',') + if i < 0 { + field, env = env, "" + } else { + field, env = env[:i], env[i+1:] + } + if len(field) < 4 || field[:4] != "cpu." { + continue + } + i = strings.IndexByte(field, '=') + if i < 0 { + print("GODEBUG sys/cpu: no value specified for \"", field, "\"\n") + continue + } + key, value := field[4:i], field[i+1:] // e.g. "SSE2", "on" + + var enable bool + switch value { + case "on": + enable = true + case "off": + enable = false + default: + print("GODEBUG sys/cpu: value \"", value, "\" not supported for cpu option \"", key, "\"\n") + continue field + } + + if key == "all" { + for i := range options { + options[i].Specified = true + options[i].Enable = enable || options[i].Required + } + continue field + } + + for i := range options { + if options[i].Name == key { + options[i].Specified = true + options[i].Enable = enable + continue field + } + } + + print("GODEBUG sys/cpu: unknown cpu feature \"", key, "\"\n") + } + + for _, o := range options { + if !o.Specified { + continue + } + + if o.Enable && !*o.Feature { + print("GODEBUG sys/cpu: can not enable \"", o.Name, "\", missing CPU support\n") + continue + } + + if !o.Enable && o.Required { + print("GODEBUG sys/cpu: can not disable \"", o.Name, "\", required CPU feature\n") + continue + } + + *o.Feature = o.Enable + } +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_aix.go b/cli/vendor/golang.org/x/sys/cpu/cpu_aix.go new file mode 100644 index 0000000..9bf0c32 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_aix.go @@ -0,0 +1,33 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix + +package cpu + +const ( + // getsystemcfg constants + _SC_IMPL = 2 + _IMPL_POWER8 = 0x10000 + _IMPL_POWER9 = 0x20000 +) + +func archInit() { + impl := getsystemcfg(_SC_IMPL) + if impl&_IMPL_POWER8 != 0 { + PPC64.IsPOWER8 = true + } + if impl&_IMPL_POWER9 != 0 { + PPC64.IsPOWER8 = true + PPC64.IsPOWER9 = true + } + + Initialized = true +} + +func getsystemcfg(label int) (n uint64) { + r0, _ := callgetsystemcfg(label) + n = uint64(r0) + return +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_arm.go b/cli/vendor/golang.org/x/sys/cpu/cpu_arm.go new file mode 100644 index 0000000..301b752 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_arm.go @@ -0,0 +1,73 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +const cacheLineSize = 32 + +// HWCAP/HWCAP2 bits. +// These are specific to Linux. +const ( + hwcap_SWP = 1 << 0 + hwcap_HALF = 1 << 1 + hwcap_THUMB = 1 << 2 + hwcap_26BIT = 1 << 3 + hwcap_FAST_MULT = 1 << 4 + hwcap_FPA = 1 << 5 + hwcap_VFP = 1 << 6 + hwcap_EDSP = 1 << 7 + hwcap_JAVA = 1 << 8 + hwcap_IWMMXT = 1 << 9 + hwcap_CRUNCH = 1 << 10 + hwcap_THUMBEE = 1 << 11 + hwcap_NEON = 1 << 12 + hwcap_VFPv3 = 1 << 13 + hwcap_VFPv3D16 = 1 << 14 + hwcap_TLS = 1 << 15 + hwcap_VFPv4 = 1 << 16 + hwcap_IDIVA = 1 << 17 + hwcap_IDIVT = 1 << 18 + hwcap_VFPD32 = 1 << 19 + hwcap_LPAE = 1 << 20 + hwcap_EVTSTRM = 1 << 21 + + hwcap2_AES = 1 << 0 + hwcap2_PMULL = 1 << 1 + hwcap2_SHA1 = 1 << 2 + hwcap2_SHA2 = 1 << 3 + hwcap2_CRC32 = 1 << 4 +) + +func initOptions() { + options = []option{ + {Name: "pmull", Feature: &ARM.HasPMULL}, + {Name: "sha1", Feature: &ARM.HasSHA1}, + {Name: "sha2", Feature: &ARM.HasSHA2}, + {Name: "swp", Feature: &ARM.HasSWP}, + {Name: "thumb", Feature: &ARM.HasTHUMB}, + {Name: "thumbee", Feature: &ARM.HasTHUMBEE}, + {Name: "tls", Feature: &ARM.HasTLS}, + {Name: "vfp", Feature: &ARM.HasVFP}, + {Name: "vfpd32", Feature: &ARM.HasVFPD32}, + {Name: "vfpv3", Feature: &ARM.HasVFPv3}, + {Name: "vfpv3d16", Feature: &ARM.HasVFPv3D16}, + {Name: "vfpv4", Feature: &ARM.HasVFPv4}, + {Name: "half", Feature: &ARM.HasHALF}, + {Name: "26bit", Feature: &ARM.Has26BIT}, + {Name: "fastmul", Feature: &ARM.HasFASTMUL}, + {Name: "fpa", Feature: &ARM.HasFPA}, + {Name: "edsp", Feature: &ARM.HasEDSP}, + {Name: "java", Feature: &ARM.HasJAVA}, + {Name: "iwmmxt", Feature: &ARM.HasIWMMXT}, + {Name: "crunch", Feature: &ARM.HasCRUNCH}, + {Name: "neon", Feature: &ARM.HasNEON}, + {Name: "idivt", Feature: &ARM.HasIDIVT}, + {Name: "idiva", Feature: &ARM.HasIDIVA}, + {Name: "lpae", Feature: &ARM.HasLPAE}, + {Name: "evtstrm", Feature: &ARM.HasEVTSTRM}, + {Name: "aes", Feature: &ARM.HasAES}, + {Name: "crc32", Feature: &ARM.HasCRC32}, + } + +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_arm64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_arm64.go new file mode 100644 index 0000000..5fc09e2 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_arm64.go @@ -0,0 +1,191 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import "runtime" + +// cacheLineSize is used to prevent false sharing of cache lines. +// We choose 128 because Apple Silicon, a.k.a. M1, has 128-byte cache line size. +// It doesn't cost much and is much more future-proof. +const cacheLineSize = 128 + +func initOptions() { + options = []option{ + {Name: "fp", Feature: &ARM64.HasFP}, + {Name: "asimd", Feature: &ARM64.HasASIMD}, + {Name: "evstrm", Feature: &ARM64.HasEVTSTRM}, + {Name: "aes", Feature: &ARM64.HasAES}, + {Name: "fphp", Feature: &ARM64.HasFPHP}, + {Name: "jscvt", Feature: &ARM64.HasJSCVT}, + {Name: "lrcpc", Feature: &ARM64.HasLRCPC}, + {Name: "pmull", Feature: &ARM64.HasPMULL}, + {Name: "sha1", Feature: &ARM64.HasSHA1}, + {Name: "sha2", Feature: &ARM64.HasSHA2}, + {Name: "sha3", Feature: &ARM64.HasSHA3}, + {Name: "sha512", Feature: &ARM64.HasSHA512}, + {Name: "sm3", Feature: &ARM64.HasSM3}, + {Name: "sm4", Feature: &ARM64.HasSM4}, + {Name: "sve", Feature: &ARM64.HasSVE}, + {Name: "sve2", Feature: &ARM64.HasSVE2}, + {Name: "crc32", Feature: &ARM64.HasCRC32}, + {Name: "atomics", Feature: &ARM64.HasATOMICS}, + {Name: "asimdhp", Feature: &ARM64.HasASIMDHP}, + {Name: "cpuid", Feature: &ARM64.HasCPUID}, + {Name: "asimrdm", Feature: &ARM64.HasASIMDRDM}, + {Name: "fcma", Feature: &ARM64.HasFCMA}, + {Name: "dcpop", Feature: &ARM64.HasDCPOP}, + {Name: "asimddp", Feature: &ARM64.HasASIMDDP}, + {Name: "asimdfhm", Feature: &ARM64.HasASIMDFHM}, + {Name: "dit", Feature: &ARM64.HasDIT}, + {Name: "i8mm", Feature: &ARM64.HasI8MM}, + } +} + +func archInit() { + if runtime.GOOS == "freebsd" { + readARM64Registers() + } else { + // Most platforms don't seem to allow directly reading these registers. + doinit() + } +} + +// setMinimalFeatures fakes the minimal ARM64 features expected by +// TestARM64minimalFeatures. +func setMinimalFeatures() { + ARM64.HasASIMD = true + ARM64.HasFP = true +} + +func readARM64Registers() { + Initialized = true + + parseARM64SystemRegisters(getisar0(), getisar1(), getpfr0()) +} + +func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) { + // ID_AA64ISAR0_EL1 + switch extractBits(isar0, 4, 7) { + case 1: + ARM64.HasAES = true + case 2: + ARM64.HasAES = true + ARM64.HasPMULL = true + } + + switch extractBits(isar0, 8, 11) { + case 1: + ARM64.HasSHA1 = true + } + + switch extractBits(isar0, 12, 15) { + case 1: + ARM64.HasSHA2 = true + case 2: + ARM64.HasSHA2 = true + ARM64.HasSHA512 = true + } + + switch extractBits(isar0, 16, 19) { + case 1: + ARM64.HasCRC32 = true + } + + switch extractBits(isar0, 20, 23) { + case 2: + ARM64.HasATOMICS = true + } + + switch extractBits(isar0, 28, 31) { + case 1: + ARM64.HasASIMDRDM = true + } + + switch extractBits(isar0, 32, 35) { + case 1: + ARM64.HasSHA3 = true + } + + switch extractBits(isar0, 36, 39) { + case 1: + ARM64.HasSM3 = true + } + + switch extractBits(isar0, 40, 43) { + case 1: + ARM64.HasSM4 = true + } + + switch extractBits(isar0, 44, 47) { + case 1: + ARM64.HasASIMDDP = true + } + + // ID_AA64ISAR1_EL1 + switch extractBits(isar1, 0, 3) { + case 1: + ARM64.HasDCPOP = true + } + + switch extractBits(isar1, 12, 15) { + case 1: + ARM64.HasJSCVT = true + } + + switch extractBits(isar1, 16, 19) { + case 1: + ARM64.HasFCMA = true + } + + switch extractBits(isar1, 20, 23) { + case 1: + ARM64.HasLRCPC = true + } + + switch extractBits(isar1, 52, 55) { + case 1: + ARM64.HasI8MM = true + } + + // ID_AA64PFR0_EL1 + switch extractBits(pfr0, 16, 19) { + case 0: + ARM64.HasFP = true + case 1: + ARM64.HasFP = true + ARM64.HasFPHP = true + } + + switch extractBits(pfr0, 20, 23) { + case 0: + ARM64.HasASIMD = true + case 1: + ARM64.HasASIMD = true + ARM64.HasASIMDHP = true + } + + switch extractBits(pfr0, 32, 35) { + case 1: + ARM64.HasSVE = true + + parseARM64SVERegister(getzfr0()) + } + + switch extractBits(pfr0, 48, 51) { + case 1: + ARM64.HasDIT = true + } +} + +func parseARM64SVERegister(zfr0 uint64) { + switch extractBits(zfr0, 0, 3) { + case 1: + ARM64.HasSVE2 = true + } +} + +func extractBits(data uint64, start, end uint) uint { + return (uint)(data>>start) & ((1 << (end - start + 1)) - 1) +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_arm64.s b/cli/vendor/golang.org/x/sys/cpu/cpu_arm64.s new file mode 100644 index 0000000..3b0450a --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_arm64.s @@ -0,0 +1,35 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc + +#include "textflag.h" + +// func getisar0() uint64 +TEXT ·getisar0(SB),NOSPLIT,$0-8 + // get Instruction Set Attributes 0 into x0 + MRS ID_AA64ISAR0_EL1, R0 + MOVD R0, ret+0(FP) + RET + +// func getisar1() uint64 +TEXT ·getisar1(SB),NOSPLIT,$0-8 + // get Instruction Set Attributes 1 into x0 + MRS ID_AA64ISAR1_EL1, R0 + MOVD R0, ret+0(FP) + RET + +// func getpfr0() uint64 +TEXT ·getpfr0(SB),NOSPLIT,$0-8 + // get Processor Feature Register 0 into x0 + MRS ID_AA64PFR0_EL1, R0 + MOVD R0, ret+0(FP) + RET + +// func getzfr0() uint64 +TEXT ·getzfr0(SB),NOSPLIT,$0-8 + // get SVE Feature Register 0 into x0 + MRS ID_AA64ZFR0_EL1, R0 + MOVD R0, ret+0(FP) + RET diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64.go new file mode 100644 index 0000000..0b47074 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64.go @@ -0,0 +1,67 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin && arm64 && gc + +package cpu + +func doinit() { + setMinimalFeatures() + + // The feature flags are explained in [Instruction Set Detection]. + // There are some differences between MacOS versions: + // + // MacOS 11 and 12 do not have "hw.optional" sysctl values for some of the features. + // + // MacOS 13 changed some of the naming conventions to align with ARM Architecture Reference Manual. + // For example "hw.optional.armv8_2_sha512" became "hw.optional.arm.FEAT_SHA512". + // It currently checks both to stay compatible with MacOS 11 and 12. + // The old names also work with MacOS 13, however it's not clear whether + // they will continue working with future OS releases. + // + // Once MacOS 12 is no longer supported the old names can be removed. + // + // [Instruction Set Detection]: https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics + + // Encryption, hashing and checksum capabilities + + // For the following flags there are no MacOS 11 sysctl flags. + ARM64.HasAES = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_AES\x00")) + ARM64.HasPMULL = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_PMULL\x00")) + ARM64.HasSHA1 = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA1\x00")) + ARM64.HasSHA2 = true || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA256\x00")) + + ARM64.HasSHA3 = darwinSysctlEnabled([]byte("hw.optional.armv8_2_sha3\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA3\x00")) + ARM64.HasSHA512 = darwinSysctlEnabled([]byte("hw.optional.armv8_2_sha512\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SHA512\x00")) + + ARM64.HasCRC32 = darwinSysctlEnabled([]byte("hw.optional.armv8_crc32\x00")) + + // Atomic and memory ordering + ARM64.HasATOMICS = darwinSysctlEnabled([]byte("hw.optional.armv8_1_atomics\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_LSE\x00")) + ARM64.HasLRCPC = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_LRCPC\x00")) + + // SIMD and floating point capabilities + ARM64.HasFPHP = darwinSysctlEnabled([]byte("hw.optional.neon_fp16\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_FP16\x00")) + ARM64.HasASIMDHP = darwinSysctlEnabled([]byte("hw.optional.neon_hpfp\x00")) || darwinSysctlEnabled([]byte("hw.optional.AdvSIMD_HPFPCvt\x00")) + ARM64.HasASIMDRDM = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_RDM\x00")) + ARM64.HasASIMDDP = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_DotProd\x00")) + ARM64.HasASIMDFHM = darwinSysctlEnabled([]byte("hw.optional.armv8_2_fhm\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_FHM\x00")) + ARM64.HasI8MM = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_I8MM\x00")) + + ARM64.HasJSCVT = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_JSCVT\x00")) + ARM64.HasFCMA = darwinSysctlEnabled([]byte("hw.optional.armv8_3_compnum\x00")) || darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_FCMA\x00")) + + // Miscellaneous + ARM64.HasDCPOP = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_DPB\x00")) + ARM64.HasEVTSTRM = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_ECV\x00")) + ARM64.HasDIT = darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_DIT\x00")) + + // Not supported, but added for completeness + ARM64.HasCPUID = false + + ARM64.HasSM3 = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SM3\x00")) + ARM64.HasSM4 = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SM4\x00")) + ARM64.HasSVE = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SVE\x00")) + ARM64.HasSVE2 = false // darwinSysctlEnabled([]byte("hw.optional.arm.FEAT_SVE2\x00")) +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go b/cli/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go new file mode 100644 index 0000000..37ecc66 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go @@ -0,0 +1,31 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin && arm64 && !gc + +package cpu + +import "runtime" + +func doinit() { + setMinimalFeatures() + + ARM64.HasASIMD = true + ARM64.HasFP = true + + // Go already assumes these to be available because they were on the M1 + // and these are supported on all Apple arm64 chips. + ARM64.HasAES = true + ARM64.HasPMULL = true + ARM64.HasSHA1 = true + ARM64.HasSHA2 = true + + if runtime.GOOS != "ios" { + // Apple A7 processors do not support these, however + // M-series SoCs are at least armv8.4-a + ARM64.HasCRC32 = true // armv8.1 + ARM64.HasATOMICS = true // armv8.2 + ARM64.HasJSCVT = true // armv8.3, if HasFP + } +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_darwin_x86.go b/cli/vendor/golang.org/x/sys/cpu/cpu_darwin_x86.go new file mode 100644 index 0000000..b838cb9 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_darwin_x86.go @@ -0,0 +1,61 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin && amd64 && gc + +package cpu + +// darwinSupportsAVX512 checks Darwin kernel for AVX512 support via sysctl +// call (see issue 43089). It also restricts AVX512 support for Darwin to +// kernel version 21.3.0 (MacOS 12.2.0) or later (see issue 49233). +// +// Background: +// Darwin implements a special mechanism to economize on thread state when +// AVX512 specific registers are not in use. This scheme minimizes state when +// preempting threads that haven't yet used any AVX512 instructions, but adds +// special requirements to check for AVX512 hardware support at runtime (e.g. +// via sysctl call or commpage inspection). See issue 43089 and link below for +// full background: +// https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.1.10/osfmk/i386/fpu.c#L214-L240 +// +// Additionally, all versions of the Darwin kernel from 19.6.0 through 21.2.0 +// (corresponding to MacOS 10.15.6 - 12.1) have a bug that can cause corruption +// of the AVX512 mask registers (K0-K7) upon signal return. For this reason +// AVX512 is considered unsafe to use on Darwin for kernel versions prior to +// 21.3.0, where a fix has been confirmed. See issue 49233 for full background. +func darwinSupportsAVX512() bool { + return darwinSysctlEnabled([]byte("hw.optional.avx512f\x00")) && darwinKernelVersionCheck(21, 3, 0) +} + +// Ensure Darwin kernel version is at least major.minor.patch, avoiding dependencies +func darwinKernelVersionCheck(major, minor, patch int) bool { + var release [256]byte + err := darwinOSRelease(&release) + if err != nil { + return false + } + + var mmp [3]int + c := 0 +Loop: + for _, b := range release[:] { + switch { + case b >= '0' && b <= '9': + mmp[c] = 10*mmp[c] + int(b-'0') + case b == '.': + c++ + if c > 2 { + return false + } + case b == 0: + break Loop + default: + return false + } + } + if c != 2 { + return false + } + return mmp[0] > major || mmp[0] == major && (mmp[1] > minor || mmp[1] == minor && mmp[2] >= patch) +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go new file mode 100644 index 0000000..6ac6e1e --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go @@ -0,0 +1,12 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc + +package cpu + +func getisar0() uint64 +func getisar1() uint64 +func getpfr0() uint64 +func getzfr0() uint64 diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_gc_s390x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_gc_s390x.go new file mode 100644 index 0000000..c8ae6dd --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_gc_s390x.go @@ -0,0 +1,21 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc + +package cpu + +// haveAsmFunctions reports whether the other functions in this file can +// be safely called. +func haveAsmFunctions() bool { return true } + +// The following feature detection functions are defined in cpu_s390x.s. +// They are likely to be expensive to call so the results should be cached. +func stfle() facilityList +func kmQuery() queryResult +func kmcQuery() queryResult +func kmctrQuery() queryResult +func kmaQuery() queryResult +func kimdQuery() queryResult +func klmdQuery() queryResult diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_gc_x86.go b/cli/vendor/golang.org/x/sys/cpu/cpu_gc_x86.go new file mode 100644 index 0000000..32a4451 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_gc_x86.go @@ -0,0 +1,15 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (386 || amd64 || amd64p32) && gc + +package cpu + +// cpuid is implemented in cpu_gc_x86.s for gc compiler +// and in cpu_gccgo.c for gccgo. +func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) + +// xgetbv with ecx = 0 is implemented in cpu_gc_x86.s for gc compiler +// and in cpu_gccgo.c for gccgo. +func xgetbv() (eax, edx uint32) diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_gc_x86.s b/cli/vendor/golang.org/x/sys/cpu/cpu_gc_x86.s new file mode 100644 index 0000000..ce208ce --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_gc_x86.s @@ -0,0 +1,26 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (386 || amd64 || amd64p32) && gc + +#include "textflag.h" + +// func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) +TEXT ·cpuid(SB), NOSPLIT, $0-24 + MOVL eaxArg+0(FP), AX + MOVL ecxArg+4(FP), CX + CPUID + MOVL AX, eax+8(FP) + MOVL BX, ebx+12(FP) + MOVL CX, ecx+16(FP) + MOVL DX, edx+20(FP) + RET + +// func xgetbv() (eax, edx uint32) +TEXT ·xgetbv(SB), NOSPLIT, $0-8 + MOVL $0, CX + XGETBV + MOVL AX, eax+0(FP) + MOVL DX, edx+4(FP) + RET diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go new file mode 100644 index 0000000..0591308 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go @@ -0,0 +1,12 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gccgo + +package cpu + +func getisar0() uint64 { return 0 } +func getisar1() uint64 { return 0 } +func getpfr0() uint64 { return 0 } +func getzfr0() uint64 { return 0 } diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_s390x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_s390x.go new file mode 100644 index 0000000..9526d2c --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_s390x.go @@ -0,0 +1,22 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gccgo + +package cpu + +// haveAsmFunctions reports whether the other functions in this file can +// be safely called. +func haveAsmFunctions() bool { return false } + +// TODO(mundaym): the following feature detection functions are currently +// stubs. See https://golang.org/cl/162887 for how to fix this. +// They are likely to be expensive to call so the results should be cached. +func stfle() facilityList { panic("not implemented for gccgo") } +func kmQuery() queryResult { panic("not implemented for gccgo") } +func kmcQuery() queryResult { panic("not implemented for gccgo") } +func kmctrQuery() queryResult { panic("not implemented for gccgo") } +func kmaQuery() queryResult { panic("not implemented for gccgo") } +func kimdQuery() queryResult { panic("not implemented for gccgo") } +func klmdQuery() queryResult { panic("not implemented for gccgo") } diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.c b/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.c new file mode 100644 index 0000000..3f73a05 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.c @@ -0,0 +1,37 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (386 || amd64 || amd64p32) && gccgo + +#include +#include +#include + +// Need to wrap __get_cpuid_count because it's declared as static. +int +gccgoGetCpuidCount(uint32_t leaf, uint32_t subleaf, + uint32_t *eax, uint32_t *ebx, + uint32_t *ecx, uint32_t *edx) +{ + return __get_cpuid_count(leaf, subleaf, eax, ebx, ecx, edx); +} + +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC push_options +#pragma GCC target("xsave") +#pragma clang attribute push (__attribute__((target("xsave"))), apply_to=function) + +// xgetbv reads the contents of an XCR (Extended Control Register) +// specified in the ECX register into registers EDX:EAX. +// Currently, the only supported value for XCR is 0. +void +gccgoXgetbv(uint32_t *eax, uint32_t *edx) +{ + uint64_t v = _xgetbv(0); + *eax = v & 0xffffffff; + *edx = v >> 32; +} + +#pragma clang attribute pop +#pragma GCC pop_options diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.go b/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.go new file mode 100644 index 0000000..170d21d --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.go @@ -0,0 +1,25 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (386 || amd64 || amd64p32) && gccgo + +package cpu + +//extern gccgoGetCpuidCount +func gccgoGetCpuidCount(eaxArg, ecxArg uint32, eax, ebx, ecx, edx *uint32) + +func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) { + var a, b, c, d uint32 + gccgoGetCpuidCount(eaxArg, ecxArg, &a, &b, &c, &d) + return a, b, c, d +} + +//extern gccgoXgetbv +func gccgoXgetbv(eax, edx *uint32) + +func xgetbv() (eax, edx uint32) { + var a, d uint32 + gccgoXgetbv(&a, &d) + return a, d +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_linux.go b/cli/vendor/golang.org/x/sys/cpu/cpu_linux.go new file mode 100644 index 0000000..743eb54 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_linux.go @@ -0,0 +1,15 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !386 && !amd64 && !amd64p32 && !arm64 + +package cpu + +func archInit() { + if err := readHWCAP(); err != nil { + return + } + doinit() + Initialized = true +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_linux_arm.go b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_arm.go new file mode 100644 index 0000000..2057006 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_arm.go @@ -0,0 +1,39 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +func doinit() { + ARM.HasSWP = isSet(hwCap, hwcap_SWP) + ARM.HasHALF = isSet(hwCap, hwcap_HALF) + ARM.HasTHUMB = isSet(hwCap, hwcap_THUMB) + ARM.Has26BIT = isSet(hwCap, hwcap_26BIT) + ARM.HasFASTMUL = isSet(hwCap, hwcap_FAST_MULT) + ARM.HasFPA = isSet(hwCap, hwcap_FPA) + ARM.HasVFP = isSet(hwCap, hwcap_VFP) + ARM.HasEDSP = isSet(hwCap, hwcap_EDSP) + ARM.HasJAVA = isSet(hwCap, hwcap_JAVA) + ARM.HasIWMMXT = isSet(hwCap, hwcap_IWMMXT) + ARM.HasCRUNCH = isSet(hwCap, hwcap_CRUNCH) + ARM.HasTHUMBEE = isSet(hwCap, hwcap_THUMBEE) + ARM.HasNEON = isSet(hwCap, hwcap_NEON) + ARM.HasVFPv3 = isSet(hwCap, hwcap_VFPv3) + ARM.HasVFPv3D16 = isSet(hwCap, hwcap_VFPv3D16) + ARM.HasTLS = isSet(hwCap, hwcap_TLS) + ARM.HasVFPv4 = isSet(hwCap, hwcap_VFPv4) + ARM.HasIDIVA = isSet(hwCap, hwcap_IDIVA) + ARM.HasIDIVT = isSet(hwCap, hwcap_IDIVT) + ARM.HasVFPD32 = isSet(hwCap, hwcap_VFPD32) + ARM.HasLPAE = isSet(hwCap, hwcap_LPAE) + ARM.HasEVTSTRM = isSet(hwCap, hwcap_EVTSTRM) + ARM.HasAES = isSet(hwCap2, hwcap2_AES) + ARM.HasPMULL = isSet(hwCap2, hwcap2_PMULL) + ARM.HasSHA1 = isSet(hwCap2, hwcap2_SHA1) + ARM.HasSHA2 = isSet(hwCap2, hwcap2_SHA2) + ARM.HasCRC32 = isSet(hwCap2, hwcap2_CRC32) +} + +func isSet(hwc uint, value uint) bool { + return hwc&value != 0 +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_linux_arm64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_arm64.go new file mode 100644 index 0000000..f1caf0f --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_arm64.go @@ -0,0 +1,120 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import ( + "strings" + "syscall" +) + +// HWCAP/HWCAP2 bits. These are exposed by Linux. +const ( + hwcap_FP = 1 << 0 + hwcap_ASIMD = 1 << 1 + hwcap_EVTSTRM = 1 << 2 + hwcap_AES = 1 << 3 + hwcap_PMULL = 1 << 4 + hwcap_SHA1 = 1 << 5 + hwcap_SHA2 = 1 << 6 + hwcap_CRC32 = 1 << 7 + hwcap_ATOMICS = 1 << 8 + hwcap_FPHP = 1 << 9 + hwcap_ASIMDHP = 1 << 10 + hwcap_CPUID = 1 << 11 + hwcap_ASIMDRDM = 1 << 12 + hwcap_JSCVT = 1 << 13 + hwcap_FCMA = 1 << 14 + hwcap_LRCPC = 1 << 15 + hwcap_DCPOP = 1 << 16 + hwcap_SHA3 = 1 << 17 + hwcap_SM3 = 1 << 18 + hwcap_SM4 = 1 << 19 + hwcap_ASIMDDP = 1 << 20 + hwcap_SHA512 = 1 << 21 + hwcap_SVE = 1 << 22 + hwcap_ASIMDFHM = 1 << 23 + hwcap_DIT = 1 << 24 + + hwcap2_SVE2 = 1 << 1 + hwcap2_I8MM = 1 << 13 +) + +// linuxKernelCanEmulateCPUID reports whether we're running +// on Linux 4.11+. Ideally we'd like to ask the question about +// whether the current kernel contains +// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=77c97b4ee21290f5f083173d957843b615abbff2 +// but the version number will have to do. +func linuxKernelCanEmulateCPUID() bool { + var un syscall.Utsname + syscall.Uname(&un) + var sb strings.Builder + for _, b := range un.Release[:] { + if b == 0 { + break + } + sb.WriteByte(byte(b)) + } + major, minor, _, ok := parseRelease(sb.String()) + return ok && (major > 4 || major == 4 && minor >= 11) +} + +func doinit() { + if err := readHWCAP(); err != nil { + // We failed to read /proc/self/auxv. This can happen if the binary has + // been given extra capabilities(7) with /bin/setcap. + // + // When this happens, we have two options. If the Linux kernel is new + // enough (4.11+), we can read the arm64 registers directly which'll + // trap into the kernel and then return back to userspace. + // + // But on older kernels, such as Linux 4.4.180 as used on many Synology + // devices, calling readARM64Registers (specifically getisar0) will + // cause a SIGILL and we'll die. So for older kernels, parse /proc/cpuinfo + // instead. + // + // See golang/go#57336. + if linuxKernelCanEmulateCPUID() { + readARM64Registers() + } else { + readLinuxProcCPUInfo() + } + return + } + + // HWCAP feature bits + ARM64.HasFP = isSet(hwCap, hwcap_FP) + ARM64.HasASIMD = isSet(hwCap, hwcap_ASIMD) + ARM64.HasEVTSTRM = isSet(hwCap, hwcap_EVTSTRM) + ARM64.HasAES = isSet(hwCap, hwcap_AES) + ARM64.HasPMULL = isSet(hwCap, hwcap_PMULL) + ARM64.HasSHA1 = isSet(hwCap, hwcap_SHA1) + ARM64.HasSHA2 = isSet(hwCap, hwcap_SHA2) + ARM64.HasCRC32 = isSet(hwCap, hwcap_CRC32) + ARM64.HasATOMICS = isSet(hwCap, hwcap_ATOMICS) + ARM64.HasFPHP = isSet(hwCap, hwcap_FPHP) + ARM64.HasASIMDHP = isSet(hwCap, hwcap_ASIMDHP) + ARM64.HasCPUID = isSet(hwCap, hwcap_CPUID) + ARM64.HasASIMDRDM = isSet(hwCap, hwcap_ASIMDRDM) + ARM64.HasJSCVT = isSet(hwCap, hwcap_JSCVT) + ARM64.HasFCMA = isSet(hwCap, hwcap_FCMA) + ARM64.HasLRCPC = isSet(hwCap, hwcap_LRCPC) + ARM64.HasDCPOP = isSet(hwCap, hwcap_DCPOP) + ARM64.HasSHA3 = isSet(hwCap, hwcap_SHA3) + ARM64.HasSM3 = isSet(hwCap, hwcap_SM3) + ARM64.HasSM4 = isSet(hwCap, hwcap_SM4) + ARM64.HasASIMDDP = isSet(hwCap, hwcap_ASIMDDP) + ARM64.HasSHA512 = isSet(hwCap, hwcap_SHA512) + ARM64.HasSVE = isSet(hwCap, hwcap_SVE) + ARM64.HasASIMDFHM = isSet(hwCap, hwcap_ASIMDFHM) + ARM64.HasDIT = isSet(hwCap, hwcap_DIT) + + // HWCAP2 feature bits + ARM64.HasSVE2 = isSet(hwCap2, hwcap2_SVE2) + ARM64.HasI8MM = isSet(hwCap2, hwcap2_I8MM) +} + +func isSet(hwc uint, value uint) bool { + return hwc&value != 0 +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_linux_loong64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_loong64.go new file mode 100644 index 0000000..4f34114 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_loong64.go @@ -0,0 +1,22 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +// HWCAP bits. These are exposed by the Linux kernel. +const ( + hwcap_LOONGARCH_LSX = 1 << 4 + hwcap_LOONGARCH_LASX = 1 << 5 +) + +func doinit() { + // TODO: Features that require kernel support like LSX and LASX can + // be detected here once needed in std library or by the compiler. + Loong64.HasLSX = hwcIsSet(hwCap, hwcap_LOONGARCH_LSX) + Loong64.HasLASX = hwcIsSet(hwCap, hwcap_LOONGARCH_LASX) +} + +func hwcIsSet(hwc uint, val uint) bool { + return hwc&val != 0 +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_linux_mips64x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_mips64x.go new file mode 100644 index 0000000..4686c1d --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_mips64x.go @@ -0,0 +1,22 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && (mips64 || mips64le) + +package cpu + +// HWCAP bits. These are exposed by the Linux kernel 5.4. +const ( + // CPU features + hwcap_MIPS_MSA = 1 << 1 +) + +func doinit() { + // HWCAP feature bits + MIPS64X.HasMSA = isSet(hwCap, hwcap_MIPS_MSA) +} + +func isSet(hwc uint, value uint) bool { + return hwc&value != 0 +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_linux_noinit.go b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_noinit.go new file mode 100644 index 0000000..a428dec --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_noinit.go @@ -0,0 +1,9 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && !arm && !arm64 && !loong64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !s390x && !riscv64 + +package cpu + +func doinit() {} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_linux_ppc64x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_ppc64x.go new file mode 100644 index 0000000..197188e --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_ppc64x.go @@ -0,0 +1,30 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && (ppc64 || ppc64le) + +package cpu + +// HWCAP/HWCAP2 bits. These are exposed by the kernel. +const ( + // ISA Level + _PPC_FEATURE2_ARCH_2_07 = 0x80000000 + _PPC_FEATURE2_ARCH_3_00 = 0x00800000 + + // CPU features + _PPC_FEATURE2_DARN = 0x00200000 + _PPC_FEATURE2_SCV = 0x00100000 +) + +func doinit() { + // HWCAP2 feature bits + PPC64.IsPOWER8 = isSet(hwCap2, _PPC_FEATURE2_ARCH_2_07) + PPC64.IsPOWER9 = isSet(hwCap2, _PPC_FEATURE2_ARCH_3_00) + PPC64.HasDARN = isSet(hwCap2, _PPC_FEATURE2_DARN) + PPC64.HasSCV = isSet(hwCap2, _PPC_FEATURE2_SCV) +} + +func isSet(hwc uint, value uint) bool { + return hwc&value != 0 +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go new file mode 100644 index 0000000..ad74153 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go @@ -0,0 +1,160 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import ( + "syscall" + "unsafe" +) + +// RISC-V extension discovery code for Linux. The approach here is to first try the riscv_hwprobe +// syscall falling back to HWCAP to check for the C extension if riscv_hwprobe is not available. +// +// A note on detection of the Vector extension using HWCAP. +// +// Support for the Vector extension version 1.0 was added to the Linux kernel in release 6.5. +// Support for the riscv_hwprobe syscall was added in 6.4. It follows that if the riscv_hwprobe +// syscall is not available then neither is the Vector extension (which needs kernel support). +// The riscv_hwprobe syscall should then be all we need to detect the Vector extension. +// However, some RISC-V board manufacturers ship boards with an older kernel on top of which +// they have back-ported various versions of the Vector extension patches but not the riscv_hwprobe +// patches. These kernels advertise support for the Vector extension using HWCAP. Falling +// back to HWCAP to detect the Vector extension, if riscv_hwprobe is not available, or simply not +// bothering with riscv_hwprobe at all and just using HWCAP may then seem like an attractive option. +// +// Unfortunately, simply checking the 'V' bit in AT_HWCAP will not work as this bit is used by +// RISC-V board and cloud instance providers to mean different things. The Lichee Pi 4A board +// and the Scaleway RV1 cloud instances use the 'V' bit to advertise their support for the unratified +// 0.7.1 version of the Vector Specification. The Banana Pi BPI-F3 and the CanMV-K230 board use +// it to advertise support for 1.0 of the Vector extension. Versions 0.7.1 and 1.0 of the Vector +// extension are binary incompatible. HWCAP can then not be used in isolation to populate the +// HasV field as this field indicates that the underlying CPU is compatible with RVV 1.0. +// +// There is a way at runtime to distinguish between versions 0.7.1 and 1.0 of the Vector +// specification by issuing a RVV 1.0 vsetvli instruction and checking the vill bit of the vtype +// register. This check would allow us to safely detect version 1.0 of the Vector extension +// with HWCAP, if riscv_hwprobe were not available. However, the check cannot +// be added until the assembler supports the Vector instructions. +// +// Note the riscv_hwprobe syscall does not suffer from these ambiguities by design as all of the +// extensions it advertises support for are explicitly versioned. It's also worth noting that +// the riscv_hwprobe syscall is the only way to detect multi-letter RISC-V extensions, e.g., Zba. +// These cannot be detected using HWCAP and so riscv_hwprobe must be used to detect the majority +// of RISC-V extensions. +// +// Please see https://docs.kernel.org/arch/riscv/hwprobe.html for more information. + +// golang.org/x/sys/cpu is not allowed to depend on golang.org/x/sys/unix so we must +// reproduce the constants, types and functions needed to make the riscv_hwprobe syscall +// here. + +const ( + // Copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go. + riscv_HWPROBE_KEY_IMA_EXT_0 = 0x4 + riscv_HWPROBE_IMA_C = 0x2 + riscv_HWPROBE_IMA_V = 0x4 + riscv_HWPROBE_EXT_ZBA = 0x8 + riscv_HWPROBE_EXT_ZBB = 0x10 + riscv_HWPROBE_EXT_ZBS = 0x20 + riscv_HWPROBE_EXT_ZVBB = 0x20000 + riscv_HWPROBE_EXT_ZVBC = 0x40000 + riscv_HWPROBE_EXT_ZVKB = 0x80000 + riscv_HWPROBE_EXT_ZVKG = 0x100000 + riscv_HWPROBE_EXT_ZVKNED = 0x200000 + riscv_HWPROBE_EXT_ZVKNHB = 0x800000 + riscv_HWPROBE_EXT_ZVKSED = 0x1000000 + riscv_HWPROBE_EXT_ZVKSH = 0x2000000 + riscv_HWPROBE_EXT_ZVKT = 0x4000000 + riscv_HWPROBE_KEY_CPUPERF_0 = 0x5 + riscv_HWPROBE_MISALIGNED_FAST = 0x3 + riscv_HWPROBE_MISALIGNED_MASK = 0x7 +) + +const ( + // sys_RISCV_HWPROBE is copied from golang.org/x/sys/unix/zsysnum_linux_riscv64.go. + sys_RISCV_HWPROBE = 258 +) + +// riscvHWProbePairs is copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go. +type riscvHWProbePairs struct { + key int64 + value uint64 +} + +const ( + // CPU features + hwcap_RISCV_ISA_C = 1 << ('C' - 'A') +) + +func doinit() { + // A slice of key/value pair structures is passed to the RISCVHWProbe syscall. The key + // field should be initialised with one of the key constants defined above, e.g., + // RISCV_HWPROBE_KEY_IMA_EXT_0. The syscall will set the value field to the appropriate value. + // If the kernel does not recognise a key it will set the key field to -1 and the value field to 0. + + pairs := []riscvHWProbePairs{ + {riscv_HWPROBE_KEY_IMA_EXT_0, 0}, + {riscv_HWPROBE_KEY_CPUPERF_0, 0}, + } + + // This call only indicates that extensions are supported if they are implemented on all cores. + if riscvHWProbe(pairs, 0) { + if pairs[0].key != -1 { + v := uint(pairs[0].value) + RISCV64.HasC = isSet(v, riscv_HWPROBE_IMA_C) + RISCV64.HasV = isSet(v, riscv_HWPROBE_IMA_V) + RISCV64.HasZba = isSet(v, riscv_HWPROBE_EXT_ZBA) + RISCV64.HasZbb = isSet(v, riscv_HWPROBE_EXT_ZBB) + RISCV64.HasZbs = isSet(v, riscv_HWPROBE_EXT_ZBS) + RISCV64.HasZvbb = isSet(v, riscv_HWPROBE_EXT_ZVBB) + RISCV64.HasZvbc = isSet(v, riscv_HWPROBE_EXT_ZVBC) + RISCV64.HasZvkb = isSet(v, riscv_HWPROBE_EXT_ZVKB) + RISCV64.HasZvkg = isSet(v, riscv_HWPROBE_EXT_ZVKG) + RISCV64.HasZvkt = isSet(v, riscv_HWPROBE_EXT_ZVKT) + // Cryptography shorthand extensions + RISCV64.HasZvkn = isSet(v, riscv_HWPROBE_EXT_ZVKNED) && + isSet(v, riscv_HWPROBE_EXT_ZVKNHB) && RISCV64.HasZvkb && RISCV64.HasZvkt + RISCV64.HasZvknc = RISCV64.HasZvkn && RISCV64.HasZvbc + RISCV64.HasZvkng = RISCV64.HasZvkn && RISCV64.HasZvkg + RISCV64.HasZvks = isSet(v, riscv_HWPROBE_EXT_ZVKSED) && + isSet(v, riscv_HWPROBE_EXT_ZVKSH) && RISCV64.HasZvkb && RISCV64.HasZvkt + RISCV64.HasZvksc = RISCV64.HasZvks && RISCV64.HasZvbc + RISCV64.HasZvksg = RISCV64.HasZvks && RISCV64.HasZvkg + } + if pairs[1].key != -1 { + v := pairs[1].value & riscv_HWPROBE_MISALIGNED_MASK + RISCV64.HasFastMisaligned = v == riscv_HWPROBE_MISALIGNED_FAST + } + } + + // Let's double check with HWCAP if the C extension does not appear to be supported. + // This may happen if we're running on a kernel older than 6.4. + + if !RISCV64.HasC { + RISCV64.HasC = isSet(hwCap, hwcap_RISCV_ISA_C) + } +} + +func isSet(hwc uint, value uint) bool { + return hwc&value != 0 +} + +// riscvHWProbe is a simplified version of the generated wrapper function found in +// golang.org/x/sys/unix/zsyscall_linux_riscv64.go. We simplify it by removing the +// cpuCount and cpus parameters which we do not need. We always want to pass 0 for +// these parameters here so the kernel only reports the extensions that are present +// on all cores. +func riscvHWProbe(pairs []riscvHWProbePairs, flags uint) bool { + var _zero uintptr + var p0 unsafe.Pointer + if len(pairs) > 0 { + p0 = unsafe.Pointer(&pairs[0]) + } else { + p0 = unsafe.Pointer(&_zero) + } + + _, _, e1 := syscall.Syscall6(sys_RISCV_HWPROBE, uintptr(p0), uintptr(len(pairs)), uintptr(0), uintptr(0), uintptr(flags), 0) + return e1 == 0 +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_linux_s390x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_s390x.go new file mode 100644 index 0000000..1517ac6 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_linux_s390x.go @@ -0,0 +1,40 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +const ( + // bit mask values from /usr/include/bits/hwcap.h + hwcap_ZARCH = 2 + hwcap_STFLE = 4 + hwcap_MSA = 8 + hwcap_LDISP = 16 + hwcap_EIMM = 32 + hwcap_DFP = 64 + hwcap_ETF3EH = 256 + hwcap_VX = 2048 + hwcap_VXE = 8192 +) + +func initS390Xbase() { + // test HWCAP bit vector + has := func(featureMask uint) bool { + return hwCap&featureMask == featureMask + } + + // mandatory + S390X.HasZARCH = has(hwcap_ZARCH) + + // optional + S390X.HasSTFLE = has(hwcap_STFLE) + S390X.HasLDISP = has(hwcap_LDISP) + S390X.HasEIMM = has(hwcap_EIMM) + S390X.HasETF3EH = has(hwcap_ETF3EH) + S390X.HasDFP = has(hwcap_DFP) + S390X.HasMSA = has(hwcap_MSA) + S390X.HasVX = has(hwcap_VX) + if S390X.HasVX { + S390X.HasVXE = has(hwcap_VXE) + } +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_loong64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_loong64.go new file mode 100644 index 0000000..45ecb29 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_loong64.go @@ -0,0 +1,50 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build loong64 + +package cpu + +const cacheLineSize = 64 + +// Bit fields for CPUCFG registers, Related reference documents: +// https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#_cpucfg +const ( + // CPUCFG1 bits + cpucfg1_CRC32 = 1 << 25 + + // CPUCFG2 bits + cpucfg2_LAM_BH = 1 << 27 + cpucfg2_LAMCAS = 1 << 28 +) + +func initOptions() { + options = []option{ + {Name: "lsx", Feature: &Loong64.HasLSX}, + {Name: "lasx", Feature: &Loong64.HasLASX}, + {Name: "crc32", Feature: &Loong64.HasCRC32}, + {Name: "lam_bh", Feature: &Loong64.HasLAM_BH}, + {Name: "lamcas", Feature: &Loong64.HasLAMCAS}, + } + + // The CPUCFG data on Loong64 only reflects the hardware capabilities, + // not the kernel support status, so features such as LSX and LASX that + // require kernel support cannot be obtained from the CPUCFG data. + // + // These features only require hardware capability support and do not + // require kernel specific support, so they can be obtained directly + // through CPUCFG + cfg1 := get_cpucfg(1) + cfg2 := get_cpucfg(2) + + Loong64.HasCRC32 = cfgIsSet(cfg1, cpucfg1_CRC32) + Loong64.HasLAMCAS = cfgIsSet(cfg2, cpucfg2_LAMCAS) + Loong64.HasLAM_BH = cfgIsSet(cfg2, cpucfg2_LAM_BH) +} + +func get_cpucfg(reg uint32) uint32 + +func cfgIsSet(cfg uint32, val uint32) bool { + return cfg&val != 0 +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_loong64.s b/cli/vendor/golang.org/x/sys/cpu/cpu_loong64.s new file mode 100644 index 0000000..71cbaf1 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_loong64.s @@ -0,0 +1,13 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" + +// func get_cpucfg(reg uint32) uint32 +TEXT ·get_cpucfg(SB), NOSPLIT|NOFRAME, $0 + MOVW reg+0(FP), R5 + // CPUCFG R5, R4 = 0x00006ca4 + WORD $0x00006ca4 + MOVW R4, ret+8(FP) + RET diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_mips64x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_mips64x.go new file mode 100644 index 0000000..fedb00c --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_mips64x.go @@ -0,0 +1,15 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build mips64 || mips64le + +package cpu + +const cacheLineSize = 32 + +func initOptions() { + options = []option{ + {Name: "msa", Feature: &MIPS64X.HasMSA}, + } +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_mipsx.go b/cli/vendor/golang.org/x/sys/cpu/cpu_mipsx.go new file mode 100644 index 0000000..ffb4ec7 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_mipsx.go @@ -0,0 +1,11 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build mips || mipsle + +package cpu + +const cacheLineSize = 32 + +func initOptions() {} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go new file mode 100644 index 0000000..ebfb3fc --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go @@ -0,0 +1,173 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import ( + "syscall" + "unsafe" +) + +// Minimal copy of functionality from x/sys/unix so the cpu package can call +// sysctl without depending on x/sys/unix. + +const ( + _CTL_QUERY = -2 + + _SYSCTL_VERS_1 = 0x1000000 +) + +var _zero uintptr + +func sysctl(mib []int32, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, errno := syscall.Syscall6( + syscall.SYS___SYSCTL, + uintptr(_p0), + uintptr(len(mib)), + uintptr(unsafe.Pointer(old)), + uintptr(unsafe.Pointer(oldlen)), + uintptr(unsafe.Pointer(new)), + uintptr(newlen)) + if errno != 0 { + return errno + } + return nil +} + +type sysctlNode struct { + Flags uint32 + Num int32 + Name [32]int8 + Ver uint32 + __rsvd uint32 + Un [16]byte + _sysctl_size [8]byte + _sysctl_func [8]byte + _sysctl_parent [8]byte + _sysctl_desc [8]byte +} + +func sysctlNodes(mib []int32) ([]sysctlNode, error) { + var olen uintptr + + // Get a list of all sysctl nodes below the given MIB by performing + // a sysctl for the given MIB with CTL_QUERY appended. + mib = append(mib, _CTL_QUERY) + qnode := sysctlNode{Flags: _SYSCTL_VERS_1} + qp := (*byte)(unsafe.Pointer(&qnode)) + sz := unsafe.Sizeof(qnode) + if err := sysctl(mib, nil, &olen, qp, sz); err != nil { + return nil, err + } + + // Now that we know the size, get the actual nodes. + nodes := make([]sysctlNode, olen/sz) + np := (*byte)(unsafe.Pointer(&nodes[0])) + if err := sysctl(mib, np, &olen, qp, sz); err != nil { + return nil, err + } + + return nodes, nil +} + +func nametomib(name string) ([]int32, error) { + // Split name into components. + var parts []string + last := 0 + for i := 0; i < len(name); i++ { + if name[i] == '.' { + parts = append(parts, name[last:i]) + last = i + 1 + } + } + parts = append(parts, name[last:]) + + mib := []int32{} + // Discover the nodes and construct the MIB OID. + for partno, part := range parts { + nodes, err := sysctlNodes(mib) + if err != nil { + return nil, err + } + for _, node := range nodes { + n := make([]byte, 0) + for i := range node.Name { + if node.Name[i] != 0 { + n = append(n, byte(node.Name[i])) + } + } + if string(n) == part { + mib = append(mib, int32(node.Num)) + break + } + } + if len(mib) != partno+1 { + return nil, err + } + } + + return mib, nil +} + +// aarch64SysctlCPUID is struct aarch64_sysctl_cpu_id from NetBSD's +type aarch64SysctlCPUID struct { + midr uint64 /* Main ID Register */ + revidr uint64 /* Revision ID Register */ + mpidr uint64 /* Multiprocessor Affinity Register */ + aa64dfr0 uint64 /* A64 Debug Feature Register 0 */ + aa64dfr1 uint64 /* A64 Debug Feature Register 1 */ + aa64isar0 uint64 /* A64 Instruction Set Attribute Register 0 */ + aa64isar1 uint64 /* A64 Instruction Set Attribute Register 1 */ + aa64mmfr0 uint64 /* A64 Memory Model Feature Register 0 */ + aa64mmfr1 uint64 /* A64 Memory Model Feature Register 1 */ + aa64mmfr2 uint64 /* A64 Memory Model Feature Register 2 */ + aa64pfr0 uint64 /* A64 Processor Feature Register 0 */ + aa64pfr1 uint64 /* A64 Processor Feature Register 1 */ + aa64zfr0 uint64 /* A64 SVE Feature ID Register 0 */ + mvfr0 uint32 /* Media and VFP Feature Register 0 */ + mvfr1 uint32 /* Media and VFP Feature Register 1 */ + mvfr2 uint32 /* Media and VFP Feature Register 2 */ + pad uint32 + clidr uint64 /* Cache Level ID Register */ + ctr uint64 /* Cache Type Register */ +} + +func sysctlCPUID(name string) (*aarch64SysctlCPUID, error) { + mib, err := nametomib(name) + if err != nil { + return nil, err + } + + out := aarch64SysctlCPUID{} + n := unsafe.Sizeof(out) + _, _, errno := syscall.Syscall6( + syscall.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + uintptr(len(mib)), + uintptr(unsafe.Pointer(&out)), + uintptr(unsafe.Pointer(&n)), + uintptr(0), + uintptr(0)) + if errno != 0 { + return nil, errno + } + return &out, nil +} + +func doinit() { + cpuid, err := sysctlCPUID("machdep.cpu0.cpu_id") + if err != nil { + setMinimalFeatures() + return + } + parseARM64SystemRegisters(cpuid.aa64isar0, cpuid.aa64isar1, cpuid.aa64pfr0) + + Initialized = true +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go new file mode 100644 index 0000000..85b64d5 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go @@ -0,0 +1,65 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import ( + "syscall" + "unsafe" +) + +// Minimal copy of functionality from x/sys/unix so the cpu package can call +// sysctl without depending on x/sys/unix. + +const ( + // From OpenBSD's sys/sysctl.h. + _CTL_MACHDEP = 7 + + // From OpenBSD's machine/cpu.h. + _CPU_ID_AA64ISAR0 = 2 + _CPU_ID_AA64ISAR1 = 3 +) + +// Implemented in the runtime package (runtime/sys_openbsd3.go) +func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno) + +//go:linkname syscall_syscall6 syscall.syscall6 + +func sysctl(mib []uint32, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + _, _, errno := syscall_syscall6(libc_sysctl_trampoline_addr, uintptr(unsafe.Pointer(&mib[0])), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if errno != 0 { + return errno + } + return nil +} + +var libc_sysctl_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_sysctl sysctl "libc.so" + +func sysctlUint64(mib []uint32) (uint64, bool) { + var out uint64 + nout := unsafe.Sizeof(out) + if err := sysctl(mib, (*byte)(unsafe.Pointer(&out)), &nout, nil, 0); err != nil { + return 0, false + } + return out, true +} + +func doinit() { + setMinimalFeatures() + + // Get ID_AA64ISAR0 and ID_AA64ISAR1 from sysctl. + isar0, ok := sysctlUint64([]uint32{_CTL_MACHDEP, _CPU_ID_AA64ISAR0}) + if !ok { + return + } + isar1, ok := sysctlUint64([]uint32{_CTL_MACHDEP, _CPU_ID_AA64ISAR1}) + if !ok { + return + } + parseARM64SystemRegisters(isar0, isar1, 0) + + Initialized = true +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.s b/cli/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.s new file mode 100644 index 0000000..054ba05 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.s @@ -0,0 +1,11 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" + +TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_sysctl(SB) + +GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8 +DATA ·libc_sysctl_trampoline_addr(SB)/8, $libc_sysctl_trampoline<>(SB) diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_other_arm.go b/cli/vendor/golang.org/x/sys/cpu/cpu_other_arm.go new file mode 100644 index 0000000..e9ecf2a --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_other_arm.go @@ -0,0 +1,9 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux && arm + +package cpu + +func archInit() {} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go new file mode 100644 index 0000000..53f814d --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go @@ -0,0 +1,11 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !darwin && !linux && !netbsd && !openbsd && arm64 + +package cpu + +func doinit() { + setMinimalFeatures() +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_other_mips64x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_other_mips64x.go new file mode 100644 index 0000000..5f8f241 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_other_mips64x.go @@ -0,0 +1,11 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux && (mips64 || mips64le) + +package cpu + +func archInit() { + Initialized = true +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_other_ppc64x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_other_ppc64x.go new file mode 100644 index 0000000..89608fb --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_other_ppc64x.go @@ -0,0 +1,12 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !linux && (ppc64 || ppc64le) + +package cpu + +func archInit() { + PPC64.IsPOWER8 = true + Initialized = true +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_other_riscv64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_other_riscv64.go new file mode 100644 index 0000000..5ab8780 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_other_riscv64.go @@ -0,0 +1,11 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux && riscv64 + +package cpu + +func archInit() { + Initialized = true +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_other_x86.go b/cli/vendor/golang.org/x/sys/cpu/cpu_other_x86.go new file mode 100644 index 0000000..a0fd7e2 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_other_x86.go @@ -0,0 +1,11 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build 386 || amd64p32 || (amd64 && (!darwin || !gc)) + +package cpu + +func darwinSupportsAVX512() bool { + panic("only implemented for gc && amd64 && darwin") +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_ppc64x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_ppc64x.go new file mode 100644 index 0000000..c14f12b --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_ppc64x.go @@ -0,0 +1,16 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ppc64 || ppc64le + +package cpu + +const cacheLineSize = 128 + +func initOptions() { + options = []option{ + {Name: "darn", Feature: &PPC64.HasDARN}, + {Name: "scv", Feature: &PPC64.HasSCV}, + } +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_riscv64.go b/cli/vendor/golang.org/x/sys/cpu/cpu_riscv64.go new file mode 100644 index 0000000..0f617ae --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_riscv64.go @@ -0,0 +1,32 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build riscv64 + +package cpu + +const cacheLineSize = 64 + +func initOptions() { + options = []option{ + {Name: "fastmisaligned", Feature: &RISCV64.HasFastMisaligned}, + {Name: "c", Feature: &RISCV64.HasC}, + {Name: "v", Feature: &RISCV64.HasV}, + {Name: "zba", Feature: &RISCV64.HasZba}, + {Name: "zbb", Feature: &RISCV64.HasZbb}, + {Name: "zbs", Feature: &RISCV64.HasZbs}, + // RISC-V Cryptography Extensions + {Name: "zvbb", Feature: &RISCV64.HasZvbb}, + {Name: "zvbc", Feature: &RISCV64.HasZvbc}, + {Name: "zvkb", Feature: &RISCV64.HasZvkb}, + {Name: "zvkg", Feature: &RISCV64.HasZvkg}, + {Name: "zvkt", Feature: &RISCV64.HasZvkt}, + {Name: "zvkn", Feature: &RISCV64.HasZvkn}, + {Name: "zvknc", Feature: &RISCV64.HasZvknc}, + {Name: "zvkng", Feature: &RISCV64.HasZvkng}, + {Name: "zvks", Feature: &RISCV64.HasZvks}, + {Name: "zvksc", Feature: &RISCV64.HasZvksc}, + {Name: "zvksg", Feature: &RISCV64.HasZvksg}, + } +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_s390x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_s390x.go new file mode 100644 index 0000000..5881b88 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_s390x.go @@ -0,0 +1,172 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +const cacheLineSize = 256 + +func initOptions() { + options = []option{ + {Name: "zarch", Feature: &S390X.HasZARCH, Required: true}, + {Name: "stfle", Feature: &S390X.HasSTFLE, Required: true}, + {Name: "ldisp", Feature: &S390X.HasLDISP, Required: true}, + {Name: "eimm", Feature: &S390X.HasEIMM, Required: true}, + {Name: "dfp", Feature: &S390X.HasDFP}, + {Name: "etf3eh", Feature: &S390X.HasETF3EH}, + {Name: "msa", Feature: &S390X.HasMSA}, + {Name: "aes", Feature: &S390X.HasAES}, + {Name: "aescbc", Feature: &S390X.HasAESCBC}, + {Name: "aesctr", Feature: &S390X.HasAESCTR}, + {Name: "aesgcm", Feature: &S390X.HasAESGCM}, + {Name: "ghash", Feature: &S390X.HasGHASH}, + {Name: "sha1", Feature: &S390X.HasSHA1}, + {Name: "sha256", Feature: &S390X.HasSHA256}, + {Name: "sha3", Feature: &S390X.HasSHA3}, + {Name: "sha512", Feature: &S390X.HasSHA512}, + {Name: "vx", Feature: &S390X.HasVX}, + {Name: "vxe", Feature: &S390X.HasVXE}, + } +} + +// bitIsSet reports whether the bit at index is set. The bit index +// is in big endian order, so bit index 0 is the leftmost bit. +func bitIsSet(bits []uint64, index uint) bool { + return bits[index/64]&((1<<63)>>(index%64)) != 0 +} + +// facility is a bit index for the named facility. +type facility uint8 + +const ( + // mandatory facilities + zarch facility = 1 // z architecture mode is active + stflef facility = 7 // store-facility-list-extended + ldisp facility = 18 // long-displacement + eimm facility = 21 // extended-immediate + + // miscellaneous facilities + dfp facility = 42 // decimal-floating-point + etf3eh facility = 30 // extended-translation 3 enhancement + + // cryptography facilities + msa facility = 17 // message-security-assist + msa3 facility = 76 // message-security-assist extension 3 + msa4 facility = 77 // message-security-assist extension 4 + msa5 facility = 57 // message-security-assist extension 5 + msa8 facility = 146 // message-security-assist extension 8 + msa9 facility = 155 // message-security-assist extension 9 + + // vector facilities + vx facility = 129 // vector facility + vxe facility = 135 // vector-enhancements 1 + vxe2 facility = 148 // vector-enhancements 2 +) + +// facilityList contains the result of an STFLE call. +// Bits are numbered in big endian order so the +// leftmost bit (the MSB) is at index 0. +type facilityList struct { + bits [4]uint64 +} + +// Has reports whether the given facilities are present. +func (s *facilityList) Has(fs ...facility) bool { + if len(fs) == 0 { + panic("no facility bits provided") + } + for _, f := range fs { + if !bitIsSet(s.bits[:], uint(f)) { + return false + } + } + return true +} + +// function is the code for the named cryptographic function. +type function uint8 + +const ( + // KM{,A,C,CTR} function codes + aes128 function = 18 // AES-128 + aes192 function = 19 // AES-192 + aes256 function = 20 // AES-256 + + // K{I,L}MD function codes + sha1 function = 1 // SHA-1 + sha256 function = 2 // SHA-256 + sha512 function = 3 // SHA-512 + sha3_224 function = 32 // SHA3-224 + sha3_256 function = 33 // SHA3-256 + sha3_384 function = 34 // SHA3-384 + sha3_512 function = 35 // SHA3-512 + shake128 function = 36 // SHAKE-128 + shake256 function = 37 // SHAKE-256 + + // KLMD function codes + ghash function = 65 // GHASH +) + +// queryResult contains the result of a Query function +// call. Bits are numbered in big endian order so the +// leftmost bit (the MSB) is at index 0. +type queryResult struct { + bits [2]uint64 +} + +// Has reports whether the given functions are present. +func (q *queryResult) Has(fns ...function) bool { + if len(fns) == 0 { + panic("no function codes provided") + } + for _, f := range fns { + if !bitIsSet(q.bits[:], uint(f)) { + return false + } + } + return true +} + +func doinit() { + initS390Xbase() + + // We need implementations of stfle, km and so on + // to detect cryptographic features. + if !haveAsmFunctions() { + return + } + + // optional cryptographic functions + if S390X.HasMSA { + aes := []function{aes128, aes192, aes256} + + // cipher message + km, kmc := kmQuery(), kmcQuery() + S390X.HasAES = km.Has(aes...) + S390X.HasAESCBC = kmc.Has(aes...) + if S390X.HasSTFLE { + facilities := stfle() + if facilities.Has(msa4) { + kmctr := kmctrQuery() + S390X.HasAESCTR = kmctr.Has(aes...) + } + if facilities.Has(msa8) { + kma := kmaQuery() + S390X.HasAESGCM = kma.Has(aes...) + } + } + + // compute message digest + kimd := kimdQuery() // intermediate (no padding) + klmd := klmdQuery() // last (padding) + S390X.HasSHA1 = kimd.Has(sha1) && klmd.Has(sha1) + S390X.HasSHA256 = kimd.Has(sha256) && klmd.Has(sha256) + S390X.HasSHA512 = kimd.Has(sha512) && klmd.Has(sha512) + S390X.HasGHASH = kimd.Has(ghash) // KLMD-GHASH does not exist + sha3 := []function{ + sha3_224, sha3_256, sha3_384, sha3_512, + shake128, shake256, + } + S390X.HasSHA3 = kimd.Has(sha3...) && klmd.Has(sha3...) + } +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_s390x.s b/cli/vendor/golang.org/x/sys/cpu/cpu_s390x.s new file mode 100644 index 0000000..1fb4b70 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_s390x.s @@ -0,0 +1,57 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build gc + +#include "textflag.h" + +// func stfle() facilityList +TEXT ·stfle(SB), NOSPLIT|NOFRAME, $0-32 + MOVD $ret+0(FP), R1 + MOVD $3, R0 // last doubleword index to store + XC $32, (R1), (R1) // clear 4 doublewords (32 bytes) + WORD $0xb2b01000 // store facility list extended (STFLE) + RET + +// func kmQuery() queryResult +TEXT ·kmQuery(SB), NOSPLIT|NOFRAME, $0-16 + MOVD $0, R0 // set function code to 0 (KM-Query) + MOVD $ret+0(FP), R1 // address of 16-byte return value + WORD $0xB92E0024 // cipher message (KM) + RET + +// func kmcQuery() queryResult +TEXT ·kmcQuery(SB), NOSPLIT|NOFRAME, $0-16 + MOVD $0, R0 // set function code to 0 (KMC-Query) + MOVD $ret+0(FP), R1 // address of 16-byte return value + WORD $0xB92F0024 // cipher message with chaining (KMC) + RET + +// func kmctrQuery() queryResult +TEXT ·kmctrQuery(SB), NOSPLIT|NOFRAME, $0-16 + MOVD $0, R0 // set function code to 0 (KMCTR-Query) + MOVD $ret+0(FP), R1 // address of 16-byte return value + WORD $0xB92D4024 // cipher message with counter (KMCTR) + RET + +// func kmaQuery() queryResult +TEXT ·kmaQuery(SB), NOSPLIT|NOFRAME, $0-16 + MOVD $0, R0 // set function code to 0 (KMA-Query) + MOVD $ret+0(FP), R1 // address of 16-byte return value + WORD $0xb9296024 // cipher message with authentication (KMA) + RET + +// func kimdQuery() queryResult +TEXT ·kimdQuery(SB), NOSPLIT|NOFRAME, $0-16 + MOVD $0, R0 // set function code to 0 (KIMD-Query) + MOVD $ret+0(FP), R1 // address of 16-byte return value + WORD $0xB93E0024 // compute intermediate message digest (KIMD) + RET + +// func klmdQuery() queryResult +TEXT ·klmdQuery(SB), NOSPLIT|NOFRAME, $0-16 + MOVD $0, R0 // set function code to 0 (KLMD-Query) + MOVD $ret+0(FP), R1 // address of 16-byte return value + WORD $0xB93F0024 // compute last message digest (KLMD) + RET diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_wasm.go b/cli/vendor/golang.org/x/sys/cpu/cpu_wasm.go new file mode 100644 index 0000000..384787e --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_wasm.go @@ -0,0 +1,17 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build wasm + +package cpu + +// We're compiling the cpu package for an unknown (software-abstracted) CPU. +// Make CacheLinePad an empty struct and hope that the usual struct alignment +// rules are good enough. + +const cacheLineSize = 0 + +func initOptions() {} + +func archInit() {} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_x86.go b/cli/vendor/golang.org/x/sys/cpu/cpu_x86.go new file mode 100644 index 0000000..f5723d4 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_x86.go @@ -0,0 +1,236 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build 386 || amd64 || amd64p32 + +package cpu + +import "runtime" + +const cacheLineSize = 64 + +func initOptions() { + options = []option{ + {Name: "adx", Feature: &X86.HasADX}, + {Name: "aes", Feature: &X86.HasAES}, + {Name: "avx", Feature: &X86.HasAVX}, + {Name: "avx2", Feature: &X86.HasAVX2}, + {Name: "avx512", Feature: &X86.HasAVX512}, + {Name: "avx512f", Feature: &X86.HasAVX512F}, + {Name: "avx512cd", Feature: &X86.HasAVX512CD}, + {Name: "avx512er", Feature: &X86.HasAVX512ER}, + {Name: "avx512pf", Feature: &X86.HasAVX512PF}, + {Name: "avx512vl", Feature: &X86.HasAVX512VL}, + {Name: "avx512bw", Feature: &X86.HasAVX512BW}, + {Name: "avx512dq", Feature: &X86.HasAVX512DQ}, + {Name: "avx512ifma", Feature: &X86.HasAVX512IFMA}, + {Name: "avx512vbmi", Feature: &X86.HasAVX512VBMI}, + {Name: "avx512vnniw", Feature: &X86.HasAVX5124VNNIW}, + {Name: "avx5124fmaps", Feature: &X86.HasAVX5124FMAPS}, + {Name: "avx512vpopcntdq", Feature: &X86.HasAVX512VPOPCNTDQ}, + {Name: "avx512vpclmulqdq", Feature: &X86.HasAVX512VPCLMULQDQ}, + {Name: "avx512vnni", Feature: &X86.HasAVX512VNNI}, + {Name: "avx512gfni", Feature: &X86.HasAVX512GFNI}, + {Name: "avx512vaes", Feature: &X86.HasAVX512VAES}, + {Name: "avx512vbmi2", Feature: &X86.HasAVX512VBMI2}, + {Name: "avx512bitalg", Feature: &X86.HasAVX512BITALG}, + {Name: "avx512bf16", Feature: &X86.HasAVX512BF16}, + {Name: "amxtile", Feature: &X86.HasAMXTile}, + {Name: "amxint8", Feature: &X86.HasAMXInt8}, + {Name: "amxbf16", Feature: &X86.HasAMXBF16}, + {Name: "bmi1", Feature: &X86.HasBMI1}, + {Name: "bmi2", Feature: &X86.HasBMI2}, + {Name: "cx16", Feature: &X86.HasCX16}, + {Name: "erms", Feature: &X86.HasERMS}, + {Name: "fma", Feature: &X86.HasFMA}, + {Name: "osxsave", Feature: &X86.HasOSXSAVE}, + {Name: "pclmulqdq", Feature: &X86.HasPCLMULQDQ}, + {Name: "popcnt", Feature: &X86.HasPOPCNT}, + {Name: "rdrand", Feature: &X86.HasRDRAND}, + {Name: "rdseed", Feature: &X86.HasRDSEED}, + {Name: "sse3", Feature: &X86.HasSSE3}, + {Name: "sse41", Feature: &X86.HasSSE41}, + {Name: "sse42", Feature: &X86.HasSSE42}, + {Name: "ssse3", Feature: &X86.HasSSSE3}, + {Name: "avxifma", Feature: &X86.HasAVXIFMA}, + {Name: "avxvnni", Feature: &X86.HasAVXVNNI}, + {Name: "avxvnniint8", Feature: &X86.HasAVXVNNIInt8}, + + // These capabilities should always be enabled on amd64: + {Name: "sse2", Feature: &X86.HasSSE2, Required: runtime.GOARCH == "amd64"}, + } +} + +func archInit() { + + // From internal/cpu + const ( + // eax bits + cpuid_AVXVNNI = 1 << 4 + + // ecx bits + cpuid_SSE3 = 1 << 0 + cpuid_PCLMULQDQ = 1 << 1 + cpuid_AVX512VBMI = 1 << 1 + cpuid_AVX512VBMI2 = 1 << 6 + cpuid_SSSE3 = 1 << 9 + cpuid_AVX512GFNI = 1 << 8 + cpuid_AVX512VAES = 1 << 9 + cpuid_AVX512VNNI = 1 << 11 + cpuid_AVX512BITALG = 1 << 12 + cpuid_FMA = 1 << 12 + cpuid_AVX512VPOPCNTDQ = 1 << 14 + cpuid_SSE41 = 1 << 19 + cpuid_SSE42 = 1 << 20 + cpuid_POPCNT = 1 << 23 + cpuid_AES = 1 << 25 + cpuid_OSXSAVE = 1 << 27 + cpuid_AVX = 1 << 28 + + // "Extended Feature Flag" bits returned in EBX for CPUID EAX=0x7 ECX=0x0 + cpuid_BMI1 = 1 << 3 + cpuid_AVX2 = 1 << 5 + cpuid_BMI2 = 1 << 8 + cpuid_ERMS = 1 << 9 + cpuid_AVX512F = 1 << 16 + cpuid_AVX512DQ = 1 << 17 + cpuid_ADX = 1 << 19 + cpuid_AVX512CD = 1 << 28 + cpuid_SHA = 1 << 29 + cpuid_AVX512BW = 1 << 30 + cpuid_AVX512VL = 1 << 31 + + // "Extended Feature Flag" bits returned in ECX for CPUID EAX=0x7 ECX=0x0 + cpuid_AVX512_VBMI = 1 << 1 + cpuid_AVX512_VBMI2 = 1 << 6 + cpuid_GFNI = 1 << 8 + cpuid_AVX512VPCLMULQDQ = 1 << 10 + cpuid_AVX512_BITALG = 1 << 12 + + // edx bits + cpuid_FSRM = 1 << 4 + // edx bits for CPUID 0x80000001 + cpuid_RDTSCP = 1 << 27 + ) + // Additional constants not in internal/cpu + const ( + // eax=1: edx + cpuid_SSE2 = 1 << 26 + // eax=1: ecx + cpuid_CX16 = 1 << 13 + cpuid_RDRAND = 1 << 30 + // eax=7,ecx=0: ebx + cpuid_RDSEED = 1 << 18 + cpuid_AVX512IFMA = 1 << 21 + cpuid_AVX512PF = 1 << 26 + cpuid_AVX512ER = 1 << 27 + // eax=7,ecx=0: edx + cpuid_AVX5124VNNIW = 1 << 2 + cpuid_AVX5124FMAPS = 1 << 3 + cpuid_AMXBF16 = 1 << 22 + cpuid_AMXTile = 1 << 24 + cpuid_AMXInt8 = 1 << 25 + // eax=7,ecx=1: eax + cpuid_AVX512BF16 = 1 << 5 + cpuid_AVXIFMA = 1 << 23 + // eax=7,ecx=1: edx + cpuid_AVXVNNIInt8 = 1 << 4 + ) + + Initialized = true + + maxID, _, _, _ := cpuid(0, 0) + + if maxID < 1 { + return + } + + _, _, ecx1, edx1 := cpuid(1, 0) + X86.HasSSE2 = isSet(edx1, cpuid_SSE2) + + X86.HasSSE3 = isSet(ecx1, cpuid_SSE3) + X86.HasPCLMULQDQ = isSet(ecx1, cpuid_PCLMULQDQ) + X86.HasSSSE3 = isSet(ecx1, cpuid_SSSE3) + X86.HasFMA = isSet(ecx1, cpuid_FMA) + X86.HasCX16 = isSet(ecx1, cpuid_CX16) + X86.HasSSE41 = isSet(ecx1, cpuid_SSE41) + X86.HasSSE42 = isSet(ecx1, cpuid_SSE42) + X86.HasPOPCNT = isSet(ecx1, cpuid_POPCNT) + X86.HasAES = isSet(ecx1, cpuid_AES) + X86.HasOSXSAVE = isSet(ecx1, cpuid_OSXSAVE) + X86.HasRDRAND = isSet(ecx1, cpuid_RDRAND) + + var osSupportsAVX, osSupportsAVX512 bool + // For XGETBV, OSXSAVE bit is required and sufficient. + if X86.HasOSXSAVE { + eax, _ := xgetbv() + // Check if XMM and YMM registers have OS support. + osSupportsAVX = isSet(eax, 1<<1) && isSet(eax, 1<<2) + + if runtime.GOOS == "darwin" { + // Darwin requires special AVX512 checks, see cpu_darwin_x86.go + osSupportsAVX512 = osSupportsAVX && darwinSupportsAVX512() + } else { + // Check if OPMASK and ZMM registers have OS support. + osSupportsAVX512 = osSupportsAVX && isSet(eax, 1<<5) && isSet(eax, 1<<6) && isSet(eax, 1<<7) + } + } + + X86.HasAVX = isSet(ecx1, cpuid_AVX) && osSupportsAVX + + if maxID < 7 { + return + } + + eax7, ebx7, ecx7, edx7 := cpuid(7, 0) + X86.HasBMI1 = isSet(ebx7, cpuid_BMI1) + X86.HasAVX2 = isSet(ebx7, cpuid_AVX2) && osSupportsAVX + X86.HasBMI2 = isSet(ebx7, cpuid_BMI2) + X86.HasERMS = isSet(ebx7, cpuid_ERMS) + X86.HasRDSEED = isSet(ebx7, cpuid_RDSEED) + X86.HasADX = isSet(ebx7, cpuid_ADX) + + X86.HasAVX512 = isSet(ebx7, cpuid_AVX512F) && osSupportsAVX512 // Because avx-512 foundation is the core required extension + if X86.HasAVX512 { + X86.HasAVX512F = true + X86.HasAVX512CD = isSet(ebx7, cpuid_AVX512CD) + X86.HasAVX512ER = isSet(ebx7, cpuid_AVX512ER) + X86.HasAVX512PF = isSet(ebx7, cpuid_AVX512PF) + X86.HasAVX512VL = isSet(ebx7, cpuid_AVX512VL) + X86.HasAVX512BW = isSet(ebx7, cpuid_AVX512BW) + X86.HasAVX512DQ = isSet(ebx7, cpuid_AVX512DQ) + X86.HasAVX512IFMA = isSet(ebx7, cpuid_AVX512IFMA) + X86.HasAVX512VBMI = isSet(ecx7, cpuid_AVX512_VBMI) + X86.HasAVX5124VNNIW = isSet(edx7, cpuid_AVX5124VNNIW) + X86.HasAVX5124FMAPS = isSet(edx7, cpuid_AVX5124FMAPS) + X86.HasAVX512VPOPCNTDQ = isSet(ecx7, cpuid_AVX512VPOPCNTDQ) + X86.HasAVX512VPCLMULQDQ = isSet(ecx7, cpuid_AVX512VPCLMULQDQ) + X86.HasAVX512VNNI = isSet(ecx7, cpuid_AVX512VNNI) + X86.HasAVX512GFNI = isSet(ecx7, cpuid_AVX512GFNI) + X86.HasAVX512VAES = isSet(ecx7, cpuid_AVX512VAES) + X86.HasAVX512VBMI2 = isSet(ecx7, cpuid_AVX512VBMI2) + X86.HasAVX512BITALG = isSet(ecx7, cpuid_AVX512BITALG) + } + + X86.HasAMXTile = isSet(edx7, cpuid_AMXTile) + X86.HasAMXInt8 = isSet(edx7, cpuid_AMXInt8) + X86.HasAMXBF16 = isSet(edx7, cpuid_AMXBF16) + + // These features depend on the second level of extended features. + if eax7 >= 1 { + eax71, _, _, edx71 := cpuid(7, 1) + if X86.HasAVX512 { + X86.HasAVX512BF16 = isSet(eax71, cpuid_AVX512BF16) + } + if X86.HasAVX { + X86.HasAVXIFMA = isSet(eax71, cpuid_AVXIFMA) + X86.HasAVXVNNI = isSet(eax71, cpuid_AVXVNNI) + X86.HasAVXVNNIInt8 = isSet(edx71, cpuid_AVXVNNIInt8) + } + } +} + +func isSet(hwc uint32, value uint32) bool { + return hwc&value != 0 +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_zos.go b/cli/vendor/golang.org/x/sys/cpu/cpu_zos.go new file mode 100644 index 0000000..5f54683 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_zos.go @@ -0,0 +1,10 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +func archInit() { + doinit() + Initialized = true +} diff --git a/cli/vendor/golang.org/x/sys/cpu/cpu_zos_s390x.go b/cli/vendor/golang.org/x/sys/cpu/cpu_zos_s390x.go new file mode 100644 index 0000000..ccb1b70 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/cpu_zos_s390x.go @@ -0,0 +1,25 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +func initS390Xbase() { + // get the facilities list + facilities := stfle() + + // mandatory + S390X.HasZARCH = facilities.Has(zarch) + S390X.HasSTFLE = facilities.Has(stflef) + S390X.HasLDISP = facilities.Has(ldisp) + S390X.HasEIMM = facilities.Has(eimm) + + // optional + S390X.HasETF3EH = facilities.Has(etf3eh) + S390X.HasDFP = facilities.Has(dfp) + S390X.HasMSA = facilities.Has(msa) + S390X.HasVX = facilities.Has(vx) + if S390X.HasVX { + S390X.HasVXE = facilities.Has(vxe) + } +} diff --git a/cli/vendor/golang.org/x/sys/cpu/endian_big.go b/cli/vendor/golang.org/x/sys/cpu/endian_big.go new file mode 100644 index 0000000..7fe04b0 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/endian_big.go @@ -0,0 +1,10 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build armbe || arm64be || m68k || mips || mips64 || mips64p32 || ppc || ppc64 || s390 || s390x || shbe || sparc || sparc64 + +package cpu + +// IsBigEndian records whether the GOARCH's byte order is big endian. +const IsBigEndian = true diff --git a/cli/vendor/golang.org/x/sys/cpu/endian_little.go b/cli/vendor/golang.org/x/sys/cpu/endian_little.go new file mode 100644 index 0000000..48eccc4 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/endian_little.go @@ -0,0 +1,10 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh || wasm + +package cpu + +// IsBigEndian records whether the GOARCH's byte order is big endian. +const IsBigEndian = false diff --git a/cli/vendor/golang.org/x/sys/cpu/hwcap_linux.go b/cli/vendor/golang.org/x/sys/cpu/hwcap_linux.go new file mode 100644 index 0000000..34e49f9 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/hwcap_linux.go @@ -0,0 +1,71 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import ( + "os" +) + +const ( + _AT_HWCAP = 16 + _AT_HWCAP2 = 26 + + procAuxv = "/proc/self/auxv" + + uintSize = int(32 << (^uint(0) >> 63)) +) + +// For those platforms don't have a 'cpuid' equivalent we use HWCAP/HWCAP2 +// These are initialized in cpu_$GOARCH.go +// and should not be changed after they are initialized. +var hwCap uint +var hwCap2 uint + +func readHWCAP() error { + // For Go 1.21+, get auxv from the Go runtime. + if a := getAuxv(); len(a) > 0 { + for len(a) >= 2 { + tag, val := a[0], uint(a[1]) + a = a[2:] + switch tag { + case _AT_HWCAP: + hwCap = val + case _AT_HWCAP2: + hwCap2 = val + } + } + return nil + } + + buf, err := os.ReadFile(procAuxv) + if err != nil { + // e.g. on android /proc/self/auxv is not accessible, so silently + // ignore the error and leave Initialized = false. On some + // architectures (e.g. arm64) doinit() implements a fallback + // readout and will set Initialized = true again. + return err + } + bo := hostByteOrder() + for len(buf) >= 2*(uintSize/8) { + var tag, val uint + switch uintSize { + case 32: + tag = uint(bo.Uint32(buf[0:])) + val = uint(bo.Uint32(buf[4:])) + buf = buf[8:] + case 64: + tag = uint(bo.Uint64(buf[0:])) + val = uint(bo.Uint64(buf[8:])) + buf = buf[16:] + } + switch tag { + case _AT_HWCAP: + hwCap = val + case _AT_HWCAP2: + hwCap2 = val + } + } + return nil +} diff --git a/cli/vendor/golang.org/x/sys/cpu/parse.go b/cli/vendor/golang.org/x/sys/cpu/parse.go new file mode 100644 index 0000000..56a7e1a --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/parse.go @@ -0,0 +1,43 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import "strconv" + +// parseRelease parses a dot-separated version number. It follows the semver +// syntax, but allows the minor and patch versions to be elided. +// +// This is a copy of the Go runtime's parseRelease from +// https://golang.org/cl/209597. +func parseRelease(rel string) (major, minor, patch int, ok bool) { + // Strip anything after a dash or plus. + for i := range len(rel) { + if rel[i] == '-' || rel[i] == '+' { + rel = rel[:i] + break + } + } + + next := func() (int, bool) { + for i := range len(rel) { + if rel[i] == '.' { + ver, err := strconv.Atoi(rel[:i]) + rel = rel[i+1:] + return ver, err == nil + } + } + ver, err := strconv.Atoi(rel) + rel = "" + return ver, err == nil + } + if major, ok = next(); !ok || rel == "" { + return + } + if minor, ok = next(); !ok || rel == "" { + return + } + patch, ok = next() + return +} diff --git a/cli/vendor/golang.org/x/sys/cpu/proc_cpuinfo_linux.go b/cli/vendor/golang.org/x/sys/cpu/proc_cpuinfo_linux.go new file mode 100644 index 0000000..4cd64c7 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/proc_cpuinfo_linux.go @@ -0,0 +1,53 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && arm64 + +package cpu + +import ( + "errors" + "io" + "os" + "strings" +) + +func readLinuxProcCPUInfo() error { + f, err := os.Open("/proc/cpuinfo") + if err != nil { + return err + } + defer f.Close() + + var buf [1 << 10]byte // enough for first CPU + n, err := io.ReadFull(f, buf[:]) + if err != nil && err != io.ErrUnexpectedEOF { + return err + } + in := string(buf[:n]) + const features = "\nFeatures : " + i := strings.Index(in, features) + if i == -1 { + return errors.New("no CPU features found") + } + in = in[i+len(features):] + if i := strings.Index(in, "\n"); i != -1 { + in = in[:i] + } + m := map[string]*bool{} + + initOptions() // need it early here; it's harmless to call twice + for _, o := range options { + m[o.Name] = o.Feature + } + // The EVTSTRM field has alias "evstrm" in Go, but Linux calls it "evtstrm". + m["evtstrm"] = &ARM64.HasEVTSTRM + + for _, f := range strings.Fields(in) { + if p, ok := m[f]; ok { + *p = true + } + } + return nil +} diff --git a/cli/vendor/golang.org/x/sys/cpu/runtime_auxv.go b/cli/vendor/golang.org/x/sys/cpu/runtime_auxv.go new file mode 100644 index 0000000..5f92ac9 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/runtime_auxv.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +// getAuxvFn is non-nil on Go 1.21+ (via runtime_auxv_go121.go init) +// on platforms that use auxv. +var getAuxvFn func() []uintptr + +func getAuxv() []uintptr { + if getAuxvFn == nil { + return nil + } + return getAuxvFn() +} diff --git a/cli/vendor/golang.org/x/sys/cpu/runtime_auxv_go121.go b/cli/vendor/golang.org/x/sys/cpu/runtime_auxv_go121.go new file mode 100644 index 0000000..4c9788e --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/runtime_auxv_go121.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.21 + +package cpu + +import ( + _ "unsafe" // for linkname +) + +//go:linkname runtime_getAuxv runtime.getAuxv +func runtime_getAuxv() []uintptr + +func init() { + getAuxvFn = runtime_getAuxv +} diff --git a/cli/vendor/golang.org/x/sys/cpu/syscall_aix_gccgo.go b/cli/vendor/golang.org/x/sys/cpu/syscall_aix_gccgo.go new file mode 100644 index 0000000..1b9ccb0 --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/syscall_aix_gccgo.go @@ -0,0 +1,26 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Recreate a getsystemcfg syscall handler instead of +// using the one provided by x/sys/unix to avoid having +// the dependency between them. (See golang.org/issue/32102) +// Moreover, this file will be used during the building of +// gccgo's libgo and thus must not used a CGo method. + +//go:build aix && gccgo + +package cpu + +import ( + "syscall" +) + +//extern getsystemcfg +func gccgoGetsystemcfg(label uint32) (r uint64) + +func callgetsystemcfg(label int) (r1 uintptr, e1 syscall.Errno) { + r1 = uintptr(gccgoGetsystemcfg(uint32(label))) + e1 = syscall.GetErrno() + return +} diff --git a/cli/vendor/golang.org/x/sys/cpu/syscall_aix_ppc64_gc.go b/cli/vendor/golang.org/x/sys/cpu/syscall_aix_ppc64_gc.go new file mode 100644 index 0000000..e8b6cdb --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/syscall_aix_ppc64_gc.go @@ -0,0 +1,35 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Minimal copy of x/sys/unix so the cpu package can make a +// system call on AIX without depending on x/sys/unix. +// (See golang.org/issue/32102) + +//go:build aix && ppc64 && gc + +package cpu + +import ( + "syscall" + "unsafe" +) + +//go:cgo_import_dynamic libc_getsystemcfg getsystemcfg "libc.a/shr_64.o" + +//go:linkname libc_getsystemcfg libc_getsystemcfg + +type syscallFunc uintptr + +var libc_getsystemcfg syscallFunc + +type errno = syscall.Errno + +// Implemented in runtime/syscall_aix.go. +func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err errno) +func syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err errno) + +func callgetsystemcfg(label int) (r1 uintptr, e1 errno) { + r1, _, e1 = syscall6(uintptr(unsafe.Pointer(&libc_getsystemcfg)), 1, uintptr(label), 0, 0, 0, 0, 0) + return +} diff --git a/cli/vendor/golang.org/x/sys/cpu/syscall_darwin_arm64_gc.go b/cli/vendor/golang.org/x/sys/cpu/syscall_darwin_arm64_gc.go new file mode 100644 index 0000000..7b4e67f --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/syscall_darwin_arm64_gc.go @@ -0,0 +1,54 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Minimal copy from internal/cpu and runtime to make sysctl calls. + +//go:build darwin && arm64 && gc + +package cpu + +import ( + "syscall" + "unsafe" +) + +type Errno = syscall.Errno + +// adapted from internal/cpu/cpu_arm64_darwin.go +func darwinSysctlEnabled(name []byte) bool { + out := int32(0) + nout := unsafe.Sizeof(out) + if ret := sysctlbyname(&name[0], (*byte)(unsafe.Pointer(&out)), &nout, nil, 0); ret != nil { + return false + } + return out > 0 +} + +//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib" + +var libc_sysctlbyname_trampoline_addr uintptr + +// adapted from runtime/sys_darwin.go in the pattern of sysctl() above, as defined in x/sys/unix +func sysctlbyname(name *byte, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error { + if _, _, err := syscall_syscall6( + libc_sysctlbyname_trampoline_addr, + uintptr(unsafe.Pointer(name)), + uintptr(unsafe.Pointer(old)), + uintptr(unsafe.Pointer(oldlen)), + uintptr(unsafe.Pointer(new)), + uintptr(newlen), + 0, + ); err != 0 { + return err + } + + return nil +} + +//go:cgo_import_dynamic libc_sysctlbyname sysctlbyname "/usr/lib/libSystem.B.dylib" + +// Implemented in the runtime package (runtime/sys_darwin.go) +func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) + +//go:linkname syscall_syscall6 syscall.syscall6 diff --git a/cli/vendor/golang.org/x/sys/cpu/syscall_darwin_x86_gc.go b/cli/vendor/golang.org/x/sys/cpu/syscall_darwin_x86_gc.go new file mode 100644 index 0000000..4d0888b --- /dev/null +++ b/cli/vendor/golang.org/x/sys/cpu/syscall_darwin_x86_gc.go @@ -0,0 +1,98 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Minimal copy of x/sys/unix so the cpu package can make a +// system call on Darwin without depending on x/sys/unix. + +//go:build darwin && amd64 && gc + +package cpu + +import ( + "syscall" + "unsafe" +) + +type _C_int int32 + +// adapted from unix.Uname() at x/sys/unix/syscall_darwin.go L419 +func darwinOSRelease(release *[256]byte) error { + // from x/sys/unix/zerrors_openbsd_amd64.go + const ( + CTL_KERN = 0x1 + KERN_OSRELEASE = 0x2 + ) + + mib := []_C_int{CTL_KERN, KERN_OSRELEASE} + n := unsafe.Sizeof(*release) + + return sysctl(mib, &release[0], &n, nil, 0) +} + +type Errno = syscall.Errno + +var _zero uintptr // Single-word zero for use when we need a valid pointer to 0 bytes. + +// from x/sys/unix/zsyscall_darwin_amd64.go L791-807 +func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + if _, _, err := syscall_syscall6( + libc_sysctl_trampoline_addr, + uintptr(_p0), + uintptr(len(mib)), + uintptr(unsafe.Pointer(old)), + uintptr(unsafe.Pointer(oldlen)), + uintptr(unsafe.Pointer(new)), + uintptr(newlen), + ); err != 0 { + return err + } + + return nil +} + +var libc_sysctl_trampoline_addr uintptr + +// adapted from internal/cpu/cpu_arm64_darwin.go +func darwinSysctlEnabled(name []byte) bool { + out := int32(0) + nout := unsafe.Sizeof(out) + if ret := sysctlbyname(&name[0], (*byte)(unsafe.Pointer(&out)), &nout, nil, 0); ret != nil { + return false + } + return out > 0 +} + +//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib" + +var libc_sysctlbyname_trampoline_addr uintptr + +// adapted from runtime/sys_darwin.go in the pattern of sysctl() above, as defined in x/sys/unix +func sysctlbyname(name *byte, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error { + if _, _, err := syscall_syscall6( + libc_sysctlbyname_trampoline_addr, + uintptr(unsafe.Pointer(name)), + uintptr(unsafe.Pointer(old)), + uintptr(unsafe.Pointer(oldlen)), + uintptr(unsafe.Pointer(new)), + uintptr(newlen), + 0, + ); err != 0 { + return err + } + + return nil +} + +//go:cgo_import_dynamic libc_sysctlbyname sysctlbyname "/usr/lib/libSystem.B.dylib" + +// Implemented in the runtime package (runtime/sys_darwin.go) +func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) + +//go:linkname syscall_syscall6 syscall.syscall6 diff --git a/cli/vendor/modules.txt b/cli/vendor/modules.txt index 6d82c2a..1524fac 100644 --- a/cli/vendor/modules.txt +++ b/cli/vendor/modules.txt @@ -1,3 +1,36 @@ +# code.gitea.io/sdk/gitea v0.25.1 +## explicit; go 1.26 +code.gitea.io/sdk/gitea +# github.com/42wim/httpsig v1.2.4 +## explicit; go 1.25.0 +github.com/42wim/httpsig +# github.com/davidmz/go-pageant v1.0.2 +## explicit; go 1.16 +github.com/davidmz/go-pageant +# github.com/go-fed/httpsig v1.1.0 +## explicit; go 1.13 +github.com/go-fed/httpsig +# github.com/hashicorp/go-version v1.9.0 +## explicit +github.com/hashicorp/go-version +# golang.org/x/crypto v0.50.0 +## explicit; go 1.25.0 +golang.org/x/crypto/blake2b +golang.org/x/crypto/blake2s +golang.org/x/crypto/blowfish +golang.org/x/crypto/chacha20 +golang.org/x/crypto/curve25519 +golang.org/x/crypto/ed25519 +golang.org/x/crypto/internal/alias +golang.org/x/crypto/internal/poly1305 +golang.org/x/crypto/ripemd160 +golang.org/x/crypto/sha3 +golang.org/x/crypto/ssh +golang.org/x/crypto/ssh/agent +golang.org/x/crypto/ssh/internal/bcrypt_pbkdf +# golang.org/x/sys v0.43.0 +## explicit; go 1.25.0 +golang.org/x/sys/cpu # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3