diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml
new file mode 100644
index 0000000000..a40e424966
--- /dev/null
+++ b/.github/workflows/build-macos.yml
@@ -0,0 +1,54 @@
+name: Build macOS
+on:
+ workflow_dispatch:
+
+env:
+ GO_VERSION: "1.26.2"
+ NODE_VERSION: 22
+ NODE_OPTIONS: --max-old-space-size=4096
+
+jobs:
+ build:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Install Go
+ run: |
+ curl -sL "https://go.dev/dl/go${{ env.GO_VERSION }}.darwin-arm64.tar.gz" | tar -xzf - -C .
+ mv go golang-${{ env.GO_VERSION }}
+ echo "module golang" > golang-${{ env.GO_VERSION }}/go.mod
+
+ - name: Install Zig
+ run: |
+ curl -sL "https://ziglang.org/download/0.14.0/zig-macos-aarch64-0.14.0.tar.xz" | tar -xJf -
+ mv zig-macos-aarch64-0.14.0 zig-0.14.0
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: npm
+ cache-dependency-path: package-lock.json
+
+ - name: Install Task
+ uses: arduino/setup-task@v2
+ with:
+ version: 3.x
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install deps
+ run: npm ci --no-audit --no-fund
+ env:
+ GIT_ASKPASS: "echo"
+ GIT_TERMINAL_PROMPT: "0"
+
+ - name: Build
+ run: task package
+ env:
+ USE_SYSTEM_FPM: true
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v5
+ with:
+ name: macos-build
+ path: make/
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 30a8979b9b..9adf8336d1 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -12,25 +12,7 @@
name: "CodeQL"
on:
- push:
- branches: ["main"]
- paths:
- - "**/*.go"
- - "**/*.ts"
- - "**/*.tsx"
- pull_request:
- branches: ["main"]
- paths:
- - "**/*.go"
- - "**/*.ts"
- - "**/*.tsx"
- types:
- - opened
- - synchronize
- - reopened
- - ready_for_review
- schedule:
- - cron: "36 5 * * 5"
+ workflow_dispatch:
env:
NODE_VERSION: 22
diff --git a/.github/workflows/deploy-docsite.yml b/.github/workflows/deploy-docsite.yml
deleted file mode 100644
index 092b024cb5..0000000000
--- a/.github/workflows/deploy-docsite.yml
+++ /dev/null
@@ -1,80 +0,0 @@
-name: Docsite CI/CD
-
-run-name: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'Build and Deploy' || 'Test Build' }} Docsite
-
-env:
- NODE_VERSION: 22
-
-on:
- push:
- branches:
- - main
- workflow_dispatch:
- # Also run any time a PR is opened targeting the docs
- pull_request:
- branches:
- - main
- types:
- - opened
- - synchronize
- - reopened
- - ready_for_review
- paths:
- - "docs/**"
- - ".github/workflows/deploy-docsite.yml"
- - "Taskfile.yml"
-
-jobs:
- build:
- name: Build Docsite
- runs-on: ubuntu-latest
- if: github.event.pull_request.draft == false
- steps:
- - uses: actions/checkout@v6
- with:
- fetch-depth: 0
- - uses: actions/setup-node@v6
- with:
- node-version: ${{env.NODE_VERSION}}
- cache: npm
- cache-dependency-path: package-lock.json
- - name: Install Task
- uses: arduino/setup-task@v2
- with:
- version: 3.x
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- - uses: nick-fields/retry@v4
- name: npm ci
- with:
- command: npm ci --no-audit --no-fund
- retry_on: error
- max_attempts: 3
- timeout_minutes: 5
- - name: Build docsite
- run: task docsite:build:public
- - name: Upload Build Artifact
- # Only upload the build artifact when pushed to the main branch
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- uses: actions/upload-pages-artifact@v4
- with:
- path: docs/build
- deploy:
- name: Deploy to GitHub Pages
- # Only deploy when pushed to the main branch
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- needs: build
- # Grant GITHUB_TOKEN the permissions required to make a Pages deployment
- permissions:
- pages: write # to deploy to Pages
- id-token: write # to verify the deployment originates from an appropriate source
-
- # Deploy to the github-pages environment
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
-
- runs-on: ubuntu-latest
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v5
diff --git a/.github/workflows/testdriver-build.yml b/.github/workflows/testdriver-build.yml
deleted file mode 100644
index da190073e6..0000000000
--- a/.github/workflows/testdriver-build.yml
+++ /dev/null
@@ -1,83 +0,0 @@
-name: TestDriver.ai Build
-
-on:
- push:
- branches:
- - main
- tags:
- - "v[0-9]+.[0-9]+.[0-9]+*"
- pull_request:
- # branches:
- # - main
- # paths-ignore:
- # - "docs/**"
- # - ".storybook/**"
- # - ".vscode/**"
- # - ".editorconfig"
- # - ".gitignore"
- # - ".prettierrc"
- # - ".eslintrc.js"
- # - "**/*.md"
- types:
- - opened
- - synchronize
- - reopened
- - ready_for_review
- schedule:
- - cron: 0 21 * * *
- workflow_dispatch: null
-
-env:
- GO_VERSION: "1.25.6"
- NODE_VERSION: 22
-
-permissions:
- contents: read # To allow the action to read repository contents
- pull-requests: write # To allow the action to create/update pull request comments
-
-jobs:
- build_and_upload:
- name: Build for TestDriver.ai
- runs-on: windows-latest
- if: github.event.pull_request.draft == false
- steps:
- - uses: actions/checkout@v6
-
- # General build dependencies
- - uses: actions/setup-go@v6
- with:
- go-version: ${{env.GO_VERSION}}
- - uses: actions/setup-node@v6
- with:
- node-version: ${{env.NODE_VERSION}}
- cache: npm
- cache-dependency-path: package-lock.json
- - uses: nick-fields/retry@v4
- name: npm ci
- with:
- command: npm ci --no-audit --no-fund
- retry_on: error
- max_attempts: 3
- timeout_minutes: 5
- - name: Install Task
- uses: arduino/setup-task@v2
- with:
- version: 3.x
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- - name: Install Zig
- uses: mlugg/setup-zig@v2
-
- - name: Build
- run: task package
- env:
- USE_SYSTEM_FPM: true # Ensure that the installed version of FPM is used rather than the bundled one.
- CSC_IDENTITY_AUTO_DISCOVERY: false # disable codesign
- shell: powershell # electron-builder's Windows code signing package has some compatibility issues with pwsh, so we need to use Windows Powershell
-
- # Upload .exe as an artifact
- - name: Upload .exe artifact
- id: upload
- uses: actions/upload-artifact@v5
- with:
- name: windows-exe
- path: make/*.exe
diff --git a/.github/workflows/testdriver.yml b/.github/workflows/testdriver.yml
deleted file mode 100644
index 9d51ec7659..0000000000
--- a/.github/workflows/testdriver.yml
+++ /dev/null
@@ -1,141 +0,0 @@
-name: TestDriver.ai Run
-
-on:
- workflow_run:
- workflows: ["TestDriver.ai Build"]
- types:
- - completed
-
-env:
- GO_VERSION: "1.25.6"
- NODE_VERSION: 22
-
-permissions:
- contents: read
- statuses: write
-
-jobs:
- context:
- runs-on: ubuntu-22.04
- steps:
- - name: Dump GitHub context
- env:
- GITHUB_CONTEXT: ${{ toJson(github) }}
- run: echo "$GITHUB_CONTEXT"
- - name: Dump job context
- env:
- JOB_CONTEXT: ${{ toJson(job) }}
- run: echo "$JOB_CONTEXT"
- - name: Dump steps context
- env:
- STEPS_CONTEXT: ${{ toJson(steps) }}
- run: echo "$STEPS_CONTEXT"
- - name: Dump runner context
- env:
- RUNNER_CONTEXT: ${{ toJson(runner) }}
- run: echo "$RUNNER_CONTEXT"
- - name: Dump strategy context
- env:
- STRATEGY_CONTEXT: ${{ toJson(strategy) }}
- run: echo "$STRATEGY_CONTEXT"
- - name: Dump matrix context
- env:
- MATRIX_CONTEXT: ${{ toJson(matrix) }}
- run: echo "$MATRIX_CONTEXT"
- run_testdriver:
- name: Run TestDriver.ai
- runs-on: windows-latest
- if: github.event.workflow_run.conclusion == 'success'
- steps:
- - uses: testdriverai/action@main
- id: testdriver
- env:
- FORCE_COLOR: "3"
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- key: ${{ secrets.DASHCAM_API }}
- prerun: |
- $headers = @{
- Authorization = "token ${{ secrets.GITHUB_TOKEN }}"
- }
-
- $downloadFolder = "./download"
- $artifactFileName = "waveterm.exe"
- $artifactFilePath = "$downloadFolder/$artifactFileName"
-
- Write-Host "Starting the artifact download process..."
-
- # Create the download directory if it doesn't exist
- if (-not (Test-Path -Path $downloadFolder)) {
- Write-Host "Creating download folder..."
- mkdir $downloadFolder
- } else {
- Write-Host "Download folder already exists."
- }
-
- # Fetch the artifact upload URL
- Write-Host "Fetching the artifact upload URL..."
- $artifactUrl = (Invoke-RestMethod -Uri "https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/artifacts" -Headers $headers).artifacts[0].archive_download_url
-
- if ($artifactUrl) {
- Write-Host "Artifact URL successfully fetched: $artifactUrl"
- } else {
- Write-Error "Failed to fetch the artifact URL."
- exit 1
- }
-
- # Download the artifact (zipped file)
- Write-Host "Starting artifact download..."
- $artifactZipPath = "$env:TEMP\artifact.zip"
- try {
- Invoke-WebRequest -Uri $artifactUrl `
- -Headers $headers `
- -OutFile $artifactZipPath `
- -MaximumRedirection 5
-
- Write-Host "Artifact downloaded successfully to $artifactZipPath"
- } catch {
- Write-Error "Error downloading artifact: $_"
- exit 1
- }
-
- # Unzip the artifact
- $artifactUnzipPath = "$env:TEMP\artifact"
- Write-Host "Unzipping the artifact to $artifactUnzipPath..."
- try {
- Expand-Archive -Path $artifactZipPath -DestinationPath $artifactUnzipPath -Force
- Write-Host "Artifact unzipped successfully to $artifactUnzipPath"
- } catch {
- Write-Error "Failed to unzip the artifact: $_"
- exit 1
- }
-
- # Find the installer or app executable
- $artifactInstallerPath = Get-ChildItem -Path $artifactUnzipPath -Filter *.exe -Recurse | Select-Object -First 1
-
- if ($artifactInstallerPath) {
- Write-Host "Executable file found: $($artifactInstallerPath.FullName)"
- } else {
- Write-Error "Executable file not found. Exiting."
- exit 1
- }
-
- # Run the installer and log the result
- Write-Host "Running the installer: $($artifactInstallerPath.FullName)..."
- try {
- Start-Process -FilePath $artifactInstallerPath.FullName -Wait
- Write-Host "Installer ran successfully."
- } catch {
- Write-Error "Failed to run the installer: $_"
- exit 1
- }
-
- # Optional: If the app executable is different from the installer, find and launch it
- $wavePath = Join-Path $env:USERPROFILE "AppData\Local\Programs\waveterm\Wave.exe"
-
- Write-Host "Launching the application: $($wavePath)"
- Start-Process -FilePath $wavePath
- Write-Host "Application launched."
-
- prompt: |
- 1. /run testdriver/onboarding.yml
diff --git a/.gitignore b/.gitignore
index 7bd717e540..e9327e60dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,6 @@ docsite/
.superpowers
docs/superpowers
.claude
+golang-*/
+zig-*/
+.pi/delegate-results/
diff --git a/.pi/context.md b/.pi/context.md
new file mode 100644
index 0000000000..c0f8aa764a
--- /dev/null
+++ b/.pi/context.md
@@ -0,0 +1,50 @@
+# Project Context
+
+## Problem Statement
+
+Most modern terminals and developer tools assume a **local-first workflow**:
+- Code lives on the local machine
+- Build tools run locally
+- Terminal access is to local shell or occasional remote SSH sessions
+- AI assistants analyze local files and local terminal output
+
+This doesn't match how many developers actually work:
+- Code lives on remote servers, cloud VMs, or containers
+- Builds happen remotely (CI/CD, remote compile farms)
+- The developer's machine is primarily a thin client
+- Network connectivity is the primary bottleneck, not local CPU
+
+## What This Fork Targets
+
+A terminal where **remote is the default**, not an afterthought:
+- SSH connections are first-class, not a plugin
+- Port forwarding is automatic from SSH config
+- File editing on remote machines feels as seamless as local
+- Durable sessions survive network interruptions gracefully
+- The terminal understands remote context (which host, which directory, which project)
+- Local resources (AI, file previews) enhance remote work rather than competing with it
+
+## What's Different from Upstream
+
+Wave Terminal already has excellent SSH and durable session support. This fork will:
+
+| Area | Upstream Wave | This Fork |
+|------|---------------|-----------|
+| **Port forwarding** | Not supported from SSH config | Automatic from `~/.ssh/config` |
+| **Local-first features** | AI, widgets, file previews for local | Evaluate which to keep/diminish |
+| **Remote context** | Basic | Potentially enhanced (host-aware prompts, etc.) |
+| **UI chrome** | Full Wave branding/chrome | Potentially stripped for remote-dev focus |
+
+## Non-Goals
+
+- Rebuild from scratch — this is a fork, not a rewrite
+- Remove all local features indiscriminately — evaluate usefulness
+- Compete with upstream — this is a specialized variant
+
+## Target User
+
+Developers who:
+- Spend >50% of terminal time in SSH sessions
+- Have multiple persistent remote development environments
+- Use `~/.ssh/config` extensively for host/forwarding configuration
+- Want a terminal that treats remote machines as "primary" workspaces
diff --git a/.pi/decisions.md b/.pi/decisions.md
new file mode 100644
index 0000000000..ece40128de
--- /dev/null
+++ b/.pi/decisions.md
@@ -0,0 +1,189 @@
+# Architecture Decisions
+
+## 2026-05-10: Fork Purpose
+
+**Decision:** Fork Wave Terminal to create a remote-development-optimized variant.
+
+**Context:** Most terminals assume local-first workflows. This fork treats remote SSH environments as primary workspaces.
+
+**Consequences:**
+- Upstream remains the base; we merge regularly
+- Features evaluated against "remote-first" usefulness
+- Local-first features may be removed/diminished if they conflict with remote workflow
+
+## 2026-05-10: `.pi/` as Planning Hub
+
+**Decision:** Use `.pi/` directory for all fork planning, specs, and agent context.
+
+**Context:** Keeps planning centralized and agent-accessible without cluttering the root or public docs.
+
+**Files:**
+- `.pi/index.md` — entry point
+- `.pi/context.md` — project background
+- `.pi/todos.md` — active tasks
+- `.pi/decisions.md` — this file
+- `.pi/specs/` — feature specifications
+
+## 2026-05-10: Port Forwarding — Config-First Approach
+
+**Decision:** Implement `LocalForward`/`RemoteForward` from `~/.ssh/config` and `connections.json`, not CLI flags.
+
+**Context:** SSH config is the standard place developers already define forwarding rules. Making Wave respect them is the least-surprise approach.
+
+**Approach:**
+1. Parse `LocalForward`/`RemoteForward` in `findSshConfigKeywords()`
+2. Add to `ConnKeywords` struct
+3. Return merged keywords from `ConnectToClient()`
+4. Start forwarding goroutines in `SSHConn.connectInternal()`
+5. Clean up listeners in `closeInternal_withlifecyclelock()`
+
+**Deferred:**
+- `DynamicForward` (needs SOCKS5 handler)
+- CLI flags on `wsh ssh` (can add later)
+- UI status indicator
+
+## 2026-05-14: Tab-Close Crash — Root Cause Found & Fixed
+
+**Decision:** Remove redundant `DestroyBlockController` goroutine from `CloseTab`; add `sync.Once` to `ShellProc.Close()` as defense-in-depth.
+
+**Context:** Investigation confirmed a race where `CloseTab` explicitly launched `DestroyBlockController` in a goroutine while `DeleteTab` → `DeleteBlock` → `BlockCloseEvent` triggered the same destruction again. This caused concurrent double-`Stop` on `ShellController` (with its Lock/Unlock/Relock window) and `DurableShellController` (which has no lock), leading to double `Session.Close()` / double `TerminateAndDetachJob`.
+
+**Fix applied:**
+1. `pkg/service/workspaceservice/workspaceservice.go` — removed the explicit `go DestroyBlockController()` loop; `DeleteTab` already triggers cleanup via events.
+2. `pkg/shellexec/shellexec.go` — added `closeOnce sync.Once` to `ShellProc` and wrapped `Close()` in `sp.closeOnce.Do`, preventing double `KillGraceful` / double goroutine spawn even if two Stops race.
+3. Added trace logging to `CloseTab`, `DestroyBlockController`, `ShellController.Stop`, `DurableShellController.Stop`, `handleBlockCloseEvent` for interactive diagnosis.
+4. Fixed 2 test-code panics (manual `close` of channel already closed by mock `KillGraceful`).
+
+**Consequences:**
+- `CloseTab` now has a single cleanup path: `DeleteTab` → `DeleteBlock` → event → `DestroyBlockController`
+- `ShellProc.Close()` is idempotent; any future code path that calls it twice is safe
+- 14 unit tests pass under `-race`
+
+## 2026-05-12: Secret Store — Keep
+
+**Decision:** Keep the secret store infrastructure; it's not AI-specific.
+
+**Context:** The secret store (`pkg/secretstore/`) is an encrypted key-value store backed by the OS keychain. It has three consumers:
+1. **AI API tokens** (`ai:apitokensecretname`) — going away with AI removal
+2. **SSH password auth** (`ssh:passwordsecretname`) — stays, useful for password-authenticated hosts
+3. **Wave App Store** — stays, general-purpose
+
+**Consequences:**
+- Remove `ai:apitokensecretname` field from `ConnKeywords` as part of AI cleanup
+- Keep `pkg/secretstore/`, `wsh secret` CLI, and `ssh:passwordsecretname` intact
+- Lightweight general infrastructure; useful for future features (e.g., file transfer credentials)
+
+## 2026-05-15: Claude Code Shell Integration — Analysis for Future Pi Agent Support
+
+**Finding:** Wave Terminal's Claude Code detection is built on top of a generic **shell integration protocol** (OSC 16162) that could be reused for pi coding agent support.
+
+### How Claude Code Integration Works
+
+| Layer | What it does | Relevant file |
+|-------|-------------|---------------|
+| **Shell integration protocol** | Custom OSC 16162 sequences injected into shell prompt. Sends command-start (`C`), command-done (`D`), shell-ready (`M`) events via base64-encoded payloads. | `frontend/app/view/term/osc-handlers.ts` |
+| **Command detection** | `isClaudeCodeCommand(decodedCmd)` checks if normalized command matches `/^claude\b/`. Also detects `opencode` with similar regex. | `frontend/app/view/term/osc-handlers.ts` |
+| **State atoms** | `shellIntegrationStatusAtom` (`"ready" \| "running-command" \| null`) and `claudeCodeActiveAtom` (`boolean`) track terminal state per block. | `frontend/app/view/term/termwrap.ts` |
+| **Visual indicator** | `getShellIntegrationIconButton()` in `term-model.ts` reads atoms and renders either generic sparkle icon or `TermClaudeIcon` (Anthropic SVG logo) with status tooltip. | `frontend/app/view/term/term-model.ts` |
+| **Telemetry gate** | `checkCommandForTelemetry()` filters out `ssh`, editors (`vim/nano/nvim`), `tail -f`, `claude`, and `opencode` from AI telemetry. | `frontend/app/view/term/osc-handlers.ts` |
+
+### What Was Removed Today
+
+- Sparkle icon + Claude logo from terminal block header (`getShellIntegrationIconButton` now returns `null`)
+- All tooltips referencing "Wave AI can run commands"
+- The `TermClaudeIcon` import from `term-model.ts`
+
+### What Remains (Dead Code, Phase D Cleanup)
+
+- `claudeCodeActiveAtom` in `termwrap.ts` — still set by OSC handlers, never read
+- `shellIntegrationStatusAtom` in `termwrap.ts` — still set by OSC handlers, never read
+- `isClaudeCodeCommand()` and `ClaudeCodeRegex` in `osc-handlers.ts` — still execute, results unused
+- `TermClaudeIcon` component in `term.tsx` — still exported, never imported
+- `checkCommandForTelemetry()` in `osc-handlers.ts` — still runs, telemetry already removed
+
+### Reuse Potential for Pi Coding Agent
+
+**The shell integration protocol itself is valuable** — it gives the terminal real-time awareness of:
+- When a command starts / finishes
+- What the command line is
+- Exit codes
+- Shell type and version
+- Whether the terminal is in an alternate buffer (e.g., `vim`, `less`)
+
+**For pi integration, we could:**
+1. Reuse the same OSC 16162 injection into `.bashrc`/`.zshrc`
+2. Add a `piActiveAtom` alongside `claudeCodeActiveAtom` with a `/^pi\b/` regex
+3. Show a pi icon in the terminal header when pi is the active command
+4. Use command-start/finish events to show "pi is running" status in the UI
+5. Use the alternate-buffer detection (`getBlockingCommand`) to suppress pi actions while inside `vim`/`less`/`ssh`
+
+**Key insight:** The protocol is generic AI-agent-agnostic infrastructure. The Claude-specific parts are just a regex (`/^claude\b/`) and an SVG icon. Replacing them with pi equivalents would be trivial if we want this later.
+
+**Decision:** Keep the underlying OSC 16162 shell integration infrastructure intact for now. Only the visual indicator (sparkle/Claude icon) and Wave-AI-specific tooltips were removed. If we want pi agent integration later, we can add `piActiveAtom` and a pi icon with minimal changes.
+
+## 2026-05-20: MOSH Research — Not a Priority
+
+**Finding:** MOSH (Mobile Shell) provides seamless reconnection (roaming, sleep/wake) and client-side local echo via UDP-based State Synchronization Protocol. However, it's not a priority for this fork.
+
+**Why not:**
+- **No port forwarding** — open issue since 2014, no movement. Port forwarding is a core requirement.
+- **No OSC52 clipboard** — remote programs can't put text in local clipboard.
+- **No scrollback** — only syncs visible terminal state.
+- **No file transfer** (scp/sftp).
+- **C++ only** — no Go or JS library implementations of the core protocol.
+- **Slow development** — last release 1.4.0 (October 2022).
+
+**Alternative: tsshd (trzsz-ssh)** — Go-based, supports full SSH features (port forwarding, agent forwarding, X11, scrollback, OSC52) + UDP roaming via QUIC/KCP. More architecturally relevant but would require significant integration effort.
+
+**Local echo with wsh** — Technically possible (Wave Terminal already knows screen state and intercepts keystrokes), but non-trivial (must detect line-editing vs application mode, validate predictions against round-trip timing). Low value for typical homelab latency (<50ms).
+
+## 2026-05-23: Auto-Reconnect P0 Fixed; Server Reboot → Manual Reconnect
+
+**Decision:** After fixing the three P0 auto-reconnect bugs (cooldown race, reconcile race, singleflight deduplication), we explicitly chose **NOT** to implement auto-restart of fresh shells on server reboot or `wsh` death.
+
+**Why manual reconnect:**
+- Auto-restart would change durable-session semantics from *"resume my existing remote shell"* to *"keep a shell open at all costs."*
+- Context loss (cwd, env, running processes) is confusing for users who think their old session survived.
+- Risk of `wsh` re-install loops after server reboot.
+- Cleaner to let the user explicitly click Connect and know it's a fresh session.
+
+**What we did:**
+- `ReconnectJob` now correctly detects `JobManagerGone` and marks the job done.
+- User sees `[session gone]` in the terminal and clicks Connect to start fresh.
+
+**Future direction (Jeremy's idea):** Tmux auto-restore on reconnect — instead of restarting raw shells, recreate tmux sessions/layouts after server reboot. This preserves tmux's own session persistence while giving WaveTerm visibility into the sessions.
+
+---
+
+## 2026-06-01: CPU Spin Bug — Root Cause & Fix Strategy
+
+**Decision:** Fix the `x/crypto/ssh` drain loop bug locally via `go.mod` replace directive, not by reordering cleanup in waveterm.
+
+**Root cause:** `golang.org/x/crypto@v0.52.0` `ssh/mux.go` and `ssh/channel.go` have drain loops that spin forever when `globalResponses`/`ch.msg` channels are closed. Receiving from a closed channel always succeeds immediately (returns zero value), so `default` case is never reached. Tracked as [golang/go#79658](https://github.com/golang/go/issues/79658).
+
+**Upstream fixes:** Commits 4c4d20b (mux.go) and e3e62d9 (channel.go) on May 27, 2026. Not yet in a tagged release (awaiting v0.53.0).
+
+**Why the reorder workaround (issue #22 commit eb2c659a) was rolled back:**
+- Only addressed the cleanup goroutine path, not keepalive monitors or `mux.loop()` exiting independently
+- Wake-from-sleep pprof showed 37 spinning goroutines + 37 blocked on Mutex.Lock — reorder can't prevent all
+- Original close order (client first) is correct: force-closes transport, unblocking pending `writePacket` calls
+- With the mux patch, drain loops exit immediately on closed channels regardless of call order
+
+**Implementation:**
+- `local_crypto_patch/contents/` — local copy of `x/crypto v0.52.0` with the 2-line drain loop fix applied
+- `go.mod` replace directive: `replace golang.org/x/crypto v0.52.0 => ./local_crypto_patch/contents`
+- Rollback plan: when `x/crypto >= v0.53.0` released, remove replace, delete `local_crypto_patch/`, `go mod tidy`
+
+**Consequences:**
+- 100% CPU (wifi switch) and 900% CPU (wake from sleep) bugs both resolved
+- No additional goroutines or timeouts needed in cleanup path
+- Original close order restored (client first, then listener)
+
+---
+
+**Priority order:**
+1. Fix auto-reconnect bugs in durable sessions (#4) — DONE 2026-05-23
+2. SSH port forwarding (spec ready)
+3. Remote file paste (image paste + drag-drop for SSH sessions) — primary use case: pi / Claude Code TUI
+4. MOSH/tsshd support (backlog, if roaming becomes a real pain point)
+
diff --git a/.pi/draft-issue-autoconnect-bugs.md b/.pi/draft-issue-autoconnect-bugs.md
new file mode 100644
index 0000000000..ed0ee8f3ff
--- /dev/null
+++ b/.pi/draft-issue-autoconnect-bugs.md
@@ -0,0 +1,152 @@
+# Bug: Durable session auto-reconnect unreliable — cooldown set before connection check, connStates race condition
+
+## Symptom
+
+Durable SSH sessions sometimes stay disconnected after the SSH connection is restored, while other times they auto-reconnect correctly. The behavior is inconsistent.
+
+## Root Cause Analysis
+
+The auto-reconnect system has two paths:
+
+1. **Route-level** (`Event_RouteDown` on `job:`): Fires when the job stream drops. Triggers `attemptAutoReconnect` after a 1s delay, with a 30s cooldown per job.
+2. **Connection-level** (`Event_ConnChange`): Fires when the SSH connection state changes. When connection comes up, `onConnectionUp` reconnects all running jobs on that connection.
+
+Both paths converge on `ReconnectJob()`. Neither is specific to durable vs standard — but **only durable sessions create jobs**, so only durable sessions have auto-reconnect. Standard sessions always require manual reconnect.
+
+## Bug #1: Route-level cooldown fires before connection check (High impact)
+
+In `pkg/jobcontroller/jobcontroller.go`:
+
+```go
+func handleRouteEvent(event *wps.WaveEvent, newStatus string) {
+ // ...
+ if shouldAttemptAutoReconnect(jobId) { // sets cooldown timestamp HERE
+ go attemptAutoReconnect(jobId, job.Connection)
+ }
+}
+
+func attemptAutoReconnect(jobId string, connName string) {
+ time.Sleep(AutoReconnectDelay) // 1s delay
+ isConnected, err := conncontroller.IsConnected(connName)
+ if err != nil || !isConnected {
+ return // cooldown already set, but reconnect never happened
+ }
+ // ... actual reconnect
+}
+```
+
+The 30s cooldown (`lastAutoReconnectAttempt.Set(jobId, now)`) is set in `shouldAttemptAutoReconnect` **before** `attemptAutoReconnect` checks if the connection is actually up. If the connection is down, the reconnect is skipped but the cooldown has already been consumed.
+
+**Scenario**: SSH drops → job route goes down → cooldown set → 1s delay → connection still down → skip. Connection comes back 5s later → `onConnectionUp` tries `ReconnectJob` → may hit singleflight cache or other timing issues → job stays disconnected.
+
+**Fix**: Move `lastAutoReconnectAttempt.Set(jobId, time.Now().Unix())` into `attemptAutoReconnect`, only after the connection check passes.
+
+## Bug #2: connStates reconciliation race with buffered channel (Medium impact)
+
+In `reconcileAllConns()` / `reconcileConn()`:
+
+- `reconcileAllConns` scans `connStates.m`, sets `cs.reconciling = true`, spawns `go reconcileConn()`, releases lock
+- `reconcileConn` does the work, then sets `cs.processed = targetState` and signals `reconcileCh` (buffer size 1)
+- If `Event_ConnChange` fires rapidly (e.g., `disconnected` → `connected` → `disconnected`), `cs.actual` is updated by the event handler while the worker is mid-processing
+- After worker sets `cs.processed`, it may match the new `cs.actual` — causing the next reconcile pass to skip with `cs.actual == cs.processed`
+- The buffered `reconcileCh` (size 1) can also drop signals if multiple events fire before the worker drains it
+
+**Scenario**: Connection flaps quickly — reconcile worker misses a state transition — `onConnectionUp` never fires — jobs never reconnect.
+
+**Fix**: Pass the target state to the goroutine (already done), and have the goroutine do a fresh reconcile check after completing, rather than relying on the buffered channel signal. Or use an unbuffered channel with a dedicated worker loop.
+
+## Bug #3: singleflight in ReconnectJob can cache transient failures (Low impact, timing-dependent)
+
+`ReconnectJob` uses `reconnectGroup.Do(jobId, ...)` (singleflight). If route-level `attemptAutoReconnect` calls `ReconnectJob` concurrently with connection-level `onConnectionUp` calling `ReconnectJob` for the same jobId, they share the same result. If the route-level call runs first and fails (connection down), the connection-level call gets the cached failure.
+
+This only affects very tight timing windows (< 10s context timeout), but is worth noting.
+
+## Files involved
+
+| File | Relevant functions |
+|------|--------------------|
+| `pkg/jobcontroller/jobcontroller.go` | `handleRouteEvent`, `shouldAttemptAutoReconnect`, `attemptAutoReconnect`, `reconcileAllConns`, `reconcileConn`, `onConnectionUp`, `ReconnectJob` |
+| `pkg/remote/conncontroller/conncontroller.go` | `waitForDisconnect`, `Connect`, `FireConnChangeEvent` |
+
+## Missing Detection Mechanisms
+
+Beyond the bugs above, several reconnection triggers are missing entirely:
+
+### Missing #1: System sleep/wake does nothing
+
+`emain/emain.ts` listens for `powerMonitor.on("resume")` and calls `NotifySystemResumeCommand`, which is a **stub that just logs and returns nil**. No reconnect is attempted.
+
+**Fix**: Have `NotifySystemResumeCommand` trigger reconnect for all disconnected durable jobs.
+
+### Missing #2: No network-online detection
+
+There is no monitoring of network connectivity state. The system relies entirely on TCP-level failure detection (SSH connection drops), which can be slow:
+- TCP keepalive may not be enabled or may have very long timeouts
+- Silent packet loss (asymmetric routing, firewall drop) may not trigger TCP timeout for minutes
+- Network interface comes back up but no event triggers reconnect attempt
+
+**Fix**: Add periodic network-online check (e.g., every 30s) when durable jobs are in disconnected state. When network comes back up, trigger reconnect attempt.
+
+### Missing #3: No SSH/TCP keepalive configuration
+
+SSH connections may not have aggressive keepalive settings, meaning a "zombie" connection (network dropped but TCP hasn't detected it) can persist for a long time. The connection appears "up" in `connStates` but is actually dead.
+
+**Fix**: Configure `ClientAliveInterval` and `ClientAliveCountMax` on SSH connections to detect dead connections faster.
+
+## Edge Cases
+
+| Case | Scenario | Current behavior | Desired behavior |
+|------|----------|------------------|------------------|
+| **Job manager died** | `wsh jobmanager` process crashed on remote | Reconnect attempts fail repeatedly | Detect this case, mark job as "dead" instead of retrying |
+| **User manually disconnects** | Click "Disconnect" in UI | May trigger auto-reconnect | Respect explicit disconnect vs network failure |
+| **Multiple jobs, one connection** | SSH drops, 5 durable jobs on that host | Connection-level reconnect handles, but timing matters | Reconnect jobs in parallel after SSH is back, per-job backoff |
+| **Reconnect during active typing** | User typing when network drops, comes back | Keystrokes lost, terminal may be inconsistent | Buffer keystrokes or show clear "reconnecting" state |
+| **Connection flapping** | Network rapidly up/down | Each flap triggers reconnect, may hit cooldown | Exponential backoff with jitter |
+
+## Priority
+
+**P0 — Fix existing bugs:**
+- Bug #1: Cooldown consumed before connection check (High impact)
+- Bug #2: Channel buffer drops rapid state changes (Medium impact)
+- Bug #3: singleflight caches transient failures (Low impact, timing-dependent)
+
+**P1 — Add missing detection:**
+- Wire up `NotifySystemResumeCommand` to trigger reconnect on system wake
+- Add network-online polling when jobs are disconnected
+- Enable SSH keepalive on connections for faster dead-connection detection
+
+**P2 — Edge cases:**
+- Detect job manager death vs route drop
+- Respect manual disconnect (don't auto-reconnect)
+- Reconnect state indicator in UI
+
+### UX Enhancement: Tab-focus triggers reconnect
+
+**Problem:** When a durable session is disconnected, the user sees a disconnected block with a connect button. But if they switch to another tab and back, they expect the session to be ready — instead they have to manually click connect.
+
+**Proposal:** When a tab gains focus, scan all blocks in that tab. For any terminal block with a disconnected durable session, automatically trigger a reconnect attempt for that connection.
+
+**Why tab, not block:** Blocks already have a connect button. Tab focus is the "I want to work in this context" gesture and may cover multiple blocks/connections at once.
+
+**Implementation:**
+- Frontend: in tab focus handler, scan tab's blocks → find disconnected durable terminals → call `ReconnectConnection` RPC for each unique connection
+- Backend: new `ReconnectConnectionCommand` that triggers existing connection-level reconnect logic
+- Guardrails: respect cooldown, durable sessions only, respect manual disconnect
+- UI: show "Reconnecting..." in block headers during attempt
+
+**Effort:** ~150 lines (frontend focus handler + RPC call + UI indicator, backend RPC wrapper). Does not fix underlying bugs but improves UX significantly.
+
+## Files involved
+
+| File | Relevant functions |
+|------|--------------------|
+| `pkg/jobcontroller/jobcontroller.go` | `handleRouteEvent`, `shouldAttemptAutoReconnect`, `attemptAutoReconnect`, `reconcileAllConns`, `reconcileConn`, `onConnectionUp`, `ReconnectJob` |
+| `pkg/remote/conncontroller/conncontroller.go` | `waitForDisconnect`, `Connect`, `FireConnChangeEvent` |
+| `emain/emain.ts` | `powerMonitor.on("resume")` — currently calls stub |
+| `pkg/wshrpc/wshserver/wshserver.go` | `NotifySystemResumeCommand` — currently no-op |
+
+## Notes
+
+- These bugs affect **durable sessions only** — standard SSH sessions have no auto-reconnect machinery
+- The `DurableDetachedContent` flyover tells users "Wave will automatically reconnect when the connection is restored" — but this is unreliable due to these bugs
+- No existing tests cover `jobcontroller.go` reconnect paths
diff --git a/.pi/index.md b/.pi/index.md
new file mode 100644
index 0000000000..2d3e303ba5
--- /dev/null
+++ b/.pi/index.md
@@ -0,0 +1,28 @@
+# waveterm-remote Fork
+
+A fork of [Wave Terminal](https://github.com/wavetermdev/waveterm) optimized for **remote development workflows**.
+
+## Upstream
+
+- Original: `https://github.com/wavetermdev/waveterm`
+- This fork: `https://github.com/whoisjeremylam/waveterm-remote`
+- CWD origin points to this fork
+
+## Purpose
+
+Most developer terminals assume code is installed, built, and tested locally. This fork targets developers who primarily work on remote machines via SSH — with the local machine as a thin client.
+
+## Active Specs
+
+- [[specs/remove-telemetry.md]] — Remove all telemetry, analytics, and tracking
+- [[specs/remove-waveai.md]] — Remove/disable all Wave AI features
+- [[specs/portforwarding.md]] — SSH port forwarding (`LocalForward` / `RemoteForward`)
+
+## Context & Decisions
+
+- [[context.md]] — Full project background and goals
+- [[decisions.md]] — Architecture decisions (ADRs)
+
+## Tasks
+
+- [[todos.md]] — Active work and backlog
diff --git a/.pi/reviews/remove-telemetry-independent-review.md b/.pi/reviews/remove-telemetry-independent-review.md
new file mode 100644
index 0000000000..321157ae69
--- /dev/null
+++ b/.pi/reviews/remove-telemetry-independent-review.md
@@ -0,0 +1,263 @@
+# Independent Review: Remove Telemetry Spec
+
+**Date:** 2026-05-13
+**Spec reviewed:** `.pi/specs/remove-telemetry.md`
+**Method:** Full codebase audit against spec claims; did not read the prior review at `.pi/reviews/remove-telemetry-review.md`
+
+---
+
+## Summary
+
+The spec is well-structured with a sound phased approach (A→B→C→D) and a thorough file-by-file inventory for most Go backend files. However, it has **4 critical gaps** (entire subsystems unaddressed), **5 errors/omissions** in listed items, and **6 unintended side effects** that need mitigation. The most significant gap is the complete absence of the Electron main process (`emain/`) which contains the primary activity-tracking loop.
+
+---
+
+## 🔴 Critical Gaps — Items the Spec Misses Entirely
+
+### 1. `emain/emain.ts` — Electron main process telemetry
+
+The spec has **no section** for the Electron main process. `emain/emain.ts` contains the most important telemetry call sites:
+
+- **`sendDisplaysTDataEvent()`** (lines 135–162): Sends display info via `RpcApi.RecordTEventCommand` with event `"app:display"`.
+- **`logActiveState()`** (lines 168–230+): Core activity tracking loop. Calls `RpcApi.ActivityCommand` and `RpcApi.RecordTEventCommand` with `"app:activity"` events including foreground minutes, active minutes, terminal command counts, AI usage minutes, and display data.
+- **`emain-activity.ts`**: Module that collects `wasActive`, `wasInFg`, `termCommandsRun`, `termCommandsRemote`, `termCommandsWsl`, `termCommandsDurable`. All increment/export functions exist solely to feed telemetry.
+- **`emain-ipc.ts`**: IPC handler for `"increment-term-commands"` (lines 441–454) which routes to `emain-activity.ts`.
+- **`preload.ts`**: Exposes `incrementTermCommands` API to the renderer process.
+
+Without addressing these, telemetry removal is incomplete. The Electron main process is where periodic activity pings are orchestrated.
+
+**Recommendation:** Add new sections (B.7 for emain call sites, B.8 for emain-activity tracking module) covering:
+- Remove `sendDisplaysTDataEvent()` and `logActiveState()` from `emain.ts`
+- Remove all `RpcApi.RecordTEventCommand` and `RpcApi.ActivityCommand` calls from `emain.ts`
+- Remove the `TEventProps`/`ActivityUpdate` type imports
+- Remove `incrementTermCommands*` functions from `emain-activity.ts` (or make them no-ops)
+- Remove the `"increment-term-commands"` IPC handler from `emain-ipc.ts`
+- Remove `incrementTermCommands` from `preload.ts`
+- Remove `getActivityState`, `setWasActive`, `setWasInFg` if only used by telemetry (verify no other callers)
+
+### 2. `cmd/wsh/cmd/wshcmd-root.go` — `sendActivity()` and `activityWrap()`
+
+The spec does not mention the `wsh` CLI's activity tracking:
+
+- **`activityWrap()`** (line 106): A wrapper that calls `sendActivity` after every wsh command execution.
+- **`sendActivity()`** (line 221): Calls `wshclient.WshActivityCommand` to report which CLI command was run and whether it succeeded.
+- **`wshcmd-file.go`**: Every file subcommand (`file list`, `file cat`, `file info`, `file rm`, `file write`, `file append`, `file cp`, `file mv`) uses `activityWrap`.
+
+The spec mentions removing `WshActivityCommand` from the server/RPC layers (A.2/A.3/A.4) but never addresses the **callers**.
+
+**Recommendation:** Add new section A.16:
+- Remove `sendActivity()` function from `wshcmd-root.go`
+- Remove `activityWrap()` function from `wshcmd-root.go`
+- Remove `activityWrap` wrapping from all command `RunE` assignments in `wshcmd-file.go` (change `activityWrap("file", fileListRun)` to just `fileListRun`, etc.)
+- Remove the `sendActivity` comment block (lines 216–218)
+- Remove `wshclient` import if no other uses remain in `wshcmd-root.go`
+
+### 3. `cmd/generatego/main-generatego.go` — Code generator imports `telemetrydata`
+
+The Go code generator that produces `pkg/wshrpc/wshclient/wshclient.go` has a hardcoded import of `"github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"` (line 29). When Phase C deletes `pkg/telemetry/telemetrydata/`, this generator will fail to compile. The spec does not mention updating it.
+
+**Recommendation:** Add to Phase C.3 (or new C.4):
+- Remove `"github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"` from `cmd/generatego/main-generatego.go` imports
+- After removing telemetry RPC types from `wshrpctypes.go`, regenerate `wshclient.go` to remove the generated telemetry methods
+
+### 4. Auto-generated TypeScript files — manual edits will be overwritten
+
+`frontend/types/gotypes.d.ts`, `frontend/app/store/services.ts`, and `frontend/app/store/wshclientapi.ts` are **auto-generated** by `cmd/generatets/main-generatets.go`. They contain telemetry-related types and methods (`ActivityUpdate`, `TEvent`, `TEventProps`, `TEventUserProps`, `TelemetryUpdate`, etc.). The spec lists manual edits to these files (B.2, B.3), but since they're auto-generated, **manual edits will be overwritten** on the next build.
+
+**Recommendation:** Revise B.2/B.3 to note:
+- These files are auto-generated; do NOT edit them directly
+- Instead, remove the telemetry RPC methods/types from the Go source types that feed the generator:
+ - Remove `ActivityCommand`, `RecordTEventCommand`, `SendTelemetryCommand`, `WaveAIEnableTelemetryCommand`, `WshActivityCommand` from `pkg/wshrpc/wshrpctypes.go`
+ - Remove `TelemetryUpdate` from `pkg/service/clientservice/clientservice.go`
+ - Remove `ActivityUpdate` type from `pkg/wshrpc/wshrpctypes.go`
+ - Remove `TEvent`/`TEventProps`/`TEventUserProps` types from `pkg/telemetry/telemetrydata/telemetrydata.go` (or delete the package in Phase C)
+- After Go source changes, regenerate with `task dev` (or the appropriate generate task)
+- **Also** remove the manual call sites in frontend code that reference these generated types/methods (B.4 table entries)
+
+---
+
+## 🟡 Errors and Omissions
+
+### 5. `WshActivityCommand` missing from A.2, A.3, A.4
+
+The spec lists these methods to remove from wshserver/wshrpctypes/wshclient:
+- `ActivityCommand()`, `RecordTEventCommand()`, `SendTelemetryCommand()`, `WaveAIEnableTelemetryCommand()`
+
+But it **omits `WshActivityCommand()`**, which is a distinct RPC method (separate from `ActivityCommand`). `WshActivityCommand` takes `map[string]int` and is used by the `wsh` CLI's `sendActivity()` function. It exists in all three layers:
+- `wshserver.go` line 1316
+- `wshrpctypes.go` line 86
+- `wshclient.go` line 1051
+
+**Fix:** Add `WshActivityCommand()` to A.2, A.3, and A.4.
+
+### 6. Phase A.1 missing `diagnosticLoop()` and related constants
+
+The spec lists functions to remove from `main-server.go` but omits:
+- **`diagnosticLoop()`** (line 136) — the periodic diagnostic ping loop
+- **`InitialDiagnosticWait`** constant (line 65)
+- **`DiagnosticTick`** constant (line 66)
+- **`go diagnosticLoop()`** startup call (line 570)
+
+The spec mentions `wcloud.SendDiagnosticPing()` and `WAVETERM_NOPING` but not the loop that orchestrates them.
+
+**Fix:** Add to A.1:
+- Remove `diagnosticLoop()` function
+- Remove `InitialDiagnosticWait` and `DiagnosticTick` constants
+- Remove `go diagnosticLoop()` from startup sequence
+
+### 7. `frontend/app/store/keymodel.ts` — Missing call site
+
+The spec's table in B.4 does not include `keymodel.ts`, but it contains:
+- Import of `recordTEvent` (line 18)
+- `recordTEvent("action:other", { "action:type": "conndropdown", "action:initiator": "keyboard" })` (line 653)
+
+**Fix:** Add to B.4 table:
+| `frontend/app/store/keymodel.ts` | `recordTEvent` import and call (line 653) |
+
+### 8. `frontend/app/aipanel/telemetryrequired.tsx` — Dedicated telemetry consent component
+
+This is an entire React component (`TelemetryRequiredMessage`) that blocks AI panel usage until the user enables telemetry. It calls `RpcApi.WaveAIEnableTelemetryCommand`. The spec's B.6 delegates AI panel files to the AI removal spec, but this component is **about telemetry**, not AI — it's the telemetry consent gate for AI features. It must be addressed here or with an explicit cross-reference guaranteeing the AI spec handles it.
+
+**Fix:** Either:
+- Add explicit entry in B.6 noting that `telemetryrequired.tsx` must be removed or replaced with a non-telemetry gate when AI panel is modified, OR
+- Add it to Phase B with a note that it's co-owned with the AI removal spec
+
+### 9. Preview/test files not addressed
+
+- `frontend/preview/mock/mockfilesystem.ts` line 317: References `telemetry.log` in mock filesystem data.
+- `frontend/preview/previews/onboarding.preview.tsx` line 27: Passes `telemetryUpdateFn={async () => {}}` to `InitPage`.
+
+**Fix:** Add to B.4 or B.5:
+| `frontend/preview/mock/mockfilesystem.ts` | Remove `telemetry.log` mock entry |
+| `frontend/preview/previews/onboarding.preview.tsx` | Remove `telemetryUpdateFn` prop (after onboarding restructuring) |
+
+---
+
+## 🟠 Unintended Side Effects
+
+### 10. Onboarding flow will break without restructuring
+
+The current onboarding has a 3-page flow:
+```
+init → (telemetry enabled → features) | (telemetry disabled → notelemetrystar → features)
+```
+
+The `InitPage` component has:
+- A telemetry toggle checkbox
+- A `telemetryUpdateFn` prop that calls `TelemetryUpdate`
+- Logic in `acceptTos` that opens the AI panel only when `telemetryEnabled`
+- A `NoTelemetryStarPage` ("Telemetry Disabled ✓") dedicated page
+
+Simply removing telemetry calls without restructuring this UI will leave:
+- A non-functional checkbox
+- A dead `telemetryUpdateFn` callback
+- An `acceptTos` handler that conditionally opens AI panel based on a setting that no longer exists
+- A meaningless `NoTelemetryStarPage`
+
+**Recommendation:** Expand B.5 with a restructuring plan:
+- Remove the telemetry toggle from `InitPage`
+- Remove the `telemetryUpdateFn` prop
+- Remove `NoTelemetryStarPage` and the `"notelemetrystar"` page state
+- Simplify the flow to `init → features`
+- Move the GitHub star prompt to `InitPage` or `FeaturesPage` without the telemetry-disabled framing
+- Remove `telemetry:enabled` state reads (`useSettingsKeyAtom("telemetry:enabled")`)
+- In `acceptTos`, remove the `if (telemetryEnabled)` AI panel check (or hardcode it if AI spec removes the panel)
+
+### 11. `AgreeTos`/TOS flow dependency in onboarding
+
+`AgreeTos()` in `clientservice.go` sets `TosAgreed` timestamp. The onboarding `acceptTos` function calls `AgreeTos` then checks `telemetryEnabled`. The spec says to keep `TosAgreed` "for now" but doesn't address how the TOS acceptance flow works without the telemetry consent step.
+
+**Recommendation:** `AgreeTos` should remain (it's a TOS acceptance, not telemetry), but the onboarding `acceptTos` handler needs restructuring to decouple TOS from telemetry consent.
+
+### 12. PanicHandler rename direction is confusing
+
+The spec says "Rename `PanicHandlerNoTelemetry` to `PanicHandler`" and "Update all callers of `PanicHandlerNoTelemetry`". But:
+- `PanicHandlerNoTelemetry` is only called **within `pkg/telemetry/`** (2 calls in `telemetry.go`). After Phase C deletes that package, there are zero callers.
+- `PanicHandler` (the one that calls `PanicTelemetryHandler`) is called by ~80+ sites across the codebase.
+
+The practical approach is: keep the name `PanicHandler`, remove the `PanicTelemetryHandler` dispatch code, and make it behave like `PanicHandlerNoTelemetry`. Delete `PanicHandlerNoTelemetry` entirely since it becomes redundant. No callers outside the telemetry package need updating.
+
+**Recommendation:** Revise A.12 to:
+- Remove `PanicTelemetryHandler` variable and its `if` block from `PanicHandler`
+- Remove `PanicHandlerNoTelemetry` function entirely
+- Remove `panichandler.PanicTelemetryHandler = panicTelemetryHandler` from `main-server.go`
+- No caller renames needed
+
+### 13. `autoupdate:channel` and `autoupdate:enabled` entangled with telemetry config
+
+`CountCustomSettings()` in `settingsconfig.go` excludes both `telemetry:enabled` AND `autoupdate:channel` from counting as "custom settings" (lines 990–993). After removing `telemetry:enabled`:
+- The exclusion logic needs updating (remove the `telemetry:enabled` check)
+- The `autoupdate:channel` exclusion should remain (it's not telemetry-related)
+
+Also, `telemetry.AutoUpdateChannel()` and `telemetry.IsAutoUpdateEnabled()` are convenience functions in `pkg/telemetry/` that just read settings config. They're called in `main-server.go` only for telemetry event payloads (lines 316–317). But `autoupdate` settings are used by `emain/updater.ts` for genuine auto-update. These functions should either:
+- Be moved to `wconfig` or a utility package before Phase C, OR
+- Have their callers in `main-server.go` removed (since they're only used for telemetry payloads)
+
+**Recommendation:** Add to A.1: Remove `telemetry.AutoUpdateChannel()` and `telemetry.IsAutoUpdateEnabled()` calls from `main-server.go` (they're only used in `startupActivityUpdate`). Add to A.14: Update `CountCustomSettings` to remove the `telemetry:enabled` exclusion.
+
+### 14. `waveai-model.tsx`/`aimode.tsx`/`aipanel.tsx` — `telemetry:enabled` reads
+
+These files read `telemetry:enabled` to gate AI features:
+- `waveai-model.tsx` lines 144, 149: Returns `"invalid"` mode when telemetry disabled
+- `waveai-model.tsx` lines 422–423: Blocks cloud AI when telemetry disabled
+- `aimode.tsx` line 147: Reads `telemetry:enabled`
+- `aipanel.tsx` lines 238, 265: Reads `telemetry:enabled`
+
+If the AI removal spec removes the AI panel entirely, these references vanish. If it doesn't, they become dangling references to a deleted setting.
+
+**Recommendation:** Add cross-reference note: If AI panel is NOT fully removed by the AI spec, these `telemetry:enabled` reads must be replaced with either hardcoded `true` (always allow) or removed entirely.
+
+### 15. Database schema — `db_tevent` and `db_activity` tables
+
+These tables are created by SQL migrations (`000003_activity.up.sql` and `000007_events.up.sql`). Phase C deletes the Go code that reads/writes them, but the tables remain in users' databases. This is harmless (empty tables), but for a clean removal:
+
+**Recommendation (optional):** Add a new migration that drops these tables:
+```sql
+-- 000012_drop_telemetry.up.sql
+DROP TABLE IF EXISTS db_tevent;
+DROP TABLE IF EXISTS db_activity;
+```
+
+### 16. Existing users' `telemetry:enabled` setting
+
+After removing `TelemetryEnabled` from `SettingsConfig`, existing users who have `telemetry:enabled` in their config will have an unrecognized key. The JSON unmarshaling with `omitempty` means it will be silently ignored, which is fine. But the `CountCustomSettings` function explicitly checks for this key — that check needs removal (covered in item 13).
+
+### 17. `WCLOUD_ENDPOINT`/`WCLOUD_PING_ENDPOINT` environment variables
+
+`wcloud.CacheAndRemoveEnvVars()` reads and then unsets these environment variables early in startup (main-server.go line 408). This is a security measure to prevent child processes from accessing cloud endpoint URLs. After removing wcloud, these env vars will remain set in the waveterm process. Harmless for a fork that doesn't use cloud services, but worth noting.
+
+---
+
+## 🟢 What the Spec Gets Right
+
+- The phased approach (A→B→C→D) is sound: removing call sites first, then frontend, then packages, then docs.
+- The file-by-file inventory in Phase A is thorough for the Go backend files it lists.
+- The risk assessment correctly identifies `ClientId` dual-use and `TosAgreed` concerns.
+- The interaction note with the AI removal spec is valuable.
+- Phase C (delete packages) after call sites are clean is the right order.
+- The verification checklist is practical and well-scoped.
+- Correctly identifies that `pkg/telemetry/` and `pkg/wcloud/` should be kept during Phase A to minimize upstream merge conflicts.
+
+---
+
+## Recommended Additions Summary
+
+| # | Item | Severity | Action |
+|---|------|----------|--------|
+| 1 | `emain/emain.ts` + `emain-activity.ts` + `emain-ipc.ts` + `preload.ts` | 🔴 Critical | New sections B.7/B.8 |
+| 2 | `wshcmd-root.go` `sendActivity`/`activityWrap` + `wshcmd-file.go` callers | 🔴 Critical | New section A.16 |
+| 3 | `cmd/generatego/main-generatego.go` telemetrydata import | 🔴 Gap | Add to Phase C |
+| 4 | Auto-generated TS files — edit Go sources, not TS output | 🔴 Gap | Revise B.2/B.3 |
+| 5 | `WshActivityCommand` missing from A.2/A.3/A.4 | 🟡 Error | Fix A.2/A.3/A.4 |
+| 6 | `diagnosticLoop()` + constants missing from A.1 | 🟡 Error | Fix A.1 |
+| 7 | `keymodel.ts` recordTEvent call site | 🟡 Gap | Add to B.4 table |
+| 8 | `telemetryrequired.tsx` — telemetry consent gate | 🟡 Gap | Add to B.6 or cross-reference |
+| 9 | Preview files (mockfilesystem.ts, onboarding.preview.tsx) | 🟡 Minor | Add to B.4/B.5 |
+| 10 | Onboarding UI restructuring plan | 🟠 Side effect | Expand B.5 |
+| 11 | `AgreeTos`/TOS flow decoupling | 🟠 Side effect | Add to B.5 |
+| 12 | PanicHandler rename direction | 🟠 Side effect | Revise A.12 |
+| 13 | `CountCustomSettings` + `AutoUpdateChannel`/`IsAutoUpdateEnabled` | 🟠 Side effect | Add to A.1 and A.14 |
+| 14 | AI panel `telemetry:enabled` reads | 🟠 Side effect | Cross-reference with AI spec |
+| 15 | Database migration to drop telemetry tables | 🟢 Optional | New Phase D.3 |
+| 16 | Existing users' `telemetry:enabled` config key | 🟢 Low risk | Note in A.14 |
+| 17 | `WCLOUD_ENDPOINT`/`WCLOUD_PING_ENDPOINT` env vars | 🟢 Low risk | Note in Phase C |
\ No newline at end of file
diff --git a/.pi/reviews/remove-telemetry-review.md b/.pi/reviews/remove-telemetry-review.md
new file mode 100644
index 0000000000..c865de263d
--- /dev/null
+++ b/.pi/reviews/remove-telemetry-review.md
@@ -0,0 +1,253 @@
+# Review: Remove Telemetry Spec
+
+**Date:** 2026-05-13
+**Spec:** `.pi/specs/remove-telemetry.md`
+**Status:** Draft spec review — issues found
+
+---
+
+## Executive Summary
+
+The spec is well-structured and covers the majority of telemetry call sites. However, there are **significant omissions** on both backend and frontend, **ambiguous instructions** for `PanicHandler` cleanup, and **missing consideration** of auto-generated code files. If executed as written, the app will fail to compile in Phase C due to unresolved imports in code generators, and the Electron main process (`emain/emain.ts`) will continue sending telemetry events unbeknownst to the remover.
+
+The onboarding flow requires more extensive structural changes than just removing `RecordTEventCommand` calls — the state machine has telemetry baked into page transitions.
+
+**Recommendation:** Update the spec before implementation. The issues are correctable with targeted additions.
+
+---
+
+## Critical Omissions (Will Cause Compile Errors or Missed Telemetry)
+
+### 1. `emain/emain.ts` — Entirely Absent from Spec
+
+The Electron main process sends telemetry independently of the frontend React app. This file is **not mentioned anywhere** in the spec.
+
+**What it does:**
+- `sendDisplaysTDataEvent()` — sends display metrics (count, resolution, DPR) via `RpcApi.RecordTEventCommand` (event: `app:display`)
+- `logActiveState()` — every 60 seconds sends:
+ - `RpcApi.ActivityCommand(ElectronWshClient, activity, ...)` with fg/active minutes, terminal command counts, Wave AI active minutes
+ - `RpcApi.RecordTEventCommand(ElectronWshClient, { event: "app:activity", props }, ...)` with aggregated activity props
+- `runActiveTimer()` — triggers `logActiveState()` on a 60-second loop, started at app launch (line 419)
+- `sendDisplaysTDataEvent()` — called once at startup (line 420)
+
+**Where to add:** Phase B (frontend), or a new Phase E (electron main)
+
+### 2. `cmd/server/main-server.go` — Missing `diagnosticLoop` and Constants
+
+The spec's A.1 lists many removals but **completely omits** the diagnostic ping loop:
+
+| Missing Item | Lines | Purpose |
+|--------------|-------|---------|
+| `const InitialDiagnosticWait` | 65 | Wait before first diagnostic ping |
+| `const DiagnosticTick` | 66 | Sleep interval between ping attempts |
+| `func diagnosticLoop()` | 136-154 | Daily ping loop to `ping.waveterm.dev` |
+| `func sendDiagnosticPing()` | 157-169 | Sends diagnostic ping via `wcloud.SendDiagnosticPing` |
+| `go diagnosticLoop()` | 570 | Starts the diagnostic loop goroutine |
+
+The spec mentions removing `wcloud.SendDiagnosticPing()` call from startup and `WAVETERM_NOPING` env var, but the **loop that repeatedly calls `sendDiagnosticPing()` is never mentioned**. This is the most active telemetry channel (daily pings) and would survive Phase A if the spec is followed literally.
+
+**Note:** `WAVETERM_NOPING` is checked inside `diagnosticLoop()` (line 140), not just at startup.
+
+### 3. `cmd/wsh/cmd/wshcmd-root.go` — Missing CLI Activity Tracking
+
+The `wsh` CLI tool sends command usage stats to the local server via `WshActivityCommand`:
+
+```go
+func sendActivity(wshCmdName string, success bool) {
+ ...
+ wshclient.WshActivityCommand(RpcClient, dataMap, nil)
+}
+```
+
+This is called from the CLI root command after each `wsh` invocation. Even though the comment says "it does not contact any wave cloud infrastructure," the data is fed into the local telemetry system (`ActivityCommand` → `UpdateActivity` → `db_tevent`).
+
+**Where to add:** Phase A.15 or new A.16
+
+### 4. `cmd/generatego/main-generatego.go` — Missing Code Generator Update
+
+The Go code generator that produces `pkg/wshrpc/wshclient/wshclient.go` explicitly imports `pkg/telemetry/telemetrydata` in its boilerplate (line 29). After Phase C deletes `pkg/telemetry/`, this generator will **fail to compile**, blocking future code regeneration.
+
+```go
+gogen.GenerateBoilerplate(&buf, "wshclient", []string{
+ ...
+ "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata",
+ ...
+})
+```
+
+Since `TEvent` is removed from the RPC interface in Phase A.2/A.3, the generated `wshclient.go` won't reference `telemetrydata` anymore, but the **static import in the generator template** will still cause a compile error.
+
+**Where to add:** Phase C.3 (cleanup) or Phase C.1
+
+### 5. `frontend/app/store/keymodel.ts` — Missing Call Site
+
+The spec's B.4 table lists many frontend call sites but **misses** `frontend/app/store/keymodel.ts:653`:
+
+```typescript
+recordTEvent("action:other", { "action:type": "conndropdown", "action:initiator": "keyboard" });
+```
+
+This is triggered by a keyboard shortcut for the connection dropdown.
+
+### 6. Auto-Generated Files — Spec Treats Them as Manual Edits
+
+The spec instructs manual removal from:
+- `frontend/app/store/wshclientapi.ts` (B.2)
+- `frontend/app/store/services.ts` (B.3)
+
+Both files are **auto-generated** by `cmd/generatets/main-generatets.go`. If the Go RPC interface and service definitions are cleaned up first, regenerating these files will automatically remove the telemetry methods. Manual edits work but will be overwritten on next regeneration.
+
+**Recommendation:** Note that these files should be regenerated, not manually edited, or edit them AND update the generators to prevent drift.
+
+Similarly, `frontend/types/gotypes.d.ts` is auto-generated and will lose `ActivityUpdate`, `TEvent`, `TEventProps`, `TEventUserProps` types automatically once the Go source types are removed.
+
+---
+
+## Side Effects & Risks
+
+### 1. `PanicHandler` Cleanup — Name Collision Risk
+
+The spec says (A.12): "Rename `PanicHandlerNoTelemetry` to `PanicHandler` (since all telemetry is gone, the 'NoTelemetry' variant is now the only one)".
+
+**Problem:** `PanicHandler` already exists in the same file. You cannot rename `PanicHandlerNoTelemetry` to `PanicHandler` while `PanicHandler` still exists. Go will reject the duplicate function name.
+
+**Correct approach:**
+1. Modify the existing `PanicHandler` function to remove the `if PanicTelemetryHandler != nil` block and the `go func() { ... }()` telemetry call
+2. Delete `PanicHandlerNoTelemetry`
+3. Update the two callers of `PanicHandlerNoTelemetry` (both in `pkg/telemetry/telemetry.go`) to use `PanicHandler` — but since `pkg/telemetry/` is deleted in Phase C, this step may be moot
+4. Remove `var PanicTelemetryHandler func(panicType string)`
+
+**Result:** The existing `PanicHandler` becomes the no-telemetry version automatically; all its existing callers (40+ locations across the codebase) continue to work without modification.
+
+### 2. Onboarding Flow — Structural Changes Required
+
+The onboarding state machine in `frontend/app/onboarding/onboarding.tsx` has telemetry embedded in its page transitions:
+
+```typescript
+type PageName = "init" | "notelemetrystar" | "features";
+```
+
+The `InitPage` component receives a `telemetryUpdateFn` prop and a telemetry toggle UI. The state transition is:
+```
+init -> (telemetry enabled) -> features
+init -> (telemetry disabled) -> notelemetrystar -> features
+```
+
+**What's needed beyond removing `RecordTEventCommand` calls:**
+- Remove `telemetryUpdateFn` prop from `InitPage`
+- Remove the telemetry toggle/checkbox from `InitPage` UI
+- Remove `"notelemetrystar"` from `PageName` type
+- Remove the `notelemetrystar` branch from the switch/case page renderer
+- Simplify the transition to: `init -> features` unconditionally
+- Remove `telemetryEnabled` state and `telemetrySetting` atom usage
+- Remove `NoTelemetryStarPage` export if it's no longer used
+
+The spec mentions "The InitPage in onboarding has a telemetry toggle; remove that UI element" which is correct directionally, but the actual code changes are more extensive than just removing a toggle.
+
+### 3. AI Cloud Mode Gate
+
+In `pkg/aiusechat/usechat.go:86`:
+```go
+if config.WaveAICloud && !telemetry.IsTelemetryEnabled() {
+ return nil, fmt.Errorf("Wave AI cloud modes require telemetry to be enabled")
+}
+```
+
+The spec says (A.11): "remove the gate or hardcode it to pass". If telemetry is removed first (before AI removal), this gate **must** be removed or hardcoded to allow cloud AI, or Wave AI cloud modes will break with a compile error (the `telemetry` package won't exist).
+
+**Side effect:** Removing this gate means Wave AI cloud modes will work without telemetry, which is the desired end state for the fork, but it's a behavioral change that should be noted.
+
+### 4. `wsh` CLI Activity Tracking Behavior Change
+
+The `sendActivity` function in `cmd/wsh/cmd/wshcmd-root.go` currently helps the developers "understand which commands are actually being used." Removing it means zero visibility into `wsh` command usage, but that's aligned with the goal.
+
+### 5. Database Tables — Orphaned Data
+
+`db_tevent` and `db_activity` tables exist in user's SQLite databases. The spec correctly deletes the code that writes to them, but **existing data remains on disk**. This is harmless (no new data accumulates, no data is sent), but if completeness is desired, a one-time cleanup or migration to drop these tables could be added. Not strictly necessary.
+
+### 6. `TosAgreed` Field
+
+The spec correctly notes that `TosAgreed` is harmless without telemetry reading it. However, `TosAgreed` is also referenced in `cmd/server/main-server.go` (line 326) as part of `startupActivityUpdate`, which is being removed anyway. No side effects.
+
+### 7. `ClientId` Non-Telemetry Uses
+
+Verified: `wstore.GetClientId()` is used by:
+- `pkg/remote/sshclient.go` (durable sessions)
+- `pkg/jobcontroller/jobcontroller.go` (job manager)
+- `pkg/wcloud/wcloud.go` (telemetry — being removed)
+- `cmd/server/main-server.go` (diagnostic ping — being removed)
+
+The spec correctly advises keeping `ClientId`. Good.
+
+---
+
+## Documentation Audit Findings
+
+### Covered Correctly
+- `docs/docs/telemetry.mdx` — listed for deletion ✓
+- `docs/docs/telemetry-old.mdx` — listed for deletion ✓
+- `docs/docs/config.mdx` — listed for audit ✓
+- `docs/docs/faq.mdx` — listed for audit ✓
+- `docs/docs/index.mdx` — listed for audit ✓
+
+### Additional Doc References Found
+- `docs/docs/releasenotes.mdx` — has telemetry mentions (lines 178, 379, 505, 690)
+- `docs/docs/waveai.mdx` — line 93 mentions "anonymous telemetry"
+- `docs/docs/waveai-modes.mdx` — line 80 mentions "telemetry requirement messages"
+
+These are noted in the spec as "handled by AI removal spec" or "optional, historical." This is reasonable.
+
+---
+
+## Recommendations for Spec Updates
+
+### Immediate Additions (Before Implementation)
+
+| # | Addition | Phase |
+|---|----------|-------|
+| 1 | Add `emain/emain.ts`: remove `sendDisplaysTDataEvent()`, `logActiveState()`, `runActiveTimer()`, and their startup calls | Phase B or new Phase E |
+| 2 | Add `cmd/server/main-server.go`: remove `diagnosticLoop()`, `sendDiagnosticPing()`, `go diagnosticLoop()`, `InitialDiagnosticWait`, `DiagnosticTick` | Phase A.1 |
+| 3 | Add `cmd/wsh/cmd/wshcmd-root.go`: remove `sendActivity()` function and its call site | Phase A.16 |
+| 4 | Add `cmd/generatego/main-generatego.go`: remove `telemetrydata` from generator boilerplate imports | Phase C.3 |
+| 5 | Add `frontend/app/store/keymodel.ts` to Phase B.4 call sites table | Phase B.4 |
+| 6 | Clarify `PanicHandler` cleanup: modify existing `PanicHandler` to remove telemetry block, delete `PanicHandlerNoTelemetry`, update its callers | Phase A.12 |
+| 7 | Expand onboarding instructions: remove `telemetryUpdateFn` prop, `PageName` variants, simplify state machine transitions | Phase B.5 |
+| 8 | Note auto-generated files (`wshclientapi.ts`, `services.ts`, `gotypes.d.ts`) should be regenerated after Go changes, not just manually edited | Phase B intro |
+
+### Optional but Recommended
+
+| # | Addition | Rationale |
+|---|----------|-----------|
+| 9 | Add DB cleanup note: `db_tevent` and `db_activity` tables will remain in existing user databases but won't receive new data | Verification checklist or risk assessment |
+| 10 | Consider removing `autoupdate:channel` and `autoupdate:enabled` from `CountCustomSettings` exclusion since auto-update is being discussed for removal | If auto-update is removed in a follow-up spec, this becomes relevant |
+
+---
+
+## Risk Matrix (Post-Spec-Correction)
+
+| Risk | Severity | Mitigation |
+|------|----------|------------|
+| ClientId used by non-telemetry code | Low | Keep `wstore.GetClientId()` — spec already covers ✓ |
+| `TosAgreed` field harmless without readers | Low | Keep field, no migration needed — spec already covers ✓ |
+| Onboarding structural changes | Medium | Expand Phase B.5 instructions per review |
+| Upstream merge conflicts | Medium | Phase A keeps `pkg/telemetry/` intact; Phase C deferred — spec already covers ✓ |
+| Compile errors from code generators | High | Add `cmd/generatego/` cleanup to spec |
+| Electron main telemetry survives | High | Add `emain/emain.ts` to spec |
+| Daily diagnostic pings survive | High | Add `diagnosticLoop` removal to spec |
+| `PanicHandler` name collision | Medium | Clarify A.12 instructions |
+| AI cloud modes break if gate removed before AI spec | Low | Hardcode gate to pass or remove — spec covers, just note execution order |
+
+---
+
+## Verification Checklist Amendments
+
+After the spec is updated and implemented:
+
+- [ ] `emain/emain.ts` has no `RecordTEventCommand` or `ActivityCommand` calls
+- [ ] `cmd/server/main-server.go` has no `diagnosticLoop`, `sendDiagnosticPing`, or `go diagnosticLoop()`
+- [ ] `cmd/wsh/cmd/wshcmd-root.go` has no `sendActivity` function
+- [ ] `cmd/generatego/main-generatego.go` does not import `telemetrydata`
+- [ ] `frontend/app/store/keymodel.ts` has no `recordTEvent` calls
+- [ ] Onboarding flow transitions directly from `init` to `features` without telemetry consent
+- [ ] `pkg/panichandler/panichandler.go` has only one `PanicHandler` function (no telemetry side-effects)
+- [ ] No references to `telemetry:enabled` in `docs/docs/` remain
diff --git a/.pi/specs/bug-tabclose-crash.md b/.pi/specs/bug-tabclose-crash.md
new file mode 100644
index 0000000000..f4650c6b7c
--- /dev/null
+++ b/.pi/specs/bug-tabclose-crash.md
@@ -0,0 +1,442 @@
+# Bug: Crash on Tab Close After SSH Session Exit
+
+**Status:** Fixed (2026-05-14)
+**Priority:** High
+**Date:** 2026-05-13
+**Resolution:** Root cause confirmed; redundant goroutine removed from `CloseTab`; `ShellProc.Close()` made idempotent with `sync.Once`; trace logging added.
+
+## Reproduction Steps
+
+1. Connect to SSH from the dropdown
+2. Type `exit` in the shell
+3. Click the tab 'x' to close the tab
+4. → Crash
+
+## Thesis: Root Cause Analysis
+
+### Primary Suspect: Race Condition in `CloseTab` — Double Block Controller Destruction
+
+**Location:** `pkg/service/workspaceservice/workspaceservice.go:218-232`
+
+The `CloseTab` method has a critical design flaw that triggers **concurrent** `DestroyBlockController` calls for each block:
+
+```go
+func (svc *WorkspaceService) CloseTab(...) {
+ tab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)
+ if err == nil && tab != nil {
+ go func() { // ← Goroutine A
+ for _, blockId := range tab.BlockIds {
+ blockcontroller.DestroyBlockController(blockId)
+ }
+ }()
+ }
+ newActiveTabId, err := wcore.DeleteTab(ctx, ...) // ← Synchronous
+ // DeleteTab → DeleteBlock → sendBlockCloseEvent
+ // → handleBlockCloseEvent → go DestroyBlockController(blockId) ← Goroutine B
+}
+```
+
+Each block gets `DestroyBlockController` called **twice concurrently** — once from the explicit goroutine in `CloseTab` (Goroutine A), and once from the block-close event handler triggered by `DeleteBlock` (Goroutine B).
+
+### How This Leads to a Crash
+
+#### Path 1: `ShellController.Stop` — Double `ShellProc.Close()` on SSH Session
+
+`DestroyBlockController` calls `controller.Stop(true, Status_Done, true)`. For `ShellController`, `Stop` has a **Lock/Unlock/Relock** pattern that creates a race window:
+
+```go
+func (sc *ShellController) Stop(graceful bool, newStatus string, destroy bool) {
+ sc.Lock.Lock()
+ defer sc.Lock.Unlock()
+
+ if sc.ShellProc == nil || sc.ProcStatus == Status_Done || sc.ProcStatus == Status_Init {
+ return // ← Guard check, but...
+ }
+ sc.ShellProc.Close() // ← First Close
+ if graceful {
+ doneCh := sc.ShellProc.DoneCh
+ sc.Lock.Unlock() // ← UNLOCKS here, allowing concurrent Stop to enter
+ <-doneCh // ← Waits for shell process to finish
+ sc.Lock.Lock() // ← Re-locks after waiting
+ }
+ sc.ProcStatus = newStatus // ← Only NOW updated, but second Stop already entered
+}
+```
+
+**Race sequence:**
+1. Goroutine A calls `Stop`, acquires lock, checks `ProcStatus == Status_Running`, calls `ShellProc.Close()`, **unlocks** to wait on `doneCh`
+2. Goroutine B calls `Stop`, acquires lock, sees `ShellProc != nil` and `ProcStatus == Status_Running` (not yet updated), calls `ShellProc.Close()` **again**
+3. `ShellProc.Close()` calls `Cmd.KillGraceful()` → `SessionWrap.Kill()` → `Tty.Close()` + `Session.Close()`
+4. **Double `ssh.Session.Close()`** on a session that may already be closing (after user typed `exit`)
+5. **Double `PipePty.Close()`** — closing `os.File` descriptors twice
+
+On a closed/dying SSH session, `Session.Close()` sends a channel close message over a potentially-dead SSH mux. The `x/crypto/ssh` library's `channel.Close()` calls `channel.sendMessage()` which writes to the transport. If the mux loop has already exited and cleaned up, this can cause:
+- Panic from writing to a closed/cleaned-up channel
+- Panic from `close` on a closed channel (Go runtime panic)
+- Data race on mux internals that have been cleaned up
+
+#### Path 2: `ShellProc.Close()` Double Channel Close
+
+`ShellProc.Close()` spawns a goroutine:
+
+```go
+func (sp *ShellProc) Close() {
+ sp.Cmd.KillGraceful(DefaultGracefulKillWait)
+ go func() {
+ waitErr := sp.Cmd.Wait()
+ sp.SetWaitErrorAndSignalDone(waitErr)
+ if runtime.GOOS != "windows" {
+ sp.Cmd.Close()
+ }
+ }()
+}
+```
+
+When called twice concurrently, `KillGraceful` is called twice, and two goroutines are spawned that both call `Wait()` and `Close()`. While `Wait()` is protected by `sync.Once` and `SetWaitErrorAndSignalDone` is protected by `CloseOnce`, **`KillGraceful` and `Close` are NOT idempotent or protected**.
+
+For `SessionWrap`:
+- `KillGraceful` → `Kill()` → `Tty.Close()` + `Session.Close()` — called twice
+- `Close()` is a no-op (the `pty.Pty` interface has no `Close` method beyond `ReadWriteCloser`, and `SessionWrap` doesn't implement an explicit `Close()`)
+
+For `CmdWrap` (local shells):
+- `KillGraceful` → sends signal, then force-kills after timeout
+- `Close()` → `Cmd.Wait()` + pty close — double close on pty
+
+#### Path 3: Durable Shell — Job Termination Race with Block Deletion
+
+For SSH blocks using `DurableShellController` (the default for SSH connections):
+
+When user types `exit`:
+1. The remote shell process exits
+2. `HandleCmdJobExited` is called → `tryTerminateJobManager` terminates the job manager
+3. The output stream reaches EOF → `StreamDone = true`
+
+When user clicks tab X:
+- `DestroyBlockController` → `DurableShellController.Stop(true, Status_Done, true)` → `TerminateAndDetachJob(ctx, jobId)`
+- But the job may already be terminated/detached
+- `DetachJobFromBlock` tries to update the block's `JobId` field via `wstore.DBUpdateFn`
+- But the block may already be **deleted from the DB** by `DeleteBlock` (running concurrently in `DeleteTab`)
+- This could cause a DB error or panic if the update operates on a non-existent record
+
+#### Path 4: ConnMonitor Keepalive on Closing SSH Client
+
+When the SSH connection is still alive (connserver session persists after shell exit), `ConnMonitor` runs keepalive checks every 5 seconds:
+
+```go
+func (cm *ConnMonitor) SendKeepAlive() error {
+ client := cm.Client // ← Stale reference captured at creation time
+ if !cm.setKeepAliveInFlight() {
+ return nil
+ }
+ go func() {
+ _, _, err := client.SendRequest("keepalive@openssh.com", true, nil)
+ // ...
+ }()
+}
+```
+
+If `closeInternal_withlifecyclelock()` is called concurrently:
+1. It calls `conn.Monitor.Close()` (cancels context)
+2. It calls `client.Close()` (closes SSH client)
+3. It sets `conn.Client = nil`
+
+But a keepalive goroutine may have already captured `client := cm.Client` and started `SendRequest` on a closing/closed client. While `x/crypto/ssh` generally handles this gracefully (returning `io.EOF`), if the mux loop has already exited and cleaned up its internal channels, accessing the mux can race.
+
+### Secondary Contributing Factors
+
+1. **`DurableShellController.Stop` has no lock** — Unlike `ShellController.Stop`, concurrent calls can race on the `JobId` field read.
+
+2. **No idempotency guard on `ShellProc.Close()`** — No `sync.Once` or closed-flag prevents double-close.
+
+3. **`ConnMonitor` holds stale `*ssh.Client` reference** — Never updated when client is closed/nilled.
+
+4. **`handleBlockCloseEvent` launches goroutine** — `go DestroyBlockController(blockId)` makes the double-destroy race uncontrolled.
+
+### Most Likely Crash Sequence (SSH + Durable Shell)
+
+1. User types `exit` in SSH shell → shell process on remote exits
+2. `HandleCmdJobExited` fires → `tryTerminateJobManager` → job manager terminated
+3. User clicks tab X → `CloseTab` called
+4. Goroutine A: `DestroyBlockController(blockId)` → `DurableShellController.Stop()` → `TerminateAndDetachJob(jobId)`
+5. `DeleteTab` → `DeleteBlock` → `sendBlockCloseEvent`
+6. Goroutine B: `handleBlockCloseEvent` → `DestroyBlockController(blockId)` → second `DurableShellController.Stop()` → second `TerminateAndDetachJob(jobId)`
+7. First call terminates job and detaches from block (block's `JobId` cleared)
+8. Second call tries to detach from already-detached/non-existent block → potential DB error or nil pointer
+
+### Most Likely Crash Sequence (SSH + Non-Durable Shell)
+
+1. User types `exit` in SSH shell → `manageRunningShellProcess` wait loop detects exit → `ProcStatus = Status_Done`
+2. User clicks tab X → `CloseTab` called
+3. Goroutine A: `DestroyBlockController(blockId)` → `ShellController.Stop(true, Status_Done, true)` → sees `ProcStatus == Status_Done` → returns early (OK)
+4. Goroutine B (from `sendBlockCloseEvent`): `DestroyBlockController(blockId)` → controller already deleted from registry → returns early (OK)
+5. **But** if the timing is different — tab close happens while `ProcStatus` is still `Status_Running` (shell still exiting):
+ - Goroutine A: `Stop` acquires lock, sees Running, calls `ShellProc.Close()`, **unlocks** to wait
+ - Goroutine B: `Stop` acquires lock, sees Running (not yet Done), calls `ShellProc.Close()` **again**
+ - Double `Session.Close()` → potential panic in SSH library
+
+---
+
+## Logging Additions (Suggested)
+
+### 1. In `CloseTab` — Trace the double-destroy path
+
+**File:** `pkg/service/workspaceservice/workspaceservice.go`
+
+```go
+func (svc *WorkspaceService) CloseTab(ctx context.Context, workspaceId string, tabId string, fromElectron bool) (*CloseTabRtnType, waveobj.UpdatesRtnType, error) {
+ ctx = waveobj.ContextWithUpdates(ctx)
+ tab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)
+ if err == nil && tab != nil {
+ log.Printf("[closetab] tab=%s blocks=%v launching async DestroyBlockController goroutine", tabId, tab.BlockIds)
+ go func() {
+ for _, blockId := range tab.BlockIds {
+ log.Printf("[closetab] DestroyBlockController block=%s (from CloseTab goroutine)", blockId)
+ blockcontroller.DestroyBlockController(blockId)
+ }
+ }()
+ }
+ // ...
+}
+```
+
+### 2. In `DestroyBlockController` — Detect double-destroy
+
+**File:** `pkg/blockcontroller/blockcontroller.go`
+
+```go
+func DestroyBlockController(blockId string) {
+ controller := getController(blockId)
+ if controller == nil {
+ log.Printf("[destroy] block=%s: controller already nil (possible double-destroy)", blockId)
+ return
+ }
+ log.Printf("[destroy] block=%s: stopping controller (type=%T, connName=%s)", blockId, controller, controller.GetConnName())
+ controller.Stop(true, Status_Done, true)
+ wstore.DeleteRTInfo(waveobj.MakeORef(waveobj.OType_Block, blockId))
+ deleteController(blockId)
+ log.Printf("[destroy] block=%s: controller deleted from registry", blockId)
+}
+```
+
+### 3. In `ShellController.Stop` — Detect concurrent Stop and double-Close
+
+**File:** `pkg/blockcontroller/shellcontroller.go`
+
+```go
+func (sc *ShellController) Stop(graceful bool, newStatus string, destroy bool) {
+ sc.Lock.Lock()
+ defer sc.Lock.Unlock()
+ log.Printf("[shellcontroller] Stop block=%s procStatus=%s shellProcNil=%v destroy=%v", sc.BlockId, sc.ProcStatus, sc.ShellProc == nil, destroy)
+
+ if sc.ShellProc == nil || sc.ProcStatus == Status_Done || sc.ProcStatus == Status_Init {
+ if newStatus != sc.ProcStatus {
+ sc.ProcStatus = newStatus
+ sc.sendUpdate_nolock()
+ }
+ return
+ }
+ // ...
+ sc.ShellProc.Close()
+ if graceful {
+ doneCh := sc.ShellProc.DoneCh
+ sc.Lock.Unlock() // ← RACE WINDOW STARTS HERE
+ log.Printf("[shellcontroller] Stop block=%s waiting on DoneCh (lock released)", sc.BlockId)
+ <-doneCh
+ sc.Lock.Lock() // ← RACE WINDOW ENDS HERE
+ log.Printf("[shellcontroller] Stop block=%s DoneCh closed (lock reacquired)", sc.BlockId)
+ }
+ // ...
+}
+```
+
+### 4. In `DurableShellController.Stop` — Log concurrent access
+
+**File:** `pkg/blockcontroller/durableshellcontroller.go`
+
+```go
+func (dsc *DurableShellController) Stop(graceful bool, newStatus string, destroy bool) {
+ if !destroy {
+ return
+ }
+ jobId := dsc.getJobId()
+ log.Printf("[durableshellcontroller] Stop block=%s jobId=%s destroy=%v", dsc.BlockId, jobId, destroy)
+ if jobId == "" {
+ return
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ jobcontroller.TerminateAndDetachJob(ctx, jobId)
+}
+```
+
+### 5. In `ConnMonitor.SendKeepAlive` — Detect stale client reference
+
+**File:** `pkg/remote/conncontroller/connmonitor.go`
+
+```go
+func (cm *ConnMonitor) SendKeepAlive() error {
+ client := cm.Client
+ currentClient := cm.Conn.GetClient()
+ if currentClient == nil {
+ log.Printf("[connmonitor] SendKeepAlive: conn=%s client is nil (connection closed)", cm.Conn.GetName())
+ return nil
+ }
+ if client != currentClient {
+ log.Printf("[connmonitor] SendKeepAlive: conn=%s stale client reference (monitor client != current client)", cm.Conn.GetName())
+ return nil
+ }
+ // ... rest of SendKeepAlive
+}
+```
+
+### 6. In `handleBlockCloseEvent` — Log the event handling
+
+**File:** `pkg/blockcontroller/blockcontroller.go`
+
+```go
+func handleBlockCloseEvent(event *wps.WaveEvent) {
+ blockId, ok := event.Data.(string)
+ if !ok {
+ log.Printf("[blockclose] invalid event data type")
+ return
+ }
+ log.Printf("[blockclose] block=%s: launching DestroyBlockController goroutine from event handler", blockId)
+ go DestroyBlockController(blockId)
+}
+```
+
+**File:** `pkg/jobcontroller/jobcontroller.go`
+
+```go
+func handleBlockCloseEvent(event *wps.WaveEvent) {
+ // ... existing code ...
+ log.Printf("[blockclose-job] block=%s: found %d jobs to terminate", blockId, len(jobIds))
+ for _, jobId := range jobIds {
+ log.Printf("[blockclose-job] block=%s: terminating job=%s", blockId, jobId)
+ TerminateAndDetachJob(ctx, jobId)
+ }
+}
+```
+
+---
+
+## Tests Written
+
+**File:** `pkg/blockcontroller/blockcontroller_test.go`
+
+### Tests Implemented
+
+| Test | What it tests | Result |
+|------|--------------|--------|
+| `TestShellControllerStopConcurrent/double_stop_does_not_double_kill` | Two concurrent `Stop` calls on a running ShellController — uses slow mock to expose Lock/Unlock/Relock race | **PASS** — but only checks KillGraceful count, not data race |
+| `TestShellControllerStopConcurrent/stop_after_proc_done_is_noop` | Stop on a controller with ProcStatus=Done | **PASS** |
+| `TestShellControllerStopConcurrent/stop_sets_status_done` | Stop updates ProcStatus correctly | **PASS** |
+| `TestDestroyBlockControllerDoubleCall` | Two concurrent `DestroyBlockController` calls for same blockId | **PASS** — second call finds nil controller and returns |
+| `TestDestroyBlockControllerDoubleCallDurable` | Same test with DurableShellController | **PASS** |
+| `TestDurableShellControllerStopConcurrent/stop_with_empty_jobid_is_noop` | Stop with no jobId | **PASS** |
+| `TestDurableShellControllerStopConcurrent/stop_without_destroy_is_noop` | Stop with destroy=false | **PASS** |
+| `TestShellControllerStopNilShellProc/nil_proc_updates_status` | Stop with nil ShellProc updates status | **PASS** |
+| `TestShellControllerStopNilShellProc/nil_proc_already_done_noop` | Stop when already Done | **PASS** |
+| `TestShellControllerStopNilShellProc/nil_proc_init_status` | Stop when Init | **PASS** |
+| `TestShellProcDoubleClose/double_close_on_running_proc` | Two concurrent `ShellProc.Close()` calls | **PASS** |
+| `TestShellProcDoubleClose/close_then_wait` | Close then second Close after Wait | **PASS** — Wait is protected by sync.Once |
+| `TestShellControllerStopRaceWithDoneStatus` | Tab-close Stop racing with shell-exit status update | **PASS** |
+| `TestShellControllerStopDoesNotPanicOnClosedSession/closed_session_stop` | Stop on closed SSH session | **PASS** |
+| `TestShellControllerStopDoesNotPanicOnClosedSession/concurrent_stop_on_closing_session` | Three concurrent operations: two Stops + shell exit | **PASS** |
+
+### Test Infrastructure
+
+- **`mockConnInterface`**: Fast mock where `Wait()` returns immediately. Good for testing the guard conditions and status updates.
+- **`slowMockConnInterface`**: Slow mock where `Wait()` blocks until `KillGraceful` signals it or `waitDone` is closed. Essential for exposing the Lock/Unlock/Relock race in `ShellController.Stop`.
+- **`mockClosedConnInterface`**: Mock that returns errors from all operations, simulating a closed SSH session.
+
+### Key Finding from Tests
+
+The `double_stop_does_not_double_kill` test **passes without detecting the double-KillGraceful** in the default case because:
+- With the slow mock, `KillGraceful` triggers `Wait()` to complete, and the `DoneCh` is signaled
+- The second `Stop` call sees `ProcStatus == Status_Done` (updated after the first Stop completes) and returns early
+- **However**, this depends on timing. If the second `Stop` enters during the `Lock.Unlock()` / `<-doneCh` / `Lock.Lock()` window, it WILL call `ShellProc.Close()` again
+
+Running with `go test -race` does not flag a data race in the test because the test's `Stop` calls are serialized by the `ShellController.Lock`. The actual race is a **logical race** (double-close), not a data race detectable by the race detector. The race detector would catch it if two goroutines accessed the same `ShellProc` fields without synchronization, but `ShellProc.Close()` is called under the controller's lock.
+
+**The real danger** is that `ShellProc.Close()` launches a **goroutine** (`go func() { waitErr := sp.Cmd.Wait(); ... }()`), and the second `Close()` launches another goroutine. Both goroutines call `Cmd.Wait()` and `Cmd.Close()` concurrently without synchronization. This IS a data race on the SSH session internals, but it happens inside the `x/crypto/ssh` library, not in waveterm code, so the Go race detector won't flag it directly.
+
+---
+
+## In Flight / Not Yet Done
+
+### Tests Still Needed
+
+1. **`go test -race` on the double-ShellProc.Close goroutine race** — The `ShellProc.Close()` method spawns a goroutine that calls `Cmd.Wait()` and `Cmd.Close()`. Two concurrent `Close()` calls spawn two goroutines that race on SSH session internals. Need a test that directly exercises this goroutine race.
+
+2. **Integration test with real SSH session** — Unit tests can't fully simulate the `x/crypto/ssh` library's behavior when `Session.Close()` is called on a closing session. An integration test with a real SSH connection would catch panics in the SSH library.
+
+3. **`CloseTab` double-destroy integration test** — Test the full `CloseTab` flow: create a tab with blocks, then close it, and verify no double-destroy or panic.
+
+4. **`ConnMonitor` keepalive on stale client test** — Test that `SendKeepAlive` on a closed/nilled client doesn't panic.
+
+5. **`DurableShellController.Stop` concurrent job termination test** — Test two concurrent `TerminateAndDetachJob` calls on the same jobId. This requires setting up the job DB, which is more complex.
+
+### Logging Not Yet Added
+
+The logging additions described above are **designed but not yet implemented** in the source files. They should be added to enable interactive crash reproduction and diagnosis.
+
+### Fix Not Yet Implemented
+
+The fix for the primary root cause (double-destroy in `CloseTab`) should be one of:
+
+**Option A: Remove the redundant goroutine in `CloseTab`**
+The explicit `go func() { DestroyBlockController(...) }()` goroutine in `CloseTab` is redundant because `DeleteTab` → `DeleteBlock` → `sendBlockCloseEvent` already triggers controller destruction. Removing it eliminates the double-destroy entirely.
+
+**Option B: Add idempotency to `DestroyBlockController`**
+Make `DestroyBlockController` safe for concurrent calls by adding a `destroyed` flag or using `sync.Once`:
+
+```go
+func DestroyBlockController(blockId string) {
+ // Use sync.Map or a separate set to track in-progress destructions
+ if !markDestroyInProgress(blockId) {
+ return // already being destroyed
+ }
+ defer clearDestroyInProgress(blockId)
+ controller := getController(blockId)
+ if controller == nil {
+ return
+ }
+ controller.Stop(true, Status_Done, true)
+ wstore.DeleteRTInfo(waveobj.MakeORef(waveobj.OType_Block, blockId))
+ deleteController(blockId)
+}
+```
+
+**Option C: Make `ShellProc.Close()` idempotent**
+Add a `sync.Once` to `ShellProc.Close()`:
+
+```go
+func (sp *ShellProc) Close() {
+ sp.closeOnce.Do(func() {
+ sp.Cmd.KillGraceful(DefaultGracefulKillWait)
+ go func() {
+ defer func() {
+ panichandler.PanicHandler("ShellProc.Close", recover())
+ }()
+ waitErr := sp.Cmd.Wait()
+ sp.SetWaitErrorAndSignalDone(waitErr)
+ if runtime.GOOS != "windows" {
+ sp.Cmd.Close()
+ }
+ }()
+ })
+}
+```
+
+**Recommended approach:** Option A (remove redundant goroutine) + Option C (make ShellProc.Close idempotent) as defense-in-depth. Option A fixes the root cause; Option C protects against any other code path that might call Close twice.
+
+### Interactive Reproduction Needed
+
+The tests above confirm the structural race conditions exist but don't reproduce the actual crash. To confirm the crash:
+1. Add the logging additions
+2. Build and run waveterm with `task dev`
+3. Connect to an SSH server from the dropdown
+4. Type `exit` in the shell
+5. Click the tab X
+6. Check logs for double-destroy patterns and any panic/crash output
\ No newline at end of file
diff --git a/.pi/specs/configurable-reconnect-thresholds.md b/.pi/specs/configurable-reconnect-thresholds.md
new file mode 100644
index 0000000000..b3d9070652
--- /dev/null
+++ b/.pi/specs/configurable-reconnect-thresholds.md
@@ -0,0 +1,282 @@
+# Configurable Reconnect & Monitor Thresholds (PR #3)
+
+## Problem
+
+PR #1 hardcodes fast thresholds (3s keepalive, 5s auto-disconnect, 5s retry interval) for laptop use. These work well for Wi-Fi switching but may be too aggressive for:
+
+- Slow SSH servers (high latency, old hardware)
+- Cellular/satellite connections (intermittent, high jitter)
+- Server room workstations (stable wired networks don't need aggressive polling)
+- User preference (some users prefer stability over speed)
+
+Hardcoded values also prevent tuning without recompiling the app.
+
+## Scope
+
+- **In scope**: Per-connection configurable thresholds for monitor and scheduler; removal of the `degraded` health status; simplified monitor model.
+- **Out of scope**: UI overlay changes (PR #2); changes to the underlying reconnect logic (PR #1).
+
+## Goals
+
+1. Every threshold from PR #1 becomes a `ConnKeywords` field with sensible defaults.
+2. The `degraded` health status is removed — the monitor uses a single `good → stalled` transition.
+3. The `inputNotifyCh` path is simplified: typing still triggers an immediate keepalive, but no `degraded` state is set.
+4. Backward compatibility: missing config values fall back to PR #1's fast defaults.
+
+## New Configurable Fields
+
+### `ConnKeywords` additions (`pkg/wconfig/settingsconfig.go`)
+
+```go
+type ConnKeywords struct {
+ // ... existing fields ...
+
+ // Keepalive / Stall detection
+ ConnKeepaliveIntervalSec *int `json:"conn:keepaliveinterval,omitempty"`
+ ConnStallThresholdSec *int `json:"conn:stallthreshold,omitempty"`
+ ConnAutoDisconnectThresholdSec *int `json:"conn:autodisconnectthreshold,omitempty"`
+
+ // Reconnect scheduler
+ ConnReconnectTimeoutSec *int `json:"conn:reconnecttimeout,omitempty"`
+ ConnReconnectIntervalSec *int `json:"conn:reconnectinterval,omitempty"`
+ ConnReconnectAggressiveIntervalSec *int `json:"conn:reconnectaggressiveinterval,omitempty"`
+
+ // Feature flags
+ ConnEnableStallAutoDisconnect *bool `json:"conn:stallautodisconnect,omitempty"`
+}
+```
+
+### Defaults (used when field is nil)
+
+| Field | PR #1 Default | Rationale |
+|-------|---------------|-----------|
+| `conn:keepaliveinterval` | 3 | Seconds of inactivity before sending keepalive |
+| `conn:stallthreshold` | 3 | Seconds after keepalive before declaring stalled |
+| `conn:autodisconnectthreshold` | 5 | Seconds of stall before forcing disconnect |
+| `conn:reconnecttimeout` | 5 | Timeout for each `AttemptReconnect` dial |
+| `conn:reconnectinterval` | 5 | Seconds between normal reconnect retries |
+| `conn:reconnectaggressiveinterval` | 3 | Seconds between aggressive-mode retries |
+| `conn:stallautodisconnect` | true | Whether to auto-disconnect on stall at all |
+
+### Example `connections.json`
+
+```json
+{
+ "user@slow-server": {
+ "conn:keepaliveinterval": 10,
+ "conn:stallthreshold": 10,
+ "conn:autodisconnectthreshold": 30,
+ "conn:reconnecttimeout": 30,
+ "conn:reconnectinterval": 30
+ },
+ "user@laptop-target": {
+ "conn:keepaliveinterval": 2,
+ "conn:stallthreshold": 2,
+ "conn:autodisconnectthreshold": 3,
+ "conn:reconnectinterval": 3
+ }
+}
+```
+
+## Changes
+
+### 1. `pkg/wconfig/settingsconfig.go` — Add fields
+
+Add the 7 new fields to `ConnKeywords`. Placement: after existing `ConnStall*` fields, before `Display*`.
+
+### 2. `pkg/remote/conncontroller/connmonitor.go` — Read config + remove `degraded`
+
+#### 2a. Config helpers
+
+```go
+func (cm *ConnMonitor) getIntConfig(key string, defaultVal int) int {
+ connConfig, ok := cm.Conn.getConnectionConfig()
+ if !ok {
+ return defaultVal
+ }
+ switch key {
+ case "keepaliveinterval":
+ if connConfig.ConnKeepaliveIntervalSec != nil && *connConfig.ConnKeepaliveIntervalSec > 0 {
+ return *connConfig.ConnKeepaliveIntervalSec
+ }
+ case "stallthreshold":
+ if connConfig.ConnStallThresholdSec != nil && *connConfig.ConnStallThresholdSec > 0 {
+ return *connConfig.ConnStallThresholdSec
+ }
+ case "autodisconnectthreshold":
+ if connConfig.ConnAutoDisconnectThresholdSec != nil && *connConfig.ConnAutoDisconnectThresholdSec > 0 {
+ return *connConfig.ConnAutoDisconnectThresholdSec
+ }
+ }
+ return defaultVal
+}
+```
+
+#### 2b. Remove `degraded` state
+
+Delete `LastInputTime`, `isUrgent()`, and the `degraded` status constant. The `inputNotifyCh` path is kept but simplified:
+
+```go
+func (cm *ConnMonitor) keepAliveMonitor() {
+ ticker := time.NewTicker(cm.getTickerInterval())
+ defer ticker.Stop()
+
+ for {
+ if cm.Conn.GetClient() != cm.Client {
+ return
+ }
+ select {
+ case <-ticker.C:
+ cm.checkConnection()
+
+ case <-cm.inputNotifyCh:
+ // Immediate keepalive on input, no "degraded" state
+ cm.SendKeepAlive()
+
+ case <-cm.ctx.Done():
+ return
+ }
+ }
+}
+```
+
+Note: `getTickerInterval()` should return `min(keepaliveinterval, 1)` or similar — the ticker must run at least as fast as the keepalive interval.
+
+#### 2c. Use config in `checkConnection()`
+
+```go
+func (cm *ConnMonitor) checkConnection() {
+ lastActivity := cm.LastActivityTime.Load()
+ if lastActivity == 0 {
+ return
+ }
+ timeSinceActivity := time.Now().UnixMilli() - lastActivity
+
+ keepAliveThreshold := int64(cm.getIntConfig("keepaliveinterval", 3)) * 1000
+ if timeSinceActivity > keepAliveThreshold {
+ cm.SendKeepAlive()
+ }
+
+ stalledThreshold := int64(cm.getIntConfig("stallthreshold", 3)) * 1000
+ timeSinceKeepAlive := cm.getTimeSinceKeepAlive()
+ if timeSinceKeepAlive > stalledThreshold {
+ cm.setConnHealthStatus(ConnHealthStatus_Stalled)
+
+ stallStart := cm.StallStartTime.Load()
+ now := time.Now().UnixMilli()
+ if stallStart == 0 {
+ cm.StallStartTime.Store(now)
+ } else {
+ thresholdMs := int64(cm.getIntConfig("autodisconnectthreshold", 5)) * 1000
+ if now-stallStart > thresholdMs {
+ cm.disconnectOnStall()
+ }
+ }
+ } else {
+ cm.StallStartTime.Store(0)
+ }
+}
+```
+
+#### 2d. Remove `degraded` constant
+
+```go
+const (
+ ConnHealthStatus_Good = "good"
+ // ConnHealthStatus_Degraded = "degraded" // REMOVED
+ ConnHealthStatus_Stalled = "stalled"
+)
+```
+
+### 3. `pkg/jobcontroller/jobcontroller.go` — Read scheduler config
+
+Replace hardcoded constants with config-aware lookups. The scheduler gets a reference to `ConnKeywords` via the connection name.
+
+```go
+func getReconnectConfig(connName string) (timeout, interval, aggressiveInterval time.Duration) {
+ connOpts, err := remote.ParseOpts(connName)
+ if err != nil {
+ return 5*time.Second, 5*time.Second, 3*time.Second
+ }
+ conn := conncontroller.MaybeGetConn(connOpts)
+ if conn == nil {
+ return 5*time.Second, 5*time.Second, 3*time.Second
+ }
+ // ... read from conn config or defaults ...
+}
+```
+
+Use these in `scheduleConnectionReconnect` instead of `ConnReconnectInterval`, etc.
+
+### 4. `pkg/remote/conncontroller/conncontroller.go` — Remove `degraded` references
+
+Update `DeriveConnStatus()` and any code that references `ConnHealthStatus_Degraded`.
+
+### 5. Schema & Type Updates
+
+#### `pkg/schema/schema.go` or `schema/connections.json`
+
+Add the new fields to the connections schema so Monaco editor validates them.
+
+#### `frontend/types/gotypes.d.ts`
+
+Add the new fields to the TypeScript `ConnKeywords` type.
+
+### 6. Documentation
+
+#### `docs/docs/connections.mdx`
+
+Add a new "Connection Resilience Settings" subsection:
+
+```markdown
+### Connection Resilience
+
+These settings control how aggressively Wave detects and recovers from network interruptions.
+
+| Keyword | Default | Description |
+|---------|---------|-------------|
+| `conn:keepaliveinterval` | 3 | Seconds of inactivity before sending a keepalive probe. Lower = faster detection, more network traffic. |
+| `conn:stallthreshold` | 3 | Seconds after keepalive before declaring the connection `stalled`. |
+| `conn:autodisconnectthreshold` | 5 | Seconds of stall before forcing disconnect and starting reconnect. |
+| `conn:reconnecttimeout` | 5 | Timeout for each reconnect attempt. |
+| `conn:reconnectinterval` | 5 | Seconds between reconnect retries in normal mode. |
+| `conn:reconnectaggressiveinterval` | 3 | Seconds between retries when the network appears unreachable. |
+| `conn:stallautodisconnect` | true | Whether to auto-disconnect when stalled. Disable if you prefer manual control. |
+
+Example for a high-latency satellite connection:
+```json
+{
+ "user@satellite": {
+ "conn:keepaliveinterval": 15,
+ "conn:stallthreshold": 15,
+ "conn:autodisconnectthreshold": 60,
+ "conn:reconnecttimeout": 30,
+ "conn:reconnectinterval": 30
+ }
+}
+```
+```
+
+## Test Plan
+
+| Test | Setup | Expected |
+|------|-------|----------|
+| Default values | No config set | Falls back to PR #1 fast defaults (3s/5s/5s) |
+| Custom values | Set `conn:keepaliveinterval=10` | Monitor sends keepalive every 10s |
+| `degraded` removed | Type during dead network | No `degraded` event; immediate keepalive sent instead |
+| Backward compat | Existing `connections.json` without new fields | No error, defaults used |
+| UI still works | PR #2 overlay with configurable intervals | Overlay countdown respects `conn:reconnectinterval` |
+
+## Validation Checklist
+
+- [ ] `task build:backend` succeeds
+- [ ] `task build:frontend` succeeds
+- [ ] `go test ./pkg/remote/conncontroller/...` passes
+- [ ] `go test ./pkg/jobcontroller/...` passes
+- [ ] Manual test: override one setting, verify behavior changes
+- [ ] Manual test: delete all new settings, verify defaults work
+
+## Dependencies
+
+- **Requires PR #1**: Fast reconnect hardcoded thresholds must be in place first — this PR just makes them configurable.
+- **No dependency on PR #2**: Configurable thresholds are backend-only; the overlay (PR #2) consumes whatever values are active.
diff --git a/.pi/specs/fast-reconnect-hardcoded.md b/.pi/specs/fast-reconnect-hardcoded.md
new file mode 100644
index 0000000000..83d6596053
--- /dev/null
+++ b/.pi/specs/fast-reconnect-hardcoded.md
@@ -0,0 +1,235 @@
+# Fast Reconnect — Hardcoded Thresholds (PR #1)
+
+## Problem
+
+When a laptop switches Wi-Fi networks or sleeps/resumes, Wave Terminal takes 30–60 seconds to detect the disconnection and reconnect. The root causes are:
+
+1. **Slow stall detection**: The keepalive monitor waits 10 seconds of inactivity before sending a probe, then 10 more seconds before declaring `stalled`.
+2. **Slow auto-disconnect**: Even after `stalled` is declared, it takes 30 seconds (default) before forcing a disconnect.
+3. **Slow reconnect scheduler**: The first reconnect attempt uses a 30-second timeout, and subsequent retries wait 30 seconds between attempts.
+4. **Deadlock risk**: `Close()` and `Connect()` can deadlock on `lifecycleLock` when a fast reconnect is attempted after system resume.
+5. **Event timing bug**: The disconnect `connchange` event was fired after `closeInternal()` returned, so if `client.Close()` blocked on dead TCP, the frontend never learned the connection was down.
+
+## Scope
+
+- **In scope**: Tighten all hardcoded thresholds to laptop-appropriate values; fix the lifecycle lock deadlock; fix the deferred disconnect event.
+- **Out of scope**: UI overlay changes (PR #2); configurable thresholds (PR #3); removing the `degraded` health status.
+
+## Current Architecture
+
+```
+ConnMonitor (5s ticker)
+ ├─ checkConnection() every 5s
+ │ ├─ SendKeepAlive() if >10s inactivity (1s if "urgent")
+ │ └─ set stalled if keepalive unanswered >10s (>5s urgent)
+ ├─ inputNotifyCh branch
+ │ └─ set degraded after 1s if no echo
+ └─ auto-disconnect if stalled >30s
+
+waitForDisconnect() goroutine
+ └─ client.Wait() blocks on dead TCP → natural disconnect
+
+conn.Close()
+ ├─ set Status = Disconnected
+ ├─ FireConnChangeEvent() ← BUG: was deferred after closeInternal
+ └─ closeInternal_withlifecyclelock()
+ └─ client.Close() / listener.Close() / controller.Close()
+
+jobcontroller
+ ├─ onConnectionDown() → scheduleConnectionReconnect()
+ │ └─ loop: AttemptReconnect(timeout=30s), wait 30s, retry
+ └─ HandleSystemResume() → conn.Close() + AttemptReconnect()
+ └─ DEADLOCK: Close() holds lifecycleLock, Connect() needs it
+```
+
+## Changes
+
+### 1. `pkg/remote/conncontroller/conncontroller.go` — Event before blocking close
+
+#### 1a. `Close()` and `waitForDisconnect()`
+
+Move `FireConnChangeEvent()` to fire **immediately** after setting `Status = Disconnected` and `ConnHealthStatus = Good`, before calling `closeInternal_withlifecyclelock()`.
+
+```go
+func (conn *SSHConn) Close() {
+ conn.WithLock(func() {
+ if conn.Status == Status_Connecting {
+ conn.cancelConnectCtx()
+ }
+ if conn.Status == Status_Connected || conn.Status == Status_Connecting {
+ conn.Status = Status_Disconnected
+ }
+ conn.ConnHealthStatus = ConnHealthStatus_Good
+ })
+ // FIRE EVENT FIRST — so UI and jobcontroller react even if cleanup blocks
+ conn.FireConnChangeEvent()
+ conn.closeInternal_withlifecyclelock()
+}
+```
+
+Same pattern in `waitForDisconnect()`:
+
+```go
+func (conn *SSHConn) waitForDisconnect(client *ssh.Client, listener net.Listener, controller *genconn.ConnController) {
+ // ... wait for error ...
+ conn.WithLock(func() {
+ if conn.Client == client {
+ conn.Status = Status_Disconnected
+ conn.ConnHealthStatus = ConnHealthStatus_Good
+ }
+ })
+ conn.FireConnChangeEvent() // ← moved BEFORE closeInternal
+ conn.closeInternal_withlifecyclelock()
+}
+```
+
+#### 1b. `closeInternal_withlifecyclelock()` — Run blocking cleanup in goroutine
+
+To prevent the deadlock where `HandleSystemResume` calls `Close()` then spawns a reconnect goroutine that calls `Connect()`, which also needs `lifecycleLock`:
+
+```go
+func (conn *SSHConn) closeInternal_withlifecyclelock() {
+ conn.lifecycleLock.Lock()
+ defer conn.lifecycleLock.Unlock()
+
+ // Capture old references under conn.lock, nil them immediately
+ // so Connect() sees clean state and can proceed without waiting
+ var oldClient *ssh.Client
+ var oldListener net.Listener
+ var oldController *genconn.ConnController
+ conn.WithLock(func() {
+ oldClient = conn.Client
+ oldListener = conn.Listener
+ oldController = conn.ConnController
+ conn.Client = nil
+ conn.Monitor = nil
+ conn.Listener = nil
+ conn.ConnController = nil
+ })
+
+ // Run the actual blocking Close() calls in a background goroutine
+ // This frees lifecycleLock immediately for Connect() / HandleSystemResume
+ go func() {
+ if oldListener != nil {
+ oldListener.Close()
+ }
+ if oldController != nil {
+ oldController.Close()
+ }
+ if oldClient != nil {
+ oldClient.Close()
+ }
+ }()
+}
+```
+
+### 2. `pkg/remote/conncontroller/connmonitor.go` — Tighten thresholds
+
+Change hardcoded constants to laptop-appropriate values:
+
+| Constant | Current | New | Rationale |
+|----------|---------|-----|-----------|
+| `keepAliveThreshold` (normal) | 10000 (10s) | **3000 (3s)** | Detect dead network faster |
+| `keepAliveThreshold` (urgent) | 1000 (1s) | **1000 (1s)** | Keep — typing already triggers fast path |
+| `stalledThreshold` (normal) | 10000 (10s) | **3000 (3s)** | Declare stall after 3s no response |
+| `stalledThreshold` (urgent) | 5000 (5s) | **2000 (2s)** | Faster when user is actively typing |
+| `getStallDisconnectThresholdMs()` default | 30000 (30s) | **5000 (5s)** | Disconnect 5s after stall, don't wait for TCP |
+| `ticker interval` | 5 * time.Second | **3 * time.Second** | Check more frequently |
+
+```go
+// In checkConnection():
+keepAliveThreshold := int64(3000)
+if urgent {
+ keepAliveThreshold = 1000
+}
+
+stalledThreshold := int64(3000)
+if urgent {
+ stalledThreshold = 2000
+}
+
+// In keepAliveMonitor():
+ticker := time.NewTicker(3 * time.Second)
+
+// In getStallDisconnectThresholdMs():
+return 5000 // 5s default
+```
+
+### 3. `pkg/jobcontroller/jobcontroller.go` — Tighten scheduler
+
+Change hardcoded constants:
+
+| Constant | Current | New | Rationale |
+|----------|---------|-----|-----------|
+| `ConnReconnectInterval` | 30s | **5s** | Retry every 5s instead of 30s |
+| `ConnReconnectMaxDuration` | 5m | **5m** | Keep — don't retry forever |
+| `ConnReconnectAggressiveInterval` | 5s | **3s** | Even faster when network is known down |
+| `ConnReconnectAggressiveDuration` | 2m | **2m** | Keep |
+| First attempt `connectTimeout` | 30s | **5s** | Don't block 30s on first attempt |
+| Aggressive `connectTimeout` | 8s | **5s** | Consistent 5s timeout |
+
+```go
+const ConnReconnectInterval = 5 * time.Second
+const ConnReconnectAggressiveInterval = 3 * time.Second
+
+// In scheduleConnectionReconnect:
+connectTimeout := 5 * time.Second
+if aggressiveMode {
+ connectTimeout = 5 * time.Second
+}
+```
+
+#### Add `context deadline exceeded` to `isNetworkUnreachableError`
+
+```go
+func isNetworkUnreachableError(err error) bool {
+ // ... existing patterns ...
+ if strings.Contains(s, "context deadline exceeded") {
+ return true
+ }
+ return false
+}
+```
+
+This ensures that ANY timeout (including the 5s context deadline) triggers aggressive mode.
+
+### 4. `pkg/remote/sshclient.go` — Dial timing diagnostics (optional, temporary)
+
+Add lightweight timing logs to `connectInternal()` for validating the fix in real-world testing:
+
+```go
+startDial := time.Now()
+// ... dial ...
+log.Printf("[conndebug] dial %s: %v", addr, time.Since(startDial))
+
+startHandshake := time.Now()
+// ... ssh handshake ...
+log.Printf("[conndebug] ssh handshake %s: %v", addr, time.Since(startHandshake))
+```
+
+These can be removed or downgraded after validation.
+
+## Test Plan
+
+| Test | Setup | Expected |
+|------|-------|----------|
+| Disconnect event fires before cleanup | Unit test: mock `client.Close()` that blocks 10s | `FireConnChangeEvent` fires immediately, status shows `disconnected` within 1s |
+| No lifecycleLock deadlock | Unit test: call `Close()` then `Connect()` from same goroutine | `Connect()` proceeds without blocking on `lifecycleLock` |
+| Fast stall detection | Unit test: simulate no keepalive response | `stalled` declared within 3s of keepalive sent |
+| Fast auto-disconnect | Unit test: simulate stall persists | `disconnectOnStall` fires within 5s of stall |
+| Fast reconnect | Unit test: mock `AttemptReconnect` failure with network error | Aggressive mode triggers after first failure, retries every 3s |
+| Real Wi-Fi switch (manual) | Build app, switch SSIDs, observe logs | Total disconnect-to-reconnect < 15s |
+
+## Validation Checklist
+
+- [ ] `task build:backend` succeeds
+- [ ] `go test ./pkg/remote/conncontroller/...` passes
+- [ ] `go test ./pkg/jobcontroller/...` passes
+- [ ] Manual test: switch Wi-Fi, verify reconnect in < 15s
+- [ ] Manual test: system sleep/resume, verify no deadlock/popup
+
+## Notes
+
+- All changes are **hardcoded constants** — no new config fields, no schema changes, no frontend changes.
+- The `degraded` health status and `inputNotifyCh` path are **left untouched** — simplified in PR #3.
+- Diagnostic logging in `sshclient.go` and `jobcontroller.go` is **temporary** for validation and can be removed after confirming behavior.
diff --git a/.pi/specs/phase1-gaps.md b/.pi/specs/phase1-gaps.md
new file mode 100644
index 0000000000..f7fc0c596a
--- /dev/null
+++ b/.pi/specs/phase1-gaps.md
@@ -0,0 +1,200 @@
+# Phase 1–3 Auto-Reconnect Implementation (Complete)
+
+**Branch:** `fix/auto-reconnect-detection-gaps`
+**Date:** 2026-05-27
+**Related:** Issue #7 (problem), Issue #8 (implementation plan), Issue #9 (future: native network hooks)
+
+## What Phase 1 Implements
+
+Auto-disconnect on persistent stall in `ConnMonitor`: when a connection stalls (keepalive timeout) for longer than a configurable threshold (default 30s), the connection is forcibly closed via `conn.Close()`. This converts a zombie "Connected" state into "Disconnected," allowing the existing auto-reconnect machinery to detect the state change.
+
+**Files changed:**
+- `pkg/remote/conncontroller/connmonitor.go` — stall tracking + disconnect logic
+- `pkg/wconfig/settingsconfig.go` — `ConnStallAutoDisconnect`, `ConnStallDisconnectThreshold`
+- `pkg/remote/sshclient.go` — merge function for new config fields
+
+## What Phase 2 Implements
+
+macOS sleep/wake fast-path: `NotifySystemResumeCommand` is no longer a no-op. On system resume (Electron `powerMonitor` event), the Go backend iterates all connections with durable jobs, forces disconnect on stalled zombies, and immediately spawns `AttemptReconnect()` goroutines — bypassing the 30s scheduler tick.
+
+**Files changed:**
+- `pkg/wshrpc/wshserver/wshserver.go` — `NotifySystemResumeCommand` calls `HandleSystemResume`
+- `pkg/jobcontroller/jobcontroller.go` — `HandleSystemResume` added
+
+## What Phase 3 Implements
+
+Aggressive reconnect scheduler: when `AttemptReconnect` fails with a network-unreachable error (dial tcp i/o timeout, no route, DNS failure), the scheduler switches from 30s to 5s interval for 2 minutes. This catches Wi-Fi/VPN return quickly without any native modules.
+
+**Files changed:**
+- `pkg/jobcontroller/jobcontroller.go` — `isNetworkUnreachableError`, aggressive mode in `scheduleConnectionReconnect`
+
+## Gaps Found
+
+### GAP-1: Disconnect→Reconnect loop is incomplete (critical)
+
+**The auto-disconnect fires, but nothing reconnects the connection automatically.**
+
+After `disconnectOnStall()` calls `conn.Close()`:
+
+1. `conn.Status` → `Disconnected` + `FireConnChangeEvent()` fires
+2. `handleConnChangeEvent` records `cs.actual = false` → `reconcileConn` → `onConnectionDown()` (just logs, no reconnect)
+3. Individual job routes go down → `handleRouteDownEvent` → `attemptAutoReconnect(jobId, connName)`
+4. `attemptAutoReconnect` checks `conncontroller.IsConnected(connName)` → returns `false` → **logs "connection is down, skipping auto-reconnect"** and returns
+5. Connection stays **Disconnected** indefinitely — user must still manually reconnect
+
+**Root cause:** `onConnectionUp` (which triggers `ReconnectJob` for all durable sessions) only fires when a connection transitions **to** Connected. `attemptAutoReconnect` only tries when the connection is **still up** after a route drops. Neither mechanism calls `conn.Connect()` to bring a Disconnected connection back.
+
+**Impact:** The user experience is essentially unchanged — they still see a disconnected session and must manually click "Connect." The only improvement is that the state accurately shows "Disconnected" instead of a zombie "Connected (stalled)" state.
+
+**Fix needed:** A mechanism that calls `conn.Connect()` when a connection becomes Disconnected and has durable jobs that need it. Options:
+
+| Option | Description | Effort |
+|--------|-------------|--------|
+| A. `onConnectionDown` reconnect scheduler | When connection goes down with running durable jobs, schedule periodic `Connect()` attempts (e.g., every 30s for 5 min) | ~80 lines in `jobcontroller.go` |
+| B. Phase 2: `NotifySystemResumeCommand` | On macOS wake, force `Connect()` for all previously-connected sessions | ~50 lines in `wshserver.go` + helper |
+| C. Phase 3: network-online polling | Detect network return and trigger `Connect()` | ~100 lines, cross-platform |
+| D. Hybrid A+B | A for general network drops, B for immediate wake response | Recommended |
+
+Option A is the most general — it covers sleep/wake, Wi-Fi drops, VPN changes, and any other network interruption without platform-specific detection. Option B adds an immediate fast-path for the most common user-facing scenario (macOS wake).
+
+---
+
+### GAP-2: Urgent guard prevents disconnect on dead connections (bug)
+
+When a connection is truly dead (TCP RST never received — the exact macOS sleep scenario), user keystrokes still call `NotifyInput()` which updates `LastInputTime`. This makes `isUrgent()` return `true` indefinitely, because the user keeps typing into a dead socket.
+
+The stall-disconnect code:
+```go
+} else if !urgent {
+ // only disconnects if user ISN'T typing
+ thresholdMs := cm.getStallDisconnectThresholdMs()
+ if now-stallStart > thresholdMs {
+ cm.disconnectOnStall()
+ }
+}
+```
+
+**If `urgent` stays true, `disconnectOnStall()` never fires.** The connection remains in zombie "Connected + Stalled" state forever — the exact problem Phase 1 is meant to fix.
+
+**Fix:** When the connection health status is already `Stalled`, the urgent guard should be relaxed. Options:
+
+| Option | Description |
+|--------|-------------|
+| A. Remove urgent guard entirely for stall-disconnect | User typing on a stalled connection is going nowhere — disconnect regardless |
+| B. Cap urgent-protected stall duration | Still honor urgent for the first threshold period, but disconnect after 2× threshold even if urgent |
+| C. Check stall health in urgent | `urgent = isUrgent() && cm.Conn.GetConnHealthStatus() != ConnHealthStatus_Stalled` |
+
+Option A is simplest and most correct: if the keepalive monitor says the connection is stalled, the user's keystrokes are not reaching the remote. Disconnecting is the right action regardless of local input activity.
+
+---
+
+### GAP-3: No unit tests
+
+No tests exist for any of the new code:
+- `disconnectOnStall()` logic
+- `getStallDisconnectThresholdMs()` config reading
+- `shouldAutoDisconnectOnStall()` config reading
+- `StallStartTime` tracking in `checkConnection()`
+- `urgent` guard interaction with stall-disconnect
+
+The existing test pattern in `jobcontroller_test.go` uses hand-written mocks and `t.Parallel()`. New tests should follow the same pattern.
+
+**Suggested test cases:**
+
+| Test | Scenario | Expected |
+|------|----------|----------|
+| StallDisconnectAfterThreshold | Stall persists >30s, not urgent | `conn.Close()` called |
+| NoDisconnectWhenUrgent | Stall persists >30s, user typing | `conn.Close()` NOT called (current behavior) |
+| NoDisconnectWhenStallClears | Stall <30s then clears | `StallStartTime` reset, no disconnect |
+| DisconnectRespectsConfig | `ConnStallAutoDisconnect=false` | `conn.Close()` NOT called |
+| ThresholdFromConfig | `ConnStallDisconnectThreshold=10` (10s) | Disconnect after 10s, not 30s |
+| UrgentOnDeadConnection | Stall + user typing on dead socket | Should disconnect (requires GAP-2 fix) |
+
+---
+
+### GAP-4: No user-facing documentation
+
+New config keywords `conn:stallautodisconnect` and `conn:stalldisconnectthreshold` are not documented in `docs/docs/connections.mdx`. Users have no way to discover or configure these settings.
+
+**Fix:** Add a table entry in `docs/docs/connections.mdx` (similar to existing `conn:askbeforewshinstall` entry):
+
+| Keyword | Type | Default | Description |
+|---------|------|---------|-------------|
+| `conn:stallautodisconnect` | bool | true | Automatically disconnect SSH connections when stalled for the threshold duration |
+| `conn:stalldisconnectthreshold` | int (seconds) | 30 | How long (in seconds) a connection must be stalled before auto-disconnecting |
+
+---
+
+### GAP-5: `StallStartTime` not explicitly reset on monitor recreation
+
+When a connection reconnects, `connectInternal()` creates a new `ConnMonitor` via `MakeConnMonitor()`. The new `StallStartTime` defaults to 0 (correct for `atomic.Int64`). However, if the old monitor's `Close()` is called while a stall-disconnect goroutine is still running, there's a subtle timing window:
+
+1. `disconnectOnStall()` launches `go func() { cm.Conn.Close() }()`
+2. `Close()` on the connection calls `conn.Monitor.Close()` (cancels the ticker)
+3. The goroutine from step 1 still references `cm` (the old monitor)
+4. `conn.Close()` runs (from both the goroutine AND the `waitForDisconnect` path)
+
+This is not a functional bug — `Close()` is idempotent (checks status via `lifecycleLock`) — but it means two `Close()` calls happen, one from the stall-disconnect goroutine and one from `waitForDisconnect`. The log message `"disconnecting due to persistent stall"` fires before `Close()`, which is fine for debugging.
+
+**Assessment:** Low risk, no code change needed. The `lifecycleLock` in `Close()` prevents double-status-change. Just noting for awareness.
+
+---
+
+### GAP-6: Config field naming — unit not obvious
+
+`ConnStallDisconnectThreshold` is stored as `*int` in seconds, but the field name doesn't indicate the unit. Compare with `SshPort` (also `*string`, not `*int16` — different concern but same pattern of implicit units).
+
+**Suggestion:** Rename to `ConnStallDisconnectThresholdSec` or add a doc comment. Low priority — the `getStallDisconnectThresholdMs()` conversion makes the unit clear in code.
+
+---
+
+## Priority Order
+
+| Priority | Gap | Action | Impact |
+|----------|-----|--------|--------|
+| **P0** | GAP-1 | Implement disconnect→reconnect loop | Without this, Phase 1 doesn't actually fix the user-facing problem |
+| **P0** | GAP-2 | Fix urgent guard on dead connections | Without this, Phase 1 doesn't fire in the primary scenario (macOS sleep) |
+| **P1** | GAP-3 | Add unit tests | No coverage for new logic |
+| **P1** | GAP-4 | Add documentation | Users can't discover new config |
+| **P2** | GAP-5 | Document timing window | No code change, awareness only |
+| **P2** | GAP-6 | Consider field rename | Cosmetic |
+
+## Resolution
+
+All gaps were addressed across commits `a157b234` (Phase 1 fixes), `1672eb37` (Phase 2 wake fast-path), and `ec341ebb` (Phase 3 aggressive scheduler).
+
+| Phase | Gap | Fix | Lines | File(s) |
+|-------|-----|-----|-------|---------|
+| Phase 1 | GAP-1 | Reconnect scheduler in `onConnectionDown` + `AttemptReconnect` helper | ~120 | `pkg/jobcontroller/jobcontroller.go`, `pkg/remote/conncontroller/conncontroller.go` |
+| Phase 1 | GAP-2 | Remove `!urgent` guard from stall-disconnect | 2 | `pkg/remote/conncontroller/connmonitor.go` |
+| Phase 1 | GAP-3 | 11 new tests in `conncontroller_test.go`, 2 in `jobcontroller_test.go` | ~300 | `pkg/remote/conncontroller/conncontroller_test.go`, `pkg/jobcontroller/jobcontroller_test.go` |
+| Phase 1 | GAP-4 | Document `conn:stallautodisconnect` and `conn:stalldisconnectthreshold` | 2 | `docs/docs/connections.mdx` |
+| Phase 2 | — | `NotifySystemResumeCommand` fast-path wake | ~35 | `pkg/wshrpc/wshserver/wshserver.go`, `pkg/jobcontroller/jobcontroller.go` |
+| Phase 3 | — | Aggressive scheduler (5s interval on network-unreachable) | ~50 | `pkg/jobcontroller/jobcontroller.go` |
+
+**Test hooks added:** `connectInternalTestHook`, `getConnectionConfigTestHook` in `conncontroller.go`; `hasRunningDurableJobsTestHook` in `jobcontroller.go`.
+
+## End-to-End Auto-Reconnect Flow
+
+### Sleep/Wake (macOS)
+1. macOS sleep → network drops
+2. Wake → `powerMonitor.resume` → `NotifySystemResumeCommand`
+3. `HandleSystemResume` → disconnect stalled + `AttemptReconnect()` immediately
+4. Network back? `Connect()` succeeds → `onConnectionUp` → `ReconnectJob`
+5. **Delay: ~1-2s**
+
+### Wi-Fi/VPN Change
+1. Wi-Fi drops/VPN toggles → connection stalls
+2. Stall >30s → `disconnectOnStall()` → `Status = Disconnected`
+3. `onConnectionDown` → scheduler starts
+4. First `AttemptReconnect` fails with `dial tcp: i/o timeout`
+5. Scheduler switches to aggressive: **5s interval for 2 minutes**
+6. User switches to good Wi-Fi → next 5s tick: `Connect()` succeeds
+7. **Delay: ~5-10s** (vs. ~60s before)
+
+### General Network Drop
+1. Network drops → keepalive stall detected (~20s)
+2. Stall threshold (~30s) → auto-disconnect
+3. Scheduler tries every 30s for 5 minutes
+4. Network returns within 5min → auto-reconnect, sessions restored
+5. **Delay: ~30-60s** (worst case, but fully automatic)
\ No newline at end of file
diff --git a/.pi/specs/portforwarding.md b/.pi/specs/portforwarding.md
new file mode 100644
index 0000000000..a42e377bff
--- /dev/null
+++ b/.pi/specs/portforwarding.md
@@ -0,0 +1,438 @@
+# SSH Port Forwarding Implementation Spec
+
+## Problem
+
+Wave Terminal parses `~/.ssh/config` for connection settings but ignores `LocalForward`, `RemoteForward`, and `DynamicForward` directives. Users who define port forwarding rules in their SSH config get no forwarding when connecting through Wave.
+
+## Scope
+
+- **In scope**: `LocalForward` and `RemoteForward` parsed from `~/.ssh/config` and `connections.json`
+- **Out of scope**: `DynamicForward` (requires SOCKS5 handler not in stdlib), CLI flags on `wsh ssh`, UI status indicators
+
+## Current Architecture
+
+```
+~/.ssh/config ──┐
+ │
+connections.json ─┼──→ findSshConfigKeywords() / ConnKeywords struct
+ │
+wsh ssh flags ─┘
+ │
+ ▼
+ ConnectToClient() — merges keywords, creates *ssh.Client
+ │
+ ▼
+ SSHConn.connectInternal() — stores client, starts monitor/wsh
+ │
+ ▼
+ SSHConn.Close() — tears down client, monitor, domain socket
+```
+
+The merged `ConnKeywords` are consumed inside `ConnectToClient()` to build `ssh.ClientConfig` and are **never returned** to the caller. `conncontroller` only receives `connFlags` (CLI/frontend flags), not the full merged config.
+
+## Changes
+
+### 1. `pkg/wconfig/settingsconfig.go` — ConnKeywords struct
+
+Add two fields to `ConnKeywords`:
+
+```go
+SshLocalForward []string `json:"ssh:localforward,omitempty"`
+SshRemoteForward []string `json:"ssh:remoteforward,omitempty"`
+```
+
+Placement: after `SshGlobalKnownHostsFile`, before the closing `}`.
+
+### 2. `pkg/remote/sshclient.go` — Config parsing
+
+#### 2a. `findSshConfigKeywords()` — Parse from `~/.ssh/config`
+
+Add after the `GlobalKnownHostsFile` parsing block (before the `return`):
+
+```go
+localForwardRaw := WaveSshConfigUserSettings().GetAll(hostPattern, "LocalForward")
+for i := 0; i < len(localForwardRaw); i++ {
+ localForwardRaw[i] = trimquotes.TryTrimQuotes(localForwardRaw[i])
+}
+sshKeywords.SshLocalForward = localForwardRaw
+
+remoteForwardRaw := WaveSshConfigUserSettings().GetAll(hostPattern, "RemoteForward")
+for i := 0; i < len(remoteForwardRaw); i++ {
+ remoteForwardRaw[i] = trimquotes.TryTrimQuotes(remoteForwardRaw[i])
+}
+sshKeywords.SshRemoteForward = remoteForwardRaw
+```
+
+This follows the exact pattern used for `IdentityFile` (multi-value keyword via `GetAll` + quote trimming).
+
+#### 2b. `findSshDefaults()` — Default values
+
+Add to the defaults function:
+
+```go
+sshKeywords.SshLocalForward = []string{}
+sshKeywords.SshRemoteForward = []string{}
+```
+
+#### 2c. `mergeKeywords()` — Cascade merging
+
+Add to the merge function (follows the `SshProxyJump` pattern):
+
+```go
+if newKeywords.SshLocalForward != nil {
+ outKeywords.SshLocalForward = newKeywords.SshLocalForward
+}
+if newKeywords.SshRemoteForward != nil {
+ outKeywords.SshRemoteForward = newKeywords.SshRemoteForward
+}
+```
+
+#### 2d. `ConnectToClient()` — Return merged keywords
+
+Change the signature from:
+
+```go
+func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.Client, jumpNum int32, connFlags *wconfig.ConnKeywords) (*ssh.Client, int32, error)
+```
+
+To:
+
+```go
+func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.Client, jumpNum int32, connFlags *wconfig.ConnKeywords) (*ssh.Client, int32, *wconfig.ConnKeywords, error)
+```
+
+The `sshKeywords` variable already exists at the point of the final return. Change all return statements:
+
+- `return nil, jumpNum, ConnectionError{...}` → `return nil, jumpNum, nil, ConnectionError{...}`
+- `return client, debugInfo.JumpNum, nil` → `return client, debugInfo.JumpNum, sshKeywords, nil`
+- `return nil, debugInfo.JumpNum, ConnectionError{...}` → `return nil, debugInfo.JumpNum, nil, ConnectionError{...}`
+
+### 3. `pkg/remote/conncontroller/conncontroller.go` — Runtime forwarding
+
+#### 3a. `SSHConn` struct — Store forwarding state
+
+Add fields:
+
+```go
+LocalForwardListeners []net.Listener // local listeners for LocalForward
+RemoteForwardListeners []net.Listener // remote listeners (from client.Listen) for RemoteForward
+```
+
+#### 3b. `copyBoth` helper (unexported)
+
+Add near the `startPortForwarding` method (same file, package-private):
+
+```go
+func copyBoth(a net.Conn, b net.Conn) {
+ var wg sync.WaitGroup
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ io.Copy(a, b)
+ }()
+ go func() {
+ defer wg.Done()
+ io.Copy(b, a)
+ }()
+ wg.Wait()
+ a.Close()
+ b.Close()
+}
+```
+
+#### 3c. Forwarding setup function
+
+Add a new unexported method:
+
+```go
+func (conn *SSHConn) startPortForwarding(ctx context.Context, keywords *wconfig.ConnKeywords) {
+ client := conn.GetClient()
+ if client == nil {
+ return
+ }
+
+ // LocalForward: listen locally, dial through SSH to remote
+ for _, fwd := range keywords.SshLocalForward {
+ parts := strings.Fields(fwd)
+ if len(parts) != 2 {
+ conn.Infof(ctx, "LocalForward: skipping malformed rule: %q\n", fwd)
+ continue
+ }
+ bindAddr, dest := parts[0], parts[1]
+ go func() {
+ defer panichandler.PanicHandler("conncontroller:localforward", recover())
+ listener, err := net.Listen("tcp", bindAddr)
+ if err != nil {
+ conn.Infof(ctx, "LocalForward %s: failed to listen: %v\n", fwd, err)
+ return
+ }
+ conn.WithLock(func() {
+ conn.LocalForwardListeners = append(conn.LocalForwardListeners, listener)
+ })
+ conn.Infof(ctx, "LocalForward started: %s -> %s\n", bindAddr, dest)
+ for {
+ localConn, err := listener.Accept()
+ if err != nil {
+ return
+ }
+ go func(dest string) {
+ defer panichandler.PanicHandler("conncontroller:localforward-tunnel", recover())
+ remoteConn, err := client.Dial("tcp", dest)
+ if err != nil {
+ localConn.Close()
+ return
+ }
+ copyBoth(localConn, remoteConn)
+ }(dest)
+ }
+ }()
+ }
+
+ // RemoteForward: listen on remote via SSH, dial locally
+ for _, fwd := range keywords.SshRemoteForward {
+ parts := strings.Fields(fwd)
+ if len(parts) != 2 {
+ conn.Infof(ctx, "RemoteForward: skipping malformed rule: %q\n", fwd)
+ continue
+ }
+ bindAddr, dest := parts[0], parts[1]
+ go func() {
+ defer panichandler.PanicHandler("conncontroller:remoteforward", recover())
+ listener, err := client.Listen("tcp", bindAddr)
+ if err != nil {
+ conn.Infof(ctx, "RemoteForward %s: failed to listen: %v\n", fwd, err)
+ return
+ }
+ conn.WithLock(func() {
+ conn.RemoteForwardListeners = append(conn.RemoteForwardListeners, listener)
+ })
+ conn.Infof(ctx, "RemoteForward started: %s -> %s\n", bindAddr, dest)
+ for {
+ remoteConn, err := listener.Accept()
+ if err != nil {
+ return
+ }
+ go func(dest string) {
+ defer panichandler.PanicHandler("conncontroller:remoteforward-tunnel", recover())
+ localConn, err := net.Dial("tcp", dest)
+ if err != nil {
+ remoteConn.Close()
+ return
+ }
+ copyBoth(localConn, remoteConn)
+ }(dest)
+ }
+ }()
+ }
+}
+```
+
+Notes:
+- Follows the existing goroutine pattern: `defer panichandler.PanicHandler("...", recover())`
+- Listeners are stored on the struct via `conn.WithLock` for cleanup
+- Uses `conn.Infof` for debug logging (consistent with existing connection debug output)
+- `copyBoth` (unexported helper) for bidirectional tunneling (spawns two `io.Copy` goroutines, waits for both, then closes both connections)
+
+#### 3d. `connectInternal()` — Call forwarding setup
+
+Change the `ConnectToClient` call to capture the merged keywords:
+
+```go
+client, _, sshKeywords, err := remote.ConnectToClient(ctx, conn.Opts, nil, 0, connFlags)
+```
+
+After the client is stored and the monitor is started, add:
+
+```go
+// Start port forwarding with merged SSH config keywords
+if sshKeywords != nil {
+ conn.startPortForwarding(ctx, sshKeywords)
+}
+```
+
+Placement: after the `conn.WithLock` block that sets `conn.Client` and `conn.Monitor`, before the `waitForDisconnect` goroutine.
+
+#### 3e. `closeInternal_withlifecyclelock()` — Cleanup
+
+Add forwarding listener capture alongside the existing `oldListener` capture, then close them in the cleanup goroutine:
+
+```go
+var oldLocalForwardListeners []net.Listener
+var oldRemoteForwardListeners []net.Listener
+conn.WithLock(func() {
+ // ... existing oldClient, oldListener, oldController, oldMonitor capture ...
+ oldLocalForwardListeners = conn.LocalForwardListeners
+ conn.LocalForwardListeners = nil
+ oldRemoteForwardListeners = conn.RemoteForwardListeners
+ conn.RemoteForwardListeners = nil
+})
+
+// In the cleanup goroutine (after oldMonitor.Close()):
+for _, l := range oldLocalForwardListeners {
+ l.Close()
+}
+for _, l := range oldRemoteForwardListeners {
+ l.Close()
+}
+```
+
+This follows the existing pattern: references are captured and nilled under `conn.WithLock` (protected by the `expectedClient` stale-goroutine guard), then closed in the background goroutine so `lifecycleLock` is freed immediately.
+
+### 4. Call site updates
+
+Every caller of `remote.ConnectToClient` must handle the new 4th return value.
+
+#### `pkg/remote/conncontroller/conncontroller.go`
+
+Already covered in 3c above.
+
+#### `cmd/test-conn/main-test-conn.go`
+
+No direct `ConnectToClient` calls in `cmd/test-conn/` — it uses `conn.Connect()` → `connectInternal()` → `ConnectToClient()` indirectly. No changes needed.
+
+#### Other direct call sites
+
+Run `grep -rn "ConnectToClient" --include="*.go" .` to find any direct callers. As of this spec, only these direct calls exist:
+- `pkg/remote/sshclient.go` — the function definition and the recursive ProxyJump call
+- `pkg/remote/conncontroller/conncontroller.go` — `connectInternal`
+
+The recursive ProxyJump call (line ~1057) should capture the returned keywords with `_` since proxy connections don't need forwarding.
+
+### 5. Tests
+
+#### `pkg/remote/sshclient_test.go` (new file)
+
+Table-driven tests for config parsing. No network required.
+
+```go
+package remote
+
+import "testing"
+
+func TestFindSshConfigKeywords_LocalForward(t *testing.T) {
+ t.Parallel()
+ // Uses a temp ~/.ssh/config with LocalForward directives
+ // Verifies SshLocalForward is populated correctly
+}
+
+func TestMergeKeywords_LocalForward(t *testing.T) {
+ t.Parallel()
+ tests := []struct {
+ name string
+ old *wconfig.ConnKeywords
+ new *wconfig.ConnKeywords
+ wantLocal []string
+ wantRemote []string
+ }{
+ {
+ name: "new overrides old",
+ old: &wconfig.ConnKeywords{SshLocalForward: []string{"8080 localhost:80"}},
+ new: &wconfig.ConnKeywords{SshLocalForward: []string{"9090 localhost:90"}},
+ wantLocal: []string{"9090 localhost:90"},
+ },
+ {
+ name: "nil new preserves old",
+ old: &wconfig.ConnKeywords{SshLocalForward: []string{"8080 localhost:80"}},
+ new: &wconfig.ConnKeywords{},
+ wantLocal: []string{"8080 localhost:80"},
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := mergeKeywords(tt.old, tt.new)
+ // assert got.SshLocalForward matches tt.wantLocal
+ })
+ }
+}
+```
+
+#### `pkg/remote/conncontroller/conncontroller_test.go` (new file)
+
+Integration-style test using `net.Listener` (no real SSH):
+
+```go
+package conncontroller
+
+func TestLocalForwardStartsAndStops(t *testing.T) {
+ // Create a mock SSHConn with a real net.Listener as the "remote"
+ // Verify LocalForward listener is created on startPortForwarding
+ // Verify it's closed on closeInternal_withlifecyclelock
+}
+```
+
+This follows the `sshagent_unix_test.go` pattern: real sockets, no SSH daemon.
+
+### 6. Documentation
+
+#### `docs/docs/connections.mdx`
+
+**SSH Config Parsing table** — Add rows:
+
+| Keyword | Description |
+|---------|-------------|
+| LocalForward | Can be specified multiple times. Format: `bind_address destination` (e.g., `8080 localhost:80` or `127.0.0.1:8080 localhost:80`). Listens on the local machine and forwards connections through the SSH tunnel to the remote destination. |
+| RemoteForward | Can be specified multiple times. Format: `bind_address destination` (e.g., `9090 localhost:3000`). Listens on the remote machine and forwards connections back to the local destination. Requires `AllowTcpForwarding` on the remote sshd. |
+
+**Internal SSH Configuration table** — Add rows:
+
+| Keyword | Description |
+|---------|-------------|
+| ssh:localforward | A list of strings for local port forwarding rules. Format: `"8080 localhost:80"`. Can be used to override or supplement `~/.ssh/config` values. |
+| ssh:remoteforward | A list of strings for remote port forwarding rules. Format: `"9090 localhost:3000"`. Can be used to override or supplement `~/.ssh/config` values. |
+
+**New example section** after "Example SSH Config Host":
+
+```markdown
+### Port Forwarding
+
+Port forwarding rules from `~/.ssh/config` are automatically applied when you connect through Wave:
+
+```
+Host myserver
+ User username
+ HostName 203.0.113.254
+ LocalForward 8080 localhost:80
+ RemoteForward 9090 localhost:3000
+```
+
+Connecting to `myserver` will listen on local port 8080 (forwarded to the remote's localhost:80) and listen on the remote's port 9090 (forwarded to your local localhost:3000).
+
+Port forwarding can also be defined entirely in `connections.json`:
+
+```json
+{
+ "myusername@myhost": {
+ "ssh:localforward": ["8080 localhost:80"],
+ "ssh:remoteforward": ["9090 localhost:3000"]
+ }
+}
+```
+```
+
+#### `docs/docs/releasenotes.mdx`
+
+Add entry under the current development version.
+
+## Error Handling
+
+- Malformed forwarding rules (wrong number of fields) are logged via `conn.Infof` and skipped — they never break the connection
+- Listener bind failures (port already in use) are logged — the connection proceeds without that specific forward
+- Tunnel dial failures log and close the individual connection — other tunnels and the SSH session continue
+- All forwarding goroutines use `panichandler.PanicHandler` to prevent crashes from propagating
+
+## Lifecycle
+
+| Event | Action |
+|-------|--------|
+| Connect starts | `ConnectToClient` returns merged keywords including forwarding rules |
+| Client established | `startPortForwarding` spawns goroutines, stores listeners on `SSHConn` |
+| Connection active | Tunnels run via `copyBoth`; SSH transport activity keeps connection alive |
+| Disconnect starts | `closeInternal_withlifecyclelock` closes all forwarding listeners under `lifecycleLock` |
+| Client closes | `client.Close()` tears down remote listeners and all in-flight tunnels |
+
+## Out of Scope (Future)
+
+- **`DynamicForward`** — Requires a SOCKS5 proxy handler. The `golang.org/x/crypto/ssh` library has no built-in one. Would need a third-party package or custom ~200-line SOCKS5 implementation.
+- **`wsh ssh -L` / `-R` CLI flags** — Can be added to `wshcmd-ssh.go` later, following the existing `-i`/`-l`/`-p` flag pattern.
+- **UI status indicator** — A block header icon showing active port forwards (similar to the wsh icon).
+- **`GatewayPorts`** support — The `ssh` keyword for binding remote forwards to all interfaces.
diff --git a/.pi/specs/reconnect-ui-overlay.md b/.pi/specs/reconnect-ui-overlay.md
new file mode 100644
index 0000000000..63f75685c6
--- /dev/null
+++ b/.pi/specs/reconnect-ui-overlay.md
@@ -0,0 +1,337 @@
+# Reconnect UI Overlay with Retry Transparency (PR #2)
+
+## Problem
+
+When a connection drops, the user experience is poor:
+
+1. **No immediate feedback on disconnect**: The terminal just stops responding. The `disconnected` status is only visible in a small toolbar icon — there's no overlay explaining what happened.
+2. **No visibility into retry attempts**: The reconnect scheduler loops in the background, but the user sees nothing — just a frozen terminal. They don't know if retry #1 failed, when retry #2 will happen, or whether the app is even trying.
+3. **Stale `stalled` overlay**: The existing `StalledOverlay` only shows after `stalled` is declared (which may still take seconds even with PR #1's faster thresholds). During `disconnected` → `connecting` → retry cycles, there's zero UI feedback.
+
+## Scope
+
+- **In scope**: Rich overlay for `disconnected`, `connecting` (retry), and inter-retry countdown; backend events to feed the UI.
+- **Out of scope**: Configurable thresholds (PR #3); changing the monitor/scheduler logic itself (PR #1 handles that).
+
+## Desired User Experience
+
+```
+[09:16:43] User switches Wi-Fi
+[09:16:45] Overlay appears: "Connection lost — retrying in 5s"
+[09:16:46] Countdown: 4… 3… 2… 1…
+[09:16:49] Overlay: "Attempt 1 — connecting to user@host…"
+[09:16:51] Attempt fails (no route)
+[09:16:51] Overlay: "No route to host — retrying in 3s"
+[09:16:52] Countdown: 2… 1…
+[09:16:54] Overlay: "Attempt 2 — connecting…"
+[09:16:56] Wi-Fi ready! Reconnects.
+[09:16:57] Overlay vanishes, terminal resumes.
+```
+
+Compare to current: blank frozen screen for 30–60 seconds, then sudden reconnect.
+
+## Architecture
+
+### Backend: New Retry State & Events
+
+The backend currently emits `connchange` events with `ConnStatus`. We need additional per-connection retry state and more granular events.
+
+**Option A: Extend `ConnStatus`**
+Add fields to `wshrpc.ConnStatus`:
+```go
+type ConnStatus struct {
+ // ... existing fields ...
+ ReconnectAttempt int `json:"reconnectattempt,omitempty"`
+ ReconnectNextAttempt int64 `json:"reconnectnextattempt,omitempty"` // UnixMilli
+ ReconnectError string `json:"reconnecterror,omitempty"`
+}
+```
+
+**Option B: Separate retry event**
+Emit a new `wps.Event_ReconnectAttempt` with attempt details. Simpler, doesn't bloat `ConnStatus`.
+
+Recommendation: **Option A** — the overlay already consumes `ConnStatus`, so extending it is least frontend rework.
+
+**New events to emit from `scheduleConnectionReconnect`:**
+- When attempt starts: set `ReconnectAttempt = N`, `ReconnectNextAttempt = 0`, fire `connchange`
+- When attempt fails: set `ReconnectError = err.Error()`, `ReconnectNextAttempt = time.Now().Add(interval).UnixMilli()`, fire `connchange`
+- When attempt succeeds: clear retry fields, fire `connchange`
+- When entering aggressive mode: same pattern, shorter interval
+
+### Frontend: New Overlay States
+
+The current `ConnStatusOverlay` only handles:
+- `stalled` → `StalledOverlay` (yellow warning bar)
+- `error` / `disconnected` / `connected` → generic overlay
+
+We need a unified overlay that handles the full reconnect lifecycle:
+
+```tsx
+// Unified states:
+const showDisconnected = connStatus.status === "disconnected" && !connStatus.connected;
+const showRetrying = connStatus.status === "connecting" && connStatus.reconnectattempt > 0;
+const showCountdown = connStatus.reconnectnextattempt > 0 && connStatus.status === "disconnected";
+```
+
+**Overlay components:**
+1. **`DisconnectedOverlay`** — immediate on disconnect
+ - Red/yellow icon
+ - "Disconnected from `host`"
+ - Error detail (TCP error, connserver error)
+ - Reconnect button (manual trigger)
+ - If retry is scheduled: "Auto-retrying in `countdown`s"
+
+2. **`RetryingOverlay`** — during an active attempt
+ - Spinner icon
+ - "Attempt `N` — connecting to `host`…"
+ - Cancel button (stop scheduler)
+
+3. **`CountdownOverlay`** — between attempts
+ - Timer icon
+ - "Last attempt failed: `error`"
+ - "Retrying in `countdown`s"
+ - Reconnect now button (skip wait)
+
+## Changes
+
+### 1. Backend: `pkg/wshrpc/wshrpctypes.go` — Extend `ConnStatus`
+
+Add optional retry fields:
+
+```go
+type ConnStatus struct {
+ Status string `json:"status"`
+ ConnHealthStatus string `json:"connhealthstatus"`
+ WshEnabled bool `json:"wshenabled"`
+ Connection string `json:"connection"`
+ Connected bool `json:"connected"`
+ HasConnected bool `json:"hasconnected"`
+ ActiveConnNum int `json:"activeconnnum"`
+ Error string `json:"error"`
+ WshError string `json:"wsherror"`
+ NoWshReason string `json:"nowshreason"`
+ WshVersion string `json:"wshversion"`
+ LastActivityBeforeStalledTime int64 `json:"lastactivitybeforestalledtime,omitempty"`
+ KeepAliveSentTime int64 `json:"keepalivesenttime,omitempty"`
+ // NEW:
+ ReconnectAttempt int `json:"reconnectattempt,omitempty"`
+ ReconnectNextAttempt int64 `json:"reconnectnextattempt,omitempty"`
+ ReconnectError string `json:"reconnecterror,omitempty"`
+}
+```
+
+### 2. Backend: `pkg/jobcontroller/jobcontroller.go` — Emit retry state
+
+In `scheduleConnectionReconnect`, before/after each `AttemptReconnect` call, update the connection's retry state and fire events:
+
+```go
+func scheduleConnectionReconnect(connName string) {
+ // ... existing setup ...
+ attempt := 0
+ for {
+ // ... existing checks ...
+
+ attempt++
+ updateRetryState(connName, attempt, 0, "") // active attempt
+
+ ctx, cancelFn := context.WithTimeout(context.Background(), connectTimeout)
+ err := conncontroller.AttemptReconnect(ctx, connName)
+ cancelFn()
+
+ if err != nil {
+ isNetErr := isNetworkUnreachableError(err)
+ interval := ConnReconnectInterval
+ if aggressiveMode {
+ interval = ConnReconnectAggressiveInterval
+ }
+ nextAttempt := time.Now().Add(interval).UnixMilli()
+ updateRetryState(connName, attempt, nextAttempt, err.Error())
+ // ... existing logging ...
+ } else {
+ clearRetryState(connName)
+ return
+ }
+
+ // ... wait for interval ...
+ }
+}
+```
+
+Add helper functions:
+
+```go
+func updateRetryState(connName string, attempt int, nextAttempt int64, errMsg string) {
+ connOpts, _ := remote.ParseOpts(connName)
+ conn := conncontroller.GetConn(connOpts)
+ if conn != nil {
+ conn.SetReconnectState(attempt, nextAttempt, errMsg)
+ conn.FireConnChangeEvent()
+ }
+}
+
+func clearRetryState(connName string) {
+ connOpts, _ := remote.ParseOpts(connName)
+ conn := conncontroller.GetConn(connOpts)
+ if conn != nil {
+ conn.ClearReconnectState()
+ conn.FireConnChangeEvent()
+ }
+}
+```
+
+### 3. Backend: `pkg/remote/conncontroller/conncontroller.go` — Store retry state
+
+Add fields to `SSHConn`:
+
+```go
+type SSHConn struct {
+ // ... existing fields ...
+ ReconnectAttempt int
+ ReconnectNextAttempt int64
+ ReconnectError string
+}
+```
+
+Add methods:
+
+```go
+func (conn *SSHConn) SetReconnectState(attempt int, nextAttempt int64, err string) {
+ conn.WithLock(func() {
+ conn.ReconnectAttempt = attempt
+ conn.ReconnectNextAttempt = nextAttempt
+ conn.ReconnectError = err
+ })
+}
+
+func (conn *SSHConn) ClearReconnectState() {
+ conn.WithLock(func() {
+ conn.ReconnectAttempt = 0
+ conn.ReconnectNextAttempt = 0
+ conn.ReconnectError = ""
+ })
+}
+```
+
+Update `DeriveConnStatus()` to include retry fields:
+
+```go
+func (conn *SSHConn) DeriveConnStatus() wshrpc.ConnStatus {
+ var status wshrpc.ConnStatus
+ conn.WithLock(func() {
+ status = wshrpc.ConnStatus{
+ // ... existing fields ...
+ ReconnectAttempt: conn.ReconnectAttempt,
+ ReconnectNextAttempt: conn.ReconnectNextAttempt,
+ ReconnectError: conn.ReconnectError,
+ }
+ })
+ return status
+}
+```
+
+### 4. Frontend: `frontend/app/block/connstatusoverlay.tsx` — New overlay states
+
+Refactor `ConnStatusOverlay` to handle the full lifecycle:
+
+```tsx
+export const ConnStatusOverlay = React.memo(...) => {
+ // ... existing setup ...
+ const connStatus = jotai.useAtomValue(waveEnv.getConnStatusAtom(connName));
+
+ const showStalled = connStatus.status === "connected" && connStatus.connhealthstatus === "stalled";
+ const showDisconnected = connStatus.status === "disconnected" && !connStatus.connected;
+ const showRetrying = connStatus.status === "connecting" && connStatus.reconnectattempt > 0;
+ const showCountdown = connStatus.reconnectnextattempt > 0 && connStatus.status === "disconnected";
+
+ if (showStalled && !showWshError) {
+ return ;
+ }
+
+ if (showRetrying) {
+ return ;
+ }
+
+ if (showCountdown) {
+ return ;
+ }
+
+ if (showDisconnected) {
+ return ;
+ }
+ // ...
+};
+```
+
+**New components to implement:**
+
+1. `DisconnectedOverlay` — shows immediately on disconnect, with error detail and optional countdown
+2. `RetryingOverlay` — spinner + "Attempt N — connecting…"
+3. `CountdownOverlay` — countdown timer that updates every second, shows last error, "Reconnect now" button
+
+The `CountdownOverlay` needs a `useEffect` with `setInterval(1000)` to compute `Math.max(0, nextAttempt - Date.now())`.
+
+### 5. Frontend: `frontend/app/block/connectionbutton.tsx` — Update status icon
+
+Update the toolbar icon logic to show retry states:
+
+```tsx
+} else if (connStatus?.status === "connecting" && connStatus?.reconnectattempt > 0) {
+ color = "var(--warning-color)";
+ iconName = "fa-solid fa-rotate";
+ titleText = `Reconnecting to ${connection} (attempt ${connStatus.reconnectattempt})`;
+} else if (connStatus?.status === "disconnected" && connStatus?.reconnectnextattempt > 0) {
+ color = "var(--grey-text-color)";
+ iconName = "fa-solid fa-clock";
+ titleText = `Disconnected from ${connection} — retrying soon`;
+}
+```
+
+### 6. Schema/Types: `frontend/types/gotypes.d.ts` — Update `ConnStatus`
+
+Add the new fields to the TypeScript type definition:
+
+```typescript
+interface ConnStatus {
+ status: string;
+ connhealthstatus: string;
+ // ... existing fields ...
+ reconnectattempt?: number;
+ reconnectnextattempt?: number;
+ reconnecterror?: string;
+}
+```
+
+## Test Plan
+
+| Test | Setup | Expected |
+|------|-------|----------|
+| Disconnect overlay | Block `client.Close()` for 5s, trigger disconnect | Overlay shows "Disconnected" within 1s (not after 5s) |
+| Retry overlay | Start reconnect scheduler | Overlay shows "Attempt 1 — connecting…" during `AttemptReconnect` |
+| Countdown overlay | Failed attempt with 5s interval | Overlay shows countdown 5…4…3…2…1… then "Attempt 2" |
+| Toolbar icon | Same scenarios | Icon changes from green → warning spinner → grey clock |
+| Manual reconnect | Click "Reconnect now" during countdown | Immediate `AttemptReconnect`, skipping countdown |
+| Success clears overlay | Reconnect succeeds | Overlay vanishes, terminal resumes |
+
+## Validation Checklist
+
+- [ ] `task build:backend` succeeds
+- [ ] `task build:frontend` succeeds
+- [ ] `go test ./pkg/remote/conncontroller/...` passes
+- [ ] `go test ./pkg/jobcontroller/...` passes
+- [ ] Manual test: disconnect, verify overlay sequence (disconnected → countdown → retry → connected)
+- [ ] Manual test: verify toolbar icon reflects each state
+
+## Dependencies
+
+- **Requires PR #1**: Fast reconnect with hardcoded thresholds must be merged first, or the overlay will still show 30s+ delays.
+- **No dependency on PR #3**: This is purely UI + event plumbing, independent of configurable thresholds.
diff --git a/.pi/specs/remove-telemetry.md b/.pi/specs/remove-telemetry.md
new file mode 100644
index 0000000000..30468a898a
--- /dev/null
+++ b/.pi/specs/remove-telemetry.md
@@ -0,0 +1,575 @@
+# Spec: Remove Telemetry
+
+**Date:** 2026-05-13
+**Status:** Implemented
+**Review:** `.pi/reviews/remove-telemetry-independent-review.md`
+
+## Goal
+
+Completely remove all telemetry, analytics, and user tracking from waveterm. No data should be collected locally or sent to external servers.
+
+## What Telemetry Does Today
+
+1. **Local collection** — Events and activity metrics stored in SQLite (`db_tevent`, `db_activity` tables)
+2. **Periodic upload** — Sent to `https://api.waveterm.dev/central` every 4 hours, on startup, on shutdown
+3. **Diagnostics ping** — Sent to `https://ping.waveterm.dev/central` once on startup, then at most once/day
+4. **Opt-out notification** — When user disables telemetry, a single record is sent to `/no-telemetry`
+5. **Electron activity tracking** — `emain/emain.ts` collects display info, foreground/active state, terminal command counts, and AI usage minutes; sends via `ActivityCommand` and `RecordTEventCommand`
+6. **wsh CLI activity** — Every `wsh` command reports its name and success/failure via `WshActivityCommand`
+
+## Scope
+
+### What to remove
+
+- All event recording (`RecordTEvent`, `GoRecordTEventWrap`)
+- All activity tracking (`UpdateActivity`, `GoUpdateActivityWrap`, `WshActivityCommand`)
+- All cloud uploads (`wcloud.SendAllTelemetry`, `wcloud.SendDiagnosticPing`, `wcloud.SendNoTelemetryUpdate`)
+- Telemetry config setting (`telemetry:enabled`)
+- Telemetry loops in `main-server.go` (including `diagnosticLoop`)
+- Telemetry RPC commands (including `WshActivityCommand`)
+- Telemetry call sites in all packages and `emain/`
+- `pkg/telemetry/` and `pkg/wcloud/` directories
+- Telemetry documentation
+- `WAVETERM_NOPING` env var handling
+- `WCLOUD_ENDPOINT` / `WCLOUD_PING_ENDPOINT` env var handling (via `wcloud.CacheAndRemoveEnvVars`)
+- Onboarding telemetry consent flow
+- Electron activity tracking (`emain-activity.ts` increment functions, IPC handler)
+- `telemetryrequired.tsx` (telemetry consent gate for AI panel)
+
+### What to keep
+
+- `wstore.GetClientId()` / `SetClientId()` — ClientId is used for non-telemetry purposes (durable sessions, remote SSH, job manager)
+- `pkg/waveobj/wtype.go` `TosAgreed` field — Keep for now; removing it requires DB migration schema changes. `AgreeTos()` in `clientservice.go` stays since it's a TOS acceptance, not telemetry
+- `autoupdate:*` settings — `AutoUpdateEnabled`, `AutoUpdateChannel`, etc. are genuine auto-update config, not telemetry. Keep all `autoupdate` fields in `SettingsConfig` and `metaconsts.go`
+
+### Auto-generated files warning
+
+The following TypeScript files are **auto-generated** by `cmd/generatets/main-generatets.go` and **must not be edited manually** — changes will be overwritten on rebuild:
+
+- `frontend/types/gotypes.d.ts` (contains `ActivityUpdate`, `TEvent`, `TEventProps`, `TEventUserProps`, `telemetry:enabled` type)
+- `frontend/app/store/services.ts` (contains `TelemetryUpdate()` method)
+- `frontend/app/store/wshclientapi.ts` (contains `ActivityCommand()`, `RecordTEventCommand()`, `SendTelemetryCommand()`, `WaveAIEnableTelemetryCommand()`, `WshActivityCommand()`)
+
+Instead, remove the Go source types/methods that feed the generator (Phase A), then regenerate. After regeneration, also remove manual frontend call sites that reference these methods (Phase B).
+
+Similarly, `pkg/wshrpc/wshclient/wshclient.go` is auto-generated by `cmd/generatego/main-generatego.go`. Remove the Go source types (Phase A.3), then regenerate.
+
+## Implementation Phases
+
+### Phase A: Remove call sites (make everything no-op)
+
+**Goal:** Every telemetry call site is removed. The app builds and runs without collecting any data. `pkg/telemetry/` and `pkg/wcloud/` stay intact but orphaned.
+
+#### A.1: Remove telemetry from main server loops
+
+**File:** `cmd/server/main-server.go`
+
+- Remove imports: `telemetry`, `telemetrydata`, `wcloud`
+- Remove constants: `InitialTelemetryWait`, `TelemetryTick`, `TelemetryInterval`, `TelemetryInitialCountsWait`, `TelemetryCountsInterval`, `InitialDiagnosticWait`, `DiagnosticTick`
+- Remove functions: `telemetryLoop()`, `diagnosticLoop()`, `sendDiagnosticPing()`, `setupTelemetryConfigHandler()`, `panicTelemetryHandler()`, `sendTelemetryWrapper()`, `updateTelemetryCounts()`, `updateTelemetryCountsLoop()`, `beforeSendActivityUpdate()`, `startupActivityUpdate()`, `shutdownActivityUpdate()`
+- Remove from startup sequence:
+ - `sendTelemetryWrapper()` call
+ - `go telemetryLoop()`
+ - `go diagnosticLoop()`
+ - `setupTelemetryConfigHandler()`
+ - `go updateTelemetryCountsLoop()`
+ - `wcloud.CacheAndRemoveEnvVars()` call
+ - `wcloud.SendDiagnosticPing()` call
+ - `panichandler.PanicTelemetryHandler = panicTelemetryHandler`
+ - `go startupActivityUpdate(firstLaunch)` call
+ - `shutdownActivityUpdate()` call
+- Remove all `telemetry.UpdateActivity()` calls
+- Remove all `telemetry.RecordTEvent()` calls (`app:startup`, `app:shutdown`, `app:counts`, `app:display`, etc.)
+- Remove all `telemetrydata.TEventUserProps` and `TEventProps` construction
+- Remove `telemetry.AutoUpdateChannel()`, `telemetry.IsAutoUpdateEnabled()`, `telemetry.GetTosAgreedTs()` calls (these only exist in telemetry payloads; the actual `autoupdate:*` settings remain in config)
+- Remove `os.Getenv("WAVETERM_NOPING")` check and related code
+
+#### A.2: Remove telemetry from wshserver
+
+**File:** `pkg/wshrpc/wshserver/wshserver.go`
+
+- Remove imports: `telemetry`, `telemetrydata`, `wcloud`
+- Remove methods: `RecordTEventCommand()`, `SendTelemetryCommand()`, `WaveAIEnableTelemetryCommand()`, `ActivityCommand()`, **`WshActivityCommand()`**
+- Remove telemetry calls in `WshRunCommand()` (the `activityUpdate` and `GoRecordTEventWrap` at lines ~1338-1344)
+
+#### A.3: Remove telemetry RPC types
+
+**File:** `pkg/wshrpc/wshrpctypes.go`
+
+- Remove from interface: `ActivityCommand()`, `RecordTEventCommand()`, `SendTelemetryCommand()`, `WaveAIEnableTelemetryCommand()`, **`WshActivityCommand()`**
+- Remove type: `ActivityUpdate` struct
+- Remove import: `telemetrydata` (if no other uses)
+
+#### A.4: Remove telemetry RPC client helpers
+
+**File:** `pkg/wshrpc/wshclient/wshclient.go` (auto-generated)
+
+- Remove: `ActivityCommand()`, `RecordTEventCommand()`, `SendTelemetryCommand()`, `WaveAIEnableTelemetryCommand()`, **`WshActivityCommand()`**
+- Remove import: `telemetrydata` (if no other uses)
+- **Note:** This file is auto-generated by `cmd/generatego/main-generatego.go`. Remove the source types in A.3, then regenerate. Do NOT edit `wshclient.go` directly.
+
+#### A.5: Remove telemetry from block creation
+
+**File:** `pkg/wcore/block.go`
+
+- Remove imports: `telemetry`, `telemetrydata`
+- Remove: `recordBlockCreationTelemetry()` function
+- Remove calls to `recordBlockCreationTelemetry()` in `CreateBlock()` and `CreateBlockWithTelemetry()`
+- Rename `CreateBlockWithTelemetry` to `CreateBlock` (or keep name, just remove the telemetry parameter)
+
+#### A.6: Remove telemetry from workspace
+
+**File:** `pkg/wcore/workspace.go`
+
+- Remove imports: `telemetry`, `telemetrydata`
+- Remove: `GoUpdateActivityWrap()` and `GoRecordTEventWrap()` calls
+
+#### A.7: Remove telemetry from wcore
+
+**File:** `pkg/wcore/wcore.go`
+
+- Remove import: `wcloud`
+- Remove: `GoSendNoTelemetryUpdate()` function
+
+#### A.8: Remove telemetry from connection controller
+
+**File:** `pkg/remote/conncontroller/conncontroller.go`
+
+- Remove imports: `telemetry`, `telemetrydata`
+- Remove all `GoUpdateActivityWrap()` and `GoRecordTEventWrap()` calls (lines ~760, 763, 781, 784, 987)
+
+#### A.9: Remove telemetry from WSL connection
+
+**File:** `pkg/wslconn/wslconn.go`
+
+- Remove imports: `telemetry`, `telemetrydata`
+- Remove all `GoUpdateActivityWrap()` and `GoRecordTEventWrap()` calls (lines ~515, 518, 531, 534)
+
+#### A.10: Remove telemetry from job controller
+
+**File:** `pkg/jobcontroller/jobcontroller.go`
+
+- Remove imports: `telemetry`, `telemetrydata`
+- Remove all `GoRecordTEventWrap()` calls (lines ~730, 757, 1040, 1135, 1160)
+
+#### A.11: Remove telemetry from AI usechat
+
+**File:** `pkg/aiusechat/usechat.go`
+
+- Remove imports: `telemetry`, `telemetrydata`
+- Remove: `sendAIMetricsTelemetry()` function
+- Remove the `telemetry.IsTelemetryEnabled()` check for cloud modes (line ~86) — either remove the gate or hardcode it to pass
+
+#### A.12: Remove telemetry from panic handler
+
+**File:** `pkg/panichandler/panichandler.go`
+
+- Remove: `PanicTelemetryHandler` variable
+- Remove the `if PanicTelemetryHandler != nil` block from `PanicHandler`
+- Delete `PanicHandlerNoTelemetry` function entirely (it becomes redundant)
+- **Do NOT rename** `PanicHandlerNoTelemetry` to `PanicHandler` — instead, keep `PanicHandler` as the name and strip the telemetry dispatch from it. After this change, `PanicHandler` behaves exactly as `PanicHandlerNoTelemetry` did.
+- No callers outside `pkg/telemetry/` need renaming (the only callers of `PanicHandlerNoTelemetry` are in `telemetry.go`, which gets deleted in Phase C)
+- Remove `panichandler.PanicTelemetryHandler = panicTelemetryHandler` from `main-server.go` (already listed in A.1)
+
+#### A.13: Remove telemetry from client service
+
+**File:** `pkg/service/clientservice/clientservice.go`
+
+- Remove: `TelemetryUpdate()` method
+- Keep: `AgreeTos()` method (TOS acceptance, not telemetry)
+
+#### A.14: Remove telemetry config fields
+
+**File:** `pkg/wconfig/settingsconfig.go`
+
+- Remove from `SettingsConfig`: `TelemetryClear`, `TelemetryEnabled`
+- Update `CountCustomSettings()`: Remove the `telemetry:enabled` exclusion check (line ~993). Keep the `autoupdate:channel` exclusion since that's not telemetry.
+
+**File:** `pkg/wconfig/metaconsts.go`
+
+- Remove: `ConfigKey_TelemetryClear`, `ConfigKey_TelemetryEnabled`
+- **Note:** This file is auto-generated by `cmd/generatego/main-generatego.go` via `GenerateSettingsMetaConsts()`. After removing the fields from `SettingsConfig`, regenerate.
+
+#### A.15: Remove debug CLI command
+
+**File:** `cmd/wsh/cmd/wshcmd-debug.go`
+
+- Remove: `debugSendTelemetryCmd` and `debugSendTelemetryRun()`
+
+#### A.16: Remove wsh CLI activity tracking
+
+**File:** `cmd/wsh/cmd/wshcmd-root.go`
+
+- Remove: `sendActivity()` function (lines 221-231)
+- Remove: `activityWrap()` function (lines 106-113)
+- Remove the comment block above `sendActivity` (lines 216-218)
+- Remove `wshclient` import if no other uses remain in the file
+- Update all command `RunE` assignments that use `activityWrap` to use the inner function directly
+
+**File:** `cmd/wsh/cmd/wshcmd-file.go`
+
+- Change all `activityWrap("file", )` to just `` in `RunE` assignments:
+ - `activityWrap("file", fileListRun)` → `fileListRun`
+ - `activityWrap("file", fileCatRun)` → `fileCatRun`
+ - `activityWrap("file", fileInfoRun)` → `fileInfoRun`
+ - `activityWrap("file", fileRmRun)` → `fileRmRun`
+ - `activityWrap("file", fileWriteRun)` → `fileWriteRun`
+ - `activityWrap("file", fileAppendRun)` → `fileAppendRun`
+ - `activityWrap("file", fileCpRun)` → `fileCpRun`
+ - `activityWrap("file", fileMvRun)` → `fileMvRun`
+
+#### A.17: Remove code generator telemetrydata import
+
+**File:** `cmd/generatego/main-generatego.go`
+
+- Remove `"github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"` from the imports list in `GenerateWshClient()` (line 29)
+- After Phase A.3 removes the telemetry RPC types from `wshrpctypes.go`, regenerate `wshclient.go`
+
+**File:** `cmd/generatets/main-generatets.go`
+
+- No direct changes needed — it reads Go type information via reflection. After Phase A removes the telemetry types from Go sources, regeneration will automatically exclude them.
+
+### Phase B: Remove frontend telemetry
+
+**Goal:** No telemetry calls from the frontend. No telemetry in onboarding.
+
+#### B.1: Remove recordTEvent from global store
+
+**File:** `frontend/app/store/global.ts`
+
+- Remove: `recordTEvent()` function
+- Remove from exports
+
+#### B.2: Remove telemetry RPC client methods
+
+**File:** `frontend/app/store/wshclientapi.ts` (auto-generated)
+
+- Remove: `ActivityCommand()`, `RecordTEventCommand()`, `SendTelemetryCommand()`, `WaveAIEnableTelemetryCommand()`, `WshActivityCommand()`
+- **Note:** This file is auto-generated by `cmd/generatets/main-generatets.go`. After removing the Go source types in A.3, regenerate. Then remove manual frontend call sites (B.4).
+
+#### B.3: Remove TelemetryUpdate from services
+
+**File:** `frontend/app/store/services.ts` (auto-generated)
+
+- Remove: `TelemetryUpdate()` method from `ClientService`
+- **Note:** This file is auto-generated. After removing `TelemetryUpdate()` from `clientservice.go` (A.13), regeneration will exclude it. Then remove manual frontend call sites (B.5).
+
+#### B.4: Remove recordTEvent call sites (non-AI)
+
+| File | What to remove |
+|------|----------------|
+| `frontend/app/block/blockframe-header.tsx` | `recordTEvent("action:magnify")`, `ActivityCommand({nummagnify: 1})` |
+| `frontend/app/block/connectionbutton.tsx` | `recordTEvent("action:other")` |
+| `frontend/app/block/durable-session-flyover.tsx` | `recordTEvent("action:termdurable")` |
+| `frontend/app/tab/tabcontextmenu.ts` | `recordTEvent("action:settabtheme")`, `ActivityCommand({settabtheme: 1})` |
+| `frontend/app/view/term/osc-handlers.ts` | All `recordTEvent` calls (`conn:connect`, `action:term`); also remove `incrementTermCommands` IPC call |
+| `frontend/app/view/term/term-model.ts` | `recordTEvent("action:term")` |
+| `frontend/app/view/waveconfig/waveconfig-model.ts` | `RecordTEventCommand` calls |
+| `frontend/app/workspace/workspace-layout-model.ts` | `recordTEvent("action:openwaveai")` |
+| `frontend/app/modals/about.tsx` | `RecordTEventCommand` call |
+| `frontend/app/store/keymodel.ts` | `recordTEvent` import and call (line 653: `"action:other"` for conndropdown) |
+| `frontend/app/block/blockenv.ts` | `ActivityCommand` type reference |
+| `frontend/app/tab/tabbarenv.ts` | `ActivityCommand` type reference |
+| `frontend/app/tab/tab.tsx` | `ActivityCommand` type reference |
+| `frontend/app/tab/vtabbarenv.ts` | `ActivityCommand` type reference |
+| `frontend/app/view/waveconfig/waveconfigenv.ts` | `RecordTEventCommand` type reference |
+
+#### B.5: Remove telemetry from onboarding
+
+This section requires UI restructuring, not just deletion of telemetry calls. The current onboarding flow is:
+
+```
+init (telemetry toggle + TOS) → (enabled → features) | (disabled → notelemetrystar → features)
+```
+
+After telemetry removal, the flow should be:
+
+```
+init (TOS only) → features
+```
+
+**File:** `frontend/app/onboarding/onboarding.tsx`
+
+- Remove `InitPage`'s `telemetryUpdateFn` prop
+- Remove `telemetry:enabled` setting read (`useSettingsKeyAtom("telemetry:enabled")`)
+- Remove `telemetryEnabled` / `setTelemetryEnabled` state
+- Remove `setTelemetry` function
+- Remove the telemetry toggle checkbox UI (the entire "Anonymous usage data" section)
+- Remove `telemetryEnabled` check in `acceptTos` — if AI panel is still present, always open it (or let AI removal spec handle this)
+- Remove `NoTelemetryStarPage` component entirely
+- Remove `"notelemetrystar"` page state from `pageNameAtom`
+- Simplify page flow: `acceptTos` always goes to `"features"`
+- Remove `RecordTEventCommand` calls in `handleStarClick` and `handleMaybeLater`
+- Remove `TelemetryUpdate` import/call (via `services.ClientService.TelemetryUpdate`)
+- Keep `AgreeTos` call in `acceptTos` (TOS acceptance is not telemetry)
+- Keep `RecordTEventCommand` calls in `handleStarClick`/`handleMaybeLater` — remove them. The `SetMetaCommand` call (for `"onboarding:githubstar"`) should stay since it's local metadata, not telemetry upload.
+- Update `NewInstallOnboardingModal`: remove `telemetryUpdateFn` prop from `InitPage` instantiation
+- Move the GitHub star prompt from `NoTelemetryStarPage` to `InitPage` or `FeaturesPage` (without the "telemetry disabled" framing)
+
+**Other onboarding files:**
+
+| File | What to remove |
+|------|----------------|
+| `frontend/app/onboarding/onboarding-starask.tsx` | All `RecordTEventCommand` calls |
+| `frontend/app/onboarding/onboarding-features.tsx` | All `RecordTEventCommand` calls |
+| `frontend/app/onboarding/onboarding-durable.tsx` | `RecordTEventCommand` call |
+| `frontend/app/onboarding/onboarding-upgrade-minor.tsx` | All `RecordTEventCommand` calls |
+| `frontend/app/onboarding/onboarding-upgrade-v0131.tsx` | `RecordTEventCommand` calls (if any) |
+
+**Preview files:**
+
+| File | What to remove |
+|------|----------------|
+| `frontend/preview/previews/onboarding.preview.tsx` | Remove `telemetryUpdateFn` prop from `InitPage` (after onboarding restructuring) |
+| `frontend/preview/mock/mockfilesystem.ts` | Remove `telemetry.log` mock entry (line 317) |
+
+> Note: Onboarding files that are part of the AI removal spec (`.pi/specs/remove-waveai.md`) can have their telemetry removed as part of that work instead.
+
+#### B.6: Remove telemetry from AI panel files
+
+> These are handled by the AI removal spec (`.pi/specs/remove-waveai.md`). The AI panel files have extensive telemetry calls that go away when the AI panel is removed.
+
+| File | Handled by |
+|------|------------|
+| `frontend/app/aipanel/*.tsx` | remove-waveai.md Phase A |
+| `frontend/app/onboarding/fakechat.tsx` | remove-waveai.md Phase A |
+
+**Special case:** `frontend/app/aipanel/telemetryrequired.tsx` — This is a telemetry consent gate component (`TelemetryRequiredMessage`) that blocks AI panel usage until the user enables telemetry. It calls `RpcApi.WaveAIEnableTelemetryCommand`. This component is about **telemetry**, not AI. If the AI removal spec removes the AI panel entirely, this file gets deleted with it. If the AI panel is kept, this component must be removed or replaced with a non-telemetry gate. **Coordinate with AI removal spec.**
+
+**Special case:** `frontend/app/aipanel/waveai-model.tsx` and `frontend/app/aipanel/aimode.tsx` — These read `telemetry:enabled` to gate AI cloud features (returning `"invalid"` mode or blocking cloud AI when telemetry is disabled). After `telemetry:enabled` is removed from settings, these reads will return `undefined/false`, which could permanently disable AI cloud features for existing users. If the AI removal spec does not remove these files, replace the `telemetry:enabled` reads with `true` (always allow). **Coordinate with AI removal spec.**
+
+#### B.7: Remove Electron main process telemetry
+
+**File:** `emain/emain.ts`
+
+- Remove `sendDisplaysTDataEvent()` function (sends display info via `RecordTEventCommand` with `"app:display"`)
+- Remove `logActiveState()` function (the core activity tracking loop that calls `ActivityCommand` and `RecordTEventCommand` with `"app:activity"`)
+- Remove all `RpcApi.RecordTEventCommand` calls
+- Remove all `RpcApi.ActivityCommand` calls
+- Remove `ActivityUpdate`, `TEventProps`, `ActivityDisplayType` type imports
+- Remove references to `getActivityState`, `setWasActive`, `setWasInFg`, `incrementTermCommands*`, `getAndClearTermCommands*` from `emain-activity`
+
+**File:** `emain/emain-activity.ts`
+
+- Remove `incrementTermCommandsRun()`, `incrementTermCommandsRemote()`, `incrementTermCommandsWsl()`, `incrementTermCommandsDurable()` functions
+- Remove `getAndClearTermCommandsRun()`, `getAndClearTermCommandsRemote()`, `getAndClearTermCommandsWsl()`, `getAndClearTermCommandsDurable()` functions
+- Remove `termCommandsRun`, `termCommandsRemote`, `termCommandsWsl`, `termCommandsDurable` variables
+- Keep `wasActive`, `wasInFg`, `setWasActive`, `setWasInFg`, `getActivityState` if they have non-telemetry callers (verify first; if only used by `logActiveState`, remove them too)
+- Keep quit/starting/relaunching state variables (`globalIsQuitting`, `globalIsStarting`, `globalIsRelaunching`, `forceQuit`, `userConfirmedQuit`) — these are process lifecycle state, not telemetry
+
+**File:** `emain/emain-ipc.ts`
+
+- Remove `"increment-term-commands"` IPC handler (lines ~441-454)
+- Remove imports of `incrementTermCommandsDurable`, `incrementTermCommandsRemote`, `incrementTermCommandsRun`, `incrementTermCommandsWsl` from `emain-activity`
+- Keep `setWasActive` import if it's used elsewhere in `emain-ipc.ts`
+
+**File:** `emain/preload.ts`
+
+- Remove `incrementTermCommands` API exposure (line 67)
+
+**File:** `frontend/types/custom.d.ts`
+
+- Remove `incrementTermCommands` type declaration from the Electron API interface (line 131)
+
+**File:** `frontend/app/view/term/osc-handlers.ts`
+
+- Remove `getApi().incrementTermCommands({ isRemote, isWsl, isDurable })` call (line 110)
+
+#### B.8: Remove telemetryrequired.tsx (if AI panel is kept)
+
+If the AI removal spec removes the AI panel entirely, this file is deleted with it. Otherwise:
+
+**File:** `frontend/app/aipanel/telemetryrequired.tsx`
+
+- Delete the entire file
+- Remove all imports/references to `TelemetryRequiredMessage` from AI panel components
+- Remove the `telemetry:enabled` gate that renders this component — AI cloud features should work without requiring telemetry
+
+### Phase C: Delete unused packages and regenerate
+
+**Goal:** Remove `pkg/telemetry/`, `pkg/telemetry/telemetrydata/`, `pkg/wcloud/` entirely. Regenerate auto-generated files.
+
+#### C.1: Delete pkg/telemetry/
+
+- Delete entire directory (`telemetry.go`, `telemetrydata/telemetrydata.go`)
+- **Before deletion:** Verify `telemetry.AutoUpdateChannel()` and `telemetry.IsAutoUpdateEnabled()` have no remaining callers. These are convenience functions that just read `wconfig` settings. Their callers in `main-server.go` were removed in A.1. If any other callers exist, move the functions to `pkg/wconfig/` before deleting `pkg/telemetry/`.
+
+#### C.2: Delete pkg/wcloud/
+
+- Delete entire directory (`wcloud.go`, `wclouddata.go`)
+
+#### C.3: Clean up remaining imports and regenerate
+
+- Update `cmd/generatego/main-generatego.go`: Remove `"github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"` from imports (if not already done in A.17)
+- Run `go mod tidy` and fix any remaining import errors
+- Regenerate auto-generated Go files: `pkg/wshrpc/wshclient/wshclient.go`, `pkg/wconfig/metaconsts.go`
+- Regenerate auto-generated TypeScript files: `frontend/types/gotypes.d.ts`, `frontend/app/store/services.ts`, `frontend/app/store/wshclientapi.ts`
+
+#### C.4: Environment variable cleanup
+
+After deleting `pkg/wcloud/`, the `WCLOUD_ENDPOINT` and `WCLOUD_PING_ENDPOINT` environment variables will no longer be read or unset. This is harmless for a fork that doesn't use cloud services, but note that these env vars will remain set in the waveterm process environment (previously `wcloud.CacheAndRemoveEnvVars()` unset them for security). No action needed unless the fork wants to sanitize env vars for other reasons.
+
+### Phase D: Clean up docs, schemas, and database
+
+#### D.1: Remove telemetry documentation
+
+- Delete: `docs/docs/telemetry.mdx`
+- Delete: `docs/docs/telemetry-old.mdx`
+- Audit: `docs/docs/config.mdx` — remove telemetry config references
+- Audit: `docs/docs/faq.mdx` — remove telemetry Q&A
+- Audit: `docs/docs/index.mdx` — remove telemetry mentions
+- Audit: `docs/docs/releasenotes.mdx` — remove telemetry release notes (optional, historical)
+
+#### D.2: Remove telemetry from other docs
+
+- Audit: `docs/docs/waveai.mdx`, `waveai-modes.mdx` — remove telemetry mentions (handled by AI removal spec)
+
+#### D.3: Drop telemetry database tables (optional)
+
+The `db_tevent` and `db_activity` tables were created by SQL migrations (`000003_activity.up.sql` and `000007_events.up.sql`). After Phase C, no code reads or writes these tables. They remain empty in existing databases.
+
+To fully clean up, add a new migration:
+
+```
+db/migrations-wstore/000012_drop_telemetry.up.sql:
+ DROP TABLE IF EXISTS db_tevent;
+ DROP TABLE IF EXISTS db_activity;
+
+db/migrations-wstore/000012_drop_telemetry.down.sql:
+ -- Recreating these tables is not necessary; the data is obsolete
+```
+
+(Verify the next available migration number — `000011_job.down.sql` is the latest existing.)
+
+#### D.4: Clean up existing users' `telemetry:enabled` config
+
+After removing `TelemetryEnabled` from `SettingsConfig`, existing users who have `telemetry:enabled` in their config JSON will have an unrecognized key. JSON unmarshaling with `omitempty` silently ignores unknown keys, so this is harmless. No migration needed. The `CountCustomSettings` fix in A.14 removes the code that explicitly checks for this key.
+
+## Verification Checklist
+
+After each phase:
+
+- [ ] `task dev` completes without errors
+- [ ] `task start` launches the app
+- [ ] No console errors about missing telemetry functions
+- [ ] No network requests to `api.waveterm.dev` or `ping.waveterm.dev`
+- [ ] No `db_tevent` or `db_activity` table writes
+- [ ] App functions normally (terminals, SSH, file browser, etc.)
+- [ ] Onboarding flow works without telemetry consent step (simplified `init → features` flow)
+- [ ] `wsh` CLI commands work without activity tracking
+- [ ] Electron activity tracking loop is no longer running
+- [ ] AI panel works without telemetry gate (if AI panel is still present)
+- [ ] Auto-generated files (`wshclient.go`, `gotypes.d.ts`, `services.ts`, `wshclientapi.ts`) have been regenerated and contain no telemetry types/methods
+
+## Risk Assessment
+
+| Risk | Mitigation |
+|------|------------|
+| ClientId is used by telemetry AND non-telemetry code | Keep `wstore.GetClientId()`/`SetClientId()` — only remove telemetry-specific usage |
+| `TosAgreed` field is used for telemetry cohorts | Keep the field in `waveobj.Client`; it's harmless without telemetry reading it. Keep `AgreeTos()` in `clientservice.go`. |
+| Onboarding flow assumes telemetry consent step | Restructure onboarding to remove telemetry toggle, simplify page flow to `init → features`, move GitHub star prompt out of `NoTelemetryStarPage`. See B.5. |
+| Upstream merge conflicts | Phase A (remove call sites) keeps `pkg/telemetry/` and `pkg/wcloud/` intact, minimizing conflicts. Phase C (delete packages) is deferred. |
+| `autoupdate:*` settings in `pkg/telemetry/` | `telemetry.AutoUpdateChannel()` and `telemetry.IsAutoUpdateEnabled()` are convenience functions that just read `wconfig` settings. Callers in `main-server.go` are removed in A.1. If any callers remain elsewhere, move these functions to `pkg/wconfig/` before deleting `pkg/telemetry/` in Phase C. |
+| Auto-generated files overwritten by manual edits | Do NOT manually edit `wshclient.go`, `gotypes.d.ts`, `services.ts`, or `wshclientapi.ts`. Remove Go source types first, then regenerate. |
+| AI panel `telemetry:enabled` reads become dangling | After removing the setting, reads return `undefined/false`. If AI panel is kept (not removed by AI spec), replace reads with `true`. See B.6. |
+| `PanicHandlerNoTelemetry` callers | Only called within `pkg/telemetry/telemetry.go` (2 calls). After Phase C deletes that package, no callers remain. No rename needed. See A.12. |
+| Existing `telemetry:enabled` in user configs | Silently ignored by JSON unmarshaling with `omitempty`. No migration needed. |
+| `WCLOUD_ENDPOINT`/`WCLOUD_PING_ENDPOINT` env vars | After removing `wcloud`, these env vars will stay set in the process. Harmless for a fork. See C.4. |
+
+## Interaction with AI Removal Spec
+
+The AI removal spec (`.pi/specs/remove-waveai.md`) and this spec overlap in these areas:
+
+- `WaveAIEnableTelemetryCommand` — remove in both (telemetry spec covers it)
+- AI panel telemetry calls — removed when AI panel is removed
+- `ai:apitokensecretname` — removed in AI spec, not telemetry
+- `telemetry.IsTelemetryEnabled()` check in AI cloud modes — removed in telemetry spec
+- `telemetryrequired.tsx` — telemetry consent gate for AI. If AI panel is deleted, this file is deleted with it. If AI panel is kept, remove this component and the `telemetry:enabled` gate. See B.6.
+- `waveai-model.tsx`/`aimode.tsx` `telemetry:enabled` reads — if AI panel is kept after AI spec, replace with `true`. See B.6.
+
+**Recommendation:** Execute telemetry spec first (Phase A), then AI spec. This way the AI spec doesn't need to worry about telemetry imports.
+
+## File Cross-Reference
+
+Complete list of all files that need changes, organized by phase:
+
+### Phase A (Go backend)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `cmd/server/main-server.go` | A.1 | Remove telemetry loops, functions, constants, imports |
+| `pkg/wshrpc/wshserver/wshserver.go` | A.2 | Remove 5 RPC methods + WshRunCommand telemetry |
+| `pkg/wshrpc/wshrpctypes.go` | A.3 | Remove 5 interface methods + `ActivityUpdate` type |
+| `pkg/wshrpc/wshclient/wshclient.go` | A.4 | Auto-generated; regenerate after A.3 |
+| `pkg/wcore/block.go` | A.5 | Remove `recordBlockCreationTelemetry` + rename |
+| `pkg/wcore/workspace.go` | A.6 | Remove activity/event tracking calls |
+| `pkg/wcore/wcore.go` | A.7 | Remove `GoSendNoTelemetryUpdate` |
+| `pkg/remote/conncontroller/conncontroller.go` | A.8 | Remove activity/event calls |
+| `pkg/wslconn/wslconn.go` | A.9 | Remove activity/event calls |
+| `pkg/jobcontroller/jobcontroller.go` | A.10 | Remove event tracking calls |
+| `pkg/aiusechat/usechat.go` | A.11 | Remove `sendAIMetricsTelemetry` + telemetry gate |
+| `pkg/panichandler/panichandler.go` | A.12 | Remove `PanicTelemetryHandler`, simplify `PanicHandler` |
+| `pkg/service/clientservice/clientservice.go` | A.13 | Remove `TelemetryUpdate` method |
+| `pkg/wconfig/settingsconfig.go` | A.14 | Remove `TelemetryClear`/`TelemetryEnabled` fields + `CountCustomSettings` fix |
+| `pkg/wconfig/metaconsts.go` | A.14 | Auto-generated; regenerate after settingsconfig change |
+| `cmd/wsh/cmd/wshcmd-debug.go` | A.15 | Remove `debugSendTelemetryCmd` |
+| `cmd/wsh/cmd/wshcmd-root.go` | A.16 | Remove `sendActivity`, `activityWrap`, comment block |
+| `cmd/wsh/cmd/wshcmd-file.go` | A.16 | Unwrap `activityWrap` from all `RunE` assignments |
+| `cmd/generatego/main-generatego.go` | A.17 | Remove `telemetrydata` import |
+
+### Phase B (Frontend + Electron)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `frontend/app/store/global.ts` | B.1 | Remove `recordTEvent` |
+| `frontend/app/store/wshclientapi.ts` | B.2 | Auto-generated; regenerate after A.3 |
+| `frontend/app/store/services.ts` | B.3 | Auto-generated; regenerate after A.13 |
+| `frontend/app/block/blockframe-header.tsx` | B.4 | Remove `recordTEvent` + `ActivityCommand` calls |
+| `frontend/app/block/connectionbutton.tsx` | B.4 | Remove `recordTEvent` call |
+| `frontend/app/block/durable-session-flyover.tsx` | B.4 | Remove `recordTEvent` call |
+| `frontend/app/tab/tabcontextmenu.ts` | B.4 | Remove `recordTEvent` + `ActivityCommand` calls |
+| `frontend/app/view/term/osc-handlers.ts` | B.4 | Remove `recordTEvent` calls + `incrementTermCommands` IPC |
+| `frontend/app/view/term/term-model.ts` | B.4 | Remove `recordTEvent` call |
+| `frontend/app/view/waveconfig/waveconfig-model.ts` | B.4 | Remove `RecordTEventCommand` calls |
+| `frontend/app/workspace/workspace-layout-model.ts` | B.4 | Remove `recordTEvent` call |
+| `frontend/app/modals/about.tsx` | B.4 | Remove `RecordTEventCommand` call |
+| `frontend/app/store/keymodel.ts` | B.4 | Remove `recordTEvent` import + call |
+| `frontend/app/block/blockenv.ts` | B.4 | Remove `ActivityCommand` type ref |
+| `frontend/app/tab/tabbarenv.ts` | B.4 | Remove `ActivityCommand` type ref |
+| `frontend/app/tab/tab.tsx` | B.4 | Remove `ActivityCommand` type ref |
+| `frontend/app/tab/vtabbarenv.ts` | B.4 | Remove `ActivityCommand` type ref |
+| `frontend/app/view/waveconfig/waveconfigenv.ts` | B.4 | Remove `RecordTEventCommand` type ref |
+| `frontend/app/onboarding/onboarding.tsx` | B.5 | Restructure: remove toggle, simplify flow |
+| `frontend/app/onboarding/onboarding-starask.tsx` | B.5 | Remove `RecordTEventCommand` calls |
+| `frontend/app/onboarding/onboarding-features.tsx` | B.5 | Remove `RecordTEventCommand` calls |
+| `frontend/app/onboarding/onboarding-durable.tsx` | B.5 | Remove `RecordTEventCommand` call |
+| `frontend/app/onboarding/onboarding-upgrade-minor.tsx` | B.5 | Remove `RecordTEventCommand` calls |
+| `frontend/app/onboarding/onboarding-upgrade-v0131.tsx` | B.5 | Remove `RecordTEventCommand` calls |
+| `frontend/preview/previews/onboarding.preview.tsx` | B.5 | Remove `telemetryUpdateFn` prop |
+| `frontend/preview/mock/mockfilesystem.ts` | B.5 | Remove `telemetry.log` mock entry |
+| `frontend/app/aipanel/telemetryrequired.tsx` | B.6/B.8 | Delete or replace (coordinate with AI spec) |
+| `emain/emain.ts` | B.7 | Remove `sendDisplaysTDataEvent`, `logActiveState`, all RPC calls |
+| `emain/emain-activity.ts` | B.7 | Remove term command tracking functions |
+| `emain/emain-ipc.ts` | B.7 | Remove `"increment-term-commands"` IPC handler |
+| `emain/preload.ts` | B.7 | Remove `incrementTermCommands` API |
+| `frontend/types/custom.d.ts` | B.7 | Remove `incrementTermCommands` type decl |
+| `frontend/types/gotypes.d.ts` | — | Auto-generated; regen removes `ActivityUpdate`, `TEvent`, `telemetry:enabled` types |
+
+### Phase C (Delete packages)
+
+| File/Directory | Section | What changes |
+|------|---------|--------------|
+| `pkg/telemetry/` | C.1 | Delete entire directory |
+| `pkg/wcloud/` | C.2 | Delete entire directory |
+| Various Go files | C.3 | `go mod tidy`, fix imports, regenerate |
+
+### Phase D (Docs + DB)
+
+| File/Directory | Section | What changes |
+|------|---------|--------------|
+| `docs/docs/telemetry.mdx` | D.1 | Delete |
+| `docs/docs/telemetry-old.mdx` | D.1 | Delete |
+| `docs/docs/config.mdx` | D.1 | Remove telemetry config refs |
+| `docs/docs/faq.mdx` | D.1 | Remove telemetry Q&A |
+| `docs/docs/index.mdx` | D.1 | Remove telemetry mentions |
+| `db/migrations-wstore/` | D.3 | Add migration to drop `db_tevent`/`db_activity` tables |
\ No newline at end of file
diff --git a/.pi/specs/remove-updater-delete.md b/.pi/specs/remove-updater-delete.md
new file mode 100644
index 0000000000..20c07eac0c
--- /dev/null
+++ b/.pi/specs/remove-updater-delete.md
@@ -0,0 +1,333 @@
+# Spec: Delete Updater Dead Code
+
+**Date:** 2026-05-18
+**Status:** Draft
+**Prerequisite:** [[.pi/specs/remove-updater.md]] (all phases A-E complete)
+
+## Goal
+
+Delete all unreachable updater code left behind by the disable-phase. After this spec, no updater-related code or dependencies remain in the fork.
+
+## Background
+
+The disable-phase ([[.pi/specs/remove-updater.md]]) made `configureAutoUpdater()` a no-op and stubbed IPC APIs, but left the `Updater` class, event listeners, and menu items intact for upstream compatibility. Several files still import from `emain/updater.ts` and reference the `updater` object. This spec removes all of it.
+
+## What Remains After Disable-Phase
+
+| File | What's Left | Status |
+|------|-------------|--------|
+| `emain/updater.ts` | Full `Updater` class, `getUpdateChannel()`, event listeners, `autoUpdater` import, `configureAutoUpdater()` no-op | Dead code |
+| `emain/emain-menu.ts` | "Check for Updates" menu item → `updater?.checkForUpdates(true)` | Calls dead code |
+| `emain/emain-wavesrv.ts` | `import { updater }`, checks `updater?.status == "installing"` before restart | Always false |
+| `emain/emain-window.ts` | `import { updater }`, checks `updater?.status == "installing"` in quit handlers (2 places) | Always false |
+| `emain/emain-wsh.ts` | `import { getResolvedUpdateChannel }`, `handle_getupdatechannel()` RPC handler | Returns stale value |
+| `emain/emain.ts` | `updater?.stop()` in cleanup, `import { configureAutoUpdater, updater }` | Both no-ops |
+| `package.json` | `"electron-updater": "^6.6"` dependency | Unused |
+
+## Implementation Phases
+
+### Phase A: Remove updater references from Electron main process
+
+**Goal:** No file imports from `emain/updater.ts`. All updater-related menu items and guards removed.
+
+#### A.1: Remove "Check for Updates" menu item
+
+**File:** `emain/emain-menu.ts`
+
+- Remove the import: `import { updater } from "./updater"`
+- Remove the "Check for Updates" menu item from `appMenuItems`:
+ ```typescript
+ {
+ label: "Check for Updates",
+ click: () => {
+ fireAndForget(() => updater?.checkForUpdates(true));
+ },
+ },
+ ```
+- Keep the "About Wave Terminal" item and separator (adjust separator if needed for menu formatting)
+
+**Before:**
+```typescript
+const appMenuItems: Electron.MenuItemConstructorOptions[] = [
+ { label: "About Wave Terminal", ... },
+ { label: "Check for Updates", ... },
+ { type: "separator" },
+];
+```
+
+**After:**
+```typescript
+const appMenuItems: Electron.MenuItemConstructorOptions[] = [
+ { label: "About Wave Terminal", ... },
+ { type: "separator" },
+];
+```
+
+#### A.2: Remove updater guard from wavesrv restart
+
+**File:** `emain/emain-wavesrv.ts`
+
+- Remove the import: `import { updater } from "./updater"`
+- Remove the `updater?.status == "installing"` check from the restart logic
+- The check is a guard that prevents restart during an update install. Since updates are disabled, this check is always false and can be removed.
+
+**Before (approximate):**
+```typescript
+if (updater?.status == "installing") {
+ // skip restart during update install
+}
+```
+
+**After:**
+```typescript
+// (check removed — updater disabled)
+```
+
+#### A.3: Remove updater guards from window quit handlers
+
+**File:** `emain/emain-window.ts`
+
+- Remove the import: `import { updater } from "./updater"`
+- Remove `updater?.status == "installing"` from quit handler guards (2 locations, lines ~304 and ~335)
+- These checks prevent window close during update install. Always false now.
+
+**Before (line ~304):**
+```typescript
+if (getGlobalIsQuitting() || updater?.status == "installing" || getGlobalIsRelaunching()) {
+```
+
+**After:**
+```typescript
+if (getGlobalIsQuitting() || getGlobalIsRelaunching()) {
+```
+
+**Before (line ~335):**
+```typescript
+if (getGlobalIsQuitting() || updater?.status == "installing") {
+```
+
+**After:**
+```typescript
+if (getGlobalIsQuitting()) {
+```
+
+#### A.4: Remove `handle_getupdatechannel` RPC handler
+
+**File:** `emain/emain-wsh.ts`
+
+- Remove the import: `import { getResolvedUpdateChannel } from "emain/updater"`
+- Remove the `handle_getupdatechannel()` method entirely
+
+**Before:**
+```typescript
+async handle_getupdatechannel(rh: RpcResponseHelper): Promise {
+ return getResolvedUpdateChannel();
+}
+```
+
+**After:**
+```typescript
+// (method removed)
+```
+
+#### A.5: Clean up `emain/emain.ts`
+
+**File:** `emain/emain.ts`
+
+- Remove `updater` from the import: change `import { configureAutoUpdater, updater } from "./updater"` to `import { configureAutoUpdater } from "./updater"`
+- Remove `updater?.stop()` call (line ~175) — updater is never created, this is a no-op
+
+### Phase B: Delete `emain/updater.ts`
+
+**Goal:** The entire updater file is removed. All imports are already cleaned in Phase A.
+
+#### B.1: Delete the file
+
+- Delete: `emain/updater.ts`
+- This removes: `Updater` class (~150 lines), `getUpdateChannel()`, `getResolvedUpdateChannel()`, all `autoUpdater` event listeners, `configureAutoUpdater()` no-op
+
+#### B.2: Remove `configureAutoUpdater` import from `emain/emain.ts`
+
+- Remove `import { configureAutoUpdater } from "./updater"` (the last remaining import)
+- Remove the `await configureAutoUpdater()` call from the startup sequence (line ~303)
+
+### Phase C: Remove `electron-updater` dependency
+
+**Goal:** The npm package is no longer in the project.
+
+#### C.1: Remove from `package.json`
+
+- Remove `"electron-updater": "^6.6"` from dependencies
+
+#### C.2: Update lock file
+
+- Run `npm install` to regenerate `package-lock.json` without `electron-updater`
+
+### Phase D: Clean up stub IPC APIs (optional)
+
+**Goal:** Remove the stubbed IPC methods that no longer serve any purpose.
+
+#### D.1: Remove stubs from preload
+
+**File:** `emain/preload.ts`
+
+- Remove the 4 updater IPC stubs:
+ ```typescript
+ onUpdaterStatusChange: (callback) => {},
+ getUpdaterStatus: () => "up-to-date",
+ getUpdaterChannel: () => "latest",
+ installAppUpdate: () => {},
+ ```
+
+#### D.2: Remove type declarations
+
+**File:** `frontend/types/custom.d.ts`
+
+- Remove from `ElectronApi` interface:
+ ```typescript
+ onUpdaterStatusChange: (callback: (status: UpdaterStatus) => void) => void;
+ getUpdaterStatus: () => UpdaterStatus;
+ getUpdaterChannel: () => string;
+ installAppUpdate: () => void;
+ ```
+- Remove `UpdaterStatus` type:
+ ```typescript
+ type UpdaterStatus = "up-to-date" | "checking" | "downloading" | "ready" | "error" | "installing";
+ ```
+
+#### D.3: Remove `updaterStatusAtom` from global atoms
+
+**File:** `frontend/app/store/global-atoms.ts`
+
+- Remove `updaterStatusAtom` declaration
+- Remove it from the exported `atoms` object
+
+#### D.4: Remove updaterStatusAtom from env subsets
+
+| File | What to remove |
+|------|----------------|
+| `frontend/app/tab/tabbarenv.ts` | `updaterStatusAtom` from env subset type |
+| `frontend/app/tab/vtabbarenv.ts` | `updaterStatusAtom` from env subset type |
+
+#### D.5: Remove from preview mocks
+
+| File | What to remove |
+|------|----------------|
+| `frontend/preview/mock/mockwaveenv.ts` | `updaterStatusAtom` from mock atoms |
+| `frontend/preview/mock/preview-electron-api.ts` | `onUpdaterStatusChange`, `getUpdaterStatus`, `getUpdaterChannel`, `installAppUpdate` stubs |
+| `frontend/preview/previews/tabbar.preview.tsx` | `updaterStatus` usage |
+| `frontend/preview/previews/vtabbar.preview.tsx` | `updaterStatus` usage |
+
+#### D.6: Remove `UpdateStatusBanner` entirely
+
+| File | What to remove |
+|------|----------------|
+| `frontend/app/tab/updatebanner.tsx` | Delete entire file |
+| `frontend/app/tab/tabbar.tsx` | Remove `import { UpdateStatusBanner }` and ` ` render |
+
+### Phase E: Clean up Go autoupdate settings (optional, later)
+
+**Goal:** Remove `autoupdate:*` fields from Go config structs.
+
+> **Recommendation: Defer this phase.** The Go fields are harmless and keeping them minimizes upstream merge conflicts. Only remove if the fork diverges significantly from upstream.
+
+| File | What to remove |
+|------|----------------|
+| `pkg/wconfig/settingsconfig.go` | `AutoUpdateClear`, `AutoUpdateEnabled`, `AutoUpdateIntervalMs`, `AutoUpdateInstallOnQuit`, `AutoUpdateChannel` fields |
+| `pkg/wconfig/metaconsts.go` | `ConfigKey_AutoUpdate*` constants (auto-generated; regenerate after settingsconfig change) |
+| `pkg/wconfig/defaultconfig/settings.json` | `autoupdate:enabled`, `autoupdate:installonquit`, `autoupdate:intervalms` entries |
+| `schema/settings.json` | `autoupdate:*`, `autoupdate:enabled`, `autoupdate:intervalms`, `autoupdate:installonquit`, `autoupdate:channel` entries |
+| `frontend/types/gotypes.d.ts` | Auto-generated; regenerate after Go changes |
+
+## Implementation Order
+
+1. **A.1** — Remove "Check for Updates" menu item
+2. **A.2** — Remove updater guard from wavesrv restart
+3. **A.3** — Remove updater guards from window quit handlers
+4. **A.4** — Remove `handle_getupdatechannel` RPC handler
+5. **A.5** — Clean up `emain.ts` import + `updater?.stop()`
+6. **B.1** — Delete `emain/updater.ts`
+7. **B.2** — Remove `configureAutoUpdater` import + call from `emain.ts`
+8. **C.1** — Remove `electron-updater` from `package.json`
+9. **C.2** — `npm install` to update lock file
+10. **D.1–D.6** — Clean up stub IPC APIs (optional)
+11. **E** — Clean up Go settings (deferred)
+
+## Verification Checklist
+
+- [ ] `task dev` completes without errors
+- [ ] `task start` launches the app
+- [ ] No "Check for Updates" menu item in the app menu
+- [ ] No console errors about missing `updater` module
+- [ ] No `electron-updater` in `package.json` dependencies
+- [ ] `emain/updater.ts` no longer exists
+- [ ] App functions normally (terminals, SSH, file browser, etc.)
+- [ ] Existing tests pass
+
+## Risk Assessment
+
+| Risk | Mitigation |
+|------|------------|
+| `updater?.status == "installing"` guards prevent restart/quit during updates | Always false now; removing them is safe. No update can ever reach "installing" state. |
+| `handle_getupdatechannel` RPC called by external code | Only called by frontend via IPC; frontend stub already returns `"latest"`. After D.2, no callers remain. |
+| Upstream merge conflicts on `emain/` files | Larger diff than disable-phase, but changes are surgical (remove specific lines, not restructure files). |
+| `electron-updater` transitive dependencies | Removing it may clean up several sub-dependencies. `npm install` handles this. |
+| Preview mocks break without `updaterStatusAtom` | Phase D handles mock cleanup. If Phase D is deferred, mocks still work (atom returns static value). |
+
+## File Cross-Reference
+
+### Phase A (Remove updater references)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `emain/emain-menu.ts` | A.1 | Remove `updater` import + "Check for Updates" menu item |
+| `emain/emain-wavesrv.ts` | A.2 | Remove `updater` import + `updater?.status == "installing"` guard |
+| `emain/emain-window.ts` | A.3 | Remove `updater` import + 2 `updater?.status == "installing"` guards |
+| `emain/emain-wsh.ts` | A.4 | Remove `getResolvedUpdateChannel` import + `handle_getupdatechannel()` method |
+| `emain/emain.ts` | A.5 | Remove `updater` from import + `updater?.stop()` call |
+
+### Phase B (Delete updater file)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `emain/updater.ts` | B.1 | Delete entire file |
+| `emain/emain.ts` | B.2 | Remove `configureAutoUpdater` import + `await configureAutoUpdater()` call |
+
+### Phase C (Remove dependency)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `package.json` | C.1 | Remove `"electron-updater"` from dependencies |
+| `package-lock.json` | C.2 | Regenerated by `npm install` |
+
+### Phase D (Clean up stubs — optional)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `emain/preload.ts` | D.1 | Remove 4 updater IPC stubs |
+| `frontend/types/custom.d.ts` | D.2 | Remove `UpdaterStatus` type + 4 IPC API declarations |
+| `frontend/app/store/global-atoms.ts` | D.3 | Remove `updaterStatusAtom` declaration + export |
+| `frontend/app/tab/tabbarenv.ts` | D.4 | Remove `updaterStatusAtom` from env subset |
+| `frontend/app/tab/vtabbarenv.ts` | D.4 | Remove `updaterStatusAtom` from env subset |
+| `frontend/preview/mock/mockwaveenv.ts` | D.5 | Remove `updaterStatusAtom` from mock atoms |
+| `frontend/preview/mock/preview-electron-api.ts` | D.5 | Remove 4 updater API stubs |
+| `frontend/preview/previews/tabbar.preview.tsx` | D.5 | Remove `updaterStatus` usage |
+| `frontend/preview/previews/vtabbar.preview.tsx` | D.5 | Remove `updaterStatus` usage |
+| `frontend/app/tab/updatebanner.tsx` | D.6 | Delete entire file |
+| `frontend/app/tab/tabbar.tsx` | D.6 | Remove `UpdateStatusBanner` import + render |
+
+### Phase E (Go settings — deferred)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `pkg/wconfig/settingsconfig.go` | E | Remove `AutoUpdate*` fields |
+| `pkg/wconfig/metaconsts.go` | E | Remove `ConfigKey_AutoUpdate*` constants |
+| `pkg/wconfig/defaultconfig/settings.json` | E | Remove `autoupdate:*` entries |
+| `schema/settings.json` | E | Remove `autoupdate:*` schema entries |
+| `frontend/types/gotypes.d.ts` | E | Regenerate after Go changes |
+
+## Interaction with Other Specs
+
+- **[[.pi/specs/remove-updater.md]]** — This spec is the deletion follow-up. All phases A-E of the disable-spec must be complete first.
+- **[[.pi/specs/remove-waveai.md]]** — No overlap.
+- **[[.pi/specs/remove-telemetry.md]]** — No overlap.
diff --git a/.pi/specs/remove-updater.md b/.pi/specs/remove-updater.md
new file mode 100644
index 0000000000..5f3584906e
--- /dev/null
+++ b/.pi/specs/remove-updater.md
@@ -0,0 +1,280 @@
+# Spec: Remove Auto-Updater
+
+**Date:** 2026-05-18
+**Status:** Draft
+
+## Goal
+
+Disable and remove all automatic update checking, downloading, and installation from Wave Terminal. The fork should not contact `dl.waveterm.dev`, check GitHub releases, or install updates. No update-related UI should be visible to the user.
+
+## What the Updater Does Today
+
+1. **Periodic check** — On startup, then every hour (configurable via `autoupdate:intervalms`), contacts `https://dl.waveterm.dev/releases-w2` to check for new versions
+2. **Automatic download** — When a newer version is found, downloads it silently in the background
+3. **User notification** — Shows a system notification + in-tab-bar banner when download completes
+4. **Install on click** — User clicks notification → dialog with "Restart" / "Later" → `autoUpdater.quitAndInstall()`
+5. **Install on quit** — `autoUpdater.autoInstallOnAppQuit = true` (default) — installs downloaded update on next launch if user quits normally
+6. **Channel selection** — Supports `latest` and `beta` channels via `autoupdate:channel` setting; reads `app-update.yml` bundled in the binary
+
+## Scope
+
+### What to remove/disable
+
+- All outbound network calls to `dl.waveterm.dev`
+- Background update checking (periodic + startup)
+- Automatic download of new versions
+- System notifications about updates
+- In-tab-bar update status banner (`UpdateStatusBanner`)
+- "Update Channel" line in the About modal
+- IPC APIs for updater status (`onUpdaterStatusChange`, `getUpdaterStatus`, `getUpdaterChannel`, `installAppUpdate`)
+- `updaterStatusAtom` in the frontend store
+- `electron-updater` dependency (optional, see Phase E)
+- `publish` config in `electron-builder.config.cjs`
+- Default `autoupdate:*` settings values
+
+### What to keep
+
+- `autoupdate:*` fields in `SettingsType` (`settingsconfig.go`) — harmless, keeps fork closer to upstream
+- `ConfigKey_AutoUpdate*` constants in `metaconsts.go` — auto-generated, harmless
+- `autoupdate:*` entries in `schema/settings.json` — harmless
+- `emain/updater.ts` file itself — stubbed to no-op, not deleted (upstream compatibility)
+- `electron-updater` in `package.json` — kept as dependency (upstream compatibility, no runtime cost)
+
+## Implementation Phases
+
+### Phase A: Disable the updater in the Electron main process
+
+**Goal:** No network calls to update servers. No downloads. No installs. App starts without errors.
+
+#### A.1: Make `configureAutoUpdater()` a no-op
+
+**File:** `emain/updater.ts`
+
+- Replace the body of `configureAutoUpdater()` to skip all updater initialization:
+ ```typescript
+ export async function configureAutoUpdater() {
+ console.log("skipping auto-updater (disabled in this build)");
+ }
+ ```
+- This eliminates:
+ - All calls to `autoUpdater.checkForUpdates()`
+ - All event listeners on `autoUpdater`
+ - All periodic intervals
+ - All system notifications
+ - All calls to `autoUpdater.quitAndInstall()`
+- The `Updater` class, `getUpdateChannel()`, and all other code stays intact but unreachable.
+
+#### A.2: Remove `updater?.stop()` call in cleanup
+
+**File:** `emain/emain.ts`
+
+- Line ~175: `updater?.stop()` — This is harmless (updater is null since `configureAutoUpdater` is a no-op), but can be removed for cleanliness.
+- **Decision:** Leave it. It's a single line that does nothing when `updater` is undefined/never created. Removing it creates a larger diff for no benefit.
+
+### Phase B: Remove update UI from the frontend
+
+**Goal:** No update status banner, no update channel in About modal. App renders without errors.
+
+#### B.1: Make `UpdateStatusBanner` always render null
+
+**File:** `frontend/app/tab/updatebanner.tsx`
+
+- Change `UpdateStatusBannerComponent` to return `null` unconditionally:
+ ```typescript
+ const UpdateStatusBannerComponent = () => {
+ return null;
+ };
+ ```
+- Keep the file and export intact. The component is still imported by `tabbar.tsx` and `vtabbar` preview, so keeping the export avoids cascading changes.
+
+#### B.2: Remove "Update Channel" from the About modal
+
+**File:** `frontend/app/modals/about.tsx`
+
+- Remove the `updaterChannel` prop from `AboutModalVProps` interface
+- Remove the "Update Channel: {updaterChannel}" line from the rendered JSX
+- Remove the `updaterChannel` computation in `AboutModal` component:
+ ```typescript
+ // Remove this line:
+ const updaterChannel = fullConfig?.settings?.["autoupdate:channel"] ?? "latest";
+ ```
+- Remove `updaterChannel` from the `` props
+
+### Phase C: Stub updater IPC APIs in the frontend
+
+**Goal:** Frontend code that references updater APIs doesn't crash. Atoms initialize cleanly.
+
+#### C.1: Stub IPC methods in preload
+
+**File:** `emain/preload.ts`
+
+- Replace the 4 updater IPC bindings with stubs:
+ ```typescript
+ // Updater disabled — stubs for upstream compatibility
+ onUpdaterStatusChange: (callback) => {}, // never fires
+ getUpdaterStatus: () => "up-to-date",
+ getUpdaterChannel: () => "latest",
+ installAppUpdate: () => {},
+ ```
+
+#### C.2: Simplify `updaterStatusAtom` initialization
+
+**File:** `frontend/app/store/global-atoms.ts`
+
+- Remove the `try/catch` block that calls `getApi().getUpdaterStatus()` and `getApi().onUpdaterStatusChange()`
+- Initialize the atom to `"up-to-date"` directly:
+ ```typescript
+ const updaterStatusAtom = atom("up-to-date") as PrimitiveAtom;
+ ```
+- Remove the error logging for the updater init.
+- Keep the atom in the exported `atoms` object (it's referenced by tabbar env subsets and preview mocks).
+
+#### C.3: Remove updater init from `wave.ts`
+
+**File:** `frontend/wave.ts`
+
+- Remove the line:
+ ```typescript
+ globalStore.set(atoms.updaterStatusAtom, getApi().getUpdaterStatus());
+ ```
+- This is redundant since the atom is already initialized to `"up-to-date"` and the IPC API is stubbed.
+
+### Phase D: Update default settings
+
+**Goal:** Default config reflects disabled updater. Existing user configs are harmless (settings are just ignored).
+
+#### D.1: Set autoupdate defaults to disabled
+
+**File:** `pkg/wconfig/defaultconfig/settings.json`
+
+- Change `"autoupdate:enabled"` from `true` to `false`
+- Leave `autoupdate:installonquit` and `autoupdate:intervalms` as-is (harmless, never read)
+
+### Phase E: Remove `publish` config from build configuration (optional)
+
+**Goal:** Even if someone runs `electron-builder` on the fork, it won't try to publish to upstream servers.
+
+#### E.1: Remove publish URL from electron-builder config
+
+**File:** `electron-builder.config.cjs`
+
+- Remove the `publish` block:
+ ```javascript
+ publish: {
+ provider: "generic",
+ url: "https://dl.waveterm.dev/releases-w2",
+ },
+ ```
+- This prevents the build system from generating update manifests pointing to upstream servers.
+
+### Phase F: Remove `electron-updater` dependency (optional, later)
+
+**Goal:** Remove the `electron-updater` npm package entirely.
+
+#### F.1: Remove import from `emain/updater.ts`
+
+- Remove `import { autoUpdater } from "electron-updater"`
+- Remove `import YAML from "yaml"` (only used for `app-update.yml` parsing in `getUpdateChannel()`)
+- Remove `import { readFileSync } from "fs"` (only used in `getUpdateChannel()`)
+- Remove `import path from "path"` (only used in `getUpdateChannel()`)
+- The file becomes a single-line no-op export with no dependencies.
+
+#### F.2: Remove from `package.json`
+
+- Remove `"electron-updater": "^6.6"` from dependencies
+- Run `npm install` to update `package-lock.json`
+
+**Recommendation:** Defer Phase F. The `electron-updater` package is ~300KB and adds no runtime overhead when `configureAutoUpdater()` is a no-op. Keeping it minimizes diff size and simplifies upstream merges.
+
+## Implementation Order
+
+1. **A.1** — Disable `configureAutoUpdater()` (stops all network activity immediately)
+2. **B.1** — Hide update banner in tab bar
+3. **B.2** — Remove update channel from About modal
+4. **C.1** — Stub IPC APIs
+5. **C.2** — Simplify `updaterStatusAtom`
+6. **C.3** — Remove updater init from `wave.ts`
+7. **D.1** — Update default settings
+8. **E.1** — Remove publish config from build
+
+## Verification Checklist
+
+- [ ] `task dev` completes without errors
+- [ ] `task start` launches the app
+- [ ] No console errors related to updater
+- [ ] No outbound HTTP to `dl.waveterm.dev` (verify with `lsof -i` or network monitoring)
+- [ ] No update banner appears in tab bar
+- [ ] About modal does not show "Update Channel"
+- [ ] No system notifications about updates
+- [ ] App functions normally (terminals, SSH, file browser, etc.)
+- [ ] Existing tests pass (`task test` / `npm test`)
+
+## Risk Assessment
+
+| Risk | Mitigation |
+|------|------------|
+| `electron-updater` import causes build errors if removed | Phase F deferred; keep dependency. Even if removed, `emain/updater.ts` stays as a no-op stub. |
+| Upstream merge conflicts on `updater.ts` | Minimal changes (single function body). Rest of file untouched. |
+| Existing user configs with `autoupdate:enabled: true` | Harmless — the setting is never read after `configureAutoUpdater()` is a no-op. |
+| Preview mocks reference `updaterStatusAtom` | Atom stays in the atoms object, just always `"up-to-date"`. Mocks work unchanged. |
+| Tabbar env subsets declare `updaterStatusAtom` | Env declarations stay unchanged; atom exists and is accessible. |
+| `app-update.yml` bundled in production binary | Without `configureAutoUpdater()` creating an `Updater`, the file is never read. Can be cleaned in a later pass. |
+
+## File Cross-Reference
+
+### Phase A (Electron main — disable updater)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `emain/updater.ts` | A.1 | `configureAutoUpdater()` → no-op |
+
+### Phase B (Frontend — hide UI)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `frontend/app/tab/updatebanner.tsx` | B.1 | Component always returns `null` |
+| `frontend/app/modals/about.tsx` | B.2 | Remove "Update Channel" line + prop |
+
+### Phase C (Frontend — stub IPC)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `emain/preload.ts` | C.1 | 4 updater IPC methods → stubs |
+| `frontend/app/store/global-atoms.ts` | C.2 | `updaterStatusAtom` → static `"up-to-date"`, remove IPC subscription |
+| `frontend/wave.ts` | C.3 | Remove `set(atoms.updaterStatusAtom, ...)` call |
+
+### Phase D (Default settings)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `pkg/wconfig/defaultconfig/settings.json` | D.1 | `autoupdate:enabled` → `false` |
+
+### Phase E (Build config)
+
+| File | Section | What changes |
+|------|---------|--------------|
+| `electron-builder.config.cjs` | E.1 | Remove `publish` block |
+
+### Left Untouched
+
+| File/Directory | Why |
+|----------------|-----|
+| `emain/emain.ts` | `updater?.stop()` is harmless null-op; `configureAutoUpdater()` call stays (now no-op) |
+| `pkg/wconfig/settingsconfig.go` | `AutoUpdate*` fields stay for upstream compatibility |
+| `pkg/wconfig/metaconsts.go` | `ConfigKey_AutoUpdate*` stay (auto-generated) |
+| `schema/settings.json` | `autoupdate:*` entries stay (harmless) |
+| `frontend/types/custom.d.ts` | `UpdaterStatus` type + IPC API declarations stay (type-safe stubs) |
+| `frontend/app/tab/tabbarenv.ts` | `updaterStatusAtom` env declaration stays |
+| `frontend/app/tab/vtabbarenv.ts` | `updaterStatusAtom` env declaration stays |
+| `frontend/app/tab/tabbar.tsx` | ` ` render stays (component returns null) |
+| `frontend/preview/mock/mockwaveenv.ts` | `updaterStatusAtom` mock stays |
+| `frontend/preview/previews/tabbar.preview.tsx` | `updaterStatus` usage stays |
+| `frontend/preview/previews/vtabbar.preview.tsx` | `updaterStatus` usage stays |
+| `package.json` | `electron-updater` dependency stays (Phase F deferred) |
+| `.github/workflows/publish-release.yml` | Fork doesn't publish releases; workflow is upstream-only |
+| `Taskfile.yml` | `RELEASES_BUCKET` var stays; `artifacts:publish:*` tasks are upstream-only |
+
+## Interaction with Other Specs
+
+- **remove-telemetry.md** — No overlap. Telemetry spec explicitly called out `autoupdate:*` settings as "genuine auto-update config, not telemetry" and left them untouched.
+- **remove-waveai.md** — No overlap. AI removal doesn't touch updater code.
diff --git a/.pi/specs/remove-waveai.md b/.pi/specs/remove-waveai.md
new file mode 100644
index 0000000000..6506a80b3f
--- /dev/null
+++ b/.pi/specs/remove-waveai.md
@@ -0,0 +1,459 @@
+# Spec: Remove Wave AI Features
+
+**Date:** 2026-05-12
+**Status:** Draft
+
+## Goal
+
+Disable and hide all Wave AI features from the UI. Do not delete code initially — comment out or guard behind no-ops so the fork stays close to upstream and re-enabling is trivial.
+
+## Scope
+
+### What to remove/disable
+
+- Wave AI chat panel (`waveai` block type)
+- AI file diff viewer (`aifilediff` block type)
+- AI modes configuration (`waveai.json`)
+- AI presets configuration (`aipresets.json`)
+- AI-related keyboard shortcuts
+- AI focus management
+- AI RPC commands (frontend client + backend server)
+- AI web endpoints
+- AI activity telemetry
+- AI config fields in `settingsconfig.go`
+- AI documentation pages
+- `ai:apitokensecretname` field (AI token via secrets)
+- AI button in tab bar (`WaveAIButton`)
+- AI panel from workspace layout
+- AI onboarding page (`WaveAIPage`, `fakechat.tsx`)
+
+### What to keep
+
+- `pkg/secretstore/` — general encrypted key-value store (used by SSH passwords, potentially future features)
+- `ssh:passwordsecretname` — SSH password via secrets (non-AI use case)
+
+## Implementation Phases
+
+### Phase A: Disable the UI (frontend only)
+
+**Goal:** AI panels cannot be opened, AI is not visible in any menus or settings. App builds and runs without errors.
+
+#### A.1: Unregister AI block types
+
+**File:** `frontend/app/block/blockregistry.ts`
+
+- Comment out or remove: `BlockRegistry.set("waveai", WaveAiModel)`
+- Comment out or remove: `BlockRegistry.set("aifilediff", AiFileDiffViewModel)`
+- Remove imports: `WaveAiModel`, `AiFileDiffViewModel`
+
+**Verification:** App starts without errors. No AI block types registered.
+
+#### A.2: Strip AI from block utilities
+
+**File:** `frontend/app/block/blockutil.tsx`
+
+- Remove the `view == "waveai"` cases in `getBlockTitle()` and `getBlockIcon()` (or return empty/nil)
+
+#### A.3: Remove AI keyboard shortcuts
+
+**File:** `frontend/app/store/keymodel.ts`
+
+- Remove the `WaveAIModel` import
+- Remove all `WaveAIModel.getInstance()` calls (lines ~151, 155, 177, 184, 192, 199, 227, 248, 252, 260, 265, 268, 687, 691, 696, 700)
+- Remove `focusType === "waveai"` branches
+- Remove `inWaveAI` variable and related navigation logic
+
+#### A.4: Remove AI focus management
+
+**File:** `frontend/app/store/focusManager.ts`
+
+- Remove `waveAIHasFocusWithin` and `WaveAIModel` imports
+- Change `FocusStrType` from `"node" | "waveai"` to just `"node"`
+- Remove `setWaveAIFocused()` and `requestWaveAIFocus()` methods
+- Remove `"waveai"` branches in focus handling
+
+#### A.5: Remove AI global atoms
+
+**File:** `frontend/app/store/global-atoms.ts`
+
+- Remove `waveaiModeConfigAtom`
+- Remove `ai@` preset filtering logic (line ~68)
+- Remove from exported atoms list
+
+#### A.6: Remove AI event listeners
+
+**File:** `frontend/app/store/global.ts`
+
+- Remove `waveai:modeconfig` event handler
+- Remove `waveai:ratelimit` event handler
+
+#### A.7: Remove AI RPC client methods
+
+**File:** `frontend/app/store/wshclientapi.ts`
+
+- Remove: `GetWaveAIChatCommand`, `GetWaveAIModeConfigCommand`, `GetWaveAIRateLimitCommand`, `WaveAIAddContextCommand`, `WaveAIEnableTelemetryCommand`, `WaveAIGetToolDiffCommand`, `WaveAIToolApproveCommand`
+
+**File:** `frontend/app/store/tabrpcclient.ts`
+
+- Remove `WaveAIModel` import
+- Remove `handle_waveaiaddcontext()` method
+
+#### A.8: Remove AI from term model
+
+**File:** `frontend/app/view/term/term-model.ts`
+
+- Remove `WaveAIModel` import
+- Remove the AI-related code at line ~848
+
+#### A.9: Remove AI config file handling
+
+**File:** `frontend/app/view/waveconfig/waveconfig-model.ts`
+
+- Remove the `waveai.json` config file entry (line ~84)
+- Remove `validateWaveAiJson()` function
+- Remove `aipresets.json` references (line ~122)
+
+**File:** `frontend/app/monaco/schemaendpoints.ts`
+
+- Remove `waveaiSchema` import and registration
+- Remove `aipresetsSchema` import and registration
+
+**File:** `frontend/preview/mock/defaultconfig.ts`
+
+- Remove `waveaiJson` import
+- Remove `waveai` entry from mock config
+
+#### A.10: Remove AI visual component
+
+**File:** `frontend/app/view/waveconfig/waveaivisual.tsx`
+
+- Mark as unused (can keep file but it won't be imported anywhere)
+
+#### A.11: Remove AI button from tab bar
+
+**File:** `frontend/app/tab/tabbar.tsx`
+
+- Remove `WaveAIButton` component (lines ~48-76)
+- Remove ` ` from render (line ~616)
+- Remove `waveAIButtonRef` usage
+- Remove `export { TabBar, WaveAIButton }` — change to `export { TabBar }`
+
+#### A.12: Remove AI from workspace
+
+**File:** `frontend/app/workspace/workspace.tsx`
+
+- Remove `import { AIPanel } from "@/app/aipanel/aipanel"`
+- Remove `getApi().setWaveAIOpen(isVisible)` call (line ~87)
+- Remove `` rendering from JSX
+
+**File:** `frontend/app/workspace/workspace-layout-model.ts`
+
+- Remove `import { WaveAIModel } from "@/app/aipanel/waveai-model"`
+- Remove `waveai:panelopen` and `waveai:panelwidth` meta key handling (lines ~93, ~133, ~137)
+- Remove `getApi().setWaveAIOpen(visible)` call (line ~397)
+- Remove `WaveAIModel.getInstance().focusInput()` call (line ~409)
+- Remove the "vtab stays constant, aipanel absorbs the change" logic (line ~230)
+
+#### A.13: Remove AI from onboarding
+
+**File:** `frontend/app/onboarding/onboarding-features.tsx`
+
+- Remove `WaveAIPage` component (lines ~22-247)
+- Remove `"waveai"` from `FeaturePageName` type
+- Change default `currentPage` from `"waveai"` to next feature (e.g., `"durable"`)
+- Remove `"waveai"` case from page navigation logic
+- Remove `handlePrev()` navigation to `"waveai"`
+
+**File:** `frontend/app/onboarding/fakechat.tsx`
+
+- Mark as unused (won't be imported after WaveAIPage is removed)
+
+#### A.14: Electron main — remove AI activity tracking
+
+**File:** `emain/emain.ts`
+
+- Already clean — no AI references found (telemetry removed in prior phase)
+
+**File:** `emain/emain-window.ts`
+
+- Remove `ipcMain.on("set-waveai-open", ...)` handler (line ~760)
+
+**File:** `emain/preload.ts`
+
+- Remove `setWaveAIOpen` from IPC exposed methods (line ~65)
+
+**File:** `emain/emain-tabview.ts`
+
+- Remove `isWaveAIOpen` field from tab view struct (line ~121)
+- Remove `this.isWaveAIOpen = false` initialization (line ~145)
+
+### Phase B: Remove backend wiring (Go)
+
+**Goal:** No AI RPC handlers, no AI config fields, no AI web endpoints. `pkg/aiusechat/` stays intact but unused.
+
+#### B.1: Remove AI RPC types
+
+**File:** `pkg/wshrpc/wshrpctypes.go`
+
+- Remove from interface: `GetWaveAIModeConfigCommand`, `WaveAIEnableTelemetryCommand`, `GetWaveAIChatCommand`, `GetWaveAIRateLimitCommand`, `WaveAIToolApproveCommand`, `WaveAIAddContextCommand`, `WaveAIGetToolDiffCommand`
+- Remove types: `CommandGetWaveAIChatData`, `CommandWaveAIToolApproveData`, `CommandWaveAIAddContextData`, `CommandWaveAIGetToolDiffData`, `CommandWaveAIGetToolDiffRtnData`
+- Remove from telemetry props: `WaveAIFgMinutes`, `WaveAIActiveMinutes`
+- Remove `uctypes` import if no longer needed
+
+#### B.2: Remove AI RPC server handlers
+
+**File:** `pkg/wshrpc/wshserver/wshserver.go`
+
+- Remove: `GetWaveAIModeConfigCommand()`, `WaveAIEnableTelemetryCommand()`, `GetWaveAIChatCommand()`, `GetWaveAIRateLimitCommand()`, `WaveAIToolApproveCommand()`, `WaveAIGetToolDiffCommand()`
+- Remove imports: `aiusechat`, `chatstore`, `uctypes` (if no other uses remain)
+
+#### B.3: Remove AI RPC client helpers
+
+**File:** `pkg/wshrpc/wshclient/wshclient.go`
+
+- Remove: `GetWaveAIChatCommand()`, `GetWaveAIModeConfigCommand()`, `GetWaveAIRateLimitCommand()`, `WaveAIAddContextCommand()`, `WaveAIEnableTelemetryCommand()`, `WaveAIGetToolDiffCommand()`, `WaveAIToolApproveCommand()`
+- Remove `uctypes` import if no longer needed
+
+#### B.4: Remove AI web endpoints
+
+**File:** `pkg/web/web.go`
+
+- Remove: `/api/post-chat-message` handler
+- Remove: `/wave/aichat` handler
+- Remove `aiusechat` import if no longer needed
+
+#### B.5: Remove AI initialization
+
+**File:** `cmd/server/main-server.go`
+
+- Remove: `aiusechat.InitAIModeConfigWatcher()` call
+- Remove `aiusechat` import if no longer needed
+
+#### B.6: Remove AI config fields
+
+**File:** `pkg/wconfig/settingsconfig.go`
+
+- Remove from `FrontendConfig`: `WaveAiShowCloudModes`, `WaveAiDefaultMode`
+- Remove from `AIProviderConfig`: `WaveAICloud`, `WaveAIPremium`
+- Remove from `FullConfig`: `WaveAIModes`
+- Remove `GetCustomAIModeConfigs()` function
+- Remove `ai:apitokensecretname` from `AIProviderConfig` (field `APITokenSecretName`)
+- Remove `AIModeConfigType` if no longer referenced
+
+#### B.7: Remove AI TypeScript generation
+
+**File:** `pkg/tsgen/tsgenevent.go`
+
+- Remove: `Event_WaveAIRateLimit` mapping
+- Remove `uctypes` import if no longer needed
+
+#### B.8: Remove default AI config
+
+**File:** `pkg/wconfig/defaultconfig/waveai.json`
+
+- Delete or mark as unused (won't be loaded if `WaveAIModes` is removed from config)
+
+### Phase C: Clean up docs & schemas
+
+**Goal:** No AI references in public-facing documentation or JSON schemas.
+
+#### C.1: Remove AI documentation
+
+- Delete: `docs/docs/waveai.mdx`
+- Delete: `docs/docs/waveai-modes.mdx`
+- Delete: `docs/docs/ai-presets.mdx`
+- Audit: `docs/docs/secrets.mdx` — remove AI token examples, keep SSH password secret examples
+- Audit: `docs/docs/config.mdx` — remove AI config references
+- Audit: `docs/docs/telemetry.mdx` — remove AI telemetry references
+- Audit: `docs/docs/connections.mdx` — remove `ai:apitokensecretname` references
+
+#### C.2: Remove JSON schemas
+
+- Delete: `schema/waveai.json`
+- Delete: `schema/aipresets.json`
+
+### Phase D: Delete unused code (optional, later)
+
+**Goal:** Remove dead code after the fork is stable and verified.
+
+- Delete: `pkg/aiusechat/` (entire directory, ~12K lines)
+- Delete: `frontend/app/aipanel/` (17 files)
+- Delete: `frontend/app/view/waveai/waveai.tsx`
+- Delete: `frontend/app/view/aifilediff/aifilediff.tsx`
+- Delete: `frontend/app/view/waveconfig/waveaivisual.tsx`
+
+## Implementation Order
+
+Start with deepest dependencies and work up to UI components to avoid dangling imports:
+
+1. **A.1–A.2** — Block registry + utilities (foundation)
+2. **A.3–A.6** — Store layer (keyboard, focus, atoms, events)
+3. **A.7–A.9** — RPC clients + config handling
+4. **A.10** — Visual component (orphaned)
+5. **A.11–A.13** — UI components (tab bar, workspace, onboarding)
+6. **A.14** — Electron main (IPC cleanup)
+
+## Verification Checklist
+
+After each phase:
+
+- [ ] `task dev` completes without errors
+- [ ] `task start` launches the app
+- [ ] No console errors related to missing AI components
+- [ ] No AI panels appear in the UI
+- [ ] No AI entries in settings/config UI
+- [ ] No AI keyboard shortcuts active
+- [ ] App functions normally for non-AI features (terminals, file browser, SSH connections)
+
+## Phase A Review — 2026-05-14
+
+### Issues Found During Review
+
+#### 🔴 A.15: Builder workspace still imports AIPanel and WaveAIModel (Not in original spec)
+
+The builder subsystem (`frontend/builder/`) has deep AI integration that was not covered by the original Phase A spec. These are live imports that will crash if `aipanel/` is ever deleted (Phase D).
+
+**Files:**
+- `frontend/builder/builder-workspace.tsx` — imports `AIPanel` from `@/app/aipanel/aipanel`, renders ` `
+- `frontend/builder/builder-buildpanel.tsx` — imports `WaveAIModel`, calls `WaveAIModel.getInstance()` for "Add to Context" context menu and AI model access
+- `frontend/builder/tabs/builder-previewtab.tsx` — imports `WaveAIModel`, calls `WaveAIModel.getInstance()` for chat ID access
+- `frontend/builder/tabs/builder-filestab.tsx` — imports `formatFileSize` from `@/app/aipanel/ai-utils` (generic utility trapped in AI module)
+- `frontend/builder/store/builder-focusmanager.ts` — has `BuilderFocusType = "waveai" | "app"` and `setWaveAIFocused()` method
+
+**Fix:** Remove AIPanel from builder workspace, replace WaveAIModel calls with stubs or no-ops, move `formatFileSize` to a shared utility, change `BuilderFocusType` to just `"app"`.
+
+#### 🟡 A.9 partial: "AI Presets" deprecated config entry still in settings UI
+
+`frontend/app/view/waveconfig/waveconfig-model.ts` still has:
+- `validateAiJson()` function (lines 35-44) that validates keys starting with `ai@`
+- "AI Presets" deprecated config file entry (lines 93-103) pointing to `presets/ai.json` with `docsUrl: "https://docs.waveterm.dev/ai-presets"`
+
+This means AI Presets still appears in the settings UI as a deprecated file.
+
+**Fix:** Remove `validateAiJson()` and the "AI Presets" entry from `deprecatedConfigFiles`.
+
+#### 🟡 A.3 partial: `inWaveAI` dead code in layoutModel.ts
+
+`frontend/layout/lib/layoutModel.ts` line 1107 still has `inWaveAI` parameter in `switchNodeFocusInDirection()`, and lines 1127-1131 have WaveAI-specific navigation logic. Caller in `keymodel.ts` passes `false`, so it's harmless but is dead code.
+
+**Fix:** Remove `inWaveAI` parameter and the WaveAI-specific branch from `switchNodeFocusInDirection()`. Update caller in `keymodel.ts`.
+
+#### 🟢 A.14 partial: Mock Electron API still has `setWaveAIOpen`
+
+`frontend/preview/mock/preview-electron-api.ts` line 53 still has `setWaveAIOpen: (_isOpen: boolean) => {}`.
+
+**Fix:** Remove `setWaveAIOpen` from the mock API object.
+
+#### 🟢 Dead `rateLimitInfoAtom` declaration in global-atoms.ts
+
+`frontend/app/store/global-atoms.ts` line 115 declares `rateLimitInfoAtom` but never exports it or adds it to the `atoms` object. Leftover from AI rate limit tracking.
+
+**Fix:** Remove the `rateLimitInfoAtom` declaration.
+
+### Deferred Items (Not Phase A Bugs)
+
+These are expected to be cleaned up in later phases:
+
+| Item | Why Deferred | Phase |
+|------|-------------|-------|
+| Auto-generated TS types (`gotypes.d.ts`, `waveevent.d.ts`, `wshclientapi.ts`, `custom.d.ts`) still have AI definitions | Regeneration depends on Go backend types being removed first | B → regenerate |
+| `wshclientapi.ts` still has 7 AI RPC commands + `AiSendMessageCommand` | Auto-generated file; will be regenerated after Go types removed | B → regenerate |
+| `aipanel/`, `aifilediff/`, `fakechat.tsx`, `waveaivisual.tsx` files still exist | Intentionally kept per spec; Phase D deletes them | D |
+| `waveai.tsx` stub still exports `WaveAiModel` class | File exists but not registered in blockregistry; dead code | D |
+| `schema/waveai.json`, `schema/aipresets.json` still exist | Phase C removes them | C |
+| `schema/settings.json` has `waveai:showcloudmodes` and `waveai:defaultmode` | Phase B/C handles schema cleanup | B/C |
+| `docs/docs/waveai.mdx`, `waveai-modes.mdx`, `ai-presets.mdx` still exist | Phase C removes them | C |
+| `docs/docs/config.mdx` still has `waveai:showcloudmodes`/`waveai:defaultmode` | Phase C audits this | C |
+| `pkg/wconfig/defaultconfig/waveai.json`, `presets/ai.json` still exist | Phase B.8 handles this | B |
+| All Go backend types and handlers untouched | Phase B scope | B |
+| `filebackup.go` uses `waveai-backups` directory name | Low priority; just a directory name, harmless | D |
+
+### Unintended Consequences to Track
+
+1. **Builder mode will break at Phase D** — The builder imports `AIPanel`, `WaveAIModel`, and `formatFileSize` from `aipanel/`. When that directory is deleted in Phase D, the builder crashes unless A.15 fixes are applied first.
+2. **`formatFileSize` trapped in AI module** — `builder-filestab.tsx` imports it from `@/app/aipanel/ai-utils`. Must be relocated before Phase D.
+3. **Type definitions out of sync** — Auto-generated TS files have AI types that no longer match runtime reality. No runtime errors but creates confusion. Will resolve when Go types removed and generator re-run.
+
+## Phase B Review — 2026-05-15
+
+### Items Already Removed (from telemetry phase)
+
+- `WaveAIEnableTelemetryCommand` — already gone from interface, server, and client
+- `WaveAIFgMinutes` / `WaveAIActiveMinutes` telemetry props — already gone from `wshrpctypes.go`
+- `GetCustomAIModeConfigs()` — spec mentioned it but it doesn't exist in `settingsconfig.go` (it's `ComputeResolvedAIModeConfigs()` in `aiusechat/`)
+
+### Additional Items Found (Not in Original Spec)
+
+| ID | File | What to remove |
+|----|------|---------------|
+| **B.1 (extra)** | `pkg/wshrpc/wshrpctypes.go` | `AiSendMessageCommand` interface method + `AiMessageData` type (no server handler exists) |
+| **B.3 (extra)** | `pkg/wshrpc/wshclient/wshclient.go` | `AiSendMessageCommand()` helper function |
+| **B.6 (extra)** | `pkg/wconfig/settingsconfig.go` | `CountCustomAIModes()` function (dead code) |
+| **B.9** | `pkg/wps/wpstypes.go` | `Event_WaveAIRateLimit`, `Event_AIModeConfig` constants + from `AllEvents` list |
+| **B.10** | `pkg/wconfig/metaconsts.go` | `ConfigKey_WaveAiShowCloudModes`, `ConfigKey_WaveAiDefaultMode` (auto-generated; will not reappear after B.6) |
+| **B.11** | `pkg/tsgen/tsgen.go` | `uctypes.RateLimitInfo{}` and `wconfig.AIModeConfigUpdate{}` from `Types` slice, `aiusechat/uctypes` import |
+| **B.12** | `cmd/generateschema/main-generateschema.go` | `WaveSchemaWaveAIFileName` const + waveai schema generation block |
+| **B.13** | `cmd/generatego/main-generatego.go` | `"github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"` from boilerplate import list |
+
+### Updated Implementation Order
+
+1. **B.1** — `wshrpctypes.go` (interface + types, including `AiSendMessageCommand` + `AiMessageData`)
+2. **B.2** — `wshserver.go` (server handlers)
+3. **B.3** — `wshclient.go` (client helpers, including `AiSendMessageCommand`)
+4. **B.4** — `web.go` (web endpoints)
+5. **B.5** — `main-server.go` (init call)
+6. **B.6** — `settingsconfig.go` (config types + `CountCustomAIModes`)
+7. **B.7** — `tsgenevent.go` (event type mapping)
+8. **B.11** — `tsgen.go` (Types slice + import)
+9. **B.9** — `wpstypes.go` (event constants + AllEvents list)
+10. **B.13** — `generatego/main-generatego.go` (import list)
+11. **B.12** — `generateschema/main-generateschema.go` (schema generation)
+12. **B.10** — `metaconsts.go` (AI config key constants)
+13. **B.8** — `defaultconfig/waveai.json` (delete)
+
+### Left Untouched
+
+- `cmd/testai/`, `cmd/testopenai/`, `cmd/testsummarize/` — test-only binaries; harmless dead code
+- `pkg/aiusechat/` — entire package stays intact (Phase D)
+
+### Phase B Completion — 2026-05-15
+
+All Phase B items implemented and verified. `task build:backend` completes without errors.
+
+**Additional item found during implementation:**
+- `cmd/wsh/cmd/wshcmd-ai.go` — `wsh ai` CLI command, deleted (used `AIAttachedFile`, `CommandWaveAIAddContextData`, `WaveAIAddContextCommand`)
+
+**Post-Phase B state:**
+- `pkg/aiusechat/` is now a dead package (no external callers)
+- Auto-generated TS types (`gotypes.d.ts`, `waveevent.d.ts`, `wshclientapi.ts`) still have stale AI definitions — will be regenerated when the generator is re-run
+- `schema/waveai.json` still exists on disk (was pre-generated, not regenerated by `task build:schema` since the generator code is removed) — Phase C will delete it
+
+## Phase C Review — 2026-05-16
+
+### Completed
+
+- **C.1**: Deleted `docs/docs/waveai.mdx`, `waveai-modes.mdx`, `ai-presets.mdx`
+- **C.1**: Cleaned `docs/docs/config.mdx` — removed 13 AI config rows (`ai:*`, `waveai:*`, `app:hideaibutton`), cleaned default config JSON, updated env var examples
+- **C.1**: Cleaned `docs/docs/gettingstarted.mdx` — removed AI mentions from intro, key features, quick start, and next steps
+- **C.1**: Cleaned `docs/docs/index.mdx` — removed Wave AI card, removed AI from intro text
+- **C.1**: Cleaned `docs/docs/wsh-reference.mdx` — removed `waveai` from view type filter, removed `presets/ai.json` example
+- **C.1**: Truncated `docs/docs/releasenotes.mdx` — kept v0.14.x only (v0.13.1 and earlier removed), stripped all AI mentions from v0.14.x entries
+- **C.2**: Deleted `schema/waveai.json`, `schema/aipresets.json`
+- **Phase A carryover**: Fixed misleading AI text in `frontend/builder/tabs/builder-previewtab.tsx` EmptyStateView
+
+### Audit Results
+
+| File | AI references found | Action |
+|------|-------------------|--------|
+| `docs/docs/secrets.mdx` | None (already clean) | No change needed |
+| `docs/docs/telemetry.mdx` | File doesn't exist (removed in telemetry phase) | N/A |
+| `docs/docs/connections.mdx` | None | No change needed |
+## Risk Assessment
+
+| Risk | Mitigation |
+|------|------------|
+| AI code is imported by non-AI files | Phase A handles frontend imports first; Phase B handles Go imports. Each phase is independently buildable. Builder imports discovered in A.15 review — must fix before Phase D. |
+| Builder has AI dependencies | A.15 documents builder AI imports; must fix before Phase D deletes `aipanel/`. Move `formatFileSize` to shared utility. |
+| Config migration for existing users | `waveai.json` and `aipresets.json` are simply ignored if not loaded. Existing files on disk are harmless. |
+| Upstream merge conflicts | Keeping `pkg/aiusechat/` intact (Phase D deferred) minimizes conflicts. Only wiring code is removed. |
+| Secret store still needed | `ssh:passwordsecretname` justifies keeping it. Documented in [[decisions.md#2026-05-12-secret-store--keep]]. |
diff --git a/.pi/specs/widget-follow-focus.md b/.pi/specs/widget-follow-focus.md
new file mode 100644
index 0000000000..a67dc51494
--- /dev/null
+++ b/.pi/specs/widget-follow-focus.md
@@ -0,0 +1,169 @@
+# Spec: System Widgets Follow Terminal Focus
+
+**Date:** 2026-05-20
+**Status:** Draft
+
+## Problem
+
+When opening a system widget (Process Viewer, File Browser, etc.), it always defaults to showing information for the local machine, regardless of which terminal has focus. For remote-first workflows, this means users must manually switch the widget's connection after opening it.
+
+**Example:** You're working in a remote SSH terminal, click the Process Viewer widget, and it shows local processes. You then have to change the connection dropdown to see the remote processes you actually care about.
+
+## Solution
+
+When creating a system widget block, inherit the `connection` meta from the currently focused terminal block.
+
+## Scope
+
+### Widgets that should follow focus
+
+| Widget | View Type | Reason |
+|--------|-----------|--------|
+| Process Viewer | `processviewer` | Shows processes on the focused host |
+| File Browser / Preview | `preview` | Shows files at the focused terminal's cwd |
+
+### Widgets that should NOT follow focus (always local)
+
+| Widget | View Type | Reason |
+|--------|-----------|--------|
+| Settings | `waveconfig` | App configuration is local |
+| Secrets | `waveconfig` + `file: "secrets"` | Secret store is local |
+| Help | `help` | Static content |
+| Tips | `tips` | Static content |
+| Tsunami apps | `tsunami` | App-specific, may have own connection logic |
+
+## Current Behavior
+
+### Widget creation paths
+
+1. **Widgets bar** (`widgets.tsx`) — `handleWidgetSelect()` reads `widget.blockdef` from config, calls `env.createBlock(blockDef)`
+2. **Terminal context menu** (`term-model.ts`) — e.g., File Browser already reads `blockData?.meta?.connection` and passes it
+3. **Keyboard shortcuts** — various, depending on widget
+4. **Programmatic** — stickers, other UI elements
+
+### Connection resolution today
+
+In `ProcessViewerViewModel.constructor()`:
+```typescript
+this.connection = jotai.atom((get) => {
+ const connValue = get(this.env.getBlockMetaKeyAtom(blockId, "connection"));
+ if (isBlank(connValue)) {
+ return "local"; // <-- always defaults to local
+ }
+ return connValue;
+});
+```
+
+### What already works
+
+The File Browser in the terminal context menu already inherits connection:
+```typescript
+// term-model.ts, line ~900
+const connection = blockData?.meta?.connection;
+const meta: Record = { view: "preview", file: cwd };
+if (connection) {
+ meta.connection = connection;
+}
+```
+
+## Proposed Changes
+
+### 1. Helper: get focused block's connection
+
+Add to `frontend/app/store/global.ts`:
+
+```typescript
+/**
+ * Returns the connection name of the currently focused terminal block,
+ * or null if no terminal is focused or it's a local session.
+ */
+function getFocusedTerminalConnection(): string | null {
+ const focusedBlockId = getFocusedBlockId();
+ if (!focusedBlockId) return null;
+ const blockData = globalStore.get(getBlockAtom(focusedBlockId));
+ // Only inherit from terminal blocks
+ if (blockData?.meta?.view !== "term" && blockData?.meta?.view !== "splitterm") {
+ return null;
+ }
+ const conn = blockData?.meta?.connection;
+ return conn || null; // null means local session
+}
+```
+
+### 2. Widget creation: inject connection
+
+Create a helper that wraps `createBlock` for system widgets:
+
+```typescript
+/**
+ * Creates a block for a system widget, inheriting connection from focused terminal.
+ * If @inheritConnection is false, creates block without connection meta.
+ */
+async function createWidgetBlock(
+ blockDef: BlockDef,
+ magnified?: boolean,
+ ephemeral?: boolean,
+ inheritConnection: boolean = true
+): Promise {
+ if (inheritConnection) {
+ const focusedConn = getFocusedTerminalConnection();
+ if (focusedConn) {
+ blockDef.meta = { ...blockDef.meta, connection: focusedConn };
+ }
+ // if focusedConn is null, don't set connection (defaults to "local" in widget)
+ }
+ return createBlock(blockDef, magnified, ephemeral);
+}
+```
+
+### 3. Update creation sites
+
+| File | Widget | Change |
+|------|--------|--------|
+| `widgets.tsx` | Process Viewer (from widgets bar) | Use `createWidgetBlock` instead of `env.createBlock` |
+| `term-model.ts` | File Browser | Already works, no change needed |
+| Any future widget | N/A | Use `createWidgetBlock` with `inheritConnection: true/false` |
+
+### 4. Widget config: declare connection inheritance
+
+Add an optional field to `WidgetConfigType` in the widgets config schema:
+
+```jsonc
+{
+ "processviewer": {
+ "label": "Processes",
+ "icon": "microchip",
+ "blockdef": { "meta": { "view": "processviewer" } },
+ "inheritconnection": true // <-- new field, default: false
+ }
+}
+```
+
+This way each widget declares whether it should follow focus, rather than hardcoding a list in the code.
+
+## Files to Modify
+
+| File | Change |
+|------|--------|
+| `frontend/app/store/global.ts` | Add `getFocusedTerminalConnection()`, `createWidgetBlock()` |
+| `frontend/app/workspace/widgets.tsx` | Use `createWidgetBlock` in `handleWidgetSelect()` |
+| `frontend/app/waveenv/waveenv.ts` | Add `createWidgetBlock` to WaveEnv interface |
+| `frontend/app/waveenv/waveenvimpl.ts` | Export `createWidgetBlock` |
+| `docs/docs/widgets.json` (or schema) | Add `inheritconnection` field to widget config |
+
+## Test Cases
+
+| Scenario | Expected |
+|----------|----------|
+| Focus on remote SSH terminal, click Process Viewer | Widget shows remote processes |
+| Focus on local terminal, click Process Viewer | Widget shows local processes |
+| Focus on Settings widget, click Process Viewer | Widget shows local processes (Settings is not a terminal) |
+| Focus on remote terminal, click Settings | Settings opens (no connection meta, stays local) |
+| Switch focus between two remote terminals, open Process Viewer each time | Widget follows whichever terminal is focused |
+| File Browser from terminal context menu | Already works, verify no regression |
+
+## Out of Scope
+
+- Widget remembers last-used connection per-host (could be a future enhancement)
+- Multiple widgets showing different hosts simultaneously (already works, each widget is independent)
+- Non-terminal views inheriting connection (e.g., splitting a process viewer)
diff --git a/.pi/specs/wsh-agent-api.md b/.pi/specs/wsh-agent-api.md
new file mode 100644
index 0000000000..69c5faf8f9
--- /dev/null
+++ b/.pi/specs/wsh-agent-api.md
@@ -0,0 +1,367 @@
+# Spec: wsh Agent API — Terminal Orchestration via wsh
+
+**Date:** 2026-05-20
+**Status:** Draft
+
+## Problem
+
+AI coding agents (pi, Claude Code, Cursor, etc.) running inside Wave Terminal terminals have no awareness of the Wave Terminal application itself. They can execute shell commands and edit files, but cannot:
+- See what other blocks/connections exist
+- Open new terminals on different connections
+- Read terminal output from other blocks
+- Reorganize layout or manage connections
+
+This limits agents to operating within a single terminal context. With Wave Terminal as an orchestration surface, agents can coordinate across connections, manage the workspace, and spawn other agents.
+
+## Scope Guardrail
+
+**Include:** Anything a human could do via the UI or keyboard.
+**Exclude:** Streaming event subscriptions, programmatic UI rendering, anything beyond human capability.
+
+## Design Principles
+
+1. **wsh-first** — All agent capabilities exposed via `wsh` commands. Works locally and remotely (routed through connserver).
+2. **JSON output** — `--json` flag on all read commands for machine parsing.
+3. **Idempotent** — Commands are safe to retry (important for agents dealing with flaky networks).
+4. **No new auth surface** — Agent runs as the same user; inherits existing permissions. No API keys or tokens.
+
+## Proposed Commands
+
+### Read Commands
+
+#### `wsh block list`
+
+List all blocks in the current workspace.
+
+```bash
+wsh block list # human-readable table
+wsh block list --json # JSON array
+wsh block list --tab # filter by tab
+wsh block list --connection # filter by connection
+wsh block list --view term # filter by view type
+```
+
+**JSON output:**
+```json
+[
+ {
+ "id": "block-uuid",
+ "view": "term",
+ "tab": "tab-uuid",
+ "connection": "prod-server",
+ "connStatus": "connected",
+ "cwd": "/home/user/project",
+ "title": "prod-server: ~/project",
+ "magnified": false,
+ "focused": true
+ }
+]
+```
+
+#### `wsh block get`
+
+Get details for a specific block, including terminal scrollback.
+
+```bash
+wsh block get --json
+wsh block get --lines 50 # last N lines of scrollback
+wsh block get --no-scrollback # block metadata only
+```
+
+**JSON output:**
+```json
+{
+ "id": "block-uuid",
+ "view": "term",
+ "connection": "prod-server",
+ "connStatus": "connected",
+ "cwd": "/home/user/project",
+ "scrollback": [
+ {"line": 1, "text": "$ ls -la"},
+ {"line": 2, "text": "total 42"},
+ {"line": 3, "text": "drwxr-xr-x 5 user user 4096 May 20 10:00 ."}
+ ],
+ "scrollbackTotal": 1250,
+ "scrollbackFrom": 1201,
+ "scrollbackTo": 1250
+}
+```
+
+#### `wsh connection list`
+
+List all connections and their status.
+
+```bash
+wsh connection list --json
+```
+
+**JSON output:**
+```json
+[
+ {
+ "name": "prod-server",
+ "type": "ssh",
+ "host": "prod.example.com",
+ "user": "deploy",
+ "status": "connected",
+ "durableSessions": 2
+ },
+ {
+ "name": "local",
+ "type": "local",
+ "status": "connected",
+ "durableSessions": 0
+ }
+]
+```
+
+#### `wsh tab list`
+
+List all tabs and their layout.
+
+```bash
+wsh tab list --json
+```
+
+**JSON output:**
+```json
+[
+ {
+ "id": "tab-uuid",
+ "title": "Production",
+ "blockIds": ["block-uuid-1", "block-uuid-2"],
+ "focusedBlockId": "block-uuid-1",
+ "layout": "split-horizontal"
+ }
+]
+```
+
+#### `wsh config get`
+
+Read configuration values.
+
+```bash
+wsh config get "term:fontfamily" --json
+wsh config get --all --json # all settings
+wsh config get --connection --json # connection-specific settings
+```
+
+### Write Commands
+
+#### `wsh block create`
+
+Create a new block (terminal, file browser, etc.).
+
+```bash
+# Create terminal on a connection
+wsh block create --view term --connection prod-server
+
+# Create terminal with a command
+wsh block create --view term --connection prod-server --cmd "tail -f /var/log/syslog"
+
+# Create file browser at a path
+wsh block create --view preview --connection prod-server --file /var/log
+
+# Split relative to focused block
+wsh block create --view term --split horizontal --connection staging
+
+# In a specific tab
+wsh block create --view term --tab --connection prod-server
+
+# Magnified (fullscreen within tab)
+wsh block create --view term --magnified --connection prod-server
+```
+
+**Output:** `block-uuid` (the ID of the created block)
+
+#### `wsh block close`
+
+Close a block.
+
+```bash
+wsh block close
+wsh block close --force # close even if has running process
+```
+
+#### `wsh block send-keys`
+
+Send keystrokes to a terminal block (as if typed by a human).
+
+```bash
+wsh block send-keys "ls -la"
+wsh block send-keys "ls -la" --enter # append Enter key
+wsh block send-keys --literal "\u001b[A" # Escape sequence (up arrow)
+```
+
+#### `wsh block focus`
+
+Focus a block (give it keyboard focus).
+
+```bash
+wsh block focus
+```
+
+#### `wsh block magnify`
+
+Magnify (fullscreen) or unmagnify a block within its tab.
+
+```bash
+wsh block magnify
+wsh block magnify --toggle
+wsh block unmagnify
+```
+
+#### `wsh connection connect`
+
+Connect to a connection (if disconnected).
+
+```bash
+wsh connection connect
+```
+
+#### `wsh connection disconnect`
+
+Disconnect from a connection.
+
+```bash
+wsh connection disconnect
+```
+
+#### `wsh config set`
+
+Set a configuration value.
+
+```bash
+wsh config set "term:fontfamily" "JetBrains Mono"
+wsh config set "term:fontsize" 14
+```
+
+### Agent Orchestration
+
+#### `wsh agent spawn`
+
+Spawn an agent command in a new terminal block.
+
+```bash
+# Spawn Claude Code on a remote connection
+wsh agent spawn --connection prod-server --cmd "claude"
+
+# Spawn with a prompt
+wsh agent spawn --connection prod-server --cmd "claude" --prompt "Fix the build error"
+
+# Spawn in a split next to focused block
+wsh agent spawn --connection prod-server --cmd "claude" --split horizontal
+```
+
+This is syntactic sugar for `wsh block create --view term --connection --cmd ""`.
+
+## Implementation Approach
+
+### Phase 1: JSON output on existing commands
+
+Many `wsh` commands already exist. Add `--json` flag:
+
+| Command | Current state | Change |
+|---------|---------------|--------|
+| `wsh block list` | Exists, human-readable | Add `--json`, add filters (`--tab`, `--connection`, `--view`) |
+| `wsh block get` | May not exist | New command with `--lines`, `--no-scrollback` |
+| `wsh connection list` | Exists | Add `--json` |
+| `wsh tab list` | May not exist | New command with `--json` |
+| `wsh config get` | May not exist | New command |
+
+### Phase 2: Write commands
+
+| Command | Current state | Change |
+|---------|---------------|--------|
+| `wsh block create` | Exists via RPC | Expose via `wsh` CLI with more options |
+| `wsh block close` | Exists | Already works |
+| `wsh block send-keys` | New | Send keystrokes to terminal block |
+| `wsh block focus` | New | Focus a block |
+| `wsh config set` | New | Set configuration values |
+
+### Phase 3: Agent helpers
+
+| Command | Current state | Change |
+|---------|---------------|--------|
+| `wsh agent spawn` | New | Syntactic sugar for block create + cmd |
+| `wsh agent help` | New | Discovery command — lists all agent-capable commands |
+
+## Files to Modify
+
+| File | Change |
+|------|--------|
+| `cmd/wsh/cmd/` | New CLI commands (`wshcmd-block-list.go`, `wshcmd-block-get.go`, etc.) |
+| `pkg/wshrpc/wshserver/` | Server-side handlers for new RPC commands |
+| `pkg/wshrpc/wshrpctypes.go` | New RPC method signatures |
+| `pkg/wcore/block.go` | Block query helpers |
+| `frontend/app/store/global.ts` | `createBlock` already exists, may need exposure via wsh |
+
+## Security Considerations
+
+| Concern | Mitigation |
+|---------|-----------|
+| Agent reads sensitive terminal output (API keys, passwords) | Same as human — if agent has shell access, it can read anything anyway. No additional risk. |
+| Agent modifies config | Same as human — `wsh config set` is available to the user. No additional risk. |
+| Agent on remote server controls local Wave Terminal | `wsh` commands already route through connserver RPC. Agent inherits user's permissions. |
+| Agent spawns processes on other connections | Agent already has SSH access if it's running in a terminal on that connection. No additional risk. |
+| **Secrets access** | `wsh secret get` already exists. Agent can use it. Document this as a capability, not hide it. |
+
+**Key insight:** The agent runs as the same user with the same permissions. There is no privilege escalation. The "security model" is identical to a human using the Wave Terminal UI.
+
+## Out of Scope (for now)
+
+- **Streaming/event subscriptions** — "notify me when block output changes" (needs WebSocket/domain socket)
+- **Programmatic UI rendering** — Agent creates custom UI elements (beyond human capability)
+- **Cross-workspace operations** — Agent controls multiple Wave Terminal windows (single workspace scope)
+- **Agent authentication/API keys** — Agent inherits user's permissions, no separate auth
+- **Rate limiting** — Agent runs locally, no DoS concern
+- **Audit logging** — Can add later if needed
+
+## Test Cases
+
+| Scenario | Expected |
+|----------|----------|
+| `wsh block list --json` from local terminal | Returns all blocks including remote ones |
+| `wsh block list --json` from remote terminal | Same result (routed through connserver) |
+| `wsh block create --view term --connection prod` | New terminal block opens, connected to prod |
+| `wsh block send-keys "echo hello" --enter` | "echo hello\n" sent to terminal, command executes |
+| `wsh block get --lines 10` | Returns last 10 lines of scrollback |
+| `wsh agent spawn --connection prod --cmd "claude"` | New block with claude running on prod |
+| `wsh config get --all --json` | Returns all settings as JSON |
+| Agent parses JSON, creates block, sends keys | Full orchestration flow works end-to-end |
+
+## Discovery
+
+How does an agent know this exists?
+
+1. **Environment variable** — `WAVE_TERMINAL=1` set in all terminal sessions
+2. **wsh agent help** — Lists all agent-capable commands with examples
+3. **Documentation** — `.pi/specs/wsh-agent-api.md` + public docs in `docs/docs/`
+
+Example `wsh agent help` output:
+```
+Wave Terminal Agent API
+=======================
+The following wsh commands support --json output for agent integration:
+
+Read workspace state:
+ wsh block list --json List all blocks
+ wsh block get --json Get block details + scrollback
+ wsh connection list --json List connections
+ wsh tab list --json List tabs
+
+Modify workspace:
+ wsh block create --view term --connection
+ wsh block close
+ wsh block send-keys "command" --enter
+ wsh block focus
+
+Configuration:
+ wsh config get --json
+ wsh config set
+
+Spawn agents:
+ wsh agent spawn --connection --cmd "claude"
+
+For full documentation: wsh agent help --full
+```
diff --git a/.pi/todos.md b/.pi/todos.md
new file mode 100644
index 0000000000..6512d6c766
--- /dev/null
+++ b/.pi/todos.md
@@ -0,0 +1,190 @@
+# Active Tasks
+
+## Phase 1: Dev Environment ✅
+
+- [x] Install Task (build runner)
+- [x] Install Go 1.25+
+- [x] Run `task init` to install dependencies
+- [x] Run `task dev` — confirm app launches
+- [x] Run `task start` — confirm standalone build works
+- [x] Set up macOS CI workflow
+
+## Phase 2: Feature Planning
+
+- [ ] Finalize list of features to ADD
+- [x] Finalize list of features to REMOVE or DIMINISH
+- [ ] Prioritize implementation order
+
+### Features to Remove / Disable
+
+> "Remove" means **disable and hide from the UI** — don't delete code initially. Makes it easy to re-enable if needed and keeps the fork closer to upstream.
+
+- **All Wave AI features** — AI widgets, AI chat, AI presets, context-aware assistant, AI-related UI elements and settings
+
+## Dependency Maintenance
+
+- [x] **Upstream dependency bumps** (issue #12) — completed 2026-05-29
+ - [x] Merge 3 upstream commits (`a5ac0962`, `81f7b1a5`, `c0687de2`)
+ - `google.golang.org/api` 0.275.0 → 0.277.0
+ - `qs` 6.14.2 → 6.15.2, `express` 4.22.1 → 4.22.2
+ - Resolved merge conflict: `docs/docs/waveai-modes.mdx` (keep deletion)
+ - [x] Bump `golang.org/x/crypto` 0.50.0 → 0.52.0 (CVE-2026-39827 SSH memory leak, CVE-2026-46598 ed25519 panic)
+ - [x] `go mod tidy` cleaned up transitive deps (`x/net`, `x/sys`, `x/term`, `x/text`)
+ - [x] `task build:server` passes
+
+## Phase 3: Implementation
+
+### High Priority — Bugfix
+
+- [x] **Durable session auto-reconnect unreliable** (draft: [[.pi/draft-issue-autoconnect-bugs.md]]) — P0 bugs fixed 2026-05-23
+ - [x] Bug #1 (P0): Route-level cooldown consumed before connection check — moved `lastAutoReconnectAttempt.Set` into `attemptAutoReconnect` after `IsConnected` passes
+ - [x] Bug #2 (P0): connStates reconciliation race — replaced `processed bool` with generation counters (`actualGen` / `procGen`); `reconcileConn` now sends follow-up signal if `actualGen != procGen` at finish
+ - [x] Bug #3 (P0): singleflight caches transient reconnect failures — split `reconnectGroup` into `reconnectConnGroup` and `reconnectRouteGroup`; route-level `attemptAutoReconnect` now calls `ReconnectJobRoute` instead of sharing the connection-level cache
+ - [x] Decision 2026-05-23: Server reboot / `wsh` death → manual reconnect (do NOT auto-restart fresh shell). Auto-restart would change durable-session semantics from "resume existing shell" to "keep shell open at all costs," creating context-loss confusion and `wsh` re-install loops.
+ - GitHub issue (problem): https://github.com/whoisjeremylam/waveterm-remote/issues/7
+ - GitHub issue (implementation): https://github.com/whoisjeremylam/waveterm-remote/issues/8
+ - Branch: `fix/auto-reconnect-detection-gaps`
+ - [x] Phase 1 (Gap C): Auto-disconnect on stall — `ConnMonitor` detects stall but doesn't set `Status=Disconnected`
+ - Commit `b4c4dbea`: Add configurable `ConnStallDisconnectThreshold` to `ConnKeywords`
+ - Trigger `conn.Close()` when stall exceeds threshold (removed `!isUrgent()` guard per spec review)
+ - Commit `a157b234`: Add `AttemptReconnect` helper + reconnect scheduler in `onConnectionDown` (fixes GAP-1)
+ - This makes sleep/Wi-Fi/VPN interruptions self-healing via existing `onConnectionUp`
+ - [x] Phase 2 (Gap A): Implement `NotifySystemResumeCommand` — commit `a157b234` + Phase 2 additions
+ - `emain.ts` already hooks `powerMonitor.on('resume')` → calls `NotifySystemResumeCommand`
+ - `wshserver.go`: `NotifySystemResumeCommand` now calls `jobcontroller.HandleSystemResume(ctx)` instead of no-op
+ - `jobcontroller.go`: `HandleSystemResume` iterates all connections, finds those with durable jobs, forces disconnect on stalled zombies, spawns `AttemptReconnect()` goroutines for immediate reconnect
+ - Fast-path: bypasses 30s scheduler tick, attempts reconnect within ~1-2s of system wake
+ - [x] Phase 3 (Gap B): Aggressive scheduler enhancement — implemented as Option B
+ - `isNetworkUnreachableError()` detects dial tcp i/o timeout, no route, DNS failure
+ - On network-unreachable error: switch to 5s interval for 2 minutes
+ - When user switches back to good Wi-Fi, next 5s tick reconnects automatically
+ - After 2 minutes aggressive: returns to 30s interval for remaining scheduler window
+ - If still no network after total 5 min: scheduler gives up (manual reconnect required)
+ - No native modules, zero build risk, cross-platform automatically
+ - Edge cases (P2): respect manual disconnect, reconnect UI indicator
+
+- [x] **Tmux mouse integration lost on durable session reconnect** — FIXED 2026-05-19
+ - Bug: tmux mouse mode (click to switch windows, wheel scrollback, click-drag select) works in new sessions but NOT in reconnected durable sessions after full WaveTerm restart
+ - Repro: close WaveTerm completely → restart → durable sessions reconnect → tmux mouse integration disabled
+ - Expected: durable sessions should re-enable tmux mouse integration on reconnect, same as new sessions
+ - Root cause: xterm.js internal DEC private mode state lost on reconnect; only cached terminal data was replayed, not mode negotiation sequences
+ - Fix commits: `af669bcb` (original DEC mode restore), `01f5073d` (multi-param CSI tracking, clear-all reset, stale cache purge, replay whitelist)
+ - Tests: `f839f8ab` (14 Vitest unit tests with mocked xterm.js)
+ - GitHub comment posted to issue #2 with full analysis
+ - README fork notes updated with bug fix reference
+- [x] **Crash on tab close after SSH session exit** — Fixed 2026-05-14
+ - Root cause found: double `DestroyBlockController` race in `CloseTab` (explicit goroutine + `DeleteTab` → `BlockCloseEvent` handler)
+ - Fix: removed redundant goroutine in `CloseTab`; added `sync.Once` to `ShellProc.Close()` as defense-in-depth
+ - Added trace logging to `CloseTab`, `DestroyBlockController`, `ShellController.Stop`, `DurableShellController.Stop`, `handleBlockCloseEvent`
+ - Tests: fixed 2 panicking tests (channel double-close bug in test code), all 14 tests pass under `-race`
+ - Spec: [[.pi/specs/bug-tabclose-crash.md]]
+ - [x] **Post-confirm cleanup:** Removed trace logging 2026-05-14
+
+### Features
+
+- [x] Remove telemetry (spec: [[.pi/specs/remove-telemetry.md]])
+ - [x] Phase A: Remove call sites
+ - [x] Phase B: Remove frontend telemetry
+ - [x] Phase C: Delete unused packages
+ - [x] Phase D: Clean up docs
+- [x] Remove Wave AI features (spec: [[.pi/specs/remove-waveai.md]])
+ - [x] Phase A: Disable UI (frontend) — completed 2026-05-16
+ - [x] Fix blank screen: invalid nested `` in `workspace.tsx` (removed inner PanelGroup but left VTabBar `` orphaned inside outer ``)
+ - [x] Remove sparkle/Claude icon from terminal block header (`getShellIntegrationIconButton` → no-op stub)
+ - [x] Minor: update misleading AI text in `builder-previewtab.tsx` EmptyStateView — fixed 2026-05-16
+ - [x] Phase B: Remove backend wiring (Go) — 2026-05-15
+ - [x] Phase C: Clean up docs & schemas — 2026-05-16
+ - [x] Phase D: Delete unused code — completed 2026-05-16
+ - [x] Remove builder AI dependencies (A.15: `AIPanel`, `WaveAIModel`, `formatFileSize`, `builder-focusmanager.ts`)
+ - [x] Move `formatFileSize` to shared utility (`@/util/util`) — completed in commit bd355fad
+ - [x] Delete `pkg/aiusechat/` (entire directory, ~12K lines, dead package)
+ - [x] Delete `frontend/app/aipanel/` (17 files, orphaned after builder deps removed)
+ - [x] Delete `frontend/app/view/waveai/`, `frontend/app/view/aifilediff/`, `frontend/app/view/waveconfig/waveaivisual.tsx`
+ - [x] Delete `frontend/app/onboarding/fakechat.tsx`, preview files
+ - [x] Clean Go structs: `SettingsType`, `MetaTSType`, `ObjRTInfo`, `FullConfigType`, `AIModeConfigType`, etc.
+ - [x] Delete default configs: `waveai.json`, `presets/ai.json`, clean `settings.json`
+ - [x] Regenerate auto-generated TS types (`gotypes.d.ts`, `waveevent.d.ts`, `wshclientapi.ts`) and Go metaconsts
+ - [x] Document Claude Code shell integration analysis for future pi agent reuse (`.pi/decisions.md`)
+- [x] SSH port forwarding (`LocalForward` / `RemoteForward`) (spec: [[.pi/specs/portforwarding.md]]) — completed 2026-06-04
+ - [x] Modify `pkg/wconfig/settingsconfig.go`
+ - [x] Modify `pkg/remote/sshclient.go` (parse + return merged keywords)
+ - [x] Modify `pkg/remote/conncontroller/conncontroller.go` (runtime forwarding)
+ - [x] Update call sites for new `ConnectToClient` signature
+ - [x] Add tests
+ - [x] Update documentation (`docs/docs/connections.mdx`)
+- [x] **Port forwarding UI status indicators** (spec: [[.pi/specs/portforwarding-ui.md]]) — completed 2026-06-07
+ - [x] Add `ForwardingRules []string` to `ConnStatus` struct (no new RPC needed)
+ - [x] Populate in `DeriveConnStatus()` from `LocalForwardListeners`/`RemoteForwardListeners`
+ - [x] Create `port-forward-status.tsx` component (plug icon + badge + tooltip)
+ - [x] Wire into `blockframe-header.tsx` between DurableSessionFlyover and badge
+ - [x] Go build passes, Go tests pass, TypeScript compiles cleanly
+- [ ] **Remote file paste** — image paste + drag-drop for remote sessions
+ - Primary use case: pasting screenshots and dragging files when using pi or Claude Code's TUI over SSH
+ - Currently pastes local file paths that don't exist on the remote server
+ - Need: upload file to remote (SSH exec with stdin, SFTP, or SCP), then paste remote path
+ - Sub-tasks:
+ - [ ] Detect when terminal block is on a remote SSH connection
+ - [ ] Add RPC command to upload file bytes to remote server via existing SSH connection
+ - [ ] Wire up image paste (`termwrap.ts` `pasteHandler`) to use remote upload for SSH sessions
+ - [ ] Wire up drag-drop (`termwrap.ts` `dropHandler`) to use remote upload for SSH sessions
+ - [ ] Add tests
+
+- [ ] **System widgets follow terminal focus** (spec: [[.pi/specs/widget-follow-focus.md]])
+ - When opening Process Viewer, File Browser, etc., inherit connection from focused terminal
+ - [ ] Add `getFocusedTerminalConnection()` helper in `global.ts`
+ - [ ] Add `createWidgetBlock()` wrapper that injects connection meta
+ - [ ] Update widgets bar (`widgets.tsx`) to use `createWidgetBlock`
+ - [ ] Add `inheritconnection` field to widget config schema
+ - [ ] Verify non-terminal widgets (Settings, Help) are unaffected
+ - [ ] Add tests
+
+- [ ] Paste screenshots into terminal (local sessions — polish)
+ - [ ] Consider implementing paste-as-image in Pi directly for tighter integration (avoid SCP+filename pattern, inject binary data or use OSC52/terminal-native paste)
+
+## Backlog / Ideas
+
+### Features to Add (discuss, spec, scope later)
+
+- **MOSH support** — Research done 2026-05-20. MOSH's main benefits: seamless reconnection (roaming, sleep/wake) and client-side local echo. Not a priority because: (1) no port forwarding (open issue since 2014), (2) no OSC52 clipboard, (3) no scrollback, (4) C++ only, slow development. tsshd (trzsz-ssh) is the more relevant reference — Go-based, full SSH features + UDP roaming, but significant architectural change. Local echo is technically possible with wsh but non-trivial and low-value for typical latency.
+- **Vertical tabs** — Tab layout optimized for remote host switching
+
+
+
+### Agent Orchestration API
+
+- [ ] **wsh Agent API** — Agent orchestration via wsh commands (spec: [[.pi/specs/wsh-agent-api.md]])
+ - Scope guardrail: "anything a human could do via the UI or keyboard"
+ - Phase 1: `--json` output on existing read commands (`block list`, `connection list`, `tab list`)
+ - Phase 2: New read commands (`block get` with scrollback, `config get`)
+ - Phase 3: Write commands (`block create` with options, `block send-keys`, `block focus`, `config set`)
+ - Phase 4: Agent helpers (`agent spawn`, `agent help`)
+ - Discovery: `WAVE_TERMINAL=1` env var + `wsh agent help`
+ - Security: no new auth surface — agent inherits user's permissions
+
+### Forwarding Enhancements
+
+- DynamicForward (SOCKS proxy) — out of scope for v1, needs SOCKS5 handler
+- `wsh ssh -L` / `-R` CLI flags
+- UI status indicator for active port forwards
+
+### Session Persistence (Tmux + Wsh Overlap)
+
+> Jeremy's note 2026-05-23: "I frequently lose all sessions when the server automatically restarts each week (part of a backup). I have to recreate tmux sessions manually."
+
+- **Tmux session auto-restore on reconnect** — After server reboot + reconnect, automatically recreate tmux sessions (restore layout, windows, sessions). Currently lost because `wsh` / job manager dies and WaveTerm only reconnects the raw shell.
+- **Tab name sync with tmux session name** — WaveTerm tab label follows tmux session name for visibility.
+- **Bring tmux features into wsh** — Tmux provides persistence, session multiplexing, and screen visibility (for agents). Consider which tmux features overlap with WaveTerm durable sessions and where wsh could natively replicate them (session restore, window splitting, scrollback capture).
+
+### UX Improvements
+
+- **New block default connection** — Currently clicking '+' defaults to local; for remote-first workflow, should default to SSH/remote or at least not require manual switching
+- **SSH config as source of truth** — Connection management currently pushes users to JSON/settings UI instead of naturally leveraging `~/.ssh/config` as the primary management interface
+
+### File Transfer
+
+- **Drag and drop file transfer** — Drag files into the file browser to upload; drag from file browser to download
+
+### General
+
+- Remove checks to `dl.waveterm.dev` (e.g., update checks, download URLs)
+- Evaluate which other local-first widgets to remove/diminish
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000000..64bf1784d6
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,81 @@
+# AGENTS.md — waveterm-remote Fork
+
+This fork of Wave Terminal is optimized for remote development workflows. The local machine is a thin client; remote SSH environments are primary workspaces.
+
+## Git Remotes
+
+- `origin` → `https://github.com/whoisjeremylam/waveterm-remote` (this fork)
+- `upstream` → `https://github.com/wavetermdev/waveterm` (original)
+- Do not run `git push` — the user handles pushes interactively with 2FA
+
+## Dev Environment
+
+| Tool | Status |
+|------|--------|
+| NodeJS v24.14.0 | Available |
+| npm 11.9.0 | Available |
+| git 2.43.0 | Available |
+| Go 1.26.2 | Local install in `golang-1.26.2/` |
+| Task (build runner) | Local npm dep (`@go-task/cli`) |
+
+Go and Task are installed locally (not globally). The Taskfile uses `{{.GO_DIR}}` and `{{.GO}}` vars to reference the local Go binary.
+
+**When upgrading Go**: download to `golang-/`, update `GO_DIR` in Taskfile.yml vars, and run `echo "module golang" > golang-/go.mod` (prevents `go mod tidy` from scanning the Go install dir as part of the project module).
+
+Run `./node_modules/.bin/task init` then `./node_modules/.bin/task dev`.
+
+## Planning Documents
+
+All fork planning lives in `.pi/`:
+- `.pi/index.md` — entry point
+- `.pi/context.md` — fork purpose and problem statement
+- `.pi/todos.md` — active tasks and backlog
+- `.pi/decisions.md` — architecture decisions
+- `.pi/specs/` — feature specifications
+
+Current active spec: `.pi/specs/portforwarding.md`
+
+## Architecture
+
+- **Frontend**: React/TypeScript in `frontend/`
+- **Backend**: Go in `pkg/` and `cmd/`
+- **Electron main**: `emain/` (Node.js bridge between frontend and Go)
+- **Go backend runs as separate process** — Electron main process bridges to it via IPC
+
+## Priorities
+
+1. Verify `task dev` and `task start` work (build tools installed)
+2. Implement SSH port forwarding (`LocalForward`/`RemoteForward`) — spec ready
+3. Later: remove/disable AI features, MOSH support, vertical tabs, UX improvements
+
+## Conventions
+
+- Follow existing code patterns: `panichandler` on goroutines, `WithLock` for struct mutations, table-driven tests with `t.Run`, manual `if` assertions (no testify)
+- `docs/docs/` is public-facing documentation (Docusaurus) — do not mix fork planning with user docs
+- `README.md` stays close to upstream; fork differences go in `.pi/` or `README-FORK.md` if needed
+- All new SSH config keywords follow the parsing pattern in `pkg/remote/sshclient.go`
+- ConnKeywords fields use `json:"ssh:..."` tags for SSH config and `json:"conn:..."` for internal config
+
+## Key Files for SSH Work
+
+| File | Purpose |
+|------|---------|
+| `pkg/wconfig/settingsconfig.go` | `ConnKeywords` struct — add new SSH fields here |
+| `pkg/remote/sshclient.go` | Config parsing (`findSshConfigKeywords`), merging (`mergeKeywords`), `ConnectToClient` |
+| `pkg/remote/conncontroller/conncontroller.go` | Connection lifecycle — start forwarding after connect, cleanup on disconnect |
+| `pkg/genconn/ssh-impl.go` | SSH session implementation |
+| `cmd/wsh/cmd/wshcmd-ssh.go` | `wsh ssh` CLI command |
+| `docs/docs/connections.mdx` | Public docs for connections and SSH config |
+
+## Testing
+
+- No existing tests for `sshclient.go` or `conncontroller.go` — new tests would be first coverage
+- Use `t.TempDir()` for filesystem fixtures, not external fixture files
+- Use hand-written inline mocks, not gomock
+- `t.Parallel()` on independent tests only
+
+## Out of Scope (Current)
+
+- DynamicForward (needs SOCKS5 handler)
+- `wsh ssh -L`/`-R` CLI flags
+- UI status indicators for port forwarding
diff --git a/README.md b/README.md
index a9f406725c..e518d0b93e 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,8 @@
+> **Fork:** This is a fork of [Wave Terminal](https://github.com/wavetermdev/waveterm) optimized for remote development workflows.
+
# Wave Terminal
@@ -17,23 +19,29 @@
-[](https://app.fossa.com/projects/git%2Bgithub.com%2Fwavetermdev%2Fwaveterm?ref=badge_shield)
+Wave is an open-source terminal for macOS, Linux, and Windows. No accounts required.
+
+Wave supports durable SSH sessions that survive network interruptions and restarts, with automatic reconnection. Edit remote files with a built-in graphical editor and preview files inline without leaving the terminal.
+
+## Fork Notes
-Wave is an open-source, AI-integrated terminal for macOS, Linux, and Windows. It works with any AI model. Bring your own API keys for OpenAI, Claude, or Gemini, or run local models via Ollama and LM Studio. No accounts required.
+This fork is optimized for remote development workflows with a focus on macOS.
-Wave also supports durable SSH sessions that survive network interruptions and restarts, with automatic reconnection. Edit remote files with a built-in graphical editor and preview files inline without leaving the terminal.
+- **No telemetry** — All analytics, telemetry, and cloud data collection have been completely removed; no usage data is sent to external servers
+- **Local toolchain** — Go and Task are installed locally (not global), no system dependencies required
+- **macOS builds** — CI builds macOS `.dmg` via GitHub Actions (manual trigger)
+- **Bug fixes** — Fixed tmux mouse integration lost on durable SSH session reconnect (commit `01f5073d`, issue #2); fixed crash on tab close after SSH session exit (commit `0cd6489b`)
+- **Planned changes** — SSH port forwarding, remote file paste (image/drag-drop for SSH sessions), vertical tabs, SSH config as source of truth for connections

## Key Features
-- Wave AI - Context-aware terminal assistant that reads your terminal output, analyzes widgets, and performs file operations
- Durable SSH Sessions - Remote terminal sessions survive connection interruptions, network changes, and Wave restarts with automatic reconnection
-- Flexible drag & drop interface to organize terminal blocks, editors, web browsers, and AI assistants
+- Flexible drag & drop interface to organize terminal blocks, editors, web browsers, and previews
- Built-in editor for editing remote files with syntax highlighting and modern editor features
- Rich file preview system for remote files (markdown, images, video, PDFs, CSVs, directories)
- Quick full-screen toggle for any block - expand terminals, editors, and previews for better visibility, then instantly return to multi-block view
-- AI chat widget with support for multiple models (OpenAI, Claude, Azure, Perplexity, Ollama)
- Command Blocks for isolating and monitoring individual commands
- One-click remote connections with full terminal and file system access
- Secure secret storage using native system backends - store API keys and credentials locally, access them across SSH sessions
@@ -41,28 +49,10 @@ Wave also supports durable SSH sessions that survive network interruptions and r
- Powerful `wsh` command system for managing your workspace from the CLI and sharing data between terminal sessions
- Connected file management with `wsh file` - seamlessly copy and sync files between local and remote SSH hosts
-## Wave AI
-
-Wave AI is your context-aware terminal assistant with access to your workspace:
-
-- **Terminal Context**: Reads terminal output and scrollback for debugging and analysis
-- **File Operations**: Read, write, and edit files with automatic backups and user approval
-- **CLI Integration**: Use `wsh ai` to pipe output or attach files directly from the command line
-- **BYOK Support**: Bring your own API keys for OpenAI, Claude, Gemini, Azure, and other providers
-- **Local Models**: Run local models with Ollama, LM Studio, and other OpenAI-compatible providers
-- **Free Beta**: Included AI credits while we refine the experience
-- **Coming Soon**: Command execution (with approval)
-
-Learn more in our [Wave AI documentation](https://docs.waveterm.dev/waveai) and [Wave AI Modes documentation](https://docs.waveterm.dev/waveai-modes).
-
## Installation
Wave Terminal works on macOS, Linux, and Windows.
-Platform-specific installation instructions can be found [here](https://docs.waveterm.dev/gettingstarted).
-
-You can also install Wave Terminal directly from: [www.waveterm.dev/download](https://www.waveterm.dev/download).
-
### Minimum requirements
Wave Terminal runs on the following platforms:
@@ -77,20 +67,6 @@ The WSH helper runs on the following platforms:
- Windows 10 or later (x64)
- Linux Kernel 2.6.32 or later (x64), Linux Kernel 3.1 or later (arm64)
-## Roadmap
-
-Wave is constantly improving! Our roadmap will be continuously updated with our goals for each release. You can find it [here](./ROADMAP.md).
-
-Want to provide input to our future releases? Connect with us on [Discord](https://discord.gg/XfvZ334gwU) or open a [Feature Request](https://github.com/wavetermdev/waveterm/issues/new/choose)!
-
-## Links
-
-- Homepage — https://www.waveterm.dev
-- Download Page — https://www.waveterm.dev/download
-- Documentation — https://docs.waveterm.dev
-- X — https://x.com/wavetermdev
-- Discord Community — https://discord.gg/XfvZ334gwU
-
## Building from Source
See [Building Wave Terminal](BUILD.md).
@@ -104,14 +80,6 @@ Find more information in our [Contributions Guide](CONTRIBUTING.md), which inclu
- [Ways to contribute](CONTRIBUTING.md#contributing-to-wave-terminal)
- [Contribution guidelines](CONTRIBUTING.md#before-you-start)
-### Sponsoring Wave ❤️
-
-If Wave Terminal is useful to you or your company, consider sponsoring development.
-
-Sponsorship helps support the time spent building and maintaining the project.
-
-- https://github.com/sponsors/wavetermdev
-
## License
Wave Terminal is licensed under the Apache-2.0 License. For more information on our dependencies, see [here](./ACKNOWLEDGEMENTS.md).
diff --git a/Taskfile.yml b/Taskfile.yml
index bf37a83e45..76154d1b91 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -13,6 +13,10 @@ vars:
ARTIFACTS_BUCKET: waveterm-github-artifacts/staging-w2
RELEASES_BUCKET: dl.waveterm.dev/releases-w2
WINGET_PACKAGE: CommandLine.Wave
+ GO_DIR: "golang-1.26.2"
+ GO: "{{.ROOT_DIR}}/{{.GO_DIR}}/bin/go"
+ ZIG_DIR: "zig-0.14.0"
+ ZIG: "{{.ROOT_DIR}}/{{.ZIG_DIR}}/zig"
tasks:
electron:dev:
@@ -166,7 +170,7 @@ tasks:
generates:
- "dist/schema/**/*"
cmds:
- - go run cmd/generateschema/main-generateschema.go
+ - "{{.GO}} run cmd/generateschema/main-generateschema.go"
- cmd: '{{.RMRF}} "dist/schema"'
ignore_error: true
- task: copyfiles:'schema':'dist/schema'
@@ -225,7 +229,7 @@ tasks:
- task: build:server:internal
vars:
ARCHS: amd64
- GO_ENV_VARS: CC="zig cc -target x86_64-windows-gnu"
+ GO_ENV_VARS: CC="{{.ZIG}} cc -target x86_64-windows-gnu"
deps:
- go:mod:tidy
sources:
@@ -248,7 +252,7 @@ tasks:
ARCHS:
sh: echo {{if eq "arm" ARCH}}arm64{{else}}{{ARCH}}{{end}}
GO_ENV_VARS:
- sh: echo "{{if eq "amd64" ARCH}}CC=\"zig cc -target x86_64-windows-gnu\"{{else}}CC=\"zig cc -target aarch64-windows-gnu\"{{end}}"
+ sh: echo "{{if eq "amd64" ARCH}}CC=\"{{.ZIG}} cc -target x86_64-windows-gnu\"{{else}}CC=\"{{.ZIG}} cc -target aarch64-windows-gnu\"{{end}}"
build:server:linux:
desc: Build the wavesrv component for Linux platforms (only generates artifacts for the current architecture).
@@ -261,14 +265,14 @@ tasks:
ARCHS:
sh: echo {{if eq "arm" ARCH}}arm64{{else}}{{ARCH}}{{end}}
GO_ENV_VARS:
- sh: echo "{{if eq "amd64" ARCH}}CC=\"zig cc -target x86_64-linux-gnu.2.28\"{{else}}CC=\"zig cc -target aarch64-linux-gnu.2.28\"{{end}}"
+ sh: echo "{{if eq "amd64" ARCH}}CC=\"{{.ZIG}} cc -target x86_64-linux-gnu.2.28\"{{else}}CC=\"{{.ZIG}} cc -target aarch64-linux-gnu.2.28\"{{end}}"
build:server:internal:
requires:
vars:
- ARCHS
cmd:
- cmd: CGO_ENABLED=1 GOARCH={{.GOARCH}} {{.GO_ENV_VARS}} go build -tags "osusergo,sqlite_omit_load_extension" -ldflags "{{.GO_LDFLAGS}} -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wavesrv.{{if eq .GOARCH "amd64"}}x64{{else}}{{.GOARCH}}{{end}}{{exeExt}} cmd/server/main-server.go
+ cmd: CGO_ENABLED=1 GOARCH={{.GOARCH}} {{.GO_ENV_VARS}} {{.GO}} build -tags "osusergo,sqlite_omit_load_extension" -ldflags "{{.GO_LDFLAGS}} -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wavesrv.{{if eq .GOARCH "amd64"}}x64{{else}}{{.GOARCH}}{{end}}{{exeExt}} cmd/server/main-server.go
for:
var: ARCHS
split: ","
@@ -342,7 +346,7 @@ tasks:
- GOOS
- GOARCH
- VERSION
- cmd: (CGO_ENABLED=0 GOOS={{.GOOS}} GOARCH={{.GOARCH}} go build -ldflags="-s -w -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wsh-{{.VERSION}}-{{.GOOS}}.{{.NORMALIZEDARCH}}{{.EXT}} cmd/wsh/main-wsh.go)
+ cmd: (CGO_ENABLED=0 GOOS={{.GOOS}} GOARCH={{.GOARCH}} {{.GO}} build -ldflags="-s -w -X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wsh-{{.VERSION}}-{{.GOOS}}.{{.NORMALIZEDARCH}}{{.EXT}} cmd/wsh/main-wsh.go)
internal: true
build:tsunamiscaffold:
@@ -363,8 +367,8 @@ tasks:
generate:
desc: Generate Typescript bindings for the Go backend.
cmds:
- - go run cmd/generatets/main-generatets.go
- - go run cmd/generatego/main-generatego.go
+ - "{{.GO}} run cmd/generatets/main-generatets.go"
+ - "{{.GO}} run cmd/generatego/main-generatego.go"
deps:
- build:schema
sources:
@@ -469,7 +473,7 @@ tasks:
desc: Initialize the project for development.
cmds:
- npm install
- - go mod tidy
+ - "{{.GO}} mod tidy"
- cd docs && npm install
dev:cleardata:windows:
@@ -505,7 +509,7 @@ tasks:
- go.sum
sources:
- go.mod
- cmd: go mod tidy
+ cmd: "{{.GO}} mod tidy"
copyfiles:*:*:
desc: Recursively copy directory and its contents.
@@ -522,7 +526,7 @@ tasks:
tsunami:demo:todo:
desc: Run the tsunami todo demo application
- cmd: go run demo/todo/*.go
+ cmd: "{{.GO}} run demo/todo/*.go"
dir: tsunami
env:
TSUNAMI_LISTENADDR: "localhost:12026"
@@ -632,7 +636,7 @@ tasks:
platforms: [windows]
ignore_error: true
- mkdir -p bin
- - cd tsunami && go build -ldflags "-X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.TsunamiVersion={{.VERSION}}" -o ../bin/tsunami{{exeExt}} cmd/main-tsunami.go
+ - cd tsunami && {{.GO}} build -ldflags "-X main.BuildTime=$({{.DATE}} +'%Y%m%d%H%M') -X main.TsunamiVersion={{.VERSION}}" -o ../bin/tsunami{{exeExt}} cmd/main-tsunami.go
sources:
- "tsunami/**/*.go"
- "tsunami/go.mod"
@@ -651,9 +655,9 @@ tasks:
godoc:
desc: Start the Go documentation server for the root module
- cmd: $(go env GOPATH)/bin/pkgsite -http=:6060
+ cmd: $({{.GO}} env GOPATH)/bin/pkgsite -http=:6060
tsunami:godoc:
desc: Start the Go documentation server for the tsunami module
- cmd: $(go env GOPATH)/bin/pkgsite -http=:6060
+ cmd: $({{.GO}} env GOPATH)/bin/pkgsite -http=:6060
dir: tsunami
diff --git a/cmd/generatego/main-generatego.go b/cmd/generatego/main-generatego.go
index ab7e338439..99e268ad85 100644
--- a/cmd/generatego/main-generatego.go
+++ b/cmd/generatego/main-generatego.go
@@ -24,9 +24,7 @@ func GenerateWshClient() error {
fmt.Fprintf(os.Stderr, "generating wshclient file to %s\n", WshClientFileName)
var buf strings.Builder
gogen.GenerateBoilerplate(&buf, "wshclient", []string{
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes",
"github.com/wavetermdev/waveterm/pkg/baseds",
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata",
"github.com/wavetermdev/waveterm/pkg/vdom",
"github.com/wavetermdev/waveterm/pkg/waveobj",
"github.com/wavetermdev/waveterm/pkg/wconfig",
diff --git a/cmd/generateschema/main-generateschema.go b/cmd/generateschema/main-generateschema.go
index dd24a4df0d..c9e77f4d79 100644
--- a/cmd/generateschema/main-generateschema.go
+++ b/cmd/generateschema/main-generateschema.go
@@ -18,10 +18,8 @@ import (
const WaveSchemaSettingsFileName = "schema/settings.json"
const WaveSchemaConnectionsFileName = "schema/connections.json"
-const WaveSchemaAiPresetsFileName = "schema/aipresets.json"
const WaveSchemaWidgetsFileName = "schema/widgets.json"
const WaveSchemaBackgroundsFileName = "schema/backgrounds.json"
-const WaveSchemaWaveAIFileName = "schema/waveai.json"
// ViewNameType is a string type whose JSON Schema offers enum suggestions for the most
// common widget view names while still accepting any arbitrary string value.
@@ -193,12 +191,6 @@ func main() {
log.Fatalf("connections schema error: %v", err)
}
- aiPresetsTemplate := make(map[string]wconfig.AiSettingsType)
- err = generateSchema(&aiPresetsTemplate, WaveSchemaAiPresetsFileName, false)
- if err != nil {
- log.Fatalf("ai presets schema error: %v", err)
- }
-
err = generateWidgetsSchema(WaveSchemaWidgetsFileName)
if err != nil {
log.Fatalf("widgets schema error: %v", err)
@@ -210,9 +202,5 @@ func main() {
log.Fatalf("backgrounds schema error: %v", err)
}
- waveAITemplate := make(map[string]wconfig.AIModeConfigType)
- err = generateSchema(&waveAITemplate, WaveSchemaWaveAIFileName, false)
- if err != nil {
- log.Fatalf("waveai schema error: %v", err)
- }
+
}
diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go
index b204643ee8..5e8593c424 100644
--- a/cmd/server/main-server.go
+++ b/cmd/server/main-server.go
@@ -14,7 +14,6 @@ import (
"time"
"github.com/joho/godotenv"
- "github.com/wavetermdev/waveterm/pkg/aiusechat"
"github.com/wavetermdev/waveterm/pkg/authkey"
"github.com/wavetermdev/waveterm/pkg/blockcontroller"
"github.com/wavetermdev/waveterm/pkg/blocklogger"
@@ -22,19 +21,13 @@ import (
"github.com/wavetermdev/waveterm/pkg/filestore"
"github.com/wavetermdev/waveterm/pkg/jobcontroller"
"github.com/wavetermdev/waveterm/pkg/panichandler"
- "github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
"github.com/wavetermdev/waveterm/pkg/remote/fileshare/wshfs"
- "github.com/wavetermdev/waveterm/pkg/secretstore"
"github.com/wavetermdev/waveterm/pkg/service"
- "github.com/wavetermdev/waveterm/pkg/telemetry"
- "github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
"github.com/wavetermdev/waveterm/pkg/util/envutil"
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
"github.com/wavetermdev/waveterm/pkg/util/sigutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/waveobj"
- "github.com/wavetermdev/waveterm/pkg/wcloud"
"github.com/wavetermdev/waveterm/pkg/wconfig"
"github.com/wavetermdev/waveterm/pkg/wcore"
"github.com/wavetermdev/waveterm/pkg/web"
@@ -44,7 +37,6 @@ import (
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshremote"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshserver"
"github.com/wavetermdev/waveterm/pkg/wshutil"
- "github.com/wavetermdev/waveterm/pkg/wslconn"
"github.com/wavetermdev/waveterm/pkg/wstore"
"net/http"
@@ -55,15 +47,8 @@ import (
var WaveVersion = "0.0.0"
var BuildTime = "0"
-const InitialTelemetryWait = 10 * time.Second
-const TelemetryTick = 2 * time.Minute
-const TelemetryInterval = 4 * time.Hour
-const TelemetryInitialCountsWait = 5 * time.Second
-const TelemetryCountsInterval = 1 * time.Hour
const BackupCleanupTick = 2 * time.Minute
const BackupCleanupInterval = 4 * time.Hour
-const InitialDiagnosticWait = 5 * time.Minute
-const DiagnosticTick = 10 * time.Minute
var shutdownOnce sync.Once
@@ -81,8 +66,6 @@ func doShutdown(reason string) {
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFn()
go blockcontroller.StopAllBlockControllersForShutdown()
- shutdownActivityUpdate()
- sendTelemetryWrapper()
// TODO deal with flush in progress
clearTempFiles()
filestore.WFS.FlushCache(ctx)
@@ -118,74 +101,6 @@ func startConfigWatcher() {
}
}
-func telemetryLoop() {
- defer func() {
- panichandler.PanicHandler("telemetryLoop", recover())
- }()
- var nextSend int64
- time.Sleep(InitialTelemetryWait)
- for {
- if time.Now().Unix() > nextSend {
- nextSend = time.Now().Add(TelemetryInterval).Unix()
- sendTelemetryWrapper()
- }
- time.Sleep(TelemetryTick)
- }
-}
-
-func diagnosticLoop() {
- defer func() {
- panichandler.PanicHandler("diagnosticLoop", recover())
- }()
- if os.Getenv("WAVETERM_NOPING") != "" {
- log.Printf("WAVETERM_NOPING set, disabling diagnostic ping\n")
- return
- }
- var lastSentDate string
- time.Sleep(InitialDiagnosticWait)
- for {
- currentDate := time.Now().Format("2006-01-02")
- if lastSentDate == "" || lastSentDate != currentDate {
- if sendDiagnosticPing() {
- lastSentDate = currentDate
- }
- }
- time.Sleep(DiagnosticTick)
- }
-}
-
-func sendDiagnosticPing() bool {
- ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancelFn()
-
- rpcClient := wshclient.GetBareRpcClient()
- isOnline, err := wshclient.NetworkOnlineCommand(rpcClient, &wshrpc.RpcOpts{Route: "electron", Timeout: 2000})
- if err != nil || !isOnline {
- return false
- }
- clientId := wstore.GetClientId()
- usageTelemetry := telemetry.IsTelemetryEnabled()
- wcloud.SendDiagnosticPing(ctx, clientId, usageTelemetry)
- return true
-}
-
-func setupTelemetryConfigHandler() {
- watcher := wconfig.GetWatcher()
- if watcher == nil {
- return
- }
- currentConfig := watcher.GetFullConfig()
- currentTelemetryEnabled := currentConfig.Settings.TelemetryEnabled
-
- watcher.RegisterUpdateHandler(func(newConfig wconfig.FullConfigType) {
- newTelemetryEnabled := newConfig.Settings.TelemetryEnabled
- if newTelemetryEnabled != currentTelemetryEnabled {
- currentTelemetryEnabled = newTelemetryEnabled
- wcore.GoSendNoTelemetryUpdate(newTelemetryEnabled)
- }
- })
-}
-
func backupCleanupLoop() {
defer func() {
panichandler.PanicHandler("backupCleanupLoop", recover())
@@ -203,185 +118,6 @@ func backupCleanupLoop() {
}
}
-func panicTelemetryHandler(panicName string) {
- activity := wshrpc.ActivityUpdate{NumPanics: 1}
- err := telemetry.UpdateActivity(context.Background(), activity)
- if err != nil {
- log.Printf("error updating activity (panicTelemetryHandler): %v\n", err)
- }
- telemetry.RecordTEvent(context.Background(), telemetrydata.MakeTEvent("debug:panic", telemetrydata.TEventProps{
- PanicType: panicName,
- }))
-}
-
-func sendTelemetryWrapper() {
- defer func() {
- panichandler.PanicHandler("sendTelemetryWrapper", recover())
- }()
- ctx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second)
- defer cancelFn()
- beforeSendActivityUpdate(ctx)
- clientId := wstore.GetClientId()
- err := wcloud.SendAllTelemetry(clientId)
- if err != nil {
- log.Printf("[error] sending telemetry: %v\n", err)
- }
-}
-
-func updateTelemetryCounts(lastCounts telemetrydata.TEventProps) telemetrydata.TEventProps {
- ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancelFn()
- var props telemetrydata.TEventProps
- props.CountBlocks, _ = wstore.DBGetCount[*waveobj.Block](ctx)
- props.CountTabs, _ = wstore.DBGetCount[*waveobj.Tab](ctx)
- props.CountWindows, _ = wstore.DBGetCount[*waveobj.Window](ctx)
- props.CountWorkspaces, _, _ = wstore.DBGetWSCounts(ctx)
- props.CountSSHConn = conncontroller.GetNumSSHHasConnected()
- props.CountWSLConn = wslconn.GetNumWSLHasConnected()
- props.CountJobs = jobcontroller.GetNumJobsRunning()
- props.CountJobsConnected = jobcontroller.GetNumJobsConnected()
- props.CountViews, _ = wstore.DBGetBlockViewCounts(ctx)
-
- fullConfig := wconfig.GetWatcher().GetFullConfig()
- customWidgets := fullConfig.CountCustomWidgets()
- customAIPresets := fullConfig.CountCustomAIPresets()
- customSettings := wconfig.CountCustomSettings()
- customAIModes := fullConfig.CountCustomAIModes()
-
- props.UserSet = &telemetrydata.TEventUserProps{
- SettingsCustomWidgets: customWidgets,
- SettingsCustomAIPresets: customAIPresets,
- SettingsCustomSettings: customSettings,
- SettingsCustomAIModes: customAIModes,
- }
-
- secretsCount, err := secretstore.CountSecrets()
- if err == nil {
- props.UserSet.SettingsSecretsCount = secretsCount
- }
-
- if utilfn.CompareAsMarshaledJson(props, lastCounts) {
- return lastCounts
- }
- tevent := telemetrydata.MakeTEvent("app:counts", props)
- err = telemetry.RecordTEvent(ctx, tevent)
- if err != nil {
- log.Printf("error recording counts tevent: %v\n", err)
- }
- return props
-}
-
-func updateTelemetryCountsLoop() {
- defer func() {
- panichandler.PanicHandler("updateTelemetryCountsLoop", recover())
- }()
- var nextSend int64
- var lastCounts telemetrydata.TEventProps
- time.Sleep(TelemetryInitialCountsWait)
- for {
- if time.Now().Unix() > nextSend {
- nextSend = time.Now().Add(TelemetryCountsInterval).Unix()
- lastCounts = updateTelemetryCounts(lastCounts)
- }
- time.Sleep(TelemetryTick)
- }
-}
-
-func beforeSendActivityUpdate(ctx context.Context) {
- activity := wshrpc.ActivityUpdate{}
- activity.NumTabs, _ = wstore.DBGetCount[*waveobj.Tab](ctx)
- activity.NumBlocks, _ = wstore.DBGetCount[*waveobj.Block](ctx)
- activity.Blocks, _ = wstore.DBGetBlockViewCounts(ctx)
- activity.NumWindows, _ = wstore.DBGetCount[*waveobj.Window](ctx)
- activity.NumSSHConn = conncontroller.GetNumSSHHasConnected()
- activity.NumWSLConn = wslconn.GetNumWSLHasConnected()
- activity.NumWSNamed, activity.NumWS, _ = wstore.DBGetWSCounts(ctx)
- err := telemetry.UpdateActivity(ctx, activity)
- if err != nil {
- log.Printf("error updating before activity: %v\n", err)
- }
-}
-
-func startupActivityUpdate(firstLaunch bool) {
- defer func() {
- panichandler.PanicHandler("startupActivityUpdate", recover())
- }()
- ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancelFn()
- activity := wshrpc.ActivityUpdate{Startup: 1}
- err := telemetry.UpdateActivity(ctx, activity) // set at least one record into activity (don't use go routine wrap here)
- if err != nil {
- log.Printf("error updating startup activity: %v\n", err)
- }
- autoUpdateChannel := telemetry.AutoUpdateChannel()
- autoUpdateEnabled := telemetry.IsAutoUpdateEnabled()
- shellType, shellVersion, shellErr := shellutil.DetectShellTypeAndVersion()
- if shellErr != nil {
- shellType = "error"
- shellVersion = ""
- }
- userSetOnce := &telemetrydata.TEventUserProps{
- ClientInitialVersion: "v" + WaveVersion,
- }
- tosTs := telemetry.GetTosAgreedTs()
- var cohortTime time.Time
- if tosTs > 0 {
- cohortTime = time.UnixMilli(tosTs)
- } else {
- cohortTime = time.Now()
- }
- cohortMonth := cohortTime.Format("2006-01")
- year, week := cohortTime.ISOWeek()
- cohortISOWeek := fmt.Sprintf("%04d-W%02d", year, week)
- userSetOnce.CohortMonth = cohortMonth
- userSetOnce.CohortISOWeek = cohortISOWeek
- fullConfig := wconfig.GetWatcher().GetFullConfig()
- props := telemetrydata.TEventProps{
- UserSet: &telemetrydata.TEventUserProps{
- ClientVersion: "v" + wavebase.WaveVersion,
- ClientBuildTime: wavebase.BuildTime,
- ClientArch: wavebase.ClientArch(),
- ClientOSRelease: wavebase.UnameKernelRelease(),
- ClientIsDev: wavebase.IsDevMode(),
- ClientPackageType: wavebase.ClientPackageType(),
- ClientMacOSVersion: wavebase.ClientMacOSVersion(),
- AutoUpdateChannel: autoUpdateChannel,
- AutoUpdateEnabled: autoUpdateEnabled,
- LocalShellType: shellType,
- LocalShellVersion: shellVersion,
- SettingsTransparent: fullConfig.Settings.WindowTransparent,
- },
- UserSetOnce: userSetOnce,
- }
- if firstLaunch {
- props.AppFirstLaunch = true
- }
- tevent := telemetrydata.MakeTEvent("app:startup", props)
- err = telemetry.RecordTEvent(ctx, tevent)
- if err != nil {
- log.Printf("error recording startup event: %v\n", err)
- }
-}
-
-func shutdownActivityUpdate() {
- ctx, cancelFn := context.WithTimeout(context.Background(), 1*time.Second)
- defer cancelFn()
- activity := wshrpc.ActivityUpdate{Shutdown: 1}
- err := telemetry.UpdateActivity(ctx, activity) // do NOT use the go routine wrap here (this needs to be synchronous)
- if err != nil {
- log.Printf("error updating shutdown activity: %v\n", err)
- }
- err = telemetry.TruncateActivityTEventForShutdown(ctx)
- if err != nil {
- log.Printf("error truncating activity t-event for shutdown: %v\n", err)
- }
- tevent := telemetrydata.MakeTEvent("app:shutdown", telemetrydata.TEventProps{})
- err = telemetry.RecordTEvent(ctx, tevent)
- if err != nil {
- log.Printf("error recording shutdown event: %v\n", err)
- }
-}
-
func createMainWshClient() {
rpc := wshserver.GetMainRpcClient()
wshutil.DefaultRouter.RegisterTrustedLeaf(rpc, wshutil.DefaultRoute)
@@ -405,11 +141,6 @@ func grabAndRemoveEnvVars() error {
if err != nil {
return err
}
- err = wcloud.CacheAndRemoveEnvVars()
- if err != nil {
- return err
- }
-
// Remove WAVETERM env vars that leak from prod => dev
os.Unsetenv("WAVETERM_CLIENTID")
os.Unsetenv("WAVETERM_WORKSPACEID")
@@ -525,7 +256,6 @@ func main() {
log.Printf("error initializing wstore: %v\n", err)
return
}
- panichandler.PanicTelemetryHandler = panicTelemetryHandler
go func() {
defer func() {
panichandler.PanicHandler("InitCustomShellStartupFiles", recover())
@@ -563,15 +293,9 @@ func main() {
sigutil.InstallSIGUSR1Handler()
wconfig.MigratePresetsBackgrounds()
startConfigWatcher()
- aiusechat.InitAIModeConfigWatcher()
maybeStartPprofServer()
go stdinReadWatch()
- go telemetryLoop()
- go diagnosticLoop()
- setupTelemetryConfigHandler()
- go updateTelemetryCountsLoop()
go backupCleanupLoop()
- go startupActivityUpdate(firstLaunch) // must be after startConfigWatcher()
blocklogger.InitBlockLogger()
jobcontroller.InitJobController()
blockcontroller.InitBlockController()
diff --git a/cmd/testai/main-testai.go b/cmd/testai/main-testai.go
deleted file mode 100644
index 606e6ac6a1..0000000000
--- a/cmd/testai/main-testai.go
+++ /dev/null
@@ -1,548 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-package main
-
-import (
- "context"
- _ "embed"
- "encoding/json"
- "flag"
- "fmt"
- "log"
- "net/http"
- "os"
- "time"
-
- "github.com/google/uuid"
- "github.com/wavetermdev/waveterm/pkg/aiusechat"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes"
- "github.com/wavetermdev/waveterm/pkg/web/sse"
-)
-
-//go:embed testschema.json
-var testSchemaJSON string
-
-const (
- DefaultAnthropicModel = "claude-sonnet-4-5"
- DefaultOpenAIModel = "gpt-5.1"
- DefaultOpenRouterModel = "mistralai/mistral-small-3.2-24b-instruct"
- DefaultNanoGPTModel = "zai-org/glm-4.7"
- DefaultGeminiModel = "gemini-3-pro-preview"
-)
-
-// TestResponseWriter implements http.ResponseWriter and additional interfaces for testing
-type TestResponseWriter struct {
- header http.Header
-}
-
-func (w *TestResponseWriter) Header() http.Header {
- if w.header == nil {
- w.header = make(http.Header)
- }
- return w.header
-}
-
-func (w *TestResponseWriter) Write(data []byte) (int, error) {
- fmt.Printf("SSE: %s", string(data))
- return len(data), nil
-}
-
-func (w *TestResponseWriter) WriteHeader(statusCode int) {
- fmt.Printf("Status: %d\n", statusCode)
-}
-
-// Implement http.Flusher interface
-func (w *TestResponseWriter) Flush() {
- // No-op for testing
-}
-
-// Implement interfaces needed by http.ResponseController
-func (w *TestResponseWriter) SetWriteDeadline(deadline time.Time) error {
- // No-op for testing
- return nil
-}
-
-func (w *TestResponseWriter) SetReadDeadline(deadline time.Time) error {
- // No-op for testing
- return nil
-}
-
-func getToolDefinitions() []uctypes.ToolDefinition {
- var schemas map[string]any
- if err := json.Unmarshal([]byte(testSchemaJSON), &schemas); err != nil {
- log.Printf("Error parsing schema: %v\n", err)
- return nil
- }
-
- var configSchema map[string]any
- if rawSchema, ok := schemas["config"]; ok && rawSchema != nil {
- if schema, ok := rawSchema.(map[string]any); ok {
- configSchema = schema
- }
- }
- if configSchema == nil {
- configSchema = map[string]any{"type": "object"}
- }
-
- return []uctypes.ToolDefinition{
- {
- Name: "get_config",
- Description: "Get the current GitHub Actions Monitor configuration settings including repository, workflow, polling interval, and max workflow runs",
- InputSchema: map[string]any{
- "type": "object",
- },
- },
- {
- Name: "update_config",
- Description: "Update GitHub Actions Monitor configuration settings",
- InputSchema: configSchema,
- },
- {
- Name: "get_data",
- Description: "Get the current GitHub Actions workflow run data including workflow runs, loading state, and errors",
- InputSchema: map[string]any{
- "type": "object",
- },
- },
- }
-}
-
-func testOpenAI(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) {
- apiKey := os.Getenv("OPENAI_APIKEY")
- if apiKey == "" {
- fmt.Println("Error: OPENAI_APIKEY environment variable not set")
- os.Exit(1)
- }
-
- opts := &uctypes.AIOptsType{
- APIType: uctypes.APIType_OpenAIResponses,
- APIToken: apiKey,
- Model: model,
- MaxTokens: 4096,
- ThinkingLevel: uctypes.ThinkingLevelMedium,
- }
-
- // Generate a chat ID
- chatID := uuid.New().String()
-
- // Convert to AIMessage format for WaveAIPostMessageWrap
- aiMessage := &uctypes.AIMessage{
- MessageId: uuid.New().String(),
- Parts: []uctypes.AIMessagePart{
- {
- Type: uctypes.AIMessagePartTypeText,
- Text: message,
- },
- },
- }
-
- fmt.Printf("Testing OpenAI streaming with WaveAIPostMessageWrap, model: %s\n", model)
- fmt.Printf("Message: %s\n", message)
- fmt.Printf("Chat ID: %s\n", chatID)
- fmt.Println("---")
-
- testWriter := &TestResponseWriter{}
- sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx)
- defer sseHandler.Close()
-
- chatOpts := uctypes.WaveChatOpts{
- ChatId: chatID,
- ClientId: uuid.New().String(),
- Config: *opts,
- Tools: tools,
- }
- err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts)
- if err != nil {
- fmt.Printf("OpenAI streaming error: %v\n", err)
- }
-}
-
-func testOpenAIComp(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) {
- apiKey := os.Getenv("OPENAI_APIKEY")
- if apiKey == "" {
- fmt.Println("Error: OPENAI_APIKEY environment variable not set")
- os.Exit(1)
- }
-
- opts := &uctypes.AIOptsType{
- APIType: uctypes.APIType_OpenAIChat,
- APIToken: apiKey,
- Endpoint: "https://api.openai.com/v1/chat/completions",
- Model: model,
- MaxTokens: 4096,
- ThinkingLevel: uctypes.ThinkingLevelMedium,
- }
-
- chatID := uuid.New().String()
-
- aiMessage := &uctypes.AIMessage{
- MessageId: uuid.New().String(),
- Parts: []uctypes.AIMessagePart{
- {
- Type: uctypes.AIMessagePartTypeText,
- Text: message,
- },
- },
- }
-
- fmt.Printf("Testing OpenAI Completions API with WaveAIPostMessageWrap, model: %s\n", model)
- fmt.Printf("Message: %s\n", message)
- fmt.Printf("Chat ID: %s\n", chatID)
- fmt.Println("---")
-
- testWriter := &TestResponseWriter{}
- sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx)
- defer sseHandler.Close()
-
- chatOpts := uctypes.WaveChatOpts{
- ChatId: chatID,
- ClientId: uuid.New().String(),
- Config: *opts,
- Tools: tools,
- SystemPrompt: []string{"You are a helpful assistant. Be concise and clear in your responses."},
- }
- err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts)
- if err != nil {
- fmt.Printf("OpenAI Completions API streaming error: %v\n", err)
- }
-}
-
-func testOpenRouter(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) {
- apiKey := os.Getenv("OPENROUTER_APIKEY")
- if apiKey == "" {
- fmt.Println("Error: OPENROUTER_APIKEY environment variable not set")
- os.Exit(1)
- }
-
- opts := &uctypes.AIOptsType{
- APIType: uctypes.APIType_OpenAIChat,
- APIToken: apiKey,
- Endpoint: "https://openrouter.ai/api/v1/chat/completions",
- Model: model,
- MaxTokens: 4096,
- ThinkingLevel: uctypes.ThinkingLevelMedium,
- }
-
- chatID := uuid.New().String()
-
- aiMessage := &uctypes.AIMessage{
- MessageId: uuid.New().String(),
- Parts: []uctypes.AIMessagePart{
- {
- Type: uctypes.AIMessagePartTypeText,
- Text: message,
- },
- },
- }
-
- fmt.Printf("Testing OpenRouter with WaveAIPostMessageWrap, model: %s\n", model)
- fmt.Printf("Message: %s\n", message)
- fmt.Printf("Chat ID: %s\n", chatID)
- fmt.Println("---")
-
- testWriter := &TestResponseWriter{}
- sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx)
- defer sseHandler.Close()
-
- chatOpts := uctypes.WaveChatOpts{
- ChatId: chatID,
- ClientId: uuid.New().String(),
- Config: *opts,
- Tools: tools,
- SystemPrompt: []string{"You are a helpful assistant. Be concise and clear in your responses."},
- }
- err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts)
- if err != nil {
- fmt.Printf("OpenRouter streaming error: %v\n", err)
- }
-}
-
-func testNanoGPT(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) {
- apiKey := os.Getenv("NANOGPT_KEY")
- if apiKey == "" {
- fmt.Println("Error: NANOGPT_KEY environment variable not set")
- os.Exit(1)
- }
-
- opts := &uctypes.AIOptsType{
- APIType: uctypes.APIType_OpenAIChat,
- APIToken: apiKey,
- Endpoint: "https://nano-gpt.com/api/v1/chat/completions",
- Model: model,
- MaxTokens: 4096,
- }
-
- chatID := uuid.New().String()
-
- aiMessage := &uctypes.AIMessage{
- MessageId: uuid.New().String(),
- Parts: []uctypes.AIMessagePart{
- {
- Type: uctypes.AIMessagePartTypeText,
- Text: message,
- },
- },
- }
-
- fmt.Printf("Testing NanoGPT with WaveAIPostMessageWrap, model: %s\n", model)
- fmt.Printf("Message: %s\n", message)
- fmt.Printf("Chat ID: %s\n", chatID)
- fmt.Println("---")
-
- testWriter := &TestResponseWriter{}
- sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx)
- defer sseHandler.Close()
-
- chatOpts := uctypes.WaveChatOpts{
- ChatId: chatID,
- ClientId: uuid.New().String(),
- Config: *opts,
- Tools: tools,
- SystemPrompt: []string{"You are a helpful assistant. Be concise and clear in your responses."},
- }
- err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts)
- if err != nil {
- fmt.Printf("NanoGPT streaming error: %v\n", err)
- }
-}
-
-func testAnthropic(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) {
- apiKey := os.Getenv("ANTHROPIC_APIKEY")
- if apiKey == "" {
- fmt.Println("Error: ANTHROPIC_APIKEY environment variable not set")
- os.Exit(1)
- }
-
- opts := &uctypes.AIOptsType{
- APIType: uctypes.APIType_AnthropicMessages,
- APIToken: apiKey,
- Model: model,
- MaxTokens: 4096,
- ThinkingLevel: uctypes.ThinkingLevelMedium,
- }
-
- // Generate a chat ID
- chatID := uuid.New().String()
-
- // Convert to AIMessage format for WaveAIPostMessageWrap
- aiMessage := &uctypes.AIMessage{
- MessageId: uuid.New().String(),
- Parts: []uctypes.AIMessagePart{
- {
- Type: uctypes.AIMessagePartTypeText,
- Text: message,
- },
- },
- }
-
- fmt.Printf("Testing Anthropic streaming with WaveAIPostMessageWrap, model: %s\n", model)
- fmt.Printf("Message: %s\n", message)
- fmt.Printf("Chat ID: %s\n", chatID)
- fmt.Println("---")
-
- testWriter := &TestResponseWriter{}
- sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx)
- defer sseHandler.Close()
-
- chatOpts := uctypes.WaveChatOpts{
- ChatId: chatID,
- ClientId: uuid.New().String(),
- Config: *opts,
- Tools: tools,
- }
- err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts)
- if err != nil {
- fmt.Printf("Anthropic streaming error: %v\n", err)
- }
-}
-
-func testGemini(ctx context.Context, model, message string, tools []uctypes.ToolDefinition) {
- apiKey := os.Getenv("GOOGLE_APIKEY")
- if apiKey == "" {
- fmt.Println("Error: GOOGLE_APIKEY environment variable not set")
- os.Exit(1)
- }
-
- opts := &uctypes.AIOptsType{
- APIType: uctypes.APIType_GoogleGemini,
- APIToken: apiKey,
- Model: model,
- MaxTokens: 8192,
- Capabilities: []string{uctypes.AICapabilityTools, uctypes.AICapabilityImages, uctypes.AICapabilityPdfs},
- }
-
- // Generate a chat ID
- chatID := uuid.New().String()
-
- // Convert to AIMessage format for WaveAIPostMessageWrap
- aiMessage := &uctypes.AIMessage{
- MessageId: uuid.New().String(),
- Parts: []uctypes.AIMessagePart{
- {
- Type: uctypes.AIMessagePartTypeText,
- Text: message,
- },
- },
- }
-
- fmt.Printf("Testing Google Gemini streaming with WaveAIPostMessageWrap, model: %s\n", model)
- fmt.Printf("Message: %s\n", message)
- fmt.Printf("Chat ID: %s\n", chatID)
- fmt.Println("---")
-
- testWriter := &TestResponseWriter{}
- sseHandler := sse.MakeSSEHandlerCh(testWriter, ctx)
- defer sseHandler.Close()
-
- chatOpts := uctypes.WaveChatOpts{
- ChatId: chatID,
- ClientId: uuid.New().String(),
- Config: *opts,
- Tools: tools,
- SystemPrompt: []string{"You are a helpful assistant. Be concise and clear in your responses."},
- }
- err := aiusechat.WaveAIPostMessageWrap(ctx, sseHandler, aiMessage, chatOpts)
- if err != nil {
- fmt.Printf("Google Gemini streaming error: %v\n", err)
- }
-}
-
-func testT1(ctx context.Context) {
- tool := aiusechat.GetAdderToolDefinition()
- tools := []uctypes.ToolDefinition{tool}
- testAnthropic(ctx, DefaultAnthropicModel, "what is 2+2, use the provider adder tool", tools)
-}
-
-func testT2(ctx context.Context) {
- tool := aiusechat.GetAdderToolDefinition()
- tools := []uctypes.ToolDefinition{tool}
- testOpenAI(ctx, DefaultOpenAIModel, "what is 2+2+8, use the provider adder tool", tools)
-}
-
-func testT3(ctx context.Context) {
- testOpenAIComp(ctx, "gpt-4o", "what is 2+2? please be brief", nil)
-}
-
-func testT4(ctx context.Context) {
- tool := aiusechat.GetAdderToolDefinition()
- tools := []uctypes.ToolDefinition{tool}
- testGemini(ctx, DefaultGeminiModel, "what is 2+2+8, use the provider adder tool", tools)
-}
-
-func printUsage() {
- fmt.Println("Usage: go run main-testai.go [--anthropic|--openaicomp|--openrouter|--nanogpt|--gemini] [--tools] [--model ] [message]")
- fmt.Println("Examples:")
- fmt.Println(" go run main-testai.go 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --model o4-mini 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --anthropic 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --anthropic --model claude-3-5-sonnet-20241022 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --openaicomp --model gpt-4o 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --openrouter 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --openrouter --model anthropic/claude-3.5-sonnet 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --nanogpt 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --nanogpt --model gpt-4o 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --gemini 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --gemini --model gemini-1.5-pro 'What is 2+2?'")
- fmt.Println(" go run main-testai.go --tools 'Help me configure GitHub Actions monitoring'")
- fmt.Println("")
- fmt.Println("Default models:")
- fmt.Printf(" OpenAI: %s\n", DefaultOpenAIModel)
- fmt.Printf(" Anthropic: %s\n", DefaultAnthropicModel)
- fmt.Printf(" OpenAI Completions: gpt-4o\n")
- fmt.Printf(" OpenRouter: %s\n", DefaultOpenRouterModel)
- fmt.Printf(" NanoGPT: %s\n", DefaultNanoGPTModel)
- fmt.Printf(" Google Gemini: %s\n", DefaultGeminiModel)
- fmt.Println("")
- fmt.Println("Environment variables:")
- fmt.Println(" OPENAI_APIKEY (for OpenAI models)")
- fmt.Println(" ANTHROPIC_APIKEY (for Anthropic models)")
- fmt.Println(" OPENROUTER_APIKEY (for OpenRouter models)")
- fmt.Println(" NANOGPT_KEY (for NanoGPT models)")
- fmt.Println(" GOOGLE_APIKEY (for Google Gemini models)")
-}
-
-func main() {
- var anthropic, openaicomp, openrouter, nanogpt, gemini, tools, help, t1, t2, t3, t4 bool
- var model string
- flag.BoolVar(&anthropic, "anthropic", false, "Use Anthropic API instead of OpenAI")
- flag.BoolVar(&openaicomp, "openaicomp", false, "Use OpenAI Completions API")
- flag.BoolVar(&openrouter, "openrouter", false, "Use OpenRouter API")
- flag.BoolVar(&nanogpt, "nanogpt", false, "Use NanoGPT API")
- flag.BoolVar(&gemini, "gemini", false, "Use Google Gemini API")
- flag.BoolVar(&tools, "tools", false, "Enable GitHub Actions Monitor tools for testing")
- flag.StringVar(&model, "model", "", fmt.Sprintf("AI model to use (defaults: %s for OpenAI, %s for Anthropic, %s for OpenRouter, %s for NanoGPT, %s for Gemini)", DefaultOpenAIModel, DefaultAnthropicModel, DefaultOpenRouterModel, DefaultNanoGPTModel, DefaultGeminiModel))
- flag.BoolVar(&help, "help", false, "Show usage information")
- flag.BoolVar(&t1, "t1", false, fmt.Sprintf("Run preset T1 test (%s with 'what is 2+2')", DefaultAnthropicModel))
- flag.BoolVar(&t2, "t2", false, fmt.Sprintf("Run preset T2 test (%s with 'what is 2+2')", DefaultOpenAIModel))
- flag.BoolVar(&t3, "t3", false, "Run preset T3 test (OpenAI Completions API with gpt-5.1)")
- flag.BoolVar(&t4, "t4", false, "Run preset T4 test (OpenAI Completions API with gemini-3-pro-preview)")
- flag.Parse()
-
- if help {
- printUsage()
- os.Exit(0)
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
- defer cancel()
-
- if t1 {
- testT1(ctx)
- return
- }
- if t2 {
- testT2(ctx)
- return
- }
- if t3 {
- testT3(ctx)
- return
- }
- if t4 {
- testT4(ctx)
- return
- }
-
- // Set default model based on API type if not provided
- if model == "" {
- if anthropic {
- model = DefaultAnthropicModel
- } else if openaicomp {
- model = "gpt-4o"
- } else if openrouter {
- model = DefaultOpenRouterModel
- } else if nanogpt {
- model = DefaultNanoGPTModel
- } else if gemini {
- model = DefaultGeminiModel
- } else {
- model = DefaultOpenAIModel
- }
- }
-
- args := flag.Args()
- message := "What is 2+2?"
- if len(args) > 0 {
- message = args[0]
- }
-
- var toolDefs []uctypes.ToolDefinition
- if tools {
- toolDefs = getToolDefinitions()
- }
-
- if anthropic {
- testAnthropic(ctx, model, message, toolDefs)
- } else if openaicomp {
- testOpenAIComp(ctx, model, message, toolDefs)
- } else if openrouter {
- testOpenRouter(ctx, model, message, toolDefs)
- } else if nanogpt {
- testNanoGPT(ctx, model, message, toolDefs)
- } else if gemini {
- testGemini(ctx, model, message, toolDefs)
- } else {
- testOpenAI(ctx, model, message, toolDefs)
- }
-}
diff --git a/cmd/testai/testschema.json b/cmd/testai/testschema.json
deleted file mode 100644
index dc9de2b834..0000000000
--- a/cmd/testai/testschema.json
+++ /dev/null
@@ -1,104 +0,0 @@
-{
- "config": {
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "description": "Application configuration settings",
- "properties": {
- "maxWorkflowRuns": {
- "description": "Maximum number of workflow runs to fetch",
- "maximum": 100,
- "minimum": 1,
- "type": "integer"
- },
- "pollInterval": {
- "description": "Polling interval for GitHub API requests",
- "maximum": 300,
- "minimum": 1,
- "type": "integer",
- "units": "s"
- },
- "repository": {
- "description": "GitHub repository in owner/repo format",
- "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$",
- "type": "string"
- },
- "workflow": {
- "description": "GitHub Actions workflow file name",
- "pattern": "^.+\\.(yml|yaml)$",
- "type": "string"
- }
- },
- "title": "Application Configuration",
- "type": "object"
- },
- "data": {
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "definitions": {
- "WorkflowRun": {
- "properties": {
- "conclusion": {
- "type": "string"
- },
- "created_at": {
- "format": "date-time",
- "type": "string"
- },
- "html_url": {
- "type": "string"
- },
- "id": {
- "type": "integer"
- },
- "name": {
- "type": "string"
- },
- "run_number": {
- "type": "integer"
- },
- "status": {
- "type": "string"
- },
- "updated_at": {
- "format": "date-time",
- "type": "string"
- }
- },
- "required": [
- "id",
- "name",
- "status",
- "conclusion",
- "created_at",
- "updated_at",
- "html_url",
- "run_number"
- ],
- "type": "object"
- }
- },
- "description": "Application data schema",
- "properties": {
- "isLoading": {
- "description": "Loading state for workflow data fetch",
- "type": "boolean"
- },
- "lastError": {
- "description": "Last error message from GitHub API",
- "type": "string"
- },
- "lastRefreshTime": {
- "description": "Timestamp of last successful data refresh",
- "format": "date-time",
- "type": "string"
- },
- "workflowRuns": {
- "description": "List of GitHub Actions workflow runs",
- "items": {
- "$ref": "#/definitions/WorkflowRun"
- },
- "type": "array"
- }
- },
- "title": "Application Data",
- "type": "object"
- }
-}
diff --git a/cmd/testopenai/main-testopenai.go b/cmd/testopenai/main-testopenai.go
deleted file mode 100644
index 7017407b47..0000000000
--- a/cmd/testopenai/main-testopenai.go
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-package main
-
-import (
- "bufio"
- "bytes"
- "context"
- "encoding/json"
- "flag"
- "fmt"
- "io"
- "net/http"
- "os"
- "time"
-
- "github.com/wavetermdev/waveterm/pkg/aiusechat"
- "github.com/wavetermdev/waveterm/pkg/aiusechat/openai"
-)
-
-func makeOpenAIRequest(ctx context.Context, apiKey, model, message string, tools bool) error {
- reqBody := openai.OpenAIRequest{
- Model: model,
- Input: []any{
- openai.OpenAIMessage{
- Role: "user",
- Content: []openai.OpenAIMessageContent{
- {
- Type: "input_text",
- Text: message,
- },
- },
- },
- },
- Stream: true,
- StreamOptions: &openai.StreamOptionsType{IncludeObfuscation: false},
- Reasoning: &openai.ReasoningType{Effort: "medium"},
- }
- if tools {
- reqBody.Tools = []openai.OpenAIRequestTool{
- openai.ConvertToolDefinitionToOpenAI(aiusechat.GetAdderToolDefinition()),
- }
- }
-
- jsonData, err := json.Marshal(reqBody)
- if err != nil {
- return fmt.Errorf("error marshaling request: %v", err)
- }
-
- // Pretty print the request JSON for debugging
- prettyJSON, err := json.MarshalIndent(reqBody, "", " ")
- if err == nil {
- fmt.Printf("Request JSON:\n%s\n", string(prettyJSON))
- }
-
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.openai.com/v1/responses", bytes.NewBuffer(jsonData))
- if err != nil {
- return fmt.Errorf("error creating request: %v", err)
- }
-
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Authorization", "Bearer "+apiKey)
- req.Header.Set("Accept", "text/event-stream")
-
- client := &http.Client{
- Timeout: 60 * time.Second,
- }
-
- resp, err := client.Do(req)
- if err != nil {
- return fmt.Errorf("error making request: %v", err)
- }
- defer resp.Body.Close()
-
- fmt.Printf("Response Status: %s\n", resp.Status)
- fmt.Printf("Response Headers:\n")
- for name, values := range resp.Header {
- for _, value := range values {
- fmt.Printf(" %s: %s\n", name, value)
- }
- }
- fmt.Println("---")
-
- if resp.StatusCode != http.StatusOK {
- body, _ := io.ReadAll(resp.Body)
- return fmt.Errorf("API error (%d): %s", resp.StatusCode, string(body))
- }
-
- return processSSEStream(resp.Body)
-}
-
-func processSSEStream(reader io.Reader) error {
- scanner := bufio.NewScanner(reader)
-
- fmt.Println("SSE Stream:")
- fmt.Println("---")
-
- for scanner.Scan() {
- line := scanner.Text()
- fmt.Println(line)
- }
-
- if err := scanner.Err(); err != nil {
- return fmt.Errorf("error reading stream: %v", err)
- }
-
- return nil
-}
-
-func printUsage() {
- fmt.Println("Usage: go run main-testopenai.go [--model ] [--tools] [message]")
- fmt.Println("Examples:")
- fmt.Println(" go run main-testopenai.go 'Stream me a limerick about gophers coding in Go.'")
- fmt.Println(" go run main-testopenai.go --model gpt-4 'What is 2+2?'")
- fmt.Println(" go run main-testopenai.go --tools 'What is 2+2? Use the adder tool.'")
- fmt.Println("")
- fmt.Println("Default model: gpt-5-mini")
- fmt.Println("")
- fmt.Println("Environment variables:")
- fmt.Println(" OPENAI_APIKEY (required)")
-}
-
-func main() {
- var model string
- var showHelp bool
- var tools bool
-
- flag.StringVar(&model, "model", "gpt-5-mini", "OpenAI model to use")
- flag.BoolVar(&showHelp, "help", false, "Show usage information")
- flag.BoolVar(&tools, "tools", false, "Enable tools for testing")
- flag.Parse()
-
- if showHelp {
- printUsage()
- os.Exit(0)
- }
-
- apiKey := os.Getenv("OPENAI_APIKEY")
- if apiKey == "" {
- fmt.Println("Error: OPENAI_APIKEY environment variable not set")
- printUsage()
- os.Exit(1)
- }
-
- args := flag.Args()
- message := "Stream me a limerick about gophers coding in Go."
- if len(args) > 0 {
- message = args[0]
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
- defer cancel()
-
- fmt.Printf("Testing OpenAI Responses API\n")
- fmt.Printf("Model: %s\n", model)
- fmt.Printf("Message: %s\n", message)
- fmt.Println("===")
-
- if err := makeOpenAIRequest(ctx, apiKey, model, message, tools); err != nil {
- fmt.Printf("Error: %v\n", err)
- os.Exit(1)
- }
-}
diff --git a/cmd/testsummarize/main-testsummarize.go b/cmd/testsummarize/main-testsummarize.go
deleted file mode 100644
index fc16e59e04..0000000000
--- a/cmd/testsummarize/main-testsummarize.go
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-package main
-
-import (
- "context"
- "flag"
- "fmt"
- "os"
- "time"
-
- "github.com/wavetermdev/waveterm/pkg/aiusechat/google"
-)
-
-func printUsage() {
- fmt.Println("Usage: go run main-testsummarize.go [--help] [--mode MODE] ")
- fmt.Println("Examples:")
- fmt.Println(" go run main-testsummarize.go README.md")
- fmt.Println(" go run main-testsummarize.go --mode useful /path/to/image.png")
- fmt.Println(" go run main-testsummarize.go -m publiccode document.pdf")
- fmt.Println("")
- fmt.Println("Supported file types:")
- fmt.Println(" - Text files (up to 200KB)")
- fmt.Println(" - Images (up to 7MB)")
- fmt.Println(" - PDFs (up to 5MB)")
- fmt.Println("")
- fmt.Println("Flags:")
- fmt.Println(" --mode, -m Summarization mode (default: quick)")
- fmt.Println(" Options: quick, useful, publiccode, htmlcontent, htmlfull")
- fmt.Println("")
- fmt.Println("Environment variables:")
- fmt.Println(" GOOGLE_APIKEY (required)")
-}
-
-func main() {
- var showHelp bool
- var mode string
- flag.BoolVar(&showHelp, "help", false, "Show usage information")
- flag.StringVar(&mode, "mode", "quick", "Summarization mode")
- flag.StringVar(&mode, "m", "quick", "Summarization mode (shorthand)")
- flag.Parse()
-
- if showHelp {
- printUsage()
- os.Exit(0)
- }
-
- apiKey := os.Getenv("GOOGLE_APIKEY")
- if apiKey == "" {
- fmt.Println("Error: GOOGLE_APIKEY environment variable not set")
- printUsage()
- os.Exit(1)
- }
-
- args := flag.Args()
- if len(args) == 0 {
- fmt.Println("Error: filename required")
- printUsage()
- os.Exit(1)
- }
-
- filename := args[0]
-
- // Check if file exists
- if _, err := os.Stat(filename); os.IsNotExist(err) {
- fmt.Printf("Error: file '%s' does not exist\n", filename)
- os.Exit(1)
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
- defer cancel()
-
- fmt.Printf("Summarizing file: %s\n", filename)
- fmt.Printf("Model: %s\n", google.SummarizeModel)
- fmt.Printf("Mode: %s\n", mode)
-
- startTime := time.Now()
- summary, usage, err := google.SummarizeFile(ctx, filename, google.SummarizeOpts{
- APIKey: apiKey,
- Mode: mode,
- })
- latency := time.Since(startTime)
-
- fmt.Printf("Latency: %d ms\n", latency.Milliseconds())
- fmt.Println("===")
- if err != nil {
- fmt.Printf("Error: %v\n", err)
- os.Exit(1)
- }
-
- fmt.Println("\nSummary:")
- fmt.Println("---")
- fmt.Println(summary)
- fmt.Println("---")
-
- if usage != nil {
- fmt.Println("\nUsage Statistics:")
- fmt.Printf(" Prompt tokens: %d\n", usage.PromptTokenCount)
- fmt.Printf(" Cached tokens: %d\n", usage.CachedContentTokenCount)
- fmt.Printf(" Response tokens: %d\n", usage.CandidatesTokenCount)
- fmt.Printf(" Total tokens: %d\n", usage.TotalTokenCount)
- }
-}
\ No newline at end of file
diff --git a/cmd/wsh/cmd/wshcmd-ai.go b/cmd/wsh/cmd/wshcmd-ai.go
deleted file mode 100644
index 643c80ee7a..0000000000
--- a/cmd/wsh/cmd/wshcmd-ai.go
+++ /dev/null
@@ -1,211 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-package cmd
-
-import (
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/spf13/cobra"
- "github.com/wavetermdev/waveterm/pkg/util/fileutil"
- "github.com/wavetermdev/waveterm/pkg/util/utilfn"
- "github.com/wavetermdev/waveterm/pkg/wshrpc"
- "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
-)
-
-var aiCmd = &cobra.Command{
- Use: "ai [options] [files...]",
- Short: "Append content to Wave AI sidebar prompt",
- Long: `Append content to Wave AI sidebar prompt (does not auto-submit by default)
-
-Arguments:
- files... Files to attach (use '-' for stdin)
-
-Examples:
- git diff | wsh ai - # Pipe diff to AI, ask question in UI
- wsh ai main.go # Attach file, ask question in UI
- wsh ai *.go -m "find bugs" # Attach files with message
- wsh ai -s - -m "review" < log.txt # Stdin + message, auto-submit
- wsh ai -n config.json # New chat with file attached`,
- RunE: aiRun,
- PreRunE: preRunSetupRpcClient,
- DisableFlagsInUseLine: true,
-}
-
-var aiMessageFlag string
-var aiSubmitFlag bool
-var aiNewBlockFlag bool
-
-func init() {
- rootCmd.AddCommand(aiCmd)
- aiCmd.Flags().StringVarP(&aiMessageFlag, "message", "m", "", "optional message/question to append after files")
- aiCmd.Flags().BoolVarP(&aiSubmitFlag, "submit", "s", false, "submit the prompt immediately after appending")
- aiCmd.Flags().BoolVarP(&aiNewBlockFlag, "new", "n", false, "create a new AI chat instead of using existing")
-}
-
-func detectMimeType(data []byte) string {
- mimeType := http.DetectContentType(data)
- return strings.Split(mimeType, ";")[0]
-}
-
-func getMaxFileSize(mimeType string) (int, string) {
- if mimeType == "application/pdf" {
- return 5 * 1024 * 1024, "5MB"
- }
- if strings.HasPrefix(mimeType, "image/") {
- return 7 * 1024 * 1024, "7MB"
- }
- return 200 * 1024, "200KB"
-}
-
-func aiRun(cmd *cobra.Command, args []string) (rtnErr error) {
- defer func() {
- sendActivity("ai", rtnErr == nil)
- }()
-
- if len(args) == 0 && aiMessageFlag == "" {
- OutputHelpMessage(cmd)
- return fmt.Errorf("no files or message provided")
- }
-
- const maxFileCount = 15
- const rpcTimeout = 30000
-
- var allFiles []wshrpc.AIAttachedFile
- var stdinUsed bool
-
- if len(args) > maxFileCount {
- return fmt.Errorf("too many files (maximum %d files allowed)", maxFileCount)
- }
-
- for _, filePath := range args {
- var data []byte
- var fileName string
- var mimeType string
- var err error
-
- if filePath == "-" {
- if stdinUsed {
- return fmt.Errorf("stdin (-) can only be used once")
- }
- stdinUsed = true
-
- data, err = io.ReadAll(os.Stdin)
- if err != nil {
- return fmt.Errorf("reading from stdin: %w", err)
- }
- fileName = "stdin"
- mimeType = "text/plain"
- } else {
- fileInfo, err := os.Stat(filePath)
- if err != nil {
- return fmt.Errorf("accessing file %s: %w", filePath, err)
- }
- absPath, err := filepath.Abs(filePath)
- if err != nil {
- return fmt.Errorf("getting absolute path for %s: %w", filePath, err)
- }
-
- if fileInfo.IsDir() {
- result, err := fileutil.ReadDir(filePath, 500)
- if err != nil {
- return fmt.Errorf("reading directory %s: %w", filePath, err)
- }
- jsonData, err := json.Marshal(result)
- if err != nil {
- return fmt.Errorf("marshaling directory listing for %s: %w", filePath, err)
- }
- data = jsonData
- fileName = absPath
- mimeType = "directory"
- } else {
- data, err = os.ReadFile(filePath)
- if err != nil {
- return fmt.Errorf("reading file %s: %w", filePath, err)
- }
- fileName = absPath
- mimeType = detectMimeType(data)
- }
- }
-
- isPDF := mimeType == "application/pdf"
- isImage := strings.HasPrefix(mimeType, "image/")
- isDirectory := mimeType == "directory"
-
- if !isPDF && !isImage && !isDirectory {
- mimeType = "text/plain"
- if utilfn.ContainsBinaryData(data) {
- return fmt.Errorf("file %s contains binary data and cannot be uploaded as text", fileName)
- }
- }
-
- maxSize, sizeStr := getMaxFileSize(mimeType)
- if len(data) > maxSize {
- return fmt.Errorf("file %s exceeds maximum size of %s for %s files", fileName, sizeStr, mimeType)
- }
-
- allFiles = append(allFiles, wshrpc.AIAttachedFile{
- Name: fileName,
- Type: mimeType,
- Size: len(data),
- Data64: base64.StdEncoding.EncodeToString(data),
- })
- }
-
- tabId := os.Getenv("WAVETERM_TABID")
- if tabId == "" {
- return fmt.Errorf("WAVETERM_TABID environment variable not set")
- }
-
- route := wshutil.MakeTabRouteId(tabId)
-
- if aiNewBlockFlag {
- newChatData := wshrpc.CommandWaveAIAddContextData{
- NewChat: true,
- }
- err := wshclient.WaveAIAddContextCommand(RpcClient, newChatData, &wshrpc.RpcOpts{
- Route: route,
- Timeout: rpcTimeout,
- })
- if err != nil {
- return fmt.Errorf("creating new chat: %w", err)
- }
- }
-
- for _, file := range allFiles {
- contextData := wshrpc.CommandWaveAIAddContextData{
- Files: []wshrpc.AIAttachedFile{file},
- }
- err := wshclient.WaveAIAddContextCommand(RpcClient, contextData, &wshrpc.RpcOpts{
- Route: route,
- Timeout: rpcTimeout,
- })
- if err != nil {
- return fmt.Errorf("adding file %s: %w", file.Name, err)
- }
- }
-
- if aiMessageFlag != "" || aiSubmitFlag {
- finalContextData := wshrpc.CommandWaveAIAddContextData{
- Text: aiMessageFlag,
- Submit: aiSubmitFlag,
- }
- err := wshclient.WaveAIAddContextCommand(RpcClient, finalContextData, &wshrpc.RpcOpts{
- Route: route,
- Timeout: rpcTimeout,
- })
- if err != nil {
- return fmt.Errorf("adding context: %w", err)
- }
- }
-
- return nil
-}
diff --git a/cmd/wsh/cmd/wshcmd-badge.go b/cmd/wsh/cmd/wshcmd-badge.go
index 590ed1e40b..f2b17a0f24 100644
--- a/cmd/wsh/cmd/wshcmd-badge.go
+++ b/cmd/wsh/cmd/wshcmd-badge.go
@@ -44,7 +44,6 @@ func init() {
func badgeRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("badge", rtnErr == nil)
}()
if badgePid > 0 && runtime.GOOS == "windows" {
diff --git a/cmd/wsh/cmd/wshcmd-blocks.go b/cmd/wsh/cmd/wshcmd-blocks.go
index 7e4b935ee3..8c231ae9c3 100644
--- a/cmd/wsh/cmd/wshcmd-blocks.go
+++ b/cmd/wsh/cmd/wshcmd-blocks.go
@@ -31,7 +31,7 @@ type BlockDetails struct {
BlockId string `json:"blockid"` // Unique identifier for the block
WorkspaceId string `json:"workspaceid"` // ID of the workspace containing the block
TabId string `json:"tabid"` // ID of the tab containing the block
- View string `json:"view"` // Canonical view type (term, web, preview, edit, sysinfo, waveai)
+ View string `json:"view"` // Canonical view type (term, web, preview, edit, sysinfo)
Meta waveobj.MetaMapType `json:"meta"` // Block metadata including view type
}
@@ -74,7 +74,7 @@ func init() {
blocksListCmd.Flags().StringVar(&blocksWindowId, "window", "", "restrict to window id")
blocksListCmd.Flags().StringVar(&blocksWorkspaceId, "workspace", "", "restrict to workspace id")
blocksListCmd.Flags().StringVar(&blocksTabId, "tab", "", "restrict to specific tab id")
- blocksListCmd.Flags().StringVar(&blocksView, "view", "", "restrict to view type (term/terminal, web/browser, preview/edit, sysinfo, waveai)")
+ blocksListCmd.Flags().StringVar(&blocksView, "view", "", "restrict to view type (term/terminal, web/browser, preview/edit, sysinfo)")
blocksListCmd.Flags().BoolVar(&blocksJSON, "json", false, "output as JSON")
blocksListCmd.Flags().IntVar(&blocksTimeout, "timeout", 5000, "timeout in milliseconds for RPC calls (default: 5000)")
@@ -100,7 +100,7 @@ func init() {
func blocksListRun(cmd *cobra.Command, args []string) error {
if v := strings.TrimSpace(blocksView); v != "" {
if !isKnownViewFilter(v) {
- return fmt.Errorf("unknown --view %q; try one of: term, web, preview, edit, sysinfo, waveai", v)
+ return fmt.Errorf("unknown --view %q; try one of: term, web, preview, edit, sysinfo", v)
}
}
@@ -270,8 +270,6 @@ func matchesViewType(actual, filter string) bool {
return strings.EqualFold(actual, "term")
case "web", "browser", "url":
return strings.EqualFold(actual, "web")
- case "ai", "waveai", "assistant":
- return strings.EqualFold(actual, "waveai")
case "sys", "sysinfo", "system":
return strings.EqualFold(actual, "sysinfo")
}
@@ -285,8 +283,7 @@ func isKnownViewFilter(f string) bool {
case "term", "terminal", "shell", "console",
"web", "browser", "url",
"preview", "edit",
- "sysinfo", "sys", "system",
- "waveai", "ai", "assistant":
+ "sysinfo", "sys", "system":
return true
default:
return false
diff --git a/cmd/wsh/cmd/wshcmd-debug.go b/cmd/wsh/cmd/wshcmd-debug.go
index 9efac0ff87..29a1857689 100644
--- a/cmd/wsh/cmd/wshcmd-debug.go
+++ b/cmd/wsh/cmd/wshcmd-debug.go
@@ -24,24 +24,11 @@ var debugBlockIdsCmd = &cobra.Command{
Hidden: true,
}
-var debugSendTelemetryCmd = &cobra.Command{
- Use: "send-telemetry",
- Short: "send telemetry",
- RunE: debugSendTelemetryRun,
- Hidden: true,
-}
-
func init() {
debugCmd.AddCommand(debugBlockIdsCmd)
- debugCmd.AddCommand(debugSendTelemetryCmd)
rootCmd.AddCommand(debugCmd)
}
-func debugSendTelemetryRun(cmd *cobra.Command, args []string) error {
- err := wshclient.SendTelemetryCommand(RpcClient, nil)
- return err
-}
-
func debugBlockIdsRun(cmd *cobra.Command, args []string) error {
oref, err := resolveBlockArg()
if err != nil {
diff --git a/cmd/wsh/cmd/wshcmd-debugterm.go b/cmd/wsh/cmd/wshcmd-debugterm.go
index 66346c460a..dabea2540e 100644
--- a/cmd/wsh/cmd/wshcmd-debugterm.go
+++ b/cmd/wsh/cmd/wshcmd-debugterm.go
@@ -50,7 +50,6 @@ func init() {
func debugTermRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("debugterm", rtnErr == nil)
}()
mode, err := getDebugTermMode()
if err != nil {
diff --git a/cmd/wsh/cmd/wshcmd-deleteblock.go b/cmd/wsh/cmd/wshcmd-deleteblock.go
index 76518e721c..c6406f928e 100644
--- a/cmd/wsh/cmd/wshcmd-deleteblock.go
+++ b/cmd/wsh/cmd/wshcmd-deleteblock.go
@@ -24,7 +24,6 @@ func init() {
func deleteBlockRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("deleteblock", rtnErr == nil)
}()
fullORef, err := resolveBlockArg()
if err != nil {
diff --git a/cmd/wsh/cmd/wshcmd-editconfig.go b/cmd/wsh/cmd/wshcmd-editconfig.go
index cbd4015bae..cedba81ad0 100644
--- a/cmd/wsh/cmd/wshcmd-editconfig.go
+++ b/cmd/wsh/cmd/wshcmd-editconfig.go
@@ -30,7 +30,6 @@ func init() {
func editConfigRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("editconfig", rtnErr == nil)
}()
configFile := "settings.json" // default
diff --git a/cmd/wsh/cmd/wshcmd-editor.go b/cmd/wsh/cmd/wshcmd-editor.go
index 4968b17509..9459dcc065 100644
--- a/cmd/wsh/cmd/wshcmd-editor.go
+++ b/cmd/wsh/cmd/wshcmd-editor.go
@@ -32,7 +32,6 @@ func init() {
func editorRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("editor", rtnErr == nil)
}()
if len(args) == 0 {
OutputHelpMessage(cmd)
diff --git a/cmd/wsh/cmd/wshcmd-file.go b/cmd/wsh/cmd/wshcmd-file.go
index e40eb324d2..7c668ef2dd 100644
--- a/cmd/wsh/cmd/wshcmd-file.go
+++ b/cmd/wsh/cmd/wshcmd-file.go
@@ -90,7 +90,7 @@ var fileListCmd = &cobra.Command{
Short: "list files",
Long: "List files in a directory. By default, lists files in the current directory." + UriHelpText,
Example: " wsh file ls wsh://user@ec2/home/user/",
- RunE: activityWrap("file", fileListRun),
+ RunE: fileListRun,
PreRunE: preRunSetupRpcClient,
}
@@ -100,7 +100,7 @@ var fileCatCmd = &cobra.Command{
Long: "Display the contents of a file." + UriHelpText,
Example: " wsh file cat wsh://user@ec2/home/user/config.txt",
Args: cobra.ExactArgs(1),
- RunE: activityWrap("file", fileCatRun),
+ RunE: fileCatRun,
PreRunE: preRunSetupRpcClient,
}
@@ -110,7 +110,7 @@ var fileInfoCmd = &cobra.Command{
Long: "Show information about a file." + UriHelpText,
Example: " wsh file info wsh://user@ec2/home/user/config.txt",
Args: cobra.ExactArgs(1),
- RunE: activityWrap("file", fileInfoRun),
+ RunE: fileInfoRun,
PreRunE: preRunSetupRpcClient,
}
@@ -120,7 +120,7 @@ var fileRmCmd = &cobra.Command{
Long: "Remove a file." + UriHelpText,
Example: " wsh file rm wsh://user@ec2/home/user/config.txt",
Args: cobra.ExactArgs(1),
- RunE: activityWrap("file", fileRmRun),
+ RunE: fileRmRun,
PreRunE: preRunSetupRpcClient,
}
@@ -130,7 +130,7 @@ var fileWriteCmd = &cobra.Command{
Long: "Write stdin into a file, buffering input (10MB total file size limit)." + UriHelpText,
Example: " echo 'hello' | wsh file write ./greeting.txt",
Args: cobra.ExactArgs(1),
- RunE: activityWrap("file", fileWriteRun),
+ RunE: fileWriteRun,
PreRunE: preRunSetupRpcClient,
}
@@ -140,7 +140,7 @@ var fileAppendCmd = &cobra.Command{
Long: "Append stdin to a file, buffering input (10MB total file size limit)." + UriHelpText,
Example: " tail -f log.txt | wsh file append ./app.log",
Args: cobra.ExactArgs(1),
- RunE: activityWrap("file", fileAppendRun),
+ RunE: fileAppendRun,
PreRunE: preRunSetupRpcClient,
}
@@ -151,7 +151,7 @@ var fileCpCmd = &cobra.Command{
Long: "Copy files between different storage systems." + UriHelpText,
Example: " wsh file cp wsh://user@ec2/home/user/config.txt ./local-config.txt\n wsh file cp ./local-config.txt wsh://user@ec2/home/user/config.txt",
Args: cobra.ExactArgs(2),
- RunE: activityWrap("file", fileCpRun),
+ RunE: fileCpRun,
PreRunE: preRunSetupRpcClient,
}
@@ -162,7 +162,7 @@ var fileMvCmd = &cobra.Command{
Long: "Move files between different storage systems. The source file will be deleted once the operation completes successfully." + UriHelpText,
Example: " wsh file mv wsh://user@ec2/home/user/config.txt ./local-config.txt\n wsh file mv ./local-config.txt wsh://user@ec2/home/user/config.txt",
Args: cobra.ExactArgs(2),
- RunE: activityWrap("file", fileMvRun),
+ RunE: fileMvRun,
PreRunE: preRunSetupRpcClient,
}
diff --git a/cmd/wsh/cmd/wshcmd-focusblock.go b/cmd/wsh/cmd/wshcmd-focusblock.go
index 3f6603a3e2..ff5f224d9d 100644
--- a/cmd/wsh/cmd/wshcmd-focusblock.go
+++ b/cmd/wsh/cmd/wshcmd-focusblock.go
@@ -26,7 +26,6 @@ func init() {
func focusBlockRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("focusblock", rtnErr == nil)
}()
tabId := os.Getenv("WAVETERM_TABID")
diff --git a/cmd/wsh/cmd/wshcmd-getmeta.go b/cmd/wsh/cmd/wshcmd-getmeta.go
index f5e1e40f67..b713bbef4f 100644
--- a/cmd/wsh/cmd/wshcmd-getmeta.go
+++ b/cmd/wsh/cmd/wshcmd-getmeta.go
@@ -74,7 +74,6 @@ func filterMetaKeys(meta map[string]interface{}, keys []string) map[string]inter
func getMetaRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("getmeta", rtnErr == nil)
}()
fullORef, err := resolveBlockArg()
if err != nil {
diff --git a/cmd/wsh/cmd/wshcmd-getvar.go b/cmd/wsh/cmd/wshcmd-getvar.go
index 9391c4f5f2..1a42be43a3 100644
--- a/cmd/wsh/cmd/wshcmd-getvar.go
+++ b/cmd/wsh/cmd/wshcmd-getvar.go
@@ -53,7 +53,6 @@ func shouldPrintNewline() bool {
func getVarRun(cmd *cobra.Command, args []string) error {
defer func() {
- sendActivity("getvar", WshExitCode == 0)
}()
// Resolve block to get zoneId
diff --git a/cmd/wsh/cmd/wshcmd-launch.go b/cmd/wsh/cmd/wshcmd-launch.go
index 3ec582a6cd..d7d29385e1 100644
--- a/cmd/wsh/cmd/wshcmd-launch.go
+++ b/cmd/wsh/cmd/wshcmd-launch.go
@@ -28,7 +28,6 @@ func init() {
func launchRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("launch", rtnErr == nil)
}()
widgetId := args[0]
diff --git a/cmd/wsh/cmd/wshcmd-notify.go b/cmd/wsh/cmd/wshcmd-notify.go
index de2086e1f7..a73f2b9551 100644
--- a/cmd/wsh/cmd/wshcmd-notify.go
+++ b/cmd/wsh/cmd/wshcmd-notify.go
@@ -31,7 +31,6 @@ func init() {
func notifyRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("notify", rtnErr == nil)
}()
message := args[0]
notificationOptions := &wshrpc.WaveNotificationOptions{
diff --git a/cmd/wsh/cmd/wshcmd-root.go b/cmd/wsh/cmd/wshcmd-root.go
index 9534d2e5f5..a477f4c5a4 100644
--- a/cmd/wsh/cmd/wshcmd-root.go
+++ b/cmd/wsh/cmd/wshcmd-root.go
@@ -103,15 +103,6 @@ func getIsTty() bool {
type RunEFnType = func(*cobra.Command, []string) error
-func activityWrap(activityStr string, origRunE RunEFnType) RunEFnType {
- return func(cmd *cobra.Command, args []string) (rtnErr error) {
- defer func() {
- sendActivity(activityStr, rtnErr == nil)
- }()
- return origRunE(cmd, args)
- }
-}
-
func resolveBlockArg() (*waveobj.ORef, error) {
oref := blockArg
if oref == "" {
@@ -213,23 +204,6 @@ func getTabIdFromEnv() string {
return os.Getenv("WAVETERM_TABID")
}
-// this will send wsh activity to the client running on *your* local machine (it does not contact any wave cloud infrastructure)
-// if you've turned off telemetry in your local client, this data never gets sent to us
-// no parameters or timestamps are sent, as you can see below, it just sends the name of the command (and if there was an error)
-// (e.g. "wsh ai ..." would send "ai")
-// this helps us understand which commands are actually being used so we know where to concentrate our effort
-func sendActivity(wshCmdName string, success bool) {
- if RpcClient == nil || wshCmdName == "" {
- return
- }
- dataMap := make(map[string]int)
- dataMap[wshCmdName] = 1
- if !success {
- dataMap[wshCmdName+"#"+"error"] = 1
- }
- wshclient.WshActivityCommand(RpcClient, dataMap, nil)
-}
-
// Execute executes the root command.
func Execute() {
defer func() {
diff --git a/cmd/wsh/cmd/wshcmd-run.go b/cmd/wsh/cmd/wshcmd-run.go
index 6faf424c99..dffa24f515 100644
--- a/cmd/wsh/cmd/wshcmd-run.go
+++ b/cmd/wsh/cmd/wshcmd-run.go
@@ -40,7 +40,6 @@ func init() {
func runRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("run", rtnErr == nil)
}()
flags := cmd.Flags()
diff --git a/cmd/wsh/cmd/wshcmd-secret.go b/cmd/wsh/cmd/wshcmd-secret.go
index 916e3ae4a5..cbb45c45a9 100644
--- a/cmd/wsh/cmd/wshcmd-secret.go
+++ b/cmd/wsh/cmd/wshcmd-secret.go
@@ -77,7 +77,6 @@ func init() {
func secretGetRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("secret", rtnErr == nil)
}()
name := args[0]
@@ -101,7 +100,6 @@ func secretGetRun(cmd *cobra.Command, args []string) (rtnErr error) {
func secretSetRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("secret", rtnErr == nil)
}()
parts := strings.SplitN(args[0], "=", 2)
@@ -137,7 +135,6 @@ func secretSetRun(cmd *cobra.Command, args []string) (rtnErr error) {
func secretListRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("secret", rtnErr == nil)
}()
names, err := wshclient.GetSecretsNamesCommand(RpcClient, &wshrpc.RpcOpts{Timeout: 2000})
@@ -153,7 +150,6 @@ func secretListRun(cmd *cobra.Command, args []string) (rtnErr error) {
func secretDeleteRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("secret", rtnErr == nil)
}()
name := args[0]
@@ -173,7 +169,6 @@ func secretDeleteRun(cmd *cobra.Command, args []string) (rtnErr error) {
func secretUiRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("secret", rtnErr == nil)
}()
tabId := getTabIdFromEnv()
diff --git a/cmd/wsh/cmd/wshcmd-setbg.go b/cmd/wsh/cmd/wshcmd-setbg.go
index 4385409187..68215a8c17 100644
--- a/cmd/wsh/cmd/wshcmd-setbg.go
+++ b/cmd/wsh/cmd/wshcmd-setbg.go
@@ -90,7 +90,6 @@ func validateColor(color string) error {
func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("setbg", rtnErr == nil)
}()
borderColorChanged := cmd.Flags().Changed("border-color")
diff --git a/cmd/wsh/cmd/wshcmd-setconfig.go b/cmd/wsh/cmd/wshcmd-setconfig.go
index 3fcd1f94b2..6ce1b5297c 100644
--- a/cmd/wsh/cmd/wshcmd-setconfig.go
+++ b/cmd/wsh/cmd/wshcmd-setconfig.go
@@ -25,7 +25,6 @@ func init() {
func setConfigRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("setconfig", rtnErr == nil)
}()
metaSetsStrs := args[:]
diff --git a/cmd/wsh/cmd/wshcmd-setmeta.go b/cmd/wsh/cmd/wshcmd-setmeta.go
index 79faa7e78c..2f2524e46f 100644
--- a/cmd/wsh/cmd/wshcmd-setmeta.go
+++ b/cmd/wsh/cmd/wshcmd-setmeta.go
@@ -158,7 +158,6 @@ func simpleMergeMeta(meta map[string]interface{}, metaUpdate map[string]interfac
func setMetaRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("setmeta", rtnErr == nil)
}()
var jsonMeta map[string]interface{}
if setMetaJsonFilePath != "" {
diff --git a/cmd/wsh/cmd/wshcmd-setvar.go b/cmd/wsh/cmd/wshcmd-setvar.go
index bbfb3e15a1..ea0918fee5 100644
--- a/cmd/wsh/cmd/wshcmd-setvar.go
+++ b/cmd/wsh/cmd/wshcmd-setvar.go
@@ -58,7 +58,6 @@ func parseKeyValue(arg string) (key, value string, err error) {
func setVarRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("setvar", rtnErr == nil)
}()
// Resolve block to get zoneId
diff --git a/cmd/wsh/cmd/wshcmd-ssh.go b/cmd/wsh/cmd/wshcmd-ssh.go
index 4eb1d42a4e..cdcdff319a 100644
--- a/cmd/wsh/cmd/wshcmd-ssh.go
+++ b/cmd/wsh/cmd/wshcmd-ssh.go
@@ -39,7 +39,6 @@ func init() {
func sshRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("ssh", rtnErr == nil)
}()
sshArg := args[0]
diff --git a/cmd/wsh/cmd/wshcmd-tabindicator.go b/cmd/wsh/cmd/wshcmd-tabindicator.go
index c3fa499cf9..56d892b1c3 100644
--- a/cmd/wsh/cmd/wshcmd-tabindicator.go
+++ b/cmd/wsh/cmd/wshcmd-tabindicator.go
@@ -43,7 +43,6 @@ func init() {
func tabIndicatorRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("tabindicator", rtnErr == nil)
}()
fmt.Fprintf(os.Stderr, "tabindicator is deprecated, use 'wsh badge' instead\n")
diff --git a/cmd/wsh/cmd/wshcmd-term.go b/cmd/wsh/cmd/wshcmd-term.go
index f2119ad5b7..6a5f83ab2a 100644
--- a/cmd/wsh/cmd/wshcmd-term.go
+++ b/cmd/wsh/cmd/wshcmd-term.go
@@ -32,7 +32,6 @@ func init() {
func termRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("term", rtnErr == nil)
}()
var cwd string
diff --git a/cmd/wsh/cmd/wshcmd-termscrollback.go b/cmd/wsh/cmd/wshcmd-termscrollback.go
index 6368e1559d..995a07eb0a 100644
--- a/cmd/wsh/cmd/wshcmd-termscrollback.go
+++ b/cmd/wsh/cmd/wshcmd-termscrollback.go
@@ -45,7 +45,6 @@ func init() {
func termScrollbackRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("termscrollback", rtnErr == nil)
}()
// Resolve the block argument
diff --git a/cmd/wsh/cmd/wshcmd-version.go b/cmd/wsh/cmd/wshcmd-version.go
index 80caab9f69..6156ae1e35 100644
--- a/cmd/wsh/cmd/wshcmd-version.go
+++ b/cmd/wsh/cmd/wshcmd-version.go
@@ -11,7 +11,6 @@ import (
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
- "github.com/wavetermdev/waveterm/pkg/wshutil"
)
var versionVerbose bool
@@ -46,19 +45,13 @@ func runVersionCmd(cmd *cobra.Command, args []string) error {
return err
}
- updateChannel, err := wshclient.GetUpdateChannelCommand(RpcClient, &wshrpc.RpcOpts{Timeout: 2000, Route: wshutil.ElectronRoute})
- if err != nil {
- return err
- }
-
if versionJSON {
info := map[string]interface{}{
- "version": resp.Version,
- "clientid": resp.ClientId,
- "buildtime": resp.BuildTime,
- "configdir": resp.ConfigDir,
- "datadir": resp.DataDir,
- "updatechannel": updateChannel,
+ "version": resp.Version,
+ "clientid": resp.ClientId,
+ "buildtime": resp.BuildTime,
+ "configdir": resp.ConfigDir,
+ "datadir": resp.DataDir,
}
outBArr, err := json.MarshalIndent(info, "", " ")
if err != nil {
@@ -73,6 +66,5 @@ func runVersionCmd(cmd *cobra.Command, args []string) error {
fmt.Printf("clientid: %s\n", resp.ClientId)
fmt.Printf("configdir: %s\n", resp.ConfigDir)
fmt.Printf("datadir: %s\n", resp.DataDir)
- fmt.Printf("update-channel: %s\n", updateChannel)
return nil
}
diff --git a/cmd/wsh/cmd/wshcmd-view.go b/cmd/wsh/cmd/wshcmd-view.go
index 1ba84b516f..2347cea407 100644
--- a/cmd/wsh/cmd/wshcmd-view.go
+++ b/cmd/wsh/cmd/wshcmd-view.go
@@ -43,7 +43,6 @@ func init() {
func viewRun(cmd *cobra.Command, args []string) (rtnErr error) {
cmdName := cmd.Name()
defer func() {
- sendActivity(cmdName, rtnErr == nil)
}()
if len(args) == 0 {
OutputHelpMessage(cmd)
diff --git a/cmd/wsh/cmd/wshcmd-wavepath.go b/cmd/wsh/cmd/wshcmd-wavepath.go
index 9a5ad6af39..bdb54d4367 100644
--- a/cmd/wsh/cmd/wshcmd-wavepath.go
+++ b/cmd/wsh/cmd/wshcmd-wavepath.go
@@ -30,7 +30,6 @@ func init() {
func wavepathRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("wavepath", rtnErr == nil)
}()
if len(args) == 0 {
diff --git a/cmd/wsh/cmd/wshcmd-web.go b/cmd/wsh/cmd/wshcmd-web.go
index bfda76b82c..3ffdc93d13 100644
--- a/cmd/wsh/cmd/wshcmd-web.go
+++ b/cmd/wsh/cmd/wshcmd-web.go
@@ -97,7 +97,6 @@ func webGetRun(cmd *cobra.Command, args []string) error {
func webOpenRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("web", rtnErr == nil)
}()
var replaceBlockORef *waveobj.ORef
diff --git a/cmd/wsh/cmd/wshcmd-wsl.go b/cmd/wsh/cmd/wshcmd-wsl.go
index cfe9cd47d8..ee67f7a945 100644
--- a/cmd/wsh/cmd/wshcmd-wsl.go
+++ b/cmd/wsh/cmd/wshcmd-wsl.go
@@ -30,7 +30,6 @@ func init() {
func wslRun(cmd *cobra.Command, args []string) (rtnErr error) {
defer func() {
- sendActivity("wsl", rtnErr == nil)
}()
var err error
diff --git a/db/migrations-wstore/000012_drop_telemetry.down.sql b/db/migrations-wstore/000012_drop_telemetry.down.sql
new file mode 100644
index 0000000000..bcdcbcff58
--- /dev/null
+++ b/db/migrations-wstore/000012_drop_telemetry.down.sql
@@ -0,0 +1 @@
+-- Recreating these tables is not necessary; the data is obsolete
diff --git a/db/migrations-wstore/000012_drop_telemetry.up.sql b/db/migrations-wstore/000012_drop_telemetry.up.sql
new file mode 100644
index 0000000000..2650b693e0
--- /dev/null
+++ b/db/migrations-wstore/000012_drop_telemetry.up.sql
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS db_tevent;
+DROP TABLE IF EXISTS db_activity;
diff --git a/docs/docs/ai-presets.mdx b/docs/docs/ai-presets.mdx
deleted file mode 100644
index 6321dae3ad..0000000000
--- a/docs/docs/ai-presets.mdx
+++ /dev/null
@@ -1,253 +0,0 @@
----
-sidebar_position: 3.6
-id: "ai-presets"
-title: "AI Presets (Deprecated)"
----
-:::warning Deprecation Notice
-The AI Widget and its presets are being replaced by [Wave AI](./waveai.mdx). Please refer to the Wave AI documentation for the latest AI features and configuration options.
-:::
-
-
-
-
-Wave's AI widget can be configured to work with various AI providers and models through presets. Presets allow you to define multiple AI configurations and easily switch between them using the dropdown menu in the AI widget.
-
-## How AI Presets Work
-
-AI presets are defined in `~/.config/waveterm/presets/ai.json`. You can easily edit this file using:
-
-```bash
-wsh editconfig presets/ai.json
-```
-
-Each preset defines a complete set of configuration values for the AI widget. When you select a preset from the dropdown menu, those configuration values are applied to the widget. If no preset is selected, the widget uses the default values from `settings.json`.
-
-Here's a basic example using Claude:
-
-```json
-{
- "ai@claude-sonnet": {
- "display:name": "Claude 3 Sonnet",
- "display:order": 1,
- "ai:*": true,
- "ai:apitype": "anthropic",
- "ai:model": "claude-3-5-sonnet-latest",
- "ai:apitoken": ""
- }
-}
-```
-
-To make a preset your default, add this single line to your `settings.json`:
-
-```json
-{
- "ai:preset": "ai@claude-sonnet"
-}
-```
-
-:::info
-You can quickly set your default preset using the `setconfig` command:
-
-```bash
-wsh setconfig ai:preset=ai@claude-sonnet
-```
-
-This is easier than editing settings.json directly!
-:::
-
-## Provider-Specific Configurations
-
-### Anthropic (Claude)
-
-To use Claude models, create a preset like this:
-
-```json
-{
- "ai@claude-sonnet": {
- "display:name": "Claude 3 Sonnet",
- "display:order": 1,
- "ai:*": true,
- "ai:apitype": "anthropic",
- "ai:model": "claude-3-5-sonnet-latest",
- "ai:apitoken": ""
- }
-}
-```
-
-### OpenAI
-
-To use OpenAI's models:
-
-```json
-{
- "ai@openai-gpt41": {
- "display:name": "GPT-4.1",
- "display:order": 2,
- "ai:*": true,
- "ai:model": "gpt-4.1",
- "ai:apitoken": ""
- }
-}
-```
-
-### Local LLMs (Ollama)
-
-To connect to a local Ollama instance:
-
-```json
-{
- "ai@ollama-llama": {
- "display:name": "Ollama - Llama2",
- "display:order": 3,
- "ai:*": true,
- "ai:baseurl": "http://localhost:11434/v1",
- "ai:name": "llama2",
- "ai:model": "llama2",
- "ai:apitoken": "ollama"
- }
-}
-```
-
-Note: The `ai:apitoken` is required but can be any value as Ollama ignores it. See [Ollama OpenAI compatibility docs](https://github.com/ollama/ollama/blob/main/docs/openai.md) for more details.
-
-### Azure OpenAI
-
-To connect to Azure AI services:
-
-```json
-{
- "ai@azure-gpt4": {
- "display:name": "Azure GPT-4",
- "display:order": 4,
- "ai:*": true,
- "ai:apitype": "azure",
- "ai:baseurl": "",
- "ai:model": "",
- "ai:apitoken": ""
- }
-}
-```
-
-Note: Do not include query parameters or `api-version` in the `ai:baseurl`. The `ai:model` should be your model deployment name in Azure.
-
-### Perplexity
-
-To use Perplexity's models:
-
-```json
-{
- "ai@perplexity-sonar": {
- "display:name": "Perplexity Sonar",
- "display:order": 5,
- "ai:*": true,
- "ai:apitype": "perplexity",
- "ai:model": "llama-3.1-sonar-small-128k-online",
- "ai:apitoken": ""
- }
-}
-```
-
-### Google (Gemini)
-
-To use Google's Gemini models from [Google AI Studio](https://aistudio.google.com):
-
-```json
-{
- "ai@gemini-2.0": {
- "display:name": "Gemini 2.0",
- "display:order": 6,
- "ai:*": true,
- "ai:apitype": "google",
- "ai:model": "gemini-2.0-flash-exp",
- "ai:apitoken": ""
- }
-}
-```
-
-### OpenRouter
-
-To use OpenRouter's models:
-
-```json
-{
- "ai@openrouter": {
- "display:name": "OpenRouter (Qwen)",
- "display:order": 7,
- "ai:*": true,
- "ai:model": "qwen/qwen3-next-80b-a3b-thinking",
- "ai:apitoken": "",
- "ai:baseurl": "https://openrouter.ai/api/v1"
- }
-}
-```
-
-## Multiple Presets Example
-
-You can define multiple presets in your `ai.json` file:
-
-```json
-{
- "ai@claude-sonnet": {
- "display:name": "Claude 3 Sonnet",
- "display:order": 1,
- "ai:*": true,
- "ai:apitype": "anthropic",
- "ai:model": "claude-3-5-sonnet-latest",
- "ai:apitoken": ""
- },
- "ai@openai-gpt41": {
- "display:name": "GPT-4.1",
- "display:order": 2,
- "ai:*": true,
- "ai:model": "gpt-4.1",
- "ai:apitoken": ""
- },
- "ai@ollama-llama": {
- "display:name": "Ollama - Llama2",
- "display:order": 3,
- "ai:*": true,
- "ai:baseurl": "http://localhost:11434/v1",
- "ai:name": "llama2",
- "ai:model": "llama2",
- "ai:apitoken": "ollama"
- },
- "ai@perplexity-sonar": {
- "display:name": "Perplexity Sonar",
- "display:order": 4,
- "ai:*": true,
- "ai:apitype": "perplexity",
- "ai:model": "llama-3.1-sonar-small-128k-online",
- "ai:apitoken": ""
- }
-}
-```
-
-The `display:order` value determines the order in which presets appear in the dropdown menu.
-
-Remember to set your default preset in `settings.json`:
-
-```json
-{
- "ai:preset": "ai@claude-sonnet"
-}
-```
-
-## Using a Proxy
-
-If you need to route AI requests through an HTTP proxy, you can add the `ai:proxyurl` setting to any preset:
-
-```json
-{
- "ai@claude-with-proxy": {
- "display:name": "Claude 3 Sonnet (via Proxy)",
- "display:order": 1,
- "ai:*": true,
- "ai:apitype": "anthropic",
- "ai:model": "claude-3-5-sonnet-latest",
- "ai:apitoken": "",
- "ai:proxyurl": "http://proxy.example.com:8080"
- }
-}
-```
-
-The proxy URL should be in the format `http://host:port` or `https://host:port`. This setting works with all AI providers except Wave Cloud AI (the default).
diff --git a/docs/docs/config.mdx b/docs/docs/config.mdx
index 05389e99ef..63020fb229 100644
--- a/docs/docs/config.mdx
+++ b/docs/docs/config.mdx
@@ -40,22 +40,10 @@ wsh editconfig
| app:showoverlayblocknums | bool | Set to false to disable the Ctrl+Shift block number overlay that appears when holding Ctrl+Shift (defaults to true) |
| app:ctrlvpaste | bool | On Windows/Linux, when null (default) uses Control+V on Windows only. Set to true to force Control+V on all non-macOS platforms, false to disable the accelerator. macOS always uses Command+V regardless of this setting |
| app:confirmquit | bool | Set to false to disable the quit confirmation dialog when closing Wave Terminal (defaults to true, requires app restart) |
-| app:hideaibutton | bool | Set to true to hide the AI button in the tab bar (defaults to false) |
| app:disablectrlshiftarrows | bool | Set to true to disable Ctrl+Shift block-navigation keybindings (`Arrow` and `h/j/k/l`) (defaults to false) |
| app:disablectrlshiftdisplay | bool | Set to true to disable the Ctrl+Shift visual indicator display (defaults to false) |
| app:focusfollowscursor | string | Controls whether block focus follows cursor movement: `"off"` (default), `"on"` (all blocks), or `"term"` (terminal blocks only) |
| app:tabbar | string | Controls the position of the tab bar: `"top"` (default) for a horizontal tab bar at the top of the window, or `"left"` for a vertical tab bar on the left side of the window |
-| ai:preset | string | the default AI preset to use |
-| ai:baseurl | string | Set the AI Base Url (must be OpenAI compatible) |
-| ai:apitoken | string | your AI api token |
-| ai:apitype | string | defaults to "open_ai", but can also set to "azure" (forspecial Azure AI handling), "anthropic", or "perplexity" |
-| ai:name | string | string to display in the Wave AI block header |
-| ai:model | string | model name to pass to API |
-| ai:apiversion | string | for Azure AI only (when apitype is "azure", this will default to "2023-05-15") |
-| ai:orgid | string | |
-| ai:maxtokens | int | max tokens to pass to API |
-| ai:timeoutms | int | timeout (in milliseconds) for AI calls |
-| ai:proxyurl | string | HTTP proxy URL for AI API requests (does not apply to Wave Cloud AI) |
| conn:askbeforewshinstall | bool | set to false to disable popup asking if you want to install wsh extensions on new machines |
| conn:localhostdisplayname | string | override the display name for localhost in the UI (e.g., set to "My Laptop" or "Local", or set to empty string to hide the name) |
| term:fontsize | float | the fontsize for the terminal block |
@@ -115,19 +103,13 @@ wsh editconfig
| window:savelastwindow | bool | when `true`, the last window that is closed is preserved and is reopened the next time the app is launched (defaults to `true`) |
| window:confirmonclose | bool | when `true`, a prompt will ask a user to confirm that they want to close a window if it has an unsaved workspace with more than one tab (defaults to `true`) |
| window:dimensions | string | set the default dimensions for new windows using the format "WIDTHxHEIGHT" (e.g. "1920x1080"). when a new window is created, these dimensions will be automatically applied. The width and height values should be specified in pixels. |
-| telemetry:enabled | bool | set to enable/disable telemetry |
For reference, this is the current default configuration (v0.14.0):
```json
{
- "ai:preset": "ai@global",
- "ai:model": "gpt-5-mini",
- "ai:maxtokens": 4000,
- "ai:timeoutms": 60000,
"app:defaultnewblock": "term",
"app:confirmquit": true,
- "app:hideaibutton": false,
"app:disablectrlshiftarrows": false,
"app:disablectrlshiftdisplay": false,
"app:focusfollowscursor": "off",
@@ -148,17 +130,13 @@ For reference, this is the current default configuration (v0.14.0):
"window:fullscreenonlaunch": false,
"window:magnifiedblockblursecondarypx": 2,
"window:confirmclose": true,
- "window:savelastwindow": true,
- "telemetry:enabled": true,
- "term:bellsound": false,
+ "window:savelastwindow": true, "term:bellsound": false,
"term:bellindicator": false,
"term:osc52": "always",
"term:cursor": "block",
"term:cursorblink": false,
"term:copyonselect": true,
"term:durable": false,
- "waveai:showcloudmodes": true,
- "waveai:defaultmode": "waveai@balanced",
"preview:defaultsort": "name"
}
```
@@ -173,12 +151,12 @@ files as well: `termthemes.json`, `presets.json`, and `widgets.json`.
## Environment Variable Resolution
-To avoid putting secrets directly in config files, Wave supports environment variable resolution using `$ENV:VARIABLE_NAME` or `$ENV:VARIABLE_NAME:fallback` syntax. This works for any string value in any config file (settings.json, presets.json, ai.json, etc.).
+To avoid putting secrets directly in config files, Wave supports environment variable resolution using `$ENV:VARIABLE_NAME` or `$ENV:VARIABLE_NAME:fallback` syntax. This works for any string value in any config file (settings.json, presets.json, etc.).
```json
{
- "ai:apitoken": "$ENV:OPENAI_APIKEY",
- "ai:baseurl": "$ENV:AI_BASEURL:https://api.openai.com/v1"
+ "conn:sshpassword": "$ENV:SSH_PASSWORD",
+ "web:defaulturl": "$ENV:HOME_PAGE:https://example.com"
}
```
diff --git a/docs/docs/connections.mdx b/docs/docs/connections.mdx
index b5b050da6a..24085f865b 100644
--- a/docs/docs/connections.mdx
+++ b/docs/docs/connections.mdx
@@ -105,6 +105,8 @@ At the moment, we are capable of parsing any SSH config file that does not conta
|ProxyJump| Specifies one or more jump proxies in a comma separated list. Each will be visited sequentially using TCP forwarding before connecting to the desired connection (also using TCP forwarding). It can be set to `none` to disable the feature.|
|UserKnownHostsFile| Provides the location of one or more user host key database files for recording trusted remote connections. The filenames are entered in the same string and separated by whitespace. The default value is `"~/.ssh/known_hosts ~/.ssh/known_hosts2"`.|
|GlobalKnownHostsFile| Provides the location of one or more global host key database files for recording trusted remote connections. The filenames are entered in the same string and separated by whitespace. The default value is `"/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2"`.|
+|LocalForward| Can be specified multiple times. Format: `bind_address destination` (e.g., `8080 localhost:80` or `127.0.0.1:8080 localhost:80`). Listens on the local machine and forwards connections through the SSH tunnel to the remote destination.|
+|RemoteForward| Can be specified multiple times. Format: `bind_address destination` (e.g., `9090 localhost:3000`). Listens on the remote machine and forwards connections back to the local destination. Requires `AllowTcpForwarding` on the remote sshd.|
### Example SSH Config Host
@@ -130,6 +132,8 @@ In addition to the regular ssh config file, wave also has its own config file to
| conn:wshpath | A string indicating the path to the `wsh` executable on the connection. It defaults to `"~/.waveterm/bin/wsh"`.|
| conn:shellpath | A string indicating the path to the shell executable on the connection. If not set, the output of `$SHELL` on the connection will be used.|
| conn:ignoresshconfig | This boolean allows wave to ignore the `~/.ssh/config` file for resolving keywords for this connection. The regular defaults will be used, but all changes to those must be specified in the `connections.json` file instead. This defaults to false.|
+| conn:stallautodisconnect | This boolean automatically disconnects SSH connections when they stall (keepalive timeout) for longer than the threshold. Once disconnected, the existing auto-reconnect machinery will attempt to restore the connection and any durable sessions. It defaults to `true`. See [Connection Resilience](/reconnect) for details on how detection and reconnection work.|
+| conn:stalldisconnectthreshold | This int (seconds) controls how long a connection must be stalled before auto-disconnect triggers. It defaults to `30`. See [Connection Resilience](/reconnect) for details. |
| display:hidden | This boolean hides the connection from the dropdown list. It defaults to `false` |
| display:order | This float determines the order of connections in the connection dropdown. It defaults to `0`.|
| term:fontsize | This int can be used to override the terminal font size for blocks using this connection. The block metadata takes priority over this setting. It defaults to null which means the global setting will be used instead. |
@@ -158,6 +162,8 @@ In addition to the regular ssh config file, wave also has its own config file to
| ssh:proxyjump | A list of strings specifying the names of hosts that must be successively visited with tcp forwarding to establish a connection. Can be used to overwrite the value in `~/.ssh/config` or to set it if the ssh config is being ignored.|
| ssh:userknownhostsfile | A list containing the paths of any user host key database files used to keep track of authorized connections. Can be used to overwrite the value in `~/.ssh/config` or to set it if the ssh config is being ignored.|
| ssh:globalknownhostsfile | A list containing the paths of any global host key database files used to keep track of authorized connections. Can be used to overwrite the value in `~/.ssh/config` or to set it if the ssh config is being ignored.|
+| ssh:localforward | A list of strings for local port forwarding rules. Format: `"8080 localhost:80"`. Can be used to override or supplement `~/.ssh/config` values.|
+| ssh:remoteforward | A list of strings for remote port forwarding rules. Format: `"9090 localhost:3000"`. Can be used to override or supplement `~/.ssh/config` values.|
### SSH Agent Detection
@@ -252,6 +258,31 @@ While Wave provides an option disable `wsh` when first connecting to a remote, t
Note that this same line gets added to your `connections.json` file automatically when you choose to disable `wsh` in gui when initially connecting.
+### Port Forwarding
+
+Port forwarding rules from `~/.ssh/config` are automatically applied when you connect through Wave:
+
+```
+Host myserver
+ User username
+ HostName 203.0.113.254
+ LocalForward 8080 localhost:80
+ RemoteForward 9090 localhost:3000
+```
+
+Connecting to `myserver` will listen on local port 8080 (forwarded to the remote's localhost:80) and listen on the remote's port 9090 (forwarded to your local localhost:3000).
+
+Port forwarding can also be defined entirely in `connections.json`:
+
+```json
+{
+ "myusername@myserver" : {
+ "ssh:localforward": ["8080 localhost:80"],
+ "ssh:remoteforward": ["9090 localhost:3000"]
+ }
+}
+```
+
## Managing Connections with the CLI
The `wsh` command gives some commands specifically for interacting with the connections. You can view these [here](/wsh-reference#conn).
diff --git a/docs/docs/durable-sessions.mdx b/docs/docs/durable-sessions.mdx
index fa112ba07d..fa66ac0ab2 100644
--- a/docs/docs/durable-sessions.mdx
+++ b/docs/docs/durable-sessions.mdx
@@ -122,7 +122,7 @@ Converting between standard and durable modes requires restarting the shell. Any
Your terminal is connected to the remote session. You can interact with the shell and see real-time output.
### Detached
-Connection lost, but the session continues running on the remote server. Wave will automatically reconnect when possible. Any commands you ran continue executing.
+Connection lost, but the session continues running on the remote server. Wave will automatically reconnect when possible (see [Connection Resilience](/reconnect) for details on detection and retry timing). Any commands you ran continue executing.
### Awaiting Start
Session configured for durability but not yet started. Click "Start Session" or run a command to begin.
@@ -152,7 +152,7 @@ Start a build, deployment, or data processing job and close your laptop. The com
```
### Unstable Networks
-Work from a café, train, or cellular connection. Brief disconnections won't terminate your session or lose your work.
+Work from a café, train, or cellular connection. Brief disconnections won't terminate your session or lose your work. Wave monitors connections with keepalive probes every 3 seconds and automatically attempts reconnection — see [Connection Resilience](/reconnect) for the full detection and retry strategy.
### Multiple Locations
Start work on your desktop, continue on your laptop. Your session and its state are preserved on the remote server.
diff --git a/docs/docs/faq.mdx b/docs/docs/faq.mdx
index 61dc80beb4..69dc81cb7f 100644
--- a/docs/docs/faq.mdx
+++ b/docs/docs/faq.mdx
@@ -56,15 +56,3 @@ If you've installed via Snap, you can use the following command:
```sh
sudo snap install waveterm --classic --beta
```
-
-## Can I use Wave AI without enabling telemetry?
-
-
-
-Yes! Wave AI is normally disabled when telemetry is not enabled. However, you can enable Wave AI features without telemetry by configuring your own custom AI model (either a local model or using your own API key).
-
-To enable Wave AI without telemetry:
-1. Configure a custom AI mode (see [Wave AI documentation](./waveai-modes))
-2. Set `waveai:defaultmode` to your custom mode's key in your Wave settings
-
-Once you've completed both steps, Wave AI will be enabled and you can use it completely privately without telemetry. This allows you to use local models like Ollama or your own API keys with providers like OpenAI, OpenRouter, or others.
diff --git a/docs/docs/gettingstarted.mdx b/docs/docs/gettingstarted.mdx
index 7ff961a9a9..5775055ef7 100644
--- a/docs/docs/gettingstarted.mdx
+++ b/docs/docs/gettingstarted.mdx
@@ -7,7 +7,7 @@ title: "Getting Started"
import { PlatformProvider, PlatformSelectorButton, PlatformItem } from "@site/src/components/platformcontext";
import { Kbd } from "@site/src/components/kbd";
-Wave Terminal is a modern terminal that includes graphical capabilities like web browsing, file previews, and AI assistance alongside traditional terminal features. This guide will help you get started.
+Wave Terminal is a modern terminal that includes graphical capabilities like web browsing and file previews alongside traditional terminal features. This guide will help you get started.
## Installation
@@ -106,7 +106,6 @@ You can also download installers directly from our [Downloads page](https://www.
- Preview files (images, video, markdown, code with syntax highlighting)
- Browse web pages
- - Ask questions and get AI help directly from the terminal (set up multiple AI models)
- Basic system monitoring graphs
3. **Remote Connections**
@@ -131,8 +130,6 @@ You can also download installers directly from our [Downloads page](https://www.
# Open a webpage
wsh web open github.com
- # Get AI assistance
- wsh ai -m "how do I find large files in my current directory?" -s
```
3. **Customize Your Layout**
@@ -152,7 +149,6 @@ You can also download installers directly from our [Downloads page](https://www.
- Explore [Key Bindings](./keybindings) to work more efficiently
- Learn about [Tab Layouts](./layout) to organize your workspace
- Set up [Custom Widgets](./customwidgets) for quick access to your tools
-- Configure [Wave AI](./waveai) to use your preferred AI models
- Check out [Configuration](./config) for detailed customization options
## Getting Help
diff --git a/docs/docs/index.mdx b/docs/docs/index.mdx
index f1665faae8..b92b270a5e 100644
--- a/docs/docs/index.mdx
+++ b/docs/docs/index.mdx
@@ -10,21 +10,15 @@ import { Card, CardGroup } from "@site/src/components/card";
# Welcome to Wave Terminal
-Wave is an [open-source](https://github.com/wavetermdev/waveterm) terminal that combines traditional terminal features with graphical capabilities like file previews, web browsing, and AI assistance. It runs on MacOS, Linux, and Windows.
+Wave is an [open-source](https://github.com/wavetermdev/waveterm) terminal that combines traditional terminal features with graphical capabilities like file previews and web browsing. It runs on MacOS, Linux, and Windows.
-Modern development involves constantly switching between terminals and browsers - checking documentation, previewing files, monitoring systems, and using AI tools. Wave brings these graphical tools directly into the terminal, letting you control them from the command line. This means you can stay in your terminal workflow while still having access to the visual interfaces you need.
+Modern development involves constantly switching between terminals and browsers - checking documentation, previewing files, and monitoring systems. Wave brings these graphical tools directly into the terminal, letting you control them from the command line. This means you can stay in your terminal workflow while still having access to the visual interfaces you need.
Check out [Getting Started](./gettingstarted) for installation instructions.

-
| Open a new tab |
| | Open a new block (defaults to a terminal block with the same connection and working directory). Switch to launcher using `app:defaultnewblock` setting |
-| | Toggle WaveAI panel visibility |
| | Split horizontally, open a new block to the right |
| | Split vertically, open a new block below |
| | Split vertically, open a new block above |
@@ -41,7 +40,6 @@ Chords are shown with a + between the keys. You have 2 seconds to hit the 2nd ch
| | Open the "connection" switcher |
| | Refocus the current block (useful if the block has lost input focus) |
| | Show block numbers |
-| | Focus WaveAI input |
| | Switch to block number |
| / | Move left, right, up, down between blocks |
| | Replace the current block with a launcher block |
@@ -81,14 +79,6 @@ Chords are shown with a + between the keys. You have 2 seconds to hit the 2nd ch
| | Find in webpage |
| | Open a bookmark |
-## WaveAI Keybindings
-
-| Key | Function |
-| ----------------------- | ----------------------- |
-| | Toggle WaveAI panel |
-| | Focus WaveAI input |
-| | Clear AI Chat |
-
## Terminal Keybindings
| Key | Function |
diff --git a/docs/docs/reconnect.mdx b/docs/docs/reconnect.mdx
new file mode 100644
index 0000000000..de482f754d
--- /dev/null
+++ b/docs/docs/reconnect.mdx
@@ -0,0 +1,225 @@
+---
+sidebar_position: 3.2
+id: "reconnect"
+title: "Connection Resilience"
+---
+
+# Connection Resilience
+
+Wave monitors every remote SSH connection and automatically detects disconnections, stalls, and network changes. When a connection drops, Wave attempts to reconnect so your durable sessions survive network interruptions, Wi-Fi switches, and computer sleep.
+
+## Overview
+
+The reconnect system has three layers:
+
+1. **Monitor** — detects connection problems by sending periodic keepalive probes
+2. **Scheduler** — attempts reconnection with configurable timing
+3. **Events** — triggers that start, stop, or restart the reconnect process
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Monitor │
+│ (keepalive probes, stall detection, auto-disconnect) │
+└──────────────────────────┬──────────────────────────────────┘
+ │ disconnect detected
+ ▼
+┌─────────────────────────────────────────────────────────────┐
+│ Scheduler │
+│ (retry attempts, aggressive mode, max duration) │
+└─────────────────────────────────────────────────────────────┘
+ │
+ ┌───────────────┼───────────────┐
+ ▼ ▼ ▼
+ Wi-Fi switch System resume Manual reconnect
+ (automatic) (automatic) (user-initiated)
+```
+
+## Connection Monitor
+
+The monitor runs as a background goroutine for each active SSH connection. It periodically checks whether the connection is alive by sending SSH keepalive probes.
+
+### How Detection Works
+
+Every **3 seconds**, the monitor checks when the last SSH activity occurred. If more than **3 seconds** have passed with no activity, it sends a keepalive probe (`keepalive@openssh.com`).
+
+If the keepalive probe receives a response, the connection is confirmed alive. If no response arrives within **3 seconds**, the connection is declared **stalled**.
+
+If the stall persists for **5 seconds** (default), the monitor forces a disconnect and triggers the reconnect scheduler.
+
+### Timing Summary
+
+| Parameter | Value | Description |
+|-----------|-------|-------------|
+| Ticker interval | 3s | How often the monitor checks |
+| Keepalive threshold | 3s | Inactivity before sending a keepalive probe |
+| Keepalive threshold (urgent) | 1s | Inactivity before keepalive when user is actively typing |
+| Stall threshold | 3s | Time after keepalive with no response before declaring stalled |
+| Stall threshold (urgent) | 2s | Stall threshold when user is actively typing |
+| Auto-disconnect threshold | 5s | How long a stall persists before forced disconnect |
+
+### Urgent Mode
+
+When the user types in a terminal, the monitor switches to **urgent mode** for 10 seconds. In urgent mode, keepalive probes and stall detection use shorter thresholds (1s and 2s respectively) to detect problems faster. This ensures that if a connection drops while the user is actively typing, the problem is detected within seconds rather than waiting for the normal 3s thresholds.
+
+### Auto-Disconnect
+
+When a stall persists beyond the auto-disconnect threshold (default 5s), the monitor forces the connection to disconnect. This triggers the reconnect scheduler. Without auto-disconnect, a zombie connection (TCP link is dead but the OS hasn't timed out) could block reconnection for 30+ seconds waiting for the OS-level TCP timeout.
+
+Auto-disconnect can be disabled per connection by setting `conn:stallautodisconnect` to `false` in `connections.json`.
+
+## Reconnect Scheduler
+
+When a connection drops, the reconnect scheduler periodically attempts to re-establish it. The scheduler only runs for connections that have **running durable jobs** — if there are no active terminal sessions, there's nothing to reconnect.
+
+### Timing
+
+| Parameter | Value | Description |
+|-----------|-------|-------------|
+| First attempt | Immediate | No delay before the first reconnect attempt |
+| Connect timeout | 5s | How long each attempt waits for the SSH handshake |
+| Normal interval | 5s | Time between attempts in normal mode |
+| Aggressive interval | 3s | Time between attempts in aggressive mode |
+| Aggressive duration | 2min | How long aggressive mode lasts before reverting to normal |
+| Max duration | 5min | Total scheduler lifetime before giving up |
+
+### Aggressive Mode
+
+When a reconnect attempt fails with a network-unreachable error (e.g., "no route to host", "network is unreachable", "context deadline exceeded"), the scheduler switches to **aggressive mode**. In aggressive mode, attempts happen every 3 seconds instead of every 5 seconds, catching the moment the network returns more quickly.
+
+Aggressive mode lasts for 2 minutes. Each time a network-unreachable error occurs, the 2-minute window resets. After 2 minutes of no network errors, the scheduler returns to normal mode.
+
+### Scheduler Lifecycle
+
+The scheduler runs for a maximum of **5 minutes**. After 5 minutes, it stops entirely. The connection remains disconnected until a new event triggers reconnection (e.g., system resume, manual reconnect).
+
+```
+0s ── scheduler starts, attempt 1 (immediate) → fails
+0s ── aggressive mode ON (3s interval)
+3s ── attempt 2 → fails
+6s ── attempt 3 → fails
+...
+120s ── aggressive window expires → normal mode (5s interval)
+125s ── attempt → fails
+130s ── attempt → fails
+...
+300s ── 5 minute max → scheduler stops
+```
+
+### Known Limitation: Scheduler Does Not Restart
+
+After the scheduler stops (5 minutes), the connection stays disconnected indefinitely until a new event triggers reconnection. If the network is down for longer than 5 minutes (e.g., server maintenance, extended outage), the user must manually reconnect or wait for a system resume event.
+
+## Events That Trigger Reconnection
+
+### Automatic Events
+
+| Event | Source | What Happens |
+|-------|--------|-------------|
+| Connection drops | Monitor detects stall | Scheduler starts for connections with durable jobs |
+| System resume | macOS sleep/wake | Forces disconnect + immediate reconnect attempt |
+| Route down | Network route lost | Job-level auto-reconnect (1s delay, 30s cooldown) |
+
+### User-Initiated Events
+
+| Event | Source | What Happens |
+|-------|--------|-------------|
+| Click Reconnect button | UI overlay | Immediate `AttemptReconnect` call |
+| Focus terminal tab | UI interaction | Connection status checked, reconnect if needed |
+| `wsh ssh` command | CLI | Establishes or reconnects the connection |
+
+### Event Flow
+
+```
+Connection drops
+ │
+ ▼
+handleConnChangeEvent (wps.Event_ConnChange)
+ │
+ ├── Connected=false → onConnectionDown
+ │ │
+ │ ├── needsInteractiveAuth? → skip (user must reconnect manually)
+ │ │
+ │ └── start scheduleConnectionReconnect
+ │ │
+ │ ├── attempt reconnect (5s timeout)
+ │ │ │
+ │ │ ├── success → stop
+ │ │ └── failure → retry after interval
+ │ │
+ │ └── max duration (5min) → stop
+ │
+ └── Connected=true → onConnectionUp
+ │
+ └── reconnect all running durable jobs for this connection
+```
+
+## Interactive Authentication Handling
+
+Some SSH connections require password or keyboard-interactive authentication. The reconnect scheduler **cannot** handle these automatically because it cannot type a password for the user.
+
+### Detection
+
+Before starting the scheduler, Wave checks if the connection might need interactive authentication by examining the SSH config:
+
+- `ssh:passwordauthentication` — enables password auth (default: true)
+- `ssh:kbdinteractiveauthentication` — enables keyboard-interactive auth (default: true)
+- `ssh:batchmode` — disables all interactive prompts (default: false)
+- `ssh:passwordsecretname` — stored password in secret store (no prompt needed)
+- `ssh:preferredauthentications` — ordered list of auth methods to try
+
+If interactive auth is possible and no stored password exists, the scheduler is **skipped entirely**. The user must reconnect manually (which will prompt for the password).
+
+### Stored Passwords
+
+If `ssh:passwordsecretname` is set, the password is retrieved from the secret store automatically. No user prompt is needed, and the scheduler runs normally.
+
+## Configuration
+
+### Existing Config Fields
+
+These fields can be set per connection in `connections.json`:
+
+| Field | Default | Description |
+|-------|---------|-------------|
+| `conn:stallautodisconnect` | `true` | Enable auto-disconnect on stall |
+| `conn:stalldisconnectthreshold` | `5` | Seconds of stall before forced disconnect |
+
+### Example
+
+```json
+{
+ "connections": {
+ "user@high-latency-server": {
+ "conn:stallautodisconnect": true,
+ "conn:stalldisconnectthreshold": 30
+ }
+ }
+}
+```
+
+:::note
+All other reconnect thresholds (keepalive interval, stall threshold, reconnect interval, etc.) are currently hardcoded. Issue [#19](https://github.com/whoisjeremylam/waveterm-remote/issues/19) will make them configurable per connection.
+:::
+
+## Connection Health States
+
+| State | Description |
+|-------|-------------|
+| `good` | Connection is healthy, keepalive responses received |
+| `stalled` | Keepalive sent, no response received within threshold |
+
+The `stalled` state indicates the connection may be dead. If stall persists beyond the auto-disconnect threshold, the connection is forced to disconnect.
+
+## Troubleshooting
+
+### Connection takes 30+ seconds to reconnect
+
+Check if the connection uses password authentication without a stored secret. If `ssh:passwordauthentication` is true and `ssh:passwordsecretname` is not set, the scheduler is skipped. Store the password in the secret store or set `ssh:batchmode` to true.
+
+### Connection disconnects too aggressively
+
+If the remote server has high latency (satellite, international), the 5-second auto-disconnect threshold may be too short. Increase `conn:stalldisconnectthreshold` in `connections.json`.
+
+### Scheduler stops after 5 minutes
+
+This is the current max duration. After 5 minutes, the scheduler stops and the connection stays disconnected until manually reconnected or until a system resume event occurs. This limitation will be addressed in a future update.
\ No newline at end of file
diff --git a/docs/docs/releasenotes.mdx b/docs/docs/releasenotes.mdx
index 987be81534..e3ccce8211 100644
--- a/docs/docs/releasenotes.mdx
+++ b/docs/docs/releasenotes.mdx
@@ -6,6 +6,15 @@ sidebar_position: 200
# Release Notes
+### v0.14.5-fork — Jun 4, 2026
+
+**SSH Port Forwarding:**
+
+- **`LocalForward` and `RemoteForward` support** — Port forwarding rules from `~/.ssh/config` are now automatically applied when connecting through Wave. Rules can also be defined in `connections.json` via `ssh:localforward` and `ssh:remoteforward` keys.
+- `LocalForward` listens on the local machine and forwards connections through the SSH tunnel to the remote destination.
+- `RemoteForward` listens on the remote machine and forwards connections back to the local destination (requires `AllowTcpForwarding` on the remote sshd).
+- Malformed rules are logged and skipped without breaking the connection.
+
### v0.14.5 — Apr 16, 2026
Wave v0.14.5 introduces a new Process Viewer widget, Quake Mode for global hotkey, and several quality-of-life improvements.
@@ -40,8 +49,7 @@ Wave v0.14.4 introduces vertical tabs, upgrades to xterm.js v6, and includes a c
- **Config Errors Moved** - Config errors removed from the tab bar and moved to Settings / WaveConfig view for less clutter
- **Warn on Unsaved Changes** - WaveConfig view now warns before discarding unsaved changes
- **Stream Performance** - Migrated file streaming to new modern interface with flow control, fixing a large time-to-first-byte streaming bug
-- **macOS First Click** - Improved first-click handling on macOS (cancel the click but properly set block/WaveAI focus)
-- Deprecated legacy AI widget has been removed
+- **macOS First Click** - Improved first-click handling on macOS (cancel the click but properly set block focus)
- [bugfix] Fixed focus bug for newly created blocks
- [bugfix] Fixed an issue around starting a new durable session by splitting an old one
- Electron upgraded to v41
@@ -65,8 +73,6 @@ Wave v0.14.2 adds block/tab badges, directory preview improvements, and assorted
- [bugfix] Fixed "Save Session As..." (focused window tracking bug)
- [bugfix] Zoom change notifications were not being properly sent to all tabs (layout inconsistencies)
- Added a Release Notes link in the settings menu
-- Working on anthropic-messages Wave AI backend (for native Claude integration)
-- Lots of internal work on testing/mock infrastructure to enable quicker async AI edits
- Documention updates
- Package updates and dependency upgrades
@@ -89,7 +95,6 @@ Wave v0.14.1 fixes several high-impact terminal bugs (Claude Code scrolling, IME
- **Tab Close Confirmation** - New `tab:confirmclose` setting to prompt before closing a tab
- **Workspace-Scoped Widgets** - New optional `workspaces` field in `widgets.json` to show/hide widgets per-workspace
- **Vim-Style Block Navigation** - Added Ctrl+Shift+H/J/K/L to navigate between blocks
-- **New AI Providers** - Added Groq and NanoGPT as built-in AI provider presets
**Other Changes:**
@@ -121,20 +126,12 @@ Wave v0.14 introduces Durable Sessions for SSH connections, allowing your remote
- **Enhanced Context Menu** - Right-click terminals for quick access to splits, URL opening, themes, file browser, and more
- **Streamlined Header Layout** - Terminal headers now focus on connection info without redundant view type labels
-**Wave AI Updates:**
-- **Image/Vision Support** - Added image support for OpenAI chat completions API, enabling vision capabilities with compatible models
-- **Stop Generation** - New ability to stop AI responses mid-generation across OpenAI and Gemini backends
-- **AI Panel Scroll Latch** - Improved auto-scrolling behavior in Wave AI panel
-- **Configurable Verbosity** - Control verbosity levels for OpenAI Responses API
-- Deprecated old AI-widget proxy endpoint
-
**RPC and Performance:**
- **RPC Streaming with Flow Control** - New streaming primitives with built-in flow control for better performance and reliability
- **WSH Router Refactor** - Major routing architecture improvements to prevent hangs on connection interruptions
- **RPC Client/Server Cleanup** - Improved RPC implementation and error handling
**Configuration Updates:**
-- **Hide AI Button** - New `app:hideaibutton` setting to hide the AI button from the UI
- **Disable Ctrl+Shift Arrows** - New `app:disablectrlshiftarrows` setting for keyboard shortcut conflicts
- **Disable Ctrl+Shift Display** - New `app:disablectrlshiftdisplay` setting to disable overlay block numbers
@@ -149,550 +146,11 @@ Wave v0.14 introduces Durable Sessions for SSH connections, allowing your remote
- Removed OSC 23198 and OSC 9283 legacy handlers
- Updated contribution guidelines
- Upgraded Go toolchain to 1.25.6
-- Enhanced OpenAI-compatible API provider documentation
- [bugfix] Fixed empty data handling in sysinfo view
- [bugfix] Fixed `app:ctrlvpaste` setting on Windows (can now be disabled)
-- [bugfix] Fixed duplicated Wave AI system prompt for some providers
- [bugfix] Fixed disconnect hanging issue - disconnects now happen immediately
- [bugfix] Fixed tool approval lifecycle to match SSE connection timing
- [bugfix] Increased WSL connection timeout to handle slow initial WSL startup
- [bugfix] Improved terminal shutdown with SIGHUP for graceful shell exit
- Package updates and dependency upgrades
-### v0.13.1 — Dec 16, 2025
-
-**Windows Improvements and Wave AI Enhancements**
-
-This release focuses on significant Windows platform improvements, Wave AI visual updates, and better flexibility for local AI usage.
-
-**Windows Platform Enhancements:**
-- **Integrated Window Layout** - Removed separate title bar and menu bar on Windows, integrating controls directly into the tab-bar header for a cleaner, more unified interface
-- **Git Bash Auto-Detection** - Wave now automatically detects Git Bash installations and adds them to the connection dropdown for easy access
-- **SSH Agent Fallback** - Improved SSH agent support with automatic fallback to `\\.\pipe\openssh-ssh-agent` on Windows
-- **Updated Focus Keybinding** - Wave AI focus key changed to Alt:0 on Windows for better consistency
-- **Config Schemas** - Improved configuration validation and schema support
-- Ctrl-V now works as standard paste in terminal on Windows
-
-**Wave AI Updates:**
-- **Refreshed Visual Design** - Complete UI refresh removing blue accents and adding transparency support for better integration with custom backgrounds
-- **BYOK Without Telemetry** - Wave AI now works with bring-your-own-key and local models without requiring telemetry to be enabled
-- [bugfix] Fixed tool type "function" compatibility with providers like Mistral
-
-**Terminal Improvements:**
-- **New Scrolling Keybindings** - Added Shift+Home, Shift+End, Shift+PageUp, and Shift+PageDown for better terminal navigation
-
-**Other Changes:**
-- Package updates and dependency upgrades
-
-### v0.13.0 — Dec 8, 2025
-
-**Wave v0.13 Brings Local AI Support, BYOK, and Unified Configuration**
-
-Wave v0.13 is a major release that opens up Wave AI to local models, third-party providers, and bring-your-own-key (BYOK) configurations. This release also includes a completely redesigned configuration system and several terminal improvements.
-
-**Local AI & BYOK Support:**
-- **OpenAI-Compatible API** - Wave now supports any provider or local server using the `/v1/chat/completions` endpoint, enabling use of Ollama, LM Studio, vLLM, OpenRouter, and countless other local and hosted models
-- **Google Gemini Integration** - Native support for Google's Gemini models with a dedicated API adapter
-- **Provider Presets** - Simplified configuration with built-in presets for OpenAI, OpenRouter, Google, Azure, and custom endpoints
-- **Multiple AI Modes** - Easily switch between different models and providers with a unified interface
-- See the new [Wave AI Modes documentation](https://docs.waveterm.dev/waveai-modes) for configuration examples and setup guides
-
-**Unified Configuration Widget:**
-- **New Config Interface** - Replaced the basic JSON editor with a dedicated configuration widget accessible from the sidebar
-- **Better Organization** - Browse and edit different configuration types (general settings, AI modes, secrets) with improved validation and error handling
-- **Integrated Secrets Management** - Access Wave's secret store directly from the config widget for secure credential management
-
-**Terminal Improvements:**
-- **Bracketed Paste Mode** - Now enabled by default to improve multi-line paste behavior and compatibility with tools like Claude Code
-- **Windows Paste Fix** - Ctrl+V now works as a standard paste accelerator on Windows
-- **SSH Password Management** - Store SSH connection passwords in Wave's secret store to avoid re-typing credentials
-
-**Other Changes:**
-- Package updates and dependency upgrades
-- Various bug fixes and stability improvements
-
-### v0.12.5 — Nov 24, 2025
-
-Quick patch release to fix paste behavior on Linux (prevent raw HTML from getting pasted to the terminal).
-
-### v0.12.4 — Nov 21, 2025
-
-Quick patch release with bug fixes and minor improvements.
-
-- New `term:macoptionismeta` setting for macOS to treat Option key as Meta key in terminal
-- Fixed directory tracking for zsh shells
-- Fixed editor copy operations
-- Minor Wave AI improvements (image handling, scrolling, focus)
-- Package updates and dependency upgrades
-- WIP: WaveApps builder framework (not yet released)
-
-### v0.12.3 — Nov 17, 2025
-
-Patch release with Wave AI model upgrade, new secret management features, and improved terminal input handling.
-
-**Wave AI Updates:**
-- **GPT-5.1 Model** - Upgraded to use OpenAI's GPT-5.1 model for improved responses
-- **Thinking Mode Toggle** - New dropdown to select between Quick, Balanced, and Deep thinking modes for optimal response quality vs speed
-- [bugfix] Fixed path mismatch issue when restoring AI write file backups
-
-**New Features:**
-- **Secret Store** - New secret management widget for storing and managing sensitive credentials. Access secrets via CLI with `wsh secret list/get/set` commands
-
-**Terminal Improvements:**
-- **Enhanced Input Handling** - Better support for interactive CLI tools like Claude Code. Shift+Enter now inserts newlines by default for multi-line commands
-- **Image Paste Support** - Paste images directly into terminal (saved to temp files with path inserted). Works great in Claude Code!
-- **IME Fix** - Fixed duplicate text issue when switching input methods during Chinese/Japanese/Korean composition
-
-**Other Changes:**
-- Improved backend panic tracking for better debugging
-- Fixed memory leak around sysinfo events
-- WIP: New WaveApps builder framework (not yet released)
-- Package updates and dependency bumps
-
-### v0.12.2 — Nov 4, 2025
-
-Wave v0.12.2 adds file editing ability to Wave AI. Before approving a file edit you can easily see a diff (rendered in the Monaco Editor diff viewer), and after approving an edit you can easily roll back the change using a "Revert File" button.
-
-**Wave AI Updates:**
-- **File Write Tool** - Wave AI can now create and modify files with your approval
-- **Visual Diff Preview** - See exactly what will change before approving edits, rendered in Monaco Editor
-- **Easy Rollback** - Revert file changes with a simple "Revert File" button
-- **Drag & Drop Files** - Drag files from the preview viewer directly to Wave AI
-- **Directory Listings** - `wsh ai` can now attach directory listings to chats
-- **Adjustable Settings** - Control thinking level and max output tokens per chat
-
-**Bug Fixes & Improvements:**
-- Fixed a significant memory leak in the RPC system
-- Schema validation working again for config files
-- Improved tool descriptions and input validations (run before tool approvals)
-- Fixed issue with premature tool timeouts
-- Fixed regression with PowerShell 5.x
-- Fixed prompt caching issue when attaching files
-
-### v0.12.1 — Oct 20, 2025
-
-Patch release focused on shell integration improvements and Wave AI enhancements. This release fixes syntax highlighting in the code editor and adds significant shell context tracking capabilities.
-
-**Shell Integration & Context:**
-- **OSC 7 Support** - Added OSC 7 (current working directory) support across bash, zsh, fish, and pwsh shells. Wave now automatically tracks and restores your current directory across restarts for both local and remote terminals.
-- **Shell Context Tracking** - Implemented shell integration for bash, zsh, and fish shells. Wave now tracks when your shell is ready to receive commands, the last command executed, and exit codes. This enhanced context enables better terminal management and lays the groundwork for Wave AI to write and execute commands intelligently.
-
-**Wave AI Improvements:**
-- Display reasoning summaries in the UI while waiting for AI responses
-- Added enhanced terminal context - Wave AI now has access to shell state including current directory, command history, and exit codes
-- Added feedback buttons (thumbs up/down) for AI responses to help improve the experience
-- Added copy button to easily copy AI responses to clipboard
-
-**Other Changes:**
-- Mobile user agent emulation support for web widgets [#2442](https://github.com/wavetermdev/waveterm/issues/2442)
-- [bugfix] Fixed padding for header buttons in code editor (Tailwind regression)
-- [bugfix] Restored syntax highlighting in code editor preview blocks [#2427](https://github.com/wavetermdev/waveterm/issues/2427)
-- Package updates and dependency bumps
-
-### v0.12.0 — Oct 16, 2025
-
-**Wave v0.12 Has Arrived with Wave AI (beta)!**
-
-Wave Terminal v0.12.0 introduces a completely redesigned AI experience powered by OpenAI GPT-5. This represents a major upgrade and modernization over Wave's previous AI integration, bringing multi-modal support, advanced tool integration, and an intuitive new interface. The main AI PR alone included 128 commits and added 13,000+ lines of code.
-
-**Wave AI Features:**
-- **New Slide-Out Chat Panel** - Access Wave AI via hotkeys (Cmd-Shift-A or Ctrl-Shift-0) from the left side of your screen
-- **Multi-Modal Input** - Support for images, PDFs, and text file attachments
-- **Drag & Drop Files** - Simply drag files into the chat to attach them
-- **Command-Line Integration** - Send files and command output directly to Wave AI using `wsh ai`
-- **Smart Context Switching** - Enable Wave AI to see into your widgets and file system
-- **Built-in Tools:**
- - Web search capabilities
- - Local file and directory operations
- - Widget screenshots
- - Terminal scrollback access
- - Web navigation
-
-Wave AI is in active beta with included AI credits while we refine the experience. BYOK (Bring Your Own Key) will be available once we've stabilized core features and gathered feedback on what works best. Share your feedback in our [Discord](https://discord.gg/XfvZ334gwU).
-
-For more information and upcoming features, visit our [Wave AI documentation](https://docs.waveterm.dev/waveai).
-
-**Other Improvements:**
-- New onboarding flow showcasing block magnification, Wave AI, and wsh view/edit capabilities
-- New `wsh blocks list` command for listing and filtering blocks by workspace, tab, or view type
-- Continued migration from SCSS to Tailwind v4
-- Package upgrades and dependency updates
-- Internal code cleanup and refactoring
-
-### v0.11.6 — Sep 22, 2025
-
-Patch release to address an editor bug when you open two files in separate edit widgets. Also adds Mermaid support to markdown blocks.
-
-* WIP: Big AI overhaul coming (multi-modal support, premium models, and tool support)
-* WIP: Integrating new Tsunami widget framework to make writing and running Wave widgets easier
-* Lots of package updates
-* Much internal cleanup (preview widget)
-* More migration to Tailwind v4 CSS
-* Build updates, switched to npm from yarn
-
-### v0.11.5 — Aug 28, 2025
-
-Another housekeeping release to modernize Wave and bring it more up to date.
-
-* Wave AI Cloud Proxy now uses gpt-5-mini (upgraded from gpt-4o-mini)
-* Fixed JWT issue with running "Wave Apps" from widgets
-* Added an "$ENV:envvar:fallback" syntax to the config files to allow Wave's config to pick up values from the environment (mostly to allow moving secrets out of the config files)
-* New setting to disable showing overlay blocknums when holding Ctrl:Shift (`app:showoverlayblocknums`)
-* New setting to allow Shift-Enter to work with tools like Claude Code (`term:shiftenternewline`)
-* Upgraded frontend to React 19
-* Migrated more of the frontend to Tailwind v4 (work in progress)
-* Removed Universal MacOS build. 90% of Mac users are now on Apple Silicon, so universal build is less important (has a larger file size, and complicates the build process).
-* [bugfix] Removed build-ids in RPM build to try to fix conflicts with Slack
-* Removed some Wave v7 aware upgrades and old code paths
-* Internal cleanup, TypeScript errors, linting fixes, etc.
-* Other assorted Go/npm package bumps
-
-### v0.11.4 — Aug 19, 2025
-
-Quick patch release to update packages, fix some security issues (with dependent packages), and some small bug fixes.
-
-* Update AI Libraries, GPT-5 now supported in WaveAI
-* Added `ai:proxyurl` setting to allow proxy access (e.g. SOCKS) for AI access
-
-### v0.11.3 — May 2, 2025
-
-Quick patch release to update packages, fix some security issues (with dependent packages), and some small bug fixes.
-
-### v0.11.2 — March 8, 2025
-
-Quick patch release to fix a backend panic, and revert a change that caused WSL connections to hang.
-
-### v0.11.1 — Feb 28, 2025
-
-Wave Terminal v0.11.1 adds a lot of new functionality over v0.11.0 (it could have almost been a v0.12)!
-
-The headline feature is our files/preview widget now supports browsing S3 buckets. We read credential information directly from your ~/.aws/config, and you can now easily select any of your AWS profiles in our connections drop down to start viewing S3 files. We even support editing S3 text files using our built-in editor.
-
-Lots of other features and bug fixes as well:
-
-- **S3 Bucket** directory viewing and file previews
-- **Drag and Drop Files and Directories** between Wave directory views. This works across machines and between remote machines and S3 conections.
-- Added json-schema support for some of our config files. You'll now get auto-complete popups for fields in our settings.json, widgets.json, ai.json, and connections.json file.
-- New block splitting support -- Use Cmd-D and Cmd-Shift-D to split horizontally and vertically. For more control you can use Ctrl-Shift-S and then Up/Down/Left/Right to split in the given direction.
-- Delete block (without removing it from the layout). You can use Ctrl-Shift-D to remove a block, while keeping it in the layout. you can then launch a new widget in its place.
-- `wsh file` now supports copying files between your local machine, remote machines, and to/from S3
-- New analytics framework (event based as opposed to counter based). See Telemetry Docs for more information.
-- Web bookmarks! Edit in your bookmarks.json file, can open them in the web widget using Cmd+O
-- Edits to your ai.json presets file will now take effect _immediately_ in AI widgets
-- Much better error handling and messaging when errors occur in the preview or editor widget
-- `wsh ssh --new` added to open the new ssh connection in a new widget
-- new `wsh launch` command to open any custom widget defined in widget.json
-- When using terminal multi-input (Ctrl-Shift-I), pasting text will now be sent to all terminals
-- [bugfix] Fix some hanging goroutines when commands failed or timed out
-- [bugfix] Fix some file extension mimetypes to enable the editor for more file types
-- [bugfix] Hitting "tab" would sometimes scroll a widget off screen making it unusable
-- [bugfix] XDG variables will no longer leak to terminal widgets
-- Added tailwind CSS and shadcn support to help build new widgets faster
-- Better internal widget abstractions
-
-### v0.11.0 — Jan 24, 2025
-
-Wave Terminal v0.11.0 includes a major rewrite of our connections infrastructure, with changes to both our backend and remote file protocol systems, alongside numerous features, bug fixes, and stability improvements.
-
-A key addition in this release is the new shell initialization system, which enables customization of your shell environment across local and remote connections. You can now configure environment variables and shell-specific init scripts on both a per-block and per-connection basis.
-
-For day-to-day use, we've added search functionality across both terminal and web blocks, along with a terminal multi-input feature for simultaneous input to all terminals within a tab. We've also added support for Google Gemini to Wave AI, expanding our suite of AI integrations.
-
-Behind the scenes, we've redesigned our remote file protocol, laying the groundwork for upcoming S3 (and S3-compatible system) support in our preview widget. This architectural change sets the stage for adding more file backends in the future.
-
-- **Shell Environment Customization** -- Configure your shell environment using environment variables and init scripts, with support for both local and remote connections
-- **Connection Backend Improvements** -- Major rewrite with improved shell detection, better error logging, and reduced 2FA prompts when using ForceCommand
-- **Multi-Shell Support** -- Enhanced support for bash, zsh, pwsh, and fish shells, with shell-specific initialization capabilities
-- **Terminal Search** -- use Cmd-F to search for text in terminal widgets
-- **Web Search** -- use Cmd-F to search for text in web views
-- **Terminal Multi-Input** -- Use Ctrl-Shift-I to allow multi-input to all terminals in the same tab
-- **Wave AI now supports Google Gemini**
-- Improved WSL support with wsh-free connection options
-- Added inline connection debugging information
-- Fixed file permission handling issues on Windows systems
-- Connection related popups are now delivered only to the initiating window
-- Improved timeout handling for SSH connections which require 2FA prompts
-- Fixed escape key handling in global event handlers (closing modals)
-- Directory preview now fills the entire block width
-- Custom widgets can now be launched in magnified mode
-- Various workspace UX improvements around closing/deleting
-- file:/// urls now work in web widget
-- Increased max size of files allowed in `wsh ai` to 50k
-- Increased maximum allowed term:scrollback to 50k lines
-- Allow connections to entirely be defined in connections.json without relying on ~/.ssh/config
-- Added an option to reveal files in external file viewer for local connection
-- Added a New Window option when right clicking the MacOS dock icon button
-- [build] Switched to free Ubuntu ARM runners for better ARM64 build support
-- [build] Windows builds now use zig, simplifying Windows dev setup
-- [bugfix] Connections dropdown now populated even when ssh config is missing or invalid
-- [bugfix] Disabled bracketed paste mode by default (configuration option to turn it back on)
-- [bugfix] Timeout for `wsh ssh` increased to 60s
-- [bugfix] Fix for sysinfo widget when displaying a huge number of CPU graphs
-- [bugfix] Fixes XDG variables for Snap installs
-- [bugfix] Honor SSH IdentitiesOnly flag (useful when many keys are loaded into ssh-agent)
-- [bugfix] Better shell environment variable setup when running local shells
-- [bugfix] Fix preview for large text files
-- [bugfix] Fix URLs in terminal (now clickable again)
-- [bugfix] Windows URLs now work properly for Wave background images
-- [bugfix] Connections launch without wsh if the unix domain socket can't be opened
-- [bugfix] Connection status list lights up correctly with currently connected connections
-- [bugfix] Use en_US.UTF-8 if the requested LANG is not available in your terminal
-- Other bug fixes, performance improvements, and dependency updates
-
-### v0.10.4 — Dec 20, 2024
-
-Quick update with bug fixes and new configuration options
-
-- Added "window:confirmclose" and "window:savelastwindow" configuration options
-- [bugfix] Fixed broken scroll bar in the AI widget
-- [bugfix] Fixed default path for wsh shell detection (used in remote connections)
-- Dependency updates
-
-### v0.10.3 — Dec 19, 2024
-
-Quick update to v0.10 with new features and bug fixes.
-
-- Global hotkey support [docs](https://docs.waveterm.dev/config#customizable-systemwide-global-hotkey)
-- Added configuration to override the font size for markdown, AI-chat, and preview editor [docs](https://docs.waveterm.dev/config)
-- Added ability to set independent zoom level for the web view (right click block header)
-- New `wsh wavepath` command to open the config directory, data directory, and log file
-- [bugfix] Fixed crash when /etc/sshd_config contained an unsupported Match directive (most common on Fedora)
-- [bugfix] Workspaces are now more consistent across windows, closes associated window when Workspaces are deleted
-- [bugfix] Fixed zsh on WSL
-- [bugfix] Fixed long-standing bug around control sequences sometimes showing up in terminal output when switching tabs
-- Lots of new examples in the docs for shell overrides, presets, widgets, and connections
-- Other bug fixes and UI updates
-
-(note, v0.10.2 and v0.10.3's release notes have been merged together)
-
-### v0.10.1 — Dec 12, 2024
-
-Quick update to fix the workspace app menu actions. Also fixes workspace switching to always open a new window when invoked from a non-workspace window. This reduces the chance of losing a non-workspace window's tabs accidentally.
-
-### v0.10.0 — Dec 11, 2024
-
-Wave Terminal v0.10.0 introduces workspaces, making it easier to manage multiple work environments. We've added powerful new command execution capabilities with `wsh run`, allowing you to launch and control commands in dedicated blocks. This release also brings significant improvements to SSH with a new connections configuration system for managing your remote environments.
-
-- **Workspaces**: Organize your work into separate environments, each with their own tabs, layouts, and settings
-- **Command Blocks**: New `wsh run` command for launching terminal commands in dedicated blocks, with support for magnification, auto-closing, and execution control ([docs](https://docs.waveterm.dev/wsh-reference#run))
-- **Connections**: New configuration system for managing SSH connections, with support for wsh-free operation, per-connection themes, and more ([docs](https://docs.waveterm.dev/connections))
-- Improved tab management with better switching behavior and context menus (many bug fixes)
-- New tab features including pinned tabs and drag-and-drop improvements
-- Create, rename, and delete files/directories directly in directory preview
-- Attempt wsh-free connection as a fallback if wsh installation or execution fails
-- New `-i` flag to add identity files with the `wsh ssh` command
-- Added Perplexity API integration ([docs](https://docs.waveterm.dev/faq#perplexity))
-- `wsh setbg` command for background handling ([docs](https://docs.waveterm.dev/wsh-reference#setbg))
-- Switched from Less to SCSS for styling
-- [bugfix] Fixed tab flickering issues during tab switches
-- [bugfix] Corrected WaveAI text area resize behavior
-- [bugfix] Fixed concurrent block controller start issues
-- [bugfix] Fixed Preview Blocks for uninitialized connections
-- [bugfix] Fixed unresponsive context menus
-- [bugfix] Fixed connection errors in Help block
-- Upgraded Go toolchain to 1.23.4
-- Lots of new documentation, including new pages for [Getting Started](https://docs.waveterm.dev/gettingstarted), [AI Presets](https://docs.waveterm.dev/ai-presets), and [wsh overview](https://docs.waveterm.dev/wsh).
-- Other bug fixes, performance improvements, and dependency updates
-
-### v0.9.3 — Nov 20, 2024
-
-New minor release that introduces Wave's connected computing extensions. We've introduced new `wsh` commands that allow you to store variables and files, and access them across terminal sessions (on both local and remote machines).
-
-- `wsh setvar/getvar` to get and set variables -- [Docs](https://docs.waveterm.dev/wsh-reference#getvarsetvar)
-- `wsh file` operations (cat, write, append, rm, info, cp, and ls) -- [Docs](https://docs.waveterm.dev/wsh-reference#file)
-- Improved golang panic handling to prevent backend crashes
-- Improved SSH config logging and fixes a reused connection bug
-- Updated telemetry to track additional counters
-- New configuration settings (under "window:magnifiedblock") to control magnified block margins and display
-- New block/zone aliases (client, global, block, workspace, temp)
-- `wsh ai` file attachments are now rendered with special handling in the AI block
-- New ephemeral block type for creating modal widgets which will not disturb the underlying layout
-- Editing the AI presets file from the Wave AI block now brings up an ephemeral editor
-- Clicking outside of a magnified bglock will now un-magnify it
-- New button to clear the AI chat (also bound to Cmd-L)
-- New button to reset terminal commands in custom cmd widgets
-- [bugfix] Presets directory was not loading correctly on Windows
-- [bugfix] Magnified blocks were not showing correct on startup
-- [bugfix] Window opacity and background color was not getting applied properly in all cases
-- [bugfix] Fix terminal theming when applying global defaults [#1287](https://github.com/wavetermdev/waveterm/issues/1287)
-- MacOS 10.15 (Catalina) is no longer supported
-- Other bug fixes, docs improvements, and dependency bumps
-
-### v0.9.2 — Nov 11, 2024
-
-New minor release with bug fixes and new features! Fixed the bug around making Wave fullscreen (also affecting certain window managers like Hyprland). We've also put a lot of work into the doc site (https://docs.waveterm.dev), including documenting how [Widgets](./widgets) and Presets work!
-
-- Updated documentation
-- Wave AI now supports the Anthropic API! Checkout the [FAQ](./faq) for how to use the Claude models with Wave AI.
-- Removed defaultwidgets.json and unified it to widgets.json. Makes it more straightforward to override the default widgets.
-- New resolvers for `-b` param in `wsh`. "tab:N" for accessing the nth tab, "[view]" and "[view]:N" for accessing blocks of a particlar view.
-- New `wsh ai` command to send AI chats (and files) directly to a new or existing AI block
-- wsh setmeta/getmeta improvements. Allow setmeta to take a json file (and also read from stdin), also better output formats for getmeta (compatible with setmeta).
-- [bugfix] Set max completion tokens in the OpenAI API so we can now work with o1 models (also fallback to non-streaming mode)
-- [bugfix] Fixed content resizing when entering "full screen" mode. This bug also affected certain window managers (like Hyprland)
-- Lots of other small bug fixes, docs updates, and dependency bumps
-
-### v0.9.1 — Nov 1, 2024
-
-Minor bug fix release to follow-up on the v0.9.0 build. Lots of issues fixed (especially for Windows).
-
-- CLI applications that need microphone, camera, or location access will now work on MacOS. You'll see a security popup in Wave to allow/deny [#1086](https://github.com/wavetermdev/waveterm/issues/1086)
-- Can now use `wsh version -v` to print out the new data/config directories
-- Restores the old T1, T2, T3, ... tab naming logic
-- Temporarily revert to using the "Title Bar" on windows to mitgate a bug where the window controls were overlaying on top of our tabs (working on a real fix for the next release)
-- There is a new setting in the editor to enable/disable word wrapping [#1038](https://github.com/wavetermdev/waveterm/issues/1038)
-- Ctrl-S will now save files in codeedit [#1081](https://github.com/wavetermdev/waveterm/issues/1081)
-- [#1020](https://github.com/wavetermdev/waveterm/issues/1020) there is now a preset config option to change the active border color in tab themes
-- [bugfix] Multiple fixes for [#1167](https://github.com/wavetermdev/waveterm/issues/1167) to try to address tab loss while updating
-- [bugfix] Windows app crashed on opening View menu because of a bad accelerator key
-- [bugfix] The auto-updater messages in the tab bar are now more consistent when switching tabs, and we don't show errors when the network is disconnected
-- [bugfix] Full-screen mode now actually shows tabs in full screen
-- [bugfix] [#1175](https://github.com/wavetermdev/waveterm/issues/1175) can now edit .awk files
-- [bugfix] [#1066](https://github.com/wavetermdev/waveterm/issues/1066) applying a default theme now updates the background appropriately without a refresh
-
-### v0.9.0 — Oct 28, 2024
-
-New major Wave Terminal release! Wave tabs are now cached. Tab switching performance is
-now much faster and webview state, editor state, and scroll positions are now persisted
-across tab changes. We also have native WSL2 support. You can create native Wave connections
-to your Windows WSL2 distributions using the connection button.
-
-We've also laid the groundwork for some big features that will be released over the
-next couple of weeks, including Workspaces, AI improvments, and custom widgets.
-
-Lots of other smaller changes and bug fixes. See full list of PRs at https://github.com/wavetermdev/waveterm/releases/tag/v0.9.0
-
-### v0.8.13 — Oct 24, 2024
-
-- Wave is now available as a Snap for Linux users! You can find it [in the Snap Store](https://snapcraft.io/waveterm).
-- Wave is now available via the Windows Package Manager! You can install it via `winget install CommandLine.Wave`
-- can now use "term:fontsize" to override an individual terminal block's font size (also in context menu)
-- we now allow mixed case hostnames for connections to be compatible with ssh config
-- The Linux app icon is now updated to match the Windows icon
-- [bugfix] fixed a bug that sometimes caused escape sequences to be printed when switching between tabs
-- [bugfix] fixed an issue where the preview block was not cleaning up temp files (Windows only)
-- [bugfix] fixed chrome sandbox permissions errors in linux
-- [bugfix] fixed shutdown logic on MacOS/Linux which sometimes allowed orphaned processes to survive
-
-### v0.8.12 — Oct 18, 2024
-
-- Added support for multiple AI configurations! You can now run Open AI side-by-side with Ollama models. Can create AI presets in presets.json, and can easily switch between them using a new dropdown in the AI widget
-- Fix WebSocket reconnection error. this sometimes caused the terminal to hang when waking up from sleep
-- Added memory graphs, and per-CPU graphs to the sysinfo widget (and renamed it from cpuplot)
-- Added a new huge red "Config Error" button when there are parse errors in the config JSON file
-- Preview/CodeEdit widget now shows errors (squiggly lines) when JSON or YAML files fail to parse
-- New app icon for Windows to better match Fluent UI standards
-- Added copy-on-select to the terminal (on by default, can disable using "term:copyonselect")
-- Added a button to mute audio in webviews
-- Added a right-click "Open Clipboard URL" to easily open a webview from an URL stored in your system clipboard
-- [bugfix] fixed blank "help" pages when waking from sleep or restarting the app
-
-### v0.8.11 — Oct 10, 2024
-
-Hotfix release to address a couple of bugs introduced in v0.8.10
-
-- Fixes a regression in v0.8.10 which caused new tabs to sometimes come up blank and broken
-- Layout fixes to the AI widget spacing
-- Terminal scrollbar is now semi-transparent and overlays last column
-- Fixes initial window size (on first startup) for both smaller and larger screens
-- Added a "Don't Ask Again" checkbox for installing `wsh` on remote machines (sets a new config flag)
-- Prevent the app from downgrading when you install a beta build. Installing a beta-build will now switch you to the beta-update channel.
-
-### v0.8.10 — Oct 9, 2024
-
-Minor big fix release (but there are some new features).
-
-- added support for Azure AI [See FAQ](https://docs.waveterm.dev/faq#how-can-i-connect-to-azure-ai)
-- AI errors now appear in the chat
-- on MacOS, hitting "Space" in directorypreview will open selected file in Quick Look
-- [bugfix] fixed transparency settings
-- [bugfix] fixed issue with non-standard port numbers in connection dropdown
-- [bugfix] fixed issue with embedded docsite (returned 404 after refresh)
-
-### v0.8.9 — Oct 8, 2024
-
-Lots of bug fixes and new features!
-
-- New "help" view -- uses an embedded version of our doc site -- https://docs.waveterm.dev
-- [breaking] wsh getmeta, wsh setmeta, and wsh deleteblock now take a blockid using a `-b` parameter instead of as a positional parameter
-- allow metadata to override the block icon, header, and text (frame:title, frame:icon, and frame:text)
-- home button on web widget to return to the homepage, option to set a homepage default for the whole app or just for the given block
-- checkpoint the terminal less often to reduce frequency of output bug (still working on a full fix)
-- new terminal themes -- Warm Yellow, and One Dark Pro
-- we now support github flavored markdown alerts
-- `wsh notify` command to send a desktop notification
-- `wsh createblock` to create any block via the CLI
-- right click to "Save Image" in webview
-- `wsh edit` will now allow you to open new files (as long as the parent directly exists)
-- added 8 new fun tab background presets (right click on any tab and select "Backgrounds" to try them out)
-- [config] new config key "term:scrollback" to set the number of lines of scrollback for terminals. Use "-1" to set 0, max is 10000.
-- [config] new config key "term:theme" to set the default terminal theme for all new terminals
-- [config] new config key "preview:showhiddenfiles" to set the default "show hidden files" setting for preview
-- [bugfix] fixed an formatting issue with `wsh getmeta`
-- [bugfix] fix for startup issue on Linux when home directory is an NFS mount
-- [bugfix] fix cursor color in terminal themes to work
-- [bugfix] fix some double scrollbars when showing markdown content
-- [bugfix] improved shutdown sequence to better capture wavesrv logs
-- [bugfix] fix Alt+G keyboard accelerator for Linux/Windows
-- other assorted bug fixes, cleanups, and security fixes
-
-### v0.8.8 — Oct 1, 2024
-
-Quick patch release to fix Windows/Linux "Alt" keybindings. Also brings a huge performance improvement to AI streaming speed.
-
-### v0.8.7 — Sep 30, 2024
-
-Quick patch release to fix bugs:
-
-- Fixes windows SSH connections (invalid path while trying to install wsh tools)
-- Fixes an issue resolving `~` in windows paths `~\` now works instead of just `~/`
-- Tries to fix background color for webpages. Pulls meta tag for color-scheme, and sets a black background if dark detected (fixes issue rendering raw githubusercontent files)
-- Fixed our useDimensions hook to fire correctly. Fixes some sizing issues including allowing error messages to show consistently when SSH connections fail.
-- Allow "data:" urls in custom tab backgrounds
-- All the alias "tab" for the current tab's UUID when using wsh
-- [BUILD] conditional write generated files only if they are updated
-
-### v0.8.6 — Sep 26, 2024
-
-Another quick hotfix update. Fixes an issue where, if you deleted all of the tabs in a window, the window would be restored on next startup as completely blank.
-
-Also, as a bonus, we added fish shell support!
-
-### v0.8.5 — Sep 25, 2024
-
-Hot fix, dowgrade `jotai` library. Upgrading caused a major regression in codeedit which did not allow
-users to edit files.
-
-### v0.8.4 — Sep 25, 2024
-
-- Added a setting `window:disablehardwareacceleration` to disable native hardware acceleration
-- New startup model for legacy users given them the option to download the WaveLegacy
-- Use WAVETERM_HOME for the home directory consistently
-
-### v0.8.3 — Sep 25, 2024
-
-More hotfixes for Linux users. We now link against an older version of glibc and use
-the zig compiler on linux (the newer version caused us not to run on older distros).
-Also fixes a permissions issue when installing via .deb. There is also a new config value
-`window:nativetitlebar` which restores the native titlebar on windows/linux.
-
-### v0.8.2 — Sep 24, 2024
-
-Hot fix, fixes a nasty crash on startup for Linux users (dynamic linking but with netcgo DNS library)
-
-### v0.8.1 — Sep 23, 2024
-
-Minor cleanup release.
-
-- fix number parsing for certain config file values
-- add link to docs site
-- add new back button for directory view
-- telemetry fixes
-
-### v0.8.0 — Sep 20, 2024
-
-**Major New Release of Wave Terminal**
-
-The new build is a fresh start, and a clean break from the current version. As such, your history, settings, and configuration will not be carried over. If you'd like to continue to run the legacy version, you will need to download it separately.
-
-Release Artificats and source code diffs can be found on (Github)[https://github.com/wavetermdev/waveterm].
diff --git a/docs/docs/telemetry-old.mdx b/docs/docs/telemetry-old.mdx
deleted file mode 100644
index dba263dacb..0000000000
--- a/docs/docs/telemetry-old.mdx
+++ /dev/null
@@ -1,130 +0,0 @@
----
-id: "telemetry-old"
-title: "Legacy Telemetry"
-sidebar_class_name: hidden
----
-
-Wave Terminal collects telemetry data to help us track feature use, direct future product efforts, and generate aggregate metrics on Wave's popularity and usage. We do not collect or store any PII (personal identifiable information) and all metric data is only associated with and aggregated using your randomly generated _ClientId_. You may opt out of collection at any time.
-
-If you would like to turn telemetry on or off, the first opportunity is a button on the initial welcome page. After this, it can be turned off by adding `"telemetry:enabled": false` to the `config/settings.json` file. It can alternatively be turned on by adding `"telemetry:enabled": true` to the `config/settings.json` file.
-
-:::info
-
-You can also change your telemetry setting by running the wsh command:
-
-```
-wsh setconfig telemetry:enabled=true
-```
-
-:::
-
----
-
-## Sending Telemetry
-
-Provided that telemetry is enabled, it is sent 10 seconds after Waveterm is first booted and then again every 4 hours thereafter. It can also be sent in response to a few special cases listed below. When telemetry is sent, it is grouped into individual days as determined by your time zone. Any data from a previous day is marked as `Uploaded` so it will not need to be sent again.
-
-### Sending Once Telemetry is Enabled
-
-As soon as telemetry is enabled, a telemetry update is sent regardless of how long it has been since the last send. This does not reset the usual timer for telemetry sends.
-
-### Notifying that Telemetry is Disabled
-
-As soon as telemetry is disabled, Waveterm sends a special update that notifies us of this change. See [When Telemetry is Turned Off](#when-telemetry-is-turned-off) for more info. The timer still runs in the background but no data is sent.
-
-### When Waveterm is Closed
-
-Provided that telemetry is enabled, it will be sent when Waveterm is closed.
-
----
-
-## Telemetry Data
-
-When telemetry is active, we collect the following data. It is stored in the `telemetry.TelemetryData` type in the source code.
-
-| Name | Description |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ActiveMinutes | The number of minutes that the user has actively used Waveterm on a given day. This requires the terminal window to be in focus while the user is actively interacting with it. |
-| FgMinutes | The number of minutes that Waveterm has been in the foreground on a given day. This requires the terminal window to be in focus regardless of user interaction. |
-| OpenMinutes | The number of minutes that Waveterm has been open on a given day. This only requires that the terminal is open, even if the window is out of focus. |
-| NumBlocks | The number of existing blocks open on a given day |
-| NumTabs | The number of existing tabs open on a given day. |
-| NewTab | The number of new tabs created on a given day |
-| NumWindows | The number of existing windows open on a given day. |
-| NumWS | The number of existing workspaces on a given day. |
-| NumWSNamed | The number of named workspaces on a give day. |
-| NewTab | The number of new tabs opened on a given day. |
-| NumStartup | The number of times waveterm has been started on a given day. |
-| NumShutdown | The number of times waveterm has been shut down on a given day. |
-| SetTabTheme | The number of times the tab theme is changed from the context menu |
-| NumMagnify | The number of times any block is magnified |
-| NumPanics | The number of backend (golang) panics caught in the current day |
-| NumAIReqs | The number of AI requests made in the current day |
-| NumSSHConn | The number of distinct SSH connections that have been made to distinct hosts |
-| NumWSLConns | The number of distinct WSL connections that have been made to distinct distros |
-| Renderers | The number of new block views of each type are open on a given day. |
-| WshCmds | The number of wsh commands of each type run on a given day |
-| Blocks | The number of blocks of different view types open on a given day |
-| Conn | The number of successful remote connections made (and errors) on a given day |
-
-## Associated Data
-
-In addition to the telemetry data collected, the following is also reported. It is stored in the `telemetry.ActivityType` type in the source code.
-
-| Name | Description |
-| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| Day | The date the telemetry is associated with. It does not include the time. |
-| Uploaded | A boolean that indicates if the telemetry for this day is finalized. It is false during the day the telemetry is associated with, but gets set true at the first telemetry upload after that. Once it is true, the data for that particular day will not be sent up with the telemetry any more. |
-| TzName | The code for the timezone the user's OS is reporting (e.g. PST, GMT, JST) |
-| TzOffset | The offset for the timezone the user's OS is reporting (e.g. -08:00, +00:00, +09:00) |
-| ClientVersion | Which version of Waveterm is installed. |
-| ClientArch | This includes the user's operating system (e.g. linux or darwin) and architecture (e.g. x86_64 or arm64). It does not include data for any Connections at this time. |
-| BuildTime | This serves as a more accurate version number that keeps track of when we built the version. It has no bearing on when that version was installed by you. |
-| OSRelease | This lists the version of the operating system the user has installed. |
-| Displays | Display resolutions (added in v0.9.3 to help us understand what screen resolutions to optimize for) |
-
-## Telemetry Metadata
-
-Lastly, some data is sent along with the telemetry that describes how to classify it. It is stored in the `wcloud.TelemetryInputType` in the source code.
-
-| Name | Description |
-| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
-| UserId | Currently Unused. This is an anonymous UUID intended for use in future features. |
-| ClientId | This is an anonymous UUID created when Waveterm is first launched. It is used for telemetry and sending prompts to Open AI. |
-| AppType | This is used to differentiate the current version of waveterm from the legacy app. |
-| AutoUpdateEnabled | Whether or not auto update is turned on. |
-| AutoUpdateChannel | The type of auto update in use. This specifically refers to whether a latest or beta channel is selected. |
-| CurDay | The current day (in your time zone) when telemetry is sent. It does not include the time of day. |
-
-## Geo Data
-
-We do not store IP addresses in our telemetry table. However, CloudFlare passes us Geo-Location headers. We store these two header values:
-
-| Name | Description |
-| ------------ | ----------------------------------------------------------------- |
-| CFCountry | 2-letter country code (e.g. "US", "FR", or "JP") |
-| CFRegionCode | region code (often a provence, region, or state within a country) |
-
----
-
-## When Telemetry is Turned Off
-
-When a user disables telemetry, Waveterm sends a notification that their anonymous _ClientId_ has had its telemetry disabled. This is done with the `wcloud.NoTelemetryInputType` type in the source code. Beyond that, no further information is sent unless telemetry is turned on again. If it is turned on again, the previous 30 days of telemetry will be sent.
-
----
-
-## A Note on IP Addresses
-
-Telemetry is uploaded via https, which means your IP address is known to the telemetry server. We **do not** store your IP address in our telemetry table and **do not** associate it with your _ClientId_.
-
----
-
-## Previously Collected Telemetry Data
-
-While we believe the data we collect with telemetry is fairly minimal, we cannot make that decision for every user. If you ever change your mind about what has been collected previously, you may request that your data be deleted by emailing us at [support@waveterm.dev](mailto:support@waveterm.dev). If you do, we will need your _ClientId_ to remove it.
-
----
-
-## Privacy Policy
-
-For a summary of the above, you can take a look at our [Privacy Policy](https://www.waveterm.dev/privacy).
diff --git a/docs/docs/telemetry.mdx b/docs/docs/telemetry.mdx
deleted file mode 100644
index 2f9132276d..0000000000
--- a/docs/docs/telemetry.mdx
+++ /dev/null
@@ -1,71 +0,0 @@
----
-sidebar_position: 100
-title: Telemetry
-id: "telemetry"
----
-
-## tl;dr
-
-Wave Terminal collects telemetry data to help us track feature use, direct future product efforts, and generate aggregate metrics on Wave's popularity and usage. We do NOT collect personal information (PII), keystrokes, file contents, AI prompts, IP addresses, hostnames, or commands. We attach all information to an anonymous, randomly generated _ClientId_ (UUID). You may opt out of collection at any time.
-
-Here's a quick summary of what is collected:
-
-- Basic App/System Info - OS, architecture, app version, update settings
-- Usage Metrics - App start/shutdown, active minutes, foreground time, tab/block counts/usage
-- Feature Interactions - When you create tabs, run commands, change settings, etc.
-- Display Info - Monitor resolution, number of displays
-- Connection Events - SSH/WSL connection attempts (but NOT hostnames/IPs)
-- Wave AI Usage - Model/provider selection, token counts, request metrics, latency (but NOT prompts or responses)
-- Error Reports - Crash/panic events with minimal debugging info, but no stack traces or detailed errors
-
-Telemetry can be disabled at any time in settings. If not disabled it is sent on startup, on shutdown, and every 4-hours.
-
-## How to Disable Telemetry
-
-Telemetry can be enabled or disabled on the initial welcome screen when Wave first starts. After setup, telemetry can be disabled by setting the `telemetry:enabled` key to `false` in Wave’s general configuration file. It can also be disabled using the CLI command `wsh setconfig telemetry:enabled=false`.
-
-:::info
-
-This document outlines the current telemetry system as of v0.11.1. As of v0.12.5, Wave Terminal no longer sends legacy telemetry. The previous telemetry documentation can be found in our [Legacy Telemetry Documentation](./telemetry-old.mdx) for historical reference.
-
-:::
-
-## Diagnostics Ping
-
-Wave sends a small, anonymous diagnostics ping after the app has been running for a short time and at most once per day thereafter. This is used to estimate active installs and understand which versions are still in use, so we can make informed decisions about ongoing support and deprecations.
-
-The ping includes only: your Wave version, OS/CPU arch, local date (yyyy-mm-dd, no timezone or clock time), your randomly generated anonymous client ID, and whether usage telemetry is enabled or disabled.
-
-It does not include usage data, commands, files, or any telemetry events.
-
-This ping is intentionally separate from telemetry so Wave can count active installs. If you'd like to disable it, set the WAVETERM_NOPING environment variable.
-
-## Sending Telemetry
-
-Provided that telemetry is enabled, it is sent shortly after Wave is first launched and then again every 4 hours thereafter. It can also be sent in response to a few special cases listed below. When telemetry is sent, events are marked as sent to prevent duplicate transmissions.
-
-### Sending Once Telemetry is Enabled
-
-As soon as telemetry is enabled, a telemetry update is sent regardless of how long it has been since the last send. This does not reset the usual timer for telemetry sends.
-
-### When Wave is Closed
-
-Provided that telemetry is enabled, it will be sent when Waveterm is closed.
-
-## Event Types and Properties
-
-Wave collects the event types and properties described in the summary above. As we add features, new events and properties may be added to track their usage.
-
-For the complete, current list of all telemetry events and properties, see the source code: [telemetrydata.go](https://github.com/wavetermdev/waveterm/blob/main/pkg/telemetry/telemetrydata/telemetrydata.go)
-
-## GDPR Opt-Out Compliance
-
-When telemetry is disabled, Wave sends a single minimal opt-out record associated with the anonymous client ID, recording that telemetry was turned off and when it occurred. This record is retained for compliance purposes. After that, no telemetry or usage data is sent.
-
-## Deleting Your Data
-
-If you want your previously collected telemetry data deleted, email us at support (at) waveterm.dev with your _ClientId_ and we'll remove it.
-
-## Privacy Policy
-
-For a summary of the above, you can take a look at our [Privacy Policy](https://www.waveterm.dev/privacy).
diff --git a/docs/docs/waveai-modes.mdx b/docs/docs/waveai-modes.mdx
deleted file mode 100644
index 93403db800..0000000000
--- a/docs/docs/waveai-modes.mdx
+++ /dev/null
@@ -1,565 +0,0 @@
----
-sidebar_position: 1.6
-id: "waveai-modes"
-title: "Wave AI (Local Models + BYOK)"
----
-
-import { VersionBadge } from "@site/src/components/versionbadge";
-
-
-
-Wave AI supports custom AI modes that allow you to use local models, custom API endpoints, and alternative AI providers. This gives you complete control over which models and providers you use with Wave's AI features.
-
-## Configuration Overview
-
-AI modes are configured in `~/.config/waveterm/waveai.json`.
-
-**To edit using the UI:**
-1. Click the settings (gear) icon in the widget bar
-2. Select "Settings" from the menu
-3. Choose "Wave AI Modes" from the settings sidebar
-
-**Or launch from the command line:**
-```bash
-wsh editconfig waveai.json
-```
-
-Each mode defines a complete AI configuration including the model, API endpoint, authentication, and display properties.
-
-## Provider-Based Configuration
-
-Wave AI now supports provider-based configuration which automatically applies sensible defaults for common providers. By specifying the `ai:provider` field, you can significantly simplify your configuration as the system will automatically set up endpoints, API types, and secret names.
-
-### Supported Providers
-
-- **`openai`** - OpenAI API (automatically configures endpoint and secret name) [[see example](#openai)]
-- **`openrouter`** - OpenRouter API (automatically configures endpoint and secret name) [[see example](#openrouter)]
-- **`nanogpt`** - NanoGPT API (automatically configures endpoint and secret name) [[see example](#nanogpt)]
-- **`groq`** - Groq API (automatically configures endpoint and secret name) [[see example](#groq)]
-- **`google`** - Google AI (Gemini) [[see example](#google-ai-gemini)]
-- **`azure`** - Azure OpenAI Service (modern API) [[see example](#azure-openai-modern-api)]
-- **`azure-legacy`** - Azure OpenAI Service (legacy deployment API) [[see example](#azure-openai-legacy-deployment-api)]
-- **`custom`** - Custom API endpoint (fully manual configuration) [[see examples](#local-model-examples)]
-
-### Supported API Types
-
-Wave AI supports the following API types:
-
-- **`openai-chat`**: Uses the `/v1/chat/completions` endpoint (most common)
-- **`openai-responses`**: Uses the `/v1/responses` endpoint (modern API for GPT-5+ models)
-- **`google-gemini`**: Google's Gemini API format (automatically set when using `ai:provider: "google"`, not typically used directly)
-
-## Global Wave AI Settings
-
-You can configure global Wave AI behavior in your Wave Terminal settings (separate from the mode configurations in `waveai.json`).
-
-### Setting a Default AI Mode
-
-After configuring a local model or custom mode, you can make it the default by setting `waveai:defaultmode` in your Wave Terminal settings.
-
-:::important
-Use the **mode key** (the key in your `waveai.json` configuration), not the display name. For example, use `"ollama-llama"` (the key), not `"Ollama - Llama 3.3"` (the display name).
-:::
-
-**Using the settings command:**
-```bash
-wsh setconfig waveai:defaultmode="ollama-llama"
-```
-
-**Or edit settings.json directly:**
-1. Click the settings (gear) icon in the widget bar
-2. Select "Settings" from the menu
-3. Add the `waveai:defaultmode` key to your settings.json:
-```json
- "waveai:defaultmode": "ollama-llama"
-```
-
-This will make the specified mode the default selection when opening Wave AI features.
-
-:::note
-Wave AI normally requires telemetry to be enabled. However, if you configure your own custom model (local or BYOK) and set `waveai:defaultmode` to that custom mode's key, you will not receive telemetry requirement messages. This allows you to use Wave AI features completely privately with your own models.
-:::
-
-### Hiding Wave Cloud Modes
-
-If you prefer to use only your local or custom models and want to hide Wave's cloud AI modes from the mode dropdown, set `waveai:showcloudmodes` to `false`:
-
-**Using the settings command:**
-```bash
-wsh setconfig waveai:showcloudmodes=false
-```
-
-**Or edit settings.json directly:**
-1. Click the settings (gear) icon in the widget bar
-2. Select "Settings" from the menu
-3. Add the `waveai:showcloudmodes` key to your settings.json:
-```json
- "waveai:showcloudmodes": false
-```
-
-This will hide Wave's built-in cloud AI modes, showing only your custom configured modes.
-
-## Local Model Examples
-
-### Ollama
-
-[Ollama](https://ollama.ai) provides an OpenAI-compatible API for running models locally:
-
-```json
-{
- "ollama-llama": {
- "display:name": "Ollama - Llama 3.3",
- "display:order": 1,
- "display:icon": "microchip",
- "display:description": "Local Llama 3.3 70B model via Ollama",
- "ai:apitype": "openai-chat",
- "ai:model": "llama3.3:70b",
- "ai:thinkinglevel": "medium",
- "ai:endpoint": "http://localhost:11434/v1/chat/completions",
- "ai:apitoken": "ollama"
- }
-}
-```
-
-:::tip
-The `ai:apitoken` field is required but Ollama ignores it - you can set it to any value like `"ollama"`.
-:::
-
-### LM Studio
-
-[LM Studio](https://lmstudio.ai) provides a local server that can run various models:
-
-```json
-{
- "lmstudio-qwen": {
- "display:name": "LM Studio - Qwen",
- "display:order": 2,
- "display:icon": "server",
- "display:description": "Local Qwen model via LM Studio",
- "ai:apitype": "openai-chat",
- "ai:model": "qwen/qwen-2.5-coder-32b-instruct",
- "ai:thinkinglevel": "medium",
- "ai:endpoint": "http://localhost:1234/v1/chat/completions",
- "ai:apitoken": "not-needed"
- }
-}
-```
-
-### vLLM
-
-[vLLM](https://docs.vllm.ai) is a high-performance inference server with OpenAI API compatibility:
-
-```json
-{
- "vllm-local": {
- "display:name": "vLLM",
- "display:order": 3,
- "display:icon": "server",
- "display:description": "Local model via vLLM",
- "ai:apitype": "openai-chat",
- "ai:model": "your-model-name",
- "ai:thinkinglevel": "medium",
- "ai:endpoint": "http://localhost:8000/v1/chat/completions",
- "ai:apitoken": "not-needed"
- }
-}
-```
-
-## Cloud Provider Examples
-
-### OpenAI
-
-Using the `openai` provider automatically configures the endpoint and secret name:
-
-```json
-{
- "openai-gpt4o": {
- "display:name": "GPT-4o",
- "ai:provider": "openai",
- "ai:model": "gpt-4o"
- }
-}
-```
-
-The provider automatically sets:
-- `ai:endpoint` to `https://api.openai.com/v1/chat/completions`
-- `ai:apitype` to `openai-chat` (or `openai-responses` for GPT-5+ models)
-- `ai:apitokensecretname` to `OPENAI_KEY` (store your OpenAI API key with this name)
-- `ai:capabilities` to `["tools", "images", "pdfs"]` (automatically determined based on model)
-
-For newer models like GPT-4.1 or GPT-5, the API type is automatically determined:
-
-```json
-{
- "openai-gpt41": {
- "display:name": "GPT-4.1",
- "ai:provider": "openai",
- "ai:model": "gpt-4.1"
- }
-}
-```
-
-### OpenAI Compatible
-
-To use an OpenAI compatible API provider, you need to provide the ai:endpoint, ai:apitokensecretname, ai:model parameters,
-and use "openai-chat" as the ai:apitype.
-
-:::note
-The ai:endpoint is *NOT* a baseurl. The endpoint should contain the full endpoint, not just the baseurl.
-For example: https://api.x.ai/v1/chat/completions
-
-If you provide only the baseurl, you are likely to get a 404 message.
-:::
-
-```json
-{
- "xai-grokfast": {
- "display:name": "xAI Grok Fast",
- "display:order": 2,
- "display:icon": "server",
- "ai:apitype": "openai-chat",
- "ai:model": "grok-4-1-fast-reasoning",
- "ai:endpoint": "https://api.x.ai/v1/chat/completions",
- "ai:apitokensecretname": "XAI_KEY",
- "ai:capabilities": ["tools", "images", "pdfs"]
- }
-}
-```
-
-The `ai:apitokensecretname` should be the name of an environment variable that contains your API key. Set this environment variable before running Wave Terminal.
-
-
-### OpenRouter
-
-[OpenRouter](https://openrouter.ai) provides access to multiple AI models. Using the `openrouter` provider simplifies configuration:
-
-```json
-{
- "openrouter-qwen": {
- "display:name": "OpenRouter - Qwen",
- "ai:provider": "openrouter",
- "ai:model": "qwen/qwen-2.5-coder-32b-instruct"
- }
-}
-```
-
-The provider automatically sets:
-- `ai:endpoint` to `https://openrouter.ai/api/v1/chat/completions`
-- `ai:apitype` to `openai-chat`
-- `ai:apitokensecretname` to `OPENROUTER_KEY` (store your OpenRouter API key with this name)
-
-:::note
-For OpenRouter, you must manually specify `ai:capabilities` based on your model's features. Example:
-```json
-{
- "openrouter-qwen": {
- "display:name": "OpenRouter - Qwen",
- "ai:provider": "openrouter",
- "ai:model": "qwen/qwen-2.5-coder-32b-instruct",
- "ai:capabilities": ["tools"]
- }
-}
-```
-:::
-
-### NanoGPT
-
-[NanoGPT](https://nano-gpt.com) provides access to multiple AI models at competitive prices. Using the `nanogpt` provider simplifies configuration:
-
-```json
-{
- "nanogpt-glm47": {
- "display:name": "NanoGPT - GLM 4.7",
- "ai:provider": "nanogpt",
- "ai:model": "zai-org/glm-4.7"
- }
-}
-```
-
-The provider automatically sets:
-- `ai:endpoint` to `https://nano-gpt.com/api/v1/chat/completions`
-- `ai:apitype` to `openai-chat`
-- `ai:apitokensecretname` to `NANOGPT_KEY` (store your NanoGPT API key with this name)
-
-:::note
-NanoGPT is a proxy service that provides access to multiple AI models. You must manually specify `ai:capabilities` based on the model's features. NanoGPT supports OpenAI-compatible tool calling for models that have that capability. Check the model's `capabilities.vision` field from the [NanoGPT models API](https://nano-gpt.com/api/v1/models?detailed=true) to determine image support. Example for a text-only model with tool support:
-```json
-{
- "nanogpt-glm47": {
- "display:name": "NanoGPT - GLM 4.7",
- "ai:provider": "nanogpt",
- "ai:model": "zai-org/glm-4.7",
- "ai:capabilities": ["tools"]
- }
-}
-```
-For vision-capable models like `openai/gpt-5`, add `"images"` to capabilities.
-:::
-
-### Groq
-
-[Groq](https://groq.com) provides fast inference for open models through an OpenAI-compatible API. Using the `groq` provider simplifies configuration:
-
-```json
-{
- "groq-kimi-k2": {
- "display:name": "Groq - Kimi K2",
- "ai:provider": "groq",
- "ai:model": "moonshotai/kimi-k2-instruct"
- }
-}
-```
-
-The provider automatically sets:
-- `ai:endpoint` to `https://api.groq.com/openai/v1/chat/completions`
-- `ai:apitype` to `openai-chat`
-- `ai:apitokensecretname` to `GROQ_KEY` (store your Groq API key with this name)
-
-:::note
-For Groq, you must manually specify `ai:capabilities` based on your model's features.
-:::
-
-### Google AI (Gemini)
-
-[Google AI](https://ai.google.dev) provides the Gemini family of models. Using the `google` provider simplifies configuration:
-
-```json
-{
- "google-gemini": {
- "display:name": "Gemini 3.5 Flash",
- "ai:provider": "google",
- "ai:model": "gemini-3.5-flash"
- }
-}
-```
-
-The provider automatically sets:
-- `ai:endpoint` to `https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent`
-- `ai:apitype` to `google-gemini`
-- `ai:apitokensecretname` to `GOOGLE_AI_KEY` (store your Google AI API key with this name)
-- `ai:capabilities` to `["tools", "images", "pdfs"]` (automatically configured)
-
-### Azure OpenAI (Modern API)
-
-For the modern Azure OpenAI API, use the `azure` provider:
-
-```json
-{
- "azure-gpt4": {
- "display:name": "Azure GPT-4",
- "ai:provider": "azure",
- "ai:model": "gpt-4",
- "ai:azureresourcename": "your-resource-name"
- }
-}
-```
-
-The provider automatically sets:
-- `ai:endpoint` to `https://your-resource-name.openai.azure.com/openai/v1/chat/completions` (or `/responses` for newer models)
-- `ai:apitype` based on the model
-- `ai:apitokensecretname` to `AZURE_OPENAI_KEY` (store your Azure OpenAI key with this name)
-
-:::note
-For Azure providers, you must manually specify `ai:capabilities` based on your model's features. Example:
-```json
-{
- "azure-gpt4": {
- "display:name": "Azure GPT-4",
- "ai:provider": "azure",
- "ai:model": "gpt-4",
- "ai:azureresourcename": "your-resource-name",
- "ai:capabilities": ["tools", "images"]
- }
-}
-```
-:::
-
-### Azure OpenAI (Legacy Deployment API)
-
-For legacy Azure deployments, use the `azure-legacy` provider:
-
-```json
-{
- "azure-legacy-gpt4": {
- "display:name": "Azure GPT-4 (Legacy)",
- "ai:provider": "azure-legacy",
- "ai:azureresourcename": "your-resource-name",
- "ai:azuredeployment": "your-deployment-name"
- }
-}
-```
-
-The provider automatically constructs the full endpoint URL and sets the API version (defaults to `2025-04-01-preview`). You can override the API version with `ai:azureapiversion` if needed.
-
-:::note
-For Azure Legacy provider, you must manually specify `ai:capabilities` based on your model's features.
-:::
-
-## Using Secrets for API Keys
-
-Instead of storing API keys directly in the configuration, you should use Wave's secret store to keep your credentials secure. Secrets are stored encrypted using your system's native keychain.
-
-### Storing an API Key
-
-**Using the Secrets UI (recommended):**
-1. Click the settings (gear) icon in the widget bar
-2. Select "Secrets" from the menu
-3. Click "Add New Secret"
-4. Enter the secret name (e.g., `OPENAI_API_KEY`) and your API key
-5. Click "Save"
-
-**Or from the command line:**
-```bash
-wsh secret set OPENAI_KEY=sk-xxxxxxxxxxxxxxxx
-wsh secret set OPENROUTER_KEY=sk-xxxxxxxxxxxxxxxx
-```
-
-### Referencing the Secret
-
-When using providers like `openai` or `openrouter`, the secret name is automatically set. Just ensure the secret exists with the correct name:
-
-```json
-{
- "my-openai-mode": {
- "display:name": "OpenAI GPT-4o",
- "ai:provider": "openai",
- "ai:model": "gpt-4o"
- }
-}
-```
-
-The `openai` provider automatically looks for the `OPENAI_KEY` secret. See the [Secrets documentation](./secrets.mdx) for more information on managing secrets securely in Wave.
-
-## Multiple Modes Example
-
-You can define multiple AI modes and switch between them easily:
-
-```json
-{
- "ollama-llama": {
- "display:name": "Ollama - Llama 3.3",
- "display:order": 1,
- "ai:model": "llama3.3:70b",
- "ai:endpoint": "http://localhost:11434/v1/chat/completions",
- "ai:apitoken": "ollama"
- },
- "ollama-codellama": {
- "display:name": "Ollama - CodeLlama",
- "display:order": 2,
- "ai:model": "codellama:34b",
- "ai:endpoint": "http://localhost:11434/v1/chat/completions",
- "ai:apitoken": "ollama"
- },
- "openai-gpt4o": {
- "display:name": "GPT-4o",
- "display:order": 10,
- "ai:provider": "openai",
- "ai:model": "gpt-4o"
- }
-}
-```
-
-## Troubleshooting
-
-### Connection Issues
-
-If Wave can't connect to your model server:
-
-1. **For cloud providers with `ai:provider` set**: Ensure you have the correct secret stored (e.g., `OPENAI_KEY`, `OPENROUTER_KEY`)
-2. **For local/custom endpoints**: Verify the server is running (`curl http://localhost:11434/v1/models` for Ollama)
-3. Check the `ai:endpoint` is the complete endpoint URL including the path (e.g., `http://localhost:11434/v1/chat/completions`)
-4. Verify the `ai:apitype` matches your server's API (defaults are usually correct when using providers)
-5. Check firewall settings if using a non-localhost address
-
-### Model Not Found
-
-If you get "model not found" errors:
-
-1. Verify the model name matches exactly what your server expects
-2. For Ollama, use `ollama list` to see available models
-3. Some servers require prefixes or specific naming formats
-
-### API Type Selection
-
-- The API type defaults to `openai-chat` if not specified, which works for most providers
-- Use `openai-chat` for Ollama, LM Studio, custom endpoints, and most cloud providers
-- Use `openai-responses` for newer OpenAI models (GPT-5+) or when your provider specifically requires it
-- Provider presets automatically set the correct API type when needed
-
-## Configuration Reference
-
-### Minimal Configuration (with Provider)
-
-```json
-{
- "mode-key": {
- "display:name": "Qwen (OpenRouter)",
- "ai:provider": "openrouter",
- "ai:model": "qwen/qwen-2.5-coder-32b-instruct"
- }
-}
-```
-
-### Full Configuration (all fields)
-
-```json
-{
- "mode-key": {
- "display:name": "Display Name",
- "display:order": 1,
- "display:icon": "icon-name",
- "display:description": "Full description",
- "ai:provider": "custom",
- "ai:apitype": "openai-chat",
- "ai:model": "model-name",
- "ai:thinkinglevel": "medium",
- "ai:endpoint": "http://localhost:11434/v1/chat/completions",
- "ai:azureapiversion": "v1",
- "ai:apitoken": "your-token",
- "ai:apitokensecretname": "PROVIDER_KEY",
- "ai:azureresourcename": "your-resource",
- "ai:azuredeployment": "your-deployment",
- "ai:capabilities": ["tools", "images", "pdfs"]
- }
-}
-```
-
-### Field Reference
-
-| Field | Required | Description |
-|-------|----------|-------------|
-| `display:name` | Yes | Name shown in the AI mode selector |
-| `display:order` | No | Sort order in the selector (lower numbers first) |
-| `display:icon` | No | Icon identifier for the mode (can use any [FontAwesome icon](https://fontawesome.com/search), use the name without the "fa-" prefix). Default is "sparkles" |
-| `display:description` | No | Full description of the mode |
-| `ai:provider` | No | Provider preset: `openai`, `openrouter`, `nanogpt`, `groq`, `google`, `azure`, `azure-legacy`, `custom` |
-| `ai:apitype` | No | API type: `openai-chat`, `openai-responses`, or `google-gemini` (defaults to `openai-chat` if not specified) |
-| `ai:model` | No | Model identifier (required for most providers) |
-| `ai:thinkinglevel` | No | Thinking level: `low`, `medium`, or `high` |
-| `ai:endpoint` | No | *Full* API endpoint URL (auto-set by provider when available) |
-| `ai:azureapiversion` | No | Azure API version (for `azure-legacy` provider, defaults to `2025-04-01-preview`) |
-| `ai:apitoken` | No | API key/token (not recommended - use secrets instead) |
-| `ai:apitokensecretname` | No | Name of secret containing API token (auto-set by provider) |
-| `ai:azureresourcename` | No | Azure resource name (for Azure providers) |
-| `ai:azuredeployment` | No | Azure deployment name (for `azure-legacy` provider) |
-| `ai:capabilities` | No | Array of supported capabilities: `"tools"`, `"images"`, `"pdfs"` |
-| `waveai:cloud` | No | Internal - for Wave Cloud AI configuration only |
-| `waveai:premium` | No | Internal - for Wave Cloud AI configuration only |
-
-### AI Capabilities
-
-The `ai:capabilities` field specifies what features the AI mode supports:
-
-- **`tools`** - Enables AI tool usage for file reading/writing, shell integration, and widget interaction
-- **`images`** - Allows image attachments in chat (model can view uploaded images)
-- **`pdfs`** - Allows PDF file attachments in chat (model can read PDF content)
-
-**Provider-specific behavior:**
-- **OpenAI and Google providers**: Capabilities are automatically configured based on the model. You don't need to specify them.
-- **OpenRouter, NanoGPT, Groq, Azure, Azure-Legacy, and Custom providers**: You must manually specify capabilities based on your model's features.
-
-:::warning
-If you don't include `"tools"` in the `ai:capabilities` array, the AI model will not be able to interact with your Wave terminal widgets, read/write files, or execute commands. Most AI modes should include `"tools"` for the best Wave experience.
-:::
-
-Most models support `tools` and can benefit from it. Vision-capable models should include `images`. Not all models support PDFs, so only include `pdfs` if your model can process them.
diff --git a/docs/docs/waveai.mdx b/docs/docs/waveai.mdx
deleted file mode 100644
index 5189bc6792..0000000000
--- a/docs/docs/waveai.mdx
+++ /dev/null
@@ -1,110 +0,0 @@
----
-sidebar_position: 1.5
-id: "waveai"
-title: "Wave AI"
----
-
-import { Kbd } from "@site/src/components/kbd";
-import { PlatformProvider, PlatformSelectorButton } from "@site/src/components/platformcontext";
-
-
-
-
-
-
-Context-aware terminal assistant with access to terminal output, widgets, and filesystem.
-
-## Keyboard Shortcuts
-
-| Shortcut | Action |
-|----------|--------|
-| | Toggle AI panel |
-| | Focus AI input |
-| | Clear chat / start new |
-| | Send message |
-| | New line |
-
-## Widget Context Toggle
-
-Controls AI's access to your workspace:
-
-**ON**: AI can read terminal output, capture widget screenshots, access files/directories (with approval), navigate web widgets, and use custom widget tools. Use for debugging, code analysis, and workspace tasks.
-
-**OFF**: AI only sees your messages and attached files. Standard chat mode for general questions.
-
-## File Attachments
-
-Drag files onto the AI panel to attach (not supported with all models):
-
-| Type | Formats | Size Limit | Notes |
-|------|---------|------------|-------|
-| Images | JPEG, PNG, GIF, WebP, SVG | 10 MB | Auto-resized to 4096px max, converted to WebP |
-| PDFs | `.pdf` | 5 MB | Text extraction for analysis |
-| Text/Code | `.js`, `.ts`, `.py`, `.go`, `.md`, `.json`, `.yaml`, etc. | 200 KB | All common languages and configs |
-
-## CLI Integration
-
-Use `wsh ai` to send files and prompts from the command line:
-
-```bash
-git diff | wsh ai - # Pipe to AI
-wsh ai main.go -m "find bugs" # Attach files with message
-wsh ai $(tail -n 500 my.log) -m "review" -s # Auto-submit with output
-```
-
-Supports text files, images, PDFs, and directories. Use `-n` for new chat, `-s` to auto-submit.
-
-## AI Tools (Widget Context Enabled)
-
-### Terminal
-- **Read Terminal Output**: Fetches scrollback from terminal widgets, supports line ranges
-
-### File System
-- **Read Files**: Reads text files with line range support (requires approval)
-- **List Directories**: Returns file info, sizes, permissions, timestamps (requires approval)
-- **Write Text Files**: Create or modify files with diff preview and approval (requires approval)
-
-### Web
-- **Navigate Web**: Changes URLs in web browser widgets
-
-### All Widgets
-- **Capture Screenshots**: Takes screenshots of any widget for visual analysis (not supported on all models)
-
-:::warning Security
-File system operations require explicit approval. You control all file access.
-:::
-
-## Local Models & BYOK
-
-Wave AI supports using your own AI models and API keys:
-
-- **Local Models**: Run AI models locally with [Ollama](https://ollama.ai), [LM Studio](https://lmstudio.ai), [vLLM](https://docs.vllm.ai), and other OpenAI-compatible servers
-- **BYOK (Bring Your Own Key)**: Use your own API keys with OpenAI, OpenRouter, Google AI (Gemini), Azure OpenAI, and other cloud providers
-- **Multiple Modes**: Configure and switch between multiple AI providers and models
-- **Privacy**: Keep your data local or use your preferred cloud provider
-
-See the [**Local Models & BYOK guide**](./waveai-modes.mdx) for complete configuration instructions, examples, and troubleshooting.
-
-## Privacy
-
-**Default Wave AI Service:**
-- Messages are proxied through the Wave Cloud AI service (powered by OpenAI's APIs). Please refer to OpenAI's privacy policy for details on how they handle your data.
-- Wave does not store your chats, attachments, or use them for training
-- Usage counters included in anonymous telemetry
-- File access requires explicit approval
-
-**Local Models & BYOK:**
-- When using local models, your chat data never leaves your machine
-- When using BYOK with cloud providers, requests are sent directly to your chosen provider
-- Refer to your provider's privacy policy for details on how they handle your data
-
-:::info Under Active Development
-Wave AI is in active beta with included AI credits while we refine the experience. Share feedback in our [Discord](https://discord.gg/XfvZ334gwU).
-
-**Coming Soon:**
-- **Remote File Access**: Read files on SSH-connected systems
-- **Command Execution**: Run terminal commands with approval
-- **Web Content**: Extract text from web pages (currently screenshots only)
-:::
-
-
\ No newline at end of file
diff --git a/docs/docs/wsh-reference.mdx b/docs/docs/wsh-reference.mdx
index 6ed1bcaa3f..fb7247f91d 100644
--- a/docs/docs/wsh-reference.mdx
+++ b/docs/docs/wsh-reference.mdx
@@ -188,9 +188,6 @@ wsh editconfig presets.json
# opens widgets.json
wsh editconfig widgets.json
-
-# opens ai presets
-wsh editconfig presets/ai.json
```
---
@@ -990,7 +987,7 @@ Flags:
- `--workspace ` - restrict to specific workspace id
- `--window ` - restrict to specific window id
- `--tab ` - restrict to specific tab id
-- `--view ` - filter by view type (term, web, preview, edit, sysinfo, waveai)
+- `--view ` - filter by view type (term, web, preview, edit, sysinfo)
- `--json` - output results as JSON
- `--timeout ` - RPC timeout in milliseconds (default: 5000)
diff --git a/electron-builder.config.cjs b/electron-builder.config.cjs
index d49f2da616..1bddf2af97 100644
--- a/electron-builder.config.cjs
+++ b/electron-builder.config.cjs
@@ -117,10 +117,6 @@ const config = {
// this should remove /usr/lib/.build-id/ links which can conflict with other electron apps like slack
fpm: ["--rpm-rpmbuild-define", "_build_id_links none"],
},
- publish: {
- provider: "generic",
- url: "https://dl.waveterm.dev/releases-w2",
- },
afterPack: (context) => {
// This is a workaround to restore file permissions to the wavesrv binaries on macOS after packaging the universal binary.
if (context.electronPlatformName === "darwin" && context.arch === Arch.universal) {
diff --git a/emain/emain-activity.ts b/emain/emain-activity.ts
index 17dde466ae..360d93217f 100644
--- a/emain/emain-activity.ts
+++ b/emain/emain-activity.ts
@@ -9,10 +9,6 @@ let globalIsStarting = true;
let globalIsRelaunching = false;
let forceQuit = false;
let userConfirmedQuit = false;
-let termCommandsRun = 0;
-let termCommandsRemote = 0;
-let termCommandsWsl = 0;
-let termCommandsDurable = 0;
export function setWasActive(val: boolean) {
wasActive = val;
@@ -66,42 +62,4 @@ export function getUserConfirmedQuit(): boolean {
return userConfirmedQuit;
}
-export function incrementTermCommandsRun() {
- termCommandsRun++;
-}
-
-export function getAndClearTermCommandsRun(): number {
- const count = termCommandsRun;
- termCommandsRun = 0;
- return count;
-}
-
-export function incrementTermCommandsRemote() {
- termCommandsRemote++;
-}
-
-export function getAndClearTermCommandsRemote(): number {
- const count = termCommandsRemote;
- termCommandsRemote = 0;
- return count;
-}
-export function incrementTermCommandsWsl() {
- termCommandsWsl++;
-}
-
-export function getAndClearTermCommandsWsl(): number {
- const count = termCommandsWsl;
- termCommandsWsl = 0;
- return count;
-}
-
-export function incrementTermCommandsDurable() {
- termCommandsDurable++;
-}
-
-export function getAndClearTermCommandsDurable(): number {
- const count = termCommandsDurable;
- termCommandsDurable = 0;
- return count;
-}
diff --git a/emain/emain-ipc.ts b/emain/emain-ipc.ts
index 5e5f15b302..618342104d 100644
--- a/emain/emain-ipc.ts
+++ b/emain/emain-ipc.ts
@@ -12,12 +12,7 @@ import { RpcApi } from "../frontend/app/store/wshclientapi";
import { getWebServerEndpoint } from "../frontend/util/endpoints";
import * as keyutil from "../frontend/util/keyutil";
import { fireAndForget, parseDataUrl } from "../frontend/util/util";
-import {
- incrementTermCommandsDurable,
- incrementTermCommandsRemote,
- incrementTermCommandsRun,
- incrementTermCommandsWsl,
- setWasActive,
+import { setWasActive,
} from "./emain-activity";
import { createBuilderWindow, getAllBuilderWindows, getBuilderWindowByWebContentsId } from "./emain-builder";
import { callWithOriginalXdgCurrentDesktopAsync, unamePlatform } from "./emain-platform";
@@ -438,22 +433,6 @@ export function initIpcHandlers() {
console.log("fe-log", logStr);
});
- electron.ipcMain.on(
- "increment-term-commands",
- (event, opts?: { isRemote?: boolean; isWsl?: boolean; isDurable?: boolean }) => {
- incrementTermCommandsRun();
- if (opts?.isRemote) {
- incrementTermCommandsRemote();
- }
- if (opts?.isWsl) {
- incrementTermCommandsWsl();
- }
- if (opts?.isDurable) {
- incrementTermCommandsDurable();
- }
- }
- );
-
electron.ipcMain.on("native-paste", (event) => {
event.sender.paste();
});
diff --git a/emain/emain-menu.ts b/emain/emain-menu.ts
index 1bdf6a7139..7fee125a23 100644
--- a/emain/emain-menu.ts
+++ b/emain/emain-menu.ts
@@ -20,8 +20,6 @@ import {
WaveBrowserWindow,
} from "./emain-window";
import { ElectronWshClient } from "./emain-wsh";
-import { updater } from "./updater";
-
type AppMenuCallbacks = {
createNewWaveWindow: () => Promise;
relaunchBrowserWindows: () => Promise;
@@ -179,12 +177,6 @@ function makeAppMenuItems(webContents: electron.WebContents): Electron.MenuItemC
(getWindowWebContents(window) ?? webContents)?.send("menu-item-about");
},
},
- {
- label: "Check for Updates",
- click: () => {
- fireAndForget(() => updater?.checkForUpdates(true));
- },
- },
{ type: "separator" },
];
if (unamePlatform === "darwin") {
diff --git a/emain/emain-tabview.ts b/emain/emain-tabview.ts
index 753a53adec..fa89df5317 100644
--- a/emain/emain-tabview.ts
+++ b/emain/emain-tabview.ts
@@ -118,7 +118,6 @@ export function getWaveTabViewByWebContentsId(webContentsId: number): WaveTabVie
export class WaveTabView extends WebContentsView {
waveWindowId: string; // this will be set for any tabviews that are initialized. (unset for the hot spare)
isActiveTab: boolean;
- isWaveAIOpen: boolean;
private _waveTabId: string; // always set, WaveTabViews are unique per tab
lastUsedTs: number; // ts milliseconds
createdTs: number; // ts milliseconds
@@ -142,7 +141,6 @@ export class WaveTabView extends WebContentsView {
},
});
this.createdTs = Date.now();
- this.isWaveAIOpen = false;
this.savedInitOpts = null;
this.initPromise = new Promise((resolve, _) => {
this.initResolve = resolve;
diff --git a/emain/emain-wavesrv.ts b/emain/emain-wavesrv.ts
index f58d214a7e..14ac4c643b 100644
--- a/emain/emain-wavesrv.ts
+++ b/emain/emain-wavesrv.ts
@@ -24,7 +24,7 @@ import {
WaveAppPathVarName,
WaveAppResourcesPathVarName,
} from "./emain-util";
-import { updater } from "./updater";
+
let isWaveSrvDead = false;
let waveSrvProc: child_process.ChildProcessWithoutNullStreams | null = null;
@@ -77,9 +77,6 @@ export function runWaveSrv(handleWSEvent: (evtMsg: WSEventType) => void): Promis
env: envCopy,
});
proc.on("exit", (e) => {
- if (updater?.status == "installing") {
- return;
- }
console.log("wavesrv exited, shutting down");
setForceQuit(true);
isWaveSrvDead = true;
diff --git a/emain/emain-window.ts b/emain/emain-window.ts
index e3bfa87751..72b5584cd5 100644
--- a/emain/emain-window.ts
+++ b/emain/emain-window.ts
@@ -21,7 +21,7 @@ import { getElectronAppBasePath, isDev, unamePlatform } from "./emain-platform";
import { getOrCreateWebViewForTab, getWaveTabViewByWebContentsId, WaveTabView } from "./emain-tabview";
import { delay, ensureBoundsAreVisible, waveKeyToElectronKey } from "./emain-util";
import { ElectronWshClient } from "./emain-wsh";
-import { updater } from "./updater";
+
const DevInitTimeoutMs = 5000;
@@ -301,7 +301,7 @@ export class WaveBrowserWindow extends BaseWindow {
}
this.closeAllDevTools();
console.log("win 'close' handler fired", this.waveWindowId);
- if (getGlobalIsQuitting() || updater?.status == "installing" || getGlobalIsRelaunching()) {
+ if (getGlobalIsQuitting() || getGlobalIsRelaunching()) {
return;
}
e.preventDefault();
@@ -332,7 +332,7 @@ export class WaveBrowserWindow extends BaseWindow {
});
this.on("closed", () => {
console.log("win 'closed' handler fired", this.waveWindowId);
- if (getGlobalIsQuitting() || updater?.status == "installing") {
+ if (getGlobalIsQuitting()) {
console.log("win quitting or updating", this.waveWindowId);
return;
}
@@ -757,13 +757,6 @@ ipcMain.on("create-tab", async (event, _opts) => {
return null;
});
-ipcMain.on("set-waveai-open", (event, isOpen: boolean) => {
- const tabView = getWaveTabViewByWebContentsId(event.sender.id);
- if (tabView) {
- tabView.isWaveAIOpen = isOpen;
- }
-});
-
ipcMain.handle("close-tab", async (event, workspaceId: string, tabId: string, confirmClose: boolean) => {
const ww = getWaveWindowByWorkspaceId(workspaceId);
if (ww == null) {
diff --git a/emain/emain-wsh.ts b/emain/emain-wsh.ts
index d17dc2e106..3d935554e9 100644
--- a/emain/emain-wsh.ts
+++ b/emain/emain-wsh.ts
@@ -5,7 +5,7 @@ import { WindowService } from "@/app/store/services";
import { RpcResponseHelper, WshClient } from "@/app/store/wshclient";
import { RpcApi } from "@/app/store/wshclientapi";
import { Notification, net, safeStorage, shell } from "electron";
-import { getResolvedUpdateChannel } from "emain/updater";
+
import { unamePlatform } from "./emain-platform";
import { getWebContentsByBlockId, webGetSelector } from "./emain-web";
import { createBrowserWindow, getWaveWindowById, getWaveWindowByWorkspaceId } from "./emain-window";
@@ -39,10 +39,6 @@ export class ElectronWshClientType extends WshClient {
}).show();
}
- async handle_getupdatechannel(rh: RpcResponseHelper): Promise {
- return getResolvedUpdateChannel();
- }
-
async handle_focuswindow(rh: RpcResponseHelper, windowId: string) {
console.log(`focuswindow ${windowId}`);
const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient);
diff --git a/emain/emain.ts b/emain/emain.ts
index 8b08178aec..9d80261043 100644
--- a/emain/emain.ts
+++ b/emain/emain.ts
@@ -12,10 +12,6 @@ import { fireAndForget, sleep } from "../frontend/util/util";
import { AuthKey, configureAuthKeyRequestInjection } from "./authkey";
import {
getActivityState,
- getAndClearTermCommandsDurable,
- getAndClearTermCommandsRemote,
- getAndClearTermCommandsRun,
- getAndClearTermCommandsWsl,
getForceQuit,
getGlobalIsRelaunching,
getUserConfirmedQuit,
@@ -56,7 +52,7 @@ import {
} from "./emain-window";
import { ElectronWshClient, initElectronWshClient } from "./emain-wsh";
import { getLaunchSettings } from "./launchsettings";
-import { configureAutoUpdater, updater } from "./updater";
+
const electronApp = electron.app;
@@ -121,123 +117,8 @@ function handleWSEvent(evtMsg: WSEventType) {
});
}
-// we try to set the primary display as index [0]
-function getActivityDisplays(): ActivityDisplayType[] {
- const displays = electron.screen.getAllDisplays();
- const primaryDisplay = electron.screen.getPrimaryDisplay();
- const rtn: ActivityDisplayType[] = [];
- for (const display of displays) {
- const adt = {
- width: display.size.width,
- height: display.size.height,
- dpr: display.scaleFactor,
- internal: display.internal,
- };
- if (display.id === primaryDisplay?.id) {
- rtn.unshift(adt);
- } else {
- rtn.push(adt);
- }
- }
- return rtn;
-}
-
-async function sendDisplaysTDataEvent() {
- const displays = getActivityDisplays();
- if (displays.length === 0) {
- return;
- }
- const props: TEventProps = {};
- props["display:count"] = displays.length;
- props["display:height"] = displays[0].height;
- props["display:width"] = displays[0].width;
- props["display:dpr"] = displays[0].dpr;
- props["display:all"] = displays;
- try {
- await RpcApi.RecordTEventCommand(
- ElectronWshClient,
- {
- event: "app:display",
- props,
- },
- { noresponse: true }
- );
- } catch (e) {
- console.log("error sending display tdata event", e);
- }
-}
-
-function logActiveState() {
- fireAndForget(async () => {
- const astate = getActivityState();
- const activity: ActivityUpdate = { openminutes: 1 };
- const ww = focusedWaveWindow;
- const activeTabView = ww?.activeTabView;
- const isWaveAIOpen = activeTabView?.isWaveAIOpen ?? false;
-
- if (astate.wasInFg) {
- activity.fgminutes = 1;
- }
- if (astate.wasActive) {
- activity.activeminutes = 1;
- }
- activity.displays = getActivityDisplays();
-
- const termCmdCount = getAndClearTermCommandsRun();
- if (termCmdCount > 0) {
- activity.termcommandsrun = termCmdCount;
- }
- const termCmdRemoteCount = getAndClearTermCommandsRemote();
- const termCmdWslCount = getAndClearTermCommandsWsl();
- const termCmdDurableCount = getAndClearTermCommandsDurable();
-
- const props: TEventProps = {
- "activity:activeminutes": activity.activeminutes,
- "activity:fgminutes": activity.fgminutes,
- "activity:openminutes": activity.openminutes,
- };
- if (termCmdCount > 0) {
- props["activity:termcommandsrun"] = termCmdCount;
- }
- if (termCmdRemoteCount > 0) {
- props["activity:termcommands:remote"] = termCmdRemoteCount;
- }
- if (termCmdWslCount > 0) {
- props["activity:termcommands:wsl"] = termCmdWslCount;
- }
- if (termCmdDurableCount > 0) {
- props["activity:termcommands:durable"] = termCmdDurableCount;
- }
- if (astate.wasActive && isWaveAIOpen) {
- props["activity:waveaiactiveminutes"] = 1;
- }
- if (astate.wasInFg && isWaveAIOpen) {
- props["activity:waveaifgminutes"] = 1;
- }
-
- try {
- await RpcApi.ActivityCommand(ElectronWshClient, activity, { noresponse: true });
- await RpcApi.RecordTEventCommand(
- ElectronWshClient,
- {
- event: "app:activity",
- props,
- },
- { noresponse: true }
- );
- } catch (e) {
- console.log("error logging active state", e);
- } finally {
- setWasInFg(ww?.isFocused() ?? false);
- setWasActive(false);
- }
- });
-}
-
// this isn't perfect, but gets the job done without being complicated
-function runActiveTimer() {
- logActiveState();
- setTimeout(runActiveTimer, 60000);
+function runActiveTimer() { setTimeout(runActiveTimer, 60000);
}
function hideWindowWithCatch(window: WaveBrowserWindow) {
@@ -291,7 +172,7 @@ electronApp.on("before-quit", (e) => {
return;
}
setGlobalIsQuitting(true);
- updater?.stop();
+
if (unamePlatform == "win32") {
// win32 doesn't have a SIGINT, so we just let electron die, which
// ends up killing wavesrv via closing it's stdin.
@@ -342,7 +223,7 @@ process.on("uncaughtException", (error) => {
return;
}
- // Check if the error is related to QUIC protocol, if so, ignore (can happen with the updater)
+ // Check if the error is related to QUIC protocol, if so, ignore (can happen during network changes)
if (error?.message?.includes("net::ERR_QUIC_PROTOCOL_ERROR")) {
console.log("Ignoring QUIC protocol error:", error.message);
console.log("Stack Trace:", error.stack);
@@ -417,11 +298,9 @@ async function appMain() {
ensureHotSpareTab(fullConfig);
await relaunchBrowserWindows();
setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe
- setTimeout(sendDisplaysTDataEvent, 5000);
-
makeAndSetAppMenu();
makeDockTaskbar();
- await configureAutoUpdater();
+
setGlobalIsStarting(false);
if (fullConfig?.settings?.["window:maxtabcachesize"] != null) {
setMaxTabCacheSize(fullConfig.settings["window:maxtabcachesize"]);
diff --git a/emain/preload.ts b/emain/preload.ts
index 8d2b18a308..d7484b9ac8 100644
--- a/emain/preload.ts
+++ b/emain/preload.ts
@@ -36,10 +36,6 @@ contextBridge.exposeInMainWorld("api", {
ipcRenderer.on("fullscreen-change", (_event, isFullScreen) => callback(isFullScreen)),
onZoomFactorChange: (callback) =>
ipcRenderer.on("zoom-factor-change", (_event, zoomFactor) => callback(zoomFactor)),
- onUpdaterStatusChange: (callback) => ipcRenderer.on("app-update-status", (_event, status) => callback(status)),
- getUpdaterStatus: () => ipcRenderer.sendSync("get-app-update-status"),
- getUpdaterChannel: () => ipcRenderer.sendSync("get-updater-channel"),
- installAppUpdate: () => ipcRenderer.send("install-app-update"),
onMenuItemAbout: (callback) => ipcRenderer.on("menu-item-about", callback),
updateWindowControlsOverlay: (rect) => ipcRenderer.send("update-window-controls-overlay", rect),
onReinjectKey: (callback) => ipcRenderer.on("reinject-key", (_event, waveEvent) => callback(waveEvent)),
@@ -62,10 +58,7 @@ contextBridge.exposeInMainWorld("api", {
captureScreenshot: (rect: Rectangle) => ipcRenderer.invoke("capture-screenshot", rect),
setKeyboardChordMode: () => ipcRenderer.send("set-keyboard-chord-mode"),
clearWebviewStorage: (webContentsId: number) => ipcRenderer.invoke("clear-webview-storage", webContentsId),
- setWaveAIOpen: (isOpen: boolean) => ipcRenderer.send("set-waveai-open", isOpen),
closeBuilderWindow: () => ipcRenderer.send("close-builder-window"),
- incrementTermCommands: (opts?: { isRemote?: boolean; isWsl?: boolean; isDurable?: boolean }) =>
- ipcRenderer.send("increment-term-commands", opts),
nativePaste: () => ipcRenderer.send("native-paste"),
openBuilder: (appId?: string) => ipcRenderer.send("open-builder", appId),
setBuilderWindowAppId: (appId: string) => ipcRenderer.send("set-builder-window-appid", appId),
diff --git a/emain/updater.ts b/emain/updater.ts
deleted file mode 100644
index 8f06e6bec7..0000000000
--- a/emain/updater.ts
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { dialog, ipcMain, Notification } from "electron";
-import { autoUpdater } from "electron-updater";
-import { readFileSync } from "fs";
-import path from "path";
-import YAML from "yaml";
-import { RpcApi } from "../frontend/app/store/wshclientapi";
-import { isDev } from "../frontend/util/isdev";
-import { fireAndForget } from "../frontend/util/util";
-import { setUserConfirmedQuit } from "./emain-activity";
-import { delay } from "./emain-util";
-import { focusedWaveWindow, getAllWaveWindows } from "./emain-window";
-import { ElectronWshClient } from "./emain-wsh";
-
-export let updater: Updater;
-
-function getUpdateChannel(settings: SettingsType): string {
- const updaterConfigPath = path.join(process.resourcesPath!, "app-update.yml");
- const updaterConfig = YAML.parse(readFileSync(updaterConfigPath, { encoding: "utf8" }).toString());
- console.log("Updater config from binary:", updaterConfig);
- const updaterChannel: string = updaterConfig.channel ?? "latest";
- const settingsChannel = settings["autoupdate:channel"];
- let retVal = settingsChannel;
-
- // If the user setting doesn't exist yet, set it to the value of the updater config.
- // If the user was previously on the `latest` channel and has downloaded a `beta` version, update their configured channel to `beta` to prevent downgrading.
- if (!settingsChannel || (settingsChannel == "latest" && updaterChannel == "beta")) {
- console.log("Update channel setting does not exist, setting to value from updater config.");
- RpcApi.SetConfigCommand(ElectronWshClient, { "autoupdate:channel": updaterChannel });
- retVal = updaterChannel;
- }
- console.log("Update channel:", retVal);
- return retVal;
-}
-
-export class Updater {
- autoCheckInterval: NodeJS.Timeout | null;
- intervalms: number;
- autoCheckEnabled: boolean;
- availableUpdateReleaseName: string | null;
- availableUpdateReleaseNotes: string | null;
- private _status: UpdaterStatus;
- lastUpdateCheck: Date;
-
- constructor(settings: SettingsType) {
- this.intervalms = settings["autoupdate:intervalms"];
- console.log("Update check interval in milliseconds:", this.intervalms);
- this.autoCheckEnabled = settings["autoupdate:enabled"];
- console.log("Update check enabled:", this.autoCheckEnabled);
-
- this._status = "up-to-date";
- this.lastUpdateCheck = new Date(0);
- this.autoCheckInterval = null;
- this.availableUpdateReleaseName = null;
-
- autoUpdater.autoInstallOnAppQuit = settings["autoupdate:installonquit"];
- console.log("Install update on quit:", settings["autoupdate:installonquit"]);
-
- // Only update the release channel if it's specified, otherwise use the one configured in the updater.
- autoUpdater.channel = getUpdateChannel(settings);
- autoUpdater.allowDowngrade = false;
-
- autoUpdater.removeAllListeners();
-
- autoUpdater.on("error", (err) => {
- console.log("updater error");
- console.log(err);
- if (!err.toString()?.includes("net::ERR_INTERNET_DISCONNECTED")) this.status = "error";
- });
-
- autoUpdater.on("checking-for-update", () => {
- console.log("checking-for-update");
- this.status = "checking";
- });
-
- autoUpdater.on("update-available", () => {
- console.log("update-available; downloading...");
- this.status = "downloading";
- });
-
- autoUpdater.on("update-not-available", () => {
- console.log("update-not-available");
- this.status = "up-to-date";
- });
-
- autoUpdater.on("update-downloaded", (event) => {
- console.log("update-downloaded", [event]);
- this.availableUpdateReleaseName = event.releaseName;
- this.availableUpdateReleaseNotes = event.releaseNotes as string | null;
-
- // Display the update banner and create a system notification
- this.status = "ready";
- const updateNotification = new Notification({
- title: "Wave Terminal",
- body: "A new version of Wave Terminal is ready to install.",
- });
- updateNotification.on("click", () => {
- fireAndForget(this.promptToInstallUpdate.bind(this));
- });
- updateNotification.show();
- });
- }
-
- /**
- * The status of the Updater.
- */
- get status(): UpdaterStatus {
- return this._status;
- }
-
- private set status(value: UpdaterStatus) {
- this._status = value;
- getAllWaveWindows().forEach((window) => {
- const allTabs = Array.from(window.allLoadedTabViews.values());
- allTabs.forEach((tab) => {
- tab.webContents.send("app-update-status", value);
- });
- });
- }
-
- /**
- * Check for updates and start the background update check, if configured.
- */
- async start() {
- if (this.autoCheckEnabled) {
- console.log("starting updater");
- this.autoCheckInterval = setInterval(() => {
- fireAndForget(() => this.checkForUpdates(false));
- }, 600000); // intervals are unreliable when an app is suspended so we will check every 10 mins if the interval has passed.
- await this.checkForUpdates(false);
- }
- }
-
- /**
- * Stop the background update check, if configured.
- */
- stop() {
- console.log("stopping updater");
- if (this.autoCheckInterval) {
- clearInterval(this.autoCheckInterval);
- this.autoCheckInterval = null;
- }
- }
-
- /**
- * Checks if the configured interval time has passed since the last update check, and if so, checks for updates using the `autoUpdater` object
- * @param userInput Whether the user is requesting this. If so, an alert will report the result of the check.
- */
- async checkForUpdates(userInput: boolean) {
- const now = new Date();
-
- // Run an update check always if the user requests it, otherwise only if there's an active update check interval and enough time has elapsed.
- if (
- userInput ||
- (this.autoCheckInterval &&
- (!this.lastUpdateCheck || Math.abs(now.getTime() - this.lastUpdateCheck.getTime()) > this.intervalms))
- ) {
- const result = await autoUpdater.checkForUpdates();
-
- // If the user requested this check and we do not have an available update, let them know with a popup dialog. No need to tell them if there is an update, because we show a banner once the update is ready to install.
- if (userInput && !result.downloadPromise) {
- const dialogOpts: Electron.MessageBoxOptions = {
- type: "info",
- message: "There are currently no updates available.",
- };
- if (focusedWaveWindow) {
- dialog.showMessageBox(focusedWaveWindow, dialogOpts);
- }
- }
-
- // Only update the last check time if this is an automatic check. This ensures the interval remains consistent.
- if (!userInput) this.lastUpdateCheck = now;
- }
- }
-
- /**
- * Prompts the user to install the downloaded application update and restarts the application
- */
- async promptToInstallUpdate() {
- const dialogOpts: Electron.MessageBoxOptions = {
- type: "info",
- buttons: ["Restart", "Later"],
- title: "Application Update",
- message: process.platform === "win32" ? this.availableUpdateReleaseNotes : this.availableUpdateReleaseName,
- detail: "A new version has been downloaded. Restart the application to apply the updates.",
- };
-
- const allWindows = getAllWaveWindows();
- if (allWindows.length > 0) {
- await dialog.showMessageBox(focusedWaveWindow ?? allWindows[0], dialogOpts).then(({ response }) => {
- if (response === 0) {
- fireAndForget(this.installUpdate.bind(this));
- }
- });
- }
- }
-
- /**
- * Restarts the app and installs an update if it is available.
- */
- async installUpdate() {
- if (this.status == "ready") {
- this.status = "installing";
- await delay(1000);
- setUserConfirmedQuit(true);
- autoUpdater.quitAndInstall();
- }
- }
-}
-
-export function getResolvedUpdateChannel(): string {
- return isDev() ? "dev" : (autoUpdater.channel ?? "latest");
-}
-
-ipcMain.on("install-app-update", () => fireAndForget(updater?.promptToInstallUpdate.bind(updater)));
-ipcMain.on("get-app-update-status", (event) => {
- event.returnValue = updater?.status;
-});
-ipcMain.on("get-updater-channel", (event) => {
- event.returnValue = getResolvedUpdateChannel();
-});
-
-let autoUpdateLock = false;
-
-/**
- * Configures the auto-updater based on the user's preference
- */
-export async function configureAutoUpdater() {
- if (isDev()) {
- console.log("skipping auto-updater in dev mode");
- return;
- }
-
- // simple lock to prevent multiple auto-update configuration attempts, this should be very rare
- if (autoUpdateLock) {
- console.log("auto-update configuration already in progress, skipping");
- return;
- }
- autoUpdateLock = true;
-
- try {
- console.log("Configuring updater");
- const settings = (await RpcApi.GetFullConfigCommand(ElectronWshClient)).settings;
- updater = new Updater(settings);
- await updater.start();
- } catch (e) {
- console.warn("error configuring updater", e.toString());
- }
-
- autoUpdateLock = false;
-}
diff --git a/frontend/app/aipanel/ai-utils.ts b/frontend/app/aipanel/ai-utils.ts
deleted file mode 100644
index 8bfd67bdc0..0000000000
--- a/frontend/app/aipanel/ai-utils.ts
+++ /dev/null
@@ -1,598 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { sortByDisplayOrder } from "@/util/util";
-
-const TextFileLimit = 200 * 1024; // 200KB
-const PdfLimit = 5 * 1024 * 1024; // 5MB
-const ImageLimit = 10 * 1024 * 1024; // 10MB
-const ImagePreviewSize = 128;
-const ImagePreviewWebPQuality = 0.8;
-const ImageMaxEdge = 4096;
-
-export const isAcceptableFile = (file: File): boolean => {
- const acceptableTypes = [
- // Images
- "image/jpeg",
- "image/jpg",
- "image/png",
- "image/gif",
- "image/webp",
- "image/svg+xml",
- // PDFs
- "application/pdf",
- // Text files
- "text/plain",
- "text/markdown",
- "text/html",
- "text/css",
- "text/javascript",
- "text/typescript",
- // Application types for code files
- "application/javascript",
- "application/typescript",
- "application/json",
- "application/xml",
- ];
-
- if (acceptableTypes.includes(file.type)) {
- return true;
- }
-
- // Check file extensions for files without proper MIME types
- const extension = file.name.split(".").pop()?.toLowerCase();
- const acceptableExtensions = [
- "txt",
- "log",
- "md",
- "js",
- "mjs",
- "cjs",
- "jsx",
- "ts",
- "mts",
- "cts",
- "tsx",
- "go",
- "py",
- "java",
- "c",
- "cpp",
- "h",
- "hpp",
- "html",
- "htm",
- "css",
- "scss",
- "sass",
- "json",
- "jsonc",
- "json5",
- "jsonl",
- "ndjson",
- "xml",
- "yaml",
- "yml",
- "sh",
- "bat",
- "sql",
- "php",
- "rb",
- "rs",
- "swift",
- "kt",
- "cs",
- "vb",
- "r",
- "scala",
- "clj",
- "ex",
- "exs",
- "ini",
- "toml",
- "conf",
- "cfg",
- "env",
- "zsh",
- "fish",
- "ps1",
- "psm1",
- "bazel",
- "bzl",
- "csv",
- "tsv",
- "properties",
- "ipynb",
- "rmd",
- "gradle",
- "groovy",
- "cmake",
- ];
-
- if (extension && acceptableExtensions.includes(extension)) {
- return true;
- }
-
- // Check for specific filenames (case-insensitive)
- const fileName = file.name.toLowerCase();
- const acceptableFilenames = [
- "makefile",
- "dockerfile",
- "containerfile",
- "go.mod",
- "go.sum",
- "go.work",
- "go.work.sum",
- "package.json",
- "package-lock.json",
- "yarn.lock",
- "pnpm-lock.yaml",
- "composer.json",
- "composer.lock",
- "gemfile",
- "gemfile.lock",
- "podfile",
- "podfile.lock",
- "cargo.toml",
- "cargo.lock",
- "pipfile",
- "pipfile.lock",
- "requirements.txt",
- "setup.py",
- "pyproject.toml",
- "poetry.lock",
- "build.gradle",
- "settings.gradle",
- "pom.xml",
- "build.xml",
- "readme",
- "readme.md",
- "license",
- "license.md",
- "changelog",
- "changelog.md",
- "contributing",
- "contributing.md",
- "authors",
- "codeowners",
- "procfile",
- "jenkinsfile",
- "vagrantfile",
- "rakefile",
- "gruntfile.js",
- "gulpfile.js",
- "webpack.config.js",
- "rollup.config.js",
- "vite.config.js",
- "jest.config.js",
- "vitest.config.js",
- ".dockerignore",
- ".gitignore",
- ".gitattributes",
- ".gitmodules",
- ".editorconfig",
- ".eslintrc",
- ".prettierrc",
- ".pylintrc",
- ".bashrc",
- ".bash_profile",
- ".bash_login",
- ".bash_logout",
- ".profile",
- ".zshrc",
- ".zprofile",
- ".zshenv",
- ".zlogin",
- ".zlogout",
- ".kshrc",
- ".cshrc",
- ".tcshrc",
- ".xonshrc",
- ".shrc",
- ".aliases",
- ".functions",
- ".exports",
- ".direnvrc",
- ".vimrc",
- ".gvimrc",
- ];
-
- return acceptableFilenames.includes(fileName);
-};
-
-export const getFileIcon = (fileName: string, fileType: string): string => {
- if (fileType === "directory") {
- return "fa-folder";
- }
-
- if (fileType.startsWith("image/")) {
- return "fa-image";
- }
-
- if (fileType === "application/pdf") {
- return "fa-file-pdf";
- }
-
- // Check file extensions for code files
- const ext = fileName.split(".").pop()?.toLowerCase();
- switch (ext) {
- case "js":
- case "jsx":
- case "ts":
- case "tsx":
- return "fa-file-code";
- case "go":
- return "fa-file-code";
- case "py":
- return "fa-file-code";
- case "java":
- case "c":
- case "cpp":
- case "h":
- case "hpp":
- return "fa-file-code";
- case "html":
- case "css":
- case "scss":
- case "sass":
- return "fa-file-code";
- case "json":
- case "xml":
- case "yaml":
- case "yml":
- return "fa-file-code";
- case "md":
- case "txt":
- return "fa-file-text";
- default:
- return "fa-file";
- }
-};
-
-export const formatFileSize = (bytes: number): string => {
- if (bytes === 0) return "0 B";
- const k = 1024;
- const sizes = ["B", "KB", "MB", "GB"];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
-};
-
-// Normalize MIME type for AI processing
-export const normalizeMimeType = (file: File): string => {
- const fileType = file.type;
-
- // Images keep their real mimetype
- if (fileType.startsWith("image/")) {
- return fileType;
- }
-
- // PDFs keep their mimetype
- if (fileType === "application/pdf") {
- return fileType;
- }
-
- // Everything else (code files, markdown, text, etc.) becomes text/plain
- return "text/plain";
-};
-
-// Helper function to read file as base64 for AIMessage
-export const readFileAsBase64 = (file: File): Promise => {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = () => {
- const result = reader.result as string;
- // Remove data URL prefix to get just base64
- const base64 = result.split(",")[1];
- resolve(base64);
- };
- reader.onerror = reject;
- reader.readAsDataURL(file);
- });
-};
-
-// Helper function to create data URL for UIMessage
-export const createDataUrl = (file: File): Promise => {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = () => resolve(reader.result as string);
- reader.onerror = reject;
- reader.readAsDataURL(file);
- });
-};
-
-export interface FileSizeError {
- fileName: string;
- fileSize: number;
- maxSize: number;
- fileType: "text" | "pdf" | "image";
-}
-
-export const validateFileSize = (file: File): FileSizeError | null => {
- if (file.type.startsWith("image/")) {
- if (file.size > ImageLimit) {
- return {
- fileName: file.name,
- fileSize: file.size,
- maxSize: ImageLimit,
- fileType: "image",
- };
- }
- } else if (file.type === "application/pdf") {
- if (file.size > PdfLimit) {
- return {
- fileName: file.name,
- fileSize: file.size,
- maxSize: PdfLimit,
- fileType: "pdf",
- };
- }
- } else {
- if (file.size > TextFileLimit) {
- return {
- fileName: file.name,
- fileSize: file.size,
- maxSize: TextFileLimit,
- fileType: "text",
- };
- }
- }
-
- return null;
-};
-
-export const validateFileSizeFromInfo = (
- fileName: string,
- fileSize: number,
- mimeType: string
-): FileSizeError | null => {
- let maxSize: number;
- let fileType: "text" | "pdf" | "image";
-
- if (mimeType.startsWith("image/")) {
- maxSize = ImageLimit;
- fileType = "image";
- } else if (mimeType === "application/pdf") {
- maxSize = PdfLimit;
- fileType = "pdf";
- } else {
- maxSize = TextFileLimit;
- fileType = "text";
- }
-
- if (fileSize > maxSize) {
- return {
- fileName,
- fileSize,
- maxSize,
- fileType,
- };
- }
-
- return null;
-};
-
-export const formatFileSizeError = (error: FileSizeError): string => {
- const typeLabel = error.fileType === "image" ? "Image" : error.fileType === "pdf" ? "PDF" : "Text file";
- return `${typeLabel} "${error.fileName}" is too large (${formatFileSize(error.fileSize)}). Maximum size is ${formatFileSize(error.maxSize)}.`;
-};
-
-/**
- * Resize an image to have a maximum edge of 4096px and convert to WebP format
- * Returns the optimized image if it's smaller than the original, otherwise returns the original
- */
-export const resizeImage = async (file: File): Promise => {
- // Only process actual image files (not SVG)
- if (!file.type.startsWith("image/") || file.type === "image/svg+xml") {
- return file;
- }
-
- return new Promise((resolve) => {
- const img = new Image();
- const url = URL.createObjectURL(file);
-
- img.onload = async () => {
- URL.revokeObjectURL(url);
-
- let { width, height } = img;
-
- // Check if resizing is needed
- if (width <= ImageMaxEdge && height <= ImageMaxEdge) {
- // Image is already small enough, just try WebP conversion
- const canvas = document.createElement("canvas");
- canvas.width = width;
- canvas.height = height;
- const ctx = canvas.getContext("2d");
- ctx?.drawImage(img, 0, 0);
-
- canvas.toBlob(
- (blob) => {
- if (blob && blob.size < file.size) {
- const webpFile = new File([blob], file.name.replace(/\.[^.]+$/, ".webp"), {
- type: "image/webp",
- });
- console.log(
- `Image resized (no dimension change): ${file.name} - Original: ${formatFileSize(file.size)}, WebP: ${formatFileSize(blob.size)}`
- );
- resolve(webpFile);
- } else {
- console.log(
- `Image kept original (WebP not smaller): ${file.name} - ${formatFileSize(file.size)}`
- );
- resolve(file);
- }
- },
- "image/webp",
- ImagePreviewWebPQuality
- );
- return;
- }
-
- // Calculate new dimensions while maintaining aspect ratio
- if (width > height) {
- height = Math.round((height * ImageMaxEdge) / width);
- width = ImageMaxEdge;
- } else {
- width = Math.round((width * ImageMaxEdge) / height);
- height = ImageMaxEdge;
- }
-
- // Create canvas and resize
- const canvas = document.createElement("canvas");
- canvas.width = width;
- canvas.height = height;
- const ctx = canvas.getContext("2d");
- ctx?.drawImage(img, 0, 0, width, height);
-
- // Convert to WebP
- canvas.toBlob(
- (blob) => {
- if (blob && blob.size < file.size) {
- const webpFile = new File([blob], file.name.replace(/\.[^.]+$/, ".webp"), {
- type: "image/webp",
- });
- console.log(
- `Image resized: ${file.name} (${img.width}x${img.height} → ${width}x${height}) - Original: ${formatFileSize(file.size)}, WebP: ${formatFileSize(blob.size)}`
- );
- resolve(webpFile);
- } else {
- console.log(
- `Image kept original (WebP not smaller): ${file.name} (${img.width}x${img.height} → ${width}x${height}) - ${formatFileSize(file.size)}`
- );
- resolve(file);
- }
- },
- "image/webp",
- ImagePreviewWebPQuality
- );
- };
-
- img.onerror = () => {
- URL.revokeObjectURL(url);
- resolve(file);
- };
-
- img.src = url;
- });
-};
-
-/**
- * Create a 128x128 preview data URL for an image file
- */
-export const createImagePreview = async (file: File): Promise => {
- if (!file.type.startsWith("image/") || file.type === "image/svg+xml") {
- return null;
- }
-
- return new Promise((resolve) => {
- const img = new Image();
- const url = URL.createObjectURL(file);
-
- img.onload = () => {
- URL.revokeObjectURL(url);
-
- let { width, height } = img;
-
- if (width > height) {
- height = Math.round((height * ImagePreviewSize) / width);
- width = ImagePreviewSize;
- } else {
- width = Math.round((width * ImagePreviewSize) / height);
- height = ImagePreviewSize;
- }
-
- const canvas = document.createElement("canvas");
- canvas.width = width;
- canvas.height = height;
- const ctx = canvas.getContext("2d");
- ctx?.drawImage(img, 0, 0, width, height);
-
- canvas.toBlob(
- (blob) => {
- if (blob) {
- const reader = new FileReader();
- reader.onloadend = () => {
- resolve(reader.result as string);
- };
- reader.readAsDataURL(blob);
- } else {
- resolve(null);
- }
- },
- "image/webp",
- ImagePreviewWebPQuality
- );
- };
-
- img.onerror = () => {
- URL.revokeObjectURL(url);
- resolve(null);
- };
-
- img.src = url;
- });
-};
-
-
-/**
- * Filter and organize AI mode configs into Wave and custom provider groups
- * Returns organized configs that should be displayed based on settings and premium status
- */
-export interface FilteredAIModeConfigs {
- waveProviderConfigs: Array<{ mode: string } & AIModeConfigType>;
- otherProviderConfigs: Array<{ mode: string } & AIModeConfigType>;
- shouldShowCloudModes: boolean;
-}
-
-export const getFilteredAIModeConfigs = (
- aiModeConfigs: Record,
- showCloudModes: boolean,
- inBuilder: boolean,
- hasPremium: boolean,
- currentMode?: string
-): FilteredAIModeConfigs => {
- const hideQuick = inBuilder && hasPremium;
-
- const allConfigs = Object.entries(aiModeConfigs)
- .map(([mode, config]) => ({ mode, ...config }))
- .filter((config) => !(hideQuick && config.mode === "waveai@quick"));
-
- const otherProviderConfigs = allConfigs
- .filter((config) => config["ai:provider"] !== "wave")
- .sort(sortByDisplayOrder);
-
- const hasCustomModels = otherProviderConfigs.length > 0;
- const isCurrentModeCloud = currentMode?.startsWith("waveai@") ?? false;
- const shouldShowCloudModes = showCloudModes || !hasCustomModels || isCurrentModeCloud;
-
- const waveProviderConfigs = shouldShowCloudModes
- ? allConfigs.filter((config) => config["ai:provider"] === "wave").sort(sortByDisplayOrder)
- : [];
-
- return {
- waveProviderConfigs,
- otherProviderConfigs,
- shouldShowCloudModes,
- };
-};
-
-/**
- * Get the display name for an AI mode configuration.
- * If display:name is set, use that. Otherwise, construct from model/provider.
- * For azure-legacy, show "azureresourcename (azure)".
- * For other providers, show "model (provider)".
- */
-export function getModeDisplayName(config: AIModeConfigType): string {
- if (config["display:name"]) {
- return config["display:name"];
- }
-
- const provider = config["ai:provider"];
- const model = config["ai:model"];
- const azureResourceName = config["ai:azureresourcename"];
-
- if (provider === "azure-legacy") {
- return `${azureResourceName || "unknown"} (azure)`;
- }
-
- return `${model || "unknown"} (${provider || "custom"})`;
-}
diff --git a/frontend/app/aipanel/aidroppedfiles.tsx b/frontend/app/aipanel/aidroppedfiles.tsx
deleted file mode 100644
index d7051c412f..0000000000
--- a/frontend/app/aipanel/aidroppedfiles.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { cn } from "@/util/util";
-import { useAtomValue } from "jotai";
-import { memo } from "react";
-import { formatFileSize, getFileIcon } from "./ai-utils";
-import type { WaveAIModel } from "./waveai-model";
-
-interface AIDroppedFilesProps {
- model: WaveAIModel;
-}
-
-export const AIDroppedFiles = memo(({ model }: AIDroppedFilesProps) => {
- const droppedFiles = useAtomValue(model.droppedFiles);
-
- if (droppedFiles.length === 0) {
- return null;
- }
-
- return (
-
-
- {droppedFiles.map((file) => (
-
-
model.removeFile(file.id)}
- className="absolute top-1 right-1 w-4 h-4 bg-red-500 hover:bg-red-600 rounded-full flex items-center justify-center text-white text-xs opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
- >
-
-
-
-
- {file.previewUrl ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
- {file.name}
-
-
{formatFileSize(file.size)}
-
-
- ))}
-
-
- );
-});
-
-AIDroppedFiles.displayName = "AIDroppedFiles";
diff --git a/frontend/app/aipanel/aifeedbackbuttons.tsx b/frontend/app/aipanel/aifeedbackbuttons.tsx
deleted file mode 100644
index 30d9accc07..0000000000
--- a/frontend/app/aipanel/aifeedbackbuttons.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { cn, makeIconClass } from "@/util/util";
-import { memo, useState } from "react";
-import { WaveAIModel } from "./waveai-model";
-
-interface AIFeedbackButtonsProps {
- messageText: string;
-}
-
-export const AIFeedbackButtons = memo(({ messageText }: AIFeedbackButtonsProps) => {
- const [thumbsUpClicked, setThumbsUpClicked] = useState(false);
- const [thumbsDownClicked, setThumbsDownClicked] = useState(false);
- const [copied, setCopied] = useState(false);
-
- const handleThumbsUp = () => {
- setThumbsUpClicked(!thumbsUpClicked);
- if (thumbsDownClicked) {
- setThumbsDownClicked(false);
- }
- if (!thumbsUpClicked) {
- WaveAIModel.getInstance().handleAIFeedback("good");
- }
- };
-
- const handleThumbsDown = () => {
- setThumbsDownClicked(!thumbsDownClicked);
- if (thumbsUpClicked) {
- setThumbsUpClicked(false);
- }
- if (!thumbsDownClicked) {
- WaveAIModel.getInstance().handleAIFeedback("bad");
- }
- };
-
- const handleCopy = () => {
- navigator.clipboard.writeText(messageText);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- };
-
- return (
-
-
-
-
-
-
-
- {messageText?.trim() && (
-
-
-
- )}
-
- );
-});
-
-AIFeedbackButtons.displayName = "AIFeedbackButtons";
\ No newline at end of file
diff --git a/frontend/app/aipanel/aimessage.tsx b/frontend/app/aipanel/aimessage.tsx
deleted file mode 100644
index 1bfadd121d..0000000000
--- a/frontend/app/aipanel/aimessage.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { WaveStreamdown } from "@/app/element/streamdown";
-import { cn } from "@/util/util";
-import { memo, useEffect, useRef } from "react";
-import { getFileIcon } from "./ai-utils";
-import { AIFeedbackButtons } from "./aifeedbackbuttons";
-import { AIToolUseGroup } from "./aitooluse";
-import { WaveUIMessage, WaveUIMessagePart } from "./aitypes";
-import { WaveAIModel } from "./waveai-model";
-
-const AIThinking = memo(
- ({
- message = "AI is thinking...",
- reasoningText,
- isWaitingApproval = false,
- }: {
- message?: string;
- reasoningText?: string;
- isWaitingApproval?: boolean;
- }) => {
- const scrollRef = useRef(null);
-
- useEffect(() => {
- if (scrollRef.current && reasoningText) {
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
- }
- }, [reasoningText]);
-
- const displayText = reasoningText
- ? (() => {
- const lastDoubleNewline = reasoningText.lastIndexOf("\n\n");
- return lastDoubleNewline !== -1 ? reasoningText.substring(lastDoubleNewline + 2) : reasoningText;
- })()
- : "";
-
- return (
-
-
- {isWaitingApproval ? (
-
- ) : (
-
-
-
-
-
- )}
- {message &&
{message} }
-
-
- {displayText}
-
-
- );
- }
-);
-
-AIThinking.displayName = "AIThinking";
-
-interface UserMessageFilesProps {
- fileParts: Array;
-}
-
-const UserMessageFiles = memo(({ fileParts }: UserMessageFilesProps) => {
- if (fileParts.length === 0) return null;
-
- return (
-
-
- {fileParts.map((file, index) => (
-
-
-
- {file.data?.previewurl ? (
-
- ) : (
-
- )}
-
-
- {file.data?.filename || "File"}
-
-
-
- ))}
-
-
- );
-});
-
-UserMessageFiles.displayName = "UserMessageFiles";
-
-interface AIMessagePartProps {
- part: WaveUIMessagePart;
- role: string;
- isStreaming: boolean;
-}
-
-const AIMessagePart = memo(({ part, role, isStreaming }: AIMessagePartProps) => {
- const model = WaveAIModel.getInstance();
-
- if (part.type === "text") {
- const content = part.text ?? "";
-
- if (role === "user") {
- return {content}
;
- } else {
- return (
-
- );
- }
- }
-
- return null;
-});
-
-AIMessagePart.displayName = "AIMessagePart";
-
-interface AIMessageProps {
- message: WaveUIMessage;
- isStreaming: boolean;
-}
-
-const isDisplayPart = (part: WaveUIMessagePart): boolean => {
- return (
- part.type === "text" ||
- part.type === "data-tooluse" ||
- part.type === "data-toolprogress" ||
- (part.type.startsWith("tool-") && "state" in part && part.state === "input-available")
- );
-};
-
-type MessagePart =
- | { type: "single"; part: WaveUIMessagePart }
- | { type: "toolgroup"; parts: Array };
-
-const groupMessageParts = (parts: WaveUIMessagePart[]): MessagePart[] => {
- const grouped: MessagePart[] = [];
- let currentToolGroup: Array = [];
-
- for (const part of parts) {
- if (part.type === "data-tooluse" || part.type === "data-toolprogress") {
- currentToolGroup.push(part as WaveUIMessagePart & { type: "data-tooluse" | "data-toolprogress" });
- } else {
- if (currentToolGroup.length > 0) {
- grouped.push({ type: "toolgroup", parts: currentToolGroup });
- currentToolGroup = [];
- }
- grouped.push({ type: "single", part });
- }
- }
-
- if (currentToolGroup.length > 0) {
- grouped.push({ type: "toolgroup", parts: currentToolGroup });
- }
-
- return grouped;
-};
-
-const getThinkingMessage = (
- parts: WaveUIMessagePart[],
- isStreaming: boolean,
- role: string
-): { message: string; reasoningText?: string; isWaitingApproval?: boolean } | null => {
- if (!isStreaming || role !== "assistant") {
- return null;
- }
-
- const hasPendingApprovals = parts.some(
- (part) => part.type === "data-tooluse" && part.data?.approval === "needs-approval"
- );
-
- if (hasPendingApprovals) {
- return { message: "Waiting for Tool Approvals...", isWaitingApproval: true };
- }
-
- const lastPart = parts[parts.length - 1];
-
- if (lastPart?.type === "reasoning") {
- const reasoningContent = lastPart.text || "";
- return { message: "AI is thinking...", reasoningText: reasoningContent };
- }
-
- if (lastPart?.type === "text" && lastPart.text) {
- return null;
- }
-
- return { message: "" };
-};
-
-export const AIMessage = memo(({ message, isStreaming }: AIMessageProps) => {
- const parts = message.parts || [];
- const displayParts = parts.filter(isDisplayPart);
- const fileParts = parts.filter(
- (part): part is WaveUIMessagePart & { type: "data-userfile" } => part.type === "data-userfile"
- );
-
- const thinkingData = getThinkingMessage(parts, isStreaming, message.role);
- const groupedParts = groupMessageParts(displayParts);
-
- return (
-
-
*:first-child]:!mt-0",
- message.role === "user"
- ? "py-2 bg-zinc-700/60 text-white max-w-[calc(100%-50px)]"
- : "min-w-[min(100%,500px)]"
- )}
- >
- {displayParts.length === 0 && !isStreaming && !thinkingData ? (
-
(no text content)
- ) : (
- <>
- {groupedParts.map((group, index: number) =>
- group.type === "toolgroup" ? (
-
- ) : (
-
- )
- )}
- {thinkingData != null && (
-
- )}
- >
- )}
-
- {message.role === "user" &&
}
- {message.role === "assistant" && !isStreaming && displayParts.length > 0 && (
-
p.type === "text")
- .map((p) => p.text || "")
- .join("\n\n")}
- />
- )}
-
-
- );
-});
-
-AIMessage.displayName = "AIMessage";
diff --git a/frontend/app/aipanel/aimode.tsx b/frontend/app/aipanel/aimode.tsx
deleted file mode 100644
index 3602cdd360..0000000000
--- a/frontend/app/aipanel/aimode.tsx
+++ /dev/null
@@ -1,329 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { Tooltip } from "@/app/element/tooltip";
-import { atoms, getSettingsKeyAtom } from "@/app/store/global";
-import { RpcApi } from "@/app/store/wshclientapi";
-import { TabRpcClient } from "@/app/store/wshrpcutil";
-import { cn, fireAndForget, makeIconClass } from "@/util/util";
-import { useAtomValue } from "jotai";
-import { memo, useRef, useState } from "react";
-import { getFilteredAIModeConfigs, getModeDisplayName } from "./ai-utils";
-import { WaveAIModel } from "./waveai-model";
-
-interface AIModeMenuItemProps {
- config: AIModeConfigWithMode;
- isSelected: boolean;
- isDisabled: boolean;
- isPremiumDisabled: boolean;
- onClick: () => void;
- isFirst?: boolean;
- isLast?: boolean;
-}
-
-const AIModeMenuItem = memo(({ config, isSelected, isDisabled, isPremiumDisabled, onClick, isFirst, isLast }: AIModeMenuItemProps) => {
- return (
-
-
-
-
- {getModeDisplayName(config)}
- {isPremiumDisabled && " (premium)"}
-
- {isSelected && }
-
- {config["display:description"] && (
-
- {config["display:description"]}
-
- )}
-
- );
-});
-
-AIModeMenuItem.displayName = "AIModeMenuItem";
-
-interface ConfigSection {
- sectionName: string;
- configs: AIModeConfigWithMode[];
- isIncompatible?: boolean;
- noTelemetry?: boolean;
-}
-
-function computeCompatibleSections(
- currentMode: string,
- aiModeConfigs: Record,
- waveProviderConfigs: AIModeConfigWithMode[],
- otherProviderConfigs: AIModeConfigWithMode[]
-): ConfigSection[] {
- const currentConfig = aiModeConfigs[currentMode];
- const allConfigs = [...waveProviderConfigs, ...otherProviderConfigs];
-
- if (!currentConfig) {
- return [{ sectionName: "Incompatible Modes", configs: allConfigs, isIncompatible: true }];
- }
-
- const currentSwitchCompat = currentConfig["ai:switchcompat"] || [];
- const compatibleConfigs: AIModeConfigWithMode[] = [{ ...currentConfig, mode: currentMode }];
- const incompatibleConfigs: AIModeConfigWithMode[] = [];
-
- if (currentSwitchCompat.length === 0) {
- allConfigs.forEach((config) => {
- if (config.mode !== currentMode) {
- incompatibleConfigs.push(config);
- }
- });
- } else {
- allConfigs.forEach((config) => {
- if (config.mode === currentMode) return;
-
- const configSwitchCompat = config["ai:switchcompat"] || [];
- const hasMatch = currentSwitchCompat.some((currentTag: string) => configSwitchCompat.includes(currentTag));
-
- if (hasMatch) {
- compatibleConfigs.push(config);
- } else {
- incompatibleConfigs.push(config);
- }
- });
- }
-
- const sections: ConfigSection[] = [];
- const compatibleSectionName = compatibleConfigs.length === 1 ? "Current" : "Compatible Modes";
- sections.push({ sectionName: compatibleSectionName, configs: compatibleConfigs });
-
- if (incompatibleConfigs.length > 0) {
- sections.push({ sectionName: "Incompatible Modes", configs: incompatibleConfigs, isIncompatible: true });
- }
-
- return sections;
-}
-
-function computeWaveCloudSections(
- waveProviderConfigs: AIModeConfigWithMode[],
- otherProviderConfigs: AIModeConfigWithMode[],
- telemetryEnabled: boolean
-): ConfigSection[] {
- const sections: ConfigSection[] = [];
-
- if (waveProviderConfigs.length > 0) {
- sections.push({
- sectionName: "Wave AI Cloud",
- configs: waveProviderConfigs,
- noTelemetry: !telemetryEnabled,
- });
- }
- if (otherProviderConfigs.length > 0) {
- sections.push({ sectionName: "Custom", configs: otherProviderConfigs });
- }
-
- return sections;
-}
-
-interface AIModeDropdownProps {
- compatibilityMode?: boolean;
-}
-
-export const AIModeDropdown = memo(({ compatibilityMode = false }: AIModeDropdownProps) => {
- const model = WaveAIModel.getInstance();
- const currentMode = useAtomValue(model.currentAIMode);
- const aiModeConfigs = useAtomValue(model.aiModeConfigs);
- const waveaiModeConfigs = useAtomValue(atoms.waveaiModeConfigAtom);
- const widgetContextEnabled = useAtomValue(model.widgetAccessAtom);
- const hasPremium = useAtomValue(model.hasPremiumAtom);
- const showCloudModes = useAtomValue(getSettingsKeyAtom("waveai:showcloudmodes"));
- const telemetryEnabled = useAtomValue(getSettingsKeyAtom("telemetry:enabled")) ?? false;
- const [isOpen, setIsOpen] = useState(false);
- const dropdownRef = useRef(null);
-
- const { waveProviderConfigs, otherProviderConfigs } = getFilteredAIModeConfigs(
- aiModeConfigs,
- showCloudModes,
- model.inBuilder,
- hasPremium,
- currentMode
- );
-
- const sections: ConfigSection[] = compatibilityMode
- ? computeCompatibleSections(currentMode, aiModeConfigs, waveProviderConfigs, otherProviderConfigs)
- : computeWaveCloudSections(waveProviderConfigs, otherProviderConfigs, telemetryEnabled);
-
- const showSectionHeaders = compatibilityMode || sections.length > 1;
-
- const handleSelect = (mode: string) => {
- const config = aiModeConfigs[mode];
- if (!config) return;
- if (!hasPremium && config["waveai:premium"]) {
- return;
- }
- model.setAIMode(mode);
- setIsOpen(false);
- };
-
- const displayConfig = aiModeConfigs[currentMode];
- const displayName = displayConfig ? getModeDisplayName(displayConfig) : `Invalid (${currentMode})`;
- const displayIcon = displayConfig ? displayConfig["display:icon"] || "sparkles" : "question";
- const resolvedConfig = waveaiModeConfigs[currentMode];
- const hasToolsSupport = resolvedConfig && resolvedConfig["ai:capabilities"]?.includes("tools");
- const showNoToolsWarning = widgetContextEnabled && resolvedConfig && !hasToolsSupport;
-
- const handleNewChatClick = () => {
- model.clearChat();
- setIsOpen(false);
- };
-
- const handleConfigureClick = () => {
- fireAndForget(async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "action:other",
- props: {
- "action:type": "waveai:configuremodes:contextmenu",
- },
- },
- { noresponse: true }
- );
- await model.openWaveAIConfig();
- setIsOpen(false);
- });
- };
-
- const handleEnableTelemetry = () => {
- fireAndForget(async () => {
- await RpcApi.WaveAIEnableTelemetryCommand(TabRpcClient);
- setTimeout(() => {
- model.focusInput();
- }, 100);
- });
- };
-
- return (
-
- setIsOpen(!isOpen)}
- className={cn(
- "group flex items-center gap-1.5 px-2 py-1 text-xs text-gray-300 hover:text-white rounded transition-colors cursor-pointer border border-gray-600/50",
- isOpen ? "bg-zinc-700" : "bg-zinc-800/50 hover:bg-zinc-700"
- )}
- title={`AI Mode: ${displayName}`}
- >
-
- {displayName}
-
-
-
- {showNoToolsWarning && (
-
- Warning: This custom mode was configured without the "tools" capability in the
- "ai:capabilities" array. Without tool support, Wave AI will not be able to interact with
- widgets or files.
-
- }
- placement="bottom"
- >
-
-
- No Tools Support
-
-
- )}
-
- {isOpen && (
- <>
- setIsOpen(false)} />
-
- {sections.map((section, sectionIndex) => {
- const isFirstSection = sectionIndex === 0;
- const isLastSection = sectionIndex === sections.length - 1;
-
- return (
-
- {!isFirstSection &&
}
- {showSectionHeaders && (
- <>
-
- {section.sectionName}
-
- {section.isIncompatible && (
-
- (Start a New Chat to Switch)
-
- )}
- {section.noTelemetry && (
-
- (enable telemetry to unlock Wave AI Cloud)
-
- )}
- >
- )}
- {section.configs.map((config, index) => {
- const isFirst = index === 0 && isFirstSection && !showSectionHeaders;
- const isLast = index === section.configs.length - 1 && isLastSection;
- const isPremiumDisabled = !hasPremium && config["waveai:premium"];
- const isIncompatibleDisabled = section.isIncompatible || false;
- const isTelemetryDisabled = section.noTelemetry || false;
- const isDisabled =
- isPremiumDisabled || isIncompatibleDisabled || isTelemetryDisabled;
- const isSelected = currentMode === config.mode;
- return (
-
handleSelect(config.mode)}
- isFirst={isFirst}
- isLast={isLast}
- />
- );
- })}
-
- );
- })}
-
-
-
- New Chat
-
-
-
- Configure Modes
-
-
- >
- )}
-
- );
-});
-
-AIModeDropdown.displayName = "AIModeDropdown";
diff --git a/frontend/app/aipanel/aipanel-contextmenu.ts b/frontend/app/aipanel/aipanel-contextmenu.ts
deleted file mode 100644
index 4e78389198..0000000000
--- a/frontend/app/aipanel/aipanel-contextmenu.ts
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils";
-import { ContextMenuModel } from "@/app/store/contextmenu";
-import { isDev } from "@/app/store/global";
-import { globalStore } from "@/app/store/jotaiStore";
-import { RpcApi } from "@/app/store/wshclientapi";
-import { TabRpcClient } from "@/app/store/wshrpcutil";
-import { WaveAIModel } from "./waveai-model";
-
-export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boolean): Promise {
- e.preventDefault();
- e.stopPropagation();
-
- const model = WaveAIModel.getInstance();
- const menu: ContextMenuItem[] = [];
-
- if (showCopy) {
- const hasSelection = waveAIHasSelection();
- if (hasSelection) {
- menu.push({
- role: "copy",
- });
- menu.push({ type: "separator" });
- }
- }
-
- menu.push({
- label: "New Chat",
- click: () => {
- model.clearChat();
- },
- });
-
- menu.push({ type: "separator" });
-
- const rtInfo = await RpcApi.GetRTInfoCommand(TabRpcClient, {
- oref: model.orefContext,
- });
-
- const defaultTokens = model.inBuilder ? 24576 : 4096;
- const currentMaxTokens = rtInfo?.["waveai:maxoutputtokens"] ?? defaultTokens;
-
- const maxTokensSubmenu: ContextMenuItem[] = [];
-
- if (model.inBuilder) {
- maxTokensSubmenu.push(
- {
- label: "24k",
- type: "checkbox",
- checked: currentMaxTokens === 24576,
- click: () => {
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: model.orefContext,
- data: { "waveai:maxoutputtokens": 24576 },
- });
- },
- },
- {
- label: "64k (Pro)",
- type: "checkbox",
- checked: currentMaxTokens === 65536,
- click: () => {
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: model.orefContext,
- data: { "waveai:maxoutputtokens": 65536 },
- });
- },
- }
- );
- } else {
- if (isDev()) {
- maxTokensSubmenu.push({
- label: "1k (Dev Testing)",
- type: "checkbox",
- checked: currentMaxTokens === 1024,
- click: () => {
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: model.orefContext,
- data: { "waveai:maxoutputtokens": 1024 },
- });
- },
- });
- }
- maxTokensSubmenu.push(
- {
- label: "4k",
- type: "checkbox",
- checked: currentMaxTokens === 4096,
- click: () => {
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: model.orefContext,
- data: { "waveai:maxoutputtokens": 4096 },
- });
- },
- },
- {
- label: "16k (Pro)",
- type: "checkbox",
- checked: currentMaxTokens === 16384,
- click: () => {
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: model.orefContext,
- data: { "waveai:maxoutputtokens": 16384 },
- });
- },
- },
- {
- label: "64k (Pro)",
- type: "checkbox",
- checked: currentMaxTokens === 65536,
- click: () => {
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: model.orefContext,
- data: { "waveai:maxoutputtokens": 65536 },
- });
- },
- }
- );
- }
-
- menu.push({
- label: "Max Output Tokens",
- submenu: maxTokensSubmenu,
- });
-
- menu.push({ type: "separator" });
-
- menu.push({
- label: "Configure Modes",
- click: () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "action:other",
- props: {
- "action:type": "waveai:configuremodes:contextmenu",
- },
- },
- { noresponse: true }
- );
- model.openWaveAIConfig();
- },
- });
-
- if (model.canCloseWaveAIPanel()) {
- menu.push({ type: "separator" });
-
- menu.push({
- label: "Hide Wave AI",
- click: () => {
- model.closeWaveAIPanel();
- },
- });
- }
-
- ContextMenuModel.getInstance().showContextMenu(menu, e);
-}
diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx
deleted file mode 100644
index 32b8582141..0000000000
--- a/frontend/app/aipanel/aipanel.tsx
+++ /dev/null
@@ -1,636 +0,0 @@
-// Copyright 2026, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { handleWaveAIContextMenu } from "@/app/aipanel/aipanel-contextmenu";
-import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils";
-import { useTabBackground } from "@/app/block/blockutil";
-import { ErrorBoundary } from "@/app/element/errorboundary";
-import { atoms, getSettingsKeyAtom } from "@/app/store/global";
-import { globalStore } from "@/app/store/jotaiStore";
-import { useTabModelMaybe } from "@/app/store/tab-model";
-import { isBuilderWindow } from "@/app/store/windowtype";
-import { useWaveEnv } from "@/app/waveenv/waveenv";
-import { checkKeyPressed, keydownWrapper } from "@/util/keyutil";
-import { isMacOS, isWindows } from "@/util/platformutil";
-import { cn } from "@/util/util";
-import { useChat } from "@ai-sdk/react";
-import { DefaultChatTransport } from "ai";
-import * as jotai from "jotai";
-import { memo, useCallback, useEffect, useRef, useState } from "react";
-import { useDrop } from "react-dnd";
-import { formatFileSizeError, isAcceptableFile, validateFileSize } from "./ai-utils";
-import { AIDroppedFiles } from "./aidroppedfiles";
-import { AIModeDropdown } from "./aimode";
-import { AIPanelHeader } from "./aipanelheader";
-import { AIPanelInput } from "./aipanelinput";
-import { AIPanelMessages } from "./aipanelmessages";
-import { AIRateLimitStrip } from "./airatelimitstrip";
-import { WaveUIMessage } from "./aitypes";
-import { BYOKAnnouncement } from "./byokannouncement";
-import { TelemetryRequiredMessage } from "./telemetryrequired";
-import { WaveAIModel } from "./waveai-model";
-
-const AIBlockMask = memo(() => {
- return (
-
- );
-});
-
-AIBlockMask.displayName = "AIBlockMask";
-
-const AIDragOverlay = memo(() => {
- return (
-
-
-
-
Drop files here
-
Images, PDFs, and text/code files supported
-
-
- );
-});
-
-AIDragOverlay.displayName = "AIDragOverlay";
-
-const KeyCap = memo(({ children, className }: { children: React.ReactNode; className?: string }) => {
- return (
-
- {children}
-
- );
-});
-
-KeyCap.displayName = "KeyCap";
-
-const AIWelcomeMessage = memo(() => {
- const modKey = isMacOS() ? "⌘" : "Alt";
- const aiModeConfigs = jotai.useAtomValue(atoms.waveaiModeConfigAtom);
- const hasCustomModes = Object.keys(aiModeConfigs).some((key) => !key.startsWith("waveai@"));
- return (
-
-
-
-
- Wave AI is your terminal assistant with context. I can read your terminal output, analyze widgets,
- access files, and help you solve problems faster.
-
-
-
Getting Started:
-
-
-
-
-
-
-
Widget Context
-
When ON, I can read your terminal and analyze widgets.
-
When OFF, I'm sandboxed with no system access.
-
-
-
-
-
-
-
Drag & drop files or images for analysis
-
-
-
-
-
-
-
- {modKey}
- K
- to start a new chat
-
-
- {modKey}
- Shift
- A
- to toggle panel
-
-
- {isWindows() ? (
- <>
- Alt
- 0
- to focus
- >
- ) : (
- <>
- Ctrl
- Shift
- 0
- to focus
- >
- )}
-
-
-
-
-
-
- {!hasCustomModes &&
}
-
- BETA: Free to use. Daily limits keep our costs in check.
-
-
-
- );
-});
-
-AIWelcomeMessage.displayName = "AIWelcomeMessage";
-
-const AIBuilderWelcomeMessage = memo(() => {
- return (
-
-
-
-
- The WaveApp builder helps create wave widgets that integrate seamlessly into Wave Terminal.
-
-
-
- );
-});
-
-AIBuilderWelcomeMessage.displayName = "AIBuilderWelcomeMessage";
-
-const AIErrorMessage = memo(() => {
- const model = WaveAIModel.getInstance();
- const errorMessage = jotai.useAtomValue(model.errorMessage);
-
- if (!errorMessage) {
- return null;
- }
-
- return (
-
-
model.clearError()}
- className="absolute top-2 right-2 text-red-400 hover:text-red-300 cursor-pointer z-10"
- aria-label="Close error"
- >
-
-
-
- {errorMessage}
- model.clearChat()}
- className="ml-2 text-xs text-red-300 hover:text-red-200 cursor-pointer underline"
- >
- New Chat
-
-
-
- );
-});
-
-AIErrorMessage.displayName = "AIErrorMessage";
-
-const ConfigChangeModeFixer = memo(() => {
- const model = WaveAIModel.getInstance();
- const telemetryEnabled = jotai.useAtomValue(getSettingsKeyAtom("telemetry:enabled")) ?? false;
- const aiModeConfigs = jotai.useAtomValue(model.aiModeConfigs);
-
- useEffect(() => {
- model.fixModeAfterConfigChange();
- }, [telemetryEnabled, aiModeConfigs, model]);
-
- return null;
-});
-
-ConfigChangeModeFixer.displayName = "ConfigChangeModeFixer";
-
-type AIPanelComponentInnerProps = {
- roundTopLeft: boolean;
-};
-
-const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps) => {
- const [isDragOver, setIsDragOver] = useState(false);
- const [isReactDndDragOver, setIsReactDndDragOver] = useState(false);
- const [initialLoadDone, setInitialLoadDone] = useState(false);
- const model = WaveAIModel.getInstance();
- const containerRef = useRef(null);
- const waveEnv = useWaveEnv();
- const isLayoutMode = jotai.useAtomValue(atoms.controlShiftDelayAtom);
- const showOverlayBlockNums = jotai.useAtomValue(getSettingsKeyAtom("app:showoverlayblocknums")) ?? true;
- const isFocused = jotai.useAtomValue(model.isWaveAIFocusedAtom);
- const focusFollowsCursorMode = jotai.useAtomValue(getSettingsKeyAtom("app:focusfollowscursor")) ?? "off";
- const telemetryEnabled = jotai.useAtomValue(getSettingsKeyAtom("telemetry:enabled")) ?? false;
- const isPanelVisible = jotai.useAtomValue(model.getPanelVisibleAtom());
- const tabModel = useTabModelMaybe();
- const [tabBorderColor, tabActiveBorderColor] = useTabBackground(waveEnv, tabModel?.tabId);
- const defaultMode = jotai.useAtomValue(getSettingsKeyAtom("waveai:defaultmode")) ?? "waveai@balanced";
- const aiModeConfigs = jotai.useAtomValue(model.aiModeConfigs);
-
- const hasCustomModes = Object.keys(aiModeConfigs).some((key) => !key.startsWith("waveai@"));
- const isUsingCustomMode = !defaultMode.startsWith("waveai@");
- const allowAccess = telemetryEnabled || (hasCustomModes && isUsingCustomMode);
-
- const { messages, sendMessage, status, setMessages, error, stop } = useChat({
- transport: new DefaultChatTransport({
- api: model.getUseChatEndpointUrl(),
- prepareSendMessagesRequest: (_opts) => {
- const msg = model.getAndClearMessage();
- const body: any = {
- msg,
- chatid: globalStore.get(model.chatId),
- widgetaccess: globalStore.get(model.widgetAccessAtom),
- aimode: globalStore.get(model.currentAIMode),
- };
- if (isBuilderWindow()) {
- body.builderid = globalStore.get(atoms.builderId);
- body.builderappid = globalStore.get(atoms.builderAppId);
- } else {
- body.tabid = tabModel.tabId;
- }
- return { body };
- },
- }),
- onError: (error) => {
- console.error("AI Chat error:", error);
- model.setError(error.message || "An error occurred");
- },
- });
-
- model.registerUseChatData(sendMessage, setMessages, status, stop);
-
- // console.log("AICHAT messages", messages);
- (window as any).aichatmessages = messages;
- (window as any).aichatstatus = status;
-
- const handleKeyDown = (waveEvent: WaveKeyboardEvent): boolean => {
- if (checkKeyPressed(waveEvent, "Cmd:k")) {
- model.clearChat();
- return true;
- }
- return false;
- };
-
- useEffect(() => {
- globalStore.set(model.isAIStreaming, status === "streaming" || status === "submitted");
- }, [status]);
-
- useEffect(() => {
- const keyHandler = keydownWrapper(handleKeyDown);
- document.addEventListener("keydown", keyHandler);
- return () => {
- document.removeEventListener("keydown", keyHandler);
- };
- }, []);
-
- useEffect(() => {
- const loadChat = async () => {
- await model.uiLoadInitialChat();
- setInitialLoadDone(true);
- };
- loadChat();
- }, [model]);
-
- useEffect(() => {
- const updateWidth = () => {
- if (containerRef.current) {
- globalStore.set(model.containerWidth, containerRef.current.offsetWidth);
- }
- };
-
- updateWidth();
-
- const resizeObserver = new ResizeObserver(updateWidth);
- if (containerRef.current) {
- resizeObserver.observe(containerRef.current);
- }
-
- return () => {
- resizeObserver.disconnect();
- };
- }, [model]);
-
- useEffect(() => {
- model.ensureRateLimitSet();
- }, [model]);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- await model.handleSubmit();
- setTimeout(() => {
- model.focusInput();
- }, 100);
- };
-
- const hasFilesDragged = (dataTransfer: DataTransfer): boolean => {
- // Check if the drag operation contains files by looking at the types
- return dataTransfer.types.includes("Files");
- };
-
- const handleDragOver = (e: React.DragEvent) => {
- if (!allowAccess) {
- return;
- }
-
- const hasFiles = hasFilesDragged(e.dataTransfer);
-
- // Only handle native file drags here, let react-dnd handle FILE_ITEM drags
- if (!hasFiles) {
- return;
- }
-
- e.preventDefault();
- e.stopPropagation();
-
- if (!isDragOver) {
- setIsDragOver(true);
- }
- };
-
- const handleDragEnter = (e: React.DragEvent) => {
- if (!allowAccess) {
- return;
- }
-
- const hasFiles = hasFilesDragged(e.dataTransfer);
-
- // Only handle native file drags here, let react-dnd handle FILE_ITEM drags
- if (!hasFiles) {
- return;
- }
-
- e.preventDefault();
- e.stopPropagation();
-
- setIsDragOver(true);
- };
-
- const handleDragLeave = (e: React.DragEvent) => {
- if (!allowAccess) {
- return;
- }
-
- const hasFiles = hasFilesDragged(e.dataTransfer);
-
- // Only handle native file drags here, let react-dnd handle FILE_ITEM drags
- if (!hasFiles) {
- return;
- }
-
- e.preventDefault();
- e.stopPropagation();
-
- // Only set drag over to false if we're actually leaving the drop zone
- const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
- const x = e.clientX;
- const y = e.clientY;
-
- if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
- setIsDragOver(false);
- }
- };
-
- const handleDrop = async (e: React.DragEvent) => {
- if (!allowAccess) {
- e.preventDefault();
- e.stopPropagation();
- setIsDragOver(false);
- return;
- }
-
- // Check if this is a FILE_ITEM drag from react-dnd
- // If so, let react-dnd handle it instead
- if (!e.dataTransfer.files.length) {
- return; // Let react-dnd handle FILE_ITEM drags
- }
-
- e.preventDefault();
- e.stopPropagation();
- setIsDragOver(false);
-
- const files = Array.from(e.dataTransfer.files);
- const acceptableFiles = files.filter(isAcceptableFile);
-
- for (const file of acceptableFiles) {
- const sizeError = validateFileSize(file);
- if (sizeError) {
- model.setError(formatFileSizeError(sizeError));
- return;
- }
- await model.addFile(file);
- }
-
- if (acceptableFiles.length < files.length) {
- const rejectedCount = files.length - acceptableFiles.length;
- const rejectedFiles = files.filter((f) => !isAcceptableFile(f));
- const fileNames = rejectedFiles.map((f) => f.name).join(", ");
- model.setError(
- `${rejectedCount} file${rejectedCount > 1 ? "s" : ""} rejected (unsupported type): ${fileNames}. Supported: images, PDFs, and text/code files.`
- );
- }
- };
-
- const handleFileItemDrop = useCallback(
- (draggedFile: DraggedFile) => {
- if (!allowAccess) {
- return;
- }
- model.addFileFromRemoteUri(draggedFile);
- },
- [model, allowAccess]
- );
-
- const [{ isOver, canDrop }, drop] = useDrop(
- () => ({
- accept: "FILE_ITEM",
- drop: handleFileItemDrop,
- collect: (monitor) => ({
- isOver: monitor.isOver(),
- canDrop: monitor.canDrop(),
- }),
- }),
- [handleFileItemDrop]
- );
-
- // Update drag over state for FILE_ITEM drags
- useEffect(() => {
- if (isOver && canDrop) {
- setIsReactDndDragOver(true);
- } else {
- setIsReactDndDragOver(false);
- }
- }, [isOver, canDrop]);
-
- // Attach the drop ref to the container
- useEffect(() => {
- if (containerRef.current) {
- drop(containerRef.current);
- }
- }, [drop]);
-
- const handleFocusCapture = useCallback(
- (_event: React.FocusEvent) => {
- // console.log("Wave AI focus capture", getElemAsStr(event.target));
- model.requestWaveAIFocus();
- },
- [model]
- );
-
- const handlePointerEnter = useCallback(
- (event: React.PointerEvent) => {
- if (focusFollowsCursorMode !== "on") return;
- if (event.pointerType === "touch" || event.buttons > 0) return;
- if (isFocused) return;
- model.focusInput();
- },
- [focusFollowsCursorMode, isFocused, model]
- );
-
- const handleClick = (e: React.MouseEvent) => {
- const target = e.target as HTMLElement;
- const isInteractive = target.closest('button, a, input, textarea, select, [role="button"], [tabindex]');
-
- if (isInteractive) {
- return;
- }
-
- const hasSelection = waveAIHasSelection();
- if (hasSelection) {
- model.requestWaveAIFocus();
- return;
- }
-
- setTimeout(() => {
- if (!waveAIHasSelection()) {
- model.focusInput();
- }
- }, 0);
- };
-
- const showBlockMask = isLayoutMode && showOverlayBlockNums;
- const borderColor = isFocused ? (tabActiveBorderColor ?? null) : (tabBorderColor ?? null);
-
- return (
-
-
- {(isDragOver || isReactDndDragOver) && allowAccess &&
}
- {showBlockMask &&
}
-
-
-
-
- {!allowAccess ? (
-
- ) : (
- <>
- {messages.length === 0 && initialLoadDone ? (
-
handleWaveAIContextMenu(e, true)}
- >
-
- {model.inBuilder ?
:
}
-
- ) : (
-
handleWaveAIContextMenu(e, true)}
- />
- )}
-
-
-
- >
- )}
-
-
- );
-});
-
-AIPanelComponentInner.displayName = "AIPanelInner";
-
-type AIPanelComponentProps = {
- roundTopLeft: boolean;
-};
-
-const AIPanelComponent = ({ roundTopLeft }: AIPanelComponentProps) => {
- return (
-
-
-
- );
-};
-
-AIPanelComponent.displayName = "AIPanel";
-
-export { AIPanelComponent as AIPanel };
diff --git a/frontend/app/aipanel/aipanelheader.tsx b/frontend/app/aipanel/aipanelheader.tsx
deleted file mode 100644
index da54f6c9e9..0000000000
--- a/frontend/app/aipanel/aipanelheader.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { handleWaveAIContextMenu } from "@/app/aipanel/aipanel-contextmenu";
-import { useAtomValue } from "jotai";
-import { memo } from "react";
-import { WaveAIModel } from "./waveai-model";
-
-export const AIPanelHeader = memo(() => {
- const model = WaveAIModel.getInstance();
- const widgetAccess = useAtomValue(model.widgetAccessAtom);
- const inBuilder = model.inBuilder;
-
- const handleKebabClick = (e: React.MouseEvent) => {
- handleWaveAIContextMenu(e, false);
- };
-
- const handleContextMenu = (e: React.MouseEvent) => {
- handleWaveAIContextMenu(e, false);
- };
-
- return (
-
-
-
- Wave AI
-
-
-
- {!inBuilder && (
-
- Context
- Widget Context
- {
- model.setWidgetAccess(!widgetAccess);
- setTimeout(() => {
- model.focusInput();
- }, 0);
- }}
- className={`relative inline-flex h-6 w-14 items-center rounded-full transition-colors cursor-pointer ${
- widgetAccess ? "bg-accent-600" : "bg-zinc-600"
- }`}
- title={`Widget Access ${widgetAccess ? "ON" : "OFF"}`}
- >
-
-
- {widgetAccess ? "ON" : "OFF"}
-
-
-
- )}
-
-
-
-
-
-
- );
-});
-
-AIPanelHeader.displayName = "AIPanelHeader";
diff --git a/frontend/app/aipanel/aipanelinput.tsx b/frontend/app/aipanel/aipanelinput.tsx
deleted file mode 100644
index ec52ca0d13..0000000000
--- a/frontend/app/aipanel/aipanelinput.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { formatFileSizeError, isAcceptableFile, validateFileSize } from "@/app/aipanel/ai-utils";
-import { waveAIHasFocusWithin } from "@/app/aipanel/waveai-focus-utils";
-import { type WaveAIModel } from "@/app/aipanel/waveai-model";
-import { Tooltip } from "@/element/tooltip";
-import { cn } from "@/util/util";
-import { useAtom, useAtomValue } from "jotai";
-import { memo, useCallback, useEffect, useRef } from "react";
-
-interface AIPanelInputProps {
- onSubmit: (e: React.FormEvent) => void;
- status: string;
- model: WaveAIModel;
-}
-
-export interface AIPanelInputRef {
- focus: () => void;
- resize: () => void;
- scrollToBottom: () => void;
-}
-
-export const AIPanelInput = memo(({ onSubmit, status, model }: AIPanelInputProps) => {
- const [input, setInput] = useAtom(model.inputAtom);
- const isFocused = useAtomValue(model.isWaveAIFocusedAtom);
- const isChatEmpty = useAtomValue(model.isChatEmptyAtom);
- const textareaRef = useRef(null);
- const fileInputRef = useRef(null);
- const isPanelOpen = useAtomValue(model.getPanelVisibleAtom());
-
- let placeholder: string;
- if (!isChatEmpty) {
- placeholder = "Continue...";
- } else if (model.inBuilder) {
- placeholder = "What would you like to build...";
- } else {
- placeholder = "Ask Wave AI anything...";
- }
-
- const resizeTextarea = useCallback(() => {
- const textarea = textareaRef.current;
- if (!textarea) return;
-
- textarea.style.height = "auto";
- const scrollHeight = textarea.scrollHeight;
- const maxHeight = 7 * 24;
- textarea.style.height = `${Math.min(scrollHeight, maxHeight)}px`;
- }, []);
-
- useEffect(() => {
- const inputRefObject: React.RefObject = {
- current: {
- focus: () => {
- textareaRef.current?.focus();
- },
- resize: resizeTextarea,
- scrollToBottom: () => {
- const textarea = textareaRef.current;
- if (textarea) {
- textarea.scrollTop = textarea.scrollHeight;
- }
- },
- },
- };
- model.registerInputRef(inputRefObject);
- }, [model, resizeTextarea]);
-
- const handleKeyDown = (e: React.KeyboardEvent) => {
- const isComposing = e.nativeEvent?.isComposing || e.keyCode == 229;
- if (e.key === "Enter" && !e.shiftKey && !isComposing) {
- e.preventDefault();
- onSubmit(e as any);
- }
- };
-
- const handleFocus = useCallback(() => {
- model.requestWaveAIFocus();
- }, [model]);
-
- const handleBlur = useCallback(
- (e: React.FocusEvent) => {
- if (e.relatedTarget === null) {
- return;
- }
-
- if (waveAIHasFocusWithin(e.relatedTarget)) {
- return;
- }
-
- model.requestNodeFocus();
- },
- [model]
- );
-
- useEffect(() => {
- resizeTextarea();
- }, [input, resizeTextarea]);
-
- useEffect(() => {
- if (isPanelOpen) {
- resizeTextarea();
- }
- }, [isPanelOpen, resizeTextarea]);
-
- const handleUploadClick = () => {
- fileInputRef.current?.click();
- };
-
- const handleFileChange = async (e: React.ChangeEvent) => {
- const files = Array.from(e.target.files || []);
- const acceptableFiles = files.filter(isAcceptableFile);
-
- for (const file of acceptableFiles) {
- const sizeError = validateFileSize(file);
- if (sizeError) {
- model.setError(formatFileSizeError(sizeError));
- if (e.target) {
- e.target.value = "";
- }
- return;
- }
- await model.addFile(file);
- }
-
- if (acceptableFiles.length < files.length) {
- console.warn(`${files.length - acceptableFiles.length} files were rejected due to unsupported file types`);
- }
-
- if (e.target) {
- e.target.value = "";
- }
- };
-
- return (
-
- );
-});
-
-AIPanelInput.displayName = "AIPanelInput";
diff --git a/frontend/app/aipanel/aipanelmessages.tsx b/frontend/app/aipanel/aipanelmessages.tsx
deleted file mode 100644
index 478b20e658..0000000000
--- a/frontend/app/aipanel/aipanelmessages.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { useAtomValue } from "jotai";
-import { memo, useEffect, useRef, useState } from "react";
-import { AIMessage } from "./aimessage";
-import { AIModeDropdown } from "./aimode";
-import { type WaveUIMessage } from "./aitypes";
-import { WaveAIModel } from "./waveai-model";
-
-interface AIPanelMessagesProps {
- messages: WaveUIMessage[];
- status: string;
- onContextMenu?: (e: React.MouseEvent) => void;
-}
-
-export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPanelMessagesProps) => {
- const model = WaveAIModel.getInstance();
- const isPanelOpen = useAtomValue(model.getPanelVisibleAtom());
- const messagesEndRef = useRef(null);
- const messagesContainerRef = useRef(null);
- const prevStatusRef = useRef(status);
- const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
-
- const checkIfAtBottom = () => {
- const container = messagesContainerRef.current;
- if (!container) return true;
-
- const threshold = 50;
- const scrollBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
- return scrollBottom <= threshold;
- };
-
- const handleScroll = () => {
- const atBottom = checkIfAtBottom();
- setShouldAutoScroll(atBottom);
- };
-
- const scrollToBottom = () => {
- const container = messagesContainerRef.current;
- if (container) {
- container.scrollTop = container.scrollHeight;
- container.scrollLeft = 0;
- setShouldAutoScroll(true);
- }
- };
-
- useEffect(() => {
- const container = messagesContainerRef.current;
- if (!container) return;
-
- container.addEventListener("scroll", handleScroll);
- return () => container.removeEventListener("scroll", handleScroll);
- }, []);
-
- useEffect(() => {
- model.registerScrollToBottom(scrollToBottom);
- }, [model]);
-
- useEffect(() => {
- if (shouldAutoScroll) {
- scrollToBottom();
- }
- }, [messages, shouldAutoScroll]);
-
- useEffect(() => {
- if (isPanelOpen) {
- scrollToBottom();
- }
- }, [isPanelOpen]);
-
- useEffect(() => {
- const wasStreaming = prevStatusRef.current === "streaming";
- const isNowNotStreaming = status !== "streaming";
-
- if (wasStreaming && isNowNotStreaming) {
- requestAnimationFrame(() => {
- scrollToBottom();
- });
- }
-
- prevStatusRef.current = status;
- }, [status]);
-
- return (
-
-
- {messages.map((message, index) => {
- const isLastMessage = index === messages.length - 1;
- const isStreaming = status === "streaming" && isLastMessage && message.role === "assistant";
- return
;
- })}
-
- {status === "streaming" &&
- (messages.length === 0 || messages[messages.length - 1].role !== "assistant") && (
-
- )}
-
-
-
- );
-});
-
-AIPanelMessages.displayName = "AIPanelMessages";
diff --git a/frontend/app/aipanel/airatelimitstrip.tsx b/frontend/app/aipanel/airatelimitstrip.tsx
deleted file mode 100644
index 8b1fa134e2..0000000000
--- a/frontend/app/aipanel/airatelimitstrip.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { atoms } from "@/app/store/global";
-import * as jotai from "jotai";
-import { memo, useEffect, useState } from "react";
-
-const GetMoreButton = memo(({ variant, showClose = true }: { variant: "yellow" | "red"; showClose?: boolean }) => {
- const isYellow = variant === "yellow";
- const bgColor = isYellow ? "bg-yellow-900/30" : "bg-red-900/30";
- const hoverBg = isYellow ? "hover:bg-yellow-700/60" : "hover:bg-red-700/60";
- const borderColor = isYellow ? "border-yellow-700/50" : "border-red-700/50";
- const textColor = isYellow ? "text-yellow-200" : "text-red-200";
- const iconColor = isYellow ? "text-yellow-400" : "text-red-400";
- const iconHoverBg =
- showClose && isYellow
- ? "hover:has-[.close:hover]:bg-yellow-900/30"
- : showClose
- ? "hover:has-[.close:hover]:bg-red-900/30"
- : "";
-
- if (true as boolean) {
- // disable now until we have modal
- return null;
- }
-
- return (
-
-
- {showClose && (
-
- )}
- Get More
-
-
-
- );
-});
-
-GetMoreButton.displayName = "GetMoreButton";
-
-function formatTimeRemaining(expirationEpoch: number): string {
- const now = Math.floor(Date.now() / 1000);
- const secondsRemaining = expirationEpoch - now;
-
- if (secondsRemaining <= 0) {
- return "soon";
- }
-
- const hours = Math.floor(secondsRemaining / 3600);
- const minutes = Math.floor((secondsRemaining % 3600) / 60);
-
- if (hours > 0) {
- return `${hours}h`;
- }
- return `${minutes}m`;
-}
-
-const AIRateLimitStripComponent = memo(() => {
- let rateLimitInfo = jotai.useAtomValue(atoms.waveAIRateLimitInfoAtom);
- // rateLimitInfo = { req: 0, reqlimit: 200, preq: 0, preqlimit: 50, resetepoch: 1759374575 + 45 * 60 }; // testing
- const [, forceUpdate] = useState({});
-
- const shouldShow = rateLimitInfo && !rateLimitInfo.unknown && (rateLimitInfo.preq <= 5 || rateLimitInfo.req === 0);
-
- useEffect(() => {
- if (!shouldShow) {
- return;
- }
-
- const interval = setInterval(() => {
- forceUpdate({});
- }, 60000);
-
- return () => clearInterval(interval);
- }, [shouldShow]);
-
- if (!rateLimitInfo || rateLimitInfo.unknown || !shouldShow) {
- return null;
- }
-
- const { req, reqlimit, preq, preqlimit, resetepoch } = rateLimitInfo;
- const timeRemaining = formatTimeRemaining(resetepoch);
- const totalLimit = preqlimit + reqlimit;
-
- if (preq > 0 && preq <= 5) {
- return (
-
-
-
-
- {preqlimit - preq}/{preqlimit} Premium Used
-
-
-
Resets in {timeRemaining}
-
-
-
- );
- }
-
- if (preq === 0 && req > 0) {
- return (
-
-
-
-
- {preqlimit}/{preqlimit} Premium
-
-
•
-
Now on Basic
-
-
Resets in {timeRemaining}
-
-
-
- );
- }
-
- if (req === 0 && preq === 0) {
- return (
-
-
-
-
- {totalLimit}/{totalLimit} Reqs
-
-
•
-
Limit Reached
-
-
Resets in {timeRemaining}
-
-
-
- );
- }
-
- return null;
-});
-
-AIRateLimitStripComponent.displayName = "AIRateLimitStrip";
-
-export { AIRateLimitStripComponent as AIRateLimitStrip };
diff --git a/frontend/app/aipanel/aitooluse.tsx b/frontend/app/aipanel/aitooluse.tsx
deleted file mode 100644
index 7868c188e9..0000000000
--- a/frontend/app/aipanel/aitooluse.tsx
+++ /dev/null
@@ -1,441 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { BlockModel } from "@/app/block/block-model";
-import { Modal } from "@/app/modals/modal";
-import { recordTEvent } from "@/app/store/global";
-import { cn, fireAndForget } from "@/util/util";
-import { useAtomValue } from "jotai";
-import { memo, useEffect, useRef, useState } from "react";
-import { WaveUIMessagePart } from "./aitypes";
-import { RestoreBackupModal } from "./restorebackupmodal";
-import { WaveAIModel } from "./waveai-model";
-
-// matches pkg/filebackup/filebackup.go
-const BackupRetentionDays = 5;
-
-interface ToolDescLineProps {
- text: string;
-}
-
-const ToolDescLine = memo(({ text }: ToolDescLineProps) => {
- let displayText = text;
- if (displayText.startsWith("* ")) {
- displayText = "• " + displayText.slice(2);
- }
-
- const parts: React.ReactNode[] = [];
- let lastIndex = 0;
- const regex = /(? lastIndex) {
- parts.push(displayText.slice(lastIndex, match.index));
- }
-
- const sign = match[1];
- const number = match[2];
- const colorClass = sign === "+" ? "text-green-600" : "text-red-600";
- parts.push(
-
- {sign}
- {number}
-
- );
-
- lastIndex = match.index + match[0].length;
- }
-
- if (lastIndex < displayText.length) {
- parts.push(displayText.slice(lastIndex));
- }
-
- return {parts.length > 0 ? parts : displayText}
;
-});
-
-ToolDescLine.displayName = "ToolDescLine";
-
-interface ToolDescProps {
- text: string | string[];
- className?: string;
-}
-
-const ToolDesc = memo(({ text, className }: ToolDescProps) => {
- const lines = Array.isArray(text) ? text : text.split("\n");
-
- if (lines.length === 0) return null;
-
- return (
-
- {lines.map((line, idx) => (
-
- ))}
-
- );
-});
-
-ToolDesc.displayName = "ToolDesc";
-
-function getEffectiveApprovalStatus(baseApproval: string, isStreaming: boolean): string {
- return !isStreaming && baseApproval === "needs-approval" ? "timeout" : baseApproval;
-}
-
-interface AIToolApprovalButtonsProps {
- count: number;
- onApprove: () => void;
- onDeny: () => void;
-}
-
-const AIToolApprovalButtons = memo(({ count, onApprove, onDeny }: AIToolApprovalButtonsProps) => {
- const approveText = count > 1 ? `Approve All (${count})` : "Approve";
- const denyText = count > 1 ? "Deny All" : "Deny";
-
- return (
-
-
- {approveText}
-
-
- {denyText}
-
-
- );
-});
-
-AIToolApprovalButtons.displayName = "AIToolApprovalButtons";
-
-interface AIToolUseBatchItemProps {
- part: WaveUIMessagePart & { type: "data-tooluse" };
- effectiveApproval: string;
-}
-
-const AIToolUseBatchItem = memo(({ part, effectiveApproval }: AIToolUseBatchItemProps) => {
- const statusIcon = part.data.status === "completed" ? "✓" : part.data.status === "error" ? "✗" : "•";
- const statusColor =
- part.data.status === "completed"
- ? "text-success"
- : part.data.status === "error"
- ? "text-error"
- : "text-gray-400";
- const effectiveErrorMessage = part.data.errormessage || (effectiveApproval === "timeout" ? "Not approved" : null);
-
- return (
-
-
{statusIcon}
-
-
{part.data.tooldesc}
- {effectiveErrorMessage &&
{effectiveErrorMessage}
}
-
-
- );
-});
-
-AIToolUseBatchItem.displayName = "AIToolUseBatchItem";
-
-interface AIToolUseBatchProps {
- parts: Array;
- isStreaming: boolean;
-}
-
-const AIToolUseBatch = memo(({ parts, isStreaming }: AIToolUseBatchProps) => {
- const [userApprovalOverride, setUserApprovalOverride] = useState(null);
-
- const firstTool = parts[0].data;
- const baseApproval = userApprovalOverride || firstTool.approval;
- const effectiveApproval = getEffectiveApprovalStatus(baseApproval, isStreaming);
-
- const handleApprove = () => {
- setUserApprovalOverride("user-approved");
- parts.forEach((part) => {
- WaveAIModel.getInstance().toolUseSendApproval(part.data.toolcallid, "user-approved");
- });
- };
-
- const handleDeny = () => {
- setUserApprovalOverride("user-denied");
- parts.forEach((part) => {
- WaveAIModel.getInstance().toolUseSendApproval(part.data.toolcallid, "user-denied");
- });
- };
-
- return (
-
-
-
Reading Files
-
- {parts.map((part, idx) => (
-
- ))}
-
- {effectiveApproval === "needs-approval" && (
-
- )}
-
-
- );
-});
-
-AIToolUseBatch.displayName = "AIToolUseBatch";
-
-interface AIToolUseProps {
- part: WaveUIMessagePart & { type: "data-tooluse" };
- isStreaming: boolean;
-}
-
-const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
- const toolData = part.data;
- const [userApprovalOverride, setUserApprovalOverride] = useState(null);
- const model = WaveAIModel.getInstance();
- const restoreModalToolCallId = useAtomValue(model.restoreBackupModalToolCallId);
- const showRestoreModal = restoreModalToolCallId === toolData.toolcallid;
- const highlightTimeoutRef = useRef(null);
- const highlightedBlockIdRef = useRef(null);
-
- const statusIcon = toolData.status === "completed" ? "✓" : toolData.status === "error" ? "✗" : "•";
- const statusColor =
- toolData.status === "completed" ? "text-success" : toolData.status === "error" ? "text-error" : "text-gray-400";
-
- const baseApproval = userApprovalOverride || toolData.approval;
- const effectiveApproval = getEffectiveApprovalStatus(baseApproval, isStreaming);
-
- const isFileWriteTool = toolData.toolname === "write_text_file" || toolData.toolname === "edit_text_file";
-
- useEffect(() => {
- return () => {
- if (highlightTimeoutRef.current) {
- clearTimeout(highlightTimeoutRef.current);
- }
- };
- }, []);
-
- const handleApprove = () => {
- setUserApprovalOverride("user-approved");
- WaveAIModel.getInstance().toolUseSendApproval(toolData.toolcallid, "user-approved");
- };
-
- const handleDeny = () => {
- setUserApprovalOverride("user-denied");
- WaveAIModel.getInstance().toolUseSendApproval(toolData.toolcallid, "user-denied");
- };
-
- const handleMouseEnter = () => {
- if (!toolData.blockid) return;
-
- if (highlightTimeoutRef.current) {
- clearTimeout(highlightTimeoutRef.current);
- }
-
- highlightedBlockIdRef.current = toolData.blockid;
- BlockModel.getInstance().setBlockHighlight({
- blockId: toolData.blockid,
- icon: "sparkles",
- });
-
- highlightTimeoutRef.current = setTimeout(() => {
- if (highlightedBlockIdRef.current === toolData.blockid) {
- BlockModel.getInstance().setBlockHighlight(null);
- highlightedBlockIdRef.current = null;
- }
- }, 2000);
- };
-
- const handleMouseLeave = () => {
- if (!toolData.blockid) return;
-
- if (highlightTimeoutRef.current) {
- clearTimeout(highlightTimeoutRef.current);
- highlightTimeoutRef.current = null;
- }
-
- if (highlightedBlockIdRef.current === toolData.blockid) {
- BlockModel.getInstance().setBlockHighlight(null);
- highlightedBlockIdRef.current = null;
- }
- };
-
- const handleOpenDiff = () => {
- recordTEvent("waveai:showdiff");
- fireAndForget(() => WaveAIModel.getInstance().openDiff(toolData.inputfilename, toolData.toolcallid));
- };
-
- return (
-
-
-
{statusIcon}
-
{toolData.toolname}
-
- {isFileWriteTool &&
- toolData.inputfilename &&
- toolData.writebackupfilename &&
- toolData.runts &&
- Date.now() - toolData.runts < BackupRetentionDays * 24 * 60 * 60 * 1000 && (
-
{
- recordTEvent("waveai:revertfile", { "waveai:action": "revertfile:open" });
- model.openRestoreBackupModal(toolData.toolcallid);
- }}
- className="flex-shrink-0 px-1.5 py-0.5 border border-zinc-600 hover:border-zinc-500 hover:bg-zinc-700 rounded cursor-pointer transition-colors flex items-center gap-1 text-zinc-400"
- title="Restore backup file"
- >
- Revert File
-
-
- )}
- {isFileWriteTool && toolData.inputfilename && (
-
- Show Diff
-
-
- )}
-
- {toolData.tooldesc &&
}
- {(toolData.errormessage || effectiveApproval === "timeout") && (
-
{toolData.errormessage || "Not approved"}
- )}
- {effectiveApproval === "needs-approval" && (
-
- )}
- {showRestoreModal &&
}
-
- );
-});
-
-AIToolUse.displayName = "AIToolUse";
-
-interface AIToolProgressProps {
- part: WaveUIMessagePart & { type: "data-toolprogress" };
-}
-
-const AIToolProgress = memo(({ part }: AIToolProgressProps) => {
- const progressData = part.data;
-
- return (
-
-
-
-
{progressData.toolname}
-
- {progressData.statuslines && progressData.statuslines.length > 0 && (
-
- )}
-
- );
-});
-
-AIToolProgress.displayName = "AIToolProgress";
-
-interface AIToolUseGroupProps {
- parts: Array;
- isStreaming: boolean;
-}
-
-type ToolGroupItem =
- | { type: "batch"; parts: Array }
- | { type: "single"; part: WaveUIMessagePart & { type: "data-tooluse" } }
- | { type: "progress"; part: WaveUIMessagePart & { type: "data-toolprogress" } };
-
-export const AIToolUseGroup = memo(({ parts, isStreaming }: AIToolUseGroupProps) => {
- const tooluseParts = parts.filter((p) => p.type === "data-tooluse") as Array<
- WaveUIMessagePart & { type: "data-tooluse" }
- >;
- const toolprogressParts = parts.filter((p) => p.type === "data-toolprogress") as Array<
- WaveUIMessagePart & { type: "data-toolprogress" }
- >;
-
- const tooluseCallIds = new Set(tooluseParts.map((p) => p.data.toolcallid));
- const filteredProgressParts = toolprogressParts.filter((p) => !tooluseCallIds.has(p.data.toolcallid));
-
- const isFileOp = (part: WaveUIMessagePart & { type: "data-tooluse" }) => {
- const toolName = part.data?.toolname;
- return toolName === "read_text_file" || toolName === "read_dir";
- };
-
- const needsApproval = (part: WaveUIMessagePart & { type: "data-tooluse" }) => {
- return getEffectiveApprovalStatus(part.data?.approval, isStreaming) === "needs-approval";
- };
-
- const readFileNeedsApproval: Array = [];
- const readFileOther: Array = [];
-
- for (const part of tooluseParts) {
- if (isFileOp(part)) {
- if (needsApproval(part)) {
- readFileNeedsApproval.push(part);
- } else {
- readFileOther.push(part);
- }
- }
- }
-
- const groupedItems: ToolGroupItem[] = [];
- let addedApprovalBatch = false;
- let addedOtherBatch = false;
-
- for (const part of tooluseParts) {
- const isFileOpPart = isFileOp(part);
- const partNeedsApproval = needsApproval(part);
-
- if (isFileOpPart && partNeedsApproval) {
- if (!addedApprovalBatch) {
- groupedItems.push({ type: "batch", parts: readFileNeedsApproval });
- addedApprovalBatch = true;
- }
- } else if (isFileOpPart && !partNeedsApproval) {
- if (!addedOtherBatch) {
- groupedItems.push({ type: "batch", parts: readFileOther });
- addedOtherBatch = true;
- }
- } else {
- groupedItems.push({ type: "single", part });
- }
- }
-
- filteredProgressParts.forEach((part) => {
- groupedItems.push({ type: "progress", part });
- });
-
- return (
- <>
- {groupedItems.map((item, idx) => {
- if (item.type === "batch") {
- return (
-
- );
- } else if (item.type === "progress") {
- return (
-
- );
- } else {
- return (
-
- );
- }
- })}
- >
- );
-});
-
-AIToolUseGroup.displayName = "AIToolUseGroup";
diff --git a/frontend/app/aipanel/aitypes.ts b/frontend/app/aipanel/aitypes.ts
deleted file mode 100644
index fbce463a73..0000000000
--- a/frontend/app/aipanel/aitypes.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { ChatRequestOptions, FileUIPart, UIMessage, UIMessagePart } from "ai";
-
-type WaveUIDataTypes = {
- // pkg/aiusechat/uctypes/uctypes.go UIMessageDataUserFile
- userfile: {
- filename: string;
- size: number;
- mimetype: string;
- previewurl?: string;
- };
- // pkg/aiusechat/uctypes/uctypes.go UIMessageDataToolUse
- tooluse: {
- toolcallid: string;
- toolname: string;
- tooldesc: string;
- status: "pending" | "error" | "completed";
- runts?: number;
- errormessage?: string;
- approval?: "needs-approval" | "user-approved" | "user-denied" | "auto-approved" | "timeout";
- blockid?: string;
- writebackupfilename?: string;
- inputfilename?: string;
- };
-
- toolprogress: {
- toolcallid: string;
- toolname: string;
- statuslines: string[];
- };
-};
-
-export type WaveUIMessage = UIMessage;
-export type WaveUIMessagePart = UIMessagePart;
-
-export type UseChatSetMessagesType = (
- messages: WaveUIMessage[] | ((messages: WaveUIMessage[]) => WaveUIMessage[])
-) => void;
-
-export type UseChatSendMessageType = (
- message?:
- | (Omit & {
- id?: string;
- role?: "system" | "user" | "assistant";
- } & {
- text?: never;
- files?: never;
- messageId?: string;
- })
- | {
- text: string;
- files?: FileList | FileUIPart[];
- metadata?: unknown;
- parts?: never;
- messageId?: string;
- }
- | {
- files: FileList | FileUIPart[];
- metadata?: unknown;
- parts?: never;
- messageId?: string;
- },
- options?: ChatRequestOptions
-) => Promise;
diff --git a/frontend/app/aipanel/byokannouncement.tsx b/frontend/app/aipanel/byokannouncement.tsx
deleted file mode 100644
index 935cc4a3b0..0000000000
--- a/frontend/app/aipanel/byokannouncement.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { RpcApi } from "@/app/store/wshclientapi";
-import { TabRpcClient } from "@/app/store/wshrpcutil";
-import { WaveAIModel } from "./waveai-model";
-
-const BYOKAnnouncement = () => {
- const model = WaveAIModel.getInstance();
-
- const handleOpenConfig = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "action:other",
- props: {
- "action:type": "waveai:configuremodes:panel",
- },
- },
- { noresponse: true }
- );
- await model.openWaveAIConfig();
- };
-
- const handleViewDocs = () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "action:other",
- props: {
- "action:type": "waveai:viewdocs:panel",
- },
- },
- { noresponse: true }
- );
- };
-
- return (
-
-
-
-
-
New: BYOK & Local AI Support
-
- Wave AI now supports bring-your-own-key (BYOK) with OpenAI, Google Gemini, Azure, and
- OpenRouter, plus local models via Ollama, LM Studio, and other OpenAI-compatible providers.
-
-
-
-
-
- );
-};
-
-BYOKAnnouncement.displayName = "BYOKAnnouncement";
-
-export { BYOKAnnouncement };
diff --git a/frontend/app/aipanel/restorebackupmodal.tsx b/frontend/app/aipanel/restorebackupmodal.tsx
deleted file mode 100644
index 36b4be5b5d..0000000000
--- a/frontend/app/aipanel/restorebackupmodal.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { Modal } from "@/app/modals/modal";
-import { recordTEvent } from "@/app/store/global";
-import { useAtomValue } from "jotai";
-import { memo } from "react";
-import { WaveUIMessagePart } from "./aitypes";
-import { WaveAIModel } from "./waveai-model";
-
-interface RestoreBackupModalProps {
- part: WaveUIMessagePart & { type: "data-tooluse" };
-}
-
-export const RestoreBackupModal = memo(({ part }: RestoreBackupModalProps) => {
- const model = WaveAIModel.getInstance();
- const toolData = part.data;
- const status = useAtomValue(model.restoreBackupStatus);
- const error = useAtomValue(model.restoreBackupError);
-
- const formatTimestamp = (ts: number) => {
- if (!ts) return "";
- const date = new Date(ts);
- return date.toLocaleString();
- };
-
- const handleConfirm = () => {
- recordTEvent("waveai:revertfile", { "waveai:action": "revertfile:confirm" });
- model.restoreBackup(toolData.toolcallid, toolData.writebackupfilename, toolData.inputfilename);
- };
-
- const handleCancel = () => {
- recordTEvent("waveai:revertfile", { "waveai:action": "revertfile:cancel" });
- model.closeRestoreBackupModal();
- };
-
- const handleClose = () => {
- model.closeRestoreBackupModal();
- };
-
- if (status === "success") {
- return (
-
-
-
Backup Successfully Restored
-
- The file {toolData.inputfilename} has
- been restored to its previous state.
-
-
-
- );
- }
-
- if (status === "error") {
- return (
-
-
-
Failed to Restore Backup
-
- An error occurred while restoring the backup:
-
-
{error}
-
-
- );
- }
-
- const isProcessing = status === "processing";
-
- return (
-
-
-
Restore File Backup
-
- This will restore {toolData.inputfilename} {" "}
- to its state before this edit was made
- {toolData.runts && ({formatTimestamp(toolData.runts)}) }.
-
-
- Any changes made by this edit and subsequent edits will be lost.
-
-
-
- );
-});
-
-RestoreBackupModal.displayName = "RestoreBackupModal";
\ No newline at end of file
diff --git a/frontend/app/aipanel/telemetryrequired.tsx b/frontend/app/aipanel/telemetryrequired.tsx
deleted file mode 100644
index 692dec73d5..0000000000
--- a/frontend/app/aipanel/telemetryrequired.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { RpcApi } from "@/app/store/wshclientapi";
-import { TabRpcClient } from "@/app/store/wshrpcutil";
-import { cn } from "@/util/util";
-import { useState } from "react";
-import { WaveAIModel } from "./waveai-model";
-
-interface TelemetryRequiredMessageProps {
- className?: string;
-}
-
-const TelemetryRequiredMessage = ({ className }: TelemetryRequiredMessageProps) => {
- const [isEnabling, setIsEnabling] = useState(false);
-
- const handleEnableTelemetry = async () => {
- setIsEnabling(true);
- try {
- await RpcApi.WaveAIEnableTelemetryCommand(TabRpcClient);
- setTimeout(() => {
- WaveAIModel.getInstance().focusInput();
- }, 100);
- } catch (error) {
- console.error("Failed to enable telemetry:", error);
- setIsEnabling(false);
- }
- };
-
- return (
-
-
-
-
-
-
-
Wave AI
-
- Wave AI is free to use and provides integrated AI chat that can interact with your widgets,
- help you with code, analyze files, and assist with your terminal workflows.
-
-
-
-
-
-
-
-
Telemetry keeps Wave AI free
-
-
- To keep Wave AI free for everyone, we require a small amount of anonymous {" "}
- usage data (app version, feature usage, system info).
-
-
- This helps us block abuse by automated systems and ensure it's used by real
- people like you.
-
-
- We never collect your files, prompts, keystrokes, hostnames, or personally
- identifying information. Wave AI is powered by OpenAI's APIs, please refer to
- OpenAI's privacy policy for details on how they handle your data.
-
-
- For information about BYOK and local model support, see{" "}
-
- https://docs.waveterm.dev/waveai-modes
-
- .
-
-
-
- {isEnabling ? "Enabling..." : "Enable Telemetry and Continue"}
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-TelemetryRequiredMessage.displayName = "TelemetryRequiredMessage";
-
-export { TelemetryRequiredMessage };
diff --git a/frontend/app/aipanel/waveai-focus-utils.ts b/frontend/app/aipanel/waveai-focus-utils.ts
deleted file mode 100644
index dba3daccb2..0000000000
--- a/frontend/app/aipanel/waveai-focus-utils.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-export function findWaveAIPanel(element: HTMLElement): HTMLElement | null {
- let current: HTMLElement = element;
- while (current) {
- if (current.hasAttribute("data-waveai-panel")) {
- return current;
- }
- current = current.parentElement;
- }
- return null;
-}
-
-export function waveAIHasFocusWithin(focusTarget?: Element | null): boolean {
- if (focusTarget !== undefined) {
- if (focusTarget instanceof HTMLElement) {
- return findWaveAIPanel(focusTarget) != null;
- }
- return false;
- }
-
- const focused = document.activeElement;
- if (focused instanceof HTMLElement) {
- const waveAIPanel = findWaveAIPanel(focused);
- if (waveAIPanel) return true;
- }
-
- const sel = document.getSelection();
- if (sel && sel.anchorNode && sel.rangeCount > 0 && !sel.isCollapsed) {
- let anchor = sel.anchorNode;
- if (anchor instanceof Text) {
- anchor = anchor.parentElement;
- }
- if (anchor instanceof HTMLElement) {
- const waveAIPanel = findWaveAIPanel(anchor);
- if (waveAIPanel) return true;
- }
- }
-
- return false;
-}
-
-export function waveAIHasSelection(): boolean {
- const sel = document.getSelection();
- if (!sel || sel.rangeCount === 0 || sel.isCollapsed) {
- return false;
- }
-
- let anchor = sel.anchorNode;
- if (anchor instanceof Text) {
- anchor = anchor.parentElement;
- }
- if (anchor instanceof HTMLElement) {
- return findWaveAIPanel(anchor) != null;
- }
-
- return false;
-}
\ No newline at end of file
diff --git a/frontend/app/aipanel/waveai-model.tsx b/frontend/app/aipanel/waveai-model.tsx
deleted file mode 100644
index 194005adc6..0000000000
--- a/frontend/app/aipanel/waveai-model.tsx
+++ /dev/null
@@ -1,710 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import {
- UseChatSendMessageType,
- UseChatSetMessagesType,
- WaveUIMessage,
- WaveUIMessagePart,
-} from "@/app/aipanel/aitypes";
-import { FocusManager } from "@/app/store/focusManager";
-import { atoms, createBlock, getOrefMetaKeyAtom, getSettingsKeyAtom } from "@/app/store/global";
-import { globalStore } from "@/app/store/jotaiStore";
-import { isBuilderWindow } from "@/app/store/windowtype";
-import * as WOS from "@/app/store/wos";
-import { RpcApi } from "@/app/store/wshclientapi";
-import { TabRpcClient } from "@/app/store/wshrpcutil";
-import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
-import { BuilderFocusManager } from "@/builder/store/builder-focusmanager";
-import { getWebServerEndpoint } from "@/util/endpoints";
-import { base64ToArrayBuffer } from "@/util/util";
-import { ChatStatus } from "ai";
-import * as jotai from "jotai";
-import type React from "react";
-import {
- createDataUrl,
- createImagePreview,
- formatFileSizeError,
- isAcceptableFile,
- normalizeMimeType,
- resizeImage,
- validateFileSizeFromInfo,
-} from "./ai-utils";
-import type { AIPanelInputRef } from "./aipanelinput";
-
-export interface DroppedFile {
- id: string;
- file: File;
- name: string;
- type: string;
- size: number;
- previewUrl?: string;
-}
-
-const BuilderAIModeConfigs: Record = {
- "waveaibuilder@default": {
- "display:name": "Builder Default",
- "display:order": -2,
- "display:icon": "sparkles",
- "display:description": "Good mix of speed and accuracy\n(gpt-5.4 with minimal thinking)",
- "ai:provider": "wave",
- "ai:switchcompat": ["wavecloud"],
- "waveai:premium": true,
- },
- "waveaibuilder@deep": {
- "display:name": "Builder Deep",
- "display:order": -1,
- "display:icon": "lightbulb",
- "display:description": "Slower but most capable\n(gpt-5.4 with full reasoning)",
- "ai:provider": "wave",
- "ai:switchcompat": ["wavecloud"],
- "waveai:premium": true,
- },
-};
-
-export class WaveAIModel {
- private static instance: WaveAIModel | null = null;
- inputRef: React.RefObject | null = null;
- scrollToBottomCallback: (() => void) | null = null;
- useChatSendMessage: UseChatSendMessageType | null = null;
- useChatSetMessages: UseChatSetMessagesType | null = null;
- useChatStatus: ChatStatus = "ready";
- useChatStop: (() => void) | null = null;
- // Used for injecting Wave-specific message data into DefaultChatTransport's prepareSendMessagesRequest
- realMessage: AIMessage | null = null;
- orefContext: ORef;
- inBuilder: boolean = false;
- isAIStreaming = jotai.atom(false);
-
- widgetAccessAtom!: jotai.Atom;
- droppedFiles: jotai.PrimitiveAtom = jotai.atom([]);
- chatId!: jotai.PrimitiveAtom;
- currentAIMode!: jotai.PrimitiveAtom;
- aiModeConfigs!: jotai.Atom>;
- hasPremiumAtom!: jotai.Atom;
- defaultModeAtom!: jotai.Atom;
- errorMessage: jotai.PrimitiveAtom = jotai.atom(null) as jotai.PrimitiveAtom;
- containerWidth: jotai.PrimitiveAtom = jotai.atom(0);
- codeBlockMaxWidth!: jotai.Atom;
- inputAtom: jotai.PrimitiveAtom = jotai.atom("");
- isLoadingChatAtom: jotai.PrimitiveAtom = jotai.atom(false);
- isChatEmptyAtom: jotai.PrimitiveAtom = jotai.atom(true);
- isWaveAIFocusedAtom!: jotai.Atom;
- panelVisibleAtom!: jotai.Atom;
- restoreBackupModalToolCallId: jotai.PrimitiveAtom = jotai.atom(null) as jotai.PrimitiveAtom<
- string | null
- >;
- restoreBackupStatus: jotai.PrimitiveAtom<"idle" | "processing" | "success" | "error"> = jotai.atom("idle");
- restoreBackupError: jotai.PrimitiveAtom = jotai.atom(null) as jotai.PrimitiveAtom;
-
- private constructor(orefContext: ORef, inBuilder: boolean) {
- this.orefContext = orefContext;
- this.inBuilder = inBuilder;
- this.chatId = jotai.atom(null) as jotai.PrimitiveAtom;
- if (inBuilder) {
- this.aiModeConfigs = jotai.atom(BuilderAIModeConfigs) as jotai.Atom>;
- } else {
- this.aiModeConfigs = atoms.waveaiModeConfigAtom;
- }
-
- this.hasPremiumAtom = jotai.atom((get) => {
- const rateLimitInfo = get(atoms.waveAIRateLimitInfoAtom);
- return !rateLimitInfo || rateLimitInfo.unknown || rateLimitInfo.preq > 0;
- });
-
- this.widgetAccessAtom = jotai.atom((get) => {
- if (this.inBuilder) {
- return true;
- }
- const widgetAccessMetaAtom = getOrefMetaKeyAtom(this.orefContext, "waveai:widgetcontext");
- const value = get(widgetAccessMetaAtom);
- return value ?? true;
- });
-
- this.codeBlockMaxWidth = jotai.atom((get) => {
- const width = get(this.containerWidth);
- return width > 0 ? width - 35 : 0;
- });
-
- this.isWaveAIFocusedAtom = jotai.atom((get) => {
- if (this.inBuilder) {
- return get(BuilderFocusManager.getInstance().focusType) === "waveai";
- }
- return get(FocusManager.getInstance().focusType) === "waveai";
- });
-
- this.panelVisibleAtom = jotai.atom((get) => {
- if (this.inBuilder) {
- return true;
- }
- return get(WorkspaceLayoutModel.getInstance().panelVisibleAtom);
- });
-
- this.defaultModeAtom = jotai.atom((get) => {
- const telemetryEnabled = get(getSettingsKeyAtom("telemetry:enabled")) ?? false;
- if (this.inBuilder) {
- return telemetryEnabled ? "waveaibuilder@default" : "invalid";
- }
- const aiModeConfigs = get(this.aiModeConfigs);
- if (!telemetryEnabled) {
- let mode = get(getSettingsKeyAtom("waveai:defaultmode"));
- if (mode == null || mode.startsWith("waveai@")) {
- return "unknown";
- }
- return mode;
- }
- const hasPremium = get(this.hasPremiumAtom);
- const waveFallback = hasPremium ? "waveai@balanced" : "waveai@quick";
- let mode = get(getSettingsKeyAtom("waveai:defaultmode")) ?? waveFallback;
- if (!hasPremium && mode.startsWith("waveai@")) {
- mode = "waveai@quick";
- }
- const modeExists = aiModeConfigs != null && mode in aiModeConfigs;
- if (!modeExists) {
- mode = waveFallback;
- }
- return mode;
- });
-
- const defaultMode = globalStore.get(this.defaultModeAtom);
- this.currentAIMode = jotai.atom(defaultMode);
- }
-
- getPanelVisibleAtom(): jotai.Atom {
- return this.panelVisibleAtom;
- }
-
- static getInstance(): WaveAIModel {
- if (!WaveAIModel.instance) {
- let orefContext: ORef;
- if (isBuilderWindow()) {
- const builderId = globalStore.get(atoms.builderId);
- orefContext = WOS.makeORef("builder", builderId);
- } else {
- const tabId = globalStore.get(atoms.staticTabId);
- orefContext = WOS.makeORef("tab", tabId);
- }
- WaveAIModel.instance = new WaveAIModel(orefContext, isBuilderWindow());
- (window as any).WaveAIModel = WaveAIModel.instance;
- }
- return WaveAIModel.instance;
- }
-
- static resetInstance(): void {
- WaveAIModel.instance = null;
- }
-
- getUseChatEndpointUrl(): string {
- return `${getWebServerEndpoint()}/api/post-chat-message`;
- }
-
- async addFile(file: File): Promise {
- // Resize images before storing
- const processedFile = await resizeImage(file);
-
- const droppedFile: DroppedFile = {
- id: crypto.randomUUID(),
- file: processedFile,
- name: processedFile.name,
- type: processedFile.type,
- size: processedFile.size,
- };
-
- // Create 128x128 preview data URL for images
- if (processedFile.type.startsWith("image/")) {
- const previewDataUrl = await createImagePreview(processedFile);
- if (previewDataUrl) {
- droppedFile.previewUrl = previewDataUrl;
- }
- }
-
- const currentFiles = globalStore.get(this.droppedFiles);
- globalStore.set(this.droppedFiles, [...currentFiles, droppedFile]);
-
- return droppedFile;
- }
-
- async addFileFromRemoteUri(draggedFile: DraggedFile): Promise {
- if (draggedFile.isDir) {
- this.setError("Cannot add directories to Wave AI. Please select a file.");
- return;
- }
-
- try {
- const fileInfo = await RpcApi.FileInfoCommand(TabRpcClient, { info: { path: draggedFile.uri } }, null);
- if (fileInfo.notfound) {
- this.setError(`File not found: ${draggedFile.relName}`);
- return;
- }
- if (fileInfo.isdir) {
- this.setError("Cannot add directories to Wave AI. Please select a file.");
- return;
- }
-
- const mimeType = fileInfo.mimetype || "application/octet-stream";
- const fileSize = fileInfo.size || 0;
- const sizeError = validateFileSizeFromInfo(draggedFile.relName, fileSize, mimeType);
- if (sizeError) {
- this.setError(formatFileSizeError(sizeError));
- return;
- }
-
- const fileData = await RpcApi.FileReadCommand(TabRpcClient, { info: { path: draggedFile.uri } }, null);
- if (!fileData.data64) {
- this.setError(`Failed to read file: ${draggedFile.relName}`);
- return;
- }
-
- const buffer = base64ToArrayBuffer(fileData.data64);
- const file = new File([buffer], draggedFile.relName, { type: mimeType });
- if (!isAcceptableFile(file)) {
- this.setError(
- `File type not supported: ${draggedFile.relName}. Supported: images, PDFs, and text/code files.`
- );
- return;
- }
-
- await this.addFile(file);
- } catch (error) {
- console.error("Error handling FILE_ITEM drop:", error);
- const errorMsg = error instanceof Error ? error.message : String(error);
- this.setError(`Failed to add file: ${errorMsg}`);
- }
- }
-
- removeFile(fileId: string) {
- const currentFiles = globalStore.get(this.droppedFiles);
- const updatedFiles = currentFiles.filter((f) => f.id !== fileId);
- globalStore.set(this.droppedFiles, updatedFiles);
- }
-
- clearFiles() {
- const currentFiles = globalStore.get(this.droppedFiles);
-
- // Cleanup all preview URLs
- currentFiles.forEach((file) => {
- if (file.previewUrl) {
- URL.revokeObjectURL(file.previewUrl);
- }
- });
-
- globalStore.set(this.droppedFiles, []);
- }
-
- clearChat() {
- this.useChatStop?.();
- this.clearFiles();
- this.clearError();
- globalStore.set(this.isChatEmptyAtom, true);
- const newChatId = crypto.randomUUID();
- globalStore.set(this.chatId, newChatId);
-
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: this.orefContext,
- data: { "waveai:chatid": newChatId },
- });
-
- this.useChatSetMessages?.([]);
- }
-
- setError(message: string) {
- globalStore.set(this.errorMessage, message);
- }
-
- clearError() {
- globalStore.set(this.errorMessage, null);
- }
-
- registerInputRef(ref: React.RefObject) {
- this.inputRef = ref;
- }
-
- registerScrollToBottom(callback: () => void) {
- this.scrollToBottomCallback = callback;
- }
-
- registerUseChatData(
- sendMessage: UseChatSendMessageType,
- setMessages: UseChatSetMessagesType,
- status: ChatStatus,
- stop: () => void
- ) {
- this.useChatSendMessage = sendMessage;
- this.useChatSetMessages = setMessages;
- this.useChatStatus = status;
- this.useChatStop = stop;
- }
-
- scrollToBottom() {
- this.scrollToBottomCallback?.();
- }
-
- focusInput() {
- if (!this.inBuilder && !WorkspaceLayoutModel.getInstance().getAIPanelVisible()) {
- WorkspaceLayoutModel.getInstance().setAIPanelVisible(true);
- }
- if (this.inputRef?.current) {
- this.inputRef.current.focus();
- }
- }
-
- async reloadChatFromBackend(chatIdValue: string): Promise {
- const chatData = await RpcApi.GetWaveAIChatCommand(TabRpcClient, { chatid: chatIdValue });
- const messages: UIMessage[] = chatData?.messages ?? [];
- globalStore.set(this.isChatEmptyAtom, messages.length === 0);
- return messages as WaveUIMessage[];
- }
-
- async stopResponse() {
- this.useChatStop?.();
- await new Promise((resolve) => setTimeout(resolve, 500));
-
- const chatIdValue = globalStore.get(this.chatId);
- if (!chatIdValue) {
- return;
- }
- try {
- const messages = await this.reloadChatFromBackend(chatIdValue);
- this.useChatSetMessages?.(messages);
- } catch (error) {
- console.error("Failed to reload chat after stop:", error);
- }
- }
-
- getAndClearMessage(): AIMessage | null {
- const msg = this.realMessage;
- this.realMessage = null;
- return msg;
- }
-
- hasNonEmptyInput(): boolean {
- const input = globalStore.get(this.inputAtom);
- return input != null && input.trim().length > 0;
- }
-
- appendText(text: string, newLine?: boolean, opts?: { scrollToBottom?: boolean }) {
- const currentInput = globalStore.get(this.inputAtom);
- let newInput = currentInput;
-
- if (newInput.length > 0) {
- if (newLine) {
- if (!newInput.endsWith("\n")) {
- newInput += "\n";
- }
- } else if (!newInput.endsWith(" ") && !newInput.endsWith("\n")) {
- newInput += " ";
- }
- }
-
- newInput += text;
- globalStore.set(this.inputAtom, newInput);
-
- if (opts?.scrollToBottom && this.inputRef?.current) {
- setTimeout(() => this.inputRef.current.scrollToBottom(), 10);
- }
- }
-
- setModel(model: string) {
- RpcApi.SetMetaCommand(TabRpcClient, {
- oref: this.orefContext,
- meta: { "waveai:model": model },
- });
- }
-
- setWidgetAccess(enabled: boolean) {
- RpcApi.SetMetaCommand(TabRpcClient, {
- oref: this.orefContext,
- meta: { "waveai:widgetcontext": enabled },
- });
- }
-
- isValidMode(mode: string): boolean {
- const telemetryEnabled = globalStore.get(getSettingsKeyAtom("telemetry:enabled")) ?? false;
- if (mode.startsWith("waveai@") && !telemetryEnabled) {
- return false;
- }
-
- const aiModeConfigs = globalStore.get(this.aiModeConfigs);
- if (aiModeConfigs == null || !(mode in aiModeConfigs)) {
- return false;
- }
-
- return true;
- }
-
- setAIMode(mode: string) {
- if (!this.isValidMode(mode)) {
- this.setAIModeToDefault();
- } else {
- globalStore.set(this.currentAIMode, mode);
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: this.orefContext,
- data: { "waveai:mode": mode },
- });
- }
- }
-
- setAIModeToDefault() {
- const defaultMode = globalStore.get(this.defaultModeAtom);
- globalStore.set(this.currentAIMode, defaultMode);
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: this.orefContext,
- data: { "waveai:mode": null },
- });
- }
-
- async fixModeAfterConfigChange(): Promise {
- const rtInfo = await RpcApi.GetRTInfoCommand(TabRpcClient, {
- oref: this.orefContext,
- });
- const mode = rtInfo?.["waveai:mode"];
- if (mode == null || !this.isValidMode(mode)) {
- this.setAIModeToDefault();
- }
- }
-
- async getRTInfo(): Promise> {
- const rtInfo = await RpcApi.GetRTInfoCommand(TabRpcClient, {
- oref: this.orefContext,
- });
- return rtInfo ?? {};
- }
-
- async loadInitialChat(): Promise {
- const rtInfo = await RpcApi.GetRTInfoCommand(TabRpcClient, {
- oref: this.orefContext,
- });
- let chatIdValue = rtInfo?.["waveai:chatid"];
- if (chatIdValue == null) {
- chatIdValue = crypto.randomUUID();
- RpcApi.SetRTInfoCommand(TabRpcClient, {
- oref: this.orefContext,
- data: { "waveai:chatid": chatIdValue },
- });
- }
- globalStore.set(this.chatId, chatIdValue);
-
- const aiModeValue = rtInfo?.["waveai:mode"];
- if (aiModeValue == null) {
- const defaultMode = globalStore.get(this.defaultModeAtom);
- globalStore.set(this.currentAIMode, defaultMode);
- } else if (this.isValidMode(aiModeValue)) {
- globalStore.set(this.currentAIMode, aiModeValue);
- } else {
- this.setAIModeToDefault();
- }
-
- try {
- return await this.reloadChatFromBackend(chatIdValue);
- } catch (error) {
- console.error("Failed to load chat:", error);
- this.setError("Failed to load chat. Starting new chat...");
-
- this.clearChat();
- return [];
- }
- }
-
- async handleSubmit() {
- const input = globalStore.get(this.inputAtom);
- const droppedFiles = globalStore.get(this.droppedFiles);
-
- if (input.trim() === "/clear" || input.trim() === "/new") {
- this.clearChat();
- globalStore.set(this.inputAtom, "");
- return;
- }
-
- if (
- (!input.trim() && droppedFiles.length === 0) ||
- (this.useChatStatus !== "ready" && this.useChatStatus !== "error") ||
- globalStore.get(this.isLoadingChatAtom)
- ) {
- return;
- }
-
- this.clearError();
-
- const aiMessageParts: AIMessagePart[] = [];
- const uiMessageParts: WaveUIMessagePart[] = [];
-
- if (input.trim()) {
- aiMessageParts.push({ type: "text", text: input.trim() });
- uiMessageParts.push({ type: "text", text: input.trim() });
- }
-
- for (const droppedFile of droppedFiles) {
- const normalizedMimeType = normalizeMimeType(droppedFile.file);
- const dataUrl = await createDataUrl(droppedFile.file);
-
- aiMessageParts.push({
- type: "file",
- filename: droppedFile.name,
- mimetype: normalizedMimeType,
- url: dataUrl,
- size: droppedFile.file.size,
- previewurl: droppedFile.previewUrl,
- });
-
- uiMessageParts.push({
- type: "data-userfile",
- data: {
- filename: droppedFile.name,
- mimetype: normalizedMimeType,
- size: droppedFile.file.size,
- previewurl: droppedFile.previewUrl,
- },
- });
- }
-
- const realMessage: AIMessage = {
- messageid: crypto.randomUUID(),
- parts: aiMessageParts,
- };
- this.realMessage = realMessage;
-
- // console.log("SUBMIT MESSAGE", realMessage);
-
- this.useChatSendMessage?.({ parts: uiMessageParts });
-
- globalStore.set(this.isChatEmptyAtom, false);
- globalStore.set(this.inputAtom, "");
- this.clearFiles();
- }
-
- async uiLoadInitialChat() {
- globalStore.set(this.isLoadingChatAtom, true);
- const messages = await this.loadInitialChat();
- this.useChatSetMessages?.(messages);
- globalStore.set(this.isLoadingChatAtom, false);
- setTimeout(() => {
- this.scrollToBottom();
- }, 100);
- }
-
- async ensureRateLimitSet() {
- const currentInfo = globalStore.get(atoms.waveAIRateLimitInfoAtom);
- if (currentInfo != null) {
- return;
- }
- try {
- const rateLimitInfo = await RpcApi.GetWaveAIRateLimitCommand(TabRpcClient);
- if (rateLimitInfo != null) {
- globalStore.set(atoms.waveAIRateLimitInfoAtom, rateLimitInfo);
- }
- } catch (error) {
- console.error("Failed to fetch rate limit info:", error);
- }
- }
-
- handleAIFeedback(feedback: "good" | "bad") {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "waveai:feedback",
- props: {
- "waveai:feedback": feedback,
- },
- },
- { noresponse: true }
- );
- }
-
- requestWaveAIFocus() {
- if (this.inBuilder) {
- BuilderFocusManager.getInstance().setWaveAIFocused();
- } else {
- FocusManager.getInstance().requestWaveAIFocus();
- }
- }
-
- requestNodeFocus() {
- if (this.inBuilder) {
- BuilderFocusManager.getInstance().setAppFocused();
- } else {
- FocusManager.getInstance().requestNodeFocus();
- }
- }
-
- getChatId(): string {
- return globalStore.get(this.chatId);
- }
-
- toolUseSendApproval(toolcallid: string, approval: string) {
- RpcApi.WaveAIToolApproveCommand(TabRpcClient, {
- toolcallid: toolcallid,
- approval: approval,
- });
- }
-
- async openDiff(fileName: string, toolcallid: string) {
- const chatId = this.getChatId();
-
- if (!chatId || !fileName) {
- console.error("Missing chatId or fileName for opening diff", chatId, fileName);
- return;
- }
-
- const blockDef: BlockDef = {
- meta: {
- view: "aifilediff",
- file: fileName,
- "aifilediff:chatid": chatId,
- "aifilediff:toolcallid": toolcallid,
- },
- };
- await createBlock(blockDef, false, true);
- }
-
- async openWaveAIConfig() {
- const blockDef: BlockDef = {
- meta: {
- view: "waveconfig",
- file: "waveai.json",
- },
- };
- await createBlock(blockDef, false, true);
- }
-
- openRestoreBackupModal(toolcallid: string) {
- globalStore.set(this.restoreBackupModalToolCallId, toolcallid);
- }
-
- closeRestoreBackupModal() {
- globalStore.set(this.restoreBackupModalToolCallId, null);
- globalStore.set(this.restoreBackupStatus, "idle");
- globalStore.set(this.restoreBackupError, null);
- }
-
- async restoreBackup(toolcallid: string, backupFilePath: string, restoreToFileName: string) {
- globalStore.set(this.restoreBackupStatus, "processing");
- globalStore.set(this.restoreBackupError, null);
- try {
- await RpcApi.FileRestoreBackupCommand(TabRpcClient, {
- backupfilepath: backupFilePath,
- restoretofilename: restoreToFileName,
- });
- console.log("Backup restored successfully:", { toolcallid, backupFilePath, restoreToFileName });
- globalStore.set(this.restoreBackupStatus, "success");
- } catch (error) {
- console.error("Failed to restore backup:", error);
- const errorMsg = error?.message || String(error);
- globalStore.set(this.restoreBackupError, errorMsg);
- globalStore.set(this.restoreBackupStatus, "error");
- }
- }
-
- canCloseWaveAIPanel(): boolean {
- if (this.inBuilder) {
- return false;
- }
- return true;
- }
-
- closeWaveAIPanel() {
- if (this.inBuilder) {
- return;
- }
- WorkspaceLayoutModel.getInstance().setAIPanelVisible(false);
- }
-}
diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx
index e9c70a35df..297e9879e1 100644
--- a/frontend/app/app.tsx
+++ b/frontend/app/app.tsx
@@ -225,16 +225,6 @@ const MacOSFirstClickHandler = () => {
}
return null;
};
- const isAIPanelTarget = (target: EventTarget): boolean => {
- let elem = target as HTMLElement;
- while (elem != null) {
- if (elem.dataset?.aipanel) {
- return true;
- }
- elem = elem.parentElement;
- }
- return false;
- };
const handleMouseDown = (e: MouseEvent) => {
const timeDiff = Date.now() - windowFocusTime;
if (windowFocusTime != null && timeDiff < 50) {
@@ -248,11 +238,6 @@ const MacOSFirstClickHandler = () => {
console.log("macos first-click, focusing block", blockId);
refocusNode(blockId);
}, 10);
- } else if (isAIPanelTarget(e.target)) {
- setTimeout(() => {
- console.log("macos first-click, focusing AI panel");
- FocusManager.getInstance().setWaveAIFocused(true);
- }, 10);
}
console.log("macos first-click detected, canceled", timeDiff + "ms");
return;
diff --git a/frontend/app/block/block.scss b/frontend/app/block/block.scss
index 6b0fda769c..014cf200bb 100644
--- a/frontend/app/block/block.scss
+++ b/frontend/app/block/block.scss
@@ -84,6 +84,8 @@
font: var(--header-font);
border-bottom: 1px solid var(--border-color);
border-radius: var(--block-border-radius) var(--block-border-radius) 0 0;
+ position: relative;
+ z-index: 1;
.block-frame-default-header-iconview {
display: flex;
diff --git a/frontend/app/block/blockenv.ts b/frontend/app/block/blockenv.ts
index 8a529be11b..dec7559eeb 100644
--- a/frontend/app/block/blockenv.ts
+++ b/frontend/app/block/blockenv.ts
@@ -25,9 +25,7 @@ export type BlockEnv = WaveEnvSubset<{
electron: {
openExternal: WaveEnv["electron"]["openExternal"];
};
- rpc: {
- ActivityCommand: WaveEnv["rpc"]["ActivityCommand"];
- ConnEnsureCommand: WaveEnv["rpc"]["ConnEnsureCommand"];
+ rpc: { ConnEnsureCommand: WaveEnv["rpc"]["ConnEnsureCommand"];
ConnDisconnectCommand: WaveEnv["rpc"]["ConnDisconnectCommand"];
ConnConnectCommand: WaveEnv["rpc"]["ConnConnectCommand"];
SetConnectionsConfigCommand: WaveEnv["rpc"]["SetConnectionsConfigCommand"];
diff --git a/frontend/app/block/blockframe-header.tsx b/frontend/app/block/blockframe-header.tsx
index a70f323e71..80ce90d8b1 100644
--- a/frontend/app/block/blockframe-header.tsx
+++ b/frontend/app/block/blockframe-header.tsx
@@ -10,12 +10,11 @@ import {
} from "@/app/block/blockutil";
import { ConnectionButton } from "@/app/block/connectionbutton";
import { DurableSessionFlyover } from "@/app/block/durable-session-flyover";
+import { PortForwardStatusIndicator } from "@/app/block/port-forward-status";
import { getBlockBadgeAtom } from "@/app/store/badge";
import {
createBlockSplitHorizontally,
- createBlockSplitVertically,
- recordTEvent,
- refocusNode,
+ createBlockSplitVertically, refocusNode,
WOS,
} from "@/app/store/global";
import { globalStore } from "@/app/store/jotaiStore";
@@ -237,10 +236,7 @@ const BlockFrame_Header = ({
viewIconUnion = metaFrameIcon ?? viewIconUnion;
React.useEffect(() => {
- if (magnified && !preview && !prevMagifiedState.current) {
- waveEnv.rpc.ActivityCommand(TabRpcClient, { nummagnify: 1 });
- recordTEvent("action:magnify", { "block:view": viewName });
- }
+ if (magnified && !preview && !prevMagifiedState.current) { }
prevMagifiedState.current = magnified;
}, [magnified]);
@@ -280,6 +276,14 @@ const BlockFrame_Header = ({
divClassName="iconbutton disabled text-[13px] ml-[-4px]"
/>
)}
+ {useTermHeader && (
+
+ )}
{useTermHeader && badge && (
diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx
index 8ff2e2d0a7..0f7d231ed2 100644
--- a/frontend/app/block/blockframe.tsx
+++ b/frontend/app/block/blockframe.tsx
@@ -95,7 +95,6 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => {
const waveEnv = useWaveEnv();
const { nodeModel, viewModel, blockModel, preview, numBlocksInTab, children } = props;
const isFocused = jotai.useAtomValue(nodeModel.isFocused);
- const aiPanelVisible = jotai.useAtomValue(WorkspaceLayoutModel.getInstance().panelVisibleAtom);
const metaView = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "view"));
const viewIconUnion = util.useAtomValueSafe(viewModel?.viewIcon) ?? blockViewToIcon(metaView);
const customBg = util.useAtomValueSafe(viewModel?.blockBg);
@@ -170,7 +169,7 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => {
className={clsx("block", "block-frame-default", "block-" + nodeModel.blockId, {
"block-focused": isFocused || preview,
"block-preview": preview,
- "block-no-highlight": numBlocksInTab === 1 && !aiPanelVisible,
+ "block-no-highlight": numBlocksInTab === 1,
ephemeral: isEphemeral,
magnified: isMagnified,
})}
diff --git a/frontend/app/block/blockregistry.ts b/frontend/app/block/blockregistry.ts
index 5de7e05bd3..e68d0fadc2 100644
--- a/frontend/app/block/blockregistry.ts
+++ b/frontend/app/block/blockregistry.ts
@@ -3,7 +3,6 @@
import { BlockNodeModel } from "@/app/block/blocktypes";
import type { TabModel } from "@/app/store/tab-model";
-import { AiFileDiffViewModel } from "@/app/view/aifilediff/aifilediff";
import { LauncherViewModel } from "@/app/view/launcher/launcher";
import { PreviewModel } from "@/app/view/preview/preview-model";
import { ProcessViewerViewModel } from "@/app/view/processviewer/processviewer";
@@ -17,14 +16,12 @@ import { WaveConfigViewModel } from "../view/waveconfig/waveconfig-model";
import { blockViewToIcon, blockViewToName } from "./blockutil";
import { HelpViewModel } from "@/view/helpview/helpview";
import { TermViewModel } from "@/view/term/term-model";
-import { WaveAiModel } from "@/view/waveai/waveai";
import { WebViewModel } from "@/view/webview/webview";
const BlockRegistry: Map = new Map();
BlockRegistry.set("term", TermViewModel);
BlockRegistry.set("preview", PreviewModel);
BlockRegistry.set("web", WebViewModel);
-BlockRegistry.set("waveai", WaveAiModel);
BlockRegistry.set("cpuplot", SysinfoViewModel);
BlockRegistry.set("sysinfo", SysinfoViewModel);
BlockRegistry.set("vdom", VDomModel);
@@ -32,7 +29,6 @@ BlockRegistry.set("tips", QuickTipsViewModel);
BlockRegistry.set("help", HelpViewModel);
BlockRegistry.set("launcher", LauncherViewModel);
BlockRegistry.set("tsunami", TsunamiViewModel);
-BlockRegistry.set("aifilediff", AiFileDiffViewModel);
BlockRegistry.set("waveconfig", WaveConfigViewModel);
BlockRegistry.set("processviewer", ProcessViewerViewModel);
diff --git a/frontend/app/block/blockutil.tsx b/frontend/app/block/blockutil.tsx
index 3ef4d39821..be725496ad 100644
--- a/frontend/app/block/blockutil.tsx
+++ b/frontend/app/block/blockutil.tsx
@@ -33,9 +33,6 @@ export function blockViewToIcon(view: string): string {
if (view == "web") {
return "globe";
}
- if (view == "waveai") {
- return "sparkles";
- }
if (view == "help") {
return "circle-question";
}
@@ -61,9 +58,6 @@ export function blockViewToName(view: string): string {
if (view == "web") {
return "Web";
}
- if (view == "waveai") {
- return "WaveAI";
- }
if (view == "help") {
return "Help";
}
diff --git a/frontend/app/block/connectionbutton.tsx b/frontend/app/block/connectionbutton.tsx
index c5a9b635c3..0632ee964e 100644
--- a/frontend/app/block/connectionbutton.tsx
+++ b/frontend/app/block/connectionbutton.tsx
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
import { computeConnColorNum } from "@/app/block/blockutil";
-import { recordTEvent } from "@/app/store/global";
import { useWaveEnv } from "@/app/waveenv/waveenv";
import { IconButton } from "@/element/iconbutton";
import * as util from "@/util/util";
@@ -29,9 +28,7 @@ export const ConnectionButton = React.memo(
let connIconElem: React.ReactNode = null;
const connColorNum = computeConnColorNum(connStatus);
let color = `var(--conn-icon-color-${connColorNum})`;
- const clickHandler = function () {
- recordTEvent("action:other", { "action:type": "conndropdown", "action:initiator": "mouse" });
- setConnModalOpen(true);
+ const clickHandler = function () { setConnModalOpen(true);
};
let titleText = null;
let shouldSpin = false;
@@ -62,7 +59,12 @@ export const ConnectionButton = React.memo(
titleText = "Connected to " + connection;
let iconName = "arrow-right-arrow-left";
let iconSvg = null;
- if (connStatus?.status == "connecting") {
+ if (connStatus?.status == "connecting" && (connStatus?.reconnectattempt ?? 0) > 0) {
+ color = "var(--warning-color)";
+ titleText = `Reconnecting to ${connection} (attempt ${connStatus.reconnectattempt})`;
+ shouldSpin = true;
+ iconName = "rotate";
+ } else if (connStatus?.status == "connecting") {
color = "var(--warning-color)";
titleText = "Connecting to " + connection;
shouldSpin = false;
@@ -71,6 +73,10 @@ export const ConnectionButton = React.memo(
);
+ } else if (connStatus?.status == "disconnected" && (connStatus?.reconnectnextattempt ?? 0) > 0) {
+ color = "var(--grey-text-color)";
+ titleText = `Disconnected from ${connection} — retrying soon`;
+ iconName = "clock";
} else if (connStatus?.status == "error") {
color = "var(--error-color)";
titleText = "Error connecting to " + connection;
diff --git a/frontend/app/block/connstatusoverlay.tsx b/frontend/app/block/connstatusoverlay.tsx
index d4d6ad14b8..915ba15b98 100644
--- a/frontend/app/block/connstatusoverlay.tsx
+++ b/frontend/app/block/connstatusoverlay.tsx
@@ -43,6 +43,14 @@ function formatElapsedTime(elapsedMs: number): string {
return "more than a day";
}
+function formatCountdown(nextAttemptMs: number): string {
+ const remaining = Math.max(0, Math.ceil((nextAttemptMs - Date.now()) / 1000));
+ if (remaining <= 0) {
+ return "now";
+ }
+ return `${remaining}s`;
+}
+
const StalledOverlay = React.memo(
({
connName,
@@ -109,6 +117,154 @@ const StalledOverlay = React.memo(
);
StalledOverlay.displayName = "StalledOverlay";
+const DisconnectedOverlay = React.memo(
+ ({
+ connName,
+ connStatus,
+ overlayRefCallback,
+ onReconnect,
+ }: {
+ connName: string;
+ connStatus: ConnStatus;
+ overlayRefCallback: (el: HTMLDivElement | null) => void;
+ onReconnect: () => void;
+ }) => {
+ const [countdown, setCountdown] = React.useState("");
+ const hasCountdown = connStatus.reconnectnextattempt && connStatus.reconnectnextattempt > 0;
+
+ React.useEffect(() => {
+ if (!hasCountdown) {
+ return;
+ }
+ const update = () => {
+ setCountdown(formatCountdown(connStatus.reconnectnextattempt!));
+ };
+ update();
+ const interval = setInterval(update, 1000);
+ return () => clearInterval(interval);
+ }, [connStatus.reconnectnextattempt, hasCountdown]);
+
+ return (
+
+
+
+
+
Disconnected from "{connName}"
+ {connStatus.error && (
+
{connStatus.error}
+ )}
+ {hasCountdown && countdown !== "now" && (
+
+ Auto-retrying in {countdown}
+
+ )}
+
+
+
+ Reconnect
+
+
+
+
+ );
+ }
+);
+DisconnectedOverlay.displayName = "DisconnectedOverlay";
+
+const RetryingOverlay = React.memo(
+ ({
+ connName,
+ attempt,
+ overlayRefCallback,
+ }: {
+ connName: string;
+ attempt: number;
+ overlayRefCallback: (el: HTMLDivElement | null) => void;
+ }) => {
+ return (
+
+
+
+
+ Attempt {attempt} — connecting to "{connName}"…
+
+
+
+
+ );
+ }
+);
+RetryingOverlay.displayName = "RetryingOverlay";
+
+const CountdownOverlay = React.memo(
+ ({
+ connName,
+ connStatus,
+ overlayRefCallback,
+ onReconnectNow,
+ }: {
+ connName: string;
+ connStatus: ConnStatus;
+ overlayRefCallback: (el: HTMLDivElement | null) => void;
+ onReconnectNow: () => void;
+ }) => {
+ const [countdown, setCountdown] = React.useState("");
+
+ React.useEffect(() => {
+ if (!connStatus.reconnectnextattempt) {
+ return;
+ }
+ const update = () => {
+ setCountdown(formatCountdown(connStatus.reconnectnextattempt!));
+ };
+ update();
+ const interval = setInterval(update, 1000);
+ return () => clearInterval(interval);
+ }, [connStatus.reconnectnextattempt]);
+
+ return (
+
+
+
+
+ {connStatus.reconnecterror && (
+
+ Last attempt failed: {connStatus.reconnecterror}
+
+ )}
+
+ {countdown === "now" ? "Retrying now…" : `Retrying in ${countdown}`}
+
+
+
+
+ Reconnect now
+
+
+
+
+ );
+ }
+);
+CountdownOverlay.displayName = "CountdownOverlay";
+
export const ConnStatusOverlay = React.memo(
({
nodeModel,
@@ -217,7 +373,11 @@ export const ConnStatusOverlay = React.memo(
);
const showStalled = connStatus.status == "connected" && connStatus.connhealthstatus == "stalled";
- if (!showWshError && !showStalled && (isLayoutMode || connStatus.status == "connected" || connModalOpen)) {
+ const showRetrying = connStatus.status == "connecting" && (connStatus.reconnectattempt ?? 0) > 0;
+ const showCountdown = connStatus.status == "disconnected" && (connStatus.reconnectnextattempt ?? 0) > 0;
+ const showDisconnected = connStatus.status == "disconnected" && !connStatus.connected;
+
+ if (!showWshError && !showStalled && !showRetrying && !showCountdown && (isLayoutMode || connStatus.status == "connected" || connModalOpen)) {
return null;
}
@@ -227,6 +387,38 @@ export const ConnStatusOverlay = React.memo(
);
}
+ if (showRetrying) {
+ return (
+
+ );
+ }
+
+ if (showCountdown) {
+ return (
+
+ );
+ }
+
+ if (showDisconnected) {
+ return (
+
+ );
+ }
+
return (
@@ -268,4 +460,4 @@ export const ConnStatusOverlay = React.memo(
);
}
);
-ConnStatusOverlay.displayName = "ConnStatusOverlay";
+ConnStatusOverlay.displayName = "ConnStatusOverlay";
\ No newline at end of file
diff --git a/frontend/app/block/durable-session-flyover.tsx b/frontend/app/block/durable-session-flyover.tsx
index 7ab7fa0b10..205f4430ae 100644
--- a/frontend/app/block/durable-session-flyover.tsx
+++ b/frontend/app/block/durable-session-flyover.tsx
@@ -1,7 +1,6 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { recordTEvent } from "@/app/store/global";
import { TermViewModel } from "@/app/view/term/term-model";
import { useWaveEnv } from "@/app/waveenv/waveenv";
import * as util from "@/util/util";
@@ -43,9 +42,7 @@ interface StandardSessionContentProps {
}
function StandardSessionContent({ viewModel, onClose }: StandardSessionContentProps) {
- const handleRestartAsDurable = () => {
- recordTEvent("action:termdurable", { "action:type": "restartdurable" });
- onClose();
+ const handleRestartAsDurable = () => { onClose();
util.fireAndForget(() => viewModel.restartSessionWithDurability(true));
};
diff --git a/frontend/app/block/port-forward-status.tsx b/frontend/app/block/port-forward-status.tsx
new file mode 100644
index 0000000000..e019bcedc1
--- /dev/null
+++ b/frontend/app/block/port-forward-status.tsx
@@ -0,0 +1,138 @@
+// Copyright 2026, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+import { useWaveEnv } from "@/app/waveenv/waveenv";
+import { cn } from "@/util/util";
+import {
+ autoUpdate,
+ flip,
+ FloatingPortal,
+ offset,
+ safePolygon,
+ shift,
+ useFloating,
+ useHover,
+ useInteractions,
+} from "@floating-ui/react";
+import * as jotai from "jotai";
+import { useEffect, useRef, useState } from "react";
+import { BlockEnv } from "./blockenv";
+
+interface PortForwardStatusIndicatorProps {
+ blockId: string;
+ divClassName?: string;
+ placement?: "top" | "bottom" | "left" | "right";
+}
+
+export function PortForwardStatusIndicator({
+ blockId,
+ divClassName,
+ placement = "bottom",
+}: PortForwardStatusIndicatorProps) {
+ const waveEnv = useWaveEnv
();
+ const connName = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(blockId, "connection"));
+ const connStatus = jotai.useAtomValue(waveEnv.getConnStatusAtom(connName));
+ const forwardingRules = connStatus?.forwardingrules;
+
+ const [isOpen, setIsOpen] = useState(false);
+ const [isVisible, setIsVisible] = useState(false);
+ const timeoutRef = useRef(null);
+
+ const handleClose = () => {
+ setIsVisible(false);
+ if (timeoutRef.current !== null) {
+ window.clearTimeout(timeoutRef.current);
+ }
+ timeoutRef.current = window.setTimeout(() => {
+ setIsOpen(false);
+ }, 300);
+ };
+
+ const { refs, floatingStyles, context } = useFloating({
+ open: isOpen,
+ onOpenChange: (open) => {
+ if (open) {
+ setIsOpen(true);
+ if (timeoutRef.current !== null) {
+ window.clearTimeout(timeoutRef.current);
+ }
+ timeoutRef.current = window.setTimeout(() => {
+ setIsVisible(true);
+ }, 300);
+ } else {
+ setIsVisible(false);
+ if (timeoutRef.current !== null) {
+ window.clearTimeout(timeoutRef.current);
+ }
+ timeoutRef.current = window.setTimeout(() => {
+ setIsOpen(false);
+ }, 300);
+ }
+ },
+ placement,
+ middleware: [offset(10), flip(), shift({ padding: 12 })],
+ whileElementsMounted: autoUpdate,
+ });
+
+ useEffect(() => {
+ return () => {
+ if (timeoutRef.current !== null) {
+ window.clearTimeout(timeoutRef.current);
+ }
+ };
+ }, []);
+
+ const hover = useHover(context, {
+ handleClose: safePolygon(),
+ });
+ const { getReferenceProps, getFloatingProps } = useInteractions([hover]);
+
+ // Don't render if no forwarding rules are active
+ if (!forwardingRules || forwardingRules.length === 0) {
+ return null;
+ }
+
+ const count = forwardingRules.length;
+
+ return (
+ <>
+
+
+ {count}
+
+ {isOpen && (
+
+ e.stopPropagation()}
+ onFocusCapture={(e) => e.stopPropagation()}
+ onClick={(e) => e.stopPropagation()}
+ >
+
+
+
+ Port Forwarding
+
+
+ {forwardingRules.map((rule, idx) => (
+
+ {rule}
+
+ ))}
+
+
+
+
+ )}
+ >
+ );
+}
\ No newline at end of file
diff --git a/frontend/app/hook/useLongClick.tsx b/frontend/app/hook/useLongClick.tsx
index 86b118820e..c73bb92f22 100644
--- a/frontend/app/hook/useLongClick.tsx
+++ b/frontend/app/hook/useLongClick.tsx
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
export const useLongClick = (ref, onClick, onLongClick, disabled = false, ms = 300) => {
const timerRef = useRef(null);
const [longClickTriggered, setLongClickTriggered] = useState(false);
+ const [mounted, setMounted] = useState(false);
const startPress = useCallback(
(e: React.MouseEvent) => {
@@ -37,6 +38,10 @@ export const useLongClick = (ref, onClick, onLongClick, disabled = false, ms = 3
[longClickTriggered, onClick]
);
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
useEffect(() => {
const element = ref.current;
@@ -53,7 +58,7 @@ export const useLongClick = (ref, onClick, onLongClick, disabled = false, ms = 3
element.removeEventListener("mouseleave", stopPress);
element.removeEventListener("click", handleClick);
};
- }, [ref.current, startPress, stopPress, handleClick]);
+ }, [disabled, startPress, stopPress, handleClick, mounted]);
return ref;
};
diff --git a/frontend/app/modals/about.tsx b/frontend/app/modals/about.tsx
index d3a43d386d..de87c0ce1c 100644
--- a/frontend/app/modals/about.tsx
+++ b/frontend/app/modals/about.tsx
@@ -15,11 +15,10 @@ import { Modal } from "./modal";
interface AboutModalVProps {
versionString: string;
- updaterChannel: string;
onClose: () => void;
}
-const AboutModalV = ({ versionString, updaterChannel, onClose }: AboutModalVProps) => {
+const AboutModalV = ({ versionString, onClose }: AboutModalVProps) => {
const currentDate = new Date();
return (
@@ -37,8 +36,6 @@ const AboutModalV = ({ versionString, updaterChannel, onClose }: AboutModalVProp
Client Version {versionString}
-
- Update Channel: {updaterChannel}
{
const fullConfig = useAtomValue(atoms.fullConfigAtom);
const versionString = `${fullConfig?.version ?? ""} (${isDev() ? "dev-" : ""}${fullConfig?.buildtime ?? ""})`;
- const updaterChannel = fullConfig?.settings?.["autoupdate:channel"] ?? "latest";
useEffect(() => {
- fireAndForget(async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- { event: "action:other", props: { "action:type": "about" } },
- { noresponse: true }
- );
- });
+ fireAndForget(async () => { });
}, []);
return (
modalsModel.popModal()}
/>
);
diff --git a/frontend/app/monaco/schemaendpoints.ts b/frontend/app/monaco/schemaendpoints.ts
index 5365d1c739..a272ca120f 100644
--- a/frontend/app/monaco/schemaendpoints.ts
+++ b/frontend/app/monaco/schemaendpoints.ts
@@ -1,11 +1,9 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import aipresetsSchema from "../../../schema/aipresets.json";
import backgroundsSchema from "../../../schema/backgrounds.json";
import connectionsSchema from "../../../schema/connections.json";
import settingsSchema from "../../../schema/settings.json";
-import waveaiSchema from "../../../schema/waveai.json";
import widgetsSchema from "../../../schema/widgets.json";
type SchemaInfo = {
@@ -25,21 +23,11 @@ const MonacoSchemas: SchemaInfo[] = [
fileMatch: ["*/WAVECONFIGPATH/connections.json"],
schema: connectionsSchema,
},
- {
- uri: "wave://schema/aipresets.json",
- fileMatch: ["*/WAVECONFIGPATH/presets/ai.json"],
- schema: aipresetsSchema,
- },
{
uri: "wave://schema/backgrounds.json",
fileMatch: ["*/WAVECONFIGPATH/backgrounds.json"],
schema: backgroundsSchema,
},
- {
- uri: "wave://schema/waveai.json",
- fileMatch: ["*/WAVECONFIGPATH/waveai.json"],
- schema: waveaiSchema,
- },
{
uri: "wave://schema/widgets.json",
fileMatch: ["*/WAVECONFIGPATH/widgets.json"],
diff --git a/frontend/app/onboarding/fakechat.tsx b/frontend/app/onboarding/fakechat.tsx
deleted file mode 100644
index 00eac0598c..0000000000
--- a/frontend/app/onboarding/fakechat.tsx
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { WaveStreamdown } from "@/app/element/streamdown";
-import { memo, useEffect, useRef, useState } from "react";
-
-interface ChatConfig {
- userPrompt: string;
- toolName: string;
- toolDescription: string;
- markdownResponse: string;
-}
-
-const chatConfigs: ChatConfig[] = [
- {
- userPrompt: "Check out ~/waveterm and summarize the project — what it does and how it's organized.",
- toolName: "read_dir",
- toolDescription: 'reading directory "~/waveterm"',
- markdownResponse: `Here's a quick, file-structure–driven overview of this repo (Wave Terminal):
-
-## What it is
-- Electron + React front end with a Go backend ("wavesrv"). Provides a terminal with GUI widgets, previews, web, and AI. (README.md)
-- Licensed Apache-2.0. (LICENSE)
-
-## Architecture at a glance
-- **Electron main process:** \`emain/*.ts\` configures windows, menus, preload scripts, updater, and ties into the Go backend via local RPC. (\`emain/\`)
-- **Renderer UI:** React/TS built with Vite, Tailwind. (\`frontend/\`, \`index.html\`, \`electron.vite.config.ts\`)
-- **Go backend ("wavesrv"):** starts services, web and websocket listeners, telemetry loops, config watcher, local RPC, filestore and SQLite-backed object store. (\`cmd/server/main-server.go\`, \`pkg/*\`)
-- **CLI/helper ("wsh"):** built for multiple OS/arch; used for shell integration and remote operations. (\`cmd/wsh/\`, \`Taskfile.yml build:wsh\`)
-
-## Key directories
-- **cmd/:** entrypoints and generators
- - \`server/\`: wavesrv main
- - \`generategs/\`, \`generatego/\`: TS/Go bindings generation
- - \`wsh/\`: shell helper
-
-- **pkg/:** backend packages
- - \`wcore/\`: startup coordination, initial data, window/workspace creation
- - \`web/\`: HTTP+WS servers, Unix listener
- - \`waveai/\`: AI backends (OpenAI, Anthropic, Google, Perplexity)
- - \`wshrpc\`, \`wshutil\`, \`wslconn\`, \`remote/*\`: local/remote RPC, WSL, SSH, fileshare
- - \`wstore/\`: persistent store (SQLite via sqlx); \`waveobj/\`: object model
- - \`filestore/\`: local file cache/storage
- - \`telemetry/\`: metrics/events, periodic reporting`,
- },
- {
- userPrompt: "can you analyze the log output in my terminal?",
- toolName: "term_get_scrollback",
- toolDescription: "reading terminal output from widget",
- markdownResponse: `Here's what your log shows:
-
-- Normal startup/init at 18:06:51 (router/register, DBGetAllObjsByType, Stat/Read).
-- Many FocusWindow events — normal UI activity.
-- 18:08:28.558 WebSocket ReadPump error: close 1001 (going away) — usually a tab/app reload, sleep, or brief network hiccup.
-- 18:08:29 re-init + new WebSocket connection — successful auto-reconnect.
-- SetMetaCommand and periodic filestore flushes with err: — normal; no failures.
-- No panics/fatal errors; just the transient disconnect.
-
-If that disconnect was unexpected, check for a tab reload/sleep at 18:08:28, and watch for repeats.
-
-Want deeper triage? Run these and paste results:
-
-\`\`\`bash
-# 1) Scan recent logs for problems
-grep -Ei 'error|panic|fatal|websocket|1006|1011' \\
- "$HOME/Library/Application Support/waveterm-dev/waveapp.log" | tail -n 200
-
-# 2) Inspect around the disconnect window
-awk '($0 ~ /2025-10-10 18:08:2[0-9]/){print}' \\
- "$HOME/Library/Application Support/waveterm-dev/waveapp.log"
-
-# 3) Live follow for recurring drops
-tail -f "$HOME/Library/Application Support/waveterm-dev/waveapp.log" \\
- | grep -Ei 'error|panic|fatal|websocket|close'
-\`\`\`
-
-Need me to look at a longer slice (e.g., last 1000 lines) or a different time range?`,
- },
-];
-
-const AIThinking = memo(() => (
-
-
-
-
-
-
-
AI is thinking...
-
-));
-
-AIThinking.displayName = "AIThinking";
-
-const FakeToolCall = memo(({ toolName, toolDescription }: { toolName: string; toolDescription: string }) => {
- return (
-
-
✓
-
-
{toolName}
-
{toolDescription}
-
-
- );
-});
-
-FakeToolCall.displayName = "FakeToolCall";
-
-const FakeUserMessage = memo(({ userPrompt }: { userPrompt: string }) => {
- return (
-
- );
-});
-
-FakeUserMessage.displayName = "FakeUserMessage";
-
-const FakeAssistantMessage = memo(({ config, onComplete }: { config: ChatConfig; onComplete?: () => void }) => {
- const [phase, setPhase] = useState<"thinking" | "tool" | "streaming">("thinking");
- const [streamedText, setStreamedText] = useState("");
-
- useEffect(() => {
- const timeouts: NodeJS.Timeout[] = [];
- let streamInterval: NodeJS.Timeout | null = null;
-
- const runAnimation = () => {
- setPhase("thinking");
- setStreamedText("");
-
- timeouts.push(
- setTimeout(() => {
- setPhase("tool");
- }, 2000)
- );
-
- timeouts.push(
- setTimeout(() => {
- setPhase("streaming");
- }, 4000)
- );
-
- timeouts.push(
- setTimeout(() => {
- let currentIndex = 0;
- streamInterval = setInterval(() => {
- if (currentIndex >= config.markdownResponse.length) {
- if (streamInterval) {
- clearInterval(streamInterval);
- streamInterval = null;
- }
- if (onComplete) {
- onComplete();
- }
- return;
- }
- currentIndex += 10;
- setStreamedText(config.markdownResponse.slice(0, currentIndex));
- }, 100);
- }, 4000)
- );
- };
-
- runAnimation();
-
- return () => {
- timeouts.forEach(clearTimeout);
- if (streamInterval) {
- clearInterval(streamInterval);
- }
- };
- }, [config.markdownResponse, onComplete]);
-
- return (
-
-
- {phase === "thinking" &&
}
- {phase === "tool" && (
- <>
-
-
-
-
- >
- )}
- {phase === "streaming" && (
- <>
-
-
-
-
- >
- )}
-
-
- );
-});
-
-FakeAssistantMessage.displayName = "FakeAssistantMessage";
-
-const FakeAIPanelHeader = memo(() => {
- return (
-
-
-
- Wave AI
-
-
-
-
- Context
-
-
-
- ON
-
-
-
-
-
-
-
-
-
- );
-});
-
-FakeAIPanelHeader.displayName = "FakeAIPanelHeader";
-
-export const FakeChat = memo(() => {
- const scrollRef = useRef(null);
- const [chatIndex, setChatIndex] = useState(1);
- const config = chatConfigs[chatIndex] || chatConfigs[0];
-
- useEffect(() => {
- const interval = setInterval(() => {
- if (scrollRef.current) {
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
- }
- }, 1000);
-
- return () => clearInterval(interval);
- }, []);
-
- const handleComplete = () => {
- setTimeout(() => {
- setChatIndex((prev) => (prev + 1) % chatConfigs.length);
- }, 2000);
- };
-
- return (
-
- );
-});
-
-FakeChat.displayName = "FakeChat";
diff --git a/frontend/app/onboarding/onboarding-durable.tsx b/frontend/app/onboarding/onboarding-durable.tsx
index b716b3d7da..13acbb5b44 100644
--- a/frontend/app/onboarding/onboarding-durable.tsx
+++ b/frontend/app/onboarding/onboarding-durable.tsx
@@ -23,15 +23,7 @@ export const DurableSessionPage = ({
const handleFireClick = () => {
setFireClicked(!fireClicked);
- if (!fireClicked) {
- RpcApi.RecordTEventCommand(TabRpcClient, {
- event: "onboarding:fire",
- props: {
- "onboarding:feature": "durable",
- "onboarding:version": CurrentOnboardingVersion,
- },
- });
- }
+ if (!fireClicked) { }
};
return (
diff --git a/frontend/app/onboarding/onboarding-features.tsx b/frontend/app/onboarding/onboarding-features.tsx
index b47bbc4e1a..e47dadd9a4 100644
--- a/frontend/app/onboarding/onboarding-features.tsx
+++ b/frontend/app/onboarding/onboarding-features.tsx
@@ -10,101 +10,13 @@ import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { isMacOS } from "@/util/platformutil";
import { useEffect, useState } from "react";
-import { FakeChat } from "./fakechat";
import { EditBashrcCommand, ViewLogoCommand, ViewShortcutsCommand } from "./onboarding-command";
import { CurrentOnboardingVersion } from "./onboarding-common";
import { DurableSessionPage } from "./onboarding-durable";
import { OnboardingFooter } from "./onboarding-features-footer";
import { FakeLayout } from "./onboarding-layout";
-type FeaturePageName = "waveai" | "durable" | "magnify" | "files";
-
-export const WaveAIPage = ({ onNext, onSkip }: { onNext: () => void; onSkip: () => void }) => {
- const isMac = isMacOS();
- const shortcutKey = isMac ? "⌘-Shift-A" : "Alt-Shift-A";
- const [fireClicked, setFireClicked] = useState(false);
-
- const handleFireClick = () => {
- setFireClicked(!fireClicked);
- if (!fireClicked) {
- RpcApi.RecordTEventCommand(TabRpcClient, {
- event: "onboarding:fire",
- props: {
- "onboarding:feature": "waveai",
- "onboarding:version": CurrentOnboardingVersion,
- },
- });
- }
- };
-
- return (
-
-
-
-
-
-
-
- AI
-
-
-
-
- Wave AI is your terminal assistant with context. I can read your terminal output,
- analyze widgets, read/write files, and help you solve problems faster.
-
-
-
-
-
- Toggle the Wave AI panel with the{" "}
-
-
- AI
- {" "}
- button in the header (top left)
-
-
-
-
-
-
- Or use the keyboard shortcut{" "}
-
- {shortcutKey}
- {" "}
- to quickly toggle
-
-
-
-
-
-
- Bring your own API keys or run local models with Ollama, LM Studio, and other
- OpenAI-compatible providers
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
+type FeaturePageName = "durable" | "magnify" | "files";
export const MagnifyBlocksPage = ({
onNext,
@@ -122,13 +34,6 @@ export const MagnifyBlocksPage = ({
const handleFireClick = () => {
setFireClicked(!fireClicked);
if (!fireClicked) {
- RpcApi.RecordTEventCommand(TabRpcClient, {
- event: "onboarding:fire",
- props: {
- "onboarding:feature": "magnify",
- "onboarding:version": CurrentOnboardingVersion,
- },
- });
}
};
@@ -167,7 +72,7 @@ export const MagnifyBlocksPage = ({
-
+
);
};
@@ -180,13 +85,6 @@ export const FilesPage = ({ onFinish, onPrev }: { onFinish: () => void; onPrev?:
const handleFireClick = () => {
setFireClicked(!fireClicked);
if (!fireClicked) {
- RpcApi.RecordTEventCommand(TabRpcClient, {
- event: "onboarding:fire",
- props: {
- "onboarding:feature": "wsh",
- "onboarding:version": CurrentOnboardingVersion,
- },
- });
}
};
@@ -259,13 +157,13 @@ export const FilesPage = ({ onFinish, onPrev }: { onFinish: () => void; onPrev?:
{commands[commandIndex](handleCommandComplete)}
-
+
);
};
export const OnboardingFeatures = ({ onComplete }: { onComplete: () => void }) => {
- const [currentPage, setCurrentPage] = useState("waveai");
+ const [currentPage, setCurrentPage] = useState("durable");
useEffect(() => {
const clientId = ClientModel.getInstance().clientId;
@@ -273,18 +171,10 @@ export const OnboardingFeatures = ({ onComplete }: { onComplete: () => void }) =
oref: WOS.makeORef("client", clientId),
meta: { "onboarding:lastversion": CurrentOnboardingVersion },
});
- RpcApi.RecordTEventCommand(TabRpcClient, {
- event: "onboarding:start",
- props: {
- "onboarding:version": CurrentOnboardingVersion,
- },
- });
}, []);
const handleNext = () => {
- if (currentPage === "waveai") {
- setCurrentPage("durable");
- } else if (currentPage === "durable") {
+ if (currentPage === "durable") {
setCurrentPage("magnify");
} else if (currentPage === "magnify") {
setCurrentPage("files");
@@ -292,9 +182,7 @@ export const OnboardingFeatures = ({ onComplete }: { onComplete: () => void }) =
};
const handlePrev = () => {
- if (currentPage === "durable") {
- setCurrentPage("waveai");
- } else if (currentPage === "magnify") {
+ if (currentPage === "magnify") {
setCurrentPage("durable");
} else if (currentPage === "files") {
setCurrentPage("magnify");
@@ -302,10 +190,6 @@ export const OnboardingFeatures = ({ onComplete }: { onComplete: () => void }) =
};
const handleSkip = () => {
- RpcApi.RecordTEventCommand(TabRpcClient, {
- event: "onboarding:skip",
- props: {},
- });
onComplete();
};
@@ -315,9 +199,6 @@ export const OnboardingFeatures = ({ onComplete }: { onComplete: () => void }) =
let pageComp: React.JSX.Element = null;
switch (currentPage) {
- case "waveai":
- pageComp = ;
- break;
case "durable":
pageComp = ;
break;
diff --git a/frontend/app/onboarding/onboarding-starask.tsx b/frontend/app/onboarding/onboarding-starask.tsx
index bb7678ab2a..7297e0a100 100644
--- a/frontend/app/onboarding/onboarding-starask.tsx
+++ b/frontend/app/onboarding/onboarding-starask.tsx
@@ -14,16 +14,7 @@ type StarAskPageProps = {
};
export function StarAskPage({ onClose, page = "upgrade" }: StarAskPageProps) {
- const handleStarClick = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "onboarding:githubstar",
- props: { "onboarding:githubstar": "star", "onboarding:page": page },
- },
- { noresponse: true }
- );
- const clientId = ClientModel.getInstance().clientId;
+ const handleStarClick = async () => { const clientId = ClientModel.getInstance().clientId;
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("client", clientId),
meta: { "onboarding:githubstar": true },
@@ -32,16 +23,7 @@ export function StarAskPage({ onClose, page = "upgrade" }: StarAskPageProps) {
onClose();
};
- const handleAlreadyStarred = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "onboarding:githubstar",
- props: { "onboarding:githubstar": "already", "onboarding:page": page },
- },
- { noresponse: true }
- );
- const clientId = ClientModel.getInstance().clientId;
+ const handleAlreadyStarred = async () => { const clientId = ClientModel.getInstance().clientId;
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("client", clientId),
meta: { "onboarding:githubstar": true },
@@ -49,28 +31,10 @@ export function StarAskPage({ onClose, page = "upgrade" }: StarAskPageProps) {
onClose();
};
- const handleRepoLinkClick = () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "action:link",
- props: { "action:type": "githubrepo", "onboarding:page": page },
- },
- { noresponse: true }
- );
- window.open("https://github.com/wavetermdev/waveterm", "_blank");
+ const handleRepoLinkClick = () => { window.open("https://github.com/wavetermdev/waveterm", "_blank");
};
- const handleMaybeLater = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "onboarding:githubstar",
- props: { "onboarding:githubstar": "later", "onboarding:page": page },
- },
- { noresponse: true }
- );
- const clientId = ClientModel.getInstance().clientId;
+ const handleMaybeLater = async () => { const clientId = ClientModel.getInstance().clientId;
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("client", clientId),
meta: { "onboarding:githubstar": false },
diff --git a/frontend/app/onboarding/onboarding-upgrade-minor.tsx b/frontend/app/onboarding/onboarding-upgrade-minor.tsx
index b165c0ce7f..d9f0489dc1 100644
--- a/frontend/app/onboarding/onboarding-upgrade-minor.tsx
+++ b/frontend/app/onboarding/onboarding-upgrade-minor.tsx
@@ -130,16 +130,7 @@ const UpgradeOnboardingMinor = () => {
};
}, []);
- const handleStarClick = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "onboarding:githubstar",
- props: { "onboarding:githubstar": "star", "onboarding:page": "minorupgrade" },
- },
- { noresponse: true }
- );
- const clientId = ClientModel.getInstance().clientId;
+ const handleStarClick = async () => { const clientId = ClientModel.getInstance().clientId;
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("client", clientId),
meta: { "onboarding:githubstar": true },
@@ -148,16 +139,7 @@ const UpgradeOnboardingMinor = () => {
setPageName("features");
};
- const handleAlreadyStarred = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "onboarding:githubstar",
- props: { "onboarding:githubstar": "already", "onboarding:page": "minorupgrade" },
- },
- { noresponse: true }
- );
- const clientId = ClientModel.getInstance().clientId;
+ const handleAlreadyStarred = async () => { const clientId = ClientModel.getInstance().clientId;
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("client", clientId),
meta: { "onboarding:githubstar": true },
@@ -165,16 +147,7 @@ const UpgradeOnboardingMinor = () => {
setPageName("features");
};
- const handleMaybeLater = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "onboarding:githubstar",
- props: { "onboarding:githubstar": "later", "onboarding:page": "minorupgrade" },
- },
- { noresponse: true }
- );
- const clientId = ClientModel.getInstance().clientId;
+ const handleMaybeLater = async () => { const clientId = ClientModel.getInstance().clientId;
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("client", clientId),
meta: { "onboarding:githubstar": false },
diff --git a/frontend/app/onboarding/onboarding.tsx b/frontend/app/onboarding/onboarding.tsx
index ba139e81df..ad5886737e 100644
--- a/frontend/app/onboarding/onboarding.tsx
+++ b/frontend/app/onboarding/onboarding.tsx
@@ -7,49 +7,31 @@ import { FlexiModal } from "@/app/modals/modal";
import { OnboardingGradientBg } from "@/app/onboarding/onboarding-common";
import { OnboardingFeatures } from "@/app/onboarding/onboarding-features";
import { ClientModel } from "@/app/store/client-model";
-import { useSettingsKeyAtom } from "@/app/store/global";
import { disableGlobalKeybindings, enableGlobalKeybindings, globalRefocus } from "@/app/store/keymodel";
import { modalsModel } from "@/app/store/modalmodel";
import * as WOS from "@/app/store/wos";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
-import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
-import * as services from "@/store/services";
import { fireAndForget } from "@/util/util";
+import * as services from "@/store/services";
import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import { useEffect, useRef, useState } from "react";
import { debounce } from "throttle-debounce";
-// Page flow:
-// init -> (telemetry enabled) -> features
-// init -> (telemetry disabled) -> notelemetrystar -> features
-
-type PageName = "init" | "notelemetrystar" | "features";
+type PageName = "init" | "features";
const pageNameAtom: PrimitiveAtom = atom("init");
const InitPage = ({
isCompact,
- telemetryUpdateFn,
}: {
isCompact: boolean;
- telemetryUpdateFn: (value: boolean) => Promise;
}) => {
- const telemetrySetting = useSettingsKeyAtom("telemetry:enabled");
const clientData = useAtomValue(ClientModel.getInstance().clientAtom);
- const [telemetryEnabled, setTelemetryEnabled] = useState(!!telemetrySetting);
const setPageName = useSetAtom(pageNameAtom);
const handleStarClick = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "onboarding:githubstar",
- props: { "onboarding:githubstar": "star", "onboarding:page": "init" },
- },
- { noresponse: true }
- );
const clientId = ClientModel.getInstance().clientId;
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("client", clientId),
@@ -61,22 +43,9 @@ const InitPage = ({
if (!clientData?.tosagreed) {
fireAndForget(() => services.ClientService.AgreeTos());
}
- if (telemetryEnabled) {
- WorkspaceLayoutModel.getInstance().setAIPanelVisible(true);
- }
- setPageName(telemetryEnabled ? "features" : "notelemetrystar");
- };
-
- const setTelemetry = (value: boolean) => {
- fireAndForget(() =>
- telemetryUpdateFn(value).then(() => {
- setTelemetryEnabled(value);
- })
- );
+ setPageName("features");
};
- const label = telemetryEnabled ? "Enabled" : "Disabled";
-
return (
-
-
-
-
-
-
-
- setTelemetry(e.target.checked)}
- className="cursor-pointer accent-gray-500"
- />
- {label}
-
-
-
@@ -190,80 +131,6 @@ const InitPage = ({
);
};
-const NoTelemetryStarPage = ({ isCompact }: { isCompact: boolean }) => {
- const setPageName = useSetAtom(pageNameAtom);
-
- const handleStarClick = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "onboarding:githubstar",
- props: { "onboarding:githubstar": "star", "onboarding:page": "notelemetry" },
- },
- { noresponse: true }
- );
- const clientId = ClientModel.getInstance().clientId;
- await RpcApi.SetMetaCommand(TabRpcClient, {
- oref: WOS.makeORef("client", clientId),
- meta: { "onboarding:githubstar": true },
- });
- window.open("https://github.com/wavetermdev/waveterm?ref=not", "_blank");
- setPageName("features");
- };
-
- const handleMaybeLater = async () => {
- RpcApi.RecordTEventCommand(
- TabRpcClient,
- {
- event: "onboarding:githubstar",
- props: { "onboarding:githubstar": "later", "onboarding:page": "notelemetry" },
- },
- { noresponse: true }
- );
- const clientId = ClientModel.getInstance().clientId;
- await RpcApi.SetMetaCommand(TabRpcClient, {
- oref: WOS.makeORef("client", clientId),
- meta: { "onboarding:githubstar": false },
- });
- setPageName("features");
- };
-
- return (
-
-
-
-
-
- Telemetry Disabled ✓
-
-
-
-
-
No problem, we respect your privacy.
-
- But, without usage data, we're flying blind. A GitHub star helps us know Wave is useful and
- worth maintaining.
-
-
-
-
-
-
- );
-};
-
const FeaturesPage = () => {
const [newInstallOnboardingOpen, setNewInstallOnboardingOpen] = useAtom(modalsModel.newInstallOnboardingOpen);
@@ -325,12 +192,9 @@ const NewInstallOnboardingModal = () => {
let pageComp: React.JSX.Element = null;
switch (pageName) {
case "init":
- pageComp = services.ClientService.TelemetryUpdate(value)} />;
- break;
- case "notelemetrystar":
- pageComp = ;
+ pageComp = ;
break;
- case "features":
+ case "features":
pageComp = ;
break;
}
@@ -351,4 +215,4 @@ const NewInstallOnboardingModal = () => {
NewInstallOnboardingModal.displayName = "NewInstallOnboardingModal";
-export { InitPage, NewInstallOnboardingModal, NoTelemetryStarPage };
+export { InitPage, NewInstallOnboardingModal };
diff --git a/frontend/app/store/focusManager.ts b/frontend/app/store/focusManager.ts
index 58f78951ee..a0a9752745 100644
--- a/frontend/app/store/focusManager.ts
+++ b/frontend/app/store/focusManager.ts
@@ -1,27 +1,19 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { waveAIHasFocusWithin } from "@/app/aipanel/waveai-focus-utils";
-import { WaveAIModel } from "@/app/aipanel/waveai-model";
import { getBlockComponentModel } from "@/app/store/global";
import { globalStore } from "@/app/store/jotaiStore";
import { getLayoutModelForStaticTab } from "@/layout/index";
import { focusedBlockId } from "@/util/focusutil";
-import { Atom, atom, type PrimitiveAtom } from "jotai";
-
-export type FocusStrType = "node" | "waveai";
+import { Atom, atom } from "jotai";
export class FocusManager {
private static instance: FocusManager | null = null;
- focusType: PrimitiveAtom = atom("node");
blockFocusAtom: Atom;
private constructor() {
this.blockFocusAtom = atom((get) => {
- if (get(this.focusType) == "waveai") {
- return null;
- }
const layoutModel = getLayoutModelForStaticTab();
const lnode = get(layoutModel.focusedNode);
return lnode?.data?.blockId;
@@ -35,50 +27,11 @@ export class FocusManager {
return FocusManager.instance;
}
- setWaveAIFocused(force: boolean = false) {
- const isAlreadyFocused = globalStore.get(this.focusType) == "waveai";
- if (!force && isAlreadyFocused) {
- return;
- }
- globalStore.set(this.focusType, "waveai");
- this.refocusNode();
- }
-
- setBlockFocus(force: boolean = false) {
- const ftype = globalStore.get(this.focusType);
- if (!force && ftype == "node") {
- return;
- }
- globalStore.set(this.focusType, "node");
- this.refocusNode();
- }
-
- waveAIFocusWithin(): boolean {
- return waveAIHasFocusWithin();
- }
-
nodeFocusWithin(): boolean {
return focusedBlockId() != null;
}
- requestNodeFocus(): void {
- globalStore.set(this.focusType, "node");
- }
-
- requestWaveAIFocus(): void {
- globalStore.set(this.focusType, "waveai");
- }
-
- getFocusType(): FocusStrType {
- return globalStore.get(this.focusType);
- }
-
refocusNode() {
- const ftype = globalStore.get(this.focusType);
- if (ftype == "waveai") {
- WaveAIModel.getInstance().focusInput();
- return;
- }
const layoutModel = getLayoutModelForStaticTab();
const lnode = globalStore.get(layoutModel.focusedNode);
if (lnode == null || lnode.data?.blockId == null) {
diff --git a/frontend/app/store/global-atoms.ts b/frontend/app/store/global-atoms.ts
index 01fe12800e..204d908342 100644
--- a/frontend/app/store/global-atoms.ts
+++ b/frontend/app/store/global-atoms.ts
@@ -55,22 +55,9 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
return WOS.getObjectValue(WOS.makeORef("workspace", workspaceId), get);
});
const fullConfigAtom = atom(null) as PrimitiveAtom;
- const waveaiModeConfigAtom = atom(null) as PrimitiveAtom>;
const settingsAtom = atom((get) => {
return get(fullConfigAtom)?.settings ?? {};
}) as Atom;
- const hasCustomAIPresetsAtom = atom((get) => {
- const fullConfig = get(fullConfigAtom);
- if (!fullConfig?.presets) {
- return false;
- }
- for (const presetId in fullConfig.presets) {
- if (presetId.startsWith("ai@") && presetId !== "ai@global" && presetId !== "ai@wave") {
- return true;
- }
- }
- return false;
- }) as Atom;
const hasConfigErrors = atom((get) => {
const fullConfig = get(fullConfigAtom);
return fullConfig?.configerrors != null && fullConfig.configerrors.length > 0;
@@ -78,16 +65,6 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
// this is *the* tab that this tabview represents. it should never change.
const staticTabIdAtom: Atom = atom(initOpts.tabId);
const controlShiftDelayAtom = atom(false);
- const updaterStatusAtom = atom("up-to-date") as PrimitiveAtom;
- try {
- globalStore.set(updaterStatusAtom, getApi().getUpdaterStatus());
- getApi().onUpdaterStatusChange((status) => {
- globalStore.set(updaterStatusAtom, status);
- });
- } catch (e) {
- console.log("failed to initialize updaterStatusAtom", e);
- }
-
const reducedMotionSettingAtom = atom((get) => get(settingsAtom)?.["window:reducedmotion"]);
const reducedMotionSystemPreferenceAtom = atom(false);
@@ -125,7 +102,6 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
return connStatuses;
});
const reinitVersion = atom(0);
- const rateLimitInfoAtom = atom(null) as PrimitiveAtom;
atoms = {
// initialized in wave.ts (will not be null inside of application)
builderId: builderIdAtom,
@@ -134,21 +110,17 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
workspaceId: workspaceIdAtom,
workspace: workspaceAtom,
fullConfigAtom,
- waveaiModeConfigAtom,
settingsAtom,
- hasCustomAIPresetsAtom,
hasConfigErrors,
staticTabId: staticTabIdAtom,
isFullScreen: isFullScreenAtom,
zoomFactorAtom,
controlShiftDelayAtom,
- updaterStatusAtom,
prefersReducedMotionAtom,
documentHasFocus: documentHasFocusAtom,
modalOpen,
allConnStatus: allConnStatusAtom,
reinitVersion,
- waveAIRateLimitInfoAtom: rateLimitInfoAtom,
} as GlobalAtomsType;
}
diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts
index acc0f4d518..a71fc652cf 100644
--- a/frontend/app/store/global.ts
+++ b/frontend/app/store/global.ts
@@ -67,12 +67,6 @@ function initGlobalWaveEventSubs(initOpts: WaveInitOpts) {
globalStore.set(atoms.fullConfigAtom, event.data.fullconfig);
},
});
- waveEventSubscribeSingle({
- eventType: "waveai:modeconfig",
- handler: (event) => {
- globalStore.set(atoms.waveaiModeConfigAtom, event.data.configs);
- },
- });
waveEventSubscribeSingle({
eventType: "userinput",
handler: (event) => {
@@ -91,12 +85,6 @@ function initGlobalWaveEventSubs(initOpts: WaveInitOpts) {
}
},
});
- waveEventSubscribeSingle({
- eventType: "waveai:ratelimit",
- handler: (event) => {
- globalStore.set(atoms.waveAIRateLimitInfoAtom, event.data);
- },
- });
setupBadgesSubscription();
}
@@ -665,14 +653,6 @@ function setActiveTab(tabId: string) {
getApi().setActiveTab(tabId);
}
-function recordTEvent(event: string, props?: TEventProps) {
- if (isPreviewWindow()) return;
- if (props == null) {
- props = {};
- }
- RpcApi.RecordTEventCommand(TabRpcClient, { event, props }, { noresponse: true });
-}
-
export {
atoms,
createBlock,
@@ -707,7 +687,6 @@ export {
makeDefaultConnStatus,
openLink,
readAtom,
- recordTEvent,
refocusNode,
registerBlockComponentModel,
replaceBlock,
diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts
index cca01753bb..da7432e28d 100644
--- a/frontend/app/store/keymodel.ts
+++ b/frontend/app/store/keymodel.ts
@@ -1,7 +1,6 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { WaveAIModel } from "@/app/aipanel/waveai-model";
import { FocusManager } from "@/app/store/focusManager";
import {
atoms,
@@ -14,9 +13,7 @@ import {
getBlockComponentModel,
getFocusedBlockId,
getSettingsKeyAtom,
- globalStore,
- recordTEvent,
- refocusNode,
+ globalStore, refocusNode,
replaceBlock,
WOS,
} from "@/app/store/global";
@@ -145,22 +142,6 @@ function simpleCloseStaticTab() {
}
function uxCloseBlock(blockId: string) {
- const workspaceLayoutModel = WorkspaceLayoutModel.getInstance();
- const isAIPanelOpen = workspaceLayoutModel.getAIPanelVisible();
- if (isAIPanelOpen && getStaticTabBlockCount() === 1) {
- const aiModel = WaveAIModel.getInstance();
- const shouldSwitchToAI = !globalStore.get(aiModel.isChatEmptyAtom) || aiModel.hasNonEmptyInput();
- if (shouldSwitchToAI) {
- replaceBlock(blockId, { meta: { view: "launcher" } }, false);
- setTimeout(() => WaveAIModel.getInstance().focusInput(), 50);
- return;
- }
- }
-
- const blockAtom = WOS.getWaveObjectAtom(WOS.makeORef("block", blockId));
- const blockData = globalStore.get(blockAtom);
- const isAIFileDiff = blockData?.meta?.view === "aifilediff";
-
// If this is the last block, closing it will close the tab — route through simpleCloseStaticTab
// so the tab:confirmclose setting is respected.
if (getStaticTabBlockCount() === 1) {
@@ -172,35 +153,10 @@ function uxCloseBlock(blockId: string) {
const node = layoutModel.getNodeByBlockId(blockId);
if (node) {
fireAndForget(() => layoutModel.closeNode(node.id));
-
- if (isAIFileDiff && isAIPanelOpen) {
- setTimeout(() => WaveAIModel.getInstance().focusInput(), 50);
- }
}
}
function genericClose() {
- const focusType = FocusManager.getInstance().getFocusType();
- if (focusType === "waveai") {
- WorkspaceLayoutModel.getInstance().setAIPanelVisible(false);
- return;
- }
-
- const workspaceLayoutModel = WorkspaceLayoutModel.getInstance();
- const isAIPanelOpen = workspaceLayoutModel.getAIPanelVisible();
- if (isAIPanelOpen && getStaticTabBlockCount() === 1) {
- const aiModel = WaveAIModel.getInstance();
- const shouldSwitchToAI = !globalStore.get(aiModel.isChatEmptyAtom) || aiModel.hasNonEmptyInput();
- if (shouldSwitchToAI) {
- const layoutModel = getLayoutModelForStaticTab();
- const focusedNode = globalStore.get(layoutModel.focusedNode);
- if (focusedNode) {
- replaceBlock(focusedNode.data.blockId, { meta: { view: "launcher" } }, false);
- setTimeout(() => WaveAIModel.getInstance().focusInput(), 50);
- return;
- }
- }
- }
const blockCount = getStaticTabBlockCount();
if (blockCount === 0) {
simpleCloseStaticTab();
@@ -215,17 +171,7 @@ function genericClose() {
}
const layoutModel = getLayoutModelForStaticTab();
- const focusedNode = globalStore.get(layoutModel.focusedNode);
- const blockId = focusedNode?.data?.blockId;
- const blockAtom = blockId ? WOS.getWaveObjectAtom(WOS.makeORef("block", blockId)) : null;
- const blockData = blockAtom ? globalStore.get(blockAtom) : null;
- const isAIFileDiff = blockData?.meta?.view === "aifilediff";
-
fireAndForget(layoutModel.closeFocusedNode.bind(layoutModel));
-
- if (isAIFileDiff && isAIPanelOpen) {
- setTimeout(() => WaveAIModel.getInstance().focusInput(), 50);
- }
}
function switchBlockByBlockNum(index: number) {
@@ -241,36 +187,7 @@ function switchBlockByBlockNum(index: number) {
function switchBlockInDirection(direction: NavigateDirection) {
const layoutModel = getLayoutModelForStaticTab();
- const focusType = FocusManager.getInstance().getFocusType();
-
- if (direction === NavigateDirection.Left) {
- const numBlocks = globalStore.get(layoutModel.numLeafs);
- if (focusType === "waveai") {
- return;
- }
- if (numBlocks === 1) {
- FocusManager.getInstance().requestWaveAIFocus();
- setTimeout(() => {
- FocusManager.getInstance().refocusNode();
- }, 10);
- return;
- }
- }
-
- if (direction === NavigateDirection.Right && focusType === "waveai") {
- FocusManager.getInstance().requestNodeFocus();
- return;
- }
-
- const inWaveAI = focusType === "waveai";
- const navResult = layoutModel.switchNodeFocusInDirection(direction, inWaveAI);
- if (navResult.atLeft) {
- FocusManager.getInstance().requestWaveAIFocus();
- setTimeout(() => {
- FocusManager.getInstance().refocusNode();
- }, 10);
- return;
- }
+ layoutModel.switchNodeFocusInDirection(direction);
setTimeout(() => {
globalRefocus();
}, 10);
@@ -649,9 +566,7 @@ function registerGlobalKeys() {
});
globalKeyMap.set("Cmd:g", () => {
const bcm = getBlockComponentModel(getFocusedBlockInStaticTab());
- if (bcm.openSwitchConnection != null) {
- recordTEvent("action:other", { "action:type": "conndropdown", "action:initiator": "keyboard" });
- bcm.openSwitchConnection();
+ if (bcm.openSwitchConnection != null) { bcm.openSwitchConnection();
return true;
}
});
@@ -682,25 +597,6 @@ function registerGlobalKeys() {
return true;
});
}
- if (isWindows()) {
- globalKeyMap.set("Alt:c{Digit0}", () => {
- WaveAIModel.getInstance().focusInput();
- return true;
- });
- globalKeyMap.set("Alt:c{Numpad0}", () => {
- WaveAIModel.getInstance().focusInput();
- return true;
- });
- } else {
- globalKeyMap.set("Ctrl:Shift:c{Digit0}", () => {
- WaveAIModel.getInstance().focusInput();
- return true;
- });
- globalKeyMap.set("Ctrl:Shift:c{Numpad0}", () => {
- WaveAIModel.getInstance().focusInput();
- return true;
- });
- }
function activateSearch(event: WaveKeyboardEvent): boolean {
const bcm = getBlockComponentModel(getFocusedBlockInStaticTab());
// Ctrl+f is reserved in most shells
@@ -740,11 +636,6 @@ function registerGlobalKeys() {
}
return false;
});
- globalKeyMap.set("Cmd:Shift:a", () => {
- const currentVisible = WorkspaceLayoutModel.getInstance().getAIPanelVisible();
- WorkspaceLayoutModel.getInstance().setAIPanelVisible(!currentVisible);
- return true;
- });
const allKeys = Array.from(globalKeyMap.keys());
// special case keys, handled by web view
allKeys.push("Cmd:l", "Cmd:r", "Cmd:ArrowRight", "Cmd:ArrowLeft", "Cmd:o");
diff --git a/frontend/app/store/services.ts b/frontend/app/store/services.ts
index 9e6e156bc3..b4dcd4add4 100644
--- a/frontend/app/store/services.ts
+++ b/frontend/app/store/services.ts
@@ -31,7 +31,7 @@ export class BlockServiceType {
}
// save the terminal state to a blockfile
- SaveTerminalState(blockId: string, state: string, stateType: string, ptyOffset: number, termSize: TermSize): Promise {
+ SaveTerminalState(blockId: string, state: string, stateType: string, ptyOffset: number, termSize: TermSize, decModes: string): Promise {
return callBackendService(this?.waveEnv, "block", "SaveTerminalState", Array.from(arguments))
}
}
@@ -62,9 +62,6 @@ export class ClientServiceType {
GetTab(arg1: string): Promise {
return callBackendService(this?.waveEnv, "client", "GetTab", Array.from(arguments))
}
- TelemetryUpdate(arg2: boolean): Promise {
- return callBackendService(this?.waveEnv, "client", "TelemetryUpdate", Array.from(arguments))
- }
}
export const ClientService = new ClientServiceType();
diff --git a/frontend/app/store/tabrpcclient.ts b/frontend/app/store/tabrpcclient.ts
index d6bfef56e1..045ce5ec5c 100644
--- a/frontend/app/store/tabrpcclient.ts
+++ b/frontend/app/store/tabrpcclient.ts
@@ -1,7 +1,6 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { WaveAIModel } from "@/app/aipanel/waveai-model";
import { getApi, getBlockComponentModel, getConnStatusAtom, globalStore, WOS } from "@/app/store/global";
import type { TermViewModel } from "@/app/view/term/term-model";
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
@@ -62,36 +61,6 @@ export class TabClient extends WshClient {
return await getApi().captureScreenshot(electronRect);
}
- async handle_waveaiaddcontext(rh: RpcResponseHelper, data: CommandWaveAIAddContextData): Promise {
- const workspaceLayoutModel = WorkspaceLayoutModel.getInstance();
- if (!workspaceLayoutModel.getAIPanelVisible()) {
- workspaceLayoutModel.setAIPanelVisible(true, { nofocus: true });
- }
-
- const model = WaveAIModel.getInstance();
-
- if (data.newchat) {
- model.clearChat();
- }
-
- if (data.files && data.files.length > 0) {
- for (const fileData of data.files) {
- const decodedData = base64ToArrayBuffer(fileData.data64);
- const blob = new Blob([decodedData], { type: fileData.type });
- const file = new File([blob], fileData.name, { type: fileData.type });
- await model.addFile(file);
- }
- }
-
- if (data.text) {
- model.appendText(data.text);
- }
-
- if (data.submit) {
- await model.handleSubmit();
- }
- }
-
async handle_setblockfocus(rh: RpcResponseHelper, blockId: string): Promise {
const layoutModel = getLayoutModelForStaticTab();
if (!layoutModel) {
diff --git a/frontend/app/store/wshclientapi.ts b/frontend/app/store/wshclientapi.ts
index 8482be260d..a6caffedb2 100644
--- a/frontend/app/store/wshclientapi.ts
+++ b/frontend/app/store/wshclientapi.ts
@@ -18,18 +18,6 @@ export class RpcApiType {
this.mockClient = client;
}
- // command "activity" [call]
- ActivityCommand(client: WshClient, data: ActivityUpdate, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "activity", data, opts);
- return client.wshRpcCall("activity", data, opts);
- }
-
- // command "aisendmessage" [call]
- AiSendMessageCommand(client: WshClient, data: AiMessageData, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "aisendmessage", data, opts);
- return client.wshRpcCall("aisendmessage", data, opts);
- }
-
// command "authenticate" [call]
AuthenticateCommand(client: WshClient, data: string, opts?: RpcOpts): Promise {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "authenticate", data, opts);
@@ -480,36 +468,12 @@ export class RpcApiType {
return client.wshRpcCall("gettempdir", data, opts);
}
- // command "getupdatechannel" [call]
- GetUpdateChannelCommand(client: WshClient, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "getupdatechannel", null, opts);
- return client.wshRpcCall("getupdatechannel", null, opts);
- }
-
// command "getvar" [call]
GetVarCommand(client: WshClient, data: CommandVarData, opts?: RpcOpts): Promise {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "getvar", data, opts);
return client.wshRpcCall("getvar", data, opts);
}
- // command "getwaveaichat" [call]
- GetWaveAIChatCommand(client: WshClient, data: CommandGetWaveAIChatData, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "getwaveaichat", data, opts);
- return client.wshRpcCall("getwaveaichat", data, opts);
- }
-
- // command "getwaveaimodeconfig" [call]
- GetWaveAIModeConfigCommand(client: WshClient, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "getwaveaimodeconfig", null, opts);
- return client.wshRpcCall("getwaveaimodeconfig", null, opts);
- }
-
- // command "getwaveairatelimit" [call]
- GetWaveAIRateLimitCommand(client: WshClient, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "getwaveairatelimit", null, opts);
- return client.wshRpcCall("getwaveairatelimit", null, opts);
- }
-
// command "jobcmdexited" [call]
JobCmdExitedCommand(client: WshClient, data: CommandJobCmdExitedData, opts?: RpcOpts): Promise {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "jobcmdexited", data, opts);
@@ -672,12 +636,6 @@ export class RpcApiType {
return client.wshRpcCall("readappfile", data, opts);
}
- // command "recordtevent" [call]
- RecordTEventCommand(client: WshClient, data: TEvent, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "recordtevent", data, opts);
- return client.wshRpcCall("recordtevent", data, opts);
- }
-
// command "remotedisconnectfromjobmanager" [call]
RemoteDisconnectFromJobManagerCommand(client: WshClient, data: CommandRemoteDisconnectFromJobManagerData, opts?: RpcOpts): Promise {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "remotedisconnectfromjobmanager", data, opts);
@@ -828,12 +786,6 @@ export class RpcApiType {
return client.wshRpcCall("routeunannounce", null, opts);
}
- // command "sendtelemetry" [call]
- SendTelemetryCommand(client: WshClient, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "sendtelemetry", null, opts);
- return client.wshRpcCall("sendtelemetry", null, opts);
- }
-
// command "setblockfocus" [call]
SetBlockFocusCommand(client: WshClient, data: string, opts?: RpcOpts): Promise {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "setblockfocus", data, opts);
@@ -984,30 +936,6 @@ export class RpcApiType {
return client.wshRpcCall("waitforroute", data, opts);
}
- // command "waveaiaddcontext" [call]
- WaveAIAddContextCommand(client: WshClient, data: CommandWaveAIAddContextData, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "waveaiaddcontext", data, opts);
- return client.wshRpcCall("waveaiaddcontext", data, opts);
- }
-
- // command "waveaienabletelemetry" [call]
- WaveAIEnableTelemetryCommand(client: WshClient, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "waveaienabletelemetry", null, opts);
- return client.wshRpcCall("waveaienabletelemetry", null, opts);
- }
-
- // command "waveaigettooldiff" [call]
- WaveAIGetToolDiffCommand(client: WshClient, data: CommandWaveAIGetToolDiffData, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "waveaigettooldiff", data, opts);
- return client.wshRpcCall("waveaigettooldiff", data, opts);
- }
-
- // command "waveaitoolapprove" [call]
- WaveAIToolApproveCommand(client: WshClient, data: CommandWaveAIToolApproveData, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "waveaitoolapprove", data, opts);
- return client.wshRpcCall("waveaitoolapprove", data, opts);
- }
-
// command "wavefilereadstream" [call]
WaveFileReadStreamCommand(client: WshClient, data: CommandWaveFileReadStreamData, opts?: RpcOpts): Promise {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "wavefilereadstream", data, opts);
@@ -1056,12 +984,6 @@ export class RpcApiType {
return client.wshRpcCall("writetempfile", data, opts);
}
- // command "wshactivity" [call]
- WshActivityCommand(client: WshClient, data: {[key: string]: number}, opts?: RpcOpts): Promise {
- if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "wshactivity", data, opts);
- return client.wshRpcCall("wshactivity", data, opts);
- }
-
// command "wsldefaultdistro" [call]
WslDefaultDistroCommand(client: WshClient, opts?: RpcOpts): Promise {
if (this.mockClient) return this.mockClient.mockWshRpcCall(client, "wsldefaultdistro", null, opts);
diff --git a/frontend/app/tab/tab.tsx b/frontend/app/tab/tab.tsx
index 4972a13daa..66707c1fd1 100644
--- a/frontend/app/tab/tab.tsx
+++ b/frontend/app/tab/tab.tsx
@@ -18,9 +18,7 @@ import "./tab.scss";
import { buildTabContextMenu } from "./tabcontextmenu";
export type TabEnv = WaveEnvSubset<{
- rpc: {
- ActivityCommand: WaveEnv["rpc"]["ActivityCommand"];
- SetConfigCommand: WaveEnv["rpc"]["SetConfigCommand"];
+ rpc: { SetConfigCommand: WaveEnv["rpc"]["SetConfigCommand"];
SetMetaCommand: WaveEnv["rpc"]["SetMetaCommand"];
UpdateTabNameCommand: WaveEnv["rpc"]["UpdateTabNameCommand"];
};
diff --git a/frontend/app/tab/tabbar.tsx b/frontend/app/tab/tabbar.tsx
index b404afcb7e..049d6e38fb 100644
--- a/frontend/app/tab/tabbar.tsx
+++ b/frontend/app/tab/tabbar.tsx
@@ -15,7 +15,6 @@ import { debounce } from "throttle-debounce";
import { Tab } from "./tab";
import "./tabbar.scss";
import { TabBarEnv } from "./tabbarenv";
-import { UpdateStatusBanner } from "./updatebanner";
import { WorkspaceSwitcher } from "./workspaceswitcher";
const TabDefaultWidth = 130;
@@ -45,36 +44,6 @@ interface TabBarProps {
noTabs?: boolean;
}
-const WaveAIButton = memo(({ divRef }: { divRef?: React.RefObject }) => {
- const env = useWaveEnv();
- const aiPanelOpen = useAtomValue(WorkspaceLayoutModel.getInstance().panelVisibleAtom);
- const hideAiButton = useAtomValue(env.getSettingsKeyAtom("app:hideaibutton"));
-
- const onClick = () => {
- const currentVisible = WorkspaceLayoutModel.getInstance().getAIPanelVisible();
- WorkspaceLayoutModel.getInstance().setAIPanelVisible(!currentVisible);
- };
-
- if (hideAiButton) {
- return null;
- }
-
- return (
-
-
-
- );
-});
-WaveAIButton.displayName = "WaveAIButton";
-
function strArrayIsEqual(a: string[], b: string[]) {
// null check
if (a == null && b == null) {
@@ -122,7 +91,6 @@ const TabBar = memo(({ workspace, noTabs }: TabBarProps) => {
const draggerLeftRef = useRef(null);
const rightContainerRef = useRef(null);
const workspaceSwitcherRef = useRef(null);
- const waveAIButtonRef = useRef(null);
const appMenuButtonRef = useRef(null);
const tabWidthRef = useRef(TabDefaultWidth);
const scrollableRef = useRef(false);
@@ -132,8 +100,6 @@ const TabBar = memo(({ workspace, noTabs }: TabBarProps) => {
const zoomFactor = useAtomValue(env.atoms.zoomFactorAtom);
const showMenuBar = useAtomValue(env.getSettingsKeyAtom("window:showmenubar"));
const confirmClose = useAtomValue(env.getSettingsKeyAtom("tab:confirmclose")) ?? false;
- const hideAiButton = useAtomValue(env.getSettingsKeyAtom("app:hideaibutton"));
- const appUpdateStatus = useAtomValue(env.atoms.updaterStatusAtom);
let prevDelta: number;
let prevDragDirection: string;
@@ -189,16 +155,12 @@ const TabBar = memo(({ workspace, noTabs }: TabBarProps) => {
const addBtnWidth = getOuterWidth(addBtnRef.current);
const appMenuButtonWidth = appMenuButtonRef.current?.getBoundingClientRect().width ?? 0;
const workspaceSwitcherWidth = workspaceSwitcherRef.current?.getBoundingClientRect().width ?? 0;
- const waveAIButtonWidth =
- !hideAiButton && waveAIButtonRef.current != null ? getOuterWidth(waveAIButtonRef.current) : 0;
-
const nonTabElementsWidth =
windowDragLeftWidth +
rightContainerWidth +
addBtnWidth +
appMenuButtonWidth +
- workspaceSwitcherWidth +
- waveAIButtonWidth;
+ workspaceSwitcherWidth;
const spaceForTabs = tabbarWrapperWidth - nonTabElementsWidth;
const numberOfTabs = tabIds.length;
@@ -272,7 +234,7 @@ const TabBar = memo(({ workspace, noTabs }: TabBarProps) => {
};
}, [handleResizeTabs]);
- // update layout on changed tabIds, tabsLoaded, newTabId, hideAiButton, appUpdateStatus, or zoomFactor
+ // update layout on changed tabIds, tabsLoaded, newTabId, or zoomFactor
useEffect(() => {
// Check if all tabs are loaded
const allLoaded = tabIds.length > 0 && tabIds.every((id) => tabsLoaded[id]);
@@ -288,8 +250,6 @@ const TabBar = memo(({ workspace, noTabs }: TabBarProps) => {
tabsLoaded,
newTabId,
saveTabsPosition,
- hideAiButton,
- appUpdateStatus,
zoomFactor,
showMenuBar,
]);
@@ -613,7 +573,6 @@ const TabBar = memo(({ workspace, noTabs }: TabBarProps) => {
)}
-
{
-
{
);
});
-export { TabBar, WaveAIButton };
+export { TabBar };
diff --git a/frontend/app/tab/tabbarenv.ts b/frontend/app/tab/tabbarenv.ts
index e44b344b6b..3c0f930054 100644
--- a/frontend/app/tab/tabbarenv.ts
+++ b/frontend/app/tab/tabbarenv.ts
@@ -9,11 +9,8 @@ export type TabBarEnv = WaveEnvSubset<{
closeTab: WaveEnv["electron"]["closeTab"];
setActiveTab: WaveEnv["electron"]["setActiveTab"];
showWorkspaceAppMenu: WaveEnv["electron"]["showWorkspaceAppMenu"];
- installAppUpdate: WaveEnv["electron"]["installAppUpdate"];
};
- rpc: {
- ActivityCommand: WaveEnv["rpc"]["ActivityCommand"];
- SetConfigCommand: WaveEnv["rpc"]["SetConfigCommand"];
+ rpc: { SetConfigCommand: WaveEnv["rpc"]["SetConfigCommand"];
SetMetaCommand: WaveEnv["rpc"]["SetMetaCommand"];
UpdateTabNameCommand: WaveEnv["rpc"]["UpdateTabNameCommand"];
UpdateWorkspaceTabIdsCommand: WaveEnv["rpc"]["UpdateWorkspaceTabIdsCommand"];
@@ -25,10 +22,9 @@ export type TabBarEnv = WaveEnvSubset<{
isFullScreen: WaveEnv["atoms"]["isFullScreen"];
zoomFactorAtom: WaveEnv["atoms"]["zoomFactorAtom"];
reinitVersion: WaveEnv["atoms"]["reinitVersion"];
- updaterStatusAtom: WaveEnv["atoms"]["updaterStatusAtom"];
};
wos: WaveEnv["wos"];
- getSettingsKeyAtom: SettingsKeyAtomFnType<"app:hideaibutton" | "app:tabbar" | "tab:confirmclose" | "window:showmenubar">;
+ getSettingsKeyAtom: SettingsKeyAtomFnType<"app:tabbar" | "tab:confirmclose" | "window:showmenubar">;
showContextMenu: WaveEnv["showContextMenu"];
mockSetWaveObj: WaveEnv["mockSetWaveObj"];
isWindows: WaveEnv["isWindows"];
diff --git a/frontend/app/tab/tabcontextmenu.ts b/frontend/app/tab/tabcontextmenu.ts
index bc87302d4c..50b72827cd 100644
--- a/frontend/app/tab/tabcontextmenu.ts
+++ b/frontend/app/tab/tabcontextmenu.ts
@@ -1,7 +1,7 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { getOrefMetaKeyAtom, globalStore, recordTEvent } from "@/app/store/global";
+import { getOrefMetaKeyAtom, globalStore } from "@/app/store/global";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { fireAndForget } from "@/util/util";
import { makeORef } from "../store/wos";
@@ -92,10 +92,7 @@ export function buildTabContextMenu(
await env.rpc.SetMetaCommand(TabRpcClient, {
oref,
meta: { "bg:*": true, "tab:background": null },
- });
- env.rpc.ActivityCommand(TabRpcClient, { settabtheme: 1 }, { noresponse: true });
- recordTEvent("action:settabtheme");
- }),
+ }); }),
});
for (const bgKey of bgKeys) {
const bg = backgrounds[bgKey];
@@ -106,10 +103,7 @@ export function buildTabContextMenu(
await env.rpc.SetMetaCommand(TabRpcClient, {
oref,
meta: { "bg:*": true, "tab:background": bgKey },
- });
- env.rpc.ActivityCommand(TabRpcClient, { settabtheme: 1 }, { noresponse: true });
- recordTEvent("action:settabtheme");
- }),
+ }); }),
});
}
menu.push({ label: "Backgrounds", type: "submenu", submenu }, { type: "separator" });
diff --git a/frontend/app/tab/updatebanner.tsx b/frontend/app/tab/updatebanner.tsx
deleted file mode 100644
index b589558281..0000000000
--- a/frontend/app/tab/updatebanner.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2026, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { Tooltip } from "@/element/tooltip";
-import { WaveEnv, WaveEnvSubset, useWaveEnv } from "@/app/waveenv/waveenv";
-import { useAtomValue } from "jotai";
-import { memo, useCallback } from "react";
-
-type UpdateBannerEnv = WaveEnvSubset<{
- electron: {
- installAppUpdate: WaveEnv["electron"]["installAppUpdate"];
- };
- atoms: {
- updaterStatusAtom: WaveEnv["atoms"]["updaterStatusAtom"];
- };
-}>;
-
-function getUpdateStatusMessage(status: string): string {
- switch (status) {
- case "ready":
- return "Update";
- case "downloading":
- return "Downloading";
- case "installing":
- return "Installing";
- default:
- return null;
- }
-}
-
-const UpdateStatusBannerComponent = () => {
- const env = useWaveEnv();
- const appUpdateStatus = useAtomValue(env.atoms.updaterStatusAtom);
- const updateStatusMessage = getUpdateStatusMessage(appUpdateStatus);
-
- const onClick = useCallback(() => {
- env.electron.installAppUpdate();
- }, [env]);
-
- if (!updateStatusMessage) {
- return null;
- }
-
- const isReady = appUpdateStatus === "ready";
- const tooltipContent = isReady ? "Click to Install Update" : updateStatusMessage;
-
- return (
-
-
- {updateStatusMessage}
-
- );
-};
-UpdateStatusBannerComponent.displayName = "UpdateStatusBannerComponent";
-
-export const UpdateStatusBanner = memo(UpdateStatusBannerComponent);
diff --git a/frontend/app/tab/vtabbar.tsx b/frontend/app/tab/vtabbar.tsx
index e40bcfb374..279cf82539 100644
--- a/frontend/app/tab/vtabbar.tsx
+++ b/frontend/app/tab/vtabbar.tsx
@@ -13,41 +13,11 @@ import { cn, fireAndForget } from "@/util/util";
import { useAtomValue } from "jotai";
import { memo, useCallback, useEffect, useRef, useState } from "react";
import { buildTabBarContextMenu, buildTabContextMenu } from "./tabcontextmenu";
-import { UpdateStatusBanner } from "./updatebanner";
import { VTab, VTabItem } from "./vtab";
import { VTabBarEnv } from "./vtabbarenv";
import { WorkspaceSwitcher } from "./workspaceswitcher";
export type { VTabItem } from "./vtab";
-const VTabBarAIButton = memo(() => {
- const env = useWaveEnv();
- const aiPanelOpen = useAtomValue(WorkspaceLayoutModel.getInstance().panelVisibleAtom);
- const hideAiButton = useAtomValue(env.getSettingsKeyAtom("app:hideaibutton"));
-
- const onClick = () => {
- const currentVisible = WorkspaceLayoutModel.getInstance().getAIPanelVisible();
- WorkspaceLayoutModel.getInstance().setAIPanelVisible(!currentVisible);
- };
-
- if (hideAiButton) {
- return null;
- }
-
- return (
-
-
-
- );
-});
-VTabBarAIButton.displayName = "VTabBarAIButton";
-
const MacOSHeader = memo(() => {
const env = useWaveEnv();
const isFullScreen = useAtomValue(env.atoms.isFullScreen);
@@ -68,11 +38,9 @@ const MacOSHeader = memo(() => {
className="flex shrink-0 flex-row flex-wrap items-end px-1 pb-1 pl-2"
style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}
>
-
-
>
);
diff --git a/frontend/app/tab/vtabbarenv.ts b/frontend/app/tab/vtabbarenv.ts
index 2533780776..119ded2186 100644
--- a/frontend/app/tab/vtabbarenv.ts
+++ b/frontend/app/tab/vtabbarenv.ts
@@ -11,13 +11,10 @@ export type VTabBarEnv = WaveEnvSubset<{
deleteWorkspace: WaveEnv["electron"]["deleteWorkspace"];
createWorkspace: WaveEnv["electron"]["createWorkspace"];
switchWorkspace: WaveEnv["electron"]["switchWorkspace"];
- installAppUpdate: WaveEnv["electron"]["installAppUpdate"];
};
rpc: {
UpdateWorkspaceTabIdsCommand: WaveEnv["rpc"]["UpdateWorkspaceTabIdsCommand"];
- UpdateTabNameCommand: WaveEnv["rpc"]["UpdateTabNameCommand"];
- ActivityCommand: WaveEnv["rpc"]["ActivityCommand"];
- SetConfigCommand: WaveEnv["rpc"]["SetConfigCommand"];
+ UpdateTabNameCommand: WaveEnv["rpc"]["UpdateTabNameCommand"]; SetConfigCommand: WaveEnv["rpc"]["SetConfigCommand"];
SetMetaCommand: WaveEnv["rpc"]["SetMetaCommand"];
};
atoms: {
@@ -26,7 +23,6 @@ export type VTabBarEnv = WaveEnvSubset<{
reinitVersion: WaveEnv["atoms"]["reinitVersion"];
documentHasFocus: WaveEnv["atoms"]["documentHasFocus"];
workspace: WaveEnv["atoms"]["workspace"];
- updaterStatusAtom: WaveEnv["atoms"]["updaterStatusAtom"];
isFullScreen: WaveEnv["atoms"]["isFullScreen"];
};
services: {
@@ -34,7 +30,7 @@ export type VTabBarEnv = WaveEnvSubset<{
};
wos: WaveEnv["wos"];
showContextMenu: WaveEnv["showContextMenu"];
- getSettingsKeyAtom: SettingsKeyAtomFnType<"tab:confirmclose" | "app:tabbar" | "app:hideaibutton">;
+ getSettingsKeyAtom: SettingsKeyAtomFnType<"tab:confirmclose" | "app:tabbar">;
mockSetWaveObj: WaveEnv["mockSetWaveObj"];
isWindows: WaveEnv["isWindows"];
isMacOS: WaveEnv["isMacOS"];
diff --git a/frontend/app/view/aifilediff/aifilediff.tsx b/frontend/app/view/aifilediff/aifilediff.tsx
deleted file mode 100644
index 9d96d290c2..0000000000
--- a/frontend/app/view/aifilediff/aifilediff.tsx
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2026, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import type { BlockNodeModel } from "@/app/block/blocktypes";
-import type { TabModel } from "@/app/store/tab-model";
-import { TabRpcClient } from "@/app/store/wshrpcutil";
-import { DiffViewer } from "@/app/view/codeeditor/diffviewer";
-import type { WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
-import { globalStore } from "@/store/jotaiStore";
-import { base64ToString } from "@/util/util";
-import * as jotai from "jotai";
-import { useEffect } from "react";
-
-type DiffData = {
- original: string;
- modified: string;
- fileName: string;
-};
-
-export type AiFileDiffEnv = WaveEnvSubset<{
- rpc: {
- WaveAIGetToolDiffCommand: WaveEnv["rpc"]["WaveAIGetToolDiffCommand"];
- };
- wos: WaveEnv["wos"];
-}>;
-
-export class AiFileDiffViewModel implements ViewModel {
- blockId: string;
- nodeModel: BlockNodeModel;
- tabModel: TabModel;
- env: AiFileDiffEnv;
- viewType = "aifilediff";
- blockAtom: jotai.Atom
;
- diffDataAtom: jotai.PrimitiveAtom;
- errorAtom: jotai.PrimitiveAtom;
- loadingAtom: jotai.PrimitiveAtom;
- viewIcon: jotai.Atom;
- viewName: jotai.Atom;
- viewText: jotai.Atom;
-
- constructor({ blockId, nodeModel, tabModel, waveEnv }: ViewModelInitType) {
- this.blockId = blockId;
- this.nodeModel = nodeModel;
- this.tabModel = tabModel;
- this.env = waveEnv as AiFileDiffEnv;
- this.blockAtom = this.env.wos.getWaveObjectAtom(`block:${blockId}`);
- this.diffDataAtom = jotai.atom(null) as jotai.PrimitiveAtom;
- this.errorAtom = jotai.atom(null) as jotai.PrimitiveAtom;
- this.loadingAtom = jotai.atom(true);
- this.viewIcon = jotai.atom("file-lines");
- this.viewName = jotai.atom("AI Diff Viewer");
- this.viewText = jotai.atom((get) => {
- const diffData = get(this.diffDataAtom);
- return diffData?.fileName ?? "";
- });
- }
-
- get viewComponent(): ViewComponent {
- return AiFileDiffView;
- }
-}
-
-function AiFileDiffView({ blockId, model }: ViewComponentProps) {
- const blockData = jotai.useAtomValue(model.blockAtom);
- const diffData = jotai.useAtomValue(model.diffDataAtom);
- const error = jotai.useAtomValue(model.errorAtom);
- const loading = jotai.useAtomValue(model.loadingAtom);
-
- useEffect(() => {
- async function loadDiffData() {
- const chatId = blockData?.meta?.["aifilediff:chatid"];
- const toolCallId = blockData?.meta?.["aifilediff:toolcallid"];
- const fileName = blockData?.meta?.file;
-
- if (!chatId || !toolCallId) {
- globalStore.set(model.errorAtom, "Missing chatId or toolCallId in block metadata");
- globalStore.set(model.loadingAtom, false);
- return;
- }
-
- if (!fileName) {
- globalStore.set(model.errorAtom, "Missing file name in block metadata");
- globalStore.set(model.loadingAtom, false);
- return;
- }
-
- try {
- const result = await model.env.rpc.WaveAIGetToolDiffCommand(TabRpcClient, {
- chatid: chatId,
- toolcallid: toolCallId,
- });
-
- if (!result) {
- globalStore.set(model.errorAtom, "No diff data returned from server");
- globalStore.set(model.loadingAtom, false);
- return;
- }
-
- const originalContent = base64ToString(result.originalcontents64);
- const modifiedContent = base64ToString(result.modifiedcontents64);
-
- globalStore.set(model.diffDataAtom, {
- original: originalContent,
- modified: modifiedContent,
- fileName: fileName,
- });
- globalStore.set(model.loadingAtom, false);
- } catch (e) {
- console.error("Error loading diff data:", e);
- globalStore.set(model.errorAtom, `Error loading diff data: ${e.message}`);
- globalStore.set(model.loadingAtom, false);
- }
- }
-
- loadDiffData();
- }, [blockData?.meta?.["aifilediff:chatid"], blockData?.meta?.["aifilediff:toolcallid"], blockData?.meta?.file]);
-
- if (loading) {
- return (
-
- );
- }
-
- if (error) {
- return (
-
- );
- }
-
- if (!diffData) {
- return (
-
-
No diff data available
-
- );
- }
-
- return (
-
- );
-}
-
-export default AiFileDiffView;
diff --git a/frontend/app/view/term/dec-modes.test.ts b/frontend/app/view/term/dec-modes.test.ts
new file mode 100644
index 0000000000..3cb2f0fbae
--- /dev/null
+++ b/frontend/app/view/term/dec-modes.test.ts
@@ -0,0 +1,240 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+
+// ---------------------------------------------------------------------------
+// Mocks for heavy dependencies so TermWrap can be instantiated in Node.js
+// ---------------------------------------------------------------------------
+
+const mockWrite = vi.fn();
+const mockScrollToBottom = vi.fn();
+const mockOpen = vi.fn();
+const mockDispose = vi.fn();
+const capturedCsiHandlers: Record = {};
+
+vi.mock("@xterm/xterm", () => ({
+ Terminal: class MockTerminal {
+ write = mockWrite;
+ scrollToBottom = mockScrollToBottom;
+ rows = 24;
+ cols = 80;
+ parser = {
+ registerCsiHandler: vi.fn((id: { prefix?: string; final: string }, cb: Function) => {
+ const key = (id.prefix ?? "") + id.final;
+ capturedCsiHandlers[key] = cb;
+ return { dispose: mockDispose };
+ }),
+ registerOscHandler: vi.fn(() => ({ dispose: mockDispose })),
+ };
+ open = mockOpen;
+ loadAddon = vi.fn();
+ attachCustomKeyEventHandler = vi.fn();
+ onBell = vi.fn(() => ({ dispose: mockDispose }));
+ onData = vi.fn(() => ({ dispose: mockDispose }));
+ onBinary = vi.fn(() => ({ dispose: mockDispose }));
+ onTitleChange = vi.fn(() => ({ dispose: mockDispose }));
+ onRender = vi.fn(() => ({ dispose: mockDispose }));
+ onResize = vi.fn(() => ({ dispose: mockDispose }));
+ onWriteParsed = vi.fn(() => ({ dispose: mockDispose }));
+ onSelectionChange = vi.fn(() => ({ dispose: mockDispose }));
+ },
+}));
+
+vi.mock("@xterm/addon-fit", () => ({ FitAddon: class MockFitAddon { fit = vi.fn(); } }));
+vi.mock("@xterm/addon-search", () => ({ SearchAddon: class MockSearchAddon {} }));
+vi.mock("@xterm/addon-serialize", () => ({ SerializeAddon: class MockSerializeAddon {} }));
+vi.mock("@xterm/addon-web-links", () => ({ WebLinksAddon: class MockWebLinksAddon {} }));
+vi.mock("@xterm/addon-webgl", () => ({ WebglAddon: class MockWebglAddon {} }));
+
+vi.mock("@/store/global", () => ({
+ globalStore: {
+ get: vi.fn(() => undefined),
+ set: vi.fn(),
+ sub: vi.fn(() => () => {}),
+ },
+ getApi: vi.fn(() => ({})),
+ getOverrideConfigAtom: vi.fn(() => vi.fn()),
+ getSettingsKeyAtom: vi.fn(() => vi.fn()),
+ isDev: false,
+ openLink: vi.fn(),
+ WOS: {},
+ fetchWaveFile: vi.fn(),
+}));
+
+vi.mock("@/store/services", () => ({
+ BlockService: {
+ SaveTerminalState: vi.fn(),
+ },
+}));
+
+vi.mock("@/app/store/badge", () => ({ setBadge: vi.fn() }));
+vi.mock("@/app/store/wps", () => ({ getFileSubject: vi.fn(() => null) }));
+vi.mock("@/app/store/wshclientapi", () => ({ RpcApi: {} }));
+vi.mock("@/app/store/wshrpcutil", () => ({ TabRpcClient: {} }));
+vi.mock("@/util/platformutil", () => ({ PLATFORM: "darwin", PlatformMacOS: true }));
+vi.mock("@/util/util", () => ({ base64ToArray: vi.fn(), fireAndForget: vi.fn((f) => f()) }));
+vi.mock("debug", () => ({ default: () => vi.fn() }));
+vi.mock("jotai", () => ({
+ atom: vi.fn((init) => ({ init })),
+ PrimitiveAtom: class MockPrimitiveAtom {},
+}));
+vi.mock("throttle-debounce", () => ({ debounce: vi.fn((_, fn) => fn) }));
+vi.mock("./osc-handlers", () => ({
+ handleOsc16162Command: vi.fn(),
+ handleOsc52Command: vi.fn(),
+ handleOsc7Command: vi.fn(),
+ isClaudeCodeCommand: vi.fn(),
+}));
+vi.mock("./termutil", () => ({
+ bufferLinesToText: vi.fn(),
+ createTempFileFromBlob: vi.fn(),
+ extractAllClipboardData: vi.fn(),
+ normalizeCursorStyle: vi.fn(),
+ quoteForPosixShell: vi.fn(),
+ trimTerminalSelection: vi.fn(),
+}));
+
+// ---------------------------------------------------------------------------
+// Tests
+// ---------------------------------------------------------------------------
+
+import { TermWrap } from "./termwrap";
+
+describe("TermWrap DEC mode tracking", () => {
+ let term: TermWrap;
+
+ beforeEach(() => {
+ mockWrite.mockClear();
+ mockScrollToBottom.mockClear();
+ mockOpen.mockClear();
+ mockDispose.mockClear();
+ Object.keys(capturedCsiHandlers).forEach((k) => delete capturedCsiHandlers[k]);
+
+ const mockElem = {
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ style: {},
+ } as unknown as HTMLDivElement;
+
+ term = new TermWrap(
+ "tab-1",
+ "block-1",
+ mockElem,
+ {},
+ {}
+ );
+ });
+
+ describe("serializeDecModes", () => {
+ it("returns empty string when no modes are tracked", () => {
+ expect(term.serializeDecModes()).toBe("");
+ });
+
+ it("returns comma-separated sorted modes", () => {
+ term.activeDecModes.add(1003);
+ term.activeDecModes.add(1000);
+ term.activeDecModes.add(1002);
+ expect(term.serializeDecModes()).toBe("1000,1002,1003");
+ });
+ });
+
+ describe("replayDecModes", () => {
+ it("writes nothing for empty string", () => {
+ term.replayDecModes("");
+ expect(mockWrite).not.toHaveBeenCalled();
+ });
+
+ it("replays safe modes (mouse + bracketed paste)", () => {
+ term.replayDecModes("1002,1003,2004");
+ expect(mockWrite).toHaveBeenCalledTimes(1);
+ expect(mockWrite).toHaveBeenCalledWith("\x1b[?1002h\x1b[?1003h\x1b[?2004h");
+ });
+
+ it("filters out unsafe modes (alternate screen, cursor, sync)", () => {
+ term.replayDecModes("47,1002,1049,25,2026,1003");
+ expect(mockWrite).toHaveBeenCalledTimes(1);
+ // Only 1002 and 1003 should be written
+ expect(mockWrite).toHaveBeenCalledWith("\x1b[?1002h\x1b[?1003h");
+ });
+
+ it("handles malformed input gracefully", () => {
+ term.replayDecModes("abc,1002,,1003,NaN");
+ expect(mockWrite).toHaveBeenCalledTimes(1);
+ expect(mockWrite).toHaveBeenCalledWith("\x1b[?1002h\x1b[?1003h");
+ });
+ });
+
+ describe("CSI ? h (set) handler", () => {
+ it("tracks all modes in a multi-parameter sequence", () => {
+ const handler = capturedCsiHandlers["?h"];
+ expect(handler).toBeDefined();
+ handler([1002, 1003, 1006]);
+ expect(term.activeDecModes.has(1002)).toBe(true);
+ expect(term.activeDecModes.has(1003)).toBe(true);
+ expect(term.activeDecModes.has(1006)).toBe(true);
+ });
+
+ it("sets inSyncTransaction when mode 2026 is present", () => {
+ const handler = capturedCsiHandlers["?h"];
+ expect(term.inSyncTransaction).toBe(false);
+ handler([2026]);
+ expect(term.inSyncTransaction).toBe(true);
+ });
+
+ it("returns false to let xterm.js default handler run", () => {
+ const handler = capturedCsiHandlers["?h"];
+ expect(handler([1002])).toBe(false);
+ });
+ });
+
+ describe("CSI ? l (reset) handler", () => {
+ it("removes all modes in a multi-parameter sequence", () => {
+ const setHandler = capturedCsiHandlers["?h"];
+ setHandler([1002, 1003, 1006]);
+
+ const resetHandler = capturedCsiHandlers["?l"];
+ expect(resetHandler).toBeDefined();
+ resetHandler([1002, 1003]);
+
+ expect(term.activeDecModes.has(1002)).toBe(false);
+ expect(term.activeDecModes.has(1003)).toBe(false);
+ expect(term.activeDecModes.has(1006)).toBe(true);
+ });
+
+ it("clears inSyncTransaction when mode 2026 is reset", () => {
+ const setHandler = capturedCsiHandlers["?h"];
+ setHandler([2026]);
+ expect(term.inSyncTransaction).toBe(true);
+
+ const resetHandler = capturedCsiHandlers["?l"];
+ resetHandler([2026]);
+ expect(term.inSyncTransaction).toBe(false);
+ });
+
+ it("clears all modes when no parameters are provided", () => {
+ const setHandler = capturedCsiHandlers["?h"];
+ setHandler([1002, 1003]);
+ expect(term.activeDecModes.size).toBe(2);
+
+ const resetHandler = capturedCsiHandlers["?l"];
+ resetHandler([]);
+ expect(term.activeDecModes.size).toBe(0);
+ });
+
+ it("returns false to let xterm.js default handler run", () => {
+ const resetHandler = capturedCsiHandlers["?l"];
+ expect(resetHandler([1002])).toBe(false);
+ });
+ });
+
+ describe("full round-trip", () => {
+ it("serializes and replays only safe modes after mixed tracking", () => {
+ const setHandler = capturedCsiHandlers["?h"];
+ setHandler([47, 1002, 1049, 2004]);
+
+ const serialized = term.serializeDecModes();
+ expect(serialized).toBe("47,1002,1049,2004");
+
+ term.replayDecModes(serialized);
+ expect(mockWrite).toHaveBeenCalledWith("\x1b[?1002h\x1b[?2004h");
+ });
+ });
+});
diff --git a/frontend/app/view/term/osc-handlers.ts b/frontend/app/view/term/osc-handlers.ts
index 7fe7dcd4ee..5aecb525be 100644
--- a/frontend/app/view/term/osc-handlers.ts
+++ b/frontend/app/view/term/osc-handlers.ts
@@ -8,9 +8,7 @@ import {
getBlockMetaKeyAtom,
getBlockTermDurableAtom,
getOverrideConfigAtom,
- globalStore,
- recordTEvent,
- WOS,
+ globalStore, WOS,
} from "@/store/global";
import { base64ToString, fireAndForget, isSshConnName, isWslConnName } from "@/util/util";
import debug from "debug";
@@ -59,32 +57,22 @@ function checkCommandForTelemetry(decodedCmd: string) {
const normalizedCmd = normalizeCmd(decodedCmd);
- if (normalizedCmd.startsWith("ssh ")) {
- recordTEvent("conn:connect", { "conn:conntype": "ssh-manual" });
- return;
+ if (normalizedCmd.startsWith("ssh ")) { return;
}
const editorsRegex = /^(vim|vi|nano|nvim)\b/;
- if (editorsRegex.test(normalizedCmd)) {
- recordTEvent("action:term", { "action:type": "cli-edit" });
- return;
+ if (editorsRegex.test(normalizedCmd)) { return;
}
const tailFollowRegex = /(^|\|\s*)tail\s+-[fF]\b/;
- if (tailFollowRegex.test(normalizedCmd)) {
- recordTEvent("action:term", { "action:type": "cli-tailf" });
- return;
+ if (tailFollowRegex.test(normalizedCmd)) { return;
}
- if (ClaudeCodeRegex.test(normalizedCmd)) {
- recordTEvent("action:term", { "action:type": "claude" });
- return;
+ if (ClaudeCodeRegex.test(normalizedCmd)) { return;
}
const opencodeRegex = /^opencode\b/;
- if (opencodeRegex.test(normalizedCmd)) {
- recordTEvent("action:term", { "action:type": "opencode" });
- return;
+ if (opencodeRegex.test(normalizedCmd)) { return;
}
}
@@ -106,9 +94,7 @@ function handleShellIntegrationCommandStart(
const connName = globalStore.get(getBlockMetaKeyAtom(blockId, "connection")) ?? "";
const isRemote = isSshConnName(connName);
const isWsl = isWslConnName(connName);
- const isDurable = globalStore.get(getBlockTermDurableAtom(blockId)) ?? false;
- getApi().incrementTermCommands({ isRemote, isWsl, isDurable });
- if (cmd.data.cmd64) {
+ const isDurable = globalStore.get(getBlockTermDurableAtom(blockId)) ?? false; if (cmd.data.cmd64) {
const decodedLen = Math.ceil(cmd.data.cmd64.length * 0.75);
if (decodedLen > 8192) {
rtInfo["shell:lastcmd"] = `# command too large (${decodedLen} bytes)`;
diff --git a/frontend/app/view/term/term-model.ts b/frontend/app/view/term/term-model.ts
index a256929e7d..7ffe07d747 100644
--- a/frontend/app/view/term/term-model.ts
+++ b/frontend/app/view/term/term-model.ts
@@ -1,7 +1,6 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { WaveAIModel } from "@/app/aipanel/waveai-model";
import { BlockNodeModel } from "@/app/block/blocktypes";
import { appHandleKeyDown } from "@/app/store/keymodel";
import { modalsModel } from "@/app/store/modalmodel";
@@ -10,7 +9,7 @@ import { waveEventSubscribeSingle } from "@/app/store/wps";
import { RpcApi } from "@/app/store/wshclientapi";
import { makeFeBlockRouteId } from "@/app/store/wshrouter";
import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil";
-import { TermClaudeIcon, TerminalView } from "@/app/view/term/term";
+import { TerminalView } from "@/app/view/term/term";
import { TermWshClient } from "@/app/view/term/term-wsh";
import { VDomModel } from "@/app/view/vdom/vdom-model";
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
@@ -28,9 +27,7 @@ import {
getOverrideConfigAtom,
getSettingsKeyAtom,
globalStore,
- readAtom,
- recordTEvent,
- useBlockAtom,
+ readAtom, useBlockAtom,
WOS,
} from "@/store/global";
import * as services from "@/store/services";
@@ -285,12 +282,9 @@ export class TermViewModel implements ViewModel {
const isCmd = get(this.isCmdController);
const rtn: IconButtonDecl[] = [];
- const isAIPanelOpen = get(WorkspaceLayoutModel.getInstance().panelVisibleAtom);
- if (isAIPanelOpen) {
- const shellIntegrationButton = this.getShellIntegrationIconButton(get);
- if (shellIntegrationButton) {
- rtn.push(shellIntegrationButton);
- }
+ const shellIntegrationButton = this.getShellIntegrationIconButton(get);
+ if (shellIntegrationButton) {
+ rtn.push(shellIntegrationButton);
}
if (get(getSettingsKeyAtom("debug:webglstatus"))) {
@@ -399,53 +393,7 @@ export class TermViewModel implements ViewModel {
});
}
- getShellIntegrationIconButton(get: jotai.Getter): IconButtonDecl | null {
- if (!this.termRef.current?.shellIntegrationStatusAtom) {
- return null;
- }
- const shellIntegrationStatus = get(this.termRef.current.shellIntegrationStatusAtom);
- const claudeCodeActive = get(this.termRef.current.claudeCodeActiveAtom);
- const icon = claudeCodeActive ? React.createElement(TermClaudeIcon) : "sparkles";
- if (shellIntegrationStatus == null) {
- return {
- elemtype: "iconbutton",
- icon,
- className: "text-muted",
- title: "No shell integration — Wave AI unable to run commands.",
- noAction: true,
- };
- }
- if (shellIntegrationStatus === "ready") {
- return {
- elemtype: "iconbutton",
- icon,
- className: "text-accent",
- title: "Shell ready — Wave AI can run commands in this terminal.",
- noAction: true,
- };
- }
- if (shellIntegrationStatus === "running-command") {
- let title = claudeCodeActive
- ? "Claude Code Detected"
- : "Shell busy — Wave AI unable to run commands while another command is running.";
-
- if (this.termRef.current) {
- const inAltBuffer = this.termRef.current.terminal?.buffer?.active?.type === "alternate";
- const lastCommand = get(this.termRef.current.lastCommandAtom);
- const blockingCmd = getBlockingCommand(lastCommand, inAltBuffer);
- if (blockingCmd) {
- title = `Wave AI integration disabled while you're inside ${blockingCmd}.`;
- }
- }
-
- return {
- elemtype: "iconbutton",
- icon,
- className: "text-warning",
- title: title,
- noAction: true,
- };
- }
+ getShellIntegrationIconButton(_get: jotai.Getter): IconButtonDecl | null {
return null;
}
@@ -616,9 +564,7 @@ export class TermViewModel implements ViewModel {
keyDownHandler(waveEvent: WaveKeyboardEvent): boolean {
if (keyutil.checkKeyPressed(waveEvent, "Ctrl:r")) {
const shellIntegrationStatus = readAtom(this.termRef?.current?.shellIntegrationStatusAtom);
- if (shellIntegrationStatus === "ready") {
- recordTEvent("action:term", { "action:type": "term:ctrlr" });
- }
+ if (shellIntegrationStatus === "ready") { }
// just for telemetry, we allow this keybinding through, back to the terminal
return false;
}
@@ -841,22 +787,6 @@ export class TermViewModel implements ViewModel {
},
});
menu.push({ type: "separator" });
- menu.push({
- label: "Send to Wave AI",
- click: () => {
- if (selection) {
- const aiModel = WaveAIModel.getInstance();
- aiModel.appendText(selection, true, { scrollToBottom: true });
- const layoutModel = WorkspaceLayoutModel.getInstance();
- if (!layoutModel.getAIPanelVisible()) {
- layoutModel.setAIPanelVisible(true);
- }
- aiModel.focusInput();
- }
- },
- });
-
- menu.push({ type: "separator" });
}
const hoveredLinkUri = this.termRef.current?.hoveredLinkUri;
diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts
index d10b600459..45138079a4 100644
--- a/frontend/app/view/term/termwrap.ts
+++ b/frontend/app/view/term/termwrap.ts
@@ -74,6 +74,12 @@ type TermWrapOptions = {
nodeModel?: BlockNodeModel;
};
+// DEC private modes that are safe to replay on durable reconnect.
+// Mouse tracking (1000-1006) and bracketed paste (2004) are included.
+// Alternate screen (47/1049), cursor visibility (25), and synchronized
+// output (2026) are excluded to avoid unintended display side effects.
+const SafeReplayDecModes = new Set([1000, 1002, 1003, 1005, 1006, 2004]);
+
export class TermWrap {
tabId: string;
blockId: string;
@@ -122,6 +128,9 @@ export class TermWrap {
inSyncTransaction: boolean = false;
inRepaintTransaction: boolean = false;
+ // Track active DEC private modes for durable reconnect state restoration
+ activeDecModes: Set = new Set();
+
constructor(
tabId: string,
blockId: string,
@@ -225,9 +234,13 @@ export class TermWrap {
if (params == null || params.length < 1) {
return false;
}
- if (params[0] === 2026) {
- this.lastMode2026SetTs = Date.now();
- this.inSyncTransaction = true;
+ for (const mode of params) {
+ const m = mode as number;
+ this.activeDecModes.add(m);
+ if (m === 2026) {
+ this.lastMode2026SetTs = Date.now();
+ this.inSyncTransaction = true;
+ }
}
return false;
})
@@ -235,18 +248,36 @@ export class TermWrap {
this.toDispose.push(
this.terminal.parser.registerCsiHandler({ prefix: "?", final: "l" }, (params) => {
if (params == null || params.length < 1) {
+ // No parameters: clear all DEC modes
+ this.activeDecModes.clear();
+ if (this.inSyncTransaction) {
+ this.lastMode2026ResetTs = Date.now();
+ this.inSyncTransaction = false;
+ const wasRepaint = this.inRepaintTransaction;
+ this.inRepaintTransaction = false;
+ if (wasRepaint && Date.now() - this.lastClearScrollbackTs <= MaxRepaintTransactionMs) {
+ setTimeout(() => {
+ console.log("[termwrap] repaint transaction complete, scrolling to bottom");
+ this.terminal.scrollToBottom();
+ }, 20);
+ }
+ }
return false;
}
- if (params[0] === 2026) {
- this.lastMode2026ResetTs = Date.now();
- this.inSyncTransaction = false;
- const wasRepaint = this.inRepaintTransaction;
- this.inRepaintTransaction = false;
- if (wasRepaint && Date.now() - this.lastClearScrollbackTs <= MaxRepaintTransactionMs) {
- setTimeout(() => {
- console.log("[termwrap] repaint transaction complete, scrolling to bottom");
- this.terminal.scrollToBottom();
- }, 20);
+ for (const mode of params) {
+ const m = mode as number;
+ this.activeDecModes.delete(m);
+ if (m === 2026) {
+ this.lastMode2026ResetTs = Date.now();
+ this.inSyncTransaction = false;
+ const wasRepaint = this.inRepaintTransaction;
+ this.inRepaintTransaction = false;
+ if (wasRepaint && Date.now() - this.lastClearScrollbackTs <= MaxRepaintTransactionMs) {
+ setTimeout(() => {
+ console.log("[termwrap] repaint transaction complete, scrolling to bottom");
+ this.terminal.scrollToBottom();
+ }, 20);
+ }
}
}
return false;
@@ -542,6 +573,11 @@ export class TermWrap {
this.terminal.resize(curTermSize.cols, curTermSize.rows);
}
}
+ // Restore DEC private mode state so xterm.js matches the remote application
+ const decModes = cacheFile.meta["decmodes"] as string;
+ if (decModes) {
+ this.replayDecModes(decModes);
+ }
}
const { data: mainData, fileInfo: mainFile } = await fetchWaveFile(zoneId, TermFileName, ptyOffset);
console.log(
@@ -587,15 +623,44 @@ export class TermWrap {
}
}
+ serializeDecModes(): string {
+ if (this.activeDecModes.size === 0) {
+ return "";
+ }
+ const modes: number[] = [];
+ for (const mode of this.activeDecModes) {
+ modes.push(mode);
+ }
+ modes.sort((a, b) => a - b);
+ return modes.join(",");
+ }
+
+ replayDecModes(decModesStr: string): void {
+ if (!decModesStr) {
+ return;
+ }
+ const modes = decModesStr.split(",").map((s) => parseInt(s, 10)).filter((n) => !isNaN(n) && SafeReplayDecModes.has(n));
+ if (modes.length === 0) {
+ return;
+ }
+ let seq = "";
+ for (const mode of modes) {
+ seq += `\x1b[?${mode}h`;
+ }
+ console.log("[termwrap] replaying DEC private modes", modes);
+ this.terminal.write(seq);
+ }
+
processAndCacheData() {
if (this.dataBytesProcessed < MinDataProcessedForCache) {
return;
}
const serializedOutput = this.serializeAddon.serialize();
const termSize: TermSize = { rows: this.terminal.rows, cols: this.terminal.cols };
- console.log("idle timeout term", this.dataBytesProcessed, serializedOutput.length, termSize);
+ const decModes = this.serializeDecModes();
+ console.log("idle timeout term", this.dataBytesProcessed, serializedOutput.length, termSize, "decmodes:", decModes);
fireAndForget(() =>
- services.BlockService.SaveTerminalState(this.blockId, serializedOutput, "full", this.ptyOffset, termSize)
+ services.BlockService.SaveTerminalState(this.blockId, serializedOutput, "full", this.ptyOffset, termSize, decModes)
);
this.dataBytesProcessed = 0;
}
diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx
deleted file mode 100644
index baf6acf711..0000000000
--- a/frontend/app/view/waveai/waveai.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2026, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { Button } from "@/app/element/button";
-import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
-import { atom } from "jotai";
-import { useCallback } from "react";
-
-export class WaveAiModel implements ViewModel {
- viewType = "waveai";
- viewIcon = atom("sparkles");
- viewName = atom("Wave AI");
- noPadding = atom(true);
- viewComponent = WaveAiDeprecatedView;
-
- constructor(_: ViewModelInitType) {}
-}
-
-function WaveAiDeprecatedView() {
- const handleOpenAIPanel = useCallback(() => {
- WorkspaceLayoutModel.getInstance().setAIPanelVisible(true);
- }, []);
-
- return (
-
-
-
-
This legacy Wave AI block is no longer supported
-
- This older AI widget has been retired. Please use the modern Wave AI panel for AI chats, terminal
- context, tools, and uploads going forward.
-
-
- Open Wave AI panel
-
-
-
-
- );
-}
diff --git a/frontend/app/view/waveconfig/waveaivisual.tsx b/frontend/app/view/waveconfig/waveaivisual.tsx
deleted file mode 100644
index 8029c20b70..0000000000
--- a/frontend/app/view/waveconfig/waveaivisual.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2025, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import type { WaveConfigViewModel } from "@/app/view/waveconfig/waveconfig-model";
-import { memo } from "react";
-
-interface WaveAIVisualContentProps {
- model: WaveConfigViewModel;
-}
-
-export const WaveAIVisualContent = memo(({ model }: WaveAIVisualContentProps) => {
- return (
-
-
Wave AI Modes - Visual Editor
-
Visual editor coming soon...
-
- );
-});
-
-WaveAIVisualContent.displayName = "WaveAIVisualContent";
\ No newline at end of file
diff --git a/frontend/app/view/waveconfig/waveconfig-model.ts b/frontend/app/view/waveconfig/waveconfig-model.ts
index 73703c2e87..b41913a557 100644
--- a/frontend/app/view/waveconfig/waveconfig-model.ts
+++ b/frontend/app/view/waveconfig/waveconfig-model.ts
@@ -32,29 +32,6 @@ export type ConfigFile = {
export const SecretNameRegex = /^[A-Za-z][A-Za-z0-9_]*$/;
-function validateAiJson(parsed: any): ValidationResult {
- const keys = Object.keys(parsed);
- for (const key of keys) {
- if (!key.startsWith("ai@")) {
- return { error: `Invalid key "${key}": all top-level keys must start with "ai@"` };
- }
- }
- return { success: true };
-}
-
-function validateWaveAiJson(parsed: any): ValidationResult {
- const keys = Object.keys(parsed);
- const keyPattern = /^[a-zA-Z0-9_@.-]+$/;
- for (const key of keys) {
- if (!keyPattern.test(key)) {
- return {
- error: `Invalid key "${key}": keys must only contain letters, numbers, underscores, @, dots, and hyphens`,
- };
- }
- }
- return { success: true };
-}
-
function makeConfigFiles(isWindows: boolean): ConfigFile[] {
return [
{
@@ -79,16 +56,6 @@ function makeConfigFiles(isWindows: boolean): ConfigFile[] {
docsUrl: "https://docs.waveterm.dev/customwidgets",
hasJsonView: true,
},
- {
- name: "Wave AI Modes",
- path: "waveai.json",
- language: "json",
- description: "Local models and BYOK",
- docsUrl: "https://docs.waveterm.dev/waveai-modes",
- validator: validateWaveAiJson,
- hasJsonView: true,
- // visualComponent: WaveAIVisualContent,
- },
{
name: "Tab Backgrounds",
path: "backgrounds.json",
@@ -114,15 +81,6 @@ const deprecatedConfigFiles: ConfigFile[] = [
deprecated: true,
hasJsonView: true,
},
- {
- name: "AI Presets",
- path: "presets/ai.json",
- language: "json",
- deprecated: true,
- docsUrl: "https://docs.waveterm.dev/ai-presets",
- validator: validateAiJson,
- hasJsonView: true,
- },
];
export class WaveConfigViewModel implements ViewModel {
@@ -488,18 +446,7 @@ export class WaveConfigViewModel implements ViewModel {
globalStore.set(this.errorMessageAtom, null);
try {
- await this.env.rpc.SetSecretsCommand(TabRpcClient, { [selectedSecret]: secretValue });
- this.env.rpc.RecordTEventCommand(
- TabRpcClient,
- {
- event: "action:other",
- props: {
- "action:type": "waveconfig:savesecret",
- },
- },
- { noresponse: true }
- );
- this.closeSecretView();
+ await this.env.rpc.SetSecretsCommand(TabRpcClient, { [selectedSecret]: secretValue }); this.closeSecretView();
} catch (error) {
globalStore.set(this.errorMessageAtom, `Failed to save secret: ${error.message}`);
} finally {
@@ -569,18 +516,7 @@ export class WaveConfigViewModel implements ViewModel {
globalStore.set(this.errorMessageAtom, null);
try {
- await this.env.rpc.SetSecretsCommand(TabRpcClient, { [name]: value });
- this.env.rpc.RecordTEventCommand(
- TabRpcClient,
- {
- event: "action:other",
- props: {
- "action:type": "waveconfig:savesecret",
- },
- },
- { noresponse: true }
- );
- globalStore.set(this.isAddingNewAtom, false);
+ await this.env.rpc.SetSecretsCommand(TabRpcClient, { [name]: value }); globalStore.set(this.isAddingNewAtom, false);
globalStore.set(this.newSecretNameAtom, "");
globalStore.set(this.newSecretValueAtom, "");
await this.refreshSecrets();
diff --git a/frontend/app/view/waveconfig/waveconfigenv.ts b/frontend/app/view/waveconfig/waveconfigenv.ts
index c76352cbde..afb9fcaf6f 100644
--- a/frontend/app/view/waveconfig/waveconfigenv.ts
+++ b/frontend/app/view/waveconfig/waveconfigenv.ts
@@ -16,9 +16,7 @@ export type WaveConfigEnv = WaveEnvSubset<{
GetSecretsLinuxStorageBackendCommand: WaveEnv["rpc"]["GetSecretsLinuxStorageBackendCommand"];
GetSecretsNamesCommand: WaveEnv["rpc"]["GetSecretsNamesCommand"];
GetSecretsCommand: WaveEnv["rpc"]["GetSecretsCommand"];
- SetSecretsCommand: WaveEnv["rpc"]["SetSecretsCommand"];
- RecordTEventCommand: WaveEnv["rpc"]["RecordTEventCommand"];
- };
+ SetSecretsCommand: WaveEnv["rpc"]["SetSecretsCommand"]; };
atoms: {
fullConfigAtom: WaveEnv["atoms"]["fullConfigAtom"];
};
diff --git a/frontend/app/workspace/widgets.tsx b/frontend/app/workspace/widgets.tsx
index f11eca91da..c6d673a826 100644
--- a/frontend/app/workspace/widgets.tsx
+++ b/frontend/app/workspace/widgets.tsx
@@ -32,7 +32,6 @@ export type WidgetsEnv = WaveEnvSubset<{
fullConfigAtom: WaveEnv["atoms"]["fullConfigAtom"];
hasConfigErrors: WaveEnv["atoms"]["hasConfigErrors"];
workspaceId: WaveEnv["atoms"]["workspaceId"];
- hasCustomAIPresetsAtom: WaveEnv["atoms"]["hasCustomAIPresetsAtom"];
};
createBlock: WaveEnv["createBlock"];
showContextMenu: WaveEnv["showContextMenu"];
diff --git a/frontend/app/workspace/workspace-layout-model.ts b/frontend/app/workspace/workspace-layout-model.ts
index eb7065f90c..a904863aec 100644
--- a/frontend/app/workspace/workspace-layout-model.ts
+++ b/frontend/app/workspace/workspace-layout-model.ts
@@ -1,26 +1,20 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { WaveAIModel } from "@/app/aipanel/waveai-model";
import { globalStore } from "@/app/store/jotaiStore";
import { isBuilderWindow } from "@/app/store/windowtype";
import * as WOS from "@/app/store/wos";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { getLayoutModelForStaticTab } from "@/layout/lib/layoutModelHooks";
-import { atoms, getApi, getOrefMetaKeyAtom, getSettingsKeyAtom, recordTEvent, refocusNode } from "@/store/global";
+import { atoms, getApi, getOrefMetaKeyAtom, getSettingsKeyAtom, refocusNode } from "@/store/global";
import debug from "debug";
import * as jotai from "jotai";
import { debounce } from "lodash-es";
-import { ImperativePanelGroupHandle, ImperativePanelHandle } from "react-resizable-panels";
+import { ImperativePanelGroupHandle } from "react-resizable-panels";
const dlog = debug("wave:workspace");
-const AIPanel_DefaultWidth = 300;
-const AIPanel_DefaultWidthRatio = 0.33;
-const AIPanel_MinWidth = 300;
-const AIPanel_MaxWidthRatio = 0.66;
-
const VTabBar_DefaultWidth = 220;
const VTabBar_MinWidth = 110;
const VTabBar_MaxWidth = 280;
@@ -29,49 +23,27 @@ function clampVTabWidth(w: number): number {
return Math.max(VTabBar_MinWidth, Math.min(w, VTabBar_MaxWidth));
}
-function clampAIPanelWidth(w: number, windowWidth: number): number {
- const maxWidth = Math.floor(windowWidth * AIPanel_MaxWidthRatio);
- if (AIPanel_MinWidth > maxWidth) return AIPanel_MinWidth;
- return Math.max(AIPanel_MinWidth, Math.min(w, maxWidth));
-}
-
class WorkspaceLayoutModel {
private static instance: WorkspaceLayoutModel | null = null;
- aiPanelRef: ImperativePanelHandle | null;
- vtabPanelRef: ImperativePanelHandle | null;
outerPanelGroupRef: ImperativePanelGroupHandle | null;
- innerPanelGroupRef: ImperativePanelGroupHandle | null;
panelContainerRef: HTMLDivElement | null;
- aiPanelWrapperRef: HTMLDivElement | null;
vtabPanelWrapperRef: HTMLDivElement | null;
- panelVisibleAtom: jotai.PrimitiveAtom;
private inResize: boolean;
- private aiPanelVisible: boolean;
- private aiPanelWidth: number | null;
private vtabWidth: number;
private vtabVisible: boolean;
private transitionTimeoutRef: NodeJS.Timeout | null = null;
- private focusTimeoutRef: NodeJS.Timeout | null = null;
- private debouncedPersistAIWidth: () => void;
private debouncedPersistVTabWidth: () => void;
widgetsSidebarVisibleAtom: jotai.Atom;
private constructor() {
- this.aiPanelRef = null;
- this.vtabPanelRef = null;
this.outerPanelGroupRef = null;
- this.innerPanelGroupRef = null;
this.panelContainerRef = null;
- this.aiPanelWrapperRef = null;
this.vtabPanelWrapperRef = null;
this.inResize = false;
- this.aiPanelVisible = false;
- this.aiPanelWidth = null;
this.vtabWidth = VTabBar_DefaultWidth;
this.vtabVisible = false;
- this.panelVisibleAtom = jotai.atom(false);
this.widgetsSidebarVisibleAtom = jotai.atom(
(get) =>
get(getOrefMetaKeyAtom(WOS.makeORef("workspace", this.getWorkspaceId()), "layout:widgetsvisible")) ??
@@ -81,21 +53,6 @@ class WorkspaceLayoutModel {
this.handleWindowResize = this.handleWindowResize.bind(this);
this.handleOuterPanelLayout = this.handleOuterPanelLayout.bind(this);
- this.handleInnerPanelLayout = this.handleInnerPanelLayout.bind(this);
-
- this.debouncedPersistAIWidth = debounce(() => {
- if (!this.aiPanelVisible) return;
- const width = this.aiPanelWrapperRef?.offsetWidth;
- if (width == null || width <= 0) return;
- try {
- RpcApi.SetMetaCommand(TabRpcClient, {
- oref: WOS.makeORef("tab", this.getTabId()),
- meta: { "waveai:panelwidth": width },
- });
- } catch (e) {
- console.warn("Failed to persist AI panel width:", e);
- }
- }, 300);
this.debouncedPersistVTabWidth = debounce(() => {
if (!this.vtabVisible) return;
@@ -129,30 +86,13 @@ class WorkspaceLayoutModel {
return globalStore.get(atoms.workspace)?.oid ?? "";
}
- private getPanelOpenAtom(): jotai.Atom {
- return getOrefMetaKeyAtom(WOS.makeORef("tab", this.getTabId()), "waveai:panelopen");
- }
-
- private getPanelWidthAtom(): jotai.Atom {
- return getOrefMetaKeyAtom(WOS.makeORef("tab", this.getTabId()), "waveai:panelwidth");
- }
-
private getVTabBarWidthAtom(): jotai.Atom {
return getOrefMetaKeyAtom(WOS.makeORef("workspace", this.getWorkspaceId()), "layout:vtabbarwidth");
}
private initializeFromMeta(): void {
try {
- const savedVisible = globalStore.get(this.getPanelOpenAtom());
- const savedAIWidth = globalStore.get(this.getPanelWidthAtom());
const savedVTabWidth = globalStore.get(this.getVTabBarWidthAtom());
- if (savedVisible != null) {
- this.aiPanelVisible = savedVisible;
- globalStore.set(this.panelVisibleAtom, savedVisible);
- }
- if (savedAIWidth != null) {
- this.aiPanelWidth = savedAIWidth;
- }
if (savedVTabWidth != null && savedVTabWidth > 0) {
this.vtabWidth = savedVTabWidth;
}
@@ -166,15 +106,6 @@ class WorkspaceLayoutModel {
// ---- Resolved width getters (always clamped) ----
- private getResolvedAIWidth(windowWidth: number): number {
- let w = this.aiPanelWidth;
- if (w == null) {
- w = Math.max(AIPanel_DefaultWidth, windowWidth * AIPanel_DefaultWidthRatio);
- this.aiPanelWidth = w;
- }
- return clampAIPanelWidth(w, windowWidth);
- }
-
private getResolvedVTabWidth(): number {
return clampVTabWidth(this.vtabWidth);
}
@@ -184,37 +115,19 @@ class WorkspaceLayoutModel {
// It takes the current state (visibility flags + stored px widths)
// and produces the two percentage arrays for the panel groups.
- private computeLayout(windowWidth: number): { outer: number[]; inner: number[] } {
+ private computeLayout(windowWidth: number): number[] {
const vtabW = this.vtabVisible ? this.getResolvedVTabWidth() : 0;
- const aiW = this.aiPanelVisible ? this.getResolvedAIWidth(windowWidth) : 0;
- const leftGroupW = vtabW + aiW;
-
- // outer: [leftGroupPct, contentPct]
- const leftPct = windowWidth > 0 ? (leftGroupW / windowWidth) * 100 : 0;
+ const leftPct = windowWidth > 0 ? (vtabW / windowWidth) * 100 : 0;
const contentPct = Math.max(0, 100 - leftPct);
-
- // inner: [vtabPct, aiPanelPct] relative to leftGroupW
- let vtabPct: number;
- let aiPct: number;
- if (leftGroupW > 0) {
- vtabPct = (vtabW / leftGroupW) * 100;
- aiPct = 100 - vtabPct;
- } else {
- vtabPct = 50;
- aiPct = 50;
- }
-
- return { outer: [leftPct, contentPct], inner: [vtabPct, aiPct] };
+ return [leftPct, contentPct];
}
private commitLayouts(windowWidth: number): void {
- if (!this.outerPanelGroupRef || !this.innerPanelGroupRef) return;
- const { outer, inner } = this.computeLayout(windowWidth);
+ if (!this.outerPanelGroupRef) return;
+ const layout = this.computeLayout(windowWidth);
this.inResize = true;
- this.outerPanelGroupRef.setLayout(outer);
- this.innerPanelGroupRef.setLayout(inner);
+ this.outerPanelGroupRef.setLayout(layout);
this.inResize = false;
- this.updateWrapperWidth();
}
// ---- Drag handlers ----
@@ -226,42 +139,9 @@ class WorkspaceLayoutModel {
const windowWidth = window.innerWidth;
const newLeftGroupPx = (sizes[0] / 100) * windowWidth;
- if (this.vtabVisible && this.aiPanelVisible) {
- // vtab stays constant, aipanel absorbs the change
- const vtabW = this.getResolvedVTabWidth();
- this.aiPanelWidth = clampAIPanelWidth(newLeftGroupPx - vtabW, windowWidth);
- this.debouncedPersistAIWidth();
- } else if (this.vtabVisible) {
+ if (this.vtabVisible) {
this.vtabWidth = clampVTabWidth(newLeftGroupPx);
this.debouncedPersistVTabWidth();
- } else if (this.aiPanelVisible) {
- this.aiPanelWidth = clampAIPanelWidth(newLeftGroupPx, windowWidth);
- this.debouncedPersistAIWidth();
- }
-
- this.commitLayouts(windowWidth);
- }
-
- handleInnerPanelLayout(sizes: number[]): void {
- if (this.inResize) return;
- if (!this.vtabVisible || !this.aiPanelVisible) return;
-
- const windowWidth = window.innerWidth;
- const vtabW = this.getResolvedVTabWidth();
- const aiW = this.getResolvedAIWidth(windowWidth);
- const leftGroupW = vtabW + aiW;
-
- const newVTabW = (sizes[0] / 100) * leftGroupW;
- const clampedVTab = clampVTabWidth(newVTabW);
- const newAIW = clampAIPanelWidth(leftGroupW - clampedVTab, windowWidth);
-
- if (clampedVTab !== this.vtabWidth) {
- this.vtabWidth = clampedVTab;
- this.debouncedPersistVTabWidth();
- }
- if (newAIW !== this.aiPanelWidth) {
- this.aiPanelWidth = newAIW;
- this.debouncedPersistAIWidth();
}
this.commitLayouts(windowWidth);
@@ -282,44 +162,18 @@ class WorkspaceLayoutModel {
}
registerRefs(
- aiPanelRef: ImperativePanelHandle,
outerPanelGroupRef: ImperativePanelGroupHandle,
- innerPanelGroupRef: ImperativePanelGroupHandle,
panelContainerRef: HTMLDivElement,
- aiPanelWrapperRef: HTMLDivElement,
- vtabPanelRef?: ImperativePanelHandle,
vtabPanelWrapperRef?: HTMLDivElement,
showLeftTabBar?: boolean
): void {
- this.aiPanelRef = aiPanelRef;
- this.vtabPanelRef = vtabPanelRef ?? null;
this.outerPanelGroupRef = outerPanelGroupRef;
- this.innerPanelGroupRef = innerPanelGroupRef;
this.panelContainerRef = panelContainerRef;
- this.aiPanelWrapperRef = aiPanelWrapperRef;
this.vtabPanelWrapperRef = vtabPanelWrapperRef ?? null;
this.vtabVisible = showLeftTabBar ?? false;
- this.syncPanelCollapse();
this.commitLayouts(window.innerWidth);
}
- private syncPanelCollapse(): void {
- if (this.aiPanelRef) {
- if (this.aiPanelVisible) {
- this.aiPanelRef.expand();
- } else {
- this.aiPanelRef.collapse();
- }
- }
- if (this.vtabPanelRef) {
- if (this.vtabVisible) {
- this.vtabPanelRef.expand();
- } else {
- this.vtabPanelRef.collapse();
- }
- }
- }
-
// ---- Transitions ----
enableTransitions(duration: number): void {
@@ -340,97 +194,17 @@ class WorkspaceLayoutModel {
}, duration);
}
- // ---- Wrapper width (AI panel inner content width) ----
-
- updateWrapperWidth(): void {
- if (!this.aiPanelWrapperRef) return;
- const width = this.getResolvedAIWidth(window.innerWidth);
- this.aiPanelWrapperRef.style.width = `${width}px`;
- }
-
// ---- Public getters ----
- getAIPanelVisible(): boolean {
- return this.aiPanelVisible;
- }
-
- getAIPanelWidth(): number {
- return this.getResolvedAIWidth(window.innerWidth);
- }
-
- // ---- Initial percentage helpers (used by workspace.tsx for defaultSize) ----
-
getLeftGroupInitialPercentage(windowWidth: number, showLeftTabBar: boolean): number {
const vtabW = showLeftTabBar && !isBuilderWindow() ? this.getResolvedVTabWidth() : 0;
- const aiW = this.aiPanelVisible ? this.getResolvedAIWidth(windowWidth) : 0;
- return ((vtabW + aiW) / windowWidth) * 100;
- }
-
- getInnerVTabInitialPercentage(windowWidth: number, showLeftTabBar: boolean): number {
- if (!showLeftTabBar || isBuilderWindow()) return 0;
- const vtabW = this.getResolvedVTabWidth();
- const aiW = this.aiPanelVisible ? this.getResolvedAIWidth(windowWidth) : 0;
- const total = vtabW + aiW;
- if (total === 0) return 50;
- return (vtabW / total) * 100;
- }
-
- getInnerAIPanelInitialPercentage(windowWidth: number, showLeftTabBar: boolean): number {
- const vtabW = showLeftTabBar && !isBuilderWindow() ? this.getResolvedVTabWidth() : 0;
- const aiW = this.aiPanelVisible ? this.getResolvedAIWidth(windowWidth) : 0;
- const total = vtabW + aiW;
- if (total === 0) return 50;
- return (aiW / total) * 100;
- }
-
- // ---- Toggle visibility ----
-
- setAIPanelVisible(visible: boolean, opts?: { nofocus?: boolean }): void {
- if (this.focusTimeoutRef != null) {
- clearTimeout(this.focusTimeoutRef);
- this.focusTimeoutRef = null;
- }
- const wasVisible = this.aiPanelVisible;
- this.aiPanelVisible = visible;
- if (visible && !wasVisible) {
- recordTEvent("action:openwaveai");
- }
- globalStore.set(this.panelVisibleAtom, visible);
- getApi().setWaveAIOpen(visible);
- RpcApi.SetMetaCommand(TabRpcClient, {
- oref: WOS.makeORef("tab", this.getTabId()),
- meta: { "waveai:panelopen": visible },
- });
- this.enableTransitions(250);
- this.syncPanelCollapse();
- this.commitLayouts(window.innerWidth);
-
- if (visible) {
- if (!opts?.nofocus) {
- this.focusTimeoutRef = setTimeout(() => {
- WaveAIModel.getInstance().focusInput();
- this.focusTimeoutRef = null;
- }, 350);
- }
- } else {
- const layoutModel = getLayoutModelForStaticTab();
- const focusedNode = globalStore.get(layoutModel.focusedNode);
- if (focusedNode == null) {
- layoutModel.focusFirstNode();
- return;
- }
- const blockId = focusedNode?.data?.blockId;
- if (blockId != null) {
- refocusNode(blockId);
- }
- }
+ return (vtabW / windowWidth) * 100;
}
setShowLeftTabBar(showLeftTabBar: boolean): void {
if (this.vtabVisible === showLeftTabBar) return;
this.vtabVisible = showLeftTabBar;
this.enableTransitions(250);
- this.syncPanelCollapse();
this.commitLayouts(window.innerWidth);
}
}
diff --git a/frontend/app/workspace/workspace.tsx b/frontend/app/workspace/workspace.tsx
index 08278a4eed..6d736b49f5 100644
--- a/frontend/app/workspace/workspace.tsx
+++ b/frontend/app/workspace/workspace.tsx
@@ -1,7 +1,6 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { AIPanel } from "@/app/aipanel/aipanel";
import { ErrorBoundary } from "@/app/element/errorboundary";
import { CenteredDiv } from "@/app/element/quickelems";
import { ModalsRenderer } from "@/app/modals/modalsrenderer";
@@ -16,7 +15,6 @@ import { useAtomValue } from "jotai";
import { memo, useEffect, useRef } from "react";
import {
ImperativePanelGroupHandle,
- ImperativePanelHandle,
Panel,
PanelGroup,
PanelResizeHandle,
@@ -45,48 +43,29 @@ const WorkspaceElem = memo(() => {
const ws = useAtomValue(atoms.workspace);
const tabBarPosition = useAtomValue(getSettingsKeyAtom("app:tabbar")) ?? "top";
const showLeftTabBar = tabBarPosition === "left";
- const aiPanelVisible = useAtomValue(workspaceLayoutModel.panelVisibleAtom);
const widgetsSidebarVisible = useAtomValue(workspaceLayoutModel.widgetsSidebarVisibleAtom);
const windowWidth = window.innerWidth;
const leftGroupInitialPct = workspaceLayoutModel.getLeftGroupInitialPercentage(windowWidth, showLeftTabBar);
- const innerVTabInitialPct = workspaceLayoutModel.getInnerVTabInitialPercentage(windowWidth, showLeftTabBar);
- const innerAIPanelInitialPct = workspaceLayoutModel.getInnerAIPanelInitialPercentage(windowWidth, showLeftTabBar);
const outerPanelGroupRef = useRef(null);
- const innerPanelGroupRef = useRef(null);
- const aiPanelRef = useRef(null);
- const vtabPanelRef = useRef(null);
const panelContainerRef = useRef(null);
- const aiPanelWrapperRef = useRef(null);
const vtabPanelWrapperRef = useRef(null);
// showLeftTabBar is passed as a seed value only; subsequent changes are handled by setShowLeftTabBar below.
// Do NOT add showLeftTabBar as a dep here — re-registering refs on config changes would redundantly re-run commitLayouts.
useEffect(() => {
if (
- aiPanelRef.current &&
outerPanelGroupRef.current &&
- innerPanelGroupRef.current &&
- panelContainerRef.current &&
- aiPanelWrapperRef.current
+ panelContainerRef.current
) {
workspaceLayoutModel.registerRefs(
- aiPanelRef.current,
outerPanelGroupRef.current,
- innerPanelGroupRef.current,
panelContainerRef.current,
- aiPanelWrapperRef.current,
- vtabPanelRef.current ?? undefined,
vtabPanelWrapperRef.current ?? undefined,
showLeftTabBar
);
}
}, []);
- useEffect(() => {
- const isVisible = workspaceLayoutModel.getAIPanelVisible();
- getApi().setWaveAIOpen(isVisible);
- }, []);
-
useEffect(() => {
window.addEventListener("resize", workspaceLayoutModel.handleWindowResize);
return () => window.removeEventListener("resize", workspaceLayoutModel.handleWindowResize);
@@ -102,9 +81,7 @@ const WorkspaceElem = memo(() => {
return () => window.removeEventListener("focus", handleFocus);
}, []);
- const innerHandleVisible = showLeftTabBar && aiPanelVisible;
- const innerHandleClass = `bg-transparent hover:bg-zinc-500/20 transition-colors ${innerHandleVisible ? "w-0.5" : "w-0 pointer-events-none"}`;
- const outerHandleVisible = showLeftTabBar || aiPanelVisible;
+ const outerHandleVisible = showLeftTabBar;
const outerHandleClass = `bg-transparent hover:bg-zinc-500/20 transition-colors ${outerHandleVisible ? "w-0.5" : "w-0 pointer-events-none"}`;
return (
@@ -119,38 +96,9 @@ const WorkspaceElem = memo(() => {
ref={outerPanelGroupRef}
>
-
-
-
- {showLeftTabBar && }
-
-
-
-
-
-
-
+
+ {showLeftTabBar && }
+
diff --git a/frontend/builder/builder-buildpanel.tsx b/frontend/builder/builder-buildpanel.tsx
index 811747e720..7ff0d2dc01 100644
--- a/frontend/builder/builder-buildpanel.tsx
+++ b/frontend/builder/builder-buildpanel.tsx
@@ -1,7 +1,6 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { WaveAIModel } from "@/app/aipanel/waveai-model";
import { ContextMenuModel } from "@/app/store/contextmenu";
import { globalStore } from "@/app/store/jotaiStore";
import { BuilderAppPanelModel } from "@/builder/store/builder-apppanel-model";
@@ -20,16 +19,6 @@ function handleBuildPanelContextMenu(e: React.MouseEvent, selectedText: string):
const menu: ContextMenuItem[] = [
{ role: "copy" },
- { type: "separator" },
- {
- label: "Add to Context",
- click: () => {
- const model = WaveAIModel.getInstance();
- const formattedText = `from builder output:\n\`\`\`\n${selectedText}\n\`\`\``;
- model.appendText(formattedText, true);
- model.focusInput();
- },
- },
];
ContextMenuModel.getInstance().showContextMenu(menu, e);
}
@@ -82,21 +71,6 @@ const BuilderBuildPanel = memo(() => {
BuilderAppPanelModel.getInstance().restartBuilder();
}, []);
- const handleSendToAI = useCallback(() => {
- const currentShowDebug = globalStore.get(model.showDebug);
- const currentOutputLines = globalStore.get(model.outputLines);
- const filtered = currentShowDebug
- ? currentOutputLines
- : currentOutputLines.filter((line) => !line.startsWith("[debug]") && line.trim().length > 0);
-
- const linesToSend = filtered.slice(-200);
- const text = linesToSend.join("\n");
- const aiModel = WaveAIModel.getInstance();
- const formattedText = `from builder output:\n\`\`\`\n${text}\n\`\`\`\n`;
- aiModel.appendText(formattedText, true, { scrollToBottom: true });
- aiModel.focusInput();
- }, [model]);
-
const filteredLines = showDebug
? outputLines
: outputLines.filter((line) => !line.startsWith("[debug]") && line.trim().length > 0);
@@ -115,12 +89,6 @@ const BuilderBuildPanel = memo(() => {
/>
Debug
-
- Send Output to AI
-
{
-
+
+ AI features are disabled
+
diff --git a/frontend/builder/store/builder-focusmanager.ts b/frontend/builder/store/builder-focusmanager.ts
index f360663cc9..dfa04d48fa 100644
--- a/frontend/builder/store/builder-focusmanager.ts
+++ b/frontend/builder/store/builder-focusmanager.ts
@@ -4,7 +4,7 @@
import { globalStore } from "@/app/store/jotaiStore";
import { atom, type PrimitiveAtom } from "jotai";
-export type BuilderFocusType = "waveai" | "app";
+export type BuilderFocusType = "app";
export class BuilderFocusManager {
private static instance: BuilderFocusManager | null = null;
@@ -20,10 +20,6 @@ export class BuilderFocusManager {
return BuilderFocusManager.instance;
}
- setWaveAIFocused() {
- globalStore.set(this.focusType, "waveai");
- }
-
setAppFocused() {
globalStore.set(this.focusType, "app");
}
diff --git a/frontend/builder/tabs/builder-filestab.tsx b/frontend/builder/tabs/builder-filestab.tsx
index b33622b963..2624af6f22 100644
--- a/frontend/builder/tabs/builder-filestab.tsx
+++ b/frontend/builder/tabs/builder-filestab.tsx
@@ -1,7 +1,7 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { formatFileSize } from "@/app/aipanel/ai-utils";
+import { formatFileSize } from "@/util/util";
import { Modal } from "@/app/modals/modal";
import { ContextMenuModel } from "@/app/store/contextmenu";
import { modalsModel } from "@/app/store/modalmodel";
diff --git a/frontend/builder/tabs/builder-previewtab.tsx b/frontend/builder/tabs/builder-previewtab.tsx
index 2976080680..9fb87244f5 100644
--- a/frontend/builder/tabs/builder-previewtab.tsx
+++ b/frontend/builder/tabs/builder-previewtab.tsx
@@ -1,9 +1,7 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
-import { WaveAIModel } from "@/app/aipanel/waveai-model";
import { BuilderAppPanelModel } from "@/builder/store/builder-apppanel-model";
-import { BuilderBuildPanelModel } from "@/builder/store/builder-buildpanel-model";
import { atoms } from "@/store/global";
import { useAtomValue } from "jotai";
import { memo, useState } from "react";
@@ -16,8 +14,7 @@ const EmptyStateView = memo(() => {
No App to Preview
- Get started by using the AI chat interface on the left to create your WaveApp. Describe what you
- want to build, and the AI will help you generate the code.
+ Create an app.go file to get started.
@@ -32,32 +29,10 @@ EmptyStateView.displayName = "EmptyStateView";
const ErrorStateView = memo(({ errorMsg }: { errorMsg: string }) => {
const displayMsg = errorMsg && errorMsg.trim() ? errorMsg : "Unknown Error";
- const waveAIModel = WaveAIModel.getInstance();
- const buildPanelModel = BuilderBuildPanelModel.getInstance();
const appPanelModel = BuilderAppPanelModel.getInstance();
- const outputLines = useAtomValue(buildPanelModel.outputLines);
- const isStreaming = useAtomValue(waveAIModel.isAIStreaming);
const isSecretError = displayMsg.includes("ERR-SECRET");
- const getBuildContext = () => {
- const filteredLines = outputLines.filter((line) => !line.startsWith("[debug]"));
- const buildOutput = filteredLines.join("\n").trim();
- return `Build Error:\n\`\`\`\n${displayMsg}\n\`\`\`\n\nBuild Output:\n\`\`\`\n${buildOutput}\n\`\`\``;
- };
-
- const handleAddToContext = () => {
- const context = getBuildContext();
- waveAIModel.appendText(context, true);
- waveAIModel.focusInput();
- };
-
- const handleAskAIToFix = async () => {
- const context = getBuildContext();
- waveAIModel.appendText("Please help me fix this build error:\n\n" + context, true);
- await waveAIModel.handleSubmit();
- };
-
const handleGoToSecrets = () => {
appPanelModel.setActiveTab("secrets");
};
@@ -96,22 +71,6 @@ const ErrorStateView = memo(({ errorMsg }: { errorMsg: string }) => {
- {!isStreaming && (
-
-
- Add Error to AI Context
-
-
- Ask AI to Fix
-
-
- )}
diff --git a/frontend/layout/lib/layoutModel.ts b/frontend/layout/lib/layoutModel.ts
index 0741df9bcb..62921b6448 100644
--- a/frontend/layout/lib/layoutModel.ts
+++ b/frontend/layout/lib/layoutModel.ts
@@ -625,15 +625,9 @@ export class LayoutModel {
break;
case LayoutTreeActionType.InsertNode:
insertNode(this.treeState, action as LayoutTreeInsertNodeAction);
- if ((action as LayoutTreeInsertNodeAction).focused) {
- FocusManager.getInstance().requestNodeFocus();
- }
break;
case LayoutTreeActionType.InsertNodeAtIndex:
insertNodeAtIndex(this.treeState, action as LayoutTreeInsertNodeAtIndexAction);
- if ((action as LayoutTreeInsertNodeAtIndexAction).focused) {
- FocusManager.getInstance().requestNodeFocus();
- }
break;
case LayoutTreeActionType.DeleteNode:
deleteNode(this.treeState, action as LayoutTreeDeleteNodeAction);
@@ -668,11 +662,9 @@ export class LayoutModel {
}
case LayoutTreeActionType.FocusNode:
focusNode(this.treeState, action as LayoutTreeFocusNodeAction);
- FocusManager.getInstance().requestNodeFocus();
break;
case LayoutTreeActionType.MagnifyNodeToggle:
magnifyNodeToggle(this.treeState, action as LayoutTreeMagnifyNodeToggleAction);
- FocusManager.getInstance().requestNodeFocus();
break;
case LayoutTreeActionType.ClearTree:
clearTree(this.treeState);
@@ -1064,9 +1056,7 @@ export class LayoutModel {
blockNum: atom((get) => get(this.leafOrder).findIndex((leafEntry) => leafEntry.nodeid === nodeid) + 1),
isFocused: atom((get) => {
const treeState = get(this.localTreeStateAtom);
- const isFocused = treeState.focusedNodeId === nodeid;
- const focusType = get(FocusManager.getInstance().focusType);
- return isFocused && focusType === "node";
+ return treeState.focusedNodeId === nodeid;
}),
numLeafs: this.numLeafs,
isResizing: this.isResizing,
@@ -1114,7 +1104,7 @@ export class LayoutModel {
* Switch focus to the next node in the given direction in the layout.
* @param direction The direction in which to switch focus.
*/
- switchNodeFocusInDirection(direction: NavigateDirection, inWaveAI: boolean): NavigationResult {
+ switchNodeFocusInDirection(direction: NavigateDirection): NavigationResult {
const curNodeId = this.focusedNodeId;
// If no node is focused, set focus to the first leaf.
@@ -1134,29 +1124,11 @@ export class LayoutModel {
}
}
let curNodePos: Dimensions;
- if (inWaveAI) {
- // For WaveAI, use a fake position to the left of all nodes
- curNodePos = { left: -10, top: 10, width: 0, height: 0 };
-
- // Only allow "right" navigation from WaveAI
- if (direction !== NavigateDirection.Right) {
- const result: NavigationResult = { success: false };
- if (direction === NavigateDirection.Up) {
- result.atTop = true;
- } else if (direction === NavigateDirection.Down) {
- result.atBottom = true;
- } else if (direction === NavigateDirection.Left) {
- result.atLeft = true;
- }
- return result;
- }
- } else {
- curNodePos = nodePositions.get(curNodeId);
- if (!curNodePos) {
- return { success: false };
- }
- nodePositions.delete(curNodeId);
+ curNodePos = nodePositions.get(curNodeId);
+ if (!curNodePos) {
+ return { success: false };
}
+ nodePositions.delete(curNodeId);
const boundingRect = this.displayContainerRef?.current.getBoundingClientRect();
if (!boundingRect) {
return { success: false };
diff --git a/frontend/preview/mock/defaultconfig.ts b/frontend/preview/mock/defaultconfig.ts
index 415630b2b6..7374c29e4b 100644
--- a/frontend/preview/mock/defaultconfig.ts
+++ b/frontend/preview/mock/defaultconfig.ts
@@ -6,7 +6,6 @@ import mimetypesJson from "../../../pkg/wconfig/defaultconfig/mimetypes.json";
import presetsJson from "../../../pkg/wconfig/defaultconfig/presets.json";
import settingsJson from "../../../pkg/wconfig/defaultconfig/settings.json";
import termthemesJson from "../../../pkg/wconfig/defaultconfig/termthemes.json";
-import waveaiJson from "../../../pkg/wconfig/defaultconfig/waveai.json";
import widgetsJson from "../../../pkg/wconfig/defaultconfig/widgets.json";
export const DefaultFullConfig: FullConfigType = {
@@ -18,7 +17,6 @@ export const DefaultFullConfig: FullConfigType = {
termthemes: termthemesJson as unknown as { [key: string]: TermThemeType },
connections: {},
bookmarks: {},
- waveai: waveaiJson as unknown as { [key: string]: AIModeConfigType },
backgrounds: backgroundsJson as { [key: string]: BackgroundConfigType },
configerrors: [],
};
diff --git a/frontend/preview/mock/mockwaveenv.ts b/frontend/preview/mock/mockwaveenv.ts
index 123b9d3144..60d3d03012 100644
--- a/frontend/preview/mock/mockwaveenv.ts
+++ b/frontend/preview/mock/mockwaveenv.ts
@@ -169,9 +169,7 @@ function makeMockGlobalAtoms(
workspaceId: workspaceIdAtom,
workspace: workspaceAtom,
fullConfigAtom,
- waveaiModeConfigAtom: atom({}) as any,
settingsAtom,
- hasCustomAIPresetsAtom: atom(false),
hasConfigErrors: atom((get) => {
const c = get(fullConfigAtom);
return c?.configerrors != null && c.configerrors.length > 0;
@@ -182,11 +180,9 @@ function makeMockGlobalAtoms(
controlShiftDelayAtom: atom(false) as any,
prefersReducedMotionAtom: atom(false),
documentHasFocus: atom(true) as any,
- updaterStatusAtom: atom("up-to-date" as UpdaterStatus) as any,
modalOpen: atom(false) as any,
allConnStatus: atom([] as ConnStatus[]),
reinitVersion: atom(0) as any,
- waveAIRateLimitInfoAtom: atom(null) as any,
};
if (!atomOverrides) {
return defaults;
diff --git a/frontend/preview/mock/preview-electron-api.ts b/frontend/preview/mock/preview-electron-api.ts
index 36c82f26da..36b98299b4 100644
--- a/frontend/preview/mock/preview-electron-api.ts
+++ b/frontend/preview/mock/preview-electron-api.ts
@@ -25,10 +25,6 @@ const previewElectronApi: ElectronApi = {
openExternal: (_url: string) => {},
onFullScreenChange: (_callback: (isFullScreen: boolean) => void) => {},
onZoomFactorChange: (_callback: (zoomFactor: number) => void) => {},
- onUpdaterStatusChange: (_callback: (status: UpdaterStatus) => void) => {},
- getUpdaterStatus: () => "up-to-date",
- getUpdaterChannel: () => "",
- installAppUpdate: () => {},
onMenuItemAbout: (_callback: () => void) => {},
updateWindowControlsOverlay: (_rect: Dimensions) => {},
onReinjectKey: (_callback: (waveEvent: WaveKeyboardEvent) => void) => {},
@@ -50,9 +46,8 @@ const previewElectronApi: ElectronApi = {
captureScreenshot: (_rect: Electron.Rectangle) => Promise.resolve(""),
setKeyboardChordMode: () => {},
clearWebviewStorage: (_webContentsId: number) => Promise.resolve(),
- setWaveAIOpen: (_isOpen: boolean) => {},
closeBuilderWindow: () => {},
- incrementTermCommands: (_opts?: { isRemote?: boolean; isWsl?: boolean; isDurable?: boolean }) => {},
+
nativePaste: () => {},
openBuilder: (_appId?: string) => {},
setBuilderWindowAppId: (_appId: string) => {},
diff --git a/frontend/preview/previews/aifilediff.preview-util.ts b/frontend/preview/previews/aifilediff.preview-util.ts
deleted file mode 100644
index b3a54c8450..0000000000
--- a/frontend/preview/previews/aifilediff.preview-util.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2026, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { stringToBase64 } from "@/util/util";
-
-export const DefaultAiFileDiffChatId = "preview-aifilediff-chat";
-export const DefaultAiFileDiffToolCallId = "preview-aifilediff-toolcall";
-export const DefaultAiFileDiffFileName = "src/lib/greeting.ts";
-
-export const DefaultAiFileDiffOriginal = `export function greet(name: string) {
- return "Hello " + name;
-}
-
-export function greetAll(names: string[]) {
- return names.map(greet).join("\\n");
-}
-`;
-
-export const DefaultAiFileDiffModified = `export function greet(name: string) {
- const normalizedName = name.trim() || "friend";
- return \`Hello, \${normalizedName}!\`;
-}
-
-export function greetAll(names: string[]) {
- return names.map(greet).join("\\n");
-}
-`;
-
-export function makeMockAiFileDiffResponse(
- original = DefaultAiFileDiffOriginal,
- modified = DefaultAiFileDiffModified
-): CommandWaveAIGetToolDiffRtnData {
- return {
- originalcontents64: stringToBase64(original),
- modifiedcontents64: stringToBase64(modified),
- };
-}
diff --git a/frontend/preview/previews/aifilediff.preview.test.ts b/frontend/preview/previews/aifilediff.preview.test.ts
deleted file mode 100644
index 633b9f1881..0000000000
--- a/frontend/preview/previews/aifilediff.preview.test.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2026, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { base64ToString } from "@/util/util";
-import { describe, expect, it } from "vitest";
-import {
- DefaultAiFileDiffModified,
- DefaultAiFileDiffOriginal,
- makeMockAiFileDiffResponse,
-} from "./aifilediff.preview-util";
-
-describe("aifilediff preview helpers", () => {
- it("encodes the default diff content for the mock rpc response", () => {
- const response = makeMockAiFileDiffResponse();
-
- expect(base64ToString(response.originalcontents64)).toBe(DefaultAiFileDiffOriginal);
- expect(base64ToString(response.modifiedcontents64)).toBe(DefaultAiFileDiffModified);
- });
-
- it("accepts custom original and modified content", () => {
- const response = makeMockAiFileDiffResponse("before", "after");
-
- expect(base64ToString(response.originalcontents64)).toBe("before");
- expect(base64ToString(response.modifiedcontents64)).toBe("after");
- });
-});
diff --git a/frontend/preview/previews/aifilediff.preview.tsx b/frontend/preview/previews/aifilediff.preview.tsx
deleted file mode 100644
index 12654cb7cc..0000000000
--- a/frontend/preview/previews/aifilediff.preview.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2026, Command Line Inc.
-// SPDX-License-Identifier: Apache-2.0
-
-import { Block } from "@/app/block/block";
-import { useWaveEnv } from "@/app/waveenv/waveenv";
-import * as React from "react";
-import { makeMockNodeModel } from "../mock/mock-node-model";
-import { useRpcOverride } from "../mock/use-rpc-override";
-import {
- DefaultAiFileDiffChatId,
- DefaultAiFileDiffFileName,
- DefaultAiFileDiffToolCallId,
- makeMockAiFileDiffResponse,
-} from "./aifilediff.preview-util";
-
-const PreviewNodeId = "preview-aifilediff-node";
-
-export function AiFileDiffPreview() {
- const env = useWaveEnv();
- const [blockId, setBlockId] = React.useState(null);
-
- useRpcOverride("WaveAIGetToolDiffCommand", async (_client, data) => {
- if (data.chatid !== DefaultAiFileDiffChatId || data.toolcallid !== DefaultAiFileDiffToolCallId) {
- return null;
- }
- return makeMockAiFileDiffResponse();
- });
-
- React.useEffect(() => {
- env.createBlock(
- {
- meta: {
- view: "aifilediff",
- file: DefaultAiFileDiffFileName,
- "aifilediff:chatid": DefaultAiFileDiffChatId,
- "aifilediff:toolcallid": DefaultAiFileDiffToolCallId,
- },
- },
- false,
- false
- ).then((id) => setBlockId(id));
- }, []);
-
- const nodeModel = React.useMemo(
- () => (blockId != null ? makeMockNodeModel({ nodeId: PreviewNodeId, blockId }) : null),
- [blockId]
- );
-
- if (blockId == null || nodeModel == null) {
- return null;
- }
-
- return (
-
-
full aifilediff block (mock WOS + mock WaveAI diff RPC)
-
-
- );
-}
diff --git a/frontend/preview/previews/modal-about.preview.tsx b/frontend/preview/previews/modal-about.preview.tsx
index b1053b222e..fde0d14f15 100644
--- a/frontend/preview/previews/modal-about.preview.tsx
+++ b/frontend/preview/previews/modal-about.preview.tsx
@@ -7,7 +7,6 @@ export function AboutModalPreview() {
return (
console.log("close")}
/>
);
diff --git a/frontend/preview/previews/onboarding.preview.tsx b/frontend/preview/previews/onboarding.preview.tsx
index 063320bbb9..d5262e9c26 100644
--- a/frontend/preview/previews/onboarding.preview.tsx
+++ b/frontend/preview/previews/onboarding.preview.tsx
@@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
import Logo from "@/app/asset/logo.svg";
-import { InitPage, NoTelemetryStarPage } from "@/app/onboarding/onboarding";
+import { InitPage } from "@/app/onboarding/onboarding";
import { OnboardingGradientBg } from "@/app/onboarding/onboarding-common";
import { DurableSessionPage } from "@/app/onboarding/onboarding-durable";
-import { FilesPage, MagnifyBlocksPage, WaveAIPage } from "@/app/onboarding/onboarding-features";
+import { FilesPage, MagnifyBlocksPage } from "@/app/onboarding/onboarding-features";
import { StarAskPage } from "@/app/onboarding/onboarding-starask";
import { UpgradeMinorWelcomePage } from "@/app/onboarding/onboarding-upgrade-minor";
import { UpgradeOnboardingFooter, UpgradeOnboardingVersions } from "@/app/onboarding/onboarding-upgrade-patch";
@@ -24,13 +24,7 @@ function OnboardingFeaturesV() {
return (
- {}} />
-
-
-
-
-
-
+
diff --git a/frontend/preview/previews/tabbar.preview.tsx b/frontend/preview/previews/tabbar.preview.tsx
index f2ba2234b7..9bb26ce7a5 100644
--- a/frontend/preview/previews/tabbar.preview.tsx
+++ b/frontend/preview/previews/tabbar.preview.tsx
@@ -39,12 +39,10 @@ function TabBarPreviewInner({ platform, setPlatform }: TabBarPreviewInnerProps)
const env = useWaveEnv();
const loadBadgesEnv = useWaveEnv();
const [showConfigErrors, setShowConfigErrors] = useState(false);
- const [hideAiButton, setHideAiButton] = useState(false);
const [showMenuBar, setShowMenuBar] = useState(false);
const [isFullScreen, setIsFullScreen] = useAtom(env.atoms.isFullScreen);
const [zoomFactor, setZoomFactor] = useAtom(env.atoms.zoomFactorAtom);
const [fullConfig, setFullConfig] = useAtom(env.atoms.fullConfigAtom);
- const [updaterStatus, setUpdaterStatus] = useAtom(env.atoms.updaterStatusAtom);
const workspace = useAtomValue(env.wos.getWaveObjectAtom(`workspace:${TabBarMockWorkspaceId}`));
useEffect(() => {
@@ -56,12 +54,11 @@ function TabBarPreviewInner({ platform, setPlatform }: TabBarPreviewInnerProps)
...(prev ?? ({} as FullConfigType)),
settings: {
...(prev?.settings ?? {}),
- "app:hideaibutton": hideAiButton,
"window:showmenubar": showMenuBar,
},
configerrors: showConfigErrors ? MockConfigErrors : [],
}));
- }, [hideAiButton, showMenuBar, setFullConfig, showConfigErrors]);
+ }, [showMenuBar, setFullConfig, showConfigErrors]);
return (
@@ -78,20 +75,6 @@ function TabBarPreviewInner({ platform, setPlatform }: TabBarPreviewInnerProps)
Linux
-
- Updater banner
- setUpdaterStatus(event.target.value as UpdaterStatus)}
- className="rounded border border-border bg-background px-2 py-1 text-foreground"
- >
- Hidden
- Update Available
- Downloading
- Installing
- Error
-
-
Show config error button
-
- setHideAiButton(event.target.checked)}
- className="cursor-pointer"
- />
- Hide Wave AI button
-
();
const loadBadgesEnv = useWaveEnv();
- const [hideAiButton, setHideAiButton] = useState(false);
const [isFullScreen, setIsFullScreen] = useAtom(env.atoms.isFullScreen);
const [fullConfig, setFullConfig] = useAtom(env.atoms.fullConfigAtom);
- const [updaterStatus, setUpdaterStatus] = useAtom(env.atoms.updaterStatusAtom);
const [width, setWidth] = useState(220);
const workspace = useAtomValue(env.wos.getWaveObjectAtom(`workspace:${TabBarMockWorkspaceId}`));
@@ -49,10 +47,9 @@ function VTabBarPreviewInner({ platform, setPlatform }: VTabBarPreviewInnerProps
...(prev ?? ({} as FullConfigType)),
settings: {
...(prev?.settings ?? {}),
- "app:hideaibutton": hideAiButton,
},
}));
- }, [hideAiButton, setFullConfig]);
+ }, [setFullConfig]);
return (
@@ -69,20 +66,6 @@ function VTabBarPreviewInner({ platform, setPlatform }: VTabBarPreviewInnerProps
Linux
-
- Updater banner
- setUpdaterStatus(event.target.value as UpdaterStatus)}
- className="rounded border border-border bg-background px-2 py-1 text-foreground"
- >
- Hidden
- Update Available
- Downloading
- Installing
- Error
-
-
Width: {width}px
-
- setHideAiButton(event.target.checked)}
- className="cursor-pointer"
- />
- Hide Wave AI button
-
(null);
-
- React.useEffect(() => {
- env.createBlock(
- {
- meta: {
- view: "waveai",
- },
- },
- false,
- false
- ).then((id) => setBlockId(id));
- }, [env]);
-
- const nodeModel = React.useMemo(
- () =>
- blockId == null
- ? null
- : makeMockNodeModel({
- nodeId: PreviewNodeId,
- blockId,
- innerRect: { width: "900px", height: "480px" },
- }),
- [blockId]
- );
-
- if (blockId == null || nodeModel == null) {
- return null;
- }
-
- return (
-
-
full deprecated waveai block with the FE-only replacement UI
-
-
- );
-}
diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts
index 06157e2566..86707c0254 100644
--- a/frontend/types/custom.d.ts
+++ b/frontend/types/custom.d.ts
@@ -14,9 +14,7 @@ declare global {
workspaceId: jotai.Atom; // derived from window WOS object
workspace: jotai.Atom; // driven from workspaceId via WOS
fullConfigAtom: jotai.PrimitiveAtom; // driven from WOS, settings -- updated via WebSocket
- waveaiModeConfigAtom: jotai.PrimitiveAtom>; // resolved AI mode configs -- updated via WebSocket
settingsAtom: jotai.Atom; // derrived from fullConfig
- hasCustomAIPresetsAtom: jotai.Atom; // derived from fullConfig
hasConfigErrors: jotai.Atom; // derived from fullConfig
staticTabId: jotai.Atom;
isFullScreen: jotai.PrimitiveAtom;
@@ -24,11 +22,9 @@ declare global {
controlShiftDelayAtom: jotai.PrimitiveAtom;
prefersReducedMotionAtom: jotai.Atom;
documentHasFocus: jotai.PrimitiveAtom;
- updaterStatusAtom: jotai.PrimitiveAtom;
modalOpen: jotai.PrimitiveAtom;
allConnStatus: jotai.Atom;
reinitVersion: jotai.PrimitiveAtom;
- waveAIRateLimitInfoAtom: jotai.PrimitiveAtom;
};
type ThrottledValueAtom = jotai.WritableAtom], void>;
@@ -101,10 +97,6 @@ declare global {
openExternal: (url: string) => void; // open-external
onFullScreenChange: (callback: (isFullScreen: boolean) => void) => void; // fullscreen-change
onZoomFactorChange: (callback: (zoomFactor: number) => void) => void; // zoom-factor-change
- onUpdaterStatusChange: (callback: (status: UpdaterStatus) => void) => void; // app-update-status
- getUpdaterStatus: () => UpdaterStatus; // get-app-update-status
- getUpdaterChannel: () => string; // get-updater-channel
- installAppUpdate: () => void; // install-app-update
onMenuItemAbout: (callback: () => void) => void; // menu-item-about
updateWindowControlsOverlay: (rect: Dimensions) => void; // update-window-controls-overlay
onReinjectKey: (callback: (waveEvent: WaveKeyboardEvent) => void) => void; // reinject-key
@@ -126,9 +118,7 @@ declare global {
captureScreenshot(rect: Electron.Rectangle): Promise; // capture-screenshot
setKeyboardChordMode: () => void; // set-keyboard-chord-mode
clearWebviewStorage: (webContentsId: number) => Promise; // clear-webview-storage
- setWaveAIOpen: (isOpen: boolean) => void; // set-waveai-open
closeBuilderWindow: () => void; // close-builder-window
- incrementTermCommands: (opts?: { isRemote?: boolean; isWsl?: boolean; isDurable?: boolean }) => void; // increment-term-commands
nativePaste: () => void; // native-paste
openBuilder: (appId?: string) => void; // open-builder
setBuilderWindowAppId: (appId: string) => void; // set-builder-window-appid
@@ -365,8 +355,6 @@ declare global {
dispose?: () => void;
}
- type UpdaterStatus = "up-to-date" | "checking" | "downloading" | "ready" | "error" | "installing";
-
// jotai doesn't export this type :/
type Loadable = { state: "loading" } | { state: "hasData"; data: T } | { state: "hasError"; error: unknown };
@@ -485,7 +473,6 @@ declare global {
previewurl?: string;
};
- type AIModeConfigWithMode = { mode: string } & AIModeConfigType;
}
export {};
diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts
index c5b870d7ed..1345369426 100644
--- a/frontend/types/gotypes.d.ts
+++ b/frontend/types/gotypes.d.ts
@@ -5,86 +5,6 @@
declare global {
- // wshrpc.AIAttachedFile
- type AIAttachedFile = {
- name: string;
- type: string;
- size: number;
- data64: string;
- };
-
- // wconfig.AIModeConfigType
- type AIModeConfigType = {
- "display:name": string;
- "display:order"?: number;
- "display:icon"?: string;
- "display:description"?: string;
- "ai:provider"?: string;
- "ai:apitype"?: string;
- "ai:model"?: string;
- "ai:thinkinglevel"?: string;
- "ai:verbosity"?: string;
- "ai:endpoint"?: string;
- "ai:proxyurl"?: string;
- "ai:azureapiversion"?: string;
- "ai:apitoken"?: string;
- "ai:apitokensecretname"?: string;
- "ai:azureresourcename"?: string;
- "ai:azuredeployment"?: string;
- "ai:capabilities"?: string[];
- "ai:switchcompat"?: string[];
- "waveai:cloud"?: boolean;
- "waveai:premium"?: boolean;
- };
-
- // wconfig.AIModeConfigUpdate
- type AIModeConfigUpdate = {
- configs: {[key: string]: AIModeConfigType};
- };
-
- // wshrpc.ActivityDisplayType
- type ActivityDisplayType = {
- width: number;
- height: number;
- dpr: number;
- internal?: boolean;
- };
-
- // wshrpc.ActivityUpdate
- type ActivityUpdate = {
- fgminutes?: number;
- activeminutes?: number;
- openminutes?: number;
- waveaifgminutes?: number;
- waveaiactiveminutes?: number;
- numtabs?: number;
- newtab?: number;
- numblocks?: number;
- numwindows?: number;
- numws?: number;
- numwsnamed?: number;
- numsshconn?: number;
- numwslconn?: number;
- nummagnify?: number;
- termcommandsrun?: number;
- numpanics?: number;
- numaireqs?: number;
- startup?: number;
- shutdown?: number;
- settabtheme?: number;
- buildtime?: string;
- displays?: ActivityDisplayType[];
- renderers?: {[key: string]: number};
- blocks?: {[key: string]: number};
- wshcmds?: {[key: string]: number};
- conn?: {[key: string]: number};
- };
-
- // wshrpc.AiMessageData
- type AiMessageData = {
- message?: string;
- };
-
// wshrpc.AppInfo
type AppInfo = {
appid: string;
@@ -413,11 +333,6 @@ declare global {
filename?: string;
};
- // wshrpc.CommandGetWaveAIChatData
- type CommandGetWaveAIChatData = {
- chatid: string;
- };
-
// wshrpc.CommandJobCmdExitedData
type CommandJobCmdExitedData = {
jobid: string;
@@ -727,32 +642,6 @@ declare global {
waitms: number;
};
- // wshrpc.CommandWaveAIAddContextData
- type CommandWaveAIAddContextData = {
- files?: AIAttachedFile[];
- text?: string;
- submit?: boolean;
- newchat?: boolean;
- };
-
- // wshrpc.CommandWaveAIGetToolDiffData
- type CommandWaveAIGetToolDiffData = {
- chatid: string;
- toolcallid: string;
- };
-
- // wshrpc.CommandWaveAIGetToolDiffRtnData
- type CommandWaveAIGetToolDiffRtnData = {
- originalcontents64: string;
- modifiedcontents64: string;
- };
-
- // wshrpc.CommandWaveAIToolApproveData
- type CommandWaveAIToolApproveData = {
- toolcallid: string;
- approval?: string;
- };
-
// wshrpc.CommandWaveFileReadStreamData
type CommandWaveFileReadStreamData = {
zoneid: string;
@@ -824,6 +713,8 @@ declare global {
"conn:wshpath"?: string;
"conn:shellpath"?: string;
"conn:ignoresshconfig"?: boolean;
+ "conn:stallautodisconnect"?: boolean;
+ "conn:stalldisconnectthreshold"?: number;
"display:hidden"?: boolean;
"display:order"?: number;
"term:*"?: boolean;
@@ -854,6 +745,8 @@ declare global {
"ssh:proxyjump"?: string[];
"ssh:userknownhostsfile"?: string[];
"ssh:globalknownhostsfile"?: string[];
+ "ssh:localforward"?: string[];
+ "ssh:remoteforward"?: string[];
};
// wshrpc.ConnRequest
@@ -878,6 +771,10 @@ declare global {
wshversion?: string;
lastactivitybeforestalledtime?: number;
keepalivesenttime?: number;
+ reconnectattempt?: number;
+ reconnectnextattempt?: number;
+ reconnecterror?: string;
+ forwardingrules?: string[];
};
// wshrpc.CpuDataRequest
@@ -1018,7 +915,6 @@ declare global {
termthemes: {[key: string]: TermThemeType};
connections: {[key: string]: ConnKeywords};
bookmarks: {[key: string]: WebBookmark};
- waveai: {[key: string]: AIModeConfigType};
configerrors: ConfigError[];
version: string;
buildtime: string;
@@ -1132,19 +1028,6 @@ declare global {
"cmd:initscript.zsh"?: string;
"cmd:initscript.pwsh"?: string;
"cmd:initscript.fish"?: string;
- "ai:*"?: boolean;
- "ai:preset"?: string;
- "ai:apitype"?: string;
- "ai:baseurl"?: string;
- "ai:apitoken"?: string;
- "ai:name"?: string;
- "ai:model"?: string;
- "ai:orgid"?: string;
- "ai:apiversion"?: string;
- "ai:maxtokens"?: number;
- "ai:timeoutms"?: number;
- "aifilediff:chatid"?: string;
- "aifilediff:toolcallid"?: string;
"editor:*"?: boolean;
"editor:minimapenabled"?: boolean;
"editor:stickyscrollenabled"?: boolean;
@@ -1164,11 +1047,6 @@ declare global {
"bg:activebordercolor"?: string;
"layout:vtabbarwidth"?: number;
"layout:widgetsvisible"?: boolean;
- "waveai:panelopen"?: boolean;
- "waveai:panelwidth"?: number;
- "waveai:model"?: string;
- "waveai:chatid"?: string;
- "waveai:widgetcontext"?: boolean;
"term:*"?: boolean;
"term:fontsize"?: number;
"term:fontfamily"?: string;
@@ -1246,9 +1124,6 @@ declare global {
"builder:layout"?: {[key: string]: number};
"builder:appid"?: string;
"builder:env"?: {[key: string]: string};
- "waveai:chatid"?: string;
- "waveai:mode"?: string;
- "waveai:maxoutputtokens"?: number;
};
// wshrpc.PathCommandData
@@ -1303,16 +1178,6 @@ declare global {
cpusum?: number;
};
- // uctypes.RateLimitInfo
- type RateLimitInfo = {
- req: number;
- reqlimit: number;
- preq: number;
- preqlimit: number;
- resetepoch: number;
- unknown?: boolean;
- };
-
// wshrpc.RemoteInfo
type RemoteInfo = {
clientarch: string;
@@ -1382,28 +1247,11 @@ declare global {
"app:showoverlayblocknums"?: boolean;
"app:ctrlvpaste"?: boolean;
"app:confirmquit"?: boolean;
- "app:hideaibutton"?: boolean;
"app:disablectrlshiftarrows"?: boolean;
"app:disablectrlshiftdisplay"?: boolean;
"app:focusfollowscursor"?: string;
"app:tabbar"?: string;
"feature:waveappbuilder"?: boolean;
- "ai:*"?: boolean;
- "ai:preset"?: string;
- "ai:apitype"?: string;
- "ai:baseurl"?: string;
- "ai:apitoken"?: string;
- "ai:name"?: string;
- "ai:model"?: string;
- "ai:orgid"?: string;
- "ai:apiversion"?: string;
- "ai:maxtokens"?: number;
- "ai:timeoutms"?: number;
- "ai:proxyurl"?: string;
- "ai:fontsize"?: number;
- "ai:fixedfontsize"?: number;
- "waveai:showcloudmodes"?: boolean;
- "waveai:defaultmode"?: string;
"term:*"?: boolean;
"term:fontsize"?: number;
"term:fontfamily"?: string;
@@ -1469,8 +1317,6 @@ declare global {
"window:savelastwindow"?: boolean;
"window:dimensions"?: string;
"window:zoom"?: number;
- "telemetry:*"?: boolean;
- "telemetry:enabled"?: boolean;
"conn:*"?: boolean;
"conn:askbeforewshinstall"?: boolean;
"conn:wshenabled"?: boolean;
@@ -1540,142 +1386,6 @@ declare global {
"url:url"?: string;
};
- // telemetrydata.TEvent
- type TEvent = {
- uuid?: string;
- ts?: number;
- tslocal?: string;
- event: string;
- props: TEventProps;
- };
-
- // telemetrydata.TEventProps
- type TEventProps = {
- "client:arch"?: string;
- "client:version"?: string;
- "client:initial_version"?: string;
- "client:buildtime"?: string;
- "client:osrelease"?: string;
- "client:isdev"?: boolean;
- "client:packagetype"?: string;
- "client:macos"?: string;
- "cohort:month"?: string;
- "cohort:isoweek"?: string;
- "autoupdate:channel"?: string;
- "autoupdate:enabled"?: boolean;
- "localshell:type"?: string;
- "localshell:version"?: string;
- "loc:countrycode"?: string;
- "loc:regioncode"?: string;
- "settings:customwidgets"?: number;
- "settings:customaipresets"?: number;
- "settings:customsettings"?: number;
- "settings:customaimodes"?: number;
- "settings:secretscount"?: number;
- "settings:transparent"?: boolean;
- "activity:activeminutes"?: number;
- "activity:fgminutes"?: number;
- "activity:openminutes"?: number;
- "activity:waveaiactiveminutes"?: number;
- "activity:waveaifgminutes"?: number;
- "activity:termcommandsrun"?: number;
- "activity:termcommands:remote"?: number;
- "activity:termcommands:durable"?: number;
- "activity:termcommands:wsl"?: number;
- "app:firstday"?: boolean;
- "app:firstlaunch"?: boolean;
- "action:initiator"?: "keyboard" | "mouse";
- "action:type"?: string;
- "debug:panictype"?: string;
- "block:view"?: string;
- "block:controller"?: string;
- "ai:backendtype"?: string;
- "ai:local"?: boolean;
- "wsh:cmd"?: string;
- "wsh:errorcount"?: number;
- "wsh:count"?: number;
- "conn:conntype"?: string;
- "conn:wsherrorcode"?: string;
- "conn:errorcode"?: string;
- "conn:suberrorcode"?: string;
- "conn:contexterror"?: boolean;
- "onboarding:feature"?: "waveai" | "durable" | "magnify" | "wsh";
- "onboarding:version"?: string;
- "onboarding:githubstar"?: "already" | "star" | "later";
- "onboarding:page"?: string;
- "display:height"?: number;
- "display:width"?: number;
- "display:dpr"?: number;
- "display:count"?: number;
- "display:all"?: any;
- "count:blocks"?: number;
- "count:tabs"?: number;
- "count:windows"?: number;
- "count:workspaces"?: number;
- "count:sshconn"?: number;
- "count:wslconn"?: number;
- "count:jobs"?: number;
- "count:jobsconnected"?: number;
- "count:views"?: {[key: string]: number};
- "waveai:apitype"?: string;
- "waveai:model"?: string;
- "waveai:chatid"?: string;
- "waveai:stepnum"?: number;
- "waveai:inputtokens"?: number;
- "waveai:outputtokens"?: number;
- "waveai:nativewebsearchcount"?: number;
- "waveai:requestcount"?: number;
- "waveai:toolusecount"?: number;
- "waveai:tooluseerrorcount"?: number;
- "waveai:tooldetail"?: {[key: string]: number};
- "waveai:premiumreq"?: number;
- "waveai:proxyreq"?: number;
- "waveai:haderror"?: boolean;
- "waveai:imagecount"?: number;
- "waveai:pdfcount"?: number;
- "waveai:textdoccount"?: number;
- "waveai:textlen"?: number;
- "waveai:firstbytems"?: number;
- "waveai:requestdurms"?: number;
- "waveai:widgetaccess"?: boolean;
- "waveai:thinkinglevel"?: string;
- "waveai:mode"?: string;
- "waveai:provider"?: string;
- "waveai:islocal"?: boolean;
- "waveai:feedback"?: "good" | "bad";
- "waveai:action"?: string;
- "job:donereason"?: string;
- "job:kind"?: string;
- $set?: TEventUserProps;
- $set_once?: TEventUserProps;
- };
-
- // telemetrydata.TEventUserProps
- type TEventUserProps = {
- "client:arch"?: string;
- "client:version"?: string;
- "client:initial_version"?: string;
- "client:buildtime"?: string;
- "client:osrelease"?: string;
- "client:isdev"?: boolean;
- "client:packagetype"?: string;
- "client:macos"?: string;
- "cohort:month"?: string;
- "cohort:isoweek"?: string;
- "autoupdate:channel"?: string;
- "autoupdate:enabled"?: boolean;
- "localshell:type"?: string;
- "localshell:version"?: string;
- "loc:countrycode"?: string;
- "loc:regioncode"?: string;
- "settings:customwidgets"?: number;
- "settings:customaipresets"?: number;
- "settings:customsettings"?: number;
- "settings:customaimodes"?: number;
- "settings:secretscount"?: number;
- "settings:transparent"?: boolean;
- };
-
// waveobj.Tab
type Tab = WaveObj & {
name: string;
@@ -1723,49 +1433,12 @@ declare global {
values: {[key: string]: number};
};
- // uctypes.UIChat
- type UIChat = {
- chatid: string;
- apitype: string;
- model: string;
- apiversion: string;
- messages: UIMessage[];
- };
-
// waveobj.UIContext
type UIContext = {
windowid: string;
activetabid: string;
};
- // uctypes.UIMessage
- type UIMessage = {
- id: string;
- role: string;
- metadata?: any;
- parts?: UIMessagePart[];
- };
-
- // uctypes.UIMessagePart
- type UIMessagePart = {
- type: string;
- text?: string;
- state?: string;
- toolCallId?: string;
- input?: any;
- output?: any;
- errorText?: string;
- providerExecuted?: boolean;
- sourceId?: string;
- url?: string;
- title?: string;
- filename?: string;
- mediaType?: string;
- id?: string;
- data?: any;
- providerMetadata?: {[key: string]: any};
- };
-
// userinput.UserInputRequest
type UserInputRequest = {
requestid: string;
diff --git a/frontend/types/waveevent.d.ts b/frontend/types/waveevent.d.ts
index c3e5bd7822..c4e23f922a 100644
--- a/frontend/types/waveevent.d.ts
+++ b/frontend/types/waveevent.d.ts
@@ -20,10 +20,8 @@ declare global {
| "route:down"
| "route:up"
| "workspace:update"
- | "waveai:ratelimit"
| "waveapp:appgoupdated"
| "tsunami:updatemeta"
- | "waveai:modeconfig"
| "block:jobstatus"
| "badge"
;
@@ -48,10 +46,8 @@ declare global {
{ event: "route:down"; data?: null; } |
{ event: "route:up"; data?: null; } |
{ event: "workspace:update"; data?: null; } |
- { event: "waveai:ratelimit"; data?: RateLimitInfo; } |
{ event: "waveapp:appgoupdated"; data?: null; } |
{ event: "tsunami:updatemeta"; data?: AppMeta; } |
- { event: "waveai:modeconfig"; data?: AIModeConfigUpdate; } |
{ event: "block:jobstatus"; data?: BlockJobStatusData; } |
{ event: "badge"; data?: BadgeEvent; }
);
diff --git a/frontend/util/util.ts b/frontend/util/util.ts
index 8c2d330580..688843096d 100644
--- a/frontend/util/util.ts
+++ b/frontend/util/util.ts
@@ -506,6 +506,14 @@ function sortByDisplayOrder {
globalRefocus();
}, 50);
@@ -194,8 +193,6 @@ async function initWave(initOpts: WaveInitOpts) {
const fullConfig = await RpcApi.GetFullConfigCommand(TabRpcClient);
console.log("fullconfig", fullConfig);
globalStore.set(atoms.fullConfigAtom, fullConfig);
- const waveaiModeConfig = await RpcApi.GetWaveAIModeConfigCommand(TabRpcClient);
- globalStore.set(atoms.waveaiModeConfigAtom, waveaiModeConfig.configs);
console.log("Wave First Render");
let firstRenderResolveFn: () => void = null;
const firstRenderPromise = new Promise((resolve) => {
@@ -265,8 +262,6 @@ async function initBuilder(initOpts: BuilderInitOpts) {
const fullConfig = await RpcApi.GetFullConfigCommand(TabRpcClient);
console.log("fullconfig", fullConfig);
globalStore.set(atoms.fullConfigAtom, fullConfig);
- const waveaiModeConfig = await RpcApi.GetWaveAIModeConfigCommand(TabRpcClient);
- globalStore.set(atoms.waveaiModeConfigAtom, waveaiModeConfig.configs);
console.log("Tsunami Builder First Render");
let firstRenderResolveFn: () => void = null;
diff --git a/go.mod b/go.mod
index 460763ba8f..347b5a2271 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,6 @@ require (
github.com/fsnotify/fsnotify v1.9.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/golang-migrate/migrate/v4 v4.19.1
- github.com/google/generative-ai-go v0.20.1
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.3
@@ -20,7 +19,6 @@ require (
github.com/joho/godotenv v1.5.1
github.com/junegunn/fzf v0.65.2
github.com/kevinburke/ssh_config v1.2.0
- github.com/launchdarkly/eventsource v1.11.0
github.com/mattn/go-sqlite3 v1.14.40
github.com/mitchellh/mapstructure v1.5.0
github.com/sawka/txwrap v0.2.0
@@ -31,37 +29,25 @@ require (
github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b
github.com/wavetermdev/htmltoken v0.2.0
github.com/wavetermdev/waveterm/tsunami v0.12.3
- golang.org/x/crypto v0.50.0
+ golang.org/x/crypto v0.52.0
golang.org/x/mod v0.35.0
golang.org/x/sync v0.20.0
- golang.org/x/sys v0.43.0
- golang.org/x/term v0.42.0
- google.golang.org/api v0.277.0
+ golang.org/x/sys v0.45.0
+ golang.org/x/term v0.43.0
)
require (
- cloud.google.com/go v0.121.6 // indirect
- cloud.google.com/go/ai v0.8.0 // indirect
- cloud.google.com/go/auth v0.20.0 // indirect
- cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
- cloud.google.com/go/compute/metadata v0.9.0 // indirect
- cloud.google.com/go/longrunning v0.6.7 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
- github.com/cespare/xxhash/v2 v2.3.0 // indirect
- github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/go-logr/logr v1.4.3 // indirect
- github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
- github.com/google/s2a-go v0.1.9 // indirect
- github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
- github.com/googleapis/gax-go/v2 v2.22.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/kr/pretty v0.3.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
@@ -69,20 +55,8 @@ require (
github.com/ubuntu/decorate v0.0.0-20230125165522-2d5b0a9bb117 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
- go.opentelemetry.io/auto/sdk v1.2.1 // indirect
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
- go.opentelemetry.io/otel v1.43.0 // indirect
- go.opentelemetry.io/otel/metric v1.43.0 // indirect
- go.opentelemetry.io/otel/trace v1.43.0 // indirect
- golang.org/x/net v0.53.0 // indirect
- golang.org/x/oauth2 v0.36.0 // indirect
- golang.org/x/text v0.36.0 // indirect
- golang.org/x/time v0.15.0 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect
- google.golang.org/grpc v1.80.0 // indirect
- google.golang.org/protobuf v1.36.11 // indirect
+ golang.org/x/net v0.54.0 // indirect
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
@@ -91,3 +65,5 @@ replace github.com/kevinburke/ssh_config => github.com/wavetermdev/ssh_config v0
replace github.com/creack/pty => github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b
replace github.com/wavetermdev/waveterm/tsunami => ./tsunami
+
+replace golang.org/x/crypto v0.52.0 => ./local_crypto_patch/contents
diff --git a/go.sum b/go.sum
index 139b34b195..4d99378b2c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,15 +1,3 @@
-cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
-cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
-cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
-cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
-cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
-cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
-cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
-cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
-cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
-cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
-cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
-cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/0xrawsec/golang-utils v1.3.2 h1:ww4jrtHRSnX9xrGzJYbalx5nXoZewy4zPxiY+ubJgtg=
@@ -22,10 +10,6 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
-github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
-github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
-github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -35,20 +19,8 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
-github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
-github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
-github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
-github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
-github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
-github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
-github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
-github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
@@ -57,21 +29,11 @@ github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63Y
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ=
-github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
-github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=
-github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
-github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
-github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -87,14 +49,13 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/junegunn/fzf v0.65.2 h1:Uz6Qey1K4JoGNMskYlwRDnGuCEu/sAh+NxQ4YdX3yn0=
github.com/junegunn/fzf v0.65.2/go.mod h1:0PctWYfS0aCfyLFEIUjtE+PIXD2UFKaHgbIHiECG7Bo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/launchdarkly/eventsource v1.11.0 h1:aAdvh2XmtXA17QsRFL0XKHURMqhxg7J+CceQmhSzBas=
-github.com/launchdarkly/eventsource v1.11.0/go.mod h1:dU+rZxkPOlGPsyJPpiDqiepAcFwIITDUClY9+A6RrMw=
-github.com/launchdarkly/go-test-helpers/v3 v3.1.0 h1:E3bxJMzMoA+cJSF3xxtk2/chr1zshl1ZWa0/oR+8bvg=
-github.com/launchdarkly/go-test-helpers/v3 v3.1.0/go.mod h1:Ake5+hZFS/DmIGKx/cizhn5W9pGA7pplcR7xCxWiLIo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
@@ -110,8 +71,7 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b h1:cLGKfKb1uk0hxI0Q8L83UAJPpeJ+gSpn3cCU/tjd3eg=
github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b/go.mod h1:KO+FcPtyLAiRC0hJwreJVvfwc7vnNz77UxBTIGHdPVk=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -119,6 +79,7 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -157,31 +118,11 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
-go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
-go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
-go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
-go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
-go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
-go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
-go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
-go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
-go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
-go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
-go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
-go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
-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/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
-golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
-golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
-golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
-golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
+golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
+golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -190,29 +131,11 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220721230656-c6bc011c0c49/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-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.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.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
-golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
-golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
-golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
+golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
+golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
-gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
-google.golang.org/api v0.277.0 h1:HJfyJUiNeBBUMai7ez8u14wkp/gH/I4wpGbbO9o+cSk=
-google.golang.org/api v0.277.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ=
-google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
-google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
-google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
-google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
-google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
-google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
-google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
-google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/local_crypto_patch/README.md b/local_crypto_patch/README.md
new file mode 100644
index 0000000000..c553299958
--- /dev/null
+++ b/local_crypto_patch/README.md
@@ -0,0 +1,44 @@
+# Local Patch for golang.org/x/crypto
+
+## Why this exists
+
+This is a local copy of `golang.org/x/crypto v0.52.0` with patches for
+[golang/go#79658](https://github.com/golang/go/issues/79658): drain loops in
+`ssh/mux.go` and `ssh/channel.go` spin forever on closed channels, causing
+100%+ CPU after SSH disconnect.
+
+## Patches Applied
+
+1. **ssh/mux.go** — `SendRequest` drain loop (commit 4c4d20b upstream):
+ ```go
+ // Before (buggy):
+ case <-m.globalResponses:
+
+ // After (fixed):
+ case _, ok := <-m.globalResponses:
+ if !ok {
+ break drain
+ }
+ ```
+
+2. **ssh/channel.go** — `SendRequest` drain loop (commit e3e62d9 upstream):
+ ```go
+ // Before (buggy):
+ case <-ch.msg:
+
+ // After (fixed):
+ case _, ok := <-ch.msg:
+ if !ok {
+ break drain
+ }
+ ```
+
+## Rollback
+
+**Remove the `replace` directive in `go.mod` and delete this directory when
+upgrading to `golang.org/x/crypto >= v0.53.0`**, which will include both fixes.
+
+The go.mod replace directive:
+```
+replace golang.org/x/crypto v0.52.0 => ./local_crypto_patch/contents
+```
\ No newline at end of file
diff --git a/local_crypto_patch/contents/.gitattributes b/local_crypto_patch/contents/.gitattributes
new file mode 100644
index 0000000000..d2f212e5da
--- /dev/null
+++ b/local_crypto_patch/contents/.gitattributes
@@ -0,0 +1,10 @@
+# Treat all files in this repo as binary, with no git magic updating
+# line endings. Windows users contributing to Go will need to use a
+# modern version of git and editors capable of LF line endings.
+#
+# We'll prevent accidental CRLF line endings from entering the repo
+# via the git-review gofmt checks.
+#
+# See golang.org/issue/9281
+
+* -text
diff --git a/local_crypto_patch/contents/.gitignore b/local_crypto_patch/contents/.gitignore
new file mode 100644
index 0000000000..5a9d62efd4
--- /dev/null
+++ b/local_crypto_patch/contents/.gitignore
@@ -0,0 +1,2 @@
+# Add no patterns to .gitignore except for files generated by the build.
+last-change
diff --git a/local_crypto_patch/contents/CONTRIBUTING.md b/local_crypto_patch/contents/CONTRIBUTING.md
new file mode 100644
index 0000000000..d0485e887a
--- /dev/null
+++ b/local_crypto_patch/contents/CONTRIBUTING.md
@@ -0,0 +1,26 @@
+# Contributing to Go
+
+Go is an open source project.
+
+It is the work of hundreds of contributors. We appreciate your help!
+
+## Filing issues
+
+When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
+
+1. What version of Go are you using (`go version`)?
+2. What operating system and processor architecture are you using?
+3. What did you do?
+4. What did you expect to see?
+5. What did you see instead?
+
+General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
+The gophers there will answer or ask you to file an issue if you've tripped over a bug.
+
+## Contributing code
+
+Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
+before sending patches.
+
+Unless otherwise noted, the Go source files are distributed under
+the BSD-style license found in the LICENSE file.
diff --git a/local_crypto_patch/contents/LICENSE b/local_crypto_patch/contents/LICENSE
new file mode 100644
index 0000000000..2a7cf70da6
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/PATENTS b/local_crypto_patch/contents/PATENTS
new file mode 100644
index 0000000000..733099041f
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/README.md b/local_crypto_patch/contents/README.md
new file mode 100644
index 0000000000..f69c623ff8
--- /dev/null
+++ b/local_crypto_patch/contents/README.md
@@ -0,0 +1,20 @@
+# Go Cryptography
+
+[](https://pkg.go.dev/golang.org/x/crypto)
+
+This repository holds supplementary Go cryptography packages.
+
+## Report Issues / Send Patches
+
+This repository uses Gerrit for code changes. To learn how to submit changes to
+this repository, see https://go.dev/doc/contribute.
+
+The git repository is https://go.googlesource.com/crypto.
+
+The main issue tracker for the crypto repository is located at
+https://go.dev/issues. Prefix your issue with "x/crypto:" in the
+subject line, so it is easy to find.
+
+Note that contributions to the cryptography package receive additional scrutiny
+due to their sensitive nature. Patches may take longer than normal to receive
+feedback.
diff --git a/local_crypto_patch/contents/acme/acme.go b/local_crypto_patch/contents/acme/acme.go
new file mode 100644
index 0000000000..b53ea28891
--- /dev/null
+++ b/local_crypto_patch/contents/acme/acme.go
@@ -0,0 +1,808 @@
+// Copyright 2015 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 acme provides an implementation of the
+// Automatic Certificate Management Environment (ACME) spec,
+// most famously used by Let's Encrypt.
+//
+// The initial implementation of this package was based on an early version
+// of the spec. The current implementation supports only the modern
+// RFC 8555 but some of the old API surface remains for compatibility.
+// While code using the old API will still compile, it will return an error.
+// Note the deprecation comments to update your code.
+//
+// See https://tools.ietf.org/html/rfc8555 for the spec.
+//
+// Most common scenarios will want to use autocert subdirectory instead,
+// which provides automatic access to certificates from Let's Encrypt
+// and any other ACME-based CA.
+package acme
+
+import (
+ "context"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/sha256"
+ "crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math/big"
+ "net"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+)
+
+const (
+ // LetsEncryptURL is the Directory endpoint of Let's Encrypt CA.
+ LetsEncryptURL = "https://acme-v02.api.letsencrypt.org/directory"
+
+ // ALPNProto is the ALPN protocol name used by a CA server when validating
+ // tls-alpn-01 challenges.
+ //
+ // Package users must ensure their servers can negotiate the ACME ALPN in
+ // order for tls-alpn-01 challenge verifications to succeed.
+ // See the crypto/tls package's Config.NextProtos field.
+ ALPNProto = "acme-tls/1"
+)
+
+// idPeACMEIdentifier is the OID for the ACME extension for the TLS-ALPN challenge.
+// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
+var idPeACMEIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
+
+const (
+ maxChainLen = 5 // max depth and breadth of a certificate chain
+ maxCertSize = 1 << 20 // max size of a certificate, in DER bytes
+ // Used for decoding certs from application/pem-certificate-chain response,
+ // the default when in RFC mode.
+ maxCertChainSize = maxCertSize * maxChainLen
+
+ // Max number of collected nonces kept in memory.
+ // Expect usual peak of 1 or 2.
+ maxNonces = 100
+)
+
+// Client is an ACME client.
+//
+// The only required field is Key. An example of creating a client with a new key
+// is as follows:
+//
+// key, err := rsa.GenerateKey(rand.Reader, 2048)
+// if err != nil {
+// log.Fatal(err)
+// }
+// client := &Client{Key: key}
+type Client struct {
+ // Key is the account key used to register with a CA and sign requests.
+ // Key.Public() must return a *rsa.PublicKey or *ecdsa.PublicKey.
+ //
+ // The following algorithms are supported:
+ // RS256, ES256, ES384 and ES512.
+ // See RFC 7518 for more details about the algorithms.
+ Key crypto.Signer
+
+ // HTTPClient optionally specifies an HTTP client to use
+ // instead of http.DefaultClient.
+ HTTPClient *http.Client
+
+ // DirectoryURL points to the CA directory endpoint.
+ // If empty, LetsEncryptURL is used.
+ // Mutating this value after a successful call of Client's Discover method
+ // will have no effect.
+ DirectoryURL string
+
+ // RetryBackoff computes the duration after which the nth retry of a failed request
+ // should occur. The value of n for the first call on failure is 1.
+ // The values of r and resp are the request and response of the last failed attempt.
+ // If the returned value is negative or zero, no more retries are done and an error
+ // is returned to the caller of the original method.
+ //
+ // Requests which result in a 4xx client error are not retried,
+ // except for 400 Bad Request due to "bad nonce" errors and 429 Too Many Requests.
+ //
+ // If RetryBackoff is nil, a truncated exponential backoff algorithm
+ // with the ceiling of 10 seconds is used, where each subsequent retry n
+ // is done after either ("Retry-After" + jitter) or (2^n seconds + jitter),
+ // preferring the former if "Retry-After" header is found in the resp.
+ // The jitter is a random value up to 1 second.
+ RetryBackoff func(n int, r *http.Request, resp *http.Response) time.Duration
+
+ // UserAgent is prepended to the User-Agent header sent to the ACME server,
+ // which by default is this package's name and version.
+ //
+ // Reusable libraries and tools in particular should set this value to be
+ // identifiable by the server, in case they are causing issues.
+ UserAgent string
+
+ cacheMu sync.Mutex
+ dir *Directory // cached result of Client's Discover method
+ // KID is the key identifier provided by the CA. If not provided it will be
+ // retrieved from the CA by making a call to the registration endpoint.
+ KID KeyID
+
+ noncesMu sync.Mutex
+ nonces map[string]struct{} // nonces collected from previous responses
+}
+
+// accountKID returns a key ID associated with c.Key, the account identity
+// provided by the CA during RFC based registration.
+// It assumes c.Discover has already been called.
+//
+// accountKID requires at most one network roundtrip.
+// It caches only successful result.
+//
+// When in pre-RFC mode or when c.getRegRFC responds with an error, accountKID
+// returns noKeyID.
+func (c *Client) accountKID(ctx context.Context) KeyID {
+ c.cacheMu.Lock()
+ defer c.cacheMu.Unlock()
+ if c.KID != noKeyID {
+ return c.KID
+ }
+ a, err := c.getRegRFC(ctx)
+ if err != nil {
+ return noKeyID
+ }
+ c.KID = KeyID(a.URI)
+ return c.KID
+}
+
+var errPreRFC = errors.New("acme: server does not support the RFC 8555 version of ACME")
+
+// Discover performs ACME server discovery using c.DirectoryURL.
+//
+// It caches successful result. So, subsequent calls will not result in
+// a network round-trip. This also means mutating c.DirectoryURL after successful call
+// of this method will have no effect.
+func (c *Client) Discover(ctx context.Context) (Directory, error) {
+ c.cacheMu.Lock()
+ defer c.cacheMu.Unlock()
+ if c.dir != nil {
+ return *c.dir, nil
+ }
+
+ res, err := c.get(ctx, c.directoryURL(), wantStatus(http.StatusOK))
+ if err != nil {
+ return Directory{}, err
+ }
+ defer res.Body.Close()
+ c.addNonce(res.Header)
+
+ var v struct {
+ Reg string `json:"newAccount"`
+ Authz string `json:"newAuthz"`
+ Order string `json:"newOrder"`
+ Revoke string `json:"revokeCert"`
+ Nonce string `json:"newNonce"`
+ KeyChange string `json:"keyChange"`
+ Meta struct {
+ Terms string `json:"termsOfService"`
+ Website string `json:"website"`
+ CAA []string `json:"caaIdentities"`
+ ExternalAcct bool `json:"externalAccountRequired"`
+ }
+ }
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
+ return Directory{}, err
+ }
+ if v.Order == "" {
+ return Directory{}, errPreRFC
+ }
+ c.dir = &Directory{
+ RegURL: v.Reg,
+ AuthzURL: v.Authz,
+ OrderURL: v.Order,
+ RevokeURL: v.Revoke,
+ NonceURL: v.Nonce,
+ KeyChangeURL: v.KeyChange,
+ Terms: v.Meta.Terms,
+ Website: v.Meta.Website,
+ CAA: v.Meta.CAA,
+ ExternalAccountRequired: v.Meta.ExternalAcct,
+ }
+ return *c.dir, nil
+}
+
+func (c *Client) directoryURL() string {
+ if c.DirectoryURL != "" {
+ return c.DirectoryURL
+ }
+ return LetsEncryptURL
+}
+
+// CreateCert was part of the old version of ACME. It is incompatible with RFC 8555.
+//
+// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert.
+func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) {
+ return nil, "", errPreRFC
+}
+
+// FetchCert retrieves already issued certificate from the given url, in DER format.
+// It retries the request until the certificate is successfully retrieved,
+// context is cancelled by the caller or an error response is received.
+//
+// If the bundle argument is true, the returned value also contains the CA (issuer)
+// certificate chain.
+//
+// FetchCert returns an error if the CA's response or chain was unreasonably large.
+// Callers are encouraged to parse the returned value to ensure the certificate is valid
+// and has expected features.
+func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+ return c.fetchCertRFC(ctx, url, bundle)
+}
+
+// RevokeCert revokes a previously issued certificate cert, provided in DER format.
+//
+// The key argument, used to sign the request, must be authorized
+// to revoke the certificate. It's up to the CA to decide which keys are authorized.
+// For instance, the key pair of the certificate may be authorized.
+// If the key is nil, c.Key is used instead.
+func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
+ if _, err := c.Discover(ctx); err != nil {
+ return err
+ }
+ return c.revokeCertRFC(ctx, key, cert, reason)
+}
+
+// AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service
+// during account registration. See Register method of Client for more details.
+func AcceptTOS(tosURL string) bool { return true }
+
+// Register creates a new account with the CA using c.Key.
+// It returns the registered account. The account acct is not modified.
+//
+// The registration may require the caller to agree to the CA's Terms of Service (TOS).
+// If so, and the account has not indicated the acceptance of the terms (see Account for details),
+// Register calls prompt with a TOS URL provided by the CA. Prompt should report
+// whether the caller agrees to the terms. To always accept the terms, the caller can use AcceptTOS.
+//
+// When interfacing with an RFC-compliant CA, non-RFC 8555 fields of acct are ignored
+// and prompt is called if Directory's Terms field is non-zero.
+// Also see Error's Instance field for when a CA requires already registered accounts to agree
+// to an updated Terms of Service.
+func (c *Client) Register(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) {
+ if c.Key == nil {
+ return nil, errors.New("acme: client.Key must be set to Register")
+ }
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+ return c.registerRFC(ctx, acct, prompt)
+}
+
+// GetReg retrieves an existing account associated with c.Key.
+//
+// The url argument is a legacy artifact of the pre-RFC 8555 API
+// and is ignored.
+func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+ return c.getRegRFC(ctx)
+}
+
+// UpdateReg updates an existing registration.
+// It returns an updated account copy. The provided account is not modified.
+//
+// The account's URI is ignored and the account URL associated with
+// c.Key is used instead.
+func (c *Client) UpdateReg(ctx context.Context, acct *Account) (*Account, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+ return c.updateRegRFC(ctx, acct)
+}
+
+// AccountKeyRollover attempts to transition a client's account key to a new key.
+// On success client's Key is updated which is not concurrency safe.
+// On failure an error will be returned.
+// The new key is already registered with the ACME provider if the following is true:
+// - error is of type acme.Error
+// - StatusCode should be 409 (Conflict)
+// - Location header will have the KID of the associated account
+//
+// More about account key rollover can be found at
+// https://tools.ietf.org/html/rfc8555#section-7.3.5.
+func (c *Client) AccountKeyRollover(ctx context.Context, newKey crypto.Signer) error {
+ return c.accountKeyRollover(ctx, newKey)
+}
+
+// Authorize performs the initial step in the pre-authorization flow,
+// as opposed to order-based flow.
+// The caller will then need to choose from and perform a set of returned
+// challenges using c.Accept in order to successfully complete authorization.
+//
+// Once complete, the caller can use AuthorizeOrder which the CA
+// should provision with the already satisfied authorization.
+// For pre-RFC CAs, the caller can proceed directly to requesting a certificate
+// using CreateCert method.
+//
+// If an authorization has been previously granted, the CA may return
+// a valid authorization which has its Status field set to StatusValid.
+//
+// More about pre-authorization can be found at
+// https://tools.ietf.org/html/rfc8555#section-7.4.1.
+func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, error) {
+ return c.authorize(ctx, "dns", domain)
+}
+
+// AuthorizeIP is the same as Authorize but requests IP address authorization.
+// Clients which successfully obtain such authorization may request to issue
+// a certificate for IP addresses.
+//
+// See the ACME spec extension for more details about IP address identifiers:
+// https://tools.ietf.org/html/draft-ietf-acme-ip.
+func (c *Client) AuthorizeIP(ctx context.Context, ipaddr string) (*Authorization, error) {
+ return c.authorize(ctx, "ip", ipaddr)
+}
+
+func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+ if c.dir.AuthzURL == "" {
+ // Pre-Authorization is unsupported
+ return nil, errPreAuthorizationNotSupported
+ }
+
+ type authzID struct {
+ Type string `json:"type"`
+ Value string `json:"value"`
+ }
+ req := struct {
+ Resource string `json:"resource"`
+ Identifier authzID `json:"identifier"`
+ }{
+ Resource: "new-authz",
+ Identifier: authzID{Type: typ, Value: val},
+ }
+ res, err := c.post(ctx, nil, c.dir.AuthzURL, req, wantStatus(http.StatusCreated))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+
+ var v wireAuthz
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
+ return nil, fmt.Errorf("acme: invalid response: %v", err)
+ }
+ if v.Status != StatusPending && v.Status != StatusValid {
+ return nil, fmt.Errorf("acme: unexpected status: %s", v.Status)
+ }
+ return v.authorization(res.Header.Get("Location")), nil
+}
+
+// GetAuthorization retrieves an authorization identified by the given URL.
+//
+// If a caller needs to poll an authorization until its status is final,
+// see the WaitAuthorization method.
+func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ var v wireAuthz
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
+ return nil, fmt.Errorf("acme: invalid response: %v", err)
+ }
+ return v.authorization(url), nil
+}
+
+// RevokeAuthorization relinquishes an existing authorization identified
+// by the given URL.
+// The url argument is an Authorization.URI value.
+//
+// If successful, the caller will be required to obtain a new authorization
+// using the Authorize or AuthorizeOrder methods before being able to request
+// a new certificate for the domain associated with the authorization.
+//
+// It does not revoke existing certificates.
+func (c *Client) RevokeAuthorization(ctx context.Context, url string) error {
+ if _, err := c.Discover(ctx); err != nil {
+ return err
+ }
+
+ req := struct {
+ Resource string `json:"resource"`
+ Status string `json:"status"`
+ Delete bool `json:"delete"`
+ }{
+ Resource: "authz",
+ Status: "deactivated",
+ Delete: true,
+ }
+ res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
+ if err != nil {
+ return err
+ }
+ defer res.Body.Close()
+ return nil
+}
+
+// WaitAuthorization polls an authorization at the given URL
+// until it is in one of the final states, StatusValid or StatusInvalid,
+// the ACME CA responded with a 4xx error code, or the context is done.
+//
+// It returns a non-nil Authorization only if its Status is StatusValid.
+// In all other cases WaitAuthorization returns an error.
+// If the Status is StatusInvalid, the returned error is of type *AuthorizationError.
+func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+ for {
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
+ if err != nil {
+ return nil, err
+ }
+
+ var raw wireAuthz
+ err = json.NewDecoder(res.Body).Decode(&raw)
+ res.Body.Close()
+ switch {
+ case err != nil:
+ // Skip and retry.
+ case raw.Status == StatusValid:
+ return raw.authorization(url), nil
+ case raw.Status == StatusInvalid:
+ return nil, raw.error(url)
+ }
+
+ // Exponential backoff is implemented in c.get above.
+ // This is just to prevent continuously hitting the CA
+ // while waiting for a final authorization status.
+ d := retryAfter(res.Header.Get("Retry-After"))
+ if d == 0 {
+ // Given that the fastest challenges TLS-ALPN and HTTP-01
+ // require a CA to make at least 1 network round trip
+ // and most likely persist a challenge state,
+ // this default delay seems reasonable.
+ d = time.Second
+ }
+ t := time.NewTimer(d)
+ select {
+ case <-ctx.Done():
+ t.Stop()
+ return nil, ctx.Err()
+ case <-t.C:
+ // Retry.
+ }
+ }
+}
+
+// GetChallenge retrieves the current status of an challenge.
+//
+// A client typically polls a challenge status using this method.
+func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted))
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Body.Close()
+ v := wireChallenge{URI: url}
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
+ return nil, fmt.Errorf("acme: invalid response: %v", err)
+ }
+ return v.challenge(), nil
+}
+
+// Accept informs the server that the client accepts one of its challenges
+// previously obtained with c.Authorize.
+//
+// The server will then perform the validation asynchronously.
+func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+
+ payload := json.RawMessage("{}")
+ if len(chal.Payload) != 0 {
+ payload = chal.Payload
+ }
+ res, err := c.post(ctx, nil, chal.URI, payload, wantStatus(
+ http.StatusOK, // according to the spec
+ http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md)
+ ))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+
+ var v wireChallenge
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
+ return nil, fmt.Errorf("acme: invalid response: %v", err)
+ }
+ return v.challenge(), nil
+}
+
+// DNS01ChallengeRecord returns a DNS record value for a dns-01 challenge response.
+// A TXT record containing the returned value must be provisioned under
+// "_acme-challenge" name of the domain being validated.
+//
+// The token argument is a Challenge.Token value.
+func (c *Client) DNS01ChallengeRecord(token string) (string, error) {
+ ka, err := keyAuth(c.Key.Public(), token)
+ if err != nil {
+ return "", err
+ }
+ b := sha256.Sum256([]byte(ka))
+ return base64.RawURLEncoding.EncodeToString(b[:]), nil
+}
+
+// HTTP01ChallengeResponse returns the response for an http-01 challenge.
+// Servers should respond with the value to HTTP requests at the URL path
+// provided by HTTP01ChallengePath to validate the challenge and prove control
+// over a domain name.
+//
+// The token argument is a Challenge.Token value.
+func (c *Client) HTTP01ChallengeResponse(token string) (string, error) {
+ return keyAuth(c.Key.Public(), token)
+}
+
+// HTTP01ChallengePath returns the URL path at which the response for an http-01 challenge
+// should be provided by the servers.
+// The response value can be obtained with HTTP01ChallengeResponse.
+//
+// The token argument is a Challenge.Token value.
+func (c *Client) HTTP01ChallengePath(token string) string {
+ return "/.well-known/acme-challenge/" + token
+}
+
+// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
+// Always returns an error.
+//
+// Deprecated: This challenge type was only present in pre-standardized ACME
+// protocol drafts and is insecure for use in shared hosting environments.
+func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (tls.Certificate, string, error) {
+ return tls.Certificate{}, "", errPreRFC
+}
+
+// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response.
+// Always returns an error.
+//
+// Deprecated: This challenge type was only present in pre-standardized ACME
+// protocol drafts and is insecure for use in shared hosting environments.
+func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (tls.Certificate, string, error) {
+ return tls.Certificate{}, "", errPreRFC
+}
+
+// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response.
+// Servers can present the certificate to validate the challenge and prove control
+// over an identifier (either a DNS name or the textual form of an IPv4 or IPv6
+// address). For more details on TLS-ALPN-01 see
+// https://www.rfc-editor.org/rfc/rfc8737 and https://www.rfc-editor.org/rfc/rfc8738
+//
+// The token argument is a Challenge.Token value.
+// If a WithKey option is provided, its private part signs the returned cert,
+// and the public part is used to specify the signee.
+// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve.
+//
+// The returned certificate is valid for the next 24 hours and must be presented only when
+// the server name in the TLS ClientHello matches the identifier, and the special acme-tls/1 ALPN protocol
+// has been specified.
+//
+// Validation requests for IP address identifiers will use the reverse DNS form in the server name
+// in the TLS ClientHello since the SNI extension is not supported for IP addresses.
+// See RFC 8738 Section 6 for more information.
+func (c *Client) TLSALPN01ChallengeCert(token, identifier string, opt ...CertOption) (cert tls.Certificate, err error) {
+ ka, err := keyAuth(c.Key.Public(), token)
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ shasum := sha256.Sum256([]byte(ka))
+ extValue, err := asn1.Marshal(shasum[:])
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ acmeExtension := pkix.Extension{
+ Id: idPeACMEIdentifier,
+ Critical: true,
+ Value: extValue,
+ }
+
+ tmpl := defaultTLSChallengeCertTemplate()
+
+ var newOpt []CertOption
+ for _, o := range opt {
+ switch o := o.(type) {
+ case *certOptTemplate:
+ t := *(*x509.Certificate)(o) // shallow copy is ok
+ tmpl = &t
+ default:
+ newOpt = append(newOpt, o)
+ }
+ }
+ tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
+ newOpt = append(newOpt, WithTemplate(tmpl))
+ return tlsChallengeCert(identifier, newOpt)
+}
+
+// popNonce returns a nonce value previously stored with c.addNonce
+// or fetches a fresh one from c.dir.NonceURL.
+// If NonceURL is empty, it first tries c.directoryURL() and, failing that,
+// the provided url.
+func (c *Client) popNonce(ctx context.Context, url string) (string, error) {
+ c.noncesMu.Lock()
+ defer c.noncesMu.Unlock()
+ if len(c.nonces) == 0 {
+ if c.dir != nil && c.dir.NonceURL != "" {
+ return c.fetchNonce(ctx, c.dir.NonceURL)
+ }
+ dirURL := c.directoryURL()
+ v, err := c.fetchNonce(ctx, dirURL)
+ if err != nil && url != dirURL {
+ v, err = c.fetchNonce(ctx, url)
+ }
+ return v, err
+ }
+ var nonce string
+ for nonce = range c.nonces {
+ delete(c.nonces, nonce)
+ break
+ }
+ return nonce, nil
+}
+
+// clearNonces clears any stored nonces
+func (c *Client) clearNonces() {
+ c.noncesMu.Lock()
+ defer c.noncesMu.Unlock()
+ c.nonces = make(map[string]struct{})
+}
+
+// addNonce stores a nonce value found in h (if any) for future use.
+func (c *Client) addNonce(h http.Header) {
+ v := nonceFromHeader(h)
+ if v == "" {
+ return
+ }
+ c.noncesMu.Lock()
+ defer c.noncesMu.Unlock()
+ if len(c.nonces) >= maxNonces {
+ return
+ }
+ if c.nonces == nil {
+ c.nonces = make(map[string]struct{})
+ }
+ c.nonces[v] = struct{}{}
+}
+
+func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
+ r, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
+ if err != nil {
+ return "", err
+ }
+ resp, err := c.doNoRetry(ctx, r)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+ nonce := nonceFromHeader(resp.Header)
+ if nonce == "" {
+ if resp.StatusCode > 299 {
+ return "", responseError(resp)
+ }
+ return "", errors.New("acme: nonce not found")
+ }
+ return nonce, nil
+}
+
+func nonceFromHeader(h http.Header) string {
+ return h.Get("Replay-Nonce")
+}
+
+// linkHeader returns URI-Reference values of all Link headers
+// with relation-type rel.
+// See https://tools.ietf.org/html/rfc5988#section-5 for details.
+func linkHeader(h http.Header, rel string) []string {
+ var links []string
+ for _, v := range h["Link"] {
+ parts := strings.Split(v, ";")
+ for _, p := range parts {
+ p = strings.TrimSpace(p)
+ if !strings.HasPrefix(p, "rel=") {
+ continue
+ }
+ if v := strings.Trim(p[4:], `"`); v == rel {
+ links = append(links, strings.Trim(parts[0], "<>"))
+ }
+ }
+ }
+ return links
+}
+
+// keyAuth generates a key authorization string for a given token.
+func keyAuth(pub crypto.PublicKey, token string) (string, error) {
+ th, err := JWKThumbprint(pub)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("%s.%s", token, th), nil
+}
+
+// defaultTLSChallengeCertTemplate is a template used to create challenge certs for TLS challenges.
+func defaultTLSChallengeCertTemplate() *x509.Certificate {
+ return &x509.Certificate{
+ SerialNumber: big.NewInt(1),
+ NotBefore: time.Now(),
+ NotAfter: time.Now().Add(24 * time.Hour),
+ BasicConstraintsValid: true,
+ KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ }
+}
+
+// tlsChallengeCert creates a temporary certificate for TLS-ALPN challenges
+// for the given identifier, using an auto-generated public/private key pair.
+//
+// If the provided identifier is a domain name, it will be used as a DNS type SAN and for the
+// subject common name. If the provided identifier is an IP address it will be used as an IP type
+// SAN.
+//
+// To create a cert with a custom key pair, specify WithKey option.
+func tlsChallengeCert(identifier string, opt []CertOption) (tls.Certificate, error) {
+ var key crypto.Signer
+ tmpl := defaultTLSChallengeCertTemplate()
+ for _, o := range opt {
+ switch o := o.(type) {
+ case *certOptKey:
+ if key != nil {
+ return tls.Certificate{}, errors.New("acme: duplicate key option")
+ }
+ key = o.key
+ case *certOptTemplate:
+ t := *(*x509.Certificate)(o) // shallow copy is ok
+ tmpl = &t
+ default:
+ // package's fault, if we let this happen:
+ panic(fmt.Sprintf("unsupported option type %T", o))
+ }
+ }
+ if key == nil {
+ var err error
+ if key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); err != nil {
+ return tls.Certificate{}, err
+ }
+ }
+
+ if ip := net.ParseIP(identifier); ip != nil {
+ tmpl.IPAddresses = []net.IP{ip}
+ } else {
+ tmpl.DNSNames = []string{identifier}
+ tmpl.Subject.CommonName = identifier
+ }
+
+ der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
+ if err != nil {
+ return tls.Certificate{}, err
+ }
+ return tls.Certificate{
+ Certificate: [][]byte{der},
+ PrivateKey: key,
+ }, nil
+}
+
+// timeNow is time.Now, except in tests which can mess with it.
+var timeNow = time.Now
diff --git a/local_crypto_patch/contents/acme/acme_test.go b/local_crypto_patch/contents/acme/acme_test.go
new file mode 100644
index 0000000000..a3118f4cb5
--- /dev/null
+++ b/local_crypto_patch/contents/acme/acme_test.go
@@ -0,0 +1,831 @@
+// Copyright 2015 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 acme
+
+import (
+ "bytes"
+ "context"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/base64"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "math/big"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+ "time"
+)
+
+// newTestClient creates a client with a non-nil Directory so that it skips
+// the discovery which is otherwise done on the first call of almost every
+// exported method.
+func newTestClient() *Client {
+ return &Client{
+ Key: testKeyEC,
+ dir: &Directory{}, // skip discovery
+ }
+}
+
+// Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided
+// interface.
+func decodeJWSRequest(t *testing.T, v interface{}, r io.Reader) {
+ // Decode request
+ var req struct{ Payload string }
+ if err := json.NewDecoder(r).Decode(&req); err != nil {
+ t.Fatal(err)
+ }
+ payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = json.Unmarshal(payload, v)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+type jwsHead struct {
+ Alg string
+ Nonce string
+ URL string `json:"url"`
+ KID string `json:"kid"`
+ JWK map[string]string `json:"jwk"`
+}
+
+func decodeJWSHead(r io.Reader) (*jwsHead, error) {
+ var req struct{ Protected string }
+ if err := json.NewDecoder(r).Decode(&req); err != nil {
+ return nil, err
+ }
+ b, err := base64.RawURLEncoding.DecodeString(req.Protected)
+ if err != nil {
+ return nil, err
+ }
+ var head jwsHead
+ if err := json.Unmarshal(b, &head); err != nil {
+ return nil, err
+ }
+ return &head, nil
+}
+
+func TestRegisterWithoutKey(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "HEAD" {
+ w.Header().Set("Replay-Nonce", "test-nonce")
+ return
+ }
+ w.WriteHeader(http.StatusCreated)
+ fmt.Fprint(w, `{}`)
+ }))
+ defer ts.Close()
+ // First verify that using a complete client results in success.
+ c := Client{
+ Key: testKeyEC,
+ DirectoryURL: ts.URL,
+ dir: &Directory{RegURL: ts.URL},
+ }
+ if _, err := c.Register(context.Background(), &Account{}, AcceptTOS); err != nil {
+ t.Fatalf("c.Register() = %v; want success with a complete test client", err)
+ }
+ c.Key = nil
+ if _, err := c.Register(context.Background(), &Account{}, AcceptTOS); err == nil {
+ t.Error("c.Register() from client without key succeeded, wanted error")
+ }
+}
+
+func TestAuthorize(t *testing.T) {
+ tt := []struct{ typ, value string }{
+ {"dns", "example.com"},
+ {"ip", "1.2.3.4"},
+ }
+ for _, test := range tt {
+ t.Run(test.typ, func(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "HEAD" {
+ w.Header().Set("Replay-Nonce", "test-nonce")
+ return
+ }
+ if r.Method != "POST" {
+ t.Errorf("r.Method = %q; want POST", r.Method)
+ }
+
+ var j struct {
+ Resource string
+ Identifier struct {
+ Type string
+ Value string
+ }
+ }
+ decodeJWSRequest(t, &j, r.Body)
+
+ // Test request
+ if j.Resource != "new-authz" {
+ t.Errorf("j.Resource = %q; want new-authz", j.Resource)
+ }
+ if j.Identifier.Type != test.typ {
+ t.Errorf("j.Identifier.Type = %q; want %q", j.Identifier.Type, test.typ)
+ }
+ if j.Identifier.Value != test.value {
+ t.Errorf("j.Identifier.Value = %q; want %q", j.Identifier.Value, test.value)
+ }
+
+ w.Header().Set("Location", "https://ca.tld/acme/auth/1")
+ w.WriteHeader(http.StatusCreated)
+ fmt.Fprintf(w, `{
+ "identifier": {"type":%q,"value":%q},
+ "status":"pending",
+ "challenges":[
+ {
+ "type":"http-01",
+ "status":"pending",
+ "uri":"https://ca.tld/acme/challenge/publickey/id1",
+ "token":"token1"
+ },
+ {
+ "type":"tls-sni-01",
+ "status":"pending",
+ "uri":"https://ca.tld/acme/challenge/publickey/id2",
+ "token":"token2"
+ }
+ ],
+ "combinations":[[0],[1]]
+ }`, test.typ, test.value)
+ }))
+ defer ts.Close()
+
+ var (
+ auth *Authorization
+ err error
+ )
+ cl := Client{
+ Key: testKeyEC,
+ DirectoryURL: ts.URL,
+ dir: &Directory{AuthzURL: ts.URL},
+ }
+ switch test.typ {
+ case "dns":
+ auth, err = cl.Authorize(context.Background(), test.value)
+ case "ip":
+ auth, err = cl.AuthorizeIP(context.Background(), test.value)
+ default:
+ t.Fatalf("unknown identifier type: %q", test.typ)
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if auth.URI != "https://ca.tld/acme/auth/1" {
+ t.Errorf("URI = %q; want https://ca.tld/acme/auth/1", auth.URI)
+ }
+ if auth.Status != "pending" {
+ t.Errorf("Status = %q; want pending", auth.Status)
+ }
+ if auth.Identifier.Type != test.typ {
+ t.Errorf("Identifier.Type = %q; want %q", auth.Identifier.Type, test.typ)
+ }
+ if auth.Identifier.Value != test.value {
+ t.Errorf("Identifier.Value = %q; want %q", auth.Identifier.Value, test.value)
+ }
+
+ if n := len(auth.Challenges); n != 2 {
+ t.Fatalf("len(auth.Challenges) = %d; want 2", n)
+ }
+
+ c := auth.Challenges[0]
+ if c.Type != "http-01" {
+ t.Errorf("c.Type = %q; want http-01", c.Type)
+ }
+ if c.URI != "https://ca.tld/acme/challenge/publickey/id1" {
+ t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI)
+ }
+ if c.Token != "token1" {
+ t.Errorf("c.Token = %q; want token1", c.Token)
+ }
+
+ c = auth.Challenges[1]
+ if c.Type != "tls-sni-01" {
+ t.Errorf("c.Type = %q; want tls-sni-01", c.Type)
+ }
+ if c.URI != "https://ca.tld/acme/challenge/publickey/id2" {
+ t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI)
+ }
+ if c.Token != "token2" {
+ t.Errorf("c.Token = %q; want token2", c.Token)
+ }
+
+ combs := [][]int{{0}, {1}}
+ if !reflect.DeepEqual(auth.Combinations, combs) {
+ t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs)
+ }
+
+ })
+ }
+}
+
+func TestAuthorizeValid(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "HEAD" {
+ w.Header().Set("Replay-Nonce", "nonce")
+ return
+ }
+ w.WriteHeader(http.StatusCreated)
+ w.Write([]byte(`{"status":"valid"}`))
+ }))
+ defer ts.Close()
+ client := Client{
+ Key: testKey,
+ DirectoryURL: ts.URL,
+ dir: &Directory{AuthzURL: ts.URL},
+ }
+ _, err := client.Authorize(context.Background(), "example.com")
+ if err != nil {
+ t.Errorf("err = %v", err)
+ }
+}
+
+func TestAuthorizeUnsupported(t *testing.T) {
+ const (
+ nonce = "https://example.com/acme/new-nonce"
+ reg = "https://example.com/acme/new-acct"
+ order = "https://example.com/acme/new-order"
+ revoke = "https://example.com/acme/revoke-cert"
+ keychange = "https://example.com/acme/key-change"
+ metaTerms = "https://example.com/acme/terms/2017-5-30"
+ metaWebsite = "https://www.example.com/"
+ metaCAA = "example.com"
+ )
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Replay-Nonce", "nonce")
+ if r.Method == http.MethodHead {
+ return
+ }
+ switch r.URL.Path {
+ case "/": // Directory
+ w.Header().Set("Content-Type", "application/json")
+ fmt.Fprintf(w, `{
+ "newNonce": %q,
+ "newAccount": %q,
+ "newOrder": %q,
+ "revokeCert": %q,
+ "keyChange": %q,
+ "meta": {
+ "termsOfService": %q,
+ "website": %q,
+ "caaIdentities": [%q],
+ "externalAccountRequired": true
+ }
+ }`, nonce, reg, order, revoke, keychange, metaTerms, metaWebsite, metaCAA)
+ w.WriteHeader(http.StatusOK)
+ case "/acme/new-authz":
+ w.WriteHeader(http.StatusBadRequest)
+ }
+ }))
+ defer ts.Close()
+ client := &Client{Key: testKey, DirectoryURL: ts.URL}
+ dir, err := client.Discover(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if dir.AuthzURL != "" {
+ t.Fatalf("expected AuthzURL to be empty, got %q", dir.AuthzURL)
+ }
+ if _, err := client.Authorize(context.Background(), "example.com"); !errors.Is(err, errPreAuthorizationNotSupported) {
+ t.Errorf("expected err to indicate pre-authorization is unsupported, got %+v", err)
+ }
+}
+
+func TestWaitAuthorization(t *testing.T) {
+ t.Run("wait loop", func(t *testing.T) {
+ var count int
+ authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+ count++
+ w.Header().Set("Retry-After", "0")
+ if count > 1 {
+ fmt.Fprintf(w, `{"status":"valid"}`)
+ return
+ }
+ fmt.Fprintf(w, `{"status":"pending"}`)
+ })
+ if err != nil {
+ t.Fatalf("non-nil error: %v", err)
+ }
+ if authz == nil {
+ t.Fatal("authz is nil")
+ }
+ })
+ t.Run("invalid status", func(t *testing.T) {
+ _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, `{"status":"invalid"}`)
+ })
+ if _, ok := err.(*AuthorizationError); !ok {
+ t.Errorf("err is %v (%T); want non-nil *AuthorizationError", err, err)
+ }
+ })
+ t.Run("invalid status with error returns the authorization error", func(t *testing.T) {
+ _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, `{
+ "type": "dns-01",
+ "status": "invalid",
+ "error": {
+ "type": "urn:ietf:params:acme:error:caa",
+ "detail": "CAA record for prevents issuance",
+ "status": 403
+ },
+ "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/xxx/xxx",
+ "token": "xxx",
+ "validationRecord": [
+ {
+ "hostname": ""
+ }
+ ]
+ }`)
+ })
+
+ want := &AuthorizationError{
+ Errors: []error{
+ (&wireError{
+ Status: 403,
+ Type: "urn:ietf:params:acme:error:caa",
+ Detail: "CAA record for prevents issuance",
+ }).error(nil),
+ },
+ }
+
+ _, ok := err.(*AuthorizationError)
+ if !ok {
+ t.Errorf("err is %T; want non-nil *AuthorizationError", err)
+ }
+
+ if err.Error() != want.Error() {
+ t.Errorf("err is %v; want %v", err, want)
+ }
+ })
+ t.Run("non-retriable error", func(t *testing.T) {
+ const code = http.StatusBadRequest
+ _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(code)
+ })
+ res, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("err is %v (%T); want a non-nil *Error", err, err)
+ }
+ if res.StatusCode != code {
+ t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, code)
+ }
+ })
+ for _, code := range []int{http.StatusTooManyRequests, http.StatusInternalServerError} {
+ t.Run(fmt.Sprintf("retriable %d error", code), func(t *testing.T) {
+ var count int
+ authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) {
+ count++
+ w.Header().Set("Retry-After", "0")
+ if count > 1 {
+ fmt.Fprintf(w, `{"status":"valid"}`)
+ return
+ }
+ w.WriteHeader(code)
+ })
+ if err != nil {
+ t.Fatalf("non-nil error: %v", err)
+ }
+ if authz == nil {
+ t.Fatal("authz is nil")
+ }
+ })
+ }
+ t.Run("context cancel", func(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ _, err := runWaitAuthorization(ctx, t, func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Retry-After", "60")
+ fmt.Fprintf(w, `{"status":"pending"}`)
+ time.AfterFunc(1*time.Millisecond, cancel)
+ })
+ if err == nil {
+ t.Error("err is nil")
+ }
+ })
+}
+
+func runWaitAuthorization(ctx context.Context, t *testing.T, h http.HandlerFunc) (*Authorization, error) {
+ t.Helper()
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Replay-Nonce", fmt.Sprintf("bad-test-nonce-%v", time.Now().UnixNano()))
+ h(w, r)
+ }))
+ defer ts.Close()
+
+ client := &Client{
+ Key: testKey,
+ DirectoryURL: ts.URL,
+ dir: &Directory{},
+ KID: "some-key-id", // set to avoid lookup attempt
+ }
+ return client.WaitAuthorization(ctx, ts.URL)
+}
+
+func TestRevokeAuthorization(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "HEAD" {
+ w.Header().Set("Replay-Nonce", "nonce")
+ return
+ }
+ switch r.URL.Path {
+ case "/1":
+ var req struct {
+ Resource string
+ Status string
+ Delete bool
+ }
+ decodeJWSRequest(t, &req, r.Body)
+ if req.Resource != "authz" {
+ t.Errorf("req.Resource = %q; want authz", req.Resource)
+ }
+ if req.Status != "deactivated" {
+ t.Errorf("req.Status = %q; want deactivated", req.Status)
+ }
+ if !req.Delete {
+ t.Errorf("req.Delete is false")
+ }
+ case "/2":
+ w.WriteHeader(http.StatusBadRequest)
+ }
+ }))
+ defer ts.Close()
+ client := &Client{
+ Key: testKey,
+ DirectoryURL: ts.URL, // don't dial outside of localhost
+ dir: &Directory{}, // don't do discovery
+ }
+ ctx := context.Background()
+ if err := client.RevokeAuthorization(ctx, ts.URL+"/1"); err != nil {
+ t.Errorf("err = %v", err)
+ }
+ if client.RevokeAuthorization(ctx, ts.URL+"/2") == nil {
+ t.Error("nil error")
+ }
+}
+
+func TestFetchCertCancel(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ <-r.Context().Done()
+ w.Header().Set("Retry-After", "0")
+ w.WriteHeader(http.StatusBadRequest)
+ }))
+ defer ts.Close()
+ ctx, cancel := context.WithCancel(context.Background())
+ done := make(chan struct{})
+ var err error
+ go func() {
+ cl := newTestClient()
+ _, err = cl.FetchCert(ctx, ts.URL, false)
+ close(done)
+ }()
+ cancel()
+ <-done
+ if err != context.Canceled {
+ t.Errorf("err = %v; want %v", err, context.Canceled)
+ }
+}
+
+func TestFetchCertDepth(t *testing.T) {
+ var count byte
+ var ts *httptest.Server
+ ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ count++
+ if count > maxChainLen+1 {
+ t.Errorf("count = %d; want at most %d", count, maxChainLen+1)
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ w.Header().Set("Link", fmt.Sprintf("<%s>;rel=up", ts.URL))
+ w.Write([]byte{count})
+ }))
+ defer ts.Close()
+ cl := newTestClient()
+ _, err := cl.FetchCert(context.Background(), ts.URL, true)
+ if err == nil {
+ t.Errorf("err is nil")
+ }
+}
+
+func TestFetchCertBreadth(t *testing.T) {
+ var ts *httptest.Server
+ ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ for i := 0; i < maxChainLen+1; i++ {
+ w.Header().Add("Link", fmt.Sprintf("<%s>;rel=up", ts.URL))
+ }
+ w.Write([]byte{1})
+ }))
+ defer ts.Close()
+ cl := newTestClient()
+ _, err := cl.FetchCert(context.Background(), ts.URL, true)
+ if err == nil {
+ t.Errorf("err is nil")
+ }
+}
+
+func TestFetchCertSize(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ b := bytes.Repeat([]byte{1}, maxCertSize+1)
+ w.Write(b)
+ }))
+ defer ts.Close()
+ cl := newTestClient()
+ _, err := cl.FetchCert(context.Background(), ts.URL, false)
+ if err == nil {
+ t.Errorf("err is nil")
+ }
+}
+
+func TestNonce_add(t *testing.T) {
+ var c Client
+ c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
+ c.addNonce(http.Header{"Replay-Nonce": {}})
+ c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
+
+ nonces := map[string]struct{}{"nonce": {}}
+ if !reflect.DeepEqual(c.nonces, nonces) {
+ t.Errorf("c.nonces = %q; want %q", c.nonces, nonces)
+ }
+}
+
+func TestNonce_addMax(t *testing.T) {
+ c := &Client{nonces: make(map[string]struct{})}
+ for i := 0; i < maxNonces; i++ {
+ c.nonces[fmt.Sprintf("%d", i)] = struct{}{}
+ }
+ c.addNonce(http.Header{"Replay-Nonce": {"nonce"}})
+ if n := len(c.nonces); n != maxNonces {
+ t.Errorf("len(c.nonces) = %d; want %d", n, maxNonces)
+ }
+}
+
+func TestNonce_fetch(t *testing.T) {
+ tests := []struct {
+ code int
+ nonce string
+ }{
+ {http.StatusOK, "nonce1"},
+ {http.StatusBadRequest, "nonce2"},
+ {http.StatusOK, ""},
+ }
+ var i int
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "HEAD" {
+ t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method)
+ }
+ w.Header().Set("Replay-Nonce", tests[i].nonce)
+ w.WriteHeader(tests[i].code)
+ }))
+ defer ts.Close()
+ for ; i < len(tests); i++ {
+ test := tests[i]
+ c := newTestClient()
+ n, err := c.fetchNonce(context.Background(), ts.URL)
+ if n != test.nonce {
+ t.Errorf("%d: n=%q; want %q", i, n, test.nonce)
+ }
+ switch {
+ case err == nil && test.nonce == "":
+ t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err)
+ case err != nil && test.nonce != "":
+ t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce)
+ }
+ }
+}
+
+func TestNonce_fetchError(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusTooManyRequests)
+ }))
+ defer ts.Close()
+ c := newTestClient()
+ _, err := c.fetchNonce(context.Background(), ts.URL)
+ e, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("err is %T; want *Error", err)
+ }
+ if e.StatusCode != http.StatusTooManyRequests {
+ t.Errorf("e.StatusCode = %d; want %d", e.StatusCode, http.StatusTooManyRequests)
+ }
+}
+
+func TestNonce_popWhenEmpty(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "HEAD" {
+ t.Errorf("r.Method = %q; want HEAD", r.Method)
+ }
+ switch r.URL.Path {
+ case "/dir-with-nonce":
+ w.Header().Set("Replay-Nonce", "dirnonce")
+ case "/new-nonce":
+ w.Header().Set("Replay-Nonce", "newnonce")
+ case "/dir-no-nonce", "/empty":
+ // No nonce in the header.
+ default:
+ t.Errorf("Unknown URL: %s", r.URL)
+ }
+ }))
+ defer ts.Close()
+ ctx := context.Background()
+
+ tt := []struct {
+ dirURL, popURL, nonce string
+ wantOK bool
+ }{
+ {ts.URL + "/dir-with-nonce", ts.URL + "/new-nonce", "dirnonce", true},
+ {ts.URL + "/dir-no-nonce", ts.URL + "/new-nonce", "newnonce", true},
+ {ts.URL + "/dir-no-nonce", ts.URL + "/empty", "", false},
+ }
+ for _, test := range tt {
+ t.Run(fmt.Sprintf("nonce:%s wantOK:%v", test.nonce, test.wantOK), func(t *testing.T) {
+ c := Client{DirectoryURL: test.dirURL}
+ v, err := c.popNonce(ctx, test.popURL)
+ if !test.wantOK {
+ if err == nil {
+ t.Fatalf("c.popNonce(%q) returned nil error", test.popURL)
+ }
+ return
+ }
+ if err != nil {
+ t.Fatalf("c.popNonce(%q): %v", test.popURL, err)
+ }
+ if v != test.nonce {
+ t.Errorf("c.popNonce(%q) = %q; want %q", test.popURL, v, test.nonce)
+ }
+ })
+ }
+}
+
+func TestLinkHeader(t *testing.T) {
+ h := http.Header{"Link": {
+ `;rel="next"`,
+ `; rel=recover`,
+ `; foo=bar; rel="terms-of-service"`,
+ `;rel="next"`,
+ }}
+ tests := []struct {
+ rel string
+ out []string
+ }{
+ {"next", []string{"https://example.com/acme/new-authz", "dup"}},
+ {"recover", []string{"https://example.com/acme/recover-reg"}},
+ {"terms-of-service", []string{"https://example.com/acme/terms"}},
+ {"empty", nil},
+ }
+ for i, test := range tests {
+ if v := linkHeader(h, test.rel); !reflect.DeepEqual(v, test.out) {
+ t.Errorf("%d: linkHeader(%q): %v; want %v", i, test.rel, v, test.out)
+ }
+ }
+}
+
+func TestTLSALPN01ChallengeCert(t *testing.T) {
+ const (
+ token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
+ keyAuth = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA." + testKeyECThumbprint
+ // echo -n | shasum -a 256
+ h = "0420dbbd5eefe7b4d06eb9d1d9f5acb4c7cda27d320e4b30332f0b6cb441734ad7b0"
+ domain = "example.com"
+ )
+
+ extValue, err := hex.DecodeString(h)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tlscert, err := newTestClient().TLSALPN01ChallengeCert(token, domain)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if n := len(tlscert.Certificate); n != 1 {
+ t.Fatalf("len(tlscert.Certificate) = %d; want 1", n)
+ }
+ cert, err := x509.ParseCertificate(tlscert.Certificate[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ names := []string{domain}
+ if !reflect.DeepEqual(cert.DNSNames, names) {
+ t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
+ }
+ if cn := cert.Subject.CommonName; cn != domain {
+ t.Errorf("CommonName = %q; want %q", cn, domain)
+ }
+ acmeExts := []pkix.Extension{}
+ for _, ext := range cert.Extensions {
+ if idPeACMEIdentifier.Equal(ext.Id) {
+ acmeExts = append(acmeExts, ext)
+ }
+ }
+ if len(acmeExts) != 1 {
+ t.Errorf("acmeExts = %v; want exactly one", acmeExts)
+ }
+ if !acmeExts[0].Critical {
+ t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical)
+ }
+ if bytes.Compare(acmeExts[0].Value, extValue) != 0 {
+ t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue)
+ }
+
+}
+
+func TestTLSChallengeCertOpt(t *testing.T) {
+ key, err := rsa.GenerateKey(rand.Reader, 1024)
+ if err != nil {
+ t.Fatal(err)
+ }
+ domain := "example.com"
+ tmpl := &x509.Certificate{
+ SerialNumber: big.NewInt(2),
+ Subject: pkix.Name{Organization: []string{"Test"}},
+ DNSNames: []string{"should-be-overwritten"},
+ }
+ opts := []CertOption{WithKey(key), WithTemplate(tmpl)}
+
+ client := newTestClient()
+ cert, err := client.TLSALPN01ChallengeCert("token", domain, opts...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // verify generated cert private key
+ tlskey, ok := cert.PrivateKey.(*rsa.PrivateKey)
+ if !ok {
+ t.Fatalf("tlscert.PrivateKey is %T; want *rsa.PrivateKey", cert.PrivateKey)
+ }
+ if tlskey.D.Cmp(key.D) != 0 {
+ t.Errorf("tlskey.D = %v; want %v", tlskey.D, key.D)
+ }
+ // verify generated cert public key
+ x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey)
+ if !ok {
+ t.Fatalf("x509Cert.PublicKey is %T; want *rsa.PublicKey", x509Cert.PublicKey)
+ }
+ if tlspub.N.Cmp(key.N) != 0 {
+ t.Errorf("tlspub.N = %v; want %v", tlspub.N, key.N)
+ }
+ // verify template option
+ sn := big.NewInt(2)
+ if x509Cert.SerialNumber.Cmp(sn) != 0 {
+ t.Errorf("SerialNumber = %v; want %v", x509Cert.SerialNumber, sn)
+ }
+ org := []string{"Test"}
+ if !reflect.DeepEqual(x509Cert.Subject.Organization, org) {
+ t.Errorf("Subject.Organization = %+v; want %+v", x509Cert.Subject.Organization, org)
+ }
+ for _, v := range x509Cert.DNSNames {
+ if v != domain {
+ t.Errorf("invalid DNSNames element: %q", v)
+ }
+ }
+}
+
+func TestHTTP01Challenge(t *testing.T) {
+ const (
+ token = "xxx"
+ // thumbprint is precomputed for testKeyEC in jws_test.go
+ value = token + "." + testKeyECThumbprint
+ urlpath = "/.well-known/acme-challenge/" + token
+ )
+ client := newTestClient()
+ val, err := client.HTTP01ChallengeResponse(token)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if val != value {
+ t.Errorf("val = %q; want %q", val, value)
+ }
+ if path := client.HTTP01ChallengePath(token); path != urlpath {
+ t.Errorf("path = %q; want %q", path, urlpath)
+ }
+}
+
+func TestDNS01ChallengeRecord(t *testing.T) {
+ // echo -n xxx. | \
+ // openssl dgst -binary -sha256 | \
+ // base64 | tr -d '=' | tr '/+' '_-'
+ const value = "8DERMexQ5VcdJ_prpPiA0mVdp7imgbCgjsG4SqqNMIo"
+
+ val, err := newTestClient().DNS01ChallengeRecord("xxx")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if val != value {
+ t.Errorf("val = %q; want %q", val, value)
+ }
+}
diff --git a/local_crypto_patch/contents/acme/autocert/autocert.go b/local_crypto_patch/contents/acme/autocert/autocert.go
new file mode 100644
index 0000000000..69461e31d3
--- /dev/null
+++ b/local_crypto_patch/contents/acme/autocert/autocert.go
@@ -0,0 +1,1189 @@
+// 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 autocert provides automatic access to certificates from Let's Encrypt
+// and any other ACME-based CA.
+//
+// This package is a work in progress and makes no API stability promises.
+package autocert
+
+import (
+ "bytes"
+ "context"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io"
+ mathrand "math/rand"
+ "net"
+ "net/http"
+ "path"
+ "strings"
+ "sync"
+ "time"
+
+ "golang.org/x/crypto/acme"
+ "golang.org/x/net/idna"
+)
+
+// DefaultACMEDirectory is the default ACME Directory URL used when the Manager's Client is nil.
+const DefaultACMEDirectory = "https://acme-v02.api.letsencrypt.org/directory"
+
+// createCertRetryAfter is how much time to wait before removing a failed state
+// entry due to an unsuccessful createCert call.
+// This is a variable instead of a const for testing.
+// TODO: Consider making it configurable or an exp backoff?
+var createCertRetryAfter = time.Minute
+
+// pseudoRand is safe for concurrent use.
+var pseudoRand *lockedMathRand
+
+var errPreRFC = errors.New("autocert: ACME server doesn't support RFC 8555")
+
+func init() {
+ src := mathrand.NewSource(time.Now().UnixNano())
+ pseudoRand = &lockedMathRand{rnd: mathrand.New(src)}
+}
+
+// AcceptTOS is a Manager.Prompt function that always returns true to
+// indicate acceptance of the CA's Terms of Service during account
+// registration.
+func AcceptTOS(tosURL string) bool { return true }
+
+// HostPolicy specifies which host names the Manager is allowed to respond to.
+// It returns a non-nil error if the host should be rejected.
+// The returned error is accessible via tls.Conn.Handshake and its callers.
+// See Manager's HostPolicy field and GetCertificate method docs for more details.
+type HostPolicy func(ctx context.Context, host string) error
+
+// HostWhitelist returns a policy where only the specified host names are allowed.
+// Only exact matches are currently supported. Subdomains, regexp or wildcard
+// will not match.
+//
+// Note that all hosts will be converted to Punycode via idna.Lookup.ToASCII so that
+// Manager.GetCertificate can handle the Unicode IDN and mixedcase hosts correctly.
+// Invalid hosts will be silently ignored.
+func HostWhitelist(hosts ...string) HostPolicy {
+ whitelist := make(map[string]bool, len(hosts))
+ for _, h := range hosts {
+ if h, err := idna.Lookup.ToASCII(h); err == nil {
+ whitelist[h] = true
+ }
+ }
+ return func(_ context.Context, host string) error {
+ if !whitelist[host] {
+ return fmt.Errorf("acme/autocert: host %q not configured in HostWhitelist", host)
+ }
+ return nil
+ }
+}
+
+// defaultHostPolicy is used when Manager.HostPolicy is not set.
+func defaultHostPolicy(context.Context, string) error {
+ return nil
+}
+
+// Manager is a stateful certificate manager built on top of acme.Client.
+// It obtains and refreshes certificates automatically using "tls-alpn-01"
+// or "http-01" challenge types, as well as providing them to a TLS server
+// via tls.Config.
+//
+// You must specify a cache implementation, such as DirCache,
+// to reuse obtained certificates across program restarts.
+// Otherwise your server is very likely to exceed the certificate
+// issuer's request rate limits.
+type Manager struct {
+ // Prompt specifies a callback function to conditionally accept a CA's Terms of Service (TOS).
+ // The registration may require the caller to agree to the CA's TOS.
+ // If so, Manager calls Prompt with a TOS URL provided by the CA. Prompt should report
+ // whether the caller agrees to the terms.
+ //
+ // To always accept the terms, the callers can use AcceptTOS.
+ Prompt func(tosURL string) bool
+
+ // Cache optionally stores and retrieves previously-obtained certificates
+ // and other state. If nil, certs will only be cached for the lifetime of
+ // the Manager. Multiple Managers can share the same Cache.
+ //
+ // Using a persistent Cache, such as DirCache, is strongly recommended.
+ Cache Cache
+
+ // HostPolicy controls which domains the Manager will attempt
+ // to retrieve new certificates for. It does not affect cached certs.
+ //
+ // If non-nil, HostPolicy is called before requesting a new cert.
+ // If nil, all hosts are currently allowed. This is not recommended,
+ // as it opens a potential attack where clients connect to a server
+ // by IP address and pretend to be asking for an incorrect host name.
+ // Manager will attempt to obtain a certificate for that host, incorrectly,
+ // eventually reaching the CA's rate limit for certificate requests
+ // and making it impossible to obtain actual certificates.
+ //
+ // See GetCertificate for more details.
+ HostPolicy HostPolicy
+
+ // RenewBefore optionally specifies how early certificates should
+ // be renewed before they expire.
+ //
+ // If zero, they're renewed at the lesser of 30 days or
+ // 1/3 of the certificate lifetime.
+ RenewBefore time.Duration
+
+ // Client is used to perform low-level operations, such as account registration
+ // and requesting new certificates.
+ //
+ // If Client is nil, a zero-value acme.Client is used with DefaultACMEDirectory
+ // as the directory endpoint.
+ // If the Client.Key is nil, a new ECDSA P-256 key is generated and,
+ // if Cache is not nil, stored in cache.
+ //
+ // Mutating the field after the first call of GetCertificate method will have no effect.
+ Client *acme.Client
+
+ // Email optionally specifies a contact email address.
+ // This is used by CAs, such as Let's Encrypt, to notify about problems
+ // with issued certificates.
+ //
+ // If the Client's account key is already registered, Email is not used.
+ Email string
+
+ // ForceRSA used to make the Manager generate RSA certificates. It is now ignored.
+ //
+ // Deprecated: the Manager will request the correct type of certificate based
+ // on what each client supports.
+ ForceRSA bool
+
+ // ExtraExtensions are used when generating a new CSR (Certificate Request),
+ // thus allowing customization of the resulting certificate.
+ // For instance, TLS Feature Extension (RFC 7633) can be used
+ // to prevent an OCSP downgrade attack.
+ //
+ // The field value is passed to crypto/x509.CreateCertificateRequest
+ // in the template's ExtraExtensions field as is.
+ ExtraExtensions []pkix.Extension
+
+ // ExternalAccountBinding optionally represents an arbitrary binding to an
+ // account of the CA to which the ACME server is tied.
+ // See RFC 8555, Section 7.3.4 for more details.
+ ExternalAccountBinding *acme.ExternalAccountBinding
+
+ clientMu sync.Mutex
+ client *acme.Client // initialized by acmeClient method
+
+ stateMu sync.Mutex
+ state map[certKey]*certState
+
+ // renewal tracks the set of domains currently running renewal timers.
+ renewalMu sync.Mutex
+ renewal map[certKey]*domainRenewal
+
+ // challengeMu guards tryHTTP01, certTokens and httpTokens.
+ challengeMu sync.RWMutex
+ // tryHTTP01 indicates whether the Manager should try "http-01" challenge type
+ // during the authorization flow.
+ tryHTTP01 bool
+ // httpTokens contains response body values for http-01 challenges
+ // and is keyed by the URL path at which a challenge response is expected
+ // to be provisioned.
+ // The entries are stored for the duration of the authorization flow.
+ httpTokens map[string][]byte
+ // certTokens contains temporary certificates for tls-alpn-01 challenges
+ // and is keyed by the domain name which matches the ClientHello server name.
+ // The entries are stored for the duration of the authorization flow.
+ certTokens map[string]*tls.Certificate
+
+ // nowFunc, if not nil, returns the current time. This may be set for
+ // testing purposes.
+ nowFunc func() time.Time
+}
+
+// certKey is the key by which certificates are tracked in state, renewal and cache.
+type certKey struct {
+ domain string // without trailing dot
+ isRSA bool // RSA cert for legacy clients (as opposed to default ECDSA)
+ isToken bool // tls-based challenge token cert; key type is undefined regardless of isRSA
+}
+
+func (c certKey) String() string {
+ if c.isToken {
+ return c.domain + "+token"
+ }
+ if c.isRSA {
+ return c.domain + "+rsa"
+ }
+ return c.domain
+}
+
+// TLSConfig creates a new TLS config suitable for net/http.Server servers,
+// supporting HTTP/2 and the tls-alpn-01 ACME challenge type.
+func (m *Manager) TLSConfig() *tls.Config {
+ return &tls.Config{
+ GetCertificate: m.GetCertificate,
+ NextProtos: []string{
+ "h2", "http/1.1", // enable HTTP/2
+ acme.ALPNProto, // enable tls-alpn ACME challenges
+ },
+ }
+}
+
+// GetCertificate implements the tls.Config.GetCertificate hook.
+// It provides a TLS certificate for hello.ServerName host, including answering
+// tls-alpn-01 challenges.
+// All other fields of hello are ignored.
+//
+// If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting
+// a new cert. A non-nil error returned from m.HostPolicy halts TLS negotiation.
+// The error is propagated back to the caller of GetCertificate and is user-visible.
+// This does not affect cached certs. See HostPolicy field description for more details.
+//
+// If GetCertificate is used directly, instead of via Manager.TLSConfig, package users will
+// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler for http-01.
+func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ name := hello.ServerName
+ if name == "" {
+ return nil, errors.New("acme/autocert: missing server name")
+ }
+ if !strings.Contains(strings.Trim(name, "."), ".") {
+ return nil, errors.New("acme/autocert: server name component count invalid")
+ }
+
+ // Note that this conversion is necessary because some server names in the handshakes
+ // started by some clients (such as cURL) are not converted to Punycode, which will
+ // prevent us from obtaining certificates for them. In addition, we should also treat
+ // example.com and EXAMPLE.COM as equivalent and return the same certificate for them.
+ // Fortunately, this conversion also helped us deal with this kind of mixedcase problems.
+ //
+ // Due to the "σςΣ" problem (see https://unicode.org/faq/idn.html#22), we can't use
+ // idna.Punycode.ToASCII (or just idna.ToASCII) here.
+ name, err := idna.Lookup.ToASCII(name)
+ if err != nil {
+ return nil, errors.New("acme/autocert: server name contains invalid character")
+ }
+
+ // In the worst-case scenario, the timeout needs to account for caching, host policy,
+ // domain ownership verification and certificate issuance.
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+ defer cancel()
+
+ // Check whether this is a token cert requested for TLS-ALPN challenge.
+ if wantsTokenCert(hello) {
+ m.challengeMu.RLock()
+ defer m.challengeMu.RUnlock()
+ if cert := m.certTokens[name]; cert != nil {
+ return cert, nil
+ }
+ if cert, err := m.cacheGet(ctx, certKey{domain: name, isToken: true}); err == nil {
+ return cert, nil
+ }
+ // TODO: cache error results?
+ return nil, fmt.Errorf("acme/autocert: no token cert for %q", name)
+ }
+
+ // regular domain
+ if err := m.hostPolicy()(ctx, name); err != nil {
+ return nil, err
+ }
+
+ ck := certKey{
+ domain: strings.TrimSuffix(name, "."), // golang.org/issue/18114
+ isRSA: !supportsECDSA(hello),
+ }
+ cert, err := m.cert(ctx, ck)
+ if err == nil {
+ return cert, nil
+ }
+ if err != ErrCacheMiss {
+ return nil, err
+ }
+
+ // first-time
+ cert, err = m.createCert(ctx, ck)
+ if err != nil {
+ return nil, err
+ }
+ m.cachePut(ctx, ck, cert)
+ return cert, nil
+}
+
+// wantsTokenCert reports whether a TLS request with SNI is made by a CA server
+// for a challenge verification.
+func wantsTokenCert(hello *tls.ClientHelloInfo) bool {
+ // tls-alpn-01
+ if len(hello.SupportedProtos) == 1 && hello.SupportedProtos[0] == acme.ALPNProto {
+ return true
+ }
+ return false
+}
+
+func supportsECDSA(hello *tls.ClientHelloInfo) bool {
+ // The "signature_algorithms" extension, if present, limits the key exchange
+ // algorithms allowed by the cipher suites. See RFC 5246, section 7.4.1.4.1.
+ if hello.SignatureSchemes != nil {
+ ecdsaOK := false
+ schemeLoop:
+ for _, scheme := range hello.SignatureSchemes {
+ const tlsECDSAWithSHA1 tls.SignatureScheme = 0x0203 // constant added in Go 1.10
+ switch scheme {
+ case tlsECDSAWithSHA1, tls.ECDSAWithP256AndSHA256,
+ tls.ECDSAWithP384AndSHA384, tls.ECDSAWithP521AndSHA512:
+ ecdsaOK = true
+ break schemeLoop
+ }
+ }
+ if !ecdsaOK {
+ return false
+ }
+ }
+ if hello.SupportedCurves != nil {
+ ecdsaOK := false
+ for _, curve := range hello.SupportedCurves {
+ if curve == tls.CurveP256 {
+ ecdsaOK = true
+ break
+ }
+ }
+ if !ecdsaOK {
+ return false
+ }
+ }
+ for _, suite := range hello.CipherSuites {
+ switch suite {
+ case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:
+ return true
+ }
+ }
+ return false
+}
+
+// HTTPHandler configures the Manager to provision ACME "http-01" challenge responses.
+// It returns an http.Handler that responds to the challenges and must be
+// running on port 80. If it receives a request that is not an ACME challenge,
+// it delegates the request to the optional fallback handler.
+//
+// If fallback is nil, the returned handler redirects all GET and HEAD requests
+// to the default TLS port 443 with 302 Found status code, preserving the original
+// request path and query. It responds with 400 Bad Request to all other HTTP methods.
+// The fallback is not protected by the optional HostPolicy.
+//
+// Because the fallback handler is run with unencrypted port 80 requests,
+// the fallback should not serve TLS-only requests.
+//
+// If HTTPHandler is never called, the Manager will only use the "tls-alpn-01"
+// challenge for domain verification.
+func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler {
+ m.challengeMu.Lock()
+ defer m.challengeMu.Unlock()
+ m.tryHTTP01 = true
+
+ if fallback == nil {
+ fallback = http.HandlerFunc(handleHTTPRedirect)
+ }
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") {
+ fallback.ServeHTTP(w, r)
+ return
+ }
+ // A reasonable context timeout for cache and host policy only,
+ // because we don't wait for a new certificate issuance here.
+ ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
+ defer cancel()
+ if err := m.hostPolicy()(ctx, r.Host); err != nil {
+ http.Error(w, err.Error(), http.StatusForbidden)
+ return
+ }
+ data, err := m.httpToken(ctx, r.URL.Path)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusNotFound)
+ return
+ }
+ w.Write(data)
+ })
+}
+
+func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "GET" && r.Method != "HEAD" {
+ http.Error(w, "Use HTTPS", http.StatusBadRequest)
+ return
+ }
+ target := "https://" + stripPort(r.Host) + r.URL.RequestURI()
+ http.Redirect(w, r, target, http.StatusFound)
+}
+
+func stripPort(hostport string) string {
+ host, _, err := net.SplitHostPort(hostport)
+ if err != nil {
+ return hostport
+ }
+ return net.JoinHostPort(host, "443")
+}
+
+// cert returns an existing certificate either from m.state or cache.
+// If a certificate is found in cache but not in m.state, the latter will be filled
+// with the cached value.
+func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error) {
+ m.stateMu.Lock()
+ if s, ok := m.state[ck]; ok {
+ m.stateMu.Unlock()
+ s.RLock()
+ defer s.RUnlock()
+ return s.tlscert()
+ }
+ defer m.stateMu.Unlock()
+ cert, err := m.cacheGet(ctx, ck)
+ if err != nil {
+ return nil, err
+ }
+ signer, ok := cert.PrivateKey.(crypto.Signer)
+ if !ok {
+ return nil, errors.New("acme/autocert: private key cannot sign")
+ }
+ if m.state == nil {
+ m.state = make(map[certKey]*certState)
+ }
+ s := &certState{
+ key: signer,
+ cert: cert.Certificate,
+ leaf: cert.Leaf,
+ }
+ m.state[ck] = s
+ m.startRenew(ck, s.key, s.leaf.NotBefore, s.leaf.NotAfter)
+ return cert, nil
+}
+
+// cacheGet always returns a valid certificate, or an error otherwise.
+// If a cached certificate exists but is not valid, ErrCacheMiss is returned.
+func (m *Manager) cacheGet(ctx context.Context, ck certKey) (*tls.Certificate, error) {
+ if m.Cache == nil {
+ return nil, ErrCacheMiss
+ }
+ data, err := m.Cache.Get(ctx, ck.String())
+ if err != nil {
+ return nil, err
+ }
+
+ // private
+ priv, pub := pem.Decode(data)
+ if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
+ return nil, ErrCacheMiss
+ }
+ privKey, err := parsePrivateKey(priv.Bytes)
+ if err != nil {
+ return nil, err
+ }
+
+ // public
+ var pubDER [][]byte
+ for len(pub) > 0 {
+ var b *pem.Block
+ b, pub = pem.Decode(pub)
+ if b == nil {
+ break
+ }
+ pubDER = append(pubDER, b.Bytes)
+ }
+ if len(pub) > 0 {
+ // Leftover content not consumed by pem.Decode. Corrupt. Ignore.
+ return nil, ErrCacheMiss
+ }
+
+ // verify and create TLS cert
+ leaf, err := validCert(ck, pubDER, privKey, m.now())
+ if err != nil {
+ return nil, ErrCacheMiss
+ }
+ tlscert := &tls.Certificate{
+ Certificate: pubDER,
+ PrivateKey: privKey,
+ Leaf: leaf,
+ }
+ return tlscert, nil
+}
+
+func (m *Manager) cachePut(ctx context.Context, ck certKey, tlscert *tls.Certificate) error {
+ if m.Cache == nil {
+ return nil
+ }
+
+ // contains PEM-encoded data
+ var buf bytes.Buffer
+
+ // private
+ switch key := tlscert.PrivateKey.(type) {
+ case *ecdsa.PrivateKey:
+ if err := encodeECDSAKey(&buf, key); err != nil {
+ return err
+ }
+ case *rsa.PrivateKey:
+ b := x509.MarshalPKCS1PrivateKey(key)
+ pb := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: b}
+ if err := pem.Encode(&buf, pb); err != nil {
+ return err
+ }
+ default:
+ return errors.New("acme/autocert: unknown private key type")
+ }
+
+ // public
+ for _, b := range tlscert.Certificate {
+ pb := &pem.Block{Type: "CERTIFICATE", Bytes: b}
+ if err := pem.Encode(&buf, pb); err != nil {
+ return err
+ }
+ }
+
+ return m.Cache.Put(ctx, ck.String(), buf.Bytes())
+}
+
+func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
+ b, err := x509.MarshalECPrivateKey(key)
+ if err != nil {
+ return err
+ }
+ pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
+ return pem.Encode(w, pb)
+}
+
+// createCert starts the domain ownership verification and returns a certificate
+// for that domain upon success.
+//
+// If the domain is already being verified, it waits for the existing verification to complete.
+// Either way, createCert blocks for the duration of the whole process.
+func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate, error) {
+ // TODO: maybe rewrite this whole piece using sync.Once
+ state, err := m.certState(ck)
+ if err != nil {
+ return nil, err
+ }
+ // state may exist if another goroutine is already working on it
+ // in which case just wait for it to finish
+ if !state.locked {
+ state.RLock()
+ defer state.RUnlock()
+ return state.tlscert()
+ }
+
+ // We are the first; state is locked.
+ // Unblock the readers when domain ownership is verified
+ // and we got the cert or the process failed.
+ defer state.Unlock()
+ state.locked = false
+
+ der, leaf, err := m.authorizedCert(ctx, state.key, ck)
+ if err != nil {
+ // Remove the failed state after some time,
+ // making the manager call createCert again on the following TLS hello.
+ didRemove := testDidRemoveState // The lifetime of this timer is untracked, so copy mutable local state to avoid races.
+ time.AfterFunc(createCertRetryAfter, func() {
+ defer didRemove(ck)
+ m.stateMu.Lock()
+ defer m.stateMu.Unlock()
+ // Verify the state hasn't changed and it's still invalid
+ // before deleting.
+ s, ok := m.state[ck]
+ if !ok {
+ return
+ }
+ if _, err := validCert(ck, s.cert, s.key, m.now()); err == nil {
+ return
+ }
+ delete(m.state, ck)
+ })
+ return nil, err
+ }
+ state.cert = der
+ state.leaf = leaf
+ m.startRenew(ck, state.key, state.leaf.NotBefore, state.leaf.NotAfter)
+ return state.tlscert()
+}
+
+// certState returns a new or existing certState.
+// If a new certState is returned, state.exist is false and the state is locked.
+// The returned error is non-nil only in the case where a new state could not be created.
+func (m *Manager) certState(ck certKey) (*certState, error) {
+ m.stateMu.Lock()
+ defer m.stateMu.Unlock()
+ if m.state == nil {
+ m.state = make(map[certKey]*certState)
+ }
+ // existing state
+ if state, ok := m.state[ck]; ok {
+ return state, nil
+ }
+
+ // new locked state
+ var (
+ err error
+ key crypto.Signer
+ )
+ if ck.isRSA {
+ key, err = rsa.GenerateKey(rand.Reader, 2048)
+ } else {
+ key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ state := &certState{
+ key: key,
+ locked: true,
+ }
+ state.Lock() // will be unlocked by m.certState caller
+ m.state[ck] = state
+ return state, nil
+}
+
+// authorizedCert starts the domain ownership verification process and requests a new cert upon success.
+// The key argument is the certificate private key.
+func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck certKey) (der [][]byte, leaf *x509.Certificate, err error) {
+ csr, err := certRequest(key, ck.domain, m.ExtraExtensions)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ client, err := m.acmeClient(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+ dir, err := client.Discover(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dir.OrderURL == "" {
+ return nil, nil, errPreRFC
+ }
+
+ o, err := m.verifyRFC(ctx, client, ck.domain)
+ if err != nil {
+ return nil, nil, err
+ }
+ chain, _, err := client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ leaf, err = validCert(ck, chain, key, m.now())
+ if err != nil {
+ return nil, nil, err
+ }
+ return chain, leaf, nil
+}
+
+// verifyRFC runs the identifier (domain) order-based authorization flow for RFC compliant CAs
+// using each applicable ACME challenge type.
+func (m *Manager) verifyRFC(ctx context.Context, client *acme.Client, domain string) (*acme.Order, error) {
+ // Try each supported challenge type starting with a new order each time.
+ // The nextTyp index of the next challenge type to try is shared across
+ // all order authorizations: if we've tried a challenge type once and it didn't work,
+ // it will most likely not work on another order's authorization either.
+ challengeTypes := m.supportedChallengeTypes()
+ nextTyp := 0 // challengeTypes index
+AuthorizeOrderLoop:
+ for {
+ o, err := client.AuthorizeOrder(ctx, acme.DomainIDs(domain))
+ if err != nil {
+ return nil, err
+ }
+ // Remove all hanging authorizations to reduce rate limit quotas
+ // after we're done.
+ defer func(urls []string) {
+ go m.deactivatePendingAuthz(urls)
+ }(o.AuthzURLs)
+
+ // Check if there's actually anything we need to do.
+ switch o.Status {
+ case acme.StatusReady:
+ // Already authorized.
+ return o, nil
+ case acme.StatusPending:
+ // Continue normal Order-based flow.
+ default:
+ return nil, fmt.Errorf("acme/autocert: invalid new order status %q; order URL: %q", o.Status, o.URI)
+ }
+
+ // Satisfy all pending authorizations.
+ for _, zurl := range o.AuthzURLs {
+ z, err := client.GetAuthorization(ctx, zurl)
+ if err != nil {
+ return nil, err
+ }
+ if z.Status != acme.StatusPending {
+ // We are interested only in pending authorizations.
+ continue
+ }
+ // Pick the next preferred challenge.
+ var chal *acme.Challenge
+ for chal == nil && nextTyp < len(challengeTypes) {
+ chal = pickChallenge(challengeTypes[nextTyp], z.Challenges)
+ nextTyp++
+ }
+ if chal == nil {
+ return nil, fmt.Errorf("acme/autocert: unable to satisfy %q for domain %q: no viable challenge type found", z.URI, domain)
+ }
+ // Respond to the challenge and wait for validation result.
+ cleanup, err := m.fulfill(ctx, client, chal, domain)
+ if err != nil {
+ continue AuthorizeOrderLoop
+ }
+ defer cleanup()
+ if _, err := client.Accept(ctx, chal); err != nil {
+ continue AuthorizeOrderLoop
+ }
+ if _, err := client.WaitAuthorization(ctx, z.URI); err != nil {
+ continue AuthorizeOrderLoop
+ }
+ }
+
+ // All authorizations are satisfied.
+ // Wait for the CA to update the order status.
+ o, err = client.WaitOrder(ctx, o.URI)
+ if err != nil {
+ continue AuthorizeOrderLoop
+ }
+ return o, nil
+ }
+}
+
+func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge {
+ for _, c := range chal {
+ if c.Type == typ {
+ return c
+ }
+ }
+ return nil
+}
+
+func (m *Manager) supportedChallengeTypes() []string {
+ m.challengeMu.RLock()
+ defer m.challengeMu.RUnlock()
+ typ := []string{"tls-alpn-01"}
+ if m.tryHTTP01 {
+ typ = append(typ, "http-01")
+ }
+ return typ
+}
+
+// deactivatePendingAuthz relinquishes all authorizations identified by the elements
+// of the provided uri slice which are in "pending" state.
+// It ignores revocation errors.
+//
+// deactivatePendingAuthz takes no context argument and instead runs with its own
+// "detached" context because deactivations are done in a goroutine separate from
+// that of the main issuance or renewal flow.
+func (m *Manager) deactivatePendingAuthz(uri []string) {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+ defer cancel()
+ client, err := m.acmeClient(ctx)
+ if err != nil {
+ return
+ }
+ for _, u := range uri {
+ z, err := client.GetAuthorization(ctx, u)
+ if err == nil && z.Status == acme.StatusPending {
+ client.RevokeAuthorization(ctx, u)
+ }
+ }
+}
+
+// fulfill provisions a response to the challenge chal.
+// The cleanup is non-nil only if provisioning succeeded.
+func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge, domain string) (cleanup func(), err error) {
+ switch chal.Type {
+ case "tls-alpn-01":
+ cert, err := client.TLSALPN01ChallengeCert(chal.Token, domain)
+ if err != nil {
+ return nil, err
+ }
+ m.putCertToken(ctx, domain, &cert)
+ return func() { go m.deleteCertToken(domain) }, nil
+ case "http-01":
+ resp, err := client.HTTP01ChallengeResponse(chal.Token)
+ if err != nil {
+ return nil, err
+ }
+ p := client.HTTP01ChallengePath(chal.Token)
+ m.putHTTPToken(ctx, p, resp)
+ return func() { go m.deleteHTTPToken(p) }, nil
+ }
+ return nil, fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type)
+}
+
+// putCertToken stores the token certificate with the specified name
+// in both m.certTokens map and m.Cache.
+func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) {
+ m.challengeMu.Lock()
+ defer m.challengeMu.Unlock()
+ if m.certTokens == nil {
+ m.certTokens = make(map[string]*tls.Certificate)
+ }
+ m.certTokens[name] = cert
+ m.cachePut(ctx, certKey{domain: name, isToken: true}, cert)
+}
+
+// deleteCertToken removes the token certificate with the specified name
+// from both m.certTokens map and m.Cache.
+func (m *Manager) deleteCertToken(name string) {
+ m.challengeMu.Lock()
+ defer m.challengeMu.Unlock()
+ delete(m.certTokens, name)
+ if m.Cache != nil {
+ ck := certKey{domain: name, isToken: true}
+ m.Cache.Delete(context.Background(), ck.String())
+ }
+}
+
+// httpToken retrieves an existing http-01 token value from an in-memory map
+// or the optional cache.
+func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, error) {
+ m.challengeMu.RLock()
+ defer m.challengeMu.RUnlock()
+ if v, ok := m.httpTokens[tokenPath]; ok {
+ return v, nil
+ }
+ if m.Cache == nil {
+ return nil, fmt.Errorf("acme/autocert: no token at %q", tokenPath)
+ }
+ return m.Cache.Get(ctx, httpTokenCacheKey(tokenPath))
+}
+
+// putHTTPToken stores an http-01 token value using tokenPath as key
+// in both in-memory map and the optional Cache.
+//
+// It ignores any error returned from Cache.Put.
+func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) {
+ m.challengeMu.Lock()
+ defer m.challengeMu.Unlock()
+ if m.httpTokens == nil {
+ m.httpTokens = make(map[string][]byte)
+ }
+ b := []byte(val)
+ m.httpTokens[tokenPath] = b
+ if m.Cache != nil {
+ m.Cache.Put(ctx, httpTokenCacheKey(tokenPath), b)
+ }
+}
+
+// deleteHTTPToken removes an http-01 token value from both in-memory map
+// and the optional Cache, ignoring any error returned from the latter.
+//
+// If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout.
+func (m *Manager) deleteHTTPToken(tokenPath string) {
+ m.challengeMu.Lock()
+ defer m.challengeMu.Unlock()
+ delete(m.httpTokens, tokenPath)
+ if m.Cache != nil {
+ m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath))
+ }
+}
+
+// httpTokenCacheKey returns a key at which an http-01 token value may be stored
+// in the Manager's optional Cache.
+func httpTokenCacheKey(tokenPath string) string {
+ return path.Base(tokenPath) + "+http-01"
+}
+
+// startRenew starts a cert renewal timer loop, one per domain.
+//
+// The loop is scheduled in two cases:
+// - a cert was fetched from cache for the first time (wasn't in m.state)
+// - a new cert was created by m.createCert
+//
+// The key argument is a certificate private key.
+// The exp argument is the cert expiration time (NotAfter).
+func (m *Manager) startRenew(ck certKey, key crypto.Signer, notBefore, notAfter time.Time) {
+ m.renewalMu.Lock()
+ defer m.renewalMu.Unlock()
+ if m.renewal[ck] != nil {
+ // another goroutine is already on it
+ return
+ }
+ if m.renewal == nil {
+ m.renewal = make(map[certKey]*domainRenewal)
+ }
+ dr := &domainRenewal{m: m, ck: ck, key: key}
+ m.renewal[ck] = dr
+ dr.start(notBefore, notAfter)
+}
+
+// stopRenew stops all currently running cert renewal timers.
+// The timers are not restarted during the lifetime of the Manager.
+func (m *Manager) stopRenew() {
+ m.renewalMu.Lock()
+ defer m.renewalMu.Unlock()
+ for name, dr := range m.renewal {
+ delete(m.renewal, name)
+ dr.stop()
+ }
+}
+
+func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) {
+ const keyName = "acme_account+key"
+
+ // Previous versions of autocert stored the value under a different key.
+ const legacyKeyName = "acme_account.key"
+
+ genKey := func() (*ecdsa.PrivateKey, error) {
+ return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ }
+
+ if m.Cache == nil {
+ return genKey()
+ }
+
+ data, err := m.Cache.Get(ctx, keyName)
+ if err == ErrCacheMiss {
+ data, err = m.Cache.Get(ctx, legacyKeyName)
+ }
+ if err == ErrCacheMiss {
+ key, err := genKey()
+ if err != nil {
+ return nil, err
+ }
+ var buf bytes.Buffer
+ if err := encodeECDSAKey(&buf, key); err != nil {
+ return nil, err
+ }
+ if err := m.Cache.Put(ctx, keyName, buf.Bytes()); err != nil {
+ return nil, err
+ }
+ return key, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ priv, _ := pem.Decode(data)
+ if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
+ return nil, errors.New("acme/autocert: invalid account key found in cache")
+ }
+ return parsePrivateKey(priv.Bytes)
+}
+
+func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) {
+ m.clientMu.Lock()
+ defer m.clientMu.Unlock()
+ if m.client != nil {
+ return m.client, nil
+ }
+
+ client := m.Client
+ if client == nil {
+ client = &acme.Client{DirectoryURL: DefaultACMEDirectory}
+ }
+ if client.Key == nil {
+ var err error
+ client.Key, err = m.accountKey(ctx)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if client.UserAgent == "" {
+ client.UserAgent = "autocert"
+ }
+ var contact []string
+ if m.Email != "" {
+ contact = []string{"mailto:" + m.Email}
+ }
+ a := &acme.Account{Contact: contact, ExternalAccountBinding: m.ExternalAccountBinding}
+ _, err := client.Register(ctx, a, m.Prompt)
+ if err == nil || isAccountAlreadyExist(err) {
+ m.client = client
+ err = nil
+ }
+ return m.client, err
+}
+
+// isAccountAlreadyExist reports whether the err, as returned from acme.Client.Register,
+// indicates the account has already been registered.
+func isAccountAlreadyExist(err error) bool {
+ if err == acme.ErrAccountAlreadyExists {
+ return true
+ }
+ ae, ok := err.(*acme.Error)
+ return ok && ae.StatusCode == http.StatusConflict
+}
+
+func (m *Manager) hostPolicy() HostPolicy {
+ if m.HostPolicy != nil {
+ return m.HostPolicy
+ }
+ return defaultHostPolicy
+}
+
+func (m *Manager) now() time.Time {
+ if m.nowFunc != nil {
+ return m.nowFunc()
+ }
+ return time.Now()
+}
+
+// certState is ready when its mutex is unlocked for reading.
+type certState struct {
+ sync.RWMutex
+ locked bool // locked for read/write
+ key crypto.Signer // private key for cert
+ cert [][]byte // DER encoding
+ leaf *x509.Certificate // parsed cert[0]; always non-nil if cert != nil
+}
+
+// tlscert creates a tls.Certificate from s.key and s.cert.
+// Callers should wrap it in s.RLock() and s.RUnlock().
+func (s *certState) tlscert() (*tls.Certificate, error) {
+ if s.key == nil {
+ return nil, errors.New("acme/autocert: missing signer")
+ }
+ if len(s.cert) == 0 {
+ return nil, errors.New("acme/autocert: missing certificate")
+ }
+ return &tls.Certificate{
+ PrivateKey: s.key,
+ Certificate: s.cert,
+ Leaf: s.leaf,
+ }, nil
+}
+
+// certRequest generates a CSR for the given common name.
+func certRequest(key crypto.Signer, name string, ext []pkix.Extension) ([]byte, error) {
+ req := &x509.CertificateRequest{
+ Subject: pkix.Name{CommonName: name},
+ DNSNames: []string{name},
+ ExtraExtensions: ext,
+ }
+ return x509.CreateCertificateRequest(rand.Reader, req, key)
+}
+
+// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
+// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
+// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
+//
+// Inspired by parsePrivateKey in crypto/tls/tls.go.
+func parsePrivateKey(der []byte) (crypto.Signer, error) {
+ if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
+ return key, nil
+ }
+ if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
+ switch key := key.(type) {
+ case *rsa.PrivateKey:
+ return key, nil
+ case *ecdsa.PrivateKey:
+ return key, nil
+ default:
+ return nil, errors.New("acme/autocert: unknown private key type in PKCS#8 wrapping")
+ }
+ }
+ if key, err := x509.ParseECPrivateKey(der); err == nil {
+ return key, nil
+ }
+
+ return nil, errors.New("acme/autocert: failed to parse private key")
+}
+
+// validCert parses a cert chain provided as der argument and verifies the leaf and der[0]
+// correspond to the private key, the domain and key type match, and expiration dates
+// are valid. It doesn't do any revocation checking.
+//
+// The returned value is the verified leaf cert.
+func validCert(ck certKey, der [][]byte, key crypto.Signer, now time.Time) (leaf *x509.Certificate, err error) {
+ // parse public part(s)
+ var n int
+ for _, b := range der {
+ n += len(b)
+ }
+ pub := make([]byte, n)
+ n = 0
+ for _, b := range der {
+ n += copy(pub[n:], b)
+ }
+ x509Cert, err := x509.ParseCertificates(pub)
+ if err != nil || len(x509Cert) == 0 {
+ return nil, errors.New("acme/autocert: no public key found")
+ }
+ // verify the leaf is not expired and matches the domain name
+ leaf = x509Cert[0]
+ if now.Before(leaf.NotBefore) {
+ return nil, errors.New("acme/autocert: certificate is not valid yet")
+ }
+ if now.After(leaf.NotAfter) {
+ return nil, errors.New("acme/autocert: expired certificate")
+ }
+ if err := leaf.VerifyHostname(ck.domain); err != nil {
+ return nil, err
+ }
+ // renew certificates revoked by Let's Encrypt in January 2022
+ if isRevokedLetsEncrypt(leaf) {
+ return nil, errors.New("acme/autocert: certificate was probably revoked by Let's Encrypt")
+ }
+ // ensure the leaf corresponds to the private key and matches the certKey type
+ switch pub := leaf.PublicKey.(type) {
+ case *rsa.PublicKey:
+ prv, ok := key.(*rsa.PrivateKey)
+ if !ok {
+ return nil, errors.New("acme/autocert: private key type does not match public key type")
+ }
+ if pub.N.Cmp(prv.N) != 0 {
+ return nil, errors.New("acme/autocert: private key does not match public key")
+ }
+ if !ck.isRSA && !ck.isToken {
+ return nil, errors.New("acme/autocert: key type does not match expected value")
+ }
+ case *ecdsa.PublicKey:
+ prv, ok := key.(*ecdsa.PrivateKey)
+ if !ok {
+ return nil, errors.New("acme/autocert: private key type does not match public key type")
+ }
+ if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 {
+ return nil, errors.New("acme/autocert: private key does not match public key")
+ }
+ if ck.isRSA && !ck.isToken {
+ return nil, errors.New("acme/autocert: key type does not match expected value")
+ }
+ default:
+ return nil, errors.New("acme/autocert: unknown public key algorithm")
+ }
+ return leaf, nil
+}
+
+// https://community.letsencrypt.org/t/2022-01-25-issue-with-tls-alpn-01-validation-method/170450
+var letsEncryptFixDeployTime = time.Date(2022, time.January, 26, 00, 48, 0, 0, time.UTC)
+
+// isRevokedLetsEncrypt returns whether the certificate is likely to be part of
+// a batch of certificates revoked by Let's Encrypt in January 2022. This check
+// can be safely removed from May 2022.
+func isRevokedLetsEncrypt(cert *x509.Certificate) bool {
+ O := cert.Issuer.Organization
+ return len(O) == 1 && O[0] == "Let's Encrypt" &&
+ cert.NotBefore.Before(letsEncryptFixDeployTime)
+}
+
+type lockedMathRand struct {
+ sync.Mutex
+ rnd *mathrand.Rand
+}
+
+func (r *lockedMathRand) int63n(max int64) int64 {
+ r.Lock()
+ n := r.rnd.Int63n(max)
+ r.Unlock()
+ return n
+}
+
+// For easier testing.
+var (
+ // Called when a state is removed.
+ testDidRemoveState = func(certKey) {}
+)
diff --git a/local_crypto_patch/contents/acme/autocert/autocert_test.go b/local_crypto_patch/contents/acme/autocert/autocert_test.go
new file mode 100644
index 0000000000..d9f19c2612
--- /dev/null
+++ b/local_crypto_patch/contents/acme/autocert/autocert_test.go
@@ -0,0 +1,996 @@
+// 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 autocert
+
+import (
+ "bytes"
+ "context"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "fmt"
+ "io"
+ "math/big"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/acme"
+ "golang.org/x/crypto/acme/autocert/internal/acmetest"
+)
+
+var (
+ exampleDomain = "example.org"
+ exampleCertKey = certKey{domain: exampleDomain}
+ exampleCertKeyRSA = certKey{domain: exampleDomain, isRSA: true}
+)
+
+type memCache struct {
+ t *testing.T
+ mu sync.Mutex
+ keyData map[string][]byte
+}
+
+func (m *memCache) Get(ctx context.Context, key string) ([]byte, error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ v, ok := m.keyData[key]
+ if !ok {
+ return nil, ErrCacheMiss
+ }
+ return v, nil
+}
+
+// filenameSafe returns whether all characters in s are printable ASCII
+// and safe to use in a filename on most filesystems.
+func filenameSafe(s string) bool {
+ for _, c := range s {
+ if c < 0x20 || c > 0x7E {
+ return false
+ }
+ switch c {
+ case '\\', '/', ':', '*', '?', '"', '<', '>', '|':
+ return false
+ }
+ }
+ return true
+}
+
+func (m *memCache) Put(ctx context.Context, key string, data []byte) error {
+ if !filenameSafe(key) {
+ m.t.Errorf("invalid characters in cache key %q", key)
+ }
+
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ m.keyData[key] = data
+ return nil
+}
+
+func (m *memCache) Delete(ctx context.Context, key string) error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ delete(m.keyData, key)
+ return nil
+}
+
+func newMemCache(t *testing.T) *memCache {
+ return &memCache{
+ t: t,
+ keyData: make(map[string][]byte),
+ }
+}
+
+func (m *memCache) numCerts() int {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ res := 0
+ for key := range m.keyData {
+ if strings.HasSuffix(key, "+token") ||
+ strings.HasSuffix(key, "+key") ||
+ strings.HasSuffix(key, "+http-01") {
+ continue
+ }
+ res++
+ }
+ return res
+}
+
+func dummyCert(pub interface{}, san ...string) ([]byte, error) {
+ return dateDummyCert(pub, time.Now(), time.Now().Add(90*24*time.Hour), san...)
+}
+
+func dateDummyCert(pub interface{}, start, end time.Time, san ...string) ([]byte, error) {
+ // use EC key to run faster on 386
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ return nil, err
+ }
+ t := &x509.Certificate{
+ SerialNumber: randomSerial(),
+ NotBefore: start,
+ NotAfter: end,
+ BasicConstraintsValid: true,
+ KeyUsage: x509.KeyUsageKeyEncipherment,
+ DNSNames: san,
+ }
+ if pub == nil {
+ pub = &key.PublicKey
+ }
+ return x509.CreateCertificate(rand.Reader, t, t, pub, key)
+}
+
+func randomSerial() *big.Int {
+ serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 32))
+ if err != nil {
+ panic(err)
+ }
+ return serial
+}
+
+type algorithmSupport int
+
+const (
+ algRSA algorithmSupport = iota
+ algECDSA
+)
+
+func clientHelloInfo(sni string, alg algorithmSupport) *tls.ClientHelloInfo {
+ hello := &tls.ClientHelloInfo{
+ ServerName: sni,
+ CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
+ }
+ if alg == algECDSA {
+ hello.CipherSuites = append(hello.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
+ }
+ return hello
+}
+
+func testManager(t *testing.T) *Manager {
+ man := &Manager{
+ Prompt: AcceptTOS,
+ Cache: newMemCache(t),
+ }
+ t.Cleanup(man.stopRenew)
+ return man
+}
+
+func TestGetCertificate(t *testing.T) {
+ tests := []struct {
+ name string
+ hello *tls.ClientHelloInfo
+ domain string
+ expectError string
+ prepare func(t *testing.T, man *Manager, s *acmetest.CAServer)
+ verify func(t *testing.T, man *Manager, leaf *x509.Certificate)
+ disableALPN bool
+ disableHTTP bool
+ }{
+ {
+ name: "ALPN",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ disableHTTP: true,
+ },
+ {
+ name: "HTTP",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ disableALPN: true,
+ },
+ {
+ name: "nilPrompt",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ man.Prompt = nil
+ },
+ expectError: "missing Manager.Prompt",
+ },
+ {
+ name: "trailingDot",
+ hello: clientHelloInfo("example.org.", algECDSA),
+ domain: "example.org",
+ },
+ {
+ name: "unicodeIDN",
+ hello: clientHelloInfo("éé.com", algECDSA),
+ domain: "xn--9caa.com",
+ },
+ {
+ name: "unicodeIDN/mixedCase",
+ hello: clientHelloInfo("éÉ.com", algECDSA),
+ domain: "xn--9caa.com",
+ },
+ {
+ name: "upperCase",
+ hello: clientHelloInfo("EXAMPLE.ORG", algECDSA),
+ domain: "example.org",
+ },
+ {
+ name: "goodCache",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ // Make a valid cert and cache it.
+ c := s.Start().LeafCert(exampleDomain, "ECDSA",
+ // Use a time before the Let's Encrypt revocation cutoff to also test
+ // that non-Let's Encrypt certificates are not renewed.
+ time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC),
+ time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
+ )
+ if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+ t.Fatalf("man.cachePut: %v", err)
+ }
+ },
+ // Break the server to check that the cache is used.
+ disableALPN: true, disableHTTP: true,
+ },
+ {
+ name: "expiredCache",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ // Make an expired cert and cache it.
+ c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now().Add(-10*time.Minute), time.Now().Add(-5*time.Minute))
+ if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+ t.Fatalf("man.cachePut: %v", err)
+ }
+ },
+ },
+ {
+ name: "forceRSA",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ man.ForceRSA = true
+ },
+ verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+ if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok {
+ t.Errorf("leaf.PublicKey is %T; want *ecdsa.PublicKey", leaf.PublicKey)
+ }
+ },
+ },
+ {
+ name: "goodLetsEncrypt",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ // Make a valid certificate issued after the TLS-ALPN-01
+ // revocation window and cache it.
+ s.IssuerName(pkix.Name{Country: []string{"US"},
+ Organization: []string{"Let's Encrypt"}, CommonName: "R3"})
+ c := s.Start().LeafCert(exampleDomain, "ECDSA",
+ time.Date(2022, time.January, 26, 12, 0, 0, 0, time.UTC),
+ time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
+ )
+ if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+ t.Fatalf("man.cachePut: %v", err)
+ }
+ },
+ // Break the server to check that the cache is used.
+ disableALPN: true, disableHTTP: true,
+ },
+ {
+ name: "revokedLetsEncrypt",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ // Make a certificate issued during the TLS-ALPN-01
+ // revocation window and cache it.
+ s.IssuerName(pkix.Name{Country: []string{"US"},
+ Organization: []string{"Let's Encrypt"}, CommonName: "R3"})
+ c := s.Start().LeafCert(exampleDomain, "ECDSA",
+ time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC),
+ time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC),
+ )
+ if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+ t.Fatalf("man.cachePut: %v", err)
+ }
+ },
+ verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+ if leaf.NotBefore.Before(time.Now().Add(-10 * time.Minute)) {
+ t.Error("certificate was not reissued")
+ }
+ },
+ },
+ {
+ // TestGetCertificate/tokenCache tests the fallback of token
+ // certificate fetches to cache when Manager.certTokens misses.
+ name: "tokenCacheALPN",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ // Make a separate manager with a shared cache, simulating
+ // separate nodes that serve requests for the same domain.
+ man2 := testManager(t)
+ man2.Cache = man.Cache
+ // Redirect the verification request to man2, although the
+ // client request will hit man, testing that they can complete a
+ // verification by communicating through the cache.
+ s.ResolveGetCertificate("example.org", man2.GetCertificate)
+ },
+ // Drop the default verification paths.
+ disableALPN: true,
+ },
+ {
+ name: "tokenCacheHTTP",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ man2 := testManager(t)
+ man2.Cache = man.Cache
+ s.ResolveHandler("example.org", man2.HTTPHandler(nil))
+ },
+ disableHTTP: true,
+ },
+ {
+ name: "ecdsa",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+ if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok {
+ t.Error("an ECDSA client was served a non-ECDSA certificate")
+ }
+ },
+ },
+ {
+ name: "rsa",
+ hello: clientHelloInfo("example.org", algRSA),
+ domain: "example.org",
+ verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+ if _, ok := leaf.PublicKey.(*rsa.PublicKey); !ok {
+ t.Error("an RSA client was served a non-RSA certificate")
+ }
+ },
+ },
+ {
+ name: "wrongCacheKeyType",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ // Make an RSA cert and cache it without suffix.
+ c := s.Start().LeafCert(exampleDomain, "RSA", time.Now(), time.Now().Add(90*24*time.Hour))
+ if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+ t.Fatalf("man.cachePut: %v", err)
+ }
+ },
+ verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) {
+ // The RSA cached cert should be silently ignored and replaced.
+ if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok {
+ t.Error("an ECDSA client was served a non-ECDSA certificate")
+ }
+ if numCerts := man.Cache.(*memCache).numCerts(); numCerts != 1 {
+ t.Errorf("found %d certificates in cache; want %d", numCerts, 1)
+ }
+ },
+ },
+ {
+ name: "almostExpiredCache",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ man.RenewBefore = 24 * time.Hour
+ // Cache an almost expired cert.
+ c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now(), time.Now().Add(10*time.Minute))
+ if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+ t.Fatalf("man.cachePut: %v", err)
+ }
+ },
+ },
+ {
+ name: "provideExternalAuth",
+ hello: clientHelloInfo("example.org", algECDSA),
+ domain: "example.org",
+ prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) {
+ s.ExternalAccountRequired()
+
+ man.ExternalAccountBinding = &acme.ExternalAccountBinding{
+ KID: "test-key",
+ Key: make([]byte, 32),
+ }
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ man := testManager(t)
+ s := acmetest.NewCAServer(t)
+ if !tt.disableALPN {
+ s.ResolveGetCertificate(tt.domain, man.GetCertificate)
+ }
+ if !tt.disableHTTP {
+ s.ResolveHandler(tt.domain, man.HTTPHandler(nil))
+ }
+
+ if tt.prepare != nil {
+ tt.prepare(t, man, s)
+ }
+
+ s.Start()
+
+ man.Client = &acme.Client{DirectoryURL: s.URL()}
+
+ tlscert, err := man.GetCertificate(tt.hello)
+ if tt.expectError != "" {
+ if err == nil {
+ t.Fatal("expected error, got certificate")
+ }
+ if !strings.Contains(err.Error(), tt.expectError) {
+ t.Errorf("got %q, expected %q", err, tt.expectError)
+ }
+ return
+ }
+ if err != nil {
+ t.Fatalf("man.GetCertificate: %v", err)
+ }
+
+ leaf, err := x509.ParseCertificate(tlscert.Certificate[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ opts := x509.VerifyOptions{
+ DNSName: tt.domain,
+ Intermediates: x509.NewCertPool(),
+ Roots: s.Roots(),
+ }
+ for _, cert := range tlscert.Certificate[1:] {
+ c, err := x509.ParseCertificate(cert)
+ if err != nil {
+ t.Fatal(err)
+ }
+ opts.Intermediates.AddCert(c)
+ }
+ if _, err := leaf.Verify(opts); err != nil {
+ t.Error(err)
+ }
+
+ if san := leaf.DNSNames[0]; san != tt.domain {
+ t.Errorf("got SAN %q, expected %q", san, tt.domain)
+ }
+
+ if tt.verify != nil {
+ tt.verify(t, man, leaf)
+ }
+ })
+ }
+}
+
+func TestGetCertificate_failedAttempt(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusBadRequest)
+ }))
+ defer ts.Close()
+
+ d := createCertRetryAfter
+ f := testDidRemoveState
+ defer func() {
+ createCertRetryAfter = d
+ testDidRemoveState = f
+ }()
+ createCertRetryAfter = 0
+ done := make(chan struct{})
+ testDidRemoveState = func(ck certKey) {
+ if ck != exampleCertKey {
+ t.Errorf("testDidRemoveState: domain = %v; want %v", ck, exampleCertKey)
+ }
+ close(done)
+ }
+
+ man := &Manager{
+ Prompt: AcceptTOS,
+ Client: &acme.Client{
+ DirectoryURL: ts.URL,
+ },
+ }
+ defer man.stopRenew()
+ hello := clientHelloInfo(exampleDomain, algECDSA)
+ if _, err := man.GetCertificate(hello); err == nil {
+ t.Error("GetCertificate: err is nil")
+ }
+
+ <-done
+ man.stateMu.Lock()
+ defer man.stateMu.Unlock()
+ if v, exist := man.state[exampleCertKey]; exist {
+ t.Errorf("state exists for %v: %+v", exampleCertKey, v)
+ }
+}
+
+func TestRevokeFailedAuthz(t *testing.T) {
+ ca := acmetest.NewCAServer(t)
+ // Make the authz unfulfillable on the client side, so it will be left
+ // pending at the end of the verification attempt.
+ ca.ChallengeTypes("fake-01", "fake-02")
+ ca.Start()
+
+ m := testManager(t)
+ m.Client = &acme.Client{DirectoryURL: ca.URL()}
+
+ _, err := m.GetCertificate(clientHelloInfo("example.org", algECDSA))
+ if err == nil {
+ t.Fatal("expected GetCertificate to fail")
+ }
+
+ logTicker := time.NewTicker(3 * time.Second)
+ defer logTicker.Stop()
+ for {
+ authz, err := m.Client.GetAuthorization(context.Background(), ca.URL()+"/authz/0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if authz.Status == acme.StatusDeactivated {
+ return
+ }
+
+ select {
+ case <-logTicker.C:
+ t.Logf("still waiting on revocations")
+ default:
+ }
+ time.Sleep(50 * time.Millisecond)
+ }
+}
+
+func TestHTTPHandlerDefaultFallback(t *testing.T) {
+ tt := []struct {
+ method, url string
+ wantCode int
+ wantLocation string
+ }{
+ {"GET", "http://example.org", 302, "https://example.org/"},
+ {"GET", "http://example.org/foo", 302, "https://example.org/foo"},
+ {"GET", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
+ {"GET", "http://example.org/?a=b", 302, "https://example.org/?a=b"},
+ {"GET", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"},
+ {"GET", "http://example.org:80/foo?a=b", 302, "https://example.org:443/foo?a=b"},
+ {"GET", "http://example.org:80/foo%20bar", 302, "https://example.org:443/foo%20bar"},
+ {"GET", "http://[2602:d1:abcd::c60a]:1234", 302, "https://[2602:d1:abcd::c60a]:443/"},
+ {"GET", "http://[2602:d1:abcd::c60a]", 302, "https://[2602:d1:abcd::c60a]/"},
+ {"GET", "http://[2602:d1:abcd::c60a]/foo?a=b", 302, "https://[2602:d1:abcd::c60a]/foo?a=b"},
+ {"HEAD", "http://example.org", 302, "https://example.org/"},
+ {"HEAD", "http://example.org/foo", 302, "https://example.org/foo"},
+ {"HEAD", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"},
+ {"HEAD", "http://example.org/?a=b", 302, "https://example.org/?a=b"},
+ {"HEAD", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"},
+ {"POST", "http://example.org", 400, ""},
+ {"PUT", "http://example.org", 400, ""},
+ {"GET", "http://example.org/.well-known/acme-challenge/x", 404, ""},
+ }
+ var m Manager
+ h := m.HTTPHandler(nil)
+ for i, test := range tt {
+ r := httptest.NewRequest(test.method, test.url, nil)
+ w := httptest.NewRecorder()
+ h.ServeHTTP(w, r)
+ if w.Code != test.wantCode {
+ t.Errorf("%d: w.Code = %d; want %d", i, w.Code, test.wantCode)
+ t.Errorf("%d: body: %s", i, w.Body.Bytes())
+ }
+ if v := w.Header().Get("Location"); v != test.wantLocation {
+ t.Errorf("%d: Location = %q; want %q", i, v, test.wantLocation)
+ }
+ }
+}
+
+func TestAccountKeyCache(t *testing.T) {
+ m := Manager{Cache: newMemCache(t)}
+ ctx := context.Background()
+ k1, err := m.accountKey(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ k2, err := m.accountKey(ctx)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(k1, k2) {
+ t.Errorf("account keys don't match: k1 = %#v; k2 = %#v", k1, k2)
+ }
+}
+
+func TestCache(t *testing.T) {
+ ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cert, err := dummyCert(ecdsaKey.Public(), exampleDomain)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ecdsaCert := &tls.Certificate{
+ Certificate: [][]byte{cert},
+ PrivateKey: ecdsaKey,
+ }
+
+ rsaKey, err := rsa.GenerateKey(rand.Reader, 1024)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cert, err = dummyCert(rsaKey.Public(), exampleDomain)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rsaCert := &tls.Certificate{
+ Certificate: [][]byte{cert},
+ PrivateKey: rsaKey,
+ }
+
+ man := &Manager{Cache: newMemCache(t)}
+ defer man.stopRenew()
+ ctx := context.Background()
+
+ if err := man.cachePut(ctx, exampleCertKey, ecdsaCert); err != nil {
+ t.Fatalf("man.cachePut: %v", err)
+ }
+ if err := man.cachePut(ctx, exampleCertKeyRSA, rsaCert); err != nil {
+ t.Fatalf("man.cachePut: %v", err)
+ }
+
+ res, err := man.cacheGet(ctx, exampleCertKey)
+ if err != nil {
+ t.Fatalf("man.cacheGet: %v", err)
+ }
+ if res == nil || !bytes.Equal(res.Certificate[0], ecdsaCert.Certificate[0]) {
+ t.Errorf("man.cacheGet = %+v; want %+v", res, ecdsaCert)
+ }
+
+ res, err = man.cacheGet(ctx, exampleCertKeyRSA)
+ if err != nil {
+ t.Fatalf("man.cacheGet: %v", err)
+ }
+ if res == nil || !bytes.Equal(res.Certificate[0], rsaCert.Certificate[0]) {
+ t.Errorf("man.cacheGet = %+v; want %+v", res, rsaCert)
+ }
+}
+
+func TestHostWhitelist(t *testing.T) {
+ policy := HostWhitelist("example.com", "EXAMPLE.ORG", "*.example.net", "éÉ.com")
+ tt := []struct {
+ host string
+ allow bool
+ }{
+ {"example.com", true},
+ {"example.org", true},
+ {"xn--9caa.com", true}, // éé.com
+ {"one.example.com", false},
+ {"two.example.org", false},
+ {"three.example.net", false},
+ {"dummy", false},
+ }
+ for i, test := range tt {
+ err := policy(nil, test.host)
+ if err != nil && test.allow {
+ t.Errorf("%d: policy(%q): %v; want nil", i, test.host, err)
+ }
+ if err == nil && !test.allow {
+ t.Errorf("%d: policy(%q): nil; want an error", i, test.host)
+ }
+ }
+}
+
+func TestValidCert(t *testing.T) {
+ key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ key3, err := rsa.GenerateKey(rand.Reader, 1024)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cert1, err := dummyCert(key1.Public(), "example.org")
+ if err != nil {
+ t.Fatal(err)
+ }
+ cert2, err := dummyCert(key2.Public(), "example.org")
+ if err != nil {
+ t.Fatal(err)
+ }
+ cert3, err := dummyCert(key3.Public(), "example.org")
+ if err != nil {
+ t.Fatal(err)
+ }
+ now := time.Now()
+ early, err := dateDummyCert(key1.Public(), now.Add(time.Hour), now.Add(2*time.Hour), "example.org")
+ if err != nil {
+ t.Fatal(err)
+ }
+ expired, err := dateDummyCert(key1.Public(), now.Add(-2*time.Hour), now.Add(-time.Hour), "example.org")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tt := []struct {
+ ck certKey
+ key crypto.Signer
+ cert [][]byte
+ ok bool
+ }{
+ {certKey{domain: "example.org"}, key1, [][]byte{cert1}, true},
+ {certKey{domain: "example.org", isRSA: true}, key3, [][]byte{cert3}, true},
+ {certKey{domain: "example.org"}, key1, [][]byte{cert1, cert2, cert3}, true},
+ {certKey{domain: "example.org"}, key1, [][]byte{cert1, {1}}, false},
+ {certKey{domain: "example.org"}, key1, [][]byte{{1}}, false},
+ {certKey{domain: "example.org"}, key1, [][]byte{cert2}, false},
+ {certKey{domain: "example.org"}, key2, [][]byte{cert1}, false},
+ {certKey{domain: "example.org"}, key1, [][]byte{cert3}, false},
+ {certKey{domain: "example.org"}, key3, [][]byte{cert1}, false},
+ {certKey{domain: "example.net"}, key1, [][]byte{cert1}, false},
+ {certKey{domain: "example.org"}, key1, [][]byte{early}, false},
+ {certKey{domain: "example.org"}, key1, [][]byte{expired}, false},
+ {certKey{domain: "example.org", isRSA: true}, key1, [][]byte{cert1}, false},
+ {certKey{domain: "example.org"}, key3, [][]byte{cert3}, false},
+ }
+ for i, test := range tt {
+ leaf, err := validCert(test.ck, test.cert, test.key, now)
+ if err != nil && test.ok {
+ t.Errorf("%d: err = %v", i, err)
+ }
+ if err == nil && !test.ok {
+ t.Errorf("%d: err is nil", i)
+ }
+ if err == nil && test.ok && leaf == nil {
+ t.Errorf("%d: leaf is nil", i)
+ }
+ }
+}
+
+type cacheGetFunc func(ctx context.Context, key string) ([]byte, error)
+
+func (f cacheGetFunc) Get(ctx context.Context, key string) ([]byte, error) {
+ return f(ctx, key)
+}
+
+func (f cacheGetFunc) Put(ctx context.Context, key string, data []byte) error {
+ return fmt.Errorf("unsupported Put of %q = %q", key, data)
+}
+
+func (f cacheGetFunc) Delete(ctx context.Context, key string) error {
+ return fmt.Errorf("unsupported Delete of %q", key)
+}
+
+func TestManagerGetCertificateBogusSNI(t *testing.T) {
+ m := Manager{
+ Prompt: AcceptTOS,
+ Cache: cacheGetFunc(func(ctx context.Context, key string) ([]byte, error) {
+ return nil, fmt.Errorf("cache.Get of %s", key)
+ }),
+ }
+ tests := []struct {
+ name string
+ wantErr string
+ }{
+ {"foo.com", "cache.Get of foo.com"},
+ {"foo.com.", "cache.Get of foo.com"},
+ {`a\b.com`, "acme/autocert: server name contains invalid character"},
+ {`a/b.com`, "acme/autocert: server name contains invalid character"},
+ {"", "acme/autocert: missing server name"},
+ {"foo", "acme/autocert: server name component count invalid"},
+ {".foo", "acme/autocert: server name component count invalid"},
+ {"foo.", "acme/autocert: server name component count invalid"},
+ {"fo.o", "cache.Get of fo.o"},
+ }
+ for _, tt := range tests {
+ _, err := m.GetCertificate(clientHelloInfo(tt.name, algECDSA))
+ got := fmt.Sprint(err)
+ if got != tt.wantErr {
+ t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr)
+ }
+ }
+}
+
+func TestCertRequest(t *testing.T) {
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // An extension from RFC7633. Any will do.
+ ext := pkix.Extension{
+ Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1},
+ Value: []byte("dummy"),
+ }
+ b, err := certRequest(key, "example.org", []pkix.Extension{ext})
+ if err != nil {
+ t.Fatalf("certRequest: %v", err)
+ }
+ r, err := x509.ParseCertificateRequest(b)
+ if err != nil {
+ t.Fatalf("ParseCertificateRequest: %v", err)
+ }
+ var found bool
+ for _, v := range r.Extensions {
+ if v.Id.Equal(ext.Id) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Errorf("want %v in Extensions: %v", ext, r.Extensions)
+ }
+}
+
+func TestSupportsECDSA(t *testing.T) {
+ tests := []struct {
+ CipherSuites []uint16
+ SignatureSchemes []tls.SignatureScheme
+ SupportedCurves []tls.CurveID
+ ecdsaOk bool
+ }{
+ {[]uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ }, nil, nil, false},
+ {[]uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ }, nil, nil, true},
+
+ // SignatureSchemes limits, not extends, CipherSuites
+ {[]uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ }, []tls.SignatureScheme{
+ tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
+ }, nil, false},
+ {[]uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ }, []tls.SignatureScheme{
+ tls.PKCS1WithSHA256,
+ }, nil, false},
+ {[]uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ }, []tls.SignatureScheme{
+ tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
+ }, nil, true},
+
+ {[]uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ }, []tls.SignatureScheme{
+ tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
+ }, []tls.CurveID{
+ tls.CurveP521,
+ }, false},
+ {[]uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ }, []tls.SignatureScheme{
+ tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256,
+ }, []tls.CurveID{
+ tls.CurveP256,
+ tls.CurveP521,
+ }, true},
+ }
+ for i, tt := range tests {
+ result := supportsECDSA(&tls.ClientHelloInfo{
+ CipherSuites: tt.CipherSuites,
+ SignatureSchemes: tt.SignatureSchemes,
+ SupportedCurves: tt.SupportedCurves,
+ })
+ if result != tt.ecdsaOk {
+ t.Errorf("%d: supportsECDSA = %v; want %v", i, result, tt.ecdsaOk)
+ }
+ }
+}
+
+func TestEndToEndALPN(t *testing.T) {
+ const domain = "example.org"
+
+ // ACME CA server
+ ca := acmetest.NewCAServer(t).Start()
+
+ // User HTTPS server.
+ m := &Manager{
+ Prompt: AcceptTOS,
+ Client: &acme.Client{DirectoryURL: ca.URL()},
+ }
+ us := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("OK"))
+ }))
+ us.TLS = &tls.Config{
+ NextProtos: []string{"http/1.1", acme.ALPNProto},
+ GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ cert, err := m.GetCertificate(hello)
+ if err != nil {
+ t.Errorf("m.GetCertificate: %v", err)
+ }
+ return cert, err
+ },
+ }
+ us.StartTLS()
+ defer us.Close()
+ // In TLS-ALPN challenge verification, CA connects to the domain:443 in question.
+ // Because the domain won't resolve in tests, we need to tell the CA
+ // where to dial to instead.
+ ca.Resolve(domain, strings.TrimPrefix(us.URL, "https://"))
+
+ // A client visiting user's HTTPS server.
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: ca.Roots(),
+ ServerName: domain,
+ },
+ }
+ client := &http.Client{Transport: tr}
+ res, err := client.Get(us.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if v := string(b); v != "OK" {
+ t.Errorf("user server response: %q; want 'OK'", v)
+ }
+}
+
+func TestEndToEndHTTP(t *testing.T) {
+ const domain = "example.org"
+
+ // ACME CA server.
+ ca := acmetest.NewCAServer(t).ChallengeTypes("http-01").Start()
+
+ // User HTTP server for the ACME challenge.
+ m := testManager(t)
+ m.Client = &acme.Client{DirectoryURL: ca.URL()}
+ s := httptest.NewServer(m.HTTPHandler(nil))
+ defer s.Close()
+
+ // User HTTPS server.
+ ss := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("OK"))
+ }))
+ ss.TLS = &tls.Config{
+ NextProtos: []string{"http/1.1", acme.ALPNProto},
+ GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ cert, err := m.GetCertificate(hello)
+ if err != nil {
+ t.Errorf("m.GetCertificate: %v", err)
+ }
+ return cert, err
+ },
+ }
+ ss.StartTLS()
+ defer ss.Close()
+
+ // Redirect the CA requests to the HTTP server.
+ ca.Resolve(domain, strings.TrimPrefix(s.URL, "http://"))
+
+ // A client visiting user's HTTPS server.
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: ca.Roots(),
+ ServerName: domain,
+ },
+ }
+ client := &http.Client{Transport: tr}
+ res, err := client.Get(ss.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if v := string(b); v != "OK" {
+ t.Errorf("user server response: %q; want 'OK'", v)
+ }
+}
diff --git a/local_crypto_patch/contents/acme/autocert/cache.go b/local_crypto_patch/contents/acme/autocert/cache.go
new file mode 100644
index 0000000000..758ab12cb2
--- /dev/null
+++ b/local_crypto_patch/contents/acme/autocert/cache.go
@@ -0,0 +1,135 @@
+// 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 autocert
+
+import (
+ "context"
+ "errors"
+ "os"
+ "path/filepath"
+)
+
+// ErrCacheMiss is returned when a certificate is not found in cache.
+var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
+
+// Cache is used by Manager to store and retrieve previously obtained certificates
+// and other account data as opaque blobs.
+//
+// Cache implementations should not rely on the key naming pattern. Keys can
+// include any printable ASCII characters, except the following: \/:*?"<>|
+type Cache interface {
+ // Get returns a certificate data for the specified key.
+ // If there's no such key, Get returns ErrCacheMiss.
+ Get(ctx context.Context, key string) ([]byte, error)
+
+ // Put stores the data in the cache under the specified key.
+ // Underlying implementations may use any data storage format,
+ // as long as the reverse operation, Get, results in the original data.
+ Put(ctx context.Context, key string, data []byte) error
+
+ // Delete removes a certificate data from the cache under the specified key.
+ // If there's no such key in the cache, Delete returns nil.
+ Delete(ctx context.Context, key string) error
+}
+
+// DirCache implements Cache using a directory on the local filesystem.
+// If the directory does not exist, it will be created with 0700 permissions.
+type DirCache string
+
+// Get reads a certificate data from the specified file name.
+func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
+ name = filepath.Join(string(d), filepath.Clean("/"+name))
+ var (
+ data []byte
+ err error
+ done = make(chan struct{})
+ )
+ go func() {
+ data, err = os.ReadFile(name)
+ close(done)
+ }()
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case <-done:
+ }
+ if os.IsNotExist(err) {
+ return nil, ErrCacheMiss
+ }
+ return data, err
+}
+
+// Put writes the certificate data to the specified file name.
+// The file will be created with 0600 permissions.
+func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
+ if err := os.MkdirAll(string(d), 0700); err != nil {
+ return err
+ }
+
+ done := make(chan struct{})
+ var err error
+ go func() {
+ defer close(done)
+ var tmp string
+ if tmp, err = d.writeTempFile(name, data); err != nil {
+ return
+ }
+ defer os.Remove(tmp)
+ select {
+ case <-ctx.Done():
+ // Don't overwrite the file if the context was canceled.
+ default:
+ newName := filepath.Join(string(d), filepath.Clean("/"+name))
+ err = os.Rename(tmp, newName)
+ }
+ }()
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-done:
+ }
+ return err
+}
+
+// Delete removes the specified file name.
+func (d DirCache) Delete(ctx context.Context, name string) error {
+ name = filepath.Join(string(d), filepath.Clean("/"+name))
+ var (
+ err error
+ done = make(chan struct{})
+ )
+ go func() {
+ err = os.Remove(name)
+ close(done)
+ }()
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-done:
+ }
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ return nil
+}
+
+// writeTempFile writes b to a temporary file, closes the file and returns its path.
+func (d DirCache) writeTempFile(prefix string, b []byte) (name string, reterr error) {
+ // TempFile uses 0600 permissions
+ f, err := os.CreateTemp(string(d), prefix)
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ if reterr != nil {
+ os.Remove(f.Name())
+ }
+ }()
+ if _, err := f.Write(b); err != nil {
+ f.Close()
+ return "", err
+ }
+ return f.Name(), f.Close()
+}
diff --git a/local_crypto_patch/contents/acme/autocert/cache_test.go b/local_crypto_patch/contents/acme/autocert/cache_test.go
new file mode 100644
index 0000000000..582e6b0580
--- /dev/null
+++ b/local_crypto_patch/contents/acme/autocert/cache_test.go
@@ -0,0 +1,66 @@
+// 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 autocert
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+// make sure DirCache satisfies Cache interface
+var _ Cache = DirCache("/")
+
+func TestDirCache(t *testing.T) {
+ dir, err := os.MkdirTemp("", "autocert")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+ dir = filepath.Join(dir, "certs") // a nonexistent dir
+ cache := DirCache(dir)
+ ctx := context.Background()
+
+ // test cache miss
+ if _, err := cache.Get(ctx, "nonexistent"); err != ErrCacheMiss {
+ t.Errorf("get: %v; want ErrCacheMiss", err)
+ }
+
+ // test put/get
+ b1 := []byte{1}
+ if err := cache.Put(ctx, "dummy", b1); err != nil {
+ t.Fatalf("put: %v", err)
+ }
+ b2, err := cache.Get(ctx, "dummy")
+ if err != nil {
+ t.Fatalf("get: %v", err)
+ }
+ if !reflect.DeepEqual(b1, b2) {
+ t.Errorf("b1 = %v; want %v", b1, b2)
+ }
+ name := filepath.Join(dir, "dummy")
+ if _, err := os.Stat(name); err != nil {
+ t.Error(err)
+ }
+
+ // test put deletes temp file
+ tmp, err := filepath.Glob(name + "?*")
+ if err != nil {
+ t.Error(err)
+ }
+ if tmp != nil {
+ t.Errorf("temp file exists: %s", tmp)
+ }
+
+ // test delete
+ if err := cache.Delete(ctx, "dummy"); err != nil {
+ t.Fatalf("delete: %v", err)
+ }
+ if _, err := cache.Get(ctx, "dummy"); err != ErrCacheMiss {
+ t.Errorf("get: %v; want ErrCacheMiss", err)
+ }
+}
diff --git a/local_crypto_patch/contents/acme/autocert/example_test.go b/local_crypto_patch/contents/acme/autocert/example_test.go
new file mode 100644
index 0000000000..6c7458b0d5
--- /dev/null
+++ b/local_crypto_patch/contents/acme/autocert/example_test.go
@@ -0,0 +1,35 @@
+// 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 autocert_test
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+
+ "golang.org/x/crypto/acme/autocert"
+)
+
+func ExampleNewListener() {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Hello, TLS user! Your config: %+v", r.TLS)
+ })
+ log.Fatal(http.Serve(autocert.NewListener("example.com"), mux))
+}
+
+func ExampleManager() {
+ m := &autocert.Manager{
+ Cache: autocert.DirCache("secret-dir"),
+ Prompt: autocert.AcceptTOS,
+ Email: "example@example.org",
+ HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
+ }
+ s := &http.Server{
+ Addr: ":https",
+ TLSConfig: m.TLSConfig(),
+ }
+ s.ListenAndServeTLS("", "")
+}
diff --git a/local_crypto_patch/contents/acme/autocert/internal/acmetest/ca.go b/local_crypto_patch/contents/acme/autocert/internal/acmetest/ca.go
new file mode 100644
index 0000000000..c80a81ca77
--- /dev/null
+++ b/local_crypto_patch/contents/acme/autocert/internal/acmetest/ca.go
@@ -0,0 +1,786 @@
+// 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 acmetest provides types for testing acme and autocert packages.
+//
+// TODO: Consider moving this to x/crypto/acme/internal/acmetest for acme tests as well.
+package acmetest
+
+import (
+ "context"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "fmt"
+ "io"
+ "math/big"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "path"
+ "strconv"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/acme"
+)
+
+// CAServer is a simple test server which implements ACME spec bits needed for testing.
+type CAServer struct {
+ rootKey crypto.Signer
+ rootCert []byte // DER encoding
+ rootTemplate *x509.Certificate
+
+ t *testing.T
+ server *httptest.Server
+ issuer pkix.Name
+ challengeTypes []string
+ url string
+ roots *x509.CertPool
+ eabRequired bool
+
+ mu sync.Mutex
+ certCount int // number of issued certs
+ acctRegistered bool // set once an account has been registered
+ domainAddr map[string]string // domain name to addr:port resolution
+ domainGetCert map[string]getCertificateFunc // domain name to GetCertificate function
+ domainHandler map[string]http.Handler // domain name to Handle function
+ validAuthz map[string]*authorization // valid authz, keyed by domain name
+ authorizations []*authorization // all authz, index is used as ID
+ orders []*order // index is used as order ID
+ errors []error // encountered client errors
+}
+
+type getCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
+
+// NewCAServer creates a new ACME test server. The returned CAServer issues
+// certs signed with the CA roots available in the Roots field.
+func NewCAServer(t *testing.T) *CAServer {
+ ca := &CAServer{t: t,
+ challengeTypes: []string{"fake-01", "tls-alpn-01", "http-01"},
+ domainAddr: make(map[string]string),
+ domainGetCert: make(map[string]getCertificateFunc),
+ domainHandler: make(map[string]http.Handler),
+ validAuthz: make(map[string]*authorization),
+ }
+
+ ca.server = httptest.NewUnstartedServer(http.HandlerFunc(ca.handle))
+
+ r, err := rand.Int(rand.Reader, big.NewInt(1000000))
+ if err != nil {
+ panic(fmt.Sprintf("rand.Int: %v", err))
+ }
+ ca.issuer = pkix.Name{
+ Organization: []string{"Test Acme Co"},
+ CommonName: "Root CA " + r.String(),
+ }
+
+ return ca
+}
+
+func (ca *CAServer) generateRoot() {
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ panic(fmt.Sprintf("ecdsa.GenerateKey: %v", err))
+ }
+ tmpl := &x509.Certificate{
+ SerialNumber: big.NewInt(1),
+ Subject: ca.issuer,
+ NotBefore: time.Now(),
+ NotAfter: time.Now().Add(365 * 24 * time.Hour),
+ KeyUsage: x509.KeyUsageCertSign,
+ BasicConstraintsValid: true,
+ IsCA: true,
+ }
+ der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
+ if err != nil {
+ panic(fmt.Sprintf("x509.CreateCertificate: %v", err))
+ }
+ cert, err := x509.ParseCertificate(der)
+ if err != nil {
+ panic(fmt.Sprintf("x509.ParseCertificate: %v", err))
+ }
+ ca.roots = x509.NewCertPool()
+ ca.roots.AddCert(cert)
+ ca.rootKey = key
+ ca.rootCert = der
+ ca.rootTemplate = tmpl
+}
+
+// IssuerName sets the name of the issuing CA.
+func (ca *CAServer) IssuerName(name pkix.Name) *CAServer {
+ if ca.url != "" {
+ panic("IssuerName must be called before Start")
+ }
+ ca.issuer = name
+ return ca
+}
+
+// ChallengeTypes sets the supported challenge types.
+func (ca *CAServer) ChallengeTypes(types ...string) *CAServer {
+ if ca.url != "" {
+ panic("ChallengeTypes must be called before Start")
+ }
+ ca.challengeTypes = types
+ return ca
+}
+
+// URL returns the server address, after Start has been called.
+func (ca *CAServer) URL() string {
+ if ca.url == "" {
+ panic("URL called before Start")
+ }
+ return ca.url
+}
+
+// Roots returns a pool containing the CA root.
+func (ca *CAServer) Roots() *x509.CertPool {
+ if ca.url == "" {
+ panic("Roots called before Start")
+ }
+ return ca.roots
+}
+
+// ExternalAccountRequired makes an EAB JWS required for account registration.
+func (ca *CAServer) ExternalAccountRequired() *CAServer {
+ if ca.url != "" {
+ panic("ExternalAccountRequired must be called before Start")
+ }
+ ca.eabRequired = true
+ return ca
+}
+
+// Start starts serving requests. The server address becomes available in the
+// URL field.
+func (ca *CAServer) Start() *CAServer {
+ if ca.url == "" {
+ ca.generateRoot()
+ ca.server.Start()
+ ca.t.Cleanup(ca.server.Close)
+ ca.url = ca.server.URL
+ }
+ return ca
+}
+
+func (ca *CAServer) serverURL(format string, arg ...interface{}) string {
+ return ca.server.URL + fmt.Sprintf(format, arg...)
+}
+
+func (ca *CAServer) addr(domain string) (string, bool) {
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ addr, ok := ca.domainAddr[domain]
+ return addr, ok
+}
+
+func (ca *CAServer) getCert(domain string) (getCertificateFunc, bool) {
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ f, ok := ca.domainGetCert[domain]
+ return f, ok
+}
+
+func (ca *CAServer) getHandler(domain string) (http.Handler, bool) {
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ h, ok := ca.domainHandler[domain]
+ return h, ok
+}
+
+func (ca *CAServer) httpErrorf(w http.ResponseWriter, code int, format string, a ...interface{}) {
+ s := fmt.Sprintf(format, a...)
+ ca.t.Errorf(format, a...)
+ http.Error(w, s, code)
+}
+
+// Resolve adds a domain to address resolution for the ca to dial to
+// when validating challenges for the domain authorization.
+func (ca *CAServer) Resolve(domain, addr string) {
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ ca.domainAddr[domain] = addr
+}
+
+// ResolveGetCertificate redirects TLS connections for domain to f when
+// validating challenges for the domain authorization.
+func (ca *CAServer) ResolveGetCertificate(domain string, f getCertificateFunc) {
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ ca.domainGetCert[domain] = f
+}
+
+// ResolveHandler redirects HTTP requests for domain to f when
+// validating challenges for the domain authorization.
+func (ca *CAServer) ResolveHandler(domain string, h http.Handler) {
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ ca.domainHandler[domain] = h
+}
+
+type discovery struct {
+ NewNonce string `json:"newNonce"`
+ NewAccount string `json:"newAccount"`
+ NewOrder string `json:"newOrder"`
+ NewAuthz string `json:"newAuthz"`
+
+ Meta discoveryMeta `json:"meta,omitempty"`
+}
+
+type discoveryMeta struct {
+ Terms string `json:"termsOfService,omitempty"`
+ ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"`
+}
+
+type challenge struct {
+ URI string `json:"uri"`
+ Type string `json:"type"`
+ Token string `json:"token"`
+}
+
+type authorization struct {
+ Status string `json:"status"`
+ Challenges []challenge `json:"challenges"`
+
+ domain string
+ id int
+}
+
+type order struct {
+ Status string `json:"status"`
+ AuthzURLs []string `json:"authorizations"`
+ FinalizeURL string `json:"finalize"` // CSR submit URL
+ CertURL string `json:"certificate"` // already issued cert
+
+ leaf []byte // issued cert in DER format
+}
+
+func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) {
+ ca.t.Logf("%s %s", r.Method, r.URL)
+ w.Header().Set("Replay-Nonce", "nonce")
+ // TODO: Verify nonce header for all POST requests.
+
+ switch {
+ default:
+ ca.httpErrorf(w, http.StatusBadRequest, "unrecognized r.URL.Path: %s", r.URL.Path)
+
+ // Discovery request.
+ case r.URL.Path == "/":
+ resp := &discovery{
+ NewNonce: ca.serverURL("/new-nonce"),
+ NewAccount: ca.serverURL("/new-account"),
+ NewOrder: ca.serverURL("/new-order"),
+ Meta: discoveryMeta{
+ Terms: ca.serverURL("/terms"),
+ ExternalAccountRequired: ca.eabRequired,
+ },
+ }
+ if err := json.NewEncoder(w).Encode(resp); err != nil {
+ panic(fmt.Sprintf("discovery response: %v", err))
+ }
+
+ // Nonce requests.
+ case r.URL.Path == "/new-nonce":
+ // Nonce values are always set. Nothing else to do.
+ return
+
+ // Client key registration request.
+ case r.URL.Path == "/new-account":
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ if ca.acctRegistered {
+ ca.httpErrorf(w, http.StatusServiceUnavailable, "multiple accounts are not implemented")
+ return
+ }
+ ca.acctRegistered = true
+
+ var req struct {
+ ExternalAccountBinding json.RawMessage
+ }
+
+ if err := decodePayload(&req, r.Body); err != nil {
+ ca.httpErrorf(w, http.StatusBadRequest, "%v", err)
+ return
+ }
+
+ if ca.eabRequired && len(req.ExternalAccountBinding) == 0 {
+ ca.httpErrorf(w, http.StatusBadRequest, "registration failed: no JWS for EAB")
+ return
+ }
+
+ // TODO: Check the user account key against a ca.accountKeys?
+ w.Header().Set("Location", ca.serverURL("/accounts/1"))
+ w.WriteHeader(http.StatusCreated)
+ w.Write([]byte("{}"))
+
+ // New order request.
+ case r.URL.Path == "/new-order":
+ var req struct {
+ Identifiers []struct{ Value string }
+ }
+ if err := decodePayload(&req, r.Body); err != nil {
+ ca.httpErrorf(w, http.StatusBadRequest, "%v", err)
+ return
+ }
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ o := &order{Status: acme.StatusPending}
+ for _, id := range req.Identifiers {
+ z := ca.authz(id.Value)
+ o.AuthzURLs = append(o.AuthzURLs, ca.serverURL("/authz/%d", z.id))
+ }
+ orderID := len(ca.orders)
+ ca.orders = append(ca.orders, o)
+ w.Header().Set("Location", ca.serverURL("/orders/%d", orderID))
+ w.WriteHeader(http.StatusCreated)
+ if err := json.NewEncoder(w).Encode(o); err != nil {
+ panic(err)
+ }
+
+ // Existing order status requests.
+ case strings.HasPrefix(r.URL.Path, "/orders/"):
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/orders/"))
+ if err != nil {
+ ca.httpErrorf(w, http.StatusBadRequest, "%v", err)
+ return
+ }
+ if err := json.NewEncoder(w).Encode(o); err != nil {
+ panic(err)
+ }
+
+ // Accept challenge requests.
+ case strings.HasPrefix(r.URL.Path, "/challenge/"):
+ parts := strings.Split(r.URL.Path, "/")
+ typ, id := parts[len(parts)-2], parts[len(parts)-1]
+ ca.mu.Lock()
+ supported := false
+ for _, suppTyp := range ca.challengeTypes {
+ if suppTyp == typ {
+ supported = true
+ }
+ }
+ a, err := ca.storedAuthz(id)
+ ca.mu.Unlock()
+ if !supported {
+ ca.httpErrorf(w, http.StatusBadRequest, "unsupported challenge: %v", typ)
+ return
+ }
+ if err != nil {
+ ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: %v", err)
+ return
+ }
+ ca.validateChallenge(a, typ)
+ w.Write([]byte("{}"))
+
+ // Get authorization status requests.
+ case strings.HasPrefix(r.URL.Path, "/authz/"):
+ var req struct{ Status string }
+ decodePayload(&req, r.Body)
+ deactivate := req.Status == "deactivated"
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ authz, err := ca.storedAuthz(strings.TrimPrefix(r.URL.Path, "/authz/"))
+ if err != nil {
+ ca.httpErrorf(w, http.StatusNotFound, "%v", err)
+ return
+ }
+ if deactivate {
+ // Note we don't invalidate authorized orders as we should.
+ authz.Status = "deactivated"
+ ca.t.Logf("authz %d is now %s", authz.id, authz.Status)
+ ca.updatePendingOrders()
+ }
+ if err := json.NewEncoder(w).Encode(authz); err != nil {
+ panic(fmt.Sprintf("encoding authz %d: %v", authz.id, err))
+ }
+
+ // Certificate issuance request.
+ case strings.HasPrefix(r.URL.Path, "/new-cert/"):
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ orderID := strings.TrimPrefix(r.URL.Path, "/new-cert/")
+ o, err := ca.storedOrder(orderID)
+ if err != nil {
+ ca.httpErrorf(w, http.StatusBadRequest, "%v", err)
+ return
+ }
+ if o.Status != acme.StatusReady {
+ ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status)
+ return
+ }
+ // Validate CSR request.
+ var req struct {
+ CSR string `json:"csr"`
+ }
+ decodePayload(&req, r.Body)
+ b, _ := base64.RawURLEncoding.DecodeString(req.CSR)
+ csr, err := x509.ParseCertificateRequest(b)
+ if err != nil {
+ ca.httpErrorf(w, http.StatusBadRequest, "%v", err)
+ return
+ }
+ // Issue the certificate.
+ der, err := ca.leafCert(csr)
+ if err != nil {
+ ca.httpErrorf(w, http.StatusBadRequest, "new-cert response: ca.leafCert: %v", err)
+ return
+ }
+ o.leaf = der
+ o.CertURL = ca.serverURL("/issued-cert/%s", orderID)
+ o.Status = acme.StatusValid
+ if err := json.NewEncoder(w).Encode(o); err != nil {
+ panic(err)
+ }
+
+ // Already issued cert download requests.
+ case strings.HasPrefix(r.URL.Path, "/issued-cert/"):
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/issued-cert/"))
+ if err != nil {
+ ca.httpErrorf(w, http.StatusBadRequest, "%v", err)
+ return
+ }
+ if o.Status != acme.StatusValid {
+ ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status)
+ return
+ }
+ w.Header().Set("Content-Type", "application/pem-certificate-chain")
+ pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: o.leaf})
+ pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: ca.rootCert})
+ }
+}
+
+// storedOrder retrieves a previously created order at index i.
+// It requires ca.mu to be locked.
+func (ca *CAServer) storedOrder(i string) (*order, error) {
+ idx, err := strconv.Atoi(i)
+ if err != nil {
+ return nil, fmt.Errorf("storedOrder: %v", err)
+ }
+ if idx < 0 {
+ return nil, fmt.Errorf("storedOrder: invalid order index %d", idx)
+ }
+ if idx > len(ca.orders)-1 {
+ return nil, fmt.Errorf("storedOrder: no such order %d", idx)
+ }
+
+ ca.updatePendingOrders()
+ return ca.orders[idx], nil
+}
+
+// storedAuthz retrieves a previously created authz at index i.
+// It requires ca.mu to be locked.
+func (ca *CAServer) storedAuthz(i string) (*authorization, error) {
+ idx, err := strconv.Atoi(i)
+ if err != nil {
+ return nil, fmt.Errorf("storedAuthz: %v", err)
+ }
+ if idx < 0 {
+ return nil, fmt.Errorf("storedAuthz: invalid authz index %d", idx)
+ }
+ if idx > len(ca.authorizations)-1 {
+ return nil, fmt.Errorf("storedAuthz: no such authz %d", idx)
+ }
+ return ca.authorizations[idx], nil
+}
+
+// authz returns an existing valid authorization for the identifier or creates a
+// new one. It requires ca.mu to be locked.
+func (ca *CAServer) authz(identifier string) *authorization {
+ authz, ok := ca.validAuthz[identifier]
+ if !ok {
+ authzId := len(ca.authorizations)
+ authz = &authorization{
+ id: authzId,
+ domain: identifier,
+ Status: acme.StatusPending,
+ }
+ for _, typ := range ca.challengeTypes {
+ authz.Challenges = append(authz.Challenges, challenge{
+ Type: typ,
+ URI: ca.serverURL("/challenge/%s/%d", typ, authzId),
+ Token: challengeToken(authz.domain, typ, authzId),
+ })
+ }
+ ca.authorizations = append(ca.authorizations, authz)
+ }
+ return authz
+}
+
+// leafCert issues a new certificate.
+// It requires ca.mu to be locked.
+func (ca *CAServer) leafCert(csr *x509.CertificateRequest) (der []byte, err error) {
+ ca.certCount++ // next leaf cert serial number
+ leaf := &x509.Certificate{
+ SerialNumber: big.NewInt(int64(ca.certCount)),
+ Subject: pkix.Name{Organization: []string{"Test Acme Co"}},
+ NotBefore: time.Now(),
+ NotAfter: time.Now().Add(90 * 24 * time.Hour),
+ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ DNSNames: csr.DNSNames,
+ BasicConstraintsValid: true,
+ }
+ if len(csr.DNSNames) == 0 {
+ leaf.DNSNames = []string{csr.Subject.CommonName}
+ }
+ return x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, csr.PublicKey, ca.rootKey)
+}
+
+// LeafCert issues a leaf certificate.
+func (ca *CAServer) LeafCert(name, keyType string, notBefore, notAfter time.Time) *tls.Certificate {
+ if ca.url == "" {
+ panic("LeafCert called before Start")
+ }
+
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ var pk crypto.Signer
+ switch keyType {
+ case "RSA":
+ var err error
+ pk, err = rsa.GenerateKey(rand.Reader, 1024)
+ if err != nil {
+ ca.t.Fatal(err)
+ }
+ case "ECDSA":
+ var err error
+ pk, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ ca.t.Fatal(err)
+ }
+ default:
+ panic("LeafCert: unknown key type")
+ }
+ ca.certCount++ // next leaf cert serial number
+ leaf := &x509.Certificate{
+ SerialNumber: big.NewInt(int64(ca.certCount)),
+ Subject: pkix.Name{Organization: []string{"Test Acme Co"}},
+ NotBefore: notBefore,
+ NotAfter: notAfter,
+ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ DNSNames: []string{name},
+ BasicConstraintsValid: true,
+ }
+ der, err := x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, pk.Public(), ca.rootKey)
+ if err != nil {
+ ca.t.Fatal(err)
+ }
+ return &tls.Certificate{
+ Certificate: [][]byte{der},
+ PrivateKey: pk,
+ }
+}
+
+func (ca *CAServer) validateChallenge(authz *authorization, typ string) {
+ var err error
+ switch typ {
+ case "tls-alpn-01":
+ err = ca.verifyALPNChallenge(authz)
+ case "http-01":
+ err = ca.verifyHTTPChallenge(authz)
+ default:
+ panic(fmt.Sprintf("validation of %q is not implemented", typ))
+ }
+ ca.mu.Lock()
+ defer ca.mu.Unlock()
+ if err != nil {
+ authz.Status = "invalid"
+ } else {
+ authz.Status = "valid"
+ ca.validAuthz[authz.domain] = authz
+ }
+ ca.t.Logf("validated %q for %q, err: %v", typ, authz.domain, err)
+ ca.t.Logf("authz %d is now %s", authz.id, authz.Status)
+
+ ca.updatePendingOrders()
+}
+
+func (ca *CAServer) updatePendingOrders() {
+ // Update all pending orders.
+ // An order becomes "ready" if all authorizations are "valid".
+ // An order becomes "invalid" if any authorization is "invalid".
+ // Status changes: https://tools.ietf.org/html/rfc8555#section-7.1.6
+ for i, o := range ca.orders {
+ if o.Status != acme.StatusPending {
+ continue
+ }
+
+ countValid, countInvalid := ca.validateAuthzURLs(o.AuthzURLs, i)
+ if countInvalid > 0 {
+ o.Status = acme.StatusInvalid
+ ca.t.Logf("order %d is now invalid", i)
+ continue
+ }
+ if countValid == len(o.AuthzURLs) {
+ o.Status = acme.StatusReady
+ o.FinalizeURL = ca.serverURL("/new-cert/%d", i)
+ ca.t.Logf("order %d is now ready", i)
+ }
+ }
+}
+
+func (ca *CAServer) validateAuthzURLs(urls []string, orderNum int) (countValid, countInvalid int) {
+ for _, zurl := range urls {
+ z, err := ca.storedAuthz(path.Base(zurl))
+ if err != nil {
+ ca.t.Logf("no authz %q for order %d", zurl, orderNum)
+ continue
+ }
+ if z.Status == acme.StatusInvalid {
+ countInvalid++
+ }
+ if z.Status == acme.StatusValid {
+ countValid++
+ }
+ }
+ return countValid, countInvalid
+}
+
+func (ca *CAServer) verifyALPNChallenge(a *authorization) error {
+ const acmeALPNProto = "acme-tls/1"
+
+ addr, haveAddr := ca.addr(a.domain)
+ getCert, haveGetCert := ca.getCert(a.domain)
+ if !haveAddr && !haveGetCert {
+ return fmt.Errorf("no resolution information for %q", a.domain)
+ }
+ if haveAddr && haveGetCert {
+ return fmt.Errorf("overlapping resolution information for %q", a.domain)
+ }
+
+ var crt *x509.Certificate
+ switch {
+ case haveAddr:
+ conn, err := tls.Dial("tcp", addr, &tls.Config{
+ ServerName: a.domain,
+ InsecureSkipVerify: true,
+ NextProtos: []string{acmeALPNProto},
+ MinVersion: tls.VersionTLS12,
+ })
+ if err != nil {
+ return err
+ }
+ if v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto {
+ return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto)
+ }
+ if n := len(conn.ConnectionState().PeerCertificates); n != 1 {
+ return fmt.Errorf("len(PeerCertificates) = %d; want 1", n)
+ }
+ crt = conn.ConnectionState().PeerCertificates[0]
+ case haveGetCert:
+ hello := &tls.ClientHelloInfo{
+ ServerName: a.domain,
+ // TODO: support selecting ECDSA.
+ CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305},
+ SupportedProtos: []string{acme.ALPNProto},
+ SupportedVersions: []uint16{tls.VersionTLS12},
+ }
+ c, err := getCert(hello)
+ if err != nil {
+ return err
+ }
+ crt, err = x509.ParseCertificate(c.Certificate[0])
+ if err != nil {
+ return err
+ }
+ }
+
+ if err := crt.VerifyHostname(a.domain); err != nil {
+ return fmt.Errorf("verifyALPNChallenge: VerifyHostname: %v", err)
+ }
+ // See RFC 8737, Section 6.1.
+ oid := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
+ for _, x := range crt.Extensions {
+ if x.Id.Equal(oid) {
+ // TODO: check the token.
+ return nil
+ }
+ }
+ return fmt.Errorf("verifyTokenCert: no id-pe-acmeIdentifier extension found")
+}
+
+func (ca *CAServer) verifyHTTPChallenge(a *authorization) error {
+ addr, haveAddr := ca.addr(a.domain)
+ handler, haveHandler := ca.getHandler(a.domain)
+ if !haveAddr && !haveHandler {
+ return fmt.Errorf("no resolution information for %q", a.domain)
+ }
+ if haveAddr && haveHandler {
+ return fmt.Errorf("overlapping resolution information for %q", a.domain)
+ }
+
+ token := challengeToken(a.domain, "http-01", a.id)
+ path := "/.well-known/acme-challenge/" + token
+
+ var body string
+ switch {
+ case haveAddr:
+ t := &http.Transport{
+ DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) {
+ return (&net.Dialer{}).DialContext(ctx, network, addr)
+ },
+ }
+ req, err := http.NewRequest("GET", "http://"+a.domain+path, nil)
+ if err != nil {
+ return err
+ }
+ res, err := t.RoundTrip(req)
+ if err != nil {
+ return err
+ }
+ if res.StatusCode != http.StatusOK {
+ return fmt.Errorf("http token: w.Code = %d; want %d", res.StatusCode, http.StatusOK)
+ }
+ b, err := io.ReadAll(res.Body)
+ if err != nil {
+ return err
+ }
+ body = string(b)
+ case haveHandler:
+ r := httptest.NewRequest("GET", path, nil)
+ r.Host = a.domain
+ w := httptest.NewRecorder()
+ handler.ServeHTTP(w, r)
+ if w.Code != http.StatusOK {
+ return fmt.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK)
+ }
+ body = w.Body.String()
+ }
+
+ if !strings.HasPrefix(body, token) {
+ return fmt.Errorf("http token value = %q; want 'token-http-01.' prefix", body)
+ }
+ return nil
+}
+
+func decodePayload(v interface{}, r io.Reader) error {
+ var req struct{ Payload string }
+ if err := json.NewDecoder(r).Decode(&req); err != nil {
+ return err
+ }
+ payload, err := base64.RawURLEncoding.DecodeString(req.Payload)
+ if err != nil {
+ return err
+ }
+ return json.Unmarshal(payload, v)
+}
+
+func challengeToken(domain, challType string, authzID int) string {
+ return fmt.Sprintf("token-%s-%s-%d", domain, challType, authzID)
+}
diff --git a/local_crypto_patch/contents/acme/autocert/listener.go b/local_crypto_patch/contents/acme/autocert/listener.go
new file mode 100644
index 0000000000..460133e0cc
--- /dev/null
+++ b/local_crypto_patch/contents/acme/autocert/listener.go
@@ -0,0 +1,135 @@
+// 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 autocert
+
+import (
+ "crypto/tls"
+ "log"
+ "net"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+// NewListener returns a net.Listener that listens on the standard TLS
+// port (443) on all interfaces and returns *tls.Conn connections with
+// LetsEncrypt certificates for the provided domain or domains.
+//
+// It enables one-line HTTPS servers:
+//
+// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler))
+//
+// NewListener is a convenience function for a common configuration.
+// More complex or custom configurations can use the autocert.Manager
+// type instead.
+//
+// Use of this function implies acceptance of the LetsEncrypt Terms of
+// Service. If domains is not empty, the provided domains are passed
+// to HostWhitelist. If domains is empty, the listener will do
+// LetsEncrypt challenges for any requested domain, which is not
+// recommended.
+//
+// Certificates are cached in a "golang-autocert" directory under an
+// operating system-specific cache or temp directory. This may not
+// be suitable for servers spanning multiple machines.
+//
+// The returned listener uses a *tls.Config that enables HTTP/2, and
+// should only be used with servers that support HTTP/2.
+//
+// The returned Listener also enables TCP keep-alives on the accepted
+// connections. The returned *tls.Conn are returned before their TLS
+// handshake has completed.
+func NewListener(domains ...string) net.Listener {
+ m := &Manager{
+ Prompt: AcceptTOS,
+ }
+ if len(domains) > 0 {
+ m.HostPolicy = HostWhitelist(domains...)
+ }
+ dir := cacheDir()
+ if err := os.MkdirAll(dir, 0700); err != nil {
+ log.Printf("warning: autocert.NewListener not using a cache: %v", err)
+ } else {
+ m.Cache = DirCache(dir)
+ }
+ return m.Listener()
+}
+
+// Listener listens on the standard TLS port (443) on all interfaces
+// and returns a net.Listener returning *tls.Conn connections.
+//
+// The returned listener uses a *tls.Config that enables HTTP/2, and
+// should only be used with servers that support HTTP/2.
+//
+// The returned Listener also enables TCP keep-alives on the accepted
+// connections. The returned *tls.Conn are returned before their TLS
+// handshake has completed.
+//
+// Unlike NewListener, it is the caller's responsibility to initialize
+// the Manager m's Prompt, Cache, HostPolicy, and other desired options.
+func (m *Manager) Listener() net.Listener {
+ ln := &listener{
+ conf: m.TLSConfig(),
+ }
+ ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443")
+ return ln
+}
+
+type listener struct {
+ conf *tls.Config
+
+ tcpListener net.Listener
+ tcpListenErr error
+}
+
+func (ln *listener) Accept() (net.Conn, error) {
+ if ln.tcpListenErr != nil {
+ return nil, ln.tcpListenErr
+ }
+ conn, err := ln.tcpListener.Accept()
+ if err != nil {
+ return nil, err
+ }
+ tcpConn := conn.(*net.TCPConn)
+
+ // Because Listener is a convenience function, help out with
+ // this too. This is not possible for the caller to set once
+ // we return a *tcp.Conn wrapping an inaccessible net.Conn.
+ // If callers don't want this, they can do things the manual
+ // way and tweak as needed. But this is what net/http does
+ // itself, so copy that. If net/http changes, we can change
+ // here too.
+ tcpConn.SetKeepAlive(true)
+ tcpConn.SetKeepAlivePeriod(3 * time.Minute)
+
+ return tls.Server(tcpConn, ln.conf), nil
+}
+
+func (ln *listener) Addr() net.Addr {
+ if ln.tcpListener != nil {
+ return ln.tcpListener.Addr()
+ }
+ // net.Listen failed. Return something non-nil in case callers
+ // call Addr before Accept:
+ return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443}
+}
+
+func (ln *listener) Close() error {
+ if ln.tcpListenErr != nil {
+ return ln.tcpListenErr
+ }
+ return ln.tcpListener.Close()
+}
+
+func cacheDir() string {
+ const base = "golang-autocert"
+ cache, err := os.UserCacheDir()
+ if err != nil {
+ // Fall back to the root directory.
+ cache = "/.cache"
+ }
+
+ return filepath.Join(cache, base)
+}
diff --git a/local_crypto_patch/contents/acme/autocert/renewal.go b/local_crypto_patch/contents/acme/autocert/renewal.go
new file mode 100644
index 0000000000..93984f3866
--- /dev/null
+++ b/local_crypto_patch/contents/acme/autocert/renewal.go
@@ -0,0 +1,158 @@
+// 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 autocert
+
+import (
+ "context"
+ "crypto"
+ "sync"
+ "time"
+)
+
+// domainRenewal tracks the state used by the periodic timers
+// renewing a single domain's cert.
+type domainRenewal struct {
+ m *Manager
+ ck certKey
+ key crypto.Signer
+
+ timerMu sync.Mutex
+ timer *time.Timer
+ timerClose chan struct{} // if non-nil, renew closes this channel (and nils out the timer fields) instead of running
+}
+
+// start starts a cert renewal timer at the time
+// defined by the certificate expiration time exp.
+//
+// If the timer is already started, calling start is a noop.
+func (dr *domainRenewal) start(notBefore, notAfter time.Time) {
+ dr.timerMu.Lock()
+ defer dr.timerMu.Unlock()
+ if dr.timer != nil {
+ return
+ }
+ dr.timer = time.AfterFunc(dr.next(notBefore, notAfter), dr.renew)
+}
+
+// stop stops the cert renewal timer and waits for any in-flight calls to renew
+// to complete. If the timer is already stopped, calling stop is a noop.
+func (dr *domainRenewal) stop() {
+ dr.timerMu.Lock()
+ defer dr.timerMu.Unlock()
+ for {
+ if dr.timer == nil {
+ return
+ }
+ if dr.timer.Stop() {
+ dr.timer = nil
+ return
+ } else {
+ // dr.timer fired, and we acquired dr.timerMu before the renew callback did.
+ // (We know this because otherwise the renew callback would have reset dr.timer!)
+ timerClose := make(chan struct{})
+ dr.timerClose = timerClose
+ dr.timerMu.Unlock()
+ <-timerClose
+ dr.timerMu.Lock()
+ }
+ }
+}
+
+// renew is called periodically by a timer.
+// The first renew call is kicked off by dr.start.
+func (dr *domainRenewal) renew() {
+ dr.timerMu.Lock()
+ defer dr.timerMu.Unlock()
+ if dr.timerClose != nil {
+ close(dr.timerClose)
+ dr.timer, dr.timerClose = nil, nil
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
+ defer cancel()
+ // TODO: rotate dr.key at some point?
+ next, err := dr.do(ctx)
+ if err != nil {
+ next = time.Hour / 2
+ next += time.Duration(pseudoRand.int63n(int64(next)))
+ }
+ testDidRenewLoop(next, err)
+ dr.timer = time.AfterFunc(next, dr.renew)
+}
+
+// updateState locks and replaces the relevant Manager.state item with the given
+// state. It additionally updates dr.key with the given state's key.
+func (dr *domainRenewal) updateState(state *certState) {
+ dr.m.stateMu.Lock()
+ defer dr.m.stateMu.Unlock()
+ dr.key = state.key
+ dr.m.state[dr.ck] = state
+}
+
+// do is similar to Manager.createCert but it doesn't lock a Manager.state item.
+// Instead, it requests a new certificate independently and, upon success,
+// replaces dr.m.state item with a new one and updates cache for the given domain.
+//
+// It may lock and update the Manager.state if the expiration date of the currently
+// cached cert is far enough in the future.
+//
+// The returned value is a time interval after which the renewal should occur again.
+func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
+ // a race is likely unavoidable in a distributed environment
+ // but we try nonetheless
+ if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil {
+ next := dr.next(tlscert.Leaf.NotBefore, tlscert.Leaf.NotAfter)
+ if next > 0 {
+ signer, ok := tlscert.PrivateKey.(crypto.Signer)
+ if ok {
+ state := &certState{
+ key: signer,
+ cert: tlscert.Certificate,
+ leaf: tlscert.Leaf,
+ }
+ dr.updateState(state)
+ return next, nil
+ }
+ }
+ }
+
+ der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck)
+ if err != nil {
+ return 0, err
+ }
+ state := &certState{
+ key: dr.key,
+ cert: der,
+ leaf: leaf,
+ }
+ tlscert, err := state.tlscert()
+ if err != nil {
+ return 0, err
+ }
+ if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil {
+ return 0, err
+ }
+ dr.updateState(state)
+ return dr.next(leaf.NotBefore, leaf.NotAfter), nil
+}
+
+// next returns the wait time before the next renewal should start.
+// If manager.RenewBefore is set, it uses that capped at 30 days,
+// otherwise it uses a default of 1/3 of the cert lifetime.
+// It builds in a jitter of 10% of the renew threshold, capped at 1 hour.
+func (dr *domainRenewal) next(notBefore, notAfter time.Time) time.Duration {
+ threshold := min(notAfter.Sub(notBefore)/3, 30*24*time.Hour)
+ if dr.m.RenewBefore > 0 {
+ threshold = min(dr.m.RenewBefore, 30*24*time.Hour)
+ }
+ maxJitter := min(threshold/10, time.Hour)
+ jitter := pseudoRand.int63n(int64(maxJitter))
+ renewAt := notAfter.Add(-(threshold - time.Duration(jitter)))
+ renewWait := renewAt.Sub(dr.m.now())
+ return max(0, renewWait)
+}
+
+var testDidRenewLoop = func(next time.Duration, err error) {}
diff --git a/local_crypto_patch/contents/acme/autocert/renewal_test.go b/local_crypto_patch/contents/acme/autocert/renewal_test.go
new file mode 100644
index 0000000000..14607b886d
--- /dev/null
+++ b/local_crypto_patch/contents/acme/autocert/renewal_test.go
@@ -0,0 +1,299 @@
+// 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 autocert
+
+import (
+ "context"
+ "crypto"
+ "crypto/ecdsa"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/acme"
+ "golang.org/x/crypto/acme/autocert/internal/acmetest"
+)
+
+func TestRenewalNext(t *testing.T) {
+ now := time.Now()
+ nowFn := func() time.Time { return now }
+ tt := []struct {
+ name string
+ renewBefore time.Duration // arg to Manager
+ // leaf cert validity
+ notBefore time.Time
+ validFor time.Duration
+ // wait time
+ waitMin, waitMax time.Duration
+ }{
+ {"default renewal, 1h cert, valid",
+ 0, now, time.Hour, 40 * time.Minute, 50 * time.Minute},
+ {"default renewal, 1h cert, should renew",
+ 0, now.Add(-50 * time.Minute), time.Hour, 0, 0},
+ {"default renewal, 1h cert, expired",
+ 0, now.Add(-400 * 24 * time.Hour), time.Hour, 0, 0},
+ {"default renewal, 6d cert, valid",
+ 0, now, 6 * 24 * time.Hour, 4 * 24 * time.Hour, (4*24 + 1) * time.Hour},
+ {"default renewal, 6d cert, should renew",
+ 0, now.Add(-5 * 24 * time.Hour), 6 * 24 * time.Hour, 0, 0},
+ {"default renewal, 6d cert, expired",
+ 0, now.Add(-400 * 24 * time.Hour), 6 * 24 * time.Hour, 0, 0},
+ {"default renewal, 90d cert, valid",
+ 0, now, 90 * 24 * time.Hour, 60 * 24 * time.Hour, (60*24 + 1) * time.Hour},
+ {"default renewal, 90d cert, should renew",
+ 0, now.Add(-70 * 24 * time.Hour), 90 * 24 * time.Hour, 0, 0},
+ {"default renewal, 90d cert, expired",
+ 0, now.Add(-400 * 24 * time.Hour), 90 * 24 * time.Hour, 0, 0},
+ {"default renewal, 398d cert, valid",
+ 0, now, 398 * 24 * time.Hour, (368 * 24) * time.Hour, (368*24 + 1) * time.Hour},
+ {"default renewal, 398d cert, should renew",
+ 0, now.Add(-378 * 24 * time.Hour), 398 * 24 * time.Hour, 0, 0},
+ {"default renewal, 398d cert, expired",
+ 0, now.Add(-400 * 24 * time.Hour), 398 * 24 * time.Hour, 0, 0},
+ {"7d renewal, 90d cert, valid",
+ 7 * 24 * time.Hour, now, 90 * 24 * time.Hour, 83 * 24 * time.Hour, (83*24 + 1) * time.Hour},
+ {"7d renewal, 90d cert, should not renew",
+ 7 * 24 * time.Hour, now.Add(-70 * 24 * time.Hour), 90 * 24 * time.Hour, 13 * 24 * time.Hour, (13*24 + 1) * time.Hour},
+ {"7d renewal, 90d cert, should renew",
+ 7 * 24 * time.Hour, now.Add(-85 * 24 * time.Hour), 90 * 24 * time.Hour, 0, 0},
+ {"7d renewal, 90d cert, expired",
+ 7 * 24 * time.Hour, now.Add(-400 * 24 * time.Hour), 90 * 24 * time.Hour, 0, 0},
+ }
+
+ for _, test := range tt {
+ t.Run(test.name, func(t *testing.T) {
+ dr := &domainRenewal{m: &Manager{RenewBefore: test.renewBefore, nowFunc: nowFn}}
+ defer dr.m.stopRenew()
+
+ next := dr.next(test.notBefore, test.notBefore.Add(test.validFor))
+ if next < test.waitMin || next > test.waitMax {
+ t.Errorf("expected wait time: %v <= %v <= %v", test.waitMin, next, test.waitMax)
+ }
+ })
+ }
+}
+
+func TestRenewFromCache(t *testing.T) {
+ man := testManager(t)
+ man.RenewBefore = 24 * time.Hour
+
+ ca := acmetest.NewCAServer(t).Start()
+ ca.ResolveGetCertificate(exampleDomain, man.GetCertificate)
+
+ man.Client = &acme.Client{
+ DirectoryURL: ca.URL(),
+ }
+
+ // cache an almost expired cert
+ now := time.Now()
+ c := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
+ if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil {
+ t.Fatal(err)
+ }
+
+ // verify the renewal happened
+ defer func() {
+ // Stop the timers that read and execute testDidRenewLoop before restoring it.
+ // Otherwise the timer callback may race with the deferred write.
+ man.stopRenew()
+ testDidRenewLoop = func(next time.Duration, err error) {}
+ }()
+ renewed := make(chan bool, 1)
+ testDidRenewLoop = func(next time.Duration, err error) {
+ defer func() {
+ select {
+ case renewed <- true:
+ default:
+ // The renewal timer uses a random backoff. If the first renewal fails for
+ // some reason, we could end up with multiple calls here before the test
+ // stops the timer.
+ }
+ }()
+
+ if err != nil {
+ t.Errorf("testDidRenewLoop: %v", err)
+ }
+ // Next should be about 90 days:
+ // CaServer creates 90days expiry + account for man.RenewBefore.
+ // Previous expiration was within 1 min.
+ future := 88 * 24 * time.Hour
+ if next < future {
+ t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future)
+ }
+
+ // ensure the new cert is cached
+ after := time.Now().Add(future)
+ tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
+ if err != nil {
+ t.Errorf("man.cacheGet: %v", err)
+ return
+ }
+ if !tlscert.Leaf.NotAfter.After(after) {
+ t.Errorf("cache leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after)
+ }
+
+ // verify the old cert is also replaced in memory
+ man.stateMu.Lock()
+ defer man.stateMu.Unlock()
+ s := man.state[exampleCertKey]
+ if s == nil {
+ t.Errorf("m.state[%q] is nil", exampleCertKey)
+ return
+ }
+ tlscert, err = s.tlscert()
+ if err != nil {
+ t.Errorf("s.tlscert: %v", err)
+ return
+ }
+ if !tlscert.Leaf.NotAfter.After(after) {
+ t.Errorf("state leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after)
+ }
+ }
+
+ // trigger renew
+ hello := clientHelloInfo(exampleDomain, algECDSA)
+ if _, err := man.GetCertificate(hello); err != nil {
+ t.Fatal(err)
+ }
+ <-renewed
+}
+
+func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
+ ca := acmetest.NewCAServer(t).Start()
+ man := testManager(t)
+ man.RenewBefore = 24 * time.Hour
+ man.Client = &acme.Client{
+ DirectoryURL: "invalid",
+ }
+
+ // cache a recently renewed cert with a different private key
+ now := time.Now()
+ newCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Hour*24*90))
+ if err := man.cachePut(context.Background(), exampleCertKey, newCert); err != nil {
+ t.Fatal(err)
+ }
+ newLeaf, err := validCert(exampleCertKey, newCert.Certificate, newCert.PrivateKey.(crypto.Signer), now)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // set internal state to an almost expired cert
+ oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute))
+ oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now)
+ if err != nil {
+ t.Fatal(err)
+ }
+ man.stateMu.Lock()
+ if man.state == nil {
+ man.state = make(map[certKey]*certState)
+ }
+ s := &certState{
+ key: oldCert.PrivateKey.(crypto.Signer),
+ cert: oldCert.Certificate,
+ leaf: oldLeaf,
+ }
+ man.state[exampleCertKey] = s
+ man.stateMu.Unlock()
+
+ // verify the renewal accepted the newer cached cert
+ defer func() {
+ // Stop the timers that read and execute testDidRenewLoop before restoring it.
+ // Otherwise the timer callback may race with the deferred write.
+ man.stopRenew()
+ testDidRenewLoop = func(next time.Duration, err error) {}
+ }()
+ renewed := make(chan bool, 1)
+ testDidRenewLoop = func(next time.Duration, err error) {
+ defer func() {
+ select {
+ case renewed <- true:
+ default:
+ // The renewal timer uses a random backoff. If the first renewal fails for
+ // some reason, we could end up with multiple calls here before the test
+ // stops the timer.
+ }
+ }()
+
+ if err != nil {
+ t.Errorf("testDidRenewLoop: %v", err)
+ }
+ // Next should be about 90 days
+ // Previous expiration was within 1 min.
+ future := 88 * 24 * time.Hour
+ if next < future {
+ t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future)
+ }
+
+ // ensure the cached cert was not modified
+ tlscert, err := man.cacheGet(context.Background(), exampleCertKey)
+ if err != nil {
+ t.Errorf("man.cacheGet: %v", err)
+ return
+ }
+ if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
+ t.Errorf("cache leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
+ }
+
+ // verify the old cert is also replaced in memory
+ man.stateMu.Lock()
+ defer man.stateMu.Unlock()
+ s := man.state[exampleCertKey]
+ if s == nil {
+ t.Errorf("m.state[%q] is nil", exampleCertKey)
+ return
+ }
+ stateKey := s.key.Public().(*ecdsa.PublicKey)
+ if !stateKey.Equal(newLeaf.PublicKey) {
+ t.Error("state key was not updated from cache")
+ return
+ }
+ tlscert, err = s.tlscert()
+ if err != nil {
+ t.Errorf("s.tlscert: %v", err)
+ return
+ }
+ if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) {
+ t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
+ }
+ }
+
+ // assert the expiring cert is returned from state
+ hello := clientHelloInfo(exampleDomain, algECDSA)
+ tlscert, err := man.GetCertificate(hello)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !oldLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
+ t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, oldLeaf.NotAfter)
+ }
+
+ // trigger renew
+ man.startRenew(exampleCertKey, s.key, s.leaf.NotBefore, s.leaf.NotAfter)
+ <-renewed
+ func() {
+ man.renewalMu.Lock()
+ defer man.renewalMu.Unlock()
+
+ // verify the private key is replaced in the renewal state
+ r := man.renewal[exampleCertKey]
+ if r == nil {
+ t.Errorf("m.renewal[%q] is nil", exampleCertKey)
+ return
+ }
+ renewalKey := r.key.Public().(*ecdsa.PublicKey)
+ if !renewalKey.Equal(newLeaf.PublicKey) {
+ t.Error("renewal private key was not updated from cache")
+ }
+ }()
+
+ // assert the new cert is returned from state after renew
+ hello = clientHelloInfo(exampleDomain, algECDSA)
+ tlscert, err = man.GetCertificate(hello)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !newLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) {
+ t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter)
+ }
+}
diff --git a/local_crypto_patch/contents/acme/http.go b/local_crypto_patch/contents/acme/http.go
new file mode 100644
index 0000000000..7d1052acd4
--- /dev/null
+++ b/local_crypto_patch/contents/acme/http.go
@@ -0,0 +1,341 @@
+// 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 acme
+
+import (
+ "bytes"
+ "context"
+ "crypto"
+ "crypto/rand"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "math/big"
+ "net/http"
+ "runtime/debug"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// retryTimer encapsulates common logic for retrying unsuccessful requests.
+// It is not safe for concurrent use.
+type retryTimer struct {
+ // backoffFn provides backoff delay sequence for retries.
+ // See Client.RetryBackoff doc comment.
+ backoffFn func(n int, r *http.Request, res *http.Response) time.Duration
+ // n is the current retry attempt.
+ n int
+}
+
+func (t *retryTimer) inc() {
+ t.n++
+}
+
+// backoff pauses the current goroutine as described in Client.RetryBackoff.
+func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error {
+ d := t.backoffFn(t.n, r, res)
+ if d <= 0 {
+ return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n)
+ }
+ wakeup := time.NewTimer(d)
+ defer wakeup.Stop()
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-wakeup.C:
+ return nil
+ }
+}
+
+func (c *Client) retryTimer() *retryTimer {
+ f := c.RetryBackoff
+ if f == nil {
+ f = defaultBackoff
+ }
+ return &retryTimer{backoffFn: f}
+}
+
+// defaultBackoff provides default Client.RetryBackoff implementation
+// using a truncated exponential backoff algorithm,
+// as described in Client.RetryBackoff.
+//
+// The n argument is always bounded between 1 and 30.
+// The returned value is always greater than 0.
+func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration {
+ const maxVal = 10 * time.Second
+ var jitter time.Duration
+ if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil {
+ // Set the minimum to 1ms to avoid a case where
+ // an invalid Retry-After value is parsed into 0 below,
+ // resulting in the 0 returned value which would unintentionally
+ // stop the retries.
+ jitter = (1 + time.Duration(x.Int64())) * time.Millisecond
+ }
+ if v, ok := res.Header["Retry-After"]; ok {
+ return retryAfter(v[0]) + jitter
+ }
+
+ if n < 1 {
+ n = 1
+ }
+ if n > 30 {
+ n = 30
+ }
+ d := time.Duration(1<= 500 || code == http.StatusTooManyRequests
+}
+
+// responseError creates an error of Error type from resp.
+func responseError(resp *http.Response) error {
+ // don't care if ReadAll returns an error:
+ // json.Unmarshal will fail in that case anyway
+ b, _ := io.ReadAll(resp.Body)
+ e := &wireError{Status: resp.StatusCode}
+ if err := json.Unmarshal(b, e); err != nil {
+ // this is not a regular error response:
+ // populate detail with anything we received,
+ // e.Status will already contain HTTP response code value
+ e.Detail = string(b)
+ if e.Detail == "" {
+ e.Detail = resp.Status
+ }
+ }
+ return e.error(resp.Header)
+}
diff --git a/local_crypto_patch/contents/acme/http_test.go b/local_crypto_patch/contents/acme/http_test.go
new file mode 100644
index 0000000000..d124e4e219
--- /dev/null
+++ b/local_crypto_patch/contents/acme/http_test.go
@@ -0,0 +1,255 @@
+// 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 acme
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+)
+
+func TestDefaultBackoff(t *testing.T) {
+ tt := []struct {
+ nretry int
+ retryAfter string // Retry-After header
+ out time.Duration // expected min; max = min + jitter
+ }{
+ {-1, "", time.Second}, // verify the lower bound is 1
+ {0, "", time.Second}, // verify the lower bound is 1
+ {100, "", 10 * time.Second}, // verify the ceiling
+ {1, "3600", time.Hour}, // verify the header value is used
+ {1, "", 1 * time.Second},
+ {2, "", 2 * time.Second},
+ {3, "", 4 * time.Second},
+ {4, "", 8 * time.Second},
+ }
+ for i, test := range tt {
+ r := httptest.NewRequest("GET", "/", nil)
+ resp := &http.Response{Header: http.Header{}}
+ if test.retryAfter != "" {
+ resp.Header.Set("Retry-After", test.retryAfter)
+ }
+ d := defaultBackoff(test.nretry, r, resp)
+ max := test.out + time.Second // + max jitter
+ if d < test.out || max < d {
+ t.Errorf("%d: defaultBackoff(%v) = %v; want between %v and %v", i, test.nretry, d, test.out, max)
+ }
+ }
+}
+
+func TestErrorResponse(t *testing.T) {
+ s := `{
+ "status": 400,
+ "type": "urn:acme:error:xxx",
+ "detail": "text"
+ }`
+ res := &http.Response{
+ StatusCode: 400,
+ Status: "400 Bad Request",
+ Body: io.NopCloser(strings.NewReader(s)),
+ Header: http.Header{"X-Foo": {"bar"}},
+ }
+ err := responseError(res)
+ v, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("err = %+v (%T); want *Error type", err, err)
+ }
+ if v.StatusCode != 400 {
+ t.Errorf("v.StatusCode = %v; want 400", v.StatusCode)
+ }
+ if v.ProblemType != "urn:acme:error:xxx" {
+ t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType)
+ }
+ if v.Detail != "text" {
+ t.Errorf("v.Detail = %q; want text", v.Detail)
+ }
+ if !reflect.DeepEqual(v.Header, res.Header) {
+ t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header)
+ }
+}
+
+func TestPostWithRetries(t *testing.T) {
+ var count int
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ count++
+ w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count))
+ if r.Method == "HEAD" {
+ // We expect the client to do 2 head requests to fetch
+ // nonces, one to start and another after getting badNonce
+ return
+ }
+
+ head, err := decodeJWSHead(r.Body)
+ switch {
+ case err != nil:
+ t.Errorf("decodeJWSHead: %v", err)
+ case head.Nonce == "":
+ t.Error("head.Nonce is empty")
+ case head.Nonce == "nonce1":
+ // Return a badNonce error to force the call to retry.
+ w.Header().Set("Retry-After", "0")
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(`{"type":"urn:ietf:params:acme:error:badNonce"}`))
+ return
+ }
+ // Make client.Authorize happy; we're not testing its result.
+ w.WriteHeader(http.StatusCreated)
+ w.Write([]byte(`{"status":"valid"}`))
+ }))
+ defer ts.Close()
+
+ client := &Client{
+ Key: testKey,
+ DirectoryURL: ts.URL,
+ dir: &Directory{AuthzURL: ts.URL},
+ }
+ // This call will fail with badNonce, causing a retry
+ if _, err := client.Authorize(context.Background(), "example.com"); err != nil {
+ t.Errorf("client.Authorize 1: %v", err)
+ }
+ if count != 3 {
+ t.Errorf("total requests count: %d; want 3", count)
+ }
+}
+
+func TestRetryErrorType(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Replay-Nonce", "nonce")
+ w.WriteHeader(http.StatusTooManyRequests)
+ w.Write([]byte(`{"type":"rateLimited"}`))
+ }))
+ defer ts.Close()
+
+ client := &Client{
+ Key: testKey,
+ RetryBackoff: func(n int, r *http.Request, res *http.Response) time.Duration {
+ // Do no retries.
+ return 0
+ },
+ dir: &Directory{AuthzURL: ts.URL},
+ }
+
+ t.Run("post", func(t *testing.T) {
+ testRetryErrorType(t, func() error {
+ _, err := client.Authorize(context.Background(), "example.com")
+ return err
+ })
+ })
+ t.Run("get", func(t *testing.T) {
+ testRetryErrorType(t, func() error {
+ _, err := client.GetAuthorization(context.Background(), ts.URL)
+ return err
+ })
+ })
+}
+
+func testRetryErrorType(t *testing.T, callClient func() error) {
+ t.Helper()
+ err := callClient()
+ if err == nil {
+ t.Fatal("client.Authorize returned nil error")
+ }
+ acmeErr, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("err is %v (%T); want *Error", err, err)
+ }
+ if acmeErr.StatusCode != http.StatusTooManyRequests {
+ t.Errorf("acmeErr.StatusCode = %d; want %d", acmeErr.StatusCode, http.StatusTooManyRequests)
+ }
+ if acmeErr.ProblemType != "rateLimited" {
+ t.Errorf("acmeErr.ProblemType = %q; want 'rateLimited'", acmeErr.ProblemType)
+ }
+}
+
+func TestRetryBackoffArgs(t *testing.T) {
+ const resCode = http.StatusInternalServerError
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Replay-Nonce", "test-nonce")
+ w.WriteHeader(resCode)
+ }))
+ defer ts.Close()
+
+ // Canceled in backoff.
+ ctx, cancel := context.WithCancel(context.Background())
+
+ var nretry int
+ backoff := func(n int, r *http.Request, res *http.Response) time.Duration {
+ nretry++
+ if n != nretry {
+ t.Errorf("n = %d; want %d", n, nretry)
+ }
+ if nretry == 3 {
+ cancel()
+ }
+
+ if r == nil {
+ t.Error("r is nil")
+ }
+ if res.StatusCode != resCode {
+ t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, resCode)
+ }
+ return time.Millisecond
+ }
+
+ client := &Client{
+ Key: testKey,
+ RetryBackoff: backoff,
+ dir: &Directory{AuthzURL: ts.URL},
+ }
+ if _, err := client.Authorize(ctx, "example.com"); err == nil {
+ t.Error("err is nil")
+ }
+ if nretry != 3 {
+ t.Errorf("nretry = %d; want 3", nretry)
+ }
+}
+
+func TestUserAgent(t *testing.T) {
+ for _, custom := range []string{"", "CUSTOM_UA"} {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Log(r.UserAgent())
+ if s := "golang.org/x/crypto/acme"; !strings.Contains(r.UserAgent(), s) {
+ t.Errorf("expected User-Agent to contain %q, got %q", s, r.UserAgent())
+ }
+ if !strings.Contains(r.UserAgent(), custom) {
+ t.Errorf("expected User-Agent to contain %q, got %q", custom, r.UserAgent())
+ }
+
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"newOrder": "sure"}`))
+ }))
+ defer ts.Close()
+
+ client := &Client{
+ Key: testKey,
+ DirectoryURL: ts.URL,
+ UserAgent: custom,
+ }
+ if _, err := client.Discover(context.Background()); err != nil {
+ t.Errorf("client.Discover: %v", err)
+ }
+ }
+}
+
+func TestAccountKidLoop(t *testing.T) {
+ // if Client.postNoRetry is called with a nil key argument
+ // then Client.Key must be set, otherwise we fall into an
+ // infinite loop (which also causes a deadlock).
+ client := &Client{dir: &Directory{OrderURL: ":)"}}
+ _, _, err := client.postNoRetry(context.Background(), nil, "", nil)
+ if err == nil {
+ t.Fatal("Client.postNoRetry didn't fail with a nil key")
+ }
+ expected := "acme: Client.Key must be populated to make POST requests"
+ if err.Error() != expected {
+ t.Fatalf("Unexpected error returned: wanted %q, got %q", expected, err.Error())
+ }
+}
diff --git a/local_crypto_patch/contents/acme/internal/acmeprobe/prober.go b/local_crypto_patch/contents/acme/internal/acmeprobe/prober.go
new file mode 100644
index 0000000000..25dba0c50e
--- /dev/null
+++ b/local_crypto_patch/contents/acme/internal/acmeprobe/prober.go
@@ -0,0 +1,433 @@
+// 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.
+
+// The acmeprober program runs against an actual ACME CA implementation.
+// It spins up an HTTP server to fulfill authorization challenges
+// or execute a DNS script to provision a response to dns-01 challenge.
+//
+// For http-01 and tls-alpn-01 challenge types this requires the ACME CA
+// to be able to reach the HTTP server.
+//
+// A usage example:
+//
+// go run prober.go \
+// -d https://acme-staging-v02.api.letsencrypt.org/directory \
+// -f order \
+// -t http-01 \
+// -a :8080 \
+// -domain some.example.org
+//
+// The above assumes a TCP tunnel from some.example.org:80 to 0.0.0.0:8080
+// in order for the test to be able to fulfill http-01 challenge.
+// To test tls-alpn-01 challenge, 443 port would need to be tunneled
+// to 0.0.0.0:8080.
+// When running with dns-01 challenge type, use -s argument instead of -a.
+package main
+
+import (
+ "context"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+ "flag"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+
+ "golang.org/x/crypto/acme"
+)
+
+var (
+ // ACME CA directory URL.
+ // Let's Encrypt v2 prod: https://acme-v02.api.letsencrypt.org/directory
+ // Let's Encrypt v2 staging: https://acme-staging-v02.api.letsencrypt.org/directory
+ // See the following for more CAs implementing ACME protocol:
+ // https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment#CAs_&_PKIs_that_offer_ACME_certificates
+ directory = flag.String("d", "", "ACME directory URL.")
+ reginfo = flag.String("r", "", "ACME account registration info.")
+ flow = flag.String("f", "", `Flow to run: "order" or "preauthz" (RFC8555).`)
+ chaltyp = flag.String("t", "", "Challenge type: tls-alpn-01, http-01 or dns-01.")
+ addr = flag.String("a", "", "Local server address for tls-alpn-01 and http-01.")
+ dnsscript = flag.String("s", "", "Script to run for provisioning dns-01 challenges.")
+ domain = flag.String("domain", "", "Space separate domain identifiers.")
+ ipaddr = flag.String("ip", "", "Space separate IP address identifiers.")
+)
+
+func main() {
+ flag.Usage = func() {
+ fmt.Fprintln(flag.CommandLine.Output(), `
+The prober program runs against an actual ACME CA implementation.
+It spins up an HTTP server to fulfill authorization challenges
+or execute a DNS script to provision a response to dns-01 challenge.
+
+For http-01 and tls-alpn-01 challenge types this requires the ACME CA
+to be able to reach the HTTP server.
+
+A usage example:
+
+ go run prober.go \
+ -d https://acme-staging-v02.api.letsencrypt.org/directory \
+ -f order \
+ -t http-01 \
+ -a :8080 \
+ -domain some.example.org
+
+The above assumes a TCP tunnel from some.example.org:80 to 0.0.0.0:8080
+in order for the test to be able to fulfill http-01 challenge.
+To test tls-alpn-01 challenge, 443 port would need to be tunneled
+to 0.0.0.0:8080.
+When running with dns-01 challenge type, use -s argument instead of -a.
+ `)
+ flag.PrintDefaults()
+ }
+ flag.Parse()
+
+ identifiers := acme.DomainIDs(strings.Fields(*domain)...)
+ identifiers = append(identifiers, acme.IPIDs(strings.Fields(*ipaddr)...)...)
+ if len(identifiers) == 0 {
+ log.Fatal("at least one domain or IP addr identifier is required")
+ }
+
+ // Duration of the whole run.
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
+ defer cancel()
+
+ // Create and register a new account.
+ akey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ log.Fatal(err)
+ }
+ cl := &acme.Client{Key: akey, DirectoryURL: *directory}
+ a := &acme.Account{Contact: strings.Fields(*reginfo)}
+ if _, err := cl.Register(ctx, a, acme.AcceptTOS); err != nil {
+ log.Fatalf("Register: %v", err)
+ }
+
+ // Run the desired flow test.
+ p := &prober{
+ client: cl,
+ chalType: *chaltyp,
+ localAddr: *addr,
+ dnsScript: *dnsscript,
+ }
+ switch *flow {
+ case "order":
+ p.runOrder(ctx, identifiers)
+ case "preauthz":
+ p.runPreauthz(ctx, identifiers)
+ default:
+ log.Fatalf("unknown flow: %q", *flow)
+ }
+ if len(p.errors) > 0 {
+ os.Exit(1)
+ }
+}
+
+type prober struct {
+ client *acme.Client
+ chalType string
+ localAddr string
+ dnsScript string
+
+ errors []error
+}
+
+func (p *prober) errorf(format string, a ...interface{}) {
+ err := fmt.Errorf(format, a...)
+ log.Print(err)
+ p.errors = append(p.errors, err)
+}
+
+func (p *prober) runOrder(ctx context.Context, identifiers []acme.AuthzID) {
+ // Create a new order and pick a challenge.
+ // Note that Let's Encrypt will reply with 400 error:malformed
+ // "NotBefore and NotAfter are not supported" when providing a NotAfter
+ // value like WithOrderNotAfter(time.Now().Add(24 * time.Hour)).
+ o, err := p.client.AuthorizeOrder(ctx, identifiers)
+ if err != nil {
+ log.Fatalf("AuthorizeOrder: %v", err)
+ }
+
+ var zurls []string
+ for _, u := range o.AuthzURLs {
+ z, err := p.client.GetAuthorization(ctx, u)
+ if err != nil {
+ log.Fatalf("GetAuthorization(%q): %v", u, err)
+ }
+ log.Printf("%+v", z)
+ if z.Status != acme.StatusPending {
+ log.Printf("authz status is %q; skipping", z.Status)
+ continue
+ }
+ if err := p.fulfill(ctx, z); err != nil {
+ log.Fatalf("fulfill(%s): %v", z.URI, err)
+ }
+ zurls = append(zurls, z.URI)
+ log.Printf("authorized for %+v", z.Identifier)
+ }
+
+ log.Print("all challenges are done")
+ if _, err := p.client.WaitOrder(ctx, o.URI); err != nil {
+ log.Fatalf("WaitOrder(%q): %v", o.URI, err)
+ }
+ csr, certkey := newCSR(identifiers)
+ der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
+ if err != nil {
+ log.Fatalf("CreateOrderCert: %v", err)
+ }
+ log.Printf("cert URL: %s", curl)
+ if err := checkCert(der, identifiers); err != nil {
+ p.errorf("invalid cert: %v", err)
+ }
+
+ // Deactivate all authorizations we satisfied earlier.
+ for _, v := range zurls {
+ if err := p.client.RevokeAuthorization(ctx, v); err != nil {
+ p.errorf("RevokAuthorization(%q): %v", v, err)
+ continue
+ }
+ }
+ // Deactivate the account. We don't need it for any further calls.
+ if err := p.client.DeactivateReg(ctx); err != nil {
+ p.errorf("DeactivateReg: %v", err)
+ }
+ // Try revoking the issued cert using its private key.
+ if err := p.client.RevokeCert(ctx, certkey, der[0], acme.CRLReasonCessationOfOperation); err != nil {
+ p.errorf("RevokeCert: %v", err)
+ }
+}
+
+func (p *prober) runPreauthz(ctx context.Context, identifiers []acme.AuthzID) {
+ dir, err := p.client.Discover(ctx)
+ if err != nil {
+ log.Fatalf("Discover: %v", err)
+ }
+ if dir.AuthzURL == "" {
+ log.Fatal("CA does not support pre-authorization")
+ }
+
+ var zurls []string
+ for _, id := range identifiers {
+ z, err := authorize(ctx, p.client, id)
+ if err != nil {
+ log.Fatalf("AuthorizeID(%+v): %v", z, err)
+ }
+ if z.Status == acme.StatusValid {
+ log.Printf("authz %s is valid; skipping", z.URI)
+ continue
+ }
+ if err := p.fulfill(ctx, z); err != nil {
+ log.Fatalf("fulfill(%s): %v", z.URI, err)
+ }
+ zurls = append(zurls, z.URI)
+ log.Printf("authorized for %+v", id)
+ }
+
+ // We should be all set now.
+ // Expect all authorizations to be satisfied.
+ log.Print("all challenges are done")
+ o, err := p.client.AuthorizeOrder(ctx, identifiers)
+ if err != nil {
+ log.Fatalf("AuthorizeOrder: %v", err)
+ }
+ waitCtx, cancel := context.WithTimeout(ctx, time.Minute)
+ defer cancel()
+ if _, err := p.client.WaitOrder(waitCtx, o.URI); err != nil {
+ log.Fatalf("WaitOrder(%q): %v", o.URI, err)
+ }
+ csr, certkey := newCSR(identifiers)
+ der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
+ if err != nil {
+ log.Fatalf("CreateOrderCert: %v", err)
+ }
+ log.Printf("cert URL: %s", curl)
+ if err := checkCert(der, identifiers); err != nil {
+ p.errorf("invalid cert: %v", err)
+ }
+
+ // Deactivate all authorizations we satisfied earlier.
+ for _, v := range zurls {
+ if err := p.client.RevokeAuthorization(ctx, v); err != nil {
+ p.errorf("RevokeAuthorization(%q): %v", v, err)
+ continue
+ }
+ }
+ // Deactivate the account. We don't need it for any further calls.
+ if err := p.client.DeactivateReg(ctx); err != nil {
+ p.errorf("DeactivateReg: %v", err)
+ }
+ // Try revoking the issued cert using its private key.
+ if err := p.client.RevokeCert(ctx, certkey, der[0], acme.CRLReasonCessationOfOperation); err != nil {
+ p.errorf("RevokeCert: %v", err)
+ }
+}
+
+func (p *prober) fulfill(ctx context.Context, z *acme.Authorization) error {
+ var chal *acme.Challenge
+ for i, c := range z.Challenges {
+ log.Printf("challenge %d: %+v", i, c)
+ if c.Type == p.chalType {
+ log.Printf("picked %s for authz %s", c.URI, z.URI)
+ chal = c
+ }
+ }
+ if chal == nil {
+ return fmt.Errorf("challenge type %q wasn't offered for authz %s", p.chalType, z.URI)
+ }
+
+ switch chal.Type {
+ case "tls-alpn-01":
+ return p.runTLSALPN01(ctx, z, chal)
+ case "http-01":
+ return p.runHTTP01(ctx, z, chal)
+ case "dns-01":
+ return p.runDNS01(ctx, z, chal)
+ default:
+ return fmt.Errorf("unknown challenge type %q", chal.Type)
+ }
+}
+
+func (p *prober) runTLSALPN01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error {
+ tokenCert, err := p.client.TLSALPN01ChallengeCert(chal.Token, z.Identifier.Value)
+ if err != nil {
+ return fmt.Errorf("TLSALPN01ChallengeCert: %v", err)
+ }
+ s := &http.Server{
+ Addr: p.localAddr,
+ TLSConfig: &tls.Config{
+ NextProtos: []string{acme.ALPNProto},
+ GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ log.Printf("hello: %+v", hello)
+ return &tokenCert, nil
+ },
+ },
+ Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ log.Printf("%s %s", r.Method, r.URL)
+ w.WriteHeader(http.StatusNotFound)
+ }),
+ }
+ go s.ListenAndServeTLS("", "")
+ defer s.Close()
+
+ if _, err := p.client.Accept(ctx, chal); err != nil {
+ return fmt.Errorf("Accept(%q): %v", chal.URI, err)
+ }
+ _, zerr := p.client.WaitAuthorization(ctx, z.URI)
+ return zerr
+}
+
+func (p *prober) runHTTP01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error {
+ body, err := p.client.HTTP01ChallengeResponse(chal.Token)
+ if err != nil {
+ return fmt.Errorf("HTTP01ChallengeResponse: %v", err)
+ }
+ s := &http.Server{
+ Addr: p.localAddr,
+ Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ log.Printf("%s %s", r.Method, r.URL)
+ if r.URL.Path != p.client.HTTP01ChallengePath(chal.Token) {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ w.Write([]byte(body))
+ }),
+ }
+ go s.ListenAndServe()
+ defer s.Close()
+
+ if _, err := p.client.Accept(ctx, chal); err != nil {
+ return fmt.Errorf("Accept(%q): %v", chal.URI, err)
+ }
+ _, zerr := p.client.WaitAuthorization(ctx, z.URI)
+ return zerr
+}
+
+func (p *prober) runDNS01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error {
+ token, err := p.client.DNS01ChallengeRecord(chal.Token)
+ if err != nil {
+ return fmt.Errorf("DNS01ChallengeRecord: %v", err)
+ }
+
+ name := fmt.Sprintf("_acme-challenge.%s", z.Identifier.Value)
+ cmd := exec.CommandContext(ctx, p.dnsScript, name, token)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("%s: %v", p.dnsScript, err)
+ }
+
+ if _, err := p.client.Accept(ctx, chal); err != nil {
+ return fmt.Errorf("Accept(%q): %v", chal.URI, err)
+ }
+ _, zerr := p.client.WaitAuthorization(ctx, z.URI)
+ return zerr
+}
+
+func authorize(ctx context.Context, client *acme.Client, id acme.AuthzID) (*acme.Authorization, error) {
+ if id.Type == "ip" {
+ return client.AuthorizeIP(ctx, id.Value)
+ }
+ return client.Authorize(ctx, id.Value)
+}
+
+func checkCert(derChain [][]byte, id []acme.AuthzID) error {
+ if len(derChain) == 0 {
+ return errors.New("cert chain is zero bytes")
+ }
+ for i, b := range derChain {
+ crt, err := x509.ParseCertificate(b)
+ if err != nil {
+ return fmt.Errorf("%d: ParseCertificate: %v", i, err)
+ }
+ log.Printf("%d: serial: 0x%s", i, crt.SerialNumber)
+ log.Printf("%d: subject: %s", i, crt.Subject)
+ log.Printf("%d: issuer: %s", i, crt.Issuer)
+ log.Printf("%d: expires in %.1f day(s)", i, time.Until(crt.NotAfter).Hours()/24)
+ if i > 0 { // not a leaf cert
+ continue
+ }
+ p := &pem.Block{Type: "CERTIFICATE", Bytes: b}
+ log.Printf("%d: leaf:\n%s", i, pem.EncodeToMemory(p))
+ for _, v := range id {
+ if err := crt.VerifyHostname(v.Value); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func newCSR(identifiers []acme.AuthzID) ([]byte, crypto.Signer) {
+ var csr x509.CertificateRequest
+ for _, id := range identifiers {
+ switch id.Type {
+ case "dns":
+ csr.DNSNames = append(csr.DNSNames, id.Value)
+ case "ip":
+ csr.IPAddresses = append(csr.IPAddresses, net.ParseIP(id.Value))
+ default:
+ panic(fmt.Sprintf("newCSR: unknown identifier type %q", id.Type))
+ }
+ }
+ k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ panic(fmt.Sprintf("newCSR: ecdsa.GenerateKey for a cert: %v", err))
+ }
+ b, err := x509.CreateCertificateRequest(rand.Reader, &csr, k)
+ if err != nil {
+ panic(fmt.Sprintf("newCSR: x509.CreateCertificateRequest: %v", err))
+ }
+ return b, k
+}
diff --git a/local_crypto_patch/contents/acme/jws.go b/local_crypto_patch/contents/acme/jws.go
new file mode 100644
index 0000000000..6850275665
--- /dev/null
+++ b/local_crypto_patch/contents/acme/jws.go
@@ -0,0 +1,257 @@
+// Copyright 2015 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 acme
+
+import (
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/hmac"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ _ "crypto/sha512" // need for EC keys
+ "encoding/asn1"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math/big"
+)
+
+// KeyID is the account key identity provided by a CA during registration.
+type KeyID string
+
+// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
+// See jwsEncodeJSON for details.
+const noKeyID = KeyID("")
+
+// noPayload indicates jwsEncodeJSON will encode zero-length octet string
+// in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
+// authenticated GET requests via POSTing with an empty payload.
+// See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
+const noPayload = ""
+
+// noNonce indicates that the nonce should be omitted from the protected header.
+// See jwsEncodeJSON for details.
+const noNonce = ""
+
+// jsonWebSignature can be easily serialized into a JWS following
+// https://tools.ietf.org/html/rfc7515#section-3.2.
+type jsonWebSignature struct {
+ Protected string `json:"protected"`
+ Payload string `json:"payload"`
+ Sig string `json:"signature"`
+}
+
+// jwsEncodeJSON signs claimset using provided key and a nonce.
+// The result is serialized in JSON format containing either kid or jwk
+// fields based on the provided KeyID value.
+//
+// The claimset is marshalled using json.Marshal unless it is a string.
+// In which case it is inserted directly into the message.
+//
+// If kid is non-empty, its quoted value is inserted in the protected header
+// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
+// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
+//
+// If nonce is non-empty, its quoted value is inserted in the protected header.
+//
+// See https://tools.ietf.org/html/rfc7515#section-7.
+func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, url string) ([]byte, error) {
+ if key == nil {
+ return nil, errors.New("nil key")
+ }
+ alg, sha := jwsHasher(key.Public())
+ if alg == "" || !sha.Available() {
+ return nil, ErrUnsupportedKey
+ }
+ headers := struct {
+ Alg string `json:"alg"`
+ KID string `json:"kid,omitempty"`
+ JWK json.RawMessage `json:"jwk,omitempty"`
+ Nonce string `json:"nonce,omitempty"`
+ URL string `json:"url"`
+ }{
+ Alg: alg,
+ Nonce: nonce,
+ URL: url,
+ }
+ switch kid {
+ case noKeyID:
+ jwk, err := jwkEncode(key.Public())
+ if err != nil {
+ return nil, err
+ }
+ headers.JWK = json.RawMessage(jwk)
+ default:
+ headers.KID = string(kid)
+ }
+ phJSON, err := json.Marshal(headers)
+ if err != nil {
+ return nil, err
+ }
+ phead := base64.RawURLEncoding.EncodeToString(phJSON)
+ var payload string
+ if val, ok := claimset.(string); ok {
+ payload = val
+ } else {
+ cs, err := json.Marshal(claimset)
+ if err != nil {
+ return nil, err
+ }
+ payload = base64.RawURLEncoding.EncodeToString(cs)
+ }
+ hash := sha.New()
+ hash.Write([]byte(phead + "." + payload))
+ sig, err := jwsSign(key, sha, hash.Sum(nil))
+ if err != nil {
+ return nil, err
+ }
+ enc := jsonWebSignature{
+ Protected: phead,
+ Payload: payload,
+ Sig: base64.RawURLEncoding.EncodeToString(sig),
+ }
+ return json.Marshal(&enc)
+}
+
+// jwsWithMAC creates and signs a JWS using the given key and the HS256
+// algorithm. kid and url are included in the protected header. rawPayload
+// should not be base64-URL-encoded.
+func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
+ if len(key) == 0 {
+ return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
+ }
+ header := struct {
+ Algorithm string `json:"alg"`
+ KID string `json:"kid"`
+ URL string `json:"url,omitempty"`
+ }{
+ // Only HMAC-SHA256 is supported.
+ Algorithm: "HS256",
+ KID: kid,
+ URL: url,
+ }
+ rawProtected, err := json.Marshal(header)
+ if err != nil {
+ return nil, err
+ }
+ protected := base64.RawURLEncoding.EncodeToString(rawProtected)
+ payload := base64.RawURLEncoding.EncodeToString(rawPayload)
+
+ h := hmac.New(sha256.New, key)
+ if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
+ return nil, err
+ }
+ mac := h.Sum(nil)
+
+ return &jsonWebSignature{
+ Protected: protected,
+ Payload: payload,
+ Sig: base64.RawURLEncoding.EncodeToString(mac),
+ }, nil
+}
+
+// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
+// The result is also suitable for creating a JWK thumbprint.
+// https://tools.ietf.org/html/rfc7517
+func jwkEncode(pub crypto.PublicKey) (string, error) {
+ switch pub := pub.(type) {
+ case *rsa.PublicKey:
+ // https://tools.ietf.org/html/rfc7518#section-6.3.1
+ n := pub.N
+ e := big.NewInt(int64(pub.E))
+ // Field order is important.
+ // See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
+ return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
+ base64.RawURLEncoding.EncodeToString(e.Bytes()),
+ base64.RawURLEncoding.EncodeToString(n.Bytes()),
+ ), nil
+ case *ecdsa.PublicKey:
+ // https://tools.ietf.org/html/rfc7518#section-6.2.1
+ p := pub.Curve.Params()
+ n := p.BitSize / 8
+ if p.BitSize%8 != 0 {
+ n++
+ }
+ x := pub.X.Bytes()
+ if n > len(x) {
+ x = append(make([]byte, n-len(x)), x...)
+ }
+ y := pub.Y.Bytes()
+ if n > len(y) {
+ y = append(make([]byte, n-len(y)), y...)
+ }
+ // Field order is important.
+ // See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
+ return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
+ p.Name,
+ base64.RawURLEncoding.EncodeToString(x),
+ base64.RawURLEncoding.EncodeToString(y),
+ ), nil
+ }
+ return "", ErrUnsupportedKey
+}
+
+// jwsSign signs the digest using the given key.
+// The hash is unused for ECDSA keys.
+func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
+ switch pub := key.Public().(type) {
+ case *rsa.PublicKey:
+ return key.Sign(rand.Reader, digest, hash)
+ case *ecdsa.PublicKey:
+ sigASN1, err := key.Sign(rand.Reader, digest, hash)
+ if err != nil {
+ return nil, err
+ }
+
+ var rs struct{ R, S *big.Int }
+ if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil {
+ return nil, err
+ }
+
+ rb, sb := rs.R.Bytes(), rs.S.Bytes()
+ size := pub.Params().BitSize / 8
+ if size%8 > 0 {
+ size++
+ }
+ sig := make([]byte, size*2)
+ copy(sig[size-len(rb):], rb)
+ copy(sig[size*2-len(sb):], sb)
+ return sig, nil
+ }
+ return nil, ErrUnsupportedKey
+}
+
+// jwsHasher indicates suitable JWS algorithm name and a hash function
+// to use for signing a digest with the provided key.
+// It returns ("", 0) if the key is not supported.
+func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
+ switch pub := pub.(type) {
+ case *rsa.PublicKey:
+ return "RS256", crypto.SHA256
+ case *ecdsa.PublicKey:
+ switch pub.Params().Name {
+ case "P-256":
+ return "ES256", crypto.SHA256
+ case "P-384":
+ return "ES384", crypto.SHA384
+ case "P-521":
+ return "ES512", crypto.SHA512
+ }
+ }
+ return "", 0
+}
+
+// JWKThumbprint creates a JWK thumbprint out of pub
+// as specified in https://tools.ietf.org/html/rfc7638.
+func JWKThumbprint(pub crypto.PublicKey) (string, error) {
+ jwk, err := jwkEncode(pub)
+ if err != nil {
+ return "", err
+ }
+ b := sha256.Sum256([]byte(jwk))
+ return base64.RawURLEncoding.EncodeToString(b[:]), nil
+}
diff --git a/local_crypto_patch/contents/acme/jws_test.go b/local_crypto_patch/contents/acme/jws_test.go
new file mode 100644
index 0000000000..d5f00ba2d3
--- /dev/null
+++ b/local_crypto_patch/contents/acme/jws_test.go
@@ -0,0 +1,550 @@
+// Copyright 2015 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 acme
+
+import (
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "fmt"
+ "io"
+ "math/big"
+ "testing"
+)
+
+// The following shell command alias is used in the comments
+// throughout this file:
+// alias b64raw="base64 -w0 | tr -d '=' | tr '/+' '_-'"
+
+const (
+ // Modulus in raw base64:
+ // 4xgZ3eRPkwoRvy7qeRUbmMDe0V-xH9eWLdu0iheeLlrmD2mqWXfP9IeSKApbn34
+ // g8TuAS9g5zhq8ELQ3kmjr-KV86GAMgI6VAcGlq3QrzpTCf_30Ab7-zawrfRaFON
+ // a1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosqEXeaIkVYBEhbh
+ // Nu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZfoyFyek380mHg
+ // JAumQ_I2fjj98_97mk3ihOY4AgVdCDj1z_GCoZkG5Rq7nbCGyosyKWyDX00Zs-n
+ // NqVhoLeIvXC4nnWdJMZ6rogxyQQ
+ testKeyPEM = `
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq
+WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30
+Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq
+EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf
+oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy
+KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV
+9IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H
+r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm
+ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP
+G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS
+zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6
+9gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s
+8Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc
+7FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL
+qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ
+Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU
+RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o
+JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd
+4gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt
+jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q
+YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73
+c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G
+N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7
+EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO
+9XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx
+-----END RSA PRIVATE KEY-----
+`
+
+ // This thumbprint is for the testKey defined above.
+ testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ"
+
+ // openssl ecparam -name secp256k1 -genkey -noout
+ testKeyECPEM = `
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIK07hGLr0RwyUdYJ8wbIiBS55CjnkMD23DWr+ccnypWLoAoGCCqGSM49
+AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5
+QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ==
+-----END EC PRIVATE KEY-----
+`
+ // openssl ecparam -name secp384r1 -genkey -noout
+ testKeyEC384PEM = `
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDAQ4lNtXRORWr1bgKR1CGysr9AJ9SyEk4jiVnlUWWUChmSNL+i9SLSD
+Oe/naPqXJ6CgBwYFK4EEACKhZANiAAQzKtj+Ms0vHoTX5dzv3/L5YMXOWuI5UKRj
+JigpahYCqXD2BA1j0E/2xt5vlPf+gm0PL+UHSQsCokGnIGuaHCsJAp3ry0gHQEke
+WYXapUUFdvaK1R2/2hn5O+eiQM8YzCg=
+-----END EC PRIVATE KEY-----
+`
+ // openssl ecparam -name secp521r1 -genkey -noout
+ testKeyEC512PEM = `
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIBSNZKFcWzXzB/aJClAb305ibalKgtDA7+70eEkdPt28/3LZMM935Z
+KqYHh/COcxuu3Kt8azRAUz3gyr4zZKhlKUSgBwYFK4EEACOhgYkDgYYABAHUNKbx
+7JwC7H6pa2sV0tERWhHhB3JmW+OP6SUgMWryvIKajlx73eS24dy4QPGrWO9/ABsD
+FqcRSkNVTXnIv6+0mAF25knqIBIg5Q8M9BnOu9GGAchcwt3O7RDHmqewnJJDrbjd
+GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ==
+-----END EC PRIVATE KEY-----
+`
+ // 1. openssl ec -in key.pem -noout -text
+ // 2. remove first byte, 04 (the header); the rest is X and Y
+ // 3. convert each with: echo | xxd -r -p | b64raw
+ testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ"
+ testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk"
+ testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt"
+ testKeyEC384PubY = "Dy_lB0kLAqJBpyBrmhwrCQKd68tIB0BJHlmF2qVFBXb2itUdv9oZ-TvnokDPGMwo"
+ testKeyEC512PubX = "AdQ0pvHsnALsfqlraxXS0RFaEeEHcmZb44_pJSAxavK8gpqOXHvd5Lbh3LhA8atY738AGwMWpxFKQ1VNeci_r7SY"
+ testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax"
+
+ // echo -n '{"crv":"P-256","kty":"EC","x":"","y":""}' | \
+ // openssl dgst -binary -sha256 | b64raw
+ testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU"
+)
+
+var (
+ testKey *rsa.PrivateKey
+ testKeyEC *ecdsa.PrivateKey
+ testKeyEC384 *ecdsa.PrivateKey
+ testKeyEC512 *ecdsa.PrivateKey
+)
+
+func init() {
+ testKey = parseRSA(testKeyPEM, "testKeyPEM")
+ testKeyEC = parseEC(testKeyECPEM, "testKeyECPEM")
+ testKeyEC384 = parseEC(testKeyEC384PEM, "testKeyEC384PEM")
+ testKeyEC512 = parseEC(testKeyEC512PEM, "testKeyEC512PEM")
+}
+
+func decodePEM(s, name string) []byte {
+ d, _ := pem.Decode([]byte(s))
+ if d == nil {
+ panic("no block found in " + name)
+ }
+ return d.Bytes
+}
+
+func parseRSA(s, name string) *rsa.PrivateKey {
+ b := decodePEM(s, name)
+ k, err := x509.ParsePKCS1PrivateKey(b)
+ if err != nil {
+ panic(fmt.Sprintf("%s: %v", name, err))
+ }
+ return k
+}
+
+func parseEC(s, name string) *ecdsa.PrivateKey {
+ b := decodePEM(s, name)
+ k, err := x509.ParseECPrivateKey(b)
+ if err != nil {
+ panic(fmt.Sprintf("%s: %v", name, err))
+ }
+ return k
+}
+
+func TestJWSEncodeJSON(t *testing.T) {
+ claims := struct{ Msg string }{"Hello JWS"}
+ // JWS signed with testKey and "nonce" as the nonce value
+ // JSON-serialized JWS fields are split for easier testing
+ const (
+ // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"}
+ protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
+ "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
+ "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
+ "QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
+ "VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
+ "NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
+ "QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
+ "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
+ "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
+ "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
+ "UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
+ // {"Msg":"Hello JWS"}
+ payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
+ // printf '.' | openssl dgst -binary -sha256 -sign testKey | b64raw
+ signature = "YFyl_xz1E7TR-3E1bIuASTr424EgCvBHjt25WUFC2VaDjXYV0Rj_" +
+ "Hd3dJ_2IRqBrXDZZ2n4ZeA_4mm3QFwmwyeDwe2sWElhb82lCZ8iX" +
+ "uFnjeOmSOjx-nWwPa5ibCXzLq13zZ-OBV1Z4oN_TuailQeRoSfA3" +
+ "nO8gG52mv1x2OMQ5MAFtt8jcngBLzts4AyhI6mBJ2w7Yaj3ZCriq" +
+ "DWA3GLFvvHdW1Ba9Z01wtGT2CuZI7DUk_6Qj1b3BkBGcoKur5C9i" +
+ "bUJtCkABwBMvBQNyD3MmXsrRFRTgvVlyU_yMaucYm7nmzEr_2PaQ" +
+ "50rFt_9qOfJ4sfbLtG1Wwae57BQx1g"
+ )
+
+ b, err := jwsEncodeJSON(claims, testKey, noKeyID, "nonce", "url")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var jws struct{ Protected, Payload, Signature string }
+ if err := json.Unmarshal(b, &jws); err != nil {
+ t.Fatal(err)
+ }
+ if jws.Protected != protected {
+ t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
+ }
+ if jws.Payload != payload {
+ t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
+ }
+ if jws.Signature != signature {
+ t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature)
+ }
+}
+
+func TestJWSEncodeNoNonce(t *testing.T) {
+ kid := KeyID("https://example.org/account/1")
+ claims := "RawString"
+ const (
+ // {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"}
+ protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYWNjb3VudC8xIiwidXJsIjoidXJsIn0"
+ // "Raw String"
+ payload = "RawString"
+ )
+
+ b, err := jwsEncodeJSON(claims, testKeyEC, kid, "", "url")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var jws struct{ Protected, Payload, Signature string }
+ if err := json.Unmarshal(b, &jws); err != nil {
+ t.Fatal(err)
+ }
+ if jws.Protected != protected {
+ t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
+ }
+ if jws.Payload != payload {
+ t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
+ }
+
+ sig, err := base64.RawURLEncoding.DecodeString(jws.Signature)
+ if err != nil {
+ t.Fatalf("jws.Signature: %v", err)
+ }
+ r, s := big.NewInt(0), big.NewInt(0)
+ r.SetBytes(sig[:len(sig)/2])
+ s.SetBytes(sig[len(sig)/2:])
+ h := sha256.Sum256([]byte(protected + "." + payload))
+ if !ecdsa.Verify(testKeyEC.Public().(*ecdsa.PublicKey), h[:], r, s) {
+ t.Error("invalid signature")
+ }
+}
+
+func TestJWSEncodeKID(t *testing.T) {
+ kid := KeyID("https://example.org/account/1")
+ claims := struct{ Msg string }{"Hello JWS"}
+ // JWS signed with testKeyEC
+ const (
+ // {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"}
+ protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5" +
+ "vcmcvYWNjb3VudC8xIiwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
+ // {"Msg":"Hello JWS"}
+ payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ"
+ )
+
+ b, err := jwsEncodeJSON(claims, testKeyEC, kid, "nonce", "url")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var jws struct{ Protected, Payload, Signature string }
+ if err := json.Unmarshal(b, &jws); err != nil {
+ t.Fatal(err)
+ }
+ if jws.Protected != protected {
+ t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected)
+ }
+ if jws.Payload != payload {
+ t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload)
+ }
+
+ sig, err := base64.RawURLEncoding.DecodeString(jws.Signature)
+ if err != nil {
+ t.Fatalf("jws.Signature: %v", err)
+ }
+ r, s := big.NewInt(0), big.NewInt(0)
+ r.SetBytes(sig[:len(sig)/2])
+ s.SetBytes(sig[len(sig)/2:])
+ h := sha256.Sum256([]byte(protected + "." + payload))
+ if !ecdsa.Verify(testKeyEC.Public().(*ecdsa.PublicKey), h[:], r, s) {
+ t.Error("invalid signature")
+ }
+}
+
+func TestJWSEncodeJSONEC(t *testing.T) {
+ tt := []struct {
+ key *ecdsa.PrivateKey
+ x, y string
+ alg, crv string
+ }{
+ {testKeyEC, testKeyECPubX, testKeyECPubY, "ES256", "P-256"},
+ {testKeyEC384, testKeyEC384PubX, testKeyEC384PubY, "ES384", "P-384"},
+ {testKeyEC512, testKeyEC512PubX, testKeyEC512PubY, "ES512", "P-521"},
+ }
+ for i, test := range tt {
+ claims := struct{ Msg string }{"Hello JWS"}
+ b, err := jwsEncodeJSON(claims, test.key, noKeyID, "nonce", "url")
+ if err != nil {
+ t.Errorf("%d: %v", i, err)
+ continue
+ }
+ var jws struct{ Protected, Payload, Signature string }
+ if err := json.Unmarshal(b, &jws); err != nil {
+ t.Errorf("%d: %v", i, err)
+ continue
+ }
+
+ b, err = base64.RawURLEncoding.DecodeString(jws.Protected)
+ if err != nil {
+ t.Errorf("%d: jws.Protected: %v", i, err)
+ }
+ var head struct {
+ Alg string
+ Nonce string
+ URL string `json:"url"`
+ KID string `json:"kid"`
+ JWK struct {
+ Crv string
+ Kty string
+ X string
+ Y string
+ } `json:"jwk"`
+ }
+ if err := json.Unmarshal(b, &head); err != nil {
+ t.Errorf("%d: jws.Protected: %v", i, err)
+ }
+ if head.Alg != test.alg {
+ t.Errorf("%d: head.Alg = %q; want %q", i, head.Alg, test.alg)
+ }
+ if head.Nonce != "nonce" {
+ t.Errorf("%d: head.Nonce = %q; want nonce", i, head.Nonce)
+ }
+ if head.URL != "url" {
+ t.Errorf("%d: head.URL = %q; want 'url'", i, head.URL)
+ }
+ if head.KID != "" {
+ // We used noKeyID in jwsEncodeJSON: expect no kid value.
+ t.Errorf("%d: head.KID = %q; want empty", i, head.KID)
+ }
+ if head.JWK.Crv != test.crv {
+ t.Errorf("%d: head.JWK.Crv = %q; want %q", i, head.JWK.Crv, test.crv)
+ }
+ if head.JWK.Kty != "EC" {
+ t.Errorf("%d: head.JWK.Kty = %q; want EC", i, head.JWK.Kty)
+ }
+ if head.JWK.X != test.x {
+ t.Errorf("%d: head.JWK.X = %q; want %q", i, head.JWK.X, test.x)
+ }
+ if head.JWK.Y != test.y {
+ t.Errorf("%d: head.JWK.Y = %q; want %q", i, head.JWK.Y, test.y)
+ }
+ }
+}
+
+type customTestSigner struct {
+ sig []byte
+ pub crypto.PublicKey
+}
+
+func (s *customTestSigner) Public() crypto.PublicKey { return s.pub }
+func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, error) {
+ return s.sig, nil
+}
+
+func TestJWSEncodeJSONCustom(t *testing.T) {
+ claims := struct{ Msg string }{"hello"}
+ const (
+ // printf '{"Msg":"hello"}' | b64raw
+ payload = "eyJNc2ciOiJoZWxsbyJ9"
+ // printf 'testsig' | b64raw
+ testsig = "dGVzdHNpZw"
+
+ // the example P256 curve point from https://tools.ietf.org/html/rfc7515#appendix-A.3.1
+ // encoded as ASN.1…
+ es256stdsig = "MEUCIA7RIVN5Y2xIPC9/FVgH1AKjsigDOvl8fheBmsMWnqZlAiEA" +
+ "xQoH04w8cOXY8S2vCEpUgKZlkMXyk1Cajz9/ioOjVNU"
+ // …and RFC7518 (https://tools.ietf.org/html/rfc7518#section-3.4)
+ es256jwsig = "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw" +
+ "5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
+
+ // printf '{"alg":"ES256","jwk":{"crv":"P-256","kty":"EC","x":,"y":},"nonce":"nonce","url":"url"}' | b64raw
+ es256phead = "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0" +
+ "eSI6IkVDIiwieCI6IjVsaEV1ZzV4SzR4QkRaMm5BYmF4THRhTGl2" +
+ "ODVieEo3ZVBkMWRrTzIzSFEiLCJ5IjoiNGFpSzcyc0JlVUFHa3Yw" +
+ "VGFMc213b2tZVVl5TnhHc1M1RU1JS3dzTklLayJ9LCJub25jZSI6" +
+ "Im5vbmNlIiwidXJsIjoidXJsIn0"
+
+ // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"}
+ rs256phead = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" +
+ "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" +
+ "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" +
+ "QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" +
+ "VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" +
+ "NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" +
+ "QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" +
+ "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" +
+ "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" +
+ "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" +
+ "UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9"
+ )
+
+ tt := []struct {
+ alg, phead string
+ pub crypto.PublicKey
+ stdsig, jwsig string
+ }{
+ {"ES256", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig},
+ {"RS256", rs256phead, testKey.Public(), testsig, testsig},
+ }
+ for _, tc := range tt {
+ tc := tc
+ t.Run(tc.alg, func(t *testing.T) {
+ stdsig, err := base64.RawStdEncoding.DecodeString(tc.stdsig)
+ if err != nil {
+ t.Errorf("couldn't decode test vector: %v", err)
+ }
+ signer := &customTestSigner{
+ sig: stdsig,
+ pub: tc.pub,
+ }
+
+ b, err := jwsEncodeJSON(claims, signer, noKeyID, "nonce", "url")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var j jsonWebSignature
+ if err := json.Unmarshal(b, &j); err != nil {
+ t.Fatal(err)
+ }
+ if j.Protected != tc.phead {
+ t.Errorf("j.Protected = %q\nwant %q", j.Protected, tc.phead)
+ }
+ if j.Payload != payload {
+ t.Errorf("j.Payload = %q\nwant %q", j.Payload, payload)
+ }
+ if j.Sig != tc.jwsig {
+ t.Errorf("j.Sig = %q\nwant %q", j.Sig, tc.jwsig)
+ }
+ })
+ }
+}
+
+func TestJWSWithMAC(t *testing.T) {
+ // Example from RFC 7520 Section 4.4.3.
+ // https://tools.ietf.org/html/rfc7520#section-4.4.3
+ b64Key := "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg"
+ rawPayload := []byte("It\xe2\x80\x99s a dangerous business, Frodo, going out your " +
+ "door. You step onto the road, and if you don't keep your feet, " +
+ "there\xe2\x80\x99s no knowing where you might be swept off " +
+ "to.")
+ protected := "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW" +
+ "VlZjMxNGJjNzAzNyJ9"
+ payload := "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg" +
+ "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h" +
+ "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi" +
+ "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m" +
+ "ZiB0by4"
+ sig := "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0"
+
+ key, err := base64.RawURLEncoding.DecodeString(b64Key)
+ if err != nil {
+ t.Fatalf("unable to decode key: %q", b64Key)
+ }
+ got, err := jwsWithMAC(key, "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "", rawPayload)
+ if err != nil {
+ t.Fatalf("jwsWithMAC() = %q", err)
+ }
+ if got.Protected != protected {
+ t.Errorf("got.Protected = %q\nwant %q", got.Protected, protected)
+ }
+ if got.Payload != payload {
+ t.Errorf("got.Payload = %q\nwant %q", got.Payload, payload)
+ }
+ if got.Sig != sig {
+ t.Errorf("got.Signature = %q\nwant %q", got.Sig, sig)
+ }
+}
+
+func TestJWSWithMACError(t *testing.T) {
+ p := "{}"
+ if _, err := jwsWithMAC(nil, "", "", []byte(p)); err == nil {
+ t.Errorf("jwsWithMAC(nil, ...) = success; want err")
+ }
+}
+
+func TestJWKThumbprintRSA(t *testing.T) {
+ // Key example from RFC 7638
+ const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" +
+ "VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" +
+ "4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" +
+ "W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" +
+ "1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" +
+ "aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
+ const base64E = "AQAB"
+ const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
+
+ b, err := base64.RawURLEncoding.DecodeString(base64N)
+ if err != nil {
+ t.Fatalf("Error parsing example key N: %v", err)
+ }
+ n := new(big.Int).SetBytes(b)
+
+ b, err = base64.RawURLEncoding.DecodeString(base64E)
+ if err != nil {
+ t.Fatalf("Error parsing example key E: %v", err)
+ }
+ e := new(big.Int).SetBytes(b)
+
+ pub := &rsa.PublicKey{N: n, E: int(e.Uint64())}
+ th, err := JWKThumbprint(pub)
+ if err != nil {
+ t.Error(err)
+ }
+ if th != expected {
+ t.Errorf("thumbprint = %q; want %q", th, expected)
+ }
+}
+
+func TestJWKThumbprintEC(t *testing.T) {
+ // Key example from RFC 7520
+ // expected was computed with
+ // printf '{"crv":"P-521","kty":"EC","x":"","y":""}' | \
+ // openssl dgst -binary -sha256 | b64raw
+ const (
+ base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" +
+ "KqjqvjyekWF-7ytDyRXYgCF5cj0Kt"
+ base64Y = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUda" +
+ "QkAgDPrwQrJmbnX9cwlGfP-HqHZR1"
+ expected = "dHri3SADZkrush5HU_50AoRhcKFryN-PI6jPBtPL55M"
+ )
+
+ b, err := base64.RawURLEncoding.DecodeString(base64X)
+ if err != nil {
+ t.Fatalf("Error parsing example key X: %v", err)
+ }
+ x := new(big.Int).SetBytes(b)
+
+ b, err = base64.RawURLEncoding.DecodeString(base64Y)
+ if err != nil {
+ t.Fatalf("Error parsing example key Y: %v", err)
+ }
+ y := new(big.Int).SetBytes(b)
+
+ pub := &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y}
+ th, err := JWKThumbprint(pub)
+ if err != nil {
+ t.Error(err)
+ }
+ if th != expected {
+ t.Errorf("thumbprint = %q; want %q", th, expected)
+ }
+}
+
+func TestJWKThumbprintErrUnsupportedKey(t *testing.T) {
+ _, err := JWKThumbprint(struct{}{})
+ if err != ErrUnsupportedKey {
+ t.Errorf("err = %q; want %q", err, ErrUnsupportedKey)
+ }
+}
diff --git a/local_crypto_patch/contents/acme/pebble_test.go b/local_crypto_patch/contents/acme/pebble_test.go
new file mode 100644
index 0000000000..79051ac821
--- /dev/null
+++ b/local_crypto_patch/contents/acme/pebble_test.go
@@ -0,0 +1,809 @@
+// 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 acme_test
+
+import (
+ "bytes"
+ "context"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/acme"
+)
+
+const (
+ // pebbleModVersion is the module version used for Pebble and Pebble's
+ // challenge test server. It is ignored if `-pebble-local-dir` is provided.
+ pebbleModVersion = "v2.7.0"
+ // startingPort is the first port number used for binding interface
+ // addresses. Each call to takeNextPort() will increment a port number
+ // starting at this value.
+ startingPort = 5555
+)
+
+var (
+ pebbleLocalDir = flag.String(
+ "pebble-local-dir",
+ "",
+ "Local Pebble to use, instead of fetching from source",
+ )
+ nextPort atomic.Uint32
+)
+
+func init() {
+ nextPort.Store(startingPort)
+}
+
+func TestWithPebble(t *testing.T) {
+ // We want to use process groups w/ syscall.Kill, and the acme package
+ // is very platform-agnostic, so skip on non-Linux.
+ if runtime.GOOS != "linux" {
+ t.Skip("skipping pebble tests on non-linux OS")
+ }
+
+ if testing.Short() {
+ t.Skip("skipping pebble tests in short mode")
+ }
+
+ tests := []struct {
+ name string
+ challSrv func(*environment) (challengeServer, string)
+ }{
+ {
+ name: "TLSALPN01-Issuance",
+ challSrv: func(env *environment) (challengeServer, string) {
+ bindAddr := fmt.Sprintf(":%d", env.config.TLSPort)
+ return newChallTLSServer(bindAddr), bindAddr
+ },
+ },
+
+ {
+ name: "HTTP01-Issuance",
+ challSrv: func(env *environment) (challengeServer, string) {
+ bindAddr := fmt.Sprintf(":%d", env.config.HTTPPort)
+ return newChallHTTPServer(bindAddr), bindAddr
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ env := startPebbleEnvironment(t, nil)
+ challSrv, challSrvAddr := tt.challSrv(&env)
+ challSrv.Run()
+
+ t.Cleanup(func() {
+ challSrv.Shutdown()
+ })
+
+ waitForServer(t, challSrvAddr)
+ testIssuance(t, &env, challSrv)
+ })
+ }
+}
+
+// challengeServer abstracts over the details of running a challenge response
+// server for some supported acme.Challenge type. Responses are provisioned
+// during the test issuance process to be presented to the ACME server's
+// validation authority.
+type challengeServer interface {
+ Run()
+ Shutdown() error
+ Supported(chal *acme.Challenge) bool
+ Provision(client *acme.Client, ident acme.AuthzID, chal *acme.Challenge) error
+}
+
+// challTLSServer is a simple challenge response server that listens for TLS
+// connections on a specific port and if they are TLS-ALPN-01 challenge
+// requests, completes the handshake using the configured challenge response
+// certificate for the SNI value provided.
+type challTLSServer struct {
+ *http.Server
+ // mu protects challCerts.
+ mu sync.RWMutex
+ // challCerts is a map from SNI domain name to challenge response certificate.
+ challCerts map[string]*tls.Certificate
+}
+
+// https://datatracker.ietf.org/doc/html/rfc8737#section-4
+const acmeTLSAlpnProtocol = "acme-tls/1"
+
+func newChallTLSServer(address string) *challTLSServer {
+ challServer := &challTLSServer{Server: &http.Server{
+ Addr: address,
+ ReadTimeout: 5 * time.Second,
+ WriteTimeout: 5 * time.Second,
+ }, challCerts: make(map[string]*tls.Certificate)}
+
+ // Configure the server to support the TLS-ALPN-01 challenge protocol
+ // and to use a callback for selecting the handshake certificate.
+ challServer.Server.TLSConfig = &tls.Config{
+ NextProtos: []string{acmeTLSAlpnProtocol},
+ GetCertificate: challServer.getCertificate,
+ }
+
+ return challServer
+}
+
+func (c *challTLSServer) Shutdown() error {
+ log.Printf("challTLSServer: shutting down")
+ ctx, cancel := context.WithTimeout(context.Background(), 10)
+ defer cancel()
+ return c.Server.Shutdown(ctx)
+}
+
+func (c *challTLSServer) Run() {
+ go func() {
+ // Note: certFile and keyFile are empty because our config uses a
+ // GetCertificate callback.
+ if err := c.Server.ListenAndServeTLS("", ""); err != nil {
+ if !errors.Is(err, http.ErrServerClosed) {
+ log.Printf("challTLSServer error: %v", err)
+ }
+ }
+ }()
+}
+
+func (c *challTLSServer) Supported(chal *acme.Challenge) bool {
+ return chal.Type == "tls-alpn-01"
+}
+
+func (c *challTLSServer) Provision(client *acme.Client, ident acme.AuthzID, chal *acme.Challenge) error {
+ respCert, err := client.TLSALPN01ChallengeCert(chal.Token, ident.Value)
+ if err != nil {
+ return fmt.Errorf("challTLSServer: failed to generate challlenge response cert for %s: %w",
+ ident.Value, err)
+ }
+
+ log.Printf("challTLSServer: setting challenge response certificate for %s", ident.Value)
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.challCerts[ident.Value] = &respCert
+
+ return nil
+}
+
+func (c *challTLSServer) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ // Verify the request looks like a TLS-ALPN-01 challenge request.
+ if len(clientHello.SupportedProtos) != 1 || clientHello.SupportedProtos[0] != acmeTLSAlpnProtocol {
+ return nil, fmt.Errorf(
+ "challTLSServer: non-TLS-ALPN-01 challenge request received with SupportedProtos: %s",
+ clientHello.SupportedProtos)
+ }
+
+ serverName := clientHello.ServerName
+
+ // TLS-ALPN-01 challenge requests for IP addresses are encoded in the SNI
+ // using the reverse-DNS notation. See RFC 8738 Section 6:
+ // https://www.rfc-editor.org/rfc/rfc8738.html#section-6
+ if strings.HasSuffix(serverName, ".in-addr.arpa") {
+ serverName = strings.TrimSuffix(serverName, ".in-addr.arpa")
+ parts := strings.Split(serverName, ".")
+ for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
+ parts[i], parts[j] = parts[j], parts[i]
+ }
+ serverName = strings.Join(parts, ".")
+ }
+
+ log.Printf("challTLSServer: selecting certificate for request from %s for %s",
+ clientHello.Conn.RemoteAddr(), serverName)
+
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ cert := c.challCerts[serverName]
+ if cert == nil {
+ return nil, fmt.Errorf("challTLSServer: no challenge response certificate configured for %s", serverName)
+ }
+
+ return cert, nil
+}
+
+// challHTTPServer is a simple challenge response server that listens for HTTP
+// connections on a specific port and if they are HTTP-01 challenge requests,
+// serves the challenge response key authorization.
+type challHTTPServer struct {
+ *http.Server
+ // mu protects challMap
+ mu sync.RWMutex
+ // challMap is a mapping from request path to response body.
+ challMap map[string]string
+}
+
+func newChallHTTPServer(address string) *challHTTPServer {
+ challServer := &challHTTPServer{
+ Server: &http.Server{
+ Addr: address,
+ ReadTimeout: 5 * time.Second,
+ WriteTimeout: 5 * time.Second,
+ },
+ challMap: make(map[string]string),
+ }
+
+ challServer.Server.Handler = challServer
+
+ return challServer
+}
+
+func (c *challHTTPServer) Supported(chal *acme.Challenge) bool {
+ return chal.Type == "http-01"
+}
+
+func (c *challHTTPServer) Provision(client *acme.Client, ident acme.AuthzID, chall *acme.Challenge) error {
+ path := client.HTTP01ChallengePath(chall.Token)
+ body, err := client.HTTP01ChallengeResponse(chall.Token)
+ if err != nil {
+ return fmt.Errorf("failed to generate HTTP-01 challenge response for %v challenge %s token %s: %w",
+ ident, chall.URI, chall.Token, err)
+ }
+
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ log.Printf("challHTTPServer: setting challenge response for %s", path)
+ c.challMap[path] = body
+
+ return nil
+}
+
+func (c *challHTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ log.Printf("challHTTPServer: handling %s to %s from %s", r.Method, r.URL.Path, r.RemoteAddr)
+ if r.Method != http.MethodGet {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ response, exists := c.challMap[r.URL.Path]
+
+ if !exists {
+ http.NotFound(w, r)
+ return
+ }
+
+ w.Header().Set("Content-Type", "text/plain")
+ w.Write([]byte(response))
+}
+
+func (c *challHTTPServer) Shutdown() error {
+ log.Printf("challHTTPServer: shutting down")
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ return c.Server.Shutdown(ctx)
+}
+
+func (c *challHTTPServer) Run() {
+ go func() {
+ if err := c.Server.ListenAndServe(); err != nil {
+ if !errors.Is(err, http.ErrServerClosed) {
+ log.Printf("challHTTPServer error: %v", err)
+ }
+ }
+ }()
+}
+
+func testIssuance(t *testing.T, env *environment, challSrv challengeServer) {
+ t.Helper()
+
+ // Bound the total issuance process by a timeout of 60 seconds.
+ ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
+ defer cancel()
+
+ // Create a new ACME account.
+ client := env.client
+ acct, err := client.Register(ctx, &acme.Account{}, acme.AcceptTOS)
+ if err != nil {
+ t.Fatalf("failed to register account: %v", err)
+ }
+ if acct.Status != acme.StatusValid {
+ t.Fatalf("expected new account status to be valid, got %v", acct.Status)
+ }
+ log.Printf("registered account: %s", acct.URI)
+
+ // Create a new order for some example identifiers
+ identifiers := []acme.AuthzID{
+ {
+ Type: "dns",
+ Value: "example.com",
+ },
+ {
+ Type: "dns",
+ Value: "www.example.com",
+ },
+ {
+ Type: "ip",
+ Value: "127.0.0.1",
+ },
+ }
+ order, err := client.AuthorizeOrder(ctx, identifiers)
+ if err != nil {
+ t.Fatalf("failed to create order for %v: %v", identifiers, err)
+ }
+ if order.Status != acme.StatusPending {
+ t.Fatalf("expected new order status to be pending, got %v", order.Status)
+ }
+ orderURL := order.URI
+ log.Printf("created order: %v", orderURL)
+
+ // For each pending authz provision a supported challenge type's response
+ // with the test challenge server, and tell the ACME server to verify it.
+ for _, authzURL := range order.AuthzURLs {
+ authz, err := client.GetAuthorization(ctx, authzURL)
+ if err != nil {
+ t.Fatalf("failed to get order %s authorization %s: %v",
+ orderURL, authzURL, err)
+ }
+
+ if authz.Status != acme.StatusPending {
+ continue
+ }
+
+ for _, challenge := range authz.Challenges {
+ if challenge.Status != acme.StatusPending || !challSrv.Supported(challenge) {
+ continue
+ }
+
+ if err := challSrv.Provision(client, authz.Identifier, challenge); err != nil {
+ t.Fatalf("failed to provision challenge %s: %v", challenge.URI, err)
+ }
+
+ _, err = client.Accept(ctx, challenge)
+ if err != nil {
+ t.Fatalf("failed to accept order %s challenge %s: %v",
+ orderURL, challenge.URI, err)
+ }
+ }
+ }
+
+ // Wait for the order to become ready for finalization.
+ order, err = client.WaitOrder(ctx, order.URI)
+ if err != nil {
+ var orderErr *acme.OrderError
+ if errors.Is(err, orderErr) {
+ t.Fatalf("failed to wait for order %s: %s: %s", orderURL, err, orderErr.Problem)
+ } else {
+ t.Fatalf("failed to wait for order %s: %s", orderURL, err)
+ }
+ }
+ if order.Status != acme.StatusReady {
+ t.Fatalf("expected order %s status to be ready, got %v",
+ orderURL, order.Status)
+ }
+
+ // Generate a certificate keypair and a CSR for the order identifiers.
+ certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatalf("failed to generate certificate key: %v", err)
+ }
+ var dnsNames []string
+ var ipAddresses []net.IP
+ for _, ident := range identifiers {
+ switch ident.Type {
+ case "dns":
+ dnsNames = append(dnsNames, ident.Value)
+ case "ip":
+ ipAddresses = append(ipAddresses, net.ParseIP(ident.Value))
+ default:
+ t.Fatalf("unsupported identifier type: %s", ident.Type)
+ }
+ }
+ csrDer, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{
+ DNSNames: dnsNames,
+ IPAddresses: ipAddresses,
+ }, certKey)
+ if err != nil {
+ t.Fatalf("failed to create CSR: %v", err)
+ }
+
+ // Finalize the order by creating a certificate with our CSR.
+ chain, _, err := client.CreateOrderCert(ctx, order.FinalizeURL, csrDer, true)
+ if err != nil {
+ t.Fatalf("failed to finalize order %s with finalize URL %s: %v",
+ orderURL, order.FinalizeURL, err)
+ }
+
+ // Split the chain into the leaf and any intermediates.
+ leaf := chain[0]
+ intermediatesDER := chain[1:]
+ leafCert, err := x509.ParseCertificate(leaf)
+ if err != nil {
+ t.Fatalf("failed to parse order %s leaf certificate: %v", orderURL, err)
+ }
+ intermediates := x509.NewCertPool()
+ for i, intermediateDER := range intermediatesDER {
+ intermediate, err := x509.ParseCertificate(intermediateDER)
+ if err != nil {
+ t.Fatalf("failed to parse intermediate %d: %v", i, err)
+ }
+ intermediates.AddCert(intermediate)
+ }
+
+ // Verify there is a valid path from the leaf certificate to Pebble's
+ // issuing root using the provided intermediate certificates.
+ roots, err := env.RootCert()
+ if err != nil {
+ t.Fatalf("failed to get Pebble issuer root certs: %v", err)
+ }
+ paths, err := leafCert.Verify(x509.VerifyOptions{
+ Intermediates: intermediates,
+ Roots: roots,
+ })
+ if err != nil {
+ t.Fatalf("failed to verify order %s leaf certificate: %v", orderURL, err)
+ }
+ log.Printf("verified %d path(s) from issued leaf certificate to Pebble root CA", len(paths))
+
+ // Also verify that the leaf cert is valid for each of the DNS names
+ // and IP addresses from our order's identifiers.
+ for _, name := range dnsNames {
+ if err := leafCert.VerifyHostname(name); err != nil {
+ t.Fatalf("failed to verify order %s leaf certificate for order DNS name %s: %v",
+ orderURL, name, err)
+ }
+ }
+ for _, ip := range ipAddresses {
+ if err := leafCert.VerifyHostname(ip.String()); err != nil {
+ t.Fatalf("failed to verify order %s leaf certificate for order IP address %s: %v",
+ orderURL, ip, err)
+ }
+ }
+}
+
+type environment struct {
+ config *environmentConfig
+ client *acme.Client
+}
+
+// RootCert returns the Pebble CA's primary issuing hierarchy root certificate.
+// This is generated randomly at each startup and can be used to verify
+// certificate chains issued by Pebble's ACME interface. Note that this
+// is separate from the static root certificate used by the Pebble ACME
+// HTTPS interface.
+func (e *environment) RootCert() (*x509.CertPool, error) {
+ // NOTE: in the future we may want to consider the alternative chains
+ // returned as Link alternative headers.
+ rootURL := fmt.Sprintf("https://%s/roots/0", e.config.pebbleConfig.ManagementListenAddress)
+ resp, err := e.client.HTTPClient.Get(rootURL)
+ if err != nil || resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("failed to GET Pebble root CA from %s: %v", rootURL, err)
+ }
+
+ roots := x509.NewCertPool()
+ rootPEM, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse Pebble root CA PEM: %v", err)
+ }
+ rootDERBlock, _ := pem.Decode(rootPEM)
+ rootCA, err := x509.ParseCertificate(rootDERBlock.Bytes)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse Pebble root CA DER: %v", err)
+ }
+ roots.AddCert(rootCA)
+
+ return roots, nil
+}
+
+// environmentConfig describes the Pebble configuration, and configuration
+// shared between pebble and pebble-challtestsrv.
+type environmentConfig struct {
+ pebbleConfig
+ dnsPort uint32
+}
+
+// defaultConfig returns an environmentConfig populated with default values.
+// The provided pebbleDir is used to specify certificate/private key paths
+// for the HTTPS ACME interface.
+func defaultConfig(pebbleDir string) environmentConfig {
+ return environmentConfig{
+ pebbleConfig: pebbleConfig{
+ ListenAddress: fmt.Sprintf("127.0.0.1:%d", takeNextPort()),
+ ManagementListenAddress: fmt.Sprintf("127.0.0.1:%d", takeNextPort()),
+ HTTPPort: takeNextPort(),
+ TLSPort: takeNextPort(),
+ Certificate: fmt.Sprintf("%s/test/certs/localhost/cert.pem", pebbleDir),
+ PrivateKey: fmt.Sprintf("%s/test/certs/localhost/key.pem", pebbleDir),
+ OCSPResponderURL: "",
+ ExternalAccountBindingRequired: false,
+ ExternalAccountMACKeys: make(map[string]string),
+ DomainBlocklist: []string{"blocked-domain.example"},
+ Profiles: map[string]struct {
+ Description string
+ ValidityPeriod uint64
+ }{
+ "default": {
+ Description: "default profile",
+ ValidityPeriod: 3600,
+ },
+ },
+ RetryAfter: struct {
+ Authz int
+ Order int
+ }{
+ 3,
+ 5,
+ },
+ },
+ dnsPort: takeNextPort(),
+ }
+}
+
+// pebbleConfig matches the JSON structure of the Pebble configuration file.
+type pebbleConfig struct {
+ ListenAddress string
+ ManagementListenAddress string
+ HTTPPort uint32
+ TLSPort uint32
+ Certificate string
+ PrivateKey string
+ OCSPResponderURL string
+ ExternalAccountBindingRequired bool
+ ExternalAccountMACKeys map[string]string
+ DomainBlocklist []string
+ Profiles map[string]struct {
+ Description string
+ ValidityPeriod uint64
+ }
+ RetryAfter struct {
+ Authz int
+ Order int
+ }
+}
+
+func takeNextPort() uint32 {
+ return nextPort.Add(1) - 1
+}
+
+// startPebbleEnvironment is a test helper that spawns Pebble and Pebble
+// challenge test server processes based on the provided environmentConfig. The
+// processes will be torn down when the test ends.
+func startPebbleEnvironment(t *testing.T, config *environmentConfig) environment {
+ t.Helper()
+
+ var pebbleDir string
+ if *pebbleLocalDir != "" {
+ pebbleDir = *pebbleLocalDir
+ } else {
+ pebbleDir = fetchModule(t, "github.com/letsencrypt/pebble/v2", pebbleModVersion)
+ }
+
+ binDir := prepareBinaries(t, pebbleDir)
+
+ if config == nil {
+ cfg := defaultConfig(pebbleDir)
+ config = &cfg
+ }
+
+ marshalConfig := struct {
+ Pebble pebbleConfig
+ }{
+ Pebble: config.pebbleConfig,
+ }
+
+ configData, err := json.Marshal(marshalConfig)
+ if err != nil {
+ t.Fatalf("failed to marshal config: %v", err)
+ }
+
+ configFile, err := os.CreateTemp("", "pebble-config-*.json")
+ if err != nil {
+ t.Fatalf("failed to create temp config file: %v", err)
+ }
+ t.Cleanup(func() { os.Remove(configFile.Name()) })
+
+ if _, err := configFile.Write(configData); err != nil {
+ t.Fatalf("failed to write config file: %v", err)
+ }
+ configFile.Close()
+
+ log.Printf("pebble dir: %s", pebbleDir)
+ log.Printf("config file: %s", configFile.Name())
+
+ // Spawn the Pebble CA server. It answers ACME requests and performs
+ // outbound validations. We configure it to use a mock DNS server that
+ // always answers 127.0.0.1 for all A queries so that validation
+ // requests for any domain name will resolve to our local challenge
+ // server instances.
+ spawnServerProcess(t, binDir, "pebble", "-config", configFile.Name(),
+ "-dnsserver", fmt.Sprintf("127.0.0.1:%d", config.dnsPort),
+ "-strict")
+
+ // Spawn the Pebble challenge test server. We'll use it to mock DNS
+ // responses but disable all the other interfaces. We want to stand
+ // up our own challenge response servers for TLS-ALPN-01,
+ // etc.
+ // Note: we specify -defaultIPv6 "" so that no AAAA records are served.
+ // The LUCI CI runners have issues with IPv6 connectivity on localhost.
+ spawnServerProcess(t, binDir, "pebble-challtestsrv",
+ "-dns01", fmt.Sprintf(":%d", config.dnsPort),
+ "-defaultIPv6", "",
+ "-management", fmt.Sprintf(":%d", takeNextPort()),
+ "-doh", "",
+ "-http01", "",
+ "-tlsalpn01", "",
+ "-https01", "")
+
+ waitForServer(t, config.pebbleConfig.ListenAddress)
+ waitForServer(t, fmt.Sprintf("127.0.0.1:%d", config.dnsPort))
+
+ log.Printf("pebble environment ready")
+
+ // Construct a cert pool that contains the CA certificate used by the ACME
+ // interface's certificate chain. This is separate from the issuing
+ // hierarchy and is used for the ACME client to interact with the ACME
+ // interface without cert verification error.
+ caCertPath := filepath.Join(pebbleDir, "test/certs/pebble.minica.pem")
+ caCert, err := os.ReadFile(caCertPath)
+ if err != nil {
+ t.Fatalf("failed to read CA certificate %s: %v", caCertPath, err)
+ }
+ caCertPool := x509.NewCertPool()
+ if !caCertPool.AppendCertsFromPEM(caCert) {
+ t.Fatalf("failed to parse CA certificate %s", caCertPath)
+ }
+ httpClient := &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: caCertPool,
+ },
+ },
+ }
+
+ // Create an ACME account keypair/client and verify it can discover
+ // the Pebble server's ACME directory without error.
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatalf("failed to generate account key: %v", err)
+ }
+ client := &acme.Client{
+ Key: key,
+ HTTPClient: httpClient,
+ DirectoryURL: fmt.Sprintf("https://%s/dir", config.ListenAddress),
+ }
+ _, err = client.Discover(context.TODO())
+ if err != nil {
+ t.Fatalf("failed to discover ACME directory: %v", err)
+ }
+
+ return environment{
+ config: config,
+ client: client,
+ }
+}
+
+func waitForServer(t *testing.T, addr string) {
+ t.Helper()
+
+ for i := 0; i < 20; i++ {
+ if conn, err := net.Dial("tcp", addr); err == nil {
+ conn.Close()
+ return
+ }
+ time.Sleep(time.Duration(i*100) * time.Millisecond)
+ }
+ t.Fatalf("failed to connect to %q after 20 tries", addr)
+}
+
+// fetchModule fetches the module at the given version and returns the directory
+// containing its source tree. It skips the test if fetching modules is not
+// possible in this environment.
+//
+// Copied from the stdlib cryptotest.FetchModule and adapted to not rely on the
+// stdlib internal testenv package.
+func fetchModule(t *testing.T, module, version string) string {
+ // If the default GOMODCACHE doesn't exist, use a temporary directory
+ // instead. (For example, run.bash sets GOPATH=/nonexist-gopath.)
+ out, err := exec.Command("go", "env", "GOMODCACHE").Output()
+ if err != nil {
+ t.Errorf("go env GOMODCACHE: %v\n%s", err, out)
+ if ee, ok := err.(*exec.ExitError); ok {
+ t.Logf("%s", ee.Stderr)
+ }
+ t.FailNow()
+ }
+ modcacheOk := false
+ if gomodcache := string(bytes.TrimSpace(out)); gomodcache != "" {
+ if _, err := os.Stat(gomodcache); err == nil {
+ modcacheOk = true
+ }
+ }
+ if !modcacheOk {
+ t.Setenv("GOMODCACHE", t.TempDir())
+ // Allow t.TempDir() to clean up subdirectories.
+ t.Setenv("GOFLAGS", os.Getenv("GOFLAGS")+" -modcacherw")
+ }
+
+ t.Logf("fetching %s@%s\n", module, version)
+
+ output, err := exec.Command("go", "mod", "download", "-json", module+"@"+version).CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to download %s@%s: %s\n%s\n", module, version, err, output)
+ }
+ var j struct {
+ Dir string
+ }
+ if err := json.Unmarshal(output, &j); err != nil {
+ t.Fatalf("failed to parse 'go mod download': %s\n%s\n", err, output)
+ }
+
+ return j.Dir
+}
+
+func prepareBinaries(t *testing.T, pebbleDir string) string {
+ t.Helper()
+
+ // We don't want to build in the module cache dir, which might not be
+ // writable or to pollute the user's clone with binaries if pebbleLocalDir
+ // is used.
+ binDir := t.TempDir()
+
+ build := func(cmd string) {
+ log.Printf("building %s", cmd)
+ buildCmd := exec.Command(
+ "go",
+ "build", "-o", filepath.Join(binDir, cmd), "-mod", "mod", "./cmd/"+cmd)
+ buildCmd.Dir = pebbleDir
+ output, err := buildCmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to build %s: %s\n%s\n", cmd, err, output)
+ }
+ }
+
+ build("pebble")
+ build("pebble-challtestsrv")
+
+ return binDir
+}
+
+func spawnServerProcess(t *testing.T, dir string, cmd string, args ...string) {
+ t.Helper()
+
+ var stdout, stderr bytes.Buffer
+
+ cmdInstance := exec.Command("./"+cmd, args...)
+ cmdInstance.Dir = dir
+ cmdInstance.Stdout = &stdout
+ cmdInstance.Stderr = &stderr
+
+ if err := cmdInstance.Start(); err != nil {
+ t.Fatalf("failed to start %s: %v", cmd, err)
+ }
+
+ t.Cleanup(func() {
+ cmdInstance.Process.Kill()
+ cmdInstance.Wait()
+
+ if t.Failed() || testing.Verbose() {
+ t.Logf("=== %s output ===", cmd)
+ if stdout.Len() > 0 {
+ t.Logf("stdout:\n%s", strings.TrimSpace(stdout.String()))
+ }
+ if stderr.Len() > 0 {
+ t.Logf("stderr:\n%s", strings.TrimSpace(stderr.String()))
+ }
+ }
+ })
+}
diff --git a/local_crypto_patch/contents/acme/rfc8555.go b/local_crypto_patch/contents/acme/rfc8555.go
new file mode 100644
index 0000000000..1fb110e08a
--- /dev/null
+++ b/local_crypto_patch/contents/acme/rfc8555.go
@@ -0,0 +1,479 @@
+// 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 acme
+
+import (
+ "context"
+ "crypto"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
+)
+
+// DeactivateReg permanently disables an existing account associated with c.Key.
+// A deactivated account can no longer request certificate issuance or access
+// resources related to the account, such as orders or authorizations.
+//
+// It only works with CAs implementing RFC 8555.
+func (c *Client) DeactivateReg(ctx context.Context) error {
+ if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
+ return err
+ }
+ url := string(c.accountKID(ctx))
+ if url == "" {
+ return ErrNoAccount
+ }
+ req := json.RawMessage(`{"status": "deactivated"}`)
+ res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
+ if err != nil {
+ return err
+ }
+ res.Body.Close()
+ return nil
+}
+
+// registerRFC is equivalent to c.Register but for CAs implementing RFC 8555.
+// It expects c.Discover to have already been called.
+func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) {
+ c.cacheMu.Lock() // guard c.kid access
+ defer c.cacheMu.Unlock()
+
+ req := struct {
+ TermsAgreed bool `json:"termsOfServiceAgreed,omitempty"`
+ Contact []string `json:"contact,omitempty"`
+ ExternalAccountBinding *jsonWebSignature `json:"externalAccountBinding,omitempty"`
+ }{
+ Contact: acct.Contact,
+ }
+ if c.dir.Terms != "" {
+ if prompt == nil {
+ return nil, errors.New("acme: missing Manager.Prompt to accept server's terms of service")
+ }
+ req.TermsAgreed = prompt(c.dir.Terms)
+ }
+
+ // set 'externalAccountBinding' field if requested
+ if acct.ExternalAccountBinding != nil {
+ eabJWS, err := c.encodeExternalAccountBinding(acct.ExternalAccountBinding)
+ if err != nil {
+ return nil, fmt.Errorf("acme: failed to encode external account binding: %v", err)
+ }
+ req.ExternalAccountBinding = eabJWS
+ }
+
+ res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(
+ http.StatusOK, // account with this key already registered
+ http.StatusCreated, // new account created
+ ))
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Body.Close()
+ a, err := responseAccount(res)
+ if err != nil {
+ return nil, err
+ }
+ // Cache Account URL even if we return an error to the caller.
+ // It is by all means a valid and usable "kid" value for future requests.
+ c.KID = KeyID(a.URI)
+ if res.StatusCode == http.StatusOK {
+ return nil, ErrAccountAlreadyExists
+ }
+ return a, nil
+}
+
+// encodeExternalAccountBinding will encode an external account binding stanza
+// as described in https://tools.ietf.org/html/rfc8555#section-7.3.4.
+func (c *Client) encodeExternalAccountBinding(eab *ExternalAccountBinding) (*jsonWebSignature, error) {
+ jwk, err := jwkEncode(c.Key.Public())
+ if err != nil {
+ return nil, err
+ }
+ return jwsWithMAC(eab.Key, eab.KID, c.dir.RegURL, []byte(jwk))
+}
+
+// updateRegRFC is equivalent to c.UpdateReg but for CAs implementing RFC 8555.
+// It expects c.Discover to have already been called.
+func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error) {
+ url := string(c.accountKID(ctx))
+ if url == "" {
+ return nil, ErrNoAccount
+ }
+ req := struct {
+ Contact []string `json:"contact,omitempty"`
+ }{
+ Contact: a.Contact,
+ }
+ res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ return responseAccount(res)
+}
+
+// getRegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555.
+// It expects c.Discover to have already been called.
+func (c *Client) getRegRFC(ctx context.Context) (*Account, error) {
+ req := json.RawMessage(`{"onlyReturnExisting": true}`)
+ res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(http.StatusOK))
+ if e, ok := err.(*Error); ok && e.ProblemType == "urn:ietf:params:acme:error:accountDoesNotExist" {
+ return nil, ErrNoAccount
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Body.Close()
+ return responseAccount(res)
+}
+
+func responseAccount(res *http.Response) (*Account, error) {
+ var v struct {
+ Status string
+ Contact []string
+ Orders string
+ }
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
+ return nil, fmt.Errorf("acme: invalid account response: %v", err)
+ }
+ return &Account{
+ URI: res.Header.Get("Location"),
+ Status: v.Status,
+ Contact: v.Contact,
+ OrdersURL: v.Orders,
+ }, nil
+}
+
+// accountKeyRollover attempts to perform account key rollover.
+// On success it will change client.Key to the new key.
+func (c *Client) accountKeyRollover(ctx context.Context, newKey crypto.Signer) error {
+ dir, err := c.Discover(ctx) // Also required by c.accountKID
+ if err != nil {
+ return err
+ }
+ kid := c.accountKID(ctx)
+ if kid == noKeyID {
+ return ErrNoAccount
+ }
+ oldKey, err := jwkEncode(c.Key.Public())
+ if err != nil {
+ return err
+ }
+ payload := struct {
+ Account string `json:"account"`
+ OldKey json.RawMessage `json:"oldKey"`
+ }{
+ Account: string(kid),
+ OldKey: json.RawMessage(oldKey),
+ }
+ inner, err := jwsEncodeJSON(payload, newKey, noKeyID, noNonce, dir.KeyChangeURL)
+ if err != nil {
+ return err
+ }
+
+ res, err := c.post(ctx, nil, dir.KeyChangeURL, base64.RawURLEncoding.EncodeToString(inner), wantStatus(http.StatusOK))
+ if err != nil {
+ return err
+ }
+ defer res.Body.Close()
+ c.Key = newKey
+ return nil
+}
+
+// AuthorizeOrder initiates the order-based application for certificate issuance,
+// as opposed to pre-authorization in Authorize.
+// It is only supported by CAs implementing RFC 8555.
+//
+// The caller then needs to fetch each authorization with GetAuthorization,
+// identify those with StatusPending status and fulfill a challenge using Accept.
+// Once all authorizations are satisfied, the caller will typically want to poll
+// order status using WaitOrder until it's in StatusReady state.
+// To finalize the order and obtain a certificate, the caller submits a CSR with CreateOrderCert.
+func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderOption) (*Order, error) {
+ dir, err := c.Discover(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ req := struct {
+ Identifiers []wireAuthzID `json:"identifiers"`
+ NotBefore string `json:"notBefore,omitempty"`
+ NotAfter string `json:"notAfter,omitempty"`
+ }{}
+ for _, v := range id {
+ req.Identifiers = append(req.Identifiers, wireAuthzID{
+ Type: v.Type,
+ Value: v.Value,
+ })
+ }
+ for _, o := range opt {
+ switch o := o.(type) {
+ case orderNotBeforeOpt:
+ req.NotBefore = time.Time(o).Format(time.RFC3339)
+ case orderNotAfterOpt:
+ req.NotAfter = time.Time(o).Format(time.RFC3339)
+ default:
+ // Package's fault if we let this happen.
+ panic(fmt.Sprintf("unsupported order option type %T", o))
+ }
+ }
+
+ res, err := c.post(ctx, nil, dir.OrderURL, req, wantStatus(http.StatusCreated))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ return responseOrder(res)
+}
+
+// GetOrder retrieves an order identified by the given URL.
+// For orders created with AuthorizeOrder, the url value is Order.URI.
+//
+// If a caller needs to poll an order until its status is final,
+// see the WaitOrder method.
+func (c *Client) GetOrder(ctx context.Context, url string) (*Order, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ return responseOrder(res)
+}
+
+// WaitOrder polls an order from the given URL until it is in one of the final states,
+// StatusReady, StatusValid or StatusInvalid, the CA responded with a non-retryable error
+// or the context is done.
+//
+// It returns a non-nil Order only if its Status is StatusReady or StatusValid.
+// In all other cases WaitOrder returns an error.
+// If the Status is StatusInvalid, the returned error is of type *OrderError.
+func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
+ if _, err := c.Discover(ctx); err != nil {
+ return nil, err
+ }
+ for {
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ o, err := responseOrder(res)
+ res.Body.Close()
+ switch {
+ case err != nil:
+ // Skip and retry.
+ case o.Status == StatusInvalid:
+ return nil, &OrderError{OrderURL: o.URI, Status: o.Status, Problem: o.Error}
+ case o.Status == StatusReady || o.Status == StatusValid:
+ return o, nil
+ }
+
+ d := retryAfter(res.Header.Get("Retry-After"))
+ if d == 0 {
+ // Default retry-after.
+ // Same reasoning as in WaitAuthorization.
+ d = time.Second
+ }
+ t := time.NewTimer(d)
+ select {
+ case <-ctx.Done():
+ t.Stop()
+ return nil, ctx.Err()
+ case <-t.C:
+ // Retry.
+ }
+ }
+}
+
+func responseOrder(res *http.Response) (*Order, error) {
+ var v struct {
+ Status string
+ Expires time.Time
+ Identifiers []wireAuthzID
+ NotBefore time.Time
+ NotAfter time.Time
+ Error *wireError
+ Authorizations []string
+ Finalize string
+ Certificate string
+ }
+ if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
+ return nil, fmt.Errorf("acme: error reading order: %v", err)
+ }
+ o := &Order{
+ URI: res.Header.Get("Location"),
+ Status: v.Status,
+ Expires: v.Expires,
+ NotBefore: v.NotBefore,
+ NotAfter: v.NotAfter,
+ AuthzURLs: v.Authorizations,
+ FinalizeURL: v.Finalize,
+ CertURL: v.Certificate,
+ }
+ for _, id := range v.Identifiers {
+ o.Identifiers = append(o.Identifiers, AuthzID{Type: id.Type, Value: id.Value})
+ }
+ if v.Error != nil {
+ o.Error = v.Error.error(nil /* headers */)
+ }
+ return o, nil
+}
+
+// CreateOrderCert submits the CSR (Certificate Signing Request) to a CA at the specified URL.
+// The URL is the FinalizeURL field of an Order created with AuthorizeOrder.
+//
+// If the bundle argument is true, the returned value also contain the CA (issuer)
+// certificate chain. Otherwise, only a leaf certificate is returned.
+// The returned URL can be used to re-fetch the certificate using FetchCert.
+//
+// This method is only supported by CAs implementing RFC 8555. See CreateCert for pre-RFC CAs.
+//
+// CreateOrderCert returns an error if the CA's response is unreasonably large.
+// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
+func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) {
+ if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
+ return nil, "", err
+ }
+
+ // RFC describes this as "finalize order" request.
+ req := struct {
+ CSR string `json:"csr"`
+ }{
+ CSR: base64.RawURLEncoding.EncodeToString(csr),
+ }
+ res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, "", err
+ }
+ defer res.Body.Close()
+ o, err := responseOrder(res)
+ if err != nil {
+ return nil, "", err
+ }
+
+ // Wait for CA to issue the cert if they haven't.
+ if o.Status != StatusValid {
+ o, err = c.WaitOrder(ctx, o.URI)
+ }
+ if err != nil {
+ return nil, "", err
+ }
+ // The only acceptable status post finalize and WaitOrder is "valid".
+ if o.Status != StatusValid {
+ return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status, Problem: o.Error}
+ }
+ crt, err := c.fetchCertRFC(ctx, o.CertURL, bundle)
+ return crt, o.CertURL, err
+}
+
+// fetchCertRFC downloads issued certificate from the given URL.
+// It expects the CA to respond with PEM-encoded certificate chain.
+//
+// The URL argument is the CertURL field of Order.
+func (c *Client) fetchCertRFC(ctx context.Context, url string, bundle bool) ([][]byte, error) {
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+
+ // Get all the bytes up to a sane maximum.
+ // Account very roughly for base64 overhead.
+ const max = maxCertChainSize + maxCertChainSize/33
+ b, err := io.ReadAll(io.LimitReader(res.Body, max+1))
+ if err != nil {
+ return nil, fmt.Errorf("acme: fetch cert response stream: %v", err)
+ }
+ if len(b) > max {
+ return nil, errors.New("acme: certificate chain is too big")
+ }
+
+ // Decode PEM chain.
+ var chain [][]byte
+ for {
+ var p *pem.Block
+ p, b = pem.Decode(b)
+ if p == nil {
+ break
+ }
+ if p.Type != "CERTIFICATE" {
+ return nil, fmt.Errorf("acme: invalid PEM cert type %q", p.Type)
+ }
+
+ chain = append(chain, p.Bytes)
+ if !bundle {
+ return chain, nil
+ }
+ if len(chain) > maxChainLen {
+ return nil, errors.New("acme: certificate chain is too long")
+ }
+ }
+ if len(chain) == 0 {
+ return nil, errors.New("acme: certificate chain is empty")
+ }
+ return chain, nil
+}
+
+// sends a cert revocation request in either JWK form when key is non-nil or KID form otherwise.
+func (c *Client) revokeCertRFC(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error {
+ req := &struct {
+ Cert string `json:"certificate"`
+ Reason int `json:"reason"`
+ }{
+ Cert: base64.RawURLEncoding.EncodeToString(cert),
+ Reason: int(reason),
+ }
+ res, err := c.post(ctx, key, c.dir.RevokeURL, req, wantStatus(http.StatusOK))
+ if err != nil {
+ if isAlreadyRevoked(err) {
+ // Assume it is not an error to revoke an already revoked cert.
+ return nil
+ }
+ return err
+ }
+ defer res.Body.Close()
+ return nil
+}
+
+func isAlreadyRevoked(err error) bool {
+ e, ok := err.(*Error)
+ return ok && e.ProblemType == "urn:ietf:params:acme:error:alreadyRevoked"
+}
+
+// ListCertAlternates retrieves any alternate certificate chain URLs for the
+// given certificate chain URL. These alternate URLs can be passed to FetchCert
+// in order to retrieve the alternate certificate chains.
+//
+// If there are no alternate issuer certificate chains, a nil slice will be
+// returned.
+func (c *Client) ListCertAlternates(ctx context.Context, url string) ([]string, error) {
+ if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
+ return nil, err
+ }
+
+ res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK))
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+
+ // We don't need the body but we need to discard it so we don't end up
+ // preventing keep-alive
+ if _, err := io.Copy(io.Discard, res.Body); err != nil {
+ return nil, fmt.Errorf("acme: cert alternates response stream: %v", err)
+ }
+ alts := linkHeader(res.Header, "alternate")
+ return alts, nil
+}
diff --git a/local_crypto_patch/contents/acme/rfc8555_test.go b/local_crypto_patch/contents/acme/rfc8555_test.go
new file mode 100644
index 0000000000..e9cedb5927
--- /dev/null
+++ b/local_crypto_patch/contents/acme/rfc8555_test.go
@@ -0,0 +1,1030 @@
+// 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 acme
+
+import (
+ "bytes"
+ "context"
+ "crypto/hmac"
+ "crypto/rand"
+ "crypto/sha256"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io"
+ "math/big"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+)
+
+// While contents of this file is pertinent only to RFC8555,
+// it is complementary to the tests in the other _test.go files
+// many of which are valid for both pre- and RFC8555.
+// This will make it easier to clean up the tests once non-RFC compliant
+// code is removed.
+
+func TestRFC_Discover(t *testing.T) {
+ const (
+ nonce = "https://example.com/acme/new-nonce"
+ reg = "https://example.com/acme/new-acct"
+ order = "https://example.com/acme/new-order"
+ authz = "https://example.com/acme/new-authz"
+ revoke = "https://example.com/acme/revoke-cert"
+ keychange = "https://example.com/acme/key-change"
+ metaTerms = "https://example.com/acme/terms/2017-5-30"
+ metaWebsite = "https://www.example.com/"
+ metaCAA = "example.com"
+ )
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ fmt.Fprintf(w, `{
+ "newNonce": %q,
+ "newAccount": %q,
+ "newOrder": %q,
+ "newAuthz": %q,
+ "revokeCert": %q,
+ "keyChange": %q,
+ "meta": {
+ "termsOfService": %q,
+ "website": %q,
+ "caaIdentities": [%q],
+ "externalAccountRequired": true
+ }
+ }`, nonce, reg, order, authz, revoke, keychange, metaTerms, metaWebsite, metaCAA)
+ }))
+ defer ts.Close()
+ c := &Client{DirectoryURL: ts.URL}
+ dir, err := c.Discover(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if dir.NonceURL != nonce {
+ t.Errorf("dir.NonceURL = %q; want %q", dir.NonceURL, nonce)
+ }
+ if dir.RegURL != reg {
+ t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg)
+ }
+ if dir.OrderURL != order {
+ t.Errorf("dir.OrderURL = %q; want %q", dir.OrderURL, order)
+ }
+ if dir.AuthzURL != authz {
+ t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz)
+ }
+ if dir.RevokeURL != revoke {
+ t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke)
+ }
+ if dir.KeyChangeURL != keychange {
+ t.Errorf("dir.KeyChangeURL = %q; want %q", dir.KeyChangeURL, keychange)
+ }
+ if dir.Terms != metaTerms {
+ t.Errorf("dir.Terms = %q; want %q", dir.Terms, metaTerms)
+ }
+ if dir.Website != metaWebsite {
+ t.Errorf("dir.Website = %q; want %q", dir.Website, metaWebsite)
+ }
+ if len(dir.CAA) == 0 || dir.CAA[0] != metaCAA {
+ t.Errorf("dir.CAA = %q; want [%q]", dir.CAA, metaCAA)
+ }
+ if !dir.ExternalAccountRequired {
+ t.Error("dir.Meta.ExternalAccountRequired is false")
+ }
+}
+
+func TestRFC_popNonce(t *testing.T) {
+ var count int
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // The Client uses only Directory.NonceURL when specified.
+ // Expect no other URL paths.
+ if r.URL.Path != "/new-nonce" {
+ t.Errorf("r.URL.Path = %q; want /new-nonce", r.URL.Path)
+ }
+ if count > 0 {
+ w.WriteHeader(http.StatusTooManyRequests)
+ return
+ }
+ count++
+ w.Header().Set("Replay-Nonce", "second")
+ }))
+ cl := &Client{
+ DirectoryURL: ts.URL,
+ dir: &Directory{NonceURL: ts.URL + "/new-nonce"},
+ }
+ cl.addNonce(http.Header{"Replay-Nonce": {"first"}})
+
+ for i, nonce := range []string{"first", "second"} {
+ v, err := cl.popNonce(context.Background(), "")
+ if err != nil {
+ t.Errorf("%d: cl.popNonce: %v", i, err)
+ }
+ if v != nonce {
+ t.Errorf("%d: cl.popNonce = %q; want %q", i, v, nonce)
+ }
+ }
+ // No more nonces and server replies with an error past first nonce fetch.
+ // Expected to fail.
+ if _, err := cl.popNonce(context.Background(), ""); err == nil {
+ t.Error("last cl.popNonce returned nil error")
+ }
+}
+
+func TestRFC_postKID(t *testing.T) {
+ var ts *httptest.Server
+ ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case "/new-nonce":
+ w.Header().Set("Replay-Nonce", "nonce")
+ case "/new-account":
+ w.Header().Set("Location", "/account-1")
+ w.Write([]byte(`{"status":"valid"}`))
+ case "/post":
+ b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
+ head, err := decodeJWSHead(bytes.NewReader(b))
+ if err != nil {
+ t.Errorf("decodeJWSHead: %v", err)
+ return
+ }
+ if head.KID != "/account-1" {
+ t.Errorf("head.KID = %q; want /account-1", head.KID)
+ }
+ if len(head.JWK) != 0 {
+ t.Errorf("head.JWK = %q; want zero map", head.JWK)
+ }
+ if v := ts.URL + "/post"; head.URL != v {
+ t.Errorf("head.URL = %q; want %q", head.URL, v)
+ }
+
+ var payload struct{ Msg string }
+ decodeJWSRequest(t, &payload, bytes.NewReader(b))
+ if payload.Msg != "ping" {
+ t.Errorf("payload.Msg = %q; want ping", payload.Msg)
+ }
+ w.Write([]byte("pong"))
+ default:
+ t.Errorf("unhandled %s %s", r.Method, r.URL)
+ w.WriteHeader(http.StatusBadRequest)
+ }
+ }))
+ defer ts.Close()
+
+ ctx := context.Background()
+ cl := &Client{
+ Key: testKey,
+ DirectoryURL: ts.URL,
+ dir: &Directory{
+ NonceURL: ts.URL + "/new-nonce",
+ RegURL: ts.URL + "/new-account",
+ OrderURL: "/force-rfc-mode",
+ },
+ }
+ req := json.RawMessage(`{"msg":"ping"}`)
+ res, err := cl.post(ctx, nil /* use kid */, ts.URL+"/post", req, wantStatus(http.StatusOK))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+ b, _ := io.ReadAll(res.Body) // don't care about err - just checking b
+ if string(b) != "pong" {
+ t.Errorf("res.Body = %q; want pong", b)
+ }
+}
+
+// acmeServer simulates a subset of RFC 8555 compliant CA.
+//
+// TODO: We also have x/crypto/acme/autocert/acmetest and startACMEServerStub in autocert_test.go.
+// It feels like this acmeServer is a sweet spot between usefulness and added complexity.
+// Also, acmetest and startACMEServerStub were both written for draft-02, no RFC support.
+// The goal is to consolidate all into one ACME test server.
+type acmeServer struct {
+ ts *httptest.Server
+ handler map[string]http.HandlerFunc // keyed by r.URL.Path
+
+ mu sync.Mutex
+ nnonce int
+}
+
+func newACMEServer() *acmeServer {
+ return &acmeServer{handler: make(map[string]http.HandlerFunc)}
+}
+
+func (s *acmeServer) handle(path string, f func(http.ResponseWriter, *http.Request)) {
+ s.handler[path] = http.HandlerFunc(f)
+}
+
+func (s *acmeServer) start() {
+ s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ // Directory request.
+ if r.URL.Path == "/" {
+ fmt.Fprintf(w, `{
+ "newNonce": %q,
+ "newAccount": %q,
+ "newOrder": %q,
+ "newAuthz": %q,
+ "revokeCert": %q,
+ "keyChange": %q,
+ "meta": {"termsOfService": %q}
+ }`,
+ s.url("/acme/new-nonce"),
+ s.url("/acme/new-account"),
+ s.url("/acme/new-order"),
+ s.url("/acme/new-authz"),
+ s.url("/acme/revoke-cert"),
+ s.url("/acme/key-change"),
+ s.url("/terms"),
+ )
+ return
+ }
+
+ // All other responses contain a nonce value unconditionally.
+ w.Header().Set("Replay-Nonce", s.nonce())
+ if r.URL.Path == "/acme/new-nonce" {
+ return
+ }
+
+ h := s.handler[r.URL.Path]
+ if h == nil {
+ w.WriteHeader(http.StatusBadRequest)
+ fmt.Fprintf(w, "Unhandled %s", r.URL.Path)
+ return
+ }
+ h.ServeHTTP(w, r)
+ }))
+}
+
+func (s *acmeServer) close() {
+ s.ts.Close()
+}
+
+func (s *acmeServer) url(path string) string {
+ return s.ts.URL + path
+}
+
+func (s *acmeServer) nonce() string {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.nnonce++
+ return fmt.Sprintf("nonce%d", s.nnonce)
+}
+
+func (s *acmeServer) error(w http.ResponseWriter, e *wireError) {
+ w.WriteHeader(e.Status)
+ json.NewEncoder(w).Encode(e)
+}
+
+func TestRFC_Register(t *testing.T) {
+ const email = "mailto:user@example.org"
+
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusCreated) // 201 means new account created
+ fmt.Fprintf(w, `{
+ "status": "valid",
+ "contact": [%q],
+ "orders": %q
+ }`, email, s.url("/accounts/1/orders"))
+
+ b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
+ head, err := decodeJWSHead(bytes.NewReader(b))
+ if err != nil {
+ t.Errorf("decodeJWSHead: %v", err)
+ return
+ }
+ if len(head.JWK) == 0 {
+ t.Error("head.JWK is empty")
+ }
+
+ var req struct{ Contact []string }
+ decodeJWSRequest(t, &req, bytes.NewReader(b))
+ if len(req.Contact) != 1 || req.Contact[0] != email {
+ t.Errorf("req.Contact = %q; want [%q]", req.Contact, email)
+ }
+ })
+ s.start()
+ defer s.close()
+
+ ctx := context.Background()
+ cl := &Client{
+ Key: testKeyEC,
+ DirectoryURL: s.url("/"),
+ }
+
+ var didPrompt bool
+ a := &Account{Contact: []string{email}}
+ acct, err := cl.Register(ctx, a, func(tos string) bool {
+ didPrompt = true
+ terms := s.url("/terms")
+ if tos != terms {
+ t.Errorf("tos = %q; want %q", tos, terms)
+ }
+ return true
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ okAccount := &Account{
+ URI: s.url("/accounts/1"),
+ Status: StatusValid,
+ Contact: []string{email},
+ OrdersURL: s.url("/accounts/1/orders"),
+ }
+ if !reflect.DeepEqual(acct, okAccount) {
+ t.Errorf("acct = %+v; want %+v", acct, okAccount)
+ }
+ if !didPrompt {
+ t.Error("tos prompt wasn't called")
+ }
+ if v := cl.accountKID(ctx); v != KeyID(okAccount.URI) {
+ t.Errorf("account kid = %q; want %q", v, okAccount.URI)
+ }
+}
+
+func TestRFC_RegisterExternalAccountBinding(t *testing.T) {
+ eab := &ExternalAccountBinding{
+ KID: "kid-1",
+ Key: []byte("secret"),
+ }
+
+ type protected struct {
+ Algorithm string `json:"alg"`
+ KID string `json:"kid"`
+ URL string `json:"url"`
+ }
+ const email = "mailto:user@example.org"
+
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ if r.Method != "POST" {
+ t.Errorf("r.Method = %q; want POST", r.Method)
+ }
+
+ var j struct {
+ Protected string
+ Contact []string
+ TermsOfServiceAgreed bool
+ ExternalaccountBinding struct {
+ Protected string
+ Payload string
+ Signature string
+ }
+ }
+ decodeJWSRequest(t, &j, r.Body)
+ protData, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Protected)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var prot protected
+ err = json.Unmarshal(protData, &prot)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(j.Contact, []string{email}) {
+ t.Errorf("j.Contact = %v; want %v", j.Contact, []string{email})
+ }
+ if !j.TermsOfServiceAgreed {
+ t.Error("j.TermsOfServiceAgreed = false; want true")
+ }
+
+ // Ensure same KID.
+ if prot.KID != eab.KID {
+ t.Errorf("j.ExternalAccountBinding.KID = %s; want %s", prot.KID, eab.KID)
+ }
+ // Ensure expected Algorithm.
+ if prot.Algorithm != "HS256" {
+ t.Errorf("j.ExternalAccountBinding.Alg = %s; want %s",
+ prot.Algorithm, "HS256")
+ }
+
+ // Ensure same URL as outer JWS.
+ url := fmt.Sprintf("http://%s/acme/new-account", r.Host)
+ if prot.URL != url {
+ t.Errorf("j.ExternalAccountBinding.URL = %s; want %s",
+ prot.URL, url)
+ }
+
+ // Ensure payload is base64URL encoded string of JWK in outer JWS
+ jwk, err := jwkEncode(testKeyEC.Public())
+ if err != nil {
+ t.Fatal(err)
+ }
+ decodedPayload, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Payload)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if jwk != string(decodedPayload) {
+ t.Errorf("j.ExternalAccountBinding.Payload = %s; want %s", decodedPayload, jwk)
+ }
+
+ // Check signature on inner external account binding JWS
+ hmac := hmac.New(sha256.New, []byte("secret"))
+ _, err = hmac.Write([]byte(j.ExternalaccountBinding.Protected + "." + j.ExternalaccountBinding.Payload))
+ if err != nil {
+ t.Fatal(err)
+ }
+ mac := hmac.Sum(nil)
+ encodedMAC := base64.RawURLEncoding.EncodeToString(mac)
+
+ if !bytes.Equal([]byte(encodedMAC), []byte(j.ExternalaccountBinding.Signature)) {
+ t.Errorf("j.ExternalAccountBinding.Signature = %v; want %v",
+ []byte(j.ExternalaccountBinding.Signature), encodedMAC)
+ }
+
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusCreated)
+ b, _ := json.Marshal([]string{email})
+ fmt.Fprintf(w, `{"status":"valid","orders":"%s","contact":%s}`, s.url("/accounts/1/orders"), b)
+ })
+ s.start()
+ defer s.close()
+
+ ctx := context.Background()
+ cl := &Client{
+ Key: testKeyEC,
+ DirectoryURL: s.url("/"),
+ }
+
+ var didPrompt bool
+ a := &Account{Contact: []string{email}, ExternalAccountBinding: eab}
+ acct, err := cl.Register(ctx, a, func(tos string) bool {
+ didPrompt = true
+ terms := s.url("/terms")
+ if tos != terms {
+ t.Errorf("tos = %q; want %q", tos, terms)
+ }
+ return true
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ okAccount := &Account{
+ URI: s.url("/accounts/1"),
+ Status: StatusValid,
+ Contact: []string{email},
+ OrdersURL: s.url("/accounts/1/orders"),
+ }
+ if !reflect.DeepEqual(acct, okAccount) {
+ t.Errorf("acct = %+v; want %+v", acct, okAccount)
+ }
+ if !didPrompt {
+ t.Error("tos prompt wasn't called")
+ }
+ if v := cl.accountKID(ctx); v != KeyID(okAccount.URI) {
+ t.Errorf("account kid = %q; want %q", v, okAccount.URI)
+ }
+}
+
+func TestRFC_RegisterExisting(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK) // 200 means account already exists
+ w.Write([]byte(`{"status": "valid"}`))
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ _, err := cl.Register(context.Background(), &Account{}, AcceptTOS)
+ if err != ErrAccountAlreadyExists {
+ t.Errorf("err = %v; want %v", err, ErrAccountAlreadyExists)
+ }
+ kid := KeyID(s.url("/accounts/1"))
+ if v := cl.accountKID(context.Background()); v != kid {
+ t.Errorf("account kid = %q; want %q", v, kid)
+ }
+}
+
+func TestRFC_UpdateReg(t *testing.T) {
+ const email = "mailto:user@example.org"
+
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"status": "valid"}`))
+ })
+ var didUpdate bool
+ s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) {
+ didUpdate = true
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"status": "valid"}`))
+
+ b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
+ head, err := decodeJWSHead(bytes.NewReader(b))
+ if err != nil {
+ t.Errorf("decodeJWSHead: %v", err)
+ return
+ }
+ if len(head.JWK) != 0 {
+ t.Error("head.JWK is non-zero")
+ }
+ kid := s.url("/accounts/1")
+ if head.KID != kid {
+ t.Errorf("head.KID = %q; want %q", head.KID, kid)
+ }
+
+ var req struct{ Contact []string }
+ decodeJWSRequest(t, &req, bytes.NewReader(b))
+ if len(req.Contact) != 1 || req.Contact[0] != email {
+ t.Errorf("req.Contact = %q; want [%q]", req.Contact, email)
+ }
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ _, err := cl.UpdateReg(context.Background(), &Account{Contact: []string{email}})
+ if err != nil {
+ t.Error(err)
+ }
+ if !didUpdate {
+ t.Error("UpdateReg didn't update the account")
+ }
+}
+
+func TestRFC_GetReg(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"status": "valid"}`))
+
+ head, err := decodeJWSHead(r.Body)
+ if err != nil {
+ t.Errorf("decodeJWSHead: %v", err)
+ return
+ }
+ if len(head.JWK) == 0 {
+ t.Error("head.JWK is empty")
+ }
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ acct, err := cl.GetReg(context.Background(), "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ okAccount := &Account{
+ URI: s.url("/accounts/1"),
+ Status: StatusValid,
+ }
+ if !reflect.DeepEqual(acct, okAccount) {
+ t.Errorf("acct = %+v; want %+v", acct, okAccount)
+ }
+}
+
+func TestRFC_GetRegNoAccount(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ s.error(w, &wireError{
+ Status: http.StatusBadRequest,
+ Type: "urn:ietf:params:acme:error:accountDoesNotExist",
+ })
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ if _, err := cl.GetReg(context.Background(), ""); err != ErrNoAccount {
+ t.Errorf("err = %v; want %v", err, ErrNoAccount)
+ }
+}
+
+func TestRFC_GetRegOtherError(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusBadRequest)
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ if _, err := cl.GetReg(context.Background(), ""); err == nil || err == ErrNoAccount {
+ t.Errorf("GetReg: %v; want any other non-nil err", err)
+ }
+}
+
+func TestRFC_AccountKeyRollover(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"status": "valid"}`))
+ })
+ s.handle("/acme/key-change", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ if err := cl.AccountKeyRollover(context.Background(), testKeyEC384); err != nil {
+ t.Errorf("AccountKeyRollover: %v, wanted no error", err)
+ } else if cl.Key != testKeyEC384 {
+ t.Error("AccountKeyRollover did not rotate the client key")
+ }
+}
+
+func TestRFC_DeactivateReg(t *testing.T) {
+ const email = "mailto:user@example.org"
+ curStatus := StatusValid
+
+ type account struct {
+ Status string `json:"status"`
+ Contact []string `json:"contact"`
+ AcceptTOS bool `json:"termsOfServiceAgreed"`
+ Orders string `json:"orders"`
+ }
+
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK) // 200 means existing account
+ json.NewEncoder(w).Encode(account{
+ Status: curStatus,
+ Contact: []string{email},
+ AcceptTOS: true,
+ Orders: s.url("/accounts/1/orders"),
+ })
+
+ b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
+ head, err := decodeJWSHead(bytes.NewReader(b))
+ if err != nil {
+ t.Errorf("decodeJWSHead: %v", err)
+ return
+ }
+ if len(head.JWK) == 0 {
+ t.Error("head.JWK is empty")
+ }
+
+ var req struct {
+ Status string `json:"status"`
+ Contact []string `json:"contact"`
+ AcceptTOS bool `json:"termsOfServiceAgreed"`
+ OnlyExisting bool `json:"onlyReturnExisting"`
+ }
+ decodeJWSRequest(t, &req, bytes.NewReader(b))
+ if !req.OnlyExisting {
+ t.Errorf("req.OnlyReturnExisting = %t; want = %t", req.OnlyExisting, true)
+ }
+ })
+ s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) {
+ if curStatus == StatusValid {
+ curStatus = StatusDeactivated
+ w.WriteHeader(http.StatusOK)
+ } else {
+ s.error(w, &wireError{
+ Status: http.StatusUnauthorized,
+ Type: "urn:ietf:params:acme:error:unauthorized",
+ })
+ }
+ var req account
+ b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx
+ head, err := decodeJWSHead(bytes.NewReader(b))
+ if err != nil {
+ t.Errorf("decodeJWSHead: %v", err)
+ return
+ }
+ if len(head.JWK) != 0 {
+ t.Error("head.JWK is not empty")
+ }
+ if !strings.HasSuffix(head.KID, "/accounts/1") {
+ t.Errorf("head.KID = %q; want suffix /accounts/1", head.KID)
+ }
+
+ decodeJWSRequest(t, &req, bytes.NewReader(b))
+ if req.Status != StatusDeactivated {
+ t.Errorf("req.Status = %q; want = %q", req.Status, StatusDeactivated)
+ }
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ if err := cl.DeactivateReg(context.Background()); err != nil {
+ t.Errorf("DeactivateReg: %v, wanted no error", err)
+ }
+ if err := cl.DeactivateReg(context.Background()); err == nil {
+ t.Errorf("DeactivateReg: %v, wanted error for unauthorized", err)
+ }
+}
+
+func TestRF_DeactivateRegNoAccount(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ s.error(w, &wireError{
+ Status: http.StatusBadRequest,
+ Type: "urn:ietf:params:acme:error:accountDoesNotExist",
+ })
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ if err := cl.DeactivateReg(context.Background()); !errors.Is(err, ErrNoAccount) {
+ t.Errorf("DeactivateReg: %v, wanted ErrNoAccount", err)
+ }
+}
+
+func TestRFC_AuthorizeOrder(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"status": "valid"}`))
+ })
+ s.handle("/acme/new-order", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/orders/1"))
+ w.WriteHeader(http.StatusCreated)
+ fmt.Fprintf(w, `{
+ "status": "pending",
+ "expires": "2019-09-01T00:00:00Z",
+ "notBefore": "2019-08-31T00:00:00Z",
+ "notAfter": "2019-09-02T00:00:00Z",
+ "identifiers": [{"type":"dns", "value":"example.org"}],
+ "authorizations": [%q]
+ }`, s.url("/authz/1"))
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ o, err := cl.AuthorizeOrder(context.Background(), DomainIDs("example.org"),
+ WithOrderNotBefore(time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC)),
+ WithOrderNotAfter(time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC)),
+ )
+ if err != nil {
+ t.Fatal(err)
+ }
+ okOrder := &Order{
+ URI: s.url("/orders/1"),
+ Status: StatusPending,
+ Expires: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
+ NotBefore: time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC),
+ NotAfter: time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC),
+ Identifiers: []AuthzID{AuthzID{Type: "dns", Value: "example.org"}},
+ AuthzURLs: []string{s.url("/authz/1")},
+ }
+ if !reflect.DeepEqual(o, okOrder) {
+ t.Errorf("AuthorizeOrder = %+v; want %+v", o, okOrder)
+ }
+}
+
+func TestRFC_GetOrder(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"status": "valid"}`))
+ })
+ s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/orders/1"))
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{
+ "status": "invalid",
+ "expires": "2019-09-01T00:00:00Z",
+ "notBefore": "2019-08-31T00:00:00Z",
+ "notAfter": "2019-09-02T00:00:00Z",
+ "identifiers": [{"type":"dns", "value":"example.org"}],
+ "authorizations": ["/authz/1"],
+ "finalize": "/orders/1/fin",
+ "certificate": "/orders/1/cert",
+ "error": {"type": "badRequest"}
+ }`))
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ o, err := cl.GetOrder(context.Background(), s.url("/orders/1"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ okOrder := &Order{
+ URI: s.url("/orders/1"),
+ Status: StatusInvalid,
+ Expires: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
+ NotBefore: time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC),
+ NotAfter: time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC),
+ Identifiers: []AuthzID{AuthzID{Type: "dns", Value: "example.org"}},
+ AuthzURLs: []string{"/authz/1"},
+ FinalizeURL: "/orders/1/fin",
+ CertURL: "/orders/1/cert",
+ Error: &Error{ProblemType: "badRequest"},
+ }
+ if !reflect.DeepEqual(o, okOrder) {
+ t.Errorf("GetOrder = %+v\nwant %+v", o, okOrder)
+ }
+}
+
+func TestRFC_WaitOrder(t *testing.T) {
+ for _, st := range []string{StatusReady, StatusValid} {
+ t.Run(st, func(t *testing.T) {
+ testWaitOrderStatus(t, st)
+ })
+ }
+}
+
+func testWaitOrderStatus(t *testing.T, okStatus string) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"status": "valid"}`))
+ })
+ var count int
+ s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/orders/1"))
+ w.WriteHeader(http.StatusOK)
+ s := StatusPending
+ if count > 0 {
+ s = okStatus
+ }
+ fmt.Fprintf(w, `{"status": %q}`, s)
+ count++
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ order, err := cl.WaitOrder(context.Background(), s.url("/orders/1"))
+ if err != nil {
+ t.Fatalf("WaitOrder: %v", err)
+ }
+ if order.Status != okStatus {
+ t.Errorf("order.Status = %q; want %q", order.Status, okStatus)
+ }
+}
+
+func TestRFC_WaitOrderError(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"status": "valid"}`))
+ })
+ var count int
+ s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/orders/1"))
+ w.WriteHeader(http.StatusOK)
+ if count > 0 {
+ // https://www.rfc-editor.org/rfc/rfc8555#section-7.3.3
+ errorData := `{
+ "type": "urn:ietf:params:acme:error:userActionRequired",
+ "detail": "Terms of service have changed",
+ "instance": "https://example.com/acme/agreement/?token=W8Ih3PswD-8"
+ }`
+ fmt.Fprintf(w, `{"status": %q, "error": %s}`, StatusInvalid, errorData)
+ } else {
+ fmt.Fprintf(w, `{"status": %q}`, StatusPending)
+ }
+ count++
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ _, err := cl.WaitOrder(context.Background(), s.url("/orders/1"))
+ if err == nil {
+ t.Fatal("WaitOrder returned nil error")
+ }
+ e, ok := err.(*OrderError)
+ if !ok {
+ t.Fatalf("err = %v (%T); want OrderError", err, err)
+ }
+ if e.OrderURL != s.url("/orders/1") {
+ t.Errorf("e.OrderURL = %q; want %q", e.OrderURL, s.url("/orders/1"))
+ }
+ if e.Status != StatusInvalid {
+ t.Errorf("e.Status = %q; want %q", e.Status, StatusInvalid)
+ }
+ if e.Problem == nil {
+ t.Errorf("e.Problem = nil")
+ }
+ expectedProbType := "urn:ietf:params:acme:error:userActionRequired"
+ if e.Problem.ProblemType != expectedProbType {
+ t.Errorf("e.Problem.ProblemType = %q; want %q", e.Problem.ProblemType, expectedProbType)
+ }
+}
+
+func TestRFC_CreateOrderCert(t *testing.T) {
+ q := &x509.CertificateRequest{
+ Subject: pkix.Name{CommonName: "example.org"},
+ }
+ csr, err := x509.CreateCertificateRequest(rand.Reader, q, testKeyEC)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tmpl := &x509.Certificate{SerialNumber: big.NewInt(1)}
+ leaf, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testKeyEC.PublicKey, testKeyEC)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ s := newACMEServer()
+ s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/accounts/1"))
+ w.Write([]byte(`{"status": "valid"}`))
+ })
+ var count int
+ s.handle("/pleaseissue", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Location", s.url("/pleaseissue"))
+ st := StatusProcessing
+ if count > 0 {
+ st = StatusValid
+ }
+ fmt.Fprintf(w, `{"status":%q, "certificate":%q}`, st, s.url("/crt"))
+ count++
+ })
+ s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/pem-certificate-chain")
+ pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: leaf})
+ })
+ s.start()
+ defer s.close()
+ ctx := context.Background()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ cert, curl, err := cl.CreateOrderCert(ctx, s.url("/pleaseissue"), csr, true)
+ if err != nil {
+ t.Fatalf("CreateOrderCert: %v", err)
+ }
+ if _, err := x509.ParseCertificate(cert[0]); err != nil {
+ t.Errorf("ParseCertificate: %v", err)
+ }
+ if !reflect.DeepEqual(cert[0], leaf) {
+ t.Errorf("cert and leaf bytes don't match")
+ }
+ if u := s.url("/crt"); curl != u {
+ t.Errorf("curl = %q; want %q", curl, u)
+ }
+}
+
+func TestRFC_AlreadyRevokedCert(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/acme/revoke-cert", func(w http.ResponseWriter, r *http.Request) {
+ s.error(w, &wireError{
+ Status: http.StatusBadRequest,
+ Type: "urn:ietf:params:acme:error:alreadyRevoked",
+ })
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ err := cl.RevokeCert(context.Background(), testKeyEC, []byte{0}, CRLReasonUnspecified)
+ if err != nil {
+ t.Fatalf("RevokeCert: %v", err)
+ }
+}
+
+func TestRFC_ListCertAlternates(t *testing.T) {
+ s := newACMEServer()
+ s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/pem-certificate-chain")
+ w.Header().Add("Link", `;rel="alternate"`)
+ w.Header().Add("Link", `; rel="alternate"`)
+ w.Header().Add("Link", `; rel="index"`)
+ })
+ s.handle("/crt2", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/pem-certificate-chain")
+ })
+ s.start()
+ defer s.close()
+
+ cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
+ crts, err := cl.ListCertAlternates(context.Background(), s.url("/crt"))
+ if err != nil {
+ t.Fatalf("ListCertAlternates: %v", err)
+ }
+ want := []string{"https://example.com/crt/2", "https://example.com/crt/3"}
+ if !reflect.DeepEqual(crts, want) {
+ t.Errorf("ListCertAlternates(/crt): %v; want %v", crts, want)
+ }
+ crts, err = cl.ListCertAlternates(context.Background(), s.url("/crt2"))
+ if err != nil {
+ t.Fatalf("ListCertAlternates: %v", err)
+ }
+ if crts != nil {
+ t.Errorf("ListCertAlternates(/crt2): %v; want nil", crts)
+ }
+}
diff --git a/local_crypto_patch/contents/acme/types.go b/local_crypto_patch/contents/acme/types.go
new file mode 100644
index 0000000000..65d69b2617
--- /dev/null
+++ b/local_crypto_patch/contents/acme/types.go
@@ -0,0 +1,636 @@
+// 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 acme
+
+import (
+ "crypto"
+ "crypto/x509"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+)
+
+// ACME status values of Account, Order, Authorization and Challenge objects.
+// See https://tools.ietf.org/html/rfc8555#section-7.1.6 for details.
+const (
+ StatusDeactivated = "deactivated"
+ StatusExpired = "expired"
+ StatusInvalid = "invalid"
+ StatusPending = "pending"
+ StatusProcessing = "processing"
+ StatusReady = "ready"
+ StatusRevoked = "revoked"
+ StatusUnknown = "unknown"
+ StatusValid = "valid"
+)
+
+// CRLReasonCode identifies the reason for a certificate revocation.
+type CRLReasonCode int
+
+// CRL reason codes as defined in RFC 5280.
+const (
+ CRLReasonUnspecified CRLReasonCode = 0
+ CRLReasonKeyCompromise CRLReasonCode = 1
+ CRLReasonCACompromise CRLReasonCode = 2
+ CRLReasonAffiliationChanged CRLReasonCode = 3
+ CRLReasonSuperseded CRLReasonCode = 4
+ CRLReasonCessationOfOperation CRLReasonCode = 5
+ CRLReasonCertificateHold CRLReasonCode = 6
+ CRLReasonRemoveFromCRL CRLReasonCode = 8
+ CRLReasonPrivilegeWithdrawn CRLReasonCode = 9
+ CRLReasonAACompromise CRLReasonCode = 10
+)
+
+var (
+ // ErrUnsupportedKey is returned when an unsupported key type is encountered.
+ ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
+
+ // ErrAccountAlreadyExists indicates that the Client's key has already been registered
+ // with the CA. It is returned by Register method.
+ ErrAccountAlreadyExists = errors.New("acme: account already exists")
+
+ // ErrNoAccount indicates that the Client's key has not been registered with the CA.
+ ErrNoAccount = errors.New("acme: account does not exist")
+
+ // errPreAuthorizationNotSupported indicates that the server does not
+ // support pre-authorization of identifiers.
+ errPreAuthorizationNotSupported = errors.New("acme: pre-authorization is not supported")
+)
+
+// A Subproblem describes an ACME subproblem as reported in an Error.
+type Subproblem struct {
+ // Type is a URI reference that identifies the problem type,
+ // typically in a "urn:acme:error:xxx" form.
+ Type string
+ // Detail is a human-readable explanation specific to this occurrence of the problem.
+ Detail string
+ // Instance indicates a URL that the client should direct a human user to visit
+ // in order for instructions on how to agree to the updated Terms of Service.
+ // In such an event CA sets StatusCode to 403, Type to
+ // "urn:ietf:params:acme:error:userActionRequired", and adds a Link header with relation
+ // "terms-of-service" containing the latest TOS URL.
+ Instance string
+ // Identifier may contain the ACME identifier that the error is for.
+ Identifier *AuthzID
+}
+
+func (sp Subproblem) String() string {
+ str := fmt.Sprintf("%s: ", sp.Type)
+ if sp.Identifier != nil {
+ str += fmt.Sprintf("[%s: %s] ", sp.Identifier.Type, sp.Identifier.Value)
+ }
+ str += sp.Detail
+ return str
+}
+
+// Error is an ACME error, defined in Problem Details for HTTP APIs doc
+// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem.
+type Error struct {
+ // StatusCode is The HTTP status code generated by the origin server.
+ StatusCode int
+ // ProblemType is a URI reference that identifies the problem type,
+ // typically in a "urn:acme:error:xxx" form.
+ ProblemType string
+ // Detail is a human-readable explanation specific to this occurrence of the problem.
+ Detail string
+ // Instance indicates a URL that the client should direct a human user to visit
+ // in order for instructions on how to agree to the updated Terms of Service.
+ // In such an event CA sets StatusCode to 403, ProblemType to
+ // "urn:ietf:params:acme:error:userActionRequired" and a Link header with relation
+ // "terms-of-service" containing the latest TOS URL.
+ Instance string
+ // Header is the original server error response headers.
+ // It may be nil.
+ Header http.Header
+ // Subproblems may contain more detailed information about the individual problems
+ // that caused the error. This field is only sent by RFC 8555 compatible ACME
+ // servers. Defined in RFC 8555 Section 6.7.1.
+ Subproblems []Subproblem
+}
+
+func (e *Error) Error() string {
+ str := fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
+ if len(e.Subproblems) > 0 {
+ str += fmt.Sprintf("; subproblems:")
+ for _, sp := range e.Subproblems {
+ str += fmt.Sprintf("\n\t%s", sp)
+ }
+ }
+ return str
+}
+
+// AuthorizationError indicates that an authorization for an identifier
+// did not succeed.
+// It contains all errors from Challenge items of the failed Authorization.
+type AuthorizationError struct {
+ // URI uniquely identifies the failed Authorization.
+ URI string
+
+ // Identifier is an AuthzID.Value of the failed Authorization.
+ Identifier string
+
+ // Errors is a collection of non-nil error values of Challenge items
+ // of the failed Authorization.
+ Errors []error
+}
+
+func (a *AuthorizationError) Error() string {
+ e := make([]string, len(a.Errors))
+ for i, err := range a.Errors {
+ e[i] = err.Error()
+ }
+
+ if a.Identifier != "" {
+ return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; "))
+ }
+
+ return fmt.Sprintf("acme: authorization error: %s", strings.Join(e, "; "))
+}
+
+// OrderError is returned from Client's order related methods.
+// It indicates the order is unusable and the clients should start over with
+// AuthorizeOrder. A Problem description may be provided with details on
+// what caused the order to become unusable.
+//
+// The clients can still fetch the order object from CA using GetOrder
+// to inspect its state.
+type OrderError struct {
+ OrderURL string
+ Status string
+ // Problem is the error that occurred while processing the order.
+ Problem *Error
+}
+
+func (oe *OrderError) Error() string {
+ str := fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status)
+ if oe.Problem != nil {
+ str += fmt.Sprintf("; problem: %s", oe.Problem)
+ }
+ return str
+}
+
+// RateLimit reports whether err represents a rate limit error and
+// any Retry-After duration returned by the server.
+//
+// See the following for more details on rate limiting:
+// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6
+func RateLimit(err error) (time.Duration, bool) {
+ e, ok := err.(*Error)
+ if !ok {
+ return 0, false
+ }
+ // Some CA implementations may return incorrect values.
+ // Use case-insensitive comparison.
+ if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") {
+ return 0, false
+ }
+ if e.Header == nil {
+ return 0, true
+ }
+ return retryAfter(e.Header.Get("Retry-After")), true
+}
+
+// Account is a user account. It is associated with a private key.
+// Non-RFC 8555 fields are empty when interfacing with a compliant CA.
+type Account struct {
+ // URI is the account unique ID, which is also a URL used to retrieve
+ // account data from the CA.
+ // When interfacing with RFC 8555-compliant CAs, URI is the "kid" field
+ // value in JWS signed requests.
+ URI string
+
+ // Contact is a slice of contact info used during registration.
+ // See https://tools.ietf.org/html/rfc8555#section-7.3 for supported
+ // formats.
+ Contact []string
+
+ // Status indicates current account status as returned by the CA.
+ // Possible values are StatusValid, StatusDeactivated, and StatusRevoked.
+ Status string
+
+ // OrdersURL is a URL from which a list of orders submitted by this account
+ // can be fetched.
+ OrdersURL string
+
+ // The terms user has agreed to.
+ // A value not matching CurrentTerms indicates that the user hasn't agreed
+ // to the actual Terms of Service of the CA.
+ //
+ // It is non-RFC 8555 compliant. Package users can store the ToS they agree to
+ // during Client's Register call in the prompt callback function.
+ AgreedTerms string
+
+ // Actual terms of a CA.
+ //
+ // It is non-RFC 8555 compliant. Use Directory's Terms field.
+ // When a CA updates their terms and requires an account agreement,
+ // a URL at which instructions to do so is available in Error's Instance field.
+ CurrentTerms string
+
+ // Authz is the authorization URL used to initiate a new authz flow.
+ //
+ // It is non-RFC 8555 compliant. Use Directory's AuthzURL or OrderURL.
+ Authz string
+
+ // Authorizations is a URI from which a list of authorizations
+ // granted to this account can be fetched via a GET request.
+ //
+ // It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
+ Authorizations string
+
+ // Certificates is a URI from which a list of certificates
+ // issued for this account can be fetched via a GET request.
+ //
+ // It is non-RFC 8555 compliant and is obsoleted by OrdersURL.
+ Certificates string
+
+ // ExternalAccountBinding represents an arbitrary binding to an account of
+ // the CA which the ACME server is tied to.
+ // See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details.
+ ExternalAccountBinding *ExternalAccountBinding
+}
+
+// ExternalAccountBinding contains the data needed to form a request with
+// an external account binding.
+// See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details.
+type ExternalAccountBinding struct {
+ // KID is the Key ID of the symmetric MAC key that the CA provides to
+ // identify an external account from ACME.
+ KID string
+
+ // Key is the bytes of the symmetric key that the CA provides to identify
+ // the account. Key must correspond to the KID.
+ Key []byte
+}
+
+func (e *ExternalAccountBinding) String() string {
+ return fmt.Sprintf("&{KID: %q, Key: redacted}", e.KID)
+}
+
+// Directory is ACME server discovery data.
+// See https://tools.ietf.org/html/rfc8555#section-7.1.1 for more details.
+type Directory struct {
+ // NonceURL indicates an endpoint where to fetch fresh nonce values from.
+ NonceURL string
+
+ // RegURL is an account endpoint URL, allowing for creating new accounts.
+ // Pre-RFC 8555 CAs also allow modifying existing accounts at this URL.
+ RegURL string
+
+ // OrderURL is used to initiate the certificate issuance flow
+ // as described in RFC 8555.
+ OrderURL string
+
+ // AuthzURL is used to initiate identifier pre-authorization flow.
+ // Empty string indicates the flow is unsupported by the CA.
+ AuthzURL string
+
+ // CertURL is a new certificate issuance endpoint URL.
+ // It is non-RFC 8555 compliant and is obsoleted by OrderURL.
+ CertURL string
+
+ // RevokeURL is used to initiate a certificate revocation flow.
+ RevokeURL string
+
+ // KeyChangeURL allows to perform account key rollover flow.
+ KeyChangeURL string
+
+ // Terms is a URI identifying the current terms of service.
+ Terms string
+
+ // Website is an HTTP or HTTPS URL locating a website
+ // providing more information about the ACME server.
+ Website string
+
+ // CAA consists of lowercase hostname elements, which the ACME server
+ // recognises as referring to itself for the purposes of CAA record validation
+ // as defined in RFC 6844.
+ CAA []string
+
+ // ExternalAccountRequired indicates that the CA requires for all account-related
+ // requests to include external account binding information.
+ ExternalAccountRequired bool
+}
+
+// Order represents a client's request for a certificate.
+// It tracks the request flow progress through to issuance.
+type Order struct {
+ // URI uniquely identifies an order.
+ URI string
+
+ // Status represents the current status of the order.
+ // It indicates which action the client should take.
+ //
+ // Possible values are StatusPending, StatusReady, StatusProcessing, StatusValid and StatusInvalid.
+ // Pending means the CA does not believe that the client has fulfilled the requirements.
+ // Ready indicates that the client has fulfilled all the requirements and can submit a CSR
+ // to obtain a certificate. This is done with Client's CreateOrderCert.
+ // Processing means the certificate is being issued.
+ // Valid indicates the CA has issued the certificate. It can be downloaded
+ // from the Order's CertURL. This is done with Client's FetchCert.
+ // Invalid means the certificate will not be issued. Users should consider this order
+ // abandoned.
+ Status string
+
+ // Expires is the timestamp after which CA considers this order invalid.
+ Expires time.Time
+
+ // Identifiers contains all identifier objects which the order pertains to.
+ Identifiers []AuthzID
+
+ // NotBefore is the requested value of the notBefore field in the certificate.
+ NotBefore time.Time
+
+ // NotAfter is the requested value of the notAfter field in the certificate.
+ NotAfter time.Time
+
+ // AuthzURLs represents authorizations to complete before a certificate
+ // for identifiers specified in the order can be issued.
+ // It also contains unexpired authorizations that the client has completed
+ // in the past.
+ //
+ // Authorization objects can be fetched using Client's GetAuthorization method.
+ //
+ // The required authorizations are dictated by CA policies.
+ // There may not be a 1:1 relationship between the identifiers and required authorizations.
+ // Required authorizations can be identified by their StatusPending status.
+ //
+ // For orders in the StatusValid or StatusInvalid state these are the authorizations
+ // which were completed.
+ AuthzURLs []string
+
+ // FinalizeURL is the endpoint at which a CSR is submitted to obtain a certificate
+ // once all the authorizations are satisfied.
+ FinalizeURL string
+
+ // CertURL points to the certificate that has been issued in response to this order.
+ CertURL string
+
+ // The error that occurred while processing the order as received from a CA, if any.
+ Error *Error
+}
+
+// OrderOption allows customizing Client.AuthorizeOrder call.
+type OrderOption interface {
+ privateOrderOpt()
+}
+
+// WithOrderNotBefore sets order's NotBefore field.
+func WithOrderNotBefore(t time.Time) OrderOption {
+ return orderNotBeforeOpt(t)
+}
+
+// WithOrderNotAfter sets order's NotAfter field.
+func WithOrderNotAfter(t time.Time) OrderOption {
+ return orderNotAfterOpt(t)
+}
+
+type orderNotBeforeOpt time.Time
+
+func (orderNotBeforeOpt) privateOrderOpt() {}
+
+type orderNotAfterOpt time.Time
+
+func (orderNotAfterOpt) privateOrderOpt() {}
+
+// Authorization encodes an authorization response.
+type Authorization struct {
+ // URI uniquely identifies a authorization.
+ URI string
+
+ // Status is the current status of an authorization.
+ // Possible values are StatusPending, StatusValid, StatusInvalid, StatusDeactivated,
+ // StatusExpired and StatusRevoked.
+ Status string
+
+ // Identifier is what the account is authorized to represent.
+ Identifier AuthzID
+
+ // The timestamp after which the CA considers the authorization invalid.
+ Expires time.Time
+
+ // Wildcard is true for authorizations of a wildcard domain name.
+ Wildcard bool
+
+ // Challenges that the client needs to fulfill in order to prove possession
+ // of the identifier (for pending authorizations).
+ // For valid authorizations, the challenge that was validated.
+ // For invalid authorizations, the challenge that was attempted and failed.
+ //
+ // RFC 8555 compatible CAs require users to fuflfill only one of the challenges.
+ Challenges []*Challenge
+
+ // A collection of sets of challenges, each of which would be sufficient
+ // to prove possession of the identifier.
+ // Clients must complete a set of challenges that covers at least one set.
+ // Challenges are identified by their indices in the challenges array.
+ // If this field is empty, the client needs to complete all challenges.
+ //
+ // This field is unused in RFC 8555.
+ Combinations [][]int
+}
+
+// AuthzID is an identifier that an account is authorized to represent.
+type AuthzID struct {
+ Type string // The type of identifier, "dns" or "ip".
+ Value string // The identifier itself, e.g. "example.org".
+}
+
+// DomainIDs creates a slice of AuthzID with "dns" identifier type.
+func DomainIDs(names ...string) []AuthzID {
+ a := make([]AuthzID, len(names))
+ for i, v := range names {
+ a[i] = AuthzID{Type: "dns", Value: v}
+ }
+ return a
+}
+
+// IPIDs creates a slice of AuthzID with "ip" identifier type.
+// Each element of addr is textual form of an address as defined
+// in RFC 1123 Section 2.1 for IPv4 and in RFC 5952 Section 4 for IPv6.
+func IPIDs(addr ...string) []AuthzID {
+ a := make([]AuthzID, len(addr))
+ for i, v := range addr {
+ a[i] = AuthzID{Type: "ip", Value: v}
+ }
+ return a
+}
+
+// wireAuthzID is ACME JSON representation of authorization identifier objects.
+type wireAuthzID struct {
+ Type string `json:"type"`
+ Value string `json:"value"`
+}
+
+// wireAuthz is ACME JSON representation of Authorization objects.
+type wireAuthz struct {
+ Identifier wireAuthzID
+ Status string
+ Expires time.Time
+ Wildcard bool
+ Challenges []wireChallenge
+ Combinations [][]int
+ Error *wireError
+}
+
+func (z *wireAuthz) authorization(uri string) *Authorization {
+ a := &Authorization{
+ URI: uri,
+ Status: z.Status,
+ Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value},
+ Expires: z.Expires,
+ Wildcard: z.Wildcard,
+ Challenges: make([]*Challenge, len(z.Challenges)),
+ Combinations: z.Combinations, // shallow copy
+ }
+ for i, v := range z.Challenges {
+ a.Challenges[i] = v.challenge()
+ }
+ return a
+}
+
+func (z *wireAuthz) error(uri string) *AuthorizationError {
+ err := &AuthorizationError{
+ URI: uri,
+ Identifier: z.Identifier.Value,
+ }
+
+ if z.Error != nil {
+ err.Errors = append(err.Errors, z.Error.error(nil))
+ }
+
+ for _, raw := range z.Challenges {
+ if raw.Error != nil {
+ err.Errors = append(err.Errors, raw.Error.error(nil))
+ }
+ }
+
+ return err
+}
+
+// Challenge encodes a returned CA challenge.
+// Its Error field may be non-nil if the challenge is part of an Authorization
+// with StatusInvalid.
+type Challenge struct {
+ // Type is the challenge type, e.g. "http-01", "tls-alpn-01", "dns-01".
+ Type string
+
+ // URI is where a challenge response can be posted to.
+ URI string
+
+ // Token is a random value that uniquely identifies the challenge.
+ Token string
+
+ // Status identifies the status of this challenge.
+ // In RFC 8555, possible values are StatusPending, StatusProcessing, StatusValid,
+ // and StatusInvalid.
+ Status string
+
+ // Validated is the time at which the CA validated this challenge.
+ // Always zero value in pre-RFC 8555.
+ Validated time.Time
+
+ // Error indicates the reason for an authorization failure
+ // when this challenge was used.
+ // The type of a non-nil value is *Error.
+ Error error
+
+ // Payload is the JSON-formatted payload that the client sends
+ // to the server to indicate it is ready to respond to the challenge.
+ // When unset, it defaults to an empty JSON object: {}.
+ // For most challenges, the client must not set Payload,
+ // see https://tools.ietf.org/html/rfc8555#section-7.5.1.
+ // Payload is used only for newer challenges (such as "device-attest-01")
+ // where the client must send additional data for the server to validate
+ // the challenge.
+ Payload json.RawMessage
+}
+
+// wireChallenge is ACME JSON challenge representation.
+type wireChallenge struct {
+ URL string `json:"url"` // RFC
+ URI string `json:"uri"` // pre-RFC
+ Type string
+ Token string
+ Status string
+ Validated time.Time
+ Error *wireError
+}
+
+func (c *wireChallenge) challenge() *Challenge {
+ v := &Challenge{
+ URI: c.URL,
+ Type: c.Type,
+ Token: c.Token,
+ Status: c.Status,
+ }
+ if v.URI == "" {
+ v.URI = c.URI // c.URL was empty; use legacy
+ }
+ if v.Status == "" {
+ v.Status = StatusPending
+ }
+ if c.Error != nil {
+ v.Error = c.Error.error(nil)
+ }
+ return v
+}
+
+// wireError is a subset of fields of the Problem Details object
+// as described in https://tools.ietf.org/html/rfc7807#section-3.1.
+type wireError struct {
+ Status int
+ Type string
+ Detail string
+ Instance string
+ Subproblems []Subproblem
+}
+
+func (e *wireError) error(h http.Header) *Error {
+ err := &Error{
+ StatusCode: e.Status,
+ ProblemType: e.Type,
+ Detail: e.Detail,
+ Instance: e.Instance,
+ Header: h,
+ Subproblems: e.Subproblems,
+ }
+ return err
+}
+
+// CertOption is an optional argument type for the TLS ChallengeCert methods for
+// customizing a temporary certificate for TLS-based challenges.
+type CertOption interface {
+ privateCertOpt()
+}
+
+// WithKey creates an option holding a private/public key pair.
+// The private part signs a certificate, and the public part represents the signee.
+func WithKey(key crypto.Signer) CertOption {
+ return &certOptKey{key}
+}
+
+type certOptKey struct {
+ key crypto.Signer
+}
+
+func (*certOptKey) privateCertOpt() {}
+
+// WithTemplate creates an option for specifying a certificate template.
+// See x509.CreateCertificate for template usage details.
+//
+// In TLS ChallengeCert methods, the template is also used as parent,
+// resulting in a self-signed certificate.
+// The DNSNames or IPAddresses fields of t are always overwritten for tls-alpn challenge certs.
+func WithTemplate(t *x509.Certificate) CertOption {
+ return (*certOptTemplate)(t)
+}
+
+type certOptTemplate x509.Certificate
+
+func (*certOptTemplate) privateCertOpt() {}
diff --git a/local_crypto_patch/contents/acme/types_test.go b/local_crypto_patch/contents/acme/types_test.go
new file mode 100644
index 0000000000..2a59f6f0b5
--- /dev/null
+++ b/local_crypto_patch/contents/acme/types_test.go
@@ -0,0 +1,235 @@
+// 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 acme
+
+import (
+ "errors"
+ "net/http"
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestExternalAccountBindingString(t *testing.T) {
+ eab := ExternalAccountBinding{
+ KID: "kid",
+ Key: []byte("key"),
+ }
+ got := eab.String()
+ want := `&{KID: "kid", Key: redacted}`
+ if got != want {
+ t.Errorf("eab.String() = %q, want: %q", got, want)
+ }
+}
+
+func TestRateLimit(t *testing.T) {
+ now := time.Date(2017, 04, 27, 10, 0, 0, 0, time.UTC)
+ f := timeNow
+ defer func() { timeNow = f }()
+ timeNow = func() time.Time { return now }
+
+ h120, hTime := http.Header{}, http.Header{}
+ h120.Set("Retry-After", "120")
+ hTime.Set("Retry-After", "Tue Apr 27 11:00:00 2017")
+
+ err1 := &Error{
+ ProblemType: "urn:ietf:params:acme:error:nolimit",
+ Header: h120,
+ }
+ err2 := &Error{
+ ProblemType: "urn:ietf:params:acme:error:rateLimited",
+ Header: h120,
+ }
+ err3 := &Error{
+ ProblemType: "urn:ietf:params:acme:error:rateLimited",
+ Header: nil,
+ }
+ err4 := &Error{
+ ProblemType: "urn:ietf:params:acme:error:rateLimited",
+ Header: hTime,
+ }
+
+ tt := []struct {
+ err error
+ res time.Duration
+ ok bool
+ }{
+ {nil, 0, false},
+ {errors.New("dummy"), 0, false},
+ {err1, 0, false},
+ {err2, 2 * time.Minute, true},
+ {err3, 0, true},
+ {err4, time.Hour, true},
+ }
+ for i, test := range tt {
+ res, ok := RateLimit(test.err)
+ if ok != test.ok {
+ t.Errorf("%d: RateLimit(%+v): ok = %v; want %v", i, test.err, ok, test.ok)
+ continue
+ }
+ if res != test.res {
+ t.Errorf("%d: RateLimit(%+v) = %v; want %v", i, test.err, res, test.res)
+ }
+ }
+}
+
+func TestAuthorizationError(t *testing.T) {
+ tests := []struct {
+ desc string
+ err *AuthorizationError
+ msg string
+ }{
+ {
+ desc: "when auth error identifier is set",
+ err: &AuthorizationError{
+ Identifier: "domain.com",
+ Errors: []error{
+ (&wireError{
+ Status: 403,
+ Type: "urn:ietf:params:acme:error:caa",
+ Detail: "CAA record for domain.com prevents issuance",
+ }).error(nil),
+ },
+ },
+ msg: "acme: authorization error for domain.com: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
+ },
+
+ {
+ desc: "when auth error identifier is unset",
+ err: &AuthorizationError{
+ Errors: []error{
+ (&wireError{
+ Status: 403,
+ Type: "urn:ietf:params:acme:error:caa",
+ Detail: "CAA record for domain.com prevents issuance",
+ }).error(nil),
+ },
+ },
+ msg: "acme: authorization error: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
+ },
+ }
+
+ for _, tt := range tests {
+ if tt.err.Error() != tt.msg {
+ t.Errorf("got: %s\nwant: %s", tt.err, tt.msg)
+ }
+ }
+}
+
+func TestSubproblems(t *testing.T) {
+ tests := []struct {
+ wire wireError
+ expectedOut Error
+ }{
+ {
+ wire: wireError{
+ Status: 1,
+ Type: "urn:error",
+ Detail: "it's an error",
+ },
+ expectedOut: Error{
+ StatusCode: 1,
+ ProblemType: "urn:error",
+ Detail: "it's an error",
+ },
+ },
+ {
+ wire: wireError{
+ Status: 1,
+ Type: "urn:error",
+ Detail: "it's an error",
+ Subproblems: []Subproblem{
+ {
+ Type: "urn:error:sub",
+ Detail: "it's a subproblem",
+ },
+ },
+ },
+ expectedOut: Error{
+ StatusCode: 1,
+ ProblemType: "urn:error",
+ Detail: "it's an error",
+ Subproblems: []Subproblem{
+ {
+ Type: "urn:error:sub",
+ Detail: "it's a subproblem",
+ },
+ },
+ },
+ },
+ {
+ wire: wireError{
+ Status: 1,
+ Type: "urn:error",
+ Detail: "it's an error",
+ Subproblems: []Subproblem{
+ {
+ Type: "urn:error:sub",
+ Detail: "it's a subproblem",
+ Identifier: &AuthzID{Type: "dns", Value: "example"},
+ },
+ },
+ },
+ expectedOut: Error{
+ StatusCode: 1,
+ ProblemType: "urn:error",
+ Detail: "it's an error",
+ Subproblems: []Subproblem{
+ {
+ Type: "urn:error:sub",
+ Detail: "it's a subproblem",
+ Identifier: &AuthzID{Type: "dns", Value: "example"},
+ },
+ },
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ out := tc.wire.error(nil)
+ if !reflect.DeepEqual(*out, tc.expectedOut) {
+ t.Errorf("Unexpected error: wanted %v, got %v", tc.expectedOut, *out)
+ }
+ }
+}
+
+func TestErrorStringerWithSubproblems(t *testing.T) {
+ err := Error{
+ StatusCode: 1,
+ ProblemType: "urn:error",
+ Detail: "it's an error",
+ Subproblems: []Subproblem{
+ {
+ Type: "urn:error:sub",
+ Detail: "it's a subproblem",
+ },
+ {
+ Type: "urn:error:sub",
+ Detail: "it's a subproblem",
+ Identifier: &AuthzID{Type: "dns", Value: "example"},
+ },
+ },
+ }
+ expectedStr := "1 urn:error: it's an error; subproblems:\n\turn:error:sub: it's a subproblem\n\turn:error:sub: [dns: example] it's a subproblem"
+ if err.Error() != expectedStr {
+ t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
+ }
+}
+
+func TestOrderErrorProblem(t *testing.T) {
+ oe := &OrderError{
+ OrderURL: "url",
+ Status: "invalid",
+ Problem: &Error{
+ StatusCode: 400,
+ ProblemType: "type",
+ Detail: "detail",
+ },
+ }
+ want := "acme: order url status: invalid; problem: 400 type: detail"
+ if got := oe.Error(); got != want {
+ t.Errorf("oe.Error() = %q, want %q", got, want)
+ }
+}
diff --git a/local_crypto_patch/contents/argon2/argon2.go b/local_crypto_patch/contents/argon2/argon2.go
new file mode 100644
index 0000000000..2b65ec91ac
--- /dev/null
+++ b/local_crypto_patch/contents/argon2/argon2.go
@@ -0,0 +1,287 @@
+// 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 argon2 implements the key derivation function Argon2.
+// Argon2 was selected as the winner of the Password Hashing Competition and can
+// be used to derive cryptographic keys from passwords.
+//
+// For a detailed specification of Argon2 see [argon2-specs.pdf].
+//
+// If you aren't sure which function you need, use Argon2id (IDKey) and
+// the parameter recommendations for your scenario.
+//
+// # Argon2i
+//
+// Argon2i (implemented by Key) is the side-channel resistant version of Argon2.
+// It uses data-independent memory access, which is preferred for password
+// hashing and password-based key derivation. Argon2i requires more passes over
+// memory than Argon2id to protect from trade-off attacks. The recommended
+// parameters (taken from [RFC 9106 Section 7.3]) for non-interactive operations are time=3 and to
+// use the maximum available memory.
+//
+// # Argon2id
+//
+// Argon2id (implemented by IDKey) is a hybrid version of Argon2 combining
+// Argon2i and Argon2d. It uses data-independent memory access for the first
+// half of the first iteration over the memory and data-dependent memory access
+// for the rest. Argon2id is side-channel resistant and provides better brute-
+// force cost savings due to time-memory tradeoffs than Argon2i. The recommended
+// parameters for non-interactive operations (taken from [RFC 9106 Section 7.3]) are time=1 and to
+// use the maximum available memory.
+//
+// [argon2-specs.pdf]: https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
+// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
+package argon2
+
+import (
+ "encoding/binary"
+ "sync"
+
+ "golang.org/x/crypto/blake2b"
+)
+
+// The Argon2 version implemented by this package.
+const Version = 0x13
+
+const (
+ argon2d = iota
+ argon2i
+ argon2id
+)
+
+// Key derives a key from the password, salt, and cost parameters using Argon2i
+// returning a byte slice of length keyLen that can be used as cryptographic
+// key. The CPU cost and parallelism degree must be greater than zero.
+//
+// For example, you can get a derived key for e.g. AES-256 (which needs a
+// 32-byte key) by doing:
+//
+// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32)
+//
+// [RFC 9106 Section 7.3] recommends time=3, and memory=32*1024 as a sensible number.
+// If using that amount of memory (32 MB) is not possible in some contexts then
+// the time parameter can be increased to compensate.
+//
+// The time parameter specifies the number of passes over the memory and the
+// memory parameter specifies the size of the memory in KiB. For example
+// memory=32*1024 sets the memory cost to ~32 MB. The number of threads can be
+// adjusted to the number of available CPUs. The cost parameters should be
+// increased as memory latency and CPU parallelism increases. Remember to get a
+// good random salt.
+//
+// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
+func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
+ return deriveKey(argon2i, password, salt, nil, nil, time, memory, threads, keyLen)
+}
+
+// IDKey derives a key from the password, salt, and cost parameters using
+// Argon2id returning a byte slice of length keyLen that can be used as
+// cryptographic key. The CPU cost and parallelism degree must be greater than
+// zero.
+//
+// For example, you can get a derived key for e.g. AES-256 (which needs a
+// 32-byte key) by doing:
+//
+// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32)
+//
+// [RFC 9106 Section 7.3] recommends time=1, and memory=64*1024 as a sensible number.
+// If using that amount of memory (64 MB) is not possible in some contexts then
+// the time parameter can be increased to compensate.
+//
+// The time parameter specifies the number of passes over the memory and the
+// memory parameter specifies the size of the memory in KiB. For example
+// memory=64*1024 sets the memory cost to ~64 MB. The number of threads can be
+// adjusted to the numbers of available CPUs. The cost parameters should be
+// increased as memory latency and CPU parallelism increases. Remember to get a
+// good random salt.
+//
+// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
+func IDKey(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
+ return deriveKey(argon2id, password, salt, nil, nil, time, memory, threads, keyLen)
+}
+
+func deriveKey(mode int, password, salt, secret, data []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
+ if time < 1 {
+ panic("argon2: number of rounds too small")
+ }
+ if threads < 1 {
+ panic("argon2: parallelism degree too low")
+ }
+ h0 := initHash(password, salt, secret, data, time, memory, uint32(threads), keyLen, mode)
+
+ memory = memory / (syncPoints * uint32(threads)) * (syncPoints * uint32(threads))
+ if memory < 2*syncPoints*uint32(threads) {
+ memory = 2 * syncPoints * uint32(threads)
+ }
+ B := initBlocks(&h0, memory, uint32(threads))
+ processBlocks(B, time, memory, uint32(threads), mode)
+ return extractKey(B, memory, uint32(threads), keyLen)
+}
+
+const (
+ blockLength = 128
+ syncPoints = 4
+)
+
+type block [blockLength]uint64
+
+func initHash(password, salt, key, data []byte, time, memory, threads, keyLen uint32, mode int) [blake2b.Size + 8]byte {
+ var (
+ h0 [blake2b.Size + 8]byte
+ params [24]byte
+ tmp [4]byte
+ )
+
+ b2, _ := blake2b.New512(nil)
+ binary.LittleEndian.PutUint32(params[0:4], threads)
+ binary.LittleEndian.PutUint32(params[4:8], keyLen)
+ binary.LittleEndian.PutUint32(params[8:12], memory)
+ binary.LittleEndian.PutUint32(params[12:16], time)
+ binary.LittleEndian.PutUint32(params[16:20], uint32(Version))
+ binary.LittleEndian.PutUint32(params[20:24], uint32(mode))
+ b2.Write(params[:])
+ binary.LittleEndian.PutUint32(tmp[:], uint32(len(password)))
+ b2.Write(tmp[:])
+ b2.Write(password)
+ binary.LittleEndian.PutUint32(tmp[:], uint32(len(salt)))
+ b2.Write(tmp[:])
+ b2.Write(salt)
+ binary.LittleEndian.PutUint32(tmp[:], uint32(len(key)))
+ b2.Write(tmp[:])
+ b2.Write(key)
+ binary.LittleEndian.PutUint32(tmp[:], uint32(len(data)))
+ b2.Write(tmp[:])
+ b2.Write(data)
+ b2.Sum(h0[:0])
+ return h0
+}
+
+func initBlocks(h0 *[blake2b.Size + 8]byte, memory, threads uint32) []block {
+ var block0 [1024]byte
+ B := make([]block, memory)
+ for lane := uint32(0); lane < threads; lane++ {
+ j := lane * (memory / threads)
+ binary.LittleEndian.PutUint32(h0[blake2b.Size+4:], lane)
+
+ binary.LittleEndian.PutUint32(h0[blake2b.Size:], 0)
+ blake2bHash(block0[:], h0[:])
+ for i := range B[j+0] {
+ B[j+0][i] = binary.LittleEndian.Uint64(block0[i*8:])
+ }
+
+ binary.LittleEndian.PutUint32(h0[blake2b.Size:], 1)
+ blake2bHash(block0[:], h0[:])
+ for i := range B[j+1] {
+ B[j+1][i] = binary.LittleEndian.Uint64(block0[i*8:])
+ }
+ }
+ return B
+}
+
+func processBlocks(B []block, time, memory, threads uint32, mode int) {
+ lanes := memory / threads
+ segments := lanes / syncPoints
+
+ processSegment := func(n, slice, lane uint32, wg *sync.WaitGroup) {
+ var addresses, in, zero block
+ if mode == argon2i || (mode == argon2id && n == 0 && slice < syncPoints/2) {
+ in[0] = uint64(n)
+ in[1] = uint64(lane)
+ in[2] = uint64(slice)
+ in[3] = uint64(memory)
+ in[4] = uint64(time)
+ in[5] = uint64(mode)
+ }
+
+ index := uint32(0)
+ if n == 0 && slice == 0 {
+ index = 2 // we have already generated the first two blocks
+ if mode == argon2i || mode == argon2id {
+ in[6]++
+ processBlock(&addresses, &in, &zero)
+ processBlock(&addresses, &addresses, &zero)
+ }
+ }
+
+ offset := lane*lanes + slice*segments + index
+ var random uint64
+ for index < segments {
+ prev := offset - 1
+ if index == 0 && slice == 0 {
+ prev += lanes // last block in lane
+ }
+ if mode == argon2i || (mode == argon2id && n == 0 && slice < syncPoints/2) {
+ if index%blockLength == 0 {
+ in[6]++
+ processBlock(&addresses, &in, &zero)
+ processBlock(&addresses, &addresses, &zero)
+ }
+ random = addresses[index%blockLength]
+ } else {
+ random = B[prev][0]
+ }
+ newOffset := indexAlpha(random, lanes, segments, threads, n, slice, lane, index)
+ processBlockXOR(&B[offset], &B[prev], &B[newOffset])
+ index, offset = index+1, offset+1
+ }
+ wg.Done()
+ }
+
+ for n := uint32(0); n < time; n++ {
+ for slice := uint32(0); slice < syncPoints; slice++ {
+ var wg sync.WaitGroup
+ for lane := uint32(0); lane < threads; lane++ {
+ wg.Add(1)
+ go processSegment(n, slice, lane, &wg)
+ }
+ wg.Wait()
+ }
+ }
+
+}
+
+func extractKey(B []block, memory, threads, keyLen uint32) []byte {
+ lanes := memory / threads
+ for lane := uint32(0); lane < threads-1; lane++ {
+ for i, v := range B[(lane*lanes)+lanes-1] {
+ B[memory-1][i] ^= v
+ }
+ }
+
+ var block [1024]byte
+ for i, v := range B[memory-1] {
+ binary.LittleEndian.PutUint64(block[i*8:], v)
+ }
+ key := make([]byte, keyLen)
+ blake2bHash(key, block[:])
+ return key
+}
+
+func indexAlpha(rand uint64, lanes, segments, threads, n, slice, lane, index uint32) uint32 {
+ refLane := uint32(rand>>32) % threads
+ if n == 0 && slice == 0 {
+ refLane = lane
+ }
+ m, s := 3*segments, ((slice+1)%syncPoints)*segments
+ if lane == refLane {
+ m += index
+ }
+ if n == 0 {
+ m, s = slice*segments, 0
+ if slice == 0 || lane == refLane {
+ m += index
+ }
+ }
+ if index == 0 || lane == refLane {
+ m--
+ }
+ return phi(rand, uint64(m), uint64(s), refLane, lanes)
+}
+
+func phi(rand, m, s uint64, lane, lanes uint32) uint32 {
+ p := rand & 0xFFFFFFFF
+ p = (p * p) >> 32
+ p = (p * m) >> 32
+ return lane*lanes + uint32((s+m-(p+1))%uint64(lanes))
+}
diff --git a/local_crypto_patch/contents/argon2/argon2_test.go b/local_crypto_patch/contents/argon2/argon2_test.go
new file mode 100644
index 0000000000..775b97a404
--- /dev/null
+++ b/local_crypto_patch/contents/argon2/argon2_test.go
@@ -0,0 +1,233 @@
+// 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 argon2
+
+import (
+ "bytes"
+ "encoding/hex"
+ "testing"
+)
+
+var (
+ genKatPassword = []byte{
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ }
+ genKatSalt = []byte{0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02}
+ genKatSecret = []byte{0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03}
+ genKatAAD = []byte{0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}
+)
+
+func TestArgon2(t *testing.T) {
+ defer func(sse4 bool) { useSSE4 = sse4 }(useSSE4)
+
+ if useSSE4 {
+ t.Log("SSE4.1 version")
+ testArgon2i(t)
+ testArgon2d(t)
+ testArgon2id(t)
+ useSSE4 = false
+ }
+ t.Log("generic version")
+ testArgon2i(t)
+ testArgon2d(t)
+ testArgon2id(t)
+}
+
+func testArgon2d(t *testing.T) {
+ want := []byte{
+ 0x51, 0x2b, 0x39, 0x1b, 0x6f, 0x11, 0x62, 0x97,
+ 0x53, 0x71, 0xd3, 0x09, 0x19, 0x73, 0x42, 0x94,
+ 0xf8, 0x68, 0xe3, 0xbe, 0x39, 0x84, 0xf3, 0xc1,
+ 0xa1, 0x3a, 0x4d, 0xb9, 0xfa, 0xbe, 0x4a, 0xcb,
+ }
+ hash := deriveKey(argon2d, genKatPassword, genKatSalt, genKatSecret, genKatAAD, 3, 32, 4, 32)
+ if !bytes.Equal(hash, want) {
+ t.Errorf("derived key does not match - got: %s , want: %s", hex.EncodeToString(hash), hex.EncodeToString(want))
+ }
+}
+
+func testArgon2i(t *testing.T) {
+ want := []byte{
+ 0xc8, 0x14, 0xd9, 0xd1, 0xdc, 0x7f, 0x37, 0xaa,
+ 0x13, 0xf0, 0xd7, 0x7f, 0x24, 0x94, 0xbd, 0xa1,
+ 0xc8, 0xde, 0x6b, 0x01, 0x6d, 0xd3, 0x88, 0xd2,
+ 0x99, 0x52, 0xa4, 0xc4, 0x67, 0x2b, 0x6c, 0xe8,
+ }
+ hash := deriveKey(argon2i, genKatPassword, genKatSalt, genKatSecret, genKatAAD, 3, 32, 4, 32)
+ if !bytes.Equal(hash, want) {
+ t.Errorf("derived key does not match - got: %s , want: %s", hex.EncodeToString(hash), hex.EncodeToString(want))
+ }
+}
+
+func testArgon2id(t *testing.T) {
+ want := []byte{
+ 0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c,
+ 0x08, 0xc0, 0x37, 0xa3, 0x4a, 0x8b, 0x53, 0xc9,
+ 0xd0, 0x1e, 0xf0, 0x45, 0x2d, 0x75, 0xb6, 0x5e,
+ 0xb5, 0x25, 0x20, 0xe9, 0x6b, 0x01, 0xe6, 0x59,
+ }
+ hash := deriveKey(argon2id, genKatPassword, genKatSalt, genKatSecret, genKatAAD, 3, 32, 4, 32)
+ if !bytes.Equal(hash, want) {
+ t.Errorf("derived key does not match - got: %s , want: %s", hex.EncodeToString(hash), hex.EncodeToString(want))
+ }
+}
+
+func TestVectors(t *testing.T) {
+ password, salt := []byte("password"), []byte("somesalt")
+ for i, v := range testVectors {
+ want, err := hex.DecodeString(v.hash)
+ if err != nil {
+ t.Fatalf("Test %d: failed to decode hash: %v", i, err)
+ }
+ hash := deriveKey(v.mode, password, salt, nil, nil, v.time, v.memory, v.threads, uint32(len(want)))
+ if !bytes.Equal(hash, want) {
+ t.Errorf("Test %d - got: %s want: %s", i, hex.EncodeToString(hash), hex.EncodeToString(want))
+ }
+ }
+}
+
+func benchmarkArgon2(mode int, time, memory uint32, threads uint8, keyLen uint32, b *testing.B) {
+ password := []byte("password")
+ salt := []byte("choosing random salts is hard")
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ deriveKey(mode, password, salt, nil, nil, time, memory, threads, keyLen)
+ }
+}
+
+func BenchmarkArgon2i(b *testing.B) {
+ b.Run(" Time: 3 Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2i, 3, 32*1024, 1, 32, b) })
+ b.Run(" Time: 4 Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2i, 4, 32*1024, 1, 32, b) })
+ b.Run(" Time: 5 Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2i, 5, 32*1024, 1, 32, b) })
+ b.Run(" Time: 3 Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2i, 3, 64*1024, 4, 32, b) })
+ b.Run(" Time: 4 Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2i, 4, 64*1024, 4, 32, b) })
+ b.Run(" Time: 5 Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2i, 5, 64*1024, 4, 32, b) })
+}
+
+func BenchmarkArgon2d(b *testing.B) {
+ b.Run(" Time: 3, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2d, 3, 32*1024, 1, 32, b) })
+ b.Run(" Time: 4, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2d, 4, 32*1024, 1, 32, b) })
+ b.Run(" Time: 5, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2d, 5, 32*1024, 1, 32, b) })
+ b.Run(" Time: 3, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2d, 3, 64*1024, 4, 32, b) })
+ b.Run(" Time: 4, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2d, 4, 64*1024, 4, 32, b) })
+ b.Run(" Time: 5, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2d, 5, 64*1024, 4, 32, b) })
+}
+
+func BenchmarkArgon2id(b *testing.B) {
+ b.Run(" Time: 3, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2id, 3, 32*1024, 1, 32, b) })
+ b.Run(" Time: 4, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2id, 4, 32*1024, 1, 32, b) })
+ b.Run(" Time: 5, Memory: 32 MB, Threads: 1", func(b *testing.B) { benchmarkArgon2(argon2id, 5, 32*1024, 1, 32, b) })
+ b.Run(" Time: 3, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2id, 3, 64*1024, 4, 32, b) })
+ b.Run(" Time: 4, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2id, 4, 64*1024, 4, 32, b) })
+ b.Run(" Time: 5, Memory: 64 MB, Threads: 4", func(b *testing.B) { benchmarkArgon2(argon2id, 5, 64*1024, 4, 32, b) })
+}
+
+// Generated with the CLI of https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
+var testVectors = []struct {
+ mode int
+ time, memory uint32
+ threads uint8
+ hash string
+}{
+ {
+ mode: argon2i, time: 1, memory: 64, threads: 1,
+ hash: "b9c401d1844a67d50eae3967dc28870b22e508092e861a37",
+ },
+ {
+ mode: argon2d, time: 1, memory: 64, threads: 1,
+ hash: "8727405fd07c32c78d64f547f24150d3f2e703a89f981a19",
+ },
+ {
+ mode: argon2id, time: 1, memory: 64, threads: 1,
+ hash: "655ad15eac652dc59f7170a7332bf49b8469be1fdb9c28bb",
+ },
+ {
+ mode: argon2i, time: 2, memory: 64, threads: 1,
+ hash: "8cf3d8f76a6617afe35fac48eb0b7433a9a670ca4a07ed64",
+ },
+ {
+ mode: argon2d, time: 2, memory: 64, threads: 1,
+ hash: "3be9ec79a69b75d3752acb59a1fbb8b295a46529c48fbb75",
+ },
+ {
+ mode: argon2id, time: 2, memory: 64, threads: 1,
+ hash: "068d62b26455936aa6ebe60060b0a65870dbfa3ddf8d41f7",
+ },
+ {
+ mode: argon2i, time: 2, memory: 64, threads: 2,
+ hash: "2089f3e78a799720f80af806553128f29b132cafe40d059f",
+ },
+ {
+ mode: argon2d, time: 2, memory: 64, threads: 2,
+ hash: "68e2462c98b8bc6bb60ec68db418ae2c9ed24fc6748a40e9",
+ },
+ {
+ mode: argon2id, time: 2, memory: 64, threads: 2,
+ hash: "350ac37222f436ccb5c0972f1ebd3bf6b958bf2071841362",
+ },
+ {
+ mode: argon2i, time: 3, memory: 256, threads: 2,
+ hash: "f5bbf5d4c3836af13193053155b73ec7476a6a2eb93fd5e6",
+ },
+ {
+ mode: argon2d, time: 3, memory: 256, threads: 2,
+ hash: "f4f0669218eaf3641f39cc97efb915721102f4b128211ef2",
+ },
+ {
+ mode: argon2id, time: 3, memory: 256, threads: 2,
+ hash: "4668d30ac4187e6878eedeacf0fd83c5a0a30db2cc16ef0b",
+ },
+ {
+ mode: argon2i, time: 4, memory: 4096, threads: 4,
+ hash: "a11f7b7f3f93f02ad4bddb59ab62d121e278369288a0d0e7",
+ },
+ {
+ mode: argon2d, time: 4, memory: 4096, threads: 4,
+ hash: "935598181aa8dc2b720914aa6435ac8d3e3a4210c5b0fb2d",
+ },
+ {
+ mode: argon2id, time: 4, memory: 4096, threads: 4,
+ hash: "145db9733a9f4ee43edf33c509be96b934d505a4efb33c5a",
+ },
+ {
+ mode: argon2i, time: 4, memory: 1024, threads: 8,
+ hash: "0cdd3956aa35e6b475a7b0c63488822f774f15b43f6e6e17",
+ },
+ {
+ mode: argon2d, time: 4, memory: 1024, threads: 8,
+ hash: "83604fc2ad0589b9d055578f4d3cc55bc616df3578a896e9",
+ },
+ {
+ mode: argon2id, time: 4, memory: 1024, threads: 8,
+ hash: "8dafa8e004f8ea96bf7c0f93eecf67a6047476143d15577f",
+ },
+ {
+ mode: argon2i, time: 2, memory: 64, threads: 3,
+ hash: "5cab452fe6b8479c8661def8cd703b611a3905a6d5477fe6",
+ },
+ {
+ mode: argon2d, time: 2, memory: 64, threads: 3,
+ hash: "22474a423bda2ccd36ec9afd5119e5c8949798cadf659f51",
+ },
+ {
+ mode: argon2id, time: 2, memory: 64, threads: 3,
+ hash: "4a15b31aec7c2590b87d1f520be7d96f56658172deaa3079",
+ },
+ {
+ mode: argon2i, time: 3, memory: 1024, threads: 6,
+ hash: "d236b29c2b2a09babee842b0dec6aa1e83ccbdea8023dced",
+ },
+ {
+ mode: argon2d, time: 3, memory: 1024, threads: 6,
+ hash: "a3351b0319a53229152023d9206902f4ef59661cdca89481",
+ },
+ {
+ mode: argon2id, time: 3, memory: 1024, threads: 6,
+ hash: "1640b932f4b60e272f5d2207b9a9c626ffa1bd88d2349016",
+ },
+}
diff --git a/local_crypto_patch/contents/argon2/blake2b.go b/local_crypto_patch/contents/argon2/blake2b.go
new file mode 100644
index 0000000000..10f46948dc
--- /dev/null
+++ b/local_crypto_patch/contents/argon2/blake2b.go
@@ -0,0 +1,53 @@
+// 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 argon2
+
+import (
+ "encoding/binary"
+ "hash"
+
+ "golang.org/x/crypto/blake2b"
+)
+
+// blake2bHash computes an arbitrary long hash value of in
+// and writes the hash to out.
+func blake2bHash(out []byte, in []byte) {
+ var b2 hash.Hash
+ if n := len(out); n < blake2b.Size {
+ b2, _ = blake2b.New(n, nil)
+ } else {
+ b2, _ = blake2b.New512(nil)
+ }
+
+ var buffer [blake2b.Size]byte
+ binary.LittleEndian.PutUint32(buffer[:4], uint32(len(out)))
+ b2.Write(buffer[:4])
+ b2.Write(in)
+
+ if len(out) <= blake2b.Size {
+ b2.Sum(out[:0])
+ return
+ }
+
+ outLen := len(out)
+ b2.Sum(buffer[:0])
+ b2.Reset()
+ copy(out, buffer[:32])
+ out = out[32:]
+ for len(out) > blake2b.Size {
+ b2.Write(buffer[:])
+ b2.Sum(buffer[:0])
+ copy(out, buffer[:32])
+ out = out[32:]
+ b2.Reset()
+ }
+
+ if outLen%blake2b.Size > 0 { // outLen > 64
+ r := ((outLen + 31) / 32) - 2 // ⌈τ /32⌉-2
+ b2, _ = blake2b.New(outLen-32*r, nil)
+ }
+ b2.Write(buffer[:])
+ b2.Sum(out[:0])
+}
diff --git a/local_crypto_patch/contents/argon2/blamka_amd64.go b/local_crypto_patch/contents/argon2/blamka_amd64.go
new file mode 100644
index 0000000000..063e7784f8
--- /dev/null
+++ b/local_crypto_patch/contents/argon2/blamka_amd64.go
@@ -0,0 +1,60 @@
+// 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.
+
+//go:build amd64 && gc && !purego
+
+package argon2
+
+import "golang.org/x/sys/cpu"
+
+func init() {
+ useSSE4 = cpu.X86.HasSSE41
+}
+
+//go:noescape
+func mixBlocksSSE2(out, a, b, c *block)
+
+//go:noescape
+func xorBlocksSSE2(out, a, b, c *block)
+
+//go:noescape
+func blamkaSSE4(b *block)
+
+func processBlockSSE(out, in1, in2 *block, xor bool) {
+ var t block
+ mixBlocksSSE2(&t, in1, in2, &t)
+ if useSSE4 {
+ blamkaSSE4(&t)
+ } else {
+ for i := 0; i < blockLength; i += 16 {
+ blamkaGeneric(
+ &t[i+0], &t[i+1], &t[i+2], &t[i+3],
+ &t[i+4], &t[i+5], &t[i+6], &t[i+7],
+ &t[i+8], &t[i+9], &t[i+10], &t[i+11],
+ &t[i+12], &t[i+13], &t[i+14], &t[i+15],
+ )
+ }
+ for i := 0; i < blockLength/8; i += 2 {
+ blamkaGeneric(
+ &t[i], &t[i+1], &t[16+i], &t[16+i+1],
+ &t[32+i], &t[32+i+1], &t[48+i], &t[48+i+1],
+ &t[64+i], &t[64+i+1], &t[80+i], &t[80+i+1],
+ &t[96+i], &t[96+i+1], &t[112+i], &t[112+i+1],
+ )
+ }
+ }
+ if xor {
+ xorBlocksSSE2(out, in1, in2, &t)
+ } else {
+ mixBlocksSSE2(out, in1, in2, &t)
+ }
+}
+
+func processBlock(out, in1, in2 *block) {
+ processBlockSSE(out, in1, in2, false)
+}
+
+func processBlockXOR(out, in1, in2 *block) {
+ processBlockSSE(out, in1, in2, true)
+}
diff --git a/local_crypto_patch/contents/argon2/blamka_amd64.s b/local_crypto_patch/contents/argon2/blamka_amd64.s
new file mode 100644
index 0000000000..c3895478ed
--- /dev/null
+++ b/local_crypto_patch/contents/argon2/blamka_amd64.s
@@ -0,0 +1,2791 @@
+// Code generated by command: go run blamka_amd64.go -out ../blamka_amd64.s -pkg argon2. DO NOT EDIT.
+
+//go:build amd64 && gc && !purego
+
+#include "textflag.h"
+
+// func blamkaSSE4(b *block)
+// Requires: SSE2, SSSE3
+TEXT ·blamkaSSE4(SB), NOSPLIT, $0-8
+ MOVQ b+0(FP), AX
+ MOVOU ·c40<>+0(SB), X10
+ MOVOU ·c48<>+0(SB), X11
+ MOVOU (AX), X0
+ MOVOU 16(AX), X1
+ MOVOU 32(AX), X2
+ MOVOU 48(AX), X3
+ MOVOU 64(AX), X4
+ MOVOU 80(AX), X5
+ MOVOU 96(AX), X6
+ MOVOU 112(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, (AX)
+ MOVOU X1, 16(AX)
+ MOVOU X2, 32(AX)
+ MOVOU X3, 48(AX)
+ MOVOU X4, 64(AX)
+ MOVOU X5, 80(AX)
+ MOVOU X6, 96(AX)
+ MOVOU X7, 112(AX)
+ MOVOU 128(AX), X0
+ MOVOU 144(AX), X1
+ MOVOU 160(AX), X2
+ MOVOU 176(AX), X3
+ MOVOU 192(AX), X4
+ MOVOU 208(AX), X5
+ MOVOU 224(AX), X6
+ MOVOU 240(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 128(AX)
+ MOVOU X1, 144(AX)
+ MOVOU X2, 160(AX)
+ MOVOU X3, 176(AX)
+ MOVOU X4, 192(AX)
+ MOVOU X5, 208(AX)
+ MOVOU X6, 224(AX)
+ MOVOU X7, 240(AX)
+ MOVOU 256(AX), X0
+ MOVOU 272(AX), X1
+ MOVOU 288(AX), X2
+ MOVOU 304(AX), X3
+ MOVOU 320(AX), X4
+ MOVOU 336(AX), X5
+ MOVOU 352(AX), X6
+ MOVOU 368(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 256(AX)
+ MOVOU X1, 272(AX)
+ MOVOU X2, 288(AX)
+ MOVOU X3, 304(AX)
+ MOVOU X4, 320(AX)
+ MOVOU X5, 336(AX)
+ MOVOU X6, 352(AX)
+ MOVOU X7, 368(AX)
+ MOVOU 384(AX), X0
+ MOVOU 400(AX), X1
+ MOVOU 416(AX), X2
+ MOVOU 432(AX), X3
+ MOVOU 448(AX), X4
+ MOVOU 464(AX), X5
+ MOVOU 480(AX), X6
+ MOVOU 496(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 384(AX)
+ MOVOU X1, 400(AX)
+ MOVOU X2, 416(AX)
+ MOVOU X3, 432(AX)
+ MOVOU X4, 448(AX)
+ MOVOU X5, 464(AX)
+ MOVOU X6, 480(AX)
+ MOVOU X7, 496(AX)
+ MOVOU 512(AX), X0
+ MOVOU 528(AX), X1
+ MOVOU 544(AX), X2
+ MOVOU 560(AX), X3
+ MOVOU 576(AX), X4
+ MOVOU 592(AX), X5
+ MOVOU 608(AX), X6
+ MOVOU 624(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 512(AX)
+ MOVOU X1, 528(AX)
+ MOVOU X2, 544(AX)
+ MOVOU X3, 560(AX)
+ MOVOU X4, 576(AX)
+ MOVOU X5, 592(AX)
+ MOVOU X6, 608(AX)
+ MOVOU X7, 624(AX)
+ MOVOU 640(AX), X0
+ MOVOU 656(AX), X1
+ MOVOU 672(AX), X2
+ MOVOU 688(AX), X3
+ MOVOU 704(AX), X4
+ MOVOU 720(AX), X5
+ MOVOU 736(AX), X6
+ MOVOU 752(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 640(AX)
+ MOVOU X1, 656(AX)
+ MOVOU X2, 672(AX)
+ MOVOU X3, 688(AX)
+ MOVOU X4, 704(AX)
+ MOVOU X5, 720(AX)
+ MOVOU X6, 736(AX)
+ MOVOU X7, 752(AX)
+ MOVOU 768(AX), X0
+ MOVOU 784(AX), X1
+ MOVOU 800(AX), X2
+ MOVOU 816(AX), X3
+ MOVOU 832(AX), X4
+ MOVOU 848(AX), X5
+ MOVOU 864(AX), X6
+ MOVOU 880(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 768(AX)
+ MOVOU X1, 784(AX)
+ MOVOU X2, 800(AX)
+ MOVOU X3, 816(AX)
+ MOVOU X4, 832(AX)
+ MOVOU X5, 848(AX)
+ MOVOU X6, 864(AX)
+ MOVOU X7, 880(AX)
+ MOVOU 896(AX), X0
+ MOVOU 912(AX), X1
+ MOVOU 928(AX), X2
+ MOVOU 944(AX), X3
+ MOVOU 960(AX), X4
+ MOVOU 976(AX), X5
+ MOVOU 992(AX), X6
+ MOVOU 1008(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 896(AX)
+ MOVOU X1, 912(AX)
+ MOVOU X2, 928(AX)
+ MOVOU X3, 944(AX)
+ MOVOU X4, 960(AX)
+ MOVOU X5, 976(AX)
+ MOVOU X6, 992(AX)
+ MOVOU X7, 1008(AX)
+ MOVOU (AX), X0
+ MOVOU 128(AX), X1
+ MOVOU 256(AX), X2
+ MOVOU 384(AX), X3
+ MOVOU 512(AX), X4
+ MOVOU 640(AX), X5
+ MOVOU 768(AX), X6
+ MOVOU 896(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, (AX)
+ MOVOU X1, 128(AX)
+ MOVOU X2, 256(AX)
+ MOVOU X3, 384(AX)
+ MOVOU X4, 512(AX)
+ MOVOU X5, 640(AX)
+ MOVOU X6, 768(AX)
+ MOVOU X7, 896(AX)
+ MOVOU 16(AX), X0
+ MOVOU 144(AX), X1
+ MOVOU 272(AX), X2
+ MOVOU 400(AX), X3
+ MOVOU 528(AX), X4
+ MOVOU 656(AX), X5
+ MOVOU 784(AX), X6
+ MOVOU 912(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 16(AX)
+ MOVOU X1, 144(AX)
+ MOVOU X2, 272(AX)
+ MOVOU X3, 400(AX)
+ MOVOU X4, 528(AX)
+ MOVOU X5, 656(AX)
+ MOVOU X6, 784(AX)
+ MOVOU X7, 912(AX)
+ MOVOU 32(AX), X0
+ MOVOU 160(AX), X1
+ MOVOU 288(AX), X2
+ MOVOU 416(AX), X3
+ MOVOU 544(AX), X4
+ MOVOU 672(AX), X5
+ MOVOU 800(AX), X6
+ MOVOU 928(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 32(AX)
+ MOVOU X1, 160(AX)
+ MOVOU X2, 288(AX)
+ MOVOU X3, 416(AX)
+ MOVOU X4, 544(AX)
+ MOVOU X5, 672(AX)
+ MOVOU X6, 800(AX)
+ MOVOU X7, 928(AX)
+ MOVOU 48(AX), X0
+ MOVOU 176(AX), X1
+ MOVOU 304(AX), X2
+ MOVOU 432(AX), X3
+ MOVOU 560(AX), X4
+ MOVOU 688(AX), X5
+ MOVOU 816(AX), X6
+ MOVOU 944(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 48(AX)
+ MOVOU X1, 176(AX)
+ MOVOU X2, 304(AX)
+ MOVOU X3, 432(AX)
+ MOVOU X4, 560(AX)
+ MOVOU X5, 688(AX)
+ MOVOU X6, 816(AX)
+ MOVOU X7, 944(AX)
+ MOVOU 64(AX), X0
+ MOVOU 192(AX), X1
+ MOVOU 320(AX), X2
+ MOVOU 448(AX), X3
+ MOVOU 576(AX), X4
+ MOVOU 704(AX), X5
+ MOVOU 832(AX), X6
+ MOVOU 960(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 64(AX)
+ MOVOU X1, 192(AX)
+ MOVOU X2, 320(AX)
+ MOVOU X3, 448(AX)
+ MOVOU X4, 576(AX)
+ MOVOU X5, 704(AX)
+ MOVOU X6, 832(AX)
+ MOVOU X7, 960(AX)
+ MOVOU 80(AX), X0
+ MOVOU 208(AX), X1
+ MOVOU 336(AX), X2
+ MOVOU 464(AX), X3
+ MOVOU 592(AX), X4
+ MOVOU 720(AX), X5
+ MOVOU 848(AX), X6
+ MOVOU 976(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 80(AX)
+ MOVOU X1, 208(AX)
+ MOVOU X2, 336(AX)
+ MOVOU X3, 464(AX)
+ MOVOU X4, 592(AX)
+ MOVOU X5, 720(AX)
+ MOVOU X6, 848(AX)
+ MOVOU X7, 976(AX)
+ MOVOU 96(AX), X0
+ MOVOU 224(AX), X1
+ MOVOU 352(AX), X2
+ MOVOU 480(AX), X3
+ MOVOU 608(AX), X4
+ MOVOU 736(AX), X5
+ MOVOU 864(AX), X6
+ MOVOU 992(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 96(AX)
+ MOVOU X1, 224(AX)
+ MOVOU X2, 352(AX)
+ MOVOU X3, 480(AX)
+ MOVOU X4, 608(AX)
+ MOVOU X5, 736(AX)
+ MOVOU X6, 864(AX)
+ MOVOU X7, 992(AX)
+ MOVOU 112(AX), X0
+ MOVOU 240(AX), X1
+ MOVOU 368(AX), X2
+ MOVOU 496(AX), X3
+ MOVOU 624(AX), X4
+ MOVOU 752(AX), X5
+ MOVOU 880(AX), X6
+ MOVOU 1008(AX), X7
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFD $0xb1, X6, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ PSHUFB X10, X2
+ MOVO X0, X8
+ PMULULQ X2, X8
+ PADDQ X2, X0
+ PADDQ X8, X0
+ PADDQ X8, X0
+ PXOR X0, X6
+ PSHUFB X11, X6
+ MOVO X4, X8
+ PMULULQ X6, X8
+ PADDQ X6, X4
+ PADDQ X8, X4
+ PADDQ X8, X4
+ PXOR X4, X2
+ MOVO X2, X8
+ PADDQ X2, X8
+ PSRLQ $0x3f, X2
+ PXOR X8, X2
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFD $0xb1, X7, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ PSHUFB X10, X3
+ MOVO X1, X8
+ PMULULQ X3, X8
+ PADDQ X3, X1
+ PADDQ X8, X1
+ PADDQ X8, X1
+ PXOR X1, X7
+ PSHUFB X11, X7
+ MOVO X5, X8
+ PMULULQ X7, X8
+ PADDQ X7, X5
+ PADDQ X8, X5
+ PADDQ X8, X5
+ PXOR X5, X3
+ MOVO X3, X8
+ PADDQ X3, X8
+ PSRLQ $0x3f, X3
+ PXOR X8, 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 X0, 112(AX)
+ MOVOU X1, 240(AX)
+ MOVOU X2, 368(AX)
+ MOVOU X3, 496(AX)
+ MOVOU X4, 624(AX)
+ MOVOU X5, 752(AX)
+ MOVOU X6, 880(AX)
+ MOVOU X7, 1008(AX)
+ RET
+
+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
+
+// func mixBlocksSSE2(out *block, a *block, b *block, c *block)
+// Requires: SSE2
+TEXT ·mixBlocksSSE2(SB), NOSPLIT, $0-32
+ MOVQ out+0(FP), DX
+ MOVQ a+8(FP), AX
+ MOVQ b+16(FP), BX
+ MOVQ c+24(FP), CX
+ MOVQ $0x00000080, DI
+
+loop:
+ MOVOU (AX), X0
+ MOVOU (BX), X1
+ MOVOU (CX), X2
+ PXOR X1, X0
+ PXOR X2, X0
+ MOVOU X0, (DX)
+ ADDQ $0x10, AX
+ ADDQ $0x10, BX
+ ADDQ $0x10, CX
+ ADDQ $0x10, DX
+ SUBQ $0x02, DI
+ JA loop
+ RET
+
+// func xorBlocksSSE2(out *block, a *block, b *block, c *block)
+// Requires: SSE2
+TEXT ·xorBlocksSSE2(SB), NOSPLIT, $0-32
+ MOVQ out+0(FP), DX
+ MOVQ a+8(FP), AX
+ MOVQ b+16(FP), BX
+ MOVQ c+24(FP), CX
+ MOVQ $0x00000080, DI
+
+loop:
+ MOVOU (AX), X0
+ MOVOU (BX), X1
+ MOVOU (CX), X2
+ MOVOU (DX), X3
+ PXOR X1, X0
+ PXOR X2, X0
+ PXOR X3, X0
+ MOVOU X0, (DX)
+ ADDQ $0x10, AX
+ ADDQ $0x10, BX
+ ADDQ $0x10, CX
+ ADDQ $0x10, DX
+ SUBQ $0x02, DI
+ JA loop
+ RET
diff --git a/local_crypto_patch/contents/argon2/blamka_generic.go b/local_crypto_patch/contents/argon2/blamka_generic.go
new file mode 100644
index 0000000000..a481b2243f
--- /dev/null
+++ b/local_crypto_patch/contents/argon2/blamka_generic.go
@@ -0,0 +1,163 @@
+// 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 argon2
+
+var useSSE4 bool
+
+func processBlockGeneric(out, in1, in2 *block, xor bool) {
+ var t block
+ for i := range t {
+ t[i] = in1[i] ^ in2[i]
+ }
+ for i := 0; i < blockLength; i += 16 {
+ blamkaGeneric(
+ &t[i+0], &t[i+1], &t[i+2], &t[i+3],
+ &t[i+4], &t[i+5], &t[i+6], &t[i+7],
+ &t[i+8], &t[i+9], &t[i+10], &t[i+11],
+ &t[i+12], &t[i+13], &t[i+14], &t[i+15],
+ )
+ }
+ for i := 0; i < blockLength/8; i += 2 {
+ blamkaGeneric(
+ &t[i], &t[i+1], &t[16+i], &t[16+i+1],
+ &t[32+i], &t[32+i+1], &t[48+i], &t[48+i+1],
+ &t[64+i], &t[64+i+1], &t[80+i], &t[80+i+1],
+ &t[96+i], &t[96+i+1], &t[112+i], &t[112+i+1],
+ )
+ }
+ if xor {
+ for i := range t {
+ out[i] ^= in1[i] ^ in2[i] ^ t[i]
+ }
+ } else {
+ for i := range t {
+ out[i] = in1[i] ^ in2[i] ^ t[i]
+ }
+ }
+}
+
+func blamkaGeneric(t00, t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15 *uint64) {
+ v00, v01, v02, v03 := *t00, *t01, *t02, *t03
+ v04, v05, v06, v07 := *t04, *t05, *t06, *t07
+ v08, v09, v10, v11 := *t08, *t09, *t10, *t11
+ v12, v13, v14, v15 := *t12, *t13, *t14, *t15
+
+ v00 += v04 + 2*uint64(uint32(v00))*uint64(uint32(v04))
+ v12 ^= v00
+ v12 = v12>>32 | v12<<32
+ v08 += v12 + 2*uint64(uint32(v08))*uint64(uint32(v12))
+ v04 ^= v08
+ v04 = v04>>24 | v04<<40
+
+ v00 += v04 + 2*uint64(uint32(v00))*uint64(uint32(v04))
+ v12 ^= v00
+ v12 = v12>>16 | v12<<48
+ v08 += v12 + 2*uint64(uint32(v08))*uint64(uint32(v12))
+ v04 ^= v08
+ v04 = v04>>63 | v04<<1
+
+ v01 += v05 + 2*uint64(uint32(v01))*uint64(uint32(v05))
+ v13 ^= v01
+ v13 = v13>>32 | v13<<32
+ v09 += v13 + 2*uint64(uint32(v09))*uint64(uint32(v13))
+ v05 ^= v09
+ v05 = v05>>24 | v05<<40
+
+ v01 += v05 + 2*uint64(uint32(v01))*uint64(uint32(v05))
+ v13 ^= v01
+ v13 = v13>>16 | v13<<48
+ v09 += v13 + 2*uint64(uint32(v09))*uint64(uint32(v13))
+ v05 ^= v09
+ v05 = v05>>63 | v05<<1
+
+ v02 += v06 + 2*uint64(uint32(v02))*uint64(uint32(v06))
+ v14 ^= v02
+ v14 = v14>>32 | v14<<32
+ v10 += v14 + 2*uint64(uint32(v10))*uint64(uint32(v14))
+ v06 ^= v10
+ v06 = v06>>24 | v06<<40
+
+ v02 += v06 + 2*uint64(uint32(v02))*uint64(uint32(v06))
+ v14 ^= v02
+ v14 = v14>>16 | v14<<48
+ v10 += v14 + 2*uint64(uint32(v10))*uint64(uint32(v14))
+ v06 ^= v10
+ v06 = v06>>63 | v06<<1
+
+ v03 += v07 + 2*uint64(uint32(v03))*uint64(uint32(v07))
+ v15 ^= v03
+ v15 = v15>>32 | v15<<32
+ v11 += v15 + 2*uint64(uint32(v11))*uint64(uint32(v15))
+ v07 ^= v11
+ v07 = v07>>24 | v07<<40
+
+ v03 += v07 + 2*uint64(uint32(v03))*uint64(uint32(v07))
+ v15 ^= v03
+ v15 = v15>>16 | v15<<48
+ v11 += v15 + 2*uint64(uint32(v11))*uint64(uint32(v15))
+ v07 ^= v11
+ v07 = v07>>63 | v07<<1
+
+ v00 += v05 + 2*uint64(uint32(v00))*uint64(uint32(v05))
+ v15 ^= v00
+ v15 = v15>>32 | v15<<32
+ v10 += v15 + 2*uint64(uint32(v10))*uint64(uint32(v15))
+ v05 ^= v10
+ v05 = v05>>24 | v05<<40
+
+ v00 += v05 + 2*uint64(uint32(v00))*uint64(uint32(v05))
+ v15 ^= v00
+ v15 = v15>>16 | v15<<48
+ v10 += v15 + 2*uint64(uint32(v10))*uint64(uint32(v15))
+ v05 ^= v10
+ v05 = v05>>63 | v05<<1
+
+ v01 += v06 + 2*uint64(uint32(v01))*uint64(uint32(v06))
+ v12 ^= v01
+ v12 = v12>>32 | v12<<32
+ v11 += v12 + 2*uint64(uint32(v11))*uint64(uint32(v12))
+ v06 ^= v11
+ v06 = v06>>24 | v06<<40
+
+ v01 += v06 + 2*uint64(uint32(v01))*uint64(uint32(v06))
+ v12 ^= v01
+ v12 = v12>>16 | v12<<48
+ v11 += v12 + 2*uint64(uint32(v11))*uint64(uint32(v12))
+ v06 ^= v11
+ v06 = v06>>63 | v06<<1
+
+ v02 += v07 + 2*uint64(uint32(v02))*uint64(uint32(v07))
+ v13 ^= v02
+ v13 = v13>>32 | v13<<32
+ v08 += v13 + 2*uint64(uint32(v08))*uint64(uint32(v13))
+ v07 ^= v08
+ v07 = v07>>24 | v07<<40
+
+ v02 += v07 + 2*uint64(uint32(v02))*uint64(uint32(v07))
+ v13 ^= v02
+ v13 = v13>>16 | v13<<48
+ v08 += v13 + 2*uint64(uint32(v08))*uint64(uint32(v13))
+ v07 ^= v08
+ v07 = v07>>63 | v07<<1
+
+ v03 += v04 + 2*uint64(uint32(v03))*uint64(uint32(v04))
+ v14 ^= v03
+ v14 = v14>>32 | v14<<32
+ v09 += v14 + 2*uint64(uint32(v09))*uint64(uint32(v14))
+ v04 ^= v09
+ v04 = v04>>24 | v04<<40
+
+ v03 += v04 + 2*uint64(uint32(v03))*uint64(uint32(v04))
+ v14 ^= v03
+ v14 = v14>>16 | v14<<48
+ v09 += v14 + 2*uint64(uint32(v09))*uint64(uint32(v14))
+ v04 ^= v09
+ v04 = v04>>63 | v04<<1
+
+ *t00, *t01, *t02, *t03 = v00, v01, v02, v03
+ *t04, *t05, *t06, *t07 = v04, v05, v06, v07
+ *t08, *t09, *t10, *t11 = v08, v09, v10, v11
+ *t12, *t13, *t14, *t15 = v12, v13, v14, v15
+}
diff --git a/local_crypto_patch/contents/argon2/blamka_ref.go b/local_crypto_patch/contents/argon2/blamka_ref.go
new file mode 100644
index 0000000000..16d58c650e
--- /dev/null
+++ b/local_crypto_patch/contents/argon2/blamka_ref.go
@@ -0,0 +1,15 @@
+// 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.
+
+//go:build !amd64 || purego || !gc
+
+package argon2
+
+func processBlock(out, in1, in2 *block) {
+ processBlockGeneric(out, in1, in2, false)
+}
+
+func processBlockXOR(out, in1, in2 *block) {
+ processBlockGeneric(out, in1, in2, true)
+}
diff --git a/local_crypto_patch/contents/bcrypt/base64.go b/local_crypto_patch/contents/bcrypt/base64.go
new file mode 100644
index 0000000000..fc31160908
--- /dev/null
+++ b/local_crypto_patch/contents/bcrypt/base64.go
@@ -0,0 +1,35 @@
+// 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 bcrypt
+
+import "encoding/base64"
+
+const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+
+var bcEncoding = base64.NewEncoding(alphabet)
+
+func base64Encode(src []byte) []byte {
+ n := bcEncoding.EncodedLen(len(src))
+ dst := make([]byte, n)
+ bcEncoding.Encode(dst, src)
+ for dst[n-1] == '=' {
+ n--
+ }
+ return dst[:n]
+}
+
+func base64Decode(src []byte) ([]byte, error) {
+ numOfEquals := 4 - (len(src) % 4)
+ for i := 0; i < numOfEquals; i++ {
+ src = append(src, '=')
+ }
+
+ dst := make([]byte, bcEncoding.DecodedLen(len(src)))
+ n, err := bcEncoding.Decode(dst, src)
+ if err != nil {
+ return nil, err
+ }
+ return dst[:n], nil
+}
diff --git a/local_crypto_patch/contents/bcrypt/bcrypt.go b/local_crypto_patch/contents/bcrypt/bcrypt.go
new file mode 100644
index 0000000000..3e7f8df871
--- /dev/null
+++ b/local_crypto_patch/contents/bcrypt/bcrypt.go
@@ -0,0 +1,304 @@
+// 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 bcrypt implements Provos and Mazières's bcrypt adaptive hashing
+// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
+package bcrypt
+
+// The code is a port of Provos and Mazières's C implementation.
+import (
+ "crypto/rand"
+ "crypto/subtle"
+ "errors"
+ "fmt"
+ "io"
+ "strconv"
+
+ "golang.org/x/crypto/blowfish"
+)
+
+const (
+ MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword
+ MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword
+ DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
+)
+
+// The error returned from CompareHashAndPassword when a password and hash do
+// not match.
+var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
+
+// The error returned from CompareHashAndPassword when a hash is too short to
+// be a bcrypt hash.
+var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
+
+// The error returned from CompareHashAndPassword when a hash was created with
+// a bcrypt algorithm newer than this implementation.
+type HashVersionTooNewError byte
+
+func (hv HashVersionTooNewError) Error() string {
+ return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
+}
+
+// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
+type InvalidHashPrefixError byte
+
+func (ih InvalidHashPrefixError) Error() string {
+ return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
+}
+
+type InvalidCostError int
+
+func (ic InvalidCostError) Error() string {
+ return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed inclusive range %d..%d", int(ic), MinCost, MaxCost)
+}
+
+const (
+ majorVersion = '2'
+ minorVersion = 'a'
+ maxSaltSize = 16
+ maxCryptedHashSize = 23
+ encodedSaltSize = 22
+ encodedHashSize = 31
+ minHashSize = 59
+)
+
+// magicCipherData is an IV for the 64 Blowfish encryption calls in
+// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
+var magicCipherData = []byte{
+ 0x4f, 0x72, 0x70, 0x68,
+ 0x65, 0x61, 0x6e, 0x42,
+ 0x65, 0x68, 0x6f, 0x6c,
+ 0x64, 0x65, 0x72, 0x53,
+ 0x63, 0x72, 0x79, 0x44,
+ 0x6f, 0x75, 0x62, 0x74,
+}
+
+type hashed struct {
+ hash []byte
+ salt []byte
+ cost int // allowed range is MinCost to MaxCost
+ major byte
+ minor byte
+}
+
+// ErrPasswordTooLong is returned when the password passed to
+// GenerateFromPassword is too long (i.e. > 72 bytes).
+var ErrPasswordTooLong = errors.New("bcrypt: password length exceeds 72 bytes")
+
+// GenerateFromPassword returns the bcrypt hash of the password at the given
+// cost. If the cost given is less than MinCost, the cost will be set to
+// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
+// to compare the returned hashed password with its cleartext version.
+// GenerateFromPassword does not accept passwords longer than 72 bytes, which
+// is the longest password bcrypt will operate on.
+func GenerateFromPassword(password []byte, cost int) ([]byte, error) {
+ if len(password) > 72 {
+ return nil, ErrPasswordTooLong
+ }
+ p, err := newFromPassword(password, cost)
+ if err != nil {
+ return nil, err
+ }
+ return p.Hash(), nil
+}
+
+// CompareHashAndPassword compares a bcrypt hashed password with its possible
+// plaintext equivalent. Returns nil on success, or an error on failure.
+func CompareHashAndPassword(hashedPassword, password []byte) error {
+ p, err := newFromHash(hashedPassword)
+ if err != nil {
+ return err
+ }
+
+ otherHash, err := bcrypt(password, p.cost, p.salt)
+ if err != nil {
+ return err
+ }
+
+ otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
+ if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
+ return nil
+ }
+
+ return ErrMismatchedHashAndPassword
+}
+
+// Cost returns the hashing cost used to create the given hashed
+// password. When, in the future, the hashing cost of a password system needs
+// to be increased in order to adjust for greater computational power, this
+// function allows one to establish which passwords need to be updated.
+func Cost(hashedPassword []byte) (int, error) {
+ p, err := newFromHash(hashedPassword)
+ if err != nil {
+ return 0, err
+ }
+ return p.cost, nil
+}
+
+func newFromPassword(password []byte, cost int) (*hashed, error) {
+ if cost < MinCost {
+ cost = DefaultCost
+ }
+ p := new(hashed)
+ p.major = majorVersion
+ p.minor = minorVersion
+
+ err := checkCost(cost)
+ if err != nil {
+ return nil, err
+ }
+ p.cost = cost
+
+ unencodedSalt := make([]byte, maxSaltSize)
+ _, err = io.ReadFull(rand.Reader, unencodedSalt)
+ if err != nil {
+ return nil, err
+ }
+
+ p.salt = base64Encode(unencodedSalt)
+ hash, err := bcrypt(password, p.cost, p.salt)
+ if err != nil {
+ return nil, err
+ }
+ p.hash = hash
+ return p, err
+}
+
+func newFromHash(hashedSecret []byte) (*hashed, error) {
+ if len(hashedSecret) < minHashSize {
+ return nil, ErrHashTooShort
+ }
+ p := new(hashed)
+ n, err := p.decodeVersion(hashedSecret)
+ if err != nil {
+ return nil, err
+ }
+ hashedSecret = hashedSecret[n:]
+ n, err = p.decodeCost(hashedSecret)
+ if err != nil {
+ return nil, err
+ }
+ hashedSecret = hashedSecret[n:]
+
+ // The "+2" is here because we'll have to append at most 2 '=' to the salt
+ // when base64 decoding it in expensiveBlowfishSetup().
+ p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
+ copy(p.salt, hashedSecret[:encodedSaltSize])
+
+ hashedSecret = hashedSecret[encodedSaltSize:]
+ p.hash = make([]byte, len(hashedSecret))
+ copy(p.hash, hashedSecret)
+
+ return p, nil
+}
+
+func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
+ cipherData := make([]byte, len(magicCipherData))
+ copy(cipherData, magicCipherData)
+
+ c, err := expensiveBlowfishSetup(password, uint32(cost), salt)
+ if err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < 24; i += 8 {
+ for j := 0; j < 64; j++ {
+ c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
+ }
+ }
+
+ // Bug compatibility with C bcrypt implementations. We only encode 23 of
+ // the 24 bytes encrypted.
+ hsh := base64Encode(cipherData[:maxCryptedHashSize])
+ return hsh, nil
+}
+
+func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
+ csalt, err := base64Decode(salt)
+ if err != nil {
+ return nil, err
+ }
+
+ // Bug compatibility with C bcrypt implementations. They use the trailing
+ // NULL in the key string during expansion.
+ // We copy the key to prevent changing the underlying array.
+ ckey := append(key[:len(key):len(key)], 0)
+
+ c, err := blowfish.NewSaltedCipher(ckey, csalt)
+ if err != nil {
+ return nil, err
+ }
+
+ var i, rounds uint64
+ rounds = 1 << cost
+ for i = 0; i < rounds; i++ {
+ blowfish.ExpandKey(ckey, c)
+ blowfish.ExpandKey(csalt, c)
+ }
+
+ return c, nil
+}
+
+func (p *hashed) Hash() []byte {
+ arr := make([]byte, 60)
+ arr[0] = '$'
+ arr[1] = p.major
+ n := 2
+ if p.minor != 0 {
+ arr[2] = p.minor
+ n = 3
+ }
+ arr[n] = '$'
+ n++
+ copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
+ n += 2
+ arr[n] = '$'
+ n++
+ copy(arr[n:], p.salt)
+ n += encodedSaltSize
+ copy(arr[n:], p.hash)
+ n += encodedHashSize
+ return arr[:n]
+}
+
+func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
+ if sbytes[0] != '$' {
+ return -1, InvalidHashPrefixError(sbytes[0])
+ }
+ if sbytes[1] > majorVersion {
+ return -1, HashVersionTooNewError(sbytes[1])
+ }
+ p.major = sbytes[1]
+ n := 3
+ if sbytes[2] != '$' {
+ p.minor = sbytes[2]
+ n++
+ }
+ return n, nil
+}
+
+// sbytes should begin where decodeVersion left off.
+func (p *hashed) decodeCost(sbytes []byte) (int, error) {
+ cost, err := strconv.Atoi(string(sbytes[0:2]))
+ if err != nil {
+ return -1, err
+ }
+ err = checkCost(cost)
+ if err != nil {
+ return -1, err
+ }
+ p.cost = cost
+ return 3, nil
+}
+
+func (p *hashed) String() string {
+ return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
+}
+
+func checkCost(cost int) error {
+ if cost < MinCost || cost > MaxCost {
+ return InvalidCostError(cost)
+ }
+ return nil
+}
diff --git a/local_crypto_patch/contents/bcrypt/bcrypt_test.go b/local_crypto_patch/contents/bcrypt/bcrypt_test.go
new file mode 100644
index 0000000000..8b589e3652
--- /dev/null
+++ b/local_crypto_patch/contents/bcrypt/bcrypt_test.go
@@ -0,0 +1,250 @@
+// 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 bcrypt
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+)
+
+func TestBcryptingIsEasy(t *testing.T) {
+ pass := []byte("mypassword")
+ hp, err := GenerateFromPassword(pass, 0)
+ if err != nil {
+ t.Fatalf("GenerateFromPassword error: %s", err)
+ }
+
+ if CompareHashAndPassword(hp, pass) != nil {
+ t.Errorf("%v should hash %s correctly", hp, pass)
+ }
+
+ notPass := "notthepass"
+ err = CompareHashAndPassword(hp, []byte(notPass))
+ if err != ErrMismatchedHashAndPassword {
+ t.Errorf("%v and %s should be mismatched", hp, notPass)
+ }
+}
+
+func TestBcryptingIsCorrect(t *testing.T) {
+ pass := []byte("allmine")
+ salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+ expectedHash := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga")
+
+ hash, err := bcrypt(pass, 10, salt)
+ if err != nil {
+ t.Fatalf("bcrypt blew up: %v", err)
+ }
+ if !bytes.HasSuffix(expectedHash, hash) {
+ t.Errorf("%v should be the suffix of %v", hash, expectedHash)
+ }
+
+ h, err := newFromHash(expectedHash)
+ if err != nil {
+ t.Errorf("Unable to parse %s: %v", string(expectedHash), err)
+ }
+
+ // This is not the safe way to compare these hashes. We do this only for
+ // testing clarity. Use bcrypt.CompareHashAndPassword()
+ if err == nil && !bytes.Equal(expectedHash, h.Hash()) {
+ t.Errorf("Parsed hash %v should equal %v", h.Hash(), expectedHash)
+ }
+}
+
+func TestVeryShortPasswords(t *testing.T) {
+ key := []byte("k")
+ salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+ _, err := bcrypt(key, 10, salt)
+ if err != nil {
+ t.Errorf("One byte key resulted in error: %s", err)
+ }
+}
+
+func TestTooLongPasswordsWork(t *testing.T) {
+ salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+ // One byte over the usual 56 byte limit that blowfish has
+ tooLongPass := []byte("012345678901234567890123456789012345678901234567890123456")
+ tooLongExpected := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C")
+ hash, err := bcrypt(tooLongPass, 10, salt)
+ if err != nil {
+ t.Fatalf("bcrypt blew up on long password: %v", err)
+ }
+ if !bytes.HasSuffix(tooLongExpected, hash) {
+ t.Errorf("%v should be the suffix of %v", hash, tooLongExpected)
+ }
+}
+
+type InvalidHashTest struct {
+ err error
+ hash []byte
+}
+
+var invalidTests = []InvalidHashTest{
+ {ErrHashTooShort, []byte("$2a$10$fooo")},
+ {ErrHashTooShort, []byte("$2a")},
+ {HashVersionTooNewError('3'), []byte("$3a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+ {InvalidHashPrefixError('%'), []byte("%2a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+ {InvalidCostError(32), []byte("$2a$32$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+}
+
+func TestInvalidHashErrors(t *testing.T) {
+ check := func(name string, expected, err error) {
+ if err == nil {
+ t.Errorf("%s: Should have returned an error", name)
+ }
+ if err != nil && err != expected {
+ t.Errorf("%s gave err %v but should have given %v", name, err, expected)
+ }
+ }
+ for _, iht := range invalidTests {
+ _, err := newFromHash(iht.hash)
+ check("newFromHash", iht.err, err)
+ err = CompareHashAndPassword(iht.hash, []byte("anything"))
+ check("CompareHashAndPassword", iht.err, err)
+ }
+}
+
+func TestUnpaddedBase64Encoding(t *testing.T) {
+ original := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30}
+ encodedOriginal := []byte("XajjQvNhvvRt5GSeFk1xFe")
+
+ encoded := base64Encode(original)
+
+ if !bytes.Equal(encodedOriginal, encoded) {
+ t.Errorf("Encoded %v should have equaled %v", encoded, encodedOriginal)
+ }
+
+ decoded, err := base64Decode(encodedOriginal)
+ if err != nil {
+ t.Fatalf("base64Decode blew up: %s", err)
+ }
+
+ if !bytes.Equal(decoded, original) {
+ t.Errorf("Decoded %v should have equaled %v", decoded, original)
+ }
+}
+
+func TestCost(t *testing.T) {
+ suffix := "XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C"
+ for _, vers := range []string{"2a", "2"} {
+ for _, cost := range []int{4, 10} {
+ s := fmt.Sprintf("$%s$%02d$%s", vers, cost, suffix)
+ h := []byte(s)
+ actual, err := Cost(h)
+ if err != nil {
+ t.Errorf("Cost, error: %s", err)
+ continue
+ }
+ if actual != cost {
+ t.Errorf("Cost, expected: %d, actual: %d", cost, actual)
+ }
+ }
+ }
+ _, err := Cost([]byte("$a$a$" + suffix))
+ if err == nil {
+ t.Errorf("Cost, malformed but no error returned")
+ }
+}
+
+func TestCostValidationInHash(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+
+ pass := []byte("mypassword")
+
+ for c := 0; c < MinCost; c++ {
+ p, _ := newFromPassword(pass, c)
+ if p.cost != DefaultCost {
+ t.Errorf("newFromPassword should default costs below %d to %d, but was %d", MinCost, DefaultCost, p.cost)
+ }
+ }
+
+ p, _ := newFromPassword(pass, 14)
+ if p.cost != 14 {
+ t.Errorf("newFromPassword should default cost to 14, but was %d", p.cost)
+ }
+
+ hp, _ := newFromHash(p.Hash())
+ if p.cost != hp.cost {
+ t.Errorf("newFromHash should maintain the cost at %d, but was %d", p.cost, hp.cost)
+ }
+
+ _, err := newFromPassword(pass, 32)
+ if err == nil {
+ t.Fatalf("newFromPassword: should return a cost error")
+ }
+ if err != InvalidCostError(32) {
+ t.Errorf("newFromPassword: should return cost error, got %#v", err)
+ }
+}
+
+func TestCostReturnsWithLeadingZeroes(t *testing.T) {
+ hp, _ := newFromPassword([]byte("abcdefgh"), 7)
+ cost := hp.Hash()[4:7]
+ expected := []byte("07$")
+
+ if !bytes.Equal(expected, cost) {
+ t.Errorf("single digit costs in hash should have leading zeros: was %v instead of %v", cost, expected)
+ }
+}
+
+func TestMinorNotRequired(t *testing.T) {
+ noMinorHash := []byte("$2$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga")
+ h, err := newFromHash(noMinorHash)
+ if err != nil {
+ t.Fatalf("No minor hash blew up: %s", err)
+ }
+ if h.minor != 0 {
+ t.Errorf("Should leave minor version at 0, but was %d", h.minor)
+ }
+
+ if !bytes.Equal(noMinorHash, h.Hash()) {
+ t.Errorf("Should generate hash %v, but created %v", noMinorHash, h.Hash())
+ }
+}
+
+func BenchmarkEqual(b *testing.B) {
+ b.StopTimer()
+ passwd := []byte("somepasswordyoulike")
+ hash, _ := GenerateFromPassword(passwd, DefaultCost)
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ CompareHashAndPassword(hash, passwd)
+ }
+}
+
+func BenchmarkDefaultCost(b *testing.B) {
+ b.StopTimer()
+ passwd := []byte("mylongpassword1234")
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ GenerateFromPassword(passwd, DefaultCost)
+ }
+}
+
+// See Issue https://github.com/golang/go/issues/20425.
+func TestNoSideEffectsFromCompare(t *testing.T) {
+ source := []byte("passw0rd123456")
+ password := source[:len(source)-6]
+ token := source[len(source)-6:]
+ want := make([]byte, len(source))
+ copy(want, source)
+
+ wantHash := []byte("$2a$10$LK9XRuhNxHHCvjX3tdkRKei1QiCDUKrJRhZv7WWZPuQGRUM92rOUa")
+ _ = CompareHashAndPassword(wantHash, password)
+
+ got := bytes.Join([][]byte{password, token}, []byte(""))
+ if !bytes.Equal(got, want) {
+ t.Errorf("got=%q want=%q", got, want)
+ }
+}
+
+func TestPasswordTooLong(t *testing.T) {
+ _, err := GenerateFromPassword(make([]byte, 73), 1)
+ if err != ErrPasswordTooLong {
+ t.Errorf("unexpected error: got %q, want %q", err, ErrPasswordTooLong)
+ }
+}
diff --git a/local_crypto_patch/contents/blake2b/blake2b.go b/local_crypto_patch/contents/blake2b/blake2b.go
new file mode 100644
index 0000000000..d2e98d4295
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2b/blake2bAVX2_amd64.go b/local_crypto_patch/contents/blake2b/blake2bAVX2_amd64.go
new file mode 100644
index 0000000000..199c21d27a
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2b/blake2bAVX2_amd64.s b/local_crypto_patch/contents/blake2b/blake2bAVX2_amd64.s
new file mode 100644
index 0000000000..f75162e039
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2b/blake2b_amd64.s b/local_crypto_patch/contents/blake2b/blake2b_amd64.s
new file mode 100644
index 0000000000..9a0ce21244
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2b/blake2b_generic.go b/local_crypto_patch/contents/blake2b/blake2b_generic.go
new file mode 100644
index 0000000000..3168a8aa3c
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2b/blake2b_ref.go b/local_crypto_patch/contents/blake2b/blake2b_ref.go
new file mode 100644
index 0000000000..6e28668cd1
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2b/blake2b_test.go b/local_crypto_patch/contents/blake2b/blake2b_test.go
new file mode 100644
index 0000000000..08a9f93b46
--- /dev/null
+++ b/local_crypto_patch/contents/blake2b/blake2b_test.go
@@ -0,0 +1,849 @@
+// 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 (
+ "bytes"
+ "encoding"
+ "encoding/hex"
+ "fmt"
+ "hash"
+ "io"
+ "testing"
+)
+
+var _ hash.XOF = (*xof)(nil)
+
+func fromHex(s string) []byte {
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
+
+func TestHashes(t *testing.T) {
+ defer func(sse4, avx, avx2 bool) {
+ useSSE4, useAVX, useAVX2 = sse4, avx, avx2
+ }(useSSE4, useAVX, useAVX2)
+
+ if useAVX2 {
+ t.Log("AVX2 version")
+ testHashes(t)
+ useAVX2 = false
+ }
+ if useAVX {
+ t.Log("AVX version")
+ testHashes(t)
+ useAVX = false
+ }
+ if useSSE4 {
+ t.Log("SSE4 version")
+ testHashes(t)
+ useSSE4 = false
+ }
+ t.Log("generic version")
+ testHashes(t)
+}
+
+func TestHashes2X(t *testing.T) {
+ defer func(sse4, avx, avx2 bool) {
+ useSSE4, useAVX, useAVX2 = sse4, avx, avx2
+ }(useSSE4, useAVX, useAVX2)
+
+ if useAVX2 {
+ t.Log("AVX2 version")
+ testHashes2X(t)
+ useAVX2 = false
+ }
+ if useAVX {
+ t.Log("AVX version")
+ testHashes2X(t)
+ useAVX = false
+ }
+ if useSSE4 {
+ t.Log("SSE4 version")
+ testHashes2X(t)
+ useSSE4 = false
+ }
+ t.Log("generic version")
+ testHashes2X(t)
+}
+
+func TestMarshal(t *testing.T) {
+ input := make([]byte, 255)
+ for i := range input {
+ input[i] = byte(i)
+ }
+ for _, size := range []int{Size, Size256, Size384, 12, 25, 63} {
+ for i := 0; i < 256; i++ {
+ h, err := New(size, nil)
+ if err != nil {
+ t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err)
+ }
+ h2, err := New(size, nil)
+ if err != nil {
+ t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err)
+ }
+
+ h.Write(input[:i/2])
+ halfstate, err := h.(encoding.BinaryMarshaler).MarshalBinary()
+ if err != nil {
+ t.Fatalf("size=%d, len(input)=%d: could not marshal: %v", size, i, err)
+ }
+ err = h2.(encoding.BinaryUnmarshaler).UnmarshalBinary(halfstate)
+ if err != nil {
+ t.Fatalf("size=%d, len(input)=%d: could not unmarshal: %v", size, i, err)
+ }
+
+ h.Write(input[i/2 : i])
+ sum := h.Sum(nil)
+ h2.Write(input[i/2 : i])
+ sum2 := h2.Sum(nil)
+
+ if !bytes.Equal(sum, sum2) {
+ t.Fatalf("size=%d, len(input)=%d: results do not match; sum = %v, sum2 = %v", size, i, sum, sum2)
+ }
+
+ h3, err := New(size, nil)
+ if err != nil {
+ t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err)
+ }
+ h3.Write(input[:i])
+ sum3 := h3.Sum(nil)
+ if !bytes.Equal(sum, sum3) {
+ t.Fatalf("size=%d, len(input)=%d: sum = %v, want %v", size, i, sum, sum3)
+ }
+ }
+ }
+}
+
+func testHashes(t *testing.T) {
+ key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f")
+
+ input := make([]byte, 255)
+ for i := range input {
+ input[i] = byte(i)
+ }
+
+ for i, expectedHex := range hashes {
+ h, err := New512(key)
+ if err != nil {
+ t.Fatalf("#%d: error from New512: %v", i, err)
+ }
+
+ h.Write(input[:i])
+ sum := h.Sum(nil)
+
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+
+ h.Reset()
+ for j := 0; j < i; j++ {
+ h.Write(input[j : j+1])
+ }
+
+ sum = h.Sum(sum[:0])
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+ }
+}
+
+func testHashes2X(t *testing.T) {
+ key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f")
+
+ input := make([]byte, 256)
+ for i := range input {
+ input[i] = byte(i)
+ }
+
+ for i, expectedHex := range hashes2X {
+ length := uint32(len(expectedHex) / 2)
+ sum := make([]byte, int(length))
+
+ h, err := NewXOF(length, key)
+ if err != nil {
+ t.Fatalf("#%d: error from NewXOF: %v", i, err)
+ }
+
+ if _, err := h.Write(input); err != nil {
+ t.Fatalf("#%d (single write): error from Write: %v", i, err)
+ }
+ if _, err := h.Read(sum); err != nil {
+ t.Fatalf("#%d (single write): error from Read: %v", i, err)
+ }
+ if n, err := h.Read(sum); n != 0 || err != io.EOF {
+ t.Fatalf("#%d (single write): Read did not return (0, io.EOF) after exhaustion, got (%v, %v)", i, n, err)
+ }
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+
+ h.Reset()
+ for j := 0; j < len(input); j++ {
+ h.Write(input[j : j+1])
+ }
+ for j := 0; j < len(sum); j++ {
+ h = h.Clone()
+ if _, err := h.Read(sum[j : j+1]); err != nil {
+ t.Fatalf("#%d (byte-by-byte) - Read %d: error from Read: %v", i, j, err)
+ }
+ }
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+ }
+
+ h, err := NewXOF(OutputLengthUnknown, key)
+ if err != nil {
+ t.Fatalf("#unknown length: error from NewXOF: %v", err)
+ }
+ if _, err := h.Write(input); err != nil {
+ t.Fatalf("#unknown length: error from Write: %v", err)
+ }
+
+ var result [64]byte
+ if n, err := h.Read(result[:]); err != nil {
+ t.Fatalf("#unknown length: error from Read: %v", err)
+ } else if n != len(result) {
+ t.Fatalf("#unknown length: Read returned %d bytes, want %d", n, len(result))
+ }
+
+ const expected = "3dbba8516da76bf7330055c66ea36cf1005e92714262b24d9710f51d9e126406e1bcd6497059f9331f1091c3634b695428d475ed432f987040575520a1c29f5e"
+ if fmt.Sprintf("%x", result) != expected {
+ t.Fatalf("#unknown length: bad result %x, wanted %s", result, expected)
+ }
+}
+
+func generateSequence(out []byte, seed uint32) {
+ a := 0xDEAD4BAD * seed // prime
+ b := uint32(1)
+
+ for i := range out { // fill the buf
+ a, b = b, a+b
+ out[i] = byte(b >> 24)
+ }
+}
+
+func computeMAC(msg []byte, hashSize int, key []byte) (sum []byte) {
+ var h hash.Hash
+ switch hashSize {
+ case Size:
+ h, _ = New512(key)
+ case Size384:
+ h, _ = New384(key)
+ case Size256:
+ h, _ = New256(key)
+ case 20:
+ h, _ = newDigest(20, key)
+ default:
+ panic("unexpected hashSize")
+ }
+
+ h.Write(msg)
+ return h.Sum(sum)
+}
+
+func computeHash(msg []byte, hashSize int) (sum []byte) {
+ switch hashSize {
+ case Size:
+ hash := Sum512(msg)
+ return hash[:]
+ case Size384:
+ hash := Sum384(msg)
+ return hash[:]
+ case Size256:
+ hash := Sum256(msg)
+ return hash[:]
+ case 20:
+ var hash [64]byte
+ checkSum(&hash, 20, msg)
+ return hash[:20]
+ default:
+ panic("unexpected hashSize")
+ }
+}
+
+// Test function from RFC 7693.
+func TestSelfTest(t *testing.T) {
+ hashLens := [4]int{20, 32, 48, 64}
+ msgLens := [6]int{0, 3, 128, 129, 255, 1024}
+
+ msg := make([]byte, 1024)
+ key := make([]byte, 64)
+
+ h, _ := New256(nil)
+ for _, hashSize := range hashLens {
+ for _, msgLength := range msgLens {
+ generateSequence(msg[:msgLength], uint32(msgLength)) // unkeyed hash
+
+ md := computeHash(msg[:msgLength], hashSize)
+ h.Write(md)
+
+ generateSequence(key[:], uint32(hashSize)) // keyed hash
+ md = computeMAC(msg[:msgLength], hashSize, key[:hashSize])
+ h.Write(md)
+ }
+ }
+
+ sum := h.Sum(nil)
+ expected := [32]byte{
+ 0xc2, 0x3a, 0x78, 0x00, 0xd9, 0x81, 0x23, 0xbd,
+ 0x10, 0xf5, 0x06, 0xc6, 0x1e, 0x29, 0xda, 0x56,
+ 0x03, 0xd7, 0x63, 0xb8, 0xbb, 0xad, 0x2e, 0x73,
+ 0x7f, 0x5e, 0x76, 0x5a, 0x7b, 0xcc, 0xd4, 0x75,
+ }
+ if !bytes.Equal(sum, expected[:]) {
+ t.Fatalf("got %x, wanted %x", sum, expected)
+ }
+}
+
+// Benchmarks
+
+func benchmarkSum(b *testing.B, size int) {
+ data := make([]byte, size)
+ b.SetBytes(int64(size))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Sum512(data)
+ }
+}
+
+func benchmarkWrite(b *testing.B, size int) {
+ data := make([]byte, size)
+ h, _ := New512(nil)
+ b.SetBytes(int64(size))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ h.Write(data)
+ }
+}
+
+func BenchmarkWrite128(b *testing.B) { benchmarkWrite(b, 128) }
+func BenchmarkWrite1K(b *testing.B) { benchmarkWrite(b, 1024) }
+
+func BenchmarkSum128(b *testing.B) { benchmarkSum(b, 128) }
+func BenchmarkSum1K(b *testing.B) { benchmarkSum(b, 1024) }
+
+// These values were taken from https://blake2.net/blake2b-test.txt.
+var hashes = []string{
+ "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568",
+ "961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd",
+ "da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965",
+ "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1",
+ "beaa5a3d08f3807143cf621d95cd690514d0b49efff9c91d24b59241ec0eefa5f60196d407048bba8d2146828ebcb0488d8842fd56bb4f6df8e19c4b4daab8ac",
+ "098084b51fd13deae5f4320de94a688ee07baea2800486689a8636117b46c1f4c1f6af7f74ae7c857600456a58a3af251dc4723a64cc7c0a5ab6d9cac91c20bb",
+ "6044540d560853eb1c57df0077dd381094781cdb9073e5b1b3d3f6c7829e12066bbaca96d989a690de72ca3133a83652ba284a6d62942b271ffa2620c9e75b1f",
+ "7a8cfe9b90f75f7ecb3acc053aaed6193112b6f6a4aeeb3f65d3de541942deb9e2228152a3c4bbbe72fc3b12629528cfbb09fe630f0474339f54abf453e2ed52",
+ "380beaf6ea7cc9365e270ef0e6f3a64fb902acae51dd5512f84259ad2c91f4bc4108db73192a5bbfb0cbcf71e46c3e21aee1c5e860dc96e8eb0b7b8426e6abe9",
+ "60fe3c4535e1b59d9a61ea8500bfac41a69dffb1ceadd9aca323e9a625b64da5763bad7226da02b9c8c4f1a5de140ac5a6c1124e4f718ce0b28ea47393aa6637",
+ "4fe181f54ad63a2983feaaf77d1e7235c2beb17fa328b6d9505bda327df19fc37f02c4b6f0368ce23147313a8e5738b5fa2a95b29de1c7f8264eb77b69f585cd",
+ "f228773ce3f3a42b5f144d63237a72d99693adb8837d0e112a8a0f8ffff2c362857ac49c11ec740d1500749dac9b1f4548108bf3155794dcc9e4082849e2b85b",
+ "962452a8455cc56c8511317e3b1f3b2c37df75f588e94325fdd77070359cf63a9ae6e930936fdf8e1e08ffca440cfb72c28f06d89a2151d1c46cd5b268ef8563",
+ "43d44bfa18768c59896bf7ed1765cb2d14af8c260266039099b25a603e4ddc5039d6ef3a91847d1088d401c0c7e847781a8a590d33a3c6cb4df0fab1c2f22355",
+ "dcffa9d58c2a4ca2cdbb0c7aa4c4c1d45165190089f4e983bb1c2cab4aaeff1fa2b5ee516fecd780540240bf37e56c8bcca7fab980e1e61c9400d8a9a5b14ac6",
+ "6fbf31b45ab0c0b8dad1c0f5f4061379912dde5aa922099a030b725c73346c524291adef89d2f6fd8dfcda6d07dad811a9314536c2915ed45da34947e83de34e",
+ "a0c65bddde8adef57282b04b11e7bc8aab105b99231b750c021f4a735cb1bcfab87553bba3abb0c3e64a0b6955285185a0bd35fb8cfde557329bebb1f629ee93",
+ "f99d815550558e81eca2f96718aed10d86f3f1cfb675cce06b0eff02f617c5a42c5aa760270f2679da2677c5aeb94f1142277f21c7f79f3c4f0cce4ed8ee62b1",
+ "95391da8fc7b917a2044b3d6f5374e1ca072b41454d572c7356c05fd4bc1e0f40b8bb8b4a9f6bce9be2c4623c399b0dca0dab05cb7281b71a21b0ebcd9e55670",
+ "04b9cd3d20d221c09ac86913d3dc63041989a9a1e694f1e639a3ba7e451840f750c2fc191d56ad61f2e7936bc0ac8e094b60caeed878c18799045402d61ceaf9",
+ "ec0e0ef707e4ed6c0c66f9e089e4954b058030d2dd86398fe84059631f9ee591d9d77375355149178c0cf8f8e7c49ed2a5e4f95488a2247067c208510fadc44c",
+ "9a37cce273b79c09913677510eaf7688e89b3314d3532fd2764c39de022a2945b5710d13517af8ddc0316624e73bec1ce67df15228302036f330ab0cb4d218dd",
+ "4cf9bb8fb3d4de8b38b2f262d3c40f46dfe747e8fc0a414c193d9fcf753106ce47a18f172f12e8a2f1c26726545358e5ee28c9e2213a8787aafbc516d2343152",
+ "64e0c63af9c808fd893137129867fd91939d53f2af04be4fa268006100069b2d69daa5c5d8ed7fddcb2a70eeecdf2b105dd46a1e3b7311728f639ab489326bc9",
+ "5e9c93158d659b2def06b0c3c7565045542662d6eee8a96a89b78ade09fe8b3dcc096d4fe48815d88d8f82620156602af541955e1f6ca30dce14e254c326b88f",
+ "7775dff889458dd11aef417276853e21335eb88e4dec9cfb4e9edb49820088551a2ca60339f12066101169f0dfe84b098fddb148d9da6b3d613df263889ad64b",
+ "f0d2805afbb91f743951351a6d024f9353a23c7ce1fc2b051b3a8b968c233f46f50f806ecb1568ffaa0b60661e334b21dde04f8fa155ac740eeb42e20b60d764",
+ "86a2af316e7d7754201b942e275364ac12ea8962ab5bd8d7fb276dc5fbffc8f9a28cae4e4867df6780d9b72524160927c855da5b6078e0b554aa91e31cb9ca1d",
+ "10bdf0caa0802705e706369baf8a3f79d72c0a03a80675a7bbb00be3a45e516424d1ee88efb56f6d5777545ae6e27765c3a8f5e493fc308915638933a1dfee55",
+ "b01781092b1748459e2e4ec178696627bf4ebafebba774ecf018b79a68aeb84917bf0b84bb79d17b743151144cd66b7b33a4b9e52c76c4e112050ff5385b7f0b",
+ "c6dbc61dec6eaeac81e3d5f755203c8e220551534a0b2fd105a91889945a638550204f44093dd998c076205dffad703a0e5cd3c7f438a7e634cd59fededb539e",
+ "eba51acffb4cea31db4b8d87e9bf7dd48fe97b0253ae67aa580f9ac4a9d941f2bea518ee286818cc9f633f2a3b9fb68e594b48cdd6d515bf1d52ba6c85a203a7",
+ "86221f3ada52037b72224f105d7999231c5e5534d03da9d9c0a12acb68460cd375daf8e24386286f9668f72326dbf99ba094392437d398e95bb8161d717f8991",
+ "5595e05c13a7ec4dc8f41fb70cb50a71bce17c024ff6de7af618d0cc4e9c32d9570d6d3ea45b86525491030c0d8f2b1836d5778c1ce735c17707df364d054347",
+ "ce0f4f6aca89590a37fe034dd74dd5fa65eb1cbd0a41508aaddc09351a3cea6d18cb2189c54b700c009f4cbf0521c7ea01be61c5ae09cb54f27bc1b44d658c82",
+ "7ee80b06a215a3bca970c77cda8761822bc103d44fa4b33f4d07dcb997e36d55298bceae12241b3fa07fa63be5576068da387b8d5859aeab701369848b176d42",
+ "940a84b6a84d109aab208c024c6ce9647676ba0aaa11f86dbb7018f9fd2220a6d901a9027f9abcf935372727cbf09ebd61a2a2eeb87653e8ecad1bab85dc8327",
+ "2020b78264a82d9f4151141adba8d44bf20c5ec062eee9b595a11f9e84901bf148f298e0c9f8777dcdbc7cc4670aac356cc2ad8ccb1629f16f6a76bcefbee760",
+ "d1b897b0e075ba68ab572adf9d9c436663e43eb3d8e62d92fc49c9be214e6f27873fe215a65170e6bea902408a25b49506f47babd07cecf7113ec10c5dd31252",
+ "b14d0c62abfa469a357177e594c10c194243ed2025ab8aa5ad2fa41ad318e0ff48cd5e60bec07b13634a711d2326e488a985f31e31153399e73088efc86a5c55",
+ "4169c5cc808d2697dc2a82430dc23e3cd356dc70a94566810502b8d655b39abf9e7f902fe717e0389219859e1945df1af6ada42e4ccda55a197b7100a30c30a1",
+ "258a4edb113d66c839c8b1c91f15f35ade609f11cd7f8681a4045b9fef7b0b24c82cda06a5f2067b368825e3914e53d6948ede92efd6e8387fa2e537239b5bee",
+ "79d2d8696d30f30fb34657761171a11e6c3f1e64cbe7bebee159cb95bfaf812b4f411e2f26d9c421dc2c284a3342d823ec293849e42d1e46b0a4ac1e3c86abaa",
+ "8b9436010dc5dee992ae38aea97f2cd63b946d94fedd2ec9671dcde3bd4ce9564d555c66c15bb2b900df72edb6b891ebcadfeff63c9ea4036a998be7973981e7",
+ "c8f68e696ed28242bf997f5b3b34959508e42d613810f1e2a435c96ed2ff560c7022f361a9234b9837feee90bf47922ee0fd5f8ddf823718d86d1e16c6090071",
+ "b02d3eee4860d5868b2c39ce39bfe81011290564dd678c85e8783f29302dfc1399ba95b6b53cd9ebbf400cca1db0ab67e19a325f2d115812d25d00978ad1bca4",
+ "7693ea73af3ac4dad21ca0d8da85b3118a7d1c6024cfaf557699868217bc0c2f44a199bc6c0edd519798ba05bd5b1b4484346a47c2cadf6bf30b785cc88b2baf",
+ "a0e5c1c0031c02e48b7f09a5e896ee9aef2f17fc9e18e997d7f6cac7ae316422c2b1e77984e5f3a73cb45deed5d3f84600105e6ee38f2d090c7d0442ea34c46d",
+ "41daa6adcfdb69f1440c37b596440165c15ada596813e2e22f060fcd551f24dee8e04ba6890387886ceec4a7a0d7fc6b44506392ec3822c0d8c1acfc7d5aebe8",
+ "14d4d40d5984d84c5cf7523b7798b254e275a3a8cc0a1bd06ebc0bee726856acc3cbf516ff667cda2058ad5c3412254460a82c92187041363cc77a4dc215e487",
+ "d0e7a1e2b9a447fee83e2277e9ff8010c2f375ae12fa7aaa8ca5a6317868a26a367a0b69fbc1cf32a55d34eb370663016f3d2110230eba754028a56f54acf57c",
+ "e771aa8db5a3e043e8178f39a0857ba04a3f18e4aa05743cf8d222b0b095825350ba422f63382a23d92e4149074e816a36c1cd28284d146267940b31f8818ea2",
+ "feb4fd6f9e87a56bef398b3284d2bda5b5b0e166583a66b61e538457ff0584872c21a32962b9928ffab58de4af2edd4e15d8b35570523207ff4e2a5aa7754caa",
+ "462f17bf005fb1c1b9e671779f665209ec2873e3e411f98dabf240a1d5ec3f95ce6796b6fc23fe171903b502023467dec7273ff74879b92967a2a43a5a183d33",
+ "d3338193b64553dbd38d144bea71c5915bb110e2d88180dbc5db364fd6171df317fc7268831b5aef75e4342b2fad8797ba39eddcef80e6ec08159350b1ad696d",
+ "e1590d585a3d39f7cb599abd479070966409a6846d4377acf4471d065d5db94129cc9be92573b05ed226be1e9b7cb0cabe87918589f80dadd4ef5ef25a93d28e",
+ "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e76842d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2",
+ "30186055c07949948183c850e9a756cc09937e247d9d928e869e20bafc3cd9721719d34e04a0899b92c736084550186886efba2e790d8be6ebf040b209c439a4",
+ "f3c4276cb863637712c241c444c5cc1e3554e0fddb174d035819dd83eb700b4ce88df3ab3841ba02085e1a99b4e17310c5341075c0458ba376c95a6818fbb3e2",
+ "0aa007c4dd9d5832393040a1583c930bca7dc5e77ea53add7e2b3f7c8e231368043520d4a3ef53c969b6bbfd025946f632bd7f765d53c21003b8f983f75e2a6a",
+ "08e9464720533b23a04ec24f7ae8c103145f765387d738777d3d343477fd1c58db052142cab754ea674378e18766c53542f71970171cc4f81694246b717d7564",
+ "d37ff7ad297993e7ec21e0f1b4b5ae719cdc83c5db687527f27516cbffa822888a6810ee5c1ca7bfe3321119be1ab7bfa0a502671c8329494df7ad6f522d440f",
+ "dd9042f6e464dcf86b1262f6accfafbd8cfd902ed3ed89abf78ffa482dbdeeb6969842394c9a1168ae3d481a017842f660002d42447c6b22f7b72f21aae021c9",
+ "bd965bf31e87d70327536f2a341cebc4768eca275fa05ef98f7f1b71a0351298de006fba73fe6733ed01d75801b4a928e54231b38e38c562b2e33ea1284992fa",
+ "65676d800617972fbd87e4b9514e1c67402b7a331096d3bfac22f1abb95374abc942f16e9ab0ead33b87c91968a6e509e119ff07787b3ef483e1dcdccf6e3022",
+ "939fa189699c5d2c81ddd1ffc1fa207c970b6a3685bb29ce1d3e99d42f2f7442da53e95a72907314f4588399a3ff5b0a92beb3f6be2694f9f86ecf2952d5b41c",
+ "c516541701863f91005f314108ceece3c643e04fc8c42fd2ff556220e616aaa6a48aeb97a84bad74782e8dff96a1a2fa949339d722edcaa32b57067041df88cc",
+ "987fd6e0d6857c553eaebb3d34970a2c2f6e89a3548f492521722b80a1c21a153892346d2cba6444212d56da9a26e324dccbc0dcde85d4d2ee4399eec5a64e8f",
+ "ae56deb1c2328d9c4017706bce6e99d41349053ba9d336d677c4c27d9fd50ae6aee17e853154e1f4fe7672346da2eaa31eea53fcf24a22804f11d03da6abfc2b",
+ "49d6a608c9bde4491870498572ac31aac3fa40938b38a7818f72383eb040ad39532bc06571e13d767e6945ab77c0bdc3b0284253343f9f6c1244ebf2ff0df866",
+ "da582ad8c5370b4469af862aa6467a2293b2b28bd80ae0e91f425ad3d47249fdf98825cc86f14028c3308c9804c78bfeeeee461444ce243687e1a50522456a1d",
+ "d5266aa3331194aef852eed86d7b5b2633a0af1c735906f2e13279f14931a9fc3b0eac5ce9245273bd1aa92905abe16278ef7efd47694789a7283b77da3c70f8",
+ "2962734c28252186a9a1111c732ad4de4506d4b4480916303eb7991d659ccda07a9911914bc75c418ab7a4541757ad054796e26797feaf36e9f6ad43f14b35a4",
+ "e8b79ec5d06e111bdfafd71e9f5760f00ac8ac5d8bf768f9ff6f08b8f026096b1cc3a4c973333019f1e3553e77da3f98cb9f542e0a90e5f8a940cc58e59844b3",
+ "dfb320c44f9d41d1efdcc015f08dd5539e526e39c87d509ae6812a969e5431bf4fa7d91ffd03b981e0d544cf72d7b1c0374f8801482e6dea2ef903877eba675e",
+ "d88675118fdb55a5fb365ac2af1d217bf526ce1ee9c94b2f0090b2c58a06ca58187d7fe57c7bed9d26fca067b4110eefcd9a0a345de872abe20de368001b0745",
+ "b893f2fc41f7b0dd6e2f6aa2e0370c0cff7df09e3acfcc0e920b6e6fad0ef747c40668417d342b80d2351e8c175f20897a062e9765e6c67b539b6ba8b9170545",
+ "6c67ec5697accd235c59b486d7b70baeedcbd4aa64ebd4eef3c7eac189561a726250aec4d48cadcafbbe2ce3c16ce2d691a8cce06e8879556d4483ed7165c063",
+ "f1aa2b044f8f0c638a3f362e677b5d891d6fd2ab0765f6ee1e4987de057ead357883d9b405b9d609eea1b869d97fb16d9b51017c553f3b93c0a1e0f1296fedcd",
+ "cbaa259572d4aebfc1917acddc582b9f8dfaa928a198ca7acd0f2aa76a134a90252e6298a65b08186a350d5b7626699f8cb721a3ea5921b753ae3a2dce24ba3a",
+ "fa1549c9796cd4d303dcf452c1fbd5744fd9b9b47003d920b92de34839d07ef2a29ded68f6fc9e6c45e071a2e48bd50c5084e96b657dd0404045a1ddefe282ed",
+ "5cf2ac897ab444dcb5c8d87c495dbdb34e1838b6b629427caa51702ad0f9688525f13bec503a3c3a2c80a65e0b5715e8afab00ffa56ec455a49a1ad30aa24fcd",
+ "9aaf80207bace17bb7ab145757d5696bde32406ef22b44292ef65d4519c3bb2ad41a59b62cc3e94b6fa96d32a7faadae28af7d35097219aa3fd8cda31e40c275",
+ "af88b163402c86745cb650c2988fb95211b94b03ef290eed9662034241fd51cf398f8073e369354c43eae1052f9b63b08191caa138aa54fea889cc7024236897",
+ "48fa7d64e1ceee27b9864db5ada4b53d00c9bc7626555813d3cd6730ab3cc06ff342d727905e33171bde6e8476e77fb1720861e94b73a2c538d254746285f430",
+ "0e6fd97a85e904f87bfe85bbeb34f69e1f18105cf4ed4f87aec36c6e8b5f68bd2a6f3dc8a9ecb2b61db4eedb6b2ea10bf9cb0251fb0f8b344abf7f366b6de5ab",
+ "06622da5787176287fdc8fed440bad187d830099c94e6d04c8e9c954cda70c8bb9e1fc4a6d0baa831b9b78ef6648681a4867a11da93ee36e5e6a37d87fc63f6f",
+ "1da6772b58fabf9c61f68d412c82f182c0236d7d575ef0b58dd22458d643cd1dfc93b03871c316d8430d312995d4197f0874c99172ba004a01ee295abac24e46",
+ "3cd2d9320b7b1d5fb9aab951a76023fa667be14a9124e394513918a3f44096ae4904ba0ffc150b63bc7ab1eeb9a6e257e5c8f000a70394a5afd842715de15f29",
+ "04cdc14f7434e0b4be70cb41db4c779a88eaef6accebcb41f2d42fffe7f32a8e281b5c103a27021d0d08362250753cdf70292195a53a48728ceb5844c2d98bab",
+ "9071b7a8a075d0095b8fb3ae5113785735ab98e2b52faf91d5b89e44aac5b5d4ebbf91223b0ff4c71905da55342e64655d6ef8c89a4768c3f93a6dc0366b5bc8",
+ "ebb30240dd96c7bc8d0abe49aa4edcbb4afdc51ff9aaf720d3f9e7fbb0f9c6d6571350501769fc4ebd0b2141247ff400d4fd4be414edf37757bb90a32ac5c65a",
+ "8532c58bf3c8015d9d1cbe00eef1f5082f8f3632fbe9f1ed4f9dfb1fa79e8283066d77c44c4af943d76b300364aecbd0648c8a8939bd204123f4b56260422dec",
+ "fe9846d64f7c7708696f840e2d76cb4408b6595c2f81ec6a28a7f2f20cb88cfe6ac0b9e9b8244f08bd7095c350c1d0842f64fb01bb7f532dfcd47371b0aeeb79",
+ "28f17ea6fb6c42092dc264257e29746321fb5bdaea9873c2a7fa9d8f53818e899e161bc77dfe8090afd82bf2266c5c1bc930a8d1547624439e662ef695f26f24",
+ "ec6b7d7f030d4850acae3cb615c21dd25206d63e84d1db8d957370737ba0e98467ea0ce274c66199901eaec18a08525715f53bfdb0aacb613d342ebdceeddc3b",
+ "b403d3691c03b0d3418df327d5860d34bbfcc4519bfbce36bf33b208385fadb9186bc78a76c489d89fd57e7dc75412d23bcd1dae8470ce9274754bb8585b13c5",
+ "31fc79738b8772b3f55cd8178813b3b52d0db5a419d30ba9495c4b9da0219fac6df8e7c23a811551a62b827f256ecdb8124ac8a6792ccfecc3b3012722e94463",
+ "bb2039ec287091bcc9642fc90049e73732e02e577e2862b32216ae9bedcd730c4c284ef3968c368b7d37584f97bd4b4dc6ef6127acfe2e6ae2509124e66c8af4",
+ "f53d68d13f45edfcb9bd415e2831e938350d5380d3432278fc1c0c381fcb7c65c82dafe051d8c8b0d44e0974a0e59ec7bf7ed0459f86e96f329fc79752510fd3",
+ "8d568c7984f0ecdf7640fbc483b5d8c9f86634f6f43291841b309a350ab9c1137d24066b09da9944bac54d5bb6580d836047aac74ab724b887ebf93d4b32eca9",
+ "c0b65ce5a96ff774c456cac3b5f2c4cd359b4ff53ef93a3da0778be4900d1e8da1601e769e8f1b02d2a2f8c5b9fa10b44f1c186985468feeb008730283a6657d",
+ "4900bba6f5fb103ece8ec96ada13a5c3c85488e05551da6b6b33d988e611ec0fe2e3c2aa48ea6ae8986a3a231b223c5d27cec2eadde91ce07981ee652862d1e4",
+ "c7f5c37c7285f927f76443414d4357ff789647d7a005a5a787e03c346b57f49f21b64fa9cf4b7e45573e23049017567121a9c3d4b2b73ec5e9413577525db45a",
+ "ec7096330736fdb2d64b5653e7475da746c23a4613a82687a28062d3236364284ac01720ffb406cfe265c0df626a188c9e5963ace5d3d5bb363e32c38c2190a6",
+ "82e744c75f4649ec52b80771a77d475a3bc091989556960e276a5f9ead92a03f718742cdcfeaee5cb85c44af198adc43a4a428f5f0c2ddb0be36059f06d7df73",
+ "2834b7a7170f1f5b68559ab78c1050ec21c919740b784a9072f6e5d69f828d70c919c5039fb148e39e2c8a52118378b064ca8d5001cd10a5478387b966715ed6",
+ "16b4ada883f72f853bb7ef253efcab0c3e2161687ad61543a0d2824f91c1f81347d86be709b16996e17f2dd486927b0288ad38d13063c4a9672c39397d3789b6",
+ "78d048f3a69d8b54ae0ed63a573ae350d89f7c6cf1f3688930de899afa037697629b314e5cd303aa62feea72a25bf42b304b6c6bcb27fae21c16d925e1fbdac3",
+ "0f746a48749287ada77a82961f05a4da4abdb7d77b1220f836d09ec814359c0ec0239b8c7b9ff9e02f569d1b301ef67c4612d1de4f730f81c12c40cc063c5caa",
+ "f0fc859d3bd195fbdc2d591e4cdac15179ec0f1dc821c11df1f0c1d26e6260aaa65b79fafacafd7d3ad61e600f250905f5878c87452897647a35b995bcadc3a3",
+ "2620f687e8625f6a412460b42e2cef67634208ce10a0cbd4dff7044a41b7880077e9f8dc3b8d1216d3376a21e015b58fb279b521d83f9388c7382c8505590b9b",
+ "227e3aed8d2cb10b918fcb04f9de3e6d0a57e08476d93759cd7b2ed54a1cbf0239c528fb04bbf288253e601d3bc38b21794afef90b17094a182cac557745e75f",
+ "1a929901b09c25f27d6b35be7b2f1c4745131fdebca7f3e2451926720434e0db6e74fd693ad29b777dc3355c592a361c4873b01133a57c2e3b7075cbdb86f4fc",
+ "5fd7968bc2fe34f220b5e3dc5af9571742d73b7d60819f2888b629072b96a9d8ab2d91b82d0a9aaba61bbd39958132fcc4257023d1eca591b3054e2dc81c8200",
+ "dfcce8cf32870cc6a503eadafc87fd6f78918b9b4d0737db6810be996b5497e7e5cc80e312f61e71ff3e9624436073156403f735f56b0b01845c18f6caf772e6",
+ "02f7ef3a9ce0fff960f67032b296efca3061f4934d690749f2d01c35c81c14f39a67fa350bc8a0359bf1724bffc3bca6d7c7bba4791fd522a3ad353c02ec5aa8",
+ "64be5c6aba65d594844ae78bb022e5bebe127fd6b6ffa5a13703855ab63b624dcd1a363f99203f632ec386f3ea767fc992e8ed9686586aa27555a8599d5b808f",
+ "f78585505c4eaa54a8b5be70a61e735e0ff97af944ddb3001e35d86c4e2199d976104b6ae31750a36a726ed285064f5981b503889fef822fcdc2898dddb7889a",
+ "e4b5566033869572edfd87479a5bb73c80e8759b91232879d96b1dda36c012076ee5a2ed7ae2de63ef8406a06aea82c188031b560beafb583fb3de9e57952a7e",
+ "e1b3e7ed867f6c9484a2a97f7715f25e25294e992e41f6a7c161ffc2adc6daaeb7113102d5e6090287fe6ad94ce5d6b739c6ca240b05c76fb73f25dd024bf935",
+ "85fd085fdc12a080983df07bd7012b0d402a0f4043fcb2775adf0bad174f9b08d1676e476985785c0a5dcc41dbff6d95ef4d66a3fbdc4a74b82ba52da0512b74",
+ "aed8fa764b0fbff821e05233d2f7b0900ec44d826f95e93c343c1bc3ba5a24374b1d616e7e7aba453a0ada5e4fab5382409e0d42ce9c2bc7fb39a99c340c20f0",
+ "7ba3b2e297233522eeb343bd3ebcfd835a04007735e87f0ca300cbee6d416565162171581e4020ff4cf176450f1291ea2285cb9ebffe4c56660627685145051c",
+ "de748bcf89ec88084721e16b85f30adb1a6134d664b5843569babc5bbd1a15ca9b61803c901a4fef32965a1749c9f3a4e243e173939dc5a8dc495c671ab52145",
+ "aaf4d2bdf200a919706d9842dce16c98140d34bc433df320aba9bd429e549aa7a3397652a4d768277786cf993cde2338673ed2e6b66c961fefb82cd20c93338f",
+ "c408218968b788bf864f0997e6bc4c3dba68b276e2125a4843296052ff93bf5767b8cdce7131f0876430c1165fec6c4f47adaa4fd8bcfacef463b5d3d0fa61a0",
+ "76d2d819c92bce55fa8e092ab1bf9b9eab237a25267986cacf2b8ee14d214d730dc9a5aa2d7b596e86a1fd8fa0804c77402d2fcd45083688b218b1cdfa0dcbcb",
+ "72065ee4dd91c2d8509fa1fc28a37c7fc9fa7d5b3f8ad3d0d7a25626b57b1b44788d4caf806290425f9890a3a2a35a905ab4b37acfd0da6e4517b2525c9651e4",
+ "64475dfe7600d7171bea0b394e27c9b00d8e74dd1e416a79473682ad3dfdbb706631558055cfc8a40e07bd015a4540dcdea15883cbbf31412df1de1cd4152b91",
+ "12cd1674a4488a5d7c2b3160d2e2c4b58371bedad793418d6f19c6ee385d70b3e06739369d4df910edb0b0a54cbff43d54544cd37ab3a06cfa0a3ddac8b66c89",
+ "60756966479dedc6dd4bcff8ea7d1d4ce4d4af2e7b097e32e3763518441147cc12b3c0ee6d2ecabf1198cec92e86a3616fba4f4e872f5825330adbb4c1dee444",
+ "a7803bcb71bc1d0f4383dde1e0612e04f872b715ad30815c2249cf34abb8b024915cb2fc9f4e7cc4c8cfd45be2d5a91eab0941c7d270e2da4ca4a9f7ac68663a",
+ "b84ef6a7229a34a750d9a98ee2529871816b87fbe3bc45b45fa5ae82d5141540211165c3c5d7a7476ba5a4aa06d66476f0d9dc49a3f1ee72c3acabd498967414",
+ "fae4b6d8efc3f8c8e64d001dabec3a21f544e82714745251b2b4b393f2f43e0da3d403c64db95a2cb6e23ebb7b9e94cdd5ddac54f07c4a61bd3cb10aa6f93b49",
+ "34f7286605a122369540141ded79b8957255da2d4155abbf5a8dbb89c8eb7ede8eeef1daa46dc29d751d045dc3b1d658bb64b80ff8589eddb3824b13da235a6b",
+ "3b3b48434be27b9eababba43bf6b35f14b30f6a88dc2e750c358470d6b3aa3c18e47db4017fa55106d8252f016371a00f5f8b070b74ba5f23cffc5511c9f09f0",
+ "ba289ebd6562c48c3e10a8ad6ce02e73433d1e93d7c9279d4d60a7e879ee11f441a000f48ed9f7c4ed87a45136d7dccdca482109c78a51062b3ba4044ada2469",
+ "022939e2386c5a37049856c850a2bb10a13dfea4212b4c732a8840a9ffa5faf54875c5448816b2785a007da8a8d2bc7d71a54e4e6571f10b600cbdb25d13ede3",
+ "e6fec19d89ce8717b1a087024670fe026f6c7cbda11caef959bb2d351bf856f8055d1c0ebdaaa9d1b17886fc2c562b5e99642fc064710c0d3488a02b5ed7f6fd",
+ "94c96f02a8f576aca32ba61c2b206f907285d9299b83ac175c209a8d43d53bfe683dd1d83e7549cb906c28f59ab7c46f8751366a28c39dd5fe2693c9019666c8",
+ "31a0cd215ebd2cb61de5b9edc91e6195e31c59a5648d5c9f737e125b2605708f2e325ab3381c8dce1a3e958886f1ecdc60318f882cfe20a24191352e617b0f21",
+ "91ab504a522dce78779f4c6c6ba2e6b6db5565c76d3e7e7c920caf7f757ef9db7c8fcf10e57f03379ea9bf75eb59895d96e149800b6aae01db778bb90afbc989",
+ "d85cabc6bd5b1a01a5afd8c6734740da9fd1c1acc6db29bfc8a2e5b668b028b6b3154bfb8703fa3180251d589ad38040ceb707c4bad1b5343cb426b61eaa49c1",
+ "d62efbec2ca9c1f8bd66ce8b3f6a898cb3f7566ba6568c618ad1feb2b65b76c3ce1dd20f7395372faf28427f61c9278049cf0140df434f5633048c86b81e0399",
+ "7c8fdc6175439e2c3db15bafa7fb06143a6a23bc90f449e79deef73c3d492a671715c193b6fea9f036050b946069856b897e08c00768f5ee5ddcf70b7cd6d0e0",
+ "58602ee7468e6bc9df21bd51b23c005f72d6cb013f0a1b48cbec5eca299299f97f09f54a9a01483eaeb315a6478bad37ba47ca1347c7c8fc9e6695592c91d723",
+ "27f5b79ed256b050993d793496edf4807c1d85a7b0a67c9c4fa99860750b0ae66989670a8ffd7856d7ce411599e58c4d77b232a62bef64d15275be46a68235ff",
+ "3957a976b9f1887bf004a8dca942c92d2b37ea52600f25e0c9bc5707d0279c00c6e85a839b0d2d8eb59c51d94788ebe62474a791cadf52cccf20f5070b6573fc",
+ "eaa2376d55380bf772ecca9cb0aa4668c95c707162fa86d518c8ce0ca9bf7362b9f2a0adc3ff59922df921b94567e81e452f6c1a07fc817cebe99604b3505d38",
+ "c1e2c78b6b2734e2480ec550434cb5d613111adcc21d475545c3b1b7e6ff12444476e5c055132e2229dc0f807044bb919b1a5662dd38a9ee65e243a3911aed1a",
+ "8ab48713389dd0fcf9f965d3ce66b1e559a1f8c58741d67683cd971354f452e62d0207a65e436c5d5d8f8ee71c6abfe50e669004c302b31a7ea8311d4a916051",
+ "24ce0addaa4c65038bd1b1c0f1452a0b128777aabc94a29df2fd6c7e2f85f8ab9ac7eff516b0e0a825c84a24cfe492eaad0a6308e46dd42fe8333ab971bb30ca",
+ "5154f929ee03045b6b0c0004fa778edee1d139893267cc84825ad7b36c63de32798e4a166d24686561354f63b00709a1364b3c241de3febf0754045897467cd4",
+ "e74e907920fd87bd5ad636dd11085e50ee70459c443e1ce5809af2bc2eba39f9e6d7128e0e3712c316da06f4705d78a4838e28121d4344a2c79c5e0db307a677",
+ "bf91a22334bac20f3fd80663b3cd06c4e8802f30e6b59f90d3035cc9798a217ed5a31abbda7fa6842827bdf2a7a1c21f6fcfccbb54c6c52926f32da816269be1",
+ "d9d5c74be5121b0bd742f26bffb8c89f89171f3f934913492b0903c271bbe2b3395ef259669bef43b57f7fcc3027db01823f6baee66e4f9fead4d6726c741fce",
+ "50c8b8cf34cd879f80e2faab3230b0c0e1cc3e9dcadeb1b9d97ab923415dd9a1fe38addd5c11756c67990b256e95ad6d8f9fedce10bf1c90679cde0ecf1be347",
+ "0a386e7cd5dd9b77a035e09fe6fee2c8ce61b5383c87ea43205059c5e4cd4f4408319bb0a82360f6a58e6c9ce3f487c446063bf813bc6ba535e17fc1826cfc91",
+ "1f1459cb6b61cbac5f0efe8fc487538f42548987fcd56221cfa7beb22504769e792c45adfb1d6b3d60d7b749c8a75b0bdf14e8ea721b95dca538ca6e25711209",
+ "e58b3836b7d8fedbb50ca5725c6571e74c0785e97821dab8b6298c10e4c079d4a6cdf22f0fedb55032925c16748115f01a105e77e00cee3d07924dc0d8f90659",
+ "b929cc6505f020158672deda56d0db081a2ee34c00c1100029bdf8ea98034fa4bf3e8655ec697fe36f40553c5bb46801644a627d3342f4fc92b61f03290fb381",
+ "72d353994b49d3e03153929a1e4d4f188ee58ab9e72ee8e512f29bc773913819ce057ddd7002c0433ee0a16114e3d156dd2c4a7e80ee53378b8670f23e33ef56",
+ "c70ef9bfd775d408176737a0736d68517ce1aaad7e81a93c8c1ed967ea214f56c8a377b1763e676615b60f3988241eae6eab9685a5124929d28188f29eab06f7",
+ "c230f0802679cb33822ef8b3b21bf7a9a28942092901d7dac3760300831026cf354c9232df3e084d9903130c601f63c1f4a4a4b8106e468cd443bbe5a734f45f",
+ "6f43094cafb5ebf1f7a4937ec50f56a4c9da303cbb55ac1f27f1f1976cd96beda9464f0e7b9c54620b8a9fba983164b8be3578425a024f5fe199c36356b88972",
+ "3745273f4c38225db2337381871a0c6aafd3af9b018c88aa02025850a5dc3a42a1a3e03e56cbf1b0876d63a441f1d2856a39b8801eb5af325201c415d65e97fe",
+ "c50c44cca3ec3edaae779a7e179450ebdda2f97067c690aa6c5a4ac7c30139bb27c0df4db3220e63cb110d64f37ffe078db72653e2daacf93ae3f0a2d1a7eb2e",
+ "8aef263e385cbc61e19b28914243262af5afe8726af3ce39a79c27028cf3ecd3f8d2dfd9cfc9ad91b58f6f20778fd5f02894a3d91c7d57d1e4b866a7f364b6be",
+ "28696141de6e2d9bcb3235578a66166c1448d3e905a1b482d423be4bc5369bc8c74dae0acc9cc123e1d8ddce9f97917e8c019c552da32d39d2219b9abf0fa8c8",
+ "2fb9eb2085830181903a9dafe3db428ee15be7662224efd643371fb25646aee716e531eca69b2bdc8233f1a8081fa43da1500302975a77f42fa592136710e9dc",
+ "66f9a7143f7a3314a669bf2e24bbb35014261d639f495b6c9c1f104fe8e320aca60d4550d69d52edbd5a3cdeb4014ae65b1d87aa770b69ae5c15f4330b0b0ad8",
+ "f4c4dd1d594c3565e3e25ca43dad82f62abea4835ed4cd811bcd975e46279828d44d4c62c3679f1b7f7b9dd4571d7b49557347b8c5460cbdc1bef690fb2a08c0",
+ "8f1dc9649c3a84551f8f6e91cac68242a43b1f8f328ee92280257387fa7559aa6db12e4aeadc2d26099178749c6864b357f3f83b2fb3efa8d2a8db056bed6bcc",
+ "3139c1a7f97afd1675d460ebbc07f2728aa150df849624511ee04b743ba0a833092f18c12dc91b4dd243f333402f59fe28abdbbbae301e7b659c7a26d5c0f979",
+ "06f94a2996158a819fe34c40de3cf0379fd9fb85b3e363ba3926a0e7d960e3f4c2e0c70c7ce0ccb2a64fc29869f6e7ab12bd4d3f14fce943279027e785fb5c29",
+ "c29c399ef3eee8961e87565c1ce263925fc3d0ce267d13e48dd9e732ee67b0f69fad56401b0f10fcaac119201046cca28c5b14abdea3212ae65562f7f138db3d",
+ "4cec4c9df52eef05c3f6faaa9791bc7445937183224ecc37a1e58d0132d35617531d7e795f52af7b1eb9d147de1292d345fe341823f8e6bc1e5badca5c656108",
+ "898bfbae93b3e18d00697eab7d9704fa36ec339d076131cefdf30edbe8d9cc81c3a80b129659b163a323bab9793d4feed92d54dae966c77529764a09be88db45",
+ "ee9bd0469d3aaf4f14035be48a2c3b84d9b4b1fff1d945e1f1c1d38980a951be197b25fe22c731f20aeacc930ba9c4a1f4762227617ad350fdabb4e80273a0f4",
+ "3d4d3113300581cd96acbf091c3d0f3c310138cd6979e6026cde623e2dd1b24d4a8638bed1073344783ad0649cc6305ccec04beb49f31c633088a99b65130267",
+ "95c0591ad91f921ac7be6d9ce37e0663ed8011c1cfd6d0162a5572e94368bac02024485e6a39854aa46fe38e97d6c6b1947cd272d86b06bb5b2f78b9b68d559d",
+ "227b79ded368153bf46c0a3ca978bfdbef31f3024a5665842468490b0ff748ae04e7832ed4c9f49de9b1706709d623e5c8c15e3caecae8d5e433430ff72f20eb",
+ "5d34f3952f0105eef88ae8b64c6ce95ebfade0e02c69b08762a8712d2e4911ad3f941fc4034dc9b2e479fdbcd279b902faf5d838bb2e0c6495d372b5b7029813",
+ "7f939bf8353abce49e77f14f3750af20b7b03902e1a1e7fb6aaf76d0259cd401a83190f15640e74f3e6c5a90e839c7821f6474757f75c7bf9002084ddc7a62dc",
+ "062b61a2f9a33a71d7d0a06119644c70b0716a504de7e5e1be49bd7b86e7ed6817714f9f0fc313d06129597e9a2235ec8521de36f7290a90ccfc1ffa6d0aee29",
+ "f29e01eeae64311eb7f1c6422f946bf7bea36379523e7b2bbaba7d1d34a22d5ea5f1c5a09d5ce1fe682cced9a4798d1a05b46cd72dff5c1b355440b2a2d476bc",
+ "ec38cd3bbab3ef35d7cb6d5c914298351d8a9dc97fcee051a8a02f58e3ed6184d0b7810a5615411ab1b95209c3c810114fdeb22452084e77f3f847c6dbaafe16",
+ "c2aef5e0ca43e82641565b8cb943aa8ba53550caef793b6532fafad94b816082f0113a3ea2f63608ab40437ecc0f0229cb8fa224dcf1c478a67d9b64162b92d1",
+ "15f534efff7105cd1c254d074e27d5898b89313b7d366dc2d7d87113fa7d53aae13f6dba487ad8103d5e854c91fdb6e1e74b2ef6d1431769c30767dde067a35c",
+ "89acbca0b169897a0a2714c2df8c95b5b79cb69390142b7d6018bb3e3076b099b79a964152a9d912b1b86412b7e372e9cecad7f25d4cbab8a317be36492a67d7",
+ "e3c0739190ed849c9c962fd9dbb55e207e624fcac1eb417691515499eea8d8267b7e8f1287a63633af5011fde8c4ddf55bfdf722edf88831414f2cfaed59cb9a",
+ "8d6cf87c08380d2d1506eee46fd4222d21d8c04e585fbfd08269c98f702833a156326a0724656400ee09351d57b440175e2a5de93cc5f80db6daf83576cf75fa",
+ "da24bede383666d563eeed37f6319baf20d5c75d1635a6ba5ef4cfa1ac95487e96f8c08af600aab87c986ebad49fc70a58b4890b9c876e091016daf49e1d322e",
+ "f9d1d1b1e87ea7ae753a029750cc1cf3d0157d41805e245c5617bb934e732f0ae3180b78e05bfe76c7c3051e3e3ac78b9b50c05142657e1e03215d6ec7bfd0fc",
+ "11b7bc1668032048aa43343de476395e814bbbc223678db951a1b03a021efac948cfbe215f97fe9a72a2f6bc039e3956bfa417c1a9f10d6d7ba5d3d32ff323e5",
+ "b8d9000e4fc2b066edb91afee8e7eb0f24e3a201db8b6793c0608581e628ed0bcc4e5aa6787992a4bcc44e288093e63ee83abd0bc3ec6d0934a674a4da13838a",
+ "ce325e294f9b6719d6b61278276ae06a2564c03bb0b783fafe785bdf89c7d5acd83e78756d301b445699024eaeb77b54d477336ec2a4f332f2b3f88765ddb0c3",
+ "29acc30e9603ae2fccf90bf97e6cc463ebe28c1b2f9b4b765e70537c25c702a29dcbfbf14c99c54345ba2b51f17b77b5f15db92bbad8fa95c471f5d070a137cc",
+ "3379cbaae562a87b4c0425550ffdd6bfe1203f0d666cc7ea095be407a5dfe61ee91441cd5154b3e53b4f5fb31ad4c7a9ad5c7af4ae679aa51a54003a54ca6b2d",
+ "3095a349d245708c7cf550118703d7302c27b60af5d4e67fc978f8a4e60953c7a04f92fcf41aee64321ccb707a895851552b1e37b00bc5e6b72fa5bcef9e3fff",
+ "07262d738b09321f4dbccec4bb26f48cb0f0ed246ce0b31b9a6e7bc683049f1f3e5545f28ce932dd985c5ab0f43bd6de0770560af329065ed2e49d34624c2cbb",
+ "b6405eca8ee3316c87061cc6ec18dba53e6c250c63ba1f3bae9e55dd3498036af08cd272aa24d713c6020d77ab2f3919af1a32f307420618ab97e73953994fb4",
+ "7ee682f63148ee45f6e5315da81e5c6e557c2c34641fc509c7a5701088c38a74756168e2cd8d351e88fd1a451f360a01f5b2580f9b5a2e8cfc138f3dd59a3ffc",
+ "1d263c179d6b268f6fa016f3a4f29e943891125ed8593c81256059f5a7b44af2dcb2030d175c00e62ecaf7ee96682aa07ab20a611024a28532b1c25b86657902",
+ "106d132cbdb4cd2597812846e2bc1bf732fec5f0a5f65dbb39ec4e6dc64ab2ce6d24630d0f15a805c3540025d84afa98e36703c3dbee713e72dde8465bc1be7e",
+ "0e79968226650667a8d862ea8da4891af56a4e3a8b6d1750e394f0dea76d640d85077bcec2cc86886e506751b4f6a5838f7f0b5fef765d9dc90dcdcbaf079f08",
+ "521156a82ab0c4e566e5844d5e31ad9aaf144bbd5a464fdca34dbd5717e8ff711d3ffebbfa085d67fe996a34f6d3e4e60b1396bf4b1610c263bdbb834d560816",
+ "1aba88befc55bc25efbce02db8b9933e46f57661baeabeb21cc2574d2a518a3cba5dc5a38e49713440b25f9c744e75f6b85c9d8f4681f676160f6105357b8406",
+ "5a9949fcb2c473cda968ac1b5d08566dc2d816d960f57e63b898fa701cf8ebd3f59b124d95bfbbedc5f1cf0e17d5eaed0c02c50b69d8a402cabcca4433b51fd4",
+ "b0cead09807c672af2eb2b0f06dde46cf5370e15a4096b1a7d7cbb36ec31c205fbefca00b7a4162fa89fb4fb3eb78d79770c23f44e7206664ce3cd931c291e5d",
+ "bb6664931ec97044e45b2ae420ae1c551a8874bc937d08e969399c3964ebdba8346cdd5d09caafe4c28ba7ec788191ceca65ddd6f95f18583e040d0f30d0364d",
+ "65bc770a5faa3792369803683e844b0be7ee96f29f6d6a35568006bd5590f9a4ef639b7a8061c7b0424b66b60ac34af3119905f33a9d8c3ae18382ca9b689900",
+ "ea9b4dca333336aaf839a45c6eaa48b8cb4c7ddabffea4f643d6357ea6628a480a5b45f2b052c1b07d1fedca918b6f1139d80f74c24510dcbaa4be70eacc1b06",
+ "e6342fb4a780ad975d0e24bce149989b91d360557e87994f6b457b895575cc02d0c15bad3ce7577f4c63927ff13f3e381ff7e72bdbe745324844a9d27e3f1c01",
+ "3e209c9b33e8e461178ab46b1c64b49a07fb745f1c8bc95fbfb94c6b87c69516651b264ef980937fad41238b91ddc011a5dd777c7efd4494b4b6ecd3a9c22ac0",
+ "fd6a3d5b1875d80486d6e69694a56dbb04a99a4d051f15db2689776ba1c4882e6d462a603b7015dc9f4b7450f05394303b8652cfb404a266962c41bae6e18a94",
+ "951e27517e6bad9e4195fc8671dee3e7e9be69cee1422cb9fecfce0dba875f7b310b93ee3a3d558f941f635f668ff832d2c1d033c5e2f0997e4c66f147344e02",
+ "8eba2f874f1ae84041903c7c4253c82292530fc8509550bfdc34c95c7e2889d5650b0ad8cb988e5c4894cb87fbfbb19612ea93ccc4c5cad17158b9763464b492",
+ "16f712eaa1b7c6354719a8e7dbdfaf55e4063a4d277d947550019b38dfb564830911057d50506136e2394c3b28945cc964967d54e3000c2181626cfb9b73efd2",
+ "c39639e7d5c7fb8cdd0fd3e6a52096039437122f21c78f1679cea9d78a734c56ecbeb28654b4f18e342c331f6f7229ec4b4bc281b2d80a6eb50043f31796c88c",
+ "72d081af99f8a173dcc9a0ac4eb3557405639a29084b54a40172912a2f8a395129d5536f0918e902f9e8fa6000995f4168ddc5f893011be6a0dbc9b8a1a3f5bb",
+ "c11aa81e5efd24d5fc27ee586cfd8847fbb0e27601ccece5ecca0198e3c7765393bb74457c7e7a27eb9170350e1fb53857177506be3e762cc0f14d8c3afe9077",
+ "c28f2150b452e6c0c424bcde6f8d72007f9310fed7f2f87de0dbb64f4479d6c1441ba66f44b2accee61609177ed340128b407ecec7c64bbe50d63d22d8627727",
+ "f63d88122877ec30b8c8b00d22e89000a966426112bd44166e2f525b769ccbe9b286d437a0129130dde1a86c43e04bedb594e671d98283afe64ce331de9828fd",
+ "348b0532880b88a6614a8d7408c3f913357fbb60e995c60205be9139e74998aede7f4581e42f6b52698f7fa1219708c14498067fd1e09502de83a77dd281150c",
+ "5133dc8bef725359dff59792d85eaf75b7e1dcd1978b01c35b1b85fcebc63388ad99a17b6346a217dc1a9622ebd122ecf6913c4d31a6b52a695b86af00d741a0",
+ "2753c4c0e98ecad806e88780ec27fccd0f5c1ab547f9e4bf1659d192c23aa2cc971b58b6802580baef8adc3b776ef7086b2545c2987f348ee3719cdef258c403",
+ "b1663573ce4b9d8caefc865012f3e39714b9898a5da6ce17c25a6a47931a9ddb9bbe98adaa553beed436e89578455416c2a52a525cf2862b8d1d49a2531b7391",
+ "64f58bd6bfc856f5e873b2a2956ea0eda0d6db0da39c8c7fc67c9f9feefcff3072cdf9e6ea37f69a44f0c61aa0da3693c2db5b54960c0281a088151db42b11e8",
+ "0764c7be28125d9065c4b98a69d60aede703547c66a12e17e1c618994132f5ef82482c1e3fe3146cc65376cc109f0138ed9a80e49f1f3c7d610d2f2432f20605",
+ "f748784398a2ff03ebeb07e155e66116a839741a336e32da71ec696001f0ad1b25cd48c69cfca7265eca1dd71904a0ce748ac4124f3571076dfa7116a9cf00e9",
+ "3f0dbc0186bceb6b785ba78d2a2a013c910be157bdaffae81bb6663b1a73722f7f1228795f3ecada87cf6ef0078474af73f31eca0cc200ed975b6893f761cb6d",
+ "d4762cd4599876ca75b2b8fe249944dbd27ace741fdab93616cbc6e425460feb51d4e7adcc38180e7fc47c89024a7f56191adb878dfde4ead62223f5a2610efe",
+ "cd36b3d5b4c91b90fcbba79513cfee1907d8645a162afd0cd4cf4192d4a5f4c892183a8eacdb2b6b6a9d9aa8c11ac1b261b380dbee24ca468f1bfd043c58eefe",
+ "98593452281661a53c48a9d8cd790826c1a1ce567738053d0bee4a91a3d5bd92eefdbabebe3204f2031ca5f781bda99ef5d8ae56e5b04a9e1ecd21b0eb05d3e1",
+ "771f57dd2775ccdab55921d3e8e30ccf484d61fe1c1b9c2ae819d0fb2a12fab9be70c4a7a138da84e8280435daade5bbe66af0836a154f817fb17f3397e725a3",
+ "c60897c6f828e21f16fbb5f15b323f87b6c8955eabf1d38061f707f608abdd993fac3070633e286cf8339ce295dd352df4b4b40b2f29da1dd50b3a05d079e6bb",
+ "8210cd2c2d3b135c2cf07fa0d1433cd771f325d075c6469d9c7f1ba0943cd4ab09808cabf4acb9ce5bb88b498929b4b847f681ad2c490d042db2aec94214b06b",
+ "1d4edfffd8fd80f7e4107840fa3aa31e32598491e4af7013c197a65b7f36dd3ac4b478456111cd4309d9243510782fa31b7c4c95fa951520d020eb7e5c36e4ef",
+ "af8e6e91fab46ce4873e1a50a8ef448cc29121f7f74deef34a71ef89cc00d9274bc6c2454bbb3230d8b2ec94c62b1dec85f3593bfa30ea6f7a44d7c09465a253",
+ "29fd384ed4906f2d13aa9fe7af905990938bed807f1832454a372ab412eea1f5625a1fcc9ac8343b7c67c5aba6e0b1cc4644654913692c6b39eb9187ceacd3ec",
+ "a268c7885d9874a51c44dffed8ea53e94f78456e0b2ed99ff5a3924760813826d960a15edbedbb5de5226ba4b074e71b05c55b9756bb79e55c02754c2c7b6c8a",
+ "0cf8545488d56a86817cd7ecb10f7116b7ea530a45b6ea497b6c72c997e09e3d0da8698f46bb006fc977c2cd3d1177463ac9057fdd1662c85d0c126443c10473",
+ "b39614268fdd8781515e2cfebf89b4d5402bab10c226e6344e6b9ae000fb0d6c79cb2f3ec80e80eaeb1980d2f8698916bd2e9f747236655116649cd3ca23a837",
+ "74bef092fc6f1e5dba3663a3fb003b2a5ba257496536d99f62b9d73f8f9eb3ce9ff3eec709eb883655ec9eb896b9128f2afc89cf7d1ab58a72f4a3bf034d2b4a",
+ "3a988d38d75611f3ef38b8774980b33e573b6c57bee0469ba5eed9b44f29945e7347967fba2c162e1c3be7f310f2f75ee2381e7bfd6b3f0baea8d95dfb1dafb1",
+ "58aedfce6f67ddc85a28c992f1c0bd0969f041e66f1ee88020a125cbfcfebcd61709c9c4eba192c15e69f020d462486019fa8dea0cd7a42921a19d2fe546d43d",
+ "9347bd291473e6b4e368437b8e561e065f649a6d8ada479ad09b1999a8f26b91cf6120fd3bfe014e83f23acfa4c0ad7b3712b2c3c0733270663112ccd9285cd9",
+ "b32163e7c5dbb5f51fdc11d2eac875efbbcb7e7699090a7e7ff8a8d50795af5d74d9ff98543ef8cdf89ac13d0485278756e0ef00c817745661e1d59fe38e7537",
+ "1085d78307b1c4b008c57a2e7e5b234658a0a82e4ff1e4aaac72b312fda0fe27d233bc5b10e9cc17fdc7697b540c7d95eb215a19a1a0e20e1abfa126efd568c7",
+ "4e5c734c7dde011d83eac2b7347b373594f92d7091b9ca34cb9c6f39bdf5a8d2f134379e16d822f6522170ccf2ddd55c84b9e6c64fc927ac4cf8dfb2a17701f2",
+ "695d83bd990a1117b3d0ce06cc888027d12a054c2677fd82f0d4fbfc93575523e7991a5e35a3752e9b70ce62992e268a877744cdd435f5f130869c9a2074b338",
+ "a6213743568e3b3158b9184301f3690847554c68457cb40fc9a4b8cfd8d4a118c301a07737aeda0f929c68913c5f51c80394f53bff1c3e83b2e40ca97eba9e15",
+ "d444bfa2362a96df213d070e33fa841f51334e4e76866b8139e8af3bb3398be2dfaddcbc56b9146de9f68118dc5829e74b0c28d7711907b121f9161cb92b69a9",
+ "142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461",
+}
+
+var hashes2X = []string{
+ "64",
+ "f457",
+ "e8c045",
+ "a74c6d0d",
+ "eb02ae482a",
+ "be65b981275e",
+ "8540ccd083a455",
+ "074a02fa58d7c7c0",
+ "da6da05e10db3022b6",
+ "542a5aae2f28f2c3b68c",
+ "ca3af2afc4afe891da78b1",
+ "e0f66b8dcebf4edc85f12c85",
+ "744224d383733b3fa2c53bfcf5",
+ "b09b653e85b72ef5cdf8fcfa95f3",
+ "dd51877f31f1cf7b9f68bbb09064a3",
+ "f5ebf68e7ebed6ad445ffc0c47e82650",
+ "ebdcfe03bcb7e21a9091202c5938c0a1bb",
+ "860fa5a72ff92efafc48a89df1632a4e2809",
+ "0d6d49daa26ae2818041108df3ce0a4db48c8d",
+ "e5d7e1bc5715f5ae991e4043e39533af5d53e47f",
+ "5232028a43b9d4dfa7f37439b49495926481ab8a29",
+ "c118803c922f9ae2397fb676a2ab7603dd9c29c21fe4",
+ "2af924f48b9bd7076bfd68794bba6402e2a7ae048de3ea",
+ "61255ac38231087c79ea1a0fa14538c26be1c851b6f318c0",
+ "f9712b8e42f0532162822f142cb946c40369f2f0e77b6b186e",
+ "76da0b89558df66f9b1e66a61d1e795b178ce77a359087793ff2",
+ "9036fd1eb32061bdecebc4a32aa524b343b8098a16768ee774d93c",
+ "f4ce5a05934e125d159678bea521f585574bcf9572629f155f63efcc",
+ "5e1c0d9fae56393445d3024d6b82692d1339f7b5936f68b062c691d3bf",
+ "538e35f3e11111d7c4bab69f83b30ade4f67addf1f45cdd2ac74bf299509",
+ "17572c4dcbb17faf8785f3bba9f6903895394352eae79b01ebd758377694cc",
+ "29f6bb55de7f8868e053176c878c9fe6c2055c4c5413b51ab0386c277fdbac75",
+ "bad026c8b2bd3d294907f2280a7145253ec2117d76e3800357be6d431b16366e41",
+ "386b7cb6e0fd4b27783125cbe80065af8eb9981fafc3ed18d8120863d972fa7427d9",
+ "06e8e6e26e756fff0b83b226dce974c21f970e44fb5b3e5bbada6e4b12f81cca666f48",
+ "2f9bd300244f5bc093ba6dcdb4a89fa29da22b1de9d2c9762af919b5fedf6998fbda305b",
+ "cf6bdcc46d788074511f9e8f0a4b86704365b2d3f98340b8db53920c385b959a38c8869ae7",
+ "1171e603e5cdeb4cda8fd7890222dd8390ede87b6f3284cac0f0d832d8250c9200715af7913d",
+ "bda7b2ad5d02bd35ffb009bdd72b7d7bc9c28b3a32f32b0ba31d6cbd3ee87c60b7b98c03404621",
+ "2001455324e748503aa08eff2fb2e52ae0170e81a6e9368ada054a36ca340fb779393fb045ac72b3",
+ "45f0761aefafbf87a68f9f1f801148d9bba52616ad5ee8e8ac9207e9846a782f487d5cca8b20355a18",
+ "3a7e05708be62f087f17b41ac9f20e4ef8115c5ab6d08e84d46af8c273fb46d3ce1aabebae5eea14e018",
+ "ea318da9d042ca337ccdfb2bee3e96ecb8f907876c8d143e8e44569178353c2e593e4a82c265931ba1dd79",
+ "e0f7c08f5bd712f87094b04528fadb283d83c9ceb82a3e39ec31c19a42a1a1c3bee5613b5640abe069b0d690",
+ "d35e63fb1f3f52ab8f7c6cd7c8247e9799042e53922fbaea808ab979fa0c096588cfea3009181d2f93002dfc11",
+ "b8b0ab69e3ae55a8699eb481dd665b6a2424c89bc6b7cca02d15fdf1b9854139cab49d34de498b50b2c7e8b910cf",
+ "fb65e3222a2950eae1701d4cdd4736266f65bf2c0d2e77968996eadb60ef74fb786f6234973a2524bdfe32d100aa0e",
+ "f28b4bb3a2e2c4d5c01a23ff134558559a2d3d704b75402983ee4e0f71d273ae056842c4153b18ee5c47e2bfa54313d4",
+ "7bb78794e58a53c3e4b1aeb161e756af051583d14e0a5a3205e094b7c9a8cf62d098fa9ea1db12f330a51ab9852c17f983",
+ "a879a8ebae4d0987789bcc58ec3448e35ba1fa1ee58c668d8295aba4eaeaf2762b053a677e25404f635a53037996974d418a",
+ "695865b353ec701ecc1cb38f3154489eed0d39829fc192bb68db286d20fa0a64235cde5639137819f7e99f86bd89afcef84a0f",
+ "a6ec25f369f71176952fb9b33305dc768589a6070463ee4c35996e1ced4964a865a5c3dc8f0d809eab71366450de702318e4834d",
+ "604749f7bfadb069a036409ffac5ba291fa05be8cba2f141554132f56d9bcb88d1ce12f2004cd3ade1aa66a26e6ef64e327514096d",
+ "daf9fa7dc2464a899533594e7916fc9bc585bd29dd60c930f3bfa78bc47f6c8439448043a45119fc9228c15bce5fd24f46baf9de736b",
+ "943ea5647a8666763084da6a6f15dcf0e8dc24f27fd0d9194805d25180fe3a6d98f4b2b5e0d6a04e9b41869817030f16ae975dd41fc35c",
+ "af4f73cbfc093760dfeb52d57ef45207bbd1a515f5523404e5d95a73c237d97ae65bd195b472de6d514c2c448b12fafc282166da132258e9",
+ "605f4ed72ed7f5046a342fe4cf6808100d4632e610d59f7ebb016e367d0ff0a95cf45b02c727ba71f147e95212f52046804d376c918cadd260",
+ "3750d8ab0a6b13f78e51d321dfd1aa801680e958de45b7b977d05732ee39f856b27cb2bcce8fbf3db6666d35e21244c2881fdcc27fbfea6b1672",
+ "8f1b929e80ab752b58abe9731b7b34eb61369536995abef1c0980d93903c1880da3637d367456895f0cb4769d6de3a979e38ed6f5f6ac4d48e9b32",
+ "d8469b7aa538b36cdc711a591d60dafecca22bd421973a70e2deef72f69d8014a6f0064eabfbebf5383cbb90f452c6e113d2110e4b1092c54a38b857",
+ "7d1f1ad2029f4880e1898af8289c23bc933a40863cc4ab697fead79c58b6b8e25b68cf5324579b0fe879fe7a12e6d03907f0140dfe7b29d33d6109ecf1",
+ "87a77aca6d551642288a0dff66078225ae39d288801607429d6725ca949eed7a6f199dd8a65523b4ee7cfa4187400e96597bfffc3e38ade0ae0ab88536a9",
+ "e101f43179d8e8546e5ce6a96d7556b7e6b9d4a7d00e7aade5579d085d527ce34a9329551ebcaf6ba946949bbe38e30a62ae344c1950b4bde55306b3bac432",
+ "4324561d76c370ef35ac36a4adf8f3773a50d86504bd284f71f7ce9e2bc4c1f1d34a7fb2d67561d101955d448b67577eb30dfee96a95c7f921ef53e20be8bc44",
+ "78f0ed6e220b3da3cc9381563b2f72c8dc830cb0f39a48c6ae479a6a78dcfa94002631dec467e9e9b47cc8f0887eb680e340aec3ec009d4a33d241533c76c8ca8c",
+ "9f6589c31a472e0a736f4eb22b6c70a9d332cc15304ccb66a6b97cd051b6ed82f8990e1d9bee2e4bb1c3c45e550ae0e7b96e93ae23f2fb8f63b309131e72b36cba6a",
+ "c138077ee4ed3d7ffa85ba851dfdf6e9843fc1dc00889d117237bfaad9aa757192f73556b959f98e6d24886ce48869f2a01a48c371785f12b6484eb2078f08c22066e1",
+ "f83e7c9e0954a500576ea1fc90a3db2cbd7994eaef647dab5b34e88ab9dc0b47addbc807b21c8e6dd3d0bd357f008471d4f3e0abb18450e1d4919e03a34545b9643f870e",
+ "3277a11f2628544fc66f50428f1ad56bcba6ee36ba2ca6ecdf7e255effc0c30235c039d13e01f04cf1efe95b5c2033ab72adda30994b62f2851d17c9920eadca9a251752dc",
+ "c2a834281a06fe7b730d3a03f90761daf02714c066e33fc07e1f59ac801ec2f4433486b5a2da8faa51a0cf3c34e29b2960cd0013378938dbd47c3a3d12d70db01d7d06c3e91e",
+ "47680182924a51cabe142a6175c9253e8ba7ea579ece8d9bcb78b1e9ca00db844fa08abcf41702bd758ee2c608d9612fed50e85854469cb4ef3038acf1e35b6ba4390561d8ae82",
+ "cec45830cd71869e83b109a99a3cd7d935f83a95de7c582f3adbd34e4938fa2f3f922f52f14f169c38cc6618d3f306a8a4d607b345b8a9c48017136fbf825aecf7b620e85f837fae",
+ "46fb53c70ab105079d5d78dc60eaa30d938f26e4d0b9df122e21ec85deda94744c1daf8038b8a6652d1ff3e7e15376f5abd30e564784a999f665078340d66b0e939e0c2ef03f9c08bb",
+ "7b0dcb52791a170cc52f2e8b95d8956f325c3751d3ef3b2b83b41d82d4496b46228a750d02b71a96012e56b0720949ca77dc68be9b1ef1ad6d6a5ceb86bf565cb972279039e209dddcdc",
+ "7153fd43e6b05f5e1a4401e0fef954a737ed142ec2f60bc4daeef9ce73ea1b40a0fcaf1a1e03a3513f930dd5335723632f59f7297fe3a98b68e125eadf478eb045ed9fc4ee566d13f537f5",
+ "c7f569c79c801dab50e9d9ca6542f25774b3841e49c83efe0b89109f569509ce7887bc0d2b57b50320eb81fab9017f16c4c870e59edb6c26620d93748500231d70a36f48a7c60747ca2d5986",
+ "0a81e0c547648595adca65623ce783411aac7f7d30c3ad269efafab288e7186f6895261972f5137877669c550f34f5128850ebb50e1884814ea1055ee29a866afd04b2087abed02d9592573428",
+ "6a7b6769e1f1c95314b0c7fe77013567891bd23416374f23e4f43e27bc4c55cfada13b53b1581948e07fb96a50676baa2756db0988077b0f27d36ac088e0ff0fe72eda1e8eb4b8facff3218d9af0",
+ "a399474595cb1ccab6107f18e80f03b1707745c7bf769fc9f260094dc9f8bc6fe09271cb0b131ebb2acd073de4a6521c8368e664278be86be216d1622393f23435fae4fbc6a2e7c961282a777c2d75",
+ "4f0fc590b2755a515ae6b46e9628092369d9c8e589e3239320639aa8f7aa44f8111c7c4b3fdbe6e55e036fbf5ebc9c0aa87a4e66851c11e86f6cbf0bd9eb1c98a378c7a7d3af900f55ee108b59bc9e5c",
+ "ed96a046f08dd675107331d267379c6fce3c352a9f8d7b243008a74cb4e9410836afaabe871dab6038ca94ce5f6d41fa922ce08aba58169f94cfc86d9f688f396abd24c11a6a9b0830572105a477c33e92",
+ "379955f539abf0eb2972ee99ed9546c4bbee363403991833005dc27904c271ef22a799bc32cb39f08d2e4ba6717d55153feb692d7c5efae70890bf29d96df02333c7b05ccc314e4835b018fec9141a82c745",
+ "e16cc8d41b96547ede0d0cf4d908c5fa393399daa4a9696e76a4c1f6a2a9fef70f17fb53551a8145ed88f18db8fe780a079d94732437023f7c1d1849ef69ad536a76204239e8ba5d97e507c36c7d042f87fe0e",
+ "a81de50750ece3f84536728f227208bf01ec5b7721579d007de72c88ee20663318332efe5bc7c09ad1fa8342be51f0609046ccf760a7957a7d8dc88941adb93666a4521ebe76618e5ddc2dd3261493d400b50073",
+ "b72c5fb7c7f60d243928fa41a2d711157b96aef290185c64b4de3dcfa3d644da67a8f37c2ac55caad79ec695a473e8b481f658c497edb8a191526592b11a412282d2a4010c90ef4647bd6ce745ebc9244a71d4876b",
+ "9550703877079c90e200e830f277b605624954c549e729c359ee01ee2b07741ecc4255cb37f96682dafcdbaade1063e2c5ccbd1918fb669926a67744101fb6de3ac016be4c74165a1e5a696b704ba2ebf4a953d44b95",
+ "a17eb44d4de502dc04a80d5a5e9507d17f27c96467f24c79b06bc98a4c410741d4ac2db98ec02c2a976d788531f1a4451b6c6204cef6dae1b6ebbcd0bde23e6fffb02754043c8fd3c783d90a670b16879ce68b5554fe1c",
+ "41d3ea1eaba5be4a206732dbb5b70b79b66a6e5908795ad4fb7cf9e67efb13f06fef8f90acb080ce082aadec6a1b543af759ab63fa6f1d3941186482b0c2b312f1151ea8386253a13ed3708093279b8eb04185636488b226",
+ "5e7cdd8373dc42a243c96013cd29df9283b5f28bb50453a903c85e2ce57f35861bf93f03029072b70dac0804e7d51fd0c578c8d9fa619f1e9ce3d8044f65d55634dba611280c1d5cfb59c836a595c803124f696b07ddfac718",
+ "26a14c4aa168907cb5de0d12a82e1373a128fb21f2ed11feba108b1bebce934ad63ed89f4ed7ea5e0bc8846e4fc10142f82de0bebd39d68f7874f615c3a9c896bab34190e85df05aaa316e14820b5e478d838fa89dfc94a7fc1e",
+ "0211dfc3c35881adc170e4ba6daab1b702dff88933db9a6829a76b8f4a7c2a6d658117132a974f0a0b3a38ceea1efc2488da21905345909e1d859921dc2b5054f09bce8eeb91fa2fc6d048ce00b9cd655e6aafbdaa3a2f19270a16",
+ "ddf015b01b68c4f5f72c3145d54049867d99ee6bef24282abf0eecdb506e295bacf8f23ffa65a4cd891f76a046b9dd82cae43a8d01e18a8dff3b50aeb92672be69d7c087ec1fa2d3b2a39196ea5b49b7baede37a586fea71aded587f",
+ "6ee721f71ca4dd5c9ce7873c5c04c6ce76a2c824b984251c15535afc96adc9a4d48ca314bfeb6b8ee65092f14cf2a7ca9614e1dcf24c2a7f0f0c11207d3d8aed4af92873b56e8b9ba2fbd659c3f4ca90fa24f113f74a37181bf0fdf758",
+ "689bd150e65ac123612524f720f54def78c095eaab8a87b8bcc72b443408e3227f5c8e2bd5af9bcac684d497bc3e41b7a022c28fb5458b95e8dfa2e8caccde0492936ff1902476bb7b4ef2125b19aca2cd3384d922d9f36dddbcd96ae0d6",
+ "3a3c0ef066fa4390ec76ad6be1dc9c31ddf45fef43fbfa1f49b439caa2eb9f3042253a9853e96a9cf86b4f873785a5d2c5d3b05f6501bc876e09031188e05f48937bf3c9b667d14800db62437590b84ce96aa70bb5141ee2ea41b55a6fd944",
+ "741ce384e5e0edaebb136701ce38b3d33215415197758ae81235307a4115777d4dab23891db530c6d28f63a957428391421f742789a0e04c99c828373d9903b64dd57f26b3a38b67df829ae243feef731ead0abfca049924667fdec49d40f665",
+ "a513f450d66cd5a48a115aee862c65b26e836f35a5eb6894a80519e2cd96cc4cad8ed7eb922b4fc9bbc55c973089d627b1da9c3a95f6c019ef1d47143cc545b15e4244424be28199c51a5efc7234dcd94e72d229897c392af85f523c2633427825",
+ "71f1554d2d49bb7bd9e62e71fa049fb54a2c097032f61ebda669b3e1d4593962e47fc62a0ab5d85706aebd6a2f9a192c88aa1ee2f6a46710cf4af6d3c25b7e68ad5c3db23ac009c8f13625ff85dc8e50a9a1b2682d3329330b973ec8cbb7bb73b2bd",
+ "167cc1067bc08a8d2c1a0c10041ebe1fc327b37043f6bd8f1c63569e9d36ded58519e66b162f34b6d8f1107ef1e3de199d97b36b44141a1fc4f49b883f40507ff11f909a017869dc8a2357fc7336ae68703d25f75710b0ff5f9765321c0fa53a51675c",
+ "cb859b35dc70e264efaad2a809fea1e71cd4a3f924be3b5a13f8687a1166b538c40b2ad51d5c3e47b0de482497382673140f547068ff0b3b0fb7501209e1bf36082509ae85f60bb98fd02ac50d883a1a8daa704952d83c1f6da60c9624bc7c99912930bf",
+ "afb1f0c6b7125b04fa2578dd40f60cb411b35ebc7026c702e25b3f0ae3d4695d44cfdf37cb755691dd9c365edadf21ee44245620e6a24d4c2497135b37cd7ac67e3bd0aaee9f63f107746f9b88859ea902bc7d6895406aa2161f480cad56327d0a5bba2836",
+ "13e9c0522587460d90c7cb354604de8f1bf850e75b4b176bda92862d35ec810861f7d5e7ff6ba9302f2c2c8642ff8b7776a2f53665790f570fcef3cac069a90d50db42227331c4affb33d6c040d75b9aeafc9086eb83ced38bb02c759e95ba08c92b17031288",
+ "0549812d62d3ed497307673a4806a21060987a4dbbf43d352b9b170a29240954cf04bc3e1e250476e6800b79e843a8bd8253b7d743de01ab336e978d4bea384eaff700ce020691647411b10a60acacb6f8837fb08ad666b8dcc9eaa87ccb42aef6914a3f3bc30a",
+ "3a263efbe1f2d463f20526e1d0fd735035fd3f808925f058b32c4d8788aeeab9b8ce233b3c34894731cd73361f465bd350395aebcabd2fb63010298ca025d849c1fa3cd573309b74d7f824bbfe383f09db24bcc565f636b877333206a6ad70815c3bef5574c5fc1c",
+ "3c6a7d8a84ef7e3eaa812fc1eb8e85105467230d2c9e4562edbfd808f4d1ac15d16b786cc6a02959c2bc17149c2ce74c6f85ee5ef22a8a96b9be1f197cffd214c1ab02a06a9227f37cd432579f8c28ff2b5ac91cca8ffe6240932739d56788c354e92c591e1dd76499",
+ "b571859294b02af17541a0b5e899a5f67d6f5e36d38255bc417486e69240db56b09cf2607fbf4f95d085a779358a8a8b41f36503438c1860c8f361ce0f2783a08b21bd7232b50ca6d35428335272a5c05b436b2631d8d5c84d60e8040083768ce56a250727fb0579dd5c",
+ "98ee1b7269d2a0dd490ca38d447279870ea55326571a1b430adbb2cf65c492131136f504145df3ab113a13abfb72c33663266b8bc9c458db4bf5d7ef03e1d3b8a99d5de0c024be8fabc8dc4f5dac82a0342d8ed65c329e7018d6997e69e29a01350516c86beaf153da65ac",
+ "41c5c95f088df320d35269e5bf86d10248f17aec6776f0fe653f1c356aae409788c938befeb67c86d1c8870e8099ca0ce61a80fbb5a6654c44529368f70fc9b9c2f912f5092047d0ffc339577d24142300e34948e086f62e23ecaca410d24f8a36b5c8c5a80e0926bc8aa16a",
+ "9f93c41f533b2a82a4df893c78faaaa793c1506974ba2a604cd33101713ca4adfd30819ffd8403402b8d40aff78106f3357f3e2c24312c0d3603a17184d7b999fc9908d14d50192aebabd90d05073da7af4be37dd3d81c90acc80e8333df546f17ab6874f1ec204392d1c0571e",
+ "3da5207245ac270a915fc91cdb314e5a2577c4f8e269c4e701f0d7493ba716de79935918b917a2bd5db98050dbd1eb3894b65fac5abf13e075abebc011e651c03cafb6127147771a5c8418223e1548137a89206635c26ca9c235ccc108dc25cf846e4732444bd0c2782b197b262b",
+ "96011af3965bb941dc8f749932ea484eccb9ba94e34b39f24c1e80410f96ce1d4f6e0aa5be606def4f54301e930493d4b55d484d93ab9dd4dc2c9cfb79345363af31ad42f4bd1aa6c77b8afc9f0d551bef7570b13b927afe3e7ac4de7603a0876d5edb1ad9be05e9ee8b53941e8f59",
+ "51dbbf2a7ca224e524e3454fe82ddc901fafd2120fa8603bc343f129484e9600f688586e040566de0351d1693829045232d04ff31aa6b80125c763faab2a9b233313d931903dcfaba490538b06e4688a35886dc24cdd32a13875e6acf45454a8eb8a315ab95e608ad8b6a49aef0e299a",
+ "5a6a422529e22104681e8b18d64bc0463a45df19ae2633751c7aae412c250f8fb2cd5e1270d3d0cf009c8aa69688ccd4e2b6536f5747a5bc479b20c135bf4e89d33a26118705a614c6be7ecfe766932471ad4ba01c4f045b1abb5070f90ec78439a27a1788db9327d1c32f939e5fb1d5ba",
+ "5d26c983642093cb12ff0afabd87b7c56e211d01844ad6da3f623b9f20a0c968034299f2a65e6673530c5980a532beb831c7d0697d12760445986681076dfb6fae5f3a4d8f17a0db5008ce8619f566d2cfe4cf2a6d6f9c3664e3a48564a351c0b3c945c5ee24587521e4112c57e318be1b6a",
+ "52641dbc6e36be4d905d8d60311e303e8e859cc47901ce30d6f67f152343e3c4030e3a33463793c19effd81fb7c4d631a9479a7505a983a052b1e948ce093b30efa595fab3a00f4cef9a2f664ceeb07ec61719212d58966bca9f00a7d7a8cb4024cf6476bab7fbccee5fd4e7c3f5e2b2975aa2",
+ "a34ce135b37bf3db1c4aaa4878b4499bd2ee17b85578fcaf605d41e1826b45fdaa1b083d8235dc642787f11469a5493e36806504fe2a2063905e821475e2d5ee217057950370492f5024995e77b82aa51b4f5bd8ea24dc71e0a8a640b0592c0d80c24a726169cf0a10b40944747113d03b52708c",
+ "46b3cdf4946e15a5334fc3244d6680f5fc132afa67bf43bfade23d0c9e0ec64e7dab76faaeca1870c05f96b7d019411d8b0873d9fed04fa5057c039d5949a4d592827f619471359d6171691cfa8a5d7cb07ef2804f6ccad4821c56d4988bea7765f660f09ef87405f0a80bcf8559efa111f2a0b419",
+ "8b9fc21691477f11252fca050b121c5334eb4280aa11659e267297de1fec2b2294c7ccee9b59a149b9930b08bd320d3943130930a7d931b71d2f10234f4480c67f1de883d9894ada5ed5071660e221d78ae402f1f05af47761e13fec979f2671e3c63fb0ae7aa1327cf9b8313adab90794a52686bbc4",
+ "cd6598924ce847de7ff45b20ac940aa6292a8a99b56a74eddc24f2cfb45797188614a21d4e8867e23ff75afd7cd324248d58fcf1ddc73fbd115dfa8c09e62022fab540a59f87c989c12a86ded05130939f00cd2f3b512963dfe0289f0e54acad881c1027d2a0292138fdee902d67d9669c0ca1034a9456",
+ "594e1cd7337248704e691854af0fdb021067ddf7832b049ba7b684438c32b029eded2df2c89a6ff5f2f2c311522ae2dc6db5a815afc60637b15ec24ef9541f1550409db2a006da3affffe548a1eaee7bd114e9b805d0756c8e90c4dc33cb05226bc2b393b18d953f8730d4c7ae693159cdba758ad28964e2",
+ "1f0d292453f04406ada8be4c161b82e3cdd69099a8637659e0ee40b8f6da46005cfc6085db9804852decfbe9f7b4dda019a7112612895a144ed430a960c8b2f5458d3d56b7f427cee6358915aee7146278aed2a0296cdd929e4d21ef95a3adf8b7a6beba673cdccdbdcfb2474711732d972ad054b2dc64f38d",
+ "b65a72d4e1f9f9f75911cc46ad0806b9b18c87d105332a3fe183f45f063a746c892dc6c4b9181b1485b3e3a2cc3b453eba2d4c39d6905a774ed3fb755468beb190925ecd8e57ecb0d985125741650c6b6a1b2a3a50e93e3892c21d47ed5884eed83aa94e1602288f2f49fe286624de9d01fcb54433a0dc4ad70b",
+ "705ce0ffa469250782aff725248fc88fe98eb76659e8407edc1c4842c9867d61fe64fb86f74e980598b92bc213d06f337bd5654fc28643c7ba769a4c31563427543c00808b627a19c90d86c322f33566ce020121cc322229c3337943d46f68ef939d613dcef0077269f88151d6398b6b009abb763410b154ad76a3",
+ "7fa881ce87498440ab6af13854f0d851a7e0404de33896999a9b3292a5d2f5b3ad033530c558168fe5d2fdb9b89a2354c46cf32a0e612afc6c6485d789511bfef26800c74bf1a4cfbe30bda310d5f6029c3dccdedb6149e4971274e276dccfabd63bc4b9955e8303feb57f8a688db55ecb4b33d1f9fe1b3a8ba7ac32",
+ "23a98f71c01c0408ae16843dc03be7db0aeaf055f951709d4e0dfdf64fffbffaf900ee592ee10929648e56f6c1e9f5be5793f7df66453eb56502c7c56c0f0c88da77abc8fa371e434104627ef7c663c49f40998dbad63fa6c7aa4fac17ae138d8bbe081f9bd168cd33c1fbc92fa35ed687679f48a64b87db1fe5bae675",
+ "7b8970b6a33237e5a7bcb39272703edb92285c55842b30b9a48834b1b507cc02a6764739f2f7ee6ae02a7b715a1c455e59e8c77a1ae98abb10161853f1234d20da99016588cd8602d6b7ec7e177d4011edfa61e6b3766a3c6f8d6e9eac893c568903eb6e6aba9c4725774f6b4343b7acaa6c031593a36eef6c72806ff309",
+ "f7f4d328ba108b7b1de4443e889a985ed52f485f3ca4e0c246aa5526590cbed344e9f4fe53e4eea0e761c82324649206ca8c2b45152157d4115e68c818644b03b65bb47ad79f94d37cb03c1d953b74c2b8adfa0e1c418bda9c518ddcd7050e0f149044740a2b16479413b63fc13c36144f80c73687513dca761ba8642a8ae0",
+ "2d7dc80c19a1d12d5fe3963569547a5d1d3e821e6f06c5d5e2c09401f946c9f7e13cd019f2f9a878b62dd850453b6294b99ccaa068e542993524b0f63832d48e865be31e8ec1ee103c718340c904b32efb69170b67f038d50a3252794b1b4076c0620621ab3d91215d55ffea99f23d54e161a90d8d4902fda5931d9f6a27146a",
+ "77dff4c7ad30c954338c4b23639dae4b275086cbe654d401a2343528065e4c9f1f2eca22aa025d49ca823e76fdbb35df78b1e5075ff2c82b680bca385c6d57f7ea7d1030bb392527b25dd73e9eeff97bea397cf3b9dda0c817a9c870ed12c006cc054968c64000e0da874e9b7d7d621b0679866912243ea096c7b38a1344e98f74",
+ "83bed0d556798f2b419f7056e6d3ffada06e939b95a688d0ec8c6ac5ea45ab73a4cf01043e0a170766e21395f27ab4b78c435f5f0dfe6e93ab80df38610e41158429ddf20296f53a06a017723359fe22dc08b5da33f0800a4fe50118e8d7eab2f83a85cd764bf8a166903bd0e9dcfeeceba44ff4ca4439846458d31ea2bb564645d1",
+ "ea12cf5a113543e39504123036f15a5bafa9c555562469f99cd29996a4dfaaab2a34b00557ccf15f37fc0cc1b3be427e725f2cd952e50af7970dda9200cd5ce252b1f29c40067fea3027ed686190803b59d834179d1b8f5b55abe55ad174b2a1188f7753ec0ae2fc01316e7d498b68ee3598a0e9baaaa664a60f7fb4f90edbed494ad7",
+ "55266358332d8d9e68bd13432088beadf95833aab67a0eb3b10650414255f299e2670c3e1a5b2976159a46c72a7ce57d59b7be14c15798e09ed50fa312a431b0264d7a1396aa6168bde897e208ece53d2cfc83786113b1e6eac5e9bb98984abb6c8d64eebb991903254abc650c999bb9958a5d7937434b869bc940e21b9dc1cc8982f2ba",
+ "4d6104ded730aefe02873f4c741232c8234a6d66d85393aff57fbf56ba6347666988dfc4d58f3cc895a0da598822edeee4533d24ec0ee292fd5e1ad04898ffbc1ff4bef14dec220babcb0f28fffe32a6e2c28aaaac16442bf4feb02917d18bb3a415d84fa9358d5a9852688d846c92271911f934181c30f82434d915f93f155a1ffbf0b125",
+ "eb5f579a4c476af554aac11e5719d378549497e613b35a929d6f36bb8831d7a466aa76de9be24ebb55543f1c13924f64cfd648a5b3fa90387315c16174dbf1e9a183c196d9bb8f84af65f1f8212429aadc11ef2426d07d4716062b85c8d5d2dff8e21b9e62b7fa7dbd57d72633054b464fb28583a56ca13ccc5ddc74dae942492f31731e7046",
+ "ebddec3dcaf18063e45a76ebeac39af85a1adc2818881ccce48c106288f5988365cca2b4b1d7f037322da46840f42bebdcbc7193838d426e101087d8cea03aaff743d573eb4f4e9a71a2c884390769a6503874125d194bee8d46a3a0d5e4fcf28ff8465887d8e9df771d70157e75df3642b331d2778ceb32ceba868640171ab7a5d22eede1ee44",
+ "26d87ec70b57691e3bb359633d3ddba17f029d62cdfe977f5fd42274d79b444a32494d1c01e9f72d03cce78c806df96e93ea78da3a054209924ed765edc4d570f66168dc25ee3114e4017e387440349c8f0a94804761c3055f88e4fda2a49b860b1486a9609095f6250f268b6a4d1aecc03a505632ebf0b9dc22d0755a736faf7ad7000858b5864b",
+ "3880f5cc2d08fa70ef44b1f263fcf534d062a298c1bd5ee2eee8c3265806c4ce50b004f3a1fc1fa5b024aaac7f528c023c8181f67c6e1c357425dc4d573bd46b93a542afa3a19bdb140a2ce666e1a01f5c4d2dcd681fa9f5839b797813c394738d5ee4971386c12c7c117d17c7bec324b760aa30cda9ab2aa850284ba6fa97946f710f02449d1883c6",
+ "3317d2f452105dd3f4a96f9257af8285a80be58066b50f6f54bd633749b49f6ab9d57d45652d2ae852a2f6940cd5ec3159dd7f333358b12f502325df38843508faf7e246352d201280babd90b14fbf7722641c3601d0e458474439973c611bb5502fd0eb3078f87124ca7e1a016fcb6cfeff65f6a565985aca7122cfa8c5a11da0cb47797c5132333179",
+ "f2c5c955d0224e784a46b9125f8fef8a5e1271e145eb08bbbd07ca8e1cfc848cef14fa3b36221ac62006403dbb7f7d77958ccc54a8566c837858b809f3e310ace8ca682515bc655d2a397cab238a663b464d511f02dc5d033dad4cb5e0e519e94a54b62a3896e460ec70e5716b5921bf8396aa86a60123e6287e34570bb01bdc602e113670bf498af2ff10",
+ "180e275205691a83630cf4b0c7b80e6df8fad6ef1c23ba8013d2f09aef7abade1827f23af230de90676240b4b3b0673f8afdea0327330055041741f65560d90348de696d34ca80dfe8afae582fe4879d4594b80e9408fb53e800e01ca58552b905c365e7f1416e51c080f517d6bbd30e64ae1535d59decdc76c6624d737868f49f2f719da39ba1344d59eab9",
+ "c517a84e4631a7f65ace170d1e5c2fdb259841535d88da323e68c0883e6af7b041cfe05908815a5a9d1b14fa712c2c16fadcf1ca54d3aa954d411240df331b2aebdfb65aced84d0b8aace56ec0aa7c13ec7d75ca883b6bcf6db74c9e98463c484a8262684f29910373430651f90ecffe18b072170e61ee58de20e2a6ff67b3ab00fccbb80af943f20b56b98107",
+ "d1a56a5ee990e02b84b5862fde62f69ec07567be2d7ccb769a461c4989d11fdda6c945d942fb8b2da795ed97e43a5b7dbdde7f8fd2ff7154544336d5c50fb7380341e660d4898c7fbc39b2b782f28defac6873523c7c1de8e52c65e4395c686ba483c35a220b0416d46357a063fa4c33fa9c52d5c207a1304ae141c791e62ba6a7374ed922b8dd94079b72b69302",
+ "4720b88d6bfb1ab43958e26827730d852d9ec30173ebd0fe0d273edcece2e788558984cd9306fe5978086a5cb6d37975755d2a3daeb16f99a8a11544b8247a8b7ed5587afc5bea1daf85dcea5703c5905cf56ae7cc76408ccabb8fcc25cacc5ff456db3f62fa559c45b9c71505eb5073df1f10fc4c9060843f0cd68bbb4e8edfb48d0fd81d9c21e53b28a2aae4f7ba",
+ "f4639b511db9e092823d47d2947efacbaae0e5b912dec3b284d2350b9262f3a51796a0cd9f8bc5a65879d6578ec24a060e293100c2e12ad82d5b2a0e9d22965858030e7cdf2ab3562bfa8ac084c6e8237aa22f54b94c4e92d69f22169ced6c85a293f5e16bfc326153bf629cdd6393675c6627cd949cd367eef02e0f54779f4d5210197698e4754a5fe490a3a7521c1c",
+ "3d9e7a860a718565e3670c29079ce80e381969fea91017cfd5952e0d8a4a79bb08e2cd1e26161f30ee03a24891d1bfa8c212861b51618d07429fb48000ff87ef09c6fca526567777e9c076d58a642d5c521b1caa5fb0fb3a4b8982dc14a444732b72b239b8f01fc8ba8ee86b3013b5d3e98a92b2aeaecd4879fca5d5e9e0bd880dbfffa6f96f94f3998812aac6a714f331",
+ "4d9bf551d7fd531e7482e2ec875c0651b0bcc6caa738f7497befd11e67ae0e036c9d7ae4301cc3c7906f0d0e1ed4738753f414f9b3cd9b8a71176e325c4c74ce020680ecbfb146889597f5b40487e93f974cd866817fb9fb24c7c7c16177e6e120bfe349e83aa82ba40e59e917565788658a2b254f25cf99bc65070b3794cea2259eb10e42bb54852cba3110baa773dcd70c",
+ "b91f65ab5bc059bfa5b43b6ebae243b1c46826f3da061338b5af02b2da76bb5ebad2b426de3c3134a633499c7c36a120369727cb48a0c6cbab0acecdda137057159aa117a5d687c4286868f561a272e0c18966b2fec3e55d75abea818ce2d339e26adc005c2658493fe06271ad0cc33fcb25065e6a2a286af45a518aee5e2532f81ec9256f93ff2d0d41c9b9a2efdb1a2af899",
+ "736f6e387acb9acbee026a6080f8a9eb8dbb5d7c54ac7053ce75dd184b2cb7b942e22a3497419ddb3a04cf9e4eb9340a1a6f9474c06ee1dcfc8513979fee1fc4768087617fd424f4d65f54782c787a1d2de6efc81534343e855f20b3f3589027a5436201eee747d45b9b8375e4294d72ab6a52e04dfbb2914db92ee58f134b026527ed52d4f794459e02a43a17b0d51ea69bd7f3",
+ "9242d3eb31d26d923b99d66954cfade94f25a18912e6356810b63b971ae74bb53bc58b3c01424208ea1e0b1499936daea27e63d904f9ed65fdf69de40780a3027b2e89d94bdf214f585472613ce328f628f4f0d56217dfb53db5f7a07f54c8d71db16e27de7cdb8d23988837b49b65c12f1771d979e8b192c9f4a16b8d9fba917bcf74ce5a82aac2075608ba6c2d485fa59864b9de",
+ "5da68704f4b592d41f08aca08f62d85e2e2466e5f3be010315d11d113db674c4b98764a509a2f5aacc7ae72c9deff2bcc42810b47f64d429b35745b9efff0b18c58653461e968aaa3c2c7fc455bc5771a8f10cd184be831040df767201ab8d32cb9a58c89afbebecb524502c9b940c1b838f8361bbcde90d272715017f67609ea39b20fac985332d82daaa023999e3f8bfa5f3758bb8",
+ "71ea2af9c8ac2e5ae44a176662882e01027ca3cdb41ec2c6785606a07d7231cd4a2bded7155c2feef3d44d8fd42afa73265cef826f6e03aa761c5c51d5b1f129ddc27503ff50d9c2d748322df4b13dd5cdc7d46381528ab22b79b0049011e4d2e57fe2735e0d58d8d56e92c75dbeac8c76c4239d7f3f24fb56697593b3e4afa6671d5bbc96c079a1c154fe20212ade67b05d49ceaa7a84",
+ "1d133170582fa4bff59a21953ebbc01bc202d43cd79c083d1f5c02fa15a43a0f519e36acb710bdabac880f04bc003800641c2487930de9c03c0e0deb347fa815efca0a38c6c5de694db698743bc955581f6a945deec4ae988ef7cdf40498b77796ddea3fae0ea844891ab751c7ee20917c5a4af53cd4ebd82170078f41ada2795e6eea17593fa90cbf5290a1095e299fc7f507f360f187cd",
+ "5ec4ac45d48fc15c72471d795066bdf8e99a483d5fdd599511b9cdc408de7c0616491b73924d0266da34a495331a935c4b8884f57d7ad8cce4cbe586875aa52482215ed39d7626cce55d50349c7767981c8bd6890f132a196184247343566fc972b86fe3c5369d6a6519e9f07942f0522b77ad01c751dcf7defe31e471a0ec00963765dd8518144a3b8c3c978ad108056516a25dbe3092e73c",
+ "0d5e74b78290c689f2b3cfea45fc9b6a84c822639cd438a7f05c07c374adced42cdc12d2a9233a4ffe80307efc1ac13cb04300e165f8d90dd01c0ea955e7657332c6e86ad6b43e78ba4c13c675aed83192d8427866fb6484e6a3071b2369a46fba9005f31232da7ffec7952f831aaaddf63e225263531c2cf387f8cc14fa856c8795137142c3a52ffa69b8e30ebc88ce3bbc227597bcc8dddd89",
+ "a0fe36f983259921dc2fa7d89002b3066241d63bfc2448caf7e10522a35562be0bfedc3dce49cfce2e614a04d4c64cfc0ab898873a7fc26928dc1927c009d12f6f9b7a278205d3d0057604f4ac746f8b9287c3bc6b929832bf253b6586192ac43fdd29ba585dbd9059aab9c6ff6000a7867c67fec1457b733f6b620881166b8fed92bc8d84f0426002e7be7fcd6ee0abf3755e2babfe5636ca0b37",
+ "1d29b6d8eca793bb801becf90b7d7de215b17618ec32340da4bac707cdbb58b951d5036ec02e105d83b5960e2a72002d19b7fa8e1128cc7c5049ed1f76b82a59eac6ed09e56eb73d9ade38a6739f0e07155afa6ec0d9f5cf13c4b30f5f9a465b162a9c3ba04b5a0b3363c2a63f13f2a3b57c590ec6aa7f64f4dcf7f1582d0ca157eb3b3e53b20e306b1f24e9bda87397d413f01b453ceffeca1fb1e7",
+ "6a2860c110cd0fc5a19bcaafcd30762ee10242d34739638e716bd89fd537ea4dc630e6f85d1bd88a25ad3892ca554c232c9830bd56980c9f08d378d28f7fa6fa7df4fcbf6ad98b1adfff3ec1f63310e50f920c99a5200b8e64c2c2ca249399a149942261f737d5d72da949e914c024d57c4b639cb89990fed2b38a37e5bcd24d17ca12dfcd36ce04691fd03c32f6ed5de2a2191ed7c826375ba81f78d0",
+ "7132aa291ddc9210c60dbe7eb3c19f9053f2dd74742cf57fdc5df98312adbf4710a73245de4a0c3b24e21ab8b466a77ae29d15500d5142555ef3088cbccbe685ed9119a10755148f0b9f0dbcf02b2b9bcadc8517c88346ea4e78285e9cbab122f824cc18faf53b742a87c008bb6aa47eed8e1c8709b8c2b9adb4cc4f07fb423e5830a8e503ab4f7945a2a02ab0a019b65d4fd71dc364d07bdc6e637990e3",
+ "3e664da330f2c6007bff0d5101d88288aaacd3c07913c09e871cce16e55a39fde1ce4db6b8379977c46cce08983ca686778afe0a77a41baf447854b9aa286c398c2b83c95a127b053101b6799c1638e5efd67273b2618df6ec0b96d8d040e8c1ee01a99b9b5c8fe63fea2f749e6c90d31f6fae4e1469ac09884c4fe1a8539acb313f42c941224a0e79c059e18affc2bcb6724975c436f7bf949ebdd8aef51c",
+ "7a6ea63a271eb49470f5ce77519ed61ae9b2f1be07a96855726bc3df1d0723af3a703fdfc2e739c9d31d25814daf661a23558b50982e66ee37ad880f5c8f11c8130fac8a5d0250583700d5a324894fae6d61993f6bf9327214f8674649f355b23fd634940b2c467973a839e659169c773119919f5b81ee171edb2e5f6940d7551f9e5a70625d9ea88711ad0ed8ab2da720ad358bef954456cb2d5636425717c2",
+ "c5106bbda114168c449172e49590c7eeb827fa4e1a2a7a87a3c1f721a9047d0c0a50fbf244731be1b7eb1a2ef30f5ae846a9f38f0df44f32af61b68dbdcd0226e741dfb6ef81a2503691af5e4b3171f48c59ba4ef91eba344b5b697f261df7bbbb734ca6e6daebaa4a179feb17002823281b8534d55a6531c59305f6e3fd3fa63b747bcf0deb654c392a02fe687a269effb1238f38bcaea6b208b221c45fe7fbe7",
+ "597716a5ebeebc4bf524c15518816f0b5dcda39cc833c3d66b6368ce39f3fd02ceba8d12072bfe6137c68d3acd50c849873150928b320b4fbc31c1456679ea1d0acaeeabf666d1f1bad3e6b9312c5cbdecf9b799d3e30b0316bed5f41245107b693366accc8b2bcef2a6be54209ffabc0bb6f93377abdcd57d1b25a89e046f16d8fd00f99d1c0cd247aafa72234386ae484510c084ee609f08aad32a005a0a5710cb",
+ "0771ffe789f4135704b6970b617bae41666bc9a6939d47bd04282e140d5a861c44cf05e0aa57190f5b02e298f1431265a365d29e3127d6fccd86ec0df600e26bcdda2d8f487d2e4b38fbb20f1667591f9b5730930788f2691b9ee1564829d1ada15fffc53e785e0c5e5dd11705a5a71e390ca66f4a592785be188fefe89b4bd085b2024b22a210cb7f4a71c2ad215f082ec63746c7367c22aedb5601f513d9f1ffc1f3",
+ "be6556c94313739c115895a7bad2b620c0708e24f0390daa55521c31d2c6782acf41156271238885c367a57c72b4fe999c160e804ad58d8e565edbce14a2dd90e443eb80626b3eab9d7ab75d6f8a062d7ca89b7af8eb292c98eaf87ad1dfd0db103d1bb6188bd7e7a63502153cf3ce23d43b60c5782602bac8ad92fb2324f5a79453898c5de18415639ecc5c7974d3077f76fc1df5b956723bb19a624d7ea3ec13ba3d86",
+ "4bc33729f14cd2f1dc2ff459abee8f6860dda1062845e4adab78b53c835d106bdfa35dd9e77219eaef403d4e80488ca6bd1c93dd76ef9d543fbb7c8904dccc5f71509a6214f73d0f4e467c3e038ea639b29e7fc442ee29f57117740576188ada15a739827c647a46b0271817ab235c023c30c90f2115e5c90cd8501e7b286962fc66ffc3fe7e8978746168314908a41998bd83a1eeffda9d714b864f4d490fdeb9c7a6edfa",
+ "ab12faea205b3d3a803cf6cb32b9698c32301a1e7f7c6c23a20174c95e98b7c3cfe93fffb3c970face8f5751312a261741141b948d777b8a2ea286fe69fc8ac84d34116a4674bb09a1a0b6af90a748e511749de4697908f4acb22be08e96ebc58ab1690acf73914286c198a2b57f1dd70ea8a52325d3045b8bdfe9a09792521526b7564a2a5fcd01e291f1f8894017ce7d3e8a5dba15332fb410fcfc8d62195a48a9e7c86fc4",
+ "7d421e59a567af70594757a49809a9c22e07fe14061090b9a041875bb77933deae36c823a9b47044fa0599187c75426b6b5ed94982ab1af7882d9e952eca399ee80a8903c4bc8ebe7a0fb035b6b26a2a013536e57fa9c94b16f8c2753c9dd79fb568f638966b06da81ce87cd77ac0793b7a36c45b8687c995bf4414d28289dbee977e77bf05d931b4feaa359a397ca41be529910077c8d498e0e8fb06e8e660cc6ebf07b77a02f",
+ "0c18ab727725d62fd3a2714b7185c09faca130438eff1675b38beca7f93a6962d7b98cb300ea33067a2035cdd694348784aa2eda2f16c731eca119a050d3b3ce7d5c0fd6c234354a1da98c0642451922f670984d035f8c6f35031d6188bbeb31a95e99e21b26f6eb5e2af3c7f8eea426357b3b5f83e0029f4c4732bca366c9aa625748297f039327c276cd8d9c9bf692a47af098aa50ca97b99961bef8bc2a7a802e0b8cfdb84319",
+ "92d5909d18a8b2b9971cd1627b461e98a74ba377186a6a9df5bd133635250b300abccb2254cacb775df6d99f7c7d0952653c28e6909b9f9a45adce691f7adc1afffcd9b06e49f775364cc2c62825b9c1a86089080e26b57e732aac98d80d009bfe50df01b95205aa07ed8ec5c873da3b92d00d53af825aa64b3c634c5ece40bff152c331222d3453fd92e0ca17cef19ecb96a6eed4961b627aca48b12fecd091754f770d52ba861546",
+ "802f22e4a388e874927fef24c797408254e03910bab5bf372320207f8067f2b1ea543917d4a27df89f5bf936ba12e04302bde23119533d0976beca9e20cc16b4dbf17a2ddc44b66aba76c61ad59d5e90de02a88327ead0a8b75463a1a68e307a6e2e53ecc1986274b9ee80bc9f3140671d5285bc5fb57b281042a8978a1175900c6073fd7bd740122956602c1aa773dd2896674d0a6beab24454b107f7c847acb31a0d332b4dfc5e3f2f",
+ "3844fe65db11c92fb90bf15e2e0cd216b5b5be91604baf3b84a0ca480e41ecfaca3709b32f8c6e8761406a635b88eec91e075c48799a16ca08f295d9766d74475c47f3f2a274eae8a6ee1d191a7f37ee413a4bf42cad52acd5564a651715ae42ac2cddd52f819c692ecdef52ecb763270322cdca7bd5aef71428fa73e844568b96b43c89bf1ed42a0abf209ffad0eeec286c6f141e8af073ba4adfbbdeda253752ae36c9957dfc905b4c49",
+ "329377f7bf3c8d74991a7d61b0cf39baff5d485d79751b0d5ad017d23bec570fb19810105bab79ab5acb102ab972165224d4ec888ec7de5148077fa9c1bb6820e0d91ae4e2591a21fec2f820606ce4bafc1e377f8dc3a5bd1a9e2772a57abccd0b757164d768872c91d02789545ab5b203f688d71dd08522a3fd2f5bcd7df507aebf1ca27ddff0a82afb7aa9c180008f49d1325adf97d047e77238fc75f56356de4e87d8c961575c9f6362c9",
+ "f7f269929b0d71ea8eef7120e55ccba691c582dd534692abef35c0fe9dec7dae973cd9702e5ad420d278fe0e653fdcb22fdcb63148109ec7e94f2d0750b28157dd1764376ae10fdb0a4aef3b304bd82793e0595f941226a2d72abbc929f53134dc495b0d65ced409914f94c2523f3dfbbdeeac84ae247ab5d1b9ea33dce1a808885a55be1f3683b46f4be73d9b62eec2585f690056858dfc427aabf591cd276724885bcd4c00b93bb51fb7484d",
+ "ac022309aa2c4d7fb628255b8b7fb4c3e3ae64b1cb65e0de711a6def1653d95d8088871cb8905fe8ae76423604988a8f77589f3f776dc1e4b30dbe9dd262b2187db02518a132d219bd1a06ebac13132b5164b6c420b37dd2ccee7d69b3b7fa12e54f0a53b853d490a68379ea1fa2d79762830ffb71bf86aab506b51f85c4b6a41b69325c7d0c7aa85b93b7144489d213e8f33dbb879fce22849865337b620b155cb2d2d36a68832889e30194d36d",
+ "d009c2b78a8f02e5e5dbb586ef71fc324b375092e15913ca1a5bfd22d516baadb96867bee3562e77c4a4852344a1a76c30728be5e22400b4cc41711f66754c246a520498d8c24f0205b9c873748dbeb67fe1ad099ad04cf89f4b517f0aa481136d9f6de2d727df01c6aa4099da59d4382b51e25fd47c33d9842c32b62331e50794bfe8b61b3ba9de1b8b704779c6d65edff3af00f121ab4a7ea384edabe47c6d0098a48991f387ca4444135ec59d46",
+ "c00bab36cce69899817d1425016d222d7303197ed3e3fdcac744705e7f178a1ac745968900f69299163e19b3161f3e0a4cc55aa2e4e71e0ee6ac427d1f4d14e063f68d303ddfbb18118335cfa7a6a90d99c38319ee76f7a884846a9e0b68030bf28e78bfbd56359b9368842814da42b04cb0e307d5d846dc22f049147bae31b9a956d17676a8cc348dafa3cabc2007a30e730e3894dddf9999fb8819086311f0703e141613ed6dcd7af8510e2dc435b0",
+ "c9789152a9fc29698d49ed95f09bd11b75f18a8c5615a73dbe54ae5e550027fd0ae6a8b60667040c1b12de3d1ee3f6bf061c78c951a3210effc912e19f482dd4de152063c588c44903bc11761706fd935afa040df085b08144d83d0dde32b46ab52f4fae98ac116c7ff11d7f553450c2e37b9c5f0b1dd9e0b8640a24cba6f2a5246c41f197f46e3dc8a29131c79bef3351c6e277a0a34442274d546ccd058891277473d668420f121750d19cd684267405",
+ "06a15a0731ce52557e368bcbaa11ef3399299e36fb9f2eda6e5726907c1d29c5c6fc581405ba48c7e2e522206a8f128d7c1c939d1132a00bd7d6366aa82724e968964eb2e373563f607dfa649590dcf5589114df69da5547fef8d1604cc4c6de1ed5783c8746918a4dd31168d6bc8784cd0c769206bd803d6ca8557b66748770402b075ef44b38157d4c0da7c6281725a2065d087b1f7b23455fa673bdeeba45b983311c44eabe9ef4b7bde3420ae9881863",
+ "d08aacef2d7a41aec09473bd8a44f628e15addb7b9e5b77a1e09c8ab4942f379a0bfcb324d580b774666f18ae78dd36710824ff12393f059068fe4b559c53662c2b0e6c69e23785c8f32554e837ec1714bee902e60737b639dd933af4f68cb9d7de77e1f3b28e5b122891afce62b79acd5b1ab4ba411662cc77d806449e69c5a45a143b742d98ac84a0826d68433b9b700ace6cd472ba2d58a90847f42ce9c43f38ffc017db4bf40450b2eee1f4594dc740c0f",
+ "6a6058b0a498b7ea76a93c646eb9b8629f0cba4a0c726420c5f67ba9b0412cade356abdf0a4fb94384bad32ce0d5dd9e23dcaae1d6f28ff8683616b30f1392890c67b3a2c04b360893b801f127e527e4da82e239f4c878da13f4a4f1c76db07190e77ec123995168102fb274434a2d1e12913b9b5cbab4aacaad2bd89d88b3ca2b8e60dacf7c22c9379097ff60880f552e320ca3b571994f52534470feee2b39e0dadb5cd88257a3e459a4cc6f12f17b8d54e1bb",
+ "adeced01fc5671531cbb45679f5ddd42b3a95151677b6125aaf6f5e8f82fbabaa5ecf7c3552c2458587224f0042870f178f5fca5465250e75d71352e652eeed23cdb7f915f5ebb44099b6db116ca1be45530ac8ed32b7f161d60ed4397ad3d7d649ae6bf75ca5bec891d8e595605be9764f3a03965e1fe0eaffbf212e3df4f0fa35e08ff9d0091e6d4ac4748edfe43b611085a6ffec163014655fdd839fd9e81b63b1fa8cae4ec335ec343289758e389a79ceedfae",
+ "d014592f3a83ba40af366f137c674724916c3cdd3f6cf9d4c5c7c8d6d51ebf26e315e2c12b3546be56fb52382904046ecbd2f5b883aa4ff473de6f0c26ab862c3fa34bf3d880cc1911ce39a4088c6617c179dc5faf68a2c488bbde12d67b50f73abcfab0e3b062e68c95363e11f5f1de8ec36ed01ea21442518089045df67d346135283ad5b3fff80cf57f20876849f6db9fa139728358415a90610f69ec720fc92d8234e3e122551e9df2c644c4a2c4e3734d07de8e",
+ "c0d0c37838873ba8757d6e41b409605043bc1635edcd731219587676d94217e9f0ab44b71de25000661ce7303b7015f45e6eaa7b7ebef92b8f4a34c902c908d2172185505fa33aca5a41be83079316cdfdd430fc2c45f505f85d867e6d516f7e1bf19c001d9f43018968aab65ec031b3801399231c83ec9e622dab5629922a6b424cab938c135ff7310501c2c02971bfd2f577e25904d1a618baf0859f77f4e8b1d0cde9544e95ec52ff710c0672fdb3d891feeea2b017",
+ "7022e7f00902219ba97baa0e940e8ac7727f58955aa068c29680fac4a16bcd812c03eeb5adbcfe867a7f7c6b5d89f4641adb9173b76a1a8438866f9b4f640ce2aedf5f1080c890bcf515b4be4e3e512352f1e5323c62ec46cb73f3d71be8235fee55a154763f7c3f9aeb61ffd28f4cd93d3310f608e2133586bf1ab3f102de96f64c68a4668de8acb2a76a7ce0cddddc8fa3df5e9d230823da16ed9ebb402d36e38e6e018795e5a71517ecab5f9ca472b9ced8ff69d2d195",
+ "acaf4baf3681ab865ab9abfae41697141ead9d5e98523c2e0e1eeb6373dd15405242a3393611e19b693cabaa4e45ac866cc66663a6e898dc73095a4132d43fb78ff7166724f06562fc6c546c78f2d5087467fcfb780478ec871ac38d9516c2f62bdb66c00218747e959b24f1f1795fafe39ee4109a1f84e3f82e96436a3f8e2c74ef1a665b0daaa459c7a80757b52c905e2fb4e30c4a3f882e87bce35d70e2925a1671205c28c89886a49e045e31434abaab4a7aed077ff22c",
+ "84cb6ec8a2da4f6c3b15edf77f9af9e44e13d67acc17b24bd4c7a33980f37050c0301ba3aa15ad92efe842cd3ebd3636cf945bb1f199fe0682037b9dacf86f162dadabfa625239c37f8b8db9901df0e618ff56fa62a57499f7ba83baebc085eaf3dda850835520344a67e09419368d81012168e5de5ea45158397af9a5c6a1657b26f319b66f816cd2c28996547d697e8df2bb163ccb9dda4d6691dffd102a13667ab9cde60ffbfb872187d9c425a7f67c1d9fffff9276ed0aeb",
+ "6a52c9bbbba454c14540b2be58230d78ecbeb391646a0c6fcce2f789086a78364b81ae85d5396d7cfa8b46bda41e3083ec5cf7b4c47dc601c8a697df52f557defca248506dbebab25657f5a561d09625b7f4b2f0119a12beeac087efc9d350a735c35d2431c1da7dda99befb17f41a3dc4da0f00bb95366be128538ce27763d81f832fe3c1d4efc07b5b08ad8dc9e65fb5e48546664e18cb2d3bb3fe1f56fa7aae718c5e3bbdeaf70e15023f6a25b72a2d177fcfd04211d40664fe",
+ "c3c4d3b31f1f5f9538923df3478c84fffaef411520a542da9a220ee4132eabb9d718b5076fb2f985485e8ba058330aed27ddfd3afa3db34aa60301088caec3d0053828c0c2bc87e2e61db5ea5a29f62fdad9c8b5fc5063ec4ee865e5b2e35fac0c7a835d5f57a1b1079833c25fc38fcb14311c54f8a3bd251bca19342d69e5785f9c2e43cf189d421c76c8e8db925d70fa0fae5ee3a28c4047c23a2b8a167ce53f35ced33bec822b88b06f41558c47d4fed1bfa3e21eb060df4d8ba1",
+ "8d55e92136992ba23856c1aea109766fc44772477efc932b3194af2265e433ed77d63b44d2a1cff2e8680eff120a430fe012f0f09c6201d546e13ad46fc4ce910eab27bb1569879abed2d9c37fae9f1267c2216ec5debcb20d4de58461a621e6ce8946899de81c0add44d35e27b7982a97f2a5e6314901caebe41dbba35f48bc9244ca6dca2bdde7306435892f287036df088633a070c2e385815ab3e2bfc1a47c05a5b9fe0e80dd6e38e4713a70c8f82bd32475eea8400c7bc67f59cf",
+ "5016284e20362610fa05ca9d789cad25f6d43263787e7e085476764ce4a8908ce99b262b375e9d106170b1bec1f473d5e777e0c1896533040e39c8c1465e07907ef5860e14e4d8310013e35f12090e0bfc687474b1f15f3dd2033a0edac5246102da4deec7e188c3517d84d9c2a0a4497a4c5f82a30f1ba009e45ee6eb3ab4368c720ea6feee428ffd2c4cc52debb8d634a64176572c72368f94a66689f23f8a01218f532117af5a8060d140e7ca435a92882fcb5630ebe14a4805f1dc83",
+ "05456ec59b8d41bbd736727976b96b38c43827f9e16169be673ff37870c2ecd5f0d1ea1a136be4cc7b047a02a4421d484fd2a12ece418e42ee391a13a0b1df5a0162b29ab70d3fe3e04ba6ab26b37d62b7cf05a5e2f033611bf970b8e1f30e198e483e740fa9618c1e8677e07b61296b94a9787a68fba622d7653b5568f4a8628025939b0f74389ea8fced6098c065bf2a869fd8e07d705eadb53006be2abb716a3114ceb0236d7e916f037cb954cf977720855d12be76d900ca124a2a66bb",
+ "eb6f60b83fcee77060ff346aaf6ec34d82a8af469947d3b5074cde8eb26566eb1fa039bcc707738df1e95869bd827c246e88436f0614d9834ead5392ef376105c4a9f370071cdeaaff6ca0f18b74c3a48d19a717253c49bd9009ccbfdd5728a08b7d112a2ed8dbafbbb46d7a75dc9a05e09bfde1a0a92d74a51887f9d123d7896e9f9d0057b660ed7d55454c069d3c5260411db4cdc67e7b74f680d7ac4b9dcc2f8baf72e15e6b3cafebcdf449a6436ed2c398b675f79c644747c57553bf7ea2",
+ "187a88e88514f6c4157c1ba40b442baae1ae563a6c989277443b12a219aa484cb9fa8adbb9a29d429f50155321b15664926317477079c7060dfdaa84c1d74bba78892c34e6f21ad35208d2ae622012401696bff5cd57b6485944b3db7b9071fa5f57fbfb1085d91bb9cff5808d662cdc6c8157249478262c44b7fbc397ed42a4977b202e817717bfccc9f0467294062313f7705251ed09573f16d23429361fada259dfb300369c4198f07341b38e84d02cdb74af5de6aab1fc2026208ea7c418c0",
+ "be31bc96606d0fab007e5caeded2f1c9f747c759777e9b6eef962bed49e45a1d4fc993e279d024915e600865ecb087b960584be18c41114d3c43f92169b9e0e1f85a0ebcd4e196376ccdc920e66103cd3b1c58407d0aafd0e003c4e341a1daddb9f4faba974362a32f35db83384b05ae8e3322d728893861afd8b1c940de5a17f691e763ce4969b6d94f67fb4a0235d100225bd8602f291388f0ca4a568748ad0d6040f1262eac2aede6cd27419bb78a394c1ffad72c262be8c3f9d9619d633e51d0",
+ "4d83d85ca838b4518588f2a90228a4dd18f14dd5b4c012d26298a97d848abbd825d221d02cceb6e8c701b4ad00e1dee4889b5c533e4bb60f1f41a4a61ee5478be2c1b1016c30345afd7a5253668260515e70751f22c8b4022d7fe4877d7bbce90b46531507dd3e89549e7fd58ea28f4cb23d33662bd003c1345ba94cc4b06867f778957901a8c441bee0f3b12e16463a51f7e50690356971dd73a686a49fda1eae46c9d54fba262811d698025d0ee053f1c58591c3bb3cbde69de0b31549ef5b69cf10",
+ "cdeb07d36dc5f9a1cd717a9e9cca37a2ce93caa298eee63571f7d6c5fde2a11c666cf53cf2dcb41ca2ea2319e7230ca68e38c647905928713a13982bf47fe33d7095ebd50b2df976208920a43eb2e29b942f32467403c45cea18bf44e0f6aeb155b48a8e5c471fec972a9d62f7ae093d2758f0aaec7ca50cb4725bfa219f1a3a46ad6bde7361f445f86b94d66b8ece080e56c510250693a5d0ea0ae87b4421860b853bcf0381eae4f1bf7c5c0472a93ad18407bc88475ab8560d344a921d3e86a02da397",
+ "a598fad52852c5d51ae3b10528fc1f722e21d44fbd42ae5acdf20e85a28532e646a223d27fd907bfd38eb8bb75175636892f8242877aab89e8c0824d368f3339ce7a82aa4e5af6db1f3b588a4d667a00f67bee37cfd2724dde06d2909fb9e58d892f4cfd2c4ca85acdf8256f5458b030a6bda151154ff2e6d7a8da90b54a2884c8a99fab5a4ac211ff23dc0975f4f592fd1b6b9dc7783bdcd2d4ca4e68d2902f2013e122cb62e2bff6b0a98ec55ba25837e21f1cfe67739b568d43e6413dab2bd1dc471e5a",
+ "17b68c74c9fe4926e8102070916a4e381b9fe25f5973c9bd4b04ce25749fc18931f37a65a356d3f5e5a1ef125d546f4f0ea797c15fb2efea6fbfcc5739c564693d47adeb12dcb3d98a2830719b13247792cb2491dca159a28138c6cff925aca42f4fdb02e73fbd508ec49b25c60703a7595a3e8f44b155b371d525e48e7e5dc84ac7b17c52bf5e526a67e7187234a2f19f57c548c70fc0b27183df73ffa53fa58b658034c896fa791ae9a7fd2620f5e46ce84c842a6e60e9324ae4db224ffc87d9617cb85ca2",
+ "b9e4267ea39e1de1fed0579f93bb351007c9f8fcdd811053fae33f09e2753d7428f04e1a9efcd45ea701a5d87a35b3afb2e6b65365dee6ead0bbb611b7797b212ac688653f542e604a39df277f12514ddfee3b4e27b98395c2cd97a203f1f1153c50327965770802ec2c9783edc428271762b275471e7ac65ac36523df28b0d7e6e6ccc7674268a132a63411fc82c0738dbb68af003b769a0bf9e6587b36476cb465350fee13f88ea355d47ffac7b0f964f4139db11b7642cb8d75fe1bc74d859b6d9e884f75ac",
+ "8ca704fe7208fe5f9c23110c0b3b4eee0ef632cae82bda68d8db2436ad409aa05cf159223586e1e6d8bdae9f316ea786809fbe7fe81ec61c61552d3a83cd6beaf652d1263862664df6aae321d0323440430f400f291c3efbe5d5c690b0cc6b0bf871b3933befb40bc870e2ee1ebb68025a2dcc11b68daadef6be29b5f21e440374301bde1e80dcfade4c9d681480e65ec494a6af48df232c3d51447b9d06be714949249c44c43cf73ed13ef0d533e770284e51369d94ae241a5fb2f163893071b2b4c118aeaf9eae",
+ "4fd8dd01012bb4df82bf42e0683f998e6f52dd9c5617bae33f867d6c0b69798cead8179346d70acc941abbbdd26e3229d5651361d2252c72ff22db2938d06ff6fc29a42fdf800ae967d06479bc7bbb8e71f40b1190a4b7189ffc9a7096cdb76d40aec424e1388e1eb7ef4ac3b34f3f089da8fda7d1927f5d775c0b2801d22dd1265c973158f640cec93edfed06dc80b20ef8c496b98289d54d46ccd205951cbb0f4e7daeb866b60bacb483411e4382b6f04d472843186bd0e31fbaa93e5c901ec028efafeb45fc551a",
+ "e9ee1b22b04b321a5fdd8301627011f583887d77560fb0f35552e207561f81e38ac58a0d0aeaf832d1ee72d913720d01f75574e9a321864fe95f4d0d8f0b8db97649a53e71e940aede5c40b4b9105daa42a6fb2811b61209247534cbaf830b07abe338d75d2f5f4eb1c3cf151e9edabe2c8d5f6fff08fac1495ef48160b100d30dcb0676700bcceb28723a29980ab0766a93abb8cb3d1963007db8458ed99b689d2a7c28c788743c80e8c1239b20982c81dadd0eed6740c65fbc4ef15c7b5569cb9fc997c6550a34b3b2",
+ "ec01e3a60964360f7f23ab0b22e021815765ad706f242265ebc19a2bb9e4eac94393952dcf61aae47682671a10f9165f0b20adf83a6706bfbdcf04c6faba6114653a35584267267873291c6fe7ff5f7695243143421509502c8875aafa9e9afe5be5ef2c851c7f35d69be5d3896000ccdbbfab5c238bb34d607cfe2d55d748880545b4aa7ca61137992925189025c62654b1f20d49c3ccd75aa73ce99cd7258dabedd6480a9f5185531fc0118beb68cc0a9cd182f6973287cf9252e12be5b619f15c25b65c71b7a316ebfd",
+ "db51a2f84704b78414093aa93708ec5e78573595c6e3a16c9e15744fa0f98ec78a1b3ed1e16f9717c01f6cab1bff0d56367ffc516c2e33261074935e0735ccf0d018744b4d28450f9a4db0dcf7ff504d3183aa967f76a507357948da9018fc38f150db53e2df6cea14466f03792f8bc11bdb5266dd6d508cde9e12ff04305c0295de29de19d491ad86e766774bb517e7e65befb1c5e2c267f013e235d8483e177214f89978b4cdc81aa7eff8b39f2825ad3a1b6ac1424e30edd49b067d770f16e74dd7a9c3af2ad74289a676",
+ "00e40f30ae3746edad0f5dd03d0e640933cf3d1694804c1e1ed6399ac36611d405196ee48f129344a8512feda16a354517871322bd5d9c6a1b592933eab531923efb393ffb23d9109cbe1075cebfa5fb917b40df028a621460ff6783c798792cb1d9635b5a6f84ec13918fa302924649b5c7fcb1f7007f0d2f06e9cfd7c27491e565a96c68a0c3644f92cd8f38857258c33801c5d537a83dfe583cba59d7eec7e394199c0a2660a62fabe3ed2099d57f315a6cd8de1a4ade29d977f15d65759cff433e5ac0c182aef3761163e1",
+ "3c5ea24d0d9b618294a263f062b2414a722be4eb10dfc346a6ec3b821d7396eba61cd6ef33618b04cd087a811f299d4606820227f16000d7c839062b96d3e3f59cd1a082448d13fc8f56b3fa7fb5f66d0350aa3b72dd7c165d590282f7da2e12cfe9e60e1796122bb8c2d40fdc2997af634b9c6b127a893dfb3467909378300db3da911be1d7b616bb8e0572433e65527e15d936500a2c60e9f9909dcf22ab5e4b6700f0238c205b4a813626fac3d945bab2637fb08203044a73d20c9a3fcf7c3fc4eb7807c3276dd5f73ce89597",
+ "9271aeeebfac46f4de85df78f1bfd36136aa8905e15835c9e1941176f71e3aa5b1b131843d40479735e23e182a2bd71f66f6149dccb7ed8c16469079dc8590bbf165374951785f4531f7e7361de62f936cfb23a2b5bdf186632e7042a0dd451fdc9b7208f923f3a5f250ae590ec348c63a16c3aacaf7379f53b5dd4152dcd40d23e683e2156e64c592ffc07e2cd6bbeebef4dd590b2f6b2bcbf08fcd111c079f5c4033adb6c17574f8756ecd87be27eff1d7c8e8d0324438d59ae171d5a17128fbcb5533d921bd044a2038a5046b33",
+ "4e3e533d5bcb15793d1b9d0468aaee801f32fdb486b11027183553a09ddbee8213924296f2815dc61577297459e834bf1c7a53f87d43782209e589b8295219ba7073a8fff18ad647fdb474fa39e1faa69911bf83438d5f64fe52f38ce6a991f25812c8f548de7bf2fdea7e9b4782beb4011d3567184c817521a2ba0ebad75b892f7f8e35d68b099827a1b08a84ec5e8125651d6f260295684d0ab1011a9209d2bdeb75128bf5364774d7df91e0746b7b08bda9185035f4f226e7d0a1946fcaa9c607a66b185d8546aac2800e85b74e67",
+ "b5d89fa2d94531093365d1259cc6fe8827fea48e6374c8b9a8c4d2209c280fa5c44958a1847222a692a59e6aa2696e6cdc8a543dd89b0ce03bc293b4e78d6ef48e1839694ccd5c65661143095c705b07e3ced84a0f5959114dd89deb956ab3fac8130eb4a878278205b801ae41a29e34146192308c4e759b374757b0c3b00319bce92a1b95a4d2ee179fd6714ff96155d26f693a5bc973f84ac8b3b91e3926276297532d98b46992a3f104c08100bf1671c43134bac280c617da711e90a0100137525375ebb12802a428885ae7fce6514a",
+ "40e3d8048fc10650cb8a7fc2e7113e26dec34f9ca2d5129cd10a8e8e44d113d61ee48c7d003e19fd307fc6debd70feb30243f298c510ccc4418355ce143066f067ad7c6de7288c3080e7ad46a23c8d34deb55a43e652fe90444ad3c57d3ec1e1c489d63ef915a24bc74a7925a0a7b1e1523f21ca8fee78df24e3d0a68d0013423db97c280799a0618229c0f2c167289a891e5c8d6661ab21285951c31710e3b5fe55f6347fe16d9b40507948a59252efeb616df83e5c098b07d0a7247cd371daff0e50491c582503fd89f79ba94d6af9ed76",
+ "1fa444de01dd3901e2b4684e3d7a799ffa02d85afd35fb30fe4c9d672837bee6dd8a3b8608b4bb5e589220ad5a854f46b46e41c6d57ad124a46beab4169ff69fee7e3838a6165e19dad8eb5d7bf53d4edd3cd2769daf219510a02fdd2afe0c0e1da3cd30fcd1aa88b68965586f07a25a1720fbd90a096ea30fc8e945e3637d7857c8a9c0ab4154ffb2000e57b5f9adfa4e4eaf8065bc3c2b2e75f495963325588785a6ce417dcddffd299873b15dcccca128d63cd4eeeadb64cda28099a9ad7c80d34844901f26b88b00b9aafeb2f90286d29d",
+ "fde0a0d9d813983bd1f55cf778a003a2023b34a555322ab280584537bc6bdd844d22a7d6066c18da83ec09f3d8d5a1aab4be0d5ce19b436052f6e259a4b49017a1f47f1fe2bf115d5bc8599fb216351c60dd6b1bedb2e6f4dcadf424b833501b6f099cbfad9e2290680fb69c25032b42a6274f7cb9b5c5950401354838a45f7cb77b95bf54718e2f3d3d9fb91eb2311903980277396398d9736d8e92fd838594ac8a537c6c529db5a8a4f89290e6ba6f20ac0e5ed6fef40901d0e0e8e3e502990811f9acaae555dd54eb1bcd96b513e2fe751bec",
+ "9f8e0caec87858599f5ab29bff86da78a841a918a023a111098687ecdf2747612d3f3809d9ca400b878bd4f92c43a1004f1c17c7f19a3cd1ce449bd2b23aff551623c37dd8c0be56bf3fd857b500c2b9f9ccea62481944090a3cf3b6ee81d9af8eeb60f65ef150f9fa4d3ed6ce4762d3d4f174ee8ccd460c25cafac0ea5ec8a6a4b2f9e8c0520cb7061155e532cb65f188b01e4b9086db951f504b060c296b326b3fc1c590498ecce594f828f4a10ea416675720ae505295d38a791bd0e93f428448a8f4c1fc0af53604a9e8255384d29ae5c334e2",
+ "33d1e683a4c97ee6bbaa5f9df1a88cb53b7f3c157b6045d70a56fda0ccbd3a1fa1f049cd564da072b53f415bf5fb843771c1d2551fd075d33377362b2f7c0645f9723123d11975991db8a2b518f02e2c7c30342a044754290bae2c77496d755e5981f12e6b0a0174280b958bf11ed628a9062775993ced04bf752ea8d165e3ac2177d7cd1b9371c44efa98f0b3e68602a839d384eec007979f46429dafb138cbc231ad928a9f65f7d66fac77416395e8f1debaaf76ec2e4e03e8674102cd26f614739f3ec9f949033df1fb97e87c2326d65aef94ed5f",
+ "180048f09d0b480887af7fd548a85abf605440c1ddde6afe4c30c30670233f7bf928f43b4681f59279ebbda5e8f8f2a1abefdee129e18ac60f9224e90b38b0aabd01308e0a27f41b6fb2ee07ee176ec9048c5fe33c3f7c791469c81f30e28170585b9f3e7e3c8c2e9d74370cb4518f13bf2dee048cbd98ffa32d85e43bcc64a626b40efb51ce712925fdd6fee006dc68b88004a81549d2121986dd1966084cd654a7c6686b3bae32afbd9625e09344e85cf9611ea08dfce835a2e5b3726e69ae8a76a97db60fcc539944ba4b1e8449e4d9802ae99fae86",
+ "13c0bc2f5eb887cd90eae426143764cf82b3545998c386007cca871890912217aa143ac4ed4ddb5a7495b704aa4de18419b8664b15bc26cfc6596a4d2ae408f98b47a566476d5802d594ba84c2f538def9d016661f6404bb2337a3932a24f6e30073a6c9c274b940c62c727242e24466084a3ea336365d71ea8fa6499c0ea8d59eea505f1126b99c795023c4963aa0d99323d0391e8701110edf551b2d3799e1063ca443f1add162156e445502ca1a052fe70c289838593b58839fc63de128a03e2bbf389e22ae0cf957fd03315ee407b096cc1cfd92dee6",
+ "6f1eb607d679efef065df08987a1174aab41bdac8aece7726dfa65805d6fff5b3d17a672d96b770dc32165f144f0f7324822a5c87563b7cd9e37a742ae83ef245d09006d91576f435a03476f509ea2936636232f66aa7f6cdf1ac187bbd1fcb8e20f8791866e60ed96c73374c12ac16795e999b891c64507d2dbd97e5fc29fac750ad27f2937cbcd29fdafccf27ab22453834d475f6186eaf975a36fad5c8bd61c21da554e1ded46c4c39765dcf5c8f5ccfb49b6a4dc562c919d0c7d8940ec536ab2448ec3c9a9c8b0e8fd4870cad9de2577c7b0c38563f355",
+ "dcdd993c94d3acbc555f464871a32c5da6f13b3d5bbc3e34429705e8ad2e76393fdd96a69a94acb652f5dc3c120d41187e9aa919669f727c4868013b0cb6acc165c1b7706c52248e15c3bf81eb6c147619467945c7c48fa14a73e7c3d5bec91706c567145342a026c9d97eff97ec672c5debb9df1a998083b0b0081d65c517b3e5634c95e347e781aa30ca1c8af815e2e494d844e847fdcb41622894a518dc36571123a40bfdbe8c4f4cff44d83c61dd9dcd24c464c53b395edb31efee9f3aa080e87cdc3d22d613ae84a53c9249c32c96f9a3bc4629bb126a70",
+ "49971f9823e63c3a72574d977953329e813b22a8387cd13f56d8ea77a5d1a8a20012632d1d8732bbcb9f756b9675aab5db927beacab7ca263e5718b8dfa7b2eed9a91bf5ed163b16139d45f7b8cc7e3f7bdda6202106f67dfb23b7c315ee3e17a09d466b1e6b13e7c7428184a979f5358667b4fa8bd40bcc8ea46058db44587a85377ac46bf155136c09ac58cb6c27f28e17028c91e7e8f74d5b500e56293b316974f02b9d9ea205d9b6ac4cfb74eb8eb0c944577fd2f41316368307beab3e327bf7dbaa0a4428836ec4e895dea635234abeaf113ceeadac33c7a3",
+ "c57a9cc958cee983599b04fe694f15fb470fcbc53e4bfcc00a27351b12d5d2434444253ad4184e87b81b738922ffd7ff1dc1e54f39c5518b49fb8fe50d63e3935f99e4bd125e8dc0ba8a17fd62de709339a43fabe15cf86d96a54010112170c340cfac4132182eed7301402bc7c8276089dec38488af145cb6222525894658f03501204b7a66aba0be1b557b28a2f652d66f7313ed825ecc4d8596c1be7420d4425b86a1a90a5b7f30d0f24e0d1aae0eb619ca457a71699e44be612a4011c597ee80b94d5507e429d7fc6af22579cd6ad642723b05ef169fade526fb",
+ "0568a672cd1ecbaa947045b712e2ac27995392fbef8f9488f79803cbee561c212287f080eca95adb5ba42739d78e3ba667f06045d87850d3a0499358649caa257ad29f1a9c511e7054db20554d15cbb55ff854afa45cae475c729cea72ede953522031865bc02b95589ed4d9841c552a8cc94904a93ed09ed77222f6c178195056be59bc4e96a815adf534e6b466fb47e262ff79c803c157a21b6e2269c2e0abeb494113cd868d8466e82d4b2f6a28b73645853d96bc9242515d803e33294848d3fe42fdff68da53c03491636beede47ff1399dd3d54a5e914d55d7adf",
+ "3f19f61a4cd085796731ac9f85a75a8bce77031932c31762d87d8b8d07b8bd19ff78d6b7d1bd1e87f3a4f41aad03b6c4d17a6cbc86be55f7c8b88ada047bb04f8d49f1c34bcf81cc0f3389ad01a758fc7eeb0072aa9ad1481992bfdde82e438e75590a4423832dfbe3756e2229ea873bc3606e6d72174cb2163bf40b5d49c81009dab85ecc03e311351bbf96e32c030a2b276a7698cb25bc2c967acb3213161a1fdde7d912cd6a804490f8056c47da1333f6e35c41e749c2c23919cb9af5eec5652e6e072b034fb1682e9aaa194a9c0bd456ea0b008d14dbce37967a7a8e",
+ "705f98f632d99d3651793825c38dc4deda56c59eac539da6a0159c83131cf8ab6f2ee0c3b74111fde351f7aa1a8c500a0cecab17c212d2c58ca09eae608c8eefc922b9902ef8d6832f799ba48c3c28aa702b3242107edeba01daafe424406a3822965056cfe8783455a671e93b1e2eae2321364f1871471c82124df33bc09e1b52882bd7e1c4c7d0b2f3dd4a28c2a002a43246768af0700f9659de99d62167be93177aabf19d678e79e9c726ac510d94e74873eda99620a3961930cd91937c88a06d8153d64fd60da7ca38cf26d1d4f04a0df273f52127c53fdc593f0f8df9",
+ "ea6f8e977c954657b45f25480ff42c36c7a10c77caa26eb1c907062e24fbca5aebc65cacca0de10abea8c78322f08672e13d8ac16996eca1aa17402eaea4c1cc6c800b22dc18cb8d620192d74bac02c07b5cfa61e513c7f28b7e29b9700e0e442720bf4c669d4995da19d19f841d9eb68cc74153592591e3bf059ef616b95305aa453b32fe99a91afb35bd482cf2b7aa42702837a53be3c38883d2963020e347556f841254ec6b85854485fe8c520b05f2ea67a9bf3981555c20991e2bacd4db5b418228b6002d8d41c025cb472bf5443aaa885974a408ea7f2e3f932c600deb",
+ "408190134ed06556811b1af808ab2d986aff152a28de2c41a2207c0ccc18125ac20f48384de89ea7c80cda1da14e60cc1599943646b4c0082bbcda2d9fa55a13e9df2934edf15eb4fd41f25fa3dd706ab6de522ed351b106321e494e7a27d5f7caf44ec6fadf1122d227eefc0f57aefc140d2c63d07dcbfd65790b1099745ed042cfd1548242076b98e616b76ff0d53db5179df8dd62c06a36a8b9e95a671e2a9b9dd3fb187a31ae5828d218ec5851913e0b52e2532bd4bf9e7b349f32de2b6d5d3cdf9f372d49617b6220c93c05962327e99a0480488443349f0fd54c1860f7c8",
+ "5f9e5c6f38573a85010a9d84d33f29c057003b2645e3ea6f72cbc7af95d197ce6a06b13fea81722853e6991791b8b15091cd066f5ed913592ed3d3af5370d39ba22beeb2a582a414b16824b77e194a094c2afdcc09aa73ce36f4943cca5ae32c5017dc398801dd92a47382d9327c9f6cffd38ca4167cd836f7855fc5ff048d8efba378cdde224905a0425e6b1de061fc951c5e624a5153b008ad41160a710b3ff2081748d5e02deb9f841f4fc6cf4a15153dd4fe874fd447482696283e79ee0e6bc8c1c0409baa5ab02c5209c319e3169b2476149c0c6e541c6197ca46e004eef533",
+ "218c6b3508aec69574f2b5039b30b942b72a8349d05f48ff945bbbe5c8957d5a6199492a6bf54bab821c9377e2edfa4c908384664d2c80112d5e805d66e0a551b941021be17dd20bd825bea9a3b6afb1b8c605805b3bda58750f03ea5c953a698494b425d8980c69f34d1c3f6b5866e8717031152a127215c256e08873c21b0f5cc85875d0f7c94601659150c04cd5fe5d381ba29983a2d94fcd3a65a94c53c7279cd000dddd4253d8cff8d7f6ace10247fe3bc30d63ba4bb54f557b3d22a3924369430d71ab37b701e9500bda70b5a643704858beed4726a889b6c9c91584194c68f1",
+ "dac26aa7273fc25d6e044c79fc2bfa46e59892a42bbca59a86826c91e76ab03e4bd9f7c0b5f08d1931d88b36ea77d94f7ba67cd4f1d3086e529427201119096ae066ae6f170940830ed7900de7bb9d66e09788287403a4ecc93c6da975d2fb08e918840a236c15f5d3a8f7375c2eeebbf6f01a6e7f29ca2b8d42df158414c320777433663c59fdcd1f39ca68e3473db721be7ce8c6dba5fddc024f94fedb286b0477581d451313ca8c737484daf60d67f9b2d56d4bcc271f7e9ae958c7f258efbc74d25753e0516f28282461941bf2dcc7dd8c7df6173b89760cefcac07190243ff863fb",
+ "c46e6512e6797cc7a54254a1b26b2de29aa83d6c4b1ea5a2786fbcec388270625b12635eae39e1fba013f8a65219421bca8b52a8ddfd431cda60299bdf160734d5a7450ec79620058522702174ae451b9bfa7c4a455fbbee3e1d048c7d4bac5131018228f137c8e130440c7059b4f15eaa34ce872a851a16ce86f982df78a00be4d564da2003a450ddee9ab43ea876b8b4b65c84f0b39265fd5456417afb5bc54997c986e66fc222f2123ba5e719c4d6b9a177b188277df384f1125821cf19d5248cef0be183ccdc84ac194506f740ed2188b2689ea4c9236a9e9e3a2fff85b6af4e9b49a3",
+ "1ccd4d278d67b65cf2564ecd4de1b55fe07adc80e1f735fe2f08ea53fd3977323689122c29c798957abaff6aba09bdcbf661d77f4dc8913ab1fe2bef38846166e3834785e7105d746484eff8c656af5d8c7854abc1c62b7fadb65521dc6f793d978bda9838eb3800417d32e8a24d8c8cb1d18a5de6ca79d9e1b0ff9aa25e6218fe944cf18666fecc1e31334b390260dbe0997539e1b02f6366b2aea4f4a21efe04f4b97568fcb39e59919d5ebac6543d5d0f48fc66b923c34aac377dc95c20329b837b6ed5e8d9a3d2089cd0d8f025658006ff41cbdaccca618822ca590ab155253f8bc1c7f5",
+ "9875209588395ee3c9fdd793fd48717cc84c8c3ea622b2ccc4a1be4448e6034b7810569855255031f10be5ffd714b05f9ce01972d712d40abf03d4d0ce175813a7a668f761324996093fc2aa5912f7fc2abdadd8775d2b4d9ad492216293381460ed8f6db3d641d1525f4242c348bbfe504c704f215dc461de51b5c75c1aae967936963848f16c673eca5e78dfd47eb19001d52d1bcf96c98956dad5ddf594a5da757e7ca35f2f69803b784e66ac5a58b75c228b8266ec592505e5d1ca87d81225738855f15bc0914677e81593fd409e77d159f8a908f67788de9eb06c5561547aada96c47c535",
+ "40c90e375e366f3756d89091eb3eed9fe0fbfc5638700af4617d358812bac53124a2205dd6756456787d49cd6a35e302479a0992288f47532e4ea7ab62fc5ad5adc690a5d9a446f7e035ad4641bd8dae83946aee3338ec984ccb5cc633e1409f2531eeffe05532a8b0062ba99454c9aeabf8ecb94db195af7032bfebc22912f49d39330add47ff8fa5720612d697f0b602738930e060a1bb214efc5e292224cf34e29deaea6b1b1ff847e94ecc997325ac38df61db45d82bf0e74a664d2fe085c20b04c39e90d6a170b68d2f1d373f00c731c524456ada73d659aaac9df3191a7a3865083343fc13",
+ "e8800d82e072210ca6d7fa2472028974780b76aad4bcb9ad362422dd05ae3232668251d164daa375a43b26a38cce28dbeb3dee1a4a579f70d0fe7febb29b5ece8aa836e050fb3d188c63aa9c3c0da6c717d86458a6096b5effceb964efdec7035960c09ccd10dea3c5f1c7f9f478d5887ebbe2e15c5ff85dbacbc444bb951c4eec7abecb89ed80187e409e2972ffe1a5f01562af109f2cf09471cf72cf83a3bb8f4e2ef38ed0e326b698296394e5b2718a5000c01425708e8ad0461e62462d8819c2377f13ab1be2c7c9f33dc06fe23cad27b87569f2ce2e56e4b2c60c7b1b3d370841d89ebdc1f192",
+ "796d6d1447d5b7e8c55cd8b2f8b7010db39f27565f907e3fc0e464ea2d4bb52b37f10e7c6dcfc59231b9cdee12c32aeb4adbc42b86e86eb6defb5b69e6ca75e1f4d0dae3e124e5a1b8b6697f7e10b0403f1f0a5ff848eef3752837a9ba17780f16a9a709188a8d5b89a2fa74adb2e651163b1c2b3d261e225c9158dcd9eb7ac3d6704cee290cdff6bcb3cb90cee030aa0d19d4693655c3c30ac6fc06d2ae37787c47126d57ed9a6bef5f8a6c56859aefc08755739a95aac57a4dd916a92ba9f3afbf969df8085949615033365c751a9a3e1a18cee98a69d22e64009bebf8307169b6c61de0617ecfafdf",
+ "4f9057183566153cf337b07c3f5556006de54c56b2a1e5326c07aaeabd1886ec6f1641358925db232b2f0dbf75229c796a7395b2f934c1f99090bec1123f3c841b1cb3c5b1ec42ed5408f2940f0c48a9470b852c46d6557853d459cecd2c32bbcd8ee21fa11e385eef0857cba4d8545a61b52a484cdd779db4739fbc7aa9860dcabe0488b98fa0b60c3f7d6153db279000a52ffb573dab37d2ab1896a90e5deb7ac6bbe56239085c325d83a917dc6e8a448425b718c2356b9f3066163555ec444f372e184e02c8c4c69b1c1c2ae2b51e45b98f73d933d18750968945ca85d6bbb22014b4c4015262e3c40d",
+ "79dcca7d8b81a61359e4aece21f3df7b99518ce70bd2f57a18bab5e7114af2add0a0cea7f319d69f231f060e0a539d9a23fb3e95451ce8c6340cfb09edf931df84203a39226dd9eb278f11b691ef612585b973daab373e65d11325898badf6732100371fd759960fa8fec373268421d28bffdb9b12a430b92fe4b07566ca0c89e616e49f8fc75ccd9cdc66db820d7c02e109aa5ed86b89770262918a518f90a2292f6b68d68ae03992e4259a17a23c84ec2a417f082b5abf3a26e44d2278ecb8ba9456965303a75f25394d1aaf5544590e74b14d8a4cc4050be2b0ebcfe4d2db6b12a02c68a3bcdda70301f3",
+ "848755dc31e25e9a42f9ec12d847d19f292c14c162c9aba49e972cb123b58b8e57bb263a923929833373858594ff52dbc298dbbc078599194e4c07b0e5fc1e10808bbacdb6e93c72b333685cf961f28eb0d5a395c63266b01f130d25db384b356e5da6d01042fc2359581b89c63b3bb2d1ce897fbc9e83fe85d9666cb60e6a8c657f70caad5387b8a045bf91095606802c8424ea8ac52ef29386dc46183378a5fcb2cb927428b8c070f1c42aafd3bc70ca25437807696a46873cfeb7b80ba2ebc3c4272443d445e46343a1465253a9eebd532a0d1d2c18264b91ff45159f245404ae9335f2af55c802772426b4",
+ "ecaa6e999ef355a0768730edb835db411829a3764f79d764bb5682af6d00f51b313e017b83fffe2e332cd4a3de0a81d6a52084d5748346a1f81eb9b183ff6d93d05edc00e938d001c90872dfe234e8dd085f639af168af4a07e18f1c56ca6c7c1addffc4a70eb4660666dda0321636c3f83479ad3b64e23d749620413a2ecdcc52ad4e6e63f2b817ce99c15b5d2da3792721d7158297cce65e0c04fe810d7e2434b969e4c7892b3840623e153576356e9a696fd9e7a801c25de621a7849da3f99158d3d09bf039f43c510c8ffb00fa3e9a3c12d2c8062dd25b8dabe53d8581e30427e81c3dfc2d455352487e1255",
+ "23a3fe80e3636313fdf922a1359514d9f31775e1adf24285e8001c04dbce866df055edf25b506e18953492a173ba5aa0c1ec758123406a97025ba9b6b7a97eb14734424d1a7841ec0eaeba0051d6e9734263bea1af9895a3b8c83d8c854da2ae7832bdd7c285b73f8113c3821cced38b3656b4e6369a9f8327cd368f04128f1d78b6b4260f55995277feffa15e34532cd0306c1f47354667c17018ee012a791af2dbbc7afc92c388008c601740cccbbe66f1eb06ea657e9d478066c2bd2093ab62cd94abadc002722f50968e8acf361658fc64f50685a5b1b004888b3b4f64a4ddb67bec7e4ac64c9ee8deeda896b9",
+ "758f3567cd992228386a1c01930f7c52a9dcce28fdc1aaa54b0fed97d9a54f1df805f31bac12d559e90a2063cd7df8311a148f6904f78c5440f75e49877c0c0855d59c7f7ee52837e6ef3e54a568a7b38a0d5b896e298c8e46a56d24d8cabda8aeff85a622a3e7c87483ba921f34156defd185f608e2241224286e38121a162c2ba7604f68484717196f6628861a948180e8f06c6cc1ec66d032cf8d16da039cd74277cde31e535bc1692a44046e16881c954af3cd91dc49b443a3680e4bc42a954a46ebd1368b1398edd7580f935514b15c7fbfa9b40048a35122283af731f5e460aa85b66e65f49a9d158699bd2870",
+ "fe511e86971cea2b6af91b2afa898d9b067fa71780790bb409189f5debe719f405e16acf7c4306a6e6ac5cd535290efe088943b9e6c5d25bfc508023c1b105d20d57252fee8cdbddb4d34a6ec2f72e8d55be55afcafd2e922ab8c31888bec4e816d04f0b2cd23df6e04720969c5152b3563c6da37e4608554cc7b8715bc10aba6a2e3b6fbcd35408df0dd73a9076bfad32b741fcdb0edfb563b3f753508b9b26f0a91673255f9bcda2b9a120f6bfa0632b6551ca517d846a747b66ebda1b2170891ece94c19ce8bf682cc94afdf0053fba4e4f0530935c07cdd6f879c999a8c4328ef6d3e0a37974a230ada83910604337",
+ "a6024f5b959698c0de45f4f29e1803f99dc8112989c536e5a1337e281bc856ff721e986de183d7b0ea9eb61166830ae5d6d6bc857dc833ff189b52889b8e2bd3f35b4937624d9b36dc5f19db44f0772508029784c7dac9568d28609058bc437e2f79f95b12307d8a8fb042d7fd6ee910a9e8df609ede3283f958ba918a9925a0b1d0f9f9f232062315f28a52cbd60e71c09d83e0f6600f508f0ae8ad7642c080ffc618fcd2314e26f67f1529342569f6df37017f7e3b2dac32ad88d56d175ab22205ee7e3ee94720d76933a21132e110fefbb0689a3adbaa4c685f43652136d09b3a359b5c671e38f11915cb5612db2ae294",
+ "af6de0e227bd78494acb559ddf34d8a7d55a03912384831be21c38376f39cda8a864aff7a48aed758f6bdf777779a669068a75ce82a06f6b3325c855ed83daf5513a078a61f7dc6c1622a633367e5f3a33e765c8ec5d8d54f48494006fdbf8922063e5340013e312871b7f8f8e5ea439c0d4cb78e2f19dd11f010729b692c65dd0d347f0ce53de9d849224666ea2f6487f1c6f953e8f9dbfd3d6de291c3e9d045e633cfd83c89d2f2327d0b2f31f72ac1604a3db1febc5f22cad08153278047210cc2894582c251a014c652e3951593e70e52a5d7451be8924b64f85c8247dab6268d24710b39fc1c07b4ac829fbda34ed79b5",
+ "d7314e8b1ff82100b8f5870da62b61c31ab37ace9e6a7b6f7d294571523783c1fdedcbc00dd487dd6f848c34aab493507d07071b5eb59d1a2346068c7f356755fbde3d2cab67514f8c3a12d6ff9f96a977a9ac9263491bd33122a904da5386b943d35a6ba383932df07f259b6b45f69e9b27b4ca124fb3ae143d709853eed86690bc2754d5f8865c355a44b5279d8eb31cdc00f7407fb5f5b34edc57fc7ace943565da2222dc80632ccf42f2f125ceb19714ea964c2e50603c9f8960c3f27c2ed0e18a559931c4352bd7422109a28c5e145003f55c9b7c664fdc985168868950396eaf6fefc7b73d815c1aca721d7c67da632925",
+ "2928b55c0e4d0f5cb4b60af59e9a702e3d616a8cf427c8bb03981fb8c29026d8f7d89161f36c11654f9a5e8ccb703595a58d671ecdc22c6a784abe363158682be4643002a7da5c9d268a30ea9a8d4cc24f562ab59f55c2b43af7dbcecc7e5ebe7494e82d74145a1e7d442125eb0431c5ea0939b27afa47f8ca97849f341f707660c7fbe49b7a0712fbcb6f7562ae2961425f27c7779c7534ecdeb8047ff3cb89a25159f3e1cefe42f9ef16426241f2c4d62c11d7ac43c4500dfcd184436bb4ef33260366f875230f26d81613c334dbda4736ba9d1d2966502914ec01bbe72d885606ec11da7a2cb01b29d35eebedbb0ecc73ed6c35",
+ "fd993f50e8a68c7b2c7f87511ce65b93c0aa94dcbdf2c9cca93816f0f3b2ab34c62c586fc507b4900a34cf9d0517e0fe10a89d154c5419c1f5e38de00e8834fe3dc1032abdeb10729a81655a69a12856a78ca6e12110580de879b086fd6608726541cfa9616326bdd36064bc0d1e5f9c93b41278bff6a13b2494b81e238c0c45aea1b07d855e8f3fe1478e373bd9d3957cf8a5e5b9003386793d994c7c575cff2322e2428cbbaa4f47560316ae3354a7478842ff7cc5dcbacb6e871e72b36f06d63a9aaeb9044cfb7974afdc238a5816f537dcf33ee40b4e1a5eb3cff2402b46d548264e133008d284f11b7e4e450bc3c5ff9f79b9c4",
+ "8df21892f5fc303b0de4adef1970186db6fe71bb3ea3094922e13afcfabf1d0be009f36d6f6310c5f9fda51f1a946507a055b645c296370440e5e83d8e906a2fb51f2b42de8856a81a4f28a73a8825c68ea08e5e366730bce8047011cb7d6d9be8c6f4211308fad21856284d5bc47d199988e0abf5badf8693ceeed0a2d98e8ae94b7775a42925edb1f697ffbd8e806af23145054a85e071819cca4cd48875290ca65e5ee72a9a54ff9f19c10ef4adaf8d04c9a9afcc73853fc128bbebc61f78702787c966ca6e1b1a0e4dab646acdfcd3c6bf3e5cfbec5ebe3e06c8abaa1de56e48421d87c46b5c78030afcafd91f27e7d7c85eb4872b",
+ "48ec6ec520f8e593d7b3f653eb15553de246723b81a6d0c3221aaa42a37420fba98a23796338dff5f845dce6d5a449be5ecc1887356619270461087e08d05fb60433a83d7bd00c002b09ea210b428965124b9b27d9105a71c826c1a2491cfd60e4cfa86c2da0c7100a8dc1c3f2f94b280d54e01e043acf0e966200d9fa8a41daf3b9382820786c75cadbb8841a1b2be5b6cbeb64878e4a231ae063a99b4e2308960ef0c8e2a16bb3545cc43bdf171493fb89a84f47e7973dc60cf75aeeca71e0a7ebe17d161d4fb9fe009941cc438f16a5bae6c99fcad08cac486eb2a48060b023d8730bf1d82fe60a2f036e6f52a5bff95f43bbe088933f",
+ "f4d84ed3e564c102600a795eaa9b1eaf4ad12f1a4deca1d042a0a2750ddf6201db03073d8bf553cb9dde48a1b0083827a609f7242b86584cc180964ae794b12ce55661e00e36a6ba4dbc389e6a5a85f1b45df9af7ead1b0a54db56e68639b9d438a91504e82c35d40c7bc7e048a53ac0b04accd0dadf4ac9884b0ca0e3cb5ba4336e3581be4c4760a553823ffa283a1120d4e145af56a59f2533903650f0b9e9ad9fe2e8a3c3c3dd03a1fcb709032c8835324839c735b0c051d0cbd8b5d867617c11023432e4bd275d3d0eb98a0b6cf58071a5b712922f2bc751ac7c2588c447444cde2f37a8ea5ec126425bf517e0d17c9e2999f52fee14b3",
+ "2ccea21bac9c2b70d3923309cbf2d7cb7abd1fcc8b8b002688870a80029c62397350c3c898194e5deea360bb963d26d485cb7963f8167586976ec0556950b2e86135f4a2800991ce8473bfd44a3c5e937a48b5e355ba5141bccf2131a83988d9d2a9e8e7635a956105b3512c05ef708139ced51d7a4e204c12d8a49a21e8dc6de2629a2fd092326885d9f218745fe09f6d91fb6afce250a30a63689534b6be1f26899ffa3767d835cf586aa47776700f94241bc999b1e3deefe188f37ff734f5f16ee6a00914323dc7b8a143c9137cdcc5cd08ae9566f04bb2941532674c97dff6ffa5ce3405ef8e5d27ec403114253dd6394c0167d72a0044c5",
+ "2b681c6398aee63bf862770341648bbcd31d7de7903c5903fe3d9469311320bb24d914f2af0cdca199c97214c7c679dc32a2800ba484a03c010ea6be3bb9f2c87e30a98b606050b8a3f297f12b8f92caaeceb3e844652115934874e0a1ab093a73d759b53f6a6c3096940dd22c2bb96ce6820a7b9c6d71a208de9892aa6a7209b0fff56a0cafea52b952cdd6f5752cff3309d448800b4e4c878aa595595b56b12b83fcd6ca89520c7da664e449d7b4438fc455888aad5de0fad9a06eed14afd3513b5ebbffe01775549b701181bd26370764f56eba52fdb24286ad1ac0f5418a7c429f7dfc7f3168437fa8eed7a2ed7c723a485e4c3ed14dea2e07",
+ "aadfd505a89f4aade2c3018258a7e039401b1fc6a7f3d87910dddbb880d372ec8a13c70d92245de5b8e5f9a285c33b99dc82fa2b22decee72b93a72211656ad7a52696c8e570f78be28c0e427a371dafde856e8d5ed24f83b0660b51e7fac05d93a8666dfde6def59af863f80f3e5f6801182c87422203df390dcb736b8f830052a8832eeeb0b4e27e732aaf793d166b5a3ec7745aeef3766937c2b75a276bddd145f6010c29d035e343e267cb2d828436876ec3a7ebe3b6347d4172f7a99d6821ce152e039e53deb33340b324c7f068ffb94b3cde35a8eaa12d15c3806a7ad0acec3e8c7078c1d32a28fd3eec9f32cb86e4c22166ff69e83785e851",
+ "1605b8cce529a9d6262fd4390d9e4ae5e14e0adc0ec89b028ef68dd0f373ea259aaa96f2967091dd0874c0105385e9e6da9ca68297c31afa44ef834535fb302ce5b4e49edacbbdf359fe1228a8172495b3e57014c27edd58b685110980056c50c398a64f4923f2d720b4df16d75cb36b4233660694182099c35028a972519c24764fc94e18e582b24deb3491535fc06b83837c7958522800e822201d694af0bd0aa3834e17d4b1ba36f470905ae5f8bbeeb6c4c8604d8af02baa347b07086d6989867ddd5e8e8ed7740c3469bfa2810519c55c6add1332c4c54ee9097961d6741cb12a09713a0d07645f784f42f5ad94b48b836b34263130b0483f15e3",
+ "ff9c6125b2f60bfd6c2427b279df070e430075096647599bdc68c531152c58e13858b82385d78c856092d6c74106e87ccf51ac7e673936332d9b223444eaa0e762ee258d8a733d3a515ec68ed73285e5ca183ae3278b4820b0ab2797feb1e7d8cc864df585dfb5ebe02a993325a9ad5e2d7d49d3132cf66013898351d044e0fe908ccdfeeebf651983601e3673a1f92d36510c0cc19b2e75856db8e4a41f92a51efa66d6cc22e414944c2c34a5a89ccde0be76f51410824e330d8e7c613194338c93732e8aea651fca18bcf1ac1824340c5553aff1e58d4ab8d7c8842b4712021e517cd6c140f6743c69c7bee05b10a8f24050a8caa4f96d1664909c5a06",
+ "6e85c2f8e1fdc3aaeb969da1258cb504bbf0070cd03d23b3fb5ee08feea5ee2e0ee1c71a5d0f4f701b351f4e4b4d74cb1e2ae6184814f77b62d2f08134b7236ebf6b67d8a6c9f01b4248b30667c555f5d8646dbfe291151b23c9c9857e33a4d5c847be29a5ee7b402e03bac02d1a4319acc0dd8f25e9c7a266f5e5c896cc11b5b238df96a0963ae806cb277abc515c298a3e61a3036b177acf87a56ca4478c4c6d0d468913de602ec891318bbaf52c97a77c35c5b7d164816cf24e4c4b0b5f45853882f716d61eb947a45ce2efa78f1c70a918512af1ad536cbe6148083385b34e207f5f690d7a954021e4b5f4258a385fd8a87809a481f34202af4caccb82",
+ "1e9b2c454e9de3a2d723d850331037dbf54133dbe27488ff757dd255833a27d8eb8a128ad12d0978b6884e25737086a704fb289aaaccf930d5b582ab4df1f55f0c429b6875edec3fe45464fa74164be056a55e243c4222c586bec5b18f39036aa903d98180f24f83d09a454dfa1e03a60e6a3ba4613e99c35f874d790174ee48a557f4f021ade4d1b278d7997ef094569b37b3db0505951e9ee8400adaea275c6db51b325ee730c69df97745b556ae41cd98741e28aa3a49544541eeb3da1b1e8fa4e8e9100d66dd0c7f5e2c271b1ecc077de79c462b9fe4c273543ecd82a5bea63c5acc01eca5fb780c7d7c8c9fe208ae8bd50cad1769693d92c6c8649d20d8",
+}
diff --git a/local_crypto_patch/contents/blake2b/blake2x.go b/local_crypto_patch/contents/blake2b/blake2x.go
new file mode 100644
index 0000000000..7692bb3460
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2b/register.go b/local_crypto_patch/contents/blake2b/register.go
new file mode 100644
index 0000000000..54e446e1d2
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2s/blake2s.go b/local_crypto_patch/contents/blake2s/blake2s.go
new file mode 100644
index 0000000000..c25d07d4f4
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2s/blake2s_386.go b/local_crypto_patch/contents/blake2s/blake2s_386.go
new file mode 100644
index 0000000000..97f629617e
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2s/blake2s_386.s b/local_crypto_patch/contents/blake2s/blake2s_386.s
new file mode 100644
index 0000000000..919c026541
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2s/blake2s_amd64.go b/local_crypto_patch/contents/blake2s/blake2s_amd64.go
new file mode 100644
index 0000000000..8a7310254e
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2s/blake2s_amd64.s b/local_crypto_patch/contents/blake2s/blake2s_amd64.s
new file mode 100644
index 0000000000..57d510fc08
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2s/blake2s_generic.go b/local_crypto_patch/contents/blake2s/blake2s_generic.go
new file mode 100644
index 0000000000..24a1ff22ad
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2s/blake2s_ref.go b/local_crypto_patch/contents/blake2s/blake2s_ref.go
new file mode 100644
index 0000000000..38ce8e283f
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blake2s/blake2s_test.go b/local_crypto_patch/contents/blake2s/blake2s_test.go
new file mode 100644
index 0000000000..cde79fb1cd
--- /dev/null
+++ b/local_crypto_patch/contents/blake2s/blake2s_test.go
@@ -0,0 +1,1050 @@
+// 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 (
+ "bytes"
+ "encoding"
+ "encoding/hex"
+ "fmt"
+ "testing"
+)
+
+func TestHashes(t *testing.T) {
+ defer func(sse2, ssse3, sse4 bool) {
+ useSSE2, useSSSE3, useSSE4 = sse2, ssse3, sse4
+ }(useSSE2, useSSSE3, useSSE4)
+
+ if useSSE4 {
+ t.Log("SSE4 version")
+ testHashes(t)
+ testHashes128(t)
+ useSSE4 = false
+ }
+ if useSSSE3 {
+ t.Log("SSSE3 version")
+ testHashes(t)
+ testHashes128(t)
+ useSSSE3 = false
+ }
+ if useSSE2 {
+ t.Log("SSE2 version")
+ testHashes(t)
+ testHashes128(t)
+ useSSE2 = false
+ }
+
+ t.Log("generic version")
+ testHashes(t)
+ testHashes128(t)
+}
+
+func TestHashes2X(t *testing.T) {
+ defer func(sse2, ssse3, sse4 bool) {
+ useSSE2, useSSSE3, useSSE4 = sse2, ssse3, sse4
+ }(useSSE2, useSSSE3, useSSE4)
+
+ if useSSE4 {
+ t.Log("SSE4 version")
+ testHashes2X(t)
+ useSSE4 = false
+ }
+ if useSSSE3 {
+ t.Log("SSSE3 version")
+ testHashes2X(t)
+ useSSSE3 = false
+ }
+ if useSSE2 {
+ t.Log("SSE2 version")
+ testHashes2X(t)
+ useSSE2 = false
+ }
+
+ t.Log("generic version")
+ testHashes2X(t)
+}
+
+func TestMarshal(t *testing.T) {
+ input := make([]byte, 255)
+ for i := range input {
+ input[i] = byte(i)
+ }
+ for i := 0; i < 256; i++ {
+ h, err := New256(nil)
+ if err != nil {
+ t.Fatalf("len(input)=%d: error from New256(nil): %v", i, err)
+ }
+ h2, err := New256(nil)
+ if err != nil {
+ t.Fatalf("len(input)=%d: error from New256(nil): %v", i, err)
+ }
+
+ h.Write(input[:i/2])
+ halfstate, err := h.(encoding.BinaryMarshaler).MarshalBinary()
+ if err != nil {
+ t.Fatalf("len(input)=%d: could not marshal: %v", i, err)
+ }
+ err = h2.(encoding.BinaryUnmarshaler).UnmarshalBinary(halfstate)
+ if err != nil {
+ t.Fatalf("len(input)=%d: could not unmarshal: %v", i, err)
+ }
+
+ h.Write(input[i/2 : i])
+ sum := h.Sum(nil)
+ h2.Write(input[i/2 : i])
+ sum2 := h2.Sum(nil)
+
+ if !bytes.Equal(sum, sum2) {
+ t.Fatalf("len(input)=%d: results do not match; sum = %v, sum2 = %v", i, sum, sum2)
+ }
+
+ h3, err := New256(nil)
+ if err != nil {
+ t.Fatalf("len(input)=%d: error from New256(nil): %v", i, err)
+ }
+ h3.Write(input[:i])
+ sum3 := h3.Sum(nil)
+ if !bytes.Equal(sum, sum3) {
+ t.Fatalf("len(input)=%d: sum = %v, want %v", i, sum, sum3)
+ }
+ }
+}
+
+func testHashes(t *testing.T) {
+ key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
+
+ input := make([]byte, 255)
+ for i := range input {
+ input[i] = byte(i)
+ }
+
+ for i, expectedHex := range hashes {
+ h, err := New256(key)
+ if err != nil {
+ t.Fatalf("#%d: error from New256: %v", i, err)
+ }
+
+ h.Write(input[:i])
+ sum := h.Sum(nil)
+
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+
+ h.Reset()
+ for j := 0; j < i; j++ {
+ h.Write(input[j : j+1])
+ }
+
+ sum = h.Sum(sum[:0])
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+ }
+}
+
+func testHashes128(t *testing.T) {
+ key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
+
+ input := make([]byte, 255)
+ for i := range input {
+ input[i] = byte(i)
+ }
+
+ for i, expectedHex := range hashes128 {
+ h, err := New128(key)
+ if err != nil {
+ t.Fatalf("#%d: error from New128: %v", i, err)
+ }
+
+ h.Write(input[:i])
+ sum := h.Sum(nil)
+
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+
+ h.Reset()
+ for j := 0; j < i; j++ {
+ h.Write(input[j : j+1])
+ }
+
+ sum = h.Sum(sum[:0])
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+ }
+}
+
+func testHashes2X(t *testing.T) {
+ key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
+
+ input := make([]byte, 256)
+ for i := range input {
+ input[i] = byte(i)
+ }
+
+ for i, expectedHex := range hashes2X {
+ length := uint16(len(expectedHex) / 2)
+ sum := make([]byte, int(length))
+
+ h, err := NewXOF(length, key)
+ if err != nil {
+ t.Fatalf("#%d: error from NewXOF: %v", i, err)
+ }
+
+ if _, err := h.Write(input); err != nil {
+ t.Fatalf("#%d (single write): error from Write: %v", i, err)
+ }
+ if _, err := h.Read(sum); err != nil {
+ t.Fatalf("#%d (single write): error from Read: %v", i, err)
+ }
+
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+
+ h.Reset()
+ for j := 0; j < len(input); j++ {
+ h.Write(input[j : j+1])
+ }
+ for j := 0; j < len(sum); j++ {
+ h = h.Clone()
+ if _, err := h.Read(sum[j : j+1]); err != nil {
+ t.Fatalf("#%d (byte-by-byte) - Read %d: error from Read: %v", i, j, err)
+ }
+ }
+ if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex {
+ t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex)
+ }
+ }
+
+ h, err := NewXOF(OutputLengthUnknown, key)
+ if err != nil {
+ t.Fatalf("#unknown length: error from NewXOF: %v", err)
+ }
+ if _, err := h.Write(input); err != nil {
+ t.Fatalf("#unknown length: error from Write: %v", err)
+ }
+
+ var result [64]byte
+ if n, err := h.Read(result[:]); err != nil {
+ t.Fatalf("#unknown length: error from Read: %v", err)
+ } else if n != len(result) {
+ t.Fatalf("#unknown length: Read returned %d bytes, want %d", n, len(result))
+ }
+
+ const expected = "2a9a6977d915a2c4dd07dbcafe1918bf1682e56d9c8e567ecd19bfd7cd93528833c764d12b34a5e2a219c9fd463dab45e972c5574d73f45de5b2e23af72530d8"
+ if fmt.Sprintf("%x", result) != expected {
+ t.Fatalf("#unknown length: bad result %x, wanted %s", result, expected)
+ }
+}
+
+// Benchmarks
+
+func benchmarkSum(b *testing.B, size int) {
+ data := make([]byte, size)
+ b.SetBytes(int64(size))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Sum256(data)
+ }
+}
+
+func benchmarkWrite(b *testing.B, size int) {
+ data := make([]byte, size)
+ h, _ := New256(nil)
+ b.SetBytes(int64(size))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ h.Write(data)
+ }
+}
+
+func BenchmarkWrite64(b *testing.B) { benchmarkWrite(b, 64) }
+func BenchmarkWrite1K(b *testing.B) { benchmarkWrite(b, 1024) }
+
+func BenchmarkSum64(b *testing.B) { benchmarkSum(b, 64) }
+func BenchmarkSum1K(b *testing.B) { benchmarkSum(b, 1024) }
+
+// hashes is taken from https://blake2.net/blake2s-test.txt
+var hashes = []string{
+ "48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49",
+ "40d15fee7c328830166ac3f918650f807e7e01e177258cdc0a39b11f598066f1",
+ "6bb71300644cd3991b26ccd4d274acd1adeab8b1d7914546c1198bbe9fc9d803",
+ "1d220dbe2ee134661fdf6d9e74b41704710556f2f6e5a091b227697445dbea6b",
+ "f6c3fbadb4cc687a0064a5be6e791bec63b868ad62fba61b3757ef9ca52e05b2",
+ "49c1f21188dfd769aea0e911dd6b41f14dab109d2b85977aa3088b5c707e8598",
+ "fdd8993dcd43f696d44f3cea0ff35345234ec8ee083eb3cada017c7f78c17143",
+ "e6c8125637438d0905b749f46560ac89fd471cf8692e28fab982f73f019b83a9",
+ "19fc8ca6979d60e6edd3b4541e2f967ced740df6ec1eaebbfe813832e96b2974",
+ "a6ad777ce881b52bb5a4421ab6cdd2dfba13e963652d4d6d122aee46548c14a7",
+ "f5c4b2ba1a00781b13aba0425242c69cb1552f3f71a9a3bb22b4a6b4277b46dd",
+ "e33c4c9bd0cc7e45c80e65c77fa5997fec7002738541509e68a9423891e822a3",
+ "fba16169b2c3ee105be6e1e650e5cbf40746b6753d036ab55179014ad7ef6651",
+ "f5c4bec6d62fc608bf41cc115f16d61c7efd3ff6c65692bbe0afffb1fede7475",
+ "a4862e76db847f05ba17ede5da4e7f91b5925cf1ad4ba12732c3995742a5cd6e",
+ "65f4b860cd15b38ef814a1a804314a55be953caa65fd758ad989ff34a41c1eea",
+ "19ba234f0a4f38637d1839f9d9f76ad91c8522307143c97d5f93f69274cec9a7",
+ "1a67186ca4a5cb8e65fca0e2ecbc5ddc14ae381bb8bffeb9e0a103449e3ef03c",
+ "afbea317b5a2e89c0bd90ccf5d7fd0ed57fe585e4be3271b0a6bf0f5786b0f26",
+ "f1b01558ce541262f5ec34299d6fb4090009e3434be2f49105cf46af4d2d4124",
+ "13a0a0c86335635eaa74ca2d5d488c797bbb4f47dc07105015ed6a1f3309efce",
+ "1580afeebebb346f94d59fe62da0b79237ead7b1491f5667a90e45edf6ca8b03",
+ "20be1a875b38c573dd7faaa0de489d655c11efb6a552698e07a2d331b5f655c3",
+ "be1fe3c4c04018c54c4a0f6b9a2ed3c53abe3a9f76b4d26de56fc9ae95059a99",
+ "e3e3ace537eb3edd8463d9ad3582e13cf86533ffde43d668dd2e93bbdbd7195a",
+ "110c50c0bf2c6e7aeb7e435d92d132ab6655168e78a2decdec3330777684d9c1",
+ "e9ba8f505c9c80c08666a701f3367e6cc665f34b22e73c3c0417eb1c2206082f",
+ "26cd66fca02379c76df12317052bcafd6cd8c3a7b890d805f36c49989782433a",
+ "213f3596d6e3a5d0e9932cd2159146015e2abc949f4729ee2632fe1edb78d337",
+ "1015d70108e03be1c702fe97253607d14aee591f2413ea6787427b6459ff219a",
+ "3ca989de10cfe609909472c8d35610805b2f977734cf652cc64b3bfc882d5d89",
+ "b6156f72d380ee9ea6acd190464f2307a5c179ef01fd71f99f2d0f7a57360aea",
+ "c03bc642b20959cbe133a0303e0c1abff3e31ec8e1a328ec8565c36decff5265",
+ "2c3e08176f760c6264c3a2cd66fec6c3d78de43fc192457b2a4a660a1e0eb22b",
+ "f738c02f3c1b190c512b1a32deabf353728e0e9ab034490e3c3409946a97aeec",
+ "8b1880df301cc963418811088964839287ff7fe31c49ea6ebd9e48bdeee497c5",
+ "1e75cb21c60989020375f1a7a242839f0b0b68973a4c2a05cf7555ed5aaec4c1",
+ "62bf8a9c32a5bccf290b6c474d75b2a2a4093f1a9e27139433a8f2b3bce7b8d7",
+ "166c8350d3173b5e702b783dfd33c66ee0432742e9b92b997fd23c60dc6756ca",
+ "044a14d822a90cacf2f5a101428adc8f4109386ccb158bf905c8618b8ee24ec3",
+ "387d397ea43a994be84d2d544afbe481a2000f55252696bba2c50c8ebd101347",
+ "56f8ccf1f86409b46ce36166ae9165138441577589db08cbc5f66ca29743b9fd",
+ "9706c092b04d91f53dff91fa37b7493d28b576b5d710469df79401662236fc03",
+ "877968686c068ce2f7e2adcff68bf8748edf3cf862cfb4d3947a3106958054e3",
+ "8817e5719879acf7024787eccdb271035566cfa333e049407c0178ccc57a5b9f",
+ "8938249e4b50cadaccdf5b18621326cbb15253e33a20f5636e995d72478de472",
+ "f164abba4963a44d107257e3232d90aca5e66a1408248c51741e991db5227756",
+ "d05563e2b1cba0c4a2a1e8bde3a1a0d9f5b40c85a070d6f5fb21066ead5d0601",
+ "03fbb16384f0a3866f4c3117877666efbf124597564b293d4aab0d269fabddfa",
+ "5fa8486ac0e52964d1881bbe338eb54be2f719549224892057b4da04ba8b3475",
+ "cdfabcee46911111236a31708b2539d71fc211d9b09c0d8530a11e1dbf6eed01",
+ "4f82de03b9504793b82a07a0bdcdff314d759e7b62d26b784946b0d36f916f52",
+ "259ec7f173bcc76a0994c967b4f5f024c56057fb79c965c4fae41875f06a0e4c",
+ "193cc8e7c3e08bb30f5437aa27ade1f142369b246a675b2383e6da9b49a9809e",
+ "5c10896f0e2856b2a2eee0fe4a2c1633565d18f0e93e1fab26c373e8f829654d",
+ "f16012d93f28851a1eb989f5d0b43f3f39ca73c9a62d5181bff237536bd348c3",
+ "2966b3cfae1e44ea996dc5d686cf25fa053fb6f67201b9e46eade85d0ad6b806",
+ "ddb8782485e900bc60bcf4c33a6fd585680cc683d516efa03eb9985fad8715fb",
+ "4c4d6e71aea05786413148fc7a786b0ecaf582cff1209f5a809fba8504ce662c",
+ "fb4c5e86d7b2229b99b8ba6d94c247ef964aa3a2bae8edc77569f28dbbff2d4e",
+ "e94f526de9019633ecd54ac6120f23958d7718f1e7717bf329211a4faeed4e6d",
+ "cbd6660a10db3f23f7a03d4b9d4044c7932b2801ac89d60bc9eb92d65a46c2a0",
+ "8818bbd3db4dc123b25cbba5f54c2bc4b3fcf9bf7d7a7709f4ae588b267c4ece",
+ "c65382513f07460da39833cb666c5ed82e61b9e998f4b0c4287cee56c3cc9bcd",
+ "8975b0577fd35566d750b362b0897a26c399136df07bababbde6203ff2954ed4",
+ "21fe0ceb0052be7fb0f004187cacd7de67fa6eb0938d927677f2398c132317a8",
+ "2ef73f3c26f12d93889f3c78b6a66c1d52b649dc9e856e2c172ea7c58ac2b5e3",
+ "388a3cd56d73867abb5f8401492b6e2681eb69851e767fd84210a56076fb3dd3",
+ "af533e022fc9439e4e3cb838ecd18692232adf6fe9839526d3c3dd1b71910b1a",
+ "751c09d41a9343882a81cd13ee40818d12eb44c6c7f40df16e4aea8fab91972a",
+ "5b73ddb68d9d2b0aa265a07988d6b88ae9aac582af83032f8a9b21a2e1b7bf18",
+ "3da29126c7c5d7f43e64242a79feaa4ef3459cdeccc898ed59a97f6ec93b9dab",
+ "566dc920293da5cb4fe0aa8abda8bbf56f552313bff19046641e3615c1e3ed3f",
+ "4115bea02f73f97f629e5c5590720c01e7e449ae2a6697d4d2783321303692f9",
+ "4ce08f4762468a7670012164878d68340c52a35e66c1884d5c864889abc96677",
+ "81ea0b7804124e0c22ea5fc71104a2afcb52a1fa816f3ecb7dcb5d9dea1786d0",
+ "fe362733b05f6bedaf9379d7f7936ede209b1f8323c3922549d9e73681b5db7b",
+ "eff37d30dfd20359be4e73fdf40d27734b3df90a97a55ed745297294ca85d09f",
+ "172ffc67153d12e0ca76a8b6cd5d4731885b39ce0cac93a8972a18006c8b8baf",
+ "c47957f1cc88e83ef9445839709a480a036bed5f88ac0fcc8e1e703ffaac132c",
+ "30f3548370cfdceda5c37b569b6175e799eef1a62aaa943245ae7669c227a7b5",
+ "c95dcb3cf1f27d0eef2f25d2413870904a877c4a56c2de1e83e2bc2ae2e46821",
+ "d5d0b5d705434cd46b185749f66bfb5836dcdf6ee549a2b7a4aee7f58007caaf",
+ "bbc124a712f15d07c300e05b668389a439c91777f721f8320c1c9078066d2c7e",
+ "a451b48c35a6c7854cfaae60262e76990816382ac0667e5a5c9e1b46c4342ddf",
+ "b0d150fb55e778d01147f0b5d89d99ecb20ff07e5e6760d6b645eb5b654c622b",
+ "34f737c0ab219951eee89a9f8dac299c9d4c38f33fa494c5c6eefc92b6db08bc",
+ "1a62cc3a00800dcbd99891080c1e098458193a8cc9f970ea99fbeff00318c289",
+ "cfce55ebafc840d7ae48281c7fd57ec8b482d4b704437495495ac414cf4a374b",
+ "6746facf71146d999dabd05d093ae586648d1ee28e72617b99d0f0086e1e45bf",
+ "571ced283b3f23b4e750bf12a2caf1781847bd890e43603cdc5976102b7bb11b",
+ "cfcb765b048e35022c5d089d26e85a36b005a2b80493d03a144e09f409b6afd1",
+ "4050c7a27705bb27f42089b299f3cbe5054ead68727e8ef9318ce6f25cd6f31d",
+ "184070bd5d265fbdc142cd1c5cd0d7e414e70369a266d627c8fba84fa5e84c34",
+ "9edda9a4443902a9588c0d0ccc62b930218479a6841e6fe7d43003f04b1fd643",
+ "e412feef7908324a6da1841629f35d3d358642019310ec57c614836b63d30763",
+ "1a2b8edff3f9acc1554fcbae3cf1d6298c6462e22e5eb0259684f835012bd13f",
+ "288c4ad9b9409762ea07c24a41f04f69a7d74bee2d95435374bde946d7241c7b",
+ "805691bb286748cfb591d3aebe7e6f4e4dc6e2808c65143cc004e4eb6fd09d43",
+ "d4ac8d3a0afc6cfa7b460ae3001baeb36dadb37da07d2e8ac91822df348aed3d",
+ "c376617014d20158bced3d3ba552b6eccf84e62aa3eb650e90029c84d13eea69",
+ "c41f09f43cecae7293d6007ca0a357087d5ae59be500c1cd5b289ee810c7b082",
+ "03d1ced1fba5c39155c44b7765cb760c78708dcfc80b0bd8ade3a56da8830b29",
+ "09bde6f152218dc92c41d7f45387e63e5869d807ec70b821405dbd884b7fcf4b",
+ "71c9036e18179b90b37d39e9f05eb89cc5fc341fd7c477d0d7493285faca08a4",
+ "5916833ebb05cd919ca7fe83b692d3205bef72392b2cf6bb0a6d43f994f95f11",
+ "f63aab3ec641b3b024964c2b437c04f6043c4c7e0279239995401958f86bbe54",
+ "f172b180bfb09740493120b6326cbdc561e477def9bbcfd28cc8c1c5e3379a31",
+ "cb9b89cc18381dd9141ade588654d4e6a231d5bf49d4d59ac27d869cbe100cf3",
+ "7bd8815046fdd810a923e1984aaebdcdf84d87c8992d68b5eeb460f93eb3c8d7",
+ "607be66862fd08ee5b19facac09dfdbcd40c312101d66e6ebd2b841f1b9a9325",
+ "9fe03bbe69ab1834f5219b0da88a08b30a66c5913f0151963c360560db0387b3",
+ "90a83585717b75f0e9b725e055eeeeb9e7a028ea7e6cbc07b20917ec0363e38c",
+ "336ea0530f4a7469126e0218587ebbde3358a0b31c29d200f7dc7eb15c6aadd8",
+ "a79e76dc0abca4396f0747cd7b748df913007626b1d659da0c1f78b9303d01a3",
+ "44e78a773756e0951519504d7038d28d0213a37e0ce375371757bc996311e3b8",
+ "77ac012a3f754dcfeab5eb996be9cd2d1f96111b6e49f3994df181f28569d825",
+ "ce5a10db6fccdaf140aaa4ded6250a9c06e9222bc9f9f3658a4aff935f2b9f3a",
+ "ecc203a7fe2be4abd55bb53e6e673572e0078da8cd375ef430cc97f9f80083af",
+ "14a5186de9d7a18b0412b8563e51cc5433840b4a129a8ff963b33a3c4afe8ebb",
+ "13f8ef95cb86e6a638931c8e107673eb76ba10d7c2cd70b9d9920bbeed929409",
+ "0b338f4ee12f2dfcb78713377941e0b0632152581d1332516e4a2cab1942cca4",
+ "eaab0ec37b3b8ab796e9f57238de14a264a076f3887d86e29bb5906db5a00e02",
+ "23cb68b8c0e6dc26dc27766ddc0a13a99438fd55617aa4095d8f969720c872df",
+ "091d8ee30d6f2968d46b687dd65292665742de0bb83dcc0004c72ce10007a549",
+ "7f507abc6d19ba00c065a876ec5657868882d18a221bc46c7a6912541f5bc7ba",
+ "a0607c24e14e8c223db0d70b4d30ee88014d603f437e9e02aa7dafa3cdfbad94",
+ "ddbfea75cc467882eb3483ce5e2e756a4f4701b76b445519e89f22d60fa86e06",
+ "0c311f38c35a4fb90d651c289d486856cd1413df9b0677f53ece2cd9e477c60a",
+ "46a73a8dd3e70f59d3942c01df599def783c9da82fd83222cd662b53dce7dbdf",
+ "ad038ff9b14de84a801e4e621ce5df029dd93520d0c2fa38bff176a8b1d1698c",
+ "ab70c5dfbd1ea817fed0cd067293abf319e5d7901c2141d5d99b23f03a38e748",
+ "1fffda67932b73c8ecaf009a3491a026953babfe1f663b0697c3c4ae8b2e7dcb",
+ "b0d2cc19472dd57f2b17efc03c8d58c2283dbb19da572f7755855aa9794317a0",
+ "a0d19a6ee33979c325510e276622df41f71583d07501b87071129a0ad94732a5",
+ "724642a7032d1062b89e52bea34b75df7d8fe772d9fe3c93ddf3c4545ab5a99b",
+ "ade5eaa7e61f672d587ea03dae7d7b55229c01d06bc0a5701436cbd18366a626",
+ "013b31ebd228fcdda51fabb03bb02d60ac20ca215aafa83bdd855e3755a35f0b",
+ "332ed40bb10dde3c954a75d7b8999d4b26a1c063c1dc6e32c1d91bab7bbb7d16",
+ "c7a197b3a05b566bcc9facd20e441d6f6c2860ac9651cd51d6b9d2cdeeea0390",
+ "bd9cf64ea8953c037108e6f654914f3958b68e29c16700dc184d94a21708ff60",
+ "8835b0ac021151df716474ce27ce4d3c15f0b2dab48003cf3f3efd0945106b9a",
+ "3bfefa3301aa55c080190cffda8eae51d9af488b4c1f24c3d9a75242fd8ea01d",
+ "08284d14993cd47d53ebaecf0df0478cc182c89c00e1859c84851686ddf2c1b7",
+ "1ed7ef9f04c2ac8db6a864db131087f27065098e69c3fe78718d9b947f4a39d0",
+ "c161f2dcd57e9c1439b31a9dd43d8f3d7dd8f0eb7cfac6fb25a0f28e306f0661",
+ "c01969ad34c52caf3dc4d80d19735c29731ac6e7a92085ab9250c48dea48a3fc",
+ "1720b3655619d2a52b3521ae0e49e345cb3389ebd6208acaf9f13fdacca8be49",
+ "756288361c83e24c617cf95c905b22d017cdc86f0bf1d658f4756c7379873b7f",
+ "e7d0eda3452693b752abcda1b55e276f82698f5f1605403eff830bea0071a394",
+ "2c82ecaa6b84803e044af63118afe544687cb6e6c7df49ed762dfd7c8693a1bc",
+ "6136cbf4b441056fa1e2722498125d6ded45e17b52143959c7f4d4e395218ac2",
+ "721d3245aafef27f6a624f47954b6c255079526ffa25e9ff77e5dcff473b1597",
+ "9dd2fbd8cef16c353c0ac21191d509eb28dd9e3e0d8cea5d26ca839393851c3a",
+ "b2394ceacdebf21bf9df2ced98e58f1c3a4bbbff660dd900f62202d6785cc46e",
+ "57089f222749ad7871765f062b114f43ba20ec56422a8b1e3f87192c0ea718c6",
+ "e49a9459961cd33cdf4aae1b1078a5dea7c040e0fea340c93a724872fc4af806",
+ "ede67f720effd2ca9c88994152d0201dee6b0a2d2c077aca6dae29f73f8b6309",
+ "e0f434bf22e3088039c21f719ffc67f0f2cb5e98a7a0194c76e96bf4e8e17e61",
+ "277c04e2853484a4eba910ad336d01b477b67cc200c59f3c8d77eef8494f29cd",
+ "156d5747d0c99c7f27097d7b7e002b2e185cb72d8dd7eb424a0321528161219f",
+ "20ddd1ed9b1ca803946d64a83ae4659da67fba7a1a3eddb1e103c0f5e03e3a2c",
+ "f0af604d3dabbf9a0f2a7d3dda6bd38bba72c6d09be494fcef713ff10189b6e6",
+ "9802bb87def4cc10c4a5fd49aa58dfe2f3fddb46b4708814ead81d23ba95139b",
+ "4f8ce1e51d2fe7f24043a904d898ebfc91975418753413aa099b795ecb35cedb",
+ "bddc6514d7ee6ace0a4ac1d0e068112288cbcf560454642705630177cba608bd",
+ "d635994f6291517b0281ffdd496afa862712e5b3c4e52e4cd5fdae8c0e72fb08",
+ "878d9ca600cf87e769cc305c1b35255186615a73a0da613b5f1c98dbf81283ea",
+ "a64ebe5dc185de9fdde7607b6998702eb23456184957307d2fa72e87a47702d6",
+ "ce50eab7b5eb52bdc9ad8e5a480ab780ca9320e44360b1fe37e03f2f7ad7de01",
+ "eeddb7c0db6e30abe66d79e327511e61fcebbc29f159b40a86b046ecf0513823",
+ "787fc93440c1ec96b5ad01c16cf77916a1405f9426356ec921d8dff3ea63b7e0",
+ "7f0d5eab47eefda696c0bf0fbf86ab216fce461e9303aba6ac374120e890e8df",
+ "b68004b42f14ad029f4c2e03b1d5eb76d57160e26476d21131bef20ada7d27f4",
+ "b0c4eb18ae250b51a41382ead92d0dc7455f9379fc9884428e4770608db0faec",
+ "f92b7a870c059f4d46464c824ec96355140bdce681322cc3a992ff103e3fea52",
+ "5364312614813398cc525d4c4e146edeb371265fba19133a2c3d2159298a1742",
+ "f6620e68d37fb2af5000fc28e23b832297ecd8bce99e8be4d04e85309e3d3374",
+ "5316a27969d7fe04ff27b283961bffc3bf5dfb32fb6a89d101c6c3b1937c2871",
+ "81d1664fdf3cb33c24eebac0bd64244b77c4abea90bbe8b5ee0b2aafcf2d6a53",
+ "345782f295b0880352e924a0467b5fbc3e8f3bfbc3c7e48b67091fb5e80a9442",
+ "794111ea6cd65e311f74ee41d476cb632ce1e4b051dc1d9e9d061a19e1d0bb49",
+ "2a85daf6138816b99bf8d08ba2114b7ab07975a78420c1a3b06a777c22dd8bcb",
+ "89b0d5f289ec16401a069a960d0b093e625da3cf41ee29b59b930c5820145455",
+ "d0fdcb543943fc27d20864f52181471b942cc77ca675bcb30df31d358ef7b1eb",
+ "b17ea8d77063c709d4dc6b879413c343e3790e9e62ca85b7900b086f6b75c672",
+ "e71a3e2c274db842d92114f217e2c0eac8b45093fdfd9df4ca7162394862d501",
+ "c0476759ab7aa333234f6b44f5fd858390ec23694c622cb986e769c78edd733e",
+ "9ab8eabb1416434d85391341d56993c55458167d4418b19a0f2ad8b79a83a75b",
+ "7992d0bbb15e23826f443e00505d68d3ed7372995a5c3e498654102fbcd0964e",
+ "c021b30085151435df33b007ccecc69df1269f39ba25092bed59d932ac0fdc28",
+ "91a25ec0ec0d9a567f89c4bfe1a65a0e432d07064b4190e27dfb81901fd3139b",
+ "5950d39a23e1545f301270aa1a12f2e6c453776e4d6355de425cc153f9818867",
+ "d79f14720c610af179a3765d4b7c0968f977962dbf655b521272b6f1e194488e",
+ "e9531bfc8b02995aeaa75ba27031fadbcbf4a0dab8961d9296cd7e84d25d6006",
+ "34e9c26a01d7f16181b454a9d1623c233cb99d31c694656e9413aca3e918692f",
+ "d9d7422f437bd439ddd4d883dae2a08350173414be78155133fff1964c3d7972",
+ "4aee0c7aaf075414ff1793ead7eaca601775c615dbd60b640b0a9f0ce505d435",
+ "6bfdd15459c83b99f096bfb49ee87b063d69c1974c6928acfcfb4099f8c4ef67",
+ "9fd1c408fd75c336193a2a14d94f6af5adf050b80387b4b010fb29f4cc72707c",
+ "13c88480a5d00d6c8c7ad2110d76a82d9b70f4fa6696d4e5dd42a066dcaf9920",
+ "820e725ee25fe8fd3a8d5abe4c46c3ba889de6fa9191aa22ba67d5705421542b",
+ "32d93a0eb02f42fbbcaf2bad0085b282e46046a4df7ad10657c9d6476375b93e",
+ "adc5187905b1669cd8ec9c721e1953786b9d89a9bae30780f1e1eab24a00523c",
+ "e90756ff7f9ad810b239a10ced2cf9b2284354c1f8c7e0accc2461dc796d6e89",
+ "1251f76e56978481875359801db589a0b22f86d8d634dc04506f322ed78f17e8",
+ "3afa899fd980e73ecb7f4d8b8f291dc9af796bc65d27f974c6f193c9191a09fd",
+ "aa305be26e5deddc3c1010cbc213f95f051c785c5b431e6a7cd048f161787528",
+ "8ea1884ff32e9d10f039b407d0d44e7e670abd884aeee0fb757ae94eaa97373d",
+ "d482b2155d4dec6b4736a1f1617b53aaa37310277d3fef0c37ad41768fc235b4",
+ "4d413971387e7a8898a8dc2a27500778539ea214a2dfe9b3d7e8ebdce5cf3db3",
+ "696e5d46e6c57e8796e4735d08916e0b7929b3cf298c296d22e9d3019653371c",
+ "1f5647c1d3b088228885865c8940908bf40d1a8272821973b160008e7a3ce2eb",
+ "b6e76c330f021a5bda65875010b0edf09126c0f510ea849048192003aef4c61c",
+ "3cd952a0beada41abb424ce47f94b42be64e1ffb0fd0782276807946d0d0bc55",
+ "98d92677439b41b7bb513312afb92bcc8ee968b2e3b238cecb9b0f34c9bb63d0",
+ "ecbca2cf08ae57d517ad16158a32bfa7dc0382eaeda128e91886734c24a0b29d",
+ "942cc7c0b52e2b16a4b89fa4fc7e0bf609e29a08c1a8543452b77c7bfd11bb28",
+ "8a065d8b61a0dffb170d5627735a76b0e9506037808cba16c345007c9f79cf8f",
+ "1b9fa19714659c78ff413871849215361029ac802b1cbcd54e408bd87287f81f",
+ "8dab071bcd6c7292a9ef727b4ae0d86713301da8618d9a48adce55f303a869a1",
+ "8253e3e7c7b684b9cb2beb014ce330ff3d99d17abbdbabe4f4d674ded53ffc6b",
+ "f195f321e9e3d6bd7d074504dd2ab0e6241f92e784b1aa271ff648b1cab6d7f6",
+ "27e4cc72090f241266476a7c09495f2db153d5bcbd761903ef79275ec56b2ed8",
+ "899c2405788e25b99a1846355e646d77cf400083415f7dc5afe69d6e17c00023",
+ "a59b78c4905744076bfee894de707d4f120b5c6893ea0400297d0bb834727632",
+ "59dc78b105649707a2bb4419c48f005400d3973de3736610230435b10424b24f",
+ "c0149d1d7e7a6353a6d906efe728f2f329fe14a4149a3ea77609bc42b975ddfa",
+ "a32f241474a6c16932e9243be0cf09bcdc7e0ca0e7a6a1b9b1a0f01e41502377",
+ "b239b2e4f81841361c1339f68e2c359f929af9ad9f34e01aab4631ad6d5500b0",
+ "85fb419c7002a3e0b4b6ea093b4c1ac6936645b65dac5ac15a8528b7b94c1754",
+ "9619720625f190b93a3fad186ab314189633c0d3a01e6f9bc8c4a8f82f383dbf",
+ "7d620d90fe69fa469a6538388970a1aa09bb48a2d59b347b97e8ce71f48c7f46",
+ "294383568596fb37c75bbacd979c5ff6f20a556bf8879cc72924855df9b8240e",
+ "16b18ab314359c2b833c1c6986d48c55a9fc97cde9a3c1f10a3177140f73f738",
+ "8cbbdd14bc33f04cf45813e4a153a273d36adad5ce71f499eeb87fb8ac63b729",
+ "69c9a498db174ecaefcc5a3ac9fdedf0f813a5bec727f1e775babdec7718816e",
+ "b462c3be40448f1d4f80626254e535b08bc9cdcff599a768578d4b2881a8e3f0",
+ "553e9d9c5f360ac0b74a7d44e5a391dad4ced03e0c24183b7e8ecabdf1715a64",
+ "7a7c55a56fa9ae51e655e01975d8a6ff4ae9e4b486fcbe4eac044588f245ebea",
+ "2afdf3c82abc4867f5de111286c2b3be7d6e48657ba923cfbf101a6dfcf9db9a",
+ "41037d2edcdce0c49b7fb4a6aa0999ca66976c7483afe631d4eda283144f6dfc",
+ "c4466f8497ca2eeb4583a0b08e9d9ac74395709fda109d24f2e4462196779c5d",
+ "75f609338aa67d969a2ae2a2362b2da9d77c695dfd1df7224a6901db932c3364",
+ "68606ceb989d5488fc7cf649f3d7c272ef055da1a93faecd55fe06f6967098ca",
+ "44346bdeb7e052f6255048f0d9b42c425bab9c3dd24168212c3ecf1ebf34e6ae",
+ "8e9cf6e1f366471f2ac7d2ee9b5e6266fda71f8f2e4109f2237ed5f8813fc718",
+ "84bbeb8406d250951f8c1b3e86a7c010082921833dfd9555a2f909b1086eb4b8",
+ "ee666f3eef0f7e2a9c222958c97eaf35f51ced393d714485ab09a069340fdf88",
+ "c153d34a65c47b4a62c5cacf24010975d0356b2f32c8f5da530d338816ad5de6",
+ "9fc5450109e1b779f6c7ae79d56c27635c8dd426c5a9d54e2578db989b8c3b4e",
+ "d12bf3732ef4af5c22fa90356af8fc50fcb40f8f2ea5c8594737a3b3d5abdbd7",
+ "11030b9289bba5af65260672ab6fee88b87420acef4a1789a2073b7ec2f2a09e",
+ "69cb192b8444005c8c0ceb12c846860768188cda0aec27a9c8a55cdee2123632",
+ "db444c15597b5f1a03d1f9edd16e4a9f43a667cc275175dfa2b704e3bb1a9b83",
+ "3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd",
+}
+
+var hashes128 = []string{
+ "9536f9b267655743dee97b8a670f9f53",
+ "13bacfb85b48a1223c595f8c1e7e82cb",
+ "d47a9b1645e2feae501cd5fe44ce6333",
+ "1e2a79436a7796a3e9826bfedf07659f",
+ "7640360ed3c4f3054dba79a21dda66b7",
+ "d1207ac2bf5ac84fc9ef016da5a46a86",
+ "3123987871e59305ece3125abfc0099a",
+ "cf9e072ad522f2cda2d825218086731c",
+ "95d22870392efe2846b12b6e8e84efbb",
+ "7d63c30e2d51333f245601b038c0b93b",
+ "ed608b98e13976bdf4bedc63fa35e443",
+ "ed704b5cd1abf8e0dd67a6ac667a3fa5",
+ "77dc70109827dc74c70fd26cba379ae5",
+ "d2bf34508b07825ee934f33958f4560e",
+ "a340baa7b8a93a6e658adef42e78eeb7",
+ "b85c5ceaecbe9a251eac76f6932ba395",
+ "246519722001f6e8e97a2183f5985e53",
+ "5bce5aa0b7c6cac2ecf6406183cd779a",
+ "13408f1647c02f6efd0047ad8344f695",
+ "a63970f196760aa36cb965ab62f0e0fa",
+ "bc26f48421dd99fd45e15e736d3e7dac",
+ "4c6f70f9e3237cde918afb52d26f1823",
+ "45ed610cfbc37db80c4bf0eef14ae8d6",
+ "87c4c150705ea5078209ec008200539c",
+ "54de21f5e0e6f2afe04daeb822b6931e",
+ "9732a04e505064e19de3d542e7e71631",
+ "d2bd27e95531d6957eef511c4ba64ad4",
+ "7a36c9f70dcc7c3063b547101a5f6c35",
+ "322007d1a44c4257bc7903b183305529",
+ "dbcc9a09f412290ca2e0d53dfd142ddb",
+ "df12ed43b8e53a56db20e0f83764002c",
+ "d114cc11e7d5b33a360c45f18d4c7c6e",
+ "c43b5e836af88620a8a71b1652cb8640",
+ "9491c653e8867ed73c1b4ac6b5a9bb4d",
+ "06d0e988df94ada6c6f9f36f588ab7c5",
+ "561efad2480e93262c8eeaa3677615c4",
+ "ba8ffc702e5adc93503045eca8702312",
+ "5782be6ccdc78c8425285e85de8ccdc6",
+ "aa1c4393e4c07b53ea6e2b5b1e970771",
+ "42a229dc50e52271c51e8666023ebc1e",
+ "53706110e919f84de7f8d6c7f0e7b831",
+ "fc5ac8ee39cc1dd1424391323e2901bd",
+ "bed27b62ff66cac2fbb68193c727106a",
+ "cd5e689b96d0b9ea7e08dac36f7b211e",
+ "0b4c7f604eba058d18e322c6e1baf173",
+ "eb838227fdfad09a27f0f8413120675d",
+ "3149cf9d19a7fd529e6154a8b4c3b3ad",
+ "ca1e20126df930fd5fb7afe4422191e5",
+ "b23398f910599f3c09b6549fa81bcb46",
+ "27fb17c11b34fa5d8b5afe5ee3321ead",
+ "0f665f5f04cf2d46b7fead1a1f328158",
+ "8f068be73b3681f99f3b282e3c02bba5",
+ "ba189bbd13808dcf4e002a4dd21660d5",
+ "2732dcd1b16668ae6ab6a61595d0d62a",
+ "d410ccdd059f0e02b472ec9ec54bdd3c",
+ "b2eaa07b055b3a03a399971327f7e8c2",
+ "2e8a225655e9f99b69c60dc8b4d8e566",
+ "4eb55416c853f2152e67f8a224133cec",
+ "49552403790d8de0505a8e317a443687",
+ "7f2747cd41f56942752e868212c7d5ac",
+ "02a28f10e193b430df7112d2d98cf759",
+ "d4213404a9f1cf759017747cf5958270",
+ "faa34884344f9c65e944882db8476d34",
+ "ece382a8bd5018f1de5da44b72cea75b",
+ "f1efa90d2547036841ecd3627fafbc36",
+ "811ff8686d23a435ecbd0bdafcd27b1b",
+ "b21beea9c7385f657a76558530438721",
+ "9cb969da4f1b4fc5b13bf78fe366f0c4",
+ "8850d16d7b614d3268ccfa009d33c7fc",
+ "aa98a2b6176ea86415b9aff3268c6f6d",
+ "ec3e1efa5ed195eff667e16b1af1e39e",
+ "e40787dca57411d2630db2de699beb08",
+ "554835890735babd06318de23d31e78a",
+ "493957feecddc302ee2bb2086b6ebfd3",
+ "f6069709ad5b0139163717e9ce1114ab",
+ "ba5ed386098da284484b211555505a01",
+ "9244c8dfad8cbb68c118fa51465b3ae4",
+ "51e309a5008eb1f5185e5cc007cfb36f",
+ "6ce9ff712121b4f6087955f4911eafd4",
+ "59b51d8dcda031218ccdd7c760828155",
+ "0012878767a3d4f1c8194458cf1f8832",
+ "82900708afd5b6582dc16f008c655edd",
+ "21302c7e39b5a4cdf1d6f86b4f00c9b4",
+ "e894c7431591eab8d1ce0fe2aa1f01df",
+ "b67e1c40ee9d988226d605621854d955",
+ "6237bdafa34137cbbec6be43ea9bd22c",
+ "4172a8e19b0dcb09b978bb9eff7af52b",
+ "5714abb55bd4448a5a6ad09fbd872fdf",
+ "7ce1700bef423e1f958a94a77a94d44a",
+ "3742ec50cded528527775833453e0b26",
+ "5d41b135724c7c9c689495324b162f18",
+ "85c523333c6442c202e9e6e0f1185f93",
+ "5c71f5222d40ff5d90e7570e71ab2d30",
+ "6e18912e83d012efb4c66250ced6f0d9",
+ "4add4448c2e35e0b138a0bac7b4b1775",
+ "c0376c6bc5e7b8b9d2108ec25d2aab53",
+ "f72261d5ed156765c977751c8a13fcc1",
+ "cff4156c48614b6ceed3dd6b9058f17e",
+ "36bfb513f76c15f514bcb593419835aa",
+ "166bf48c6bffaf8291e6fdf63854bef4",
+ "0b67d33f8b859c3157fbabd9e6e47ed0",
+ "e4da659ca76c88e73a9f9f10f3d51789",
+ "33c1ae2a86b3f51c0642e6ed5b5aa1f1",
+ "27469b56aca2334449c1cf4970dcd969",
+ "b7117b2e363378aa0901b0d6a9f6ddc0",
+ "a9578233b09e5cd5231943fdb12cd90d",
+ "486d7d75253598b716a068243c1c3e89",
+ "66f6b02d682b78ffdc85e9ec86852489",
+ "38a07b9a4b228fbcc305476e4d2e05d2",
+ "aedb61c7970e7d05bf9002dae3c6858c",
+ "c03ef441f7dd30fdb61ad2d4d8e4c7da",
+ "7f45cc1eea9a00cb6aeb2dd748361190",
+ "a59538b358459132e55160899e47bd65",
+ "137010fef72364411820c3fbed15c8df",
+ "d8362b93fc504500dbd33ac74e1b4d70",
+ "a7e49f12c8f47e3b29cf8c0889b0a9c8",
+ "072e94ffbfc684bd8ab2a1b9dade2fd5",
+ "5ab438584bd2229e452052e002631a5f",
+ "f233d14221097baef57d3ec205c9e086",
+ "3a95db000c4a8ff98dc5c89631a7f162",
+ "0544f18c2994ab4ddf1728f66041ff16",
+ "0bc02116c60a3cc331928d6c9d3ba37e",
+ "b189dca6cb5b813c74200834fba97f29",
+ "ac8aaab075b4a5bc24419da239212650",
+ "1e9f19323dc71c29ae99c479dc7e8df9",
+ "12d944c3fa7caa1b3d62adfc492274dd",
+ "b4c68f1fffe8f0030e9b18aad8c9dc96",
+ "25887fab1422700d7fa3edc0b20206e2",
+ "8c09f698d03eaf88abf69f8147865ef6",
+ "5c363ae42a5bec26fbc5e996428d9bd7",
+ "7fdfc2e854fbb3928150d5e3abcf56d6",
+ "f0c944023f714df115f9e4f25bcdb89b",
+ "6d19534b4c332741c8ddd79a9644de2d",
+ "32595eb23764fbfc2ee7822649f74a12",
+ "5a51391aab33c8d575019b6e76ae052a",
+ "98b861ce2c620f10f913af5d704a5afd",
+ "b7fe2fc8b77fb1ce434f8465c7ddf793",
+ "0e8406e0cf8e9cc840668ece2a0fc64e",
+ "b89922db99c58f6a128ccffe19b6ce60",
+ "e1be9af665f0932b77d7f5631a511db7",
+ "74b96f20f58de8dc9ff5e31f91828523",
+ "36a4cfef5a2a7d8548db6710e50b3009",
+ "007e95e8d3b91948a1dedb91f75de76b",
+ "a87a702ce08f5745edf765bfcd5fbe0d",
+ "847e69a388a749a9c507354d0dddfe09",
+ "07176eefbc107a78f058f3d424ca6a54",
+ "ad7e80682333b68296f6cb2b4a8e446d",
+ "53c4aba43896ae422e5de5b9edbd46bf",
+ "33bd6c20ca2a7ab916d6e98003c6c5f8",
+ "060d088ea94aa093f9981a79df1dfcc8",
+ "5617b214b9df08d4f11e58f5e76d9a56",
+ "ca3a60ee85bd971e1daf9f7db059d909",
+ "cd2b7754505d8c884eddf736f1ec613e",
+ "f496163b252f1439e7e113ba2ecabd8e",
+ "5719c7dcf9d9f756d6213354acb7d5cf",
+ "6f7dd40b245c54411e7a9be83ae5701c",
+ "c8994dd9fdeb077a45ea04a30358b637",
+ "4b1184f1e35458c1c747817d527a252f",
+ "fc7df674afeac7a3fd994183f4c67a74",
+ "4f68e05ce4dcc533acf9c7c01d95711e",
+ "d4ebc59e918400720035dfc88e0c486a",
+ "d3105dd6fa123e543b0b3a6e0eeaea9e",
+ "874196128ed443f5bdb2800ca048fcad",
+ "01645f134978dc8f9cf0abc93b53780e",
+ "5b8b64caa257873a0ffd47c981ef6c3f",
+ "4ee208fc50ba0a6e65c5b58cec44c923",
+ "53f409a52427b3b7ffabb057ca088428",
+ "c1d6cd616f5341a93d921e356e5887a9",
+ "e85c20fea67fa7320dc23379181183c8",
+ "7912b6409489df001b7372bc94aebde7",
+ "e559f761ec866a87f1f331767fafc60f",
+ "20a6f5a36bc37043d977ed7708465ef8",
+ "6a72f526965ab120826640dd784c6cc4",
+ "bf486d92ad68e87c613689dd370d001b",
+ "d339fd0eb35edf3abd6419c8d857acaf",
+ "9521cd7f32306d969ddabc4e6a617f52",
+ "a1cd9f3e81520842f3cf6cc301cb0021",
+ "18e879b6f154492d593edd3f4554e237",
+ "66e2329c1f5137589e051592587e521e",
+ "e899566dd6c3e82cbc83958e69feb590",
+ "8a4b41d7c47e4e80659d77b4e4bfc9ae",
+ "f1944f6fcfc17803405a1101998c57dd",
+ "f6bcec07567b4f72851b307139656b18",
+ "22e7bb256918fe9924dce9093e2d8a27",
+ "dd25b925815fe7b50b7079f5f65a3970",
+ "0457f10f299acf0c230dd4007612e58f",
+ "ecb420c19efd93814fae2964d69b54af",
+ "14eb47b06dff685d88751c6e32789db4",
+ "e8f072dbb50d1ab6654aa162604a892d",
+ "69cff9c62092332f03a166c7b0034469",
+ "d3619f98970b798ca32c6c14cd25af91",
+ "2246d423774ee9d51a551e89c0539d9e",
+ "75e5d1a1e374a04a699247dad827b6cf",
+ "6d087dd1d4cd15bf47db07c7a96b1db8",
+ "967e4c055ac51b4b2a3e506cebd5826f",
+ "7417aa79247e473401bfa92a25b62e2a",
+ "24f3f4956da34b5c533d9a551ccd7b16",
+ "0c40382de693a5304e2331eb951cc962",
+ "9436f949d51b347db5c8e6258dafaaac",
+ "d2084297fe84c4ba6e04e4fb73d734fe",
+ "42a6f8ff590af21b512e9e088257aa34",
+ "c484ad06b1cdb3a54f3f6464a7a2a6fd",
+ "1b8ac860f5ceb4365400a201ed2917aa",
+ "c43eadabbe7b7473f3f837fc52650f54",
+ "0e5d3205406126b1f838875deb150d6a",
+ "6bf4946f8ec8a9c417f50cd1e67565be",
+ "42f09a2522314799c95b3fc121a0e3e8",
+ "06b8f1487f691a3f7c3f74e133d55870",
+ "1a70a65fb4f314dcf6a31451a9d2704f",
+ "7d4acdd0823279fd28a1e48b49a04669",
+ "09545cc8822a5dfc93bbab708fd69174",
+ "efc063db625013a83c9a426d39a9bddb",
+ "213bbf89b3f5be0ffdb14854bbcb2588",
+ "b69624d89fe2774df9a6f43695d755d4",
+ "c0f9ff9ded82bd73c512e365a894774d",
+ "d1b68507ed89c17ead6f69012982db71",
+ "14cf16db04648978e35c44850855d1b0",
+ "9f254d4eccab74cd91d694df863650a8",
+ "8f8946e2967baa4a814d36ff01d20813",
+ "6b9dc4d24ecba166cb2915d7a6cba43b",
+ "eb35a80418a0042b850e294db7898d4d",
+ "f55f925d280c637d54055c9df088ef5f",
+ "f48427a04f67e33f3ba0a17f7c9704a7",
+ "4a9f5bfcc0321aea2eced896cee65894",
+ "8723a67d1a1df90f1cef96e6fe81e702",
+ "c166c343ee25998f80bad4067960d3fd",
+ "dab67288d16702e676a040fd42344d73",
+ "c8e9e0d80841eb2c116dd14c180e006c",
+ "92294f546bacf0dea9042c93ecba8b34",
+ "013705b1502b37369ad22fe8237d444e",
+ "9b97f8837d5f2ebab0768fc9a6446b93",
+ "7e7e5236b05ec35f89edf8bf655498e7",
+ "7be8f2362c174c776fb9432fe93bf259",
+ "2422e80420276d2df5702c6470879b01",
+ "df645795db778bcce23bbe819a76ba48",
+ "3f97a4ac87dfc58761cda1782d749074",
+ "50e3f45df21ebfa1b706b9c0a1c245a8",
+ "7879541c7ff612c7ddf17cb8f7260183",
+ "67f6542b903b7ba1945eba1a85ee6b1c",
+ "b34b73d36ab6234b8d3f5494d251138e",
+ "0aea139641fdba59ab1103479a96e05f",
+ "02776815a87b8ba878453666d42afe3c",
+ "5929ab0a90459ebac5a16e2fb37c847e",
+ "c244def5b20ce0468f2b5012d04ac7fd",
+ "12116add6fefce36ed8a0aeccce9b6d3",
+ "3cd743841e9d8b878f34d91b793b4fad",
+ "45e87510cf5705262185f46905fae35f",
+ "276047016b0bfb501b2d4fc748165793",
+ "ddd245df5a799417d350bd7f4e0b0b7e",
+ "d34d917a54a2983f3fdbc4b14caae382",
+ "7730fbc09d0c1fb1939a8fc436f6b995",
+ "eb4899ef257a1711cc9270a19702e5b5",
+ "8a30932014bce35bba620895d374df7a",
+ "1924aabf9c50aa00bee5e1f95b5d9e12",
+ "1758d6f8b982aec9fbe50f20e3082b46",
+ "cd075928ab7e6883e697fe7fd3ac43ee",
+}
+
+// hashes2X is taken from
+// https://github.com/BLAKE2/BLAKE2/blob/master/testvectors/blake2-kat.json
+var hashes2X = []string{
+ "0e",
+ "5196",
+ "ad6bad",
+ "d8e4b32f",
+ "8eb89056f3",
+ "410497c2ed72",
+ "f0de771b375c90",
+ "8662db8685033611",
+ "9ef9f1eed88a3f52ca",
+ "08225082df0d2b0a815e",
+ "0f6e84a17439f1bc97c299",
+ "895ec39c78d3556cefdbfabc",
+ "2b396b3fa90ab556079a79b44d",
+ "abae26501c4c1d6123c0f2289111",
+ "bca098df9099b3f785a37ba40fce5f",
+ "19b827f054b67a120f11efb0d690be70",
+ "b88d32a338fd60b58570fda228a121113b",
+ "3f30143af1cad33f9b794576e078cc79062e",
+ "ffddb58d9aa8d38086fcdae07e6653e8f31dfc",
+ "abb99c2e74a74556919040ca0cd857c95ec985e9",
+ "71f13f89af55ba936f8a7188ee93d2e8fb0cf2a720",
+ "99734fdf0eef4838a7515426f4c59b800854e2fcdc1c",
+ "579b1652aa1f5779d2b0e61868af856855020bdd44d7a7",
+ "1383d4ab4a6d8672b4075d421a159f69380ff47e4bb518d5",
+ "d3fa1412712dbbab71d4c6265dc1585c8dcc73380cf807f76a",
+ "1d57868a71e7245667780455d9aaa9e0683baf08fbaf946091c2",
+ "ef80418fe7049c6251ed7960a6b0e9def0da2749781994b24593a0",
+ "ef91cb81e4bfb50231e89475e251e2ef2fde59357551cd227588b63f",
+ "d7f398a5d21c3139cff0562a84f154b6953c7bc18a5f4b60491c196b6d",
+ "0a2abc6d38f30aef253579a4088c5b9aec64391f37d576eb06a300c193a5",
+ "02dd758fa23113a14fd94830e50e0f6b86faec4e551e808b0ca8d00fef2a15",
+ "a4fe2bd0f96a215fa7164ae1a405f4030a586c12b0c29806a099d7d7fdd8dd72",
+ "7dce710a20f42ab687ec6ea83b53faaa418229ce0d5a2ff2a5e66defb0b65c03c9",
+ "0320c40b5eea641d0bc25420b7545ac1d796b61563728a4dc451207f1addeedcf860",
+ "460539415f2baeb626fad748dee0eb3e9f27221661160e13edf39d1b5d476ee0672400",
+ "02de8ffa5b9c748164f99ed9d678b02e53f4ae88fb26c6d94a8cefc328725a692eae78c2",
+ "348a61a0136436136910262ad67ef20644b32c15456d5fad6b1679386d0bea87cc1a2e2b5e",
+ "24c32966c803434d48d2283482ee8f404f598cf7a17961748125d2ed1da987039b1ce00f2ba7",
+ "bd07cb16121d3b47adf03b96c41c947beadc01e40548e0d0773e61780d48d33a0e2a675ca681a6",
+ "a35844e34c20b4b9371b6c52fac412afe5d80a4c1e40aa3a0e5a729dc3d41c2c3719d096f616f0ba",
+ "6df1efbb4567747fe98d218935612f8835852dde2ce3dec767792d7f1d876cdae0056fef085245449d",
+ "48d6094af78bd38d8f4b39c54279b80ef617bc6ad21def0b2c62113b656c5d6a55aea2e3fde94a254b92",
+ "cd6e684759d2f19083164712c2aca0038442efb5b646594396b1fccdbd21203290f44cfdecca0373b3801b",
+ "155dfbf26103c8354362663677fa27d0e1ce3487a821a2a7171014c1bd5dd071f4974df272b1374765b8f2e1",
+ "15b11067f311efa4ee813dbca48d690dc92780656bc4d4c56510523190a240180867c829a8b8b9844175a8aa23",
+ "9bc27953a17fb84d5eabe95b4ea6bc03ea450274abccfb6f3938ded8560fb59662459a11a86b0e0f32fbea6bb1f8",
+ "03b78fb0b34fb8662accdf350a6be75ace9789653ee4375d351e871f6a98ac5e782ca4b4a717665d25e49a5ae25d81",
+ "687e9a6fda6e2ce0e40e4d30fef38c31e3513d2892bbe85c991fc3715947e42bc49bcd079a40ed061c2c3665efe555ab",
+ "f3886027d2049a8909e26545bd202d6a6fa2a6f815d31c7d520f705a81fa606dd695369c37aee4fa77dc645e9b05813ceb",
+ "e4a412ccd20b97797d91ccc286904fcd17c5afe8bed0618f1af333c052c473cd327637d951c32e4af047106036a3bc8c1c45",
+ "92f4b8c240a28b6238bc2eabadaf2ff3c4bfe0e6c61268ace6aebdeb0691450caea4287db8b329bde96af8cdb8a0fe2f57ef2d",
+ "e506834b3445e1a9a9b7bae844e91e0834512a06c0dc75fa4604e3b903c4e23616f2e0c78b5cc496660b4a13064bb1138edef4ff",
+ "27031955a40d8dbd1591f26e3c26e367a3c68f8204a396c6a4ba34b89672896d11276966a42bd516716f35ed63e442e116dbcf35da",
+ "646b1635c68d2328dddd5ac26eb9877c24c28390a45753a65044c3136ae2fe4fb40d09bf555271646d3dceb1ab1b7c8d8e421f553f94",
+ "f6171f8d833743bdee7cc8f8b29c38614e1d2d8d6a5fff68bec2c0f4dd463d7941ff5c368e2683d8f1dc97119bde2b73ca412718bc8cb1",
+ "45db1c478b040aa2e23fb4427017079810775c62abe737e82ec0ef8dcd0fc51f521f29fe6412fff7eac9beb7bcf75f483f3f8b971e42454b",
+ "500dab14687db3ca3dde9304af5f54194b37bdf475628af46b07bfbf6bc2b64ecef284b17f9d1d9be41794699bc0e76c2878b3a55730f7142d",
+ "31bba2efc7b3f415c3f031d4c06bb590ae40085ad157370af30238e03e25a359c9e133212ed34b7a006f839173b577e7015a87fdff2270fafddb",
+ "0600b3fb4b5e1ed0c8b2698ac1d9905e67e027390764821f963ad8d2b33cbc378b9c25c3ee422992d22b760222ed5697be0576d73938ae9d634ed7",
+ "4c0ca4f177d132594a4c613bad68da24c564efa3b4da0d0a903f26534a2e09f8d799d10e78f48ccdb0203954a36c5cf1bf24c076632c2b022b041200",
+ "97aacf2e1b013677b2e14084f097cb1e64d7b3fa36f097e189d86dc4a263bcc46817cd1ee6ff0c7ccd9acef63201cdc0e36254e19204a7388643bb571f",
+ "71fd6846ce7adb0843d6063546a16b79b54ad6c0f018a479a45817624fa221f63525084860559d1a0679c8d89a80701c62743ec2da8419d503f8f0cd7946",
+ "f73dfb046def3362d6de36077dae2cee2587fe95fe0800548bb7d99737897096ba59052e0dadcc1fb0ccb5535391875328637a0376a43a4d89366758dfe3e2",
+ "ec470d0aa932c78c5bcf86203ec0014314114765fa679c3daef214f883a17e1b4ca12f44433772a6e4ef685c904b2fc35586c6bd88f325b965968b06d808d73f",
+ "cf601753ffa09fe48a8a84c37769991e96290e200bbaf1910c57760f989bd0c72e6128e294528ee861ad7eee70d589de3cf4a0c35f7197e1925a64d0133628d87d",
+ "f15413f7d6fc54bb55829f698da92ee42fcf58dde1aa1bd07d438ecdc32ad6bf2bcdbecc99f18ed43e81b33065af5a4ca29960ae50553e610c0bbf4153d580e73dbb",
+ "84b1738adb9757fb9402ef7113581291136184d7ae35fe0b6a738da6acb0889d4d5bac7a957024e3709fa80c77d3859871ed1aa25cf488e438a2d24cfadce6008761dd",
+ "e02814bb81f250c1835a05108396b74c7878e737654bb83155e241774d04e639bbc571b413cd9349092f926c8a149a53cd33e9b63f370b6d460e504199d2e7d849db6cbe",
+ "aeee4a789956ec0913592c30ce4f9c544894da77ba447c84df3be2c869100e4df8f7e316445d844b31c3209abcc912f647735fd4a7136c2f35c6fda5b2e6708f5ca951b2b0",
+ "8cfd11ca385de3c843de84c830d59278fe79b70fb5ddbfbfc1ddefeb22c329ef2f607d1d1abbd1cd0d0cc7c5d3ed922add76aadca0d2f57b66cb16c582b6f18f60aee2f7509b",
+ "852e5ce2047d8d8b42b4c7e4987b95d23e8026a202d4567951bbbd23111e389fe33a736318546a914d2bddedfbf53846036ad9e35f29318b1f96e33eba08f071d6dc665149feb6",
+ "f225c23164979d0d13874a90ee291627e4f61a672a5578506fd3d65a12cb48a182f78350dc24c637b2f3950dc4882a5c1d5d5bad551c6f3e0093aa87e962bea51566af3791d52d65",
+ "5f33864d882455f8ef046aed64e2d1691e5c1555e333b0852750592e6f00d3b5ec941d0c00e99629612795d5870cf93c984b45e4464ba072a34903b400a42824ac13da28c7c1cb1959",
+ "7baaee7c3eb68c18c5ae1d45ba381803de34e36a52e2d7ccc9d48a297273c4d8644b473195bc23005f7a4f5ca790b1fa11f6a96e585e635513f11745dd97a69c1222204ab28d3c7735df",
+ "d0a2a3fc450ef9af7ae982041feb2842901026467d87839c33b4a9e081ea63d5be60ae99ca6e42393ded45255b8f42886f87ba0310572d9f0d8b5a07ff4b6bae1f30559a844983cc568560",
+ "3aa4164462b3e7044c35b08b047b924790f6d5c520b1df4305b5d41f4717e81f0cd4bccb9a5a6594773832b8707443adde4047caaed2293f92234df257df54ed275a9658fab483d0576d33a9",
+ "c8b4239fd7f1b893d978268f77f6505b5775d89090374322d40083b0f4c437423f670ca213f7fe05c61069725da2561646eefaea597ac48e293fbad44c2872046857e56d04a426a84008cefd71",
+ "f94839a7024c0a16971271b6727c081770110c957b1f2e03be03d2200b565cf8240f2873b0426042aaea996a1784fadb2b27f23bc1a521b4f7320dfbed86cd38d75141365ba9b443defc0a3b4078",
+ "8af934fdc8b3376ca09bdd89f9057ed38b656bff96a8f8a3038d456a265689ca32036670cb01469cc6e958cc4a46f1e80d700ae56659828a65c0456b8e55f28f255bc86ce48e44377bf1f9970b617d",
+ "ada572989e42f0e38c1f7c22b46bb52a84df8f7b3b773c9f17a5823e59a9725248d703efb4cb011abc9474e8e711666ed3cfa60db48480a8160615dfabad761bc0eb843d2e46299c59b61a15b4422fdf",
+ "b11f1ea52a7e4bd2a5cf1e234b7c9eb909fb45860080f0a6bdb5517a37b5b7cd90f3a9e2297f995e96c293189b807a7bf6e7633bebbc36674544db5f18dd33020aeaf50ee832efe4d3d053873fd31ce3b9",
+ "e54b006cd96c43d19787c1ab1e08ea0f8922bdb7142e748212e7912a1f2c0a4fad1b9f5209c30960b8b83ef4960e929b155a8a48c8fb7ce4326915950cede6b98a96b6f1ecb12715b713985dacd1c1180413",
+ "ee2c2f31a414ccd8f6a790f55e09155fd50aac2a878f9014f6c6035cae9186f90cdef0b7adf3e207c3d24ddfba8cd321b2e9228b02a1182b6973da6698071fce8cc0a23a7bf0d5aefd21ab1b8dc7818549bba3",
+ "6d6810793bad6c7efe8fd56cac04a0fb8717a44c09cbfaebce196a80ac318c79ca5c2db54fee8191ee2d305b690a92bd9e2c947a3c29342a93ac05796484638787a184e4525e82aeb9afa2f9480caebb91014c51",
+ "91e4694366cff84854872667fd168d2d42eca9070cdc92fca9936e8361e7266931f418450d098a42686241d08024dd72f0024d22ba644bd414245e78608942321ff61860ba1245f83c88592dc7995c49c0c53aa8a9",
+ "608aa620a5cf145f4477694407ccd8faa3182465b29ae98d96a42f7409434c21e4671bcae079f6871a09d8f2965e4926a9b08277d32f9dd6a474e3a9fb232f27fc4235df9c02abf67f7e540ca9ddc270ee91b23a5b57",
+ "c14f75e92f75f4356ab01c8792af13383e7fef2ffb3064de55e8da0a50511fea364ccd8140134872adccad197228319260a7b77b67a39677a0dcdcadfb750333ac8e032121e278bdcdbed5e452dae0416011186d9ebf29",
+ "03fcb9f6e1f058091b11351e775184ff2cd1f31ee846c6ea8efd49dd344f4af473f92eb44eba8a019776f77bb24e294aa9f962b39feecf7c59d46f1a606f89b1e81c2715ac9aa252e9ce941d091ffb99bb52404961794cf8",
+ "11e189b1d90fcfe8111c79c5351d826f5ec15a602af3b71d50bc7ed813f36c9a682520984ae911669d3c3036223a53176794c7e17929efab2b1c5b500f24f8c83d3db5d1029c5714c6fd34eb800a913985c218071677b9885c",
+ "69f8f5db3ab0321a708ab2f4234645dade6bfda495851dbe7257f2b72e3e8378b9fa8120bc836b737a675271e519b4712d2b56b359e0f2234ba7552dd4828b939e0542e729878ac1f81b6ce14cb573e76af3a6aa227f95b2350e",
+ "be734d78fae92cacb009cc400e023086bc3a3a10e8ca7cb4d553ea85314f51383660b8508e8477af60baf7e07c04cc9e094690ae12c73e5f089763201b4b48d664b94b4f5820bd1540f4a84100fdf8fce7f6466aa5d5c34fcbab45",
+ "d61b77032403f9b6ea5ad2b760eb0157545e37f1712ec44d7926ccf130e8fc0fe8e9b15570a6214c3899a074811486182b250dc97ebdd3b61403614d935cd0a61c0899f31b0e49b81c8a9a4fe8409822c470aacfde229d965dd62f51",
+ "c31bd548e36d5fae95ed8fa6e807642711c897f0fcc3b0d00bd317ed2bca73412064618c6a84a61c71bce3e963333b0266a5656571dcc4ba8a8c9d84af4bdb445c34a7aef445b15d77698e0b13c436c928cc7fa7acd5f68867e8132993",
+ "9903b8adab803d085b634bfae2e109dd247a7d6249f203403216d9f7410c36142df8fa56fb4d6f78136eef5817bad5ea3608439bb19336628c37d42db16ab2df8018b773baedafb77278a50926370b48bd81710203c7abc7b4043f9a1751",
+ "4dadaf0d6a96022c8ce40d48f460526d9956da33260e1770315ead420da75b122c762762aa3ddc1aef9070ff2298b2304cf90443318b17183b60778f3859b141053e5827decfff27ff106a48cfdb0371d0ef614fc7400e860b676df3176d1a",
+ "314dda800f2f494ca9c9678f178940d2284cb29c51cb01ca2019a9bede0cdc50f8ecf2a77e238b884867e78e691461a66100b38f374c4ccac80309641533a3217eca7e6b9a9af01c026201f0afaec5a61629a59eb530c3cb81934b0cb5b45eae",
+ "4658b7500951f75c84e4509d74047ca621009835c0152f03c9f96ca73beb29608c44390ba4473323e621284be872bdb72175628780113e470036265d11dfcb284ac04604e667f1e4c1d357a411d3100d4d9f84a14a6fabd1e3f4de0ac81af50179",
+ "491f877592837e7912f16b73ee1fb06f4633d854a5723e156978f48ec48fbd8b5e863c24d838ff95fa865155d07e5513df42c8bb7706f8e3806b705866475c0ac04bbe5aa4b91b7dc373e82153483b1b03304a1a791b058926c1becd069509cbf46e",
+ "231034720c719ab31f7c146a702a971f5943b70086b80a2a3eb928fa9380b7a1ad8773bfd0739142d2ad6e19819765ca54f92db5f16c1df5fa4b445c266215a92527bd4ef50ed277b9a21aee3fb7a8128c14ce084f53eac878a7a660b7c011eb1a33c5",
+ "3366860c77804fe0b4f368b02bb5b0d150821d957e3ba37842da9fc8d336e9d702c8446ecafbd19d79b868702f32405853bc17695873a7306e0ce4573cd9ac0b7fc7dd35534d7635198d152a1802f7d8d6a4bb07600fcdaacfaa1c3f40a09bc02e974c99",
+ "ccbbbe621f910a95835f5f8d74b21e13f8a4b03f72f91f37b5c7e995aa3cd5539508d5e234e77a4668a42c239b2d13ef0e55ecf85142055e3f8a7e46320e21324a6b88e6c823ac04b485125c2aa59b61476481208f92ea4dd330cb18777c1cf0df7cd07893",
+ "87faf0e49e7e5ab66ee3147921f8817867fe637d4ab694c33ee8009c759e7d707f44c69c1b9754e2b4f8f47b25f51cd01de7273f548f4952e8efc4d9044c6ea72d1d5857e0ffeb3f44b0c88cb67683401cfb2f1d17f0ca5696641bef28d7579f68d9d066d968",
+ "38c876a007ec727c92e2503990c4d9407cea2271026aee88cd7b16c4396f00cc4b760576adf2d683713a3f6063cc13ecd7e4f3b6148ad914ca89f34d1375aa4c8e2033f1315153189507bfd116b07fc4bc14f751bbbb0e752f621153ae8df4d68491a22430b309",
+ "87d636a33dbd9ad81ecd6f3569e418bf8a972f97c5644787b99c361195231a72455a121dd7b3254d6ff80101a0a1e2b1eb1ca4866bd23063fe007310c88c4a2ab3b49f14755cd0ee0e5ffa2fd0d2c0ea41d89e67a27a8f6c94b134ba8d361491b3c20bacac3d226b",
+ "b021af793badbb857f9a353e320450c44c1030fce3885e6b271bcc02e6af65fdc5be4dc483ff44bd5d539ed1e7eb7efe3001252e92a87df8227ace601047e101c871d29302b3cb6c6f4639078afc81c4c0f4c2e04688612ecf3f7be1d58ea92894a5dab49b949f2089",
+ "c5c1f2fbf2c8504a686b615278fc6221858d401b7fe790b75fb6bca6885cdd128e9142bf925471ee126f9e62d984de1c30c9c677eff5fdbd5eb0fa4ef3bff6a831056cea20fd61cf44d56ffc5bda0e8472ecdc67946d63c40db4ba882bc4dfa16d8ddac600570b9b6bf3",
+ "88f8cc0daeaeaea7ab0520a311dff91b1fd9a7a3ec778c333422c9f3eb0bc183acc80dfefb17a5ac5f95c490693c45666ec69234919b83244003191bad837aa2a237daeb427e07b9e7aa6ca94b1db03d54ee8f4fe8d0802cb14a6599005eb6326eefe5008d9098d40aa851",
+ "2eb6b1a58e7fe39ff915ac84c2f21a22432c4f0d260380a3f993310af048b11647f95d23adf8a746500833ee4e467fb52ea9f1039519fa58bcb0f1d0151558147b3c92b83730aba0e20eeeea2b75f3ff3ad79f2f8a46cbbadb114a52e32f018342aeeaf827e03ad6d583bbce",
+ "3ba7dcd16a98be1df6b904457709b906cbf8d39516ef107006c0bf363db79f91aaae033466624d30858e61c2c368599963e49f22446e4473aa0df06e9c734e183a941510d540536377072334910e9cef56bc66c12df310ecd4b9dc14207439c1da0ac08bdd9be9f2c840df207e",
+ "a34a7926324ea96867dac6f0dba51d753268e497b1c4f272918c7eb0e34120be65b7b5ba044d583141ec3ea16fcedae6197116b16562fb0706a89dc8efd3ba173ccd0fd7d84d480e0a3dda3b580c326aa1caca623879b0fb91e7d173998889da704eda6495023b5ad4c9ad406298",
+ "5ef97d80b90d5c716322d9ba645a0e1b7a403968258a7d43d310320f60f96235f50e9f22cac0ad239636521fa0607d2f471051b505b371d88778c46fe6787d47a91a5bec4e3900fe6ed22918226fc9fbb3f70ee733c369420612b76b5f55988d757c891d7005d17ee55783fe506202",
+ "140d2c08dae0553f6a49585fd5c217796279152b2e100ebde6812d6e5f6b862b2a3a484aed4d6226197e511be2d7f05f55a916e32534ddcb81bdcf499c3f44f526eb515cc3b6fa4c4039ad251253241f541558bba7413ca29318a414179048a054104e433c674ca2d4b3a4c181878727",
+ "29fdfc1e859b001ee104d107216b5299a792d26b2418e823e0381fa390380d654e4a0a0720ba5ff59b2ff22d8c4e013284f980911dcfec7f0dca2f89867f311ced1ac8a14d669ef1114504a5b7626f67b22ecd86469800f1575543b72ab1d4c5c10ee08f06159a4a3e1ae09937f12aa173",
+ "52dfb643832a598a10786a430fc484d6370a05356ee61c80a101dbbcfac75847fba78e27e537cc4eb918eb5ab40b968d0fb23506fee2ad37e12fb7534fb55a9e50902b69ceb78d51db449cbe2d1fc0a8c0022d8a82e2182b0a059035e5f6c4f4cc90278518e178becfbea814f317f9e7c051",
+ "d32f69c6a8ee00ca83b82eaf82e312fbb00d9b2f6202412a1ffc6890b4509bbbeda4c4a90e8f7bca37e7fd82bd23307e2342d27aa10039a83da55e84ce273822740510e4ec239d73c52b0cbc245ad523af961994f19db225212bf4cc160f68a84760233952a8e09f2c963be9bb1d71ca4bb265",
+ "d1e603a46aa49ee1a9ded63918f80feca5fc22fb45f659fd837ff79be5ad7faf0bbd9c4ba91628ee293b478a7e6a7bd433fa265c20e5941b9ea7edc906055ce9799cbb06d0b33ae7ed7f4b918cc082c3d4a1ac317a4acec175a73cc3eeb7cb97d96d24133a29c19375c57f3a4105519846dd14d4",
+ "b45ac88fac2e8d8f5a4a90930cd7523730733369af9e39bf1ffb833c01108952198301f4619f04b9c399fef04c214bad3358999967c474b67a7c06457a1d61f9466489ed5c0c64c6cdc83027386d6263491d18e81ae8d68ca4e396a71207adaaa60997d0dca867065e68852e6dba9669b62dc7672b",
+ "d5f2893edd67f8a4b5245a616039ffe459d50e3d103ad4675102028f2c497ea69bf52fa62cd9e84f30ae2ea40449302932bbb0a5e426a054f166fdbe92c744314cc0a0aa58bbc3a8739f7e099961219ec208a8d01c1ae8a2a2b06534bf822aaa00ca96218e430f0389c69c7f3fd195e128c38d484ff6",
+ "37279a76e79f33f8b52f29358841db9ec2e03cc86d09a335f5a35c0a31a1db3e9c4eb7b1d1b978332f47f8c3e5409d4e443e1d15342a316f442e3bfa151f6a0d216df2443d80cbcf12c101c51f2946d81161583218584640f4f9c10de3bb3f4772bd3a0f4a365f444777456b913592719818afb26472b6",
+ "a46d252a0addf504ad2541e7d992cbed58a22ea5679980fb0df072d37540a77dd0a1448bdb7f172da7da19d6e4180a29356ecb2a8b5199b59a24e7028bb4521f3281313d2c00da9e1d284972ab6527066e9d508d68094c6aa03537226ef19c28d47f91dddebfcc796ec4221642ddf9de5b80b3b90c22d9e7",
+ "060c18d8b57b5e6572dee194c69e265c2743a48d4185a802eaa8d4dbd4c66c9ff725c93667f1fb816418f18c5f9be55e38b7718a9250bc06284bd834c7bd6dfcd11a97c14779ac539629bcd6e15b5fca3466d14fe60d8671af0fb8b080218703bc1c21563b8f640fde0304a3f4aeb9ec0482f880b5be0daa74",
+ "8f2f42bc01acca20d36054ec81272da60580a9a5414697e0bdb4e44a4ab18b8e690c8056d32f6eaaf9ee08f3448f1f23b9844cf33fb4a93cba5e8157b00b2179d18b6aa7215ae4e9dc9ad52484ad4bfb3688fc80565ddb246dd6db8f0937e01b0d2f2e2a64ad87e03c2a4ad74af5ab97976379445b96404f1d71",
+ "ccb9e524051cca0578aa1cb437116a01c400338f371f9e57525214ad5143b9c3416897eae8e584ce79347297071f67041f921cbc381c2be0b310b8004d039c7cc08cb8ff30ef83c3db413f3fb9c799e31cd930f64da1592ec980cc19830b2a448594cb12a61fc7a229e9c59fe1d66179772865894afd068f0942e5",
+ "3eb5dc42172022ab7d0bc465a3c725b2d82ee8d9844b396913ceb8a885323dbbbf9ef4ed549724cc96d451ea1d1d44a8175a75f2a7d44bb8bfc2c2dffed00db0328cfde52bf9171f4025770abbe59b3aefd8151c480bafa09f613955fd571e5d8c0d4936c670d182cf119c068d420ded12af694d63cd5aef2f4f6f71",
+ "20ea77e58e41337ad63f149ed962a8210b6efa3747fe9bea317c4b48f9641f7145b7906ed020a7ae7d2ee59435392edc32aee7eff978a661375af723fbd440dd84e4a152f2e6ef66f4ab1046b22c77ac52717de721dfe39aa8ba8cd5da27baca00cc1fffe12c52382f0ee83ad1418f4c6a122effaf7471e1e125d7e7ba",
+ "95c662b835171fa23f948c3c3ed27bab9b3c367bbfe267fe65f8037a35b50cd7fc6030bfce4000425ef646c34793f0762635ae70487a0216ef7428da622be895d1b6040423246511c2370d6876a5c5d2df8bbd48fb14f787b632ad2c1f5a927fdf36bc493c1c8606accfa52de33258669f7d2d73c9c81119591c8ea2b0ef",
+ "f708a230675d83299cc43167a771602d52fa37cbc068ef9128ef60d186e5d98efb8c98798da619d2011bf4673214f4a4c82e4b11156f6292f6e676d5b84dc1b81e7cc811b0d37310ac58da1bfcb339f6ba689d80dd876b82d131e03f450c6c9f15c3a3b3d4db43c273c94ed1d1bd6d369c4d30256ff80ea626bda56a6b94ea",
+ "f8417766ce86b275f2b7fec49da832ab9bf9cb6fdfe1b916979ae5b69176d7e0293f8d34cb55cf2b4264a8d671370cb595c419c1a3ce5b8afa642208481333522005fbe48cdc700e47b29254b79f685e1e91e7e34121784f53bd6a7d9fb6369571bba992c54316a54e309bbc2d488e9f4233d51d72a0dd8845772377f2c0feb9",
+ "3479e04efa2318afc441931a7d0134abc2f04227239fa5a6ae40f25189da1f1f313732026631969d3761aea0c478528b129808955be429136eeff003779dd0b8757e3b802bdff0f5f957e19278eabad72764aa74d469231e935f4c80040462ab56094e4a69a82346b3aeb075e73a8e30318e46fdaec0a42f17ccf5b592fb800613",
+ "03df0e061fa2ae63b42f94a1ba387661760deaab3ec8ffabcaff20eeed8d0717d8d09a0eafd9bde04e97b9501ac0c6f4255331f787d16054873f0673a3b42ce23b75a3b38c1ebcc04306d086c57a79d6095d8ce78e082a66c9efca7c2650c1046c6e0bbce0b2cba27c3824333e50e046e2a7703d3328ab3b82c9d6a51bc99b9516ff",
+ "76b488b801932932beefffdd8c19cf5b4632306e69e37e6a837e9a20c8e073bcadd5640549faa4972ebd7ee55cb2425b74cb041a52dd401b1a531beb6dfb23c4cfe74bc84f034156c8f55050ca93236eb73c4e2595d9fbf93dc49e1ec9a31705359732dda73f737ec4274e5c82626dc4ec929e5e2c7a2f5f5fb666181922bd8be575e3",
+ "ff17f6ef13abc0426b03d309dc6e8eeb822300f7b87eff4f9c44140a424098fd2aef860e5646066d22f5e8ed1e82a459c9b9ad7b9d5978c29718e17bff4eeefd1a80ba48108b551e62cd8be919e29edea8fbd5a96dfc97d01058d226105cfcdec0fba5d70769039c77be10bd182bd67f431e4b48b3345f534f08a4beb49628515d3e0b67",
+ "95b9d7b5b88431445ec80df511d4d106db2da75a2ba201484f90699157e5954d31a19f34d8f11524c1dabd88b9c3adcdba0520b2bdc8485def670409d1cd3707ff5f3e9dffe1bca56a23f254bf24770e2e636755f215814c8e897a062fd84c9f3f3fd62d16c6672a2578db26f65851b2c9f50e0f42685733a12dd9828cee198eb7c835b066",
+ "010e2192db21f3d49f96ba542b9977588025d823fc941c1c02d982eae87fb58c200b70b88d41bbe8ab0b0e8d6e0f14f7da03fde25e10148887d698289d2f686fa1408501422e1250af6b63e8bb30aac23dcdec4bba9c517361dff6dff5e6c6d9adcf42e1606e451b0004de10d90f0aed30dd853a7143e9e3f9256a1e638793713013ebee79d5",
+ "02aaf6b569e8e5b703ff5f28ccb6b89bf879b7311ea7f1a25edd372db62de8e000219afc1ad67e7909cc2f7c714c6fc63ba341062cebf24780980899950afc35cef38086ee88991e3002ae17c07fd8a16a49a8a90fc5540be0956dff95390c3d37629949de99920d93096eb35cf0427f75a6561cf68326e129dbeffb8772bfdce245d320f922ae",
+ "70752b3f18713e2f533246a2a46e38a83cc36dfccec07c1030b5204cba4432700735a8cee538b078d281a2d0262110381c5815a112bb84404f55af91652bd17502dd75e4910e062943d8a736ae3eecdfdd8e3f83e0a5e2ddeeff0ccbdadaddc95391310fc657a59724f7e6560c37dc1d5bb5db40170190f04a274c864ade9687c0f6a2a48283177a",
+ "01f3c1333b44077c518cc594d0fb90c37651fb7b2442e71fc0a5611097f1cf7bcfaf11c8e0ac1b1cab54afba15bb9332df6bc64d8032368e3f686c8324b0114e0979dad78a5ccd3fff88bbe89eef89c4be586ca092addef552ed33224e85d8c2f4fba85ac7735f34b6aa5ae5299154f861a9fb83046b0e8fca4db32c1343e02676f283975f43c086cf",
+ "509283ebc99ff8d87902fa00e2d2a6fa239e335fb840dbd0fdbab6ed2d95e8275402523f7ce9a2fabd4b6c9b533288fbe914bde84365a204711d0977a7d698f4614385984dd4c137e4820035dd6737da364edff1bb62283e87a8c7ae8637314fe9b5777ec4ec21276dafedb2ad5ee1aa0ac99e34a6c01c055c8a239fd28681607f65143082cd4553c529",
+ "c17e417e876db4e123c631f7136b8a85bfd6ce66a69180d0cd5ecfd6f037bb1c7bd7908d51f2c485bf9e92c0e1799ee5f6ab834ee481f5eb1a8020205adb4d0f90126d4e7c2c859c5a5f644bdfa9c649ff4f168e834de6f9769429732099d46d0af506ab86c6fd92175159bbc05c75db8e1fa867e6030d64250008d64c857c47caec3dc8b2ffb384d0193e",
+ "950988fbe9d62a66f5f2c492bc8dc944a78eb3796ec37ba94b6a81a9d402ccad03cd8497fff74c5f4a03081c5fecec48574fecb21c1de261332c23108195d3f6a96ff8e433a1a30eda53dd5bb414973334f8cde5510ff759f7c17046cbb5acd8e8c4a6eecf2a9121ec3fc4b22c4daa72678194ce809024cd45c4ebb9ccdb6f854205cdb624f0787480d8034d",
+ "552a212c403b473741da8e9c7b916d5e5e9bcc9949021ae1ca1ed46b7d4a98addbb604d9fff56175b7e0367db26c9635fa7813653dc8d610befdd09ec41e99b192a716106f4299eec8b940863e5a59cf26cdc2cd0c3017f9b4f215812bed15f69e77edf672178e13c55580982f01fcc2fa131ec3d736a55d56504c545f4be50fee83f1263e4d3f3c877cc6242c",
+ "b00c4283dd3d9cd26e44bd97cede6c771cb14f2571b51cfdaae4309560ffd165da025a1bbd31096c3aa8286e2d6dcc3e681b8d01f2c5064ea26dfd0b5156b7a7f5d1e046c5bd1628f8fdae24b03bdf7cf7366900cc013a8cbed9d7f5937c914b08f8c27683b956e1279812d04288515333fc6aba3684dde2292951f0610649d90fe61606630fc6a4cd383649252c",
+ "f6e79457bb6d0884dd223be2cf5ae412a1ed425f1e4012f75951b096aea3b9f3581f9013bcae1aff2d3fc1e5c7e06f24af6d53c2c5c238b71c71cc670b05a7ee5204400026a5c4e5ddec3ad96771e49fae4b0f75ec58049ad9d972e5749a32d90f847f1ed2a1bab83db181e541cf5c8adb6b29ecc64dc25add491d408d3eb3ddcb013de7f5ffb6de9dd7ff300a5fc6",
+ "fe1d71e1d5efa3f712d23216ee8ee9139e66bd648b83efc02cdb4d45a28cf36759ff190a84d14d9471477abefb5aea4111110336143dd80cf81e02f268120cc07d746538f968e9876bff8358d390f5b8e7eafa61ecd236cedaf276bd61865fdd3424988201dcdeda2e3e0c33c9e3b3670125dd1049106cc6df5695fb2dca443233ff440f265bbff055483bac1e859b83",
+ "4c80163562872a965dedd8725652906156ada6e9d999027d96f49289edb92f9ef043e9d7c3377e091b27f85275499454af32317535997fb4aaeaf93565ad481ff7d45d2abddd4df4b60f71a6923ec30496c6ae534dc5427107ab4c5e656a322c7ab058d4c13ec0ebafa76576560697ac98f84aa4a554f98ec87134c0d7dca9184cf70412a324aac91823c0aca02537d197",
+ "fdd58c5ffe88665beb7073c8f4c22472f4bc9390cdd27a42622ca55978b000ab7579f795d4de0dfcaf521b8268980ef1d20277b07567985c0fd5030784ad6c32541ac24e99ab706105a2255fc32935c0fce6fdad9bb224d94ae4eae2a3ff08836618a3adf193630647bce1952b69da4de360f59da303519278bfd39b733cf66820a5e9e971b702f45998b69a0889f4bec8ec",
+ "ff38b15aba3794e2c81d88003e045ac6cbfc9f4833cdf896cefd8ac0c88674727ad9a9fcb9ef36574deea480e6f6e8691c8390ad73b8ea0eb3665c914b0d886546948e67d7987eea248b5feb52346ffdd965d5c835144c3bc63daf325e74b11267e32e58a914ae4521a668839d9445fececa49c5fba41f9e171698bbc7c6c97fa163a377a96456958d6e1d74f91ada56a30df8",
+ "f048c19328d60b4e59ed76940415b2c84c23883198bba5699efb0a1774ad5da6d15390c7b55d77d66f37448fe08107f42a5336408d5322f4b630e3275865fc66dccab39f6e13fabc133e5a441fe352d81c7cd9a25f145a6e2e2417d3b0bbc79eafcd7ad688c02011fd268dd44ac3f4f87b37a84a46fd9e9975962fba92c9a3486deb0c45f6a2e044df4bb79f0feeea432c5008b0",
+ "1b3e5fe6f113cce28a6f8d6f7809d3cec398cabffe9ff2ff10a7fec29a4ee4b54186063fd5307a2be393c9ecd75a37620bdb94c9c18da69b658579676ec90351d10dc33a7cb3b75798b1234f9f684d4a73a0fab2df3d5d6fdb1c1b1514d0935c1f2dd21486f91c2595b2f8f8a500ff443b9305270fb6f3da7961d9316d4ed6a135a31c4a3611d40e6585bbb34f498cd5b9a5d92676",
+ "740db337baa12b16897f17a85fa5685acc85e48338867f8ac9c0198dd650f5dfa7c17725c1262c72207e365c8aa45ffaab6470a0e5afefbfc3bb702a9766064f28cc8b796878dfdd3ca9d0216c14941438fc541fb5be0a13d29a996c5c985db4f630df067a5626db5dcd8df3a2bff17dc446e46e4079b8815da4318cb228c7722684e2a795a0ca56f500ea51951a6a385385d886f678",
+ "1465f2d578d167faa017fe8f763ce3cc8dc1e8371d774ed2a8803f12585296ee71a1f2253dd16b717a81f91f0f3641018a0111182b4e65d884b0a3d0292631ad807cdccc88bdeecb476e76f72b5246a630aff6e2401fa9570f85acb73ccb4e19ef04a932a03d7b7985dbe1e5bb410df517fe362321469e6f8b0e0cef6c31d7aa8ec06aa220620d66cc0e133fdee963589b12320fc9678e",
+ "80c051952fa6f3ef6af0f1759ec3e83c8eb91abee1de360bfa09e74b05af2475a0dbf8f9135aa25892919bbe0515898cfb6f88abc9e1891f2b2180bb97370f578973d55c13c35edb22ed80647c2a7e2884d1ccb2dc2f92d7b6ec5843ade13a608a31190ce965bde97161c4d4af1d91ca9962053f9aa51865bdf04fc23fa35a6fc3c8e888941263a26ed66c2dd0b29b2325dfbd1227c5091c",
+ "9c1e2a1aed6406052eed12b4495365f2f80e9c9645473f3549b607f20910bcd16dc3a4b173ac8d128129cdb7c76ebbc8e9a2a1ba0d822c66b367e790a69ac71f0a60ed4bff0e979148e3f3ee6607c76dbc572ee5ff17c27e4b52adebb4bedddff517f591a1977299c7cb01106f1453b098d29848ba3751c816215bb0d090c50f9e445b41b2c49d4eec83b92ce6c269ce835fd279e7cbbb5e47",
+ "466abda8944d0329d2975c0f2e2afc901f117887af301881f63b714f49a2f692fa63a8871fc0b301fe8573dc9b2689880cd8969e5072c57671e0633b041481dab25e65c9de404af033a11a8070c8ab70ca6d465318501afdd9940c7efbe1bb6d49581c222fad251dba4ee0a98efe22a3c4f74da05844523b30bbad6b080ac8df70a02da80bc9d477dfb869adb211e209a316d5dd1fd89a6b8f8e",
+ "0e89a873e07799ba9372fc95d483193bd91a1ee6cc186374b51c8e4d1f40dd3d30e08f7feecfffbea5395d480ee588a294b96304b04f1ee7bbf6200cc8876395d1db3ac813e1019bb68d27204e514fe4a61ad2cbd1782dca0e38b5538c5390bca626c5895b745cfca5dac636fd4f37fed9014ab46ae1156c7789bbcbb956ff7ee5ce9effa560731d26783dc6ae8bddd53a5d28133614d0ddeddd9c",
+ "fdde2b80bc7a577ef0a6c03e59512bd5b62c265d860b75416ef0ce374d544cbb4e3a5dbd31e3b43e82975090c28bc77d1bdec907aeceb5d1c8b71375b6d631b84a46153f5f1d195bfcb2af6f597a9cdc83782c5bbbb58c5188a87ebf375eee5212fa52523820a83106e8ecd52bedd60d95cd646159774389c07e1adcaa6b6f649408f33399ec6e507d61659696b3dd249996892d5986b654d94ff337",
+ "f5d7d66929afcdff04de30e83f248e69e89604daea782e1d82d8032e91a95c1d6fb2f5578f79b51be4397e4cd7cbc608ce143fdddbc6fb6c43ffdd394a7df0124353b919aeeac025f3eb11ff246c3b9657c1a947fc534ce48e18feffada8797037c6bc7e2d9a9e2e019fe65627b3feb28e446473e3bd413047a2587f0be6a103403cb3c33fdc212dca14d8e386aa511c22308e632f5f9528dbabaf2deb",
+ "332990a8dba55f977bc814436cf386ebbf10cb487a5f6ce83e13741bac670c6810284fbbe4e303547ef411e964fae82854e8c13cf56979b89ecfedd337aad78260060122d13dfbbf8497acb2066ed89e30a1d5c11008bd4d145b5ec353956310536304d8b8bba0793baec6d8f3ff49718a56e6694f8122078265cf5731d9ba61292c1219a1affb3679576d4998290aba3684a205c3469d40761a5c4e96b2",
+ "efbdff285027610f03182009c89b953f19721cfcdb8accd74bab6ec4bdf3f555ab902cb0dd91284269d140638aaabd211748aa4da3b18cddc653b57e461b9ad8491807c535c08fe97d89eb587c6af19ca152e72479626ab764e8b62da89fefc8354c75a44851f985746d78715a5a92798dac1a4222be27897b3f0aa63d596aa7378545f49b259aa8518c3def8a2ec8f7aa956c43668c8717052035a7c36b47",
+ "0eea9bb83bdc324fd21b03669aa922fbebc448e7d25e210294c07862cfa6e061731dfb67b4810633f4dbe2130d90fa1c65843af436e74219d213c4458dcac1c48ec4541fc6e3b7918ab2bc621aedda53658050900c3865ca57cd5dfa1d28576827401956d2dd8b861fa90ab11bb0b544ded9bd3d62e3278ed484e17db8f2d5dc5ea4d19a0e15134ba6986714c2b22c59c2f0e517b74eb92ce40d2f5b89e6d79f",
+ "25da9f90d2d3f81b420ea5b03be69df8ccf05f91cc46d9ace62c7f56ead9de4af576fbeee747b906aad69e59104523fe03e1a0a4d5d902352df18d18dc8225855c46fefeec9bd09c508c916995ed4161ee633f6e6291cb16e8cac7edcce213417d34a2c1edea84a0e613278b1e853e25fb4d66ff4c7ee4584e7f9b681c319c874d43502534e8c16a57b1ae7cc0723783807738a55b661e617ee285bdb8b845607f",
+ "a76b6f81372df09322098868d469fb3fb9beafc5edb32c674974ca7032966aaca5b5c9bffef87bfe626bd8e33d1c5f054f7d5acd3b91ff95324d1ae39eb905b9f2694fe5cb03486cee86d2f661a751b0e6c716a61d1d405494c2d4e32bf803803dc02dba2c06eecf6f97fb1f6c5fd10cfc4215c06d627c46b6a16da0854e4c7c873d50aa1bd396b35961b5fa31ac962575230c07c369f8fbc1ff2256b47383a3df2a",
+ "f9db613812f2259972d91b1598ffb166031b339913925ee385f03b3b35dc4b2f1ae78a3c3d99c6ff6a07be129ce1f4b8d994d24988d7fbd31f20535d36ab6bd0592cfb4f8c1ed9244c7fa8a3c46e91272a1a40c6cfcf261c5658476c59793bf1a3775086e41a0492f88a31e2d9d1ce75cf1c6b4b928b3545d838d1de6b61b735d921bcf72e4e0615e9ff969ef76b4b947026cb016e2660ba39b0c4c953369a52c210de",
+ "e601c7e75f80b10a2d15b06c521618ddc1836fe9b024458385c53cbfcedd79f3b4239598cd7b9f72c42dec0b29dda9d4fa842173558ed16c2c0969f7117157317b57266990855b9acbf510e76310ebe4b96c0de47d7f6b00bb88d06fad2c2f01610b9a686079f3ed84613ba477922502bc2305681cd8dd465e70e357534503b7cbc68070ad16d9c51de96ccf0aae1599299331c5655b801fd1dd48dddf6902d0e9579f0c",
+ "ee5ff4ca16d1bde59ffaf2d064eac9141c1d8f120ea2bda942b7956ba3effc5f1e725a3b40b0b9223a14d7a50df1681d14ca0e0eda7bb09c428fa3b2701f83a7a3e139485a118f6287d266dbc7fe68c87b35becabc7782537c79cb8165bdc40cc103d7b6d4b627fafa0e4113f92341ab90ceab594bfae20dadbfafd401684584598941f1ffb8e23dc8a04ecd15376cda6d849fe0dfd177538c62413622d172d9d46e05c450",
+ "1daca80db6ed9cb162ae24aae07c02f4126f07cd09ecee8e798fa1bc25c26c644333b63731b4ebc3f287f2318a820c32a3a55fc976576bc936f7384e2553d2891e3771ff24dd4c7f0256906460a8f12d30ed2b23583a0259cb00a9065a757d654d6e4603e7c7eb4a8426b527ae8a849d9350e9094b890367df3e8b23ad2df4d7dcce416bd8ea3badd037f53f7b07c02e5926515f196d62aeb9b8b14c863f067fc12c5dfc90db",
+ "27ff4e58a34ff1fcd66855d014ea17889a3cf0021a9fea3fabfd5b270ae770f40b5439e00c0d26bd9766f6fb0b4f23c5fcc195edf6d04bf708e5b0bced4f5c256e5ae47cc5651e51cd9fe9dc5d101439b9bc5cc24f76a8e8847c72686e2af1ce7098ad7bc104dad00c096a6d48b6453322e9cd6773fb91fb1eabd05dc5185a9aea07a2f64c6fea9897681b4428aaffe1fe5fd3e8ceb890b12169ec9d51eaabf0ca3d5ba415770d",
+ "75e2fb56327983b04f640717be8cba6fef3655b4d8e5539587d6478356ec397efaed818b8425d052778eb30ef0dee656c52c2aeab079ed496ae4441a365f2130432c87ba757e25b4511656ad15e2eff84d342331fd2814d1f1d11af65d98a424c115ba183437c0d0aa55f5c44b8685028a47d89d0d36a0f20aed510c366ab338f074a941b404fb349caaec821e0850a627777cc8f5abce6b509290027a2a28ff1db62a5ed2f95fc6",
+ "c6ae8b6a060917cd498aa7874ad44baff73efc89a023d9f3e9d12c03d0b7f5bcb5e24e1bc2ab2f2c67b9a9d36ff8beb51b5affd4a3510361001c80642955b22ea4bf28b81a5affe5ecdbabd8d17960a6af3825a4522fe76b3d720b5d06e66bff5379d7a8de1f5cc3e7bb75163a854d77d9b3949bf904b6c4e568682f0dab7f217f80da7303cfdc9a53c17b6b51d8ddff0ce49541e0c7d7b2eed82a9d6be4aec73274c30895f5f0f5fa",
+ "606c9a15a89cd66a00f26122e33ab0a08c4f73f073d843e0f6a4c1618271cfd64e52a055327deaaea8841bdd5b778ebbbd46fbc5f43362326208fdb0d0f93153c57072e2e84cecfe3b45accae7cf9dd1b3eaf9d8250d8174b3dade2256ecc8c3acc77f79d1bf9795a53c46c0f04196d8b492608a9f2a0f0b80294e2abe012dc01e60af94323c467f44c536bf375cddbb068c78432843703dd00544f4fff3eaa1a5a1467afaae7815f80d",
+ "88b383cb266937c4259fc65b9005a8c190ee6cc4b7d3575900e6f3f091d0a2cefa26e601259ffb3fd03083270eb63db1ffb8b4515ec454d12f0944f8f9f6869eedc2c5f1689766a748d74e79ad83ff6a1639aefdec6109342dead31e9cead50bcc00c5b2206e8aaa47fdd01397b141880490174141a1e6e19268378c1b54a84aba60ca711fd72f7df88e120dfea2caa140085a0cf73342f3c588b7edfb5b5e5ccabd68a32364746d92d536",
+ "dc0b293f1ba02a326743509f41efdfeeac1efc45137ac03e397a3273a1f586a0190cfb4ea96d6c13ca692a4de6de905c8338c3e29a04cbae76272f568b9d795cea5d758106b9d9cff6f80ef650d6b7c428ea3946c3acc594907fe4227ed68faf31f2f6775f1be5139dc0b4d73ed6308fa226b9077561c9e4c7a4df68cc6b819b0f463a11b9a09682ba99752c4db7aea9beac1d9279f2c2675d42b551d27aa2c1c34125e32f2f6f45c35bca45",
+ "5d801a7413311e1d1b19b3c321542b22e2a4ccbe340545d272abede9223741d9835a0fc80cc9da97a13f8bb4110eb4ad71093efba165b1edad0da01da89d86726e0d8e42ae003b4b50297d233c87da08406f0e7fc58ba6da5ee5ba3d2d7142cbe6632734eb2e7b7863c15cc82198ee8f9a0ae0b7f93bdbda1ed269b3824d5d3c8e78513815b17a4c0cc8c9706b9c77423a309ae3fd98e1e05cdbe9e2577834fd71f964301b10b66c316a2d8f2c",
+ "2fd32a2bc15a9e96a100624404fd0a4e54ba9f8c0543d8ccf7c5c2e35f5e8c3c11dfd497320aa903900a4ca55a2b323b3ac4a7cfcd01bf0b448db8829072bee6b77c3d7bec2e1d8b414d907288d4a804d2379546ef2e2dc628269589164b13fceb32dba6fd5d48a956ce0b5c3eb28d894a95af58bf52f0d6d6cbe51317152744b4ccfc918ed17fa6856478d580b389016b772e1d02e57d2217a204e25361d91d4845a3fa20fefe2c5004f1f89ff7",
+ "f537b437662759bef8bd64368536b9c64fffbddc5e2cbdad465c3966b7f2c4bc5b96767ef40a1c144a4f1cd49edc4cc5b57e7eb30d9b90108f6fd3c0dc8a8808b9e0bd13aa3d661c4863637c5e4ba286553694a60bef18801299ae349df53a355051dcc46a7d003c4aa613808f430e9db8ca7dfe0b3f0a4c5ab6eb306aeb53e11a01f910064fbe6ca78b2a94fac34a2602f73de3f275953e13ff5c6bb5c39b82321ead17ec0f8ecc479e6afbc926e1",
+ "1dd9fb7d5b5d5074971e69300720014deba6fbdb942bd29704cdfcd40fa5281d2a1b9f5b776183e03ff99c29587f10e8d325cb49c5c93e94f5132741b92c4086eec1374dea5c1e772cbb230c7b31f3e962eb572be810076bdb926b63732522cdf815c3ab99bbc164a1036aab103cac7b823dd21a911aec9bc794028f07b7f839bae0e68211286441f1c8d3a35b281fd321312577bbda04f643ecb2a74ec4527bb5148dbccbeba749f5ea19b6072366ba",
+ "5bd63737449de2d20ca63943953338ecf4cdd6cd0a726241adb04376385a809cc6ba0f3482a310746fbc2cd5eb214f03a14cdc548777fb0d048d659cd75a962e490c4fe47affc2430a34b10275e4c76752a115aae3a24d4fb4fad89ce4d79d65de10292f3490bfdaeabfae08ed51bda6ec8230e66cb07ddbeec26e3ef68dd71c852900659fcf0c963f4574ffe4626a33db9abf0873dde68b21138498b81e8cc44d354be4073615889a7ddff633b5447d38",
+ "a683ec8250506571f9c640fb1837e1ebb06f123e745f95e521e4ea7a0b2b08a514bbe5bdfd316903d1d6a05f5a143d94dab61d8a3a146ab40b2d6b72df2f0e945875a8aa7051ed115975f6f1567cfcbf04c5e11e3a7027b8e179ba00739181ba10b028e3df7259d0712f4a6cef96469ff737865b85fee2c2db02a6423e32505381e18a1e0b4ce3c7998b8d6b1b5e09c3a280b85486d0984c9e193b0ad2043c2bc4ad04f5b00a73956715937eebf6b3e27afc",
+ "4df9d160b8e81c42930c48956fcb46b20b6656ee30e5a51dd6317876dc33e0160d31280fc185e58479f994991d575a917073b4439919c9ac49b6a7c3f985211d084c82c9d5c5b9a2d29c5699a22e79de3958d7b0e856b9aa97493cd4563aaa04fa3977a9bb89e0bc06a82296bdc76d20c8d393770176d648712454305fdfcf4e117d05acb5a5b006a9f8d0dc66dca708c4e4103ca825d2331750685c44ce3d9b3e753455580f4d6ac4533edeeb02cebec7cc84",
+ "67bb59c3ef5ee8bc79b89a673e331e581215076cc36b68f517ca0a74f74efafe9dcc240e6d8ca4b21019c27d6c9289f4419b4f218eeb39eb741c5ebebfe0ed2f6faeec5e8c477acf71907990e8e288f4d4049111779b0635c7bbec16b76493f1c22f645745fdac2b383679fee573e4f47af45ee08d84f63a5ace4ee1c06fa41e2e6e14b7bc392e38426813087a3a461efc62ed1941dc8f1728a2bdc04fde72a0b786558783c84abd4bd100e4926979a0a5e707b1",
+ "d341147169d2937ff2373bd0a9aefa77968ec8f0d993c6f9881eb174a1911e05cdc45993cb86d149a754bbe321ae38363f9518c50dd3faf087ffeeeb6a058b226ccab7858c00ba6de0e8f4d034b1d27508da5cc473f3a413189ee6fd912d7750486912944d4dc34405ce5ccc3885fb0aabcb922bcfa9081d0ab84c288022bd501235a835eb2e1124ed1d48fd4f8682da8e7919321031326502273375625c4e3a7282b9f53452195e53c6b4b57cd5c66f621bed1814",
+ "27e7872a54dfff359ea7f0fca256983f7600236e716e111be15a1fe72eb66923ea60038ca2953b0286447dfe4fe853ca13c4d1ddc7a578f1fc5fc8598b05809ad0c64a4363c0228f8d15e28280837a16a5c4dadab681e28968ae17934639fbc124bc59212138e494eecad48f6546c38366f1b7b2a0f56f579f41fb3aef75dc5a0958b25deaa50cb7fd1c69816aa9a51874a98e57911a33daf773c6e6166cecfeec7a0cf54df01ab4b931984f54424e92e08cd92d5e43",
+ "13dcc9c2783b3fbf6711d02505b924e72ec6736131159017b966dda90986b97522bf52fd15fc0560ecb91e2175322334aaaa0097e1f3777c0be6d5d3de18ed6fa3444133486068a777443a8d0fa212ca46994944555c87ad1fb3a367db711c7ebd8f7a7a6dbb3a0207de85851d1b0ad2f4149bdd5a5ba0e1a81ff742df95edee850c0de20e90dd01753137cb8f2c64e5e4638ceb893a3879ae2c049aa5bce44d56bf3f325b6c5029b2b8e1b2da8de7d4e48ca7d8f6fbdc",
+ "9ca875115b109eab538d4ec7023600ad953cacdb49b5abe263e68b48eafac89a15e803e838d048d9625972f271cc8f36344bed7bab69abf0bf05979a4cfff273b82f9961626509765fcb4b4e7fa48212bcb3ab2b1f2dd5e2af768cba6300a813514dd13e4d269e3d36548af0cacdb18bb2439ec9459f6d847d39f5598304ec46a26d75de1f9f0c2a88db915bd26e45e1f1e68c5b5b50d1890e97a3803c36755f026863d14176b8b57f42e91d3ff37787f9b38e333e9f0433",
+ "ec006ac11e6d62b6d9b32ebe2e18c002353a9ffd5dfbc5161ab887770ddd9b8c0e19e5321e5bc105add22e473050b71f0399327c7eba1ef809f8667c1f4e2c7172e10e753705e9a083f5bce88d77521225ecd9e89f1e1caed367fb0275dc28f620fbd67e6b176c9ae5d2659e6ec662116c9f2bbca3a93043233a4861e0688db6dc1800f752c5d58aa5033c250c891d9126e534ed921a9026eb333333fa8292059b8b446f336ca6a0cb4c7946b6aea3831653122f154a4ea1d7",
+ "23deadc94481ce28188f3a0ca3e85431964cb31b60fabf381e6bd45ef0332bd4dde774b0281d317dc2e7d0c298fcf8625fa734126968df8b68ef8a35c325d84ba4fc53936ff3ffdd8838d2a8cabf8a9cac54aa444ed9875944e55994a22f7fa8538b1e983b57d9215fac5c0052029644044e790ce2f5044655608c1d7ad3bb862203ba3aba3b526606f273d342ed5721648e3f600942d3f7546f679161436389d879dd8094e1bd1b1e12cde15cd3cda4c30a40835665e4e5cf94",
+ "94701e06340114f9cf715a1fb659988d33db59e87bc4844b1500448960af757b5282f6d52967a6ae11aa4ecfc6818c962b084c811a57724f5d401191567f24ce917e4f8c3963474fdc9d2c8613c16f62446448b6da6eeae54d672825ed7606a90e4611d0e318ff00566862c955b636b5e81fec3362e8672ad2a6d222a515cf410482836deba092a51a4d464dfbbab35c50a33437ac16a88256e9e23ddd3c827cc58d3e5000ee90b12e4c5175c5733662d4848ae0d406c2f0a4f498",
+ "735b0758d5a331b2304f01081172eb95ae4115de651b1a6693c5b9543de33df25d9f421dbaeca033fc8bff57313b482778005aa9fdcbca65c643da2f3320e34197868eec3848ff3c70d7ac7d910fc332e9a359f892ae01641be253013b554a0d3f249b3586b1857e5a0f9482ebd91432a852b221f4287a6e81ed24e8064645d5b28ab9a13b26cc1420ce73dbc47b31acf8a871601022ce23bc443b1222ce9a037a2fe5226295feb4efd4fd671338f459ae146032697cf82fc55c8fbf",
+ "c48d94f14549352790079fee69e3e72ebaa380510e3581a0824066413e7044a36ad08affbf9b52b21963d2f8e092ff0ac1c973c423ade3ece5d3bca852b894675e8173290529226939c24109f50b8b0d5c9f762ff10388833d99bea99c5ef3ebb2a9d19d2231e67ca6c9056d8834730605897426cd069cbeb6a46b9f5332be73ab45c03fcc35c2d91f22bf3861b2b2549f9ec8798aeff83ceaf707325c77e7389b388de8dab7c7c63a4110ec156c5145e42203c4a8e3d071a7cb83b4cd",
+ "553e9e0de274167ecdd7b5fc85f9c0e665be7c22c93ddc6ec840ce171cf5d1d1a476743eb7ea0c9492eac5a4c9837c62a91dd1a6ea9e6fff1f1470b22cc62359474a6ba0b0334b2739528454470f4e14b9c4eeb6fd2cdd7e7c6f97668eebd1000bef4388015630a8332de7b17c2004060ecb11e58029b3f9575040a5dd4e294e7c78e4fc99e4390c56534a4e933d9a45460f62ffaaba25da293f7765cd7a4ce78c28a85013b893a0099c1c128b01ee66a76f051dc1409bf4176e5afec90e",
+ "dea8f97c66a3e375d0a3412105ed4f0784f3973ec8c57b4f553d3da40fd4cfd39761de563ec96a9178804641f7ebbee48caf9dec17a14bc8246618b22e683c0090259e3db19dc5b6175710df80cdc735a92a990a3cfb166461ae713adda7d9fa3c4cf9f409b1467f3cf85d2141ef3f119d1c53f23c0380b1ebd728d7e932c535965bca41a414b6ea5bf0f9a381e098d282a554a25ce41980d7c7be75ff5ce4b1e54cc61e683f1dd817b8e2c1a430d7f895e5e7af13912cc110f0bbb95372fb",
+ "9dfda2e2f732867e60ed2b5fa99ab88eb82dc7a54334d02031258beef75fa4bd6962a1083b9c29e4eeb3e5ab8065f3e2fc732675b8d7705c16cfb4ef7305eb58120f1af5ddc55872a2cbde3a48661a0598f48f63e2e9aadc603545e2b6001748e3af9e86e1830af7b84ffd3e8f16679213d37cac91f07af0af02b37f5ed946ef5c955b60d488acc6ae736b10459ca7dabeacd7dabcfd656511ac913174f6d99327be59befe3e463a49afbb5235f0ce2840588c6edfbaaba00a4211c0764dd638",
+ "ddcd23e8b9dc8889b8599c721e7f8ecc2cbdca03e5a8fd5105f7f2941daec4e2906c654210bdd478374ddee43ee749a920ee91872e057a1157d384dcd111266221b3c79774476b4862fe450704ff2c5353e9a936cac87c96515c28ed4c830335a55d084cb5873c5fd2dd907f3266d8eb7bf13b6dd7cd4966982a0949efd8e428dae13daee549e01cc3c226211d6307823f742c5ef2155601a4644c46eddd603d4abd959c6d242e427768df3b1e22d87971df58a1564b38311a897c85b497a72556",
+ "39016647acfbc63fe55a74598bc1956eaf4e0cb49d532c5d8323fc6a3f15a0231597f06eafd74ad245e672bf6b21e4da503cb5bf9d15e9038ef354b38807564d91f38b4258378ccd9b9420a1562d7136196822a1291c913d83c4cd99fd8d420990c72cdc47607124de21da8d9c7f472fdcc780379f186a04da93cd87628abf323c8dadcd7fb8fbade37d7d2b5c9f9fc524ff77494c98f42f2158a6f68c906105ca9e8bb2df463863cfc1e9008d8344f55c4e3203dde6699b59812d49ce1279fa1c86",
+ "02cff7567067cbca5911664c6bd7daaf484181edd2a771d0b64566c3ab08d382e83932cdd7b4dbf86c9cdd1a4c353a511e68afb6746a507a9cd385c198246f4543d606c6149a5384e4ff54c1b90d663dc7a4b91aeac3cf716db7ca6f9a1914e3a33efe82e7ccc4215999c0b012782402db4726db1d7d1c73571d45739aa6fcb5a20eeb54a84d5f99902a8d356cbf95f34c9c28c8f2badfbc08c69233514493c0c04963268c88bc54039ab2999c7b06cba405936dfc43b48cb53f62e18e7ff8ff3f6eb9",
+ "5764812ae6ab9491d8d295a0299228ec7146148ff373241a510faee7db7080706a8dada87938bf726c754e416c8c63c0ac617266a0a4863c2582412bf0f53b827e9a3465949a03dc2db3cb10b8c75e45cb9bf65410a0f6e6410b7f71f3a7e229e647cbbd5a54904bb96f8358adea1aaa0e845ac2838f6dd16936baa15a7c755af8029ef50aed3066d375d3265eaaa38822d11b173f4a1de39461d17d1629c8df7334d8da1b6401daaf7f34b2b48d6556ae99cd29ed1073926bcda867421832a4c36c7095",
+ "4df3043cf0f90462b37d9106e67366d112e4938c4f06abae97869531af89e9feebce0812dffe71a226de5dc36be652e26ef6a4be47d9b2db5cdd43809a565e4fc0988bfe82037c505dd276b757b785203249fd083fb474a25acccc9f38dc5164ff9097e05989aa6e280739a755231f93670e7226e22046914c155bf33d135b3f736ccca84cc47ae643215a054b54b7e13ffcd7ad73cced9279dc3210b80700fcc757acfb64c68e0bc4da05aac2b6a99d5582e79b303c88a7ac4dd8ed4289516bba0e243527",
+ "bf041a11622715426c3a755c637d5f478dd7da949e50f05377bf333f1c62c671ebdbf9467d37b780c25f7af9d453fc67fafb2f065a3f9f15d4c3561eeaa73fa6c813bf96dcf02430a2e6b65da8d174d2558110dc1208bdcb7898e2670894c0b9e2c894da3b130f57a90ec8ea1bffd27a37b4da4645c546b2b141db4e2c919154dac00e78dd3eb6e4445974e3bb07905982da35e4069ee8f8c5acd0efcfa5c981b4fd5d42da83c633e3e35ebdc959bd14c8bacb52212b4334f94aa64d2ee183861db35d2d8a94",
+ "a170ceda0613adc9c3a1e427f07beacf3b16ed69fb42b6bc09a38d803f632ad2929dba215b85683b74e2feb1d18fe17d0ea0db84d1be4e2e73476917a2a4cff51d6eca7c5e82232afde00dd2286a4c20eb09800b4d5d80e7ea35b6965b9792d99e399abda8cf32174ae2b7414b9bdb9d63e148f7357635a7310b130c939593cd3479164724011966c4232142df9966f09422f34f20b30af4b640a2c6d3dd985fe0ba3dfa9083cbb9b8dfe540ff9f6c608d18481213040768ef33300d773f9890c724ead320a1e7",
+ "929477e9c2d0bbad3429a0e0de776695255013108261dc6404cb09828770e274d8bb650a50e490dfe917fc2047b0f8ee72e105927d9fa70523c727778cbf6ae876d641ad562938c870d12f2e047bb78920739dba0c3f8ce1fb77589623a5f1625f5d6ab81940c7dfc3dc3a641d82b2813629bab8282999317d6b93842334f123fb4693a9c2c9d8ba9bfc746642dfbd045cd2021b272eab7358aa954d453da53fc5392dfa7eb881f6f53809b692d27f3366595ff403289efcc691e118b4744a1147071d8909bef1e8",
+ "3e98bb14fff5bdf7db38a3960dc55ca7d02333daed8712cca13dd5bffd114636559279db72554cc0a0ee1f7e15557d77cab0f2f1131f94fe698db81be38300a856a5eca85e5cf915fb7b6f38ccd2f27350e62cc30ce10ffe835118be3d435d2342ed3d06199b7e20c8e34d68902f0ab8745bd8b7d5b863d525c1f5906d2dca598db8a0f1e67736182cac15677579c58b8c670cae1be3e3c882153b2aa2988933e579ec2d6dbb00c671da64443dfc027dee6dfc3233c99758304570a982bf9b2eb59ccd70d0b54c4b54",
+ "aa12c7fa50ffdc2811c1872e4bee15f43e6909212385c872eb489f7e06dc1787043f56126f8373bdfa4b3f61405c73dd4dfd3f40aa5cd207e8520849c26f67716a46c0989a99efff42f24e0736e327af8e607c401a1bac77341e9a78c91e35d55b2457bdd5317a405a1fcf7a2a23de68ef92b65819e8aa3807c545361dfc9fe89125123492da958dc313cb5d03cb4b192c54ac6b27fcbc498652f5ed36b587bb74942b3ad453a8d79e5ddc06ebf806dad5046b73251064582ef5777dc530f8701701761884783fdf197f",
+ "83e615cf6e17a29e63945710b548a6d9935850eec69830841e26cb6071e908bf72c87cf079ffb34c5eb1a390def72d004a9488224a18e189aa1092a0f1135712834d257a53dc1d0e2c6417d8f472ff13b181910f4c93a307420d44beec8875d5219a3160b8e921434ddf3f71d68db1c1d5c39d68edb7a604792f8b4e31ecda7895c99fc7031a5b98a22009c1da005ac8fd2da0b5d742743f5712d12fd76d11a18e487776ce21ca0d6e5ab9ca6d8c394c321b91c14e291399a642721361811a73b7392e8603a3004e7060bf",
+ "ae1a8f7bfe4b1a0fa94708921dadb2c20b938239d7b9a2c7c598528f20f49764d322ebe85a5b2ea15563cf2f2304baf55d6607c52e2e1160859dcb7af6d7856899eada0e9128a180d3de6fed9334ba52b80c5c362d5591a0ec30f86d37a399927eb1c53076a12d26775522c511c83eb5b7abc2a00bd2dfd5627a8febba53d85f9b74c4b7f0c862ddb0d9298899b646b774d6cc23e4e23ab47174fccd34499253996d5e0917210e2f6daa1685f89f2f1fdfd5509ebc38191d539ecfb54ff0f5bbe6ef36ea35d425af6462f518",
+ "1d033e06be253ab800c8176d3a9650ab2a5bcaa03e11ea95fb9ab3834b41eb0d1b2bcecfe219364c3104ef65a8d692bd77c798548b7d9a8faf7f5172db24ec7c93006d6e9839368291b8277a82c034a3731f1b2e298d6e0282ec8a7902e4f844d132f1d261d171375c646065e201849f2df73e3748d853a3122c2206aac92fea448500c5418ecfb3d80e0e6c0d51f85831ce74f6c659cc291f5348a1ef8b949f1b2a753633e382f40c1bd1b2f44748ea61127b6f568255ae25e1da9f52c8c53cd62cd482788ae430388a92694c",
+ "104bc838b16a641749dcf73c57b207ea3bcc84381170e4ca362065a3d492e892b426a1f4fd82f69461d1ce1f3aaf8fc291ea30d6667e7e1aea4c44f7d52a5fa6d34709e6658483260ff5da76bfb74e7d194ad40dcac00daf0e45e74db4bc2248100a8b256b257278c3c98f1f2e3a80cdb812352aaf4155b3a4033999fb9fe7f506994fcf3a8db31e9e5ca8ef8c2e9c6326ca5b0803724ba641950eca877fe6ed6afc2e014651c56d0e6a61eaff7c5ed0b861d4bebe42904c0a568c26aa8abb2e97da2bfb40f14eafb6bf16cd208f",
+ "5b92e4a175437d0a53eb10de2c56401720b11715a034459ebf506c3fd6534b5e817a0f09deac4bcfd353301d8d031b1331582ac09189b48e6ccea444655866c4bbd123d45ebabb774f877cf12d33b84cfca4a6a94f3f98869fcf2bbb6cc1b964c2438c2f348bcdf9001dce60a4706d20c169a040baa61cbeb0b8e58d505e6e3739ab03e110ae7efdf91347474033defbd1e86af322ec6456d3394699ca7ca6a29a70d9b10a38fe666eab2858bfe12dacb31568549c826c15af5b6fddf779954351be1872f04e53db7b3b5fbf61fd18",
+ "401cc7bd9f8227efaed70dad83fc8db3bd38efc166f0f11ab142c565c68ba9db680423a3d698b6f3476ef440051fd20b93f6a2ed045825567df5a65e3f62e4442ec396ad260a16a13a1dee46c7e8d88bdd7edf223ab76a9a787c1f4fe9925c051a4ca0e77a0e78baa29f36d193c862fd3a60653f544ea9e3f75f2f553891be8c1fb882f6a6aad118f576f3c2793efc67221b37a45ab6137434f6228cb002fc137b91fb8572c757f00736879453d64a8a868c131810ffdad9e9d028d132157ecb1da675d54047d19b27d3258c9b1bca0a",
+ "c20cf0354982ca6a19d9a4dbf78f810934db2373941a12c263adefa61a5f385c859bc47028829c531dc25ccc0004c7510e707175a102ec3c4b4c933e3f52033e67476ff5f864c446c042a21e6037f7798363d20267891b965879fde80af6b59d77862e3a229af01b7ac78b578e94bd9f9b073c38a627c1864df0083aabb17024bdab6c3c0f0f73d31d59480523a2f23b78baa0385c15f290114305d7f98786b7dbc17a8c2aad97448e8ea389e68ef71091a6a9735ac12ca5497b9171da11a93c28d3273f58b74e2e46279d3ce9d0b20d19",
+ "e2365c2754073b511f16a1881ff8a537541ca7362ae7b84223d3c7d1d49d03a37d6d05dd2b819af9705c015dacc9dda83474eb14b7d5fce6e8a8f8c58e870149338d320e5ae476da6749af45e65ffed550d225a39dc74ffd93ba7da476985d6f44e90fc8e82454496260458431804d802fe804d825f611772f9710667377adfb1a11e4275bcecb42175c515f6a9439a359824f82cc9d480954364e6693099a821ace362e6c7ecbe68be8823bb5b49b4f23ad81b64139e3b63d9d4d298a842f013ef0d91ce7915ee8f816c70ba2aa3994216f",
+ "9c43944676fe859327096f82049cf69e48b98715878400fdf2805e0d5ee642e6cc9c43739f418b701348a033c5cb96bf8702fcd2fac9be58262a843c1e4155ed8a1724b6ebf7cce659d88a95a0c54deb2d7d9574a45219b6419ee173d1d8fad3ace47c962b349abe1048565df85bbd0eb9b11698258c23598023a00fdd26573e41951452027125c6e894a97736ecd63fd15b29a55d8dd9dab7e2e18f541a2e341890a61b7c896e7dc67aa82f3479dacd4a8ec7558d40c34d9ae4060e13718d676c2450258d83de8a86e012813693098c165b4e",
+ "1c707c29582d98a0e99639211102f3f041660ca03ad0939fe3855b8c1b22d6a9b8673c93e3eabc0ab231509b2b0d73c76a290a363943d12d2ff0ea30c6dd54eda753767effe04cabb4c3966388fa4c83a1906a0f48519a5fba9aeb585e0f8c45d6123a75ebe98fd1d0272f733a3925119481a321fe7509346c05128302851ba17a137f956f184e057a305e79a148727a5926de6854eb0314d5492fd735fa773d99ea34c95ca7546bd3a3aa8e66bcc6d860cec3d35d0e2165d5fbe8be99b6e7967df6693e5a6243e94c9c4a2528ae6305cbeca209",
+ "8f1e88103ffa378f062cade0ec509bec99a5c73fb273e79dbef24abf718ac26ac23dfd2b8932038ed3cb9637b71643c161142019f45b25b4fa4c52356737a27027e805ec635154327a66bfe64efc6285cca98c34edc7fb6c0766970a545342cf840aec0a5ba1dd3c6949be4fe97b0f8c8186de07536fd9074db34d09b2f08af9dcf9424d6edbf9cd044102c0e5dc35aff78c36d079dbd2c500e19c8c985ae2abaf6b2a20716bb719754a8840ce97632116c4d0b0e3c83ccca27f11c4204b76b5d6cfe6348a9615d8e4af53500dc4c2cabf12ec8c76",
+ "b9a0c28f1a6156992c103a84655fc6e654fa6e45e45819513afa797024717c00cc195994512fd53ecd1e12dac4d2448e0c40308382312084d2111f7db147b2e6589ce6d977f6115f629508167df8f45bac98abd49f6b272bcc4fd874dd5e29fb6daceb2d727a2a892194cfb9269eda00626ac89b4e74bd29b21e9f6ef18cb69889a02d4f0a06a2e5718899c1dc3b051c2cfa29653e782f87fefa478e6465bf5ff27f8b6abdb500077aac97100bd955ec535a587d66f23354be51cd8170289344bac9451f74e8aee3639f7c09981f4885e018912324d7",
+ "456844a34ae1074246f8f71eeef2010ec8733265bed7c1cc60043d770edfa320cbd4284a94be2574337e16d27f125074ebd7e99031f7abb4547b9540a7b0b5148ef501b550dd929f3dfe39ac65519f563e9254424aaafa05b1d37c16c771882e9e25d4906ac58603da749adf686932cd73d81e2658134fe69294c7a521d257eaf2110c667fc9d6f09b52d24b93910e532184eeb96eae9d9c9750ac3c39e79367431ac1af7011172d0a8be46a31010219a0310a733068c589bfc4748f3626aa4ff8d355cc893d05111c287c9992e95ad47481a6c42d6eca",
+ "c5c4b9900b9727bdc24baa544cad5faf8340be6b3759361f53889f71f5f4b224aa0090d875a00ea7116772117dbefc3a81c6950ca7ceeae71e4ba975c50d61fec82e6d9448d3a0dfd10bb087bdf0673e3e19fa2aaa7e97eebf71f11b86034fcf5a61240c71444ac3da15ef09b27b3523d37d309e8722380f835c1aee4a767bb027ec0674040853e5b53d6a31657f51acff6d2487860becd5ce695696cfe5937f4a0217b69e01cc6facc24dfe5f5230b8692a0b718e3b3c789d682db36101795a9a5f8bbb838c3679be72f7941a1db180135347d0a884ab7c",
+ "1781df2fedd2c39137854737d054cd3ed16b0ade411e41d97888ac900fdb46d9ae26b3d2dd07e118fd57eabd0dfd03a55793c76420666444865371adffc9b2f35068a0d70f9cfda1ac27ccb4beff4ffa5b8bb8bddac843386675c38a181fd0d935d6d51b25d78e7ff4ecef27a9853c0f0d2879c395ed1c4883987d123890d04f851c3e042e1164c68c0d503de16816f4b0e554236e5f4c339ea11d01ce652f6208f78f457a2417a97c0a6a240f443262def4b6763abf53e597bf1a28f907dc7cbdc751a234ea7d75710ad5ab0c37e8e9805102a375abd44011",
+ "8963552ad1e729ead07750df599d734157aaa4bcdcac17e8eb19b4f99cdb162686ff433137aa4e8a0cc8df0053999196262115aec326cf37567d9ba4760e0ad21d5763977f1ab9b35c0fc667890fa87fc946ceb776a811b5adc69446bfb8f5d9908029dc5aa38db816e4a4e8f98e5a48cf0a01627031c5bd1ced8bc1940dcafe4ae2f1199b186468eafc07e96a89d95dc18ef0fed3eda5b58ce58f221a47ba5311313cc680367eeb058fafc7bcadce5f520b6371489d9e529278ae6ee2650a85aed82896879038bbd9aa8d685fc9528943ccf2235cdf69a86464",
+ "23ceae3008085134433f5de4b47bafe0f443d443491e6cd47b216dd2dcc3da65239515a6e6b9beb9a939ae9f1f1f5e11f88326475e0962f319d9bf75ddfb4a46e7cc3f799d7547f3c0b2e089018b75787b82ea1a7295e7411f4852f94c94170e98bb0647923b8eb7d184038e56560da46085540cbfef82b6b577c445d038f6c93fbfdfc96ab3a0191d20a57b8610efb4cc45cd95198198e6f80ac46b0601511885f650eb00992605be903bcb46cd53c360c6f86e476c4c9ca4ad052eb572bbf26eb81dd9c73bcbec137aea6ee27aa97dadf7bef733fa1555019dab",
+ "c0fd31e82c996d7edef095cccfcf669accb85a483ea9c59f368cc980f73da7202a95c5156c34192ae4ebf773c1a683c079b17ac9d08b4265b4054fcddaf6666ca50f38f1a2ef2497459a68c06837363a526e850ecfbd223f55dba67db017eadb7a9139abb5bf3854834478b838aafa16c5ee90ea52fb2f7b8db2bcefb85b06fc455c2b6c27d0af9a49dbf2f313bf2599370637393e7972b31d8bf6759f3e6115c618e672831f84d76ba1879c754144e1df4d56b1e264b1797dcb8ab165040c8d20b931071081d7f74fbff590bdc8e888e71acc6a720270da8db7c821",
+ "936fdab91fba396e4a8754a97a04ba333daadc29885c9d0c8fea3387165278f4974e468fea57f2bfd8428c4d0f010833283db73735d39de0c0cb5898d0c06c0ecd05f61098935cb6130a8da60d1a6c2ecfe420f972263fff5a631b09e81c837183c5528bb1c740b36fc39cb082f3383c2b4afb25d04ad1d1f4af63dcf26a0bf5a647cd2e35a51cc119c4dc5031f5715b3bfa1f2b92de06bdac0d670fdd30980f32c51f3936b51e5db6b95a8d36279da5faa4c4e454f2b7e54e9f488071011c7f6f9b63da260a2e46d796d36c9a9dcae88085806a10a77bbb670d475778",
+ "a55fe162b287bd6eebd6cf7e7aeea8672322d924ae42c7404ff89aedb98943f3755d2889bca488cc7000e6e9b8e7a0ef289273cd29c44cc600e330d1775e3cb767f12150e1615dca8c3f67466463a3ca993a1b788cf67a7a35b95dfff954206eb5ea1e1bf7fb06482a551625b5c9fd9a86e8414c8cf79d3a14104a153cbe04aac5172aa4c4a89349f5856c4262dd1d7317a7544c9afbbed449e7dcc2b58d9df6c9c9ed3883e42e80f5c2433550f30e73c7bce0fccdd880adc19282a392dae26a0108e7faf168cfc15937aeb046d60712603286b8ddfb27916b79242d56f1",
+ "2bd6976592408cdbc4e41dcd3ecfbb786775ddedef914d9058e6753f839fdfe15b17d549dbc084aa6cdf3befa0158aa84c5d58c5876144fd7e6c41ab7d42419d0dd353732e0e6d3fafc4f5626c07433390a4fd467197e85b5de7e2cf1c26cc575356adedcc0740008523b503df12ff571387726c5ccb280376d19cbacb1d7ce7aab8b13292c6a8b8881e949cbf6d4610d16ebba1d46cdb8d0459596e0aa683d0307bd926e14de19b9bfeaefa29d91b82248604673a455520cbb64eef3f38cfad8e126a3b1cfa1aaba53a784c8ae0c50279c0ecdab54095d36f67ace9b8ebbb",
+ "71913ae2b1c8729ed6da003c24a1d4f96e28d7faf55ca14ee0b2865282b9b61103ce6ee0b00b00aacf2081adedea5616f9dfd22c6d6d4f5907bcc02eb33edf92de0bd479794f51246d9b612b4543f6ff633c4fc83bfa6144c9d26721cdc690a3d5a8db54d8bc7873bfd32924eeb502810732b5ac2f1852bb021c401d26c39aa3b7eb09083093a9e89bf889b53383b5af61110aca1b9fdf38908c7d5a184fc5f46b3423a66a2749feb8de2c541c563987278dbd0513d99b732411012b5b75e385510de5f6839c3797dc094c9501d5f0504b06b43efb6e746f2129ca189c1da424",
+ "9d048a83294de08d3063d2ee4b4f3106641d9b340a3785c076233686dd3382d9064a349c9eaa78028d35652078b583e3f708e036eb2ced3f7f0e936c0fd98f5d0f8aa91b8d9badef298bd0c06843831279e7c0c67ca7e572f552cfdd984c12e924c08c13aeec6f7e13d161785546ebfd794b5d6a92a4744e52c4cab1d0df93b9468be6e264e8cfcc488f9c3c1817cbe501f4b9cc5999483b7433aea777226b25273a6ef2331b5f3b6db8091591e8e276015da3ef78bb2ee0526ffe23def2d8d193cbe594e8ced1f3d216fcedae2a1eb288da82e34cf98aebc28def658ee0849ae7",
+ "3251c96cbf82ee2e5264528c0b6cdfc23d20e1eb2d6441b5d62f0fd24c692a0d45a8bc8aac32884b7141ac0f4f113ec9fc7f6b4db3d696374177f9a42d602ca471275b928f639105a55b846da9ac7274cc37de8c38541f6895f94d72a81e117844b46601c201f7189b935a96e42505f2098ac985d92dfe86349a706ef6325b3c2e4060ced3c453e68ed09e043bcc75846b80118dc53530248da250fb57922d0afa53a7b2c89161aa4fa372a46b2a8e1307741cecedf585d2f998a9d496763800b6965c38a5d8aa566c709f13699c8185ab4fd8fdc8b824f4dd6d1c255b4788f50574",
+ "2de31dbc8a012254586f3229d3524fc529554e98850d30acdfc11406bba6a142029126ac165ee90b2de7509fc3571a8ee12e16b05054eb8baea879d135b39627f0d8331be3e66bc720c2096ce74e437daebf3bc53d8f2ccc228c3256d3edb6e9ae7c354a0c9350e6d663a9a30630bf9da3d96b96608a2a171ae28105714058b6c4b38a36c56561c4612c32aad25c65b7fb6faa4e4ecd44ebf9b2fad42ff9a807cda2581614fd30d41a7436069399b8d4f062a37a5bd4066a93d541fa5797a7d3e7dc9c4c40f0bbf5256f71613240f9ef128b3423eacaf428ada06b6a531f835281e4f3",
+ "07dadee629a08223dcd7ec441287b4c5e26347451d9c003e3a8496b4ea313b51126283a6720d7851e24423d9c9c818b4601247178f38a61f45fd4c8596d79529d416834226666a2c8552bbc901cc5cc3406a18fc88077fea52e1b620748553052ab7788c0d025b095b736fbe714cb3a968ec16b5917652eba2d7cf32ef3140d6c27b25d053e9786d24cd09a5306a0ef55e46201faa6196a91084267d7a7b5ca57c2efdeb2cb97d682d2a191b915553c8933f1d1b7faf0b4a1d83ef611f1e44438bc1c3d860fbfd12b5f26e5a6889a31ce26ae6a55c7a563b5816d113423ef3f25fa9befc",
+ "1d94166bb387526d519c4ce150221954da8930f66765fe6a5504e30a69962d595cfdd07a82c003843598864261f053bdb6f5086d516c261e089caa89990f0967605768ae9200bdfe4dcd7b77a93265cb33d9851a2a1036113c732bf3f37534530641300f0620de5c16101e16f4baf39d9fcbfcb01c52afce0992c329d8dbb438c314eee995c5020611d6f889e06b8a032785cba9a415580dbf752b5e510523c89f478cc6f047bd926f51e4a965c9749d1e76379c0e7e5b56803893bafaa4d2892b4c52f143b2fa777cd1035ea418684b8019df084f9a3f1f768753096621f342895c510d01",
+ "fc0073f199ed8a1d6edc8e7bdf182670003108d82b283aba82326e856f8de378987a03d0fe8d2041440fd29d51c63796aab44090d2b14ee00859b3a08cbe88f724badcd3c401226c5db8b307b8deea5be305412b080e9f99cf79d6d08d3646f347a7afebb62912e3e246e2e726f9aec5c101d916e47f984507b1d65d313697256c77da7eca3bc5811c87bee02a2826cefff0d92bae989609aaf95d70561b40d98474c37277c884aed887a1606d206b11e8a8a71d1f1d19319557b57351228ff0404be700a6cc56c0a30f3d4b7a0a046463fdaf19e7d5f59e155f378e35baa33db1e881f2207f",
+ "f42a6a91278d6a076feba985b1cf4ce0af1fa9d6d039c136e8971e665ff088a10b6b9a379a6f5526fc5957773a0ccb8972a4a19be0745ac13937030a54b18dee4f4c5df47a58a33a7516b90e646e5da999166ab0e52f457f7c9b7e391836a687eaae37b377e59a4c995ab0c57162c307ab951a9ba6590f429cd27250e7010eb794ec1b1ec35f8aad189b2fd3e8aff24d93601d91a4884e6f84b02757ce7620a02901519fccfda52f68ad6df709d112a9c25d66bcbb9622806427ca8b8d346b6db05874bde800cde9cf17df4b05baab0f133febd1ebbb053b49c109a7f5b1f864a304d10288e2f0",
+ "bbcefaf4a0739509f8a2f831c954071aac52e60cfa882a867b8b910dcf7edf92e1c0692bb027bc378c460a01cb6ecc8f2a012dd84ee5a678cd497b1457b6d393421fbee98ff544fc7eba24cbc3aae506254d9a2d74dde74437ce4c8a69010718506bf4c5943342a942e5e2d3406a3016280b6e37954c5d5e763346251afb0b746cad68cac757f9df765e092518729cfb9a5e76300c124e708ca33591a369767ffb63933cb72fba67beb2223d98984d0b75eb5d1a38615913747b520b3d613c715c0c77d2987bb88f3c419bcc5d38573cf4a8a4f550b2d876f05ca252d88c70a561d869a5018b32f7",
+ "dc2437010cb05d9cab2af5c275e1d2acd627ce19fb86355df91fb8d059e60d591663c8eb077d48388c9a321057a98136f49f0098348d9f29d808936f98bb1787c7ac75fb14f6076dfd2de5b59b1fa4848cabaa9a99a091dc24b561911c392ecdbe53f4adae82b852d830adea3a10490c908e337ce0a6d12354ce05a37ad3a06696b66820af8a1f67e6287533fd6f38a5f6ad1c6b078c08baf2c37d2683af01e6a5b33796c8ae48935a888f9bd265f4f11a4e27c433b8b1c9afd140bcd21a07e24378ad6badde8e47c57e3340f49e2406e8d49afadd65eaaa4c3d078c27d7e42118cb86cd248100a356",
+ "6c290db326dd3152e6fa9b9c0cd7d49e50a0221b96e32f5f34a8cb7d0c2edd3e937a7d025d6999b7b468add4d6894d8f7aceaabc18f4d9c171f1fe95ea1ae8570382a8450fbc595d95b1f51d24e1abc2970b0e1d20ca40aa21bdfb3656adf2f19882eda606f5ef1c03174e1d94c8d12f0fee8dce6852f42a364eeafa27a7971d4379405db8e46baac4d685b969238e5df06292a6c790bf1994a051b038e1d8db91e1bc4804f32443781c34a552ed2e8100cea374e77af56ba0e11c45990d3ba68df9087b1f4968cbcbb1c42f99b7267c76af926ff3134e093df28fab039cad420c6b70f2d9b5e678c155",
+ "ac724a22ebabaedbbb052953e3c264a4b6440f313bad501cdc1484b64f33402a2230898776db5c818c28035ffae6ea24abd04b7159e42159833903a0c23a7c564f7645e49ddedb748fd9e51bd6cbf2eced98caaa35226970f003ce1fd260ac5795e096f1c04aebf8fd36e5e2adeea929b5e963a3cb71d6b55c85bb7d3a2b03a7e74b4416de8fa68950168d7c3ae8ed2e29bad1e8a182a7c5418e5d564373163778cd3c34e9d320eb1a60480a8f98b12e0026cbd7752e6079812e3767d9f55f3f10b8c214a6eceb2a58954091a06b33862af171a9b60bf2c6a44e8766e6c56e98092c56f2a8510f6d05c103",
+ "8c70114f7cffb375c2b9a06e27297a5c32418b2daf68af5bbedcc7106edbc070e764bf40c1f8eb15079e2ab77f898afff3490108ed9afb7ea9cb05df41d263be0e42d2321d3d2656622d7bd232bf68d37375fe7314b09cba66f19c8b59424198ee69e7a9f3de0ecce0685127807ce336fa479ccaf7aa1ebc4e406271ce6c4923ec36093516498cc227f9218869346c80ba5ae83e023aca0ae2bc86b5bf5d115a4616b6587cb869d92f8c780ab70d5766de07a204af5e1c8dbba622516d2e911b36c82e4687e4d258ea616c07f76ff0baa376c8d5975cffac0b25817f779ae3ce88b72eb47e378484ce999bf0",
+ "0733d59f041036398233fd47a84b93f6778ae5259ef5d62aa3b9faedec34c7edb570c18b2a5d2c4c55cf656d98a1ae396d45a3b746b7ad6f07312c3d05d1a50ffa90bcdcdba105e25b7b0c52664223f8c2476925d46dc6ea2406ded7d0b0b292f6656cebcc7616cfa4b82aec68b35d1da67f6ed2bf0171849d6bb65128d8a140ea5cf97f1003f8d7093bee077be78def4f7bd2caccbf0644f26b26285225142c40038484c3bb9ba9597744f4389e76dca3eb695c33ccc621cab1fb603cb3535a0ad318d220385d5e94f8674f3d55e97e097f8d5c049e911946afbfce783819951d65d6bff4567dc951390d1aaa",
+ "398ddbba3dcb5642c102efa841c1fcdaf067062e7eef8e2ee0cd73d7f77e57372d6ee1a9b7b6f86ad12d575001ae71f593449cb5a476c6bfeddaa2af0f9239c1d7effdedf66ceaf413707b5ab9661a7cc0ef8cfe4d1651579c4f0f64e2d12a52653c54f2dd60864e769eab8a627c89c56ee93365d031f0d2523cb95664b1575d51b122f33c9e94de75432a690658c977b68aa5b721a393f9b9b3b612c10e920a7d510c6d8460b35f8614c42f5d2c241a01b28105aa7c1b521ac63ebbedafac6d5a38c898e8590f918a1927bc53aecc2b1c8b18d7df9107c6997d9b3fa4b0bdb1c603da619d9e75670b97a5b40f06",
+ "ef07bbc7c4150dd47f8c69a7989948fe831dc798b0424dcd6551bfa8e88216095a7e5d720909bf3d23526b9ba464b66ff6b63a7337c31451ab9a15f04ead809a62bb52206237de77597a730106d02d227dd6099ea9ee2a92cdc446ac3b9d024e32255adb3e9b56b561c431e0b5a721f0336f19568a5335d0ebc6c73ed8ff2c15e219477d9e4b67f2928e251f8a61a2848857e037d010806c718ab062967fd8e85f3722252957923f5f9005aae47b4b1b3fa464e3ba9df573a56055f17e903126fbbcb6cb96de92fe617c97f84ef3ba0d8f2651dc4aa80c157f372ae1bc02e5067ad076f3fe48bb72c0f3c99273f82b",
+ "c7076986d2333f3a6752adf11f1a9e5c6bc4755f341073cc86a9c7519c8db029d5ae833fdf3fee826ff4692c57880c5074620ea97c00f1dde1e8a0f18501627984ded4d1b5c4af35be5cc1bcc868060a49a968dc0547acde490b4c68d79924a93a986aa0ad060c7de706e8a99ce8f84a4f8707b52a8ee122b763ba580d6b1f35f6af25094c69f49247da96c836991851ad36f60bf577863d7471608a012afa7a56656abeee7cd9b4f1f4d9d13a8526c0f33cd251caf7486639e787250390e7e488e9ec311fc3d847a7266cc59bcc2bc34192554aa57cf25db10ce04bdabef3fde6db85f55195ecc2ff892b2e268ebea6",
+ "01789f40d42d8d3e4a416fd9ae7de78c3a30507809eda200e1afaaf8d7020cd1fad18eba62d821946f220506cf105ff0e2069a771a2c233714afa6b2f695497e4b95c9693dbb93ec4c9a14720676aa87ee31dd34e4e081756477032b4a57b328285f2cdec1b269754c474936927e93acc26012aff1bb36f30c2402aca0a9b9ce9568f5000e2c934263933b436c94f8d6589c89db7edabc5d03a8fe795fe50c5166beab64ed7c22662b984ae2c66dbe4c090b0df603b27c759278f8d66859afea3f6a8f02c2c2a2202b9fc29132256f164b5050a803b43688dc4c9ba86374a3522afba5d1a19bb3820b883aebc267627095",
+ "2c61944bd6a50da00ebb951d2b67d79fc6b6fb5aca83b1de3dbd7690ab756bb1e1a21051ccf1e24136ac8ccb42a2ee10be94d2cb9289d5f52b6f90e9d07a3478f36a1eb7d08c3dec52ca154fd1427ba92a4ecbe73a71bceafbd26e9a39d50821e2876d3a0c0e6e373b9795dbf72ea29cc439ff42706be798c90d4617b39c90ec84bf9fb699dc8a9a34e25d81759d6c57df45efb1d0d68aa51278564b99633ed5dc464bb7d53c5c21f798f33bcd868657ecfe75a1ed8149d394b398969ef624831b30f1458465bfd2fdf3f284f2ffc54bf2817b5fab2e02056e864f78bb6fd870c64f3609dab218f25da8060f756e45121e79",
+ "942fa0c68cc72f69518a3a7aac0cde45bab0e928b5cb2bd24d049fc313f74b6afa87c4e34150484f3b5200163f8a6472d04777928ecc49319539fc17d71a38090f55a74f757fe45781a3c09f08dcd3dd4c73c8533a5e00cf8a86ebe77fe45be2848574f7c5d25e9a0632a60d2dd41febdbf987d2a0487e4a4ce6ed5f49f2d741a88ecac232b1498253fa4ee8147bbd0f600abdf295e81f7570015aac5fe6ca7bb4a99bb3fc54287106d7fc1132a574af49db82a7b9a5f33e193cde527ca2176c52cdab672165e0fe5720f71ada57ee90060aa069ae2a0bfe67c1b71b17c601c3c2224bf9891bc11ba216e3ebcb51fd95b8d7cb",
+ "0d68cfe9c087ec116fe7572042385159cc705960f842aabad1ed1387ec1697f4413a23c6090041328fedd4b626c6eeaac5b5a71acc1fd1bb8fbd228857ac5bd045c364be7a5a26338ff04c99c4c473cf445a891db6422d1bdef4533442df171643fc36a092fabb464298e4194c9e2950884de13d113ee24160a416404c16ddc5d2476cb3fb80da543e6ed9105f6003977acb34e1fdd2cbdf7a00d5ff84350b74ac231418c0d88269d02d824802791ff42a51cc835deb9869a6023f867f82ef6dc0bfb03e6dfa835646bb18a4074773486e308aa39e532aaea4e6fb35dcada7e060f8282c371ed26d22302323d4fd142a85534671",
+ "45e24b167a0bbef1bd8f79dd047763d0754f36a7b623f298059d177e8ac994945c37d2c4af06f01318960301595941124592f2995af1459d854339998d3ae17534df2d9793d6e203857d02c98a0cd88991e641b3e640090ba303f87b907dca8ca462fac19ad079b2c82ea5b521ab891b10138b083b3d9fa214a8fe60d1cb3599c5d199c61a2cfb7ee2f39e5a5abad5ac4998b707545f73e92128d21803420526d2598a53bb314adf29a0ef56b94bd2221601eb53ecb8540e8fffd38fba7bd827ef255e4ef55491475c0f383a241f81c72af4e1dbf2a65cd4d18a497615aa0de2791a3511a7977a8d4d41492bfa4085f2fd4e8f751d",
+ "1c1bb695ae90e6e33fc1e8b2a62ab98bf835ac7193440f2351c8cdd830472b637d2fd9c9013cb83caef506abc1c4f7567706db6046b1d184579c7a9223ab1b35e32898c70a3c27628123ffcfa518612f080a2c4a9f8e0a927a47dc98307d2b48de9d5dddcb5c82f0b0e4e610d44f1baa9bbbf7f5a727134680bb7d1327b73b52d8e5e36dbb53971e99e699d79f75a3fc01316bd7012947d119d6aeb7f75b8fbf0479c03002148553fa0da450fd59d4f1bebc252caa11ed9bec5b6ef54279b5f8382b61cffc67ec03f4baa7ea476c31364b86aa8ccad9fd0818717f0ced2dd49477874b4341c602d7a1beab860eb476c7e3ce597e6926",
+ "7a3cd9bb2277e2c7f1134fe7233f0f7883c2db9fba80aa5742b03041de0fe589d9e5ea84470dabf41bb66816f3e33ebf19a0ca5aba1004cf971249b258ff26a98dbd0c37ec6cd574854109433357720040bafed4531e0079186b1e853e0ced35d08d27f6d732ed6e2c6651b51cc15c420a24f2dc36c16ef4b3896df1bb03b3963f9aaeb02a48eac5772abd5948c2fd0db2bb74e3351e5eabd681c4f413655bd94dec96b1544c1d5d2d1df4bdc26020d25fe81d5238de824687a5505e1fbe08d11b3924b3ccc070fd225bf01eb79e3d21f7b62a836cd3bcc11c931669c37613470e356143df87c48848a829f5e018973a5db88eb6c60203",
+ "3f158afd0733fcc5dfe1efc2dd4eada732f942af734ee664955bb1ba613eafd0f349e7554a14d68200c62d8f2dca2ec8b81c8350735eaf437041f78b452598825b6899560963ade66a0fc74ad01f8343d1d19c7bb327a8dc14ffdb1c42fa72b2970d9155e2da6a2e6419d4117842d826ff38ffab9617307a0283d3ea28c8104ad9a6e087bb750ed1d10fd8f7100b1663682e979d80e43968c33d9eff66f4d1344e583ee521e78d0a2193c0577516b978339c143bfc689bc744bbc4a9163063de82c9706384b6b385e54666c86b34f23c1e25be293af06092ca31d857e11e5b2caf0d19dd3afbe85380878eda76d718b4bb869c67e044e242",
+ "a177af4387b9bfa3d59e97ee7b0ff5f4ae4a326fd9204c8d28831a67fcc385ee6c4828247b16d11aea9bb8cd9e6c4d2876c6b2fa6d5041ad39e1b04039071e29c4d86417e7eac4fc7d3823958a021823e2c880a757dfbcd0c8196371db5bbfac15e4d1a0596508b6d26f8c4a664924c95082d173f817995b44c4285d625d9b2f56c86632fe1295c5a8a7a3760028072bcb07bc245a705e7174d06b9d5c0c8ca495b9ac218f1921fa63f2db3fd148f07545366d008fb5aead7497d902b91fbaa39669929d4ae9d07df8557f1f0aed7b51252f10c6606e5ff3ede1327530ca356b4896ecf14bf7322d77fddfbe28d52f6de7f66eeb81704c87e2",
+ "01a15b9018e35cc342c926b01d03ad9db4993a6bf92e0555969fee90033f28f3ec234c1268b11b040dfa0770d4ceb39edfeb8ee6a589f4eebcc08d2d1b0a1a52953aa26eb44fdf4a2743c3dacb212a0c0f325572f645f53027b6f3c0c55abaeb1b0918c89bedcb5028f094d743ea354f8ff553c45f111a8fd5a14a4e5c835164747d302472e19a67da04b4c8e39756a9d248ce14d1ed43de75aca86850f2455eccd4639b2af035bb3f504cc9065d091c1c47e036083cb3fc50bf39292b11737c7ce0b49673ba93981de304dc65a671775b6ff927e3ff93850b214fffb5792105a4bdc81354d5b09e84afbdd1792b8fb4e9d0ae3dad2492b03282",
+ "24f07ae31279ceed18ec6d35990f21200934ad6b132c6c62e82fe92a40a0e60a5bed10720eff5a1f728971888682772b2d9060d4fee88f37d0824e7384dddcc549475f0e1a44eda4804778b62febe46e04657a20577ee70acb3425e334881eebd8ddf714ae8c527ea747e3367de384e595a43b299b6bb3f6b0a4716cf90038e0f75a47d5057d7fcc3c8a8f9224992c67f8ae0d3251ea09a24aed9ce57ab637f6b3cbb7083df62b6287f64d0877984c4249d113bdb2b07865082aa24cd7ec07061b17de320f51f29f25b82d7073d369cf2dbf96310c0c311997911b2cc02f606f9cd99663c57e78499192a2a78f9c9fa67013e0f9817287faa69b22",
+ "4aeb32bf9d050f10bea18d9f71b4afea7bd08550e574e7d50df234c7413668b297b6721d7a0f0bdcdcceb2f55adddea28cd59bd44be0c5ec067039e428706caae11f565d961ad6e7f4c51b0aed6d05cc5b8d826c4b9c39daefb6c7da46dce619a359dc9ce215a215218fa8d54ee0b4f301b6c201c7c2c5f7cb1c6e0cb76ba6c6e8f63ef7a5213d550b0d0857fa0ff9e3e38e497161617413ac066e2fa539520233193a5cb7baa0c2cb20b45e56bfed2c40a9544d1f230dd0cd6d4976e7cf51da8a13200c3957c0154c8237b2931ce19b824963ac576ea49b548cc6aa85c47796b470fb2c6308d88f390bb13607e294c84a838b2713b14ca6a5e8bcee",
+ "77e607478be5502432230c913d9ec82f967d87c0ee169a74076f989648853eca693277287f8a5b306bc94dfdbf64ca5cb5dfc0bc498589d51a691b8d57d4b0a9ee247d038fe1b5571183be3e75c37045bf1235863ff1b84b208c10e7f1a5ba54ff36af5b2870129867164d013e0a6d2cc067a3509bba2f46390302c80b651cf590ef69aad8effd94cab28a9b44be6a38b58cfc47c9c725d6fa467894163383b6873d10d263b1cbbad932ded59ab503920267ac026726f794a335a88f6ef564f8968c6fa6f5d3ea161eb6062ca349b9a0e4038273399cfa297a6b07ceda1ebaa99c9de2d935ee230a08c5a488ad46f3393243371d40916b8063cac9da63",
+ "50957c407519951bd32e45d21129d6b83436e520b0801ec8292d79a828106a41583a0d607f853dc4410e0a1427f7e873455a75df065cfc6eef970f7e49d123b346976460aadd91cf513c140c356442a84656904a8b1d708dc6089db371c36f4fe059c62302eaab3c06c0cb3b429961f899dcf99798464b8571a440cac7a52b495f32417af6bc8f58adc63647531f804b4e96273b29b42434c1236bde80ba3744fef7b1d11c2f9db332b35bc25123338ac9a0796aac213c9709b3c514ea7ecd80e22d3d8a74f28c8194418a6e1ff30714d0f5a61c068b73b2ba6cad14e05569b4a5a100da3f91429d6e3ffee10ceea057845ec6fc47a6c5125b22e598b2dc",
+ "f2273ec31e03cf42d9ca953f8b87e78c291cb538098e0f2436194b308ce30583f553fccb21ae6c2d58f3a5a2ca6037c1b8b7afb291009e4310a0c518e75314c5bb1e813bf521f56d0a4891d0772ad84f09a00634815029a3f9ad4e41eafb4a745e409ef3d4f0b1cf6232b70a5ce262b9432f096e834201a0992db5d09ffa5cbc5471460519a4bc7cdc33ae6dfe6ffc1e80ea5d29813136406499c3514186ced71854a340701519ef33b6c82ca67049ab58578ff49c4c4fbf7d97bfec2ecd8fbefec1b6d6467503fea9d26e134e8c35739a422647aaf4db29c9a32e3df36e5845791fdd75a70903e0ce808313a3327431b7772567f779bbaee2e134c109a387",
+ "5784e614d538f7f26c803191deb464a884817002988c36448dcbecfad1997fe51ab0b3853c51ed49ce9f4e477522fb3f32cc50515b753c18fb89a8d965afcf1ed5e099b22c4225732baeb986f5c5bc88e4582d27915e2a19126d3d4555fab4f6516a6a156dbfeed9e982fc589e33ce2b9e1ba2b416e11852ddeab93025974267ac82c84f071c3d07f215f47e3565fd1d962c76e0d635892ea71488273765887d31f250a26c4ddc377ed89b17326e259f6cc1de0e63158e83aebb7f5a7c08c63c767876c8203639958a407acca096d1f606c04b4f4b3fd771781a5901b1c3cee7c04c3b6870226eee309b74f51edbf70a3817cc8da87875301e04d0416a65dc5d",
+}
diff --git a/local_crypto_patch/contents/blake2s/blake2x.go b/local_crypto_patch/contents/blake2s/blake2x.go
new file mode 100644
index 0000000000..828749ff01
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blowfish/block.go b/local_crypto_patch/contents/blowfish/block.go
new file mode 100644
index 0000000000..9d80f19521
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blowfish/blowfish_test.go b/local_crypto_patch/contents/blowfish/blowfish_test.go
new file mode 100644
index 0000000000..368ba872bf
--- /dev/null
+++ b/local_crypto_patch/contents/blowfish/blowfish_test.go
@@ -0,0 +1,274 @@
+// 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
+
+import "testing"
+
+type CryptTest struct {
+ key []byte
+ in []byte
+ out []byte
+}
+
+// Test vector values are from https://www.schneier.com/code/vectors.txt.
+var encryptTests = []CryptTest{
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}},
+ {
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x51, 0x86, 0x6F, 0xD5, 0xB8, 0x5E, 0xCB, 0x8A}},
+ {
+ []byte{0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
+ []byte{0x7D, 0x85, 0x6F, 0x9A, 0x61, 0x30, 0x63, 0xF2}},
+ {
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x24, 0x66, 0xDD, 0x87, 0x8B, 0x96, 0x3C, 0x9D}},
+
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x61, 0xF9, 0xC3, 0x80, 0x22, 0x81, 0xB0, 0x96}},
+ {
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x7D, 0x0C, 0xC6, 0x30, 0xAF, 0xDA, 0x1E, 0xC7}},
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}},
+ {
+ []byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x0A, 0xCE, 0xAB, 0x0F, 0xC6, 0xA0, 0xA2, 0x8D}},
+ {
+ []byte{0x7C, 0xA1, 0x10, 0x45, 0x4A, 0x1A, 0x6E, 0x57},
+ []byte{0x01, 0xA1, 0xD6, 0xD0, 0x39, 0x77, 0x67, 0x42},
+ []byte{0x59, 0xC6, 0x82, 0x45, 0xEB, 0x05, 0x28, 0x2B}},
+ {
+ []byte{0x01, 0x31, 0xD9, 0x61, 0x9D, 0xC1, 0x37, 0x6E},
+ []byte{0x5C, 0xD5, 0x4C, 0xA8, 0x3D, 0xEF, 0x57, 0xDA},
+ []byte{0xB1, 0xB8, 0xCC, 0x0B, 0x25, 0x0F, 0x09, 0xA0}},
+ {
+ []byte{0x07, 0xA1, 0x13, 0x3E, 0x4A, 0x0B, 0x26, 0x86},
+ []byte{0x02, 0x48, 0xD4, 0x38, 0x06, 0xF6, 0x71, 0x72},
+ []byte{0x17, 0x30, 0xE5, 0x77, 0x8B, 0xEA, 0x1D, 0xA4}},
+ {
+ []byte{0x38, 0x49, 0x67, 0x4C, 0x26, 0x02, 0x31, 0x9E},
+ []byte{0x51, 0x45, 0x4B, 0x58, 0x2D, 0xDF, 0x44, 0x0A},
+ []byte{0xA2, 0x5E, 0x78, 0x56, 0xCF, 0x26, 0x51, 0xEB}},
+ {
+ []byte{0x04, 0xB9, 0x15, 0xBA, 0x43, 0xFE, 0xB5, 0xB6},
+ []byte{0x42, 0xFD, 0x44, 0x30, 0x59, 0x57, 0x7F, 0xA2},
+ []byte{0x35, 0x38, 0x82, 0xB1, 0x09, 0xCE, 0x8F, 0x1A}},
+ {
+ []byte{0x01, 0x13, 0xB9, 0x70, 0xFD, 0x34, 0xF2, 0xCE},
+ []byte{0x05, 0x9B, 0x5E, 0x08, 0x51, 0xCF, 0x14, 0x3A},
+ []byte{0x48, 0xF4, 0xD0, 0x88, 0x4C, 0x37, 0x99, 0x18}},
+ {
+ []byte{0x01, 0x70, 0xF1, 0x75, 0x46, 0x8F, 0xB5, 0xE6},
+ []byte{0x07, 0x56, 0xD8, 0xE0, 0x77, 0x47, 0x61, 0xD2},
+ []byte{0x43, 0x21, 0x93, 0xB7, 0x89, 0x51, 0xFC, 0x98}},
+ {
+ []byte{0x43, 0x29, 0x7F, 0xAD, 0x38, 0xE3, 0x73, 0xFE},
+ []byte{0x76, 0x25, 0x14, 0xB8, 0x29, 0xBF, 0x48, 0x6A},
+ []byte{0x13, 0xF0, 0x41, 0x54, 0xD6, 0x9D, 0x1A, 0xE5}},
+ {
+ []byte{0x07, 0xA7, 0x13, 0x70, 0x45, 0xDA, 0x2A, 0x16},
+ []byte{0x3B, 0xDD, 0x11, 0x90, 0x49, 0x37, 0x28, 0x02},
+ []byte{0x2E, 0xED, 0xDA, 0x93, 0xFF, 0xD3, 0x9C, 0x79}},
+ {
+ []byte{0x04, 0x68, 0x91, 0x04, 0xC2, 0xFD, 0x3B, 0x2F},
+ []byte{0x26, 0x95, 0x5F, 0x68, 0x35, 0xAF, 0x60, 0x9A},
+ []byte{0xD8, 0x87, 0xE0, 0x39, 0x3C, 0x2D, 0xA6, 0xE3}},
+ {
+ []byte{0x37, 0xD0, 0x6B, 0xB5, 0x16, 0xCB, 0x75, 0x46},
+ []byte{0x16, 0x4D, 0x5E, 0x40, 0x4F, 0x27, 0x52, 0x32},
+ []byte{0x5F, 0x99, 0xD0, 0x4F, 0x5B, 0x16, 0x39, 0x69}},
+ {
+ []byte{0x1F, 0x08, 0x26, 0x0D, 0x1A, 0xC2, 0x46, 0x5E},
+ []byte{0x6B, 0x05, 0x6E, 0x18, 0x75, 0x9F, 0x5C, 0xCA},
+ []byte{0x4A, 0x05, 0x7A, 0x3B, 0x24, 0xD3, 0x97, 0x7B}},
+ {
+ []byte{0x58, 0x40, 0x23, 0x64, 0x1A, 0xBA, 0x61, 0x76},
+ []byte{0x00, 0x4B, 0xD6, 0xEF, 0x09, 0x17, 0x60, 0x62},
+ []byte{0x45, 0x20, 0x31, 0xC1, 0xE4, 0xFA, 0xDA, 0x8E}},
+ {
+ []byte{0x02, 0x58, 0x16, 0x16, 0x46, 0x29, 0xB0, 0x07},
+ []byte{0x48, 0x0D, 0x39, 0x00, 0x6E, 0xE7, 0x62, 0xF2},
+ []byte{0x75, 0x55, 0xAE, 0x39, 0xF5, 0x9B, 0x87, 0xBD}},
+ {
+ []byte{0x49, 0x79, 0x3E, 0xBC, 0x79, 0xB3, 0x25, 0x8F},
+ []byte{0x43, 0x75, 0x40, 0xC8, 0x69, 0x8F, 0x3C, 0xFA},
+ []byte{0x53, 0xC5, 0x5F, 0x9C, 0xB4, 0x9F, 0xC0, 0x19}},
+ {
+ []byte{0x4F, 0xB0, 0x5E, 0x15, 0x15, 0xAB, 0x73, 0xA7},
+ []byte{0x07, 0x2D, 0x43, 0xA0, 0x77, 0x07, 0x52, 0x92},
+ []byte{0x7A, 0x8E, 0x7B, 0xFA, 0x93, 0x7E, 0x89, 0xA3}},
+ {
+ []byte{0x49, 0xE9, 0x5D, 0x6D, 0x4C, 0xA2, 0x29, 0xBF},
+ []byte{0x02, 0xFE, 0x55, 0x77, 0x81, 0x17, 0xF1, 0x2A},
+ []byte{0xCF, 0x9C, 0x5D, 0x7A, 0x49, 0x86, 0xAD, 0xB5}},
+ {
+ []byte{0x01, 0x83, 0x10, 0xDC, 0x40, 0x9B, 0x26, 0xD6},
+ []byte{0x1D, 0x9D, 0x5C, 0x50, 0x18, 0xF7, 0x28, 0xC2},
+ []byte{0xD1, 0xAB, 0xB2, 0x90, 0x65, 0x8B, 0xC7, 0x78}},
+ {
+ []byte{0x1C, 0x58, 0x7F, 0x1C, 0x13, 0x92, 0x4F, 0xEF},
+ []byte{0x30, 0x55, 0x32, 0x28, 0x6D, 0x6F, 0x29, 0x5A},
+ []byte{0x55, 0xCB, 0x37, 0x74, 0xD1, 0x3E, 0xF2, 0x01}},
+ {
+ []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0xFA, 0x34, 0xEC, 0x48, 0x47, 0xB2, 0x68, 0xB2}},
+ {
+ []byte{0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0xA7, 0x90, 0x79, 0x51, 0x08, 0xEA, 0x3C, 0xAE}},
+ {
+ []byte{0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0xC3, 0x9E, 0x07, 0x2D, 0x9F, 0xAC, 0x63, 0x1D}},
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x01, 0x49, 0x33, 0xE0, 0xCD, 0xAF, 0xF6, 0xE4}},
+ {
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0xF2, 0x1E, 0x9A, 0x77, 0xB7, 0x1C, 0x49, 0xBC}},
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x24, 0x59, 0x46, 0x88, 0x57, 0x54, 0x36, 0x9A}},
+ {
+ []byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10},
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x6B, 0x5C, 0x5A, 0x9C, 0x5D, 0x9E, 0x0A, 0x5A}},
+}
+
+func TestCipherEncrypt(t *testing.T) {
+ for i, tt := range encryptTests {
+ c, err := NewCipher(tt.key)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err)
+ continue
+ }
+ ct := make([]byte, len(tt.out))
+ c.Encrypt(ct, tt.in)
+ for j, v := range ct {
+ if v != tt.out[j] {
+ t.Errorf("Cipher.Encrypt, test vector #%d: cipher-text[%d] = %#x, expected %#x", i, j, v, tt.out[j])
+ break
+ }
+ }
+ }
+}
+
+func TestCipherDecrypt(t *testing.T) {
+ for i, tt := range encryptTests {
+ c, err := NewCipher(tt.key)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err)
+ continue
+ }
+ pt := make([]byte, len(tt.in))
+ c.Decrypt(pt, tt.out)
+ for j, v := range pt {
+ if v != tt.in[j] {
+ t.Errorf("Cipher.Decrypt, test vector #%d: plain-text[%d] = %#x, expected %#x", i, j, v, tt.in[j])
+ break
+ }
+ }
+ }
+}
+
+func TestSaltedCipherKeyLength(t *testing.T) {
+ if _, err := NewSaltedCipher(nil, []byte{'a'}); err != KeySizeError(0) {
+ t.Errorf("NewSaltedCipher with short key, gave error %#v, expected %#v", err, KeySizeError(0))
+ }
+
+ // A 57-byte key. One over the typical blowfish restriction.
+ key := []byte("012345678901234567890123456789012345678901234567890123456")
+ if _, err := NewSaltedCipher(key, []byte{'a'}); err != nil {
+ t.Errorf("NewSaltedCipher with long key, gave error %#v", err)
+ }
+}
+
+// Test vectors generated with Blowfish from OpenSSH.
+var saltedVectors = [][8]byte{
+ {0x0c, 0x82, 0x3b, 0x7b, 0x8d, 0x01, 0x4b, 0x7e},
+ {0xd1, 0xe1, 0x93, 0xf0, 0x70, 0xa6, 0xdb, 0x12},
+ {0xfc, 0x5e, 0xba, 0xde, 0xcb, 0xf8, 0x59, 0xad},
+ {0x8a, 0x0c, 0x76, 0xe7, 0xdd, 0x2c, 0xd3, 0xa8},
+ {0x2c, 0xcb, 0x7b, 0xee, 0xac, 0x7b, 0x7f, 0xf8},
+ {0xbb, 0xf6, 0x30, 0x6f, 0xe1, 0x5d, 0x62, 0xbf},
+ {0x97, 0x1e, 0xc1, 0x3d, 0x3d, 0xe0, 0x11, 0xe9},
+ {0x06, 0xd7, 0x4d, 0xb1, 0x80, 0xa3, 0xb1, 0x38},
+ {0x67, 0xa1, 0xa9, 0x75, 0x0e, 0x5b, 0xc6, 0xb4},
+ {0x51, 0x0f, 0x33, 0x0e, 0x4f, 0x67, 0xd2, 0x0c},
+ {0xf1, 0x73, 0x7e, 0xd8, 0x44, 0xea, 0xdb, 0xe5},
+ {0x14, 0x0e, 0x16, 0xce, 0x7f, 0x4a, 0x9c, 0x7b},
+ {0x4b, 0xfe, 0x43, 0xfd, 0xbf, 0x36, 0x04, 0x47},
+ {0xb1, 0xeb, 0x3e, 0x15, 0x36, 0xa7, 0xbb, 0xe2},
+ {0x6d, 0x0b, 0x41, 0xdd, 0x00, 0x98, 0x0b, 0x19},
+ {0xd3, 0xce, 0x45, 0xce, 0x1d, 0x56, 0xb7, 0xfc},
+ {0xd9, 0xf0, 0xfd, 0xda, 0xc0, 0x23, 0xb7, 0x93},
+ {0x4c, 0x6f, 0xa1, 0xe4, 0x0c, 0xa8, 0xca, 0x57},
+ {0xe6, 0x2f, 0x28, 0xa7, 0x0c, 0x94, 0x0d, 0x08},
+ {0x8f, 0xe3, 0xf0, 0xb6, 0x29, 0xe3, 0x44, 0x03},
+ {0xff, 0x98, 0xdd, 0x04, 0x45, 0xb4, 0x6d, 0x1f},
+ {0x9e, 0x45, 0x4d, 0x18, 0x40, 0x53, 0xdb, 0xef},
+ {0xb7, 0x3b, 0xef, 0x29, 0xbe, 0xa8, 0x13, 0x71},
+ {0x02, 0x54, 0x55, 0x41, 0x8e, 0x04, 0xfc, 0xad},
+ {0x6a, 0x0a, 0xee, 0x7c, 0x10, 0xd9, 0x19, 0xfe},
+ {0x0a, 0x22, 0xd9, 0x41, 0xcc, 0x23, 0x87, 0x13},
+ {0x6e, 0xff, 0x1f, 0xff, 0x36, 0x17, 0x9c, 0xbe},
+ {0x79, 0xad, 0xb7, 0x40, 0xf4, 0x9f, 0x51, 0xa6},
+ {0x97, 0x81, 0x99, 0xa4, 0xde, 0x9e, 0x9f, 0xb6},
+ {0x12, 0x19, 0x7a, 0x28, 0xd0, 0xdc, 0xcc, 0x92},
+ {0x81, 0xda, 0x60, 0x1e, 0x0e, 0xdd, 0x65, 0x56},
+ {0x7d, 0x76, 0x20, 0xb2, 0x73, 0xc9, 0x9e, 0xee},
+}
+
+func TestSaltedCipher(t *testing.T) {
+ var key, salt [32]byte
+ for i := range key {
+ key[i] = byte(i)
+ salt[i] = byte(i + 32)
+ }
+ for i, v := range saltedVectors {
+ c, err := NewSaltedCipher(key[:], salt[:i])
+ if err != nil {
+ t.Fatal(err)
+ }
+ var buf [8]byte
+ c.Encrypt(buf[:], buf[:])
+ if v != buf {
+ t.Errorf("%d: expected %x, got %x", i, v, buf)
+ }
+ }
+}
+
+func BenchmarkExpandKeyWithSalt(b *testing.B) {
+ key := make([]byte, 32)
+ salt := make([]byte, 16)
+ c, _ := NewCipher(key)
+ for i := 0; i < b.N; i++ {
+ expandKeyWithSalt(key, salt, c)
+ }
+}
+
+func BenchmarkExpandKey(b *testing.B) {
+ key := make([]byte, 32)
+ c, _ := NewCipher(key)
+ for i := 0; i < b.N; i++ {
+ ExpandKey(key, c)
+ }
+}
diff --git a/local_crypto_patch/contents/blowfish/cipher.go b/local_crypto_patch/contents/blowfish/cipher.go
new file mode 100644
index 0000000000..0898956807
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/blowfish/const.go b/local_crypto_patch/contents/blowfish/const.go
new file mode 100644
index 0000000000..d04077595a
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/bn256/bn256.go b/local_crypto_patch/contents/bn256/bn256.go
new file mode 100644
index 0000000000..6661687551
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/bn256.go
@@ -0,0 +1,429 @@
+// 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 bn256 implements a particular bilinear group.
+//
+// Bilinear groups are the basis of many of the new cryptographic protocols
+// that have been proposed over the past decade. They consist of a triplet of
+// groups (G₁, G₂ and GT) such that there exists a function e(g₁ˣ,g₂ʸ)=gTˣʸ
+// (where gₓ is a generator of the respective group). That function is called
+// a pairing function.
+//
+// This package specifically implements the Optimal Ate pairing over a 256-bit
+// Barreto-Naehrig curve as described in
+// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible
+// with the implementation described in that paper.
+//
+// This package previously claimed to operate at a 128-bit security level.
+// However, recent improvements in attacks mean that is no longer true. See
+// https://moderncrypto.org/mail-archive/curves/2016/000740.html.
+//
+// Deprecated: due to its weakened security, new systems should not rely on this
+// elliptic curve. This package is frozen, and not implemented in constant time.
+// There is a more complete implementation at github.com/cloudflare/bn256, but
+// note that it suffers from the same security issues of the underlying curve.
+package bn256
+
+import (
+ "crypto/rand"
+ "io"
+ "math/big"
+)
+
+// G1 is an abstract cyclic group. The zero value is suitable for use as the
+// output of an operation, but cannot be used as an input.
+type G1 struct {
+ p *curvePoint
+}
+
+// RandomG1 returns x and g₁ˣ where x is a random, non-zero number read from r.
+func RandomG1(r io.Reader) (*big.Int, *G1, error) {
+ var k *big.Int
+ var err error
+
+ for {
+ k, err = rand.Int(r, Order)
+ if err != nil {
+ return nil, nil, err
+ }
+ if k.Sign() > 0 {
+ break
+ }
+ }
+
+ return k, new(G1).ScalarBaseMult(k), nil
+}
+
+func (e *G1) String() string {
+ if e.p == nil {
+ return "bn256.G1" + newCurvePoint(nil).String()
+ }
+ return "bn256.G1" + e.p.String()
+}
+
+// ScalarBaseMult sets e to g*k where g is the generator of the group and
+// then returns e.
+func (e *G1) ScalarBaseMult(k *big.Int) *G1 {
+ if e.p == nil {
+ e.p = newCurvePoint(nil)
+ }
+ e.p.Mul(curveGen, k, new(bnPool))
+ return e
+}
+
+// ScalarMult sets e to a*k and then returns e.
+func (e *G1) ScalarMult(a *G1, k *big.Int) *G1 {
+ if e.p == nil {
+ e.p = newCurvePoint(nil)
+ }
+ e.p.Mul(a.p, k, new(bnPool))
+ return e
+}
+
+// Add sets e to a+b and then returns e.
+//
+// Warning: this function is not complete, it fails for a equal to b.
+func (e *G1) Add(a, b *G1) *G1 {
+ if e.p == nil {
+ e.p = newCurvePoint(nil)
+ }
+ e.p.Add(a.p, b.p, new(bnPool))
+ return e
+}
+
+// Neg sets e to -a and then returns e.
+func (e *G1) Neg(a *G1) *G1 {
+ if e.p == nil {
+ e.p = newCurvePoint(nil)
+ }
+ e.p.Negative(a.p)
+ return e
+}
+
+// Marshal converts n to a byte slice.
+func (e *G1) Marshal() []byte {
+ // Each value is a 256-bit number.
+ const numBytes = 256 / 8
+
+ if e.p.IsInfinity() {
+ return make([]byte, numBytes*2)
+ }
+
+ e.p.MakeAffine(nil)
+
+ xBytes := new(big.Int).Mod(e.p.x, p).Bytes()
+ yBytes := new(big.Int).Mod(e.p.y, p).Bytes()
+
+ ret := make([]byte, numBytes*2)
+ copy(ret[1*numBytes-len(xBytes):], xBytes)
+ copy(ret[2*numBytes-len(yBytes):], yBytes)
+
+ return ret
+}
+
+// Unmarshal sets e to the result of converting the output of Marshal back into
+// a group element and then returns e.
+func (e *G1) Unmarshal(m []byte) (*G1, bool) {
+ // Each value is a 256-bit number.
+ const numBytes = 256 / 8
+
+ if len(m) != 2*numBytes {
+ return nil, false
+ }
+
+ if e.p == nil {
+ e.p = newCurvePoint(nil)
+ }
+
+ e.p.x.SetBytes(m[0*numBytes : 1*numBytes])
+ e.p.y.SetBytes(m[1*numBytes : 2*numBytes])
+
+ if e.p.x.Sign() == 0 && e.p.y.Sign() == 0 {
+ // This is the point at infinity.
+ e.p.y.SetInt64(1)
+ e.p.z.SetInt64(0)
+ e.p.t.SetInt64(0)
+ } else {
+ e.p.z.SetInt64(1)
+ e.p.t.SetInt64(1)
+
+ if !e.p.IsOnCurve() {
+ return nil, false
+ }
+ }
+
+ return e, true
+}
+
+// G2 is an abstract cyclic group. The zero value is suitable for use as the
+// output of an operation, but cannot be used as an input.
+type G2 struct {
+ p *twistPoint
+}
+
+// RandomG2 returns x and g₂ˣ where x is a random, non-zero number read from r.
+func RandomG2(r io.Reader) (*big.Int, *G2, error) {
+ var k *big.Int
+ var err error
+
+ for {
+ k, err = rand.Int(r, Order)
+ if err != nil {
+ return nil, nil, err
+ }
+ if k.Sign() > 0 {
+ break
+ }
+ }
+
+ return k, new(G2).ScalarBaseMult(k), nil
+}
+
+func (e *G2) String() string {
+ if e.p == nil {
+ return "bn256.G2" + newTwistPoint(nil).String()
+ }
+ return "bn256.G2" + e.p.String()
+}
+
+// ScalarBaseMult sets e to g*k where g is the generator of the group and
+// then returns out.
+func (e *G2) ScalarBaseMult(k *big.Int) *G2 {
+ if e.p == nil {
+ e.p = newTwistPoint(nil)
+ }
+ e.p.Mul(twistGen, k, new(bnPool))
+ return e
+}
+
+// ScalarMult sets e to a*k and then returns e.
+func (e *G2) ScalarMult(a *G2, k *big.Int) *G2 {
+ if e.p == nil {
+ e.p = newTwistPoint(nil)
+ }
+ e.p.Mul(a.p, k, new(bnPool))
+ return e
+}
+
+// Add sets e to a+b and then returns e.
+//
+// Warning: this function is not complete, it fails for a equal to b.
+func (e *G2) Add(a, b *G2) *G2 {
+ if e.p == nil {
+ e.p = newTwistPoint(nil)
+ }
+ e.p.Add(a.p, b.p, new(bnPool))
+ return e
+}
+
+// Marshal converts n into a byte slice.
+func (n *G2) Marshal() []byte {
+ // Each value is a 256-bit number.
+ const numBytes = 256 / 8
+
+ if n.p.IsInfinity() {
+ return make([]byte, numBytes*4)
+ }
+
+ n.p.MakeAffine(nil)
+
+ xxBytes := new(big.Int).Mod(n.p.x.x, p).Bytes()
+ xyBytes := new(big.Int).Mod(n.p.x.y, p).Bytes()
+ yxBytes := new(big.Int).Mod(n.p.y.x, p).Bytes()
+ yyBytes := new(big.Int).Mod(n.p.y.y, p).Bytes()
+
+ ret := make([]byte, numBytes*4)
+ copy(ret[1*numBytes-len(xxBytes):], xxBytes)
+ copy(ret[2*numBytes-len(xyBytes):], xyBytes)
+ copy(ret[3*numBytes-len(yxBytes):], yxBytes)
+ copy(ret[4*numBytes-len(yyBytes):], yyBytes)
+
+ return ret
+}
+
+// Unmarshal sets e to the result of converting the output of Marshal back into
+// a group element and then returns e.
+func (e *G2) Unmarshal(m []byte) (*G2, bool) {
+ // Each value is a 256-bit number.
+ const numBytes = 256 / 8
+
+ if len(m) != 4*numBytes {
+ return nil, false
+ }
+
+ if e.p == nil {
+ e.p = newTwistPoint(nil)
+ }
+
+ e.p.x.x.SetBytes(m[0*numBytes : 1*numBytes])
+ e.p.x.y.SetBytes(m[1*numBytes : 2*numBytes])
+ e.p.y.x.SetBytes(m[2*numBytes : 3*numBytes])
+ e.p.y.y.SetBytes(m[3*numBytes : 4*numBytes])
+
+ if e.p.x.x.Sign() == 0 &&
+ e.p.x.y.Sign() == 0 &&
+ e.p.y.x.Sign() == 0 &&
+ e.p.y.y.Sign() == 0 {
+ // This is the point at infinity.
+ e.p.y.SetOne()
+ e.p.z.SetZero()
+ e.p.t.SetZero()
+ } else {
+ e.p.z.SetOne()
+ e.p.t.SetOne()
+
+ if !e.p.IsOnCurve() {
+ return nil, false
+ }
+ }
+
+ return e, true
+}
+
+// GT is an abstract cyclic group. The zero value is suitable for use as the
+// output of an operation, but cannot be used as an input.
+type GT struct {
+ p *gfP12
+}
+
+func (e *GT) String() string {
+ if e.p == nil {
+ return "bn256.GT" + newGFp12(nil).String()
+ }
+ return "bn256.GT" + e.p.String()
+}
+
+// ScalarMult sets e to a*k and then returns e.
+func (e *GT) ScalarMult(a *GT, k *big.Int) *GT {
+ if e.p == nil {
+ e.p = newGFp12(nil)
+ }
+ e.p.Exp(a.p, k, new(bnPool))
+ return e
+}
+
+// Add sets e to a+b and then returns e.
+func (e *GT) Add(a, b *GT) *GT {
+ if e.p == nil {
+ e.p = newGFp12(nil)
+ }
+ e.p.Mul(a.p, b.p, new(bnPool))
+ return e
+}
+
+// Neg sets e to -a and then returns e.
+func (e *GT) Neg(a *GT) *GT {
+ if e.p == nil {
+ e.p = newGFp12(nil)
+ }
+ e.p.Invert(a.p, new(bnPool))
+ return e
+}
+
+// Marshal converts n into a byte slice.
+func (n *GT) Marshal() []byte {
+ n.p.Minimal()
+
+ xxxBytes := n.p.x.x.x.Bytes()
+ xxyBytes := n.p.x.x.y.Bytes()
+ xyxBytes := n.p.x.y.x.Bytes()
+ xyyBytes := n.p.x.y.y.Bytes()
+ xzxBytes := n.p.x.z.x.Bytes()
+ xzyBytes := n.p.x.z.y.Bytes()
+ yxxBytes := n.p.y.x.x.Bytes()
+ yxyBytes := n.p.y.x.y.Bytes()
+ yyxBytes := n.p.y.y.x.Bytes()
+ yyyBytes := n.p.y.y.y.Bytes()
+ yzxBytes := n.p.y.z.x.Bytes()
+ yzyBytes := n.p.y.z.y.Bytes()
+
+ // Each value is a 256-bit number.
+ const numBytes = 256 / 8
+
+ ret := make([]byte, numBytes*12)
+ copy(ret[1*numBytes-len(xxxBytes):], xxxBytes)
+ copy(ret[2*numBytes-len(xxyBytes):], xxyBytes)
+ copy(ret[3*numBytes-len(xyxBytes):], xyxBytes)
+ copy(ret[4*numBytes-len(xyyBytes):], xyyBytes)
+ copy(ret[5*numBytes-len(xzxBytes):], xzxBytes)
+ copy(ret[6*numBytes-len(xzyBytes):], xzyBytes)
+ copy(ret[7*numBytes-len(yxxBytes):], yxxBytes)
+ copy(ret[8*numBytes-len(yxyBytes):], yxyBytes)
+ copy(ret[9*numBytes-len(yyxBytes):], yyxBytes)
+ copy(ret[10*numBytes-len(yyyBytes):], yyyBytes)
+ copy(ret[11*numBytes-len(yzxBytes):], yzxBytes)
+ copy(ret[12*numBytes-len(yzyBytes):], yzyBytes)
+
+ return ret
+}
+
+// Unmarshal sets e to the result of converting the output of Marshal back into
+// a group element and then returns e.
+func (e *GT) Unmarshal(m []byte) (*GT, bool) {
+ // Each value is a 256-bit number.
+ const numBytes = 256 / 8
+
+ if len(m) != 12*numBytes {
+ return nil, false
+ }
+
+ if e.p == nil {
+ e.p = newGFp12(nil)
+ }
+
+ e.p.x.x.x.SetBytes(m[0*numBytes : 1*numBytes])
+ e.p.x.x.y.SetBytes(m[1*numBytes : 2*numBytes])
+ e.p.x.y.x.SetBytes(m[2*numBytes : 3*numBytes])
+ e.p.x.y.y.SetBytes(m[3*numBytes : 4*numBytes])
+ e.p.x.z.x.SetBytes(m[4*numBytes : 5*numBytes])
+ e.p.x.z.y.SetBytes(m[5*numBytes : 6*numBytes])
+ e.p.y.x.x.SetBytes(m[6*numBytes : 7*numBytes])
+ e.p.y.x.y.SetBytes(m[7*numBytes : 8*numBytes])
+ e.p.y.y.x.SetBytes(m[8*numBytes : 9*numBytes])
+ e.p.y.y.y.SetBytes(m[9*numBytes : 10*numBytes])
+ e.p.y.z.x.SetBytes(m[10*numBytes : 11*numBytes])
+ e.p.y.z.y.SetBytes(m[11*numBytes : 12*numBytes])
+
+ return e, true
+}
+
+// Pair calculates an Optimal Ate pairing.
+func Pair(g1 *G1, g2 *G2) *GT {
+ return >{optimalAte(g2.p, g1.p, new(bnPool))}
+}
+
+// bnPool implements a tiny cache of *big.Int objects that's used to reduce the
+// number of allocations made during processing.
+type bnPool struct {
+ bns []*big.Int
+ count int
+}
+
+func (pool *bnPool) Get() *big.Int {
+ if pool == nil {
+ return new(big.Int)
+ }
+
+ pool.count++
+ l := len(pool.bns)
+ if l == 0 {
+ return new(big.Int)
+ }
+
+ bn := pool.bns[l-1]
+ pool.bns = pool.bns[:l-1]
+ return bn
+}
+
+func (pool *bnPool) Put(bn *big.Int) {
+ if pool == nil {
+ return
+ }
+ pool.bns = append(pool.bns, bn)
+ pool.count--
+}
+
+func (pool *bnPool) Count() int {
+ return pool.count
+}
diff --git a/local_crypto_patch/contents/bn256/bn256_test.go b/local_crypto_patch/contents/bn256/bn256_test.go
new file mode 100644
index 0000000000..1cec3884ec
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/bn256_test.go
@@ -0,0 +1,304 @@
+// 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 bn256
+
+import (
+ "bytes"
+ "crypto/rand"
+ "math/big"
+ "testing"
+)
+
+func TestGFp2Invert(t *testing.T) {
+ pool := new(bnPool)
+
+ a := newGFp2(pool)
+ a.x.SetString("23423492374", 10)
+ a.y.SetString("12934872398472394827398470", 10)
+
+ inv := newGFp2(pool)
+ inv.Invert(a, pool)
+
+ b := newGFp2(pool).Mul(inv, a, pool)
+ if b.x.Int64() != 0 || b.y.Int64() != 1 {
+ t.Fatalf("bad result for a^-1*a: %s %s", b.x, b.y)
+ }
+
+ a.Put(pool)
+ b.Put(pool)
+ inv.Put(pool)
+
+ if c := pool.Count(); c > 0 {
+ t.Errorf("Pool count non-zero: %d\n", c)
+ }
+}
+
+func isZero(n *big.Int) bool {
+ return new(big.Int).Mod(n, p).Int64() == 0
+}
+
+func isOne(n *big.Int) bool {
+ return new(big.Int).Mod(n, p).Int64() == 1
+}
+
+func TestGFp6Invert(t *testing.T) {
+ pool := new(bnPool)
+
+ a := newGFp6(pool)
+ a.x.x.SetString("239487238491", 10)
+ a.x.y.SetString("2356249827341", 10)
+ a.y.x.SetString("082659782", 10)
+ a.y.y.SetString("182703523765", 10)
+ a.z.x.SetString("978236549263", 10)
+ a.z.y.SetString("64893242", 10)
+
+ inv := newGFp6(pool)
+ inv.Invert(a, pool)
+
+ b := newGFp6(pool).Mul(inv, a, pool)
+ if !isZero(b.x.x) ||
+ !isZero(b.x.y) ||
+ !isZero(b.y.x) ||
+ !isZero(b.y.y) ||
+ !isZero(b.z.x) ||
+ !isOne(b.z.y) {
+ t.Fatalf("bad result for a^-1*a: %s", b)
+ }
+
+ a.Put(pool)
+ b.Put(pool)
+ inv.Put(pool)
+
+ if c := pool.Count(); c > 0 {
+ t.Errorf("Pool count non-zero: %d\n", c)
+ }
+}
+
+func TestGFp12Invert(t *testing.T) {
+ pool := new(bnPool)
+
+ a := newGFp12(pool)
+ a.x.x.x.SetString("239846234862342323958623", 10)
+ a.x.x.y.SetString("2359862352529835623", 10)
+ a.x.y.x.SetString("928836523", 10)
+ a.x.y.y.SetString("9856234", 10)
+ a.x.z.x.SetString("235635286", 10)
+ a.x.z.y.SetString("5628392833", 10)
+ a.y.x.x.SetString("252936598265329856238956532167968", 10)
+ a.y.x.y.SetString("23596239865236954178968", 10)
+ a.y.y.x.SetString("95421692834", 10)
+ a.y.y.y.SetString("236548", 10)
+ a.y.z.x.SetString("924523", 10)
+ a.y.z.y.SetString("12954623", 10)
+
+ inv := newGFp12(pool)
+ inv.Invert(a, pool)
+
+ b := newGFp12(pool).Mul(inv, a, pool)
+ if !isZero(b.x.x.x) ||
+ !isZero(b.x.x.y) ||
+ !isZero(b.x.y.x) ||
+ !isZero(b.x.y.y) ||
+ !isZero(b.x.z.x) ||
+ !isZero(b.x.z.y) ||
+ !isZero(b.y.x.x) ||
+ !isZero(b.y.x.y) ||
+ !isZero(b.y.y.x) ||
+ !isZero(b.y.y.y) ||
+ !isZero(b.y.z.x) ||
+ !isOne(b.y.z.y) {
+ t.Fatalf("bad result for a^-1*a: %s", b)
+ }
+
+ a.Put(pool)
+ b.Put(pool)
+ inv.Put(pool)
+
+ if c := pool.Count(); c > 0 {
+ t.Errorf("Pool count non-zero: %d\n", c)
+ }
+}
+
+func TestCurveImpl(t *testing.T) {
+ pool := new(bnPool)
+
+ g := &curvePoint{
+ pool.Get().SetInt64(1),
+ pool.Get().SetInt64(-2),
+ pool.Get().SetInt64(1),
+ pool.Get().SetInt64(0),
+ }
+
+ x := pool.Get().SetInt64(32498273234)
+ X := newCurvePoint(pool).Mul(g, x, pool)
+
+ y := pool.Get().SetInt64(98732423523)
+ Y := newCurvePoint(pool).Mul(g, y, pool)
+
+ s1 := newCurvePoint(pool).Mul(X, y, pool).MakeAffine(pool)
+ s2 := newCurvePoint(pool).Mul(Y, x, pool).MakeAffine(pool)
+
+ if s1.x.Cmp(s2.x) != 0 ||
+ s2.x.Cmp(s1.x) != 0 {
+ t.Errorf("DH points don't match: (%s, %s) (%s, %s)", s1.x, s1.y, s2.x, s2.y)
+ }
+
+ pool.Put(x)
+ X.Put(pool)
+ pool.Put(y)
+ Y.Put(pool)
+ s1.Put(pool)
+ s2.Put(pool)
+ g.Put(pool)
+
+ if c := pool.Count(); c > 0 {
+ t.Errorf("Pool count non-zero: %d\n", c)
+ }
+}
+
+func TestOrderG1(t *testing.T) {
+ g := new(G1).ScalarBaseMult(Order)
+ if !g.p.IsInfinity() {
+ t.Error("G1 has incorrect order")
+ }
+
+ one := new(G1).ScalarBaseMult(new(big.Int).SetInt64(1))
+ g.Add(g, one)
+ g.p.MakeAffine(nil)
+ if g.p.x.Cmp(one.p.x) != 0 || g.p.y.Cmp(one.p.y) != 0 {
+ t.Errorf("1+0 != 1 in G1")
+ }
+}
+
+func TestOrderG2(t *testing.T) {
+ g := new(G2).ScalarBaseMult(Order)
+ if !g.p.IsInfinity() {
+ t.Error("G2 has incorrect order")
+ }
+
+ one := new(G2).ScalarBaseMult(new(big.Int).SetInt64(1))
+ g.Add(g, one)
+ g.p.MakeAffine(nil)
+ if g.p.x.x.Cmp(one.p.x.x) != 0 ||
+ g.p.x.y.Cmp(one.p.x.y) != 0 ||
+ g.p.y.x.Cmp(one.p.y.x) != 0 ||
+ g.p.y.y.Cmp(one.p.y.y) != 0 {
+ t.Errorf("1+0 != 1 in G2")
+ }
+}
+
+func TestOrderGT(t *testing.T) {
+ gt := Pair(&G1{curveGen}, &G2{twistGen})
+ g := new(GT).ScalarMult(gt, Order)
+ if !g.p.IsOne() {
+ t.Error("GT has incorrect order")
+ }
+}
+
+func TestBilinearity(t *testing.T) {
+ for i := 0; i < 2; i++ {
+ a, p1, _ := RandomG1(rand.Reader)
+ b, p2, _ := RandomG2(rand.Reader)
+ e1 := Pair(p1, p2)
+
+ e2 := Pair(&G1{curveGen}, &G2{twistGen})
+ e2.ScalarMult(e2, a)
+ e2.ScalarMult(e2, b)
+
+ minusE2 := new(GT).Neg(e2)
+ e1.Add(e1, minusE2)
+
+ if !e1.p.IsOne() {
+ t.Fatalf("bad pairing result: %s", e1)
+ }
+ }
+}
+
+func TestG1Marshal(t *testing.T) {
+ g := new(G1).ScalarBaseMult(new(big.Int).SetInt64(1))
+ form := g.Marshal()
+ _, ok := new(G1).Unmarshal(form)
+ if !ok {
+ t.Fatalf("failed to unmarshal")
+ }
+
+ g.ScalarBaseMult(Order)
+ form = g.Marshal()
+ g2, ok := new(G1).Unmarshal(form)
+ if !ok {
+ t.Fatalf("failed to unmarshal ∞")
+ }
+ if !g2.p.IsInfinity() {
+ t.Fatalf("∞ unmarshaled incorrectly")
+ }
+}
+
+func TestG2Marshal(t *testing.T) {
+ g := new(G2).ScalarBaseMult(new(big.Int).SetInt64(1))
+ form := g.Marshal()
+ _, ok := new(G2).Unmarshal(form)
+ if !ok {
+ t.Fatalf("failed to unmarshal")
+ }
+
+ g.ScalarBaseMult(Order)
+ form = g.Marshal()
+ g2, ok := new(G2).Unmarshal(form)
+ if !ok {
+ t.Fatalf("failed to unmarshal ∞")
+ }
+ if !g2.p.IsInfinity() {
+ t.Fatalf("∞ unmarshaled incorrectly")
+ }
+}
+
+func TestG1Identity(t *testing.T) {
+ g := new(G1).ScalarBaseMult(new(big.Int).SetInt64(0))
+ if !g.p.IsInfinity() {
+ t.Error("failure")
+ }
+}
+
+func TestG2Identity(t *testing.T) {
+ g := new(G2).ScalarBaseMult(new(big.Int).SetInt64(0))
+ if !g.p.IsInfinity() {
+ t.Error("failure")
+ }
+}
+
+func TestTripartiteDiffieHellman(t *testing.T) {
+ a, _ := rand.Int(rand.Reader, Order)
+ b, _ := rand.Int(rand.Reader, Order)
+ c, _ := rand.Int(rand.Reader, Order)
+
+ pa, _ := new(G1).Unmarshal(new(G1).ScalarBaseMult(a).Marshal())
+ qa, _ := new(G2).Unmarshal(new(G2).ScalarBaseMult(a).Marshal())
+ pb, _ := new(G1).Unmarshal(new(G1).ScalarBaseMult(b).Marshal())
+ qb, _ := new(G2).Unmarshal(new(G2).ScalarBaseMult(b).Marshal())
+ pc, _ := new(G1).Unmarshal(new(G1).ScalarBaseMult(c).Marshal())
+ qc, _ := new(G2).Unmarshal(new(G2).ScalarBaseMult(c).Marshal())
+
+ k1 := Pair(pb, qc)
+ k1.ScalarMult(k1, a)
+ k1Bytes := k1.Marshal()
+
+ k2 := Pair(pc, qa)
+ k2.ScalarMult(k2, b)
+ k2Bytes := k2.Marshal()
+
+ k3 := Pair(pa, qb)
+ k3.ScalarMult(k3, c)
+ k3Bytes := k3.Marshal()
+
+ if !bytes.Equal(k1Bytes, k2Bytes) || !bytes.Equal(k2Bytes, k3Bytes) {
+ t.Errorf("keys didn't agree")
+ }
+}
+
+func BenchmarkPairing(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Pair(&G1{curveGen}, &G2{twistGen})
+ }
+}
diff --git a/local_crypto_patch/contents/bn256/constants.go b/local_crypto_patch/contents/bn256/constants.go
new file mode 100644
index 0000000000..1ccefc4982
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/constants.go
@@ -0,0 +1,44 @@
+// 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 bn256
+
+import (
+ "math/big"
+)
+
+func bigFromBase10(s string) *big.Int {
+ n, _ := new(big.Int).SetString(s, 10)
+ return n
+}
+
+// u is the BN parameter that determines the prime: 1868033³.
+var u = bigFromBase10("6518589491078791937")
+
+// p is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1.
+var p = bigFromBase10("65000549695646603732796438742359905742825358107623003571877145026864184071783")
+
+// Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1.
+var Order = bigFromBase10("65000549695646603732796438742359905742570406053903786389881062969044166799969")
+
+// xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+3.
+var xiToPMinus1Over6 = &gfP2{bigFromBase10("8669379979083712429711189836753509758585994370025260553045152614783263110636"), bigFromBase10("19998038925833620163537568958541907098007303196759855091367510456613536016040")}
+
+// xiToPMinus1Over3 is ξ^((p-1)/3) where ξ = i+3.
+var xiToPMinus1Over3 = &gfP2{bigFromBase10("26098034838977895781559542626833399156321265654106457577426020397262786167059"), bigFromBase10("15931493369629630809226283458085260090334794394361662678240713231519278691715")}
+
+// xiToPMinus1Over2 is ξ^((p-1)/2) where ξ = i+3.
+var xiToPMinus1Over2 = &gfP2{bigFromBase10("50997318142241922852281555961173165965672272825141804376761836765206060036244"), bigFromBase10("38665955945962842195025998234511023902832543644254935982879660597356748036009")}
+
+// xiToPSquaredMinus1Over3 is ξ^((p²-1)/3) where ξ = i+3.
+var xiToPSquaredMinus1Over3 = bigFromBase10("65000549695646603727810655408050771481677621702948236658134783353303381437752")
+
+// xiTo2PSquaredMinus2Over3 is ξ^((2p²-2)/3) where ξ = i+3 (a cubic root of unity, mod p).
+var xiTo2PSquaredMinus2Over3 = bigFromBase10("4985783334309134261147736404674766913742361673560802634030")
+
+// xiToPSquaredMinus1Over6 is ξ^((1p²-1)/6) where ξ = i+3 (a cubic root of -1, mod p).
+var xiToPSquaredMinus1Over6 = bigFromBase10("65000549695646603727810655408050771481677621702948236658134783353303381437753")
+
+// xiTo2PMinus2Over3 is ξ^((2p-2)/3) where ξ = i+3.
+var xiTo2PMinus2Over3 = &gfP2{bigFromBase10("19885131339612776214803633203834694332692106372356013117629940868870585019582"), bigFromBase10("21645619881471562101905880913352894726728173167203616652430647841922248593627")}
diff --git a/local_crypto_patch/contents/bn256/curve.go b/local_crypto_patch/contents/bn256/curve.go
new file mode 100644
index 0000000000..63c052bc22
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/curve.go
@@ -0,0 +1,287 @@
+// 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 bn256
+
+import (
+ "math/big"
+)
+
+// curvePoint implements the elliptic curve y²=x³+3. Points are kept in
+// Jacobian form and t=z² when valid. G₁ is the set of points of this curve on
+// GF(p).
+type curvePoint struct {
+ x, y, z, t *big.Int
+}
+
+var curveB = new(big.Int).SetInt64(3)
+
+// curveGen is the generator of G₁.
+var curveGen = &curvePoint{
+ new(big.Int).SetInt64(1),
+ new(big.Int).SetInt64(-2),
+ new(big.Int).SetInt64(1),
+ new(big.Int).SetInt64(1),
+}
+
+func newCurvePoint(pool *bnPool) *curvePoint {
+ return &curvePoint{
+ pool.Get(),
+ pool.Get(),
+ pool.Get(),
+ pool.Get(),
+ }
+}
+
+func (c *curvePoint) String() string {
+ c.MakeAffine(new(bnPool))
+ return "(" + c.x.String() + ", " + c.y.String() + ")"
+}
+
+func (c *curvePoint) Put(pool *bnPool) {
+ pool.Put(c.x)
+ pool.Put(c.y)
+ pool.Put(c.z)
+ pool.Put(c.t)
+}
+
+func (c *curvePoint) Set(a *curvePoint) {
+ c.x.Set(a.x)
+ c.y.Set(a.y)
+ c.z.Set(a.z)
+ c.t.Set(a.t)
+}
+
+// IsOnCurve returns true iff c is on the curve where c must be in affine form.
+func (c *curvePoint) IsOnCurve() bool {
+ yy := new(big.Int).Mul(c.y, c.y)
+ xxx := new(big.Int).Mul(c.x, c.x)
+ xxx.Mul(xxx, c.x)
+ yy.Sub(yy, xxx)
+ yy.Sub(yy, curveB)
+ if yy.Sign() < 0 || yy.Cmp(p) >= 0 {
+ yy.Mod(yy, p)
+ }
+ return yy.Sign() == 0
+}
+
+func (c *curvePoint) SetInfinity() {
+ c.z.SetInt64(0)
+}
+
+func (c *curvePoint) IsInfinity() bool {
+ return c.z.Sign() == 0
+}
+
+func (c *curvePoint) Add(a, b *curvePoint, pool *bnPool) {
+ if a.IsInfinity() {
+ c.Set(b)
+ return
+ }
+ if b.IsInfinity() {
+ c.Set(a)
+ return
+ }
+
+ // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3
+
+ // Normalize the points by replacing a = [x1:y1:z1] and b = [x2:y2:z2]
+ // by [u1:s1:z1·z2] and [u2:s2:z1·z2]
+ // where u1 = x1·z2², s1 = y1·z2³ and u1 = x2·z1², s2 = y2·z1³
+ z1z1 := pool.Get().Mul(a.z, a.z)
+ z1z1.Mod(z1z1, p)
+ z2z2 := pool.Get().Mul(b.z, b.z)
+ z2z2.Mod(z2z2, p)
+ u1 := pool.Get().Mul(a.x, z2z2)
+ u1.Mod(u1, p)
+ u2 := pool.Get().Mul(b.x, z1z1)
+ u2.Mod(u2, p)
+
+ t := pool.Get().Mul(b.z, z2z2)
+ t.Mod(t, p)
+ s1 := pool.Get().Mul(a.y, t)
+ s1.Mod(s1, p)
+
+ t.Mul(a.z, z1z1)
+ t.Mod(t, p)
+ s2 := pool.Get().Mul(b.y, t)
+ s2.Mod(s2, p)
+
+ // Compute x = (2h)²(s²-u1-u2)
+ // where s = (s2-s1)/(u2-u1) is the slope of the line through
+ // (u1,s1) and (u2,s2). The extra factor 2h = 2(u2-u1) comes from the value of z below.
+ // This is also:
+ // 4(s2-s1)² - 4h²(u1+u2) = 4(s2-s1)² - 4h³ - 4h²(2u1)
+ // = r² - j - 2v
+ // with the notations below.
+ h := pool.Get().Sub(u2, u1)
+ xEqual := h.Sign() == 0
+
+ t.Add(h, h)
+ // i = 4h²
+ i := pool.Get().Mul(t, t)
+ i.Mod(i, p)
+ // j = 4h³
+ j := pool.Get().Mul(h, i)
+ j.Mod(j, p)
+
+ t.Sub(s2, s1)
+ yEqual := t.Sign() == 0
+ if xEqual && yEqual {
+ c.Double(a, pool)
+ return
+ }
+ r := pool.Get().Add(t, t)
+
+ v := pool.Get().Mul(u1, i)
+ v.Mod(v, p)
+
+ // t4 = 4(s2-s1)²
+ t4 := pool.Get().Mul(r, r)
+ t4.Mod(t4, p)
+ t.Add(v, v)
+ t6 := pool.Get().Sub(t4, j)
+ c.x.Sub(t6, t)
+
+ // Set y = -(2h)³(s1 + s*(x/4h²-u1))
+ // This is also
+ // y = - 2·s1·j - (s2-s1)(2x - 2i·u1) = r(v-x) - 2·s1·j
+ t.Sub(v, c.x) // t7
+ t4.Mul(s1, j) // t8
+ t4.Mod(t4, p)
+ t6.Add(t4, t4) // t9
+ t4.Mul(r, t) // t10
+ t4.Mod(t4, p)
+ c.y.Sub(t4, t6)
+
+ // Set z = 2(u2-u1)·z1·z2 = 2h·z1·z2
+ t.Add(a.z, b.z) // t11
+ t4.Mul(t, t) // t12
+ t4.Mod(t4, p)
+ t.Sub(t4, z1z1) // t13
+ t4.Sub(t, z2z2) // t14
+ c.z.Mul(t4, h)
+ c.z.Mod(c.z, p)
+
+ pool.Put(z1z1)
+ pool.Put(z2z2)
+ pool.Put(u1)
+ pool.Put(u2)
+ pool.Put(t)
+ pool.Put(s1)
+ pool.Put(s2)
+ pool.Put(h)
+ pool.Put(i)
+ pool.Put(j)
+ pool.Put(r)
+ pool.Put(v)
+ pool.Put(t4)
+ pool.Put(t6)
+}
+
+func (c *curvePoint) Double(a *curvePoint, pool *bnPool) {
+ // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3
+ A := pool.Get().Mul(a.x, a.x)
+ A.Mod(A, p)
+ B := pool.Get().Mul(a.y, a.y)
+ B.Mod(B, p)
+ C := pool.Get().Mul(B, B)
+ C.Mod(C, p)
+
+ t := pool.Get().Add(a.x, B)
+ t2 := pool.Get().Mul(t, t)
+ t2.Mod(t2, p)
+ t.Sub(t2, A)
+ t2.Sub(t, C)
+ d := pool.Get().Add(t2, t2)
+ t.Add(A, A)
+ e := pool.Get().Add(t, A)
+ f := pool.Get().Mul(e, e)
+ f.Mod(f, p)
+
+ t.Add(d, d)
+ c.x.Sub(f, t)
+
+ t.Add(C, C)
+ t2.Add(t, t)
+ t.Add(t2, t2)
+ c.y.Sub(d, c.x)
+ t2.Mul(e, c.y)
+ t2.Mod(t2, p)
+ c.y.Sub(t2, t)
+
+ t.Mul(a.y, a.z)
+ t.Mod(t, p)
+ c.z.Add(t, t)
+
+ pool.Put(A)
+ pool.Put(B)
+ pool.Put(C)
+ pool.Put(t)
+ pool.Put(t2)
+ pool.Put(d)
+ pool.Put(e)
+ pool.Put(f)
+}
+
+func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int, pool *bnPool) *curvePoint {
+ sum := newCurvePoint(pool)
+ sum.SetInfinity()
+ t := newCurvePoint(pool)
+
+ for i := scalar.BitLen(); i >= 0; i-- {
+ t.Double(sum, pool)
+ if scalar.Bit(i) != 0 {
+ sum.Add(t, a, pool)
+ } else {
+ sum.Set(t)
+ }
+ }
+
+ c.Set(sum)
+ sum.Put(pool)
+ t.Put(pool)
+ return c
+}
+
+// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets
+// c to 0 : 1 : 0.
+func (c *curvePoint) MakeAffine(pool *bnPool) *curvePoint {
+ if words := c.z.Bits(); len(words) == 1 && words[0] == 1 {
+ return c
+ }
+ if c.IsInfinity() {
+ c.x.SetInt64(0)
+ c.y.SetInt64(1)
+ c.z.SetInt64(0)
+ c.t.SetInt64(0)
+ return c
+ }
+
+ zInv := pool.Get().ModInverse(c.z, p)
+ t := pool.Get().Mul(c.y, zInv)
+ t.Mod(t, p)
+ zInv2 := pool.Get().Mul(zInv, zInv)
+ zInv2.Mod(zInv2, p)
+ c.y.Mul(t, zInv2)
+ c.y.Mod(c.y, p)
+ t.Mul(c.x, zInv2)
+ t.Mod(t, p)
+ c.x.Set(t)
+ c.z.SetInt64(1)
+ c.t.SetInt64(1)
+
+ pool.Put(zInv)
+ pool.Put(t)
+ pool.Put(zInv2)
+
+ return c
+}
+
+func (c *curvePoint) Negative(a *curvePoint) {
+ c.x.Set(a.x)
+ c.y.Neg(a.y)
+ c.z.Set(a.z)
+ c.t.SetInt64(0)
+}
diff --git a/local_crypto_patch/contents/bn256/example_test.go b/local_crypto_patch/contents/bn256/example_test.go
new file mode 100644
index 0000000000..b2d19807a2
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/example_test.go
@@ -0,0 +1,43 @@
+// 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 bn256
+
+import (
+ "crypto/rand"
+)
+
+func ExamplePair() {
+ // This implements the tripartite Diffie-Hellman algorithm from "A One
+ // Round Protocol for Tripartite Diffie-Hellman", A. Joux.
+ // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf
+
+ // Each of three parties, a, b and c, generate a private value.
+ a, _ := rand.Int(rand.Reader, Order)
+ b, _ := rand.Int(rand.Reader, Order)
+ c, _ := rand.Int(rand.Reader, Order)
+
+ // Then each party calculates g₁ and g₂ times their private value.
+ pa := new(G1).ScalarBaseMult(a)
+ qa := new(G2).ScalarBaseMult(a)
+
+ pb := new(G1).ScalarBaseMult(b)
+ qb := new(G2).ScalarBaseMult(b)
+
+ pc := new(G1).ScalarBaseMult(c)
+ qc := new(G2).ScalarBaseMult(c)
+
+ // Now each party exchanges its public values with the other two and
+ // all parties can calculate the shared key.
+ k1 := Pair(pb, qc)
+ k1.ScalarMult(k1, a)
+
+ k2 := Pair(pc, qa)
+ k2.ScalarMult(k2, b)
+
+ k3 := Pair(pa, qb)
+ k3.ScalarMult(k3, c)
+
+ // k1, k2 and k3 will all be equal.
+}
diff --git a/local_crypto_patch/contents/bn256/gfp12.go b/local_crypto_patch/contents/bn256/gfp12.go
new file mode 100644
index 0000000000..b05a8b727f
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/gfp12.go
@@ -0,0 +1,200 @@
+// 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 bn256
+
+// For details of the algorithms used, see "Multiplication and Squaring on
+// Pairing-Friendly Fields", Devegili et al.
+// http://eprint.iacr.org/2006/471.pdf.
+
+import (
+ "math/big"
+)
+
+// gfP12 implements the field of size p¹² as a quadratic extension of gfP6
+// where ω²=τ.
+type gfP12 struct {
+ x, y *gfP6 // value is xω + y
+}
+
+func newGFp12(pool *bnPool) *gfP12 {
+ return &gfP12{newGFp6(pool), newGFp6(pool)}
+}
+
+func (e *gfP12) String() string {
+ return "(" + e.x.String() + "," + e.y.String() + ")"
+}
+
+func (e *gfP12) Put(pool *bnPool) {
+ e.x.Put(pool)
+ e.y.Put(pool)
+}
+
+func (e *gfP12) Set(a *gfP12) *gfP12 {
+ e.x.Set(a.x)
+ e.y.Set(a.y)
+ return e
+}
+
+func (e *gfP12) SetZero() *gfP12 {
+ e.x.SetZero()
+ e.y.SetZero()
+ return e
+}
+
+func (e *gfP12) SetOne() *gfP12 {
+ e.x.SetZero()
+ e.y.SetOne()
+ return e
+}
+
+func (e *gfP12) Minimal() {
+ e.x.Minimal()
+ e.y.Minimal()
+}
+
+func (e *gfP12) IsZero() bool {
+ e.Minimal()
+ return e.x.IsZero() && e.y.IsZero()
+}
+
+func (e *gfP12) IsOne() bool {
+ e.Minimal()
+ return e.x.IsZero() && e.y.IsOne()
+}
+
+func (e *gfP12) Conjugate(a *gfP12) *gfP12 {
+ e.x.Negative(a.x)
+ e.y.Set(a.y)
+ return a
+}
+
+func (e *gfP12) Negative(a *gfP12) *gfP12 {
+ e.x.Negative(a.x)
+ e.y.Negative(a.y)
+ return e
+}
+
+// Frobenius computes (xω+y)^p = x^p ω·ξ^((p-1)/6) + y^p
+func (e *gfP12) Frobenius(a *gfP12, pool *bnPool) *gfP12 {
+ e.x.Frobenius(a.x, pool)
+ e.y.Frobenius(a.y, pool)
+ e.x.MulScalar(e.x, xiToPMinus1Over6, pool)
+ return e
+}
+
+// FrobeniusP2 computes (xω+y)^p² = x^p² ω·ξ^((p²-1)/6) + y^p²
+func (e *gfP12) FrobeniusP2(a *gfP12, pool *bnPool) *gfP12 {
+ e.x.FrobeniusP2(a.x)
+ e.x.MulGFP(e.x, xiToPSquaredMinus1Over6)
+ e.y.FrobeniusP2(a.y)
+ return e
+}
+
+func (e *gfP12) Add(a, b *gfP12) *gfP12 {
+ e.x.Add(a.x, b.x)
+ e.y.Add(a.y, b.y)
+ return e
+}
+
+func (e *gfP12) Sub(a, b *gfP12) *gfP12 {
+ e.x.Sub(a.x, b.x)
+ e.y.Sub(a.y, b.y)
+ return e
+}
+
+func (e *gfP12) Mul(a, b *gfP12, pool *bnPool) *gfP12 {
+ tx := newGFp6(pool)
+ tx.Mul(a.x, b.y, pool)
+ t := newGFp6(pool)
+ t.Mul(b.x, a.y, pool)
+ tx.Add(tx, t)
+
+ ty := newGFp6(pool)
+ ty.Mul(a.y, b.y, pool)
+ t.Mul(a.x, b.x, pool)
+ t.MulTau(t, pool)
+ e.y.Add(ty, t)
+ e.x.Set(tx)
+
+ tx.Put(pool)
+ ty.Put(pool)
+ t.Put(pool)
+ return e
+}
+
+func (e *gfP12) MulScalar(a *gfP12, b *gfP6, pool *bnPool) *gfP12 {
+ e.x.Mul(a.x, b, pool)
+ e.y.Mul(a.y, b, pool)
+ return e
+}
+
+func (c *gfP12) Exp(a *gfP12, power *big.Int, pool *bnPool) *gfP12 {
+ sum := newGFp12(pool)
+ sum.SetOne()
+ t := newGFp12(pool)
+
+ for i := power.BitLen() - 1; i >= 0; i-- {
+ t.Square(sum, pool)
+ if power.Bit(i) != 0 {
+ sum.Mul(t, a, pool)
+ } else {
+ sum.Set(t)
+ }
+ }
+
+ c.Set(sum)
+
+ sum.Put(pool)
+ t.Put(pool)
+
+ return c
+}
+
+func (e *gfP12) Square(a *gfP12, pool *bnPool) *gfP12 {
+ // Complex squaring algorithm
+ v0 := newGFp6(pool)
+ v0.Mul(a.x, a.y, pool)
+
+ t := newGFp6(pool)
+ t.MulTau(a.x, pool)
+ t.Add(a.y, t)
+ ty := newGFp6(pool)
+ ty.Add(a.x, a.y)
+ ty.Mul(ty, t, pool)
+ ty.Sub(ty, v0)
+ t.MulTau(v0, pool)
+ ty.Sub(ty, t)
+
+ e.y.Set(ty)
+ e.x.Double(v0)
+
+ v0.Put(pool)
+ t.Put(pool)
+ ty.Put(pool)
+
+ return e
+}
+
+func (e *gfP12) Invert(a *gfP12, pool *bnPool) *gfP12 {
+ // See "Implementing cryptographic pairings", M. Scott, section 3.2.
+ // ftp://136.206.11.249/pub/crypto/pairings.pdf
+ t1 := newGFp6(pool)
+ t2 := newGFp6(pool)
+
+ t1.Square(a.x, pool)
+ t2.Square(a.y, pool)
+ t1.MulTau(t1, pool)
+ t1.Sub(t2, t1)
+ t2.Invert(t1, pool)
+
+ e.x.Negative(a.x)
+ e.y.Set(a.y)
+ e.MulScalar(e, t2, pool)
+
+ t1.Put(pool)
+ t2.Put(pool)
+
+ return e
+}
diff --git a/local_crypto_patch/contents/bn256/gfp2.go b/local_crypto_patch/contents/bn256/gfp2.go
new file mode 100644
index 0000000000..aa39a3043b
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/gfp2.go
@@ -0,0 +1,219 @@
+// 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 bn256
+
+// For details of the algorithms used, see "Multiplication and Squaring on
+// Pairing-Friendly Fields", Devegili et al.
+// http://eprint.iacr.org/2006/471.pdf.
+
+import (
+ "math/big"
+)
+
+// gfP2 implements a field of size p² as a quadratic extension of the base
+// field where i²=-1.
+type gfP2 struct {
+ x, y *big.Int // value is xi+y.
+}
+
+func newGFp2(pool *bnPool) *gfP2 {
+ return &gfP2{pool.Get(), pool.Get()}
+}
+
+func (e *gfP2) String() string {
+ x := new(big.Int).Mod(e.x, p)
+ y := new(big.Int).Mod(e.y, p)
+ return "(" + x.String() + "," + y.String() + ")"
+}
+
+func (e *gfP2) Put(pool *bnPool) {
+ pool.Put(e.x)
+ pool.Put(e.y)
+}
+
+func (e *gfP2) Set(a *gfP2) *gfP2 {
+ e.x.Set(a.x)
+ e.y.Set(a.y)
+ return e
+}
+
+func (e *gfP2) SetZero() *gfP2 {
+ e.x.SetInt64(0)
+ e.y.SetInt64(0)
+ return e
+}
+
+func (e *gfP2) SetOne() *gfP2 {
+ e.x.SetInt64(0)
+ e.y.SetInt64(1)
+ return e
+}
+
+func (e *gfP2) Minimal() {
+ if e.x.Sign() < 0 || e.x.Cmp(p) >= 0 {
+ e.x.Mod(e.x, p)
+ }
+ if e.y.Sign() < 0 || e.y.Cmp(p) >= 0 {
+ e.y.Mod(e.y, p)
+ }
+}
+
+func (e *gfP2) IsZero() bool {
+ return e.x.Sign() == 0 && e.y.Sign() == 0
+}
+
+func (e *gfP2) IsOne() bool {
+ if e.x.Sign() != 0 {
+ return false
+ }
+ words := e.y.Bits()
+ return len(words) == 1 && words[0] == 1
+}
+
+func (e *gfP2) Conjugate(a *gfP2) *gfP2 {
+ e.y.Set(a.y)
+ e.x.Neg(a.x)
+ return e
+}
+
+func (e *gfP2) Negative(a *gfP2) *gfP2 {
+ e.x.Neg(a.x)
+ e.y.Neg(a.y)
+ return e
+}
+
+func (e *gfP2) Add(a, b *gfP2) *gfP2 {
+ e.x.Add(a.x, b.x)
+ e.y.Add(a.y, b.y)
+ return e
+}
+
+func (e *gfP2) Sub(a, b *gfP2) *gfP2 {
+ e.x.Sub(a.x, b.x)
+ e.y.Sub(a.y, b.y)
+ return e
+}
+
+func (e *gfP2) Double(a *gfP2) *gfP2 {
+ e.x.Lsh(a.x, 1)
+ e.y.Lsh(a.y, 1)
+ return e
+}
+
+func (c *gfP2) Exp(a *gfP2, power *big.Int, pool *bnPool) *gfP2 {
+ sum := newGFp2(pool)
+ sum.SetOne()
+ t := newGFp2(pool)
+
+ for i := power.BitLen() - 1; i >= 0; i-- {
+ t.Square(sum, pool)
+ if power.Bit(i) != 0 {
+ sum.Mul(t, a, pool)
+ } else {
+ sum.Set(t)
+ }
+ }
+
+ c.Set(sum)
+
+ sum.Put(pool)
+ t.Put(pool)
+
+ return c
+}
+
+// See "Multiplication and Squaring in Pairing-Friendly Fields",
+// http://eprint.iacr.org/2006/471.pdf
+func (e *gfP2) Mul(a, b *gfP2, pool *bnPool) *gfP2 {
+ tx := pool.Get().Mul(a.x, b.y)
+ t := pool.Get().Mul(b.x, a.y)
+ tx.Add(tx, t)
+ tx.Mod(tx, p)
+
+ ty := pool.Get().Mul(a.y, b.y)
+ t.Mul(a.x, b.x)
+ ty.Sub(ty, t)
+ e.y.Mod(ty, p)
+ e.x.Set(tx)
+
+ pool.Put(tx)
+ pool.Put(ty)
+ pool.Put(t)
+
+ return e
+}
+
+func (e *gfP2) MulScalar(a *gfP2, b *big.Int) *gfP2 {
+ e.x.Mul(a.x, b)
+ e.y.Mul(a.y, b)
+ return e
+}
+
+// MulXi sets e=ξa where ξ=i+3 and then returns e.
+func (e *gfP2) MulXi(a *gfP2, pool *bnPool) *gfP2 {
+ // (xi+y)(i+3) = (3x+y)i+(3y-x)
+ tx := pool.Get().Lsh(a.x, 1)
+ tx.Add(tx, a.x)
+ tx.Add(tx, a.y)
+
+ ty := pool.Get().Lsh(a.y, 1)
+ ty.Add(ty, a.y)
+ ty.Sub(ty, a.x)
+
+ e.x.Set(tx)
+ e.y.Set(ty)
+
+ pool.Put(tx)
+ pool.Put(ty)
+
+ return e
+}
+
+func (e *gfP2) Square(a *gfP2, pool *bnPool) *gfP2 {
+ // Complex squaring algorithm:
+ // (xi+b)² = (x+y)(y-x) + 2*i*x*y
+ t1 := pool.Get().Sub(a.y, a.x)
+ t2 := pool.Get().Add(a.x, a.y)
+ ty := pool.Get().Mul(t1, t2)
+ ty.Mod(ty, p)
+
+ t1.Mul(a.x, a.y)
+ t1.Lsh(t1, 1)
+
+ e.x.Mod(t1, p)
+ e.y.Set(ty)
+
+ pool.Put(t1)
+ pool.Put(t2)
+ pool.Put(ty)
+
+ return e
+}
+
+func (e *gfP2) Invert(a *gfP2, pool *bnPool) *gfP2 {
+ // See "Implementing cryptographic pairings", M. Scott, section 3.2.
+ // ftp://136.206.11.249/pub/crypto/pairings.pdf
+ t := pool.Get()
+ t.Mul(a.y, a.y)
+ t2 := pool.Get()
+ t2.Mul(a.x, a.x)
+ t.Add(t, t2)
+
+ inv := pool.Get()
+ inv.ModInverse(t, p)
+
+ e.x.Neg(a.x)
+ e.x.Mul(e.x, inv)
+ e.x.Mod(e.x, p)
+
+ e.y.Mul(a.y, inv)
+ e.y.Mod(e.y, p)
+
+ pool.Put(t)
+ pool.Put(t2)
+ pool.Put(inv)
+
+ return e
+}
diff --git a/local_crypto_patch/contents/bn256/gfp6.go b/local_crypto_patch/contents/bn256/gfp6.go
new file mode 100644
index 0000000000..7dec5eabd6
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/gfp6.go
@@ -0,0 +1,296 @@
+// 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 bn256
+
+// For details of the algorithms used, see "Multiplication and Squaring on
+// Pairing-Friendly Fields", Devegili et al.
+// http://eprint.iacr.org/2006/471.pdf.
+
+import (
+ "math/big"
+)
+
+// gfP6 implements the field of size p⁶ as a cubic extension of gfP2 where τ³=ξ
+// and ξ=i+3.
+type gfP6 struct {
+ x, y, z *gfP2 // value is xτ² + yτ + z
+}
+
+func newGFp6(pool *bnPool) *gfP6 {
+ return &gfP6{newGFp2(pool), newGFp2(pool), newGFp2(pool)}
+}
+
+func (e *gfP6) String() string {
+ return "(" + e.x.String() + "," + e.y.String() + "," + e.z.String() + ")"
+}
+
+func (e *gfP6) Put(pool *bnPool) {
+ e.x.Put(pool)
+ e.y.Put(pool)
+ e.z.Put(pool)
+}
+
+func (e *gfP6) Set(a *gfP6) *gfP6 {
+ e.x.Set(a.x)
+ e.y.Set(a.y)
+ e.z.Set(a.z)
+ return e
+}
+
+func (e *gfP6) SetZero() *gfP6 {
+ e.x.SetZero()
+ e.y.SetZero()
+ e.z.SetZero()
+ return e
+}
+
+func (e *gfP6) SetOne() *gfP6 {
+ e.x.SetZero()
+ e.y.SetZero()
+ e.z.SetOne()
+ return e
+}
+
+func (e *gfP6) Minimal() {
+ e.x.Minimal()
+ e.y.Minimal()
+ e.z.Minimal()
+}
+
+func (e *gfP6) IsZero() bool {
+ return e.x.IsZero() && e.y.IsZero() && e.z.IsZero()
+}
+
+func (e *gfP6) IsOne() bool {
+ return e.x.IsZero() && e.y.IsZero() && e.z.IsOne()
+}
+
+func (e *gfP6) Negative(a *gfP6) *gfP6 {
+ e.x.Negative(a.x)
+ e.y.Negative(a.y)
+ e.z.Negative(a.z)
+ return e
+}
+
+func (e *gfP6) Frobenius(a *gfP6, pool *bnPool) *gfP6 {
+ e.x.Conjugate(a.x)
+ e.y.Conjugate(a.y)
+ e.z.Conjugate(a.z)
+
+ e.x.Mul(e.x, xiTo2PMinus2Over3, pool)
+ e.y.Mul(e.y, xiToPMinus1Over3, pool)
+ return e
+}
+
+// FrobeniusP2 computes (xτ²+yτ+z)^(p²) = xτ^(2p²) + yτ^(p²) + z
+func (e *gfP6) FrobeniusP2(a *gfP6) *gfP6 {
+ // τ^(2p²) = τ²τ^(2p²-2) = τ²ξ^((2p²-2)/3)
+ e.x.MulScalar(a.x, xiTo2PSquaredMinus2Over3)
+ // τ^(p²) = ττ^(p²-1) = τξ^((p²-1)/3)
+ e.y.MulScalar(a.y, xiToPSquaredMinus1Over3)
+ e.z.Set(a.z)
+ return e
+}
+
+func (e *gfP6) Add(a, b *gfP6) *gfP6 {
+ e.x.Add(a.x, b.x)
+ e.y.Add(a.y, b.y)
+ e.z.Add(a.z, b.z)
+ return e
+}
+
+func (e *gfP6) Sub(a, b *gfP6) *gfP6 {
+ e.x.Sub(a.x, b.x)
+ e.y.Sub(a.y, b.y)
+ e.z.Sub(a.z, b.z)
+ return e
+}
+
+func (e *gfP6) Double(a *gfP6) *gfP6 {
+ e.x.Double(a.x)
+ e.y.Double(a.y)
+ e.z.Double(a.z)
+ return e
+}
+
+func (e *gfP6) Mul(a, b *gfP6, pool *bnPool) *gfP6 {
+ // "Multiplication and Squaring on Pairing-Friendly Fields"
+ // Section 4, Karatsuba method.
+ // http://eprint.iacr.org/2006/471.pdf
+
+ v0 := newGFp2(pool)
+ v0.Mul(a.z, b.z, pool)
+ v1 := newGFp2(pool)
+ v1.Mul(a.y, b.y, pool)
+ v2 := newGFp2(pool)
+ v2.Mul(a.x, b.x, pool)
+
+ t0 := newGFp2(pool)
+ t0.Add(a.x, a.y)
+ t1 := newGFp2(pool)
+ t1.Add(b.x, b.y)
+ tz := newGFp2(pool)
+ tz.Mul(t0, t1, pool)
+
+ tz.Sub(tz, v1)
+ tz.Sub(tz, v2)
+ tz.MulXi(tz, pool)
+ tz.Add(tz, v0)
+
+ t0.Add(a.y, a.z)
+ t1.Add(b.y, b.z)
+ ty := newGFp2(pool)
+ ty.Mul(t0, t1, pool)
+ ty.Sub(ty, v0)
+ ty.Sub(ty, v1)
+ t0.MulXi(v2, pool)
+ ty.Add(ty, t0)
+
+ t0.Add(a.x, a.z)
+ t1.Add(b.x, b.z)
+ tx := newGFp2(pool)
+ tx.Mul(t0, t1, pool)
+ tx.Sub(tx, v0)
+ tx.Add(tx, v1)
+ tx.Sub(tx, v2)
+
+ e.x.Set(tx)
+ e.y.Set(ty)
+ e.z.Set(tz)
+
+ t0.Put(pool)
+ t1.Put(pool)
+ tx.Put(pool)
+ ty.Put(pool)
+ tz.Put(pool)
+ v0.Put(pool)
+ v1.Put(pool)
+ v2.Put(pool)
+ return e
+}
+
+func (e *gfP6) MulScalar(a *gfP6, b *gfP2, pool *bnPool) *gfP6 {
+ e.x.Mul(a.x, b, pool)
+ e.y.Mul(a.y, b, pool)
+ e.z.Mul(a.z, b, pool)
+ return e
+}
+
+func (e *gfP6) MulGFP(a *gfP6, b *big.Int) *gfP6 {
+ e.x.MulScalar(a.x, b)
+ e.y.MulScalar(a.y, b)
+ e.z.MulScalar(a.z, b)
+ return e
+}
+
+// MulTau computes τ·(aτ²+bτ+c) = bτ²+cτ+aξ
+func (e *gfP6) MulTau(a *gfP6, pool *bnPool) {
+ tz := newGFp2(pool)
+ tz.MulXi(a.x, pool)
+ ty := newGFp2(pool)
+ ty.Set(a.y)
+ e.y.Set(a.z)
+ e.x.Set(ty)
+ e.z.Set(tz)
+ tz.Put(pool)
+ ty.Put(pool)
+}
+
+func (e *gfP6) Square(a *gfP6, pool *bnPool) *gfP6 {
+ v0 := newGFp2(pool).Square(a.z, pool)
+ v1 := newGFp2(pool).Square(a.y, pool)
+ v2 := newGFp2(pool).Square(a.x, pool)
+
+ c0 := newGFp2(pool).Add(a.x, a.y)
+ c0.Square(c0, pool)
+ c0.Sub(c0, v1)
+ c0.Sub(c0, v2)
+ c0.MulXi(c0, pool)
+ c0.Add(c0, v0)
+
+ c1 := newGFp2(pool).Add(a.y, a.z)
+ c1.Square(c1, pool)
+ c1.Sub(c1, v0)
+ c1.Sub(c1, v1)
+ xiV2 := newGFp2(pool).MulXi(v2, pool)
+ c1.Add(c1, xiV2)
+
+ c2 := newGFp2(pool).Add(a.x, a.z)
+ c2.Square(c2, pool)
+ c2.Sub(c2, v0)
+ c2.Add(c2, v1)
+ c2.Sub(c2, v2)
+
+ e.x.Set(c2)
+ e.y.Set(c1)
+ e.z.Set(c0)
+
+ v0.Put(pool)
+ v1.Put(pool)
+ v2.Put(pool)
+ c0.Put(pool)
+ c1.Put(pool)
+ c2.Put(pool)
+ xiV2.Put(pool)
+
+ return e
+}
+
+func (e *gfP6) Invert(a *gfP6, pool *bnPool) *gfP6 {
+ // See "Implementing cryptographic pairings", M. Scott, section 3.2.
+ // ftp://136.206.11.249/pub/crypto/pairings.pdf
+
+ // Here we can give a short explanation of how it works: let j be a cubic root of
+ // unity in GF(p²) so that 1+j+j²=0.
+ // Then (xτ² + yτ + z)(xj²τ² + yjτ + z)(xjτ² + yj²τ + z)
+ // = (xτ² + yτ + z)(Cτ²+Bτ+A)
+ // = (x³ξ²+y³ξ+z³-3ξxyz) = F is an element of the base field (the norm).
+ //
+ // On the other hand (xj²τ² + yjτ + z)(xjτ² + yj²τ + z)
+ // = τ²(y²-ξxz) + τ(ξx²-yz) + (z²-ξxy)
+ //
+ // So that's why A = (z²-ξxy), B = (ξx²-yz), C = (y²-ξxz)
+ t1 := newGFp2(pool)
+
+ A := newGFp2(pool)
+ A.Square(a.z, pool)
+ t1.Mul(a.x, a.y, pool)
+ t1.MulXi(t1, pool)
+ A.Sub(A, t1)
+
+ B := newGFp2(pool)
+ B.Square(a.x, pool)
+ B.MulXi(B, pool)
+ t1.Mul(a.y, a.z, pool)
+ B.Sub(B, t1)
+
+ C := newGFp2(pool)
+ C.Square(a.y, pool)
+ t1.Mul(a.x, a.z, pool)
+ C.Sub(C, t1)
+
+ F := newGFp2(pool)
+ F.Mul(C, a.y, pool)
+ F.MulXi(F, pool)
+ t1.Mul(A, a.z, pool)
+ F.Add(F, t1)
+ t1.Mul(B, a.x, pool)
+ t1.MulXi(t1, pool)
+ F.Add(F, t1)
+
+ F.Invert(F, pool)
+
+ e.x.Mul(C, F, pool)
+ e.y.Mul(B, F, pool)
+ e.z.Mul(A, F, pool)
+
+ t1.Put(pool)
+ A.Put(pool)
+ B.Put(pool)
+ C.Put(pool)
+ F.Put(pool)
+
+ return e
+}
diff --git a/local_crypto_patch/contents/bn256/optate.go b/local_crypto_patch/contents/bn256/optate.go
new file mode 100644
index 0000000000..7ae0746eb1
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/optate.go
@@ -0,0 +1,395 @@
+// 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 bn256
+
+func lineFunctionAdd(r, p *twistPoint, q *curvePoint, r2 *gfP2, pool *bnPool) (a, b, c *gfP2, rOut *twistPoint) {
+ // See the mixed addition algorithm from "Faster Computation of the
+ // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf
+
+ B := newGFp2(pool).Mul(p.x, r.t, pool)
+
+ D := newGFp2(pool).Add(p.y, r.z)
+ D.Square(D, pool)
+ D.Sub(D, r2)
+ D.Sub(D, r.t)
+ D.Mul(D, r.t, pool)
+
+ H := newGFp2(pool).Sub(B, r.x)
+ I := newGFp2(pool).Square(H, pool)
+
+ E := newGFp2(pool).Add(I, I)
+ E.Add(E, E)
+
+ J := newGFp2(pool).Mul(H, E, pool)
+
+ L1 := newGFp2(pool).Sub(D, r.y)
+ L1.Sub(L1, r.y)
+
+ V := newGFp2(pool).Mul(r.x, E, pool)
+
+ rOut = newTwistPoint(pool)
+ rOut.x.Square(L1, pool)
+ rOut.x.Sub(rOut.x, J)
+ rOut.x.Sub(rOut.x, V)
+ rOut.x.Sub(rOut.x, V)
+
+ rOut.z.Add(r.z, H)
+ rOut.z.Square(rOut.z, pool)
+ rOut.z.Sub(rOut.z, r.t)
+ rOut.z.Sub(rOut.z, I)
+
+ t := newGFp2(pool).Sub(V, rOut.x)
+ t.Mul(t, L1, pool)
+ t2 := newGFp2(pool).Mul(r.y, J, pool)
+ t2.Add(t2, t2)
+ rOut.y.Sub(t, t2)
+
+ rOut.t.Square(rOut.z, pool)
+
+ t.Add(p.y, rOut.z)
+ t.Square(t, pool)
+ t.Sub(t, r2)
+ t.Sub(t, rOut.t)
+
+ t2.Mul(L1, p.x, pool)
+ t2.Add(t2, t2)
+ a = newGFp2(pool)
+ a.Sub(t2, t)
+
+ c = newGFp2(pool)
+ c.MulScalar(rOut.z, q.y)
+ c.Add(c, c)
+
+ b = newGFp2(pool)
+ b.SetZero()
+ b.Sub(b, L1)
+ b.MulScalar(b, q.x)
+ b.Add(b, b)
+
+ B.Put(pool)
+ D.Put(pool)
+ H.Put(pool)
+ I.Put(pool)
+ E.Put(pool)
+ J.Put(pool)
+ L1.Put(pool)
+ V.Put(pool)
+ t.Put(pool)
+ t2.Put(pool)
+
+ return
+}
+
+func lineFunctionDouble(r *twistPoint, q *curvePoint, pool *bnPool) (a, b, c *gfP2, rOut *twistPoint) {
+ // See the doubling algorithm for a=0 from "Faster Computation of the
+ // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf
+
+ A := newGFp2(pool).Square(r.x, pool)
+ B := newGFp2(pool).Square(r.y, pool)
+ C := newGFp2(pool).Square(B, pool)
+
+ D := newGFp2(pool).Add(r.x, B)
+ D.Square(D, pool)
+ D.Sub(D, A)
+ D.Sub(D, C)
+ D.Add(D, D)
+
+ E := newGFp2(pool).Add(A, A)
+ E.Add(E, A)
+
+ G := newGFp2(pool).Square(E, pool)
+
+ rOut = newTwistPoint(pool)
+ rOut.x.Sub(G, D)
+ rOut.x.Sub(rOut.x, D)
+
+ rOut.z.Add(r.y, r.z)
+ rOut.z.Square(rOut.z, pool)
+ rOut.z.Sub(rOut.z, B)
+ rOut.z.Sub(rOut.z, r.t)
+
+ rOut.y.Sub(D, rOut.x)
+ rOut.y.Mul(rOut.y, E, pool)
+ t := newGFp2(pool).Add(C, C)
+ t.Add(t, t)
+ t.Add(t, t)
+ rOut.y.Sub(rOut.y, t)
+
+ rOut.t.Square(rOut.z, pool)
+
+ t.Mul(E, r.t, pool)
+ t.Add(t, t)
+ b = newGFp2(pool)
+ b.SetZero()
+ b.Sub(b, t)
+ b.MulScalar(b, q.x)
+
+ a = newGFp2(pool)
+ a.Add(r.x, E)
+ a.Square(a, pool)
+ a.Sub(a, A)
+ a.Sub(a, G)
+ t.Add(B, B)
+ t.Add(t, t)
+ a.Sub(a, t)
+
+ c = newGFp2(pool)
+ c.Mul(rOut.z, r.t, pool)
+ c.Add(c, c)
+ c.MulScalar(c, q.y)
+
+ A.Put(pool)
+ B.Put(pool)
+ C.Put(pool)
+ D.Put(pool)
+ E.Put(pool)
+ G.Put(pool)
+ t.Put(pool)
+
+ return
+}
+
+func mulLine(ret *gfP12, a, b, c *gfP2, pool *bnPool) {
+ a2 := newGFp6(pool)
+ a2.x.SetZero()
+ a2.y.Set(a)
+ a2.z.Set(b)
+ a2.Mul(a2, ret.x, pool)
+ t3 := newGFp6(pool).MulScalar(ret.y, c, pool)
+
+ t := newGFp2(pool)
+ t.Add(b, c)
+ t2 := newGFp6(pool)
+ t2.x.SetZero()
+ t2.y.Set(a)
+ t2.z.Set(t)
+ ret.x.Add(ret.x, ret.y)
+
+ ret.y.Set(t3)
+
+ ret.x.Mul(ret.x, t2, pool)
+ ret.x.Sub(ret.x, a2)
+ ret.x.Sub(ret.x, ret.y)
+ a2.MulTau(a2, pool)
+ ret.y.Add(ret.y, a2)
+
+ a2.Put(pool)
+ t3.Put(pool)
+ t2.Put(pool)
+ t.Put(pool)
+}
+
+// sixuPlus2NAF is 6u+2 in non-adjacent form.
+var sixuPlus2NAF = []int8{0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, -1, 0, 1, 0, 0, 0, 1, 0, -1, 0, 0, 0, -1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 1}
+
+// miller implements the Miller loop for calculating the Optimal Ate pairing.
+// See algorithm 1 from http://cryptojedi.org/papers/dclxvi-20100714.pdf
+func miller(q *twistPoint, p *curvePoint, pool *bnPool) *gfP12 {
+ ret := newGFp12(pool)
+ ret.SetOne()
+
+ aAffine := newTwistPoint(pool)
+ aAffine.Set(q)
+ aAffine.MakeAffine(pool)
+
+ bAffine := newCurvePoint(pool)
+ bAffine.Set(p)
+ bAffine.MakeAffine(pool)
+
+ minusA := newTwistPoint(pool)
+ minusA.Negative(aAffine, pool)
+
+ r := newTwistPoint(pool)
+ r.Set(aAffine)
+
+ r2 := newGFp2(pool)
+ r2.Square(aAffine.y, pool)
+
+ for i := len(sixuPlus2NAF) - 1; i > 0; i-- {
+ a, b, c, newR := lineFunctionDouble(r, bAffine, pool)
+ if i != len(sixuPlus2NAF)-1 {
+ ret.Square(ret, pool)
+ }
+
+ mulLine(ret, a, b, c, pool)
+ a.Put(pool)
+ b.Put(pool)
+ c.Put(pool)
+ r.Put(pool)
+ r = newR
+
+ switch sixuPlus2NAF[i-1] {
+ case 1:
+ a, b, c, newR = lineFunctionAdd(r, aAffine, bAffine, r2, pool)
+ case -1:
+ a, b, c, newR = lineFunctionAdd(r, minusA, bAffine, r2, pool)
+ default:
+ continue
+ }
+
+ mulLine(ret, a, b, c, pool)
+ a.Put(pool)
+ b.Put(pool)
+ c.Put(pool)
+ r.Put(pool)
+ r = newR
+ }
+
+ // In order to calculate Q1 we have to convert q from the sextic twist
+ // to the full GF(p^12) group, apply the Frobenius there, and convert
+ // back.
+ //
+ // The twist isomorphism is (x', y') -> (xω², yω³). If we consider just
+ // x for a moment, then after applying the Frobenius, we have x̄ω^(2p)
+ // where x̄ is the conjugate of x. If we are going to apply the inverse
+ // isomorphism we need a value with a single coefficient of ω² so we
+ // rewrite this as x̄ω^(2p-2)ω². ξ⁶ = ω and, due to the construction of
+ // p, 2p-2 is a multiple of six. Therefore we can rewrite as
+ // x̄ξ^((p-1)/3)ω² and applying the inverse isomorphism eliminates the
+ // ω².
+ //
+ // A similar argument can be made for the y value.
+
+ q1 := newTwistPoint(pool)
+ q1.x.Conjugate(aAffine.x)
+ q1.x.Mul(q1.x, xiToPMinus1Over3, pool)
+ q1.y.Conjugate(aAffine.y)
+ q1.y.Mul(q1.y, xiToPMinus1Over2, pool)
+ q1.z.SetOne()
+ q1.t.SetOne()
+
+ // For Q2 we are applying the p² Frobenius. The two conjugations cancel
+ // out and we are left only with the factors from the isomorphism. In
+ // the case of x, we end up with a pure number which is why
+ // xiToPSquaredMinus1Over3 is ∈ GF(p). With y we get a factor of -1. We
+ // ignore this to end up with -Q2.
+
+ minusQ2 := newTwistPoint(pool)
+ minusQ2.x.MulScalar(aAffine.x, xiToPSquaredMinus1Over3)
+ minusQ2.y.Set(aAffine.y)
+ minusQ2.z.SetOne()
+ minusQ2.t.SetOne()
+
+ r2.Square(q1.y, pool)
+ a, b, c, newR := lineFunctionAdd(r, q1, bAffine, r2, pool)
+ mulLine(ret, a, b, c, pool)
+ a.Put(pool)
+ b.Put(pool)
+ c.Put(pool)
+ r.Put(pool)
+ r = newR
+
+ r2.Square(minusQ2.y, pool)
+ a, b, c, newR = lineFunctionAdd(r, minusQ2, bAffine, r2, pool)
+ mulLine(ret, a, b, c, pool)
+ a.Put(pool)
+ b.Put(pool)
+ c.Put(pool)
+ r.Put(pool)
+ r = newR
+
+ aAffine.Put(pool)
+ bAffine.Put(pool)
+ minusA.Put(pool)
+ r.Put(pool)
+ r2.Put(pool)
+
+ return ret
+}
+
+// finalExponentiation computes the (p¹²-1)/Order-th power of an element of
+// GF(p¹²) to obtain an element of GT (steps 13-15 of algorithm 1 from
+// http://cryptojedi.org/papers/dclxvi-20100714.pdf)
+func finalExponentiation(in *gfP12, pool *bnPool) *gfP12 {
+ t1 := newGFp12(pool)
+
+ // This is the p^6-Frobenius
+ t1.x.Negative(in.x)
+ t1.y.Set(in.y)
+
+ inv := newGFp12(pool)
+ inv.Invert(in, pool)
+ t1.Mul(t1, inv, pool)
+
+ t2 := newGFp12(pool).FrobeniusP2(t1, pool)
+ t1.Mul(t1, t2, pool)
+
+ fp := newGFp12(pool).Frobenius(t1, pool)
+ fp2 := newGFp12(pool).FrobeniusP2(t1, pool)
+ fp3 := newGFp12(pool).Frobenius(fp2, pool)
+
+ fu, fu2, fu3 := newGFp12(pool), newGFp12(pool), newGFp12(pool)
+ fu.Exp(t1, u, pool)
+ fu2.Exp(fu, u, pool)
+ fu3.Exp(fu2, u, pool)
+
+ y3 := newGFp12(pool).Frobenius(fu, pool)
+ fu2p := newGFp12(pool).Frobenius(fu2, pool)
+ fu3p := newGFp12(pool).Frobenius(fu3, pool)
+ y2 := newGFp12(pool).FrobeniusP2(fu2, pool)
+
+ y0 := newGFp12(pool)
+ y0.Mul(fp, fp2, pool)
+ y0.Mul(y0, fp3, pool)
+
+ y1, y4, y5 := newGFp12(pool), newGFp12(pool), newGFp12(pool)
+ y1.Conjugate(t1)
+ y5.Conjugate(fu2)
+ y3.Conjugate(y3)
+ y4.Mul(fu, fu2p, pool)
+ y4.Conjugate(y4)
+
+ y6 := newGFp12(pool)
+ y6.Mul(fu3, fu3p, pool)
+ y6.Conjugate(y6)
+
+ t0 := newGFp12(pool)
+ t0.Square(y6, pool)
+ t0.Mul(t0, y4, pool)
+ t0.Mul(t0, y5, pool)
+ t1.Mul(y3, y5, pool)
+ t1.Mul(t1, t0, pool)
+ t0.Mul(t0, y2, pool)
+ t1.Square(t1, pool)
+ t1.Mul(t1, t0, pool)
+ t1.Square(t1, pool)
+ t0.Mul(t1, y1, pool)
+ t1.Mul(t1, y0, pool)
+ t0.Square(t0, pool)
+ t0.Mul(t0, t1, pool)
+
+ inv.Put(pool)
+ t1.Put(pool)
+ t2.Put(pool)
+ fp.Put(pool)
+ fp2.Put(pool)
+ fp3.Put(pool)
+ fu.Put(pool)
+ fu2.Put(pool)
+ fu3.Put(pool)
+ fu2p.Put(pool)
+ fu3p.Put(pool)
+ y0.Put(pool)
+ y1.Put(pool)
+ y2.Put(pool)
+ y3.Put(pool)
+ y4.Put(pool)
+ y5.Put(pool)
+ y6.Put(pool)
+
+ return t0
+}
+
+func optimalAte(a *twistPoint, b *curvePoint, pool *bnPool) *gfP12 {
+ e := miller(a, b, pool)
+ ret := finalExponentiation(e, pool)
+ e.Put(pool)
+
+ if a.IsInfinity() || b.IsInfinity() {
+ ret.SetOne()
+ }
+
+ return ret
+}
diff --git a/local_crypto_patch/contents/bn256/twist.go b/local_crypto_patch/contents/bn256/twist.go
new file mode 100644
index 0000000000..056d80f18f
--- /dev/null
+++ b/local_crypto_patch/contents/bn256/twist.go
@@ -0,0 +1,258 @@
+// 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 bn256
+
+import (
+ "math/big"
+)
+
+// twistPoint implements the elliptic curve y²=x³+3/ξ over GF(p²). Points are
+// kept in Jacobian form and t=z² when valid. The group G₂ is the set of
+// n-torsion points of this curve over GF(p²) (where n = Order)
+type twistPoint struct {
+ x, y, z, t *gfP2
+}
+
+var twistB = &gfP2{
+ bigFromBase10("6500054969564660373279643874235990574282535810762300357187714502686418407178"),
+ bigFromBase10("45500384786952622612957507119651934019977750675336102500314001518804928850249"),
+}
+
+// twistGen is the generator of group G₂.
+var twistGen = &twistPoint{
+ &gfP2{
+ bigFromBase10("21167961636542580255011770066570541300993051739349375019639421053990175267184"),
+ bigFromBase10("64746500191241794695844075326670126197795977525365406531717464316923369116492"),
+ },
+ &gfP2{
+ bigFromBase10("20666913350058776956210519119118544732556678129809273996262322366050359951122"),
+ bigFromBase10("17778617556404439934652658462602675281523610326338642107814333856843981424549"),
+ },
+ &gfP2{
+ bigFromBase10("0"),
+ bigFromBase10("1"),
+ },
+ &gfP2{
+ bigFromBase10("0"),
+ bigFromBase10("1"),
+ },
+}
+
+func newTwistPoint(pool *bnPool) *twistPoint {
+ return &twistPoint{
+ newGFp2(pool),
+ newGFp2(pool),
+ newGFp2(pool),
+ newGFp2(pool),
+ }
+}
+
+func (c *twistPoint) String() string {
+ return "(" + c.x.String() + ", " + c.y.String() + ", " + c.z.String() + ")"
+}
+
+func (c *twistPoint) Put(pool *bnPool) {
+ c.x.Put(pool)
+ c.y.Put(pool)
+ c.z.Put(pool)
+ c.t.Put(pool)
+}
+
+func (c *twistPoint) Set(a *twistPoint) {
+ c.x.Set(a.x)
+ c.y.Set(a.y)
+ c.z.Set(a.z)
+ c.t.Set(a.t)
+}
+
+// IsOnCurve returns true iff c is on the curve where c must be in affine form.
+func (c *twistPoint) IsOnCurve() bool {
+ pool := new(bnPool)
+ yy := newGFp2(pool).Square(c.y, pool)
+ xxx := newGFp2(pool).Square(c.x, pool)
+ xxx.Mul(xxx, c.x, pool)
+ yy.Sub(yy, xxx)
+ yy.Sub(yy, twistB)
+ yy.Minimal()
+ return yy.x.Sign() == 0 && yy.y.Sign() == 0
+}
+
+func (c *twistPoint) SetInfinity() {
+ c.z.SetZero()
+}
+
+func (c *twistPoint) IsInfinity() bool {
+ return c.z.IsZero()
+}
+
+func (c *twistPoint) Add(a, b *twistPoint, pool *bnPool) {
+ // For additional comments, see the same function in curve.go.
+
+ if a.IsInfinity() {
+ c.Set(b)
+ return
+ }
+ if b.IsInfinity() {
+ c.Set(a)
+ return
+ }
+
+ // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3
+ z1z1 := newGFp2(pool).Square(a.z, pool)
+ z2z2 := newGFp2(pool).Square(b.z, pool)
+ u1 := newGFp2(pool).Mul(a.x, z2z2, pool)
+ u2 := newGFp2(pool).Mul(b.x, z1z1, pool)
+
+ t := newGFp2(pool).Mul(b.z, z2z2, pool)
+ s1 := newGFp2(pool).Mul(a.y, t, pool)
+
+ t.Mul(a.z, z1z1, pool)
+ s2 := newGFp2(pool).Mul(b.y, t, pool)
+
+ h := newGFp2(pool).Sub(u2, u1)
+ xEqual := h.IsZero()
+
+ t.Add(h, h)
+ i := newGFp2(pool).Square(t, pool)
+ j := newGFp2(pool).Mul(h, i, pool)
+
+ t.Sub(s2, s1)
+ yEqual := t.IsZero()
+ if xEqual && yEqual {
+ c.Double(a, pool)
+ return
+ }
+ r := newGFp2(pool).Add(t, t)
+
+ v := newGFp2(pool).Mul(u1, i, pool)
+
+ t4 := newGFp2(pool).Square(r, pool)
+ t.Add(v, v)
+ t6 := newGFp2(pool).Sub(t4, j)
+ c.x.Sub(t6, t)
+
+ t.Sub(v, c.x) // t7
+ t4.Mul(s1, j, pool) // t8
+ t6.Add(t4, t4) // t9
+ t4.Mul(r, t, pool) // t10
+ c.y.Sub(t4, t6)
+
+ t.Add(a.z, b.z) // t11
+ t4.Square(t, pool) // t12
+ t.Sub(t4, z1z1) // t13
+ t4.Sub(t, z2z2) // t14
+ c.z.Mul(t4, h, pool)
+
+ z1z1.Put(pool)
+ z2z2.Put(pool)
+ u1.Put(pool)
+ u2.Put(pool)
+ t.Put(pool)
+ s1.Put(pool)
+ s2.Put(pool)
+ h.Put(pool)
+ i.Put(pool)
+ j.Put(pool)
+ r.Put(pool)
+ v.Put(pool)
+ t4.Put(pool)
+ t6.Put(pool)
+}
+
+func (c *twistPoint) Double(a *twistPoint, pool *bnPool) {
+ // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3
+ A := newGFp2(pool).Square(a.x, pool)
+ B := newGFp2(pool).Square(a.y, pool)
+ C := newGFp2(pool).Square(B, pool)
+
+ t := newGFp2(pool).Add(a.x, B)
+ t2 := newGFp2(pool).Square(t, pool)
+ t.Sub(t2, A)
+ t2.Sub(t, C)
+ d := newGFp2(pool).Add(t2, t2)
+ t.Add(A, A)
+ e := newGFp2(pool).Add(t, A)
+ f := newGFp2(pool).Square(e, pool)
+
+ t.Add(d, d)
+ c.x.Sub(f, t)
+
+ t.Add(C, C)
+ t2.Add(t, t)
+ t.Add(t2, t2)
+ c.y.Sub(d, c.x)
+ t2.Mul(e, c.y, pool)
+ c.y.Sub(t2, t)
+
+ t.Mul(a.y, a.z, pool)
+ c.z.Add(t, t)
+
+ A.Put(pool)
+ B.Put(pool)
+ C.Put(pool)
+ t.Put(pool)
+ t2.Put(pool)
+ d.Put(pool)
+ e.Put(pool)
+ f.Put(pool)
+}
+
+func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int, pool *bnPool) *twistPoint {
+ sum := newTwistPoint(pool)
+ sum.SetInfinity()
+ t := newTwistPoint(pool)
+
+ for i := scalar.BitLen(); i >= 0; i-- {
+ t.Double(sum, pool)
+ if scalar.Bit(i) != 0 {
+ sum.Add(t, a, pool)
+ } else {
+ sum.Set(t)
+ }
+ }
+
+ c.Set(sum)
+ sum.Put(pool)
+ t.Put(pool)
+ return c
+}
+
+// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets
+// c to 0 : 1 : 0.
+func (c *twistPoint) MakeAffine(pool *bnPool) *twistPoint {
+ if c.z.IsOne() {
+ return c
+ }
+ if c.IsInfinity() {
+ c.x.SetZero()
+ c.y.SetOne()
+ c.z.SetZero()
+ c.t.SetZero()
+ return c
+ }
+
+ zInv := newGFp2(pool).Invert(c.z, pool)
+ t := newGFp2(pool).Mul(c.y, zInv, pool)
+ zInv2 := newGFp2(pool).Square(zInv, pool)
+ c.y.Mul(t, zInv2, pool)
+ t.Mul(c.x, zInv2, pool)
+ c.x.Set(t)
+ c.z.SetOne()
+ c.t.SetOne()
+
+ zInv.Put(pool)
+ t.Put(pool)
+ zInv2.Put(pool)
+
+ return c
+}
+
+func (c *twistPoint) Negative(a *twistPoint, pool *bnPool) {
+ c.x.Set(a.x)
+ c.y.SetZero()
+ c.y.Sub(c.y, a.y)
+ c.z.Set(a.z)
+ c.t.SetZero()
+}
diff --git a/local_crypto_patch/contents/cast5/cast5.go b/local_crypto_patch/contents/cast5/cast5.go
new file mode 100644
index 0000000000..016e90215c
--- /dev/null
+++ b/local_crypto_patch/contents/cast5/cast5.go
@@ -0,0 +1,536 @@
+// 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 cast5 implements CAST5, as defined in RFC 2144.
+//
+// CAST5 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 cast5
+
+import (
+ "errors"
+ "math/bits"
+)
+
+const BlockSize = 8
+const KeySize = 16
+
+type Cipher struct {
+ masking [16]uint32
+ rotate [16]uint8
+}
+
+func NewCipher(key []byte) (c *Cipher, err error) {
+ if len(key) != KeySize {
+ return nil, errors.New("CAST5: keys must be 16 bytes")
+ }
+
+ c = new(Cipher)
+ c.keySchedule(key)
+ return
+}
+
+func (c *Cipher) BlockSize() int {
+ return BlockSize
+}
+
+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 = r, l^f1(r, c.masking[0], c.rotate[0])
+ l, r = r, l^f2(r, c.masking[1], c.rotate[1])
+ l, r = r, l^f3(r, c.masking[2], c.rotate[2])
+ l, r = r, l^f1(r, c.masking[3], c.rotate[3])
+
+ l, r = r, l^f2(r, c.masking[4], c.rotate[4])
+ l, r = r, l^f3(r, c.masking[5], c.rotate[5])
+ l, r = r, l^f1(r, c.masking[6], c.rotate[6])
+ l, r = r, l^f2(r, c.masking[7], c.rotate[7])
+
+ l, r = r, l^f3(r, c.masking[8], c.rotate[8])
+ l, r = r, l^f1(r, c.masking[9], c.rotate[9])
+ l, r = r, l^f2(r, c.masking[10], c.rotate[10])
+ l, r = r, l^f3(r, c.masking[11], c.rotate[11])
+
+ l, r = r, l^f1(r, c.masking[12], c.rotate[12])
+ l, r = r, l^f2(r, c.masking[13], c.rotate[13])
+ l, r = r, l^f3(r, c.masking[14], c.rotate[14])
+ l, r = r, l^f1(r, c.masking[15], c.rotate[15])
+
+ dst[0] = uint8(r >> 24)
+ dst[1] = uint8(r >> 16)
+ dst[2] = uint8(r >> 8)
+ dst[3] = uint8(r)
+ dst[4] = uint8(l >> 24)
+ dst[5] = uint8(l >> 16)
+ dst[6] = uint8(l >> 8)
+ dst[7] = uint8(l)
+}
+
+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 = r, l^f1(r, c.masking[15], c.rotate[15])
+ l, r = r, l^f3(r, c.masking[14], c.rotate[14])
+ l, r = r, l^f2(r, c.masking[13], c.rotate[13])
+ l, r = r, l^f1(r, c.masking[12], c.rotate[12])
+
+ l, r = r, l^f3(r, c.masking[11], c.rotate[11])
+ l, r = r, l^f2(r, c.masking[10], c.rotate[10])
+ l, r = r, l^f1(r, c.masking[9], c.rotate[9])
+ l, r = r, l^f3(r, c.masking[8], c.rotate[8])
+
+ l, r = r, l^f2(r, c.masking[7], c.rotate[7])
+ l, r = r, l^f1(r, c.masking[6], c.rotate[6])
+ l, r = r, l^f3(r, c.masking[5], c.rotate[5])
+ l, r = r, l^f2(r, c.masking[4], c.rotate[4])
+
+ l, r = r, l^f1(r, c.masking[3], c.rotate[3])
+ l, r = r, l^f3(r, c.masking[2], c.rotate[2])
+ l, r = r, l^f2(r, c.masking[1], c.rotate[1])
+ l, r = r, l^f1(r, c.masking[0], c.rotate[0])
+
+ dst[0] = uint8(r >> 24)
+ dst[1] = uint8(r >> 16)
+ dst[2] = uint8(r >> 8)
+ dst[3] = uint8(r)
+ dst[4] = uint8(l >> 24)
+ dst[5] = uint8(l >> 16)
+ dst[6] = uint8(l >> 8)
+ dst[7] = uint8(l)
+}
+
+type keyScheduleA [4][7]uint8
+type keyScheduleB [4][5]uint8
+
+// keyScheduleRound contains the magic values for a round of the key schedule.
+// The keyScheduleA deals with the lines like:
+// z0z1z2z3 = x0x1x2x3 ^ S5[xD] ^ S6[xF] ^ S7[xC] ^ S8[xE] ^ S7[x8]
+// Conceptually, both x and z are in the same array, x first. The first
+// element describes which word of this array gets written to and the
+// second, which word gets read. So, for the line above, it's "4, 0", because
+// it's writing to the first word of z, which, being after x, is word 4, and
+// reading from the first word of x: word 0.
+//
+// Next are the indexes into the S-boxes. Now the array is treated as bytes. So
+// "xD" is 0xd. The first byte of z is written as "16 + 0", just to be clear
+// that it's z that we're indexing.
+//
+// keyScheduleB deals with lines like:
+// K1 = S5[z8] ^ S6[z9] ^ S7[z7] ^ S8[z6] ^ S5[z2]
+// "K1" is ignored because key words are always written in order. So the five
+// elements are the S-box indexes. They use the same form as in keyScheduleA,
+// above.
+
+type keyScheduleRound struct{}
+type keySchedule []keyScheduleRound
+
+var schedule = []struct {
+ a keyScheduleA
+ b keyScheduleB
+}{
+ {
+ keyScheduleA{
+ {4, 0, 0xd, 0xf, 0xc, 0xe, 0x8},
+ {5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
+ {6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
+ {7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
+ },
+ keyScheduleB{
+ {16 + 8, 16 + 9, 16 + 7, 16 + 6, 16 + 2},
+ {16 + 0xa, 16 + 0xb, 16 + 5, 16 + 4, 16 + 6},
+ {16 + 0xc, 16 + 0xd, 16 + 3, 16 + 2, 16 + 9},
+ {16 + 0xe, 16 + 0xf, 16 + 1, 16 + 0, 16 + 0xc},
+ },
+ },
+ {
+ keyScheduleA{
+ {0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
+ {1, 4, 0, 2, 1, 3, 16 + 2},
+ {2, 5, 7, 6, 5, 4, 16 + 1},
+ {3, 7, 0xa, 9, 0xb, 8, 16 + 3},
+ },
+ keyScheduleB{
+ {3, 2, 0xc, 0xd, 8},
+ {1, 0, 0xe, 0xf, 0xd},
+ {7, 6, 8, 9, 3},
+ {5, 4, 0xa, 0xb, 7},
+ },
+ },
+ {
+ keyScheduleA{
+ {4, 0, 0xd, 0xf, 0xc, 0xe, 8},
+ {5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
+ {6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
+ {7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
+ },
+ keyScheduleB{
+ {16 + 3, 16 + 2, 16 + 0xc, 16 + 0xd, 16 + 9},
+ {16 + 1, 16 + 0, 16 + 0xe, 16 + 0xf, 16 + 0xc},
+ {16 + 7, 16 + 6, 16 + 8, 16 + 9, 16 + 2},
+ {16 + 5, 16 + 4, 16 + 0xa, 16 + 0xb, 16 + 6},
+ },
+ },
+ {
+ keyScheduleA{
+ {0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
+ {1, 4, 0, 2, 1, 3, 16 + 2},
+ {2, 5, 7, 6, 5, 4, 16 + 1},
+ {3, 7, 0xa, 9, 0xb, 8, 16 + 3},
+ },
+ keyScheduleB{
+ {8, 9, 7, 6, 3},
+ {0xa, 0xb, 5, 4, 7},
+ {0xc, 0xd, 3, 2, 8},
+ {0xe, 0xf, 1, 0, 0xd},
+ },
+ },
+}
+
+func (c *Cipher) keySchedule(in []byte) {
+ var t [8]uint32
+ var k [32]uint32
+
+ for i := 0; i < 4; i++ {
+ j := i * 4
+ t[i] = uint32(in[j])<<24 | uint32(in[j+1])<<16 | uint32(in[j+2])<<8 | uint32(in[j+3])
+ }
+
+ x := []byte{6, 7, 4, 5}
+ ki := 0
+
+ for half := 0; half < 2; half++ {
+ for _, round := range schedule {
+ for j := 0; j < 4; j++ {
+ var a [7]uint8
+ copy(a[:], round.a[j][:])
+ w := t[a[1]]
+ w ^= sBox[4][(t[a[2]>>2]>>(24-8*(a[2]&3)))&0xff]
+ w ^= sBox[5][(t[a[3]>>2]>>(24-8*(a[3]&3)))&0xff]
+ w ^= sBox[6][(t[a[4]>>2]>>(24-8*(a[4]&3)))&0xff]
+ w ^= sBox[7][(t[a[5]>>2]>>(24-8*(a[5]&3)))&0xff]
+ w ^= sBox[x[j]][(t[a[6]>>2]>>(24-8*(a[6]&3)))&0xff]
+ t[a[0]] = w
+ }
+
+ for j := 0; j < 4; j++ {
+ var b [5]uint8
+ copy(b[:], round.b[j][:])
+ w := sBox[4][(t[b[0]>>2]>>(24-8*(b[0]&3)))&0xff]
+ w ^= sBox[5][(t[b[1]>>2]>>(24-8*(b[1]&3)))&0xff]
+ w ^= sBox[6][(t[b[2]>>2]>>(24-8*(b[2]&3)))&0xff]
+ w ^= sBox[7][(t[b[3]>>2]>>(24-8*(b[3]&3)))&0xff]
+ w ^= sBox[4+j][(t[b[4]>>2]>>(24-8*(b[4]&3)))&0xff]
+ k[ki] = w
+ ki++
+ }
+ }
+ }
+
+ for i := 0; i < 16; i++ {
+ c.masking[i] = k[i]
+ c.rotate[i] = uint8(k[16+i] & 0x1f)
+ }
+}
+
+// These are the three 'f' functions. See RFC 2144, section 2.2.
+func f1(d, m uint32, r uint8) uint32 {
+ t := m + d
+ I := bits.RotateLeft32(t, int(r))
+ return ((sBox[0][I>>24] ^ sBox[1][(I>>16)&0xff]) - sBox[2][(I>>8)&0xff]) + sBox[3][I&0xff]
+}
+
+func f2(d, m uint32, r uint8) uint32 {
+ t := m ^ d
+ I := bits.RotateLeft32(t, int(r))
+ return ((sBox[0][I>>24] - sBox[1][(I>>16)&0xff]) + sBox[2][(I>>8)&0xff]) ^ sBox[3][I&0xff]
+}
+
+func f3(d, m uint32, r uint8) uint32 {
+ t := m - d
+ I := bits.RotateLeft32(t, int(r))
+ return ((sBox[0][I>>24] + sBox[1][(I>>16)&0xff]) ^ sBox[2][(I>>8)&0xff]) - sBox[3][I&0xff]
+}
+
+var sBox = [8][256]uint32{
+ {
+ 0x30fb40d4, 0x9fa0ff0b, 0x6beccd2f, 0x3f258c7a, 0x1e213f2f, 0x9c004dd3, 0x6003e540, 0xcf9fc949,
+ 0xbfd4af27, 0x88bbbdb5, 0xe2034090, 0x98d09675, 0x6e63a0e0, 0x15c361d2, 0xc2e7661d, 0x22d4ff8e,
+ 0x28683b6f, 0xc07fd059, 0xff2379c8, 0x775f50e2, 0x43c340d3, 0xdf2f8656, 0x887ca41a, 0xa2d2bd2d,
+ 0xa1c9e0d6, 0x346c4819, 0x61b76d87, 0x22540f2f, 0x2abe32e1, 0xaa54166b, 0x22568e3a, 0xa2d341d0,
+ 0x66db40c8, 0xa784392f, 0x004dff2f, 0x2db9d2de, 0x97943fac, 0x4a97c1d8, 0x527644b7, 0xb5f437a7,
+ 0xb82cbaef, 0xd751d159, 0x6ff7f0ed, 0x5a097a1f, 0x827b68d0, 0x90ecf52e, 0x22b0c054, 0xbc8e5935,
+ 0x4b6d2f7f, 0x50bb64a2, 0xd2664910, 0xbee5812d, 0xb7332290, 0xe93b159f, 0xb48ee411, 0x4bff345d,
+ 0xfd45c240, 0xad31973f, 0xc4f6d02e, 0x55fc8165, 0xd5b1caad, 0xa1ac2dae, 0xa2d4b76d, 0xc19b0c50,
+ 0x882240f2, 0x0c6e4f38, 0xa4e4bfd7, 0x4f5ba272, 0x564c1d2f, 0xc59c5319, 0xb949e354, 0xb04669fe,
+ 0xb1b6ab8a, 0xc71358dd, 0x6385c545, 0x110f935d, 0x57538ad5, 0x6a390493, 0xe63d37e0, 0x2a54f6b3,
+ 0x3a787d5f, 0x6276a0b5, 0x19a6fcdf, 0x7a42206a, 0x29f9d4d5, 0xf61b1891, 0xbb72275e, 0xaa508167,
+ 0x38901091, 0xc6b505eb, 0x84c7cb8c, 0x2ad75a0f, 0x874a1427, 0xa2d1936b, 0x2ad286af, 0xaa56d291,
+ 0xd7894360, 0x425c750d, 0x93b39e26, 0x187184c9, 0x6c00b32d, 0x73e2bb14, 0xa0bebc3c, 0x54623779,
+ 0x64459eab, 0x3f328b82, 0x7718cf82, 0x59a2cea6, 0x04ee002e, 0x89fe78e6, 0x3fab0950, 0x325ff6c2,
+ 0x81383f05, 0x6963c5c8, 0x76cb5ad6, 0xd49974c9, 0xca180dcf, 0x380782d5, 0xc7fa5cf6, 0x8ac31511,
+ 0x35e79e13, 0x47da91d0, 0xf40f9086, 0xa7e2419e, 0x31366241, 0x051ef495, 0xaa573b04, 0x4a805d8d,
+ 0x548300d0, 0x00322a3c, 0xbf64cddf, 0xba57a68e, 0x75c6372b, 0x50afd341, 0xa7c13275, 0x915a0bf5,
+ 0x6b54bfab, 0x2b0b1426, 0xab4cc9d7, 0x449ccd82, 0xf7fbf265, 0xab85c5f3, 0x1b55db94, 0xaad4e324,
+ 0xcfa4bd3f, 0x2deaa3e2, 0x9e204d02, 0xc8bd25ac, 0xeadf55b3, 0xd5bd9e98, 0xe31231b2, 0x2ad5ad6c,
+ 0x954329de, 0xadbe4528, 0xd8710f69, 0xaa51c90f, 0xaa786bf6, 0x22513f1e, 0xaa51a79b, 0x2ad344cc,
+ 0x7b5a41f0, 0xd37cfbad, 0x1b069505, 0x41ece491, 0xb4c332e6, 0x032268d4, 0xc9600acc, 0xce387e6d,
+ 0xbf6bb16c, 0x6a70fb78, 0x0d03d9c9, 0xd4df39de, 0xe01063da, 0x4736f464, 0x5ad328d8, 0xb347cc96,
+ 0x75bb0fc3, 0x98511bfb, 0x4ffbcc35, 0xb58bcf6a, 0xe11f0abc, 0xbfc5fe4a, 0xa70aec10, 0xac39570a,
+ 0x3f04442f, 0x6188b153, 0xe0397a2e, 0x5727cb79, 0x9ceb418f, 0x1cacd68d, 0x2ad37c96, 0x0175cb9d,
+ 0xc69dff09, 0xc75b65f0, 0xd9db40d8, 0xec0e7779, 0x4744ead4, 0xb11c3274, 0xdd24cb9e, 0x7e1c54bd,
+ 0xf01144f9, 0xd2240eb1, 0x9675b3fd, 0xa3ac3755, 0xd47c27af, 0x51c85f4d, 0x56907596, 0xa5bb15e6,
+ 0x580304f0, 0xca042cf1, 0x011a37ea, 0x8dbfaadb, 0x35ba3e4a, 0x3526ffa0, 0xc37b4d09, 0xbc306ed9,
+ 0x98a52666, 0x5648f725, 0xff5e569d, 0x0ced63d0, 0x7c63b2cf, 0x700b45e1, 0xd5ea50f1, 0x85a92872,
+ 0xaf1fbda7, 0xd4234870, 0xa7870bf3, 0x2d3b4d79, 0x42e04198, 0x0cd0ede7, 0x26470db8, 0xf881814c,
+ 0x474d6ad7, 0x7c0c5e5c, 0xd1231959, 0x381b7298, 0xf5d2f4db, 0xab838653, 0x6e2f1e23, 0x83719c9e,
+ 0xbd91e046, 0x9a56456e, 0xdc39200c, 0x20c8c571, 0x962bda1c, 0xe1e696ff, 0xb141ab08, 0x7cca89b9,
+ 0x1a69e783, 0x02cc4843, 0xa2f7c579, 0x429ef47d, 0x427b169c, 0x5ac9f049, 0xdd8f0f00, 0x5c8165bf,
+ },
+ {
+ 0x1f201094, 0xef0ba75b, 0x69e3cf7e, 0x393f4380, 0xfe61cf7a, 0xeec5207a, 0x55889c94, 0x72fc0651,
+ 0xada7ef79, 0x4e1d7235, 0xd55a63ce, 0xde0436ba, 0x99c430ef, 0x5f0c0794, 0x18dcdb7d, 0xa1d6eff3,
+ 0xa0b52f7b, 0x59e83605, 0xee15b094, 0xe9ffd909, 0xdc440086, 0xef944459, 0xba83ccb3, 0xe0c3cdfb,
+ 0xd1da4181, 0x3b092ab1, 0xf997f1c1, 0xa5e6cf7b, 0x01420ddb, 0xe4e7ef5b, 0x25a1ff41, 0xe180f806,
+ 0x1fc41080, 0x179bee7a, 0xd37ac6a9, 0xfe5830a4, 0x98de8b7f, 0x77e83f4e, 0x79929269, 0x24fa9f7b,
+ 0xe113c85b, 0xacc40083, 0xd7503525, 0xf7ea615f, 0x62143154, 0x0d554b63, 0x5d681121, 0xc866c359,
+ 0x3d63cf73, 0xcee234c0, 0xd4d87e87, 0x5c672b21, 0x071f6181, 0x39f7627f, 0x361e3084, 0xe4eb573b,
+ 0x602f64a4, 0xd63acd9c, 0x1bbc4635, 0x9e81032d, 0x2701f50c, 0x99847ab4, 0xa0e3df79, 0xba6cf38c,
+ 0x10843094, 0x2537a95e, 0xf46f6ffe, 0xa1ff3b1f, 0x208cfb6a, 0x8f458c74, 0xd9e0a227, 0x4ec73a34,
+ 0xfc884f69, 0x3e4de8df, 0xef0e0088, 0x3559648d, 0x8a45388c, 0x1d804366, 0x721d9bfd, 0xa58684bb,
+ 0xe8256333, 0x844e8212, 0x128d8098, 0xfed33fb4, 0xce280ae1, 0x27e19ba5, 0xd5a6c252, 0xe49754bd,
+ 0xc5d655dd, 0xeb667064, 0x77840b4d, 0xa1b6a801, 0x84db26a9, 0xe0b56714, 0x21f043b7, 0xe5d05860,
+ 0x54f03084, 0x066ff472, 0xa31aa153, 0xdadc4755, 0xb5625dbf, 0x68561be6, 0x83ca6b94, 0x2d6ed23b,
+ 0xeccf01db, 0xa6d3d0ba, 0xb6803d5c, 0xaf77a709, 0x33b4a34c, 0x397bc8d6, 0x5ee22b95, 0x5f0e5304,
+ 0x81ed6f61, 0x20e74364, 0xb45e1378, 0xde18639b, 0x881ca122, 0xb96726d1, 0x8049a7e8, 0x22b7da7b,
+ 0x5e552d25, 0x5272d237, 0x79d2951c, 0xc60d894c, 0x488cb402, 0x1ba4fe5b, 0xa4b09f6b, 0x1ca815cf,
+ 0xa20c3005, 0x8871df63, 0xb9de2fcb, 0x0cc6c9e9, 0x0beeff53, 0xe3214517, 0xb4542835, 0x9f63293c,
+ 0xee41e729, 0x6e1d2d7c, 0x50045286, 0x1e6685f3, 0xf33401c6, 0x30a22c95, 0x31a70850, 0x60930f13,
+ 0x73f98417, 0xa1269859, 0xec645c44, 0x52c877a9, 0xcdff33a6, 0xa02b1741, 0x7cbad9a2, 0x2180036f,
+ 0x50d99c08, 0xcb3f4861, 0xc26bd765, 0x64a3f6ab, 0x80342676, 0x25a75e7b, 0xe4e6d1fc, 0x20c710e6,
+ 0xcdf0b680, 0x17844d3b, 0x31eef84d, 0x7e0824e4, 0x2ccb49eb, 0x846a3bae, 0x8ff77888, 0xee5d60f6,
+ 0x7af75673, 0x2fdd5cdb, 0xa11631c1, 0x30f66f43, 0xb3faec54, 0x157fd7fa, 0xef8579cc, 0xd152de58,
+ 0xdb2ffd5e, 0x8f32ce19, 0x306af97a, 0x02f03ef8, 0x99319ad5, 0xc242fa0f, 0xa7e3ebb0, 0xc68e4906,
+ 0xb8da230c, 0x80823028, 0xdcdef3c8, 0xd35fb171, 0x088a1bc8, 0xbec0c560, 0x61a3c9e8, 0xbca8f54d,
+ 0xc72feffa, 0x22822e99, 0x82c570b4, 0xd8d94e89, 0x8b1c34bc, 0x301e16e6, 0x273be979, 0xb0ffeaa6,
+ 0x61d9b8c6, 0x00b24869, 0xb7ffce3f, 0x08dc283b, 0x43daf65a, 0xf7e19798, 0x7619b72f, 0x8f1c9ba4,
+ 0xdc8637a0, 0x16a7d3b1, 0x9fc393b7, 0xa7136eeb, 0xc6bcc63e, 0x1a513742, 0xef6828bc, 0x520365d6,
+ 0x2d6a77ab, 0x3527ed4b, 0x821fd216, 0x095c6e2e, 0xdb92f2fb, 0x5eea29cb, 0x145892f5, 0x91584f7f,
+ 0x5483697b, 0x2667a8cc, 0x85196048, 0x8c4bacea, 0x833860d4, 0x0d23e0f9, 0x6c387e8a, 0x0ae6d249,
+ 0xb284600c, 0xd835731d, 0xdcb1c647, 0xac4c56ea, 0x3ebd81b3, 0x230eabb0, 0x6438bc87, 0xf0b5b1fa,
+ 0x8f5ea2b3, 0xfc184642, 0x0a036b7a, 0x4fb089bd, 0x649da589, 0xa345415e, 0x5c038323, 0x3e5d3bb9,
+ 0x43d79572, 0x7e6dd07c, 0x06dfdf1e, 0x6c6cc4ef, 0x7160a539, 0x73bfbe70, 0x83877605, 0x4523ecf1,
+ },
+ {
+ 0x8defc240, 0x25fa5d9f, 0xeb903dbf, 0xe810c907, 0x47607fff, 0x369fe44b, 0x8c1fc644, 0xaececa90,
+ 0xbeb1f9bf, 0xeefbcaea, 0xe8cf1950, 0x51df07ae, 0x920e8806, 0xf0ad0548, 0xe13c8d83, 0x927010d5,
+ 0x11107d9f, 0x07647db9, 0xb2e3e4d4, 0x3d4f285e, 0xb9afa820, 0xfade82e0, 0xa067268b, 0x8272792e,
+ 0x553fb2c0, 0x489ae22b, 0xd4ef9794, 0x125e3fbc, 0x21fffcee, 0x825b1bfd, 0x9255c5ed, 0x1257a240,
+ 0x4e1a8302, 0xbae07fff, 0x528246e7, 0x8e57140e, 0x3373f7bf, 0x8c9f8188, 0xa6fc4ee8, 0xc982b5a5,
+ 0xa8c01db7, 0x579fc264, 0x67094f31, 0xf2bd3f5f, 0x40fff7c1, 0x1fb78dfc, 0x8e6bd2c1, 0x437be59b,
+ 0x99b03dbf, 0xb5dbc64b, 0x638dc0e6, 0x55819d99, 0xa197c81c, 0x4a012d6e, 0xc5884a28, 0xccc36f71,
+ 0xb843c213, 0x6c0743f1, 0x8309893c, 0x0feddd5f, 0x2f7fe850, 0xd7c07f7e, 0x02507fbf, 0x5afb9a04,
+ 0xa747d2d0, 0x1651192e, 0xaf70bf3e, 0x58c31380, 0x5f98302e, 0x727cc3c4, 0x0a0fb402, 0x0f7fef82,
+ 0x8c96fdad, 0x5d2c2aae, 0x8ee99a49, 0x50da88b8, 0x8427f4a0, 0x1eac5790, 0x796fb449, 0x8252dc15,
+ 0xefbd7d9b, 0xa672597d, 0xada840d8, 0x45f54504, 0xfa5d7403, 0xe83ec305, 0x4f91751a, 0x925669c2,
+ 0x23efe941, 0xa903f12e, 0x60270df2, 0x0276e4b6, 0x94fd6574, 0x927985b2, 0x8276dbcb, 0x02778176,
+ 0xf8af918d, 0x4e48f79e, 0x8f616ddf, 0xe29d840e, 0x842f7d83, 0x340ce5c8, 0x96bbb682, 0x93b4b148,
+ 0xef303cab, 0x984faf28, 0x779faf9b, 0x92dc560d, 0x224d1e20, 0x8437aa88, 0x7d29dc96, 0x2756d3dc,
+ 0x8b907cee, 0xb51fd240, 0xe7c07ce3, 0xe566b4a1, 0xc3e9615e, 0x3cf8209d, 0x6094d1e3, 0xcd9ca341,
+ 0x5c76460e, 0x00ea983b, 0xd4d67881, 0xfd47572c, 0xf76cedd9, 0xbda8229c, 0x127dadaa, 0x438a074e,
+ 0x1f97c090, 0x081bdb8a, 0x93a07ebe, 0xb938ca15, 0x97b03cff, 0x3dc2c0f8, 0x8d1ab2ec, 0x64380e51,
+ 0x68cc7bfb, 0xd90f2788, 0x12490181, 0x5de5ffd4, 0xdd7ef86a, 0x76a2e214, 0xb9a40368, 0x925d958f,
+ 0x4b39fffa, 0xba39aee9, 0xa4ffd30b, 0xfaf7933b, 0x6d498623, 0x193cbcfa, 0x27627545, 0x825cf47a,
+ 0x61bd8ba0, 0xd11e42d1, 0xcead04f4, 0x127ea392, 0x10428db7, 0x8272a972, 0x9270c4a8, 0x127de50b,
+ 0x285ba1c8, 0x3c62f44f, 0x35c0eaa5, 0xe805d231, 0x428929fb, 0xb4fcdf82, 0x4fb66a53, 0x0e7dc15b,
+ 0x1f081fab, 0x108618ae, 0xfcfd086d, 0xf9ff2889, 0x694bcc11, 0x236a5cae, 0x12deca4d, 0x2c3f8cc5,
+ 0xd2d02dfe, 0xf8ef5896, 0xe4cf52da, 0x95155b67, 0x494a488c, 0xb9b6a80c, 0x5c8f82bc, 0x89d36b45,
+ 0x3a609437, 0xec00c9a9, 0x44715253, 0x0a874b49, 0xd773bc40, 0x7c34671c, 0x02717ef6, 0x4feb5536,
+ 0xa2d02fff, 0xd2bf60c4, 0xd43f03c0, 0x50b4ef6d, 0x07478cd1, 0x006e1888, 0xa2e53f55, 0xb9e6d4bc,
+ 0xa2048016, 0x97573833, 0xd7207d67, 0xde0f8f3d, 0x72f87b33, 0xabcc4f33, 0x7688c55d, 0x7b00a6b0,
+ 0x947b0001, 0x570075d2, 0xf9bb88f8, 0x8942019e, 0x4264a5ff, 0x856302e0, 0x72dbd92b, 0xee971b69,
+ 0x6ea22fde, 0x5f08ae2b, 0xaf7a616d, 0xe5c98767, 0xcf1febd2, 0x61efc8c2, 0xf1ac2571, 0xcc8239c2,
+ 0x67214cb8, 0xb1e583d1, 0xb7dc3e62, 0x7f10bdce, 0xf90a5c38, 0x0ff0443d, 0x606e6dc6, 0x60543a49,
+ 0x5727c148, 0x2be98a1d, 0x8ab41738, 0x20e1be24, 0xaf96da0f, 0x68458425, 0x99833be5, 0x600d457d,
+ 0x282f9350, 0x8334b362, 0xd91d1120, 0x2b6d8da0, 0x642b1e31, 0x9c305a00, 0x52bce688, 0x1b03588a,
+ 0xf7baefd5, 0x4142ed9c, 0xa4315c11, 0x83323ec5, 0xdfef4636, 0xa133c501, 0xe9d3531c, 0xee353783,
+ },
+ {
+ 0x9db30420, 0x1fb6e9de, 0xa7be7bef, 0xd273a298, 0x4a4f7bdb, 0x64ad8c57, 0x85510443, 0xfa020ed1,
+ 0x7e287aff, 0xe60fb663, 0x095f35a1, 0x79ebf120, 0xfd059d43, 0x6497b7b1, 0xf3641f63, 0x241e4adf,
+ 0x28147f5f, 0x4fa2b8cd, 0xc9430040, 0x0cc32220, 0xfdd30b30, 0xc0a5374f, 0x1d2d00d9, 0x24147b15,
+ 0xee4d111a, 0x0fca5167, 0x71ff904c, 0x2d195ffe, 0x1a05645f, 0x0c13fefe, 0x081b08ca, 0x05170121,
+ 0x80530100, 0xe83e5efe, 0xac9af4f8, 0x7fe72701, 0xd2b8ee5f, 0x06df4261, 0xbb9e9b8a, 0x7293ea25,
+ 0xce84ffdf, 0xf5718801, 0x3dd64b04, 0xa26f263b, 0x7ed48400, 0x547eebe6, 0x446d4ca0, 0x6cf3d6f5,
+ 0x2649abdf, 0xaea0c7f5, 0x36338cc1, 0x503f7e93, 0xd3772061, 0x11b638e1, 0x72500e03, 0xf80eb2bb,
+ 0xabe0502e, 0xec8d77de, 0x57971e81, 0xe14f6746, 0xc9335400, 0x6920318f, 0x081dbb99, 0xffc304a5,
+ 0x4d351805, 0x7f3d5ce3, 0xa6c866c6, 0x5d5bcca9, 0xdaec6fea, 0x9f926f91, 0x9f46222f, 0x3991467d,
+ 0xa5bf6d8e, 0x1143c44f, 0x43958302, 0xd0214eeb, 0x022083b8, 0x3fb6180c, 0x18f8931e, 0x281658e6,
+ 0x26486e3e, 0x8bd78a70, 0x7477e4c1, 0xb506e07c, 0xf32d0a25, 0x79098b02, 0xe4eabb81, 0x28123b23,
+ 0x69dead38, 0x1574ca16, 0xdf871b62, 0x211c40b7, 0xa51a9ef9, 0x0014377b, 0x041e8ac8, 0x09114003,
+ 0xbd59e4d2, 0xe3d156d5, 0x4fe876d5, 0x2f91a340, 0x557be8de, 0x00eae4a7, 0x0ce5c2ec, 0x4db4bba6,
+ 0xe756bdff, 0xdd3369ac, 0xec17b035, 0x06572327, 0x99afc8b0, 0x56c8c391, 0x6b65811c, 0x5e146119,
+ 0x6e85cb75, 0xbe07c002, 0xc2325577, 0x893ff4ec, 0x5bbfc92d, 0xd0ec3b25, 0xb7801ab7, 0x8d6d3b24,
+ 0x20c763ef, 0xc366a5fc, 0x9c382880, 0x0ace3205, 0xaac9548a, 0xeca1d7c7, 0x041afa32, 0x1d16625a,
+ 0x6701902c, 0x9b757a54, 0x31d477f7, 0x9126b031, 0x36cc6fdb, 0xc70b8b46, 0xd9e66a48, 0x56e55a79,
+ 0x026a4ceb, 0x52437eff, 0x2f8f76b4, 0x0df980a5, 0x8674cde3, 0xedda04eb, 0x17a9be04, 0x2c18f4df,
+ 0xb7747f9d, 0xab2af7b4, 0xefc34d20, 0x2e096b7c, 0x1741a254, 0xe5b6a035, 0x213d42f6, 0x2c1c7c26,
+ 0x61c2f50f, 0x6552daf9, 0xd2c231f8, 0x25130f69, 0xd8167fa2, 0x0418f2c8, 0x001a96a6, 0x0d1526ab,
+ 0x63315c21, 0x5e0a72ec, 0x49bafefd, 0x187908d9, 0x8d0dbd86, 0x311170a7, 0x3e9b640c, 0xcc3e10d7,
+ 0xd5cad3b6, 0x0caec388, 0xf73001e1, 0x6c728aff, 0x71eae2a1, 0x1f9af36e, 0xcfcbd12f, 0xc1de8417,
+ 0xac07be6b, 0xcb44a1d8, 0x8b9b0f56, 0x013988c3, 0xb1c52fca, 0xb4be31cd, 0xd8782806, 0x12a3a4e2,
+ 0x6f7de532, 0x58fd7eb6, 0xd01ee900, 0x24adffc2, 0xf4990fc5, 0x9711aac5, 0x001d7b95, 0x82e5e7d2,
+ 0x109873f6, 0x00613096, 0xc32d9521, 0xada121ff, 0x29908415, 0x7fbb977f, 0xaf9eb3db, 0x29c9ed2a,
+ 0x5ce2a465, 0xa730f32c, 0xd0aa3fe8, 0x8a5cc091, 0xd49e2ce7, 0x0ce454a9, 0xd60acd86, 0x015f1919,
+ 0x77079103, 0xdea03af6, 0x78a8565e, 0xdee356df, 0x21f05cbe, 0x8b75e387, 0xb3c50651, 0xb8a5c3ef,
+ 0xd8eeb6d2, 0xe523be77, 0xc2154529, 0x2f69efdf, 0xafe67afb, 0xf470c4b2, 0xf3e0eb5b, 0xd6cc9876,
+ 0x39e4460c, 0x1fda8538, 0x1987832f, 0xca007367, 0xa99144f8, 0x296b299e, 0x492fc295, 0x9266beab,
+ 0xb5676e69, 0x9bd3ddda, 0xdf7e052f, 0xdb25701c, 0x1b5e51ee, 0xf65324e6, 0x6afce36c, 0x0316cc04,
+ 0x8644213e, 0xb7dc59d0, 0x7965291f, 0xccd6fd43, 0x41823979, 0x932bcdf6, 0xb657c34d, 0x4edfd282,
+ 0x7ae5290c, 0x3cb9536b, 0x851e20fe, 0x9833557e, 0x13ecf0b0, 0xd3ffb372, 0x3f85c5c1, 0x0aef7ed2,
+ },
+ {
+ 0x7ec90c04, 0x2c6e74b9, 0x9b0e66df, 0xa6337911, 0xb86a7fff, 0x1dd358f5, 0x44dd9d44, 0x1731167f,
+ 0x08fbf1fa, 0xe7f511cc, 0xd2051b00, 0x735aba00, 0x2ab722d8, 0x386381cb, 0xacf6243a, 0x69befd7a,
+ 0xe6a2e77f, 0xf0c720cd, 0xc4494816, 0xccf5c180, 0x38851640, 0x15b0a848, 0xe68b18cb, 0x4caadeff,
+ 0x5f480a01, 0x0412b2aa, 0x259814fc, 0x41d0efe2, 0x4e40b48d, 0x248eb6fb, 0x8dba1cfe, 0x41a99b02,
+ 0x1a550a04, 0xba8f65cb, 0x7251f4e7, 0x95a51725, 0xc106ecd7, 0x97a5980a, 0xc539b9aa, 0x4d79fe6a,
+ 0xf2f3f763, 0x68af8040, 0xed0c9e56, 0x11b4958b, 0xe1eb5a88, 0x8709e6b0, 0xd7e07156, 0x4e29fea7,
+ 0x6366e52d, 0x02d1c000, 0xc4ac8e05, 0x9377f571, 0x0c05372a, 0x578535f2, 0x2261be02, 0xd642a0c9,
+ 0xdf13a280, 0x74b55bd2, 0x682199c0, 0xd421e5ec, 0x53fb3ce8, 0xc8adedb3, 0x28a87fc9, 0x3d959981,
+ 0x5c1ff900, 0xfe38d399, 0x0c4eff0b, 0x062407ea, 0xaa2f4fb1, 0x4fb96976, 0x90c79505, 0xb0a8a774,
+ 0xef55a1ff, 0xe59ca2c2, 0xa6b62d27, 0xe66a4263, 0xdf65001f, 0x0ec50966, 0xdfdd55bc, 0x29de0655,
+ 0x911e739a, 0x17af8975, 0x32c7911c, 0x89f89468, 0x0d01e980, 0x524755f4, 0x03b63cc9, 0x0cc844b2,
+ 0xbcf3f0aa, 0x87ac36e9, 0xe53a7426, 0x01b3d82b, 0x1a9e7449, 0x64ee2d7e, 0xcddbb1da, 0x01c94910,
+ 0xb868bf80, 0x0d26f3fd, 0x9342ede7, 0x04a5c284, 0x636737b6, 0x50f5b616, 0xf24766e3, 0x8eca36c1,
+ 0x136e05db, 0xfef18391, 0xfb887a37, 0xd6e7f7d4, 0xc7fb7dc9, 0x3063fcdf, 0xb6f589de, 0xec2941da,
+ 0x26e46695, 0xb7566419, 0xf654efc5, 0xd08d58b7, 0x48925401, 0xc1bacb7f, 0xe5ff550f, 0xb6083049,
+ 0x5bb5d0e8, 0x87d72e5a, 0xab6a6ee1, 0x223a66ce, 0xc62bf3cd, 0x9e0885f9, 0x68cb3e47, 0x086c010f,
+ 0xa21de820, 0xd18b69de, 0xf3f65777, 0xfa02c3f6, 0x407edac3, 0xcbb3d550, 0x1793084d, 0xb0d70eba,
+ 0x0ab378d5, 0xd951fb0c, 0xded7da56, 0x4124bbe4, 0x94ca0b56, 0x0f5755d1, 0xe0e1e56e, 0x6184b5be,
+ 0x580a249f, 0x94f74bc0, 0xe327888e, 0x9f7b5561, 0xc3dc0280, 0x05687715, 0x646c6bd7, 0x44904db3,
+ 0x66b4f0a3, 0xc0f1648a, 0x697ed5af, 0x49e92ff6, 0x309e374f, 0x2cb6356a, 0x85808573, 0x4991f840,
+ 0x76f0ae02, 0x083be84d, 0x28421c9a, 0x44489406, 0x736e4cb8, 0xc1092910, 0x8bc95fc6, 0x7d869cf4,
+ 0x134f616f, 0x2e77118d, 0xb31b2be1, 0xaa90b472, 0x3ca5d717, 0x7d161bba, 0x9cad9010, 0xaf462ba2,
+ 0x9fe459d2, 0x45d34559, 0xd9f2da13, 0xdbc65487, 0xf3e4f94e, 0x176d486f, 0x097c13ea, 0x631da5c7,
+ 0x445f7382, 0x175683f4, 0xcdc66a97, 0x70be0288, 0xb3cdcf72, 0x6e5dd2f3, 0x20936079, 0x459b80a5,
+ 0xbe60e2db, 0xa9c23101, 0xeba5315c, 0x224e42f2, 0x1c5c1572, 0xf6721b2c, 0x1ad2fff3, 0x8c25404e,
+ 0x324ed72f, 0x4067b7fd, 0x0523138e, 0x5ca3bc78, 0xdc0fd66e, 0x75922283, 0x784d6b17, 0x58ebb16e,
+ 0x44094f85, 0x3f481d87, 0xfcfeae7b, 0x77b5ff76, 0x8c2302bf, 0xaaf47556, 0x5f46b02a, 0x2b092801,
+ 0x3d38f5f7, 0x0ca81f36, 0x52af4a8a, 0x66d5e7c0, 0xdf3b0874, 0x95055110, 0x1b5ad7a8, 0xf61ed5ad,
+ 0x6cf6e479, 0x20758184, 0xd0cefa65, 0x88f7be58, 0x4a046826, 0x0ff6f8f3, 0xa09c7f70, 0x5346aba0,
+ 0x5ce96c28, 0xe176eda3, 0x6bac307f, 0x376829d2, 0x85360fa9, 0x17e3fe2a, 0x24b79767, 0xf5a96b20,
+ 0xd6cd2595, 0x68ff1ebf, 0x7555442c, 0xf19f06be, 0xf9e0659a, 0xeeb9491d, 0x34010718, 0xbb30cab8,
+ 0xe822fe15, 0x88570983, 0x750e6249, 0xda627e55, 0x5e76ffa8, 0xb1534546, 0x6d47de08, 0xefe9e7d4,
+ },
+ {
+ 0xf6fa8f9d, 0x2cac6ce1, 0x4ca34867, 0xe2337f7c, 0x95db08e7, 0x016843b4, 0xeced5cbc, 0x325553ac,
+ 0xbf9f0960, 0xdfa1e2ed, 0x83f0579d, 0x63ed86b9, 0x1ab6a6b8, 0xde5ebe39, 0xf38ff732, 0x8989b138,
+ 0x33f14961, 0xc01937bd, 0xf506c6da, 0xe4625e7e, 0xa308ea99, 0x4e23e33c, 0x79cbd7cc, 0x48a14367,
+ 0xa3149619, 0xfec94bd5, 0xa114174a, 0xeaa01866, 0xa084db2d, 0x09a8486f, 0xa888614a, 0x2900af98,
+ 0x01665991, 0xe1992863, 0xc8f30c60, 0x2e78ef3c, 0xd0d51932, 0xcf0fec14, 0xf7ca07d2, 0xd0a82072,
+ 0xfd41197e, 0x9305a6b0, 0xe86be3da, 0x74bed3cd, 0x372da53c, 0x4c7f4448, 0xdab5d440, 0x6dba0ec3,
+ 0x083919a7, 0x9fbaeed9, 0x49dbcfb0, 0x4e670c53, 0x5c3d9c01, 0x64bdb941, 0x2c0e636a, 0xba7dd9cd,
+ 0xea6f7388, 0xe70bc762, 0x35f29adb, 0x5c4cdd8d, 0xf0d48d8c, 0xb88153e2, 0x08a19866, 0x1ae2eac8,
+ 0x284caf89, 0xaa928223, 0x9334be53, 0x3b3a21bf, 0x16434be3, 0x9aea3906, 0xefe8c36e, 0xf890cdd9,
+ 0x80226dae, 0xc340a4a3, 0xdf7e9c09, 0xa694a807, 0x5b7c5ecc, 0x221db3a6, 0x9a69a02f, 0x68818a54,
+ 0xceb2296f, 0x53c0843a, 0xfe893655, 0x25bfe68a, 0xb4628abc, 0xcf222ebf, 0x25ac6f48, 0xa9a99387,
+ 0x53bddb65, 0xe76ffbe7, 0xe967fd78, 0x0ba93563, 0x8e342bc1, 0xe8a11be9, 0x4980740d, 0xc8087dfc,
+ 0x8de4bf99, 0xa11101a0, 0x7fd37975, 0xda5a26c0, 0xe81f994f, 0x9528cd89, 0xfd339fed, 0xb87834bf,
+ 0x5f04456d, 0x22258698, 0xc9c4c83b, 0x2dc156be, 0x4f628daa, 0x57f55ec5, 0xe2220abe, 0xd2916ebf,
+ 0x4ec75b95, 0x24f2c3c0, 0x42d15d99, 0xcd0d7fa0, 0x7b6e27ff, 0xa8dc8af0, 0x7345c106, 0xf41e232f,
+ 0x35162386, 0xe6ea8926, 0x3333b094, 0x157ec6f2, 0x372b74af, 0x692573e4, 0xe9a9d848, 0xf3160289,
+ 0x3a62ef1d, 0xa787e238, 0xf3a5f676, 0x74364853, 0x20951063, 0x4576698d, 0xb6fad407, 0x592af950,
+ 0x36f73523, 0x4cfb6e87, 0x7da4cec0, 0x6c152daa, 0xcb0396a8, 0xc50dfe5d, 0xfcd707ab, 0x0921c42f,
+ 0x89dff0bb, 0x5fe2be78, 0x448f4f33, 0x754613c9, 0x2b05d08d, 0x48b9d585, 0xdc049441, 0xc8098f9b,
+ 0x7dede786, 0xc39a3373, 0x42410005, 0x6a091751, 0x0ef3c8a6, 0x890072d6, 0x28207682, 0xa9a9f7be,
+ 0xbf32679d, 0xd45b5b75, 0xb353fd00, 0xcbb0e358, 0x830f220a, 0x1f8fb214, 0xd372cf08, 0xcc3c4a13,
+ 0x8cf63166, 0x061c87be, 0x88c98f88, 0x6062e397, 0x47cf8e7a, 0xb6c85283, 0x3cc2acfb, 0x3fc06976,
+ 0x4e8f0252, 0x64d8314d, 0xda3870e3, 0x1e665459, 0xc10908f0, 0x513021a5, 0x6c5b68b7, 0x822f8aa0,
+ 0x3007cd3e, 0x74719eef, 0xdc872681, 0x073340d4, 0x7e432fd9, 0x0c5ec241, 0x8809286c, 0xf592d891,
+ 0x08a930f6, 0x957ef305, 0xb7fbffbd, 0xc266e96f, 0x6fe4ac98, 0xb173ecc0, 0xbc60b42a, 0x953498da,
+ 0xfba1ae12, 0x2d4bd736, 0x0f25faab, 0xa4f3fceb, 0xe2969123, 0x257f0c3d, 0x9348af49, 0x361400bc,
+ 0xe8816f4a, 0x3814f200, 0xa3f94043, 0x9c7a54c2, 0xbc704f57, 0xda41e7f9, 0xc25ad33a, 0x54f4a084,
+ 0xb17f5505, 0x59357cbe, 0xedbd15c8, 0x7f97c5ab, 0xba5ac7b5, 0xb6f6deaf, 0x3a479c3a, 0x5302da25,
+ 0x653d7e6a, 0x54268d49, 0x51a477ea, 0x5017d55b, 0xd7d25d88, 0x44136c76, 0x0404a8c8, 0xb8e5a121,
+ 0xb81a928a, 0x60ed5869, 0x97c55b96, 0xeaec991b, 0x29935913, 0x01fdb7f1, 0x088e8dfa, 0x9ab6f6f5,
+ 0x3b4cbf9f, 0x4a5de3ab, 0xe6051d35, 0xa0e1d855, 0xd36b4cf1, 0xf544edeb, 0xb0e93524, 0xbebb8fbd,
+ 0xa2d762cf, 0x49c92f54, 0x38b5f331, 0x7128a454, 0x48392905, 0xa65b1db8, 0x851c97bd, 0xd675cf2f,
+ },
+ {
+ 0x85e04019, 0x332bf567, 0x662dbfff, 0xcfc65693, 0x2a8d7f6f, 0xab9bc912, 0xde6008a1, 0x2028da1f,
+ 0x0227bce7, 0x4d642916, 0x18fac300, 0x50f18b82, 0x2cb2cb11, 0xb232e75c, 0x4b3695f2, 0xb28707de,
+ 0xa05fbcf6, 0xcd4181e9, 0xe150210c, 0xe24ef1bd, 0xb168c381, 0xfde4e789, 0x5c79b0d8, 0x1e8bfd43,
+ 0x4d495001, 0x38be4341, 0x913cee1d, 0x92a79c3f, 0x089766be, 0xbaeeadf4, 0x1286becf, 0xb6eacb19,
+ 0x2660c200, 0x7565bde4, 0x64241f7a, 0x8248dca9, 0xc3b3ad66, 0x28136086, 0x0bd8dfa8, 0x356d1cf2,
+ 0x107789be, 0xb3b2e9ce, 0x0502aa8f, 0x0bc0351e, 0x166bf52a, 0xeb12ff82, 0xe3486911, 0xd34d7516,
+ 0x4e7b3aff, 0x5f43671b, 0x9cf6e037, 0x4981ac83, 0x334266ce, 0x8c9341b7, 0xd0d854c0, 0xcb3a6c88,
+ 0x47bc2829, 0x4725ba37, 0xa66ad22b, 0x7ad61f1e, 0x0c5cbafa, 0x4437f107, 0xb6e79962, 0x42d2d816,
+ 0x0a961288, 0xe1a5c06e, 0x13749e67, 0x72fc081a, 0xb1d139f7, 0xf9583745, 0xcf19df58, 0xbec3f756,
+ 0xc06eba30, 0x07211b24, 0x45c28829, 0xc95e317f, 0xbc8ec511, 0x38bc46e9, 0xc6e6fa14, 0xbae8584a,
+ 0xad4ebc46, 0x468f508b, 0x7829435f, 0xf124183b, 0x821dba9f, 0xaff60ff4, 0xea2c4e6d, 0x16e39264,
+ 0x92544a8b, 0x009b4fc3, 0xaba68ced, 0x9ac96f78, 0x06a5b79a, 0xb2856e6e, 0x1aec3ca9, 0xbe838688,
+ 0x0e0804e9, 0x55f1be56, 0xe7e5363b, 0xb3a1f25d, 0xf7debb85, 0x61fe033c, 0x16746233, 0x3c034c28,
+ 0xda6d0c74, 0x79aac56c, 0x3ce4e1ad, 0x51f0c802, 0x98f8f35a, 0x1626a49f, 0xeed82b29, 0x1d382fe3,
+ 0x0c4fb99a, 0xbb325778, 0x3ec6d97b, 0x6e77a6a9, 0xcb658b5c, 0xd45230c7, 0x2bd1408b, 0x60c03eb7,
+ 0xb9068d78, 0xa33754f4, 0xf430c87d, 0xc8a71302, 0xb96d8c32, 0xebd4e7be, 0xbe8b9d2d, 0x7979fb06,
+ 0xe7225308, 0x8b75cf77, 0x11ef8da4, 0xe083c858, 0x8d6b786f, 0x5a6317a6, 0xfa5cf7a0, 0x5dda0033,
+ 0xf28ebfb0, 0xf5b9c310, 0xa0eac280, 0x08b9767a, 0xa3d9d2b0, 0x79d34217, 0x021a718d, 0x9ac6336a,
+ 0x2711fd60, 0x438050e3, 0x069908a8, 0x3d7fedc4, 0x826d2bef, 0x4eeb8476, 0x488dcf25, 0x36c9d566,
+ 0x28e74e41, 0xc2610aca, 0x3d49a9cf, 0xbae3b9df, 0xb65f8de6, 0x92aeaf64, 0x3ac7d5e6, 0x9ea80509,
+ 0xf22b017d, 0xa4173f70, 0xdd1e16c3, 0x15e0d7f9, 0x50b1b887, 0x2b9f4fd5, 0x625aba82, 0x6a017962,
+ 0x2ec01b9c, 0x15488aa9, 0xd716e740, 0x40055a2c, 0x93d29a22, 0xe32dbf9a, 0x058745b9, 0x3453dc1e,
+ 0xd699296e, 0x496cff6f, 0x1c9f4986, 0xdfe2ed07, 0xb87242d1, 0x19de7eae, 0x053e561a, 0x15ad6f8c,
+ 0x66626c1c, 0x7154c24c, 0xea082b2a, 0x93eb2939, 0x17dcb0f0, 0x58d4f2ae, 0x9ea294fb, 0x52cf564c,
+ 0x9883fe66, 0x2ec40581, 0x763953c3, 0x01d6692e, 0xd3a0c108, 0xa1e7160e, 0xe4f2dfa6, 0x693ed285,
+ 0x74904698, 0x4c2b0edd, 0x4f757656, 0x5d393378, 0xa132234f, 0x3d321c5d, 0xc3f5e194, 0x4b269301,
+ 0xc79f022f, 0x3c997e7e, 0x5e4f9504, 0x3ffafbbd, 0x76f7ad0e, 0x296693f4, 0x3d1fce6f, 0xc61e45be,
+ 0xd3b5ab34, 0xf72bf9b7, 0x1b0434c0, 0x4e72b567, 0x5592a33d, 0xb5229301, 0xcfd2a87f, 0x60aeb767,
+ 0x1814386b, 0x30bcc33d, 0x38a0c07d, 0xfd1606f2, 0xc363519b, 0x589dd390, 0x5479f8e6, 0x1cb8d647,
+ 0x97fd61a9, 0xea7759f4, 0x2d57539d, 0x569a58cf, 0xe84e63ad, 0x462e1b78, 0x6580f87e, 0xf3817914,
+ 0x91da55f4, 0x40a230f3, 0xd1988f35, 0xb6e318d2, 0x3ffa50bc, 0x3d40f021, 0xc3c0bdae, 0x4958c24c,
+ 0x518f36b2, 0x84b1d370, 0x0fedce83, 0x878ddada, 0xf2a279c7, 0x94e01be8, 0x90716f4b, 0x954b8aa3,
+ },
+ {
+ 0xe216300d, 0xbbddfffc, 0xa7ebdabd, 0x35648095, 0x7789f8b7, 0xe6c1121b, 0x0e241600, 0x052ce8b5,
+ 0x11a9cfb0, 0xe5952f11, 0xece7990a, 0x9386d174, 0x2a42931c, 0x76e38111, 0xb12def3a, 0x37ddddfc,
+ 0xde9adeb1, 0x0a0cc32c, 0xbe197029, 0x84a00940, 0xbb243a0f, 0xb4d137cf, 0xb44e79f0, 0x049eedfd,
+ 0x0b15a15d, 0x480d3168, 0x8bbbde5a, 0x669ded42, 0xc7ece831, 0x3f8f95e7, 0x72df191b, 0x7580330d,
+ 0x94074251, 0x5c7dcdfa, 0xabbe6d63, 0xaa402164, 0xb301d40a, 0x02e7d1ca, 0x53571dae, 0x7a3182a2,
+ 0x12a8ddec, 0xfdaa335d, 0x176f43e8, 0x71fb46d4, 0x38129022, 0xce949ad4, 0xb84769ad, 0x965bd862,
+ 0x82f3d055, 0x66fb9767, 0x15b80b4e, 0x1d5b47a0, 0x4cfde06f, 0xc28ec4b8, 0x57e8726e, 0x647a78fc,
+ 0x99865d44, 0x608bd593, 0x6c200e03, 0x39dc5ff6, 0x5d0b00a3, 0xae63aff2, 0x7e8bd632, 0x70108c0c,
+ 0xbbd35049, 0x2998df04, 0x980cf42a, 0x9b6df491, 0x9e7edd53, 0x06918548, 0x58cb7e07, 0x3b74ef2e,
+ 0x522fffb1, 0xd24708cc, 0x1c7e27cd, 0xa4eb215b, 0x3cf1d2e2, 0x19b47a38, 0x424f7618, 0x35856039,
+ 0x9d17dee7, 0x27eb35e6, 0xc9aff67b, 0x36baf5b8, 0x09c467cd, 0xc18910b1, 0xe11dbf7b, 0x06cd1af8,
+ 0x7170c608, 0x2d5e3354, 0xd4de495a, 0x64c6d006, 0xbcc0c62c, 0x3dd00db3, 0x708f8f34, 0x77d51b42,
+ 0x264f620f, 0x24b8d2bf, 0x15c1b79e, 0x46a52564, 0xf8d7e54e, 0x3e378160, 0x7895cda5, 0x859c15a5,
+ 0xe6459788, 0xc37bc75f, 0xdb07ba0c, 0x0676a3ab, 0x7f229b1e, 0x31842e7b, 0x24259fd7, 0xf8bef472,
+ 0x835ffcb8, 0x6df4c1f2, 0x96f5b195, 0xfd0af0fc, 0xb0fe134c, 0xe2506d3d, 0x4f9b12ea, 0xf215f225,
+ 0xa223736f, 0x9fb4c428, 0x25d04979, 0x34c713f8, 0xc4618187, 0xea7a6e98, 0x7cd16efc, 0x1436876c,
+ 0xf1544107, 0xbedeee14, 0x56e9af27, 0xa04aa441, 0x3cf7c899, 0x92ecbae6, 0xdd67016d, 0x151682eb,
+ 0xa842eedf, 0xfdba60b4, 0xf1907b75, 0x20e3030f, 0x24d8c29e, 0xe139673b, 0xefa63fb8, 0x71873054,
+ 0xb6f2cf3b, 0x9f326442, 0xcb15a4cc, 0xb01a4504, 0xf1e47d8d, 0x844a1be5, 0xbae7dfdc, 0x42cbda70,
+ 0xcd7dae0a, 0x57e85b7a, 0xd53f5af6, 0x20cf4d8c, 0xcea4d428, 0x79d130a4, 0x3486ebfb, 0x33d3cddc,
+ 0x77853b53, 0x37effcb5, 0xc5068778, 0xe580b3e6, 0x4e68b8f4, 0xc5c8b37e, 0x0d809ea2, 0x398feb7c,
+ 0x132a4f94, 0x43b7950e, 0x2fee7d1c, 0x223613bd, 0xdd06caa2, 0x37df932b, 0xc4248289, 0xacf3ebc3,
+ 0x5715f6b7, 0xef3478dd, 0xf267616f, 0xc148cbe4, 0x9052815e, 0x5e410fab, 0xb48a2465, 0x2eda7fa4,
+ 0xe87b40e4, 0xe98ea084, 0x5889e9e1, 0xefd390fc, 0xdd07d35b, 0xdb485694, 0x38d7e5b2, 0x57720101,
+ 0x730edebc, 0x5b643113, 0x94917e4f, 0x503c2fba, 0x646f1282, 0x7523d24a, 0xe0779695, 0xf9c17a8f,
+ 0x7a5b2121, 0xd187b896, 0x29263a4d, 0xba510cdf, 0x81f47c9f, 0xad1163ed, 0xea7b5965, 0x1a00726e,
+ 0x11403092, 0x00da6d77, 0x4a0cdd61, 0xad1f4603, 0x605bdfb0, 0x9eedc364, 0x22ebe6a8, 0xcee7d28a,
+ 0xa0e736a0, 0x5564a6b9, 0x10853209, 0xc7eb8f37, 0x2de705ca, 0x8951570f, 0xdf09822b, 0xbd691a6c,
+ 0xaa12e4f2, 0x87451c0f, 0xe0f6a27a, 0x3ada4819, 0x4cf1764f, 0x0d771c2b, 0x67cdb156, 0x350d8384,
+ 0x5938fa0f, 0x42399ef3, 0x36997b07, 0x0e84093d, 0x4aa93e61, 0x8360d87b, 0x1fa98b0c, 0x1149382c,
+ 0xe97625a5, 0x0614d1b7, 0x0e25244b, 0x0c768347, 0x589e8d82, 0x0d2059d1, 0xa466bb1e, 0xf8da0a82,
+ 0x04f19130, 0xba6e4ec0, 0x99265164, 0x1ee7230d, 0x50b2ad80, 0xeaee6801, 0x8db2a283, 0xea8bf59e,
+ },
+}
diff --git a/local_crypto_patch/contents/cast5/cast5_test.go b/local_crypto_patch/contents/cast5/cast5_test.go
new file mode 100644
index 0000000000..778b272a63
--- /dev/null
+++ b/local_crypto_patch/contents/cast5/cast5_test.go
@@ -0,0 +1,106 @@
+// 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 cast5
+
+import (
+ "bytes"
+ "encoding/hex"
+ "testing"
+)
+
+// This test vector is taken from RFC 2144, App B.1.
+// Since the other two test vectors are for reduced-round variants, we can't
+// use them.
+var basicTests = []struct {
+ key, plainText, cipherText string
+}{
+ {
+ "0123456712345678234567893456789a",
+ "0123456789abcdef",
+ "238b4fe5847e44b2",
+ },
+}
+
+func TestBasic(t *testing.T) {
+ for i, test := range basicTests {
+ key, _ := hex.DecodeString(test.key)
+ plainText, _ := hex.DecodeString(test.plainText)
+ expected, _ := hex.DecodeString(test.cipherText)
+
+ c, err := NewCipher(key)
+ if err != nil {
+ t.Errorf("#%d: failed to create Cipher: %s", i, err)
+ continue
+ }
+ var cipherText [BlockSize]byte
+ c.Encrypt(cipherText[:], plainText)
+ if !bytes.Equal(cipherText[:], expected) {
+ t.Errorf("#%d: got:%x want:%x", i, cipherText, expected)
+ }
+
+ var plainTextAgain [BlockSize]byte
+ c.Decrypt(plainTextAgain[:], cipherText[:])
+ if !bytes.Equal(plainTextAgain[:], plainText) {
+ t.Errorf("#%d: got:%x want:%x", i, plainTextAgain, plainText)
+ }
+ }
+}
+
+// TestFull performs the test specified in RFC 2144, App B.2.
+// However, due to the length of time taken, it's disabled here and a more
+// limited version is included, below.
+func TestFull(t *testing.T) {
+ if testing.Short() {
+ // This is too slow for normal testing
+ return
+ }
+
+ a, b := iterate(1000000)
+
+ const expectedA = "eea9d0a249fd3ba6b3436fb89d6dca92"
+ const expectedB = "b2c95eb00c31ad7180ac05b8e83d696e"
+
+ if hex.EncodeToString(a) != expectedA {
+ t.Errorf("a: got:%x want:%s", a, expectedA)
+ }
+ if hex.EncodeToString(b) != expectedB {
+ t.Errorf("b: got:%x want:%s", b, expectedB)
+ }
+}
+
+func iterate(iterations int) ([]byte, []byte) {
+ const initValueHex = "0123456712345678234567893456789a"
+
+ initValue, _ := hex.DecodeString(initValueHex)
+
+ var a, b [16]byte
+ copy(a[:], initValue)
+ copy(b[:], initValue)
+
+ for i := 0; i < iterations; i++ {
+ c, _ := NewCipher(b[:])
+ c.Encrypt(a[:8], a[:8])
+ c.Encrypt(a[8:], a[8:])
+ c, _ = NewCipher(a[:])
+ c.Encrypt(b[:8], b[:8])
+ c.Encrypt(b[8:], b[8:])
+ }
+
+ return a[:], b[:]
+}
+
+func TestLimited(t *testing.T) {
+ a, b := iterate(1000)
+
+ const expectedA = "23f73b14b02a2ad7dfb9f2c35644798d"
+ const expectedB = "e5bf37eff14c456a40b21ce369370a9f"
+
+ if hex.EncodeToString(a) != expectedA {
+ t.Errorf("a: got:%x want:%s", a, expectedA)
+ }
+ if hex.EncodeToString(b) != expectedB {
+ t.Errorf("b: got:%x want:%s", b, expectedB)
+ }
+}
diff --git a/local_crypto_patch/contents/chacha20/chacha_arm64.go b/local_crypto_patch/contents/chacha20/chacha_arm64.go
new file mode 100644
index 0000000000..661ea132e0
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/chacha20/chacha_arm64.s b/local_crypto_patch/contents/chacha20/chacha_arm64.s
new file mode 100644
index 0000000000..769af387e2
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/chacha20/chacha_generic.go b/local_crypto_patch/contents/chacha20/chacha_generic.go
new file mode 100644
index 0000000000..93eb5ae6de
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/chacha20/chacha_noasm.go b/local_crypto_patch/contents/chacha20/chacha_noasm.go
new file mode 100644
index 0000000000..c709b72847
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/chacha20/chacha_ppc64x.go b/local_crypto_patch/contents/chacha20/chacha_ppc64x.go
new file mode 100644
index 0000000000..bd183d9ba1
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/chacha20/chacha_ppc64x.s b/local_crypto_patch/contents/chacha20/chacha_ppc64x.s
new file mode 100644
index 0000000000..a660b4112f
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/chacha20/chacha_s390x.go b/local_crypto_patch/contents/chacha20/chacha_s390x.go
new file mode 100644
index 0000000000..683ccfd1c3
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/chacha20/chacha_s390x.s b/local_crypto_patch/contents/chacha20/chacha_s390x.s
new file mode 100644
index 0000000000..1eda91a3d4
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/chacha20/chacha_test.go b/local_crypto_patch/contents/chacha20/chacha_test.go
new file mode 100644
index 0000000000..60b11d92f6
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20/chacha_test.go
@@ -0,0 +1,274 @@
+// 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
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "math/rand"
+ "testing"
+)
+
+func _() {
+ // Assert that bufSize is a multiple of blockSize.
+ var b [1]byte
+ _ = b[bufSize%blockSize]
+}
+
+func hexDecode(s string) []byte {
+ ss, err := hex.DecodeString(s)
+ if err != nil {
+ panic(fmt.Sprintf("cannot decode input %#v: %v", s, err))
+ }
+ return ss
+}
+
+// Run the test cases with the input and output in different buffers.
+func TestNoOverlap(t *testing.T) {
+ for _, c := range testVectors {
+ s, _ := NewUnauthenticatedCipher(hexDecode(c.key), hexDecode(c.nonce))
+ input := hexDecode(c.input)
+ output := make([]byte, len(input))
+ s.XORKeyStream(output, input)
+ got := hex.EncodeToString(output)
+ if got != c.output {
+ t.Errorf("length=%v: got %#v, want %#v", len(input), got, c.output)
+ }
+ }
+}
+
+// Run the test cases with the input and output overlapping entirely.
+func TestOverlap(t *testing.T) {
+ for _, c := range testVectors {
+ s, _ := NewUnauthenticatedCipher(hexDecode(c.key), hexDecode(c.nonce))
+ data := hexDecode(c.input)
+ s.XORKeyStream(data, data)
+ got := hex.EncodeToString(data)
+ if got != c.output {
+ t.Errorf("length=%v: got %#v, want %#v", len(data), got, c.output)
+ }
+ }
+}
+
+// Run the test cases with various source and destination offsets.
+func TestUnaligned(t *testing.T) {
+ const max = 8 // max offset (+1) to test
+ for _, c := range testVectors {
+ data := hexDecode(c.input)
+ input := make([]byte, len(data)+max)
+ output := make([]byte, len(data)+max)
+ for i := 0; i < max; i++ { // input offsets
+ for j := 0; j < max; j++ { // output offsets
+ s, _ := NewUnauthenticatedCipher(hexDecode(c.key), hexDecode(c.nonce))
+
+ input := input[i : i+len(data)]
+ output := output[j : j+len(data)]
+
+ copy(input, data)
+ s.XORKeyStream(output, input)
+ got := hex.EncodeToString(output)
+ if got != c.output {
+ t.Errorf("length=%v: got %#v, want %#v", len(data), got, c.output)
+ }
+ }
+ }
+ }
+}
+
+// Run the test cases by calling XORKeyStream multiple times.
+func TestStep(t *testing.T) {
+ // wide range of step sizes to try and hit edge cases
+ steps := [...]int{1, 3, 4, 7, 8, 17, 24, 30, 64, 256}
+ rnd := rand.New(rand.NewSource(123))
+ for _, c := range testVectors {
+ s, _ := NewUnauthenticatedCipher(hexDecode(c.key), hexDecode(c.nonce))
+ input := hexDecode(c.input)
+ output := make([]byte, len(input))
+
+ // step through the buffers
+ i, step := 0, steps[rnd.Intn(len(steps))]
+ for i+step < len(input) {
+ s.XORKeyStream(output[i:i+step], input[i:i+step])
+ if i+step < len(input) && output[i+step] != 0 {
+ t.Errorf("length=%v, i=%v, step=%v: output overwritten", len(input), i, step)
+ }
+ i += step
+ step = steps[rnd.Intn(len(steps))]
+ }
+ // finish the encryption
+ s.XORKeyStream(output[i:], input[i:])
+ // ensure we tolerate a call with an empty input
+ s.XORKeyStream(output[len(output):], input[len(input):])
+
+ got := hex.EncodeToString(output)
+ if got != c.output {
+ t.Errorf("length=%v: got %#v, want %#v", len(input), got, c.output)
+ }
+ }
+}
+
+func TestSetCounter(t *testing.T) {
+ newCipher := func() *Cipher {
+ s, _ := NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
+ return s
+ }
+ s := newCipher()
+ src := bytes.Repeat([]byte("test"), 32) // two 64-byte blocks
+ dst1 := make([]byte, len(src))
+ s.XORKeyStream(dst1, src)
+ // advance counter to 1 and xor second block
+ s = newCipher()
+ s.SetCounter(1)
+ dst2 := make([]byte, len(src))
+ s.XORKeyStream(dst2[64:], src[64:])
+ if !bytes.Equal(dst1[64:], dst2[64:]) {
+ t.Error("failed to produce identical output using SetCounter")
+ }
+
+ // test again with unaligned blocks; SetCounter should reset the buffer
+ s = newCipher()
+ s.XORKeyStream(dst1[:70], src[:70])
+ s = newCipher()
+ s.XORKeyStream([]byte{0}, []byte{0})
+ s.SetCounter(1)
+ s.XORKeyStream(dst2[64:70], src[64:70])
+ if !bytes.Equal(dst1[64:70], dst2[64:70]) {
+ t.Error("SetCounter did not reset buffer")
+ }
+
+ // advancing to a lower counter value should cause a panic
+ panics := func(fn func()) (p bool) {
+ defer func() { p = recover() != nil }()
+ fn()
+ return
+ }
+ if !panics(func() { s.SetCounter(0) }) {
+ t.Error("counter decreasing should trigger a panic")
+ }
+}
+
+func TestLastBlock(t *testing.T) {
+ panics := func(fn func()) (p bool) {
+ defer func() { p = recover() != nil }()
+ fn()
+ return
+ }
+
+ checkLastBlock := func(b []byte) {
+ t.Helper()
+ // Hardcoded result to check all implementations generate the same output.
+ lastBlock := "ace4cd09e294d1912d4ad205d06f95d9c2f2bfcf453e8753f128765b62215f4d" +
+ "92c74f2f626c6a640c0b1284d839ec81f1696281dafc3e684593937023b58b1d"
+ if got := hex.EncodeToString(b); got != lastBlock {
+ t.Errorf("wrong output for the last block, got %q, want %q", got, lastBlock)
+ }
+ }
+
+ // setting the counter to 0xffffffff and crypting multiple blocks should
+ // trigger a panic
+ s, _ := NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
+ s.SetCounter(0xffffffff)
+ blocks := make([]byte, blockSize*2)
+ if !panics(func() { s.XORKeyStream(blocks, blocks) }) {
+ t.Error("crypting multiple blocks should trigger a panic")
+ }
+
+ // setting the counter to 0xffffffff - 1 and crypting two blocks should not
+ // trigger a panic
+ s, _ = NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
+ s.SetCounter(0xffffffff - 1)
+ if panics(func() { s.XORKeyStream(blocks, blocks) }) {
+ t.Error("crypting the last blocks should not trigger a panic")
+ }
+ checkLastBlock(blocks[blockSize:])
+ // once all the keystream is spent, setting the counter should panic
+ if !panics(func() { s.SetCounter(0xffffffff) }) {
+ t.Error("setting the counter after overflow should trigger a panic")
+ }
+ // crypting a subsequent block *should* panic
+ block := make([]byte, blockSize)
+ if !panics(func() { s.XORKeyStream(block, block) }) {
+ t.Error("crypting after overflow should trigger a panic")
+ }
+
+ // if we crypt less than a full block, we should be able to crypt the rest
+ // in a subsequent call without panicking
+ s, _ = NewUnauthenticatedCipher(make([]byte, KeySize), make([]byte, NonceSize))
+ s.SetCounter(0xffffffff)
+ if panics(func() { s.XORKeyStream(block[:7], block[:7]) }) {
+ t.Error("crypting part of the last block should not trigger a panic")
+ }
+ if panics(func() { s.XORKeyStream(block[7:], block[7:]) }) {
+ t.Error("crypting part of the last block should not trigger a panic")
+ }
+ checkLastBlock(block)
+ // as before, a third call should trigger a panic because all keystream is spent
+ if !panics(func() { s.XORKeyStream(block[:1], block[:1]) }) {
+ t.Error("crypting after overflow should trigger a panic")
+ }
+}
+
+func benchmarkChaCha20(b *testing.B, step, count int) {
+ tot := step * count
+ src := make([]byte, tot)
+ dst := make([]byte, tot)
+ key := make([]byte, KeySize)
+ nonce := make([]byte, NonceSize)
+ b.SetBytes(int64(tot))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ c, _ := NewUnauthenticatedCipher(key, nonce)
+ for i := 0; i < tot; i += step {
+ c.XORKeyStream(dst[i:], src[i:i+step])
+ }
+ }
+}
+
+func BenchmarkChaCha20(b *testing.B) {
+ b.Run("64", func(b *testing.B) {
+ benchmarkChaCha20(b, 64, 1)
+ })
+ b.Run("256", func(b *testing.B) {
+ benchmarkChaCha20(b, 256, 1)
+ })
+ b.Run("10x25", func(b *testing.B) {
+ benchmarkChaCha20(b, 10, 25)
+ })
+ b.Run("4096", func(b *testing.B) {
+ benchmarkChaCha20(b, 4096, 1)
+ })
+ b.Run("100x40", func(b *testing.B) {
+ benchmarkChaCha20(b, 100, 40)
+ })
+ b.Run("65536", func(b *testing.B) {
+ benchmarkChaCha20(b, 65536, 1)
+ })
+ b.Run("1000x65", func(b *testing.B) {
+ benchmarkChaCha20(b, 1000, 65)
+ })
+}
+
+func TestHChaCha20(t *testing.T) {
+ // See draft-irtf-cfrg-xchacha-00, Section 2.2.1.
+ key := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}
+ nonce := []byte{0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a,
+ 0x00, 0x00, 0x00, 0x00, 0x31, 0x41, 0x59, 0x27}
+ expected := []byte{0x82, 0x41, 0x3b, 0x42, 0x27, 0xb2, 0x7b, 0xfe,
+ 0xd3, 0x0e, 0x42, 0x50, 0x8a, 0x87, 0x7d, 0x73,
+ 0xa0, 0xf9, 0xe4, 0xd5, 0x8a, 0x74, 0xa8, 0x53,
+ 0xc1, 0x2e, 0xc4, 0x13, 0x26, 0xd3, 0xec, 0xdc,
+ }
+ result, err := HChaCha20(key[:], nonce[:])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(expected, result) {
+ t.Errorf("want %x, got %x", expected, result)
+ }
+}
diff --git a/local_crypto_patch/contents/chacha20/vectors_test.go b/local_crypto_patch/contents/chacha20/vectors_test.go
new file mode 100644
index 0000000000..3d3bbcdc51
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20/vectors_test.go
@@ -0,0 +1,511 @@
+// 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 chacha20
+
+// Test vectors for ChaCha20 implementations.
+
+type testCase struct {
+ nonce string
+ key string
+ input string
+ output string
+}
+
+var testVectors = [...]testCase{
+ {
+ // From libsodium/test/default/xchacha20.c
+ nonce: "c047548266b7c370d33566a2425cbf30d82d1eaf5294109e",
+ key: "9d23bd4149cb979ccf3c5c94dd217e9808cb0e50cd0f67812235eaaf601d6232",
+ input: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ output: "a21209096594de8c5667b1d13ad93f744106d054df210e4782cd396fec692d3515a20bf351eec011a92c367888bc464c32f0807acd6c203a247e0db854148468e9f96bee4cf718d68d5f637cbd5a376457788e6fae90fc31097cfc",
+ },
+ {
+ // From draft-irtf-cfrg-xchacha-01
+ nonce: "404142434445464748494a4b4c4d4e4f5051525354555658",
+ key: "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+ input: "5468652064686f6c65202870726f6e6f756e6365642022646f6c65222920697320616c736f206b6e6f776e2061732074686520417369617469632077696c6420646f672c2072656420646f672c20616e642077686973746c696e6720646f672e2049742069732061626f7574207468652073697a65206f662061204765726d616e20736865706865726420627574206c6f6f6b73206d6f7265206c696b652061206c6f6e672d6c656767656420666f782e205468697320686967686c7920656c757369766520616e6420736b696c6c6564206a756d70657220697320636c6173736966696564207769746820776f6c7665732c20636f796f7465732c206a61636b616c732c20616e6420666f78657320696e20746865207461786f6e6f6d69632066616d696c792043616e696461652e",
+ output: "4559abba4e48c16102e8bb2c05e6947f50a786de162f9b0b7e592a9b53d0d4e98d8d6410d540a1a6375b26d80dace4fab52384c731acbf16a5923c0c48d3575d4d0d2c673b666faa731061277701093a6bf7a158a8864292a41c48e3a9b4c0daece0f8d98d0d7e05b37a307bbb66333164ec9e1b24ea0d6c3ffddcec4f68e7443056193a03c810e11344ca06d8ed8a2bfb1e8d48cfa6bc0eb4e2464b748142407c9f431aee769960e15ba8b96890466ef2457599852385c661f752ce20f9da0c09ab6b19df74e76a95967446f8d0fd415e7bee2a12a114c20eb5292ae7a349ae577820d5520a1f3fb62a17ce6a7e68fa7c79111d8860920bc048ef43fe84486ccb87c25f0ae045f0cce1e7989a9aa220a28bdd4827e751a24a6d5c62d790a66393b93111c1a55dd7421a10184974c7c5",
+ },
+ {
+ nonce: "1733d194b3a2b6063600fe3f",
+ key: "b6a3a89de64abf3aae9cf1a207c768101e80e472925d16ce8f02e761573dac82",
+ input: "",
+ output: "",
+ },
+ {
+ nonce: "ddfa69041ecc3feeb077cf45",
+ key: "2be077349f80bf45faa1f427e81d90dbdc90a1d8d4212c1dacf2bd870000bfdf",
+ input: "23dbad0780",
+ output: "415a3e498d",
+ },
+ {
+ nonce: "496b2a516daff98da5a23653",
+ key: "254c12e973a3d14fbbf7457964f0d5ee5d18c42913cf9a3c67a3944c532c2c7c",
+ input: "f518831fab69c054a6",
+ output: "cfe40f63f81391484b",
+ },
+ {
+ nonce: "9e7a69ca17672f6b209285b7",
+ key: "205082fc0b41a45cd085527d9d1c0a16410b47152a743436faa74ae67ae60bca",
+ input: "805fad1d62951537aeed9859",
+ output: "47bd303f93c3ce04bce44710",
+ },
+ {
+ nonce: "b33dedcd7f0a77359bdeae06",
+ key: "ef2d6344a720e4a58f2ac10f159ab76314e37d3316bf2fb857bc043127927c67",
+ input: "f4e8a7577affb841cf48392cf5df",
+ output: "f445c0fb7e3d5bfdab47090ddee6",
+ },
+ {
+ nonce: "b1508a348092cc4ace14608d",
+ key: "a831bd347ef40828f9198b9d8396f54d48435831454aa734a274e10ddcc7d429",
+ input: "1179b71ec4dc34bd812f742b5a0b27",
+ output: "cc7f80f333c647d6e592e4f7ecc834",
+ },
+ {
+ nonce: "034775c821c58891a6e88cac",
+ key: "ed793ce92b1689ce66836a117f65dcec981dc85b522f5dffbb3e1f172f3f7750",
+ input: "7bd94943d55392d0311c413ac755ce0347872ba3",
+ output: "c43665de15136af232675d9d5dbbeca77f3c542a",
+ },
+ {
+ nonce: "cb55869aa56e9d6e5e70ad5d",
+ key: "b3d542358ffd7b1ff8ab3810ece814729356d0edbd63e66006d5e5e8a223a9ee",
+ input: "1505f669acc5ad9aaa0e993ba8c24e744d13655e1f",
+ output: "26cad1ccf4cf4c49b267ab7be10bc2ffa3ba66bc86",
+ },
+ {
+ nonce: "a42c203f86fc63000ea16072",
+ key: "d6b18ae2473d3be8ca711267ffbc77b97644f6a2b4791d31d0910d180c6e1aec",
+ input: "20070523ddb4ebf0d5f20fd95aacf47fb269ebadda6879638a",
+ output: "5ce972624cb2b7e7c28f5b865ba08c887911b4f5e361830a4b",
+ },
+ {
+ nonce: "ea7186cf2fdf728d8a535a8b",
+ key: "bba26ce4efb56ad06b96e2b02d0cdd549ad81588a9306c421e0f5b0175ae4b25",
+ input: "d10f8050c1186f92e26f351db36490d82ea677498562d8d4f487a0a4058adf",
+ output: "f30c11bc553b2baf6870760d735680897c9fee168f976b2a33ef395fdbd4fc",
+ },
+ {
+ nonce: "3a98bed189a35a0fe1c726fa",
+ key: "a76d5c79dcaab18c9a3542a0272eea95c45382122f59bcaa10e8910371d941f6",
+ input: "e88dc380b7d45a4a762c34f310199587867516fac4a2634022b96a9f862e17714d17",
+ output: "aac98ba3821399e55a5eab5862f7f1bfc63637d700125878c2b17151f306c9aec80e",
+ },
+ {
+ nonce: "b8f4f598731d183f2e57f45b",
+ key: "f78c6fa82b1ab48d5631e0e0598aad3dbad1e4b338fbf6759d7094dbf334dbc3",
+ input: "b0fcf0a731e2902787309697db2384e1cda07b60002c95355a4e261fb601f034b2b3",
+ output: "b6c8c40ddda029a70a21c25f724cc90c43f6edc407055683572a9f5e9690a1d571bb",
+ },
+ {
+ nonce: "18ae8972507ebe7e7691817d",
+ key: "a0076c332ba22e4a462f87a8285e7ba43f5e64be91651c377a23dcd28095c592",
+ input: "cf9ec6fa3f0a67488adb5598a48ed916729a1e416d206f9675dfa9fd6585793f274f363bbca348b3",
+ output: "bb7ed8a199aa329dcd18736ce705804ffae8c3e2ba341ae907f94f4672d57175df25d28e16962fd6",
+ },
+ {
+ nonce: "de8131fd263e198b99c7eb0b",
+ key: "2e4c1a78e255ab2743af4a8101ab0b0ae02ce69dd2032b47e818eedf935b858b",
+ input: "be9a8211d68642310724eda3dd02f63fcc03a101d9564b0ecee6f4ecececcb0099bb26aabee46b1a2c0416b4ac269e",
+ output: "3152f317cf3626e26d02cff9392619ea02e22115b6d43d6dd2e1177c6bb3cb71c4a90c3d13b63c43e03605ec98d9a1",
+ },
+ {
+ nonce: "f62fb0273e6110a5d8228b21",
+ key: "3277fc62f46cf0ced55ef4a44f65962f6e952b9ff472b542869cb55b4f78e435",
+ input: "495343a257250f8970f791f493b89d10edba89806b88aaaeb3b5aefd078ba7b765746164bce653f5e6c096dd8499fb76d97d77",
+ output: "62c01f426581551b5b16e8b1a3a23c86bcdd189ab695dbea4bf811a14741e6ebbb0261ef8ae47778a6be7e0ef11697b891412c",
+ },
+ {
+ nonce: "637ab99d4802f50f56dfb6f2",
+ key: "8f7a652b5d5767fe61d256aa979a1730f1fffcae98b68e9b5637fe1e0c45eab4",
+ input: "e37fbbd3fe37ce5a99d18e5dcb0dafe7adf8b596528708f7d310569ab44c251377f7363a390c653965e0cb8dd217464b3d8f79c1",
+ output: "b07d4c56fb83a49e8d9fc992e1230bb5086fecbd828cdbc7353f61b1a3cec0baf9c5bf67c9da06b49469a999ba3b37916ec125be",
+ },
+ {
+ nonce: "38ecdfc1d303757d663c3e9a",
+ key: "e7d8148613049bdefab4482a44e7bdcb5edc5dad3ed8449117d6d97445db0b23",
+ input: "9efab614388a7d99102bcc901e3623d31fd9dd9d3c3338d086f69c13e7aa8653f9ce76e722e5a6a8cbbbee067a6cb9c59aa9b4b4c518bbed",
+ output: "829d9fe74b7a4b3aeb04580b41d38a156ffbebba5d49ad55d1b0370f25abcd41221304941ad8e0d5095e15fbd839295bf1e7a509a807c005",
+ },
+ {
+ nonce: "1c52e2c7b4995479d76c94c7",
+ key: "74e7fc53bf534b9a3441615f1494c3a3727ca0a81123249399ecae435aeb6d21",
+ input: "03b5d7ab4bd8c9a4f47ec122cbeb595bd1a0d58de3bb3dcc66c4e288f29622d6863e846fdfb27a90740feb03a4761c6017250bc0f129cc65d19680ab9d6970",
+ output: "83db55d9eb441a909268311da67d432c732ad6bda0a0dae710d1bce040b91269deb558a68ced4aa5760ca0b9c5efc84e725f297bdbdadbc368bea4e20261c5",
+ },
+ {
+ nonce: "a1f0411d78773b7ca5ee9169",
+ key: "393e211f141d26562c7cfc15c5ccfe21c58456a9060560266bc0dcdab010c8f2",
+ input: "2f4da518578a2a82c8c855155645838ca431cdf35d9f8562f256746150580ca1c74f79b3e9ae78224573da8b47a4b3cc63fbed8d4e831a6b4d796c124d87c78a66e5",
+ output: "6fc086ded3d1d5566577ccd9971e713c1126ec52d3894f09ab701116c7b5abda959cbb207f4468eb7b6a6b7e1b6d2bc6047f337499d63522f256ee751b91f84f70b6",
+ },
+ {
+ nonce: "2c029f74b0da21a052228c64",
+ key: "b0e7aca1a10e7a56b913af52080ca3cb746d7ae039ca3b5c07acb285c0afb503",
+ input: "55739a1738b4a4028021b21549e2661b050e3d830ad9a56f57bfcaca3e0f72051b9ca92411840061083e5e45124d8425061ab26c632ac7852118411ac80026da946357c630f27225",
+ output: "8051bf98f8f2617e159ba205a9342ab700973dd045e09321805eed89e419f37f3211c5aa82666b9a097270babc26d3bfe0c990fe245ae982a31f23cfbf6156b5c8cfb77f340e2bf5",
+ },
+ {
+ nonce: "a86bc1234ecdd19fcb4e22cb",
+ key: "4a4094b698f1b586ff1bfd10544ea81309e521ab64d74374f1b33109f2876e68",
+ input: "7ffd8d5970fdee613eeae531d1c673fd379d64b0b6bfedd010433b080b561038f7f266fa7e15d7d8e10d23f21b9d7724bb200b0f58b9250483e784f4a6555d09c234e8d1c549ebb76a8e",
+ output: "c173617e36ea20ce04c490803b2098bd4f1ff4b31fdca1c51c6475ade83892c5f12731652d5774631d55ae2938617a5e9462bb6083328a23a4fba52de50ca9075586f2efc22aae56e3a8",
+ },
+ {
+ nonce: "296f5fd61962f7f39e3c039a",
+ key: "c417a0eb1a42e06917239e44118a85293a52c8d0a2c9b0a884cab20a451a01af",
+ input: "7a5766097562361cfaeac5b8a6175e1ceeeda30aec5e354df4302e7700ea48c505da9fdc57874da879480ecfea9c6c8904f330cbac5e27f296b33b667fea483348f031bef761d0b8e318a8132caa7a5943",
+ output: "5e9fbf427c4f0fcf44db3180ea47d923f52bee933a985543622eff70e2b3f5c673be8e05cd7acbcadd8593da454c60d5f19131e61730a73b9c0f87e3921ee5a591a086446b2a0fadd8a4bc7b49a8e83764",
+ },
+ {
+ nonce: "6ee50ec741ec580e616fd9af",
+ key: "bbf22a177cd285904dc4a28cda48a18ab0880c29397418888147d518ce2c3f63",
+ input: "0777c02a2900052d9b79f38387d2c234108a2ad066cbf7df6ea6acc5a3f86b3d6156abb5b18ad4ecf79e171383a1897e64a95ecdbba6aa3f1c7c12fe31283629ff547cb113a826cb348a7c10507cc645fa2eb97b5f22e44d",
+ output: "368c90db3464ba488340b1960e9f75d2c3b5b392bdd5622ff70e85e6d00b1e6a996ba3978ce64f8f2b5a9a90576c8f32b908233e15d2f443cccc98af87745c93c8056603407a3fb37ce0c1f8ab6384cc37c69c98bfecf337",
+ },
+ {
+ nonce: "79da06301d054827dc7cc172",
+ key: "e8b7cd6028e9cbceb97a9be13715d63099c1fba0bf387789a90577dd63175c3e",
+ input: "cf2dccbcfd781c030376f9019d841ca701cb54a1791f50f50bee0c2bf178182603a4712b5916eebd5001595c3f48283f1ba097ce2e7bf94f2b7fa957ce776e14a7a570093be2de386ececbd6525e72c5970c3e7d35974b8f0b831fbc",
+ output: "7c92b8c75e6eb8675229660cedcb10334965a7737cde7336512d9eff846c670d1fa8f8a427ea4f43e66be609466711fd241ccff7d3f049bda3a2394e5aa2108abc80e859611dbd3c7ba2d044a3ececa4980dd65e823dd110fea7a548",
+ },
+ {
+ nonce: "eeb10ffc0ac64c4167bd4441",
+ key: "c6913210b6032b8248b59ad2fe3e8fc86a0536691fa6aa285878dfa01935a2da",
+ input: "e08a8949a1bfd6a8c1186b431b6ad59b106ae5552821db69b66dc03fbc4a2b970dcf9c7da4f5082572bc978f8ee27c554c8884b5a450b36d70453348cd6cac9b80c9900cf98a4088803f564bb1281d24507b2f61ba737c8145c71b50eb0f6dfc",
+ output: "73d043acf9dcd758c7299bd1fd1f4100d61ff77d339e279bfbe6f9233b0d9afa24992a9c1c7a19545d469fdfb369c201322f6fe8c633fcdcffef31032bfb41b9fb55506e301d049fd447d61f974a713debeaed886f486a98efd3d6c3f25fbb30",
+ },
+ {
+ nonce: "570c03c2e1593b1e1ade7e60",
+ key: "b5c2bad18345a9569b478b621ea556308f8fbf693de0f12dd2489d4b79c3f57d",
+ input: "a0c302120111f00c99cff7d839cdf43207a7e2f73d5dd888daa00d84254db0e621a72493480420c9c61ce1cfc54188ff525bb7a0e6c1cd298f598973a1de9fd2d79a21401588775b0adbe261ba4e4f79a894d1bd5835b5924d09ba32ef03cb4bc0bd6eb4ee4274",
+ output: "bc714bd7d8399beedc238f7ddeb0b99d94ad6bf8bf54548a3e4b90a76aa5673c91db6482591e8ff9126e1412bce56d52a4c2d89f22c29858e24482f177abacef428d0ae1779f0ae0778c44f9f02fe474da93c35c615b5fad29eca697978891f426714441317f2b",
+ },
+ {
+ nonce: "1fc84df4e7036ecf966796f4",
+ key: "f4127b0d89473f68b48f82c7a0c60f82eb3112c5d71667e493ef36406cb9af26",
+ input: "ebce290c03c7cb65d053918ba2da0256dc700b337b8c124c43d5da4746888ca78387feea1a3a72c5e249d3d93a1907977dd4009699a15be5da2ca89c60e971c8df5d4553b61b710d92d3453dea595a0e45ae1e093f02ea70608b7b32f9c6aadc661a052f9b14c03ea0117a3192",
+ output: "cbb8c4ec827a1123c1141327c594d4a8b0b4a74b0008115bb9ec4275db3a8e5529a4f145551af29c473764cbaa0794b2d1eb1066f32a07fd39f5f3fe51498c46fba5310ae7c3664571d6a851e673ded3badc25e426f9c6038724779aa6d2d8ec3f54865f7df612e25575635ab5",
+ },
+ {
+ nonce: "1b463e8d60c3057eddafbb3b",
+ key: "c917b9f9f79bf89ac9bbec7d7beae5e755ab4a9bbef6ef90906d9ba11a9bf6b9",
+ input: "275c97de985aa265332065ccce437770b110737a77dea62137a5d6cb62e9cb8b504d34334a58a71aba153d9b86f21377467b2fafaf54829331bf2ce0009acb37842b7a4b5f152aab650a393153f1ed479abc21f7a6fe205b9852ff2f7f3a0e3bfe76ca9770efada4e29e06db0569a99d08648e",
+ output: "b225aa01d5c438d572deaea51ac12c0c694e0f9dc0ed2884a98e5e2943d52bb4692d7d8f12486de12d0559087e8c09e4f2d5b74e350838aa2bd36023032ccbcae56be75c6a17c59583d81a1fd60e305af5053ac89f753c9347f3040e48405232dc8428c49dcb3d9b899145f5b3bc955f34dbbe",
+ },
+ {
+ nonce: "f5331f87bae3fee4931e8ccb",
+ key: "03491233e587027e8f98d6e95f406219b5c1215fe695c62ac900b246ba98da9f",
+ input: "ceda15cfffd53ccebe31b5886facd863f6166e02ec65f46f54148860a5c2702e34fd204d881af6055952690cd1ffa8ba4d0e297cc165d981b371932adb935398c987baff335108c5e77f2e5dd5e1ca9a017bc376cbdbe3c0f45e079c212e8986b438444e79cd37927c1479f45c9e75b0076cc9f8679011",
+ output: "a3f1c3f885583b999c85cd118e2ababfa5a2de0c8eb28aacc161b1efee89d8de36ddeb584174c0e92011b8d667cb64009049976082072e6262933dbf7b14839805e1face375b7cbb54f9828ba1ed8aa55634ec5d72b6351feff4d77a3a22b34203b02e096f5e5f9ae9ad6a9dd16c57ce6d94dcc8873d18",
+ },
+ {
+ nonce: "e83c55efea20e1df3a7e049a",
+ key: "c179f4be8b4f555989f097bf1e6f315228e4410104dc26ff578f0ce1598a56a7",
+ input: "799bb2d634406753416b3a2b67513293a0b3496ef5b2d019758dedaaac2edd72502fc4a375b3f0d4237bc16b0e3d47e7ddc315c6aef3a23fcae2eb3a6083bc7ac4fd1b5bf0025cc1cb266b40234b77db762c747d3a7b27956cf3a4cf72320fb60c0d0713fa60b37a6cb5b21a599e79d0f06a5b7201aeb5d2",
+ output: "e84dfb3dbaac364085497aeabd197db852d3140c0c07f5f10e5c144c1fe26a50a9877649e88c6fe04283f4b7590a8d0d042ef577693f76f706e31c4979437590fe0ab03d89afb089d1be50ae173ea5458810372838eceac53bf4bac792735d8149e548efb432e236da92bf3168bbcf36f644c23efb478a4e",
+ },
+ {
+ nonce: "a02481d9aa80cd78fc5cc53d",
+ key: "416e2802e3383ef16b4735f7fc4bc433978797d795459c4a13040806d8fd9912",
+ input: "b2d060bd173955f44ee01b8bfcf0a6fad017c3517e4e8c8da728379f6d54471c955615e2b1effe4ce3d0139df225223c361be1cac416ade10a749c5da324563696dae8272577e44e8588cd5306bff0bfbdb32af3ac7cbc78be24b51baf4d5e47cf8f1d6b0a63ed9359da45c3e7297b2314028848f5816feab885e2",
+ output: "ffa4aa66dd5d39694ae64696bfa96f771accef68f195456ad815751e25c47ed4f27b436f1b3e3fcaa3e0d04133b53559c100cd633ced3d4321fc56225c85d2443727bce40434455aa4c1f3e6768c0fe58ad88b3a928313d41a7629f1ce874d2c8bcf822ebdaebfd9d95a31bb62daab5385eb8eefe026e8cbf1ff7a",
+ },
+ {
+ nonce: "0f6b105381fd11dff3b6d169",
+ key: "38b1360794e1cd55c173820fe668c2f7d52b366155b43cbbfcc9d344fdd3567d",
+ input: "4f0171d7309493a349530940feece3c6200693f9cff38924114d53f723d090fffa3c80731b5ca989d3e924d1fa14266632cb9ab879e1a36df22dc9f8d1dadea229db72fded0c42187c38b9fa263c20e5fb5b4aa80eb90e8616e36d9b8c613b371c402343823184ecad3532058a46cf9e7ea5a9ecad043ac3028cbcc3f36d32",
+ output: "88c773ff34b23e691e14018ba1b2bd48a4a6979b377eb0d68336ce6192dcd5177e6b4f1c4bea2df90af56b35fe2a1d6279d253c0194dcbca9bf136f92d69165b216e4c9d1ce6b3fbe40c71e32c3f4088de352732d0e2bad9c16fd0bb9bde3d6c30257ce063432d09f19da79d49aa7641124a6c9e3f09449e911edbae11a053",
+ },
+ {
+ nonce: "bdff905e73f198a8889a9f26",
+ key: "5fe044529bbeadf9ac549f9e46004623eacd8267962c98ba6b5021c7e3f710ed",
+ input: "8f8d9e18d3212bd20b96d75c06d1a63622fd83d13f79d542e45996135368772ea81511302a0d87e246dd346314cfe019bae8a5c97f567f12d82aca98dfea397c6a47dd0c419f1c609d9c52dcfcbe7eee68b2635954206ed592b7081442ce9ce3187d10ccd41cc856fb924b011f817c676c9419f52a2938c7af5f76755a75eb065411",
+ output: "4e130c5df384b9c3c84aa38a744260735e93783da0337ade99f777e692c5ea276ac4cc65880b4ae9c3b96888760fdddb74bc2e2694bedf1ee6f14619c8015f951ba81b274b466e318d09defe80bdbed57bc213ac4631d2eb14c8e348181d60f6295ceee1e9231ae047830ef4778ff66146621b76974773b5d11c8e17a476450f46ef",
+ },
+ {
+ nonce: "e8398e304ff1a49a96db11f5",
+ key: "19523b8388e5824b2c652d4bd76e8f7c63e84bfee5503a9d9b0988b868198d9f",
+ input: "30d2379dd3ceae612182576f9acf6de505ab5a9445fe1a86ae75c5c29429e11c50fd9ec657b29b173a3763b1e171b5a7da1803ba5d64fccb2d32cb7788be194dbca00c3c91774c4c4c8ede48c1027d7cc8b387101a4fe5e44a1d9693b2f627626025072806083aadbced91c9711a0171f52ffb8ed5596cf34130022398c8a1da99c7",
+ output: "b1e8da34ad0189038ee24673979b405ef73fdbdd6f376f800031d64005a4ebed51a37f2180571223848decbea6dd22b198ab9560d7edc047c5d69183dc69b5fca346911d25cb2a1a9f830dc6382ad0024e8c3eef3aa2d155abcfe43bff01956a5e20a862fbed5c5e8df8eed0601a120caac634b068314e221f175baa11ae29002bb9",
+ },
+ {
+ nonce: "5acafea5b4c13a750966a4c5",
+ key: "5948bfabf69b8d826daaf7f7a28c2025adc0a4d78232dd2fc2b8fc2b4bd88983",
+ input: "d9404ccdcc8ef128a1b1ace4f9f1669d274ec82aa914cac34b83ac00b236478fd6167e96ec658850c6c139eb0f6fc0dd7191ba9a39828032008f7f37eb9a8df9d6cdd54240e600efe7fc49a674000c5030d825b2c5c96d0f19b8ecdbf4eeb86d3e569c5e3131abc7d6359dd4255284ccacf150d42e7a899536d51ee6db329654a4581c5ac6e419",
+ output: "c5534b5fb40b4834300e9577a9d87440c5272263d06e6aee84aa92cdf5d1b033145d336f26e5fe55c09a7e75753af93d0786dfc1cb435e86c67bd3ec8e766d0801b99e68691e2c3c3ffec539cf62e68285ea9027daa2716cd6f97e8eb7b9e266357a25eb2d4839a829508a6b7228f2832b3cd998f77597ae530430e6e4ecb53eb9efe456863a04",
+ },
+ {
+ nonce: "4658aa126c4f608885950dc1",
+ key: "d6cc91149d552f60060c08d4bdfa0202f8f4d3ff174c14bf3c3fbf8875330808",
+ input: "231765f832927461f338aceb0f4cf51fd8469348c69c549c1dec7333d4aa4968c1ed58b65ab3fe3d0562600a2b076d56fd9ef91f589752e0455dd1d2e614cacfc0d757a11a4a2264bd38f23d3cca108632201b4f6c3b06477467726dde0c2f3aee01d66d788247663f1d0e66b044da9393ede27b9905b44115b067914961bdade85a2eca2844e1",
+ output: "1dd35f3f774f66d88cb7c2b23820ee078a093d0d85f86c4f103d869f93e2dbdd8a7cb8f101084fe1d7281a71754ec9aac5eb4fca8c365b24ed80e695caace1a8781a5a225938b50b8be96d0499752fdabd4f50d0b6ce396c6e2ca45308d1f2cc5a2a2361a8ca7a334e6ee62d466d74a1b0bf5b352f4ef6d8f8c589b733748bd3d7cda593243fab",
+ },
+ {
+ nonce: "f0709d1c67a388a02b4dc24e",
+ key: "75974e4952a8070d4af28aaf5c825bc68067e0c5cebafb17b5711d65efd848f5",
+ input: "e46841f12d98aeb7710b9162d342895a971b0e3a499886bbb6aa74dc744a28d89a54542b628acdc2f693cb7c03f73fc3b74069bc3f2d000a145fb8a806cdc7d6fa971da09a33b92851cc3d1f6f5646d7fa2b1d564876feefeb63b6e66dba1c0b86ca345235bb822e0f93132346840d2a3d6eb1b541178ea51affc7b31f8da02732cc4e5bcb5d8683ae0a91c9",
+ output: "1dcbfd0bb2b905656c52bd7b1bcdad9b4d434ae9ac221a0d3a316115cdd4a463fa9b3444d2612a4e277d0dcd881fa6e80e59e5a54e35e1a14747aed31edf4ac24214f9d9c329ebe2157620b64efaded9976549bc4aa100d5c15be3f85f700f8a21dfe77590dfee2de9a23cc1ed1e44f32ebf68ca289b097bc13b42802dc7c75309c4afc25b5741839f7db3d5",
+ },
+ {
+ nonce: "8b7b06236d6c275b606ccaae",
+ key: "8844d62973293a89efb4e332d4d5f32a1bc05e094cb605c87d8b4e8862708d79",
+ input: "e98e4a9550bdd29e4106f0cc8669dcc646a69438408e9a72c7cdb9b9d437b5f7a13fcb197629541c55bca1f8972a80cd1c1f591a0e24f977cdeb84763eab2648e42286e6473ea95e3a6a43b07a32b6a6cd80fe007ba0cf7f5ac7e651431f5e72690ec52a7134f9757daf0d8eff6b831a229db4ab8288f6bbf81e16fedebe621fd1737c8792cfd15fb3040f4f6a4cbc1e",
+ output: "5c69cf522c058790a3bc38979e172b60e71f7896d362d754edc1668d4f388b3fc0acdf40786d2f34886e107a142b1e724b9b9b171cb0e38fd78b35f8ac5269d74296c39c9f8628d848f57af9d8525a33f19021db2b9c64ba113171ebb3882075019ec7e77b51ce80b063ed41d48dad481d9536c030002a75d15c1c10ce0ec3ff17bc483f8416055a99b53035f4b6ea60",
+ },
+ {
+ nonce: "5896072b85daf5bd0d45758a",
+ key: "a3eac94919880462a5cfaa9bdcad7038e182c605f3fff9f44b8e84a3c1ebc10a",
+ input: "ce0f0d900dd0d31749d08631ec59f216a1391f66a73bae81d3b0e2919a461bc9a14d6a01b827e3bcb55bbccf27c1ed574157e6becd5cf47181a73c9d3e865ab48a20551027e560e965876b0e1a256bfa5cb5179bf54bd8ec65e5570e374b853b37bf4b3ef1ec612d288ebc19275fa88da9419e012f957f9b6a7e375b3377db0eb3619c731aebfeb0930772b4020d3a3e90723e72",
+ output: "b06981b57fe184091ef9f8ccf522a5bcdb59bf9a68a3ddb817fdd999a6ecf81053a602141cf1b17017bae592b6b6e64756631a2b29a9e1b4f877c8b2ae30f71bc921e4f34b6f9cd8e587c57a30245f80e95005d0f18f5114400785140e6743da352d921fb4a74632a9c40115ad7706263ac9b41a11609fa0c42fc00f8d60931976162598df63ebad9496dd8943d25a03fa47475c",
+ },
+ {
+ nonce: "b88a8e097be7d884415830bb",
+ key: "3cf9b5468d77b2c88f27c52c4c90a3d24f5dce06e859440c305ca30402dcea2f",
+ input: "eccfd66bdc691478f354b8423d6a3f20932a1f591d8e6cefa734975fb8ee6881b6dc92c0d1d5ed54fd1999efd7f11ac697a1f130587dd895eb498c9a8fc7d1714c385ec156ecae3bdea2a3462834245e724531d0fedda2b77693a53ed7354b758e875b23cfc83219a091fb2076e7a88cd77f779ed96f8d81ffa3fe5059303ac706086494b9f2982f4f88a0c6fadc3748625004db",
+ output: "925529047d4177b72bf50905ba77e47608815522c1829b24046e439d5451901257903a5409fb910373167e8b7f4fdfa543a477608ddfc11bbd1efc138366961463b9915b302a346b795dd593f6fcf4fa73529b6fe83079552aabbe99474a72806f59688d826675fa7f6649b9f5307e5028853c9821b8c4a1a0fc4bfdc7c8c78b25aeaba2b5821d17b36317381a3bd578917d2504",
+ },
+ {
+ nonce: "4a6e2a2e8a486d9ab66c96f9",
+ key: "ff3b90583f17bec2b52861e253afb6b6eb8e2f093633cf38fb90df7f374be27a",
+ input: "f0c7139c69413869bca980d7f192b2bc3f57e34ca4f26164e1a54a234e84e1aa285cc02cfbaef3dfba2dbb52a555ec1f6ef0e89d0b2f0bd1846e65b74444b5f003a7308965e67bed558689be2668ca10ca368fac072e0e4535a031af23b3c37c561e185872b86c9bceddb5c1199e43fb5f735384766d33710460b541b52d3f5b6c108c08e76724bcac7ad2d866a8bbeeea92a3d867660d2e",
+ output: "d2c16c7a242b493038203daec65960de384c030eb698ef6a53c36eabb7556cbfa4770eaa8bc0a2b385ad97495eeb1c03ff4e6efcb804aefa81c177dc62700a9eefe6e8dd10cff5d43a2f47463cab5eb1ee260c3826cac9bfa070f1e0435541a89ebd224d13cc43f8fff12f38091c2b3f2102d5c20d8b1c3ae4f129364bbe9f9ce2147dcf0639668ddb90dffe6a50f939f53fa7ba358e913f",
+ },
+ {
+ nonce: "98013e248c44840860e7319a",
+ key: "bc17e037902e1e9baa9d6715ee234af9fe6da80d4ca8eec3999719dd92fbef6e",
+ input: "7024974ebf3f66e25631c0699bcc057be0af06bc60d81a7131acaa620a998e15f385c4eaf51ff1e0a81ae5c6a7442d28a3cdc8aeb9701055e75d39ecac35f1e0ac9f9affb6f9197c0066bf39338a2286316e9d1bb7464398e411da1507c470d64f88d11d86d09e6958fa856583ace697f4ee4edc82618662cb3c5380cb4ce7f01c770aab3467d6367c409a83e447c36768a92fc78f9cbe5698c11e",
+ output: "ff56a3a6e3867588c753260b320c301ce80de8c406545fdd69025abc21ce7430cba6b4f4a08ad3d95dc09be50e67beeff20d1983a98b9cb544b91165f9a0a5b803a66c4e21bd3a10b463b7c1f565e66064f7019362290c77238d72b0ea1e264c0939d76799843439b9f09e220982eb1dc075d449412f838709428a6b8975db25163c58f40bf320514abf7a685150d37a98bac8b34ccb5245edb551",
+ },
+ {
+ nonce: "6d864ed2d8259dc5f123f6fc",
+ key: "a0cc325fc5ca6741ee4349c0eca17f50c0df8fad2dfa66624153f0223e147480",
+ input: "8d79329cf647e966fde65a57fc959223c745801c55312046b791671773cca0af4cd48ead1f316eba0da44aa5d18025eced0c9ed97abaabb24570d89b5b00c179dca15dbae89c0b12bb9e67028e3ae4d6065041b76e508706bec36517a135554d8e6ef7cf3b613cbf894bec65d4dc4e8cb5ca8734ad397238e1e5f528fa11181a57dc71cc3d8c29f3aba45f746b1e8c7faace119c9ba23a05fffd9022c6c85260",
+ output: "60aea840869f7be6fcc5584b87f43d7ba91ed2d246a8f0a58e82c5153772a9561bdf08e31a0a974f8a057b04a238feb014403cd5ffe9cf231db292199198271f9793c9202387f0835a1e1dc24f85dd86cb34608923783fd38226244a2dd745071b27d49cbffebea80d9dacad1578c09852406aa15250de58d6d09cf50c3fcfff3313fac92c8dad5cb0a61ccc02c91cecee3f628e30c666698edecf81831e55ec",
+ },
+ {
+ nonce: "4710b63001f90c812415684d",
+ key: "d07614e58d0098df9ee6df596691b30d4a4a1e6c5e1676fb85f1805135fb5973",
+ input: "85484293a843d2d80b72924b7972dfa97cbe5b8c6bcc096f4d5b38956eb3f13f47b02b0f759ea37014ecdecfb55f2707ef6d7e81fd4973c92b0043eac160aaf90a4f32b83067b708a08b48db7c5900d87e4f2f62b932cf0981de72b4feea50a5eb00e39429c374698cbe5b86cf3e1fc313a6156a1559f73c5bac146ceaaaf3ccf81917c3fdd0b639d57cf19ab5bc98295fff3c779242f8be486ba348bd757ba920ca6579be2156",
+ output: "bb1650260ef2e86d96d39170f355411b6561082dcc763df0e018fdea8f10e9dc48489fb7a075f7f84260aecc10abcfadbc6e1cd26924b25dedb1cc887ada49bb4e3e02006bdd39098ef404c1c320fb3b294ded3e82b3920c8798727badfb0d63853138c29cf1ebf1759423a1457b3d2c252acf0d1cde8165f01c0b2266297e688ff03756d1b06cb79a2cc3ba649d161b8d9ef1f8fb792bd823c4eabb7fb799393f4106ab324d98",
+ },
+ {
+ nonce: "be0c024290af62adcd539e02",
+ key: "9520adab77c41e60a123c93b1adede1e5523613358485b281467fdd3c3bcf4e0",
+ input: "a2fc6e1b5281a4e0330eecd1ab4c41670570423173255979953142b78733b2910fa5540e8294208df6ae4f18672d5ac65acf851bcd394e1932db13c81b21e6f165e5538aff862e46126c650bbe055e54b31c78f2f0221d2631d66ef6d3f4c5ae25eada043b74d8770e2c29799c0954d8ccbd17766b79e6e94e88f478db3566a20cb890846917591a07738328d5c05f7ed4695a82607660f1239661faa9af0368aeb89726f13c2aaecf0deaf7",
+ output: "d8fe402a641c388522842385de98be60f87d922c318215947d4b7562d4ca1e2dbc7ee86494e65fb0bfddfdebdb2ae6469312f95b32c722b2720d64bb8d7cc3dd82f9055b1d89f05b77984f91f94ba4ac79c5129cd7c91cc751b0defc3f2799518e372d27aa683f1e7bbd4f55414c48fe8a3a37ac1f179a1a329cda775aec0d31d75a5a38addb1de67c06bddbedf4c8d87abc18c9f9dd072d457ea29ad4dfb109ce7e99a4a82fbe330b0afbb5",
+ },
+ {
+ nonce: "8f1c02a8c4027a6693b6687a",
+ key: "c801e4ec475a80faca2f576d0c781c9ce5457564114cefd7461edc914e692aba",
+ input: "480387bc6d2bbc9e4ced2448d9ec39a4f27abe8cfb46752d773552ad7808a794058962b49e005fef4e403e6a391d1d3f59025eeb5fb8fbbe920f5361862c205d430eac613cd66108f2f2f0bd4d95a8f6ca7bd1f917eaeb388be87d8b7084a2eb98c575034578edf1b3dafff051a59313873a7be78908599e7e1c442d883d3fd3d26787eb7467eed3a3fb2d40046a4460d5d14215565606bcf8b6270af8500e3504d6d27dacf45bace32214472d525fdc",
+ output: "ab81a9c28358dfe12e35a21e96f5f4190afb59214f3cf310c092ab273c63cd73a783d080c7d4db2faccd70d1180b954cd700c0a56b086691e2c2cd735c88e765e2266cd9ebe1830d63df4b34e2611a8abeeca9c8c4fac71135dafb1cb3569540ed1362ddeb744ed62f6fd21de87b836ec2980f165c02506e0c316ae3cf3d18a862954d9781f726ecc1723af4a730ccc6d6de82553450a52499acb58fb2008969401c45b2f20e12b58f308db1d199b4ff",
+ },
+ {
+ nonce: "7c684e41c269fcc6d312cad3",
+ key: "ca1cb501af5584bc4248903f52b4426064d14dcdf0f383da72b904ff0edd72f9",
+ input: "b274e61059f3215173ae226e30a92ee4b4f8a3da95f2e768e3fac2e54ddac92c200c525f190403a6ef9d13c0661c6a7e52ed14c73b821c9680f1f29711f28a6f3163cf762742ed9474dbea51ff94503a5a404adbbdfbf4c6041e57cb14ea90945dc6cb095a52a1c57c69c5f62ac1a91cd8784b925666335bbfee331820b5f7470bc566f8bbb303366aafe75d77c4df5de2649ed55b2e5e514c3cb9f632b567594a0cf02ec6089a950dbe00554ee4dfb9",
+ output: "a0969730d48ee881792a3927b2f5d279aba9f2ed01e6b31b92d0e1fb8ba7f35a236d838e0ce5f8654957167de864f324c870864b4e7450a6050cd4950aa35e5a1a34a595e88dd6f6396300aff285de369691b6e0e894106dc5b31525e4539c1e56df3ceedbbab1e85da8c0914e816270a4bae3af294b04a3ea6e9ef7e2aab4da5f5370df2706b5e3f000d88179ac756deaa652a1cc85e80ad9622f1bf91a2776262eb7289846d44f7f8192e763cb37aa",
+ },
+ {
+ nonce: "1d5c31dd98da35230fdaa0e0",
+ key: "d6c71964420f340db8f4f27a42cf3635fbc6682f5f859dac90d4c407b1b11197",
+ input: "ee849039c6cd972dc943d2a4468844d130c0150276f4e0889047e2300c3ecc6792c4527bfe9437dad877eb986e6b1aa9b867d1798c9d314243f0a87ec9ee5b601c2554876c87cbf50df3334a077c4152f8b8fef4a2d301ddbfa90c887ece757c3eb6c4fc1e0212d6b5a8bb038acaec28cba064c9b34f5364cb7f0fc2ac4ef2c7ddde0f5ba17014459eaa78f08a46a01882ebf7c6e409dadda250bb899dc8b3b70e160bbcb4412a9963b174d0fc6bc16383a46ffaacb6e0",
+ output: "3e272ded9c0a5cebe7cf17ac03f69eb20f62996e047501b6cc3c8691ddb2780ea72c21a81888bfea96e4373a412c55ca95648390de740102d661143043baec3976230e024477d134b8504a223c36a215b34164c9e9e1fa99a49fdc56f2f04ea525a6b82997d9bbc95c4b5baeab4dec50061efb7c1a757887acb8b47b142e0a2e61885a2c14c4642d83d718a0546b90699adc545a48129603862a1c89d8e665cde54b3ba487754db6d6f5acf6a4b95693cc569577a2dc48",
+ },
+ {
+ nonce: "7c4fb4ebddc714af7acd4345",
+ key: "7719e70c860e7999dcd61065e78a96379afb17295ff29e0185d082d243d02861",
+ input: "0992396a6f29b861dd0bc256e1d1b7dce88435733506a6aa20c62e43afa542d1c46e28b2e6d8e2eacb7c08db05e356fe404684b0e3a9849596db82eb788aa09258c28eb19e9838f757425b4edef12deeca56e30cf030272e325d4246d6e083219b2f965124963ca91f066d47bf5a8282a011a78b0155aa70038259a4a59135f241fd2f88c908b9f4eef7b7df0f3a1c16a52c009b522f89dabd52601bbf6e3ce68732e1a6d444469480f06da218786cf6c9666362e7a7f7be12",
+ output: "545c05a84b5a4fffd1dd623c8f2b11443818560bdb0c26dadd3b694d4790d294b99059f4127b7cca122c4000954d745af96094ff4623f60db33e994bb6903263d775f48d7047427b3a498c2ecde65bd37bcb8ee7e240a1e08c884c0079cab518f4e1c38ba5ea547f4da83b7c6036e4259bee91c42e8fae895df07781cc166f1d50e1550a88ee0244bb2950070714dd80a891aa8a9f0580a67a35cb44609b82a5cc7235f16deea2c4f3667f2c2b33e8eeef944e1abdc25e48fa",
+ },
+ {
+ nonce: "9071cb35869a2e21e43c42bc",
+ key: "dece19faf2e86a57ab5d05585d35b3911a50d269c223637385136c2657454f13",
+ input: "3b9efcbbb607fad5e9f1263dad014cc5c2617d439fcd980408f4f9a93acb1a33d1c3a22f38c037e4603dfbbfb5571bc08c4a1958cbbf510e3e4dd19007fe15fad7808369149a9c4db7ca0496f7a600a6f2454ee1cffd5a68d45c270e4b53ac9b77f33a1ffbb1804244f57d2b05b8036fe2cda9efead3d4eff074ea5c07128e0b354a4a11ffa179163933bc6bd10d200804cc93b64575746e94e975f990bddcc8a4335e99e2459fbe9bc0e004ffcd6cac52f48ef55cc0637a75c1dc",
+ output: "631ba7301e33236da2477506ea98d3b732447389e849b68e1f09bd5fd814f40dc3247a1012fa654f08e3dda0c104ee2dff12ecf5cb018644de50d70dfb6c8cc1f5f552e5f1e50466bbb538ad6b98fd37f33fe615c326efc9cc97899b829b007f91569fa9b28ce0076c53daedf9cc0f838e22cf1125b86a6a2c2eb4a45dadea45ad00fb4f054e7d6b09c13ab1dd5328debfbf4f1b70af2b8a5b1d02df8a87d7661473e0c180ba4c815f14db87c5bdc15f11a29d8e0ce3d747d5dcd4",
+ },
+ {
+ nonce: "ac41c9cc025ba4dbd67a0dab",
+ key: "5207759b0a0927a6f0957c963f2cfff87eb9be69c1990ba383dcdd0aaf9b3f44",
+ input: "f28a71efd95e963e5e0bc0fcf04d8768ce93cb55dc73c32e6496022e214596314b7f843f5c7b136a371c2776a0bfbdd534dccbe7f55e9d3d3b5e938f2d7e74393e4caf6c38fa4b05c948e31dc6a9126817fa3d7892c478f75ab9f6ab85c0e12091bd06e89c7d3ca8d9dcdd4c21fead3d769a253919c2c72dd068474ea322b7e71cafa31684e05a63e179e6432fb70661792cc626a5060cec9e506b35d9286f15dc53cc220b1826314eec337dd8e7af688e5950b2316c30516620569ea65aab",
+ output: "1bcea54b1bf4e6e17f87e0d16388abe49b988b9c785b31f67f49f2ca4011ecd2ad5283d52ef707dd3b803e73a17663b5bfa9027710e045a0da4237f77a725cf92792b178575456de731b2971718937dd0e9ea12558c3fa06e80bbf769e9799f7470db5b91476d6175f1a6d8e974fd505854c1230b252bb892a318e6d0c24dcc9ecb4861769cd746abab58805bc41c6086a6d22b951fba57b00c5b78f6dcb2831715b9d4d788b11c06086f1d6e6279cd130bc752218d7836abc77d255a9e7a1",
+ },
+ {
+ nonce: "587c7e98949a83cc602e9530",
+ key: "6f284ae396d9dc4a12871697e8dd820ae54942010b81825ee845a4b4b0ad7995",
+ input: "c1d1ede73bd89b7c3d4ea43b7d49c065a99f789c57452670d1f92f04f2e26f4f5325c825f545016c854f2db2b3448f3dc00afe37c547d0740223515de57fd7a0861b00acfb39931dc9b1681035d69702183e4b9c6559fb8196acbf80b45e8cc5348b638c6d12cea11f6ef3cc370073c5467d0e077d2fb75e6bf89cea9e93e5cf9612862219ca743ef1696783140d833cd2147d8821a33310e3a49360cb26e393b3fee6dba08fcda38d1b7e2310ec1f715e3d8fa0c6b5e291eea07c25afd5c82759a834a89cc5",
+ output: "11a8493cdc495c179f0d29c2b4672997205a9080f596ee3c80d79b55162b1c875ac18eb94bf2a9e05b08024f524a1e9665912394a330c593d23260e6bdf87620c10a48f678693196fb744c49054182fba667c601e7b7ebf0f068e8d69ba004b804fda616a4a0d5350e1a3bd424b8266462be282308219c578569aefc1ccd09ecdf5da283356c9e524e14e69d25b0e19643dab26f54373a7272b43755c3f1ddaee6c5fb9e8e093110c41697e95f73a68c75454e050239197c9fbd8cec76698bd11894ebf6e2b2",
+ },
+ {
+ nonce: "5a021f8500c8f3e63075ae85",
+ key: "47be0d2d65e405dab2b3f6429ee726708066449e76f91d69a23db2f7fa2134bb",
+ input: "37b2dc4b6a5203d3a753d2aeffcdaed5a7c1741ed04d755dd6325902128f63b6981f93c8cc540f678987f0ddb13aae6965abb975a565f0769528e2bc8c6c19d66b8934f2a39f1234f5a5e16f8f0e47789cd3042ca24d7e1d4ddb9f69d6a96e4fd648673a3a7e988a0730229512382caaded327b6bbbbd00a35df681aca21b186bc7ac3356d50889bbf891839a22bb85db4c00bfa43717b26699c485892eb5e16d1034b08d3afa61f3b5f798af502bba33d7281f2f1942b18fb733ca983244e57963615a43b64184f00a5e220",
+ output: "b68c7a2a1c8d8c8a03fc33495199c432726b9a1500bc5b0f8034ce32c3e3a78c42c1078e087665bd93c72a41df6bfa4e5beb63e3d3226aeeba686128229a584fab0c8c074a65cef417ad06ab1565675a41cf06bb0fb38f51204eccccb75edd724cdd16b1d65a272f939c01508f0385ca55ac68a0e145806317cc12e6848b1124943a6b2d99a8c92083fc5f31ab2e7354db3f8f2d783dbf1cfec9c54f8bfcb93d6f28ef66f18f19b0fab8836458e9b09bee742ba936cb2b747dd9dcf97ca7f6c82bf0af6f1b433592d65143fe",
+ },
+ {
+ nonce: "7fd9bfae2d4483f51f2fab15",
+ key: "9bcfd1d3e68731e457a77150b4832a416f71273f88c4fd17ed771f2756b04b6c",
+ input: "68c2c5612912b5f994172720130dff092ee85a2c1395111efa64d5a281ca864d3db9600e685854d81c6de7e8747b92fb7c4c2efa829d3d4c0c9fc9d689e2e5c84c9eae8ba4ab536fb6c7523124b9e9f2997f0b36e05fb16163d6952eee066dd22fb7585925ffded0204cc76818bcead0d1f8095ca2cf9cd1ddcd0361b9c9451940e14332dac4e870e8b2af57f8c55996447e2a8c9d548255fe3ed6c08aedaf05bb599743ecb0df8655152bbb162a52e3f21bea51cb8bf29f6df8525eb1aa9f2dd73cd3d99f4cca31f90c05316a146aab2b5b",
+ output: "d0ae327fa3c4d6270a2750b1125145bdeef8ab5d0a11662c25372e56f368c82c6f5fc99115a06a5968f22ffe1e4c3034c231614dd6304e6853090c5940b4d1f7905ef4588356d16d903199186167fec57e3d5ce72c900fe1330a389200ed61eec0bdc3672554f1588ec342961bf4be874139b95df66431178d1d10b178e11fcbd26963ff589d5d5faf301b7774a56bbfa836112a6ea9c3026ebdd051085f9131132c2700674bef6e6c2c5b96aace94eb2ba6c0e0aef0eefa88995e742ca51ac50af83683b801b7c2c5af4880e2b344cc5564",
+ },
+ {
+ nonce: "b873e9f9a7a68524e6dea72e",
+ key: "11efed96267ff58c3ca8e3b6c634f49e48ea07464d7ee8ac7574d8a058949c3a",
+ input: "fed3d1efa309c8b50cb9da02b95167f3b77c76e0f213490a404f049270a9c105158160357b7922e6be78bc014053360534add61c2052265d9d1985022af6c2327cf2d565e9cef25a13202577948c01edc22337dc4c45defe6adbfb36385b2766e4fa7e9059b23754b1bad52e42fce76c87782918c5911f57a9394a565620d4b2d46716aa6b2ba73e9c4001298c77bfdca6e9f7df8c20807fa71278bd11d6c318ed323584978ad345c9d383b9186db3bd9cec6d128f43ff89998f315dd07fa56e2230c89d803c1c000a1b749107a3159a54398dac37487d9a",
+ output: "6a95fba06be8147a269599bccda0ce8f5c693398a83738512e972808ec2f25bc72402d4bcd1bc808cc7772b6e863b0e49d1d70c58fcf4fcaa442215eeb3a4648ade085177b4e7a0b0e2198f0acf5465c97bd63f93781db3f0b9a0a184c3e06a76c4793a13923f83b2242b62511c2edff00b5304584cbe317c538de23785d2504fae8faabee81c5315298186ce3dcbf63370d1ccd9efec718cbc90b3d2e0b0b6aefb3a9b31e4311f8f518be22fdc2b0f00e79a283701c53f6936dd63734ecb24480d5365d1a81392498faf9a1ddee577007acc5f8c87895be",
+ },
+ {
+ nonce: "444cbde3315ab7a30f0192fe",
+ key: "8bab05ddd17cac05203711b806475253a1ee0c8ee723eb520b73851cd51439b3",
+ input: "d776bee5625d29a2ebf6fec4df94d2b9ac62e8e7c56704fd38a87ee932b787cbc555621535e76ea30183cb0ee30604f485b541f45feb8c01b9750d37fded5cdffbbc34fb90fdc9c7c7ddf949a1d50b796f1ea5db437238c7fb83c4b22c9e491f75b33d84746f1cd10bfda56851b8514ff0ded0adfd5092a66a85202d06bd967485d06a2c56011110da74bf40b6e59f61b0273164744da02ce2b285d5c3f03aee79eea4d4503e517177692ed3bb035071d77fc1b95c97a4d6cc0d41462ae4a357edf478d457c4805fa586515614697e647e19271091d5734d90",
+ output: "60e9b2dd15da511770162345251edfb15cea929fb79285a42f6c616dfde6befc77f252e653b2d7902a403032fc4ce4934620931a2ec952a8d0f14bf1c0b65cc287b23c2300999ed993446eb416749bf0c9c7dfe60181903e5d78a92d85e7a46b5e1f824c6004d851810b0875ec7b4083e7d861aabdd251b255b3f1fd1ee64619a17d97fde45c5704ab1ef28242d607d9501709a3ac28ee7d91a3aac00cd7f27eb9e7feaf7279962b9d3468bb4367e8e725ecf168a2e1af0b0dc5ca3f5a205b8a7a2aae6534edd224efa2cf1a9cd113b372577decaaf83c1afd",
+ },
+ {
+ nonce: "50fdabcd995b0dd1850a169e",
+ key: "e9a431828b3cf3891bb1960f9dae3c85334a62f6ee2395ee5378bb28f8c68a68",
+ input: "4f57848ff5398e61bcafd4d4609bcd616ef109c0f5aa826c84f0e5055d475c6a3a90f978a38d0bd773df153179465ab6402b2c03a4bf43de1f7516eb8626d057ae1ab455316dd87f7636b15762a9e46a332645648b707b139e609b377165207bb501b8bccfa05f1bf0084631c648279afdf51c26798899777812de520f6a6f0d3c7f3ef866982f5d57f9c8d81c9a4eabb036651e8055a43c23a7f558b893dd66d8534bf8d179d8aa7d9e8987cfdaaa7b5a9381ba9c79d5c1161b1bdbd30defdd304ee07f19b7ba829a0d5b40a04b42edd6407b68399caac69069",
+ output: "e096cc68956ed16d2dea1154a259e01647913eeea488be0b54bd1816c781a35e161772ae1f7a26b82e864ade297a51cc9be518641b2e5f195b557ec6fc183e4e5c1fc01d84fe6ca75e5b073af8339427569b1b8ee7fcff0ffa5e7e6237987c40deec0abf091c06a3b28469c8f955fc72e4f3727557f78e8606123e0639dff782e954d55e236448f4223ff6301accda9f8fa6cd43a8d6381e5dde61851a5aec0f23aeca7262659bc793ce71fa7992f80e44611ae080b7d36066e5c75c30851306f0af514591d4d5034ecdf0d6c704bfdf85473f86141c9eb59377",
+ },
+ {
+ nonce: "3f32de67c92a44a0d9b1779d",
+ key: "d4338dcad949438381d5685e0ec3a79938607fdc638b7e69ce2e4c28fa3b3eee",
+ input: "046a61c0f09dcbf3e3af52fab8bbcded365092fad817b66ed8ca6603b649780ed812af0150adbc8b988c43a6ada564a70df677661aff7b9f380d62977d8180d2506c63637c0585dcef6fe3f7a2cf3bbb7b3d0df7769f04bf0f2e3af9439ab7615c304b32055aea0fc060890beb34fa9f90084814b6ed7363b400dfc52ee87925c5b4a14a98e3b50c7f65adc48c89ddd6414626c5e0bdefabab85c4a0e012243e682d4931be413af62fd7123ab7e7774fcae7e423bf1d3a31d036195437e9ea8f38aa40182daa9aacf3c9f3d90cc0050977c6065c9a46bcca6ba745",
+ output: "cd5a6a263e3ee50dda0e34c614b94c3ec1b14b99a2f4095a6b5715fdfc3449fcdf8a09d1ae02d4c52e5e638f1ee87a4a629f99f15a23dd06718792f24285f5a415e40f698752c697ee81f2f9248da1506ce04a7f489f8e2b02e6834671a2da79acc1cdfb78ea01822d09a1c4a87ffa44e56c4f85f97507044cf946ccb6a2e06e2917bac013f608d75ee78fa422a5efc9c569226bf7068d4705fde3a9fad2030256db0acf9a1d12666e0acf9f5346ad62e5af4c01a008d67ab1224b3e98278d073116ff966cdc779fb3aff985ec9411a3eefa042d71dd4ae5b15d5e",
+ },
+ {
+ nonce: "5a3d6aa35fa04717b40e4405",
+ key: "e61e702d1a5a3d14abb967bbcc8cc8ab8fadba208be40765391b1edb801d529e",
+ input: "af516216f74a6344cbe458cbba820f7e25c0b10aa84b790da2ee6317e059171076d7246c2878be83fc00c200d546c007f849e4c163d52c7b0da31beff4abff481be3266b92e668cf4dd1c84d9d7b3e5191dcd6ddb51d17d337621046e83e9ac035fccfb239648bc3c6fd340fbb50707e5a33b3ef439d292192d0e4bc727690c61450e5a28789e5ea50e746bc66d039493e080fb70e9ae06d89004cb71de8178941c422f1e9862492fc9149a4864ff52b1277b9f5a63c2f16e9adb5263cf65a034a62ebb0f1a385d2681c87a35f1c45670b4edef1c68fe9544fcf411d95",
+ output: "b22ffd8f0e549bd3e0206d7f01ff222f92d39b41cf995a331d5ef0cf5c24bcc3ddb36e64d351b5755400246fe4989b5f912e18daa46cdd33e52dafbd2872f16e94220b56315d72c1dbb1525fd34831d7202970c11711ff36de3fc479407c34fef0aea86e172f9beb0f393194355b9dd59625639f4a6bf72ba571c229f2fb053c1114e82793deb2dfe8232f1a327949689d2fb2820662dcd2a39a2546c7df12b3ff7e87e58c74badf568cddebd3c558f0f7874c834c4b8aa988653f138ec79620f5e3ed737690928a30f981dca9f2920ac7307607063b40f87c204de47c",
+ },
+ {
+ nonce: "22e02bb9c757121e1ec0d70a",
+ key: "9cbb1dca0495dbaa5ca9b8775eeb0dc5b80fbc2dc24b659e5a92d89466fb9cfe",
+ input: "a3d70bdb509f10bb28a8caab96db61652467cf4d8e608ee365699d6148d4e84d5d93bdabe29aa4f0bc8ee155f0b1fb73293c5293929eaacdd070e770c7cccfb2de120b0c3811abeeddaf77b7214a375ca67d618a5d169bb274a477421d71a651cfb9370bcf7e0d38f913754c11002089cf6cd6a8de1c8a937fb216591d57b37efdf3797f280773950f7eddeb9c3385c8315ff5ff581c64610a86ada7ff6a1657e262df94892dff9fdfb6e958d101f4c26296470c138dc4e1ca4bb565b3ff877a7f78b3d11d64b7c24e27ba6f6b06f6e368f5ac218cd5d11b815ab0987678eb",
+ output: "646314264896a6e25601e536f6e783d465b2ead1e0be4422bc9cc8eacabae4a749ad533eb28091be8397328dcfb34c92006bbda930ab070ed7b806095bb1c8f476350e7b08ffbd4d7d6055c8defaa8deff9d54f5215c2d7db27ce09e08f5d87a859145ea3126e2a01882921c3fddef3985bd451bca44063258390aec8ec725b07d064314fe43a9c83e9287b47616dfefbf539b82da209aa08a6d3176b7e3b4be4a17d44e581280a684e4a64414649bfcea82b541729f8178b580e8b972a89f5b8c4f9b68205e9396d8ae5e81873b61da074080fd44c52d50fb0880ee9c35da",
+ },
+ {
+ nonce: "27190905ba751c66ad3dc200",
+ key: "9d49002edb63dcaffb2ec6c3197a15b4138bce84796232859d1ee72ee4218731",
+ input: "f48b5ae62f9968baa9ba0754276cd8e9dcfa8a88e4571856d483ee857b1e7bc98b4732e81f1b4421a3bf05ab9020d56c573474b2a2ac4a2daf0a7e0c3a692a097e746d12507ba6c47bec1d91d4c7cfc8993c6700c65a0e5f11b1ccd07a04eac41f59b15b085c1e2a38b7d3be9eb7d08984782753ae23acdafbd01ae0065ab9c6d2a2d157c1fc9c49c2444f2e5f9b0f0bbfb055cc04e29b2658b85d414b448a5b62d32af9a1e115d3d396387d4bb97ba656a9202f868b32353cc05f15ae46cbe983d47b78ba73d2578a94d149e2c64a48d0c1a04fc68baf34c24b641ea0b7a800",
+ output: "b9af1016275eaff9905356292944168c3fe5fdffd9e4494eb33d539b34546680936c664420769204e91ead32c2bb33a8b4868b563174d1a46108b9dfe6d9ac6cc1e975f9662c8473b14950cbc9bc2c08de19d5d0653bb460bea37b4c20a9ab118a9550bfeb1b4892a3ff774e8efe3656adcdf48239f96e844d242525ee9f9559f6a469e920dcb3eaa283a0f31f5dfac3c4fac7befa586ac31bd17f8406f5c4379ba8c3e03a6992a1915afa526d5ed8cc7d5a2605423ece9f4a44f0c41d6dc35a5d2085916ca8cabd85ac257421eb78d73451f69aaedeb4ec57840231436654ce",
+ },
+ {
+ nonce: "7c996d5d8739629d36de4257",
+ key: "eaa5b2578bd6bdc5c6b0c79960a9ae26f1755cba6bcf04a9de315068990e0f0a",
+ input: "b39101601efa2ecdf41878b0fd920a3005ce709e4ec2970abb76e32c232ea21069f81b246eda75aace7555ce8ae203455d3723e684bd671389300e353eec0d2f499d10654fafda2e7a69bfca7198eb172249167ca8864b5d5f58d28723090ec86e251a1bac0346d52fd81f06e0c05429e0b2b895588290b7d00878a4da3378eb6c7e61487de2b318fedf68fa7ad7c88ee746827c1f60d98c7716f3f9695c5ffd4670f71a0fa78a1fb554ba482c5de83feaed7c65fc71acc9f541342eb8f7622b12bb2cfa222fa2ddd8b3ed210ce442275afa3132c8a0e17dd504ecbc92525c118952be",
+ output: "50eb5b21c179a03b9a822f0075906a3ce4acc32486139f92635c7d834f69071d5a6dc0e15ed06a5cee37147071d59641d140a82ad5815b954e7f28e080c3dbbeaf13943d7b7c66d49d51ba1132eeadd4cb7a7e7d726d08d95f1578d55519f267f753f3e16ff39504a87b2286d8bfba0fe6bc28887b466bf276453a82cdd0abbbbf08db0e1c26c317d50ad9b8dc09cd621bc566d362024e8404739df6468869d2125c58b25d70e392f5e75924c4341be81c263915bb514ad436fb24c2c67450e84f6d1b72d1a02a3310c07a7814d930264fdbbf5ddca7067e18e8a44faa87169b7f2e35",
+ },
+ {
+ nonce: "07a7bc75f4d1f6897a656f2a",
+ key: "cc429f94483c5d2b73e40bfeaa92ac17ddd9c9bd26dff97408754826a2417b7c",
+ input: "0a42f63b975ad0e12a1e32615813dfd6f79e53ce011e2a2f0534dd054689f8df73a8326fecfd517ff7fe530d78081af66c3a8c7c189eb9d9efed1e5577b5512d42ef1fe273f670ce380c64bc62e217a7e410a8ed89998344e29301e4e053a3a3cf7e71587fd056a6bd976f16e157476a06997dfaaff32172dd84190570621f2221420c0a0ea607ea756e9792c8c0e7157c95b89c9490e20b750ee85e4c27c9b8f409e848ec90afcad33342010bb9808358afbcb3d9b094127c38c243a204e76899677079758e7cbada9a5c18363449eebc07bab516a16372722403a046df85c7dd2ffc804c54d38aab",
+ output: "87a47bcaa1c1eb8e55151011c4f39af4b9e108a55a7124cdcf66d0dee727306e6971f783b038bd6b215f530cdbb53e17975742ec304fdb3792a88b674504396978c6a5e4a9c87a7c3ca430d61165c1a3f6162eeaf38c93e18b6ccb6a595ad428cdc98efef8f84463eed757a72ffd827b71c0579fcc1f4baa11812be2bc5a2a95df8e41d04b33343df09ce628c367d1f88488f7a2787f013c8e76f0b9257cee777ec4adc6df8c5790e41ea02da85142b777a0d4e7c7157a48118046935f8888b5352d1750bf00b92843027a349cf5685e8a2a2efde16dcf5e1c1ed8c779bb38cabfb42ec4dd87d58273",
+ },
+ {
+ nonce: "f7a40350de8cbd4025fb35fe",
+ key: "d9496e57dfe9840ea327f209e09d7c43dec86ac42b2d6a1a8476ab42b6fb5342",
+ input: "abeff48fa082dfe78cac33636c421991b0d94c3bc9e5bd6d22763601a55201fa47b09ce60cb959ba107020213c28ae31d54923d1e74ab1d9ddc2762b2d23d8c6961d81068230884a39682fa4b30676ffec19319362c075df0b879a0f083a67b23597bf95c4bb997fae4736479cb8a9c00520ba2f6e5962d54c313c576180d17779ff239ad60f1f1373627770d50a1c49718b2b2e536846299e052f8c1a5d3079e91cb1b8eac4661daac32d73b3b99e2051f8f694a61d1e9d3935f802921a4d979b6ade453cf30d73a4a498a6a2c5395c60fcf271d50b4967ac12b0d7bf818c2679d552e9b3b963f9f789",
+ output: "a0d11e732984ad575570ed51031b8ac2d7b4c536f7e85f6fce9ef5d2b946cefe2ee009227d6747c7d133ba69609f4a1e2253d0eb59d1f930611e0c26a7c0cf2d2ce7ccea6e079eadf2eb1acf0463d90fb4b3269faae3febfc88cb9fb0873d8b74894506199394c8e44a96e6b479bd3e045749cce1c3f57243abdb37e67084eb573cd820c6cee424227019592a027e9da8f7b8997bfb292627a986f83c8fb8d156a91a12a8b52659cf9272924631745ed3a2453a4c2d87a167faa9104e799c715ed597bcb66949ab15dae29a86ba147507e8d8af66e96c09c53caa053ad3b79d9ed3c0c6c00169eaec3a3",
+ },
+ {
+ nonce: "ce48aec66f90f026bfb88afd",
+ key: "502cb8420d9e517f9850b9cb32e5756f1bf6c9e242f94a5a7b7779269c1c8e6a",
+ input: "a77b7a5870335b9145fd2e08ec898ba2f158fda16e8a2661a7a416857b6ba6937b4843ecaa79d3635d28383af80290842de9ca0bb621ee22b7fd6bf379922741e812b1739c33dd6923d0607826fc84d46bbdbd1fe9d1255f56a167779a560a6eed1b9c9579b8f771147df467e67a070d9e9ce8ad92dc0543d1c28216c1dec82614ac5e853ed49b6abac7eb3426ef0c749febce2ca4e589d06ccfc8f9f622ede388282d69ceb2fd5122ba024b7a194da9dffc7acb481eabfcd127e9b854be1da727483452a83d1ca14238a496db89958afd7140dd057773ea9a1eee412875b552d464ba0fab31239c752d7dd3d9",
+ output: "b330c33a511d9809436ab0c4b84253eeda63b095d5e8dc74803de5f070444a0256d21d6c1cf82054a231b43648c3547aa37919b32cfd9893e265b55545be6d7cd11d3f238ef66c3c278fcccb7dd0dc59f57750562cb28da05d86ee30265ff6a3991a466ba7e6208c56fc8862e19ac332e5fb3cbcc84e83a6205dee61a71acd363a3c9de96d54070a69860c152d4ceb9c4b4cc3b878547b6116699885654b11f888dc3c23483a4b24fbe27c52545c06dd80ab7223d4578ab89bff5f9cbf5d55b19611a5251031df5da5060a1f198226c638ab5e8ec5db459e9cd8210f64b2521a2329d79228cc484c5065ef8a1d",
+ },
+ {
+ nonce: "8b6738eadaea410c7b148133",
+ key: "acc28f26867e2921cff89edf3452b4d4f2c4952ae36cc3cec938fad59037c47d",
+ input: "322d634bc180458123e10d0509870b54e0f0a3a72a2bd9e9cf44324c7a1ca37dd6adf9db1fcc8dadabd881f91d47d93b58382802b42ee936802fac8612ea4dd9eca5f215935ea9ba6233b9c8bddba3385861de669d95c888c8977851cb305db577a4eb2360f362fa459d61ffc8fcaa1502905b073bd8e9567ac7cff8e5fb1002c55641a3af5fc47ac0131fae372f073e19721ffcce9821e0241d7fa67bfc499c8f100e050d39bd4d7cae4557d208629603ec4a007852762ec1905d0e81b873510fd334dedcd9c288eb8415db505913af06bea94d197ab627d58f6a9944f6c56247595fc54ae3f8604aa37c3466f74561131e11dc",
+ output: "edbfb1090987762f75eba2439d746cdbefe8605b8ebad59e075d28b54edfe48813ccae891f6ed655c5ab5211ba896fff0c8e09bd1554aad987dc53f355d0822e9b0f524a99a79c68a9f3b4e30506cd725b07be135e4540078be88dac64fc545c433837b96a924452f6b844291c4c3fb5f8cc94f06d9f19dad7fc945f093020e82ed19f9eb3ddff68b813629991d1a460e5455e1cb41cf23bb3d96fdb6b96581c3bf9ef72814406329bbbba5b835e7724c728cebe88efcd996dea71d0fd5c53e081c21ce8b3764738d693e390fbf8e0137a716760fc9cd2014cd9bf3fd706bc3464d1f15803606976e96b1077cda0a62921ff7c32",
+ },
+ {
+ nonce: "84c53a88d5e7b88f66de07df",
+ key: "477e74c7c6883d8531a69abf8064f1788080247c3b97ff15408a5231e5869662",
+ input: "e6b8a9012cdfd2041ab2b65b4e4f1442794fdf1c3685e6622ce70f80b9c2252ba6d9e6384d474a7622053d35df946a3b19408b3e1712da00525070279ce381359b542a9ad7c07750e393e0834593777352c1f7dbc84cc1a2b1eba787377d2cb1d08a7d20e1393d44022107acac5d765be37f9075af02e4bbf8e60ceb262aa34e2b870cc7adcf54329a667249cb4958393bff4f4333338cae45cbca419d59e605aa0cecb1241080339198b9b283e4201afc07360b8ae2a57b0b9b97167c315f03fd7a87a00ae73f91ca560a1505f3cdf04576b9aee5ea775f719916f1e1942ad5311c7f87153f8e62855ace3f34afb08d4d7c7f4fd2bf83e42f76",
+ output: "fc2673c80812d101bca7a2e0e105fa449550e695a016596f5c3cde11fb7dc518b94fdb74058e634546a726c37896110e1d1f9cdeccba1c89958041061ded8e8bc2751ec6dad76a305e70c57f9c81a5a65b5116390af4f7bf7053a03ec13f5d60a58cc5ba61f8c46ef6d2d291de490082dcfdf294aeb3a9414d64e4bd6497d4625acfa591627bfd98f0aec7e7def71515c09942db6911d73b96b4bd2d6df03bb729e945d71549d40e4bc401e1f73baf263a74280537692240638619f92645a5ade1eb8151191c7ff8bd715b3c1cd667e69745b806e16d46d9aa680a7367b8fb45a1598631cf3d44c1f5cfcd95bc8dafdb65a2083905a6937fcf21",
+ },
+ {
+ nonce: "627acd79be19e60a36d2967d",
+ key: "648eec7d142bf1092adf706c0daae0ea14acb12733d8007aca0a3ce6e2389418",
+ input: "0cfd93b195e37dd15dfae83132c24ed5bfce7fe6fad4064b213b2c31a39e39ddad2f977e904c9c5b055ed03db46fcdd845bbb6ff0ab5a8c92e89295b6801f36ae63eba61fba24a3858aeb36f2da226b23b24d7b2c7d2670f23a9a1b60db85c0ecee584bef1b00e42d10ca17432a74bbb220d88356d82c850da4c09dd5baf413caf8f9479e02a330065fb865489c0f59605d56146ec8434182345de2d15e2a1dceeeee2fe94871d41913f6788738947ed9849ca0ae985e3e19a97bee82b96feeddceb196c9b6012264661945981c279f43db9599a4ef01116f592478619690daa64387290484d21e8d2444751194e1f361fb37f04014a3c7e4b409e5c828d8990",
+ output: "0502848571d1472ff10bec06c1299fad23a2cb824d88bf91b5447c5139500bd837a2fddc629e4a964e84907c1e6740263f1fef4f5ed41062982c150d9e77a1047b7d86c0e191945e8db00ca3845a39560857fc9e0e4a394eea4ba80a689cb5714c4bab7124ffdbfa8bbb91c3eb3caa1621f49dba1eea3ebf1d547ee337f9085638a12317b86c11aa1525813445107038942fc519eebdc1b98d313ad822bf0b94a054259aa8cf1be4b3a68f974269729941747f9a23fa5d83453071b431dac62274c24f6a32248b0785ff90aad5840fadc89af0aef7553d9352cfb00d3999ffbe28cd9fde7854e95710f4532b8bf5011e518c93361e58d22a2302182e00e8bccd",
+ },
+ {
+ nonce: "001e58b792ba1b9a74663564",
+ key: "cd9f2cdc1ade505e6b464685219bb4c1cd70a63667f387280043bf2f74030af9",
+ input: "0d8d864010ce8df1c0179cf0236dce1c100f9c115eaa5294c24a2e1afa27f9d57ebc18f00482be0218d44262bd4db73002ff53c6388f5e333470aced2a42a73b376686c8d02e05ece27cdd8b1e3f675c715981f8b656d68d0e16227b529cf881d2433e4371fbcd933eaa72346e77e688ac80ee95324512c66a4c16338cf38c941b72c21c3d01e005a07c0eb436014fb1ee61806de7e96842ca3217ab8c7607d609dd2f637f9fda8a85cb0549f262c9e4a955c384319a6ad2b696e2593d7d174f5ddb98e2a8d5d12558c18ab67571e9a0202e91ce26d720cbe41a3a6a4f309296ca4d9d9a59a9043dd2e5a707ed7d5034023d5ea06ab14b39b7852e5c984848d5670c6f2f0b189c2a8a4a4bca",
+ output: "d2a5693c9d503a8821751d085a0837579233e65b691366e4a7464481d22800e786939349f721a815f28b4e47c8889f0814fb95d592d1185e45d6dbcac14ffa4f1d6c79194f2f7eb7323439d9607edf80f01e3a968b483eb93c01d9cb9d3625d21d66927e7aeedc1d9bd589560ed2b61cbed5ad0e0310c8ebe140c64c67d4909c010902d5386efa359ab60a9573493d3e5d8761cfd4023eba23de48372032d4673b5f6ad66cd0dfab02a73aa81f269ae88fcabb3ae9cb09f6bf60fd3575a3046bc6843f444e1e9fb9ff9b991620344fb99da68df09496b40f8b9dfc34e830a87f65710940603ebab554d36e8b4c9228bc9c26c07b828f34cdfdd40b161717236ba325e8c20bd018b324345e09",
+ },
+ {
+ nonce: "cb1f642ce2c770518836a262",
+ key: "1559ed5a18ccc4c57415e5f0c694d875d182701bdba12e5d24fd09079898f6f5",
+ input: "07c50a69e168e388caf6f91471cf436886a3de58ef2c44795d94fba6538add8d414d84f3ef0ac9377fd5bed6aa6805a695f3a711025550bb6f014893c664e09bd05f4d3b850771991fc02f41c7353cd062156243b67fce9c1f0c21eb73087a5de0db0578923eb49bf87a583351e8441c7b121645bcb64ef5960fdca85af863dca7ebb56662e9707d541513bc91bf9b301431423b552e2c148e66ecfd48045ecb3a940dd65694d7fc8bf511e691b9cfd7547fe7bca6465b72ff9f1748723c4eb14f8bc1efb2fbc6726115c597a3881e0d5019335daf2e5ea8796c2a8b893ca798c4ef2639465505c4bd492bf7e934bb35be9b66c9f35730736c65fa4c1a2485378b9d71912cb924634a8e0db2802b75728818dc00fc28effdf1d8a05e4de4608bb6a78bb19c377d5ec77dca1b5ad38fded7",
+ output: "3dff5fde2ca24bf419e13cb7d12368e70449d41f2aa22e4b567f5cbdbcf3257975e44097deb180f2621ec36acf375dad3b7a19234b9856dc6c7842a7f86be00304b41a8c1662a02e8390346cbd0ff6be7bc1ceb821dbd805ab5c93c9c6ea5093249b5dc52081cbbbe1b326e831ef3c6c42fb791790086d1586f7daf031e70a71b54e9134f942e9ce229fc77980eb80c985ee0c5965eaba375d156f9b423b0615f4ca6fd77de28e28f35aba327e4f1b75725730155b7b4d6c5c264bf3d9dc9a16e7ededcc261add8c666278bac5cf0b3275d6d6678060eae30bbf2ce5f63e6a53a450b65aa0adbd1c90cf045f5ddd9700c2a99c80586c5244cf4c08035b6ff630c82cec3a4fcc83860e987898b42fe746939f8b37c814f8dab65de276e9784fb90f0751d3ba0826889e1e7e4fdbf8a90942",
+ },
+ {
+ nonce: "cc72b199d056100933750548",
+ key: "8e39cfe66660c5c34c19ffc5c4d8d2f608891d6d6520e663cb85a4ccd63db01e",
+ input: "3ddcd3c00014747903c95e49f64258615455a0b26c5070a9532382a9bbd18eeb19c9fe1a902f5c6baf544c5938fc256d310a9332223dc3c54a6eb79a4b4091c3b01c798d2800418863f2865c1cd8add760e445588576d4a6c945e1d6d50dc913674daa4737ac94d84eb0ff57cda95df915989c75adc97c4e3c1c837c798a432ba4803a246bb274b032db77e5c1bb554a5342ef2e5d3ff7f102adb5d4e282ad800ccae83f68c4bfd3b6046786a8cfaa2b63c62d64c938189b1039ae1a81ce5c91530772cca0f4a3470ba68e4e0548a221eb4addf91554e603155a4592dc5c338aa0f75a8cc2822b318fbfba4a8f73fa08512132705dae792eed6b809c251d35cca60c476406d964187b63cd59333771e37367671d0ccb393f5b8bde77bebc133485ec5c66bdd631d98cdbee78a3cf435d2f824fa2f9e91e89af28b2e155df4fb04bbe4ce0b6162dcd8e81ee8d5922ebf9c957b26c343a0396d91f6287a4af9e11b7fbb5a5a5c1fcdb186365a20617d4ff5037b0bfa97b6213a6ebcf0b78b81c65737378787b255cba03d715fed4addc2c70c1fb4d3ab16f2bff287186c26a164dae2fe9dbe3c4a2e1617f01cae79f",
+ output: "ecea5fc18dc4aed23359cacb8f79a457512e0a27d9816f353e315519d2b2faf74d14ae8ae5e227b203823998a47a050c363a807f45f610942fed4518b8091b88dff8b2af8fb6552eb654c85d2b6a918bcf56fb898392941d983b1afd867ef840e12313059ed3e4d217498dd511563a939c3c536fbbf8e019deed29262f0a655fc680b15939475e0cee0ce2e8bab5834f7354b93e2e0958a5bc608fab369b6aee3c9d73a6898e402484eac7300150517bbd137bf55762897696a3dc4be74b0c141755ac8f2f6e59f707b1690c451a774c46bbe195d826a6784f8d807b78f8ebc343ecacf37cb9b1b2fdbff6a1237b5098853d783e77515c419894c2628f8b5117042294ee2ed58a33746f9e79b13fdfaa25a75fc95340a89076e786e0ecad7de437a9a3fb3092146d255005b22895310b1252a3e34572cf74665b97f4adc30dd0f34e3216c7757953a4b618a775bbe68f9e0922d75afc80a1379aaf1745f2263afb6f0b37553d9c984f1ef781ea75b1980c559c77565c83f3e0bd7a3cd7cdb594658beb7e5eb940633dbc6ae2f50383beea676cb6c814b17b1d73dd133f544da88ab371415889ead21803c1ffe3f2",
+ },
+ {
+ nonce: "6d4adb2a1c0cd0333c19a010",
+ key: "df07d78b19202170815568dba3d1db9c1affb97dee19f11affc0d8b1cb224a3c",
+ input: "93ce72a518ae892e00c271a08ead720cc4a32b676016612b5bf2b45d9ae9a27da52e664dbbdf709d9a69ba0506e2c988bb5a587400bca8ae4773bf1f315a8f383826741bfd36afeae5219796f5ce34b229cac71c066988dbcae2cbcfcdbb49efcf335380519669aaf3058e9df7f364bfd66c84703d3faaf8747442bdd35ac98acdc719011d27beba39f62eab8656060df02fab7039223f2a96caac8649bc34da45f6f224f928d69c18b281a9b3065f376858c9fd10f26658ae21f5166a50fe9a0d20739402eec84f5240ee05e61268f34408089e264e7006a59bb63eeaa629ba72603e65718d48e94e244e7b39d21d85848d5f6f417631f3876f51b76b6c264356d7d7b1b27bbac78316c5167b689eff236078cf9e2e4626a4ae8bedeecbcaf6883e2e6e9304969b4fc7a4280dcdc5196267e9bb980e225fcbf7a9b2f7098f7f5c9edd06f50c8791edaf387ff3e85ff7bee1f61e4660fddd4eaf5ab0320508e3ccaa9823ae5a71faa86bd76e16d862d83ed57bf6a13de046a3095a74a10c4da952b3c9b8fbde36048537f76eef631a83d55d3a13096e48f02b96a5a8da74c287a9164ce03ddf2f868e9ca3119ec41f0233792e64086c903eb9247dbae80e923eae",
+ output: "bcf49d62dcd1cff9dc37d7096df0c39031e64ccaeea3830fa485edb71b7fcf2ec709a4b327ef9c7d4ea2b35f113a8485d4c236e06b3baccee30e79c6c08739fe5fbed59db30479b56dfbe584a5d79b169b200430ed27072137e940a34170606b31f22095f2151b4d9b901f6337f991a23e4c8997a1ebf5105361fdade1c889b8dc9565e3b33e0bd608c39d725becbb60da8a797186fe0986736112da3d09906442364d6e253e5b27fd5ad72e877c120ea7a11d42b19948f0df5ddabf9cf661c5ce14b81adc2a95b6b0009ece48922b6a2b6efffdf961be8f8ec1b51ad7cfc5c1bca371f42cdac2389cbddcdc5373b6507cdf3ffc7bfb7e81487a778fcf380b934f7326b131cb568bbaa14c8f427920aa78cc0b323d6ea65260022113e2febfb93dcfce791ab6a18489e9b38de281169f1cd3b35eee0a57ed30533d7411a7e50641a78d2e80db1f872398e4ae49938b8d5aa930c0c0da2182bd176e3df56ab90af3e46cdb862cfc12070bc3bd62d6b0387e4eee66d90c50972427b34acaf2baff9d8a76002a20f43c22ac93686defc68b98b7b707d78d0e7265aabadde32507a67f425cbd16c22a426d56b9892bac3a73dd2d2c03efdb22ecc6483f8d1ca67fc7d5",
+ },
+ {
+ nonce: "1552f1ecdd1ae345319d4956",
+ key: "968498f5dfc2bc49c3a34b7bbe38515d6b46cbd6f8828ce9273f7d14f08923c8",
+ input: "f72bec13b0f0b6f2317118f14c2a0d8e963b1bd49ae7584e710dbde75bb1e30c79281847cb822a5f3ae4fa56825e511212f17f0d293cfe80f872e6992d304e9283d08ce65ceeacb003b36a862c91282a22536e0b9c19953512a1bf9e20d3e7a8f1a2dff45dec0b9b04c592e88a7814540cf636a024d10008463d0b3aafbc4c9359889149433ef173124866aa6f53526ef3b3f2c630860ecdd08ffd9fc050e95da512cc87f812f9391085cdec5cc87258b8560806a52336d612da7ab05e0f60566b950904aa27c975a48c7d78455728c87f9b53aa4978374ab9592e12c22d9a760e26eb527133534ac5bbf969596b71cde8b4ef3587fa7ffa7116834348c275ad4dce68ab3397521ddc8e54380129cc81b981f9b32db20dddb0ecaa0f1ff7b06495a42b4a800a207b8e9ca38794e2fa9f40546e0e3aef7b5236d7fdadd72b1158714a5ad8d6264df1e75120088e449b9e911eddac59f1f19a795205ab7532783a93159876133b3fe3a518475a545fbe8dd2ac143f33c35d98e3ee13b63606b1e671917ac3ff9412773a3ac47b8c6627b8ba9dde6820f4f16c2ed9cb7d7086cfbb0cf2d7533eff253d14f634ab2aad3fb4289b9a0bb667a6fdd0acd5949185d53f1dd2b96ff060bb44f872a67259100669e6eaf1a7e2b11dd5fc35792db0c44a1127765934a068bf",
+ output: "bb618ae6b7739a4dedde1dbacf864b0892b93dea3007237d2f6f23be0718bdd29321e6b0fcb6a44dacf0f5c53d91e16165997e2302ae7ebc2dbd02c0fd8e8606a4ad13e409a4e807f331cf4174171c5fff23ca232192906b4eefdf2ffb4c65af78be01b0ba7d15b4341dd5a2edd49b17db2812358c8af0a4a9724e0169f50d1d331936bc2400012a60849876c3ead52cc9fe60173c9992f83f3e41ebd24fe3961835109612994c7620280539d483f91ef9a64c16032a35612a119589efe6357fa35b19531274576e304be75bc7e91d58015792095bb00ce4de251a52b946554366ea7ed9ce9317020ec155ae0071e022af36ad10eda5d671e5090c136e381cecdb8bc179474fabc7dab2d8a134772976cf0791b6cebe2333d34b4b8e2b6b2eab2b5dc7c6a08a583d091df64328cbcde36bc1b81095d82c741a1503c55d833d551a855e098166c5efffb8e4146e32e54abcaa85076ca6660abdfca9e82824217b5d3f23f7ff3455872bc76751480c1a8e3e725365c82fc135cd3713cc0f1ea733754142f8c37716a2a4fa8a6b898215c287565325774c2510df6b49e78cb986853ac5ca532c9a7e2bceb7c0157f60433f29fe29009343d6035d7b5892c77f821b644590615dc505604501dd218dcab789e6f0525387919cf25c7c6d62a8979e39d346decbed2657",
+ },
+ {
+ nonce: "478ca60b970002bca7147dbf",
+ key: "deedbe3b6c4d8f6e72cd276e60f30f14a0ef91c87f22aa4af2fe3c73f3f1512b",
+ input: "96eb94e1adbcc0646440c8824a2fc0f2c4b17d9cbddbb8ba8d9dbd6482fbf7201c74eb923153e0138b2f6f182f9c3d5656ee40bb7c26a01740b5c7d125261d4e4197614800aa152b402ba581bfbf4288e73c9ef7e7e37491212b921420eaaff880eeb458e3d0aa108b01b53492c97e328e9d10e3220b924351d583c00e76aee9325d6b89b1f162ffa30b386b37b5eaf4dfc25d22987dde4496158818c4d8f19ea300fe140be921d3f1abdaf9ab8946833a57cda5f41f995ff80e98b0f10f7afd736dd33438dfd395547f11563056078ff8f7c202aac262955f0ca5dae2365472de40f069028104ac552ea5a45ff2773335e5d3242f1e62e0e98003333dc51a3c8abbaf368f284536672e55d005b24b7aeba8e4cef23289adc12db2213aa037c797e7e753ae985568199cfe14cf1704fbca443e6036bdd05859e3583897cbefe7a0cf268b75d554b2da6e503ee04b126fbf74eaac0ebca37e84ab9c726973af780fe2bc9869fe67b7d9e4a04062ee535b2c1740d1347224e211b5cd37ee14c3325f40abee930eb6a1634986e756b3a1f86a3d7ee7184d95ea948506d8ab8b23f92ecf3eb0586f7a8b1bc227e08a0e32ca75ca4eeffc5c0a2a623547788bca66f3dc2c48671e462544d52a87d34307a7f111aeacb7da50262deab33d9f29dd6b47c3bb555be598d619cc66be8c4b74b01772725268a43d467f39bc565e5efcd0",
+ output: "590965d18ebdf1a89689662cfae1b8c8a73db8b26941313006b9b9bd6afa6a57149d09a27390b8883069e4fc2dfcf75035def1f8b865e24c21b1a1ed3e9f220d7b48046577b661bc92d9888a912984ad415ea2fc92c9e37da0bef5c7dab11495c612c27b5babe6eee28fd26482272fce69ca7f11bac95251735ad808365ac587830ec04105304f8e440a4da47d30e788718da4282941c9c76f18de4f954b8be750b54cb1145489edf273625a0df9a694a23fe7bfea12579b53c3b2a3de85705568cd7e603f3b8beba9a14cad9979ea283a8a291d3e1105b7f890e2a569804d9b7dd4c7e50bd0dcd11223fd7247af77f04212ece1b98c238d2fa0386a994bc502f83dcdd2e5a0d45b185155e1a395d91726d383c2c198fff1590e983c65ee041638510787c8c59c2e96f31678226a033e027bb40c416b73c3dbef31affc93a659c8ec7ffeca313fd5283a80533b2d63941c8f245d22b160c5fe57c5fa4b759c407b9acd6d9c4f80f244360b9acd11e2b43d4af757e16a6ef9d6756df39ca3a8a235e74351f50b2ebf54df633c8c400fd80b41b07117676d486377095660f2f20f62c034563b4560b473a8f4d6a740306d2a822fd8bd98012a840ba9b1709df9a0d61ecc305f7180fd764e334045d9a8ca23cb8036c05616a8b21fc488429ba4168c59dfa231f0ffa668a3be7b16583df1a55bb9c15d51660ddeca730d66f7a9",
+ },
+ {
+ nonce: "54df19942a3f5904d66dc071",
+ key: "4077517b5363e841089462edea2ce35fdfc56bb050b3c9ae6f2a0cc04ff4aab3",
+ input: "be3f309c6e7b89e1ec4a855cf161156d09f8a04d5630534ee19e9e071e3f4603f23f0c59a7b7f8a32c4c203ec8c129a268faba09abde7b61135c6c37fd091e2d695f0e242488098ebed30c7d321f4dcef0bdd23fa85a53569868cf2008bf4d2ee7a12a6673298c7e797321b9f4559748223b590e6fcf17aa72251586b01181cefcd32c6a1a20a0fc27143426f6572b1aab0e7301e390cb857f912d78d5153906c698ee140b36cdc72693cc019cb7add747ca3a07b2b82a2332bfa76c962b186ad94209fcf590ed0f6a73b08a771a58eb9649f2f1da4f7c385da83d50c939231f745514d14b0920deedd9c4dc6d2e547f83643d13541870875e52c610372b14b602e7a47f0b3721cfca60ec68e2eee91f40ceba2d0fdb4ebe19cb1d1ab170726c9e600030454ef355f9a40033672be520e528937f38e7a862a5ae50cd94f667cd015a72ee3f91b1a09031bf4c207e0c516b2e7a4baedf373f1ee71843e560741ed3a3094d2b513e2248caf27ce135716f6887d9f1fe5b11e02c12c989d29054ab183a3f55d9b40d78e12ff56edf936ab966c7c3130bea472b71fd69e70165a76afbf720e2c1587a77943b35acfd81b2ab6f39476623edf3663024fb84da8057ed3a361e9533caf9fc58a5e4897e4bf84f58ed063b5c353bdca3792952eec0a1404149ebeb5b17cd6350ab3e27e44e40fbcb00780d001a48d0365d534ff830553409919608881e665f83bb5cf0736d728c41cc4e985c377f89ee1186303d0d76bc634875ab3ebd87059969f24b0464ae11967bcc47f300a34e3b917b1affceea716c5ad9abf1aa3a1106e2f4d006514dc62cfd2a52426968f2f3991c9f9d8fcd",
+ output: "e4032c01bcece73fde73961ed216820dcb44ce20134678c98afb674bb03afec2f4aacbade7f87a32fff57ae9213eaf0509e9d9db1313b06fd1df53561f85896ba627cccd2d0e2ae4f24f5579bf02f6599f5e63412ba084cf53a5bc9a8061b5c029b755329fcd73f629fadd3bcf6cb4c572fea86466cb5159d19eaaf0f44c3471d0323bc7206bb514ed8117a61c6d98d44faff6a83716657531d965ba3efbcf067c452e0d2807db3423958d9a4421886fe132d7c47e82086db9507616b67f0051dffc1a49ecce3ca8e4d5f5af15684cd8837a471430ddd333ea0b6ee603b7d9e702692f857fab060ccf26f2a8e61dfd3b12923acca78b83a6004e4ff09113becf6bdd0bec3a449a195559dfeafd4e2a79ead5ae3c993a15ad9b1a2ce818e18edb010b7fece9aa437d85ba9841d89026d6aac1a3a6ab6dad932a26d7db6f3664b06d51584cf4d22a75c06e2840db7292798306e4d39379af85a6bc8dcaebb5246e07fadd5e336f122de0ecb99ca24a971701a1f43bd69933beef6e52d299b132e7510caf27b99739e32bd272afc36755ea80cc7ed3957d91325584b338d15b19fe554ee70bee903babe21d0cbecd49235c70a3a4f516ce16761d1cfcd70bb4b9c7c73c359f3fdd0753d6c1ac1a1463142f18266b6a9c84675f247d56563646fb2c8c3b6b81944c2ba2b76b685ba5ea40cf539bcf3850a8af3e0a69c0b38164de520a3bea82b91f67d36bbd87877b5be7f06c2d26b2dc747a26a51f51fe293197db0e91e6ac617c71ddc6edfeb7db8f067ac2012268deb7e5f00a640c1bbec5c4c71f10f921071308cadededad5c90e72d744d0bf790b043fd35729570889ebe5",
+ },
+ {
+ nonce: "90bece179b25feefcad4f9bd",
+ key: "e8512d17c6f5805b38e4c9b9c01961a523232162896538f5a37970de43e66906",
+ input: "0aa4fbce7e1774f0607e7ea01fc0e6d210bb283964ae75e180a9f6ff3d2c4d50914bfc32bca6d243eb33551521d54d66f377fdc1d31974ece79b157905ff7e7a9b064f349727ce37c83c15ae13df635c3e6b4baf994d9aa0bb90b06c6cda51deefda72c97a2993448e654b746b216d2b949bff1af5238558205cfc3162f1d7a020a919db4d4eb44bcf7b269d4df57e24133d1e540694b9148444cee16e64035ef006a6079dff449949c1b342991f2a27f21c8bd74ccf4bc944284a46e9fd9f9bfd4b95f80c05553950fabbf5e5aed6babb8427832266aa4d175114de9127ff6ee848534d6dd5aa6d2dc361319863cdf32cfb1b074faed17d368964393352df01fe8d86af0e994bc9dac315f7d9efa7bef47a16676cdf17a535ae71d399c4c11a3a3ba0491e8d41f419685258a4ec7d1ae588b3ca341719c0827ce5f5a653959a8671844f2d0293c09bc7d35497ed18c160fc7b6d073a311b621a7a37f7ded1df3d73dcba1821278c9e17a191997fa4dab0802e1ee1b468e91e4272c4569a17dc0b2805b980bde798640aa328a3605abea1865083d7446e960c27f69d32882a2a2295efc9c440dc203872373411925f8839715e9441d31dd9cc14bab09a3e03b4a63e14db3039d58725796326ea6327f189beecd63955f1409467c81f4691ecfe9f0ac5234f23dfb84e3199e415ee7b4f67189e8857ff6cb3f64c2ac1b554bfbd679a6ea8491cfd69d96d08ee2744d9103e0b044212560ff707974b1a9043e1f2c3592828fde8ab5e993652c00e2b3fdb19082611b67866ece6c4a2635f87e04d2136d679f632416b03ece4d7e9406f3437163f4fe0c8cc7b87d487f6de3b3022665bcafa847c2b9199e1ba9af7deb0e29b66ad41688d03a8369416dfbee6d03526adb3ebc4b4f8531d73589499a3010b5309e9d9d2f5a9cf347983a92722dbf6c4f0bae8aba57b37d322",
+ output: "a31f9a532f35f20ba604a9ab9989260e5a4ed04e6ecfa1cb9e0e1d16943906acbbb4e761a2bebc86cad0ce8b3f26d98b455e4b0835eb8b43791cea29fe8fa6e5187b60198142059bbce98917aa2957ae2555bee70e6e9e21ff6197a51ac2ca2952c413efec4d9903a2f6883e88aebe7ca8316831f6a8f2cd0e486319b58dc8db862779adff98b7f35c33faa53d56acd7a81e0feffc286b728f3a11afab7cace4c30b1a45780276b1f0ab89242410d07cb1191c7b9da5d09db7c9a729d91ac3ed82f4350f2871a12d125ba672861d1b0af7219c360a0e023a8b7c23fb9d72631c72e032c097118d90e5db0576586d8224165a8376fe8d04de93516848e7c2653cb4f7d24a971ccf4f16c527ea5b4153fad5fd5bf473b15806671854507bf1a6d9e5fe4a6f6ec977197d21d69a041dd955e199031f895adefd850c8b0ae327ba0c18ca1783560e1ff0feb2f659137e34a91e9e9ff04fe3375b7db6e4326986e6265e5fef00297f6ae627c7563846e531762748fe8d0b6baff17acf1e6c5cfefa35a95ef634ff96f83f16342a6c62311fc653d314f8a6de109356ab7801316e69a48834cb6325816b1f66d5c67d6e9c9cbc8e1a0521fd6e4bf77a7d2609f99c9579e143f530677b99d198a97620d087f058edf35eb7271701ecebb8bfde5671641ed21aeee9e7db06b932e0def91be93cf2955159e9666c770cdffa03886eb6e98dfca8f91ff5cef1927c0f82b9226d65c68d011416cbef802c264e34244ead7a6ebbe28a510a37e1276f4f3cf27a3944a08aaa23bd321092761627dae20dc269b6150545c75e995cfee0a9bcedb1ad8b364beb8839fd5c9f7984fa0a08a1a354aebe18f62acf6d6664978fcfda2ce6fc16eaa2cda5b835339001b3b98d3a407a3e18e0ec2da6ee3d1448c1ece2ed67c3f51f01e76ed59f0e61102b103a3c65aea94275e8d1f0d331538efe",
+ },
+ {
+ nonce: "09bdc9b17d49e6db953bc716",
+ key: "e5d9f90b68e6ed2e95ca1d636de3334244e63fd8891fb199175705ef5f69e91a",
+ input: "e097b1e8dea40f63714e63ab3ad9bdd518ac3e188926d1086a9850a5580affb592f6e421abc617c103479ba39a3924eea1c0bbbb051614c4b5003bbd5fcbb8093864fc1c130748194d6b560e203b889b98b574a98ec3e0e07cb2d9f271ba7794e5419123b4f2ebc7e0d65cd404104868905ff2c38d30c967fe9d77ebdd4b8fa836c3b0ad15e3e70e9a28236d5593e761e694b047f63bc62c7b0d493c3e2528c8af78f56725172ac9416ec2bdc54de92b92a63f9ccb61e686f9249c7cc337d99b2160400bb5535eb8f8eb1e3cafcbceaa821c1088edbacb3b01b5bed977e702de747ad00268ffe72e3d877dd75816db65b5459607cd1b963fe43bf2405ec223ddc0de514d59cde74f7522dc72285caa3eeb7eae527a7723b33d21ce91c91c8d26bf36eeb1dcdfc1e9e475c1565ed9c7e64ef601874a4f277280a5ceec26717e9385aee8b159379e3feed7952b87240c942970d63351259aa7a286ddb4a2620fa67565c92f592902e49422f1eecea2f44d1c0bbbf54a9e5612b86a9549aa3e6639a924c7bbe2d3c1b5669da73c0e2c6f6f6084f54a912ad2635d0141c2f5ac925414dce0da09ab8f86eae2a7b7e48741253189e5fd554d5c04d9807ac6ffd8a4f8229a3e8ab75ca5c778bd7ec5a5c02085faba9792cbc47f9e9311f3444e6544359769e1b3eb4d42ac8923ec94536e1a44497766b5da523f5763749dbc2738dfa8e13c191dfeac56c7614a96bd3ae23e4e6e5ac00be851ac9831108989b491eaade62113c531385ef3e964ce817c8ed0857adca946467682c2f4387fab2f31ce71b58370853171720268459588d5d216faca58d0bebbd7cd83a78445d9b49e83ec2cdb59b5d760880bf60532178d60372752b47d52562b316c7de5c74af9cd588643002d66bc6260595a540d2f82cf2c07fa64e0cdd1f79877b6a25b0608c735a7d35ca10852da441fcfb31061fd7e482a0989866f9eea8b0b39c3d519715c1c2766c3ad99f041143cdb36557ed647403458155dccbb80c3a365f0a85b1135695648ab67ac76b3d219c7b77e49d735c72ac947b1d7eeb279beb9d2602aba7b36ca",
+ output: "7b6e07e6415660affba56047b988f4548b308e7a642c76791f5c3742cc4cb744cde48fc30e50d458084e06c6dd29a52cb4c306a69a493a17c0838d14b107d07b81c983a2dbad09b80f087ba48465a8beaae5b16e8093e17cfb9e84ea3bdb9af00889268a5c01ddf25af434de56f65882322432aa275fac8519e317ef4d89478f29182143f97350983050f5d37c4b518611da6fa2aed7bb73e614231a194fe17c9073e377fc6ea0aa491e15ca54808e0536c8c3f1bf657283f807ebfc89b55049ac8fb86f89f17974fcf0afc1a2c690c0442842d0f4af9ee29dd960e499d1077bfdad4c0c9189a6e83799bb585acdb853c1e99da7ce9c7eeb9bf431f8d364d0ea80b0a95a7807f196c6ee69fe90e6d1f5d23e5cb256e37e65826d7a111f2272884d6319f968580b3164b2697ea6556816cea3ca316651fe2fd68dfa905d080c28622606f7d24da216289fa2c54c6f42dc244ecb047512ace62f0801f2dfad8f0218f45e2b3bbac97c2176c842398b16dfa1fdfc9a68b7b5a1e785d2a0cc592bc491f5a69c81127b758ee02c66b81674d3135c5882d1dc89dadcffa06f4b0644df5c7fd65c72611d79be7ad637edd6fc38b39946aa2a2c6d08ca9d3ff9a8ffe2e7989546489539b1a623fa937c468e59e0978602526b4367de277526895aa222fbaeae2084f418c5745d8ee844da0baa47f592970c14cf710f49539c12104a62baddb3382f5773dd18c83ecb238ae2e749a51584a38e394ebadd175bf5c3cec787907abb1d94af70ae63d3b7d8d5ff254da90b78ec8fe2ea95dfbc6e3e69ecad856c9e54906df8fe39859f2014b74dc3ca0ee2a957001939d37a6c0b489bd3f1658b835a57b24aa282c23e875c9e67e6eb8b32fe44e7d7d8e285d85da0ce1b53990f9fdb5e2e74728e433ed2c1044df9e89cb9bb316c39fc6fc8bcc74a382093926a288170e857d6b7f47858a4c2d05c74263dc9e8199332d0179687f4a4cdfc80ee6737300cefba75905b22d21e897f887b67aa3051877fff11d98bf96ca5091bb225bddd5eae697f3dfb0efcdb788ebf6694b5b39dbb0d4bf9427382a3a58f0b",
+ },
+ {
+ nonce: "3e507e0cdf0d11f88c6c3183",
+ key: "cdd1a20f05f9e74f1b4c9e2b81c85b11c5bc222925aa603f316dc21363af9620",
+ input: "0a1064714f20d9e47fe53250ecfec759f4137e60afaf65755f4709a483504c3855833b6dcaf7aa0180fd735fa9a73d46697f6c45004adf12452ea4c04a720fd7c20b9783b74b8b3ea0c8b1563d5a85f44af8afd7d91ca6298ca22642a684f66e365edd6f6bdb2dd32dfa13c62dc497fb341b86f65d40655931171416e23e3b2623c0b4a67d448877b6e3d4e0fe284034a10162b2b5e21639047036874f4bcde22b145b5f18aa8ff32dec81e6a5ac68b3c30c24bd8fd3b8e098a1cf202e2ab2a3bb66a9393222b9f7384653cda7707f00bc3c81e9591fd040a07d3629410c2db78781a4c9db3df5f9d648162f1b087974f56a89db07aa21ba827e3864a1618945b2fba06853a13c35da2909f5013feb313bae09870b8eab904024adab0d6ac46c1a1499791b47413139dee59db676949b9e9ab8d3d6abaa954ec2a9fc83953c91b483c3b6bd6700b96484850734e72e3710a1b379c0d0698aeaf68f13a0d317bfd689471e3299288e7a383a58522f0daaff210cc4917fa05f0b8ceefc2afc46148a05a100d30787accfb4da094e61ea6b58f132692aedcabae928e53c2594b01507b8fc2d0a85a1d111d1f4de0b95258281ae01873a72606753b6f878ecd8c4f613fb3477710d260f0bca0d4c06f675ab7113eded395f88755a98a0ad22b4a002cfe9447c4e39eda13738f4eccb9c13367ebc2878257c4647d31b67e5e32b6a77f23e9593658d19c0a40e8a7228767afba1cf23072b013b2d76ee66e42b57bec2797ce3619c695a661004c8129cb5c5d6a2836be22483f3b7e40bf8ac5535bf6cd065c4821a87829948c88163cfe3c0f60cea4e7ff59df4cdbf80064b2d664b39487413039999b5e86f1d467b12682d0cd355e9f7cd980e87d584ddbda89f68632d3b8fd6bc3b80205d7feb97a46842b093f74aa14bb21accda7474247b5e39ac76ef75e9b5b52b6a829a7e2297ab88fb0eb690d54ab1af2d7437149a6202035ce15f1e6c6267458d62677c263d83d3f8119af191b7d766582620e0f08b411c996c25ba6a32c2d73f592e789ed662e94103329bfa5e6573f1116ec04438997f3e4ad91b4123b570743455020d914bde2d8417fb24671e6db261732fb89dda1a36614b095529e4f97374c9bc0e55aa577bfffa663c816ca9fae3472e0a",
+ output: "b00a7caf5359c5bcebe590e6bab9aa03370050c55cbd45a257f4869937e922a15f2d38121b1493d6b5dd4a8a47d7b4e5cb049d396ad84ed421df774b0408b6939f18ebf5cf83f48c540affcc2a885967bf4bd222c42904b8a73c4185bde3f97e874fad25b46714235e60c9ff53ed2975c9c85ebad0752249e4b627ffa41555eb9074f63a5f7d61d207d2ce11b2a9fa23a13a0832eccb91efa2afd8d9acfee94ac78a733fa156bfea5006da1d0127c32aadbb75c015b68c627903e1c85bf3a1a9f99c6cfbdbb5c871f7f9661b78cf5e16d819f53e9930e201d4f58e69bcdce77ec5b9b1d2cf206a71f744342273c26b9abc71303c20df3d51f52222893d803fc8e0e0afcd99ee1c7f95b48680403566f7f9e296d7ccc0ec348b6ad515af58d11fd82c628ea29ee6a5d67aaeabd8823addc01a078b04313af73105d4ce4abef8e6ee8ce649640a19678292d4f1017d121549fd2c19ba6cdc0b613e512bc9551d759c6d38aea7e35c0847a142e273a16bb1495e652f9668b97801ba3f6d9931c0a1efaa4452e15732dca1ca9cb45ed289e0fd08d1cee1cdcc9dfba8d0b2562b0b1a180f4ee69d63573222c8d4789bf0d63d2a201a70c7b27c84e620e33e8a863cf49b784269a51ead3d4ad26f044d5859988d5485a11533ea805f5a8f6313caa6b421071a34f57170fdd8e4663e9a4cdcdcc1ddaa9f6e651fb365cf827667b018ae7d028c7f96295b2b4f9eeb4b361b48af86463a79f50b107ab0935e3cec3f4f203cea801ff95fb870d2c2f0e315dc8a6a547dd3c390a1f5403917315164bd2d40362489b389a54e8dc0ddb83e6a43a26c65923e6f76ee0ee0e3a33b0a9066620a01f0319e20b9f1beb3910ad962a3000e6aacb0ae57f3f6c5e0315be5de93edcf0e45e0e47332f9daf7f33e6e8bf1929910b78b8f88ca12bf5519a3217b7554c8c8350cc314561d580bf67a3878e3979430d070121a5e070a3458742e8549bda972f603222e2b30eb8a49a955805307e6e02f8c60a08188f69340e116422458d4a8841f46a78c833b1a822e3f6c9c97422c918f17c36175ca4b3d1c081ee4b175b4b07bf101c3836eb5b9e3cbd08a89b4a1c50edcb41ea8ea6ceb1532f5b842715d50dc21e2499e08c373d3dedb96bb477c8802ab7aa957e0b5810f38",
+ },
+ {
+ nonce: "c9da02eb6ca0cba74c76240c",
+ key: "574a41e94695e2d54c2f5e1a464c6e801f8d09481aca51437cd93e05ca95a4a6",
+ input: "00fa3b13b5cfa9b5d65a41cc2d3c420518802c22c4582873f1ad52a22032d2cef7c975078b199787e852fb1f914529f60d1cc854e5d6d547216dce043e0fc94866bb2193343c3a07fde60e668266d1cee3067c6f2ce0f9f63456ad08094b6c7f515f7ca90caa96494e2a6835ba1f3f166012ad1ff6af6b5f8455d5c26e72402966af9066ca70ad027eed23b0eb02c751195064a62283975efeb29bc5993f83360d012a2f5275ac758a9e8fe458fc7cc0673e6b9e338678f0faff60a67fff3784c3054dcbd95d1b00ed4c6156b3831cc42a2ccdeee55541f228b88e6c318e2d797c6fc035ae12868c4a4e3843b5b25a530b1477dec3f5ac27644476b5766e0ee132d833f9a63200eb0980bf72c3666150e567e01e3e1f469cf36beea65946fce714a3f354983e54ca4315b57ea35c5f48bd5eada05f49db1004cbb39888ebab3afad62f6509abad77ca8c4ff28731c7ae545e6876c8f4a80b6cc26928ee05001a9764694b52edd605e182d5a3a5fd192bff58aba90f57e4debe612d02cf6f08af33a78ebf8823bb3eb46d4da25b7dfa15ad436c380633d3db3d0dc4dfec6c2324d105e7090e65342b554854e777b40b5dab8125a58e8b212364ff88459a8466ff5ae661034abc8286a78ad5aa582e2dabbcd7a0b0cedcb9fd5f0bb8c3bef9117f2ca6520a72b94e528c1a4a464398e654995d5f4c77cbabf2b204b96a058cf1b38284b34e41ac37b05a003ed51be9602050f21c6b9326714bc425c1e22833da95a6e77571691d4dcab4ef9056c4c7f85d5b445b902eb375b5164c6bdf629ccfd4127a6c024bb6c4da0b6b08350432e58f8229e04e2e76f704be17d36e0c04fcc7a98f721d4572aa7f66ae8e9664300a189bc3862da47b60c8b33424f6d577cc10f4755f36c2a6decc30ba81bf48f96616ccfcfb74965d6bdcab82728bb224c560d1cfd7a175413ad1c14c734746be3b062b4e7514e9075c688103515e32e3335dbd272a315024d56f4ecd354264da9bc712080657b2b51b06dc7c4c441d9858935a4c3e6b207bde38ea83bba4c6854b2bcf914d758e0a174c0528e0e385c7cff355c38db1c22440369141e91266824c59f1ed23e7d4b99d31b0baa7bed4526e24259dbef5c9ae275e97267b756645f804c274d65ac7ab0f7683435bc2e4f24075cd1b790aa2b53fbf044e8f2092bdf0dbe88a582ff8f8de291e8220",
+ output: "bea32587095caac661c3ac49e65654b282192b2addf5b9a403aea6c8bd0096291a0a66ca4062acf1da91fb5749952096ec63ab652ecf94c29807f0aaac939b6896edcd6f0cd8dd8d208b906ef4d7a8766831fecd6ce98f4ea0c34fa9a5114dbeb23c2cd6d3aa962e39b18cb343c24e65d49fad0a0fb50736f8d2b24b011108932484399f4c4510ac9a5e6bc78ff0b450e67f87b49f253b99d95d6294e15a9934fc8b89a5913c08f75d3516766fb0f60f82e2b2647b4619991c78adbcf548c07c0dda30c629349d84f298313c3e629e03760b1cf860264205a950d6fd86732a6513827f72c0dff5aff96f7203464f60849c1065beb70f282cca1334f6f6c767dfff94f063361f592e85597de5d313eaed17bd533db24818d9ba9aea2afa797721fbd19eea7b8d46bbc4b9dc0164636d2e754f5e9e8c04e2a381096331731c645ea1f613a37bfa9a6fb2c6307e9bacacbeab7f5672163ff9742a8115049bce0269d7d5f6f35787be031dbee1535b0516ec0b46d12f5833cde5f2cc569edcdd20993e9776aacf48ace7bfadf79065f2803fba6b2b27aa622abb7ae023ff2b27b727f509f313f92026392485a5ed4fd53b2e22b2d2dc1538ce158d34921214638be30ae054a0f5f1d4f9c590a2d215ac2a5b23ed33871ab26c8bb6db7fe9d6f51e527c9547248a4e9734c64658b22893f4f6867a35f18e2bbfd7d62142025955cb51af8e40b6fcb91c7e959cea2c92022c87c29dae107a306f41b00e73c7bceef8cb070e8f9e830caeee463170e919cba6eee63092a5a7ee33b74db09cdd022fdafbcd5d524253a29a103ba6f4d668d31d18f867557871c0e0258221c3050d57c18bdae4cc4ff8da0daddb5c08619be127ee76a317b59a9d8e67808603a1bfce6b4e0d070082b283bf9c0e6ef8256208e482f3e2d1a40d30807f60a868e2279dfbc3586d44ee25fdca3505cd39fd469c2cd03bc2f921d22a8346750f346c919e7247301c1c8a4a3ddb8eabc6e80d85cd2459afe1cbb4851ea2c86b8075e0fef3177cb074894410ecf681242fac62b5fa4ed3a10ddaa595427851d376cf69e350207b667f7aa26d003f1ec739a8792532ebd93f3cafb1fea40d227bcadda2fb6da794cea3371240f257f80b1b8a857ea453b46938397c1f4b303a46257750003a60666a11d03bf2afb5c71e059933d617288891733b63784bd9c662234f",
+ },
+ {
+ nonce: "a4472b3c13c814f614706fa2",
+ key: "183dbd3967cdaac9b58554cb226a532087ac22bb80a59d1c2e6b997d61e46f45",
+ input: "01847d8a97d56e55e12f89adb13c8c0f9dea5555e8dc61171fbb8e181f6cf846a4dd68b2c75335c0896fa215bf7f9eb7e398e4520aaaf33461ecfb61051f43d43569fb75fabd79d319bf39469f951e4da7932a74624c46d8d26a9499c701c00d3dea57a6f65b4c0f33b568d13989340294d17cd005b26d89cf6fa1c88e7b6ef4d074291fa8c117ae05d7c785459ef4561c45af63a811e9aa1c31b69a5bdac2356d955a0f579791247a757a691b3de447a53619878397cd82a74053f06da3574045bc856500ec01fd2afbc64d8dd283ac876a50e9396f78c424ab157f481316fd9c90cd899f5aca46dad32c68f1d64ea7f1c4bdb994ad847072609bd89adc2fa8382a5d573b680533640b8321b6adf27926274660b2cbaf04fbc9a4fb17ce8957c38c7bab1aafd5bf7263171e47d2e1ae5cf0494815642209d303dba561754479c24ea01a573c9083b68acc49907b1748924d3c6a82feb9417ca932578c123f9db35521c0d992565f7396f0c23e436289c1720e4e7c6e285c04a8159f93e06801334e523b18fe188355cc6a155febe64ba053e6b5d1cc87787fd5ae68fa86d8c51868b9f6a9664cf0d56aa6cb8463362bb671e6b8423bcbefe2a1a0acba3f135496736b5cec5e329494af46aba322bf5d1cc108c98298459558773a316e09b0bb960a26f4b0bfbaa493b5f98a0e522b6203c471b10e662abe9b9e60de2a1517843933add02017fadd62608383ad53796159f3d21b2c8ed7295802ca79ea65d550114ca2bcc7f7c3b4c6709fffc3c2de00da06e83d8f0cf04b8c8edd21c0fc11a0b2aa7f6adad255fef25e5c0a9d59546e97446e1fbf6a51a8ea6cad54cabfdd19cd10d7d33ff0549b710557e3931821dd8809ab0a9d3aaa761a01ae0f2e378906672924d6a1b12fb1cca7bed41f31974b9917a05de60c32796f502e7035a2c01cb49bc8e1734b9fa138b81b4dfe19d37f5942dd1b42f03e1e5a6a046ecd457174150e17dd148e4bfea44b72da35ef42a7251244700e59e702033677d42611168fd246e1b18b9a464b6c20fc7fcf6360cd00466ece059a69d7d54a4f5565d08799f85dd3c849a08ba43415077c1c0e5dbdba52bb3167ee99a11db551f0260493be1dde58d2072e8c02251f4f574b6e115cbb6136dc2c3fbce75fdcefe812d9c07a91a89088985a52cb1fb9f6cef80fa30364706414175e42c75e8e37f6e7cd028c99f59caa88c49db5b46e8d6301bc39034013718a9eeef5506415016fb21d70e46a03b4c5ba72f91dd9321ff5e210e5e5f7b0723a3bc4bb02b5a74c1f4a63aa5a993a31f79a768fe8033c9abfeb4deb536af1054be02d8d1c4a6a0fa75f3eb787d57a03b7ae994fb1b54b2c43b230ce32e6245d944b3cea4fa6",
+ output: "785dbea5d1e50af4743ed5fd2209e441fc7c50bc7f6fd9cc7f24654c619e2606178dcbbd81a1f94c1b3176837024098bd31326145be326b32fd9277a55a6fb38780c8dc8b471a3184379d90da4aa87d80b889b1f4d8d0755c1704a526b99ac829b8ad157ca54b2b05ff8b2917e27b0c147ab54add9a89fdcad7b93ba1fe2d5be9de88b68a5324f1b42943e45ee31c4ef783ec9e2337b3f2834b10cf452b313fafdf0c03719140f64060da0a565e185cb8e544e1c185ca230ff2321739a285abe8be4be0ce76678a7b0902a77a645194de49fef8ff64cc464ea25e1f1d72c775e450f08ddd7680d27a4142879787b198583d93b84cd87fd5b4063d92d13d9c9cb580c01fac0174686a18f64e6fa0b3589624cfae04aad74950559bdf92b2b199c60cb04013aa0ef56d1f9ec5b7e968f6a83756ecc9cee7dd8b433f64649f948df5474a64549e71e46fd8bb16568d21f5fb67f5ed555f2b8aec4709383e8cbc45b9fe47c0434178ad4c6d0d42606d6eef0e21d0370898d1d5d646830a88d5f024094fe9c7a2003ca13d20ab7cd748dc11a22f578ddab416f3500eff3d89fc177b46436108e2e2c7973910cb8454a01c9e9b98f966848325444b2ac205b1ed6919fa76aaf63717574761b7f62b10649357df49f85a845a30b6acd57fa202fe58673930ec59399f537e9682b1f5f6f409988789a8e0c1f803478dded14b40d3b6eb3109758efeb6a7fe21f41c4dcc8027258da27ad74010839dbfdf8fe55050511f85c321e653f76e55f22248f46da529a380c6b1a16a19ce73af9715545c2cae098dc42dd61248dbcf7b295f4dc6b8930b41baeef677156c534869be65e723e1aa0336e8be8a3b138f840c9cd63bab6d9d61f239a47d8cf56258544e6ef65edca27069f7a57f087a7cc021fa1294b75c0c0f1093c025e426e4f041ed5187f358402676d5da5fb6ceba76a178f65c8c3046f258531c165b8808bdd221c59ff56e3e06247576e144aac01ea96a07f1be15d7a2b0b3b6c259a9133f8a50b56154ecf9f61022f470027247e6e70e6eaf7ece5e324ec8f95667ffed10337652b119e7cb8d197e306e81ea251340b9fb2c33aa230c0a16e1ca783f9344b3acbf413acd96616e6d477dba90e39326089934bc5ca6620855cdc442e25bf8b8debf335e16e7e25cceb68659cc81b13a507fbd9f30b347126beeb57016bd348fe3df592d4778011664a218227e70d7360d139480500b7f6f84153e61ca4dea105875e19ce3d11a3dfd0ad0074035ff6a9fac0ece91afd8be74c168da20c8baafcc14632eb0e774db758a3d90709cddf0266c27963788c35a842beea8ba2d916234431efde4bb32fd7e1cef51dcf580f4697206bbc3f991f4046360aea6e88ec",
+ },
+}
diff --git a/local_crypto_patch/contents/chacha20/xor.go b/local_crypto_patch/contents/chacha20/xor.go
new file mode 100644
index 0000000000..c2d04851e0
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305.go b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305.go
new file mode 100644
index 0000000000..956795524f
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305.go
@@ -0,0 +1,101 @@
+// 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 chacha20poly1305 implements the ChaCha20-Poly1305 AEAD and its
+// extended nonce variant XChaCha20-Poly1305, as specified in RFC 8439 and
+// draft-irtf-cfrg-xchacha-01.
+package chacha20poly1305
+
+import (
+ "crypto/cipher"
+ "errors"
+)
+
+const (
+ // KeySize is the size of the key used by this AEAD, in bytes.
+ KeySize = 32
+
+ // NonceSize is the size of the nonce used with the standard variant of this
+ // AEAD, 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-Poly1305
+ // variant of this AEAD, in bytes.
+ NonceSizeX = 24
+
+ // Overhead is the size of the Poly1305 authentication tag, and the
+ // difference between a ciphertext length and its plaintext.
+ Overhead = 16
+)
+
+type chacha20poly1305 struct {
+ key [KeySize]byte
+}
+
+// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key.
+func New(key []byte) (cipher.AEAD, error) {
+ if fips140Enforced() {
+ return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
+ }
+ if len(key) != KeySize {
+ return nil, errors.New("chacha20poly1305: bad key length")
+ }
+ ret := new(chacha20poly1305)
+ copy(ret.key[:], key)
+ return ret, nil
+}
+
+func (c *chacha20poly1305) NonceSize() int {
+ return NonceSize
+}
+
+func (c *chacha20poly1305) Overhead() int {
+ return Overhead
+}
+
+func (c *chacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
+ if len(nonce) != NonceSize {
+ panic("chacha20poly1305: bad nonce length passed to Seal")
+ }
+
+ if uint64(len(plaintext)) > (1<<38)-64 {
+ panic("chacha20poly1305: plaintext too large")
+ }
+
+ return c.seal(dst, nonce, plaintext, additionalData)
+}
+
+var errOpen = errors.New("chacha20poly1305: message authentication failed")
+
+func (c *chacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ if len(nonce) != NonceSize {
+ panic("chacha20poly1305: bad nonce length passed to Open")
+ }
+ if len(ciphertext) < 16 {
+ return nil, errOpen
+ }
+ if uint64(len(ciphertext)) > (1<<38)-48 {
+ panic("chacha20poly1305: ciphertext too large")
+ }
+
+ return c.open(dst, nonce, ciphertext, additionalData)
+}
+
+// sliceForAppend takes a slice and a requested number of bytes. It returns a
+// slice with the contents of the given slice followed by that many bytes and a
+// second slice that aliases into it and contains only the extra bytes. If the
+// original slice has sufficient capacity then no allocation is performed.
+func sliceForAppend(in []byte, n int) (head, tail []byte) {
+ if total := len(in) + n; cap(in) >= total {
+ head = in[:total]
+ } else {
+ head = make([]byte, total)
+ copy(head, in)
+ }
+ tail = head[len(in):]
+ return
+}
diff --git a/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_amd64.go b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_amd64.go
new file mode 100644
index 0000000000..bfe546b60a
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_amd64.go
@@ -0,0 +1,92 @@
+// 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 gc && !purego
+
+package chacha20poly1305
+
+import (
+ "encoding/binary"
+
+ "golang.org/x/crypto/internal/alias"
+ "golang.org/x/sys/cpu"
+)
+
+//go:noescape
+func chacha20Poly1305Open(dst []byte, key []uint32, src, ad []byte) bool
+
+//go:noescape
+func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte)
+
+var (
+ useAVX2 = cpu.X86.HasSSSE3 && cpu.X86.HasAVX2 && cpu.X86.HasBMI2
+)
+
+// setupState writes a ChaCha20 input matrix to state. See
+// https://tools.ietf.org/html/rfc7539#section-2.3.
+func setupState(state *[16]uint32, key *[32]byte, nonce []byte) {
+ state[0] = 0x61707865
+ state[1] = 0x3320646e
+ state[2] = 0x79622d32
+ state[3] = 0x6b206574
+
+ state[4] = binary.LittleEndian.Uint32(key[0:4])
+ state[5] = binary.LittleEndian.Uint32(key[4:8])
+ state[6] = binary.LittleEndian.Uint32(key[8:12])
+ state[7] = binary.LittleEndian.Uint32(key[12:16])
+ state[8] = binary.LittleEndian.Uint32(key[16:20])
+ state[9] = binary.LittleEndian.Uint32(key[20:24])
+ state[10] = binary.LittleEndian.Uint32(key[24:28])
+ state[11] = binary.LittleEndian.Uint32(key[28:32])
+
+ state[12] = 0
+ state[13] = binary.LittleEndian.Uint32(nonce[0:4])
+ state[14] = binary.LittleEndian.Uint32(nonce[4:8])
+ state[15] = binary.LittleEndian.Uint32(nonce[8:12])
+}
+
+func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
+ if !useAVX2 {
+ return c.sealGeneric(dst, nonce, plaintext, additionalData)
+ }
+
+ var state [16]uint32
+ setupState(&state, &c.key, nonce)
+
+ ret, out := sliceForAppend(dst, len(plaintext)+16)
+ if alias.InexactOverlap(out, plaintext) {
+ panic("chacha20poly1305: invalid buffer overlap of output and input")
+ }
+ if alias.AnyOverlap(out, additionalData) {
+ panic("chacha20poly1305: invalid buffer overlap of output and additional data")
+ }
+ chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
+ return ret
+}
+
+func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ if !useAVX2 {
+ return c.openGeneric(dst, nonce, ciphertext, additionalData)
+ }
+
+ var state [16]uint32
+ setupState(&state, &c.key, nonce)
+
+ ciphertext = ciphertext[:len(ciphertext)-16]
+ ret, out := sliceForAppend(dst, len(ciphertext))
+ if alias.InexactOverlap(out, ciphertext) {
+ panic("chacha20poly1305: invalid buffer overlap of output and input")
+ }
+ if alias.AnyOverlap(out, additionalData) {
+ panic("chacha20poly1305: invalid buffer overlap of output and additional data")
+ }
+ if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
+ for i := range out {
+ out[i] = 0
+ }
+ return nil, errOpen
+ }
+
+ return ret, nil
+}
diff --git a/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_amd64.s b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_amd64.s
new file mode 100644
index 0000000000..c703c13471
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_amd64.s
@@ -0,0 +1,5230 @@
+// Code generated by command: go run chacha20poly1305_amd64_asm.go -out ../chacha20poly1305_amd64.s -pkg chacha20poly1305. DO NOT EDIT.
+
+//go:build gc && !purego
+
+#include "textflag.h"
+
+// func polyHashADInternal<>()
+TEXT polyHashADInternal<>(SB), NOSPLIT, $0
+ XORQ R10, R10
+ XORQ R11, R11
+ XORQ R12, R12
+ CMPQ R9, $0x0d
+ JNE hashADLoop
+ MOVQ (CX), R10
+ MOVQ 5(CX), R11
+ SHRQ $0x18, R11
+ MOVQ $0x00000001, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ RET
+
+hashADLoop:
+ // Hash in 16 byte chunks
+ CMPQ R9, $0x10
+ JB hashADTail
+ ADDQ (CX), R10
+ ADCQ 8(CX), R11
+ ADCQ $0x01, R12
+ LEAQ 16(CX), CX
+ SUBQ $0x10, R9
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ JMP hashADLoop
+
+hashADTail:
+ CMPQ R9, $0x00
+ JE hashADDone
+
+ // Hash last < 16 byte tail
+ XORQ R13, R13
+ XORQ R14, R14
+ XORQ R15, R15
+ ADDQ R9, CX
+
+hashADTailLoop:
+ SHLQ $0x08, R13, R14
+ SHLQ $0x08, R13
+ MOVB -1(CX), R15
+ XORQ R15, R13
+ DECQ CX
+ DECQ R9
+ JNE hashADTailLoop
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+
+hashADDone:
+ RET
+
+// func chacha20Poly1305Open(dst []byte, key []uint32, src []byte, ad []byte) bool
+// Requires: AVX, AVX2, BMI2, CMOV, SSE2
+TEXT ·chacha20Poly1305Open(SB), $288-97
+ // For aligned stack access
+ MOVQ SP, BP
+ ADDQ $0x20, BP
+ ANDQ $-32, BP
+ MOVQ dst_base+0(FP), DI
+ MOVQ key_base+24(FP), R8
+ MOVQ src_base+48(FP), SI
+ MOVQ src_len+56(FP), BX
+ MOVQ ad_base+72(FP), CX
+ VZEROUPPER
+ VMOVDQU ·chacha20Constants<>+0(SB), Y0
+ VBROADCASTI128 16(R8), Y14
+ VBROADCASTI128 32(R8), Y12
+ VBROADCASTI128 48(R8), Y4
+ VPADDD ·avx2InitMask<>+0(SB), Y4, Y4
+
+ // Special optimization, for very short buffers
+ CMPQ BX, $0xc0
+ JBE openAVX2192
+ CMPQ BX, $0x00000140
+ JBE openAVX2320
+
+ // For the general key prepare the key first - as a byproduct we have 64 bytes of cipher stream
+ VMOVDQA Y14, 32(BP)
+ VMOVDQA Y12, 64(BP)
+ VMOVDQA Y4, 192(BP)
+ MOVQ $0x0000000a, R9
+
+openAVX2PreparePolyKey:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x04, Y4, Y4, Y4
+ DECQ R9
+ JNE openAVX2PreparePolyKey
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 192(BP), Y4, Y4
+ VPERM2I128 $0x02, Y0, Y14, Y3
+
+ // Clamp and store poly key
+ VPAND ·polyClampMask<>+0(SB), Y3, Y3
+ VMOVDQA Y3, (BP)
+
+ // Stream for the first 64 bytes
+ VPERM2I128 $0x13, Y0, Y14, Y0
+ VPERM2I128 $0x13, Y12, Y4, Y14
+
+ // Hash AD + first 64 bytes
+ MOVQ ad_len+80(FP), R9
+ CALL polyHashADInternal<>(SB)
+ XORQ CX, CX
+
+openAVX2InitialHash64:
+ ADDQ (SI)(CX*1), R10
+ ADCQ 8(SI)(CX*1), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ ADDQ $0x10, CX
+ CMPQ CX, $0x40
+ JNE openAVX2InitialHash64
+
+ // Decrypt the first 64 bytes
+ VPXOR (SI), Y0, Y0
+ VPXOR 32(SI), Y14, Y14
+ VMOVDQU Y0, (DI)
+ VMOVDQU Y14, 32(DI)
+ LEAQ 64(SI), SI
+ LEAQ 64(DI), DI
+ SUBQ $0x40, BX
+
+openAVX2MainLoop:
+ CMPQ BX, $0x00000200
+ JB openAVX2MainLoopDone
+
+ // Load state, increment counter blocks, store the incremented counters
+ VMOVDQU ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA Y0, Y5
+ VMOVDQA Y0, Y6
+ VMOVDQA Y0, Y7
+ VMOVDQA 32(BP), Y14
+ VMOVDQA Y14, Y9
+ VMOVDQA Y14, Y10
+ VMOVDQA Y14, Y11
+ VMOVDQA 64(BP), Y12
+ VMOVDQA Y12, Y13
+ VMOVDQA Y12, Y8
+ VMOVDQA Y12, Y15
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VPADDD ·avx2IncMask<>+0(SB), Y2, Y3
+ VMOVDQA Y4, 96(BP)
+ VMOVDQA Y1, 128(BP)
+ VMOVDQA Y2, 160(BP)
+ VMOVDQA Y3, 192(BP)
+ XORQ CX, CX
+
+openAVX2InternalLoop:
+ ADDQ (SI)(CX*1), R10
+ ADCQ 8(SI)(CX*1), R11
+ ADCQ $0x01, R12
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x0c, Y11, Y15
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ ADDQ 16(SI)(CX*1), R10
+ ADCQ 24(SI)(CX*1), R11
+ ADCQ $0x01, R12
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x07, Y11, Y15
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x04, Y11, Y11, Y11
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ VPALIGNR $0x0c, Y3, Y3, Y3
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ ADDQ 32(SI)(CX*1), R10
+ ADCQ 40(SI)(CX*1), R11
+ ADCQ $0x01, R12
+ LEAQ 48(CX), CX
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x0c, Y11, Y15
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x07, Y11, Y15
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x0c, Y11, Y11, Y11
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x04, Y2, Y2, Y2
+ VPALIGNR $0x04, Y3, Y3, Y3
+ CMPQ CX, $0x000001e0
+ JNE openAVX2InternalLoop
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD ·chacha20Constants<>+0(SB), Y6, Y6
+ VPADDD ·chacha20Constants<>+0(SB), Y7, Y7
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 32(BP), Y10, Y10
+ VPADDD 32(BP), Y11, Y11
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 64(BP), Y13, Y13
+ VPADDD 64(BP), Y8, Y8
+ VPADDD 64(BP), Y15, Y15
+ VPADDD 96(BP), Y4, Y4
+ VPADDD 128(BP), Y1, Y1
+ VPADDD 160(BP), Y2, Y2
+ VPADDD 192(BP), Y3, Y3
+ VMOVDQA Y15, 224(BP)
+
+ // We only hashed 480 of the 512 bytes available - hash the remaining 32 here
+ ADDQ 480(SI), R10
+ ADCQ 488(SI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPERM2I128 $0x02, Y0, Y14, Y15
+ VPERM2I128 $0x13, Y0, Y14, Y14
+ VPERM2I128 $0x02, Y12, Y4, Y0
+ VPERM2I128 $0x13, Y12, Y4, Y12
+ VPXOR (SI), Y15, Y15
+ VPXOR 32(SI), Y0, Y0
+ VPXOR 64(SI), Y14, Y14
+ VPXOR 96(SI), Y12, Y12
+ VMOVDQU Y15, (DI)
+ VMOVDQU Y0, 32(DI)
+ VMOVDQU Y14, 64(DI)
+ VMOVDQU Y12, 96(DI)
+ VPERM2I128 $0x02, Y5, Y9, Y0
+ VPERM2I128 $0x02, Y13, Y1, Y14
+ VPERM2I128 $0x13, Y5, Y9, Y12
+ VPERM2I128 $0x13, Y13, Y1, Y4
+ VPXOR 128(SI), Y0, Y0
+ VPXOR 160(SI), Y14, Y14
+ VPXOR 192(SI), Y12, Y12
+ VPXOR 224(SI), Y4, Y4
+ VMOVDQU Y0, 128(DI)
+ VMOVDQU Y14, 160(DI)
+ VMOVDQU Y12, 192(DI)
+ VMOVDQU Y4, 224(DI)
+
+ // and here
+ ADDQ 496(SI), R10
+ ADCQ 504(SI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPERM2I128 $0x02, Y6, Y10, Y0
+ VPERM2I128 $0x02, Y8, Y2, Y14
+ VPERM2I128 $0x13, Y6, Y10, Y12
+ VPERM2I128 $0x13, Y8, Y2, Y4
+ VPXOR 256(SI), Y0, Y0
+ VPXOR 288(SI), Y14, Y14
+ VPXOR 320(SI), Y12, Y12
+ VPXOR 352(SI), Y4, Y4
+ VMOVDQU Y0, 256(DI)
+ VMOVDQU Y14, 288(DI)
+ VMOVDQU Y12, 320(DI)
+ VMOVDQU Y4, 352(DI)
+ VPERM2I128 $0x02, Y7, Y11, Y0
+ VPERM2I128 $0x02, 224(BP), Y3, Y14
+ VPERM2I128 $0x13, Y7, Y11, Y12
+ VPERM2I128 $0x13, 224(BP), Y3, Y4
+ VPXOR 384(SI), Y0, Y0
+ VPXOR 416(SI), Y14, Y14
+ VPXOR 448(SI), Y12, Y12
+ VPXOR 480(SI), Y4, Y4
+ VMOVDQU Y0, 384(DI)
+ VMOVDQU Y14, 416(DI)
+ VMOVDQU Y12, 448(DI)
+ VMOVDQU Y4, 480(DI)
+ LEAQ 512(SI), SI
+ LEAQ 512(DI), DI
+ SUBQ $0x00000200, BX
+ JMP openAVX2MainLoop
+
+openAVX2MainLoopDone:
+ // Handle the various tail sizes efficiently
+ TESTQ BX, BX
+ JE openSSEFinalize
+ CMPQ BX, $0x80
+ JBE openAVX2Tail128
+ CMPQ BX, $0x00000100
+ JBE openAVX2Tail256
+ CMPQ BX, $0x00000180
+ JBE openAVX2Tail384
+ JMP openAVX2Tail512
+
+openSSEFinalize:
+ // Hash in the PT, AAD lengths
+ ADDQ ad_len+80(FP), R10
+ ADCQ src_len+56(FP), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+
+ // Final reduce
+ MOVQ R10, R13
+ MOVQ R11, R14
+ MOVQ R12, R15
+ SUBQ $-5, R10
+ SBBQ $-1, R11
+ SBBQ $0x03, R12
+ CMOVQCS R13, R10
+ CMOVQCS R14, R11
+ CMOVQCS R15, R12
+
+ // Add in the "s" part of the key
+ ADDQ 16(BP), R10
+ ADCQ 24(BP), R11
+
+ // Finally, constant time compare to the tag at the end of the message
+ XORQ AX, AX
+ MOVQ $0x00000001, DX
+ XORQ (SI), R10
+ XORQ 8(SI), R11
+ ORQ R11, R10
+ CMOVQEQ DX, AX
+
+ // Return true iff tags are equal
+ MOVB AX, ret+96(FP)
+ RET
+
+openSSETail16:
+ TESTQ BX, BX
+ JE openSSEFinalize
+
+ // We can safely load the CT from the end, because it is padded with the MAC
+ MOVQ BX, R9
+ SHLQ $0x04, R9
+ LEAQ ·andMask<>+0(SB), R13
+ MOVOU (SI), X12
+ ADDQ BX, SI
+ PAND -16(R13)(R9*1), X12
+ MOVO X12, 64(BP)
+ MOVQ X12, R13
+ MOVQ 72(BP), R14
+ PXOR X1, X12
+
+ // We can only store one byte at a time, since plaintext can be shorter than 16 bytes
+openSSETail16Store:
+ MOVQ X12, R8
+ MOVB R8, (DI)
+ PSRLDQ $0x01, X12
+ INCQ DI
+ DECQ BX
+ JNE openSSETail16Store
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ JMP openSSEFinalize
+
+openAVX2192:
+ VMOVDQA Y0, Y5
+ VMOVDQA Y14, Y9
+ VMOVDQA Y12, Y13
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VMOVDQA Y0, Y6
+ VMOVDQA Y14, Y10
+ VMOVDQA Y12, Y8
+ VMOVDQA Y4, Y2
+ VMOVDQA Y1, Y15
+ MOVQ $0x0000000a, R9
+
+openAVX2192InnerCipherLoop:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ DECQ R9
+ JNE openAVX2192InnerCipherLoop
+ VPADDD Y6, Y0, Y0
+ VPADDD Y6, Y5, Y5
+ VPADDD Y10, Y14, Y14
+ VPADDD Y10, Y9, Y9
+ VPADDD Y8, Y12, Y12
+ VPADDD Y8, Y13, Y13
+ VPADDD Y2, Y4, Y4
+ VPADDD Y15, Y1, Y1
+ VPERM2I128 $0x02, Y0, Y14, Y3
+
+ // Clamp and store poly key
+ VPAND ·polyClampMask<>+0(SB), Y3, Y3
+ VMOVDQA Y3, (BP)
+
+ // Stream for up to 192 bytes
+ VPERM2I128 $0x13, Y0, Y14, Y0
+ VPERM2I128 $0x13, Y12, Y4, Y14
+ VPERM2I128 $0x02, Y5, Y9, Y12
+ VPERM2I128 $0x02, Y13, Y1, Y4
+ VPERM2I128 $0x13, Y5, Y9, Y5
+ VPERM2I128 $0x13, Y13, Y1, Y9
+
+openAVX2ShortOpen:
+ // Hash
+ MOVQ ad_len+80(FP), R9
+ CALL polyHashADInternal<>(SB)
+
+openAVX2ShortOpenLoop:
+ CMPQ BX, $0x20
+ JB openAVX2ShortTail32
+ SUBQ $0x20, BX
+
+ // Load for hashing
+ ADDQ (SI), R10
+ ADCQ 8(SI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ ADDQ 16(SI), R10
+ ADCQ 24(SI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+
+ // Load for decryption
+ VPXOR (SI), Y0, Y0
+ VMOVDQU Y0, (DI)
+ LEAQ 32(SI), SI
+ LEAQ 32(DI), DI
+
+ // Shift stream left
+ VMOVDQA Y14, Y0
+ VMOVDQA Y12, Y14
+ VMOVDQA Y4, Y12
+ VMOVDQA Y5, Y4
+ VMOVDQA Y9, Y5
+ VMOVDQA Y13, Y9
+ VMOVDQA Y1, Y13
+ VMOVDQA Y6, Y1
+ VMOVDQA Y10, Y6
+ JMP openAVX2ShortOpenLoop
+
+openAVX2ShortTail32:
+ CMPQ BX, $0x10
+ VMOVDQA X0, X1
+ JB openAVX2ShortDone
+ SUBQ $0x10, BX
+
+ // Load for hashing
+ ADDQ (SI), R10
+ ADCQ 8(SI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+
+ // Load for decryption
+ VPXOR (SI), X0, X12
+ VMOVDQU X12, (DI)
+ LEAQ 16(SI), SI
+ LEAQ 16(DI), DI
+ VPERM2I128 $0x11, Y0, Y0, Y0
+ VMOVDQA X0, X1
+
+openAVX2ShortDone:
+ VZEROUPPER
+ JMP openSSETail16
+
+openAVX2320:
+ VMOVDQA Y0, Y5
+ VMOVDQA Y14, Y9
+ VMOVDQA Y12, Y13
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VMOVDQA Y0, Y6
+ VMOVDQA Y14, Y10
+ VMOVDQA Y12, Y8
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VMOVDQA Y14, Y7
+ VMOVDQA Y12, Y11
+ VMOVDQA Y4, Y15
+ MOVQ $0x0000000a, R9
+
+openAVX2320InnerCipherLoop:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y3
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y3
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y3
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y3
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x04, Y2, Y2, Y2
+ DECQ R9
+ JNE openAVX2320InnerCipherLoop
+ VMOVDQA ·chacha20Constants<>+0(SB), Y3
+ VPADDD Y3, Y0, Y0
+ VPADDD Y3, Y5, Y5
+ VPADDD Y3, Y6, Y6
+ VPADDD Y7, Y14, Y14
+ VPADDD Y7, Y9, Y9
+ VPADDD Y7, Y10, Y10
+ VPADDD Y11, Y12, Y12
+ VPADDD Y11, Y13, Y13
+ VPADDD Y11, Y8, Y8
+ VMOVDQA ·avx2IncMask<>+0(SB), Y3
+ VPADDD Y15, Y4, Y4
+ VPADDD Y3, Y15, Y15
+ VPADDD Y15, Y1, Y1
+ VPADDD Y3, Y15, Y15
+ VPADDD Y15, Y2, Y2
+
+ // Clamp and store poly key
+ VPERM2I128 $0x02, Y0, Y14, Y3
+ VPAND ·polyClampMask<>+0(SB), Y3, Y3
+ VMOVDQA Y3, (BP)
+
+ // Stream for up to 320 bytes
+ VPERM2I128 $0x13, Y0, Y14, Y0
+ VPERM2I128 $0x13, Y12, Y4, Y14
+ VPERM2I128 $0x02, Y5, Y9, Y12
+ VPERM2I128 $0x02, Y13, Y1, Y4
+ VPERM2I128 $0x13, Y5, Y9, Y5
+ VPERM2I128 $0x13, Y13, Y1, Y9
+ VPERM2I128 $0x02, Y6, Y10, Y13
+ VPERM2I128 $0x02, Y8, Y2, Y1
+ VPERM2I128 $0x13, Y6, Y10, Y6
+ VPERM2I128 $0x13, Y8, Y2, Y10
+ JMP openAVX2ShortOpen
+
+openAVX2Tail128:
+ // Need to decrypt up to 128 bytes - prepare two blocks
+ VMOVDQA ·chacha20Constants<>+0(SB), Y5
+ VMOVDQA 32(BP), Y9
+ VMOVDQA 64(BP), Y13
+ VMOVDQA 192(BP), Y1
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y1
+ VMOVDQA Y1, Y4
+ XORQ R9, R9
+ MOVQ BX, CX
+ ANDQ $-16, CX
+ TESTQ CX, CX
+ JE openAVX2Tail128LoopB
+
+openAVX2Tail128LoopA:
+ ADDQ (SI)(R9*1), R10
+ ADCQ 8(SI)(R9*1), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+
+openAVX2Tail128LoopB:
+ ADDQ $0x10, R9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x04, Y1, Y1, Y1
+ CMPQ R9, CX
+ JB openAVX2Tail128LoopA
+ CMPQ R9, $0xa0
+ JNE openAVX2Tail128LoopB
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 64(BP), Y13, Y13
+ VPADDD Y4, Y1, Y1
+ VPERM2I128 $0x02, Y5, Y9, Y0
+ VPERM2I128 $0x02, Y13, Y1, Y14
+ VPERM2I128 $0x13, Y5, Y9, Y12
+ VPERM2I128 $0x13, Y13, Y1, Y4
+
+openAVX2TailLoop:
+ CMPQ BX, $0x20
+ JB openAVX2Tail
+ SUBQ $0x20, BX
+
+ // Load for decryption
+ VPXOR (SI), Y0, Y0
+ VMOVDQU Y0, (DI)
+ LEAQ 32(SI), SI
+ LEAQ 32(DI), DI
+ VMOVDQA Y14, Y0
+ VMOVDQA Y12, Y14
+ VMOVDQA Y4, Y12
+ JMP openAVX2TailLoop
+
+openAVX2Tail:
+ CMPQ BX, $0x10
+ VMOVDQA X0, X1
+ JB openAVX2TailDone
+ SUBQ $0x10, BX
+
+ // Load for decryption
+ VPXOR (SI), X0, X12
+ VMOVDQU X12, (DI)
+ LEAQ 16(SI), SI
+ LEAQ 16(DI), DI
+ VPERM2I128 $0x11, Y0, Y0, Y0
+ VMOVDQA X0, X1
+
+openAVX2TailDone:
+ VZEROUPPER
+ JMP openSSETail16
+
+openAVX2Tail256:
+ VMOVDQA ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA Y0, Y5
+ VMOVDQA 32(BP), Y14
+ VMOVDQA Y14, Y9
+ VMOVDQA 64(BP), Y12
+ VMOVDQA Y12, Y13
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VMOVDQA Y4, Y7
+ VMOVDQA Y1, Y11
+
+ // Compute the number of iterations that will hash data
+ MOVQ BX, 224(BP)
+ MOVQ BX, CX
+ SUBQ $0x80, CX
+ SHRQ $0x04, CX
+ MOVQ $0x0000000a, R9
+ CMPQ CX, $0x0a
+ CMOVQGT R9, CX
+ MOVQ SI, BX
+ XORQ R9, R9
+
+openAVX2Tail256LoopA:
+ ADDQ (BX), R10
+ ADCQ 8(BX), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(BX), BX
+
+openAVX2Tail256LoopB:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ INCQ R9
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ CMPQ R9, CX
+ JB openAVX2Tail256LoopA
+ CMPQ R9, $0x0a
+ JNE openAVX2Tail256LoopB
+ MOVQ BX, R9
+ SUBQ SI, BX
+ MOVQ BX, CX
+ MOVQ 224(BP), BX
+
+openAVX2Tail256Hash:
+ ADDQ $0x10, CX
+ CMPQ CX, BX
+ JGT openAVX2Tail256HashEnd
+ ADDQ (R9), R10
+ ADCQ 8(R9), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(R9), R9
+ JMP openAVX2Tail256Hash
+
+openAVX2Tail256HashEnd:
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 64(BP), Y13, Y13
+ VPADDD Y7, Y4, Y4
+ VPADDD Y11, Y1, Y1
+ VPERM2I128 $0x02, Y0, Y14, Y6
+ VPERM2I128 $0x02, Y12, Y4, Y10
+ VPERM2I128 $0x13, Y0, Y14, Y8
+ VPERM2I128 $0x13, Y12, Y4, Y2
+ VPERM2I128 $0x02, Y5, Y9, Y0
+ VPERM2I128 $0x02, Y13, Y1, Y14
+ VPERM2I128 $0x13, Y5, Y9, Y12
+ VPERM2I128 $0x13, Y13, Y1, Y4
+ VPXOR (SI), Y6, Y6
+ VPXOR 32(SI), Y10, Y10
+ VPXOR 64(SI), Y8, Y8
+ VPXOR 96(SI), Y2, Y2
+ VMOVDQU Y6, (DI)
+ VMOVDQU Y10, 32(DI)
+ VMOVDQU Y8, 64(DI)
+ VMOVDQU Y2, 96(DI)
+ LEAQ 128(SI), SI
+ LEAQ 128(DI), DI
+ SUBQ $0x80, BX
+ JMP openAVX2TailLoop
+
+openAVX2Tail384:
+ // Need to decrypt up to 384 bytes - prepare six blocks
+ VMOVDQA ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA Y0, Y5
+ VMOVDQA Y0, Y6
+ VMOVDQA 32(BP), Y14
+ VMOVDQA Y14, Y9
+ VMOVDQA Y14, Y10
+ VMOVDQA 64(BP), Y12
+ VMOVDQA Y12, Y13
+ VMOVDQA Y12, Y8
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VMOVDQA Y4, 96(BP)
+ VMOVDQA Y1, 128(BP)
+ VMOVDQA Y2, 160(BP)
+
+ // Compute the number of iterations that will hash two blocks of data
+ MOVQ BX, 224(BP)
+ MOVQ BX, CX
+ SUBQ $0x00000100, CX
+ SHRQ $0x04, CX
+ ADDQ $0x06, CX
+ MOVQ $0x0000000a, R9
+ CMPQ CX, $0x0a
+ CMOVQGT R9, CX
+ MOVQ SI, BX
+ XORQ R9, R9
+
+openAVX2Tail384LoopB:
+ ADDQ (BX), R10
+ ADCQ 8(BX), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(BX), BX
+
+openAVX2Tail384LoopA:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y3
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y3
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ ADDQ (BX), R10
+ ADCQ 8(BX), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(BX), BX
+ INCQ R9
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y3
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y3
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x04, Y2, Y2, Y2
+ CMPQ R9, CX
+ JB openAVX2Tail384LoopB
+ CMPQ R9, $0x0a
+ JNE openAVX2Tail384LoopA
+ MOVQ BX, R9
+ SUBQ SI, BX
+ MOVQ BX, CX
+ MOVQ 224(BP), BX
+
+openAVX2Tail384Hash:
+ ADDQ $0x10, CX
+ CMPQ CX, BX
+ JGT openAVX2Tail384HashEnd
+ ADDQ (R9), R10
+ ADCQ 8(R9), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(R9), R9
+ JMP openAVX2Tail384Hash
+
+openAVX2Tail384HashEnd:
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD ·chacha20Constants<>+0(SB), Y6, Y6
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 32(BP), Y10, Y10
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 64(BP), Y13, Y13
+ VPADDD 64(BP), Y8, Y8
+ VPADDD 96(BP), Y4, Y4
+ VPADDD 128(BP), Y1, Y1
+ VPADDD 160(BP), Y2, Y2
+ VPERM2I128 $0x02, Y0, Y14, Y3
+ VPERM2I128 $0x02, Y12, Y4, Y7
+ VPERM2I128 $0x13, Y0, Y14, Y11
+ VPERM2I128 $0x13, Y12, Y4, Y15
+ VPXOR (SI), Y3, Y3
+ VPXOR 32(SI), Y7, Y7
+ VPXOR 64(SI), Y11, Y11
+ VPXOR 96(SI), Y15, Y15
+ VMOVDQU Y3, (DI)
+ VMOVDQU Y7, 32(DI)
+ VMOVDQU Y11, 64(DI)
+ VMOVDQU Y15, 96(DI)
+ VPERM2I128 $0x02, Y5, Y9, Y3
+ VPERM2I128 $0x02, Y13, Y1, Y7
+ VPERM2I128 $0x13, Y5, Y9, Y11
+ VPERM2I128 $0x13, Y13, Y1, Y15
+ VPXOR 128(SI), Y3, Y3
+ VPXOR 160(SI), Y7, Y7
+ VPXOR 192(SI), Y11, Y11
+ VPXOR 224(SI), Y15, Y15
+ VMOVDQU Y3, 128(DI)
+ VMOVDQU Y7, 160(DI)
+ VMOVDQU Y11, 192(DI)
+ VMOVDQU Y15, 224(DI)
+ VPERM2I128 $0x02, Y6, Y10, Y0
+ VPERM2I128 $0x02, Y8, Y2, Y14
+ VPERM2I128 $0x13, Y6, Y10, Y12
+ VPERM2I128 $0x13, Y8, Y2, Y4
+ LEAQ 256(SI), SI
+ LEAQ 256(DI), DI
+ SUBQ $0x00000100, BX
+ JMP openAVX2TailLoop
+
+openAVX2Tail512:
+ VMOVDQU ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA Y0, Y5
+ VMOVDQA Y0, Y6
+ VMOVDQA Y0, Y7
+ VMOVDQA 32(BP), Y14
+ VMOVDQA Y14, Y9
+ VMOVDQA Y14, Y10
+ VMOVDQA Y14, Y11
+ VMOVDQA 64(BP), Y12
+ VMOVDQA Y12, Y13
+ VMOVDQA Y12, Y8
+ VMOVDQA Y12, Y15
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VPADDD ·avx2IncMask<>+0(SB), Y2, Y3
+ VMOVDQA Y4, 96(BP)
+ VMOVDQA Y1, 128(BP)
+ VMOVDQA Y2, 160(BP)
+ VMOVDQA Y3, 192(BP)
+ XORQ CX, CX
+ MOVQ SI, R9
+
+openAVX2Tail512LoopB:
+ ADDQ (R9), R10
+ ADCQ 8(R9), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(R9), R9
+
+openAVX2Tail512LoopA:
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x0c, Y11, Y15
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ ADDQ (R9), R10
+ ADCQ 8(R9), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x07, Y11, Y15
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x04, Y11, Y11, Y11
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ VPALIGNR $0x0c, Y3, Y3, Y3
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ ADDQ 16(R9), R10
+ ADCQ 24(R9), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 32(R9), R9
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x0c, Y11, Y15
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x07, Y11, Y15
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x0c, Y11, Y11, Y11
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x04, Y2, Y2, Y2
+ VPALIGNR $0x04, Y3, Y3, Y3
+ INCQ CX
+ CMPQ CX, $0x04
+ JLT openAVX2Tail512LoopB
+ CMPQ CX, $0x0a
+ JNE openAVX2Tail512LoopA
+ MOVQ BX, CX
+ SUBQ $0x00000180, CX
+ ANDQ $-16, CX
+
+openAVX2Tail512HashLoop:
+ TESTQ CX, CX
+ JE openAVX2Tail512HashEnd
+ ADDQ (R9), R10
+ ADCQ 8(R9), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(R9), R9
+ SUBQ $0x10, CX
+ JMP openAVX2Tail512HashLoop
+
+openAVX2Tail512HashEnd:
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD ·chacha20Constants<>+0(SB), Y6, Y6
+ VPADDD ·chacha20Constants<>+0(SB), Y7, Y7
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 32(BP), Y10, Y10
+ VPADDD 32(BP), Y11, Y11
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 64(BP), Y13, Y13
+ VPADDD 64(BP), Y8, Y8
+ VPADDD 64(BP), Y15, Y15
+ VPADDD 96(BP), Y4, Y4
+ VPADDD 128(BP), Y1, Y1
+ VPADDD 160(BP), Y2, Y2
+ VPADDD 192(BP), Y3, Y3
+ VMOVDQA Y15, 224(BP)
+ VPERM2I128 $0x02, Y0, Y14, Y15
+ VPERM2I128 $0x13, Y0, Y14, Y14
+ VPERM2I128 $0x02, Y12, Y4, Y0
+ VPERM2I128 $0x13, Y12, Y4, Y12
+ VPXOR (SI), Y15, Y15
+ VPXOR 32(SI), Y0, Y0
+ VPXOR 64(SI), Y14, Y14
+ VPXOR 96(SI), Y12, Y12
+ VMOVDQU Y15, (DI)
+ VMOVDQU Y0, 32(DI)
+ VMOVDQU Y14, 64(DI)
+ VMOVDQU Y12, 96(DI)
+ VPERM2I128 $0x02, Y5, Y9, Y0
+ VPERM2I128 $0x02, Y13, Y1, Y14
+ VPERM2I128 $0x13, Y5, Y9, Y12
+ VPERM2I128 $0x13, Y13, Y1, Y4
+ VPXOR 128(SI), Y0, Y0
+ VPXOR 160(SI), Y14, Y14
+ VPXOR 192(SI), Y12, Y12
+ VPXOR 224(SI), Y4, Y4
+ VMOVDQU Y0, 128(DI)
+ VMOVDQU Y14, 160(DI)
+ VMOVDQU Y12, 192(DI)
+ VMOVDQU Y4, 224(DI)
+ VPERM2I128 $0x02, Y6, Y10, Y0
+ VPERM2I128 $0x02, Y8, Y2, Y14
+ VPERM2I128 $0x13, Y6, Y10, Y12
+ VPERM2I128 $0x13, Y8, Y2, Y4
+ VPXOR 256(SI), Y0, Y0
+ VPXOR 288(SI), Y14, Y14
+ VPXOR 320(SI), Y12, Y12
+ VPXOR 352(SI), Y4, Y4
+ VMOVDQU Y0, 256(DI)
+ VMOVDQU Y14, 288(DI)
+ VMOVDQU Y12, 320(DI)
+ VMOVDQU Y4, 352(DI)
+ VPERM2I128 $0x02, Y7, Y11, Y0
+ VPERM2I128 $0x02, 224(BP), Y3, Y14
+ VPERM2I128 $0x13, Y7, Y11, Y12
+ VPERM2I128 $0x13, 224(BP), Y3, Y4
+ LEAQ 384(SI), SI
+ LEAQ 384(DI), DI
+ SUBQ $0x00000180, BX
+ JMP openAVX2TailLoop
+
+DATA ·chacha20Constants<>+0(SB)/4, $0x61707865
+DATA ·chacha20Constants<>+4(SB)/4, $0x3320646e
+DATA ·chacha20Constants<>+8(SB)/4, $0x79622d32
+DATA ·chacha20Constants<>+12(SB)/4, $0x6b206574
+DATA ·chacha20Constants<>+16(SB)/4, $0x61707865
+DATA ·chacha20Constants<>+20(SB)/4, $0x3320646e
+DATA ·chacha20Constants<>+24(SB)/4, $0x79622d32
+DATA ·chacha20Constants<>+28(SB)/4, $0x6b206574
+GLOBL ·chacha20Constants<>(SB), RODATA|NOPTR, $32
+
+DATA ·avx2InitMask<>+0(SB)/8, $0x0000000000000000
+DATA ·avx2InitMask<>+8(SB)/8, $0x0000000000000000
+DATA ·avx2InitMask<>+16(SB)/8, $0x0000000000000001
+DATA ·avx2InitMask<>+24(SB)/8, $0x0000000000000000
+GLOBL ·avx2InitMask<>(SB), RODATA|NOPTR, $32
+
+DATA ·rol16<>+0(SB)/8, $0x0504070601000302
+DATA ·rol16<>+8(SB)/8, $0x0d0c0f0e09080b0a
+DATA ·rol16<>+16(SB)/8, $0x0504070601000302
+DATA ·rol16<>+24(SB)/8, $0x0d0c0f0e09080b0a
+GLOBL ·rol16<>(SB), RODATA|NOPTR, $32
+
+DATA ·rol8<>+0(SB)/8, $0x0605040702010003
+DATA ·rol8<>+8(SB)/8, $0x0e0d0c0f0a09080b
+DATA ·rol8<>+16(SB)/8, $0x0605040702010003
+DATA ·rol8<>+24(SB)/8, $0x0e0d0c0f0a09080b
+GLOBL ·rol8<>(SB), RODATA|NOPTR, $32
+
+DATA ·polyClampMask<>+0(SB)/8, $0x0ffffffc0fffffff
+DATA ·polyClampMask<>+8(SB)/8, $0x0ffffffc0ffffffc
+DATA ·polyClampMask<>+16(SB)/8, $0xffffffffffffffff
+DATA ·polyClampMask<>+24(SB)/8, $0xffffffffffffffff
+GLOBL ·polyClampMask<>(SB), RODATA|NOPTR, $32
+
+DATA ·avx2IncMask<>+0(SB)/8, $0x0000000000000002
+DATA ·avx2IncMask<>+8(SB)/8, $0x0000000000000000
+DATA ·avx2IncMask<>+16(SB)/8, $0x0000000000000002
+DATA ·avx2IncMask<>+24(SB)/8, $0x0000000000000000
+GLOBL ·avx2IncMask<>(SB), RODATA|NOPTR, $32
+
+DATA ·andMask<>+0(SB)/8, $0x00000000000000ff
+DATA ·andMask<>+8(SB)/8, $0x0000000000000000
+DATA ·andMask<>+16(SB)/8, $0x000000000000ffff
+DATA ·andMask<>+24(SB)/8, $0x0000000000000000
+DATA ·andMask<>+32(SB)/8, $0x0000000000ffffff
+DATA ·andMask<>+40(SB)/8, $0x0000000000000000
+DATA ·andMask<>+48(SB)/8, $0x00000000ffffffff
+DATA ·andMask<>+56(SB)/8, $0x0000000000000000
+DATA ·andMask<>+64(SB)/8, $0x000000ffffffffff
+DATA ·andMask<>+72(SB)/8, $0x0000000000000000
+DATA ·andMask<>+80(SB)/8, $0x0000ffffffffffff
+DATA ·andMask<>+88(SB)/8, $0x0000000000000000
+DATA ·andMask<>+96(SB)/8, $0x00ffffffffffffff
+DATA ·andMask<>+104(SB)/8, $0x0000000000000000
+DATA ·andMask<>+112(SB)/8, $0xffffffffffffffff
+DATA ·andMask<>+120(SB)/8, $0x0000000000000000
+DATA ·andMask<>+128(SB)/8, $0xffffffffffffffff
+DATA ·andMask<>+136(SB)/8, $0x00000000000000ff
+DATA ·andMask<>+144(SB)/8, $0xffffffffffffffff
+DATA ·andMask<>+152(SB)/8, $0x000000000000ffff
+DATA ·andMask<>+160(SB)/8, $0xffffffffffffffff
+DATA ·andMask<>+168(SB)/8, $0x0000000000ffffff
+DATA ·andMask<>+176(SB)/8, $0xffffffffffffffff
+DATA ·andMask<>+184(SB)/8, $0x00000000ffffffff
+DATA ·andMask<>+192(SB)/8, $0xffffffffffffffff
+DATA ·andMask<>+200(SB)/8, $0x000000ffffffffff
+DATA ·andMask<>+208(SB)/8, $0xffffffffffffffff
+DATA ·andMask<>+216(SB)/8, $0x0000ffffffffffff
+DATA ·andMask<>+224(SB)/8, $0xffffffffffffffff
+DATA ·andMask<>+232(SB)/8, $0x00ffffffffffffff
+GLOBL ·andMask<>(SB), RODATA|NOPTR, $240
+
+// func chacha20Poly1305Seal(dst []byte, key []uint32, src []byte, ad []byte)
+// Requires: AVX, AVX2, BMI2, CMOV, SSE2
+TEXT ·chacha20Poly1305Seal(SB), $288-96
+ MOVQ SP, BP
+ ADDQ $0x20, BP
+ ANDQ $-32, BP
+ MOVQ dst_base+0(FP), DI
+ MOVQ key_base+24(FP), R8
+ MOVQ src_base+48(FP), SI
+ MOVQ src_len+56(FP), BX
+ MOVQ ad_base+72(FP), CX
+ VZEROUPPER
+ VMOVDQU ·chacha20Constants<>+0(SB), Y0
+ VBROADCASTI128 16(R8), Y14
+ VBROADCASTI128 32(R8), Y12
+ VBROADCASTI128 48(R8), Y4
+ VPADDD ·avx2InitMask<>+0(SB), Y4, Y4
+
+ // Special optimizations, for very short buffers
+ CMPQ BX, $0x000000c0
+ JBE seal192AVX2
+ CMPQ BX, $0x00000140
+ JBE seal320AVX2
+
+ // For the general key prepare the key first - as a byproduct we have 64 bytes of cipher stream
+ VMOVDQA Y0, Y5
+ VMOVDQA Y0, Y6
+ VMOVDQA Y0, Y7
+ VMOVDQA Y14, Y9
+ VMOVDQA Y14, Y10
+ VMOVDQA Y14, Y11
+ VMOVDQA Y14, 32(BP)
+ VMOVDQA Y12, Y13
+ VMOVDQA Y12, Y8
+ VMOVDQA Y12, Y15
+ VMOVDQA Y12, 64(BP)
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VMOVDQA Y4, 96(BP)
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VMOVDQA Y1, 128(BP)
+ VPADDD ·avx2IncMask<>+0(SB), Y2, Y3
+ VMOVDQA Y2, 160(BP)
+ VMOVDQA Y3, 192(BP)
+ MOVQ $0x0000000a, R9
+
+sealAVX2IntroLoop:
+ VMOVDQA Y15, 224(BP)
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VMOVDQA 224(BP), Y15
+ VMOVDQA Y13, 224(BP)
+ VPADDD Y11, Y7, Y7
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ VPADDD Y3, Y15, Y15
+ VPXOR Y15, Y11, Y11
+ VPSLLD $0x0c, Y11, Y13
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y13, Y11, Y11
+ VPADDD Y11, Y7, Y7
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y3, Y15, Y15
+ VPXOR Y15, Y11, Y11
+ VPSLLD $0x07, Y11, Y13
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y13, Y11, Y11
+ VMOVDQA 224(BP), Y13
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ VPALIGNR $0x04, Y11, Y11, Y11
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x0c, Y3, Y3, Y3
+ VMOVDQA Y15, 224(BP)
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VMOVDQA 224(BP), Y15
+ VMOVDQA Y13, 224(BP)
+ VPADDD Y11, Y7, Y7
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ VPADDD Y3, Y15, Y15
+ VPXOR Y15, Y11, Y11
+ VPSLLD $0x0c, Y11, Y13
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y13, Y11, Y11
+ VPADDD Y11, Y7, Y7
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y3, Y15, Y15
+ VPXOR Y15, Y11, Y11
+ VPSLLD $0x07, Y11, Y13
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y13, Y11, Y11
+ VMOVDQA 224(BP), Y13
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x04, Y2, Y2, Y2
+ VPALIGNR $0x0c, Y11, Y11, Y11
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x04, Y3, Y3, Y3
+ DECQ R9
+ JNE sealAVX2IntroLoop
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD ·chacha20Constants<>+0(SB), Y6, Y6
+ VPADDD ·chacha20Constants<>+0(SB), Y7, Y7
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 32(BP), Y10, Y10
+ VPADDD 32(BP), Y11, Y11
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 64(BP), Y13, Y13
+ VPADDD 64(BP), Y8, Y8
+ VPADDD 64(BP), Y15, Y15
+ VPADDD 96(BP), Y4, Y4
+ VPADDD 128(BP), Y1, Y1
+ VPADDD 160(BP), Y2, Y2
+ VPADDD 192(BP), Y3, Y3
+ VPERM2I128 $0x13, Y12, Y4, Y12
+ VPERM2I128 $0x02, Y0, Y14, Y4
+ VPERM2I128 $0x13, Y0, Y14, Y0
+
+ // Clamp and store poly key
+ VPAND ·polyClampMask<>+0(SB), Y4, Y4
+ VMOVDQA Y4, (BP)
+
+ // Hash AD
+ MOVQ ad_len+80(FP), R9
+ CALL polyHashADInternal<>(SB)
+
+ // Can store at least 320 bytes
+ VPXOR (SI), Y0, Y0
+ VPXOR 32(SI), Y12, Y12
+ VMOVDQU Y0, (DI)
+ VMOVDQU Y12, 32(DI)
+ VPERM2I128 $0x02, Y5, Y9, Y0
+ VPERM2I128 $0x02, Y13, Y1, Y14
+ VPERM2I128 $0x13, Y5, Y9, Y12
+ VPERM2I128 $0x13, Y13, Y1, Y4
+ VPXOR 64(SI), Y0, Y0
+ VPXOR 96(SI), Y14, Y14
+ VPXOR 128(SI), Y12, Y12
+ VPXOR 160(SI), Y4, Y4
+ VMOVDQU Y0, 64(DI)
+ VMOVDQU Y14, 96(DI)
+ VMOVDQU Y12, 128(DI)
+ VMOVDQU Y4, 160(DI)
+ VPERM2I128 $0x02, Y6, Y10, Y0
+ VPERM2I128 $0x02, Y8, Y2, Y14
+ VPERM2I128 $0x13, Y6, Y10, Y12
+ VPERM2I128 $0x13, Y8, Y2, Y4
+ VPXOR 192(SI), Y0, Y0
+ VPXOR 224(SI), Y14, Y14
+ VPXOR 256(SI), Y12, Y12
+ VPXOR 288(SI), Y4, Y4
+ VMOVDQU Y0, 192(DI)
+ VMOVDQU Y14, 224(DI)
+ VMOVDQU Y12, 256(DI)
+ VMOVDQU Y4, 288(DI)
+ MOVQ $0x00000140, CX
+ SUBQ $0x00000140, BX
+ LEAQ 320(SI), SI
+ VPERM2I128 $0x02, Y7, Y11, Y0
+ VPERM2I128 $0x02, Y15, Y3, Y14
+ VPERM2I128 $0x13, Y7, Y11, Y12
+ VPERM2I128 $0x13, Y15, Y3, Y4
+ CMPQ BX, $0x80
+ JBE sealAVX2SealHash
+ VPXOR (SI), Y0, Y0
+ VPXOR 32(SI), Y14, Y14
+ VPXOR 64(SI), Y12, Y12
+ VPXOR 96(SI), Y4, Y4
+ VMOVDQU Y0, 320(DI)
+ VMOVDQU Y14, 352(DI)
+ VMOVDQU Y12, 384(DI)
+ VMOVDQU Y4, 416(DI)
+ SUBQ $0x80, BX
+ LEAQ 128(SI), SI
+ MOVQ $0x00000008, CX
+ MOVQ $0x00000002, R9
+ CMPQ BX, $0x80
+ JBE sealAVX2Tail128
+ CMPQ BX, $0x00000100
+ JBE sealAVX2Tail256
+ CMPQ BX, $0x00000180
+ JBE sealAVX2Tail384
+ CMPQ BX, $0x00000200
+ JBE sealAVX2Tail512
+
+ // We have 448 bytes to hash, but main loop hashes 512 bytes at a time - perform some rounds, before the main loop
+ VMOVDQA ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA Y0, Y5
+ VMOVDQA Y0, Y6
+ VMOVDQA Y0, Y7
+ VMOVDQA 32(BP), Y14
+ VMOVDQA Y14, Y9
+ VMOVDQA Y14, Y10
+ VMOVDQA Y14, Y11
+ VMOVDQA 64(BP), Y12
+ VMOVDQA Y12, Y13
+ VMOVDQA Y12, Y8
+ VMOVDQA Y12, Y15
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VPADDD ·avx2IncMask<>+0(SB), Y2, Y3
+ VMOVDQA Y4, 96(BP)
+ VMOVDQA Y1, 128(BP)
+ VMOVDQA Y2, 160(BP)
+ VMOVDQA Y3, 192(BP)
+ VMOVDQA Y15, 224(BP)
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VMOVDQA 224(BP), Y15
+ VMOVDQA Y13, 224(BP)
+ VPADDD Y11, Y7, Y7
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ VPADDD Y3, Y15, Y15
+ VPXOR Y15, Y11, Y11
+ VPSLLD $0x0c, Y11, Y13
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y13, Y11, Y11
+ VPADDD Y11, Y7, Y7
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y3, Y15, Y15
+ VPXOR Y15, Y11, Y11
+ VPSLLD $0x07, Y11, Y13
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y13, Y11, Y11
+ VMOVDQA 224(BP), Y13
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ VPALIGNR $0x04, Y11, Y11, Y11
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x0c, Y3, Y3, Y3
+ VMOVDQA Y15, 224(BP)
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VMOVDQA 224(BP), Y15
+ VMOVDQA Y13, 224(BP)
+ VPADDD Y11, Y7, Y7
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ VPADDD Y3, Y15, Y15
+ VPXOR Y15, Y11, Y11
+ VPSLLD $0x0c, Y11, Y13
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y13, Y11, Y11
+ VPADDD Y11, Y7, Y7
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y3, Y15, Y15
+ VPXOR Y15, Y11, Y11
+ VPSLLD $0x07, Y11, Y13
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y13, Y11, Y11
+ VMOVDQA 224(BP), Y13
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x04, Y2, Y2, Y2
+ VPALIGNR $0x0c, Y11, Y11, Y11
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x04, Y3, Y3, Y3
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x0c, Y11, Y15
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ SUBQ $0x10, DI
+ MOVQ $0x00000009, CX
+ JMP sealAVX2InternalLoopStart
+
+sealAVX2MainLoop:
+ VMOVDQU ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA Y0, Y5
+ VMOVDQA Y0, Y6
+ VMOVDQA Y0, Y7
+ VMOVDQA 32(BP), Y14
+ VMOVDQA Y14, Y9
+ VMOVDQA Y14, Y10
+ VMOVDQA Y14, Y11
+ VMOVDQA 64(BP), Y12
+ VMOVDQA Y12, Y13
+ VMOVDQA Y12, Y8
+ VMOVDQA Y12, Y15
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VPADDD ·avx2IncMask<>+0(SB), Y2, Y3
+ VMOVDQA Y4, 96(BP)
+ VMOVDQA Y1, 128(BP)
+ VMOVDQA Y2, 160(BP)
+ VMOVDQA Y3, 192(BP)
+ MOVQ $0x0000000a, CX
+
+sealAVX2InternalLoop:
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x0c, Y11, Y15
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+
+sealAVX2InternalLoopStart:
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ ADDQ 16(DI), R10
+ ADCQ 24(DI), R11
+ ADCQ $0x01, R12
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x07, Y11, Y15
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x04, Y11, Y11, Y11
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ VPALIGNR $0x0c, Y3, Y3, Y3
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ ADDQ 32(DI), R10
+ ADCQ 40(DI), R11
+ ADCQ $0x01, R12
+ LEAQ 48(DI), DI
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x0c, Y11, Y15
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x07, Y11, Y15
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x0c, Y11, Y11, Y11
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x04, Y2, Y2, Y2
+ VPALIGNR $0x04, Y3, Y3, Y3
+ DECQ CX
+ JNE sealAVX2InternalLoop
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD ·chacha20Constants<>+0(SB), Y6, Y6
+ VPADDD ·chacha20Constants<>+0(SB), Y7, Y7
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 32(BP), Y10, Y10
+ VPADDD 32(BP), Y11, Y11
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 64(BP), Y13, Y13
+ VPADDD 64(BP), Y8, Y8
+ VPADDD 64(BP), Y15, Y15
+ VPADDD 96(BP), Y4, Y4
+ VPADDD 128(BP), Y1, Y1
+ VPADDD 160(BP), Y2, Y2
+ VPADDD 192(BP), Y3, Y3
+ VMOVDQA Y15, 224(BP)
+
+ // We only hashed 480 of the 512 bytes available - hash the remaining 32 here
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 32(DI), DI
+ VPERM2I128 $0x02, Y0, Y14, Y15
+ VPERM2I128 $0x13, Y0, Y14, Y14
+ VPERM2I128 $0x02, Y12, Y4, Y0
+ VPERM2I128 $0x13, Y12, Y4, Y12
+ VPXOR (SI), Y15, Y15
+ VPXOR 32(SI), Y0, Y0
+ VPXOR 64(SI), Y14, Y14
+ VPXOR 96(SI), Y12, Y12
+ VMOVDQU Y15, (DI)
+ VMOVDQU Y0, 32(DI)
+ VMOVDQU Y14, 64(DI)
+ VMOVDQU Y12, 96(DI)
+ VPERM2I128 $0x02, Y5, Y9, Y0
+ VPERM2I128 $0x02, Y13, Y1, Y14
+ VPERM2I128 $0x13, Y5, Y9, Y12
+ VPERM2I128 $0x13, Y13, Y1, Y4
+ VPXOR 128(SI), Y0, Y0
+ VPXOR 160(SI), Y14, Y14
+ VPXOR 192(SI), Y12, Y12
+ VPXOR 224(SI), Y4, Y4
+ VMOVDQU Y0, 128(DI)
+ VMOVDQU Y14, 160(DI)
+ VMOVDQU Y12, 192(DI)
+ VMOVDQU Y4, 224(DI)
+
+ // and here
+ ADDQ -16(DI), R10
+ ADCQ -8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPERM2I128 $0x02, Y6, Y10, Y0
+ VPERM2I128 $0x02, Y8, Y2, Y14
+ VPERM2I128 $0x13, Y6, Y10, Y12
+ VPERM2I128 $0x13, Y8, Y2, Y4
+ VPXOR 256(SI), Y0, Y0
+ VPXOR 288(SI), Y14, Y14
+ VPXOR 320(SI), Y12, Y12
+ VPXOR 352(SI), Y4, Y4
+ VMOVDQU Y0, 256(DI)
+ VMOVDQU Y14, 288(DI)
+ VMOVDQU Y12, 320(DI)
+ VMOVDQU Y4, 352(DI)
+ VPERM2I128 $0x02, Y7, Y11, Y0
+ VPERM2I128 $0x02, 224(BP), Y3, Y14
+ VPERM2I128 $0x13, Y7, Y11, Y12
+ VPERM2I128 $0x13, 224(BP), Y3, Y4
+ VPXOR 384(SI), Y0, Y0
+ VPXOR 416(SI), Y14, Y14
+ VPXOR 448(SI), Y12, Y12
+ VPXOR 480(SI), Y4, Y4
+ VMOVDQU Y0, 384(DI)
+ VMOVDQU Y14, 416(DI)
+ VMOVDQU Y12, 448(DI)
+ VMOVDQU Y4, 480(DI)
+ LEAQ 512(SI), SI
+ SUBQ $0x00000200, BX
+ CMPQ BX, $0x00000200
+ JG sealAVX2MainLoop
+
+ // Tail can only hash 480 bytes
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ ADDQ 16(DI), R10
+ ADCQ 24(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 32(DI), DI
+ MOVQ $0x0000000a, CX
+ MOVQ $0x00000000, R9
+ CMPQ BX, $0x80
+ JBE sealAVX2Tail128
+ CMPQ BX, $0x00000100
+ JBE sealAVX2Tail256
+ CMPQ BX, $0x00000180
+ JBE sealAVX2Tail384
+ JMP sealAVX2Tail512
+
+sealSSETail:
+ TESTQ BX, BX
+ JE sealSSEFinalize
+
+ // We can only load the PT one byte at a time to avoid read after end of buffer
+ MOVQ BX, R9
+ SHLQ $0x04, R9
+ LEAQ ·andMask<>+0(SB), R13
+ MOVQ BX, CX
+ LEAQ -1(SI)(BX*1), SI
+ XORQ R15, R15
+ XORQ R8, R8
+ XORQ AX, AX
+
+sealSSETailLoadLoop:
+ SHLQ $0x08, R15, R8
+ SHLQ $0x08, R15
+ MOVB (SI), AX
+ XORQ AX, R15
+ LEAQ -1(SI), SI
+ DECQ CX
+ JNE sealSSETailLoadLoop
+ MOVQ R15, 64(BP)
+ MOVQ R8, 72(BP)
+ PXOR 64(BP), X1
+ MOVOU X1, (DI)
+ MOVOU -16(R13)(R9*1), X12
+ PAND X12, X1
+ MOVQ X1, R13
+ PSRLDQ $0x08, X1
+ MOVQ X1, R14
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ ADDQ BX, DI
+
+sealSSEFinalize:
+ // Hash in the buffer lengths
+ ADDQ ad_len+80(FP), R10
+ ADCQ src_len+56(FP), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+
+ // Final reduce
+ MOVQ R10, R13
+ MOVQ R11, R14
+ MOVQ R12, R15
+ SUBQ $-5, R10
+ SBBQ $-1, R11
+ SBBQ $0x03, R12
+ CMOVQCS R13, R10
+ CMOVQCS R14, R11
+ CMOVQCS R15, R12
+
+ // Add in the "s" part of the key
+ ADDQ 16(BP), R10
+ ADCQ 24(BP), R11
+
+ // Finally store the tag at the end of the message
+ MOVQ R10, (DI)
+ MOVQ R11, 8(DI)
+ RET
+
+seal192AVX2:
+ VMOVDQA Y0, Y5
+ VMOVDQA Y14, Y9
+ VMOVDQA Y12, Y13
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VMOVDQA Y0, Y6
+ VMOVDQA Y14, Y10
+ VMOVDQA Y12, Y8
+ VMOVDQA Y4, Y2
+ VMOVDQA Y1, Y15
+ MOVQ $0x0000000a, R9
+
+sealAVX2192InnerCipherLoop:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ DECQ R9
+ JNE sealAVX2192InnerCipherLoop
+ VPADDD Y6, Y0, Y0
+ VPADDD Y6, Y5, Y5
+ VPADDD Y10, Y14, Y14
+ VPADDD Y10, Y9, Y9
+ VPADDD Y8, Y12, Y12
+ VPADDD Y8, Y13, Y13
+ VPADDD Y2, Y4, Y4
+ VPADDD Y15, Y1, Y1
+ VPERM2I128 $0x02, Y0, Y14, Y3
+
+ // Clamp and store poly key
+ VPAND ·polyClampMask<>+0(SB), Y3, Y3
+ VMOVDQA Y3, (BP)
+
+ // Stream for up to 192 bytes
+ VPERM2I128 $0x13, Y0, Y14, Y0
+ VPERM2I128 $0x13, Y12, Y4, Y14
+ VPERM2I128 $0x02, Y5, Y9, Y12
+ VPERM2I128 $0x02, Y13, Y1, Y4
+ VPERM2I128 $0x13, Y5, Y9, Y5
+ VPERM2I128 $0x13, Y13, Y1, Y9
+
+sealAVX2ShortSeal:
+ // Hash aad
+ MOVQ ad_len+80(FP), R9
+ CALL polyHashADInternal<>(SB)
+ XORQ CX, CX
+
+sealAVX2SealHash:
+ // itr1 holds the number of bytes encrypted but not yet hashed
+ CMPQ CX, $0x10
+ JB sealAVX2ShortSealLoop
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ SUBQ $0x10, CX
+ ADDQ $0x10, DI
+ JMP sealAVX2SealHash
+
+sealAVX2ShortSealLoop:
+ CMPQ BX, $0x20
+ JB sealAVX2ShortTail32
+ SUBQ $0x20, BX
+
+ // Load for encryption
+ VPXOR (SI), Y0, Y0
+ VMOVDQU Y0, (DI)
+ LEAQ 32(SI), SI
+
+ // Now can hash
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ ADDQ 16(DI), R10
+ ADCQ 24(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 32(DI), DI
+
+ // Shift stream left
+ VMOVDQA Y14, Y0
+ VMOVDQA Y12, Y14
+ VMOVDQA Y4, Y12
+ VMOVDQA Y5, Y4
+ VMOVDQA Y9, Y5
+ VMOVDQA Y13, Y9
+ VMOVDQA Y1, Y13
+ VMOVDQA Y6, Y1
+ VMOVDQA Y10, Y6
+ JMP sealAVX2ShortSealLoop
+
+sealAVX2ShortTail32:
+ CMPQ BX, $0x10
+ VMOVDQA X0, X1
+ JB sealAVX2ShortDone
+ SUBQ $0x10, BX
+
+ // Load for encryption
+ VPXOR (SI), X0, X12
+ VMOVDQU X12, (DI)
+ LEAQ 16(SI), SI
+
+ // Hash
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(DI), DI
+ VPERM2I128 $0x11, Y0, Y0, Y0
+ VMOVDQA X0, X1
+
+sealAVX2ShortDone:
+ VZEROUPPER
+ JMP sealSSETail
+
+seal320AVX2:
+ VMOVDQA Y0, Y5
+ VMOVDQA Y14, Y9
+ VMOVDQA Y12, Y13
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VMOVDQA Y0, Y6
+ VMOVDQA Y14, Y10
+ VMOVDQA Y12, Y8
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VMOVDQA Y14, Y7
+ VMOVDQA Y12, Y11
+ VMOVDQA Y4, Y15
+ MOVQ $0x0000000a, R9
+
+sealAVX2320InnerCipherLoop:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y3
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y3
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y3
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y3
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x04, Y2, Y2, Y2
+ DECQ R9
+ JNE sealAVX2320InnerCipherLoop
+ VMOVDQA ·chacha20Constants<>+0(SB), Y3
+ VPADDD Y3, Y0, Y0
+ VPADDD Y3, Y5, Y5
+ VPADDD Y3, Y6, Y6
+ VPADDD Y7, Y14, Y14
+ VPADDD Y7, Y9, Y9
+ VPADDD Y7, Y10, Y10
+ VPADDD Y11, Y12, Y12
+ VPADDD Y11, Y13, Y13
+ VPADDD Y11, Y8, Y8
+ VMOVDQA ·avx2IncMask<>+0(SB), Y3
+ VPADDD Y15, Y4, Y4
+ VPADDD Y3, Y15, Y15
+ VPADDD Y15, Y1, Y1
+ VPADDD Y3, Y15, Y15
+ VPADDD Y15, Y2, Y2
+
+ // Clamp and store poly key
+ VPERM2I128 $0x02, Y0, Y14, Y3
+ VPAND ·polyClampMask<>+0(SB), Y3, Y3
+ VMOVDQA Y3, (BP)
+
+ // Stream for up to 320 bytes
+ VPERM2I128 $0x13, Y0, Y14, Y0
+ VPERM2I128 $0x13, Y12, Y4, Y14
+ VPERM2I128 $0x02, Y5, Y9, Y12
+ VPERM2I128 $0x02, Y13, Y1, Y4
+ VPERM2I128 $0x13, Y5, Y9, Y5
+ VPERM2I128 $0x13, Y13, Y1, Y9
+ VPERM2I128 $0x02, Y6, Y10, Y13
+ VPERM2I128 $0x02, Y8, Y2, Y1
+ VPERM2I128 $0x13, Y6, Y10, Y6
+ VPERM2I128 $0x13, Y8, Y2, Y10
+ JMP sealAVX2ShortSeal
+
+sealAVX2Tail128:
+ VMOVDQA ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA 32(BP), Y14
+ VMOVDQA 64(BP), Y12
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VMOVDQA Y4, Y1
+
+sealAVX2Tail128LoopA:
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(DI), DI
+
+sealAVX2Tail128LoopB:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ ADDQ 16(DI), R10
+ ADCQ 24(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 32(DI), DI
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x04, Y4, Y4, Y4
+ DECQ CX
+ JG sealAVX2Tail128LoopA
+ DECQ R9
+ JGE sealAVX2Tail128LoopB
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y5
+ VPADDD 32(BP), Y14, Y9
+ VPADDD 64(BP), Y12, Y13
+ VPADDD Y1, Y4, Y1
+ VPERM2I128 $0x02, Y5, Y9, Y0
+ VPERM2I128 $0x02, Y13, Y1, Y14
+ VPERM2I128 $0x13, Y5, Y9, Y12
+ VPERM2I128 $0x13, Y13, Y1, Y4
+ JMP sealAVX2ShortSealLoop
+
+sealAVX2Tail256:
+ VMOVDQA ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA ·chacha20Constants<>+0(SB), Y5
+ VMOVDQA 32(BP), Y14
+ VMOVDQA 32(BP), Y9
+ VMOVDQA 64(BP), Y12
+ VMOVDQA 64(BP), Y13
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VMOVDQA Y4, Y7
+ VMOVDQA Y1, Y11
+
+sealAVX2Tail256LoopA:
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(DI), DI
+
+sealAVX2Tail256LoopB:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ ADDQ 16(DI), R10
+ ADCQ 24(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 32(DI), DI
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ DECQ CX
+ JG sealAVX2Tail256LoopA
+ DECQ R9
+ JGE sealAVX2Tail256LoopB
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 64(BP), Y13, Y13
+ VPADDD Y7, Y4, Y4
+ VPADDD Y11, Y1, Y1
+ VPERM2I128 $0x02, Y0, Y14, Y3
+ VPERM2I128 $0x02, Y12, Y4, Y7
+ VPERM2I128 $0x13, Y0, Y14, Y11
+ VPERM2I128 $0x13, Y12, Y4, Y15
+ VPXOR (SI), Y3, Y3
+ VPXOR 32(SI), Y7, Y7
+ VPXOR 64(SI), Y11, Y11
+ VPXOR 96(SI), Y15, Y15
+ VMOVDQU Y3, (DI)
+ VMOVDQU Y7, 32(DI)
+ VMOVDQU Y11, 64(DI)
+ VMOVDQU Y15, 96(DI)
+ MOVQ $0x00000080, CX
+ LEAQ 128(SI), SI
+ SUBQ $0x80, BX
+ VPERM2I128 $0x02, Y5, Y9, Y0
+ VPERM2I128 $0x02, Y13, Y1, Y14
+ VPERM2I128 $0x13, Y5, Y9, Y12
+ VPERM2I128 $0x13, Y13, Y1, Y4
+ JMP sealAVX2SealHash
+
+sealAVX2Tail384:
+ VMOVDQA ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA Y0, Y5
+ VMOVDQA Y0, Y6
+ VMOVDQA 32(BP), Y14
+ VMOVDQA Y14, Y9
+ VMOVDQA Y14, Y10
+ VMOVDQA 64(BP), Y12
+ VMOVDQA Y12, Y13
+ VMOVDQA Y12, Y8
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VMOVDQA Y4, Y7
+ VMOVDQA Y1, Y11
+ VMOVDQA Y2, Y15
+
+sealAVX2Tail384LoopA:
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(DI), DI
+
+sealAVX2Tail384LoopB:
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y3
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y3
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x0c, Y14, Y3
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y14, Y0, Y0
+ VPXOR Y0, Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPADDD Y4, Y12, Y12
+ VPXOR Y12, Y14, Y14
+ VPSLLD $0x07, Y14, Y3
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y3, Y14, Y14
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x0c, Y9, Y3
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y9, Y5, Y5
+ VPXOR Y5, Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPADDD Y1, Y13, Y13
+ VPXOR Y13, Y9, Y9
+ VPSLLD $0x07, Y9, Y3
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y3, Y9, Y9
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x0c, Y10, Y3
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ VPADDD Y10, Y6, Y6
+ VPXOR Y6, Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPADDD Y2, Y8, Y8
+ VPXOR Y8, Y10, Y10
+ VPSLLD $0x07, Y10, Y3
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y3, Y10, Y10
+ ADDQ 16(DI), R10
+ ADCQ 24(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 32(DI), DI
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x04, Y2, Y2, Y2
+ DECQ CX
+ JG sealAVX2Tail384LoopA
+ DECQ R9
+ JGE sealAVX2Tail384LoopB
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD ·chacha20Constants<>+0(SB), Y6, Y6
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 32(BP), Y10, Y10
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 64(BP), Y13, Y13
+ VPADDD 64(BP), Y8, Y8
+ VPADDD Y7, Y4, Y4
+ VPADDD Y11, Y1, Y1
+ VPADDD Y15, Y2, Y2
+ VPERM2I128 $0x02, Y0, Y14, Y3
+ VPERM2I128 $0x02, Y12, Y4, Y7
+ VPERM2I128 $0x13, Y0, Y14, Y11
+ VPERM2I128 $0x13, Y12, Y4, Y15
+ VPXOR (SI), Y3, Y3
+ VPXOR 32(SI), Y7, Y7
+ VPXOR 64(SI), Y11, Y11
+ VPXOR 96(SI), Y15, Y15
+ VMOVDQU Y3, (DI)
+ VMOVDQU Y7, 32(DI)
+ VMOVDQU Y11, 64(DI)
+ VMOVDQU Y15, 96(DI)
+ VPERM2I128 $0x02, Y5, Y9, Y3
+ VPERM2I128 $0x02, Y13, Y1, Y7
+ VPERM2I128 $0x13, Y5, Y9, Y11
+ VPERM2I128 $0x13, Y13, Y1, Y15
+ VPXOR 128(SI), Y3, Y3
+ VPXOR 160(SI), Y7, Y7
+ VPXOR 192(SI), Y11, Y11
+ VPXOR 224(SI), Y15, Y15
+ VMOVDQU Y3, 128(DI)
+ VMOVDQU Y7, 160(DI)
+ VMOVDQU Y11, 192(DI)
+ VMOVDQU Y15, 224(DI)
+ MOVQ $0x00000100, CX
+ LEAQ 256(SI), SI
+ SUBQ $0x00000100, BX
+ VPERM2I128 $0x02, Y6, Y10, Y0
+ VPERM2I128 $0x02, Y8, Y2, Y14
+ VPERM2I128 $0x13, Y6, Y10, Y12
+ VPERM2I128 $0x13, Y8, Y2, Y4
+ JMP sealAVX2SealHash
+
+sealAVX2Tail512:
+ VMOVDQA ·chacha20Constants<>+0(SB), Y0
+ VMOVDQA Y0, Y5
+ VMOVDQA Y0, Y6
+ VMOVDQA Y0, Y7
+ VMOVDQA 32(BP), Y14
+ VMOVDQA Y14, Y9
+ VMOVDQA Y14, Y10
+ VMOVDQA Y14, Y11
+ VMOVDQA 64(BP), Y12
+ VMOVDQA Y12, Y13
+ VMOVDQA Y12, Y8
+ VMOVDQA Y12, Y15
+ VMOVDQA 192(BP), Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y4
+ VPADDD ·avx2IncMask<>+0(SB), Y4, Y1
+ VPADDD ·avx2IncMask<>+0(SB), Y1, Y2
+ VPADDD ·avx2IncMask<>+0(SB), Y2, Y3
+ VMOVDQA Y4, 96(BP)
+ VMOVDQA Y1, 128(BP)
+ VMOVDQA Y2, 160(BP)
+ VMOVDQA Y3, 192(BP)
+
+sealAVX2Tail512LoopA:
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), AX
+ MOVQ AX, R15
+ MULQ R10
+ MOVQ AX, R13
+ MOVQ DX, R14
+ MOVQ (BP), AX
+ MULQ R11
+ IMULQ R12, R15
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), AX
+ MOVQ AX, R8
+ MULQ R10
+ ADDQ AX, R14
+ ADCQ $0x00, DX
+ MOVQ DX, R10
+ MOVQ 8(BP), AX
+ MULQ R11
+ ADDQ AX, R15
+ ADCQ $0x00, DX
+ IMULQ R12, R8
+ ADDQ R10, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 16(DI), DI
+
+sealAVX2Tail512LoopB:
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x0c, Y11, Y15
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ ADDQ (DI), R10
+ ADCQ 8(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x07, Y11, Y15
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ VPALIGNR $0x04, Y14, Y14, Y14
+ VPALIGNR $0x04, Y9, Y9, Y9
+ VPALIGNR $0x04, Y10, Y10, Y10
+ VPALIGNR $0x04, Y11, Y11, Y11
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x0c, Y4, Y4, Y4
+ VPALIGNR $0x0c, Y1, Y1, Y1
+ VPALIGNR $0x0c, Y2, Y2, Y2
+ VPALIGNR $0x0c, Y3, Y3, Y3
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol16<>+0(SB), Y4, Y4
+ VPSHUFB ·rol16<>+0(SB), Y1, Y1
+ VPSHUFB ·rol16<>+0(SB), Y2, Y2
+ VPSHUFB ·rol16<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ ADDQ 16(DI), R10
+ ADCQ 24(DI), R11
+ ADCQ $0x01, R12
+ MOVQ (BP), DX
+ MOVQ DX, R15
+ MULXQ R10, R13, R14
+ IMULQ R12, R15
+ MULXQ R11, AX, DX
+ ADDQ AX, R14
+ ADCQ DX, R15
+ MOVQ 8(BP), DX
+ MULXQ R10, R10, AX
+ ADDQ R10, R14
+ MULXQ R11, R11, R8
+ ADCQ R11, R15
+ ADCQ $0x00, R8
+ IMULQ R12, DX
+ ADDQ AX, R15
+ ADCQ DX, R8
+ MOVQ R13, R10
+ MOVQ R14, R11
+ MOVQ R15, R12
+ ANDQ $0x03, R12
+ MOVQ R15, R13
+ ANDQ $-4, R13
+ MOVQ R8, R14
+ SHRQ $0x02, R8, R15
+ SHRQ $0x02, R8
+ ADDQ R13, R10
+ ADCQ R14, R11
+ ADCQ $0x00, R12
+ ADDQ R15, R10
+ ADCQ R8, R11
+ ADCQ $0x00, R12
+ LEAQ 32(DI), DI
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x0c, Y14, Y15
+ VPSRLD $0x14, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x0c, Y9, Y15
+ VPSRLD $0x14, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x0c, Y10, Y15
+ VPSRLD $0x14, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x0c, Y11, Y15
+ VPSRLD $0x14, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ VPADDD Y14, Y0, Y0
+ VPADDD Y9, Y5, Y5
+ VPADDD Y10, Y6, Y6
+ VPADDD Y11, Y7, Y7
+ VPXOR Y0, Y4, Y4
+ VPXOR Y5, Y1, Y1
+ VPXOR Y6, Y2, Y2
+ VPXOR Y7, Y3, Y3
+ VPSHUFB ·rol8<>+0(SB), Y4, Y4
+ VPSHUFB ·rol8<>+0(SB), Y1, Y1
+ VPSHUFB ·rol8<>+0(SB), Y2, Y2
+ VPSHUFB ·rol8<>+0(SB), Y3, Y3
+ VPADDD Y4, Y12, Y12
+ VPADDD Y1, Y13, Y13
+ VPADDD Y2, Y8, Y8
+ VPADDD Y3, Y15, Y15
+ VPXOR Y12, Y14, Y14
+ VPXOR Y13, Y9, Y9
+ VPXOR Y8, Y10, Y10
+ VPXOR Y15, Y11, Y11
+ VMOVDQA Y15, 224(BP)
+ VPSLLD $0x07, Y14, Y15
+ VPSRLD $0x19, Y14, Y14
+ VPXOR Y15, Y14, Y14
+ VPSLLD $0x07, Y9, Y15
+ VPSRLD $0x19, Y9, Y9
+ VPXOR Y15, Y9, Y9
+ VPSLLD $0x07, Y10, Y15
+ VPSRLD $0x19, Y10, Y10
+ VPXOR Y15, Y10, Y10
+ VPSLLD $0x07, Y11, Y15
+ VPSRLD $0x19, Y11, Y11
+ VPXOR Y15, Y11, Y11
+ VMOVDQA 224(BP), Y15
+ VPALIGNR $0x0c, Y14, Y14, Y14
+ VPALIGNR $0x0c, Y9, Y9, Y9
+ VPALIGNR $0x0c, Y10, Y10, Y10
+ VPALIGNR $0x0c, Y11, Y11, Y11
+ VPALIGNR $0x08, Y12, Y12, Y12
+ VPALIGNR $0x08, Y13, Y13, Y13
+ VPALIGNR $0x08, Y8, Y8, Y8
+ VPALIGNR $0x08, Y15, Y15, Y15
+ VPALIGNR $0x04, Y4, Y4, Y4
+ VPALIGNR $0x04, Y1, Y1, Y1
+ VPALIGNR $0x04, Y2, Y2, Y2
+ VPALIGNR $0x04, Y3, Y3, Y3
+ DECQ CX
+ JG sealAVX2Tail512LoopA
+ DECQ R9
+ JGE sealAVX2Tail512LoopB
+ VPADDD ·chacha20Constants<>+0(SB), Y0, Y0
+ VPADDD ·chacha20Constants<>+0(SB), Y5, Y5
+ VPADDD ·chacha20Constants<>+0(SB), Y6, Y6
+ VPADDD ·chacha20Constants<>+0(SB), Y7, Y7
+ VPADDD 32(BP), Y14, Y14
+ VPADDD 32(BP), Y9, Y9
+ VPADDD 32(BP), Y10, Y10
+ VPADDD 32(BP), Y11, Y11
+ VPADDD 64(BP), Y12, Y12
+ VPADDD 64(BP), Y13, Y13
+ VPADDD 64(BP), Y8, Y8
+ VPADDD 64(BP), Y15, Y15
+ VPADDD 96(BP), Y4, Y4
+ VPADDD 128(BP), Y1, Y1
+ VPADDD 160(BP), Y2, Y2
+ VPADDD 192(BP), Y3, Y3
+ VMOVDQA Y15, 224(BP)
+ VPERM2I128 $0x02, Y0, Y14, Y15
+ VPXOR (SI), Y15, Y15
+ VMOVDQU Y15, (DI)
+ VPERM2I128 $0x02, Y12, Y4, Y15
+ VPXOR 32(SI), Y15, Y15
+ VMOVDQU Y15, 32(DI)
+ VPERM2I128 $0x13, Y0, Y14, Y15
+ VPXOR 64(SI), Y15, Y15
+ VMOVDQU Y15, 64(DI)
+ VPERM2I128 $0x13, Y12, Y4, Y15
+ VPXOR 96(SI), Y15, Y15
+ VMOVDQU Y15, 96(DI)
+ VPERM2I128 $0x02, Y5, Y9, Y0
+ VPERM2I128 $0x02, Y13, Y1, Y14
+ VPERM2I128 $0x13, Y5, Y9, Y12
+ VPERM2I128 $0x13, Y13, Y1, Y4
+ VPXOR 128(SI), Y0, Y0
+ VPXOR 160(SI), Y14, Y14
+ VPXOR 192(SI), Y12, Y12
+ VPXOR 224(SI), Y4, Y4
+ VMOVDQU Y0, 128(DI)
+ VMOVDQU Y14, 160(DI)
+ VMOVDQU Y12, 192(DI)
+ VMOVDQU Y4, 224(DI)
+ VPERM2I128 $0x02, Y6, Y10, Y0
+ VPERM2I128 $0x02, Y8, Y2, Y14
+ VPERM2I128 $0x13, Y6, Y10, Y12
+ VPERM2I128 $0x13, Y8, Y2, Y4
+ VPXOR 256(SI), Y0, Y0
+ VPXOR 288(SI), Y14, Y14
+ VPXOR 320(SI), Y12, Y12
+ VPXOR 352(SI), Y4, Y4
+ VMOVDQU Y0, 256(DI)
+ VMOVDQU Y14, 288(DI)
+ VMOVDQU Y12, 320(DI)
+ VMOVDQU Y4, 352(DI)
+ MOVQ $0x00000180, CX
+ LEAQ 384(SI), SI
+ SUBQ $0x00000180, BX
+ VPERM2I128 $0x02, Y7, Y11, Y0
+ VPERM2I128 $0x02, 224(BP), Y3, Y14
+ VPERM2I128 $0x13, Y7, Y11, Y12
+ VPERM2I128 $0x13, 224(BP), Y3, Y4
+ JMP sealAVX2SealHash
diff --git a/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_generic.go b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_generic.go
new file mode 100644
index 0000000000..2ecc840fca
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_generic.go
@@ -0,0 +1,87 @@
+// 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 chacha20poly1305
+
+import (
+ "encoding/binary"
+
+ "golang.org/x/crypto/chacha20"
+ "golang.org/x/crypto/internal/alias"
+ "golang.org/x/crypto/internal/poly1305"
+)
+
+func writeWithPadding(p *poly1305.MAC, b []byte) {
+ p.Write(b)
+ if rem := len(b) % 16; rem != 0 {
+ var buf [16]byte
+ padLen := 16 - rem
+ p.Write(buf[:padLen])
+ }
+}
+
+func writeUint64(p *poly1305.MAC, n int) {
+ var buf [8]byte
+ binary.LittleEndian.PutUint64(buf[:], uint64(n))
+ p.Write(buf[:])
+}
+
+func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []byte) []byte {
+ ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize)
+ ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
+ if alias.InexactOverlap(out, plaintext) {
+ panic("chacha20poly1305: invalid buffer overlap of output and input")
+ }
+ if alias.AnyOverlap(out, additionalData) {
+ panic("chacha20poly1305: invalid buffer overlap of output and additional data")
+ }
+
+ var polyKey [32]byte
+ s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
+ s.XORKeyStream(polyKey[:], polyKey[:])
+ s.SetCounter(1) // set the counter to 1, skipping 32 bytes
+ s.XORKeyStream(ciphertext, plaintext)
+
+ p := poly1305.New(&polyKey)
+ writeWithPadding(p, additionalData)
+ writeWithPadding(p, ciphertext)
+ writeUint64(p, len(additionalData))
+ writeUint64(p, len(plaintext))
+ p.Sum(tag[:0])
+
+ return ret
+}
+
+func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ tag := ciphertext[len(ciphertext)-16:]
+ ciphertext = ciphertext[:len(ciphertext)-16]
+
+ var polyKey [32]byte
+ s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce)
+ s.XORKeyStream(polyKey[:], polyKey[:])
+ s.SetCounter(1) // set the counter to 1, skipping 32 bytes
+
+ p := poly1305.New(&polyKey)
+ writeWithPadding(p, additionalData)
+ writeWithPadding(p, ciphertext)
+ writeUint64(p, len(additionalData))
+ writeUint64(p, len(ciphertext))
+
+ ret, out := sliceForAppend(dst, len(ciphertext))
+ if alias.InexactOverlap(out, ciphertext) {
+ panic("chacha20poly1305: invalid buffer overlap of output and input")
+ }
+ if alias.AnyOverlap(out, additionalData) {
+ panic("chacha20poly1305: invalid buffer overlap of output and additional data")
+ }
+ if !p.Verify(tag) {
+ for i := range out {
+ out[i] = 0
+ }
+ return nil, errOpen
+ }
+
+ s.XORKeyStream(out, ciphertext)
+ return ret, nil
+}
diff --git a/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_noasm.go b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_noasm.go
new file mode 100644
index 0000000000..34e6ab1df8
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_noasm.go
@@ -0,0 +1,15 @@
+// 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 chacha20poly1305
+
+func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte {
+ return c.sealGeneric(dst, nonce, plaintext, additionalData)
+}
+
+func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ return c.openGeneric(dst, nonce, ciphertext, additionalData)
+}
diff --git a/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_test.go b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_test.go
new file mode 100644
index 0000000000..b38970d177
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_test.go
@@ -0,0 +1,268 @@
+// 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 chacha20poly1305
+
+import (
+ "bytes"
+ "crypto/cipher"
+ cryptorand "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ mathrand "math/rand"
+ "strconv"
+ "testing"
+)
+
+func TestVectors(t *testing.T) {
+ for i, test := range chacha20Poly1305Tests {
+ key, _ := hex.DecodeString(test.key)
+ nonce, _ := hex.DecodeString(test.nonce)
+ ad, _ := hex.DecodeString(test.aad)
+ plaintext, _ := hex.DecodeString(test.plaintext)
+
+ var (
+ aead cipher.AEAD
+ err error
+ )
+ switch len(nonce) {
+ case NonceSize:
+ aead, err = New(key)
+ case NonceSizeX:
+ aead, err = NewX(key)
+ default:
+ t.Fatalf("#%d: wrong nonce length: %d", i, len(nonce))
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ct := aead.Seal(nil, nonce, plaintext, ad)
+ if ctHex := hex.EncodeToString(ct); ctHex != test.out {
+ t.Errorf("#%d: got %s, want %s", i, ctHex, test.out)
+ continue
+ }
+
+ plaintext2, err := aead.Open(nil, nonce, ct, ad)
+ if err != nil {
+ t.Errorf("#%d: Open failed", i)
+ continue
+ }
+
+ if !bytes.Equal(plaintext, plaintext2) {
+ t.Errorf("#%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext)
+ continue
+ }
+
+ if len(ad) > 0 {
+ alterAdIdx := mathrand.Intn(len(ad))
+ ad[alterAdIdx] ^= 0x80
+ if _, err := aead.Open(nil, nonce, ct, ad); err == nil {
+ t.Errorf("#%d: Open was successful after altering additional data", i)
+ }
+ ad[alterAdIdx] ^= 0x80
+ }
+
+ alterNonceIdx := mathrand.Intn(aead.NonceSize())
+ nonce[alterNonceIdx] ^= 0x80
+ if _, err := aead.Open(nil, nonce, ct, ad); err == nil {
+ t.Errorf("#%d: Open was successful after altering nonce", i)
+ }
+ nonce[alterNonceIdx] ^= 0x80
+
+ alterCtIdx := mathrand.Intn(len(ct))
+ ct[alterCtIdx] ^= 0x80
+ if _, err := aead.Open(nil, nonce, ct, ad); err == nil {
+ t.Errorf("#%d: Open was successful after altering ciphertext", i)
+ }
+ ct[alterCtIdx] ^= 0x80
+ }
+}
+
+func TestRandom(t *testing.T) {
+ // Some random tests to verify Open(Seal) == Plaintext
+ f := func(t *testing.T, nonceSize int) {
+ for i := 0; i < 256; i++ {
+ var nonce = make([]byte, nonceSize)
+ var key [32]byte
+
+ al := mathrand.Intn(128)
+ pl := mathrand.Intn(16384)
+ ad := make([]byte, al)
+ plaintext := make([]byte, pl)
+ cryptorand.Read(key[:])
+ cryptorand.Read(nonce[:])
+ cryptorand.Read(ad)
+ cryptorand.Read(plaintext)
+
+ var (
+ aead cipher.AEAD
+ err error
+ )
+ switch len(nonce) {
+ case NonceSize:
+ aead, err = New(key[:])
+ case NonceSizeX:
+ aead, err = NewX(key[:])
+ default:
+ t.Fatalf("#%d: wrong nonce length: %d", i, len(nonce))
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ct := aead.Seal(nil, nonce[:], plaintext, ad)
+
+ plaintext2, err := aead.Open(nil, nonce[:], ct, ad)
+ if err != nil {
+ t.Errorf("Random #%d: Open failed", i)
+ continue
+ }
+
+ if !bytes.Equal(plaintext, plaintext2) {
+ t.Errorf("Random #%d: plaintext's don't match: got %x vs %x", i, plaintext2, plaintext)
+ continue
+ }
+
+ if len(ad) > 0 {
+ alterAdIdx := mathrand.Intn(len(ad))
+ ad[alterAdIdx] ^= 0x80
+ if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
+ t.Errorf("Random #%d: Open was successful after altering additional data", i)
+ }
+ ad[alterAdIdx] ^= 0x80
+ }
+
+ alterNonceIdx := mathrand.Intn(aead.NonceSize())
+ nonce[alterNonceIdx] ^= 0x80
+ if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
+ t.Errorf("Random #%d: Open was successful after altering nonce", i)
+ }
+ nonce[alterNonceIdx] ^= 0x80
+
+ alterCtIdx := mathrand.Intn(len(ct))
+ ct[alterCtIdx] ^= 0x80
+ if _, err := aead.Open(nil, nonce[:], ct, ad); err == nil {
+ t.Errorf("Random #%d: Open was successful after altering ciphertext", i)
+ }
+ ct[alterCtIdx] ^= 0x80
+ }
+ }
+ t.Run("Standard", func(t *testing.T) { f(t, NonceSize) })
+ t.Run("X", func(t *testing.T) { f(t, NonceSizeX) })
+}
+
+func benchmarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) {
+ b.ReportAllocs()
+ b.SetBytes(int64(len(buf)))
+
+ var key [32]byte
+ var nonce = make([]byte, nonceSize)
+ var ad [13]byte
+ var out []byte
+
+ var aead cipher.AEAD
+ switch len(nonce) {
+ case NonceSize:
+ aead, _ = New(key[:])
+ case NonceSizeX:
+ aead, _ = NewX(key[:])
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ out = aead.Seal(out[:0], nonce[:], buf[:], ad[:])
+ }
+}
+
+func benchmarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
+ b.ReportAllocs()
+ b.SetBytes(int64(len(buf)))
+
+ var key [32]byte
+ var nonce = make([]byte, nonceSize)
+ var ad [13]byte
+ var ct []byte
+ var out []byte
+
+ var aead cipher.AEAD
+ switch len(nonce) {
+ case NonceSize:
+ aead, _ = New(key[:])
+ case NonceSizeX:
+ aead, _ = NewX(key[:])
+ }
+ ct = aead.Seal(ct[:0], nonce[:], buf[:], ad[:])
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ out, _ = aead.Open(out[:0], nonce[:], ct[:], ad[:])
+ }
+}
+
+func BenchmarkChacha20Poly1305(b *testing.B) {
+ for _, length := range []int{64, 1350, 8 * 1024} {
+ b.Run("Open-"+strconv.Itoa(length), func(b *testing.B) {
+ benchmarkChaCha20Poly1305Open(b, make([]byte, length), NonceSize)
+ })
+ b.Run("Seal-"+strconv.Itoa(length), func(b *testing.B) {
+ benchmarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize)
+ })
+
+ b.Run("Open-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
+ benchmarkChaCha20Poly1305Open(b, make([]byte, length), NonceSizeX)
+ })
+ b.Run("Seal-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
+ benchmarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX)
+ })
+ }
+}
+
+func ExampleNewX() {
+ // key should be randomly generated or derived from a function like Argon2.
+ key := make([]byte, KeySize)
+ if _, err := cryptorand.Read(key); err != nil {
+ panic(err)
+ }
+
+ aead, err := NewX(key)
+ if err != nil {
+ panic(err)
+ }
+
+ // Encryption.
+ var encryptedMsg []byte
+ {
+ msg := []byte("Gophers, gophers, gophers everywhere!")
+
+ // Select a random nonce, and leave capacity for the ciphertext.
+ nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead())
+ if _, err := cryptorand.Read(nonce); err != nil {
+ panic(err)
+ }
+
+ // Encrypt the message and append the ciphertext to the nonce.
+ encryptedMsg = aead.Seal(nonce, nonce, msg, nil)
+ }
+
+ // Decryption.
+ {
+ if len(encryptedMsg) < aead.NonceSize() {
+ panic("ciphertext too short")
+ }
+
+ // Split nonce and ciphertext.
+ nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]
+
+ // Decrypt the message and check it wasn't tampered with.
+ plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("%s\n", plaintext)
+ }
+
+ // Output: Gophers, gophers, gophers everywhere!
+}
diff --git a/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_vectors_test.go b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_vectors_test.go
new file mode 100644
index 0000000000..76823d13e6
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/chacha20poly1305_vectors_test.go
@@ -0,0 +1,727 @@
+// 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 chacha20poly1305
+
+var chacha20Poly1305Tests = []struct {
+ plaintext, aad, key, nonce, out string
+}{
+ {
+ "",
+ "",
+ "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+ "070000004041424344454647",
+ "a0784d7a4716f3feb4f64e7f4b39bf04",
+ },
+ {
+ // https://tools.ietf.org/html/draft-irtf-cfrg-xchacha-01#appendix-A.3.1
+ "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e",
+ "50515253c0c1c2c3c4c5c6c7",
+ "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+ "404142434445464748494a4b4c4d4e4f5051525354555657",
+ "bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b4522f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff921f9664c97637da9768812f615c68b13b52ec0875924c1c7987947deafd8780acf49",
+ },
+ {
+ "1400000cebccee3bf561b292340fec60",
+ "00000000000000001603030010",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "2b487a2941bc07f3cc76d1a531662588ee7c2598e59778c24d5b27559a80d163",
+ },
+ {
+ "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "00000000000000000000000000",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "3f487a25aa70e9c8391763370569c9e83b7650dd1921c8b78869f241f25d2096c910b180930c5b8747fd90959fe8ca2dcadb4fa50fa1439f916b2301e1cc0810d6725775d3ab86721700f96e22709b0a7a8bef32627dd929b2dd3ba15772b669062bb558bc92e6c241a1d60d9f0035e80c335f854815fe1138ab8af653eab3e122135feeec7dfaba1cc24af82a2b7acccdd824899a7e03cc29c25be8a4f56a66673845b93bae1556f09dafc89a0d22af207718e2a6bb022e9d917597295992ea3b750cc0e7a7c3d33b23c5a8aeab45f5bb542f6c9e6c1747ae5a344aff483ba38577ad534b33b3abc7d284776ea33ed488c2a2475648a4fcda561745ea7787ed60f2368deb27c75adce6ff9b6cc6de1f5e72a741e2d59f64751b3ae482d714e0c90e83c671ff98ed611823afb39e6e5019a6ba548a2a72e829c7b7b4a101ac9deb90a25d3e0c50d22e1fc26c7c02296fa13c6d9c14767f68aaf46450a8d0fd5feb60d9d73c6e68623425b4984a79d619dd6bf896459aa77a681ec9c1a97f645e121f47779b051f8948a817f84d1f55da170d5bbbaf2f64e18b97ed3fd822db2819f523314f1e5ac72e8f69bbe6c87c22daddb0e1ac6790f8534071de2f258064b99789bfb165b065b8fe96f9127cd7dca9f7cb0368420f1e802faa3ca23792f2a5b93773dd405e71c320b211b54f7a26626b03c060e1ab87f32ac588abfa056ce090bd7c69913a700c80f325bfe824fa",
+ },
+ {
+ "0967de57eefe1aaa999b9b746d88a1a248000d8734e0e938c6aa87",
+ "e4f0a3a4f90a8250f8806aa319053e8d73c62f150e2f239563037e9cc92823ad18c65111d0d462c954cc6c6ed2aafb45702a5a7e597d13bd8091594ab97cf7d1",
+ "f2db28620582e05f00f31c808475ca3df1c20e340bf14828352499466d79295f",
+ "4349e2131d44dc711148dfe3",
+ "bd06cc144fdc0d8b735fa4452eabbf78fd4ad2966ea41a84f68da40ca2da439777bc2ba6c4ec2de0d003eb",
+ },
+ {
+ "c4c920fb52a56fe66eaa8aa3fa187c543e3db8e5c8094c4313dc4ed35dfc5821c5791d171e8cfe8d37883031a0ad",
+ "85deea3dc4",
+ "05ff881d1e151bab4ca3db7d44880222733fe62686f71ce1e4610f2ea19599a7",
+ "b34710f65aed442e4a40866b",
+ "b154452fb7e85d175dd0b0db08591565c5587a725cf22386922f5d27a01015aba778975510b38754b2182e24352f019b7ad493e1ed255906715644aec6e0",
+ },
+ {
+ "c4b337df5e83823900c6c202e93541cf5bc8c677a9aad8b8d87a4d7221e294e595cbc4f34e462d4e0def50f62491c57f598cf60236cfba0f4908816aea154f80e013732e59a07c668fcc5cb35d2232b7ae29b9e4f874f3417c74ab6689fae6690d5a9766fa13cd8adf293d3d4b70f4f999adde9121d1d29d467d04cf77ea398444d0ea3fe4b7c9c3e106002c76f4260fa204a0c3d5",
+ "72611bef65eb664f24ea94f4d5d3d88c9c9c6da29c9a1991c02833c4c9f6993b57b5",
+ "dd0f2d4bb1c9e5ca5aa5f38d69bc8402f7dbb7229857b4a41b3044d481b7655e",
+ "2bbca0910cc47ca0b8517391",
+ "83aa28d6d98901e2981d21d3758ae4db8cce07fe08d82ca6f036a68daa88a7dda56eeb38040c942bdda0fd2d369eec44bd070e2c9314992f68dc16989a6ac0c3912c378cf3254f4bae74a66b075e828df6f855c0d8a827ffed3c03582c12a9112eeb7be43dfe8bd78beb2d1e56678b99a0372531727cb7f2b98d2f917ec10de93fe86267100c20356e80528c5066688c8b7acba76e591449952343f663993d5b642e59eb0f",
+ },
+ {
+ "a9775b8e42b63335439cf1c79fe8a3560b3baebfdfc9ef239d70da02cea0947817f00659a63a8ee9d67fb1756854cc738f7a326e432191e1916be35f0b78d72268de7c0e180af7ee8aa864f2fc30658baa97f9edb88ace49f5b2a8002a8023925e9fa076a997643340c8253cf88ac8a221c190d94c5e224110cb423a4b65cca9046c1fad0483e1444c0680449148e7b20a778c56d5ae97e679d920c43eed6d42598cf05d10d1a15cd722a0686a871b74fea7cad45562bacf3bda937ac701bc218dac7e9d7d20f955429abdac21d821207febf4d54daea4898837035038bf71c66cef63e90f5d3e51f7fcfe18d41f38540a2c2958dacde16304e4b33da324030f1366f923c337",
+ "74ba3372d308910b5c9c3885f41252d57556",
+ "9cf77bd06a4ed8fb59349791b98ba40b6019611942f5768e8be2ee88477149e3",
+ "b928935c4c966c60fd6583c0",
+ "ec7fd64fd75b254961a2b7fc942470d8620f439258b871d0d00f58028b5e0bee5e139e8108ac439391465d6658f559b1df57aa21cf826ede1a28bc11af885e13eebfc009870928fae8abfdd943a60c54fca93f0502dc23d29c2fd5340f9bc0e6ef2a18b66ef627af95f796d5bbca50de22c8ec802da9397089b25c6ba5262468e3977b45dc112e51896c70731b0a52d7efec7c93b41995823436bf4b0c477ae79684407c9831b487928b2b8303caca752b3edf1f0598e15831155462706f94ef3fa3a9e5f937f37085afa9b4bbf939d275796a61b78f70597acfd25cd87f967021cd99328fc371b5eb5739869520657b30e4a5b0db7c8715cbe275dee78e719b357d3a9731f9eaba95986479bb2004a77822fc115a3d",
+ },
+ {
+ "b3d3128bce6bbf66fd78f1a18352bae56bfcdae18b65c379ee0aeb37ee54fba1270d2df578ec5b75654d16e89fd1cd0acda7ec580dafd2fbbabd32a8112d49383a762db2638928c8d63eb0750f7e7fdd256b35321b072dd5c45f7dd58cc60dc63d3b79a0c4a1689adf180fef968eccbcfa01ee15091ceacd7b67a3082db0ce6aeb470aafe87249c88b58b721e783dde184ccf68de8e05b6347fe6b74ae3adf9a81e9496a5c9332e7ebe908d26ce6b3f0b2a97e9a89d9fdd0d7694585a3241f240d698e69fcc050e7a959ba153f6d06f117848ba05d887134f1b6b994dad9b9e74247513e08a125b1fadfc7394dcd2a6451b504ae3e75e22f2b9bc405747dedb6c43ef4ccdf1a7edaf9451346123eaa63f3af113124f361508e255503a242b96680ae3360c8b13ac1f64d08088bb26b7f617cb0866f11d6fd362b00d86eba3fee68724e302388f119d6f92161ac8ce00d08919377a26974d99575b1032ff0f1976240c785c8b89e9eb2bf005e4be06b5371ffca14683fedfdb49e00e38ff27af1324177faf91599abd5990920797574eb743effdc7decda318ada1419cc8e0bfecf82f9c99792746c2b",
+ "7e8da4f3018f673f8e43bd7a1dee05f8031ec49129c361abbc2a434e9eaf791c3c1d0f3dad767d3bba3ab6d728bbcf2bd994bd03571eae1348f161e6a1da03ddf7121ba4",
+ "7ee32dd501dce849cd492f6e23324c1a4567bfceff9f11d1352bcb8615f1b093",
+ "8998e043d2961afa51ea262a",
+ "ba85e72af18cb5ba85a4a0d6c28b4ac1e5509a3a2fdb0e3255cbc559df5e6a661fc560c756a0264dd99b72c61c51a4b7ad56ca4c8ccb7e8edfc48ff3cceac5d1e8ac5fc87096adc4d0e9a27492857b17604c3a694cfe0e70b22df106c8f3c61f840bcd634964cdb571840e125e381e7dd3a0d97972e965f16f775fa4ce555124318290bf508beb7bd77e633042deb0e863631478fc3dc9122862b3c31264471bcce54e0b74040c8bafd481cf798f332e8940f1134d3027d6f28e771d15e154fc89c6c25fe18a5d312807cc2e623bb1bbb4f0b6ec71d009407eb54bb0759f03682f65d0da8812f84d8e97483f6a8d76a8417efcd9526444abba24288647609791578887ef49780b0b89f51b072cae81c5b5014463da3633dda105b82add0f9c2f065dca46eedd2928be2570493c79a996fa78ea6aec0996497fe2dc444432ade4eaa662ee2255f0f4b92d593288a8e3ffe7a15a10e9d33b0203af23f4c9fd2cfcb6160db63b52810869ff1e65423dbe2c4415884b9f8dec3c968e14cd74f323c89053a96111bc9ce59ec483832c49c53a648e5f0f797f53642ac60170c94b473f1f2e7d8a38e46460b81219b52081263027f74cbf63a75af3a7",
+ },
+ {
+ "68d5ba501e87994ef6bc8042d7c5a99693a835a4796ad044f0e536a0790a7ee1e03832fec0cb4cb688cdf85f92a1f526492acac2949a0684803c24f947a3da27db0c259bd87251603f49bfd1eab4f733dec2f5725cfcf6dc381ad57fbdb0a699bccc34943e86f47dcfb34eba6746ed4508e3b764dfad4117c8169785c63d1e8309531747d90cc4a8bf13622759506c613324c512d10629991dc01fe3fe3d6607907e4f698a1312492674707fc4dde0f701a609d2ac336cc9f38badf1c813f9599148c21b5bd4658249d5010db2e205b3880e863441f2fe357dab2645be1f9e5067616bc335d0457ea6468c5828910cb09f92e5e184e316018e3c464c5ce59cc34608867bd8cbfa7e1286d73a17e3ebb675d097f9b3adfa41ea408d46252a096b3290e70a5be1896d6760a87e439334b863ccb11679ab5763ebe4a9110eb37c4043634b9e44d40cab34b42977475e2faa2ae0c0a38b170776fbb0870a63044aa6679545ac6951579d0581144cdf43f60923b6acaecdb325c864acd2c7b01d6e18b2b3c41c041bb9099cce557b114b84350131e3cee4089648b5691065867e7d38314154355d0e3ef9dc9375eddef922df2a06ad0f0e4357c3ac672932e5a66b16e8bf4b45cd893ea91cb397faadb9d9d7bf86e6ceca3e9176a5baa98b6114a149d3ed8ea176cc4a9380e18d2d9b67045aedeb28b729ba2ece74d759d5ebfb1ebee8ac5f5e79aaf1f98b7f2626e62a81d315a98b3e",
+ "63b90dd89066ad7b61cc39497899a8f14399eace1810f5fe3b76d2501f5d8f83169c5ba602082164d45aad4df3553e36ef29050739fa067470d8c58f3554124bf06df1f27612564a6c04976059d69648ff9b50389556ad052e729563c6a7",
+ "7d5c4314a542aff57a454b274a7999dfdc5f878a159c29be27dabdfcf7c06975",
+ "aeb6159fa88bb1ffd51d036d",
+ "7597f7f44191e815a409754db7fea688e0105c987fa065e621823ea6dea617aed613092ad566c487cfa1a93f556615d2a575fb30ac34b11e19cd908d74545906f929dc9e59f6f1e1e6eaaabe182748ef87057ef7820ffcf254c40237d3ea9ff004472db783ed54b5a294a46cf90519bf89367b04fc01ce544c5bcdd3197eb1237923ce2c0c99921ca959c53b54176d292e97f6d9696ded6054711721aebda543e3e077c90e6f216cdc275b86d45603521c5aab24f08fd06833b0743c388382f941e19e0283ac7c4ef22383e1b9b08572882769c1382bab9ad127e7f3e09b5330b82d3e0c7d6f0df46edc93265999eef8e7afa0cb1db77df7accf5bff8631a320d146a5c751a637a80f627b0c9a41b44f09212f38c154226de02f4906ef34139bbeacc3f06739c8540e37334392d38ba1cbf4bc7debe77c09b35d2200216db15ed4389f43bfd8ae9bf76fd8243c3d869546e16b8e44a6cd1edbd2c58ef890b5a84cda889131e5cd9402ca4d8271052c6b4fe3f2dff54fb77bcb575c315b9109f90b14bc8e109919808a581c1809e2a188d29fd34ce639088a6683f641925f5b4b3529baa34e080bb47fb7ad9b43d0d67c9e6ae7cacb50527fa74e56d0c8b20149f5d332d686d48ebbe634c2b5d35fc84c69a5bcc93b93dedcf9fdf19a1fb9b75f6df9692d16f6c3490377a06294499e4b8ebeaa0cfd840bfa05fde21c0b5e94d13063b3f5da7b537caefe89069cfa9de9eb8f06e4d30125de64716f821bcc8279c0c7ea2e",
+ },
+ {
+ "89c1ee38b6697d0190c87a2aa756892ee09fca095df1e31aeedbda5750f604d9b8f2116e5b8f70ec57ea16fe419f2d213ef72b9be90eb5d7e98f2e398632123e2524ac80b31c6c0a07820848223569602d94fc16a3b1ed8c411bc6c74ed80573fcb1f3afce60b9d5e2c21d04f78665241b613abe12274a5343101a91e91f04e5d1f7959f574e743a10913e0817a32c320467f0178e3b6ad14b856234a4661a755eaf14b5fd88ef0e192e1631d14263d6a954ed388f5709dadc6c0f81d229f630d80be6d593d5e3ad03f9ded53c41abe595981d24ef27ffcc930e4d653743960f4e7ce4e251c88f55c16d2afdaed5e3446d00685c276728ba757520acb9b6bb0732a0e9836878d829e5022794d70ad8440a40a132a8c9ec1d3f0ccaf8c285fff425e9788d6150b74753dedb2ae8b36ff2f310249bd911b9181d8310e00810d42ef94cbb5a9d72a1f0507c1a382f892b23994fbe7360778b7efa9c5e03ac3231a57fecff1c5fa10caf1d26e84db0137049622ebcc3a64841a0e49fa390d1d43550c1346c20d578cff39fb7404fcab0982dde55f0849d312581d0c811a19d46f25e7a5e7e50d74d43760583c5cf335dfc11b2ec964f1dbbd0ed83e18f2027817ea2dffcf2b64a352c4fb8f11eeb4f1bfc01079251254d2112d103a1f12a2270cc026cbeb8b6f3e505abd62496253f93274625786b73997e449c1f35c742a593441252fcc845e1cef1b8f287dd311a0477407ce3b31661f7b2802c79c2d20d06e45f03aca4e47a959c6c1d7a9d377e1577fbf82a115921c3d94e3d9c204aa204a9a5b04d8a2be3269700a035371f4aaf1a42d92b9bfbee74492b106975b36d1e581d6ce2484f09e04fa91586c85f35e2a10f0d3c0afcb05327c1bc9d7429bbcc4627af8f76b86fc561844c2ae3810c84901ac09a1670ed3d31a9daa5d296",
+ "7219bd21a834d917f93a9b45647ec77102578bc2f2a132dfde6489b9095b4f7b740c9c1c4075333ab0ce7f14",
+ "a7f849b054982cc8a4c8e5e53e181feee79e0233e58882839892134ad582da7c",
+ "4c46854e9e101090b1436f90",
+ "ab2e189baf60886bed88eb751bf3560a8bd3cdb6ee621d8c18b5fb3aa418f350048ecf359a7d542daf7090ec8688c3b0fe85914aa49d83be4ae3396f7bdc48051afae6a97fca7b42c0bf612a42d3c79ef6aadceb57f5cfe8d67f89d49add0ea1ffd423da058297239e72a85fa6cd1d82e243a503b1b0e12d7510a9ee98d7921dae2754d7581e52acb8ab9e7f9df3c73410789115cef6ce7c937a5441ad4edf2b7a8c0c6d152d5a5909c4ce839d59594a6163364038c4c71a1507389717f61e2bda1ea66a83ef477762e7834ebcfaa8f2ee61ced1605ba1380108236e1763bf40af5259da07dd3e3d0fb2801868c2e7c839e318678687cbe33384e2ef5750a0a0e2d2e19e869a4277e32a315ed4de79357f6a12a8a25d5b18291316d9bf40dad2d05d1b523ade76650669c700a1c2965f4e51337aa5d45ec7b4981072779401d6d30ed69034053334bccb18425ac68460becf2aeccc75aacd3d6709f07ee10366ed848c8a54904af4ea71fc2117de133f01e1cc031f2a4d0779b997b82682433ee615202d5dfffba6c916f11a00551d56ffde8c36b303263e14adaf45b6eab0bedf344e5214ce52f071d2f40154d788c6870020791a03d2fd4ec5879d9026241954ed45cfddef4937ea3d0d45647f252be31411237983a1be340fc65ebab9a5620abb0e8d475af4e89e842e895eda0cbd283bb5d0bf20236c62d956de733d60ebceb42fc0c9adbf9b69f8d66551b0aca0e260625ad41cad75d752a234af7caf7902c2c5b62f04b6a8e019a6179d44feeb2ad5859ef1c45371e66f1af1fe0de63997266c290e27f0dd62185c53f81e0a50c296a51ace7c90d9cf0dda8b2d7e72a347f64c44262e2a544d1acc7bb05734dc1783bbc1903279092fe7fe434610aa95fc2ce5fc5ee45858f5e8337d8fcb0a468464becb1cef6b7e5ea48ba383ad8a406df9c581f1cac057d8711fcb",
+ },
+ {
+ "2dcfbb59975f217c445f95634d7c0250afe7d8316a70c47dba99ff94167ab74349729ce1d2bd5d161df27a6a6e7cba1e63924fcd03134abdad4952c3c409060d7ca2ee4e5f4c647c3edee7ad5aa1cbbd341a8a372ed4f4db1e469ee250a4efcc46de1aa52a7e22685d0915b7aae075defbff1529d40a04f250a2d4a046c36c8ca18631cb055334625c4919072a8ee5258efb4e6205525455f428f63aeb62c68de9f758ee4b8c50a7d669ae00f89425868f73e894c53ce9b964dff34f42b9dc2bb03519fbc169a397d25197cae5bc50742f3808f474f2add8d1a0281359043e0a395705fbc0a89293fa2a5ddfe6ae5416e65c0a5b4eb83320585b33b26072bc99c9c1948a6a271d64517a433728974d0ff4586a42109d6268f9961a5908d6f2d198875b02ae7866fff3a9361b41842a35dc9477ec32da542b706f8478457649ddfda5dfab1d45aa10efe12c3065566541ebdc2d1db6814826f0cc9e3642e813408df3ebaa3896bb2777e757dc3dbc1d28994a454fcb8d76bc5914f29cfc05dc89f8c734315def58d4d6b0b0136ccd3c05178155e30fcb9f68df9104dc96e0658fa899c0058818da5ec88a723558ae3a6f2f8f523e5af1a73a82ab16198c7ba8341568399d8013fc499e6e7ef61cb8654b48b88aa2a931dc2cdcf245686eed9c8355d620d5e91c1e878a9c7da655e3f29d9b7c3f44ad1c70890eb5f27ca28efff76420cd4e3cebd5c788536ddd365f7ad1dbb91588d58612e43b0460de9260d5f780a245bc8e1a83166df1f3a3506d742c268ab4fc10c6e04bca40295da0ff5420a199dd2fb36045215138c4a2a539ceccc382c8d349a81e13e848708947c4a9e85d861811e75d323896f6da3b2fa807f22bcfc57477e487602cf8e973bc925b1a19732b00d15d38675313a283bbaa75e6793b5af11fe2514bda3abe96cc19b0e58ddbe55e381ec58c31670fec1184d38bbf2d7cde0fcd29e907e780d30130b98e0c9eec44bcb1d0ed18dfda2a64adb523da3102eafe2bd3051353d8148491a290308ed4ec3fa5da5784b481e861360c3b670e256539f96a4c4c4360d0d40260049035f1cfdacb275e7fa847e0df531b466141ac9a3a16e7865947572e4ab732daec23aac6eed1256d796c4d58bf699f20aa4bbae461a16abbe9c1e9",
+ "33791b0d653fb72c2d88519b02bde85a7c51f99cfb4456dfa6f84a61e10b4a14846521",
+ "a0a7b73ca2fc9282a28acc036bd74d7f5cb2a146577a5c29dbc3963fe7ebfd87",
+ "eaa4d916d261676d632455be",
+ "c9a631de470fd04dcbf8ea9f4d8ac37c3988878b6381707ac2c91d3720edbb31576ba90731f433a5e13582aca2b3c76ae75ca8881a463ecfa789910d3a776a9ad4800521c6baa120b2f1afd10f32ef8da63f5b69f5e5fd88ee84bf66b0666b15d05c4050f5358a050b9d5cf1503719f56cd48ceba78f29efe2ae8092e37f5134df526831532f86ccb9339637e2c9e9b9036f83cc058fda23e826a188456e7fd3f4ee20f4e4a3221883fe3232b49db607b90a8956133ab95051c9ec33a908ea7e81a1bfa7bd06c09f0143d07bb23a3feeac7f0d7720269c93e2df19d03605828c8713b84d183c9a50954c12fe3b047511ad15ef03a63355520cbd224d06a34de67a671368e6a8f9feeefe48fc273764a8c69c00314e5d693f159cb5270544f3c4e1760b0529e3303ab308e9a6d03835a3a42aef2df5f7643696f707a574d1dcc676aeecdd9947ebe8c13bcf15d30b2d10d2cd95445a307c1d22d39450615ad38f9302c6eb9dc05764b0503d6a7eaff9feb94834853b47bc25660207be3e7c0e27cb3127b5402cb016396e5ff07ddc3df29861dd68a17f53bf660b23352b739d6da72381b8d19a9fc95da7efb79330a2b360dce4309860af429e3fd10cab235c4acc1d80d9e20d67019375bd161ab65648400f308815afe63cfc717f7d0eea150e687caac25b6603287d44dca4a7cc2f67c3bdd54450bd3170340253b03ba054ec003070eddf9c14fb9dc595e228e4968524900cb5d85af6d1e658a42d744e0e7eb6995023823a8dc33528c6715b2e1aa607782c8e1ddddad72026d657bf122ece8685f6e92236e809139325e4a3c069facf94c10b7896995bba01eb22c7b3a87ea2114a7649d7ed3e83d223e5e785c66a75119beab0968d3eaf0cbcc2d7ede95d024041e6db39a880ce3e19efea32fb89a40a2aae22f407e5fd615e51e48dbd50a8b4ec27ce95e2ba1928bf699d0418705482ed0ed7acc858dfbd690403c74667a88dd5221bb79940c6c4a268379c10343aaefb635982c14f33ad83d47ced9682961540bd4f75804d3d48ba8aa67fb2e3a1db83fbcbe57fec9e4ffb1b575e947f8bd8263c680357960e3a39382974774b5a013f2f8514b3c63c21dbfd314fd5d927d82ba616d76629ac018879f54ff84b5808e94af4fcfe1cf8845b65208ca5510b5b593ce6c109611652cd",
+ },
+ {
+ "c335b055b752e083554b5aa2cbb6556cfcace658d5c11b6b000256fd89e9b24c1e62a2d5b582580acdb2ad9869020465aeeabe83acd9eeacdc44aa652d5cb24bbe542073d6787ea32b2b3c942d40f9db2bb75ed7914c836d902dd2be89840948d82abbaea23952cd648e6191ce5b6cf912cad0a3165410a781e3650b676e5340980eee3b484008acce6a3e9dc5aa96d775677b8bbb8b323c6e9747d6069a169ea904d9f145e29d134cdbb0118647e8fbae638669efb9a55d50ed33568749f5304ece2193b0bfa6fc9a570d209ef61b4c59a2b5485b5aa6ab47d902cf23f7ff71c5210476e0aa727a01809b9f76b6ebcf58a018b3fbbe5f42976111ba58112b1d322f9312da068cdb86277bfcde66cb3607e3ea02a1494439aa56f302671f1f994eb3ab28b937043f5f7f3b3de50673ecea5dee8ba633c45089b852f0d772892525344ede6b521dcad15807b65e7ba348d891d47fc498cf4d50223d2794c64db9fa9b9766edb430be0c38746ab317b38ba9870a6d1fdabb70fcf89790bfe449b97fe01f6c94502aa0889f0a3bb6bdc65f44d1cd64ab88d4a7806b373f5080f9cf60183cf4686694f0059e2bbc5cf21ba0c3e8046e70d815f1444c3094cc29632c429f20aa06b49b0b52c6c7aeb8e34f7bcb53e93c2cfe2d704a5d0416876742c90762730d160e1869d5e0178dc366098ebaf2cae6f1f7563b555a52dcc194a5c8f718d50d27ee76fcce8e8991f4921fae85ea9476e1eab1364403120698b7ce8fd0a49cf79213f360a17cf1950f104494fad80adcc3bb1207bf250d57dcdce6ac8082a312959672361363cc227310b66ee8c04aab7b5cb33a81c0915e9c770a1cfaae2e8f44a0c65703927977a22fe58aef2f366b8be9a50da9376b46ae7562a82391386831febf359039ac326891bc58c0f2c34bdb6858859fc3cb4e392df65cbe2ec4f02c8425bcbdd1ee2562ab7d229d406d79a9c6fe4889c996c2f68d1fb5bbe3a5e867caa4249b934afd3ec71fdb088c54b15252f9dc1b909e121dbdc7d8a16cc00836652dd1f877ce363eed11467966f7ccb8f1a8d48146e69e04ad76a51937ad4f9cda209451eeca90dbdbd65441ce20fabfc8ce400fb4de136154b87a8b65c92740e9bb91d78521b261f806a2c6279c85ef6ac5fe1ea3117ff7c9f9832fc2aa6fab660082eb22344c1a3befe0628b6551f62a5014cd6194c42b8d475a50f2c9fb58c97e43ebb29005ed7fe54f0a4aa10074f1154152a9067d364dd7863fa082976a00db55b26b5ba0ea40eff48b90",
+ "f5ff810a41d4b34751e9942970d4c9f26b33f24689a4b1e4449b243490afc485af468ff01a42376b2bcb949b9f5e8d0b917f511a",
+ "a74271c184a82cb074c14b131fd91eb05870cb7c73c9e511ec8140bfe2f34089",
+ "2403fe689e239c2ed261b381",
+ "af9be893d5fd23aab42e6a2e59a8e7cb13d4f543db02af87cb0802bc1af7c717cd0093cc8244994cf21189146922b69927ffd5745e57118bea07a6afe7c21d952c13ab636b3c2e461dc9ffb3ae701175360156338be94b1fa7115799831019455cfaf5114010fe45f8fb9c77ec50fe06f2c5a32423edccb3b2210ee1200a78e1a3130c567542377827586ca8cf0c14c19fa1449a2cce9c039bb441b04e9c0a3f9a743b31c828032174fcdb7c894349aa68f5adf97dfe9294d24e6b5fed95eb994397883f58487bf5c57b0aea5268be7cee9efeab370f89805ebe5373ab2e93658fc078955ccf68b554dd5605005751ee8531c35ca5336a5d0ce273370c0dc9307779b86e96d2d1daf2620d67d43e1fb7800ccf250ca3c02eb74047c1d2a2bc7f29fff8320301694b80d0fd975f834337d00d5f0e4215044d52aa4ca21e6a9d7e03f186d7cdd5c48e3765dc926fb0a46bb0f05c50d9f69c9c507527a60366b7dc251aae1d6bb0d9c73735dcfab959f6fd4382fe2a1f6ad07affb0601bb9040f81b55a48f6a6c5f8ac4a2acc2b0c9a6c439198f7926460695fa11e0b0b017e39de5cf0d5d5f84d972b5eee7b5d1e0343b5485cd84b92ad892e5b23f3e803f5b363f2398c11c15be9f13e59922b0d49902dc8483fb142850b4226da2fb84e9b434a34f6bb67f575a9e57fde3354bc3077a876e260311bb2481bb139aa9af55df5074749fe532d7b8a554218a90cc7e7ac69db280bae5d55a174dfc8d325b9909a8da1016d4e162fe5ba70cf8726cdf291f5e47083d9929cd5e32021cbfd982fd0975f6f9baf4322b553cb3174b11c007559879f308419ff9e4e18eee8d3640cec8aea082b90f69cf3c7676c28af0265c24c91cd58a06513198892ce6ce1ab3ee9ac0a2e937b973a9cac06a039a54f8d994c13d42c59187f677352e5feb32a417aebec4d852b2595e7e67450e06dbd183279e3b63022a3813b37257b085bf8454d6890875a2950d20210a8df4f9da746722f62687e92f0e9efc3e5d526d65ccfbcc042fcac7964dbe147932c73924bdcdf62f9eae58d29e8567ffed90048bcf0566b952e986efeae4c477944af18bd243c3eccf8d88c06d07279adad037450cb8547a8aa0a74223f4851747c803cb21a2dd027e7080aed75038cdcecbc4639d87763cdd41829a1b72cedf0d722b180d0d492a5740ea7607b95f3201df352fb1ab28149124d2df5d5ec106867897b537302c3431402348f94d28eebc701ae1b49d10adedea38f1654fbc48885e59e6e6dfd413c6b5a97d8c35dfb07a6cdefe317bf61cf91",
+ },
+ {
+ "4aba5a776ace38b6e2578f0007e770d264e39c49f588ca3547ad2888365e3a811994f8836330394587c8458eb0b6611499fd5d8e8527c3cdd4ec550b4a8f8c632384e786b420cb3be911c999c72aad60270aefad31b27a069ecf11e95e9d4c81213308d554d3103de4d9d6ab04830c2b8dfbd8bead52c44c21d5357f72810193b5096809dc7846c1521c6c569f78812c735aea21acaf6dce84a24df7234e8ad857f3e1346b27f5bd436113e2da950e4deff96e9ba8db692c7db723a105ae795da15b910c8286cac6e7dda8c172b70f61b07dfd58596684d61da8772356f180f74c1103ce97cd947eab3d401df44f7fa4cc7cfc25e280fc002873237e64a375b0b4797f4b4613c9f150090f44588ee8250ae44aec6546ec8dba0f0c1eb281cf66fa4eb141617b32b28441f6ddcfdf02d9c34cc62893b2b64dc2c26b74433adb3e888c7fea07b19c8cf39269c2716b9c35b7625d4a141397d6d5034b193d2657c6b2d6b0ba874c467adeaf3d501ad985d13be21c4ff6b326cbb671e4f4973bba49116a0399b6491394f850e4122969e4644c00b442b3da0d6a4bf25ee22d182b3f822fd83878ebcc713cb183651a67ca66677ea81b58b685a3a8e385d5fbb0147ddfecb558d881c914324c794db443b31bc15c361912bbbcba9e418f99f2a416d190cb29684df27c7f3ff6ccf339800efbdc4514ee00d1a89f12373804db4fd66c1affd467f251e73147b3248033327b0f7790fd7861a51773dd4f78b89e4e24b94df9203f4a077091bb9411eec78dfe3e1dfbb67ea1cdf17e1d6936bbb75b74055495449e9cb52f5749404610cd444fea3f0568e0d35a5ef0c395ab7bf0208044b5c4e2517911a9c351efd31f33220972287253fbccb1eb8f46960a36b68a7a6b4f5cbdc86d668bbf555fde8881e7faa9594da425ff8fb54526bf7cdc4af64899530561c06bed7fc04c5d48cd4542779e901bc48fab79d4d13850ad8247f51b9afa7d5a656ada25b6376d837cb0fa1b4016dfcfc158a39290f43f133b352ed52fab2f951509bacb41284fbdd849d8185fb7e7200f8ab2a07ef2b3b927e18e568dbeeba2c7a66e08cebdc6a6069ebe6656a586652f3905ae2bb867529af6a827b494c97b3a378408f44aaefbe86c613e11e7a44020a9ee4b62569dfc4c462300daec7b1424ff1c1849ca1332367470475c14877cbe76c820cc651c18ab3f18852b93994f93b568dc7f7b0eb5f07ffc4c9384c851fa9071c6f68ddea1ccf627f889c0471c76aff9f52b07ab1b86a7671a2b2f6b25c0ddebb66ac95737bf7e2f493f7665b5265eaa5166556cecfdd3062802724ec24f3978b903d0f0c24e1f0b8d967142bccfed0d354279223f4c28684e9ab611e9ef89a3f25993b5a8b3c0354931780501651236a78b58e7d7814f251b053605f4c0a8e7193b9cc1ee5cf7378e6f3c8fd44ec57bd91e62b09fb1d6bab60cbfabcc6792e6a32ea7918a9ec9180d05a7e1546d5d2d8bbfde2a71b4e427c0a4d28d0b6473ae",
+ "921a401db90935c60edda8624a0590d5c46eff3522e35de2872f6f9394e24126fd8143b68a797c995624fba0298b75eef974",
+ "6a4d35ae03bf277f587da4541dcddf92bbd906dff45d5ff23c0f60ae53b062be",
+ "231b5780fedfb06d724450b3",
+ "ba40968282d98849b19d867f8b564ea5a81d657516099362926bca4cb6e9ae02719d10c8061f53008c727a0eeea5e1e36c9e55c117e9434e213316c96840231a1e356b254a9981d4a6ca3c66cfc61018bcaade1a4486506559e6aa3a86bac980d391d835fd5ded98d10f1394d84bf1bbf2cd3397890d704154802f7864ecc753db782fd3d19213ae65ace4770e1bacf32d61c6730aa5adcab4d7e2e437888c11c29abba4890a17a00f67a53b660becd94092df0598df5ac57326f6860593a519e28bd4a39f6481e1a4748881fd5f0456a3cd9f28d1d1e78dc64030cbd8fdb2c5abdab3f13d6ccccd187e71e989f8c486929efcdbf2a763effa95af62db5cef95e9081b818275c69267022fda4b7fdb8c650b491a785b03d4d0186625962b6326ec3f4e176373da4dc1f83a14815adf82c6bffa7c6967d77528d0249754bb4d17656bc4a89449b16152a4a1aea7eb0054a8892f271138971507d2f3b237ba5b620f444544e4a8c2b1ab4f9168762c27478c9f776c47ee2e9ff05bfa35ed127f0cabe7cc053640bb8aa01f8359b74bf89ef43ca94c48fcd201eae39d1835957eeccd6b3a852f4e1bbfef9a469f42c764481ff8408fe5871afeeae7676b58f4202199aad50a596626dff97c8e60d750cc59da9f595ce12ce9afdce14481cb1e39994de8fe4cce07845110d6703dc59d34734e93e9e57e1c52d61f44143a2d290220a4bad5098d098ee65ea4b6757d8a9bf5485aa3d697a7826d4a285186f5da10eff707566c23c6a15033365bcb498c44487c72d96402d1834753fdbf86770239761f03e0dc8963766441da99c0813e4f1df5a1d018c8799861a396562eb24ce305ca15f4022d83ea3c56b68d9a7ceac4742ec0ce50f4d36273df26005ec2b051fa071b319be2d8a5ed26eb75bc1ea83761b8454db234d15d84d6706cd178981c1f156e6d28f774aee3e9a4fade022e71b52b50aa532b8bc7fe464f22d6eb169c69671875d614e987658820c2f584a4fea3008afdcbb646dba3d69020fbf503f121be3480344db23efdda0d255aa058c3ff66abd3a5fe35db977521608bba7eddae72ae801f4fbb12a1de4133039e046ceb8db87e465e5ede1d79a08c857d59076d7ff858942c31e15cbbdae6fc15c3f9545a0825d6ff8583c0aba8a7d143d27b93f6caefb98c0d83bd8715abcab2a49087f55a9daf9090eacdf45be08ad80b5df5070e1719f68c4cc8f8711083f0f7823a09ec092f22df95fe9e95114fdf82a3f6eed0bfc9c0aa65222609442776154a474dbc9e662cd5dce66846572e52417ee5d7eb59287d07ef60a9537fe1f85c7fa74fe84dea0da235ac7574335e6649b54a6bd33397df4bf4a7976c4ab868aa702766d2bc8d2c82c2d1c2653fc8428b8d1e61852ac185a3a0b416dbcf8eb54c44967ff43c44f2b32c6d4a9dbf2c2f3a587b430aef50f0375cdb4c1b319ac9aca486d9bb321141b065f52f7b6decaf1985531ca7bbc3772a561eb1efb8a6297075920bc432131a5b211bf25e35fa31e12833bc77a9de14c7",
+ },
+ {
+ "6c0056937faf1023032df1e2bfacbbc58bb022eba25ffa020d4eb26f0caf0678af5d0b2f0c1b520f4843f107f0adcc7b5dee66ff4d61025bafb4cabb64d133132e3e423a599549a1d83aa8c8e774444462aa44b00b460bbafad5755ea6a872d4e6b40e3f4957e0229288ea79fc2ebe5fd9020fe4481a9f42ef14a196bd136aa3c779e311d0c333624c1ddc484c9aa7259cb609e4d0a826c0bdc7567adac01da23900b30ac4e66c100348584fe200747eb67e6287268947e3509d5d2b5d7bcd977b80a13f660d4f6956a8b938a82db75eab19e5d2a22cb5f3c9131e278eebbe096b5f49d16c983ac240f3fbe821b247cccb2c9e6e59546122677f49f56a07fed56647a6d3e0e09520d49009f54250c10e7c607cd5b4ddf81b5c4110c6490e9baf56418236211856f5a85feaebafacf92c0c7501c052f9dbae3beb7484f90f334f50b68571cedc67763b5161ebfd5a1709cf18c92112a4cf4d8f43d1895204d8a2ba5e14883a7bff75cc6060cabb77d38a909daca2417befd1bfc05a11c432b47f90c807ca4306400f67a0d92218adaca84a584a8bd4395c93f9b6a4bde9583c79204444634a8473b1244cd33cf980e443d82ecfac672b3f60e2e41ecb3c5a445d9e88c0e90c339a31806e6d79ee52bdc6808c73e8b7b24899966664d3c1a9305f31f0483e24e36fa451dc1d3f2eda05af6678971e2bdfb7c1461c9407c5c466f6b5af34d992a37de3809a22ae75275ddba0f4f9cbd4b18c1acd212192e587889a36bd73c860f0abe08bcd8f00f5ecdb95e1d560b586eccf530df0e5f3776d8dae2a01768bf1226b7ceffa7ce4e75879c82dd97db3c64c06d33cebc6b35854618355d80e46fa79c3e9743fce5b974723c421a077e7ec7dba286881dbc1d53d442a1552700fcb33f83f73c69a0a0ebdcf2f5d461649c4d0712c514ded268a31509f83c1ae4ff4a68e676d29727be641aa4487c08d4b90ff78e24c6508d69759751a1a23690ec9f8763621e8b107295b4bb01bd9fcacd8748e24d996fa70ef6f8b0992f4185bec8e920d7643159f9f604fba394b6611bff435998b2f097a9e948430899c8c752a1e83a061983f00f88ebb32da214399167932a1a83c1b47d09f77593b03cf6521520583ea4483e2d33e14ad60584676d1791779b532c085d238df0d3bae735d0078e0eabd63cc90a2e13d023983780afc8f83b1c14437937c16a1b7c41414c48cf4ae49587ad9fa5b16fc949a749e96032248c4667f58e295f999590dae1d99a2cbe3fa45bcf4a1d3f0356d64d40367f64b2c5cca843e5f7dd7b88a85d52328a00622e6c317879607bc036c9006d38652ffe21c83207c00f8348a7d0aaea5aab4c89077df170de6d41052641726eb6925cd85a9ee01a9e636346340e209ea96d17b0eb0921b96662ce9cb430fb6ac348331dd7133875769bbbba99dc49333950e4145a15ddb0789c4d2ccd38878080ca9e57ddc6cd5452790eec45482f8e990392e319609391fce0beba19463a9a00d8f1de9fbf22f23821de7d69fdfbf3019ed61aff79acfc5a6ba663a1e10da2b9ff7149aea43bd6c61a543008402309df0924de72c1cacd2d6120cf422e61fc1de345cc8771934d8be77d9437a09e06a9b2d51c849fd9a200fa714328d34f36b684f33df6968b827df916a599a4bc3367814fec21198e2213ff653cd2a463892966c72ffd42a26b3bb91",
+ "0d55dcd08b54e58916f622f81761ef6a2e19b167ac47d3",
+ "e42e1d6d44138f3d2bf12c951f454686f18d590fd30057405b5a3dc2b317fa97",
+ "1e46a7486c5a03fd6758d938",
+ "fd3c1fac10cc82e49235fd57f5aea0ee7a7bd6d539b138d4b3fb623aee591615c1a61228ef9673113a3a90a3687a12d4c6367d5f7bc67d422fdc4106455084d79c2c42c5e86368dd164bcbce7925bfffe7d96c13a2f49aac8e9d1ada3554e3fdc21aab00455a0f33b0c1fdea91b3588e7ad301bfccf9940027332fbdf966463491f7a33c093e0a13831ea9d2183294f89f414cf7b5876af04fa68d594430194429df74fa5915394427259e832bc545c13400aef6cf16620d48280798a6e49773c9316d79fa1dc758e54cde2e2cdb856092d83f4e9b698385cb976fd6cc2538abe055273a5b34a784182ea5e7d3ac9019a05de5e5afe4308a7ed2d363cd50ed6a52df1c616e4a82f607ced768445d13ae4884f2ae1f9fd8313924e8a1a8a23905c92eb231f638dfa6f4cb27bbb9844e05afbbe2ca4d1a3b3a5b371bf33c9ab6f82a7387d61cf8bf662097624145a983839b0cb9f4bd07556800b4054fb3d0bac94f44bcc9b4ac49c39f5571fac4e02ff09f08b3ed5add4bf8bba934e9feb773c0590b45c45fa036382f3fe9782ad19107d4630321e414b7b442b64f18fdd5219039e5740f34b3ce8925d1afe8a39e35ce8db086060bab63b9720700499f82db19a62897c6d845389461260303f9cf2bc7235a898b4620c2191ef05604a5c8c783d58009533a86b27c12b0772635d34ac53993ccf174c9087073e5e69b26c0c3d9f768507ac4d4e2af847b65e3a6e1b7a6dafb0aefc190871cdae6c60f0b1d6137c351d4cb211870791cf4cb8af2ea446f6401eb9ec8a5bcebccce898d1dfb13454df6b35b81ed6d7637e6e261e004080c60944f3a08e8e5fc7e2e4939e7c2607c8cf07d1d10883ba3ad43e2611826f245df571857ae0a7a867df9659f2082c19f94ce400132e48c7f8de2b102c7f83ba5cd1e785597a0ba0d73bb81bba0c00300d4bcd6ec25fb73105a46122873bfa729c0979d8d314ab7ea52391aabab513dbfd1cf01c2990c0a3612f4511c2bcf0f5a07e659a881a7f99c3f1fc4a46e66904427fe26a4a80a904c047d090c861a075c0ae4e29bfbc18b9620aaa42237f4c6fa76ee7491ee638ab5f1cf0b440759828e1ec519679efc776eb1468999a00f667e87199ad6891e98b95fb682e02517b024a6bb803ed23c944010cb7bad0733eccc12d6ab6030c6e88d510ce92e2f98fdcfaa1e37e41fbfb4e99589c0e8efbefd40473db42b3a73b57b22a2f8c9bdaab16831f1b117dd83a77dd01ee8d0c2e92203adb670f4fd65e618823ad196220d70e014c1aafd8863797c61c16382c2600062683ed3a180c70891717c52da15191b02f25d1715ebf33a5e6037092421989c942082f4b836423cc3e976c9bcda185de36f06265dfc250a27d2de0bc48c73b3bff704f3b386f962522f572108458bdb283c6ab3fd33b3ac13a406268fd5d97e17db9c0f780b4b2a8f761d15a4d8b3a0cd73357ecf4d26a6492ee069f19325823ef50bcb2f73326719a57b67eeef506fe8915a1b1ba1a637592268257b91e9c7c5d33cdd947967efc1952005d82ccef9a3ad7ef8ffbb6b658983d64c51242ba53f8f8963245b87a25aa9324c527e53f8c11d55f30aab598401589acd13f090541b3b057b162190f27910718b02a6b8ddbb8ca6cf40bf0d2848f4b76341bd5e78f476862bcdbe2d1bac84c0566fb45b21388221ecd8483d99fe603646b1a9f38a49230cf4dbe5d7883d73eece01bf",
+ },
+ {
+ "04892b94c65685f2eba438322b29bf8439938590d3e0eb10a29e279d356cb439f6dfcdbc3552af21f7e753221012a649a52bda780bc589ae63b04b981dffd113df9fcf14f17e35e865880a769bb1bf40dc99b9e85e4296c1f2e1590fe02b22bfcaf2d4bb7009a4d692ae4c2d5f0b6d3ca526240368bac55b9b1e6a7b498d3b137f0fcfef1873c5aa2111d7811d45bdc26be1c5d49b8a2f36a999b1f226ec06a5fbd59514485abe696c96ea89dba74b4688101a239b495944e30b3609f73caff3114407599ec5c30a5bad933655de7dddef97018ae15acec46504cd5d417c5052c057ac5f1c6f69781cfdae71db2b4fcac35054a4aa22681027356d68b2bdba721466d130d53ba8f23857631382b2de450232e9ad5551bd7c872ae439e79eabfb057d2bdab8d4ccf02b3003ade2e1f3e514dc92692e4fe5b579c9ee6067995b6c168647ce5a13be8543c23326a3260bb7029d2030ec05e565ced3c5366d20a283a6e95201fd108640d2b96676df712de20e4e12fa53f85f22cb24583844fabcebe40eece11e7221f12c88670bf994ed08e2000236f86258c386b0fccbaab8b68ec6a26fe41491d540193c4c12d1391ab3391de9317f41f505f1f1d09ca9862a6f289a533d2b297d4465c956360371ea3c8ed36e0d1563120654e3a2fd69cd6c9267bfcf92e84cd64e162c84199d6e552b42c33857264b5d7a2e007797cde32934a3f8c68b459cd95bc85e7466ccc9910e8dca65b315c32e43c3a5da908904c42cfc8ab74126919ceeef1054bbdae6ca67b02f1ac5f24808b5eee24577e609a3e3935a24b9ebc1a8dad1fc96abe26012928f2d5782755f3763427dda28867d0b1ad830d3c3f17b9ec278346e5a9480ed23ad44a523a4dd86e65a610ee0de1afab64ace7a3b4918fdc14c6b1ce0ec0903994da9bcf18643d7e0a4e6c08200bb394a89b385d2cb829417eeb0f7dab9fa7306a330f82973cf0917b5ca99b585d2ff0e8584e050077467f5245ecfdd5942e4fc72dc26e5ab2ffc61f996167e68168cee9a6d3ea1e1a696060465e35da8c75a1aa380004faffcb0a992c627fbdcb4e97721271802cdaf08d214ec2fbcb389d75709d7a6b9d35662661c8961f93d4a705e7188613f3769114c55400809cadf60d3b6068c8a5ceef078785171b59be1140c6a754ba1de5ced349df63d67d59d3a8ca3c716ffb506772d57e9e3f2caf7fe346c4ad64aa6c37e43b9bbaa8f58e51bfbac31fa6137728f8e5b728025697e5ad5c8301f6ff39eb2ad595d3cb24257adee88a84fbf1ade4d7550cd9ab94bf48e1424ae83184c35c5a5920157d45805c2e0ad129fc7f0ec3c41b9d6fa04cb8918ef379b0783d1cc2863cd80382585fa05320ca4f9fd90353e490b384ed6c166c6f802cd7bd39aa43667246e8da96992db7537d472c709b01114e95febaac5b1a3c77e1e9a18c2d180e63f0d8fa89f6a1ed63e909e4741af5c2a0e47d4d3f8779b7696358f58060f3f461cceeebb390c92779d30bfdedf1b08ed62dcc05a545bd0ea915f42976e81dd8a50cc4689d8d8007508bf53e7da5bd43c3894968cf0677681c6b818353af6bf8ac205139add1310e5d363ccadbfa0eaf735808325e7f9a6aeb1bee3ebb4a27576a88811859c216b6f84371c43d8063a0d87bd326eb6d81c6896ff534ba2c9c14a51d2cfedf33a5c787279bb4a7ff65706b389756a6191d2f791254233ee047d40d64c2dca878a42f903fd4382f39a89a723fe11848fe37b2008be53f7c2d037981d6462a4eea49df1a2e074957afd3c9dfb4d218a309cab395afe301ccf",
+ "67b5eccb1790babc2dab5e0d1ff3871c3024177d45a2ae",
+ "259603e1c3af3fd0ce3257eb627b02e0c0a48ea2f175de3d8c36570a445e5369",
+ "e14de73c4b17581a7e0d0649",
+ "33522e67ef932da5fa8abe628b51f3abd5049951dbc982ea95b7769652d4830c588fa45e3fcff094c8602b9008d7b2f9bf6c1c4a8cfb515401c7c44a7ec42ccb967722a710199e121a41160b1ec581507e9bd2e2e506b10c4b5a8d6977435aa08e27504957cd49e756e1574c4ccbbdde937de35128b7ee3455d2e665c596c2e97c253c94e405f85eb5de84874c099b4a97eb8f492d28f2e4bc64b228dd5984e76ca08376d7f1355ba8e0fa60fca96635075417d8b436278e0fb91e3bfc7d61ca8c7407086933c061b2d318f46f352099e1d317d6c44098539d1d2c1b7894db668e7a82ff991864fae236570cc420a4229883f1e2242d05aa07e175bc6abe11cc643cf1786a4456a2de8c066fb1a70fe387f149ffbe8cca7b110e256fd0c09b1d3bd7381cfa82fa700c8db1e79809ccf75ea52d0b349264557046e8703a191ddaace00ccfc513db5e78810eaac0a99d7bb1a5725e722d4e595216a0e12f3a7aab2e623ea9e1dad06169914bcd51b643016fea7dc3f2743b1e65877f1fd5581bee5ef206d86494a587ec8462a170746fcedb2c9f99090674ee687382711b4610ddac599732453dc063518aa36f5b4129098fb9fddc02eb8f8cfc2fdf0d904ef4d6d06014f977b29d0e9aab4044ce9c662a18b1a8db1ceea97854e90704430fe9b1046b221b27ac79054fcc68c3abd6fab7da66e255ff0cbd0506c852e961e619615c944cd9a05c25abb63742f5da7bd9939feb0f2f2208c8ce82f551a9d4d70e935dad018e3e4e6998e39670221601c3e34716ba75eb4e2fdf53c4d471c444330514986de45cf44d77f793c17e36a271fc65e6bf08943aef4c66547dc310c7a430e3fe7a54898de48f69f282f52bbdc4daabdb325cec7ab66fce1aea4e2fd932dc1a316c821f5220ea437447feae2fa478adade7cd515a27d8c132d0299b3ca1bc8516c9d9e7c65c38c238c69f03e104eb42a29cacc8d79b808ea6fb233a5056201e3697f81a2d49ccd8b8efd1ab0fd407c16a210767d1d3ca798ee53a4bbf1ce5090d321b1a64fc2c5f013c23829f5b0d2737936ca71595a1d02711c8a7b0e74654e5d76376ae26977dd49c68e3c0a7b36e047d44be42d732c31f681bd7b1b4b339f004ecd847960377acd005debfab13d0fb88355025877630aff753a7cfddf6851e8bcc8ec37b8f9149830f47e6b601098b2ba19a4c0808e31e8927b2525cb82bfddc9b4bcba2b46bbe768ee278fb89010243d16f9679f5ba4f13cfe76b5beb16c7b28daf99b0873098115c2233ee3402ac0f6c899a2cfcc83b2ccc06676999ad48017c4ace507080a26501993327ebdcbd1e2eaaaa99f4998b716cd9e36eb26b4573a03fd1d18047198fdf675ef4f979864ac85d230a011c69d8b6c45e9efbdc2a03f195c9731b4cefa60208ba845c0978e73d082bf6d6a513b93dc805a4f5973f4158f60a200167ca88704a15ac5ab1f38ed455a426f7c6a96b6bfea2ebc1ae1247cfe5ff29ee81bdbcb53b03b89568bae9a6f311d2b20e31c2d91bd18fd93a37be266d0de8015d52e325f78356dea0b77cc76f28e0f06e4ec705d1328340013a77b0b6196f44b7712fff4ae0ac7f6afab9456a95012b7c6d387285487476d189977e28f6c9d1a3f736320d61302c2d627d5a7ac8cde4988056b55eeba27efe7e640f94c115762ad5849423ae138c76f15b47bd2a2bde2c492489b7980aaf1c4e32a155f858d7be4fcd0f8a18e7b5d97c5a08d7885d6d56222ef49542c7f80498a14a8eed1c092543aac3439966d5b5d0cb9e602f4fd795c09d652b64f9ab67e38f48c88d18e30a9774f37e9c77b7a94cc7310d",
+ },
+ {
+ "4ab8068988d4bbe0bf1e5bc2fe1c668cbe58019c958dd2ec97164aea7f3f41c9f747527f1c0e5fdb2cbb9d2ad704b6955cb731f14403dddb1a28c5996707635e4eb5dd6ac33d46eff8e319cfe7cf6443869534ca9812a5b23a6b4ca172afffc064dc2b28197117115431e03c00447f87d9b45172c6f724006270a1d41fa094847cbfac9630c3a785f488c1f5cc407ca6f4cd18bac43cba26ad5bfaccfb8f50784efc0e7fc0b504b43dc5a90a0525b0faf3c8b4b7046fdeb1cad87ec667ce3eb6cb4c358b01393f3ffee949030ef9fd01c1b2b9c5219777eb6ff5b1d7c3ef8d8e3bc2193dfb597cf942c5fc50befa527fac0b44cda2bbb811b06ae87459750295371cd232754e2bb7132807d1225950ce64949b0650531800bd0074177677acad937ee008cc0bbfdf33c6b0552000238494be8be412a3e5cfa359e619d092c76310a76bdcb22abbe6f16b3b116b5f95001d20e42fc3c9ff6723e580f378475788eec265a1ed2087de8cc2eff72184f73fa5dc6e68a56dcfc85350bccb97135386d5b827c2d9aea065708f5c921454d1b9303f21d5adf19e00415acbd86d1e5e42d78505b033a515a435713649c50702f54623cbf31469f355c3be2e30dd8c72b4127764451d79e952ea1f9bb0269da56dc07060d5d9542a9c1258ccefe53fa3f7b6073cd38026256b45c01b6c5dc0d91e3139f30a8d1da7a076738f5bb23352693a8e3cbbb46226fa22416680013f9e3278913d06aee4a62457357f0a68d173a360af5e1411840e34c574b4c6b352f92ce33632911ad8b6710d357b7607ee19679e777baffb8ae3c0fe9786b2e97fdeccb5105ecfe81441f549bc6b50ab84b749fb33f8f6bddcb6bb733d6d5dbc4b29725b8741439b8239e53fa435ea29ed3324202b1bdd07d1987b0e06d8cb51013dad897ef02401290940ce3f2af72c5d1b4c8836299008c10b16c7e3e119e41ec66d9db6929ee09bdeaeda08a50665c052edf77b7dff3d8815046bf71d5015e3bdb29a4f507aeb2e28c536cdcc9b8d1e89849a0683d78f99dbfa90f94aa5dc08587657a8f042d718080de5d4a973f232f78c387b63c7143fc2a4380c491414a18b6c4a7bae2194b62e798ad7ec7d09e409425f6d0973accb17e4d860f8ec0283584cff076d93bd9b0c4873f9c57cddcebe3c3bc8afe793c6cb6b26c4582847b07446b7e1d9757de6bdf0df826cbc502bf88cf3a773866d3ff293034abc4afa3091b2126a278f50e47f2f66ebebb616e342098ab690f7f5828bf8cc4742c677d378893e9f188e8397bee983a9a0998de2a31798330f8db59a8581e1c847589bc0e2d95ffa68e39226cc15cf6cae5c4f5174e7848375391dfabafec202565ec2383721339f04c5c5d1da953d88f18cda65745ee8e99805e35203a6545a0416923b38c5db3c8aa00d64354bed27d7c78c4b257534bd7a18107ebe64d8c27b6afdb330d8efba79fd1fae480cd51fd3626bf8d79fb651b7c6cf752aa737a5123558420d48fc86451b358d270aacfa6c17f343b7a9956e6f64e4990c1b3f1e5097605edf5ce4247819b19f245e9a90758dd42c36699ba5cd7f3ed99a7df7eb155749f4b42d192c47cacb6b2865fb9ef2cfca283865cd06e40cdf7f89d76a9e2eb393e2e0ac0e2776da929f3f8e3d325d075a966d289c51347bd0bd523a5c81edef63ce9b72f5114c88b08b16edbd73f518096240a5b37421843173be8df4ac7c587a17ca6f2916f7d9a10dc75f81bc778a1eb730d12b51555cc414eab9c066113a7edba9a7f1a18092ae47f12f0368ba211feaf34a3b48a7ff5c91b81cf7c95675a4001c95a19d284fe4197fe8823909a123fcec5e45935da12416be1bdf14918414ad19b54a41052f5b8417ddbd207ee01d6a3e62fd9b0321b1c13d91d6ce15ea7b2ea0c670a5f5cb290ca8e62c26c6499104ab8e9fafb05170ede246bbf7313625d1fc9576f1609ffd08852a2f4b73c04f1f4eeecefe3f3eeb2185a618b6dd3e87d9d3fdcb349cc83c21f26b6c662bbb857aa95378e991640a160a23cce76153c134508c68ec54a5",
+ "0d471079ad3c3432b6de852ec71692d12d9df4f984554d458a9dd1f28a2697976da8111ae4454c9a23d1c8eae75bbc14f8b00e7c065bc290f8938282b91a1a26c22b40a6708c40945d087e45633a595beb67d8f1c29a81",
+ "f3dac58738ce057d3140d68d2b3e651c00ff9dbb2ca0f913be50219dd36f23c6",
+ "bb2d033de71d570ddf824e85",
+ "238c4e6be84bfb151557327095c88f6dc2889bce2d6f0329e0c42a5cd7554ab16c8b5a4db26eab30f519c24766b1085e11d40823053ca77adfe2af387b4dcde12bc38502229510606ff086265f45b1087375dc4a022eb0b641101c74ad566ab6f230133b7aa61861aa8202b67beddc30dda506691a42032357010d45adc7ee633b536a2fefb3b2143837bb46db04f66a6e2bc628d6041b3d306ff78e96205ab66847036efa1fb6e6a387cf8d5a105738be7163df9da0db48e3d8fd6a786f0f887968e180ad6888e110fb3d7919c42a7f8c92491d795c813f30ea645fafcddf877f5035f133f864fd0ba1415b3d698f2349ebe03d9e76610355e7fc23221c5c72b1b2628a40b14badf93288fc4abeaff5306d274f21938650ab236a39496d3f8a6e9086eac058e365d4335b51eafac813f9175bb7bebb75605909ec3fde6515694e119f7b6e96aa1d6d6454c3a7dddeacc83bf0c1f5f6c2a9dd2f460f3e5b074a33b8d7904e6988ae43a22a87f0933f812e45c4c518bf83e606bad4c3c55422ab2207e9d3cfcbc5819049f55e35b9663273d9d3a6f8a897fa38b0dca77eb6c344290cc007b68d913187f2cd480a40262623a4e95d90d5701ac2b9d858d70a27f0672f919c2ded1fb89134ac9a8ba6ac62931c832372abb70e811dc50cce264ece65e87338231f18ac007c5f68f3b1c5904ffbb2e1dc361d53914917770d66afe28c547d8cd5896d892cbdadc34cd6af348c93bdb8b072f38b085361e62ded7a38b4368824c759ec7d2cf4caddb9191e5deedc8b8388bc4ba2c0672321bcda3a7343c9ea71ef03750912f35624d81da5fa8a6ee676c4efd99d0c7258b844ded7b35d8c8233a316b508d79c7c0b3edabad5db9543615179b1c111bfd78b79327ac5b4155336d670baa592d441c810cb1b7c07f3d35473a45b57e780b7d997782aeecfc0363976fb608d6967844ed00b63ba75996054d090aeb605c195b1ff86f9d9ab5892d27632cbb59c06b3ccd69d33ed5dea9398f00b7c6404fcfe2fcb5924e4cb75cbcae0a1b084ea8b15eaa5847431e9ab70e4afe15b4c82239f6165e243e3b76d6c91d23b16edecad8bcb16898641f8e323671452034a8ec9b42b29cec0db210bad0444f1c5bf3505cc41d514d5a270d556f0a34333bd06cd6509ba253a6ba7a6db8f1a60c99f0c3d566a038a72f1271a178cc3ff890b0df1e7438c0c1a12d9873643e2d7bfeb92379545de50834abe2a345faf7ca49beeab87ee516dd8598b71196b8cdb15e7200cb5bd814338babd74c565faaf33d9a8ed4209b417345a1ae611880ea22ab2e894d5d14a28fe3835d3b2718125f0e6daabd85327455646290ceab89e579ed5e1d72a0172e4a6d8da70290b5022c941f3866f96cc4218de5d2622d13af6dab15760a1ec5d10918267f9585284058aba611ba07b1d5711cef505869831699bedc2b190fe1d578814065c91d87a8c8dc9b0d4dae0c80cd241f0bda3a6d5e714c894b7a48b1e5eed4555f103eb03c9db30efcb855df422d7451a6d70f28174c7ebff536dd2cd2891f6c3f264d632ca924c4e0d84b37cf8e06e6f2e29efac6cf008cc27f062441278dbc9f09cf44987e0e9ca088a48437b0b89efb9cf00d3d0c5fb449fd4b64e21dc48cf300c2d80a502cb583219f1881e78e647783d91dd2f3b389a1594eefd8ea07d4786f983d13e33cf7a34e4c9a0ec4b791f1666a4eef4e63bde7a241f49b5cf615888bd8130743bc8a6d502bfc73ab64d1184ead9a611832b7e24483a1a0fc475d9ff6166b86a18a3dc96910ff182cf326456c4461ce8acb3467f801890eaf1ce0b24791da9c650876e718c0bf43c475174f9712dd4a228695e8f8b2b23fc4a06358b4a6a8e1afa87a0280c3e098f218f7a6d6bd716f8c105a7eb799ba0220837fa5a96c8a22a826a6f7ea9d7216a24acbc7b0133210cc17c8190507badb421bc54997ff9340cdc1ee415126ac46a4fec9fee12d40f06300f7e397b228250f36d6f0d2ddad5fe1898ea690e4c7cc3a116a70bfaf6d2dc996753fffae40ba5280b8356b7ab4ffbc914ec74eaa070581fdd1d9e5aa2",
+ },
+ {
+ "4d81b652fee892d575bd13dad913d976cf0517c819d5183a72eba995b1f27efe743451721ce34791a15a6b7a6e44f13d4a080563dd1d9d4f0946e5ba3863b9ac970a1fb4ed66458ec1b1092ff5fa6c3f0271a2df8e3f2e97851352be760b6a0e1589c202f00791b1b89ae0ae944ced96bd90754bcfa3e355b735132d407d3b5507fd57f705e8a8bd82886b16d459ac91e921dcb8c5bf0d7cf420a9349ee589a5e2e19ce7c944a54ccc1062a0690f3152300d0bf5cd1871c1391bf6d7007f7ce26018ca2a5c6f76287fd8c8e9e7f93b1806460dd35f7f95989a8b6f9a0aeb7c6b0346955fb50b8735e34f1ecb4859e34ea0f022ff6fb797094206a34cf120b7f4664c531c57da513b296f0671c8e9bf68d9e1674998fe52da04f627f516dee97c2b3c988216e9bd3f58c3b021ac70898651f1cfeaef21c4f417ebe92dcad3aaf50f4277262c356584f816a5a5862f2bd720fac10f1b86033371ed603bc00a30cf4da8f579dd5bfdd571a37af7d2a5cef29f9001bb1605ee87f24ec3b259f381a69b771f78d21c4e43bfc83a916e08830d9885c8ae8ab6367c05f92e5eecaf0488262300f83f4e3bff177590857e149216995bc52311fb9f16f4cd74e07c7868a39b699bdbb7d7dace4c6a53ca7ee6e11741a63a52a1d96995a6dd752356dec6f14761ccfe38a6cd8511204f8f0630a747d6e19a77bb030c61e0828436604a28a7acf4a5e49b7269ac93b93b99e9e2e1c0c47b377f7e44e05ec6659526afbdcd5bb172404ce5a9f8786234114c16f20cda6d4359eb873a4a4d9fdf734e9c40aa4db3ea9a98939210f6c62142dd144eb78191116d194bb766ea96da38321ae27fcdcc196560ac75567297984fabe6072c771899906350f74de6d18518eb6898b934b11e945d94ead02b821fd6682602e03e9c70a1ec67eed33874eb24dc83dd1035fba5928f8f62ba1282907aa8935ae72fcb881b3277ee6bebda8fc75d6cd792677c25f70c87b11e094298b2d5f39904be211ff0980e5b83e8ea4a455622d8be9efdb5aa8466c88ea861407d54d98112faa10293af5e16974861dc9f83b45d21b112cc367894c421f5049e49dd205bd7c15e6a70bc810704e2e3a3659800864912527f8be743acdc474a26246a81fc2bdf669b9be7a2a0c986432e1e44b5675607e7e1ee2a8dcb72d8f1964272926e52f909ede0ac8daa32d1d850158db76b959e4d83c9da4e3bb23fd1f5b26463045d6cf13d187fe74a50c09a654d52d0e2f01d66b9f8b4f4aaf4c69fa62a02aa876f9bc4871aacd26a6c6ccfb9bea09cafbd0268b5b65d60aa23ff504d02fad4719698f8b044ca1bb037ea6af58a06a448080dfdbe6a5d698d5db9da5fb4aed04a46c8fa8b93153bca00a5bf8aab64d2b371d072db2ddb688a9442e948f0b99236828dc115a2fddfa2a29e2d4e02ff0173cf734efd4eb687e3f8712be82abe1fac4be0c1eddda090803fbdce41bccfb58c43038991ba1074b281a09bac5eba58a99a1a9678ba26f8f9e3c63ba095f02cd8f3b56aadc5de60477efbf3dcb54b854f651cc72042bf19268554c61b44f2f338a75de56c3c45b3ba40a697f5f21c4557380c777bcc91a151e5676c2a59606200bd476cf98d20b4cdc64bc3b8670810a014871be018bc32fe239e287cfe8a7cbcd1e8b55e08692ccfb4ef871cf797bc0b1fd7ec37931e35b6bc5d32bbe7ae77b9962c179f96436e4a32f566298d2235acf921e38c3f1942fb7674b65e222d17b95a2e58f072c63aa4bba1ce48c303f4bd24d84963f18c5e670015c52342dcdc9c0b348c7dfac721b568effe2bf2f2e816ca3279bbbed823beede8e12fc5bdccd0f1584deb1f6ea1875e9fb350919b675ccde0178bb83a4aa5232bd5e8e9a1b8daf905c6197367a0d106532297ef89f3bc690b48224592c768bd9c50a63d0881370d475081aef052b444744b33fd3fef674a37898fc950f887ed482d2a51ae615ef5b1dfa3a23257e6a6a319a4e2080b2c4094bb09e4b390d1fcbefc4d6c5dab620f8b05b1bd5d976300b007e2b8120ef8a6c9028b7d925c795058c6bdb6711fc5fc2476b9810d1d81bd24637537716edd3b7068b802c531531df710d3682f9865530e1ed51b3b56d860ba4e972bbc74662cdd1e2ea24f81bf469193afc02b14143a32e9556e3f2ecef97c65",
+ "2538d98b64b6aa9258f9141840a5abef66d6037a10356366a3a294719c10d6c148b04cac66f63ebff052d730f8821f5e5822d869573bcffbdd636c7973433abbf38767597da5186df8ef9df071bc4ecade2633366102313e659db8d8e0f293d379fa2df79f456497",
+ "a5049b0aa153e282457555bf6f82b60fc81aa6fd1c2ea3db031478ffb74b5b5d",
+ "350287a6bed5709dfba3d35c",
+ "849670914f5fe318eb01e8849e536374ec11e813acdbbe6a5e82a506f6aef4f916a3a7fb2e41db3adf990175e21f2386d1805af9bbc32a6ac156b13b1a9505958f68599019c4b7297314229c467114754277b10e9f49a4d12837ef24184629c8902ebe2a23f740dc826b01f8963d47100bf617b314835e436104eb207fa9a1079b8feba06d9369b9aa8222d38d87096b73678bc5db9a1add59394530e678b6ec93a80efc6e8320f2909e3e891306d69b016ade0d30cde64c2c903b401f9d01a29b5cb8619dc68ad6c21900b365a6b657f7d9ca4c145fe598a94eeea741e20a9329996b17aba5d7115c93623f2f5d6927068d0f190b49eb885429d771bbbb3980e9293e4d664a71c3cb629d869dc97e58fc3d328331b11df19a38d61e1705ec4c3d779168abe049e9d675337ff658e00d2d610c8f227d1341d1c41f1c01d8b5d83c4b1b30ae4318da9822f46402ee8cd5cfe9f3f22d90a5ec2d0aaa0baa85e10f5295cc6005c5a0887287b0c867a23da1a4c2196f91fe0bd4f0db1ab324c26fe6088d7583f3cd052b7f6fca38e8b21f98fd07fe78b7657da1f586f1fbd3d2b4079e20f21dccc0d269d53a29deb7c7fb63cc291d1d2c50ff163e08ce612310d3bd622f2416e193078ce4e1463f8a3490578af96ca98e665468281f1af9117a2ed23367df19b570885de9d6594f09aaba4090bdd1079720b08d54311793c97bbe14433b031c865b059cb4f75db74779b82c4f83eb4bd829c62eab995027b548063d7cab7d1a6f9642da6cf7181c0ac71594b97fc2c84b1768f81eb287091f63c76623c61e7ba90c922c74d46b9ae5d8094d9752bc1e8020a82601c356a201e0473d540053c707a88f4baad37826152dd245c4cee6b0019583c61e4327fdf6bdcae53584cdba8a503b835bfb5df9d649705fcc1f09376eec96c3da1e105accc1cbc21d90f527041a9beb85f8cbb1ee8db798838bb45374b741618f83b5d0801a3af2f640abdbe74ec3dc15d6711b4c1480aa8d6084defba82ed221ba359c9744705c4feee0955c27ef468cbb816694516f73fb541e0ad4ccf99ec8b67ef090505d1f7c4c3a8ed7e291c820261f12d92bbc6609da6c275349819848c9112826674f243acb9a29ab73f17c8f8af12c7437c11972c824f00db7ad284e51b9b508a925f0664bb259b4443d56463bffc9e5d845c9b9f79b24c1f457088fadd281f48238866e0b92d6253638eb188bbaa8bf6a81d2b1087904974752697cffb00b4ba05e5b7b842a3d2c0a743e4bd691625788fbe9df14600643b1d161bb2916176b6ee40aee38dbb594ec2735d41369ed3a0c6dd9073f1eb51d1b77eb9a967b53670a8ed755f3b2b73a6cb50a9e1ea7549346646dbe4b801c8aa642779d8761b6c2d2e1a9995e758ab92f07c4eb4a23c042171a4b354f434ced5f6d9ccd26cd6c2506e5023dc076ced15566fdabc7364f4a8594cd6ec404e1a9470f52a83052390e4f7789ade9179b069d9f84ca2c7ac9eea51035db817845aded7405bee90cbe92364c8c7cf8a366cbebd7a972438f2a9881395a8610a2cd0c06c46b60cdae5b1f473f4fd6ec48479cf35101656f05485198a470cd36af22838e7ba3e28863cd8ba7bbba7e3c2625c1106a6be44c9e3d9b9938679b26f0713c62c3757a2dc8b2d9eed5e652220a7711cd220bc91a9afd7c940dd8be71616ebb8b2cb0686dfa161c6ef56994a3cafaec5e79bd0a2531fd1c1a42771acb101a38988bcba51ad85bffcd8c67aebec5b37d526b29f7b9d31388e1e7ad7154f8e65516f0d80a30b88c2b868be2541d19ea1d2bcbadd30e2fbb1b4678bfef7f200e0f8309ac0701000c52ebbcd6fa00cb85c8d3ea9c5aceeb3adcf3773cfb3bfc9ac764d031d7c63ab888e9b03eb9fa74554dab4719d426d0875a508c8c86b22cabfeeb70b0f1461db4e5f639d2a2d28a089dbcc48e3f34394ff1acb887b89f75d3236c8143bb9b06273c3878744340ea1858a9f383f8bbdc259250e23a3c3992bf8b7ca7e1a66913547710402bb538a8866772d11cf4214060ed091d403e1c9ca3af75859259f88656a1cfecfdb49d57c193e60a2223627c681a2fbc7390140aeddc19df035a5207adde4f5736bc542bfdc943ae8b094f4a8701618688fadc2284fb423f602c41ad8ee11e5d9fdfa67fb7dc7d4dce7847d4875b3af667168ebb6082f6911c95",
+ },
+ {
+ "67f0494a728fbfc84e2f4a043e121ee40f3b12b31616c78e157ed970db28674318b08d8b3f4c538d7b9d91b9b0b09ebfebb07201c6398fdbb8684c9390b3d6a8636333a3b086302b24c2e5d47283935d33065efa3fedd5f755218be5d4618d38c5c1db75470ba06bcd853f3f08d39c3cd9fa3618e70b103c2d9b2101fcaf39c1701436b720d723ed5c622d6535c9a10ec4d727abe237e80fd20911ceb84a90285fc6e07f9d036cfa65995f9b6300a927d7d0d2b907bac9d9c4daa87c2438a583fe85029c886f96ed08f5886bf53292cc0265850a1f4ee3e3288b604dc305d0c28ad35e1242f4ff4ae988b6deba48aabcad2fc6cd7eaab0a63510f3f915c4bb9f9719b1d90db123f639d9d4f3227eafcfad769c2b204dd2555dc54e738909122022c4f92f751d25aef6f9a1187750e825c68450e6d1223c2fe88aa27194b492b6788be6eda80b9b9f053cb77c8d9fa15324f23af5147624fc00c66e947b004bf38b31e1343c7cd341b98abe462a5f994e51d343664968624a2ed0dea9d0299d5c5a7e9097fa63d8b3ed96f917f693654766a9adb01110fa3fe0d8e9b102860d5c049df3fe00ccb2ed62ab05583e6aa0a5134d55245d4f643e274def29d3fc86d79979d599458786a8338b0071f6a01609ee6b2e4bba9289e2df780bb27491890d0b5ea650e62df819b8f98aae99a1b8870ce6d3c7785ca957d5b4094946925751f0fda1d62a9aefe3937a912c1b49b4272f87eea7e397feb84c0702929959e38a568460811e5064b1caf5dee53f920c6e19fb16fc9214b5de1cb770b510533f66d8a0e7f6f04ba8ba41869f8018abee31a6042d3919e217359988eaa9db2a10b3caf7aaba43527484d81304f0bef22165f74e9e1031b545ca3d2f74195984cc237b76ddbec85142a06446902339b1883000264031db85fb19b46f320ef3fe316f750f2d3d6070dec5b66ee8ef20701f20965f5171e44c8a99bcbca7afbbd81e30e74c6d48bc4b0d72baf562da6581fafbe14b6cc597f75e53b305036ede219ec56d0c0d29571a9c110ffeeb747fe56f6030dc26c8d3841b868a1ef56840932dad9f3bd7f75573086571f4d9f0d949510a2577d2f8fbed7e850c73ed4c071bf9a656d09dab43a610b49aeaa57333f67d586d4f50683dceee4942db9549f68eef4c5f8df8a2330857cdf2fc4025f2be7d5f0dcdc74a9cb593de91282787b716d416a3ccb8d6d40fa3c70be4ecfda26a5caf3724fad3d98db16ab6d8f26defc68392923b69664b0c2d56f01a549284b042bbd43c8faec940187f190aec08d06f9a62ab03c9f610f64c0010a0939451d5502511dfd3da1fec5a38f64640c7b6db2961def257eee9a3eff944828e9557deba68bd8e42dc7a9c1570e35537993061fa0f5351fd3cf4ec36386ec4cdc5a2882d5f16703b900c5000efa63888d69982e5ecd3e329c8cf5f003e23ce03c55631246ca15ffcadb0fc9d5634252ccda812ba7bf5e343c44244026512062a68374ed4d8add0855dcc22b30148e0cef0f2886be76bafabadf3ae1205b43c6deb8a41c338114895dd6b49deb329ada31b350e02a1bdad4eb05b61b50f9d22fa2863bd607406f552713e302467ddc78213d584b4933202438d63f99d011b97297f5589f35b7e45ccbd76f02453b7a7668c2b1a1f5d1d63eb805c8881771faaf67433eacfb22f9b6fa58b93f9423a5fcf667aeec39751ae17ad36992556431bca77059a29353598dac12bd3036633d2ccadc18f44123e5bc074f4e5ca380095af062fd83b647015259be929011cfbcdc9bc5d0dcf9b688f0f5d74da95746f447a9e1cb5028ccb2827b45129d04cf6990953a6d8ee0e67fe6bdbd8004f4744cae5607fe7ec4a0f14fe603dcead3367b6870d8e751cf57387d04b881f92cce9772d695f19b36e2db2cf6a807c9ee83225f5c09a11b50e99855921a4eced8e631af7c234aa31615c00ccdd7c6ac5ae8fba6e29cc233765a891864c7d73dae08ed1a3c27cd423d8d4efb550597afee8356c12018f496637daec83575f5e38ed2fdbafabafd38483c239d31cb4d104e93d16eacc6050033a3c86929be4ca8914a538bf540b43d7ce7daaea317bee1ab80504846554879f900d312bf2fbb406a0edc5f4f809cbc68675b0b7f09fd1a8a4d52c0929b3a8b9c1dae4b3d599b976867e6a7e8736450dabf5c49c949544386a71419324ea4ce5c4319899ca510f50d07ace57b013655b0929f79dbf3cd629ad17bdd10109b7c53a4f5f04a16e5471e823c898362df43f57ebdd1627b33fd4cafca6cc065d9140acf0454d5f99be47bc87e0f3b4d4320bbf0f21e7c261bb8d5d615963beeaa46bdbe9b83a8277813ffe6132b23564bef5",
+ "74dfdc364097c39ef91c01b707a522e28edb1c11529d5050ff820234e6c0295aa00591e09d547e9671804d7825705ab44b76c59d1315ed1297ef477db070d85076693013bdafa92e2ff6a654660008b176cd4e8ae23b9c792be3f7db54cf2bca385bddf50a8624397cca8ee3cb96944164e3cb461e68",
+ "b3b5ccd7ef49a27d2c6d13c0ae77a37abec2e27e0b2d3530cdbb7f36792a7d2c",
+ "c0494bb7249f864f69beab46",
+ "ed8d6e964bcde1df68e7f362243073941fd68ac77929c8e480c89f519f748b3dc337b1af6231632c975167a8425b174b42c2c60dfc0ec85a0a212bf5c9aada818a83f9664c8712d96de1036b5e5d8c8298786b753638de3a8da958549f16eb9c723355cdf7b999aac464ec39df7d6c1607e81b88b63043d1c847dab618f1b19336911b4b0145c2a694e61db71e021282006d48e37f10f3b6314dd012a07618228532c28ca84a936e0eff83723d117b2f2db857d14af5bbd5948a0e53018b31e57cc2a81f36aa013a844990753ccb347fe98fab294cbd252a8b8f7246276275d2780511fd3cb7baa2fd1548184f968c422230f7ad73ae9dde91295f79f6b799e7d234dfd6573fee6d6ae748b0a8cd7ed4862ebd957390826f276c2afb01fbb4b64b61a1bfc138508efd630e77580867bdc1e96a48a694cf0db6c2a11f05dd0bc8769e7200bb0749f5798b6f3559de55d0c281eb5df22b731fbbc109da9c68f209b888e61240c4c0ca006d105c0a7f43144021547d3316e5a99f6c429f9ea2f17d77dc68bc9d5125b6260f79bc8b3b8061972e6757d87b6544f21645c0b4debe5224f7c48142c09f35b8e144c0c1e6521f04c170519ff744d61abd59a56d25a26c5ed5972191b25e78e2140f3ce68fe17be9e59a79f6c69619a79b83614c670c7736d19c27fd22515fb5b896a6418cc0b4850e85c07b38b995cffafd9f69763cbbcfa9d1bbea6868244a66a5cc82e815fae09f5775d28437634926d571c2b0d200855e09cbdc67d10f85bd4cc334ded4c83aeea57f8e373a950f135997666b653e8de47a3bc0059525720045996bff500a47baeec97808fe971d7693dfde339e8beca3598fbc053121536c30d0af10f8f5d8e5eeaaaa9586d7abb563fd69e88351f93bcc46520f6d97c1a49ba9f8f6a25cdcfc11b2a722910aabe7435ac8f0dcda9f824fdde80850f21a2d4bcbfd2e9fcbd14dec05c117a9796db49e2f0dc55e74c7f0f615bd049fa7d0bfcf197dcda3ef3de90762e6f6f9f8a8936bd04fcf2a97cf18ecc8f2f118ffbf02b67f252097e4289d02f264161f6f90f79e1e1ef8414b01a9e1a77b88c039ad6eda6df1e28fcfe9370f0d574aa9e857dcebb19eb7ce8af9b19b4481c9fb3e1f0db3b02af483f737ce3ea824b2165e7c0fca8585383d4b0a16eab2c7e3ee5c038f939a97bc8e1c093cc5372ee45d81836c988f3ab3e6ee0e5f9549e4b7bc381a2afac2074cf75ed56b0e757e7966cb253d549fb0902da98294c6dd4de3c2e166b7e45098d2729b1393deb68471d4d3218dea3dfd0183b654ae4092a79357945eea4b28cfd06b40d30d1b4b8f19827895f6f908f0fe511f74ec84cbab2483ca4bdfc6ef50178eabad79b18b58529c9328c13c52c2869858cc20ec36ef7717e1c743d13f9607bbdb0b701d9df6aca7366814e883d23e51ee5b0f20ef70e2c4134ab037d213315fddc89009260981329a1872e541767adbd5ee9501e7df4ef0cdfae9769961f8716ee7dfbab0ec89b3f62e987387d5842e124a69b07245d359052ada50cfd67472d27ce2c4eacb5421b62dd7331da54ebf0989803797f4c8c781d0e2e6477b421c7d5cefc8146aacc0012af3f1f7cd71ce2b1045d86bf48c9a13fe469a1865294e160b4975023d0eb24ed26837afefc250a914f86f8b1f5d67d65e9737e841519148d4dd5dbf2b5a8b073861288ec9793d4b113d71c01727f67d791852fc3946dc912d60fc66bffccf4c45d859eed9f0bfc7f89086df5d5cd830ac919aa7cdb4504018052d67f6a3ca012ed69187cd5fbe91875cfade381bff1e804ba59cd59f0f75cb46dcfba234ab9832c3fb9aa8dde19fc1fb30677ac1793a38d94aefd9ffcd4e777e9e4f6d49e0cdac6c16a36bc2f3ed8e23b80350e3be6d866aaafbc8cbf7c69fe44c2aa80651164803150c23ebe262aa669c77ca94d215895d2ee9c3e325a0bf2c61e419a41e0f7b1ba8ee0508307d49301abccd5b74c054b6c7bd1aa67cffeafee033761d8226d9dbd7214b130a867764062cf4da685deefa23693b8549d5ef5e53df85c19bfb3c43c6bd073e7a836f849587a4747e1a9a3c7194f6d5472d2e3e4c81784a3061fc9bd3b94862c4784974d859134369486f2651f1db94f511c6f59f41da0d75307191602730b88e4e6101fc8d392c87687f3be454dd92fb8ec380715bcd88aadb63717cbce4db91a36821a572c363759d8d0a2ab007e5981b78731dfdea20d900b14f0c5ee6a4a9b532ed2134e6edb4dc267f001cb88dbe43aac4aad453b839d035697df7de98ca7a9ee7601228a79004b89796e9ab971aeb8e62c789bb21f38b77b492c57db402bf6a42ad0cee169e9251d865ea3e5f79b1801ef1e53797aa6c7060d6f9486081",
+ },
+ {
+ "04cf92a64cbe135f7fc1d7223b95e41d13f04b482018039f4e7ccacba8aa15ac79a752c5666524e527fb076290ec80a3dccbebfce3ee9b316a65fd130f12bf88b9124d1f7772049e6d0c01fef881a1d44c8dd02f7b6b60e6d15df9e06fb86929cab64842284de09659e19451623525aec2f5dd3e603e24319b1d120bd57b34a0317ce25ac9c2f022a4847306b998b57c8d92baeed0de1f6cfb3177d0acab70de275238f1152813b9ac87bf651f74e1ad079b9bd779ba4374ecba459865b5768d08ae7e1dd691d6821895e8380ac9e5116580e8de3a2c5326e698bf4c4d35d955e45772bae8483d01de2539e8ee1ef9539ee132d80d85fff41dbe406af319c0d7703292587bcf5959f49241e2b03a364e1b682729ed261d0ae45d74d77634afe667413ee210983b042a7ce6dbb61c29d18450fa7176177b5a74f032ea24e1d08b220f6d32a7a836d1241cacda39d6acbd26a62f9dbeaaf7329a291dbf0aed4a2cfcb85ea360947585b1215feaf70ba71eb2d6bb7081b2a21bdcbfdae6ad2513a9dd714d3d06c2c2b7e322a1db2d48f9df1fb44fa066f2bb42b196295ebb3c0898ad55d5b317986afaba0bd5e754cec773821613e908ce2bba6454181f9020b73e758df18c255c87df675cc6bb2b8d2eada44196ac10c26674167f94a79f4be515d8d6a1fd3228dc9a85a355b030845dd4c5f481d5b6e74acc66de730629581b022fbcff61e5dcfb6a7f511aafd577849a6b057021ecbaee53986159c1ba74c3e930c34a159f467f1e9799cd6c1151067c56769e43308c96c8edef8aa7634d909310dba9af2128cdb8c29b24d3ec2a4f43a1ed86d1791c9a670b240e6e719f01827aaa319bd3ff53959a776886a1b7c942a54f141e6bae8576d294e44333e6c5ad90f74863f69bf890126016b318e0f6bd2f0adb9bb861118af5f6cd28dc93d56c8a1dd080b8c810ca29267d410673fe367dd9d1353ae2bf2fd88d57b4202c21aa49f12a01b93acbe260492367bc219d3afb6e6f35502f6529bcbcdddce9fe8632efb034a9eaff8b4a48afb105d04e3fcbbcae010ddd6636992213750b12fb3e01ab72aa957136e0bae591bfb5e0fe819cac82a98ae8df230af399160594540640c6b1d537e7b5f1cc47b08127ae02c35b846de56c4c08773fa18d4436e14b76a7fc4bdee301d0af4880306f2f33328ab79f6f24ec779b2b1928704f09bbc5b0b7108e9a115e4959df79c80eacfb98649a0788867e23b2974b22e654ddab0494bc922ecdf17727d0f0efde9dea7601857d890bfbacbd93f7df794bbc254f50e1e17eaed2f5d5a2e6c58083aff68434730d406fb9fd02b0dd7bfb99a04aea812b6830fe5e05a044ca21c77a174bae8b58eefa11ecfcc1c977bc6218064c9931b5c92f13cfd05799f11e130869c293c1b08dd29c899365014fc8195514b286c97cb6dc4b8633e47751f87fbaba137b6aa04d072ae06c2b2f34448449f60b1272c1efbd4722a2be749a3d2e5450aabef1f7c51bd8324607668a8caf8097c2f358b1b09fd3525d47ec9a7640eb20ffdc17c4f7eff63df75dc7830c471ace3a727feb11533d6e9a2a08106af33069cf482ec63724032e81cab18e12cb5c4c3ddc374e2f75bcc99fc5da09b80a738852a14e8ac552b8471c6ad52e35317b730db2c13c277e06c643e0d0fbea43833de4d2c7a9247ff040e9c56f1ff7ea92049c5341c4d1478a14275a10119d934e8165152b89951bca7ee1399dd8232fdcbf831d8354640e698b68799d060ceb877201b2fb96cec514affeb28721e163e1648164b9e5722271db9b0ee1a7f96819fa1b1590e9daa598d9571ffa3882db9d034056e9b2785a8d13686eba61d7d45cf2e9ecdbc391739ce89297211472be18b21401658c5bf29fc3615924382d802a166d05dafe7876e70a0d081e80c63632da379766928a0555eb5e7a238cfa4da267527c66caf34dd40055f2801b29b3f5604a5bf3d46048bfbec2e24abd2fed2481698a4b5cd71f5d2c12dd473b903c9bdb978eaff7d76fb69951005681ed7b0257054eb3dd6d10097fee51ba7e8d565925e4091cbb78d255c9d3ab4ac0264d172c9bcb0908db1288c9634248f198a1167daa323822058decd83936985f83b08b1e7b942756a7af200af168fb8a091107b4443fd649cdc22106f9b9657c69f19be485c23b2c715b3762c332eccc44f380883357d10019f20612ab6b8f155c2af9e2ec340e5d8f45bf5278ac1fbc9f9f44d2f615d21007d822b244b1c7a0dbc182c7f5912485d6e4d74e90f60a2f964e028c63d49c6aadbf1df170e4914ca514139ba538207b1cf7caaceed4db8423dd1086b2adf15f6c0e50dcf2e12898f53c339a745316904ae03c38b417bcd7f5cd5ea77a4f06e65d56c24f37ebe72d271ac79b6ddd2bb8bd67f0727ead49737aa71af4f620da53769ca3ae878adbaea5a249128074ca3ddbbbaf5a68f9cde2a0e8d69708b0ea7f4c8d2dd4180882bdaacccf2a409a681c551776bd10439fb12b7548342532b371c0e045d8e8c895929464bdd4fe25f0533c66104daaaffed52446094978bcbb389c",
+ "001084c8a5175c0ad43108f9215c35886c82321c800740c6118a3fcd45776a4588ee0e2d056a37d99b40d2bd9c0845088a77240b7e65d18fef105d3488c77910b5873dbbf275948db5",
+ "d614d2e671066e387f7eee07bca9f9313d86e6a4d1371360900d670e11ecf1e7",
+ "674a516f8f59f9067e0485e3",
+ "1ee376e9e3c89b2147bcf75480ff0dec1d0e8cd45ba812f34c84124871d484b4ca87bfc8cf99f85ad452c482933801426e2737a97468809fa36caebebe8eed07a626b3bc3614ef1ceb54f9221ecb16f413f0bd9ed4b3010c40632f05223484af7bf5948c2fb8a3d2ce04c53e3f2682494f3969a0f8eb738cf93c0141799c9e6b68924433f0326991e19626bb19e6fbb5dd46baf39f92e830f9b1ff465a007f031891fb1f1799cc122d3ae7a55624356b5297bd5d948d9ff2e414cd8adf00a53524df43f398938d33c93b2c06bcde2679566c0a7b0177b4a873f35874739d550712d5cfe3d25c19292ba97c01d84224738bb25546e5c252fe5e5f260ca881aaf176a271a6fca2edbb2cf23ae6d4c56c20daadadb8205c2e33881867cd67ae6e59132edccc3601f014b744ff8eb6aef5e09b358607695d3af42ab8fa30e9fdf99ce54427ba9da3699de19f7a8f9be368df47ff0607601a91e7a5fa6e72be50bb32b825427cdeda3972a18a23af290986cde14f5fb9cbddad336f5efcd2d7a0cf3d5b23e54b702352fd5ee52d7e3479441497d56e17d5868574c56cfc421ee47bb00e9c75b84262a1b9e2cbfcccfed9c4c386ef0d2c1be9a7b7556909b5d72a38b7258acdd624de2396c75386e077c34f005f92a2203c82d1072c8998f03b1df22de832ac733977705453b1d72336b8d371cf1ed3923f462ecd22075de5df68c83ab1e6648ede7fd5ee5794a744abcb32af73bcb182cf97d36f37c15535c4107b7c8f2321f9fe0e2b6ccbe74204df3d748c05bc1e0e2c55ae1aee2d4aa4a52e98ca7229d6d06576196ac8e4b14a9ce807075cdc876aaf904c9962741efa8c6caf41e6b87b2ecd6636e2e58f3ecf576e5d8b895162545e618960ff6e336ff17eacd5a1eb335001633fa78c41ed05466d904ef9b81b643a043298c0e291a085e4e67da72e329adfccc407f800709865147db49cbdf4232073b7bc7ad89b3dd901d927ee08ae6497e0f2f9d052ca8d7444d2e2ae2197f930a7b1c8af38d8739ad298464169823684612cb628c484f710cf9c552551b6837b575a43275100bf800b7a3d777adc44d07f67cee5000422b9049dcfbedfccded0f2aa4d189621579b01e3fdaedc4d772dcc593316ca85e7aa248d219dac21c561d318a4936ac0d3bd5c75311486c174e0e2182affdf69bdd6a086534e4a602efba2b9363beeb5346539b45336cbaf479da6b15b226a9ac026482216dedb84ae3443b306820d9f05f78dca7090d727c7481d82c6e5df80e189e24e46f5758e453e542bd91a58eb51a89e07c50afb543c6b998704432e863dc4c0d0236e0672835a7b0b64e14f5ced2904e54da4287597f920bb4d542c35d3b0271cf0eec055656d523d7d2cbd667445d3e8634854f8616b7d7a7f3e14fd32651e9df40e1daedfdff1371f16d5549ed5646adf2d417e4b3a4d145bbe0974ab388c2716861a08296b862e4fd035163281457877eff89dadb160eb2b780414435784804bf4fd36602699d8c2f6a8cbcb509198c38e2df2edaae7bd7c93313ca98a9c2d24419a12ce35b0b3d68c18840e3ff8739d70969927c7db9a6569787bdedf5c99948a9e79b2302a83a71159f4c789b3b3f05f1e574f8a24c899ae3457f8e73f9bd86976fbddd83b1af337eb8da4c0dbac3792921597e18a2fd3a0ac89a270794529d370d36bb6dc7452e754e903781cbf57c8646b92d5d02842e7df229b3d721f9b981f9d61a48f00e53948a5dbc4f739849609d94aba3e3f5f8163d40321576cb8eb8e89953b608a01184d41aafc13f40c47b12240e3ad49413473c26b6843f4514be221c2af632d1a54cba230457f23f00b2608485c381ae03b389ad0a1671fb416de4659cc7f7a9c4b6d9807789c307d061fcf613b96a2d79e5e3e20b863c8b1b75f35c982b40ac8dcb7d2712ef7df94901facef783e8015a9a48574aa6f0cfb0bf6c1a3409028f8d62137c347f5a35ad6a3cd60d71aeb29bae56bb4590f69226fb4e08fab7a9f41e58f4d5784540a70e7a97720c549c8440b089eabd0eb3e4d37a2e54b1160572ce568f4256dd244decec31fec555017ebf488e878945383750eff26a8a1cca73e7d6f52d8cb229d5603360a3bffec23029ee34145c4aade82d486758e0aea9e1b7bf0b4bfbd4fcc96aab66a27fb463b48c6a6c5c5a60253e2fbc5716ef55629277a5f3b89c300e21bf1226241ce0d587fe3f5b11e47f35614169dcfaa375ee1aa589be33a4363765368f5666d155cf72e851d426fa67b982aac4dbbc29356d71deb0715b34e00b9fd8876bbb09ca0701b15615f05cc45e128b3864b26003e6ffe801c4e27402f37b8997e0c29ebc273dc03358cd22fdb68d9cd3b56ff8248a727c2d4ac65acda4d0e0f511bc07ab06cefcf444f1002c151b953d7f7b19695668a86683497c2a2d2e69f19a4997148d2e8d158da859c8f44437d9ce9db92f84a88e89cbffc74c0ef4295088e2543a4f7c6ae9c908bd987bcfd7a074f83ffaf3888bd7f430dc5a5bb70d223c21b1bcd8bff2103408460df864dcc168486f6a66d67ded366c6e10f50bcddada93627cda711764a57ec36035ebc",
+ },
+ {
+ "ce72c93caa49bb9850774149a87fcf8e23a0c53701554468645554553d54190bc6e247712b02097b794bc421ca94afed34742435ca689d2ebef183fb469c060c7f4d7daa508726c9d2eaeb9c7e9a89b30faee8d9168607d4778acfbd27d5caa623475073ce763ca061273cdfc2c692d1747baa8a01b15f783b2e36620400082747599a16cfd6b630fef310c0b9a2912d1d3bb71eec16972745cd8a49cd927014eb0a2abbe0e1ebded4fb9e8d9e2fbabb6a71da5688717ecd3e08160b9a861f86904a41702b2c4fff28ed8cc61d468187b75bde3fcc5c0c0a642215fea83584387fc5a9aaf2f8a91ae535e0027b618a32bd687289c47e9428a1a92649deab825d702b076223b07c08e55c0b60be95937bfd0504c18398e924420f6e20baf07e2b1b858d3e360a461b66517c24e60f9fe314a4a4973c8dbc7e9d2a9f571a1d8235a21073d81ab9f4800b70a5f17f44d593e8792a2507e6a3a41042fb2a5f7e5f028ed2daa88cce28973ecd88bd125d50fad77b1fde61c38272057d9c65fbfc6789ce41315a105af14e277a0c39d75c34aed7538c39160eab1c8c47818743e8111229426c399c5e88c4d894fdaff0315ec885ea019bf9acb785f3380c37201d494a60b583fc130bc0eb9fbe9b90eff95874e35910dc05c761f8006e2f208b786aeb2eeee841f9a82d9966c82956c181caa4dada81dfa2e2d7a25007c2dc7f2dc7ad1bafef14581cadbee4d614a557df4931b9ca105bade8fdfdefc0d96eeda11c08500b1ca827ca670ba07bb0f85af92914c43a6f71226d6e112d487f1ae99b2239a63ee2cd0849d8a9c488a11f82ca334604a2b7260f25373c6db75656527890f9b772c6bfbb9f687f27099ea9d4d1efd874a6ff83cc36c039ed1690408f20394692ff054d9e6eccc6776b6f4b3c5f24b0052334d159f40b470a9b8799bbc0df4dbfe59a5e536624cad193160ef23abef85df2c9b6e6d4fdf16f848a2a446a77044f1162a278866c491982570cbc16041908cdd0efa2cde011526a3c96d4b39a23c5fcc53d8232869cb4dea871f4ac8afc795aeb1b28cb2d7a3669100a1cab2ee1a7f31e2a25a5c6da836e4b771ad57393305faf582adcd26045e26b618d9943358c615fb206258c8993d700adac7440dcd3ef34fdcb065e10e9c9727662b5abee160aa01d2f2ca6c203a76fb01bb08cee9fc1eb6bc7497bb012ed2774a2d263b9dd03d60c307ccf33233ee33eee702c8e3118f9f86174a97462d0e804a24bbd7f4f938c7f105bb23399967288069e1637b60f2f1883d88ce5a874ea4bc0a7ca0f3b568e4bb1407e4bd6f0d3dc8fe91345f8435d7b1be961c45e4b0f1ef2d92d2d30bb78e1fbf72cd2e7ffae76e8c2bce005195c2003bde46108f37ffacdac28fd67a0de62970b347f0ae3f5f3a5b1d3aacb2fcaceecaf2ff4a2aeef6f5a176cc1b74b234f5658ce603bc353e075278a4056540e43033d37a6eb2615453d8206f5cd294423811283bcd5d79c4afe268a547b98977ed5cf24c0f53a0533bc0b2889356cacb67e2f7353060f9e04362859b1c1f02f96bf5457b58e5ce84a6810d39d7c7f53faaec64db5d6ebb90c1412bdd503ec6bc240c277ce1f5f18876feb24eb6a77e5193e33ce141e8720329add079dc9735f0a35d7d85436f1dba6dcff9147777760b5aa2ec9c8b5e9fb4fc602ec8f754c99ab2372ff5963dbff3fda91865108e606b214cf7acab875197e78060eed52a798751998ce7c73cebc4d5f429f6729a5193d7593072d0921ac8127ba6e796107ee7b9fbcf7128ab35fe9f6fe501fa4695c19fd64460685f287acacf5250efc13899bcf80ad5a340d432a0b9449affda5c8fa090f008e01873aae7d5fbc7972451542c5c29cf9cfdf23db736c8a7112536b1b626caa63f3e4117044cdeab612fff8d8c194d19174f56ce761f6587349c48fab30390f231d209461ee7e18007d10d83ea5aacf199f3b00003259747b1d03274d3c3670595604bb4482d345ffe31d3e88c70da16649a2677bfbdbf618de1d651a53d573aada2eee5c01335ce5519a6d18a70f7ff0b1e66bacc162c49f7f29b9d3fe2c7dd85b6b355c9f9141f02baf08d2be87c36f6d2e1b2e90dfcd100886e306b360df0ecb146a6aa5ac5ad05b63a219ea65885894a386248254348ada17908d776f9b438306ad28b208f80d6b9b265500aead945134b9d388ed5d6205edf07c5d8bbfe0916d0943750150e09c76359d24e3317517ea489fd8a501dd93f159f07d19d00e86d952fbdba2db771910143df346b30a30fba908a1abe5349c3f241958f428dece7ad9a91cb42035c43573b87b26c2ab216cb4c21799f6b3d81acd300ff50edd6fe7868b9ba6c160db3418565ada027b46b63e5d4f3411284fde585ed3673b424ec1cdea678e4a43c262991c3c9b988351d6e0a10af1c959cf21b7a288f2e4d7b3b2c11b400b5e036df71fa993b72ce48d0d8598fe4ef1ce70a970f89b55cf4f07906a479bc84a08bf6ab25221de37afebbc47ea0b38b87be128737d7d43cc84d336cc6ffe1677bd802910a2084751f30398dd0ed09589b2befd2f3b40fbc013318c822fa2faec2323fcc52b43161f47aefc557e92df3050dc5f8b1c5a4b2f8bd7b2ba7aaca79dcfa362fbe7781a2e261683a4a862d5f83e34845a8fcf8a1aa73cd521e87cbeb71f20b20698cc34bee3b8628b1a3784596c",
+ "08b3fbd73d157e79ea9f61665d19867dcb8c1598c1d37b793606936d8aecd992a0d46addeae857d488b83be5d1c1639d4d78350e4cb08782b61bef4107c9d3a79d3d85",
+ "a56f38b4bbe83b3d9e562cdf5ef5b30593f08a1a166676c4fb0d5861654e640b",
+ "8726dc5cae9497936658f603",
+ "88420357d1ad70e7c7bfd55b3cfd4bf06cd4e9b4ed5cba681045199a06985956d35fe86b28b9a4599964930d05d230a23c55a6a152f67082a453fc31f68489df05c553f9ae5cdb3f611445db384d79af865e52440a876fc4153d896b7a2318dbc2a4495ecdbb2e9dc68022326d35289e82aa55197aedc266dd91ba3018c7b474ba22b4e773773f3e9890ea84bc16a6b235e4bb69e785c40c1adc15b0e0ef03aa147b0d14e62341e27398b84a53f72c9199cc1c94cbcad2bd31aa69c96b06d01775b8c0f80278a43f526664bdd430164863c9c9140ad87798a5b8f38dfe90d37f54d1137709d5311136b728e6c799da244294daa4c8b44bfb0acc603a16c088a081129a0d2cff55ce1c4ccb486fa0ecc3098ef2196f47c49f9d253112bd5746fd99df5d2be577617dc2519c0ad04ee49ee1d7be3d50492017108fffc9a414ea227af39fe49fb2c895fcf00d927bf4a2d78c466fd44df4768e6775d39fa5c834b60979ca27ee9f00faf37a090838f56275a894ddadd265a8d2de74265e4d8d286639ce8f01eccd4f551cf6b4429eae3f08902b6ce6ef422cf91ce8946d9403fe8064784895b62a7f5df76ea294132c59da6b9f53d4195c1e9000bec499c14cf8bad460aebb024a76ac50616f0dcda71c0f56dd3239b11764f3ed6ed06c049b2ad673e4beea391dbb854fde1f01b1900858b9809259f3906b34f95a1c6ce8d24fdf0cf7c2ab7bde2202a7f1482baa6e51caaccef9f541c377da620bfbc63955cae0e6644ec8ed6878f704f1dea30d6b50d4291892bad19b0234582d50c6cc0b4165322cff24a9dc2ce1be35be0fdb3bb7abb777ff0b2f4cf16277388af5a89220d59f1f45ee9cc2a0fd7af9aa8e9e8d548fd65be4e47e7f8ef58f7701f93a42e7ff78f70e807fb63513157fcba96ad9731b2e8f80da85ef407d5c368ad16f0657620bfc122ba1b10d7ac2bf46d8133a9c6fec1fe04882f3d5765da8f825e1984a4313f72b67d806ed45c000dd3ddedd524d474b9b5788547d0712e8edb4c6c586d0cdf8f2384f1e093a7f6dffea6e79df9cb9398f5d0b9a7cbd63d489430fbfa397a0d03ef916b7702f33a54ebab84a7055b7ec6179b0ab7722f03e126ed343b1cdf2af3763df7e3a070162535514b01ad86c6cb051859aba1cc4766b12c8cd57b73fdd3c65af6961c45395aa7b885dd59e115db885f644e1c94bfa26b3804f767601c86e2c7dcecd4daa59955e6a40991a4b4701e63fc82b46dc0ccf59af40a8583171375551c868436ede535705f2e6380c5899cddfcaf9e94314794bab98846cd5ba9e9afbdbe1ea7fec5e22e7b2aae59fa598f4d6c0cc6f936a616e11bf01a2acc891cbfa2bc53c511a8a3a3da2e3aa5907d123ab2a4a3c0009fdb5235a3c33718fe4c504e1539abac6370e06150c402b5fc2f8c32608db4ce2eca9d1e4b96371ee195f6cd632f5b972385f9d5d357b87c78cb4e2c27aa9851534de14de923543f5fd9d55e34d6e8b7e1f3f2735df80046de01f79d0321066f9bbd76299c7386d285f7bf4ac15e033e89a040710c90f87aacc09fb8159f93c8b4860247eef079e32d05707e88aac734a2eadaa853f528d9986e0af3435b5c5f44ddfdab9b0c9ab3eea97676e920f80d1794740067f9b229fb018c804e595aa997533a5e967cb79ee58eea18995a90ac08333f1c69600b17ef4f454f540dbfa8b502457761bc4daa876d9053ae1f55001b6916ce559dc6268d01841255990e56614e6f4ee4ce04472dff0657360d75da4e83a71c852a2585110e53137e91bd89d64d99b5614ab2a5691c876f15d9931b092fc6729c0732db5cc40f966fe440ff99d7d05b24a872f552c27fb0cf2af443340b153214b407fb9ca3750d9c157aa75763b0b7600959663889d00f392d6ebc12835bd2f03ad802a21d0228f1d2e9731d0f0051eb2d5369ab790d1134c38e28d2bc2d5d57d6d897244742c176559961a1e40c84ee5c8225c8d72b92352a011e3785c262aac115cafccc2fe1b5e81a677a0220f207ebadd786b93f58e40eb6ade68ddda5b66c5f0f6b4b95cdb8241156110ba3303beb79acbd54423315768bb43b4fe8c4a465e50c4e63bce272c4d731ea4c797e14b2de31ce4264e2479179b906f67af4a23c56e817abafedc2c7a65aa45f0c89fcd0baba60561a8d013e2d5e0bdf9fbcc1346d3edb20e6e9f9c410982e1ac43039ad8fd0ebd453a6788376951fc20374b59946a6803498929d9fdf2e0f5e58c441329a79d1232e957b3a9ed17231c663b4819dcb6b4e33d205edaeb7d7ec466930bd84a064b40aa67fd76f6ca005408062b45b5aed6f8161836c7160a8c8313dc9aa1c6d42c2c16972a1065e41aea9c58db7916e1670cb42a8b54d85498561b4401761506860b19b446655f8988101fb4c45067e30edc3f00df8d88ee34111dd6626d605d993ff207be09704fd8dc242ce514bae77cecd20f10d4a38435a3f5e545882fdc224586a04ca6a162e118d23716240fa67892b78faf98a17916471f7f121fb9f85497a0b34bf5aaa4ee1ed8a4681bec55d1b4973d4368600115bea70f20a37c9e942b87f6cd1e2ab70fd401e703e3c8334c75fc338508e06d6370779578fbe737a75954b4701bfd92028ec32d3d7ae606caaf9f049d9774f70efa707c1c1174d9fcb5b0a0ae2a961c6f58e48ba82c2db14ebbbdc24288e42879f547b855c86dea9a3b9877e4b105515bd78cc43465",
+ },
+ {
+ "bf7884fab52251e202afd7b5b46aa53f85bca5fb80a009d8016e276579e401385d853312a884f4aa33cc5fe7360426bbc0ccb7416cc0196e2e40d3a825d5e0825a1394029789acca550bb28b10d847d0a4fe1111be2b7fec6b5294902775128288a784203031ea853c9c104c75571d19552e2a1359a900c5fc9455230968a5920f2ab23f5b9cc49739d4e4ae2c01c7812ff295899b954e9729a3bb330b60c51a8a7759e5131d7d4cf261fa1e62c29f91b4341a4fc968e7f30ca2261702eb328d628b7275a9efc29b50bcb9b27e5844328d5e8256c76949d30b6fea0d5a1c9abca80d3251fcf4ec4db0a5ff2ffd43618aa2e3e1694c2a3c579a2665f443ffb1eb0ce33c09d7285687cd55b6ca9918553bfb36a44860e09ffa0604ef4904a034108370195a986fe165c598305eb08599abbb3df31b1d93162397056d9ba5a1ac2812c582aa356310fafb4058abc5f157802e4a9b4bddb16e75b6db105b7dbc838f820539b76949b1648909104efa67ce28b16a738f1be104d2bd142d3ad1b1c953b6020a1f4cbb84d5c49424befbf2e6ac5c593b783a3f4af75477312528fa78dffd82fe493d821e011642bf1135a5be91fef909383953308dcb61b2f35c2ad259acd1a2e953c0ea6a03a97b384e39c94c33d3846c26b4f9f116abe572d5b7cb81886d6adc2d544630fdc1684bfb32972e051b9a2bd0931de63e025813b923944290fe1ebd5264ee4f25569a2088314e8d4ce8b91c7bd602b9d85acc917d60d30d5ef1cbb055b9ff7b0f999b98caea2517d2de334eb436078c90d41e0e34f11b93e3e643389f43b3afdc4f47a7396cbe0b4bf159ff27618cb835aac6699be1fc7ec840b767836a165fb95d06f2cac4fe15b65714ddb8a095ed4a5b57e63d536405931b6c168683763fe07c32aa4130bff787d4d440746a2dbfc584a502d809076b257482abf7f8ead7741c82b54c41acd41581148aeb4149b0c6eeb39ef7ba091c2e8bc72583b2fdf8ce7fad1bc05aefd6db0360c644a9760a9729a88ee4b2ab123d7238c12435b9f3b4660e74c0fd4a9b00aa614453d84fea01f779e5a924f8e79630a8bb6561ae19c7bc8d88b9d823b98285fdd65d4cc05e443944ed5d3cd4f46c7cafd1dd5deaa519772dd24f508bd2d588a832d5689119a2d506ff11dbf37d57a24e35ff38da18af07eaff5775d12dfe795fd3e1f0ec83c5f283d6cd76532519a15a18d93431893b1b88929159bf8fd21f62b30f4e37d540baab0e30ff3349a08d627ac19303fcae8b8e3fe44eceb66d30697c7ea051bf5afdcd8bfc00d49c8d36164ec9194a78a4d8b78826863e93b6a810354861f4a35ec12e5ac102f74e390d9c0227e67acbbe3254e5b892786e3a88a383ea9726485854a319569a678fa70392cee90c9aa83eee8df6800565bb8e083e78a064c0f8b863120efd799ea57d3073663c0d0e7bfb9b717ca1d6372fdf75a77fd9677791cb899fc8033d6d806de1e6aaeef525ea909666316d9d604c1207cbeb6f427c3acc1b02cf59704fc65135703f2a9529bb2c8fec992c4de53e54b029c3f2a5fdbec1008d1a70dce0c11251003ce62af712b9e4abe631902485404e4933f346f1b4467fceb65baf776d0078aae6a2a1f95b85a441b635663c75b485a8a7cb9a5c12192ac874d940e2d9b88cc05a2db9b5b35df769925da508112ab0b8f64a1408633fd0d81810baf2c846b222736bd826c8cf905b2c35633d6013f5565e0a5ec1492e99613f53530799052a0d70023339d1c394fdf9f73a590a2faf68390d2a823bc3e47a173782b03dacbdadaef1e67fb47a7cad71b6067ce5b5e41fc20ea1fed28578e9bdfa99faa657a754488ed3fc084faa7a05b0f6eb66da0a28e9ab26bb319fa4ee993de840948f94dc1d68d926b783a0bd3396a89970b2c2595de8148e87b87c21f664618af4f567115d403715c3d7d2f66d7a90de2c5237893a4c18c20494e3faf94485ed39ecfe972c36acef0d7ee57bf8755924c790ad02dcc5c4e15aa7db53eb6040244c3ebb7874676782e54dfdddc256018ae6af8cc37450a4cef77f21e2e061062ca0c2a514290c960f5993ec1ce9eea6d09d3293118237e079b6015b966361c3032368174d74ae5cce4148ea2b3690fbd3c28ee544c5c5bd7bc618122979d52c9d3d44eab1f2467f338e695ec5f95998bbe77dffac42bc2809d43a324e0f5feb4ca3d5fd951b7dc8a9e6276ee080079b68849b14c7573cd02c76027a856165d1043acf99554c62fe32896d120974ae71f84986bfa0c28fcc399246bef3ab90f8e55f913aabf339dd7ca6f0861a9ef712e77dd28740615479f39a37e746c7df2b267066d1649fafe0459f665f3d5e7124db43ab1ba5ff94989acc7fe0935e0bbacf718b33103a1355d97ab416d8263ab369e6cf0ee563a77f2f265fc3856b7d54dc0887ed439a421c14f733ec1d6da086536f9539d23cb8026218c5e783423b5f4ac24c8d5d8faa7186dd5ea34afe299e6dbed73ffa8f415da706442a48808a9342d6209f65ca11eba76f8ef26db890da76671971f65bce9e6112c8aa92523dd5295d748e28857acff408c161c0513b37b855a8afb0764d118815bb1b68f8f09156641f7eea994ddea20f4062607b9919d041c880b71592402a4d5b92464b239caf431a99dc67787e76b8e1d7337af004bcb88473cd16b3f7640e8aaa59ad4609f060a2cdc71a4b3ed22c1506a7050a63bd8ed68aa58a8109980bb3f2b9f9fba9599d7620b8c25e8aee739095789af83529cfbfce5941d7f14c8ae30583deafdc7c25fc34e75bbed6ce4f6b47e9647c12333ce08c7db77dc94161cfc43f7ea0bba39def8bf8ae61c6fdcc0de6308af963c6d9ef43916d0cd2cedb970d5937c9fdd4b888cc66e99363b5a04ae62a4349f01c3933ada64080741b1781754e4a722303faef69e382cd9823177e4d5ac83e76017124a7b1a41bcdbb9c4209e7b42c",
+ "eaae1c53919e2029c137a80f91704d0871be2c1870d0333d8bcf7f94",
+ "4c434cddb399e52457113cc7e16f046c3f8301f5b6c296979f8a091a9ea557ea",
+ "b633c1a0e1ddf4727b149b3d",
+ "f1de487001a580cee6edadb1ef6b700c861a70c6ef16274447b8c61bb10d2d1efbf104d5f7d7172c6a5cf9c06d886165a2919ee9418e2e8f803d47832dae5ef232ee300d1f973a6298c22d777a1b16264353cc731a7a683cfe31e0abc704460788c555c0c24f281b81d7761235a955c736f17f213a896b40a034609ca8456ec3cf5906d01121b7580ce19d89347b6a59c81add318df487b2442a7a8b5e30df78467abbf46bcd5ee5b994a39ca5bd8846caba6f02f4f1335b73d4e20be0b6ad85966f86d1bb857713ebf947ae936782f1f4929498bbd66bdd5ad6fa252364a5a6b46180e93b54cc321b3cf63cf23d55392475c6b8c8c9dc707924b55544151c7c55ae0bf391f793e52bed70829fcd32b2926600f65be0943d6a9a96547675426b0dca9cc7b0f5dbc9d5439d0281014c6c159d055d6bd89d67828ba7fd2a0570ba82996037f7dcce297fe6518331270f6fd5ee63d406cc5081472bc5f2298a9208dba9398ccf807ce9af982885897715b3c5742456f756d79c70434a9baf7b4b6664c9d9f5696c5256b74099e593f97a2d4a469cb3430d0c3eb06083398cabd58af598945a85c9235a3fdd9ba7686e54d0de9afb594b1bb030be8e6bb839f6b45699dbcd2f771db64b0c62bbf6c8672fb412d60c00b3d87f82ffff6512e8308877573323c5a2d6a216ce3e2ce07c9763835ae59d44d7958fd873e3995b62b1b347e489ce86e023ae27a6cb03ddec27a38fb233499a714acd89232a91d38abce30299f38f437f7a46df647f2be862c1e7bcc1e4263c2147b13ee5b345b7fcb973f3ac71db8bc12309f67ddb62659bd73fbd20664eadcd23a79233386aeec1a6fcc8c592053954ee53826cb9b6bba22400648887311cdfa5414c96d5956fe193a3729be1434d923a3f9849f6c419f77ea05fb72f3c4f75ccec03b7f7aef8c8e55c8c5480ee505ae1a7594e6a911dfbc39dbb0ae8656f5972eb644c64203a920fe0078f3d050cc5666ed9747c23df7853d6913005d0156e741a5ead3bb1b22e5bd802c303a73a961f0b60d0fa698041c22577b44eba5d6071de4b545d9f5de24944c151de6a189bfdc223e0507c74ff929f06a2e7497e8c63073294b4aba110a006a6e9510a9617405d9ee711831e085940006761822672549d1d1c70e50002c2227f6f304b9a7f11dc05751be2dfd297087044d2e20ecfa0c091478d62c1bf5f0aacd25bb0384853762a51144b77d30418b633c4c10a6eda7b2eac46905641da0b685f85349749a91cdbaa4027fc50eb97a7dea9e8cbb5b5f386ace0363803ba579cd16ef80dc40ba1044b4ecd0e81e382635d7855e2341b18e0ca705ff46990282fe25093a248ca04a1fff64ebee25065350ea4b9e5990da4dd2e28688ab08b6d6fcb54d70f6d74fd7e5e05d21c12f5b140839aa966aea9ee094a923ee5ec704b5b709ff009c20ed89a75468c48b505d07c7a5ba1ad54ed610886c9d84468eaa598c71b017578404c909dbca431703e0cb1cfb975a696a1677bc015a75db007eccdcb21b9e5e119c48f148c2cffcf29e245e52156ba5ba0a8b0031570e4cbe7b3ac4646353594f0c4a9424c9d97845c5e1a4b4016df9be8df3013e5269484cf32258849afbdd733189ea11783f0f64d3aba9b4f48818011e868cc03ecaa44ff0ab83ed12981a6df445294ff672f3a16d6e0d19b90007d4646e967e0fb1763b3c879f548e1103a75c94f3a7f72be78555eafc086c1c58d1761aac60b843704f234c55b951a1303a12705f2120f784c2bc1494432a94c835d908f0edd5cbb169afd2d38087ca5bc5e5df9c3bd970dd2da4fb2a00933538148ebf669a20b5beef0402e53dbfc3a0f289b33b41ca27eb2f036a22f0d02e0617bd01e8c74be264515c9b46b9ab6fc67403a35837844580794088a9d3c14ad9309435daa0396f48017be524856ab6c191350529962ead64bab33171a01bb3c144b23bed406cb05102c693ce5df36eb541c47e871acf56f2b47de687eb9b3511ae83d06b1f69fbcef3225c3469c304741437fcd0ff4ae3484c117f51d24b6ae1363beb7d85d9b61e01e3dee901b90f2d3272eedb384ddb4d3b9594b9c0926595e500f8ce2e5cd407bd7a4e2c8e6f4315bf693e8c961ba5b8a6c7f5030c68a6b995e9d3f9eaee9eebc9d679eaf72a5f1cb6b2fc66edc7dffa2370dd778ea7ff446121999afba7bb35ceabf626c6269bc466d65f7f812c663bcb2fd87d3e09ab7d71e727f66d20ec48a5d2bf0aaf0aca05d1546d6e974f90df85c1393e3d45731f71ec7b5cb6cfb4e5c29976ead6944a99df2045056e198b19905362d4e9b765adb65eb089233a8b3777352665489c9456cceed593c6590d9f3cc4024d0bb92e1a0dc619bf8ae65be77456c18f8171e4d2d846073cf5c57ba93adbc0db9799e3d98934aa6899372acfa4d7d2ea32e20164b79c71d7bd33c94f9a781a25cbcafe563462eeacaec0e8d9d6c0199de85558a3a05d1ee3483351915d8a4e65ca0ab129a2386a9e26aff9b912c588babbcf25f8c467145061b9b8fbbff19d8c6ded8527d457be7c926c8f490bbcd627b3002044b7729a52e94147f95772591616f6074047e758597f410b3100f9efafaa4137dedfd0edfa85b0927804f0b4fcea1a174622116222004d42b36c2c73d04781f2f49d080f351e57154a3980005bcfb0ea34288e2fafec5bfd01e1f7901b3efc71ae58bf8df4cd7c045856103b77bd78073f0174aaaef4a3c0e8b5b46dc92db55478f012dc1b7d513e215e735573257f105d2390b5366f49b61809033c13ed4e1ebe19ab89313c947f2585f0788a0c5de90b41ad0dbbfc604a0d414d0e5390a0f3c9616cfce4097e38e05888b8bc6e55e40368bacdba7e5b76f4bd8fe619746155c30b38807a1ad325b00ecc3dbcf23014e79f1c39af7cdd0dc7ea58ce733e6611b7eae069deb047aeadfc21960e614db19d2e7e0905a9873268b9a24f856c28059321a742cd6cb3d1527",
+ },
+ {
+ "c89c3cadc094bffd5ba06c600dabe30ea19ad037316fc13b895fe0e14ac8841264c1bf25557e22b01f8e102c3af43adb8e0a12bf79d3fa0232dae37ca3688e07294e2c7ecc4e2eebdd3f17173351f2c15b0480d4d77bd70955ba86f82214004b622cc92f7bf81a5837326f6a83612bdf65abb33c268a457c45cb7467e074b342a17c711c748c74abbee31541444020a9ecd4e5125e2a8ea3f6030bd677be18183a8a34af16a85ad48b7015cfb036789c0a5daf68883d0c7e401754b8d56cd00ff605be0cad19e03989f608392c81d636de859e66c2aae403c138bb96a58ba69b9064a83e7d8877067e7f40aa0016e0df9b7f455d292a60eb621b8107a727a3378c4b7509d3ec10526c50fc6c66dd4b015c915e85bbbf701ddaf2258119c8b9a5132eafe61bbf38870f35f375123f766ed0d4f38b9364a86e56cdef6f95a815a8d7c48ff283c77992fc6c070eab7d7c7b517006e5d4af532a7c429912ebaebac27249b4f5112d870d998e1c450b98c05d08c742dc769506f2d7a004c24ebf84c10838b619653e27ffcc4344d8db0435e4cb77c0410cc734e36738a6b5f72a7600632d19c86b40c737830b0f5f104443dbbb031dc7ca51ab318951e7817b5d81de8a9aa7f5db6e2d5e7a3cbd8a8100653c048204ced3af005d00e7de7b445f5acff901c4d46ff133e92ef073aff1d9ebf55befc32f9ec38c9eaa6a1aefc974bec2758297e474cacea2ba4151ab1a3ca0762c64a5ca273169d29b83c164f77f266c01bd5075871e17426068ed7aa58ef0d1f2959b19c604eb6187acc57e2becea2da93ba23159ba73b9226034c7ee2498e0ba34fa8038e5e2c092a73ebd9329ea3d648d6ebd47e1776941ab3130cfc91089fd0a0a36f0ecf68293343f275d2a64c1b7d27ffeb3f667f4a19824706235fa5f3f04952ff08bb183c0f1aa1d1b0edfd2e05ed093543788f5d0ac6532e15f912163275053b202d772f381900e906fe070cdb00421e78c16b7387be91adb7b3b3ea28b92548d69c780ea578e7ac66eeb931eefb4067bcabdb345a7cd2022085fc494f118215adfa2443630bffc9faa8fbd9943c3140d81c7532895734a9dd20e31c326531d06f5623c252139c4cbc882640c457819c63f6ceed4e03872b246a3766df69373ebf5af1116e8d5e1b15745bd9dbdd663fd4352d1238a43d5d1e74b3edddfb1c9d460daeb49afccfa0712b7a4cf8d07ccd0599ef3e4e1c9b5c814f3a6f3a46fc80449b34df87f47ff91fea3618cab2d5c04cb50e8ad199d752d901b21348ae939d39c86cc1bcecbadcc6f0e581a3bb51e070507b41ea4294b35456c69cf55a2a3f1296f0df73abac3a9c81cc303d1e20ad6e9bef48de83fc22dac2cfc01ce9ff3f70e00ee49bab2f282ceb6859f989075814e690e36a8d16354fd6056cbff49c30e49b1570363498531ff0ad0979a4518e9ae271f57f883abf5e301c0e24a83f09335479698911bca90269a28c0e040a98e67c9e55f4c91542f921511dd980270cd490766da22306b48ca9309aad3b2393b7b1e9ac7afeff64204081f9c0a8f6a5396d02eb9009901ca2c0a75ffbdae3a38ccd5007cc4f6bec8fedd64086cce5c039e8abc9e23bd694fc8de4e858c89bd585ebdd422b492eab26f4ebbdc1d17dfbba19b5ac458c31320a161a52dea638548205a6ad4ec54875ca34238c059177bfab2d5be0a98d12b3932d0661d33ec655446d0283224af8ec7f1c6874add03448fd8029a71d3c5aa06951123c9fd881d435845757df50444e6cacc31a8cf7537a778d1184b96c3512cd474f5d1fd1214555789d24c8d173358e36400b2d937595109729d9f35eecb0963c0da60d2eeb52a778876059fa95d820d5d34e7948d389dffd53d34c4083d27c917879b053cc57dc43c8263e5dfe5f33c19dad0a7126ea6e8abdbacb318d37c305a183596ddb25b1934beff13a4f24fbdcc2064de8e0bc639e672ecfe45692e9f8164365e1691784b4f775ef369aeb135ce15135c20da95064c810592ea33316b9767caaef842f948b9573b2205ec57d3026a2f2244c42991462e233061549cf9bc66a7b4a8a0fc61f73883fd24dad02644004989c4721a0aa03d3b0191d7fa4d3da102e541fe463936c9365ba30681e706ca70cb3c8ad5dcc710de59e7d8a6247aa809bba74ff4dd182a38bb31baa337841302c19ed89d65e87bbed05465f4ce0dfe89b44d7e9266a8ca21d984c41109d813ca76eb67dbd4e39aa437ff98050c968ec1e40c534ab51d6b8ea2309fab08b3757e9edc5972bff316f6f2affbff458ac0299613734b30dfdad20f797d172cf295cbcfee3d8ee25485d40380d3480a9372a1a6e5ecd7c4c6a9d34027ea6c197f37e86e757750c9fc24cc7cf814878b8628326c140930dbb2041bd9ee87f36ebfdbdc34522cfd4e50c9cb48dd52d4647a06d08e0f0069c104849bf30c8e61cb693dffbc69fc0ab9c5d502a227d606a1dcd630ebd799acdb1e47ce2ad52ff53f6cf4fbd5f0058fb5db915702675ea44334d42e0b6ddae78b22b5b5f7e5aa36519e31278e37b64312479b14aef9b8f12d8c1f39faf920851bd53b13bae5490c847b3312b2e956c430f1d8deea91cf171dee5017e7709d0346d81600bd5f0c41da3f548c28aa50589b293685ba059cd7f3edefdb5d8cdea364f4a42153b0632ef0b7ba18610b71fc34a781eead1dc5a00ab47b6840590ba44dafc6a16029cf50e089684194d93dc881beb62edb7ccee6304a4e71a35915f109db92690461b9e4ea21257ffb62477c20feaafc7a78e2aac2301b66893157920ce9fb114ab4f534d61bb3d17dfb4d9ef9f79a736f7c1d32ac3998356aefc876d8c38722787d564e980a1f15056cb3fe634d71d2c98e0475c79cab318b73a863362f85aeacdcfc44e61b5aeb870de9ea5b5abd24e8c19ab05e45e1e9b8894deeb9d29d65ae99aa94b5047f3c1168276cc2e491aba52b5b03703ced28c63a167f0cb3e4bb4d8e4f0292cf3ea4376510fa49a1a5efcc00f23c3cdf6402197b81262e66e17bf4307d87ffbc2b37213b316bddd65aa9d64ce6122c4a1545c5966bf4fc4c6ff17ded787ca9a3b3cadee435bbba8f6590dc4ba30895b84d5b4eb94f4b05be3c",
+ "82abb4ff5457b21f359754f151e456e2c0a185c8363d15918bcee0d6d49f12564ac655",
+ "b63b577e719494032062e3f63682098dcdcfe26cedea2a40893c847a331e4ce9",
+ "9f1d08be539f1244f0f69ad2",
+ "88dcdb0309f8c4a96ad5560f8210eda1f5afb31b85b7a8b15525777748967d4ed77c063f65d64ef19b31044f2adc690f5e457faa1abe2e127b38c626eaa94053c9ae1b6b4d0db1f02c8404b50f58210cc9fcc6fa4ecc615631da631031cd6253b4a13a3e88295ffdc775fd4bdf29655d9780dbe02b0a82aad4c4088e90b51f170909c0f98ff93ca3926067ec94be05841603db4f913b7025a9ee34b8d8bc629ed827a2a9857e0814d36b83cba21e670f8f94ceb4be5757e0b8782895b5d8605868e4f584b5bb6a5f3a94edd9b23fc2b6fa06914aec970c260fc370aa245ca68888c90c43eecb68474c9e45c53a7da055f5bfe39b56769fa56264dc8bf4c1616e30262bd501ff9fc5cd78f73ad89e093feba0393a11c6b2cbca765ba025c40dd0417dfa644fce96db5a0362235ad37a317145e7b5f3c7213c7fb3c393be57a1cb55035f06da1f0bf665653c5fe8a0f3ca67dbcbfc59852694d34819d0978cd09b508d103017168f6848258493be737cc24c2112f2afeabf41038bea1f74bc8656d9910b77d33cc691a0d9b12f7c518ecef93423cb4871949a518d2f06e5427823324275b97110f8f88b0d14788741e617f4b194e679a1627da50376a08d4f23b005c0446b46d4f534ed85e4692e7946ec818437089425ee30e47de995e8774b61003801de67939d9fed7bf0cdaf625798d0d0d04a61a2482217b890168e36f20cf1d6b81f9daf1a49a781567c4363ac2f3ebf0252d5adfbed17f98cc264ed2765aa279b7437410ee8b4cf42932e5055f4884deefd2a979ab1328f97cb750b3b7e4615b9c1c61659c90a5ff6d1c736e785587ec85040fb2c6decf789c2707974bfcbd0c7f699627b31e0762321d55bcc6acf1aabbd44abd7766d397bfbb68c424b311611d9eeb6598ca3126f569f688455da8d5ab86eb01f9c96186858c4b5e447aa2b9ca11aa5453f731beed4e09f95bb7376e200212e2f03551b8b09a19d6910f25898d692bc20bf6ed3ac9a0276db560de5c9e264f4db8fec6577042fbbd4510bb7070086508ac451a1fda26582c259412fbf1bd60cd5e921160c2604fde559b5ed4df52b805010b225f999450adadc6e108b70f169a3d8da6efbe1cce1c4908b004e928e3cdfdd0b4c5f742fd72a11c9585aa3517486201b6d9a98739b77970a88072750d29d005a291546f13b576b4249d71f04a9abf8f653ca206d98f738af2a1203bf0975f0a40138df054ee834ab73a3b1d7036567369a7ae15f808904e08adfc84b34a0e1356009d8a82e51c3e8f2170908179bfe47be8ad819cb12e85b6b76bba7c9b9398dfc00f550e32c171b4d5f2d9676063efee0b0b49660c10260ce052dd00addc3359e35c25dc33066d4b05bec7d93f71e0ad7d5ab83d844c7f33137894327f464260688ea4ce9847046e7dd0bfa48d4e15277a9586b4742daf0c5ecc59aceea6867068b03c20aad38d04a814472287d809a9285cd4dcdbf68f3f4ffb794701f4c265b2dff4aee55c9815938689162e08309df150538e60dccc03d495adcc560fb831444b922a6375845cef5dec56eff2910b5bde5f25f0e550ab5a13205de91d20896fe04a8ecc2c83d1371cf381424f8c43d2a5ced374878405f52bb92f4fa3c15d29ec151508488f9b4e42527921e245a8ee4b5d6ee95797f6ec4374d79acd7b467454a1d7eda05a8ae104534b23c46b27581abed6afc3ca555202dda94fc2b93501fe78867730a84f6f726dfd7364bc240b65d6c3022a04e09c89e36a809fbf244cc5522315110e9e33c8a4e1f1396e3e51fcdd53d9ae94fe7bf6c6ccef0ce02048a11441de3c25aa9787c577501977e486f8dfaa4c81e3183e648311148ce5cf3de56878847a9d14c0645777022c158670377dd9553eb63eb17e19ebb06202be8fd9bc2b24878cc86f9938e5996751ad9ca04b636497199f7f27dfa0f5ba2a01c3a491bec6dc5113d127f6aae38fa07ce7539a0c1817f7f0de0da538f4d85ffa394784a42eb50994e28530e3997e3345db28bafbb836fa463d34146d9f46d8d2b28b3954b9bc7f84046828e9b55e2fd663e562aa95caa97873f48f0a003d2251fb3ddbce0b6072fc17e0d3f99b655b8f41e8e6986ef7526544222e2d402489eabed4c219540605b9f5dd321ad902708601e85bc874c11efedd072aab7e10272c87b08b9457223de9fbc3abc2d1346656a524e9c67d79d4053c4257e886d6b430f5b7f57b2e5e92ae69273c1705a3074d5066def69fadea1af8fa9b3bf4890f9cda4b1833e5ed27f22bc4fe4cf452880c7b53320bc7cb748c0af6e7550ffa84e4714ec18d208131ae9e3edc6cd6fa2c60ab8ebc1ee56eafc01fbfba061e55014b9711eb58fdd01f8936d29dd081565de0b175b02989c5ff374e6f58c3383e9bc00d8a93903e6a221c7475e15aaef77594849af877f3807a76e03bdd54ff0b192bf34385d24d858d6f454810ee48141d73e3acf1aa3d19cd4c723a634cd8e25b4fb604c744e408dfd82961e46e8444f001d0991af24b3b6ec57ba41fb45122afc73ec6b25f501f1abd46181247945729337bf5083e5821968502a5a696043ee696c290095feac000957f968ac61ccb572ab2f37008830ab9a81d02456190af99873450b52df1888c3d8b6b13df65a9bb36a4b6d0538a0f179daebca2bed6f94b4670560fc5471c3770f2d004b6a138b8243068d754fd246e9881242638c6675f1611f237146f6e0f72ff2fba96f479fe0a662a81f40928f5400a0bbfb5ed07a87f457d5febdbdd6f323e2a59f749e6fc8a51d08b023734c762a91cc517401be57ffdf6a52b9174ea153abf2190ae2642955c3c02b4a15d72456c9d2f323de6fabbf56dfa3b566f1aa688c86b662bd34cf2511cc4a30621b6f1f1ac382bc1c4fa4c0d4d5a30ae90a5e54a9fb4afc1475e7c612eeb7f0e09e894c2004cd04126df9359d525d7f090e4b531916207c38c3512341c84218c86fc50061043ba1b89ddfb21cd756b391cb53e8c1cd55352be05efe562669e3986c022e30c79a97bdf087889a392e6da0d72cc7ea208aaf23408df23f3a9ea9bf9a935e49c9994a37a5dd0faf1267d5f7db47cf64ae1d3ec166466b2f882eb21698aa375cb50146c0e660e9bbb38d7bbc1c1c6d8333f7031d6a",
+ },
+ {
+ "68ca38fccd585eb14f953358220027046c14ef965478d3d8f206f63fef4fe3571a1b53e774b298c347cc1b69cc426d39575ccfabd5a284c7a87a0698cae9efe40543cb79f5643c3c3057a5fc991235f06f059c44a7200b509a12e864fbd748001a14790f78b54ba80cf0a4a603da9672df32b68652c1d6edd3be51cf969acfb0ae49c026fe0bce0bfc72b1ff4c47712b7a27b2cce888b9bc470b8bdda55a8d53a34d79a25947ad55b95e5406a5c5311fece3ecd46ca590b3b01b9055761da8196b21bbc468681922c66d286c32598b1e3d77f2a91d835ccd9eec231409cb2e74ede9385552517718be9f84f0f9100e368701dfa4843b7222279537306065a54d4edda3a02f1ab9edba3ddeb34dece9d5edc8797103eb942a80cb5ae130ff2e7eddd11f0cecd8f9a615d75963c44238b10ab1230d9db7371d8291feb2912d306efe4f7aea2773903d4be9a00f2bd8c03589e342269a79441c0b42ce9c6fff0a6e4e845876f7e9b342d25351fe2b1233b4f576db90ef1facfa617b96d17aa03fc824973e1c80f15e5344b0516fc28424b7faff47ea1ef4e47f6f7b50e91b8fb14027f05ca7e1bafa266a4b952cd0b9e4cab82bb4d61f99568e14a6772f36296f5d19cb04fa86ff20f04ab61d1a6f01e5282c99fe4c3254da46fb5276317be58e94b1928e3791af27dc6544f6d445dbfc7275fbbea74f98ee4aea647b654909f9fa9c88312d3759099c9d0070e3db6d55506813f8b7abe602964a7dfb9387f58e237dbf50b4185a50b65ac099352dee8695017e4dac644f42aecc3e415333cf76b08fc764a721b45d7b74f6b0a2e43637e5b4849218d3d4c6a01208f345d76af56631590e520d6bcd82627d2446b45b2c68e0be81b3924753a54f47ea27b1e08de2399b34470701c9697eedaf3248db9b28991cdc2c632fd1b376bbda279b6709d5033d1c0a3ee573bdd222ef1afe8a4397a61fc30a4e94bdc55097ecebfef6c00133dc0b72c17e2f93a11eae9fa9f1364f10fa595e8e1623dead10caac068aad3967b9ab2837dabcd8f96a77a25baef16ba84bc93661ed150ffddfbb0369683cd021e8f959c2b531bb1fa95d7a574fe5ff9aae35fb9b4a0a9829c59b932ed8634fc05ca88211da3e30839baadaea8fd9843e0e80d9598a7282500d157ee579cda5d57628e7506617d301c2adec5186708f94f069ed7bdb70cbe88549cefe1673d48c9bbbdc01d2af03945cefe6e25f757750de64cbb9d496a25adf7058f5e32c827fe75e80ba0e834e6a72344dd2aac4228828ed42fd83e4576254af5737dcd9b6c338377d46baccb02d00fdffaac12133ea0e75e791593ef3aded4ae4c9249b8d5cd20aa28cd652b9d750b88111d9b4fbe729e27882206b2f0eb614d7daaf6436816fd80d452ac71c7a7f9e8c595287407c6ab9fe8a242e98da4270b4f1d4ea7243c27f89ed46a567c643f31f967b5f12e518106f3d3e08178078cc714cb6e39079631966a9becd6f02c18e983ceeaa2106ba9043f9985b791027eb5dddceed563106bcdbc48a4ac64bd95e87c708a8cdc33811bcd16c35e193203e70ef2bc7203183fbf60d46bc581f1bdfe62387b3e6c0c4a29130d872c3f8b134e7dcfb080e7e03048c49c0e468dbc44eff4b02e50bc6889cf7600fba58c0ee409ce948aa684baef4956fd8fd4a9c4c49e84e2ff314b7900b179fc66f5fb4affb9ef7a6064354fad8c3d2d50e6f2157576f864a843dda8f547955c4d80a73d4a86b7aaeaecea886927a5ba0e97df740ec7e8b70bb650010df55d4b75f478b07b205b560d45de666d84206c1bffd02ab7b8d1c37f21c47d1711b89d16214d8151a8e75eeeb5c54c39e5a855d578708d314240a064051d8b26c6183ce755be38fe9597dd5b5d198532b1db083a4b856b8dd60bf1db197cf1df852eb6daecffd97287a6cdd4c05307722e0fac798507f75b03e9361d5627ecdb56a3b633938fa61b2673efe6c6e768e4e7055e6c1d55c7113efd3e95151b606bbf169f4296455dccb93da370150c54fc11b3682f092f30381c6ecd218a3d9d39442c8bea61d9a71b174a8b2c56e028689380879cafb7c4bc2691dda0cf6ada039755edf93f851446df9f63267f8b8f030c069fabbe6457d4f63575b5905fb927a5a720d52c351bfbc48f12440a91471697e6b2564b1a2b314fa0e6dff090079637287b635d875f120671561102ad27aa83d9f0cee41bf023bcd703ad670b43ae23bf01713650834cc1e95dd486757f0a4f6fc9337bb95738805ad5e756198579c886eb0ee77e4ba957997dde0eecd84e4c9171c84ad8f0cb23c6a289e037f3a8beeea7965ce34fa47cbd727baa4ac9e6dc3baf17049fd2386674b246aca5ef6b8496f1d17a3175f6fee86299232c7fff682f066cbed895155d475bf9fd4b5571d257534c88c93377b1a600d4c280d42aafda975eb32c740073cffa610b5fd2dda7262a2fff5da7a0f3a875c62949e0c9247827d7a49bd8185bc27967124c34b9725ee961bc8102a029786652c2571be6cf33be63cf867c2b48e5826b31b714a415fe05c27f0862a870d8fb33200719ef4ac8530a4ecf2597b4a7f2e66f078a7505803774889a1cf963083c831f46725a1ec5545d8489e53921d81f80ef99f5e51a2d5992c7769c2a7ec8bd8e0f2fd81de53c7b69b650a2d838b269185c5efd668c470943bd956e3c5e1bba5d3b927b10cee68a75372d4d6fdfa6782c05659281bc9bc56a2123967f4f50cc7ae3379ba21e1617553354b5030b3d3f0092c1824f5d47b97e6b4fedaa90aa2573e1b115ffc72d44fa8209fd8d372c8dc9ee00193b47c2a9a302875da331731713243d02eb5a57d5dc51c35988ffd742ddd75c191f1eb2c2214a1fc47b82db8ea708818262d9583f2b1b98a40b6ff6e94742f25661a51882ef28475aab12d9422b6ac48e341cbd6f38460333b5fa1cfd4d0f43aeb46c21938468fe3f7bc771972246156652d2c58b18c8cecec2dbbc0feb0fae9f6bc022e029111f94e8913c0ad741612a1426b53cff73fbb24fb7b22ab750ba1310ecf339fe12ced6a3fae17b4c429550794a8d68be891b0e30cd28e81de2fb2ecfee58bdf11794951276005eb8a5af21e03c8aaeb595ace652c5ce60a8b98f6897d82698ffbb2e02213e50d9d3f00bb42c8652d22bffb87ec576ef6e206ed6c846fd5136a87f38c9ad899371799f35a6258878418830b04da79fabd80e7290456fe17c0850a4c20e2e657f97f4a53e1a3db65bb5e71bf38eab9f56aa11e6ef71c85b8a28f04c271",
+ "ea196b6a64be4b0799b1c2f72281300c3a2577db44e5d3606b8b02fa8fc46c7aded7e442",
+ "7c86a2c06b7520d60023d18fe73d09c514ed07a91d50d8fd5bff00e7550faed1",
+ "952f492fe00b26028c560fc6",
+ "b3f3294815ce461c8843172efe93f73a8254e58a0e71953e35c15aa89a7bd9dfee967853dcbfba73d3b87fa60449cbcabf13b1206d0cb27d2c3fedcfa695b6d41efda37bb6db35449bd470a23787619ee48f981d3f0b1c8e121725b2289b6d67858a4f9ab41683bdaec8a913ca2cc292a9640efe50fb85a1d1f7b286f45d4448f85b3242f45ab44e3281d759db24dfabbae4259f127d6546ecb914d7e93e2c19230c67fba8a6cba6069023ff7ea3d8a170289c2b4391bb97a7b899228d032b36186dfbb29ae8f0e6c06d753f4c6b21982d49ee682bef50a5c2c8434510c5fa2b9c0349592f33f8d7ad6f7243d42b292aee6d210c61e3f898875b91a17a89148275031b74cb34e628d7b701775dbfcf87c79ab279a73dad14d8eed365eb9f29a007b7d2ccc07ceb8cdcdaece67fa0166e135c9a4b939426882eeca98ab887ed2e4888bbebd5afc9f2da3e9162527262b0fa85903246bc8b80df3060c890ebaa516781a2b2a138b98001287e12a9c68471912dd297bc0beadccdc31a27b7c726baf31510cd355a28e4ef786b30084af66ef135909795aa73814cbbc6552270d5e11d46e9497ba30d6d8cecf343d16e7e3357bc9bbfbc7c1dcaa5fafd8a9b07056129da02e6228886463474c5af1d670bc14cf2868b816cc71578ad807a37477341c8192bfc2e8b1f7bfd58827e041f70384f92bb4c6acc415dde5099a1c2b27b709f9e53d1dab07c87a042ca4af7a2a6ee57b37bf2bb42259d372ecfeaf1dc55ac3a9f211f16fef3b2d5f11dc19fd1f425c14779580b2501ec6e0a84220e7e12baf9e0fee3e8cf499a7fba6721a746f598f04ee8ab4df31fb8fa5ce2d2419d5551155c009f2780cdd225ec2c19f94fb9c8b785ad4574b4da766eabfa696a1994e64a2518d1bcade6390cc683a6e80cf8b163c3e58cfa1134ee743079347f08a89c81478668df32ce9cdd7b853db5cf7af13436f3bbb11bcfa8f6b6d727a1df84f99fb3a5c248b8fd5baf669b68fd9af45298030f3251bf0351fa9b58b0b9fba53ecfd838300790ebd689744c1b7b333fbed76c8fb96fc669ecc6695ff5bf8379dd2a3c270af858cc60894be8922d69fb9707bd2a7825f2eec4a5056e5e91714f4dcfa86974259fcbfd5f20d55923a0a9936fb20e5ae9670e2019336e15f530c0be449fe355a7a02c0938d60720d5b8f4f59d2e4213ad5251c6058312b43d47c44ffc8946a98797f5ace279d3e126da63633c0eff1c412febdd47817aaee466c639e43637c1e179f606780ab490d3f0b3c2d79709f1262305fc87c02f68da2dc32f8c544e7b358c3a5d2c27986a19d13fe736c60a3524e94caa55e853eedeece985d16bfa6c487bed6583436cf82077fcdcf90a05f49db50588f46550f7a0c3a1cfca902d66d25dba8d2c53bb5557cc1d87c8a407898b3c30c4f0852df92d839859c191228d0a47324ea9ec2e0ae84513cbe4ff4aff85e77b8587f1044bcb9775099ebc2f28fbcd1cad58a8ce1f072f2228f559fbfdd8405d86f8262c27c3d95e01016b343c6a4e59dec81b59bb6e3c6109a4cffffa85e9752ed2149b5624417c0dfd1a27bd2630bf59814f15820c43bfa317be59ef6f433c95e8be154a8ae94765bcedadebb717f0d8c24e01e1952bd104ba9620f067554ae0faeb78f13c622c45d97b2b5774a3e30cb07f2cf0e8b19d1266d8a8861f3772305e24ec5c9cb714806c7d705a3bed6385f8be4e12562e17ec3df01afb4ef6f7427c48a1bc0e64fc65eb1c3d3ff2d6687e4c275a019f5ab5c63bbe47e3680fb1802d5835c4d494f0f394de1ae47f81eef005127d0971c4589c456ae6a69855f35635c28b590c1b93f155fabcab59b6c7cd8ea1c4ed1f67093aa782c54329cdcf9bf84a40400de707b894587d6e08cf7fd72fa45b6709a26e97ff5ec1269b8042358f872a79e8c2db1c7ebffac014d6b6f71b0c1c1945ddedaf5b6911668059b61b55eea4737aa307c829309c9ea548fba2bede023849bd61b5a467cd1ab1c61205ce64301e2531e5d58d03c74ecdafe1f5b74627be8716cad0d0a0be60984c9f9dfeae24a6c4949170ce2f589326e0a76c447a578ea3a5e4bd9f18884f18843eb1a78aa2fae06a7569a97551b227c34d429c8e1c8c5417ced93c30dcc607cb32a365d87328aaecb4ce57ab8e74f0d9099e267cfb747a3bca9f76b5f6dfb543bc4b5c06c3646062ec14f511058eb2939601913f8a0f1785249cb72b0bb1c12a9508b23caf490537eec53f614f3e06592eb61f75c1cecfa514cf7b500b0375095d5db74556220131390b77d0db72711c0c7229a5769b1d2b3f5105f3a4370beb1cacbd93ce32f89f1fc833c7949211dd204616c013a3399a22f5325f1a00008f4c8ee7dc5bd7476848721fef843123a6213cb0c0b6ae84233ed01a77a115d06e08990b8e60cfa4f41dbc9505cfae76463278b6c6b5ac7c3b83284caaba4a6a1d739c392528ff5b06bc3b82e98060e3001279a44aabaacc661fb14e7581d1235940cbe067c6b386da09454e0467c785ed0b65d41ff4cf36ba5f63d3ff2b45c11c6c22d3ea8ebbf1d52d770e0ebf2ba0c67c7d3641c145cab474a88119335990137fa82a340c2cc8c453752a3aa801127a47aeefe66d1af1a26ee1cd0e6d935bd548f6ce33a9c204be02ba08f9fa03c685665375db7c0c656ddf3e441ddd96b0d2018beff5086cc63339f26bc8332a5e6a1422bfedb69187a3443c23b630a28b02f8075faf3ff2fbeef6cdf02ba4af47a765003de2254b69f487bb5d038759a33ce6885611198b81b0b6fc5d7a531a7a90dbc3556aa758db1657698cb3698b8207b1c1b589efe5d52790667ac483dde9543953c6392d5eb8afdafa205d325e314f810e9c7722cbf5bb76fd6502733149bf21c60717ff5bc366b85ee9f206bb1f330ea72f61a9766090eabde747b1eb9c046cc8713d5a4f8d4b7dcd7c61f2496c5b467608cd9260382b8f11b04c318a5ebb6411a4c7fa060e08c295c6062ac644bd3d10bcbfcfe2e3748eba66f65d904ff21147faa8475f508f21238d42f62b697249b9fceb905127f7684c8130cb8663f09cd25ea038078e1980237389337d1446c3a77bce41b37b50b9c3a020526e7b7b3bef370cd7af71b225700627060eb65693899d277ed130ec5ed9eee75d4886f31aa93bbf302e0c69c9c4499396b43dceb67c02fafaff8b56698308393a03f60babde883f00de2c66831f024fafaf98b2fcf37a9ce01d4f34e95c9408395716dcf83fe86c7a0f5e3e6741c3b63b6ebe9964f1d5005eeb732ce66402007beb3e6a087053",
+ },
+ {
+ "9100c5b2d7c5d5a854bce55e82f94b89a268da7b66357a661dcf75cba10a1b320ae0e4e1a5b989f9766e57f867a3810a0b5b857191ffd7aece4c796f5694a2617486421940cc12b63a6aaea20d2fac188b318a1c3061cafeae436e04d710654b96a864d674768caee03a50ed6afc06f52d90115df1db5c9f1ecaa4f5da094070b1a447251ad3d4fb0e24e87821ee6d4e7e7eac7059080f77d2b36cacbdac1c6e5063946a376865458c4ebdad3c2afcbba8a82b01b03a7882eee42eab904a19e0aead4ae515b02aa2fee74f3a114bf5b9f320baa35b3225491653f4a69e0d864cbbd031d0805b727e42c2b9530dae0c01cfc6a42af8ca730e1d67b4bb743a072f0a38008b937209d534c2284271344340fae76af2b1dd00cf44b48ab8ee92e8f9cae8845e5a8d338f505cd1c19014018bfb6b7dad487e7c8c32064421982c1a63149ec16f2bf4fe7b50cf3ce1e33d6cdea8e98bf067077c9a0ec1bba6edd5090273ca719ebf6f1a0f3e56f021945cff3c468b2dad92a947a06a024758d7505a4a1bcbe9da3a03e97859da99ed36982a7c23572ab60071566b749dc34bee1d9609e87fe32282cc9adba633c9ddcbf359ef4a83a54af5fbb5699978b487954a907dc9739f4b3f3927e66cf0c338e31c272da0cc7795c72dfe60a5b2e73bfd77b8c6ea58122a913910fe29d3360cef5d398f29b024f0dd225183d538bed2b076989aceaac460e3d45e0ca7941897f151261a024b0adf6d5b62429420144497adde6557a3c53b7723471fb760b6a8b1dcc2b327cd939528f5d7bc16ec00ad99df12f082d82bf9fb7318b3d3ce5b84ab1e38d2ebcb6713c03fd0d62bd083c4af96b4316ee02b6953431c261278aabd96e28f81adf7946e3664446135c825e45ed916ccb941350c84523296cadd5360bfe3e16dda75db10da1f710fe796f3456f0911294a4735cf9968656345b9c3049ca47176194c86f36cf702538df699fcffaa254af15b198ac37eed0837b00cd3547e496ecacf6136c6648a535a235059cd75a3bfd0bc49933b379b72e7a8463c268faaf05f0b27256fb179c9d4c923a13ec6600f83aaa2bee13e30c8e676040c06aefc65ba238a29d403f3a8cc164a0bdcaa1a5f54bc1d35fa4efee0c402eccab1e92f6b0cba94e1bd87898a9dd3957a7eafd9d26bf70866450646090833d4b91c032428bdb9097b409305de669a58e44931b7b428bf1a6dc56177cd944b87b04eabd80c64e287a5758c83db26dbc06f0c772335363ea2fb9f19c833644fe3b3fbbbbf5f9d460412d287eef862ae676f258aa45bc8465667601e9ac46e7d77693936c8d67ccde94e54d746b785ad26aa38ca0500105b6870790235e780ac50b9e3198f5fe678ae3a4ff4f1d4a2177edae183daf2de42625845973fc544907e27a90d868f8634c9d529bbaacbd228a5b4ac7fa68ac208e207a022cce4b24a0b5b5791eaddc6b3b3ef6e5dba41855ff531de9bbca0a39ea743c0732772bd32cd15c4b7f28a6ba579d902331a88920fb970aa75114e14b891d42cb947e9eb14feafccf1393796b21099e52b21773adae8e550f93364b1c438dd7d7fc76994c51860b652974d04a7e6ead207610de149f231422595f4e9ced1674d98d0e15ee841143ad8613f804729524e8a5f30d451611676f70a60c5dcc7127497f4d27f35e7ba0e48f98e9022e0deac400e809170970867a1682c7d2f3ef2c632c44568abff76f4f804841ae462c7247147b6e1debe48802674fd55b2ef1be5b4604d5f60c35358c7d773ab3a3ad0ab81868c6044d4e06a48ddbffacddadf813a2ce09aef34f3b60b666245a032f021b87c81fc506166983f25930cff728d399f6dd48ea1c745ad2da7f2cdd9e3ee915f708db0d1f3481018db1c174ea950ed17247bb8ebc065186758e5403bd4d19a445e4a15519326696e4280bcecd1a903f525bbe1e521f94d79df8db4b35f4ef7bd990c0f2c32789a75f95761ca0064bf251fa00b409a58b979e56d2c44bc2302552f118162891bd78272384c739c0c98bbaca3fc46fbb5bfe123eb25df0e27343e38b5a0c2d0774443af91b64b9d4e0649f20290edb84fcedb3bf4ba491bee8754a32716739e5ab64deb6c9888bb9fd2ada1629a59b16934ec5dee3678dcbdcc7fe5e2f3833da9d1281669b1d108837eaae5180396813883de26b957037623825b0675df431fb06b35191c06229f84cc849ccf1b1e079efc2e575331cd77b3297d2908c048b82b7dd14883f3e707bf6ca38f87c19625bec47c11f54988a97205d27ac51a32f19704391af72021b78cc4461386dc3844a1b45596fede3f70e311eba92b1d9ac221d3dc19f3fdd080c2169348f2cc8c9380e12a7ebf69efa37bda4ca6f7e66919b94532ac43022c0518c04d0a8cd99e0cbac88b7a317a1dac5469534b4fbc64080196b44498e149b0a196bb2d6f59392a21c4a4523ec1ff922a52de790e42810fd9355471169d22b734dde4a3361ecd57e271a92132a8b35cfa91d508d45618ad8c6c1ea209405a3d1d3ee1535caeaa3f20546052fc13aff7a584ff79db1726678344098d8563caa2a2abf6fe5aa03d7af49dccf1b17be85600e7cfdbfff54282394b0fbeafda615185574fdff78d59ec2a26dddba1c531a1ac007cabf5be2e2f0a3dedb9174e0a9da5597c9de6d68911fc66ec9d2b1e3fd71ebb83147ab14384ee303d067f47a324a01fc187f54a98f1b0848fdba2ceb3c18936d503e71887d548c4dbc70b7eecac9ead3393f8cb85a84f1484f2e237b36b6d886f54a0f629e8bb05b0c6839c722149a5b541703aeac04e6eb230a5659b12ed0a668d018f75bc94258218c1f5390b9aee4c0b2836cb76a47da649e2425bcf4cc15c4d51d109e5f78cfdb88137c31b2510264e46f1c4eb6e6b3450ad901ff9517b47a24d508844dc85fc5dbcc079e2d09f301691f401ff5f36500cc66f0617eb4dba389d427c7ac778d78438506608f0961f818a2080ea56d0f61c40fc342b49ee63e730df61f757387b9089e1987977b7fa02d87aec2e4be24b8bdf7fb6286d190f9df870944fa910df32f178ab692fa56b071f57366a3981f51800ab416dc4500abcc19e0c6aaeeb9ca063470993ec749a0bcbd07604516b1d51175ebedbaec8986f67a4d9158f75b5f3bcbe86a83220b4fdf12a0242951f94ac7d52882b1b209b82c4749753ea4d46a60bcc4f3eed033bde2d3d20c25cb46fd907f7052217a0a4db143b2efe8875a59441f4d22ef70d0c244b2de6a7e15581e84c860a6326ae3e3aea6d3972e2de0623d2d852c9e65eed318bd3d86d29595575df60d9050e1740f884796b6657718a294adcf2303adf61c6b23933db93885172e82a78f741b8efc6315a2c88ccb6b11692a346cd82a79334e0c610734e61e6378b5e2ecc161d924778bfcf4475805a0823a0d5a54768d9272ee99b7c4a81b3d5dfe1a2f5ff34",
+ "3c77f30bbb698b1571aeb54653fcae2c23dc16be58603f0c361eedd813ec0c4f63005a1e69e533da93e820e6e4ce1308aa29c60289060ebf24fc9738e8a4874ca4e26a0dc79ee75b8607416bd554737f",
+ "0223c0a6052bb3cdc99a284fa169ba76be2df53b677642a606090a9267a60769",
+ "7d3981073f90c6648c5e9c74",
+ "61ec5230306b70113f67b340575b77ef76d521ff75b754d551e4177591a02351ad382b2a4067f2b3af7e8e15431c7133e98be9d8293d17ef40161dbad9a4f1a4f30cdd557bb9a8b03b5f1b277c850e23ecfa0fc2ab1102e4b1d5e836a606883c3d43527fc3aa26955964b144a9a56cafa7b174d72a0635b80e7b4f871ead3838a955a14c4b8c5c3c66fd86a5e4ff10dfaa92105378bbc5f76ad29727e5bc4779ba3e6dc19bf45020f6ce4dfb3400df05cac51577d58eec21b22839b8f055226b204e641783bb3305b4461172f1c1d48eec56fe6f82aae564ac6688d7b0994747d9b23a24418e69f8a4fc548f854f86baacbdec78b7597b138c453349034c8cad2ff272781e0e6799ef2f8addaf18528736aef21ef8c2d213161e36b2c7815fcfc40747626e0165684e46a9a2275c533d548e52a9952a556168195d602ead86f6bd699e97ca59f4cb2050ff148f5bdfec358dc4542ff2f700db9861dfe5ba377ec7fdc0fcb2501e72fe6873c7cc76b95b4f300857f76e6e6e370119f403b556115b19fee7009f4f6675ad2d174f44002e35ddc360f309f20a3a1dbf39d90d7e5fa2106c53afb0bf445e4cede59cb50b8a7a2c0961d00b2c251f2d815309f74a46a424838ee87f1229273ff3b66dfb79e3b1ce11bd60e061e60e3f37bd7ac896b618cd78388590f44b1a276b965a4b95f2e3a7a175b30fb45dc7a71d4b3a1a33e98af30dbb46a217c50046ac21b8bbe9537c02f05a5780c8a5d796bd6424fd9e9f3ed5932069bc050bf4a1898a0ef0ca756aa2e2269b709cc92e0c5192ab49d692143388ede2bde4923c85eae8f59db5c7711dabeb33743c692be6dfebd815456958b5e1384a109f891f433e7b4a1031d4f30478b05766dd97eb964a28f2f7b55aa6c27c7f4ebf4d47ee8709bf99915426b3896412a855798e392e111789213af537cff7a976b4509e0eb6ffbb8e886a3596a242d16d95109b0ff562c624e06636a3611f804f9b2e252afe8a4e5e868b48e9e734f688f2da2012d7fdfe2d3aca75fd74730a85aae90353417fd52b92d28a5098b6af358a096b859859916bcd5a8f779676c6e04ea461fe62872050af92d08cdf1124bde1e889ace3c923457ecfe0a635ec757907a131ad7c2ca3f60e1317880f843c5e63f4ba59ab2882a492dd1e070b070af6f60e18cca29541206a7b267c3f75a5327fd9b8ffc9b36b57b73b36e586541d15c85253e17a2581e8f8a1518f275cc79afcf2b5c88a16e9bf553e757df089b5db90a9dcdc1867b788fe75abb5161dd7ee1cf37d3f0faa793ddb1bbf1eca13f4220ea63af8ef7c0e7144d999ba1c5a983e74d48cef708c1d28d3c0a168ab87d0ef70f381693f0d438ce013ffa2cba65a8cf6b498a7120209564535b7372690329cdbd74eaa76765962720f06aae58338a10064ad80f5a67395db2c31d36b1f5eb777306395f192599d2f737327afdcd9f14b3f24155a3f974915d3302427494fad756703b13afcd1764ef9735e7dbff920f1253cb668e9f40632aea1e0b4620db162138e4a97e6f0729b14be4a7c3256250d5e7423ba1238c704503c51cfc9cb68db7001b2f597a15e77138beea02e11e0bb98a72f2a77b7260e9172fe7e60483114ddd836addd966b69570db5eb26a0cfc4f8a8b80d26357ed51a70165bc0dd11ad7467688025bdb532e7222ea12f23c44d08d111b0ad4acb2f5b3d6b45c387d541ffc84466ed57acacefb1436ef00bcb5b6211dfd0650113ac369b9f3e4891acb2693c377467b1e9c949cc0ea6c4a72ef9292964275ed397cd2b1ed25fe1aa8f47e90cde362392da5e53893eef6e4f61decae1a75e3b726f0596f09c3cba62aa08bea89984b484d5768296a5afa8b0759dceba530a169d22b81979212b3343db35ce4e4766dd251ea6a47f5033cc090d6577efbed441bb4f8944937e812f12ef17ede76df621bd4cfa31567ade18b74583a2b783279150d584ca13c0d4784b70156afdf9be8ae96666b82def888465cd3df349de427d5f5b3572e4f963d33f968e6780e381ca196bc04a6664fe93fdc8558b21b84130dfa2a646950eb2e927885925af46d7a28d1507bcc3c02ba98318bfebe5b9eea1bd47935ad869eb701cbc35a9aef5efad88ff54eb350a34ccef2e159de8e16135b81105bf799fbd86aa11653b5ef93a1ab1c367231d61b42b8bdb4f04d8d05396d53247d51890be9b56c51cb19eec0fd1e6b8cdc98376b6c6b30963ac7ab02656ff94dec0e3a0eb3f3ffb8bebd99d5889df98e6c77093c370373dd5f17871fb334c7eb12c6ca22deb75bdac9eaf24281c965dffe03da9c940e13fb382fb6be332797813710a7cd2e7720f5b9e53fc0d98fcceeea4a8e9f787e670d60bfc4a849f34571e5d09b9e9c28cdf2b2d888eca9bb31ea8b9239bd19dca86880ad3e12b1583acc3a6d1f0a438ce3b5a337487279dc4ead1b214272d455e6a2c8cce4ae3bb29abfdbe77a67ababeaff5dd9c96b17f589cd4615c0209eba5e4b1c7167b4b739ca4b9957185961529d1082226f85068890c94aa1f1c244259ef7b120e40114926a49c4412b67b4caef1ff3ce6f3aea3c6107b830cd34df9f4d73d7d978b6b9d5c481e9d76e83d649e742b098334838fe50d80975fb567642d3b72c461ef3072ebb1d03c0099e97575bae6a12cd2352d9d296351df6965d736d7568c2911394a73d199743526ba54dd62c56c598f4e78495c0172739274c0b8c96755e489765723a24a8704093a94544f6c8764dcd1ce6b4bf2917cfad27d85e4442b4e5bd577ea1a88c2b79d61cc1be01ee9028235b36444483b4e45da1087bf6d45ca540620de5aacc644a0d5c4b807b582c7b058e140eebca539947502bf73c9abc81a0e3a618b39d3a38c4ff7f94767fd7e6b9eb61e629806bc3d183bdade7e369d180dd2f57fef677e22ce41be7224f11723a85a3f1d14d7b72dc98ccb2816b77e625ce3db3e2c5753af8b079e0d63939079a01910ee4699cb405d4d9c60e4ac86a7fda3a4c9c290662afbdb7678c3a84c87ff83470fa8a416511a06d3216a1445699d7ad7e6980491fd596d39762d576b08fcbf0825243c1fc01ec8300780857c429c607113160a8354f6699b368a87983464472a5754fd58943fca6f6779764fbe6cbb510d5280292df02c4a7ed9acec8c95ad67ebcda71d0f519ac18db9b43b28244cd34fe02c5d694df57410eb54c5e1ca0f8501e7776a811d7ee81eb9d8c80b2ca50a012b5eecd5428af965b217e7fdac80be88a01f76d473105b027eb557a523f13c55e1670ff34627667649573e0f19dda41c525a8c96c2866a88bd73e66c786767e1657960f6676d8a22be1c6024158a0f0e4ec761148b5a3d8ea481d8fed94855be82479ba23213190054f937838f0e35e00aa74c89b294c29ea25ad7e96b4b6fa952ea8f1cbe5397b7c86d0b74ccc25e22c88736b045fe86110bffa0679f28a1f27162b51410498cb7",
+ },
+ {
+ "0fcff2c29cbb5cc40bfd2ec573ecf368275ade6a00e5730b77dab17e437b46524b3814e7f470acff6ddac4e0c6b748ed112657120bca1d83a4ce01e74a473995804d7c74bd28732a02370ac8ef52b600790d1284d82f077cfe096448509dddd0eb5944a882b7d384efdd4dde3003dea910f12de82035651e3ec9668e66435f519da3fa1f5bcda34aaaf028daf3068304f7b1ec18e65136241a9db281e011d27db5cc9c1099405a4430821e2488a228805314983966ce5d806b0f014c21d4c9d6a066e63aa6407ed6c29cfa4a3e22ca913762ca9d31271d9c371fe858f3b22e931814cdbe544b9416e88f6026b12bb8e88d8285beaaa35be1c24339b5f567480d7b16cbcf6160e549ef4570a0702889feaa0ebc54b11735735b6e2850d5715e5087291fe8890432784aa219bacaa2b874b075c9628cfed5e76dfe38426f9693f6bfb2de49b710c101b2dabb7c7c74f12de9ba8f75b8645d25629568d12bfbc7eaada63364b6f56569cf21e54c95d6797e9008f3496c506ecfe5d6a010d168fb7f0e2ee3c423492df36a133fffe9b87d7ac070c32cc131fba6089cb7d904b25812e03cd6048504f7ef1736ee00ee6b7aaedb3dda9c6fd6437772fa5076aca9888ce55e906a62875979bd477aabb2f4598d32342aa10a6d187c6768f213117a9ff6d830603bb7b9b475002e20b2237a4055ae6af6b8d70e343e76265188a0f07e7820dfb3d898684d99966d4bb9e78b0e95f5044dcc12810a89a75b11474c8fc06c6e734407db91a072ffeb2be6773a7c6c3ec939514b43daf29feb3aeb7afa57e96d9cf0492d90bb2c7be613f2208f5f5f5898b0a3db8a967a75d065efcabdd83759c88086583bb3d422c6c6425525a1adbd515199dbe71350b77940813618b88fe139153974c80d968ed4d9e3f97a91b7cce250a7c963f880dc38011250b9a131f2b76b677f78fd0e4cd6f1465182fd1d644dc42db0bcad8df4ae9f456841765af8e1c1775abf85a69577ece6f9e9035e36c88be784397479e713be4f5434aa4c166bc4702a4916c0c003a6baecaa182372a30af6dc7e6fc4912d13e662bd327829f6e85340fe130001babaee64d211d6761bcc52993c162a692a10cbe7434310392b64792a777a2b31341995072a6b7d4538cfde74e609dd1019a9f75cec0896186c0f42e3896d15be87aac5b11642f74e11d5c2f7de9f07f848ff543507ea4d73fa8f5683fc6b41831606352c482c7a5a013c51e0db59d824582c595f17a6d2113528943194d6b5aadcead62516507f178cd0f76729cf8b81fce4e0138ab224bfdbb8f16f8ea6196b90ef90a63f0fbdcbdfb5320984be8a80a26b932d1db7ecf870dd67fe838069136ff9b9ae087779e82cacf1b06a7b310ce6c439047c26fcec0364ea87e4549a544d540256cb7c3ef7282fa792aad89e919dd89519fe910501f5ef88da43232e917730e742ac2539d454e066feb9058f56dd246fdbb674dcab636585a788b338ffe41f4190447a65985acb9613d02669ad4ad888004c65acb0ca315752e58f51c9ae9259f20cbe8a668a207a5a46e30891bc909108f53db8bf6f0f11549e621d4cf4763e0035c867bfe9e1192fc421c080b25289a78f4167fe517852efdb6f3ccfe67ad01b4337da2c18f35bdc151c5dc76ee66efd27d5fc784e4e6829bea4f8a41ec8bf61ff998d178ce9f4a10551687337d7705eac6cd7fabb3f2379e31c1d01e4dc63e475f0fb01d9efa3de400b5177e2c2d68f2ead89e9ecad62cfc97fd0ad5b3391d0248dd2fd7c75dcbd802d3463ef0af21eb77b07a3286a72f1e9439f457630159abde7983a5c74f7dda12b40913632afedadb691d62003c70a46664fbd976457544cef8ea863858505b1c596e7f745d4a5fb657b1c694226afa9756c40d9c49425b323ce17a8531c5919b24010f715b5f27a300ee37334931ca9ff5c83c3f0a87713768ebccaaa15e35c56f3536ba945e5d954c94c885c68325bc4b51fb55d96c8d424849ece9a812af0747d5b1dc240f71609439f65acd1c17086e025e376eeb79a7255680cd692fc4b0f5768d1985fe8a1a387074f58c8bfdea8e5c11ed379b845ce2052a5b24ef0c1a658923eb87adf5b01e6aa59ae6937564ef97421722c67404cb9e5fe07d5bfad2e52ebe6cccb41ceb1eb2760545fb6a3582bc4ca572b0aa4e4f0a2ecc56299f3b485d980501a4e010576615ad518fd2d43c1f79aed013ed1f1e1bdb74357aaf7dc84772c9ec62da43c8ffe11a7fb3eeabc3584a936c37b28a438dfe78f89de6b0d5597ac1bc55057544e68fb49a6e505db69af122c2a3ad06219b7f2a2955db0ebf55c06baac5e0efac609436dee484857f75a8421945484ad0c7650a1d3008cc85c938208f19002b7994524878d6ddf85c763a65cb72a09c3a059657459f13cb584bfbd754fbf2de904517092be4f1786b2bde26ae8eb2d884592fc9e84395408f8117e47d1ab30d5fca167bbf07e41a33c230d240e3aac53cda9f251e24659da57d721288252fe7ff3653ae3e47b86209e9344accef0009b99f2ec7b3845558f1d77b89fc9b61ebc1b589fffd3261f71b9631e87541e22ed100e694854bed771358f10fe452fba61875a605b8080cc39e3eac13708e32518f28e60464c38b782c7c7800df63b6e7e95ced9154ea54e32900f6998f38eb1e51c112b6949e2eb11a96b1ea0a68c1e3b5af750a99c9fdb2cae44c5a1d37686ef87b158d19343e23daf00dd558cfb91e6f2e18f8e806abb2faf80d082f657717d08ca4e9c0d30d9bc30b612bcb1a3a3a3843231059dec344c6c04ce625b3fe064092e00175fd9d38f8fe54c4088efe30d211412be01460a6d4ad8d0a618b00a21de0a383de30ccd72f119b27a08958729a999e8aadff21829cbe8cfe398d90476e33db4c64981383a9aeab4a27f3bcb29d4b3d3b3a6ebdd71d3ac546b8658e269959630de176819b153cd53d2091efbddd2cf9178ba6ee98e1a3df9a095db0a2b713a0988a22239f5f08cc8f9abc3d67d9267f54dd5dedbf01bd490b0b09adb21d4e5aa7707e36cf77034f01bf8c7988a2e8dd7046bb2f486878436371f1258f3f7026afee6d7f6560be67103ad098edc9665e00118d4879f58bdd677cf2e6bc631d5c517acbb6db8a1debb4fe7492b7daf0b7ec7df056637c23caf926a1a589bef1db29cd81f547afd0fc9e459f46108ffdfcfdee43515a771c439dbde9177ceaf296a8749be0146cdca2b26be8c2ebd6cfd9b5032b1f7a375307f54c2f622711f8cf8684afaaf17c4da3e83666c40d26adc239c8d1a40024bbf560db5787ed404763d4e70ec6635c6a4b82c10f8ff7ad42217613c57648716ba94cb33129f3789dc86f9c8ec2e8e90e6bba0dfba1bb3dc3215188979a09f33346a6647099ed0e624c9ae10f83da0def840bdb25b718e8d86a616ff46b5327b1f99c22937920f5b5bbd6b53fa0b32f24befa4a7603234e6d94be51f00189a20b15c49e8ee58434a15ae9d10b9cf0204bfa7ab1fd9e006b22bebd22b036c4bb4c9949cb7ecdf01028d9f12466e144b2dbbf64d95d65347013e192d428678f64f0d9306f97208fb00a70d4615229143dd8890725ee3ba6021d38d6359055aa812edaf",
+ "0c5fb7075f5e15a6733737b614bf46871e29417e4b140bae6e10081623f5c52f557c36b4da4b5a4e82920497514b1e6f745fedbf73f86ee10976f82c6cbd5bc13a917514ddd062",
+ "e70954c812cac03e367e99f7b82a6dcc073d2f679f965d524872756ee58654cc",
+ "5f6267f6b3b21423267310e6",
+ "c53868c0fdc14e891ae1bc257fbb13be210a5d9cdbd9d18fe1b474f9a1929dbba3f25222d8fe8c1be3eef22352100064b922fd9642ad128a202b6382ae0a67c8affb0c5bfa1a80e55c1084cc372485243df872d677a80a3ef1ca3589908bca621f6f50133eb762cb9c05775d13db7dd3eb65ffd3eef96e8dd42928facc68390f6bbc50b17e1ef5ea6310d8756dd177be2cceb63a97bcceaa046794915589ca022d90756b02c22e8634c0ed44192abc3b8b1e2814c855ab27aaae3bdd801a73e6209fdd559ceb59a94fd98a66d12a31a643ca2f4b07ed910bc390f77ab89395d5cd1d783d8940dad4447f0452991b209cfcd998b0c814cebd08f9ff15052818bab0bf51c3b72ac1020d3b0974fbdf4ff941b1ab9c01f284fe82f2fd89c0aeb4b9fbb0a74ece08b3debc7b65e7263e2922fd4aba15ae3cba7885d04127c8e06a67f244e7aa4556f8694a5db6653f6e48d6de54f9e4024d25d3236d4f933205b6a358aa1506f832ef7d556c6a1bfe4aabfce51f3b5ac64bf6ab1e665bddb12fe13db9f07a55db3da3886df36ddb89f3a4939b1e9e5b701301570e3d01c0b947f498dcc6af438cc15e6038cb78a78986da0316cab67bca3e28c95e6b7e6b36cae9202cf4a77a0e15d3c3291d267aeee172dd587a944719b9fbe077603b4d39d4302b9a6415aa07af309a5e1cf7a9379552becdb4bc6a0b5c85d2e63bb141c405afc58a8b2b4188b3883a24eedf98dd50fc54725c440ccdb03514a6f37cab49296b6826b6bc7d7ad8cac0a3425eeb6866d94119acdad468cefe162a29e8831c77aa83321e8ae3e20e968cfe51dbf2b63f4e26c61536e6be4f63d61bbd06af38023b15f4fccb8ae0356d924dbf646bff69d1ac0d6e1c7f40b12d6d16e52d1c15958add5708bd38c514e47fe623a67c9ec211cd625b398fa7fd67a23e6e9f65d42dda2bae94524372fbc1a7e0ab3f1c451c126135536e73c573749aa60177dfb68843752b010e2cb9c1afaf51c94a48cf8ac7aab3fb200aaebcedefc6cccb581848da0121af92d9f4be002f0c2beffdfa65c36bec80e7f62d7009b1eb719d24b96e97059e6b50a52662c2c833738849f342391514349305228b29bfa9c7cf2a931558ca8e704c600148a28bd871465b23af499c11784aa45acd051f276d82789c58b14f12619372be4bc3a285f6cee21d65648d18e61752d6e7957736d3385f8ad36702c451c61ed475997d6d9f11c8be5257d8febce329aa701028aa2b5644b8515a95b5e866780e32754ac2e6f2e31b2c04a4ad35cbcbc25b23e9bf49cb1a5d877ca30880741757c29303af8676546760016f1538991b37cf0cd24ad3b1d877e5e1bd083e4b990af6ff5c0b28e530db3f463d21e76c928c8e1ffaa6c045937ea171a9071827a173e231f50e95430ae4895932c88ce048058ce6d0a50ca5c1842506158e98bb2912a61c7991a2256c97cb9050a4bb3ca32594622756291340561e9e584dd2e096263b6ff8eb898ae86f5f24500320d2d0ebb30d84cb4ef876a877dad23a611b39bf0cba5e22f2850e11c298fa23fed40691b83acc87136f8fa540b1dc40d1b0d0bd489ee9dad785c121955a094a2c6bd3353e142c04f7b88b2eb3305fd00d5eddb391b73fa2b16a6357aaa2abf2059ec979bd3ce06d5fff1c325bbe5c833a101615750613047d8155ac0c3a0734cc6aaeae7cb65d7501cb95f9d6d1161d09c961c0681547faf7983ed2efaf4e0fbb87a06169ecff1d0ee540a9223a73f75584441d4669cac09c2dbdb8aa2aed74eb9a2870f2021eb16e5f5c3e79a24d7110af4bece22a1086d27642550cadfa4f0e03f2c032a2745e1c9277a4f67fa4dc74ba056110fed3a63f643567d079c9430b8d5b3bf57a9b3f02d486d870229fee5462043b6bda8d265c745ddc1b8952bf91828d6db2edcfca7051e74df9dd456dca5e04ba469b9ff6a8130aab3903c05659b8f31cf4ba4c22511493a36541ff9d88c708dfb714d52a3c0356543e6efad37530b598bb63c3724772907abe4cad39c896c62daf5b30cd7d37eb36a7be2494353028c76e8d148b018c7bb755c45d2a33f61944071bae8316881e9aa37e4ec2374aac4f8436ed3c7db2092326538f07fc6644e0239899e3335f73c1e3c4602b12d19d7b639d4968974b6b2703ec1add8cd930cbafff4158f68f06aaac83bb4a2e31466e2ddc247ad71c5f4c49af7defd1394e21819cc24c78380caefb2ce87c0d1050680313037def12ca21cf67bb6692d6e4a9e90a9c9a0b7118ac300c6c6f636337aa25bc59cf1d9749dc183803cc0ccd1ff53210352795c6edb49ff1e5e8ebaee7b3eda6e3c0c340fa60594115e37fab60133b8a3b39d2e63db0bc6a03973e236fca801553912f93feafd8b96766049dd2066f3c5ac9222121ee9d36cbcd8f713adc8779949941f8a8dcc92ade62e46e9f1b292d5f7eced14c3bff50a811cb762ced1f103652773ef946e18569eb5892626627e085d4ffb3102c1586ddf88acbaeed903b22d3e7ccd8b8ddcdfddb872403240bc8e0e46a068f55bbddaf90fffb9a914187aac2ceedf21fefa1fe32fc7bdbb9fd76dcda1fca7b39107d308d11a118e47499dc4092ef0cd28d0d9af84440f095b4feb7adcba198894cd89a324c60ed0b996c520d4b33391bbbef1997256af7ba7ec1069244359066af81543ca23105742fee3480f890373d3205236bed566cd22a62bf69f8c0f27b714f84a203bca1605865e2cc2f9211389e0df7a4b3aab9d10826639357efe1f5fe64a1bd6d06d0b5605658c4d2d12e1bec77e70ea393b0a09043dd7d6684bd53f4c883f2f6928d99ba91873d063d43600f9105d503b11d8dc2b05e34b4fcf18e78b2b6c97d3b2c9249a2f6566ddab2a8a67fed6c9f8af2f4ef98dd579f2d4fb572e178489c503df5d5f03bee9920db347a6e734ed72ec7233387f1579c13725599a33a90915ddf03725dce20fd3806abc1029a20732380596057830ed63b6edcaa4d4418871bbfd58de1d1f2800588ed207f2016e11abd1baf1895f6096e2c75cc5916836a9ddc09cab4c28e53fadbd7d3080088131cc270095315b61011b0cea5b4d64b647bbcea54d20be1eec0992c72fc9c9771cae19191cf6a6f1840acec1deff605626d0a0d79ea8fe0af63ea75e80f8141fa8d7ca6f4c99dc7e78aeacc67762ed0134f1a0b053debfb9ccb145800b9818c2deb46f7124e8655f37c3291af107ed75384afcedb44518ca14cdea341c9657ec638531011cb957ed6b3434b736ae8c8199684cc58862638c5f6c07e1cbe8ae68c5582b1697ca9dbdd01e97023138a9173d6b1294cd99514a28102e6912b1c87ef22cdc611133bcc111e95c355a26b20a3d6f0ead66e932c5e1229b0fc17a7d6f78134c69beb362ca75017b1bf1105ac8970fad48acb8313cb3ff10e9d72c4ff11f95c2dab59575525c98653a9c7d31585a3742267c062d6ffc7a4303a3e81a45bf39e1ce2097623bba70f216aa612c64ba06ed6d596ad6abbdde69d56ab45e25ebcd4e485824449550232be26f987c14008f67c9db9d0f709f567fa44502b9e0839457e5f0aadec0395bf5c38ed8de7529708e58c0a895198fc8b2570fb6e68547630ca7f313526d392ac4776be973205f971854c300454d5",
+ },
+ {
+ "95a17355dfa9d378a18ba20e58aa4b8711ea1d6e3c65e0b2d3c6382892c7d02768437d47ed50bf8edc619c340be7bb1cd1d88b0d3d6bbf1031f738c4be09eb264c686d39b92cc7958e63c9994a84b61b5c412999ace8a9dee0e2a29eeb8dc537f63271af5f3844ed9c0d86e6913c02ed7d2b862a132f08f311aa92fc3757342d89a5dce8dd20d5792d5c60be9862ab168d3140a061489472f2266f297da357064833ef2554c49f8120ff40b961ebcfee1d0f8e7e5722f049485f72c502c9cc4afdbb70517f0fd2a00e12596ffe285d1b37eb998e0e89d756e9491ceb13e83610a3a66122b533c2c3461b3244438f5f7a7af8088881dfdf6a29fb563ce38c4c8632ada8e7e06baa2686dc6aca6bc944e5c14d6e432c4dad554803912b8fddb1c18a59a86bc452914b2efc1599c5597f87a6edcad33a7728827bbaad0a975ecc22b7748d7cc71ec7f51adc8fe0350e67dcfb31af35a8d7b72391642e29c2fa4b796ed8f535f6bc2b1198baf1cec858aac38959f83130af55c21383ebd57d364eeb0e442104004c1599060667ce5e1191e76a89199a386e5c4bf147206e7d6e598bb27a90b3c6a54cccacb39a0ac42bf22eb40bc8ec7925376a6c57d8eac6317578ac052b72ab773f572ad961ee05531cb95ee5a6d70add4176351960fb4bd673f7db9f698616a8dd41823f2f87924c40f131e6c83bc40ab1f92312f46ee86765c306cf4a1d77275ef9668d80f9d9c1ea0aa7b2456bbcf764e009584ef1c0b4b4c683fee3fa2641f48ccf7485a8356fb3dd22f848deefadbef8050de9c5c19e8c449c6f3ec2b1324f80a7d428dc44dbb966d40244c3af03bcb410a57ad1430615e07553a22686f1a62dc6cf090aaac3707ec5b44274b7fe28c7a3a298e7a8adc71e016944875bebb421babd2b64809be3454f25b90723e2cec68467ad2d14744b15de8f9c397a505a340e85998e207cd46fa18d76c46f458af4ac3821c0ac6cd68afb72c376c31daad1a2435fc2bf333260c1a82430edaf2499e7455a93b1301eada2e12365ffcd36a1119664d0c996318a3e55bb2c04dfc5eb251f7fd64f9d83f27ea6577d748e1f85248355ed19867857dc3383e01249cc37684b0eb8e891aa663801e4ac8f0331b38686a19f0d19f6e94c7ac95ec395962be0a4e3c8358d2f6d8f13191e164ad29cd1733bde8c31c7d8ab90366e26cc9a06707dcfa60bfe139a112db827778ac348fdfe26892fed61db7e9849a464e3aad561797b6c778e0688bbbeaf3349727b4670a2d0a08f317b0dc9c4b12ea85c0309d57e754d0c7bd5c83985fb82f776c968189908a8ca83b5944767c2efc3c5f898436de54fe8bb17224012a437896d9fa106a749d12aff657266276129ec5ac12fc7a77eb06296d2a2a876d931e479d3ea201cbb4b1b20bd81471eaa33786c624013e1f07577c2171f38f0511c6924078a40c2d55ce392dd2ab0885e29f4c06907a1597c181b933853838970edad7777ed394c491cde27478eafa5b7a36520aa0779261f94b957e83ce058298dcfa07b08ecc425caeb6c599a11103d7631e77daa0d9d3fc6f42703d57f2c624ecddd56b9a27b848de7dd28f8ed656f1e4decc95a8908217e2f2453ae50b5fc1d9352d735ce5bc2b538eaae25501d449d090df793151811443c64f28d19eeaaac4081e10edca4c4148e723ade8f7e7b988b732ba08b3ce4c8a0d655bac4ff66048148135decd7727a49ac59d82ad470b5479c55d3d8399b790ff033d3ef99d770e1eacecdc140480aeca1e2167553cbbdef2090c7592b40681b733b0a0d127beefd49bcbe8904c975a5ab8b1afe56d7ed7667b5cf92f537ad6972b876843364817c20400524097ac9b405e4b35bbba0d12355a0b54bd763b4491b2acd4e8e4fcaaf8fcfd398499d4c4e81ffa93ca07a5ff51a1540f178f43a931e07e1ad56ab5ce57a2f7dc3ccca114dc9ba8a6934e95f4efe9f3f76947909b280ea5fd795bbbc0feb3ad2b704e305cd9d8f37d178961f77355eedc9d7f77c58e1db2f7797eb8682255939293c3ef7dacd2eab46c4cbbdf929aac301a13f59831a88fab173803399d96dc216abb9f079e79bbfab667ca590266891c8a7ea4bc1724573e5c5a67e9f1341b5bffaa538e240f78da7733237999ac86141b2ac0324f17609b71c885630c90befc3b027a5f01e33979165ce2a00968c414838446c2aba76e1d7fe3707c742f68af21d30e23b637accc848f6c8df820a27bb4e94e5090ac6e008fde7cf3fdd5931fa891335ec8d01b5d6f77db57a87dc35d6701adf7ae0bf82dda6511c83ab4d7d3460b221eeb3d6c4aa537924db5559b1c6739040534fc330f5144c78bf99f5f4faa715e85aebac043e2529197a82ca40f65a8149a9447a9e58c61618600b0c5ab221420c0cee114a133a648dbc2eceb2894ffc329376d1eb3ce7039cf30ff6a53038b23c26c38739fdebc7b919956ca2e468d577dea6621a8d66b78075ad26a6e6d8e20c9b694698540d516ea2bd108625e5fd038b5f1e19c5d5993b82bfe16897c375322dbbca81c81cef6ad900f0ffe5ed02714c208a12f5234d78e32ee07af155ad1e1077a0d8938f426d8f326c751f6ee66c8f707e8493cbfc76f9ddf1ea329e094315a91ba9385e16c890823db0f0231c7f939a042665009d5edd8e48102c515341fa6eea33cc00fb5d82380d735b29f2eec3f61428f7b186d43fcee46b2037ad1aa6974d729848cf1a80dc8ddb0580c9c876def06d8f7642cf45263a655ee77f047fcd76171546319622bf71283f3bf0b519e123a85765779c8bb201e99981ed184e642f63aa61f9cc206bf45fa6e514bfc637671d9cdfba2891bb112a3cff438a6372ee0dd3e7d9f352ce52f8b367b7799e1f963bfe50638f0c74b94873fcd3d66fc1e342a8bd36fb8b88f33eefabb78eca4dc9c89e2c57aaa010f2140dc5ea7c86cebe2f8bf42a167d1d546cc80bfa9258c35af6efb1a090c293a4cf588e4bdf5c090ee7fe38fd7b5551e71e5ce2b0b5a50bab95bc4c257edfc94d37579816b4a2249ba05c991bb2ea02d047e480fc8a8ba71f48f344c6d20d140a64ac20184e45b4eea14d0953370c237ef0a47a7a2f22997715dd3ee8ea52f24ffe12674d571b3bf968454ca051701e411499bc43bb55bbd033f9b81d4baa6c49bdd49614efd20d58175af868ca16a9deaf65216abbdc3beed5f30b209e786a5b4c006f3bd27d93e9d78b51a1a2fb7f5160a0bc1b7df70952ea1573888ddde3d9dd5314b0d0a899a733eb48d5e6c7274667e362e4da6b37c480aa4d0d8730e66483fb1453a3aefad69942ac7f09d3c571b6275590938c541336a121bdd20722550236a9a5e4a37c7de628fceffbc260b1e9b6417c4295907937b13609b8585ebb8f076073abdcf19104ed80ffafe1b09997f115d987a552be5689c70fe125ca702d2ae4d807d5690bc2e90b72cabb0b61ad203b34c68df21c16b92bf8def5680b204ce327214c32e4363d5600f96162a6819dda472acc6441858f396385a16fa5ee52cc0f9ffef3d53c49d535aa37db2cd4b573ff81d74006677969ec1ad891082b5d18ca5b0b9f975574ccffaca72b805c9f7fdd76bfe3dd384dc953255a5b50b7731a137fb9aad42e77d3da1eff5a7b9eda5814993cf2d289bb25ae1680ffcdf419e073d38b4701021adb2019359bb70ff4cca930be7bb979a0678f20665d14803d8753c8ce54cae92feb026486ba747a861daa449863bd38cb4d5831aa6db1e7f404b0c3587aac8765aeecec686066ee7d11321574f04d3f3da571e71222ce07277eca7ff97607",
+ "5e24f34a8d53b17bd0c2aee5369e3276dbd7e7c2ea0990f1300fbbb00831b76655aab1e2fd625ecd",
+ "c1d796f1e651a1ee825855d80206baff6818cc8c247ee6ce62b7531e6e9ac32f",
+ "240cb25aaae4d085bbb747a5",
+ "319e968ad291ea5d4a057c38f7afa4ddb9c9565962fa1a7b231e397a268ad8e0c5030a2df09dc4f99402ddf2e0d06e753bf55e1b318b3e5ff0108de2328d3b8d53e23e08bf7d84d59fededd60d47bbb52736b0491f82c616eb5f779c496abd6499555035e4513c8613e7204e6bff8d06dfecd9ce38c6b83efd8d0e41f84f7cfc9ae07113237987a4b2eaa87f7e0a310155e282e57858244e9071712fa026cb781e5a4bfe6fa1bc480e534096394459a3d1354e2d9a54aac6926a60b388410fd0b53f7a3a9116292f37406369c22ea674418c4deeead171e00f74f5cabae5d24a0686a4bcd8ba99aea613a23edd0a019a319daa3779c212fbdca9d772fc3fe612cf178c2aca2aeaf6bce2433494027a474eff699bba95fc7dcf79ca1d77b1e097439a9050a5cc78e0b78bf2e7f50f959ea2986a59be3880519cd84d0a673acb0432feb1945c603e70748445c74600ccfec60efcf9e4d02a7df5f967de4b473f63b0b0499ff4ba350ec1182f3a0ac17ef9ae28945fc9bc714c49909a7c1e2f311aa6ad7652e22e1f48bb51cf53814a2125152813752d86c7f9468a991d0ac84b1a2f3969b8081c228b7f5760718036e26a10e211ff04ea323acdaaddf9b06a08c92ed663d0fdf13fa601cda45c416c2d3803dd9b5ca29cba57e59cf4ad93176c65c64507b1995d638541c90b381ff758833a2ad67b0de44c280fdfd82b3c6d4353ae30b33768863cd3169a2032f26e37ddd57e7da1673cfc7375bf6e6792495a2b434155d684f2a6f2b919f944469d47be5aa7da74eed69d871e6f65c3ae08904a9ad042ba39905188f0b9158fd14094bd6a408fba6ef57566d69eccda86bb54cd3ca7381f51bffeaf8bcc1ae8df91d22c359888e21b70f640d6f3726a34e6100ee269124747f0ca05110f63deee07e3628bd6aacf926036ccec02c0b6bd7259db52ea8b7a686b36ba1d0296c85e43e25d72ce46c66a1e646301dafd2f4c502281e6f949011cea69459c026c65bd130d6ef06be17b23a9c9a84746e39d017b144135025ac527c1e653f233770cd68e9f232c3b623ceda836843b3e9ea313cc6a57d28ce71ccfb7265ce73b06bce1447220645e6f66caeb06b55129b97c8dd8db54c94d771504d24cedc86a8ec706a9f7dcbbcd7fc7cf38005b2913b1cfb77370bd23183ac7b5ca5135a2738cc91d05b2b22640469e3daeb6a7b0f14fc6652563663520f7754aba624a35e5d24529a6ee9f5ef0d019d83c04f5a93a38b68cbce0cecd42a11aae305475806326aebb4f673791f50c9f90894add51a0fd7c02807efd8c1bd21fa717a860e224bc9fa3f40975fd8d558e4844a09f8920256528450d77e546604e2ce2d38efadaf39a0ea3ea12156174aa8a20481e6c1190e448564675f9ca60bcef37cacec5aa218122e7bd25b571ff10f54979d62018b779a2a3d5d7d6cd56ae31efef2c844ba50ff9da88eba7a8e0d9fc5388a805ba4ad35eaa4798e395d2fe112083cce2f11cc850d25ca5c6e60a9996cee4789ca99d519daedb62f4fb1e535b742a35d71d7390117e93821ff18948a78c1fcdcb90a5f1211327d7ee0663ef16ff446e0e22d8cb7b2d3d05469b1c02864f4a87e2d9715f60c9e7be841e308d0a5f6c50161a4a0464aebafb88e0d2df8cefcead93c9623106d5518a9852f320235594be10c45bc0cf06c9daa007100ff97959357f9be8e49c870d0a11c884213e266c35e9131439fb3654fd5f1abd1e778ccb02b8c262753a22653a09272a0c33b6b2683c9045e8f967af756b98dc1797ff605c64ac5bda8252e9ebfe0e4d8d7ca754fcca5e3de3c4b63678da095281d76d60fa12ff4ca818825f346b9c4e426cee16db5818d78a527a901cd088bc2983f9b83430b50683018996996717a1738439680b68e3f61cbdcd0f0e1a6b436af8fa05d3ce2228054e319bad1dc6ac970c75313c552fc1136fabc302fcd1d09ef1b9138d18133a772cbd9cb197ff58c6e898f9e83e4e27206f3b15b6bf2778aaf9fb38e0d50152f8dbf5763816132a04b4b2e9639584b3dc8ea6d95ade024f9497944200ab0aeab206ef099859b9240aaa15f737c1e0fe6d015d04f47261ade4928e3c2ca21d1f5ab4a3f571f2ed92ebeeebf2493e6e39f0063ba931e165384ee1b5081f5f8d26ec24716757037f5158d35effbe67009080ad7b0381292a513f312eb28328cf5ff47a6599e36c14277c3eb5053c5aca530ff5954c21c03fb3fd5fc0facdac36dd819b0495fde421411e0440991da0cc4a20d294446115c0b79045037fbfacfeac574da3bf192fec4bf38c27cef71d03787430223b6069ba6d9273ec8679736a832277c657862ca791b559a5054ee8c7c07618083f75480c8aa01cb086c7317315911802e6cefb15bbe20494b14d97e3a885806db775c216dc15949e3b724f7cbb30bd2c46bd5a2fd6132352c2b21cc2b47891dd9794975f70a6fa7a0791ee761ccf4c263f27f64790826c1aa656c39483e029baef0855935e7e6c133a4035a3699925fbde131ca62948879373346af35bd7fa52b8d6c3338f213bbd9c79977c0d710028d1d386df614c5faf4a1f8fe5506a9af7059370893ff6d07d91383baba67a617b5d829e0e2eb20e541ed5c34be7ef0eaf6c6f6f52d7ca01933a2a4e8de46e422dc95161ba8ad354f6bc7c8e4cf8ab5e08607530147fcd7c9481afc621c5a3230a05e2c4db79db9e1e73f43556a8e8f0dff7ffe420282212f23d4c5f6f8d2febe129b9fe5ba7ddf27f72ae898a4eba270b5d2bb3b6b06e38c546ba80a9b2bc46097d0b47db5ae72485ef2c6419e856c33c2d66a861b9d474699e730eb8a8992e3ea9c1ed74316687d5d9fc611189eba2aa31af5ba8e81179866dc016bda977c59c595e40001c8ab3a4a44cec00ff84c6dbd9ad4be30bcc080e69b9398089d6ea464a70f536ace3b447693301c94850606d0de1299770b5f45e6d28f8ab83e3ffe52178522eb91fdaa9e4a696674ba0f52ee18e960b04415782f018d67479081b1bf9b4c9b90de026cbb66bf7d9d12cddccdd9b2c8ee2f010892571c6f0c0feac9555c71bf61f9cd69553cf7fc2be8d058e0c3430e134adb1ba28985fdc4f0cf71bd3cd09f5f82f303cded0de62f98404477bdd0a846c6c51e3e82ebf72f475afc8e6388aec57206018ba2528ede194345cc1ee95cb2023793f692f708aac3c9e8a682af36b078f5d6c7a3ed07475e9fe73b95d1eee048ab898edfee3fac4beda45f03eeb64b2128f6df9453ed77c6010e13c0270c068f704f49e62fb7410be90ffee47584ca2efc5287dae1f63bcc1819e7548eb9f0d8a3182f9ed00da3817255a2ff735876b75cd21cb25e86aa4b2893f9e5089dfac76194563f9a14335dd37ef06a501c89623caaf6feb4afb792092dfed515ba7518e278c341834a9dd17b50a0fc860b62ec621b69408cb3fbf7d4ab88a3e367fda84c82357376fa9b1161b739361c313b99dcbf4122f3870c8175093298cf432174217398928983ab6cea4759f18e7a21d71fe1b0f3cda05d241e12db0818b8763bd23d958d6e52981ce8d84cd6d82640d2000874a53c0bd14949ec99e48ce6c954ef0d08e6e319de5ebf7e142f25c0f50ff13f6acecde6a270c8d8de05ef4c310ce9e92f40f6f2b77d6e7aa3f056d4a20f7faa7cd0b93d82e3972343a50a26ff462caada10621bc953b73913944246d2a4da25fa52cc6ee1293c436ab9031ee2dc79cce39f139f44d473c236731257c6f65ca4d383e39cf8d33923afea3c80244021d36e0ed43230c44e7d1a1297d35464861f9149d869f26cc51879027169803e43c898d1b4a2a2480197500",
+ },
+ {
+ "2158abc2472e1b9c061da2c01d0ad9e996fd687cccca331fe8a2baacd12c06f284b1b5cbdfd067e5ed09a60a137ff4a97c5c26482659680ffb22bbcd4ec1bfd272749e52440537320fdd3c225c30ccd98cf221b34b89c247ab7d14f93ed3ccb0486a028c6f3abe7e17fba1742b6d4db85f6e6baaf82df1a3aa059de8d9699821d39bad42d56cc1ec67626092cfad4a2e1cb5d814e2cab78ccf5474a8bd0dc990a877d37de394694af6cadcc57727f393dccba7bf955f4b65b3c00d71cdd701754ed4f231685b7b5e2557239d7e16305be2d81a773765dcea25ea5bf2c15d670f3159409ab5bbf8da121c779132a8ec1480068cb76b68a19152fd83135aeb228b446225f91d1ed4303a4bc16cf3ad8173b30d2a1e75ccafc8c933db231efeae6260d45c7ef230ae2c7b6f986f1c19e2cf260ded9cd99d64a2d03fc5ee3d73509e47ac1c39dcca655839fec75517a9243eb611da8fae3e317e7df66cbb6abd59b16975eb463f509e784e65cd660ef1a4c5027e54b1bc862f397c9cf4e6594d98c2c2830801d3a679220b46881a372cdf3aaa33eb66b91a9f36b6941c0fe1b4d2a437daa50b811f2d8c65b5a69de185d78bb9c2f172dc90a89324c5a2067974aab14f4fbcd06ee95cd49e03717f88480a410afbb4e68b5c79b0211cb69b90604cdfaf08af1ef10cf28f0f630e97ab18d9b5138d9b9ee9154e0b3104a6c164f2a114fa5032eb5c247a6b87880332a0dce7b36982515297a05dc8a4038a09f52b1def7b4fdad8735443fadc462c7c22132f8b9581de2d213bf5c53f7fce34aaeb24263afefead5341a72f88d3acaae6db367c5c14a97d4f9e438e1e11c3c8fde7ee37e5ece5382e8c68b660146046ef96c24caa6bc9fa0a0c88281e4bf01b32df5218cb3750f9c4b8af24cc106abca62d085198d14ba2ded3cafc1fbb17519a696965a1ba5f65720e893f1ef3fbc5200316b9d4615bb23426ae53e1c5a57b2f0ee0d0c83f353b4ebe7a6cb17531d278478b4ca8e6ffdd0cad30ed73d568a2e44972ac88a7e7d665614316d674e84ebc739b645a9a4166477254ba47bc5c2b05ced88e75bf64da21a7f1f71cd946d84de13ca77b7e0dc2f0617d371ed96323a83bb11dfa16f81bbde913d9c259b10f3aeeb6b56cc4775c25f49343cef667763118932c2e8b47ec745ac537b37746ed65fda2d1c11a2de60ec02adcb79152e8a9e614d8715cc4e6b6891d6a0063576560fa3621146308222432ffdbc351c36c37d844a934088fea92ac54920facf870a62e91ba9299dcb6cbdb918e2d54fb642c3f0d60489c4bda489f6c584b64c8f19359ab25f388dbbe636c4d90c048f5ed87024dcf9f98a9e738163f837a07750d61203254a80d120c795f9c3aa791272f9474fe330da81a45be5ac838613d46c25e781606862912ff88af393040605fd4d55d07e2052227c37ceffcdd2d42a08bbab69140dfa4406853799893daf768af546f915a91b81d0da719ebd45b8b5f1641f15621959689e810217bea18e3996c532ac6e4e2e4f289fddd5e5968bd6fa9aec5ca435c532b6c74a7568c8aeff9dd19bfc2fba3b484a191e2faf9a069a24e2e6d928ac0bdf635644cc1ef3bbacc547a8e4f1d42d4bed3b6b8cc56216fa550dc37da9cf4d1d1591d9348594d14adc7a3fde5e5d1a3b9875c85de7df483cdd0baa86dae793e0796d14fef1f649de6079acbec6b6fa5f2cb2bd0481f5316f00dbe5dbc379bc3cd6d13bd8c775a727ef43e6a5fad1051783b22c05a75d64a8394a73fcb430299b015563c8cb0ae0aa4ec750399855411c076d21aeca8656f3d0cae084fb0a1ffc6f73b52a7ea5d4bd6d24e7057a3811719533105fc967439a32241f2d3e3f299da2deb821748cdee1a1c5e71bfdf88d833bade2f505268f375a9e6488cd8e16705cce91d15b60b2fd269a19148296a7be348aa349a12270fbc0d5748e538afeb0598081a4f1349217ceab3c4141d40f765ea2bfffd530fb9606601469fb131a44939be984c07bac8f26d8c068accfdefb729eeb47cfd6ddc646e22031f53a7698c6501d86cbba05e282d64b2f962a1b08b9064078dd1e3f14006f45f599bc8e600cabe6d855fcbae8c3060859202361d929a241f6c0711ac0d050b67a1d44da19e0b0e236adad1f60a327c9c34b2b9c64cdde5b8e4f664f2fc70599d44a63ee2b14d051c27d71231098ecd3d4086038d63e84547dfaa39db1a92785e38b640ea0345062a1c185b25a72862e7ae6574114eba592d6492087e2580dc5d361c473a614d647e66c0a30de806f4976b69a8b92301e68794ee05b96ee116a5fd5edf5eab43dc1103801eec861383f17c2bab9f2d9126c1802b7aee0c909309ee72679ab644abb9c4caa54add283b5954e6f881781e42f849bce6554c7a5e3becc5d5a209805ccd4a0117272a53807e3978ffb19641a9dffd9034490a9284f658599961daf52f24f6464c2099cc9ed3459d84dbde2ebbdbbeef25c882a9beda03573bdd4c6a0143b14d634a1a021d5f9fa23a7ed0f5598ee57e56672814412b6c7c08b8e709fb98575fe2716100d000a20a7e7200d800e556564c7e6a8da9d609b18ff0bb8a8812e96b834a6b534b0d5dc97f5da17f42f8d58e763f1b201625d1a5158c2f9e9e190921637474ae81d278002f197f7211540088931ca8a941794e56067ef4a497fdc6fa713aa9f20c21f23c3a71ae4cc5aed459ca7c020bf55162fbcf56a066546660c5a009b8ad2aaae9651c97b1e145853a10013d1bf68e7df25dd492c328f823ed982da54557502ebc6cc56d4d0bf2881bf3c536ea53b4dcb0886e73b066969dfec343441b9372d7ff38454c4337d45e2b999415ec48f19cd05f0f80c5a61ec369610784f47a5cf3b2a13ff5d8145303ade7189a300936006846812dec9ff15500f8daf47236e724d72619af3a6cb3e854cb8284d5b8843dfe056beaa45c40a4541a98c7507feb27a605d6e07189c8c5554a492a03ce6701d3d2ec782e2c1c8346b54a963435bdda3a93bbac1d837172cebb9cd18903d25cd6bed404eaf18730a6d1c6da0783b5411770ed34f35fa6c11a4292a34565ff1b23d4200ec5a73e6b7905458088fac19f6aafd35e0e791f28bbb2cb0117ca1c3a9e3c4863e487ce5d8c14dd140e9eb4794d87d75b01f683bca84ebdbf19dafab716421bfac9e95755fd346a0cd31e8520a55c7ca652ff63fb4e20ba67fab41e11f7390bc02363162097802c6a9eb18b430d07ea60064d5b546d15bb68cada79c113848136e797577f1783e9b53574f9427be3a28230fdd69d139205dd6c7e9e7f031fb6eab70d69ce905384c5c77d084360aac590a89b2dbb2d339899b13619b455cf9f0cdc08db6c5b5f3223dc3a663ce42bcc8cc6f947f42cdf8dde15a6926b753177513a52be95b1f0b88d2a1ec90e49959b108fe204bbc29199d7382c42ad5dbaff970cbd2dbeade54bd70415e54daa805d396361f525f38efc2bba3fd818f9d7af0594dcc341c20f18c624fe13ce7e7108e1d2fd06c58b03f04642c95e3ba00d4035ea0476ac138f72378d85050bf60dedc90af38e96f67fdc38483a73e847b41d31b894ddcb234f02b0d507bbcb15a8941f9c23b592a291cbeacb3ed213f2f044aa842275a7717757467f121294bba6b357c969e96bfab455c6f328d9e5181d909c3f0543b17d9af7fcac099067b043be79aca8e5a75c3a6d4f6246357a63c516a3ca595447f34b43a055d3070517c67ec36e636aca9ed71a001d4f7b81149124deeb7826dec3697e183d861d544c9c17baff82849d599e9e77ed19f801aa1ce095940674576ff270ac788d00c429187e299a03c6f3a1646a8f7d6290287e70bd1276316ae624da929c67936191abdfba45e2803884e5a3136205a38a841448968a7900709dda033a42969bd3417a8d865d0dbee1f261f4556797dfebab278136a182a63e5ca9789e3f1371808efe06eb0cc5ccfe26c0538d573378035afa39fb7cdf3ad889b277c8c6e84954e74f3ff3140bf13bcb45c822784125d23b5eceb73e",
+ "088fc7ba068f80efd8d4d62813c93c1eba77e9ff400c7781314abc901873ce200295da09245bf8fd2fce254397616151d94b511957c89a881256182ac9e64acb7b25d4a080cc9daf9ac2f231235483fc9fd415f69caf7eaf0597",
+ "78d5f86b071bbf8a185e5e2d54faddd2a9e26983b1e7a74be0f0b979b9f4af31",
+ "d9ce7d249af9496e99c93b36",
+ "ad542824b49fc520f0b7ff8ce2bff8b3d47baacb4a1c95ed56a306483aac551fffba48e8a8f5e4cc536e9266182f6811d070fb9282f5c542cefb4993ccc7044b42cfd6fc71793dc8dd2de23c630f9ceaeddba45efed9d7fca25fcb07d193c000822478b19c2ee9fb31760cfe01475ba8a003db469d1130318a79345a29d054a9f9412dca1edf6d8f1498af5bb6fdbbd3d5f9a244ff176f62742c53779291ef6294df6540d841f4ee8c7c58fc8497ba74d9cf7947add5373427d81ae928305b93dd26cfc65e63b0ed0812ce759511bfbb10aca98f2abdbc9055c4e5ab82637f6a965bb74f592bdf11118b8eb79d50331e76cb4d10c6b4428cd4ec2ef4cb727bdba2b5375f5184d77772d0f9fd3a3c579a4a548b9c2dadc22c805ae959617af49a514b43f47af834313ed2e4d1fcec2c4b9ea87f328fa3d23129a36e6c54bcd08f7e30645de86e98ebb11bcaf99543503eb1e024bc9fd51fe6bd5e6d749033f2452cdf28b3d0f8a304111bdd26dbde641c02fcb15dc21b1a9baac5e86d35b4126ed1cc8a2c3c2a5b94c99fb9b2008daf1a0c090633bf9e31326428c75a50e821b1e72a6504c9d7bcfcaabecd929163d365832e8971f5efebff99ee3f5b95f957e8904d05b410936d8a81c60b4947f8605c58e5b727d491995c76fbe06e556c8ab5cc661a0c09ebc98d61010050f68b31fbe1f9de8f6481b2704204b0164d8433ba4dc1076908c782826e9b555e8d608463581099a466f92bfd6ac9796eacc0ab771a3f11d03806b0f33ec04c69cef6b87d58c11acb5d1374450ce61ba159456b915043c5c17cb03f0ba66d027105bb6fff41e6422f13e2a466f073358bf68149a3b577cfba7ea08b42f83fbc5a2aff17c5ee7dbdac3ff97389f5b8d1f3750e5c9be651209eeb9574127ea81bd7619da16d1cfab85754883543f6474c8c0cc9d5b80e34bf8262d2b4798f9917bcab4b880339397907a5bafe7d149247fd735523df3cbb17ae5e298846ad3bfb7d4f902aa549b7667d3ea945b002e7b209bc83842a7b120d6d27ce80631404371f31d1f61efc5423e1822032a1cbf4fa1a6b6fe79934a202d5add8c6e3595e49be3dd9553a569521c50e9653bc684ef2b73c3526ff7a0843fcac9cc9ecf46e63df5b9328a54c576bd299a366bbdc0f83a9de67b03f1da16244bd6d52e7e4b52c4ed693827735554b05b3a260cd01a41d7c944d0b7b58ae4b0eb052da34bc22b779d7ad46f90f3d4049c097e0adeaf71bbb30ed24b32ff5c7a65177db77492c2571e9cd99f15e613797e319ea7377038d53b28a4cd66a697e5e8f84cf16bd0f0430b34826114b4e1d1ebaaf2939dff7f9f4ce7c0861e51701c42d9cc9e871018b447ccaf4e402e3d63be164dcdf6799314a389ada8bf5e51a35148acf627e51481b9b0e4bec09c9e6d59229721b151fa9adf8323001fcf33afbc9a949643172f39b0d10ef57b37973683fdd9b9eb46e63054fd05ffbef889ff8fc8f251b0ab41fb00757ec1964ef373fceb8f6d148a7f7c89944b3cfc240d091601b23046188ba70a7cdf7b6f96eb93dcd3d24d4aebdc4a29a749bfe3cf5f6e1a025b62982ce188e6b57245d829c9fc1dcaaa5309a8b9557b8824a78eceef6e977721de4065b474ae008642b974001a5565ef5fe4250194e8b861cc45a8691c461817f10b646fb526bf0fe7790bb0db29d1356e8c7a197ec78df8310431d632a032b5490c2a458eb8d4327a9679d7e8ef8739797b0e820e2c567ce3562592e862a1dfcecd50bf77fcfcd00518db65ee0effb9eb3655d5d401a4a47808faa596d17b316f828cbbc14a7e018a0593da9320140a752f3824b5fcb66aa4c3cb94366ee8b821b09e7bea2c04ece15e8a7be1f58463b525e8cfcfc3fdd395ec5b0575094313557e632d0a65e3099e3c653111a5fb4f0eb2aa710229fc055a2bfd8a7147cbecc10823f1244fbb6894af1408ff9047d6483ef83573b5421b9798ee387dc38f166b11de6c33e9785e9b3d9d28bc24c37890e4f8f8ff24cca298b44d6fb1c6aad28cc634a67dd427205285521a172c2a4884ac5b038e261e38faf0086a02aa29195713cea335c47d03d67fa0dec7a8cb21db741519f5f0ba0143f14d71e33d82c75d6a19b3f7a42e6c16d762354daa2670ffa55bd400637de9cddf9e7964a03b4c8956f36bf54d89cf16de23e8c52957b52eb4572a11d1398be72bdb129e2c1abb58c65cc291bb7b0d2dc326c6125a441863a6c92de0f47a355222d58bf10af0d297a86a98b4e933a8f844fc7f1bbc8ba77919dfc50c41219e3db309b92ba056349faa758daf360b8ac05e43fc2069cd46e63fec399cd7764b111467fc65407ac06f5f84a3179930f6215ac5ec906146c19e0d3e162e77a2bca3582128284282b251cdcac03ecc204266ac3a9cfe8d8854008baf89c0ea0096a400d6a0d2f7c681c99462cf0105f7a3dde690ece0438fbb820b9c73c6cdf6208c336831101b904526cf8ac331d879d71615d8b1f750ac7f0ec692d97a5e21e17e194a98c10172b5c4bc1049a8743188ae7c4d70384a7e68c1353aab7882bb91aa383821046ed0ebabb4b2dd126ccb935f48646b299095cdb71ecd5cc402e4635a3f7a3c8a6f54f4076ba028dedb402bcc92f5668dec3d91dda7319f58382017e306237e42480ee2c1f5930564cf16fdf37a3434585336b8e4535bba87311cd47722b9da727250560624a5dde48a2090ee44592d2fc06edda634b600fad9f843c6b2eaa0697b42858afee8191dd2a31e5685bd104188e2ccb057dd0a8d4d1205d7c846f5b8ec0f06bff61c7f47ac4da30e1bc80a4e95af79b14a83e9af2e0f195cb92d14f752a5f12ff90a05765be453075d799694848fcddb07859336ec101c8052bdc273d4abc313cfb351b543fa340dcd01bf32fea59881ddb8f33c6023ccea70532814ce4a2d0c66c846347b86c29dfc34f6fa4db298911d4367c59939020a3d078194e6a3a3c5126c24ed182398468e77fd61a5b1271f5cb2a97868876954c3f7179d6a045f4bd770f681cd82216cd2b1ceeb4e724b3fddeb74481e662fbd7f5dd45bed6d4f89d21b8dd9c1009ad2b0b16954e97993ab8f3fdd9d61f8db102a945591b4552f419971a9e46a792dd8392c8d9502767c82d9b4f69e66071eb579859e9ca070cad5fe3b7fcb77b8474926ea991ce7ad201421f8a79c051b762a066027ab2b9595a1c97ad57f3149f5872ed4d8e99195d47bd3c03bbee590a50a99d8048e912aaeed797977b52f0240a6cf2c865b108456881adbfda60cf701454da17bae879cf098df808f34e50bccaada2d3edeb1aa73cfe3c512d814eb33897b6ff9d67d3d682517cc333c3c2552adc99860b1f0d1076390de9f84fcc9e802581f77e14f5254da01831c70cb8581630dadb44209377d90447a1a21cc8a2d6d897db62d8420afbcc6ed85ce42f3281255bd43e0afd3e86b27d3b957104ef54959282b0e1b381a26f16057246704c7888126055af5a1f494540f01897e8781e1a5c0193b7bef4b5588d0e9b9c8de74dcdb63f03f7b15cf48fbb71c7c3bbe9329e3d326988bad7d0cb85537c1e0b3cd88f37a3c7765f548f99e495ddc29daed8c7f15dadf2e5b79def91dbbea277c51a5da250e66c305604bcce4789ca2df9a10614d72824ba8e4f179f35ccae7119fd962cce13b282f0f970ca6c4776374c4bc438f0de98aa04fb3cf23d2c6800a4a666c15bd20c486e88e688ff9e5fce906b4ae96ec7c3388d7567ce6c8bc61f6d2373b93f9ddbb02b384084b3f28f54c9ddda232d3084daa5fac5ca356ac0059f2fd3fde5d6a9516d0954653b699aa986f70733538e19721daa41329abb95058450e602eb5726ad5a8b81aa474650659c6f7f6f53f8a6e635bf35f4b1191e0dbefad3be756c6141c7d55f007f4fd131e5d5eaa120ba31cc32b8d4c69d4fa784fe0af7dc272898789c774e7995cb252eb6c8e8053c9e7adb59c27f675952d161dba78bdfb15859fdfe4fe4a44c01efd394bf51d43c600aa9a527d9c490971e188e28b980e77a9c6ea0a4ef6bd38d11b47f5745ecdb",
+ },
+ {
+ "9cd1c25b5bdab9b9080db3e5e05dc749e0783087c310777d89307138613bdffe0ca259677c13208420d4690031314a11a97a986d8b0fea143f5b4da0972c9ea3cef80b4b0b2bcf2bff392c306a764113f0d9807be86a9027c6ddc85d096600d85e0b236937f295362bc1679537a8a9278229a36a9433925a105ab719c0b7f11fc31488fa071d3032de97c81540713dc29ae02c2e13be8823183f3cd9f72ef8ba4280b4499ee47c7c7c4492bcb5cf7e4fafaa7ec26906e58146215a3d4f52f792d3abdb718f57ed0b9b7fc7504e45a0fdf01ebf5924a4da6ac635a715879ea75a4983cbd9dab9e47638acc687f16684e184443aa9e81513ae4abbc4d1596b2ca3eef77cc9b0603fe90c0570fe6cf4dff0381a99212fadcf7968934ac1ff7664ed6ee0b61e41f5074dfb774b676c2b57a445f1c5749e95ed062837c727ae2c151c0ccb3a4dc1429bbcb9e62325117aca566b8fca0924b70f4defd7749d0389b90f55f35d1635f8d2efdef514f06fde46db6e11e492c8f4dfb7cb5454cedd0ddd32013a4836321a25110f3a017f18475a86583e192132f8d8fd4c2dcb2a3aa95c3be3a57216bf9727cfd1284eea6fa870c8e689e91982c116ceeee2f8298b55646efad684b96eab883fd3d629437e9a0b6523f47ea5b59474a4766ccd01c13170bb08f47576a0fdb573d4dfb65279c1b79cb535426bcab60f4022dc42e40db29f15a6148b461241bae62070389932f035e7257752ef2d6130503d72344b24d360cae8ec11fa2dcbe04d3b18e66d081b552e93a71dc0094d1046bf4491e318f2ae00debffa0b8ada58c5f23e33fb598829ec2f46ad3894bd7f530210371a02e51ae0a414eb2eee43f3e08126dbdbae04c7de4b7416df32953234a6694ea84e6889f27c74206ab8144a393a2614e92adcc77550dd54827387b619f004c13f6c4a31e8bf525277669db0a0c3c589eda15063f12eb774a13e2aba2f2f7b6e9bc69f8485f1d6fc5773acf83671812412d28704003e78a17da25bacd1d61a6d9cb9f121abc71d023bcafa713b7c954e4e1c524e5bcaefd86c4a843e209eabbd579cde0263fc059ec6ff10017ba54fc9c2a1171d6b06f5d85079167117c12e6e5d0c71c008765fce756fd0f1141fbad6c1d2f32cd8e80429611a9a78dbc8e738d458f9ddce58ab43c77b34db9befb25cc1a588998e8dc2efa75c6883244fbbf9a7b4d6750c81b8d3fdedaf98dc61f49d067c369409f984b155ec347a3bef73e2a44957b0ca0f84c7fc335fd89453759ad0ac2fd9a5b38afa9fbe74daaee7bc52301302fb2286c21fb922f74d756de84519171fbecaa9b869682d431614ff6845126a4034f10253aa244bf89ab8e0dfd1f7fe8fc1a8472a10746d26896c8ece7ef80eb2e910069435518ccf096caeda63ad692455b04e6525bb8bae27197ca5118a57fb9a5d8fcfae1b9eb7874d91eafafa0e4fab5cb4d0173f7e3e58fae369843a641e98f3ee460e8cfe95d98f7fd38a8d2235e9d6050015833e6d7d21d7015c3b1ff42f0d3a3d9a38d373c8524752e06987c9408cca550f08c38c2a9a8d86d5ac7a04bab44254ed15c7b5670e0747788e11b81adb0d29e3d0b50d6a429340ee0d44a8c286fcaf9bc46403d26b4a4af95b021336103c1ae0f1274b33bb8b21c8cfca8a56c639f18a9df45d083fa7019aaa14d1ba50eb9a4112e574cd70969640602096265a87b1f77c0e00bbb501555f1626196611b4a824991cf10ab2874a12a8e0390267eaf9e3f8f99eadfbf40d111a26772cda1f50743c417eeec9c80171a83a730f246cf31c6691c96185d672a0fde9ccd7091c4b455dc93326913497396e0a4992773caeddcd783e534eb0f34b99bf23a2db6ee738381b5fc94ff603be014c507888ff55557793a8c5439b11dc5a347f35a2666eda81cda4d1c3a78fc4f3df3c7bde91d05524791b67142c446f60c3a4022912ddabdf817ca3280b671beaa496c935661e5adf39c1f4650563c5c807c8f21aa59df926199c4e2404690ea8ffd7dd65f637452ff93995fe9c5ac7a322b9bdc756b7ed6f533b9357a4a1ffa379dd096f144e9e0d87330c238ed3c6b08c8478e23b65518ea1e4e64585e5e9fec2f26dd7400ce4c73ff0eacdc3b07e4f34f6316f5b82fefc66e442ecc92bea8c1d58635d644724a3380e71fbbeef4bf3e57c6240ff603d65447f510eaa3c9ac794fd24f844489b7c560c7814fbc307e03f6a213eca5ea40fddf51d8731b74ec5b472bdf8ba59751065ed2461b02c41ef96622e60c0d26f9dc78c24f94372bef7e47cf09ed565ae3a52d39b02ffddf1953f1ff500f1659db9f1c2b23534702c19ec1cb7c18166fcd33997d53874c7cdb4e6c2b4d82751911913434e48b37a61a0971861187e5decb7f5c1ef6988bc1d6f7fd147a623d8bf361b0d7ece88df6e1ff8d037762d232e22e51d8c6ddaa9dc597b23ff9efbbfd416cc53e5543253732a23aba151cecf73b3ecff21c6a9fd1f24211fc21cde9633aae918ff1c6b72468f1de7e0ecb6539fa353c069fcbe8920dfa8e2fb86782e3062462f7eb2a2c441bfac21ab62744b05c70b6fc3c9f8e3a8a0c5a4263ed256a019861ecb28e20ce78e2d93f1a1def669e9652cb35d105bfdd5ff2313d27ab3eb00d1b628b4c20f42efa23390802af96a8f261ded3678ea0b780e1f4a88d23588a4ebb058adbf9a9c62ce2ce2f8264c874c697482e25f8d5a6daca4f57fd97d23c42d7b71ec150d4ee33931db5f7d63abe7d72dc936bb23a367c798e6a01509644284d52f9ae27d7d1bae597b2cbc26139354dcca0fff6d76c6065d661b66ca5eeb9f8d85810a029cb95b17e5173ef8ab92d475a1d3e21799e874ff04dbc962c668ef4be9f94d85b2a99d97c0db8f6b6d63e00e36c325cfab9aceaf7597113bff0086e8fad36eac7c0b443de6d3a8533789616d4c863df7200ba795a3b8d0a2b9568bb32af95fa604a3e3ea778c3dae159e1b612458584564ffda07b8aba9710134242b2d83d23127b51b9e41584c56f667b71bc01060240f3a2bc7e5d438e7095c1236e0e468079a83a5dbdcf132d258e9ed18f94d3c098867d06d3c09544565677b454be34ce567f1c143e2f3153bdc0353d65090dfd8f7af4633b89a781e01f4634dd7b0323ea1f38184e697bfc39a1299eaa278c39a2709cde0a346fea53a61f211112450b318d137fe68f6c102085aedabd2b045fab912da5c58d8019239f3a44b18f4fe30c5352e2e2bf030334a1dde1dcd23178636f1e38ec9e42102d8c54df0b94b207e804eacab3edddf89fabda6c8e1bd4e17ae31a57716c679ee8bc7de4412fec3934c6f3e8b4c1d1447dbba0fbc775dd3258f789ca53f1593cadc710fef6fd282bb41c0468ede5ad5b914e4758b4148b0d0c04c75ff6208ca3e79d92de8abafa4ec70ea7a4e454f0759337ce575c4954584e2bb8444c34e823d27b025d25fc9becfb4391df9882452bca0373164cd76e9af316df3f5bb7532e22557b485217254d5ab72ce349620f03758219b259784d4c9f1c7beac3cf08e624742e768b53b3d60ad0b94442c847b84a516a93d9b7d068c44c43980b4c7e2fb0ac964bf05a11fb2adb4f6d938715dde88061b238321afc7e5e84799b02a94baf3f879f89a98ab474ca12085137d639b837ebe069f6dcd8456141d063eb1c032aa392a44d1d58b1e77aba38a280625ab84e3b123507ea7a692c4acd1756c031fa52d637703ee957a993804c13e296cc20c1de55c9b8c032e50afffc51c02e5c12f48383237cdacd005b09243d9fe05e51cea42b77645e5c6f4e48c10e671d216b90a48f0d8f5c1dda553217f5126646d11a62587eb0a4ee0efdaf0d54bc2eb04cd34f5a529b682ce09a34d5acab2c8db58ed6244f7b024e68a14bcd5d7a7daa4dbcf490485cbd38e6f20e839d2b0142b9d766f9527937bb1a737877edf6122ba306bbfb5379243a6b22bdf85dcf3b079691f0e90b28a4259c1c9d8a02afa5b5a661a0f9dac52435e7d22e3591593d37eb2e10f646b51be2d1a96cd4490289ef642ad93eeffd64d7cf830d60dc4a98c768a9bdbf6ec9923062ff04abf19e8b65b95494a9420971018c7e6268b8fb2021a4ddd103976333fa52389643c711a980664e29a8479aa9c4091c2cc2074ce3ac1ab4afa217d39c6a1",
+ "c22add33457539a957d32dd07ec9110f8cdd2f00ab6ac256b4bc7732f63dd3b867b0ecac262555",
+ "e71f9a3dd457b4064df1d9055889f105af175a2d10dd7b8729da0d0116c2d9fd",
+ "7df9824e774c5f86d83cb5d8",
+ "689683c9e7aa9c48b9fda0cfffea0458ea0c3dedccd21efeb06126f1194780917c9f4f2f44b1daceec3f6b1f75506f4169bdacf12c1f65958784851056fe0b4b42a22aeb043ab35ca73747346ac58c550324c4b849a404c94b8860967b6fc58aff25dad0556f1952c045b91f56ec8eebf6f552c18b2a0641c037e6c6538b289601e1fd5a7bbe7b6e0b224124fec341bf77615183abafb52b3e30082a0abfc2cf224324338c132426011d9f800b382e6b834896ea48a8247f149d92ded7e69c7800096076cd2a729a1fe41c70dafb1f855ffa2ffc27b93e2f5f6827ade7118af60730033675d84de9cde6c260d3d615a945dfe0ed25f33b6cbd2c0e204ee919219d85c7536f4700f06fa61937f8dbbe9bda88db1f4ba8a8d195cd385eec62edd9ce673880800be9aa4430e5c10a5908f6dd349af70f32b32d8db38a7d73821af47b993b622bf168565082d07e88fc48231a440469adeca59263302438ece96d89de11cf8057454d1bfe8e4e36965a4d82618834a0847af39dd8776866d9558a5cff79a1cc9d1e3c22e050677e54ead68b3cf0094daa01330d41bb66708a8bbb8a196fae5c77dc6774629d38905e81d97c5b16d755182f687a8046e55d148419cf9c12139fee50c0533b0f04a805723ce1ea5595fca5b668e58f6b3b396f438308372489b640317cfa3a79392cf6d1afdd8c3359557a83790021a4eb418fa189ad15ba9be0f74182ac76076f102ec171117a3d16ca20b4d200e03e54f1f0ee6308e463a148c0c85aac3ccbe5781cf45b53a313f7c9975a45d1853ed9104a860c08634a8211b87500b5ffa3d8d9d56f22256d485b9b45b24d3873159adb8ae25966cc40f164f342519e88d1ead1e711e1b2bbd4be64c7e83f056f797c2d3a5cf7c5025f92be5637fa7738a1bbba55f761dcd1451ce4b1e85a6628b629a2f7917a86363b01516472c0f8614abe2ad1c9d5501b2a44a68e3eeeb34a64541125bf49138bcd15b7c82dfd40708414b85107d8b982c4f99783a03c707a37787a91a7198063f0e8a2d52dca61755105faaa09c063c7a0849570cba1aa7ddb3600eeba602c7e7c9b90ed00ec731d4d1d8e4bb42f9e9db21616c4aca48dc27b939428834404331288f03c2b5e887103c51748d0257519c3988f6492eb70cabbc2dd8a8a910d737a678d0970ec48bef3b81673bd10b687b37e11d49e7cf90c03c54826ecd833bfd9dbb8174274dd45b139d08371d5d248ee33298193194734c5863adf4bca92bc282bae2f47da5201fc240dd0710a22a8d922faf92c2071a7eede7ee17232d3b6ee5f3ebb1a8b230600b243c860968ab427a5f540912e5e7bfa0271201f288727f2bd5173539d5318e5c1c0a71cba4d9501b91c3bffa7bb61b3713f1751efe94a66e17d2b42da51d13c3df40f4db988dace42a6a1b9d138c4f590b7227990711afbf8f56fa63f2800cc019bbd4a7b3a0983c9b9e5f77562dcad6de96e3b2eb85cd99d28a021a10d6734400a91369236b48ed68528afc68f247d45c79318fc5d634ecb0f3ef8536d8ec2e877adc3308be906c5b96777d0e05970023e5c5dffed12310cc97249e4b95e32451c9acca8394fde699deda57e938bed7167e62e2cb62357f82fbe821ee73b4e09c6e2f512515412c2f27805762a8493e74a3d30bb409e499002a97354381318af28311ce484bdf7c39db53f08f73ca5793945e13fc8c66d503fa95506b37ce134ce2945d75b424ca6367ef4ed47b9cb8ba7de80e773279bf23ac888eb105385ea958b1b49b27c8db6b1e14a5c8ed5d28808a7d0b6bff1a58f24f9c57fd8b8f477a9d1365f89c698b8ba923896181299d474b93e05d3c915b10a69e61910761a6d8644933c593661b0828afeca590ca18e702322d9140d98fcf836c2f7a4f72b59eb529823a52ab05d919c3eee4db2cae1067213c5070450a160fd52fa44bc9bacc5c136701cd7adb1faf484da376477da08f6a4dcaa37af47c7b026c2da9d5fd0b30741357104cb2bc0d3cebd132b5fc7c873ebeceec5492aecab95ab393f35b93b923d2ca071e6bd8522c3ad8598a05e96646504f1620c045aa5734d665acbdda0ef73612be4ca4d95ba069041e042497f7b10445869989ce30f55206a1feb4e64890b7d1f7e9df2e88a352674a52ae4267c06592d425ed1d88101cf94588135892218ac11f3976ab2b47a27f02eb887696c94b13d48b4370eb11222274b5513a0fef905c66d0c1893832ffdb9b333178b65338fd8b81094d8f86f2e4e96a47e72032cd6fd47af87eec295c6e980f595b57f79abeb4654c4039fa03ade732b1e579551898b801ecd6e0fb1c5fd198335834b51673d074a8222640d2a969998f5b878bf897fdcf3426c4e24a7c599e5567643fa79ea5d20e7de581a873ee0181e3632a4e304f9dae09a81f882d4061ec17e588793b160c93a926874d5a8b78727f88de9bc125589a9562db5bb1c01012bbea1b2eeab68877871ce83455db43cc48455effbc71c436aebe362af22c6a319d134f65681c4d0d51f9aa42fb20f48ae3f7065664aeff5d8349624a5d79eb0bef3cbb2a1244ee445f560a6bf7a796b2c950a37dfb85ed5be11e8e305e835c9e077e676aa5ce23edb1f74806278548e3fa35059abc2f032289f9bd76043c8dd1352b6131cf34f66bcd0e7f1d13081f5b08ed0c69136f3b7ad8e05e9fe99a9b73624095f96740c1f40074e5d92ffeccdc0f15502082fdfcfc97a800be511c22b875f2832b2b891cb1aad2a17c7bd0be4427a4549404172f7c14d5e425e14498237c26a7813cd8612d048703cb180f1a6194f688b4644304950b078692faec7a2a5c5bbc482f3a7e8ef2825c4c19032a7a79a2908ca9774c6403e6b15625c485f2dd078902aff769dfee2dca9373704bf63ad981b51f61253910fd48c49ef10e3938f35ca8dd491a8e569baef675df30367b093f1088ebe8f876191dc32055481d074e5e47a4bd728efaea9fee3e83d8556255ffb2fa08194bdc66897d97d1557186d5f873169461494a83368ed8065b9a033fa4c2f07f7c60f945b60479e3c89233d58f674c0c6fa5918150bae0c6de2b65a09ccd490e2ad8571745bc37e70982411af667f3e8e9b9f7f75d863e5fef05c1f0d2acc7c86585a83ee32e0a64a9e67e75b80def5bfeb7cffe6e6822efa7a9cf049689b58336b081c039696e0fd3b2a2a6b0d177c9b3f8fe5cbb1c69ea93c1235b2c5b6934f603127eeafc4ed0728161612acdb2ba894a5ac376c4ef1fa8d49b4722379e5cb39752837395c413dd29a2a88c03849b6fb2221fd85ba6d5a50ba7ee9c09ecc5e6dc66afdaa1b021282cadc68f19529eadab809341187d57cfdfe01d0798ab8a94277b9b868612e575bd98f70de80ebe5f57637c511800373262eb5ac3836b03808ca5d5f732f286a5f18a7b7fb8cd8f60e4debe54731c9c524b84694c5469975443964ed28ccff2f4e8e0cf4c60c1c8a092e986cf12fa90a994e4f26ac89fabe8a0d1e27fdc00f1d3d3fdb73bb76809f93ea113e336cb0a5438147e454e262fbb7d656aa1be1288839bc342b48ba7d0e72c85a2e24be1a97dfb2db85b5d850481e62f3b11a28c6407686e73d550b9f1d0f010602e82af26813d2484a8db2da0814782c8404b2865abfbe3c98a07ffb37eea6de7992cad73a9b81ae96a9acb13ba213eb4111d868cc73b0432d2b6c2d7e0e0ca7ccbdce86d01576e1136871a07c76498eae53fb7ebf2e85fb8561d10dfba740400ef4495ece7eb33ce3bce26344eddd88cf1ed8028ec5fe8e71edda54dbdae08f50f8df6295f6d7ef1163f62262a200456a7777d0565d7f5832fcc7ac144b5c3e0ce3e5c9b7f880a54ed5e80662e96b356ff58f2e372b1dc0d73cb8b96c72caa9e5dd312841a8be23f838bc706d893e1a8a48b2c069874c293c41d00226f73f987aec8686046ac4c0c972c991c38b98cabce30e7255dbf16039b95dc7d103fde630b03441b15bd2c214763fece9d6778d1c6354d2c9478c226175c02cb006006715fffc879a6a2b4111f6234ee330d6c84d453c9ffac08efda1f380110a8ef8c2fe44e2ed644cc3e0146b4d02f76586fbb6d69b827be38b9add444e2bac4d7165007cdbf2ea8c4b967fc1bb70c68b229f19bc3f79cb13ee6265264885f04c09a96583f331ed46de3e5dcaf08313ba6053f3d0c1916a0f",
+ },
+ {
+ "3ab6cbeebc18df951d371e0f3cce2697fb367476bd9d50ca9e668c77636eeb9d24b68be0ce6a75eca194fbde6221755d57e9d3148623de24896a9becd98789fd3d14de0c7e53f81fe7f3fd491472a66b5b797fe19c5d0525c7a111a0289a9e65ae7c712ccf694cb75c490070bca7db17205af9bdb7fee27f9ff41fc78ebd2d3d399e690908b5c064ffc0d5bb67b0d2880bcb45c2ca2741691b6131aa1e5ee758fc50610406216905e13ec049ee92d1f95e16bc283dfd91595ec2037d20ead51d3a362140578a4538c80581b79852b0f6686c1ea66aafffc872024592ec1aaf2650d167a75bace024b261db4ab48b401cf85ec2620dc12a7fc37012af8ac1d6db923d82eee962129bc4ede578782594708357d29118fd10dc6d228bf7e461d2769e556488b776237b6309f3dc2e884cb2df1f43f71c53d389765f805ac053d05fa835e75fab0adb0f13ceeb425637f43556372d728a00fb005f7c5a20cf2b7f776066d60b70b11a848005c6d63dba0c93f139067b39017c997dd6b94c0138c3619e9a6d0e4b8792cb8d58a2ca12ae5d03e7637f2065fbb9e2d1722fd3aaf234488ca157d829e9a3b642458054f3dd58da41d7fba6d2b488a327b776d1aaab1a364c710e755ab22b9cf7abf1eb8949c5ca20c070f275f8959cb00c6d5ab7879003f89f795351a4ef4850e033d929f9a349b9133b2e0bd1cabbdd381594bfa697b845100b96b5fade05db12de040b814ec49489f39f5abd5b37f570cbb516636d5b7378f12872d02d4de20b52ed8ca0b12029a4c084621bbb578b870ca2ea79fd5df1ef8664bfb3b1a1bf038e4ba33f6ccde42c5146470c9dd293aa747d2372db1561617920142ac1d32e4f1fd18e8b9e72b7efb8fefc56d08f00450d23b7e8381849b1385ddcf9310a4850dbd6db7a4992690190655760f557a5027b5ceab3743365ac9041a5c14bed1126c4eca00d7e0a0e0e6f666f64bd1466387150ece5835192149237d5dd25e703e9d3a4f652ae04601d6acf8228e4e86055394c3abc9dccd02f04a60c298d101260b408b2620c137f77e2019fc6eaff1b234c56dfe922b0192656254fe3356143e969f64b7609cbedebcc8cb2b68bcdd9d723b9c14669da6cbfffbca2351de51e87db6afde435ead0017682b8014f91d9734a9ab9b374257273e114a8fffac786d53183ba666d8a67e30c1fe45bb1bdcefb5787afcbad213f8e36e78d30ae1305df96bf450349ade655cccbb17d887f79e00728abb449ea427fd2d0af80e3b5607a74a57dbe5264131f2fc49cb74415974b3d43ff872d4106ff11b680f56be06fdf85ec9dd850b1f77f759337b9a9ce04e611036d3f45743e562abe4b959eba7424a712fcf7c3f3773886aef22f7cf6168efa83cd3ff70b9521cae1b6689b2b8c423d883a007bb138025f2a31db2147691bcb365ac242efe40cd09a746cc501ae0289e80205993b07f86538d486803da14b74fb0db6ebf1c2bb8c36275137d654c1be56c65891cd50f705247d85621fd0d61ade8c05cf4ec15b84e8adbcbe017d7d5743d5e91025e0154a5d9bac7c6b8297490e9c195c5d74e046219c042219817a5c56636c7c4382c6a01d721d88f4b4d20250eb5eae5f3ef481dbf8a3f47a1d51d080bd4cc33f12645c8481e57835b77a85a2d83301172782f22026e69a43376ac4f5b78734c9eb914e6c76c6a12d4127cf195ad030825322a279093cbc40a680355d086a27f3fb7560713b019e7c286d96833dc60590e9a709f2e3c632894668e74ed20e42cd83a23ebea3dc3bcc49d14f8697541780fb2072dee6a5672d0d4e7bdf5cbdacdf5fea9e03c6d9cf0faa1e954172acc26dcd344bb3d9b2e0e6015cc55d19713d795bdb7c21b44b305e69c69fdb7261483f9693f36f45d356462f1ba4498de1c2e8bc3e0a70893acef2006dcd73cf15b265a8a5d4ed792a34a846d8f1d3b9b3bb75f1c5e57a00b36c00203973ef4e2654f6cb29e4445318ed99f0de6ca992281e83ed03feedb66aeed6a461c6f2871ae95343cd9797e58430d5639d7ef5c59c78b29f76a055e18e2b85eff177770c60ca4f2d61e612e617e749b4653e7901b62ba02dcbf50e59219349120ac01e6b8a6e98eb54abd16b921a1ff85898f90fc49a3c8f8f4ae9b0dd32c3e7f2e1527c4feb67a496390f28532f20acc71abb8bb4f71b434104f41e36b705289858a4e8430b8cd9449b0198ca2244923cff1df0f63833373c275572de5a9a77b23e5ff54aebce8e86d02651f26ae32e69001e5f3951967579ebe8574682cef8c12dee0b18bc999f8cc0f07e2ad3ac94d3caf30c1c8a8295756aecbbecbbb4ade8a2b8015e52a0eb1290693c6316d036e0c443fc4ec591c32f7e7f1b3933c921d5812233d3c21ee5528822b59ef2ec7eb62f7b04f40cc8238a473ec37a07e54f8907825ccaa1421c2964d2c756be450dedc011e1cdd9045720421b9a4a00e9d3076c2fd10d71ee36d5c0fd2c7e42396b034a4cd0245027449242dfdc42c8af4a34df1b4150097726c9745247b78bb2bad5fe8af94eb13ee1f41dbd36e56d801a4c9c5b9ca5d3c26f4714b6fe9f69b87567426eb6f4ac97e8c9541eafc19fc90d3b24aae0f76c4f3f81063d206ff695d638048c2cb023147a78332939d2f2470d16f1ed0e5d3d4dde438affb2809488b99815e54938fac3b02deceaffde310cf422f9027f364f5e79da5d2b5af1b4138ac9f9d301f396b220829c1f60cd2b54ef24576e5ba6ccd4802900db1bb4eea57de7787eda0e30fa90cc19f099444488699bf7c442c398c2ed989d084c8cadc97325484e337848c34562b3dea6f7670f935ed3d5216c970e04351651c1c31a34e862821bdbcbde202d91fed38965e31cc3b6f1e52288f327bd0a787ecd92b3b6f535d1d000b0f02d41ee01ca54e4e6179ad7fcbd60f0e41dfa5c9cc7ee4f7de3844fb385ffa3b24092b30be697f1fd32c9faef29ead346e42fe2ab1d312901b678b43b7758edb7eaa1c2d038b4cd6a7dc759a6b12cec955bcf4179006a7ab6e22ef15986df107080d340b8870e2304d57caa87a9961c04655d7d66c7f71ca9260e02aced131d6de65d256d6b487141c51bc86eb1e4721742f07d09e799b30da7b5ba94c8d701ae34271ba06f8ce134a7a9a2598d1570cf05edd9ec868cfa2e41b4c20a8bc4b8bfebd45f5a60408f08e931617746d1464bbe1f3844ab3272ede635f771f9af30e483903ee4d0cdecbaff4d31451e7791dc97c92042fb932fe1c82652c1d682a55912e33de3b1299db076cef594458670dc4f911f4a244e2bec757dad4b0052a41235e2f5e60b929682608c16a61287826218a1ac3cf0d8286555d5b0552754685c365d4342f0d9c45065daf6786179da791a86b50a5edd6fb4b21f09d9747136aacf79ecbf52b00fb88b0630ec7f0a6699901ba4eff913a3ab33ac85a71ebb51ed343eac86eebb3e79c16e664078ccda09e77ef8e0919b8cc447116b65ccbd5200fbfe86e9bac5637b33c9bcac9596b57c14ad5da548e96a8ffad5f5c69247c68d464c770011da7b45a337f138cda6b4e15311879bfaf12af4c61fba596780e6adcd5dadde372823da6014122dbac70f0dd896a8d387d3c74df282a659028d06cfeab3ae22dcd1fc3ce60f69a0d678aeae0e5681952949e31ccb8975cd167c9d012f4b230b1c1f47022eb1a3042951b338a734cdd17db0ed483a621650deb3510efe74191a94611dc212c0c73b117a73b8ae41892cf176742bd98a7cb73dcdc53b42df56d640739852335f8d44d901fc884286b433fc285fd5b3db8df0a8522cea3182c071f559c328b8516c9252681a94eecec7ebf626c0a9014d9aaaa0c694d14855433dae06656657d1f8a939123d28e00513d72bd3802d211ad7c1e06b9228c0d5656edccad5339bcdddd5e01afdc01f10974be3187804324fc513ba583b7b2da1e9096bbe3d078c1adc6c34d92c54e9c49fccdc17d10e66962120ee5d9b1cfe852569436270cf7c4c3bb12568050e2ca4db08bbac16214238413195dd4d936272fca5d56d7551b9b002df1807ed44abc84c66746387b79bc9e830a635c308a7bfad7c2c22cee6d3d0c5ebd8b230837b7ceaefdf71a67a3a8eaae0c36de86b2d96e759b8b53f8b8604775eb7a7e13223cb21033dc87d775628581a954085c2d66c1c8f225b1aa86091061738e7495cb36a5ff032dc678904bfa39a00285cd6947865b6d4805e3411644b4a4c94a6fffe05ef31e156bae6165d801685dcec195552d029d22e5de393a82ddf3cd3de3ad8cd6bba2325a03982204f07fc3c21518ef17a601fd743b27f7191bb446ff61d3c61d7608777990997e911932532e5b3235f13423756f5b6c786720cf6682932c90092",
+ "50772c5a0e156ba13a9d86edc0e600021d56f7d31e7e452a74ad53a6775339c7ca6521d87a8c79b42900a1e9e6a1ec03f7e3d615611c3fd5c9927c40e5b508af1a298794b60148df01e9c9e78ab5ea8198c097fadcd6cfa6694be64e00eefe1a1885aece86f6ad87df766e692b58ebc41982bef5",
+ "93a2561a9904a1787a10e2a668cd6a814f2877a7b512698e94796805875c8d1a",
+ "588d9bc1d98210d9700ef488",
+ "165d8c9eabcd5e93e6eff7be122c8c242e1a7f284790c93324f924efabcec4a4ce48262011b7360c2833143d645ff295453853c92f0c48c6dfc2af7ec58d9bec0d13239c7e5593cdb39d49376c6341263df80c0ed2ed79fe9899d0c07de93f6ea95a5dfd307e49bdb5672b158a4df623ee86d54cd1a0fa9a60ce39d1f5f4b6b0ce9daf2a61a907cff3bdd3f29156ac439638e0910d728843ae17ea7368814ad7734732e7c023d4954e1cd5fd19fc9b76e9bb84b61dd4371478917757b14b366b4bfab4eab0d9de746088ad43d8742e2b9e58faff15c2eff084df5f4316111d5dd7d23cc0b1ee1000253f26cd260aa636f03f64a8342e531ca1515b3beecc3ee07a29184988325322d5c09754c278231f92c0d980adc919d4fccf4a1da1d37f1ddb58ca997d6d700946199fa007c43853b6caf5f8049233584087fb23c3952414ac487e452f0c3898486d04e5b008b843122501f9c8a294da9159a04119ad5c8e9f5c211411e34559d3a7bcf2ac10e0174f94f3f2968c80ebdf4498de172884dbdad0acc3a887f9bfe896a6004d54cc424567d53f1198ba33c56aa460edc6af0e437b34322c1144854bafb2434f00703c1992dbad0ceaa0616aec60a380676ca11558cece57a936959d6c2ffe0647eeffd37524fbafa9691f31499701b202d9dc9980e79ea517089eced779aa45b522c9ad193e63ea8b64e8a942f630d44370f23b7e9acfedac51dd9f139f8806b09a8fbbabc76fec3c3721fad5087a6d41f93973af8d787d8bc74a3122d99ea14e2f30a3c90be4b695c8b269784eefafa52d6a79e785eb47a23d72f037ca572b7029d2f37baabce57658119fb02c5b659e3aadfe0052f1cc3c0afc6fe4624533d9700388713945c20c1d175da53738fc73f48fe57fef8305e796b474b6f8d3fc5040042373a13384237d95bb045ce0c20934a964a8372acedfd6e559aa84180a86311a3996cc17bf7f73e5d85d4db2529989e5836edad490aaa5f56d17326825aa20608fd209903335de4b36b79f68b6a52194f6ea8ce42570533df650e65b50c367f69b9f08c32b3ce3e75318106b8b2c6b6d09369c781fbf2aaa35053af215b621f833814ec4778ac683de0dc22c418b077a917a6e405ccbde9f72ed523aa696be1a6f247b096b9235217bcf19b88d43178cce5a7d82335fccb4c079e00280bfd272b9f16ffefa7fea38d09dfb2e4874553b135052595812aed3fa15096abf1eebf9abd598289e0d156974de4c2654c60825d42b662ca7439816d9d3a0255f40a4965504f643f029da535d4b109e8658ec570e99859382ca0ede0b0495d508c63c7f1eff3f648c60e9b773590cc663a751178ba7603a11985ff519056661b9460c1aabc30e83bb0073a927682a06d1b8050c345f7920c1a37546d79587fae2a92c803a986248f90547f0b6c0ad0552d8260d2a0dc3cc76d092ab76b8c12f05dcf141167a6ea300bc23227933396ef6fe9d51a1ba5a754485950f06cfa6964db2d0fd1d4393cc36f0592fca25ac1a6aacda2a32f548ed20287e3d291661848a62d41504e4fcb1cd1785617fa5786712b3005f1a1041733df6cf838ea3ea0b93685889bc6b2857d80a9bc0e7a66f7fb3d805770402f049889311fc112dccc72a25bd127777fd87bf5ab56d39bfe6be2b45a8301c2f324dcc50b27540200d522c24941701f7293b8877ac84cf35638507c7d912a3a94e4384b68c507412df65d0c4ca8ec2da704bd4483eb2e0d13b68c0c2b68c106a55b9710ad0a1436d655a3cf3c419d5e6f027ddf5dcfc896a5b316a7dae9290a7bf81aed539a647c8c98e24e7ed6a4f7f00a11134ca715e5826625c250500f8f16b40de048b095b5dd08268407f58a91c86c36ca5a2bf4f8fc682adf1bf601da24414c74956e1a8fd2888b5260e980c32f6678a4dc4ff73220c22593d23144b84c2ff56920342248876d15ea54fc100c09a81b802dd15f030bda9aa08727ea49e34f0ca8693e0a06d0af06ea7ceddbf0584adfdebeb20510bbac683451d9f84cf0f4e85c34d979e550e07e7f414d6f1011cb3dc28d0df6d4aac113f2d5b04e4486ee2cdcd4157dafcbbd55e8330a7176d1b231d9f47a63da9ee30fec6cc2c5aba3a8c6154f79997af89d972743255355647235ee939f4f305ec655271e0cd562ff6f401b86dd5826c769298445108ad0d9e13c504551f74c507436911331db60ef0ea99dc259b13cfcb0596fa9b3c95cd7fc3b1611e3b012b6719afbcee7548939676dffc372276aecd08e6a14251407cf995266545427d49ae5ab245cd5d534c52542fc71b3973f0b766f3d234c8baaec8b74eaa8ba90abe160b4504769d02e08d7af4e7ecc167780c619cefa58865169b674b2b1e10d82f6560ba0be41a781f4afa46bd722566d941a8e6f87e4a5c03d89685a22a3470354f2922e2915f9d46288a5e8896ed13617dce694a595e379f25fe621dde8ba73d865976950954e5bd07db147a0fb74f87cb06aba49b073942b82fab33a878651df73df2721ef800b658bdc6c359d396f684598e93f38e79639b8736b02dfcc124fb9fc199c35f2fa1d0dc39939c57286e58a7deed7b6c76e02b99a14d9bbf11f65d8eb7fa096fe4baf0f78cb34736499a0ca550f10d7edc8909dc34b039e3abdf1aa67a51d37a2eaf4c07022897d4d8355d3325bcf392d91d02d462488ead90b366e9645b956c3802e4249d34b5b2b2484a1dec15a9477821df6bef5e1626ec5ee9832fc3bd0b63a3c4100d32fac3e9085f0b5ba43123f54beaa7ccbe6ba68231649f35a28acfcbbf97dea2d6cfd96025032b3950ec8437108d0f07baf1bc89e3afbc2cdbb5031d3cd9e20b19018adda466382059229e4c8c54b455eda4280bde43b36afa96e146e408c7104523d5f565d22ef86d4c7cbf9c6e0d0b30e37b37feb9332939c642eacfe19d0dae1259d3267635051ea5f9b518dd74786e45fb8bdf72cbe3753bd50bea2a961b49cc0e2d589e77fd25ebd962463fc728b1d288c38a79a182b124d345872afbcfe792d259e7e5334311244edc75d05f9a12eadb61fd3ff79fe8c097eb01a4ac1f0c339d3be74be3d96b0b6a15e8868d043a0f2007ee8aa51756d78b7a78ad90fd9a26afbcb51fdc20ed7a3947f715c833e363bb87504d8efc9f8b93a993e2e26430f79f3cce203b09093c9b456b1967212eb0db4f7688d4dccd4a523866f75c9d9e7ce07825ae34399c5607a60b771866a647b6d5e1e20795ca906e451f367d8c40ffe79a2cecfe7aa47a402f8d49be9084661c96ebb11f1b48e7e8abd2978ee626f962e98f99db4eb3c6a52aa2bb2e62194120ce1e773b9db784e8c9b5adcfb70e3bd5717293eebf014e9872c5c1bdf3fb296cb88eab5e97a5ac320092033b49f37d840dac23021c19ab2a89190f3c8dde927f6e6b41874bf71ba7747a616682bd5b3f17a1dad40f4993a1b186ce4f44afb4e36af7715450bac62cb1527eb8db1d87bbc4d9c99415d16660e48efd911e02f5777a77e72733af3c3f5315dd0c785d5212b79c46c3bccd74582c57cfac0d50fc0c85370476913f9d8e8e10d0f6602f2271994972de49ab1a91728713c3cfcedb0e61c270b5fb331a980965bcfe10b41251a0f7915d5943f49fb139626f1c424524f2fba3a407e77dd7513669894fd09fff4185fbb997b4e4677f6ea0b52892f013f1691bdb38eee9307a565e396bab484d91cea9268f49aed29e319b0add900b6a75f7461db5486aaf5366f98df05674361308931de753c70777de73337a996f6d4b0e06d63a69849ba7533bb0e446f062edbd6250e61a49f4120f84efc1cf74c1bd30cc61a2d719fa76991dab119fc814a7c56f48bd584c7935679c53bb0ac78905b5d961fcd89a4b567d17a5182651cb07146aa9a94972ce613e8ff9c878a8433c0244052f09980a52d800e97ba65e8ac186862def58c72b9feec91266e26aa5075b3337c7bb8716b3acafe666ffe2df32b78f9995661d3ba28f8a8780436aae1da2a3e6a0a16dc562b8d5df6f68391aab73a10508e0f55208f974a0505f0fc0d8a55049a7b631fc94fab91459ae1f199527362695b41972e50faee34c5cca9e35e8682099f5e9652f88cfe9fa990ff2154c89c1c2a4ed6bb8a889fecfdf048ee0aae7798c55d6cdfd062cbca97ca289578c832d658ceaf26faba54c9c3ee9eb5bac80698c1441b9cba287f749a5e30d5cc715a01c89353ceab0974ae77fecc1d2dfb31a5101783cbc002c73cd155dfd14685c2f9acc170dc437c649b6b4720b676848a7f9b56cc4787eabe72f6e3f2aed776f9bb1432fba93a63bfa44fbcfcb6eaa9ef4b79b32bdbd68cddbb9897cf5a02c6f99fc765790092edf0d5bca7c55cf232a03fbb6f3eae09b12e09a9b49a538e0589394700d16ebd3",
+ },
+ {
+ "3497e8d61062e6f2084ebf72d00e9a47b550591edeee9746f31ea28039a1646d384c4348af293ab778f92a4807c48fbd14e8dbf3d67339c991dc4aca7dae38b5fb7bfeaaa538611d328b653950f4f664dcd257b345917cd66dc6a1ea75d99f70549d1af9d67b1608077b41576f38bb4c0a13ff4fa47b251142c6fbb79f9a27f43841ed0ebc0416c37f571aef8fd63b99e93ae88db50e9ef7d499ae7433d5686b165579d3598f96d9e7b1c876870310703df8fdf2069beadb34984f676eb7d3840c4c5766dcee3fc39f0739260a499647429339482e232362bc72c92a299cae36e9069cc5f4db8893e2c1b9ec0b4f334de26c951090b9724c2b3b7655d8248bc12a27861e020eb1e4cf6ad0dab903279b6fbdabff761d4ba159c1f631e681f210a8782faa86e08e554b5e30046157a0d1144bd08a691c2cc2dd22f3c3a4e5d44c5d03f7e3e385382ee4683345c0d316d41ee75f87038b49e0ad3ca45121789e7e7b95615e1a9a8dfe02c044c2935a97b141f639448182252ebfc980e0411e5fbcb3c01acd5aa7cc5d67101ffa6ab6acacace5f02d67155c26dedc071ffa66dbad26f67a819d46de0556fdffc1b4ab6d60905d8ef873ea1e51c62571c08b4c6db242e733e02e11e5840ee445c290b2232010b118839b37d4615c4521e8928e9ad475cdb4a3de9928ec7e6daf0e20d22e308347b31e7e877fdacda0c25f2e5c33a329e84707816ff4ffdca30dfc753c2cf883df16016795db34359e9363fac60624ae4d2b30bc1f2f99c23d953779c22ffca145fd08dad83c0f76cf727196799544c6c07483e0a41ca2e1b1da5a730956154f531d292b5a39a229ab13bf24a804eb68786e481c8aebfd3bc557afceadc41d00e1472c3b80ce652be1245089283bf1a1a93abd3325bb6eea121db8c0e1d6c0c31decfe9dba63c89b881824b0531651fc500f2f75ca9e5fdcbb179c9ded5d600a495ea704c2709f4a88c4fadcda4cd82a5b089f25a6fe0161159efe03fb5e0d44bdb5487f25e8c9adacc389860f62b06a6a4f8f104d9171622f70652ace736e8b28b70a4d9fd3fa4b9784d1a6e6811150d0a0601d31d17f6041e58a1058f99b80b0a6cd4f79c79a104b6bb731ecc881bc68e1d99ab358faf43d8504957ea0152e46e27dbfaa17d0f58287276e4fa82ab78a03513d5b4c3199d1362e4fd6447d1c26fadbd011abc69332ed0181952b391f2e8a5c89d68e22a7c451f69a9573b6bb6d918c7e3d52116f3f12f1d43d2af46bb450f58bde1732a268293cfd9cf2b90a844588c1979a30d6ac21aaea4b9e5500ef4a8bcd62bd70cae6acc8839f818d23c615e45daf14335c36dd46817c9b816be60c3848caa812b055da33f45bc01721d6fb7e850fb1e1458f27c70bc34876a955aef11f5703cfacde03a039c3b75b99b2d91fc18b00071a28ce25eb169b946b49858aa0885a4c665deca020a3fbba55d4d9175fd91e7901ec9eec0239806e8305f8238e5270f4af5c94d0008f8a5564636cc33c8a3d3e76db2a7915abe798b0dfbb3e322b33e188c7b188573bddbb9e4a7edbd4bb194b9743c4aceeab449f8affddbc2b109eb3d84f3b2f8b18ea2962680437241d82bb6146674ff1abee7baacc38d5dcd688b425c3e3b0dccdda3e36de755afcf7155d3d7cac2e279baad167e2a743b82ff8ddf3db8ecfa9680ddf468339427a4e9fb8ca4ce6f1e790c24e7269912a9989088c65965b0efe68ed44eb26876674261e3e72042f5995f1a7075b3932f4c23a8027d0db35ce4322122f489995bcc0b3fa32b7298c4c1b3354766c866a2fc0ea5690c58c5e08ae7037f70accb3ca7faefc37d78883f2bcd768285dd2571dbcaead813a0b8ae87cc1df868e93500d414c4418d5c80b919f73b9fd46111a02bfc884f9d30ee14fcfc1d55d54256b9572afad4777b8d8172c911472a22e7461f6f85aca063c19d6fdef3351149ee6864e93cdc54ca5dc7837f0ead91f5e3b155795df5dd1f933cee8671ffc05058353995019e5f6f55d2de6470605a5411afcd7fa5aa8f38d77dbf496d7fa9c5a4d35ab661aa15c77ce42bed44763166160ed5bba954e470c293ca301363f5b837406ea8ea746057588c34acf266030864d8c40e2da88ef04c49205fad1607d456767d30eadd884359bce04c12e35487bc1885d9b104c9fd4dea4ceaf054cf46cb3c77a619ffe963acc9bfcfad0447591ccd32cdd1fccb1fe7080ad75cca2e17f695ce0095a774327123f21e2839773506a9f2d896bde87dc5e35512ad733aa408f8a49e9018d1013cc32f550c968a03308cdbc73ab444f0a79a13450d4de906369da4c6a675d7e338f738358dc238be4f047579c8ba7a60448da541cb9e57f22bfcb8c26280a59b77edd0f5a009a3ef1e2958d6d3c3372840dc6a0c6ab1fe86aeb7590137feacbfdc7da57c77595b8572b45c4677836ec86fd8c4ca8ac351397aaa3aa298d752754507e1cc514d41c3f1ae0a692179218141f65bccb9acf6244730c6d00829455d21371972745b3665f930cf2aa9f0abebe6f7b89094aeb4dbdf7bbbe794f134b6284e289c995ef2929fc1bd39b259259950de29e57cdec15c4a7d33ef6e689596a6ce23301d25c2ace77fe699d90c2329da4d0f471bc093563dc735ac2fdb32c6995606a67bc953534939ed1236003c004d3b47590beabf39a1e4d5d1b00898496e9effda68433da17d1ab3a32aefa3681aeac116c5705077552649153ed15e9d704e67d8819579feb02d91db0d3533182ff43ee5648f5cc9a595ded4772d61e77bd9bffd6f29fc1f478dea44c32d5ce3118bc8860b254fb0bb1e85223bf709a7c0b9a52fd3914f1b1f295fd246bcb568388dee43a32df45e3c798068608a102143b5511746903255b98238003eed68776b46bb0e64af6c9118ecf9896709aaaabefbc1f58bf45b45768345b560ae2cdbe4d7da497736da8013c4098addb4258cafe7823bdbdd715250b707b155248d39fc6773639e4de3b201fd3cdfa1526c4149ee7d15bbee680c956fbdea844b1470a287d430c5c7e2d7b51fa756720397bbe214c19df3399a989958732d93979e361f7266e53a59bcef695435db67cd8749d258e7d582726e1bcad1395e68d7848849fb6d74451a53ae6e8989c64701102959f7fedc6a5cf8352e218396f9181f33037ca74886fae6e57460bbcb71cbe4cbb3d3a81e2090434eb1d6d5baeee4ede251952ad88001ce047279cfe435a4afe97847f798d84ad79a11bd44f09222d2f3b7fdcc47ff8a4c61f40c4629a0f603193e0aa2164579a05726e547c9081abcc0087907f8034469f740a020e19623fad42e9cea64068abb3d6ff2f6680da328061c200e1f646816a5083786ae5b71728a0e5cee14d7a942379c389fa9dbc7afe7e7ae075c061df11e4587bc90f92f1b077c091c43a25e7b3e870ad852c2883aba2632063c4ff74a857ef7267816317f823a8bc5dcda311b513be3a40e6bdeb89210bece50a608e624f00c9d063e0c8878884e45527f50a3ab4447a9a01652322700f087b6f96ddbe96a68ef98656800eda6563015a6d3c0eb1b6a9b21cccd58cdcdd074b73e40a098a980210ef831ec9e881cb42ee07519fbdfa52d9c62766a2046dee7752f880dc9082ed7f050b49ed8d14307b1b811bd87b6db2419418e49885d20fd7ca8fb45a11a1da17ac2304393734b552b5d02a303ddc72d1f456697a287851f207054c18a6262f5349348c806841d21e11fd4e4ed9c01fce1688483e009930079f7d2045a34f98ed83256dec66400a783d58c61619e6e42f6e2c6e6fc69e76651b96aabfe643ac69681955ce595f4696b80dadd1f3910061be6ed0840d47e928dd93e7c3d6932d3ead820d06e2539d9a604a6b53db6bb599da851de7cc060faa9af76d708a9aaf371dbc3eff0fdb99702504c3006f789a49feb730cabe40745837e2c8c17c77f999333798431231b337357637a5efd1eeed891fb7475f2c9f960e67578adf50241287bc5599ee08d0237f08c86ed9b75b62d612a9353e48cb4cb022d78f73fba1fab7f794a5ff64c97e6c91ec464847a81e5a5253989a1ee54a41bcd9b4b77bae6e72421471a7ddf0136edc59b72402d57e542916ee47fb3988b7123c6e8debddff2df171d4ce61e83c3d41f36143c9df97f2f68639f1bfc2a9d1fe175fe9f45e17e5cfebb330d3f06e15e3cf58acaff09ea576d896359a3f06985765824bc499319384e4c458d4326db801c564b0b503552bdbec60752b670d82cc8fce9028ff24ade3e805b81a72701b37d4ccedd72118b20d792739e035bbacc4893ded88619a6c499f246311947e48684a35406c4ef279c71ab2a74f6e5313f7900080f19aec3a39109d4aa41c930c66c84cd2163f4cdd59fe84a86cd8bb6468bce45a56d09490e032da844e6d90b436dd874c1cd32a75d1ae1d3e86d8a2ef948649eb56dd7b360f55ba5dc34a12f9279945436c6fb83d1ed57ba4ae1d9342a3dc2df9baa82fc9fee927c13439ba5bd2ff9f3e6f577b8d2df731db14c51db8a14bb15bf3e125f1ca4cb2fe856c5a576cf995db5010687d0799581c5e76d400c1855bb46680a631cc582f51c589a831",
+ "823d0cd34e7450550da9716c1f456ce0cbc79431483a6214939266581b0e899e4c95719a09c1ef166a618289a6ee6971b6fea3fe380512cb977823b387ac51d341c26d4a835c61eebde37764d2e1d588df7886177e98e3151106c898b3196bf4dbd83f5f",
+ "a4639c22fc7f370d8500a53819102df5e86c541c0ca10e8f6564e50b90c28f34",
+ "34a04df283c45655a52bdd84",
+ "cd8d1b2e5f65ddb3c0da8f12096134da22ad4d541444964077610aafc1f77f8da5ffc75bee807541cb6eb0526e78d57fd88fa9d9608914cf391ae7ccb8eedb0aa711889f9b6192601163b271c90df5d69fef487b6c05a24fc667469cf16cbd5afd58fc830119fc9f61b26dd50a96ed84c96825a615a3aee84ea4c950152323b20884346b25c9e2a6be3a93505ba059fbb114c224bed8f05f54eab76b2c9c23a0fd942eef9696ff67484b542c8347f1b1fd7df7242872b3528c9e45030447b2bc85eaf191963291e4223b75778335e5f1256618ff87bbd68b5a9e5cbd2ca1dc8aff4625c834edf8fb0d879b1f75ba9b85895a6bb4d7569a41bb3be6cdd020065bcc69b44a8fa335d9418ea2d090d8061e042e8e1a6ac03a6d5525079f14274079734ed42c5c9ab9986f0fee6bc9ee6c485e233e9b4d6de70664902529a135a5675ae129353eb2c00b73f226e84fe8c594272d6eceaca28b6da30492c92074250ec80beddb7208f9b5418944305b0864009b3bbb3dfbfb4cc2bba3313f8f7c6c19860f1dc0f5d7aa06e3b551adfc63dddac980a79d72bd2225d54a87a93717291c7b78bdfc5521f7f3239d5564fe9c9559dfefe76b77efc2e75991f31a0134529a6611ab9ef076491f2d2d81ffc5774ba8f8009dd7e5881e09ddf5116fcb5a44e576aef6cea91ebf52c56c742049639392cfb8b280dc2229252e04d8d394ffafa539290acdd8118656e7e1a4f7bfc0bb689448379e8cedff7590a09a3f5a29bf819fd87297b96ca07431a29a07ae126eb9d65e21824c16707db89868e127f17614a536de6ed268b1600a8b02aac2bca54a09b7cccf8e184448df334f95b9f0221187d56da7bd422f09b4d94228098b563df53414a5a86728962a2ea63023d8c3f03847b36db7cd189ccfef3e623b14842b8cccb18b4f80f01b32a4cec48f3009b98ffa25dbad76089c8700e90848da74aeca81d01f4dab2b7e844a3e48bef21f33c92734b821ab382bdf6d0b1048a9866e676b78ac9398678ff626d5c173a15a0a7514b2544405dd54eccaa2791605c87d7117bc9f8c0ad84623a9d3a2b1733304b492d4dec38f7981db9361b03a2837a95fe937976c7f4341a802dbf583366fbe368a3af3f92618046bb55696cf7af1f465a5a57ec5908621f431ffc762f35abe892f772a60a3f75ad8401321f67981e90083fdd1cce40903ce56a629120d6e13c8871523c4d848664331966298c8b31a5bc8174a8c14f61cbe98ae7ee3e90bc832b04318864d19a9b8b6d49a260f42bb120cef9afbe704faecf0f428d917ead9f020f5e9d772bc8f29600f8a7623d8971c1e3c5f1a3b094191e497bd70f85de124137cc4b9fe0617cb73cd44b89aada072625e25976e7aaa5a8fe9d9e3f32db47d1565aaef0e84d256bfce6aedfa1a2dce5a94976a2bb9a0da95941fb7ed444990b0e0e87627e35f3235a998019650a5e5cae804ecab8cf729a5c712f1e7d17486082dd50cbeb2ee1b0be6a7bf08a66ab3cf1fe9f49c7083f5b8ad183f32fb35fb8a41230e4041bcf0e5ef54bc3d21ecc1fceb08d95d745a997e8f2fc3c0f6b1b6c1c02e03ff02ae0d879d13eedd42d9f9949ca7ebb785764162ceb6c6f9944dcb3927b2f4eab23ab566b2b2bcc0c7d77b82579e88203602264064ce98b5b1ed992c1bb13edce579ae7f5e11697b493749f308b33e47512533350df5c07c3dadff656197884f359cdfcb736d29231aea1524b56e06c92f5a98ea663543f67e44003f5b41907a951dd792468c84c5e0e1b46149a5c9751295e153990b78c0cc712889a21b299b0315150dc50aa3b4f7fb0079ddd39d263a754b1dcc595c76ea9fea6c120384afb38d4bd40491c4689b1afc9dd096dd0327c84802bda6bb6b7a8830bc6c06b308ae9665a8666a5551ec954eb72adb827ef38f036c51698a28c92dc1c9e25c267532da2c04c1bf27f5b683ac750c3ef53a8460dc186331549bf82868f9327422c09afe1cd15e161bc41a70cab2f973efcfc8f01a380b86a432e1ae540e09d404d93d22a20dd5f685a52f0acb863dadea236288b1714700f23d1c19e40e219e8ed21f6a393e541abba850ffbbd4030e5f6567b7202fb66d86cc2a0beabd495814f6a50690e8d74cb8b093e4d43261fff80e7a67ca06dfe808899cbef84c09ece01414baac740cbe4c656b17991868e2a136f4785a0de311aeb18cc95ed33fbece22aaed8cc1e47f58cf6c09a6f92c96f37d2d2485b369093506f5e9f8534f8569655277d0399ddd3d33861bd40c71ac53a44d1981cd744d79202322d47a0228356c0e27efa2ff1009cf2a416fb6e8844eb76b8077a4a3961ff193e1c95b222e72688ba48be82ec5da498e58861ea613782ed1ab50a95b5cc236834af98e61528ab18453c20ff978551b81e1bcc0ff4b7092bdd9ab0b946b7324b7361ef05e1f7d7f6a336281b4bb2c671a95a6ab84be6bef1b9c8c3d2536edb8d79b40637e16d7281ec5243016232d7c9fc07ed9dfcf555055d8ae65f12ad150da81f62f2e1e82b3adacf6d623ee4759ad61a09038905bcf1dbbab671dd28fc1d10a0b7eaaef73a5862ab449bd84c8698d061e79fbe52a86739ba945a01353e0f3916667bd7b4356cc65451c7003927f2aa738d98245760550156dda529be741ce3ae1afdea0de35ada26ac241fcb5d518e6ee7f9930baf88bacf8bdaccbecfdb920f3b26285439912a8902ae029b07f28c1dbcfde780cd2bee6c6e5f4520c5c7ff3ab5448ec86cfb270c39586f80041f3764b5dc77dc5ced0695c89671cf90ed34c4067b4bd938b1493c7902dd94be824810a00bbde4915d138fcc7584790bb0b6682fc0799cd415441ac90c1caa008c7fde3ab4a3aae478c64991ebe07e6c4587d3046c9ebb8e125e795f0be9266bcee5a4e4355a2830c5b34e583b0355b34b89c08011db6f6b8371de003074704e8cdda37ce42c7e395b6a37bae3dfbe67bcfd1f125c9a262d56883ddc028773988270aa30c6dd326cbffee589f38286533e1d5c9486011170be591beab5e0ce98837cf91f0a58d69d872e364aa88daf9cfa71bad167129420282d99ed5884a1276dfffb2c4100c74a8b863b063c07937f2e9c12523deac4ea16178863d975e3a5be5efb5ffbea994d07f7ddc5326bed1f5c9415c1d4ee1667e3a581499bb573595158636ad94d84f7c6e4b8efc2b141f2bfab7932a050fd88a8c7b21877cddd488543db5b11138cc808e1248b6e2ef492faa8a32f9d93e3c060b5cec10f03794248f9662ed8c283a8e0eb493824e2750ec75b3b1292d80ce002083a3c64cc487afc31b20f84a778f386b012ef7bef46e638d0f1cd75487ea46e05621d608482637b3e642a9a2c5371bead4386eff968b3e007fc263086d8a930dc76a8431a4e6907ae35c7b3291075d1c723f02e4895714803c0e97d65b04c0f27d01d5d68001bdb3bbd44dfee1eff1754fe8c182cd9bc6ee273beb2a444ca1766f747d86f36cd8cef6eb1dafe0c38b9327a8cac6e83e076099188f02721cc4de3d940c3ef19d9b067be07b890c798a79ee8c44d96c5e05ee5d5202d941a674378386233a83bc85134dc8c46a7531b2b952fb277d8089cfb13e882bcf7545f0605271fe38bf4754f98dfa13fe6b635a62bcf962553882a8f28a9a5fc0b3f85509b702d4a7555d40c4f7d10fbe80d48b4826995fda7d15f14aa9b95fc6526101cf09c97fd74baca6bd26b4fce8a57b0726e0f68118969ec067e9ca39b2ba59fb0d78eb5cec5b872613b1b76763b3217d859bd6d991bbb5448bd4e49dd6597ddec9e46afb3f71d254aba828c91de51904139ab19138e36e6996a207da80323d96077c97a3e8994296376d4dcb602f1e77371efe8b020b7b6f6f7bd2bd733ad9c06c45b77a2893d73b4a8a57707969af74ba06b2fe7d4079bcad1cfeb3689ab95c8b1215fe0a855eb431f67df4ea589dadbf055086924e42cb142c9031e25b81e8e1167a54008ba1ad7fec6794f203b27f3092dd72bb766c9653a72b2e25c965f53487cf3baf74eb7742702380303af8c0a61cca3eec78d4b709e35e2cc5bd586263d9f56fc12454547bc6165e3f070ce7b2bcace5c8cbf52f987568dd90237cf190dabd4ee7a80494692a5379b013611f4eebeef8e1ab9a9c5ba61926095545e19c3dd61b7b404230729aff7d82b6bbbed6b4a926f6e49189e3bccb578fcb3537951fe9c78ac842350ddd80133275ac0bce3a669183776fee8288f874d29190b452d65bb7d8edfedc6fa0ae147102b92041af6dd8a566932e016763b60a5b9b1e3667f228cab075f966d1c525ac19d12046c6409345799adfd7154b6d8b51eeb1eab3a132ac6a2e08acd1a34bbbbdd019195af9f8a93c6ed5463765173e669cb0d42b6cffee1a4b45987853d43c02f920819f45a4fe0905d8c65aca182b4bf56fa0dc51cb53c642fef003d92c13ef4bc1bac571cbe2ba3673a49694f6311b7dfc17a4069759177930b179748d4403c7259e10a5d221cd0a6b745966e598f894e607b779dd5289fbdae0b4348141ad373a62c76aa454b35b39a7be875598bb30007fc300606ee2537cfcd7c22b6149880fb3cd8eb53054d698a0d20f26a5c3ce468255737a68706784",
+ },
+ {
+ "5622aa8d2f308dd468a7e4959ccc01f0e80d91f79df65b8201eb44911f6abc758c6703bb97908fff377395d33f96c328a4541f414b7ac34c6607dd85729afbfe01feba988e4997c6bd2c99fcc35d2467b143a8fcbe6b49247226a9e4c0a4e3c1a29d5931e6f1f7a31d90a0e0edc4479f08ef9bc65ae4eacd0b93b1cb38948dda31e60b18d702bbf5935bd580201d1f280cbbee679fd834aa6be576a37a037eabe989c3c18c7fb61fda8b9ffaa8bf22b57a101c19e850c454353af7af3d755b26ff1ee78b9d9daa78294972d108958682a5a29c8ef260e2289ad9d7d74f32fd4e51e5d9ee828366abccd97dd56e035713a6f3a1985383c0ed5d98c4accac2fa1ba7d30a295670d5224952f7b7554fcbfb426c9496f054834dec48f9b70af3d2b1c6dcda1c4daf3e9601364e57851952c785e65d753be1c22729bbde33aeb1e4748dbe90da6ecf716f05bfc68ad819515dffafd33a909562b95140ecfff1d0747f8e0459fcd3ca6cd8893262614bb4bf4b639285f327e7ac782898781968ec98f6f0f2f3c4bc5f9c4691ffa7ddb3662816f8ad092095b598bd4d10d6b5fc6fabed619eb11dfd4d638f4c0b6cff7194156a411e8ad6d3229320336ad52fd9811c3a1fcd571d1bbbac67c6186737ac7ca1ed9b2bc46e4e578f81c164b09ae5cdd4059a2c22b5e7ce1dade684e49200867f9bb1430aff9b99805cfd31f7e3fecbe898f70a4eded86b8bbeef7050eff6cf8ba71395a7ae2e270a2b58010e56cdf6efc4003da3d8a82e96979ee68694b6113cc9a6e377d40a810063830eb95005a81405e5b7de8de67424845bab1911bc55da6338513742d237a555465fa54b07ba50ed712e7a57a39fdcfe4af50f064ae969823aa1c40cd86a621ec90769d0c1babd33e8388a8bd76689215b9827a5819127bb32ecc80a562a291f3192eff34cad2635e5b0c0bc174add72e2041864953f1fc72be7d28111fba0438d9036da3d5c0f220ccfde2319bb96fcbfae6055ed7f1c1967ee9a78e93bbb77cbf151084d602a5a2f087d49c3134582c1a5d7af24f4c88be26204cc9dbf4368b19470fef49a5823a2d66c65e9b1e8ab56bf5a7bb3220696840a6222caa58a7b39fb792d95d25038a8bd9d916e853cc5459640f8b8468e3d51f05f1b95e996cee40ffb7ae14cb289094f1b77d5573c1aee7c12a6c3a1e31491422f272cc5f510d4f18ab63d3c3f468c5abd61b2fa7ba0768d46392e2a4dc06c7ce79841dca916cd33cc0a700b50fc660e5d1808d8b87e65feb89428055495823b2dc317d6d9e50aa5ef7ab14076174ed32f56abe7d410e58ca40e92f8a31433d0d74ba7b130b1561f2b075fa11ead744d031f34d82f1a64d428f6cccb0a009be24b42937bf3e99a1ef1fabf0fa7335dab52918382abe756d3de229ee8223aca6d7c5de87047838e387d4e472481a4cfd4365256e13aacb518ce5300f18dcb5e0a28477a6fca08a74756ef6bd8933bacc98d02abc7ae60df7cb3e06d41abcc4bd313c543ddcdea2424d98ffc6dcaa83658aae11f5841ffd4f5df42368a0e815d2146a0fe138b223764b133d17cdb08d485e9f3dd2bf2b220d1f4565b02d7b9231d592130e4436849f49b1a70772244fc0c38da372a8c57fc80ad57828410a5a16ac6d14e093997fdd5b26e4cd4b248e0ea221715ae6e112e1b68b09f795540e31b1231244bc922207b906c4f42b5302dd7474286b653b4d1bb657134bab117d6c349fa0f121c2f8dac9cdcef510c1c28545eae0ab163db6cc84ca182feb858c10153d0136f00a01c9c7d0bed892715dd85c4e73627c3a2ef0f43710dfccacffd1d9f118c9fb1a83b2eb328b8da3e955f027d95294038184f7b895d77532c7570cb86fd6b37a5a66659cf1e330db3930f302838706050c0dcd91d532d49c89d144e9a7f864026ec99f50acc02bd5f11ee88495ee8991ec4723b189f84e03d992fd718b5173ea1b033ab7d3568dc4656648fb54d28d3119b0f293a930a772c394f45ee66838f17b73a94eca27033f9d5c2ae22eb813386905dc024673850a087958eed191d04d05798bcf909eff2deb2a0009d223323b290e3d6f71b2797a2bc2590d54294a5992d629336518514032614a04847c3fad8a7d1cfc2f86765b48cf58acf892f68b691fbece38100e6a71487ef5c4ae934f1ba03b4b26a1967f70ef1c697202e4eb22a3a95ab3b7b524f0241ab4d2adf3ee5e3f2974d0bfe4419ef0ab11039ffc26339570e74d260c4d5a16f22cb4f60b03253487f5e46c47836ce29460728086a615f78d631d89a06790928455889f58adc3d0a3a84ceb2ba9cdb00a403080e6567873b985fd59fd9dec71e375013c12c51cb67d599198f36f58fdaf897e85dfe6f9896cf6d35a84cfdc6834dd9447a2a10e1ffa9fa8edfef1db9e8b4a245b211de49e04b7e88977b4e1ac9285f43526f2452181ee0f80efeb1f6b2533b656519ae45652ccefca81c17714476b497e5d8e9fdf6c9f504c7a7fa7afa36df5f4f8da5b4b973b1618fc8d2d43e866b235e5420551d1659e5bd545fb78a3e17d9cbbc8e842f3fe6be07b892453ffd689d5188f26f9e4c545ba0b3132af12a03bce6914015d026d3d7df661c1e6384bbb50dae24abfa78079a2b1ac41c44c7d82a59183f293f12011e781d3cdca2f791afa5b55a9f2d6139587bfd74bfc54ce91e642847a33b48c1b366fd8f08f520b79ad5113a0273735aee71ceae361a97547fc09b22fbe4e4ae4ae13e52d65e0971341aab368d1e917c8f5f2ac57ac119f981b51b7c99ff2be3e16935b7c73e28fb58d332e6f2c36281228c479c4d6095cf15b14baeb0769191dfc649a70471a25d45d4433797a5b8ba31ff567e60ec4d759d99244d0fb5dfef7c2896809938ddde0d2015a4c5ce5ef6cdb5752da1c2a33e5bc78b6b7c6a5af892f0792c28560a357720da3cee3833bbeda8e98e6a8cccc6535831cfc28bc8557b4181a3978bd90eabb34b99eb7e55d9263e6790ca34561d8c87ec4e12b4a38df524318db00a9b5bbde6f5a8644a818a88e91b521d716fa9f95bf70b109b9905bfca926fd42ecb9114c039790abb0392a41ee4c190536a89ae6194befc2dc4bcf7562bcb84f65c99b69612c0511552f53436b6c489204d3881e1f67e0fba3a061165d2955c2e2e12c440d31556250a8a5cc04ee5e09b1d627c14e08bce1a92df7f6475db92a3ee57e4c16c3ae677c44237122818ad457a29595ab528744707f3ab7ccf3d20bd94047e013e647802a7af14cfc7c11441ea6e9b9f960fe69d03911ad2cf3a8f633e0d647c71dc7e188c92e75353fc953d6a30dd0040c39d4355b71524f1a4872fb1ecab22c8293b54bb22a80e1e3d4c886d2988adec26f041dd0565cfa9edfe5ad9aa7da1d3b8f68fda9e9df9dbe98148120af6ff30e6400deca6dc9593dbf06c856d0d582503e7ffa185f87c6e7ac58184bb80b4a1c0c18d669e23f9791365fe807356a5763ea418c39d94311759b29b14324fb6f3104359ae66532779b825f92b7c9ea2ba43ba7de04eaef7a86192bc93e17286f1b6e0a01c33c796ebed8f17692eb9237173a051c14e4869afda2643bb98c9ac4ea94c6bdc1401c80190df6abe988d2f0b2d80cc7bc8362ba25c6e5df4370a43e156aebd6aaf856b3f64d5fefc622d078faed40b760a361966a4765adb809dbcd74b7a41faffad3a64823860e5656874133c7f8a46b5a3ac591906359aa4f171ef6bb2ea6b5f24cfe25c2fc7c1973bd5d3bb5f197002c5ca1bccffb570f0265f5cd949c7386d961ac9c5e18b5d1d6030d8bf4a48c10f12dcdb11924b02b8ab5e91f425ca62bbe42b80c6b6dde3160ebbd55803966716734327058e29bd39874f2eac199067fdbbe8c372c5a688d3615e2b65f4937b67d6a26c64cc2a9e5379cc00925c678f174f538915f912e85b7014c064a73bcc7ddd38e1a9627ffddb4bfd6da764fdbfb45048c9495ab1a4cac5642f6c9ffbe97d33cb26964a23719620df3d85dcfc392c4502759fb31a6a797e99e51e94cf9bc79ac15de4e5cf7a05aeb88a8ab4c3b6f9c52b99794503f2c49cd7e230a67df7403e552523249f29d257b35c0c7712053c3d9eb583a1a7473d7f296d25a66566e4ba8b08de2a31b082e40c8e5b1e93985b324dded3f52511744e7e99f4e3ffd99d8ae17bb5122b37f637c5525558eab18a378f5e2cb56fa003ed3af8d139d16ec4b2ea79c415b0ba4d750ca2cdf653582ee3b65a9825fb9b123593e36e645232163cabda515b959ed0a1419e9894f6c677ac200fd11babe3503ec7bfa319f1b9559d94a6f82945c9ca8667621a5d28920949a1da644cbdb58b84742e9d65e7f2027b99fba4dec46f642bd17e88fa109143b26ba7fe285c89add0b74a369f3d381ad633bfb4f72e1822ff96aaf9a73b3c59a6e457cf40e17c1198c64737037f52d9b3118daa3fa5cd3e3c7738e3b3743c595893289974a4aa0d6bf1446e70964823a7d5cee67b9b25b7125d9ac5d1d61f2a6947c3deec6deb575e2fc5cec60df26de3c0545e5b79156dd6af33a78552d1ee9994cc8501b7dc5fe7a22eadaf201a92e06ef03be705a8bdb4db65392d3628c7cbf44cccac292c93cb5a407a7a5a0d5ac9fd95b0033d6eb719d3f14609190dd40d5aa1b983cd4c4e278cc8a1e7d5fbb0d39060d6cdce8de6a17e2dab973a7fa594205e17edab6514372eb51e03b0ced6402fac0efd3af49fb8214a505cc9f5f0ea5308d7fe6dec369ba154",
+ "9f522375925222a04f5c95ee14b6386412025903ecad0bc3ab78afe1145136b3a3592835ab4ad6faa66be9",
+ "d1ba82b3ced3e9817642aaacedf482e79bedd0560ef2754215ee792514bbf8e6",
+ "bb21211f342379370f2642d3",
+ "1a6683805d3f478ca1c1512b9846468378f83be27393db63956e151ec408368b47334afe610249182f54c4d0a01b704db2aa90a9755b8feb67ef9301f0715d7d6bdfa5cc4497cef1142a43eeb42f7c413e8f489af30d742a706d05a40a0c4a5991f9e2cc5d9fbca6ad3767682e20c146ac35aef38dfb2a77388b738fa022158d5c802e5f0761096bb45b50815ebf09172759521b5c5d459703ebe9ff669ee4d14a86e5d0650b597f4a082ba0aef366a924ea378b91c3262d99f48189eea19c76c0f644079f8415c11033cf24d30d6c149ab13ca5c29deafdc816e457257361c1af4b915da312d2e6c7fc712faa27be3e67c893f9005a0e2c28369991c1dab22d38961d1abd6d94c4d549cf491aa1f8d522be3ffa6d214825a5fde3c94c4e35c29b8d05b2627eb12c9d94f450a85eec6bc963a279a37c2344ca36eb604c4bd11c2bf2ecc0dc16c2c365bbbcad3541bd54f8d0bdbb3ca4a087b62fc19fcc1c13984eab807d2a6a1386643d90d412d027bcd0a638765498cdbb1f4cc1b91b69bd241eab3645f225ece85a56e5008d6094041f8cca6b9a0ae3b15585de6fe0695d79d348f8619431ece40e736957a7627224fe92bbe30df5124f476d97e36b5b08b3787e8e00f0c10013068eb156f82f3494a35d6edd5f7048d1e91954f1013ede22eca8b4ba41699ee08decedde87139180a567c6d169b672af0f12aa09ce20e9cac4e78b8067d31ba4f63606c00d1d787b868cf7643fbb170f8074667c9f7584d36af80b4e6557724013618c28d0dd40bfe9d4b25761b3c99558af528c2d290d04b09821bd7f992c044dd61dde9395bd0c9ddec6d0bf6e044ddf0b4b2d6753f5acf2e9c904caa4e9f310578527b85e6738803758da646919989f735b09c9a5744e63fed2c3982e59fd29d2baeb9771316bf8d29213a4956b66c78d5654436ffdd82d0d572530fd09507b988d13fd743f35333237681f8abbb301a8ea870159f802a57760659094d0e4902036c5a62c563f1fc86c4238e1ce89f5176ecaea194ca112fbdeefbef4fa7c203678cafd34486fe58b2af04f84a1cb620c6e123bfd96301e0a5e5e5abcc95d28b852d0cee2f51faa73e42f22fc335f50de4c3812ee14038633a195083f3944284c1086c34995832c3cceb7d385b4ce86af10685c16005495121105272d1d739c584a07ec7801c3667bb280987a8aa41f9537e9d1812a5dba5b385a0b71d2e9573c6f3e9ebf0bf7267528946a6aa6f43efce908d32525cdc3b825bb11c7239f1de412704d24c17455b9382fd6a873180f0d5d44dc449320973d5cd0d4e67e83946b6ef47e5fc3dabadd80751f1421404e56b1bce748b7bde63c6975ca81f3eaf52586a55242c9745dee3f7c796d4508e818eaa4fa50490c1a79624561b98d2e1139a328806414c905372356a22393ea0da51c83957029edd8c2dfcf46d9564264d74c1c0497034ec018b1dd4c14acebc34b6d2c1a616937c37b8b4a0ee5dcdf787a0de1173798ab929b72e0fa83a6c9b9a99d8024328d9c236a8f57550a4f83e8071eac76adb55939f85f5b5f514174b670a3e8dc2b54656f6201940a81fe4953d2680ae4ec58635ba74d15efab3e06dca6ac269711ef2d4dd49f731e24a92a3b935ebbb3fe8d001cd4062669ae4baa62c2947033afcfaca227d88a11769f87456d5cd1bb6606891e71d63aff9cd5a7d23263a78768ac2ac54ece1441fd37d096cd27e916e68891137fc3cca427febd1947cfb4d7ccfad75b2ec5e809c132111eadf25a73043d68333139bd2435de9941bbc61c5c509897cfc19a21645019eaaccb6d06371e3d0570c09c7556e41a727e44d9bd672fccd1f89cc7d58761c16df8fb75fb8a1dde2caaf088f02dad91b6489114398740e6798f3ea8c7b0cfd974e160a0106d703d9589ab09aae79108e3212f19cb950ea9c0798a1532bc2a065d5900a12054395c0545b0878ac0b1d461f553dccfc2a22bf254ced88dcb538e3889549960b77ba6237ab1458e158f4f46606372e797ec9d9ecc6534acaa1218e7540eef11030bb9c3e5a7816f3b33a590d970619bdd2dc04d5c6f4ec38b7cb4d525234b836eab57f65dd045e02367eede9049e219b8712b8d6fe178080c5f77b821f1a475259ae571a5578eb3b48863162d45486f71a28ecbcedb35b320e5b6401f9e7870aa5418449bf47502626e1f42abf481b48d5a6819c640bfdb64f873d583fc4e40187940a6c3373ea7b47195270a8657898f55568985018abcea9bce1c155d95b426f91a734b2a14ec2c7ca2011a4d30019fd9b3ef63a804e9c30c3de2651c4213e90285a4ba100b31ee402e8a7f23cf9d4dba003bbf982526bc63be5af102dca34e7d362d6fbf6f56046160d7af33b364f2a86074d1c0fdd54aae89b19480efde2a9caef9de7c0f9491e1cf43a48752cef405a0ff16b0fc67bbe433a3c1b9661406c3726092efdc076febd60c436476f24dab1b0b8f8893986d951ed72282990e8b1526f4dcf539b22c01c6a7eb5577cd540a16a81296ebeeb7ddda72e60fcf2840c5b42c5cba30eaea5402f267d1d04bc80da5ef0dd2bf3c7a2be986507617c9bdbc96c6273a0c9e586a0c48c98b4552113149c6f79557fc8ace0b1a512fec3aa09ef191f95c2163113ac5cdd940f0c2120509bc53c3ea493c54703effb902ef752c830c61e85636ca95429bf16937bf6786b3eae1b277bf08dcd69f521a0078d633beb33c9aa0cb33b238e1021ca67df122a403a3698452740bdcac81d22ccfe4ab5f835d1961708d1faf6d40f115f16c6094ea37a7ff15e0534f62c19a6f4ded0967be337cdbdd2a7c58ba16ba2e4c3686e9d075c6fa7d29b2a0335ab4940d2a95c4500295f4db84ae65e46c54b7300909cc5411c725a31fd962d239aa0e2007c285586b4c778e2ac7afec42cd8409a63d7cd9c677031f43f4aaf04258dcf1270c02a4764177aa66db2d8f860eeb1fd06d0b27587537410bcb641f90aaa7bfc6f12bd143f66e7c933a0f3ce6b5048913e1b2d79eaa6c19e7255d5eabd24d5f12426339541a22d600cdfd1781a1a3894740887840aa82e5a461fc324285b0223ac9b95c3eb88160353f168b3d4ae8a2e87b7715b5fd2671f66e6eaaf9365b3d9e3acd9a749faefba6009783771177aa4dc91f72fed7a5bf6b1b7738b84ac0a07b4a5a3f0a9134a39e1e7e3e2f9a92d5644295f31c5a356092bf07c709b4c34305ebf50e857a4f593dd1cce0439d3fd125c1ede1a48f583bbbe0eec7058345129ef78868a96f8a76ba7fbfd1c5eebf75f3e0eeeb9db87474b96f321b87fffc02433513fb467fb74e2fc8feb498d51530c753e9a173e95e0edc5ba9802641a45db281b2e2d87d409057b4fb1925e834e90fa5619ae3a9237d5b104e7ac67c2bdc31001eedb4ec7064b2f72e0379bf8780f67ec4b195db014a2d130e77b1778efe3dc703f1310a566a6d3b5c9b12b1d4e25815493ed1510a516a31ced3b64ca49a783ad63ea71a57290727fa31386d2fbfe41f12d36a618c6c28d8f10405eb3e0a33e8ac2e4133ba75c688c8c9a2bb33c8fa032eaf3ea0d2c27bf89269c4aec55f8232b292e7fa9fc24527184f19187d9d8a3f52335e2feb5dc6d997b9b773a79a31db832b752e5738963ee5d61a1b426414975693f986e165e52d46cb059fdd4f48f008e96d4c1a48306b7c002fd0c861721656074cf11173ca65cbdb694c79f58a3f3365e872b24670b691682c10261eb1ffb2b65da031d070e31542f49704b77970a78bcfb4c4ca517b4c966a4e8e27664704f633e90cb7d7917dc1d3a8b8b7fcf59ea3a8a81305761923cb182cebdd59255803a14ca8a75fd007670d79a25eacda1138d67a0fd1da981529dbf182fc4d7a700ba498e4476a1d415381c9e2ffa3bd46201cf2e454c4aaedbbe3893bb4121a6de02cbecc1f319155eb8c99d1030103bb6194bee51e74fa01f28dbe16092955b9599d5c1f1c3f356e26d48fcad7c4cdf0eef25c25273dd62171785c9d2c5a01b1f3da9b4786b1b399d890e2049b73c12de2fb7177f2bc3d9c645398111ebcfd83b73119897bb994f998f4a6fae1b3d6361e171059dba0bf9de9af7a5a1b21641790baf82a36278945d649cf5d310f3792fdefe8c58986a48118fd94647b786e47733ae703701e18992bc1b143b1da6110a98030bb9895c14d7b8eae1a155a550e219a5b6301b6d26d7956ecfe4c7023eec1ff62538b3606ebc7906a1243bf8357f593b6cfff32e3fc6b51f6a0ffaecb658d526f7a5e9faa6294e4808b779f4832318cc184e49e8957b72bea0d67366e040cf76a85889fc6b04e84afab0d02947d0d83e0de19f12966fa8372f6e82ff402bd7a69195eb1a7864a3375aa9e23736fa4d4b0224647e416474c01f72b7d4af240d7f43395b5b04c8fdef1165ce1d56ee8ba0e350e6ada893e0594facbfb5f0d8829ae203929525951584c21371b86deb0f76ef5daad5e847135a6488b35ea33e3a165fea502975d6421d4567a229bf3ce94605885453610eb9c82f9ea743bee9e14776bc3076a29af268cc72d9092a492d9ff08c345dc2eb2f8003b561d9912ae1198c58107f8b37a08b35075af9863110e6770425e9d59c2dfff9d9942c8bc3bf7904c2a952bcd573706caf1ee14420564ffc433c0f5871c4bda916f2530ac75819ade49fa1de21edacbbf6b7075dba21a84989411c566b7c356b81803c7215ab0f326a6b8910dbc62c1bee3af51f105fcdebc0dbc56a50b22cf81eda563bf8c2eff98b476e8",
+ },
+ {
+ "99444e82c6c4c47070b164f298ffdf6955ee5bcb3070b9aa95ce658db4db084d2056cfe61a93568b44ba7ddcba5d450f4ba0da7b119425a6628b3416663c638692326cacc5c237097db5e537122b465dcb21d8dcb5fe831789b72deff3907685c2e23187a56990221e755930a09f8d6cc065487563cb8cec82b9dc754952fa0b342c92d99522fbb39854e338f470a4b4d5ed2a39b8b6253b7001b0b953abc588d757616c7a5d1f12b1024aa572ef5a47dc8480943aa6cfaaa78064fb2b29830280e46efa418d0cf38f57980146f2482276c9b6b16f865b1606bf1131e894336979a163ba2e70adbdc746be0d38062fafcfe5603e6bbb55717b66a263fbd5cc7476302ea4a0dc6167221f745a26a309f5886934f4258965a0ef0803eaddd05e54008df8a0695a078b797be59f1eef95a658c99a7d52001d4108212ce5f18a39f1173291808c980b0513f1a531e03ad7380372b65572d3967af4c25fe54d99d664cb67e557fff05c12e10143c13b1bfa3e8db093ff832a7978ecd85d3971349e3c9b83939b73f0ad55f1f1162d0c106b99c0ff98442911bc15e9194f5b4ded97e9702b84e31b31380c224f392e5fa5c720a45f64cd7020e25a3931b5871e4c708e77f4729225aa9f48f9d876597d3e79219dddee0efdd16836021dbd21692dafe121217347cc128fc5eb051e6843978ae17478ef714957a84c74656ddd931cbeb43e32fb0a448acf2f90ee98d38522b4fa9aa36be4fa13306e799d4c0cb90ac0f73cbc018146d1b0d6bf48aa446a5e3e0502aae9fcbd196b36b6b7426fc10367febf687f05392fdcf878863de2e47be7e625d0e3e3e94e199f055c0fc65f76c41ede43231873ff10eb854dcd6ac9b550ee8533d16f81eb0e86471d4da69311c47255e78ac8e79ab36ce880d6b135279fbb5a712adc5c3862a356af49e9c10d5b16f4e5dedb80914868111e194745b802a0292c7c8564de28ba8e71a44f7eff6573e5434e65d496cde5b5e62cfa9e2e9ac85a164dbff5767983e71dd2661d37d9027a27674ebe3433731a606db88e0880e91ecea8134421962b3f68915c9f6a5e1992c56750f99bc313fb30cb89384c72571a1a6a5e3c01897b691bd70985352217fa8a67f3252a06205bd1a9931d1cea3736559572561fedbf3ac4c8bff9ebd7f3753ee69a69ecbac4be6357db7f4213b697a828edc716ac01da75c1d46098c7d5d6ae6f3f9a2903588c5b340c9d47c234efea21b700cdb8db4279afa2117677e824e627bf0f2b179c864ba823926a57825478395545f130886bdf2a7c55a2647a888c3998b750343d9cdc602e46b7b09a2fe9ef74db1ffc46fe27c254c927ce51b307e96a571da7f3f907223fbed2daedbcc96197e95edde7859f3b4ec6099f791089e368a68a5ba0917ddf4f50b93c0c839ea36cfc8053811f8fcfe6986e5fa9f743119ecd6c3e5fea1dae3ad7eb465a89e9c68569190688a8d56e4143ceea3b11fbd9de67173d5134ec8b0bd7d16560ba2be52345ebacedc01a2e03e8183ef91317d87b2e15cc6301586ed829d438e4ff1d074408b332c8ce60ccb6790ab08c228807509dd4b39f2c227755f6b039f5cd413ad6f46c9ec2cc6a79457529d297b1d9e74ead9bedd9bd652fb31568a8e2a9e2b89e4e57601bc1d960360232cdb30cb502b950ef930d54c2c0692a684cd44b0472995bd2b41dac1553ae47216253d6640d2653a033a862f3118c5b5d60a662d240bda5f4da51092eff514f61a425c5b14b19517ec1b371d240cc30a0739273b34f18a72a69b1586802a7caa6cc8f5817a8a995695d063c9dd26c3d45feb0f84dc8a0773151cf9a537664f942f351599cfbee0558f441f5c7ad320cabe305f9aba570ddf6407749b6db42f9ce94526a8f4170e735b1dcfc5f0e090af10e039db3747aa9b4f1f26acc34639ac8b60557f7753e2c261a29852932901a4093b7f307319cbb228e26eec289898b3f8ee236032163293b8caf64be3f7ffed236f1da688d958a1bbb79dd45026884904bbb936c1ebca7aa6b0c68aa8b667dc1575729e4ecb4ffa82ddced2f4571bf902c52fc4a0ea3f47aaf5c243ac2a1fc19f825fde5d9fc8d06d97a351eebf4ae1846aa62554d57cffdb3f3377695338f8d598d723289ff3962796e8065632e7da9d8dffe2636cd23eac15a60568eefe3e77c561906555268cfc1e9342417b1cdf090cc16c79939b15a9311b0210094087dea22833f74eb0e35d44259ecf327dc84f3f24b8c2bfce7be0d97e00d2be88a150a0d557ff963b4cda60eb99935951d288768b4b2649b717133517f5e3909744417c9c3102c77ddd285976cba2c89e2b4f297665632d7c8652847c4625038a6670169772de0550066ec6c2018f503cce79a333ecc0a0632334df6959d2e3b052fa47c5c84d15ceabdc80bd6be0ea2a5a8d5e374e0e9a613369ca8d4cae3d9f98755560b27b2f6e47b01ba390f5ddeb732c22b12abd225e26ecdb639b08f3237e488430b3b39f0b63aaaef4907cd003a8f2b4c3bfd721d6c3fd3a5f062d72746606a529ba34251ddec4026f40d262e9d527ad84fecf5bb2cc8601c2a38437098aec2335104842ff1c455e5d17c136ece8d461d7a3bd9a60339c22d71059e09b3603c0565c0345684893b56054ec4d3db0bf15546cafb4a03bd7775c3157e7676bb7bdb7baf3100396c563eba1a12952503eb6ccde6b6d0a42d456743c4ddb97f5994fa08c5fa41315080eb6b928090956bfc6252b232f6e0785d233c3adcbb9370b59c35b0dd66005d516befd1fc843df8e68fab19858b91e2aecd1c8a88b0fa3d4c2fed2995ee87e65976b755fbf44ee183f9fa08848bea325807bce0b7b61e03e50b2c7af9b360532a17a8250cf6068fef0198738c82a5e58961c54017e343fcef7076e823d63b4deee472fada7989ca7a213d06a4e3eb2d44b16e5c94b1588321cf6c45a5a792938b058d667e1730f8386dfedc50ea0a959b78f12f2949b34b181f90bec622515227dfb8a5f6e89d2e559c0ba686153b218d2c50b67503018e22914ce9b49d3bdb7cf38172db1ea130baacd640c111614e3db204b3b50641d8978dc14b2afc27a7efa819cac6bafa8166d1c127e2237520d57ad38a80146217a12363cb1f8a720e328cd8f846d379ada43bd4865e4aa633c479bd448d205b2e43befa63486c717af84a733f1dececc127c047850aeeb8ce677612f5966e23d92c1d3c758aaeef82f862c1154fadd6766e1dfc780bb447732a5968c0c78b9af4a9d669338458b57cbb77910a24678092857c0b903152035bab6b1c73f7b667a08cd0d31128888de3ff1fed24866eb60beac19c1b139f77bf0b9332024999a2d56975e691fd7475fd93622119d0d725bb99c1d6ac604d6b6be09d6d29360fff9f84e5318259a67fec08a006d9772b9410ec6abd4cb828b898c625c2fc35c19cb9a6cd3b0073baec7b5af254d21de8e209539f560bc80ea38e33658a68262622cdf35dcd6618b9e272ac3644c91f27d372c6297d8e37201c6a86a7d3accdf579c15246276a0009ddac4021755f4848d10f714e9da86eba13f461e6a12edb1aef2d6117986120750d609682bfdfcb90ee3cde8be54d45f841a6dee2d5b9fdc4e65edb7ebffcf3cc5c8a4e1c6919ac57568be23bd8283319ce11fca3caf968b057432f163f22e29cac30b8154a646ca0ef4fdbc7770ee1451fdde9e9d651992d94c843d4eb2570975528ad9f8c193f7c681a43df28242547010e30d75fca04f39247c77d6c3715c25fc261ecdba16844bbab23e4d0482bd1565ca9b526ada9b8f5703661a84b23070d85f3e8265b2ce10750c5d798f1a8ef4d51a473ff4d2bf4be615566ac796db9fe61a224bcce05c31ecb9ab7bc43a609944a7c9398a7875609ddbcb556296f548a117847df7d0afe48a5b504e85b0d7ca589103d3197933a744fefca795e1e036f964a4f14554d5cfa0261e25d6e5e02f86e402906d3637a2352459cb1639f20faea6f0e3fbc6a39becb1b1b3a791e32e85e5bee31be685410adf0c11190e20b7a5119b90e83f2cc4f0de8898606bb6e64165c95d4c5eae472daa6836a888ee4d9a79de72b8fb47a9c9c0323a2be9106d4ee9ba8b3858c256032a9caba37af94df4c7b0adc2f8478cb879b6d452d73191b0fc1ce944df3f4809cbf3ad46eceb3ba4abd9679410f45c8aab20dd72626f235e7c0c934b4beb4507def24ebbdd7a507943c81d54bc69df578aacd9ed0bfd3b7809dec345ba084d88fa9c34d80685415a4d5eaef9b88e51432b2b2037186baf123a6257e47aa56d6531923d38178e8264dd315e95bfafd8dacaf901e354b0f58f135d638df2c0f32453205c7aaeeedf8c102e11cfddea9a98d3ac7c385d71b760cf2afeb1ebe1d64f0222b9b101893d11a74ed175297c1dfd188a2565fbecc6bb07b56ce3973322a965dc5a675587890cc65a71efc68fdcdf1a023505ef0bc0e6b12dca5860fcf1c6c94c2e2ec3a72b8a019d69c82d36a73738dc3d17d7fdfe992bc8e18cb5d3437f1f619dd318b95d1a56b6d273ed79ab2655d83e2dd63cb6f1f5987eab6bb21a7b13b84e2c619b36b842192c3f82c755d8af840675b0bd67a655d641b1886c3c9c147ac87615ff3e58085a879b21dd63c1616a3712279ec87d650a2eed665b797ad631f0ec312f343979cbc49b99385cfa92841cba12d52777df565545a1deb07800a15431c0987b4a543fd5ed6832e80ab6f4b4d9c9ec419932a6ded4759f5c7630a0b80139234b8d53117acb4452c60b477ad50157169a89bd796e2308baa9395b513a94747611c7978c82dbdf48d716c3ac181ac2b2a4702c02a324bd4c5e089d989d020ebec9963b5c721a95492158f54973b7fc1828181acb3cc8078ac095136d97221c60b847bd2a52427383ab68cd1f10b92738c13203fdfa0b78baa09c1837be2498667c459",
+ "0ce980442336d0f427db869a6799baa6785b5e030567c588e2a7d2680e96c11b7f415fa27730969e0b1c3973b5f3192d4e773153def6dcc09dae29ac44eac7c42c2666a356fd4262197bd5cf6eeefcbd662d104423ec05c19a2e6ddf1834a3445a09e8b1062a1320a5e8ef13d6ebd03c19e1813ccd86fd68b46a",
+ "1ac8a509db7bf4acb80d8d394a5abf47c273b2093f50f35049e749f3e16cb0fb",
+ "47cc9eea11f9f3f9aafa23bd",
+ "088888333340b3a057b05491fb2402301c8654948aa6d5ee1ec75eb045858c22056fef0873d6675f897126052923a47a30675b266ffb6181cbd29ce2da3720e36a227e4c6e53328d789913c0d9cd149a6e49293996b1be7d6c513b24d876445a950e723ade3efc36907c840b9b8cfdb1503811b4044d931a0009b381fd60a5bf1e73d16348cb57eea672709875fb9d56908dbc729d5d7d322a17a41d0f62c9af9a013ab1e19fb7b6c6e7fa0c0b18bec5e3d3e92546c77e3753193389e5fcdb6a6a1896cba461343e71ef7a156b136b27ae6f45be9368301cfade203e9b53824d70f07de9abfea1968b8ff8489b9804422ba05ac3c3adf23ba0848817fa51febab5e9b5500100310479e710b663f064c1ef101c9a5320367cd8bc6e52081a32f070e7d3fd6f4210cdffdb9fcab1de4af5b06a7c6d191dcc12b25b3053e58952bfd1f723afbf570796946c1df9579ad14ea9c8c30389c1de4d1e845c764fec5eb8faaf4c558c5eb5113018c6a21ef653ac7d7f5b6c7e1a8fd48c6f423e9913436202da176a86731287db7331db055508acc94168888040ee37b3c119c8a0d88360241d68745825fe480324a944d56e7cd0375d4d33a5fe7a3863c2aaa899b2d24f65b70bd804039116fe959c32442c9f0b5470463523eb4336985b71125fe5235cbca0c88a6f92416d038e144de5ff8ef6ca749a9e239f02db505bff8e16fad1cba8b1500445f067a674142b6413e9dc0f432242d8301879bfc11fa86d1ac9992ab12319fea8b703e10a13bfd4b017496222be26b56af3ef67610f904f0ca8a3e7cc249ca8122735a542b289f13922904ff23dd197f8883c7ac77150d7331316ef94e0cf13b6ad95070420513599100b0a6d117640b781c622ed7ef7ead29476b3c835bd9dbda2203930bcee7ac01c3b9c89da405ee436ee652ddcc3e96c7f1a94e200eec9a4a226f3cf7ae5725068916e73b61149497d11dd85157f895669f51978d1bea8fd2afabb18d082365daba2682ef623109988b7d0e27ae57bc14d86603f93b5ac040ae52d8db404ee27e6c34cd4246f40eccf9d3f8637a4615a4006918b01d34709bcbebd02ea72958d54db3e87d69e6d783de2f1841029d6975eb11f9b076c247108797d5368c656f888092b82aa81aa26e164e038b359bd68801c22fc107e4083a9d85fc254b002ece9d4545310b0cb22ec1af04a7ee31d210ede4b605dbdbcb70e4301989422ef46edf63f9c96de9cb3f70638b51df5c0abe79b7af8cd97148f2b7bf394bea0f7bbbf6925f83b901b87a6079f2c3b38a98fe1a86dc7f48bf97553701834f557451df4b41e7db984a34432823585380b45c1b84813d6aa21107cae252923fb4673cf660a541e65610ac0127d238285f53bf329b62169f3e42d5efe268dea62578e97da59a58a1314a1bd46cf7a7cae772814130b51411082e30062fdbda1c9e14d6b2bfff89d0379d32461f3b8e833b105f6a89532ae748b5fb43f283fc86450404e8befb8442b65e338aa0408303a70e9c27a1d923d9f2a06e7c6159c50bf2e3ba5b035420ecbd9d0b5fae478eb1ab72fa714f99d00188bb10e60380fa3a3a318c2d359ea3805c2fa0dde17ee52a504f70d6b466bd38d1dd4196be336a9ab4a9e573d1bc6404018a119f688c1dc2a8ed1433e8a8ebf455ce3808c245f0220f0c12d28c771757763bd111ab829294e2429a6f7a59858dfa1fe0b806e986d40aaff934589fefd75ab91097a979f26bc9352267efb2d82c4738e4e6c451b0d5adc398f546c646b9e6b8fc84e91651a1252d5b805a857c7798d102d1e6f90749252bc53588348ecec0897c79f514442fe3b27608c95d0cba999a7e0fbd7f601689b4dc63ecb9ff553ff12eca3e9b26e3eccbde28770bb6aff7c864ad6be77fc09f81f90df6efd0c4025d0916ab5197ab846dfe6121c462761d9cc87112ebbca197b0a222fd34a15b824b7eda06a56a6ffda760fae5f0b527e2798f01e205a3f47947a4bd190f6abfb1dab2e3a53131af95d593bb57e4f4af506440cf20636d9fccc449d9565bf43dec8b6877337ca5a43900c1dc600c877b290342914e909aad8c5f0755bc25652781535c057ed5ab2ff8ad4322a8edf3fc1b5311dae6361a7395919725f4cd87ce0ccba37c64eb3618f9c5a53644ada569b90cd07184fc048f1b589eb29852909e75e7116ef96a268ea85c2bd257cefdde9222d7eda875a2a3abcd3a02a1fb470ba967b20beb54914b8b0c6ed464ba978088d7f8b30d098966b0bde82a8f1210f5d0c3405c9bc73f703134d0b6ee13326f65fa0b8154f4e30808997d4afbd060285942ca1dededc3410a099881492b5730ab7bdc2a4cfd0068f67766d60b5d4945f121459d2083334ac878d067bef644b9ee427bbbd6c9351d7b019bfc051c05ac301ff3792a1c687546dbf6a07a0cf56717374bfa1191c22b7753f6ae02392f8aac9207d1ad0fcd57c5c8b35817574b7dd90a00cab75f508f8a234eabce6618305f94746cb6a8573389d336bb67e1b0d2b6e9bd3959ef344e1eb245b522c35222813b8c6e82df48987436b5592025e9786ca63b6d1a064223bfacf59ada713c2a3116611393aa8446ea79b3cb21e96d13b659ada2d6524686fd46ec66c1b4d8f5ae7831840c9e3db64d528f83a1cef1e0a586a783f8306cb261ed9c2905493e74d35883fcb39cfc5745c282104cc3ce804999231d13e1bc6f2c022f05999fb57575bbdaf00d7a990e17dd2f8b9dfe66a637b42f58ee49ba60f2dd9718d09d7025b6061b2087bc35f0a8c884f5b67a5e18c2b4e857d3b48b79dc7cab6b72f572d22987566238a7153ed6264578424f1ce091fd05b7f14563fe12c76104d3373367af3ed3aca694a21127b5912c0b7eb1ddf9d4a9f03f660d49f7a7f0fb42797fd112414c3eba2b75a04282dcb9645191fd3dbe376e7f60ab40bb7ca1e991053a1912854a68d7dcf854201d1f2c26c6cfaea32e29d80847e6288274713d2ca973b91dab97884326b280c6f06c65b8fd25d314be29139961051a1d8699467d02b67991baabc9b05629660c243ca3b0477362d5e6bf9eaa33beeb52cf399846c77fcae11a89cbfdb2058e443ddd44fe202a3ba5c2efce937d78b9639781b8b2b99077b433189cf3b0733ed73b59bb194c9a98c5aa0cba6e71d1c5522f193defb9e31fd2cd60f22bedaf7008c2fb0b55a8dd52731dfa2bc69b40f835ae95db040cda6a4a1588a5ba4769edfeb7369c1e9a3b1cda293255b4942881d94d771b7b82460004875e71be64c582f2830c5e80dd6de421a311c5852f4912bea1451b0328d01c7029867cf9af99284cdfc1e1f0aa0d8c19ba9bc035dc270b45724247137da5d3fc4daa09e7014fe1439889968eb23fe124f067825d5f7b304f17a983580e009e0e51630ea0006dbc74a30b512cd9eb4d0b315a0ffdbfb581609ea9661b0007cd234ce43c17c92269a7519bfe99c2ca94b5cd3e7654946e67b37d4270a369266db6804336a446022677a024d44cc02cb04108292dc12f790578a0d61cb6fada738902eed3afdf1850bafcb279f18b5798d7466752c6368a594533baff5dbd17974638ecc41753b184845206c79bbab84dfef148eb7f1390f8cb7346a14c88caf540c241cad11ce8869be3bec85d029ef490fc5edacf94fa962be39a33c8efefcbb6b43960d5bc35f8fb72038af3801466aed141b50e9ac7dcf1921f7a6abaf320ff02ac34bbfac265e05e27495e6e027e673a48a874e6f0c33827a050fa21c2efa789c1e3df2ecda95fc52ca7be35dbf17ff6c73f37cb236e5131542e002913d177ffb21ac450e2542e24b894650007c36c52d90f83731009a7c3239ccf11829cf0fb6510d9924e927f14d6a06f8dc772fc9b028a8bbd2d3388985f3e2609abbd08434c46642b97240c9380a831bbafdc5db77be63a1400cc9a4f7362a689b07a77162022c6ba7a1bb9f0446a0b6b460ebdd9111132694fa5f1b29da39be66c5179849ae9720b2da0a012d4bdfd1b18b8fbef0d5c32b92c351dcf2c599f069c3b53f622fc8e904f27584b2d97d43f779abcde6dc1413c0a677dd187b28cfbcf7fa6316f0967b53977432d45944ce8ebd2e265c0bf6b2870c75ae808fed52aa35421ef55667ecd6f9d279c9b91c9314bd9411bce267d6ad52b1d910b3e65147c3eb6021a0af98707408e66bb11ca5abf5e34b2bc85b144fd06ea56f5d7f8939fe0cfa4862e7f306de069cf85f4aa7aa97c6848594f5a6dbcc718d2af77497f4b9d5ffa217fc301127071e9bc9c2c9222ba90e286506e384f321e622f05d81c114953d0f7e9626b74f4a6bea8cfb86ceb4575e5cf4fb84e9efac8291d1f4153ad3cd9a34ce0ffcfbe30b6829c0f986a4f85d63b602ab99ff3934b1e0c46e55d56eb479b79ca0729beb59aed783e9a3ccd55db8d884733dbd93f9fd7a7209fb92fcc49826b2d4356ca676f01b0981637897b3d2f90f37bfd73b214a398a8e4e2f9e5abec01d8192ca690191255dd8304a2d95a69331288bce00385f462e942f4d694dc3560a263c8ac2b5cd1d2c63b90ec67c32eaf5bd947bd8ac730da9c09ebc6888b0b4f3bead157aa9d31c2802df8ff0e4d69b7abfed6f184bf35a16ffb5677ddfc4682322128932d57fe4c32f21e190e1147d8e673ae407b1dbbca31331310b299e9f3db08ebfd2dad3158562c2e47addcbcc831cef0194ac8ba9778d0103c2955c886d439967bf788eae688f2a7459b0ef3bd16808e8d768b8962a24588d918ceb2cd1cd611b504019f65216beca212f44600cb7fac77216b7645c49f18064a3acdc01399315084dc9ea151ee28534fb31628d190bc540ac6b6aba572ba51aee89544015e6fbca2b3c2330f2ac1f68849e99e1a1f7f523599eaee22720392ea52259e26f1101614d4edae481b3783af4e99082d75dcca549049290731bbadd1ec0a93789ad5c9afe8bae44e35b3e59e562362964",
+ },
+ {
+ "0410d1f8bc890649c250a3819766f4496f339a6384e34acdd72b3a87266edd2a7eae223a372883f978277a108d6e59fca1f35f25d7a9f3aed42d35fa9b12241ac04754f76fd8f0e8ff6af88cd851887a45e89f1c9192ca66bfff605b128575d2ccc9ca3ba1ba23a0251b2cfd6db577b29d17ce2ea998946997f5c4a97a397c46024681a400a54425c071232d269adfc3b1adf15b4586c4dd7b8886f5c1023bc348bc674961ac6e221d914f432c2f06dddcf738227dfcfff88485ed45882809d0e57019461c88683919b87c45e78223c37a5be5f758e4f0dc6add22f2062bc2eb9bdc31b8649af17d526ec339f0e6fc6a41e26299c65276302f982235c3e5205ec1521625ec08a23e766577664b73d18d5533261c859c4cb4346feaf7540a56155c6c3a4874dc86ea42fd518d71221ac65541e2dadd2f8e129e7809f2835f07dfcc4128401dae2b5fac7ced1d9e07e3f348c6cd26f55b3893d4418557a18c366dcd5eadea0dd84ab95437d6f23eb9e5877fb2ad740ee507e2268c39c7186f34e5cee2d0dbba1a940f516a018f23e716a399c317a7a81f89cfabc296c432cba900ad79db67936f76e4d97874fc5f8a9ff84eb7a0f6d629c581ec5c451e27ef1ed468f93bfc68b2e0412a543d89dfdd812d9421236a4be9eb374531556c207340886c7b84d42d651557b952e0982f62c5c383e92dced21905174a5a836acdc3f2393e770d6cdc22c39575a42ea406f36889dc9558aeae5dc5f8b84862850b55bf4accccb6a8ef793d641d6b08235f70ad3b0605eab462afad1af80fa003645f4d302b03d81a7d167e9a8187bee0f76b1cfd7006b2d2b55fedad6e8db1d3ecfe031702dc327ff2b0197337d7542f42702cb276de852b3d72d9acff8a7feb8882028a5e340950e523c41cfa184b3d8878effe56742994e60240e58cbfd01541d39fa007a9f0ecccb409c6cc540354ccf35223677cb74e7ef7330bb60420f7d7bf97de6888cb343cd4fb0928fe5df5f1b018592ccfa7aac6dab57cded573b5950b94fd935f32cf332dd85b2b36501de6687612371dbcfdf77279d647ed8bdcf81fda8b7e0c5ab139330d64695d814fc6f761fd141dfb0c8f74e2d7616db3598d8de40b993fbdd272ca37db27b82aedb08bebc4a8e6d0385ab20fbc20c215ad50fab8e93975bcab3ff38667abb0545b3b3f20e325f01b80a32a3cc3ed51703d4b2826849ee22fddd5b544816599dca0d8fc84feed9f7e90caba53b70bc3f457eb1adb89fd0b67d2c0ab53264430c61d2c4a1b19ea99a9b453fc6b5ebf5fb5ab799134769c9b495c479c828bcc49a8f993c3127d5cbc31afb89c0e78fbc323755457ebf0f3344d3ad1cfc59d186e96ac31a9298e655b3d1df74b95f30fb868631053540388a13d597002f689708d35a2365e309bb96db8b1b94ea4c8060c2b165f7f19e72056409159371ac9c44f6bfaad9b9567094d18c29bbc8aa2c8b5b82735d20f55284fe68186004b4a4fb644fd52d9645b277c1dc238a764005c1d2791ef36e71786cd990ccee4571d9a9b1aec757e479cfa645e320bc33268e05af9cf90e0e616ae7f237c637a99fe15b4ea8a3232262d96855fa248920a28ec03f77ce4dd93925db60ec030a7be455ba9d08edbf6bb717b1a13c3ac1deb9821e21505c0a8971d5ea5dd8e4c9cd3a845a336209af191150ba5d9b8c2c450e3a765e8670d7f846b2461f971fdcd1942704f620a40f4204b99f9035bbd543f64b927cbc7a74f32cbb12c3caef955f169a45374e4479430e08d333c4a877baf41a27a0849ca3a157b6651295fa71ac94b6e3d30b5d160965e93d2a81b4d575cefd264399c9e4e17059f4064465b2d92c96ac27e3b221499b5e642d033992c236b905c072faa1e34495f9890bac6228330e4016c061605bbfc478c30e1b8534c49af54785972aca2d144328b0a540e3b3810a73e26acfa22f48652d53ea521875475ffade8ab50b9f08245fad753350f63dc4e898948ac7dcefe520ca47394f8e993a6d13ff68a2f78cf294f235f5f863bad10c4f5bc41c3ba93cf5e076357f0f7fdc136f34b656b1b8ebb3eed1ac429c7d4edbc902f7f4bc24ea9c9b200b9a9fd7adff0c6445ce1d2171fc031e3e9f8b8d6b448053393c8813d91333d4bdc3bc5bb2b8bff876cd29e8b92cf6f7bc727517b6f57ae031f3040b0637dfb40b8c1fbe44cfb6bb9cd0a445fd9b3daa1da2b1c4a82cb4da1fb8d525e0a4d9ec30e9aa75b951214621c58c1f60c9b97e6c6b330497e7dea790a3cd8158a76d898107ff3a5910707ae60c8a46c633b522aee83736d005de60b9abe202435f8bc4577b0eb08b7f2b617bb5a831e95d6488459bbf15919d764b39684d7cb7c9310f343fbfcfbeeb212a90d96c7a26c1026c5cb171ee4ef839785076e5084026077455c73404a2653f333e9bad555cafc1a9613387a02bb1287c380d7478238bec8943208de585bd18b448b6099565cb3ec70ec6672a778fa6af9d1b17b0970439da24c7bfaa74c85ecd8e5852e42391ab2258024ccf91e37f2f0e86df958b197fafd12f4a45f7990375f1665a14f7f5374ff7740f89677ea8660587fb80916b30629a7aa88213bbf80512421a0a37414a2eb549b81cc85072cdd87e4e69d97ecc63f974e60d20de0233101c3d475d777602b12e2f797e9237570085b0e9f48d4dedf233eb1301ed4621f9736946eadf599bfd79157c0b4cc31bc273f5c6f133a4e3679ff6797d3c9b76aff4bd8ad40726c1703c3d8b78f0974b748d0265b0a75928374f91b48c2d2b2c11d8b6e5efddb75009e4db72e562be59efb0bfa06808c89f585a43d4776ef08947a77f277526777f0b52f1e0b5a03aa560fa45c8f30e584b58ac1fc00b104942b7b86a3cdee1abea349dcaea4e058faeffc567e2c3b03e1c5c4ddc675e25aa15de1442bcf5ee972a8c5204ca5794694759c13a2d716839dda61635043bdf1a09e35cb6d93b4df3b7a00871f79cdb4ee69c79041dd14deb7754107b8fef8589d2d240ac1d8eafc52ea847263512651bbede2fccaf6da816b1b892319817bb6af9fc17078ab6cca95f03cf8426249fd4f2bf91921d39b8cee24af07a52bbe54ca7fc4422a310dbf2149b763ac0060fb2c59154d2cb0da1ad4892279b4e0ce7f5f92c189c3ce48e518ff48c4ffa9bf2b02d4792f84534958dc6bd2914ba010aa32d133f6a07bdbb87a237c7acc3ba5cf101efe947147ed4eb3bfdffe5fefa991c0dc8760586218d286944c52d0f221e0101f74826761d01a20af187f9ec1115e9e98bff6fbd7c8816c15d33c07f51c171490997bf269951218ae92b66fa3150d3bd40336abccb717e18b53e8806fff94009910f202a5041b5396d1c339e6d075bad4ab66a0637d81eed1696e4068024001123204b8371f0bcdf0ce07d79f7c917327f7138a75947846fde68665e9c767fbf96bb3308abffe7a8d05512c81e39fa8dab2334f46ab9543921ca97be31076dc7b2a0d05e90b7f7610d1a391b442398ef56cde3b18737faa8f282572389b4fb3c55cb8ae6737257708c808bc0a414bffae293bc69cba702ce2959e1a30edcdf64985a4b0bcc927c5912f819c71cc9b1ff5d6e5929055be72ea5c8c1a4a591093deb5449b7e6b60109be1ac0cae472ba31e1035ae65f3214f50ad699a077a2de52f7180addde0bd78c2698470b1af13cfbf497d243c9e738c4cdc265356543885c5b933a299f01a5b5a9ecb0b4ddfda0c28573064f6a3f142801795d66bcd5c31868fd3207fee7bd98c47e4da26bee64e1617b20cbaa34e3abbe31126b06d5737fc2b577b19d255a519397f3ff8668d0e7d401a37e368729e4b83c5fbf01c32ec478967605cbc0675f685b5eeeb42fc688216a0667e1204c995c9c485e6f7712d80d88edc9594528b1907790549756dcc8b0d32091f36d2b4009639e68daa130e83a1ea18353ca34f431c548d91c1591ccf8b25eec1f7a3c18ddca71b87bb290a5c13229250c5e193e1352072f6798ec504b3b4c6aa578737332f52baea7bc4468fe6d8dfabb9728cee93fee50c8caa113f5ed7e9b55e21e98d73a377ef68be7e4e965dfa50cf863e6285236f11ce80512c573ae2b55bcb43cf6ebabed6783c250f991f5f68a59dcb2ac13a3c8fba8dbb11c79dc6236809f2d7c4b0ad3cecd24b85f1aaed9748b8c109f2fd98ac8a53bd52f18475598d67305117de8e03b0d988a2847539cc2efad520f86dcd82c08ad4b10e490b9cb03bedc7197bcaca55526cd9c8a5a5f69f7a1697e7e31aa76eee597c386418e89f06b0b9817a83d6cdefaf9594548b33cea1cbb585e55df3d3b66f0b1a88f4b98ea4720f1ef5e6ebe4958078ea0bacb8ad776e325ccb252f81943b9b1c2f54aad3c7baf1bca0dda1355d191f69c5d8163c464898116dc89201032d1e3281c8054882f60522d3a65831bf779a854fb0c195f85aa66522386625658457e74d5c2fcf5234f226da4a579ac1f11f11a1e0a6993a4dfe5c856481ebe9d8d2363401058736f7ad104104aa03f5c91496aaba2fe4072d418d91c2787a9b4ab0cf4bb65681ad0392ef073cf2fc060692b0c0c194c8eed5558098cdfa3317ab02626159e40e5c76fd64b2ef60b8f5f368b6b4fd7ea3d2d3236aa01d9db7c8a01929f9fd38557335b926251ade1a0d47d0c1444e6416218781c1a51e786dbe9297b78fcf0d0304c62929e00744ed4e14af926313a9849b2a464048bead075044bee013cbe318920c4172138560629a0ff4fd229d81bdc7c7fd1086ab17d6efd5b603a1991b33a55ca5b9e2051b7c140f7937adfaf474c2f284489d9b1e8c71d58f126eaa451407eacde9f0e86504f7de3ba4d830199a229de2bf39014baad6dbbc448501588ceb2575db0ddae005b81ba9914bc22b6d600e2c990f7843e553ff29d8008265eba7dac7b5b5a7ba6dc263fe0e262a7b8638a81f4720622c7361554b61d7b04c7f8b133440baeead7d51ac8b77d606fd0eae1c55ce7e8141dfd68d40ae3d8d2dc8a061085b4fb6d8a06263183869154618329be6b01c2890f2b5d0a0f25dcdbbfe2ec3597d79311edb943613fd4b59157df4fc2e1024be03d98ea3cbec7186ea9f4a431dc3743b9f0871b205bc0c1b3a001768",
+ "113b261414b4b7dfa028668ac8b0cde5734120124991c54f4dd16a87d181efe2bc15f6d0caaeaf6ad615f59ec5c2833904a34b4d34109c82e10609b387f995430e8c13d83ac34310d838af9efa32d7fed6224c0a33",
+ "cd762390b93369f1e207eb15deeaeb0036f5331e82480d180f84a76c3e44550b",
+ "e88c14ef96c7768f5dba9de9",
+ "8d6aaa27892a76fb05a2e96cef9a9b4b7ae0670a12cff95f7b076372456889fbd3b9b4fb5fd98b3bd85b247f15009be2f4e7a0329dd118b6872199b314e159618ede0381dd97db28743461ace1a694c0383d8458150a501d6c45f4b50d5b1bd47e61a51f9ed4929bf2e564f201ed0e6825170027d93e482c1ce268459d2f81cab41f0e7ff281430c16b34a29b5c76630dba72ab9e751bae41122b26121d91f2af271a23e818263f46e05fdd52f319d58330bcabf66637a368c0a8aeeb20cad1916d966e5e0b0de74cc67ebe57e3d1fe01e9743d42a931cb4b98bb762ea43ab937d1e5c42eb08fd56e70e911bdcc1ca4ca0604a329c5364b262ce2de282b4732ea657b89300cc7b7127ba4a2d08c13f581f024fd093ac09c2bc245be60c80e102405597fa8082f4d28cc954a93217edffaba3d2a397bb59ee89c8cc0f33eded78f21183bd1acdce64a923dd609a0620d2911f61e81fb2c8ccad8ad9d81157223253a121ea2bc60d6a3670c563fe06bd75688572b3be83cd31dfeac6b17cf8455267b481219c42034b2252977f32b8e6588fb05166498fa37d17c2b002a655b5711bbc21175348225fdcca041b1f97fae48fb1e222c5bb46b5202191c00666b7e1b2d84aca3edbee7a97dc0f6d1330e929226f8a76c155e973c1ab62c867e1f87be37788754e51825ba31af9f4722b5782ef782fbb70c391a664f252d14e49a805e94790135ff6bd881a687f98b42da96fd34bf240eae4914488af739ec15f13f048a7eb5fa94af14e8b6ac5fae714cbef6268b114813ca2a3920a7a9d5eb506a2ca211758de292047eefdb5a97e18530dcd8410495fc42abed91b1204d9b8ba9d6aed11d2d0fa0d931d46f93f2c1a560ef9f5f7cee1497be770d3cb07c534215cec12c1458bb57aab4d95cf4a15a5e3a3bf8e650206d5cac4af3193d169f1a57638d9a50f6b7c6985d42f7138b9226451670d7359351c2affbca65680557693d03458341198b8e13d0ea6abb7496edea3cd4dee2eb93695e668c7c0901c6809b8ef434e88b85a8b22cab6508b9560fae62900056b7c5c29a8c899bed45a2b5159a1d4929476ef350101317f77f02d48a039cf4cf01c56319cbba16fe908c49ed6f3face88867c0ad3703452baa7b86fe58a00ab8f740b4e8055164b0385dd3fa44502ffbb99cdd843bc3287ea468aafe4cc298a3fc180f284dbf78aa09e0a2f7d8593356eab016ad8dc505420edd376b66598a3d0aaa848fd68c4e07419b8b50e40febe2b6b17ad07726fae1f87e86abd01490a0ce24fb57b533c765504ee0a9ca154187bcf5e6828e3addc7597532643cfd992558d63b1acd00e7aa41b9765094217480c08c43f4f0b3f0127120699b7f2a5ac07c655b6143e467777cdad4bc21d4b57da4d8f9b9a7e4523d8c6fba3614b7f7281e80ff0f9004577adcff1b79fe443c80ca9655ecc102d5df6aab2ff6c3401f344b77666c59ac7d5b92bf4f1e2322f74b75e6ef2bf43ad9e018f164ae76a91451e5221bdf5b65a4fbbaa8dc31e6063b451edbbf4965307f8e65bfae87b15f2453083bea8484017228a9cdc6edab1a28834eed8ce07430f776b916b3bdd2340798955ce9ffcf114c3f6a88bcc4c7b6f2e3842426488c340d00f2c4d2d6fd3b6263dcf7a57f5cea6c77efba7013297bd3320accf033acc0833aaa8e8f95cecba469704214f54a1ed581349878a591f9993371f1daf92e55b2a4faf8f952cf785c687a59b3c258daef1b6d7bf9f904123c7384a859933c3ac31e33edf648a1be4d6264ffade860915bd118f0b9aaec2eb8e16b2015fc25e68caac77a3accea53b9b178f6cf48d15029fac12963b4277df037b7a494cb29b1d9e6d2148531a1f7360519cba5657c080254f130a1cc3ccaadb4298d7ea0223897e63d798b4f4909577cf9b491a82de0275a246bb1211bc4144574c8ef176b382262c0e087975cbef33cc616d32e0131a9efdbe8ad3d9cb5f935d3f4f409852acca22ae2a6e7450e9a426ec3b9183f93b4b7f89d850e1c7053c661936e0cde23e831a261b319b430da45772f0fc0113679d06f025983bbf37ecfba35eeca28de5ff4815a490570491266e92faaf8d0ad4ac8df106faff8fe3c8d050ae9dfc03a01ad177c21d7b653509a80369a668a97eaa532dc9867c32aebaf89ed36586e1ebbe1045347766a354a86ec1e8b2f30c8fdfbb6c5d549e7a84db81b73fb828499c5c4be0d4b2b7ffb197133a0ee18abb5a4e371be0ec0a6535507029316f8decde30833ca47493ffcab781d028edfb91c138609baf1054ad52a5d8ccb98b3ca5b138f253d99bd556afd80f71b39f36e0d96fba4e0cbdb18926894968aa825392f12d98b6497ff85a0e4a91c97f37ba1dcad30fe688b54008b925805104a61dc22b712685202ecdb073fad9b10b5b9ee2ff781f23fd41ecdec87f85b369a304b85bd2af126d08f79d8a9e2bff0b18607a95c4efe35941c5493c94e3f2f3902e79f4cfe84c138b83c7f32d7c5a125b28c6107921e8ac92f1af7da015b46a2f9169369cede770292eee8a5f40d080ea1c267c33cb7d4187093d486dc3911bb2d6cae036cb508e81ca783ab5e95cec751e39f3038003081a252eefa7cd913baf136d4e27076251da9cbf0c7d2586fe02b62ec786790ef08fb3ff3d79bd06868eb1abd9875920e14fccf6dc144e898f578b7295fb5f4e84cbf683722ce3597aafe3195e194736fc317ed03ebbb00d956ce89f7a41a334020e1a88da355d3b47d5bd3965a290f6fbf5dfdc8c8e6347b4eb85151e53a960311582235f3b546ca80a670dcb628fef572dfae0c101bc08c80f78d5630a793bdfe402592c316227f2333b386839a67e6ee8d9396fabc9648ea656a407670efaf80966034958f4a70fe7b920c79dea3d5a0ff05f3ed0516537d51a686efcb258520936fdd415345251c9ac1143a41be295cf12da5d4319e78e1c57ce20507490e5213ca7be92afca8ec8b6a07b33571afe6940daa2afb0dd4dcc1c329474ff8e13d740488e5ced552074fff695a04fc1b70755245895a1e9c387fd9514261dbb0f600ae03f4896e795d1e72f421d8572543243d662f6811eb9402b6a3b8dbb0f32de95bb1ac01b1287663d3b6a3f52339a4f6b27789e15519b2b59f2f4fc8fd33ad1a6e4d02cf0ddf8499f45746da424ee78e72847e3cd3833551b6e6fd6b1aa98c688252b57a1d97660ff006ea1b970a0b8fc7d2e313ffd0b0b85299ded47b60cd2fe9bdd7ebace4b0c1072cdf67231a475045990b35ec761e1dc1dfbd0c402296566eb4b9462979d33c9d652a9295ae70943f38adb212b48bd8ebe82722b1712ab6a3be6060297e2aa54e7d0158e4aba6975237e7c7a1e22b29560b8d262125ff2a6e5c1332acd0f6b5ba15b4a82d3631891a01530321830aa8f2e8ab6b41bc5b5356957a4d0c3bc3eab04df7700305a95d0f9cd18d486c675c963876b25b1a0f78e245deb40dedd14dafdaa9d614fb06eb2538c5411e13be116c76fbd3377ff212eb07c5c035612e4cd7a1de2ceafe95832eff88a9bdb3595cc19287fa40b8d244afe9bd24dca40db49893602a59640d7a1b8e7475825b09cb0cee111864deba9d3d1beac03664279910accb9fac534ef099e398d7f6e3235cef7685fd1ae46e47da093135741894273c0c3486197c26057044b10faa57244721328b47e611633d16d3e4776d90309d68ce4a60d3ecda26c9f39c1c6da67ff79fde4977efc5653d79ad86c3b53090003bb72e78aeedcf4c8107185d9aa65221df4e2104640a1a083845c01000370371fea2a6bc8ae43fbe290949da4e559d3867c16df16b143fdc807616f51ebce8d05bb03c2b0bd587b95e3f6a15d907aa9a5b11622ddf4c81ff9fda4bb49d3e9577551bae649cf64ac0cfd646b02f6f16cdefde09a55e77afd16c74e8a3d777d80b7cc42c51f618a3c467968631119f11ca4385f0f5713e37ab1133b692de475db1d44fbfe9d274b9a09e673dac88aea74ba88cde8db3c831e9b5a0f1e40261281e5aea9d4dfd48c5d9e173f4d9cd56fe7fd610909c838bcbe1d6c729e151ecb4caef511a36a14b03cca7ec5d0feacb4647ea5212a11d18cbcbedf78443127680ac0b1bb65120b4197570288226830e2a92b380e32387bbcd3be2c77d6c7722054d849be9de459cc1832ec3ac8e7f60fba9c81cf5fbad37d228eba137a23227d56cd24970340f2b7599aada9d2424cdba8b50c2b97244dc83f7391e2ceba5bc0a11ba547c142126c791265b33a3db6238321a5f3273ffb01e42adee17b898153e41818b91413ec4f6386ab3dd48db875afe659db9eac94d16f850ac179d087d93784d607349e8711f5f96fd514e8d096de8b4a74122ba914520e93a11fa4adf006700e122e2531e1f39340cccbab4862708d69c117d3efbebabc14a0231916ae1ee8285727c9fc980051360346d53dfc76aa5a11fb1fc8f36f95f741e913bd2cd1031e508b320abd2d3a62baa400dc439969eb44e6abf8223b29d4025c3d1ca08d2dbdbbf9927c625270543e8c0cb5ac5bb5d504d224e66a1895719e4f975d819a95e54cecfa59ec8e385aaacbb023772fdddbe093afaf5a75e63a62d51926254e5b47da1e9b05851196644b9180734d05810dcf3502747c4ece652b67674c02aae74f20d07de2ad5993b3a68d10207eab6be5be34e52ada655aa96c1d82df9b24c2acec35e8f0bec9131c20d0ad8936880af87215611b80d07d7a741a12d8145bd05066c6ac171afd8684b92f72237bb0e4ca4aec1ec280e39f36928852d5d8d02fe463acbad8ecefc103083fd4298f399bb254e7bfa166638460b760ccf2b0f5fec0e3875206bdc8ce096274643824acfad71ba06441c74788356caebdd2208f6f077b056fa9d85aa4357e93bf064a776f5f3b0f288d0afdc51558c8f25cbee17247364c2bb24637dd69017f92bbb43024d9c773439626a02bd0cd44136a642c9c5ae593f32eada790c31a6704030f2e07f1173cbc0dabc410bf9864214c298a6283b3631acbf94b8371681ba81eed1aa81ccf258252d7f90fe733ac770b9744d0170cb554b39e6c72e05919cc237f8f4d7f3545f4d2732f4c9473c77401dcba04c0fd33efc73219f31c08dfab26abee9a7cd4ad3584730768fae899fc",
+ },
+ {
+ "9c73ac05648e0c50a3ea3a8eea70841e8e06669c1e7520c5e25e093769c4b005375c0a9cea16ec8e00261ceb96a00924a66fc0c4e4e089c63e93fea857aead8e0ab82af4ce1682cf3c9fbad23fc3f7e632b7aa169834ddd6c7db7e1e892cac93e4d787b2ed0a812aa93bfce8fef3ce30ab794743ad241974ff989288c43e1ba815a25a03acdc2d5517293e161d0c46c8858d0b32b124a6b0bc3838807753288cf6838fa25fbcf876e6368c0342d3cbc860d6fa12faa1c2b7d9fb37504e60dd44e36ce74229dfb80f1545125718dd1f78b31a8aadbb4d6494489ce596fcc2dbdf2ec22157a1d966b61e780d36552daf084739b602861a96ceb67b65b23d40916c02b2c3a38c2a59aaa266e1f8939000dac9b6dc50d1731e87ee833a2cc3cb98c57e5b680a85c1b428289520bb252096efd7723fa8e55d2fd4e16900a435986ab3f3d2bd799471a1bc07c1772ce10d1bb8805a6065b8903999f9393d2ed1a7e1c57a9e3e0e10dfca17a04143814f5f3acfb99a34712a6e0a24a7485279ef343e69d27c77e25b41f9fb833d7cd29cb6a15551d5c77b43d19feb19f2640926a272f81eeadb792bd474ae11f080ada72103f8f7ca733a9b1325b50589be2b2b3023491afec246d336f4e4277592ce9695c68d5f39c8fa4cedaf51776d7ca29ea0ecb89eaefe71e5f3560c68e8dafe7da08cdcd954d626418677b8f3f45b9194474a32f548a4da3bfae6a3e2c0a25f602e3b3a821160c397d77c8bcbd71c5f1e669213af36eeea30d48e12953071f55eac2fe0bd8fa355671fe032f6fc9214632428125a16fc8aea8a9c7fba0d7518b9a4f876349ccb9bbbabcdb2a85fc60b83ee1ddd041967efa4036e5e10e377c9886f40bc0b0b57c7b724795f843f6a072e87e532a04c21445090a360731a2afb896ab795750e5c2c33d58bb714f5be427ca3751df09661402604a09a1eca95a8344d3daa5b99d68e6e6245825704c5d4a73af197d052d7f75778917542261d77735a21cff3f75d6159a3e4b1a7a9854ee376e6b3c8bdaa1f353b957862b2efd50d10a40007026261a546124cef979ad20d8085d53e30f5736b8aebcd3cdaa349ea474af249ac53eef2653ae1fcd5b3095538de9368d307d45df2a19acd44e3b78c2da9d5d9fcc4cb61feac5dd35f66299845bc0018c3d476b6761083baf33a4621e41cfae0e0c642de729fb2d206db6a4b976a635b3fd911b5e9946fddceb6feb2d2f893b2bed590317442037a1d6dc5b5d72910160221cbecb53bc983f1c736c3bfc9757e9e05af1248b28d651f521af67b2a0d7e4bd86a0013338404fabac7b9833c372142e6338a98c0efb7130aae8e34bb0c80937680a7a904aba3be735d41af9462f17b967b13566bcb697579f8a9340429c77baa6e24ae1ac86d8d25ae3cb9112e34a7a948fd141367898c5f33c0635c87de06f603b510cb229df0d0d9a9e107de88b12686c539ed4fc54c8285afde0c8ee502919a125cbcaf4c8c89f56e90d3f641f97c07326956f7b5d87c65b689f39b8b84359ee0f14d2c7ed621ec67f5e2a8ee5faf21c805187edd95e3941ed62fa95a65473a569566d46b87c0d27ca37b6b022a8cca30a4480d392ba15701d1015b3648958cddfb614983211bffc4966ac6c1f691f19bd9fed405a02c06712d62a775f73353f3949c76b6b7757a4ee0410fd6d20071abfe46b09e72b70f9f19b61410ea67037e037934bbefaf09cff018a5c218176d165d1eb5cfd5c46eee7b82fe65ea02e3ed7b18a86ac7b139b7c9df79e1f6e6f85304ad22d97190c7ec12c651fcc835ea434d92ae1444e7cb0dc644efbc2ae70f2f94310805c1d0f2d49643d05e78baa1c54d4fd99137a49efde88dba1374c94208fb4a0ebc1a0090b043610ebc1bb08168ff5bf936ff9834e825eefb9ab73da2b287b06fa2b0ff52f46061b07c1131e4108cde478c767b749b696f3520acd8d3338842d53941282da289dd1e9a0e02aa9be0f127566c9bf2d50a27f6b6ffc9e9880bbfc14ce7eeee70cb0c0ad90fb474efa69b46123638e8405fdef65fa7e0e7b29fa8fe8696edf661f9003a08b4aff85a4a3e6d817655c1d533b834da981b8c37c38abd5977b3ba71b3f57967a471c2eeaf2f6f258431fbb7e92f91814b1db80ea775681f282290db170942bb7b04aa2a331950b74a4b6e337affb4c51c6cd4c4e13ce3095e73e4767c2731f72bdb225ff572163fbd8573378427fda194d165750d487f6bbb63e1378a132fb6ee5115e3c32b2380b096b735bdb4d651853bc7928346fe3ea9df7534f2a4eae1f5ffc4b82ae738db7df0103ba4e68c2a2153bca499bae2439a57778cfc616df16032aa8a19e26597d275d2775b5ea17cb25d204b18028eb25a053e5666ac47c6def151f7d4b68ea62c601d87bfbe04711c24bc34274be6815024d7b7d01e7dae10cea6e485348ab195a83854663cc5826181b688cc9c091dc1e0d491fe51400e20e6f2a51a7d56af258e038bcbc80e2c4ac4b41661bd33229d07b39b59f3aa79d99c1ef41974a33e02a7cacd6fd8f9b99cadd0fd6a031f070bd3a364c64ddda0e9fb94036f374171de0b3f4ee3380780e6d77d50db9d58e670fb4a364827d631226a3491a27602808141ce657ad6e560ad62b088ff086e6f03b8a64bdf7c7d01e7b19289279509a9d6d80e50aef3b05b5561e4556952c46d0b6ab8eae735eccee77e570e1360b7ea38c53ae6b8eb420e4c2663b57827228392db6e79105a47f7d89e06ecfebdd63783101d3bfb5f494785acfdfed41f8166faefdf0b49260222c4080ec2c6e4f949f41784f076ce37fc7a34fa4e547bb44e6b9359b4b95cd67d64e4402ac83973bd50f8adc7c6e4c34019bd8f6d3843bba3d7155890712e0ed5134e00db877398d86b459f312a6272431f01b057446bfb1b8053acf181bac79408c7708f3a0867a64e06d7786849bb874a6bdf8fd6daaa572d5648ae100f4318d6b3a811bb0fb709168e817ed83c0622a7e5b17ebf5cd5ecb21d9ac32ddddb039083144c93cb55a95ad72732132d54bb120639d1620ebd142b58d75835b35cc6367012c93c6772963e9ac852c71c0dda2246ab845469997fc170d8f62334bc5aa4ce23e036967674303ec6f75bd3d17d197d026de69beda70bc59d2ff95a899d28ac7e5e42f4d37233996a8e6d3b0b86b80df49ea8e145b4a6e3e39f3d6c3c6518bac45baf97cde23037709d737b242b8918ca31f90fe59ff2c83e2f347a954d3559a8e4f075c620ad36be20b1e24b3afa156cf3255192171ad0474e4adc9b7f35436325b92945665f038611e5d14bdfe7b7d20c09642323346a717f460dfe7b5062a0098be66febe9f5fccfc747aeaeff81ba08e5dd2b1a489c998ea9970afaf9aa03859073707a686c492fb3f7ddb27897ba5e75e578bd82114b2ba85525a2002927909c970a04035334b64b1169c3a923211e0999db8baa26b6537cdcf57c051c0ca1b317a5b66ad96cb5ebd57994f99ab202348d8ddeb343312f1f26ab2442b8c5f5cf6bab394418ef2fed68c3e60275e836027515b6b946e5d86d91fdaf49c2a5182d5051726840a156a8653cabda25e1dd9af693533d782caa09295952ebfe6a194fbc8bb7fc2c0da5914a506c6f31490928dc5d6554890f5eb268b09d671bb6b6d7416dd36e7b78ffc5c86b34fab43d22909a87e5239643d5fef373650e291be56b89b9d90431d8c9fa44fdf4f83a1689d59d6ef833b1ce31a44197b36ab298d53b51ae3f8387087dcb0571c340874c1524ba0d576bdb88101c1fc387d25b5c0dad0b4d309255ad5d5b1e209ba56db0c927bd209399a8a3b5c8663c9ac199a76ea4f49e364a4b93a569b3400e20f0d748adf7db46a07efc68e43802a5d1a914759eb2abe8fe3e8d67f2cd7612bd4d5a6a4535b1e5b3ad4d97e54f3db7f8512c9603d87e01160b6908d8df1b952c750071abb1565e5ea3f643f233faeb84278187ff0089150bf21ee4d13979fdae796f592ac5b88869aecc5be1c64665edc8ececc87502d36720b73859313607aaa561d56a195dd3c7292fa8f0750ddd3df9ca056fccd9d6ec900f45c1454c6ceaad4154c69e288dc85735b8cc42950a3c5f0fab2be8811779905c3ad5a9a6bf56e7141d863caa4e93e0065f229b695efb790926618b3eda1b9a15f143bbb09aa3c4b72900617793417df364185cc213d5cc3a375778117212266356e214f085d8a7aed908256c4aa25faebabc70ce913c08c89380da06920069e8e27dd867567f152f883a9bd2dcfb8097b7f065482d6d11c0edebc67feb3068cead403503c04b324885ce1a62c99af9808a5ec8b7cbd978b8c43e37b06e9f7e1ce0b31fa0fe52e8842002e6e99cdf69263d31de080b56c0cf94f77f0397fd1f77b13e17af90ff33b00119999df802c33534a13d3ff7fd0e8cf58e8f8c8bae033cec1aec7d191f2d1a39c7b731c97a67fd1ca43c13a24b9f97d92e2364dc26a1c9408d4659ac7373e53a2a1704a47e01c0223ed4c489735b62a27ec67ea46747e4f48d3da101b0863bda9d3f7f1b413f3e7f130208875e6a29dc30a78198ef658c7ca32d7d53b4b92e51f8ad6d39ecabb800adc0870b2ab0e85b5769f346ce7fc371ad40c561f9f3b2f2a01f2b8ccae48c78a41383cfc36b2a1bd41d61a39c24144965d9aa5ecc5d506c7c7cf9476085bf049942d35caefd77821ad925b7fd3a006213abc1e008114c848d45cbedcb8af264cdc5c07bc338fddd1123940e5d95717040325048439dccd1e298bead22b011ef76d26a390a68161b8bab29e8409a5880cca9c8104694e1282c9fd64f50e73ec6b9a9ffc31115de9cc0088400a2dc806f85487fcbdd60f409ffca584fb197156b40142e512a0dedea1571ebb74d6b26d3b4a59e9105929a055cf3540e8a6a79ca7ea71ba8b40893c9797e81c6e9a7999d4d382e52cac95727bcac354616ae1094552b3d0a33d0d3ac4e547237fc0cd54944039b0eccf335889f6aceb518de496e0986783c564be8a4a05bdc9c67b1e5abb480b98173ef091259d8c772b611e0c09758fceea3e59243406edfa71fc452d4450b55b8fa5ecb543692c6eda3a6ad3bfea929a18ebbe5ce2ac4754989c71dced37286cdd1512107e4e7f4878da1c28b4beb2dd9a712a8d1d61d1a5fe5382db8aab4857b05a783e98e77711c1933a7641fd43dc6e6e597bd03b11ce8e94aa094fe250f03cc92ed5b0a5e7723911e87b0f3c476d9aa0d96adbfb395a8fd353cfb5a4cfe27deeb82e849f90bdb17928b0a5702e4010f7aaece2d43772a78b325d2ff24f9de0f7bc65974d2348c64",
+ "bf96bbc17abcd1f56a9f22ad164d25ca72f8c996f1a7a66d6effe140336da4f20460b47e1c8573872496343be35a055552ceec437692b0e4919224c4ffc8b603286a8245eff5cc148b004f6e5a54c4ac22b0f09842a07cd332a09732694d3591b8b7d6a7ada2bb38a30aa7fd5e6baa811b9a195d3a96306d",
+ "aa2f714d3a184a9883f4199e8e33fbc9c92b36fff2d59f07a9d0d335d7476e81",
+ "36c79f9f14d431cc8c077439",
+ "873d0617c986dc9d83e9cdfc50b1f916626a9d9e1c595dc7ccd99d1e993d25d89b04a893c89e205952eef8f1733054bbb55fa5e1b07135787d4fcfae226737b50cafa2c11276e8708451be9b4d7f662e98ef6b705c5c4fc64588728eab1dfee22a0a92bae61828a7394977b0ae8a3b6d0126a23583fec025becf0a72a28891391ac1495732a7a4a1d43a63ed8eb37b280b6d886096fbc4f77aadbc5e441e996334d0e10cd7f3dbba9bb7efb147297986509a07735385c681e0543186dc166291edc3b4664f5c8ffb0965c85bc30ff5e7769a69609c69ebb68f35d104bafe3dbd3e2a40e13865f19bca3612e48592aa930eaee29440b4ebc1c0a59f1c54519857c929709b086bfddd6d4a30940b592be48e0067976099efe71f45f956182dbb300e8076e1207baa32d59c1afef7f34171bd66099d2d7f07b39d16d0f8b085185bf2554c6ad66bcd656f07979e8f19575a116f5c4fb9700ec3b46a3254f28afa1ed51348c1af6dba26fd398098a76d7bfa2ff195eebab41330ef290bf75205a2ee570a2fa46bbaa74aa6ba68a0e63e2731dc1974eb44794f3c89ba58cf96f7a070fcca678185711d97cd9d7d8202351ed589e0b05a7a190e60ae4aa109254a7bcf7013f8addd07a64145e21226795ff7c7b1c225f40ed7c3552da8eb18b9bc9bc70c2e7ecb10c8b20c54f04b6e27b5044a7a67b558407eb330f2083444375c022565c45fe817dc00c7d24c23db320d15949b0b64fbbaedd310e73e423fcebe6e1e98a5cd232d97e6466642e5e3b23f06525ac1cdf8688650cd366b1b7ba2a9033e62d836b14bb73717757b76b9673671bd3d3b2a56628f5a309f3b86ad32abac0590c50f7c5a22e0a920d88dc9fbcb3add08b900a2a2fae4178aa100a0e645ab428e0e79bd90baf4af2755e48262b64838a6fbc21226e323c0a1ba5703e30738fc7b5a7df9eabec6199df5ff6ad58f9df5a734ccd6509e53ecb3de1c881732e26e52ab848a0335b04b25f2254aaf8c130c78b0c9a40b60d402673ac7ec7311d0b00c45bd176bc73ad81c2478611804f59e3c145110aacce922e473ef346f8acaabdbb9f313dd3f8d0a937d0c048e5af789e2e09a816146f9ea28170909caf2572a2f6e2d0d511242909de2815e9ec586b2d12183ddbeb7dd70f32424097e2ec28b4ba62cf78f547e2057a4c050cccdf6b582172343742ec8c85e2847efb1595bccf89ece3b3ebba824d2f097b1987ec26c6e5710544739d54a714060fa91b7995cff0161415eaf55758078772c0271d9d282354e47a25b673eb11497a6ed8db82267d65ad47412300ed525af96f943c5336b1de88676dc346e7339230032463d305b0442f934018bdf0242768511d20474c6ecc82fd752c0c0ca5cee1f3e06e679fa5835540f97870d47ccc6bab233290be7a3bbd4a73f1dc7682049bf7b3cbfb6687479c18d246e3c07161df5c889ee95d39cccd989625a8c9e80f951f8b1832f6378e05daa8566477d7fe547e49ae6e822a68de4df9fc4d6500d5219c3d3bd8887bd7f695151ba378da17c2e750399f7482973510a386721c59683a86003edb9f0ce1ea89bd7bb8a25c222df7ebedcc1b56c8ce18f367b2cae720e0591b477f6ffb498c3d7ce59cabb1b01d7cba84d7180b4b2a165d4b889a6ac361720e768f2913aa50b0b5c88e55c35bb4df4fbc4460338809605f1fd445a2bcd97ec1d2f269b5e779a18c8f215bbc5555c745424484ee5436119eb8754f5e9e91f51fe715353596baa1fbb0a690e99691636e6027cbd4b7be752bc278661e2677070ddc12dccc262d3dd47160345de51359ee8dcf2f61044f95dfdaf323881b2bbff68af6572348f786f6e52d1309cff871ad58148307d7eaedc93ef037922b6092ac62171433adc4934884efdee3052ebd60ee115f76f9dbd0eab7c4c0a77b4ce8078209d23d81d957335f331965b556ebd54732327b5aacc899f9ed0edacad9eb98cb845867f249efb0e1a5fa2483227f78decbf7f1f32d060ab0c01eb985d83920b2cc24b5f9a0d5d869e980129d3b78277fb87e5cda61e340a729d86b6617b8828dffc7c37d4c38080ef3515c2784935973dd184e0a8160f84bb78bcd8a5e691760be4a4d41ed6512ee436ce24650c0e17e7d74b5e01cc39b21e21514a84db262d673f24a82cfd5dfe2a162976171c538b24af16429bf8ed5fa8e37f89ec6e7d63ea1d83ac1087cf89e8f43161f225108889e922493d973e36b510074533cb1cb22174d21c4076959e4191a5df880a8b868b95a9cb5151a7ad47375fcd87725660cc0b59c88ceb86984941268493c49b8aa2baa8c531ecf497853ffc3d26b926a379e72188e246d42073041fbca453bd558f328881c8f8d9e099e898a912530c4be499f2b32229c359ea10e0befe6d94cba5ddafe51d164898166e890b22fd1eebd5724451511dce1f8f7431d712a3f1e50fa5f609da686253311af255b84b2106b09b803e94b51729cfa0826869945d46b9606547e7e33fd9961cf15b400d0f5e01d8fd4d92a83ae526934059d4514b9e0005317a70466aa0b6086d5fcfed201d958a0de55fd23f0919ea29b8aa02440031a9fc206b9feef362a73430a4204869354ec81b6fff92eca97e7f1bb12d25228eae466b8137b4806895ce34b57dc14bdcd107fe160776b0e5daab150ba06976eb884eaa574da393af4de355381c7caa4f611a2ee70a0c78df93a4276f55e6281997b4aeb36888a6d9638cc95444047e5202f41f8bdd787f1ff44a648cc7d39f05e49e5d6989fedb194c526780709763da81a780db0d1534a466cce57e11dd3a4c0e273d9873af1040d52a90e20101e1f80ef296d45769d204cd5417a84e022b6b336675d36d9cbdb16b0cbb08f5e240012967c8067c92f97f981cd19d449084400d76adfb7c610abb73bf21e161db04debe6665fca79d71c8cc50adc3ecf0e52d07773478ca97b8e9821a5704dc58acc647a5bc618d2b681f17942c46c266c73ec211ca403a7d47e42e12c775b370cd500d70a4aac7124f5f6d2d4ca78e1c17a96426c326bb60379ceb0c84a86200f3b450e5e9aaa11f45440f5260eee7675a8b9c47fbc58cf18a651a1dc7b39a911442504f12c103054bb50f15381e512dc6e3af7b414b3db26fe767d83a2a53d7181fec8f6b196c7874befd6628b31797ee3c9260c7b7853b137893e36696e2a47277add98462ea9a0edeb7d2d3c0f2805fd7db64c2c7eff353ff2b36f4de862a42779ffd4dbe77b6a79bc9f4ea3e909474ead915fa3fa990bc82b83a670b163e79300b627fb91c4502e96bb9dde00f716ae6ad14dac647c9f7c2e5b2e505708b5fee996b8e9113a8f4f2caaf414061ee72e76b8bf47ec4f781bd7c589adebc2c267448247e30d659998d8037783494a1fdadcc819d7ad7ea2674f75e10639c3d3055046a00814ddda0e463185454a4455d60b9780250183d591c3db6f27373cd2ce4f02f206ae10a8c32d71226e7cb8d5b05909445977164983c0073434d6c0f2bb62bda66a16792d6e53a49ccb5ac3e285a6baba935f30e9d1ddb812a018ce04f29e2009ad678ba72b6a7112d6e7cfcd3ee7b058ec954a6fd7fd01018a6eba6209687c3130de58147b07bcfa02ec1caf30b59daf87db4618b4a5fad34cbc8014a7529b9458e05eccb9a77ef1621aa95513c6fa4003b0877ffa6d48805e7867dcf53447caf348228ce926233f65d553146584d6ff3dc3ed3296db9bfe69dec6a07add13037b3aade118b2ac3c52350b9691a6cb32356ad93377059fb8ceab68de38d96876d6d383db01f3cf620e47cbfd471bf6dd1f601210482f7c3bdd4c3bd37dd0a7507e1f0fe515151634813dd4ecefe97b52eda28e7a7129993b0af311abd3a07bc463f3cbbcb4fb0eb265a5835663fdbab0d8b8b5a73837ac98ced6582348fdeb41ac8ea9e36f9818ab9c0a41bac1389a6b518ea17df043dd50550f32471645791bf59855ed695b84919aa5cb688e569122786660f06e3a919ef9cf18c355bb397b86710c367362cddb0239aa1d32d489328e4bf92b3abdc3d0dacd76ef1a1efa28fdb848e708aed6780e2d8efb19a2e26fea56b4440dc3eafd796896d73fd150bbd967871f5e6ee5db58995f2f85cc2a15077d7d472bec2e30430af6891193ef03dfc7761e2b3b3b54a72d4f1084a8fc541526fdeb0633dcba14e9485b43065aee8750397ea88d9ff13417149e0fa145be666e6f4afdabe7ad8e4864e777c20ee7a2842db44dedee22f3ce2f97d72919b9ff6059352083be816a7515c48c5140a99af8e81b9e18b10074dc73dab55fae66261421629c8e323d8134f08beefbda555660a51e4b55a9ba4573bdf0396cc413145a941c4175aa672586f7676027f9fe211db87fe07a23962f5b1ad8f566f0d5b13c5146457276f307a02e1e13d00c5032a06d225248215e4bc4be1b672f1eaff16ca95da42513fc4315c7a6663f9101aba80224acbf0c87fd3a2ee9dedd1808c1247c5bebf3cb8d77377a508ddb484ed91203a438ef5ed3ca14e087102bc5f3828d8c3437ecf5c92eeec0331ed93ae33520740abae9b7bfc45f097da70adbb9b9b879e46a7d655dbf75d89773f737b66fd8a8c13506cff7b44bd85dee279ea7053f3ed8447fe79c400cf23726fae800449d27af5e342ecf776378e2eb449a3af27a40fe4a9806487b81c942bfe1a4b0fc146c971a13f83669e0189e337cc9fa2024864436189a9165ade6b864698ecb797ea05fed0d60f0ab4b92cbae36c72ccb5aa45337cc02dd086afed9e5522ecdb75ccf389fcd63c5a4abbf60908e39cb3268c76a08687588be67a856a841eeaaee8ed016f6640ef0f5acce12ab8bb58dda380696e3fb22d0bae0788c4fb79d00cfa5ae3e479dcf7d08b45f4592c2d2a7f8081d5a9398659613ba4932ebfd7382d516b2648ec4ff4477648069b9b2e4decc89547c16ab82a0ad9cf293fee5adb17cea4c95ab7b8e386dcae6acac63ad0d1d13656dfd97d5623dbe45230de597751321bbe5a03c879c303fd7a0d837d48141decb6df4f0865717628c85dbfda29df9a8a69b2c956c75fc66e45c08960c23bbbc706e48395057f989dfe675305067b3ed8d046db339e504d5b2bc978ab4dc261d8afb325c5e794ec79d63d8db53f9dd24b623fbcc202679fae8f7d39f7f7e0667b142c714b6a723996e5254ad2ebafd63c3577f8909981ce6b3eb1a6ad67a4e93c45ac3b34587d153ec5ab67a2697a9741610d5a176cb9b5856bdccb98f69421061c84811dd6660495d9f30548efaa69e36ead246d997c95bad0ca3fdc1a08b4be31b12daf211d3e29d585cdac48af8f2268ec304bb35d",
+ },
+ {
+ "ceb1f819497c0d631a9c9616655f419b5e3470fd3b19cd0e4fa556bd26cd9df57e960ec7121b2a2cb7c0421c1f84b77eb8277bf341490190ee574d1424eb09a281176a933394bfea5502077486bef23ee66e3127b732b7a58a04b9aeefc35170dabb030d4fc3f8a4c5ff194bbd0b89a379baca30ec81d576868f25755276e62c31e93a80ac322571313ebcee494592c3ff5cf3ecdec962645887d9aafdbfd62ea910af5542d4c7731283625bc9f41ec85012b42edb1792339e6cdd9c2bb3cad4c4792a064df17a5f74dcbb3dd0d90620ebba4fc6d1e1f9704dd60c798ad64d4e5077549d68cefdddaab81a7a91209b7ddbea43accb3d1c191328929dffdfeb4f5740ecbf0ee99cb9a1b73333d7ceb0b2b8f35f84307b9d44a42fe1a30ecdf2650dde251bc8c1d46978089c50d64c028f40611370ddb0b481df9624ed63165370f4788bbc396026b268c2023e0f04cd4f66e0bf439074c46f0ae85d6dfeb0ddf22868af61c8d5133097156fa61a3cf5801db5c3ad29871d336f7aa06d2a7d5f52e50eb3aee3c7de7bdc4d21f68a1776a7cc3954f5c071282febc89c1545fc672a0a1bd8eee2b769be048ab58ea12b356d658a6225fb8a55e752f1fc97ed64c2f87f9ae661514f1f56d9d4e47b001ae865a44b8a9fd5df8628d183bfbee781b6661c9cc76debe6c3c5bba840bbc228206673aa05498a8c715b0f3019f6b2d05cce6c233b5809ff1dc4a75d7f69859fcff94ad442d460b32f6fe348659518c16385e49fddee9efab2455732aedcd17dd51b5117efb2ca1e21ae6787437f48a7042d46e11be4dbcd2932ffd70fd154e4eca5fcdc57c6fa79746100b8e1485fe575a5c79089a25eb2d55d89e42eddc81b82c4f7da8bf153ff5353b7349b161911bbe0a14483fff6585d7f3c8b5c04a6dfc99db9548f0c53e25f0b16fa212f0bdd10ad2193ac18eb09972795f42b3bd3f4d98c4868989c4af7a760f1c88ffda59faac73256df1d607644f56a70303d6409c9ad716149bb58f01b4ab8ab475e4af1257d47049aa77adf9ce54fcd22b3d6ec60484da903a6991ff052ca37b01428d5916fd92c17530bb3385a805b0d57476e9f9417a23ab1c12a038b61b3a0898831f9615d10b468c3edc24448d09b8f3e3a2355dc5e069e880929eabcc97344fb6ca5587c5ac1404783848f531f1e915941e7359fedd328f7fd12b3c685f8c1f29d1a6ef7dbae3e5e32cdb251eb43aa2d2ae0cc18b3f40fb006c2778cba387e5852ec4f2d9b8e8ccd5b3e1f4781c974aca940c45d35d30d3b9584c750bd45a80f32f73dcd85c99ae107b92888839c342cdcf88911cb974d611b14b1d85a59e88c502559d6eef3b7f5addf7d307bb25c57aae669767db6d798ca887124e159b0317e09076cfdbe61aa9ddeda189036703b1cd9b1998f88325910a37ef1fc2e227a382ae635e847df8625b99eb6ef0ef10ce7a2a5762ad7d03a7a4e2b767c4df0b477d6e9601dc8e6438184f97193ea7d7a8c22f1b6fac1f0740f1beb8b68db40e0b22940cff2261273aa0be43df561b88184a9377e6a27f27942dd04abb9448b6b6ecb3a60f14dd39b58b8d94e1991cf9d3a071ba42e0e1d71eb211ca466a70fd4724a34639707feefbfd73dd9680d76a214924642a063b38b85cf30eb763fbfe889f34b20fa4a10ba214d938a5a092c6e9b73b13bd664c75b34f746aa360593c0f8dee0f328f0ad4a3e40d498490007e573b8204a1ce7a550deecfb15f18ed5ea6cb5dd95a68adfe4cab37c13b383f8273b1971580016a8df02a3f4f431c9de9e7ebb33244512080fc5852278081b9f4434109c3427441329e8071d19d0fbb74fb6ea73fbfc7c0ac1012d3a0948d94d7ceae9b0112ec43a16cb582f9c53e7eb0ad15e05ceda108fdb3dc9e585a332018d1cb19e4a75d86041308fdd8476c88e4826931601a3a5dce06fc16512f4669f10183d5a8d15bace4649abcac07358089aeb1e9b8fc3776f3239d5442d3be33d532097e13651af7c9a5b465ace9e626889800318447b8876b45dbbe1989e1eecbfb5cdf5067c71a0d7b7fba6555d0edede12f7228d7f9841dc532274f24060b1f52da6fbaa179b81ce962723f43601d248f8f4d5778c1653e038c8d27828836d562968004003810e9aa9318edf3260272b54fca2e012f6c04abe92c2e6152f3c3e973c7e9abe8c3467bdc246f0226d1b7669bd577bb317c571aa8758bfb694fe4dd17ce78f091cf6c6de3cb601a9d177128fce8d42e652b490d90c4f8fa04ddc71cac300d3dff699be3250bfdb2136edb0057af3ebcca77ba5b3ca34531810c5e2d4c5b5b3bc4e71ee9e30cac067b7706c326357fe0ad2a4bd9cd811b4e9d696bd9b4b70579ae246381210f879c769e5f9cc3cf8d70e9c94ab74a55f5d7bf61a17418b6edb6db4147fc40cf98c75de85421b7d192919add48e5334ebce2a06e56b915447fe085b7dcd677659dd55de1f705c389975e56e0338a2ef07ccf5ec3786407e8449d9011641786f1ecd4d3d3da975d61f5a442293e6119ab20686ea8cc7681010421226838a95a157e2de948c536aabadafcd4095dfda48e5613272289a8238dc945e5f1ef30075d5de096131740cdf23da1fb8b9fa009e5b321083cd93bba9271909460c09bbe1e8c54319394ff85c291814e21215816d4791f01424abbe4cc4c792d0d04db1b812f4d24b44caa76de2bc50f4d1d1611862512d87fcebd3c0b2659082b2423bc5360d107ad7b8e8ba7438ae4509105d6b618af25e75c51e272aafaaddf1e5a227f2b2a2c96a8a83dec23223cb428136a30b290181ee20a819cf52f6c03798e7294a89f3b5137693d5a8b7a0ea38d78e43008fc4eeaf6d077ebffd3ef7952620e0af1395c38a289832df391d1710ab5b103a1ffeea8c06684c03a74399cd63797c770e3f0136d8331611502d21fb883136a82f2034358880392fc3d2fc274b799e59b89f8f90d2a5a123d3c21e5bf3540323743858fdb8912c7c6329a3aea241075ae097ebb23c8cd50f4ff46b42486e65bda6beba5f4fe6dbb30f7e61b1bf690c9f00f7513c83274cd21bb71563257a20cc38da2b88c1063bd0849c8243058ee205853342085a8edb7545f0d96a6af936a3d4612b95676665eb02e72e0875100dfa444f039eddde1422ceed8d38e6c3dbba25064f8c6cb5786f9ca67712b7840cfbd40f99b1edadd4bb9a61f48124cf3b49d68bd642404eb1dcf428eeabadfba6810a4032f8ed06b38867a7098c7744d54dcfab8f0ff941ecee69da9916d54097e080cad86dd08bf53833fec4aa4399f7124586223ec70e2c31e8c647be06df9e86a976f37901e9b134e775de2a0fd53d545c5f92236dbf5455859c138b7bb1112427049d29ed4f5dd5c43cffd3113c276d9bba910879e55efe817189fc239a204a9ebe738c0dd161d10d60a51e9dcc8c38861d41ff029ffd841086803320a17ebf5ff14b6cc2ac3dcf0ce2eea9af7ae23597233599c2321dd2b99e06d93f84989e75e30a388f47079c2af545d96f270e064a43a00c76bddf2f5be5089a69a138de844216148a1eb0b413f58d831d9b8967df297455e7538442388cdda12d157fb25896c6e2b47696c76b234a88bed4f09dfd64f2e4b77627ef03049030190fe271a5a853591ee9218a0c6b12cb3f02683d665b211dd1480cd44c9c0566ace7d751902babae14cc3821374bec774d54b4b4afd5d1811ede556a7a5ad02642a878d2d32380e7efb9082604f49d51495105f827d77945b5cfaf2f2980566b28ce3dfbf1bee2e077eb067bdfa4cc28f5d2211ca99a615e69118d9391e3feb9b13cb4a2fa9682718189ec612db889228aaa3f3345a091aeb11f41420240fbb47caf567646d9e7c762d3288f8bb2b1165cf049a191db5042fa9185fcd180b04d3007c376e0aa3d427d66d10918821f74736816044366463df7cb3ac94cea167cf1daf2d1842f130295e40bad672a22da9238ded69e241395f04d5e3c3875b8294faafbd3d90ed56ff3e01c5a0a3e349d761273143686aa26d408620c7d1a35ccc430a09e3f750d3256298c6068c0fdded270f308f79d2fcba591d723ac0cef703d8f0e7c051bae5b453abbadfab98bcc297ed4201b03ebc195c2e441cfd3b10c63c08868db36c320707ecd6a37593661d70a81f30e6db4a32f98e4fe6b950ace55923631c8f95138781fa2af78d8104fe39242f1fff6942e8e782dfa0d37c863caff9492f8e5cb70046d207c4630cc29c20e1ac105aef093261d8d335456961e552ab14d107cbe14e9de912f0e5d58d16b729270208204469f917af4e710123c3bc38a4b3f485f2926f058344db105b9239829441a2d8ababf04aea615c0e350846d9bc3b5faecdbeb450f38f615f119ad1b5dc748e88107ec2fae01f0915174feec37b3e7248ed2699d0a5fb2fc785f17d6275fbea867aad815acc8a6fd3ca4ea7357d197e5a30082ad5f35a9d894c0aebb206c6487163c9cc20442c040e6aab33d7b4b221e4ba4cbabd975836e353129559d8ddcb3c97876cdba360da0e0c1dd5b0cff7957a444027db985ebefb6154453a221076c997d3954b347f49308d2ee14d1676b75ab6ef365f3de54aaf398fd96b9040253813ba734829bc78a6db59e3f1c0ab4c878a72d6b8681157919130fd3171126994dcdcdcf68955ad64af8156702c92f7a715ce6f7ddfb70f60e80c92691efbfdebc8cae252108fb6c0010d303d9027d4a5e63413b5fb2316d32fb93c3ea52a2a7df50cc0058c76c58d73f5bb041d9fb9f3c3cda9bee0c0920079ce4f1ef8698ced664ce2e2b3b86027ae2b3bcbbae5bf7ea3693d9429cf94938dd3a2763d3f53937c46763ffee6579d018358bc69182b1c7158a09b18352ea618c11c45f07fe97cb65faca535f43237879ae3e0a31efd14679daf8fd2ce25eb8f32218fa20afc586a98fd908d3fd804cabbf56dcae272328011b252dfd83e5f0a5fdebc6acb04c5540255e1322de5fce9db5aa4cdccd74dde8990ae51cefd6c1edc1879971d3efb1f94dc41b2b23e9c9d89415b46189914a229b2f3e8b05ff78c68711385a00e9534dae6f79d15842aaec575e4ee0f098028bc74016cd3f8e93c6a0cb21a0b574ee63e367343ca9de28003d76e02d0ee2b8d622cfa3615d3628fd02499eb7bd8c1aa1f34edd9c2d059c6a7c7c978a5e4f60801e03e17c3a09793c5217f310a30db1965b8e328893cef20f4a899aa8d9fa28f7fe0a733813ed7466046776a874273ecfb57158483f4a588ad4f232adec5ba4ea651822780596de09fd54b1717bf04130619979a0e3d12ab7c35d64afb8099a1d21bc952653742f50c8e1c244d10374329cedd27fbefd37815a9b3112a4cb2fc587c4ebda381b2b01fced45cdf0b9ff8ca7d10b65ce42e728de183a82e369486a2e3345664e70674a5dac174d6616d90de8e472b62759df057119875483cfbfb103041751747f9cd12bb31e91caf79eb2db1168026a4707dc618f30",
+ "e45eef9561f3acb3672b4f38570256e8cc4d877e2998e72b022e33de8fc20f7320fe0882f2b53559e084923786e8205336a7d15f3fb88a41e7bd20767f2feaa02df2221fa7577988db0bbf61f3dfb429868688c53e130725d0279c505686f083",
+ "475a44cde0cc931edf9a44b0c1e0001766f09ade023dfe6b59a6af800e549b55",
+ "7812a320691ca8442767a51a",
+ "eaa577bd67fe79ce4586f43355c94528e306c1678946e4f7a907d2a8ee7f4281270502522119a8b09b6f05d864921cb515fddf6a1000fc2f67b52d0627998591e2acf5b6faf71c278e5754b2703662ce670dd049da8d6e280c2b84d6a9b29ce28980563c40e03381a49c54608b72faec9b272ef05cfa41957d9eaf3e944b22610c725d8efea90aaac6e782848d368ffc08784d7fe37ea1effbbbb34952def29fc511fb10a1282bb0b6334328e4d00529a44de3259b522553a07d524dc75f431cc9670127c15670c0df419826617cfb5ebdd8788d5f528a9eb1e61324eac5c1746f339aae2e2e2fae598642a389da671482128acf2d69814258d83de98f186468136868b729aa5f0874fef2ff2575a1f87439d64e049e4d0637e9c99ecb7275417af654541306615f30b75a6caaa563e4790dfb28fe9f0e7881ea2d885eefdba99efa7f878925ce7d33e86d888154a1b03189429fe20af8fa3a68d65ced9b690a709031121425cfcd7e1890ed9614f9dc3ecbd0e38c6c84e453e3204978ddc1ef8d7fc6cae28c61a472d8e089e23209f0c36e80c994af771e6505e72ba90e5543f6bad6dcd31fdd468b13533a0254e44797825764ac1f63747d8d6ca019ff16fa732068ee94be382c46b168050ba725379df31a98ab81ec8eb266a3c3f2e1cd95e5f12b3bc79b8b435e4d94098c6184631cec57e9d8913458889223a2a4541f34d2f9df380f34c3e541fc587f0a6cf08c82e99476060eb84709a292f4c7a8551bda3a9eb6735787dbb9d7f1e83937c2e0e49f2cf6e0ab0ad84c40fbafc3c7e61886a8629bea816972fa0afd0f617b6340b1af19e341875e97565c8eb0b25fcf68696ee674d2abdc29396bfd0f282543d2b72a239c6470f76d3b5bff6d1d064e6e2d06f9deef2aae8a259c034373efc820f9a2fdbce36cc27f35dd6386de3b49509d0c305757257f8674d958c580a09e768c0f6ef237416fd53c31511badb2e7cdfee636508482f01899e72052b46b5d844799cf94708520178cfec2b61c8980fa7dfaad8915b0b75ce6eb57ed4a01edcb4a35c1dfcdf8d60f3191bbcdfd522a0e321ea41c2cd87a303522d0f98b82dcbe53232ecbf0e2528de7e1be75569584bf2ec574687fde67ffe9827ebbe78f2e5bc4fb368f3c9b0f588c97f7a139bd82fe86eb605b8e29cee75d07b510da1b24fd62cd2fb366f1621e7dbf268b15937f7f7ea4acf6e615775a32c90733769996dd2c5aebe08ecba73e0bc4781d33971992b2764c1b08aa972859cb61b003406479423254a01ea85a348ef249d408157cc0962d1e24cd9c426e6e6a3784dec6fe935be1f6730b01e8683d97e21d8774b2e2655f85db7149e930a44524d4f86004cd687d8a528b6ceadd890707458cab62809110ee28f61a7277ed79dc41e573fd4a59fabf15393ed4c21bf4d5138ac843e80bbf5e1c39ac2d7f2147f35996eb51a9e835db63faaa196b8aef1823ad72523fbfcb35b5560582a48a25ab770e7528e4b3ef291e6f62f5fac916e2162b3b56304287e46839858daf322b0de083d1691d6bda44d66d085ef0d0ad364eebacdd0a43a4456035e58910d0b2dacce45b1c0beabc784f3620a3e4390c345df6117b86d4fc386523b7ceeaecc21233a2865ec6b63bffba6689fb3323402119db8f0665a4730b2e26ca6411db04f1bcc78ce6272159ed2665a286f1ad7758d6d90090a6fd320e697dafbdfef575077e282b825bd64a4dbcf92d1fc0c6f795154e8466ee4b318f2d44b6f81c52523ab68ff8367e01090c2623e00b4008e784049df873a35c29e0abcfae7acbf27236adba0b913d19a15b4af4996669aba4c656c317084347ca962ac8df15cd2f849f522016eb92de4de62944b917d88200ef9aa2def0d13e5f4ae09d2eb4a2d0800af1d704cb01975f6d59768a2b50e39e78116147fd6dcdfbc08354c1b4033bf6772fa127856a4072556a9f07bd7516d01ef41bcb519005c0a3b2a04400427ec033f1b52fe5fdc1aed8e2521fd0fff663e203defc39d7546281a98a502b8a470af16cc62a6581c9985d7ca516864b799fcc55a803ce80711484f6b81591d2402bb1499c95dfb1dee9846679c22853be87c84b4547138dc4fd46b4e79ad12773a5392540a595954112f0cb1d9be4d4eb3aaa4286b6c01520558d58587d9d7f0df3a0282011ce01c9c17111d10ad61b3675b1826c1ad37fc562bdde951b43f890555d6f74ac4fbdb9abbe8bc1e80bb6d52c13de8960a3ff8f65201265e82981dbe39e0d65cf3f1fb6c56e11f9786210383d0150a5e0cbbdb52ca8b2bc45c12fb572657380df369082685b3de9847d5014beaeef815d63e203cc911061eb53d89a312d187f9f02760bfa71083fb643f5d8c324c410070b7ebde250a185e7359837899bb1568a43fa3418f39c12feb03b148b924bfb98b99352b1fbad3f07ac8e4302f85d1fe9ee4bf7507972670ff8beca105cdeb037f1cc4f944d6ca869d0281653de5ee93a7362420fdba8b01a375ff08fe27873655953ec1c00f53613c6ab8b244e2fc1b6babdca5311428d06f57aa4882dc870165deff75ba877dd2a04d1799f26ebfac97a1be53a83ab77dbc2cd4aa45bd779f61b1283eae1a1866ec8a9c150dd0a4deceb2ddea1bc0f4206cd435600a8f190b999b952337d9eb2bdeb3aba2cb2e7000319056629dc1f00901f0880278509417223a3ea0919fcdcf12bff0771c7cc725bdca292068478ccb2e1f35ae8964e0601789a73e7e7c1769ba53f865910fc3d0085c922d7f7849d27b6e7503d521371351f9d7dfd5afc5df0effdf6ac49617fa228501ad72154a73e07781dc4b07765dbfa721d95cf1dc41e161cbd34fc7883a25e3ba6b03e504b2c3b98c8b12ff629b965c2aefc26d74faff7f784baf09c3fc38c487a9d1f5818261162f97e9dff70cf42eb5dbcd7bebb66d68f26d917ddf2a3efc0db1e3372b170b4cd18da507e44c467943f73648dba74db1053b53f989e481c3054bac22c6342fca2c26d30a859a1312e9c353bf921f68136de2b1589747bc765153927c31ebe749dcdff98b5da84c4b66085451b4c87fe1ba2142f98636bcb268c33f7b8c2b96a6525298814578377aa189dd73d5bb27ec5cd2110d8751c18a3110273df2595d4c3a00809bdeda70d86c4a8169b7010c9cdeabfbc3dd3266518226d0ade9bcc4825f18198c854de329fb8fe456dd3bf35d89bd9d2384f3f3282f6872351a18a2f852bf173ea4426de6d01b3ef4b4685aa82df7dc45b99617a8b8c8a0c65a2237b3eaae8267e1f6c453f485432529d973924a080f6a1cc2cc18f804f53209383ce3601ad9361afc331707be1c88b4370404cb7fe0bc538df04adc5c8d9ced94b4c474b19619a53dca3fddb434cac09ce10c0293fea04e8e1b19fd3ff3d174baa988d91cb604fadc59ac0b61f4f87bfd07eee20f7f3ffd96766dd6f3555cd48da7ecd71d2fef34ab082678bfc4dd007669b3fc7a937a5a46269baa7e4e4e43eff1b2b847ea70b6c6c23905d6fb2fbccd944251087ac00c35c2eedba30641797d36ef9d3cb1afc0e3e8930f5b605a847ee77106995bd44047294d04350194369c5a7bf246d1108e1d18d9a638be0c051f695ce86579db613cd8922e86c683c91800b9a34fe6339e0dd79472daa662f78f04f0151a3acd18f11faa4e1216222843b521fb998c8490ab8bab27fde36395b456501307d07b484b453b189fa339282a634af30fea99c9af8f877e61871fe743238b2cee6cb69dbd17d574b5106ebe4b0fde4ef42fab469a5ba7d62c23b67d857f1af6ac981c320db70cdbb6be41bbca60bb7a159ee1c85cb82e0a220064359c06c660b75de6b49839eea68c80283b75d9d627aa4500c0c0f21edafe4a2cf7ee079d5310479da06ba58b142614fe69cb236c51447d63db31cdff91485b46325c26d40dc6d608d46a5e2fb01df06064a022ddf6d5cce0147d5b2a5aba5f9fadc5e778010a924e00a13e21daeea2cd330f45536ef4f42c2e77be00bb53b3f9a93d3eb327dbf30baccee5d26849cfad654ff3ef2b035b78dd3ef42de3302e5514551a968a205b823dffb040ac9452ae3efb43219b02436d0761ca11470405510e534d56caeaacc40eaf9c47a39475adad266f5ddc813e71223800dd46fa7c02b078353f870049806ed7ba57b40b7c3c6272296667500c4b97dd2d7026698b6bc4985bc01be99e0097013a2632c71740888ffaf902a02bf644b38cf9a42528880d9dd142de967cc2ad3e1f1737f0cb8dc5c59c252496e8cfe4e53c82f4a28d9ba2bfa62b6415ba3e5e09040d7f3e3abfeba53e46575e8817ac5eca806ec8a84c7cf77c9fa86c9dd2940f5b96b25a92d4a8f894d4717c8f80a62a35a51d8511f1e822fd79e6fc27cc3f3097d9e3272447de6f223971657ded9e660ee4f8836359742ce7616fd0ca2de6656c71b212b34b8edc71ff36bc84ac4af58eb1adcba4b2c0cb31468dbd2c2b7ee6752981ee1d152c4e4a9b25b2ce87796820def34b662381806d2e4fc77f0b69d7a87de43d94d62a6a6526a7f8c588392890e96f9c51bb58b4f438eb5d197477ce9b160d1c898c89ab408b3c1d648be93b531a5bb4988592c5a8999ae3acbe586d947fe6dd507cddb92dff4974ae17ab99aad5aec9d07b96bd29489876f51afa67570e86b69321d9e565d86001514638403f86666dbf93f18e0a62bf65db333bb85a3ae12d8411aa3c2a423a29bacbbfeebb8a5bafd90436bfded16f992232360211086a3084d9fd1980dd96631820a2cf25c3ac5c19d164cf5ab9a852399491962100ca4fd640146b7ea5460b4fb9e46bf8d23d508a4eeb8a3e9fad8249ece3648c2ec7705a7414eb8e8d602549204cb437f589161fe40de1447d14efa4d738b775d0333526c845cef5ffcbaf5c957df1d8022176b56eeb198e7ad2dfc3d7ea46b125ed432cd04c77efc011a2dad8573345080d7c3cdf5cc160fbc86c4ee1959ee1b8258056b0f3d9343c22dbb2f7858c5f162f08cffdca1acc866aa68e5f1c00b74f66544e8a61e429335adf6f73e32fa87e48e1adf15bb6c7aeacc93713dbc31cdccc9b0e52f922842679494039c395cc1d95eb97ae4df3bb8aba9a2584d97a236f87cb22f00c0a078b045044a5c456e22b2b94a76a559de2672c880660f9785b76bcc2aaed780e05212415c6e73880ca110654ed155a1004af45d5f15ae8e5bfd4817440c5d3d5589eea2c6c344ca0d85d91460638b37f877ea4cbbed35ea75678ef2335a5922cc8541987cc256c8f58045028d33a1c4899cc32265c619ac782ff998a478996be6a0c5b102a664831b395a884f18e77885d860d6b236c52a8066d2ced25432bce79a31b23117f405ef4ebdf3517de98d288f8c3baf04b63b6817c46c14b646308e9f97170b7dbbf9d1a36480338d8eb7466df56feb6baef42cba75512954fd7e33961d247b7393726e46c6e94e156d5776a89ad3e288554470ca0bc4cf4d2d2b0c01ae4fcafcb65ccd6ead03df1d4d6577bb",
+ },
+ {
+ "228eabb5ad8b4ff13b10d13b27372bc2152dff149859ba47d9c89b741d4a5340d8fff5858a4576c55547007d7e2b3f94583ea8f0976237712bd2e5481c3988f5387e7ac2c3f18718388795b7b2d44b0a13f3faaa55311b800301c9203a511572cf8f349280bbabb9424070f415bbfe28aef8d20329ee842cef4d4c299e619b6ef1cf00718aab2accec9ac00155be2903b6fb07dfe98b0bd8d8580176b99ce4aa6be51cf59046c17ce1817d363fa63af5a241d48bcce064a438651af102ff9c6de4b86374fe24f1dfa66e16e51550dbb791af425d8fa601c70c1bb90e1a557bfe0dde730b0364eba9d2018ee751699ee219e13fa8874070935b29a1767e1d748bfbe796fe4b81a71e823605d39fa4b5b885f4610c34d1a090fa4106785e7a035a629958ad1b00cb9d36d171d575268efa1bef064fc0a6dfbae8e532466035a0c2cef96fe9f93b872f0cf804811e927b39818189412868fb104e2d56ae62f77031f0df1ae91aa11826991ca7b8af22f130a47a72cce36ddc319b32dffd294f2e192e490249ea1a6f8437173ce6392d16dda888a98bf685bc91b89b8ee1eabdfb1806fd61f018d1744fe8b03521de4bff86d4a811ca2ecd5be668e9c752a6c26aacc0cc9dd89d112785c25ca6a0a7a5267b4e37457c04a0626c8a29be30ec28ddacf47a84918bab164d07bdedae62132ab04a6f2c4e108eba9ab878caa4a1a7509521d427ad7f3dfa86fae8345dfb5e0d46ce3a94dec84f7880c7422468ea74fe0b4825b8c762b34d5d9b82ba96e0c7dcae01718ccac0044a87476ff031e3ee3c2c13f5f375a841d243c38cd9a354b6525527de1fe7e36a6e2ad95e5bbc4c97e85f8cdcd5341da777e03451838807d5dd2eb4fd15976783c140e21cfc2eb3e58e40c16374de0aecbe3e3d41c64417a472cba18762080a2348ec3f441bf229a932ea0ca7c816938655d0c81b14dfbf86aa600d0c68172fb0046ef51f601ec89309d43ad1eacd583f9d205bb1ff1a37a97b44b5e35be4945f52897eb2a74645b01a7f82054cda44e9fa9f9af9bad1a235155718713bacd08d354f3fdd95858db0040fb551e9f93ae399d5dc53a67e88bcd5a02d104dfd9d824cdd5fe262ed9266fc47b7e640f2c9d9c7a62c6d24b429fa55560aa254a824a0858482e771144d6d5b05539cf71d75bec3a22be75655e1ababec4dff9472a019f6220067374dd49252282e4945a407084633ef9c88d14833bd95335107d36afdf56a642cb739bf0a61ed53a6915baed78e9d74166ebc492b517c7c594fe6564550bb7108f43012551e65fbafc0a9874e46fb64b5b7aee0082a5d617a43b8bf9473309c6761aebc7f13b72ed460b522a6b0875b67353c705f99d1d9dc899870fcc90c632aba1fa9ced6d7a2368dc4dd3d4b38a5807415e00de6b9ea70525a6c1b67d04521efeeefc6c591fc5256d990a1123522864a029430bb7ea00dd80d283fdd6d61cc5b509221e28f73386803d97a38fb0182fd95b3b91353c6eb60ef2b3d5c8c0ab8dc9cd9be2b4cf69450d00e88cb0f0bc9a4be82b71148a37237ceaf945ab94c365625f58171eb15c1bb244a87335550d813d28f241a3296520046e65aff3291555786d7c871ec8a2d10d4b44429041c3cd6ab60f0def742de3d28393c5aca92b150697ac15504ee66d8a2aa01a6c63d7c719d6d4f94af2ed1d8670e3231a0e481095e425e6231c43ad36e3b7a3478f6a61563f5aa13237beb8a891dbb29013c325f7f91c1b055fb83c436fdf8aef49ec457946e6ab7e955427373fd9c743acfd4b9609569b591ec79c7ea7276de103a35a4a8a05c91f59e04689ba1ddd570b18ed046f785d7e4ff9fce7115ac814fe126f781828877208ddfbb2ebc919e6d1f6eb417f38bfbf22ac9633f75e58e560b85d88d0e4fad9b2e68c9ebf9675819d50c30c8982bbbc2f41e02690390bf0e16979b24e648bf15b18800aaef58c3c465f38cfd1e47bf1266c17b69523b7868d2138cb95c4bce0dd3ceb7c2267b868b6e12888d5a489fc0091b295b56a1c328b54fe1119aaf1e6d7dd52fa450b52fbfc8b84c2200ebe209060b655cad288562786673121691809366af37b76567762d1fc24f1fad3128b43c8d10e9b6954b2efcbe40124fc0a5b670dd6dd544e30263a551825282aa06be3817a8eeacf31ca8b25cba011d60b78d3d2462810764e4acb566ff371005f5481c9d36c991527143af2c44cc8cfc59c920bb4a281f2ed4d494d30ba4d900edf59e23be2f763072255cb6f1e8b24ab1d305fbfb2429cff8bda303617c034e71a17230d0e860420dbcf9fea4ab48557e4d50797179496936ec6c97686fe6d9115809e14069244d251d4bc9c8931e47e06ec051e709ba1df526b55d959b37a6f3408833aaac80cfc9cb99915eb7d83e26998f0da2492b986fe0f5047b2cab6e6d33a117df21e6a8ec7f394a3712885dab176a4d6095e5cf75dbd3f0077e5e74b1ff8b902072380cf172562884de852ff5f07c55856224fb3df8eb44764ab9284944b86ab6f176a863cdd0e7ab5616a14692f6cbf41bc63113b27689fc2fb145736aaf2a5b26d2bef3a2a59ef8bb3f3e4d360a4251d0736482e9ed7e189fc48c0973b6649988228c2ac72b23826a61cfa06b11f13c8555be6e433d87e20113eb74c94f0e51719a7b38c59eba300089d06b9bc2a72017668e5aa3153ca4282718f1762642e7c1be1f865cd9b65c6387c8fe496f1e60d5acbb78c2f71cea1f35dc955b1e7d1cdc9ca339765995d9e05dd729cdf58aa2a1451b633c374e5b6c2af1c8486ee4250a875e80e1f359c15130eb1e2575c0c7badb2af61378527fa24347ebb12c10bbb36e3c94619556b2c641d0ebb691b2706cdd667f55b8fff8fb46e3ac72f3682661a4bac2391075ff5145eb07d69d77437adec2d096c1c89208ab3e7a9ea6a0ff4a5bc1846b3683bd7c6ec4520c3c95861a5856b0191e4221c9819c67273c66729728f6035e79c0dae8842df4c0c27ada1ad18b34efcd55b94ef120762e87e8c5afdec80d5788e83f0d1533cdd7aea8f27f33266e007b274f6d48c59bcfad607e8b298be2b17322be88558c60033452826778f167f318b660607bfb2f285cadb385399636acb8f5350d819511b5e7931c5f8483529d3ab3fdb5ae2dde0ada918f1327c6c0dfbbf5ed3c8afef171910dd0169022b3cad5b08084dd5e8eb8ef1ecb17e48bf69f80e3db0ae1cc7b73d94b89696e3c3443ecb4c7ca12568201744d1858d90ff759f2d264d49edf47772bd0e0990c14dcf8c8a4c2dafa44dc6e92f4c66b03bdc4f68f28ca2d0811a433e184cced99a8e5614ca83c46ec18b47e0c7ae91037ae06c6d6d0f3dee19711c21cddafb5869416d23c5219296acda7774891877f3f8d46155d39f43ed10500ede3afa26943b83b800b54a9752250ec6ae173e920002f365d692a9b3a2f9b27124ac97b8e81b70e8c0bb7022d07ee97e962810962b03fc019695b5399f77aab414327cfc5dedd51e99453179c42ae85a42f8e06e0cec6f937224dd019c77c5a0ba32ad08107216a9c758138b730bd5b5f4b613f192839514a8621634d9dbd5840e728c1ef4a2c8bbfadc376dd80d13dcb327ce55ab536a43b570789f5c5e135ac0af79b54232613d0e989ae695aeb358c671ae71d508b58a793e19c58c3d204cdc9a021ecc634bcb0bd6a1917554ea3bd688adab8163260a914fc01d7ce05a497a5c5836cf9401cb6aa35cd008470bdecfb97a511c905badd01bbb4d0c05867661debd2162beeccd52399d5a70a929405293916f33ed0d03f8b850f4bdd77b1fb6283118d71de629577383c81cad086f4099ce7476cb787f73c96431a0df4156f7826fce9045f7e7c97bbfd618b845595203cdc8df4638430fac74a07bc5f773486731d8ad29c06695704cbe2882077a85d543551b7ba81b181ccb93d2b3071b1a38f3c762b42df8246aa64cecbdc772830ac79e766fa99e8c65225f28297a32526df9b51227bd368253737f013ae18435a912bc18cc4a95216ce449865e8bd8bc759dce9d4af52f9e789eafa37023e91946952202dfb7243cab7db2f9f98bb66f19750c547a2bf2e2ba92862ab66f33fcf465ffc41d23f0b891a3b28b3f68ea48dde6ad4802902abd22b0d7d9101bd61471c5d88ee9d9477b7cf9f6ac52e0f520c79278da22938745446f1e647ae478ecba416b941aa31f979d0633efe72910bebb8988de1d0013616f31c5da163eb6c07022649ac57422627a5642618f53103adc9918f9992c5b085e10d2744f9934bfbb994a710d6cd387c325e94278f97d5582864f1bb29a1400aaf674ea8fb99a3b42e4ac50418fd804a5b1471eaac4642d4aa338fd3d5d0dd84372b2c32c5cfe7f319acf731a9787b048cedee3833300dde639cb1386c8fbca4bae8d67fb7bd72d1696a0212e27e166e6b04a79e34b47c98502ed0bdbd8d61777537f72df569fe5ed30071b57e8724e98ccb88c07f0458cf32298cefb6ed672b255e581ac756789b57e950d57174bffd3f47bdbe4b168e7e3f1a6df508d4202d327947facfbf9526a9e5fc1a5abb179902d4584deae6cb2900391e080d3f3540b87c3a873ccfaee5b4aaff0e6516a867ea00b4d5e680fee6b91defc65c240614a1409bdd0f49c2c4f3c1d258d77abfc17a749660f49547adb236730e5a7a22fbbabdd8ca079a8efa5b605332db12f455868ab67a1ffd27d1339bdf8d150189cfbf6199c6fc27c05788138a63267eb8ac086e27286b4ef99ee9d92cfedab5ce9916675f128f206a1733f47a597232067aa12da20c7b9cab6575d7634f8c31e9a29948b528681f3f9c13b9f585ebfbff8c28a299a43e4409b31b6c02a79eeb493734fe5f9c1d9e3830572eb54229b5cf525768f695acff48c76b4a6e0936b7406ab69f06d33d3f04946db9d7966ea6e8c50ede5abadda28149edef5223a6938d5c32933070d234043feddbd65c81be218f9d7c497a1ecac30bb9162e60a9bbbcdb4fec4b212050610e2b376aadf58b3c9207860d2650d0310ae6606a8f1b266b6a13b68c3306ed413224abdf19371bac3ea1b964f28996fc70f666ff118c6a7c9f2108d327f5145919c03832f754de35f5979ae72130e39126499037d6fbb3751cbb4843b05d9dc91dd5fc1429da491f72e3069313ea243933b47109af247fcbe0c70f9024ac5a41815655ab309fcaa282d03596ba59cfee0e40f7bd657689453e98d562442fa4c585f970b6983a581b0b8eb1c5e780b3f5c1abb326213c6b5fd440c2187066ddf55f4eabf88804139392c45979440c6f05b7222bd95e963832d7fa4a4760273cc075e8b8feeccb917e8feaf7d3f766d9ae880487e69bc01872ba62b91b8af5dbffdd93fdc95e8f47ed793fc070a5991f2e9ea61439662dab218f643c1959171937aa160008a548f51f87b58f2c4fae5aed556f26bb9cd1dc2b3518458e2f5ec5d974c6e11a0ed639958cc8c1db771cc8cc8bee8727bf6452f47c9782acf548856a0e67841c3dbdb1c98572a4fc8e6cc8195a504019b4930d302a90dc20d8628ae6c90e0206cbb3d05025744db4e115cd3b650e5519a1624acbf226ebca8875b05183b2584e65289f8b9cec3f7d010cb9671a0e80bb70ca8763f1722d79e8decb6b9023baf64b5981e745c06546cc1e",
+ "ade72c2ea29cf829ffe99c2d63840b2eef9b51a9919c02128347d2e88e9f063b86326928cf6252ce4beefbae7206dc61a22d0b33c90d464d551835e3b73c1e3d6e88663deab80c35a607e4180ec079b0ee84e3b7922904e7423acaf976e837",
+ "43348cf32211d7daa300de8a4218543c8e3c7373ad10950765c39760f80b733c",
+ "e4709d225a552e90fb357413",
+ "562050bfb40451f27b1181c389508550a0f46b53d14ca73143da9dae3d3d2b466e9618db39e3219675d2b6eadded7dd9c741d7c9bf3c5619a521189607acbcf6b3964d469d966fa134444aa06d80749c873f0f976e0c5efc5be8d00a2729f03eda6a7b8630575df8b3a19388ff88daf0d00bb3e7c35a525ded90a4511ce815fe6c8904406cf72d7bfa14ca533566f7b54268835285c5402e22a63f98b5d90c86dae0a76d65eacc1ba85b3f5a1499d5f3432dd5455fab9e8bfbd266e99283c2bddf9b556410956b2f061603d1fc91194766f90da841699ba7da3d53ed5abdd8e98034f8fe734446d92b458a731aa4c578552ec1ac5d1baaccc4153a67b48a290602d5f955d61a08436b27cfb0786a80afef76e1266310a42d90feeb3bcc40ae5c4506432dcc92f7e5758ceaf277255401f5c5f4b10df93a249e38edd9effe7bacdf7fecc451d3b2cea77c9bab0403450c41929775b8c0ace46f6928f4d9cf3adf86832d298ea32b236d3201464e2ff506ef01da0e1e389e26e2b3ddc553b369b48d1aa5dd43edd5cab065e276aeff72a4c43206063fc7eea3bcc783ba2221f5b615a7a43a75cecda6bca5aa159e9208bf66af61e2e465c2daee630c4c62077ea6ef0e8b4b4e272d4e93a5f5284f9da463e1a60f815a8a31698ecdc09dff2b62f00e37aea5fd4b07a110cef27e12466c1814d3b10017cb9b8e12f2f38f10cbe31296de2570d5662b16639fcdc05db81e0d48178d055ef873501148d00903ec771400fa4873c5579dc3265028f531538f6dab1e5607a15c8b90cbfa4835107cba6f453bbdc71d08c7e423f58b44be38a9c8a610469f2551ee6177edf639cde35fe8e02f76b7ed106d691a876a4fda3b42d8ace3e0d3d4e026206c5d7d4d56fdda9dcd30fd7b74217fab3c617903f1aeffb8363443ed128af94c391810e327704d6f655e57dece97658d41e074029823850ddf7c5937af41c64465046d8544bba65c691ac69121bd272107f7eef8cfdb6a25da5da16d1033cede09129d51f6abfe63905a6fba9a64d7832fa35825447150595a60163af848eea878fb31a5fb97b1859efbfcc8586eebce8cfe64386461a9b88aa5efc1db43c64dfd5d4a45aa74803fd178f9e16a3f59acfb6e13a564d645cedd73890d0a82fb6dffeef527694a7cf2a89aed9750c3675a67505bff77de8d046087bd39a85c90aedb085e99baf04c7e3bf92e350b332da1b8af85550a00d68904ca426da61add864496d6ff442bb0b848e9aa463bb0c2085cff1a83a47d6f702bd184cfb5c139752754c8978d27b58d364bd88722b9097ee3a6ae28eabb14ca7c31e40461101e92448dbbc63b55cfe56efd078d0058c5e6146c73bcd949c4b3ec9f881b9a5f7b41ca83301261e0c674f2d35d96761baa00ce0675c082bf73dc52dc726a3e605067569a372d2bb47fc8fe1e74f00078ce6f352a6d9d97fd2834670ba3a45aa6751eafc7ed6694e1e07542860c8ea516f296ee901a3ee16b00b40419c74bf6db12c7230325e85a918f412bc2f6469c1a13a5aa77f028e327749efd05b91053f49d9f1edf49aa552c58c68257233a168db60ac55b4086ddaea275b078869cda7b69493c4b371b4e9c8361357a7ac7d3d3bbb464c960addfa8df2b208b21b090d540c440241598212d33273203d484e0930e22469c2a8e866579a4a2b3db8f8344dbf8baa1b97be0c4d976f6aaf14cc09ec52630139b894b2b6f4dad3a205a7b286253f1522b1d6e43bfa37beaf06f831c6f0945cefb2593b9b298da13b0d910582086c5d7e256ed4067bfb476dbe01bcddb437d46ba716d6ace2ff9912c8e460ad33ab3d8f97b7b08dd4ba9e01968d1949ff85b4b9d5b8da291fc0f90ab1eab1d246f67d76092b7a37528ceb388dd76f8a8f0aabb7490f02a2c8bc6498cb26350d859c466dd611bf0ceb81a8b7899c67742c22697ccee21c4963acb003d15c1a2078112bab05595917584e417db3872a0ff0a29138bbca7314449b19827525340370d7e48fdf9f7c6b4a280e78d00775a291081a5e78e7a00ff915015dd5af5f0a45690baba8b1b503bf85f326c23136f4424be4a559aed03fbc81400ac27a33dadb2155d1704950d98043dcd86df1eee78f3f266c4d14deb8126708f74b59aa15e8b497c6a52924a473f999aaf0abd3d148fee8503a1568efec7bfb0bd463402f563e4019cc9c9e1eb498aa54dcb659f43b86df0a34de4e51ec558bbbade3d69511d3fea2baf44f67e85ada7398d7f72ecadcd9e981f82b0743ed74bd33088ba4cbc85b0c99dc5382c599706dd2d51aa9f470c25a98e7e8248dec216a155495630662bf6ba0b7a4baa2cdad30e9ce3e1a65e3c23d69d5f946606ee8504dd70830aa5a8ddd84f10e064695469727d2efeb46186c9d3b7a170057636f05b9ec4c2de7d935fba504a1e7eddf7a5a95226b253b0b9eccec976ca3c57599850db40c27a51ae755c1f30d392467cb74e5c8235861d11d0f8461b0e1d84f5718d64ea92da62f4de184a6499dba473e82b3d197305de0e494f118a263237c7b4c0652327977edb427ccded35552c00a5804b9557ccf2bca2484d9da2c33f6c1bbf2c666ea10b4644a21e3905e5c4eb417ac3572e783428d23dd7222e75c356b99e8183d033034e29e618c90e66ec2f1e9fca47d82c1cffda8ad14c96045159d9437e91ecef41d24cff89009ff57e18c1a422860aa9cd31dd2a85b07422c72a5decc614a9742e62a4988f394421b6918e51c2412d749bb53b1e8fed7b2ef0873ffe14fa77bc366bbd5fa1432be465f5e25266c6c12b55df1f19b1a491acfc5c9019f122c422243d751d8eaa8ff721397915171556e999b34425f7d3ad6f6c3323b8133b4618c65ac16cb5941edc979472734bdccafc73c08939c0b1e306ae3015faa9cfa09ed6560269a1dc54c2c046a12a178144f4381f7b6fd3fd2d28f778d444d9f7a0dae00ea96c6969b78ef326a962d23275f1518f0e6a2469440612f3710b53538fe99a6179471be8c5b2d682ab3e9a5126e41ed6de000cd9e92fec3974e0f4cb2d2245d03d6ee80d6a793b16efa829d75c796f34d4e918250f457703559bb48ff78f0896be1bda403b7f1fd6a319d68478ff70d88238f2b8afc7d20e51757bb9db3bffb35a8040fc0db913c4f03d48619af7fd24cb8986b3e139058be3cc253b3de9b3bb3f8dab7b8818638279b2e6a0c29cfe16fa7250d3c74362ffa07e2977cf562140fe28afba8f61d81f7c73bdd4a2faddb00752bb049d0a57d05c6475c7387e6716ee31974169930c9fd830cef138659cf56f2212de185186c3d683fc6b7fd36e7821f69d0de041a569765066dc4a1934870a7b80f174e8f9e484942e62404a42b21658467873865ef94fc262c231527f39e82dfec91215947b99567daf75c6a28073ee4e67d4307e4b35b46f85433abd9812f35438b34598ff3b6dbd60b60747ad64565391df45ac80b272d0141702ab807fa27c6a6ba2f42c3facfae0c773940cb2943bb1353b41298258bc0d07542b69483e17ab9ce709e4160b80a0968dae9af8fc7c0324c753ca4a11a6df32dfa79a87b445c988154bb3c503e6884cf6d8f5e062a16b4ff230fbda109a6127d35e3bf2b29bfd3b18ba275af773b1981d603300035e046ef023d51874aa105d136bfcc9c7323bd0513a6b2b397ffea71afb7a8d4695411d86164917099eef504f6cff3c5cefb88f23f56c4ae3e2b09a3f353fa55630f45f06c29e8912e8c3c4f493f25eda781680585580595bba43dca9cfd400d9eaf5081d2c6697da59e012dfd0b875336b88fe16609c2e9876737b9afb868ed52417ed0c6b359d582d585ff82d98edd4e63c6b65cf43d4f69eee2af4819157b8a433966953862d1ff2c6d0cba382644a1b0033ddb7be3d1fa9a204042d7b821b293bd659dca980c108ad1db740800b9bd2fc1a163f9b4066f7604f160a7910bd947cb48ce6c81e680fc6571ff0cd12a3ded9c8cd560970ca5cb480a70a8322d5072edcd257604eba8dcf55f9ec97ea2b14fdcc72fbf615131836fb14e42b8d7171d0a06d2fb3caec2e0759e86b0d8f21e312d9211ed7fe0b48669934ffb892baf1db9aa457c07820723e5446420334bf6479f2099e01ef8adf273adfdd9ed0b741931284515d69c211cc2efead8339e450b13be71b35c36c1f00c2b8ed0cfa9792e422912e14b5b1455ef6abdbbec0035480c6cb69d21321d12ee19d528dd48f43b142cf0502eae5304ce52b7fb827552db9ab885b93e83d56a33346135aef11b7e48efca7cd52e2499a7edab0bd0562862187ff4599b2446bff11c37181092fbb05d0e05220ca6bc37f529d6599e8c29acb9f25616c27df291d4fb07430188e6470df7002f73cfe5fe6907dab0b4f90bb58130fe90241c29c6063a22c9f45d032b282eb92c93736692bd5cbde2a17552e942b595b08e6ba0c91a03b9079e9117fbba8f26ce6c5d0500c69bb6e22e3562a50baece49109c2d42b6714250665afd0f0a7e951182012f21aef4b917cd434d9ca22661437608e32666497516be34652500def6c28ef8f56f2273de5416142ce9606faf7df92ab779ed6aa74cb99bb1bfe758ffd344e1d31f479807326d1a7b98f6811e275545d69198707b0fbf027dc6a5e4815d62ef191535569a452c27c4e25ecf139df949d70dd5935bddc04f33b2f0bcf5073c51fc51c15067963a20569b5659f0e7413b347d6d5ee38a92b7e6e656c199149f07ebafe5281db6b1b2ecd9e0384b6f5a8e27ecea9a0249c61b16564964054f5f9621471a98de132e102f518c1419829e2ae2c8c5fffd1270f0a0b33a383437b0034783d50bce8bd7420c059d16364eecbd55b6ac8df8a70382734d8127f4f5895cc9e508b13c000ea053ab59b87ee639745418ffc566ceebad37a17b842d24d3423ac3f086142c622eceaadc4106f8c90c5dae1f52f407fa0bf1e6bf9385cbcbf3b61006ea3b1e66b693ce704577ca9598587f41e05d36d1de424e0e51290a5f2e2f99f1960c0253a046a49b19eef249ca2dda2af1e8dd78411088eff1e9c23c31bd20abd4fc9e7eab19500827d202f76270fe9f90e95309516343e0fca48e5a12182e91c78ebf2cdd4644629afdc90bbccb77546cd765135910ba1cd8a3e3c00fa77e585865e898bfecd06c01a0a4d7be483801099c61941c4967154af5620b171b426cf229df59d2944ba50754140c3f305c16956953be376fe6e7cf31a2e9c276bb09cc24c4b86b2b26f039b0d8511853adcb7feb8502e7641a34e3242bf2c538006bb1983345ec3cacbf219ef10efc1681d52e6e1b1c60bb556b6b8a63d1d1f6869077841d1b816f3165a35833e33d39a8c6e62a2f7c482c395768fc6a0e3cbfc7a1a6d64da53adad66c8016f76eaa73df1b8ef83012ecbe75c92a8e39b48169433f951a539b28a034d5fdd00639a5e3e17ef14dafe869064d130c90c68be4d5ceddabed1bc94e97e2cdf7313f780cd6e175a9e3eba3eaed896fe464073fcf07ae7b5bd41d58c3160f66ac95a76fdaa7a8cbaebb304fe3c8f03cef927a1182ac2281c3b32378813b24bb99e42cb0774331ad78b74d46b8ce48bbf4ef8431a82d4240edfd61b910c38570ba0bfbd4a41665117e6d5f5a97908462e62d0b76160d06aa56cc6e17aaf4607ba8263648f2a0077e306c25486f5f39a75",
+ },
+ {
+ "2f6210063cb3071b3d49339185c2cef8357b08ca826d8d1acd852540c16540f1c850f70404fe1f414853d3cd15a1c64a1cce149e3ca1b80926de4ae8438ad90bdad010decf2f201782f3e49794aae1b079f54eb59607bebde508a528927e346d4e444b1d736b34f65e198df2c36fa23c64f1f1fbf8b0b8ddb85d054bdb39b8297d0347f16f7be7cd9474c058e36294485386434b36fb28ee582e393367f15ce5f5a3d6641fbd31b331f10b1554a05da726a0f35c9b1b4af3498426b17582966a266cce452900f85af1046f45a4ccedca6ce02607fb70fa45f420f66aa38cd4c9f8a30e21a3067b940aebdaaeb7c77824a79e2ba20f26e70346dd6de96942b261e5c08288c7fe1cd1e9f680a0bdf8c46497f007a616eea95ccc17463559f8973eb919c68017e25100d9d1a196ca65fb615502076bf0b0c8bcc70ef22006895ebfa2243fba0791bae0625b762cc1718d1673948264454a200c58122d5e9b8b1e3eb05df8b7eeb297510e0d7dcf7f0be5f29f6756e4b177f109891e6825a9866359e35b10d20da7231bb5a0ea34abd0264b377d2fe9f420f27d3e5aa2e8e00541c46052966ef9b989ae5974e2054409507b867f647aa057f7deb19ac6929f0856005aec6e53a5f702fe6be403afed532b73d38fed73e6e551987f182a1e20801e7a6c8ccd1184cf0fefb4139fa166ca15395902ac40e7fed8661602853682a3b0ee307dffb44d0ea3012142a2880cb7c166ba6ea6a16c7e0882808db8023068f060e5ef1432fdb8331ffad6a7078d686d47d613e94291f1c4117e7c13aee4030fcaf223fcefdb300ed606b5dd931e4adbf45dc437eeb5fbff337812e15c15f026071423f6ef5305c559baa2ecd8ecc7cd498b043740ff3673774855d45d45fa64591d5b4970600ec91ab1b6f39d7dc0e709c41e49c355bd3b9d120ffb57095fb127bafa971a086135b917285794e83e9dac5ce76fb1a4aa4fb6b94a0dc3a9beea64b8817ec1e2b37af9dbd18ec30f2b6f6c12df1db6896c6c43b67a066038f0c4f17142b254f62c4dd1fedb950d07047919e397d06d033cb0bab6b61aefa6dee01720926b16beb9e8bc947dca9b8143b565da85d2dec182987838b267de9047f5b0d961c7971aaf54ae2c1e4aad61ff123c84e41a4566b2bd9e64247cf46b72a444d36bdced1a309b464ee5f4afe406eb68eb05ae51b76bf01b906c0ffbdeb440b11f1c9e3a4c3a809a1f7449047b356c663a1ab7f286a70d16141d11f2d151a4f06d422ab97cab539c1f9da09ad20c000c27b8fead5f0cc37329d466fa260aea934c154dc9c0a065df3d057a0f117a1c38321ae59226a8054f7d6b49a3753436c249838b0924f0e861f5627106dd8d3f0fa724a1cecda71d4a1267ed889b234ae4a7d5edcbc5d52cba389dc0152aff24d224c6a0f16dbd3b7f242807bf4b51a3f22690bdeb66eaa59e8766b3b265d784899d247a0ae1b58a06dd91c529e3691b09f9d9f55fc39afd4a00b0fc668880ef25a46a30861fba8cfd4b51262eba4138b41a2d13ddc71128c8c1242e49a51d6f49879fcfa7595ba4a4adcad3670b0b1b26382f03ff402bc70150f54bf513ba3e9a590e41b269e55616af297ebb3499e16cc8e46c0810330a602955553c0f93d668a1181a0bfd7021ad9a9f68ce39493b012da70a3dda149d0369f23f788616e0272efa322b6a54d804f340d32c890e2eb7b538f48f4c9293b584d22d0ae80d321607644271b81a76ac5b49d8e457069b0c3e909b8a222e3fa6016cb1e979e300804742f2005c68acb7b1849c088b3714c9c7af54e9de9390df0041c87924c8fa6b0aec6b6754171e059cba0d27f221f0b9d044a3aed8338dd8745651981e4b0329376f908b86ae9022699d495bbe3a148f7eb73d56eacb2e5e2180f63fcbfa680369f88eefa71f1210bc5b6b7b957f0a1437476a2112998033197673e470dbe7d9d476c97b95db8b5136f6cccc75d6e0ac1e4ace30e34e64fcc4d7e135b2c80e863ed701d3b28c25e982f1b5f8c895a4e6df7216c3c07abf8551a0ba0469c88aa7a08c7b5218a03b9b91f0935985373f65aa56286ad0e7ef2288a926f172b098123c136455b3a0f04590839e16bade7b6434a3cf048abe2612684c03dafd9cec39af508e63f07ea881014697bc24122058b5ef5d3fae835216d055f0cdf1dc06a12c95041d13ac9e15f235d11747f16ffce1cc3b8f508da520e395edd471f3759d8879ba9c2558b1188d822fd4739ed0546b0ce3bb9988db7c1dc8518ebbc62c4440e6e0653f917dcc13aca1864b71dbb67dbe7117474c936414e4f3cfab1f13eb05f3504484ce11977ab21ec523f97ba1b7ecb8fe384b634c30561cdb752fc67a2316bfa7e4d03f5f825d24a556a0460d8cfe0cc54a6f117ac52d553a5d1bb48031732716436675c5c3996b1939b127c6b0338bfaa29c7467cac9a127e455a715c9ce2b0c35a0d2f83a3d1273ee39399e6cc4980e610c752bd51652b96bf9cf34c7fa41fc9b13f5d55007483e4082ddac4675baa7822fd257452411b01de0e5e5da26e17539d64a89dd93c71d15a4c95b1a83039cb2d5f3f7fa04a817e48dfcbfb3de34ecb47f7592123caf27e17982fbfc8597af5b8aa6558f4e6c73db69328e47677afbe6ef8df82c3d1f0db6a108b2279f61822908d7b856432c32ac5ec0f3c53befab2a7ca356b9c2636f646b228b0a830d348be4ece2271814d477d4c73c0fb6e83a338b90ec4ef45cb25f7e3d6a014a9e8d2e8a6f55a383291a57f15667a73ea1daca31c7182523ca85a107efa2518d2f7f179ed4ba21fed479ef2be09669817133b2384bd85b155dfc1c4c9e6dd9ceecf06cc1ab8ebf7f07aeaae7441468b5471aed93f248a84f44c59be33274b11f651de010ab9f8fb24d3a99914e0147951c34280e7dd15ec196f9a4c86e55e7d373c7e31e6672d1b3ac6a45fa6c8c9088c0b8963d89f4ff1feea3e85cf9cf2f6c97128afd845bb131c6f62b3282bbba42745080fd457f1d3322058f1bd4be876bd01269546d1a853310b165926c1fd4e07054deb5d3fbe8f6007711d435994005aba95918c3df4cd390b165fcd139dd418ebbf661b6de57b655698a8a02ca8fad73e8c536c7110957c36e5494a831d536eccb97a2a9ef58fe58e2885aad170720ffcc57c7de601ea1cf723577a30aad8fd544317e33897c8b6c04e5191bec391ab990e197f10038c0726d371677e4a54c28d7ca5c6046e7cc4acde565b91f7f72af6109a0614160d3ae97e9257b8f71a4663b00c681e793cbb478306e97b0e04711eae7722b4845dadf2fff5bbe71ff24acffea2ee67df99bf62a098ddae9d4ebd3bc5dff04a2d9e3d1d83e8f493db3f63c9e24231b1dbe1147c79f21b0730c842f6983330c5c17dd34556d7e932074cfbe98f2dab5b0ebfd778a1e28fe2bac2d942f61a08b787ebfcdeb3d600bb130ca4922a4ffd38ffc4a1a1a7218451e45da4da67ad81ef898ece3d54cef877cb9d09f5dcf72eccbbc06e62f1e2b4d64059b0a807329780b155ce1614b68de04387d6108ef4dd3ab54b9da72e528d6eac3e16a360ae3421f3f23808a8b5e8ec3dbefcbca3c9f76905850033d78d9283bba9272c475b4e3b4d7643e62c2cc259ebbf168f890de88e82f8b26a7654ee31fe055e45609c70ae02b4942ee15678cd158f4c9e8d351d102ddf7a942458c6125e1457bea0d86ca38cf0c26e474b2b5cca77eb57ad0867cad7d25efc2b250e79396637ea3e948dbb855029cc9b452955bd04ad5a0d0514d4d773c0f298df7bc235a3ac64383a1fbd8a397a158e936b3ba81895a51daa89f51e4ae7a71a53794ff715a42f4fc3dcc9fd56df7bea4ab782534d3760e7b15605fc4dad16911656983c0ab77bce9445bbeb1537c55fef57a32c8f1404306a0a2ca7b73348cd99d0f9948875531cbb0ef7c036cd201614c33293d746c44140e0e8f82421c5bdf2bf428b249597df949fafdb5ccfe1618323f56a6ab9abab9a84a3beb6696ca918af244d34cc1cd95bbca4a87c860a0fa9ff6a04a905b0338a53f230bd5ee9c60e0e0332ca200c15dca0be5936b858d0a7b2e540b8958432e9767396c55d5cc35b60062580023b5cb2f9a5e9a1feba59a19f9a5a251e9d0e8500955a5df21da95213ced2260a2ed8f3d4b295c36cef750c89cf21985c302d5cc577aab7855409a912dbcf1d0a9800df4aa692a78607a40fd6d5a82305c58fcb3d2a82b27e8c5b91681aae62a2bf31ed55c494dbdc38eba30e83c6044945df76705228eede8470369f2e9941ddcb2f239fb3ff6bfcdb0efb5ec50f981adf0e8b213769ffbbea364b08cf8cd69abbfa2a6fe9865cc48558134a57bb5526b9d047e14a379d246de82d3d64f3c810ede280c768dd8bee25af287d5a8d94045ddbf5981382bc716ad9aedfcd66e0ab496172a24efe80649db8e1e83675fc8451e22c6564d8d6dfb285af7fec802b35f19dd8308c68952a11770247fcfecc4ed0e8a445c17b1573f0b4e3ed350f13269ceb572943fc435563459d5044699f1542335b03be6077af156b8c5a6a9f71078ad820cec4642427a9b187ee1b17036d5a5e6108cee8a7d444342eaec3afa64e77c71d3c2b3153d4e2dbb30df2b66b4d14cc45d3a4eda7e911d697e5763e23ee05311a20626df55549b8533c6ebe79737abf472f9cff08bec590943bdeb819d3f923f45b81f9a0cba1f3f800a261842d10cb4cbdba456c7fe5f0abb4a8b58891d97cfd6b669e2708922f1934809d51a1589e5f12e3bb82c9ac3e7e44e3f6e6cd63d428da624fd2f46eec38ff798a90d228efe50c9b67c63796347c8a2b53478f27605999a03c8e1f18b70e92419f646a7f49670aa12d324751aec17d0208fc296955b3098241189af8172d39a6819415cafb107c1842b369f174d6f37dd31cd728dfd0ab10f93609006342b6e4d6ccbfd1ed2bea2fdf5411442b04b1fe218916f159b20242f80b535b4e0a3024c6eff6a40bd0d3db24e51f5ff9c14e1b4a650ca4170ee70f0a3a5a58349a7d0b7a63af86347351696870b95231f76d8c5c6a20736907726341dcbb76672871d18c2157c094b929fd29d34f5bcaacd82706f89a60000cd341d98eb830b73a12335b69f3e0131ded3ce12c98bbd960d2d0696d40696a13ab43925374498d868cd8f070c9039ea6407fc2d92b9c39fe7c935bbcfcc5c0980952fb7dac79042951f49a1af828b138a87401c4104bc28cdf1e39dbd3fa63dd4d5f5ae9d85f032a43ad353bc5e6746e5a76326ab1f4e79103116ce70bc0b459200f32f85e461291e347dda92e421778b849e37a3ecb0b31ec6818e828dd3148dc74313aba43cc9d8b9a36a9dc4e229488060eb6c109f8ad6201958adec6d3bb3b04e5e558a272d44cb98e18f7a0ad8fa6ac3667a62f150830aa930f6166baac6b9081b44304988fbe1698a5b746255de26bb5988aca90bb6523cad68a7572f615f4aa58f932d8a749615cf0a7724e99de042268ceb31433e6df0a61547d576a6201b36b348c028ded5f7e94d1cd2eafc141088ff42cb3dafbbe4c402b93aa9d955df8d9d9fb57c75ac65c2c837acc44bbd4d4aff1888aed46c73d625ad7fff035e8ca0fe411c73ed8135b6b8e17a039ec74e9de0d64cb442bf8a676c0a666f68f21066332cd921ae0ed766f0516a8e19b82cf98e78add0373737a3419e13aa902310c44feae5fdf8bc64e80dce772686a31f141bcce452041bf545b908ef4a2b000e7beaf378e2afdccbbcaa42e330e5024400cf2852d3444718",
+ "fd5008477b0855f6f2486fd4f74b9fb4f6e19726c6996bc66893183bd76054d5b05c1c2b64722256ba912ab2dcca66d2abfdf972966438fff7513acfb18ea461eac08c4e32aea4ed3fcf9f1c9905ee4402e7b6984bef974340d212f160b6524b76de99a98d3e96cc0d35e8a63ad7ea3cbea1d40a906c4dd03e5fc19e1513e9",
+ "390a5e75c9ff4ad38fb6205ff47f209294337c1f25ff54a3c01eee8e1e220257",
+ "8bf183347ec1ca4bceff3374",
+ "19fa2641519e21293094e9d767ee1237f9e0715dc57172794867c3bbe2cb647f9b28a8d3f85c0ff557b91bad66f5ea16e0107757b0277fdd3ca05bf47c19bcb92a958a57e8c142a51af29bddb20af84377b6db65f77494e0dc4d2634a776b3a5d777319873bc0dacbbd4b9ebccfae849fa7e9769cdf54660ecca0d5cf4fa5190713726d54d02b3a3f21857125b8a808c0ca2f99d11dc430ed5113ee49ff8f00bcc08f0370dd510e8100e1285659a7b2c7457a6049f2af7786c4db1471ce5bd164e11c7a2165e83e03a135ae2b3429f82f677de044a067e99e0bda2d65a7270d629c00e1d528212d3aeb2896e58ee5145a93ed06a9c00705ad5c5988d3a192304c1d17661d45257c5d16799ef70771964435b12e3b2ee9d5b467c3b1992f45b7a59871b40d8daa1c280747ecb3d170257b91df1f549ce6d66455b5b6f60b7c6e95c92a67e20cffe8599ceb183de53f1dedfe19bae836447af8e053ba419660e0912cad064d6125b9e978e8d0d5f28f8a4e43ca3cdf2d4c0e9a11221d8184e9eb6c90761b0beac82d0d22793279aedb1c7db3632adbee323bc3bbde4801152694831abf5676979af26af7dcbadfba1cad1306b635840cbca76c558b37db0803b4c12befa27d16f21506b07ade4a838d6beba1816eb29ed5e3c4f132a752fc747bd9ba879156e87e6c1584e911da9f796e1fa4a055e427272559e4bd6d0f54b8257100f8a55d84c27b702bb1fe2f995425c85fd48b0a0610db5b39f7a5031407a12dae9f508b21b1378f14952d1beb2dea81d016b2d9b7f1a67b814569b69c0e619adea02a8683242d63a11d3317d060e5b4d85df5ad73127541ba5314715d187990735aa81f438f8b94070ec506ba536274d98b766c1694e54367891a602b99e370425b47a70b819277a249fa429c5bbd0530267f987e6022f25030c30f3baeedc0d13c95f3d5e4b2b87465d179a3a23b9f9e76a42ceea55226ce072f9488392f40621289124d786109d2498e74fb37e2ef466fe8bf3016d96e34204c32978775765aa80461cac48518157f86d59f6187bad4ee62fba1ddbe166b29452f4a59af1e057300c353440644a8e40ae8171ea028be2fa315804abf518847c7945e8228b7766cfdb08d3a3116b59aab8e94b6d8c8c9ef442c2dc7f923bc2cd3e5c663baca7dded976bf191fe36da16948c89c385fe71434f4aa5dd15fe0e925d2459e3b068b9d82a9cc8b8f9786bd9f5fef9baaaf2d67027d9bfd58bb2c58ec7c746b747ab62f9242e4b53ed14d6fc75f5280eca0de23717c97a2293826e19cc8eb47f946421516c349dc4ba49225b91e4e868874bdebd373700df1f3792aaa140597e58b88f90e163397dbad3941705b53d754e3e0c9003df836a7fb8d23f40362fcb5f3947a4281b24240be4ee89aa8e917b194f94345eeca224df0adc15f22a617b6427f29410bc48ea3f92216163785723efc36301d23ed52780c6fd7924bcfaa03269b13582b7c7ea9c0e4a451f38a469fbdb585dcb7c81452da77945ebe27eb26ff6e8c7b2decea289aac5af74746dc257c9bea44a0847f02c4f586e1d76f39d5bf952355a0875f177a666d1d354ad86ce5ec0aba2c2b20cab050eaffd31095395132f5af80a2d2d53b77bda49f948bbb37bdf31c8a690476488e14e542ff6841e7fbfc2eb84795696562d079dc1612274b6dff362567084f793f0bc2dd8de23392d05aeeeeac6991c9f74387153a4b7da94790375e336a00c8293bad0fcef2dd1880e7094e2e53f738247c860780ebe308410ca02ae409ae720e841f48c9677acc6e7d4ccd18c219c400f8b7e1257f692e09eaef96802b17a1cb7d93eb81d3bfcbc7af4cdf05b98e22556b3d1a8b56d6d83bb5f5724696f8f329839dbe477483ec3c09fa2e0628faeba1bf285c224bea3f6cdc7bbd768133c6ef1da14f248cc3b819b196588811b073a7291817bd1e89c65760435d8d17cbf9423744a92143e0f956e2977b39c54fdead5a57f3a04a0facca01bbf44d3b1fb9c4fa83ae1046985e3f26aa0a437999004dd8adc04c5111759849f919b93558dbc559173a23b069b59f800096d9fcf077c7640f59170bb9a6fffe64778bac272365d27ea62aa956559e90edd3f6393cc8775597bcf7d91990ab9511973d948324a27261059e93f4b5dd2f70caf12e1a08e0493cb05588618764391f355379578cf94dd33e616136eea997ec11c0d4ff064ff51a767e5558433a2e3a9a74c232d8e187f47b8cca010709eb9fea0dac8f1ea53bf18822e154ecd929c83b0eac366e30fffbd5ba6a46d734f58d26e7f5df538e18b3d827884aa857a680823131bcf30a76f1a555bcabb17b02b53aefad96fe76f7312da69719434c580d3ff1bcdcd594e6375935003d5d732cc577e11ea2abb1d04259f50aed4c3af9866e8c4a52a09809046ee330f05c4403acbc297a9416c5208fadb31ed4eb7a3b01b87bf08c75cf44c2b0df84df30872d021d6567ea649859268e5e1b5b6405e1b41e350a32c1af13722959c17c01b52c42241313b26b25995a1c89a53e248488724d280647226195746901929501df36d1e94815d7fe6c4ca2731f3181293217f71b9d7f59c2474856972013924ae4796db4cbd22d8905a6043c959941ca6b556c53d1688c439036c715d33a47a7dfc2fe40e53424c5093020d2e85e4b04aa4c704ea5bfe5a2384878da38319c59d41d66b6add2a443d9ea11edd8d18fa41004251653857733b388b453943eb33df93dcd5d549757fa2967ef0f9a5105836c48826c47fcccb2d9bc349032b286962136b848632bdcf186a08cbeaa52d195efcfc3a440bac154971d11ff4994f293b14fb8c3214ebe7ab8b3d0f2fe0b03ed7b145fafd7730a173e3cc1847f0cdf2cf629f5ea81a07bef716b1a67dd9e3b7a52fea1aaa7a393f53b5bdb5988df78a57a9dad19a8253316835acab8a6b9a9fb42d97bf29b2443322f46de386fd82bd3453ed68e2370c6eac4497b1bde7b42d569c452f377bd38bd50fa5a6792ef5c9ec6c647001149b86fedb3e2f18d4271e9cc4801aa16ecddb31b6a795fecabc613bfbc8e4f5636d71e74595c841fd11b6a6bc7f169317c1added56b82a71fc36d774bb4d661685363e9da5fd2e1f357006dc5b5bbf8b42ee3f869e75a541586fba558a8f490d641b78c27368b9b4c2db046354e9358ae9140e91cd95ebeffc6c0d2676a3ff4ab10d463bf32bed97023a80a79df191ab9858c43537a03072a17c30b1bd99efbd361590ed6b7d5b0ec4e2326fa35904ab9a48596f44491cbbc0112890f9386ed04dec30126be359a05e99b2b77fa2c8f6b7460a6cd590d71c73b2a1b23312ff89306b6e41c76ddc0a099bfa79498e36ae5cf0c560b8854dff32d2b690ce0ac4aabfa723ac6f2e97ad1083235196b464ad67fdd649aec01695d55c8b4bb198f30630ca635aa5a1915f3718341bcfd8b522f764015fa5479004d28eceea7fe67df7ee24a97a9708d528b89589f1899f13242a0d00f7464c3cdfce213699340e754533b934f4a8410224e111f31cf8e54d7b5e90cd8c68bf96edbc8d183894deefdf4fcc1a83162a3f6341dcd9a9aecf171c0df28257a68b1af1b67c54c43c3cff27fed89cc64bc46e23a49ec74a9efbab7981d9f0a018247441e4f0f5b5f68ba9325582f92de4cca4a5f878a0c5c387581e64324e3246d8f3205c838a29f1abeea24446e496421f0e742d411adb55f70272ae4a992e825a3d327e44b8b3762b25aa451d07eb4eac0322b431fa676462632daba2aba7bdeee1b438f051d21d4b1897e2ac2f95ee7c23f9996a805de8fffb3b30b855cd6c5b84c011accf4bf94d304d944079f04b5cadf8fcd6751c22a0f9165ab98998b2d89e6514641f1f3b91b8c0bf057d69c3d893fc4e041e06a2229e2ee58082ffb58cb920972ede58483287d0ace94c1becef26a410b93e4ff402e61dcc574b790d49679f18f4e2004f8b7cc357faba34a80e56821bb5b883d1a8b49c6605002152f270bbc36bc79095644e29ab08cc988deda765d67e4fff12b726d5de135ff9d0cbd9d5f9d440e548836633b93a38330d638468b59a32642da3375cdf70b062d14b46a78569c24a706e179baa2058dcae5c61fb6cadd9e015b017f26e9dbe3e6366cf5f1ec839aa3bbb21dd6c9b8e910245fa95b09b7d6cbf08a4c6c84bef257a70389be962dad14d97a893c128b73bf6580689e540d004f21edf8403f36b1ad7c9a2e83ffceb141af59700c316c8c1e3347187f24819c2ff0c9f9a2360dce354f3374374eab1643d2d8831310a8e3ca6768200ea7759822b82f7027cd450479fcc7f6d04802b15735a137ad489f1e1ee78434a253a9dd16684ad58fc91960cde6754f82e8b38edd5e798fdbbbf8fc2e2380a4e21dd94f8c1c063b18f29d8cd8d89f65deac5640799d4ca2caa29c1e72ad8bc417490d11e4051d94956fbc74289857e5f8e9e87b9a2d83074a994de0b10bc7782f6650cfbdb8c835c81cd88bdce5f04ca939b3c5cd010d4dc5d51224fcacbca9851694b8bf55b22dead859d023eee5a7ad3436a912c3fc0284456d5d72ea5f1afa8545c856676ac2dd9a057028bd3ca0f50e7070fa74152f13997c95c1834c3e67504f1a4165d2b49a96919b88f72caed60f56ca7ab5a3204fb12ad3592c725fdebb048732fc189c7dfed185c6c184a626e07d7356860d00389862d5b9701eaa4e5f7889e6db0f54633369b8d26805c08471de8fc3f8fa1fb0b0711d9e015add5373f7f8b64abaddbac3399c756244b1b07c579d33e4967e5e0cf16de29cb8a7efad07ff9039ca305772a6e45c76bd9b77e24949556766a8b8425c5e595efb431bde4ee222f9eb3fc2d002a1e2d14db2b23135266c942eea33bffd30eb0218405373240e0cd3040436ca895093bf056fd001c00ba59d90502042e6e6c0167105051628895c8164c9ab959400898309cabafdef12be53604fa57df44e0a90a81bd63c331291a93bffefe809e80db0679568f6e94e0d8e2edec0087c35bcb3c4f4725e6013bcf197156cd9d90612423348123383e45c14d27d8833f56ddb04083c069fd6e282fe69c940840f5f747dfb72ad72fd8cf9f3ded15c9e2f4727fd60b4f40e95dbe77a89b47dde7d5326942600554905d9dade9d145ab6da802643f2081678392609c2fdd1b79dd8caec137cbed315374c6f05c0758070f3bb17e23d81ccc39c6aa89913897e487fde889c5aacd422278f8571641cc4f0a93d9768aef9e45d6bd187d1ba637ce0fbd3c573d6778cf7bf5188c00dcdf13be3fd599143952b376220283e34e014e83b214bd5f64eb0ecb098ae8bef883949907cc36e22ece60b893b963cfa73d120513e285aaf70ce5add34edbdac60b3aa7b385b90e339058fb9b3cf984b06f79788016035c5ce490f2de7995b98a8c1c9c80f29603ae2b7fc41886663163e604275cb085f8453b27f4d795b9bad19ade2f98a1c99b43a7581bd991e5d0e5e1a6e713acc522ba9fe8302658a9782558e35436e714ac6bc85ad1d3cd008f24106901fa954f5fefb61210d6f8dc9ff35c480f1d14e59c0e501917a31ee9d00c6bdb06a00af5a8b08c3928cc5f37476248223627cb77eaf0e96213cb0a13e97d3fe9b9814d462690e8d68d02655a32fc271ee73db4f88a33386ea88a5857e15a28d9b3e3a96f00c7cd85aa53f9282ab8c8ca6d6a8afed43aa87fe7fc1ad59b0f0db2dd25c20af96e8c282c19fc883ef01a4060398926a1c82f07bcd3bc314580d7636b623b7bad8ddba05850291a6344df0f346fa4a321a85ee3e9c",
+ },
+ {
+ "67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b",
+ "0942e506c433afcda3847f2dad",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff4b4086fbbd1b6cec23e45481eac5a25d",
+ },
+ {
+ "67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314",
+ "d3d934f75ea0f210a8f6059401",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f685eb7731024bbf6794c3f4c7c5a1cf925",
+ },
+ {
+ "67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314",
+ "d3d934f75ea0f210a8f6059401beb4bc4478fa4969e623d01ada696a7e4c7e5125b34884533a94fb319990325744ee9bbce9e525cf08f5e9e25e5360aad2b2d085fa54d835e8d466826498d9a8877565705a8a3f62802944de7ca5894e5759d351adac869580ec17e485f18c0c66f17cc07cbb",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f68a12d0f1cc99e132db9014100d9668c91",
+ },
+ {
+ "67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314d3d934f75ea0f210a8f6059401beb4bc4478fa4969e623d01ada696a7e4c7e5125b34884533a94fb319990325744ee9b",
+ "bc",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f68d1f05b5662cd6e04de896d3ef5dae4149485a5a2093ff4ec74b20b5e5bf8e61b5c65515938c202beab3eea5a498d2f32d4d00a24b826b6efb16013ef54cbe170",
+ },
+ {
+ "67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314d3d934f75ea0f210a8f6059401beb4bc4478fa4969e623d01ada696a7e4c7e5125b34884533a94fb319990325744ee9bbce9e525cf08f5e9e25e5360aad2b2d085fa54d835e8d466826498d9a8877565705a8a3f62802944de7ca5894e5759d351adac869580ec17e485f18c0c66f17cc0",
+ "7cbb22fce466da610b63af62bc83b4692f3affaf271693ac071fb86d11342d",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f68d1f05b5662cd6e04de896d3ef5dae4149485a5a2093ff4ec74b20b5e5bf8e61b5c65515938c202beab3eea5a498d2f32c38dbb37d04f8272e741da2802c54a9d9aaf8ecf38b36fc9ad0079523f6a4abd5281a22697a3180bc02662a7c13ee23599d18e5c48300dbb831509df4c172f53e524b3c15124a87ac73e5028cde6c94d8d",
+ },
+ {
+ "67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314d3d934f75ea0f210a8f6059401beb4bc4478fa4969e623d01ada696a7e4c7e5125b34884533a94fb319990325744ee9bbce9e525",
+ "",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f68d1f05b5662cd6e04de896d3ef5dae4149485a5a2093ff4ec74b20b5e5bf8e61b5c65515938c202beab3eea5a498d2f32c38dbb370a9bbc3187cc260ddac991f94ce4f0d5",
+ },
+ {
+ "0fb826ddb2eb5e708de203d0438be12cf708d635ebdbae56278be09077009586b9bc646ba7c2db35a5de05e86ae71461efea96dac64430edcf117d461113cccacf303576f310ab98efb180599894ba877e50614494923163a3afa9b4c2757f91a6b40799c5b331b464b10dfc45c783c317e408ab76390e19e8b7ceaa2c4d3bd201436bc6f69c7a5a4d8756924ed95665bd5e1034971e4d80d51b2a",
+ "026866d46aa940309fdcabf92a324fbc",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "30f05cf8189bb7b8b4f560e746e228c4cc7e86e8f2fa66e1afe212d1855db51070acd5eb34ce80b2e223957df50fde4c2531d97fc9e573725e7a5e47f0dfc4da1942620320bb2deaf8b17937bae4218d04db8e76f6efe84a117292159507c9f8a09fb2c17921d7762510dbf1dac7b62b1bd7572e3e2cf008d01c445c7fa78833235034281ae180e051451c6a64f22ca9708634bd0d604e4cfcd971b13742efa5b6363e662a875daccb2b00",
+ },
+ {
+ "c7d4f8790e4c47d4daecbddf5939973521ddbf3b832e564afc66f03b5583c41c58bd956609dc3ae3c8f7c2213059575236168dba44e3044049f47c9e7840bbd0fd5036062d70e9f567ac1797056ee93c8476f6c959fa09a3ee854166c6fc36c34d6cca7adcb36f435f86db65f4c4a1793b974294914b377fd179e697751c5ac289243c65d8aca93732849c27483da083d4e218652d4fe5fec8cb953ee7f00070143dd6ece97f241b03c0424bfee2cfd2c4e738f2361df0ffe8863dcf763d408a7a167763959b7f985bc1e359a4b22c6899645ad0814bcf69d10c38474978d1c48e482723e3a6bb3f689f980c51c474eb28cfbba91a8a12eb964b32dfc303a3524ccb752f71316ed9d007e521cb5a0cf429c79d4351b02ee7fb60c7be636a10af3586dfa7b74d80875466a820c0b514e97cb12cce615ab55cba7c1b1de72bcd1cb1acc368f944ef4eaa986e6a4d8253c9337f9795d94df193c90cb0b0387dcde929905223d441717ed9dfe826613bf094ba872993d41b269e27d74e5f541b497eac9ba180dc12ffb6f1e7dc5223cce6dd541071282b97c6526e15b2c330fb41dc96e25d72f45c28e543053766d11d44252db54e584c14abbb295d7e5a58bf36eea1936095ef897a338eb1995fcedd85fc92d354dfe7ff9a115c186bb4d7a1a27835030d248c87571a38f17906cefe0261d15740b9",
+ "56",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "f89c825ca43cae1ce3fbdee85c505edd1aabefe69a0f9efd740f027aa7dee48a91ad24e69ad061648f0a52b4afb19d7ffccdc21f4b4247dfd89f5f9f998cb3c02b226173fedb6f8770aceef9271e7236fefd19fb3b87d08a5c587ac7918e80aa4b477f22602189811e270d686bc4949137a41d11d95ec96ee9d26c6126f6e923ab37638b34d1538d2e46d6df6216da4f193a3cecb731e632e109ced643056a1673059355d2d1314df35ded8364efed7de490201090a6f2d1751748585f64d26041637ba3723cbc4b60e226f10a19699d223075bc1f27d82e7f560c0db630ea670b3f8a70a8950894af4d1c7b3f674a3fa00d19ee4cc2b6174c1d259a297424bf2c3943a29a16a9830ce11abaa79cd2eb77b53a02b365b1838e7bfd5ae1bd044ffc885c61c6b2186a357e8b8f732b7ab96517969aeb70c7b493bbaca9462a61815a3c6135c748bf9c8487ac0631807aa69243fa09cd3b8efb63f8d4e090ad30b6c2f08bf4e82f191cedfa5cbe2b42268d67ecd105918181e44fc9879efd642d20be84e6f74717e03fb94fcbaa6ed3b307431d2a9384b8a2b3e5825ffce8d99af48f177e43bb4272226d8a5edd37d53807f768feb9e0733b437a1d0f84779ab68a1804e92a5eecca56364f0fa6dca152203b249fdc8fbd950fdc37c1887596308a90ba3a5751c7096bfbd1cb177bb17847b33c4379b43938a67674459cd9a06e3017ccac5b",
+ },
+ {
+ "135a28170fe89066da7bcff3a9ccc1b27dfe942a6f47b23835ef746aaea63dc10066d90f4e697528e5451b8e11dd408fdbd4b94a1c6c82515bf7bc099df9cb9d5fa4acad0d22d5f267f18078cec107a995c1f3b12d7603886dbf910ab85ca7180053c50e759b00dc8c81555a425c03d71df6894a6c8cd2d94b64e303c08a1bc1dee1cf537ccf300850856292e1656aff5bf349c87f1ca1ca8085cd400fe901edcad04146a0714ef0f6b083d715edd670e020385f3cda29bc5ff6fc6edffe5ca9ce9def6e0e3d5f04ede2db02cfb2",
+ "73afd2ab0e0e8537cae42dc6530dc4afb6934ca6",
+ "a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+ "129039b5572e8a7a8131f76a",
+ "2c125232a59879aee36cacc4aca5085a4688c4f776667a8fbd86862b5cfb1d57c976688fdd652eafa2b88b1b8e358aa2110ff6ef13cdc1ceca9c9f087c35c38d89d6fbd8de89538070f17916ecb19ca3ef4a1c834f0bdaa1df62aaabef2e117106787056c909e61ecd208357dd5c363f11c5d6cf24992cc873cf69f59360a820fcf290bd90b2cab24c47286acb4e1033962b6d41e562a206a94796a8ab1c6b8bade804ff9bdf5ba6062d2c1f8fe0f4dfc05720bd9a612b92c26789f9f6a7ce43f5e8e3aee99a9cd7d6c11eaa611983c36935b0dda57d898a60a0ab7c4b54",
+ },
+
+ // XChaCha20-Poly1305 vectors
+ {
+ "000000000000000000000000000000",
+ "",
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ "000000000000000000000000000000000000000000000000",
+ "789e9689e5208d7fd9e1f3c5b5341fb2f7033812ac9ebd3745e2c99c7bbfeb",
+ },
+ {
+ "02dc819b71875e49f5e1e5a768141cfd3f14307ae61a34d81decd9a3367c00c7",
+ "",
+ "b7bbfe61b8041658ddc95d5cbdc01bbe7626d24f3a043b70ddee87541234cff7",
+ "e293239d4c0a07840c5f83cb515be7fd59c333933027e99c",
+ "7a51f271bd2e547943c7be3316c05519a5d16803712289aa2369950b1504dd8267222e47b13280077ecada7b8795d535",
+ },
+ {
+ "7afc5f3f24155002e17dc176a8f1f3a097ff5a991b02ff4640f70b90db0c15c328b696d6998ea7988edfe3b960e47824e4ae002fbe589be57896a9b7bf5578599c6ba0153c7c",
+ "d499bb9758debe59a93783c61974b7",
+ "4ea8fab44a07f7ffc0329b2c2f8f994efdb6d505aec32113ae324def5d929ba1",
+ "404d5086271c58bf27b0352a205d21ce4367d7b6a7628961",
+ "26d2b46ad58b6988e2dcf1d09ba8ab6f532dc7e0847cdbc0ed00284225c02bbdb278ee8381ebd127a06926107d1b731cfb1521b267168926492e8f77219ad922257a5be2c5e52e6183ca4dfd0ad3912d7bd1ec968065",
+ },
+ {
+ "",
+ "",
+ "48d8bd02c2e9947eae58327114d35e055407b5519c8019535efcb4fc875b5e2b",
+ "cc0a587a475caba06f8dbc09afec1462af081fe1908c2cba",
+ "fc3322d0a9d6fac3eb4a9e09b00b361e",
+ },
+ {
+ "e0862731e5",
+ "",
+ "6579e7ee96151131a1fcd06fe0d52802c0021f214960ecceec14b2b8591f62cd",
+ "e2230748649bc22e2b71e46a7814ecabe3a7005e949bd491",
+ "e991efb85d8b1cfa3f92cb72b8d3c882e88f4529d9",
+ },
+ {
+ "00c7dd8f440af1530b44",
+ "",
+ "ffb733657c849d50ab4ab40c4ae18f8ee2f0acf7c907afefdc04dff3537fdff3",
+ "02c6fd8032a8d89edbedcd1db024c09d29f08b1e74325085",
+ "13dbcdb8c60c3ed28449a57688edfaea89e309ab4faa6d51e532",
+ },
+ {
+ "7422f311ea476cf819cb8b3c77369f",
+ "",
+ "ef0d05d028d6abdd5e99d1761d2028de75ee6eb376ff0dc8036e9a8e10743876",
+ "f772745200b0f92e38f1d8dae79bf8138e84b301f0be74df",
+ "d5f992f9834df1be86b580ac59c7eae063a68072829c51bc8a26970dd3d310",
+ },
+ {
+ "ba09ca69450e6c7bece31a7a3f216e3b9ed0e536",
+ "",
+ "8d93e31abfe22a63faf45cbea91877050718f13fef6e2664a1892d7f23007ccf",
+ "260b7b3554a7e6ff8aae7dd6234077ca539689a20c1610a8",
+ "c99e9a768eb2ec8569bdff8a37295069552faebcafb1a76e98bc7c5b6b778b3d1b6291f0",
+ },
+ {
+ "424ec5f98a0fdc5a7388532d11ab0edb26733505627b7f2d1f",
+ "",
+ "b68d5e6c46cdbb0060445522bdc5c562ae803b6aaaf1e103c146e93527a59299",
+ "80bb5dc1dd44a35ec4f91307f1a95b4ca31183a1a596fb7c",
+ "29d4eed0fff0050d4bb40de3b055d836206e7cbd62de1a63904f0cf731129ba3f9c2b9d46251a6de89",
+ },
+ {
+ "e7e4515cc0a6ef0491af983eaac4f862d6e726758a3c657f4ec444841e42",
+ "",
+ "e31a1d3af650e8e2848bd78432d89ecd1fdece9842dc2792e7bda080f537b17b",
+ "f3f09905e9a871e757348834f483ed71be9c0f437c8d74b0",
+ "f5c69528963e17db725a28885d30a45194f12848b8b7644c7bded47a2ee83e6d4ef34006305cfdf82effdced461d",
+ },
+ {
+ "0f5ca45a54875d1d19e952e53caeaa19389342f776dab11723535503338d6f77202a37",
+ "",
+ "1031bc920d4fcb4434553b1bf2d25ab375200643bf523ff037bf8914297e8dca",
+ "4cc77e2ef5445e07b5f44de2dc5bf62d35b8c6f69502d2bf",
+ "7aa8669e1bfe8b0688899cdddbb8cee31265928c66a69a5090478da7397573b1cc0f64121e7d8bff8db0ddd3c17460d7f29a12",
+ },
+ {
+ "c45578c04c194994e89025c7ffb015e5f138be3cd1a93640af167706aee2ad25ad38696df41ad805",
+ "",
+ "ac8648b7c94328419c668ce1c57c71893adf73abbb98892a4fc8da17400e3a5e",
+ "4ad637facf97af5fc03207ae56219da9972858b7430b3611",
+ "49e093fcd074fb67a755669119b8bd430d98d9232ca988882deeb3508bde7c00160c35cea89092db864dcb6d440aefa5aacb8aa7b9c04cf0",
+ },
+ {
+ "b877bfa192ea7e4c7569b9ee973f89924d45f9d8ed03c7098ad0cad6e7880906befedcaf6417bb43efabca7a2f",
+ "",
+ "125e331d5da423ecabc8adf693cdbc2fc3d3589740d40a3894f914db86c02492",
+ "913f8b2f08006e6260de41ec3ee01d938a3e68fb12dc44c4",
+ "1be334253423c90fc8ea885ee5cd3a54268c035ba8a2119e5bd4f7822cd7bf9cb4cec568d5b6d6292606d32979e044df3504e6eb8c0b2fc7e2a0e17d62",
+ },
+ {
+ "d946484a1df5f85ff72c92ff9e192660cde5074bd0ddd5de900c35eb10ed991113b1b19884631bc8ceb386bcd83908061ce9",
+ "",
+ "b7e83276373dcf8929b6a6ea80314c9de871f5f241c9144189ee4caf62726332",
+ "f59f9d6e3e6c00720dc20dc21586e8330431ebf42cf9180e",
+ "a38a662b18c2d15e1b7b14443cc23267a10bee23556b084b6254226389c414069b694159a4d0b5abbe34de381a0e2c88b947b4cfaaebf50c7a1ad6c656e386280ad7",
+ },
+ {
+ "d266927ca40b2261d5a4722f3b4da0dd5bec74e103fab431702309fd0d0f1a259c767b956aa7348ca923d64c04f0a2e898b0670988b15e",
+ "",
+ "a60e09cd0bea16f26e54b62b2908687aa89722c298e69a3a22cf6cf1c46b7f8a",
+ "92da9d67854c53597fc099b68d955be32df2f0d9efe93614",
+ "9dd6d05832f6b4d7f555a5a83930d6aed5423461d85f363efb6c474b6c4c8261b680dea393e24c2a3c8d1cc9db6df517423085833aa21f9ab5b42445b914f2313bcd205d179430",
+ },
+ {
+ "f7e11b4d372ed7cb0c0e157f2f9488d8efea0f9bbe089a345f51bdc77e30d1392813c5d22ca7e2c7dfc2e2d0da67efb2a559058d4de7a11bd2a2915e",
+ "",
+ "194b1190fa31d483c222ec475d2d6117710dd1ac19a6f1a1e8e894885b7fa631",
+ "6b07ea26bb1f2d92e04207b447f2fd1dd2086b442a7b6852",
+ "25ae14585790d71d39a6e88632228a70b1f6a041839dc89a74701c06bfa7c4de3288b7772cb2919818d95777ab58fe5480d6e49958f5d2481431014a8f88dab8f7e08d2a9aebbe691430011d",
+ },
+ {
+ "",
+ "1e2b11e3",
+ "70cd96817da85ede0efdf03a358103a84561b25453dee73735e5fb0161b0d493",
+ "5ddeba49f7266d11827a43931d1c300dd47a3c33f9f8bf9b",
+ "592fc4c19f3cddec517b2a00f9df9665",
+ },
+ {
+ "81b3cb7eb3",
+ "efcfd0cf",
+ "a977412f889281a6d75c24186f1bfaa00dcc5132f0929f20ef15bbf9e63c4c91",
+ "3f26ca997fb9166d9c615babe3e543ca43ab7cab20634ac5",
+ "8e4ade3e254cf52e93eace5c46667f150832725594",
+ },
+ {
+ "556f97f2ebdb4e949923",
+ "f7cee2e0",
+ "787b3e86546a51028501c801dadf8d5b996fd6f6f2363d5d0f900c44f6a2f4c2",
+ "7fa6af59a779657d1cada847439ea5b92a1337cfbebbc3b1",
+ "608ec22dae5f48b89d6f0d2a940d5a7661e0a8e68aaee4ad2d96",
+ },
+ {
+ "c06847a36ad031595b60edd44dc245",
+ "d4175e1f",
+ "16de31e534dd5af32801b1acd0ec541d1f8d82bcbc3af25ec815f3575b7aca73",
+ "29f6656972838f56c1684f6a278f9e4e207b51d68706fc25",
+ "836082cc51303e500fceade0b1a18f1d97d64ff41cc81754c07d6231b9fd1b",
+ },
+ {
+ "0d03c22ced7b29c6741e72166cd61792028dfc80",
+ "e505dad0",
+ "ac2b426e5c5c8e00666180a3410e8a2f6e52247a43aecea9622163e8433c93b2",
+ "c1123430468228625967bbc0fbd0f963e674372259ff2deb",
+ "bf09979bf4fed2eec6c97f6e1bcfac35eeffc6d54a55cc1d83d8767ae74db2d7cdfbc371",
+ },
+ {
+ "05bf00e1707cffe7ccbd06a9f846d0fd471a700ed43b4facb8",
+ "d863bebe",
+ "66c121f0f84b95ba1e6d29e7d81900bc96a642421b9b6105ae5eb5f2e7b07577",
+ "8ed6ae211a661e967995b71f7316ba88f44322bb62b4187b",
+ "b2c5c85d087e0305e9058fba52b661fb3d7f21cb4d4915ae048bc9e5d66a2f921dd4a1c1b030f442c9",
+ },
+ {
+ "5f2b91a9be8bfaa21451ddc6c5cf28d1cc00b046b76270b95cda3c280c83",
+ "a8750275",
+ "39592eb276877fca9dd11e2181c0b23127328407e3cc11e315e5d748f43529cc",
+ "1084bebd756f193d9eea608b3a0193a5028f8ced19684821",
+ "eaee1f49ac8468154c601a5dd8b84d597602e5a73534b5fad5664f97d0f017dd114752be969679cf610340c6a312",
+ },
+ {
+ "01e8e269b5376943f3b2d245483a76461dc8b7634868b559165f5dbb20839029fae9bb",
+ "a1e96da0",
+ "b8386123b87e50d9d046242cf1bf141fce7f65aff0fba76861a2bc72582d6ff0",
+ "0fbe2a13a89bea031de96d78f9f11358ba7b6a5e724b4392",
+ "705ec3f910ec85c6005baa99641de6ca43332ff52b5466df6af4ffbe4ef2a376a8f871d1eae503b5896601fee005cdc1f4c1c6",
+ },
+ {
+ "706daba66e2edb1f828f3c0051e3cc214b12210bde0587bba02580f741a4c83e84d4e9fe961120cd",
+ "87663c5a",
+ "d519d82ba8a3f0c3af9efe36682b62e285167be101a526c1d73000f169c2a486",
+ "ad651aac536978e2bc1a54816345ac5e9a9b43b3d9cc0bfc",
+ "07051b5e72da9c4811beb07ff9f95aece67eae18420eb3f0e8bb8a5e26d4b483fa40eb063a2354842d0c8a41d981cc2b77c530b496db01c8",
+ },
+ {
+ "1f6b24f2f0d9eb460d726bed953d66fcc4ecc29da6ed2fd711358eac3b2609d74ba3e21885156cde3cbe6d9b6f",
+ "f5efbc4e",
+ "86068a00544f749ad4ad15bb8e427ae78577ae22f4ca9778efff828ba10f6b20",
+ "c8420412c9626dcd34ece14593730f6aa2d01ec51cacd59f",
+ "a99f6c88eac35bb34439e34b292fe9db8192446dcdc81e2192060ec36d98b47de2bee12bf0f67cb24fb0949c07733a6781cd9455cdc61123f506886b04",
+ },
+ {
+ "d69389d83362be8c0ddb738659a6cc4bd65d88cb5b525232f4d59a7d4751a7203c254923ecb6873e803220aab19664789a63",
+ "bc35fb1c",
+ "835855b326a98682b3075b4d7f1b89059c3cdfc547d4296c80ce7a77ba6434e3",
+ "c27cb75fc319ba431cbaeb120341d0c4745d883eb47e92bc",
+ "db6dc3f9a0f4f1a6df2495a88910550c2c6205478bfc1e81282e34b5b36d984c72c0509c522c987c61d2e640ced69402a6d33aa10d3d0b81e680b3c19bc142e81923",
+ },
+ {
+ "a66a7f089115ed9e2d5bb5d33d7282a7afe401269b00f2a233a59c04b794a42901d862140b61d18d7c7f0ad5da040613e557f8abc74219",
+ "2c060aaf",
+ "99758aa7714fd707931f71803eefe04a06955041308a0b2a1104313b270ccf34",
+ "63f690d8926408c7a34fe8ddd505a8dc58769dc74e8d5da6",
+ "92b21ee85afcd8996ac28f3aed1047ad814d6e4ffbca3159af16f26eded83e4abda9e4275eb3ff0ad90dffe09f2d443b628f824f680b46527ce0128e8de1920f7c44350ebe7913",
+ },
+ {
+ "f955183b1f762d4536d3f6885ea7f5ac27414caf46c2e24a2fd3bd56b91c53d840fb657224565e0a6f686f8ba320e04a401057399d9a3d995ab17c13",
+ "c372ddc5",
+ "a188be3795b2ca2e69b6aa263244f0963c492d694cf6c9b705a1d7045f3f2a26",
+ "51bb484ea094ee140474681e1c838e4442fd148de2cc345a",
+ "48759a5ddfdd829d11de8e0c538ce4a9c475faab6912039b568ad92d737d172fc1eb0c00c3793de6dddbfacfdbbc7f44aeba33684e18005aa982b6fc6c556e63bb90ff7a1dde8153a63eabe0",
+ },
+ {
+ "",
+ "e013cd0bfafd486d",
+ "af3d3ba094d38299ecb91c17bfe3d085da5bd42e11acf8acb5bc26a4be9a7583",
+ "7dd63c14173831f109761b1c1abe18f6ba937d825957011b",
+ "8bc685a7d9d501952295cd25d8c92517",
+ },
+ {
+ "284b64597e",
+ "31d013e53aa3ea79",
+ "93c77409d7f805f97fe683b2dd6ee06152a5e918b3eed5b731acccffdcb2cc04",
+ "3d331e90c4597cf0c30d1b7cfbd07bcb6ab927eda056873c",
+ "3538a449d6c18d148a8c6cb76f1bc288657ac7036a",
+ },
+ {
+ "9fe67f5c78180ede8274",
+ "188608d230d75860",
+ "b7cca89a82640aea6f80b458c9e633d88594fb498959d39787be87030892d48f",
+ "ef891d50e8c08958f814590fdb7a9f16c61cc2aae1682109",
+ "bbb40c30f3d1391a5b38df480cbbf964b71e763e8140751f4e28",
+ },
+ {
+ "3a2826b6f7e3d542e4ded8f23c9aa4",
+ "260033e789c4676a",
+ "7fe2731214f2b4b42f93217d43f1776498413725e4f6cfe62b756e5a52df10ea",
+ "888728219ebf761547f5e2218532714403020e5a8b7a49d0",
+ "fe0328f883fcd88930ae017c0f54ed90f883041efc020e959125af370c1d47",
+ },
+ {
+ "91858bf7b969005d7164acbd5678052b651c53e0",
+ "f3cc53ecafcbadb3",
+ "d69c04e9726b22d51f97bc9da0f0fda86736e6b78e8ef9f6f0000f79890d6d43",
+ "6de3c45161b434e05445cf6bf69eef7bddf595fc6d8836bd",
+ "a8869dd578c0835e120c843bb7dedc7a1e9eae24ffd742be6bf5b74088a8a2c550976fcb",
+ },
+ {
+ "b3b1a4d6b2a2b9c5a1ca6c1efaec34dcfa1acbe7074d5e10cc",
+ "d0f72bd16cda3bae",
+ "2b317857b089c9305c49b83019f6e158bc4ecc3339b39ade02ee10c37c268da0",
+ "cb5fa6d1e14a0b4bdf350cd10c8a7bd638102911ec74be09",
+ "e6372f77c14343650074e07a2b7223c37b29242224b722b24d63b5956f27aa64ce7ce4e39cd14a2787",
+ },
+ {
+ "057d3e9f865be7dff774938cab6d080e50cf9a1593f53c0063201e0bb7ae",
+ "fd3881e505c8b12d",
+ "36e42b1ef1ee8d068f09b5fad3ee43d98d34aa3e3f994f2055aee139da71de9d",
+ "24124da36473d01bdca30297c9eef4fe61955525a453da17",
+ "a8b28139524c98c1f8776f442eac4c22766fe6aac83224641c58bf021fc9cb709ec4706f49c2d0c1828acf2bfe8d",
+ },
+ {
+ "bd8f13e928c34d67a6c70c3c7efdf2982ecc31d8cee68f9cbddc75912cd828ac93d28b",
+ "193206c8fcc5b19b",
+ "6e47c40c9d7b757c2efca4d73890e4c73f3c859aab4fdc64b564b8480dd84e72",
+ "ca31340ae20d30fe488be355cb36652c5db7c9d6265a3e95",
+ "a121efc5e1843deade4b8adbfef1808de4eda222f176630ad34fb476fca19e0299e4a13668e53cf13882035ba4f04f47c8b4e3",
+ },
+ {
+ "23067a196e977d10039c14ff358061c918d2148d31961bb3e12c27c5122383cb25c4d1d79c775720",
+ "62338d02fff78a00",
+ "2c5c79c92d91fb40ef7d0a77e8033f7b265e3bab998b8116d17b2e62bb4f8a09",
+ "024736adb1d5c01006dffd8158b57936d158d5b42054336d",
+ "46d0905473a995d38c7cdbb8ef3da96ecc82a22c5b3c6c9d1c4a61ae7a17db53cb88c5f7eccf2da1d0c417c300f989b4273470e36f03542f",
+ },
+ {
+ "252e966c680329eb687bff813b78fea3bfd3505333f106c6f9f45ba69896723c41bb763793d9b266e897d05557",
+ "1e93e0cfe6523380",
+ "9ec6fd1baa13ee16aec3fac16718a2baccf18a403cec467c25b7448e9b321110",
+ "e7120b1018ab363a36e61102eedbcbe9847a6cbacaa9c328",
+ "2934f034587d4144bb11182679cd2cd1c99c8088d18e233379e9bc9c41107a1f57a2723ecc7b9ba4e6ee198adf0fd766738e828827dc73136fc5b996e9",
+ },
+ {
+ "6744aefcb318f12bc6eeb59d4d62f7eb95f347cea14bd5158415f07f84e4e3baa3de07512d9b76095ac1312cfcb1bb77f499",
+ "608d2a33ce5d0b04",
+ "0f665cbdaaa40f4f5a00c53d951b0a98aac2342be259a52670f650a783be7aab",
+ "378bdb57e957b8c2e1500c9513052a3b02ff5b7edbd4a3a7",
+ "341c60fcb374b394f1b01a4a80aedef49ab0b67ec963675e6eec43ef106f7003be87dbf4a8976709583dccc55abc7f979c4721837e8664a69804ea31736aa2af615a",
+ },
+ {
+ "bcf1004f988220b7ce063ef2ec4e276ffd074f0a90aa807de1532679d2a1505568eaa4192d9a6ea52cc500322343ce9f8e68cc2c606d83",
+ "e64bd00126c8792c",
+ "58e65150d6a15dcefbc14a171998987ad0d709fb06a17d68d6a778759681c308",
+ "106d2bd120b06e4eb10bc674fe55c77a3742225268319303",
+ "a28052a6686a1e9435fee8702f7da563a7b3d7b5d3e9e27f11abf73db309cd1f39a34756258c1c5c7f2fb12cf15eb20175c2a08fc93dd19c5e482ef3fbef3d8404a3cfd54a7baf",
+ },
+ {
+ "acd08d4938a224b4cb2d723bf75420f3ea27b698fadd815bb7db9548a05651398644354334e69f8e4e5503bf1a6f92b38e860044a7edca6874038ce1",
+ "28a137808d0225b8",
+ "a031203b963a395b08be55844d81af39d19b23b7cc24b21afa31edc1eea6edd6",
+ "e8b31c52b6690f10f4ae62ba9d50ba39fb5edcfb78400e35",
+ "35cf39ba31da95ac9b661cdbd5e9c9655d13b8ff065c4ec10c810833a47a87d8057dd1948a7801bfe6904b49fed0aabfb3cd755a1a262d372786908ddcf64cae9f71cb9ed199c3ddacc50116",
+ },
+ {
+ "",
+ "cda7ee2857e09e9054ef6806",
+ "d91dffb18132d8dd3d144a2f10ba28bc5df36cb60369f3b19893ec91db3cf904",
+ "ee56f19c62b0438da6a0d9e01844313902be44f84a6a4ce7",
+ "ccd48b61a5683c195d4424009eb1d147",
+ },
+ {
+ "350f4c7ac2",
+ "7c104b539c1d2ae022434cd6",
+ "cbb61e369117f9250f68fa707240c554359262a4d66c757f80e3aeb6920894fb",
+ "fbb14c9943444eac5413c6f5c8095451eddece02c9461043",
+ "b5c6a35865ed8e5216ff6c77339ee1ab570de50e51",
+ },
+ {
+ "4f0d61d3ea03a44a8df0",
+ "51c20a8ae9e9794da931fe23",
+ "ba6ced943aa62f9261d7513b822e02054e099acafb5360f0d850064da48b5a4f",
+ "04c68cb50cdbb0ec03f8381cf59b886e64c40548bf8e3f82",
+ "ea45a73957e2a853655623f2a3bb58791f7ea36dd2957ed66ffa",
+ },
+ {
+ "4fbdd4d4293a8f34fdbc8f3ad44cf6",
+ "8212f315e3759c3253c588bb",
+ "5354791bc2370415811818e913e310dd12e6a0cf5dcab2b6424816eecccf4b65",
+ "7ee6353c2fbc73c9ebc652270bc86e4008e09583e623e679",
+ "50a354811a918e1801fb567621a8924baf8dd79da6d36702855d3753f1319c",
+ },
+ {
+ "5a6f68b5a9a9920ca9c6edf5be7c0af150a063c4",
+ "9a524aa62938fb7a1e50ed06",
+ "fd91605a6ad85d8ba7a71b08dce1032aa9992bf4f28d407a53ddda04c043cada",
+ "46791d99d6de33e79025bf9e97c198e7cf409614c6284b4d",
+ "648033c1eb615467e90b7d3ac24202d8b849549141f9bab03e9e910c29b8eab3d4fb3f2c",
+ },
+ {
+ "d9318c2c0d9ed89e35d242a6b1d496e7e0c5bbdf77eba14c56",
+ "a16053c35fbe8dc93c14a81f",
+ "f21406aec83134ebf7bc48c6d0f45acb5f341fbc7d3b5a9bff3ea1333c916af7",
+ "de6b977be450d5efa7777e006802ddbb10814a22da1c3cd9",
+ "8d3dad487d5161663da830b71c3e24ec5cdb74d858cbb73b084ed0902198532aad3a18416966bff223",
+ },
+ {
+ "68d0ee08d38cb4bcc9268fee3030666e70e41fcabf6fe06536eeec43eec5",
+ "11e09447d40b22dc98070eec",
+ "da5ee1ec02eab13220fcb94f16efec848a8dd57c0f4d67955423f5d17fde5aa3",
+ "8f13e61d773a250810f75d46bf163a3f9205be5751f6049a",
+ "92a103b03764c1ad1f88500d22eeae5c0fe1044c872987c0b97affc5e8c3d783f8cc28a11dc91990ea22dd1bad74",
+ },
+ {
+ "a1d960bda08efcf19e136dc1e8b05b6b381c820eda5f9a8047e1a2dd1803a1e4d11a7f",
+ "aa73d8d4aaa0cfd9d80a9ae8",
+ "08028833d617c28ba75b48f177cb5da87189189abb68dcb8974eca9230c25945",
+ "f7b6f34a910fd11588f567de8555932291f7df05f6e2b193",
+ "99cfc4cca193998bae153b744e6c94a82a2867780aa0f43acddb7c433fcb297311313ec2199f00d7ca7da0646b40113c60e935",
+ },
+ {
+ "3b4ae39a745b6247ce5baf675ec36c5065b1bf76c8379eab4b769961d43a753896d068938017777e",
+ "128c017a985052f8cdbc6b28",
+ "4683d5caff613187a9b16af897253848e9c54fc0ec319de62452a86961d3cbb2",
+ "5612a13c2da003b91188921cbac3fa093eba99d8cbbb51ff",
+ "91a98b93b2174257175f7c882b45cc252e0db8667612bd270c1c12fe28b6bf209760bf8f370318f92ae3f88a5d4773b05714132cc28dddb8",
+ },
+ {
+ "22ccf680d2995ef6563de281cff76882a036a59ad73f250e710b3040590d69bccde8a8411abe8b0d3cb728ca82",
+ "13a97d0a167a61aa21e531ec",
+ "9e140762eed274948b66de25e6e8f36ab65dc730b0cb096ef15aaba900a5588c",
+ "d0e9594cfd42ab72553bf34062a263f588bb8f1fc86a19f5",
+ "f194fc866dfba30e42c4508b7d90b3fa3f8983831ede713334563e36aa861f2f885b40be1dbe20ba2d10958a12823588d4bbbefb81a87d87315204f5e3",
+ },
+ {
+ "a65f5d10c482b3381af296e631eb605eba6a11ccec6ceab021460d0bd35feb676ec6dbba5d4ad6c9f4d683ea541035bc80fa",
+ "f15ae71ffed50a8fcc4996b0",
+ "f535d60e8b75ac7e526041eed86eb4d65ae7e315eff15dba6c0133acc2a6a4bf",
+ "01ba61691ebb3c66d2f94c1b1c597ecd7b5ff7d2a30be405",
+ "d79e7c3893df5a5879c2f0a3f7ca619f08e4540f3ac7db35790b4211b9d47ae735adadf35fd47252a4763e3fd2b2cd8157f6ea7986108a53437962670a97d68ee281",
+ },
+ {
+ "8c014655b97f6da76b0b168b565fd62de874c164fd7e227346a0ec22c908bed1e2a0b429620e6f3a68dd518f13a2c0250608a1cb08a7c3",
+ "10a7eff999029c5040c1b3bd",
+ "bf11af23e88c350a443493f6fa0eb34f234f4daa2676e26f0701bce5642d13f4",
+ "f14c97392afd2e32e2c625910ca029f9b6e81676c79cc42f",
+ "78d5226f372d5d60681dbfc749d12df74249f196b0cbf14fa65a3a59dc65ae458455ec39baa1df3397afe752bb06f6f13bf03c99abda7a95c1d0b73fd92d5f888a5f6f889a9aea",
+ },
+ {
+ "66234d7a5b71eef134d60eccf7d5096ee879a33983d6f7a575e3a5e3a4022edccffe7865dde20b5b0a37252e31cb9a3650c63e35b057a1bc200a5b5b",
+ "ccc2406f997bcae737ddd0f5",
+ "d009eeb5b9b029577b14d200b7687b655eedb7d74add488f092681787999d66d",
+ "99319712626b400f9458dbb7a9abc9f5810f25b47fc90b39",
+ "543a2bbf52fd999027ae7c297353f3ce986f810bc2382583d0a81fda5939e4c87b6e8d262790cd614d6f753d8035b32adf43acc7f6d4c2c44289538928564b6587c2fcb99de1d8e34ffff323",
+ },
+}
diff --git a/local_crypto_patch/contents/chacha20poly1305/fips140only_compat.go b/local_crypto_patch/contents/chacha20poly1305/fips140only_compat.go
new file mode 100644
index 0000000000..9b9d5643ec
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/fips140only_compat.go
@@ -0,0 +1,9 @@
+// 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.26
+
+package chacha20poly1305
+
+func fips140Enforced() bool { return false }
diff --git a/local_crypto_patch/contents/chacha20poly1305/fips140only_go1.26.go b/local_crypto_patch/contents/chacha20poly1305/fips140only_go1.26.go
new file mode 100644
index 0000000000..f71089c486
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/fips140only_go1.26.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.26
+
+package chacha20poly1305
+
+import "crypto/fips140"
+
+func fips140Enforced() bool { return fips140.Enforced() }
diff --git a/local_crypto_patch/contents/chacha20poly1305/xchacha20poly1305.go b/local_crypto_patch/contents/chacha20poly1305/xchacha20poly1305.go
new file mode 100644
index 0000000000..b4299b718c
--- /dev/null
+++ b/local_crypto_patch/contents/chacha20poly1305/xchacha20poly1305.go
@@ -0,0 +1,89 @@
+// 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 chacha20poly1305
+
+import (
+ "crypto/cipher"
+ "errors"
+
+ "golang.org/x/crypto/chacha20"
+)
+
+type xchacha20poly1305 struct {
+ key [KeySize]byte
+}
+
+// NewX returns a XChaCha20-Poly1305 AEAD that uses the given 256-bit key.
+//
+// XChaCha20-Poly1305 is a ChaCha20-Poly1305 variant that takes a longer nonce,
+// suitable to be generated randomly without risk of collisions. It should be
+// preferred when nonce uniqueness cannot be trivially ensured, or whenever
+// nonces are randomly generated.
+func NewX(key []byte) (cipher.AEAD, error) {
+ if fips140Enforced() {
+ return nil, errors.New("chacha20poly1305: use of ChaCha20Poly1305 is not allowed in FIPS 140-only mode")
+ }
+ if len(key) != KeySize {
+ return nil, errors.New("chacha20poly1305: bad key length")
+ }
+ ret := new(xchacha20poly1305)
+ copy(ret.key[:], key)
+ return ret, nil
+}
+
+func (*xchacha20poly1305) NonceSize() int {
+ return NonceSizeX
+}
+
+func (*xchacha20poly1305) Overhead() int {
+ return Overhead
+}
+
+func (x *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
+ if len(nonce) != NonceSizeX {
+ panic("chacha20poly1305: bad nonce length passed to Seal")
+ }
+
+ // XChaCha20-Poly1305 technically supports a 64-bit counter, so there is no
+ // size limit. However, since we reuse the ChaCha20-Poly1305 implementation,
+ // the second half of the counter is not available. This is unlikely to be
+ // an issue because the cipher.AEAD API requires the entire message to be in
+ // memory, and the counter overflows at 256 GB.
+ if uint64(len(plaintext)) > (1<<38)-64 {
+ panic("chacha20poly1305: plaintext too large")
+ }
+
+ c := new(chacha20poly1305)
+ hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16])
+ copy(c.key[:], hKey)
+
+ // The first 4 bytes of the final nonce are unused counter space.
+ cNonce := make([]byte, NonceSize)
+ copy(cNonce[4:12], nonce[16:24])
+
+ return c.seal(dst, cNonce[:], plaintext, additionalData)
+}
+
+func (x *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
+ if len(nonce) != NonceSizeX {
+ panic("chacha20poly1305: bad nonce length passed to Open")
+ }
+ if len(ciphertext) < 16 {
+ return nil, errOpen
+ }
+ if uint64(len(ciphertext)) > (1<<38)-48 {
+ panic("chacha20poly1305: ciphertext too large")
+ }
+
+ c := new(chacha20poly1305)
+ hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16])
+ copy(c.key[:], hKey)
+
+ // The first 4 bytes of the final nonce are unused counter space.
+ cNonce := make([]byte, NonceSize)
+ copy(cNonce[4:12], nonce[16:24])
+
+ return c.open(dst, cNonce[:], ciphertext, additionalData)
+}
diff --git a/local_crypto_patch/contents/codereview.cfg b/local_crypto_patch/contents/codereview.cfg
new file mode 100644
index 0000000000..3f8b14b64e
--- /dev/null
+++ b/local_crypto_patch/contents/codereview.cfg
@@ -0,0 +1 @@
+issuerepo: golang/go
diff --git a/local_crypto_patch/contents/cryptobyte/asn1.go b/local_crypto_patch/contents/cryptobyte/asn1.go
new file mode 100644
index 0000000000..d25979d9f5
--- /dev/null
+++ b/local_crypto_patch/contents/cryptobyte/asn1.go
@@ -0,0 +1,825 @@
+// 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 cryptobyte
+
+import (
+ encoding_asn1 "encoding/asn1"
+ "fmt"
+ "math/big"
+ "reflect"
+ "time"
+
+ "golang.org/x/crypto/cryptobyte/asn1"
+)
+
+// This file contains ASN.1-related methods for String and Builder.
+
+// Builder
+
+// AddASN1Int64 appends a DER-encoded ASN.1 INTEGER.
+func (b *Builder) AddASN1Int64(v int64) {
+ b.addASN1Signed(asn1.INTEGER, v)
+}
+
+// AddASN1Int64WithTag appends a DER-encoded ASN.1 INTEGER with the
+// given tag.
+func (b *Builder) AddASN1Int64WithTag(v int64, tag asn1.Tag) {
+ b.addASN1Signed(tag, v)
+}
+
+// AddASN1Enum appends a DER-encoded ASN.1 ENUMERATION.
+func (b *Builder) AddASN1Enum(v int64) {
+ b.addASN1Signed(asn1.ENUM, v)
+}
+
+func (b *Builder) addASN1Signed(tag asn1.Tag, v int64) {
+ b.AddASN1(tag, func(c *Builder) {
+ length := 1
+ for i := v; i >= 0x80 || i < -0x80; i >>= 8 {
+ length++
+ }
+
+ for ; length > 0; length-- {
+ i := v >> uint((length-1)*8) & 0xff
+ c.AddUint8(uint8(i))
+ }
+ })
+}
+
+// AddASN1Uint64 appends a DER-encoded ASN.1 INTEGER.
+func (b *Builder) AddASN1Uint64(v uint64) {
+ b.AddASN1(asn1.INTEGER, func(c *Builder) {
+ length := 1
+ for i := v; i >= 0x80; i >>= 8 {
+ length++
+ }
+
+ for ; length > 0; length-- {
+ i := v >> uint((length-1)*8) & 0xff
+ c.AddUint8(uint8(i))
+ }
+ })
+}
+
+// AddASN1BigInt appends a DER-encoded ASN.1 INTEGER.
+func (b *Builder) AddASN1BigInt(n *big.Int) {
+ if b.err != nil {
+ return
+ }
+
+ b.AddASN1(asn1.INTEGER, func(c *Builder) {
+ if n.Sign() < 0 {
+ // A negative number has to be converted to two's-complement form. So we
+ // invert and subtract 1. 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 {
+ c.add(0xff)
+ }
+ c.add(bytes...)
+ } else if n.Sign() == 0 {
+ c.add(0)
+ } else {
+ bytes := n.Bytes()
+ if bytes[0]&0x80 != 0 {
+ c.add(0)
+ }
+ c.add(bytes...)
+ }
+ })
+}
+
+// AddASN1OctetString appends a DER-encoded ASN.1 OCTET STRING.
+func (b *Builder) AddASN1OctetString(bytes []byte) {
+ b.AddASN1(asn1.OCTET_STRING, func(c *Builder) {
+ c.AddBytes(bytes)
+ })
+}
+
+const generalizedTimeFormatStr = "20060102150405Z0700"
+
+// AddASN1GeneralizedTime appends a DER-encoded ASN.1 GENERALIZEDTIME.
+func (b *Builder) AddASN1GeneralizedTime(t time.Time) {
+ if t.Year() < 0 || t.Year() > 9999 {
+ b.err = fmt.Errorf("cryptobyte: cannot represent %v as a GeneralizedTime", t)
+ return
+ }
+ b.AddASN1(asn1.GeneralizedTime, func(c *Builder) {
+ c.AddBytes([]byte(t.Format(generalizedTimeFormatStr)))
+ })
+}
+
+// AddASN1UTCTime appends a DER-encoded ASN.1 UTCTime.
+func (b *Builder) AddASN1UTCTime(t time.Time) {
+ b.AddASN1(asn1.UTCTime, func(c *Builder) {
+ // As utilized by the X.509 profile, UTCTime can only
+ // represent the years 1950 through 2049.
+ if t.Year() < 1950 || t.Year() >= 2050 {
+ b.err = fmt.Errorf("cryptobyte: cannot represent %v as a UTCTime", t)
+ return
+ }
+ c.AddBytes([]byte(t.Format(defaultUTCTimeFormatStr)))
+ })
+}
+
+// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not
+// support BIT STRINGs that are not a whole number of bytes.
+func (b *Builder) AddASN1BitString(data []byte) {
+ b.AddASN1(asn1.BIT_STRING, func(b *Builder) {
+ b.AddUint8(0)
+ b.AddBytes(data)
+ })
+}
+
+func (b *Builder) addBase128Int(n int64) {
+ var length int
+ if n == 0 {
+ length = 1
+ } else {
+ for i := n; i > 0; i >>= 7 {
+ length++
+ }
+ }
+
+ for i := length - 1; i >= 0; i-- {
+ o := byte(n >> uint(i*7))
+ o &= 0x7f
+ if i != 0 {
+ o |= 0x80
+ }
+
+ b.add(o)
+ }
+}
+
+func isValidOID(oid encoding_asn1.ObjectIdentifier) bool {
+ if len(oid) < 2 {
+ return false
+ }
+
+ if oid[0] > 2 || (oid[0] <= 1 && oid[1] >= 40) {
+ return false
+ }
+
+ for _, v := range oid {
+ if v < 0 {
+ return false
+ }
+ }
+
+ return true
+}
+
+func (b *Builder) AddASN1ObjectIdentifier(oid encoding_asn1.ObjectIdentifier) {
+ b.AddASN1(asn1.OBJECT_IDENTIFIER, func(b *Builder) {
+ if !isValidOID(oid) {
+ b.err = fmt.Errorf("cryptobyte: invalid OID: %v", oid)
+ return
+ }
+
+ b.addBase128Int(int64(oid[0])*40 + int64(oid[1]))
+ for _, v := range oid[2:] {
+ b.addBase128Int(int64(v))
+ }
+ })
+}
+
+func (b *Builder) AddASN1Boolean(v bool) {
+ b.AddASN1(asn1.BOOLEAN, func(b *Builder) {
+ if v {
+ b.AddUint8(0xff)
+ } else {
+ b.AddUint8(0)
+ }
+ })
+}
+
+func (b *Builder) AddASN1NULL() {
+ b.add(uint8(asn1.NULL), 0)
+}
+
+// MarshalASN1 calls encoding_asn1.Marshal on its input and appends the result if
+// successful or records an error if one occurred.
+func (b *Builder) MarshalASN1(v interface{}) {
+ // NOTE(martinkr): This is somewhat of a hack to allow propagation of
+ // encoding_asn1.Marshal errors into Builder.err. N.B. if you call MarshalASN1 with a
+ // value embedded into a struct, its tag information is lost.
+ if b.err != nil {
+ return
+ }
+ bytes, err := encoding_asn1.Marshal(v)
+ if err != nil {
+ b.err = err
+ return
+ }
+ b.AddBytes(bytes)
+}
+
+// AddASN1 appends an ASN.1 object. The object is prefixed with the given tag.
+// Tags greater than 30 are not supported and result in an error (i.e.
+// low-tag-number form only). The child builder passed to the
+// BuilderContinuation can be used to build the content of the ASN.1 object.
+func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) {
+ if b.err != nil {
+ return
+ }
+ // Identifiers with the low five bits set indicate high-tag-number format
+ // (two or more octets), which we don't support.
+ if tag&0x1f == 0x1f {
+ b.err = fmt.Errorf("cryptobyte: high-tag number identifier octets not supported: 0x%x", tag)
+ return
+ }
+ b.AddUint8(uint8(tag))
+ b.addLengthPrefixed(1, true, f)
+}
+
+// String
+
+// ReadASN1Boolean decodes an ASN.1 BOOLEAN and converts it to a boolean
+// representation into out and advances. It reports whether the read
+// was successful.
+func (s *String) ReadASN1Boolean(out *bool) bool {
+ var bytes String
+ if !s.ReadASN1(&bytes, asn1.BOOLEAN) || len(bytes) != 1 {
+ return false
+ }
+
+ switch bytes[0] {
+ case 0:
+ *out = false
+ case 0xff:
+ *out = true
+ default:
+ return false
+ }
+
+ return true
+}
+
+// ReadASN1Integer decodes an ASN.1 INTEGER into out and advances. If out does
+// not point to an integer, to a big.Int, or to a []byte it panics. Only
+// positive and zero values can be decoded into []byte, and they are returned as
+// big-endian binary values that share memory with s. Positive values will have
+// no leading zeroes, and zero will be returned as a single zero byte.
+// ReadASN1Integer reports whether the read was successful.
+func (s *String) ReadASN1Integer(out interface{}) bool {
+ switch out := out.(type) {
+ case *int, *int8, *int16, *int32, *int64:
+ var i int64
+ if !s.readASN1Int64(&i) || reflect.ValueOf(out).Elem().OverflowInt(i) {
+ return false
+ }
+ reflect.ValueOf(out).Elem().SetInt(i)
+ return true
+ case *uint, *uint8, *uint16, *uint32, *uint64:
+ var u uint64
+ if !s.readASN1Uint64(&u) || reflect.ValueOf(out).Elem().OverflowUint(u) {
+ return false
+ }
+ reflect.ValueOf(out).Elem().SetUint(u)
+ return true
+ case *big.Int:
+ return s.readASN1BigInt(out)
+ case *[]byte:
+ return s.readASN1Bytes(out)
+ default:
+ panic("out does not point to an integer type")
+ }
+}
+
+func checkASN1Integer(bytes []byte) bool {
+ if len(bytes) == 0 {
+ // An INTEGER is encoded with at least one octet.
+ return false
+ }
+ if len(bytes) == 1 {
+ return true
+ }
+ if bytes[0] == 0 && bytes[1]&0x80 == 0 || bytes[0] == 0xff && bytes[1]&0x80 == 0x80 {
+ // Value is not minimally encoded.
+ return false
+ }
+ return true
+}
+
+var bigOne = big.NewInt(1)
+
+func (s *String) readASN1BigInt(out *big.Int) bool {
+ var bytes String
+ if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) {
+ return false
+ }
+ if bytes[0]&0x80 == 0x80 {
+ // Negative number.
+ neg := make([]byte, len(bytes))
+ for i, b := range bytes {
+ neg[i] = ^b
+ }
+ out.SetBytes(neg)
+ out.Add(out, bigOne)
+ out.Neg(out)
+ } else {
+ out.SetBytes(bytes)
+ }
+ return true
+}
+
+func (s *String) readASN1Bytes(out *[]byte) bool {
+ var bytes String
+ if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) {
+ return false
+ }
+ if bytes[0]&0x80 == 0x80 {
+ return false
+ }
+ for len(bytes) > 1 && bytes[0] == 0 {
+ bytes = bytes[1:]
+ }
+ *out = bytes
+ return true
+}
+
+func (s *String) readASN1Int64(out *int64) bool {
+ var bytes String
+ if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Signed(out, bytes) {
+ return false
+ }
+ return true
+}
+
+func asn1Signed(out *int64, n []byte) bool {
+ length := len(n)
+ if length > 8 {
+ return false
+ }
+ for i := 0; i < length; i++ {
+ *out <<= 8
+ *out |= int64(n[i])
+ }
+ // Shift up and down in order to sign extend the result.
+ *out <<= 64 - uint8(length)*8
+ *out >>= 64 - uint8(length)*8
+ return true
+}
+
+func (s *String) readASN1Uint64(out *uint64) bool {
+ var bytes String
+ if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Unsigned(out, bytes) {
+ return false
+ }
+ return true
+}
+
+func asn1Unsigned(out *uint64, n []byte) bool {
+ length := len(n)
+ if length > 9 || length == 9 && n[0] != 0 {
+ // Too large for uint64.
+ return false
+ }
+ if n[0]&0x80 != 0 {
+ // Negative number.
+ return false
+ }
+ for i := 0; i < length; i++ {
+ *out <<= 8
+ *out |= uint64(n[i])
+ }
+ return true
+}
+
+// ReadASN1Int64WithTag decodes an ASN.1 INTEGER with the given tag into out
+// and advances. It reports whether the read was successful and resulted in a
+// value that can be represented in an int64.
+func (s *String) ReadASN1Int64WithTag(out *int64, tag asn1.Tag) bool {
+ var bytes String
+ return s.ReadASN1(&bytes, tag) && checkASN1Integer(bytes) && asn1Signed(out, bytes)
+}
+
+// ReadASN1Enum decodes an ASN.1 ENUMERATION into out and advances. It reports
+// whether the read was successful.
+func (s *String) ReadASN1Enum(out *int) bool {
+ var bytes String
+ var i int64
+ if !s.ReadASN1(&bytes, asn1.ENUM) || !checkASN1Integer(bytes) || !asn1Signed(&i, bytes) {
+ return false
+ }
+ if int64(int(i)) != i {
+ return false
+ }
+ *out = int(i)
+ return true
+}
+
+func (s *String) readBase128Int(out *int) bool {
+ ret := 0
+ for i := 0; len(*s) > 0; i++ {
+ if i == 5 {
+ return false
+ }
+ // Avoid overflowing int on a 32-bit platform.
+ // We don't want different behavior based on the architecture.
+ if ret >= 1<<(31-7) {
+ return false
+ }
+ ret <<= 7
+ b := s.read(1)[0]
+
+ // ITU-T X.690, section 8.19.2:
+ // The subidentifier shall be encoded in the fewest possible octets,
+ // that is, the leading octet of the subidentifier shall not have the value 0x80.
+ if i == 0 && b == 0x80 {
+ return false
+ }
+
+ ret |= int(b & 0x7f)
+ if b&0x80 == 0 {
+ *out = ret
+ return true
+ }
+ }
+ return false // truncated
+}
+
+// ReadASN1ObjectIdentifier decodes an ASN.1 OBJECT IDENTIFIER into out and
+// advances. It reports whether the read was successful.
+func (s *String) ReadASN1ObjectIdentifier(out *encoding_asn1.ObjectIdentifier) bool {
+ var bytes String
+ if !s.ReadASN1(&bytes, asn1.OBJECT_IDENTIFIER) || len(bytes) == 0 {
+ return false
+ }
+
+ // In the worst case, we get two elements from the first byte (which is
+ // encoded differently) and then every varint is a single byte long.
+ components := make([]int, len(bytes)+1)
+
+ // The first varint is 40*value1 + value2:
+ // According to this packing, value1 can take the values 0, 1 and 2 only.
+ // When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2,
+ // then there are no restrictions on value2.
+ var v int
+ if !bytes.readBase128Int(&v) {
+ return false
+ }
+ if v < 80 {
+ components[0] = v / 40
+ components[1] = v % 40
+ } else {
+ components[0] = 2
+ components[1] = v - 80
+ }
+
+ i := 2
+ for ; len(bytes) > 0; i++ {
+ if !bytes.readBase128Int(&v) {
+ return false
+ }
+ components[i] = v
+ }
+ *out = components[:i]
+ return true
+}
+
+// ReadASN1GeneralizedTime decodes an ASN.1 GENERALIZEDTIME into out and
+// advances. It reports whether the read was successful.
+func (s *String) ReadASN1GeneralizedTime(out *time.Time) bool {
+ var bytes String
+ if !s.ReadASN1(&bytes, asn1.GeneralizedTime) {
+ return false
+ }
+ t := string(bytes)
+ res, err := time.Parse(generalizedTimeFormatStr, t)
+ if err != nil {
+ return false
+ }
+ if serialized := res.Format(generalizedTimeFormatStr); serialized != t {
+ return false
+ }
+ *out = res
+ return true
+}
+
+const defaultUTCTimeFormatStr = "060102150405Z0700"
+
+// ReadASN1UTCTime decodes an ASN.1 UTCTime into out and advances.
+// It reports whether the read was successful.
+func (s *String) ReadASN1UTCTime(out *time.Time) bool {
+ var bytes String
+ if !s.ReadASN1(&bytes, asn1.UTCTime) {
+ return false
+ }
+ t := string(bytes)
+
+ formatStr := defaultUTCTimeFormatStr
+ var err error
+ res, err := time.Parse(formatStr, t)
+ if err != nil {
+ // Fallback to minute precision if we can't parse second
+ // precision. If we are following X.509 or X.690 we shouldn't
+ // support this, but we do.
+ formatStr = "0601021504Z0700"
+ res, err = time.Parse(formatStr, t)
+ }
+ if err != nil {
+ return false
+ }
+
+ if serialized := res.Format(formatStr); serialized != t {
+ return false
+ }
+
+ if res.Year() >= 2050 {
+ // UTCTime interprets the low order digits 50-99 as 1950-99.
+ // This only applies to its use in the X.509 profile.
+ // See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
+ res = res.AddDate(-100, 0, 0)
+ }
+ *out = res
+ return true
+}
+
+// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances.
+// It reports whether the read was successful.
+func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {
+ var bytes String
+ if !s.ReadASN1(&bytes, asn1.BIT_STRING) || len(bytes) == 0 ||
+ len(bytes)*8/8 != len(bytes) {
+ return false
+ }
+
+ paddingBits := bytes[0]
+ bytes = bytes[1:]
+ if paddingBits > 7 ||
+ len(bytes) == 0 && paddingBits != 0 ||
+ len(bytes) > 0 && bytes[len(bytes)-1]&(1< 4 || len(*s) < int(2+lenLen) {
+ return false
+ }
+
+ lenBytes := String((*s)[2 : 2+lenLen])
+ if !lenBytes.readUnsigned(&len32, int(lenLen)) {
+ return false
+ }
+
+ // ITU-T X.690 section 10.1 (DER length forms) requires encoding the length
+ // with the minimum number of octets.
+ if len32 < 128 {
+ // Length should have used short-form encoding.
+ return false
+ }
+ if len32>>((lenLen-1)*8) == 0 {
+ // Leading octet is 0. Length should have been at least one byte shorter.
+ return false
+ }
+
+ headerLen = 2 + uint32(lenLen)
+ if headerLen+len32 < len32 {
+ // Overflow.
+ return false
+ }
+ length = headerLen + len32
+ }
+
+ if int(length) < 0 || !s.ReadBytes((*[]byte)(out), int(length)) {
+ return false
+ }
+ if skipHeader && !out.Skip(int(headerLen)) {
+ panic("cryptobyte: internal error")
+ }
+
+ return true
+}
diff --git a/local_crypto_patch/contents/cryptobyte/asn1/asn1.go b/local_crypto_patch/contents/cryptobyte/asn1/asn1.go
new file mode 100644
index 0000000000..90ef6a241d
--- /dev/null
+++ b/local_crypto_patch/contents/cryptobyte/asn1/asn1.go
@@ -0,0 +1,46 @@
+// 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 asn1 contains supporting types for parsing and building ASN.1
+// messages with the cryptobyte package.
+package asn1
+
+// Tag represents an ASN.1 identifier octet, consisting of a tag number
+// (indicating a type) and class (such as context-specific or constructed).
+//
+// Methods in the cryptobyte package only support the low-tag-number form, i.e.
+// a single identifier octet with bits 7-8 encoding the class and bits 1-6
+// encoding the tag number.
+type Tag uint8
+
+const (
+ classConstructed = 0x20
+ classContextSpecific = 0x80
+)
+
+// Constructed returns t with the constructed class bit set.
+func (t Tag) Constructed() Tag { return t | classConstructed }
+
+// ContextSpecific returns t with the context-specific class bit set.
+func (t Tag) ContextSpecific() Tag { return t | classContextSpecific }
+
+// The following is a list of standard tag and class combinations.
+const (
+ BOOLEAN = Tag(1)
+ INTEGER = Tag(2)
+ BIT_STRING = Tag(3)
+ OCTET_STRING = Tag(4)
+ NULL = Tag(5)
+ OBJECT_IDENTIFIER = Tag(6)
+ ENUM = Tag(10)
+ UTF8String = Tag(12)
+ SEQUENCE = Tag(16 | classConstructed)
+ SET = Tag(17 | classConstructed)
+ PrintableString = Tag(19)
+ T61String = Tag(20)
+ IA5String = Tag(22)
+ UTCTime = Tag(23)
+ GeneralizedTime = Tag(24)
+ GeneralString = Tag(27)
+)
diff --git a/local_crypto_patch/contents/cryptobyte/asn1_test.go b/local_crypto_patch/contents/cryptobyte/asn1_test.go
new file mode 100644
index 0000000000..93760b06e9
--- /dev/null
+++ b/local_crypto_patch/contents/cryptobyte/asn1_test.go
@@ -0,0 +1,457 @@
+// 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 cryptobyte
+
+import (
+ "bytes"
+ encoding_asn1 "encoding/asn1"
+ "math/big"
+ "reflect"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/cryptobyte/asn1"
+)
+
+type readASN1Test struct {
+ name string
+ in []byte
+ tag asn1.Tag
+ ok bool
+ out interface{}
+}
+
+var readASN1TestData = []readASN1Test{
+ {"valid", []byte{0x30, 2, 1, 2}, 0x30, true, []byte{1, 2}},
+ {"truncated", []byte{0x30, 3, 1, 2}, 0x30, false, nil},
+ {"zero length of length", []byte{0x30, 0x80}, 0x30, false, nil},
+ {"invalid long form length", []byte{0x30, 0x81, 1, 1}, 0x30, false, nil},
+ {"non-minimal length", append([]byte{0x30, 0x82, 0, 0x80}, make([]byte, 0x80)...), 0x30, false, nil},
+ {"invalid tag", []byte{0xa1, 3, 0x4, 1, 1}, 31, false, nil},
+ {"high tag", []byte{0x1f, 0x81, 0x80, 0x01, 2, 1, 2}, 0xff /* actually 0x4001, but tag is uint8 */, false, nil},
+ {"2**31 - 1 length", []byte{0x30, 0x84, 0x7f, 0xff, 0xff, 0xff}, 0x30, false, nil},
+ {"2**32 - 1 length", []byte{0x30, 0x84, 0xff, 0xff, 0xff, 0xff}, 0x30, false, nil},
+ {"2**63 - 1 length", []byte{0x30, 0x88, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0x30, false, nil},
+ {"2**64 - 1 length", []byte{0x30, 0x88, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0x30, false, nil},
+}
+
+func TestReadASN1(t *testing.T) {
+ for _, test := range readASN1TestData {
+ t.Run(test.name, func(t *testing.T) {
+ var in, out String = test.in, nil
+ ok := in.ReadASN1(&out, test.tag)
+ if ok != test.ok || ok && !bytes.Equal(out, test.out.([]byte)) {
+ t.Errorf("in.ReadASN1() = %v, want %v; out = %v, want %v", ok, test.ok, out, test.out)
+ }
+ })
+ }
+}
+
+func TestReadASN1Optional(t *testing.T) {
+ var empty String
+ var present bool
+ ok := empty.ReadOptionalASN1(nil, &present, 0xa0)
+ if !ok || present {
+ t.Errorf("empty.ReadOptionalASN1() = %v, want true; present = %v want false", ok, present)
+ }
+
+ var in, out String = []byte{0xa1, 3, 0x4, 1, 1}, nil
+ ok = in.ReadOptionalASN1(&out, &present, 0xa0)
+ if !ok || present {
+ t.Errorf("in.ReadOptionalASN1() = %v, want true, present = %v, want false", ok, present)
+ }
+ ok = in.ReadOptionalASN1(&out, &present, 0xa1)
+ wantBytes := []byte{4, 1, 1}
+ if !ok || !present || !bytes.Equal(out, wantBytes) {
+ t.Errorf("in.ReadOptionalASN1() = %v, want true; present = %v, want true; out = %v, want = %v", ok, present, out, wantBytes)
+ }
+}
+
+var optionalOctetStringTestData = []struct {
+ readASN1Test
+ present bool
+}{
+ {readASN1Test{"empty", []byte{}, 0xa0, true, []byte{}}, false},
+ {readASN1Test{"invalid", []byte{0xa1, 3, 0x4, 2, 1}, 0xa1, false, []byte{}}, true},
+ {readASN1Test{"missing", []byte{0xa1, 3, 0x4, 1, 1}, 0xa0, true, []byte{}}, false},
+ {readASN1Test{"present", []byte{0xa1, 3, 0x4, 1, 1}, 0xa1, true, []byte{1}}, true},
+}
+
+func TestReadASN1OptionalOctetString(t *testing.T) {
+ for _, test := range optionalOctetStringTestData {
+ t.Run(test.name, func(t *testing.T) {
+ in := String(test.in)
+ var out []byte
+ var present bool
+ ok := in.ReadOptionalASN1OctetString(&out, &present, test.tag)
+ if ok != test.ok || present != test.present || !bytes.Equal(out, test.out.([]byte)) {
+ t.Errorf("in.ReadOptionalASN1OctetString() = %v, want %v; present = %v want %v; out = %v, want %v", ok, test.ok, present, test.present, out, test.out)
+ }
+ })
+ }
+}
+
+const defaultInt = -1
+
+var optionalIntTestData = []readASN1Test{
+ {"empty", []byte{}, 0xa0, true, defaultInt},
+ {"invalid", []byte{0xa1, 3, 0x2, 2, 127}, 0xa1, false, 0},
+ {"missing", []byte{0xa1, 3, 0x2, 1, 127}, 0xa0, true, defaultInt},
+ {"present", []byte{0xa1, 3, 0x2, 1, 42}, 0xa1, true, 42},
+}
+
+func TestReadASN1OptionalInteger(t *testing.T) {
+ for _, test := range optionalIntTestData {
+ t.Run(test.name, func(t *testing.T) {
+ in := String(test.in)
+ var out int
+ ok := in.ReadOptionalASN1Integer(&out, test.tag, defaultInt)
+ if ok != test.ok || ok && out != test.out.(int) {
+ t.Errorf("in.ReadOptionalASN1Integer() = %v, want %v; out = %v, want %v", ok, test.ok, out, test.out)
+ }
+ })
+ }
+}
+
+const defaultBool = false
+
+var optionalBoolTestData = []readASN1Test{
+ {"empty", []byte{}, 0xa0, true, false},
+ {"invalid", []byte{0xa1, 0x3, 0x1, 0x2, 0x7f}, 0xa1, false, defaultBool},
+ {"missing", []byte{0xa1, 0x3, 0x1, 0x1, 0x7f}, 0xa0, true, defaultBool},
+ {"present", []byte{0xa1, 0x3, 0x1, 0x1, 0xff}, 0xa1, true, true},
+}
+
+func TestReadASN1OptionalBoolean(t *testing.T) {
+ for _, test := range optionalBoolTestData {
+ t.Run(test.name, func(t *testing.T) {
+ in := String(test.in)
+ var out bool
+ ok := in.ReadOptionalASN1Boolean(&out, test.tag, defaultBool)
+ if ok != test.ok || ok && out != test.out.(bool) {
+ t.Errorf("in.ReadOptionalASN1Boolean() = %v, want %v; out = %v, want %v", ok, test.ok, out, test.out)
+ }
+ })
+ }
+}
+
+func TestReadASN1IntegerSigned(t *testing.T) {
+ testData64 := []struct {
+ in []byte
+ out int64
+ }{
+ {[]byte{2, 3, 128, 0, 0}, -0x800000},
+ {[]byte{2, 2, 255, 0}, -256},
+ {[]byte{2, 2, 255, 127}, -129},
+ {[]byte{2, 1, 128}, -128},
+ {[]byte{2, 1, 255}, -1},
+ {[]byte{2, 1, 0}, 0},
+ {[]byte{2, 1, 1}, 1},
+ {[]byte{2, 1, 2}, 2},
+ {[]byte{2, 1, 127}, 127},
+ {[]byte{2, 2, 0, 128}, 128},
+ {[]byte{2, 2, 1, 0}, 256},
+ {[]byte{2, 4, 0, 128, 0, 0}, 0x800000},
+ }
+ for i, test := range testData64 {
+ in := String(test.in)
+ var out int64
+ ok := in.ReadASN1Integer(&out)
+ if !ok || out != test.out {
+ t.Errorf("#%d: in.ReadASN1Integer() = %v, want true; out = %d, want %d", i, ok, out, test.out)
+ }
+ }
+
+ // Repeat the same cases, reading into a big.Int.
+ t.Run("big.Int", func(t *testing.T) {
+ for i, test := range testData64 {
+ in := String(test.in)
+ var out big.Int
+ ok := in.ReadASN1Integer(&out)
+ if !ok || out.Int64() != test.out {
+ t.Errorf("#%d: in.ReadASN1Integer() = %v, want true; out = %d, want %d", i, ok, out.Int64(), test.out)
+ }
+ }
+ })
+
+ // Repeat the same cases, reading into a []byte.
+ t.Run("bytes", func(t *testing.T) {
+ for i, test := range testData64 {
+ in := String(test.in)
+ var out []byte
+ ok := in.ReadASN1Integer(&out)
+ if test.out < 0 {
+ if ok {
+ t.Errorf("#%d: in.ReadASN1Integer(%d) = %v, want false", i, test.out, ok)
+ }
+ continue
+ }
+ if !ok {
+ t.Errorf("#%d: in.ReadASN1Integer() = %v, want true", i, ok)
+ continue
+ }
+ n := new(big.Int).SetBytes(out).Int64()
+ if n != test.out {
+ t.Errorf("#%d: in.ReadASN1Integer() = %v, want true; out = %x, want %d", i, ok, out, test.out)
+ }
+ if out[0] == 0 && len(out) > 1 {
+ t.Errorf("#%d: in.ReadASN1Integer() = %v; out = %x, has leading zeroes", i, ok, out)
+ }
+ }
+ })
+
+ // Repeat with the implicit-tagging functions
+ t.Run("WithTag", func(t *testing.T) {
+ for i, test := range testData64 {
+ tag := asn1.Tag((i * 3) % 32).ContextSpecific()
+
+ testData := make([]byte, len(test.in))
+ copy(testData, test.in)
+
+ // Alter the tag of the test case.
+ testData[0] = uint8(tag)
+
+ in := String(testData)
+ var out int64
+ ok := in.ReadASN1Int64WithTag(&out, tag)
+ if !ok || out != test.out {
+ t.Errorf("#%d: in.ReadASN1Int64WithTag() = %v, want true; out = %d, want %d", i, ok, out, test.out)
+ }
+
+ var b Builder
+ b.AddASN1Int64WithTag(test.out, tag)
+ result, err := b.Bytes()
+
+ if err != nil {
+ t.Errorf("#%d: AddASN1Int64WithTag failed: %s", i, err)
+ continue
+ }
+
+ if !bytes.Equal(result, testData) {
+ t.Errorf("#%d: AddASN1Int64WithTag: got %x, want %x", i, result, testData)
+ }
+ }
+ })
+}
+
+func TestReadASN1IntegerUnsigned(t *testing.T) {
+ testData := []struct {
+ in []byte
+ out uint64
+ }{
+ {[]byte{2, 1, 0}, 0},
+ {[]byte{2, 1, 1}, 1},
+ {[]byte{2, 1, 2}, 2},
+ {[]byte{2, 1, 127}, 127},
+ {[]byte{2, 2, 0, 128}, 128},
+ {[]byte{2, 2, 1, 0}, 256},
+ {[]byte{2, 4, 0, 128, 0, 0}, 0x800000},
+ {[]byte{2, 8, 127, 255, 255, 255, 255, 255, 255, 255}, 0x7fffffffffffffff},
+ {[]byte{2, 9, 0, 128, 0, 0, 0, 0, 0, 0, 0}, 0x8000000000000000},
+ {[]byte{2, 9, 0, 255, 255, 255, 255, 255, 255, 255, 255}, 0xffffffffffffffff},
+ }
+ for i, test := range testData {
+ in := String(test.in)
+ var out uint64
+ ok := in.ReadASN1Integer(&out)
+ if !ok || out != test.out {
+ t.Errorf("#%d: in.ReadASN1Integer() = %v, want true; out = %d, want %d", i, ok, out, test.out)
+ }
+ }
+}
+
+func TestReadASN1IntegerInvalid(t *testing.T) {
+ testData := []String{
+ []byte{3, 1, 0}, // invalid tag
+ // truncated
+ []byte{2, 1},
+ []byte{2, 2, 0},
+ // not minimally encoded
+ []byte{2, 2, 0, 1},
+ []byte{2, 2, 0xff, 0xff},
+ }
+
+ for i, test := range testData {
+ var out int64
+ if test.ReadASN1Integer(&out) {
+ t.Errorf("#%d: in.ReadASN1Integer() = true, want false (out = %d)", i, out)
+ }
+ }
+}
+
+func TestASN1ObjectIdentifier(t *testing.T) {
+ testData := []struct {
+ in []byte
+ ok bool
+ out []int
+ }{
+ {[]byte{}, false, []int{}},
+ {[]byte{6, 0}, false, []int{}},
+ {[]byte{5, 1, 85}, false, []int{2, 5}},
+ {[]byte{6, 1, 85}, true, []int{2, 5}},
+ {[]byte{6, 2, 85, 0x02}, true, []int{2, 5, 2}},
+ {[]byte{6, 4, 85, 0x02, 0xc0, 0x00}, true, []int{2, 5, 2, 0x2000}},
+ {[]byte{6, 3, 0x81, 0x34, 0x03}, true, []int{2, 100, 3}},
+ {[]byte{6, 7, 85, 0x02, 0xc0, 0x80, 0x80, 0x80, 0x80}, false, []int{}},
+ {[]byte{6, 7, 85, 0x02, 0x85, 0xc7, 0xcc, 0xfb, 0x01}, true, []int{2, 5, 2, 1492336001}},
+ {[]byte{6, 7, 0x55, 0x02, 0x87, 0xff, 0xff, 0xff, 0x7f}, true, []int{2, 5, 2, 2147483647}}, // 2**31-1
+ {[]byte{6, 7, 0x55, 0x02, 0x88, 0x80, 0x80, 0x80, 0x00}, false, []int{}}, // 2**31
+ {[]byte{6, 3, 85, 0x80, 0x02}, false, []int{}}, // leading 0x80 octet
+ }
+
+ for i, test := range testData {
+ in := String(test.in)
+ var out encoding_asn1.ObjectIdentifier
+ ok := in.ReadASN1ObjectIdentifier(&out)
+ if ok != test.ok || ok && !out.Equal(test.out) {
+ t.Errorf("#%d: in.ReadASN1ObjectIdentifier() = %v, want %v; out = %v, want %v", i, ok, test.ok, out, test.out)
+ continue
+ }
+
+ var b Builder
+ b.AddASN1ObjectIdentifier(out)
+ result, err := b.Bytes()
+ if builderOk := err == nil; test.ok != builderOk {
+ t.Errorf("#%d: error from Builder.Bytes: %s", i, err)
+ continue
+ }
+ if test.ok && !bytes.Equal(result, test.in) {
+ t.Errorf("#%d: reserialisation didn't match, got %x, want %x", i, result, test.in)
+ continue
+ }
+ }
+}
+
+func TestReadASN1GeneralizedTime(t *testing.T) {
+ testData := []struct {
+ in string
+ ok bool
+ out time.Time
+ }{
+ {"20100102030405Z", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.UTC)},
+ {"20100102030405", false, time.Time{}},
+ {"20100102030405+0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", 6*60*60+7*60))},
+ {"20100102030405-0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", -6*60*60-7*60))},
+ /* These are invalid times. However, the time package normalises times
+ * and they were accepted in some versions. See #11134. */
+ {"00000100000000Z", false, time.Time{}},
+ {"20101302030405Z", false, time.Time{}},
+ {"20100002030405Z", false, time.Time{}},
+ {"20100100030405Z", false, time.Time{}},
+ {"20100132030405Z", false, time.Time{}},
+ {"20100231030405Z", false, time.Time{}},
+ {"20100102240405Z", false, time.Time{}},
+ {"20100102036005Z", false, time.Time{}},
+ {"20100102030460Z", false, time.Time{}},
+ {"-20100102030410Z", false, time.Time{}},
+ {"2010-0102030410Z", false, time.Time{}},
+ {"2010-0002030410Z", false, time.Time{}},
+ {"201001-02030410Z", false, time.Time{}},
+ {"20100102-030410Z", false, time.Time{}},
+ {"2010010203-0410Z", false, time.Time{}},
+ {"201001020304-10Z", false, time.Time{}},
+ }
+ for i, test := range testData {
+ in := String(append([]byte{byte(asn1.GeneralizedTime), byte(len(test.in))}, test.in...))
+ var out time.Time
+ ok := in.ReadASN1GeneralizedTime(&out)
+ if ok != test.ok || ok && !reflect.DeepEqual(out, test.out) {
+ t.Errorf("#%d: in.ReadASN1GeneralizedTime() = %v, want %v; out = %q, want %q", i, ok, test.ok, out, test.out)
+ }
+ }
+}
+
+func TestReadASN1UTCTime(t *testing.T) {
+ testData := []struct {
+ in string
+ ok bool
+ out time.Time
+ }{
+ {"000102030405Z", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.UTC)},
+ {"500102030405Z", true, time.Date(1950, 01, 02, 03, 04, 05, 0, time.UTC)},
+ {"490102030405Z", true, time.Date(2049, 01, 02, 03, 04, 05, 0, time.UTC)},
+ {"990102030405Z", true, time.Date(1999, 01, 02, 03, 04, 05, 0, time.UTC)},
+ {"250102030405Z", true, time.Date(2025, 01, 02, 03, 04, 05, 0, time.UTC)},
+ {"750102030405Z", true, time.Date(1975, 01, 02, 03, 04, 05, 0, time.UTC)},
+ {"000102030405+0905", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.FixedZone("", 9*60*60+5*60))},
+ {"000102030405-0905", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.FixedZone("", -9*60*60-5*60))},
+ {"0001020304Z", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.UTC)},
+ {"5001020304Z", true, time.Date(1950, 01, 02, 03, 04, 00, 0, time.UTC)},
+ {"0001020304+0905", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.FixedZone("", 9*60*60+5*60))},
+ {"0001020304-0905", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.FixedZone("", -9*60*60-5*60))},
+ {"000102030405Z0700", false, time.Time{}},
+ {"000102030405", false, time.Time{}},
+ }
+ for i, test := range testData {
+ in := String(append([]byte{byte(asn1.UTCTime), byte(len(test.in))}, test.in...))
+ var out time.Time
+ ok := in.ReadASN1UTCTime(&out)
+ if ok != test.ok || ok && !reflect.DeepEqual(out, test.out) {
+ t.Errorf("#%d: in.ReadASN1UTCTime() = %v, want %v; out = %q, want %q", i, ok, test.ok, out, test.out)
+ }
+ }
+}
+
+func TestReadASN1BitString(t *testing.T) {
+ testData := []struct {
+ in []byte
+ ok bool
+ out encoding_asn1.BitString
+ }{
+ {[]byte{}, false, encoding_asn1.BitString{}},
+ {[]byte{0x00}, true, encoding_asn1.BitString{}},
+ {[]byte{0x07, 0x00}, true, encoding_asn1.BitString{Bytes: []byte{0}, BitLength: 1}},
+ {[]byte{0x07, 0x01}, false, encoding_asn1.BitString{}},
+ {[]byte{0x07, 0x40}, false, encoding_asn1.BitString{}},
+ {[]byte{0x08, 0x00}, false, encoding_asn1.BitString{}},
+ {[]byte{0xff}, false, encoding_asn1.BitString{}},
+ {[]byte{0xfe, 0x00}, false, encoding_asn1.BitString{}},
+ }
+ for i, test := range testData {
+ in := String(append([]byte{3, byte(len(test.in))}, test.in...))
+ var out encoding_asn1.BitString
+ ok := in.ReadASN1BitString(&out)
+ if ok != test.ok || ok && (!bytes.Equal(out.Bytes, test.out.Bytes) || out.BitLength != test.out.BitLength) {
+ t.Errorf("#%d: in.ReadASN1BitString() = %v, want %v; out = %v, want %v", i, ok, test.ok, out, test.out)
+ }
+ }
+}
+
+func TestAddASN1BigInt(t *testing.T) {
+ x := big.NewInt(-1)
+ var b Builder
+ b.AddASN1BigInt(x)
+ got, err := b.Bytes()
+ if err != nil {
+ t.Fatalf("unexpected error adding -1: %v", err)
+ }
+ s := String(got)
+ var y big.Int
+ ok := s.ReadASN1Integer(&y)
+ if !ok || x.Cmp(&y) != 0 {
+ t.Errorf("unexpected bytes %v, want %v", &y, x)
+ }
+}
+
+func TestReadASN1Boolean(t *testing.T) {
+ testData := []struct {
+ in []byte
+ ok bool
+ out bool
+ }{
+ {[]byte{}, false, false},
+ {[]byte{0x01, 0x01, 0x00}, true, false},
+ {[]byte{0x01, 0x01, 0xff}, true, true},
+ {[]byte{0x01, 0x01, 0x01}, false, false},
+ }
+ for i, test := range testData {
+ in := String(test.in)
+ var out bool
+ ok := in.ReadASN1Boolean(&out)
+ if ok != test.ok || ok && (out != test.out) {
+ t.Errorf("#%d: in.ReadASN1Boolean() = %v, want %v; out = %v, want %v", i, ok, test.ok, out, test.out)
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/cryptobyte/builder.go b/local_crypto_patch/contents/cryptobyte/builder.go
new file mode 100644
index 0000000000..cf254f5f1e
--- /dev/null
+++ b/local_crypto_patch/contents/cryptobyte/builder.go
@@ -0,0 +1,350 @@
+// 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 cryptobyte
+
+import (
+ "errors"
+ "fmt"
+)
+
+// A Builder builds byte strings from fixed-length and length-prefixed values.
+// Builders either allocate space as needed, or are ‘fixed’, which means that
+// they write into a given buffer and produce an error if it's exhausted.
+//
+// The zero value is a usable Builder that allocates space as needed.
+//
+// Simple values are marshaled and appended to a Builder using methods on the
+// Builder. Length-prefixed values are marshaled by providing a
+// BuilderContinuation, which is a function that writes the inner contents of
+// the value to a given Builder. See the documentation for BuilderContinuation
+// for details.
+type Builder struct {
+ err error
+ result []byte
+ fixedSize bool
+ child *Builder
+ offset int
+ pendingLenLen int
+ pendingIsASN1 bool
+ inContinuation *bool
+}
+
+// NewBuilder creates a Builder that appends its output to the given buffer.
+// Like append(), the slice will be reallocated if its capacity is exceeded.
+// Use Bytes to get the final buffer.
+func NewBuilder(buffer []byte) *Builder {
+ return &Builder{
+ result: buffer,
+ }
+}
+
+// NewFixedBuilder creates a Builder that appends its output into the given
+// buffer. This builder does not reallocate the output buffer. Writes that
+// would exceed the buffer's capacity are treated as an error.
+func NewFixedBuilder(buffer []byte) *Builder {
+ return &Builder{
+ result: buffer,
+ fixedSize: true,
+ }
+}
+
+// SetError sets the value to be returned as the error from Bytes. Writes
+// performed after calling SetError are ignored.
+func (b *Builder) SetError(err error) {
+ b.err = err
+}
+
+// Bytes returns the bytes written by the builder or an error if one has
+// occurred during building.
+func (b *Builder) Bytes() ([]byte, error) {
+ if b.err != nil {
+ return nil, b.err
+ }
+ return b.result[b.offset:], nil
+}
+
+// BytesOrPanic returns the bytes written by the builder or panics if an error
+// has occurred during building.
+func (b *Builder) BytesOrPanic() []byte {
+ if b.err != nil {
+ panic(b.err)
+ }
+ return b.result[b.offset:]
+}
+
+// AddUint8 appends an 8-bit value to the byte string.
+func (b *Builder) AddUint8(v uint8) {
+ b.add(byte(v))
+}
+
+// AddUint16 appends a big-endian, 16-bit value to the byte string.
+func (b *Builder) AddUint16(v uint16) {
+ b.add(byte(v>>8), byte(v))
+}
+
+// AddUint24 appends a big-endian, 24-bit value to the byte string. The highest
+// byte of the 32-bit input value is silently truncated.
+func (b *Builder) AddUint24(v uint32) {
+ b.add(byte(v>>16), byte(v>>8), byte(v))
+}
+
+// AddUint32 appends a big-endian, 32-bit value to the byte string.
+func (b *Builder) AddUint32(v uint32) {
+ b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
+}
+
+// AddUint48 appends a big-endian, 48-bit value to the byte string.
+func (b *Builder) AddUint48(v uint64) {
+ b.add(byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
+}
+
+// AddUint64 appends a big-endian, 64-bit value to the byte string.
+func (b *Builder) AddUint64(v uint64) {
+ b.add(byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
+}
+
+// AddBytes appends a sequence of bytes to the byte string.
+func (b *Builder) AddBytes(v []byte) {
+ b.add(v...)
+}
+
+// BuilderContinuation is a continuation-passing interface for building
+// length-prefixed byte sequences. Builder methods for length-prefixed
+// sequences (AddUint8LengthPrefixed etc) will invoke the BuilderContinuation
+// supplied to them. The child builder passed to the continuation can be used
+// to build the content of the length-prefixed sequence. For example:
+//
+// parent := cryptobyte.NewBuilder()
+// parent.AddUint8LengthPrefixed(func (child *Builder) {
+// child.AddUint8(42)
+// child.AddUint8LengthPrefixed(func (grandchild *Builder) {
+// grandchild.AddUint8(5)
+// })
+// })
+//
+// It is an error to write more bytes to the child than allowed by the reserved
+// length prefix. After the continuation returns, the child must be considered
+// invalid, i.e. users must not store any copies or references of the child
+// that outlive the continuation.
+//
+// If the continuation panics with a value of type BuildError then the inner
+// error will be returned as the error from Bytes. If the child panics
+// otherwise then Bytes will repanic with the same value.
+type BuilderContinuation func(child *Builder)
+
+// BuildError wraps an error. If a BuilderContinuation panics with this value,
+// the panic will be recovered and the inner error will be returned from
+// Builder.Bytes.
+type BuildError struct {
+ Err error
+}
+
+// AddUint8LengthPrefixed adds a 8-bit length-prefixed byte sequence.
+func (b *Builder) AddUint8LengthPrefixed(f BuilderContinuation) {
+ b.addLengthPrefixed(1, false, f)
+}
+
+// AddUint16LengthPrefixed adds a big-endian, 16-bit length-prefixed byte sequence.
+func (b *Builder) AddUint16LengthPrefixed(f BuilderContinuation) {
+ b.addLengthPrefixed(2, false, f)
+}
+
+// AddUint24LengthPrefixed adds a big-endian, 24-bit length-prefixed byte sequence.
+func (b *Builder) AddUint24LengthPrefixed(f BuilderContinuation) {
+ b.addLengthPrefixed(3, false, f)
+}
+
+// AddUint32LengthPrefixed adds a big-endian, 32-bit length-prefixed byte sequence.
+func (b *Builder) AddUint32LengthPrefixed(f BuilderContinuation) {
+ b.addLengthPrefixed(4, false, f)
+}
+
+func (b *Builder) callContinuation(f BuilderContinuation, arg *Builder) {
+ if !*b.inContinuation {
+ *b.inContinuation = true
+
+ defer func() {
+ *b.inContinuation = false
+
+ r := recover()
+ if r == nil {
+ return
+ }
+
+ if buildError, ok := r.(BuildError); ok {
+ b.err = buildError.Err
+ } else {
+ panic(r)
+ }
+ }()
+ }
+
+ f(arg)
+}
+
+func (b *Builder) addLengthPrefixed(lenLen int, isASN1 bool, f BuilderContinuation) {
+ // Subsequent writes can be ignored if the builder has encountered an error.
+ if b.err != nil {
+ return
+ }
+
+ offset := len(b.result)
+ b.add(make([]byte, lenLen)...)
+
+ if b.inContinuation == nil {
+ b.inContinuation = new(bool)
+ }
+
+ b.child = &Builder{
+ result: b.result,
+ fixedSize: b.fixedSize,
+ offset: offset,
+ pendingLenLen: lenLen,
+ pendingIsASN1: isASN1,
+ inContinuation: b.inContinuation,
+ }
+
+ b.callContinuation(f, b.child)
+ b.flushChild()
+ if b.child != nil {
+ panic("cryptobyte: internal error")
+ }
+}
+
+func (b *Builder) flushChild() {
+ if b.child == nil {
+ return
+ }
+ b.child.flushChild()
+ child := b.child
+ b.child = nil
+
+ if child.err != nil {
+ b.err = child.err
+ return
+ }
+
+ length := len(child.result) - child.pendingLenLen - child.offset
+
+ if length < 0 {
+ panic("cryptobyte: internal error") // result unexpectedly shrunk
+ }
+
+ if child.pendingIsASN1 {
+ // For ASN.1, we reserved a single byte for the length. If that turned out
+ // to be incorrect, we have to move the contents along in order to make
+ // space.
+ if child.pendingLenLen != 1 {
+ panic("cryptobyte: internal error")
+ }
+ var lenLen, lenByte uint8
+ if int64(length) > 0xfffffffe {
+ b.err = errors.New("pending ASN.1 child too long")
+ return
+ } else if length > 0xffffff {
+ lenLen = 5
+ lenByte = 0x80 | 4
+ } else if length > 0xffff {
+ lenLen = 4
+ lenByte = 0x80 | 3
+ } else if length > 0xff {
+ lenLen = 3
+ lenByte = 0x80 | 2
+ } else if length > 0x7f {
+ lenLen = 2
+ lenByte = 0x80 | 1
+ } else {
+ lenLen = 1
+ lenByte = uint8(length)
+ length = 0
+ }
+
+ // Insert the initial length byte, make space for successive length bytes,
+ // and adjust the offset.
+ child.result[child.offset] = lenByte
+ extraBytes := int(lenLen - 1)
+ if extraBytes != 0 {
+ child.add(make([]byte, extraBytes)...)
+ childStart := child.offset + child.pendingLenLen
+ copy(child.result[childStart+extraBytes:], child.result[childStart:])
+ }
+ child.offset++
+ child.pendingLenLen = extraBytes
+ }
+
+ l := length
+ for i := child.pendingLenLen - 1; i >= 0; i-- {
+ child.result[child.offset+i] = uint8(l)
+ l >>= 8
+ }
+ if l != 0 {
+ b.err = fmt.Errorf("cryptobyte: pending child length %d exceeds %d-byte length prefix", length, child.pendingLenLen)
+ return
+ }
+
+ if b.fixedSize && &b.result[0] != &child.result[0] {
+ panic("cryptobyte: BuilderContinuation reallocated a fixed-size buffer")
+ }
+
+ b.result = child.result
+}
+
+func (b *Builder) add(bytes ...byte) {
+ if b.err != nil {
+ return
+ }
+ if b.child != nil {
+ panic("cryptobyte: attempted write while child is pending")
+ }
+ if len(b.result)+len(bytes) < len(bytes) {
+ b.err = errors.New("cryptobyte: length overflow")
+ }
+ if b.fixedSize && len(b.result)+len(bytes) > cap(b.result) {
+ b.err = errors.New("cryptobyte: Builder is exceeding its fixed-size buffer")
+ return
+ }
+ b.result = append(b.result, bytes...)
+}
+
+// Unwrite rolls back non-negative n bytes written directly to the Builder.
+// An attempt by a child builder passed to a continuation to unwrite bytes
+// from its parent will panic.
+func (b *Builder) Unwrite(n int) {
+ if b.err != nil {
+ return
+ }
+ if b.child != nil {
+ panic("cryptobyte: attempted unwrite while child is pending")
+ }
+ length := len(b.result) - b.pendingLenLen - b.offset
+ if length < 0 {
+ panic("cryptobyte: internal error")
+ }
+ if n < 0 {
+ panic("cryptobyte: attempted to unwrite negative number of bytes")
+ }
+ if n > length {
+ panic("cryptobyte: attempted to unwrite more than was written")
+ }
+ b.result = b.result[:len(b.result)-n]
+}
+
+// A MarshalingValue marshals itself into a Builder.
+type MarshalingValue interface {
+ // Marshal is called by Builder.AddValue. It receives a pointer to a builder
+ // to marshal itself into. It may return an error that occurred during
+ // marshaling, such as unset or invalid values.
+ Marshal(b *Builder) error
+}
+
+// AddValue calls Marshal on v, passing a pointer to the builder to append to.
+// If Marshal returns an error, it is set on the Builder so that subsequent
+// appends don't have an effect.
+func (b *Builder) AddValue(v MarshalingValue) {
+ err := v.Marshal(b)
+ if err != nil {
+ b.err = err
+ }
+}
diff --git a/local_crypto_patch/contents/cryptobyte/cryptobyte_test.go b/local_crypto_patch/contents/cryptobyte/cryptobyte_test.go
new file mode 100644
index 0000000000..9b55d52e32
--- /dev/null
+++ b/local_crypto_patch/contents/cryptobyte/cryptobyte_test.go
@@ -0,0 +1,557 @@
+// 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 cryptobyte
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "testing"
+)
+
+func builderBytesEq(b *Builder, want ...byte) error {
+ got := b.BytesOrPanic()
+ if !bytes.Equal(got, want) {
+ return fmt.Errorf("Bytes() = %v, want %v", got, want)
+ }
+ return nil
+}
+
+func TestContinuationError(t *testing.T) {
+ const errorStr = "TestContinuationError"
+ var b Builder
+ b.AddUint8LengthPrefixed(func(b *Builder) {
+ b.AddUint8(1)
+ panic(BuildError{Err: errors.New(errorStr)})
+ })
+
+ ret, err := b.Bytes()
+ if ret != nil {
+ t.Error("expected nil result")
+ }
+ if err == nil {
+ t.Fatal("unexpected nil error")
+ }
+ if s := err.Error(); s != errorStr {
+ t.Errorf("expected error %q, got %v", errorStr, s)
+ }
+}
+
+func TestContinuationNonError(t *testing.T) {
+ defer func() {
+ recover()
+ }()
+
+ var b Builder
+ b.AddUint8LengthPrefixed(func(b *Builder) {
+ b.AddUint8(1)
+ panic(1)
+ })
+
+ t.Error("Builder did not panic")
+}
+
+func TestGeneratedPanic(t *testing.T) {
+ defer func() {
+ recover()
+ }()
+
+ var b Builder
+ b.AddUint8LengthPrefixed(func(b *Builder) {
+ var p *byte
+ *p = 0
+ })
+
+ t.Error("Builder did not panic")
+}
+
+func TestBytes(t *testing.T) {
+ var b Builder
+ v := []byte("foobarbaz")
+ b.AddBytes(v[0:3])
+ b.AddBytes(v[3:4])
+ b.AddBytes(v[4:9])
+ if err := builderBytesEq(&b, v...); err != nil {
+ t.Error(err)
+ }
+ s := String(b.BytesOrPanic())
+ for _, w := range []string{"foo", "bar", "baz"} {
+ var got []byte
+ if !s.ReadBytes(&got, 3) {
+ t.Errorf("ReadBytes() = false, want true (w = %v)", w)
+ }
+ want := []byte(w)
+ if !bytes.Equal(got, want) {
+ t.Errorf("ReadBytes(): got = %v, want %v", got, want)
+ }
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+}
+
+func TestUint8(t *testing.T) {
+ var b Builder
+ b.AddUint8(42)
+ if err := builderBytesEq(&b, 42); err != nil {
+ t.Error(err)
+ }
+
+ var s String = b.BytesOrPanic()
+ var v uint8
+ if !s.ReadUint8(&v) {
+ t.Error("ReadUint8() = false, want true")
+ }
+ if v != 42 {
+ t.Errorf("v = %d, want 42", v)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+}
+
+func TestUint16(t *testing.T) {
+ var b Builder
+ b.AddUint16(65534)
+ if err := builderBytesEq(&b, 255, 254); err != nil {
+ t.Error(err)
+ }
+ var s String = b.BytesOrPanic()
+ var v uint16
+ if !s.ReadUint16(&v) {
+ t.Error("ReadUint16() == false, want true")
+ }
+ if v != 65534 {
+ t.Errorf("v = %d, want 65534", v)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+}
+
+func TestUint24(t *testing.T) {
+ var b Builder
+ b.AddUint24(0xfffefd)
+ if err := builderBytesEq(&b, 255, 254, 253); err != nil {
+ t.Error(err)
+ }
+
+ var s String = b.BytesOrPanic()
+ var v uint32
+ if !s.ReadUint24(&v) {
+ t.Error("ReadUint24() = false, want true")
+ }
+ if v != 0xfffefd {
+ t.Errorf("v = %d, want fffefd", v)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+}
+
+func TestUint24Truncation(t *testing.T) {
+ var b Builder
+ b.AddUint24(0x10111213)
+ if err := builderBytesEq(&b, 0x11, 0x12, 0x13); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestUint32(t *testing.T) {
+ var b Builder
+ b.AddUint32(0xfffefdfc)
+ if err := builderBytesEq(&b, 255, 254, 253, 252); err != nil {
+ t.Error(err)
+ }
+
+ var s String = b.BytesOrPanic()
+ var v uint32
+ if !s.ReadUint32(&v) {
+ t.Error("ReadUint32() = false, want true")
+ }
+ if v != 0xfffefdfc {
+ t.Errorf("v = %x, want fffefdfc", v)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+}
+
+func TestUint48(t *testing.T) {
+ var b Builder
+ var u uint64 = 0xfefcff3cfdfc
+ b.AddUint48(u)
+ if err := builderBytesEq(&b, 254, 252, 255, 60, 253, 252); err != nil {
+ t.Error(err)
+ }
+
+ var s String = b.BytesOrPanic()
+ var v uint64
+ if !s.ReadUint48(&v) {
+ t.Error("ReadUint48() = false, want true")
+ }
+ if v != u {
+ t.Errorf("v = %x, want %x", v, u)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+}
+
+func TestUint64(t *testing.T) {
+ var b Builder
+ b.AddUint64(0xf2fefefcff3cfdfc)
+ if err := builderBytesEq(&b, 242, 254, 254, 252, 255, 60, 253, 252); err != nil {
+ t.Error(err)
+ }
+
+ var s String = b.BytesOrPanic()
+ var v uint64
+ if !s.ReadUint64(&v) {
+ t.Error("ReadUint64() = false, want true")
+ }
+ if v != 0xf2fefefcff3cfdfc {
+ t.Errorf("v = %x, want f2fefefcff3cfdfc", v)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+}
+
+func TestUMultiple(t *testing.T) {
+ var b Builder
+ b.AddUint8(23)
+ b.AddUint32(0xfffefdfc)
+ b.AddUint16(42)
+ if err := builderBytesEq(&b, 23, 255, 254, 253, 252, 0, 42); err != nil {
+ t.Error(err)
+ }
+
+ var s String = b.BytesOrPanic()
+ var (
+ x uint8
+ y uint32
+ z uint16
+ )
+ if !s.ReadUint8(&x) || !s.ReadUint32(&y) || !s.ReadUint16(&z) {
+ t.Error("ReadUint8() = false, want true")
+ }
+ if x != 23 || y != 0xfffefdfc || z != 42 {
+ t.Errorf("x, y, z = %d, %d, %d; want 23, 4294901244, 5", x, y, z)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+}
+
+func TestUint8LengthPrefixedSimple(t *testing.T) {
+ var b Builder
+ b.AddUint8LengthPrefixed(func(c *Builder) {
+ c.AddUint8(23)
+ c.AddUint8(42)
+ })
+ if err := builderBytesEq(&b, 2, 23, 42); err != nil {
+ t.Error(err)
+ }
+
+ var base, child String = b.BytesOrPanic(), nil
+ var x, y uint8
+ if !base.ReadUint8LengthPrefixed(&child) || !child.ReadUint8(&x) ||
+ !child.ReadUint8(&y) {
+ t.Error("parsing failed")
+ }
+ if x != 23 || y != 42 {
+ t.Errorf("want x, y == 23, 42; got %d, %d", x, y)
+ }
+ if len(base) != 0 {
+ t.Errorf("len(base) = %d, want 0", len(base))
+ }
+ if len(child) != 0 {
+ t.Errorf("len(child) = %d, want 0", len(child))
+ }
+}
+
+func TestUint8LengthPrefixedMulti(t *testing.T) {
+ var b Builder
+ b.AddUint8LengthPrefixed(func(c *Builder) {
+ c.AddUint8(23)
+ c.AddUint8(42)
+ })
+ b.AddUint8(5)
+ b.AddUint8LengthPrefixed(func(c *Builder) {
+ c.AddUint8(123)
+ c.AddUint8(234)
+ })
+ if err := builderBytesEq(&b, 2, 23, 42, 5, 2, 123, 234); err != nil {
+ t.Error(err)
+ }
+
+ var s, child String = b.BytesOrPanic(), nil
+ var u, v, w, x, y uint8
+ if !s.ReadUint8LengthPrefixed(&child) || !child.ReadUint8(&u) || !child.ReadUint8(&v) ||
+ !s.ReadUint8(&w) || !s.ReadUint8LengthPrefixed(&child) || !child.ReadUint8(&x) || !child.ReadUint8(&y) {
+ t.Error("parsing failed")
+ }
+ if u != 23 || v != 42 || w != 5 || x != 123 || y != 234 {
+ t.Errorf("u, v, w, x, y = %d, %d, %d, %d, %d; want 23, 42, 5, 123, 234",
+ u, v, w, x, y)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+ if len(child) != 0 {
+ t.Errorf("len(child) = %d, want 0", len(child))
+ }
+}
+
+func TestUint8LengthPrefixedNested(t *testing.T) {
+ var b Builder
+ b.AddUint8LengthPrefixed(func(c *Builder) {
+ c.AddUint8(5)
+ c.AddUint8LengthPrefixed(func(d *Builder) {
+ d.AddUint8(23)
+ d.AddUint8(42)
+ })
+ c.AddUint8(123)
+ })
+ if err := builderBytesEq(&b, 5, 5, 2, 23, 42, 123); err != nil {
+ t.Error(err)
+ }
+
+ var base, child1, child2 String = b.BytesOrPanic(), nil, nil
+ var u, v, w, x uint8
+ if !base.ReadUint8LengthPrefixed(&child1) {
+ t.Error("parsing base failed")
+ }
+ if !child1.ReadUint8(&u) || !child1.ReadUint8LengthPrefixed(&child2) || !child1.ReadUint8(&x) {
+ t.Error("parsing child1 failed")
+ }
+ if !child2.ReadUint8(&v) || !child2.ReadUint8(&w) {
+ t.Error("parsing child2 failed")
+ }
+ if u != 5 || v != 23 || w != 42 || x != 123 {
+ t.Errorf("u, v, w, x = %d, %d, %d, %d, want 5, 23, 42, 123",
+ u, v, w, x)
+ }
+ if len(base) != 0 {
+ t.Errorf("len(base) = %d, want 0", len(base))
+ }
+ if len(child1) != 0 {
+ t.Errorf("len(child1) = %d, want 0", len(child1))
+ }
+ if len(base) != 0 {
+ t.Errorf("len(child2) = %d, want 0", len(child2))
+ }
+}
+
+func TestPreallocatedBuffer(t *testing.T) {
+ var buf [5]byte
+ b := NewBuilder(buf[0:0])
+ b.AddUint8(1)
+ b.AddUint8LengthPrefixed(func(c *Builder) {
+ c.AddUint8(3)
+ c.AddUint8(4)
+ })
+ b.AddUint16(1286) // Outgrow buf by one byte.
+ want := []byte{1, 2, 3, 4, 0}
+ if !bytes.Equal(buf[:], want) {
+ t.Errorf("buf = %v want %v", buf, want)
+ }
+ if err := builderBytesEq(b, 1, 2, 3, 4, 5, 6); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestWriteWithPendingChild(t *testing.T) {
+ var b Builder
+ b.AddUint8LengthPrefixed(func(c *Builder) {
+ c.AddUint8LengthPrefixed(func(d *Builder) {
+ func() {
+ defer func() {
+ if recover() == nil {
+ t.Errorf("recover() = nil, want error; c.AddUint8() did not panic")
+ }
+ }()
+ c.AddUint8(2) // panics
+ }()
+
+ defer func() {
+ if recover() == nil {
+ t.Errorf("recover() = nil, want error; b.AddUint8() did not panic")
+ }
+ }()
+ b.AddUint8(2) // panics
+ })
+
+ defer func() {
+ if recover() == nil {
+ t.Errorf("recover() = nil, want error; b.AddUint8() did not panic")
+ }
+ }()
+ b.AddUint8(2) // panics
+ })
+}
+
+func TestSetError(t *testing.T) {
+ const errorStr = "TestSetError"
+ var b Builder
+ b.SetError(errors.New(errorStr))
+
+ ret, err := b.Bytes()
+ if ret != nil {
+ t.Error("expected nil result")
+ }
+ if err == nil {
+ t.Fatal("unexpected nil error")
+ }
+ if s := err.Error(); s != errorStr {
+ t.Errorf("expected error %q, got %v", errorStr, s)
+ }
+}
+
+func TestUnwrite(t *testing.T) {
+ var b Builder
+ b.AddBytes([]byte{1, 2, 3, 4, 5})
+ b.Unwrite(2)
+ if err := builderBytesEq(&b, 1, 2, 3); err != nil {
+ t.Error(err)
+ }
+
+ func() {
+ defer func() {
+ if recover() == nil {
+ t.Errorf("recover() = nil, want error; b.Unwrite() did not panic")
+ }
+ }()
+ b.Unwrite(4) // panics
+ }()
+
+ b = Builder{}
+ b.AddBytes([]byte{1, 2, 3, 4, 5})
+ b.AddUint8LengthPrefixed(func(b *Builder) {
+ b.AddBytes([]byte{1, 2, 3, 4, 5})
+
+ defer func() {
+ if recover() == nil {
+ t.Errorf("recover() = nil, want error; b.Unwrite() did not panic")
+ }
+ }()
+ b.Unwrite(6) // panics
+ })
+
+ b = Builder{}
+ b.AddBytes([]byte{1, 2, 3, 4, 5})
+ b.AddUint8LengthPrefixed(func(c *Builder) {
+ defer func() {
+ if recover() == nil {
+ t.Errorf("recover() = nil, want error; b.Unwrite() did not panic")
+ }
+ }()
+ b.Unwrite(2) // panics (attempted unwrite while child is pending)
+ })
+}
+
+func TestFixedBuilderLengthPrefixed(t *testing.T) {
+ bufCap := 10
+ inner := bytes.Repeat([]byte{0xff}, bufCap-2)
+ buf := make([]byte, 0, bufCap)
+ b := NewFixedBuilder(buf)
+ b.AddUint16LengthPrefixed(func(b *Builder) {
+ b.AddBytes(inner)
+ })
+ if got := b.BytesOrPanic(); len(got) != bufCap {
+ t.Errorf("Expected output length to be %d, got %d", bufCap, len(got))
+ }
+}
+
+func TestFixedBuilderPanicReallocate(t *testing.T) {
+ defer func() {
+ recover()
+ }()
+
+ b := NewFixedBuilder(make([]byte, 0, 10))
+ b1 := NewFixedBuilder(make([]byte, 0, 10))
+ b.AddUint16LengthPrefixed(func(b *Builder) {
+ *b = *b1
+ })
+
+ t.Error("Builder did not panic")
+}
+
+// ASN.1
+
+func TestASN1Int64(t *testing.T) {
+ tests := []struct {
+ in int64
+ want []byte
+ }{
+ {-0x800000, []byte{2, 3, 128, 0, 0}},
+ {-256, []byte{2, 2, 255, 0}},
+ {-129, []byte{2, 2, 255, 127}},
+ {-128, []byte{2, 1, 128}},
+ {-1, []byte{2, 1, 255}},
+ {0, []byte{2, 1, 0}},
+ {1, []byte{2, 1, 1}},
+ {2, []byte{2, 1, 2}},
+ {127, []byte{2, 1, 127}},
+ {128, []byte{2, 2, 0, 128}},
+ {256, []byte{2, 2, 1, 0}},
+ {0x800000, []byte{2, 4, 0, 128, 0, 0}},
+ }
+ for i, tt := range tests {
+ var b Builder
+ b.AddASN1Int64(tt.in)
+ if err := builderBytesEq(&b, tt.want...); err != nil {
+ t.Errorf("%v, (i = %d; in = %v)", err, i, tt.in)
+ }
+
+ var n int64
+ s := String(b.BytesOrPanic())
+ ok := s.ReadASN1Integer(&n)
+ if !ok || n != tt.in {
+ t.Errorf("s.ReadASN1Integer(&n) = %v, n = %d; want true, n = %d (i = %d)",
+ ok, n, tt.in, i)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+ }
+}
+
+func TestASN1Uint64(t *testing.T) {
+ tests := []struct {
+ in uint64
+ want []byte
+ }{
+ {0, []byte{2, 1, 0}},
+ {1, []byte{2, 1, 1}},
+ {2, []byte{2, 1, 2}},
+ {127, []byte{2, 1, 127}},
+ {128, []byte{2, 2, 0, 128}},
+ {256, []byte{2, 2, 1, 0}},
+ {0x800000, []byte{2, 4, 0, 128, 0, 0}},
+ {0x7fffffffffffffff, []byte{2, 8, 127, 255, 255, 255, 255, 255, 255, 255}},
+ {0x8000000000000000, []byte{2, 9, 0, 128, 0, 0, 0, 0, 0, 0, 0}},
+ {0xffffffffffffffff, []byte{2, 9, 0, 255, 255, 255, 255, 255, 255, 255, 255}},
+ }
+ for i, tt := range tests {
+ var b Builder
+ b.AddASN1Uint64(tt.in)
+ if err := builderBytesEq(&b, tt.want...); err != nil {
+ t.Errorf("%v, (i = %d; in = %v)", err, i, tt.in)
+ }
+
+ var n uint64
+ s := String(b.BytesOrPanic())
+ ok := s.ReadASN1Integer(&n)
+ if !ok || n != tt.in {
+ t.Errorf("s.ReadASN1Integer(&n) = %v, n = %d; want true, n = %d (i = %d)",
+ ok, n, tt.in, i)
+ }
+ if len(s) != 0 {
+ t.Errorf("len(s) = %d, want 0", len(s))
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/cryptobyte/example_test.go b/local_crypto_patch/contents/cryptobyte/example_test.go
new file mode 100644
index 0000000000..86c098adf6
--- /dev/null
+++ b/local_crypto_patch/contents/cryptobyte/example_test.go
@@ -0,0 +1,154 @@
+// 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 cryptobyte_test
+
+import (
+ "errors"
+ "fmt"
+
+ "golang.org/x/crypto/cryptobyte"
+ "golang.org/x/crypto/cryptobyte/asn1"
+)
+
+func ExampleString_lengthPrefixed() {
+ // This is an example of parsing length-prefixed data (as found in, for
+ // example, TLS). Imagine a 16-bit prefixed series of 8-bit prefixed
+ // strings.
+
+ input := cryptobyte.String([]byte{0, 12, 5, 'h', 'e', 'l', 'l', 'o', 5, 'w', 'o', 'r', 'l', 'd'})
+ var result []string
+
+ var values cryptobyte.String
+ if !input.ReadUint16LengthPrefixed(&values) ||
+ !input.Empty() {
+ panic("bad format")
+ }
+
+ for !values.Empty() {
+ var value cryptobyte.String
+ if !values.ReadUint8LengthPrefixed(&value) {
+ panic("bad format")
+ }
+
+ result = append(result, string(value))
+ }
+
+ // Output: []string{"hello", "world"}
+ fmt.Printf("%#v\n", result)
+}
+
+func ExampleString_aSN1() {
+ // This is an example of parsing ASN.1 data that looks like:
+ // Foo ::= SEQUENCE {
+ // version [6] INTEGER DEFAULT 0
+ // data OCTET STRING
+ // }
+
+ input := cryptobyte.String([]byte{0x30, 12, 0xa6, 3, 2, 1, 2, 4, 5, 'h', 'e', 'l', 'l', 'o'})
+
+ var (
+ version int64
+ data, inner, versionBytes cryptobyte.String
+ haveVersion bool
+ )
+ if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
+ !input.Empty() ||
+ !inner.ReadOptionalASN1(&versionBytes, &haveVersion, asn1.Tag(6).Constructed().ContextSpecific()) ||
+ (haveVersion && !versionBytes.ReadASN1Integer(&version)) ||
+ (haveVersion && !versionBytes.Empty()) ||
+ !inner.ReadASN1(&data, asn1.OCTET_STRING) ||
+ !inner.Empty() {
+ panic("bad format")
+ }
+
+ // Output: haveVersion: true, version: 2, data: hello
+ fmt.Printf("haveVersion: %t, version: %d, data: %s\n", haveVersion, version, string(data))
+}
+
+func ExampleBuilder_aSN1() {
+ // This is an example of building ASN.1 data that looks like:
+ // Foo ::= SEQUENCE {
+ // version [6] INTEGER DEFAULT 0
+ // data OCTET STRING
+ // }
+
+ version := int64(2)
+ data := []byte("hello")
+ const defaultVersion = 0
+
+ var b cryptobyte.Builder
+ b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
+ if version != defaultVersion {
+ b.AddASN1(asn1.Tag(6).Constructed().ContextSpecific(), func(b *cryptobyte.Builder) {
+ b.AddASN1Int64(version)
+ })
+ }
+ b.AddASN1OctetString(data)
+ })
+
+ result, err := b.Bytes()
+ if err != nil {
+ panic(err)
+ }
+
+ // Output: 300ca603020102040568656c6c6f
+ fmt.Printf("%x\n", result)
+}
+
+func ExampleBuilder_lengthPrefixed() {
+ // This is an example of building length-prefixed data (as found in,
+ // for example, TLS). Imagine a 16-bit prefixed series of 8-bit
+ // prefixed strings.
+ input := []string{"hello", "world"}
+
+ var b cryptobyte.Builder
+ b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
+ for _, value := range input {
+ b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
+ b.AddBytes([]byte(value))
+ })
+ }
+ })
+
+ result, err := b.Bytes()
+ if err != nil {
+ panic(err)
+ }
+
+ // Output: 000c0568656c6c6f05776f726c64
+ fmt.Printf("%x\n", result)
+}
+
+func ExampleBuilder_lengthPrefixOverflow() {
+ // Writing more data that can be expressed by the length prefix results
+ // in an error from Bytes().
+
+ tooLarge := make([]byte, 256)
+
+ var b cryptobyte.Builder
+ b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
+ b.AddBytes(tooLarge)
+ })
+
+ result, err := b.Bytes()
+ fmt.Printf("len=%d err=%s\n", len(result), err)
+
+ // Output: len=0 err=cryptobyte: pending child length 256 exceeds 1-byte length prefix
+}
+
+func ExampleBuilderContinuation_errorHandling() {
+ var b cryptobyte.Builder
+ // Continuations that panic with a BuildError will cause Bytes to
+ // return the inner error.
+ b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
+ b.AddUint32(0)
+ panic(cryptobyte.BuildError{Err: errors.New("example error")})
+ })
+
+ result, err := b.Bytes()
+ fmt.Printf("len=%d err=%s\n", len(result), err)
+
+ // Output: len=0 err=example error
+}
diff --git a/local_crypto_patch/contents/cryptobyte/string.go b/local_crypto_patch/contents/cryptobyte/string.go
new file mode 100644
index 0000000000..4b0f8097f9
--- /dev/null
+++ b/local_crypto_patch/contents/cryptobyte/string.go
@@ -0,0 +1,183 @@
+// 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 cryptobyte contains types that help with parsing and constructing
+// length-prefixed, binary messages, including ASN.1 DER. (The asn1 subpackage
+// contains useful ASN.1 constants.)
+//
+// The String type is for parsing. It wraps a []byte slice and provides helper
+// functions for consuming structures, value by value.
+//
+// The Builder type is for constructing messages. It providers helper functions
+// for appending values and also for appending length-prefixed submessages –
+// without having to worry about calculating the length prefix ahead of time.
+//
+// See the documentation and examples for the Builder and String types to get
+// started.
+package cryptobyte
+
+// String represents a string of bytes. It provides methods for parsing
+// fixed-length and length-prefixed values from it.
+type String []byte
+
+// read advances a String by n bytes and returns them. If less than n bytes
+// remain, it returns nil.
+func (s *String) read(n int) []byte {
+ if len(*s) < n || n < 0 {
+ return nil
+ }
+ v := (*s)[:n]
+ *s = (*s)[n:]
+ return v
+}
+
+// Skip advances the String by n byte and reports whether it was successful.
+func (s *String) Skip(n int) bool {
+ return s.read(n) != nil
+}
+
+// ReadUint8 decodes an 8-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint8(out *uint8) bool {
+ v := s.read(1)
+ if v == nil {
+ return false
+ }
+ *out = uint8(v[0])
+ return true
+}
+
+// ReadUint16 decodes a big-endian, 16-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint16(out *uint16) bool {
+ v := s.read(2)
+ if v == nil {
+ return false
+ }
+ *out = uint16(v[0])<<8 | uint16(v[1])
+ return true
+}
+
+// ReadUint24 decodes a big-endian, 24-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint24(out *uint32) bool {
+ v := s.read(3)
+ if v == nil {
+ return false
+ }
+ *out = uint32(v[0])<<16 | uint32(v[1])<<8 | uint32(v[2])
+ return true
+}
+
+// ReadUint32 decodes a big-endian, 32-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint32(out *uint32) bool {
+ v := s.read(4)
+ if v == nil {
+ return false
+ }
+ *out = uint32(v[0])<<24 | uint32(v[1])<<16 | uint32(v[2])<<8 | uint32(v[3])
+ return true
+}
+
+// ReadUint48 decodes a big-endian, 48-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint48(out *uint64) bool {
+ v := s.read(6)
+ if v == nil {
+ return false
+ }
+ *out = uint64(v[0])<<40 | uint64(v[1])<<32 | uint64(v[2])<<24 | uint64(v[3])<<16 | uint64(v[4])<<8 | uint64(v[5])
+ return true
+}
+
+// ReadUint64 decodes a big-endian, 64-bit value into out and advances over it.
+// It reports whether the read was successful.
+func (s *String) ReadUint64(out *uint64) bool {
+ v := s.read(8)
+ if v == nil {
+ return false
+ }
+ *out = uint64(v[0])<<56 | uint64(v[1])<<48 | uint64(v[2])<<40 | uint64(v[3])<<32 | uint64(v[4])<<24 | uint64(v[5])<<16 | uint64(v[6])<<8 | uint64(v[7])
+ return true
+}
+
+func (s *String) readUnsigned(out *uint32, length int) bool {
+ v := s.read(length)
+ if v == nil {
+ return false
+ }
+ var result uint32
+ for i := 0; i < length; i++ {
+ result <<= 8
+ result |= uint32(v[i])
+ }
+ *out = result
+ return true
+}
+
+func (s *String) readLengthPrefixed(lenLen int, outChild *String) bool {
+ lenBytes := s.read(lenLen)
+ if lenBytes == nil {
+ return false
+ }
+ var length uint32
+ for _, b := range lenBytes {
+ length = length << 8
+ length = length | uint32(b)
+ }
+ v := s.read(int(length))
+ if v == nil {
+ return false
+ }
+ *outChild = v
+ return true
+}
+
+// ReadUint8LengthPrefixed reads the content of an 8-bit length-prefixed value
+// into out and advances over it. It reports whether the read was successful.
+func (s *String) ReadUint8LengthPrefixed(out *String) bool {
+ return s.readLengthPrefixed(1, out)
+}
+
+// ReadUint16LengthPrefixed reads the content of a big-endian, 16-bit
+// length-prefixed value into out and advances over it. It reports whether the
+// read was successful.
+func (s *String) ReadUint16LengthPrefixed(out *String) bool {
+ return s.readLengthPrefixed(2, out)
+}
+
+// ReadUint24LengthPrefixed reads the content of a big-endian, 24-bit
+// length-prefixed value into out and advances over it. It reports whether
+// the read was successful.
+func (s *String) ReadUint24LengthPrefixed(out *String) bool {
+ return s.readLengthPrefixed(3, out)
+}
+
+// ReadBytes reads n bytes into out and advances over them. It reports
+// whether the read was successful.
+func (s *String) ReadBytes(out *[]byte, n int) bool {
+ v := s.read(n)
+ if v == nil {
+ return false
+ }
+ *out = v
+ return true
+}
+
+// CopyBytes copies len(out) bytes into out and advances over them. It reports
+// whether the copy operation was successful
+func (s *String) CopyBytes(out []byte) bool {
+ n := len(out)
+ v := s.read(n)
+ if v == nil {
+ return false
+ }
+ return copy(out, v) == n
+}
+
+// Empty reports whether the string does not contain any bytes.
+func (s String) Empty() bool {
+ return len(s) == 0
+}
diff --git a/local_crypto_patch/contents/curve25519/curve25519.go b/local_crypto_patch/contents/curve25519/curve25519.go
new file mode 100644
index 0000000000..048faef3a5
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/curve25519/curve25519_test.go b/local_crypto_patch/contents/curve25519/curve25519_test.go
new file mode 100644
index 0000000000..e2b338b5ec
--- /dev/null
+++ b/local_crypto_patch/contents/curve25519/curve25519_test.go
@@ -0,0 +1,142 @@
+// 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 curve25519_test
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "testing"
+
+ "golang.org/x/crypto/curve25519"
+)
+
+const expectedHex = "89161fde887b2b53de549af483940106ecc114d6982daa98256de23bdf77661a"
+
+func TestX25519Basepoint(t *testing.T) {
+ x := make([]byte, 32)
+ x[0] = 1
+
+ for i := 0; i < 200; i++ {
+ var err error
+ x, err = curve25519.X25519(x, curve25519.Basepoint)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ result := hex.EncodeToString(x)
+ if result != expectedHex {
+ t.Errorf("incorrect result: got %s, want %s", result, expectedHex)
+ }
+}
+
+func TestLowOrderPoints(t *testing.T) {
+ scalar := make([]byte, curve25519.ScalarSize)
+ if _, err := rand.Read(scalar); err != nil {
+ t.Fatal(err)
+ }
+ for i, p := range lowOrderPoints {
+ out, err := curve25519.X25519(scalar, p)
+ if err == nil {
+ t.Errorf("%d: expected error, got nil", i)
+ }
+ if out != nil {
+ t.Errorf("%d: expected nil output, got %x", i, out)
+ }
+ }
+}
+
+func TestTestVectors(t *testing.T) {
+ t.Run("Legacy", func(t *testing.T) { testTestVectors(t, curve25519.ScalarMult) })
+ t.Run("X25519", func(t *testing.T) {
+ testTestVectors(t, func(dst, scalar, point *[32]byte) {
+ out, err := curve25519.X25519(scalar[:], point[:])
+ if err != nil {
+ t.Fatal(err)
+ }
+ copy(dst[:], out)
+ })
+ })
+}
+
+func testTestVectors(t *testing.T, scalarMult func(dst, scalar, point *[32]byte)) {
+ for _, tv := range testVectors {
+ var got [32]byte
+ scalarMult(&got, &tv.In, &tv.Base)
+ if !bytes.Equal(got[:], tv.Expect[:]) {
+ t.Logf(" in = %x", tv.In)
+ t.Logf(" base = %x", tv.Base)
+ t.Logf(" got = %x", got)
+ t.Logf("expect = %x", tv.Expect)
+ t.Fail()
+ }
+ }
+}
+
+// TestHighBitIgnored tests the following requirement in RFC 7748:
+//
+// When receiving such an array, implementations of X25519 (but not X448) MUST
+// mask the most significant bit in the final byte.
+//
+// Regression test for issue #30095.
+func TestHighBitIgnored(t *testing.T) {
+ var s, u [32]byte
+ rand.Read(s[:])
+ rand.Read(u[:])
+
+ var hi0, hi1 [32]byte
+
+ u[31] &= 0x7f
+ curve25519.ScalarMult(&hi0, &s, &u)
+
+ u[31] |= 0x80
+ curve25519.ScalarMult(&hi1, &s, &u)
+
+ if !bytes.Equal(hi0[:], hi1[:]) {
+ t.Errorf("high bit of group point should not affect result")
+ }
+}
+
+var benchmarkSink byte
+
+func BenchmarkX25519Basepoint(b *testing.B) {
+ scalar := make([]byte, curve25519.ScalarSize)
+ if _, err := rand.Read(scalar); err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ out, err := curve25519.X25519(scalar, curve25519.Basepoint)
+ if err != nil {
+ b.Fatal(err)
+ }
+ benchmarkSink ^= out[0]
+ }
+}
+
+func BenchmarkX25519(b *testing.B) {
+ scalar := make([]byte, curve25519.ScalarSize)
+ if _, err := rand.Read(scalar); err != nil {
+ b.Fatal(err)
+ }
+ point, err := curve25519.X25519(scalar, curve25519.Basepoint)
+ if err != nil {
+ b.Fatal(err)
+ }
+ if _, err := rand.Read(scalar); err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ out, err := curve25519.X25519(scalar, point)
+ if err != nil {
+ b.Fatal(err)
+ }
+ benchmarkSink ^= out[0]
+ }
+}
diff --git a/local_crypto_patch/contents/curve25519/vectors_test.go b/local_crypto_patch/contents/curve25519/vectors_test.go
new file mode 100644
index 0000000000..f4c0a1414f
--- /dev/null
+++ b/local_crypto_patch/contents/curve25519/vectors_test.go
@@ -0,0 +1,105 @@
+// 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_test
+
+// lowOrderPoints from libsodium.
+// https://github.com/jedisct1/libsodium/blob/65621a1059a37d/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c#L11-L70
+var lowOrderPoints = [][]byte{
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ {0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00},
+ {0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57},
+ {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
+ {0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
+ {0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
+}
+
+// testVectors generated with BoringSSL.
+var testVectors = []struct {
+ In [32]byte
+ Base [32]byte
+ Expect [32]byte
+}{
+ {
+ In: [32]byte{0x66, 0x8f, 0xb9, 0xf7, 0x6a, 0xd9, 0x71, 0xc8, 0x1a, 0xc9, 0x0, 0x7, 0x1a, 0x15, 0x60, 0xbc, 0xe2, 0xca, 0x0, 0xca, 0xc7, 0xe6, 0x7a, 0xf9, 0x93, 0x48, 0x91, 0x37, 0x61, 0x43, 0x40, 0x14},
+ Base: [32]byte{0xdb, 0x5f, 0x32, 0xb7, 0xf8, 0x41, 0xe7, 0xa1, 0xa0, 0x9, 0x68, 0xef, 0xfd, 0xed, 0x12, 0x73, 0x5f, 0xc4, 0x7a, 0x3e, 0xb1, 0x3b, 0x57, 0x9a, 0xac, 0xad, 0xea, 0xe8, 0x9, 0x39, 0xa7, 0xdd},
+ Expect: [32]byte{0x9, 0xd, 0x85, 0xe5, 0x99, 0xea, 0x8e, 0x2b, 0xee, 0xb6, 0x13, 0x4, 0xd3, 0x7b, 0xe1, 0xe, 0xc5, 0xc9, 0x5, 0xf9, 0x92, 0x7d, 0x32, 0xf4, 0x2a, 0x9a, 0xa, 0xfb, 0x3e, 0xb, 0x40, 0x74},
+ },
+ {
+ In: [32]byte{0x63, 0x66, 0x95, 0xe3, 0x4f, 0x75, 0xb9, 0xa2, 0x79, 0xc8, 0x70, 0x6f, 0xad, 0x12, 0x89, 0xf2, 0xc0, 0xb1, 0xe2, 0x2e, 0x16, 0xf8, 0xb8, 0x86, 0x17, 0x29, 0xc1, 0xa, 0x58, 0x29, 0x58, 0xaf},
+ Base: [32]byte{0x9, 0xd, 0x7, 0x1, 0xf8, 0xfd, 0xe2, 0x8f, 0x70, 0x4, 0x3b, 0x83, 0xf2, 0x34, 0x62, 0x25, 0x41, 0x9b, 0x18, 0xa7, 0xf2, 0x7e, 0x9e, 0x3d, 0x2b, 0xfd, 0x4, 0xe1, 0xf, 0x3d, 0x21, 0x3e},
+ Expect: [32]byte{0xbf, 0x26, 0xec, 0x7e, 0xc4, 0x13, 0x6, 0x17, 0x33, 0xd4, 0x40, 0x70, 0xea, 0x67, 0xca, 0xb0, 0x2a, 0x85, 0xdc, 0x1b, 0xe8, 0xcf, 0xe1, 0xff, 0x73, 0xd5, 0x41, 0xcc, 0x8, 0x32, 0x55, 0x6},
+ },
+ {
+ In: [32]byte{0x73, 0x41, 0x81, 0xcd, 0x1a, 0x94, 0x6, 0x52, 0x2a, 0x56, 0xfe, 0x25, 0xe4, 0x3e, 0xcb, 0xf0, 0x29, 0x5d, 0xb5, 0xdd, 0xd0, 0x60, 0x9b, 0x3c, 0x2b, 0x4e, 0x79, 0xc0, 0x6f, 0x8b, 0xd4, 0x6d},
+ Base: [32]byte{0xf8, 0xa8, 0x42, 0x1c, 0x7d, 0x21, 0xa9, 0x2d, 0xb3, 0xed, 0xe9, 0x79, 0xe1, 0xfa, 0x6a, 0xcb, 0x6, 0x2b, 0x56, 0xb1, 0x88, 0x5c, 0x71, 0xc5, 0x11, 0x53, 0xcc, 0xb8, 0x80, 0xac, 0x73, 0x15},
+ Expect: [32]byte{0x11, 0x76, 0xd0, 0x16, 0x81, 0xf2, 0xcf, 0x92, 0x9d, 0xa2, 0xc7, 0xa3, 0xdf, 0x66, 0xb5, 0xd7, 0x72, 0x9f, 0xd4, 0x22, 0x22, 0x6f, 0xd6, 0x37, 0x42, 0x16, 0xbf, 0x7e, 0x2, 0xfd, 0xf, 0x62},
+ },
+ {
+ In: [32]byte{0x1f, 0x70, 0x39, 0x1f, 0x6b, 0xa8, 0x58, 0x12, 0x94, 0x13, 0xbd, 0x80, 0x1b, 0x12, 0xac, 0xbf, 0x66, 0x23, 0x62, 0x82, 0x5c, 0xa2, 0x50, 0x9c, 0x81, 0x87, 0x59, 0xa, 0x2b, 0xe, 0x61, 0x72},
+ Base: [32]byte{0xd3, 0xea, 0xd0, 0x7a, 0x0, 0x8, 0xf4, 0x45, 0x2, 0xd5, 0x80, 0x8b, 0xff, 0xc8, 0x97, 0x9f, 0x25, 0xa8, 0x59, 0xd5, 0xad, 0xf4, 0x31, 0x2e, 0xa4, 0x87, 0x48, 0x9c, 0x30, 0xe0, 0x1b, 0x3b},
+ Expect: [32]byte{0xf8, 0x48, 0x2f, 0x2e, 0x9e, 0x58, 0xbb, 0x6, 0x7e, 0x86, 0xb2, 0x87, 0x24, 0xb3, 0xc0, 0xa3, 0xbb, 0xb5, 0x7, 0x3e, 0x4c, 0x6a, 0xcd, 0x93, 0xdf, 0x54, 0x5e, 0xff, 0xdb, 0xba, 0x50, 0x5f},
+ },
+ {
+ In: [32]byte{0x3a, 0x7a, 0xe6, 0xcf, 0x8b, 0x88, 0x9d, 0x2b, 0x7a, 0x60, 0xa4, 0x70, 0xad, 0x6a, 0xd9, 0x99, 0x20, 0x6b, 0xf5, 0x7d, 0x90, 0x30, 0xdd, 0xf7, 0xf8, 0x68, 0xc, 0x8b, 0x1a, 0x64, 0x5d, 0xaa},
+ Base: [32]byte{0x4d, 0x25, 0x4c, 0x80, 0x83, 0xd8, 0x7f, 0x1a, 0x9b, 0x3e, 0xa7, 0x31, 0xef, 0xcf, 0xf8, 0xa6, 0xf2, 0x31, 0x2d, 0x6f, 0xed, 0x68, 0xe, 0xf8, 0x29, 0x18, 0x51, 0x61, 0xc8, 0xfc, 0x50, 0x60},
+ Expect: [32]byte{0x47, 0xb3, 0x56, 0xd5, 0x81, 0x8d, 0xe8, 0xef, 0xac, 0x77, 0x4b, 0x71, 0x4c, 0x42, 0xc4, 0x4b, 0xe6, 0x85, 0x23, 0xdd, 0x57, 0xdb, 0xd7, 0x39, 0x62, 0xd5, 0xa5, 0x26, 0x31, 0x87, 0x62, 0x37},
+ },
+ {
+ In: [32]byte{0x20, 0x31, 0x61, 0xc3, 0x15, 0x9a, 0x87, 0x6a, 0x2b, 0xea, 0xec, 0x29, 0xd2, 0x42, 0x7f, 0xb0, 0xc7, 0xc3, 0xd, 0x38, 0x2c, 0xd0, 0x13, 0xd2, 0x7c, 0xc3, 0xd3, 0x93, 0xdb, 0xd, 0xaf, 0x6f},
+ Base: [32]byte{0x6a, 0xb9, 0x5d, 0x1a, 0xbe, 0x68, 0xc0, 0x9b, 0x0, 0x5c, 0x3d, 0xb9, 0x4, 0x2c, 0xc9, 0x1a, 0xc8, 0x49, 0xf7, 0xe9, 0x4a, 0x2a, 0x4a, 0x9b, 0x89, 0x36, 0x78, 0x97, 0xb, 0x7b, 0x95, 0xbf},
+ Expect: [32]byte{0x11, 0xed, 0xae, 0xdc, 0x95, 0xff, 0x78, 0xf5, 0x63, 0xa1, 0xc8, 0xf1, 0x55, 0x91, 0xc0, 0x71, 0xde, 0xa0, 0x92, 0xb4, 0xd7, 0xec, 0xaa, 0xc8, 0xe0, 0x38, 0x7b, 0x5a, 0x16, 0xc, 0x4e, 0x5d},
+ },
+ {
+ In: [32]byte{0x13, 0xd6, 0x54, 0x91, 0xfe, 0x75, 0xf2, 0x3, 0xa0, 0x8, 0xb4, 0x41, 0x5a, 0xbc, 0x60, 0xd5, 0x32, 0xe6, 0x95, 0xdb, 0xd2, 0xf1, 0xe8, 0x3, 0xac, 0xcb, 0x34, 0xb2, 0xb7, 0x2c, 0x3d, 0x70},
+ Base: [32]byte{0x2e, 0x78, 0x4e, 0x4, 0xca, 0x0, 0x73, 0x33, 0x62, 0x56, 0xa8, 0x39, 0x25, 0x5e, 0xd2, 0xf7, 0xd4, 0x79, 0x6a, 0x64, 0xcd, 0xc3, 0x7f, 0x1e, 0xb0, 0xe5, 0xc4, 0xc8, 0xd1, 0xd1, 0xe0, 0xf5},
+ Expect: [32]byte{0x56, 0x3e, 0x8c, 0x9a, 0xda, 0xa7, 0xd7, 0x31, 0x1, 0xb0, 0xf2, 0xea, 0xd3, 0xca, 0xe1, 0xea, 0x5d, 0x8f, 0xcd, 0x5c, 0xd3, 0x60, 0x80, 0xbb, 0x8e, 0x6e, 0xc0, 0x3d, 0x61, 0x45, 0x9, 0x17},
+ },
+ {
+ In: [32]byte{0x68, 0x6f, 0x7d, 0xa9, 0x3b, 0xf2, 0x68, 0xe5, 0x88, 0x6, 0x98, 0x31, 0xf0, 0x47, 0x16, 0x3f, 0x33, 0x58, 0x99, 0x89, 0xd0, 0x82, 0x6e, 0x98, 0x8, 0xfb, 0x67, 0x8e, 0xd5, 0x7e, 0x67, 0x49},
+ Base: [32]byte{0x8b, 0x54, 0x9b, 0x2d, 0xf6, 0x42, 0xd3, 0xb2, 0x5f, 0xe8, 0x38, 0xf, 0x8c, 0xc4, 0x37, 0x5f, 0x99, 0xb7, 0xbb, 0x4d, 0x27, 0x5f, 0x77, 0x9f, 0x3b, 0x7c, 0x81, 0xb8, 0xa2, 0xbb, 0xc1, 0x29},
+ Expect: [32]byte{0x1, 0x47, 0x69, 0x65, 0x42, 0x6b, 0x61, 0x71, 0x74, 0x9a, 0x8a, 0xdd, 0x92, 0x35, 0x2, 0x5c, 0xe5, 0xf5, 0x57, 0xfe, 0x40, 0x9, 0xf7, 0x39, 0x30, 0x44, 0xeb, 0xbb, 0x8a, 0xe9, 0x52, 0x79},
+ },
+ {
+ In: [32]byte{0x82, 0xd6, 0x1c, 0xce, 0xdc, 0x80, 0x6a, 0x60, 0x60, 0xa3, 0x34, 0x9a, 0x5e, 0x87, 0xcb, 0xc7, 0xac, 0x11, 0x5e, 0x4f, 0x87, 0x77, 0x62, 0x50, 0xae, 0x25, 0x60, 0x98, 0xa7, 0xc4, 0x49, 0x59},
+ Base: [32]byte{0x8b, 0x6b, 0x9d, 0x8, 0xf6, 0x1f, 0xc9, 0x1f, 0xe8, 0xb3, 0x29, 0x53, 0xc4, 0x23, 0x40, 0xf0, 0x7, 0xb5, 0x71, 0xdc, 0xb0, 0xa5, 0x6d, 0x10, 0x72, 0x4e, 0xce, 0xf9, 0x95, 0xc, 0xfb, 0x25},
+ Expect: [32]byte{0x9c, 0x49, 0x94, 0x1f, 0x9c, 0x4f, 0x18, 0x71, 0xfa, 0x40, 0x91, 0xfe, 0xd7, 0x16, 0xd3, 0x49, 0x99, 0xc9, 0x52, 0x34, 0xed, 0xf2, 0xfd, 0xfb, 0xa6, 0xd1, 0x4a, 0x5a, 0xfe, 0x9e, 0x5, 0x58},
+ },
+ {
+ In: [32]byte{0x7d, 0xc7, 0x64, 0x4, 0x83, 0x13, 0x97, 0xd5, 0x88, 0x4f, 0xdf, 0x6f, 0x97, 0xe1, 0x74, 0x4c, 0x9e, 0xb1, 0x18, 0xa3, 0x1a, 0x7b, 0x23, 0xf8, 0xd7, 0x9f, 0x48, 0xce, 0x9c, 0xad, 0x15, 0x4b},
+ Base: [32]byte{0x1a, 0xcd, 0x29, 0x27, 0x84, 0xf4, 0x79, 0x19, 0xd4, 0x55, 0xf8, 0x87, 0x44, 0x83, 0x58, 0x61, 0xb, 0xb9, 0x45, 0x96, 0x70, 0xeb, 0x99, 0xde, 0xe4, 0x60, 0x5, 0xf6, 0x89, 0xca, 0x5f, 0xb6},
+ Expect: [32]byte{0x0, 0xf4, 0x3c, 0x2, 0x2e, 0x94, 0xea, 0x38, 0x19, 0xb0, 0x36, 0xae, 0x2b, 0x36, 0xb2, 0xa7, 0x61, 0x36, 0xaf, 0x62, 0x8a, 0x75, 0x1f, 0xe5, 0xd0, 0x1e, 0x3, 0xd, 0x44, 0x25, 0x88, 0x59},
+ },
+ {
+ In: [32]byte{0xfb, 0xc4, 0x51, 0x1d, 0x23, 0xa6, 0x82, 0xae, 0x4e, 0xfd, 0x8, 0xc8, 0x17, 0x9c, 0x1c, 0x6, 0x7f, 0x9c, 0x8b, 0xe7, 0x9b, 0xbc, 0x4e, 0xff, 0x5c, 0xe2, 0x96, 0xc6, 0xbc, 0x1f, 0xf4, 0x45},
+ Base: [32]byte{0x55, 0xca, 0xff, 0x21, 0x81, 0xf2, 0x13, 0x6b, 0xe, 0xd0, 0xe1, 0xe2, 0x99, 0x44, 0x48, 0xe1, 0x6c, 0xc9, 0x70, 0x64, 0x6a, 0x98, 0x3d, 0x14, 0xd, 0xc4, 0xea, 0xb3, 0xd9, 0x4c, 0x28, 0x4e},
+ Expect: [32]byte{0xae, 0x39, 0xd8, 0x16, 0x53, 0x23, 0x45, 0x79, 0x4d, 0x26, 0x91, 0xe0, 0x80, 0x1c, 0xaa, 0x52, 0x5f, 0xc3, 0x63, 0x4d, 0x40, 0x2c, 0xe9, 0x58, 0xb, 0x33, 0x38, 0xb4, 0x6f, 0x8b, 0xb9, 0x72},
+ },
+ {
+ In: [32]byte{0x4e, 0x6, 0xc, 0xe1, 0xc, 0xeb, 0xf0, 0x95, 0x9, 0x87, 0x16, 0xc8, 0x66, 0x19, 0xeb, 0x9f, 0x7d, 0xf6, 0x65, 0x24, 0x69, 0x8b, 0xa7, 0x98, 0x8c, 0x3b, 0x90, 0x95, 0xd9, 0xf5, 0x1, 0x34},
+ Base: [32]byte{0x57, 0x73, 0x3f, 0x2d, 0x86, 0x96, 0x90, 0xd0, 0xd2, 0xed, 0xae, 0xc9, 0x52, 0x3d, 0xaa, 0x2d, 0xa9, 0x54, 0x45, 0xf4, 0x4f, 0x57, 0x83, 0xc1, 0xfa, 0xec, 0x6c, 0x3a, 0x98, 0x28, 0x18, 0xf3},
+ Expect: [32]byte{0xa6, 0x1e, 0x74, 0x55, 0x2c, 0xce, 0x75, 0xf5, 0xe9, 0x72, 0xe4, 0x24, 0xf2, 0xcc, 0xb0, 0x9c, 0x83, 0xbc, 0x1b, 0x67, 0x1, 0x47, 0x48, 0xf0, 0x2c, 0x37, 0x1a, 0x20, 0x9e, 0xf2, 0xfb, 0x2c},
+ },
+ {
+ In: [32]byte{0x5c, 0x49, 0x2c, 0xba, 0x2c, 0xc8, 0x92, 0x48, 0x8a, 0x9c, 0xeb, 0x91, 0x86, 0xc2, 0xaa, 0xc2, 0x2f, 0x1, 0x5b, 0xf3, 0xef, 0x8d, 0x3e, 0xcc, 0x9c, 0x41, 0x76, 0x97, 0x62, 0x61, 0xaa, 0xb1},
+ Base: [32]byte{0x67, 0x97, 0xc2, 0xe7, 0xdc, 0x92, 0xcc, 0xbe, 0x7c, 0x5, 0x6b, 0xec, 0x35, 0xa, 0xb6, 0xd3, 0xbd, 0x2a, 0x2c, 0x6b, 0xc5, 0xa8, 0x7, 0xbb, 0xca, 0xe1, 0xf6, 0xc2, 0xaf, 0x80, 0x36, 0x44},
+ Expect: [32]byte{0xfc, 0xf3, 0x7, 0xdf, 0xbc, 0x19, 0x2, 0xb, 0x28, 0xa6, 0x61, 0x8c, 0x6c, 0x62, 0x2f, 0x31, 0x7e, 0x45, 0x96, 0x7d, 0xac, 0xf4, 0xae, 0x4a, 0xa, 0x69, 0x9a, 0x10, 0x76, 0x9f, 0xde, 0x14},
+ },
+ {
+ In: [32]byte{0xea, 0x33, 0x34, 0x92, 0x96, 0x5, 0x5a, 0x4e, 0x8b, 0x19, 0x2e, 0x3c, 0x23, 0xc5, 0xf4, 0xc8, 0x44, 0x28, 0x2a, 0x3b, 0xfc, 0x19, 0xec, 0xc9, 0xdc, 0x64, 0x6a, 0x42, 0xc3, 0x8d, 0xc2, 0x48},
+ Base: [32]byte{0x2c, 0x75, 0xd8, 0x51, 0x42, 0xec, 0xad, 0x3e, 0x69, 0x44, 0x70, 0x4, 0x54, 0xc, 0x1c, 0x23, 0x54, 0x8f, 0xc8, 0xf4, 0x86, 0x25, 0x1b, 0x8a, 0x19, 0x46, 0x3f, 0x3d, 0xf6, 0xf8, 0xac, 0x61},
+ Expect: [32]byte{0x5d, 0xca, 0xb6, 0x89, 0x73, 0xf9, 0x5b, 0xd3, 0xae, 0x4b, 0x34, 0xfa, 0xb9, 0x49, 0xfb, 0x7f, 0xb1, 0x5a, 0xf1, 0xd8, 0xca, 0xe2, 0x8c, 0xd6, 0x99, 0xf9, 0xc1, 0xaa, 0x33, 0x37, 0x34, 0x2f},
+ },
+ {
+ In: [32]byte{0x4f, 0x29, 0x79, 0xb1, 0xec, 0x86, 0x19, 0xe4, 0x5c, 0xa, 0xb, 0x2b, 0x52, 0x9, 0x34, 0x54, 0x1a, 0xb9, 0x44, 0x7, 0xb6, 0x4d, 0x19, 0xa, 0x76, 0xf3, 0x23, 0x14, 0xef, 0xe1, 0x84, 0xe7},
+ Base: [32]byte{0xf7, 0xca, 0xe1, 0x8d, 0x8d, 0x36, 0xa7, 0xf5, 0x61, 0x17, 0xb8, 0xb7, 0xe, 0x25, 0x52, 0x27, 0x7f, 0xfc, 0x99, 0xdf, 0x87, 0x56, 0xb5, 0xe1, 0x38, 0xbf, 0x63, 0x68, 0xbc, 0x87, 0xf7, 0x4c},
+ Expect: [32]byte{0xe4, 0xe6, 0x34, 0xeb, 0xb4, 0xfb, 0x66, 0x4f, 0xe8, 0xb2, 0xcf, 0xa1, 0x61, 0x5f, 0x0, 0xe6, 0x46, 0x6f, 0xff, 0x73, 0x2c, 0xe1, 0xf8, 0xa0, 0xc8, 0xd2, 0x72, 0x74, 0x31, 0xd1, 0x6f, 0x14},
+ },
+ {
+ In: [32]byte{0xf5, 0xd8, 0xa9, 0x27, 0x90, 0x1d, 0x4f, 0xa4, 0x24, 0x90, 0x86, 0xb7, 0xff, 0xec, 0x24, 0xf5, 0x29, 0x7d, 0x80, 0x11, 0x8e, 0x4a, 0xc9, 0xd3, 0xfc, 0x9a, 0x82, 0x37, 0x95, 0x1e, 0x3b, 0x7f},
+ Base: [32]byte{0x3c, 0x23, 0x5e, 0xdc, 0x2, 0xf9, 0x11, 0x56, 0x41, 0xdb, 0xf5, 0x16, 0xd5, 0xde, 0x8a, 0x73, 0x5d, 0x6e, 0x53, 0xe2, 0x2a, 0xa2, 0xac, 0x14, 0x36, 0x56, 0x4, 0x5f, 0xf2, 0xe9, 0x52, 0x49},
+ Expect: [32]byte{0xab, 0x95, 0x15, 0xab, 0x14, 0xaf, 0x9d, 0x27, 0xe, 0x1d, 0xae, 0xc, 0x56, 0x80, 0xcb, 0xc8, 0x88, 0xb, 0xd8, 0xa8, 0xe7, 0xeb, 0x67, 0xb4, 0xda, 0x42, 0xa6, 0x61, 0x96, 0x1e, 0xfc, 0xb},
+ },
+}
diff --git a/local_crypto_patch/contents/ed25519/ed25519.go b/local_crypto_patch/contents/ed25519/ed25519.go
new file mode 100644
index 0000000000..df453dcce0
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ed25519/ed25519_test.go b/local_crypto_patch/contents/ed25519/ed25519_test.go
new file mode 100644
index 0000000000..ab433ba02b
--- /dev/null
+++ b/local_crypto_patch/contents/ed25519/ed25519_test.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.
+
+package ed25519_test
+
+import (
+ ed25519std "crypto/ed25519"
+ "testing"
+
+ "golang.org/x/crypto/ed25519"
+)
+
+func TestTypeAlias(t *testing.T) {
+ public, private, _ := ed25519std.GenerateKey(nil)
+
+ message := []byte("test message")
+ sig := ed25519.Sign(private, message)
+ if !ed25519.Verify(public, message, sig) {
+ t.Errorf("valid signature rejected")
+ }
+}
diff --git a/local_crypto_patch/contents/go.mod b/local_crypto_patch/contents/go.mod
new file mode 100644
index 0000000000..716943b7d6
--- /dev/null
+++ b/local_crypto_patch/contents/go.mod
@@ -0,0 +1,11 @@
+module golang.org/x/crypto
+
+go 1.25.0
+
+require (
+ golang.org/x/net v0.54.0 // tagx:ignore
+ golang.org/x/sys v0.45.0
+ golang.org/x/term v0.43.0
+)
+
+require golang.org/x/text v0.37.0 // indirect
diff --git a/local_crypto_patch/contents/go.sum b/local_crypto_patch/contents/go.sum
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/local_crypto_patch/contents/hkdf/example_test.go b/local_crypto_patch/contents/hkdf/example_test.go
new file mode 100644
index 0000000000..e89c260e9b
--- /dev/null
+++ b/local_crypto_patch/contents/hkdf/example_test.go
@@ -0,0 +1,56 @@
+// 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 hkdf_test
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/sha256"
+ "fmt"
+ "io"
+
+ "golang.org/x/crypto/hkdf"
+)
+
+// Usage example that expands one master secret into three other
+// cryptographically secure keys.
+func Example_usage() {
+ // Underlying hash function for HMAC.
+ hash := sha256.New
+
+ // Cryptographically secure master secret.
+ secret := []byte{0x00, 0x01, 0x02, 0x03} // i.e. NOT this.
+
+ // Non-secret salt, optional (can be nil).
+ // Recommended: hash-length random value.
+ salt := make([]byte, hash().Size())
+ if _, err := rand.Read(salt); err != nil {
+ panic(err)
+ }
+
+ // Non-secret context info, optional (can be nil).
+ info := []byte("hkdf example")
+
+ // Generate three 128-bit derived keys.
+ hkdf := hkdf.New(hash, secret, salt, info)
+
+ var keys [][]byte
+ for i := 0; i < 3; i++ {
+ key := make([]byte, 16)
+ if _, err := io.ReadFull(hkdf, key); err != nil {
+ panic(err)
+ }
+ keys = append(keys, key)
+ }
+
+ for i := range keys {
+ fmt.Printf("Key #%d: %v\n", i+1, !bytes.Equal(keys[i], make([]byte, 16)))
+ }
+
+ // Output:
+ // Key #1: true
+ // Key #2: true
+ // Key #3: true
+}
diff --git a/local_crypto_patch/contents/hkdf/hkdf.go b/local_crypto_patch/contents/hkdf/hkdf.go
new file mode 100644
index 0000000000..49f3a55820
--- /dev/null
+++ b/local_crypto_patch/contents/hkdf/hkdf.go
@@ -0,0 +1,100 @@
+// 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 hkdf implements the HMAC-based Extract-and-Expand Key Derivation
+// Function (HKDF) as defined in RFC 5869.
+//
+// HKDF is a cryptographic key derivation function (KDF) with the goal of
+// expanding limited input keying material into one or more cryptographically
+// strong secret keys.
+package hkdf
+
+import (
+ "crypto/hkdf"
+ "crypto/hmac"
+ "errors"
+ "hash"
+ "io"
+)
+
+// Extract generates a pseudorandom key for use with Expand from an input secret
+// and an optional independent salt.
+//
+// Only use this function if you need to reuse the extracted key with multiple
+// Expand invocations and different context values. Most common scenarios,
+// including the generation of multiple keys, should use New instead.
+func Extract(hash func() hash.Hash, secret, salt []byte) []byte {
+ // Use the stdlib Extract, which disables FIPS 140 enforcement of the HMAC
+ // key (which in HKDF is the salt). The only possible error is FIPS 140
+ // enforcement of the hash, which had to panic under this API anyway. We
+ // don't use the stdlib Expand, because it switched to returning a []byte
+ // instead of an io.Reader, and Expand uses the HMAC key as a key.
+ out, err := hkdf.Extract(hash, secret, salt)
+ if err != nil {
+ panic(err)
+ }
+ return out
+}
+
+type hkdfReader struct {
+ expander hash.Hash
+ size int
+
+ info []byte
+ counter byte
+
+ prev []byte
+ buf []byte
+}
+
+func (f *hkdfReader) Read(p []byte) (int, error) {
+ // Check whether enough data can be generated
+ need := len(p)
+ remains := len(f.buf) + int(255-f.counter+1)*f.size
+ if remains < need {
+ return 0, errors.New("hkdf: entropy limit reached")
+ }
+ // Read any leftover from the buffer
+ n := copy(p, f.buf)
+ p = p[n:]
+
+ // Fill the rest of the buffer
+ for len(p) > 0 {
+ if f.counter > 1 {
+ f.expander.Reset()
+ }
+ f.expander.Write(f.prev)
+ f.expander.Write(f.info)
+ f.expander.Write([]byte{f.counter})
+ f.prev = f.expander.Sum(f.prev[:0])
+ f.counter++
+
+ // Copy the new batch into p
+ f.buf = f.prev
+ n = copy(p, f.buf)
+ p = p[n:]
+ }
+ // Save leftovers for next run
+ f.buf = f.buf[n:]
+
+ return need, nil
+}
+
+// Expand returns a Reader, from which keys can be read, using the given
+// pseudorandom key and optional context info, skipping the extraction step.
+//
+// The pseudorandomKey should have been generated by Extract, or be a uniformly
+// random or pseudorandom cryptographically strong key. See RFC 5869, Section
+// 3.3. Most common scenarios will want to use New instead.
+func Expand(hash func() hash.Hash, pseudorandomKey, info []byte) io.Reader {
+ expander := hmac.New(hash, pseudorandomKey)
+ return &hkdfReader{expander, expander.Size(), info, 1, nil, nil}
+}
+
+// New returns a Reader, from which keys can be read, using the given hash,
+// secret, salt and context info. Salt and info can be nil.
+func New(hash func() hash.Hash, secret, salt, info []byte) io.Reader {
+ prk := Extract(hash, secret, salt)
+ return Expand(hash, prk, info)
+}
diff --git a/local_crypto_patch/contents/hkdf/hkdf_test.go b/local_crypto_patch/contents/hkdf/hkdf_test.go
new file mode 100644
index 0000000000..ea575772ef
--- /dev/null
+++ b/local_crypto_patch/contents/hkdf/hkdf_test.go
@@ -0,0 +1,449 @@
+// 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 hkdf
+
+import (
+ "bytes"
+ "crypto/md5"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/sha512"
+ "hash"
+ "io"
+ "testing"
+)
+
+type hkdfTest struct {
+ hash func() hash.Hash
+ master []byte
+ salt []byte
+ prk []byte
+ info []byte
+ out []byte
+}
+
+var hkdfTests = []hkdfTest{
+ // Tests from RFC 5869
+ {
+ sha256.New,
+ []byte{
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ },
+ []byte{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+ },
+ []byte{
+ 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf,
+ 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63,
+ 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31,
+ 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5,
+ },
+ []byte{
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9,
+ },
+ []byte{
+ 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a,
+ 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a,
+ 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c,
+ 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf,
+ 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18,
+ 0x58, 0x65,
+ },
+ },
+ {
+ sha256.New,
+ []byte{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ },
+ []byte{
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ },
+ []byte{
+ 0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a,
+ 0x06, 0x10, 0x4c, 0x9c, 0xeb, 0x35, 0xb4, 0x5c,
+ 0xef, 0x76, 0x00, 0x14, 0x90, 0x46, 0x71, 0x01,
+ 0x4a, 0x19, 0x3f, 0x40, 0xc1, 0x5f, 0xc2, 0x44,
+ },
+ []byte{
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+ },
+ []byte{
+ 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1,
+ 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34,
+ 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8,
+ 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c,
+ 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72,
+ 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09,
+ 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
+ 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71,
+ 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87,
+ 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f,
+ 0x1d, 0x87,
+ },
+ },
+ {
+ sha256.New,
+ []byte{
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ },
+ []byte{},
+ []byte{
+ 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16,
+ 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf,
+ 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77,
+ 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04,
+ },
+ []byte{},
+ []byte{
+ 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f,
+ 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31,
+ 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e,
+ 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d,
+ 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a,
+ 0x96, 0xc8,
+ },
+ },
+ {
+ sha256.New,
+ []byte{
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ },
+ nil,
+ []byte{
+ 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16,
+ 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf,
+ 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77,
+ 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04,
+ },
+ nil,
+ []byte{
+ 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f,
+ 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31,
+ 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e,
+ 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d,
+ 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a,
+ 0x96, 0xc8,
+ },
+ },
+ {
+ sha1.New,
+ []byte{
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b,
+ },
+ []byte{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+ },
+ []byte{
+ 0x9b, 0x6c, 0x18, 0xc4, 0x32, 0xa7, 0xbf, 0x8f,
+ 0x0e, 0x71, 0xc8, 0xeb, 0x88, 0xf4, 0xb3, 0x0b,
+ 0xaa, 0x2b, 0xa2, 0x43,
+ },
+ []byte{
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9,
+ },
+ []byte{
+ 0x08, 0x5a, 0x01, 0xea, 0x1b, 0x10, 0xf3, 0x69,
+ 0x33, 0x06, 0x8b, 0x56, 0xef, 0xa5, 0xad, 0x81,
+ 0xa4, 0xf1, 0x4b, 0x82, 0x2f, 0x5b, 0x09, 0x15,
+ 0x68, 0xa9, 0xcd, 0xd4, 0xf1, 0x55, 0xfd, 0xa2,
+ 0xc2, 0x2e, 0x42, 0x24, 0x78, 0xd3, 0x05, 0xf3,
+ 0xf8, 0x96,
+ },
+ },
+ {
+ sha1.New,
+ []byte{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ },
+ []byte{
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+ },
+ []byte{
+ 0x8a, 0xda, 0xe0, 0x9a, 0x2a, 0x30, 0x70, 0x59,
+ 0x47, 0x8d, 0x30, 0x9b, 0x26, 0xc4, 0x11, 0x5a,
+ 0x22, 0x4c, 0xfa, 0xf6,
+ },
+ []byte{
+ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+ },
+ []byte{
+ 0x0b, 0xd7, 0x70, 0xa7, 0x4d, 0x11, 0x60, 0xf7,
+ 0xc9, 0xf1, 0x2c, 0xd5, 0x91, 0x2a, 0x06, 0xeb,
+ 0xff, 0x6a, 0xdc, 0xae, 0x89, 0x9d, 0x92, 0x19,
+ 0x1f, 0xe4, 0x30, 0x56, 0x73, 0xba, 0x2f, 0xfe,
+ 0x8f, 0xa3, 0xf1, 0xa4, 0xe5, 0xad, 0x79, 0xf3,
+ 0xf3, 0x34, 0xb3, 0xb2, 0x02, 0xb2, 0x17, 0x3c,
+ 0x48, 0x6e, 0xa3, 0x7c, 0xe3, 0xd3, 0x97, 0xed,
+ 0x03, 0x4c, 0x7f, 0x9d, 0xfe, 0xb1, 0x5c, 0x5e,
+ 0x92, 0x73, 0x36, 0xd0, 0x44, 0x1f, 0x4c, 0x43,
+ 0x00, 0xe2, 0xcf, 0xf0, 0xd0, 0x90, 0x0b, 0x52,
+ 0xd3, 0xb4,
+ },
+ },
+ {
+ sha1.New,
+ []byte{
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ },
+ []byte{},
+ []byte{
+ 0xda, 0x8c, 0x8a, 0x73, 0xc7, 0xfa, 0x77, 0x28,
+ 0x8e, 0xc6, 0xf5, 0xe7, 0xc2, 0x97, 0x78, 0x6a,
+ 0xa0, 0xd3, 0x2d, 0x01,
+ },
+ []byte{},
+ []byte{
+ 0x0a, 0xc1, 0xaf, 0x70, 0x02, 0xb3, 0xd7, 0x61,
+ 0xd1, 0xe5, 0x52, 0x98, 0xda, 0x9d, 0x05, 0x06,
+ 0xb9, 0xae, 0x52, 0x05, 0x72, 0x20, 0xa3, 0x06,
+ 0xe0, 0x7b, 0x6b, 0x87, 0xe8, 0xdf, 0x21, 0xd0,
+ 0xea, 0x00, 0x03, 0x3d, 0xe0, 0x39, 0x84, 0xd3,
+ 0x49, 0x18,
+ },
+ },
+ {
+ sha1.New,
+ []byte{
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ },
+ nil,
+ []byte{
+ 0x2a, 0xdc, 0xca, 0xda, 0x18, 0x77, 0x9e, 0x7c,
+ 0x20, 0x77, 0xad, 0x2e, 0xb1, 0x9d, 0x3f, 0x3e,
+ 0x73, 0x13, 0x85, 0xdd,
+ },
+ nil,
+ []byte{
+ 0x2c, 0x91, 0x11, 0x72, 0x04, 0xd7, 0x45, 0xf3,
+ 0x50, 0x0d, 0x63, 0x6a, 0x62, 0xf6, 0x4f, 0x0a,
+ 0xb3, 0xba, 0xe5, 0x48, 0xaa, 0x53, 0xd4, 0x23,
+ 0xb0, 0xd1, 0xf2, 0x7e, 0xbb, 0xa6, 0xf5, 0xe5,
+ 0x67, 0x3a, 0x08, 0x1d, 0x70, 0xcc, 0xe7, 0xac,
+ 0xfc, 0x48,
+ },
+ },
+}
+
+func TestHKDF(t *testing.T) {
+ for i, tt := range hkdfTests {
+ prk := Extract(tt.hash, tt.master, tt.salt)
+ if !bytes.Equal(prk, tt.prk) {
+ t.Errorf("test %d: incorrect PRK: have %v, need %v.", i, prk, tt.prk)
+ }
+
+ hkdf := New(tt.hash, tt.master, tt.salt, tt.info)
+ out := make([]byte, len(tt.out))
+
+ n, err := io.ReadFull(hkdf, out)
+ if n != len(tt.out) || err != nil {
+ t.Errorf("test %d: not enough output bytes: %d.", i, n)
+ }
+
+ if !bytes.Equal(out, tt.out) {
+ t.Errorf("test %d: incorrect output: have %v, need %v.", i, out, tt.out)
+ }
+
+ hkdf = Expand(tt.hash, prk, tt.info)
+
+ n, err = io.ReadFull(hkdf, out)
+ if n != len(tt.out) || err != nil {
+ t.Errorf("test %d: not enough output bytes from Expand: %d.", i, n)
+ }
+
+ if !bytes.Equal(out, tt.out) {
+ t.Errorf("test %d: incorrect output from Expand: have %v, need %v.", i, out, tt.out)
+ }
+ }
+}
+
+func TestHKDFMultiRead(t *testing.T) {
+ for i, tt := range hkdfTests {
+ hkdf := New(tt.hash, tt.master, tt.salt, tt.info)
+ out := make([]byte, len(tt.out))
+
+ for b := 0; b < len(tt.out); b++ {
+ n, err := io.ReadFull(hkdf, out[b:b+1])
+ if n != 1 || err != nil {
+ t.Errorf("test %d.%d: not enough output bytes: have %d, need %d .", i, b, n, len(tt.out))
+ }
+ }
+
+ if !bytes.Equal(out, tt.out) {
+ t.Errorf("test %d: incorrect output: have %v, need %v.", i, out, tt.out)
+ }
+ }
+}
+
+func TestHKDFLimit(t *testing.T) {
+ hash := sha1.New
+ master := []byte{0x00, 0x01, 0x02, 0x03}
+ info := []byte{}
+
+ hkdf := New(hash, master, nil, info)
+ limit := hash().Size() * 255
+ out := make([]byte, limit)
+
+ // The maximum output bytes should be extractable
+ n, err := io.ReadFull(hkdf, out)
+ if n != limit || err != nil {
+ t.Errorf("not enough output bytes: %d, %v.", n, err)
+ }
+
+ // Reading one more should fail
+ n, err = io.ReadFull(hkdf, make([]byte, 1))
+ if n > 0 || err == nil {
+ t.Errorf("key expansion overflowed: n = %d, err = %v", n, err)
+ }
+}
+
+func Benchmark16ByteMD5Single(b *testing.B) {
+ benchmarkHKDFSingle(md5.New, 16, b)
+}
+
+func Benchmark20ByteSHA1Single(b *testing.B) {
+ benchmarkHKDFSingle(sha1.New, 20, b)
+}
+
+func Benchmark32ByteSHA256Single(b *testing.B) {
+ benchmarkHKDFSingle(sha256.New, 32, b)
+}
+
+func Benchmark64ByteSHA512Single(b *testing.B) {
+ benchmarkHKDFSingle(sha512.New, 64, b)
+}
+
+func Benchmark8ByteMD5Stream(b *testing.B) {
+ benchmarkHKDFStream(md5.New, 8, b)
+}
+
+func Benchmark16ByteMD5Stream(b *testing.B) {
+ benchmarkHKDFStream(md5.New, 16, b)
+}
+
+func Benchmark8ByteSHA1Stream(b *testing.B) {
+ benchmarkHKDFStream(sha1.New, 8, b)
+}
+
+func Benchmark20ByteSHA1Stream(b *testing.B) {
+ benchmarkHKDFStream(sha1.New, 20, b)
+}
+
+func Benchmark8ByteSHA256Stream(b *testing.B) {
+ benchmarkHKDFStream(sha256.New, 8, b)
+}
+
+func Benchmark32ByteSHA256Stream(b *testing.B) {
+ benchmarkHKDFStream(sha256.New, 32, b)
+}
+
+func Benchmark8ByteSHA512Stream(b *testing.B) {
+ benchmarkHKDFStream(sha512.New, 8, b)
+}
+
+func Benchmark64ByteSHA512Stream(b *testing.B) {
+ benchmarkHKDFStream(sha512.New, 64, b)
+}
+
+func benchmarkHKDFSingle(hasher func() hash.Hash, block int, b *testing.B) {
+ master := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}
+ salt := []byte{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}
+ info := []byte{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}
+ out := make([]byte, block)
+
+ b.SetBytes(int64(block))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ hkdf := New(hasher, master, salt, info)
+ io.ReadFull(hkdf, out)
+ }
+}
+
+func benchmarkHKDFStream(hasher func() hash.Hash, block int, b *testing.B) {
+ master := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}
+ salt := []byte{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}
+ info := []byte{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}
+ out := make([]byte, block)
+
+ b.SetBytes(int64(block))
+ b.ResetTimer()
+
+ hkdf := New(hasher, master, salt, info)
+ for i := 0; i < b.N; i++ {
+ _, err := io.ReadFull(hkdf, out)
+ if err != nil {
+ hkdf = New(hasher, master, salt, info)
+ i--
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/alias/alias.go b/local_crypto_patch/contents/internal/alias/alias.go
new file mode 100644
index 0000000000..551ff0c353
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/alias/alias_purego.go b/local_crypto_patch/contents/internal/alias/alias_purego.go
new file mode 100644
index 0000000000..6fe61b5c6e
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/alias/alias_test.go b/local_crypto_patch/contents/internal/alias/alias_test.go
new file mode 100644
index 0000000000..a68fb33667
--- /dev/null
+++ b/local_crypto_patch/contents/internal/alias/alias_test.go
@@ -0,0 +1,46 @@
+// 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 alias
+
+import "testing"
+
+var a, b [100]byte
+
+var aliasingTests = []struct {
+ x, y []byte
+ anyOverlap, inexactOverlap bool
+}{
+ {a[:], b[:], false, false},
+ {a[:], b[:0], false, false},
+ {a[:], b[:50], false, false},
+ {a[40:50], a[50:60], false, false},
+ {a[40:50], a[60:70], false, false},
+ {a[:51], a[50:], true, true},
+ {a[:], a[:], true, false},
+ {a[:50], a[:60], true, false},
+ {a[:], nil, false, false},
+ {nil, nil, false, false},
+ {a[:], a[:0], false, false},
+ {a[:10], a[:10:20], true, false},
+ {a[:10], a[5:10:20], true, true},
+}
+
+func testAliasing(t *testing.T, i int, x, y []byte, anyOverlap, inexactOverlap bool) {
+ any := AnyOverlap(x, y)
+ if any != anyOverlap {
+ t.Errorf("%d: wrong AnyOverlap result, expected %v, got %v", i, anyOverlap, any)
+ }
+ inexact := InexactOverlap(x, y)
+ if inexact != inexactOverlap {
+ t.Errorf("%d: wrong InexactOverlap result, expected %v, got %v", i, inexactOverlap, any)
+ }
+}
+
+func TestAliasing(t *testing.T) {
+ for i, tt := range aliasingTests {
+ testAliasing(t, i, tt.x, tt.y, tt.anyOverlap, tt.inexactOverlap)
+ testAliasing(t, i, tt.y, tt.x, tt.anyOverlap, tt.inexactOverlap)
+ }
+}
diff --git a/local_crypto_patch/contents/internal/poly1305/mac_noasm.go b/local_crypto_patch/contents/internal/poly1305/mac_noasm.go
new file mode 100644
index 0000000000..8d99551fee
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/poly1305/poly1305.go b/local_crypto_patch/contents/internal/poly1305/poly1305.go
new file mode 100644
index 0000000000..4aaea810a2
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/poly1305/poly1305_test.go b/local_crypto_patch/contents/internal/poly1305/poly1305_test.go
new file mode 100644
index 0000000000..e7ec6d19dd
--- /dev/null
+++ b/local_crypto_patch/contents/internal/poly1305/poly1305_test.go
@@ -0,0 +1,276 @@
+// 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
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "encoding/hex"
+ "flag"
+ "testing"
+ "unsafe"
+)
+
+var stressFlag = flag.Bool("stress", false, "run slow stress tests")
+
+type test struct {
+ in string
+ key string
+ tag string
+ state string
+}
+
+func (t *test) Input() []byte {
+ in, err := hex.DecodeString(t.in)
+ if err != nil {
+ panic(err)
+ }
+ return in
+}
+
+func (t *test) Key() [32]byte {
+ buf, err := hex.DecodeString(t.key)
+ if err != nil {
+ panic(err)
+ }
+ var key [32]byte
+ copy(key[:], buf[:32])
+ return key
+}
+
+func (t *test) Tag() [16]byte {
+ buf, err := hex.DecodeString(t.tag)
+ if err != nil {
+ panic(err)
+ }
+ var tag [16]byte
+ copy(tag[:], buf[:16])
+ return tag
+}
+
+func (t *test) InitialState() [3]uint64 {
+ // state is hex encoded in big-endian byte order
+ if t.state == "" {
+ return [3]uint64{0, 0, 0}
+ }
+ buf, err := hex.DecodeString(t.state)
+ if err != nil {
+ panic(err)
+ }
+ if len(buf) != 3*8 {
+ panic("incorrect state length")
+ }
+ return [3]uint64{
+ binary.BigEndian.Uint64(buf[16:24]),
+ binary.BigEndian.Uint64(buf[8:16]),
+ binary.BigEndian.Uint64(buf[0:8]),
+ }
+}
+
+func testSum(t *testing.T, unaligned bool, sumImpl func(tag *[TagSize]byte, msg []byte, key *[32]byte)) {
+ var tag [16]byte
+ for i, v := range testData {
+ // cannot set initial state before calling sum, so skip those tests
+ if v.InitialState() != [3]uint64{0, 0, 0} {
+ continue
+ }
+
+ in := v.Input()
+ if unaligned {
+ in = unalignBytes(in)
+ }
+ key := v.Key()
+ sumImpl(&tag, in, &key)
+ if tag != v.Tag() {
+ t.Errorf("%d: expected %x, got %x", i, v.Tag(), tag[:])
+ }
+ if !Verify(&tag, in, &key) {
+ t.Errorf("%d: tag didn't verify", i)
+ }
+ // If the key is zero, the tag will always be zero, independent of the input.
+ if len(in) > 0 && key != [32]byte{} {
+ in[0] ^= 0xff
+ if Verify(&tag, in, &key) {
+ t.Errorf("%d: tag verified after altering the input", i)
+ }
+ in[0] ^= 0xff
+ }
+ // If the input is empty, the tag only depends on the second half of the key.
+ if len(in) > 0 {
+ key[0] ^= 0xff
+ if Verify(&tag, in, &key) {
+ t.Errorf("%d: tag verified after altering the key", i)
+ }
+ key[0] ^= 0xff
+ }
+ tag[0] ^= 0xff
+ if Verify(&tag, in, &key) {
+ t.Errorf("%d: tag verified after altering the tag", i)
+ }
+ tag[0] ^= 0xff
+ }
+}
+
+func TestBurnin(t *testing.T) {
+ // This test can be used to sanity-check significant changes. It can
+ // take about many minutes to run, even on fast machines. It's disabled
+ // by default.
+ if !*stressFlag {
+ t.Skip("skipping without -stress")
+ }
+
+ var key [32]byte
+ var input [25]byte
+ var output [16]byte
+
+ for i := range key {
+ key[i] = 1
+ }
+ for i := range input {
+ input[i] = 2
+ }
+
+ for i := uint64(0); i < 1e10; i++ {
+ Sum(&output, input[:], &key)
+ copy(key[0:], output[:])
+ copy(key[16:], output[:])
+ copy(input[:], output[:])
+ copy(input[16:], output[:])
+ }
+
+ const expected = "5e3b866aea0b636d240c83c428f84bfa"
+ if got := hex.EncodeToString(output[:]); got != expected {
+ t.Errorf("expected %s, got %s", expected, got)
+ }
+}
+
+func TestSum(t *testing.T) { testSum(t, false, Sum) }
+func TestSumUnaligned(t *testing.T) { testSum(t, true, Sum) }
+func TestSumGeneric(t *testing.T) { testSum(t, false, sumGeneric) }
+func TestSumGenericUnaligned(t *testing.T) { testSum(t, true, sumGeneric) }
+
+func TestWriteGeneric(t *testing.T) { testWriteGeneric(t, false) }
+func TestWriteGenericUnaligned(t *testing.T) { testWriteGeneric(t, true) }
+func TestWrite(t *testing.T) { testWrite(t, false) }
+func TestWriteUnaligned(t *testing.T) { testWrite(t, true) }
+
+func testWriteGeneric(t *testing.T, unaligned bool) {
+ for i, v := range testData {
+ key := v.Key()
+ input := v.Input()
+ var out [16]byte
+
+ if unaligned {
+ input = unalignBytes(input)
+ }
+ h := newMACGeneric(&key)
+ if s := v.InitialState(); s != [3]uint64{0, 0, 0} {
+ h.macState.h = s
+ }
+ n, err := h.Write(input[:len(input)/3])
+ if err != nil || n != len(input[:len(input)/3]) {
+ t.Errorf("#%d: unexpected Write results: n = %d, err = %v", i, n, err)
+ }
+ n, err = h.Write(input[len(input)/3:])
+ if err != nil || n != len(input[len(input)/3:]) {
+ t.Errorf("#%d: unexpected Write results: n = %d, err = %v", i, n, err)
+ }
+ h.Sum(&out)
+ if tag := v.Tag(); out != tag {
+ t.Errorf("%d: expected %x, got %x", i, tag[:], out[:])
+ }
+ }
+}
+
+func testWrite(t *testing.T, unaligned bool) {
+ for i, v := range testData {
+ key := v.Key()
+ input := v.Input()
+ var out [16]byte
+
+ if unaligned {
+ input = unalignBytes(input)
+ }
+ h := New(&key)
+ if s := v.InitialState(); s != [3]uint64{0, 0, 0} {
+ h.macState.h = s
+ }
+ n, err := h.Write(input[:len(input)/3])
+ if err != nil || n != len(input[:len(input)/3]) {
+ t.Errorf("#%d: unexpected Write results: n = %d, err = %v", i, n, err)
+ }
+ n, err = h.Write(input[len(input)/3:])
+ if err != nil || n != len(input[len(input)/3:]) {
+ t.Errorf("#%d: unexpected Write results: n = %d, err = %v", i, n, err)
+ }
+ h.Sum(out[:0])
+ tag := v.Tag()
+ if out != tag {
+ t.Errorf("%d: expected %x, got %x", i, tag[:], out[:])
+ }
+ if !h.Verify(tag[:]) {
+ t.Errorf("%d: Verify failed", i)
+ }
+ tag[0] ^= 0xff
+ if h.Verify(tag[:]) {
+ t.Errorf("%d: Verify succeeded after modifying the tag", i)
+ }
+ }
+}
+
+func benchmarkSum(b *testing.B, size int, unaligned bool) {
+ var out [16]byte
+ var key [32]byte
+ in := make([]byte, size)
+ if unaligned {
+ in = unalignBytes(in)
+ }
+ rand.Read(in)
+ b.SetBytes(int64(len(in)))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Sum(&out, in, &key)
+ }
+}
+
+func benchmarkWrite(b *testing.B, size int, unaligned bool) {
+ var key [32]byte
+ h := New(&key)
+ in := make([]byte, size)
+ if unaligned {
+ in = unalignBytes(in)
+ }
+ rand.Read(in)
+ b.SetBytes(int64(len(in)))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ h.Write(in)
+ }
+}
+
+func Benchmark64(b *testing.B) { benchmarkSum(b, 64, false) }
+func Benchmark1K(b *testing.B) { benchmarkSum(b, 1024, false) }
+func Benchmark2M(b *testing.B) { benchmarkSum(b, 2*1024*1024, false) }
+func Benchmark64Unaligned(b *testing.B) { benchmarkSum(b, 64, true) }
+func Benchmark1KUnaligned(b *testing.B) { benchmarkSum(b, 1024, true) }
+func Benchmark2MUnaligned(b *testing.B) { benchmarkSum(b, 2*1024*1024, true) }
+
+func BenchmarkWrite64(b *testing.B) { benchmarkWrite(b, 64, false) }
+func BenchmarkWrite1K(b *testing.B) { benchmarkWrite(b, 1024, false) }
+func BenchmarkWrite2M(b *testing.B) { benchmarkWrite(b, 2*1024*1024, false) }
+func BenchmarkWrite64Unaligned(b *testing.B) { benchmarkWrite(b, 64, true) }
+func BenchmarkWrite1KUnaligned(b *testing.B) { benchmarkWrite(b, 1024, true) }
+func BenchmarkWrite2MUnaligned(b *testing.B) { benchmarkWrite(b, 2*1024*1024, true) }
+
+func unalignBytes(in []byte) []byte {
+ out := make([]byte, len(in)+1)
+ if uintptr(unsafe.Pointer(&out[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
+ out = out[1:]
+ } else {
+ out = out[:len(in)]
+ }
+ copy(out, in)
+ return out
+}
diff --git a/local_crypto_patch/contents/internal/poly1305/sum_amd64.s b/local_crypto_patch/contents/internal/poly1305/sum_amd64.s
new file mode 100644
index 0000000000..133757384b
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/poly1305/sum_asm.go b/local_crypto_patch/contents/internal/poly1305/sum_asm.go
new file mode 100644
index 0000000000..315b84ac39
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/poly1305/sum_generic.go b/local_crypto_patch/contents/internal/poly1305/sum_generic.go
new file mode 100644
index 0000000000..ec2202bd7d
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/poly1305/sum_loong64.s b/local_crypto_patch/contents/internal/poly1305/sum_loong64.s
new file mode 100644
index 0000000000..bc8361da40
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/poly1305/sum_ppc64x.s b/local_crypto_patch/contents/internal/poly1305/sum_ppc64x.s
new file mode 100644
index 0000000000..6899a1dabc
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/poly1305/sum_s390x.go b/local_crypto_patch/contents/internal/poly1305/sum_s390x.go
new file mode 100644
index 0000000000..e1d033a491
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/poly1305/sum_s390x.s b/local_crypto_patch/contents/internal/poly1305/sum_s390x.s
new file mode 100644
index 0000000000..0fe3a7c217
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/internal/poly1305/vectors_test.go b/local_crypto_patch/contents/internal/poly1305/vectors_test.go
new file mode 100644
index 0000000000..4788950f19
--- /dev/null
+++ b/local_crypto_patch/contents/internal/poly1305/vectors_test.go
@@ -0,0 +1,3000 @@
+// 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 poly1305
+
+var testData = [...]test{
+ // edge cases
+ {
+ // see https://go-review.googlesource.com/#/c/30101/
+ key: "3b3a29e93b213a5c5c3b3b053a3a8c0d00000000000000000000000000000000",
+ tag: "6dc18b8c344cd79927118bbe84b7f314",
+ in: "81d8b2e46a25213b58fee4213a2a28e921c12a9632516d3b73272727becf2129",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "04000000000000000000000000000000", // (2¹³⁰-1) % (2¹³⁰-5)
+ in: "ffffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "faffffffffffffffffffffffffffffff", // (2¹³⁰-6) % (2¹³⁰-5)
+ in: "faffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "00000000000000000000000000000000", // (2¹³⁰-5) % (2¹³⁰-5)
+ in: "fbffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "f9ffffffffffffffffffffffffffffff", // (2*(2¹³⁰-6)) % (2¹³⁰-5)
+ in: "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "00000000000000000000000000000000", // (2*(2¹³⁰-5)) % (2¹³⁰-5)
+ in: "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "f8ffffffffffffffffffffffffffffff", // (3*(2¹³⁰-6)) % (2¹³⁰-5)
+ in: "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "00000000000000000000000000000000", // (3*(2¹³⁰-5)) % (2¹³⁰-5)
+ in: "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "f7ffffffffffffffffffffffffffffff", // (4*(2¹³⁰-6)) % (2¹³⁰-5)
+ in: "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "00000000000000000000000000000000", // (4*(2¹³⁰-5)) % (2¹³⁰-5)
+ in: "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "f3ffffffffffffffffffffffffffffff", // (8*(2¹³⁰-6)) % (2¹³⁰-5)
+ in: "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "00000000000000000000000000000000", // (8*(2¹³⁰-5)) % (2¹³⁰-5)
+ in: "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "ebffffffffffffffffffffffffffffff", // (16*(2¹³⁰-6)) % (2¹³⁰-5)
+ in: "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "faffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ {
+ key: "0100000000000000000000000000000000000000000000000000000000000000",
+ tag: "00000000000000000000000000000000", // (16*(2¹³⁰-5)) % (2¹³⁰-5)
+ in: "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "fbffffffffffffffffffffffffffffff" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000" +
+ "00000000000000000000000000000000",
+ },
+ // original smoke tests
+ {
+ key: "746869732069732033322d62797465206b657920666f7220506f6c7931333035",
+ tag: "a6f745008f81c916a20dcc74eef2b2f0",
+ in: "48656c6c6f20776f726c6421",
+ },
+ {
+ key: "746869732069732033322d62797465206b657920666f7220506f6c7931333035",
+ tag: "49ec78090e481ec6c26b33b91ccc0307",
+ in: "0000000000000000000000000000000000000000000000000000000000000000",
+ },
+ {
+ key: "746869732069732033322d62797465206b657920666f7220506f6c7931333035",
+ tag: "da84bcab02676c38cdb015604274c2aa",
+ in: "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000",
+ },
+ {
+ key: "0000000000000000000000000000000000000000000000000000000000000000",
+ tag: "00000000000000000000000000000000",
+ in: "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000000000" +
+ "000000000000000000000000000000000000000000000000000000",
+ },
+ // randomly generated
+ {
+ key: "52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649",
+ tag: "9566c74d10037c4d7bbb0407d1e2c649",
+ in: "",
+ },
+ {
+ key: "81855ad8681d0d86d1e91e00167939cb6694d2c422acd208a0072939487f6999",
+ tag: "eaa270caaa12faa39b797374a4b8a420",
+ in: "eb",
+ },
+ {
+ key: "9d18a44784045d87f3c67cf22746e995af5a25367951baa2ff6cd471c483f15f",
+ tag: "dbea66e1da48a8f822887c6162c2acf1",
+ in: "b90b",
+ },
+ {
+ key: "adb37c5821b6d95526a41a9504680b4e7c8b763a1b1d49d4955c848621632525",
+ tag: "6ac09aaa88c32ee95a7198376f16abdb",
+ in: "3fec73",
+ },
+ {
+ key: "8dd7a9e28bf921119c160f0702448615bbda08313f6a8eb668d20bf505987592",
+ tag: "b1443487f97fe340b04a74719ed4de68",
+ in: "1e668a5b",
+ },
+ {
+ key: "df2c7fc4844592d2572bcd0668d2d6c52f5054e2d0836bf84c7174cb7476364c",
+ tag: "7463be0f9d99a5348039e4afcbf4019c",
+ in: "c3dbd968b0",
+ },
+ {
+ key: "f7172ed85794bb358b0c3b525da1786f9fff094279db1944ebd7a19d0f7bbacb",
+ tag: "2edaee3bcf303fd05609e131716f8157",
+ in: "e0255aa5b7d4",
+ },
+ {
+ key: "4bec40f84c892b9bffd43629b0223beea5f4f74391f445d15afd4294040374f6",
+ tag: "965f18767420c1d94a4ef657e8d15e1e",
+ in: "924b98cbf8713f",
+ },
+ {
+ key: "8d962d7c8d019192c24224e2cafccae3a61fb586b14323a6bc8f9e7df1d92933",
+ tag: "2bf4a33287dd6d87e1ed4282f7342b6a",
+ in: "3ff993933bea6f5b",
+ },
+ {
+ key: "3af6de0374366c4719e43a1b067d89bc7f01f1f573981659a44ff17a4c7215a3",
+ tag: "c5e987b60373a48893c5af30acf2471f",
+ in: "b539eb1e5849c6077d",
+ },
+ {
+ key: "bb5722f5717a289a266f97647981998ebea89c0b4b373970115e82ed6f4125c8",
+ tag: "19f0f640b309d168ea1b480e6a4faee5",
+ in: "fa7311e4d7defa922daa",
+ },
+ {
+ key: "e7786667f7e936cd4f24abf7df866baa56038367ad6145de1ee8f4a8b0993ebd",
+ tag: "de75e5565d97834b9fa84ad568d31359",
+ in: "f8883a0ad8be9c3978b048",
+ },
+ {
+ key: "83e56a156a8de563afa467d49dec6a40e9a1d007f033c2823061bdd0eaa59f8e",
+ tag: "de184a5a9b826aa203c5c017986d6690",
+ in: "4da6430105220d0b29688b73",
+ },
+ {
+ key: "4b8ea0f3ca9936e8461f10d77c96ea80a7a665f606f6a63b7f3dfd2567c18979",
+ tag: "7478f18d9684905aa5d1a34ee67e4c84",
+ in: "e4d60f26686d9bf2fb26c901ff",
+ },
+ {
+ key: "354cde1607ee294b39f32b7c7822ba64f84ab43ca0c6e6b91c1fd3be89904341",
+ tag: "3b2008a9c52b5308f5538b789ab5506f",
+ in: "79d3af4491a369012db92d184fc3",
+ },
+ {
+ key: "9d1734ff5716428953bb6865fcf92b0c3a17c9028be9914eb7649c6c93478009",
+ tag: "71c8e76a67a505b7370b562ba15ba032",
+ in: "79d1830356f2a54c3deab2a4b4475d",
+ },
+ {
+ key: "63afbe8fb56987c77f5818526f1814be823350eab13935f31d84484517e924ae",
+ tag: "1dc895f74f866bdb3edf6c4430829c1c",
+ in: "f78ae151c00755925836b7075885650c",
+ },
+ {
+ key: "30ec29a3703934bf50a28da102975deda77e758579ea3dfe4136abf752b3b827",
+ tag: "afca2b3ba7b0e1a928001966883e9b16",
+ in: "1d03e944b3c9db366b75045f8efd69d22ae5411947cb553d7694267aef4e" +
+ "bcea406b32d6108bd68584f57e37caac6e33feaa3263a399437024ba9c9b" +
+ "14678a274f01a910ae295f6efbfe5f5abf44ccde263b5606633e2bf0006f" +
+ "28295d7d39069f01a239c4365854c3af7f6b41d631f92b9a8d12f4125732" +
+ "5fff332f7576b0620556304a3e3eae14c28d0cea39d2901a52720da85ca1" +
+ "e4b38eaf3f",
+ },
+ {
+ key: "44c6c6ef8362f2f54fc00e09d6fc25640854c15dfcacaa8a2cecce5a3aba53ab",
+ tag: "6f2a09aa76c9b76774e31ec02dcf7991",
+ in: "705b18db94b4d338a5143e63408d8724b0cf3fae17a3f79be1072fb63c35" +
+ "d6042c4160f38ee9e2a9f3fb4ffb0019b454d522b5ffa17604193fb89667" +
+ "10a7960732ca52cf53c3f520c889b79bf504cfb57c7601232d589baccea9" +
+ "d6e263e25c27741d3f6c62cbbb15d9afbcbf7f7da41ab0408e3969c2e2cd" +
+ "cf233438bf1774ace7709a4f091e9a83fdeae0ec55eb233a9b5394cb3c78" +
+ "56b546d313c8a3b4c1c0e05447f4ba370eb36dbcfdec90b302dcdc3b9ef5" +
+ "22e2a6f1ed0afec1f8e20faabedf6b162e717d3a748a58677a0c56348f89" +
+ "21a266b11d0f334c62fe52ba53af19779cb2948b6570ffa0b773963c130a" +
+ "d797ddea",
+ },
+ {
+ key: "fe4e3ad29b5125210f0ef1c314090f07c79a6f571c246f3e9ac0b7413ef110bd",
+ tag: "27381e3fc2a356103fb796f107d826e7",
+ in: "58b00ce73bff706f7ff4b6f44090a32711f3208e4e4b89cb5165ce64002c" +
+ "bd9c2887aa113df2468928d5a23b9ca740f80c9382d9c6034ad2960c7965" +
+ "03e1ce221725f50caf1fbfe831b10b7bf5b15c47a53dbf8e7dcafc9e1386" +
+ "47a4b44ed4bce964ed47f74aa594468ced323cb76f0d3fac476c9fb03fc9" +
+ "228fbae88fd580663a0454b68312207f0a3b584c62316492b49753b5d502" +
+ "7ce15a4f0a58250d8fb50e77f2bf4f0152e5d49435807f9d4b97be6fb779" +
+ "70466a5626fe33408cf9e88e2c797408a32d29416baf206a329cfffd4a75" +
+ "e498320982c85aad70384859c05a4b13a1d5b2f5bfef5a6ed92da482caa9" +
+ "568e5b6fe9d8a9ddd9eb09277b92cef9046efa18500944cbe800a0b1527e" +
+ "a6",
+ },
+ {
+ key: "4729a861d2f6497a3235c37f4192779ec1d96b3b1c5424fce0b727b03072e641",
+ tag: "0173965669fb9de88d38a827a0271271",
+ in: "5a761f03abaa40abc9448fddeb2191d945c04767af847afd0edb5d8857b7" +
+ "99acb18e4affabe3037ffe7fa68aa8af5e39cc416e734d373c5ebebc9cdc" +
+ "c595bcce3c7bd3d8df93fab7e125ddebafe65a31bd5d41e2d2ce9c2b1789" +
+ "2f0fea1931a290220777a93143dfdcbfa68406e877073ff08834e197a403" +
+ "4aa48afa3f85b8a62708caebbac880b5b89b93da53810164402104e648b6" +
+ "226a1b78021851f5d9ac0f313a89ddfc454c5f8f72ac89b38b19f53784c1" +
+ "9e9beac03c875a27db029de37ae37a42318813487685929359ca8c5eb94e" +
+ "152dc1af42ea3d1676c1bdd19ab8e2925c6daee4de5ef9f9dcf08dfcbd02" +
+ "b80809398585928a0f7de50be1a6dc1d5768e8537988fddce562e9b948c9" +
+ "18bba3e933e5c400cde5e60c5ead6fc7ae77ba1d259b188a4b21c86fbc23" +
+ "d728b45347eada650af24c56d0800a8691332088a805bd55c446e25eb075" +
+ "90bafcccbec6177536401d9a2b7f512b54bfc9d00532adf5aaa7c3a96bc5" +
+ "9b489f77d9042c5bce26b163defde5ee6a0fbb3e9346cef81f0ae9515ef3" +
+ "0fa47a364e75aea9e111d596e685a591121966e031650d510354aa845580" +
+ "ff560760fd36514ca197c875f1d02d9216eba7627e2398322eb5cf43d72b" +
+ "d2e5b887d4630fb8d4747ead6eb82acd1c5b078143ee26a586ad23139d50" +
+ "41723470bf24a865837c",
+ },
+ {
+ key: "9123461c41f5ff99aa99ce24eb4d788576e3336e65491622558fdf297b9fa007",
+ tag: "1eb0cdad9237905250d30a24fe172a34",
+ in: "864bafd7cd4ca1b2fb5766ab431a032b72b9a7e937ed648d0801f29055d3" +
+ "090d2463718254f9442483c7b98b938045da519843854b0ed3f7ba951a49" +
+ "3f321f0966603022c1dfc579b99ed9d20d573ad53171c8fef7f1f4e4613b" +
+ "b365b2ebb44f0ffb6907136385cdc838f0bdd4c812f042577410aca008c2" +
+ "afbc4c79c62572e20f8ed94ee62b4de7aa1cc84c887e1f7c31e927dfe52a" +
+ "5f8f46627eb5d3a4fe16fafce23623e196c9dfff7fbaff4ffe94f4589733" +
+ "e563e19d3045aad3e226488ac02cca4291aed169dce5039d6ab00e40f67a" +
+ "ab29332de1448b35507c7c8a09c4db07105dc31003620405da3b2169f5a9" +
+ "10c9d0096e5e3ef1b570680746acd0cc7760331b663138d6d342b051b5df" +
+ "410637cf7aee9b0c8c10a8f9980630f34ce001c0ab7ac65e502d39b216cb" +
+ "c50e73a32eaf936401e2506bd8b82c30d346bc4b2fa319f245a8657ec122" +
+ "eaf4ad5425c249ee160e17b95541c2aee5df820ac85de3f8e784870fd87a" +
+ "36cc0d163833df636613a9cc947437b6592835b9f6f4f8c0e70dbeebae7b" +
+ "14cdb9bc41033aa5baf40d45e24d72eac4a28e3ca030c9937ab8409a7cbf" +
+ "05ae21f97425254543d94d115900b90ae703b97d9856d2441d14ba49a677" +
+ "de8b18cb454b99ddd9daa7ccbb7500dae4e2e5df8cf3859ebddada6745fb" +
+ "a6a04c5c37c7ca35036f11732ce8bc27b48868611fc73c82a491bfabd7a1" +
+ "9df50fdc78a55dbbc2fd37f9296566557fab885b039f30e706f0cd5961e1" +
+ "9b642221db44a69497b8ad99408fe1e037c68bf7c5e5de1d2c68192348ec" +
+ "1189fb2e36973cef09ff14be23922801f6eaee41409158b45f2dec82d17c" +
+ "aaba160cd6",
+ },
+ {
+ key: "40ff73495fe4a05ce1202ca7287ed3235b95e69f571fa5e656aaa51fae1ebdd7",
+ tag: "2e619d8ea81b77484e4fddeb29844e4b",
+ in: "aa6269c2ec7f4057b33593bc84888c970fd528d4a99a1eab9d2420134537" +
+ "cd6d02282e0981e140232a4a87383a21d1845c408ad757043813032a0bd5" +
+ "a30dcca6e3aa2df04715d879279a96879a4f3690ac2025a60c7db15e0501" +
+ "ebc34b734355fe4a059bd3899d920e95f1c46d432f9b08e64d7f9b38965d" +
+ "5a77a7ac183c3833e1a3425ead69d4f975012fd1a49ed832f69e6e9c63b4" +
+ "53ec049c9e7a5cf944232d10353f64434abae060f6506ad3fdb1f4415b0a" +
+ "f9ce8c208bc20ee526741539fa3203c77ecba410fd6718f227e0b430f9bc" +
+ "b049a3d38540dc222969120ce80f2007cd42a708a721aa29987b45d4e428" +
+ "811984ecad349cc35dd93515cefe0b002cee5e71c47935e281ebfc4b8b65" +
+ "2b69ccb092e55a20f1b9f97d046296124621928739a86671cc180152b953" +
+ "e3bf9d19f825c3dd54ae1688e49efb5efe65dcdad34bc860010e7c8c997c" +
+ "d5f9e320ca7d39d4ba801a175b1c76f057832f3f36d7d893e216e4c7bbdb" +
+ "548d0ba48449330027368b34f9c69776b4591532da1c5be68ef4eebe8cb8" +
+ "fa7dc5483fb70c2c896334cb1f9cb5dfe044fa086197ff5dfd02f2ba3884" +
+ "c53dd718c8560da743a8e9d4aeae20ccef002d82ca352592b8d8f2a8df3b" +
+ "0c35f15b9b370dca80d4ca8e9a133eb52094f2dd5c08731f52315d828846" +
+ "e37df68fd10658b480f2ac84233633957e688e924ffe3713b52c76fd8a56" +
+ "da8bb07daa8eb4eb8f7334f99256e2766a4109150eed424f0f743543cdea" +
+ "66e5baaa03edc918e8305bb19fc0c6b4ddb4aa3886cb5090940fc6d4cabe" +
+ "2153809e4ed60a0e2af07f1b2a6bb5a6017a578a27cbdc20a1759f76b088" +
+ "9a83ce25ce3ca91a4eb5c2f8580819da04d02c41770c01746de44f3db6e3" +
+ "402e7873db7635516e87b33e4b412ba3df68544920f5ea27ec097710954f" +
+ "42158bdba66d4814c064b4112538676095467c89ba98e6a543758d7093a4" +
+ "94df",
+ },
+ {
+ key: "5cc36d09c7a6472a41f29c380a987b1ecdcf84765f4e5d3ceefc1c02181f570f",
+ tag: "0d57b8cbea8090df0541354673dcb4e0",
+ in: "44fcd629f08dc1ef53c9ae0d8869fe67fdc7a2c67b425f13c5be8d9f630c" +
+ "1d063c02fd75cf64c1aec9d2e2ef6e6431d5f5ad0489078dc61f46494dcc" +
+ "f403dad7f094170d2c3e29c198b0f341e284c4be8fa60c1a478d6bd55dd2" +
+ "c04dad86d2053d5d25b014e3d8b64322cdcb5004faa46cfa2d6ad2ff933b" +
+ "c3bd9a5a74660af3d048a9a43634c0250427d9a6219197a3f3633f841753" +
+ "ba7c27f3619f387b6b1a6cb9c1dc227674aa020724d137da2cb87b1615d5" +
+ "12974fa4747dd1e17d02c9462a44fec150ca3a8f99cc1e4953365e429956" +
+ "5e108535b1f62e1d4ba18e17a52164418bfd1a933f7fb3a126c860830a87" +
+ "293d9271da736e4398c1e37fb75c4bf02786e1faf4b610cd1377fbb9ae18" +
+ "0655a0abefbad700c09473469f1eca5a66d53fa3dc7cd3e7c3b0411d7e14" +
+ "5f96eb9654ab94913dda503a50f9e773842f4d2a5faa60869bf365830511" +
+ "f2ededd03e0a73000edb60c9a29a5f5e194cf3b5667a694690384599d116" +
+ "f8d2fd93b2aed55b7d44b5b054f3f38e788e4fdf36e591568c41d1052cad" +
+ "0fcb68ca4c4bf5090d57df9db6f0d91dd8b11b804f331adb7efb087a5604" +
+ "e9e22b4d54db40bcbc6e272ff5eaddfc1471459e59f0554c58251342134a" +
+ "8daaef1498069ba581ef1da2510be92843487a4eb8111c79a6f0195fc38a" +
+ "d6aee93c1df2b5897eaa38ad8f47ab2fe0e3aa3e6accbfd4c16d46843318" +
+ "5fc61c861b96ca65e34d31f24d6f56ee85092314a4d7656205c15322f1c9" +
+ "7613c079eae292ba966e10d1e700164e518b243f424c46f9ea63db1c2c34" +
+ "b512c403c128ee19030a6226517b805a072512a5e4cd274b7fd1fa23f830" +
+ "058208ff1a063b41039c74036b5b3da8b1a0b93135a710352da0f6c31203" +
+ "a09d1f2329651bb3ab3984ab591f2247e71cd44835e7a1a1b66d8595f7ae" +
+ "f9bf39d1417d2d31ea3599d405ff4b5999a86f52f3259b452909b57937d8" +
+ "5364d6c23deb4f14e0d9fcee9184df5994fdc11f045c025c8d561adb0e7d" +
+ "fd4748fd4b20f84e53322471a410cdb3fd88e48b2e7eb7ae5dae994cb5ea" +
+ "e3eaf21cf9005db560d6d22e4d9b97d7e9e488751afcd72aa176c0fcde93" +
+ "16f676fd527d9c42105b851639f09ea70533d26fc60cbeb4b76ed554fc99" +
+ "177620b28ca6f56a716f8cb384",
+ },
+ {
+ key: "811c3e356e7c793acf114c624dc86ace38e67bff2a60e5b2a6c20723c1b9f003",
+ tag: "c6e59044cefc43ee681c3eed872d02b3",
+ in: "e115b304c023792448794546a2474f04294d7a616215e5dd6c40a65bb6ed" +
+ "b508c3680b14c176c327fdfb1ee21962c0006b7deb4e5de87db21989d13c" +
+ "3ab0462d5d2a52ef4ca0d366ae06a314f50e3a21d9247f814037798cc5e1" +
+ "0a63de027477decdeb8a8e0c279299272490106ddf8683126f60d35772c6" +
+ "dfc744b0adbfd5dcf118c4f2b06cfaf077881d733a5e643b7c46976647d1" +
+ "c1d3f8f6237c6218fa86fb47080b1f7966137667bd6661660c43b75b6339" +
+ "0b514bbe491aa46b524bde1c5b7456255fb214c3f74907b7ce1cba94210b" +
+ "78b5e68f049fcb002b96a5d38d59df6e977d587abb42d0972d5f3ffc898b" +
+ "3cbec26f104255761aee1b8a232d703585dd276ee1f43c8cd7e92a993eb1" +
+ "5107d02f59ba75f8dd1442ee37786ddb902deb88dd0ebdbf229fb25a9dca" +
+ "86d0ce46a278a45f5517bff2c049cc959a227dcdd3aca677e96ce84390e9" +
+ "b9a28e0988777331847a59f1225b027a66c1421422683dd6081af95e16f2" +
+ "48ab03da494112449ce7bdace6c988292f95699bb5e4d9c8d250aa28a6df" +
+ "44c0c265156deb27e9476a0a4af44f34bdf631b4af1146afe34ea988fc95" +
+ "3e71fc21ce60b3962313000fe46d757109281f6e55bc950200d0834ceb5c" +
+ "41553afd12576f3fbb9a8e05883ccc51c9a1269b6d8e9d27123dce5d0bd6" +
+ "db649c6fea06b4e4e9dea8d2d17709dc50ae8aa38231fd409e9580e255fe" +
+ "2bf59e6e1b6e310610ea4881206262be76120d6c97db969e003947f08bad" +
+ "8fa731f149397c47d2c964e84f090e77e19046277e18cd8917c48a776c9d" +
+ "e627b6656203b522c60e97cc61914621c564243913ae643f1c9c9e0ad00a" +
+ "14f66eaa45844229ecc35abb2637317ae5d5e338c68691bea8fa1fd469b7" +
+ "b54d0fccd730c1284ec7e6fccdec800b8fa67e6e55ac574f1e53a65ab976" +
+ "4c218a404184793cc9892308e296b334c85f7097edc16927c2451c4cd7e5" +
+ "3f239aa4f4c83241bde178f692898b1ece2dbcb19a97e64c4710326528f2" +
+ "4b099d0b674bd614fad307d9b9440adab32117f0f15b1450277b00eb366e" +
+ "0260fca84c1d27e50a1116d2ce16c8f5eb212c77c1a84425744ea3195edb" +
+ "b54c970b77e090b644942d43fe8c4546a158bad7620217a40e34b9bb84d1" +
+ "89eff32b20ef3f015714dbb1f150015d6eeb84cbccbd3fffa63bde89",
+ },
+ {
+ key: "f33691f5db2dea41e1e608af3ff39f3a6988dba204ce1b09214475ae0ea864b8",
+ tag: "6e50e70411201378c8d67857d7b631d2",
+ in: "439bc9ea10db4d2b08c7fcf2e8bd89fa9844f8061d462e28f174489e7514" +
+ "0f84e842040141cc59ce38f9551850cfbdfac2d75337d155090d70d0d930" +
+ "04340bdfe60062f17c53f3c9005b9995a0feb49f6bef8eaff80f4feb7ef3" +
+ "f2181733a4b43b6ac43a5130a73a9b3c2cbc93bd296cd5f48c9df022b6c8" +
+ "2bb752bc21e3d8379be31328aa32edc11efc8a4b4b3f370ee8c870cd281d" +
+ "614e6bc2c0a5ca303bc48696a3bd574ee34738de4c4c29910f8feb7557bf" +
+ "ffcfe7428b4703144bd6d7fe5b3f5de748918553df5453b3c6001696f3de" +
+ "0137e454aadf30cedfb6be36b0b908a38409f1a2dc202fc285610765e4c8" +
+ "6414692bf4bde20ed899e97727b7ea1d95d7c621717c560f1d260ab3624e" +
+ "d6168d77c483dd5ce0d234049017795f2e5a7569d7ad323c50a5b1170337" +
+ "4174a9977026c20cd52c10b72f14e0569a684a3dcf2ccbc148fd3db506e2" +
+ "8d24f6c55544cb3980a36e86747adc89ebad78d1630618d113fa445f8625" +
+ "b583cd7be33913c30c419d047cf3baf40fd05219a1fcec717b87a65fa022" +
+ "1a3aa8143062d77588168019454240ae3d37640996f2967810459bc658df" +
+ "e556de4d07263dc3d9158ec242008226d1c6aea7f0846e12ce2d316e80da" +
+ "522343264ec9451ec23aaaa367d640faad4af3d44d6d86544ade34c93518" +
+ "2843f6b4d1c934996778affa9ee962e7dfef5e70d933d4309f0f343e9606" +
+ "1b91b11ac380a9675e17a96099fe411bedc28a298cd78d5496e28fbbd4f5" +
+ "b0a27735d1144348e22be5b75724d8f125e99c4cb4e9c3a1f0b4e9da5146" +
+ "e6afaa33d02fda74bf58a8badee2b634b989c01755afa6ab20ee494c6ae4" +
+ "c2c6f17af6b53b61d2947d83a18eb3b8a1612aad5d3ea7e8e35f325c9168" +
+ "ac490f22cb713ddb61fbd96011c5849ac8e2fcd42db820349bdf9157dcc0" +
+ "0d9f9ed9c099b10c7194d48b623b0df43759734b2a2e5f8a35e7192bf9a0" +
+ "03dcb9d16a54bd84d922f85b6021b28aacc5264fe9e83deb48f18f864cbd" +
+ "367eb163d39c45b0eb907311a2a4b09fb26109088df782ce031b02f3caff" +
+ "d2dbe25b1cbde9f35ba7c47292a4fd49e7def7a28824f3dfda259a86c3de" +
+ "59257c255c712686ee47d128a55c7b9e8c546035eab7e2da420f32ed5c94" +
+ "bc12a34dc68eb99257a7ea03b69d6c760b0681fa24e4ca97b7c377182ab5" +
+ "fee30a278b08c44c988a8f925af2997883111c750d176b432735868208f4" +
+ "0de7137331b544f2d28040a3581d195e82811c945c3f9fde68fc21b36a44" +
+ "e1cfa2d8eb625f3102461539b3f13c660936a5ddb29a0ae791fbf52c2f69" +
+ "7bd334653f3605b362d91cd78569b41dbd09b2a5892440b5097fa08d0b4b" +
+ "291fc5b934585dd8d5adc80d573fdd194b2eae26dfc49f5e51c1f1607d7e" +
+ "87740702f244bf39ca1d52423e0ae84891dfdf4f43ef984c7a5f293a2007" +
+ "a1e00e39c757f064518953f55621f955986f63",
+ },
+ {
+ key: "d115b6ac998a65b48b3dae5977abaf985258d3d1cfe1616cec3d6a77f7a75785",
+ tag: "b431c9318ec2769fc8ee8f5fc3c079c3",
+ in: "7e7eb43839a6d7616b8a7b1fb7144817904342a9bd34167051162941a6b1" +
+ "b85db5e587f76e4a53211755d5ab29c11822d7711a97b3f1ff5b21f2485d" +
+ "9c86241fb56cdd6796245d3112df11ad9a7344db44d09934c4efb280ed65" +
+ "80cfcafb5c97a32993cbbf4917183e0b7bb38f2ce2479c28e1d39f673962" +
+ "17a7010448dfd39a4e7f406c8bd2d804f993bb410fffa4eb57518a531ecf" +
+ "259a8af068230acb826d9ffc20ee0fc43885221a321e3928971bb28615f0" +
+ "d9f099f5b68a80503a910fdba0bc643c60b64837900be38770b6b30c362c" +
+ "4580722b5dbb1b9c8cd02a18fd7b5661d2c4d28aa941c50af6655c826690" +
+ "37312fbf9f1cf4adb0b9400532755011b40e8252bd0e3c7a22efb0ef9122" +
+ "1e04b4aa8316d4a4ffeaa11909d38cc264650e7ca416835ded0953f39e29" +
+ "b01d3a33bba454760fb0a96d9fe50b3e42c95271e57840380d1fd39a375b" +
+ "3e5513a31a4b80a2dad8731d4fd1ced5ff61e1fbe8ff3ff90a277e6b5631" +
+ "f99f046c4c3c66158554f61af2ede73aede97e94b1d1f129aaadf9b53548" +
+ "553cc2304103e245b77701f134d94d2a3658f2b41108c5a519c2c8f450db" +
+ "027824f1c0ab94010589a4139ff521938b4f0c7bf0986585f535b6e292e5" +
+ "b3ded23bf81cec17c8420fe67a449e508864e4cbb7eaf335975668f013e9" +
+ "da70b33bd52a72094a8f03762ea7440ce9fcd10e251837cfc9ccc1a8cc47" +
+ "0c67379f6a32f16cf70ea8c19d1a67779a9b2d2b379665e0e908a88b26e7" +
+ "8c9f94f17acefa6d5feb70a7095e0297c53e091cf98df132a23a5ce5aa72" +
+ "59f1154b92e079f0b6f95d2a38aa5d62a2fd97c12ee7b085e57cc4652863" +
+ "8defacc1e70c3aceab82a9fa04e6aa70f5fbfd19de075bee4e3aac4a87d0" +
+ "ad0226a463a554816f1ebac08f30f4c3a93fa85d79b92f0da06348b4f008" +
+ "880fac2df0f768d8f9d082f5a747afb0f62eb29c89d926de9fc491921474" +
+ "1d8647c67d57ac55f94751389ee466bbd44dbe186f2f38abbc61a0425613" +
+ "e9b6a64e6bcb45a2e2bb783b9103483643d5610a7e2dcdb10b5d78423285" +
+ "506b42a99b00a4fb7b619b4526bb4ec78299dd01ad894fde2f053e18c55b" +
+ "6047f86333f2690c2cb8e87d9834ab8a5e339aa346e4d9952ed62dc083e3" +
+ "b11a823a67f23fec099a033f127ebe8626a89fa1a5a6b3520aa0d215a8e7" +
+ "dea3af37907686c16521739a95d6c532cc259c497bf397fceaea49cd46b9" +
+ "ad5c1b39a36fdd2f0d2225fef1b6ca2bb73fe604646c10ba4c572ab13a26" +
+ "559ededc98f5a34c874cc25621e65ba4852529b5a4e9c1b2bf8e1a8f8ff0" +
+ "5a31095b84696c6381eb9ad37ac0db184fe5fccf3554e514946a33cabe6f" +
+ "4d617b549d28ad1cc4642dac96e0215ee1596481600d3619e8f45e2c9ae1" +
+ "da834d44aca216bba0efef6254503ca90339f2d7ca508b2722d50c08def8" +
+ "a736590fa44855cd9eb9979c743783aa26e633696739f2ae25ff7b72ceb2" +
+ "4dff4455b85bbd675c8cb71ad18386dc58c371bdf37b4b3875b98a9423ff" +
+ "3becfc0d0ba2aacab3ee7683cb3b345095fefcaca5751ca793da63c89428",
+ },
+ {
+ key: "f3717306b9729be998cdb2c9d856306c5ae3d89da2cdcef12f86f6110c98d873",
+ tag: "907dba0f4849c7cf4570b5128b5f31d5",
+ in: "079572187d4559f24d8e48dc366441acf226a4db79e214ec3ee288acc349" +
+ "887e2e377419bcafa377d0151497b52e4d9cf2a02b0fc91ad9516482bdf6" +
+ "eccd1497954b53241bfb0bc5c04cc45045c6251f23a510060fee32721872" +
+ "bbc95cd8d400dff00bcac2ecce6229c7d73d8f85ed5a87afdccf6dedd299" +
+ "2d5c7b5b8090c47c737ded036ff0e9aedf02a2242fd9820be618b9601e73" +
+ "d3ba5d8f1ae9805cfd2306251704bc74e3546997f109f1dfae20c03ff31f" +
+ "17564769aa49f01233c9c4b79f90fa3d1433d18cdc497914046ad77d2792" +
+ "2588a7d0e61d4258d7d80cdab8503e3111ddca22cf7f39c1f80f1e16a68d" +
+ "9e21db8b53dd316dfa4233cb453a39a90101c60efc08514a3057db007e96" +
+ "507745bd4a0764ed8717a250bffb5fd1ea58474bdfb5b869681939693926" +
+ "40d832a3387ed4ac9cdab0d2af8fcb51b86e4d927097f1e79b5af96574ec" +
+ "d59d0dd150a0208978c41de28ad6cadf72a49279cffd6dc281c640f2e294" +
+ "4cde49a13ed390da1dd92e3011ce0f4a0863375a9db3f67fca1e3b8288a0" +
+ "78611161d7cb668ecdb932e1ff3733982c8c460eeeff2bca46c96e8a02cf" +
+ "b55d770940de556373a4dd676e3a0dd66f1280c8cb77a85136b3f003fab4" +
+ "887dad548de7bfe6488ae55e7a71da4097db03900d4b94e776a939530328" +
+ "83492da900b2a6c3e73d7a6f12ee30c9dd06cc34e5a3893976eb1de5864d" +
+ "32e792ac02e68d052d9d0cfc7cfb40b77728422f6c26cf68987c6b40fcfe" +
+ "9d660abc657360eb129de11bd70af5eb8fe350af2c27a6ece2cdf81b94c8" +
+ "0e68e8c51106497cfa5171236efe2d71d76b5dff3352af9b407dc5aab60f" +
+ "46b5683646f5b28732b7c750d351a08a507243d8e437cc4bef13a3edaa20" +
+ "5fc4e9968b4e563fa0dc965ba20b8e48bc188a321b16d3213bed69647512" +
+ "7a20afc1a3680ef261df6d37b017dee05cfc3a42e4130216e5540cf715c4" +
+ "e638d7d615c50bef576eeb19b3b15b2c2b454dfcef2b18161a143ddf52fc" +
+ "8e88fa71cbe34c92cd4b5a0adc81e5c33e11d2721bc1b95a9e693ac3cabc" +
+ "490889a8a42bf7e22375b679e8598c8faef22a006ed2da8ab1c08aaed2f5" +
+ "6d6f26649036335c0881bfec1e3a5346335c3b3707ee92173f1a7a3305c2" +
+ "933f78e995da8f1df64daf12b81ce23c8813c27fd4551103dc33561c2e80" +
+ "45b6b6770fa03498fd359a104884699d628020173edbcc4398b977e456e4" +
+ "885964840466176a490e7c513ba5d66090277c1ab1632a995a54f555a452" +
+ "1170a000507865b6650730aa6d6050a55959102836fff3d37e4773340e59" +
+ "2e56951ff9652519de4421d9c5b63edbeb30a3852a1ea110a9a29721aee3" +
+ "23d5a306de1624cecc87badc47aa87f489635d2fb60bff62ba67f5257999" +
+ "6af0a1f1a6fbcd8704e119196fcc289a6db6a4170a2cae31a1d30744b702" +
+ "2536d1526d41659c2dcc8b39c26aecfc0f8a707136d81b2827a158fd7386" +
+ "a537514471c213a8c859016748e0264cf3fbde10f40c620840ec4df99432" +
+ "e2b9e1e368e33f126ec40c572e841c2618d49d4eb098b9533b1f4ae00b46" +
+ "8d15de8c8ab6d0b650e599576f2bd90a124c9c6a0f911fd1bd8253bac272" +
+ "942cbdf8864f3747ff7f09d8a5a9d8599be7ee1744e5f1faf3e526cd2a06" +
+ "b157527272af9d38565957c9ce663c295766c0e0e464971c6282b70d4c0c" +
+ "1fb3b69856b34c089ad2b2c745f5a033cee1429c5b855581ee285278893c" +
+ "43a5968d9c28384b7abe8d072ba69089c938685cb1eab461f05314ad6d06" +
+ "eaa58512f8738bde35b7b15ef359dd2e8753cb1ed6",
+ },
+ {
+ key: "9772c1a4b74cbf53586e5df04369b35f1fdca390565872251bc6844bc81bda88",
+ tag: "68eb7fc459ecc3be819485001ab438dc",
+ in: "e115cc2f33e367cb85c01a914b3a512404ad6a98b5b0c3a211d4bffd5802" +
+ "ee43b3fb07451c74524ec8b4eddbb41ca33dd6e49791875d716a44bec97b" +
+ "7c2d4546616939ffa3b1ab9b8ba1d1a637e7c985cc922606caa0453085e3" +
+ "5f2fe0bd2de129d1d1856ade975a3281a62965927d8bb695e54514e69558" +
+ "89361a2a00a1b24e62bda78d0b71a0d40147016fcdaf1a702331dda8e678" +
+ "d8f476dcc91698da1688c610ec0cb1d9b8fbcd45dfde6d1503ba60a01337" +
+ "ae5b2f5c854a82c3087779babd2e522dd92f4718cd9f8c649ac226745ca2" +
+ "fa1696442764758f67cd926369578ae87612790dc56ed9cda935281a490e" +
+ "5c984950ec7a4e930520d273a69da4ed3a330e532508e26f942961fed0e3" +
+ "efeed52a7b96250d723155aa39a8ae85131c255c32bf406b647de1a37fba" +
+ "dc61e302bb5b70adec4505ee66b3a1d1b7bfe9c58b11e53ad556d56e5807" +
+ "017bb30b71be94e8f86aaf1496e8b8d6db75ec0afbe1cd336c23963c745d" +
+ "7b4ba1787ceb30728f1762b46f6eaad5064c8029d29b86266b87f93142a2" +
+ "74f519f3281d8c1cb43c23eb184ae41f3f625cf624b05a48d73cd7783fdf" +
+ "14954a03ec1a930e9a954424eff030e3f15357de4c19983f484619a0e9e2" +
+ "b67221cf965e9aa8d8926595c793adfe0181050df8b845ce648a66df532f" +
+ "78b10c83ecc86374a4f8abf8edcc303654bafd3dcc7de9c77a0a9d1d98fb" +
+ "121534b47d16f75b55fdc2a5e2e6799f8a2f8000d4292282e56863ae422a" +
+ "5779900ad6881b78946e750d7777f33f2f013a75c19615632c0e40b98338" +
+ "1e9b8d35a26abe30242c45662eebb157e6d7a8a5519de60268ac289b8295" +
+ "5d4feb47b9eef6da65031c6f52c2c4f5baa36fce3618b6a331f1e8bdd621" +
+ "48954fcf0846afeeb0a6cadb495c909a7fe671b021d5b0b4669961052187" +
+ "d01b67d44218471bfb04c1a3d82bf7b776208013fc8adabaefb11719f7a7" +
+ "e6cb0b92d4cc39b403ceb56bd806cbdcc9ee75362ab4aaeb760e170fdc6a" +
+ "23c038d45f465d8ec8519af8b0aad2eb5fae2972c603ed35ff8e46644803" +
+ "fc042ff8044540280766e35d8aaddcaa81e7c0c7eba28674f710492924c6" +
+ "1743da4d241e12b0c519910d4e31de332c2672ea77c9a3d5c60cd78a35d7" +
+ "924fda105b6f0a7cc11523157982418405be0bacf554b6398aeb9a1a3b12" +
+ "fe411c09e9bfb66416a47dd51cbd29abf8fbbd264dd57ba21a388c7e19e8" +
+ "12e66768b2584ad8471bef36245881fc04a22d9900a246668592ca35cfc3" +
+ "a8faf77da494df65f7d5c3daa129b7c98cef57e0826dee394eb927b3d6b3" +
+ "a3c42fa2576dcc6efd1259b6819da9544c82728276b324a36121a519aee5" +
+ "ae850738a44349cdec1220a6a933808aee44ba48ce46ec8fb7d897bd9e6b" +
+ "c4c325a27d1b457eb6be5c1806cd301c5d874d2e863fb0a01cbd3e1f5b0f" +
+ "8e0c771fca0c0b14042a7b0f3ae6264294a82212119b73821dcfbbfd85bb" +
+ "625b6f75e4dc0ee0292ab4f17daf1d507e6c97364260480d406bd43b7d8e" +
+ "8c2f26672a916321b482d5fa7166e282bfeed9b3598c8f8c19d2f8c8b98d" +
+ "f24c2500c8ad41cd6ed3f2835737916d846f1a6406cda1125ed7740fe301" +
+ "d1144559b7c95fa407599ae40a795226513153f86c9b8abe7d8aa6963c99" +
+ "5646ec586cbf20a03a698cc0681b7bd333402d00fa8e15cb32300b5a24ea" +
+ "316c5e1df67de78891846cb9183a4b112c3bcc17bcaa5fecd6c1dbbf6ef8" +
+ "272d9269e7f0ba9f17050a6aa5f11cb28874360396ab647941f2c9a85cb0" +
+ "6a969919b16997b0827af8f909c614545f1ad638ebb23109f6bab6b49b22" +
+ "b2285cabbb998b3e1bf42771b4d4e52330b224e5a1d63169ec85fe1c7dd2" +
+ "46dbafa6138448420f463d547a41c2b26026d4621b854bc7786ab3a0a93a" +
+ "e5390dd840f2454028b7c3bb87680f04f084089bbc8786ee42cf06904d01" +
+ "7e405144d2fae141599e2babe71abfbe7644fb25ec8a8a44a8928ff77a59" +
+ "a3e235de6bd7c7b803cf3cf60435e473e3315f02d7292b1c3f5a19c93646" +
+ "3cc4ccd6b24961083756f86ffa107322c5c7dd8d2e4ca0466f6725e8a35b" +
+ "574f0439f34ca52a393b2f017d2503ba2018fb4a0991fddc1949832d370a" +
+ "27c42e",
+ },
+ {
+ key: "d18a328b63a1d0f34e987682fe6ca3d48b4834b4312a17e99b3d88827b8d2238",
+ tag: "938b43b80cb3935e39b21dd8ba133cf8",
+ in: "bc2b0baf92580ee6c5efe640f2a029a791a3c77bec459be74cbc30931508" +
+ "d9f312c3a0944212831cbe4fc92e8f107f2f750c91bcc09f7624fa9a09b4" +
+ "9b7712cf5d619ea9da100fc23068ae2f4e353047e3956b215884bdb12235" +
+ "3f06b8ee98f36c3212493d61ae9ce151cd0453f3075b18a12d7d73da3de7" +
+ "dc2d98376cfb420069ca8148c511ca6bbae57572394a3c615a6fefb30c5f" +
+ "d727f964b4065ac9ee252bdd2bcae3e70162fe0e8069974e073f0a093d45" +
+ "be52d7de16a8f5f65c548aa6525822ffb00dc642530fedf355f7188ef017" +
+ "56384760c80afb61ad903d10119a7d615ec4fbdc79c490160bdeaf200915" +
+ "e405f2a921a2380c0ab9d2ac1e4fdc8ec4b907368c004458598efac13dc7" +
+ "2751e7faded538e3dc8b16590cac9b7ec294da0ad53e22cb9c05d8ef494f" +
+ "a04f6ab7c843c867fbe3cf1b4eb146d65339b0b03392259f12627a8e98e8" +
+ "0f4896c30b8ecd210acb2365539a872541921dcd8e1e54caf4936dfc7e1f" +
+ "68f3bbce61d325b447a8cce7f0fcad28494f2e47dae46b136594b5dfca7a" +
+ "bdafd6856f91496c05b21079aa55aa8c41628220a2cf0cdd755893375b7b" +
+ "b13d914c9a1d1db4a18f8fa36c55e52d0342352052032fb62d32fcd51cb1" +
+ "ac46f44b06e682db5d96d583cda03b966c650c03ae53542e8da1066b6884" +
+ "4a7e2280c664415e413f270b1fdcfbb40b9daa6131d071ee7eb1553dc5b1" +
+ "a50677971223dc316d2d326d57cbd529c88698facdca425e2d5c6b10d7ae" +
+ "cae28b8890aa44ede9b9193dbe8d1d8aa1fa580ca384b57eadcbefc96dd8" +
+ "bfccbe3b855a96f1fd4913035f817b75954ef1827c7718aab24d353e41cb" +
+ "a73748e14e0c2750d5b6a9752125708cc7ee7a498c7fbadf4186e7f8fa93" +
+ "bfdf281a49400f877621651b8ba87edda5231e80b758564e75139b61b1a9" +
+ "9fb9ec694f928ab1f47c6c4287bd4182d1b2be053380616e98da06f3ef57" +
+ "b570ade17c51da1d602b6ebc5a638ebde30d99bf4f91d0e01557c7dcd8f7" +
+ "9e5120143c935fc699eb5616ccd3cac56b5f8a53ed9e6c47ba896bfefe71" +
+ "2004ad908c12cf6d954b83bec8fb0e641cc261ff8f542b86e62d90e227f2" +
+ "a5bd59c9d390c0dd857f6da2b7624787a0bb31908bae84896890b283da61" +
+ "d8ec4f56eea38b22b438d6374b42243f9c1d94288874e53ab90c554cc1f1" +
+ "d736acde67aff55007fd4b3becc4d0f3ddd96f10dc75255cb0327aa47076" +
+ "2b3a3a656e33c87b02a682658b6cd2a75d9c0462803c9bbffa51441501a0" +
+ "3a2fbb2344aa13d27ffb9e98704ea6720b6a9992e53449688cd74d0648fa" +
+ "e8e776b0ea6bf048b2ec05341e5948cab0af015328b284ae7bd89a5f763c" +
+ "eaf5ca3e647a9f5bff7197e4d357e4359fa5fe30709545453149be510e3b" +
+ "ff86beeba5110c79c0215fbe9ac9339a8ac7d41f7488588ab14ac657aaf7" +
+ "d5c03a353932bbb2b261f0e83f3526c5e8e0c2348a10ab4eed6ecdcf9014" +
+ "7550abcb0a722f257e01d38bad47cdd5a64eef43ef4e741bf50da275720a" +
+ "0aee47adfc5cd2534b911dc269197c3c396820b303f6941e3fd85b5ed21d" +
+ "6d8136745c3eeb9f36b1f226434e334dc94be8a5606079cb7643136aacd2" +
+ "da9c38b2eb7e2b898bd8632003767bf0c87d00a3c2fcee48bbbcdd949af3" +
+ "3455128216709df25879b0ce894ac4f121dfca6b8c7865002b828696641d" +
+ "14ffc59924fbda50866fded0afaea545c8008c564a3a0b023f519a9980ea" +
+ "d541d91d1c07a739fd02286ea5660e473f80494236a68e84ea31aad71348" +
+ "e45055ded69c39941e31d51df257a4d0b0d8f025dbedee093f2b91795bc1" +
+ "533dc472020769a157a187abd6d8d52e1693e2ef56b2212759d0c0120e54" +
+ "c425d0084fdb3925e296dd6cdd8e677043a90674904057d88ebdea5998aa" +
+ "03562a790adecc4399352df43e5179cf8c584d95ef8e4b37295946b1d37f" +
+ "faf4b3b7b98869184e42ea8b304fe1059f180ff83d14a0861ca7c0682c34" +
+ "b48a70df8653bd8d9a26f9489e1271fa44e41b392e648d0e619ecdad2c53" +
+ "952094802eeb70ade4ffe096e3049867de93a824217e31364b18204e9681" +
+ "dd8e84ae2678aad155b238f59dd9bf9ce07e97183a690b2a46a8f3624843" +
+ "5b2f713e7d8dcda4dea1e3c4cf9692dda082322c51f7bb1f63d92aa987ec" +
+ "cf1355a043e21a7b8d60a2b97f18487f6fff4c77df92dbfdc9837540c518" +
+ "9fd9585731bc6e726a34ca21154b0499522c9d1016953dd0fa2eb6a92b6d" +
+ "14d6e3da5c12fabe92bd639e253983fc91041091791643",
+ },
+ {
+ key: "46e8eb27acfdc8f4be622d8741c7bc414464c149e21da97ab4afbf3e07b98b0e",
+ tag: "56b5f49be824c7a19b19faabf0787a87",
+ in: "ced52b76c057872a60107194b432cf04b7be05e65209045d2952ea0284d8" +
+ "3e2ed5a15cfdc58071204573c18ab03765b4d5e63a601419e039c42075b2" +
+ "7ebb2827de9c6233d6632e6d3db9140bdb4a9291d53f33734c2dc8e24df9" +
+ "0764dc10e0d321d20fdf659bfa2a81bc9e04fd0f83448143276647c08bfa" +
+ "dcfe3bc23898eda655c9353693ed7b022f43eefa23c21db7660c5029ca64" +
+ "a6085d93029ea6c43197356f56b7624d4819f5008d053357d981ffbe7f40" +
+ "96d6c55d8417002d36189b04bbb2c637339d90f4910a400833a8d422d88d" +
+ "c816c1636e8d9f7f926c244a28d9e0a956cec11e81d0fd81d4b2b5d4904a" +
+ "d1a5f55b5ec078dcb5c2bc1112bbfd5efc8c2577fe6d9872a985ee129e5b" +
+ "953e9cebf28cf23c6f9c6a5e09cb09ab586c6a50e4389cd3110777591d7f" +
+ "0608a3fd95b99f6ba03984fb0e13c6bbbde3668c59f2f2b69d7caadffa94" +
+ "6f67e725d56280e59e66dca025a18d4616e81abd9801835bd94485bb2025" +
+ "dee81fba440005b181ee81dc1d7796cbec92e4ec1c9016c8e8073cf281ce" +
+ "f749993f09a618a4671d58b476feffa454600f82955c591882715148a826" +
+ "586f68bb50059914dce1c1c85e5e3951647c9964ec9316005209a58baeb5" +
+ "2c6d01e6b4c275c0050a7e2bdc52133e433b050a700b556d4314e5c041d1" +
+ "93ee47f47adc971aed1b63259dd5cd4f95854a71a947eae3d3d12d0d7b52" +
+ "c6cd2fef2d2e892607a9681d73ac3236fad21ee30a4f857010bc95c00d5f" +
+ "6f0c6b3fe50cd6452be6eec4f5f01542dc2cb5e2db1f52224f11348fe2a0" +
+ "5d1e5885f1317f2d06ce2813dc4c723008e836a2ee95d0aac66855fe4c3b" +
+ "1b2e02ba0700be759b1ef1c2a3123ee4ccf9200d8d4de5e0d503f04c2053" +
+ "66393d1e91b648392ca28389d976aa618b4796acbfe8aa356ecdce1f7786" +
+ "bf09af226bb9402317b6fa319bbb9248d8ce00b1f49f066c69d4df93266b" +
+ "938342cd7fd4b07c320c2409ef72d8a57c21d0c6d6d493f7ca94d01b9852" +
+ "e4fca6a9291e9060154bc38af6c86932645f53914709fc90e11db56ec471" +
+ "6d600ee6452041248ea8244f79534f793bfc1f2020855d817cb4ca3c48ea" +
+ "7f6441ce9af9bda61936c226d810086c04a35e8654fdc30d4b35701adccc" +
+ "016d5895b2121ba4066e44d694f6371d97911786edb73dc3020ba186a01f" +
+ "ee3dd6036c0e205a8d05979bad228fd12c0fd2fded6c7f1e4c11354d266e" +
+ "d9c2f706269c43cd90504997d93a17b39b10dab0ff083ab3bd06540ce612" +
+ "d08f46ce75a16ef330525737410a0d98fb3d484968f9c12edcaf50103fdc" +
+ "c14128ea4ad6c30b56247eab28197fe617e5f88afa5cbe003c63d423647a" +
+ "d3042626fafd2084a0582ff1b1efdb5baa162662048019546234e2f6b6a1" +
+ "d8bb971114aae41df7795b4f3598f2af9e8921a9aadc7fab6c780aaa32a3" +
+ "84865a4ccb02351dbc55ec92a3152d1e66ec9d478be5dca17b4a131b4a0d" +
+ "3d4420fc6123fef80fd56ca266407d58a7880d6b7e5ce2b6bdc9a3721071" +
+ "7feec573d83c83a2e3f7d4023f2f68e785cde728fdbf5054060e4c89faa6" +
+ "1c9dd10524a08811d15c627b3b4ada549a3fa1d8dd77c005daaf2addeb10" +
+ "0abf694da8dd692f113965cd6366a5a7b0c17e1f2a320243e2c90b01418e" +
+ "22426d0401a2c8fd02cb3129a14fdfa6cbcaa1f1c2f17706e9ac374a3458" +
+ "777761e986ee4c358d26f8e420d33230d198fd86704e77298dd4c40c5205" +
+ "7566ac0cd92993b21937c3a3b4a8b89110a97cf38c781ad758bdc28f3565" +
+ "60cf3acbedfa8e05b396d226ef619746e8e4fa84c8e00a7f0e6d652808c8" +
+ "9c9b123d9bd802624cfa949eb68af85ca459b9aa85b81dbc0b630856cb9d" +
+ "7e18cdc96b3c069a006dd5b716e218a5ed1f580be3e3ccf0083017607902" +
+ "a7967a02d0a439e7c54b3b7ca4cc9d94a7754efba0bb5e192e8d1a6e7c79" +
+ "4aa59e410869b21009d9443204213f7bceb880ccf1f61edb6a67c395a361" +
+ "ff14144262b4d90c0e715dbefce92339ff704cc4065d56118624a7e429e4" +
+ "cadf0b9d2e7ffc4eb31c6078474a5265beba0774209c79bf81a930b302bd" +
+ "0f142534a6ae402da6d355a010d8c82dc379ea16d49b9d859a7de4db6e62" +
+ "40f6976ae0f47bc583b327df7ec88f5bd68f713b5d53796e72e28c29e843" +
+ "6c64cd411d335623ff4f5d167f3c7b8cba411e82f03714662425c8e1bc1e" +
+ "fbf435d28df541a914a55317de0ded8c744a1c3a6e047590244b207bcdcb" +
+ "f4bd1f9f81210deddd629192c58e6fd73e83812f084ef52f21c67bea98ee" +
+ "17554437d9642e2e",
+ },
+ {
+ key: "b41210e5ef845bd5a8128455c4e67b533e3e2b19dffc1fb754caa528c234d6a0",
+ tag: "72c9534aec8c1d883eef899f04e1c65e",
+ in: "7eeca180bb20d99635e36b9208221b2b8ef073fbf5a57f5190e19cb86c49" +
+ "89b0e8150d22ec3aaf56f6ed9cb6720284d13a4b0a34cd3d7f7fc7089326" +
+ "6d1893fa4185269fb806677ff490aec8f889896fca50d6c80d295875b1d5" +
+ "4a779b6d49305360b31011b48537157d0f323ff4e865d46fba6bd23a06c1" +
+ "46878cf9404360d325432312ff08ce495edca63a3c93c44d79c050e3f1de" +
+ "4b6ca5fedbbd43dbdef9ceb26d440a59c7e0be3a8e461c4f15b6b1e1dc36" +
+ "a71fc723ad593fb903e83d0804ce497fc49bfc6b6a602b9dc6e9891010b1" +
+ "4ca066cb1c68044c1ad837c638076dd3708078509cba49fdc54922cdf5d7" +
+ "715fb43e9b5a5942cb8950eade143577bc9dcedde58d51deddc70075e452" +
+ "bbceab1e95b5d003eb96bea69687faa6d50d9c605769cb4287b5d9924dd6" +
+ "8881c699abaa6f93e41dac7639cdbbbd0259099a3ed096f482a1fa322b15" +
+ "ffc379812c74e09e95f1bd3706347eac421fe56895e738a47fcd3e118773" +
+ "c3a7e7e264cc7ff5a53a80e436df058265dab9756fdf6913786a47e98bbc" +
+ "411052d58ffec9ee948e28cbaadaae471c5d828eaf3b3c87d3bfd495477b" +
+ "403da54f1418a15ace0d4d0df68f6a8f2b0457b127d5eae1f45ae055afa1" +
+ "8f058d5dd7eea559de3ae9378ca53f7d6dc9a9465ea1f945295f16ee0404" +
+ "7fc9dd3deda8ee32631d7af70c20edc1e12c5f8abd2e78f43dbd4cd6407f" +
+ "038efab144a24ea8a090a7ba3e6499345a60106220c2959a388e1a73d070" +
+ "1d854bfaaa86165a5aee934b615ac7f45da7c43a1e8f74613917ed10dcd2" +
+ "27e4b070414412e77851db5bc053e5f502bb4e2b2645bca074c18643e814" +
+ "4caeccb58be49ea9a552913c0616382c899635eea79a166988c206b9aaa0" +
+ "977c7ced89c4c7aaeaa8fb89b38030c44530a97187fda592b088198b63a5" +
+ "2dfad59a0a4c1aadf812bdf1881924e8b51b8fd4dbca8e73b2986b3ab484" +
+ "171e9d0cbb08be40ae60de8818bd7f400191b42c7b3200c27643f06720a7" +
+ "e0a17441f34131629388ac43955b78c31ea6602a70dd665f872e7669e865" +
+ "f6f40e634e8772d747608cd3a570e1726eb1ddca64f08582b022bb026eda" +
+ "6a913dc83f174ce3c18b9fc0503d3ac74e2fe45691d6dfb4af8c86d752a1" +
+ "6d6664fab4de08afe8858392fcc35cb9ea82fc42c42d48c0c0556267ea0d" +
+ "cc19b10f05e0318c4488ffe704b5036908f5cb938eebd3163503acaa874f" +
+ "592d945448fbeb93a877a26a72306a36e181745ba300afdc30cb7986919f" +
+ "3dbdc5c47ef1fa052a9e4aeeda3955f61ce2f30a0593a81dbaffebac5a49" +
+ "e5a8d1308352701d1ca9e620a67a89abdf5f0f8b1a0acfde5819981d4b77" +
+ "58799c0fe41030b86754837712af821c315301aa8dd50d1387b9fb92ee63" +
+ "10777e08229edd54e5e86b086ac281bd321082ef46ce298a6211aaa3aa4f" +
+ "6e55b5a4641220ec94cca73087760da1b1ac3e0da3f438214e691aa184b0" +
+ "535950b715a64d11485940dcaa3f72e0aa521002b1443f5e7880e2a85b83" +
+ "40d32db0fc4c4702e10f0fa24a35da9307850e945f608ad34d6cfdf6f2b9" +
+ "ff4f6b8e9eb5a883546578e2ff3cc5787322e4384640f42dc5bd05f432d9" +
+ "610dcf7c06cdf34762dd2a5e805e24aee8cebb3b4db9e4d1471da995bba9" +
+ "a72cf59ea8a040671b1d8ce24a3dce4fc86d2df85c8ab5e1eb2b0567c186" +
+ "4fb464f48c3ca72c7df2749542ed4d4be51b63769012ce3d06356856b2a4" +
+ "24995a2429a156ad93bc79c705e7b163149ce53a42c34a19680dfe4fd0f7" +
+ "fce38c30dffe9da9bc941d131f435c1398f8284a230e9d6e3992710074c3" +
+ "881d03aa309a9edd0fde7a39c33f6455dfcc5ae3fa20ea0e0d6549a43536" +
+ "b4cd8a2991a135b7d7a4265fb840318813091274414108f13fe191db7774" +
+ "6a5f4270f6d51a29ff523954f84cb76131d4abee79161dcbd97dc1ef24cf" +
+ "db1fade057dddee00a1e0de0db1afaeed1b535f7bb402afa3b297551fd14" +
+ "8c8f3e05f1351d3a8ee2948daaf14e7fc448c4670c906ae076eac5a7c656" +
+ "fd5f9cd937b91e26c9e5adb43c138f8d65e447b0022a524e059f879c6e27" +
+ "4ff7e671f75717233aae70853d5bd7bbb41b43c47bb08d6dc2f54f9ec606" +
+ "9487d1267add72403d01552a3d138abab9ca8a0d2dc32439759aa5695f70" +
+ "1a17d28dfb85850fdb55fddadcdde4d220e4b05821e5736d346e7dc9c945" +
+ "72743366488b1de8975184771361894b6520e3407c5c2e38473430969e35" +
+ "b106024da8618665d58c9d084824a28991a33658d6ec702139e01b65b7d0" +
+ "cc537a644caeee880657803d95f5f67816948d5ab362922f8ffbd531473e" +
+ "b0ff8fde2afc37a4abfa28dbed0be1b3d4ed48a1d02358e8403905d33b12" +
+ "3066e7a9fe2491ee9eb24fc9de7dbd322c8ddbc5ebcd0d92cd102ebac96b" +
+ "90e2fd784fd6d4b699304df23b17d963080a013794322690456be525c071" +
+ "b78fcd2d1148026e44ff14c4d0f942cd44d2b3263f4a93b79ec7a618b4b0" +
+ "d77ae7a1f6e6c7c7e2f498b825bf1954df348bae45ae1d7c87b6787f1212" +
+ "60c9a724429a4a2491ef989f65acfdc72fa717486dcf1984905218e11cc3" +
+ "970a09d71061e6df751f100abfbf",
+ },
+ {
+ key: "d9b0dc303188756312c12d08488c29f43a72e78714560fe476703c1d9d3e20c1",
+ tag: "6b9782f2a09b59653aa448348a49291b",
+ in: "dbde1820035997dc8a8ff3015b4e0674e7ce7bf0c2d994b7977f2d91b49b" +
+ "f200995040daeb1218a0f4307b6b8211913992b070d321bdb947b4ba5017" +
+ "a0885e7e5502710a75cbbcb56d49e1bdc2bc2afa5a0e83851162dec41340" +
+ "bafc41c5e11fcbf4ea2ac45bc57def4742281bbf734777f83c9ae1ea3d5e" +
+ "d42380230570f59c40d5dd9a2d89b75fa3c92664f12a274d965ed8de79a8" +
+ "b37f3763939ad21d1703ad794f617c8b32b20cc4dd7c1b7f969a65e1bafa" +
+ "f6c43f30c9eba256f10201910e2cc31a9b13a46ad29257024ef8f2ee29b2" +
+ "ee63cc5b6230ab9f87cd5cb534f4b0bb08a790466e0d57b849fffa1ed21b" +
+ "fb0b27804e3ff9df7bebf14e100cf91691a493e53870abfad6321f6711c5" +
+ "0fbcf1f0b2c1e5231d6c0a08e710525176355f6f82bedc1f787f0d3cb41f" +
+ "a11e91ebf9f4cbae46035a371232d63ef0d8bda0355af8cd0a2f7d1327d8" +
+ "0ab769ea0f1da0f76ec99cc737b5ce84675fa8a9ac0c98342bb82b5848bf" +
+ "656d35327ea01a1b09d84ab974c307511af68a30cd6978b529a8f58c68a5" +
+ "9d476062ace8897ec0d1a90d5d167e29ebaa6f46d93d697760c8771417ce" +
+ "94c0f3698985a98702833d1b68641b811840ca3d935386dbd4600fbc81c8" +
+ "728c4fd0e4588be739a048f03bd4ac651ceecd7e2fb120fe7190011f957f" +
+ "cbbfdc025f1ca0b356208db8cad87fcd53c5d3a30a7c2a48140ccd4cdb49" +
+ "f3961cef742caedd1e848bf3cacafb0da030416bf3177877aa0bc5f9d1cc" +
+ "41fafcb829d5e3ace9394028683d712552579e024084a6b855830ad9f567" +
+ "ff58f05d3ec263eddd6f56adec378f167e8dabbeaf7d0a9e65c71660314d" +
+ "6c8d54beeca2711113fbc32a2ff8c0daa8373278d10085d2a0660ad53f4e" +
+ "1ade74a483be180180acf9e9ad3ea5bdd9162ccd69599163a451c6837d5e" +
+ "a5e115bd9a560f395128ea002ee739009a44fa46078b18959933fb6e866f" +
+ "eb4612a56ce93b1affcb95fccaa18d71a148582ba1412a5daa07404fcb39" +
+ "c3cb4a2519cc506c1172c6c326016ae2e5410f6a438569f35a50d45cbf3c" +
+ "c46188651aa22c257858f60649cee8c05c75953ce49358dfe5980445fce9" +
+ "614ccd16d333ad236e29d204691ca0bf46f29da954bcaae52e41016556d2" +
+ "f4cae1d37565bcbe84de1b49f344d0200478a38187da29c155cc98184d9d" +
+ "33dca088d70054e0fce321f7a90c48a14963d0ace2b4e7a24b21c14a5e67" +
+ "1994fe1f7d22d1135d4df9268dd18d323fde3603288735626a5449582d35" +
+ "30e2c2225414e05a8c7b987c873a82e272a5d83e59b90f3d7264631d6ad0" +
+ "4a0cf3b5e96596a66ed5bfbc24ab6e4870aeec0acbad2cc5affaee06de32" +
+ "dca06f175bf763cf8e7fdf95941a177e934f0078be7dbaa4c9b6f5c16b4a" +
+ "5607bab5d56144a6ba3c7d9a084b8d1f4b24b6f9754ed207b230d3a2cc26" +
+ "259ccc725e1f8a44c4df8143e13edb5ebf073e2c9d2da5f1562df4feece2" +
+ "f6480987f093f642eb7afa3aa92dce2a8b60bb925cd2d11cf6c2ae7d2153" +
+ "1a9c8f068d71d0e682023932fe64e956a49347aed22b21084c4a84480491" +
+ "244ac6b337b6d12d5551ad5684766c68bacca62bdcafab6603c81bdbd8e6" +
+ "80d9d8b3825eaea4df023142e840f98ee251466a0422d810a54726a9f03a" +
+ "7e0afeb0043e60e2ba4908f951d2e87fcbc372096f2a9f4f2a95ad5faede" +
+ "3796b11ecf4401c3ee3d268bd8c46476c61e0ffc5c43c0f3c58c79e20f75" +
+ "520c102aa3c260972a870fc50f8841fa0553a9e30bf37ad282fb51b34adc" +
+ "7a933ca1691a8a706605ce0b906fdccbe954f8e5f2f63c42599a483c4be7" +
+ "3a041ef90ad930fe60e7e6d44bab29eebde5abb111e433447825c8a46ef7" +
+ "070d1f65862b30418efd93bfea9c2b601a994354a2ff1fc11c383e7bc555" +
+ "9e7546b8bf8d44358b1ce8cb63978dd194260e00a88a8fd17df06373aa80" +
+ "04a89172a6051bd5b8cea41bdaf3f23fc0612197f5573f3f72bce39c9f89" +
+ "faf3fb48d8ca918586d4feaea7e0f2a0d7a6afca096a081af462ea5318cc" +
+ "898a9cc09e8258a837559570cbd5eb901e8c0e04ee88ba31c81a76b000b8" +
+ "0e544feba576b3eb5272b53e46e96a0b35b9c759caadcec61444f8ec47c3" +
+ "45a1d2304e2708eeddfbfa75a98eab3493889047d690e84431d445407fdd" +
+ "99560c0bdd287e0944116f8ac62ab992ed3f1e2b415aea784b03c6904795" +
+ "f4326ff60bc839615f2894570dc9c27cf928ef192047528a1a19ec990978" +
+ "3b0d1a13dd4baf4a19e49bf798975abe2ad167dd574b32b3d0c22aa4d9b5" +
+ "2761e8f56cf2100fe5a39fceae3d865f3724d4f299d07ff899fed6baf7fc" +
+ "eb7189357bf56cf94a6493e61301b43e3ed158cb9c7a0e615fd9888c2db0" +
+ "7f7689762f62ef6b3ad4125e06b07a422f5040c3aa8b8f205d68356c9225" +
+ "56fc4c976165fed9599daeb297498ecf744bf6c7dc5e30604c461ad99402" +
+ "2eea0fb6fe33f82a97b5c272fd24162a94b761ec7e52173e7bb42e88b343" +
+ "64f5fa2c141ed04a86b8d00fd9c25bf77a8dc3e63f5543331405be6bf421" +
+ "6a891089b316aa4f887cb4aff0dfb4e80c2ccd65ddd9daa74b17b4411c0f" +
+ "c849dc748d9b138279dcd9ebfc6e6759a53f5c28a41bb82107d71cc161fa" +
+ "81291a8290",
+ },
+ {
+ key: "fb70ae7ec12264ff9f51124da188e5b11dbf53cae2671363f6054b575b1ddcc1",
+ tag: "d9ab81fab28b3be96fa3331714e78c9a",
+ in: "c62edf20b1d53962b42386eb570b10378f9764421ecbd7c4802853332747" +
+ "19ff4c89c06005050fa9ba6579a844060eb7ece6c43bab520e683e0f36ba" +
+ "49cba259edc6ae35d41e0d7812a7d5edbe4d90cd5e0504d16f4c3f70d01f" +
+ "5a0313de55934b661ce1ec317968c2c4de60f45c66cded8c10565a1ca6d2" +
+ "3a84bf182df2fcb05956ed4d46b49fc0fe3bd23961d9466fde070341ce41" +
+ "bc6e148449360a31634fe10e91082d82def90d9da2c250ea72c58add2058" +
+ "d046b4392b78bc3af5b3936ed568733e8ad5672dabbfa3130a6a535ec73b" +
+ "da8e7223535f49f96cd35d56ed4792c5cb7076720d5461d96a2692b2ada5" +
+ "2be08fb7bad15d15a0108143790024f0f15f5adc275e783aa56b70844061" +
+ "e30952a040e4cb9650f2a010417812790105d8f58bd25d99b0db3cb16229" +
+ "3f6322e86cd5b0bb1505a7b998fb0f81d1e1915faca3c2c8ddea39115507" +
+ "80339430a7955521839deff5b301f3fad54edd5ebd2ac4ec9b1795cb4dc0" +
+ "e2eb62ebca8e886c3f1e507d10a0228c3027b472a7104b815f5ec8dae55e" +
+ "0783ff7ae9a3e6b99e381ad788206b135520cb870ba0cdbe876feea843b8" +
+ "5a82adc95a6d71c555f798da92b82daf0abfcdbc82ec30b1f12d78490b06" +
+ "7315735017a94ac150b44dfaace151896f873923310ffcd41e91bac04de6" +
+ "d70ea71565948c907ab21c4a23703fbbd2a8de6d3095f3d8f901538968e3" +
+ "60e7bfddb9d22036b1c23f4f5f1b2ee22623426a2d5de68c1e1a38e38e08" +
+ "e2b5670aac1edff69e9c73c2ca56cb69c709009ef1d541aff1fdb2b40c92" +
+ "9b87f162f394b76cdbba1f5605993e4dd9c312321d59b0aa5c6e33be1b10" +
+ "bfd00b92d4c02db064d0e4a98f2913c89051b0f0ead163deb5087b6466d9" +
+ "84f57553b0fa53850eaa142e072fd91802eb9f0d2eb7318dd620555e6ce1" +
+ "86706b866d41cf6ba81f100342faa14d801dc6f3d522db38fab17a879fcb" +
+ "b6acfe922163505bd23a6842f6ef6397ae5fb6e6016421998bd43b0142b0" +
+ "3ca3b16d6ccb7a47891c75c687d791a930b26aaa2e3412e7aa16e2cf1501" +
+ "7bf6df6d2e1c289af0d7ce03954a60c1dfcee5e4b3da51eb43ddd14faf59" +
+ "082005d0c8b104561f66c002ff426be60be769282fc5685cfd1968df1941" +
+ "73667e48e9ad681d35757f1199f1d93377bbad093c8cc3efa2bcb6ecb703" +
+ "694422772d15aaa58cab9e9ab277ed510f684114cc4a44ccadb3eb1c9a76" +
+ "d8619a9b7743106df6fb6f927ac49b22ae5bb9a9a4d231e340a2cd0e3282" +
+ "53f6d75df694826f60e4b3e758398793eaf73ef5d4b56cd1471e16400f40" +
+ "4a947e9737f4f874fe09a29ad799f4525156e3abbf0585c3c3c0a3744c86" +
+ "5d56db3d2ecba6bcbb1adcc8bf5f3b2a2d46d3eba18cda55201598a8112f" +
+ "d8f14e205f0e615f081b8ff6c5aa6669da776bfc7c34d5af4d0b26d0d819" +
+ "f6aacc53cf3c6653138b9a962acee9d6ea01d280c35bb1f05d1509238ccf" +
+ "004c5013167f804d1780d9f4ef9d45742fccac346b0472bde24ff5db9ae0" +
+ "16455a3c02256358fcd8e6a9aae94f8a37a1a3da58a889bbe3d295e16544" +
+ "2e580f59bdd31c92ffcab40c49c1cdbb4db1dd4882b66edc10fcb1704203" +
+ "c518c1d8d4c268588ce13fc38e0210aeb47d11d2603d4b3de5c6ff5e969b" +
+ "9d5904abb282b699bd04a6e9f1cb323679e30400d725aab128a032745dc0" +
+ "be05a46b02b34b93bff02523cd8498c021fc35a488f164a70ef1ceb873d9" +
+ "14a681d3a3a34cc76bfd5a547e2630d7741a284511bae5897d9f7a197fc2" +
+ "456af5c6cd7e1a93d3388c7a990b5feacd7749cf39fdecdc20adfdd540c6" +
+ "9d330195db7cc0d4555ea5f5356a3647e2265399f153c34ed1e217c5dafd" +
+ "c2c5dd3d566c332c7ddacb0d76ecd3a0ad505a4165443aa81b0f43cabfb4" +
+ "62942fe74a77c22b8f68a8b1a6d712d1e9b86e6a750005a3796ba1545396" +
+ "13170906d228dabf572ab969c762f8b296054f23d5d4a37bff64bf9cc46f" +
+ "43b491b41101256018376d487fe8097f1653a7a9e99e1ef2492600598fb0" +
+ "bbb7df8270be8b9106126d6f491f8b342a96ab95df6133e883d3db4c6a99" +
+ "402aeb58d371263a32dcf76d33c8904395b9cf0016fdfc15608eb43e20b0" +
+ "99cbe7455f7a76f69bba058ef96f83ae752587485657f89c7f26fde7fbeb" +
+ "a82ede581ee92821dc13b8202930aa58bd4f1c86f68926baca0d06fee642" +
+ "ea8c652d226af91a9638a0244f1a03c7ce56969b87cd5c1f86110d192e0b" +
+ "98dd979d74acca6c1956b1127d9a1f456053d17974081ed8ced0faa4293a" +
+ "319e5b25ba285c1151214f52c283e39c35af51c4572c8e395b7856697bfe" +
+ "dfc4145ab4ed0bdbe43ba509c06a196ae6bf30d7582550cb546c63b51833" +
+ "cb0dfff7196d83f6a1c6d6d712cce2ec1989fd9ff5a0a22ac5022b49d566" +
+ "58f196703e4809e7624fe7cfa6c13b378f5aac7e66e657ed7eaa942d1a00" +
+ "544a947199f24d736b8976ec2cfb563433c49ba131bd08b63636854219d4" +
+ "c45100c98e3092773ef492dd9210bfd8f54cfe2cddafcf5c05468d90e620" +
+ "0c2ef99d17fa6992cc45eff3072b7cfd51cabb07ea3019582c245b3ff758" +
+ "0302e88edc2c13fc43646ba34de37338568baa66ecff3accfebad88d143a" +
+ "fd1c3b09ae39c501e3f116af33b0b720d6c2baf5acd7f31220788b2f9017" +
+ "3ed7a51f400054e174d3b692273fcab263eb87bc38b1f486e707d399fe8d" +
+ "5a3f0a7ed4f5e443d477d1ab30bc0b312b7d85754cb886e9",
+ },
+ {
+ key: "f7e7affceb80a0127d9ce2f27693f447be80efc695d2e3ee9ca37c3f1b4120f4",
+ tag: "41c32ced08a16bb35ac8c23868f58ac9",
+ in: "5a3607fb98eaea52e4d642e98aa35719bfce5b7d7902950995f4a87c3dc6" +
+ "ad6238aadc71b7884318c2b93cd24139eed13d68773f901307a90189e272" +
+ "6471e4bf9e786b2e4cf144764f33c3ac3e66521f845f6f0688f09eaa227f" +
+ "e71033b0f74295f6ddb91fe741323f2b54f420cb9b774d4291b06219f1fb" +
+ "4410b55900425c5e6fcabec76a5c2424d637a1641db6f0f6cad564a36a91" +
+ "0f49894bfd598e91f38ceea65e8253c1284f210cf7b50a96e664e562f3cc" +
+ "01c4fc490fa6d4679fd63fbb3ed8995a8a05166b573e92d22ef4370c6aac" +
+ "74ae94c94177e5f71143c6f340efceefda679ae76f6ed7f26eaa4848a8de" +
+ "8c40894316efbb06400f9695b18ba279e8947c032a84a40ca647d9ace457" +
+ "6dd0082494d6bd7be4e7928e749c78110af8774a5d43e9c9479964e2fddc" +
+ "ee51146460eac734311225d08c60706e40f298a7cb97f369ef599be097ac" +
+ "3bf1c275497bbd68968a235fdf8a61bc7cfeef0fe451bb04e662ca39f34e" +
+ "a8e3acdd0befe9762f9eeb275c0cdd43c80fc91131d1e0e790020975ab65" +
+ "afbea81f303ebd86760821efb4cad7cc01fd6d6fd194ac5ffe7703d890d0" +
+ "169e21b444cdbaf691fc741a5d99bd47357c37785755fa72582ca4754a03" +
+ "b4def86ded39aa6d9eb3f38801077e6d17e3cee3fb57ae83f30c79c3cf29" +
+ "0e2739c6b7323612cec3a561ebeadb4faa642f150323aaa9d270658c907c" +
+ "4c1610a5e1834730c08be3379cf1abc50c30e2bf01ce903927c27d85e135" +
+ "3db9e216dda8860c45925e2bb791abe5c8281ee6d16607bdca87f60662dc" +
+ "bd6e20224e7f009a86db66fadd8e37e0a59559328385090c6953cd20bb61" +
+ "f28a734fb056714f5159977f18e5c5f11de75f7a00ba807e47a29e4da32d" +
+ "5c67ec76ce4d7b669b5e6ee17e1df7c673dd8a7c87fce665cda8adb9547d" +
+ "1dccbdbe7be44846b4b121b0bfa65e4ed530789510d79bc4477e50178060" +
+ "f2668ac8956f39ef422ecb0e4cf90b8ce508552eedeeefa6c7d1bccc077e" +
+ "8088bd7e0e6aaf0bda9f11c412c270ee2ad6912f9808f9344a4bb137bdac" +
+ "b5b9372b00b0de026a8f5d1fb13972e1290b5005689f7636c43aee2fd443" +
+ "93d390371ae573f0e064b2d7df552b9adf04bf173d71c621795b9fb503dc" +
+ "5e918536c6ad25ce4a76f70e6b752b6d44be321187269a19bcf33ec899ca" +
+ "40e88b4eb23217095a85057bf95d8a54812cae4a7d32e0c2966a21376110" +
+ "74c6c8c3dd45a553c43c675d23308709f91be0b235d0222aa5e1e1ce08f9" +
+ "c6b45ceb5b47bcd7d7b2d4380bcdbd6eced452d93e6d8cbe18123277889c" +
+ "7f86b15fb991364a501fbf5d8244f2e3332ea0ab49e833c6f765017a4006" +
+ "cc7cd1a0365945a8d8873cb21832b210c83e451c01ac949de2fb0f7a420e" +
+ "405bf64eb251c6f022181595d68174b91e503187d3b3f49b60c23e44ea40" +
+ "ca20311305b413047bb22e89672758b74d6bd1a06decf09e9556421087a4" +
+ "0c1d2c44c5fb13d4d9625581ac4ccef1a1b5eeb5689aac5c0291aebda276" +
+ "50daf9d4396a64d02c6d58bcbd609d9a0017880ae0cbaf02ad0f1fc8d1b3" +
+ "ec987ffe13102d77352690c9b761bf13ea0b3a8ebad4a0823817fcaab4d0" +
+ "9b0bf03486620761dc77a6ba007ba07153b17425c4026597473e78863cbf" +
+ "430c0e5e9b04a83ad11506b61b8d9be3aeb06b5114e0d53d4724863eba12" +
+ "4f3b974bdb0d02743520409910621cd730c97ca984fe2921c38055f83ee8" +
+ "c4611db92e52d8ea51d89203e89df7586c574df15f3a96ed5a10bf04cb27" +
+ "f9656b5b11cf35fd21360b029ab26e9a741c6b3e6357aa1a41de2cac6e85" +
+ "f9a49e3441e60a60e74f434e1b8cd4454b11962e5507ebf904e9d6c52a7d" +
+ "9722300517c434758fbd6191f4550108b143eb16c0b60094fdc29327492c" +
+ "18a3f36737e506fda2ae48cd48691533f525acfffb619d356bf8347a8bbb" +
+ "4babdc2ac866e497f192e65a694d620687cfb4f631fbd6ae5d20ac2e3a12" +
+ "4d85f9391a240b616d829ac2adceedf8f3451ee77e4835639b13c622ef8c" +
+ "48a181fc7598eacb419fa438d4046aa971942c86b36eb8e16eab67105783" +
+ "d27fc56f5b66f35451b2a407d4648a87ae70807e45bccf14983b3abcb198" +
+ "d661d562dfcb00ffc569ca967171746e4e36f839946bc7d2ea9a0eda85b5" +
+ "a5594f6a9c1b179f7230eaa7797a6aaf8628d67fd538050cf47aa654778c" +
+ "11dbdc149458c1ec2233c7ca5cb172356424eb79479b6a3eed1deb9f3278" +
+ "5282a1034ba165032b0d30733912e7cd775cdb7e0f2616b05d521dc407a2" +
+ "ae7dfcf46fbae30547b56f14dbb0ead11b3666666c45d345cd5dbfa200ae" +
+ "24d5d0b747cdc29dfe7d9029a3e8c94d205c0b78b56d5e18613b3169bd44" +
+ "1b3c31513528fe102f9bac588c400f29c515d59bbcb0725a62c2e5bfb32b" +
+ "5cf291d737e67f923080f52d8a79f2324e45a3bd051bd51bac2816c501af" +
+ "873b27f253ef9b92ba4d7a422e2fb26a35c1e99eca605acc10d2a60369d0" +
+ "1f52bca5850299a522b3aa126f470675fa2ec84793a31e9ac0d11beab08e" +
+ "2c66d989a1e1b89db8d11439ad0d0e79617eafe0160e88384f936c15eb15" +
+ "ece4ff00e1ba80b0f9fb7a7d6138bdf0bf48d5d2ad494deae0ccf448c4bd" +
+ "60f0788d3f2b76de8ad1456f7572bd0ffd27bc2836d704d95e9c0df34571" +
+ "9dab267dd805577fafda03b834dd225ad9714d2bd182b4103faa5975180f" +
+ "90d5d6cac1825a19b9d4c87cc825512ae9dbeb33d2759c990905050f960c" +
+ "db3eb364c15b593524c882902b2a1d7fe40ea3f54fb0202fd8821463c7e3" +
+ "4b02a1209ba0048a9805f0468a13e03d18009318ecd92042959be263a51a" +
+ "407f1e660632c4247419659a4e073a8e9cd4a226763a7daea464d5427270" +
+ "7efd053cb4efc0504602c4f63e7d247b55db2ce1c07138f585d16cec97a3" +
+ "0731d5aec2166cb4de41695feb76280cbae1af8a2e67c2d5a3ac5487ffe8" +
+ "640f308ace6137e83576b79d586b663122221c20aba7a6bf60f73958f436" +
+ "59f087f850ba6e2d7fd862249c5fa6b20e3e43d4f2aa10d4c9cebfcbdf02" +
+ "6b8d103e4f89b93dd8af172f421001c8b162bd6d0b847a58ac108b6d6cc4" +
+ "9c7a9ba069deee",
+ },
+ {
+ key: "e3d21f9674f72ae65661aebe726a8a6496dd3cc4b3319f797e75ccbc98125caa",
+ tag: "3c95668130de728d24f7bca0c91588bc",
+ in: "baaea2b4b4cbe9dbc4fa193c376271f40a9e216836dc35ac8012476e9abd" +
+ "43dac6b9ce67dc6815904e6c84a5730cea0f9b4c6900a04ae2f7344fd846" +
+ "58a99513ffb268c6899dfe98d605c11e7dc77de77b0d30986f3051754503" +
+ "7c26be7b719aa9ca1140cfdf4c586b7fe726a8bc403249396a11cfee0a6a" +
+ "f6c5e72259785cfd13c2897384fe527100170001ea19106aed38f7d5d9a7" +
+ "ad43f0b41451e19989192a46b4f9734a774b6304cb74feb7d83822044a24" +
+ "2e51d55c0b8318e0439493bd1a57cc13f6079166cabc46877d003dcd39b2" +
+ "c0b90f6b32fc77acf04a6c125e11b35d91e2b18401cd53df4aff804e3c67" +
+ "a8bb3894b27c6e9b0070b53a85aafab0c0a253f9cfd4d3cd3be52428385b" +
+ "24a3f9f71660ca2c38474d14a0309e2f400e2c21af6e379099283ff241d7" +
+ "51da5a96a8dcbfdc43b913b29cc8cf8020eebb4a67f5bed31f2e383f8656" +
+ "8c815ff172382b425e95902e80f5fc219eccb51b656d37b56660f749e5b1" +
+ "4976a23648680a472d02ba71476e0afb29a0e084984f4eac3befbf8dd802" +
+ "2b7dca4dadd18bbe58e49c49ce48a06a71557a9a620c51e2623f818e4d62" +
+ "c2564c7ba04595cc109685869b183faeff2ac7a65049fc57cb10fb01951e" +
+ "a525332782d691f9759ec2ecd68bebb9c7aece5d522a08ce7830be520db4" +
+ "c9d60a2e490eaa0c91e37b256a97f84b39fe3c77953748c3b86fd84e9547" +
+ "a298c049cb28b8c85d59548b8dce635d59487c9de615802d16a8adc4c0e7" +
+ "80f35b9f10588a431b39b499dca929ab9d225f26e5721820627fe62427fe" +
+ "06d5773a50878b6effe840dc55bd3ea0c35168f6b6a972d57e8f88c5993d" +
+ "1ae33e0b7e9459c123753b518c184de7aaf429df078c9a18a29af77c727b" +
+ "796f5c1a501fa8105ee873c4e78c907142eb19690638a182fddb413adb06" +
+ "d66db19c7f6f46dac582bd72a6347b4427a576eb769d233febaf7be8f768" +
+ "337273c12253924f15653f9f3602b783703a81454a1dd7a8772a9ab1eeb8" +
+ "51be33e0c6c0708f3cc2012cabe8e2f0c38e35372abe27bc148fc4e1054d" +
+ "9d151f80aec0232a3a92dd77928a3678ebd7d09ba7b4e1d83227257292c0" +
+ "b8bc4a76de36bff6c9deb383029afaf4f37d5b935dc080a18665545e4acc" +
+ "195da0b9545d8902408886204b64f8548b32d012e0cdc520c17d9fb3be97" +
+ "800c2e2b945cb09a75a0a49e5d4d81c4194d91e839333b2b9b9e34d588e4" +
+ "e20cc1e911ca0a1429fa70ff063f0090fd842f89dfc5cc44affcce4e1e1b" +
+ "8b11c612f66b074c03ac2a055fd8f51ac9ed4f2e624589ff5730721d077a" +
+ "fb4c19e43abf8cf3ffa698362be8be51e92c2c91a4a56be64d9ac6d3fbaf" +
+ "5536a24c7fd0adaf74ca84c508e5e8c8bf7d4254e0c44158bd26acdf3f64" +
+ "e78438b3aaff89ac9986cef1e3a88d5bf2016340367a1cacd01ec167ec6d" +
+ "185d93a2a220d718b43ce1d429d2cb598605660b030e51e8d75fdbdd5b8f" +
+ "8677675e196a40a88285b18b24c5d2d594bab3d457e6f9e503e38cd470a6" +
+ "9ff8037c9a0a0f110a434335d954fa856a3721e0edcfb14287c3dd9639ba" +
+ "4db32b7da0670dd0a872e468e3819741d0d4ecf0a4f7a011bbae1493c01e" +
+ "642757491189f8664be3ec6437c4f3c76abfb0276e44a4d28871d3487c2c" +
+ "ce2f230452cb06184bb8620919659a7ba0a3d5c12ec25678b03403715ee4" +
+ "acb6a53d281036d8f3a085143cf5ecc3a0c6c92129caa7ac1f645c7bb95e" +
+ "4f63da38dc319e2ccff4a9006f9b9b1a38c4c39f6dc686bb82d43fb9fce4" +
+ "0c767d3ff22f52c5f9900130c65bb6a9cc7408a777d49b70946665f4a733" +
+ "5099376b276a43dc9a6382bb2d40425f6481b1846148434c672b84dd7a20" +
+ "33deb5140d43ba39e04ffe83659b6deb48629e1abf51e68748deffb756a3" +
+ "ed9e0807506b248a024cd509f539f4161366547c62c72933584e851599b6" +
+ "82ec16f1d79e9c6a01cff6f51ba7f46b67cdca09f3ab8496322b990a6116" +
+ "8d7574854a1cb1cb8f30a303dbd13a095df56dbb940dd16ce79879cd2d73" +
+ "80a419842fa1b34da668286de4c1ff5917b7aaa64713c349dc8f855d04ae" +
+ "de9a3a4d0739dfc36510b1e7bb1695418164285c44631b4b1a7c5798ecb2" +
+ "d976c1a3679a827bf0e8c662567e402bcc1354222036ad5959a6f0b8508c" +
+ "6a8c7d4a63e7dde154d778fc80a011592771d55801c7e1297b00b77f80d6" +
+ "314ebd1f5b3057398d1943599897cfabb65e7568d8fbdfcbecfd4b8a83ca" +
+ "0a7bed08ab9a656424831e0d7718c15727af7c83b2ef5eb5684aa044eca2" +
+ "ba896811246766248b20a325094a4b4159f9cde1ee349be6dc3c9a190453" +
+ "0349212a9537f65ae333c288753cd2bef6c5beb2f4164168d965a2c0fb9c" +
+ "c8c73d9e776e23d53ddcfb83bb7dfe2a1b8c781280f449d6f310faf8b53e" +
+ "89e6a611d6d3f42f2aaed5259730d149b3e7dabdc9f865bc1555374738c8" +
+ "456abe112e9628fb31efc2ecdc972da05987aafce728ccaed246cfcdf518" +
+ "3fe5dae528bbfb99d33194167e0f84d462d3d0da83e92227cf57922c7956" +
+ "4fe44648d87c69ad708e797972c44c4a5183fd5d1150a1182e3d39c3cd16" +
+ "3920f1d7ed83992bc4116d9351ae1c6c4827d1374242e374310409f32d5f" +
+ "0f38c78b6489c568b791c70394d29ea2516dcb10e51bdad862ce3339d5e6" +
+ "14fe14f150961809c36e0a2c8eb872e9f7a1c0956fbc9194cb63ff9993e5" +
+ "d0dcf62c0f49e81dbe99f3656c4dea57b766ae9a11254f9970618f1b33c8" +
+ "f339f440de240170f7a21f03ff2da42102b323ce2b9b7d0de5aae324d1ba" +
+ "c87b1e4c5279a566bf659778f8b03882aded57377a0f1b063af2897060e4" +
+ "23be7cefd4aa9a28479c16773944d254fc21d3e1acdf508b7972372b5991" +
+ "3b8b088e93471a7d54c6ae4c52ba465ef07f19f269677fc2f64d3fb3d7f1" +
+ "9069d6c7001d4b002ed6683c59bd5651a450503b68a4a00820b8c17e3263" +
+ "18f32c21dfbcb2a02a104edaeff67ec09533aaf3d1a7fb41aa5d506ccdbb" +
+ "e6e35fa0a263c0aad3acc91182addf8c5bdfbd0626702694b8d652a63c65" +
+ "8d6b2b7c75d015630de508195e1fca9573b61bc549ca017c4bd888194d44" +
+ "3e031f36170215a301f922736a819f3ffda69117170d1933300366c5f2ae" +
+ "1052446ef7c3b82c5868be158a881597132f51c91c80c24ebf621393dc45" +
+ "05fe057364a76ae67494a8a5f67acb551cfe89f447df272ed9c1509fc330" +
+ "2c3e16541452d4d68438f26858724012ad3b72c094b9f166c6bedb8336a3" +
+ "41e032988f39cf53535789b320b5424d07b6bf5f8792e3aceb0e868765b8" +
+ "611d7905089949e0c273e2410c72a146cd63981f420405bd883e5390e985" +
+ "8214a8db714e8400a21d0636d7e5d9671a3582ab9ff032170b8dd6b9d5a2" +
+ "144d065228fa54aea9a22654df67f3f62c5fc59d68914d8b219829b536cd" +
+ "2ae937ecccdb6031d94cb3",
+ },
+ {
+ key: "84373472e362a356bd5c9b50f55c588d067b939009944f02564f136c62dac36b",
+ tag: "12dd5297cfcec53deae1dd5f9325d894",
+ in: "860d9b2954c3daf18fd67eb8bd9e6e3de2e4988ad9b04b1987219204dee2" +
+ "388db1c59a935de27bce29e7cd3ebdf038785efb35eabd4c3785a62b1d9c" +
+ "3ffa25e2273cfe5eb10b4ec6152cd8f21dea415421b452efc7cc4ea6bf1a" +
+ "b85fa6614e7f6d650125424865386ff8ab53247a63ff023b2d0753a9e5bd" +
+ "458d6ab0156fd3cf2d5002f902f927a847e8c4a8426b0a5191f5e237d590" +
+ "2659ce9be9024750d1d618a6b8dd57efb6c2bbac2930858f1132639391aa" +
+ "9e8a620a2a7d64bb7e943c77753401b5b619d95ef857df25a52b4eb97372" +
+ "a05416706b2644e2687bf1d42c0cf06e5eef8a1fc7e178440bfebb85c44a" +
+ "4837f69e43a1789728a999c5e04291576e757510f22bca11583a4e93688b" +
+ "442f2b2dab8d5ea9441ff09b8287862ca538ad979297cc75510a3d9ef36a" +
+ "662b4b7c373f184202befa5bf3f315642e6210763d033b7e2c59731cb356" +
+ "045e9470bf2f83cd62f11b3e904b0c0b1be99bcb805150ba7ef12b8df3ca" +
+ "bfc5055640687d710ab88e0fa8034b26112ebfd044a4b290b1c6f6d18c31" +
+ "ba9880b1cf2d81b5d02f00d6d351da5dbf47b6a5cb7b53eaf6de52c8a68d" +
+ "053602ccffa37ccb44a7683ab4f8a58c4bbc9e140e4e6f3cc10a5c07ebd6" +
+ "070818db983f9f415168606011efab6b8d7b4e61e8eadd8bfd8d028b89bf" +
+ "b0a16996252d7b4ee4f9ab50fc9d6e482ecf99beeabc38d70efbb9a0d4b7" +
+ "9a1c5d2835adf8e25111352eabd24d562644efc97637f695e4792f2049c6" +
+ "00f4d889ceb951cfe289adf159865d013046985d7fe2598014bf2dbbc528" +
+ "b4166fc2180e724ded8e7ea1c8d66338ec50d955d5594a0a7b4655338b70" +
+ "e8978485a722df814fdc6fd2436dbc060121fcb575672b2a5e454c1209bc" +
+ "2bb21a99d39dcb3c697306dbc2104d60fd8051c43ea2fce268987d0ec249" +
+ "a5c02f91d3b0dfee181b3cf8ef1ba9665daf7ea1f1d3b216e378943b78b6" +
+ "bb41e5dba095748bc776f8df6383033a1f5504955da3f42153b1c7ea83e2" +
+ "f90b990ea0c5bd3906b5c4060b19f447ec7762916b8766e5a23bc4d39cdf" +
+ "8e27752df8129b60ccee1731e47383b589d4fcad865eed4041a186df206e" +
+ "9fb69ab6ea092e36f186a6fea8d77bd7f3ab0fa0e29404d617317c75c832" +
+ "854427848237cfc18486c95f7213b9d53f324da036e8d298133b5003984a" +
+ "b9d71836f9f1b059db90005a9067c261bd85aaeed4d623df2220eb52b73d" +
+ "d683abcdee5cebd411996f853752f638bd28df6d78bec2ed3e00d7beea06" +
+ "2b81c19682ffb2f6abe3a3623a2e0570650c1384f1818d76fbefe3a7ef3f" +
+ "46138160ef897f9934e00e066e215230e719c23905dc60d7fa4d666fa52f" +
+ "e7737db15126d3262c3a4c385cdb23ff3b56c131e43b241f4a6062a1a248" +
+ "de9f13eb82c11f7b6a22c28904a1eb6513cdb11179067b13c7b5f83a58c1" +
+ "4f2753f19fdb356f124f52923249d6e4a2c8dadc8bb0fc91e360155a14c5" +
+ "c194334b9f0a566d51fad98592b59c1cc4b40eeddb34e64f337f83874884" +
+ "0583f853398c343dabc29b9444be1e316309fb8d81304d654b3d4bc4cff3" +
+ "55fc31278fe22e649324ef10acd247c0b72397edf96a1c16bbbef0640296" +
+ "4d219575fd23c36efc1fb8f8a34b510ba9bdfb3b478e236777ef7c6c47f5" +
+ "5a2bd0383d8eed3759456ffcffb15e61985b08c022658a5ffc875821bdf8" +
+ "83f69f096dcc72a96888c3af76db57a54be701759670bf05cc9015f5bf1a" +
+ "745cf755a25b1403a870875701427f820c4b29eccc260f30113629ba03e2" +
+ "785014bdcbf34d0c67aa6aca20d2dece811788686d5a45820d2980bf7d69" +
+ "d5c820a09bad7bd95166f63dcfbe8652565c285e60e2704955d69b3037d8" +
+ "7f5e6567d95b8891276d5cf7c59047d10a02ae4a28794405e2524ec2d595" +
+ "1b36ad1b9d5265fa098a033b88aa66cd9eaf01eea49c7dc4cc51c486f624" +
+ "507a2be23f152f43709b2cfecee44945ca506950e90e70164b77e12e1c13" +
+ "0b4d1021c2afa20038f190096276cd22e89b6e7dd10fd58fa033c9d42536" +
+ "98de3f4908203be8dbf259112f840c76726d982b4a837cae7139e27182b6" +
+ "1b4dfbcc50e42d5ab8532edfbd30f668879824e9ebc34b63ff1526cda81a" +
+ "e38352a774d79f73219500e57f0159a32326195d8895d965071834876a45" +
+ "c1a3c0bc4b1638535f7d40011cd5b23343fc27fa318c1aa3f9d8c43351c6" +
+ "6148dc2175e0e620813266da3000954dfa22048f305244629d512e852376" +
+ "6248a897a3ec3e2983aaa8a0f025f18feea57a5153a59b02604ebfcc7a9f" +
+ "b03e62443df88ead9dee955e23bcf6528c278a353f254c9484a67a7b263d" +
+ "a301923a4efb6866aeaaafd428e6da48781365bc49e90cd16b2388220d08" +
+ "bb9f79d14012b5a8299a651917b6a829488753b6ca449a14e8dd8c5fd5ef" +
+ "657d627b8e7773475b802655dc033694f24376e3b01e519d1aa8365d0e55" +
+ "92d0a4adbf555639b6d75d7ee59a7d12c6c11317b7927f11bbe75ed90508" +
+ "b0698420e231206704d22dd1f1740edbdcaf19a47d66ace4eecbcefb77b0" +
+ "85cfcfaced4d2d6048ce76434eb79990f0898adb4af2c377b581ebab3f3a" +
+ "150f40dcae002d4caa60050591c0de4ba83bfd59a08670beaa4641aa9829" +
+ "bdbb720d6eb8b2f3e864a98676a67271a82cffdca2b3590a0b5f97efa5d4" +
+ "ba062b4798707159782bedc75e5363d5f5d55ec2bef70db22955adf401fa" +
+ "c3b7af937816eb25d54d9f2a92e5a2a04bd8b8d7568204fd289f5ed2e033" +
+ "a76209d288e11e8a4dbb06b9029e90cb186446746853f02d738e06bba538" +
+ "894e03e2658ab3d7f9ac861d2cffdf12396004d1cd15f18812d3803ab9e0" +
+ "6f41c9b374d6a0678bb82ce06d9e3b9dbc8d2e90b8f64d0d040f3fa8a3fa" +
+ "8be71d2b3183cceae1bcbfa2353689d842f7d7052e5699dcc70ab2b58761" +
+ "7041e5aa1e2f41911d525505f061d3ca45152f5a7a1fab50c674e4597a52" +
+ "b46aafb4ba57413879cad1308321843abb7c39696fc2f2e225878bb1191e" +
+ "e151cc76f1a1b8d491c1672fecbf710db82dcd32554361967fc839c8e5d4" +
+ "e488856e1b9382eb3fc3bdc3b6886a3cd79761b02bafa080a745ef6afa26" +
+ "822f1d10d5e8eefb842837d82c9986e78fc3390caa142b7643de8f613e5a" +
+ "890a57f5883409549537f8139534f4ca1b60f33e42be25433f1d82add530" +
+ "6a4cfce258c0d4f1f3c9148ffb5c4b626d51f78ac20bff0393b7fdb4b9cd" +
+ "70fee7f69892c8a9ee089c6c5c7bee0a1b825e5b9517f2c82d6c149735fe" +
+ "45a8839812c2deb2a355b6230697053092eca450b7b0d3242b2689efe364" +
+ "09e820d91fa4932034d96495d9dd3baa4b385da815a7cb69438ff648b326" +
+ "e7efe8d688e88570ba59df7c439faf72c95317a10c984c5ec0043407e9fc" +
+ "9b46487810eac19d2bb40e0a654935f76e7d8861480c5f48419eb33084d4" +
+ "0e1070e5ad542c94f58b49e67dd05b6637a2c67d41451b7e00ba30eff221" +
+ "755d6d427ec634a2b95980d274a89579feccf1c7df3787a9435e588f2496" +
+ "06a93b7ac41c8aaa84b91c95cad9463d4881de7353d95b13bbde4c9da90b" +
+ "f1fe96257309a416407c64368b5564f022c4a493f2a39df1696f45801e42" +
+ "a5",
+ },
+ {
+ key: "2d0035a30d19b9cbc7a27561f3ab474c01115c4499b4adec660ea06ebaa1a14c",
+ tag: "a2c77b55cb0c076d8ea83cfe0e64f293",
+ in: "4e667580ba4f38f64e5cb5566bffb486dcae10cd17acb3754251e837767f" +
+ "16429bba2b832f29ba538f97f3556548d163be25e69f88fff0743150623b" +
+ "e0a1d82af9384ca335927a0e9cacc3dadbdf1e24fa5c81f2602d109e1400" +
+ "33929e409b9a0fa4f2653944edcb8b3ef963ba7f8806196c73bff0ded670" +
+ "c6def5d240c5f3daa121f8d5bec9b2a0b0f1d62d54b013dc742d6bd46325" +
+ "460f692b76d4991f0796820ddebf150c7d33829795784dd2759b334d2706" +
+ "70a7264941be5d99d460d078a9eedc3660cb3176ad302f9365f0bd698e46" +
+ "9f3e63511abc81109995dba17be1abe8bcd28407c7fc8d02c14794bb033e" +
+ "178a94f6dc73719d5bc235f980a16eccb4121ca83b13c4e165931ae4f192" +
+ "4292f8cfdf1c3ed40feb71e13d919b48fa296dddb4d23114a3d86ec10f16" +
+ "f314de4cef813ed24b49f4c7bc44cb8424df1f70e8d77366161c7cdd709e" +
+ "97610aca3a24fb2202ffe15eaaa25d711cb5179212a2c6497a13e5d7c365" +
+ "7bc502b3d2ebde2e57b714dd9bc21e73795f3d35d620613918c4c9aa0e89" +
+ "031481c97a5a4c15ec6abe42d40498c33d71c823bf1d5bb5fee457e2fff0" +
+ "bf777c80c6e3336ab3ce793440e74b336a8f7034f6ea2e4ff5ea4ea7c350" +
+ "65cf2ccd2da1d6df29bde10f4cc0202b5e4cf7ed097da49b970a6db41e5e" +
+ "98f3845b42f46663b1d1ff01da71389a8737ba8f51eac1ef357ba5ac9a80" +
+ "dd2c7f9476111dcd651fc33f4c86dc8658656f3f02a8878bc38ff0d0a1af" +
+ "2e31fb92eaef08c50195490818661feaf90e8b6f5daa1ebedb2cdbc8d5dc" +
+ "16db3505f9611ac46bc37931e02c1fd6aad6e4b7e187d5e6f990fddc9563" +
+ "2b33f55bf68b0db3890b11113ecc839a4fa4de25160e574289aabe4d8fb7" +
+ "9cecf9d2fa75ac8d0195beefbdfe0815f8d7d9751c1280a29b547149ec7c" +
+ "2295f5afa53cfb516158086bf203357eec2a5db71143f996c81555a47f92" +
+ "209719a71570a5553f1ff9b4b41827dd74657b463f36623565f0c9f4d2ee" +
+ "8735d6af56ceb3b3d0ec516b22f0ddafbc24647481f61ab169e2616c91c0" +
+ "e1f6a35436598ed801670e1dba76226cbd0544959ebe70f836c8a7df575c" +
+ "b907d780ed5aa0d6e4e8e0d2f457efe89a777374aa49d4961db96dbb787f" +
+ "021d99231001360d532a70ee1fb94bd6f26524dd4b7556c6d40e08723d7f" +
+ "9905aca66c4743f2bf8b34493bdabcfca617809a867bfe0a4f94c756a6a3" +
+ "dcd04ffc0a3ac671a0afefe0d5d447efcec48c6368998760db6a572676d4" +
+ "29b6d3d6e0c815650447748c4b27541c5447acfb8f7261b6378f3fc0fdd7" +
+ "375eb9d458648c7fe9cd96344f11aca912cc5098e9ee39e0b6794cc1dc2d" +
+ "f1b10f927102705efa20e667b63a91f935c17764650b287f5289d5790766" +
+ "555f31985c5aad94c652ba41fa9c0195d15405f1fcce9e23054a42c8a252" +
+ "da83bf6268782ba44edec5d8f94a20b1830cd1c5894cc6b9b52ad0b12a5e" +
+ "cf3195a32a0b02483ae3b954ac6f3af1e0f334221279d03a72138f3a2cb2" +
+ "1e706427c4d604674dab88d429f28a67be7a996126e077a1dcf8989d90d0" +
+ "8b08f4abb9a546b3c64ecaa287bf3468c59add86365b885f52afe13ed8d2" +
+ "69ea61832a7ecbb96ff3336f58a1eeaa6dde3611f3ff7c2cc8c9b745b0e8" +
+ "b5919914245a49ac192cd77d10deb9a249623f696065a532c20eef9e9b0f" +
+ "e706579566a9eeb14d4e8251a7750e29eaa60f034c1a7a1d51aa03a45fff" +
+ "89acf41080deec5506128b06f003fa46bc4021a82fad6a8052a49744ed69" +
+ "45bd9331b5ae80d873cd042bff079b2b9d8af8065a22c449c32a56dbbe7a" +
+ "80d0f3e30b9167532506915883dce0aa9cb749e4368c595c5bd33b57e36d" +
+ "98cc9bf91cbfa47331d69b5cbe9c92bc66c0fc9ca8717bfc108e1f710333" +
+ "14dba02a28b9aa05890cb01ae9175806c3c4215bd446f6cc96ec5d08982b" +
+ "4f83cd1646160e1d306b3cdec02d251f0901b03e8c3c35464eaa5082586b" +
+ "b55482db97599d513ed8d7a82e32fae302684b7ede058474c1fac7893444" +
+ "16fec93fb982accd162dd956ba2f31a894e9366eca00e6e997fbbf9a2980" +
+ "8b83a139f6432147a717381bb8baa2205715f735c1e0db273cdda6897c9f" +
+ "39bf0d7eb7caf93f657ef4d3fecea28baf69cf36d3cf347081df3114455e" +
+ "b4fe3e49ad3c3f14435e0b39b6c0d16db0fbcfd7ba8da8760d5952c03667" +
+ "251e7a4c3008cfb0904225e55c23b884bb09d26631650460c4240bd5a165" +
+ "b531ee76ba5749b3bc60adad35de519321c1672b47bc35fb59f7792a3495" +
+ "11b2bb3504ba4a28717823a27a1f99ce6970290b26efcf1e7a0399b10eb1" +
+ "0c1299c09b80f4520d00e7908d004d5b6a72a411759cfa9523f6b2912234" +
+ "481b1d8fe4c2365961c0528bd593d42bebb398b5836ae6ca013fe440adbb" +
+ "0090e8ea274f4d8bcae483e3663051a328f7c12870b40e4973a9797a2336" +
+ "3d3c53e1b0d1a9159bfb26158f44734b3c34b571be641bba2db937d4ae1e" +
+ "edc807b95b1c2a7d44804885536316ad38aedf0d83b1519661f2bb5283cb" +
+ "9c50dd61c3753433e988189f26962d1f4befd444257d0b6d5b819d5fd572" +
+ "22c9fdff032e07a4d8686d451e71de4748965309c0a2d7c422ab7cf3d96a" +
+ "8c0a1b0afb229debd1c9421cb828b9f2be96bb9d6b5be7ef8134bd9ccf81" +
+ "51620937d720d83dbdddbfaba8ecd2eab6f1974090efde0ca963e9fdd691" +
+ "ed0cc5e074c5780779222552fa46ddcd951763a32aa3a044ff4a73cbab41" +
+ "dabb3c2c03fcda68303477f0dc26f35bdb5c9bde721fba1a2db732a89629" +
+ "a8de3cfebc3918df1a9d5053d09da5b7316e3285bf62156ca28cb64d343e" +
+ "72445fd66757bf4ab374fe7932a65f3d7fb6e42cb12e5b67ddf8530383a4" +
+ "6c1ee7ec8883e454a467df1aa7e468a6e7035515f473901efca5d46ff358" +
+ "70e0cc2575bbd7f8866c8e73cb157903a1694ff3051424f28de826984dcd" +
+ "065dc3658df144ae3a6d37b88c367e3cf7c58169dfdedda4a2821ce22188" +
+ "40472ff72f0dd1a6b0100555ff188b80f835259a634405e3dad61fc299f9" +
+ "307e27503b2cb7714bf3b636cc64b61d2e374119c8ef8adb21f1516c7fe2" +
+ "38c807818065bf312003c12e02525d69d9629a99e4ac66ad2e792f302cd2" +
+ "a6f5f702dd28040738a084a7052f2c3ed0924c33b7a5d357b7c9a29cebd8" +
+ "621a4bfb7bb34676ff210d59f7f9d4eafb7c5c490c9ea48402af5bb072c4" +
+ "731bdebcbed4e8e08a67931b6d7342d4ef7bc4a75ca1dfbd32ed6027d8fc" +
+ "b71e3f55565c02e06daa8c579b69774889181291c470576a99e11f2c5acf" +
+ "77e091ef65ed243d4287176f7f6ac7aba6908c9ff1fa43b894a499b642ad" +
+ "c01b2fa1c4b58801411941bb448f1f7a04794d2cfe5db1be61f7b86d6eca" +
+ "c547ee51d4c9050f9e9f318dae958c150acc21c878f0c7df6065294eb1d9" +
+ "a278c920838a0db752b080a32e67ac312fa76b589a385f31847196076ed8" +
+ "1021fcc375bfcc8e1361878e2693860eb21ff0595e4eaaf7897f2b79367f" +
+ "7c4f711279bf0c93a97dcb1cd8d87e444ad5f4cb5c1de44e37868c6743f1" +
+ "cd72cec376726f26c8bd4836f9a9f9c68042f95ca6f9d7cde493e531c553" +
+ "8bf7ace6dd768db69ac7b41ce93e8ca27ff20a83ff2148ec5b89e05d8b8f" +
+ "5d78d0fe16b96f6eb8d3b20126a186085c6825df81aa16b3dbf57eabc360" +
+ "71299ccdda60e250c652408d9cd1da94d73c728440ae08fddb901aec0fac" +
+ "1050a778b10f94f84883bee158bc53b1c001807c43a3151fbf581b18dda2" +
+ "527430872834e5c380575c54b7aa50f817cf3249fb943d46933cad32092e" +
+ "bfc575bd31cc744b7405580a5f2eabe27a02eec31e0d7306750adbbb9f08" +
+ "c78cb2d4c738b2274c7310cbf8dd0e59138b6a91b8253ae9512fe3d7367e" +
+ "a965ac44d54a7ed664e5e5c3c6c2d942eac388cd32beffb38f",
+ },
+ {
+ key: "2f29d71d73f7af98f96b34e939e1a21e2789ec6271b878bbebd14d7942d30080",
+ tag: "ec02f4953a9a63ab6f2bfc3501e4fab8",
+ in: "0e0950987f3508239063e26a13727fefcdfd2cea6a903615c64bf12d9ed3" +
+ "887f9b2cf7ccaa196ccc7756b09471475b9daefd4261e69abd23b9faf9c5" +
+ "1fd5d5788bb39d3c068fa6807d30f6201d3f6dfd31715d08b1733440cde1" +
+ "049608d23c4e45c5ed61f863350232f85827e7c292dc5f1eced1cbc912e3" +
+ "f5c420bd945911d3881ede5153d3b2cc85371fff98d2caf97cad6ef59001" +
+ "4017f9690cab08989851c2647e77e81401714a93ed9f938b79f8f54e3133" +
+ "fc2cdef259df2ba0d48f37bf9e43792e3a777214cf4aab6dde6deeb543a8" +
+ "813b71b5974136c1220d6218a252881f0f5677ff5b6aba127f19a5f3c5aa" +
+ "c988543d7839a90a3f947c4e4d5c6ae1ab48dbd40456d1aa65339a4c15eb" +
+ "520e8ff9f965ac4c37735937cf09942e7958f8a6cddee41707423f715903" +
+ "ffe0d15af8c3140d3a736d23be7485fceb9f07c6509f2c506eda4ec9d30c" +
+ "cc133708f48d8828e332808c84a745d337296d871b9794de1c5d06534aaf" +
+ "65587526a84e2521f8b332645e0e72564bb308ecf99b7bc69608474389d1" +
+ "686ffab8c49b7f04dadc28d2ecdd0f508dad2135843304e378b3bc7a4f25" +
+ "7fa4316be956e0a021edb8045f39fa9f002087f067199bd6001acaadd261" +
+ "4bf6aefd3f098f92a959685f24bb2206c347359d9c6adc6847117bb434ac" +
+ "6c40ec618f6ae8b75a5e2e4d44c332b7b06c8b4d521493b9b0bde8894209" +
+ "717a24b320214297b62dec741cea018ea681c9b56702068528b3726953e8" +
+ "c5e4ccd5029e4183e772d9834a56a88d45bf87603dfda40e03f7e894766a" +
+ "7623ab4dcc0dfc3086d17566945069173935916f772e2a5f8e1547348f28" +
+ "782400fc069ac0e2b94242e9e0f1ba2d0e76898f9b986540e61ea64d7f69" +
+ "1006b86ce61565da75eb16a8b4c5865ca4eebdde2190e354734bda94fe7e" +
+ "12ff47dcb5d5e6ad93cfadcc491cb350b09ffe391a157e14b65e3a211b5d" +
+ "4e447c3ff95571dbab33a83126d68dfddf9383b4359d4103ca64af1e6963" +
+ "d09e17eb944aa71e76711dca33168586bfc44ebe9fdc55497d83f238c66d" +
+ "bcb16063bc85635f0f1a6280563bca49ef971db96a41b6ac5e0642643262" +
+ "61eb4662f3d6ad4cac826db895de22c9b8aa35e6464a7f44e1ae7238e355" +
+ "068d68754ffcca76c50b7ce7ef9bfebac9eeab32c87d059cc7ef2adb5d57" +
+ "c7419adb394eef48441952253e8391e555730e29789d6293c3696f441449" +
+ "0aebe2bbe541e191a6652ffbec1192f0f9395b7ea370aefc1f1cc8438035" +
+ "d7681f12f1e11d6e334da188b10c302fc0f4bcf1de448090510a8f1d5683" +
+ "0c943a3c388b33a038c26741a4cf3487313f755fe7a28e25e44b5383c5f4" +
+ "cd6ef34d7dd73462226281899dc3f2e69809a0150f694673f31addc89888" +
+ "072a7d4ecd63d6b90540f9522ec05829a7f17d48728345ad808fb0203883" +
+ "3cbd018d612992a88df944b8e34a70920b3f26cda2e8bb16c3aa38b12b33" +
+ "b395c9ba5e809f60ff05f087112151af1b5987403cff8bb2dce79093f431" +
+ "2c744f911a6f3091e4f9ef9375c4dce4c241d2f6024a1797321851ca316c" +
+ "4e460fc060e7839deaff8ab5e8bf682c0f21ab6952eb793cffe690db911f" +
+ "50b11f56ea352942c43bfff51d4360882754faeb7cf28b6b32bf7fc9ca71" +
+ "fbfe1d72be05b8bac9ba513d731e2c9d13d6f2f10eb926edaaf0e3996656" +
+ "da8718a8e103c59326529e91ebac6ed52657c9690ccbf81028cd9fb189ec" +
+ "4de94fc0771e53302c8d9082835a68780cccd772660a110a1b40c57bef3a" +
+ "c1d69428aea549ed17663a96895a66a3bb5ff6ff61dc64908df49b760caf" +
+ "a5aff05e2766a418dbaa1e7d189a9edd55a04fee8c9d6e506d299abc36a9" +
+ "d67be035fea5d220f41d081af67615fe627c4dd04bd8659c7fa4f57f35d0" +
+ "db40d9684aa178d7483ed5d86f04eaea412e0ea05a4698377dbff4fc3a39" +
+ "1f6ce0cb833d3118d6c69319b511cce65fdc74928e270da0c537f8201eff" +
+ "77416155d4a39c7ad38c22cdbf7d2b7ff7d85383c178a835ec604c3f9ee3" +
+ "7399f7dd826e34f1a35ab75da44ba56f86097ddc0f3658ef5bd65a24f4de" +
+ "4255d0b03411a9d7f0ddc29e33cb865da23393471aa94e6c9e72e789206d" +
+ "3ba118aecd39727068f528f01b25fae2280d70033e4ee46b41b864bb922e" +
+ "001d8bf46d6fbaa5a594e926f45eb3a4d2f074506d7834b606f43c89699a" +
+ "6db00b374658d9333700894d440a712a1f25f5538f9e7c8ee57ae7e612df" +
+ "13292c8ba9dbede4fb77cc6c8944aaef59ea6ad3b36db398f4bb0f82d40b" +
+ "44879835f224d6e05992b1b8a68dd58c3dbda2fd73786492ee48c7a25f87" +
+ "264b766930fe9427487504fad17f8d230934f044e49ba219f26ead728856" +
+ "cb30eecc33a3946d3b1b781061f2458c7c46f6d96f3e06f369f97be91835" +
+ "f23b38347d1e381ad5be4419275772c2abd549522a0203c1ee9c96faefe1" +
+ "df413c4b7b2624417890e0716854b7092b3b3b368cb674035d3e6bab2357" +
+ "e7c262b606f7141b6dad2f6145ebc1deb7597814719784f3c17848a90ffb" +
+ "cb0289e2f3cc7da12442b837c4e47f468bca3eb4e944a31c48562c2f144e" +
+ "9e920ab5e4cf90a14ccadbae29af13db38cda911e3c8f6f525e6722809b5" +
+ "31a4de1926ab12f643d25af87eb8610df59eded6ec278242247dc69a4213" +
+ "13f7c2b26ae7a917c1bdaf66c56876e9104d40b59e6ca1431ddb77fc89f3" +
+ "14b46a154cf127688564a4f9e120d7b5816cd24a6e095dc8ab8b43bc3639" +
+ "329719f0e0f723e2f5136d82638e2249e648ebca67cf0306741e9e8d45cb" +
+ "903bca85485c4007397c88a1ce07266f4f611b96b7e0ace3074247a7dfb1" +
+ "cdbbdd66e25e172fd2bda74abde7f3b4cb5cc7ee7859f053b2f04f9de03b" +
+ "a8e96264117f502087c3ddbee8d850bf3618b4de90f7b3e562dfa57e4426" +
+ "5357236e35e71d1669226d63bca50b1b944ac07a1f794e73e80985689b25" +
+ "f18fc709367d63b8639d71865cee667536040be827145c08cf3e57a66678" +
+ "4c81115706a146eccadc7aa1a9f074b47e95bcba7db8108a13279077bef2" +
+ "64699fb87e5abf5b05ff3879d7c7c5169c7cae817c13f0859d4e9c05db0f" +
+ "74c045ecc30a51e515feea627da387ff780719395b5b9ad93179b16fad10" +
+ "5856049169dcebd43a7f39c549762405f807378e854b1654a1179d895ef0" +
+ "85aafc72c7fe1e0e1cd3abf8e20935e331145bbcece4f17ad24ebb6c64ea" +
+ "73bd98a7494c134859206c9422f7c4a057db0ae0770c4bcb08c1a6b9ca4b" +
+ "7dd8c1cdb3e4977c7ce6c1e79b9d6ad98e27d2759b53cee73ec037a8b686" +
+ "f1ff78eb8421f41c74ce9c62a90d38b75159ec925f232e0db71362f31e29" +
+ "4336f5580a34b26c5a01ee3454cba227c7f400f6889a319d7121dcea27b9" +
+ "584f33ac796d48a9a24cc5b6799ee12f10725fbc10d7cf83e4b87d9c444b" +
+ "f43e2f5ee49d8f3b531ebb58fed4234cb8bcab1b8b18bf50956506baae8b" +
+ "c1b7492250f3adf64294310387f1d4bcac12652895d4f2dce26f380733ce" +
+ "0b5820e9fcd8512a1585a49940a32fc8875ac3c9542a4270602e5e97e720" +
+ "90ed71b51badb775340429fdbe45b887fb9ee61cf9e091c06092cf0a2129" +
+ "b26572574c46910cb458bca7c63eddd29d89753d57e568323e380065794d" +
+ "3fa1ffb874543f5b0ddc702b087e91e22604d9600d37fa0dd90d7acb2458" +
+ "4cd408a4e66bb781dde5f39efda6a8fc26be0d08ffdf851e422ab1500c28" +
+ "bf6b4c85bdfa94e8aef5cda22870c39ad49c3c6acdbb3b0d58cd05424c65" +
+ "20740b5c2bce4336545eda12716317df58e6fb764fcb3004f5248c5ccd84" +
+ "f63abdc0dd2a64e447c0de4da4a1082a729d8ebe14810d396933085cde18" +
+ "318278481fdb9a748b637cacb491f5234bfe16b53a35da6677336baeedb7" +
+ "4a28c19a412e7812dace251446d40ec07afd63854c3dffbd5c0f6a9a3cac" +
+ "ee3bab07fba94800fd1fa0fe44f5f2ecb2b4a188cd02b8a2df0728347c50" +
+ "7d0cc58fcd5d54dffdbda11dd1bcc59758396ed8db77498fbe13238d3d8a" +
+ "0040194dfe66811542ddaa658094a9580d4e4b4e29",
+ },
+ {
+ key: "1285f117bd90b70ef078ae62f37d2218419e894b7d334759ddb2d88833b287b5",
+ tag: "429b2b39195a10357043c9601590a277",
+ in: "00ef065a1adb4ce7108b497813ccc748933fa8442689a7cb8dc7c1ffdbf6" +
+ "c09adfe05ca2cc5ec3acb7493f3497ee8f9cd9bb8a4b332c18e33f78114a" +
+ "c8f9a72ddb9f13494e934ad711818909831013ba195b53f5e9e5b4689399" +
+ "6d0b669f3860958a32b85a21009d47fddbc8697b7c9b92dc75d5060eb4fb" +
+ "40aed7a1dbe69dbbeb6296f5467ea2426cd17d323671fa408855bc53e5c2" +
+ "d111203ae38cecac7719c0bd7f21f6bd6a1588187b3b513983627b80ac0b" +
+ "300b7fa038af1cc8512403ac2cea6e406595202ec3e74014d94cf8780ed0" +
+ "33c570e887ca7fb35ee4768202aa52427d02c24e63f7f2cede95ca9909e9" +
+ "dfa86246a27db757750667c198c9aff4ce348f7ac51864b36ef5695df713" +
+ "d17b8f561a972d0136bd9ee9aa16079c2ab5d29ac9ab472255ade05dc49c" +
+ "b966e0c1c04258ef9ec59ded01f402d9fdcd9a2020a2038a8c78892ca218" +
+ "30136069485527069132959dab2b81c73ca590fde2a7ecff761d95a54d63" +
+ "a2664aa5a6deec163e46b5225bc98976a4f363063b0f42e29f792d138af8" +
+ "eae68d3854b5c1985d5cd1c9f49f529b0b4d2c936887b5b92cdebacef992" +
+ "c35e0b7bbd52114aff8c6b261852e28e451b02099814f809b0289cba0586" +
+ "04a363e3f969aad3d982f645ec4c549f943fb360fb8fa0d5a597bf89842f" +
+ "8ced6014a5b2590ef71524a7ad50fe0ef0e2f81b6e26b99f9ebbc8036549" +
+ "f7eacbf6ab884710c6406ff59788e03ede35c30d4781ad5af171e0623e8f" +
+ "cf5344d71165f0475e256e9159040f702b359a2963116ed135dd6c1d111d" +
+ "2a1e33e15c178ca4f02c5fb15593c50cf9a8a492f01e04778dbb81d26c99" +
+ "0c58cf50a9bcf4fe38fbfc0fc0685d8bd422a773c7bce649f7a86c59118e" +
+ "f5f857b2c72508cd1ef05e1a0c0b7ab4687fdd57437092eb49bf41a9ae8b" +
+ "bd98272ea2f8ee2515ff267fa6ae892c266a7effe61ed54984924aefc461" +
+ "6cf483dec024ad666bc797beaa429a742d1b8806f67d451b6d3a85b4d474" +
+ "003cfe9e9dd906df47da5559c41f15afabecc3e6af279cca0f2a200eb2e8" +
+ "31437e034d457fc880f60f5ae635690bce82bf6d1ad6b4f5344ec042bf25" +
+ "7d010273c861e3ac516e9ee2bab3a255f570baa32298467bf704bf6d9076" +
+ "a4c0b08a528a05cd1fcbdf51f3885fbaba7891a144fc058919903b269b4a" +
+ "29f43926eda32c38853b814a7d528156c223748d674d8f7f5448350f011b" +
+ "bfab1511001b8014e20fee37ccd4a0456f638c197c86dc116b34f955c0b7" +
+ "dee10bac5ea0c2fec8a780ac05098b51b902ca6afff4db3c6fb4f761df79" +
+ "b2039dc5f16d9402442a6fcf6c4297769e6c36824d908beba8e584ea0b3a" +
+ "91b9017baeefac651d0307bd89f517789236c0693c65a5a20f244d39684c" +
+ "eb810cd2ffd3c78fe9285d2eb9f55d133b86113efb8dffcbc6d258e84c38" +
+ "2dd8f4d7d63b65672516d9bfcc3310a79ce244b60d380128d529487f99b7" +
+ "d532d5f5c28fad8b9a071fd2fab8fd98f6d7ed9dadbd2fc4396476eba6e2" +
+ "1a1b1cc594a31fbd3418d98e4aa736cab285a2786fbbd4650e49f9b080ed" +
+ "3fda34941c28d25545395e1408fc3e60730d0696061f821a4d24123cadf2" +
+ "3af3d37ba7ce1ba3cde1368d468f136df82c02f9be9210022192aa02117a" +
+ "ef5ff70bcfeffd47bc37b920826a4d3db001f956939abc0df520f3ec1613" +
+ "ba1c4b3385cad97e42bfd15a3150711fe86ba4562f17780cee1cdf198615" +
+ "ca06270db84986f33e1d53d552b0da82397c496a23c7a78ca7641a908e71" +
+ "89249cc657c0431f1e09ae0213f28a27e6267e9d17b5bba0ea4f3c21f266" +
+ "fe538e215ec62f85517ae6bd87799ac5ce68453f09cbbc50d6e2a168f0cf" +
+ "7166ad50cb65b6c76406c326573c00e04a3186251c6181933828c58f4198" +
+ "f8208c4484805639b0d428fd05b57e4356239638f458a84000c7a7a8de62" +
+ "ec25b54d1e39d2579ec9c512fec475f243576f35efc02a1cd6b0478e2dc8" +
+ "be5f17aa4e3849cd42e76fbffe6e7d6f912d6edf80f718f94a7e48e1fc10" +
+ "6cac29627d9d4b82f05a30cd7c739f7f3ef7ea368d22612f189da450e274" +
+ "de7b61c6361521e684d639be5af4cb11fefa5fce6f8a5065c90873e504c1" +
+ "2c940571ea7bd7e9221129b83039d2edb069e8b5bb68567d8fcae34c6ee0" +
+ "cb94474d8b056cc3c7403873f2fe6db3b567a44e702e4f4813b2a264231b" +
+ "0a998207b41916715ef94e5eec281589d0a711f8e74be32bc60f43d693de" +
+ "77f21d5f7eef892abe87725f3d2b01d9ddb6dee15f40735a8fb67766dbcd" +
+ "020a93b8eef4361dc3a891d521551f65dbe6e3f68c60819b0a540b0991c6" +
+ "4449d207cf5b1c198c17ad6caf3adc628d09fa0baae7a696d84e1879577c" +
+ "ffe9b3f62669d4ea5ebab6364f08c66d170ee4a94d61fb77d60b33dd6b60" +
+ "650f034c5c9879243d5c16f853dd7a89885a9047a341b076912d47872b3b" +
+ "3de49edf7451b435698ac4e182d16c339be83e18531a34aebad36c5c7c93" +
+ "aaf121cf99ff92d3844d40740fe001eeca9ee71300d826bc3cfc87a29d39" +
+ "ea108a3cf259657ec4b967fbb534e7513ef3a96bffb35abc5ce0e890696e" +
+ "54fab515af3d2c0be6e003747504e486c0ec6e30fa4ca79d6596ae0425f3" +
+ "396e40fd37432e52c74f812250dad603b3502f97ada48a26e39fd4d44584" +
+ "6591bfa5ffb3770d95d3dbd49e9c3a38c6305796b8f7d79bd0845170925d" +
+ "575774445299bdf9d3f8ad3dc2dc5cfd3ef0293b84d6e11370851af05ebf" +
+ "b3510a22edd930797dcb76b759a9b5a77ed8dd5130e79ff5ac44b01901bb" +
+ "79603cecf674202bc5d84076ff41b3c806454ce80cb9e5fa9db77294d20e" +
+ "6d3008ae3017aba712862ecd4b32daafef1b8cc8b19ee8f8bc3835e2372b" +
+ "5cec66222ad5ea9df753c033508ec43c8b5995e88c36c13ea3465c8bc462" +
+ "ae0a659d9767db34499e9d01fb1588410257d6f588b3fdb766a66bce28b5" +
+ "e0880f8cf988a2e5eb5bf80cd7d83192b7392fbb2e3a07d51aea2b6bfac0" +
+ "d74d304f56d5af3598a0712cb09c04c5dc14194eca8e1b9b29f88344c0ea" +
+ "55638c0f8ebb70b6242b797fe2525fa1bde76293dbc0a66ab4715e6f9b11" +
+ "f7ecd8f35a20ee4ff3552caf01bb307e257ec0576023d624d6094d43d25a" +
+ "aadfce939a6808f8baacb2109c3de50a1cfada9e384cdba3e97d2c9025a3" +
+ "2377bb195fce68c5569d2d1267e1bc68fcd925ddb4acf567fb29ea80517a" +
+ "7e4056fb014cdee597333ac2408157ff60cfa1afdc363a11fd4883308cab" +
+ "d9a8fe56c2b41c95eaef854f20bf5941ed23156d86de3bd413465a3bc74d" +
+ "5acffcd15722879849c261c1bbe987f89a1f00b3069453841b7da667d566" +
+ "e41fd894d94de44c23fed08d9bdffb723aa8449bf236261240d865efd7b1" +
+ "74a4460e5004ff77f4196d1d421227dff7c78f1726df7b5eebddb4bb5f57" +
+ "5ade25296dda2e71ab87ea2b44ef2ce8742a7ad5c1e7a40e097eb336561e" +
+ "865515f7ee0efbe01d5a928f208f7c9f2f58974d1c11af0e737c673dc446" +
+ "1795da9757010cefc6e7f2784658717938735ed8cbcbd7981a1bb8f31cab" +
+ "b901c87a3218dd1195c59f64d0bc3ce8b72580fe38e6dbf1181e0090e5c6" +
+ "d162df9f31cc52fa6a8ac61897e9b4b3cb0ca2bfb38a38d9b78e46d775d5" +
+ "7645d2d6da16bda8edd8675e2ba121f7f85400cf7cacb9ffcdfae583fb93" +
+ "753d07985a00afc3a4e26c9939a5116d9b61196502f5d774ab4c7fb6cfa6" +
+ "01bcfddcfabfcd28055e858d7d3c19feb6bd7c02565add3a3af61bfba8b6" +
+ "f4b52c072a8613e878368318383143059a98a85ba521f781a8983c2486ba" +
+ "b83f5b91fce02acee0be8d0dda7489975f0506c8f363b5adc48ba971adeb" +
+ "4e1c830b5f264ed42da36d2b5ce2fdab1e63333b1061ec5a44ec1b6e99da" +
+ "0f25e7f7250e788fe3f1b8e64467d3d709aeb7360720f854afe38e190cc0" +
+ "925c6cbd77fbfccc07d8beeb0ce68e47442fadaf13b53c30a03ce317cf79" +
+ "dc9155ddf96814583695f15c970fd0b6cea0b04b1825eb26e65ea9351bf2" +
+ "f7a841ddaa8c9f8e885b7c30b9985bac23d3ce777b",
+ },
+ {
+ key: "491ebd0dddefc9f0117176772f9bab61b92a1f1de13796176091c56d1e53dfbe",
+ tag: "fbd3f884a3dc2a8be06ce03883282e1e",
+ in: "953b9a40789b206fb507ec2c5e9c88ca1baf25ad24c11a62f664db1da8bf" +
+ "dbe9b54f8e93b0bfb4adb12f8873096b8960fd91eb92a8ddb53232ac9141" +
+ "57caced33424cff943a8db129049af7e7b733afbec6637d8ee4f39d063e2" +
+ "be241cca6a339e48d72372efabceac57220692c40856532d95529adfae87" +
+ "a71c72f30244126d01a875375ad8836ef8db929bc81027935042a05c346f" +
+ "bc94dcc057db015e55c56064d2b11154596b813ee64b73bcac05d2688bf6" +
+ "f1fbb0cf3f8307b3df44c3e2dd1d226a4d0e9dc5f7482bada9611970f887" +
+ "f656dcb19ce1f8c5c86f4cbd1e4f49b18f170ecfd184028e769e79d7424f" +
+ "d01cb315897c21111f53f4d41c3b71402eea695272cb5b4e5f33abb9df50" +
+ "cbdaa55ed629d3ed7d93b43e550295502db1f2ed884afc320518e88be4c6" +
+ "b62a13f8d3636ba091d07dbc6c20c7e7fda016c05b2fadcfc9ea32f4ee2c" +
+ "4893de78ad8a1771aacf6efdbd8fb1f6ee9b0572ced3edc6313185b5d398" +
+ "88ce77950aa4c5201a256e3ae3e74f05b70faada14124b35b105a70e7769" +
+ "7184576b69708eaabd36e0ba885fc6bafd5738a67307a1181792333cddfd" +
+ "a4ef19c88497c82fccff05a8f9f732fc7505f0467a14e135288ee018aef3" +
+ "d0412f6b0760573d8ee4ab455d2789b4d22a42eebdf60616fe403627cfca" +
+ "fea672bd0a49e8e7b80e7b7b8feebce3381f2fc16819a8996a99ea230c3a" +
+ "84b510cf2e0d914610d646a2f45a14268ec1d6fca03d0aea5c9ae1c8d519" +
+ "b0e8b0f6fb8ad176b5d6aa620b253cc492b5e5645353fbd9b6c02bea48f0" +
+ "286e2c669782b5ffefa4d8f3f1037151026d9cca78e7808dfbe61df29e82" +
+ "951d7154f3c97606cd1e99300012578ea6a776dcef0811338b56606b51a6" +
+ "9893fe68f762af6c9c26066b1d503e64877d8cd988b443af66a36af8bdfa" +
+ "41b4dfb3721d1d81895884755b9c52527030afdfaecd66d4638fab1d1786" +
+ "3d5517ef7ee7d081b5555d24991810f1edde30930fd392f817cfe632b4ca" +
+ "6fb0460c36bde4a5620b9c369bf51c7d870c43998b8171a553d2f643fe8a" +
+ "58aabfce8cf7363ea978ff4d53f58284db822ca95b80306ec02a64d26a29" +
+ "c98520f1924c70d161682c54d08a2c48f54bb72980a8cf5babd0aaf0fd72" +
+ "7d5b1b9d9b731dc49bad228fe83f7347750e277a4fbd526983c206e075d6" +
+ "a03d68957b3e925a71bc1ea7304c77660d112a5d19fd21a785d4a8d7f2eb" +
+ "dc4183376d8125341eb28b2df5be0b4e04bbf95c47d2fe2aed939619cb97" +
+ "79548b752f57b723cf8295dfce69c9b7486b75a4e900f91926636f3fc78f" +
+ "7b7720a5151abdf5868fecf1e1a1d830cd6a4c5e3cd739da4432cf1fe2af" +
+ "a1090d6a1eeb32e7236ecfddb9d07b97220ab8e23edcc93d91abc11b0c30" +
+ "460d2027869d1c2487070cf60b85ad0b8bc5df566f6fdb0e58fd044da530" +
+ "6d277e564ca6cbfa820ca73fb6201b240a5a94c4ecd11d466cdc44046a66" +
+ "32478221bfa69b3a2cebd16baa302a573c90895d7f4cab453b11e3a4d8bb" +
+ "b5a9bf264781ce5b9796e3c47d0fa57f46b923889af4d073270a360dae8d" +
+ "51d85ea916f14787c6500d2d906ccaaa92d20d93edd09139f79bfeb5fcd9" +
+ "8c1cdbcbe9f2587e9c9094e3c4a32ab9ba56f400b929e80c0551f953896b" +
+ "e8eda6ecf22e6d4a541957dec21d6a9cf388ff0ba58169ab934902892a58" +
+ "86e1126b16118e965a271495ffa339c49466209ed3875b568a4290b7b949" +
+ "69d0465744a3c2a75c599c3a04ab1a3fd09125fe8f45724b2f48c7822b9f" +
+ "ef95af4b758ae66a8b6646df7a0a1aabe2a24c052fd6d30561cae0389263" +
+ "e3388c4c1effe431a04356c334aac64f36593544885c4b7295b57dc39638" +
+ "b665b22dcbf7dd6da867615de38c6a575cc66391135d47f8e1f0c73c6129" +
+ "17ada4099723933a758d83311b384364263cad5fe14bdd7c825d9601c400" +
+ "3537a5aca7f9da4710c132ce8b0f1464cee625633ef57f507739a0ab1cd2" +
+ "21ae634d4d0b3ff07e9ecb1baaef0a82a97279d46543a0464855cd62c07d" +
+ "5e890265612906a9eac88bec07b1dea5f67054c31ae40f8c673296cc5df7" +
+ "f0dd8cc9e643b44fd90dc2d1e870ad8acdbe165237642fd04c00965837cf" +
+ "bd2344ae830887a5719a3c16dc8ec08bd9131d055bfb959b64ff4cb638a1" +
+ "002a4fe02e369871cc4e3ffda17dd85343e679fab43e11970e60198b424b" +
+ "676ab17fb0dee10cc9c2e92b32b68d5b05b7a559176f822850c0557ed98b" +
+ "7454916e32af549a0027db95f02b88cfc5e7e05f28f53757dd97cc0f0594" +
+ "212f8801e58043cb17b040413c226dfce2104a172d218caa4353890de17d" +
+ "be1f53af6ceda24b8781801516cc51de9ca459e469b3c322be13d8c9541f" +
+ "755c518ca41a0ed42e44b9f87faa2a968b0292216e9f3d3e8987282103e5" +
+ "016fe9f7681496e1e8d663eb2d8bc30b41d735465527f19e336a98d2dc54" +
+ "d7c020bfab30fe6c62cbae7d09f84af69bc2c51a1839ffba15015d381ba0" +
+ "a44a3758771c4f18d13827f518f30bb74f4bff29a87d4b9e949f1063f63f" +
+ "662721cfd64ffe1dab3761852387f78fa83fb48ae2c75fc567475b673da6" +
+ "fa8f53770b6e5a3c9fad951ec099c6bc1e72d1c489e1ae620e7f12ddc29f" +
+ "ed65f29c65cef75014b999d739e2e6e015f928a30f2fee3f2e59bf65b54d" +
+ "89948bf2bfde98b076e5460643952befd02fc1b0f472a8b75195c53ea296" +
+ "6403b9028db529cd04b97231bac3068855fa211f4d976a88bc27a0088f04" +
+ "576e2487ac0467992066ef7667ca8429faee92db38003728e5c219c751f6" +
+ "6f011b5d679fdd957f4575a0cfb6b54693a9624f2c7e66c578f5f0367005" +
+ "c66addd1e3ab7ea1ac404e357cbdab9438b9b4f80b3a6761b864b006f1df" +
+ "689ae4c0434b06b686d5353d3e421b57381ea24fdcf6199195ccdb3d5cf4" +
+ "623a6bb1f9eba9b22fa15395f65f8093b5f90455061c1cbf8128b44a31e3" +
+ "910862a59e187aa7f4d22e0317ae6c177cef24eebc44171f70c25efac73b" +
+ "38ada0cba0b74f72d1c171277a734819c1111ebe46d5db20a6ff20e2c1a9" +
+ "a57edae95a3c1f80ddf2b12c86d3df0078a7bf68695b16ccf92053c727a4" +
+ "80586b8d87d0d1772e456fde0c20a7927f351a641bff5f22f9ee2217b6a2" +
+ "d0983c8102d7d5356dea60a19e105ce366b9d000987c8c33396569f97c56" +
+ "2d0fc0bc5859779aa10efd1f8df0909c307a9110083cc6d9748456c9bddf" +
+ "16dccee52b7974867cec718bb0b76b3353379a621257094277a30148ac38" +
+ "e5cf67ed7cc9c1bae12dbdeb99d7d880ce98e17f0dc93c5330d1824a3c9e" +
+ "ffd86f89e15b59a4bee5a48d4f674766896e187abaa39917b83f8d2f3265" +
+ "bbe7aac44c9f8d92f775fe6493e85ab44e6e28f79f28eff156c21e1abdae" +
+ "d10a291b88c4020b1ae8be001080870847a852d073e82bfc751028ac62d5" +
+ "6aeac1b18f2cff1c0c7d336bf08f8cd5099d9d3b28f9e16077e9caabab49" +
+ "f2d234616a7522a6bde1a3b3c608df4cc74a6c633d4c8068138abda8d26b" +
+ "4ca70f95d152888fb32bdee5dfad8ff4a5b002a0a327c873656db8d6fdd8" +
+ "ed882e47ce8e47c729e1292db9122ce2e9fa275f9bb986eb7e0a1dccb7cf" +
+ "abd0449c92fd35e2aedc4aa89caf53bcd28170cae85e93f93988e723a896" +
+ "10cefb4edb6fa545835fba3107e21dceb272c5a32da26fa77df070f41d7c" +
+ "ad1d68b836199ff0f1221e36b9b976b5e69bed54b5bfec67fe9cbb383484" +
+ "696265204797634594bc335150daea92dbc1004f613b4c27bf5c699debf9" +
+ "4365041b5a894701da68a93bcb61f4e546c553fe61f14ab0322b45915da6" +
+ "ecacaa093b0071f2516ca8c3fef2f1e3c403993d734403c47bfe5f4379e9" +
+ "cb5b613fde3c0d880cecef4101aad8b8b1c60a92ac5185f6c243fdf1711b" +
+ "0b56f0fd8e5ed6cc0f99da888e4f156455a0f0eb365b8964347eedd15d80" +
+ "2f297977af667ed1376dfcc610f5152421b97afaaf16f9db57a435328595" +
+ "b9aa00b5ed9ff106c66970fafef379f4d2f98f2c5984ea05aad64651fbf7" +
+ "7968c8cbc4e959859b85302a88a3c2faed37765f3f6ced59d8feb6c72e71" +
+ "f9d4497d98bccf95fcb650f29131e1df1bf06a5443f8af844aa1a7b5a68e" +
+ "bb250c7de3a65ae9b1086cf83f832050e55030d0f67c6a54ea2a1dbe18e2" +
+ "8a96c9e0dea2966997bfc5c5afd4244e3c8477c4f5e8bee8fc8ca9a5cde4" +
+ "d9c5a2c7f3d2e811b1de7ce4279229319e432674c609b4c8b70dc6172e9e" +
+ "653fe1969bbc2cb3685e64fd81d96d33",
+ },
+ {
+ key: "b41db44465a0f0d70093f0303bbd7776017bca8461c92116595ae89f1da1e95f",
+ tag: "d8a111a09db22b841fa28367ce35438b",
+ in: "b074b0984fb83749586881e8ec2c5ce9e086cfb2aad17b42b2429d4cf43a" +
+ "0400fd15352d182e6c51e9338da892f886f460d40bd178d81c52e9ab9c1c" +
+ "bdd812594e6fe7a9bb7fb729c11328d3288604097600a0c151fa3d9e4268" +
+ "de75866558e9f47d8dd331994bf69f826fd4a6cb475ae5e18365f59a477a" +
+ "dde7fbcf7e40b4e3dee020a115830b86f0faae561751e9b596c07491c42d" +
+ "e02fc979e69071113953729d7b99f1867116d058a90f1b8c0f9ba12c6322" +
+ "4ebd1b563a87734f5d6e2d4e6715d5f0213e33316500cc4b23784f78a9bf" +
+ "13fdf99bfe149cf47aeaaeb9df1cee140c3c1264fe89bcde8acda6bde16c" +
+ "e3d770ba51950b67ad2c5232ae0cff048ddfda8540cf18e673582dc96987" +
+ "4b127f655e7d4e08859f2c6b95403cd5b4e2c21f72bb872e49e592306286" +
+ "48ba1b16fc9637709636b198f9a297aec364d4c3bc869dcad32b1830e434" +
+ "b556b429136f0012a0a0b6fb3797bc8668014b010ea51674ef8865348dcc" +
+ "197672047fcf72e6b6910a0e32a4f110d85e28db0e338d9cfdec715a8800" +
+ "b4f007a7951d09e41620815848c89f8768344c50bd522c46f64ac6c98e53" +
+ "92176651961c7a70b62f3d1819bfda674e2ecd3167415edc4b97419e8ae4" +
+ "9974b56cd8d52e1d05b82610b59606a750b34844ca33bfc9b21fb970738d" +
+ "b66f48928df79cf67730a30b0b612f8c15c22892120548ab460a6b9bb3ac" +
+ "e30554c86c9681c797821a1b1ce91d0e87fe90ad4097c974cfbdfd5c4c24" +
+ "a5f808f388e1b1473e858f48a387614501c8c39d6973ded69b1764663cd5" +
+ "166be02b596a49e392d637e3d8afc91323f7450318b79d5488c040e346cf" +
+ "0cee512044514b570aa66bb98d639a9ee23a7cebe28474592623d082873b" +
+ "73efb3eaa4721fc4761e15a390497cb13cce181107e8b1a0186b9e47a5a4" +
+ "b67a5be3cd88a43d341ef63f10af6970aaf56035db938655020809033a92" +
+ "8d4fe6d2f5424fbde2fe82adfd991d388edf293cb4e3eb68d876f225a5f1" +
+ "58208bcb1aaefcbc28d6763d267406aa8d6ecb413d18cff7a318ba031ba6" +
+ "0ac4560748c248de64eec56dd4540124b38581604f502d94a2004f9eb1d6" +
+ "edb009e16af6c6d3ccbea79b10743da98aee7ace407a90c6cfdde694f36b" +
+ "e0271e722618a457be68619b980754795f4ac95ebf4f1820b85ca8e3fbff" +
+ "a2430f8e01ab422d7140751f7741f2c921400dac404b04e049736738a87b" +
+ "6f49bd54b1b447b922c473831a65f224ab84fc96e4551a0333bc6187e15c" +
+ "c0f0ad628068bcd7c043bd1e3036ec01e7fdc3d157476149917baafaced0" +
+ "15d09fafb92181a0ec65b00c9c13631e65de184377416e04d3d93b847e0e" +
+ "286c1d88245d4d550d30d4fbfcb416ff26a39a94275631c2deafc7cb6780" +
+ "f149e4d0e9c4515b708fcd62be5252485407a6ceeb9247de34e0266ef384" +
+ "976f6d31284c97468b3b03e951d87a5a00836ea303a266147a79ff3431b4" +
+ "b382e86c74d92661e0f65e266b7d569c03994b667a8137f3080eda2ff542" +
+ "0f0b52b427558dc26932a22a615c9e6b1834a251c6b68fdfc0bbe0e8781e" +
+ "36adf669f2d78bd23509ef7e086634e526258e8d11a1e0be0a678ac09c7b" +
+ "b4e3c5758504011e701dc85997fe2a3e40c7af83f032bdbe7adc10ef1e4a" +
+ "666946c2bf31dd8e3a383211c9684d5302f89dafcf77976d5a02c14e2462" +
+ "09d2d99918e82402cb0eacaa12032ad8316315af1b3d3bd5058f7c935d35" +
+ "ef0d4e71373958fd5e4140a9a586d89c53e4144c00148a4706a524896eb0" +
+ "5b1479a0de5d3f57be46b3f5fa4e49bffe027c81a33e37abc01a4cafe08b" +
+ "8e21fa86b42be52d75d6407e6cdf399de7aedb9b61a6917b2677b211c979" +
+ "33536664c637a57ce2234e3319fe8b4a77d7285ae6347464dfd0aab3e6f1" +
+ "178e0029686770d3b0dd541490b097f001e95f27efe8eb16e4747937d643" +
+ "cdefd49e586ecad541270cedc3064bdb7c79f086bf1fa8c666304d977a15" +
+ "54ae268881e17d8bc3fe51fa9969f7e560e3d3e050424febec0998b35f2a" +
+ "7378b2c3e384cbfc80c4987734d76c78224cb81cc5376f88f0ceda28aa50" +
+ "44e956537c3ee209071d84a66173384e0aa466d989759fb1f2f17fe627a0" +
+ "ffeaae7c5a3884b237f5151278a07117c2e833f1815c7e0e0b1611f25058" +
+ "ca338d21deb1a571faf1d0486667cb7c58e2814c3722d24fb77ce1b7e018" +
+ "2ae5746442b5ad00208b17c0a68bab4df8a8f36edead4fbe79b4c9220dd6" +
+ "acea6d23c7caaf6ce7cabeeca677a1c764d610ea6c7e994d6a9c88f57fda" +
+ "ef160b251e7595578ea2cc1441d480c14b8b6945e76a001891b1f214979b" +
+ "c52ec15e9480d706a40cb6e3b259ee99a9e84e63a738f1b52cf71c8ecb04" +
+ "fc833c2c680bfed587aa1541e5ffe8bbd7b21302bbf745011e559f94f952" +
+ "8b7fad8a37f6d855306a5be22725859cc950bcc334179d49564af3b9c78c" +
+ "e1de59a9cb45086a33856ba7195c17cef573950155bea73ed16645768bf0" +
+ "a5cefce78ba3ff98a54a8e8afc5dfcb0d422bd811ba9b7770a663b081dbb" +
+ "40aefffbeabca955a9638830f0c5d70663cbf5b26067cd061c4a3f5cf8fa" +
+ "4b6678d82d9a2aa33f8538b7499a3466f6b0ae2a1daf280ab91a6c220684" +
+ "12705245f353b4b83db50bedd3bf99d42bde6363fd6212cb745467acb007" +
+ "b678128f6580629a06171f7f3af272f8900b801af3bf47439167871e7b0c" +
+ "33f198333992a6c52c32be46071738cfbf245937d48f816ebb88ff0e726a" +
+ "dc41de4c771ff0bd320a4c0b1fcccd9fd6c42ec9c5185943c70e9a4b7c26" +
+ "a980afe104bb1f99576671a254704c7d4233eaf9915e1d56c103ba9f6e8a" +
+ "46aff466933bf58c9842796ae9cd21f7ac6aa96ef42ca54e390203bac354" +
+ "b7c1de7d1887c48255201335f819020e2782a2ee8af92ceb206b651ae92b" +
+ "3f4fdefed05e08974aee0a353d104b1be9a5e75c7f958f1981271b0a6928" +
+ "05a7a2f28a0448d86102b4fadf9ab4ec2f98e31e64fcfdf2b524780b3342" +
+ "7a2a3100c2032fc93199f3ea7a9e8063fe73282dcb1fafaa9496c7da868f" +
+ "dcf33bbb761df0bfc6fef30fadd2b6efef4fd3216a8aee48a2ef28102491" +
+ "cf7278b567c272d1064a277eb193b3f6f01df641ddb729f72454943cbd3b" +
+ "671ec077f9e3548f5f57d063c653ebee4f228a78f8a128d26f7f4b44160a" +
+ "07e942bab87b2d043c77ecdf10c1a419e0a1c4162a99c21d4abae0558b8f" +
+ "4dc0b7f1ca3892a6babf71f2f70aaca26bb813ac884ee5d71abd273ff1c4" +
+ "add230a771b678afbb12a1ca7fbcb2c0f5589c9ce67fe8f78a8db87825b3" +
+ "09ca34f48ac35aa7ac69c2fb2423807650fcf47ee5529e9d79dd2628718e" +
+ "230ffe5b83f9d5bdfd9c5d211282e71cbcacf972995bf1b13d21419f7fa2" +
+ "8829ed1dcc459da35883b9269a474f7fceff01d44ab78caf1ef7d8117f50" +
+ "cc83eb624062b149a6ed06ddd1cd1feafccdee7122353e7b3eb82978ca69" +
+ "247fde52d2d6cfe7324f04af5259e1b5c2460889da4541b431ba342a1c25" +
+ "3a1b1b65fce7120829e5466e7ad2fe4e0f773c7c13954a9c92d906c91aa1" +
+ "de211f40916596bfa8245344e257e5907a2c49ebcc864cfbe28663e700d8" +
+ "472c50355313d5cf088e9e8a19cdd85bcfc483520498c6386050e53a3ff8" +
+ "1e2b77b55b116a853d71f60d621265166cd7e95ff5cb4466226d7cef68ff" +
+ "d0a35b61e76a43cdcfa8da7fff9558e2f89b981ec6be632b126303ca1fe8" +
+ "53d5c628d967d39317b60ac904d6a882beb0746f6925a86693aff4deaac2" +
+ "e5b64b611de86767d55a6e11221605508b1c5cc828251539b1b6f65c2c04" +
+ "8e65be5422c1b11194eb687d906c559068c0a810713b23b30d8b17f10df7" +
+ "0962c5e7e782aff7bb95adfe4cba9d90b0ebc975fa56822025100b5cb8b3" +
+ "8bdc8928c1a2a8034dd66e2a763696d7ce6cef4dd586b83f7d01749d37fc" +
+ "4fe8d7abd324d4ff1efdbdbfeb0a2fbb8b266fc2bce8e5e5b95d0089e7c5" +
+ "d7de4db837d1822ac8db8198889d6bfe778d0b19e842f12b5afd740aaecd" +
+ "e36e2cefc2cf0b082aa0c4f75684d024b8d828d8f2911fe1aae270251f62" +
+ "4f49584e40bb193577c9d8e04eb16c094653cdf9a15fe9210f724c7a7c73" +
+ "74cfd1a74abb5ceae88ea54f7e7569f8eb674529cbec965ed05bb62f1968" +
+ "8fdaa97297268bfeefd06eb21f700cc56f9bf7f6cecbbbe7278ada8399fb" +
+ "960371a2d5cdb852b11c9fa17650e614c5297bf46cb7889d52bcf49d2560" +
+ "720852822b75bb16524d88273cb366b84b88282da91875562e5a1fe73973" +
+ "afe90e5cdd3f5381612d3ba7bfa058d023a9326e403ec474d8938313fb32" +
+ "bdb5bf899b900c3818c43c8a0af6a061bd26e847ed75983402ee8a9cf4ef" +
+ "85bba5545a0d329ba81495157eda0286f1917de512fe448251697dea406d" +
+ "a510adcb05",
+ },
+ {
+ key: "b78d5b3019688e6ef5980c17d28d7f543ca5b8f9f360f805ee459717ca0d85a1",
+ tag: "f01babc4901e957d0c2032a7279321e1",
+ in: "ba7d35b2ef8af1118bce1e78018c9314b0c8c320591e103d23f715acb05e" +
+ "dc98fbc618de06627661df5842dbba9f604c2d20d664e5db06e949b11d49" +
+ "665088dbafdb0d39d20beaca7d723f8dcdc57e9c5583d303b6cdfdbecf95" +
+ "7d8daf2f1c72b2a6fa27e3d18841f4841abafd334c110cd2b74efb6191db" +
+ "ab9b8fc8427ee17664082f31db98d30bf15dda967e20730a9ef525abe9f3" +
+ "f620e559ed22bf74d347c9869f0311f33da7f1a3dc858b3a8aa73a35989d" +
+ "b055a4a2c269c95e352259c57de8b94d8de48984ecde426d3ef60ec1c7b4" +
+ "41cc950f7764f55bd0cf52d069b9ad446d1f765f35d02ec104ffcc00bf1e" +
+ "dc1b951ef953acd19984ff1b41041bea0e9f5326a7c9ed97e6aab42174ee" +
+ "971ea1dbe2fd1c1f67f977ab215962b0195417170f6b7748fd57262424d6" +
+ "cf7c235b34425f4047191232722932213b3eb73904cadd6a2e9c7571d7c6" +
+ "6c2f705b5039ff75e5e71c5aa738bf4177653e6eb0b49303a4bc0e641e91" +
+ "2691f217296a3325431d578d615afddf47784e4618a2ca40ccecb05d621d" +
+ "a52f272b8cf84f7fd8177c83af1580d25a764cc06436d67171cb5d1e3b39" +
+ "367b46d9a59d849d87ab6bfcf3fb9bac2b1ebfcd1cef4459e74b0e1b7080" +
+ "dabd2dea79f75581a55de63c4b23ff67d986ad060102933fc6cce8d614c9" +
+ "c86dc84068828dd9e21ffc5665c809d83b09432fd315dfce5d7a4ebd8143" +
+ "181953e3f8716e47b0b30cc1f753e31a7d509f2dbd4177b6da310cf3cd02" +
+ "5db270adf98e96259a5ae1b81f5be4d5c76f502a612ca73c76b91e0ca695" +
+ "aa921f9489948619482c2956205ae71fffc3aba4476ff754e4878e36c763" +
+ "2c935c076857c5b90cd63ea4764efbcee53e2ddc9bdce54b1cbbcf0e7544" +
+ "d023e7c2b79419ad92221a1f76abe31a8236e370d38e2493cc9ca2aaa811" +
+ "30fc713d11f500fd071d6eba6861e8b0859b372e62fe60b627a96c377f66" +
+ "236aedf307e1d148a61bdad072b93d7d2a73367c595b1e048f7023e72729" +
+ "1ec508326f5424a5bbf4e010d0240b71fa9137e6642ab40c5e4fff79877d" +
+ "b3253c663a221b49b3e77ea307c7b9f3f72a0f3a54d0112c45c64a0c0034" +
+ "baf2b55ae36ea6f811bbb480cee663136474dacac174c73b1e8be817916c" +
+ "fd4eb1876582bb3a36cfbabad91776aa676305ddf568a86e3a5eb687fa81" +
+ "67771fca7b5ca00e974b3cc3e322b4bd9bcee2a87d0ae7976da5e04fa18c" +
+ "219fa988d4f6fce62f194b05c26ed3ae1b066cd9751a2d916d53426a454d" +
+ "58f9c3b2fb49374e5791b412fdee1b6029144f1ca787f56fece4f64f4fac" +
+ "bfe4cfd8ba7c807a83cf44008fe5126a283ab2631a87acd8e2a3bd10979c" +
+ "4b07a84a49b0687a45a4798ded0b5e9b2acce30e714d78395bfa8f33ca91" +
+ "e68b2138bd67d8a694cd87c88dcefcd101a3b408d7a9095cc6a4b38898ec" +
+ "c8b375f5a67deaaf73eb7e99b10314ca6bba824658bee85dd731d9a1475f" +
+ "976b7c0aed4b67b088f0db5ca5091273217f724969dff6cf184181377c45" +
+ "5722beb23fd9d097a82ea2d8d527ba6284acc20cb30f2e52af28800c61fd" +
+ "1faf9f4f619550e0162a1a63758e202533889b27420fe7d0eac9a47a6e11" +
+ "1d80054412340e0426cdddbb3c7b9b823b8db3ef58230fad7a3ac21a7805" +
+ "d30878d4ea78dda95c951b7a5dc552e9434c35e03e1dd88652d3714f8fbe" +
+ "a39936cc0717c2e0335371f2a751204f5d9386baaec853f019325edfd1b0" +
+ "719d1fdac3fbd774a64bf957fc54039501f66df94b5b9b82c2076c597065" +
+ "dfcfe58b2e215a3734066aeb685ef97759c704b5f32dd672ba59b74806cf" +
+ "ad5daeeb98d16f7332ff0ca713d541c84e4aef0750bab7477ea707e2e497" +
+ "e12882dbc0765106070ec6a722d08fe5c84a677817b28fa3a41a6117f2f5" +
+ "465c2a2f0eb2b8be4f36e676b4115008bade3573c86cfb1370c03b6b0dc4" +
+ "bbbb0ada4dedac10a593655068a26febc2bf10d869cac84e046c9c846ce7" +
+ "927431f606f07b92abdfd81260199ae05ed01dfa07088c56a6a8de9c6d51" +
+ "d61d6a6d3f9904c216ea8329467a006a3d2495a768a39ef99a21827d2def" +
+ "909bb743fed7209f7fe59ff1c1e710095b05f166c6173deef5c6ec4105c5" +
+ "fc3b87c8269c786bebd999af4acbf12d20453b125f338aee87e9509ee405" +
+ "9c9e568e336304d7be9ffe81d1700555b0800242d9b7450d7256f2b17f6e" +
+ "d46a39f67bb2980572ce73169e352070dbafd4c7fa5a6be78cf9b72981c0" +
+ "a01f1e1e30ee3736c59828b791d2373799854497a28a44bbe0e074925723" +
+ "4986696fbb06ef9ea83fbd49c45a583ce12ff10258ba06127c67b0f66dd1" +
+ "09f1366d8036853973d8884f93de54fb2a12949eefc020717eff47898cef" +
+ "306b5de068411f1e113ffdfe2556e0faedc3e27d95a45b8afc15ba0eeeff" +
+ "eb86da7b4324e20af80c62bf0ceb4aee1515f5912f71c6bf2febf20123e3" +
+ "dd3a82dc1e58a108f1039942dcdacdeb1f0ad0b2ef34488d98d6a52311ae" +
+ "acbd03c12f6e775e375d5979c7c295bb049f2cfd3580e3da3841ddd8e6af" +
+ "4de5e6512ca79cebcab9280554524881da37984d340e8f0163fe10a02ed0" +
+ "88682560bc6d3c4dbcf1a542ffb3dcc2ed16a2eb96896e8269697ffeb50b" +
+ "73f2cc354092e782a0072fc12e1eaff117c2cc8a5a1ad8b47802ac9e23fb" +
+ "91a0cef9e4027595e0885464e61563093ee2b1dc5f22dfd04af7de6a70d5" +
+ "977d3751a4b3cc0c71a71c59c0534cb1f8c0eeddcf1c0e1b3e5ad0d083b6" +
+ "6e8b998ddf9ae9d3b365c851d42e995b9afdf8d66b2ac40bf514ce32e456" +
+ "0880afd38c42c08926067eb243c4b1184e667ba756c14ace5f525eb48df7" +
+ "ebb429d0a23d159664f8021d27dc7167081de331c7114c9c6456e1ffdb42" +
+ "2172a81c06d8deca995e158c48df27261a83f83e0127f5e056a139be9b76" +
+ "e25dadf534d3d1ed6ebc0b5d77d51e5b90ff86f30d4023066115bc11b33c" +
+ "c827b1103098826d0bf8777176b2da6f1e5b580e407ccf7e614fdf4f5b53" +
+ "3ef6d30b20c1bee61eab90e983b1a97173a62720ffd27abb8976a948d532" +
+ "d06596c23b0ef31c79831bead8f8e99ad209af3658cac0cb3c3f9c88379b" +
+ "9bc871d8e84171d53400902da1243f664afeaff60bd96ba2639a7644676c" +
+ "a79f43130af12ba2c877d67f7ec030a4217a72f5368af7c9f24e643db6ac" +
+ "97a04adaf57dbc53762d8dfa1afd49667c4041adcb5ec303e191b786273b" +
+ "bb065cd9f16a3a4a399c6a7aab9c1a6604998264e8b3dbd13d8f2228b13b" +
+ "2c2b9fec5055d8e9f2df1d9a25e4bfe2029776389877bbef7e2c7621f06b" +
+ "c0b7fc0786e2b2d042483ccd4a59d2872a6c5ac73e217123e5c8401580a8" +
+ "d967e0895aaa28f4d25ce68c90b4394d8113bc423e9fae46ac47bc2ac191" +
+ "fb97b80b5a85feb2bb54f84c493235c1408662fe253c6786fcf6fdb8be87" +
+ "dc66a72cc847f94dfb5214af5905b7039a7363a1b23a07853daa26862783" +
+ "ba08a80846fbb93ce98700a4f9961115128dd67bd7d19e0c588fdf6196c1" +
+ "1cb0154002ae862f11421f5dc3a57b6c0870b452272be556a1d14eab1af0" +
+ "a91ff5b89de6bbeed6e03bc64f5efddf9e54da71c594bc5ef78e0192cfde" +
+ "da36e4ad1a6b0b51110c1b24d20dea1f19e18cb1184d80189f842d4f07ac" +
+ "834744dd009aa3771b1e5502fe4b65a403a4bb319e1880ff6ba852e90a8f" +
+ "4fcb52cf374c88408428cdb1255291b04ed58c992310955198d61fa1fd9d" +
+ "762d48f2f65a287773efc67d549981c291b427889d3e3dfc0cc6cd68415c" +
+ "dbed81b516786dacf431472a7dfc99688d15bb6c1b85b1a2015a106e5de8" +
+ "cb9eec4c80b17d00fdcf4a9c64de4643a95dade8fa9f1bc5c839037d86c1" +
+ "3800a244188e3b18561a74912ed72f99f2365f0126732d037dd54a3ab77f" +
+ "9a9f6a1c1469ea92eb707482066bd4990dec4d7614ccb4ea6dd4deb8bee2" +
+ "2c4dc0b9b4d4cc70a500d2c8a5ac3ef88a38439b7dc254a6d920cfd317a8" +
+ "4d7747148c65b6730709e43369d4c995b03c58b9df444f77f216944e70f6" +
+ "6446554d8d513b8f7f28ef0a2d7ad5ca2f6110304196953247a7ac184f68" +
+ "61fba896c2d5a59007ec2b2c8e263957e54cdc1f3b4a145228823fdf0960" +
+ "c33a28f59b03ee4be21001d2f56fd49ed14db33b2c4eec2c3f41b250a624" +
+ "99a9b6602c1e838526a54cdcd058af1c252d56009d4c7769deace53bdb66" +
+ "543f5a081cdde775e61efa70956fe2a7a6019a164c6e413ded314bc928b4" +
+ "aebccb946ffdf3eb33e187bf421febe26112b3262a526de65678cd1fa03b" +
+ "83513705108fe0bb87aa99aceb28af3641c46a2c4427cc1063de01aedaea" +
+ "fba68155d4de494a27ff6b7fcc8f5c5c3f7d3a115c397a1a295bc55aec8f" +
+ "7f150cbce2a8aa4706d54ec863877bb966ad441c57e612a1b5d438b98d9e" +
+ "fcdfe6d4f66e885f96407e038015cf974ae5a3540692b054d2ddfde59b28" +
+ "ede7e2f581eeb56c5b88e2779aea60c1d8ca6107b0cdda1ac93e6c7520da" +
+ "edc66afeed12f980e20e1e1c327d15ade4bb90de30b011a9cb33855ca3ca" +
+ "e2",
+ },
+ {
+ key: "2b0b0fd3347e73c2fa3a9234e2787e690a11aec97a1c6d555ff7b4047b36f372",
+ tag: "81b1a6633f849ab0aa7baafa58a5d9b8",
+ in: "427f3a7a5f1142ffa68e83df5f917e07b2bc454f3adce068a8ae9e0908e1" +
+ "3e0099aaa9074697593c6d8c2528fedddeca05e3888be1a0a201c389a72d" +
+ "20cb661017544d95a431e70e7c6580d8fb46ea4495bc59db6ae2cd69510a" +
+ "02426c50de1b6110120f759960605aca718d4d0a497e003e1ea2b8ae9a53" +
+ "df3c1eb4f704eb32f8f05eb08cecba0fd4a94f0daa3b0984c30a38f94b7a" +
+ "10cde723182d30588bc40f1f9d38a3bab4800fdd5148e34e396144763696" +
+ "c9b3e9b8adfdb337123d54237c7413f98bb2056152b256e37a27bb947c67" +
+ "240fa3ce8da62ab367db540bcdd9eb873d6c71c75a08fe99b5c11ec8e6af" +
+ "f926d2adfcf073479de394d4aac5fdc6241824d944b8773db604c59afc01" +
+ "495ee755905e5616f256c8a64321d743a1c9368d46418826d99b762e2f6b" +
+ "f998d37a995969cdc1de85f0ce3987c6550459f5e5bfd9173bfcb9e0112a" +
+ "d91f092de446beba14fb3b8ce3fb2f9c941815b2cb5a3b406e2d887b7912" +
+ "bba07c8dc7caab9836827da93ca71fa5ada810da1e5e9b09738524564d8c" +
+ "923746d19c78dc9107b9f20f653e05d7f2eb6bd90cf5eb30fdd7b587eb46" +
+ "74a1064c70ef0af2e75373044d32b78d96eb1db3112342d38dca0e47b96e" +
+ "9307fcdd711b1c66355186369a28481cb47ef6bf6651c2ff7ee4665247cb" +
+ "12b573933d3b626d1c6264c88bd77873c2e73e73ee649216bf0b6d6615ab" +
+ "245c43569d0b8096596f25ceca8667661de1cd60dd575697370ebd63f7e9" +
+ "5333e8a2cdb829b75ea83d72cd246d50358f7c094c8a515805fda03165d5" +
+ "21391617c9f9a2ea562b419632df611a67912d2b369e5e505dbd5c719253" +
+ "16d66cd608cc4a9583a8eaa4661b7279870345fac3031631c1a220551527" +
+ "5be7d8d89b71960e687aace3a0e8f206e475053d6fbf97717b154c75406f" +
+ "2caa97d1ab66048f1c99281c188a2f37b8bfc736c25840a9130ef2031c05" +
+ "6acd9dc10592eddf94f5bac85319b10ae46cc136a0738aa803837287ed7e" +
+ "dafe08d1fcf31d5e63763e39a5e1f4d7d0edab368d44e63fdb33c28905ff" +
+ "d6be406a024c017081b4f2d70860776e9d2556cd008fa5017b58733da13c" +
+ "634938407a118827a80baa28d4e605db59430f65862b90cd8356baa287b8" +
+ "4e6d9199fd80abb9fa697e2c2c4c760128e4ec0438388cf407e2a2fe0f57" +
+ "908187ed8efd4c5cb83cc91dbe6a11444eede85099149ca82921bc28bdd6" +
+ "b9999594a41d97307f8854b1bf77b697e8cdd4daead2aa49fbc571aa44c0" +
+ "bc84a57cb5fd85f06847ad897ceaf449eec45bddd4e4eb1e1e119d15d5e7" +
+ "90957e686acbdda1bbe47ea935ebc4b8c2e3cf9b7157cc6dc03bcb19508d" +
+ "a9e19cb76d166da55559ec7e0995d9b50c6c45932d5b46eee400c56d9dee" +
+ "618977dcf6f76e3e86bc5207493afbc2aae9f569ec9277f33d9f61c03d59" +
+ "dd6d8250ee8cb3e54e5e941afb74f0735c41d52ef967610c9f55b2b52868" +
+ "4b549a99ae3392a7237bb52ff5f8d97327e2837268e767bed0bea51f76bf" +
+ "88bf0286bf22b881f93f1d54fab5cd4e3c148c96c39e7aeef375de249df0" +
+ "4d89d1bd97a7afb2be0cbfd3380cb861d31e4ad1ea8627721e4518b9db3c" +
+ "cda20273ec23549c4adc3c027e3ac9558de2010a0263c1225a77dac8be60" +
+ "d498b913f91391d8b2656ffddb06e748cb454dc2b7226745f11030a6b9ae" +
+ "09ac8ac428d9c6500801fb540650c94610ab70465b1210c6db2064dc84dd" +
+ "7f52573f8f40c281470e85176c85ec6de3c718663d30ad6b3dfc1a3a9606" +
+ "1936744357ca62fb8bb066aa1fcac6d7a2adf0a635cd546bef39fbd3ee0a" +
+ "8802ab0466ec9b049b5892a9befa4377cd199a887c34569b6f90852139a7" +
+ "86babc0049ee2b527aa96b988237a52eae8b4b49d2ee15ee5294118cee62" +
+ "3c3e11cecb836b21af88555f10be2eff8379beb615b7b3d6c01d545cacf6" +
+ "61be8ebbf7a3c58ac5e0e7b17997659a2bf15f2b2e3d680d142fd29d23a7" +
+ "aea9890f3ff7c337fce49ecedaf38573edfae07810ba9806723e576d687e" +
+ "a11700b8ccb96a6559259c367cef4e3999a05a373ab00a5672ce8b3d1dec" +
+ "a414187f383e449d10021b73c1f7e39ce01516b7af96193f9993036049fc" +
+ "72ac059ef36b2bcfbe13acf140d41592880fb8294ebffb98eb428ce9e65e" +
+ "1094521bcf8ecd71b84c7064539a7a1aac1ad2a8a22558fb3febe8a44b87" +
+ "72fc00c735773d4ce2868a0b478ee574b4f2e2ceb189221d36780b66212c" +
+ "dd8fd3627cf2faaa23a3d0b3cd7779b4d2b7f5b01eb8f1d78f5b6549c32a" +
+ "cc27945b5209f2dc82979324aebb5a80ab8a3b02129d358a7a98003e701c" +
+ "788a64de89726da470010eda8fdcf3da58b020fadc8970fafb08a29bef20" +
+ "2bd0707e994015258b08958fc2af4c86c3a570443fe6e1d786d7617b0c66" +
+ "29a6d9a97740c487622b5b8186c529d7f8af04d9f0a9f883043f08103ca4" +
+ "d70057ee76639f3b1046d86928d54cd79fb5bb7b46defdf15d2f8578568f" +
+ "1d7b73e475e798ec6812586700e038ed4791b23ac9439d679a1a4bc04cea" +
+ "e328330c24b065c9cdcdcedfbaf58e5299779e6f48783d29ec3b1643bc8f" +
+ "1095c724dea75770583b15797fc666f787510d91e65a8e2090cc1ed2013f" +
+ "e63ab17bc7640ee817487f4eac8326e9c4698cb4df05d01bae8c0d00fc00" +
+ "08919484d5e386c8f60b8ac097c93c025d74faa56e8cb688d1f0c554fc95" +
+ "aae30873e09aae39b2b53b1fd330b8546e82d9e09bbb80132d794c46263f" +
+ "4fd7b45fda61f86576dec52c49f2373e4dca31f276d033e155bbcdda82af" +
+ "8f823948498f4949bf23a08f4c8ca5fcc8598b89c7691a13e5aba3299ee0" +
+ "0b479b031463a11b97a9d0ed3189d60a6b6c2390fa5c27ce27e28384e4fb" +
+ "04291b476f01689292ace4db14abcb22a1a37556675c3497ac08098dfd94" +
+ "d682401cabec239377dff592c91aca7eb86634e9d5a2848161dc9f8c0c3a" +
+ "f7b6a728371fac9be057107b32634478476a34cbc8b95f83e5b7c08d28f6" +
+ "fb793e557513ca4c5342b124ad7808c7de9ecd2ac22d35d6d3c9ce2f8418" +
+ "7f16103879ed1f4827d1537f7a92b5bbd7cd12d1ecc13b91b2257ad073b7" +
+ "a9b1ea8f56b781bea1bddf19b3d7b5973f1065fb72105bb4aeecca5b7513" +
+ "ffd44d62bf41751e58490f171eb9e9eb6d57ffebedd4f77dd32f4016b769" +
+ "fed08dd96929e8efb39774d3c694b0d30c58610541dcfab3c1cd34970195" +
+ "7bf50204acd498da7e83947815e40f42338204392563a7b9039c8583a4dc" +
+ "faba5eaf2d0c27ada3b357b4fccd1595b9de09c607ebf20c537eb5b214b8" +
+ "e358cd97992fa5487bc1572c8459c583116a71e87c45c0ba2ca801931a47" +
+ "a18ef0785ebbe420790a30278d2d0d42a0225d211900618438d1a0b2d5be" +
+ "d14f8b4be850dc8cb08d775a011683a69ee1970bb114d8d5017de492f672" +
+ "09062d9ba3616e256d24078536f30489e4dacd6429ed37aab9b73c53fdd8" +
+ "a8a7aff1b914b9d82d75a46d0ccf85f48d3ce9a8d3f959b596ae9994ac3e" +
+ "3b4af137d0c8e07ece1b21fd8aa05522ba98f85a7ab24ed8c1e265fadf4e" +
+ "9a18c5ab5684d8ba8d3382ad53b415c73ebfaba35abeebaf973b6f18e0d8" +
+ "7f019420eb34e09bbb12afc5b149f1e9e9b6ae36ebde429d437ada1a2d52" +
+ "b998f7c75ef731132aafc3bb106a2ad3ae11223a355804d4869ebaa47166" +
+ "2df261d95d48ac6eb17c1781e81c0027ccf8f05c39e1eda7793cb16622be" +
+ "ce7a1ad5d2f72f8bf4bdb2f4f4dcadac3db3bf727f0d447adddad4500360" +
+ "09ee011bf4155e5e46c74b00d72e8e6a88de9a81a5a4685651b90e874dfe" +
+ "eba41698c98370fd9e99619ce59ebb8342417d03fc724f9c910ae36ac5e5" +
+ "b46c424141073199aaac34232a8e17ebbfdd80eb75e82290de92968f3893" +
+ "0ab53dc83ac433833576e86fbabfb9d7cd792c7e062811f4cb017710f841" +
+ "1e0fb65ea4b3cd68b0af132cb08330aa13579196ec632091476f268b44ba" +
+ "8f2e64b482427dfc535d40d3f58b4dee99053b35a3fed1cb245c711fa16f" +
+ "c141974c8db04f4c525205dad6ca23ccaebde585cd3bc91f5874452ed473" +
+ "08de95cb6164102744f90b3007e511e091653c97d364fe0cbd7f4cd3249c" +
+ "1f5c452becd722ccc8c6b4e371e2631337dff78efd903a8fc195a90ca5a2" +
+ "aa4513bc63cd43794ff06c5337329055c43d4fb547e63d6e4d14fbe37b52" +
+ "1411caf2f1b0df51a68f677db59aa227c725cf494ccb7f8cacc5a06ac5bd" +
+ "f135a2603175a5fd5e5af615fd2e7cea61934e6d938b9e672290aaccd99a" +
+ "7e26dc55efe928e56ae6354168264e61668a61f842a581cd0c4b39e0e429" +
+ "04631c01320857b4d7e260a39c7fbed0593875b495a76aa782b51fee4f88" +
+ "84ca8ddb8dda560b695323cdde78f82dd85757cadea12ef7cf205138c7ba" +
+ "db6a7361a8d7868c7aefa7aaf15f212f5f5ab090fd40113e5e3ad1ab04f9" +
+ "b7f68a12ad0c6db642d4efb3d9f54070cc80d05842272991bcdae54cd484" +
+ "9a017d2879fd2f6d6ebce27469dda28ad5c345c7f3c9738038667cc9a5bf" +
+ "97f8f3bc",
+ },
+ {
+ key: "aa3a83a6843cec16ab9a02db3725654cb177e55ec9c0c4abd03ada0fbafca99a",
+ tag: "719dbe5a028d634398ce98e6702a164b",
+ in: "643883153c215352a4ff2bb2d6c857bafa6444f910653cacd2bbdb50ffdb" +
+ "cae23cc297a66e3afefbd85ab885e8ccf8d8f4930e403662fb4db5121aca" +
+ "82dfcc3069bd5f90be4f5bfd3c10f8038272021f155e5de0a381d1716abe" +
+ "0b64b6d0f73c30baf6ddfe0e6a700483cad0fa14f637afb2f72361e84915" +
+ "78ba117e1c03f01fd61aa8f31da6464f3d0c529524d12dc53b68f4d4b326" +
+ "db7fc45c63f75244002b8f9a185556f8aab85948647818f1486d32c73614" +
+ "b8c4763e2645bdb457721ff3901327588da01622a37ccbbd0374fec6fd1b" +
+ "cce62157e64c4cde22c3a5f14c54cd6db63db0bd77e14579989f1dd46461" +
+ "4c8691ef26406984b3f794bb7b612e8b160374be11586ec91e3dbb3d2ccc" +
+ "dbfd9c4b52f0069df27f04853e7cc8b2e382323345b82ce19473c30296cc" +
+ "453f479af9a09ec759597337221e37e395b5ef958d91767eeb2df37069a4" +
+ "f3a530399961b6bf01a88ce9dfcc21c573e899b7951723d76d3993666b7e" +
+ "24dc2570afe738cbe215272ccedb9d752e1a2da00d76adb4bc0bd05b52c3" +
+ "fa08445671c7c99981a1b535582e9b3228ce61662a1d90a9c79afbdcfcd4" +
+ "74def2b7880cac6533ba0a73fa0ba595e81fd9a72ec26965acc0f4159ba5" +
+ "08cd42553c23540bc582e6e9ac996a95a63309f3fa012eac14128818a377" +
+ "4d39936338827bbaafad7316e500a89ed0df7af81be99e2f6aae6bb62568" +
+ "1dfa7e100ebca5c8d70f67be3c1e534f25446738d990ee821c195c98d19c" +
+ "fd901e7722b4e388da90b95ac0b5b5dc5d052ad6b54f6ea34a824bcf0cd8" +
+ "7f1fc9a07e8f5b8aa0793e3c9c1022109a7c7ae97ee2a2867fd0cf0f8971" +
+ "34b3d150d3b24fcf8323de929b73cca01244df02510393f0b3905caa0268" +
+ "7fe35f64391e7d4b30be1cc98319716528ca4f35bb75d7e55cf7749968c5" +
+ "37136eddb149a9f91c456fde51937c0f35e7e524647311077e6fbe7f3c12" +
+ "37b9584fcf3b0f78744c7b2d3b452823aca06d144e4463eb5b01014201cc" +
+ "bfed1adf3414427072135d48e705b1b36ab602cae69428e7c19d39cbb4e0" +
+ "ca26a871d607ed4daa158b5c58a0a9f4aa935c18a66bdeff42f3dc44166b" +
+ "a299d71a2141877f23213b11c52d068b5afadc1fad76387cf1e76571e334" +
+ "0b066ade8da02fe3b0bdc575b1d9ec5d5f5a5f78599f14b62db0bef7ccc6" +
+ "1711482dfa4787957d42a58fdc2f99525c32962b06492229399980601bd2" +
+ "ee252306b1464914424de9aa414a0a6e5dadf8ffbf789e6d18a761035d3e" +
+ "f2ff0753becbd2dd19fc1c28f9acebec86f934f20b608a9ef735ac91f6b7" +
+ "83d9327cce7f4870d39bbbfb0100838dee83e6baf2b40cfc98415dd174ed" +
+ "72e393ad0459e8035dce7eb18eb3af2f39d2712846b9e1852cd61d06dfc3" +
+ "5e34fb761b67e2a711ceb4a82557371ed32ca8db2e4cd7fea0b6bd026177" +
+ "4057b9abc45dae6869cab1097459473a389a80a4523e5de696554f8b0bec" +
+ "0ca605e6acfaa00386fb5a48e0f5893860a29f35e680be979cf3bf81ee7e" +
+ "ed88262dc80af042b8cfe6359cf8b475560bb704728034e2bd67e590bd76" +
+ "1632e516e3292b564c7265d7a6dc15c75ba6f6a447b1c98c25315ac7de59" +
+ "9edc4993e4dc7d1dbfcea7e50ebd0b226e096500216c42de3abe352e5b09" +
+ "a3c9754aa35d00883906599c90a80284d172a90abbeaf7e156fe2166ada1" +
+ "794420fe55b1a166d752d0eb7f04e822d021c615e84777101e7c9f9dd12e" +
+ "565b7d093fe978f85e6142c1ca26798b45f4b8d23ecff6be836e810e314f" +
+ "ebd2ea66f2ac95bad84b39b7a6bac41448f237b45e9ec579235ba2bf5fa1" +
+ "f00286379ec107c743f06ae0d11b57a2f5b32e3bc5f1697aae812d7ca303" +
+ "b196a8a43259257f7697bae67adc7f121be561b2d0725982532ffc06cb22" +
+ "839d9066dce0e4d683d9348899089f6732de62751ca77f1c439e43054468" +
+ "2c531b9c61977bc221b66030f7571dfb3ddfb91d9838529dbc99612f650a" +
+ "d72bb78de061192068941a81d6ac341101aeb745b61bd7a87a35a2714d50" +
+ "c3eb2c3ea148fb9ebed948307f8b491aec277ac01903ba36e6ad54f89fe4" +
+ "280a17f8e7ae639e75aec16d56576f03c2a1efe4af995eb825ccaa6efe0f" +
+ "d6d878299a351591d791c286cac5cb049834580d47a9bb7720d0603e3141" +
+ "ad7c1ec2dd23d3002e15d73c1828a7f08062848b1b6fcf816bd954743547" +
+ "6f0d6f882125bd03095eb1b1a846d535730e258fc279f7095de7c2d3fcca" +
+ "a4640a2e2d5ce0974c1e073c60bb78171c1c88ae62c7213a95d36ea9ab17" +
+ "59093813b85d17ff106e69100bd739ede9656388bf47cc52730766a8a186" +
+ "9dcc623e09e43cfba1f83ae1d9f16789064ec73504c29686760ea02c6634" +
+ "a929ca10c6d334b1751494c6d143671ce8e1e7dcc9bcda25af895a193032" +
+ "ce27c1016ccc4d85507fd2265ebf280d3419f54f66ba2a161c068491578f" +
+ "be056f02f97be745db443e25ed2647c5348f278f4ad8bf5b2a2c2d56e795" +
+ "532e25585984a3a94f435ef2742a0413abed7230ff2e9724187c91f73a7a" +
+ "726ebf36bc8d0d959418dd586452664990889358c56720c1001c004ff768" +
+ "54b9850890ce1b31735fd9f4a3640622ef0b25c659e8a937daa0df7a21f1" +
+ "77be13dfdb8f729da1f48e39a05f592d8c98da416b022fd8edab8e6132eb" +
+ "a80c00501f5cc1e0243b6b096c8dbe7f8c6ffa2f8bcc7f309fb80b489b92" +
+ "c4878fabad42d91876e10ee64ccd415124461cdc7d86c7bb6bcd9133f3c0" +
+ "dfa8f629ddb43ab914c0ac5ecddf4398052229876fd838b9ae72523946cb" +
+ "bba0906a6b3ef26672c78cb24cbf691a5ec869d9fc912009d840772b7da0" +
+ "c7f47856037c7608705cd533918c207a744f75fdfac618a6981778e09332" +
+ "5c7d22170da85bdc61044b4c397919d601a30746cefefa798c58f02cb827" +
+ "0d130c813cbeb67b77fe67da37a1b04bf3f1e9ee95b104939220fb8a0394" +
+ "86ab8954b2a1468016f546406d1946d531966eadce8af3e02a1f59043ff6" +
+ "e1efc237dbf4dfd482c876531d131c9b120af8b8fd9662cef1a47a32da40" +
+ "da96c57dc4efad707a4e86d0b84262d850b451bda48e630c482ef7ede5bd" +
+ "c55147f69e2ff8d49262d9fe66368d1e38ecdb5c1d4e4042effff0670e69" +
+ "04e47d7d3047a971d65372126ff5d0426d82b12b253bb4b55005e7a22de5" +
+ "6fa54f1dfcce30b1e4b4f12b1e3c0de27cea30ce79b08c8c1aceb1ffa285" +
+ "c317d203a9f2e01d542874fc8035b7670f3648eec79561d6ff2fc20d114f" +
+ "ba4fbed462f1cd975ee78763c41663849b44cb2827ee875e500b445193e1" +
+ "4556bcccfaba833bb4ea331d24a6a3bd8ec09906c7b75598b44ce1820a49" +
+ "fca4a0c1501e6c67515d4fa7f88f6aa3cd7fbc6802131a7b14b219e154db" +
+ "9ed241133e10ace40e4d963f904dd9f3bdaaade99f19de1ddfe8af2b3cc4" +
+ "0a48374dd8eb559782bea5410f8f9a1cd128523c0157b6baad9ea331c273" +
+ "311492fa65c032d0d3b513d23b13b86201840d51759021e4133f873f2781" +
+ "8f54f34ba73b4f33107d49c8de1533856ec37bb440f3c67d42148765610c" +
+ "3296bce932c839fd866bec3762a38406ac2b39d0d93730d0c88cb8f765dc" +
+ "d8ee71263fc96068b538da06fc49e25dbeaa10a5111a9af8e8f8d78e6ed1" +
+ "3752ad021d9f2c6b5ff18a859fee9651d23a7237bd5a5c29029db3882c47" +
+ "0470de59fd19fb3bfbd25d116f2f13ef5c534bf3a84284ae03e3cf9cf01d" +
+ "9e984af9a2e63de54e030857b1a071267cc33d22843b28b64b66e4e02803" +
+ "c6ab5635291aefa69cfeb3958c09d0b37176842b902da26caae3f0d305e7" +
+ "c6ab550414e862e1d13d9bb9dc6122cb90ddb1a7bc6d31c55f146659baa9" +
+ "6cca4ea283e5e1639967889543ecb6849e355b6c0227572097221dd46c1d" +
+ "f8600b230e9644ba611ba45cd83fa4ac7df647b3be57387b6db12682018a" +
+ "de9be50a8ea7d5f7c743bf0c6382964bb385b3c207c0cdd63279c16130b3" +
+ "73ba974125291673344b35c8ef9a33be5a8a394e28dc1448f54d46af675a" +
+ "edc88ce85a11ad7e50058df4f3f2364abd243683d58a2b13fcb0dc0eed21" +
+ "380b666eb87f4be75e7f2842bae916c15af3e9658c55408537b2301faa6e" +
+ "42af4d94e3eda6a41d6d302be281e2a9299e9d0fb1f20cf4ca978e66bdd7" +
+ "4c8bea0f15c84d6513cdea787dacbd4bb529ed03528284cb12f6ecd841d3" +
+ "c58c3a57c6bc19b65d6d10692f4e1ad63b091137c8acacc6bc1496953f81" +
+ "2972bf6362cf883bb75a2d10614029596bf9f35e92addbb50315b30161b7" +
+ "de8867a1393d9583887a292cadceb54078c9c846ec30882e6ff987494060" +
+ "721d3c761940b91a126e8d1e0118617bdae01a7f9c1aa96bdd6c78ca06f2" +
+ "6c8d85664a8705334f4997c724ef98fe265985593d5a9c30798714e6de1e" +
+ "bd04b648be47a6b5d986a3103e738a5cd114b19b7ba99d2e2eec6181bf3d" +
+ "ff0fec8c54ae6118be8702c3e775d493a6fafb509712a43ee66c3f4b75b0" +
+ "194c88937cffa5fa17b284d2556f2b0eebf876e05f92c065515198bd5e83" +
+ "00d0db432cb256a4a0f9963a05694ffce3ecbd182209e0b7bb50120f6be4" +
+ "eeb9d268b17790ee14a2c887dc5753e0086630b3123734053aa37595aa8f" +
+ "31968ddae4991af4ab970c1e3cfa1146a2efd9dc42abd6af14777b8a0455" +
+ "3865691cbac4b4417b3fa13c154d581b498f3b8cb77adf0e42dc2f2fb521" +
+ "732447de97271e542c6cf8cad3ba0148cc3ba1f2983ead836a25a2c022d0" +
+ "43ba18fcd009d518d07b53344a5bc4d626b3b38405a114471f75dc70e015" +
+ "d11e8f6f57d087fa72909785573008b1",
+ },
+ {
+ key: "1793bfda9c8666f0839b4b983776735a927bdaa3da99b13c9f3d1cc57d4d6b03",
+ tag: "bc89cfec34ab2f4f2d5308b8c1a5e70a",
+ in: "a09f661aa125471417d88912f0a4a14115df9a3a19c1de184878291acb0e" +
+ "89ee1f9d8213f62df442f8969a9a5a7c402fea09bdbe236fb832544e1f93" +
+ "9cdd4873802b2bb8fc35ba06b7ff96da6dc7efddfeeda84116bc525a7fc5" +
+ "2d84d2e63cbac00b122dc64f2d15b36595259d81a1d2a09f204c54072751" +
+ "dd812259df1104bb2d2ee58baee917c5d0aa2649c8a1503114501e6ed6fe" +
+ "239847d3d88dccd63d5f842426b600079c6bf06e80a2813b2208181163b8" +
+ "61dca07fa4d88254e84dac1c78c38397a016b5ad55a6b58878f99036db56" +
+ "89871ab3c321f6ed5895f218f8fd976c348b3f1269fcdf4d38c9492b4721" +
+ "6c45f499f5705830b33114d721f9731acf6c69fca681b74c2d82c92e145b" +
+ "7bab77110821d3a12cc818d7595a5c60c4b5e5219376c38a4dd52d435d41" +
+ "562802ff65ba2bba5c331c333d5adf194d29b2cd9ebb55927bb4ec17681a" +
+ "3f5574ad34fb4e964f2c756f6dbbb7a6876a21579a515263444de7a30a33" +
+ "15005458bc137ccfdff18a3892fc9f58f1de10d4de20bbcf860f5f036d8e" +
+ "8a188f18e5cf7ea3cd260710e7491befcb131d49a28dfb1ef688fd021a1e" +
+ "e4420d32fbfb03b47f5e85c37d91e49a1b0db85d966eb5434c4197433eb4" +
+ "9d56f2ff999c9a72230447032dc949202468261b48b6ac212e3f651d6c63" +
+ "03a06c90bb2d3a755ed91ba73bcdc28e1c5b0936e51e0a9f69c3ebabd3db" +
+ "add7abab6d8f6a44daeb3126429a01815f57444fb7022a4a510f8b564ae2" +
+ "dd9779b3a273fef15859a33e233724846c30d89fb78a595b6ff6c834812c" +
+ "00a991e405806aafd0c26a788895ad00a5e43c5426197aa8247207077548" +
+ "ee67db4cd6f878431a2e36e952d84b5fb89d681f553198e2c066310ea6ac" +
+ "3a31f5b1792620616f6c41d486fb844eeacc7fd36971abf416e8d6d50985" +
+ "c83cc92ea46ac37da8f0026aba30c945d8bb15080d2d95e4081bad626199" +
+ "3f95f57ed3252822a7caa035ae22a36c35e280cbbc82d729346cacdb1794" +
+ "ae9a9bb2793fd1d5c47121b135c2836063367339c5151b4e35278e97f62a" +
+ "fdd2f231d4b47812d083a829ebb9c374ff2ae8479cc4b76d55f9cef3ec6c" +
+ "4894f53e8caaeb0d8cd072960cedaf758e48e3640590d4f728626e0a08ee" +
+ "ebf719c96bf8ed4d0c283be09c0ae67b609e22d3b9aa6b03642854909de0" +
+ "5ed52b39673867bf586a632ab8072de15c637cc212cba8387515c9c9c433" +
+ "abd7ba6b02abd09da06a34694ad34f88515b65c0c9c247fdf9819fb05a1a" +
+ "ea4728c1182f8a08a64b7581cd0fb2131265edcb3d4874b009aede0e87ed" +
+ "463a2e4392aefd55e008eb7ba931788262f56e53193122a3555d4c08133b" +
+ "66020154b15643fa7f4f5e9f17621d350ede3dc70be02c59e40fea74dbbd" +
+ "7919d1a8d4e22ef07c916fa65e7d4b89fb11a7c24ddc4ca5f43344c753b6" +
+ "1331c3fa4558738ba7832b5b2a275bc9b7989b6e6888865793329806cd3b" +
+ "f0ba57c941d4428623e062f4ac05e7cd79ad5446f8838f2b247b66bddadf" +
+ "540845a1bb304a04b7edbbff579c8d37e2f6718f8690abd5231822c7e565" +
+ "69365ce532449a41ae963ec23a2a75e88307dc6b59cbb3fab913e43ed74d" +
+ "841ca9f6e4ef96dfd9f04e29e89361aece439c0b2e1943b30410a63d495c" +
+ "522ac3ec1b04ec4cb345f7f86969957ad750e5bd7dbf1d6a22eed02f70b8" +
+ "1cb5b2b020c0694d7f63044f9de0c3de1ede52009c858992d01ebb92ff19" +
+ "a9e0fbea18942fbafb77746c8e9e687dd58ccc569e767528bde43b62c7c1" +
+ "270a5721f1212de2b29a7aae2d6ba6cd173d7fbc78aec4356ce2e8ba9164" +
+ "d97dec061dd0c3a0e3c520a7611ac99739049dd5825537c70b7ef660046c" +
+ "1785546cd99aa400da848eb7c3c91247415c8e245d0f14c30d482c5849ae" +
+ "aaeab2568288229b08267818dae8f76fc674c684c99eb5faf88a0783813d" +
+ "f7298e0b50cb233f78471e5ca9cc3b04927c26a3871cf253798cc49aa717" +
+ "d8f18a1ddcbdc26497d188f15f86ec494dcf8f942c3e07e572385c6fa0ef" +
+ "40c0b625f1737543074a747a369482a0b342a08b3eccac9f9209be31aefe" +
+ "5a7794974f71ac0bc9a58026397ea3dd4f5e40511d58d2a3b45925c194ef" +
+ "13987037d736dd48b509d003a86471d5f161e0e5dd168b4f1ce32f703b89" +
+ "15004d8dfc708a5bb02b2e6fb67424b2cbcb31ddaa0114c4016b0917382d" +
+ "aad11815ff5b6e37d5af48daa5ef67cee3439283712bc51b5adf2356cb2a" +
+ "5181b8941fd78945c7c9d61497683e44fee456ad345e12b4258f15945d45" +
+ "b6ca4369ee792d849112d583fdb39cd4d333ee057355f0abc8d1eea4640c" +
+ "128cc1617982db0394233dbd416102eec1874081247d2982bbf9fed1b1b3" +
+ "8f4da923d68c8975c698f189a4d7840fd7aca9dceb7d91c076f85e1c546f" +
+ "4d5de4f60c91348455aaea30cac134c844dad93d583c139dd52b3be6346c" +
+ "4d2e6864125c5a2d0aed8f67930e1ebf8700ca88aacc914ea76ff17148f0" +
+ "777738cc126e75a2c81110faf02fefc47c91edbab7814599000ce55fe20e" +
+ "f313566e9b62457acf2f22e1141e220bd9d4747417d03e703d4e39282803" +
+ "386327fc65dd597f723ee28185c78d9195fc70a75706c36287ab9c6e00e8" +
+ "5cecbbd6043c6af8d30df6cdd8777be0686853b7c8a55a5b1e03e4431d39" +
+ "1725ff99875a85cae6926998723b36d13ad458220712209bfc5e8d2ca5d4" +
+ "4ea044d5ba846b4035e7ac7e9885f55d3f85c0c1b3d09fe929a74450f5d2" +
+ "9c9672e42d3f59be4ca9d864a4322cc454c2578493bd498a51bbe960e657" +
+ "3e5dd02c4a3a386d4f29e4578a39e9184024cd28d0e86ecac893b8e271bf" +
+ "ce3f944d130817378c74d471bd20a4086f2429ed66c5c99969fd8da358ff" +
+ "5c3be72bf356ae49a385aa0a631b588ddb63628fd162673e915cfc4de56e" +
+ "ae6ff7101df3b33125c9bab95928f6e61c60039b6cc07a66f9c733251447" +
+ "ef9c1ffefa2158a8ddf89dc08686a4cf9b86ea09914e79842d72a3236afc" +
+ "98a3afa0a1cac5590ab6a923e35a2ab8db6410a9d33cb84d1c48a054377e" +
+ "549774b25f50fbb343ecd5db095155cce9fb0c77d09752f62d4bbf16a770" +
+ "30452a75f6bdf73f7807d8f3a6bae16ad06b22175fee60549c22548de9c1" +
+ "3df35ef4e7bf7b66491a62b93c2c3fb0c5edc51f60f5704b56af30f1079d" +
+ "7c385b99f958ef8209e030e381d1ee8d67d3cb84f32e030e8ea2c1d0c77f" +
+ "d6b242a9f48707557c8682a08e1127f51221a55c733ab1edd00a9c2912cb" +
+ "36dde85f73b524e1a4f4da6414c5e4c18d9537722b2becc8a91bcc63f2b0" +
+ "9f32409c53c2beee0de6726dabcd6bf33118a5c23fb9c5c1810476efe658" +
+ "4bb6109c516b45e16b2f79f96755680374d82b91f2c519639a1815fd485b" +
+ "a3c00b46fbefeafcf25554ec5a6a5ae2da07c85b8a0f9fcde50263d9ed85" +
+ "038b2f7aadb9de765655bd201235218bfc74bcad6a9ddf4506167a649afa" +
+ "df400b85752d68a92b7a97f26b334dd77fce824862046b286a7c8e0adc36" +
+ "f713a252a673d4d995b268badf4bec8b8eefe85c25b823b6728582d35c4a" +
+ "60041114dab72b0623b99e2758f6a1e97365279bfba0eb1fc8952ca4f2c6" +
+ "fbffd9f5fd7dcad1125b18a796981b5ead0b6431141315898ace96f0d38f" +
+ "865698df8822ca7b65644b6b1f0a0f0d2e5850d4c93ec48ca3eba1b919e2" +
+ "4413a46d595ffa427715e499db3b7b9ab53c64abec7302bc737a5bd124bc" +
+ "da756abbca132f7f67e6989e09bfb23b497da31bf156bb9c69ae54588df1" +
+ "7420e8fe989f0472c8893b2bfe57cdae265a8cc7aeb39624167a567a6fbe" +
+ "bb1aa30c3dcfd14f2808a070994085e6e1fa79021e77c399f90ab1f995a7" +
+ "baff672cb693bd39b798b4c890b7d0a57978d6b9bcdc5bf3f4d205f8f24b" +
+ "2b43d3ae300a96971c9182be297618b9adceebedba1ab0f324b01d23d7e6" +
+ "35f009db3dbbc643c2d787567594bc639bfd78c4f3e6d948caf06f013423" +
+ "eb3c764666b58f886d5d28137c053c2a28535efcea400147e92ac6753574" +
+ "3b47f9cb48852abed1d057647d5b1c6f334eab1a813401fccd3dae332738" +
+ "776bb223e359f3c459b5c573ba64fa945bdd66c5ac0fcbd53b67032a7b80" +
+ "25f551e8d1fd2a4291bdb7941cbabe3a09765dc263e2bbb6db7077cc8fe6" +
+ "790d4bed5e36bd976d1e37dfdba36aafcdaa10c5f3ed51ba973379bcb8fd" +
+ "203d8b7282abbd271ecf947e54486e8653b7712c9df996a8ad035f41f29c" +
+ "ab81509f922c67dacb03f25f8f120cb1365ab3c1c286849c2722448ba9bc" +
+ "ff42a6b8a7a52f2c79b2bfcbdd22ef8a5651c18879a9575dac35f57d8107" +
+ "d6bece37b15d7dfff480c01f4461ef11f22228792accda4f7936d29d4c56" +
+ "cbba103b6d3e6db86e39e5f1bb9e9fd955df65b8a6e44a148620f02b5b90" +
+ "b2be9e5bb526d0ec75b1e723e94da933a356d7ca42d0ce8349699f730b8e" +
+ "59bac24a6b633759c88041d29399ce60a2ca2261c7eec1acb9a56e0e65bd" +
+ "e37653ce2cf7eb83a4d019c755bdc5d685b6394ecddb9006823182dd8138" +
+ "a1bf79a32d07a8e5e8ab221995c714e571b40bb255b79e328ab883542c16" +
+ "4899fffa16eb3296f310e302512352a864fd809beaab4169113027c6ccca" +
+ "99a92c6ce35c30f9449a3add70f10db1ed08078e8e6cbaafef630aab7e9f" +
+ "c8adb09c18e33fe1af3620d1e4d069ac11325e23cc18e5519a1ed249caf8" +
+ "ddba871c701f1287cc160019766988f63e089bd9bf1af7e6f5b9002e3b6c" +
+ "264d69a8bac16914ab55c418d3a8e974677cdcbea36c912e90386a839a37" +
+ "77b878e680c07c7cc99f42a7dd71924babf7fb0627d1f2cc60d9d390d1e1" +
+ "50d47386be6eefec9ddbb83b28fa7e2fd28cc3867cbe42d13b00545af8a0" +
+ "48cc07016ec79808b180e0b258c564739185da754f2e",
+ },
+ {
+ key: "0d41cb4ac25217feb20e86fc2490e8d2ea2e8225c051252a9395cc4f56e1ae5a",
+ tag: "42df9f9a59d6dc05c98fd9e9577f7176",
+ in: "01caba7a19cdb09dc0ec6c522c61c628eacf17ef15485aa5710fed723875" +
+ "2e4e8e93dd4bbc414e4c5620bab596876dfbea33987e568ddabf7814b318" +
+ "8210a5f8d70041351e4d8410840642a29cc8d901c25fa67cc8f9664ea5e1" +
+ "9e433eaff7c722d0258ae112b7aca47120aa8af4420d4412a10732551db2" +
+ "cd3e0af6e5855d5eea61035af15a4d0d898d04033809e995706eba750a7c" +
+ "ac07aaa0dc71477d3020f778d0347f1a8e37c18540deb9ae967e734c0264" +
+ "df0e1f52b0b5334805579ea744c8784c3ae0c3ff8217cd3f53cb747f6996" +
+ "f3d2147699799e649061b205f97f7992e147fb20f21ff862c6c512e95534" +
+ "f03075e8e52f162e0d70d7a259e3618474427f400f44f75198edebae6e40" +
+ "a2173257d114e1bb5a13cf419c821eb124d90e89a938d91f4d2e70dfd1ab" +
+ "60446f1b602614930a329e98a0c30f107d342281db25b8f8259933e14d20" +
+ "8bbd991e42969e8b0600272f9bd408483cddfc4cb8dfe7bc19be1989c7fa" +
+ "129d38e1078d094b82e0a845040ddd69f220dc4aa2b236c44101d7da7779" +
+ "9827a7b037561b51e50fa033a045571c7267af93b96192df3bf6180c9a30" +
+ "7e8c8f2b1d6b9391767369625015da02730ad6070df4595eb8099bd8e484" +
+ "59214310cb62c3a91a4fa8ac3b3d7b2017d4254fb465f0a248e1bf45819b" +
+ "4f0360f37c9a79d405e2bb72e5c25a1b4df192cfd524d61e1e8b274f2fe0" +
+ "634c73f0653c7c9e9062c9d081f22a8b0327897eed7c6e870f2815bbac8f" +
+ "585c1bd868759a98dcb5c3db2f6c53244b9cc494a56f28a9ba673167cea8" +
+ "b799f37049ee7b0772972b3a6603f0b80eddb58ef03f916106814d72f000" +
+ "250b3573c97c5c105910d79b2f85ad9d56002a76a1f43d9d1c244ef56d3e" +
+ "032a9bab95fe3bd5dd830ad7d7e341f28b58c0440658f7fc2ca98f157708" +
+ "1c647e91432cb0739d9acdbf973ceb9b0047634d695279e8837b04dc5357" +
+ "f013fde3c55c9c53bf1d817ec59a1b18ed0ac0081ed9bbb3bcd1a5d3634f" +
+ "50f7506f79dc6a4ebfa640bf65682fe9aeca68088e276937669250064de1" +
+ "c19ad6d5c697f862114d0f81d2cc52be831ed20d3aab1e41fe6f476b5392" +
+ "af4799392464c51394c2d1a8325ee2e84f1635d295ee663490e538eb338c" +
+ "7126a8e731ad5c0becf144c7a9cae5c6493350b589385de29e1a0ad6716c" +
+ "346ec4f0a31ca5ea35c59ab6b099f65d7f0b3d00925a1da1b5777c029aea" +
+ "9679e895d7100645dc83f81d82a6174beab2357f7888ea640900cf3ee67a" +
+ "e0724a123919d78e70e05288f67e5e69ffa6f345be8a96e58bbe260184b5" +
+ "ec5c0c1354cfd516ebdb8d420029137d41b029641959cc07fa7b4e16b39d" +
+ "17f36b2367057410a42e0550e9ec1dcd2df4604d52d4f9dd1140d57af08d" +
+ "50e1527dad793b6d649324de799754f755818bf10e6d1ab614958dbb24ac" +
+ "8e2c01270a90ec3df4379c3f509b5ef721b0fd4f91a1bdb8127ae4dc74d0" +
+ "75f6cd8bb28319d6f8e8d8ff64fb4a42d646e9365156c6bc72cc46e9cd1c" +
+ "f9e735549e3df9a8e6b5fe541948b126190117db71fd1d61ad84be0f725f" +
+ "20b99eb141b240326d399976c4f2ce5823d94649a9580e1e8820bf49184d" +
+ "fc34378a60bea89b12aca69cb996c17847b7fb517cf2d51f16d78e3875ce" +
+ "aa33be15f6a154004f0e1134c6652c815c705efc34bcf35bd7743d28f0a2" +
+ "77d82dea4709dab41fbfb4e0cbc118c17aa00808872f0edc6437c357cd31" +
+ "74a02aee61890464e03e9458853189431bf5df6a0ad5d69951e24be7f266" +
+ "5bb3c904aa03f799fe7edc7bc6779d621cab7e520b5994f81505d0f01e55" +
+ "96e14b4c1efdf3e8aadee866c5337c1e50066b3acc039c84567b29b7d957" +
+ "683cadfb04fb35402acaba631e46ca83dbdd8adf28e377ec147e4d555a21" +
+ "e6d779d7c5a3078ab72702234d36ca65f68bd01221c9411f68f32e16ef04" +
+ "99a20c2d945fa31b79d9965853d38ada9d48eead9084d868c6bad974b0f4" +
+ "0956aa0fcbce6dac905858e46c4b62c0ee576b8db7d484a524e951f4c179" +
+ "decfc7d6f619e86dee808f246dd71c7e0b51d28bc958110d122fa2717148" +
+ "77823242711632f6e1c7c15248655ced8e451a107707cec8c84929beece4" +
+ "efe5503d3c1763d0ab7f139f043e26027d5e52a00d5414dd98a324a8fc2a" +
+ "06a1345cbde747f41099c3377b86bbdc5a17c8f6e5b773a761f78573832e" +
+ "4359b143810361dedc79142fffc49ddc0b32f225d50d360ceec3920fb0ba" +
+ "0693b644ee07fbd1ce829e223a02794b197614061c4bfa46112d105c2b7b" +
+ "4efea448501d146dece44f6640d674d5749db498b32969de6e165e705a18" +
+ "2aa1f3d8e16892b0120337640d52c9bee35e5b4b17f03eaeb31205c8ecbe" +
+ "1ae1b110023016e40ee87370a65c5c20bfb00f100d3c6c1de6e4a1c90162" +
+ "f25bddbf300ed637330206788a4ff96903f971c9618493ad074412af625c" +
+ "ff9e0f8f183bbd5e96c1f28307e6cae8b50cc0eb1a3a8154e44e9de947af" +
+ "002e4d1098d6b0ee3f2e71a10d03eb444729c42461283f37be8af2ce81ba" +
+ "bac246a05c2c94efacc43f0cf9ff3df38ab6fc1648c796ae7026ea95752e" +
+ "b70873a6da59da10d8b5316126431c4a17289466e95dc739c061d7a4b13a" +
+ "450809479eef421bddcdade77a6df133410328c754af8999a09b1a5c056b" +
+ "ecbb6fc2c339586ab92100f46d2fa1fa689994b36aa70703d76bf7738adc" +
+ "f0589fdfa6bd215339ad69ed983f62efce0add5a63fe7dfe4bfa006ff16e" +
+ "0cc06d39199ad60adcae12b75ca98d764502a783373da3a41281e03c2037" +
+ "e1b3ca7f7eb60e2b67427e97ec72d36670db7662c6daa505701fd279f116" +
+ "ac0ef569471f204e1531c25a4ac3ce19b6f68a8994b6f89b5abf034a6507" +
+ "32c7fad4206eb4eaa7cd9a710d866bf3c3f13c16faa268ae0cf4f69be909" +
+ "bb9b79aab80dd25101d4cc813a48d3f38d870f10ac0b6768005aa0e69e87" +
+ "dfc0424deef06414c9ba6f498c93c41c692a7a6221fb5595b390a32c70e0" +
+ "2cd64471c797ee8a143725849c1e054ee2043dcfc0b4cb1c00be21a14be9" +
+ "2d9a07f1b4e975d4c86b8a5c1387e6c42bf393e078fe86d24612d497e14b" +
+ "874485a3cc922b5b6d91295d7b79ab8bfa1c7f64b51e761d19bb9da82a5a" +
+ "a34aa469699036b6b2c55e2b84f84942f10585027ab07e2e0e562e0fc3dd" +
+ "36047850ded84be4416e22aa41c7a2f7d4a4d8e3dd420d746a1d8d56d87e" +
+ "5133a1b4380bd9a89500fd6d7e68a1ec02eb9e79e4a13edfdde1273466e4" +
+ "6b0e6a75f59ff6175716629da52463ad21de27f40fa2e25a566eec4b2696" +
+ "4af3a717dfb0170a73144c0bd9b00bed67ad8c0a146eb5a055812d071209" +
+ "c9d530cd4f50a41488c2238898dea8bb36b0f1496d3ea8c4ff8e263b367f" +
+ "64977679e697d88e5295bd97ac16a0420850d1ead9621e25a3f58925c266" +
+ "ef5246488b1c15a8fe0d8ec4291864faa5a67b2388b7786f47b6d27e8fe8" +
+ "46f85f85163e54155ef95cea4901e712a44404a4d3f27f28dd961ce36b84" +
+ "f3856770f07f20a2ebd34d77405beab04ddfc09770167d7d6340f494dc6b" +
+ "7e4c3df896bd974730193b1e862b58d4a5938e6e4ae8897dba8812924379" +
+ "e54f51a71364d39f76e24fdf2c6c704479ce85b456558ca6947b8fd76f03" +
+ "78273f0a7bcd1d860ef1defe4eea8fdb81c73eda028d82fdcb2248582ac4" +
+ "59eb7698a811e6c5823be886410f6b8577ff2e8252343b6ea890016ae846" +
+ "01c5894cfb988121059fd9c8fbc1596da470a149404fc67baa15383d38cb" +
+ "d17ac107b4ff3c1ca4c76b7930de02b240e7547d39f4978e0cc1fa37f8c1" +
+ "012b677f07bb4df4486196e9b0beb823a3827585475b878e3f6f0a2d3836" +
+ "2c7d34f9f3c91ed46c39cec95c2a0b6f0279a03a00ed5035b0725c393849" +
+ "cdb1ed3c0ecbcf3c2ce108017f468e1c3d469c03e8231d4195344ced70cf" +
+ "daa667252cc1554dce8d0c54eb4cf4da62367d77d7dcc02f81e788ce9f8d" +
+ "d306ba1b48192359cfe92bdbea9980f87ea0677d7d2082205a436cf514e6" +
+ "fde5eadd21b13dc836ce33b5dfb6118bcac79ae00fbb16d61f00a923b145" +
+ "f9caa9f3a2c7f0104f8b052e390987e57c8dc80cd5f0358afb0111af1fc4" +
+ "e31f92bd832ad35fd2e0bdf768272de52ce0b152f74d43a8973ad516b3ea" +
+ "f5937ec8a236ebc86adeba610de0cf7168453111f3c983b64df07678cae0" +
+ "a75466ae15adfb127328e716448cdbd2c1b73424cc29d93df11a765441e0" +
+ "0eeed72228e1099bd20569d9d0e9e5a0b3c11d0002e2896631186483db61" +
+ "c1a0cb407951f9b1ea6d3ebc79b37afb5a7037e957985e4955979b91fb85" +
+ "61ca7d5e8b9cdd5b7ce0130a880d9241027b011fea7696b0c695d4949ca2" +
+ "d0cf22d44b9fee073ecaef66d4981e172e03ea71a6edc7144393bfea5071" +
+ "2afac137f091bae2f5700bfb073a6d57fddcba674a899d7349044a10aadb" +
+ "2e7f547887dd2f765f394de5dc9ef5dbf1eab4d869be8cb68aad8e2614ac" +
+ "37bbf21ccd5a832ee09fdd07ce50a580a2af36256b1046e646fe3dff6d20" +
+ "0c5110f1ad1311bc39b8114cd11ecdb87f94df43d4f6468932fc0ed892d0" +
+ "3d8f3db3f8323ebb29776ab7d260493a36700bcda668abd62126a8189e91" +
+ "df2d2970ef688d4e8172fc942e69ba63941a36b79ac546fff38f5f7d1176" +
+ "57612a662ea38134e1090c3e903c9adacdeefd3ac2a0467e9f5125058c19" +
+ "7b2260d2afad2b0e627a9ae52cd579ee27168065658089e1b83a2d8cdb47" +
+ "e08966e4ec0018e78c4d267f9575b8fea2a42de5c2d25356fe4b8c9cb1ac" +
+ "daf0d1af4bf58b9704cd4bc08471e3b9a0e45a5693433ede2eb1374bce44" +
+ "1f1811cdc7612d7bb61f4f34aea0a44757bbcc12a55c1ba41a7901eb004e" +
+ "689587a38e5b4df4574ddcc7b2eda97f6e480d7d39f45247ea3b03c90a93" +
+ "0dd168b65d52a59ce9c2cb4e860cc6aaa0ee02a58d0c8ba990194bce80fe" +
+ "8c34ba5693fb0943ec2cbfc919e534cc47c04f502b6c217c2f860d1d482a" +
+ "a016aa02adfc2bea3171fc4e27e2a262fd37b824099aa227fccca508f778" +
+ "b8c6ec7aaff1d15f6497753f439daa9e52060fd6e9e056e6843d770fb057" +
+ "6d9e2e782db4843c0c2c7f408a17376719a3c5cf9fa08f04f8a779885a16" +
+ "5cf93ce404be",
+ },
+ {
+ key: "ddbd5d6c5ebd61fa72b453dd849dc302c98a0f3e300f4768bf1dc698a3827dd2",
+ tag: "af608b71a353e63c64911558baa122f3",
+ in: "c67e2524b0de16483158a0232078fadcf611e4fbdb9e642e397b21222423" +
+ "cc2ed42ed34ffcb178448919ee337eff9d7d691f622e70fd3317cfd271df" +
+ "fe6a9d9b7e07db0d20813e2331164a654386db2ab06ae2983bf2460eaaa6" +
+ "3aa0171fb87afb82e85b40d95c8993b2039d32e9d38473dd13f41fb1ff1e" +
+ "261752ab004b221a4472b9b1a0e139f0c999f826a26a7e7df362b0611aac" +
+ "fa83c55cca2f7c0138d2c30313c2f6eb357278328ea6ebd6a5077947e18a" +
+ "a97c34b9dde3b6f2de4b83778ffcebc8c9cb58756691d5e2a3d15a759a2e" +
+ "5050b6da937a6f5551aec069a08027d60dd870d175d2a5b5f0b4f3143904" +
+ "7445c368a5c866370e9426abbc1a1c5a272b96731c4128aedeee93e8e00b" +
+ "b450601a6d31ea279b9450e738b4a47c0dc22d2d8ed5d44257f6318e0c59" +
+ "b951fb6b57746062ab95cd73c23ef0a5c000a7d14c18bfff172e59b6f6de" +
+ "aa61b81009e803eb05e24fb0b706870e18889a9180ac16a042d12dfff9d9" +
+ "1b88130f045d2342fd5ddc5f443681c31090459f262d1a65654c55251fc7" +
+ "d5a67bd2e62940ccd606f3e50700e4d1e992a3fdf0388b9ce3df9de6dda1" +
+ "5c1cd6b70622ac062dcb7ed7058872c00ff3df94032853927126cf6fa4cd" +
+ "c468d91c9b52dcbc272fd7ba920dcd3ea1e048af9c3286dba74d988ce9ce" +
+ "77174e25a87935352721dc23b60a9549322fadbe6a00dd1197dfa25b33fd" +
+ "9e5713afcfd0fae6dbcf27147fa58d995580d7e0a903c895752fe9819f5b" +
+ "b002ed752719552d0f3575312f2e618173a8ae7c147ca64a709053e5d2e1" +
+ "2f4d1ea337afa9ac4f9ba62760046ec1e48f4ed8f6df66786c9fd9f5bc7f" +
+ "9ca2526e1327b042f4657c405757690e190c91f260dee2dd3d2e6616b721" +
+ "e489c7c3cb828478a3d953b88f09904e7927cdf6dbd6a5419eeeb83c0be2" +
+ "51934a80dfe61e09442f0761aa2d013e10aeec3a32df204571ce8984a430" +
+ "9bbe30ccc91977790bf0305d2651ee450b749c3e7761534e45970e70a0a8" +
+ "473cadbc88f096970c275f188c9d2644e237fd50c2e24c1eabbf7578e80e" +
+ "6500762ac513fcd68cf6f8bb7a9d9eedadca059d9ecec07fe6fe7792b468" +
+ "9311861728dd482f087c28374cf9c5ea20b2c8630029e8485fa6fe518c74" +
+ "ef77d44eb7526ca764e50b5f34ed0f253a91fb2af6e59338e2af6e041e01" +
+ "084e1efade1aebb7d1b698ccdb8b4248ac89cd40d9517d840960c08f5e86" +
+ "88d8ba2b54889c1870d315498b70e0e9720f2c8c53a3377a8c0bd2d6a1c6" +
+ "f17c6ff847eb14def6855dc3886b99039e528b421ccbf6064e39263f8f3d" +
+ "340d5d20b1b14c264ac2310b5f3a0c6f0c1006d0d4f1a69af68d28ab447f" +
+ "cd17387e1fc98f164982a6d05dd32d6b4f0f1b04e40c6c6e0fb4467dd6b1" +
+ "0c5a9c92cc8c2bc97ef669b6d55cdd0aa8a15c46af954359165949012713" +
+ "4ea9f74181d54a300d3172c9f01db73288ef6a709c763a4891666d0baf88" +
+ "8531dcc77f0911412d096aef9033fa36b5c1ed283b8b5c109e45b5cde911" +
+ "6f3da2533fa0ab81929bd5783271d5501a9e4fce2aff9eb5a70a4215b253" +
+ "46885d7e4225fe34bb55b309a114a312693d60ccc61267359a8c2dd28141" +
+ "226e7cfd99f0f12c69df57d75dd790dbabfe3145f7fd1a24fa58e03bc2e2" +
+ "6ea19288af4929e5acc517d8f52a074745ff4644d94179eae6ba7d267292" +
+ "bbd2053167a0da9be5e4b6cd0a4200fcac5182d9957dffbefa857e662b82" +
+ "fc3a7cc32506e78030ed5c5d448d7f1b4fd854a735a0c50016bb85e6e716" +
+ "0f87527bca0de235f4b7dacb75be84919c15a5b8cf6bec035795cb67061b" +
+ "7855c2134c1b1bfa6affe04b7db239f73af6ea9c02bc9f7972b7f6400b6b" +
+ "838f4653aefc42179c21765e3ca7a5e96b4402ff544d4bc2332756a23500" +
+ "11241dc42ec6848afe127c00b9c333e69bb5a54ea5c7193e59ea22bd6d32" +
+ "af4f56b1bd2d5982ef7d9c1b02d7668525e4e81b68a400f7afc2653f0f41" +
+ "a03e11c7a02bd094830093481afbab96397245b9f37a568ea1c4ae248cdf" +
+ "afc87f88b1fb5dc300d8e9039af4e6e701b458ed3f32d693f2e869b76bb5" +
+ "1358cbbe5b5089013bf452734388a176cccfc1ae9b7cff603631ca48e129" +
+ "b5c9573d4e379547272cce8aeeeb407d3fc57f782a0eb5fcbd41e6fb13be" +
+ "7e4f1067cd407b42a6121b2969c384916ba2b32563e659f52aae09c8ce2e" +
+ "3c500fbb7e58be74cc1592dcfacd9f0d4cea1a90a18658147c81cccf6fb3" +
+ "078ed27f369e7646f551386a74e1b07074d93e0c1f298c761af46cdaae9f" +
+ "f4be86808b66d0e228016d27a3a77c843365cb847fddccb0bbcfb3b9008a" +
+ "1bacac59ffb0aa759a0568c72c556caf0ac1091431b574687c5fc7bd486e" +
+ "963e0fc3bdc828d988734a21070747c955cf8dba2df1c3a0ba8146cd58b5" +
+ "91b6d54712db67a9851b1607c8445bc97406eeb7488f5f85e547850d619c" +
+ "407f97632ca1801f52c09c2b314b4ab0f8e7fb5851fd60852f4666913ca6" +
+ "bc840c1ec8f8f06caefdbfbf02ce00f20b87b14ba9e651c80f40a31d0306" +
+ "403f541776075fbf23733a6b19e3b44d04b455b29ef8effa70cce0c59331" +
+ "7119abc07aa8c8d0246a760b0b36a3d87b244e83bae8a745b8277a531298" +
+ "f5d0283498a509c89898ddf0f7a7455be1f8a6889c46d323f1dd18c3babe" +
+ "1751a05f871f0639f50967afa46c19cb93d9c2a79c81e2436a7a62f225bc" +
+ "37c90698640f5b43673e1dc276de05ff1e29acdb4ace5121659db5f23c49" +
+ "57aae22f53e6f2cc935824fbd07c2ac87672eeeab895c3f06e09e178560e" +
+ "2fcfa7097f10201dfb8b1ebac08ca806c1b3ba3aff9284846a1a3beada53" +
+ "e9f7ade12eb89b5591f462b2543bb4090e081fee9fb53bbf821dc92d6b16" +
+ "fe820ab2ee4b1f6c0b6a6f19edb0bf6479e257fc73bcd60dc2261d0a4752" +
+ "e23a0be18abf355f3065177d8c3c14e21edc178d0abd1b39f703e6335131" +
+ "ec90cba3d9846cee7354a06c320a3f61b8a269abc7138831614f57ca6c19" +
+ "a4a621142889cd924bf4ffb82b57f871b854f3157e8874c22d43a5726900" +
+ "bafbb8f2260a1eba3a462e23d4def2ccf68ebaae8e52739a1ce67c039eaf" +
+ "9a6c3232fbb5a91d1e59a8dcd3798ba71345fbf83d09b83b41cc49d5ff5f" +
+ "2e809d2b1d5fbc1e7001ea76b9b2d8f896eb6609e2e1c5c562d2a6e74960" +
+ "2d67a0f6b43a201d5087509b8dc7b0440144e308c18ff8b96b607de2f20c" +
+ "6ee99bb05367a8b25947011889f724965a2b5c52c9db1e0622df9343c548" +
+ "d054699badeb15fc41055af0d79a2bfc1a5b4574634fa0dd9dd10a6213ed" +
+ "b6991187dc560facdc27440456a0a209fd7f5ee4fb350ae71f869723e5eb" +
+ "5338e3d1448bc993afca6957f4cc7b047a2c7c9593b7234725e66cc0eb23" +
+ "3824eb4cb905701cc522ec210950b871397c6c0bb3d0b839f2eb1a120f70" +
+ "36107246df4dfb2c24891bef0bd1dc131f2c9d7c295ee967e3184d963037" +
+ "fcc9e0b8c7011c8e04b4e70038150d34caab4f8c0230418cd2d8a91146e4" +
+ "4e11cf6707452ddc03d9b4e6380658135dfb48f62c0690ebad75167f4dd1" +
+ "c0df3ed555b5081a7b82616d9e501757c83c2193d0f640236d59f9c97a4a" +
+ "5c8bf532aea2cf5964ed2dbd8a70c01ca5c7677224cf2a37f3b24d8fe4ba" +
+ "91cd3b5033715de227de51deed15afb8eda9d2b9615d197b8f98322d7096" +
+ "79c5131eed48050fbe0145a9284e236605c25a4876e2adba42f4e35a8949" +
+ "3d59bbf44b3338d9d2e65a7d7ec6c863cd47cae9e23181b07298078a5e9b" +
+ "06a5c7e1059f474eb1a4247e8f02cdd4efdca67d22035b12abecf9b15982" +
+ "de4932a28e797bc4de38442cff2cba263eeddba0ab14fc706dbca04eaca1" +
+ "b4cc13000a10e35b32461424809b299798e4d8e66c92aa3181c5df16ab65" +
+ "9611cb625e895a8021af8c60960227d6f2ebeacb17b13536a5ff139734ef" +
+ "37cb67018ef9a410b856e6f6eddbe3f59b088d538c50a8f3f0912d06e47b" +
+ "88d773069aa759cc614e1f53cf6e572c127123d1ab56b79ee753a921cb22" +
+ "a60e4e6cae768c9966de4e2625484f2e990154da7fca84b6e6c0b59201e7" +
+ "fb8a729cb20b4c774381e84f1bd6e304543d952dc76ef741b72f3a4ca7a6" +
+ "ea7958b8b6337994ed82dcf988eb70f509610b9a279ab4d0f28cc2b2dd99" +
+ "3b8637a6be0cb4b5f67c79654c6b15e1b61120374ba9b974a628c547f11e" +
+ "52d72d39f8f9c5dbfc23a89f22d38984dd8d5c3ca72cd54e6adfe2b3d163" +
+ "86afdb50967846a4c311351a51e5fd322757bdb061d44c8796a61fa4db36" +
+ "793bc11984eac83bbcefb40d0bc7bab0ca81e7df3a7f58c6fe800396716d" +
+ "832acaddff6d72c8e19dc9ea838294ead800deadb6bc18d3e399fa76c46c" +
+ "5d88ee72a86a87399423b0578eb6e27d78156ea2abf6f08b5cbf747f2f74" +
+ "5301b694bfba84bfe3c5527acd50660eea5105a2644c1aa92f954a604fb6" +
+ "a1b3b2d0331497deafc3aaadc7040b9188a36cf607ee85a0655ae963fd32" +
+ "91dd58f8bb50b4e46dcf7c2957639bffa6b12d895660dc0323b7a092f999" +
+ "813380b820e1873c60d3e3038129c66d507862100a5d5842150869e7873d" +
+ "6bb6ad022350ffa3813aca26c80ccae72692bed9c77c9d4da23178c57153" +
+ "90b5f4505240a796ec9d10a7f280bd60a570b1b693453807707651fc0464" +
+ "03e4768965a6f42f112152942134f0a38c84137c7a6e086ef1ab9ad20d24" +
+ "3b93356b305c0996ab7d02c02c44cbaf8f7e60b8c0b8c9fece3f189b099d" +
+ "dbd126b7357c1c4ea1c8bc1ad93db91ea9bf043a4320acb60b502bec37b8" +
+ "6b2a5004b8225e549e613c6f83b97b7e4aeda1b013e0a442d7ce2f14e78e" +
+ "a94bab700c9ac0abba945e28f39fdadff223c4498cb204f01ddfcb450a41" +
+ "f32ae47f99a49114c6646a5cb103e9cd75f9d81dba417e48c4053e3b0295" +
+ "2267cd30589b0f5d993a5485a6ead1ffab9f2f4294c5853ba76383a326a6" +
+ "a42fb8b78948aa49f0f1f614bd0a3fbd2a58a3197daf2094605bd838285a" +
+ "1260f1265dca74aadd95652632335fd17cafcb73b202c3f0e5da836c2dcf" +
+ "2934f005935dca80154af43fa34c8ba440d1581b74ff17dfaca369dc9aa6" +
+ "734c03916d78e1b952691cef918fe033d33f7f4323cf724ffb8cd6c219bd" +
+ "046e9f268eb0601098e93daa59dde370e46269dd7c54891f71bee2829a53" +
+ "df86a2c7fb1046cd7c98fa21cd83597be554997a70acebe0b6e60f1f7098" +
+ "6f65adcae24385cb7102bdd3e01300ffd15d00f9764b3a5c51e35e5c9cdd" +
+ "da84f4b656fe514ec4ff8dcd774373f8a9103cf36abefe875f7084b9bbd9" +
+ "42e0c997ec2d860a4b622ff1a39a628582fd81f237d3d8f6843d26ac77cf" +
+ "bd48003e8e8c591ff813a9a897e3149ff0297ff476299d717e54d885cdd4" +
+ "4c3ba6ebf54bc7a1",
+ },
+ {
+ key: "b15578da1020f662ada0ad4f33a180d9f8ad4991b3720bc42a22b52625c7414a",
+ tag: "b0e4ad4a010afd6dd41ed82868cda555",
+ in: "6d2afb7a9154064341bdbb533f11990d4987e7c90fbfc0167c1e58d6efff" +
+ "6010f7ed569dac62ad37183b0d384519ebed0bf9c6e05a070b4858e6b846" +
+ "547ab5e45619c866f83cce83dcdab6a8a6c36b115ac832de1c6d433b94fa" +
+ "35803fa1a36f1ee114f8632402a027a74ac110394f32ec4006beb0057f09" +
+ "a94dada8bd0d1ca9a14b1f2efb8f526d79d6438bbbaac0ca1a43935627e5" +
+ "d129d52c06bf6413af07513bc579447eccc3a9406645c94dae59dab98d6a" +
+ "f92fa90fd4efaaa4bec466806ed401d2083cda587139ad7e9ee2adbb1dfe" +
+ "a88b59dd788b954a0f52c3854a3fffecb4bea83debbb2f5f8883e6415d3b" +
+ "ac1b872df1afe185468adc59364c173082f1dd6da9d348f5f5ba2d216243" +
+ "23de1f623eeec875bf31d12acec40dc0c1b9562826f3105cdad4c43cf45d" +
+ "829aa8b14012c47847aef7a2a6e3935fd972235f5d3a7ce4ad3582785393" +
+ "602e2e27329914021eff38ed2926c88acec1551f17a1b818fc1c3ed4b3b6" +
+ "6825d55bea269d710123b52e12ca9520a069d9c6a21df3a0253b3a4a6a8c" +
+ "dc226d667541548834da6bdbbdc165f39e40047d4b647c507d981be17b3a" +
+ "836063436241a8bb46b11a2867b621413c42d838e4578b72cc1982e34bde" +
+ "c303b5575ef4b8dd9fea8ed5bf69539413909d03461d3853b5fbf714a61c" +
+ "769569f42b38fac4b849104e2f2ac1dad0e388646278789f83e0b0511571" +
+ "019d3bfc5b03ca4cb5564e4e75e103ea1b6000be6588e27105d7cdc2d2f1" +
+ "f680ad34ef823ac4bd4068146e9997834665aec7dcc7a82ff28d85d52dd6" +
+ "9c18dd35f326bcf709f74df5981bb90ca8e765fef9f0698a19e12220b287" +
+ "24a6d9e4f4c7ce93f8ca9a126689ad1df820072557ce3db246cdf41599dd" +
+ "44ca841bece6c7869358005536e1189aa86b764e890ef90970d6e3831def" +
+ "fa890bf8692381123924e7d9df804fd770a0a30ee97d5dcdca302833efe8" +
+ "1d4b2505b17382f0b3429b38c41269ac95e36e9f5a1dbc6e6c8963741917" +
+ "02a23198decb4efe6809fcbeb5d0c9098a4c300155dc841610e55c8a6e27" +
+ "2a38a39de3d8ebf38a750af25836ffb1bb7822bb98886280f0cab6838c01" +
+ "cec57961bdc2e1bf158248309ff9294adcb962252b1c24646d132a3be2c9" +
+ "1ff82e8e101facbdb807826cc9d1840a90874ba08692e808c336c9d280ee" +
+ "f36a43a75c746fb864f85711e802546ab5cc3f8f117904ba1a85d6e4b729" +
+ "85122c5041891e16d55b93d6fc1b7fcfdc80ed3d72d55d64b8895bbf2f8e" +
+ "d188684e7e89afdc1e6a7ab9bd1d3da95d68698df2cdcbb2e1a4ae70e2fd" +
+ "dd4760f9e5cf4255eeb1e9e8009ab507395bacb8b2177e7c5757ad02baa9" +
+ "a96db967d20a150d2dd7f3081d90675fe0c82f94aa3cfdf6ac5585583901" +
+ "7a8e122170cc817f327a3c8ef44acd6e4fa81b73bcd0bcb5792eed470481" +
+ "152e87f7a20c3f7c69d5a8199bf9bb7c7269b450dc37a9b22102acaa8438" +
+ "134d6d733d231cee9522f7d02fbb37b5818ad3ca72df4752230ee11392ef" +
+ "8f8219be55202bc3d476f5a9078b32fb63d42bed4cda5ef90cc62467bf5e" +
+ "418ecd9d5d0cf1a33eb9a930e652ce96057fef40b65588aac67621d651a0" +
+ "9003dbc3925912e385296cd3b2b386a44113308ddf2af52ca390487eb20c" +
+ "716b76d78ad45129e7c285d918de7107ea8c3b0cfd9e73933b87c0b2b505" +
+ "cb4c95794f2ee6d6d43e2e76026923a0bbfbc3bb22df9ad729452283ce62" +
+ "dc9b26684fd45e07650581afd73713a708869a069c58b599ab478974f206" +
+ "dbd3e4e563e346ff1881723c5fd440bdf9f70f761c6f746113397d7c04b6" +
+ "b341d7e44de7de0aae79badaaef5ed372ef629dffd52926110683ab2d4da" +
+ "a4be83eb86c8700703a660edd5a5029f66f1581da96fe1feefc970ab4086" +
+ "a83ae02e959821967bd27b3b629652f5bc3db2b7f1af674f9f3fb3a788f7" +
+ "88e6dc1722382971831a7ed72502f85b25888c1534d81c0a4f7351ecc40f" +
+ "4e0412e05718403fae5746d313a78c80ac297f1391ad389070410e1330a1" +
+ "b07d683d1c795bda74bde947f2cf0dc9638b5d0851cda27df030403816dd" +
+ "3b70f042888c9c192656cc4b9fea10b81b5347900d9199c8f0f47d42f2ee" +
+ "482b68acfa5ff47d9950c950a926a497d94c6a796e0b715416520bd6c59f" +
+ "30217718d5f1d7bf7c24039f6467214ac8783cf011b25c37c67dfddde426" +
+ "40afe97f94879f4586954737b86701b32d560f08caec3fc45184bc719c7c" +
+ "5bf699074fde814acae32c189158c737665a8f94637068322f0c23ff8860" +
+ "f1b1c1bd766440afee290aa6f7150c7adefa6d72a738cd2268da7c94788e" +
+ "bb39002e9a328a51f3a92dc5c7cd9e4faed5702d3592ad16217c4978f84e" +
+ "af0fd2c9e4c6f4dcdd9112c781eb41a9aacb0f7935bb5c92d41e67cfff6b" +
+ "991ccefbd667ffeded1de325da50c33e28e2eef2f636c9726dc5bfe753ee" +
+ "c7bb6e1f080c89451f81bc8c29dc9067ce83deed02769714fa9bb477aca5" +
+ "c09089934674a0cc8e4b2c3136b2e4af8040cc601b90a4dec898dc922ca4" +
+ "976ab5ae4ac5af93fa5b1854a76ac3bcc2090bdeaa49ec4f319cf7c7b674" +
+ "6d8e617abb3361b28b27983dd1b139ec4f5af7e116439d7ecb16534817bf" +
+ "264dbd8f59e80b443be12c17fa013c7f4d029504c9bb62b296c2326f4f49" +
+ "cc3201b70ac3f62abb683c630179594a6d4cf30fd55b163bf8d01986bb6b" +
+ "cb7050fd527f095c45661920268e56f760fee80a29c9d37b7fc23f608710" +
+ "1e723038e64ee1b91c4849d69bd95fc9bc24fc4a234f4855f2a203e3f699" +
+ "c32698585c83781677739f2c48697c93b3388dcc64aa61f01118495ded33" +
+ "21ef9a1c949481f96005f8d5b277a7d6a0d906ec304cf4292df172e72d20" +
+ "29ecdeb65f06267a605f376804bf7bc5b82d5c8facfe7e41dc10806d27e0" +
+ "bcc5a341d80b3c1532407f75088716d732632cd88b0037f0d829bf385fec" +
+ "b52a202956489f61f16b0f4781bf59068b33d7330571d0b4a6ed91830258" +
+ "e1220b308784fa155be9bc821f5c0009a33802fa66dd66d1dde997dddd97" +
+ "873ddf65927dc1be979af2b5f110eee627dc1e210326ac20544a757ac168" +
+ "1823f3dd04b1ddc4bf96677a0a87633994e7af2ec99b7d5dfe44c6192be6" +
+ "a6e69d17b074256da3947808fbf68c7506a7e2c99e6b64d1ffadbd6285d8" +
+ "e7e032e24d42dde0594bf03fd550be05e5d66c91a660cd1ab7cb1f43fa9d" +
+ "69885203a7aee35a28f117427d7ac02b742f53d13b818f8631081b1730d1" +
+ "5b4e1e283cc8e5c4fc3b4652fce05fd8db821f99fcf93e6842816a549791" +
+ "7f6c49cc53d733788b2fe3c687de58bfe6153c70d99380df1fd566a7c758" +
+ "8052c62e73340d6a9eccd2ed26b763d518f3a0c4d6362212fbecebb4ffb7" +
+ "dc94d29944fcc4ab37725b105aa7571f364146782356d8ef056a0be93a55" +
+ "0c890df8fecc178776fe40703ad1bd2443d92c420be4306d99686592c030" +
+ "fd3e2230c0b48d8db79002e8a832ef27edb53a45532955f1171203d38414" +
+ "b4692e901e9f40f918528fc494430f86cf967452f456b01846ac6a383fc0" +
+ "de2243c7d804e8643aabcb78e2653b145f400a999670217c8da43bbb9c11" +
+ "e074176424be0c116c304a420120138e901eca4b12ce68fec460b23bc0c7" +
+ "765a74fc66cbda0e503e7b1baf5883744e468c97c5f1c4b0acc4b87de9f1" +
+ "4b537405dfb28195439d1ff848d9cd28a8d375038ebb540a9075b7b5074b" +
+ "ebc18418a370f1d3ac5d68f5d239513002ad11bfc2b7ff53e2e41ccffc4b" +
+ "0503acc4967c93ae8590a43439b5e7987d10cb8d1957bd9ef717ee3d12df" +
+ "5d6736c1d8bd8da102337a94b7d14f830f6c403cbaf7925a8a2a7af1311c" +
+ "57224967a38f6ca374013a9819c55fd2e2a5fac4f2490be5b059f4cd9c60" +
+ "2d62f80789eb8d9ab893c7f44a4945e41886af218179dfa754bbb59aab68" +
+ "13b71d2202eb8fc8a425625d21176a28a620e21bb0dad820c0b7051ce8d1" +
+ "3a33f3af0958bb6cd89f9d6414ab00ddd1d2f9fdece9183d0c05fcdfd117" +
+ "10d250e4b2029e6992a88293d0457e73e5b1b6a1aae182c69b9cb664992f" +
+ "073595ef68117026ad7ea579a4043cda318931eee7b2946a34cdc7c9755f" +
+ "80cc79a2bfe3ed9c79dc52faa5126b824868c965eeb37e9e4e6a49600f3a" +
+ "cce93c0853b546edb310dcd16a5755f15b1098b2f59dbd2d90e2ea8360ba" +
+ "f12108236e854465456598ae2f7bc380f008f2e3cd7c98c87643cafd7c36" +
+ "d40e2597236428d46aa5b260f84b4212d5e26804086adcf00363ce4becb4" +
+ "9b57eb2847b2f18ec82c99714ad4ddfe4ff3bcac1d0fcaa32660a1dccc68" +
+ "5bed83254c8e2ea0ae3632a70cfbcbeadef922d78a006d43ac7ab1f8a609" +
+ "c6e0ebc3ca6bb8430f1a562f41010db74b9febf931ca794fa08d1bc17780" +
+ "532ae76f25c4ee679d788835dfa4e70ca154c9e2865c3750ffe7b837eed1" +
+ "972be058fdf2bdb3eb301867bb132306c7aa237f6771d60bbc56cf31cb30" +
+ "32a87204d454542de747418470025ab84935d3eaaca01dbbdae9ef6b5d3a" +
+ "ca62ce9f871a3e1272b2b671582c096a349c00f32d742ddb17993994d8ae" +
+ "fc178cbcf9abc03114ff2bf7db8f757c63d6898faccd822f5c2e9a7570fb" +
+ "9cfff148570888be24ae42644c1a5bebb6f6287147a4bcc01c7675be9e4a" +
+ "897519dd3132a7cc2e778f8c90d23dc8073f6fa108d7ef82d561794bd9d5" +
+ "f1faa306334f338ac3ba99c853f79c24f7048fa906fde87d1ed28a7b11c0" +
+ "66a3bb98f8d21055aaafdf7e069b77b60b3d5cbe7c5e4379c7651af955cd" +
+ "82a19a09caf36becb6cd3fe9e12f40379941542709991066df21b7b12dfb" +
+ "2416d83fcdc33bb583e3b42f24f53edf8dc7c579ad3be831c99f72bf9fb7" +
+ "a35b6562e824e039e6bf1adc8f5ca53846de7bae11c4317e696d887df33c" +
+ "525f0a9c01fc29f2c26c90b85fe82ed8bd50954cd4e9ac7c85c7f3efec75" +
+ "da1da4ed173cb695cee295190527edb3cb06c5dbdabe0228cc60b6455153" +
+ "76244f27aa56da2db10f2659090137ffb82c57233c833e0bbf22d6f647fb" +
+ "97b3652d2888b3ab08010b8e8a6967d560b747757806736dc98b78226634" +
+ "f1eecaa4a2e23ba36591acb5737d735c5bc7a2e36f1a46946927e061fdf7" +
+ "7a3b68ef582c26b01f5aa9a438ecc26c6941221d1590c838072f9e471fe7" +
+ "fd59dacb0d092d40d76ea2f7c6e954a132a015bd4cb31147f3ebe4518322" +
+ "916438a62836ac85a4cf4492190a85bcc8edb37e38b99ea552d749c30f74" +
+ "ca20c298165e8ed02d4671e0b41cac3a32a345b9349ad22c2a4bb2c16a4c" +
+ "e0613ca0f0518759f7d2b33cfad2fae764f410d4d9ff8a76ae02a8107e7e" +
+ "01d9cd0552676b85ba002f19c01ad5f416d1d08bb84fec7c3555b098dbce" +
+ "48e1a5d847895e54db9c5b80cc22d5b87cd41a1a94be102bdd45a3cda5d1" +
+ "181e10446d213d6b3fdc350d486d2011d705c5f16ccf7519065c47bad7d6" +
+ "89c71e5fdf9d04bfb91eb1f07fa0f001009c1d4b1f6a116a570823a8580b",
+ },
+ {
+ key: "392468efccff36dade31fc1c62eb38bb61394fe448def9d9d9beec2413ddb418",
+ tag: "e1122e7c8e6965b90addbd46d8a548d6",
+ in: "6a13d37f0ec933194c227351f4a19b507d93465b1f3e88dcb5f1ed1262fa" +
+ "58ea99ff31e6fc85c39c04129fa69195b71b2060122fe618dd9430a63f97" +
+ "54b52a80b3cd099f248f91a468bae211a27bdb47ba005d29881ea5143a82" +
+ "967c4c30c9a4f0dba1a4975e6407fe296d40023a00efa06be763f2d73d46" +
+ "a2901ae28b3d8ce18009a462e223b71476d7b954c138e177d15a390847de" +
+ "96a7f7fd0598748e86b0f08e64d915e67c7e3cf936f3dcd60edebd36e2a1" +
+ "d65b6ac29530c48ab3bd52d45b4f938a19b9b31e2911105a8561600d5377" +
+ "905a67112ec28025aa680350ff85b808c5b4c98b7b9567d03f5ed3911ec9" +
+ "365a8de4b15ca62adaa69e5ba710eb1756a346016c67a297d8624f9f1ab5" +
+ "b3fbce98b141049f0ce26c85d2f8a9cc6ca8ab6c6e148be968931430dcc6" +
+ "2bf58ea9698ef52a5d271cf48e6748ac9e04bc7ae7da205a1a7535478322" +
+ "d820eca146cedf4b2f9aa9fcfd77ab56a7276977401dcc1f96baa1b607e0" +
+ "256bd04ec324ec67a4313e2d5a53d3a3fb5332927929b20c63bde805f637" +
+ "eb1050fee2a152a0405634f55c48a59fe370d54b2ab1671dae2c7fd92243" +
+ "10627808e553127c74f724362b4a6ee49b697daae7df3ddc5d2ed9d6befd" +
+ "77fb9f68fe3041f6ef13f46f34ab682ab8563e8996344f82b2ef006a8d54" +
+ "3dd9c1db4979d7da97bda45e722065f8a238f0873217b783a9a629a12b3a" +
+ "4de437445039997bd243efbf5e3b6059b9459d395290efb9081c632fb694" +
+ "81000dc74c395cb507422df181aba20f776ce3fd8765ac485021992c98b1" +
+ "67c68805662cb4356a0ee7ba6bdae51ac10cd06bb5b2f3a72841c714c8ed" +
+ "bc56998fe2fefb9bf69e172fdf54b2ab138ae59372c52a67e93882a3000f" +
+ "d966992aa2250c6ff93e9cac89645d70625d79332ade5dab7eb1adbe7dce" +
+ "5a013fb65ad32fe22ed16fb9bb35eca1f37a0433c320e8752f8fc4b7618c" +
+ "5e4df2efece832e259ad98b895c474e47d0e3fc488bea8f717a17de0dcf7" +
+ "597fb8fe12e62246296f9a887dcc3a700820c190a55a4931a7d44bd3bb2e" +
+ "ab6c8a8126f1be93790cebabc1d69e01796e6cc80e7c16bbc82fb333fb21" +
+ "c774ab7db843242838e82d8e1cb8ccab385e67a4271fe7031d74b6e8edcc" +
+ "8ed585d1c05a365c7665899c1dbc561151d3b44bceace77c4f53c0e0f6f7" +
+ "74d42f9ad3e56f1c2a8d53879d695f895690afb4698472a3d52d67159313" +
+ "133c87823fe0500eb68fe286f8b9a2f59f12785d026dc97bdbf793c7d1eb" +
+ "155f1f136aae66c256583e987f718afbe733e0a5ce30d021493fb84e2242" +
+ "5b18754d126235ef80335004fa84f88361a584753df409360cd8bd45bace" +
+ "8f48156bec66577bf2c685089f5ac7e7ec76c0df068fbaa47661f8517f92" +
+ "e14723b3b278f151816537a7212c96bd340a00c15c9c9bc9a2a5d163655d" +
+ "84b38073e2be9217cad97d362d89d4baf3ce0a8d8562f19a8c97a9aaf5e7" +
+ "77d60456360ffb77b30f177d2809052020d141697ecf9cb65f42b9190caf" +
+ "6540b2c82f6e5a8482934a6a1a5711a8c24546cd8ba432068404eae5a827" +
+ "2e09efc3c6037af4feaac0a46329229b010ecac6b9f077a9b076bb6d9ce1" +
+ "38401eb38d124baa11507a994185295020bf9b754fcf78430db9253f5929" +
+ "87c46c0f8589c4e463b15a3840b1cea795e24cf6b20f29a630136e0589b3" +
+ "8dd7fbe5ea21da72c88bd8e56473586822aa3765660a45a988df9b8eb8e8" +
+ "141939d3e4cc637c5d788064d40a9f7c734e43fdf8d7189a5d76700d9743" +
+ "fe0122944663afdb88c5201318ca782f6848b742ddebe7463fd4a32280ac" +
+ "1cf8311e9137d319de05ce9cd85abab24c5364041c14d3b4ce650400498e" +
+ "122166eccc12784b7ac3b262ac0b198ffc26eeed9a5da5374f7a2a53c87a" +
+ "78c217ea1fbf8d38f62511657b73109f31691aef14d82ce6e1010eae9e6f" +
+ "a419e5c1c16c0cc70651eb3374c03549a1bc7d3ed42d60f886102c798dbc" +
+ "ba56f0a2b3b9b412530c35f5f7ed06311ee14571f9c26ed9c81ef38ff000" +
+ "2f5ef3aab7351e32049a6ef8f48a43da1d84402d229df513dfaf1b2e4043" +
+ "6ce68c70ebeddd7477c9164f0dce45a6fc5de050f52ec269659d5854bcae" +
+ "f7762ed7400713c27a4d523eaf8c136c4a1ca00b9e9e55902daf6cdf8528" +
+ "c22ca1f2fa7ce87902d75a6850e1a5a4592497be1bb401878f18b189b0e2" +
+ "c59d10705bfabde3cd2da01eb452006b294108d5d42e88e9e15424d8cd0b" +
+ "8ab43a6c546b3dbf52e47b59cde6a3e417b0395220b6d63736d429da3458" +
+ "9a2524f1629320206fa7f1d8a041e17222c4a5814561937e1030e6375c77" +
+ "9dc988bb928bbdbe2c2eb20111639725d82b5d7192cd3e4acc27581f0ba7" +
+ "286cff41f97aa5a52ea0083de5057fd2ba985aa738e4d03fcf11ebab1d97" +
+ "e2ac77d1c2beb8799150a421a07b3777d0b850f24194b8309135b13da6c7" +
+ "e38653a711e407a1811290fbb7bc15d8b12efc6916e97ead41e042a44721" +
+ "e9cde3388073d921595bcddcac758dc675173f38242e65e4a284aaa7e8fa" +
+ "6adddaf00bc46428ab2d8601205b8895bcedfc80ca0aa4619ed6bb082ddf" +
+ "33ec04fa5d417f33fcdd238c6b11320c5a08f800e0f350b75d81e3bcbd15" +
+ "58a1eab87a3c8c2ffd7ba1d7e754e607cf98ba22a3fc766c45bd6f2569b4" +
+ "84639e6611714119d188a24a5e963089a16ed34e20b9f154cad8ac6031dd" +
+ "7a3a885afc2ae5e003ae8d4e4aabdb3e51dfc423b8cf4ed9ae2010072cbb" +
+ "b1108c7da1ff075e54ed827a0963ac5523ecdf3fc5eee7b4d1a6773764ec" +
+ "5c30f41690523fd70d895edb7ca6a1806d54240c4c7b43410da73503a323" +
+ "90d9070ed30da3a2fb5eccd40d083be7cf8bf40b4279f819cf795b6f075b" +
+ "5a67a10a06a6076d0d83c72efea05f244901c4b5fd9eb380432519311baf" +
+ "8c81f6325df4d37ff4d30d318f904ebb837ec76b341dd00a8f247cf0bbe9" +
+ "6f3784dc8f5feb344958fdf1a9ececb105f8770826db1f17a5281e997951" +
+ "d3c60cc28fc3e66ffeb5dbac315f98f6d240208043f28dee963d843e68ab" +
+ "57d847f76ae2f96ce6e37f377ef5dfef2176ecd7440ce4dadcec2231b606" +
+ "e4a80420fb3ed135640e1f05d6bd58b8dce062dd7d36b885d424f6318e5e" +
+ "a0753efbb33bbc7360d2b5dfab3ae0d5e000b8d31f2ba0f5fd8b34f96b55" +
+ "28fff35e769461d0f03cf3bfdf0b801dcbbf2838180cb9b108e06c353e3f" +
+ "0b9ef61678cfed1ea37ae76bccb5ef5957ac2c8e8f4794c8145a15f1cc88" +
+ "bfb0881080326c481b373c3bc9b07a9b60a0c8bd5fa4f6f90145590a5227" +
+ "6fcc0ccc2375d0ccb571d414d1b0c38b4e02c39db4d701c5e25e90785ef4" +
+ "d26f35edd8c4b96455bdca7245cfefd9cfbd2f319615e5fdf07bb9564fa0" +
+ "44bb35a58391d02e3927780b4076bc0893dfcb4b63a32cd7a541a4a8c253" +
+ "0349c6e96e378dbeb66dedf87d813d0b744452c1c4088507dca722193827" +
+ "9e2dfa24e4a409de494acf654f44262db9206a7717fa434ac4fdc6a6eb5b" +
+ "1fd5a193b6043bc4327c8c09fd6822eaa9df37bbcac1077754a295621601" +
+ "267b68733b62dadc2563f1700af180141f29899e2689dbbe9745ba8477f4" +
+ "352921900b403a01c9dd042a8c1b0e0489959fb0b0a8431c97b41e202204" +
+ "212ebfa00c593399dbd14d7aec07b8292d2e40b48f05fcd54a15da4a24d7" +
+ "2759e409f4c7b5b98fce4abac6c30e4872d92efa1f96479ec30f21699825" +
+ "50fa60584f5a09051a00f8e7dbb3853e66ca3f05fbfe43bef9b120a25a01" +
+ "eb436ba8ecda715201eda72e517d628f883386c1503aa8b8e75610f7155e" +
+ "9f916335ab6d6f0f9589b6220cd2b81c2c937dc065d3d14a7df8cc916cd0" +
+ "0ce1bb53fd9c8974298d3bd316f3658aa8cc6904f073a1472149e4b08c64" +
+ "5e11abe0428ccb6174df2103edd735965d6454b543d3f01410f77053f65e" +
+ "c1d1aee56fdd3af23bcd4e1a7fcc4e600c4831007c33fe5f0c8300f686eb" +
+ "9b4d1e4f08fe4ddc8a90be14dc3a5a88ff96716509341d5db24c0d016863" +
+ "998b1859c5021df815a6f1ca9845f1a8e99dbad132b406227c5897a1bdf3" +
+ "e698962f799133ff4429decbef6ce036296facf38e4812fec102b76c6d30" +
+ "beba1b70722254fafbc471096153478c971db7d96263660209265cb10f13" +
+ "b34b5fd55c4abe818a5f9715d8a85094e2946b7a001b47f629e26c636d86" +
+ "4968ad2ab616dfe28840bd60b4b9855c8dbe1cb873fcbc4577b5fefeb8bb" +
+ "4832039867dc35db9c036c83bc204396e3474ddfe806c77c65c936f488b6" +
+ "7c1028739562d7bb055d21441af29ae2921290e548dccf8a56021385422b" +
+ "15da6b232b24151309a75a00296d11aa1952a1513110b0faa93d1d8cd9ae" +
+ "fa9f1c59377ec9165b2c9e07cbde40db7b81bca6d58fc28bae8f473cd0e9" +
+ "a2420e0b943a83d284108626c24ac570b1d6c1ab971e71f43fbd6c00e171" +
+ "238141a6dc987a60385c3a04dd147a2f8e80dfe727b104c0fdd80b326f59" +
+ "0b9f86fd7b2fd1122a390979889eabd803ab57159c8509a1443eb6789382" +
+ "090a770ae4eba03306f96e50e19a7d44c584ccc230d104548946efca4520" +
+ "d61de5f473e2f4eada6c8ce9c7ee975eb4f63c0483cb775ed7d3cf690a61" +
+ "7d6656d683a8512707d81ca5ba176a42bcffcfa692129f292607d2a47536" +
+ "ccaeb464c9272d6f3816074b712af602470088b253deba18771e5f67734b" +
+ "587707cdd06f35264b2262fd253c25b5d38ee7db287610e5398062b7a34e" +
+ "6e4cf7447d00873b930ad148fd96f0ab18771bc468b874bb109924101c84" +
+ "c4e239ecc7687d875e4d94a1a973620ca61e35a872c2e2e61a502169f1bb" +
+ "4e5ff5fa2bff657be6195b3e2c7151a52fc0096d98e7f08f5a98f570aee1" +
+ "7b4275f1356e87e080ce0e1b9bbabe7dea48b5903bc390ce23472ad64a89" +
+ "41c3247bfd23ea90b2dee09085571bad85568040105e098f993bb37e43c3" +
+ "e6d511171c77cfc450570dfb9fc6a3930ef43c03f8213f6203d545d791c7" +
+ "d3fa42d5dde1655038d35c5dfacc12e9dee24fe833977549eda68ae8b508" +
+ "be277e743921b584f9dfa0eefbd8bf3c23f51efdef7f7487001d29e8097b" +
+ "ba63289cfca743023d1668555a46fe6d5b7421377414df1e9ef135480622" +
+ "22e2e9a7baa618d88f407517f6317b6a0ba3384ace16d68631d59ea169d5" +
+ "092d20afc1a481b82be5e734bb092953a0a94702bae1a0f48d2a22b9a05f" +
+ "f64493b7b2e984f27582b1eb937fddf8512c49830435d146dcc291a4118d" +
+ "5dc638b99cdcbcc5860de7a92c5b13cbd1e01e051f01af40afe124346320" +
+ "d3626bf9d8f7850744e032a993c276fd388718237740c6caf260fca60b8d" +
+ "d846102e3262b6e05ceca00c6affe938fac1847350865fc858d3ddd1d130" +
+ "71d1221ce7c5d575587fcba580e544b74d877ed5ca92763ef0ca0d7bfa08" +
+ "d57a0216b2a01a2b9ec74b8430051e0074862b7be25b6766ab520f2eb75d" +
+ "eeb979c28f03795f6f1e4b8410beab19a20febc91985b8a7c298534a6598" +
+ "f2c5b0dc5de9f5e55a97791507bc6373db26",
+ },
+
+ // Override initial state to ensure large h (subject to h < 2(2¹³⁰ - 5)) is
+ // deserialized from the state correctly.
+ {
+ key: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ state: "0000000000000007fffffffffffffffffffffffffffffff5", // 2(2¹³⁰ - 5) - 1
+ in: "",
+ tag: "f9ffffffffffffffffffffffffffffff",
+ },
+ {
+ key: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ state: "000000000000000700000000000000000000000000000000", // 2¹³⁰
+ in: "",
+ tag: "04000000000000000000000000000000",
+ },
+ {
+ key: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ state: "0000000000000007fffffffffffffffffffffffffffffff5", // 2(2¹³⁰ - 5) - 1
+ in: "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff",
+ tag: "1b000e5e5dfe8f5c4da11dd17b7654e7",
+ },
+ {
+ key: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ state: "000000000000000700000000000000000000000000000001", // 2¹³⁰
+ in: "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff" +
+ "ffffffffffffffffffffffffffffffff",
+ tag: "380859a4a5860b0e0967edfd711d37de",
+ },
+}
diff --git a/local_crypto_patch/contents/internal/testenv/exec.go b/local_crypto_patch/contents/internal/testenv/exec.go
new file mode 100644
index 0000000000..df5d3c20df
--- /dev/null
+++ b/local_crypto_patch/contents/internal/testenv/exec.go
@@ -0,0 +1,120 @@
+// 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 testenv
+
+import (
+ "context"
+ "os"
+ "os/exec"
+ "reflect"
+ "strconv"
+ "testing"
+ "time"
+)
+
+// CommandContext is like exec.CommandContext, but:
+// - skips t if the platform does not support os/exec,
+// - sends SIGQUIT (if supported by the platform) instead of SIGKILL
+// in its Cancel function
+// - if the test has a deadline, adds a Context timeout and WaitDelay
+// for an arbitrary grace period before the test's deadline expires,
+// - fails the test if the command does not complete before the test's deadline, and
+// - sets a Cleanup function that verifies that the test did not leak a subprocess.
+func CommandContext(t testing.TB, ctx context.Context, name string, args ...string) *exec.Cmd {
+ t.Helper()
+
+ var (
+ cancelCtx context.CancelFunc
+ gracePeriod time.Duration // unlimited unless the test has a deadline (to allow for interactive debugging)
+ )
+
+ if t, ok := t.(interface {
+ testing.TB
+ Deadline() (time.Time, bool)
+ }); ok {
+ if td, ok := t.Deadline(); ok {
+ // Start with a minimum grace period, just long enough to consume the
+ // output of a reasonable program after it terminates.
+ gracePeriod = 100 * time.Millisecond
+ if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
+ scale, err := strconv.Atoi(s)
+ if err != nil {
+ t.Fatalf("invalid GO_TEST_TIMEOUT_SCALE: %v", err)
+ }
+ gracePeriod *= time.Duration(scale)
+ }
+
+ // If time allows, increase the termination grace period to 5% of the
+ // test's remaining time.
+ testTimeout := time.Until(td)
+ if gp := testTimeout / 20; gp > gracePeriod {
+ gracePeriod = gp
+ }
+
+ // When we run commands that execute subprocesses, we want to reserve two
+ // grace periods to clean up: one for the delay between the first
+ // termination signal being sent (via the Cancel callback when the Context
+ // expires) and the process being forcibly terminated (via the WaitDelay
+ // field), and a second one for the delay between the process being
+ // terminated and the test logging its output for debugging.
+ //
+ // (We want to ensure that the test process itself has enough time to
+ // log the output before it is also terminated.)
+ cmdTimeout := testTimeout - 2*gracePeriod
+
+ if cd, ok := ctx.Deadline(); !ok || time.Until(cd) > cmdTimeout {
+ // Either ctx doesn't have a deadline, or its deadline would expire
+ // after (or too close before) the test has already timed out.
+ // Add a shorter timeout so that the test will produce useful output.
+ ctx, cancelCtx = context.WithTimeout(ctx, cmdTimeout)
+ }
+ }
+ }
+
+ cmd := exec.CommandContext(ctx, name, args...)
+ // Set the Cancel and WaitDelay fields only if present (go 1.20 and later).
+ // TODO: When Go 1.19 is no longer supported, remove this use of reflection
+ // and instead set the fields directly.
+ if cmdCancel := reflect.ValueOf(cmd).Elem().FieldByName("Cancel"); cmdCancel.IsValid() {
+ cmdCancel.Set(reflect.ValueOf(func() error {
+ if cancelCtx != nil && ctx.Err() == context.DeadlineExceeded {
+ // The command timed out due to running too close to the test's deadline.
+ // There is no way the test did that intentionally — it's too close to the
+ // wire! — so mark it as a test failure. That way, if the test expects the
+ // command to fail for some other reason, it doesn't have to distinguish
+ // between that reason and a timeout.
+ t.Errorf("test timed out while running command: %v", cmd)
+ } else {
+ // The command is being terminated due to ctx being canceled, but
+ // apparently not due to an explicit test deadline that we added.
+ // Log that information in case it is useful for diagnosing a failure,
+ // but don't actually fail the test because of it.
+ t.Logf("%v: terminating command: %v", ctx.Err(), cmd)
+ }
+ return cmd.Process.Signal(Sigquit)
+ }))
+ }
+ if cmdWaitDelay := reflect.ValueOf(cmd).Elem().FieldByName("WaitDelay"); cmdWaitDelay.IsValid() {
+ cmdWaitDelay.Set(reflect.ValueOf(gracePeriod))
+ }
+
+ t.Cleanup(func() {
+ if cancelCtx != nil {
+ cancelCtx()
+ }
+ if cmd.Process != nil && cmd.ProcessState == nil {
+ t.Errorf("command was started, but test did not wait for it to complete: %v", cmd)
+ }
+ })
+
+ return cmd
+}
+
+// Command is like exec.Command, but applies the same changes as
+// testenv.CommandContext (with a default Context).
+func Command(t testing.TB, name string, args ...string) *exec.Cmd {
+ t.Helper()
+ return CommandContext(t, context.Background(), name, args...)
+}
diff --git a/local_crypto_patch/contents/internal/testenv/testenv_notunix.go b/local_crypto_patch/contents/internal/testenv/testenv_notunix.go
new file mode 100644
index 0000000000..c8918ce592
--- /dev/null
+++ b/local_crypto_patch/contents/internal/testenv/testenv_notunix.go
@@ -0,0 +1,15 @@
+// Copyright 2021 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 windows || plan9 || (js && wasm) || wasip1
+
+package testenv
+
+import (
+ "os"
+)
+
+// Sigquit is the signal to send to kill a hanging subprocess.
+// On Unix we send SIGQUIT, but on non-Unix we only have os.Kill.
+var Sigquit = os.Kill
diff --git a/local_crypto_patch/contents/internal/testenv/testenv_unix.go b/local_crypto_patch/contents/internal/testenv/testenv_unix.go
new file mode 100644
index 0000000000..4f51823ec6
--- /dev/null
+++ b/local_crypto_patch/contents/internal/testenv/testenv_unix.go
@@ -0,0 +1,15 @@
+// Copyright 2021 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 unix
+
+package testenv
+
+import (
+ "syscall"
+)
+
+// Sigquit is the signal to send to kill a hanging subprocess.
+// Send SIGQUIT to get a stack trace.
+var Sigquit = syscall.SIGQUIT
diff --git a/local_crypto_patch/contents/internal/wycheproof/README.md b/local_crypto_patch/contents/internal/wycheproof/README.md
new file mode 100644
index 0000000000..8ae6c6c3d5
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/README.md
@@ -0,0 +1,12 @@
+This package runs a set of the Wycheproof tests provided by
+https://github.com/google/wycheproof.
+
+The JSON test files live in
+https://github.com/google/wycheproof/tree/master/testvectors
+and are being fetched and cached at a pinned version every time
+these tests are run. To change the version of the wycheproof
+repository that is being used for testing, update wycheproofModVer.
+
+The structs for these tests are generated from the
+schemas provided in https://github.com/google/wycheproof/tree/master/schemas
+using https://github.com/a-h/generate.
\ No newline at end of file
diff --git a/local_crypto_patch/contents/internal/wycheproof/aead_test.go b/local_crypto_patch/contents/internal/wycheproof/aead_test.go
new file mode 100644
index 0000000000..292d85425f
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/aead_test.go
@@ -0,0 +1,176 @@
+// 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 wycheproof
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "fmt"
+ "testing"
+
+ "golang.org/x/crypto/chacha20poly1305"
+)
+
+func TestAEAD(t *testing.T) {
+ // AeadTestVector
+ type AeadTestVector struct {
+
+ // additional authenticated data
+ Aad string `json:"aad,omitempty"`
+
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+
+ // the ciphertext (without iv and tag)
+ Ct string `json:"ct,omitempty"`
+
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+
+ // the nonce
+ Iv string `json:"iv,omitempty"`
+
+ // the key
+ Key string `json:"key,omitempty"`
+
+ // the plaintext
+ Msg string `json:"msg,omitempty"`
+
+ // Test result
+ Result string `json:"result,omitempty"`
+
+ // the authentication tag
+ Tag string `json:"tag,omitempty"`
+
+ // Identifier of the test case
+ TcId int `json:"tcId,omitempty"`
+ }
+
+ // Notes a description of the labels used in the test vectors
+ type Notes struct {
+ }
+
+ // AeadTestGroup
+ type AeadTestGroup struct {
+
+ // the IV size in bits
+ IvSize int `json:"ivSize,omitempty"`
+
+ // the keySize in bits
+ KeySize int `json:"keySize,omitempty"`
+
+ // the expected size of the tag in bits
+ TagSize int `json:"tagSize,omitempty"`
+ Tests []*AeadTestVector `json:"tests,omitempty"`
+ Type interface{} `json:"type,omitempty"`
+ }
+
+ // Root
+ type Root struct {
+
+ // the primitive tested in the test file
+ Algorithm string `json:"algorithm,omitempty"`
+
+ // the version of the test vectors.
+ GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+ // additional documentation
+ Header []string `json:"header,omitempty"`
+
+ // a description of the labels used in the test vectors
+ Notes *Notes `json:"notes,omitempty"`
+
+ // the number of test vectors in this test
+ NumberOfTests int `json:"numberOfTests,omitempty"`
+ Schema interface{} `json:"schema,omitempty"`
+ TestGroups []*AeadTestGroup `json:"testGroups,omitempty"`
+ }
+
+ testSealOpen := func(t *testing.T, aead cipher.AEAD, tv *AeadTestVector, recoverBadNonce func()) {
+ defer recoverBadNonce()
+
+ iv, tag, ct, msg, aad := decodeHex(tv.Iv), decodeHex(tv.Tag), decodeHex(tv.Ct), decodeHex(tv.Msg), decodeHex(tv.Aad)
+
+ genCT := aead.Seal(nil, iv, msg, aad)
+ genMsg, err := aead.Open(nil, iv, genCT, aad)
+ if err != nil {
+ t.Errorf("failed to decrypt generated ciphertext: %s", err)
+ }
+ if !bytes.Equal(genMsg, msg) {
+ t.Errorf("unexpected roundtripped plaintext: got %x, want %x", genMsg, msg)
+ }
+
+ ctWithTag := append(ct, tag...)
+ msg2, err := aead.Open(nil, iv, ctWithTag, aad)
+ wantPass := shouldPass(tv.Result, tv.Flags, nil)
+ if !wantPass && err == nil {
+ t.Error("decryption succeeded when it should've failed")
+ } else if wantPass {
+ if err != nil {
+ t.Fatalf("decryption failed: %s", err)
+ }
+ if !bytes.Equal(genCT, ctWithTag) {
+ t.Errorf("generated ciphertext doesn't match expected: got %x, want %x", genCT, ctWithTag)
+ }
+ if !bytes.Equal(msg, msg2) {
+ t.Errorf("decrypted ciphertext doesn't match expected: got %x, want %x", msg2, msg)
+ }
+ }
+ }
+
+ vectors := map[string]func(*testing.T, []byte) cipher.AEAD{
+ "aes_gcm_test.json": func(t *testing.T, key []byte) cipher.AEAD {
+ aesCipher, err := aes.NewCipher(key)
+ if err != nil {
+ t.Fatalf("failed to construct cipher: %s", err)
+ }
+ aead, err := cipher.NewGCM(aesCipher)
+ if err != nil {
+ t.Fatalf("failed to construct cipher: %s", err)
+ }
+ return aead
+ },
+ "chacha20_poly1305_test.json": func(t *testing.T, key []byte) cipher.AEAD {
+ aead, err := chacha20poly1305.New(key)
+ if err != nil {
+ t.Fatalf("failed to construct cipher: %s", err)
+ }
+ return aead
+ },
+ "xchacha20_poly1305_test.json": func(t *testing.T, key []byte) cipher.AEAD {
+ aead, err := chacha20poly1305.NewX(key)
+ if err != nil {
+ t.Fatalf("failed to construct cipher: %s", err)
+ }
+ return aead
+ },
+ }
+ for file, cipherInit := range vectors {
+ var root Root
+ readTestVector(t, file, &root)
+ for _, tg := range root.TestGroups {
+ for _, tv := range tg.Tests {
+ testName := fmt.Sprintf("%s #%d", file, tv.TcId)
+ if tv.Comment != "" {
+ testName += fmt.Sprintf(" %s", tv.Comment)
+ }
+ t.Run(testName, func(t *testing.T) {
+ aead := cipherInit(t, decodeHex(tv.Key))
+ testSealOpen(t, aead, tv, func() {
+ // A bad nonce causes a panic in AEAD.Seal and AEAD.Open,
+ // so should be recovered. Fail the test if it broke for
+ // some other reason.
+ if r := recover(); r != nil {
+ if tg.IvSize/8 == aead.NonceSize() {
+ t.Error("unexpected panic")
+ }
+ }
+ })
+ })
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/aes_cbc_test.go b/local_crypto_patch/contents/internal/wycheproof/aes_cbc_test.go
new file mode 100644
index 0000000000..0a60fc355c
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/aes_cbc_test.go
@@ -0,0 +1,127 @@
+// 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 wycheproof
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "encoding/hex"
+ "fmt"
+ "testing"
+)
+
+func TestAesCbc(t *testing.T) {
+ // IndCpaTestVector
+ type IndCpaTestVector struct {
+
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+
+ // the raw ciphertext (without IV)
+ Ct string `json:"ct,omitempty"`
+
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+
+ // the initialization vector
+ Iv string `json:"iv,omitempty"`
+
+ // the key
+ Key string `json:"key,omitempty"`
+
+ // the plaintext
+ Msg string `json:"msg,omitempty"`
+
+ // Test result
+ Result string `json:"result,omitempty"`
+
+ // Identifier of the test case
+ TcId int `json:"tcId,omitempty"`
+ }
+
+ // Notes a description of the labels used in the test vectors
+ type Notes struct {
+ }
+
+ // IndCpaTestGroup
+ type IndCpaTestGroup struct {
+
+ // the IV size in bits
+ IvSize int `json:"ivSize,omitempty"`
+
+ // the keySize in bits
+ KeySize int `json:"keySize,omitempty"`
+
+ // the expected size of the tag in bits
+ TagSize int `json:"tagSize,omitempty"`
+ Tests []*IndCpaTestVector `json:"tests,omitempty"`
+ Type interface{} `json:"type,omitempty"`
+ }
+
+ // Root
+ type Root struct {
+
+ // the primitive tested in the test file
+ Algorithm string `json:"algorithm,omitempty"`
+
+ // the version of the test vectors.
+ GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+ // additional documentation
+ Header []string `json:"header,omitempty"`
+
+ // a description of the labels used in the test vectors
+ Notes *Notes `json:"notes,omitempty"`
+
+ // the number of test vectors in this test
+ NumberOfTests int `json:"numberOfTests,omitempty"`
+ Schema interface{} `json:"schema,omitempty"`
+ TestGroups []*IndCpaTestGroup `json:"testGroups,omitempty"`
+ }
+
+ var root Root
+ readTestVector(t, "aes_cbc_pkcs5_test.json", &root)
+ for _, tg := range root.TestGroups {
+ tests:
+ for _, tv := range tg.Tests {
+ block, err := aes.NewCipher(decodeHex(tv.Key))
+ if err != nil {
+ t.Fatalf("#%d: %v", tv.TcId, err)
+ }
+ mode := cipher.NewCBCDecrypter(block, decodeHex(tv.Iv))
+ ct := decodeHex(tv.Ct)
+ if len(ct)%aes.BlockSize != 0 {
+ panic(fmt.Sprintf("#%d: ciphertext is not a multiple of the block size", tv.TcId))
+ }
+ mode.CryptBlocks(ct, ct) // decrypt the block in place
+
+ // Skip the tests that are broken due to bad padding. Panic if there are any
+ // tests left that are invalid for some other reason in the future, to
+ // evaluate what to do with those tests.
+ for _, flag := range tv.Flags {
+ if flag == "BadPadding" {
+ continue tests
+ }
+ }
+ if !shouldPass(tv.Result, tv.Flags, nil) {
+ panic(fmt.Sprintf("#%d: found an invalid test that is broken for some reason other than bad padding", tv.TcId))
+ }
+
+ // Remove the PKCS#5 padding from the given ciphertext to validate it
+ padding := ct[len(ct)-1]
+ paddingNum := int(padding)
+ for i := paddingNum; i > 0; i-- {
+ if ct[len(ct)-i] != padding { // panic if the padding is unexpectedly bad
+ panic(fmt.Sprintf("#%d: bad padding at index=%d of %v", tv.TcId, i, ct))
+ }
+ }
+ ct = ct[:len(ct)-paddingNum]
+
+ if got, want := hex.EncodeToString(ct), tv.Msg; got != want {
+ t.Errorf("#%d, type: %s, comment: %q, decoded ciphertext not equal: %s, want %s", tv.TcId, tv.Result, tv.Comment, got, want)
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/boring.go b/local_crypto_patch/contents/internal/wycheproof/boring.go
new file mode 100644
index 0000000000..aefa3ab30d
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/boring.go
@@ -0,0 +1,9 @@
+// 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 boringcrypto
+
+package wycheproof
+
+const boringcryptoEnabled = true
diff --git a/local_crypto_patch/contents/internal/wycheproof/dsa_test.go b/local_crypto_patch/contents/internal/wycheproof/dsa_test.go
new file mode 100644
index 0000000000..e554708460
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/dsa_test.go
@@ -0,0 +1,123 @@
+// 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 wycheproof
+
+import (
+ "crypto/dsa"
+ "testing"
+
+ wdsa "golang.org/x/crypto/internal/wycheproof/internal/dsa"
+)
+
+func TestDsa(t *testing.T) {
+ // AsnSignatureTestVector
+ type AsnSignatureTestVector struct {
+
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+
+ // The message to sign
+ Msg string `json:"msg,omitempty"`
+
+ // Test result
+ Result string `json:"result,omitempty"`
+
+ // An ASN encoded signature for msg
+ Sig string `json:"sig,omitempty"`
+
+ // Identifier of the test case
+ TcId int `json:"tcId,omitempty"`
+ }
+
+ // DsaPublicKey
+ type DsaPublicKey struct {
+
+ // the generator of the multiplicative subgroup
+ G string `json:"g,omitempty"`
+
+ // the key size in bits
+ KeySize int `json:"keySize,omitempty"`
+
+ // the modulus p
+ P string `json:"p,omitempty"`
+
+ // the order of the generator g
+ Q string `json:"q,omitempty"`
+
+ // the key type
+ Type string `json:"type,omitempty"`
+
+ // the public key value
+ Y string `json:"y,omitempty"`
+ }
+
+ // DsaTestGroup
+ type DsaTestGroup struct {
+
+ // unenocded DSA public key
+ Key *DsaPublicKey `json:"key,omitempty"`
+
+ // DER encoded public key
+ KeyDer string `json:"keyDer,omitempty"`
+
+ // Pem encoded public key
+ KeyPem string `json:"keyPem,omitempty"`
+
+ // the hash function used for DSA
+ Sha string `json:"sha,omitempty"`
+ Tests []*AsnSignatureTestVector `json:"tests,omitempty"`
+ Type interface{} `json:"type,omitempty"`
+ }
+
+ // Notes a description of the labels used in the test vectors
+ type Notes struct {
+ }
+
+ // Root
+ type Root struct {
+
+ // the primitive tested in the test file
+ Algorithm string `json:"algorithm,omitempty"`
+
+ // the version of the test vectors.
+ GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+ // additional documentation
+ Header []string `json:"header,omitempty"`
+
+ // a description of the labels used in the test vectors
+ Notes *Notes `json:"notes,omitempty"`
+
+ // the number of test vectors in this test
+ NumberOfTests int `json:"numberOfTests,omitempty"`
+ Schema interface{} `json:"schema,omitempty"`
+ TestGroups []*DsaTestGroup `json:"testGroups,omitempty"`
+ }
+
+ flagsShouldPass := map[string]bool{
+ // An encoded ASN.1 integer missing a leading zero is invalid, but accepted by some implementations.
+ "NoLeadingZero": false,
+ }
+
+ var root Root
+ readTestVector(t, "dsa_test.json", &root)
+ for _, tg := range root.TestGroups {
+ pub := decodePublicKey(tg.KeyDer).(*dsa.PublicKey)
+ h := parseHash(tg.Sha).New()
+ for _, sig := range tg.Tests {
+ h.Reset()
+ h.Write(decodeHex(sig.Msg))
+ hashed := h.Sum(nil)
+ hashed = hashed[:pub.Q.BitLen()/8] // Truncate to the byte-length of the subgroup (Q)
+ got := wdsa.VerifyASN1(pub, hashed, decodeHex(sig.Sig))
+ if want := shouldPass(sig.Result, sig.Flags, flagsShouldPass); got != want {
+ t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/ecdh_stdlib_test.go b/local_crypto_patch/contents/internal/wycheproof/ecdh_stdlib_test.go
new file mode 100644
index 0000000000..24a83e245b
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/ecdh_stdlib_test.go
@@ -0,0 +1,140 @@
+// 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 wycheproof
+
+import (
+ "bytes"
+ "crypto/ecdh"
+ "fmt"
+ "testing"
+)
+
+func TestECDHStdLib(t *testing.T) {
+ type ECDHTestVector struct {
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+ // the private key
+ Private string `json:"private,omitempty"`
+ // Encoded public key
+ Public string `json:"public,omitempty"`
+ // Test result
+ Result string `json:"result,omitempty"`
+ // The shared secret key
+ Shared string `json:"shared,omitempty"`
+ // Identifier of the test case
+ TcID int `json:"tcId,omitempty"`
+ }
+
+ type ECDHTestGroup struct {
+ Curve string `json:"curve,omitempty"`
+ Tests []*ECDHTestVector `json:"tests,omitempty"`
+ }
+
+ type Root struct {
+ TestGroups []*ECDHTestGroup `json:"testGroups,omitempty"`
+ }
+
+ flagsShouldPass := map[string]bool{
+ // We don't support compressed points.
+ "CompressedPoint": false,
+ // We don't support decoding custom curves.
+ "UnnamedCurve": false,
+ // WrongOrder and UnusedParam are only found with UnnamedCurve.
+ "WrongOrder": false,
+ "UnusedParam": false,
+
+ // X25519 specific flags
+ "Twist": true,
+ "SmallPublicKey": false,
+ "LowOrderPublic": false,
+ "ZeroSharedSecret": false,
+ "NonCanonicalPublic": true,
+ }
+
+ // curveToCurve is a map of all elliptic curves supported
+ // by crypto/elliptic, which can subsequently be parsed and tested.
+ curveToCurve := map[string]ecdh.Curve{
+ "secp256r1": ecdh.P256(),
+ "secp384r1": ecdh.P384(),
+ "secp521r1": ecdh.P521(),
+ "curve25519": ecdh.X25519(),
+ }
+
+ curveToKeySize := map[string]int{
+ "secp256r1": 32,
+ "secp384r1": 48,
+ "secp521r1": 66,
+ "curve25519": 32,
+ }
+
+ for _, f := range []string{
+ "ecdh_secp256r1_ecpoint_test.json",
+ "ecdh_secp384r1_ecpoint_test.json",
+ "ecdh_secp521r1_ecpoint_test.json",
+ "x25519_test.json",
+ } {
+ var root Root
+ readTestVector(t, f, &root)
+ for _, tg := range root.TestGroups {
+ if _, ok := curveToCurve[tg.Curve]; !ok {
+ continue
+ }
+ for _, tt := range tg.Tests {
+ tg, tt := tg, tt
+ t.Run(fmt.Sprintf("%s/%d", tg.Curve, tt.TcID), func(t *testing.T) {
+ t.Logf("Type: %v", tt.Result)
+ t.Logf("Flags: %q", tt.Flags)
+ t.Log(tt.Comment)
+
+ shouldPass := shouldPass(tt.Result, tt.Flags, flagsShouldPass)
+
+ curve := curveToCurve[tg.Curve]
+ p := decodeHex(tt.Public)
+ pub, err := curve.NewPublicKey(p)
+ if err != nil {
+ if shouldPass {
+ t.Errorf("NewPublicKey: %v", err)
+ }
+ return
+ }
+
+ privBytes := decodeHex(tt.Private)
+ if len(privBytes) != curveToKeySize[tg.Curve] {
+ t.Skipf("non-standard key size %d", len(privBytes))
+ }
+
+ priv, err := curve.NewPrivateKey(privBytes)
+ if err != nil {
+ if shouldPass {
+ t.Errorf("NewPrivateKey: %v", err)
+ }
+ return
+ }
+
+ shared := decodeHex(tt.Shared)
+ x, err := priv.ECDH(pub)
+ if err != nil {
+ if tg.Curve == "curve25519" && !shouldPass {
+ // ECDH is expected to only return an error when using X25519,
+ // in all other cases an error is unexpected.
+ return
+ }
+ t.Fatalf("ECDH: %v", err)
+ }
+
+ if bytes.Equal(shared, x) != shouldPass {
+ if shouldPass {
+ t.Errorf("ECDH = %x, want %x", shared, x)
+ } else {
+ t.Errorf("ECDH = %x, want anything else", shared)
+ }
+ }
+ })
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/ecdh_test.go b/local_crypto_patch/contents/internal/wycheproof/ecdh_test.go
new file mode 100644
index 0000000000..a3918ba62f
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/ecdh_test.go
@@ -0,0 +1,163 @@
+// 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 wycheproof
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/x509"
+ "encoding/asn1"
+ "errors"
+ "fmt"
+ "testing"
+
+ "golang.org/x/crypto/cryptobyte"
+ casn1 "golang.org/x/crypto/cryptobyte/asn1"
+)
+
+func TestECDH(t *testing.T) {
+ type ECDHTestVector struct {
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+ // the private key
+ Private string `json:"private,omitempty"`
+ // Encoded public key
+ Public string `json:"public,omitempty"`
+ // Test result
+ Result string `json:"result,omitempty"`
+ // The shared secret key
+ Shared string `json:"shared,omitempty"`
+ // Identifier of the test case
+ TcID int `json:"tcId,omitempty"`
+ }
+
+ type ECDHTestGroup struct {
+ Curve string `json:"curve,omitempty"`
+ Tests []*ECDHTestVector `json:"tests,omitempty"`
+ }
+
+ type Root struct {
+ TestGroups []*ECDHTestGroup `json:"testGroups,omitempty"`
+ }
+
+ flagsShouldPass := map[string]bool{
+ // ParsePKIXPublicKey doesn't support compressed points, but we test
+ // them against UnmarshalCompressed anyway.
+ "CompressedPoint": true,
+ // We don't support decoding custom curves.
+ "UnnamedCurve": false,
+ // WrongOrder and UnusedParam are only found with UnnamedCurve.
+ "WrongOrder": false,
+ "UnusedParam": false,
+ }
+
+ // supportedCurves is a map of all elliptic curves supported
+ // by crypto/elliptic, which can subsequently be parsed and tested.
+ supportedCurves := map[string]bool{
+ "secp224r1": true,
+ "secp256r1": true,
+ "secp384r1": true,
+ "secp521r1": true,
+ }
+
+ var root Root
+ readTestVector(t, "ecdh_test.json", &root)
+ for _, tg := range root.TestGroups {
+ if !supportedCurves[tg.Curve] {
+ continue
+ }
+ for _, tt := range tg.Tests {
+ tg, tt := tg, tt
+ t.Run(fmt.Sprintf("%s/%d", tg.Curve, tt.TcID), func(t *testing.T) {
+ t.Logf("Type: %v", tt.Result)
+ t.Logf("Flags: %q", tt.Flags)
+ t.Log(tt.Comment)
+
+ shouldPass := shouldPass(tt.Result, tt.Flags, flagsShouldPass)
+
+ p := decodeHex(tt.Public)
+ pp, err := x509.ParsePKIXPublicKey(p)
+ if err != nil {
+ pp, err = decodeCompressedPKIX(p)
+ }
+ if err != nil {
+ if shouldPass {
+ t.Errorf("unexpected parsing error: %s", err)
+ }
+ return
+ }
+ pub := pp.(*ecdsa.PublicKey)
+
+ priv := decodeHex(tt.Private)
+ shared := decodeHex(tt.Shared)
+
+ x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, priv)
+ xBytes := make([]byte, (pub.Curve.Params().BitSize+7)/8)
+ got := bytes.Equal(shared, x.FillBytes(xBytes))
+
+ if want := shouldPass; got != want {
+ t.Errorf("wanted success %v, got %v", want, got)
+ }
+ })
+ }
+ }
+}
+
+func decodeCompressedPKIX(der []byte) (interface{}, error) {
+ s := cryptobyte.String(der)
+ var s1, s2 cryptobyte.String
+ var algoOID, namedCurveOID asn1.ObjectIdentifier
+ var pointDER []byte
+ if !s.ReadASN1(&s1, casn1.SEQUENCE) || !s.Empty() ||
+ !s1.ReadASN1(&s2, casn1.SEQUENCE) ||
+ !s2.ReadASN1ObjectIdentifier(&algoOID) ||
+ !s2.ReadASN1ObjectIdentifier(&namedCurveOID) || !s2.Empty() ||
+ !s1.ReadASN1BitStringAsBytes(&pointDER) || !s1.Empty() {
+ return nil, errors.New("failed to parse PKIX structure")
+ }
+
+ if !algoOID.Equal(oidPublicKeyECDSA) {
+ return nil, errors.New("wrong algorithm OID")
+ }
+ namedCurve := namedCurveFromOID(namedCurveOID)
+ if namedCurve == nil {
+ return nil, errors.New("unsupported elliptic curve")
+ }
+ x, y := elliptic.UnmarshalCompressed(namedCurve, pointDER)
+ if x == nil {
+ return nil, errors.New("failed to unmarshal elliptic curve point")
+ }
+ pub := &ecdsa.PublicKey{
+ Curve: namedCurve,
+ X: x,
+ Y: y,
+ }
+ return pub, nil
+}
+
+var (
+ oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
+ oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
+ oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
+ oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
+ oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
+)
+
+func namedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
+ switch {
+ case oid.Equal(oidNamedCurveP224):
+ return elliptic.P224()
+ case oid.Equal(oidNamedCurveP256):
+ return elliptic.P256()
+ case oid.Equal(oidNamedCurveP384):
+ return elliptic.P384()
+ case oid.Equal(oidNamedCurveP521):
+ return elliptic.P521()
+ }
+ return nil
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/ecdsa_test.go b/local_crypto_patch/contents/internal/wycheproof/ecdsa_test.go
new file mode 100644
index 0000000000..80125ada75
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/ecdsa_test.go
@@ -0,0 +1,105 @@
+// 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 wycheproof
+
+import (
+ "crypto/ecdsa"
+ "math/big"
+ "testing"
+
+ "golang.org/x/crypto/cryptobyte"
+ "golang.org/x/crypto/cryptobyte/asn1"
+)
+
+func TestECDSA(t *testing.T) {
+ type ASNSignatureTestVector struct {
+ // A brief description of the test case
+ Comment string `json:"comment"`
+ // A list of flags
+ Flags []string `json:"flags"`
+ // The message to sign
+ Msg string `json:"msg"`
+ // Test result
+ Result string `json:"result"`
+ // An ASN.1 encoded signature for msg
+ Sig string `json:"sig"`
+ // Identifier of the test case
+ TcID int `json:"tcId"`
+ }
+
+ type ECPublicKey struct {
+ // The EC group used by this public key
+ Curve interface{} `json:"curve"`
+ }
+
+ type ECDSATestGroup struct {
+ // Unencoded EC public key
+ Key *ECPublicKey `json:"key"`
+ // DER encoded public key
+ KeyDER string `json:"keyDer"`
+ // the hash function used for ECDSA
+ SHA string `json:"sha"`
+ Tests []*ASNSignatureTestVector `json:"tests"`
+ }
+
+ type Root struct {
+ TestGroups []*ECDSATestGroup `json:"testGroups"`
+ }
+
+ flagsShouldPass := map[string]bool{
+ // An encoded ASN.1 integer missing a leading zero is invalid, but
+ // accepted by some implementations.
+ "MissingZero": false,
+ // A signature using a weaker hash than the EC params is not a security
+ // risk, as long as the hash is secure.
+ // https://www.imperialviolet.org/2014/05/25/strengthmatching.html
+ "WeakHash": true,
+ }
+
+ // supportedCurves is a map of all elliptic curves supported
+ // by crypto/elliptic, which can subsequently be parsed and tested.
+ supportedCurves := map[string]bool{
+ "secp224r1": true,
+ "secp256r1": true,
+ "secp384r1": true,
+ "secp521r1": true,
+ }
+
+ var root Root
+ readTestVector(t, "ecdsa_test.json", &root)
+ for _, tg := range root.TestGroups {
+ curve := tg.Key.Curve.(string)
+ if !supportedCurves[curve] {
+ continue
+ }
+ pub := decodePublicKey(tg.KeyDER).(*ecdsa.PublicKey)
+ h := parseHash(tg.SHA).New()
+ for _, sig := range tg.Tests {
+ h.Reset()
+ h.Write(decodeHex(sig.Msg))
+ hashed := h.Sum(nil)
+ sigBytes := decodeHex(sig.Sig)
+ got := ecdsa.VerifyASN1(pub, hashed, sigBytes)
+ if want := shouldPass(sig.Result, sig.Flags, flagsShouldPass); got != want {
+ t.Errorf("tcid: %d, type: %s, comment: %q, VerifyASN1 wanted success: %t", sig.TcID, sig.Result, sig.Comment, want)
+ }
+
+ var r, s big.Int
+ var inner cryptobyte.String
+ input := cryptobyte.String(sigBytes)
+ if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
+ !input.Empty() ||
+ !inner.ReadASN1Integer(&r) ||
+ !inner.ReadASN1Integer(&s) ||
+ !inner.Empty() {
+ continue
+ }
+ got = ecdsa.Verify(pub, hashed, &r, &s)
+ if want := shouldPass(sig.Result, sig.Flags, flagsShouldPass); got != want {
+ t.Errorf("tcid: %d, type: %s, comment: %q, Verify wanted success: %t", sig.TcID, sig.Result, sig.Comment, want)
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/eddsa_test.go b/local_crypto_patch/contents/internal/wycheproof/eddsa_test.go
new file mode 100644
index 0000000000..a74f343f02
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/eddsa_test.go
@@ -0,0 +1,97 @@
+// 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 wycheproof
+
+import (
+ "crypto/ed25519"
+ "testing"
+)
+
+func TestEddsa(t *testing.T) {
+ // Jwk the private key in webcrypto format
+ type Jwk struct {
+ }
+
+ // Key unencoded key pair
+ type Key struct {
+ }
+
+ // Notes a description of the labels used in the test vectors
+ type Notes struct {
+ }
+
+ // SignatureTestVector
+ type SignatureTestVector struct {
+
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+
+ // The message to sign
+ Msg string `json:"msg,omitempty"`
+
+ // Test result
+ Result string `json:"result,omitempty"`
+
+ // A signature for msg
+ Sig string `json:"sig,omitempty"`
+
+ // Identifier of the test case
+ TcId int `json:"tcId,omitempty"`
+ }
+
+ // EddsaTestGroup
+ type EddsaTestGroup struct {
+
+ // the private key in webcrypto format
+ Jwk *Jwk `json:"jwk,omitempty"`
+
+ // unencoded key pair
+ Key *Key `json:"key,omitempty"`
+
+ // Asn encoded public key
+ KeyDer string `json:"keyDer,omitempty"`
+
+ // Pem encoded public key
+ KeyPem string `json:"keyPem,omitempty"`
+ Tests []*SignatureTestVector `json:"tests,omitempty"`
+ Type interface{} `json:"type,omitempty"`
+ }
+
+ // Root
+ type Root struct {
+
+ // the primitive tested in the test file
+ Algorithm string `json:"algorithm,omitempty"`
+
+ // the version of the test vectors.
+ GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+ // additional documentation
+ Header []string `json:"header,omitempty"`
+
+ // a description of the labels used in the test vectors
+ Notes *Notes `json:"notes,omitempty"`
+
+ // the number of test vectors in this test
+ NumberOfTests int `json:"numberOfTests,omitempty"`
+ Schema interface{} `json:"schema,omitempty"`
+ TestGroups []*EddsaTestGroup `json:"testGroups,omitempty"`
+ }
+
+ var root Root
+ readTestVector(t, "eddsa_test.json", &root)
+ for _, tg := range root.TestGroups {
+ pub := decodePublicKey(tg.KeyDer).(ed25519.PublicKey)
+ for _, sig := range tg.Tests {
+ got := ed25519.Verify(pub, decodeHex(sig.Msg), decodeHex(sig.Sig))
+ if want := shouldPass(sig.Result, sig.Flags, nil); got != want {
+ t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/hkdf_test.go b/local_crypto_patch/contents/internal/wycheproof/hkdf_test.go
new file mode 100644
index 0000000000..6b72e2c870
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/hkdf_test.go
@@ -0,0 +1,111 @@
+// 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 wycheproof
+
+import (
+ "bytes"
+ "io"
+ "testing"
+
+ "golang.org/x/crypto/hkdf"
+)
+
+func TestHkdf(t *testing.T) {
+
+ // HkdfTestVector
+ type HkdfTestVector struct {
+
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+
+ // the key (input key material)
+ Ikm string `json:"ikm,omitempty"`
+
+ // additional information used in the key derivation
+ Info string `json:"info,omitempty"`
+
+ // the generated bytes (output key material)
+ Okm string `json:"okm,omitempty"`
+
+ // Test result
+ Result string `json:"result,omitempty"`
+
+ // the salt for the key derivation
+ Salt string `json:"salt,omitempty"`
+
+ // the size of the output in bytes
+ Size int `json:"size,omitempty"`
+
+ // Identifier of the test case
+ TcId int `json:"tcId,omitempty"`
+ }
+
+ // Notes a description of the labels used in the test vectors
+ type Notes struct {
+ }
+
+ // HkdfTestGroup
+ type HkdfTestGroup struct {
+
+ // the size of the ikm in bits
+ KeySize int `json:"keySize,omitempty"`
+ Tests []*HkdfTestVector `json:"tests,omitempty"`
+ Type interface{} `json:"type,omitempty"`
+ }
+
+ // Root
+ type Root struct {
+
+ // the primitive tested in the test file
+ Algorithm string `json:"algorithm,omitempty"`
+
+ // the version of the test vectors.
+ GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+ // additional documentation
+ Header []string `json:"header,omitempty"`
+
+ // a description of the labels used in the test vectors
+ Notes *Notes `json:"notes,omitempty"`
+
+ // the number of test vectors in this test
+ NumberOfTests int `json:"numberOfTests,omitempty"`
+ Schema interface{} `json:"schema,omitempty"`
+ TestGroups []*HkdfTestGroup `json:"testGroups,omitempty"`
+ }
+
+ fileHashAlgorithms := map[string]string{
+ "hkdf_sha1_test.json": "SHA-1",
+ "hkdf_sha256_test.json": "SHA-256",
+ "hkdf_sha384_test.json": "SHA-384",
+ "hkdf_sha512_test.json": "SHA-512",
+ }
+
+ for f := range fileHashAlgorithms {
+ var root Root
+ readTestVector(t, f, &root)
+ for _, tg := range root.TestGroups {
+ for _, tv := range tg.Tests {
+ h := parseHash(fileHashAlgorithms[f]).New
+ hkdf := hkdf.New(h, decodeHex(tv.Ikm), decodeHex(tv.Salt), decodeHex(tv.Info))
+ key := make([]byte, tv.Size)
+ wantPass := shouldPass(tv.Result, tv.Flags, nil)
+ _, err := io.ReadFull(hkdf, key)
+ if (err == nil) != wantPass {
+ t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t, got: %v", tv.TcId, tv.Result, tv.Comment, wantPass, err)
+ }
+ if err != nil {
+ continue // don't validate output text if reading failed
+ }
+ if got, want := key, decodeHex(tv.Okm); !bytes.Equal(got, want) {
+ t.Errorf("tcid: %d, type: %s, comment: %q, output bytes don't match", tv.TcId, tv.Result, tv.Comment)
+ }
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/hmac_test.go b/local_crypto_patch/contents/internal/wycheproof/hmac_test.go
new file mode 100644
index 0000000000..bcc56f28fc
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/hmac_test.go
@@ -0,0 +1,105 @@
+// 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 wycheproof
+
+import (
+ "crypto/hmac"
+ "testing"
+)
+
+func TestHMAC(t *testing.T) {
+ // MacTestVector
+ type MacTestVector struct {
+
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+
+ // the key
+ Key string `json:"key,omitempty"`
+
+ // the plaintext
+ Msg string `json:"msg,omitempty"`
+
+ // Test result
+ Result string `json:"result,omitempty"`
+
+ // the authentication tag
+ Tag string `json:"tag,omitempty"`
+
+ // Identifier of the test case
+ TcId int `json:"tcId,omitempty"`
+ }
+
+ // MacTestGroup
+ type MacTestGroup struct {
+
+ // the keySize in bits
+ KeySize int `json:"keySize,omitempty"`
+
+ // the expected size of the tag in bits
+ TagSize int `json:"tagSize,omitempty"`
+ Tests []*MacTestVector `json:"tests,omitempty"`
+ Type interface{} `json:"type,omitempty"`
+ }
+
+ // Notes a description of the labels used in the test vectors
+ type Notes struct {
+ }
+
+ // Root
+ type Root struct {
+
+ // the primitive tested in the test file
+ Algorithm string `json:"algorithm,omitempty"`
+
+ // the version of the test vectors.
+ GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+ // additional documentation
+ Header []string `json:"header,omitempty"`
+
+ // a description of the labels used in the test vectors
+ Notes *Notes `json:"notes,omitempty"`
+
+ // the number of test vectors in this test
+ NumberOfTests int `json:"numberOfTests,omitempty"`
+ Schema interface{} `json:"schema,omitempty"`
+ TestGroups []*MacTestGroup `json:"testGroups,omitempty"`
+ }
+
+ fileHashAlgs := map[string]string{
+ "hmac_sha1_test.json": "SHA-1",
+ "hmac_sha224_test.json": "SHA-224",
+ "hmac_sha256_test.json": "SHA-256",
+ "hmac_sha384_test.json": "SHA-384",
+ "hmac_sha512_test.json": "SHA-512",
+ }
+
+ for f := range fileHashAlgs {
+ var root Root
+ readTestVector(t, f, &root)
+ for _, tg := range root.TestGroups {
+ h := parseHash(fileHashAlgs[f])
+ // Skip test vectors where the tag length does not equal the
+ // hash length, since crypto/hmac does not support generating
+ // these truncated tags.
+ if tg.TagSize/8 != h.Size() {
+ continue
+ }
+ for _, tv := range tg.Tests {
+ hm := hmac.New(h.New, decodeHex(tv.Key))
+ hm.Write(decodeHex(tv.Msg))
+ tag := hm.Sum(nil)
+ got := hmac.Equal(decodeHex(tv.Tag), tag)
+ if want := shouldPass(tv.Result, tv.Flags, nil); want != got {
+ t.Errorf("%s, tcid: %d, type: %s, comment: %q, unexpected result", f, tv.TcId, tv.Result, tv.Comment)
+ }
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/internal/dsa/dsa.go b/local_crypto_patch/contents/internal/wycheproof/internal/dsa/dsa.go
new file mode 100644
index 0000000000..3101dfc1c2
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/internal/dsa/dsa.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.
+
+// Package dsa provides an internal version of dsa.Verify
+// that is used for the Wycheproof tests.
+package dsa
+
+import (
+ "crypto/dsa"
+ "math/big"
+
+ "golang.org/x/crypto/cryptobyte"
+ "golang.org/x/crypto/cryptobyte/asn1"
+)
+
+// VerifyASN1 verifies the ASN1 encoded signature, sig, of hash using the
+// public key, pub. Its return value records whether the signature is valid.
+func VerifyASN1(pub *dsa.PublicKey, hash, sig []byte) bool {
+ var (
+ r, s = &big.Int{}, &big.Int{}
+ inner cryptobyte.String
+ )
+ input := cryptobyte.String(sig)
+ if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
+ !input.Empty() ||
+ !inner.ReadASN1Integer(r) ||
+ !inner.ReadASN1Integer(s) ||
+ !inner.Empty() {
+ return false
+ }
+ return dsa.Verify(pub, hash, r, s)
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/notboring.go b/local_crypto_patch/contents/internal/wycheproof/notboring.go
new file mode 100644
index 0000000000..746af130f1
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/notboring.go
@@ -0,0 +1,9 @@
+// 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 !boringcrypto
+
+package wycheproof
+
+const boringcryptoEnabled = false
diff --git a/local_crypto_patch/contents/internal/wycheproof/rsa_oaep_decrypt_test.go b/local_crypto_patch/contents/internal/wycheproof/rsa_oaep_decrypt_test.go
new file mode 100644
index 0000000000..19cc4fdcb7
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/rsa_oaep_decrypt_test.go
@@ -0,0 +1,149 @@
+// 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 wycheproof
+
+import (
+ "bytes"
+ "crypto/rsa"
+ "crypto/x509"
+ "fmt"
+ "testing"
+)
+
+func TestRSAOAEPDecrypt(t *testing.T) {
+ // Notes a description of the labels used in the test vectors
+ type Notes struct {
+ }
+
+ // RsaesOaepTestVector
+ type RsaesOaepTestVector struct {
+
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+
+ // An encryption of msg
+ Ct string `json:"ct,omitempty"`
+
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+
+ // The label used for the encryption
+ Label string `json:"label,omitempty"`
+
+ // The encrypted message
+ Msg string `json:"msg,omitempty"`
+
+ // Test result
+ Result string `json:"result,omitempty"`
+
+ // Identifier of the test case
+ TcId int `json:"tcId,omitempty"`
+ }
+
+ // RsaesOaepTestGroup
+ type RsaesOaepTestGroup struct {
+
+ // The private exponent
+ D string `json:"d,omitempty"`
+
+ // The public exponent
+ E string `json:"e,omitempty"`
+
+ // the message generating function (e.g. MGF1)
+ Mgf string `json:"mgf,omitempty"`
+
+ // The hash function used for the message generating function.
+ MgfSha string `json:"mgfSha,omitempty"`
+
+ // The modulus of the key
+ N string `json:"n,omitempty"`
+
+ // Pem encoded private key
+ PrivateKeyPem string `json:"privateKeyPem,omitempty"`
+
+ // Pkcs 8 encoded private key.
+ PrivateKeyPkcs8 string `json:"privateKeyPkcs8,omitempty"`
+
+ // The hash function for hashing the label.
+ Sha string `json:"sha,omitempty"`
+ Tests []*RsaesOaepTestVector `json:"tests,omitempty"`
+ Type interface{} `json:"type,omitempty"`
+ }
+
+ // Root
+ type Root struct {
+
+ // the primitive tested in the test file
+ Algorithm string `json:"algorithm,omitempty"`
+
+ // the version of the test vectors.
+ GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+ // additional documentation
+ Header []string `json:"header,omitempty"`
+
+ // a description of the labels used in the test vectors
+ Notes *Notes `json:"notes,omitempty"`
+
+ // the number of test vectors in this test
+ NumberOfTests int `json:"numberOfTests,omitempty"`
+ Schema interface{} `json:"schema,omitempty"`
+ TestGroups []*RsaesOaepTestGroup `json:"testGroups,omitempty"`
+ }
+
+ // rsa.DecryptOAEP doesn't support using a different hash for the
+ // MGF and the label, so skip all of the test vectors that use
+ // these unbalanced constructions. rsa_oaep_misc_test.json contains
+ // both balanced and unbalanced constructions so in that case
+ // we just filter out any test groups where MgfSha != Sha
+ files := []string{
+ "rsa_oaep_2048_sha1_mgf1sha1_test.json",
+ "rsa_oaep_2048_sha224_mgf1sha224_test.json",
+ "rsa_oaep_2048_sha256_mgf1sha256_test.json",
+ "rsa_oaep_2048_sha384_mgf1sha384_test.json",
+ "rsa_oaep_2048_sha512_mgf1sha512_test.json",
+ "rsa_oaep_3072_sha256_mgf1sha256_test.json",
+ "rsa_oaep_3072_sha512_mgf1sha512_test.json",
+ "rsa_oaep_4096_sha256_mgf1sha256_test.json",
+ "rsa_oaep_4096_sha512_mgf1sha512_test.json",
+ "rsa_oaep_misc_test.json",
+ }
+
+ flagsShouldPass := map[string]bool{
+ // rsa.DecryptOAEP happily supports small key sizes
+ "SmallModulus": true,
+ }
+
+ for _, f := range files {
+ var root Root
+ readTestVector(t, f, &root)
+ for _, tg := range root.TestGroups {
+ if tg.MgfSha != tg.Sha {
+ continue
+ }
+ priv, err := x509.ParsePKCS8PrivateKey(decodeHex(tg.PrivateKeyPkcs8))
+ if err != nil {
+ t.Fatalf("%s failed to parse PKCS #8 private key: %s", f, err)
+ }
+ hash := parseHash(tg.Sha)
+ for _, tv := range tg.Tests {
+ t.Run(fmt.Sprintf("%s #%d", f, tv.TcId), func(t *testing.T) {
+ wantPass := shouldPass(tv.Result, tv.Flags, flagsShouldPass)
+ plaintext, err := rsa.DecryptOAEP(hash.New(), nil, priv.(*rsa.PrivateKey), decodeHex(tv.Ct), decodeHex(tv.Label))
+ if wantPass {
+ if err != nil {
+ t.Fatalf("comment: %s, expected success: %s", tv.Comment, err)
+ }
+ if !bytes.Equal(plaintext, decodeHex(tv.Msg)) {
+ t.Errorf("comment: %s, unexpected plaintext: got %x, want %s", tv.Comment, plaintext, tv.Msg)
+ }
+ } else if err == nil {
+ t.Errorf("comment: %s, expected failure", tv.Comment)
+ }
+ })
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/rsa_pss_test.go b/local_crypto_patch/contents/internal/wycheproof/rsa_pss_test.go
new file mode 100644
index 0000000000..f2f9b6ebae
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/rsa_pss_test.go
@@ -0,0 +1,169 @@
+// 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 wycheproof
+
+import (
+ "crypto/rsa"
+ "testing"
+)
+
+func TestRsaPss(t *testing.T) {
+ // KeyJwk Public key in JWK format
+ type KeyJwk struct {
+ }
+
+ // Notes a description of the labels used in the test vectors
+ type Notes struct {
+ }
+
+ // SignatureTestVector
+ type SignatureTestVector struct {
+
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+
+ // The message to sign
+ Msg string `json:"msg,omitempty"`
+
+ // Test result
+ Result string `json:"result,omitempty"`
+
+ // A signature for msg
+ Sig string `json:"sig,omitempty"`
+
+ // Identifier of the test case
+ TcId int `json:"tcId,omitempty"`
+ }
+
+ // RsassaPkcs1TestGroup
+ type RsassaPkcs1TestGroup struct {
+
+ // The private exponent
+ D string `json:"d,omitempty"`
+
+ // The public exponent
+ E string `json:"e,omitempty"`
+
+ // ASN encoding of the sequence [n, e]
+ KeyAsn string `json:"keyAsn,omitempty"`
+
+ // ASN encoding of the public key
+ KeyDer string `json:"keyDer,omitempty"`
+
+ // Public key in JWK format
+ KeyJwk *KeyJwk `json:"keyJwk,omitempty"`
+
+ // Pem encoded public key
+ KeyPem string `json:"keyPem,omitempty"`
+
+ // the size of the modulus in bits
+ KeySize int `json:"keySize,omitempty"`
+
+ // The modulus of the key
+ N string `json:"n,omitempty"`
+
+ // The salt length
+ SLen int `json:"sLen,omitempty"`
+
+ // the hash function used for the message
+ Sha string `json:"sha,omitempty"`
+ Tests []*SignatureTestVector `json:"tests,omitempty"`
+ Type interface{} `json:"type,omitempty"`
+ }
+
+ // Root
+ type Root struct {
+
+ // the primitive tested in the test file
+ Algorithm string `json:"algorithm,omitempty"`
+
+ // the version of the test vectors.
+ GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+ // additional documentation
+ Header []string `json:"header,omitempty"`
+
+ // a description of the labels used in the test vectors
+ Notes *Notes `json:"notes,omitempty"`
+
+ // the number of test vectors in this test
+ NumberOfTests int `json:"numberOfTests,omitempty"`
+ Schema interface{} `json:"schema,omitempty"`
+ TestGroups []*RsassaPkcs1TestGroup `json:"testGroups,omitempty"`
+ }
+
+ flagsShouldPass := map[string]bool{
+ // A signature using a weaker hash than the EC params is not a security risk, as long as the hash is secure.
+ // https://www.imperialviolet.org/2014/05/25/strengthmatching.html
+ "WeakHash": true,
+ }
+
+ // filesOverrideToPassZeroSLen is a map of all test files
+ // and which TcIds that should be overridden to pass if the
+ // rsa.PSSOptions.SaltLength is zero.
+ // These tests expect a failure with a PSSOptions.SaltLength: 0
+ // and a signature that uses a different salt length. However,
+ // a salt length of 0 is defined as rsa.PSSSaltLengthAuto which
+ // works deterministically to auto-detect the length when
+ // verifying, so these tests actually pass as they should.
+ filesOverrideToPassZeroSLen := map[string][]int{
+ "rsa_pss_2048_sha1_mgf1_20_test.json": []int{46, 47},
+ "rsa_pss_2048_sha256_mgf1_0_test.json": []int{67, 68},
+ "rsa_pss_2048_sha256_mgf1_32_test.json": []int{67, 68},
+ "rsa_pss_3072_sha256_mgf1_32_test.json": []int{67, 68},
+ "rsa_pss_4096_sha256_mgf1_32_test.json": []int{67, 68},
+ "rsa_pss_4096_sha512_mgf1_32_test.json": []int{136, 137},
+ // "rsa_pss_misc_test.json": nil, // TODO: This ones seems to be broken right now, but can enable later on.
+ }
+
+ if !boringcryptoEnabled {
+ // boringcrypto doesn't support the truncated SHA-512 hashes, so only
+ // test them if boringcrypto isn't enabled.
+ filesOverrideToPassZeroSLen["rsa_pss_2048_sha512_256_mgf1_28_test.json"] = []int{13, 14, 15}
+ filesOverrideToPassZeroSLen["rsa_pss_2048_sha512_256_mgf1_32_test.json"] = []int{13, 14}
+ }
+
+ for f := range filesOverrideToPassZeroSLen {
+ var root Root
+ readTestVector(t, f, &root)
+ for _, tg := range root.TestGroups {
+ pub := decodePublicKey(tg.KeyDer).(*rsa.PublicKey)
+ ch := parseHash(tg.Sha)
+ h := ch.New()
+ opts := &rsa.PSSOptions{
+ Hash: ch,
+ SaltLength: rsa.PSSSaltLengthAuto,
+ }
+ // Run all the tests twice: the first time with the salt length
+ // as PSSSaltLengthAuto, and the second time with the salt length
+ // explicitly set to tg.SLen.
+ for i := 0; i < 2; i++ {
+ for _, sig := range tg.Tests {
+ h.Reset()
+ h.Write(decodeHex(sig.Msg))
+ hashed := h.Sum(nil)
+ err := rsa.VerifyPSS(pub, ch, hashed, decodeHex(sig.Sig), opts)
+ want := shouldPass(sig.Result, sig.Flags, flagsShouldPass)
+ if opts.SaltLength == 0 {
+ for _, id := range filesOverrideToPassZeroSLen[f] {
+ if sig.TcId == id {
+ want = true
+ break
+ }
+ }
+ }
+ if (err == nil) != want {
+ t.Errorf("file: %v, tcid: %d, type: %s, opts.SaltLength: %v, comment: %q, wanted success: %t", f, sig.TcId, sig.Result, opts.SaltLength, sig.Comment, want)
+ }
+ }
+ // Update opts.SaltLength for the second run of the tests.
+ opts.SaltLength = tg.SLen
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/rsa_signature_test.go b/local_crypto_patch/contents/internal/wycheproof/rsa_signature_test.go
new file mode 100644
index 0000000000..3c31c22552
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/rsa_signature_test.go
@@ -0,0 +1,123 @@
+// 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 wycheproof
+
+import (
+ "crypto/rsa"
+ "testing"
+)
+
+func TestRsa(t *testing.T) {
+ // KeyJwk Public key in JWK format
+ type KeyJwk struct {
+ }
+
+ // Notes a description of the labels used in the test vectors
+ type Notes struct {
+ }
+
+ // SignatureTestVector
+ type SignatureTestVector struct {
+
+ // A brief description of the test case
+ Comment string `json:"comment,omitempty"`
+
+ // A list of flags
+ Flags []string `json:"flags,omitempty"`
+
+ // The message to sign
+ Msg string `json:"msg,omitempty"`
+
+ // Test result
+ Result string `json:"result,omitempty"`
+
+ // A signature for msg
+ Sig string `json:"sig,omitempty"`
+
+ // Identifier of the test case
+ TcId int `json:"tcId,omitempty"`
+ }
+
+ // RsassaPkcs1TestGroup
+ type RsassaPkcs1TestGroup struct {
+
+ // The private exponent
+ D string `json:"d,omitempty"`
+
+ // The public exponent
+ E string `json:"e,omitempty"`
+
+ // ASN encoding of the sequence [n, e]
+ KeyAsn string `json:"keyAsn,omitempty"`
+
+ // ASN encoding of the public key
+ KeyDer string `json:"keyDer,omitempty"`
+
+ // Public key in JWK format
+ KeyJwk *KeyJwk `json:"keyJwk,omitempty"`
+
+ // Pem encoded public key
+ KeyPem string `json:"keyPem,omitempty"`
+
+ // the size of the modulus in bits
+ KeySize int `json:"keySize,omitempty"`
+
+ // The modulus of the key
+ N string `json:"n,omitempty"`
+
+ // the hash function used for the message
+ Sha string `json:"sha,omitempty"`
+ Tests []*SignatureTestVector `json:"tests,omitempty"`
+ Type interface{} `json:"type,omitempty"`
+ }
+
+ // Root
+ type Root struct {
+
+ // the primitive tested in the test file
+ Algorithm string `json:"algorithm,omitempty"`
+
+ // the version of the test vectors.
+ GeneratorVersion string `json:"generatorVersion,omitempty"`
+
+ // additional documentation
+ Header []string `json:"header,omitempty"`
+
+ // a description of the labels used in the test vectors
+ Notes *Notes `json:"notes,omitempty"`
+
+ // the number of test vectors in this test
+ NumberOfTests int `json:"numberOfTests,omitempty"`
+ Schema interface{} `json:"schema,omitempty"`
+ TestGroups []*RsassaPkcs1TestGroup `json:"testGroups,omitempty"`
+ }
+
+ flagsShouldPass := map[string]bool{
+ // Omitting the parameter field in an ASN encoded integer is a legacy behavior.
+ "MissingNull": false,
+ // Keys with a modulus less than 2048 bits are supported by crypto/rsa.
+ "SmallModulus": true,
+ // Small public keys are supported by crypto/rsa.
+ "SmallPublicKey": true,
+ }
+
+ var root Root
+ readTestVector(t, "rsa_signature_test.json", &root)
+ for _, tg := range root.TestGroups {
+ pub := decodePublicKey(tg.KeyDer).(*rsa.PublicKey)
+ ch := parseHash(tg.Sha)
+ h := ch.New()
+ for _, sig := range tg.Tests {
+ h.Reset()
+ h.Write(decodeHex(sig.Msg))
+ hashed := h.Sum(nil)
+ err := rsa.VerifyPKCS1v15(pub, ch, hashed, decodeHex(sig.Sig))
+ want := shouldPass(sig.Result, sig.Flags, flagsShouldPass)
+ if (err == nil) != want {
+ t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/internal/wycheproof/wycheproof_test.go b/local_crypto_patch/contents/internal/wycheproof/wycheproof_test.go
new file mode 100644
index 0000000000..bbaae3b338
--- /dev/null
+++ b/local_crypto_patch/contents/internal/wycheproof/wycheproof_test.go
@@ -0,0 +1,141 @@
+// 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 wycheproof runs a set of the Wycheproof tests
+// provided by https://github.com/google/wycheproof.
+package wycheproof
+
+import (
+ "crypto"
+ "crypto/x509"
+ "encoding/hex"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ _ "crypto/sha1"
+ _ "crypto/sha256"
+ _ "crypto/sha512"
+)
+
+const wycheproofModVer = "v0.0.0-20191219022705-2196000605e4"
+
+var wycheproofTestVectorsDir string
+
+func TestMain(m *testing.M) {
+ flag.Parse()
+ if flag.Lookup("test.short").Value.(flag.Getter).Get().(bool) {
+ log.Println("skipping test that downloads testdata via 'go mod download' in short mode")
+ os.Exit(0)
+ }
+ if _, err := exec.LookPath("go"); err != nil {
+ log.Printf("skipping test because 'go' command is unavailable: %v", err)
+ os.Exit(0)
+ }
+ if os.Getenv("GO_BUILDER_FLAKY_NET") != "" {
+ log.Printf("skipping test because GO_BUILDER_FLAKY_NET is set")
+ os.Exit(0)
+ }
+
+ // Download the JSON test files from github.com/google/wycheproof
+ // using `go mod download -json` so the cached source of the testdata
+ // can be used in the following tests.
+ path := "github.com/google/wycheproof@" + wycheproofModVer
+ cmd := exec.Command("go", "mod", "download", "-json", path)
+ output, err := cmd.Output()
+ if err != nil {
+ log.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output)
+ }
+ var dm struct {
+ Dir string // absolute path to cached source root directory
+ }
+ if err := json.Unmarshal(output, &dm); err != nil {
+ log.Fatal(err)
+ }
+ // Now that the module has been downloaded, use the absolute path of the
+ // cached source as the root directory for all tests going forward.
+ wycheproofTestVectorsDir = filepath.Join(dm.Dir, "testvectors")
+ os.Exit(m.Run())
+}
+
+func readTestVector(t *testing.T, f string, dest interface{}) {
+ b, err := os.ReadFile(filepath.Join(wycheproofTestVectorsDir, f))
+ if err != nil {
+ t.Fatalf("failed to read json file: %v", err)
+ }
+ if err := json.Unmarshal(b, &dest); err != nil {
+ t.Fatalf("failed to unmarshal json file: %v", err)
+ }
+}
+
+func decodeHex(s string) []byte {
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
+
+func decodePublicKey(der string) interface{} {
+ d := decodeHex(der)
+ pub, err := x509.ParsePKIXPublicKey(d)
+ if err != nil {
+ panic(fmt.Sprintf("failed to parse DER encoded public key: %v", err))
+ }
+ return pub
+}
+
+func parseHash(h string) crypto.Hash {
+ switch h {
+ case "SHA-1":
+ return crypto.SHA1
+ case "SHA-256":
+ return crypto.SHA256
+ case "SHA-224":
+ return crypto.SHA224
+ case "SHA-384":
+ return crypto.SHA384
+ case "SHA-512":
+ return crypto.SHA512
+ case "SHA-512/224":
+ return crypto.SHA512_224
+ case "SHA-512/256":
+ return crypto.SHA512_256
+ default:
+ panic(fmt.Sprintf("could not identify SHA hash algorithm: %q", h))
+ }
+}
+
+// shouldPass returns whether or not the test should pass.
+// flagsShouldPass is a map associated with whether or not
+// a flag for an "acceptable" result should pass.
+// Every possible flag value that's associated with an
+// "acceptable" result should be explicitly specified,
+// otherwise the test will panic.
+func shouldPass(result string, flags []string, flagsShouldPass map[string]bool) bool {
+ switch result {
+ case "valid":
+ return true
+ case "invalid":
+ return false
+ case "acceptable":
+ for _, flag := range flags {
+ pass, ok := flagsShouldPass[flag]
+ if !ok {
+ panic(fmt.Sprintf("unspecified flag: %q", flag))
+ }
+ if !pass {
+ return false
+ }
+ }
+ return true // There are no flags, or all are meant to pass.
+ default:
+ panic(fmt.Sprintf("unexpected result: %v", result))
+ }
+}
diff --git a/local_crypto_patch/contents/md4/example_test.go b/local_crypto_patch/contents/md4/example_test.go
new file mode 100644
index 0000000000..db3f59b19b
--- /dev/null
+++ b/local_crypto_patch/contents/md4/example_test.go
@@ -0,0 +1,20 @@
+// 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 md4_test
+
+import (
+ "fmt"
+ "io"
+
+ "golang.org/x/crypto/md4"
+)
+
+func ExampleNew() {
+ h := md4.New()
+ data := "These pretzels are making me thirsty."
+ io.WriteString(h, data)
+ fmt.Printf("%x", h.Sum(nil))
+ // Output: 48c4e365090b30a32f084c4888deceaa
+}
diff --git a/local_crypto_patch/contents/md4/md4.go b/local_crypto_patch/contents/md4/md4.go
new file mode 100644
index 0000000000..7d9281e025
--- /dev/null
+++ b/local_crypto_patch/contents/md4/md4.go
@@ -0,0 +1,122 @@
+// Copyright 2009 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 md4 implements the MD4 hash algorithm as defined in RFC 1320.
+//
+// Deprecated: MD4 is cryptographically broken and should only be used
+// where compatibility with legacy systems, not security, is the goal. Instead,
+// use a secure hash like SHA-256 (from crypto/sha256).
+package md4
+
+import (
+ "crypto"
+ "hash"
+)
+
+func init() {
+ crypto.RegisterHash(crypto.MD4, New)
+}
+
+// The size of an MD4 checksum in bytes.
+const Size = 16
+
+// The blocksize of MD4 in bytes.
+const BlockSize = 64
+
+const (
+ _Chunk = 64
+ _Init0 = 0x67452301
+ _Init1 = 0xEFCDAB89
+ _Init2 = 0x98BADCFE
+ _Init3 = 0x10325476
+)
+
+// digest represents the partial evaluation of a checksum.
+type digest struct {
+ s [4]uint32
+ x [_Chunk]byte
+ nx int
+ len uint64
+}
+
+func (d *digest) Reset() {
+ d.s[0] = _Init0
+ d.s[1] = _Init1
+ d.s[2] = _Init2
+ d.s[3] = _Init3
+ d.nx = 0
+ d.len = 0
+}
+
+// New returns a new hash.Hash computing the MD4 checksum.
+func New() hash.Hash {
+ d := new(digest)
+ d.Reset()
+ return d
+}
+
+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.len += uint64(nn)
+ if d.nx > 0 {
+ n := len(p)
+ if n > _Chunk-d.nx {
+ n = _Chunk - d.nx
+ }
+ for i := 0; i < n; i++ {
+ d.x[d.nx+i] = p[i]
+ }
+ d.nx += n
+ if d.nx == _Chunk {
+ _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 := new(digest)
+ *d = *d0
+
+ // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
+ len := d.len
+ var tmp [64]byte
+ tmp[0] = 0x80
+ if len%64 < 56 {
+ d.Write(tmp[0 : 56-len%64])
+ } else {
+ d.Write(tmp[0 : 64+56-len%64])
+ }
+
+ // Length in bits.
+ len <<= 3
+ for i := uint(0); i < 8; i++ {
+ tmp[i] = byte(len >> (8 * i))
+ }
+ d.Write(tmp[0:8])
+
+ if d.nx != 0 {
+ panic("d.nx != 0")
+ }
+
+ for _, s := range d.s {
+ in = append(in, byte(s>>0))
+ in = append(in, byte(s>>8))
+ in = append(in, byte(s>>16))
+ in = append(in, byte(s>>24))
+ }
+ return in
+}
diff --git a/local_crypto_patch/contents/md4/md4_test.go b/local_crypto_patch/contents/md4/md4_test.go
new file mode 100644
index 0000000000..b56edd7875
--- /dev/null
+++ b/local_crypto_patch/contents/md4/md4_test.go
@@ -0,0 +1,71 @@
+// Copyright 2009 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 md4
+
+import (
+ "fmt"
+ "io"
+ "testing"
+)
+
+type md4Test struct {
+ out string
+ in string
+}
+
+var golden = []md4Test{
+ {"31d6cfe0d16ae931b73c59d7e0c089c0", ""},
+ {"bde52cb31de33e46245e05fbdbd6fb24", "a"},
+ {"ec388dd78999dfc7cf4632465693b6bf", "ab"},
+ {"a448017aaf21d8525fc10ae87aa6729d", "abc"},
+ {"41decd8f579255c5200f86a4bb3ba740", "abcd"},
+ {"9803f4a34e8eb14f96adba49064a0c41", "abcde"},
+ {"804e7f1c2586e50b49ac65db5b645131", "abcdef"},
+ {"752f4adfe53d1da0241b5bc216d098fc", "abcdefg"},
+ {"ad9daf8d49d81988590a6f0e745d15dd", "abcdefgh"},
+ {"1e4e28b05464316b56402b3815ed2dfd", "abcdefghi"},
+ {"dc959c6f5d6f9e04e4380777cc964b3d", "abcdefghij"},
+ {"1b5701e265778898ef7de5623bbe7cc0", "Discard medicine more than two years old."},
+ {"d7f087e090fe7ad4a01cb59dacc9a572", "He who has a shady past knows that nice guys finish last."},
+ {"a6f8fd6df617c72837592fc3570595c9", "I wouldn't marry him with a ten foot pole."},
+ {"c92a84a9526da8abc240c05d6b1a1ce0", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"},
+ {"f6013160c4dcb00847069fee3bb09803", "The days of the digital watch are numbered. -Tom Stoppard"},
+ {"2c3bb64f50b9107ed57640fe94bec09f", "Nepal premier won't resign."},
+ {"45b7d8a32c7806f2f7f897332774d6e4", "For every action there is an equal and opposite government program."},
+ {"b5b4f9026b175c62d7654bdc3a1cd438", "His money is twice tainted: 'taint yours and 'taint mine."},
+ {"caf44e80f2c20ce19b5ba1cab766e7bd", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"},
+ {"191fae6707f496aa54a6bce9f2ecf74d", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"},
+ {"9ddc753e7a4ccee6081cd1b45b23a834", "size: a.out: bad magic"},
+ {"8d050f55b1cadb9323474564be08a521", "The major problem is with sendmail. -Mark Horton"},
+ {"ad6e2587f74c3e3cc19146f6127fa2e3", "Give me a rock, paper and scissors and I will move the world. CCFestoon"},
+ {"1d616d60a5fabe85589c3f1566ca7fca", "If the enemy is within range, then so are you."},
+ {"aec3326a4f496a2ced65a1963f84577f", "It's well we cannot hear the screams/That we create in others' dreams."},
+ {"77b4fd762d6b9245e61c50bf6ebf118b", "You remind me of a TV show, but that's all right: I watch it anyway."},
+ {"e8f48c726bae5e516f6ddb1a4fe62438", "C is as portable as Stonehedge!!"},
+ {"a3a84366e7219e887423b01f9be7166e", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"},
+ {"a6b7aa35157e984ef5d9b7f32e5fbb52", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"},
+ {"75661f0545955f8f9abeeb17845f3fd6", "How can you write a big system without C++? -Paul Glick"},
+}
+
+func TestGolden(t *testing.T) {
+ for i := 0; i < len(golden); i++ {
+ g := golden[i]
+ c := New()
+ for j := 0; j < 3; j++ {
+ if j < 2 {
+ io.WriteString(c, g.in)
+ } else {
+ io.WriteString(c, g.in[0:len(g.in)/2])
+ c.Sum(nil)
+ io.WriteString(c, g.in[len(g.in)/2:])
+ }
+ s := fmt.Sprintf("%x", c.Sum(nil))
+ if s != g.out {
+ t.Fatalf("md4[%d](%s) = %s want %s", j, g.in, s, g.out)
+ }
+ c.Reset()
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/md4/md4block.go b/local_crypto_patch/contents/md4/md4block.go
new file mode 100644
index 0000000000..5ea1ba966e
--- /dev/null
+++ b/local_crypto_patch/contents/md4/md4block.go
@@ -0,0 +1,91 @@
+// Copyright 2009 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.
+
+// MD4 block step.
+// In its own file so that a faster assembly or C version
+// can be substituted easily.
+
+package md4
+
+import "math/bits"
+
+var shift1 = []int{3, 7, 11, 19}
+var shift2 = []int{3, 5, 9, 13}
+var shift3 = []int{3, 9, 11, 15}
+
+var xIndex2 = []uint{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}
+var xIndex3 = []uint{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
+
+func _Block(dig *digest, p []byte) int {
+ a := dig.s[0]
+ b := dig.s[1]
+ c := dig.s[2]
+ d := dig.s[3]
+ n := 0
+ var X [16]uint32
+ for len(p) >= _Chunk {
+ aa, bb, cc, dd := a, b, c, d
+
+ 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
+ }
+
+ // If this needs to be made faster in the future,
+ // the usual trick is to unroll each of these
+ // loops by a factor of 4; that lets you replace
+ // the shift[] lookups with constants and,
+ // with suitable variable renaming in each
+ // unrolled body, delete the a, b, c, d = d, a, b, c
+ // (or you can let the optimizer do the renaming).
+ //
+ // The index variables are uint so that % by a power
+ // of two can be optimized easily by a compiler.
+
+ // Round 1.
+ for i := uint(0); i < 16; i++ {
+ x := i
+ s := shift1[i%4]
+ f := ((c ^ d) & b) ^ d
+ a += f + X[x]
+ a = bits.RotateLeft32(a, s)
+ a, b, c, d = d, a, b, c
+ }
+
+ // Round 2.
+ for i := uint(0); i < 16; i++ {
+ x := xIndex2[i]
+ s := shift2[i%4]
+ g := (b & c) | (b & d) | (c & d)
+ a += g + X[x] + 0x5a827999
+ a = bits.RotateLeft32(a, s)
+ a, b, c, d = d, a, b, c
+ }
+
+ // Round 3.
+ for i := uint(0); i < 16; i++ {
+ x := xIndex3[i]
+ s := shift3[i%4]
+ h := b ^ c ^ d
+ a += h + X[x] + 0x6ed9eba1
+ a = bits.RotateLeft32(a, s)
+ a, b, c, d = d, a, b, c
+ }
+
+ a += aa
+ b += bb
+ c += cc
+ d += dd
+
+ p = p[_Chunk:]
+ n += _Chunk
+ }
+
+ dig.s[0] = a
+ dig.s[1] = b
+ dig.s[2] = c
+ dig.s[3] = d
+ return n
+}
diff --git a/local_crypto_patch/contents/nacl/auth/auth.go b/local_crypto_patch/contents/nacl/auth/auth.go
new file mode 100644
index 0000000000..136093841f
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/auth/auth.go
@@ -0,0 +1,49 @@
+// 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 auth authenticates a message using a secret key.
+//
+// This package is interoperable with [NaCl].
+//
+// The auth package is essentially a wrapper for HMAC-SHA-512 (implemented by
+// crypto/hmac and crypto/sha512), truncated to 32 bytes. It is [frozen] and is
+// not accepting new features.
+//
+// [NaCl]: https://nacl.cr.yp.to/auth.html
+// [frozen]: https://go.dev/wiki/Frozen
+package auth
+
+import (
+ "crypto/hmac"
+ "crypto/sha512"
+)
+
+const (
+ // Size is the size, in bytes, of an authenticated digest.
+ Size = 32
+ // KeySize is the size, in bytes, of an authentication key.
+ KeySize = 32
+)
+
+// Sum generates an authenticator for m using a secret key and returns the
+// 32-byte digest.
+func Sum(m []byte, key *[KeySize]byte) *[Size]byte {
+ mac := hmac.New(sha512.New, key[:])
+ mac.Write(m)
+ out := new([Size]byte)
+ copy(out[:], mac.Sum(nil)[:Size])
+ return out
+}
+
+// Verify checks that digest is a valid authenticator of message m under the
+// given secret key. Verify does not leak timing information.
+func Verify(digest []byte, m []byte, key *[KeySize]byte) bool {
+ if len(digest) != Size {
+ return false
+ }
+ mac := hmac.New(sha512.New, key[:])
+ mac.Write(m)
+ expectedMAC := mac.Sum(nil) // first 256 bits of 512-bit sum
+ return hmac.Equal(digest, expectedMAC[:Size])
+}
diff --git a/local_crypto_patch/contents/nacl/auth/auth_test.go b/local_crypto_patch/contents/nacl/auth/auth_test.go
new file mode 100644
index 0000000000..92074b50b6
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/auth/auth_test.go
@@ -0,0 +1,172 @@
+// 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 auth
+
+import (
+ "bytes"
+ rand "crypto/rand"
+ mrand "math/rand"
+ "testing"
+)
+
+// Test cases are from RFC 4231, and match those present in the tests directory
+// of the download here: https://nacl.cr.yp.to/install.html
+var testCases = []struct {
+ key [32]byte
+ msg []byte
+ out [32]byte
+}{
+ {
+ key: [32]byte{
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b,
+ },
+ msg: []byte("Hi There"),
+ out: [32]byte{
+ 0x87, 0xaa, 0x7c, 0xde, 0xa5, 0xef, 0x61, 0x9d,
+ 0x4f, 0xf0, 0xb4, 0x24, 0x1a, 0x1d, 0x6c, 0xb0,
+ 0x23, 0x79, 0xf4, 0xe2, 0xce, 0x4e, 0xc2, 0x78,
+ 0x7a, 0xd0, 0xb3, 0x05, 0x45, 0xe1, 0x7c, 0xde,
+ },
+ },
+ {
+ key: [32]byte{'J', 'e', 'f', 'e'},
+ msg: []byte("what do ya want for nothing?"),
+ out: [32]byte{
+ 0x16, 0x4b, 0x7a, 0x7b, 0xfc, 0xf8, 0x19, 0xe2,
+ 0xe3, 0x95, 0xfb, 0xe7, 0x3b, 0x56, 0xe0, 0xa3,
+ 0x87, 0xbd, 0x64, 0x22, 0x2e, 0x83, 0x1f, 0xd6,
+ 0x10, 0x27, 0x0c, 0xd7, 0xea, 0x25, 0x05, 0x54,
+ },
+ },
+ {
+ key: [32]byte{
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa,
+ },
+ msg: []byte{ // 50 bytes of 0xdd
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+ 0xdd, 0xdd,
+ },
+ out: [32]byte{
+ 0xfa, 0x73, 0xb0, 0x08, 0x9d, 0x56, 0xa2, 0x84,
+ 0xef, 0xb0, 0xf0, 0x75, 0x6c, 0x89, 0x0b, 0xe9,
+ 0xb1, 0xb5, 0xdb, 0xdd, 0x8e, 0xe8, 0x1a, 0x36,
+ 0x55, 0xf8, 0x3e, 0x33, 0xb2, 0x27, 0x9d, 0x39,
+ },
+ },
+ {
+ key: [32]byte{
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19,
+ },
+ msg: []byte{
+ 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+ 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+ 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+ 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+ 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+ 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+ 0xcd, 0xcd,
+ },
+ out: [32]byte{
+ 0xb0, 0xba, 0x46, 0x56, 0x37, 0x45, 0x8c, 0x69,
+ 0x90, 0xe5, 0xa8, 0xc5, 0xf6, 0x1d, 0x4a, 0xf7,
+ 0xe5, 0x76, 0xd9, 0x7f, 0xf9, 0x4b, 0x87, 0x2d,
+ 0xe7, 0x6f, 0x80, 0x50, 0x36, 0x1e, 0xe3, 0xdb,
+ },
+ },
+}
+
+func TestSum(t *testing.T) {
+ for i, test := range testCases {
+ tag := Sum(test.msg, &test.key)
+ if !bytes.Equal(tag[:], test.out[:]) {
+ t.Errorf("#%d: Sum: got\n%x\nwant\n%x", i, tag, test.out)
+ }
+ }
+}
+
+func TestVerify(t *testing.T) {
+ wrongMsg := []byte("unknown msg")
+
+ for i, test := range testCases {
+ if !Verify(test.out[:], test.msg, &test.key) {
+ t.Errorf("#%d: Verify(%x, %q, %x) failed", i, test.out, test.msg, test.key)
+ }
+ if Verify(test.out[:], wrongMsg, &test.key) {
+ t.Errorf("#%d: Verify(%x, %q, %x) unexpectedly passed", i, test.out, wrongMsg, test.key)
+ }
+ }
+}
+
+func TestStress(t *testing.T) {
+ if testing.Short() {
+ t.Skip("exhaustiveness test")
+ }
+
+ var key [32]byte
+ msg := make([]byte, 10000)
+ prng := mrand.New(mrand.NewSource(0))
+
+ // copied from tests/auth5.c in nacl
+ for i := 0; i < 10000; i++ {
+ if _, err := rand.Read(key[:]); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := rand.Read(msg[:i]); err != nil {
+ t.Fatal(err)
+ }
+ tag := Sum(msg[:i], &key)
+ if !Verify(tag[:], msg[:i], &key) {
+ t.Errorf("#%d: unexpected failure from Verify", i)
+ }
+ if i > 0 {
+ msgIndex := prng.Intn(i)
+ oldMsgByte := msg[msgIndex]
+ msg[msgIndex] += byte(1 + prng.Intn(255))
+ if Verify(tag[:], msg[:i], &key) {
+ t.Errorf("#%d: unexpected success from Verify after corrupting message", i)
+ }
+ msg[msgIndex] = oldMsgByte
+
+ tag[prng.Intn(len(tag))] += byte(1 + prng.Intn(255))
+ if Verify(tag[:], msg[:i], &key) {
+ t.Errorf("#%d: unexpected success from Verify after corrupting authenticator", i)
+ }
+ }
+ }
+}
+
+func BenchmarkAuth(b *testing.B) {
+ var key [32]byte
+ if _, err := rand.Read(key[:]); err != nil {
+ b.Fatal(err)
+ }
+ buf := make([]byte, 1024)
+ if _, err := rand.Read(buf[:]); err != nil {
+ b.Fatal(err)
+ }
+
+ b.SetBytes(int64(len(buf)))
+ b.ReportAllocs()
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ tag := Sum(buf, &key)
+ if Verify(tag[:], buf, &key) == false {
+ b.Fatal("unexpected failure from Verify")
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/nacl/auth/example_test.go b/local_crypto_patch/contents/nacl/auth/example_test.go
new file mode 100644
index 0000000000..02a2cd6c46
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/auth/example_test.go
@@ -0,0 +1,36 @@
+// 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 auth_test
+
+import (
+ "encoding/hex"
+ "fmt"
+
+ "golang.org/x/crypto/nacl/auth"
+)
+
+func Example() {
+ // Load your secret key from a safe place and reuse it across multiple
+ // Sum calls. (Obviously don't use this example key for anything
+ // real.) If you want to convert a passphrase to a key, use a suitable
+ // package like bcrypt or scrypt.
+ secretKeyBytes, err := hex.DecodeString("6368616e676520746869732070617373776f726420746f206120736563726574")
+ if err != nil {
+ panic(err)
+ }
+
+ var secretKey [32]byte
+ copy(secretKey[:], secretKeyBytes)
+
+ mac := auth.Sum([]byte("hello world"), &secretKey)
+ fmt.Printf("%x\n", *mac)
+ result := auth.Verify(mac[:], []byte("hello world"), &secretKey)
+ fmt.Println(result)
+ badResult := auth.Verify(mac[:], []byte("different message"), &secretKey)
+ fmt.Println(badResult)
+ // Output: eca5a521f3d77b63f567fb0cb6f5f2d200641bc8dada42f60c5f881260c30317
+ // true
+ // false
+}
diff --git a/local_crypto_patch/contents/nacl/box/box.go b/local_crypto_patch/contents/nacl/box/box.go
new file mode 100644
index 0000000000..357bdc773c
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/box/box.go
@@ -0,0 +1,182 @@
+// 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 box authenticates and encrypts small messages using public-key cryptography.
+
+Box uses Curve25519, XSalsa20 and Poly1305 to encrypt and authenticate
+messages. The length of messages is not hidden.
+
+It is the caller's responsibility to ensure the uniqueness of nonces—for
+example, by using nonce 1 for the first message, nonce 2 for the second
+message, etc. Nonces are long enough that randomly generated nonces have
+negligible risk of collision.
+
+Messages should be small because:
+
+1. The whole message needs to be held in memory to be processed.
+
+2. Using large messages pressures implementations on small machines to decrypt
+and process plaintext before authenticating it. This is very dangerous, and
+this API does not allow it, but a protocol that uses excessive message sizes
+might present some implementations with no other choice.
+
+3. Fixed overheads will be sufficiently amortised by messages as small as 8KB.
+
+4. Performance may be improved by working with messages that fit into data caches.
+
+Thus large amounts of data should be chunked so that each message is small.
+(Each message still needs a unique nonce.) If in doubt, 16KB is a reasonable
+chunk size.
+
+This package is interoperable with NaCl: https://nacl.cr.yp.to/box.html.
+Anonymous sealing/opening is an extension of NaCl defined by and interoperable
+with libsodium:
+https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes.
+*/
+package box
+
+import (
+ cryptorand "crypto/rand"
+ "io"
+
+ "golang.org/x/crypto/blake2b"
+ "golang.org/x/crypto/curve25519"
+ "golang.org/x/crypto/nacl/secretbox"
+ "golang.org/x/crypto/salsa20/salsa"
+)
+
+const (
+ // Overhead is the number of bytes of overhead when boxing a message.
+ Overhead = secretbox.Overhead
+
+ // AnonymousOverhead is the number of bytes of overhead when using anonymous
+ // sealed boxes.
+ AnonymousOverhead = Overhead + 32
+)
+
+// GenerateKey generates a new public/private key pair suitable for use with
+// Seal and Open.
+func GenerateKey(rand io.Reader) (publicKey, privateKey *[32]byte, err error) {
+ publicKey = new([32]byte)
+ privateKey = new([32]byte)
+ _, err = io.ReadFull(rand, privateKey[:])
+ if err != nil {
+ publicKey = nil
+ privateKey = nil
+ return
+ }
+
+ curve25519.ScalarBaseMult(publicKey, privateKey)
+ return
+}
+
+var zeros [16]byte
+
+// Precompute calculates the shared key between peersPublicKey and privateKey
+// and writes it to sharedKey. The shared key can be used with
+// OpenAfterPrecomputation and SealAfterPrecomputation to speed up processing
+// when using the same pair of keys repeatedly.
+func Precompute(sharedKey, peersPublicKey, privateKey *[32]byte) {
+ curve25519.ScalarMult(sharedKey, privateKey, peersPublicKey)
+ salsa.HSalsa20(sharedKey, &zeros, sharedKey, &salsa.Sigma)
+}
+
+// Seal appends an encrypted and authenticated copy of message to out, which
+// will be Overhead bytes longer than the original and must not overlap it. The
+// nonce must be unique for each distinct message for a given pair of keys.
+func Seal(out, message []byte, nonce *[24]byte, peersPublicKey, privateKey *[32]byte) []byte {
+ var sharedKey [32]byte
+ Precompute(&sharedKey, peersPublicKey, privateKey)
+ return secretbox.Seal(out, message, nonce, &sharedKey)
+}
+
+// SealAfterPrecomputation performs the same actions as Seal, but takes a
+// shared key as generated by Precompute.
+func SealAfterPrecomputation(out, message []byte, nonce *[24]byte, sharedKey *[32]byte) []byte {
+ return secretbox.Seal(out, message, nonce, sharedKey)
+}
+
+// Open authenticates and decrypts a box produced by Seal and appends the
+// message to out, which must not overlap box. The output will be Overhead
+// bytes smaller than box.
+func Open(out, box []byte, nonce *[24]byte, peersPublicKey, privateKey *[32]byte) ([]byte, bool) {
+ var sharedKey [32]byte
+ Precompute(&sharedKey, peersPublicKey, privateKey)
+ return secretbox.Open(out, box, nonce, &sharedKey)
+}
+
+// OpenAfterPrecomputation performs the same actions as Open, but takes a
+// shared key as generated by Precompute.
+func OpenAfterPrecomputation(out, box []byte, nonce *[24]byte, sharedKey *[32]byte) ([]byte, bool) {
+ return secretbox.Open(out, box, nonce, sharedKey)
+}
+
+// SealAnonymous appends an encrypted and authenticated copy of message to out,
+// which will be AnonymousOverhead bytes longer than the original and must not
+// overlap it. This differs from Seal in that the sender is not required to
+// provide a private key.
+func SealAnonymous(out, message []byte, recipient *[32]byte, rand io.Reader) ([]byte, error) {
+ if rand == nil {
+ rand = cryptorand.Reader
+ }
+ ephemeralPub, ephemeralPriv, err := GenerateKey(rand)
+ if err != nil {
+ return nil, err
+ }
+
+ var nonce [24]byte
+ if err := sealNonce(ephemeralPub, recipient, &nonce); err != nil {
+ return nil, err
+ }
+
+ if total := len(out) + AnonymousOverhead + len(message); cap(out) < total {
+ original := out
+ out = make([]byte, 0, total)
+ out = append(out, original...)
+ }
+ out = append(out, ephemeralPub[:]...)
+
+ return Seal(out, message, &nonce, recipient, ephemeralPriv), nil
+}
+
+// OpenAnonymous authenticates and decrypts a box produced by SealAnonymous and
+// appends the message to out, which must not overlap box. The output will be
+// AnonymousOverhead bytes smaller than box.
+func OpenAnonymous(out, box []byte, publicKey, privateKey *[32]byte) (message []byte, ok bool) {
+ if len(box) < AnonymousOverhead {
+ return nil, false
+ }
+
+ var ephemeralPub [32]byte
+ copy(ephemeralPub[:], box[:32])
+
+ var nonce [24]byte
+ if err := sealNonce(&ephemeralPub, publicKey, &nonce); err != nil {
+ return nil, false
+ }
+
+ return Open(out, box[32:], &nonce, &ephemeralPub, privateKey)
+}
+
+// sealNonce generates a 24 byte nonce that is a blake2b digest of the
+// ephemeral public key and the receiver's public key.
+func sealNonce(ephemeralPub, peersPublicKey *[32]byte, nonce *[24]byte) error {
+ h, err := blake2b.New(24, nil)
+ if err != nil {
+ return err
+ }
+
+ if _, err = h.Write(ephemeralPub[:]); err != nil {
+ return err
+ }
+
+ if _, err = h.Write(peersPublicKey[:]); err != nil {
+ return err
+ }
+
+ h.Sum(nonce[:0])
+
+ return nil
+}
diff --git a/local_crypto_patch/contents/nacl/box/box_test.go b/local_crypto_patch/contents/nacl/box/box_test.go
new file mode 100644
index 0000000000..404925743c
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/box/box_test.go
@@ -0,0 +1,181 @@
+// 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 box
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "testing"
+
+ "golang.org/x/crypto/curve25519"
+)
+
+func TestSealOpen(t *testing.T) {
+ publicKey1, privateKey1, _ := GenerateKey(rand.Reader)
+ publicKey2, privateKey2, _ := GenerateKey(rand.Reader)
+
+ if *privateKey1 == *privateKey2 {
+ t.Fatalf("private keys are equal!")
+ }
+ if *publicKey1 == *publicKey2 {
+ t.Fatalf("public keys are equal!")
+ }
+ message := []byte("test message")
+ var nonce [24]byte
+
+ box := Seal(nil, message, &nonce, publicKey1, privateKey2)
+ opened, ok := Open(nil, box, &nonce, publicKey2, privateKey1)
+ if !ok {
+ t.Fatalf("failed to open box")
+ }
+
+ if !bytes.Equal(opened, message) {
+ t.Fatalf("got %x, want %x", opened, message)
+ }
+
+ for i := range box {
+ box[i] ^= 0x40
+ _, ok := Open(nil, box, &nonce, publicKey2, privateKey1)
+ if ok {
+ t.Fatalf("opened box with byte %d corrupted", i)
+ }
+ box[i] ^= 0x40
+ }
+}
+
+func TestBox(t *testing.T) {
+ var privateKey1, privateKey2 [32]byte
+ for i := range privateKey1[:] {
+ privateKey1[i] = 1
+ }
+ for i := range privateKey2[:] {
+ privateKey2[i] = 2
+ }
+
+ var publicKey1 [32]byte
+ curve25519.ScalarBaseMult(&publicKey1, &privateKey1)
+ var message [64]byte
+ for i := range message[:] {
+ message[i] = 3
+ }
+
+ var nonce [24]byte
+ for i := range nonce[:] {
+ nonce[i] = 4
+ }
+
+ box := Seal(nil, message[:], &nonce, &publicKey1, &privateKey2)
+
+ // expected was generated using the C implementation of NaCl.
+ expected, _ := hex.DecodeString("78ea30b19d2341ebbdba54180f821eec265cf86312549bea8a37652a8bb94f07b78a73ed1708085e6ddd0e943bbdeb8755079a37eb31d86163ce241164a47629c0539f330b4914cd135b3855bc2a2dfc")
+
+ if !bytes.Equal(box, expected) {
+ t.Fatalf("box didn't match, got\n%x\n, expected\n%x", box, expected)
+ }
+}
+
+func TestSealOpenAnonymous(t *testing.T) {
+ publicKey, privateKey, _ := GenerateKey(rand.Reader)
+ message := []byte("test message")
+
+ box, err := SealAnonymous(nil, message, publicKey, nil)
+ if err != nil {
+ t.Fatalf("Unexpected error sealing %v", err)
+ }
+ opened, ok := OpenAnonymous(nil, box, publicKey, privateKey)
+ if !ok {
+ t.Fatalf("failed to open box")
+ }
+
+ if !bytes.Equal(opened, message) {
+ t.Fatalf("got %x, want %x", opened, message)
+ }
+
+ for i := range box {
+ box[i] ^= 0x40
+ _, ok := OpenAnonymous(nil, box, publicKey, privateKey)
+ if ok {
+ t.Fatalf("opened box with byte %d corrupted", i)
+ }
+ box[i] ^= 0x40
+ }
+
+ // allocates new slice if out isn't long enough
+ out := []byte("hello")
+ orig := append([]byte(nil), out...)
+ box, err = SealAnonymous(out, message, publicKey, nil)
+ if err != nil {
+ t.Fatalf("Unexpected error sealing %v", err)
+ }
+ if !bytes.Equal(out, orig) {
+ t.Fatal("expected out to be unchanged")
+ }
+ if !bytes.HasPrefix(box, orig) {
+ t.Fatal("expected out to be copied to returned slice")
+ }
+ _, ok = OpenAnonymous(nil, box[len(out):], publicKey, privateKey)
+ if !ok {
+ t.Fatalf("failed to open box")
+ }
+
+ // uses provided slice if it's long enough
+ out = append(make([]byte, 0, 1000), []byte("hello")...)
+ orig = append([]byte(nil), out...)
+ box, err = SealAnonymous(out, message, publicKey, nil)
+ if err != nil {
+ t.Fatalf("Unexpected error sealing %v", err)
+ }
+ if !bytes.Equal(out, orig) {
+ t.Fatal("expected out to be unchanged")
+ }
+ if &out[0] != &box[0] {
+ t.Fatal("expected box to point to out")
+ }
+ _, ok = OpenAnonymous(nil, box[len(out):], publicKey, privateKey)
+ if !ok {
+ t.Fatalf("failed to open box")
+ }
+}
+
+func TestSealedBox(t *testing.T) {
+ var privateKey [32]byte
+ for i := range privateKey[:] {
+ privateKey[i] = 1
+ }
+
+ var publicKey [32]byte
+ curve25519.ScalarBaseMult(&publicKey, &privateKey)
+ var message [64]byte
+ for i := range message[:] {
+ message[i] = 3
+ }
+
+ fakeRand := bytes.NewReader([]byte{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5})
+ box, err := SealAnonymous(nil, message[:], &publicKey, fakeRand)
+ if err != nil {
+ t.Fatalf("Unexpected error sealing %v", err)
+ }
+
+ // expected was generated using the C implementation of libsodium with a
+ // random implementation that always returns 5.
+ // https://gist.github.com/mastahyeti/942ec3f175448d68fed25018adbce5a7
+ expected, _ := hex.DecodeString("50a61409b1ddd0325e9b16b700e719e9772c07000b1bd7786e907c653d20495d2af1697137a53b1b1dfc9befc49b6eeb38f86be720e155eb2be61976d2efb34d67ecd44a6ad634625eb9c288bfc883431a84ab0f5557dfe673aa6f74c19f033e648a947358cfcc606397fa1747d5219a")
+
+ if !bytes.Equal(box, expected) {
+ t.Fatalf("box didn't match, got\n%x\n, expected\n%x", box, expected)
+ }
+
+ // box was generated using the C implementation of libsodium.
+ // https://gist.github.com/mastahyeti/942ec3f175448d68fed25018adbce5a7
+ box, _ = hex.DecodeString("3462e0640728247a6f581e3812850d6edc3dcad1ea5d8184c072f62fb65cb357e27ffa8b76f41656bc66a0882c4d359568410665746d27462a700f01e314f382edd7aae9064879b0f8ba7b88866f88f5e4fbd7649c850541877f9f33ebd25d46d9cbcce09b69a9ba07f0eb1d105d4264")
+ result, ok := OpenAnonymous(nil, box, &publicKey, &privateKey)
+ if !ok {
+ t.Fatalf("failed to open box")
+ }
+ if !bytes.Equal(result, message[:]) {
+ t.Fatalf("message didn't match, got\n%x\n, expected\n%x", result, message[:])
+ }
+}
diff --git a/local_crypto_patch/contents/nacl/box/example_test.go b/local_crypto_patch/contents/nacl/box/example_test.go
new file mode 100644
index 0000000000..25e42d2be9
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/box/example_test.go
@@ -0,0 +1,95 @@
+package box_test
+
+import (
+ crypto_rand "crypto/rand" // Custom so it's clear which rand we're using.
+ "fmt"
+ "io"
+
+ "golang.org/x/crypto/nacl/box"
+)
+
+func Example() {
+ senderPublicKey, senderPrivateKey, err := box.GenerateKey(crypto_rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+
+ recipientPublicKey, recipientPrivateKey, err := box.GenerateKey(crypto_rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+
+ // You must use a different nonce for each message you encrypt with the
+ // same key. Since the nonce here is 192 bits long, a random value
+ // provides a sufficiently small probability of repeats.
+ var nonce [24]byte
+ if _, err := io.ReadFull(crypto_rand.Reader, nonce[:]); err != nil {
+ panic(err)
+ }
+
+ msg := []byte("Alas, poor Yorick! I knew him, Horatio")
+ // This encrypts msg and appends the result to the nonce.
+ encrypted := box.Seal(nonce[:], msg, &nonce, recipientPublicKey, senderPrivateKey)
+
+ // The recipient can decrypt the message using their private key and the
+ // sender's public key. When you decrypt, you must use the same nonce you
+ // used to encrypt the message. One way to achieve this is to store the
+ // nonce alongside the encrypted message. Above, we stored the nonce in the
+ // first 24 bytes of the encrypted text.
+ var decryptNonce [24]byte
+ copy(decryptNonce[:], encrypted[:24])
+ decrypted, ok := box.Open(nil, encrypted[24:], &decryptNonce, senderPublicKey, recipientPrivateKey)
+ if !ok {
+ panic("decryption error")
+ }
+ fmt.Println(string(decrypted))
+ // Output: Alas, poor Yorick! I knew him, Horatio
+}
+
+func Example_precompute() {
+ senderPublicKey, senderPrivateKey, err := box.GenerateKey(crypto_rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+
+ recipientPublicKey, recipientPrivateKey, err := box.GenerateKey(crypto_rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+
+ // The shared key can be used to speed up processing when using the same
+ // pair of keys repeatedly.
+ sharedEncryptKey := new([32]byte)
+ box.Precompute(sharedEncryptKey, recipientPublicKey, senderPrivateKey)
+
+ // You must use a different nonce for each message you encrypt with the
+ // same key. Since the nonce here is 192 bits long, a random value
+ // provides a sufficiently small probability of repeats.
+ var nonce [24]byte
+ if _, err := io.ReadFull(crypto_rand.Reader, nonce[:]); err != nil {
+ panic(err)
+ }
+
+ msg := []byte("A fellow of infinite jest, of most excellent fancy")
+ // This encrypts msg and appends the result to the nonce.
+ encrypted := box.SealAfterPrecomputation(nonce[:], msg, &nonce, sharedEncryptKey)
+
+ // The shared key can be used to speed up processing when using the same
+ // pair of keys repeatedly.
+ var sharedDecryptKey [32]byte
+ box.Precompute(&sharedDecryptKey, senderPublicKey, recipientPrivateKey)
+
+ // The recipient can decrypt the message using the shared key. When you
+ // decrypt, you must use the same nonce you used to encrypt the message.
+ // One way to achieve this is to store the nonce alongside the encrypted
+ // message. Above, we stored the nonce in the first 24 bytes of the
+ // encrypted text.
+ var decryptNonce [24]byte
+ copy(decryptNonce[:], encrypted[:24])
+ decrypted, ok := box.OpenAfterPrecomputation(nil, encrypted[24:], &decryptNonce, &sharedDecryptKey)
+ if !ok {
+ panic("decryption error")
+ }
+ fmt.Println(string(decrypted))
+ // Output: A fellow of infinite jest, of most excellent fancy
+}
diff --git a/local_crypto_patch/contents/nacl/secretbox/example_test.go b/local_crypto_patch/contents/nacl/secretbox/example_test.go
new file mode 100644
index 0000000000..789f4ff03f
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/secretbox/example_test.go
@@ -0,0 +1,53 @@
+// 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 secretbox_test
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "io"
+
+ "golang.org/x/crypto/nacl/secretbox"
+)
+
+func Example() {
+ // Load your secret key from a safe place and reuse it across multiple
+ // Seal calls. (Obviously don't use this example key for anything
+ // real.) If you want to convert a passphrase to a key, use a suitable
+ // package like bcrypt or scrypt.
+ secretKeyBytes, err := hex.DecodeString("6368616e676520746869732070617373776f726420746f206120736563726574")
+ if err != nil {
+ panic(err)
+ }
+
+ var secretKey [32]byte
+ copy(secretKey[:], secretKeyBytes)
+
+ // You must use a different nonce for each message you encrypt with the
+ // same key. Since the nonce here is 192 bits long, a random value
+ // provides a sufficiently small probability of repeats.
+ var nonce [24]byte
+ if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
+ panic(err)
+ }
+
+ // This encrypts "hello world" and appends the result to the nonce.
+ encrypted := secretbox.Seal(nonce[:], []byte("hello world"), &nonce, &secretKey)
+
+ // When you decrypt, you must use the same nonce and key you used to
+ // encrypt the message. One way to achieve this is to store the nonce
+ // alongside the encrypted message. Above, we stored the nonce in the first
+ // 24 bytes of the encrypted text.
+ var decryptNonce [24]byte
+ copy(decryptNonce[:], encrypted[:24])
+ decrypted, ok := secretbox.Open(nil, encrypted[24:], &decryptNonce, &secretKey)
+ if !ok {
+ panic("decryption error")
+ }
+
+ fmt.Println(string(decrypted))
+ // Output: hello world
+}
diff --git a/local_crypto_patch/contents/nacl/secretbox/secretbox.go b/local_crypto_patch/contents/nacl/secretbox/secretbox.go
new file mode 100644
index 0000000000..1fe600ad03
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/secretbox/secretbox.go
@@ -0,0 +1,173 @@
+// 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 secretbox encrypts and authenticates small messages.
+
+Secretbox uses XSalsa20 and Poly1305 to encrypt and authenticate messages with
+secret-key cryptography. The length of messages is not hidden.
+
+It is the caller's responsibility to ensure the uniqueness of nonces—for
+example, by using nonce 1 for the first message, nonce 2 for the second
+message, etc. Nonces are long enough that randomly generated nonces have
+negligible risk of collision.
+
+Messages should be small because:
+
+1. The whole message needs to be held in memory to be processed.
+
+2. Using large messages pressures implementations on small machines to decrypt
+and process plaintext before authenticating it. This is very dangerous, and
+this API does not allow it, but a protocol that uses excessive message sizes
+might present some implementations with no other choice.
+
+3. Fixed overheads will be sufficiently amortised by messages as small as 8KB.
+
+4. Performance may be improved by working with messages that fit into data caches.
+
+Thus large amounts of data should be chunked so that each message is small.
+(Each message still needs a unique nonce.) If in doubt, 16KB is a reasonable
+chunk size.
+
+This package is interoperable with NaCl: https://nacl.cr.yp.to/secretbox.html.
+*/
+package secretbox
+
+import (
+ "golang.org/x/crypto/internal/alias"
+ "golang.org/x/crypto/internal/poly1305"
+ "golang.org/x/crypto/salsa20/salsa"
+)
+
+// Overhead is the number of bytes of overhead when boxing a message.
+const Overhead = poly1305.TagSize
+
+// setup produces a sub-key and Salsa20 counter given a nonce and key.
+func setup(subKey *[32]byte, counter *[16]byte, nonce *[24]byte, key *[32]byte) {
+ // We use XSalsa20 for encryption so first we need to generate a
+ // key and nonce with HSalsa20.
+ var hNonce [16]byte
+ copy(hNonce[:], nonce[:])
+ salsa.HSalsa20(subKey, &hNonce, key, &salsa.Sigma)
+
+ // The final 8 bytes of the original nonce form the new nonce.
+ copy(counter[:], nonce[16:])
+}
+
+// sliceForAppend takes a slice and a requested number of bytes. It returns a
+// slice with the contents of the given slice followed by that many bytes and a
+// second slice that aliases into it and contains only the extra bytes. If the
+// original slice has sufficient capacity then no allocation is performed.
+func sliceForAppend(in []byte, n int) (head, tail []byte) {
+ if total := len(in) + n; cap(in) >= total {
+ head = in[:total]
+ } else {
+ head = make([]byte, total)
+ copy(head, in)
+ }
+ tail = head[len(in):]
+ return
+}
+
+// Seal appends an encrypted and authenticated copy of message to out, which
+// must not overlap message. The key and nonce pair must be unique for each
+// distinct message and the output will be Overhead bytes longer than message.
+func Seal(out, message []byte, nonce *[24]byte, key *[32]byte) []byte {
+ var subKey [32]byte
+ var counter [16]byte
+ setup(&subKey, &counter, nonce, key)
+
+ // The Poly1305 key is generated by encrypting 32 bytes of zeros. Since
+ // Salsa20 works with 64-byte blocks, we also generate 32 bytes of
+ // keystream as a side effect.
+ var firstBlock [64]byte
+ salsa.XORKeyStream(firstBlock[:], firstBlock[:], &counter, &subKey)
+
+ var poly1305Key [32]byte
+ copy(poly1305Key[:], firstBlock[:])
+
+ ret, out := sliceForAppend(out, len(message)+poly1305.TagSize)
+ if alias.AnyOverlap(out, message) {
+ panic("nacl: invalid buffer overlap")
+ }
+
+ // We XOR up to 32 bytes of message with the keystream generated from
+ // the first block.
+ firstMessageBlock := message
+ if len(firstMessageBlock) > 32 {
+ firstMessageBlock = firstMessageBlock[:32]
+ }
+
+ tagOut := out
+ out = out[poly1305.TagSize:]
+ for i, x := range firstMessageBlock {
+ out[i] = firstBlock[32+i] ^ x
+ }
+ message = message[len(firstMessageBlock):]
+ ciphertext := out
+ out = out[len(firstMessageBlock):]
+
+ // Now encrypt the rest.
+ counter[8] = 1
+ salsa.XORKeyStream(out, message, &counter, &subKey)
+
+ var tag [poly1305.TagSize]byte
+ poly1305.Sum(&tag, ciphertext, &poly1305Key)
+ copy(tagOut, tag[:])
+
+ return ret
+}
+
+// Open authenticates and decrypts a box produced by Seal and appends the
+// message to out, which must not overlap box. The output will be Overhead
+// bytes smaller than box.
+func Open(out, box []byte, nonce *[24]byte, key *[32]byte) ([]byte, bool) {
+ if len(box) < Overhead {
+ return nil, false
+ }
+
+ var subKey [32]byte
+ var counter [16]byte
+ setup(&subKey, &counter, nonce, key)
+
+ // The Poly1305 key is generated by encrypting 32 bytes of zeros. Since
+ // Salsa20 works with 64-byte blocks, we also generate 32 bytes of
+ // keystream as a side effect.
+ var firstBlock [64]byte
+ salsa.XORKeyStream(firstBlock[:], firstBlock[:], &counter, &subKey)
+
+ var poly1305Key [32]byte
+ copy(poly1305Key[:], firstBlock[:])
+ var tag [poly1305.TagSize]byte
+ copy(tag[:], box)
+
+ if !poly1305.Verify(&tag, box[poly1305.TagSize:], &poly1305Key) {
+ return nil, false
+ }
+
+ ret, out := sliceForAppend(out, len(box)-Overhead)
+ if alias.AnyOverlap(out, box) {
+ panic("nacl: invalid buffer overlap")
+ }
+
+ // We XOR up to 32 bytes of box with the keystream generated from
+ // the first block.
+ box = box[Overhead:]
+ firstMessageBlock := box
+ if len(firstMessageBlock) > 32 {
+ firstMessageBlock = firstMessageBlock[:32]
+ }
+ for i, x := range firstMessageBlock {
+ out[i] = firstBlock[32+i] ^ x
+ }
+
+ box = box[len(firstMessageBlock):]
+ out = out[len(firstMessageBlock):]
+
+ // Now decrypt the rest.
+ counter[8] = 1
+ salsa.XORKeyStream(out, box, &counter, &subKey)
+
+ return ret, true
+}
diff --git a/local_crypto_patch/contents/nacl/secretbox/secretbox_test.go b/local_crypto_patch/contents/nacl/secretbox/secretbox_test.go
new file mode 100644
index 0000000000..3c70b0f4b3
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/secretbox/secretbox_test.go
@@ -0,0 +1,154 @@
+// 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 secretbox
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "testing"
+)
+
+func TestSealOpen(t *testing.T) {
+ var key [32]byte
+ var nonce [24]byte
+
+ rand.Reader.Read(key[:])
+ rand.Reader.Read(nonce[:])
+
+ var box, opened []byte
+
+ for msgLen := 0; msgLen < 128; msgLen += 17 {
+ message := make([]byte, msgLen)
+ rand.Reader.Read(message)
+
+ box = Seal(box[:0], message, &nonce, &key)
+ var ok bool
+ opened, ok = Open(opened[:0], box, &nonce, &key)
+ if !ok {
+ t.Errorf("%d: failed to open box", msgLen)
+ continue
+ }
+
+ if !bytes.Equal(opened, message) {
+ t.Errorf("%d: got %x, expected %x", msgLen, opened, message)
+ continue
+ }
+ }
+
+ for i := range box {
+ box[i] ^= 0x20
+ _, ok := Open(opened[:0], box, &nonce, &key)
+ if ok {
+ t.Errorf("box was opened after corrupting byte %d", i)
+ }
+ box[i] ^= 0x20
+ }
+}
+
+func TestSecretBox(t *testing.T) {
+ var key [32]byte
+ var nonce [24]byte
+ var message [64]byte
+
+ for i := range key[:] {
+ key[i] = 1
+ }
+ for i := range nonce[:] {
+ nonce[i] = 2
+ }
+ for i := range message[:] {
+ message[i] = 3
+ }
+
+ box := Seal(nil, message[:], &nonce, &key)
+ // expected was generated using the C implementation of NaCl.
+ expected, _ := hex.DecodeString("8442bc313f4626f1359e3b50122b6ce6fe66ddfe7d39d14e637eb4fd5b45beadab55198df6ab5368439792a23c87db70acb6156dc5ef957ac04f6276cf6093b84be77ff0849cc33e34b7254d5a8f65ad")
+
+ if !bytes.Equal(box, expected) {
+ t.Fatalf("box didn't match, got\n%x\n, expected\n%x", box, expected)
+ }
+}
+
+func TestAppend(t *testing.T) {
+ var key [32]byte
+ var nonce [24]byte
+ var message [8]byte
+
+ out := make([]byte, 4)
+ box := Seal(out, message[:], &nonce, &key)
+ if !bytes.Equal(box[:4], out[:4]) {
+ t.Fatalf("Seal didn't correctly append")
+ }
+
+ out = make([]byte, 4, 100)
+ box = Seal(out, message[:], &nonce, &key)
+ if !bytes.Equal(box[:4], out[:4]) {
+ t.Fatalf("Seal didn't correctly append with sufficient capacity.")
+ }
+}
+
+func benchmarkSealSize(b *testing.B, size int) {
+ message := make([]byte, size)
+ out := make([]byte, size+Overhead)
+ var nonce [24]byte
+ var key [32]byte
+
+ b.SetBytes(int64(size))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ out = Seal(out[:0], message, &nonce, &key)
+ }
+}
+
+func BenchmarkSeal8Bytes(b *testing.B) {
+ benchmarkSealSize(b, 8)
+}
+
+func BenchmarkSeal100Bytes(b *testing.B) {
+ benchmarkSealSize(b, 100)
+}
+
+func BenchmarkSeal1K(b *testing.B) {
+ benchmarkSealSize(b, 1024)
+}
+
+func BenchmarkSeal8K(b *testing.B) {
+ benchmarkSealSize(b, 8192)
+}
+
+func benchmarkOpenSize(b *testing.B, size int) {
+ msg := make([]byte, size)
+ result := make([]byte, size)
+ var nonce [24]byte
+ var key [32]byte
+ box := Seal(nil, msg, &nonce, &key)
+
+ b.SetBytes(int64(size))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ if _, ok := Open(result[:0], box, &nonce, &key); !ok {
+ panic("Open failed")
+ }
+ }
+}
+
+func BenchmarkOpen8Bytes(b *testing.B) {
+ benchmarkOpenSize(b, 8)
+}
+
+func BenchmarkOpen100Bytes(b *testing.B) {
+ benchmarkOpenSize(b, 100)
+}
+
+func BenchmarkOpen1K(b *testing.B) {
+ benchmarkOpenSize(b, 1024)
+}
+
+func BenchmarkOpen8K(b *testing.B) {
+ benchmarkOpenSize(b, 8192)
+}
diff --git a/local_crypto_patch/contents/nacl/sign/sign.go b/local_crypto_patch/contents/nacl/sign/sign.go
new file mode 100644
index 0000000000..1cf2c4be2c
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/sign/sign.go
@@ -0,0 +1,85 @@
+// 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 sign signs small messages using public-key cryptography.
+//
+// This package is interoperable with [libsodium], as well as [TweetNaCl].
+//
+// The sign package is essentially a wrapper for the Ed25519 signature
+// algorithm (implemented by crypto/ed25519). It is [frozen] and is not accepting
+// new features.
+//
+// [libsodium]: https://libsodium.gitbook.io/doc/public-key_cryptography/public-key_signatures
+// [TweetNaCl]: https://tweetnacl.cr.yp.to/
+// [frozen]: https://go.dev/wiki/Frozen
+package sign
+
+import (
+ "crypto/ed25519"
+ "io"
+
+ "golang.org/x/crypto/internal/alias"
+)
+
+// Overhead is the number of bytes of overhead when signing a message.
+const Overhead = 64
+
+// GenerateKey generates a new public/private key pair suitable for use with
+// Sign and Open.
+func GenerateKey(rand io.Reader) (publicKey *[32]byte, privateKey *[64]byte, err error) {
+ pub, priv, err := ed25519.GenerateKey(rand)
+ if err != nil {
+ return nil, nil, err
+ }
+ publicKey, privateKey = new([32]byte), new([64]byte)
+ copy((*publicKey)[:], pub)
+ copy((*privateKey)[:], priv)
+ return publicKey, privateKey, nil
+}
+
+// Sign appends a signed copy of message to out, which will be Overhead bytes
+// longer than the original and must not overlap it.
+func Sign(out, message []byte, privateKey *[64]byte) []byte {
+ sig := ed25519.Sign(ed25519.PrivateKey((*privateKey)[:]), message)
+ ret, out := sliceForAppend(out, Overhead+len(message))
+ if alias.AnyOverlap(out, message) {
+ panic("nacl: invalid buffer overlap")
+ }
+ copy(out, sig)
+ copy(out[Overhead:], message)
+ return ret
+}
+
+// Open verifies a signed message produced by Sign and appends the message to
+// out, which must not overlap the signed message. The output will be Overhead
+// bytes smaller than the signed message.
+func Open(out, signedMessage []byte, publicKey *[32]byte) ([]byte, bool) {
+ if len(signedMessage) < Overhead {
+ return nil, false
+ }
+ if !ed25519.Verify(ed25519.PublicKey((*publicKey)[:]), signedMessage[Overhead:], signedMessage[:Overhead]) {
+ return nil, false
+ }
+ ret, out := sliceForAppend(out, len(signedMessage)-Overhead)
+ if alias.AnyOverlap(out, signedMessage) {
+ panic("nacl: invalid buffer overlap")
+ }
+ copy(out, signedMessage[Overhead:])
+ return ret, true
+}
+
+// sliceForAppend takes a slice and a requested number of bytes. It returns a
+// slice with the contents of the given slice followed by that many bytes and a
+// second slice that aliases into it and contains only the extra bytes. If the
+// original slice has sufficient capacity then no allocation is performed.
+func sliceForAppend(in []byte, n int) (head, tail []byte) {
+ if total := len(in) + n; cap(in) >= total {
+ head = in[:total]
+ } else {
+ head = make([]byte, total)
+ copy(head, in)
+ }
+ tail = head[len(in):]
+ return
+}
diff --git a/local_crypto_patch/contents/nacl/sign/sign_test.go b/local_crypto_patch/contents/nacl/sign/sign_test.go
new file mode 100644
index 0000000000..95ba801f2c
--- /dev/null
+++ b/local_crypto_patch/contents/nacl/sign/sign_test.go
@@ -0,0 +1,74 @@
+// 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 sign
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "testing"
+)
+
+var testSignedMessage, _ = hex.DecodeString("26a0a47f733d02ddb74589b6cbd6f64a7dab1947db79395a1a9e00e4c902c0f185b119897b89b248d16bab4ea781b5a3798d25c2984aec833dddab57e0891e0d68656c6c6f20776f726c64")
+var testMessage = testSignedMessage[Overhead:]
+var testPublicKey [32]byte
+var testPrivateKey = [64]byte{
+ 0x98, 0x3c, 0x6a, 0xa6, 0x21, 0xcc, 0xbb, 0xb2, 0xa7, 0xe8, 0x97, 0x94, 0xde, 0x5f, 0xf8, 0x11,
+ 0x8a, 0xf3, 0x33, 0x1a, 0x03, 0x5c, 0x43, 0x99, 0x03, 0x13, 0x2d, 0xd7, 0xb4, 0xc4, 0x8b, 0xb0,
+ 0xf6, 0x33, 0x20, 0xa3, 0x34, 0x8b, 0x7b, 0xe2, 0xfe, 0xb4, 0xe7, 0x3a, 0x54, 0x08, 0x2d, 0xd7,
+ 0x0c, 0xb7, 0xc0, 0xe3, 0xbf, 0x62, 0x6c, 0x55, 0xf0, 0x33, 0x28, 0x52, 0xf8, 0x48, 0x7d, 0xfd,
+}
+
+func init() {
+ copy(testPublicKey[:], testPrivateKey[32:])
+}
+
+func TestSign(t *testing.T) {
+ signedMessage := Sign(nil, testMessage, &testPrivateKey)
+ if !bytes.Equal(signedMessage, testSignedMessage) {
+ t.Fatalf("signed message did not match, got\n%x\n, expected\n%x", signedMessage, testSignedMessage)
+ }
+}
+
+func TestOpen(t *testing.T) {
+ message, ok := Open(nil, testSignedMessage, &testPublicKey)
+ if !ok {
+ t.Fatalf("valid signed message not successfully verified")
+ }
+ if !bytes.Equal(message, testMessage) {
+ t.Fatalf("message did not match, got\n%x\n, expected\n%x", message, testMessage)
+ }
+ _, ok = Open(nil, testSignedMessage[1:], &testPublicKey)
+ if ok {
+ t.Fatalf("invalid signed message successfully verified")
+ }
+
+ badMessage := make([]byte, len(testSignedMessage))
+ copy(badMessage, testSignedMessage)
+ badMessage[5] ^= 1
+ if _, ok := Open(nil, badMessage, &testPublicKey); ok {
+ t.Fatalf("Open succeeded with a corrupt message")
+ }
+
+ var badPublicKey [32]byte
+ copy(badPublicKey[:], testPublicKey[:])
+ badPublicKey[5] ^= 1
+ if _, ok := Open(nil, testSignedMessage, &badPublicKey); ok {
+ t.Fatalf("Open succeeded with a corrupt public key")
+ }
+}
+
+func TestGenerateSignOpen(t *testing.T) {
+ publicKey, privateKey, _ := GenerateKey(rand.Reader)
+ signedMessage := Sign(nil, testMessage, privateKey)
+ message, ok := Open(nil, signedMessage, publicKey)
+ if !ok {
+ t.Fatalf("failed to verify signed message")
+ }
+
+ if !bytes.Equal(message, testMessage) {
+ t.Fatalf("verified message does not match signed message, got\n%x\n, expected\n%x", message, testMessage)
+ }
+}
diff --git a/local_crypto_patch/contents/ocsp/ocsp.go b/local_crypto_patch/contents/ocsp/ocsp.go
new file mode 100644
index 0000000000..e6c645e7ce
--- /dev/null
+++ b/local_crypto_patch/contents/ocsp/ocsp.go
@@ -0,0 +1,793 @@
+// 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 ocsp parses OCSP responses as specified in RFC 2560. OCSP responses
+// are signed messages attesting to the validity of a certificate for a small
+// period of time. This is used to manage revocation for X.509 certificates.
+package ocsp
+
+import (
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ _ "crypto/sha1"
+ _ "crypto/sha256"
+ _ "crypto/sha512"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "errors"
+ "fmt"
+ "math/big"
+ "strconv"
+ "time"
+)
+
+var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})
+
+// ResponseStatus contains the result of an OCSP request. See
+// https://tools.ietf.org/html/rfc6960#section-2.3
+type ResponseStatus int
+
+const (
+ Success ResponseStatus = 0
+ Malformed ResponseStatus = 1
+ InternalError ResponseStatus = 2
+ TryLater ResponseStatus = 3
+ // Status code four is unused in OCSP. See
+ // https://tools.ietf.org/html/rfc6960#section-4.2.1
+ SignatureRequired ResponseStatus = 5
+ Unauthorized ResponseStatus = 6
+)
+
+func (r ResponseStatus) String() string {
+ switch r {
+ case Success:
+ return "success"
+ case Malformed:
+ return "malformed"
+ case InternalError:
+ return "internal error"
+ case TryLater:
+ return "try later"
+ case SignatureRequired:
+ return "signature required"
+ case Unauthorized:
+ return "unauthorized"
+ default:
+ return "unknown OCSP status: " + strconv.Itoa(int(r))
+ }
+}
+
+// ResponseError is an error that may be returned by ParseResponse to indicate
+// that the response itself is an error, not just that it's indicating that a
+// certificate is revoked, unknown, etc.
+type ResponseError struct {
+ Status ResponseStatus
+}
+
+func (r ResponseError) Error() string {
+ return "ocsp: error from server: " + r.Status.String()
+}
+
+// These are internal structures that reflect the ASN.1 structure of an OCSP
+// response. See RFC 2560, section 4.2.
+
+type certID struct {
+ HashAlgorithm pkix.AlgorithmIdentifier
+ NameHash []byte
+ IssuerKeyHash []byte
+ SerialNumber *big.Int
+}
+
+// https://tools.ietf.org/html/rfc2560#section-4.1.1
+type ocspRequest struct {
+ TBSRequest tbsRequest
+}
+
+type tbsRequest struct {
+ Version int `asn1:"explicit,tag:0,default:0,optional"`
+ RequestorName pkix.RDNSequence `asn1:"explicit,tag:1,optional"`
+ RequestList []request
+}
+
+type request struct {
+ Cert certID
+}
+
+type responseASN1 struct {
+ Status asn1.Enumerated
+ Response responseBytes `asn1:"explicit,tag:0,optional"`
+}
+
+type responseBytes struct {
+ ResponseType asn1.ObjectIdentifier
+ Response []byte
+}
+
+type basicResponse struct {
+ TBSResponseData responseData
+ SignatureAlgorithm pkix.AlgorithmIdentifier
+ Signature asn1.BitString
+ Certificates []asn1.RawValue `asn1:"explicit,tag:0,optional"`
+}
+
+type responseData struct {
+ Raw asn1.RawContent
+ Version int `asn1:"optional,default:0,explicit,tag:0"`
+ RawResponderID asn1.RawValue
+ ProducedAt time.Time `asn1:"generalized"`
+ Responses []singleResponse
+}
+
+type singleResponse struct {
+ CertID certID
+ Good asn1.Flag `asn1:"tag:0,optional"`
+ Revoked revokedInfo `asn1:"tag:1,optional"`
+ Unknown asn1.Flag `asn1:"tag:2,optional"`
+ ThisUpdate time.Time `asn1:"generalized"`
+ NextUpdate time.Time `asn1:"generalized,explicit,tag:0,optional"`
+ SingleExtensions []pkix.Extension `asn1:"explicit,tag:1,optional"`
+}
+
+type revokedInfo struct {
+ RevocationTime time.Time `asn1:"generalized"`
+ Reason asn1.Enumerated `asn1:"explicit,tag:0,optional"`
+}
+
+var (
+ oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2}
+ oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4}
+ oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
+ oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
+ oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
+ oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
+ oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3}
+ oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2}
+ oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1}
+ oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
+ oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
+ oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
+)
+
+var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{
+ crypto.SHA1: asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}),
+ crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}),
+ crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}),
+ crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}),
+}
+
+// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below
+var signatureAlgorithmDetails = []struct {
+ algo x509.SignatureAlgorithm
+ oid asn1.ObjectIdentifier
+ pubKeyAlgo x509.PublicKeyAlgorithm
+ hash crypto.Hash
+}{
+ {x509.MD2WithRSA, oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */},
+ {x509.MD5WithRSA, oidSignatureMD5WithRSA, x509.RSA, crypto.MD5},
+ {x509.SHA1WithRSA, oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1},
+ {x509.SHA256WithRSA, oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256},
+ {x509.SHA384WithRSA, oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384},
+ {x509.SHA512WithRSA, oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512},
+ {x509.DSAWithSHA1, oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1},
+ {x509.DSAWithSHA256, oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256},
+ {x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1},
+ {x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256},
+ {x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384},
+ {x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512},
+}
+
+// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below
+func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) {
+ var pubType x509.PublicKeyAlgorithm
+
+ switch pub := pub.(type) {
+ case *rsa.PublicKey:
+ pubType = x509.RSA
+ hashFunc = crypto.SHA256
+ sigAlgo.Algorithm = oidSignatureSHA256WithRSA
+ sigAlgo.Parameters = asn1.RawValue{
+ Tag: 5,
+ }
+
+ case *ecdsa.PublicKey:
+ pubType = x509.ECDSA
+
+ switch pub.Curve {
+ case elliptic.P224(), elliptic.P256():
+ hashFunc = crypto.SHA256
+ sigAlgo.Algorithm = oidSignatureECDSAWithSHA256
+ case elliptic.P384():
+ hashFunc = crypto.SHA384
+ sigAlgo.Algorithm = oidSignatureECDSAWithSHA384
+ case elliptic.P521():
+ hashFunc = crypto.SHA512
+ sigAlgo.Algorithm = oidSignatureECDSAWithSHA512
+ default:
+ err = errors.New("x509: unknown elliptic curve")
+ }
+
+ default:
+ err = errors.New("x509: only RSA and ECDSA keys supported")
+ }
+
+ if err != nil {
+ return
+ }
+
+ if requestedSigAlgo == 0 {
+ return
+ }
+
+ found := false
+ for _, details := range signatureAlgorithmDetails {
+ if details.algo == requestedSigAlgo {
+ if details.pubKeyAlgo != pubType {
+ err = errors.New("x509: requested SignatureAlgorithm does not match private key type")
+ return
+ }
+ sigAlgo.Algorithm, hashFunc = details.oid, details.hash
+ if hashFunc == 0 {
+ err = errors.New("x509: cannot sign with hash function requested")
+ return
+ }
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ err = errors.New("x509: unknown SignatureAlgorithm")
+ }
+
+ return
+}
+
+// TODO(agl): this is taken from crypto/x509 and so should probably be exported
+// from crypto/x509 or crypto/x509/pkix.
+func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) x509.SignatureAlgorithm {
+ for _, details := range signatureAlgorithmDetails {
+ if oid.Equal(details.oid) {
+ return details.algo
+ }
+ }
+ return x509.UnknownSignatureAlgorithm
+}
+
+// TODO(rlb): This is not taken from crypto/x509, but it's of the same general form.
+func getHashAlgorithmFromOID(target asn1.ObjectIdentifier) crypto.Hash {
+ for hash, oid := range hashOIDs {
+ if oid.Equal(target) {
+ return hash
+ }
+ }
+ return crypto.Hash(0)
+}
+
+func getOIDFromHashAlgorithm(target crypto.Hash) asn1.ObjectIdentifier {
+ for hash, oid := range hashOIDs {
+ if hash == target {
+ return oid
+ }
+ }
+ return nil
+}
+
+// This is the exposed reflection of the internal OCSP structures.
+
+// The status values that can be expressed in OCSP. See RFC 6960.
+// These are used for the Response.Status field.
+const (
+ // Good means that the certificate is valid.
+ Good = 0
+ // Revoked means that the certificate has been deliberately revoked.
+ Revoked = 1
+ // Unknown means that the OCSP responder doesn't know about the certificate.
+ Unknown = 2
+ // ServerFailed is unused and was never used (see
+ // https://go-review.googlesource.com/#/c/18944). ParseResponse will
+ // return a ResponseError when an error response is parsed.
+ ServerFailed = 3
+)
+
+// The enumerated reasons for revoking a certificate. See RFC 5280.
+const (
+ Unspecified = 0
+ KeyCompromise = 1
+ CACompromise = 2
+ AffiliationChanged = 3
+ Superseded = 4
+ CessationOfOperation = 5
+ CertificateHold = 6
+
+ RemoveFromCRL = 8
+ PrivilegeWithdrawn = 9
+ AACompromise = 10
+)
+
+// Request represents an OCSP request. See RFC 6960.
+type Request struct {
+ HashAlgorithm crypto.Hash
+ IssuerNameHash []byte
+ IssuerKeyHash []byte
+ SerialNumber *big.Int
+}
+
+// Marshal marshals the OCSP request to ASN.1 DER encoded form.
+func (req *Request) Marshal() ([]byte, error) {
+ hashAlg := getOIDFromHashAlgorithm(req.HashAlgorithm)
+ if hashAlg == nil {
+ return nil, errors.New("Unknown hash algorithm")
+ }
+ return asn1.Marshal(ocspRequest{
+ tbsRequest{
+ Version: 0,
+ RequestList: []request{
+ {
+ Cert: certID{
+ pkix.AlgorithmIdentifier{
+ Algorithm: hashAlg,
+ Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
+ },
+ req.IssuerNameHash,
+ req.IssuerKeyHash,
+ req.SerialNumber,
+ },
+ },
+ },
+ },
+ })
+}
+
+// Response represents an OCSP response containing a single SingleResponse. See
+// RFC 6960.
+type Response struct {
+ Raw []byte
+
+ // Status is one of {Good, Revoked, Unknown}
+ Status int
+ SerialNumber *big.Int
+ ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time
+ RevocationReason int
+ Certificate *x509.Certificate
+ // TBSResponseData contains the raw bytes of the signed response. If
+ // Certificate is nil then this can be used to verify Signature.
+ TBSResponseData []byte
+ Signature []byte
+ SignatureAlgorithm x509.SignatureAlgorithm
+
+ // IssuerHash is the hash used to compute the IssuerNameHash and IssuerKeyHash.
+ // Valid values are crypto.SHA1, crypto.SHA256, crypto.SHA384, and crypto.SHA512.
+ // If zero, the default is crypto.SHA1.
+ IssuerHash crypto.Hash
+
+ // RawResponderName optionally contains the DER-encoded subject of the
+ // responder certificate. Exactly one of RawResponderName and
+ // ResponderKeyHash is set.
+ RawResponderName []byte
+ // ResponderKeyHash optionally contains the SHA-1 hash of the
+ // responder's public key. Exactly one of RawResponderName and
+ // ResponderKeyHash is set.
+ ResponderKeyHash []byte
+
+ // Extensions contains raw X.509 extensions from the singleExtensions field
+ // of the OCSP response. When parsing certificates, this can be used to
+ // extract non-critical extensions that are not parsed by this package. When
+ // marshaling OCSP responses, the Extensions field is ignored, see
+ // ExtraExtensions.
+ Extensions []pkix.Extension
+
+ // ExtraExtensions contains extensions to be copied, raw, into any marshaled
+ // OCSP response (in the singleExtensions field). Values override any
+ // extensions that would otherwise be produced based on the other fields. The
+ // ExtraExtensions field is not populated when parsing certificates, see
+ // Extensions.
+ ExtraExtensions []pkix.Extension
+}
+
+// These are pre-serialized error responses for the various non-success codes
+// defined by OCSP. The Unauthorized code in particular can be used by an OCSP
+// responder that supports only pre-signed responses as a response to requests
+// for certificates with unknown status. See RFC 5019.
+var (
+ MalformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01}
+ InternalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02}
+ TryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03}
+ SigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05}
+ UnauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06}
+)
+
+// CheckSignatureFrom checks that the signature in resp is a valid signature
+// from issuer. This should only be used if resp.Certificate is nil. Otherwise,
+// the OCSP response contained an intermediate certificate that created the
+// signature. That signature is checked by ParseResponse and only
+// resp.Certificate remains to be validated.
+func (resp *Response) CheckSignatureFrom(issuer *x509.Certificate) error {
+ return issuer.CheckSignature(resp.SignatureAlgorithm, resp.TBSResponseData, resp.Signature)
+}
+
+// ParseError results from an invalid OCSP response.
+type ParseError string
+
+func (p ParseError) Error() string {
+ return string(p)
+}
+
+// ParseRequest parses an OCSP request in DER form. It only supports
+// requests for a single certificate. Signed requests are not supported.
+// If a request includes a signature, it will result in a ParseError.
+func ParseRequest(bytes []byte) (*Request, error) {
+ var req ocspRequest
+ rest, err := asn1.Unmarshal(bytes, &req)
+ if err != nil {
+ return nil, err
+ }
+ if len(rest) > 0 {
+ return nil, ParseError("trailing data in OCSP request")
+ }
+
+ if len(req.TBSRequest.RequestList) == 0 {
+ return nil, ParseError("OCSP request contains no request body")
+ }
+ innerRequest := req.TBSRequest.RequestList[0]
+
+ hashFunc := getHashAlgorithmFromOID(innerRequest.Cert.HashAlgorithm.Algorithm)
+ if hashFunc == crypto.Hash(0) {
+ return nil, ParseError("OCSP request uses unknown hash function")
+ }
+
+ return &Request{
+ HashAlgorithm: hashFunc,
+ IssuerNameHash: innerRequest.Cert.NameHash,
+ IssuerKeyHash: innerRequest.Cert.IssuerKeyHash,
+ SerialNumber: innerRequest.Cert.SerialNumber,
+ }, nil
+}
+
+// ParseResponse parses an OCSP response in DER form. The response must contain
+// only one certificate status. To parse the status of a specific certificate
+// from a response which may contain multiple statuses, use ParseResponseForCert
+// instead.
+//
+// If the response contains an embedded certificate, then that certificate will
+// be used to verify the response signature. If the response contains an
+// embedded certificate and issuer is not nil, then issuer will be used to verify
+// the signature on the embedded certificate.
+//
+// If the response does not contain an embedded certificate and issuer is not
+// nil, then issuer will be used to verify the response signature.
+//
+// Invalid responses and parse failures will result in a ParseError.
+// Error responses will result in a ResponseError.
+func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
+ return ParseResponseForCert(bytes, nil, issuer)
+}
+
+// ParseResponseForCert acts identically to ParseResponse, except it supports
+// parsing responses that contain multiple statuses. If the response contains
+// multiple statuses and cert is not nil, then ParseResponseForCert will return
+// the first status which contains a matching serial, otherwise it will return an
+// error. If cert is nil, then the first status in the response will be returned.
+func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Response, error) {
+ var resp responseASN1
+ rest, err := asn1.Unmarshal(bytes, &resp)
+ if err != nil {
+ return nil, err
+ }
+ if len(rest) > 0 {
+ return nil, ParseError("trailing data in OCSP response")
+ }
+
+ if status := ResponseStatus(resp.Status); status != Success {
+ return nil, ResponseError{status}
+ }
+
+ if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) {
+ return nil, ParseError("bad OCSP response type")
+ }
+
+ var basicResp basicResponse
+ rest, err = asn1.Unmarshal(resp.Response.Response, &basicResp)
+ if err != nil {
+ return nil, err
+ }
+ if len(rest) > 0 {
+ return nil, ParseError("trailing data in OCSP response")
+ }
+
+ if n := len(basicResp.TBSResponseData.Responses); n == 0 || cert == nil && n > 1 {
+ return nil, ParseError("OCSP response contains bad number of responses")
+ }
+
+ var singleResp singleResponse
+ if cert == nil {
+ singleResp = basicResp.TBSResponseData.Responses[0]
+ } else {
+ match := false
+ for _, resp := range basicResp.TBSResponseData.Responses {
+ if cert.SerialNumber.Cmp(resp.CertID.SerialNumber) == 0 {
+ singleResp = resp
+ match = true
+ break
+ }
+ }
+ if !match {
+ return nil, ParseError("no response matching the supplied certificate")
+ }
+ }
+
+ ret := &Response{
+ Raw: bytes,
+ TBSResponseData: basicResp.TBSResponseData.Raw,
+ Signature: basicResp.Signature.RightAlign(),
+ SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm),
+ Extensions: singleResp.SingleExtensions,
+ SerialNumber: singleResp.CertID.SerialNumber,
+ ProducedAt: basicResp.TBSResponseData.ProducedAt,
+ ThisUpdate: singleResp.ThisUpdate,
+ NextUpdate: singleResp.NextUpdate,
+ }
+
+ // Handle the ResponderID CHOICE tag. ResponderID can be flattened into
+ // TBSResponseData once https://go-review.googlesource.com/34503 has been
+ // released.
+ rawResponderID := basicResp.TBSResponseData.RawResponderID
+ switch rawResponderID.Tag {
+ case 1: // Name
+ var rdn pkix.RDNSequence
+ if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &rdn); err != nil || len(rest) != 0 {
+ return nil, ParseError("invalid responder name")
+ }
+ ret.RawResponderName = rawResponderID.Bytes
+ case 2: // KeyHash
+ if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &ret.ResponderKeyHash); err != nil || len(rest) != 0 {
+ return nil, ParseError("invalid responder key hash")
+ }
+ default:
+ return nil, ParseError("invalid responder id tag")
+ }
+
+ if len(basicResp.Certificates) > 0 {
+ // Responders should only send a single certificate (if they
+ // send any) that connects the responder's certificate to the
+ // original issuer. We accept responses with multiple
+ // certificates due to a number responders sending them[1], but
+ // ignore all but the first.
+ //
+ // [1] https://github.com/golang/go/issues/21527
+ ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := ret.CheckSignatureFrom(ret.Certificate); err != nil {
+ return nil, ParseError("bad signature on embedded certificate: " + err.Error())
+ }
+
+ if issuer != nil {
+ if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil {
+ return nil, ParseError("bad OCSP signature: " + err.Error())
+ }
+ }
+ } else if issuer != nil {
+ if err := ret.CheckSignatureFrom(issuer); err != nil {
+ return nil, ParseError("bad OCSP signature: " + err.Error())
+ }
+ }
+
+ for _, ext := range singleResp.SingleExtensions {
+ if ext.Critical {
+ return nil, ParseError("unsupported critical extension")
+ }
+ }
+
+ for h, oid := range hashOIDs {
+ if singleResp.CertID.HashAlgorithm.Algorithm.Equal(oid) {
+ ret.IssuerHash = h
+ break
+ }
+ }
+ if ret.IssuerHash == 0 {
+ return nil, ParseError("unsupported issuer hash algorithm")
+ }
+
+ switch {
+ case bool(singleResp.Good):
+ ret.Status = Good
+ case bool(singleResp.Unknown):
+ ret.Status = Unknown
+ default:
+ ret.Status = Revoked
+ ret.RevokedAt = singleResp.Revoked.RevocationTime
+ ret.RevocationReason = int(singleResp.Revoked.Reason)
+ }
+
+ return ret, nil
+}
+
+// RequestOptions contains options for constructing OCSP requests.
+type RequestOptions struct {
+ // Hash contains the hash function that should be used when
+ // constructing the OCSP request. If zero, SHA-1 will be used.
+ Hash crypto.Hash
+}
+
+func (opts *RequestOptions) hash() crypto.Hash {
+ if opts == nil || opts.Hash == 0 {
+ // SHA-1 is nearly universally used in OCSP.
+ return crypto.SHA1
+ }
+ return opts.Hash
+}
+
+// CreateRequest returns a DER-encoded, OCSP request for the status of cert. If
+// opts is nil then sensible defaults are used.
+func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte, error) {
+ hashFunc := opts.hash()
+
+ // OCSP seems to be the only place where these raw hash identifiers are
+ // used. I took the following from
+ // http://msdn.microsoft.com/en-us/library/ff635603.aspx
+ _, ok := hashOIDs[hashFunc]
+ if !ok {
+ return nil, x509.ErrUnsupportedAlgorithm
+ }
+
+ if !hashFunc.Available() {
+ return nil, x509.ErrUnsupportedAlgorithm
+ }
+ h := opts.hash().New()
+
+ var publicKeyInfo struct {
+ Algorithm pkix.AlgorithmIdentifier
+ PublicKey asn1.BitString
+ }
+ if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
+ return nil, err
+ }
+
+ h.Write(publicKeyInfo.PublicKey.RightAlign())
+ issuerKeyHash := h.Sum(nil)
+
+ h.Reset()
+ h.Write(issuer.RawSubject)
+ issuerNameHash := h.Sum(nil)
+
+ req := &Request{
+ HashAlgorithm: hashFunc,
+ IssuerNameHash: issuerNameHash,
+ IssuerKeyHash: issuerKeyHash,
+ SerialNumber: cert.SerialNumber,
+ }
+ return req.Marshal()
+}
+
+// CreateResponse returns a DER-encoded OCSP response with the specified contents.
+// The fields in the response are populated as follows:
+//
+// The responder cert is used to populate the responder's name field, and the
+// certificate itself is provided alongside the OCSP response signature.
+//
+// The issuer cert is used to populate the IssuerNameHash and IssuerKeyHash fields.
+//
+// The template is used to populate the SerialNumber, Status, RevokedAt,
+// RevocationReason, ThisUpdate, and NextUpdate fields.
+//
+// If template.IssuerHash is not set, SHA1 will be used.
+//
+// The ProducedAt date is automatically set to the current date, to the nearest minute.
+func CreateResponse(issuer, responderCert *x509.Certificate, template Response, priv crypto.Signer) ([]byte, error) {
+ var publicKeyInfo struct {
+ Algorithm pkix.AlgorithmIdentifier
+ PublicKey asn1.BitString
+ }
+ if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
+ return nil, err
+ }
+
+ if template.IssuerHash == 0 {
+ template.IssuerHash = crypto.SHA1
+ }
+ hashOID := getOIDFromHashAlgorithm(template.IssuerHash)
+ if hashOID == nil {
+ return nil, errors.New("unsupported issuer hash algorithm")
+ }
+
+ if !template.IssuerHash.Available() {
+ return nil, fmt.Errorf("issuer hash algorithm %v not linked into binary", template.IssuerHash)
+ }
+ h := template.IssuerHash.New()
+ h.Write(publicKeyInfo.PublicKey.RightAlign())
+ issuerKeyHash := h.Sum(nil)
+
+ h.Reset()
+ h.Write(issuer.RawSubject)
+ issuerNameHash := h.Sum(nil)
+
+ innerResponse := singleResponse{
+ CertID: certID{
+ HashAlgorithm: pkix.AlgorithmIdentifier{
+ Algorithm: hashOID,
+ Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
+ },
+ NameHash: issuerNameHash,
+ IssuerKeyHash: issuerKeyHash,
+ SerialNumber: template.SerialNumber,
+ },
+ ThisUpdate: template.ThisUpdate.UTC(),
+ NextUpdate: template.NextUpdate.UTC(),
+ SingleExtensions: template.ExtraExtensions,
+ }
+
+ switch template.Status {
+ case Good:
+ innerResponse.Good = true
+ case Unknown:
+ innerResponse.Unknown = true
+ case Revoked:
+ innerResponse.Revoked = revokedInfo{
+ RevocationTime: template.RevokedAt.UTC(),
+ Reason: asn1.Enumerated(template.RevocationReason),
+ }
+ }
+
+ rawResponderID := asn1.RawValue{
+ Class: 2, // context-specific
+ Tag: 1, // Name (explicit tag)
+ IsCompound: true,
+ Bytes: responderCert.RawSubject,
+ }
+ tbsResponseData := responseData{
+ Version: 0,
+ RawResponderID: rawResponderID,
+ ProducedAt: time.Now().Truncate(time.Minute).UTC(),
+ Responses: []singleResponse{innerResponse},
+ }
+
+ tbsResponseDataDER, err := asn1.Marshal(tbsResponseData)
+ if err != nil {
+ return nil, err
+ }
+
+ hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(priv.Public(), template.SignatureAlgorithm)
+ if err != nil {
+ return nil, err
+ }
+
+ responseHash := hashFunc.New()
+ responseHash.Write(tbsResponseDataDER)
+ signature, err := priv.Sign(rand.Reader, responseHash.Sum(nil), hashFunc)
+ if err != nil {
+ return nil, err
+ }
+
+ response := basicResponse{
+ TBSResponseData: tbsResponseData,
+ SignatureAlgorithm: signatureAlgorithm,
+ Signature: asn1.BitString{
+ Bytes: signature,
+ BitLength: 8 * len(signature),
+ },
+ }
+ if template.Certificate != nil {
+ response.Certificates = []asn1.RawValue{
+ {FullBytes: template.Certificate.Raw},
+ }
+ }
+ responseDER, err := asn1.Marshal(response)
+ if err != nil {
+ return nil, err
+ }
+
+ return asn1.Marshal(responseASN1{
+ Status: asn1.Enumerated(Success),
+ Response: responseBytes{
+ ResponseType: idPKIXOCSPBasic,
+ Response: responseDER,
+ },
+ })
+}
diff --git a/local_crypto_patch/contents/ocsp/ocsp_test.go b/local_crypto_patch/contents/ocsp/ocsp_test.go
new file mode 100644
index 0000000000..6dc273ec28
--- /dev/null
+++ b/local_crypto_patch/contents/ocsp/ocsp_test.go
@@ -0,0 +1,743 @@
+// 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 ocsp
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha1"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "encoding/hex"
+ "encoding/pem"
+ "math/big"
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestOCSPDecode(t *testing.T) {
+ responseBytes, _ := hex.DecodeString(ocspResponseHex)
+ resp, err := ParseResponse(responseBytes, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // keyHash is the SKID of the issuer of the certificate the OCSP
+ // response is for.
+ keyHash, err := hex.DecodeString("8a747faf85cdee95cd3d9cd0e24614f371351d27")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // serialBytes is the serial number of the certificate the OCSP
+ // response is for.
+ serialBytes, err := hex.DecodeString("f374542e3c7a68360a00000001103462")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := Response{
+ Status: Good,
+ SerialNumber: big.NewInt(0).SetBytes(serialBytes),
+ RevocationReason: Unspecified,
+ ThisUpdate: time.Date(2021, 11, 7, 14, 25, 51, 0, time.UTC),
+ NextUpdate: time.Date(2021, 11, 14, 13, 25, 50, 0, time.UTC),
+ ResponderKeyHash: keyHash,
+ }
+
+ if !reflect.DeepEqual(resp.ThisUpdate, expected.ThisUpdate) {
+ t.Errorf("resp.ThisUpdate: got %v, want %v", resp.ThisUpdate, expected.ThisUpdate)
+ }
+
+ if !reflect.DeepEqual(resp.NextUpdate, expected.NextUpdate) {
+ t.Errorf("resp.NextUpdate: got %v, want %v", resp.NextUpdate, expected.NextUpdate)
+ }
+
+ if resp.Status != expected.Status {
+ t.Errorf("resp.Status: got %d, want %d", resp.Status, expected.Status)
+ }
+
+ if resp.SerialNumber.Cmp(expected.SerialNumber) != 0 {
+ t.Errorf("resp.SerialNumber: got %x, want %x", resp.SerialNumber, expected.SerialNumber)
+ }
+
+ if resp.RevocationReason != expected.RevocationReason {
+ t.Errorf("resp.RevocationReason: got %d, want %d", resp.RevocationReason, expected.RevocationReason)
+ }
+
+ if !bytes.Equal(resp.RawResponderName, expected.RawResponderName) {
+ t.Errorf("resp.RawResponderName: got %x, want %x", resp.RawResponderName, expected.RawResponderName)
+ }
+
+ if !bytes.Equal(resp.ResponderKeyHash, expected.ResponderKeyHash) {
+ t.Errorf("resp.ResponderKeyHash: got %x, want %x", resp.ResponderKeyHash, expected.ResponderKeyHash)
+ }
+}
+
+func TestOCSPDecodeWithoutCert(t *testing.T) {
+ responseBytes, _ := hex.DecodeString(ocspResponseWithoutCertHex)
+ _, err := ParseResponse(responseBytes, nil)
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestOCSPDecodeWithExtensions(t *testing.T) {
+ responseBytes, _ := hex.DecodeString(ocspResponseWithCriticalExtensionHex)
+ _, err := ParseResponse(responseBytes, nil)
+ if err == nil {
+ t.Error(err)
+ }
+
+ responseBytes, _ = hex.DecodeString(ocspResponseWithExtensionHex)
+ response, err := ParseResponse(responseBytes, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(response.Extensions) != 1 {
+ t.Errorf("len(response.Extensions): got %v, want %v", len(response.Extensions), 1)
+ }
+
+ extensionBytes := response.Extensions[0].Value
+ expectedBytes, _ := hex.DecodeString(ocspExtensionValueHex)
+ if !bytes.Equal(extensionBytes, expectedBytes) {
+ t.Errorf("response.Extensions[0]: got %x, want %x", extensionBytes, expectedBytes)
+ }
+}
+
+func TestOCSPSignature(t *testing.T) {
+ b, _ := pem.Decode([]byte(GTSRoot))
+ issuer, err := x509.ParseCertificate(b.Bytes)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ response, _ := hex.DecodeString(ocspResponseHex)
+ if _, err := ParseResponse(response, issuer); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestOCSPRequest(t *testing.T) {
+ leafCert, _ := hex.DecodeString(leafCertHex)
+ cert, err := x509.ParseCertificate(leafCert)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ issuerCert, _ := hex.DecodeString(issuerCertHex)
+ issuer, err := x509.ParseCertificate(issuerCert)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ request, err := CreateRequest(cert, issuer, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expectedBytes, _ := hex.DecodeString(ocspRequestHex)
+ if !bytes.Equal(request, expectedBytes) {
+ t.Errorf("request: got %x, wanted %x", request, expectedBytes)
+ }
+
+ decodedRequest, err := ParseRequest(expectedBytes)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if decodedRequest.HashAlgorithm != crypto.SHA1 {
+ t.Errorf("request.HashAlgorithm: got %v, want %v", decodedRequest.HashAlgorithm, crypto.SHA1)
+ }
+
+ var publicKeyInfo struct {
+ Algorithm pkix.AlgorithmIdentifier
+ PublicKey asn1.BitString
+ }
+ _, err = asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ h := sha1.New()
+ h.Write(publicKeyInfo.PublicKey.RightAlign())
+ issuerKeyHash := h.Sum(nil)
+
+ h.Reset()
+ h.Write(issuer.RawSubject)
+ issuerNameHash := h.Sum(nil)
+
+ if got := decodedRequest.IssuerKeyHash; !bytes.Equal(got, issuerKeyHash) {
+ t.Errorf("request.IssuerKeyHash: got %x, want %x", got, issuerKeyHash)
+ }
+
+ if got := decodedRequest.IssuerNameHash; !bytes.Equal(got, issuerNameHash) {
+ t.Errorf("request.IssuerKeyHash: got %x, want %x", got, issuerNameHash)
+ }
+
+ if got := decodedRequest.SerialNumber; got.Cmp(cert.SerialNumber) != 0 {
+ t.Errorf("request.SerialNumber: got %x, want %x", got, cert.SerialNumber)
+ }
+
+ marshaledRequest, err := decodedRequest.Marshal()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if bytes.Compare(expectedBytes, marshaledRequest) != 0 {
+ t.Errorf(
+ "Marshaled request doesn't match expected: wanted %x, got %x",
+ expectedBytes,
+ marshaledRequest,
+ )
+ }
+}
+
+func TestOCSPResponse(t *testing.T) {
+ leafCert, _ := hex.DecodeString(leafCertHex)
+ leaf, err := x509.ParseCertificate(leafCert)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ issuerCert, _ := hex.DecodeString(issuerCertHex)
+ issuer, err := x509.ParseCertificate(issuerCert)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ responderCert, _ := hex.DecodeString(responderCertHex)
+ responder, err := x509.ParseCertificate(responderCert)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ responderPrivateKeyDER, _ := hex.DecodeString(responderPrivateKeyHex)
+ responderPrivateKey, err := x509.ParsePKCS1PrivateKey(responderPrivateKeyDER)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ extensionBytes, _ := hex.DecodeString(ocspExtensionValueHex)
+ extensions := []pkix.Extension{
+ {
+ Id: ocspExtensionOID,
+ Critical: false,
+ Value: extensionBytes,
+ },
+ }
+
+ thisUpdate := time.Date(2010, 7, 7, 15, 1, 5, 0, time.UTC)
+ nextUpdate := time.Date(2010, 7, 7, 18, 35, 17, 0, time.UTC)
+ template := Response{
+ Status: Revoked,
+ SerialNumber: leaf.SerialNumber,
+ ThisUpdate: thisUpdate,
+ NextUpdate: nextUpdate,
+ RevokedAt: thisUpdate,
+ RevocationReason: KeyCompromise,
+ Certificate: responder,
+ ExtraExtensions: extensions,
+ }
+
+ template.IssuerHash = crypto.MD5
+ _, err = CreateResponse(issuer, responder, template, responderPrivateKey)
+ if err == nil {
+ t.Fatal("CreateResponse didn't fail with non-valid template.IssuerHash value crypto.MD5")
+ }
+
+ testCases := []struct {
+ name string
+ issuerHash crypto.Hash
+ }{
+ {"Zero value", 0},
+ {"crypto.SHA1", crypto.SHA1},
+ {"crypto.SHA256", crypto.SHA256},
+ {"crypto.SHA384", crypto.SHA384},
+ {"crypto.SHA512", crypto.SHA512},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ template.IssuerHash = tc.issuerHash
+ responseBytes, err := CreateResponse(issuer, responder, template, responderPrivateKey)
+ if err != nil {
+ t.Fatalf("CreateResponse failed: %s", err)
+ }
+
+ resp, err := ParseResponse(responseBytes, nil)
+ if err != nil {
+ t.Fatalf("ParseResponse failed: %s", err)
+ }
+
+ if !reflect.DeepEqual(resp.ThisUpdate, template.ThisUpdate) {
+ t.Errorf("resp.ThisUpdate: got %v, want %v", resp.ThisUpdate, template.ThisUpdate)
+ }
+
+ if !reflect.DeepEqual(resp.NextUpdate, template.NextUpdate) {
+ t.Errorf("resp.NextUpdate: got %v, want %v", resp.NextUpdate, template.NextUpdate)
+ }
+
+ if !reflect.DeepEqual(resp.RevokedAt, template.RevokedAt) {
+ t.Errorf("resp.RevokedAt: got %v, want %v", resp.RevokedAt, template.RevokedAt)
+ }
+
+ if !reflect.DeepEqual(resp.Extensions, template.ExtraExtensions) {
+ t.Errorf("resp.Extensions: got %v, want %v", resp.Extensions, template.ExtraExtensions)
+ }
+
+ delay := time.Since(resp.ProducedAt)
+ if delay < -time.Hour || delay > time.Hour {
+ t.Errorf("resp.ProducedAt: got %s, want close to current time (%s)", resp.ProducedAt, time.Now())
+ }
+
+ if resp.Status != template.Status {
+ t.Errorf("resp.Status: got %d, want %d", resp.Status, template.Status)
+ }
+
+ if resp.SerialNumber.Cmp(template.SerialNumber) != 0 {
+ t.Errorf("resp.SerialNumber: got %x, want %x", resp.SerialNumber, template.SerialNumber)
+ }
+
+ if resp.RevocationReason != template.RevocationReason {
+ t.Errorf("resp.RevocationReason: got %d, want %d", resp.RevocationReason, template.RevocationReason)
+ }
+
+ expectedHash := tc.issuerHash
+ if tc.issuerHash == 0 {
+ expectedHash = crypto.SHA1
+ }
+
+ if resp.IssuerHash != expectedHash {
+ t.Errorf("resp.IssuerHash: got %d, want %d", resp.IssuerHash, expectedHash)
+ }
+ })
+ }
+}
+
+func TestErrorResponse(t *testing.T) {
+ responseBytes, _ := hex.DecodeString(errorResponseHex)
+ _, err := ParseResponse(responseBytes, nil)
+
+ respErr, ok := err.(ResponseError)
+ if !ok {
+ t.Fatalf("expected ResponseError from ParseResponse but got %#v", err)
+ }
+ if respErr.Status != Malformed {
+ t.Fatalf("expected Malformed status from ParseResponse but got %d", respErr.Status)
+ }
+}
+
+func createMultiResp() ([]byte, error) {
+ rawResponderID := asn1.RawValue{
+ Class: 2, // context-specific
+ Tag: 1, // Name (explicit tag)
+ IsCompound: true,
+ Bytes: []byte{48, 0},
+ }
+ tbsResponseData := responseData{
+ Version: 0,
+ RawResponderID: rawResponderID,
+ ProducedAt: time.Now().Truncate(time.Minute).UTC(),
+ }
+ this := time.Now()
+ next := this.Add(time.Hour * 24 * 4)
+ for i := int64(0); i < 5; i++ {
+ tbsResponseData.Responses = append(tbsResponseData.Responses, singleResponse{
+ CertID: certID{
+ HashAlgorithm: pkix.AlgorithmIdentifier{
+ Algorithm: hashOIDs[crypto.SHA1],
+ Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
+ },
+ NameHash: []byte{1, 2, 3},
+ IssuerKeyHash: []byte{4, 5, 6},
+ SerialNumber: big.NewInt(i),
+ },
+ ThisUpdate: this.UTC(),
+ NextUpdate: next.UTC(),
+ Good: true,
+ })
+ }
+
+ tbsResponseDataDER, err := asn1.Marshal(tbsResponseData)
+ if err != nil {
+ return nil, err
+ }
+
+ k, err := rsa.GenerateKey(rand.Reader, 1024)
+ if err != nil {
+ return nil, err
+ }
+
+ hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(k.Public(), x509.SHA1WithRSA)
+ if err != nil {
+ return nil, err
+ }
+
+ responseHash := hashFunc.New()
+ responseHash.Write(tbsResponseDataDER)
+ signature, err := k.Sign(rand.Reader, responseHash.Sum(nil), hashFunc)
+ if err != nil {
+ return nil, err
+ }
+
+ response := basicResponse{
+ TBSResponseData: tbsResponseData,
+ SignatureAlgorithm: signatureAlgorithm,
+ Signature: asn1.BitString{
+ Bytes: signature,
+ BitLength: 8 * len(signature),
+ },
+ }
+ responseDER, err := asn1.Marshal(response)
+ if err != nil {
+ return nil, err
+ }
+
+ return asn1.Marshal(responseASN1{
+ Status: asn1.Enumerated(Success),
+ Response: responseBytes{
+ ResponseType: idPKIXOCSPBasic,
+ Response: responseDER,
+ },
+ })
+}
+
+func TestOCSPDecodeMultiResponse(t *testing.T) {
+ respBytes, err := createMultiResp()
+ if err != nil {
+ t.Fatal(err)
+ }
+ matchingCert := &x509.Certificate{SerialNumber: big.NewInt(3)}
+ resp, err := ParseResponseForCert(respBytes, matchingCert, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if resp.SerialNumber.Cmp(matchingCert.SerialNumber) != 0 {
+ t.Errorf("resp.SerialNumber: got %x, want %x", resp.SerialNumber, 3)
+ }
+}
+
+func TestOCSPDecodeMultiResponseWithoutMatchingCert(t *testing.T) {
+ respBytes, err := createMultiResp()
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = ParseResponseForCert(respBytes, &x509.Certificate{SerialNumber: big.NewInt(100)}, nil)
+ want := ParseError("no response matching the supplied certificate")
+ if err != want {
+ t.Errorf("err: got %q, want %q", err, want)
+ }
+}
+
+// This OCSP response was taken from GTS's public OCSP responder.
+// To recreate:
+// $ openssl s_client -tls1 -showcerts -servername golang.org -connect golang.org:443
+// Copy and paste the first certificate into /tmp/cert.crt and the second into
+// /tmp/intermediate.crt
+// Note: depending on what version of openssl you are using, you may need to use the key=value
+// form for the header argument (i.e. -header host=ocsp.pki.goog)
+// $ openssl ocsp -issuer /tmp/intermediate.crt -cert /tmp/cert.crt -url http://ocsp.pki.goog/gts1c3 -header host ocsp.pki.goog -resp_text -respout /tmp/ocsp.der
+// Then hex encode the result:
+// $ python -c 'print file("/tmp/ocsp.der", "r").read().encode("hex")'
+
+const ocspResponseHex = "308201d40a0100a08201cd308201c906092b0601050507300101048201ba308201b630819fa21604148a747faf85cdee95cd3d9cd0e24614f371351d27180f32303231313130373134323535335a30743072304a300906052b0e03021a05000414c72e798addff6134b3baed4742b8bbc6c024076304148a747faf85cdee95cd3d9cd0e24614f371351d27021100f374542e3c7a68360a000000011034628000180f32303231313130373134323535315aa011180f32303231313131343133323535305a300d06092a864886f70d01010b0500038201010087749296e681abe36f2efef047730178ce57e948426959ac62ac5f25b9a63ba3b7f31b9f683aea384d21845c8dda09498f2531c78f3add3969ca4092f31f58ac3c2613719d63b7b9a5260e52814c827f8dd44f4f753b2528bcd03ccec02cdcd4918247f5323f8cfc12cee4ac8f0361587b267019cfd12336db09b04eac59807a480213cfcd9913a3aa2d13a6c88c0a750475a0e991806d94ec0fc9dab599171a43a08e6d935b4a4a13dff9c4a97ad46cef6fb4d61cb2363d788c12d81cce851b478889c2e05d80cd00ae346772a1e7502f011e2ed9be8ef4b194c8b65d6e33671d878cfb30267972075b062ff3d56b51984bf685161afc6e2538dd6e6a23063c"
+
+const GTSRoot = `-----BEGIN CERTIFICATE-----
+MIIFljCCA36gAwIBAgINAgO8U1lrNMcY9QFQZjANBgkqhkiG9w0BAQsFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjAwODEzMDAwMDQyWhcNMjcwOTMwMDAw
+MDQyWjBGMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzETMBEGA1UEAxMKR1RTIENBIDFDMzCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAPWI3+dijB43+DdCkH9sh9D7ZYIl/ejLa6T/belaI+KZ9hzp
+kgOZE3wJCor6QtZeViSqejOEH9Hpabu5dOxXTGZok3c3VVP+ORBNtzS7XyV3NzsX
+lOo85Z3VvMO0Q+sup0fvsEQRY9i0QYXdQTBIkxu/t/bgRQIh4JZCF8/ZK2VWNAcm
+BA2o/X3KLu/qSHw3TT8An4Pf73WELnlXXPxXbhqW//yMmqaZviXZf5YsBvcRKgKA
+gOtjGDxQSYflispfGStZloEAoPtR28p3CwvJlk/vcEnHXG0g/Zm0tOLKLnf9LdwL
+tmsTDIwZKxeWmLnwi/agJ7u2441Rj72ux5uxiZ0CAwEAAaOCAYAwggF8MA4GA1Ud
+DwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0T
+AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUinR/r4XN7pXNPZzQ4kYU83E1HScwHwYD
+VR0jBBgwFoAU5K8rJnEaK0gnhS9SZizv8IkTcT4waAYIKwYBBQUHAQEEXDBaMCYG
+CCsGAQUFBzABhhpodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHNyMTAwBggrBgEFBQcw
+AoYkaHR0cDovL3BraS5nb29nL3JlcG8vY2VydHMvZ3RzcjEuZGVyMDQGA1UdHwQt
+MCswKaAnoCWGI2h0dHA6Ly9jcmwucGtpLmdvb2cvZ3RzcjEvZ3RzcjEuY3JsMFcG
+A1UdIARQME4wOAYKKwYBBAHWeQIFAzAqMCgGCCsGAQUFBwIBFhxodHRwczovL3Br
+aS5nb29nL3JlcG9zaXRvcnkvMAgGBmeBDAECATAIBgZngQwBAgIwDQYJKoZIhvcN
+AQELBQADggIBAIl9rCBcDDy+mqhXlRu0rvqrpXJxtDaV/d9AEQNMwkYUuxQkq/BQ
+cSLbrcRuf8/xam/IgxvYzolfh2yHuKkMo5uhYpSTld9brmYZCwKWnvy15xBpPnrL
+RklfRuFBsdeYTWU0AIAaP0+fbH9JAIFTQaSSIYKCGvGjRFsqUBITTcFTNvNCCK9U
++o53UxtkOCcXCb1YyRt8OS1b887U7ZfbFAO/CVMkH8IMBHmYJvJh8VNS/UKMG2Yr
+PxWhu//2m+OBmgEGcYk1KCTd4b3rGS3hSMs9WYNRtHTGnXzGsYZbr8w0xNPM1IER
+lQCh9BIiAfq0g3GvjLeMcySsN1PCAJA/Ef5c7TaUEDu9Ka7ixzpiO2xj2YC/WXGs
+Yye5TBeg2vZzFb8q3o/zpWwygTMD0IZRcZk0upONXbVRWPeyk+gB9lm+cZv9TSjO
+z23HFtz30dZGm6fKa+l3D/2gthsjgx0QGtkJAITgRNOidSOzNIb2ILCkXhAd4FJG
+AJ2xDx8hcFH1mt0G/FX0Kw4zd8NLQsLxdxP8c4CU6x+7Nz/OAipmsHMdMqUybDKw
+juDEI/9bfU1lcKwrmz3O2+BtjjKAvpafkmO8l7tdufThcV4q5O8DIrGKZTqPwJNl
+1IXNDw9bg1kWRxYtnCQ6yICmJhSFm/Y3m6xv+cXDBlHz4n/FsRC6UfTd
+-----END CERTIFICATE-----`
+
+const startComHex = "308206343082041ca003020102020118300d06092a864886f70d0101050500307d310b30" +
+ "0906035504061302494c31163014060355040a130d5374617274436f6d204c74642e312b" +
+ "3029060355040b1322536563757265204469676974616c20436572746966696361746520" +
+ "5369676e696e6731293027060355040313205374617274436f6d20436572746966696361" +
+ "74696f6e20417574686f72697479301e170d3037313032343230353431375a170d313731" +
+ "3032343230353431375a30818c310b300906035504061302494c31163014060355040a13" +
+ "0d5374617274436f6d204c74642e312b3029060355040b13225365637572652044696769" +
+ "74616c204365727469666963617465205369676e696e67313830360603550403132f5374" +
+ "617274436f6d20436c6173732031205072696d61727920496e7465726d65646961746520" +
+ "53657276657220434130820122300d06092a864886f70d01010105000382010f00308201" +
+ "0a0282010100b689c6acef09527807ac9263d0f44418188480561f91aee187fa3250b4d3" +
+ "4706f0e6075f700e10f71dc0ce103634855a0f92ac83c6ac58523fba38e8fce7a724e240" +
+ "a60876c0926e9e2a6d4d3f6e61200adb59ded27d63b33e46fefa215118d7cd30a6ed076e" +
+ "3b7087b4f9faebee823c056f92f7a4dc0a301e9373fe07cad75f809d225852ae06da8b87" +
+ "2369b0e42ad8ea83d2bdf371db705a280faf5a387045123f304dcd3baf17e50fcba0a95d" +
+ "48aab16150cb34cd3c5cc30be810c08c9bf0030362feb26c3e720eee1c432ac9480e5739" +
+ "c43121c810c12c87fe5495521f523c31129b7fe7c0a0a559d5e28f3ef0d5a8e1d77031a9" +
+ "c4b3cfaf6d532f06f4a70203010001a38201ad308201a9300f0603551d130101ff040530" +
+ "030101ff300e0603551d0f0101ff040403020106301d0603551d0e04160414eb4234d098" +
+ "b0ab9ff41b6b08f7cc642eef0e2c45301f0603551d230418301680144e0bef1aa4405ba5" +
+ "17698730ca346843d041aef2306606082b06010505070101045a3058302706082b060105" +
+ "05073001861b687474703a2f2f6f6373702e737461727473736c2e636f6d2f6361302d06" +
+ "082b060105050730028621687474703a2f2f7777772e737461727473736c2e636f6d2f73" +
+ "667363612e637274305b0603551d1f045430523027a025a0238621687474703a2f2f7777" +
+ "772e737461727473736c2e636f6d2f73667363612e63726c3027a025a023862168747470" +
+ "3a2f2f63726c2e737461727473736c2e636f6d2f73667363612e63726c3081800603551d" +
+ "20047930773075060b2b0601040181b5370102013066302e06082b060105050702011622" +
+ "687474703a2f2f7777772e737461727473736c2e636f6d2f706f6c6963792e7064663034" +
+ "06082b060105050702011628687474703a2f2f7777772e737461727473736c2e636f6d2f" +
+ "696e7465726d6564696174652e706466300d06092a864886f70d01010505000382020100" +
+ "2109493ea5886ee00b8b48da314d8ff75657a2e1d36257e9b556f38545753be5501f048b" +
+ "e6a05a3ee700ae85d0fbff200364cbad02e1c69172f8a34dd6dee8cc3fa18aa2e37c37a7" +
+ "c64f8f35d6f4d66e067bdd21d9cf56ffcb302249fe8904f385e5aaf1e71fe875904dddf9" +
+ "46f74234f745580c110d84b0c6da5d3ef9019ee7e1da5595be741c7bfc4d144fac7e5547" +
+ "7d7bf4a50d491e95e8f712c1ccff76a62547d0f37535be97b75816ebaa5c786fec5330af" +
+ "ea044dcca902e3f0b60412f630b1113d904e5664d7dc3c435f7339ef4baf87ebf6fe6888" +
+ "4472ead207c669b0c1a18bef1749d761b145485f3b2021e95bb2ccf4d7e931f50b15613b" +
+ "7a94e3ebd9bc7f94ae6ae3626296a8647cb887f399327e92a252bebbf865cfc9f230fc8b" +
+ "c1c2a696d75f89e15c3480f58f47072fb491bfb1a27e5f4b5ad05b9f248605515a690365" +
+ "434971c5e06f94346bf61bd8a9b04c7e53eb8f48dfca33b548fa364a1a53a6330cd089cd" +
+ "4915cd89313c90c072d7654b52358a461144b93d8e2865a63e799e5c084429adb035112e" +
+ "214eb8d2e7103e5d8483b3c3c2e4d2c6fd094b7409ddf1b3d3193e800da20b19f038e7c5" +
+ "c2afe223db61e29d5c6e2089492e236ab262c145b49faf8ba7f1223bf87de290d07a19fb" +
+ "4a4ce3d27d5f4a8303ed27d6239e6b8db459a2d9ef6c8229dd75193c3f4c108defbb7527" +
+ "d2ae83a7a8ce5ba7"
+
+const ocspResponseWithoutCertHex = "308201d40a0100a08201cd308201c906092b0601050507300101048201ba3082" +
+ "01b630819fa2160414884451ff502a695e2d88f421bad90cf2cecbea7c180f3230313330" +
+ "3631383037323434335a30743072304a300906052b0e03021a0500041448b60d38238df8" +
+ "456e4ee5843ea394111802979f0414884451ff502a695e2d88f421bad90cf2cecbea7c02" +
+ "1100f78b13b946fc9635d8ab49de9d2148218000180f3230313330363138303732343433" +
+ "5aa011180f32303133303632323037323434335a300d06092a864886f70d010105050003" +
+ "82010100103e18b3d297a5e7a6c07a4fc52ac46a15c0eba96f3be17f0ffe84de5b8c8e05" +
+ "5a8f577586a849dc4abd6440eb6fedde4622451e2823c1cbf3558b4e8184959c9fe96eff" +
+ "8bc5f95866c58c6d087519faabfdae37e11d9874f1bc0db292208f645dd848185e4dd38b" +
+ "6a8547dfa7b74d514a8470015719064d35476b95bebb03d4d2845c5ca15202d2784878f2" +
+ "0f904c24f09736f044609e9c271381713400e563023d212db422236440c6f377bbf24b2b" +
+ "9e7dec8698e36a8df68b7592ad3489fb2937afb90eb85d2aa96b81c94c25057dbd4759d9" +
+ "20a1a65c7f0b6427a224b3c98edd96b9b61f706099951188b0289555ad30a216fb774651" +
+ "5a35fca2e054dfa8"
+
+// PKIX nonce extension
+var ocspExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 2}
+var ocspExtensionValueHex = "0403000000"
+
+const ocspResponseWithCriticalExtensionHex = "308204fe0a0100a08204f7308204f306092b0601050507300101048204e4308204e03081" +
+ "dba003020100a11b3019311730150603550403130e4f43535020526573706f6e64657218" +
+ "0f32303136303130343137303130305a3081a53081a23049300906052b0e03021a050004" +
+ "14c0fe0278fc99188891b3f212e9c7e1b21ab7bfc004140dfc1df0a9e0f01ce7f2b21317" +
+ "7e6f8d157cd4f60210017f77deb3bcbb235d44ccc7dba62e72a116180f32303130303730" +
+ "373135303130355aa0030a0101180f32303130303730373135303130355aa011180f3230" +
+ "3130303730373138333531375aa1193017301506092b06010505073001020101ff040504" +
+ "03000000300d06092a864886f70d01010b0500038201010031c730ca60a7a0d92d8e4010" +
+ "911b469de95b4d27e89de6537552436237967694f76f701cf6b45c932bd308bca4a8d092" +
+ "5c604ba94796903091d9e6c000178e72c1f0a24a277dd262835af5d17d3f9d7869606c9f" +
+ "e7c8e708a41645699895beee38bfa63bb46296683761c5d1d65439b8ab868dc3017c9eeb" +
+ "b70b82dbf3a31c55b457d48bb9e82b335ed49f445042eaf606b06a3e0639824924c89c63" +
+ "eccddfe85e6694314138b2536f5e15e07085d0f6e26d4b2f8244bab0d70de07283ac6384" +
+ "a0501fc3dea7cf0adfd4c7f34871080900e252ddc403e3f0265f2a704af905d3727504ed" +
+ "28f3214a219d898a022463c78439799ca81c8cbafdbcec34ea937cd6a08202ea308202e6" +
+ "308202e2308201caa003020102020101300d06092a864886f70d01010b05003019311730" +
+ "150603550403130e4f43535020526573706f6e646572301e170d31353031333031353530" +
+ "33335a170d3136303133303135353033335a3019311730150603550403130e4f43535020" +
+ "526573706f6e64657230820122300d06092a864886f70d01010105000382010f00308201" +
+ "0a0282010100e8155f2d3e6f2e8d14c62a788bd462f9f844e7a6977c83ef1099f0f6616e" +
+ "c5265b56f356e62c5400f0b06a2e7945a82752c636df32a895152d6074df1701dc6ccfbc" +
+ "bec75a70bd2b55ae2be7e6cad3b5fd4cd5b7790ab401a436d3f5f346074ffde8a99d5b72" +
+ "3350f0a112076614b12ef79c78991b119453445acf2416ab0046b540db14c9fc0f27b898" +
+ "9ad0f63aa4b8aefc91aa8a72160c36307c60fec78a93d3fddf4259902aa77e7332971c7d" +
+ "285b6a04f648993c6922a3e9da9adf5f81508c3228791843e5d49f24db2f1290bafd97e6" +
+ "55b1049a199f652cd603c4fafa330c390b0da78fbbc67e8fa021cbd74eb96222b12ace31" +
+ "a77dcf920334dc94581b0203010001a3353033300e0603551d0f0101ff04040302078030" +
+ "130603551d25040c300a06082b06010505070309300c0603551d130101ff04023000300d" +
+ "06092a864886f70d01010b05000382010100718012761b5063e18f0dc44644d8e6ab8612" +
+ "31c15fd5357805425d82aec1de85bf6d3e30fce205e3e3b8b795bbe52e40a439286d2288" +
+ "9064f4aeeb150359b9425f1da51b3a5c939018555d13ac42c565a0603786a919328f3267" +
+ "09dce52c22ad958ecb7873b9771d1148b1c4be2efe80ba868919fc9f68b6090c2f33c156" +
+ "d67156e42766a50b5d51e79637b7e58af74c2a951b1e642fa7741fec982cc937de37eff5" +
+ "9e2005d5939bfc031589ca143e6e8ab83f40ee08cc20a6b4a95a318352c28d18528dcaf9" +
+ "66705de17afa19d6e8ae91ddf33179d16ebb6ac2c69cae8373d408ebf8c55308be6c04d9" +
+ "3a25439a94299a65a709756c7a3e568be049d5c38839"
+
+const ocspResponseWithExtensionHex = "308204fb0a0100a08204f4308204f006092b0601050507300101048204e1308204dd3081" +
+ "d8a003020100a11b3019311730150603550403130e4f43535020526573706f6e64657218" +
+ "0f32303136303130343136353930305a3081a230819f3049300906052b0e03021a050004" +
+ "14c0fe0278fc99188891b3f212e9c7e1b21ab7bfc004140dfc1df0a9e0f01ce7f2b21317" +
+ "7e6f8d157cd4f60210017f77deb3bcbb235d44ccc7dba62e72a116180f32303130303730" +
+ "373135303130355aa0030a0101180f32303130303730373135303130355aa011180f3230" +
+ "3130303730373138333531375aa1163014301206092b0601050507300102040504030000" +
+ "00300d06092a864886f70d01010b05000382010100c09a33e0b2324c852421bb83f85ac9" +
+ "9113f5426012bd2d2279a8166e9241d18a33c870894250622ffc7ed0c4601b16d624f90b" +
+ "779265442cdb6868cf40ab304ab4b66e7315ed02cf663b1601d1d4751772b31bc299db23" +
+ "9aebac78ed6797c06ed815a7a8d18d63cfbb609cafb47ec2e89e37db255216eb09307848" +
+ "d01be0a3e943653c78212b96ff524b74c9ec456b17cdfb950cc97645c577b2e09ff41dde" +
+ "b03afb3adaa381cc0f7c1d95663ef22a0f72f2c45613ae8e2b2d1efc96e8463c7d1d8a1d" +
+ "7e3b35df8fe73a301fc3f804b942b2b3afa337ff105fc1462b7b1c1d75eb4566c8665e59" +
+ "f80393b0adbf8004ff6c3327ed34f007cb4a3348a7d55e06e3a08202ea308202e6308202" +
+ "e2308201caa003020102020101300d06092a864886f70d01010b05003019311730150603" +
+ "550403130e4f43535020526573706f6e646572301e170d3135303133303135353033335a" +
+ "170d3136303133303135353033335a3019311730150603550403130e4f43535020526573" +
+ "706f6e64657230820122300d06092a864886f70d01010105000382010f003082010a0282" +
+ "010100e8155f2d3e6f2e8d14c62a788bd462f9f844e7a6977c83ef1099f0f6616ec5265b" +
+ "56f356e62c5400f0b06a2e7945a82752c636df32a895152d6074df1701dc6ccfbcbec75a" +
+ "70bd2b55ae2be7e6cad3b5fd4cd5b7790ab401a436d3f5f346074ffde8a99d5b723350f0" +
+ "a112076614b12ef79c78991b119453445acf2416ab0046b540db14c9fc0f27b8989ad0f6" +
+ "3aa4b8aefc91aa8a72160c36307c60fec78a93d3fddf4259902aa77e7332971c7d285b6a" +
+ "04f648993c6922a3e9da9adf5f81508c3228791843e5d49f24db2f1290bafd97e655b104" +
+ "9a199f652cd603c4fafa330c390b0da78fbbc67e8fa021cbd74eb96222b12ace31a77dcf" +
+ "920334dc94581b0203010001a3353033300e0603551d0f0101ff04040302078030130603" +
+ "551d25040c300a06082b06010505070309300c0603551d130101ff04023000300d06092a" +
+ "864886f70d01010b05000382010100718012761b5063e18f0dc44644d8e6ab861231c15f" +
+ "d5357805425d82aec1de85bf6d3e30fce205e3e3b8b795bbe52e40a439286d22889064f4" +
+ "aeeb150359b9425f1da51b3a5c939018555d13ac42c565a0603786a919328f326709dce5" +
+ "2c22ad958ecb7873b9771d1148b1c4be2efe80ba868919fc9f68b6090c2f33c156d67156" +
+ "e42766a50b5d51e79637b7e58af74c2a951b1e642fa7741fec982cc937de37eff59e2005" +
+ "d5939bfc031589ca143e6e8ab83f40ee08cc20a6b4a95a318352c28d18528dcaf966705d" +
+ "e17afa19d6e8ae91ddf33179d16ebb6ac2c69cae8373d408ebf8c55308be6c04d93a2543" +
+ "9a94299a65a709756c7a3e568be049d5c38839"
+
+const ocspRequestHex = "3051304f304d304b3049300906052b0e03021a05000414c0fe0278fc99188891b3f212e9" +
+ "c7e1b21ab7bfc004140dfc1df0a9e0f01ce7f2b213177e6f8d157cd4f60210017f77deb3" +
+ "bcbb235d44ccc7dba62e72"
+
+const leafCertHex = "308203c830820331a0030201020210017f77deb3bcbb235d44ccc7dba62e72300d06092a" +
+ "864886f70d01010505003081ba311f301d060355040a1316566572695369676e20547275" +
+ "7374204e6574776f726b31173015060355040b130e566572695369676e2c20496e632e31" +
+ "333031060355040b132a566572695369676e20496e7465726e6174696f6e616c20536572" +
+ "766572204341202d20436c617373203331493047060355040b13407777772e7665726973" +
+ "69676e2e636f6d2f43505320496e636f72702e6279205265662e204c494142494c495459" +
+ "204c54442e286329393720566572695369676e301e170d3132303632313030303030305a" +
+ "170d3133313233313233353935395a3068310b3009060355040613025553311330110603" +
+ "550408130a43616c69666f726e6961311230100603550407130950616c6f20416c746f31" +
+ "173015060355040a130e46616365626f6f6b2c20496e632e311730150603550403140e2a" +
+ "2e66616365626f6f6b2e636f6d30819f300d06092a864886f70d010101050003818d0030" +
+ "818902818100ae94b171e2deccc1693e051063240102e0689ae83c39b6b3e74b97d48d7b" +
+ "23689100b0b496ee62f0e6d356bcf4aa0f50643402f5d1766aa972835a7564723f39bbef" +
+ "5290ded9bcdbf9d3d55dfad23aa03dc604c54d29cf1d4b3bdbd1a809cfae47b44c7eae17" +
+ "c5109bee24a9cf4a8d911bb0fd0415ae4c3f430aa12a557e2ae10203010001a382011e30" +
+ "82011a30090603551d130402300030440603551d20043d303b3039060b6086480186f845" +
+ "01071703302a302806082b06010505070201161c68747470733a2f2f7777772e76657269" +
+ "7369676e2e636f6d2f727061303c0603551d1f043530333031a02fa02d862b687474703a" +
+ "2f2f535652496e746c2d63726c2e766572697369676e2e636f6d2f535652496e746c2e63" +
+ "726c301d0603551d250416301406082b0601050507030106082b06010505070302300b06" +
+ "03551d0f0404030205a0303406082b0601050507010104283026302406082b0601050507" +
+ "30018618687474703a2f2f6f6373702e766572697369676e2e636f6d30270603551d1104" +
+ "20301e820e2a2e66616365626f6f6b2e636f6d820c66616365626f6f6b2e636f6d300d06" +
+ "092a864886f70d0101050500038181005b6c2b75f8ed30aa51aad36aba595e555141951f" +
+ "81a53b447910ac1f76ff78fc2781616b58f3122afc1c87010425e9ed43df1a7ba6498060" +
+ "67e2688af03db58c7df4ee03309a6afc247ccb134dc33e54c6bc1d5133a532a73273b1d7" +
+ "9cadc08e7e1a83116d34523340b0305427a21742827c98916698ee7eaf8c3bdd71700817"
+
+const issuerCertHex = "30820383308202eca003020102021046fcebbab4d02f0f926098233f93078f300d06092a" +
+ "864886f70d0101050500305f310b300906035504061302555331173015060355040a130e" +
+ "566572695369676e2c20496e632e31373035060355040b132e436c617373203320507562" +
+ "6c6963205072696d6172792043657274696669636174696f6e20417574686f7269747930" +
+ "1e170d3937303431373030303030305a170d3136313032343233353935395a3081ba311f" +
+ "301d060355040a1316566572695369676e205472757374204e6574776f726b3117301506" +
+ "0355040b130e566572695369676e2c20496e632e31333031060355040b132a5665726953" +
+ "69676e20496e7465726e6174696f6e616c20536572766572204341202d20436c61737320" +
+ "3331493047060355040b13407777772e766572697369676e2e636f6d2f43505320496e63" +
+ "6f72702e6279205265662e204c494142494c495459204c54442e28632939372056657269" +
+ "5369676e30819f300d06092a864886f70d010101050003818d0030818902818100d88280" +
+ "e8d619027d1f85183925a2652be1bfd405d3bce6363baaf04c6c5bb6e7aa3c734555b2f1" +
+ "bdea9742ed9a340a15d4a95cf54025ddd907c132b2756cc4cabba3fe56277143aa63f530" +
+ "3e9328e5faf1093bf3b74d4e39f75c495ab8c11dd3b28afe70309542cbfe2b518b5a3c3a" +
+ "f9224f90b202a7539c4f34e7ab04b27b6f0203010001a381e33081e0300f0603551d1304" +
+ "0830060101ff02010030440603551d20043d303b3039060b6086480186f8450107010130" +
+ "2a302806082b06010505070201161c68747470733a2f2f7777772e766572697369676e2e" +
+ "636f6d2f43505330340603551d25042d302b06082b0601050507030106082b0601050507" +
+ "030206096086480186f8420401060a6086480186f845010801300b0603551d0f04040302" +
+ "0106301106096086480186f842010104040302010630310603551d1f042a30283026a024" +
+ "a0228620687474703a2f2f63726c2e766572697369676e2e636f6d2f706361332e63726c" +
+ "300d06092a864886f70d010105050003818100408e4997968a73dd8e4def3e61b7caa062" +
+ "adf40e0abb753de26ed82cc7bff4b98c369bcaa2d09c724639f6a682036511c4bcbf2da6" +
+ "f5d93b0ab598fab378b91ef22b4c62d5fdb27a1ddf33fd73f9a5d82d8c2aead1fcb028b6" +
+ "e94948134b838a1b487b24f738de6f4154b8ab576b06dfc7a2d4a9f6f136628088f28b75" +
+ "d68071"
+
+// Key and certificate for the OCSP responder were not taken from the Thawte
+// responder, since CreateResponse requires that we have the private key.
+// Instead, they were generated randomly.
+const responderPrivateKeyHex = "308204a40201000282010100e8155f2d3e6f2e8d14c62a788bd462f9f844e7a6977c83ef" +
+ "1099f0f6616ec5265b56f356e62c5400f0b06a2e7945a82752c636df32a895152d6074df" +
+ "1701dc6ccfbcbec75a70bd2b55ae2be7e6cad3b5fd4cd5b7790ab401a436d3f5f346074f" +
+ "fde8a99d5b723350f0a112076614b12ef79c78991b119453445acf2416ab0046b540db14" +
+ "c9fc0f27b8989ad0f63aa4b8aefc91aa8a72160c36307c60fec78a93d3fddf4259902aa7" +
+ "7e7332971c7d285b6a04f648993c6922a3e9da9adf5f81508c3228791843e5d49f24db2f" +
+ "1290bafd97e655b1049a199f652cd603c4fafa330c390b0da78fbbc67e8fa021cbd74eb9" +
+ "6222b12ace31a77dcf920334dc94581b02030100010282010100bcf0b93d7238bda329a8" +
+ "72e7149f61bcb37c154330ccb3f42a85c9002c2e2bdea039d77d8581cd19bed94078794e" +
+ "56293d601547fc4bf6a2f9002fe5772b92b21b254403b403585e3130cc99ccf08f0ef81a" +
+ "575b38f597ba4660448b54f44bfbb97072b5a2bf043bfeca828cf7741d13698e3f38162b" +
+ "679faa646b82abd9a72c5c7d722c5fc577a76d2c2daac588accad18516d1bbad10b0dfa2" +
+ "05cfe246b59e28608a43942e1b71b0c80498075121de5b900d727c31c42c78cf1db5c0aa" +
+ "5b491e10ea4ed5c0962aaf2ae025dd81fa4ce490d9d6b4a4465411d8e542fc88617e5695" +
+ "1aa4fc8ea166f2b4d0eb89ef17f2b206bd5f1014bf8fe0e71fe62f2cccf102818100f2dc" +
+ "ddf878d553286daad68bac4070a82ffec3dc4666a2750f47879eec913f91836f1d976b60" +
+ "daf9356e078446dafab5bd2e489e5d64f8572ba24a4ba4f3729b5e106c4dd831cc2497a7" +
+ "e6c7507df05cb64aeb1bbc81c1e340d58b5964cf39cff84ea30c29ec5d3f005ee1362698" +
+ "07395037955955655292c3e85f6187fa1f9502818100f4a33c102630840705f8c778a47b" +
+ "87e8da31e68809af981ac5e5999cf1551685d761cdf0d6520361b99aebd5777a940fa64d" +
+ "327c09fa63746fbb3247ec73a86edf115f1fe5c83598db803881ade71c33c6e956118345" +
+ "497b98b5e07bb5be75971465ec78f2f9467e1b74956ca9d4c7c3e314e742a72d8b33889c" +
+ "6c093a466cef0281801d3df0d02124766dd0be98349b19eb36a508c4e679e793ba0a8bef" +
+ "4d786888c1e9947078b1ea28938716677b4ad8c5052af12eb73ac194915264a913709a0b" +
+ "7b9f98d4a18edd781a13d49899f91c20dbd8eb2e61d991ba19b5cdc08893f5cb9d39e5a6" +
+ "0629ea16d426244673b1b3ee72bd30e41fac8395acac40077403de5efd028180050731dd" +
+ "d71b1a2b96c8d538ba90bb6b62c8b1c74c03aae9a9f59d21a7a82b0d572ef06fa9c807bf" +
+ "c373d6b30d809c7871df96510c577421d9860c7383fda0919ece19996b3ca13562159193" +
+ "c0c246471e287f975e8e57034e5136aaf44254e2650def3d51292474c515b1588969112e" +
+ "0a85cc77073e9d64d2c2fc497844284b02818100d71d63eabf416cf677401ebf965f8314" +
+ "120b568a57dd3bd9116c629c40dc0c6948bab3a13cc544c31c7da40e76132ef5dd3f7534" +
+ "45a635930c74326ae3df0edd1bfb1523e3aa259873ac7cf1ac31151ec8f37b528c275622" +
+ "48f99b8bed59fd4da2576aa6ee20d93a684900bf907e80c66d6e2261ae15e55284b4ed9d" +
+ "6bdaa059"
+
+const responderCertHex = "308202e2308201caa003020102020101300d06092a864886f70d01010b05003019311730" +
+ "150603550403130e4f43535020526573706f6e646572301e170d31353031333031353530" +
+ "33335a170d3136303133303135353033335a3019311730150603550403130e4f43535020" +
+ "526573706f6e64657230820122300d06092a864886f70d01010105000382010f00308201" +
+ "0a0282010100e8155f2d3e6f2e8d14c62a788bd462f9f844e7a6977c83ef1099f0f6616e" +
+ "c5265b56f356e62c5400f0b06a2e7945a82752c636df32a895152d6074df1701dc6ccfbc" +
+ "bec75a70bd2b55ae2be7e6cad3b5fd4cd5b7790ab401a436d3f5f346074ffde8a99d5b72" +
+ "3350f0a112076614b12ef79c78991b119453445acf2416ab0046b540db14c9fc0f27b898" +
+ "9ad0f63aa4b8aefc91aa8a72160c36307c60fec78a93d3fddf4259902aa77e7332971c7d" +
+ "285b6a04f648993c6922a3e9da9adf5f81508c3228791843e5d49f24db2f1290bafd97e6" +
+ "55b1049a199f652cd603c4fafa330c390b0da78fbbc67e8fa021cbd74eb96222b12ace31" +
+ "a77dcf920334dc94581b0203010001a3353033300e0603551d0f0101ff04040302078030" +
+ "130603551d25040c300a06082b06010505070309300c0603551d130101ff04023000300d" +
+ "06092a864886f70d01010b05000382010100718012761b5063e18f0dc44644d8e6ab8612" +
+ "31c15fd5357805425d82aec1de85bf6d3e30fce205e3e3b8b795bbe52e40a439286d2288" +
+ "9064f4aeeb150359b9425f1da51b3a5c939018555d13ac42c565a0603786a919328f3267" +
+ "09dce52c22ad958ecb7873b9771d1148b1c4be2efe80ba868919fc9f68b6090c2f33c156" +
+ "d67156e42766a50b5d51e79637b7e58af74c2a951b1e642fa7741fec982cc937de37eff5" +
+ "9e2005d5939bfc031589ca143e6e8ab83f40ee08cc20a6b4a95a318352c28d18528dcaf9" +
+ "66705de17afa19d6e8ae91ddf33179d16ebb6ac2c69cae8373d408ebf8c55308be6c04d9" +
+ "3a25439a94299a65a709756c7a3e568be049d5c38839"
+
+const errorResponseHex = "30030a0101"
diff --git a/local_crypto_patch/contents/openpgp/armor/armor.go b/local_crypto_patch/contents/openpgp/armor/armor.go
new file mode 100644
index 0000000000..e664d127cb
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/armor/armor.go
@@ -0,0 +1,233 @@
+// 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 armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
+// very similar to PEM except that it has an additional CRC checksum.
+//
+// Deprecated: this package is unmaintained except for security fixes. New
+// applications should consider a more focused, modern alternative to OpenPGP
+// for their specific task. If you are required to interoperate with OpenPGP
+// systems and need a maintained package, consider a community fork.
+// See https://golang.org/issue/44226.
+package armor
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/base64"
+ "io"
+
+ "golang.org/x/crypto/openpgp/errors"
+)
+
+// A Block represents an OpenPGP armored structure.
+//
+// The encoded form is:
+//
+// -----BEGIN Type-----
+// Headers
+//
+// base64-encoded Bytes
+// '=' base64 encoded checksum
+// -----END Type-----
+//
+// where Headers is a possibly empty sequence of Key: Value lines.
+//
+// Since the armored data can be very large, this package presents a streaming
+// interface.
+type Block struct {
+ Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE").
+ Header map[string]string // Optional headers.
+ Body io.Reader // A Reader from which the contents can be read
+ lReader lineReader
+ oReader openpgpReader
+}
+
+var ArmorCorrupt error = errors.StructuralError("armor invalid")
+
+const crc24Init = 0xb704ce
+const crc24Poly = 0x1864cfb
+const crc24Mask = 0xffffff
+
+// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1
+func crc24(crc uint32, d []byte) uint32 {
+ for _, b := range d {
+ crc ^= uint32(b) << 16
+ for i := 0; i < 8; i++ {
+ crc <<= 1
+ if crc&0x1000000 != 0 {
+ crc ^= crc24Poly
+ }
+ }
+ }
+ return crc
+}
+
+var armorStart = []byte("-----BEGIN ")
+var armorEnd = []byte("-----END ")
+var armorEndOfLine = []byte("-----")
+
+// lineReader wraps a line based reader. It watches for the end of an armor
+// block and records the expected CRC value.
+type lineReader struct {
+ in *bufio.Reader
+ buf []byte
+ eof bool
+ crc uint32
+ crcSet bool
+}
+
+func (l *lineReader) Read(p []byte) (n int, err error) {
+ if l.eof {
+ return 0, io.EOF
+ }
+
+ if len(l.buf) > 0 {
+ n = copy(p, l.buf)
+ l.buf = l.buf[n:]
+ return
+ }
+
+ line, isPrefix, err := l.in.ReadLine()
+ if err != nil {
+ return
+ }
+ if isPrefix {
+ return 0, ArmorCorrupt
+ }
+
+ if bytes.HasPrefix(line, armorEnd) {
+ l.eof = true
+ return 0, io.EOF
+ }
+
+ if len(line) == 5 && line[0] == '=' {
+ // This is the checksum line
+ var expectedBytes [3]byte
+ var m int
+ m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:])
+ if m != 3 || err != nil {
+ return
+ }
+ l.crc = uint32(expectedBytes[0])<<16 |
+ uint32(expectedBytes[1])<<8 |
+ uint32(expectedBytes[2])
+
+ line, _, err = l.in.ReadLine()
+ if err != nil && err != io.EOF {
+ return
+ }
+ if !bytes.HasPrefix(line, armorEnd) {
+ return 0, ArmorCorrupt
+ }
+
+ l.eof = true
+ l.crcSet = true
+ return 0, io.EOF
+ }
+
+ if len(line) > 96 {
+ return 0, ArmorCorrupt
+ }
+
+ n = copy(p, line)
+ bytesToSave := len(line) - n
+ if bytesToSave > 0 {
+ if cap(l.buf) < bytesToSave {
+ l.buf = make([]byte, 0, bytesToSave)
+ }
+ l.buf = l.buf[0:bytesToSave]
+ copy(l.buf, line[n:])
+ }
+
+ return
+}
+
+// openpgpReader passes Read calls to the underlying base64 decoder, but keeps
+// a running CRC of the resulting data and checks the CRC against the value
+// found by the lineReader at EOF.
+type openpgpReader struct {
+ lReader *lineReader
+ b64Reader io.Reader
+ currentCRC uint32
+}
+
+func (r *openpgpReader) Read(p []byte) (n int, err error) {
+ n, err = r.b64Reader.Read(p)
+ r.currentCRC = crc24(r.currentCRC, p[:n])
+
+ if err == io.EOF && r.lReader.crcSet && r.lReader.crc != r.currentCRC&crc24Mask {
+ return 0, ArmorCorrupt
+ }
+
+ return
+}
+
+// Decode reads a PGP armored block from the given Reader. It will ignore
+// leading garbage. If it doesn't find a block, it will return nil, io.EOF. The
+// given Reader is not usable after calling this function: an arbitrary amount
+// of data may have been read past the end of the block.
+func Decode(in io.Reader) (p *Block, err error) {
+ r := bufio.NewReaderSize(in, 100)
+ var line []byte
+ ignoreNext := false
+
+TryNextBlock:
+ p = nil
+
+ // Skip leading garbage
+ for {
+ ignoreThis := ignoreNext
+ line, ignoreNext, err = r.ReadLine()
+ if err != nil {
+ return
+ }
+ if ignoreNext || ignoreThis {
+ continue
+ }
+ line = bytes.TrimSpace(line)
+ if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) {
+ break
+ }
+ }
+
+ p = new(Block)
+ p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)])
+ p.Header = make(map[string]string)
+ nextIsContinuation := false
+ var lastKey string
+
+ // Read headers
+ for {
+ isContinuation := nextIsContinuation
+ line, nextIsContinuation, err = r.ReadLine()
+ if err != nil {
+ p = nil
+ return
+ }
+ if isContinuation {
+ p.Header[lastKey] += string(line)
+ continue
+ }
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 {
+ break
+ }
+
+ i := bytes.Index(line, []byte(": "))
+ if i == -1 {
+ goto TryNextBlock
+ }
+ lastKey = string(line[:i])
+ p.Header[lastKey] = string(line[i+2:])
+ }
+
+ p.lReader.in = r
+ p.oReader.currentCRC = crc24Init
+ p.oReader.lReader = &p.lReader
+ p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader)
+ p.Body = &p.oReader
+
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/armor/armor_test.go b/local_crypto_patch/contents/openpgp/armor/armor_test.go
new file mode 100644
index 0000000000..c05af95fde
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/armor/armor_test.go
@@ -0,0 +1,95 @@
+// 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 armor
+
+import (
+ "bytes"
+ "hash/adler32"
+ "io"
+ "testing"
+)
+
+func TestDecodeEncode(t *testing.T) {
+ buf := bytes.NewBuffer([]byte(armorExample1))
+ result, err := Decode(buf)
+ if err != nil {
+ t.Error(err)
+ }
+ expectedType := "PGP SIGNATURE"
+ if result.Type != expectedType {
+ t.Errorf("result.Type: got:%s want:%s", result.Type, expectedType)
+ }
+ if len(result.Header) != 1 {
+ t.Errorf("len(result.Header): got:%d want:1", len(result.Header))
+ }
+ v, ok := result.Header["Version"]
+ if !ok || v != "GnuPG v1.4.10 (GNU/Linux)" {
+ t.Errorf("result.Header: got:%#v", result.Header)
+ }
+
+ contents, err := io.ReadAll(result.Body)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if adler32.Checksum(contents) != 0x27b144be {
+ t.Errorf("contents: got: %x", contents)
+ }
+
+ buf = bytes.NewBuffer(nil)
+ w, err := Encode(buf, result.Type, result.Header)
+ if err != nil {
+ t.Error(err)
+ }
+ _, err = w.Write(contents)
+ if err != nil {
+ t.Error(err)
+ }
+ w.Close()
+
+ if !bytes.Equal(buf.Bytes(), []byte(armorExample1)) {
+ t.Errorf("got: %s\nwant: %s", string(buf.Bytes()), armorExample1)
+ }
+}
+
+func TestLongHeader(t *testing.T) {
+ buf := bytes.NewBuffer([]byte(armorLongLine))
+ result, err := Decode(buf)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ value, ok := result.Header["Version"]
+ if !ok {
+ t.Errorf("missing Version header")
+ }
+ if value != longValueExpected {
+ t.Errorf("got: %s want: %s", value, longValueExpected)
+ }
+}
+
+const armorExample1 = `-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+iJwEAAECAAYFAk1Fv/0ACgkQo01+GMIMMbsYTwQAiAw+QAaNfY6WBdplZ/uMAccm
+4g+81QPmTSGHnetSb6WBiY13kVzK4HQiZH8JSkmmroMLuGeJwsRTEL4wbjRyUKEt
+p1xwUZDECs234F1xiG5enc5SGlRtP7foLBz9lOsjx+LEcA4sTl5/2eZR9zyFZqWW
+TxRjs+fJCIFuo71xb1g=
+=/teI
+-----END PGP SIGNATURE-----`
+
+const armorLongLine = `-----BEGIN PGP SIGNATURE-----
+Version: 0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz
+
+iQEcBAABAgAGBQJMtFESAAoJEKsQXJGvOPsVj40H/1WW6jaMXv4BW+1ueDSMDwM8
+kx1fLOXbVM5/Kn5LStZNt1jWWnpxdz7eq3uiqeCQjmqUoRde3YbB2EMnnwRbAhpp
+cacnAvy9ZQ78OTxUdNW1mhX5bS6q1MTEJnl+DcyigD70HG/yNNQD7sOPMdYQw0TA
+byQBwmLwmTsuZsrYqB68QyLHI+DUugn+kX6Hd2WDB62DKa2suoIUIHQQCd/ofwB3
+WfCYInXQKKOSxu2YOg2Eb4kLNhSMc1i9uKUWAH+sdgJh7NBgdoE4MaNtBFkHXRvv
+okWuf3+xA9ksp1npSY/mDvgHijmjvtpRDe6iUeqfCn8N9u9CBg8geANgaG8+QA4=
+=wfQG
+-----END PGP SIGNATURE-----`
+
+const longValueExpected = "0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz"
diff --git a/local_crypto_patch/contents/openpgp/armor/encode.go b/local_crypto_patch/contents/openpgp/armor/encode.go
new file mode 100644
index 0000000000..5b6e16c19d
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/armor/encode.go
@@ -0,0 +1,161 @@
+// 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 armor
+
+import (
+ "encoding/base64"
+ "io"
+)
+
+var armorHeaderSep = []byte(": ")
+var blockEnd = []byte("\n=")
+var newline = []byte("\n")
+var armorEndOfLineOut = []byte("-----\n")
+
+// writeSlices writes its arguments to the given Writer.
+func writeSlices(out io.Writer, slices ...[]byte) (err error) {
+ for _, s := range slices {
+ _, err = out.Write(s)
+ if err != nil {
+ return err
+ }
+ }
+ return
+}
+
+// lineBreaker breaks data across several lines, all of the same byte length
+// (except possibly the last). Lines are broken with a single '\n'.
+type lineBreaker struct {
+ lineLength int
+ line []byte
+ used int
+ out io.Writer
+ haveWritten bool
+}
+
+func newLineBreaker(out io.Writer, lineLength int) *lineBreaker {
+ return &lineBreaker{
+ lineLength: lineLength,
+ line: make([]byte, lineLength),
+ used: 0,
+ out: out,
+ }
+}
+
+func (l *lineBreaker) Write(b []byte) (n int, err error) {
+ n = len(b)
+
+ if n == 0 {
+ return
+ }
+
+ if l.used == 0 && l.haveWritten {
+ _, err = l.out.Write([]byte{'\n'})
+ if err != nil {
+ return
+ }
+ }
+
+ if l.used+len(b) < l.lineLength {
+ l.used += copy(l.line[l.used:], b)
+ return
+ }
+
+ l.haveWritten = true
+ _, err = l.out.Write(l.line[0:l.used])
+ if err != nil {
+ return
+ }
+ excess := l.lineLength - l.used
+ l.used = 0
+
+ _, err = l.out.Write(b[0:excess])
+ if err != nil {
+ return
+ }
+
+ _, err = l.Write(b[excess:])
+ return
+}
+
+func (l *lineBreaker) Close() (err error) {
+ if l.used > 0 {
+ _, err = l.out.Write(l.line[0:l.used])
+ if err != nil {
+ return
+ }
+ }
+
+ return
+}
+
+// encoding keeps track of a running CRC24 over the data which has been written
+// to it and outputs a OpenPGP checksum when closed, followed by an armor
+// trailer.
+//
+// It's built into a stack of io.Writers:
+//
+// encoding -> base64 encoder -> lineBreaker -> out
+type encoding struct {
+ out io.Writer
+ breaker *lineBreaker
+ b64 io.WriteCloser
+ crc uint32
+ blockType []byte
+}
+
+func (e *encoding) Write(data []byte) (n int, err error) {
+ e.crc = crc24(e.crc, data)
+ return e.b64.Write(data)
+}
+
+func (e *encoding) Close() (err error) {
+ err = e.b64.Close()
+ if err != nil {
+ return
+ }
+ e.breaker.Close()
+
+ var checksumBytes [3]byte
+ checksumBytes[0] = byte(e.crc >> 16)
+ checksumBytes[1] = byte(e.crc >> 8)
+ checksumBytes[2] = byte(e.crc)
+
+ var b64ChecksumBytes [4]byte
+ base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:])
+
+ return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine)
+}
+
+// Encode returns a WriteCloser which will encode the data written to it in
+// OpenPGP armor.
+func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
+ bType := []byte(blockType)
+ err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
+ if err != nil {
+ return
+ }
+
+ for k, v := range headers {
+ err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline)
+ if err != nil {
+ return
+ }
+ }
+
+ _, err = out.Write(newline)
+ if err != nil {
+ return
+ }
+
+ e := &encoding{
+ out: out,
+ breaker: newLineBreaker(out, 64),
+ crc: crc24Init,
+ blockType: bType,
+ }
+ e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
+ return e, nil
+}
diff --git a/local_crypto_patch/contents/openpgp/canonical_text.go b/local_crypto_patch/contents/openpgp/canonical_text.go
new file mode 100644
index 0000000000..e601e389f1
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/canonical_text.go
@@ -0,0 +1,59 @@
+// 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 openpgp
+
+import "hash"
+
+// NewCanonicalTextHash reformats text written to it into the canonical
+// form and then applies the hash h. See RFC 4880, section 5.2.1.
+func NewCanonicalTextHash(h hash.Hash) hash.Hash {
+ return &canonicalTextHash{h, 0}
+}
+
+type canonicalTextHash struct {
+ h hash.Hash
+ s int
+}
+
+var newline = []byte{'\r', '\n'}
+
+func (cth *canonicalTextHash) Write(buf []byte) (int, error) {
+ start := 0
+
+ for i, c := range buf {
+ switch cth.s {
+ case 0:
+ if c == '\r' {
+ cth.s = 1
+ } else if c == '\n' {
+ cth.h.Write(buf[start:i])
+ cth.h.Write(newline)
+ start = i + 1
+ }
+ case 1:
+ cth.s = 0
+ }
+ }
+
+ cth.h.Write(buf[start:])
+ return len(buf), nil
+}
+
+func (cth *canonicalTextHash) Sum(in []byte) []byte {
+ return cth.h.Sum(in)
+}
+
+func (cth *canonicalTextHash) Reset() {
+ cth.h.Reset()
+ cth.s = 0
+}
+
+func (cth *canonicalTextHash) Size() int {
+ return cth.h.Size()
+}
+
+func (cth *canonicalTextHash) BlockSize() int {
+ return cth.h.BlockSize()
+}
diff --git a/local_crypto_patch/contents/openpgp/canonical_text_test.go b/local_crypto_patch/contents/openpgp/canonical_text_test.go
new file mode 100644
index 0000000000..8f3ba2a881
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/canonical_text_test.go
@@ -0,0 +1,52 @@
+// 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 openpgp
+
+import (
+ "bytes"
+ "testing"
+)
+
+type recordingHash struct {
+ buf *bytes.Buffer
+}
+
+func (r recordingHash) Write(b []byte) (n int, err error) {
+ return r.buf.Write(b)
+}
+
+func (r recordingHash) Sum(in []byte) []byte {
+ return append(in, r.buf.Bytes()...)
+}
+
+func (r recordingHash) Reset() {
+ panic("shouldn't be called")
+}
+
+func (r recordingHash) Size() int {
+ panic("shouldn't be called")
+}
+
+func (r recordingHash) BlockSize() int {
+ panic("shouldn't be called")
+}
+
+func testCanonicalText(t *testing.T, input, expected string) {
+ r := recordingHash{bytes.NewBuffer(nil)}
+ c := NewCanonicalTextHash(r)
+ c.Write([]byte(input))
+ result := c.Sum(nil)
+ if expected != string(result) {
+ t.Errorf("input: %x got: %x want: %x", input, result, expected)
+ }
+}
+
+func TestCanonicalText(t *testing.T) {
+ testCanonicalText(t, "foo\n", "foo\r\n")
+ testCanonicalText(t, "foo", "foo")
+ testCanonicalText(t, "foo\r\n", "foo\r\n")
+ testCanonicalText(t, "foo\r\nbar", "foo\r\nbar")
+ testCanonicalText(t, "foo\r\nbar\n\n", "foo\r\nbar\r\n\r\n")
+}
diff --git a/local_crypto_patch/contents/openpgp/clearsign/clearsign.go b/local_crypto_patch/contents/openpgp/clearsign/clearsign.go
new file mode 100644
index 0000000000..cea48efdcd
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/clearsign/clearsign.go
@@ -0,0 +1,424 @@
+// 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 clearsign generates and processes OpenPGP, clear-signed data. See
+// RFC 4880, section 7.
+//
+// Clearsigned messages are cryptographically signed, but the contents of the
+// message are kept in plaintext so that it can be read without special tools.
+//
+// Deprecated: this package is unmaintained except for security fixes. New
+// applications should consider a more focused, modern alternative to OpenPGP
+// for their specific task. If you are required to interoperate with OpenPGP
+// systems and need a maintained package, consider a community fork.
+// See https://golang.org/issue/44226.
+package clearsign
+
+import (
+ "bufio"
+ "bytes"
+ "crypto"
+ "fmt"
+ "hash"
+ "io"
+ "net/textproto"
+ "strconv"
+ "strings"
+
+ "golang.org/x/crypto/openpgp/armor"
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/packet"
+)
+
+// A Block represents a clearsigned message. A signature on a Block can
+// be checked by passing Bytes into openpgp.CheckDetachedSignature.
+type Block struct {
+ Headers textproto.MIMEHeader // Optional unverified Hash headers
+ Plaintext []byte // The original message text
+ Bytes []byte // The signed message
+ ArmoredSignature *armor.Block // The signature block
+}
+
+// start is the marker which denotes the beginning of a clearsigned message.
+var start = []byte("\n-----BEGIN PGP SIGNED MESSAGE-----")
+
+// dashEscape is prefixed to any lines that begin with a hyphen so that they
+// can't be confused with endText.
+var dashEscape = []byte("- ")
+
+// endText is a marker which denotes the end of the message and the start of
+// an armored signature.
+var endText = []byte("-----BEGIN PGP SIGNATURE-----")
+
+// end is a marker which denotes the end of the armored signature.
+var end = []byte("\n-----END PGP SIGNATURE-----")
+
+var crlf = []byte("\r\n")
+var lf = byte('\n')
+
+// getLine returns the first \r\n or \n delineated line from the given byte
+// array. The line does not include the \r\n or \n. The remainder of the byte
+// array (also not including the new line bytes) is also returned and this will
+// always be smaller than the original argument.
+func getLine(data []byte) (line, rest []byte) {
+ i := bytes.Index(data, []byte{'\n'})
+ var j int
+ if i < 0 {
+ i = len(data)
+ j = i
+ } else {
+ j = i + 1
+ if i > 0 && data[i-1] == '\r' {
+ i--
+ }
+ }
+ return data[0:i], data[j:]
+}
+
+// Decode finds the first clearsigned message in data and returns it, as well as
+// the suffix of data which remains after the message. Any prefix data is
+// discarded.
+//
+// If no message is found, or if the message is invalid, Decode returns nil and
+// the whole data slice. The only allowed header type is Hash, and it is not
+// verified against the signature hash.
+func Decode(data []byte) (b *Block, rest []byte) {
+ // start begins with a newline. However, at the very beginning of
+ // the byte array, we'll accept the start string without it.
+ rest = data
+ if bytes.HasPrefix(data, start[1:]) {
+ rest = rest[len(start)-1:]
+ } else if i := bytes.Index(data, start); i >= 0 {
+ rest = rest[i+len(start):]
+ } else {
+ return nil, data
+ }
+
+ // Consume the start line and check it does not have a suffix.
+ suffix, rest := getLine(rest)
+ if len(suffix) != 0 {
+ return nil, data
+ }
+
+ var line []byte
+ b = &Block{
+ Headers: make(textproto.MIMEHeader),
+ }
+
+ // Next come a series of header lines.
+ for {
+ // This loop terminates because getLine's second result is
+ // always smaller than its argument.
+ if len(rest) == 0 {
+ return nil, data
+ }
+ // An empty line marks the end of the headers.
+ if line, rest = getLine(rest); len(line) == 0 {
+ break
+ }
+
+ // Reject headers with control or Unicode characters.
+ if i := bytes.IndexFunc(line, func(r rune) bool {
+ return r < 0x20 || r > 0x7e
+ }); i != -1 {
+ return nil, data
+ }
+
+ i := bytes.Index(line, []byte{':'})
+ if i == -1 {
+ return nil, data
+ }
+
+ key, val := string(line[0:i]), string(line[i+1:])
+ key = strings.TrimSpace(key)
+ if key != "Hash" {
+ return nil, data
+ }
+ val = strings.TrimSpace(val)
+ b.Headers.Add(key, val)
+ }
+
+ firstLine := true
+ for {
+ start := rest
+
+ line, rest = getLine(rest)
+ if len(line) == 0 && len(rest) == 0 {
+ // No armored data was found, so this isn't a complete message.
+ return nil, data
+ }
+ if bytes.Equal(line, endText) {
+ // Back up to the start of the line because armor expects to see the
+ // header line.
+ rest = start
+ break
+ }
+
+ // The final CRLF isn't included in the hash so we don't write it until
+ // we've seen the next line.
+ if firstLine {
+ firstLine = false
+ } else {
+ b.Bytes = append(b.Bytes, crlf...)
+ }
+
+ if bytes.HasPrefix(line, dashEscape) {
+ line = line[2:]
+ }
+ line = bytes.TrimRight(line, " \t")
+ b.Bytes = append(b.Bytes, line...)
+
+ b.Plaintext = append(b.Plaintext, line...)
+ b.Plaintext = append(b.Plaintext, lf)
+ }
+
+ // We want to find the extent of the armored data (including any newlines at
+ // the end).
+ i := bytes.Index(rest, end)
+ if i == -1 {
+ return nil, data
+ }
+ i += len(end)
+ for i < len(rest) && (rest[i] == '\r' || rest[i] == '\n') {
+ i++
+ }
+ armored := rest[:i]
+ rest = rest[i:]
+
+ var err error
+ b.ArmoredSignature, err = armor.Decode(bytes.NewBuffer(armored))
+ if err != nil {
+ return nil, data
+ }
+
+ return b, rest
+}
+
+// A dashEscaper is an io.WriteCloser which processes the body of a clear-signed
+// message. The clear-signed message is written to buffered and a hash, suitable
+// for signing, is maintained in h.
+//
+// When closed, an armored signature is created and written to complete the
+// message.
+type dashEscaper struct {
+ buffered *bufio.Writer
+ hashers []hash.Hash // one per key in privateKeys
+ hashType crypto.Hash
+ toHash io.Writer // writes to all the hashes in hashers
+
+ atBeginningOfLine bool
+ isFirstLine bool
+
+ whitespace []byte
+ byteBuf []byte // a one byte buffer to save allocations
+
+ privateKeys []*packet.PrivateKey
+ config *packet.Config
+}
+
+func (d *dashEscaper) Write(data []byte) (n int, err error) {
+ for _, b := range data {
+ d.byteBuf[0] = b
+
+ if d.atBeginningOfLine {
+ // The final CRLF isn't included in the hash so we have to wait
+ // until this point (the start of the next line) before writing it.
+ if !d.isFirstLine {
+ d.toHash.Write(crlf)
+ }
+ d.isFirstLine = false
+ }
+
+ // Any whitespace at the end of the line has to be removed so we
+ // buffer it until we find out whether there's more on this line.
+ if b == ' ' || b == '\t' || b == '\r' {
+ d.whitespace = append(d.whitespace, b)
+ d.atBeginningOfLine = false
+ continue
+ }
+
+ if d.atBeginningOfLine {
+ // At the beginning of a line, hyphens have to be escaped.
+ if b == '-' {
+ // The signature isn't calculated over the dash-escaped text so
+ // the escape is only written to buffered.
+ if _, err = d.buffered.Write(dashEscape); err != nil {
+ return
+ }
+ d.toHash.Write(d.byteBuf)
+ d.atBeginningOfLine = false
+ } else if b == '\n' {
+ // Nothing to do because we delay writing CRLF to the hash.
+ } else {
+ d.toHash.Write(d.byteBuf)
+ d.atBeginningOfLine = false
+ }
+ if err = d.buffered.WriteByte(b); err != nil {
+ return
+ }
+ } else {
+ if b == '\n' {
+ // We got a raw \n. Drop any trailing whitespace and write a
+ // CRLF.
+ d.whitespace = d.whitespace[:0]
+ // We delay writing CRLF to the hash until the start of the
+ // next line.
+ if err = d.buffered.WriteByte(b); err != nil {
+ return
+ }
+ d.atBeginningOfLine = true
+ } else {
+ // Any buffered whitespace wasn't at the end of the line so
+ // we need to write it out.
+ if len(d.whitespace) > 0 {
+ d.toHash.Write(d.whitespace)
+ if _, err = d.buffered.Write(d.whitespace); err != nil {
+ return
+ }
+ d.whitespace = d.whitespace[:0]
+ }
+ d.toHash.Write(d.byteBuf)
+ if err = d.buffered.WriteByte(b); err != nil {
+ return
+ }
+ }
+ }
+ }
+
+ n = len(data)
+ return
+}
+
+func (d *dashEscaper) Close() (err error) {
+ if !d.atBeginningOfLine {
+ if err = d.buffered.WriteByte(lf); err != nil {
+ return
+ }
+ }
+
+ out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil)
+ if err != nil {
+ return
+ }
+
+ t := d.config.Now()
+ for i, k := range d.privateKeys {
+ sig := new(packet.Signature)
+ sig.SigType = packet.SigTypeText
+ sig.PubKeyAlgo = k.PubKeyAlgo
+ sig.Hash = d.hashType
+ sig.CreationTime = t
+ sig.IssuerKeyId = &k.KeyId
+
+ if err = sig.Sign(d.hashers[i], k, d.config); err != nil {
+ return
+ }
+ if err = sig.Serialize(out); err != nil {
+ return
+ }
+ }
+
+ if err = out.Close(); err != nil {
+ return
+ }
+ if err = d.buffered.Flush(); err != nil {
+ return
+ }
+ return
+}
+
+// Encode returns a WriteCloser which will clear-sign a message with privateKey
+// and write it to w. If config is nil, sensible defaults are used.
+func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
+ return EncodeMulti(w, []*packet.PrivateKey{privateKey}, config)
+}
+
+// EncodeMulti returns a WriteCloser which will clear-sign a message with all the
+// private keys indicated and write it to w. If config is nil, sensible defaults
+// are used.
+func EncodeMulti(w io.Writer, privateKeys []*packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) {
+ for _, k := range privateKeys {
+ if k.Encrypted {
+ return nil, errors.InvalidArgumentError(fmt.Sprintf("signing key %s is encrypted", k.KeyIdString()))
+ }
+ }
+
+ hashType := config.Hash()
+ name := nameOfHash(hashType)
+ if len(name) == 0 {
+ return nil, errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(hashType)))
+ }
+
+ if !hashType.Available() {
+ return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType)))
+ }
+ var hashers []hash.Hash
+ var ws []io.Writer
+ for range privateKeys {
+ h := hashType.New()
+ hashers = append(hashers, h)
+ ws = append(ws, h)
+ }
+ toHash := io.MultiWriter(ws...)
+
+ buffered := bufio.NewWriter(w)
+ // start has a \n at the beginning that we don't want here.
+ if _, err = buffered.Write(start[1:]); err != nil {
+ return
+ }
+ if err = buffered.WriteByte(lf); err != nil {
+ return
+ }
+ if _, err = buffered.WriteString("Hash: "); err != nil {
+ return
+ }
+ if _, err = buffered.WriteString(name); err != nil {
+ return
+ }
+ if err = buffered.WriteByte(lf); err != nil {
+ return
+ }
+ if err = buffered.WriteByte(lf); err != nil {
+ return
+ }
+
+ plaintext = &dashEscaper{
+ buffered: buffered,
+ hashers: hashers,
+ hashType: hashType,
+ toHash: toHash,
+
+ atBeginningOfLine: true,
+ isFirstLine: true,
+
+ byteBuf: make([]byte, 1),
+
+ privateKeys: privateKeys,
+ config: config,
+ }
+
+ return
+}
+
+// nameOfHash returns the OpenPGP name for the given hash, or the empty string
+// if the name isn't known. See RFC 4880, section 9.4.
+func nameOfHash(h crypto.Hash) string {
+ switch h {
+ case crypto.MD5:
+ return "MD5"
+ case crypto.SHA1:
+ return "SHA1"
+ case crypto.RIPEMD160:
+ return "RIPEMD160"
+ case crypto.SHA224:
+ return "SHA224"
+ case crypto.SHA256:
+ return "SHA256"
+ case crypto.SHA384:
+ return "SHA384"
+ case crypto.SHA512:
+ return "SHA512"
+ }
+ return ""
+}
diff --git a/local_crypto_patch/contents/openpgp/clearsign/clearsign_test.go b/local_crypto_patch/contents/openpgp/clearsign/clearsign_test.go
new file mode 100644
index 0000000000..821c35ff47
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/clearsign/clearsign_test.go
@@ -0,0 +1,386 @@
+// 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 clearsign
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "testing"
+
+ "golang.org/x/crypto/openpgp"
+ "golang.org/x/crypto/openpgp/packet"
+)
+
+func testParse(t *testing.T, input []byte, expected, expectedPlaintext string) {
+ b, rest := Decode(input)
+ if b == nil {
+ t.Fatal("failed to decode clearsign message")
+ }
+ if !bytes.Equal(rest, []byte("trailing")) {
+ t.Errorf("unexpected remaining bytes returned: %s", string(rest))
+ }
+ if b.ArmoredSignature.Type != "PGP SIGNATURE" {
+ t.Errorf("bad armor type, got:%s, want:PGP SIGNATURE", b.ArmoredSignature.Type)
+ }
+ if !bytes.Equal(b.Bytes, []byte(expected)) {
+ t.Errorf("bad body, got:%x want:%x", b.Bytes, expected)
+ }
+
+ if !bytes.Equal(b.Plaintext, []byte(expectedPlaintext)) {
+ t.Errorf("bad plaintext, got:%x want:%x", b.Plaintext, expectedPlaintext)
+ }
+
+ keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(signingKey))
+ if err != nil {
+ t.Errorf("failed to parse public key: %s", err)
+ }
+
+ if _, err := openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body); err != nil {
+ t.Errorf("failed to check signature: %s", err)
+ }
+}
+
+func TestParse(t *testing.T) {
+ testParse(t, clearsignInput, "Hello world\r\nline 2", "Hello world\nline 2\n")
+ testParse(t, clearsignInput2, "\r\n\r\n(This message has a couple of blank lines at the start and end.)\r\n\r\n", "\n\n(This message has a couple of blank lines at the start and end.)\n\n\n")
+}
+
+func TestParseWithNoNewlineAtEnd(t *testing.T) {
+ input := clearsignInput
+ input = input[:len(input)-len("trailing")-1]
+ b, rest := Decode(input)
+ if b == nil {
+ t.Fatal("failed to decode clearsign message")
+ }
+ if len(rest) > 0 {
+ t.Errorf("unexpected remaining bytes returned: %s", string(rest))
+ }
+}
+
+var signingTests = []struct {
+ in, signed, plaintext string
+}{
+ {"", "", ""},
+ {"a", "a", "a\n"},
+ {"a\n", "a", "a\n"},
+ {"-a\n", "-a", "-a\n"},
+ {"--a\nb", "--a\r\nb", "--a\nb\n"},
+ // leading whitespace
+ {" a\n", " a", " a\n"},
+ {" a\n", " a", " a\n"},
+ // trailing whitespace (should be stripped)
+ {"a \n", "a", "a\n"},
+ {"a ", "a", "a\n"},
+ // whitespace-only lines (should be stripped)
+ {" \n", "", "\n"},
+ {" ", "", "\n"},
+ {"a\n \n \nb\n", "a\r\n\r\n\r\nb", "a\n\n\nb\n"},
+}
+
+func TestSigning(t *testing.T) {
+ keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(signingKey))
+ if err != nil {
+ t.Errorf("failed to parse public key: %s", err)
+ }
+
+ for i, test := range signingTests {
+ var buf bytes.Buffer
+
+ plaintext, err := Encode(&buf, keyring[0].PrivateKey, nil)
+ if err != nil {
+ t.Errorf("#%d: error from Encode: %s", i, err)
+ continue
+ }
+ if _, err := plaintext.Write([]byte(test.in)); err != nil {
+ t.Errorf("#%d: error from Write: %s", i, err)
+ continue
+ }
+ if err := plaintext.Close(); err != nil {
+ t.Fatalf("#%d: error from Close: %s", i, err)
+ continue
+ }
+
+ b, _ := Decode(buf.Bytes())
+ if b == nil {
+ t.Errorf("#%d: failed to decode clearsign message", i)
+ continue
+ }
+ if !bytes.Equal(b.Bytes, []byte(test.signed)) {
+ t.Errorf("#%d: bad result, got:%x, want:%x", i, b.Bytes, test.signed)
+ continue
+ }
+ if !bytes.Equal(b.Plaintext, []byte(test.plaintext)) {
+ t.Errorf("#%d: bad result, got:%x, want:%x", i, b.Plaintext, test.plaintext)
+ continue
+ }
+
+ if _, err := openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body); err != nil {
+ t.Errorf("#%d: failed to check signature: %s", i, err)
+ }
+ }
+}
+
+func TestMultiSign(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping long test in -short mode")
+ }
+
+ var config packet.Config
+
+ for nKeys := 0; nKeys < 4; nKeys++ {
+ nextTest:
+ for nExtra := 0; nExtra < 4; nExtra++ {
+ var signKeys []*packet.PrivateKey
+ var verifyKeys openpgp.EntityList
+
+ desc := fmt.Sprintf("%d keys; %d of which will be used to verify", nKeys+nExtra, nKeys)
+ for i := 0; i < nKeys+nExtra; i++ {
+ e, err := openpgp.NewEntity("name", "comment", "email", &config)
+ if err != nil {
+ t.Errorf("cannot create key: %v", err)
+ continue nextTest
+ }
+ if i < nKeys {
+ verifyKeys = append(verifyKeys, e)
+ }
+ signKeys = append(signKeys, e.PrivateKey)
+ }
+
+ input := []byte("this is random text\r\n4 17")
+ var output bytes.Buffer
+ w, err := EncodeMulti(&output, signKeys, nil)
+ if err != nil {
+ t.Errorf("EncodeMulti (%s) failed: %v", desc, err)
+ }
+ if _, err := w.Write(input); err != nil {
+ t.Errorf("Write(%q) to signer (%s) failed: %v", string(input), desc, err)
+ }
+ if err := w.Close(); err != nil {
+ t.Errorf("Close() of signer (%s) failed: %v", desc, err)
+ }
+
+ block, _ := Decode(output.Bytes())
+ if string(block.Bytes) != string(input) {
+ t.Errorf("Inline data didn't match original; got %q want %q", string(block.Bytes), string(input))
+ }
+ _, err = openpgp.CheckDetachedSignature(verifyKeys, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body)
+ if nKeys == 0 {
+ if err == nil {
+ t.Errorf("verifying inline (%s) succeeded; want failure", desc)
+ }
+ } else {
+ if err != nil {
+ t.Errorf("verifying inline (%s) failed (%v); want success", desc, err)
+ }
+ }
+ }
+ }
+}
+
+func TestDecodeMissingCRC(t *testing.T) {
+ block, rest := Decode(clearsignInput3)
+ if block == nil {
+ t.Fatal("failed to decode PGP signature missing a CRC")
+ }
+ if len(rest) > 0 {
+ t.Fatalf("Decode should not have any remaining data left: %s", rest)
+ }
+ if _, err := packet.Read(block.ArmoredSignature.Body); err != nil {
+ t.Error(err)
+ }
+ if _, err := packet.Read(block.ArmoredSignature.Body); err != io.EOF {
+ t.Error(err)
+ }
+}
+
+const signatureBlock = `
+-----BEGIN PGP SIGNATURE-----
+Version: OpenPrivacy 0.99
+
+yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzS
+vBSFjNSiVHsuAA==
+=njUN
+-----END PGP SIGNATURE-----
+`
+
+var invalidInputs = []string{
+ `
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+(This message was truncated.)
+`,
+ `
+-----BEGIN PGP SIGNED MESSAGE-----garbage
+Hash: SHA256
+
+_o/
+` + signatureBlock,
+ `
+garbage-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+_o/
+` + signatureBlock,
+ `
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA` + "\x0b\x0b" + `256
+
+_o/
+` + signatureBlock,
+ `
+-----BEGIN PGP SIGNED MESSAGE-----
+NotHash: SHA256
+
+_o/
+` + signatureBlock,
+}
+
+func TestParseInvalid(t *testing.T) {
+ for i, input := range invalidInputs {
+ if b, rest := Decode([]byte(input)); b != nil {
+ t.Errorf("#%d: decoded a bad clearsigned message without any error", i)
+ } else if string(rest) != input {
+ t.Errorf("#%d: did not return all data with a bad message", i)
+ }
+ }
+}
+
+var clearsignInput = []byte(`
+;lasjlkfdsa
+
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Hello world
+line 2
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+iJwEAQECAAYFAk8kMuEACgkQO9o98PRieSpMsAQAhmY/vwmNpflrPgmfWsYhk5O8
+pjnBUzZwqTDoDeINjZEoPDSpQAHGhjFjgaDx/Gj4fAl0dM4D0wuUEBb6QOrwflog
+2A2k9kfSOMOtk0IH/H5VuFN1Mie9L/erYXjTQIptv9t9J7NoRBMU0QOOaFU0JaO9
+MyTpno24AjIAGb+mH1U=
+=hIJ6
+-----END PGP SIGNATURE-----
+trailing`)
+
+var clearsignInput2 = []byte(`
+asdlfkjasdlkfjsadf
+
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+
+
+(This message has a couple of blank lines at the start and end.)
+
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.11 (GNU/Linux)
+
+iJwEAQEIAAYFAlPpSREACgkQO9o98PRieSpZTAP+M8QUoCt/7Rf3YbXPcdzIL32v
+pt1I+cMNeopzfLy0u4ioEFi8s5VkwpL1AFmirvgViCwlf82inoRxzZRiW05JQ5LI
+ESEzeCoy2LIdRCQ2hcrG8pIUPzUO4TqO5D/dMbdHwNH4h5nNmGJUAEG6FpURlPm+
+qZg6BaTvOxepqOxnhVU=
+=e+C6
+-----END PGP SIGNATURE-----
+
+trailing`)
+
+var clearsignInput3 = []byte(`-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+Origin: vscode stable
+Label: vscode stable
+Suite: stable
+Codename: stable
+Date: Mon, 13 Jan 2020 08:41:45 UTC
+Architectures: amd64
+Components: main
+Description: Generated by aptly
+MD5Sum:
+ 66437152b3082616d8053e52c4bafafb 5821166 Contents-amd64
+ 8024662ed51109946a517754bbafdd33 286298 Contents-amd64.gz
+ 66437152b3082616d8053e52c4bafafb 5821166 main/Contents-amd64
+ 8024662ed51109946a517754bbafdd33 286298 main/Contents-amd64.gz
+ 3062a08b3eca94a65d6d17ba1dafcf3e 1088265 main/binary-amd64/Packages
+ b8ee22200fba8fa3be56c1ff946cdd24 159344 main/binary-amd64/Packages.bz2
+ f89c47c81ebd25caf287c8e6dda16c1a 169456 main/binary-amd64/Packages.gz
+ 4c9ca25b556f111a5536c78df885ad82 95 main/binary-amd64/Release
+SHA1:
+ 2b62d0e322746b7d094878278f49993ca4314bf7 5821166 Contents-amd64
+ aafe35cce12e03d8b1939e403ddf5c0958c6e9bd 286298 Contents-amd64.gz
+ 2b62d0e322746b7d094878278f49993ca4314bf7 5821166 main/Contents-amd64
+ aafe35cce12e03d8b1939e403ddf5c0958c6e9bd 286298 main/Contents-amd64.gz
+ 30316ac5d4ce3b472a96a797eeb0a2a82d43ed3e 1088265 main/binary-amd64/Packages
+ 6507e0b4da8194fd1048fcbb74c6e7433edaf3d6 159344 main/binary-amd64/Packages.bz2
+ ec9d39c39567c74001221e4900fb5d11ec11b833 169456 main/binary-amd64/Packages.gz
+ 58bf20987a91d35936f18efce75ea233d43dbf8b 95 main/binary-amd64/Release
+SHA256:
+ deff9ebfc44bf482e10a6ea10f608c6bb0fdc8373bf86b88cad9d99879ae3c39 5821166 Contents-amd64
+ f163bc65c7666ef58e0be3336e8c846ae2b7b388fbb2d7db0bcdc3fd1abae462 286298 Contents-amd64.gz
+ deff9ebfc44bf482e10a6ea10f608c6bb0fdc8373bf86b88cad9d99879ae3c39 5821166 main/Contents-amd64
+ f163bc65c7666ef58e0be3336e8c846ae2b7b388fbb2d7db0bcdc3fd1abae462 286298 main/Contents-amd64.gz
+ 0fba50799ef72d0c2b354d0bcbbc8c623f6dae5a7fd7c218a54ea44dd8a49d5e 1088265 main/binary-amd64/Packages
+ 69382470a88b67acde80fe45ab223016adebc445713ff0aa3272902581d21f13 159344 main/binary-amd64/Packages.bz2
+ 1724b8ace5bd8882943e9463d8525006f33ca704480da0186fd47937451dc216 169456 main/binary-amd64/Packages.gz
+ 0f509a0cb07e0ab433176fa47a21dccccc6b519f25f640cc58561104c11de6c2 95 main/binary-amd64/Release
+SHA512:
+ f69f09c6180ceb6625a84b5f7123ad27972983146979dcfd9c38b2990459b52b4975716f85374511486bb5ad5852ebb1ef8265176df7134fc15b17ada3ba596c 5821166 Contents-amd64
+ 46031bf89166188989368957d20cdcaac6eec72bab3f9839c9704bb08cbee3174ca6da11e290b0eab0e6b5754c1e7feb06d18ec9c5a0c955029cef53235e0a3a 286298 Contents-amd64.gz
+ f69f09c6180ceb6625a84b5f7123ad27972983146979dcfd9c38b2990459b52b4975716f85374511486bb5ad5852ebb1ef8265176df7134fc15b17ada3ba596c 5821166 main/Contents-amd64
+ 46031bf89166188989368957d20cdcaac6eec72bab3f9839c9704bb08cbee3174ca6da11e290b0eab0e6b5754c1e7feb06d18ec9c5a0c955029cef53235e0a3a 286298 main/Contents-amd64.gz
+ 3f78baf5adbaf0100996555b154807c794622fd0b5879b568ae0b6560e988fbfabed8d97db5a703d1a58514b9690fc6b60f9ad2eeece473d86ab257becd0ae41 1088265 main/binary-amd64/Packages
+ 18f26df90beff29192662ca40525367c3c04f4581d59d2e9ab1cd0700a145b6a292a1609ca33ebe1c211f13718a8eee751f41fd8189cf93d52aa3e0851542dfc 159344 main/binary-amd64/Packages.bz2
+ 6a6d917229e0cf06c493e174a87d76e815717676f2c70bcbd3bc689a80bd3c5489ea97db83b8f74cba8e70f374f9d9974f22b1ed2687a4ba1dacd22fdef7e14d 169456 main/binary-amd64/Packages.gz
+ e1a4378ad266c13c2edf8a0e590fa4d11973ab99ce79f15af005cb838f1600f66f3dc6da8976fa8b474da9073c118039c27623ab3360c6df115071497fe4f50c 95 main/binary-amd64/Release
+
+-----BEGIN PGP SIGNATURE-----
+Version: BSN Pgp v1.0.0.0
+
+iQEcBAEBCAAGBQJeHC1bAAoJEOs+lK2+EinPAg8H/1rrhcgfm1HYL+Vmr9Ns6ton
+LWQ8r13ADN66UTRa3XsO9V+q1fYowTqpXq6EZt2Gmlby/cpDf7mFPM5IteOXWLl7
+QcWxPKHcdPIUi+h5F7BkFW65imP9GyX+V5Pxx5X544op7hYKaI0gAQ1oYtWDb3HE
+4D27fju6icbj8w6E8TePcrDn82UvWAcaI5WSLboyhXCt2DxS3PNGFlyaP58zKJ8F
+9cbBzksuMgMaTPAAMrU0zrFGfGeQz0Yo6nV/gRGiQaL9pSeIJWSKLNCMG/nIGmv2
+xHVNFqTEetREY6UcQmuhwOn4HezyigH6XCBVp/Uez1izXiNdwBOet34SSvnkuJ4=
+-----END PGP SIGNATURE-----`)
+
+var signingKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+lQHYBE2rFNoBBADFwqWQIW/DSqcB4yCQqnAFTJ27qS5AnB46ccAdw3u4Greeu3Bp
+idpoHdjULy7zSKlwR1EA873dO/k/e11Ml3dlAFUinWeejWaK2ugFP6JjiieSsrKn
+vWNicdCS4HTWn0X4sjl0ZiAygw6GNhqEQ3cpLeL0g8E9hnYzJKQ0LWJa0QARAQAB
+AAP/TB81EIo2VYNmTq0pK1ZXwUpxCrvAAIG3hwKjEzHcbQznsjNvPUihZ+NZQ6+X
+0HCfPAdPkGDCLCb6NavcSW+iNnLTrdDnSI6+3BbIONqWWdRDYJhqZCkqmG6zqSfL
+IdkJgCw94taUg5BWP/AAeQrhzjChvpMQTVKQL5mnuZbUCeMCAN5qrYMP2S9iKdnk
+VANIFj7656ARKt/nf4CBzxcpHTyB8+d2CtPDKCmlJP6vL8t58Jmih+kHJMvC0dzn
+gr5f5+sCAOOe5gt9e0am7AvQWhdbHVfJU0TQJx+m2OiCJAqGTB1nvtBLHdJnfdC9
+TnXXQ6ZXibqLyBies/xeY2sCKL5qtTMCAKnX9+9d/5yQxRyrQUHt1NYhaXZnJbHx
+q4ytu0eWz+5i68IYUSK69jJ1NWPM0T6SkqpB3KCAIv68VFm9PxqG1KmhSrQIVGVz
+dCBLZXmIuAQTAQIAIgUCTasU2gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA
+CgkQO9o98PRieSoLhgQAkLEZex02Qt7vGhZzMwuN0R22w3VwyYyjBx+fM3JFETy1
+ut4xcLJoJfIaF5ZS38UplgakHG0FQ+b49i8dMij0aZmDqGxrew1m4kBfjXw9B/v+
+eIqpODryb6cOSwyQFH0lQkXC040pjq9YqDsO5w0WYNXYKDnzRV0p4H1pweo2VDid
+AdgETasU2gEEAN46UPeWRqKHvA99arOxee38fBt2CI08iiWyI8T3J6ivtFGixSqV
+bRcPxYO/qLpVe5l84Nb3X71GfVXlc9hyv7CD6tcowL59hg1E/DC5ydI8K8iEpUmK
+/UnHdIY5h8/kqgGxkY/T/hgp5fRQgW1ZoZxLajVlMRZ8W4tFtT0DeA+JABEBAAEA
+A/0bE1jaaZKj6ndqcw86jd+QtD1SF+Cf21CWRNeLKnUds4FRRvclzTyUMuWPkUeX
+TaNNsUOFqBsf6QQ2oHUBBK4VCHffHCW4ZEX2cd6umz7mpHW6XzN4DECEzOVksXtc
+lUC1j4UB91DC/RNQqwX1IV2QLSwssVotPMPqhOi0ZLNY7wIA3n7DWKInxYZZ4K+6
+rQ+POsz6brEoRHwr8x6XlHenq1Oki855pSa1yXIARoTrSJkBtn5oI+f8AzrnN0BN
+oyeQAwIA/7E++3HDi5aweWrViiul9cd3rcsS0dEnksPhvS0ozCJiHsq/6GFmy7J8
+QSHZPteedBnZyNp5jR+H7cIfVN3KgwH/Skq4PsuPhDq5TKK6i8Pc1WW8MA6DXTdU
+nLkX7RGmMwjC0DBf7KWAlPjFaONAX3a8ndnz//fy1q7u2l9AZwrj1qa1iJ8EGAEC
+AAkFAk2rFNoCGwwACgkQO9o98PRieSo2/QP/WTzr4ioINVsvN1akKuekmEMI3LAp
+BfHwatufxxP1U+3Si/6YIk7kuPB9Hs+pRqCXzbvPRrI8NHZBmc8qIGthishdCYad
+AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL
+VrM0m72/jnpKo04=
+=zNCn
+-----END PGP PRIVATE KEY BLOCK-----
+`
diff --git a/local_crypto_patch/contents/openpgp/elgamal/elgamal.go b/local_crypto_patch/contents/openpgp/elgamal/elgamal.go
new file mode 100644
index 0000000000..f922bdbcaa
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/elgamal/elgamal.go
@@ -0,0 +1,130 @@
+// 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 elgamal implements ElGamal encryption, suitable for OpenPGP,
+// as specified in "A Public-Key Cryptosystem and a Signature Scheme Based on
+// Discrete Logarithms," IEEE Transactions on Information Theory, v. IT-31,
+// n. 4, 1985, pp. 469-472.
+//
+// This form of ElGamal embeds PKCS#1 v1.5 padding, which may make it
+// unsuitable for other protocols. RSA should be used in preference in any
+// case.
+//
+// Deprecated: this package was only provided to support ElGamal encryption in
+// OpenPGP. The golang.org/x/crypto/openpgp package is now deprecated (see
+// https://golang.org/issue/44226), and ElGamal in the OpenPGP ecosystem has
+// compatibility and security issues (see https://eprint.iacr.org/2021/923).
+// Moreover, this package doesn't protect against side-channel attacks.
+package elgamal
+
+import (
+ "crypto/rand"
+ "crypto/subtle"
+ "errors"
+ "io"
+ "math/big"
+)
+
+// PublicKey represents an ElGamal public key.
+type PublicKey struct {
+ G, P, Y *big.Int
+}
+
+// PrivateKey represents an ElGamal private key.
+type PrivateKey struct {
+ PublicKey
+ X *big.Int
+}
+
+// Encrypt encrypts the given message to the given public key. The result is a
+// pair of integers. Errors can result from reading random, or because msg is
+// too large to be encrypted to the public key.
+func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) {
+ pLen := (pub.P.BitLen() + 7) / 8
+ if len(msg) > pLen-11 {
+ err = errors.New("elgamal: message too long")
+ return
+ }
+
+ // EM = 0x02 || PS || 0x00 || M
+ em := make([]byte, pLen-1)
+ em[0] = 2
+ ps, mm := em[1:len(em)-len(msg)-1], em[len(em)-len(msg):]
+ err = nonZeroRandomBytes(ps, random)
+ if err != nil {
+ return
+ }
+ em[len(em)-len(msg)-1] = 0
+ copy(mm, msg)
+
+ m := new(big.Int).SetBytes(em)
+
+ k, err := rand.Int(random, pub.P)
+ if err != nil {
+ return
+ }
+
+ c1 = new(big.Int).Exp(pub.G, k, pub.P)
+ s := new(big.Int).Exp(pub.Y, k, pub.P)
+ c2 = s.Mul(s, m)
+ c2.Mod(c2, pub.P)
+
+ return
+}
+
+// Decrypt takes two integers, resulting from an ElGamal encryption, and
+// returns the plaintext of the message. An error can result only if the
+// ciphertext is invalid. Users should keep in mind that this is a padding
+// oracle and thus, if exposed to an adaptive chosen ciphertext attack, can
+// be used to break the cryptosystem. See “Chosen Ciphertext Attacks
+// Against Protocols Based on the RSA Encryption Standard PKCS #1”, Daniel
+// Bleichenbacher, Advances in Cryptology (Crypto '98),
+func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) {
+ s := new(big.Int).Exp(c1, priv.X, priv.P)
+ if s.ModInverse(s, priv.P) == nil {
+ return nil, errors.New("elgamal: invalid private key")
+ }
+ s.Mul(s, c2)
+ s.Mod(s, priv.P)
+ em := s.Bytes()
+
+ firstByteIsTwo := subtle.ConstantTimeByteEq(em[0], 2)
+
+ // The remainder of the plaintext must be a string of non-zero random
+ // octets, followed by a 0, followed by the message.
+ // lookingForIndex: 1 iff we are still looking for the zero.
+ // index: the offset of the first zero byte.
+ var lookingForIndex, index int
+ lookingForIndex = 1
+
+ for i := 1; i < len(em); i++ {
+ equals0 := subtle.ConstantTimeByteEq(em[i], 0)
+ index = subtle.ConstantTimeSelect(lookingForIndex&equals0, i, index)
+ lookingForIndex = subtle.ConstantTimeSelect(equals0, 0, lookingForIndex)
+ }
+
+ if firstByteIsTwo != 1 || lookingForIndex != 0 || index < 9 {
+ return nil, errors.New("elgamal: decryption error")
+ }
+ return em[index+1:], nil
+}
+
+// nonZeroRandomBytes fills the given slice with non-zero random octets.
+func nonZeroRandomBytes(s []byte, rand io.Reader) (err error) {
+ _, err = io.ReadFull(rand, s)
+ if err != nil {
+ return
+ }
+
+ for i := 0; i < len(s); i++ {
+ for s[i] == 0 {
+ _, err = io.ReadFull(rand, s[i:i+1])
+ if err != nil {
+ return
+ }
+ }
+ }
+
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/elgamal/elgamal_test.go b/local_crypto_patch/contents/openpgp/elgamal/elgamal_test.go
new file mode 100644
index 0000000000..9f0a8547c3
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/elgamal/elgamal_test.go
@@ -0,0 +1,64 @@
+// 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 elgamal
+
+import (
+ "bytes"
+ "crypto/rand"
+ "math/big"
+ "testing"
+)
+
+// This is the 1024-bit MODP group from RFC 5114, section 2.1:
+const primeHex = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C69A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C013ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD7098488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708DF1FB2BC2E4A4371"
+
+const generatorHex = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507FD6406CFF14266D31266FEA1E5C41564B777E690F5504F213160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28AD662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24855E6EEB22B3B2E5"
+
+func fromHex(hex string) *big.Int {
+ n, ok := new(big.Int).SetString(hex, 16)
+ if !ok {
+ panic("failed to parse hex number")
+ }
+ return n
+}
+
+func TestEncryptDecrypt(t *testing.T) {
+ priv := &PrivateKey{
+ PublicKey: PublicKey{
+ G: fromHex(generatorHex),
+ P: fromHex(primeHex),
+ },
+ X: fromHex("42"),
+ }
+ priv.Y = new(big.Int).Exp(priv.G, priv.X, priv.P)
+
+ message := []byte("hello world")
+ c1, c2, err := Encrypt(rand.Reader, &priv.PublicKey, message)
+ if err != nil {
+ t.Errorf("error encrypting: %s", err)
+ }
+ message2, err := Decrypt(priv, c1, c2)
+ if err != nil {
+ t.Errorf("error decrypting: %s", err)
+ }
+ if !bytes.Equal(message2, message) {
+ t.Errorf("decryption failed, got: %x, want: %x", message2, message)
+ }
+}
+
+func TestDecryptBadKey(t *testing.T) {
+ priv := &PrivateKey{
+ PublicKey: PublicKey{
+ G: fromHex(generatorHex),
+ P: fromHex("2"),
+ },
+ X: fromHex("42"),
+ }
+ priv.Y = new(big.Int).Exp(priv.G, priv.X, priv.P)
+ c1, c2 := fromHex("8"), fromHex("8")
+ if _, err := Decrypt(priv, c1, c2); err == nil {
+ t.Errorf("unexpected success decrypting")
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/errors/errors.go b/local_crypto_patch/contents/openpgp/errors/errors.go
new file mode 100644
index 0000000000..a328749471
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/errors/errors.go
@@ -0,0 +1,78 @@
+// 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 errors contains common error types for the OpenPGP packages.
+//
+// Deprecated: this package is unmaintained except for security fixes. New
+// applications should consider a more focused, modern alternative to OpenPGP
+// for their specific task. If you are required to interoperate with OpenPGP
+// systems and need a maintained package, consider a community fork.
+// See https://golang.org/issue/44226.
+package errors
+
+import (
+ "strconv"
+)
+
+// A StructuralError is returned when OpenPGP data is found to be syntactically
+// invalid.
+type StructuralError string
+
+func (s StructuralError) Error() string {
+ return "openpgp: invalid data: " + string(s)
+}
+
+// UnsupportedError indicates that, although the OpenPGP data is valid, it
+// makes use of currently unimplemented features.
+type UnsupportedError string
+
+func (s UnsupportedError) Error() string {
+ return "openpgp: unsupported feature: " + string(s)
+}
+
+// InvalidArgumentError indicates that the caller is in error and passed an
+// incorrect value.
+type InvalidArgumentError string
+
+func (i InvalidArgumentError) Error() string {
+ return "openpgp: invalid argument: " + string(i)
+}
+
+// SignatureError indicates that a syntactically valid signature failed to
+// validate.
+type SignatureError string
+
+func (b SignatureError) Error() string {
+ return "openpgp: invalid signature: " + string(b)
+}
+
+type keyIncorrectError int
+
+func (ki keyIncorrectError) Error() string {
+ return "openpgp: incorrect key"
+}
+
+var ErrKeyIncorrect error = keyIncorrectError(0)
+
+type unknownIssuerError int
+
+func (unknownIssuerError) Error() string {
+ return "openpgp: signature made by unknown entity"
+}
+
+var ErrUnknownIssuer error = unknownIssuerError(0)
+
+type keyRevokedError int
+
+func (keyRevokedError) Error() string {
+ return "openpgp: signature made by revoked key"
+}
+
+var ErrKeyRevoked error = keyRevokedError(0)
+
+type UnknownPacketTypeError uint8
+
+func (upte UnknownPacketTypeError) Error() string {
+ return "openpgp: unknown packet type: " + strconv.Itoa(int(upte))
+}
diff --git a/local_crypto_patch/contents/openpgp/keys.go b/local_crypto_patch/contents/openpgp/keys.go
new file mode 100644
index 0000000000..d62f787e9d
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/keys.go
@@ -0,0 +1,693 @@
+// 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 openpgp
+
+import (
+ "crypto/rsa"
+ "io"
+ "time"
+
+ "golang.org/x/crypto/openpgp/armor"
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/packet"
+)
+
+// PublicKeyType is the armor type for a PGP public key.
+var PublicKeyType = "PGP PUBLIC KEY BLOCK"
+
+// PrivateKeyType is the armor type for a PGP private key.
+var PrivateKeyType = "PGP PRIVATE KEY BLOCK"
+
+// An Entity represents the components of an OpenPGP key: a primary public key
+// (which must be a signing key), one or more identities claimed by that key,
+// and zero or more subkeys, which may be encryption keys.
+type Entity struct {
+ PrimaryKey *packet.PublicKey
+ PrivateKey *packet.PrivateKey
+ Identities map[string]*Identity // indexed by Identity.Name
+ Revocations []*packet.Signature
+ Subkeys []Subkey
+}
+
+// An Identity represents an identity claimed by an Entity and zero or more
+// assertions by other entities about that claim.
+type Identity struct {
+ Name string // by convention, has the form "Full Name (comment) "
+ UserId *packet.UserId
+ SelfSignature *packet.Signature
+ Signatures []*packet.Signature
+}
+
+// A Subkey is an additional public key in an Entity. Subkeys can be used for
+// encryption.
+type Subkey struct {
+ PublicKey *packet.PublicKey
+ PrivateKey *packet.PrivateKey
+ Sig *packet.Signature
+}
+
+// A Key identifies a specific public key in an Entity. This is either the
+// Entity's primary key or a subkey.
+type Key struct {
+ Entity *Entity
+ PublicKey *packet.PublicKey
+ PrivateKey *packet.PrivateKey
+ SelfSignature *packet.Signature
+}
+
+// A KeyRing provides access to public and private keys.
+type KeyRing interface {
+ // KeysById returns the set of keys that have the given key id.
+ KeysById(id uint64) []Key
+ // KeysByIdUsage returns the set of keys with the given id
+ // that also meet the key usage given by requiredUsage.
+ // The requiredUsage is expressed as the bitwise-OR of
+ // packet.KeyFlag* values.
+ KeysByIdUsage(id uint64, requiredUsage byte) []Key
+ // DecryptionKeys returns all private keys that are valid for
+ // decryption.
+ DecryptionKeys() []Key
+}
+
+// primaryIdentity returns the Identity marked as primary or the first identity
+// if none are so marked.
+func (e *Entity) primaryIdentity() *Identity {
+ var firstIdentity *Identity
+ for _, ident := range e.Identities {
+ if firstIdentity == nil {
+ firstIdentity = ident
+ }
+ if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
+ return ident
+ }
+ }
+ return firstIdentity
+}
+
+// encryptionKey returns the best candidate Key for encrypting a message to the
+// given Entity.
+func (e *Entity) encryptionKey(now time.Time) (Key, bool) {
+ candidateSubkey := -1
+
+ // Iterate the keys to find the newest key
+ var maxTime time.Time
+ for i, subkey := range e.Subkeys {
+ if subkey.Sig.FlagsValid &&
+ subkey.Sig.FlagEncryptCommunications &&
+ subkey.PublicKey.PubKeyAlgo.CanEncrypt() &&
+ !subkey.Sig.KeyExpired(now) &&
+ (maxTime.IsZero() || subkey.Sig.CreationTime.After(maxTime)) {
+ candidateSubkey = i
+ maxTime = subkey.Sig.CreationTime
+ }
+ }
+
+ if candidateSubkey != -1 {
+ subkey := e.Subkeys[candidateSubkey]
+ return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true
+ }
+
+ // If we don't have any candidate subkeys for encryption and
+ // the primary key doesn't have any usage metadata then we
+ // assume that the primary key is ok. Or, if the primary key is
+ // marked as ok to encrypt to, then we can obviously use it.
+ i := e.primaryIdentity()
+ if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptCommunications &&
+ e.PrimaryKey.PubKeyAlgo.CanEncrypt() &&
+ !i.SelfSignature.KeyExpired(now) {
+ return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true
+ }
+
+ // This Entity appears to be signing only.
+ return Key{}, false
+}
+
+// signingKey return the best candidate Key for signing a message with this
+// Entity.
+func (e *Entity) signingKey(now time.Time) (Key, bool) {
+ candidateSubkey := -1
+
+ for i, subkey := range e.Subkeys {
+ if subkey.Sig.FlagsValid &&
+ subkey.Sig.FlagSign &&
+ subkey.PublicKey.PubKeyAlgo.CanSign() &&
+ !subkey.Sig.KeyExpired(now) {
+ candidateSubkey = i
+ break
+ }
+ }
+
+ if candidateSubkey != -1 {
+ subkey := e.Subkeys[candidateSubkey]
+ return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true
+ }
+
+ // If we have no candidate subkey then we assume that it's ok to sign
+ // with the primary key.
+ i := e.primaryIdentity()
+ if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagSign &&
+ !i.SelfSignature.KeyExpired(now) {
+ return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true
+ }
+
+ return Key{}, false
+}
+
+// An EntityList contains one or more Entities.
+type EntityList []*Entity
+
+// KeysById returns the set of keys that have the given key id.
+func (el EntityList) KeysById(id uint64) (keys []Key) {
+ for _, e := range el {
+ if e.PrimaryKey.KeyId == id {
+ var selfSig *packet.Signature
+ for _, ident := range e.Identities {
+ if selfSig == nil {
+ selfSig = ident.SelfSignature
+ } else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
+ selfSig = ident.SelfSignature
+ break
+ }
+ }
+ keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig})
+ }
+
+ for _, subKey := range e.Subkeys {
+ if subKey.PublicKey.KeyId == id {
+ keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
+ }
+ }
+ }
+ return
+}
+
+// KeysByIdUsage returns the set of keys with the given id that also meet
+// the key usage given by requiredUsage. The requiredUsage is expressed as
+// the bitwise-OR of packet.KeyFlag* values.
+func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) {
+ for _, key := range el.KeysById(id) {
+ if len(key.Entity.Revocations) > 0 {
+ continue
+ }
+
+ if key.SelfSignature.RevocationReason != nil {
+ continue
+ }
+
+ if key.SelfSignature.FlagsValid && requiredUsage != 0 {
+ var usage byte
+ if key.SelfSignature.FlagCertify {
+ usage |= packet.KeyFlagCertify
+ }
+ if key.SelfSignature.FlagSign {
+ usage |= packet.KeyFlagSign
+ }
+ if key.SelfSignature.FlagEncryptCommunications {
+ usage |= packet.KeyFlagEncryptCommunications
+ }
+ if key.SelfSignature.FlagEncryptStorage {
+ usage |= packet.KeyFlagEncryptStorage
+ }
+ if usage&requiredUsage != requiredUsage {
+ continue
+ }
+ }
+
+ keys = append(keys, key)
+ }
+ return
+}
+
+// DecryptionKeys returns all private keys that are valid for decryption.
+func (el EntityList) DecryptionKeys() (keys []Key) {
+ for _, e := range el {
+ for _, subKey := range e.Subkeys {
+ if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) {
+ keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
+ }
+ }
+ }
+ return
+}
+
+// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file.
+func ReadArmoredKeyRing(r io.Reader) (EntityList, error) {
+ block, err := armor.Decode(r)
+ if err == io.EOF {
+ return nil, errors.InvalidArgumentError("no armored data found")
+ }
+ if err != nil {
+ return nil, err
+ }
+ if block.Type != PublicKeyType && block.Type != PrivateKeyType {
+ return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type)
+ }
+
+ return ReadKeyRing(block.Body)
+}
+
+// ReadKeyRing reads one or more public/private keys. Unsupported keys are
+// ignored as long as at least a single valid key is found.
+func ReadKeyRing(r io.Reader) (el EntityList, err error) {
+ packets := packet.NewReader(r)
+ var lastUnsupportedError error
+
+ for {
+ var e *Entity
+ e, err = ReadEntity(packets)
+ if err != nil {
+ // TODO: warn about skipped unsupported/unreadable keys
+ if _, ok := err.(errors.UnsupportedError); ok {
+ lastUnsupportedError = err
+ err = readToNextPublicKey(packets)
+ } else if _, ok := err.(errors.StructuralError); ok {
+ // Skip unreadable, badly-formatted keys
+ lastUnsupportedError = err
+ err = readToNextPublicKey(packets)
+ }
+ if err == io.EOF {
+ err = nil
+ break
+ }
+ if err != nil {
+ el = nil
+ break
+ }
+ } else {
+ el = append(el, e)
+ }
+ }
+
+ if len(el) == 0 && err == nil {
+ err = lastUnsupportedError
+ }
+ return
+}
+
+// readToNextPublicKey reads packets until the start of the entity and leaves
+// the first packet of the new entity in the Reader.
+func readToNextPublicKey(packets *packet.Reader) (err error) {
+ var p packet.Packet
+ for {
+ p, err = packets.Next()
+ if err == io.EOF {
+ return
+ } else if err != nil {
+ if _, ok := err.(errors.UnsupportedError); ok {
+ err = nil
+ continue
+ }
+ return
+ }
+
+ if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey {
+ packets.Unread(p)
+ return
+ }
+ }
+}
+
+// ReadEntity reads an entity (public key, identities, subkeys etc) from the
+// given Reader.
+func ReadEntity(packets *packet.Reader) (*Entity, error) {
+ e := new(Entity)
+ e.Identities = make(map[string]*Identity)
+
+ p, err := packets.Next()
+ if err != nil {
+ return nil, err
+ }
+
+ var ok bool
+ if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok {
+ if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok {
+ packets.Unread(p)
+ return nil, errors.StructuralError("first packet was not a public/private key")
+ }
+ e.PrimaryKey = &e.PrivateKey.PublicKey
+ }
+
+ if !e.PrimaryKey.PubKeyAlgo.CanSign() {
+ return nil, errors.StructuralError("primary key cannot be used for signatures")
+ }
+
+ var revocations []*packet.Signature
+EachPacket:
+ for {
+ p, err := packets.Next()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return nil, err
+ }
+
+ switch pkt := p.(type) {
+ case *packet.UserId:
+ if err := addUserID(e, packets, pkt); err != nil {
+ return nil, err
+ }
+ case *packet.Signature:
+ if pkt.SigType == packet.SigTypeKeyRevocation {
+ revocations = append(revocations, pkt)
+ } else if pkt.SigType == packet.SigTypeDirectSignature {
+ // TODO: RFC4880 5.2.1 permits signatures
+ // directly on keys (eg. to bind additional
+ // revocation keys).
+ }
+ // Else, ignoring the signature as it does not follow anything
+ // we would know to attach it to.
+ case *packet.PrivateKey:
+ if pkt.IsSubkey == false {
+ packets.Unread(p)
+ break EachPacket
+ }
+ err = addSubkey(e, packets, &pkt.PublicKey, pkt)
+ if err != nil {
+ return nil, err
+ }
+ case *packet.PublicKey:
+ if pkt.IsSubkey == false {
+ packets.Unread(p)
+ break EachPacket
+ }
+ err = addSubkey(e, packets, pkt, nil)
+ if err != nil {
+ return nil, err
+ }
+ default:
+ // we ignore unknown packets
+ }
+ }
+
+ if len(e.Identities) == 0 {
+ return nil, errors.StructuralError("entity without any identities")
+ }
+
+ for _, revocation := range revocations {
+ err = e.PrimaryKey.VerifyRevocationSignature(revocation)
+ if err == nil {
+ e.Revocations = append(e.Revocations, revocation)
+ } else {
+ // TODO: RFC 4880 5.2.3.15 defines revocation keys.
+ return nil, errors.StructuralError("revocation signature signed by alternate key")
+ }
+ }
+
+ return e, nil
+}
+
+func addUserID(e *Entity, packets *packet.Reader, pkt *packet.UserId) error {
+ // Make a new Identity object, that we might wind up throwing away.
+ // We'll only add it if we get a valid self-signature over this
+ // userID.
+ identity := new(Identity)
+ identity.Name = pkt.Id
+ identity.UserId = pkt
+
+ for {
+ p, err := packets.Next()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return err
+ }
+
+ sig, ok := p.(*packet.Signature)
+ if !ok {
+ packets.Unread(p)
+ break
+ }
+
+ if (sig.SigType == packet.SigTypePositiveCert || sig.SigType == packet.SigTypeGenericCert) && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId {
+ if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, e.PrimaryKey, sig); err != nil {
+ return errors.StructuralError("user ID self-signature invalid: " + err.Error())
+ }
+ identity.SelfSignature = sig
+ e.Identities[pkt.Id] = identity
+ } else {
+ identity.Signatures = append(identity.Signatures, sig)
+ }
+ }
+
+ return nil
+}
+
+func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) error {
+ var subKey Subkey
+ subKey.PublicKey = pub
+ subKey.PrivateKey = priv
+
+ for {
+ p, err := packets.Next()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ return errors.StructuralError("subkey signature invalid: " + err.Error())
+ }
+
+ sig, ok := p.(*packet.Signature)
+ if !ok {
+ packets.Unread(p)
+ break
+ }
+
+ if sig.SigType != packet.SigTypeSubkeyBinding && sig.SigType != packet.SigTypeSubkeyRevocation {
+ return errors.StructuralError("subkey signature with wrong type")
+ }
+
+ if err := e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, sig); err != nil {
+ return errors.StructuralError("subkey signature invalid: " + err.Error())
+ }
+
+ switch sig.SigType {
+ case packet.SigTypeSubkeyRevocation:
+ subKey.Sig = sig
+ case packet.SigTypeSubkeyBinding:
+
+ if shouldReplaceSubkeySig(subKey.Sig, sig) {
+ subKey.Sig = sig
+ }
+ }
+ }
+
+ if subKey.Sig == nil {
+ return errors.StructuralError("subkey packet not followed by signature")
+ }
+
+ e.Subkeys = append(e.Subkeys, subKey)
+
+ return nil
+}
+
+func shouldReplaceSubkeySig(existingSig, potentialNewSig *packet.Signature) bool {
+ if potentialNewSig == nil {
+ return false
+ }
+
+ if existingSig == nil {
+ return true
+ }
+
+ if existingSig.SigType == packet.SigTypeSubkeyRevocation {
+ return false // never override a revocation signature
+ }
+
+ return potentialNewSig.CreationTime.After(existingSig.CreationTime)
+}
+
+const defaultRSAKeyBits = 2048
+
+// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a
+// single identity composed of the given full name, comment and email, any of
+// which may be empty but must not contain any of "()<>\x00".
+// If config is nil, sensible defaults will be used.
+func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) {
+ creationTime := config.Now()
+
+ bits := defaultRSAKeyBits
+ if config != nil && config.RSABits != 0 {
+ bits = config.RSABits
+ }
+
+ uid := packet.NewUserId(name, comment, email)
+ if uid == nil {
+ return nil, errors.InvalidArgumentError("user id field contained invalid characters")
+ }
+ signingPriv, err := rsa.GenerateKey(config.Random(), bits)
+ if err != nil {
+ return nil, err
+ }
+ encryptingPriv, err := rsa.GenerateKey(config.Random(), bits)
+ if err != nil {
+ return nil, err
+ }
+
+ e := &Entity{
+ PrimaryKey: packet.NewRSAPublicKey(creationTime, &signingPriv.PublicKey),
+ PrivateKey: packet.NewRSAPrivateKey(creationTime, signingPriv),
+ Identities: make(map[string]*Identity),
+ }
+ isPrimaryId := true
+ e.Identities[uid.Id] = &Identity{
+ Name: uid.Id,
+ UserId: uid,
+ SelfSignature: &packet.Signature{
+ CreationTime: creationTime,
+ SigType: packet.SigTypePositiveCert,
+ PubKeyAlgo: packet.PubKeyAlgoRSA,
+ Hash: config.Hash(),
+ IsPrimaryId: &isPrimaryId,
+ FlagsValid: true,
+ FlagSign: true,
+ FlagCertify: true,
+ IssuerKeyId: &e.PrimaryKey.KeyId,
+ },
+ }
+ err = e.Identities[uid.Id].SelfSignature.SignUserId(uid.Id, e.PrimaryKey, e.PrivateKey, config)
+ if err != nil {
+ return nil, err
+ }
+
+ // If the user passes in a DefaultHash via packet.Config,
+ // set the PreferredHash for the SelfSignature.
+ if config != nil && config.DefaultHash != 0 {
+ e.Identities[uid.Id].SelfSignature.PreferredHash = []uint8{hashToHashId(config.DefaultHash)}
+ }
+
+ // Likewise for DefaultCipher.
+ if config != nil && config.DefaultCipher != 0 {
+ e.Identities[uid.Id].SelfSignature.PreferredSymmetric = []uint8{uint8(config.DefaultCipher)}
+ }
+
+ e.Subkeys = make([]Subkey, 1)
+ e.Subkeys[0] = Subkey{
+ PublicKey: packet.NewRSAPublicKey(creationTime, &encryptingPriv.PublicKey),
+ PrivateKey: packet.NewRSAPrivateKey(creationTime, encryptingPriv),
+ Sig: &packet.Signature{
+ CreationTime: creationTime,
+ SigType: packet.SigTypeSubkeyBinding,
+ PubKeyAlgo: packet.PubKeyAlgoRSA,
+ Hash: config.Hash(),
+ FlagsValid: true,
+ FlagEncryptStorage: true,
+ FlagEncryptCommunications: true,
+ IssuerKeyId: &e.PrimaryKey.KeyId,
+ },
+ }
+ e.Subkeys[0].PublicKey.IsSubkey = true
+ e.Subkeys[0].PrivateKey.IsSubkey = true
+ err = e.Subkeys[0].Sig.SignKey(e.Subkeys[0].PublicKey, e.PrivateKey, config)
+ if err != nil {
+ return nil, err
+ }
+ return e, nil
+}
+
+// SerializePrivate serializes an Entity, including private key material, but
+// excluding signatures from other entities, to the given Writer.
+// Identities and subkeys are re-signed in case they changed since NewEntry.
+// If config is nil, sensible defaults will be used.
+func (e *Entity) SerializePrivate(w io.Writer, config *packet.Config) (err error) {
+ err = e.PrivateKey.Serialize(w)
+ if err != nil {
+ return
+ }
+ for _, ident := range e.Identities {
+ err = ident.UserId.Serialize(w)
+ if err != nil {
+ return
+ }
+ err = ident.SelfSignature.SignUserId(ident.UserId.Id, e.PrimaryKey, e.PrivateKey, config)
+ if err != nil {
+ return
+ }
+ err = ident.SelfSignature.Serialize(w)
+ if err != nil {
+ return
+ }
+ }
+ for _, subkey := range e.Subkeys {
+ err = subkey.PrivateKey.Serialize(w)
+ if err != nil {
+ return
+ }
+ err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config)
+ if err != nil {
+ return
+ }
+ err = subkey.Sig.Serialize(w)
+ if err != nil {
+ return
+ }
+ }
+ return nil
+}
+
+// Serialize writes the public part of the given Entity to w, including
+// signatures from other entities. No private key material will be output.
+func (e *Entity) Serialize(w io.Writer) error {
+ err := e.PrimaryKey.Serialize(w)
+ if err != nil {
+ return err
+ }
+ for _, ident := range e.Identities {
+ err = ident.UserId.Serialize(w)
+ if err != nil {
+ return err
+ }
+ err = ident.SelfSignature.Serialize(w)
+ if err != nil {
+ return err
+ }
+ for _, sig := range ident.Signatures {
+ err = sig.Serialize(w)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ for _, subkey := range e.Subkeys {
+ err = subkey.PublicKey.Serialize(w)
+ if err != nil {
+ return err
+ }
+ err = subkey.Sig.Serialize(w)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// SignIdentity adds a signature to e, from signer, attesting that identity is
+// associated with e. The provided identity must already be an element of
+// e.Identities and the private key of signer must have been decrypted if
+// necessary.
+// If config is nil, sensible defaults will be used.
+func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Config) error {
+ if signer.PrivateKey == nil {
+ return errors.InvalidArgumentError("signing Entity must have a private key")
+ }
+ if signer.PrivateKey.Encrypted {
+ return errors.InvalidArgumentError("signing Entity's private key must be decrypted")
+ }
+ ident, ok := e.Identities[identity]
+ if !ok {
+ return errors.InvalidArgumentError("given identity string not found in Entity")
+ }
+
+ sig := &packet.Signature{
+ SigType: packet.SigTypeGenericCert,
+ PubKeyAlgo: signer.PrivateKey.PubKeyAlgo,
+ Hash: config.Hash(),
+ CreationTime: config.Now(),
+ IssuerKeyId: &signer.PrivateKey.KeyId,
+ }
+ if err := sig.SignUserId(identity, e.PrimaryKey, signer.PrivateKey, config); err != nil {
+ return err
+ }
+ ident.Signatures = append(ident.Signatures, sig)
+ return nil
+}
diff --git a/local_crypto_patch/contents/openpgp/keys_data_test.go b/local_crypto_patch/contents/openpgp/keys_data_test.go
new file mode 100644
index 0000000000..7779bd9774
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/keys_data_test.go
@@ -0,0 +1,200 @@
+package openpgp
+
+const expiringKeyHex = "988d0451d1ec5d010400ba3385721f2dc3f4ab096b2ee867ab77213f0a27a8538441c35d2fa225b08798a1439a66a5150e6bdc3f40f5d28d588c712394c632b6299f77db8c0d48d37903fb72ebd794d61be6aa774688839e5fdecfe06b2684cc115d240c98c66cb1ef22ae84e3aa0c2b0c28665c1e7d4d044e7f270706193f5223c8d44e0d70b7b8da830011010001b40f4578706972792074657374206b657988be041301020028050251d1ec5d021b03050900278d00060b090807030206150802090a0b0416020301021e01021780000a091072589ad75e237d8c033503fd10506d72837834eb7f994117740723adc39227104b0d326a1161871c0b415d25b4aedef946ca77ea4c05af9c22b32cf98be86ab890111fced1ee3f75e87b7cc3c00dc63bbc85dfab91c0dc2ad9de2c4d13a34659333a85c6acc1a669c5e1d6cecb0cf1e56c10e72d855ae177ddc9e766f9b2dda57ccbb75f57156438bbdb4e42b88d0451d1ec5d0104009c64906559866c5cb61578f5846a94fcee142a489c9b41e67b12bb54cfe86eb9bc8566460f9a720cb00d6526fbccfd4f552071a8e3f7744b1882d01036d811ee5a3fb91a1c568055758f43ba5d2c6a9676b012f3a1a89e47bbf624f1ad571b208f3cc6224eb378f1645dd3d47584463f9eadeacfd1ce6f813064fbfdcc4b5a53001101000188a504180102000f021b0c050251d1f06b050900093e89000a091072589ad75e237d8c20e00400ab8310a41461425b37889c4da28129b5fae6084fafbc0a47dd1adc74a264c6e9c9cc125f40462ee1433072a58384daef88c961c390ed06426a81b464a53194c4e291ddd7e2e2ba3efced01537d713bd111f48437bde2363446200995e8e0d4e528dda377fd1e8f8ede9c8e2198b393bd86852ce7457a7e3daf74d510461a5b77b88d0451d1ece8010400b3a519f83ab0010307e83bca895170acce8964a044190a2b368892f7a244758d9fc193482648acb1fb9780d28cc22d171931f38bb40279389fc9bf2110876d4f3db4fcfb13f22f7083877fe56592b3b65251312c36f83ffcb6d313c6a17f197dd471f0712aad15a8537b435a92471ba2e5b0c72a6c72536c3b567c558d7b6051001101000188a504180102000f021b0c050251d1f07b050900279091000a091072589ad75e237d8ce69e03fe286026afacf7c97ee20673864d4459a2240b5655219950643c7dba0ac384b1d4359c67805b21d98211f7b09c2a0ccf6410c8c04d4ff4a51293725d8d6570d9d8bb0e10c07d22357caeb49626df99c180be02d77d1fe8ed25e7a54481237646083a9f89a11566cd20b9e995b1487c5f9e02aeb434f3a1897cd416dd0a87861838da3e9e"
+const subkeyUsageHex = "988d04533a52bc010400d26af43085558f65b9e7dbc90cb9238015259aed5e954637adcfa2181548b2d0b60c65f1f42ec5081cbf1bc0a8aa4900acfb77070837c58f26012fbce297d70afe96e759ad63531f0037538e70dbf8e384569b9720d99d8eb39d8d0a2947233ed242436cb6ac7dfe74123354b3d0119b5c235d3dd9c9d6c004f8ffaf67ad8583001101000188b7041f010200210502533b8552170c8001ce094aa433f7040bb2ddf0be3893cb843d0fe70c020700000a0910a42704b92866382aa98404009d63d916a27543da4221c60087c33f1c44bec9998c5438018ed370cca4962876c748e94b73eb39c58eb698063f3fd6346d58dd2a11c0247934c4a9d71f24754f7468f96fb24c3e791dd2392b62f626148ad724189498cbf993db2df7c0cdc2d677c35da0f16cb16c9ce7c33b4de65a4a91b1d21a130ae9cc26067718910ef8e2b417556d627261203c756d627261407379642e65642e61753e88b80413010200220502533a52bc021b03060b090807030206150802090a0b0416020301021e01021780000a0910a42704b92866382a47840400c0c2bd04f5fca586de408b395b3c280a278259c93eaaa8b79a53b97003f8ed502a8a00446dd9947fb462677e4fcac0dac2f0701847d15130aadb6cd9e0705ea0cf5f92f129136c7be21a718d46c8e641eb7f044f2adae573e11ae423a0a9ca51324f03a8a2f34b91fa40c3cc764bee4dccadedb54c768ba0469b683ea53f1c29b88d04533a52bc01040099c92a5d6f8b744224da27bc2369127c35269b58bec179de6bbc038f749344222f85a31933224f26b70243c4e4b2d242f0c4777eaef7b5502f9dad6d8bf3aaeb471210674b74de2d7078af497d55f5cdad97c7bedfbc1b41e8065a97c9c3d344b21fc81d27723af8e374bc595da26ea242dccb6ae497be26eea57e563ed517e90011010001889f0418010200090502533a52bc021b0c000a0910a42704b92866382afa1403ff70284c2de8a043ff51d8d29772602fa98009b7861c540535f874f2c230af8caf5638151a636b21f8255003997ccd29747fdd06777bb24f9593bd7d98a3e887689bf902f999915fcc94625ae487e5d13e6616f89090ebc4fdc7eb5cad8943e4056995bb61c6af37f8043016876a958ec7ebf39c43d20d53b7f546cfa83e8d2604b88d04533b8283010400c0b529316dbdf58b4c54461e7e669dc11c09eb7f73819f178ccd4177b9182b91d138605fcf1e463262fabefa73f94a52b5e15d1904635541c7ea540f07050ce0fb51b73e6f88644cec86e91107c957a114f69554548a85295d2b70bd0b203992f76eb5d493d86d9eabcaa7ef3fc7db7e458438db3fcdb0ca1cc97c638439a9170011010001889f0418010200090502533b8283021b0c000a0910a42704b92866382adc6d0400cfff6258485a21675adb7a811c3e19ebca18851533f75a7ba317950b9997fda8d1a4c8c76505c08c04b6c2cc31dc704d33da36a21273f2b388a1a706f7c3378b66d887197a525936ed9a69acb57fe7f718133da85ec742001c5d1864e9c6c8ea1b94f1c3759cebfd93b18606066c063a63be86085b7e37bdbc65f9a915bf084bb901a204533b85cd110400aed3d2c52af2b38b5b67904b0ef73d6dd7aef86adb770e2b153cd22489654dcc91730892087bb9856ae2d9f7ed1eb48f214243fe86bfe87b349ebd7c30e630e49c07b21fdabf78b7a95c8b7f969e97e3d33f2e074c63552ba64a2ded7badc05ce0ea2be6d53485f6900c7860c7aa76560376ce963d7271b9b54638a4028b573f00a0d8854bfcdb04986141568046202192263b9b67350400aaa1049dbc7943141ef590a70dcb028d730371d92ea4863de715f7f0f16d168bd3dc266c2450457d46dcbbf0b071547e5fbee7700a820c3750b236335d8d5848adb3c0da010e998908dfd93d961480084f3aea20b247034f8988eccb5546efaa35a92d0451df3aaf1aee5aa36a4c4d462c760ecd9cebcabfbe1412b1f21450f203fd126687cd486496e971a87fd9e1a8a765fe654baa219a6871ab97768596ab05c26c1aeea8f1a2c72395a58dbc12ef9640d2b95784e974a4d2d5a9b17c25fedacfe551bda52602de8f6d2e48443f5dd1a2a2a8e6a5e70ecdb88cd6e766ad9745c7ee91d78cc55c3d06536b49c3fee6c3d0b6ff0fb2bf13a314f57c953b8f4d93bf88e70418010200090502533b85cd021b0200520910a42704b92866382a47200419110200060502533b85cd000a091042ce2c64bc0ba99214b2009e26b26852c8b13b10c35768e40e78fbbb48bd084100a0c79d9ea0844fa5853dd3c85ff3ecae6f2c9dd6c557aa04008bbbc964cd65b9b8299d4ebf31f41cc7264b8cf33a00e82c5af022331fac79efc9563a822497ba012953cefe2629f1242fcdcb911dbb2315985bab060bfd58261ace3c654bdbbe2e8ed27a46e836490145c86dc7bae15c011f7e1ffc33730109b9338cd9f483e7cef3d2f396aab5bd80efb6646d7e778270ee99d934d187dd98"
+const revokedKeyHex = "988d045331ce82010400c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be10011010001889f04200102000905025331d0e3021d03000a0910a401d9f09a34f7c042aa040086631196405b7e6af71026b88e98012eab44aa9849f6ef3fa930c7c9f23deaedba9db1538830f8652fb7648ec3fcade8dbcbf9eaf428e83c6cbcc272201bfe2fbb90d41963397a7c0637a1a9d9448ce695d9790db2dc95433ad7be19eb3de72dacf1d6db82c3644c13eae2a3d072b99bb341debba012c5ce4006a7d34a1f4b94b444526567205265766f6b657220283c52656727732022424d204261726973746122204b657920262530305c303e5c29203c72656740626d626172697374612e636f2e61753e88b704130102002205025331ce82021b03060b090807030206150802090a0b0416020301021e01021780000a0910a401d9f09a34f7c0019c03f75edfbeb6a73e7225ad3cc52724e2872e04260d7daf0d693c170d8c4b243b8767bc7785763533febc62ec2600c30603c433c095453ede59ff2fcabeb84ce32e0ed9d5cf15ffcbc816202b64370d4d77c1e9077d74e94a16fb4fa2e5bec23a56d7a73cf275f91691ae1801a976fcde09e981a2f6327ac27ea1fecf3185df0d56889c04100102000605025331cfb5000a0910fe9645554e8266b64b4303fc084075396674fb6f778d302ac07cef6bc0b5d07b66b2004c44aef711cbac79617ef06d836b4957522d8772dd94bf41a2f4ac8b1ee6d70c57503f837445a74765a076d07b829b8111fc2a918423ddb817ead7ca2a613ef0bfb9c6b3562aec6c3cf3c75ef3031d81d95f6563e4cdcc9960bcb386c5d757b104fcca5fe11fc709df884604101102000605025331cfe7000a09107b15a67f0b3ddc0317f6009e360beea58f29c1d963a22b962b80788c3fa6c84e009d148cfde6b351469b8eae91187eff07ad9d08fcaab88d045331ce820104009f25e20a42b904f3fa555530fe5c46737cf7bd076c35a2a0d22b11f7e0b61a69320b768f4a80fe13980ce380d1cfc4a0cd8fbe2d2e2ef85416668b77208baa65bf973fe8e500e78cc310d7c8705cdb34328bf80e24f0385fce5845c33bc7943cf6b11b02348a23da0bf6428e57c05135f2dc6bd7c1ce325d666d5a5fd2fd5e410011010001889f04180102000905025331ce82021b0c000a0910a401d9f09a34f7c0418003fe34feafcbeaef348a800a0d908a7a6809cc7304017d820f70f0474d5e23cb17e38b67dc6dca282c6ca00961f4ec9edf2738d0f087b1d81e4871ef08e1798010863afb4eac4c44a376cb343be929c5be66a78cfd4456ae9ec6a99d97f4e1c3ff3583351db2147a65c0acef5c003fb544ab3a2e2dc4d43646f58b811a6c3a369d1f"
+const revokedSubkeyHex = "988d04533121f6010400aefc803a3e4bb1a61c86e8a86d2726c6a43e0079e9f2713f1fa017e9854c83877f4aced8e331d675c67ea83ddab80aacbfa0b9040bb12d96f5a3d6be09455e2a76546cbd21677537db941cab710216b6d24ec277ee0bd65b910f416737ed120f6b93a9d3b306245c8cfd8394606fdb462e5cf43c551438d2864506c63367fc890011010001b41d416c696365203c616c69636540626d626172697374612e636f2e61753e88bb041301020025021b03060b090807030206150802090a0b0416020301021e01021780050253312798021901000a09104ef7e4beccde97f015a803ff5448437780f63263b0df8442a995e7f76c221351a51edd06f2063d8166cf3157aada4923dfc44aa0f2a6a4da5cf83b7fe722ba8ab416c976e77c6b5682e7f1069026673bd0de56ba06fd5d7a9f177607f277d9b55ff940a638c3e68525c67517e2b3d976899b93ca267f705b3e5efad7d61220e96b618a4497eab8d04403d23f8846041011020006050253312910000a09107b15a67f0b3ddc03d96e009f50b6365d86c4be5d5e9d0ea42d5e56f5794c617700a0ab274e19c2827780016d23417ce89e0a2c0d987d889c04100102000605025331cf7a000a0910a401d9f09a34f7c0ee970400aca292f213041c9f3b3fc49148cbda9d84afee6183c8dd6c5ff2600b29482db5fecd4303797be1ee6d544a20a858080fec43412061c9a71fae4039fd58013b4ae341273e6c66ad4c7cdd9e68245bedb260562e7b166f2461a1032f2b38c0e0e5715fb3d1656979e052b55ca827a76f872b78a9fdae64bc298170bfcebedc1271b41a416c696365203c616c696365407379646973702e6f722e61753e88b804130102002205025331278b021b03060b090807030206150802090a0b0416020301021e01021780000a09104ef7e4beccde97f06a7003fa03c3af68d272ebc1fa08aa72a03b02189c26496a2833d90450801c4e42c5b5f51ad96ce2d2c9cef4b7c02a6a2fcf1412d6a2d486098eb762f5010a201819c17fd2888aec8eda20c65a3b75744de7ee5cc8ac7bfc470cbe3cb982720405a27a3c6a8c229cfe36905f881b02ed5680f6a8f05866efb9d6c5844897e631deb949ca8846041011020006050253312910000a09107b15a67f0b3ddc0347bc009f7fa35db59147469eb6f2c5aaf6428accb138b22800a0caa2f5f0874bacc5909c652a57a31beda65eddd5889c04100102000605025331cf7a000a0910a401d9f09a34f7c0316403ff46f2a5c101256627f16384d34a38fb47a6c88ba60506843e532d91614339fccae5f884a5741e7582ffaf292ba38ee10a270a05f139bde3814b6a077e8cd2db0f105ebea2a83af70d385f13b507fac2ad93ff79d84950328bb86f3074745a8b7f9b64990fb142e2a12976e27e8d09a28dc5621f957ac49091116da410ac3cbde1b88d04533121f6010400cbd785b56905e4192e2fb62a720727d43c4fa487821203cf72138b884b78b701093243e1d8c92a0248a6c0203a5a88693da34af357499abacaf4b3309c640797d03093870a323b4b6f37865f6eaa2838148a67df4735d43a90ca87942554cdf1c4a751b1e75f9fd4ce4e97e278d6c1c7ed59d33441df7d084f3f02beb68896c70011010001889f0418010200090502533121f6021b0c000a09104ef7e4beccde97f0b98b03fc0a5ccf6a372995835a2f5da33b282a7d612c0ab2a97f59cf9fff73e9110981aac2858c41399afa29624a7fd8a0add11654e3d882c0fd199e161bdad65e5e2548f7b68a437ea64293db1246e3011cbb94dc1bcdeaf0f2539bd88ff16d95547144d97cead6a8c5927660a91e6db0d16eb36b7b49a3525b54d1644e65599b032b7eb901a204533127a0110400bd3edaa09eff9809c4edc2c2a0ebe52e53c50a19c1e49ab78e6167bf61473bb08f2050d78a5cbbc6ed66aff7b42cd503f16b4a0b99fa1609681fca9b7ce2bbb1a5b3864d6cdda4d7ef7849d156d534dea30fb0efb9e4cf8959a2b2ce623905882d5430b995a15c3b9fe92906086788b891002924f94abe139b42cbbfaaabe42f00a0b65dc1a1ad27d798adbcb5b5ad02d2688c89477b03ff4eebb6f7b15a73b96a96bed201c0e5e4ea27e4c6e2dd1005b94d4b90137a5b1cf5e01c6226c070c4cc999938101578877ee76d296b9aab8246d57049caacf489e80a3f40589cade790a020b1ac146d6f7a6241184b8c7fcde680eae3188f5dcbe846d7f7bdad34f6fcfca08413e19c1d5df83fc7c7c627d493492e009c2f52a80400a2fe82de87136fd2e8845888c4431b032ba29d9a29a804277e31002a8201fb8591a3e55c7a0d0881496caf8b9fb07544a5a4879291d0dc026a0ea9e5bd88eb4aa4947bbd694b25012e208a250d65ddc6f1eea59d3aed3b4ec15fcab85e2afaa23a40ab1ef9ce3e11e1bc1c34a0e758e7aa64deb8739276df0af7d4121f834a9b88e70418010200090502533127a0021b02005209104ef7e4beccde97f047200419110200060502533127a0000a0910dbce4ee19529437fe045009c0b32f5ead48ee8a7e98fac0dea3d3e6c0e2c552500a0ad71fadc5007cfaf842d9b7db3335a8cdad15d3d1a6404009b08e2c68fe8f3b45c1bb72a4b3278cdf3012aa0f229883ad74aa1f6000bb90b18301b2f85372ca5d6b9bf478d235b733b1b197d19ccca48e9daf8e890cb64546b4ce1b178faccfff07003c172a2d4f5ebaba9f57153955f3f61a9b80a4f5cb959908f8b211b03b7026a8a82fc612bfedd3794969bcf458c4ce92be215a1176ab88d045331d144010400a5063000c5aaf34953c1aa3bfc95045b3aab9882b9a8027fecfe2142dc6b47ba8aca667399990244d513dd0504716908c17d92c65e74219e004f7b83fc125e575dd58efec3ab6dd22e3580106998523dea42ec75bf9aa111734c82df54630bebdff20fe981cfc36c76f865eb1c2fb62c9e85bc3a6e5015a361a2eb1c8431578d0011010001889f04280102000905025331d433021d03000a09104ef7e4beccde97f02e5503ff5e0630d1b65291f4882b6d40a29da4616bb5088717d469fbcc3648b8276de04a04988b1f1b9f3e18f52265c1f8b6c85861691c1a6b8a3a25a1809a0b32ad330aec5667cb4262f4450649184e8113849b05e5ad06a316ea80c001e8e71838190339a6e48bbde30647bcf245134b9a97fa875c1d83a9862cae87ffd7e2c4ce3a1b89013d04180102000905025331d144021b0200a809104ef7e4beccde97f09d2004190102000605025331d144000a0910677815e371c2fd23522203fe22ab62b8e7a151383cea3edd3a12995693911426f8ccf125e1f6426388c0010f88d9ca7da2224aee8d1c12135998640c5e1813d55a93df472faae75bef858457248db41b4505827590aeccf6f9eb646da7f980655dd3050c6897feddddaca90676dee856d66db8923477d251712bb9b3186b4d0114daf7d6b59272b53218dd1da94a03ff64006fcbe71211e5daecd9961fba66cdb6de3f914882c58ba5beddeba7dcb950c1156d7fba18c19ea880dccc800eae335deec34e3b84ac75ffa24864f782f87815cda1c0f634b3dd2fa67cea30811d21723d21d9551fa12ccbcfa62b6d3a15d01307b99925707992556d50065505b090aadb8579083a20fe65bd2a270da9b011"
+
+const missingCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Charset: UTF-8
+
+mQENBFMYynYBCACVOZ3/e8Bm2b9KH9QyIlHGo/i1bnkpqsgXj8tpJ2MIUOnXMMAY
+ztW7kKFLCmgVdLIC0vSoLA4yhaLcMojznh/2CcUglZeb6Ao8Gtelr//Rd5DRfPpG
+zqcfUo+m+eO1co2Orabw0tZDfGpg5p3AYl0hmxhUyYSc/xUq93xL1UJzBFgYXY54
+QsM8dgeQgFseSk/YvdP5SMx1ev+eraUyiiUtWzWrWC1TdyRa5p4UZg6Rkoppf+WJ
+QrW6BWrhAtqATHc8ozV7uJjeONjUEq24roRc/OFZdmQQGK6yrzKnnbA6MdHhqpdo
+9kWDcXYb7pSE63Lc+OBa5X2GUVvXJLS/3nrtABEBAAG0F2ludmFsaWQtc2lnbmlu
+Zy1zdWJrZXlziQEoBBMBAgASBQJTnKB5AhsBAgsHAhUIAh4BAAoJEO3UDQUIHpI/
+dN4H/idX4FQ1LIZCnpHS/oxoWQWfpRgdKAEM0qCqjMgiipJeEwSQbqjTCynuh5/R
+JlODDz85ABR06aoF4l5ebGLQWFCYifPnJZ/Yf5OYcMGtb7dIbqxWVFL9iLMO/oDL
+ioI3dotjPui5e+2hI9pVH1UHB/bZ/GvMGo6Zg0XxLPolKQODMVjpjLAQ0YJ3spew
+RAmOGre6tIvbDsMBnm8qREt7a07cBJ6XK7xjxYaZHQBiHVxyEWDa6gyANONx8duW
+/fhQ/zDTnyVM/ik6VO0Ty9BhPpcEYLFwh5c1ilFari1ta3e6qKo6ZGa9YMk/REhu
+yBHd9nTkI+0CiQUmbckUiVjDKKe5AQ0EUxjKdgEIAJcXQeP+NmuciE99YcJoffxv
+2gVLU4ZXBNHEaP0mgaJ1+tmMD089vUQAcyGRvw8jfsNsVZQIOAuRxY94aHQhIRHR
+bUzBN28ofo/AJJtfx62C15xt6fDKRV6HXYqAiygrHIpEoRLyiN69iScUsjIJeyFL
+C8wa72e8pSL6dkHoaV1N9ZH/xmrJ+k0vsgkQaAh9CzYufncDxcwkoP+aOlGtX1gP
+WwWoIbz0JwLEMPHBWvDDXQcQPQTYQyj+LGC9U6f9VZHN25E94subM1MjuT9OhN9Y
+MLfWaaIc5WyhLFyQKW2Upofn9wSFi8ubyBnv640Dfd0rVmaWv7LNTZpoZ/GbJAMA
+EQEAAYkBHwQYAQIACQUCU5ygeQIbAgAKCRDt1A0FCB6SP0zCB/sEzaVR38vpx+OQ
+MMynCBJrakiqDmUZv9xtplY7zsHSQjpd6xGflbU2n+iX99Q+nav0ETQZifNUEd4N
+1ljDGQejcTyKD6Pkg6wBL3x9/RJye7Zszazm4+toJXZ8xJ3800+BtaPoI39akYJm
++ijzbskvN0v/j5GOFJwQO0pPRAFtdHqRs9Kf4YanxhedB4dIUblzlIJuKsxFit6N
+lgGRblagG3Vv2eBszbxzPbJjHCgVLR3RmrVezKOsZjr/2i7X+xLWIR0uD3IN1qOW
+CXQxLBizEEmSNVNxsp7KPGTLnqO3bPtqFirxS9PJLIMPTPLNBY7ZYuPNTMqVIUWF
+4artDmrG
+=7FfJ
+-----END PGP PUBLIC KEY BLOCK-----`
+
+const invalidCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBFMYynYBCACVOZ3/e8Bm2b9KH9QyIlHGo/i1bnkpqsgXj8tpJ2MIUOnXMMAY
+ztW7kKFLCmgVdLIC0vSoLA4yhaLcMojznh/2CcUglZeb6Ao8Gtelr//Rd5DRfPpG
+zqcfUo+m+eO1co2Orabw0tZDfGpg5p3AYl0hmxhUyYSc/xUq93xL1UJzBFgYXY54
+QsM8dgeQgFseSk/YvdP5SMx1ev+eraUyiiUtWzWrWC1TdyRa5p4UZg6Rkoppf+WJ
+QrW6BWrhAtqATHc8ozV7uJjeONjUEq24roRc/OFZdmQQGK6yrzKnnbA6MdHhqpdo
+9kWDcXYb7pSE63Lc+OBa5X2GUVvXJLS/3nrtABEBAAG0F2ludmFsaWQtc2lnbmlu
+Zy1zdWJrZXlziQEoBBMBAgASBQJTnKB5AhsBAgsHAhUIAh4BAAoJEO3UDQUIHpI/
+dN4H/idX4FQ1LIZCnpHS/oxoWQWfpRgdKAEM0qCqjMgiipJeEwSQbqjTCynuh5/R
+JlODDz85ABR06aoF4l5ebGLQWFCYifPnJZ/Yf5OYcMGtb7dIbqxWVFL9iLMO/oDL
+ioI3dotjPui5e+2hI9pVH1UHB/bZ/GvMGo6Zg0XxLPolKQODMVjpjLAQ0YJ3spew
+RAmOGre6tIvbDsMBnm8qREt7a07cBJ6XK7xjxYaZHQBiHVxyEWDa6gyANONx8duW
+/fhQ/zDTnyVM/ik6VO0Ty9BhPpcEYLFwh5c1ilFari1ta3e6qKo6ZGa9YMk/REhu
+yBHd9nTkI+0CiQUmbckUiVjDKKe5AQ0EUxjKdgEIAIINDqlj7X6jYKc6DjwrOkjQ
+UIRWbQQar0LwmNilehmt70g5DCL1SYm9q4LcgJJ2Nhxj0/5qqsYib50OSWMcKeEe
+iRXpXzv1ObpcQtI5ithp0gR53YPXBib80t3bUzomQ5UyZqAAHzMp3BKC54/vUrSK
+FeRaxDzNLrCeyI00+LHNUtwghAqHvdNcsIf8VRumK8oTm3RmDh0TyjASWYbrt9c8
+R1Um3zuoACOVy+mEIgIzsfHq0u7dwYwJB5+KeM7ZLx+HGIYdUYzHuUE1sLwVoELh
++SHIGHI1HDicOjzqgajShuIjj5hZTyQySVprrsLKiXS6NEwHAP20+XjayJ/R3tEA
+EQEAAYkCPgQYAQIBKAUCU5ygeQIbAsBdIAQZAQIABgUCU5ygeQAKCRCpVlnFZmhO
+52RJB/9uD1MSa0wjY6tHOIgquZcP3bHBvHmrHNMw9HR2wRCMO91ZkhrpdS3ZHtgb
+u3/55etj0FdvDo1tb8P8FGSVtO5Vcwf5APM8sbbqoi8L951Q3i7qt847lfhu6sMl
+w0LWFvPTOLHrliZHItPRjOltS1WAWfr2jUYhsU9ytaDAJmvf9DujxEOsN5G1YJep
+54JCKVCkM/y585Zcnn+yxk/XwqoNQ0/iJUT9qRrZWvoeasxhl1PQcwihCwss44A+
+YXaAt3hbk+6LEQuZoYS73yR3WHj+42tfm7YxRGeubXfgCEz/brETEWXMh4pe0vCL
+bfWrmfSPq2rDegYcAybxRQz0lF8PAAoJEO3UDQUIHpI/exkH/0vQfdHA8g/N4T6E
+i6b1CUVBAkvtdJpCATZjWPhXmShOw62gkDw306vHPilL4SCvEEi4KzG72zkp6VsB
+DSRcpxCwT4mHue+duiy53/aRMtSJ+vDfiV1Vhq+3sWAck/yUtfDU9/u4eFaiNok1
+8/Gd7reyuZt5CiJnpdPpjCwelK21l2w7sHAnJF55ITXdOxI8oG3BRKufz0z5lyDY
+s2tXYmhhQIggdgelN8LbcMhWs/PBbtUr6uZlNJG2lW1yscD4aI529VjwJlCeo745
+U7pO4eF05VViUJ2mmfoivL3tkhoTUWhx8xs8xCUcCg8DoEoSIhxtOmoTPR22Z9BL
+6LCg2mg=
+=Dhm4
+-----END PGP PUBLIC KEY BLOCK-----`
+
+const goodCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1
+
+mI0EVUqeVwEEAMufHRrMPWK3gyvi0O0tABCs/oON9zV9KDZlr1a1M91ShCSFwCPo
+7r80PxdWVWcj0V5h50/CJYtpN3eE/mUIgW2z1uDYQF1OzrQ8ubrksfsJvpAhENom
+lTQEppv9mV8qhcM278teb7TX0pgrUHLYF5CfPdp1L957JLLXoQR/lwLVABEBAAG0
+E2dvb2Qtc2lnbmluZy1zdWJrZXmIuAQTAQIAIgUCVUqeVwIbAwYLCQgHAwIGFQgC
+CQoLBBYCAwECHgECF4AACgkQNRjL95IRWP69XQQAlH6+eyXJN4DZTLX78KGjHrsw
+6FCvxxClEPtPUjcJy/1KCRQmtLAt9PbbA78dvgzjDeZMZqRAwdjyJhjyg/fkU2OH
+7wq4ktjUu+dLcOBb+BFMEY+YjKZhf6EJuVfxoTVr5f82XNPbYHfTho9/OABKH6kv
+X70PaKZhbwnwij8Nts65AaIEVUqftREEAJ3WxZfqAX0bTDbQPf2CMT2IVMGDfhK7
+GyubOZgDFFjwUJQvHNvsrbeGLZ0xOBumLINyPO1amIfTgJNm1iiWFWfmnHReGcDl
+y5mpYG60Mb79Whdcer7CMm3AqYh/dW4g6IB02NwZMKoUHo3PXmFLxMKXnWyJ0clw
+R0LI/Qn509yXAKDh1SO20rqrBM+EAP2c5bfI98kyNwQAi3buu94qo3RR1ZbvfxgW
+CKXDVm6N99jdZGNK7FbRifXqzJJDLcXZKLnstnC4Sd3uyfyf1uFhmDLIQRryn5m+
+LBYHfDBPN3kdm7bsZDDq9GbTHiFZUfm/tChVKXWxkhpAmHhU/tH6GGzNSMXuIWSO
+aOz3Rqq0ED4NXyNKjdF9MiwD/i83S0ZBc0LmJYt4Z10jtH2B6tYdqnAK29uQaadx
+yZCX2scE09UIm32/w7pV77CKr1Cp/4OzAXS1tmFzQ+bX7DR+Gl8t4wxr57VeEMvl
+BGw4Vjh3X8//m3xynxycQU18Q1zJ6PkiMyPw2owZ/nss3hpSRKFJsxMLhW3fKmKr
+Ey2KiOcEGAECAAkFAlVKn7UCGwIAUgkQNRjL95IRWP5HIAQZEQIABgUCVUqftQAK
+CRD98VjDN10SqkWrAKDTpEY8D8HC02E/KVC5YUI01B30wgCgurpILm20kXEDCeHp
+C5pygfXw1DJrhAP+NyPJ4um/bU1I+rXaHHJYroYJs8YSweiNcwiHDQn0Engh/mVZ
+SqLHvbKh2dL/RXymC3+rjPvQf5cup9bPxNMa6WagdYBNAfzWGtkVISeaQW+cTEp/
+MtgVijRGXR/lGLGETPg2X3Afwn9N9bLMBkBprKgbBqU7lpaoPupxT61bL70=
+=vtbN
+-----END PGP PUBLIC KEY BLOCK-----`
+
+const revokedUserIDKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBFsgO5EBCADhREPmcjsPkXe1z7ctvyWL0S7oa9JaoGZ9oPDHFDlQxd0qlX2e
+DZJZDg0qYvVixmaULIulApq1puEsaJCn3lHUbHlb4PYKwLEywYXM28JN91KtLsz/
+uaEX2KC5WqeP40utmzkNLq+oRX/xnRMgwbO7yUNVG2UlEa6eI+xOXO3YtLdmJMBW
+ClQ066ZnOIzEo1JxnIwha1CDBMWLLfOLrg6l8InUqaXbtEBbnaIYO6fXVXELUjkx
+nmk7t/QOk0tXCy8muH9UDqJkwDUESY2l79XwBAcx9riX8vY7vwC34pm22fAUVLCJ
+x1SJx0J8bkeNp38jKM2Zd9SUQqSbfBopQ4pPABEBAAG0I0dvbGFuZyBHb3BoZXIg
+PG5vLXJlcGx5QGdvbGFuZy5jb20+iQFUBBMBCgA+FiEE5Ik5JLcNx6l6rZfw1oFy
+9I6cUoMFAlsgO5ECGwMFCQPCZwAFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQ
+1oFy9I6cUoMIkwf8DNPeD23i4jRwd/pylbvxwZintZl1fSwTJW1xcOa1emXaEtX2
+depuqhP04fjlRQGfsYAQh7X9jOJxAHjTmhqFBi5sD7QvKU00cPFYbJ/JTx0B41bl
+aXnSbGhRPh63QtEZL7ACAs+shwvvojJqysx7kyVRu0EW2wqjXdHwR/SJO6nhNBa2
+DXzSiOU/SUA42mmG+5kjF8Aabq9wPwT9wjraHShEweNerNMmOqJExBOy3yFeyDpa
+XwEZFzBfOKoxFNkIaVf5GSdIUGhFECkGvBMB935khftmgR8APxdU4BE7XrXexFJU
+8RCuPXonm4WQOwTWR0vQg64pb2WKAzZ8HhwTGbQiR29sYW5nIEdvcGhlciA8cmV2
+b2tlZEBnb2xhbmcuY29tPokBNgQwAQoAIBYhBOSJOSS3Dcepeq2X8NaBcvSOnFKD
+BQJbIDv3Ah0AAAoJENaBcvSOnFKDfWMIAKhI/Tvu3h8fSUxp/gSAcduT6bC1JttG
+0lYQ5ilKB/58lBUA5CO3ZrKDKlzW3M8VEcvohVaqeTMKeoQd5rCZq8KxHn/KvN6N
+s85REfXfniCKfAbnGgVXX3kDmZ1g63pkxrFu0fDZjVDXC6vy+I0sGyI/Inro0Pzb
+tvn0QCsxjapKK15BtmSrpgHgzVqVg0cUp8vqZeKFxarYbYB2idtGRci4b9tObOK0
+BSTVFy26+I/mrFGaPrySYiy2Kz5NMEcRhjmTxJ8jSwEr2O2sUR0yjbgUAXbTxDVE
+/jg5fQZ1ACvBRQnB7LvMHcInbzjyeTM3FazkkSYQD6b97+dkWwb1iWG5AQ0EWyA7
+kQEIALkg04REDZo1JgdYV4x8HJKFS4xAYWbIva1ZPqvDNmZRUbQZR2+gpJGEwn7z
+VofGvnOYiGW56AS5j31SFf5kro1+1bZQ5iOONBng08OOo58/l1hRseIIVGB5TGSa
+PCdChKKHreJI6hS3mShxH6hdfFtiZuB45rwoaArMMsYcjaezLwKeLc396cpUwwcZ
+snLUNd1Xu5EWEF2OdFkZ2a1qYdxBvAYdQf4+1Nr+NRIx1u1NS9c8jp3PuMOkrQEi
+bNtc1v6v0Jy52mKLG4y7mC/erIkvkQBYJdxPaP7LZVaPYc3/xskcyijrJ/5ufoD8
+K71/ShtsZUXSQn9jlRaYR0EbojMAEQEAAYkBPAQYAQoAJhYhBOSJOSS3Dcepeq2X
+8NaBcvSOnFKDBQJbIDuRAhsMBQkDwmcAAAoJENaBcvSOnFKDkFMIAIt64bVZ8x7+
+TitH1bR4pgcNkaKmgKoZz6FXu80+SnbuEt2NnDyf1cLOSimSTILpwLIuv9Uft5Pb
+OraQbYt3xi9yrqdKqGLv80bxqK0NuryNkvh9yyx5WoG1iKqMj9/FjGghuPrRaT4l
+QinNAghGVkEy1+aXGFrG2DsOC1FFI51CC2WVTzZ5RwR2GpiNRfESsU1rZAUqf/2V
+yJl9bD5R4SUNy8oQmhOxi+gbhD4Ao34e4W0ilibslI/uawvCiOwlu5NGd8zv5n+U
+heiQvzkApQup5c+BhH5zFDFdKJ2CBByxw9+7QjMFI/wgLixKuE0Ob2kAokXf7RlB
+7qTZOahrETw=
+=IKnw
+-----END PGP PUBLIC KEY BLOCK-----`
+
+const keyWithSubKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mI0EWyKwKQEEALwXhKBnyaaNFeK3ljfc/qn9X/QFw+28EUfgZPHjRmHubuXLE2uR
+s3ZoSXY2z7Dkv+NyHYMt8p+X8q5fR7JvUjK2XbPyKoiJVnHINll83yl67DaWfKNL
+EjNoO0kIfbXfCkZ7EG6DL+iKtuxniGTcnGT47e+HJSqb/STpLMnWwXjBABEBAAG0
+I0dvbGFuZyBHb3BoZXIgPG5vLXJlcGx5QGdvbGFuZy5jb20+iM4EEwEKADgWIQQ/
+lRafP/p9PytHbwxMvYJsOQdOOAUCWyKwKQIbAwULCQgHAwUVCgkICwUWAgMBAAIe
+AQIXgAAKCRBMvYJsOQdOOOsFBAC62mXww8XuqvYLcVOvHkWLT6mhxrQOJXnlfpn7
+2uBV9CMhoG/Ycd43NONsJrB95Apr9TDIqWnVszNbqPCuBhZQSGLdbiDKjxnCWBk0
+69qv4RNtkpOhYB7jK4s8F5oQZqId6JasT/PmJTH92mhBYhhTQr0GYFuPX2UJdkw9
+Sn9C67iNBFsisDUBBAC3A+Yo9lgCnxi/pfskyLrweYif6kIXWLAtLTsM6g/6jt7b
+wTrknuCPyTv0QKGXsAEe/cK/Xq3HvX9WfXPGIHc/X56ZIsHQ+RLowbZV/Lhok1IW
+FAuQm8axr/by80cRwFnzhfPc/ukkAq2Qyj4hLsGblu6mxeAhzcp8aqmWOO2H9QAR
+AQABiLYEKAEKACAWIQQ/lRafP/p9PytHbwxMvYJsOQdOOAUCWyK16gIdAAAKCRBM
+vYJsOQdOOB1vA/4u4uLONsE+2GVOyBsHyy7uTdkuxaR9b54A/cz6jT/tzUbeIzgx
+22neWhgvIEghnUZd0vEyK9k1wy5vbDlEo6nKzHso32N1QExGr5upRERAxweDxGOj
+7luDwNypI7QcifE64lS/JmlnunwRCdRWMKc0Fp+7jtRc5mpwyHN/Suf5RokBagQY
+AQoAIBYhBD+VFp8/+n0/K0dvDEy9gmw5B044BQJbIrA1AhsCAL8JEEy9gmw5B044
+tCAEGQEKAB0WIQSNdnkaWY6t62iX336UXbGvYdhXJwUCWyKwNQAKCRCUXbGvYdhX
+JxJSA/9fCPHP6sUtGF1o3G1a3yvOUDGr1JWcct9U+QpbCt1mZoNopCNDDQAJvDWl
+mvDgHfuogmgNJRjOMznvahbF+wpTXmB7LS0SK412gJzl1fFIpK4bgnhu0TwxNsO1
+8UkCZWqxRMgcNUn9z6XWONK8dgt5JNvHSHrwF4CxxwjL23AAtK+FA/UUoi3U4kbC
+0XnSr1Sl+mrzQi1+H7xyMe7zjqe+gGANtskqexHzwWPUJCPZ5qpIa2l8ghiUim6b
+4ymJ+N8/T8Yva1FaPEqfMzzqJr8McYFm0URioXJPvOAlRxdHPteZ0qUopt/Jawxl
+Xt6B9h1YpeLoJwjwsvbi98UTRs0jXwoY
+=3fWu
+-----END PGP PUBLIC KEY BLOCK-----`
+
+const keyWithSubKeyAndBadSelfSigOrder = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mI0EWyLLDQEEAOqIOpJ/ha1OYAGduu9tS3rBz5vyjbNgJO4sFveEM0mgsHQ0X9/L
+plonW+d0gRoO1dhJ8QICjDAc6+cna1DE3tEb5m6JtQ30teLZuqrR398Cf6w7NNVz
+r3lrlmnH9JaKRuXl7tZciwyovneBfZVCdtsRZjaLI1uMQCz/BToiYe3DABEBAAG0
+I0dvbGFuZyBHb3BoZXIgPG5vLXJlcGx5QGdvbGFuZy5jb20+iM4EEwEKADgWIQRZ
+sixZOfQcZdW0wUqmgmdsv1O9xgUCWyLLDQIbAwULCQgHAwUVCgkICwUWAgMBAAIe
+AQIXgAAKCRCmgmdsv1O9xql2A/4pix98NxjhdsXtazA9agpAKeADf9tG4Za27Gj+
+3DCww/E4iP2X35jZimSm/30QRB6j08uGCqd9vXkkJxtOt63y/IpVOtWX6vMWSTUm
+k8xKkaYMP0/IzKNJ1qC/qYEUYpwERBKg9Z+k99E2Ql4kRHdxXUHq6OzY79H18Y+s
+GdeM/riNBFsiyxsBBAC54Pxg/8ZWaZX1phGdwfe5mek27SOYpC0AxIDCSOdMeQ6G
+HPk38pywl1d+S+KmF/F4Tdi+kWro62O4eG2uc/T8JQuRDUhSjX0Qa51gPzJrUOVT
+CFyUkiZ/3ZDhtXkgfuso8ua2ChBgR9Ngr4v43tSqa9y6AK7v0qjxD1x+xMrjXQAR
+AQABiQFxBBgBCgAmAhsCFiEEWbIsWTn0HGXVtMFKpoJnbL9TvcYFAlsizTIFCQAN
+MRcAv7QgBBkBCgAdFiEEJcoVUVJIk5RWj1c/o62jUpRPICQFAlsiyxsACgkQo62j
+UpRPICQq5gQApoWIigZxXFoM0uw4uJBS5JFZtirTANvirZV5RhndwHeMN6JttaBS
+YnjyA4+n1D+zB2VqliD2QrsX12KJN6rGOehCtEIClQ1Hodo9nC6kMzzAwW1O8bZs
+nRJmXV+bsvD4sidLZLjdwOVa3Cxh6pvq4Uur6a7/UYx121hEY0Qx0s8JEKaCZ2y/
+U73GGi0D/i20VW8AWYAPACm2zMlzExKTOAV01YTQH/3vW0WLrOse53WcIVZga6es
+HuO4So0SOEAvxKMe5HpRIu2dJxTvd99Bo9xk9xJU0AoFrO0vNCRnL+5y68xMlODK
+lEw5/kl0jeaTBp6xX0HDQOEVOpPGUwWV4Ij2EnvfNDXaE1vK1kffiQFrBBgBCgAg
+AhsCFiEEWbIsWTn0HGXVtMFKpoJnbL9TvcYFAlsi0AYAv7QgBBkBCgAdFiEEJcoV
+UVJIk5RWj1c/o62jUpRPICQFAlsiyxsACgkQo62jUpRPICQq5gQApoWIigZxXFoM
+0uw4uJBS5JFZtirTANvirZV5RhndwHeMN6JttaBSYnjyA4+n1D+zB2VqliD2QrsX
+12KJN6rGOehCtEIClQ1Hodo9nC6kMzzAwW1O8bZsnRJmXV+bsvD4sidLZLjdwOVa
+3Cxh6pvq4Uur6a7/UYx121hEY0Qx0s8JEKaCZ2y/U73GRl0EAJokkXmy4zKDHWWi
+wvK9gi2gQgRkVnu2AiONxJb5vjeLhM/07BRmH6K1o+w3fOeEQp4FjXj1eQ5fPSM6
+Hhwx2CTl9SDnPSBMiKXsEFRkmwQ2AAsQZLmQZvKBkLZYeBiwf+IY621eYDhZfo+G
+1dh1WoUCyREZsJQg2YoIpWIcvw+a
+=bNRo
+-----END PGP PUBLIC KEY BLOCK-----
+`
diff --git a/local_crypto_patch/contents/openpgp/keys_test.go b/local_crypto_patch/contents/openpgp/keys_test.go
new file mode 100644
index 0000000000..9631eb6408
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/keys_test.go
@@ -0,0 +1,495 @@
+package openpgp
+
+import (
+ "bytes"
+ "crypto"
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/packet"
+)
+
+func TestKeyExpiry(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(expiringKeyHex))
+ if err != nil {
+ t.Fatal(err)
+ }
+ entity := kring[0]
+
+ const timeFormat = "2006-01-02"
+ time1, _ := time.Parse(timeFormat, "2013-07-01")
+
+ // The expiringKeyHex key is structured as:
+ //
+ // pub 1024R/5E237D8C created: 2013-07-01 expires: 2013-07-31 usage: SC
+ // sub 1024R/1ABB25A0 created: 2013-07-01 23:11:07 +0200 CEST expires: 2013-07-08 usage: E
+ // sub 1024R/96A672F5 created: 2013-07-01 23:11:23 +0200 CEST expires: 2013-07-31 usage: E
+ //
+ // So this should select the newest, non-expired encryption key.
+ key, _ := entity.encryptionKey(time1)
+ if id, expected := key.PublicKey.KeyIdShortString(), "96A672F5"; id != expected {
+ t.Errorf("Expected key %s at time %s, but got key %s", expected, time1.Format(timeFormat), id)
+ }
+
+ // Once the first encryption subkey has expired, the second should be
+ // selected.
+ time2, _ := time.Parse(timeFormat, "2013-07-09")
+ key, _ = entity.encryptionKey(time2)
+ if id, expected := key.PublicKey.KeyIdShortString(), "96A672F5"; id != expected {
+ t.Errorf("Expected key %s at time %s, but got key %s", expected, time2.Format(timeFormat), id)
+ }
+
+ // Once all the keys have expired, nothing should be returned.
+ time3, _ := time.Parse(timeFormat, "2013-08-01")
+ if key, ok := entity.encryptionKey(time3); ok {
+ t.Errorf("Expected no key at time %s, but got key %s", time3.Format(timeFormat), key.PublicKey.KeyIdShortString())
+ }
+}
+
+func TestMissingCrossSignature(t *testing.T) {
+ // This public key has a signing subkey, but the subkey does not
+ // contain a cross-signature.
+ keys, err := ReadArmoredKeyRing(bytes.NewBufferString(missingCrossSignatureKey))
+ if len(keys) != 0 {
+ t.Errorf("Accepted key with missing cross signature")
+ }
+ if err == nil {
+ t.Fatal("Failed to detect error in keyring with missing cross signature")
+ }
+ structural, ok := err.(errors.StructuralError)
+ if !ok {
+ t.Fatalf("Unexpected class of error: %T. Wanted StructuralError", err)
+ }
+ const expectedMsg = "signing subkey is missing cross-signature"
+ if !strings.Contains(string(structural), expectedMsg) {
+ t.Fatalf("Unexpected error: %q. Expected it to contain %q", err, expectedMsg)
+ }
+}
+
+func TestInvalidCrossSignature(t *testing.T) {
+ // This public key has a signing subkey, and the subkey has an
+ // embedded cross-signature. However, the cross-signature does
+ // not correctly validate over the primary and subkey.
+ keys, err := ReadArmoredKeyRing(bytes.NewBufferString(invalidCrossSignatureKey))
+ if len(keys) != 0 {
+ t.Errorf("Accepted key with invalid cross signature")
+ }
+ if err == nil {
+ t.Fatal("Failed to detect error in keyring with an invalid cross signature")
+ }
+ structural, ok := err.(errors.StructuralError)
+ if !ok {
+ t.Fatalf("Unexpected class of error: %T. Wanted StructuralError", err)
+ }
+ const expectedMsg = "subkey signature invalid"
+ if !strings.Contains(string(structural), expectedMsg) {
+ t.Fatalf("Unexpected error: %q. Expected it to contain %q", err, expectedMsg)
+ }
+}
+
+func TestGoodCrossSignature(t *testing.T) {
+ // This public key has a signing subkey, and the subkey has an
+ // embedded cross-signature which correctly validates over the
+ // primary and subkey.
+ keys, err := ReadArmoredKeyRing(bytes.NewBufferString(goodCrossSignatureKey))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(keys) != 1 {
+ t.Errorf("Failed to accept key with good cross signature, %d", len(keys))
+ }
+ if len(keys[0].Subkeys) != 1 {
+ t.Errorf("Failed to accept good subkey, %d", len(keys[0].Subkeys))
+ }
+}
+
+func TestRevokedUserID(t *testing.T) {
+ // This key contains 2 UIDs, one of which is revoked:
+ // [ultimate] (1) Golang Gopher
+ // [ revoked] (2) Golang Gopher
+ keys, err := ReadArmoredKeyRing(bytes.NewBufferString(revokedUserIDKey))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(keys) != 1 {
+ t.Fatal("Failed to read key with a revoked user id")
+ }
+
+ var identities []*Identity
+ for _, identity := range keys[0].Identities {
+ identities = append(identities, identity)
+ }
+
+ if numIdentities, numExpected := len(identities), 1; numIdentities != numExpected {
+ t.Errorf("obtained %d identities, expected %d", numIdentities, numExpected)
+ }
+
+ if identityName, expectedName := identities[0].Name, "Golang Gopher "; identityName != expectedName {
+ t.Errorf("obtained identity %s expected %s", identityName, expectedName)
+ }
+}
+
+// TestExternallyRevocableKey attempts to load and parse a key with a third party revocation permission.
+func TestExternallyRevocableKey(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // The 0xA42704B92866382A key can be revoked by 0xBE3893CB843D0FE70C
+ // according to this signature that appears within the key:
+ // :signature packet: algo 1, keyid A42704B92866382A
+ // version 4, created 1396409682, md5len 0, sigclass 0x1f
+ // digest algo 2, begin of digest a9 84
+ // hashed subpkt 2 len 4 (sig created 2014-04-02)
+ // hashed subpkt 12 len 22 (revocation key: c=80 a=1 f=CE094AA433F7040BB2DDF0BE3893CB843D0FE70C)
+ // hashed subpkt 7 len 1 (not revocable)
+ // subpkt 16 len 8 (issuer key ID A42704B92866382A)
+ // data: [1024 bits]
+
+ id := uint64(0xA42704B92866382A)
+ keys := kring.KeysById(id)
+ if len(keys) != 1 {
+ t.Errorf("Expected to find key id %X, but got %d matches", id, len(keys))
+ }
+}
+
+func TestKeyRevocation(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(revokedKeyHex))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // revokedKeyHex contains these keys:
+ // pub 1024R/9A34F7C0 2014-03-25 [revoked: 2014-03-25]
+ // sub 1024R/1BA3CD60 2014-03-25 [revoked: 2014-03-25]
+ ids := []uint64{0xA401D9F09A34F7C0, 0x5CD3BE0A1BA3CD60}
+
+ for _, id := range ids {
+ keys := kring.KeysById(id)
+ if len(keys) != 1 {
+ t.Errorf("Expected KeysById to find revoked key %X, but got %d matches", id, len(keys))
+ }
+ keys = kring.KeysByIdUsage(id, 0)
+ if len(keys) != 0 {
+ t.Errorf("Expected KeysByIdUsage to filter out revoked key %X, but got %d matches", id, len(keys))
+ }
+ }
+}
+
+func TestKeyWithRevokedSubKey(t *testing.T) {
+ // This key contains a revoked sub key:
+ // pub rsa1024/0x4CBD826C39074E38 2018-06-14 [SC]
+ // Key fingerprint = 3F95 169F 3FFA 7D3F 2B47 6F0C 4CBD 826C 3907 4E38
+ // uid Golang Gopher
+ // sub rsa1024/0x945DB1AF61D85727 2018-06-14 [S] [revoked: 2018-06-14]
+
+ keys, err := ReadArmoredKeyRing(bytes.NewBufferString(keyWithSubKey))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(keys) != 1 {
+ t.Fatal("Failed to read key with a sub key")
+ }
+
+ identity := keys[0].Identities["Golang Gopher "]
+
+ // Test for an issue where Subkey Binding Signatures (RFC 4880 5.2.1) were added to the identity
+ // preceding the Subkey Packet if the Subkey Packet was followed by more than one signature.
+ // For example, the current key has the following layout:
+ // PUBKEY UID SELFSIG SUBKEY REV SELFSIG
+ // The last SELFSIG would be added to the UID's signatures. This is wrong.
+ if numIdentitySigs, numExpected := len(identity.Signatures), 0; numIdentitySigs != numExpected {
+ t.Fatalf("got %d identity signatures, expected %d", numIdentitySigs, numExpected)
+ }
+
+ if numSubKeys, numExpected := len(keys[0].Subkeys), 1; numSubKeys != numExpected {
+ t.Fatalf("got %d subkeys, expected %d", numSubKeys, numExpected)
+ }
+
+ subKey := keys[0].Subkeys[0]
+ if subKey.Sig == nil {
+ t.Fatalf("subkey signature is nil")
+ }
+
+}
+
+func TestSubkeyRevocation(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(revokedSubkeyHex))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // revokedSubkeyHex contains these keys:
+ // pub 1024R/4EF7E4BECCDE97F0 2014-03-25
+ // sub 1024R/D63636E2B96AE423 2014-03-25
+ // sub 1024D/DBCE4EE19529437F 2014-03-25
+ // sub 1024R/677815E371C2FD23 2014-03-25 [revoked: 2014-03-25]
+ validKeys := []uint64{0x4EF7E4BECCDE97F0, 0xD63636E2B96AE423, 0xDBCE4EE19529437F}
+ revokedKey := uint64(0x677815E371C2FD23)
+
+ for _, id := range validKeys {
+ keys := kring.KeysById(id)
+ if len(keys) != 1 {
+ t.Errorf("Expected KeysById to find key %X, but got %d matches", id, len(keys))
+ }
+ keys = kring.KeysByIdUsage(id, 0)
+ if len(keys) != 1 {
+ t.Errorf("Expected KeysByIdUsage to find key %X, but got %d matches", id, len(keys))
+ }
+ }
+
+ keys := kring.KeysById(revokedKey)
+ if len(keys) != 1 {
+ t.Errorf("Expected KeysById to find key %X, but got %d matches", revokedKey, len(keys))
+ }
+
+ keys = kring.KeysByIdUsage(revokedKey, 0)
+ if len(keys) != 0 {
+ t.Errorf("Expected KeysByIdUsage to filter out revoked key %X, but got %d matches", revokedKey, len(keys))
+ }
+}
+
+func TestKeyWithSubKeyAndBadSelfSigOrder(t *testing.T) {
+ // This key was altered so that the self signatures following the
+ // subkey are in a sub-optimal order.
+ //
+ // Note: Should someone have to create a similar key again, look into
+ // gpgsplit, gpg --dearmor, and gpg --enarmor.
+ //
+ // The packet ordering is the following:
+ // PUBKEY UID UIDSELFSIG SUBKEY SELFSIG1 SELFSIG2
+ //
+ // Where:
+ // SELFSIG1 expires on 2018-06-14 and was created first
+ // SELFSIG2 does not expire and was created after SELFSIG1
+ //
+ // Test for RFC 4880 5.2.3.3:
+ // > An implementation that encounters multiple self-signatures on the
+ // > same object may resolve the ambiguity in any way it sees fit, but it
+ // > is RECOMMENDED that priority be given to the most recent self-
+ // > signature.
+ //
+ // This means that we should keep SELFSIG2.
+
+ keys, err := ReadArmoredKeyRing(bytes.NewBufferString(keyWithSubKeyAndBadSelfSigOrder))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(keys) != 1 {
+ t.Fatal("Failed to read key with a sub key and a bad selfsig packet order")
+ }
+
+ key := keys[0]
+
+ if numKeys, expected := len(key.Subkeys), 1; numKeys != expected {
+ t.Fatalf("Read %d subkeys, expected %d", numKeys, expected)
+ }
+
+ subKey := key.Subkeys[0]
+
+ if lifetime := subKey.Sig.KeyLifetimeSecs; lifetime != nil {
+ t.Errorf("The signature has a key lifetime (%d), but it should be nil", *lifetime)
+ }
+
+}
+
+func TestKeyUsage(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(subkeyUsageHex))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // subkeyUsageHex contains these keys:
+ // pub 1024R/2866382A created: 2014-04-01 expires: never usage: SC
+ // sub 1024R/936C9153 created: 2014-04-01 expires: never usage: E
+ // sub 1024R/64D5F5BB created: 2014-04-02 expires: never usage: E
+ // sub 1024D/BC0BA992 created: 2014-04-02 expires: never usage: S
+ certifiers := []uint64{0xA42704B92866382A}
+ signers := []uint64{0xA42704B92866382A, 0x42CE2C64BC0BA992}
+ encrypters := []uint64{0x09C0C7D9936C9153, 0xC104E98664D5F5BB}
+
+ for _, id := range certifiers {
+ keys := kring.KeysByIdUsage(id, packet.KeyFlagCertify)
+ if len(keys) == 1 {
+ if keys[0].PublicKey.KeyId != id {
+ t.Errorf("Expected to find certifier key id %X, but got %X", id, keys[0].PublicKey.KeyId)
+ }
+ } else {
+ t.Errorf("Expected one match for certifier key id %X, but got %d matches", id, len(keys))
+ }
+ }
+
+ for _, id := range signers {
+ keys := kring.KeysByIdUsage(id, packet.KeyFlagSign)
+ if len(keys) == 1 {
+ if keys[0].PublicKey.KeyId != id {
+ t.Errorf("Expected to find signing key id %X, but got %X", id, keys[0].PublicKey.KeyId)
+ }
+ } else {
+ t.Errorf("Expected one match for signing key id %X, but got %d matches", id, len(keys))
+ }
+
+ // This keyring contains no encryption keys that are also good for signing.
+ keys = kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications)
+ if len(keys) != 0 {
+ t.Errorf("Unexpected match for encryption key id %X", id)
+ }
+ }
+
+ for _, id := range encrypters {
+ keys := kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications)
+ if len(keys) == 1 {
+ if keys[0].PublicKey.KeyId != id {
+ t.Errorf("Expected to find encryption key id %X, but got %X", id, keys[0].PublicKey.KeyId)
+ }
+ } else {
+ t.Errorf("Expected one match for encryption key id %X, but got %d matches", id, len(keys))
+ }
+
+ // This keyring contains no encryption keys that are also good for signing.
+ keys = kring.KeysByIdUsage(id, packet.KeyFlagSign)
+ if len(keys) != 0 {
+ t.Errorf("Unexpected match for signing key id %X", id)
+ }
+ }
+}
+
+func TestIdVerification(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := kring[1].PrivateKey.Decrypt([]byte("passphrase")); err != nil {
+ t.Fatal(err)
+ }
+
+ const identity = "Test Key 1 (RSA)"
+ if err := kring[0].SignIdentity(identity, kring[1], nil); err != nil {
+ t.Fatal(err)
+ }
+
+ ident, ok := kring[0].Identities[identity]
+ if !ok {
+ t.Fatal("identity missing from key after signing")
+ }
+
+ checked := false
+ for _, sig := range ident.Signatures {
+ if sig.IssuerKeyId == nil || *sig.IssuerKeyId != kring[1].PrimaryKey.KeyId {
+ continue
+ }
+
+ if err := kring[1].PrimaryKey.VerifyUserIdSignature(identity, kring[0].PrimaryKey, sig); err != nil {
+ t.Fatalf("error verifying new identity signature: %s", err)
+ }
+ checked = true
+ break
+ }
+
+ if !checked {
+ t.Fatal("didn't find identity signature in Entity")
+ }
+}
+
+func TestNewEntityWithPreferredHash(t *testing.T) {
+ c := &packet.Config{
+ DefaultHash: crypto.SHA256,
+ }
+ entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", c)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, identity := range entity.Identities {
+ if len(identity.SelfSignature.PreferredHash) == 0 {
+ t.Fatal("didn't find a preferred hash in self signature")
+ }
+ ph := hashToHashId(c.DefaultHash)
+ if identity.SelfSignature.PreferredHash[0] != ph {
+ t.Fatalf("Expected preferred hash to be %d, got %d", ph, identity.SelfSignature.PreferredHash[0])
+ }
+ }
+}
+
+func TestNewEntityWithoutPreferredHash(t *testing.T) {
+ entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, identity := range entity.Identities {
+ if len(identity.SelfSignature.PreferredHash) != 0 {
+ t.Fatalf("Expected preferred hash to be empty but got length %d", len(identity.SelfSignature.PreferredHash))
+ }
+ }
+}
+
+func TestNewEntityCorrectName(t *testing.T) {
+ entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(entity.Identities) != 1 {
+ t.Fatalf("len(entity.Identities) = %d, want 1", len(entity.Identities))
+ }
+ var got string
+ for _, i := range entity.Identities {
+ got = i.Name
+ }
+ want := "Golang Gopher (Test Key) "
+ if got != want {
+ t.Fatalf("Identity.Name = %q, want %q", got, want)
+ }
+}
+
+func TestNewEntityWithPreferredSymmetric(t *testing.T) {
+ c := &packet.Config{
+ DefaultCipher: packet.CipherAES256,
+ }
+ entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", c)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, identity := range entity.Identities {
+ if len(identity.SelfSignature.PreferredSymmetric) == 0 {
+ t.Fatal("didn't find a preferred cipher in self signature")
+ }
+ if identity.SelfSignature.PreferredSymmetric[0] != uint8(c.DefaultCipher) {
+ t.Fatalf("Expected preferred cipher to be %d, got %d", uint8(c.DefaultCipher), identity.SelfSignature.PreferredSymmetric[0])
+ }
+ }
+}
+
+func TestNewEntityWithoutPreferredSymmetric(t *testing.T) {
+ entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, identity := range entity.Identities {
+ if len(identity.SelfSignature.PreferredSymmetric) != 0 {
+ t.Fatalf("Expected preferred cipher to be empty but got length %d", len(identity.SelfSignature.PreferredSymmetric))
+ }
+ }
+}
+
+func TestNewEntityPublicSerialization(t *testing.T) {
+ entity, err := NewEntity("Golang Gopher", "Test Key", "no-reply@golang.com", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ serializedEntity := bytes.NewBuffer(nil)
+ entity.Serialize(serializedEntity)
+
+ _, err = ReadEntity(packet.NewReader(bytes.NewBuffer(serializedEntity.Bytes())))
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/compressed.go b/local_crypto_patch/contents/openpgp/packet/compressed.go
new file mode 100644
index 0000000000..353f945247
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/compressed.go
@@ -0,0 +1,123 @@
+// 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 packet
+
+import (
+ "compress/bzip2"
+ "compress/flate"
+ "compress/zlib"
+ "golang.org/x/crypto/openpgp/errors"
+ "io"
+ "strconv"
+)
+
+// Compressed represents a compressed OpenPGP packet. The decompressed contents
+// will contain more OpenPGP packets. See RFC 4880, section 5.6.
+type Compressed struct {
+ Body io.Reader
+}
+
+const (
+ NoCompression = flate.NoCompression
+ BestSpeed = flate.BestSpeed
+ BestCompression = flate.BestCompression
+ DefaultCompression = flate.DefaultCompression
+)
+
+// CompressionConfig contains compressor configuration settings.
+type CompressionConfig struct {
+ // Level is the compression level to use. It must be set to
+ // between -1 and 9, with -1 causing the compressor to use the
+ // default compression level, 0 causing the compressor to use
+ // no compression and 1 to 9 representing increasing (better,
+ // slower) compression levels. If Level is less than -1 or
+ // more then 9, a non-nil error will be returned during
+ // encryption. See the constants above for convenient common
+ // settings for Level.
+ Level int
+}
+
+func (c *Compressed) parse(r io.Reader) error {
+ var buf [1]byte
+ _, err := readFull(r, buf[:])
+ if err != nil {
+ return err
+ }
+
+ switch buf[0] {
+ case 1:
+ c.Body = flate.NewReader(r)
+ case 2:
+ c.Body, err = zlib.NewReader(r)
+ case 3:
+ c.Body = bzip2.NewReader(r)
+ default:
+ err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0])))
+ }
+
+ return err
+}
+
+// compressedWriteCloser represents the serialized compression stream
+// header and the compressor. Its Close() method ensures that both the
+// compressor and serialized stream header are closed. Its Write()
+// method writes to the compressor.
+type compressedWriteCloser struct {
+ sh io.Closer // Stream Header
+ c io.WriteCloser // Compressor
+}
+
+func (cwc compressedWriteCloser) Write(p []byte) (int, error) {
+ return cwc.c.Write(p)
+}
+
+func (cwc compressedWriteCloser) Close() (err error) {
+ err = cwc.c.Close()
+ if err != nil {
+ return err
+ }
+
+ return cwc.sh.Close()
+}
+
+// SerializeCompressed serializes a compressed data packet to w and
+// returns a WriteCloser to which the literal data packets themselves
+// can be written and which MUST be closed on completion. If cc is
+// nil, sensible defaults will be used to configure the compression
+// algorithm.
+func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *CompressionConfig) (literaldata io.WriteCloser, err error) {
+ compressed, err := serializeStreamHeader(w, packetTypeCompressed)
+ if err != nil {
+ return
+ }
+
+ _, err = compressed.Write([]byte{uint8(algo)})
+ if err != nil {
+ return
+ }
+
+ level := DefaultCompression
+ if cc != nil {
+ level = cc.Level
+ }
+
+ var compressor io.WriteCloser
+ switch algo {
+ case CompressionZIP:
+ compressor, err = flate.NewWriter(compressed, level)
+ case CompressionZLIB:
+ compressor, err = zlib.NewWriterLevel(compressed, level)
+ default:
+ s := strconv.Itoa(int(algo))
+ err = errors.UnsupportedError("Unsupported compression algorithm: " + s)
+ }
+ if err != nil {
+ return
+ }
+
+ literaldata = compressedWriteCloser{compressed, compressor}
+
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/compressed_test.go b/local_crypto_patch/contents/openpgp/packet/compressed_test.go
new file mode 100644
index 0000000000..37fcc0b097
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/compressed_test.go
@@ -0,0 +1,40 @@
+// 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 packet
+
+import (
+ "bytes"
+ "encoding/hex"
+ "io"
+ "testing"
+)
+
+func TestCompressed(t *testing.T) {
+ packet, err := Read(readerFromHex(compressedHex))
+ if err != nil {
+ t.Errorf("failed to read Compressed: %s", err)
+ return
+ }
+
+ c, ok := packet.(*Compressed)
+ if !ok {
+ t.Error("didn't find Compressed packet")
+ return
+ }
+
+ contents, err := io.ReadAll(c.Body)
+ if err != nil && err != io.EOF {
+ t.Error(err)
+ return
+ }
+
+ expected, _ := hex.DecodeString(compressedExpectedHex)
+ if !bytes.Equal(expected, contents) {
+ t.Errorf("got:%x want:%x", contents, expected)
+ }
+}
+
+const compressedHex = "a3013b2d90c4e02b72e25f727e5e496a5e49b11e1700"
+const compressedExpectedHex = "cb1062004d14c8fe636f6e74656e74732e0a"
diff --git a/local_crypto_patch/contents/openpgp/packet/config.go b/local_crypto_patch/contents/openpgp/packet/config.go
new file mode 100644
index 0000000000..c76eecc963
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/config.go
@@ -0,0 +1,91 @@
+// 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 packet
+
+import (
+ "crypto"
+ "crypto/rand"
+ "io"
+ "time"
+)
+
+// Config collects a number of parameters along with sensible defaults.
+// A nil *Config is valid and results in all default values.
+type Config struct {
+ // Rand provides the source of entropy.
+ // If nil, the crypto/rand Reader is used.
+ Rand io.Reader
+ // DefaultHash is the default hash function to be used.
+ // If zero, SHA-256 is used.
+ DefaultHash crypto.Hash
+ // DefaultCipher is the cipher to be used.
+ // If zero, AES-128 is used.
+ DefaultCipher CipherFunction
+ // Time returns the current time as the number of seconds since the
+ // epoch. If Time is nil, time.Now is used.
+ Time func() time.Time
+ // DefaultCompressionAlgo is the compression algorithm to be
+ // applied to the plaintext before encryption. If zero, no
+ // compression is done.
+ DefaultCompressionAlgo CompressionAlgo
+ // CompressionConfig configures the compression settings.
+ CompressionConfig *CompressionConfig
+ // S2KCount is only used for symmetric encryption. It
+ // determines the strength of the passphrase stretching when
+ // the said passphrase is hashed to produce a key. S2KCount
+ // should be between 1024 and 65011712, inclusive. If Config
+ // is nil or S2KCount is 0, the value 65536 used. Not all
+ // values in the above range can be represented. S2KCount will
+ // be rounded up to the next representable value if it cannot
+ // be encoded exactly. When set, it is strongly encrouraged to
+ // use a value that is at least 65536. See RFC 4880 Section
+ // 3.7.1.3.
+ S2KCount int
+ // RSABits is the number of bits in new RSA keys made with NewEntity.
+ // If zero, then 2048 bit keys are created.
+ RSABits int
+}
+
+func (c *Config) Random() io.Reader {
+ if c == nil || c.Rand == nil {
+ return rand.Reader
+ }
+ return c.Rand
+}
+
+func (c *Config) Hash() crypto.Hash {
+ if c == nil || uint(c.DefaultHash) == 0 {
+ return crypto.SHA256
+ }
+ return c.DefaultHash
+}
+
+func (c *Config) Cipher() CipherFunction {
+ if c == nil || uint8(c.DefaultCipher) == 0 {
+ return CipherAES128
+ }
+ return c.DefaultCipher
+}
+
+func (c *Config) Now() time.Time {
+ if c == nil || c.Time == nil {
+ return time.Now()
+ }
+ return c.Time()
+}
+
+func (c *Config) Compression() CompressionAlgo {
+ if c == nil {
+ return CompressionNone
+ }
+ return c.DefaultCompressionAlgo
+}
+
+func (c *Config) PasswordHashIterations() int {
+ if c == nil || c.S2KCount == 0 {
+ return 0
+ }
+ return c.S2KCount
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/encrypted_key.go b/local_crypto_patch/contents/openpgp/packet/encrypted_key.go
new file mode 100644
index 0000000000..6d7639722c
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/encrypted_key.go
@@ -0,0 +1,208 @@
+// 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 packet
+
+import (
+ "crypto"
+ "crypto/rsa"
+ "encoding/binary"
+ "io"
+ "math/big"
+ "strconv"
+
+ "golang.org/x/crypto/openpgp/elgamal"
+ "golang.org/x/crypto/openpgp/errors"
+)
+
+const encryptedKeyVersion = 3
+
+// EncryptedKey represents a public-key encrypted session key. See RFC 4880,
+// section 5.1.
+type EncryptedKey struct {
+ KeyId uint64
+ Algo PublicKeyAlgorithm
+ CipherFunc CipherFunction // only valid after a successful Decrypt
+ Key []byte // only valid after a successful Decrypt
+
+ encryptedMPI1, encryptedMPI2 parsedMPI
+}
+
+func (e *EncryptedKey) parse(r io.Reader) (err error) {
+ var buf [10]byte
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+ if buf[0] != encryptedKeyVersion {
+ return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0])))
+ }
+ e.KeyId = binary.BigEndian.Uint64(buf[1:9])
+ e.Algo = PublicKeyAlgorithm(buf[9])
+ switch e.Algo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+ e.encryptedMPI1.bytes, e.encryptedMPI1.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ case PubKeyAlgoElGamal:
+ e.encryptedMPI1.bytes, e.encryptedMPI1.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ e.encryptedMPI2.bytes, e.encryptedMPI2.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ }
+ _, err = consumeAll(r)
+ return
+}
+
+func checksumKeyMaterial(key []byte) uint16 {
+ var checksum uint16
+ for _, v := range key {
+ checksum += uint16(v)
+ }
+ return checksum
+}
+
+// Decrypt decrypts an encrypted session key with the given private key. The
+// private key must have been decrypted first.
+// If config is nil, sensible defaults will be used.
+func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error {
+ var err error
+ var b []byte
+
+ // TODO(agl): use session key decryption routines here to avoid
+ // padding oracle attacks.
+ switch priv.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+ // Supports both *rsa.PrivateKey and crypto.Decrypter
+ k := priv.PrivateKey.(crypto.Decrypter)
+ b, err = k.Decrypt(config.Random(), padToKeySize(k.Public().(*rsa.PublicKey), e.encryptedMPI1.bytes), nil)
+ case PubKeyAlgoElGamal:
+ c1 := new(big.Int).SetBytes(e.encryptedMPI1.bytes)
+ c2 := new(big.Int).SetBytes(e.encryptedMPI2.bytes)
+ b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2)
+ default:
+ err = errors.InvalidArgumentError("cannot decrypted encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo)))
+ }
+
+ if err != nil {
+ return err
+ }
+
+ e.CipherFunc = CipherFunction(b[0])
+ e.Key = b[1 : len(b)-2]
+ expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
+ checksum := checksumKeyMaterial(e.Key)
+ if checksum != expectedChecksum {
+ return errors.StructuralError("EncryptedKey checksum incorrect")
+ }
+
+ return nil
+}
+
+// Serialize writes the encrypted key packet, e, to w.
+func (e *EncryptedKey) Serialize(w io.Writer) error {
+ var mpiLen int
+ switch e.Algo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+ mpiLen = 2 + len(e.encryptedMPI1.bytes)
+ case PubKeyAlgoElGamal:
+ mpiLen = 2 + len(e.encryptedMPI1.bytes) + 2 + len(e.encryptedMPI2.bytes)
+ default:
+ return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo)))
+ }
+
+ serializeHeader(w, packetTypeEncryptedKey, 1 /* version */ +8 /* key id */ +1 /* algo */ +mpiLen)
+
+ w.Write([]byte{encryptedKeyVersion})
+ binary.Write(w, binary.BigEndian, e.KeyId)
+ w.Write([]byte{byte(e.Algo)})
+
+ switch e.Algo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+ writeMPIs(w, e.encryptedMPI1)
+ case PubKeyAlgoElGamal:
+ writeMPIs(w, e.encryptedMPI1, e.encryptedMPI2)
+ default:
+ panic("internal error")
+ }
+
+ return nil
+}
+
+// SerializeEncryptedKey serializes an encrypted key packet to w that contains
+// key, encrypted to pub.
+// If config is nil, sensible defaults will be used.
+func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error {
+ var buf [10]byte
+ buf[0] = encryptedKeyVersion
+ binary.BigEndian.PutUint64(buf[1:9], pub.KeyId)
+ buf[9] = byte(pub.PubKeyAlgo)
+
+ keyBlock := make([]byte, 1 /* cipher type */ +len(key)+2 /* checksum */)
+ keyBlock[0] = byte(cipherFunc)
+ copy(keyBlock[1:], key)
+ checksum := checksumKeyMaterial(key)
+ keyBlock[1+len(key)] = byte(checksum >> 8)
+ keyBlock[1+len(key)+1] = byte(checksum)
+
+ switch pub.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+ return serializeEncryptedKeyRSA(w, config.Random(), buf, pub.PublicKey.(*rsa.PublicKey), keyBlock)
+ case PubKeyAlgoElGamal:
+ return serializeEncryptedKeyElGamal(w, config.Random(), buf, pub.PublicKey.(*elgamal.PublicKey), keyBlock)
+ case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly:
+ return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
+ }
+
+ return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
+}
+
+func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub *rsa.PublicKey, keyBlock []byte) error {
+ cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock)
+ if err != nil {
+ return errors.InvalidArgumentError("RSA encryption failed: " + err.Error())
+ }
+
+ packetLen := 10 /* header length */ + 2 /* mpi size */ + len(cipherText)
+
+ err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(header[:])
+ if err != nil {
+ return err
+ }
+ return writeMPI(w, 8*uint16(len(cipherText)), cipherText)
+}
+
+func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, pub *elgamal.PublicKey, keyBlock []byte) error {
+ c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock)
+ if err != nil {
+ return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error())
+ }
+
+ packetLen := 10 /* header length */
+ packetLen += 2 /* mpi size */ + (c1.BitLen()+7)/8
+ packetLen += 2 /* mpi size */ + (c2.BitLen()+7)/8
+
+ err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(header[:])
+ if err != nil {
+ return err
+ }
+ err = writeBig(w, c1)
+ if err != nil {
+ return err
+ }
+ return writeBig(w, c2)
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/encrypted_key_test.go b/local_crypto_patch/contents/openpgp/packet/encrypted_key_test.go
new file mode 100644
index 0000000000..ecb22bc2ba
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/encrypted_key_test.go
@@ -0,0 +1,220 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/rsa"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "math/big"
+ "testing"
+)
+
+func bigFromBase10(s string) *big.Int {
+ b, ok := new(big.Int).SetString(s, 10)
+ if !ok {
+ panic("bigFromBase10 failed")
+ }
+ return b
+}
+
+var encryptedKeyPub = rsa.PublicKey{
+ E: 65537,
+ N: bigFromBase10("115804063926007623305902631768113868327816898845124614648849934718568541074358183759250136204762053879858102352159854352727097033322663029387610959884180306668628526686121021235757016368038585212410610742029286439607686208110250133174279811431933746643015923132833417396844716207301518956640020862630546868823"),
+}
+
+var encryptedKeyRSAPriv = &rsa.PrivateKey{
+ PublicKey: encryptedKeyPub,
+ D: bigFromBase10("32355588668219869544751561565313228297765464314098552250409557267371233892496951383426602439009993875125222579159850054973310859166139474359774543943714622292329487391199285040721944491839695981199720170366763547754915493640685849961780092241140181198779299712578774460837139360803883139311171713302987058393"),
+}
+
+var encryptedKeyPriv = &PrivateKey{
+ PublicKey: PublicKey{
+ PubKeyAlgo: PubKeyAlgoRSA,
+ },
+ PrivateKey: encryptedKeyRSAPriv,
+}
+
+func TestDecryptingEncryptedKey(t *testing.T) {
+ for i, encryptedKeyHex := range []string{
+ "c18c032a67d68660df41c70104005789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8",
+ // MPI can be shorter than the length of the key.
+ "c18b032a67d68660df41c70103f8e520c52ae9807183c669ce26e772e482dc5d8cf60e6f59316e145be14d2e5221ee69550db1d5618a8cb002a719f1f0b9345bde21536d410ec90ba86cac37748dec7933eb7f9873873b2d61d3321d1cd44535014f6df58f7bc0c7afb5edc38e1a974428997d2f747f9a173bea9ca53079b409517d332df62d805564cffc9be6",
+ } {
+ const expectedKeyHex = "d930363f7e0308c333b9618617ea728963d8df993665ae7be1092d4926fd864b"
+
+ p, err := Read(readerFromHex(encryptedKeyHex))
+ if err != nil {
+ t.Errorf("#%d: error from Read: %s", i, err)
+ return
+ }
+ ek, ok := p.(*EncryptedKey)
+ if !ok {
+ t.Errorf("#%d: didn't parse an EncryptedKey, got %#v", i, p)
+ return
+ }
+
+ if ek.KeyId != 0x2a67d68660df41c7 || ek.Algo != PubKeyAlgoRSA {
+ t.Errorf("#%d: unexpected EncryptedKey contents: %#v", i, ek)
+ return
+ }
+
+ err = ek.Decrypt(encryptedKeyPriv, nil)
+ if err != nil {
+ t.Errorf("#%d: error from Decrypt: %s", i, err)
+ return
+ }
+
+ if ek.CipherFunc != CipherAES256 {
+ t.Errorf("#%d: unexpected EncryptedKey contents: %#v", i, ek)
+ return
+ }
+
+ keyHex := fmt.Sprintf("%x", ek.Key)
+ if keyHex != expectedKeyHex {
+ t.Errorf("#%d: bad key, got %s want %s", i, keyHex, expectedKeyHex)
+ }
+ }
+}
+
+type rsaDecrypter struct {
+ rsaPrivateKey *rsa.PrivateKey
+ decryptCount int
+}
+
+func (r *rsaDecrypter) Public() crypto.PublicKey {
+ return &r.rsaPrivateKey.PublicKey
+}
+
+func (r *rsaDecrypter) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) {
+ r.decryptCount++
+ return r.rsaPrivateKey.Decrypt(rand, msg, opts)
+}
+
+func TestRSADecrypter(t *testing.T) {
+ const encryptedKeyHex = "c18c032a67d68660df41c70104005789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8"
+
+ const expectedKeyHex = "d930363f7e0308c333b9618617ea728963d8df993665ae7be1092d4926fd864b"
+
+ p, err := Read(readerFromHex(encryptedKeyHex))
+ if err != nil {
+ t.Errorf("error from Read: %s", err)
+ return
+ }
+ ek, ok := p.(*EncryptedKey)
+ if !ok {
+ t.Errorf("didn't parse an EncryptedKey, got %#v", p)
+ return
+ }
+
+ if ek.KeyId != 0x2a67d68660df41c7 || ek.Algo != PubKeyAlgoRSA {
+ t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+ return
+ }
+
+ customDecrypter := &rsaDecrypter{
+ rsaPrivateKey: encryptedKeyRSAPriv,
+ }
+
+ customKeyPriv := &PrivateKey{
+ PublicKey: PublicKey{
+ PubKeyAlgo: PubKeyAlgoRSA,
+ },
+ PrivateKey: customDecrypter,
+ }
+
+ err = ek.Decrypt(customKeyPriv, nil)
+ if err != nil {
+ t.Errorf("error from Decrypt: %s", err)
+ return
+ }
+
+ if ek.CipherFunc != CipherAES256 {
+ t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+ return
+ }
+
+ keyHex := fmt.Sprintf("%x", ek.Key)
+ if keyHex != expectedKeyHex {
+ t.Errorf("bad key, got %s want %s", keyHex, expectedKeyHex)
+ }
+
+ if customDecrypter.decryptCount != 1 {
+ t.Errorf("Expected customDecrypter.Decrypt() to be called 1 time, but was called %d times", customDecrypter.decryptCount)
+ }
+}
+
+func TestEncryptingEncryptedKey(t *testing.T) {
+ key := []byte{1, 2, 3, 4}
+ const expectedKeyHex = "01020304"
+ const keyId = 42
+
+ pub := &PublicKey{
+ PublicKey: &encryptedKeyPub,
+ KeyId: keyId,
+ PubKeyAlgo: PubKeyAlgoRSAEncryptOnly,
+ }
+
+ buf := new(bytes.Buffer)
+ err := SerializeEncryptedKey(buf, pub, CipherAES128, key, nil)
+ if err != nil {
+ t.Errorf("error writing encrypted key packet: %s", err)
+ }
+
+ p, err := Read(buf)
+ if err != nil {
+ t.Errorf("error from Read: %s", err)
+ return
+ }
+ ek, ok := p.(*EncryptedKey)
+ if !ok {
+ t.Errorf("didn't parse an EncryptedKey, got %#v", p)
+ return
+ }
+
+ if ek.KeyId != keyId || ek.Algo != PubKeyAlgoRSAEncryptOnly {
+ t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+ return
+ }
+
+ err = ek.Decrypt(encryptedKeyPriv, nil)
+ if err != nil {
+ t.Errorf("error from Decrypt: %s", err)
+ return
+ }
+
+ if ek.CipherFunc != CipherAES128 {
+ t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+ return
+ }
+
+ keyHex := fmt.Sprintf("%x", ek.Key)
+ if keyHex != expectedKeyHex {
+ t.Errorf("bad key, got %s want %s", keyHex, expectedKeyHex)
+ }
+}
+
+func TestSerializingEncryptedKey(t *testing.T) {
+ const encryptedKeyHex = "c18c032a67d68660df41c70104005789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8"
+
+ p, err := Read(readerFromHex(encryptedKeyHex))
+ if err != nil {
+ t.Fatalf("error from Read: %s", err)
+ }
+ ek, ok := p.(*EncryptedKey)
+ if !ok {
+ t.Fatalf("didn't parse an EncryptedKey, got %#v", p)
+ }
+
+ var buf bytes.Buffer
+ ek.Serialize(&buf)
+
+ if bufHex := hex.EncodeToString(buf.Bytes()); bufHex != encryptedKeyHex {
+ t.Fatalf("serialization of encrypted key differed from original. Original was %s, but reserialized as %s", encryptedKeyHex, bufHex)
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/literal.go b/local_crypto_patch/contents/openpgp/packet/literal.go
new file mode 100644
index 0000000000..1a9ec6e51e
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/literal.go
@@ -0,0 +1,89 @@
+// 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 packet
+
+import (
+ "encoding/binary"
+ "io"
+)
+
+// LiteralData represents an encrypted file. See RFC 4880, section 5.9.
+type LiteralData struct {
+ IsBinary bool
+ FileName string
+ Time uint32 // Unix epoch time. Either creation time or modification time. 0 means undefined.
+ Body io.Reader
+}
+
+// ForEyesOnly returns whether the contents of the LiteralData have been marked
+// as especially sensitive.
+func (l *LiteralData) ForEyesOnly() bool {
+ return l.FileName == "_CONSOLE"
+}
+
+func (l *LiteralData) parse(r io.Reader) (err error) {
+ var buf [256]byte
+
+ _, err = readFull(r, buf[:2])
+ if err != nil {
+ return
+ }
+
+ l.IsBinary = buf[0] == 'b'
+ fileNameLen := int(buf[1])
+
+ _, err = readFull(r, buf[:fileNameLen])
+ if err != nil {
+ return
+ }
+
+ l.FileName = string(buf[:fileNameLen])
+
+ _, err = readFull(r, buf[:4])
+ if err != nil {
+ return
+ }
+
+ l.Time = binary.BigEndian.Uint32(buf[:4])
+ l.Body = r
+ return
+}
+
+// SerializeLiteral serializes a literal data packet to w and returns a
+// WriteCloser to which the data itself can be written and which MUST be closed
+// on completion. The fileName is truncated to 255 bytes.
+func SerializeLiteral(w io.WriteCloser, isBinary bool, fileName string, time uint32) (plaintext io.WriteCloser, err error) {
+ var buf [4]byte
+ buf[0] = 't'
+ if isBinary {
+ buf[0] = 'b'
+ }
+ if len(fileName) > 255 {
+ fileName = fileName[:255]
+ }
+ buf[1] = byte(len(fileName))
+
+ inner, err := serializeStreamHeader(w, packetTypeLiteralData)
+ if err != nil {
+ return
+ }
+
+ _, err = inner.Write(buf[:2])
+ if err != nil {
+ return
+ }
+ _, err = inner.Write([]byte(fileName))
+ if err != nil {
+ return
+ }
+ binary.BigEndian.PutUint32(buf[:], time)
+ _, err = inner.Write(buf[:])
+ if err != nil {
+ return
+ }
+
+ plaintext = inner
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/ocfb.go b/local_crypto_patch/contents/openpgp/packet/ocfb.go
new file mode 100644
index 0000000000..ce2a33a547
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/ocfb.go
@@ -0,0 +1,143 @@
+// 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.
+
+// OpenPGP CFB Mode. http://tools.ietf.org/html/rfc4880#section-13.9
+
+package packet
+
+import (
+ "crypto/cipher"
+)
+
+type ocfbEncrypter struct {
+ b cipher.Block
+ fre []byte
+ outUsed int
+}
+
+// An OCFBResyncOption determines if the "resynchronization step" of OCFB is
+// performed.
+type OCFBResyncOption bool
+
+const (
+ OCFBResync OCFBResyncOption = true
+ OCFBNoResync OCFBResyncOption = false
+)
+
+// NewOCFBEncrypter returns a cipher.Stream which encrypts data with OpenPGP's
+// cipher feedback mode using the given cipher.Block, and an initial amount of
+// ciphertext. randData must be random bytes and be the same length as the
+// cipher.Block's block size. Resync determines if the "resynchronization step"
+// from RFC 4880, 13.9 step 7 is performed. Different parts of OpenPGP vary on
+// this point.
+func NewOCFBEncrypter(block cipher.Block, randData []byte, resync OCFBResyncOption) (cipher.Stream, []byte) {
+ blockSize := block.BlockSize()
+ if len(randData) != blockSize {
+ return nil, nil
+ }
+
+ x := &ocfbEncrypter{
+ b: block,
+ fre: make([]byte, blockSize),
+ outUsed: 0,
+ }
+ prefix := make([]byte, blockSize+2)
+
+ block.Encrypt(x.fre, x.fre)
+ for i := 0; i < blockSize; i++ {
+ prefix[i] = randData[i] ^ x.fre[i]
+ }
+
+ block.Encrypt(x.fre, prefix[:blockSize])
+ prefix[blockSize] = x.fre[0] ^ randData[blockSize-2]
+ prefix[blockSize+1] = x.fre[1] ^ randData[blockSize-1]
+
+ if resync {
+ block.Encrypt(x.fre, prefix[2:])
+ } else {
+ x.fre[0] = prefix[blockSize]
+ x.fre[1] = prefix[blockSize+1]
+ x.outUsed = 2
+ }
+ return x, prefix
+}
+
+func (x *ocfbEncrypter) XORKeyStream(dst, src []byte) {
+ for i := 0; i < len(src); i++ {
+ if x.outUsed == len(x.fre) {
+ x.b.Encrypt(x.fre, x.fre)
+ x.outUsed = 0
+ }
+
+ x.fre[x.outUsed] ^= src[i]
+ dst[i] = x.fre[x.outUsed]
+ x.outUsed++
+ }
+}
+
+type ocfbDecrypter struct {
+ b cipher.Block
+ fre []byte
+ outUsed int
+}
+
+// NewOCFBDecrypter returns a cipher.Stream which decrypts data with OpenPGP's
+// cipher feedback mode using the given cipher.Block. Prefix must be the first
+// blockSize + 2 bytes of the ciphertext, where blockSize is the cipher.Block's
+// block size. If an incorrect key is detected then nil is returned. On
+// successful exit, blockSize+2 bytes of decrypted data are written into
+// prefix. Resync determines if the "resynchronization step" from RFC 4880,
+// 13.9 step 7 is performed. Different parts of OpenPGP vary on this point.
+func NewOCFBDecrypter(block cipher.Block, prefix []byte, resync OCFBResyncOption) cipher.Stream {
+ blockSize := block.BlockSize()
+ if len(prefix) != blockSize+2 {
+ return nil
+ }
+
+ x := &ocfbDecrypter{
+ b: block,
+ fre: make([]byte, blockSize),
+ outUsed: 0,
+ }
+ prefixCopy := make([]byte, len(prefix))
+ copy(prefixCopy, prefix)
+
+ block.Encrypt(x.fre, x.fre)
+ for i := 0; i < blockSize; i++ {
+ prefixCopy[i] ^= x.fre[i]
+ }
+
+ block.Encrypt(x.fre, prefix[:blockSize])
+ prefixCopy[blockSize] ^= x.fre[0]
+ prefixCopy[blockSize+1] ^= x.fre[1]
+
+ if prefixCopy[blockSize-2] != prefixCopy[blockSize] ||
+ prefixCopy[blockSize-1] != prefixCopy[blockSize+1] {
+ return nil
+ }
+
+ if resync {
+ block.Encrypt(x.fre, prefix[2:])
+ } else {
+ x.fre[0] = prefix[blockSize]
+ x.fre[1] = prefix[blockSize+1]
+ x.outUsed = 2
+ }
+ copy(prefix, prefixCopy)
+ return x
+}
+
+func (x *ocfbDecrypter) XORKeyStream(dst, src []byte) {
+ for i := 0; i < len(src); i++ {
+ if x.outUsed == len(x.fre) {
+ x.b.Encrypt(x.fre, x.fre)
+ x.outUsed = 0
+ }
+
+ c := src[i]
+ dst[i] = x.fre[x.outUsed] ^ src[i]
+ x.fre[x.outUsed] = c
+ x.outUsed++
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/ocfb_test.go b/local_crypto_patch/contents/openpgp/packet/ocfb_test.go
new file mode 100644
index 0000000000..91022c042d
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/ocfb_test.go
@@ -0,0 +1,46 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/rand"
+ "testing"
+)
+
+var commonKey128 = []byte{0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c}
+
+func testOCFB(t *testing.T, resync OCFBResyncOption) {
+ block, err := aes.NewCipher(commonKey128)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ plaintext := []byte("this is the plaintext, which is long enough to span several blocks.")
+ randData := make([]byte, block.BlockSize())
+ rand.Reader.Read(randData)
+ ocfb, prefix := NewOCFBEncrypter(block, randData, resync)
+ ciphertext := make([]byte, len(plaintext))
+ ocfb.XORKeyStream(ciphertext, plaintext)
+
+ ocfbdec := NewOCFBDecrypter(block, prefix, resync)
+ if ocfbdec == nil {
+ t.Errorf("NewOCFBDecrypter failed (resync: %t)", resync)
+ return
+ }
+ plaintextCopy := make([]byte, len(plaintext))
+ ocfbdec.XORKeyStream(plaintextCopy, ciphertext)
+
+ if !bytes.Equal(plaintextCopy, plaintext) {
+ t.Errorf("got: %x, want: %x (resync: %t)", plaintextCopy, plaintext, resync)
+ }
+}
+
+func TestOCFB(t *testing.T) {
+ testOCFB(t, OCFBNoResync)
+ testOCFB(t, OCFBResync)
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/one_pass_signature.go b/local_crypto_patch/contents/openpgp/packet/one_pass_signature.go
new file mode 100644
index 0000000000..1713503395
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/one_pass_signature.go
@@ -0,0 +1,73 @@
+// 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 packet
+
+import (
+ "crypto"
+ "encoding/binary"
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/s2k"
+ "io"
+ "strconv"
+)
+
+// OnePassSignature represents a one-pass signature packet. See RFC 4880,
+// section 5.4.
+type OnePassSignature struct {
+ SigType SignatureType
+ Hash crypto.Hash
+ PubKeyAlgo PublicKeyAlgorithm
+ KeyId uint64
+ IsLast bool
+}
+
+const onePassSignatureVersion = 3
+
+func (ops *OnePassSignature) parse(r io.Reader) (err error) {
+ var buf [13]byte
+
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+ if buf[0] != onePassSignatureVersion {
+ err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
+ }
+
+ var ok bool
+ ops.Hash, ok = s2k.HashIdToHash(buf[2])
+ if !ok {
+ return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
+ }
+
+ ops.SigType = SignatureType(buf[1])
+ ops.PubKeyAlgo = PublicKeyAlgorithm(buf[3])
+ ops.KeyId = binary.BigEndian.Uint64(buf[4:12])
+ ops.IsLast = buf[12] != 0
+ return
+}
+
+// Serialize marshals the given OnePassSignature to w.
+func (ops *OnePassSignature) Serialize(w io.Writer) error {
+ var buf [13]byte
+ buf[0] = onePassSignatureVersion
+ buf[1] = uint8(ops.SigType)
+ var ok bool
+ buf[2], ok = s2k.HashToHashId(ops.Hash)
+ if !ok {
+ return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
+ }
+ buf[3] = uint8(ops.PubKeyAlgo)
+ binary.BigEndian.PutUint64(buf[4:12], ops.KeyId)
+ if ops.IsLast {
+ buf[12] = 1
+ }
+
+ if err := serializeHeader(w, packetTypeOnePassSignature, len(buf)); err != nil {
+ return err
+ }
+ _, err := w.Write(buf[:])
+ return err
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/opaque.go b/local_crypto_patch/contents/openpgp/packet/opaque.go
new file mode 100644
index 0000000000..3984477310
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/opaque.go
@@ -0,0 +1,161 @@
+// 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 packet
+
+import (
+ "bytes"
+ "io"
+
+ "golang.org/x/crypto/openpgp/errors"
+)
+
+// OpaquePacket represents an OpenPGP packet as raw, unparsed data. This is
+// useful for splitting and storing the original packet contents separately,
+// handling unsupported packet types or accessing parts of the packet not yet
+// implemented by this package.
+type OpaquePacket struct {
+ // Packet type
+ Tag uint8
+ // Reason why the packet was parsed opaquely
+ Reason error
+ // Binary contents of the packet data
+ Contents []byte
+}
+
+func (op *OpaquePacket) parse(r io.Reader) (err error) {
+ op.Contents, err = io.ReadAll(r)
+ return
+}
+
+// Serialize marshals the packet to a writer in its original form, including
+// the packet header.
+func (op *OpaquePacket) Serialize(w io.Writer) (err error) {
+ err = serializeHeader(w, packetType(op.Tag), len(op.Contents))
+ if err == nil {
+ _, err = w.Write(op.Contents)
+ }
+ return
+}
+
+// Parse attempts to parse the opaque contents into a structure supported by
+// this package. If the packet is not known then the result will be another
+// OpaquePacket.
+func (op *OpaquePacket) Parse() (p Packet, err error) {
+ hdr := bytes.NewBuffer(nil)
+ err = serializeHeader(hdr, packetType(op.Tag), len(op.Contents))
+ if err != nil {
+ op.Reason = err
+ return op, err
+ }
+ p, err = Read(io.MultiReader(hdr, bytes.NewBuffer(op.Contents)))
+ if err != nil {
+ op.Reason = err
+ p = op
+ }
+ return
+}
+
+// OpaqueReader reads OpaquePackets from an io.Reader.
+type OpaqueReader struct {
+ r io.Reader
+}
+
+func NewOpaqueReader(r io.Reader) *OpaqueReader {
+ return &OpaqueReader{r: r}
+}
+
+// Read the next OpaquePacket.
+func (or *OpaqueReader) Next() (op *OpaquePacket, err error) {
+ tag, _, contents, err := readHeader(or.r)
+ if err != nil {
+ return
+ }
+ op = &OpaquePacket{Tag: uint8(tag), Reason: err}
+ err = op.parse(contents)
+ if err != nil {
+ consumeAll(contents)
+ }
+ return
+}
+
+// OpaqueSubpacket represents an unparsed OpenPGP subpacket,
+// as found in signature and user attribute packets.
+type OpaqueSubpacket struct {
+ SubType uint8
+ Contents []byte
+}
+
+// OpaqueSubpackets extracts opaque, unparsed OpenPGP subpackets from
+// their byte representation.
+func OpaqueSubpackets(contents []byte) (result []*OpaqueSubpacket, err error) {
+ var (
+ subHeaderLen int
+ subPacket *OpaqueSubpacket
+ )
+ for len(contents) > 0 {
+ subHeaderLen, subPacket, err = nextSubpacket(contents)
+ if err != nil {
+ break
+ }
+ result = append(result, subPacket)
+ contents = contents[subHeaderLen+len(subPacket.Contents):]
+ }
+ return
+}
+
+func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacket, err error) {
+ // RFC 4880, section 5.2.3.1
+ var subLen uint32
+ if len(contents) < 1 {
+ goto Truncated
+ }
+ subPacket = &OpaqueSubpacket{}
+ switch {
+ case contents[0] < 192:
+ subHeaderLen = 2 // 1 length byte, 1 subtype byte
+ if len(contents) < subHeaderLen {
+ goto Truncated
+ }
+ subLen = uint32(contents[0])
+ contents = contents[1:]
+ case contents[0] < 255:
+ subHeaderLen = 3 // 2 length bytes, 1 subtype
+ if len(contents) < subHeaderLen {
+ goto Truncated
+ }
+ subLen = uint32(contents[0]-192)<<8 + uint32(contents[1]) + 192
+ contents = contents[2:]
+ default:
+ subHeaderLen = 6 // 5 length bytes, 1 subtype
+ if len(contents) < subHeaderLen {
+ goto Truncated
+ }
+ subLen = uint32(contents[1])<<24 |
+ uint32(contents[2])<<16 |
+ uint32(contents[3])<<8 |
+ uint32(contents[4])
+ contents = contents[5:]
+ }
+ if subLen > uint32(len(contents)) || subLen == 0 {
+ goto Truncated
+ }
+ subPacket.SubType = contents[0]
+ subPacket.Contents = contents[1:subLen]
+ return
+Truncated:
+ err = errors.StructuralError("subpacket truncated")
+ return
+}
+
+func (osp *OpaqueSubpacket) Serialize(w io.Writer) (err error) {
+ buf := make([]byte, 6)
+ n := serializeSubpacketLength(buf, len(osp.Contents)+1)
+ buf[n] = osp.SubType
+ if _, err = w.Write(buf[:n+1]); err != nil {
+ return
+ }
+ _, err = w.Write(osp.Contents)
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/opaque_test.go b/local_crypto_patch/contents/openpgp/packet/opaque_test.go
new file mode 100644
index 0000000000..06a3bbf019
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/opaque_test.go
@@ -0,0 +1,67 @@
+// 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 packet
+
+import (
+ "bytes"
+ "encoding/hex"
+ "io"
+ "testing"
+)
+
+// Test packet.Read error handling in OpaquePacket.Parse,
+// which attempts to re-read an OpaquePacket as a supported
+// Packet type.
+func TestOpaqueParseReason(t *testing.T) {
+ buf, err := hex.DecodeString(UnsupportedKeyHex)
+ if err != nil {
+ t.Fatal(err)
+ }
+ or := NewOpaqueReader(bytes.NewBuffer(buf))
+ count := 0
+ badPackets := 0
+ var uid *UserId
+ for {
+ op, err := or.Next()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ t.Errorf("#%d: opaque read error: %v", count, err)
+ break
+ }
+ // try to parse opaque packet
+ p, _ := op.Parse()
+ switch pkt := p.(type) {
+ case *UserId:
+ uid = pkt
+ case *OpaquePacket:
+ // If an OpaquePacket can't re-parse, packet.Read
+ // certainly had its reasons.
+ if pkt.Reason == nil {
+ t.Errorf("#%d: opaque packet, no reason", count)
+ } else {
+ badPackets++
+ }
+ }
+ count++
+ }
+
+ const expectedBad = 3
+ // Test post-conditions, make sure we actually parsed packets as expected.
+ if badPackets != expectedBad {
+ t.Errorf("unexpected # unparsable packets: %d (want %d)", badPackets, expectedBad)
+ }
+ if uid == nil {
+ t.Errorf("failed to find expected UID in unsupported keyring")
+ } else if uid.Id != "Armin M. Warda " {
+ t.Errorf("unexpected UID: %v", uid.Id)
+ }
+}
+
+// This key material has public key and signature packet versions modified to
+// an unsupported value (1), so that trying to parse the OpaquePacket to
+// a typed packet will get an error. It also contains a GnuPG trust packet.
+// (Created with: od -An -t x1 pubring.gpg | xargs | sed 's/ //g')
+const UnsupportedKeyHex = `988d012e7a18a20000010400d6ac00d92b89c1f4396c243abb9b76d2e9673ad63483291fed88e22b82e255e441c078c6abbbf7d2d195e50b62eeaa915b85b0ec20c225ce2c64c167cacb6e711daf2e45da4a8356a059b8160e3b3628ac0dd8437b31f06d53d6e8ea4214d4a26406a6b63e1001406ef23e0bb3069fac9a99a91f77dfafd5de0f188a5da5e3c9000511b42741726d696e204d2e205761726461203c7761726461406e657068696c696d2e727568722e64653e8900950105102e8936c705d1eb399e58489901013f0e03ff5a0c4f421e34fcfa388129166420c08cd76987bcdec6f01bd0271459a85cc22048820dd4e44ac2c7d23908d540f54facf1b36b0d9c20488781ce9dca856531e76e2e846826e9951338020a03a09b57aa5faa82e9267458bd76105399885ac35af7dc1cbb6aaed7c39e1039f3b5beda2c0e916bd38560509bab81235d1a0ead83b0020000`
diff --git a/local_crypto_patch/contents/openpgp/packet/packet.go b/local_crypto_patch/contents/openpgp/packet/packet.go
new file mode 100644
index 0000000000..a84a1a214e
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/packet.go
@@ -0,0 +1,590 @@
+// 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 packet implements parsing and serialization of OpenPGP packets, as
+// specified in RFC 4880.
+//
+// Deprecated: this package is unmaintained except for security fixes. New
+// applications should consider a more focused, modern alternative to OpenPGP
+// for their specific task. If you are required to interoperate with OpenPGP
+// systems and need a maintained package, consider a community fork.
+// See https://golang.org/issue/44226.
+package packet
+
+import (
+ "bufio"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/des"
+ "crypto/rsa"
+ "io"
+ "math/big"
+ "math/bits"
+
+ "golang.org/x/crypto/cast5"
+ "golang.org/x/crypto/openpgp/errors"
+)
+
+// readFull is the same as io.ReadFull except that reading zero bytes returns
+// ErrUnexpectedEOF rather than EOF.
+func readFull(r io.Reader, buf []byte) (n int, err error) {
+ n, err = io.ReadFull(r, buf)
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
+
+// readLength reads an OpenPGP length from r. See RFC 4880, section 4.2.2.
+func readLength(r io.Reader) (length int64, isPartial bool, err error) {
+ var buf [4]byte
+ _, err = readFull(r, buf[:1])
+ if err != nil {
+ return
+ }
+ switch {
+ case buf[0] < 192:
+ length = int64(buf[0])
+ case buf[0] < 224:
+ length = int64(buf[0]-192) << 8
+ _, err = readFull(r, buf[0:1])
+ if err != nil {
+ return
+ }
+ length += int64(buf[0]) + 192
+ case buf[0] < 255:
+ length = int64(1) << (buf[0] & 0x1f)
+ isPartial = true
+ default:
+ _, err = readFull(r, buf[0:4])
+ if err != nil {
+ return
+ }
+ length = int64(buf[0])<<24 |
+ int64(buf[1])<<16 |
+ int64(buf[2])<<8 |
+ int64(buf[3])
+ }
+ return
+}
+
+// partialLengthReader wraps an io.Reader and handles OpenPGP partial lengths.
+// The continuation lengths are parsed and removed from the stream and EOF is
+// returned at the end of the packet. See RFC 4880, section 4.2.2.4.
+type partialLengthReader struct {
+ r io.Reader
+ remaining int64
+ isPartial bool
+}
+
+func (r *partialLengthReader) Read(p []byte) (n int, err error) {
+ for r.remaining == 0 {
+ if !r.isPartial {
+ return 0, io.EOF
+ }
+ r.remaining, r.isPartial, err = readLength(r.r)
+ if err != nil {
+ return 0, err
+ }
+ }
+
+ toRead := int64(len(p))
+ if toRead > r.remaining {
+ toRead = r.remaining
+ }
+
+ n, err = r.r.Read(p[:int(toRead)])
+ r.remaining -= int64(n)
+ if n < int(toRead) && err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
+
+// partialLengthWriter writes a stream of data using OpenPGP partial lengths.
+// See RFC 4880, section 4.2.2.4.
+type partialLengthWriter struct {
+ w io.WriteCloser
+ lengthByte [1]byte
+ sentFirst bool
+ buf []byte
+}
+
+// RFC 4880 4.2.2.4: the first partial length MUST be at least 512 octets long.
+const minFirstPartialWrite = 512
+
+func (w *partialLengthWriter) Write(p []byte) (n int, err error) {
+ off := 0
+ if !w.sentFirst {
+ if len(w.buf) > 0 || len(p) < minFirstPartialWrite {
+ off = len(w.buf)
+ w.buf = append(w.buf, p...)
+ if len(w.buf) < minFirstPartialWrite {
+ return len(p), nil
+ }
+ p = w.buf
+ w.buf = nil
+ }
+ w.sentFirst = true
+ }
+
+ power := uint8(30)
+ for len(p) > 0 {
+ l := 1 << power
+ if len(p) < l {
+ power = uint8(bits.Len32(uint32(len(p)))) - 1
+ l = 1 << power
+ }
+ w.lengthByte[0] = 224 + power
+ _, err = w.w.Write(w.lengthByte[:])
+ if err == nil {
+ var m int
+ m, err = w.w.Write(p[:l])
+ n += m
+ }
+ if err != nil {
+ if n < off {
+ return 0, err
+ }
+ return n - off, err
+ }
+ p = p[l:]
+ }
+ return n - off, nil
+}
+
+func (w *partialLengthWriter) Close() error {
+ if len(w.buf) > 0 {
+ // In this case we can't send a 512 byte packet.
+ // Just send what we have.
+ p := w.buf
+ w.sentFirst = true
+ w.buf = nil
+ if _, err := w.Write(p); err != nil {
+ return err
+ }
+ }
+
+ w.lengthByte[0] = 0
+ _, err := w.w.Write(w.lengthByte[:])
+ if err != nil {
+ return err
+ }
+ return w.w.Close()
+}
+
+// A spanReader is an io.LimitReader, but it returns ErrUnexpectedEOF if the
+// underlying Reader returns EOF before the limit has been reached.
+type spanReader struct {
+ r io.Reader
+ n int64
+}
+
+func (l *spanReader) Read(p []byte) (n int, err error) {
+ if l.n <= 0 {
+ return 0, io.EOF
+ }
+ if int64(len(p)) > l.n {
+ p = p[0:l.n]
+ }
+ n, err = l.r.Read(p)
+ l.n -= int64(n)
+ if l.n > 0 && err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
+
+// readHeader parses a packet header and returns an io.Reader which will return
+// the contents of the packet. See RFC 4880, section 4.2.
+func readHeader(r io.Reader) (tag packetType, length int64, contents io.Reader, err error) {
+ var buf [4]byte
+ _, err = io.ReadFull(r, buf[:1])
+ if err != nil {
+ return
+ }
+ if buf[0]&0x80 == 0 {
+ err = errors.StructuralError("tag byte does not have MSB set")
+ return
+ }
+ if buf[0]&0x40 == 0 {
+ // Old format packet
+ tag = packetType((buf[0] & 0x3f) >> 2)
+ lengthType := buf[0] & 3
+ if lengthType == 3 {
+ length = -1
+ contents = r
+ return
+ }
+ lengthBytes := 1 << lengthType
+ _, err = readFull(r, buf[0:lengthBytes])
+ if err != nil {
+ return
+ }
+ for i := 0; i < lengthBytes; i++ {
+ length <<= 8
+ length |= int64(buf[i])
+ }
+ contents = &spanReader{r, length}
+ return
+ }
+
+ // New format packet
+ tag = packetType(buf[0] & 0x3f)
+ length, isPartial, err := readLength(r)
+ if err != nil {
+ return
+ }
+ if isPartial {
+ contents = &partialLengthReader{
+ remaining: length,
+ isPartial: true,
+ r: r,
+ }
+ length = -1
+ } else {
+ contents = &spanReader{r, length}
+ }
+ return
+}
+
+// serializeHeader writes an OpenPGP packet header to w. See RFC 4880, section
+// 4.2.
+func serializeHeader(w io.Writer, ptype packetType, length int) (err error) {
+ var buf [6]byte
+ var n int
+
+ buf[0] = 0x80 | 0x40 | byte(ptype)
+ if length < 192 {
+ buf[1] = byte(length)
+ n = 2
+ } else if length < 8384 {
+ length -= 192
+ buf[1] = 192 + byte(length>>8)
+ buf[2] = byte(length)
+ n = 3
+ } else {
+ buf[1] = 255
+ buf[2] = byte(length >> 24)
+ buf[3] = byte(length >> 16)
+ buf[4] = byte(length >> 8)
+ buf[5] = byte(length)
+ n = 6
+ }
+
+ _, err = w.Write(buf[:n])
+ return
+}
+
+// serializeStreamHeader writes an OpenPGP packet header to w where the
+// length of the packet is unknown. It returns a io.WriteCloser which can be
+// used to write the contents of the packet. See RFC 4880, section 4.2.
+func serializeStreamHeader(w io.WriteCloser, ptype packetType) (out io.WriteCloser, err error) {
+ var buf [1]byte
+ buf[0] = 0x80 | 0x40 | byte(ptype)
+ _, err = w.Write(buf[:])
+ if err != nil {
+ return
+ }
+ out = &partialLengthWriter{w: w}
+ return
+}
+
+// Packet represents an OpenPGP packet. Users are expected to try casting
+// instances of this interface to specific packet types.
+type Packet interface {
+ parse(io.Reader) error
+}
+
+// consumeAll reads from the given Reader until error, returning the number of
+// bytes read.
+func consumeAll(r io.Reader) (n int64, err error) {
+ var m int
+ var buf [1024]byte
+
+ for {
+ m, err = r.Read(buf[:])
+ n += int64(m)
+ if err == io.EOF {
+ err = nil
+ return
+ }
+ if err != nil {
+ return
+ }
+ }
+}
+
+// packetType represents the numeric ids of the different OpenPGP packet types. See
+// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-2
+type packetType uint8
+
+const (
+ packetTypeEncryptedKey packetType = 1
+ packetTypeSignature packetType = 2
+ packetTypeSymmetricKeyEncrypted packetType = 3
+ packetTypeOnePassSignature packetType = 4
+ packetTypePrivateKey packetType = 5
+ packetTypePublicKey packetType = 6
+ packetTypePrivateSubkey packetType = 7
+ packetTypeCompressed packetType = 8
+ packetTypeSymmetricallyEncrypted packetType = 9
+ packetTypeLiteralData packetType = 11
+ packetTypeUserId packetType = 13
+ packetTypePublicSubkey packetType = 14
+ packetTypeUserAttribute packetType = 17
+ packetTypeSymmetricallyEncryptedMDC packetType = 18
+)
+
+// peekVersion detects the version of a public key packet about to
+// be read. A bufio.Reader at the original position of the io.Reader
+// is returned.
+func peekVersion(r io.Reader) (bufr *bufio.Reader, ver byte, err error) {
+ bufr = bufio.NewReader(r)
+ var verBuf []byte
+ if verBuf, err = bufr.Peek(1); err != nil {
+ return
+ }
+ ver = verBuf[0]
+ return
+}
+
+// Read reads a single OpenPGP packet from the given io.Reader. If there is an
+// error parsing a packet, the whole packet is consumed from the input.
+func Read(r io.Reader) (p Packet, err error) {
+ tag, _, contents, err := readHeader(r)
+ if err != nil {
+ return
+ }
+
+ switch tag {
+ case packetTypeEncryptedKey:
+ p = new(EncryptedKey)
+ case packetTypeSignature:
+ var version byte
+ // Detect signature version
+ if contents, version, err = peekVersion(contents); err != nil {
+ return
+ }
+ if version < 4 {
+ p = new(SignatureV3)
+ } else {
+ p = new(Signature)
+ }
+ case packetTypeSymmetricKeyEncrypted:
+ p = new(SymmetricKeyEncrypted)
+ case packetTypeOnePassSignature:
+ p = new(OnePassSignature)
+ case packetTypePrivateKey, packetTypePrivateSubkey:
+ pk := new(PrivateKey)
+ if tag == packetTypePrivateSubkey {
+ pk.IsSubkey = true
+ }
+ p = pk
+ case packetTypePublicKey, packetTypePublicSubkey:
+ var version byte
+ if contents, version, err = peekVersion(contents); err != nil {
+ return
+ }
+ isSubkey := tag == packetTypePublicSubkey
+ if version < 4 {
+ p = &PublicKeyV3{IsSubkey: isSubkey}
+ } else {
+ p = &PublicKey{IsSubkey: isSubkey}
+ }
+ case packetTypeCompressed:
+ p = new(Compressed)
+ case packetTypeSymmetricallyEncrypted:
+ p = new(SymmetricallyEncrypted)
+ case packetTypeLiteralData:
+ p = new(LiteralData)
+ case packetTypeUserId:
+ p = new(UserId)
+ case packetTypeUserAttribute:
+ p = new(UserAttribute)
+ case packetTypeSymmetricallyEncryptedMDC:
+ se := new(SymmetricallyEncrypted)
+ se.MDC = true
+ p = se
+ default:
+ err = errors.UnknownPacketTypeError(tag)
+ }
+ if p != nil {
+ err = p.parse(contents)
+ }
+ if err != nil {
+ consumeAll(contents)
+ }
+ return
+}
+
+// SignatureType represents the different semantic meanings of an OpenPGP
+// signature. See RFC 4880, section 5.2.1.
+type SignatureType uint8
+
+const (
+ SigTypeBinary SignatureType = 0
+ SigTypeText = 1
+ SigTypeGenericCert = 0x10
+ SigTypePersonaCert = 0x11
+ SigTypeCasualCert = 0x12
+ SigTypePositiveCert = 0x13
+ SigTypeSubkeyBinding = 0x18
+ SigTypePrimaryKeyBinding = 0x19
+ SigTypeDirectSignature = 0x1F
+ SigTypeKeyRevocation = 0x20
+ SigTypeSubkeyRevocation = 0x28
+)
+
+// PublicKeyAlgorithm represents the different public key system specified for
+// OpenPGP. See
+// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-12
+type PublicKeyAlgorithm uint8
+
+const (
+ PubKeyAlgoRSA PublicKeyAlgorithm = 1
+ PubKeyAlgoElGamal PublicKeyAlgorithm = 16
+ PubKeyAlgoDSA PublicKeyAlgorithm = 17
+ // RFC 6637, Section 5.
+ PubKeyAlgoECDH PublicKeyAlgorithm = 18
+ PubKeyAlgoECDSA PublicKeyAlgorithm = 19
+
+ // Deprecated in RFC 4880, Section 13.5. Use key flags instead.
+ PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2
+ PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3
+)
+
+// CanEncrypt returns true if it's possible to encrypt a message to a public
+// key of the given type.
+func (pka PublicKeyAlgorithm) CanEncrypt() bool {
+ switch pka {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal:
+ return true
+ }
+ return false
+}
+
+// CanSign returns true if it's possible for a public key of the given type to
+// sign a message.
+func (pka PublicKeyAlgorithm) CanSign() bool {
+ switch pka {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA:
+ return true
+ }
+ return false
+}
+
+// CipherFunction represents the different block ciphers specified for OpenPGP. See
+// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13
+type CipherFunction uint8
+
+const (
+ Cipher3DES CipherFunction = 2
+ CipherCAST5 CipherFunction = 3
+ CipherAES128 CipherFunction = 7
+ CipherAES192 CipherFunction = 8
+ CipherAES256 CipherFunction = 9
+)
+
+// KeySize returns the key size, in bytes, of cipher.
+func (cipher CipherFunction) KeySize() int {
+ switch cipher {
+ case Cipher3DES:
+ return 24
+ case CipherCAST5:
+ return cast5.KeySize
+ case CipherAES128:
+ return 16
+ case CipherAES192:
+ return 24
+ case CipherAES256:
+ return 32
+ }
+ return 0
+}
+
+// blockSize returns the block size, in bytes, of cipher.
+func (cipher CipherFunction) blockSize() int {
+ switch cipher {
+ case Cipher3DES:
+ return des.BlockSize
+ case CipherCAST5:
+ return 8
+ case CipherAES128, CipherAES192, CipherAES256:
+ return 16
+ }
+ return 0
+}
+
+// new returns a fresh instance of the given cipher.
+func (cipher CipherFunction) new(key []byte) (block cipher.Block) {
+ switch cipher {
+ case Cipher3DES:
+ block, _ = des.NewTripleDESCipher(key)
+ case CipherCAST5:
+ block, _ = cast5.NewCipher(key)
+ case CipherAES128, CipherAES192, CipherAES256:
+ block, _ = aes.NewCipher(key)
+ }
+ return
+}
+
+// readMPI reads a big integer from r. The bit length returned is the bit
+// length that was specified in r. This is preserved so that the integer can be
+// reserialized exactly.
+func readMPI(r io.Reader) (mpi []byte, bitLength uint16, err error) {
+ var buf [2]byte
+ _, err = readFull(r, buf[0:])
+ if err != nil {
+ return
+ }
+ bitLength = uint16(buf[0])<<8 | uint16(buf[1])
+ numBytes := (int(bitLength) + 7) / 8
+ mpi = make([]byte, numBytes)
+ _, err = readFull(r, mpi)
+ // According to RFC 4880 3.2. we should check that the MPI has no leading
+ // zeroes (at least when not an encrypted MPI?), but this implementation
+ // does generate leading zeroes, so we keep accepting them.
+ return
+}
+
+// writeMPI serializes a big integer to w.
+func writeMPI(w io.Writer, bitLength uint16, mpiBytes []byte) (err error) {
+ // Note that we can produce leading zeroes, in violation of RFC 4880 3.2.
+ // Implementations seem to be tolerant of them, and stripping them would
+ // make it complex to guarantee matching re-serialization.
+ _, err = w.Write([]byte{byte(bitLength >> 8), byte(bitLength)})
+ if err == nil {
+ _, err = w.Write(mpiBytes)
+ }
+ return
+}
+
+// writeBig serializes a *big.Int to w.
+func writeBig(w io.Writer, i *big.Int) error {
+ return writeMPI(w, uint16(i.BitLen()), i.Bytes())
+}
+
+// padToKeySize left-pads a MPI with zeroes to match the length of the
+// specified RSA public.
+func padToKeySize(pub *rsa.PublicKey, b []byte) []byte {
+ k := (pub.N.BitLen() + 7) / 8
+ if len(b) >= k {
+ return b
+ }
+ bb := make([]byte, k)
+ copy(bb[len(bb)-len(b):], b)
+ return bb
+}
+
+// CompressionAlgo Represents the different compression algorithms
+// supported by OpenPGP (except for BZIP2, which is not currently
+// supported). See Section 9.3 of RFC 4880.
+type CompressionAlgo uint8
+
+const (
+ CompressionNone CompressionAlgo = 0
+ CompressionZIP CompressionAlgo = 1
+ CompressionZLIB CompressionAlgo = 2
+)
diff --git a/local_crypto_patch/contents/openpgp/packet/packet_test.go b/local_crypto_patch/contents/openpgp/packet/packet_test.go
new file mode 100644
index 0000000000..c8fc4e50c0
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/packet_test.go
@@ -0,0 +1,290 @@
+// 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 packet
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "golang.org/x/crypto/openpgp/errors"
+ "io"
+ "testing"
+)
+
+func TestReadFull(t *testing.T) {
+ var out [4]byte
+
+ b := bytes.NewBufferString("foo")
+ n, err := readFull(b, out[:3])
+ if n != 3 || err != nil {
+ t.Errorf("full read failed n:%d err:%s", n, err)
+ }
+
+ b = bytes.NewBufferString("foo")
+ n, err = readFull(b, out[:4])
+ if n != 3 || err != io.ErrUnexpectedEOF {
+ t.Errorf("partial read failed n:%d err:%s", n, err)
+ }
+
+ b = bytes.NewBuffer(nil)
+ n, err = readFull(b, out[:3])
+ if n != 0 || err != io.ErrUnexpectedEOF {
+ t.Errorf("empty read failed n:%d err:%s", n, err)
+ }
+}
+
+func readerFromHex(s string) io.Reader {
+ data, err := hex.DecodeString(s)
+ if err != nil {
+ panic("readerFromHex: bad input")
+ }
+ return bytes.NewBuffer(data)
+}
+
+var readLengthTests = []struct {
+ hexInput string
+ length int64
+ isPartial bool
+ err error
+}{
+ {"", 0, false, io.ErrUnexpectedEOF},
+ {"1f", 31, false, nil},
+ {"c0", 0, false, io.ErrUnexpectedEOF},
+ {"c101", 256 + 1 + 192, false, nil},
+ {"e0", 1, true, nil},
+ {"e1", 2, true, nil},
+ {"e2", 4, true, nil},
+ {"ff", 0, false, io.ErrUnexpectedEOF},
+ {"ff00", 0, false, io.ErrUnexpectedEOF},
+ {"ff0000", 0, false, io.ErrUnexpectedEOF},
+ {"ff000000", 0, false, io.ErrUnexpectedEOF},
+ {"ff00000000", 0, false, nil},
+ {"ff01020304", 16909060, false, nil},
+}
+
+func TestReadLength(t *testing.T) {
+ for i, test := range readLengthTests {
+ length, isPartial, err := readLength(readerFromHex(test.hexInput))
+ if test.err != nil {
+ if err != test.err {
+ t.Errorf("%d: expected different error got:%s want:%s", i, err, test.err)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("%d: unexpected error: %s", i, err)
+ continue
+ }
+ if length != test.length || isPartial != test.isPartial {
+ t.Errorf("%d: bad result got:(%d,%t) want:(%d,%t)", i, length, isPartial, test.length, test.isPartial)
+ }
+ }
+}
+
+var partialLengthReaderTests = []struct {
+ hexInput string
+ err error
+ hexOutput string
+}{
+ {"e0", io.ErrUnexpectedEOF, ""},
+ {"e001", io.ErrUnexpectedEOF, ""},
+ {"e0010102", nil, "0102"},
+ {"ff00000000", nil, ""},
+ {"e10102e1030400", nil, "01020304"},
+ {"e101", io.ErrUnexpectedEOF, ""},
+}
+
+func TestPartialLengthReader(t *testing.T) {
+ for i, test := range partialLengthReaderTests {
+ r := &partialLengthReader{readerFromHex(test.hexInput), 0, true}
+ out, err := io.ReadAll(r)
+ if test.err != nil {
+ if err != test.err {
+ t.Errorf("%d: expected different error got:%s want:%s", i, err, test.err)
+ }
+ continue
+ }
+ if err != nil {
+ t.Errorf("%d: unexpected error: %s", i, err)
+ continue
+ }
+
+ got := fmt.Sprintf("%x", out)
+ if got != test.hexOutput {
+ t.Errorf("%d: got:%s want:%s", i, test.hexOutput, got)
+ }
+ }
+}
+
+var readHeaderTests = []struct {
+ hexInput string
+ structuralError bool
+ unexpectedEOF bool
+ tag int
+ length int64
+ hexOutput string
+}{
+ {"", false, false, 0, 0, ""},
+ {"7f", true, false, 0, 0, ""},
+
+ // Old format headers
+ {"80", false, true, 0, 0, ""},
+ {"8001", false, true, 0, 1, ""},
+ {"800102", false, false, 0, 1, "02"},
+ {"81000102", false, false, 0, 1, "02"},
+ {"820000000102", false, false, 0, 1, "02"},
+ {"860000000102", false, false, 1, 1, "02"},
+ {"83010203", false, false, 0, -1, "010203"},
+
+ // New format headers
+ {"c0", false, true, 0, 0, ""},
+ {"c000", false, false, 0, 0, ""},
+ {"c00102", false, false, 0, 1, "02"},
+ {"c0020203", false, false, 0, 2, "0203"},
+ {"c00202", false, true, 0, 2, ""},
+ {"c3020203", false, false, 3, 2, "0203"},
+}
+
+func TestReadHeader(t *testing.T) {
+ for i, test := range readHeaderTests {
+ tag, length, contents, err := readHeader(readerFromHex(test.hexInput))
+ if test.structuralError {
+ if _, ok := err.(errors.StructuralError); ok {
+ continue
+ }
+ t.Errorf("%d: expected StructuralError, got:%s", i, err)
+ continue
+ }
+ if err != nil {
+ if len(test.hexInput) == 0 && err == io.EOF {
+ continue
+ }
+ if !test.unexpectedEOF || err != io.ErrUnexpectedEOF {
+ t.Errorf("%d: unexpected error from readHeader: %s", i, err)
+ }
+ continue
+ }
+ if int(tag) != test.tag || length != test.length {
+ t.Errorf("%d: got:(%d,%d) want:(%d,%d)", i, int(tag), length, test.tag, test.length)
+ continue
+ }
+
+ body, err := io.ReadAll(contents)
+ if err != nil {
+ if !test.unexpectedEOF || err != io.ErrUnexpectedEOF {
+ t.Errorf("%d: unexpected error from contents: %s", i, err)
+ }
+ continue
+ }
+ if test.unexpectedEOF {
+ t.Errorf("%d: expected ErrUnexpectedEOF from contents but got no error", i)
+ continue
+ }
+ got := fmt.Sprintf("%x", body)
+ if got != test.hexOutput {
+ t.Errorf("%d: got:%s want:%s", i, got, test.hexOutput)
+ }
+ }
+}
+
+func TestSerializeHeader(t *testing.T) {
+ tag := packetTypePublicKey
+ lengths := []int{0, 1, 2, 64, 192, 193, 8000, 8384, 8385, 10000}
+
+ for _, length := range lengths {
+ buf := bytes.NewBuffer(nil)
+ serializeHeader(buf, tag, length)
+ tag2, length2, _, err := readHeader(buf)
+ if err != nil {
+ t.Errorf("length %d, err: %s", length, err)
+ }
+ if tag2 != tag {
+ t.Errorf("length %d, tag incorrect (got %d, want %d)", length, tag2, tag)
+ }
+ if int(length2) != length {
+ t.Errorf("length %d, length incorrect (got %d)", length, length2)
+ }
+ }
+}
+
+func TestPartialLengths(t *testing.T) {
+ buf := bytes.NewBuffer(nil)
+ w := new(partialLengthWriter)
+ w.w = noOpCloser{buf}
+
+ const maxChunkSize = 64
+
+ var b [maxChunkSize]byte
+ var n uint8
+ for l := 1; l <= maxChunkSize; l++ {
+ for i := 0; i < l; i++ {
+ b[i] = n
+ n++
+ }
+ m, err := w.Write(b[:l])
+ if m != l {
+ t.Errorf("short write got: %d want: %d", m, l)
+ }
+ if err != nil {
+ t.Errorf("error from write: %s", err)
+ }
+ }
+ if err := w.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ // The first packet should be at least 512 bytes.
+ first, err := buf.ReadByte()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if plen := 1 << (first & 0x1f); plen < 512 {
+ t.Errorf("first packet too short: got %d want at least %d", plen, 512)
+ }
+ if err := buf.UnreadByte(); err != nil {
+ t.Fatal(err)
+ }
+
+ want := (maxChunkSize * (maxChunkSize + 1)) / 2
+ copyBuf := bytes.NewBuffer(nil)
+ r := &partialLengthReader{buf, 0, true}
+ m, err := io.Copy(copyBuf, r)
+ if m != int64(want) {
+ t.Errorf("short copy got: %d want: %d", m, want)
+ }
+ if err != nil {
+ t.Errorf("error from copy: %s", err)
+ }
+
+ copyBytes := copyBuf.Bytes()
+ for i := 0; i < want; i++ {
+ if copyBytes[i] != uint8(i) {
+ t.Errorf("bad pattern in copy at %d", i)
+ break
+ }
+ }
+}
+
+func TestPartialLengthsShortWrite(t *testing.T) {
+ buf := bytes.NewBuffer(nil)
+ w := &partialLengthWriter{
+ w: noOpCloser{buf},
+ }
+ data := bytes.Repeat([]byte("a"), 510)
+ if _, err := w.Write(data); err != nil {
+ t.Fatal(err)
+ }
+ if err := w.Close(); err != nil {
+ t.Fatal(err)
+ }
+ copyBuf := bytes.NewBuffer(nil)
+ r := &partialLengthReader{buf, 0, true}
+ if _, err := io.Copy(copyBuf, r); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(copyBuf.Bytes(), data) {
+ t.Errorf("got %q want %q", buf.Bytes(), data)
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/private_key.go b/local_crypto_patch/contents/openpgp/packet/private_key.go
new file mode 100644
index 0000000000..192aac376d
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/private_key.go
@@ -0,0 +1,384 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/cipher"
+ "crypto/dsa"
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "crypto/sha1"
+ "io"
+ "math/big"
+ "strconv"
+ "time"
+
+ "golang.org/x/crypto/openpgp/elgamal"
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/s2k"
+)
+
+// PrivateKey represents a possibly encrypted private key. See RFC 4880,
+// section 5.5.3.
+type PrivateKey struct {
+ PublicKey
+ Encrypted bool // if true then the private key is unavailable until Decrypt has been called.
+ encryptedData []byte
+ cipher CipherFunction
+ s2k func(out, in []byte)
+ PrivateKey interface{} // An *{rsa|dsa|ecdsa}.PrivateKey or crypto.Signer/crypto.Decrypter (Decryptor RSA only).
+ sha1Checksum bool
+ iv []byte
+}
+
+func NewRSAPrivateKey(creationTime time.Time, priv *rsa.PrivateKey) *PrivateKey {
+ pk := new(PrivateKey)
+ pk.PublicKey = *NewRSAPublicKey(creationTime, &priv.PublicKey)
+ pk.PrivateKey = priv
+ return pk
+}
+
+func NewDSAPrivateKey(creationTime time.Time, priv *dsa.PrivateKey) *PrivateKey {
+ pk := new(PrivateKey)
+ pk.PublicKey = *NewDSAPublicKey(creationTime, &priv.PublicKey)
+ pk.PrivateKey = priv
+ return pk
+}
+
+func NewElGamalPrivateKey(creationTime time.Time, priv *elgamal.PrivateKey) *PrivateKey {
+ pk := new(PrivateKey)
+ pk.PublicKey = *NewElGamalPublicKey(creationTime, &priv.PublicKey)
+ pk.PrivateKey = priv
+ return pk
+}
+
+func NewECDSAPrivateKey(creationTime time.Time, priv *ecdsa.PrivateKey) *PrivateKey {
+ pk := new(PrivateKey)
+ pk.PublicKey = *NewECDSAPublicKey(creationTime, &priv.PublicKey)
+ pk.PrivateKey = priv
+ return pk
+}
+
+// NewSignerPrivateKey creates a PrivateKey from a crypto.Signer that
+// implements RSA or ECDSA.
+func NewSignerPrivateKey(creationTime time.Time, signer crypto.Signer) *PrivateKey {
+ pk := new(PrivateKey)
+ // In general, the public Keys should be used as pointers. We still
+ // type-switch on the values, for backwards-compatibility.
+ switch pubkey := signer.Public().(type) {
+ case *rsa.PublicKey:
+ pk.PublicKey = *NewRSAPublicKey(creationTime, pubkey)
+ case rsa.PublicKey:
+ pk.PublicKey = *NewRSAPublicKey(creationTime, &pubkey)
+ case *ecdsa.PublicKey:
+ pk.PublicKey = *NewECDSAPublicKey(creationTime, pubkey)
+ case ecdsa.PublicKey:
+ pk.PublicKey = *NewECDSAPublicKey(creationTime, &pubkey)
+ default:
+ panic("openpgp: unknown crypto.Signer type in NewSignerPrivateKey")
+ }
+ pk.PrivateKey = signer
+ return pk
+}
+
+func (pk *PrivateKey) parse(r io.Reader) (err error) {
+ err = (&pk.PublicKey).parse(r)
+ if err != nil {
+ return
+ }
+ var buf [1]byte
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+
+ s2kType := buf[0]
+
+ switch s2kType {
+ case 0:
+ pk.s2k = nil
+ pk.Encrypted = false
+ case 254, 255:
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+ pk.cipher = CipherFunction(buf[0])
+ pk.Encrypted = true
+ pk.s2k, err = s2k.Parse(r)
+ if err != nil {
+ return
+ }
+ if s2kType == 254 {
+ pk.sha1Checksum = true
+ }
+ default:
+ return errors.UnsupportedError("deprecated s2k function in private key")
+ }
+
+ if pk.Encrypted {
+ blockSize := pk.cipher.blockSize()
+ if blockSize == 0 {
+ return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher)))
+ }
+ pk.iv = make([]byte, blockSize)
+ _, err = readFull(r, pk.iv)
+ if err != nil {
+ return
+ }
+ }
+
+ pk.encryptedData, err = io.ReadAll(r)
+ if err != nil {
+ return
+ }
+
+ if !pk.Encrypted {
+ return pk.parsePrivateKey(pk.encryptedData)
+ }
+
+ return
+}
+
+func mod64kHash(d []byte) uint16 {
+ var h uint16
+ for _, b := range d {
+ h += uint16(b)
+ }
+ return h
+}
+
+func (pk *PrivateKey) Serialize(w io.Writer) (err error) {
+ // TODO(agl): support encrypted private keys
+ buf := bytes.NewBuffer(nil)
+ err = pk.PublicKey.serializeWithoutHeaders(buf)
+ if err != nil {
+ return
+ }
+ buf.WriteByte(0 /* no encryption */)
+
+ privateKeyBuf := bytes.NewBuffer(nil)
+
+ switch priv := pk.PrivateKey.(type) {
+ case *rsa.PrivateKey:
+ err = serializeRSAPrivateKey(privateKeyBuf, priv)
+ case *dsa.PrivateKey:
+ err = serializeDSAPrivateKey(privateKeyBuf, priv)
+ case *elgamal.PrivateKey:
+ err = serializeElGamalPrivateKey(privateKeyBuf, priv)
+ case *ecdsa.PrivateKey:
+ err = serializeECDSAPrivateKey(privateKeyBuf, priv)
+ default:
+ err = errors.InvalidArgumentError("unknown private key type")
+ }
+ if err != nil {
+ return
+ }
+
+ ptype := packetTypePrivateKey
+ contents := buf.Bytes()
+ privateKeyBytes := privateKeyBuf.Bytes()
+ if pk.IsSubkey {
+ ptype = packetTypePrivateSubkey
+ }
+ err = serializeHeader(w, ptype, len(contents)+len(privateKeyBytes)+2)
+ if err != nil {
+ return
+ }
+ _, err = w.Write(contents)
+ if err != nil {
+ return
+ }
+ _, err = w.Write(privateKeyBytes)
+ if err != nil {
+ return
+ }
+
+ checksum := mod64kHash(privateKeyBytes)
+ var checksumBytes [2]byte
+ checksumBytes[0] = byte(checksum >> 8)
+ checksumBytes[1] = byte(checksum)
+ _, err = w.Write(checksumBytes[:])
+
+ return
+}
+
+func serializeRSAPrivateKey(w io.Writer, priv *rsa.PrivateKey) error {
+ err := writeBig(w, priv.D)
+ if err != nil {
+ return err
+ }
+ err = writeBig(w, priv.Primes[1])
+ if err != nil {
+ return err
+ }
+ err = writeBig(w, priv.Primes[0])
+ if err != nil {
+ return err
+ }
+ return writeBig(w, priv.Precomputed.Qinv)
+}
+
+func serializeDSAPrivateKey(w io.Writer, priv *dsa.PrivateKey) error {
+ return writeBig(w, priv.X)
+}
+
+func serializeElGamalPrivateKey(w io.Writer, priv *elgamal.PrivateKey) error {
+ return writeBig(w, priv.X)
+}
+
+func serializeECDSAPrivateKey(w io.Writer, priv *ecdsa.PrivateKey) error {
+ return writeBig(w, priv.D)
+}
+
+// Decrypt decrypts an encrypted private key using a passphrase.
+func (pk *PrivateKey) Decrypt(passphrase []byte) error {
+ if !pk.Encrypted {
+ return nil
+ }
+
+ key := make([]byte, pk.cipher.KeySize())
+ pk.s2k(key, passphrase)
+ block := pk.cipher.new(key)
+ cfb := cipher.NewCFBDecrypter(block, pk.iv)
+
+ data := make([]byte, len(pk.encryptedData))
+ cfb.XORKeyStream(data, pk.encryptedData)
+
+ if pk.sha1Checksum {
+ if len(data) < sha1.Size {
+ return errors.StructuralError("truncated private key data")
+ }
+ h := sha1.New()
+ h.Write(data[:len(data)-sha1.Size])
+ sum := h.Sum(nil)
+ if !bytes.Equal(sum, data[len(data)-sha1.Size:]) {
+ return errors.StructuralError("private key checksum failure")
+ }
+ data = data[:len(data)-sha1.Size]
+ } else {
+ if len(data) < 2 {
+ return errors.StructuralError("truncated private key data")
+ }
+ var sum uint16
+ for i := 0; i < len(data)-2; i++ {
+ sum += uint16(data[i])
+ }
+ if data[len(data)-2] != uint8(sum>>8) ||
+ data[len(data)-1] != uint8(sum) {
+ return errors.StructuralError("private key checksum failure")
+ }
+ data = data[:len(data)-2]
+ }
+
+ return pk.parsePrivateKey(data)
+}
+
+func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) {
+ switch pk.PublicKey.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoRSAEncryptOnly:
+ return pk.parseRSAPrivateKey(data)
+ case PubKeyAlgoDSA:
+ return pk.parseDSAPrivateKey(data)
+ case PubKeyAlgoElGamal:
+ return pk.parseElGamalPrivateKey(data)
+ case PubKeyAlgoECDSA:
+ return pk.parseECDSAPrivateKey(data)
+ }
+ panic("impossible")
+}
+
+func (pk *PrivateKey) parseRSAPrivateKey(data []byte) (err error) {
+ rsaPub := pk.PublicKey.PublicKey.(*rsa.PublicKey)
+ rsaPriv := new(rsa.PrivateKey)
+ rsaPriv.PublicKey = *rsaPub
+
+ buf := bytes.NewBuffer(data)
+ d, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+ p, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+ q, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+
+ rsaPriv.D = new(big.Int).SetBytes(d)
+ rsaPriv.Primes = make([]*big.Int, 2)
+ rsaPriv.Primes[0] = new(big.Int).SetBytes(p)
+ rsaPriv.Primes[1] = new(big.Int).SetBytes(q)
+ if err := rsaPriv.Validate(); err != nil {
+ return err
+ }
+ rsaPriv.Precompute()
+ pk.PrivateKey = rsaPriv
+ pk.Encrypted = false
+ pk.encryptedData = nil
+
+ return nil
+}
+
+func (pk *PrivateKey) parseDSAPrivateKey(data []byte) (err error) {
+ dsaPub := pk.PublicKey.PublicKey.(*dsa.PublicKey)
+ dsaPriv := new(dsa.PrivateKey)
+ dsaPriv.PublicKey = *dsaPub
+
+ buf := bytes.NewBuffer(data)
+ x, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+
+ dsaPriv.X = new(big.Int).SetBytes(x)
+ pk.PrivateKey = dsaPriv
+ pk.Encrypted = false
+ pk.encryptedData = nil
+
+ return nil
+}
+
+func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) {
+ pub := pk.PublicKey.PublicKey.(*elgamal.PublicKey)
+ priv := new(elgamal.PrivateKey)
+ priv.PublicKey = *pub
+
+ buf := bytes.NewBuffer(data)
+ x, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+
+ priv.X = new(big.Int).SetBytes(x)
+ pk.PrivateKey = priv
+ pk.Encrypted = false
+ pk.encryptedData = nil
+
+ return nil
+}
+
+func (pk *PrivateKey) parseECDSAPrivateKey(data []byte) (err error) {
+ ecdsaPub := pk.PublicKey.PublicKey.(*ecdsa.PublicKey)
+
+ buf := bytes.NewBuffer(data)
+ d, _, err := readMPI(buf)
+ if err != nil {
+ return
+ }
+
+ pk.PrivateKey = &ecdsa.PrivateKey{
+ PublicKey: *ecdsaPub,
+ D: new(big.Int).SetBytes(d),
+ }
+ pk.Encrypted = false
+ pk.encryptedData = nil
+
+ return nil
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/private_key_test.go b/local_crypto_patch/contents/openpgp/packet/private_key_test.go
new file mode 100644
index 0000000000..cc08b48371
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/private_key_test.go
@@ -0,0 +1,249 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/hex"
+ "hash"
+ "testing"
+ "time"
+)
+
+var privateKeyTests = []struct {
+ privateKeyHex string
+ creationTime time.Time
+}{
+ {
+ privKeyRSAHex,
+ time.Unix(0x4cc349a8, 0),
+ },
+ {
+ privKeyElGamalHex,
+ time.Unix(0x4df9ee1a, 0),
+ },
+}
+
+func TestPrivateKeyRead(t *testing.T) {
+ for i, test := range privateKeyTests {
+ packet, err := Read(readerFromHex(test.privateKeyHex))
+ if err != nil {
+ t.Errorf("#%d: failed to parse: %s", i, err)
+ continue
+ }
+
+ privKey := packet.(*PrivateKey)
+
+ if !privKey.Encrypted {
+ t.Errorf("#%d: private key isn't encrypted", i)
+ continue
+ }
+
+ err = privKey.Decrypt([]byte("wrong password"))
+ if err == nil {
+ t.Errorf("#%d: decrypted with incorrect key", i)
+ continue
+ }
+
+ err = privKey.Decrypt([]byte("testing"))
+ if err != nil {
+ t.Errorf("#%d: failed to decrypt: %s", i, err)
+ continue
+ }
+
+ if !privKey.CreationTime.Equal(test.creationTime) || privKey.Encrypted {
+ t.Errorf("#%d: bad result, got: %#v", i, privKey)
+ }
+ }
+}
+
+func populateHash(hashFunc crypto.Hash, msg []byte) (hash.Hash, error) {
+ h := hashFunc.New()
+ if _, err := h.Write(msg); err != nil {
+ return nil, err
+ }
+ return h, nil
+}
+
+func TestRSAPrivateKey(t *testing.T) {
+ privKeyDER, _ := hex.DecodeString(pkcs1PrivKeyHex)
+ rsaPriv, err := x509.ParsePKCS1PrivateKey(privKeyDER)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var buf bytes.Buffer
+ if err := NewRSAPrivateKey(time.Now(), rsaPriv).Serialize(&buf); err != nil {
+ t.Fatal(err)
+ }
+
+ p, err := Read(&buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ priv, ok := p.(*PrivateKey)
+ if !ok {
+ t.Fatal("didn't parse private key")
+ }
+
+ sig := &Signature{
+ PubKeyAlgo: PubKeyAlgoRSA,
+ Hash: crypto.SHA256,
+ }
+ msg := []byte("Hello World!")
+
+ h, err := populateHash(sig.Hash, msg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := sig.Sign(h, priv, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ if h, err = populateHash(sig.Hash, msg); err != nil {
+ t.Fatal(err)
+ }
+ if err := priv.VerifySignature(h, sig); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestECDSAPrivateKey(t *testing.T) {
+ ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var buf bytes.Buffer
+ if err := NewECDSAPrivateKey(time.Now(), ecdsaPriv).Serialize(&buf); err != nil {
+ t.Fatal(err)
+ }
+
+ p, err := Read(&buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ priv, ok := p.(*PrivateKey)
+ if !ok {
+ t.Fatal("didn't parse private key")
+ }
+
+ sig := &Signature{
+ PubKeyAlgo: PubKeyAlgoECDSA,
+ Hash: crypto.SHA256,
+ }
+ msg := []byte("Hello World!")
+
+ h, err := populateHash(sig.Hash, msg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := sig.Sign(h, priv, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ if h, err = populateHash(sig.Hash, msg); err != nil {
+ t.Fatal(err)
+ }
+ if err := priv.VerifySignature(h, sig); err != nil {
+ t.Fatal(err)
+ }
+}
+
+type rsaSigner struct {
+ *rsa.PrivateKey
+}
+
+func TestRSASignerPrivateKey(t *testing.T) {
+ rsaPriv, err := rsa.GenerateKey(rand.Reader, 1024)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ priv := NewSignerPrivateKey(time.Now(), &rsaSigner{rsaPriv})
+
+ sig := &Signature{
+ PubKeyAlgo: PubKeyAlgoRSA,
+ Hash: crypto.SHA256,
+ }
+ msg := []byte("Hello World!")
+
+ h, err := populateHash(sig.Hash, msg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := sig.Sign(h, priv, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ if h, err = populateHash(sig.Hash, msg); err != nil {
+ t.Fatal(err)
+ }
+ if err := priv.VerifySignature(h, sig); err != nil {
+ t.Fatal(err)
+ }
+}
+
+type ecdsaSigner struct {
+ *ecdsa.PrivateKey
+}
+
+func TestECDSASignerPrivateKey(t *testing.T) {
+ ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ priv := NewSignerPrivateKey(time.Now(), &ecdsaSigner{ecdsaPriv})
+
+ if priv.PubKeyAlgo != PubKeyAlgoECDSA {
+ t.Fatal("NewSignerPrivateKey should have made an ECSDA private key")
+ }
+
+ sig := &Signature{
+ PubKeyAlgo: PubKeyAlgoECDSA,
+ Hash: crypto.SHA256,
+ }
+ msg := []byte("Hello World!")
+
+ h, err := populateHash(sig.Hash, msg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := sig.Sign(h, priv, nil); err != nil {
+ t.Fatal(err)
+ }
+
+ if h, err = populateHash(sig.Hash, msg); err != nil {
+ t.Fatal(err)
+ }
+ if err := priv.VerifySignature(h, sig); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestIssue11505(t *testing.T) {
+ // parsing a rsa private key with p or q == 1 used to panic due to a divide by zero
+ _, _ = Read(readerFromHex("9c3004303030300100000011303030000000000000010130303030303030303030303030303030303030303030303030303030303030303030303030303030303030"))
+}
+
+// Generated with `gpg --export-secret-keys "Test Key 2"`
+const privKeyRSAHex = "9501fe044cc349a8010400b70ca0010e98c090008d45d1ee8f9113bd5861fd57b88bacb7c68658747663f1e1a3b5a98f32fda6472373c024b97359cd2efc88ff60f77751adfbf6af5e615e6a1408cfad8bf0cea30b0d5f53aa27ad59089ba9b15b7ebc2777a25d7b436144027e3bcd203909f147d0e332b240cf63d3395f5dfe0df0a6c04e8655af7eacdf0011010001fe0303024a252e7d475fd445607de39a265472aa74a9320ba2dac395faa687e9e0336aeb7e9a7397e511b5afd9dc84557c80ac0f3d4d7bfec5ae16f20d41c8c84a04552a33870b930420e230e179564f6d19bb153145e76c33ae993886c388832b0fa042ddda7f133924f3854481533e0ede31d51278c0519b29abc3bf53da673e13e3e1214b52413d179d7f66deee35cac8eacb060f78379d70ef4af8607e68131ff529439668fc39c9ce6dfef8a5ac234d234802cbfb749a26107db26406213ae5c06d4673253a3cbee1fcbae58d6ab77e38d6e2c0e7c6317c48e054edadb5a40d0d48acb44643d998139a8a66bb820be1f3f80185bc777d14b5954b60effe2448a036d565c6bc0b915fcea518acdd20ab07bc1529f561c58cd044f723109b93f6fd99f876ff891d64306b5d08f48bab59f38695e9109c4dec34013ba3153488ce070268381ba923ee1eb77125b36afcb4347ec3478c8f2735b06ef17351d872e577fa95d0c397c88c71b59629a36aec"
+
+// Generated by `gpg --export-secret-keys` followed by a manual extraction of
+// the ElGamal subkey from the packets.
+const privKeyElGamalHex = "9d0157044df9ee1a100400eb8e136a58ec39b582629cdadf830bc64e0a94ed8103ca8bb247b27b11b46d1d25297ef4bcc3071785ba0c0bedfe89eabc5287fcc0edf81ab5896c1c8e4b20d27d79813c7aede75320b33eaeeaa586edc00fd1036c10133e6ba0ff277245d0d59d04b2b3421b7244aca5f4a8d870c6f1c1fbff9e1c26699a860b9504f35ca1d700030503fd1ededd3b840795be6d9ccbe3c51ee42e2f39233c432b831ddd9c4e72b7025a819317e47bf94f9ee316d7273b05d5fcf2999c3a681f519b1234bbfa6d359b4752bd9c3f77d6b6456cde152464763414ca130f4e91d91041432f90620fec0e6d6b5116076c2985d5aeaae13be492b9b329efcaf7ee25120159a0a30cd976b42d7afe030302dae7eb80db744d4960c4df930d57e87fe81412eaace9f900e6c839817a614ddb75ba6603b9417c33ea7b6c93967dfa2bcff3fa3c74a5ce2c962db65b03aece14c96cbd0038fc"
+
+// pkcs1PrivKeyHex is a PKCS#1, RSA private key.
+// Generated by `openssl genrsa 1024 | openssl rsa -outform DER | xxd -p`
+const pkcs1PrivKeyHex = "3082025d02010002818100e98edfa1c3b35884a54d0b36a6a603b0290fa85e49e30fa23fc94fef9c6790bc4849928607aa48d809da326fb42a969d06ad756b98b9c1a90f5d4a2b6d0ac05953c97f4da3120164a21a679793ce181c906dc01d235cc085ddcdf6ea06c389b6ab8885dfd685959e693138856a68a7e5db263337ff82a088d583a897cf2d59e9020301000102818100b6d5c9eb70b02d5369b3ee5b520a14490b5bde8a317d36f7e4c74b7460141311d1e5067735f8f01d6f5908b2b96fbd881f7a1ab9a84d82753e39e19e2d36856be960d05ac9ef8e8782ea1b6d65aee28fdfe1d61451e8cff0adfe84322f12cf455028b581cf60eb9e0e140ba5d21aeba6c2634d7c65318b9a665fc01c3191ca21024100fa5e818da3705b0fa33278bb28d4b6f6050388af2d4b75ec9375dd91ccf2e7d7068086a8b82a8f6282e4fbbdb8a7f2622eb97295249d87acea7f5f816f54d347024100eecf9406d7dc49cdfb95ab1eff4064de84c7a30f64b2798936a0d2018ba9eb52e4b636f82e96c49cc63b80b675e91e40d1b2e4017d4b9adaf33ab3d9cf1c214f024100c173704ace742c082323066226a4655226819a85304c542b9dacbeacbf5d1881ee863485fcf6f59f3a604f9b42289282067447f2b13dfeed3eab7851fc81e0550240741fc41f3fc002b382eed8730e33c5d8de40256e4accee846667f536832f711ab1d4590e7db91a8a116ac5bff3be13d3f9243ff2e976662aa9b395d907f8e9c9024046a5696c9ef882363e06c9fa4e2f5b580906452befba03f4a99d0f873697ef1f851d2226ca7934b30b7c3e80cb634a67172bbbf4781735fe3e09263e2dd723e7"
diff --git a/local_crypto_patch/contents/openpgp/packet/public_key.go b/local_crypto_patch/contents/openpgp/packet/public_key.go
new file mode 100644
index 0000000000..fcd5f52519
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/public_key.go
@@ -0,0 +1,753 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/dsa"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rsa"
+ "crypto/sha1"
+ _ "crypto/sha256"
+ _ "crypto/sha512"
+ "encoding/binary"
+ "fmt"
+ "hash"
+ "io"
+ "math/big"
+ "strconv"
+ "time"
+
+ "golang.org/x/crypto/openpgp/elgamal"
+ "golang.org/x/crypto/openpgp/errors"
+)
+
+var (
+ // NIST curve P-256
+ oidCurveP256 []byte = []byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}
+ // NIST curve P-384
+ oidCurveP384 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x22}
+ // NIST curve P-521
+ oidCurveP521 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x23}
+)
+
+const maxOIDLength = 8
+
+// ecdsaKey stores the algorithm-specific fields for ECDSA keys.
+// as defined in RFC 6637, Section 9.
+type ecdsaKey struct {
+ // oid contains the OID byte sequence identifying the elliptic curve used
+ oid []byte
+ // p contains the elliptic curve point that represents the public key
+ p parsedMPI
+}
+
+// parseOID reads the OID for the curve as defined in RFC 6637, Section 9.
+func parseOID(r io.Reader) (oid []byte, err error) {
+ buf := make([]byte, maxOIDLength)
+ if _, err = readFull(r, buf[:1]); err != nil {
+ return
+ }
+ oidLen := buf[0]
+ if int(oidLen) > len(buf) {
+ err = errors.UnsupportedError("invalid oid length: " + strconv.Itoa(int(oidLen)))
+ return
+ }
+ oid = buf[:oidLen]
+ _, err = readFull(r, oid)
+ return
+}
+
+func (f *ecdsaKey) parse(r io.Reader) (err error) {
+ if f.oid, err = parseOID(r); err != nil {
+ return err
+ }
+ f.p.bytes, f.p.bitLength, err = readMPI(r)
+ return
+}
+
+func (f *ecdsaKey) serialize(w io.Writer) (err error) {
+ buf := make([]byte, maxOIDLength+1)
+ buf[0] = byte(len(f.oid))
+ copy(buf[1:], f.oid)
+ if _, err = w.Write(buf[:len(f.oid)+1]); err != nil {
+ return
+ }
+ return writeMPIs(w, f.p)
+}
+
+func (f *ecdsaKey) newECDSA() (*ecdsa.PublicKey, error) {
+ var c elliptic.Curve
+ if bytes.Equal(f.oid, oidCurveP256) {
+ c = elliptic.P256()
+ } else if bytes.Equal(f.oid, oidCurveP384) {
+ c = elliptic.P384()
+ } else if bytes.Equal(f.oid, oidCurveP521) {
+ c = elliptic.P521()
+ } else {
+ return nil, errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", f.oid))
+ }
+ x, y := elliptic.Unmarshal(c, f.p.bytes)
+ if x == nil {
+ return nil, errors.UnsupportedError("failed to parse EC point")
+ }
+ return &ecdsa.PublicKey{Curve: c, X: x, Y: y}, nil
+}
+
+func (f *ecdsaKey) byteLen() int {
+ return 1 + len(f.oid) + 2 + len(f.p.bytes)
+}
+
+type kdfHashFunction byte
+type kdfAlgorithm byte
+
+// ecdhKdf stores key derivation function parameters
+// used for ECDH encryption. See RFC 6637, Section 9.
+type ecdhKdf struct {
+ KdfHash kdfHashFunction
+ KdfAlgo kdfAlgorithm
+}
+
+func (f *ecdhKdf) parse(r io.Reader) (err error) {
+ buf := make([]byte, 1)
+ if _, err = readFull(r, buf); err != nil {
+ return
+ }
+ kdfLen := int(buf[0])
+ if kdfLen < 3 {
+ return errors.UnsupportedError("Unsupported ECDH KDF length: " + strconv.Itoa(kdfLen))
+ }
+ buf = make([]byte, kdfLen)
+ if _, err = readFull(r, buf); err != nil {
+ return
+ }
+ reserved := int(buf[0])
+ f.KdfHash = kdfHashFunction(buf[1])
+ f.KdfAlgo = kdfAlgorithm(buf[2])
+ if reserved != 0x01 {
+ return errors.UnsupportedError("Unsupported KDF reserved field: " + strconv.Itoa(reserved))
+ }
+ return
+}
+
+func (f *ecdhKdf) serialize(w io.Writer) (err error) {
+ buf := make([]byte, 4)
+ // See RFC 6637, Section 9, Algorithm-Specific Fields for ECDH keys.
+ buf[0] = byte(0x03) // Length of the following fields
+ buf[1] = byte(0x01) // Reserved for future extensions, must be 1 for now
+ buf[2] = byte(f.KdfHash)
+ buf[3] = byte(f.KdfAlgo)
+ _, err = w.Write(buf[:])
+ return
+}
+
+func (f *ecdhKdf) byteLen() int {
+ return 4
+}
+
+// PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2.
+type PublicKey struct {
+ CreationTime time.Time
+ PubKeyAlgo PublicKeyAlgorithm
+ PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey or *ecdsa.PublicKey
+ Fingerprint [20]byte
+ KeyId uint64
+ IsSubkey bool
+
+ n, e, p, q, g, y parsedMPI
+
+ // RFC 6637 fields
+ ec *ecdsaKey
+ ecdh *ecdhKdf
+}
+
+// signingKey provides a convenient abstraction over signature verification
+// for v3 and v4 public keys.
+type signingKey interface {
+ SerializeSignaturePrefix(io.Writer)
+ serializeWithoutHeaders(io.Writer) error
+}
+
+func fromBig(n *big.Int) parsedMPI {
+ return parsedMPI{
+ bytes: n.Bytes(),
+ bitLength: uint16(n.BitLen()),
+ }
+}
+
+// NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
+func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey {
+ pk := &PublicKey{
+ CreationTime: creationTime,
+ PubKeyAlgo: PubKeyAlgoRSA,
+ PublicKey: pub,
+ n: fromBig(pub.N),
+ e: fromBig(big.NewInt(int64(pub.E))),
+ }
+
+ pk.setFingerPrintAndKeyId()
+ return pk
+}
+
+// NewDSAPublicKey returns a PublicKey that wraps the given dsa.PublicKey.
+func NewDSAPublicKey(creationTime time.Time, pub *dsa.PublicKey) *PublicKey {
+ pk := &PublicKey{
+ CreationTime: creationTime,
+ PubKeyAlgo: PubKeyAlgoDSA,
+ PublicKey: pub,
+ p: fromBig(pub.P),
+ q: fromBig(pub.Q),
+ g: fromBig(pub.G),
+ y: fromBig(pub.Y),
+ }
+
+ pk.setFingerPrintAndKeyId()
+ return pk
+}
+
+// NewElGamalPublicKey returns a PublicKey that wraps the given elgamal.PublicKey.
+func NewElGamalPublicKey(creationTime time.Time, pub *elgamal.PublicKey) *PublicKey {
+ pk := &PublicKey{
+ CreationTime: creationTime,
+ PubKeyAlgo: PubKeyAlgoElGamal,
+ PublicKey: pub,
+ p: fromBig(pub.P),
+ g: fromBig(pub.G),
+ y: fromBig(pub.Y),
+ }
+
+ pk.setFingerPrintAndKeyId()
+ return pk
+}
+
+func NewECDSAPublicKey(creationTime time.Time, pub *ecdsa.PublicKey) *PublicKey {
+ pk := &PublicKey{
+ CreationTime: creationTime,
+ PubKeyAlgo: PubKeyAlgoECDSA,
+ PublicKey: pub,
+ ec: new(ecdsaKey),
+ }
+
+ switch pub.Curve {
+ case elliptic.P256():
+ pk.ec.oid = oidCurveP256
+ case elliptic.P384():
+ pk.ec.oid = oidCurveP384
+ case elliptic.P521():
+ pk.ec.oid = oidCurveP521
+ default:
+ panic("unknown elliptic curve")
+ }
+
+ pk.ec.p.bytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
+
+ // The bit length is 3 (for the 0x04 specifying an uncompressed key)
+ // plus two field elements (for x and y), which are rounded up to the
+ // nearest byte. See https://tools.ietf.org/html/rfc6637#section-6
+ fieldBytes := (pub.Curve.Params().BitSize + 7) & ^7
+ pk.ec.p.bitLength = uint16(3 + fieldBytes + fieldBytes)
+
+ pk.setFingerPrintAndKeyId()
+ return pk
+}
+
+func (pk *PublicKey) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.5.2
+ var buf [6]byte
+ _, err = readFull(r, buf[:])
+ if err != nil {
+ return
+ }
+ if buf[0] != 4 {
+ return errors.UnsupportedError("public key version")
+ }
+ pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
+ pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5])
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ err = pk.parseRSA(r)
+ case PubKeyAlgoDSA:
+ err = pk.parseDSA(r)
+ case PubKeyAlgoElGamal:
+ err = pk.parseElGamal(r)
+ case PubKeyAlgoECDSA:
+ pk.ec = new(ecdsaKey)
+ if err = pk.ec.parse(r); err != nil {
+ return err
+ }
+ pk.PublicKey, err = pk.ec.newECDSA()
+ case PubKeyAlgoECDH:
+ pk.ec = new(ecdsaKey)
+ if err = pk.ec.parse(r); err != nil {
+ return
+ }
+ pk.ecdh = new(ecdhKdf)
+ if err = pk.ecdh.parse(r); err != nil {
+ return
+ }
+ // The ECDH key is stored in an ecdsa.PublicKey for convenience.
+ pk.PublicKey, err = pk.ec.newECDSA()
+ default:
+ err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
+ }
+ if err != nil {
+ return
+ }
+
+ pk.setFingerPrintAndKeyId()
+ return
+}
+
+func (pk *PublicKey) setFingerPrintAndKeyId() {
+ // RFC 4880, section 12.2
+ fingerPrint := sha1.New()
+ pk.SerializeSignaturePrefix(fingerPrint)
+ pk.serializeWithoutHeaders(fingerPrint)
+ copy(pk.Fingerprint[:], fingerPrint.Sum(nil))
+ pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20])
+}
+
+// parseRSA parses RSA public key material from the given Reader. See RFC 4880,
+// section 5.5.2.
+func (pk *PublicKey) parseRSA(r io.Reader) (err error) {
+ pk.n.bytes, pk.n.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.e.bytes, pk.e.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+
+ if len(pk.e.bytes) > 3 {
+ err = errors.UnsupportedError("large public exponent")
+ return
+ }
+ rsa := &rsa.PublicKey{
+ N: new(big.Int).SetBytes(pk.n.bytes),
+ E: 0,
+ }
+ for i := 0; i < len(pk.e.bytes); i++ {
+ rsa.E <<= 8
+ rsa.E |= int(pk.e.bytes[i])
+ }
+ pk.PublicKey = rsa
+ return
+}
+
+// parseDSA parses DSA public key material from the given Reader. See RFC 4880,
+// section 5.5.2.
+func (pk *PublicKey) parseDSA(r io.Reader) (err error) {
+ pk.p.bytes, pk.p.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.q.bytes, pk.q.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.g.bytes, pk.g.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.y.bytes, pk.y.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+
+ dsa := new(dsa.PublicKey)
+ dsa.P = new(big.Int).SetBytes(pk.p.bytes)
+ dsa.Q = new(big.Int).SetBytes(pk.q.bytes)
+ dsa.G = new(big.Int).SetBytes(pk.g.bytes)
+ dsa.Y = new(big.Int).SetBytes(pk.y.bytes)
+ pk.PublicKey = dsa
+ return
+}
+
+// parseElGamal parses ElGamal public key material from the given Reader. See
+// RFC 4880, section 5.5.2.
+func (pk *PublicKey) parseElGamal(r io.Reader) (err error) {
+ pk.p.bytes, pk.p.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.g.bytes, pk.g.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+ pk.y.bytes, pk.y.bitLength, err = readMPI(r)
+ if err != nil {
+ return
+ }
+
+ elgamal := new(elgamal.PublicKey)
+ elgamal.P = new(big.Int).SetBytes(pk.p.bytes)
+ elgamal.G = new(big.Int).SetBytes(pk.g.bytes)
+ elgamal.Y = new(big.Int).SetBytes(pk.y.bytes)
+ pk.PublicKey = elgamal
+ return
+}
+
+// SerializeSignaturePrefix writes the prefix for this public key to the given Writer.
+// The prefix is used when calculating a signature over this public key. See
+// RFC 4880, section 5.2.4.
+func (pk *PublicKey) SerializeSignaturePrefix(h io.Writer) {
+ var pLength uint16
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ pLength += 2 + uint16(len(pk.n.bytes))
+ pLength += 2 + uint16(len(pk.e.bytes))
+ case PubKeyAlgoDSA:
+ pLength += 2 + uint16(len(pk.p.bytes))
+ pLength += 2 + uint16(len(pk.q.bytes))
+ pLength += 2 + uint16(len(pk.g.bytes))
+ pLength += 2 + uint16(len(pk.y.bytes))
+ case PubKeyAlgoElGamal:
+ pLength += 2 + uint16(len(pk.p.bytes))
+ pLength += 2 + uint16(len(pk.g.bytes))
+ pLength += 2 + uint16(len(pk.y.bytes))
+ case PubKeyAlgoECDSA:
+ pLength += uint16(pk.ec.byteLen())
+ case PubKeyAlgoECDH:
+ pLength += uint16(pk.ec.byteLen())
+ pLength += uint16(pk.ecdh.byteLen())
+ default:
+ panic("unknown public key algorithm")
+ }
+ pLength += 6
+ h.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)})
+ return
+}
+
+func (pk *PublicKey) Serialize(w io.Writer) (err error) {
+ length := 6 // 6 byte header
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ length += 2 + len(pk.n.bytes)
+ length += 2 + len(pk.e.bytes)
+ case PubKeyAlgoDSA:
+ length += 2 + len(pk.p.bytes)
+ length += 2 + len(pk.q.bytes)
+ length += 2 + len(pk.g.bytes)
+ length += 2 + len(pk.y.bytes)
+ case PubKeyAlgoElGamal:
+ length += 2 + len(pk.p.bytes)
+ length += 2 + len(pk.g.bytes)
+ length += 2 + len(pk.y.bytes)
+ case PubKeyAlgoECDSA:
+ length += pk.ec.byteLen()
+ case PubKeyAlgoECDH:
+ length += pk.ec.byteLen()
+ length += pk.ecdh.byteLen()
+ default:
+ panic("unknown public key algorithm")
+ }
+
+ packetType := packetTypePublicKey
+ if pk.IsSubkey {
+ packetType = packetTypePublicSubkey
+ }
+ err = serializeHeader(w, packetType, length)
+ if err != nil {
+ return
+ }
+ return pk.serializeWithoutHeaders(w)
+}
+
+// serializeWithoutHeaders marshals the PublicKey to w in the form of an
+// OpenPGP public key packet, not including the packet header.
+func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) {
+ var buf [6]byte
+ buf[0] = 4
+ t := uint32(pk.CreationTime.Unix())
+ buf[1] = byte(t >> 24)
+ buf[2] = byte(t >> 16)
+ buf[3] = byte(t >> 8)
+ buf[4] = byte(t)
+ buf[5] = byte(pk.PubKeyAlgo)
+
+ _, err = w.Write(buf[:])
+ if err != nil {
+ return
+ }
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ return writeMPIs(w, pk.n, pk.e)
+ case PubKeyAlgoDSA:
+ return writeMPIs(w, pk.p, pk.q, pk.g, pk.y)
+ case PubKeyAlgoElGamal:
+ return writeMPIs(w, pk.p, pk.g, pk.y)
+ case PubKeyAlgoECDSA:
+ return pk.ec.serialize(w)
+ case PubKeyAlgoECDH:
+ if err = pk.ec.serialize(w); err != nil {
+ return
+ }
+ return pk.ecdh.serialize(w)
+ }
+ return errors.InvalidArgumentError("bad public-key algorithm")
+}
+
+// CanSign returns true iff this public key can generate signatures
+func (pk *PublicKey) CanSign() bool {
+ return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly && pk.PubKeyAlgo != PubKeyAlgoElGamal
+}
+
+// VerifySignature returns nil iff sig is a valid signature, made by this
+// public key, of the data hashed into signed. signed is mutated by this call.
+func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err error) {
+ if !pk.CanSign() {
+ return errors.InvalidArgumentError("public key cannot generate signatures")
+ }
+
+ signed.Write(sig.HashSuffix)
+ hashBytes := signed.Sum(nil)
+
+ if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
+ return errors.SignatureError("hash tag doesn't match")
+ }
+
+ if pk.PubKeyAlgo != sig.PubKeyAlgo {
+ return errors.InvalidArgumentError("public key and signature use different algorithms")
+ }
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ rsaPublicKey, _ := pk.PublicKey.(*rsa.PublicKey)
+ err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, padToKeySize(rsaPublicKey, sig.RSASignature.bytes))
+ if err != nil {
+ return errors.SignatureError("RSA verification failure")
+ }
+ return nil
+ case PubKeyAlgoDSA:
+ dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey)
+ // Need to truncate hashBytes to match FIPS 186-3 section 4.6.
+ subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
+ if len(hashBytes) > subgroupSize {
+ hashBytes = hashBytes[:subgroupSize]
+ }
+ if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
+ return errors.SignatureError("DSA verification failure")
+ }
+ return nil
+ case PubKeyAlgoECDSA:
+ ecdsaPublicKey := pk.PublicKey.(*ecdsa.PublicKey)
+ if !ecdsa.Verify(ecdsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.ECDSASigR.bytes), new(big.Int).SetBytes(sig.ECDSASigS.bytes)) {
+ return errors.SignatureError("ECDSA verification failure")
+ }
+ return nil
+ default:
+ return errors.SignatureError("Unsupported public key algorithm used in signature")
+ }
+}
+
+// VerifySignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, of the data hashed into signed. signed is mutated by this call.
+func (pk *PublicKey) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (err error) {
+ if !pk.CanSign() {
+ return errors.InvalidArgumentError("public key cannot generate signatures")
+ }
+
+ suffix := make([]byte, 5)
+ suffix[0] = byte(sig.SigType)
+ binary.BigEndian.PutUint32(suffix[1:], uint32(sig.CreationTime.Unix()))
+ signed.Write(suffix)
+ hashBytes := signed.Sum(nil)
+
+ if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
+ return errors.SignatureError("hash tag doesn't match")
+ }
+
+ if pk.PubKeyAlgo != sig.PubKeyAlgo {
+ return errors.InvalidArgumentError("public key and signature use different algorithms")
+ }
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ rsaPublicKey := pk.PublicKey.(*rsa.PublicKey)
+ if err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, padToKeySize(rsaPublicKey, sig.RSASignature.bytes)); err != nil {
+ return errors.SignatureError("RSA verification failure")
+ }
+ return
+ case PubKeyAlgoDSA:
+ dsaPublicKey := pk.PublicKey.(*dsa.PublicKey)
+ // Need to truncate hashBytes to match FIPS 186-3 section 4.6.
+ subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
+ if len(hashBytes) > subgroupSize {
+ hashBytes = hashBytes[:subgroupSize]
+ }
+ if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
+ return errors.SignatureError("DSA verification failure")
+ }
+ return nil
+ default:
+ panic("shouldn't happen")
+ }
+}
+
+// keySignatureHash returns a Hash of the message that needs to be signed for
+// pk to assert a subkey relationship to signed.
+func keySignatureHash(pk, signed signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
+ if !hashFunc.Available() {
+ return nil, errors.UnsupportedError("hash function")
+ }
+ h = hashFunc.New()
+
+ // RFC 4880, section 5.2.4
+ pk.SerializeSignaturePrefix(h)
+ pk.serializeWithoutHeaders(h)
+ signed.SerializeSignaturePrefix(h)
+ signed.serializeWithoutHeaders(h)
+ return
+}
+
+// VerifyKeySignature returns nil iff sig is a valid signature, made by this
+// public key, of signed.
+func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error {
+ h, err := keySignatureHash(pk, signed, sig.Hash)
+ if err != nil {
+ return err
+ }
+ if err = pk.VerifySignature(h, sig); err != nil {
+ return err
+ }
+
+ if sig.FlagSign {
+ // Signing subkeys must be cross-signed. See
+ // https://www.gnupg.org/faq/subkey-cross-certify.html.
+ if sig.EmbeddedSignature == nil {
+ return errors.StructuralError("signing subkey is missing cross-signature")
+ }
+ // Verify the cross-signature. This is calculated over the same
+ // data as the main signature, so we cannot just recursively
+ // call signed.VerifyKeySignature(...)
+ if h, err = keySignatureHash(pk, signed, sig.EmbeddedSignature.Hash); err != nil {
+ return errors.StructuralError("error while hashing for cross-signature: " + err.Error())
+ }
+ if err := signed.VerifySignature(h, sig.EmbeddedSignature); err != nil {
+ return errors.StructuralError("error while verifying cross-signature: " + err.Error())
+ }
+ }
+
+ return nil
+}
+
+func keyRevocationHash(pk signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
+ if !hashFunc.Available() {
+ return nil, errors.UnsupportedError("hash function")
+ }
+ h = hashFunc.New()
+
+ // RFC 4880, section 5.2.4
+ pk.SerializeSignaturePrefix(h)
+ pk.serializeWithoutHeaders(h)
+
+ return
+}
+
+// VerifyRevocationSignature returns nil iff sig is a valid signature, made by this
+// public key.
+func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) {
+ h, err := keyRevocationHash(pk, sig.Hash)
+ if err != nil {
+ return err
+ }
+ return pk.VerifySignature(h, sig)
+}
+
+// userIdSignatureHash returns a Hash of the message that needs to be signed
+// to assert that pk is a valid key for id.
+func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash.Hash, err error) {
+ if !hashFunc.Available() {
+ return nil, errors.UnsupportedError("hash function")
+ }
+ h = hashFunc.New()
+
+ // RFC 4880, section 5.2.4
+ pk.SerializeSignaturePrefix(h)
+ pk.serializeWithoutHeaders(h)
+
+ var buf [5]byte
+ buf[0] = 0xb4
+ buf[1] = byte(len(id) >> 24)
+ buf[2] = byte(len(id) >> 16)
+ buf[3] = byte(len(id) >> 8)
+ buf[4] = byte(len(id))
+ h.Write(buf[:])
+ h.Write([]byte(id))
+
+ return
+}
+
+// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this
+// public key, that id is the identity of pub.
+func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) {
+ h, err := userIdSignatureHash(id, pub, sig.Hash)
+ if err != nil {
+ return err
+ }
+ return pk.VerifySignature(h, sig)
+}
+
+// VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, that id is the identity of pub.
+func (pk *PublicKey) VerifyUserIdSignatureV3(id string, pub *PublicKey, sig *SignatureV3) (err error) {
+ h, err := userIdSignatureV3Hash(id, pub, sig.Hash)
+ if err != nil {
+ return err
+ }
+ return pk.VerifySignatureV3(h, sig)
+}
+
+// KeyIdString returns the public key's fingerprint in capital hex
+// (e.g. "6C7EE1B8621CC013").
+func (pk *PublicKey) KeyIdString() string {
+ return fmt.Sprintf("%X", pk.Fingerprint[12:20])
+}
+
+// KeyIdShortString returns the short form of public key's fingerprint
+// in capital hex, as shown by gpg --list-keys (e.g. "621CC013").
+func (pk *PublicKey) KeyIdShortString() string {
+ return fmt.Sprintf("%X", pk.Fingerprint[16:20])
+}
+
+// A parsedMPI is used to store the contents of a big integer, along with the
+// bit length that was specified in the original input. This allows the MPI to
+// be reserialized exactly.
+type parsedMPI struct {
+ bytes []byte
+ bitLength uint16
+}
+
+// writeMPIs is a utility function for serializing several big integers to the
+// given Writer.
+func writeMPIs(w io.Writer, mpis ...parsedMPI) (err error) {
+ for _, mpi := range mpis {
+ err = writeMPI(w, mpi.bitLength, mpi.bytes)
+ if err != nil {
+ return
+ }
+ }
+ return
+}
+
+// BitLength returns the bit length for the given public key.
+func (pk *PublicKey) BitLength() (bitLength uint16, err error) {
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ bitLength = pk.n.bitLength
+ case PubKeyAlgoDSA:
+ bitLength = pk.p.bitLength
+ case PubKeyAlgoElGamal:
+ bitLength = pk.p.bitLength
+ default:
+ err = errors.InvalidArgumentError("bad public-key algorithm")
+ }
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/public_key_test.go b/local_crypto_patch/contents/openpgp/packet/public_key_test.go
new file mode 100644
index 0000000000..103696ee7c
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/public_key_test.go
@@ -0,0 +1,228 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "encoding/hex"
+ "math/big"
+ "testing"
+ "time"
+)
+
+var pubKeyTests = []struct {
+ hexData string
+ hexFingerprint string
+ creationTime time.Time
+ pubKeyAlgo PublicKeyAlgorithm
+ keyId uint64
+ keyIdString string
+ keyIdShort string
+}{
+ {rsaPkDataHex, rsaFingerprintHex, time.Unix(0x4d3c5c10, 0), PubKeyAlgoRSA, 0xa34d7e18c20c31bb, "A34D7E18C20C31BB", "C20C31BB"},
+ {dsaPkDataHex, dsaFingerprintHex, time.Unix(0x4d432f89, 0), PubKeyAlgoDSA, 0x8e8fbe54062f19ed, "8E8FBE54062F19ED", "062F19ED"},
+ {ecdsaPkDataHex, ecdsaFingerprintHex, time.Unix(0x5071c294, 0), PubKeyAlgoECDSA, 0x43fe956c542ca00b, "43FE956C542CA00B", "542CA00B"},
+}
+
+func TestPublicKeyRead(t *testing.T) {
+ for i, test := range pubKeyTests {
+ packet, err := Read(readerFromHex(test.hexData))
+ if err != nil {
+ t.Errorf("#%d: Read error: %s", i, err)
+ continue
+ }
+ pk, ok := packet.(*PublicKey)
+ if !ok {
+ t.Errorf("#%d: failed to parse, got: %#v", i, packet)
+ continue
+ }
+ if pk.PubKeyAlgo != test.pubKeyAlgo {
+ t.Errorf("#%d: bad public key algorithm got:%x want:%x", i, pk.PubKeyAlgo, test.pubKeyAlgo)
+ }
+ if !pk.CreationTime.Equal(test.creationTime) {
+ t.Errorf("#%d: bad creation time got:%v want:%v", i, pk.CreationTime, test.creationTime)
+ }
+ expectedFingerprint, _ := hex.DecodeString(test.hexFingerprint)
+ if !bytes.Equal(expectedFingerprint, pk.Fingerprint[:]) {
+ t.Errorf("#%d: bad fingerprint got:%x want:%x", i, pk.Fingerprint[:], expectedFingerprint)
+ }
+ if pk.KeyId != test.keyId {
+ t.Errorf("#%d: bad keyid got:%x want:%x", i, pk.KeyId, test.keyId)
+ }
+ if g, e := pk.KeyIdString(), test.keyIdString; g != e {
+ t.Errorf("#%d: bad KeyIdString got:%q want:%q", i, g, e)
+ }
+ if g, e := pk.KeyIdShortString(), test.keyIdShort; g != e {
+ t.Errorf("#%d: bad KeyIdShortString got:%q want:%q", i, g, e)
+ }
+ }
+}
+
+func TestPublicKeySerialize(t *testing.T) {
+ for i, test := range pubKeyTests {
+ packet, err := Read(readerFromHex(test.hexData))
+ if err != nil {
+ t.Errorf("#%d: Read error: %s", i, err)
+ continue
+ }
+ pk, ok := packet.(*PublicKey)
+ if !ok {
+ t.Errorf("#%d: failed to parse, got: %#v", i, packet)
+ continue
+ }
+ serializeBuf := bytes.NewBuffer(nil)
+ err = pk.Serialize(serializeBuf)
+ if err != nil {
+ t.Errorf("#%d: failed to serialize: %s", i, err)
+ continue
+ }
+
+ packet, err = Read(serializeBuf)
+ if err != nil {
+ t.Errorf("#%d: Read error (from serialized data): %s", i, err)
+ continue
+ }
+ pk, ok = packet.(*PublicKey)
+ if !ok {
+ t.Errorf("#%d: failed to parse serialized data, got: %#v", i, packet)
+ continue
+ }
+ }
+}
+
+func TestEcc384Serialize(t *testing.T) {
+ r := readerFromHex(ecc384PubHex)
+ var w bytes.Buffer
+ for i := 0; i < 2; i++ {
+ // Public key
+ p, err := Read(r)
+ if err != nil {
+ t.Error(err)
+ }
+ pubkey := p.(*PublicKey)
+ if !bytes.Equal(pubkey.ec.oid, []byte{0x2b, 0x81, 0x04, 0x00, 0x22}) {
+ t.Errorf("Unexpected pubkey OID: %x", pubkey.ec.oid)
+ }
+ if !bytes.Equal(pubkey.ec.p.bytes[:5], []byte{0x04, 0xf6, 0xb8, 0xc5, 0xac}) {
+ t.Errorf("Unexpected pubkey P[:5]: %x", pubkey.ec.p.bytes)
+ }
+ if pubkey.KeyId != 0x098033880F54719F {
+ t.Errorf("Unexpected pubkey ID: %x", pubkey.KeyId)
+ }
+ err = pubkey.Serialize(&w)
+ if err != nil {
+ t.Error(err)
+ }
+ // User ID
+ p, err = Read(r)
+ if err != nil {
+ t.Error(err)
+ }
+ uid := p.(*UserId)
+ if uid.Id != "ec_dsa_dh_384 " {
+ t.Error("Unexpected UID:", uid.Id)
+ }
+ err = uid.Serialize(&w)
+ if err != nil {
+ t.Error(err)
+ }
+ // User ID Sig
+ p, err = Read(r)
+ if err != nil {
+ t.Error(err)
+ }
+ uidSig := p.(*Signature)
+ err = pubkey.VerifyUserIdSignature(uid.Id, pubkey, uidSig)
+ if err != nil {
+ t.Error(err, ": UID")
+ }
+ err = uidSig.Serialize(&w)
+ if err != nil {
+ t.Error(err)
+ }
+ // Subkey
+ p, err = Read(r)
+ if err != nil {
+ t.Error(err)
+ }
+ subkey := p.(*PublicKey)
+ if !bytes.Equal(subkey.ec.oid, []byte{0x2b, 0x81, 0x04, 0x00, 0x22}) {
+ t.Errorf("Unexpected subkey OID: %x", subkey.ec.oid)
+ }
+ if !bytes.Equal(subkey.ec.p.bytes[:5], []byte{0x04, 0x2f, 0xaa, 0x84, 0x02}) {
+ t.Errorf("Unexpected subkey P[:5]: %x", subkey.ec.p.bytes)
+ }
+ if subkey.ecdh.KdfHash != 0x09 {
+ t.Error("Expected KDF hash function SHA384 (0x09), got", subkey.ecdh.KdfHash)
+ }
+ if subkey.ecdh.KdfAlgo != 0x09 {
+ t.Error("Expected KDF symmetric alg AES256 (0x09), got", subkey.ecdh.KdfAlgo)
+ }
+ if subkey.KeyId != 0xAA8B938F9A201946 {
+ t.Errorf("Unexpected subkey ID: %x", subkey.KeyId)
+ }
+ err = subkey.Serialize(&w)
+ if err != nil {
+ t.Error(err)
+ }
+ // Subkey Sig
+ p, err = Read(r)
+ if err != nil {
+ t.Error(err)
+ }
+ subkeySig := p.(*Signature)
+ err = pubkey.VerifyKeySignature(subkey, subkeySig)
+ if err != nil {
+ t.Error(err)
+ }
+ err = subkeySig.Serialize(&w)
+ if err != nil {
+ t.Error(err)
+ }
+ // Now read back what we've written again
+ r = bytes.NewBuffer(w.Bytes())
+ w.Reset()
+ }
+}
+
+func TestP256KeyID(t *testing.T) {
+ // Confirm that key IDs are correctly calculated for ECC keys.
+ ecdsaPub := &ecdsa.PublicKey{
+ Curve: elliptic.P256(),
+ X: fromHex("81fbbc20eea9e8d1c3ceabb0a8185925b113d1ac42cd5c78403bd83da19235c6"),
+ Y: fromHex("5ed6db13d91db34507d0129bf88981878d29adbf8fcd1720afdb767bb3fcaaff"),
+ }
+ pub := NewECDSAPublicKey(time.Unix(1297309478, 0), ecdsaPub)
+
+ const want = uint64(0xd01055fbcadd268e)
+ if pub.KeyId != want {
+ t.Errorf("want key ID: %x, got %x", want, pub.KeyId)
+ }
+}
+
+func fromHex(hex string) *big.Int {
+ n, ok := new(big.Int).SetString(hex, 16)
+ if !ok {
+ panic("bad hex number: " + hex)
+ }
+ return n
+}
+
+const rsaFingerprintHex = "5fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb"
+
+const rsaPkDataHex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001"
+
+const dsaFingerprintHex = "eece4c094db002103714c63c8e8fbe54062f19ed"
+
+const dsaPkDataHex = "9901a2044d432f89110400cd581334f0d7a1e1bdc8b9d6d8c0baf68793632735d2bb0903224cbaa1dfbf35a60ee7a13b92643421e1eb41aa8d79bea19a115a677f6b8ba3c7818ce53a6c2a24a1608bd8b8d6e55c5090cbde09dd26e356267465ae25e69ec8bdd57c7bbb2623e4d73336f73a0a9098f7f16da2e25252130fd694c0e8070c55a812a423ae7f00a0ebf50e70c2f19c3520a551bd4b08d30f23530d3d03ff7d0bf4a53a64a09dc5e6e6e35854b7d70c882b0c60293401958b1bd9e40abec3ea05ba87cf64899299d4bd6aa7f459c201d3fbbd6c82004bdc5e8a9eb8082d12054cc90fa9d4ec251a843236a588bf49552441817436c4f43326966fe85447d4e6d0acf8fa1ef0f014730770603ad7634c3088dc52501c237328417c31c89ed70400b2f1a98b0bf42f11fefc430704bebbaa41d9f355600c3facee1e490f64208e0e094ea55e3a598a219a58500bf78ac677b670a14f4e47e9cf8eab4f368cc1ddcaa18cc59309d4cc62dd4f680e73e6cc3e1ce87a84d0925efbcb26c575c093fc42eecf45135fabf6403a25c2016e1774c0484e440a18319072c617cc97ac0a3bb0"
+
+const ecdsaFingerprintHex = "9892270b38b8980b05c8d56d43fe956c542ca00b"
+
+const ecdsaPkDataHex = "9893045071c29413052b8104002304230401f4867769cedfa52c325018896245443968e52e51d0c2df8d939949cb5b330f2921711fbee1c9b9dddb95d15cb0255e99badeddda7cc23d9ddcaacbc290969b9f24019375d61c2e4e3b36953a28d8b2bc95f78c3f1d592fb24499be348656a7b17e3963187b4361afe497bc5f9f81213f04069f8e1fb9e6a6290ae295ca1a92b894396cb4"
+
+// Source: https://sites.google.com/site/brainhub/pgpecckeys#TOC-ECC-NIST-P-384-key
+const ecc384PubHex = `99006f044d53059213052b81040022030304f6b8c5aced5b84ef9f4a209db2e4a9dfb70d28cb8c10ecd57674a9fa5a67389942b62d5e51367df4c7bfd3f8e500feecf07ed265a621a8ebbbe53e947ec78c677eba143bd1533c2b350e1c29f82313e1e1108eba063be1e64b10e6950e799c2db42465635f6473615f64685f333834203c6f70656e70677040627261696e6875622e6f72673e8900cb04101309005305024d530592301480000000002000077072656665727265642d656d61696c2d656e636f64696e67407067702e636f6d7067706d696d65040b090807021901051b03000000021602051e010000000415090a08000a0910098033880f54719fca2b0180aa37350968bd5f115afd8ce7bc7b103822152dbff06d0afcda835329510905b98cb469ba208faab87c7412b799e7b633017f58364ea480e8a1a3f253a0c5f22c446e8be9a9fce6210136ee30811abbd49139de28b5bdf8dc36d06ae748579e9ff503b90073044d53059212052b810400220303042faa84024a20b6735c4897efa5bfb41bf85b7eefeab5ca0cb9ffc8ea04a46acb25534a577694f9e25340a4ab5223a9dd1eda530c8aa2e6718db10d7e672558c7736fe09369ea5739a2a3554bf16d41faa50562f11c6d39bbd5dffb6b9a9ec9180301090989008404181309000c05024d530592051b0c000000000a0910098033880f54719f80970180eee7a6d8fcee41ee4f9289df17f9bcf9d955dca25c583b94336f3a2b2d4986dc5cf417b8d2dc86f741a9e1a6d236c0e3017d1c76575458a0cfb93ae8a2b274fcc65ceecd7a91eec83656ba13219969f06945b48c56bd04152c3a0553c5f2f4bd1267`
diff --git a/local_crypto_patch/contents/openpgp/packet/public_key_v3.go b/local_crypto_patch/contents/openpgp/packet/public_key_v3.go
new file mode 100644
index 0000000000..5daf7b6cfd
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/public_key_v3.go
@@ -0,0 +1,279 @@
+// 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 packet
+
+import (
+ "crypto"
+ "crypto/md5"
+ "crypto/rsa"
+ "encoding/binary"
+ "fmt"
+ "hash"
+ "io"
+ "math/big"
+ "strconv"
+ "time"
+
+ "golang.org/x/crypto/openpgp/errors"
+)
+
+// PublicKeyV3 represents older, version 3 public keys. These keys are less secure and
+// should not be used for signing or encrypting. They are supported here only for
+// parsing version 3 key material and validating signatures.
+// See RFC 4880, section 5.5.2.
+type PublicKeyV3 struct {
+ CreationTime time.Time
+ DaysToExpire uint16
+ PubKeyAlgo PublicKeyAlgorithm
+ PublicKey *rsa.PublicKey
+ Fingerprint [16]byte
+ KeyId uint64
+ IsSubkey bool
+
+ n, e parsedMPI
+}
+
+// newRSAPublicKeyV3 returns a PublicKey that wraps the given rsa.PublicKey.
+// Included here for testing purposes only. RFC 4880, section 5.5.2:
+// "an implementation MUST NOT generate a V3 key, but MAY accept it."
+func newRSAPublicKeyV3(creationTime time.Time, pub *rsa.PublicKey) *PublicKeyV3 {
+ pk := &PublicKeyV3{
+ CreationTime: creationTime,
+ PublicKey: pub,
+ n: fromBig(pub.N),
+ e: fromBig(big.NewInt(int64(pub.E))),
+ }
+
+ pk.setFingerPrintAndKeyId()
+ return pk
+}
+
+func (pk *PublicKeyV3) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.5.2
+ var buf [8]byte
+ if _, err = readFull(r, buf[:]); err != nil {
+ return
+ }
+ if buf[0] < 2 || buf[0] > 3 {
+ return errors.UnsupportedError("public key version")
+ }
+ pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
+ pk.DaysToExpire = binary.BigEndian.Uint16(buf[5:7])
+ pk.PubKeyAlgo = PublicKeyAlgorithm(buf[7])
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ err = pk.parseRSA(r)
+ default:
+ err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
+ }
+ if err != nil {
+ return
+ }
+
+ pk.setFingerPrintAndKeyId()
+ return
+}
+
+func (pk *PublicKeyV3) setFingerPrintAndKeyId() {
+ // RFC 4880, section 12.2
+ fingerPrint := md5.New()
+ fingerPrint.Write(pk.n.bytes)
+ fingerPrint.Write(pk.e.bytes)
+ fingerPrint.Sum(pk.Fingerprint[:0])
+ pk.KeyId = binary.BigEndian.Uint64(pk.n.bytes[len(pk.n.bytes)-8:])
+}
+
+// parseRSA parses RSA public key material from the given Reader. See RFC 4880,
+// section 5.5.2.
+func (pk *PublicKeyV3) parseRSA(r io.Reader) (err error) {
+ if pk.n.bytes, pk.n.bitLength, err = readMPI(r); err != nil {
+ return
+ }
+ if pk.e.bytes, pk.e.bitLength, err = readMPI(r); err != nil {
+ return
+ }
+
+ // RFC 4880 Section 12.2 requires the low 8 bytes of the
+ // modulus to form the key id.
+ if len(pk.n.bytes) < 8 {
+ return errors.StructuralError("v3 public key modulus is too short")
+ }
+ if len(pk.e.bytes) > 3 {
+ err = errors.UnsupportedError("large public exponent")
+ return
+ }
+ rsa := &rsa.PublicKey{N: new(big.Int).SetBytes(pk.n.bytes)}
+ for i := 0; i < len(pk.e.bytes); i++ {
+ rsa.E <<= 8
+ rsa.E |= int(pk.e.bytes[i])
+ }
+ pk.PublicKey = rsa
+ return
+}
+
+// SerializeSignaturePrefix writes the prefix for this public key to the given Writer.
+// The prefix is used when calculating a signature over this public key. See
+// RFC 4880, section 5.2.4.
+func (pk *PublicKeyV3) SerializeSignaturePrefix(w io.Writer) {
+ var pLength uint16
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ pLength += 2 + uint16(len(pk.n.bytes))
+ pLength += 2 + uint16(len(pk.e.bytes))
+ default:
+ panic("unknown public key algorithm")
+ }
+ pLength += 6
+ w.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)})
+ return
+}
+
+func (pk *PublicKeyV3) Serialize(w io.Writer) (err error) {
+ length := 8 // 8 byte header
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ length += 2 + len(pk.n.bytes)
+ length += 2 + len(pk.e.bytes)
+ default:
+ panic("unknown public key algorithm")
+ }
+
+ packetType := packetTypePublicKey
+ if pk.IsSubkey {
+ packetType = packetTypePublicSubkey
+ }
+ if err = serializeHeader(w, packetType, length); err != nil {
+ return
+ }
+ return pk.serializeWithoutHeaders(w)
+}
+
+// serializeWithoutHeaders marshals the PublicKey to w in the form of an
+// OpenPGP public key packet, not including the packet header.
+func (pk *PublicKeyV3) serializeWithoutHeaders(w io.Writer) (err error) {
+ var buf [8]byte
+ // Version 3
+ buf[0] = 3
+ // Creation time
+ t := uint32(pk.CreationTime.Unix())
+ buf[1] = byte(t >> 24)
+ buf[2] = byte(t >> 16)
+ buf[3] = byte(t >> 8)
+ buf[4] = byte(t)
+ // Days to expire
+ buf[5] = byte(pk.DaysToExpire >> 8)
+ buf[6] = byte(pk.DaysToExpire)
+ // Public key algorithm
+ buf[7] = byte(pk.PubKeyAlgo)
+
+ if _, err = w.Write(buf[:]); err != nil {
+ return
+ }
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ return writeMPIs(w, pk.n, pk.e)
+ }
+ return errors.InvalidArgumentError("bad public-key algorithm")
+}
+
+// CanSign returns true iff this public key can generate signatures
+func (pk *PublicKeyV3) CanSign() bool {
+ return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly
+}
+
+// VerifySignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, of the data hashed into signed. signed is mutated by this call.
+func (pk *PublicKeyV3) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (err error) {
+ if !pk.CanSign() {
+ return errors.InvalidArgumentError("public key cannot generate signatures")
+ }
+
+ suffix := make([]byte, 5)
+ suffix[0] = byte(sig.SigType)
+ binary.BigEndian.PutUint32(suffix[1:], uint32(sig.CreationTime.Unix()))
+ signed.Write(suffix)
+ hashBytes := signed.Sum(nil)
+
+ if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
+ return errors.SignatureError("hash tag doesn't match")
+ }
+
+ if pk.PubKeyAlgo != sig.PubKeyAlgo {
+ return errors.InvalidArgumentError("public key and signature use different algorithms")
+ }
+
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ if err = rsa.VerifyPKCS1v15(pk.PublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes); err != nil {
+ return errors.SignatureError("RSA verification failure")
+ }
+ return
+ default:
+ // V3 public keys only support RSA.
+ panic("shouldn't happen")
+ }
+}
+
+// VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, that id is the identity of pub.
+func (pk *PublicKeyV3) VerifyUserIdSignatureV3(id string, pub *PublicKeyV3, sig *SignatureV3) (err error) {
+ h, err := userIdSignatureV3Hash(id, pk, sig.Hash)
+ if err != nil {
+ return err
+ }
+ return pk.VerifySignatureV3(h, sig)
+}
+
+// VerifyKeySignatureV3 returns nil iff sig is a valid signature, made by this
+// public key, of signed.
+func (pk *PublicKeyV3) VerifyKeySignatureV3(signed *PublicKeyV3, sig *SignatureV3) (err error) {
+ h, err := keySignatureHash(pk, signed, sig.Hash)
+ if err != nil {
+ return err
+ }
+ return pk.VerifySignatureV3(h, sig)
+}
+
+// userIdSignatureV3Hash returns a Hash of the message that needs to be signed
+// to assert that pk is a valid key for id.
+func userIdSignatureV3Hash(id string, pk signingKey, hfn crypto.Hash) (h hash.Hash, err error) {
+ if !hfn.Available() {
+ return nil, errors.UnsupportedError("hash function")
+ }
+ h = hfn.New()
+
+ // RFC 4880, section 5.2.4
+ pk.SerializeSignaturePrefix(h)
+ pk.serializeWithoutHeaders(h)
+
+ h.Write([]byte(id))
+
+ return
+}
+
+// KeyIdString returns the public key's fingerprint in capital hex
+// (e.g. "6C7EE1B8621CC013").
+func (pk *PublicKeyV3) KeyIdString() string {
+ return fmt.Sprintf("%X", pk.KeyId)
+}
+
+// KeyIdShortString returns the short form of public key's fingerprint
+// in capital hex, as shown by gpg --list-keys (e.g. "621CC013").
+func (pk *PublicKeyV3) KeyIdShortString() string {
+ return fmt.Sprintf("%X", pk.KeyId&0xFFFFFFFF)
+}
+
+// BitLength returns the bit length for the given public key.
+func (pk *PublicKeyV3) BitLength() (bitLength uint16, err error) {
+ switch pk.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+ bitLength = pk.n.bitLength
+ default:
+ err = errors.InvalidArgumentError("bad public-key algorithm")
+ }
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/public_key_v3_test.go b/local_crypto_patch/contents/openpgp/packet/public_key_v3_test.go
new file mode 100644
index 0000000000..e06405904b
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/public_key_v3_test.go
@@ -0,0 +1,82 @@
+// 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 packet
+
+import (
+ "bytes"
+ "encoding/hex"
+ "testing"
+ "time"
+)
+
+var pubKeyV3Test = struct {
+ hexFingerprint string
+ creationTime time.Time
+ pubKeyAlgo PublicKeyAlgorithm
+ keyId uint64
+ keyIdString string
+ keyIdShort string
+}{
+ "103BECF5BD1E837C89D19E98487767F7",
+ time.Unix(779753634, 0),
+ PubKeyAlgoRSA,
+ 0xDE0F188A5DA5E3C9,
+ "DE0F188A5DA5E3C9",
+ "5DA5E3C9"}
+
+func TestPublicKeyV3Read(t *testing.T) {
+ i, test := 0, pubKeyV3Test
+ packet, err := Read(v3KeyReader(t))
+ if err != nil {
+ t.Fatalf("#%d: Read error: %s", i, err)
+ }
+ pk, ok := packet.(*PublicKeyV3)
+ if !ok {
+ t.Fatalf("#%d: failed to parse, got: %#v", i, packet)
+ }
+ if pk.PubKeyAlgo != test.pubKeyAlgo {
+ t.Errorf("#%d: bad public key algorithm got:%x want:%x", i, pk.PubKeyAlgo, test.pubKeyAlgo)
+ }
+ if !pk.CreationTime.Equal(test.creationTime) {
+ t.Errorf("#%d: bad creation time got:%v want:%v", i, pk.CreationTime, test.creationTime)
+ }
+ expectedFingerprint, _ := hex.DecodeString(test.hexFingerprint)
+ if !bytes.Equal(expectedFingerprint, pk.Fingerprint[:]) {
+ t.Errorf("#%d: bad fingerprint got:%x want:%x", i, pk.Fingerprint[:], expectedFingerprint)
+ }
+ if pk.KeyId != test.keyId {
+ t.Errorf("#%d: bad keyid got:%x want:%x", i, pk.KeyId, test.keyId)
+ }
+ if g, e := pk.KeyIdString(), test.keyIdString; g != e {
+ t.Errorf("#%d: bad KeyIdString got:%q want:%q", i, g, e)
+ }
+ if g, e := pk.KeyIdShortString(), test.keyIdShort; g != e {
+ t.Errorf("#%d: bad KeyIdShortString got:%q want:%q", i, g, e)
+ }
+}
+
+func TestPublicKeyV3Serialize(t *testing.T) {
+ //for i, test := range pubKeyV3Tests {
+ i := 0
+ packet, err := Read(v3KeyReader(t))
+ if err != nil {
+ t.Fatalf("#%d: Read error: %s", i, err)
+ }
+ pk, ok := packet.(*PublicKeyV3)
+ if !ok {
+ t.Fatalf("#%d: failed to parse, got: %#v", i, packet)
+ }
+ var serializeBuf bytes.Buffer
+ if err = pk.Serialize(&serializeBuf); err != nil {
+ t.Fatalf("#%d: failed to serialize: %s", i, err)
+ }
+
+ if packet, err = Read(bytes.NewBuffer(serializeBuf.Bytes())); err != nil {
+ t.Fatalf("#%d: Read error (from serialized data): %s", i, err)
+ }
+ if pk, ok = packet.(*PublicKeyV3); !ok {
+ t.Fatalf("#%d: failed to parse serialized data, got: %#v", i, packet)
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/reader.go b/local_crypto_patch/contents/openpgp/packet/reader.go
new file mode 100644
index 0000000000..34bc7c613e
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/reader.go
@@ -0,0 +1,76 @@
+// 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 packet
+
+import (
+ "golang.org/x/crypto/openpgp/errors"
+ "io"
+)
+
+// Reader reads packets from an io.Reader and allows packets to be 'unread' so
+// that they result from the next call to Next.
+type Reader struct {
+ q []Packet
+ readers []io.Reader
+}
+
+// New io.Readers are pushed when a compressed or encrypted packet is processed
+// and recursively treated as a new source of packets. However, a carefully
+// crafted packet can trigger an infinite recursive sequence of packets. See
+// http://mumble.net/~campbell/misc/pgp-quine
+// https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-4402
+// This constant limits the number of recursive packets that may be pushed.
+const maxReaders = 32
+
+// Next returns the most recently unread Packet, or reads another packet from
+// the top-most io.Reader. Unknown packet types are skipped.
+func (r *Reader) Next() (p Packet, err error) {
+ if len(r.q) > 0 {
+ p = r.q[len(r.q)-1]
+ r.q = r.q[:len(r.q)-1]
+ return
+ }
+
+ for len(r.readers) > 0 {
+ p, err = Read(r.readers[len(r.readers)-1])
+ if err == nil {
+ return
+ }
+ if err == io.EOF {
+ r.readers = r.readers[:len(r.readers)-1]
+ continue
+ }
+ if _, ok := err.(errors.UnknownPacketTypeError); !ok {
+ return nil, err
+ }
+ }
+
+ return nil, io.EOF
+}
+
+// Push causes the Reader to start reading from a new io.Reader. When an EOF
+// error is seen from the new io.Reader, it is popped and the Reader continues
+// to read from the next most recent io.Reader. Push returns a StructuralError
+// if pushing the reader would exceed the maximum recursion level, otherwise it
+// returns nil.
+func (r *Reader) Push(reader io.Reader) (err error) {
+ if len(r.readers) >= maxReaders {
+ return errors.StructuralError("too many layers of packets")
+ }
+ r.readers = append(r.readers, reader)
+ return nil
+}
+
+// Unread causes the given Packet to be returned from the next call to Next.
+func (r *Reader) Unread(p Packet) {
+ r.q = append(r.q, p)
+}
+
+func NewReader(r io.Reader) *Reader {
+ return &Reader{
+ q: nil,
+ readers: []io.Reader{r},
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/signature.go b/local_crypto_patch/contents/openpgp/packet/signature.go
new file mode 100644
index 0000000000..b2a24a5323
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/signature.go
@@ -0,0 +1,731 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/dsa"
+ "crypto/ecdsa"
+ "encoding/asn1"
+ "encoding/binary"
+ "hash"
+ "io"
+ "math/big"
+ "strconv"
+ "time"
+
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/s2k"
+)
+
+const (
+ // See RFC 4880, section 5.2.3.21 for details.
+ KeyFlagCertify = 1 << iota
+ KeyFlagSign
+ KeyFlagEncryptCommunications
+ KeyFlagEncryptStorage
+)
+
+// Signature represents a signature. See RFC 4880, section 5.2.
+type Signature struct {
+ SigType SignatureType
+ PubKeyAlgo PublicKeyAlgorithm
+ Hash crypto.Hash
+
+ // HashSuffix is extra data that is hashed in after the signed data.
+ HashSuffix []byte
+ // HashTag contains the first two bytes of the hash for fast rejection
+ // of bad signed data.
+ HashTag [2]byte
+ CreationTime time.Time
+
+ RSASignature parsedMPI
+ DSASigR, DSASigS parsedMPI
+ ECDSASigR, ECDSASigS parsedMPI
+
+ // rawSubpackets contains the unparsed subpackets, in order.
+ rawSubpackets []outputSubpacket
+
+ // The following are optional so are nil when not included in the
+ // signature.
+
+ SigLifetimeSecs, KeyLifetimeSecs *uint32
+ PreferredSymmetric, PreferredHash, PreferredCompression []uint8
+ IssuerKeyId *uint64
+ IsPrimaryId *bool
+
+ // FlagsValid is set if any flags were given. See RFC 4880, section
+ // 5.2.3.21 for details.
+ FlagsValid bool
+ FlagCertify, FlagSign, FlagEncryptCommunications, FlagEncryptStorage bool
+
+ // RevocationReason is set if this signature has been revoked.
+ // See RFC 4880, section 5.2.3.23 for details.
+ RevocationReason *uint8
+ RevocationReasonText string
+
+ // MDC is set if this signature has a feature packet that indicates
+ // support for MDC subpackets.
+ MDC bool
+
+ // EmbeddedSignature, if non-nil, is a signature of the parent key, by
+ // this key. This prevents an attacker from claiming another's signing
+ // subkey as their own.
+ EmbeddedSignature *Signature
+
+ outSubpackets []outputSubpacket
+}
+
+func (sig *Signature) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.2.3
+ var buf [5]byte
+ _, err = readFull(r, buf[:1])
+ if err != nil {
+ return
+ }
+ if buf[0] != 4 {
+ err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
+ return
+ }
+
+ _, err = readFull(r, buf[:5])
+ if err != nil {
+ return
+ }
+ sig.SigType = SignatureType(buf[0])
+ sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1])
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA:
+ default:
+ err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
+ return
+ }
+
+ var ok bool
+ sig.Hash, ok = s2k.HashIdToHash(buf[2])
+ if !ok {
+ return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
+ }
+
+ hashedSubpacketsLength := int(buf[3])<<8 | int(buf[4])
+ l := 6 + hashedSubpacketsLength
+ sig.HashSuffix = make([]byte, l+6)
+ sig.HashSuffix[0] = 4
+ copy(sig.HashSuffix[1:], buf[:5])
+ hashedSubpackets := sig.HashSuffix[6:l]
+ _, err = readFull(r, hashedSubpackets)
+ if err != nil {
+ return
+ }
+ // See RFC 4880, section 5.2.4
+ trailer := sig.HashSuffix[l:]
+ trailer[0] = 4
+ trailer[1] = 0xff
+ trailer[2] = uint8(l >> 24)
+ trailer[3] = uint8(l >> 16)
+ trailer[4] = uint8(l >> 8)
+ trailer[5] = uint8(l)
+
+ err = parseSignatureSubpackets(sig, hashedSubpackets, true)
+ if err != nil {
+ return
+ }
+
+ _, err = readFull(r, buf[:2])
+ if err != nil {
+ return
+ }
+ unhashedSubpacketsLength := int(buf[0])<<8 | int(buf[1])
+ unhashedSubpackets := make([]byte, unhashedSubpacketsLength)
+ _, err = readFull(r, unhashedSubpackets)
+ if err != nil {
+ return
+ }
+ err = parseSignatureSubpackets(sig, unhashedSubpackets, false)
+ if err != nil {
+ return
+ }
+
+ _, err = readFull(r, sig.HashTag[:2])
+ if err != nil {
+ return
+ }
+
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r)
+ case PubKeyAlgoDSA:
+ sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r)
+ if err == nil {
+ sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r)
+ }
+ case PubKeyAlgoECDSA:
+ sig.ECDSASigR.bytes, sig.ECDSASigR.bitLength, err = readMPI(r)
+ if err == nil {
+ sig.ECDSASigS.bytes, sig.ECDSASigS.bitLength, err = readMPI(r)
+ }
+ default:
+ panic("unreachable")
+ }
+ return
+}
+
+// parseSignatureSubpackets parses subpackets of the main signature packet. See
+// RFC 4880, section 5.2.3.1.
+func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool) (err error) {
+ for len(subpackets) > 0 {
+ subpackets, err = parseSignatureSubpacket(sig, subpackets, isHashed)
+ if err != nil {
+ return
+ }
+ }
+
+ if sig.CreationTime.IsZero() {
+ err = errors.StructuralError("no creation time in signature")
+ }
+
+ return
+}
+
+type signatureSubpacketType uint8
+
+const (
+ creationTimeSubpacket signatureSubpacketType = 2
+ signatureExpirationSubpacket signatureSubpacketType = 3
+ keyExpirationSubpacket signatureSubpacketType = 9
+ prefSymmetricAlgosSubpacket signatureSubpacketType = 11
+ issuerSubpacket signatureSubpacketType = 16
+ prefHashAlgosSubpacket signatureSubpacketType = 21
+ prefCompressionSubpacket signatureSubpacketType = 22
+ primaryUserIdSubpacket signatureSubpacketType = 25
+ keyFlagsSubpacket signatureSubpacketType = 27
+ reasonForRevocationSubpacket signatureSubpacketType = 29
+ featuresSubpacket signatureSubpacketType = 30
+ embeddedSignatureSubpacket signatureSubpacketType = 32
+)
+
+// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1.
+func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (rest []byte, err error) {
+ // RFC 4880, section 5.2.3.1
+ var (
+ length uint32
+ packetType signatureSubpacketType
+ isCritical bool
+ )
+ switch {
+ case subpacket[0] < 192:
+ length = uint32(subpacket[0])
+ subpacket = subpacket[1:]
+ case subpacket[0] < 255:
+ if len(subpacket) < 2 {
+ goto Truncated
+ }
+ length = uint32(subpacket[0]-192)<<8 + uint32(subpacket[1]) + 192
+ subpacket = subpacket[2:]
+ default:
+ if len(subpacket) < 5 {
+ goto Truncated
+ }
+ length = uint32(subpacket[1])<<24 |
+ uint32(subpacket[2])<<16 |
+ uint32(subpacket[3])<<8 |
+ uint32(subpacket[4])
+ subpacket = subpacket[5:]
+ }
+ if length > uint32(len(subpacket)) {
+ goto Truncated
+ }
+ rest = subpacket[length:]
+ subpacket = subpacket[:length]
+ if len(subpacket) == 0 {
+ err = errors.StructuralError("zero length signature subpacket")
+ return
+ }
+ packetType = signatureSubpacketType(subpacket[0] & 0x7f)
+ isCritical = subpacket[0]&0x80 == 0x80
+ subpacket = subpacket[1:]
+ sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket})
+ switch packetType {
+ case creationTimeSubpacket:
+ if !isHashed {
+ err = errors.StructuralError("signature creation time in non-hashed area")
+ return
+ }
+ if len(subpacket) != 4 {
+ err = errors.StructuralError("signature creation time not four bytes")
+ return
+ }
+ t := binary.BigEndian.Uint32(subpacket)
+ sig.CreationTime = time.Unix(int64(t), 0)
+ case signatureExpirationSubpacket:
+ // Signature expiration time, section 5.2.3.10
+ if !isHashed {
+ return
+ }
+ if len(subpacket) != 4 {
+ err = errors.StructuralError("expiration subpacket with bad length")
+ return
+ }
+ sig.SigLifetimeSecs = new(uint32)
+ *sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket)
+ case keyExpirationSubpacket:
+ // Key expiration time, section 5.2.3.6
+ if !isHashed {
+ return
+ }
+ if len(subpacket) != 4 {
+ err = errors.StructuralError("key expiration subpacket with bad length")
+ return
+ }
+ sig.KeyLifetimeSecs = new(uint32)
+ *sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket)
+ case prefSymmetricAlgosSubpacket:
+ // Preferred symmetric algorithms, section 5.2.3.7
+ if !isHashed {
+ return
+ }
+ sig.PreferredSymmetric = make([]byte, len(subpacket))
+ copy(sig.PreferredSymmetric, subpacket)
+ case issuerSubpacket:
+ // Issuer, section 5.2.3.5
+ if len(subpacket) != 8 {
+ err = errors.StructuralError("issuer subpacket with bad length")
+ return
+ }
+ sig.IssuerKeyId = new(uint64)
+ *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket)
+ case prefHashAlgosSubpacket:
+ // Preferred hash algorithms, section 5.2.3.8
+ if !isHashed {
+ return
+ }
+ sig.PreferredHash = make([]byte, len(subpacket))
+ copy(sig.PreferredHash, subpacket)
+ case prefCompressionSubpacket:
+ // Preferred compression algorithms, section 5.2.3.9
+ if !isHashed {
+ return
+ }
+ sig.PreferredCompression = make([]byte, len(subpacket))
+ copy(sig.PreferredCompression, subpacket)
+ case primaryUserIdSubpacket:
+ // Primary User ID, section 5.2.3.19
+ if !isHashed {
+ return
+ }
+ if len(subpacket) != 1 {
+ err = errors.StructuralError("primary user id subpacket with bad length")
+ return
+ }
+ sig.IsPrimaryId = new(bool)
+ if subpacket[0] > 0 {
+ *sig.IsPrimaryId = true
+ }
+ case keyFlagsSubpacket:
+ // Key flags, section 5.2.3.21
+ if !isHashed {
+ return
+ }
+ if len(subpacket) == 0 {
+ err = errors.StructuralError("empty key flags subpacket")
+ return
+ }
+ sig.FlagsValid = true
+ if subpacket[0]&KeyFlagCertify != 0 {
+ sig.FlagCertify = true
+ }
+ if subpacket[0]&KeyFlagSign != 0 {
+ sig.FlagSign = true
+ }
+ if subpacket[0]&KeyFlagEncryptCommunications != 0 {
+ sig.FlagEncryptCommunications = true
+ }
+ if subpacket[0]&KeyFlagEncryptStorage != 0 {
+ sig.FlagEncryptStorage = true
+ }
+ case reasonForRevocationSubpacket:
+ // Reason For Revocation, section 5.2.3.23
+ if !isHashed {
+ return
+ }
+ if len(subpacket) == 0 {
+ err = errors.StructuralError("empty revocation reason subpacket")
+ return
+ }
+ sig.RevocationReason = new(uint8)
+ *sig.RevocationReason = subpacket[0]
+ sig.RevocationReasonText = string(subpacket[1:])
+ case featuresSubpacket:
+ // Features subpacket, section 5.2.3.24 specifies a very general
+ // mechanism for OpenPGP implementations to signal support for new
+ // features. In practice, the subpacket is used exclusively to
+ // indicate support for MDC-protected encryption.
+ sig.MDC = len(subpacket) >= 1 && subpacket[0]&1 == 1
+ case embeddedSignatureSubpacket:
+ // Only usage is in signatures that cross-certify
+ // signing subkeys. section 5.2.3.26 describes the
+ // format, with its usage described in section 11.1
+ if sig.EmbeddedSignature != nil {
+ err = errors.StructuralError("Cannot have multiple embedded signatures")
+ return
+ }
+ sig.EmbeddedSignature = new(Signature)
+ // Embedded signatures are required to be v4 signatures see
+ // section 12.1. However, we only parse v4 signatures in this
+ // file anyway.
+ if err := sig.EmbeddedSignature.parse(bytes.NewBuffer(subpacket)); err != nil {
+ return nil, err
+ }
+ if sigType := sig.EmbeddedSignature.SigType; sigType != SigTypePrimaryKeyBinding {
+ return nil, errors.StructuralError("cross-signature has unexpected type " + strconv.Itoa(int(sigType)))
+ }
+ default:
+ if isCritical {
+ err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType)))
+ return
+ }
+ }
+ return
+
+Truncated:
+ err = errors.StructuralError("signature subpacket truncated")
+ return
+}
+
+// subpacketLengthLength returns the length, in bytes, of an encoded length value.
+func subpacketLengthLength(length int) int {
+ if length < 192 {
+ return 1
+ }
+ if length < 16320 {
+ return 2
+ }
+ return 5
+}
+
+// serializeSubpacketLength marshals the given length into to.
+func serializeSubpacketLength(to []byte, length int) int {
+ // RFC 4880, Section 4.2.2.
+ if length < 192 {
+ to[0] = byte(length)
+ return 1
+ }
+ if length < 16320 {
+ length -= 192
+ to[0] = byte((length >> 8) + 192)
+ to[1] = byte(length)
+ return 2
+ }
+ to[0] = 255
+ to[1] = byte(length >> 24)
+ to[2] = byte(length >> 16)
+ to[3] = byte(length >> 8)
+ to[4] = byte(length)
+ return 5
+}
+
+// subpacketsLength returns the serialized length, in bytes, of the given
+// subpackets.
+func subpacketsLength(subpackets []outputSubpacket, hashed bool) (length int) {
+ for _, subpacket := range subpackets {
+ if subpacket.hashed == hashed {
+ length += subpacketLengthLength(len(subpacket.contents) + 1)
+ length += 1 // type byte
+ length += len(subpacket.contents)
+ }
+ }
+ return
+}
+
+// serializeSubpackets marshals the given subpackets into to.
+func serializeSubpackets(to []byte, subpackets []outputSubpacket, hashed bool) {
+ for _, subpacket := range subpackets {
+ if subpacket.hashed == hashed {
+ n := serializeSubpacketLength(to, len(subpacket.contents)+1)
+ to[n] = byte(subpacket.subpacketType)
+ to = to[1+n:]
+ n = copy(to, subpacket.contents)
+ to = to[n:]
+ }
+ }
+ return
+}
+
+// KeyExpired returns whether sig is a self-signature of a key that has
+// expired.
+func (sig *Signature) KeyExpired(currentTime time.Time) bool {
+ if sig.KeyLifetimeSecs == nil {
+ return false
+ }
+ expiry := sig.CreationTime.Add(time.Duration(*sig.KeyLifetimeSecs) * time.Second)
+ return currentTime.After(expiry)
+}
+
+// buildHashSuffix constructs the HashSuffix member of sig in preparation for signing.
+func (sig *Signature) buildHashSuffix() (err error) {
+ hashedSubpacketsLen := subpacketsLength(sig.outSubpackets, true)
+
+ var ok bool
+ l := 6 + hashedSubpacketsLen
+ sig.HashSuffix = make([]byte, l+6)
+ sig.HashSuffix[0] = 4
+ sig.HashSuffix[1] = uint8(sig.SigType)
+ sig.HashSuffix[2] = uint8(sig.PubKeyAlgo)
+ sig.HashSuffix[3], ok = s2k.HashToHashId(sig.Hash)
+ if !ok {
+ sig.HashSuffix = nil
+ return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash)))
+ }
+ sig.HashSuffix[4] = byte(hashedSubpacketsLen >> 8)
+ sig.HashSuffix[5] = byte(hashedSubpacketsLen)
+ serializeSubpackets(sig.HashSuffix[6:l], sig.outSubpackets, true)
+ trailer := sig.HashSuffix[l:]
+ trailer[0] = 4
+ trailer[1] = 0xff
+ trailer[2] = byte(l >> 24)
+ trailer[3] = byte(l >> 16)
+ trailer[4] = byte(l >> 8)
+ trailer[5] = byte(l)
+ return
+}
+
+func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) {
+ err = sig.buildHashSuffix()
+ if err != nil {
+ return
+ }
+
+ h.Write(sig.HashSuffix)
+ digest = h.Sum(nil)
+ copy(sig.HashTag[:], digest)
+ return
+}
+
+// Sign signs a message with a private key. The hash, h, must contain
+// the hash of the message to be signed and will be mutated by this function.
+// On success, the signature is stored in sig. Call Serialize to write it out.
+// If config is nil, sensible defaults will be used.
+func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err error) {
+ sig.outSubpackets = sig.buildSubpackets()
+ digest, err := sig.signPrepareHash(h)
+ if err != nil {
+ return
+ }
+
+ switch priv.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ // supports both *rsa.PrivateKey and crypto.Signer
+ sig.RSASignature.bytes, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, sig.Hash)
+ sig.RSASignature.bitLength = uint16(8 * len(sig.RSASignature.bytes))
+ case PubKeyAlgoDSA:
+ dsaPriv := priv.PrivateKey.(*dsa.PrivateKey)
+
+ // Need to truncate hashBytes to match FIPS 186-3 section 4.6.
+ subgroupSize := (dsaPriv.Q.BitLen() + 7) / 8
+ if len(digest) > subgroupSize {
+ digest = digest[:subgroupSize]
+ }
+ r, s, err := dsa.Sign(config.Random(), dsaPriv, digest)
+ if err == nil {
+ sig.DSASigR.bytes = r.Bytes()
+ sig.DSASigR.bitLength = uint16(8 * len(sig.DSASigR.bytes))
+ sig.DSASigS.bytes = s.Bytes()
+ sig.DSASigS.bitLength = uint16(8 * len(sig.DSASigS.bytes))
+ }
+ case PubKeyAlgoECDSA:
+ var r, s *big.Int
+ if pk, ok := priv.PrivateKey.(*ecdsa.PrivateKey); ok {
+ // direct support, avoid asn1 wrapping/unwrapping
+ r, s, err = ecdsa.Sign(config.Random(), pk, digest)
+ } else {
+ var b []byte
+ b, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, sig.Hash)
+ if err == nil {
+ r, s, err = unwrapECDSASig(b)
+ }
+ }
+ if err == nil {
+ sig.ECDSASigR = fromBig(r)
+ sig.ECDSASigS = fromBig(s)
+ }
+ default:
+ err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
+ }
+
+ return
+}
+
+// unwrapECDSASig parses the two integer components of an ASN.1-encoded ECDSA
+// signature.
+func unwrapECDSASig(b []byte) (r, s *big.Int, err error) {
+ var ecsdaSig struct {
+ R, S *big.Int
+ }
+ _, err = asn1.Unmarshal(b, &ecsdaSig)
+ if err != nil {
+ return
+ }
+ return ecsdaSig.R, ecsdaSig.S, nil
+}
+
+// SignUserId computes a signature from priv, asserting that pub is a valid
+// key for the identity id. On success, the signature is stored in sig. Call
+// Serialize to write it out.
+// If config is nil, sensible defaults will be used.
+func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, config *Config) error {
+ h, err := userIdSignatureHash(id, pub, sig.Hash)
+ if err != nil {
+ return err
+ }
+ return sig.Sign(h, priv, config)
+}
+
+// SignKey computes a signature from priv, asserting that pub is a subkey. On
+// success, the signature is stored in sig. Call Serialize to write it out.
+// If config is nil, sensible defaults will be used.
+func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) error {
+ h, err := keySignatureHash(&priv.PublicKey, pub, sig.Hash)
+ if err != nil {
+ return err
+ }
+ return sig.Sign(h, priv, config)
+}
+
+// Serialize marshals sig to w. Sign, SignUserId or SignKey must have been
+// called first.
+func (sig *Signature) Serialize(w io.Writer) (err error) {
+ if len(sig.outSubpackets) == 0 {
+ sig.outSubpackets = sig.rawSubpackets
+ }
+ if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil && sig.ECDSASigR.bytes == nil {
+ return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize")
+ }
+
+ sigLength := 0
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ sigLength = 2 + len(sig.RSASignature.bytes)
+ case PubKeyAlgoDSA:
+ sigLength = 2 + len(sig.DSASigR.bytes)
+ sigLength += 2 + len(sig.DSASigS.bytes)
+ case PubKeyAlgoECDSA:
+ sigLength = 2 + len(sig.ECDSASigR.bytes)
+ sigLength += 2 + len(sig.ECDSASigS.bytes)
+ default:
+ panic("impossible")
+ }
+
+ unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false)
+ length := len(sig.HashSuffix) - 6 /* trailer not included */ +
+ 2 /* length of unhashed subpackets */ + unhashedSubpacketsLen +
+ 2 /* hash tag */ + sigLength
+ err = serializeHeader(w, packetTypeSignature, length)
+ if err != nil {
+ return
+ }
+
+ _, err = w.Write(sig.HashSuffix[:len(sig.HashSuffix)-6])
+ if err != nil {
+ return
+ }
+
+ unhashedSubpackets := make([]byte, 2+unhashedSubpacketsLen)
+ unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 8)
+ unhashedSubpackets[1] = byte(unhashedSubpacketsLen)
+ serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false)
+
+ _, err = w.Write(unhashedSubpackets)
+ if err != nil {
+ return
+ }
+ _, err = w.Write(sig.HashTag[:])
+ if err != nil {
+ return
+ }
+
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ err = writeMPIs(w, sig.RSASignature)
+ case PubKeyAlgoDSA:
+ err = writeMPIs(w, sig.DSASigR, sig.DSASigS)
+ case PubKeyAlgoECDSA:
+ err = writeMPIs(w, sig.ECDSASigR, sig.ECDSASigS)
+ default:
+ panic("impossible")
+ }
+ return
+}
+
+// outputSubpacket represents a subpacket to be marshaled.
+type outputSubpacket struct {
+ hashed bool // true if this subpacket is in the hashed area.
+ subpacketType signatureSubpacketType
+ isCritical bool
+ contents []byte
+}
+
+func (sig *Signature) buildSubpackets() (subpackets []outputSubpacket) {
+ creationTime := make([]byte, 4)
+ binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix()))
+ subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, false, creationTime})
+
+ if sig.IssuerKeyId != nil {
+ keyId := make([]byte, 8)
+ binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId)
+ subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId})
+ }
+
+ if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
+ sigLifetime := make([]byte, 4)
+ binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs)
+ subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime})
+ }
+
+ // Key flags may only appear in self-signatures or certification signatures.
+
+ if sig.FlagsValid {
+ var flags byte
+ if sig.FlagCertify {
+ flags |= KeyFlagCertify
+ }
+ if sig.FlagSign {
+ flags |= KeyFlagSign
+ }
+ if sig.FlagEncryptCommunications {
+ flags |= KeyFlagEncryptCommunications
+ }
+ if sig.FlagEncryptStorage {
+ flags |= KeyFlagEncryptStorage
+ }
+ subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}})
+ }
+
+ // The following subpackets may only appear in self-signatures
+
+ if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 {
+ keyLifetime := make([]byte, 4)
+ binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs)
+ subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime})
+ }
+
+ if sig.IsPrimaryId != nil && *sig.IsPrimaryId {
+ subpackets = append(subpackets, outputSubpacket{true, primaryUserIdSubpacket, false, []byte{1}})
+ }
+
+ if len(sig.PreferredSymmetric) > 0 {
+ subpackets = append(subpackets, outputSubpacket{true, prefSymmetricAlgosSubpacket, false, sig.PreferredSymmetric})
+ }
+
+ if len(sig.PreferredHash) > 0 {
+ subpackets = append(subpackets, outputSubpacket{true, prefHashAlgosSubpacket, false, sig.PreferredHash})
+ }
+
+ if len(sig.PreferredCompression) > 0 {
+ subpackets = append(subpackets, outputSubpacket{true, prefCompressionSubpacket, false, sig.PreferredCompression})
+ }
+
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/signature_test.go b/local_crypto_patch/contents/openpgp/packet/signature_test.go
new file mode 100644
index 0000000000..56e761179d
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/signature_test.go
@@ -0,0 +1,78 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto"
+ "encoding/hex"
+ "testing"
+)
+
+func TestSignatureRead(t *testing.T) {
+ packet, err := Read(readerFromHex(signatureDataHex))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ sig, ok := packet.(*Signature)
+ if !ok || sig.SigType != SigTypeBinary || sig.PubKeyAlgo != PubKeyAlgoRSA || sig.Hash != crypto.SHA1 {
+ t.Errorf("failed to parse, got: %#v", packet)
+ }
+}
+
+func TestSignatureReserialize(t *testing.T) {
+ packet, _ := Read(readerFromHex(signatureDataHex))
+ sig := packet.(*Signature)
+ out := new(bytes.Buffer)
+ err := sig.Serialize(out)
+ if err != nil {
+ t.Errorf("error reserializing: %s", err)
+ return
+ }
+
+ expected, _ := hex.DecodeString(signatureDataHex)
+ if !bytes.Equal(expected, out.Bytes()) {
+ t.Errorf("output doesn't match input (got vs expected):\n%s\n%s", hex.Dump(out.Bytes()), hex.Dump(expected))
+ }
+}
+
+func TestSignUserId(t *testing.T) {
+ sig := &Signature{
+ SigType: SigTypeGenericCert,
+ PubKeyAlgo: PubKeyAlgoRSA,
+ Hash: 0, // invalid hash function
+ }
+
+ packet, err := Read(readerFromHex(rsaPkDataHex))
+ if err != nil {
+ t.Fatalf("failed to deserialize public key: %v", err)
+ }
+ pubKey := packet.(*PublicKey)
+
+ packet, err = Read(readerFromHex(privKeyRSAHex))
+ if err != nil {
+ t.Fatalf("failed to deserialize private key: %v", err)
+ }
+ privKey := packet.(*PrivateKey)
+
+ err = sig.SignUserId("", pubKey, privKey, nil)
+ if err == nil {
+ t.Errorf("did not receive an error when expected")
+ }
+
+ sig.Hash = crypto.SHA256
+ err = privKey.Decrypt([]byte("testing"))
+ if err != nil {
+ t.Fatalf("failed to decrypt private key: %v", err)
+ }
+
+ err = sig.SignUserId("", pubKey, privKey, nil)
+ if err != nil {
+ t.Errorf("failed to sign user id: %v", err)
+ }
+}
+
+const signatureDataHex = "c2c05c04000102000605024cb45112000a0910ab105c91af38fb158f8d07ff5596ea368c5efe015bed6e78348c0f033c931d5f2ce5db54ce7f2a7e4b4ad64db758d65a7a71773edeab7ba2a9e0908e6a94a1175edd86c1d843279f045b021a6971a72702fcbd650efc393c5474d5b59a15f96d2eaad4c4c426797e0dcca2803ef41c6ff234d403eec38f31d610c344c06f2401c262f0993b2e66cad8a81ebc4322c723e0d4ba09fe917e8777658307ad8329adacba821420741009dfe87f007759f0982275d028a392c6ed983a0d846f890b36148c7358bdb8a516007fac760261ecd06076813831a36d0459075d1befa245ae7f7fb103d92ca759e9498fe60ef8078a39a3beda510deea251ea9f0a7f0df6ef42060f20780360686f3e400e"
diff --git a/local_crypto_patch/contents/openpgp/packet/signature_v3.go b/local_crypto_patch/contents/openpgp/packet/signature_v3.go
new file mode 100644
index 0000000000..6edff88934
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/signature_v3.go
@@ -0,0 +1,146 @@
+// 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 packet
+
+import (
+ "crypto"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "strconv"
+ "time"
+
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/s2k"
+)
+
+// SignatureV3 represents older version 3 signatures. These signatures are less secure
+// than version 4 and should not be used to create new signatures. They are included
+// here for backwards compatibility to read and validate with older key material.
+// See RFC 4880, section 5.2.2.
+type SignatureV3 struct {
+ SigType SignatureType
+ CreationTime time.Time
+ IssuerKeyId uint64
+ PubKeyAlgo PublicKeyAlgorithm
+ Hash crypto.Hash
+ HashTag [2]byte
+
+ RSASignature parsedMPI
+ DSASigR, DSASigS parsedMPI
+}
+
+func (sig *SignatureV3) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.2.2
+ var buf [8]byte
+ if _, err = readFull(r, buf[:1]); err != nil {
+ return
+ }
+ if buf[0] < 2 || buf[0] > 3 {
+ err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
+ return
+ }
+ if _, err = readFull(r, buf[:1]); err != nil {
+ return
+ }
+ if buf[0] != 5 {
+ err = errors.UnsupportedError(
+ "invalid hashed material length " + strconv.Itoa(int(buf[0])))
+ return
+ }
+
+ // Read hashed material: signature type + creation time
+ if _, err = readFull(r, buf[:5]); err != nil {
+ return
+ }
+ sig.SigType = SignatureType(buf[0])
+ t := binary.BigEndian.Uint32(buf[1:5])
+ sig.CreationTime = time.Unix(int64(t), 0)
+
+ // Eight-octet Key ID of signer.
+ if _, err = readFull(r, buf[:8]); err != nil {
+ return
+ }
+ sig.IssuerKeyId = binary.BigEndian.Uint64(buf[:])
+
+ // Public-key and hash algorithm
+ if _, err = readFull(r, buf[:2]); err != nil {
+ return
+ }
+ sig.PubKeyAlgo = PublicKeyAlgorithm(buf[0])
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA:
+ default:
+ err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
+ return
+ }
+ var ok bool
+ if sig.Hash, ok = s2k.HashIdToHash(buf[1]); !ok {
+ return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
+ }
+
+ // Two-octet field holding left 16 bits of signed hash value.
+ if _, err = readFull(r, sig.HashTag[:2]); err != nil {
+ return
+ }
+
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r)
+ case PubKeyAlgoDSA:
+ if sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r); err != nil {
+ return
+ }
+ sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r)
+ default:
+ panic("unreachable")
+ }
+ return
+}
+
+// Serialize marshals sig to w. Sign, SignUserId or SignKey must have been
+// called first.
+func (sig *SignatureV3) Serialize(w io.Writer) (err error) {
+ buf := make([]byte, 8)
+
+ // Write the sig type and creation time
+ buf[0] = byte(sig.SigType)
+ binary.BigEndian.PutUint32(buf[1:5], uint32(sig.CreationTime.Unix()))
+ if _, err = w.Write(buf[:5]); err != nil {
+ return
+ }
+
+ // Write the issuer long key ID
+ binary.BigEndian.PutUint64(buf[:8], sig.IssuerKeyId)
+ if _, err = w.Write(buf[:8]); err != nil {
+ return
+ }
+
+ // Write public key algorithm, hash ID, and hash value
+ buf[0] = byte(sig.PubKeyAlgo)
+ hashId, ok := s2k.HashToHashId(sig.Hash)
+ if !ok {
+ return errors.UnsupportedError(fmt.Sprintf("hash function %v", sig.Hash))
+ }
+ buf[1] = hashId
+ copy(buf[2:4], sig.HashTag[:])
+ if _, err = w.Write(buf[:4]); err != nil {
+ return
+ }
+
+ if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil {
+ return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize")
+ }
+
+ switch sig.PubKeyAlgo {
+ case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+ err = writeMPIs(w, sig.RSASignature)
+ case PubKeyAlgoDSA:
+ err = writeMPIs(w, sig.DSASigR, sig.DSASigS)
+ default:
+ panic("impossible")
+ }
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/signature_v3_test.go b/local_crypto_patch/contents/openpgp/packet/signature_v3_test.go
new file mode 100644
index 0000000000..abb2d8c144
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/signature_v3_test.go
@@ -0,0 +1,96 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto"
+ "encoding/hex"
+ "io"
+ "testing"
+
+ "golang.org/x/crypto/openpgp/armor"
+)
+
+func TestSignatureV3Read(t *testing.T) {
+ r := v3KeyReader(t)
+ Read(r) // Skip public key
+ Read(r) // Skip uid
+ packet, err := Read(r) // Signature
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ sig, ok := packet.(*SignatureV3)
+ if !ok || sig.SigType != SigTypeGenericCert || sig.PubKeyAlgo != PubKeyAlgoRSA || sig.Hash != crypto.MD5 {
+ t.Errorf("failed to parse, got: %#v", packet)
+ }
+}
+
+func TestSignatureV3Reserialize(t *testing.T) {
+ r := v3KeyReader(t)
+ Read(r) // Skip public key
+ Read(r) // Skip uid
+ packet, err := Read(r)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ sig := packet.(*SignatureV3)
+ out := new(bytes.Buffer)
+ if err = sig.Serialize(out); err != nil {
+ t.Errorf("error reserializing: %s", err)
+ return
+ }
+ expected, err := io.ReadAll(v3KeyReader(t))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ expected = expected[4+141+4+39:] // See pgpdump offsets below, this is where the sig starts
+ if !bytes.Equal(expected, out.Bytes()) {
+ t.Errorf("output doesn't match input (got vs expected):\n%s\n%s", hex.Dump(out.Bytes()), hex.Dump(expected))
+ }
+}
+
+func v3KeyReader(t *testing.T) io.Reader {
+ armorBlock, err := armor.Decode(bytes.NewBufferString(keySigV3Armor))
+ if err != nil {
+ t.Fatalf("armor Decode failed: %v", err)
+ }
+ return armorBlock.Body
+}
+
+// keySigV3Armor is some V3 public key I found in an SKS dump.
+// Old: Public Key Packet(tag 6)(141 bytes)
+//
+// Ver 4 - new
+// Public key creation time - Fri Sep 16 17:13:54 CDT 1994
+// Pub alg - unknown(pub 0)
+// Unknown public key(pub 0)
+//
+// Old: User ID Packet(tag 13)(39 bytes)
+//
+// User ID - Armin M. Warda
+//
+// Old: Signature Packet(tag 2)(149 bytes)
+//
+// Ver 4 - new
+// Sig type - unknown(05)
+// Pub alg - ElGamal Encrypt-Only(pub 16)
+// Hash alg - unknown(hash 46)
+// Hashed Sub: unknown(sub 81, critical)(1988 bytes)
+const keySigV3Armor = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: SKS 1.0.10
+
+mI0CLnoYogAAAQQA1qwA2SuJwfQ5bCQ6u5t20ulnOtY0gykf7YjiK4LiVeRBwHjGq7v30tGV
+5Qti7qqRW4Ww7CDCJc4sZMFnystucR2vLkXaSoNWoFm4Fg47NiisDdhDezHwbVPW6OpCFNSi
+ZAamtj4QAUBu8j4LswafrJqZqR9336/V3g8Yil2l48kABRG0J0FybWluIE0uIFdhcmRhIDx3
+YXJkYUBuZXBoaWxpbS5ydWhyLmRlPoiVAgUQLok2xwXR6zmeWEiZAQE/DgP/WgxPQh40/Po4
+gSkWZCDAjNdph7zexvAb0CcUWahcwiBIgg3U5ErCx9I5CNVA9U+s8bNrDZwgSIeBzp3KhWUx
+524uhGgm6ZUTOAIKA6CbV6pfqoLpJnRYvXYQU5mIWsNa99wcu2qu18OeEDnztb7aLA6Ra9OF
+YFCbq4EjXRoOrYM=
+=LPjs
+-----END PGP PUBLIC KEY BLOCK-----`
diff --git a/local_crypto_patch/contents/openpgp/packet/symmetric_key_encrypted.go b/local_crypto_patch/contents/openpgp/packet/symmetric_key_encrypted.go
new file mode 100644
index 0000000000..744c2d2c42
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/symmetric_key_encrypted.go
@@ -0,0 +1,155 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto/cipher"
+ "io"
+ "strconv"
+
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/s2k"
+)
+
+// This is the largest session key that we'll support. Since no 512-bit cipher
+// has even been seriously used, this is comfortably large.
+const maxSessionKeySizeInBytes = 64
+
+// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC
+// 4880, section 5.3.
+type SymmetricKeyEncrypted struct {
+ CipherFunc CipherFunction
+ s2k func(out, in []byte)
+ encryptedKey []byte
+}
+
+const symmetricKeyEncryptedVersion = 4
+
+func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
+ // RFC 4880, section 5.3.
+ var buf [2]byte
+ if _, err := readFull(r, buf[:]); err != nil {
+ return err
+ }
+ if buf[0] != symmetricKeyEncryptedVersion {
+ return errors.UnsupportedError("SymmetricKeyEncrypted version")
+ }
+ ske.CipherFunc = CipherFunction(buf[1])
+
+ if ske.CipherFunc.KeySize() == 0 {
+ return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1])))
+ }
+
+ var err error
+ ske.s2k, err = s2k.Parse(r)
+ if err != nil {
+ return err
+ }
+
+ encryptedKey := make([]byte, maxSessionKeySizeInBytes)
+ // The session key may follow. We just have to try and read to find
+ // out. If it exists then we limit it to maxSessionKeySizeInBytes.
+ n, err := readFull(r, encryptedKey)
+ if err != nil && err != io.ErrUnexpectedEOF {
+ return err
+ }
+
+ if n != 0 {
+ if n == maxSessionKeySizeInBytes {
+ return errors.UnsupportedError("oversized encrypted session key")
+ }
+ ske.encryptedKey = encryptedKey[:n]
+ }
+
+ return nil
+}
+
+// Decrypt attempts to decrypt an encrypted session key and returns the key and
+// the cipher to use when decrypting a subsequent Symmetrically Encrypted Data
+// packet.
+func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) ([]byte, CipherFunction, error) {
+ key := make([]byte, ske.CipherFunc.KeySize())
+ ske.s2k(key, passphrase)
+
+ if len(ske.encryptedKey) == 0 {
+ return key, ske.CipherFunc, nil
+ }
+
+ // the IV is all zeros
+ iv := make([]byte, ske.CipherFunc.blockSize())
+ c := cipher.NewCFBDecrypter(ske.CipherFunc.new(key), iv)
+ plaintextKey := make([]byte, len(ske.encryptedKey))
+ c.XORKeyStream(plaintextKey, ske.encryptedKey)
+ cipherFunc := CipherFunction(plaintextKey[0])
+ if cipherFunc.blockSize() == 0 {
+ return nil, ske.CipherFunc, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
+ }
+ plaintextKey = plaintextKey[1:]
+ if l, cipherKeySize := len(plaintextKey), cipherFunc.KeySize(); l != cipherFunc.KeySize() {
+ return nil, cipherFunc, errors.StructuralError("length of decrypted key (" + strconv.Itoa(l) + ") " +
+ "not equal to cipher keysize (" + strconv.Itoa(cipherKeySize) + ")")
+ }
+ return plaintextKey, cipherFunc, nil
+}
+
+// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w. The
+// packet contains a random session key, encrypted by a key derived from the
+// given passphrase. The session key is returned and must be passed to
+// SerializeSymmetricallyEncrypted.
+// If config is nil, sensible defaults will be used.
+func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Config) (key []byte, err error) {
+ cipherFunc := config.Cipher()
+ keySize := cipherFunc.KeySize()
+ if keySize == 0 {
+ return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
+ }
+
+ s2kBuf := new(bytes.Buffer)
+ keyEncryptingKey := make([]byte, keySize)
+ // s2k.Serialize salts and stretches the passphrase, and writes the
+ // resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf.
+ err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash(), S2KCount: config.PasswordHashIterations()})
+ if err != nil {
+ return
+ }
+ s2kBytes := s2kBuf.Bytes()
+
+ packetLength := 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize
+ err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength)
+ if err != nil {
+ return
+ }
+
+ var buf [2]byte
+ buf[0] = symmetricKeyEncryptedVersion
+ buf[1] = byte(cipherFunc)
+ _, err = w.Write(buf[:])
+ if err != nil {
+ return
+ }
+ _, err = w.Write(s2kBytes)
+ if err != nil {
+ return
+ }
+
+ sessionKey := make([]byte, keySize)
+ _, err = io.ReadFull(config.Random(), sessionKey)
+ if err != nil {
+ return
+ }
+ iv := make([]byte, cipherFunc.blockSize())
+ c := cipher.NewCFBEncrypter(cipherFunc.new(keyEncryptingKey), iv)
+ encryptedCipherAndKey := make([]byte, keySize+1)
+ c.XORKeyStream(encryptedCipherAndKey, buf[1:])
+ c.XORKeyStream(encryptedCipherAndKey[1:], sessionKey)
+ _, err = w.Write(encryptedCipherAndKey)
+ if err != nil {
+ return
+ }
+
+ key = sessionKey
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/symmetric_key_encrypted_test.go b/local_crypto_patch/contents/openpgp/packet/symmetric_key_encrypted_test.go
new file mode 100644
index 0000000000..5471d77706
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/symmetric_key_encrypted_test.go
@@ -0,0 +1,116 @@
+// 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 packet
+
+import (
+ "bytes"
+ "encoding/hex"
+ "io"
+ "testing"
+)
+
+func TestSymmetricKeyEncrypted(t *testing.T) {
+ buf := readerFromHex(symmetricallyEncryptedHex)
+ packet, err := Read(buf)
+ if err != nil {
+ t.Errorf("failed to read SymmetricKeyEncrypted: %s", err)
+ return
+ }
+ ske, ok := packet.(*SymmetricKeyEncrypted)
+ if !ok {
+ t.Error("didn't find SymmetricKeyEncrypted packet")
+ return
+ }
+ key, cipherFunc, err := ske.Decrypt([]byte("password"))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ packet, err = Read(buf)
+ if err != nil {
+ t.Errorf("failed to read SymmetricallyEncrypted: %s", err)
+ return
+ }
+ se, ok := packet.(*SymmetricallyEncrypted)
+ if !ok {
+ t.Error("didn't find SymmetricallyEncrypted packet")
+ return
+ }
+ r, err := se.Decrypt(cipherFunc, key)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ contents, err := io.ReadAll(r)
+ if err != nil && err != io.EOF {
+ t.Error(err)
+ return
+ }
+
+ expectedContents, _ := hex.DecodeString(symmetricallyEncryptedContentsHex)
+ if !bytes.Equal(expectedContents, contents) {
+ t.Errorf("bad contents got:%x want:%x", contents, expectedContents)
+ }
+}
+
+const symmetricallyEncryptedHex = "8c0d04030302371a0b38d884f02060c91cf97c9973b8e58e028e9501708ccfe618fb92afef7fa2d80ddadd93cf"
+const symmetricallyEncryptedContentsHex = "cb1062004d14c4df636f6e74656e74732e0a"
+
+func TestSerializeSymmetricKeyEncryptedCiphers(t *testing.T) {
+ tests := [...]struct {
+ cipherFunc CipherFunction
+ name string
+ }{
+ {Cipher3DES, "Cipher3DES"},
+ {CipherCAST5, "CipherCAST5"},
+ {CipherAES128, "CipherAES128"},
+ {CipherAES192, "CipherAES192"},
+ {CipherAES256, "CipherAES256"},
+ }
+
+ for _, test := range tests {
+ var buf bytes.Buffer
+ passphrase := []byte("testing")
+ config := &Config{
+ DefaultCipher: test.cipherFunc,
+ }
+
+ key, err := SerializeSymmetricKeyEncrypted(&buf, passphrase, config)
+ if err != nil {
+ t.Errorf("cipher(%s) failed to serialize: %s", test.name, err)
+ continue
+ }
+
+ p, err := Read(&buf)
+ if err != nil {
+ t.Errorf("cipher(%s) failed to reparse: %s", test.name, err)
+ continue
+ }
+
+ ske, ok := p.(*SymmetricKeyEncrypted)
+ if !ok {
+ t.Errorf("cipher(%s) parsed a different packet type: %#v", test.name, p)
+ continue
+ }
+
+ if ske.CipherFunc != config.DefaultCipher {
+ t.Errorf("cipher(%s) SKE cipher function is %d (expected %d)", test.name, ske.CipherFunc, config.DefaultCipher)
+ }
+ parsedKey, parsedCipherFunc, err := ske.Decrypt(passphrase)
+ if err != nil {
+ t.Errorf("cipher(%s) failed to decrypt reparsed SKE: %s", test.name, err)
+ continue
+ }
+ if !bytes.Equal(key, parsedKey) {
+ t.Errorf("cipher(%s) keys don't match after Decrypt: %x (original) vs %x (parsed)", test.name, key, parsedKey)
+ }
+ if parsedCipherFunc != test.cipherFunc {
+ t.Errorf("cipher(%s) cipher function doesn't match after Decrypt: %d (original) vs %d (parsed)",
+ test.name, test.cipherFunc, parsedCipherFunc)
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/symmetrically_encrypted.go b/local_crypto_patch/contents/openpgp/packet/symmetrically_encrypted.go
new file mode 100644
index 0000000000..1a1a62964f
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/symmetrically_encrypted.go
@@ -0,0 +1,290 @@
+// 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 packet
+
+import (
+ "crypto/cipher"
+ "crypto/sha1"
+ "crypto/subtle"
+ "golang.org/x/crypto/openpgp/errors"
+ "hash"
+ "io"
+ "strconv"
+)
+
+// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The
+// encrypted contents will consist of more OpenPGP packets. See RFC 4880,
+// sections 5.7 and 5.13.
+type SymmetricallyEncrypted struct {
+ MDC bool // true iff this is a type 18 packet and thus has an embedded MAC.
+ contents io.Reader
+ prefix []byte
+}
+
+const symmetricallyEncryptedVersion = 1
+
+func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
+ if se.MDC {
+ // See RFC 4880, section 5.13.
+ var buf [1]byte
+ _, err := readFull(r, buf[:])
+ if err != nil {
+ return err
+ }
+ if buf[0] != symmetricallyEncryptedVersion {
+ return errors.UnsupportedError("unknown SymmetricallyEncrypted version")
+ }
+ }
+ se.contents = r
+ return nil
+}
+
+// Decrypt returns a ReadCloser, from which the decrypted contents of the
+// packet can be read. An incorrect key can, with high probability, be detected
+// immediately and this will result in a KeyIncorrect error being returned.
+func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) {
+ keySize := c.KeySize()
+ if keySize == 0 {
+ return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c)))
+ }
+ if len(key) != keySize {
+ return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
+ }
+
+ if se.prefix == nil {
+ se.prefix = make([]byte, c.blockSize()+2)
+ _, err := readFull(se.contents, se.prefix)
+ if err != nil {
+ return nil, err
+ }
+ } else if len(se.prefix) != c.blockSize()+2 {
+ return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths")
+ }
+
+ ocfbResync := OCFBResync
+ if se.MDC {
+ // MDC packets use a different form of OCFB mode.
+ ocfbResync = OCFBNoResync
+ }
+
+ s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync)
+ if s == nil {
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ plaintext := cipher.StreamReader{S: s, R: se.contents}
+
+ if se.MDC {
+ // MDC packets have an embedded hash that we need to check.
+ h := sha1.New()
+ h.Write(se.prefix)
+ return &seMDCReader{in: plaintext, h: h}, nil
+ }
+
+ // Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser.
+ return seReader{plaintext}, nil
+}
+
+// seReader wraps an io.Reader with a no-op Close method.
+type seReader struct {
+ in io.Reader
+}
+
+func (ser seReader) Read(buf []byte) (int, error) {
+ return ser.in.Read(buf)
+}
+
+func (ser seReader) Close() error {
+ return nil
+}
+
+const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size
+
+// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold
+// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an
+// MDC packet containing a hash of the previous contents which is checked
+// against the running hash. See RFC 4880, section 5.13.
+type seMDCReader struct {
+ in io.Reader
+ h hash.Hash
+ trailer [mdcTrailerSize]byte
+ scratch [mdcTrailerSize]byte
+ trailerUsed int
+ error bool
+ eof bool
+}
+
+func (ser *seMDCReader) Read(buf []byte) (n int, err error) {
+ if ser.error {
+ err = io.ErrUnexpectedEOF
+ return
+ }
+ if ser.eof {
+ err = io.EOF
+ return
+ }
+
+ // If we haven't yet filled the trailer buffer then we must do that
+ // first.
+ for ser.trailerUsed < mdcTrailerSize {
+ n, err = ser.in.Read(ser.trailer[ser.trailerUsed:])
+ ser.trailerUsed += n
+ if err == io.EOF {
+ if ser.trailerUsed != mdcTrailerSize {
+ n = 0
+ err = io.ErrUnexpectedEOF
+ ser.error = true
+ return
+ }
+ ser.eof = true
+ n = 0
+ return
+ }
+
+ if err != nil {
+ n = 0
+ return
+ }
+ }
+
+ // If it's a short read then we read into a temporary buffer and shift
+ // the data into the caller's buffer.
+ if len(buf) <= mdcTrailerSize {
+ n, err = readFull(ser.in, ser.scratch[:len(buf)])
+ copy(buf, ser.trailer[:n])
+ ser.h.Write(buf[:n])
+ copy(ser.trailer[:], ser.trailer[n:])
+ copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:])
+ if n < len(buf) {
+ ser.eof = true
+ err = io.EOF
+ }
+ return
+ }
+
+ n, err = ser.in.Read(buf[mdcTrailerSize:])
+ copy(buf, ser.trailer[:])
+ ser.h.Write(buf[:n])
+ copy(ser.trailer[:], buf[n:])
+
+ if err == io.EOF {
+ ser.eof = true
+ }
+ return
+}
+
+// This is a new-format packet tag byte for a type 19 (MDC) packet.
+const mdcPacketTagByte = byte(0x80) | 0x40 | 19
+
+func (ser *seMDCReader) Close() error {
+ if ser.error {
+ return errors.SignatureError("error during reading")
+ }
+
+ for !ser.eof {
+ // We haven't seen EOF so we need to read to the end
+ var buf [1024]byte
+ _, err := ser.Read(buf[:])
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return errors.SignatureError("error during reading")
+ }
+ }
+
+ if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size {
+ return errors.SignatureError("MDC packet not found")
+ }
+ ser.h.Write(ser.trailer[:2])
+
+ final := ser.h.Sum(nil)
+ if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 {
+ return errors.SignatureError("hash mismatch")
+ }
+ return nil
+}
+
+// An seMDCWriter writes through to an io.WriteCloser while maintains a running
+// hash of the data written. On close, it emits an MDC packet containing the
+// running hash.
+type seMDCWriter struct {
+ w io.WriteCloser
+ h hash.Hash
+}
+
+func (w *seMDCWriter) Write(buf []byte) (n int, err error) {
+ w.h.Write(buf)
+ return w.w.Write(buf)
+}
+
+func (w *seMDCWriter) Close() (err error) {
+ var buf [mdcTrailerSize]byte
+
+ buf[0] = mdcPacketTagByte
+ buf[1] = sha1.Size
+ w.h.Write(buf[:2])
+ digest := w.h.Sum(nil)
+ copy(buf[2:], digest)
+
+ _, err = w.w.Write(buf[:])
+ if err != nil {
+ return
+ }
+ return w.w.Close()
+}
+
+// noOpCloser is like an io.NopCloser, but for an io.Writer.
+type noOpCloser struct {
+ w io.Writer
+}
+
+func (c noOpCloser) Write(data []byte) (n int, err error) {
+ return c.w.Write(data)
+}
+
+func (c noOpCloser) Close() error {
+ return nil
+}
+
+// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet
+// to w and returns a WriteCloser to which the to-be-encrypted packets can be
+// written.
+// If config is nil, sensible defaults will be used.
+func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, key []byte, config *Config) (contents io.WriteCloser, err error) {
+ if c.KeySize() != len(key) {
+ return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length")
+ }
+ writeCloser := noOpCloser{w}
+ ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC)
+ if err != nil {
+ return
+ }
+
+ _, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion})
+ if err != nil {
+ return
+ }
+
+ block := c.new(key)
+ blockSize := block.BlockSize()
+ iv := make([]byte, blockSize)
+ _, err = config.Random().Read(iv)
+ if err != nil {
+ return
+ }
+ s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync)
+ _, err = ciphertext.Write(prefix)
+ if err != nil {
+ return
+ }
+ plaintext := cipher.StreamWriter{S: s, W: ciphertext}
+
+ h := sha1.New()
+ h.Write(iv)
+ h.Write(iv[blockSize-2:])
+ contents = &seMDCWriter{w: plaintext, h: h}
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/symmetrically_encrypted_test.go b/local_crypto_patch/contents/openpgp/packet/symmetrically_encrypted_test.go
new file mode 100644
index 0000000000..4c47c7b145
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/symmetrically_encrypted_test.go
@@ -0,0 +1,122 @@
+// 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 packet
+
+import (
+ "bytes"
+ "crypto/sha1"
+ "encoding/hex"
+ "golang.org/x/crypto/openpgp/errors"
+ "io"
+ "testing"
+)
+
+// TestReader wraps a []byte and returns reads of a specific length.
+type testReader struct {
+ data []byte
+ stride int
+}
+
+func (t *testReader) Read(buf []byte) (n int, err error) {
+ n = t.stride
+ if n > len(t.data) {
+ n = len(t.data)
+ }
+ if n > len(buf) {
+ n = len(buf)
+ }
+ copy(buf, t.data)
+ t.data = t.data[n:]
+ if len(t.data) == 0 {
+ err = io.EOF
+ }
+ return
+}
+
+func testMDCReader(t *testing.T) {
+ mdcPlaintext, _ := hex.DecodeString(mdcPlaintextHex)
+
+ for stride := 1; stride < len(mdcPlaintext)/2; stride++ {
+ r := &testReader{data: mdcPlaintext, stride: stride}
+ mdcReader := &seMDCReader{in: r, h: sha1.New()}
+ body, err := io.ReadAll(mdcReader)
+ if err != nil {
+ t.Errorf("stride: %d, error: %s", stride, err)
+ continue
+ }
+ if !bytes.Equal(body, mdcPlaintext[:len(mdcPlaintext)-22]) {
+ t.Errorf("stride: %d: bad contents %x", stride, body)
+ continue
+ }
+
+ err = mdcReader.Close()
+ if err != nil {
+ t.Errorf("stride: %d, error on Close: %s", stride, err)
+ }
+ }
+
+ mdcPlaintext[15] ^= 80
+
+ r := &testReader{data: mdcPlaintext, stride: 2}
+ mdcReader := &seMDCReader{in: r, h: sha1.New()}
+ _, err := io.ReadAll(mdcReader)
+ if err != nil {
+ t.Errorf("corruption test, error: %s", err)
+ return
+ }
+ err = mdcReader.Close()
+ if err == nil {
+ t.Error("corruption: no error")
+ } else if _, ok := err.(*errors.SignatureError); !ok {
+ t.Errorf("corruption: expected SignatureError, got: %s", err)
+ }
+}
+
+const mdcPlaintextHex = "a302789c3b2d93c4e0eb9aba22283539b3203335af44a134afb800c849cb4c4de10200aff40b45d31432c80cb384299a0655966d6939dfdeed1dddf980"
+
+func TestSerialize(t *testing.T) {
+ buf := bytes.NewBuffer(nil)
+ c := CipherAES128
+ key := make([]byte, c.KeySize())
+
+ w, err := SerializeSymmetricallyEncrypted(buf, c, key, nil)
+ if err != nil {
+ t.Errorf("error from SerializeSymmetricallyEncrypted: %s", err)
+ return
+ }
+
+ contents := []byte("hello world\n")
+
+ w.Write(contents)
+ w.Close()
+
+ p, err := Read(buf)
+ if err != nil {
+ t.Errorf("error from Read: %s", err)
+ return
+ }
+
+ se, ok := p.(*SymmetricallyEncrypted)
+ if !ok {
+ t.Errorf("didn't read a *SymmetricallyEncrypted")
+ return
+ }
+
+ r, err := se.Decrypt(c, key)
+ if err != nil {
+ t.Errorf("error from Decrypt: %s", err)
+ return
+ }
+
+ contentsCopy := bytes.NewBuffer(nil)
+ _, err = io.Copy(contentsCopy, r)
+ if err != nil {
+ t.Errorf("error from io.Copy: %s", err)
+ return
+ }
+ if !bytes.Equal(contentsCopy.Bytes(), contents) {
+ t.Errorf("contents not equal got: %x want: %x", contentsCopy.Bytes(), contents)
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/userattribute.go b/local_crypto_patch/contents/openpgp/packet/userattribute.go
new file mode 100644
index 0000000000..ff7ef53075
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/userattribute.go
@@ -0,0 +1,90 @@
+// 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 packet
+
+import (
+ "bytes"
+ "image"
+ "image/jpeg"
+ "io"
+)
+
+const UserAttrImageSubpacket = 1
+
+// UserAttribute is capable of storing other types of data about a user
+// beyond name, email and a text comment. In practice, user attributes are typically used
+// to store a signed thumbnail photo JPEG image of the user.
+// See RFC 4880, section 5.12.
+type UserAttribute struct {
+ Contents []*OpaqueSubpacket
+}
+
+// NewUserAttributePhoto creates a user attribute packet
+// containing the given images.
+func NewUserAttributePhoto(photos ...image.Image) (uat *UserAttribute, err error) {
+ uat = new(UserAttribute)
+ for _, photo := range photos {
+ var buf bytes.Buffer
+ // RFC 4880, Section 5.12.1.
+ data := []byte{
+ 0x10, 0x00, // Little-endian image header length (16 bytes)
+ 0x01, // Image header version 1
+ 0x01, // JPEG
+ 0, 0, 0, 0, // 12 reserved octets, must be all zero.
+ 0, 0, 0, 0,
+ 0, 0, 0, 0}
+ if _, err = buf.Write(data); err != nil {
+ return
+ }
+ if err = jpeg.Encode(&buf, photo, nil); err != nil {
+ return
+ }
+ uat.Contents = append(uat.Contents, &OpaqueSubpacket{
+ SubType: UserAttrImageSubpacket,
+ Contents: buf.Bytes()})
+ }
+ return
+}
+
+// NewUserAttribute creates a new user attribute packet containing the given subpackets.
+func NewUserAttribute(contents ...*OpaqueSubpacket) *UserAttribute {
+ return &UserAttribute{Contents: contents}
+}
+
+func (uat *UserAttribute) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.13
+ b, err := io.ReadAll(r)
+ if err != nil {
+ return
+ }
+ uat.Contents, err = OpaqueSubpackets(b)
+ return
+}
+
+// Serialize marshals the user attribute to w in the form of an OpenPGP packet, including
+// header.
+func (uat *UserAttribute) Serialize(w io.Writer) (err error) {
+ var buf bytes.Buffer
+ for _, sp := range uat.Contents {
+ sp.Serialize(&buf)
+ }
+ if err = serializeHeader(w, packetTypeUserAttribute, buf.Len()); err != nil {
+ return err
+ }
+ _, err = w.Write(buf.Bytes())
+ return
+}
+
+// ImageData returns zero or more byte slices, each containing
+// JPEG File Interchange Format (JFIF), for each photo in the
+// user attribute packet.
+func (uat *UserAttribute) ImageData() (imageData [][]byte) {
+ for _, sp := range uat.Contents {
+ if sp.SubType == UserAttrImageSubpacket && len(sp.Contents) > 16 {
+ imageData = append(imageData, sp.Contents[16:])
+ }
+ }
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/userattribute_test.go b/local_crypto_patch/contents/openpgp/packet/userattribute_test.go
new file mode 100644
index 0000000000..13ca5143ce
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/userattribute_test.go
@@ -0,0 +1,109 @@
+// 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 packet
+
+import (
+ "bytes"
+ "encoding/base64"
+ "image/color"
+ "image/jpeg"
+ "testing"
+)
+
+func TestParseUserAttribute(t *testing.T) {
+ r := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(userAttributePacket))
+ for i := 0; i < 2; i++ {
+ p, err := Read(r)
+ if err != nil {
+ t.Fatal(err)
+ }
+ uat := p.(*UserAttribute)
+ imgs := uat.ImageData()
+ if len(imgs) != 1 {
+ t.Errorf("Unexpected number of images in user attribute packet: %d", len(imgs))
+ }
+ if len(imgs[0]) != 3395 {
+ t.Errorf("Unexpected JPEG image size: %d", len(imgs[0]))
+ }
+ img, err := jpeg.Decode(bytes.NewBuffer(imgs[0]))
+ if err != nil {
+ t.Errorf("Error decoding JPEG image: %v", err)
+ }
+ // A pixel in my right eye.
+ pixel := color.NRGBAModel.Convert(img.At(56, 36))
+ ref := color.NRGBA{R: 157, G: 128, B: 124, A: 255}
+ if pixel != ref {
+ t.Errorf("Unexpected pixel color: %v", pixel)
+ }
+ w := bytes.NewBuffer(nil)
+ err = uat.Serialize(w)
+ if err != nil {
+ t.Errorf("Error writing user attribute: %v", err)
+ }
+ r = bytes.NewBuffer(w.Bytes())
+ }
+}
+
+const userAttributePacket = `
+0cyWzJQBEAABAQAAAAAAAAAAAAAAAP/Y/+AAEEpGSUYAAQIAAAEAAQAA/9sAQwAFAwQEBAMFBAQE
+BQUFBgcMCAcHBwcPCgsJDBEPEhIRDxEQExYcFxMUGhUQERghGBocHR8fHxMXIiQiHiQcHh8e/9sA
+QwEFBQUHBgcOCAgOHhQRFB4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e
+Hh4eHh4eHh4e/8AAEQgAZABkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYH
+CAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHw
+JDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6
+g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk
+5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIB
+AgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEX
+GBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
+lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX2
+9/j5+v/aAAwDAQACEQMRAD8A5uGP06VehQ4pIox04q5EnHSvAep+hIIl4zVuMHGPWmRrUWtalaaN
+pU2oXsgSGJSxPr6ClvoitErs0Itqjc7BQOpPAFYmrfEnwjojtHNqaXEynBjtx5hH4jj9a8B8d+Od
+W8UXZjWR4LJT+7t0Jwfc+prnIdO1CWZEW2mZ3HyDactXXDB3V5s8evm1namj6r0H4weCLtxG+ova
+ueP30RA/MV6not1bX0Ed1ZzxzwyDKvGwZSPqK+Ff+ES8R8t/ZV2oHUmM10Hgbxp4m8BatEfNnWBH
+/eWshOxx9Kmpg4te49RUM1kn+8Wh9zQ4P1FaMC7l465rjPh14y0fxnoseoaXOpfaPOgJ+eI98j09
+67W19M15bi4uzPSqTU480WXkjZkAyAR61DPE6OCSOalWRRgZxjvTb598sfU4FBwx5uY4T4feIm8P
+TeJbAgc65NIM+8cX+FFeLfF3Vr3SfiNrMFrMypJMJcDPUqP8KK+kpVFyLU+ar037SXqX4hxVpMY7
+1UhPpVlT2rybKx9smWYz3NeH/EDVLzxt40j8O6bITaQybPlbKkjq39K9O8fasdH8IahfKxWQRFIy
+Ou9uB/OuE/Z/0y3j1d9TuyoZCMs5xjuea1pLli5nn46q240l13PcfhN8EvDNtpcEl/CklyVBLuMk
+mvU/Dfwo0BL/AO13FjEDD/qyV7Vn+CvGPg8zRpJrVm8ikLtEg6+1ew2dxZ3EQaJgysuQPasH7eXW
+1zzsbVhT92kk/PsYieEND+zlPs6c/wCyAPyryH4wfCPRtW0u6j+xRLOxLxSoADkDpXY+MPjJ4c0S
+9k082d3O8ZKkxw5XI96ytK+IGk+IpFjRpod+Qq3C7QT6A1E6NenaXbqRg6rlLlqS0fRnxjpd1r/w
+w8afa7GWRPKbZLGeBKmeVNfZngLxNaeKfDdprVjxHcLlkJ5Vh1H5185/tDad9h8XOsqAw3Cb0cjq
+CfX61P8AsveKf7L8T3fhe5nxa3g324YniQdh9R/KuivTdSmp9TXB1/Z1nRlsfU249QBx1pWfcwI7
+Cq6u2Ovamb9rYz16V5x7Psz5q/aJhZfibcupIElvE3H+7j+lFbXx9szP45jlUfeso8/99OKK9elL
+3EeNVopzZVharCtxVRGGMk02S5JyFOB69zWTieypnL/GksfB+0cr9oQt69awPhPpD69Y3Ky3DWth
+CWluGU4LAdq3vibGs/g68BJygVxjrwRW5+ztoRv/AAs8EeCZnO/J/hzz/Kumi4wp3kePjlOdZKPY
+ml8Mvo6WM9ppi7J0EkQYMzkb1X0wW+bJHGACa+ivg14huZPCkjXUO6SImIYOQAP6UQ2sGneHmiWF
+CYoSAAuM8etXfhBpMr+EZ3SSNRcMx6ZxWdes6ytBGSwkMNFuo7pnP614Ut9Zn1C4uLySKcwObGFA
+Qnm4+XcR71h+CfDHiKCQWuv2YWFtw+bBZQD8rcE8n2Ney+GbGGQSM6I7xvtI681rXdp8hKRRp6t3
+FYPE1VDlsY1nQjWdl+J8w/tOeDZZ/AMd/EGefTHyxxyYjwfyODXg3waRh8UtEcFh+8Jb8FNfZPxh
+Ak8J6nbPIsiyW7LnseK+Ofh99ptPHFnf2lu0y2twGcKuSEPB/Q1WHk50miq1o14TXU+xop+On61H
+NMC6Nis1LgsAcUTSt1APFcXJZn0EqmhyvxA037friTYziBV6f7Tf40Vr3k4aXLx5OMZIzRXZB2ik
+efJXbPHJJcnaD9aN2R1qoGO8/WkuLlIV+YjdjpXSonQ5lTxfiTwzqCnkeQxx9BWx+zPrQsrBFYja
+zEfrXL6lfie3khcjY6lSPUGud+G3iA6FrY0uQ/KJsA9gCa0jSvFpnBi6tpKSPu++nsIfDFxeXciR
+qIicscY4rxTwB8RUkn1axsPEf2LTYx85kTGzqCUP8VcJ47+JOs+I0Hhq1njjt/ufIeSvq1VtE+Gs
+eoaUbSHUrkHdu3WtuX5Ix81XRh7OL5jirVpV5Whdn0F8C/iX4auVn0i612T7bASoe8wjTAd89K9g
+vtSt5NMa4t5lkRhgOh3Dn6V8aaz8KZrIR3OlQ6r56LySmSxxz06Vo/CHx34h0rxBP4XvJ5AjK2RP
+nEbAEj6ZxjPrWM6fMmoswqJxqJ1VZnqHxn1NLPwveqWHmNC2BnnNcD8DfDkGi+CH1m+ijN1qMzNA
+4GSIiAMf+hVxPxU8Tapc3c0F9MGCn5GU5BX0Pau3+HmrT3XgXSIJCBHDGdgAx1NYSpezha52Yauq
+1dya2Wh2onAIwTj1p0lxxWWLkhRyCKWa5O3ORXOos9KVQluZm83j0oqi84JyWH50Vdmc7ep43d3I
+t1Z2Iz2FYdxeSTsxyRnvTdVuDNcNluM9KrKcg817NOnZGNbEXdkNckjrXGeIIprPxFFdRHAlIwem
+COtdmxrG8Q2cd/ZNExw45RvQ1bVjim+dWNzw7eaTD4mN3dndCQCo6hmI5zXpj/Ea/wBHjkh0kwRW
+xXEfl4yTxXzXZalJDL9nuWKMmRnHcV2Hh3WreCyYXW2SWQhd5P3F6n+lS43d2cTm6d7Ox9EWPxH1
+ODQxPqWpCaSU/ukUc4z3/WvKW8UhviAdaMewYZG98gj9c1ymoa8LyWOJHwkTDaVPb0qpr+q2m6Nb
+cfvNo349az9mou9iZVXNWbub3jm98/Vza2ReV7lsJg/e3dsV654UR9N0K0sZP9ZDGFbHr3rzL4P+
+H7rXfEEWr3I3W1qf3IYdW9fwqDxf4k8UeH/G95p08kscHmk25dPlZT0we9YTj7SXKjpw1aNG8mj3
+FLv5ccU959ycnmvKPDnxB82YQarGsZPAlTp+IrvIr1ZIgySKwIyCOhFYTpyg9T0qWIhVV4svzPvf
+IdhgY4orPachj81FRdmtzxqdiZmJ9aQEgdqZcPtmbJ71DJcAZ5r20kkeXJtsfPIQDwPzrG1a+S3i
+LyHAHvmp7y7HOD1rlNdm+1T7Acovf3o+J2RMpezjzMvrob67pX9o2ShZlYgg/wAWKxZLLWLZ/Ke3
+mVh14yK9M+BMC3dre2ko3LHKCB7EV7EngeGQJdQ7HyBkMKS0djgq1W3c+XtK03U522RwzsTwNiEk
+ntXoHgf4calql9El/G8UZbLfLyfr7V9FeGvh+s+0Lbxxcglu2K1NW1nwN4Gk/wBLuI57tV5jjwzE
+/QVNS+0dWYRqNvXRFv4eeCodKsY1ggVIY1G3K4z714h+1Jqul3GpwaXYeXJLbzgyyrg4b+6D+HNb
+vjz436zq9m+naHF/ZdkeGfOZXH17V4Vqt2b29K+ZuOc5bnce5zWdPBShL2lTfojSeJhy+zp/NjVz
+1Bwa6DSfFGq6fbJFDKrov8DjPFcu97ZxsUe4jVhwVJ5Bpp1mwQiLewJPXacVq6fNpYyjOUXdHoKf
+EG8VQHsInbuVcgflRXnt5fIs2FYHgcgUVi8LG+xusdW/mN7U2KgEVkTzPt60UVfQ9eHxGHrV1MGi
+iD4V25x1qvdgLAMd6KK0pbHm4x++dp8FtUubLxJ5EIjMc+A4Za+qfD8pe1JZVOBmiinW3RyRPMfi
+R8QPE638+k2l6LK0Hylbddhb6nOa80mlkcmWR2kcnlnOSaKK7qCXKcNdu5narcSrAoBxvODWJIga
+VckjDdqKKwq/EaQ0gUdbjQ6mr7QGBUcd6tPBC6gtGpOOuKKKie5qn7qIpEXd0HSiiimSf//Z`
diff --git a/local_crypto_patch/contents/openpgp/packet/userid.go b/local_crypto_patch/contents/openpgp/packet/userid.go
new file mode 100644
index 0000000000..359a462eb8
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/userid.go
@@ -0,0 +1,159 @@
+// 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 packet
+
+import (
+ "io"
+ "strings"
+)
+
+// UserId contains text that is intended to represent the name and email
+// address of the key holder. See RFC 4880, section 5.11. By convention, this
+// takes the form "Full Name (Comment) "
+type UserId struct {
+ Id string // By convention, this takes the form "Full Name (Comment) " which is split out in the fields below.
+
+ Name, Comment, Email string
+}
+
+func hasInvalidCharacters(s string) bool {
+ for _, c := range s {
+ switch c {
+ case '(', ')', '<', '>', 0:
+ return true
+ }
+ }
+ return false
+}
+
+// NewUserId returns a UserId or nil if any of the arguments contain invalid
+// characters. The invalid characters are '\x00', '(', ')', '<' and '>'
+func NewUserId(name, comment, email string) *UserId {
+ // RFC 4880 doesn't deal with the structure of userid strings; the
+ // name, comment and email form is just a convention. However, there's
+ // no convention about escaping the metacharacters and GPG just refuses
+ // to create user ids where, say, the name contains a '('. We mirror
+ // this behaviour.
+
+ if hasInvalidCharacters(name) || hasInvalidCharacters(comment) || hasInvalidCharacters(email) {
+ return nil
+ }
+
+ uid := new(UserId)
+ uid.Name, uid.Comment, uid.Email = name, comment, email
+ uid.Id = name
+ if len(comment) > 0 {
+ if len(uid.Id) > 0 {
+ uid.Id += " "
+ }
+ uid.Id += "("
+ uid.Id += comment
+ uid.Id += ")"
+ }
+ if len(email) > 0 {
+ if len(uid.Id) > 0 {
+ uid.Id += " "
+ }
+ uid.Id += "<"
+ uid.Id += email
+ uid.Id += ">"
+ }
+ return uid
+}
+
+func (uid *UserId) parse(r io.Reader) (err error) {
+ // RFC 4880, section 5.11
+ b, err := io.ReadAll(r)
+ if err != nil {
+ return
+ }
+ uid.Id = string(b)
+ uid.Name, uid.Comment, uid.Email = parseUserId(uid.Id)
+ return
+}
+
+// Serialize marshals uid to w in the form of an OpenPGP packet, including
+// header.
+func (uid *UserId) Serialize(w io.Writer) error {
+ err := serializeHeader(w, packetTypeUserId, len(uid.Id))
+ if err != nil {
+ return err
+ }
+ _, err = w.Write([]byte(uid.Id))
+ return err
+}
+
+// parseUserId extracts the name, comment and email from a user id string that
+// is formatted as "Full Name (Comment) ".
+func parseUserId(id string) (name, comment, email string) {
+ var n, c, e struct {
+ start, end int
+ }
+ var state int
+
+ for offset, rune := range id {
+ switch state {
+ case 0:
+ // Entering name
+ n.start = offset
+ state = 1
+ fallthrough
+ case 1:
+ // In name
+ if rune == '(' {
+ state = 2
+ n.end = offset
+ } else if rune == '<' {
+ state = 5
+ n.end = offset
+ }
+ case 2:
+ // Entering comment
+ c.start = offset
+ state = 3
+ fallthrough
+ case 3:
+ // In comment
+ if rune == ')' {
+ state = 4
+ c.end = offset
+ }
+ case 4:
+ // Between comment and email
+ if rune == '<' {
+ state = 5
+ }
+ case 5:
+ // Entering email
+ e.start = offset
+ state = 6
+ fallthrough
+ case 6:
+ // In email
+ if rune == '>' {
+ state = 7
+ e.end = offset
+ }
+ default:
+ // After email
+ }
+ }
+ switch state {
+ case 1:
+ // ended in the name
+ n.end = len(id)
+ case 3:
+ // ended in comment
+ c.end = len(id)
+ case 6:
+ // ended in email
+ e.end = len(id)
+ }
+
+ name = strings.TrimSpace(id[n.start:n.end])
+ comment = strings.TrimSpace(id[c.start:c.end])
+ email = strings.TrimSpace(id[e.start:e.end])
+ return
+}
diff --git a/local_crypto_patch/contents/openpgp/packet/userid_test.go b/local_crypto_patch/contents/openpgp/packet/userid_test.go
new file mode 100644
index 0000000000..2968193893
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/packet/userid_test.go
@@ -0,0 +1,87 @@
+// 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 packet
+
+import (
+ "testing"
+)
+
+var userIdTests = []struct {
+ id string
+ name, comment, email string
+}{
+ {"", "", "", ""},
+ {"John Smith", "John Smith", "", ""},
+ {"John Smith ()", "John Smith", "", ""},
+ {"John Smith () <>", "John Smith", "", ""},
+ {"(comment", "", "comment", ""},
+ {"(comment)", "", "comment", ""},
+ {" sdfk", "", "", "email"},
+ {" John Smith ( Comment ) asdkflj < email > lksdfj", "John Smith", "Comment", "email"},
+ {" John Smith < email > lksdfj", "John Smith", "", "email"},
+ {"("},
+ {"foo", "bar", "", "foo (bar)"},
+ {"foo", "", "baz", "foo "},
+ {"", "bar", "baz", "(bar) "},
+ {"foo", "bar", "baz", "foo (bar) "},
+}
+
+func TestNewUserId(t *testing.T) {
+ for i, test := range newUserIdTests {
+ uid := NewUserId(test.name, test.comment, test.email)
+ if uid == nil {
+ t.Errorf("#%d: returned nil", i)
+ continue
+ }
+ if uid.Id != test.id {
+ t.Errorf("#%d: got '%s', want '%s'", i, uid.Id, test.id)
+ }
+ }
+}
+
+var invalidNewUserIdTests = []struct {
+ name, comment, email string
+}{
+ {"foo(", "", ""},
+ {"foo<", "", ""},
+ {"", "bar)", ""},
+ {"", "bar<", ""},
+ {"", "", "baz>"},
+ {"", "", "baz)"},
+ {"", "", "baz\x00"},
+}
+
+func TestNewUserIdWithInvalidInput(t *testing.T) {
+ for i, test := range invalidNewUserIdTests {
+ if uid := NewUserId(test.name, test.comment, test.email); uid != nil {
+ t.Errorf("#%d: returned non-nil value: %#v", i, uid)
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/read.go b/local_crypto_patch/contents/openpgp/read.go
new file mode 100644
index 0000000000..cff3db9196
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/read.go
@@ -0,0 +1,448 @@
+// 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 openpgp implements high level operations on OpenPGP messages.
+//
+// Deprecated: this package is unmaintained except for security fixes. New
+// applications should consider a more focused, modern alternative to OpenPGP
+// for their specific task. If you are required to interoperate with OpenPGP
+// systems and need a maintained package, consider a community fork.
+// See https://golang.org/issue/44226.
+package openpgp
+
+import (
+ "crypto"
+ _ "crypto/sha256"
+ "hash"
+ "io"
+ "strconv"
+
+ "golang.org/x/crypto/openpgp/armor"
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/packet"
+)
+
+// SignatureType is the armor type for a PGP signature.
+var SignatureType = "PGP SIGNATURE"
+
+// readArmored reads an armored block with the given type.
+func readArmored(r io.Reader, expectedType string) (body io.Reader, err error) {
+ block, err := armor.Decode(r)
+ if err != nil {
+ return
+ }
+
+ if block.Type != expectedType {
+ return nil, errors.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type)
+ }
+
+ return block.Body, nil
+}
+
+// MessageDetails contains the result of parsing an OpenPGP encrypted and/or
+// signed message.
+type MessageDetails struct {
+ IsEncrypted bool // true if the message was encrypted.
+ EncryptedToKeyIds []uint64 // the list of recipient key ids.
+ IsSymmetricallyEncrypted bool // true if a passphrase could have decrypted the message.
+ DecryptedWith Key // the private key used to decrypt the message, if any.
+ IsSigned bool // true if the message is signed.
+ SignedByKeyId uint64 // the key id of the signer, if any.
+ SignedBy *Key // the key of the signer, if available.
+ LiteralData *packet.LiteralData // the metadata of the contents
+ UnverifiedBody io.Reader // the contents of the message.
+
+ // If IsSigned is true and SignedBy is non-zero then the signature will
+ // be verified as UnverifiedBody is read. The signature cannot be
+ // checked until the whole of UnverifiedBody is read so UnverifiedBody
+ // must be consumed until EOF before the data can be trusted. Even if a
+ // message isn't signed (or the signer is unknown) the data may contain
+ // an authentication code that is only checked once UnverifiedBody has
+ // been consumed. Once EOF has been seen, the following fields are
+ // valid. (An authentication code failure is reported as a
+ // SignatureError error when reading from UnverifiedBody.)
+ SignatureError error // nil if the signature is good.
+ Signature *packet.Signature // the signature packet itself, if v4 (default)
+ SignatureV3 *packet.SignatureV3 // the signature packet if it is a v2 or v3 signature
+
+ decrypted io.ReadCloser
+}
+
+// A PromptFunction is used as a callback by functions that may need to decrypt
+// a private key, or prompt for a passphrase. It is called with a list of
+// acceptable, encrypted private keys and a boolean that indicates whether a
+// passphrase is usable. It should either decrypt a private key or return a
+// passphrase to try. If the decrypted private key or given passphrase isn't
+// correct, the function will be called again, forever. Any error returned will
+// be passed up.
+type PromptFunction func(keys []Key, symmetric bool) ([]byte, error)
+
+// A keyEnvelopePair is used to store a private key with the envelope that
+// contains a symmetric key, encrypted with that key.
+type keyEnvelopePair struct {
+ key Key
+ encryptedKey *packet.EncryptedKey
+}
+
+// ReadMessage parses an OpenPGP message that may be signed and/or encrypted.
+// The given KeyRing should contain both public keys (for signature
+// verification) and, possibly encrypted, private keys for decrypting.
+// If config is nil, sensible defaults will be used.
+func ReadMessage(r io.Reader, keyring KeyRing, prompt PromptFunction, config *packet.Config) (md *MessageDetails, err error) {
+ var p packet.Packet
+
+ var symKeys []*packet.SymmetricKeyEncrypted
+ var pubKeys []keyEnvelopePair
+ var se *packet.SymmetricallyEncrypted
+
+ packets := packet.NewReader(r)
+ md = new(MessageDetails)
+ md.IsEncrypted = true
+
+ // The message, if encrypted, starts with a number of packets
+ // containing an encrypted decryption key. The decryption key is either
+ // encrypted to a public key, or with a passphrase. This loop
+ // collects these packets.
+ParsePackets:
+ for {
+ p, err = packets.Next()
+ if err != nil {
+ return nil, err
+ }
+ switch p := p.(type) {
+ case *packet.SymmetricKeyEncrypted:
+ // This packet contains the decryption key encrypted with a passphrase.
+ md.IsSymmetricallyEncrypted = true
+ symKeys = append(symKeys, p)
+ case *packet.EncryptedKey:
+ // This packet contains the decryption key encrypted to a public key.
+ md.EncryptedToKeyIds = append(md.EncryptedToKeyIds, p.KeyId)
+ switch p.Algo {
+ case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal:
+ break
+ default:
+ continue
+ }
+ var keys []Key
+ if p.KeyId == 0 {
+ keys = keyring.DecryptionKeys()
+ } else {
+ keys = keyring.KeysById(p.KeyId)
+ }
+ for _, k := range keys {
+ pubKeys = append(pubKeys, keyEnvelopePair{k, p})
+ }
+ case *packet.SymmetricallyEncrypted:
+ se = p
+ break ParsePackets
+ case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature:
+ // This message isn't encrypted.
+ if len(symKeys) != 0 || len(pubKeys) != 0 {
+ return nil, errors.StructuralError("key material not followed by encrypted message")
+ }
+ packets.Unread(p)
+ return readSignedMessage(packets, nil, keyring)
+ }
+ }
+
+ var candidates []Key
+ var decrypted io.ReadCloser
+
+ // Now that we have the list of encrypted keys we need to decrypt at
+ // least one of them or, if we cannot, we need to call the prompt
+ // function so that it can decrypt a key or give us a passphrase.
+FindKey:
+ for {
+ // See if any of the keys already have a private key available
+ candidates = candidates[:0]
+ candidateFingerprints := make(map[string]bool)
+
+ for _, pk := range pubKeys {
+ if pk.key.PrivateKey == nil {
+ continue
+ }
+ if !pk.key.PrivateKey.Encrypted {
+ if len(pk.encryptedKey.Key) == 0 {
+ pk.encryptedKey.Decrypt(pk.key.PrivateKey, config)
+ }
+ if len(pk.encryptedKey.Key) == 0 {
+ continue
+ }
+ decrypted, err = se.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key)
+ if err != nil && err != errors.ErrKeyIncorrect {
+ return nil, err
+ }
+ if decrypted != nil {
+ md.DecryptedWith = pk.key
+ break FindKey
+ }
+ } else {
+ fpr := string(pk.key.PublicKey.Fingerprint[:])
+ if v := candidateFingerprints[fpr]; v {
+ continue
+ }
+ candidates = append(candidates, pk.key)
+ candidateFingerprints[fpr] = true
+ }
+ }
+
+ if len(candidates) == 0 && len(symKeys) == 0 {
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ if prompt == nil {
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ passphrase, err := prompt(candidates, len(symKeys) != 0)
+ if err != nil {
+ return nil, err
+ }
+
+ // Try the symmetric passphrase first
+ if len(symKeys) != 0 && passphrase != nil {
+ for _, s := range symKeys {
+ key, cipherFunc, err := s.Decrypt(passphrase)
+ if err == nil {
+ decrypted, err = se.Decrypt(cipherFunc, key)
+ if err != nil && err != errors.ErrKeyIncorrect {
+ return nil, err
+ }
+ if decrypted != nil {
+ break FindKey
+ }
+ }
+
+ }
+ }
+ }
+
+ md.decrypted = decrypted
+ if err := packets.Push(decrypted); err != nil {
+ return nil, err
+ }
+ return readSignedMessage(packets, md, keyring)
+}
+
+// readSignedMessage reads a possibly signed message if mdin is non-zero then
+// that structure is updated and returned. Otherwise a fresh MessageDetails is
+// used.
+func readSignedMessage(packets *packet.Reader, mdin *MessageDetails, keyring KeyRing) (md *MessageDetails, err error) {
+ if mdin == nil {
+ mdin = new(MessageDetails)
+ }
+ md = mdin
+
+ var p packet.Packet
+ var h hash.Hash
+ var wrappedHash hash.Hash
+FindLiteralData:
+ for {
+ p, err = packets.Next()
+ if err != nil {
+ return nil, err
+ }
+ switch p := p.(type) {
+ case *packet.Compressed:
+ if err := packets.Push(p.Body); err != nil {
+ return nil, err
+ }
+ case *packet.OnePassSignature:
+ if !p.IsLast {
+ return nil, errors.UnsupportedError("nested signatures")
+ }
+
+ h, wrappedHash, err = hashForSignature(p.Hash, p.SigType)
+ if err != nil {
+ md = nil
+ return
+ }
+
+ md.IsSigned = true
+ md.SignedByKeyId = p.KeyId
+ keys := keyring.KeysByIdUsage(p.KeyId, packet.KeyFlagSign)
+ if len(keys) > 0 {
+ md.SignedBy = &keys[0]
+ }
+ case *packet.LiteralData:
+ md.LiteralData = p
+ break FindLiteralData
+ }
+ }
+
+ if md.SignedBy != nil {
+ md.UnverifiedBody = &signatureCheckReader{packets, h, wrappedHash, md}
+ } else if md.decrypted != nil {
+ md.UnverifiedBody = checkReader{md}
+ } else {
+ md.UnverifiedBody = md.LiteralData.Body
+ }
+
+ return md, nil
+}
+
+// hashForSignature returns a pair of hashes that can be used to verify a
+// signature. The signature may specify that the contents of the signed message
+// should be preprocessed (i.e. to normalize line endings). Thus this function
+// returns two hashes. The second should be used to hash the message itself and
+// performs any needed preprocessing.
+func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
+ if !hashId.Available() {
+ return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId)))
+ }
+ h := hashId.New()
+
+ switch sigType {
+ case packet.SigTypeBinary:
+ return h, h, nil
+ case packet.SigTypeText:
+ return h, NewCanonicalTextHash(h), nil
+ }
+
+ return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType)))
+}
+
+// checkReader wraps an io.Reader from a LiteralData packet. When it sees EOF
+// it closes the ReadCloser from any SymmetricallyEncrypted packet to trigger
+// MDC checks.
+type checkReader struct {
+ md *MessageDetails
+}
+
+func (cr checkReader) Read(buf []byte) (n int, err error) {
+ n, err = cr.md.LiteralData.Body.Read(buf)
+ if err == io.EOF {
+ mdcErr := cr.md.decrypted.Close()
+ if mdcErr != nil {
+ err = mdcErr
+ }
+ }
+ return
+}
+
+// signatureCheckReader wraps an io.Reader from a LiteralData packet and hashes
+// the data as it is read. When it sees an EOF from the underlying io.Reader
+// it parses and checks a trailing Signature packet and triggers any MDC checks.
+type signatureCheckReader struct {
+ packets *packet.Reader
+ h, wrappedHash hash.Hash
+ md *MessageDetails
+}
+
+func (scr *signatureCheckReader) Read(buf []byte) (n int, err error) {
+ n, err = scr.md.LiteralData.Body.Read(buf)
+ scr.wrappedHash.Write(buf[:n])
+ if err == io.EOF {
+ var p packet.Packet
+ p, scr.md.SignatureError = scr.packets.Next()
+ if scr.md.SignatureError != nil {
+ return
+ }
+
+ var ok bool
+ if scr.md.Signature, ok = p.(*packet.Signature); ok {
+ scr.md.SignatureError = scr.md.SignedBy.PublicKey.VerifySignature(scr.h, scr.md.Signature)
+ } else if scr.md.SignatureV3, ok = p.(*packet.SignatureV3); ok {
+ scr.md.SignatureError = scr.md.SignedBy.PublicKey.VerifySignatureV3(scr.h, scr.md.SignatureV3)
+ } else {
+ scr.md.SignatureError = errors.StructuralError("LiteralData not followed by Signature")
+ return
+ }
+
+ // The SymmetricallyEncrypted packet, if any, might have an
+ // unsigned hash of its own. In order to check this we need to
+ // close that Reader.
+ if scr.md.decrypted != nil {
+ mdcErr := scr.md.decrypted.Close()
+ if mdcErr != nil {
+ err = mdcErr
+ }
+ }
+ }
+ return
+}
+
+// CheckDetachedSignature takes a signed file and a detached signature and
+// returns the signer if the signature is valid. If the signer isn't known,
+// ErrUnknownIssuer is returned.
+func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err error) {
+ var issuerKeyId uint64
+ var hashFunc crypto.Hash
+ var sigType packet.SignatureType
+ var keys []Key
+ var p packet.Packet
+
+ packets := packet.NewReader(signature)
+ for {
+ p, err = packets.Next()
+ if err == io.EOF {
+ return nil, errors.ErrUnknownIssuer
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ switch sig := p.(type) {
+ case *packet.Signature:
+ if sig.IssuerKeyId == nil {
+ return nil, errors.StructuralError("signature doesn't have an issuer")
+ }
+ issuerKeyId = *sig.IssuerKeyId
+ hashFunc = sig.Hash
+ sigType = sig.SigType
+ case *packet.SignatureV3:
+ issuerKeyId = sig.IssuerKeyId
+ hashFunc = sig.Hash
+ sigType = sig.SigType
+ default:
+ return nil, errors.StructuralError("non signature packet found")
+ }
+
+ keys = keyring.KeysByIdUsage(issuerKeyId, packet.KeyFlagSign)
+ if len(keys) > 0 {
+ break
+ }
+ }
+
+ if len(keys) == 0 {
+ panic("unreachable")
+ }
+
+ h, wrappedHash, err := hashForSignature(hashFunc, sigType)
+ if err != nil {
+ return nil, err
+ }
+
+ if _, err := io.Copy(wrappedHash, signed); err != nil && err != io.EOF {
+ return nil, err
+ }
+
+ for _, key := range keys {
+ switch sig := p.(type) {
+ case *packet.Signature:
+ err = key.PublicKey.VerifySignature(h, sig)
+ case *packet.SignatureV3:
+ err = key.PublicKey.VerifySignatureV3(h, sig)
+ default:
+ panic("unreachable")
+ }
+
+ if err == nil {
+ return key.Entity, nil
+ }
+ }
+
+ return nil, err
+}
+
+// CheckArmoredDetachedSignature performs the same actions as
+// CheckDetachedSignature but expects the signature to be armored.
+func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err error) {
+ body, err := readArmored(signature, SignatureType)
+ if err != nil {
+ return
+ }
+
+ return CheckDetachedSignature(keyring, signed, body)
+}
diff --git a/local_crypto_patch/contents/openpgp/read_test.go b/local_crypto_patch/contents/openpgp/read_test.go
new file mode 100644
index 0000000000..6bbfaf1afb
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/read_test.go
@@ -0,0 +1,612 @@
+// 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 openpgp
+
+import (
+ "bytes"
+ _ "crypto/sha512"
+ "encoding/hex"
+ "io"
+ "strings"
+ "testing"
+
+ "golang.org/x/crypto/openpgp/armor"
+ "golang.org/x/crypto/openpgp/errors"
+)
+
+func readerFromHex(s string) io.Reader {
+ data, err := hex.DecodeString(s)
+ if err != nil {
+ panic("readerFromHex: bad input")
+ }
+ return bytes.NewBuffer(data)
+}
+
+func TestReadKeyRing(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B {
+ t.Errorf("bad keyring: %#v", kring)
+ }
+}
+
+func TestRereadKeyRing(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+ if err != nil {
+ t.Errorf("error in initial parse: %s", err)
+ return
+ }
+ out := new(bytes.Buffer)
+ err = kring[0].Serialize(out)
+ if err != nil {
+ t.Errorf("error in serialization: %s", err)
+ return
+ }
+ kring, err = ReadKeyRing(out)
+ if err != nil {
+ t.Errorf("error in second parse: %s", err)
+ return
+ }
+
+ if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB {
+ t.Errorf("bad keyring: %#v", kring)
+ }
+}
+
+func TestReadPrivateKeyRing(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B || kring[0].PrimaryKey == nil {
+ t.Errorf("bad keyring: %#v", kring)
+ }
+}
+
+func TestReadDSAKey(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(dsaTestKeyHex))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0x0CCC0360 {
+ t.Errorf("bad parse: %#v", kring)
+ }
+}
+
+func TestReadP256Key(t *testing.T) {
+ kring, err := ReadKeyRing(readerFromHex(p256TestKeyHex))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0x5918513E {
+ t.Errorf("bad parse: %#v", kring)
+ }
+}
+
+func TestDSAHashTruncatation(t *testing.T) {
+ // dsaKeyWithSHA512 was generated with GnuPG and --cert-digest-algo
+ // SHA512 in order to require DSA hash truncation to verify correctly.
+ _, err := ReadKeyRing(readerFromHex(dsaKeyWithSHA512))
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestGetKeyById(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+
+ keys := kring.KeysById(0xa34d7e18c20c31bb)
+ if len(keys) != 1 || keys[0].Entity != kring[0] {
+ t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys)
+ }
+
+ keys = kring.KeysById(0xfd94408d4543314f)
+ if len(keys) != 1 || keys[0].Entity != kring[0] {
+ t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys)
+ }
+}
+
+func checkSignedMessage(t *testing.T, signedHex, expected string) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+
+ md, err := ReadMessage(readerFromHex(signedHex), kring, nil, nil)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ if !md.IsSigned || md.SignedByKeyId != 0xa34d7e18c20c31bb || md.SignedBy == nil || md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) != 0 || md.DecryptedWith != (Key{}) {
+ t.Errorf("bad MessageDetails: %#v", md)
+ }
+
+ contents, err := io.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("error reading UnverifiedBody: %s", err)
+ }
+ if string(contents) != expected {
+ t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected)
+ }
+ if md.SignatureError != nil || md.Signature == nil {
+ t.Errorf("failed to validate: %s", md.SignatureError)
+ }
+}
+
+func TestSignedMessage(t *testing.T) {
+ checkSignedMessage(t, signedMessageHex, signedInput)
+}
+
+func TestTextSignedMessage(t *testing.T) {
+ checkSignedMessage(t, signedTextMessageHex, signedTextInput)
+}
+
+// The reader should detect "compressed quines", which are compressed
+// packets that expand into themselves and cause an infinite recursive
+// parsing loop.
+// The packet in this test case comes from Taylor R. Campbell at
+// http://mumble.net/~campbell/misc/pgp-quine/
+func TestCampbellQuine(t *testing.T) {
+ md, err := ReadMessage(readerFromHex(campbellQuine), nil, nil, nil)
+ if md != nil {
+ t.Errorf("Reading a compressed quine should not return any data: %#v", md)
+ }
+ structural, ok := err.(errors.StructuralError)
+ if !ok {
+ t.Fatalf("Unexpected class of error: %T", err)
+ }
+ if !strings.Contains(string(structural), "too many layers of packets") {
+ t.Fatalf("Unexpected error: %s", err)
+ }
+}
+
+var signedEncryptedMessageTests = []struct {
+ keyRingHex string
+ messageHex string
+ signedByKeyId uint64
+ encryptedToKeyId uint64
+}{
+ {
+ testKeys1And2PrivateHex,
+ signedEncryptedMessageHex,
+ 0xa34d7e18c20c31bb,
+ 0x2a67d68660df41c7,
+ },
+ {
+ dsaElGamalTestKeysHex,
+ signedEncryptedMessage2Hex,
+ 0x33af447ccd759b09,
+ 0xcf6a7abcd43e3673,
+ },
+}
+
+func TestSignedEncryptedMessage(t *testing.T) {
+ for i, test := range signedEncryptedMessageTests {
+ expected := "Signed and encrypted message\n"
+ kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex))
+ prompt := func(keys []Key, symmetric bool) ([]byte, error) {
+ if symmetric {
+ t.Errorf("prompt: message was marked as symmetrically encrypted")
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ if len(keys) == 0 {
+ t.Error("prompt: no keys requested")
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ err := keys[0].PrivateKey.Decrypt([]byte("passphrase"))
+ if err != nil {
+ t.Errorf("prompt: error decrypting key: %s", err)
+ return nil, errors.ErrKeyIncorrect
+ }
+
+ return nil, nil
+ }
+
+ md, err := ReadMessage(readerFromHex(test.messageHex), kring, prompt, nil)
+ if err != nil {
+ t.Errorf("#%d: error reading message: %s", i, err)
+ return
+ }
+
+ if !md.IsSigned || md.SignedByKeyId != test.signedByKeyId || md.SignedBy == nil || !md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) == 0 || md.EncryptedToKeyIds[0] != test.encryptedToKeyId {
+ t.Errorf("#%d: bad MessageDetails: %#v", i, md)
+ }
+
+ contents, err := io.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("#%d: error reading UnverifiedBody: %s", i, err)
+ }
+ if string(contents) != expected {
+ t.Errorf("#%d: bad UnverifiedBody got:%s want:%s", i, string(contents), expected)
+ }
+
+ if md.SignatureError != nil || md.Signature == nil {
+ t.Errorf("#%d: failed to validate: %s", i, md.SignatureError)
+ }
+ }
+}
+
+func TestUnspecifiedRecipient(t *testing.T) {
+ expected := "Recipient unspecified\n"
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+
+ md, err := ReadMessage(readerFromHex(recipientUnspecifiedHex), kring, nil, nil)
+ if err != nil {
+ t.Errorf("error reading message: %s", err)
+ return
+ }
+
+ contents, err := io.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("error reading UnverifiedBody: %s", err)
+ }
+ if string(contents) != expected {
+ t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected)
+ }
+}
+
+func TestSymmetricallyEncrypted(t *testing.T) {
+ firstTimeCalled := true
+
+ prompt := func(keys []Key, symmetric bool) ([]byte, error) {
+ if len(keys) != 0 {
+ t.Errorf("prompt: len(keys) = %d (want 0)", len(keys))
+ }
+
+ if !symmetric {
+ t.Errorf("symmetric is not set")
+ }
+
+ if firstTimeCalled {
+ firstTimeCalled = false
+ return []byte("wrongpassword"), nil
+ }
+
+ return []byte("password"), nil
+ }
+
+ md, err := ReadMessage(readerFromHex(symmetricallyEncryptedCompressedHex), nil, prompt, nil)
+ if err != nil {
+ t.Errorf("ReadMessage: %s", err)
+ return
+ }
+
+ contents, err := io.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("ReadAll: %s", err)
+ }
+
+ expectedCreationTime := uint32(1295992998)
+ if md.LiteralData.Time != expectedCreationTime {
+ t.Errorf("LiteralData.Time is %d, want %d", md.LiteralData.Time, expectedCreationTime)
+ }
+
+ const expected = "Symmetrically encrypted.\n"
+ if string(contents) != expected {
+ t.Errorf("contents got: %s want: %s", string(contents), expected)
+ }
+}
+
+func testDetachedSignature(t *testing.T, kring KeyRing, signature io.Reader, sigInput, tag string, expectedSignerKeyId uint64) {
+ signed := bytes.NewBufferString(sigInput)
+ signer, err := CheckDetachedSignature(kring, signed, signature)
+ if err != nil {
+ t.Errorf("%s: signature error: %s", tag, err)
+ return
+ }
+ if signer == nil {
+ t.Errorf("%s: signer is nil", tag)
+ return
+ }
+ if signer.PrimaryKey.KeyId != expectedSignerKeyId {
+ t.Errorf("%s: wrong signer got:%x want:%x", tag, signer.PrimaryKey.KeyId, expectedSignerKeyId)
+ }
+}
+
+func TestDetachedSignature(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+ testDetachedSignature(t, kring, readerFromHex(detachedSignatureHex), signedInput, "binary", testKey1KeyId)
+ testDetachedSignature(t, kring, readerFromHex(detachedSignatureTextHex), signedInput, "text", testKey1KeyId)
+ testDetachedSignature(t, kring, readerFromHex(detachedSignatureV3TextHex), signedInput, "v3", testKey1KeyId)
+
+ incorrectSignedInput := signedInput + "X"
+ _, err := CheckDetachedSignature(kring, bytes.NewBufferString(incorrectSignedInput), readerFromHex(detachedSignatureHex))
+ if err == nil {
+ t.Fatal("CheckDetachedSignature returned without error for bad signature")
+ }
+ if err == errors.ErrUnknownIssuer {
+ t.Fatal("CheckDetachedSignature returned ErrUnknownIssuer when the signer was known, but the signature invalid")
+ }
+}
+
+func TestDetachedSignatureDSA(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyHex))
+ testDetachedSignature(t, kring, readerFromHex(detachedSignatureDSAHex), signedInput, "binary", testKey3KeyId)
+}
+
+func TestMultipleSignaturePacketsDSA(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyHex))
+ testDetachedSignature(t, kring, readerFromHex(missingHashFunctionHex+detachedSignatureDSAHex), signedInput, "binary", testKey3KeyId)
+}
+
+func TestDetachedSignatureP256(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(p256TestKeyHex))
+ testDetachedSignature(t, kring, readerFromHex(detachedSignatureP256Hex), signedInput, "binary", testKeyP256KeyId)
+}
+
+func testHashFunctionError(t *testing.T, signatureHex string) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+ _, err := CheckDetachedSignature(kring, nil, readerFromHex(signatureHex))
+ if err == nil {
+ t.Fatal("Packet with bad hash type was correctly parsed")
+ }
+ unsupported, ok := err.(errors.UnsupportedError)
+ if !ok {
+ t.Fatalf("Unexpected class of error: %s", err)
+ }
+ if !strings.Contains(string(unsupported), "hash ") {
+ t.Fatalf("Unexpected error: %s", err)
+ }
+}
+
+func TestUnknownHashFunction(t *testing.T) {
+ // unknownHashFunctionHex contains a signature packet with hash
+ // function type 153 (which isn't a real hash function id).
+ testHashFunctionError(t, unknownHashFunctionHex)
+}
+
+func TestMissingHashFunction(t *testing.T) {
+ // missingHashFunctionHex contains a signature packet that uses
+ // RIPEMD160, which isn't compiled in. Since that's the only signature
+ // packet we don't find any suitable packets and end up with ErrUnknownIssuer
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+ _, err := CheckDetachedSignature(kring, nil, readerFromHex(missingHashFunctionHex))
+ if err == nil {
+ t.Fatal("Packet with missing hash type was correctly parsed")
+ }
+ if err != errors.ErrUnknownIssuer {
+ t.Fatalf("Unexpected class of error: %s", err)
+ }
+}
+
+func TestReadingArmoredPrivateKey(t *testing.T) {
+ el, err := ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKeyBlock))
+ if err != nil {
+ t.Error(err)
+ }
+ if len(el) != 1 {
+ t.Errorf("got %d entities, wanted 1\n", len(el))
+ }
+}
+
+func TestReadingArmoredPublicKey(t *testing.T) {
+ el, err := ReadArmoredKeyRing(bytes.NewBufferString(e2ePublicKey))
+ if err != nil {
+ t.Error(err)
+ }
+ if len(el) != 1 {
+ t.Errorf("didn't get a valid entity")
+ }
+}
+
+func TestNoArmoredData(t *testing.T) {
+ _, err := ReadArmoredKeyRing(bytes.NewBufferString("foo"))
+ if _, ok := err.(errors.InvalidArgumentError); !ok {
+ t.Errorf("error was not an InvalidArgumentError: %s", err)
+ }
+}
+
+func testReadMessageError(t *testing.T, messageHex string) {
+ buf, err := hex.DecodeString(messageHex)
+ if err != nil {
+ t.Errorf("hex.DecodeString(): %v", err)
+ }
+
+ kr, err := ReadKeyRing(new(bytes.Buffer))
+ if err != nil {
+ t.Errorf("ReadKeyring(): %v", err)
+ }
+
+ _, err = ReadMessage(bytes.NewBuffer(buf), kr,
+ func([]Key, bool) ([]byte, error) {
+ return []byte("insecure"), nil
+ }, nil)
+
+ if err == nil {
+ t.Errorf("ReadMessage(): Unexpected nil error")
+ }
+}
+
+func TestIssue11503(t *testing.T) {
+ testReadMessageError(t, "8c040402000aa430aa8228b9248b01fc899a91197130303030")
+}
+
+func TestIssue11504(t *testing.T) {
+ testReadMessageError(t, "9303000130303030303030303030983002303030303030030000000130")
+}
+
+// TestSignatureV3Message tests the verification of V3 signature, generated
+// with a modern V4-style key. Some people have their clients set to generate
+// V3 signatures, so it's useful to be able to verify them.
+func TestSignatureV3Message(t *testing.T) {
+ sig, err := armor.Decode(strings.NewReader(signedMessageV3))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ key, err := ReadArmoredKeyRing(strings.NewReader(keyV4forVerifyingSignedMessageV3))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ md, err := ReadMessage(sig.Body, key, nil, nil)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ _, err = io.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ // We'll see a sig error here after reading in the UnverifiedBody above,
+ // if there was one to see.
+ if err = md.SignatureError; err != nil {
+ t.Error(err)
+ return
+ }
+
+ if md.SignatureV3 == nil {
+ t.Errorf("No available signature after checking signature")
+ return
+ }
+ if md.Signature != nil {
+ t.Errorf("Did not expect a signature V4 back")
+ return
+ }
+ return
+}
+
+const testKey1KeyId = 0xA34D7E18C20C31BB
+const testKey3KeyId = 0x338934250CCC0360
+const testKeyP256KeyId = 0xd44a2c495918513e
+
+const signedInput = "Signed message\nline 2\nline 3\n"
+const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n"
+
+const recipientUnspecifiedHex = "848c0300000000000000000103ff62d4d578d03cf40c3da998dfe216c074fa6ddec5e31c197c9666ba292830d91d18716a80f699f9d897389a90e6d62d0238f5f07a5248073c0f24920e4bc4a30c2d17ee4e0cae7c3d4aaa4e8dced50e3010a80ee692175fa0385f62ecca4b56ee6e9980aa3ec51b61b077096ac9e800edaf161268593eedb6cc7027ff5cb32745d250010d407a6221ae22ef18469b444f2822478c4d190b24d36371a95cb40087cdd42d9399c3d06a53c0673349bfb607927f20d1e122bde1e2bf3aa6cae6edf489629bcaa0689539ae3b718914d88ededc3b"
+
+const detachedSignatureHex = "889c04000102000605024d449cd1000a0910a34d7e18c20c31bb167603ff57718d09f28a519fdc7b5a68b6a3336da04df85e38c5cd5d5bd2092fa4629848a33d85b1729402a2aab39c3ac19f9d573f773cc62c264dc924c067a79dfd8a863ae06c7c8686120760749f5fd9b1e03a64d20a7df3446ddc8f0aeadeaeba7cbaee5c1e366d65b6a0c6cc749bcb912d2f15013f812795c2e29eb7f7b77f39ce77"
+
+const detachedSignatureTextHex = "889c04010102000605024d449d21000a0910a34d7e18c20c31bbc8c60400a24fbef7342603a41cb1165767bd18985d015fb72fe05db42db36cfb2f1d455967f1e491194fbf6cf88146222b23bf6ffbd50d17598d976a0417d3192ff9cc0034fd00f287b02e90418bbefe609484b09231e4e7a5f3562e199bf39909ab5276c4d37382fe088f6b5c3426fc1052865da8b3ab158672d58b6264b10823dc4b39"
+
+const detachedSignatureV3TextHex = "8900950305005255c25ca34d7e18c20c31bb0102bb3f04009f6589ef8a028d6e54f6eaf25432e590d31c3a41f4710897585e10c31e5e332c7f9f409af8512adceaff24d0da1474ab07aa7bce4f674610b010fccc5b579ae5eb00a127f272fb799f988ab8e4574c141da6dbfecfef7e6b2c478d9a3d2551ba741f260ee22bec762812f0053e05380bfdd55ad0f22d8cdf71b233fe51ae8a24"
+
+const detachedSignatureDSAHex = "884604001102000605024d6c4eac000a0910338934250ccc0360f18d00a087d743d6405ed7b87755476629600b8b694a39e900a0abff8126f46faf1547c1743c37b21b4ea15b8f83"
+
+const detachedSignatureP256Hex = "885e0400130a0006050256e5bb00000a0910d44a2c495918513edef001009841a4f792beb0befccb35c8838a6a87d9b936beaa86db6745ddc7b045eee0cf00fd1ac1f78306b17e965935dd3f8bae4587a76587e4af231efe19cc4011a8434817"
+
+const testKeys1And2Hex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b0020003b88d044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f0011010001889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab0020003988d044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b0020003b88d044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020003"
+
+const testKeys1And2PrivateHex = "9501d8044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd00110100010003ff4d91393b9a8e3430b14d6209df42f98dc927425b881f1209f319220841273a802a97c7bdb8b3a7740b3ab5866c4d1d308ad0d3a79bd1e883aacf1ac92dfe720285d10d08752a7efe3c609b1d00f17f2805b217be53999a7da7e493bfc3e9618fd17018991b8128aea70a05dbce30e4fbe626aa45775fa255dd9177aabf4df7cf0200c1ded12566e4bc2bb590455e5becfb2e2c9796482270a943343a7835de41080582c2be3caf5981aa838140e97afa40ad652a0b544f83eb1833b0957dce26e47b0200eacd6046741e9ce2ec5beb6fb5e6335457844fb09477f83b050a96be7da043e17f3a9523567ed40e7a521f818813a8b8a72209f1442844843ccc7eb9805442570200bdafe0438d97ac36e773c7162028d65844c4d463e2420aa2228c6e50dc2743c3d6c72d0d782a5173fe7be2169c8a9f4ef8a7cf3e37165e8c61b89c346cdc6c1799d2b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b00200009d01d8044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f00110100010003fd17a7490c22a79c59281fb7b20f5e6553ec0c1637ae382e8adaea295f50241037f8997cf42c1ce26417e015091451b15424b2c59eb8d4161b0975630408e394d3b00f88d4b4e18e2cc85e8251d4753a27c639c83f5ad4a571c4f19d7cd460b9b73c25ade730c99df09637bd173d8e3e981ac64432078263bb6dc30d3e974150dd0200d0ee05be3d4604d2146fb0457f31ba17c057560785aa804e8ca5530a7cd81d3440d0f4ba6851efcfd3954b7e68908fc0ba47f7ac37bf559c6c168b70d3a7c8cd0200da1c677c4bce06a068070f2b3733b0a714e88d62aa3f9a26c6f5216d48d5c2b5624144f3807c0df30be66b3268eeeca4df1fbded58faf49fc95dc3c35f134f8b01fd1396b6c0fc1b6c4f0eb8f5e44b8eace1e6073e20d0b8bc5385f86f1cf3f050f66af789f3ef1fc107b7f4421e19e0349c730c68f0a226981f4e889054fdb4dc149e8e889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab00200009501fe044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001fe030302e9030f3c783e14856063f16938530e148bc57a7aa3f3e4f90df9dceccdc779bc0835e1ad3d006e4a8d7b36d08b8e0de5a0d947254ecfbd22037e6572b426bcfdc517796b224b0036ff90bc574b5509bede85512f2eefb520fb4b02aa523ba739bff424a6fe81c5041f253f8d757e69a503d3563a104d0d49e9e890b9d0c26f96b55b743883b472caa7050c4acfd4a21f875bdf1258d88bd61224d303dc9df77f743137d51e6d5246b88c406780528fd9a3e15bab5452e5b93970d9dcc79f48b38651b9f15bfbcf6da452837e9cc70683d1bdca94507870f743e4ad902005812488dd342f836e72869afd00ce1850eea4cfa53ce10e3608e13d3c149394ee3cbd0e23d018fcbcb6e2ec5a1a22972d1d462ca05355d0d290dd2751e550d5efb38c6c89686344df64852bf4ff86638708f644e8ec6bd4af9b50d8541cb91891a431326ab2e332faa7ae86cfb6e0540aa63160c1e5cdd5a4add518b303fff0a20117c6bc77f7cfbaf36b04c865c6c2b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b00200009d01fe044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001fe030302e9030f3c783e148560f936097339ae381d63116efcf802ff8b1c9360767db5219cc987375702a4123fd8657d3e22700f23f95020d1b261eda5257e9a72f9a918e8ef22dd5b3323ae03bbc1923dd224db988cadc16acc04b120a9f8b7e84da9716c53e0334d7b66586ddb9014df604b41be1e960dcfcbc96f4ed150a1a0dd070b9eb14276b9b6be413a769a75b519a53d3ecc0c220e85cd91ca354d57e7344517e64b43b6e29823cbd87eae26e2b2e78e6dedfbb76e3e9f77bcb844f9a8932eb3db2c3f9e44316e6f5d60e9e2a56e46b72abe6b06dc9a31cc63f10023d1f5e12d2a3ee93b675c96f504af0001220991c88db759e231b3320dcedf814dcf723fd9857e3d72d66a0f2af26950b915abdf56c1596f46a325bf17ad4810d3535fb02a259b247ac3dbd4cc3ecf9c51b6c07cebb009c1506fba0a89321ec8683e3fd009a6e551d50243e2d5092fefb3321083a4bad91320dc624bd6b5dddf93553e3d53924c05bfebec1fb4bd47e89a1a889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020000"
+
+const dsaElGamalTestKeysHex = "9501e1044dfcb16a110400aa3e5c1a1f43dd28c2ffae8abf5cfce555ee874134d8ba0a0f7b868ce2214beddc74e5e1e21ded354a95d18acdaf69e5e342371a71fbb9093162e0c5f3427de413a7f2c157d83f5cd2f9d791256dc4f6f0e13f13c3302af27f2384075ab3021dff7a050e14854bbde0a1094174855fc02f0bae8e00a340d94a1f22b32e48485700a0cec672ac21258fb95f61de2ce1af74b2c4fa3e6703ff698edc9be22c02ae4d916e4fa223f819d46582c0516235848a77b577ea49018dcd5e9e15cff9dbb4663a1ae6dd7580fa40946d40c05f72814b0f88481207e6c0832c3bded4853ebba0a7e3bd8e8c66df33d5a537cd4acf946d1080e7a3dcea679cb2b11a72a33a2b6a9dc85f466ad2ddf4c3db6283fa645343286971e3dd700703fc0c4e290d45767f370831a90187e74e9972aae5bff488eeff7d620af0362bfb95c1a6c3413ab5d15a2e4139e5d07a54d72583914661ed6a87cce810be28a0aa8879a2dd39e52fb6fe800f4f181ac7e328f740cde3d09a05cecf9483e4cca4253e60d4429ffd679d9996a520012aad119878c941e3cf151459873bdfc2a9563472fe0303027a728f9feb3b864260a1babe83925ce794710cfd642ee4ae0e5b9d74cee49e9c67b6cd0ea5dfbb582132195a121356a1513e1bca73e5b80c58c7ccb4164453412f456c47616d616c2054657374204b65792031886204131102002205024dfcb16a021b03060b090807030206150802090a0b0416020301021e01021780000a091033af447ccd759b09fadd00a0b8fd6f5a790bad7e9f2dbb7632046dc4493588db009c087c6a9ba9f7f49fab221587a74788c00db4889ab00200009d0157044dfcb16a1004008dec3f9291205255ccff8c532318133a6840739dd68b03ba942676f9038612071447bf07d00d559c5c0875724ea16a4c774f80d8338b55fca691a0522e530e604215b467bbc9ccfd483a1da99d7bc2648b4318fdbd27766fc8bfad3fddb37c62b8ae7ccfe9577e9b8d1e77c1d417ed2c2ef02d52f4da11600d85d3229607943700030503ff506c94c87c8cab778e963b76cf63770f0a79bf48fb49d3b4e52234620fc9f7657f9f8d56c96a2b7c7826ae6b57ebb2221a3fe154b03b6637cea7e6d98e3e45d87cf8dc432f723d3d71f89c5192ac8d7290684d2c25ce55846a80c9a7823f6acd9bb29fa6cd71f20bc90eccfca20451d0c976e460e672b000df49466408d527affe0303027a728f9feb3b864260abd761730327bca2aaa4ea0525c175e92bf240682a0e83b226f97ecb2e935b62c9a133858ce31b271fa8eb41f6a1b3cd72a63025ce1a75ee4180dcc284884904181102000905024dfcb16a021b0c000a091033af447ccd759b09dd0b009e3c3e7296092c81bee5a19929462caaf2fff3ae26009e218c437a2340e7ea628149af1ec98ec091a43992b00200009501e1044dfcb1be1104009f61faa61aa43df75d128cbe53de528c4aec49ce9360c992e70c77072ad5623de0a3a6212771b66b39a30dad6781799e92608316900518ec01184a85d872365b7d2ba4bacfb5882ea3c2473d3750dc6178cc1cf82147fb58caa28b28e9f12f6d1efcb0534abed644156c91cca4ab78834268495160b2400bc422beb37d237c2300a0cac94911b6d493bda1e1fbc6feeca7cb7421d34b03fe22cec6ccb39675bb7b94a335c2b7be888fd3906a1125f33301d8aa6ec6ee6878f46f73961c8d57a3e9544d8ef2a2cbfd4d52da665b1266928cfe4cb347a58c412815f3b2d2369dec04b41ac9a71cc9547426d5ab941cccf3b18575637ccfb42df1a802df3cfe0a999f9e7109331170e3a221991bf868543960f8c816c28097e503fe319db10fb98049f3a57d7c80c420da66d56f3644371631fad3f0ff4040a19a4fedc2d07727a1b27576f75a4d28c47d8246f27071e12d7a8de62aad216ddbae6aa02efd6b8a3e2818cda48526549791ab277e447b3a36c57cefe9b592f5eab73959743fcc8e83cbefec03a329b55018b53eec196765ae40ef9e20521a603c551efe0303020950d53a146bf9c66034d00c23130cce95576a2ff78016ca471276e8227fb30b1ffbd92e61804fb0c3eff9e30b1a826ee8f3e4730b4d86273ca977b4164453412f456c47616d616c2054657374204b65792032886204131102002205024dfcb1be021b03060b090807030206150802090a0b0416020301021e01021780000a0910a86bf526325b21b22bd9009e34511620415c974750a20df5cb56b182f3b48e6600a0a9466cb1a1305a84953445f77d461593f1d42bc1b00200009d0157044dfcb1be1004009565a951da1ee87119d600c077198f1c1bceb0f7aa54552489298e41ff788fa8f0d43a69871f0f6f77ebdfb14a4260cf9fbeb65d5844b4272a1904dd95136d06c3da745dc46327dd44a0f16f60135914368c8039a34033862261806bb2c5ce1152e2840254697872c85441ccb7321431d75a747a4bfb1d2c66362b51ce76311700030503fc0ea76601c196768070b7365a200e6ddb09307f262d5f39eec467b5f5784e22abdf1aa49226f59ab37cb49969d8f5230ea65caf56015abda62604544ed526c5c522bf92bed178a078789f6c807b6d34885688024a5bed9e9f8c58d11d4b82487b44c5f470c5606806a0443b79cadb45e0f897a561a53f724e5349b9267c75ca17fe0303020950d53a146bf9c660bc5f4ce8f072465e2d2466434320c1e712272fafc20e342fe7608101580fa1a1a367e60486a7cd1246b7ef5586cf5e10b32762b710a30144f12dd17dd4884904181102000905024dfcb1be021b0c000a0910a86bf526325b21b2904c00a0b2b66b4b39ccffda1d10f3ea8d58f827e30a8b8e009f4255b2d8112a184e40cde43a34e8655ca7809370b0020000"
+
+const signedMessageHex = "a3019bc0cbccc0c4b8d8b74ee2108fe16ec6d3ca490cbe362d3f8333d3f352531472538b8b13d353b97232f352158c20943157c71c16064626063656269052062e4e01987e9b6fccff4b7df3a34c534b23e679cbec3bc0f8f6e64dfb4b55fe3f8efa9ce110ddb5cd79faf1d753c51aecfa669f7e7aa043436596cccc3359cb7dd6bbe9ecaa69e5989d9e57209571edc0b2fa7f57b9b79a64ee6e99ce1371395fee92fec2796f7b15a77c386ff668ee27f6d38f0baa6c438b561657377bf6acff3c5947befd7bf4c196252f1d6e5c524d0300"
+
+const signedTextMessageHex = "a3019bc0cbccc8c4b8d8b74ee2108fe16ec6d36a250cbece0c178233d3f352531472538b8b13d35379b97232f352158ca0b4312f57c71c1646462606365626906a062e4e019811591798ff99bf8afee860b0d8a8c2a85c3387e3bcf0bb3b17987f2bbcfab2aa526d930cbfd3d98757184df3995c9f3e7790e36e3e9779f06089d4c64e9e47dd6202cb6e9bc73c5d11bb59fbaf89d22d8dc7cf199ddf17af96e77c5f65f9bbed56f427bd8db7af37f6c9984bf9385efaf5f184f986fb3e6adb0ecfe35bbf92d16a7aa2a344fb0bc52fb7624f0200"
+
+const signedEncryptedMessageHex = "848c032a67d68660df41c70103ff5789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8d2c03b018bd210b1d3791e1aba74b0f1034e122ab72e760492c192383cf5e20b5628bd043272d63df9b923f147eb6091cd897553204832aba48fec54aa447547bb16305a1024713b90e77fd0065f1918271947549205af3c74891af22ee0b56cd29bfec6d6e351901cd4ab3ece7c486f1e32a792d4e474aed98ee84b3f591c7dff37b64e0ecd68fd036d517e412dcadf85840ce184ad7921ad446c4ee28db80447aea1ca8d4f574db4d4e37688158ddd19e14ee2eab4873d46947d65d14a23e788d912cf9a19624ca7352469b72a83866b7c23cb5ace3deab3c7018061b0ba0f39ed2befe27163e5083cf9b8271e3e3d52cc7ad6e2a3bd81d4c3d7022f8d"
+
+const signedEncryptedMessage2Hex = "85010e03cf6a7abcd43e36731003fb057f5495b79db367e277cdbe4ab90d924ddee0c0381494112ff8c1238fb0184af35d1731573b01bc4c55ecacd2aafbe2003d36310487d1ecc9ac994f3fada7f9f7f5c3a64248ab7782906c82c6ff1303b69a84d9a9529c31ecafbcdb9ba87e05439897d87e8a2a3dec55e14df19bba7f7bd316291c002ae2efd24f83f9e3441203fc081c0c23dc3092a454ca8a082b27f631abf73aca341686982e8fbda7e0e7d863941d68f3de4a755c2964407f4b5e0477b3196b8c93d551dd23c8beef7d0f03fbb1b6066f78907faf4bf1677d8fcec72651124080e0b7feae6b476e72ab207d38d90b958759fdedfc3c6c35717c9dbfc979b3cfbbff0a76d24a5e57056bb88acbd2a901ef64bc6e4db02adc05b6250ff378de81dca18c1910ab257dff1b9771b85bb9bbe0a69f5989e6d1710a35e6dfcceb7d8fb5ccea8db3932b3d9ff3fe0d327597c68b3622aec8e3716c83a6c93f497543b459b58ba504ed6bcaa747d37d2ca746fe49ae0a6ce4a8b694234e941b5159ff8bd34b9023da2814076163b86f40eed7c9472f81b551452d5ab87004a373c0172ec87ea6ce42ccfa7dbdad66b745496c4873d8019e8c28d6b3"
+
+const symmetricallyEncryptedCompressedHex = "8c0d04030302eb4a03808145d0d260c92f714339e13de5a79881216431925bf67ee2898ea61815f07894cd0703c50d0a76ef64d482196f47a8bc729af9b80bb6"
+
+const dsaTestKeyHex = "9901a2044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794"
+
+const dsaTestKeyPrivateHex = "9501bb044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4d00009f592e0619d823953577d4503061706843317e4fee083db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794"
+
+const p256TestKeyHex = "98520456e5b83813082a8648ce3d030107020304a2072cd6d21321266c758cc5b83fab0510f751cb8d91897cddb7047d8d6f185546e2107111b0a95cb8ef063c33245502af7a65f004d5919d93ee74eb71a66253b424502d3235362054657374204b6579203c696e76616c6964406578616d706c652e636f6d3e8879041313080021050256e5b838021b03050b09080702061508090a0b020416020301021e01021780000a0910d44a2c495918513e54e50100dfa64f97d9b47766fc1943c6314ba3f2b2a103d71ad286dc5b1efb96a345b0c80100dbc8150b54241f559da6ef4baacea6d31902b4f4b1bdc09b34bf0502334b7754b8560456e5b83812082a8648ce3d030107020304bfe3cea9cee13486f8d518aa487fecab451f25467d2bf08e58f63e5fa525d5482133e6a79299c274b068ef0be448152ad65cf11cf764348588ca4f6a0bcf22b6030108078861041813080009050256e5b838021b0c000a0910d44a2c495918513e4a4800ff49d589fa64024ad30be363a032e3a0e0e6f5db56ba4c73db850518bf0121b8f20100fd78e065f4c70ea5be9df319ea67e493b936fc78da834a71828043d3154af56e"
+
+const p256TestKeyPrivateHex = "94a50456e5b83813082a8648ce3d030107020304a2072cd6d21321266c758cc5b83fab0510f751cb8d91897cddb7047d8d6f185546e2107111b0a95cb8ef063c33245502af7a65f004d5919d93ee74eb71a66253fe070302f0c2bfb0b6c30f87ee1599472b8636477eab23ced13b271886a4b50ed34c9d8436af5af5b8f88921f0efba6ef8c37c459bbb88bc1c6a13bbd25c4ce9b1e97679569ee77645d469bf4b43de637f5561b424502d3235362054657374204b6579203c696e76616c6964406578616d706c652e636f6d3e8879041313080021050256e5b838021b03050b09080702061508090a0b020416020301021e01021780000a0910d44a2c495918513e54e50100dfa64f97d9b47766fc1943c6314ba3f2b2a103d71ad286dc5b1efb96a345b0c80100dbc8150b54241f559da6ef4baacea6d31902b4f4b1bdc09b34bf0502334b77549ca90456e5b83812082a8648ce3d030107020304bfe3cea9cee13486f8d518aa487fecab451f25467d2bf08e58f63e5fa525d5482133e6a79299c274b068ef0be448152ad65cf11cf764348588ca4f6a0bcf22b603010807fe0703027510012471a603cfee2968dce19f732721ddf03e966fd133b4e3c7a685b788705cbc46fb026dc94724b830c9edbaecd2fb2c662f23169516cacd1fe423f0475c364ecc10abcabcfd4bbbda1a36a1bd8861041813080009050256e5b838021b0c000a0910d44a2c495918513e4a4800ff49d589fa64024ad30be363a032e3a0e0e6f5db56ba4c73db850518bf0121b8f20100fd78e065f4c70ea5be9df319ea67e493b936fc78da834a71828043d3154af56e"
+
+const armoredPrivateKeyBlock = `-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+lQHYBE2rFNoBBADFwqWQIW/DSqcB4yCQqnAFTJ27qS5AnB46ccAdw3u4Greeu3Bp
+idpoHdjULy7zSKlwR1EA873dO/k/e11Ml3dlAFUinWeejWaK2ugFP6JjiieSsrKn
+vWNicdCS4HTWn0X4sjl0ZiAygw6GNhqEQ3cpLeL0g8E9hnYzJKQ0LWJa0QARAQAB
+AAP/TB81EIo2VYNmTq0pK1ZXwUpxCrvAAIG3hwKjEzHcbQznsjNvPUihZ+NZQ6+X
+0HCfPAdPkGDCLCb6NavcSW+iNnLTrdDnSI6+3BbIONqWWdRDYJhqZCkqmG6zqSfL
+IdkJgCw94taUg5BWP/AAeQrhzjChvpMQTVKQL5mnuZbUCeMCAN5qrYMP2S9iKdnk
+VANIFj7656ARKt/nf4CBzxcpHTyB8+d2CtPDKCmlJP6vL8t58Jmih+kHJMvC0dzn
+gr5f5+sCAOOe5gt9e0am7AvQWhdbHVfJU0TQJx+m2OiCJAqGTB1nvtBLHdJnfdC9
+TnXXQ6ZXibqLyBies/xeY2sCKL5qtTMCAKnX9+9d/5yQxRyrQUHt1NYhaXZnJbHx
+q4ytu0eWz+5i68IYUSK69jJ1NWPM0T6SkqpB3KCAIv68VFm9PxqG1KmhSrQIVGVz
+dCBLZXmIuAQTAQIAIgUCTasU2gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA
+CgkQO9o98PRieSoLhgQAkLEZex02Qt7vGhZzMwuN0R22w3VwyYyjBx+fM3JFETy1
+ut4xcLJoJfIaF5ZS38UplgakHG0FQ+b49i8dMij0aZmDqGxrew1m4kBfjXw9B/v+
+eIqpODryb6cOSwyQFH0lQkXC040pjq9YqDsO5w0WYNXYKDnzRV0p4H1pweo2VDid
+AdgETasU2gEEAN46UPeWRqKHvA99arOxee38fBt2CI08iiWyI8T3J6ivtFGixSqV
+bRcPxYO/qLpVe5l84Nb3X71GfVXlc9hyv7CD6tcowL59hg1E/DC5ydI8K8iEpUmK
+/UnHdIY5h8/kqgGxkY/T/hgp5fRQgW1ZoZxLajVlMRZ8W4tFtT0DeA+JABEBAAEA
+A/0bE1jaaZKj6ndqcw86jd+QtD1SF+Cf21CWRNeLKnUds4FRRvclzTyUMuWPkUeX
+TaNNsUOFqBsf6QQ2oHUBBK4VCHffHCW4ZEX2cd6umz7mpHW6XzN4DECEzOVksXtc
+lUC1j4UB91DC/RNQqwX1IV2QLSwssVotPMPqhOi0ZLNY7wIA3n7DWKInxYZZ4K+6
+rQ+POsz6brEoRHwr8x6XlHenq1Oki855pSa1yXIARoTrSJkBtn5oI+f8AzrnN0BN
+oyeQAwIA/7E++3HDi5aweWrViiul9cd3rcsS0dEnksPhvS0ozCJiHsq/6GFmy7J8
+QSHZPteedBnZyNp5jR+H7cIfVN3KgwH/Skq4PsuPhDq5TKK6i8Pc1WW8MA6DXTdU
+nLkX7RGmMwjC0DBf7KWAlPjFaONAX3a8ndnz//fy1q7u2l9AZwrj1qa1iJ8EGAEC
+AAkFAk2rFNoCGwwACgkQO9o98PRieSo2/QP/WTzr4ioINVsvN1akKuekmEMI3LAp
+BfHwatufxxP1U+3Si/6YIk7kuPB9Hs+pRqCXzbvPRrI8NHZBmc8qIGthishdCYad
+AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL
+VrM0m72/jnpKo04=
+=zNCn
+-----END PGP PRIVATE KEY BLOCK-----`
+
+const e2ePublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Charset: UTF-8
+
+xv8AAABSBAAAAAATCCqGSM49AwEHAgME1LRoXSpOxtHXDUdmuvzchyg6005qIBJ4
+sfaSxX7QgH9RV2ONUhC+WiayCNADq+UMzuR/vunSr4aQffXvuGnR383/AAAAFDxk
+Z2lsQHlhaG9vLWluYy5jb20+wv8AAACGBBATCAA4/wAAAAWCVGvAG/8AAAACiwn/
+AAAACZC2VkQCOjdvYf8AAAAFlQgJCgv/AAAAA5YBAv8AAAACngEAAE1BAP0X8veD
+24IjmI5/C6ZAfVNXxgZZFhTAACFX75jUA3oD6AEAzoSwKf1aqH6oq62qhCN/pekX
++WAsVMBhNwzLpqtCRjLO/wAAAFYEAAAAABIIKoZIzj0DAQcCAwT50ain7vXiIRv8
+B1DO3x3cE/aattZ5sHNixJzRCXi2vQIA5QmOxZ6b5jjUekNbdHG3SZi1a2Ak5mfX
+fRxC/5VGAwEIB8L/AAAAZQQYEwgAGP8AAAAFglRrwBz/AAAACZC2VkQCOjdvYQAA
+FJAA9isX3xtGyMLYwp2F3nXm7QEdY5bq5VUcD/RJlj792VwA/1wH0pCzVLl4Q9F9
+ex7En5r7rHR5xwX82Msc+Rq9dSyO
+=7MrZ
+-----END PGP PUBLIC KEY BLOCK-----`
+
+const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003`
+
+const unknownHashFunctionHex = `8a00000040040001990006050253863c24000a09103b4fe6acc0b21f32ffff01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101`
+
+const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101`
+
+const campbellQuine = `a0b001000300fcffa0b001000d00f2ff000300fcffa0b001000d00f2ff8270a01c00000500faff8270a01c00000500faff000500faff001400ebff8270a01c00000500faff000500faff001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400000000ffff000000ffff000b00f4ff428821c400000000ffff000000ffff000b00f4ff0233214c40000100feff000233214c40000100feff0000`
+
+const keyV4forVerifyingSignedMessageV3 = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: GPGTools - https://gpgtools.org
+
+mI0EVfxoFQEEAMBIqmbDfYygcvP6Phr1wr1XI41IF7Qixqybs/foBF8qqblD9gIY
+BKpXjnBOtbkcVOJ0nljd3/sQIfH4E0vQwK5/4YRQSI59eKOqd6Fx+fWQOLG+uu6z
+tewpeCj9LLHvibx/Sc7VWRnrznia6ftrXxJ/wHMezSab3tnGC0YPVdGNABEBAAG0
+JEdvY3J5cHRvIFRlc3QgS2V5IDx0aGVtYXhAZ21haWwuY29tPoi5BBMBCgAjBQJV
+/GgVAhsDBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQeXnQmhdGW9PFVAP+
+K7TU0qX5ArvIONIxh/WAweyOk884c5cE8f+3NOPOOCRGyVy0FId5A7MmD5GOQh4H
+JseOZVEVCqlmngEvtHZb3U1VYtVGE5WZ+6rQhGsMcWP5qaT4soYwMBlSYxgYwQcx
+YhN9qOr292f9j2Y//TTIJmZT4Oa+lMxhWdqTfX+qMgG4jQRV/GgVAQQArhFSiij1
+b+hT3dnapbEU+23Z1yTu1DfF6zsxQ4XQWEV3eR8v+8mEDDNcz8oyyF56k6UQ3rXi
+UMTIwRDg4V6SbZmaFbZYCOwp/EmXJ3rfhm7z7yzXj2OFN22luuqbyVhuL7LRdB0M
+pxgmjXb4tTvfgKd26x34S+QqUJ7W6uprY4sAEQEAAYifBBgBCgAJBQJV/GgVAhsM
+AAoJEHl50JoXRlvT7y8D/02ckx4OMkKBZo7viyrBw0MLG92i+DC2bs35PooHR6zz
+786mitjOp5z2QWNLBvxC70S0qVfCIz8jKupO1J6rq6Z8CcbLF3qjm6h1omUBf8Nd
+EfXKD2/2HV6zMKVknnKzIEzauh+eCKS2CeJUSSSryap/QLVAjRnckaES/OsEWhNB
+=RZia
+-----END PGP PUBLIC KEY BLOCK-----
+`
+
+const signedMessageV3 = `-----BEGIN PGP MESSAGE-----
+Comment: GPGTools - https://gpgtools.org
+
+owGbwMvMwMVYWXlhlrhb9GXG03JJDKF/MtxDMjKLFYAoUaEktbhEITe1uDgxPVWP
+q5NhKjMrWAVcC9evD8z/bF/uWNjqtk/X3y5/38XGRQHm/57rrDRYuGnTw597Xqka
+uM3137/hH3Os+Jf2dc0fXOITKwJvXJvecPVs0ta+Vg7ZO1MLn8w58Xx+6L58mbka
+DGHyU9yTueZE8D+QF/Tz28Y78dqtF56R1VPn9Xw4uJqrWYdd7b3vIZ1V6R4Nh05d
+iT57d/OhWwA=
+=hG7R
+-----END PGP MESSAGE-----
+`
diff --git a/local_crypto_patch/contents/openpgp/s2k/s2k.go b/local_crypto_patch/contents/openpgp/s2k/s2k.go
new file mode 100644
index 0000000000..490cb633ce
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/s2k/s2k.go
@@ -0,0 +1,279 @@
+// 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 s2k implements the various OpenPGP string-to-key transforms as
+// specified in RFC 4800 section 3.7.1.
+//
+// Deprecated: this package is unmaintained except for security fixes. New
+// applications should consider a more focused, modern alternative to OpenPGP
+// for their specific task. If you are required to interoperate with OpenPGP
+// systems and need a maintained package, consider a community fork.
+// See https://golang.org/issue/44226.
+package s2k
+
+import (
+ "crypto"
+ "hash"
+ "io"
+ "strconv"
+
+ "golang.org/x/crypto/openpgp/errors"
+)
+
+// Config collects configuration parameters for s2k key-stretching
+// transformatioms. A nil *Config is valid and results in all default
+// values. Currently, Config is used only by the Serialize function in
+// this package.
+type Config struct {
+ // Hash is the default hash function to be used. If
+ // nil, SHA1 is used.
+ Hash crypto.Hash
+ // S2KCount is only used for symmetric encryption. It
+ // determines the strength of the passphrase stretching when
+ // the said passphrase is hashed to produce a key. S2KCount
+ // should be between 1024 and 65011712, inclusive. If Config
+ // is nil or S2KCount is 0, the value 65536 used. Not all
+ // values in the above range can be represented. S2KCount will
+ // be rounded up to the next representable value if it cannot
+ // be encoded exactly. When set, it is strongly encrouraged to
+ // use a value that is at least 65536. See RFC 4880 Section
+ // 3.7.1.3.
+ S2KCount int
+}
+
+func (c *Config) hash() crypto.Hash {
+ if c == nil || uint(c.Hash) == 0 {
+ // SHA1 is the historical default in this package.
+ return crypto.SHA1
+ }
+
+ return c.Hash
+}
+
+func (c *Config) encodedCount() uint8 {
+ if c == nil || c.S2KCount == 0 {
+ return 96 // The common case. Corresponding to 65536
+ }
+
+ i := c.S2KCount
+ switch {
+ // Behave like GPG. Should we make 65536 the lowest value used?
+ case i < 1024:
+ i = 1024
+ case i > 65011712:
+ i = 65011712
+ }
+
+ return encodeCount(i)
+}
+
+// encodeCount converts an iterative "count" in the range 1024 to
+// 65011712, inclusive, to an encoded count. The return value is the
+// octet that is actually stored in the GPG file. encodeCount panics
+// if i is not in the above range (encodedCount above takes care to
+// pass i in the correct range). See RFC 4880 Section 3.7.7.1.
+func encodeCount(i int) uint8 {
+ if i < 1024 || i > 65011712 {
+ panic("count arg i outside the required range")
+ }
+
+ for encoded := 0; encoded < 256; encoded++ {
+ count := decodeCount(uint8(encoded))
+ if count >= i {
+ return uint8(encoded)
+ }
+ }
+
+ return 255
+}
+
+// decodeCount returns the s2k mode 3 iterative "count" corresponding to
+// the encoded octet c.
+func decodeCount(c uint8) int {
+ return (16 + int(c&15)) << (uint32(c>>4) + 6)
+}
+
+// Simple writes to out the result of computing the Simple S2K function (RFC
+// 4880, section 3.7.1.1) using the given hash and input passphrase.
+func Simple(out []byte, h hash.Hash, in []byte) {
+ Salted(out, h, in, nil)
+}
+
+var zero [1]byte
+
+// Salted writes to out the result of computing the Salted S2K function (RFC
+// 4880, section 3.7.1.2) using the given hash, input passphrase and salt.
+func Salted(out []byte, h hash.Hash, in []byte, salt []byte) {
+ done := 0
+ var digest []byte
+
+ for i := 0; done < len(out); i++ {
+ h.Reset()
+ for j := 0; j < i; j++ {
+ h.Write(zero[:])
+ }
+ h.Write(salt)
+ h.Write(in)
+ digest = h.Sum(digest[:0])
+ n := copy(out[done:], digest)
+ done += n
+ }
+}
+
+// Iterated writes to out the result of computing the Iterated and Salted S2K
+// function (RFC 4880, section 3.7.1.3) using the given hash, input passphrase,
+// salt and iteration count.
+func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) {
+ combined := make([]byte, len(in)+len(salt))
+ copy(combined, salt)
+ copy(combined[len(salt):], in)
+
+ if count < len(combined) {
+ count = len(combined)
+ }
+
+ done := 0
+ var digest []byte
+ for i := 0; done < len(out); i++ {
+ h.Reset()
+ for j := 0; j < i; j++ {
+ h.Write(zero[:])
+ }
+ written := 0
+ for written < count {
+ if written+len(combined) > count {
+ todo := count - written
+ h.Write(combined[:todo])
+ written = count
+ } else {
+ h.Write(combined)
+ written += len(combined)
+ }
+ }
+ digest = h.Sum(digest[:0])
+ n := copy(out[done:], digest)
+ done += n
+ }
+}
+
+// Parse reads a binary specification for a string-to-key transformation from r
+// and returns a function which performs that transform.
+func Parse(r io.Reader) (f func(out, in []byte), err error) {
+ var buf [9]byte
+
+ _, err = io.ReadFull(r, buf[:2])
+ if err != nil {
+ return
+ }
+
+ hash, ok := HashIdToHash(buf[1])
+ if !ok {
+ return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(buf[1])))
+ }
+ if !hash.Available() {
+ return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hash)))
+ }
+ h := hash.New()
+
+ switch buf[0] {
+ case 0:
+ f := func(out, in []byte) {
+ Simple(out, h, in)
+ }
+ return f, nil
+ case 1:
+ _, err = io.ReadFull(r, buf[:8])
+ if err != nil {
+ return
+ }
+ f := func(out, in []byte) {
+ Salted(out, h, in, buf[:8])
+ }
+ return f, nil
+ case 3:
+ _, err = io.ReadFull(r, buf[:9])
+ if err != nil {
+ return
+ }
+ count := decodeCount(buf[8])
+ f := func(out, in []byte) {
+ Iterated(out, h, in, buf[:8], count)
+ }
+ return f, nil
+ }
+
+ return nil, errors.UnsupportedError("S2K function")
+}
+
+// Serialize salts and stretches the given passphrase and writes the
+// resulting key into key. It also serializes an S2K descriptor to
+// w. The key stretching can be configured with c, which may be
+// nil. In that case, sensible defaults will be used.
+func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Config) error {
+ var buf [11]byte
+ buf[0] = 3 /* iterated and salted */
+ buf[1], _ = HashToHashId(c.hash())
+ salt := buf[2:10]
+ if _, err := io.ReadFull(rand, salt); err != nil {
+ return err
+ }
+ encodedCount := c.encodedCount()
+ count := decodeCount(encodedCount)
+ buf[10] = encodedCount
+ if _, err := w.Write(buf[:]); err != nil {
+ return err
+ }
+
+ Iterated(key, c.hash().New(), passphrase, salt, count)
+ return nil
+}
+
+// hashToHashIdMapping contains pairs relating OpenPGP's hash identifier with
+// Go's crypto.Hash type. See RFC 4880, section 9.4.
+var hashToHashIdMapping = []struct {
+ id byte
+ hash crypto.Hash
+ name string
+}{
+ {1, crypto.MD5, "MD5"},
+ {2, crypto.SHA1, "SHA1"},
+ {3, crypto.RIPEMD160, "RIPEMD160"},
+ {8, crypto.SHA256, "SHA256"},
+ {9, crypto.SHA384, "SHA384"},
+ {10, crypto.SHA512, "SHA512"},
+ {11, crypto.SHA224, "SHA224"},
+}
+
+// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP
+// hash id.
+func HashIdToHash(id byte) (h crypto.Hash, ok bool) {
+ for _, m := range hashToHashIdMapping {
+ if m.id == id {
+ return m.hash, true
+ }
+ }
+ return 0, false
+}
+
+// HashIdToString returns the name of the hash function corresponding to the
+// given OpenPGP hash id.
+func HashIdToString(id byte) (name string, ok bool) {
+ for _, m := range hashToHashIdMapping {
+ if m.id == id {
+ return m.name, true
+ }
+ }
+
+ return "", false
+}
+
+// HashToHashId returns an OpenPGP hash id which corresponds the given Hash.
+func HashToHashId(h crypto.Hash) (id byte, ok bool) {
+ for _, m := range hashToHashIdMapping {
+ if m.hash == h {
+ return m.id, true
+ }
+ }
+ return 0, false
+}
diff --git a/local_crypto_patch/contents/openpgp/s2k/s2k_test.go b/local_crypto_patch/contents/openpgp/s2k/s2k_test.go
new file mode 100644
index 0000000000..183d26056b
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/s2k/s2k_test.go
@@ -0,0 +1,137 @@
+// 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 s2k
+
+import (
+ "bytes"
+ "crypto"
+ _ "crypto/md5"
+ "crypto/rand"
+ "crypto/sha1"
+ _ "crypto/sha256"
+ _ "crypto/sha512"
+ "encoding/hex"
+ "testing"
+
+ _ "golang.org/x/crypto/ripemd160"
+)
+
+var saltedTests = []struct {
+ in, out string
+}{
+ {"hello", "10295ac1"},
+ {"world", "ac587a5e"},
+ {"foo", "4dda8077"},
+ {"bar", "bd8aac6b9ea9cae04eae6a91c6133b58b5d9a61c14f355516ed9370456"},
+ {"x", "f1d3f289"},
+ {"xxxxxxxxxxxxxxxxxxxxxxx", "e00d7b45"},
+}
+
+func TestSalted(t *testing.T) {
+ h := sha1.New()
+ salt := [4]byte{1, 2, 3, 4}
+
+ for i, test := range saltedTests {
+ expected, _ := hex.DecodeString(test.out)
+ out := make([]byte, len(expected))
+ Salted(out, h, []byte(test.in), salt[:])
+ if !bytes.Equal(expected, out) {
+ t.Errorf("#%d, got: %x want: %x", i, out, expected)
+ }
+ }
+}
+
+var iteratedTests = []struct {
+ in, out string
+}{
+ {"hello", "83126105"},
+ {"world", "6fa317f9"},
+ {"foo", "8fbc35b9"},
+ {"bar", "2af5a99b54f093789fd657f19bd245af7604d0f6ae06f66602a46a08ae"},
+ {"x", "5a684dfe"},
+ {"xxxxxxxxxxxxxxxxxxxxxxx", "18955174"},
+}
+
+func TestIterated(t *testing.T) {
+ h := sha1.New()
+ salt := [4]byte{4, 3, 2, 1}
+
+ for i, test := range iteratedTests {
+ expected, _ := hex.DecodeString(test.out)
+ out := make([]byte, len(expected))
+ Iterated(out, h, []byte(test.in), salt[:], 31)
+ if !bytes.Equal(expected, out) {
+ t.Errorf("#%d, got: %x want: %x", i, out, expected)
+ }
+ }
+}
+
+var parseTests = []struct {
+ spec, in, out string
+}{
+ /* Simple with SHA1 */
+ {"0002", "hello", "aaf4c61d"},
+ /* Salted with SHA1 */
+ {"01020102030405060708", "hello", "f4f7d67e"},
+ /* Iterated with SHA1 */
+ {"03020102030405060708f1", "hello", "f2a57b7c"},
+}
+
+func TestParse(t *testing.T) {
+ for i, test := range parseTests {
+ spec, _ := hex.DecodeString(test.spec)
+ buf := bytes.NewBuffer(spec)
+ f, err := Parse(buf)
+ if err != nil {
+ t.Errorf("%d: Parse returned error: %s", i, err)
+ continue
+ }
+
+ expected, _ := hex.DecodeString(test.out)
+ out := make([]byte, len(expected))
+ f(out, []byte(test.in))
+ if !bytes.Equal(out, expected) {
+ t.Errorf("%d: output got: %x want: %x", i, out, expected)
+ }
+ if testing.Short() {
+ break
+ }
+ }
+}
+
+func TestSerialize(t *testing.T) {
+ hashes := []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.RIPEMD160,
+ crypto.SHA256, crypto.SHA384, crypto.SHA512, crypto.SHA224}
+ testCounts := []int{-1, 0, 1024, 65536, 4063232, 65011712}
+ for _, h := range hashes {
+ for _, c := range testCounts {
+ testSerializeConfig(t, &Config{Hash: h, S2KCount: c})
+ }
+ }
+}
+
+func testSerializeConfig(t *testing.T, c *Config) {
+ t.Logf("Running testSerializeConfig() with config: %+v", c)
+
+ buf := bytes.NewBuffer(nil)
+ key := make([]byte, 16)
+ passphrase := []byte("testing")
+ err := Serialize(buf, key, rand.Reader, passphrase, c)
+ if err != nil {
+ t.Errorf("failed to serialize: %s", err)
+ return
+ }
+
+ f, err := Parse(buf)
+ if err != nil {
+ t.Errorf("failed to reparse: %s", err)
+ return
+ }
+ key2 := make([]byte, len(key))
+ f(key2, passphrase)
+ if !bytes.Equal(key2, key) {
+ t.Errorf("keys don't match: %x (serialied) vs %x (parsed)", key, key2)
+ }
+}
diff --git a/local_crypto_patch/contents/openpgp/write.go b/local_crypto_patch/contents/openpgp/write.go
new file mode 100644
index 0000000000..b89d48b81d
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/write.go
@@ -0,0 +1,418 @@
+// 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 openpgp
+
+import (
+ "crypto"
+ "hash"
+ "io"
+ "strconv"
+ "time"
+
+ "golang.org/x/crypto/openpgp/armor"
+ "golang.org/x/crypto/openpgp/errors"
+ "golang.org/x/crypto/openpgp/packet"
+ "golang.org/x/crypto/openpgp/s2k"
+)
+
+// DetachSign signs message with the private key from signer (which must
+// already have been decrypted) and writes the signature to w.
+// If config is nil, sensible defaults will be used.
+func DetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
+ return detachSign(w, signer, message, packet.SigTypeBinary, config)
+}
+
+// ArmoredDetachSign signs message with the private key from signer (which
+// must already have been decrypted) and writes an armored signature to w.
+// If config is nil, sensible defaults will be used.
+func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) (err error) {
+ return armoredDetachSign(w, signer, message, packet.SigTypeBinary, config)
+}
+
+// DetachSignText signs message (after canonicalising the line endings) with
+// the private key from signer (which must already have been decrypted) and
+// writes the signature to w.
+// If config is nil, sensible defaults will be used.
+func DetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
+ return detachSign(w, signer, message, packet.SigTypeText, config)
+}
+
+// ArmoredDetachSignText signs message (after canonicalising the line endings)
+// with the private key from signer (which must already have been decrypted)
+// and writes an armored signature to w.
+// If config is nil, sensible defaults will be used.
+func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error {
+ return armoredDetachSign(w, signer, message, packet.SigTypeText, config)
+}
+
+func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
+ out, err := armor.Encode(w, SignatureType, nil)
+ if err != nil {
+ return
+ }
+ err = detachSign(out, signer, message, sigType, config)
+ if err != nil {
+ return
+ }
+ return out.Close()
+}
+
+func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) {
+ if signer.PrivateKey == nil {
+ return errors.InvalidArgumentError("signing key doesn't have a private key")
+ }
+ if signer.PrivateKey.Encrypted {
+ return errors.InvalidArgumentError("signing key is encrypted")
+ }
+
+ sig := new(packet.Signature)
+ sig.SigType = sigType
+ sig.PubKeyAlgo = signer.PrivateKey.PubKeyAlgo
+ sig.Hash = config.Hash()
+ sig.CreationTime = config.Now()
+ sig.IssuerKeyId = &signer.PrivateKey.KeyId
+
+ h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
+ if err != nil {
+ return
+ }
+ io.Copy(wrappedHash, message)
+
+ err = sig.Sign(h, signer.PrivateKey, config)
+ if err != nil {
+ return
+ }
+
+ return sig.Serialize(w)
+}
+
+// FileHints contains metadata about encrypted files. This metadata is, itself,
+// encrypted.
+type FileHints struct {
+ // IsBinary can be set to hint that the contents are binary data.
+ IsBinary bool
+ // FileName hints at the name of the file that should be written. It's
+ // truncated to 255 bytes if longer. It may be empty to suggest that the
+ // file should not be written to disk. It may be equal to "_CONSOLE" to
+ // suggest the data should not be written to disk.
+ FileName string
+ // ModTime contains the modification time of the file, or the zero time if not applicable.
+ ModTime time.Time
+}
+
+// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase.
+// The resulting WriteCloser must be closed after the contents of the file have
+// been written.
+// If config is nil, sensible defaults will be used.
+func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
+ if hints == nil {
+ hints = &FileHints{}
+ }
+
+ key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config)
+ if err != nil {
+ return
+ }
+ w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), key, config)
+ if err != nil {
+ return
+ }
+
+ literaldata := w
+ if algo := config.Compression(); algo != packet.CompressionNone {
+ var compConfig *packet.CompressionConfig
+ if config != nil {
+ compConfig = config.CompressionConfig
+ }
+ literaldata, err = packet.SerializeCompressed(w, algo, compConfig)
+ if err != nil {
+ return
+ }
+ }
+
+ var epochSeconds uint32
+ if !hints.ModTime.IsZero() {
+ epochSeconds = uint32(hints.ModTime.Unix())
+ }
+ return packet.SerializeLiteral(literaldata, hints.IsBinary, hints.FileName, epochSeconds)
+}
+
+// intersectPreferences mutates and returns a prefix of a that contains only
+// the values in the intersection of a and b. The order of a is preserved.
+func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
+ var j int
+ for _, v := range a {
+ for _, v2 := range b {
+ if v == v2 {
+ a[j] = v
+ j++
+ break
+ }
+ }
+ }
+
+ return a[:j]
+}
+
+func hashToHashId(h crypto.Hash) uint8 {
+ v, ok := s2k.HashToHashId(h)
+ if !ok {
+ panic("tried to convert unknown hash")
+ }
+ return v
+}
+
+// writeAndSign writes the data as a payload package and, optionally, signs
+// it. hints contains optional information, that is also encrypted,
+// that aids the recipients in processing the message. The resulting
+// WriteCloser must be closed after the contents of the file have been
+// written. If config is nil, sensible defaults will be used.
+func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
+ var signer *packet.PrivateKey
+ if signed != nil {
+ signKey, ok := signed.signingKey(config.Now())
+ if !ok {
+ return nil, errors.InvalidArgumentError("no valid signing keys")
+ }
+ signer = signKey.PrivateKey
+ if signer == nil {
+ return nil, errors.InvalidArgumentError("no private key in signing key")
+ }
+ if signer.Encrypted {
+ return nil, errors.InvalidArgumentError("signing key must be decrypted")
+ }
+ }
+
+ var hash crypto.Hash
+ for _, hashId := range candidateHashes {
+ if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() {
+ hash = h
+ break
+ }
+ }
+
+ // If the hash specified by config is a candidate, we'll use that.
+ if configuredHash := config.Hash(); configuredHash.Available() {
+ for _, hashId := range candidateHashes {
+ if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash {
+ hash = h
+ break
+ }
+ }
+ }
+
+ if hash == 0 {
+ hashId := candidateHashes[0]
+ name, ok := s2k.HashIdToString(hashId)
+ if !ok {
+ name = "#" + strconv.Itoa(int(hashId))
+ }
+ return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)")
+ }
+
+ if signer != nil {
+ ops := &packet.OnePassSignature{
+ SigType: packet.SigTypeBinary,
+ Hash: hash,
+ PubKeyAlgo: signer.PubKeyAlgo,
+ KeyId: signer.KeyId,
+ IsLast: true,
+ }
+ if err := ops.Serialize(payload); err != nil {
+ return nil, err
+ }
+ }
+
+ if hints == nil {
+ hints = &FileHints{}
+ }
+
+ w := payload
+ if signer != nil {
+ // If we need to write a signature packet after the literal
+ // data then we need to stop literalData from closing
+ // encryptedData.
+ w = noOpCloser{w}
+
+ }
+ var epochSeconds uint32
+ if !hints.ModTime.IsZero() {
+ epochSeconds = uint32(hints.ModTime.Unix())
+ }
+ literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
+ if err != nil {
+ return nil, err
+ }
+
+ if signer != nil {
+ return signatureWriter{payload, literalData, hash, hash.New(), signer, config}, nil
+ }
+ return literalData, nil
+}
+
+// Encrypt encrypts a message to a number of recipients and, optionally, signs
+// it. hints contains optional information, that is also encrypted, that aids
+// the recipients in processing the message. The resulting WriteCloser must
+// be closed after the contents of the file have been written.
+// If config is nil, sensible defaults will be used.
+func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) {
+ if len(to) == 0 {
+ return nil, errors.InvalidArgumentError("no encryption recipient provided")
+ }
+
+ // These are the possible ciphers that we'll use for the message.
+ candidateCiphers := []uint8{
+ uint8(packet.CipherAES128),
+ uint8(packet.CipherAES256),
+ uint8(packet.CipherCAST5),
+ }
+ // These are the possible hash functions that we'll use for the signature.
+ candidateHashes := []uint8{
+ hashToHashId(crypto.SHA256),
+ hashToHashId(crypto.SHA384),
+ hashToHashId(crypto.SHA512),
+ hashToHashId(crypto.SHA1),
+ hashToHashId(crypto.RIPEMD160),
+ }
+ // In the event that a recipient doesn't specify any supported ciphers
+ // or hash functions, these are the ones that we assume that every
+ // implementation supports.
+ defaultCiphers := candidateCiphers[len(candidateCiphers)-1:]
+ defaultHashes := candidateHashes[len(candidateHashes)-1:]
+
+ encryptKeys := make([]Key, len(to))
+ for i := range to {
+ var ok bool
+ encryptKeys[i], ok = to[i].encryptionKey(config.Now())
+ if !ok {
+ return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys")
+ }
+
+ sig := to[i].primaryIdentity().SelfSignature
+
+ preferredSymmetric := sig.PreferredSymmetric
+ if len(preferredSymmetric) == 0 {
+ preferredSymmetric = defaultCiphers
+ }
+ preferredHashes := sig.PreferredHash
+ if len(preferredHashes) == 0 {
+ preferredHashes = defaultHashes
+ }
+ candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric)
+ candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
+ }
+
+ if len(candidateCiphers) == 0 || len(candidateHashes) == 0 {
+ return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms")
+ }
+
+ cipher := packet.CipherFunction(candidateCiphers[0])
+ // If the cipher specified by config is a candidate, we'll use that.
+ configuredCipher := config.Cipher()
+ for _, c := range candidateCiphers {
+ cipherFunc := packet.CipherFunction(c)
+ if cipherFunc == configuredCipher {
+ cipher = cipherFunc
+ break
+ }
+ }
+
+ symKey := make([]byte, cipher.KeySize())
+ if _, err := io.ReadFull(config.Random(), symKey); err != nil {
+ return nil, err
+ }
+
+ for _, key := range encryptKeys {
+ if err := packet.SerializeEncryptedKey(ciphertext, key.PublicKey, cipher, symKey, config); err != nil {
+ return nil, err
+ }
+ }
+
+ payload, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config)
+ if err != nil {
+ return
+ }
+
+ return writeAndSign(payload, candidateHashes, signed, hints, config)
+}
+
+// Sign signs a message. The resulting WriteCloser must be closed after the
+// contents of the file have been written. hints contains optional information
+// that aids the recipients in processing the message.
+// If config is nil, sensible defaults will be used.
+func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Config) (input io.WriteCloser, err error) {
+ if signed == nil {
+ return nil, errors.InvalidArgumentError("no signer provided")
+ }
+
+ // These are the possible hash functions that we'll use for the signature.
+ candidateHashes := []uint8{
+ hashToHashId(crypto.SHA256),
+ hashToHashId(crypto.SHA384),
+ hashToHashId(crypto.SHA512),
+ hashToHashId(crypto.SHA1),
+ hashToHashId(crypto.RIPEMD160),
+ }
+ defaultHashes := candidateHashes[len(candidateHashes)-1:]
+ preferredHashes := signed.primaryIdentity().SelfSignature.PreferredHash
+ if len(preferredHashes) == 0 {
+ preferredHashes = defaultHashes
+ }
+ candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
+ return writeAndSign(noOpCloser{output}, candidateHashes, signed, hints, config)
+}
+
+// signatureWriter hashes the contents of a message while passing it along to
+// literalData. When closed, it closes literalData, writes a signature packet
+// to encryptedData and then also closes encryptedData.
+type signatureWriter struct {
+ encryptedData io.WriteCloser
+ literalData io.WriteCloser
+ hashType crypto.Hash
+ h hash.Hash
+ signer *packet.PrivateKey
+ config *packet.Config
+}
+
+func (s signatureWriter) Write(data []byte) (int, error) {
+ s.h.Write(data)
+ return s.literalData.Write(data)
+}
+
+func (s signatureWriter) Close() error {
+ sig := &packet.Signature{
+ SigType: packet.SigTypeBinary,
+ PubKeyAlgo: s.signer.PubKeyAlgo,
+ Hash: s.hashType,
+ CreationTime: s.config.Now(),
+ IssuerKeyId: &s.signer.KeyId,
+ }
+
+ if err := sig.Sign(s.h, s.signer, s.config); err != nil {
+ return err
+ }
+ if err := s.literalData.Close(); err != nil {
+ return err
+ }
+ if err := sig.Serialize(s.encryptedData); err != nil {
+ return err
+ }
+ return s.encryptedData.Close()
+}
+
+// noOpCloser is like an io.NopCloser, but for an io.Writer.
+// TODO: we have two of these in OpenPGP packages alone. This probably needs
+// to be promoted somewhere more common.
+type noOpCloser struct {
+ w io.Writer
+}
+
+func (c noOpCloser) Write(data []byte) (n int, err error) {
+ return c.w.Write(data)
+}
+
+func (c noOpCloser) Close() error {
+ return nil
+}
diff --git a/local_crypto_patch/contents/openpgp/write_test.go b/local_crypto_patch/contents/openpgp/write_test.go
new file mode 100644
index 0000000000..8b686789ee
--- /dev/null
+++ b/local_crypto_patch/contents/openpgp/write_test.go
@@ -0,0 +1,361 @@
+// 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 openpgp
+
+import (
+ "bytes"
+ "io"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/openpgp/packet"
+)
+
+func TestSignDetached(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+ out := bytes.NewBuffer(nil)
+ message := bytes.NewBufferString(signedInput)
+ err := DetachSign(out, kring[0], message, nil)
+ if err != nil {
+ t.Error(err)
+ }
+
+ testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId)
+}
+
+func TestSignTextDetached(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+ out := bytes.NewBuffer(nil)
+ message := bytes.NewBufferString(signedInput)
+ err := DetachSignText(out, kring[0], message, nil)
+ if err != nil {
+ t.Error(err)
+ }
+
+ testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId)
+}
+
+func TestSignDetachedDSA(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyPrivateHex))
+ out := bytes.NewBuffer(nil)
+ message := bytes.NewBufferString(signedInput)
+ err := DetachSign(out, kring[0], message, nil)
+ if err != nil {
+ t.Error(err)
+ }
+
+ testDetachedSignature(t, kring, out, signedInput, "check", testKey3KeyId)
+}
+
+func TestSignDetachedP256(t *testing.T) {
+ kring, _ := ReadKeyRing(readerFromHex(p256TestKeyPrivateHex))
+ kring[0].PrivateKey.Decrypt([]byte("passphrase"))
+
+ out := bytes.NewBuffer(nil)
+ message := bytes.NewBufferString(signedInput)
+ err := DetachSign(out, kring[0], message, nil)
+ if err != nil {
+ t.Error(err)
+ }
+
+ testDetachedSignature(t, kring, out, signedInput, "check", testKeyP256KeyId)
+}
+
+func TestNewEntity(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+
+ // Check bit-length with no config.
+ e, err := NewEntity("Test User", "test", "test@example.com", nil)
+ if err != nil {
+ t.Errorf("failed to create entity: %s", err)
+ return
+ }
+ bl, err := e.PrimaryKey.BitLength()
+ if err != nil {
+ t.Errorf("failed to find bit length: %s", err)
+ }
+ if int(bl) != defaultRSAKeyBits {
+ t.Errorf("BitLength %v, expected %v", int(bl), defaultRSAKeyBits)
+ }
+
+ // Check bit-length with a config.
+ cfg := &packet.Config{RSABits: 1024}
+ e, err = NewEntity("Test User", "test", "test@example.com", cfg)
+ if err != nil {
+ t.Errorf("failed to create entity: %s", err)
+ return
+ }
+ bl, err = e.PrimaryKey.BitLength()
+ if err != nil {
+ t.Errorf("failed to find bit length: %s", err)
+ }
+ if int(bl) != cfg.RSABits {
+ t.Errorf("BitLength %v, expected %v", bl, cfg.RSABits)
+ }
+
+ w := bytes.NewBuffer(nil)
+ if err := e.SerializePrivate(w, nil); err != nil {
+ t.Errorf("failed to serialize entity: %s", err)
+ return
+ }
+ serialized := w.Bytes()
+
+ el, err := ReadKeyRing(w)
+ if err != nil {
+ t.Errorf("failed to reparse entity: %s", err)
+ return
+ }
+
+ if len(el) != 1 {
+ t.Errorf("wrong number of entities found, got %d, want 1", len(el))
+ }
+
+ w = bytes.NewBuffer(nil)
+ if err := e.SerializePrivate(w, nil); err != nil {
+ t.Errorf("failed to serialize entity second time: %s", err)
+ return
+ }
+
+ if !bytes.Equal(w.Bytes(), serialized) {
+ t.Errorf("results differed")
+ }
+}
+
+func TestSymmetricEncryption(t *testing.T) {
+ buf := new(bytes.Buffer)
+ plaintext, err := SymmetricallyEncrypt(buf, []byte("testing"), nil, nil)
+ if err != nil {
+ t.Errorf("error writing headers: %s", err)
+ return
+ }
+ message := []byte("hello world\n")
+ _, err = plaintext.Write(message)
+ if err != nil {
+ t.Errorf("error writing to plaintext writer: %s", err)
+ }
+ err = plaintext.Close()
+ if err != nil {
+ t.Errorf("error closing plaintext writer: %s", err)
+ }
+
+ md, err := ReadMessage(buf, nil, func(keys []Key, symmetric bool) ([]byte, error) {
+ return []byte("testing"), nil
+ }, nil)
+ if err != nil {
+ t.Errorf("error rereading message: %s", err)
+ }
+ messageBuf := bytes.NewBuffer(nil)
+ _, err = io.Copy(messageBuf, md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("error rereading message: %s", err)
+ }
+ if !bytes.Equal(message, messageBuf.Bytes()) {
+ t.Errorf("recovered message incorrect got '%s', want '%s'", messageBuf.Bytes(), message)
+ }
+}
+
+var testEncryptionTests = []struct {
+ keyRingHex string
+ isSigned bool
+}{
+ {
+ testKeys1And2PrivateHex,
+ false,
+ },
+ {
+ testKeys1And2PrivateHex,
+ true,
+ },
+ {
+ dsaElGamalTestKeysHex,
+ false,
+ },
+ {
+ dsaElGamalTestKeysHex,
+ true,
+ },
+}
+
+func TestEncryption(t *testing.T) {
+ for i, test := range testEncryptionTests {
+ kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex))
+
+ passphrase := []byte("passphrase")
+ for _, entity := range kring {
+ if entity.PrivateKey != nil && entity.PrivateKey.Encrypted {
+ err := entity.PrivateKey.Decrypt(passphrase)
+ if err != nil {
+ t.Errorf("#%d: failed to decrypt key", i)
+ }
+ }
+ for _, subkey := range entity.Subkeys {
+ if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted {
+ err := subkey.PrivateKey.Decrypt(passphrase)
+ if err != nil {
+ t.Errorf("#%d: failed to decrypt subkey", i)
+ }
+ }
+ }
+ }
+
+ var signed *Entity
+ if test.isSigned {
+ signed = kring[0]
+ }
+
+ buf := new(bytes.Buffer)
+ w, err := Encrypt(buf, kring[:1], signed, nil /* no hints */, nil)
+ if err != nil {
+ t.Errorf("#%d: error in Encrypt: %s", i, err)
+ continue
+ }
+
+ const message = "testing"
+ _, err = w.Write([]byte(message))
+ if err != nil {
+ t.Errorf("#%d: error writing plaintext: %s", i, err)
+ continue
+ }
+ err = w.Close()
+ if err != nil {
+ t.Errorf("#%d: error closing WriteCloser: %s", i, err)
+ continue
+ }
+
+ md, err := ReadMessage(buf, kring, nil /* no prompt */, nil)
+ if err != nil {
+ t.Errorf("#%d: error reading message: %s", i, err)
+ continue
+ }
+
+ testTime, _ := time.Parse("2006-01-02", "2013-07-01")
+ if test.isSigned {
+ signKey, _ := kring[0].signingKey(testTime)
+ expectedKeyId := signKey.PublicKey.KeyId
+ if md.SignedByKeyId != expectedKeyId {
+ t.Errorf("#%d: message signed by wrong key id, got: %v, want: %v", i, *md.SignedBy, expectedKeyId)
+ }
+ if md.SignedBy == nil {
+ t.Errorf("#%d: failed to find the signing Entity", i)
+ }
+ }
+
+ plaintext, err := io.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("#%d: error reading encrypted contents: %s", i, err)
+ continue
+ }
+
+ encryptKey, _ := kring[0].encryptionKey(testTime)
+ expectedKeyId := encryptKey.PublicKey.KeyId
+ if len(md.EncryptedToKeyIds) != 1 || md.EncryptedToKeyIds[0] != expectedKeyId {
+ t.Errorf("#%d: expected message to be encrypted to %v, but got %#v", i, expectedKeyId, md.EncryptedToKeyIds)
+ }
+
+ if string(plaintext) != message {
+ t.Errorf("#%d: got: %s, want: %s", i, string(plaintext), message)
+ }
+
+ if test.isSigned {
+ if md.SignatureError != nil {
+ t.Errorf("#%d: signature error: %s", i, md.SignatureError)
+ }
+ if md.Signature == nil {
+ t.Error("signature missing")
+ }
+ }
+ }
+}
+
+var testSigningTests = []struct {
+ keyRingHex string
+}{
+ {
+ testKeys1And2PrivateHex,
+ },
+ {
+ dsaElGamalTestKeysHex,
+ },
+}
+
+func TestSigning(t *testing.T) {
+ for i, test := range testSigningTests {
+ kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex))
+
+ passphrase := []byte("passphrase")
+ for _, entity := range kring {
+ if entity.PrivateKey != nil && entity.PrivateKey.Encrypted {
+ err := entity.PrivateKey.Decrypt(passphrase)
+ if err != nil {
+ t.Errorf("#%d: failed to decrypt key", i)
+ }
+ }
+ for _, subkey := range entity.Subkeys {
+ if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted {
+ err := subkey.PrivateKey.Decrypt(passphrase)
+ if err != nil {
+ t.Errorf("#%d: failed to decrypt subkey", i)
+ }
+ }
+ }
+ }
+
+ signed := kring[0]
+
+ buf := new(bytes.Buffer)
+ w, err := Sign(buf, signed, nil /* no hints */, nil)
+ if err != nil {
+ t.Errorf("#%d: error in Sign: %s", i, err)
+ continue
+ }
+
+ const message = "testing"
+ _, err = w.Write([]byte(message))
+ if err != nil {
+ t.Errorf("#%d: error writing plaintext: %s", i, err)
+ continue
+ }
+ err = w.Close()
+ if err != nil {
+ t.Errorf("#%d: error closing WriteCloser: %s", i, err)
+ continue
+ }
+
+ md, err := ReadMessage(buf, kring, nil /* no prompt */, nil)
+ if err != nil {
+ t.Errorf("#%d: error reading message: %s", i, err)
+ continue
+ }
+
+ testTime, _ := time.Parse("2006-01-02", "2013-07-01")
+ signKey, _ := kring[0].signingKey(testTime)
+ expectedKeyId := signKey.PublicKey.KeyId
+ if md.SignedByKeyId != expectedKeyId {
+ t.Errorf("#%d: message signed by wrong key id, got: %v, want: %v", i, *md.SignedBy, expectedKeyId)
+ }
+ if md.SignedBy == nil {
+ t.Errorf("#%d: failed to find the signing Entity", i)
+ }
+
+ plaintext, err := io.ReadAll(md.UnverifiedBody)
+ if err != nil {
+ t.Errorf("#%d: error reading contents: %v", i, err)
+ continue
+ }
+
+ if string(plaintext) != message {
+ t.Errorf("#%d: got: %q, want: %q", i, plaintext, message)
+ }
+
+ if md.SignatureError != nil {
+ t.Errorf("#%d: signature error: %q", i, md.SignatureError)
+ }
+ if md.Signature == nil {
+ t.Error("signature missing")
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/otr/libotr_test_helper.c b/local_crypto_patch/contents/otr/libotr_test_helper.c
new file mode 100644
index 0000000000..aae03a3df7
--- /dev/null
+++ b/local_crypto_patch/contents/otr/libotr_test_helper.c
@@ -0,0 +1,197 @@
+// 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.
+
+// This code can be compiled and used to test the otr package against libotr.
+// See otr_test.go.
+
+//go:build ignore
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+static int g_session_established = 0;
+
+OtrlPolicy policy(void *opdata, ConnContext *context) {
+ return OTRL_POLICY_ALWAYS;
+}
+
+int is_logged_in(void *opdata, const char *accountname, const char *protocol,
+ const char *recipient) {
+ return 1;
+}
+
+void inject_message(void *opdata, const char *accountname, const char *protocol,
+ const char *recipient, const char *message) {
+ printf("%s\n", message);
+ fflush(stdout);
+ fprintf(stderr, "libotr helper sent: %s\n", message);
+}
+
+void update_context_list(void *opdata) {}
+
+void new_fingerprint(void *opdata, OtrlUserState us, const char *accountname,
+ const char *protocol, const char *username,
+ unsigned char fingerprint[20]) {
+ fprintf(stderr, "NEW FINGERPRINT\n");
+ g_session_established = 1;
+}
+
+void write_fingerprints(void *opdata) {}
+
+void gone_secure(void *opdata, ConnContext *context) {}
+
+void gone_insecure(void *opdata, ConnContext *context) {}
+
+void still_secure(void *opdata, ConnContext *context, int is_reply) {}
+
+int max_message_size(void *opdata, ConnContext *context) { return 99999; }
+
+const char *account_name(void *opdata, const char *account,
+ const char *protocol) {
+ return "ACCOUNT";
+}
+
+void account_name_free(void *opdata, const char *account_name) {}
+
+const char *error_message(void *opdata, ConnContext *context,
+ OtrlErrorCode err_code) {
+ return "ERR";
+}
+
+void error_message_free(void *opdata, const char *msg) {}
+
+void resent_msg_prefix_free(void *opdata, const char *prefix) {}
+
+void handle_smp_event(void *opdata, OtrlSMPEvent smp_event,
+ ConnContext *context, unsigned short progress_event,
+ char *question) {}
+
+void handle_msg_event(void *opdata, OtrlMessageEvent msg_event,
+ ConnContext *context, const char *message,
+ gcry_error_t err) {
+ fprintf(stderr, "msg event: %d %s\n", msg_event, message);
+}
+
+OtrlMessageAppOps uiops = {
+ policy,
+ NULL,
+ is_logged_in,
+ inject_message,
+ update_context_list,
+ new_fingerprint,
+ write_fingerprints,
+ gone_secure,
+ gone_insecure,
+ still_secure,
+ max_message_size,
+ account_name,
+ account_name_free,
+ NULL, /* received_symkey */
+ error_message,
+ error_message_free,
+ NULL, /* resent_msg_prefix */
+ resent_msg_prefix_free,
+ handle_smp_event,
+ handle_msg_event,
+ NULL /* create_instag */,
+ NULL /* convert_msg */,
+ NULL /* convert_free */,
+ NULL /* timer_control */,
+};
+
+static const char kPrivateKeyData[] =
+ "(privkeys (account (name \"account\") (protocol proto) (private-key (dsa "
+ "(p "
+ "#00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F"
+ "30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E"
+ "5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB"
+ "8C031D3561FECEE72EBB4A090D450A9B7A857#) (q "
+ "#00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#) (g "
+ "#535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F"
+ "1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F"
+ "6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57"
+ "597766A2F9CE3857D7ACE3E1E3BC1FC6F26#) (y "
+ "#0AC8670AD767D7A8D9D14CC1AC6744CD7D76F993B77FFD9E39DF01E5A6536EF65E775FCEF"
+ "2A983E2A19BD6415500F6979715D9FD1257E1FE2B6F5E1E74B333079E7C880D39868462A93"
+ "454B41877BE62E5EF0A041C2EE9C9E76BD1E12AE25D9628DECB097025DD625EF49C3258A1A"
+ "3C0FF501E3DC673B76D7BABF349009B6ECF#) (x "
+ "#14D0345A3562C480A039E3C72764F72D79043216#)))))\n";
+
+int main() {
+ OTRL_INIT;
+
+ // We have to write the private key information to a file because the libotr
+ // API demands a filename to read from.
+ const char *tmpdir = "/tmp";
+ if (getenv("TMP")) {
+ tmpdir = getenv("TMP");
+ }
+
+ char private_key_file[256];
+ snprintf(private_key_file, sizeof(private_key_file),
+ "%s/libotr_test_helper_privatekeys-XXXXXX", tmpdir);
+ int fd = mkstemp(private_key_file);
+ if (fd == -1) {
+ perror("creating temp file");
+ }
+ write(fd, kPrivateKeyData, sizeof(kPrivateKeyData) - 1);
+ close(fd);
+
+ OtrlUserState userstate = otrl_userstate_create();
+ otrl_privkey_read(userstate, private_key_file);
+ unlink(private_key_file);
+
+ fprintf(stderr, "libotr helper started\n");
+
+ char buf[4096];
+
+ for (;;) {
+ char *message = fgets(buf, sizeof(buf), stdin);
+ if (strlen(message) == 0) {
+ break;
+ }
+ message[strlen(message) - 1] = 0;
+ fprintf(stderr, "libotr helper got: %s\n", message);
+
+ char *newmessage = NULL;
+ OtrlTLV *tlvs;
+ int ignore_message = otrl_message_receiving(
+ userstate, &uiops, NULL, "account", "proto", "peer", message,
+ &newmessage, &tlvs, NULL, NULL, NULL);
+ if (tlvs) {
+ otrl_tlv_free(tlvs);
+ }
+
+ if (newmessage != NULL) {
+ fprintf(stderr, "libotr got: %s\n", newmessage);
+ otrl_message_free(newmessage);
+
+ gcry_error_t err;
+ char *newmessage = NULL;
+
+ err = otrl_message_sending(userstate, &uiops, NULL, "account", "proto",
+ "peer", 0, "test message", NULL, &newmessage,
+ OTRL_FRAGMENT_SEND_SKIP, NULL, NULL, NULL);
+ if (newmessage == NULL) {
+ fprintf(stderr, "libotr didn't encrypt message\n");
+ return 1;
+ }
+ write(1, newmessage, strlen(newmessage));
+ write(1, "\n", 1);
+ fprintf(stderr, "libotr sent: %s\n", newmessage);
+ otrl_message_free(newmessage);
+
+ g_session_established = 0;
+ write(1, "?OTRv2?\n", 8);
+ fprintf(stderr, "libotr sent: ?OTRv2\n");
+ }
+ }
+
+ return 0;
+}
diff --git a/local_crypto_patch/contents/otr/otr.go b/local_crypto_patch/contents/otr/otr.go
new file mode 100644
index 0000000000..a36f7ca245
--- /dev/null
+++ b/local_crypto_patch/contents/otr/otr.go
@@ -0,0 +1,1423 @@
+// 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 otr implements the Off The Record protocol as specified in
+// http://www.cypherpunks.ca/otr/Protocol-v2-3.1.0.html
+//
+// The version of OTR implemented by this package has been deprecated
+// (https://bugs.otr.im/lib/libotr/issues/140). An implementation of OTRv3 is
+// available at https://github.com/coyim/otr3.
+//
+// The otr package is [frozen] and is not accepting new features.
+//
+// [frozen]: https://go.dev/wiki/Frozen
+package otr
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/dsa"
+ "crypto/hmac"
+ "crypto/rand"
+ "crypto/sha1"
+ "crypto/sha256"
+ "crypto/subtle"
+ "encoding/base64"
+ "encoding/hex"
+ "errors"
+ "hash"
+ "io"
+ "math/big"
+ "strconv"
+)
+
+// SecurityChange describes a change in the security state of a Conversation.
+type SecurityChange int
+
+const (
+ NoChange SecurityChange = iota
+ // NewKeys indicates that a key exchange has completed. This occurs
+ // when a conversation first becomes encrypted, and when the keys are
+ // renegotiated within an encrypted conversation.
+ NewKeys
+ // SMPSecretNeeded indicates that the peer has started an
+ // authentication and that we need to supply a secret. Call SMPQuestion
+ // to get the optional, human readable challenge and then Authenticate
+ // to supply the matching secret.
+ SMPSecretNeeded
+ // SMPComplete indicates that an authentication completed. The identity
+ // of the peer has now been confirmed.
+ SMPComplete
+ // SMPFailed indicates that an authentication failed.
+ SMPFailed
+ // ConversationEnded indicates that the peer ended the secure
+ // conversation.
+ ConversationEnded
+)
+
+// QueryMessage can be sent to a peer to start an OTR conversation.
+var QueryMessage = "?OTRv2?"
+
+// ErrorPrefix can be used to make an OTR error by appending an error message
+// to it.
+var ErrorPrefix = "?OTR Error:"
+
+var (
+ fragmentPartSeparator = []byte(",")
+ fragmentPrefix = []byte("?OTR,")
+ msgPrefix = []byte("?OTR:")
+ queryMarker = []byte("?OTR")
+)
+
+// isQuery attempts to parse an OTR query from msg and returns the greatest
+// common version, or 0 if msg is not an OTR query.
+func isQuery(msg []byte) (greatestCommonVersion int) {
+ pos := bytes.Index(msg, queryMarker)
+ if pos == -1 {
+ return 0
+ }
+ for i, c := range msg[pos+len(queryMarker):] {
+ if i == 0 {
+ if c == '?' {
+ // Indicates support for version 1, but we don't
+ // implement that.
+ continue
+ }
+
+ if c != 'v' {
+ // Invalid message
+ return 0
+ }
+
+ continue
+ }
+
+ if c == '?' {
+ // End of message
+ return
+ }
+
+ if c == ' ' || c == '\t' {
+ // Probably an invalid message
+ return 0
+ }
+
+ if c == '2' {
+ greatestCommonVersion = 2
+ }
+ }
+
+ return 0
+}
+
+const (
+ statePlaintext = iota
+ stateEncrypted
+ stateFinished
+)
+
+const (
+ authStateNone = iota
+ authStateAwaitingDHKey
+ authStateAwaitingRevealSig
+ authStateAwaitingSig
+)
+
+const (
+ msgTypeDHCommit = 2
+ msgTypeData = 3
+ msgTypeDHKey = 10
+ msgTypeRevealSig = 17
+ msgTypeSig = 18
+)
+
+const (
+ // If the requested fragment size is less than this, it will be ignored.
+ minFragmentSize = 18
+ // Messages are padded to a multiple of this number of bytes.
+ paddingGranularity = 256
+ // The number of bytes in a Diffie-Hellman private value (320-bits).
+ dhPrivateBytes = 40
+ // The number of bytes needed to represent an element of the DSA
+ // subgroup (160-bits).
+ dsaSubgroupBytes = 20
+ // The number of bytes of the MAC that are sent on the wire (160-bits).
+ macPrefixBytes = 20
+)
+
+// These are the global, common group parameters for OTR.
+var (
+ p *big.Int // group prime
+ g *big.Int // group generator
+ q *big.Int // group order
+ pMinus2 *big.Int
+)
+
+func init() {
+ p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", 16)
+ q, _ = new(big.Int).SetString("7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68948127044533E63A0105DF531D89CD9128A5043CC71A026EF7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6F71C35FDAD44CFD2D74F9208BE258FF324943328F6722D9EE1003E5C50B1DF82CC6D241B0E2AE9CD348B1FD47E9267AFC1B2AE91EE51D6CB0E3179AB1042A95DCF6A9483B84B4B36B3861AA7255E4C0278BA36046511B993FFFFFFFFFFFFFFFF", 16)
+ g = new(big.Int).SetInt64(2)
+ pMinus2 = new(big.Int).Sub(p, g)
+}
+
+// Conversation represents a relation with a peer. The zero value is a valid
+// Conversation, although PrivateKey must be set.
+//
+// When communicating with a peer, all inbound messages should be passed to
+// Conversation.Receive and all outbound messages to Conversation.Send. The
+// Conversation will take care of maintaining the encryption state and
+// negotiating encryption as needed.
+type Conversation struct {
+ // PrivateKey contains the private key to use to sign key exchanges.
+ PrivateKey *PrivateKey
+
+ // Rand can be set to override the entropy source. Otherwise,
+ // crypto/rand will be used.
+ Rand io.Reader
+ // If FragmentSize is set, all messages produced by Receive and Send
+ // will be fragmented into messages of, at most, this number of bytes.
+ FragmentSize int
+
+ // Once Receive has returned NewKeys once, the following fields are
+ // valid.
+ SSID [8]byte
+ TheirPublicKey PublicKey
+
+ state, authState int
+
+ r [16]byte
+ x, y *big.Int
+ gx, gy *big.Int
+ gxBytes []byte
+ digest [sha256.Size]byte
+
+ revealKeys, sigKeys akeKeys
+
+ myKeyId uint32
+ myCurrentDHPub *big.Int
+ myCurrentDHPriv *big.Int
+ myLastDHPub *big.Int
+ myLastDHPriv *big.Int
+
+ theirKeyId uint32
+ theirCurrentDHPub *big.Int
+ theirLastDHPub *big.Int
+
+ keySlots [4]keySlot
+
+ myCounter [8]byte
+ theirLastCtr [8]byte
+ oldMACs []byte
+
+ k, n int // fragment state
+ frag []byte
+
+ smp smpState
+}
+
+// A keySlot contains key material for a specific (their keyid, my keyid) pair.
+type keySlot struct {
+ // used is true if this slot is valid. If false, it's free for reuse.
+ used bool
+ theirKeyId uint32
+ myKeyId uint32
+ sendAESKey, recvAESKey []byte
+ sendMACKey, recvMACKey []byte
+ theirLastCtr [8]byte
+}
+
+// akeKeys are generated during key exchange. There's one set for the reveal
+// signature message and another for the signature message. In the protocol
+// spec the latter are indicated with a prime mark.
+type akeKeys struct {
+ c [16]byte
+ m1, m2 [32]byte
+}
+
+func (c *Conversation) rand() io.Reader {
+ if c.Rand != nil {
+ return c.Rand
+ }
+ return rand.Reader
+}
+
+func (c *Conversation) randMPI(buf []byte) *big.Int {
+ _, err := io.ReadFull(c.rand(), buf)
+ if err != nil {
+ panic("otr: short read from random source")
+ }
+
+ return new(big.Int).SetBytes(buf)
+}
+
+// tlv represents the type-length value from the protocol.
+type tlv struct {
+ typ, length uint16
+ data []byte
+}
+
+const (
+ tlvTypePadding = 0
+ tlvTypeDisconnected = 1
+ tlvTypeSMP1 = 2
+ tlvTypeSMP2 = 3
+ tlvTypeSMP3 = 4
+ tlvTypeSMP4 = 5
+ tlvTypeSMPAbort = 6
+ tlvTypeSMP1WithQuestion = 7
+)
+
+// Receive handles a message from a peer. It returns a human readable message,
+// an indicator of whether that message was encrypted, a hint about the
+// encryption state and zero or more messages to send back to the peer.
+// These messages do not need to be passed to Send before transmission.
+func (c *Conversation) Receive(in []byte) (out []byte, encrypted bool, change SecurityChange, toSend [][]byte, err error) {
+ if bytes.HasPrefix(in, fragmentPrefix) {
+ in, err = c.processFragment(in)
+ if in == nil || err != nil {
+ return
+ }
+ }
+
+ if bytes.HasPrefix(in, msgPrefix) && in[len(in)-1] == '.' {
+ in = in[len(msgPrefix) : len(in)-1]
+ } else if version := isQuery(in); version > 0 {
+ c.authState = authStateAwaitingDHKey
+ c.reset()
+ toSend = c.encode(c.generateDHCommit())
+ return
+ } else {
+ // plaintext message
+ out = in
+ return
+ }
+
+ msg := make([]byte, base64.StdEncoding.DecodedLen(len(in)))
+ msgLen, err := base64.StdEncoding.Decode(msg, in)
+ if err != nil {
+ err = errors.New("otr: invalid base64 encoding in message")
+ return
+ }
+ msg = msg[:msgLen]
+
+ // The first two bytes are the protocol version (2)
+ if len(msg) < 3 || msg[0] != 0 || msg[1] != 2 {
+ err = errors.New("otr: invalid OTR message")
+ return
+ }
+
+ msgType := int(msg[2])
+ msg = msg[3:]
+
+ switch msgType {
+ case msgTypeDHCommit:
+ switch c.authState {
+ case authStateNone:
+ c.authState = authStateAwaitingRevealSig
+ if err = c.processDHCommit(msg); err != nil {
+ return
+ }
+ c.reset()
+ toSend = c.encode(c.generateDHKey())
+ return
+ case authStateAwaitingDHKey:
+ // This is a 'SYN-crossing'. The greater digest wins.
+ var cmp int
+ if cmp, err = c.compareToDHCommit(msg); err != nil {
+ return
+ }
+ if cmp > 0 {
+ // We win. Retransmit DH commit.
+ toSend = c.encode(c.serializeDHCommit())
+ return
+ } else {
+ // They win. We forget about our DH commit.
+ c.authState = authStateAwaitingRevealSig
+ if err = c.processDHCommit(msg); err != nil {
+ return
+ }
+ c.reset()
+ toSend = c.encode(c.generateDHKey())
+ return
+ }
+ case authStateAwaitingRevealSig:
+ if err = c.processDHCommit(msg); err != nil {
+ return
+ }
+ toSend = c.encode(c.serializeDHKey())
+ case authStateAwaitingSig:
+ if err = c.processDHCommit(msg); err != nil {
+ return
+ }
+ c.reset()
+ toSend = c.encode(c.generateDHKey())
+ c.authState = authStateAwaitingRevealSig
+ default:
+ panic("bad state")
+ }
+ case msgTypeDHKey:
+ switch c.authState {
+ case authStateAwaitingDHKey:
+ var isSame bool
+ if isSame, err = c.processDHKey(msg); err != nil {
+ return
+ }
+ if isSame {
+ err = errors.New("otr: unexpected duplicate DH key")
+ return
+ }
+ toSend = c.encode(c.generateRevealSig())
+ c.authState = authStateAwaitingSig
+ case authStateAwaitingSig:
+ var isSame bool
+ if isSame, err = c.processDHKey(msg); err != nil {
+ return
+ }
+ if isSame {
+ toSend = c.encode(c.serializeDHKey())
+ }
+ }
+ case msgTypeRevealSig:
+ if c.authState != authStateAwaitingRevealSig {
+ return
+ }
+ if err = c.processRevealSig(msg); err != nil {
+ return
+ }
+ toSend = c.encode(c.generateSig())
+ c.authState = authStateNone
+ c.state = stateEncrypted
+ change = NewKeys
+ case msgTypeSig:
+ if c.authState != authStateAwaitingSig {
+ return
+ }
+ if err = c.processSig(msg); err != nil {
+ return
+ }
+ c.authState = authStateNone
+ c.state = stateEncrypted
+ change = NewKeys
+ case msgTypeData:
+ if c.state != stateEncrypted {
+ err = errors.New("otr: encrypted message received without encrypted session established")
+ return
+ }
+ var tlvs []tlv
+ out, tlvs, err = c.processData(msg)
+ encrypted = true
+
+ EachTLV:
+ for _, inTLV := range tlvs {
+ switch inTLV.typ {
+ case tlvTypeDisconnected:
+ change = ConversationEnded
+ c.state = stateFinished
+ break EachTLV
+ case tlvTypeSMP1, tlvTypeSMP2, tlvTypeSMP3, tlvTypeSMP4, tlvTypeSMPAbort, tlvTypeSMP1WithQuestion:
+ var reply tlv
+ var complete bool
+ reply, complete, err = c.processSMP(inTLV)
+ if err == smpSecretMissingError {
+ err = nil
+ change = SMPSecretNeeded
+ c.smp.saved = &inTLV
+ return
+ }
+ if err == smpFailureError {
+ err = nil
+ change = SMPFailed
+ } else if complete {
+ change = SMPComplete
+ }
+ if reply.typ != 0 {
+ toSend = c.encode(c.generateData(nil, &reply))
+ }
+ break EachTLV
+ default:
+ // skip unknown TLVs
+ }
+ }
+ default:
+ err = errors.New("otr: unknown message type " + strconv.Itoa(msgType))
+ }
+
+ return
+}
+
+// Send takes a human readable message from the local user, possibly encrypts
+// it and returns zero one or more messages to send to the peer.
+func (c *Conversation) Send(msg []byte) ([][]byte, error) {
+ switch c.state {
+ case statePlaintext:
+ return [][]byte{msg}, nil
+ case stateEncrypted:
+ return c.encode(c.generateData(msg, nil)), nil
+ case stateFinished:
+ return nil, errors.New("otr: cannot send message because secure conversation has finished")
+ }
+
+ return nil, errors.New("otr: cannot send message in current state")
+}
+
+// SMPQuestion returns the human readable challenge question from the peer.
+// It's only valid after Receive has returned SMPSecretNeeded.
+func (c *Conversation) SMPQuestion() string {
+ return c.smp.question
+}
+
+// Authenticate begins an authentication with the peer. Authentication involves
+// an optional challenge message and a shared secret. The authentication
+// proceeds until either Receive returns SMPComplete, SMPSecretNeeded (which
+// indicates that a new authentication is happening and thus this one was
+// aborted) or SMPFailed.
+func (c *Conversation) Authenticate(question string, mutualSecret []byte) (toSend [][]byte, err error) {
+ if c.state != stateEncrypted {
+ err = errors.New("otr: can't authenticate a peer without a secure conversation established")
+ return
+ }
+
+ if c.smp.saved != nil {
+ c.calcSMPSecret(mutualSecret, false /* they started it */)
+
+ var out tlv
+ var complete bool
+ out, complete, err = c.processSMP(*c.smp.saved)
+ if complete {
+ panic("SMP completed on the first message")
+ }
+ c.smp.saved = nil
+ if out.typ != 0 {
+ toSend = c.encode(c.generateData(nil, &out))
+ }
+ return
+ }
+
+ c.calcSMPSecret(mutualSecret, true /* we started it */)
+ outs := c.startSMP(question)
+ for _, out := range outs {
+ toSend = append(toSend, c.encode(c.generateData(nil, &out))...)
+ }
+ return
+}
+
+// End ends a secure conversation by generating a termination message for
+// the peer and switches to unencrypted communication.
+func (c *Conversation) End() (toSend [][]byte) {
+ switch c.state {
+ case statePlaintext:
+ return nil
+ case stateEncrypted:
+ c.state = statePlaintext
+ return c.encode(c.generateData(nil, &tlv{typ: tlvTypeDisconnected}))
+ case stateFinished:
+ c.state = statePlaintext
+ return nil
+ }
+ panic("unreachable")
+}
+
+// IsEncrypted returns true if a message passed to Send would be encrypted
+// before transmission. This result remains valid until the next call to
+// Receive or End, which may change the state of the Conversation.
+func (c *Conversation) IsEncrypted() bool {
+ return c.state == stateEncrypted
+}
+
+var fragmentError = errors.New("otr: invalid OTR fragment")
+
+// processFragment processes a fragmented OTR message and possibly returns a
+// complete message. Fragmented messages look like "?OTR,k,n,msg," where k is
+// the fragment number (starting from 1), n is the number of fragments in this
+// message and msg is a substring of the base64 encoded message.
+func (c *Conversation) processFragment(in []byte) (out []byte, err error) {
+ in = in[len(fragmentPrefix):] // remove "?OTR,"
+ parts := bytes.Split(in, fragmentPartSeparator)
+ if len(parts) != 4 || len(parts[3]) != 0 {
+ return nil, fragmentError
+ }
+
+ k, err := strconv.Atoi(string(parts[0]))
+ if err != nil {
+ return nil, fragmentError
+ }
+
+ n, err := strconv.Atoi(string(parts[1]))
+ if err != nil {
+ return nil, fragmentError
+ }
+
+ if k < 1 || n < 1 || k > n {
+ return nil, fragmentError
+ }
+
+ if k == 1 {
+ c.frag = append(c.frag[:0], parts[2]...)
+ c.k, c.n = k, n
+ } else if n == c.n && k == c.k+1 {
+ c.frag = append(c.frag, parts[2]...)
+ c.k++
+ } else {
+ c.frag = c.frag[:0]
+ c.n, c.k = 0, 0
+ }
+
+ if c.n > 0 && c.k == c.n {
+ c.n, c.k = 0, 0
+ return c.frag, nil
+ }
+
+ return nil, nil
+}
+
+func (c *Conversation) generateDHCommit() []byte {
+ _, err := io.ReadFull(c.rand(), c.r[:])
+ if err != nil {
+ panic("otr: short read from random source")
+ }
+
+ var xBytes [dhPrivateBytes]byte
+ c.x = c.randMPI(xBytes[:])
+ c.gx = new(big.Int).Exp(g, c.x, p)
+ c.gy = nil
+ c.gxBytes = appendMPI(nil, c.gx)
+
+ h := sha256.New()
+ h.Write(c.gxBytes)
+ h.Sum(c.digest[:0])
+
+ aesCipher, err := aes.NewCipher(c.r[:])
+ if err != nil {
+ panic(err.Error())
+ }
+
+ var iv [aes.BlockSize]byte
+ ctr := cipher.NewCTR(aesCipher, iv[:])
+ ctr.XORKeyStream(c.gxBytes, c.gxBytes)
+
+ return c.serializeDHCommit()
+}
+
+func (c *Conversation) serializeDHCommit() []byte {
+ var ret []byte
+ ret = appendU16(ret, 2) // protocol version
+ ret = append(ret, msgTypeDHCommit)
+ ret = appendData(ret, c.gxBytes)
+ ret = appendData(ret, c.digest[:])
+ return ret
+}
+
+func (c *Conversation) processDHCommit(in []byte) error {
+ var ok1, ok2 bool
+ c.gxBytes, in, ok1 = getData(in)
+ digest, in, ok2 := getData(in)
+ if !ok1 || !ok2 || len(in) > 0 {
+ return errors.New("otr: corrupt DH commit message")
+ }
+ copy(c.digest[:], digest)
+ return nil
+}
+
+func (c *Conversation) compareToDHCommit(in []byte) (int, error) {
+ _, in, ok1 := getData(in)
+ digest, in, ok2 := getData(in)
+ if !ok1 || !ok2 || len(in) > 0 {
+ return 0, errors.New("otr: corrupt DH commit message")
+ }
+ return bytes.Compare(c.digest[:], digest), nil
+}
+
+func (c *Conversation) generateDHKey() []byte {
+ var yBytes [dhPrivateBytes]byte
+ c.y = c.randMPI(yBytes[:])
+ c.gy = new(big.Int).Exp(g, c.y, p)
+ return c.serializeDHKey()
+}
+
+func (c *Conversation) serializeDHKey() []byte {
+ var ret []byte
+ ret = appendU16(ret, 2) // protocol version
+ ret = append(ret, msgTypeDHKey)
+ ret = appendMPI(ret, c.gy)
+ return ret
+}
+
+func (c *Conversation) processDHKey(in []byte) (isSame bool, err error) {
+ gy, _, ok := getMPI(in)
+ if !ok {
+ err = errors.New("otr: corrupt DH key message")
+ return
+ }
+ if gy.Cmp(g) < 0 || gy.Cmp(pMinus2) > 0 {
+ err = errors.New("otr: DH value out of range")
+ return
+ }
+ if c.gy != nil {
+ isSame = c.gy.Cmp(gy) == 0
+ return
+ }
+ c.gy = gy
+ return
+}
+
+func (c *Conversation) generateEncryptedSignature(keys *akeKeys, xFirst bool) ([]byte, []byte) {
+ var xb []byte
+ xb = c.PrivateKey.PublicKey.Serialize(xb)
+
+ var verifyData []byte
+ if xFirst {
+ verifyData = appendMPI(verifyData, c.gx)
+ verifyData = appendMPI(verifyData, c.gy)
+ } else {
+ verifyData = appendMPI(verifyData, c.gy)
+ verifyData = appendMPI(verifyData, c.gx)
+ }
+ verifyData = append(verifyData, xb...)
+ verifyData = appendU32(verifyData, c.myKeyId)
+
+ mac := hmac.New(sha256.New, keys.m1[:])
+ mac.Write(verifyData)
+ mb := mac.Sum(nil)
+
+ xb = appendU32(xb, c.myKeyId)
+ xb = append(xb, c.PrivateKey.Sign(c.rand(), mb)...)
+
+ aesCipher, err := aes.NewCipher(keys.c[:])
+ if err != nil {
+ panic(err.Error())
+ }
+ var iv [aes.BlockSize]byte
+ ctr := cipher.NewCTR(aesCipher, iv[:])
+ ctr.XORKeyStream(xb, xb)
+
+ mac = hmac.New(sha256.New, keys.m2[:])
+ encryptedSig := appendData(nil, xb)
+ mac.Write(encryptedSig)
+
+ return encryptedSig, mac.Sum(nil)
+}
+
+func (c *Conversation) generateRevealSig() []byte {
+ s := new(big.Int).Exp(c.gy, c.x, p)
+ c.calcAKEKeys(s)
+ c.myKeyId++
+
+ encryptedSig, mac := c.generateEncryptedSignature(&c.revealKeys, true /* gx comes first */)
+
+ c.myCurrentDHPub = c.gx
+ c.myCurrentDHPriv = c.x
+ c.rotateDHKeys()
+ incCounter(&c.myCounter)
+
+ var ret []byte
+ ret = appendU16(ret, 2)
+ ret = append(ret, msgTypeRevealSig)
+ ret = appendData(ret, c.r[:])
+ ret = append(ret, encryptedSig...)
+ ret = append(ret, mac[:20]...)
+ return ret
+}
+
+func (c *Conversation) processEncryptedSig(encryptedSig, theirMAC []byte, keys *akeKeys, xFirst bool) error {
+ mac := hmac.New(sha256.New, keys.m2[:])
+ mac.Write(appendData(nil, encryptedSig))
+ myMAC := mac.Sum(nil)[:20]
+
+ if len(myMAC) != len(theirMAC) || subtle.ConstantTimeCompare(myMAC, theirMAC) == 0 {
+ return errors.New("bad signature MAC in encrypted signature")
+ }
+
+ aesCipher, err := aes.NewCipher(keys.c[:])
+ if err != nil {
+ panic(err.Error())
+ }
+ var iv [aes.BlockSize]byte
+ ctr := cipher.NewCTR(aesCipher, iv[:])
+ ctr.XORKeyStream(encryptedSig, encryptedSig)
+
+ sig := encryptedSig
+ sig, ok1 := c.TheirPublicKey.Parse(sig)
+ keyId, sig, ok2 := getU32(sig)
+ if !ok1 || !ok2 {
+ return errors.New("otr: corrupt encrypted signature")
+ }
+
+ var verifyData []byte
+ if xFirst {
+ verifyData = appendMPI(verifyData, c.gx)
+ verifyData = appendMPI(verifyData, c.gy)
+ } else {
+ verifyData = appendMPI(verifyData, c.gy)
+ verifyData = appendMPI(verifyData, c.gx)
+ }
+ verifyData = c.TheirPublicKey.Serialize(verifyData)
+ verifyData = appendU32(verifyData, keyId)
+
+ mac = hmac.New(sha256.New, keys.m1[:])
+ mac.Write(verifyData)
+ mb := mac.Sum(nil)
+
+ sig, ok1 = c.TheirPublicKey.Verify(mb, sig)
+ if !ok1 {
+ return errors.New("bad signature in encrypted signature")
+ }
+ if len(sig) > 0 {
+ return errors.New("corrupt encrypted signature")
+ }
+
+ c.theirKeyId = keyId
+ zero(c.theirLastCtr[:])
+ return nil
+}
+
+func (c *Conversation) processRevealSig(in []byte) error {
+ r, in, ok1 := getData(in)
+ encryptedSig, in, ok2 := getData(in)
+ theirMAC := in
+ if !ok1 || !ok2 || len(theirMAC) != 20 {
+ return errors.New("otr: corrupt reveal signature message")
+ }
+
+ aesCipher, err := aes.NewCipher(r)
+ if err != nil {
+ return errors.New("otr: cannot create AES cipher from reveal signature message: " + err.Error())
+ }
+ var iv [aes.BlockSize]byte
+ ctr := cipher.NewCTR(aesCipher, iv[:])
+ ctr.XORKeyStream(c.gxBytes, c.gxBytes)
+ h := sha256.New()
+ h.Write(c.gxBytes)
+ digest := h.Sum(nil)
+ if len(digest) != len(c.digest) || subtle.ConstantTimeCompare(digest, c.digest[:]) == 0 {
+ return errors.New("otr: bad commit MAC in reveal signature message")
+ }
+ var rest []byte
+ c.gx, rest, ok1 = getMPI(c.gxBytes)
+ if !ok1 || len(rest) > 0 {
+ return errors.New("otr: gx corrupt after decryption")
+ }
+ if c.gx.Cmp(g) < 0 || c.gx.Cmp(pMinus2) > 0 {
+ return errors.New("otr: DH value out of range")
+ }
+ s := new(big.Int).Exp(c.gx, c.y, p)
+ c.calcAKEKeys(s)
+
+ if err := c.processEncryptedSig(encryptedSig, theirMAC, &c.revealKeys, true /* gx comes first */); err != nil {
+ return errors.New("otr: in reveal signature message: " + err.Error())
+ }
+
+ c.theirCurrentDHPub = c.gx
+ c.theirLastDHPub = nil
+
+ return nil
+}
+
+func (c *Conversation) generateSig() []byte {
+ c.myKeyId++
+
+ encryptedSig, mac := c.generateEncryptedSignature(&c.sigKeys, false /* gy comes first */)
+
+ c.myCurrentDHPub = c.gy
+ c.myCurrentDHPriv = c.y
+ c.rotateDHKeys()
+ incCounter(&c.myCounter)
+
+ var ret []byte
+ ret = appendU16(ret, 2)
+ ret = append(ret, msgTypeSig)
+ ret = append(ret, encryptedSig...)
+ ret = append(ret, mac[:macPrefixBytes]...)
+ return ret
+}
+
+func (c *Conversation) processSig(in []byte) error {
+ encryptedSig, in, ok1 := getData(in)
+ theirMAC := in
+ if !ok1 || len(theirMAC) != macPrefixBytes {
+ return errors.New("otr: corrupt signature message")
+ }
+
+ if err := c.processEncryptedSig(encryptedSig, theirMAC, &c.sigKeys, false /* gy comes first */); err != nil {
+ return errors.New("otr: in signature message: " + err.Error())
+ }
+
+ c.theirCurrentDHPub = c.gy
+ c.theirLastDHPub = nil
+
+ return nil
+}
+
+func (c *Conversation) rotateDHKeys() {
+ // evict slots using our retired key id
+ for i := range c.keySlots {
+ slot := &c.keySlots[i]
+ if slot.used && slot.myKeyId == c.myKeyId-1 {
+ slot.used = false
+ c.oldMACs = append(c.oldMACs, slot.recvMACKey...)
+ }
+ }
+
+ c.myLastDHPriv = c.myCurrentDHPriv
+ c.myLastDHPub = c.myCurrentDHPub
+
+ var xBytes [dhPrivateBytes]byte
+ c.myCurrentDHPriv = c.randMPI(xBytes[:])
+ c.myCurrentDHPub = new(big.Int).Exp(g, c.myCurrentDHPriv, p)
+ c.myKeyId++
+}
+
+func (c *Conversation) processData(in []byte) (out []byte, tlvs []tlv, err error) {
+ origIn := in
+ flags, in, ok1 := getU8(in)
+ theirKeyId, in, ok2 := getU32(in)
+ myKeyId, in, ok3 := getU32(in)
+ y, in, ok4 := getMPI(in)
+ counter, in, ok5 := getNBytes(in, 8)
+ encrypted, in, ok6 := getData(in)
+ macedData := origIn[:len(origIn)-len(in)]
+ theirMAC, in, ok7 := getNBytes(in, macPrefixBytes)
+ _, in, ok8 := getData(in)
+ if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 || !ok6 || !ok7 || !ok8 || len(in) > 0 {
+ err = errors.New("otr: corrupt data message")
+ return
+ }
+
+ ignoreErrors := flags&1 != 0
+
+ slot, err := c.calcDataKeys(myKeyId, theirKeyId)
+ if err != nil {
+ if ignoreErrors {
+ err = nil
+ }
+ return
+ }
+
+ mac := hmac.New(sha1.New, slot.recvMACKey)
+ mac.Write([]byte{0, 2, 3})
+ mac.Write(macedData)
+ myMAC := mac.Sum(nil)
+ if len(myMAC) != len(theirMAC) || subtle.ConstantTimeCompare(myMAC, theirMAC) == 0 {
+ if !ignoreErrors {
+ err = errors.New("otr: bad MAC on data message")
+ }
+ return
+ }
+
+ if bytes.Compare(counter, slot.theirLastCtr[:]) <= 0 {
+ err = errors.New("otr: counter regressed")
+ return
+ }
+ copy(slot.theirLastCtr[:], counter)
+
+ var iv [aes.BlockSize]byte
+ copy(iv[:], counter)
+ aesCipher, err := aes.NewCipher(slot.recvAESKey)
+ if err != nil {
+ panic(err.Error())
+ }
+ ctr := cipher.NewCTR(aesCipher, iv[:])
+ ctr.XORKeyStream(encrypted, encrypted)
+ decrypted := encrypted
+
+ if myKeyId == c.myKeyId {
+ c.rotateDHKeys()
+ }
+ if theirKeyId == c.theirKeyId {
+ // evict slots using their retired key id
+ for i := range c.keySlots {
+ slot := &c.keySlots[i]
+ if slot.used && slot.theirKeyId == theirKeyId-1 {
+ slot.used = false
+ c.oldMACs = append(c.oldMACs, slot.recvMACKey...)
+ }
+ }
+
+ c.theirLastDHPub = c.theirCurrentDHPub
+ c.theirKeyId++
+ c.theirCurrentDHPub = y
+ }
+
+ if nulPos := bytes.IndexByte(decrypted, 0); nulPos >= 0 {
+ out = decrypted[:nulPos]
+ tlvData := decrypted[nulPos+1:]
+ for len(tlvData) > 0 {
+ var t tlv
+ var ok1, ok2, ok3 bool
+
+ t.typ, tlvData, ok1 = getU16(tlvData)
+ t.length, tlvData, ok2 = getU16(tlvData)
+ t.data, tlvData, ok3 = getNBytes(tlvData, int(t.length))
+ if !ok1 || !ok2 || !ok3 {
+ err = errors.New("otr: corrupt tlv data")
+ return
+ }
+ tlvs = append(tlvs, t)
+ }
+ } else {
+ out = decrypted
+ }
+
+ return
+}
+
+func (c *Conversation) generateData(msg []byte, extra *tlv) []byte {
+ slot, err := c.calcDataKeys(c.myKeyId-1, c.theirKeyId)
+ if err != nil {
+ panic("otr: failed to generate sending keys: " + err.Error())
+ }
+
+ var plaintext []byte
+ plaintext = append(plaintext, msg...)
+ plaintext = append(plaintext, 0)
+
+ padding := paddingGranularity - ((len(plaintext) + 4) % paddingGranularity)
+ plaintext = appendU16(plaintext, tlvTypePadding)
+ plaintext = appendU16(plaintext, uint16(padding))
+ for i := 0; i < padding; i++ {
+ plaintext = append(plaintext, 0)
+ }
+
+ if extra != nil {
+ plaintext = appendU16(plaintext, extra.typ)
+ plaintext = appendU16(plaintext, uint16(len(extra.data)))
+ plaintext = append(plaintext, extra.data...)
+ }
+
+ encrypted := make([]byte, len(plaintext))
+
+ var iv [aes.BlockSize]byte
+ copy(iv[:], c.myCounter[:])
+ aesCipher, err := aes.NewCipher(slot.sendAESKey)
+ if err != nil {
+ panic(err.Error())
+ }
+ ctr := cipher.NewCTR(aesCipher, iv[:])
+ ctr.XORKeyStream(encrypted, plaintext)
+
+ var ret []byte
+ ret = appendU16(ret, 2)
+ ret = append(ret, msgTypeData)
+ ret = append(ret, 0 /* flags */)
+ ret = appendU32(ret, c.myKeyId-1)
+ ret = appendU32(ret, c.theirKeyId)
+ ret = appendMPI(ret, c.myCurrentDHPub)
+ ret = append(ret, c.myCounter[:]...)
+ ret = appendData(ret, encrypted)
+
+ mac := hmac.New(sha1.New, slot.sendMACKey)
+ mac.Write(ret)
+ ret = append(ret, mac.Sum(nil)[:macPrefixBytes]...)
+ ret = appendData(ret, c.oldMACs)
+ c.oldMACs = nil
+ incCounter(&c.myCounter)
+
+ return ret
+}
+
+func incCounter(counter *[8]byte) {
+ for i := 7; i >= 0; i-- {
+ counter[i]++
+ if counter[i] > 0 {
+ break
+ }
+ }
+}
+
+// calcDataKeys computes the keys used to encrypt a data message given the key
+// IDs.
+func (c *Conversation) calcDataKeys(myKeyId, theirKeyId uint32) (slot *keySlot, err error) {
+ // Check for a cache hit.
+ for i := range c.keySlots {
+ slot = &c.keySlots[i]
+ if slot.used && slot.theirKeyId == theirKeyId && slot.myKeyId == myKeyId {
+ return
+ }
+ }
+
+ // Find an empty slot to write into.
+ slot = nil
+ for i := range c.keySlots {
+ if !c.keySlots[i].used {
+ slot = &c.keySlots[i]
+ break
+ }
+ }
+ if slot == nil {
+ return nil, errors.New("otr: internal error: no more key slots")
+ }
+
+ var myPriv, myPub, theirPub *big.Int
+
+ if myKeyId == c.myKeyId {
+ myPriv = c.myCurrentDHPriv
+ myPub = c.myCurrentDHPub
+ } else if myKeyId == c.myKeyId-1 {
+ myPriv = c.myLastDHPriv
+ myPub = c.myLastDHPub
+ } else {
+ err = errors.New("otr: peer requested keyid " + strconv.FormatUint(uint64(myKeyId), 10) + " when I'm on " + strconv.FormatUint(uint64(c.myKeyId), 10))
+ return
+ }
+
+ if theirKeyId == c.theirKeyId {
+ theirPub = c.theirCurrentDHPub
+ } else if theirKeyId == c.theirKeyId-1 && c.theirLastDHPub != nil {
+ theirPub = c.theirLastDHPub
+ } else {
+ err = errors.New("otr: peer requested keyid " + strconv.FormatUint(uint64(myKeyId), 10) + " when they're on " + strconv.FormatUint(uint64(c.myKeyId), 10))
+ return
+ }
+
+ var sendPrefixByte, recvPrefixByte [1]byte
+
+ if myPub.Cmp(theirPub) > 0 {
+ // we're the high end
+ sendPrefixByte[0], recvPrefixByte[0] = 1, 2
+ } else {
+ // we're the low end
+ sendPrefixByte[0], recvPrefixByte[0] = 2, 1
+ }
+
+ s := new(big.Int).Exp(theirPub, myPriv, p)
+ sBytes := appendMPI(nil, s)
+
+ h := sha1.New()
+ h.Write(sendPrefixByte[:])
+ h.Write(sBytes)
+ slot.sendAESKey = h.Sum(slot.sendAESKey[:0])[:16]
+
+ h.Reset()
+ h.Write(slot.sendAESKey)
+ slot.sendMACKey = h.Sum(slot.sendMACKey[:0])
+
+ h.Reset()
+ h.Write(recvPrefixByte[:])
+ h.Write(sBytes)
+ slot.recvAESKey = h.Sum(slot.recvAESKey[:0])[:16]
+
+ h.Reset()
+ h.Write(slot.recvAESKey)
+ slot.recvMACKey = h.Sum(slot.recvMACKey[:0])
+
+ slot.theirKeyId = theirKeyId
+ slot.myKeyId = myKeyId
+ slot.used = true
+
+ zero(slot.theirLastCtr[:])
+ return
+}
+
+func (c *Conversation) calcAKEKeys(s *big.Int) {
+ mpi := appendMPI(nil, s)
+ h := sha256.New()
+
+ var cBytes [32]byte
+ hashWithPrefix(c.SSID[:], 0, mpi, h)
+
+ hashWithPrefix(cBytes[:], 1, mpi, h)
+ copy(c.revealKeys.c[:], cBytes[:16])
+ copy(c.sigKeys.c[:], cBytes[16:])
+
+ hashWithPrefix(c.revealKeys.m1[:], 2, mpi, h)
+ hashWithPrefix(c.revealKeys.m2[:], 3, mpi, h)
+ hashWithPrefix(c.sigKeys.m1[:], 4, mpi, h)
+ hashWithPrefix(c.sigKeys.m2[:], 5, mpi, h)
+}
+
+func hashWithPrefix(out []byte, prefix byte, in []byte, h hash.Hash) {
+ h.Reset()
+ var p [1]byte
+ p[0] = prefix
+ h.Write(p[:])
+ h.Write(in)
+ if len(out) == h.Size() {
+ h.Sum(out[:0])
+ } else {
+ digest := h.Sum(nil)
+ copy(out, digest)
+ }
+}
+
+func (c *Conversation) encode(msg []byte) [][]byte {
+ b64 := make([]byte, base64.StdEncoding.EncodedLen(len(msg))+len(msgPrefix)+1)
+ base64.StdEncoding.Encode(b64[len(msgPrefix):], msg)
+ copy(b64, msgPrefix)
+ b64[len(b64)-1] = '.'
+
+ if c.FragmentSize < minFragmentSize || len(b64) <= c.FragmentSize {
+ // We can encode this in a single fragment.
+ return [][]byte{b64}
+ }
+
+ // We have to fragment this message.
+ var ret [][]byte
+ bytesPerFragment := c.FragmentSize - minFragmentSize
+ numFragments := (len(b64) + bytesPerFragment) / bytesPerFragment
+
+ for i := 0; i < numFragments; i++ {
+ frag := []byte("?OTR," + strconv.Itoa(i+1) + "," + strconv.Itoa(numFragments) + ",")
+ todo := bytesPerFragment
+ if todo > len(b64) {
+ todo = len(b64)
+ }
+ frag = append(frag, b64[:todo]...)
+ b64 = b64[todo:]
+ frag = append(frag, ',')
+ ret = append(ret, frag)
+ }
+
+ return ret
+}
+
+func (c *Conversation) reset() {
+ c.myKeyId = 0
+
+ for i := range c.keySlots {
+ c.keySlots[i].used = false
+ }
+}
+
+type PublicKey struct {
+ dsa.PublicKey
+}
+
+func (pk *PublicKey) Parse(in []byte) ([]byte, bool) {
+ var ok bool
+ var pubKeyType uint16
+
+ if pubKeyType, in, ok = getU16(in); !ok || pubKeyType != 0 {
+ return nil, false
+ }
+ if pk.P, in, ok = getMPI(in); !ok {
+ return nil, false
+ }
+ if pk.Q, in, ok = getMPI(in); !ok {
+ return nil, false
+ }
+ if pk.G, in, ok = getMPI(in); !ok {
+ return nil, false
+ }
+ if pk.Y, in, ok = getMPI(in); !ok {
+ return nil, false
+ }
+
+ return in, true
+}
+
+func (pk *PublicKey) Serialize(in []byte) []byte {
+ in = appendU16(in, 0)
+ in = appendMPI(in, pk.P)
+ in = appendMPI(in, pk.Q)
+ in = appendMPI(in, pk.G)
+ in = appendMPI(in, pk.Y)
+ return in
+}
+
+// Fingerprint returns the 20-byte, binary fingerprint of the PublicKey.
+func (pk *PublicKey) Fingerprint() []byte {
+ b := pk.Serialize(nil)
+ h := sha1.New()
+ h.Write(b[2:])
+ return h.Sum(nil)
+}
+
+func (pk *PublicKey) Verify(hashed, sig []byte) ([]byte, bool) {
+ if len(sig) != 2*dsaSubgroupBytes {
+ return nil, false
+ }
+ r := new(big.Int).SetBytes(sig[:dsaSubgroupBytes])
+ s := new(big.Int).SetBytes(sig[dsaSubgroupBytes:])
+ ok := dsa.Verify(&pk.PublicKey, hashed, r, s)
+ return sig[dsaSubgroupBytes*2:], ok
+}
+
+type PrivateKey struct {
+ PublicKey
+ dsa.PrivateKey
+}
+
+func (priv *PrivateKey) Sign(rand io.Reader, hashed []byte) []byte {
+ r, s, err := dsa.Sign(rand, &priv.PrivateKey, hashed)
+ if err != nil {
+ panic(err.Error())
+ }
+ rBytes := r.Bytes()
+ sBytes := s.Bytes()
+ if len(rBytes) > dsaSubgroupBytes || len(sBytes) > dsaSubgroupBytes {
+ panic("DSA signature too large")
+ }
+
+ out := make([]byte, 2*dsaSubgroupBytes)
+ copy(out[dsaSubgroupBytes-len(rBytes):], rBytes)
+ copy(out[len(out)-len(sBytes):], sBytes)
+ return out
+}
+
+func (priv *PrivateKey) Serialize(in []byte) []byte {
+ in = priv.PublicKey.Serialize(in)
+ in = appendMPI(in, priv.PrivateKey.X)
+ return in
+}
+
+func (priv *PrivateKey) Parse(in []byte) ([]byte, bool) {
+ in, ok := priv.PublicKey.Parse(in)
+ if !ok {
+ return in, ok
+ }
+ priv.PrivateKey.PublicKey = priv.PublicKey.PublicKey
+ priv.PrivateKey.X, in, ok = getMPI(in)
+ return in, ok
+}
+
+func (priv *PrivateKey) Generate(rand io.Reader) {
+ if err := dsa.GenerateParameters(&priv.PrivateKey.PublicKey.Parameters, rand, dsa.L1024N160); err != nil {
+ panic(err.Error())
+ }
+ if err := dsa.GenerateKey(&priv.PrivateKey, rand); err != nil {
+ panic(err.Error())
+ }
+ priv.PublicKey.PublicKey = priv.PrivateKey.PublicKey
+}
+
+func notHex(r rune) bool {
+ if r >= '0' && r <= '9' ||
+ r >= 'a' && r <= 'f' ||
+ r >= 'A' && r <= 'F' {
+ return false
+ }
+
+ return true
+}
+
+// Import parses the contents of a libotr private key file.
+func (priv *PrivateKey) Import(in []byte) bool {
+ mpiStart := []byte(" #")
+
+ mpis := make([]*big.Int, 5)
+
+ for i := 0; i < len(mpis); i++ {
+ start := bytes.Index(in, mpiStart)
+ if start == -1 {
+ return false
+ }
+ in = in[start+len(mpiStart):]
+ end := bytes.IndexFunc(in, notHex)
+ if end == -1 {
+ return false
+ }
+ hexBytes := in[:end]
+ in = in[end:]
+
+ if len(hexBytes)&1 != 0 {
+ return false
+ }
+
+ mpiBytes := make([]byte, len(hexBytes)/2)
+ if _, err := hex.Decode(mpiBytes, hexBytes); err != nil {
+ return false
+ }
+
+ mpis[i] = new(big.Int).SetBytes(mpiBytes)
+ }
+
+ for _, mpi := range mpis {
+ if mpi.Sign() <= 0 {
+ return false
+ }
+ }
+
+ priv.PrivateKey.P = mpis[0]
+ priv.PrivateKey.Q = mpis[1]
+ priv.PrivateKey.G = mpis[2]
+ priv.PrivateKey.Y = mpis[3]
+ priv.PrivateKey.X = mpis[4]
+ priv.PublicKey.PublicKey = priv.PrivateKey.PublicKey
+
+ a := new(big.Int).Exp(priv.PrivateKey.G, priv.PrivateKey.X, priv.PrivateKey.P)
+ return a.Cmp(priv.PrivateKey.Y) == 0
+}
+
+func getU8(in []byte) (uint8, []byte, bool) {
+ if len(in) < 1 {
+ return 0, in, false
+ }
+ return in[0], in[1:], true
+}
+
+func getU16(in []byte) (uint16, []byte, bool) {
+ if len(in) < 2 {
+ return 0, in, false
+ }
+ r := uint16(in[0])<<8 | uint16(in[1])
+ return r, in[2:], true
+}
+
+func getU32(in []byte) (uint32, []byte, bool) {
+ if len(in) < 4 {
+ return 0, in, false
+ }
+ r := uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3])
+ return r, in[4:], true
+}
+
+func getMPI(in []byte) (*big.Int, []byte, bool) {
+ l, in, ok := getU32(in)
+ if !ok || uint32(len(in)) < l {
+ return nil, in, false
+ }
+ r := new(big.Int).SetBytes(in[:l])
+ return r, in[l:], true
+}
+
+func getData(in []byte) ([]byte, []byte, bool) {
+ l, in, ok := getU32(in)
+ if !ok || uint32(len(in)) < l {
+ return nil, in, false
+ }
+ return in[:l], in[l:], true
+}
+
+func getNBytes(in []byte, n int) ([]byte, []byte, bool) {
+ if len(in) < n {
+ return nil, in, false
+ }
+ return in[:n], in[n:], true
+}
+
+func appendU16(out []byte, v uint16) []byte {
+ out = append(out, byte(v>>8), byte(v))
+ return out
+}
+
+func appendU32(out []byte, v uint32) []byte {
+ out = append(out, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
+ return out
+}
+
+func appendData(out, v []byte) []byte {
+ out = appendU32(out, uint32(len(v)))
+ out = append(out, v...)
+ return out
+}
+
+func appendMPI(out []byte, v *big.Int) []byte {
+ vBytes := v.Bytes()
+ out = appendU32(out, uint32(len(vBytes)))
+ out = append(out, vBytes...)
+ return out
+}
+
+func appendMPIs(out []byte, mpis ...*big.Int) []byte {
+ for _, mpi := range mpis {
+ out = appendMPI(out, mpi)
+ }
+ return out
+}
+
+func zero(b []byte) {
+ for i := range b {
+ b[i] = 0
+ }
+}
diff --git a/local_crypto_patch/contents/otr/otr_test.go b/local_crypto_patch/contents/otr/otr_test.go
new file mode 100644
index 0000000000..3b7aa1986e
--- /dev/null
+++ b/local_crypto_patch/contents/otr/otr_test.go
@@ -0,0 +1,470 @@
+// 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 otr
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/rand"
+ "encoding/hex"
+ "math/big"
+ "os"
+ "os/exec"
+ "testing"
+)
+
+var isQueryTests = []struct {
+ msg string
+ expectedVersion int
+}{
+ {"foo", 0},
+ {"?OtR", 0},
+ {"?OtR?", 0},
+ {"?OTR?", 0},
+ {"?OTRv?", 0},
+ {"?OTRv1?", 0},
+ {"?OTR?v1?", 0},
+ {"?OTR?v?", 0},
+ {"?OTR?v2?", 2},
+ {"?OTRv2?", 2},
+ {"?OTRv23?", 2},
+ {"?OTRv23 ?", 0},
+}
+
+func TestIsQuery(t *testing.T) {
+ for i, test := range isQueryTests {
+ version := isQuery([]byte(test.msg))
+ if version != test.expectedVersion {
+ t.Errorf("#%d: got %d, want %d", i, version, test.expectedVersion)
+ }
+ }
+}
+
+var alicePrivateKeyHex = "000000000080c81c2cb2eb729b7e6fd48e975a932c638b3a9055478583afa46755683e30102447f6da2d8bec9f386bbb5da6403b0040fee8650b6ab2d7f32c55ab017ae9b6aec8c324ab5844784e9a80e194830d548fb7f09a0410df2c4d5c8bc2b3e9ad484e65412be689cf0834694e0839fb2954021521ffdffb8f5c32c14dbf2020b3ce7500000014da4591d58def96de61aea7b04a8405fe1609308d000000808ddd5cb0b9d66956e3dea5a915d9aba9d8a6e7053b74dadb2fc52f9fe4e5bcc487d2305485ed95fed026ad93f06ebb8c9e8baf693b7887132c7ffdd3b0f72f4002ff4ed56583ca7c54458f8c068ca3e8a4dfa309d1dd5d34e2a4b68e6f4338835e5e0fb4317c9e4c7e4806dafda3ef459cd563775a586dd91b1319f72621bf3f00000080b8147e74d8c45e6318c37731b8b33b984a795b3653c2cd1d65cc99efe097cb7eb2fa49569bab5aab6e8a1c261a27d0f7840a5e80b317e6683042b59b6dceca2879c6ffc877a465be690c15e4a42f9a7588e79b10faac11b1ce3741fcef7aba8ce05327a2c16d279ee1b3d77eb783fb10e3356caa25635331e26dd42b8396c4d00000001420bec691fea37ecea58a5c717142f0b804452f57"
+
+var aliceFingerprintHex = "0bb01c360424522e94ee9c346ce877a1a4288b2f"
+
+var bobPrivateKeyHex = "000000000080a5138eb3d3eb9c1d85716faecadb718f87d31aaed1157671d7fee7e488f95e8e0ba60ad449ec732710a7dec5190f7182af2e2f98312d98497221dff160fd68033dd4f3a33b7c078d0d9f66e26847e76ca7447d4bab35486045090572863d9e4454777f24d6706f63e02548dfec2d0a620af37bbc1d24f884708a212c343b480d00000014e9c58f0ea21a5e4dfd9f44b6a9f7f6a9961a8fa9000000803c4d111aebd62d3c50c2889d420a32cdf1e98b70affcc1fcf44d59cca2eb019f6b774ef88153fb9b9615441a5fe25ea2d11b74ce922ca0232bd81b3c0fcac2a95b20cb6e6c0c5c1ace2e26f65dc43c751af0edbb10d669890e8ab6beea91410b8b2187af1a8347627a06ecea7e0f772c28aae9461301e83884860c9b656c722f0000008065af8625a555ea0e008cd04743671a3cda21162e83af045725db2eb2bb52712708dc0cc1a84c08b3649b88a966974bde27d8612c2861792ec9f08786a246fcadd6d8d3a81a32287745f309238f47618c2bd7612cb8b02d940571e0f30b96420bcd462ff542901b46109b1e5ad6423744448d20a57818a8cbb1647d0fea3b664e0000001440f9f2eb554cb00d45a5826b54bfa419b6980e48"
+
+func TestKeySerialization(t *testing.T) {
+ var priv PrivateKey
+ alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
+ rest, ok := priv.Parse(alicePrivateKey)
+ if !ok {
+ t.Error("failed to parse private key")
+ }
+ if len(rest) > 0 {
+ t.Error("data remaining after parsing private key")
+ }
+
+ out := priv.Serialize(nil)
+ if !bytes.Equal(alicePrivateKey, out) {
+ t.Errorf("serialization (%x) is not equal to original (%x)", out, alicePrivateKey)
+ }
+
+ aliceFingerprint, _ := hex.DecodeString(aliceFingerprintHex)
+ fingerprint := priv.PublicKey.Fingerprint()
+ if !bytes.Equal(aliceFingerprint, fingerprint) {
+ t.Errorf("fingerprint (%x) is not equal to expected value (%x)", fingerprint, aliceFingerprint)
+ }
+}
+
+const libOTRPrivateKey = `(privkeys
+ (account
+(name "foo@example.com")
+(protocol prpl-jabber)
+(private-key
+ (dsa
+ (p #00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB8C031D3561FECEE72EBB4A090D450A9B7A857#)
+ (q #00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#)
+ (g #535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57597766A2F9CE3857D7ACE3E1E3BC1FC6F26#)
+ (y #0AC8670AD767D7A8D9D14CC1AC6744CD7D76F993B77FFD9E39DF01E5A6536EF65E775FCEF2A983E2A19BD6415500F6979715D9FD1257E1FE2B6F5E1E74B333079E7C880D39868462A93454B41877BE62E5EF0A041C2EE9C9E76BD1E12AE25D9628DECB097025DD625EF49C3258A1A3C0FF501E3DC673B76D7BABF349009B6ECF#)
+ (x #14D0345A3562C480A039E3C72764F72D79043216#)
+ )
+ )
+ )
+)`
+
+func TestParseLibOTRPrivateKey(t *testing.T) {
+ var priv PrivateKey
+
+ if !priv.Import([]byte(libOTRPrivateKey)) {
+ t.Fatalf("Failed to import sample private key")
+ }
+}
+
+func TestSignVerify(t *testing.T) {
+ var priv PrivateKey
+ alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
+ _, ok := priv.Parse(alicePrivateKey)
+ if !ok {
+ t.Error("failed to parse private key")
+ }
+
+ var msg [32]byte
+ rand.Reader.Read(msg[:])
+
+ sig := priv.Sign(rand.Reader, msg[:])
+ rest, ok := priv.PublicKey.Verify(msg[:], sig)
+ if !ok {
+ t.Errorf("signature (%x) of %x failed to verify", sig, msg[:])
+ } else if len(rest) > 0 {
+ t.Error("signature data remains after verification")
+ }
+
+ sig[10] ^= 80
+ _, ok = priv.PublicKey.Verify(msg[:], sig)
+ if ok {
+ t.Errorf("corrupted signature (%x) of %x verified", sig, msg[:])
+ }
+}
+
+func setupConversation(t *testing.T) (alice, bob *Conversation) {
+ alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
+ bobPrivateKey, _ := hex.DecodeString(bobPrivateKeyHex)
+
+ alice, bob = new(Conversation), new(Conversation)
+
+ alice.PrivateKey = new(PrivateKey)
+ bob.PrivateKey = new(PrivateKey)
+ alice.PrivateKey.Parse(alicePrivateKey)
+ bob.PrivateKey.Parse(bobPrivateKey)
+ alice.FragmentSize = 100
+ bob.FragmentSize = 100
+
+ if alice.IsEncrypted() {
+ t.Error("Alice believes that the conversation is secure before we've started")
+ }
+ if bob.IsEncrypted() {
+ t.Error("Bob believes that the conversation is secure before we've started")
+ }
+
+ performHandshake(t, alice, bob)
+ return alice, bob
+}
+
+func performHandshake(t *testing.T, alice, bob *Conversation) {
+ var alicesMessage, bobsMessage [][]byte
+ var out []byte
+ var aliceChange, bobChange SecurityChange
+ var err error
+ alicesMessage = append(alicesMessage, []byte(QueryMessage))
+
+ for round := 0; len(alicesMessage) > 0 || len(bobsMessage) > 0; round++ {
+ bobsMessage = nil
+ for i, msg := range alicesMessage {
+ out, _, bobChange, bobsMessage, err = bob.Receive(msg)
+ if len(out) > 0 {
+ t.Errorf("Bob generated output during key exchange, round %d, message %d", round, i)
+ }
+ if err != nil {
+ t.Fatalf("Bob returned an error, round %d, message %d (%x): %s", round, i, msg, err)
+ }
+ if len(bobsMessage) > 0 && i != len(alicesMessage)-1 {
+ t.Errorf("Bob produced output while processing a fragment, round %d, message %d", round, i)
+ }
+ }
+
+ alicesMessage = nil
+ for i, msg := range bobsMessage {
+ out, _, aliceChange, alicesMessage, err = alice.Receive(msg)
+ if len(out) > 0 {
+ t.Errorf("Alice generated output during key exchange, round %d, message %d", round, i)
+ }
+ if err != nil {
+ t.Fatalf("Alice returned an error, round %d, message %d (%x): %s", round, i, msg, err)
+ }
+ if len(alicesMessage) > 0 && i != len(bobsMessage)-1 {
+ t.Errorf("Alice produced output while processing a fragment, round %d, message %d", round, i)
+ }
+ }
+ }
+
+ if aliceChange != NewKeys {
+ t.Errorf("Alice terminated without signaling new keys")
+ }
+ if bobChange != NewKeys {
+ t.Errorf("Bob terminated without signaling new keys")
+ }
+
+ if !bytes.Equal(alice.SSID[:], bob.SSID[:]) {
+ t.Errorf("Session identifiers don't match. Alice has %x, Bob has %x", alice.SSID[:], bob.SSID[:])
+ }
+
+ if !alice.IsEncrypted() {
+ t.Error("Alice doesn't believe that the conversation is secure")
+ }
+ if !bob.IsEncrypted() {
+ t.Error("Bob doesn't believe that the conversation is secure")
+ }
+}
+
+const (
+ firstRoundTrip = iota
+ subsequentRoundTrip
+ noMACKeyCheck
+)
+
+func roundTrip(t *testing.T, alice, bob *Conversation, message []byte, macKeyCheck int) {
+ alicesMessage, err := alice.Send(message)
+ if err != nil {
+ t.Errorf("Error from Alice sending message: %s", err)
+ }
+
+ if len(alice.oldMACs) != 0 {
+ t.Errorf("Alice has not revealed all MAC keys")
+ }
+
+ for i, msg := range alicesMessage {
+ out, encrypted, _, _, err := bob.Receive(msg)
+
+ if err != nil {
+ t.Errorf("Error generated while processing test message: %s", err.Error())
+ }
+ if len(out) > 0 {
+ if i != len(alicesMessage)-1 {
+ t.Fatal("Bob produced a message while processing a fragment of Alice's")
+ }
+ if !encrypted {
+ t.Errorf("Message was not marked as encrypted")
+ }
+ if !bytes.Equal(out, message) {
+ t.Errorf("Message corrupted: got %x, want %x", out, message)
+ }
+ }
+ }
+
+ switch macKeyCheck {
+ case firstRoundTrip:
+ if len(bob.oldMACs) != 0 {
+ t.Errorf("Bob should not have MAC keys to reveal")
+ }
+ case subsequentRoundTrip:
+ if len(bob.oldMACs) != 40 {
+ t.Errorf("Bob has %d bytes of MAC keys to reveal, but should have 40", len(bob.oldMACs))
+ }
+ }
+
+ bobsMessage, err := bob.Send(message)
+ if err != nil {
+ t.Errorf("Error from Bob sending message: %s", err)
+ }
+
+ if len(bob.oldMACs) != 0 {
+ t.Errorf("Bob has not revealed all MAC keys")
+ }
+
+ for i, msg := range bobsMessage {
+ out, encrypted, _, _, err := alice.Receive(msg)
+
+ if err != nil {
+ t.Errorf("Error generated while processing test message: %s", err.Error())
+ }
+ if len(out) > 0 {
+ if i != len(bobsMessage)-1 {
+ t.Fatal("Alice produced a message while processing a fragment of Bob's")
+ }
+ if !encrypted {
+ t.Errorf("Message was not marked as encrypted")
+ }
+ if !bytes.Equal(out, message) {
+ t.Errorf("Message corrupted: got %x, want %x", out, message)
+ }
+ }
+ }
+
+ switch macKeyCheck {
+ case firstRoundTrip:
+ if len(alice.oldMACs) != 20 {
+ t.Errorf("Alice has %d bytes of MAC keys to reveal, but should have 20", len(alice.oldMACs))
+ }
+ case subsequentRoundTrip:
+ if len(alice.oldMACs) != 40 {
+ t.Errorf("Alice has %d bytes of MAC keys to reveal, but should have 40", len(alice.oldMACs))
+ }
+ }
+}
+
+func TestConversation(t *testing.T) {
+ alice, bob := setupConversation(t)
+
+ var testMessages = [][]byte{
+ []byte("hello"), []byte("bye"),
+ }
+
+ roundTripType := firstRoundTrip
+
+ for _, testMessage := range testMessages {
+ roundTrip(t, alice, bob, testMessage, roundTripType)
+ roundTripType = subsequentRoundTrip
+ }
+}
+
+func TestGoodSMP(t *testing.T) {
+ var alice, bob Conversation
+
+ alice.smp.secret = new(big.Int).SetInt64(42)
+ bob.smp.secret = alice.smp.secret
+
+ var alicesMessages, bobsMessages []tlv
+ var aliceComplete, bobComplete bool
+ var err error
+ var out tlv
+
+ alicesMessages = alice.startSMP("")
+ for round := 0; len(alicesMessages) > 0 || len(bobsMessages) > 0; round++ {
+ bobsMessages = bobsMessages[:0]
+ for i, msg := range alicesMessages {
+ out, bobComplete, err = bob.processSMP(msg)
+ if err != nil {
+ t.Errorf("Error from Bob in round %d: %s", round, err)
+ }
+ if bobComplete && i != len(alicesMessages)-1 {
+ t.Errorf("Bob returned a completed signal before processing all of Alice's messages in round %d", round)
+ }
+ if out.typ != 0 {
+ bobsMessages = append(bobsMessages, out)
+ }
+ }
+
+ alicesMessages = alicesMessages[:0]
+ for i, msg := range bobsMessages {
+ out, aliceComplete, err = alice.processSMP(msg)
+ if err != nil {
+ t.Errorf("Error from Alice in round %d: %s", round, err)
+ }
+ if aliceComplete && i != len(bobsMessages)-1 {
+ t.Errorf("Alice returned a completed signal before processing all of Bob's messages in round %d", round)
+ }
+ if out.typ != 0 {
+ alicesMessages = append(alicesMessages, out)
+ }
+ }
+ }
+
+ if !aliceComplete || !bobComplete {
+ t.Errorf("SMP completed without both sides reporting success: alice: %v, bob: %v\n", aliceComplete, bobComplete)
+ }
+}
+
+func TestBadSMP(t *testing.T) {
+ var alice, bob Conversation
+
+ alice.smp.secret = new(big.Int).SetInt64(42)
+ bob.smp.secret = new(big.Int).SetInt64(43)
+
+ var alicesMessages, bobsMessages []tlv
+
+ alicesMessages = alice.startSMP("")
+ for round := 0; len(alicesMessages) > 0 || len(bobsMessages) > 0; round++ {
+ bobsMessages = bobsMessages[:0]
+ for _, msg := range alicesMessages {
+ out, complete, _ := bob.processSMP(msg)
+ if complete {
+ t.Errorf("Bob signaled completion in round %d", round)
+ }
+ if out.typ != 0 {
+ bobsMessages = append(bobsMessages, out)
+ }
+ }
+
+ alicesMessages = alicesMessages[:0]
+ for _, msg := range bobsMessages {
+ out, complete, _ := alice.processSMP(msg)
+ if complete {
+ t.Errorf("Alice signaled completion in round %d", round)
+ }
+ if out.typ != 0 {
+ alicesMessages = append(alicesMessages, out)
+ }
+ }
+ }
+}
+
+func TestRehandshaking(t *testing.T) {
+ alice, bob := setupConversation(t)
+ roundTrip(t, alice, bob, []byte("test"), firstRoundTrip)
+ roundTrip(t, alice, bob, []byte("test 2"), subsequentRoundTrip)
+ roundTrip(t, alice, bob, []byte("test 3"), subsequentRoundTrip)
+ roundTrip(t, alice, bob, []byte("test 4"), subsequentRoundTrip)
+ roundTrip(t, alice, bob, []byte("test 5"), subsequentRoundTrip)
+ roundTrip(t, alice, bob, []byte("test 6"), subsequentRoundTrip)
+ roundTrip(t, alice, bob, []byte("test 7"), subsequentRoundTrip)
+ roundTrip(t, alice, bob, []byte("test 8"), subsequentRoundTrip)
+ performHandshake(t, alice, bob)
+ roundTrip(t, alice, bob, []byte("test"), noMACKeyCheck)
+ roundTrip(t, alice, bob, []byte("test 2"), noMACKeyCheck)
+}
+
+func TestAgainstLibOTR(t *testing.T) {
+ // This test requires otr.c.test to be built as /tmp/a.out.
+ // If enabled, this tests runs forever performing OTR handshakes in a
+ // loop.
+ t.Skip("This test requires otr.c.test to be built as /tmp/a.out")
+
+ alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
+ var alice Conversation
+ alice.PrivateKey = new(PrivateKey)
+ alice.PrivateKey.Parse(alicePrivateKey)
+
+ cmd := exec.Command("/tmp/a.out")
+ cmd.Stderr = os.Stderr
+
+ out, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer out.Close()
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ in := bufio.NewReader(stdout)
+
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ out.Write([]byte(QueryMessage))
+ out.Write([]byte("\n"))
+ var expectedText = []byte("test message")
+
+ for {
+ line, isPrefix, err := in.ReadLine()
+ if isPrefix {
+ t.Fatal("line from subprocess too long")
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ text, encrypted, change, alicesMessage, err := alice.Receive(line)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, msg := range alicesMessage {
+ out.Write(msg)
+ out.Write([]byte("\n"))
+ }
+ if change == NewKeys {
+ alicesMessage, err := alice.Send([]byte("Go -> libotr test message"))
+ if err != nil {
+ t.Fatalf("error sending message: %s", err.Error())
+ } else {
+ for _, msg := range alicesMessage {
+ out.Write(msg)
+ out.Write([]byte("\n"))
+ }
+ }
+ }
+ if len(text) > 0 {
+ if !bytes.Equal(text, expectedText) {
+ t.Fatalf("expected %x, but got %x", expectedText, text)
+ }
+ if !encrypted {
+ t.Fatal("message wasn't encrypted")
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/otr/smp.go b/local_crypto_patch/contents/otr/smp.go
new file mode 100644
index 0000000000..dc6de4ee0e
--- /dev/null
+++ b/local_crypto_patch/contents/otr/smp.go
@@ -0,0 +1,572 @@
+// 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.
+
+// This file implements the Socialist Millionaires Protocol as described in
+// http://www.cypherpunks.ca/otr/Protocol-v2-3.1.0.html. The protocol
+// specification is required in order to understand this code and, where
+// possible, the variable names in the code match up with the spec.
+
+package otr
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "errors"
+ "hash"
+ "math/big"
+)
+
+type smpFailure string
+
+func (s smpFailure) Error() string {
+ return string(s)
+}
+
+var smpFailureError = smpFailure("otr: SMP protocol failed")
+var smpSecretMissingError = smpFailure("otr: mutual secret needed")
+
+const smpVersion = 1
+
+const (
+ smpState1 = iota
+ smpState2
+ smpState3
+ smpState4
+)
+
+type smpState struct {
+ state int
+ a2, a3, b2, b3, pb, qb *big.Int
+ g2a, g3a *big.Int
+ g2, g3 *big.Int
+ g3b, papb, qaqb, ra *big.Int
+ saved *tlv
+ secret *big.Int
+ question string
+}
+
+func (c *Conversation) startSMP(question string) (tlvs []tlv) {
+ if c.smp.state != smpState1 {
+ tlvs = append(tlvs, c.generateSMPAbort())
+ }
+ tlvs = append(tlvs, c.generateSMP1(question))
+ c.smp.question = ""
+ c.smp.state = smpState2
+ return
+}
+
+func (c *Conversation) resetSMP() {
+ c.smp.state = smpState1
+ c.smp.secret = nil
+ c.smp.question = ""
+}
+
+func (c *Conversation) processSMP(in tlv) (out tlv, complete bool, err error) {
+ data := in.data
+
+ switch in.typ {
+ case tlvTypeSMPAbort:
+ if c.smp.state != smpState1 {
+ err = smpFailureError
+ }
+ c.resetSMP()
+ return
+ case tlvTypeSMP1WithQuestion:
+ // We preprocess this into a SMP1 message.
+ nulPos := bytes.IndexByte(data, 0)
+ if nulPos == -1 {
+ err = errors.New("otr: SMP message with question didn't contain a NUL byte")
+ return
+ }
+ c.smp.question = string(data[:nulPos])
+ data = data[nulPos+1:]
+ }
+
+ numMPIs, data, ok := getU32(data)
+ if !ok || numMPIs > 20 {
+ err = errors.New("otr: corrupt SMP message")
+ return
+ }
+
+ mpis := make([]*big.Int, numMPIs)
+ for i := range mpis {
+ var ok bool
+ mpis[i], data, ok = getMPI(data)
+ if !ok {
+ err = errors.New("otr: corrupt SMP message")
+ return
+ }
+ }
+
+ switch in.typ {
+ case tlvTypeSMP1, tlvTypeSMP1WithQuestion:
+ if c.smp.state != smpState1 {
+ c.resetSMP()
+ out = c.generateSMPAbort()
+ return
+ }
+ if c.smp.secret == nil {
+ err = smpSecretMissingError
+ return
+ }
+ if err = c.processSMP1(mpis); err != nil {
+ return
+ }
+ c.smp.state = smpState3
+ out = c.generateSMP2()
+ case tlvTypeSMP2:
+ if c.smp.state != smpState2 {
+ c.resetSMP()
+ out = c.generateSMPAbort()
+ return
+ }
+ if out, err = c.processSMP2(mpis); err != nil {
+ out = c.generateSMPAbort()
+ return
+ }
+ c.smp.state = smpState4
+ case tlvTypeSMP3:
+ if c.smp.state != smpState3 {
+ c.resetSMP()
+ out = c.generateSMPAbort()
+ return
+ }
+ if out, err = c.processSMP3(mpis); err != nil {
+ return
+ }
+ c.smp.state = smpState1
+ c.smp.secret = nil
+ complete = true
+ case tlvTypeSMP4:
+ if c.smp.state != smpState4 {
+ c.resetSMP()
+ out = c.generateSMPAbort()
+ return
+ }
+ if err = c.processSMP4(mpis); err != nil {
+ out = c.generateSMPAbort()
+ return
+ }
+ c.smp.state = smpState1
+ c.smp.secret = nil
+ complete = true
+ default:
+ panic("unknown SMP message")
+ }
+
+ return
+}
+
+func (c *Conversation) calcSMPSecret(mutualSecret []byte, weStarted bool) {
+ h := sha256.New()
+ h.Write([]byte{smpVersion})
+ if weStarted {
+ h.Write(c.PrivateKey.PublicKey.Fingerprint())
+ h.Write(c.TheirPublicKey.Fingerprint())
+ } else {
+ h.Write(c.TheirPublicKey.Fingerprint())
+ h.Write(c.PrivateKey.PublicKey.Fingerprint())
+ }
+ h.Write(c.SSID[:])
+ h.Write(mutualSecret)
+ c.smp.secret = new(big.Int).SetBytes(h.Sum(nil))
+}
+
+func (c *Conversation) generateSMP1(question string) tlv {
+ var randBuf [16]byte
+ c.smp.a2 = c.randMPI(randBuf[:])
+ c.smp.a3 = c.randMPI(randBuf[:])
+ g2a := new(big.Int).Exp(g, c.smp.a2, p)
+ g3a := new(big.Int).Exp(g, c.smp.a3, p)
+ h := sha256.New()
+
+ r2 := c.randMPI(randBuf[:])
+ r := new(big.Int).Exp(g, r2, p)
+ c2 := new(big.Int).SetBytes(hashMPIs(h, 1, r))
+ d2 := new(big.Int).Mul(c.smp.a2, c2)
+ d2.Sub(r2, d2)
+ d2.Mod(d2, q)
+ if d2.Sign() < 0 {
+ d2.Add(d2, q)
+ }
+
+ r3 := c.randMPI(randBuf[:])
+ r.Exp(g, r3, p)
+ c3 := new(big.Int).SetBytes(hashMPIs(h, 2, r))
+ d3 := new(big.Int).Mul(c.smp.a3, c3)
+ d3.Sub(r3, d3)
+ d3.Mod(d3, q)
+ if d3.Sign() < 0 {
+ d3.Add(d3, q)
+ }
+
+ var ret tlv
+ if len(question) > 0 {
+ ret.typ = tlvTypeSMP1WithQuestion
+ ret.data = append(ret.data, question...)
+ ret.data = append(ret.data, 0)
+ } else {
+ ret.typ = tlvTypeSMP1
+ }
+ ret.data = appendU32(ret.data, 6)
+ ret.data = appendMPIs(ret.data, g2a, c2, d2, g3a, c3, d3)
+ return ret
+}
+
+func (c *Conversation) processSMP1(mpis []*big.Int) error {
+ if len(mpis) != 6 {
+ return errors.New("otr: incorrect number of arguments in SMP1 message")
+ }
+ g2a := mpis[0]
+ c2 := mpis[1]
+ d2 := mpis[2]
+ g3a := mpis[3]
+ c3 := mpis[4]
+ d3 := mpis[5]
+ h := sha256.New()
+
+ r := new(big.Int).Exp(g, d2, p)
+ s := new(big.Int).Exp(g2a, c2, p)
+ r.Mul(r, s)
+ r.Mod(r, p)
+ t := new(big.Int).SetBytes(hashMPIs(h, 1, r))
+ if c2.Cmp(t) != 0 {
+ return errors.New("otr: ZKP c2 incorrect in SMP1 message")
+ }
+ r.Exp(g, d3, p)
+ s.Exp(g3a, c3, p)
+ r.Mul(r, s)
+ r.Mod(r, p)
+ t.SetBytes(hashMPIs(h, 2, r))
+ if c3.Cmp(t) != 0 {
+ return errors.New("otr: ZKP c3 incorrect in SMP1 message")
+ }
+
+ c.smp.g2a = g2a
+ c.smp.g3a = g3a
+ return nil
+}
+
+func (c *Conversation) generateSMP2() tlv {
+ var randBuf [16]byte
+ b2 := c.randMPI(randBuf[:])
+ c.smp.b3 = c.randMPI(randBuf[:])
+ r2 := c.randMPI(randBuf[:])
+ r3 := c.randMPI(randBuf[:])
+ r4 := c.randMPI(randBuf[:])
+ r5 := c.randMPI(randBuf[:])
+ r6 := c.randMPI(randBuf[:])
+
+ g2b := new(big.Int).Exp(g, b2, p)
+ g3b := new(big.Int).Exp(g, c.smp.b3, p)
+
+ r := new(big.Int).Exp(g, r2, p)
+ h := sha256.New()
+ c2 := new(big.Int).SetBytes(hashMPIs(h, 3, r))
+ d2 := new(big.Int).Mul(b2, c2)
+ d2.Sub(r2, d2)
+ d2.Mod(d2, q)
+ if d2.Sign() < 0 {
+ d2.Add(d2, q)
+ }
+
+ r.Exp(g, r3, p)
+ c3 := new(big.Int).SetBytes(hashMPIs(h, 4, r))
+ d3 := new(big.Int).Mul(c.smp.b3, c3)
+ d3.Sub(r3, d3)
+ d3.Mod(d3, q)
+ if d3.Sign() < 0 {
+ d3.Add(d3, q)
+ }
+
+ c.smp.g2 = new(big.Int).Exp(c.smp.g2a, b2, p)
+ c.smp.g3 = new(big.Int).Exp(c.smp.g3a, c.smp.b3, p)
+ c.smp.pb = new(big.Int).Exp(c.smp.g3, r4, p)
+ c.smp.qb = new(big.Int).Exp(g, r4, p)
+ r.Exp(c.smp.g2, c.smp.secret, p)
+ c.smp.qb.Mul(c.smp.qb, r)
+ c.smp.qb.Mod(c.smp.qb, p)
+
+ s := new(big.Int)
+ s.Exp(c.smp.g2, r6, p)
+ r.Exp(g, r5, p)
+ s.Mul(r, s)
+ s.Mod(s, p)
+ r.Exp(c.smp.g3, r5, p)
+ cp := new(big.Int).SetBytes(hashMPIs(h, 5, r, s))
+
+ // D5 = r5 - r4 cP mod q and D6 = r6 - y cP mod q
+
+ s.Mul(r4, cp)
+ r.Sub(r5, s)
+ d5 := new(big.Int).Mod(r, q)
+ if d5.Sign() < 0 {
+ d5.Add(d5, q)
+ }
+
+ s.Mul(c.smp.secret, cp)
+ r.Sub(r6, s)
+ d6 := new(big.Int).Mod(r, q)
+ if d6.Sign() < 0 {
+ d6.Add(d6, q)
+ }
+
+ var ret tlv
+ ret.typ = tlvTypeSMP2
+ ret.data = appendU32(ret.data, 11)
+ ret.data = appendMPIs(ret.data, g2b, c2, d2, g3b, c3, d3, c.smp.pb, c.smp.qb, cp, d5, d6)
+ return ret
+}
+
+func (c *Conversation) processSMP2(mpis []*big.Int) (out tlv, err error) {
+ if len(mpis) != 11 {
+ err = errors.New("otr: incorrect number of arguments in SMP2 message")
+ return
+ }
+ g2b := mpis[0]
+ c2 := mpis[1]
+ d2 := mpis[2]
+ g3b := mpis[3]
+ c3 := mpis[4]
+ d3 := mpis[5]
+ pb := mpis[6]
+ qb := mpis[7]
+ cp := mpis[8]
+ d5 := mpis[9]
+ d6 := mpis[10]
+ h := sha256.New()
+
+ r := new(big.Int).Exp(g, d2, p)
+ s := new(big.Int).Exp(g2b, c2, p)
+ r.Mul(r, s)
+ r.Mod(r, p)
+ s.SetBytes(hashMPIs(h, 3, r))
+ if c2.Cmp(s) != 0 {
+ err = errors.New("otr: ZKP c2 failed in SMP2 message")
+ return
+ }
+
+ r.Exp(g, d3, p)
+ s.Exp(g3b, c3, p)
+ r.Mul(r, s)
+ r.Mod(r, p)
+ s.SetBytes(hashMPIs(h, 4, r))
+ if c3.Cmp(s) != 0 {
+ err = errors.New("otr: ZKP c3 failed in SMP2 message")
+ return
+ }
+
+ c.smp.g2 = new(big.Int).Exp(g2b, c.smp.a2, p)
+ c.smp.g3 = new(big.Int).Exp(g3b, c.smp.a3, p)
+
+ r.Exp(g, d5, p)
+ s.Exp(c.smp.g2, d6, p)
+ r.Mul(r, s)
+ s.Exp(qb, cp, p)
+ r.Mul(r, s)
+ r.Mod(r, p)
+
+ s.Exp(c.smp.g3, d5, p)
+ t := new(big.Int).Exp(pb, cp, p)
+ s.Mul(s, t)
+ s.Mod(s, p)
+ t.SetBytes(hashMPIs(h, 5, s, r))
+ if cp.Cmp(t) != 0 {
+ err = errors.New("otr: ZKP cP failed in SMP2 message")
+ return
+ }
+
+ var randBuf [16]byte
+ r4 := c.randMPI(randBuf[:])
+ r5 := c.randMPI(randBuf[:])
+ r6 := c.randMPI(randBuf[:])
+ r7 := c.randMPI(randBuf[:])
+
+ pa := new(big.Int).Exp(c.smp.g3, r4, p)
+ r.Exp(c.smp.g2, c.smp.secret, p)
+ qa := new(big.Int).Exp(g, r4, p)
+ qa.Mul(qa, r)
+ qa.Mod(qa, p)
+
+ r.Exp(g, r5, p)
+ s.Exp(c.smp.g2, r6, p)
+ r.Mul(r, s)
+ r.Mod(r, p)
+
+ s.Exp(c.smp.g3, r5, p)
+ cp.SetBytes(hashMPIs(h, 6, s, r))
+
+ r.Mul(r4, cp)
+ d5 = new(big.Int).Sub(r5, r)
+ d5.Mod(d5, q)
+ if d5.Sign() < 0 {
+ d5.Add(d5, q)
+ }
+
+ r.Mul(c.smp.secret, cp)
+ d6 = new(big.Int).Sub(r6, r)
+ d6.Mod(d6, q)
+ if d6.Sign() < 0 {
+ d6.Add(d6, q)
+ }
+
+ r.ModInverse(qb, p)
+ qaqb := new(big.Int).Mul(qa, r)
+ qaqb.Mod(qaqb, p)
+
+ ra := new(big.Int).Exp(qaqb, c.smp.a3, p)
+ r.Exp(qaqb, r7, p)
+ s.Exp(g, r7, p)
+ cr := new(big.Int).SetBytes(hashMPIs(h, 7, s, r))
+
+ r.Mul(c.smp.a3, cr)
+ d7 := new(big.Int).Sub(r7, r)
+ d7.Mod(d7, q)
+ if d7.Sign() < 0 {
+ d7.Add(d7, q)
+ }
+
+ c.smp.g3b = g3b
+ c.smp.qaqb = qaqb
+
+ r.ModInverse(pb, p)
+ c.smp.papb = new(big.Int).Mul(pa, r)
+ c.smp.papb.Mod(c.smp.papb, p)
+ c.smp.ra = ra
+
+ out.typ = tlvTypeSMP3
+ out.data = appendU32(out.data, 8)
+ out.data = appendMPIs(out.data, pa, qa, cp, d5, d6, ra, cr, d7)
+ return
+}
+
+func (c *Conversation) processSMP3(mpis []*big.Int) (out tlv, err error) {
+ if len(mpis) != 8 {
+ err = errors.New("otr: incorrect number of arguments in SMP3 message")
+ return
+ }
+ pa := mpis[0]
+ qa := mpis[1]
+ cp := mpis[2]
+ d5 := mpis[3]
+ d6 := mpis[4]
+ ra := mpis[5]
+ cr := mpis[6]
+ d7 := mpis[7]
+ h := sha256.New()
+
+ r := new(big.Int).Exp(g, d5, p)
+ s := new(big.Int).Exp(c.smp.g2, d6, p)
+ r.Mul(r, s)
+ s.Exp(qa, cp, p)
+ r.Mul(r, s)
+ r.Mod(r, p)
+
+ s.Exp(c.smp.g3, d5, p)
+ t := new(big.Int).Exp(pa, cp, p)
+ s.Mul(s, t)
+ s.Mod(s, p)
+ t.SetBytes(hashMPIs(h, 6, s, r))
+ if t.Cmp(cp) != 0 {
+ err = errors.New("otr: ZKP cP failed in SMP3 message")
+ return
+ }
+
+ r.ModInverse(c.smp.qb, p)
+ qaqb := new(big.Int).Mul(qa, r)
+ qaqb.Mod(qaqb, p)
+
+ r.Exp(qaqb, d7, p)
+ s.Exp(ra, cr, p)
+ r.Mul(r, s)
+ r.Mod(r, p)
+
+ s.Exp(g, d7, p)
+ t.Exp(c.smp.g3a, cr, p)
+ s.Mul(s, t)
+ s.Mod(s, p)
+ t.SetBytes(hashMPIs(h, 7, s, r))
+ if t.Cmp(cr) != 0 {
+ err = errors.New("otr: ZKP cR failed in SMP3 message")
+ return
+ }
+
+ var randBuf [16]byte
+ r7 := c.randMPI(randBuf[:])
+ rb := new(big.Int).Exp(qaqb, c.smp.b3, p)
+
+ r.Exp(qaqb, r7, p)
+ s.Exp(g, r7, p)
+ cr = new(big.Int).SetBytes(hashMPIs(h, 8, s, r))
+
+ r.Mul(c.smp.b3, cr)
+ d7 = new(big.Int).Sub(r7, r)
+ d7.Mod(d7, q)
+ if d7.Sign() < 0 {
+ d7.Add(d7, q)
+ }
+
+ out.typ = tlvTypeSMP4
+ out.data = appendU32(out.data, 3)
+ out.data = appendMPIs(out.data, rb, cr, d7)
+
+ r.ModInverse(c.smp.pb, p)
+ r.Mul(pa, r)
+ r.Mod(r, p)
+ s.Exp(ra, c.smp.b3, p)
+ if r.Cmp(s) != 0 {
+ err = smpFailureError
+ }
+
+ return
+}
+
+func (c *Conversation) processSMP4(mpis []*big.Int) error {
+ if len(mpis) != 3 {
+ return errors.New("otr: incorrect number of arguments in SMP4 message")
+ }
+ rb := mpis[0]
+ cr := mpis[1]
+ d7 := mpis[2]
+ h := sha256.New()
+
+ r := new(big.Int).Exp(c.smp.qaqb, d7, p)
+ s := new(big.Int).Exp(rb, cr, p)
+ r.Mul(r, s)
+ r.Mod(r, p)
+
+ s.Exp(g, d7, p)
+ t := new(big.Int).Exp(c.smp.g3b, cr, p)
+ s.Mul(s, t)
+ s.Mod(s, p)
+ t.SetBytes(hashMPIs(h, 8, s, r))
+ if t.Cmp(cr) != 0 {
+ return errors.New("otr: ZKP cR failed in SMP4 message")
+ }
+
+ r.Exp(rb, c.smp.a3, p)
+ if r.Cmp(c.smp.papb) != 0 {
+ return smpFailureError
+ }
+
+ return nil
+}
+
+func (c *Conversation) generateSMPAbort() tlv {
+ return tlv{typ: tlvTypeSMPAbort}
+}
+
+func hashMPIs(h hash.Hash, magic byte, mpis ...*big.Int) []byte {
+ if h != nil {
+ h.Reset()
+ } else {
+ h = sha256.New()
+ }
+
+ h.Write([]byte{magic})
+ for _, mpi := range mpis {
+ h.Write(appendMPI(nil, mpi))
+ }
+ return h.Sum(nil)
+}
diff --git a/local_crypto_patch/contents/pbkdf2/pbkdf2.go b/local_crypto_patch/contents/pbkdf2/pbkdf2.go
new file mode 100644
index 0000000000..b332122033
--- /dev/null
+++ b/local_crypto_patch/contents/pbkdf2/pbkdf2.go
@@ -0,0 +1,30 @@
+// 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 pbkdf2 implements the key derivation function PBKDF2 as defined in
+// RFC 8018 (PKCS #5 v2.1).
+//
+// This package is a wrapper for the PBKDF2 implementation in the
+// [crypto/pbkdf2] package. It is [frozen] and is not accepting new features.
+//
+// [frozen]: https://go.dev/wiki/Frozen
+package pbkdf2
+
+import (
+ "crypto/pbkdf2"
+ "hash"
+)
+
+// Key derives a key from the password, salt and iteration count, returning a
+// []byte of length keylen that can be used as cryptographic key. The key is
+// derived based on the method described as PBKDF2 with the HMAC variant using
+// the supplied hash function.
+func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
+ out, err := pbkdf2.Key(h, string(password), salt, iter, keyLen)
+ if err != nil {
+ // FIPS 140 enforcement, or an invalid key length.
+ panic(err)
+ }
+ return out
+}
diff --git a/local_crypto_patch/contents/pbkdf2/pbkdf2_test.go b/local_crypto_patch/contents/pbkdf2/pbkdf2_test.go
new file mode 100644
index 0000000000..f83cb69227
--- /dev/null
+++ b/local_crypto_patch/contents/pbkdf2/pbkdf2_test.go
@@ -0,0 +1,176 @@
+// 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 pbkdf2
+
+import (
+ "bytes"
+ "crypto/sha1"
+ "crypto/sha256"
+ "hash"
+ "testing"
+)
+
+type testVector struct {
+ password string
+ salt string
+ iter int
+ output []byte
+}
+
+// Test vectors from RFC 6070, http://tools.ietf.org/html/rfc6070
+var sha1TestVectors = []testVector{
+ {
+ "password",
+ "salt",
+ 1,
+ []byte{
+ 0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71,
+ 0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60, 0x12, 0x06,
+ 0x2f, 0xe0, 0x37, 0xa6,
+ },
+ },
+ {
+ "password",
+ "salt",
+ 2,
+ []byte{
+ 0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c,
+ 0xcd, 0x1e, 0xd9, 0x2a, 0xce, 0x1d, 0x41, 0xf0,
+ 0xd8, 0xde, 0x89, 0x57,
+ },
+ },
+ {
+ "password",
+ "salt",
+ 4096,
+ []byte{
+ 0x4b, 0x00, 0x79, 0x01, 0xb7, 0x65, 0x48, 0x9a,
+ 0xbe, 0xad, 0x49, 0xd9, 0x26, 0xf7, 0x21, 0xd0,
+ 0x65, 0xa4, 0x29, 0xc1,
+ },
+ },
+ // // This one takes too long
+ // {
+ // "password",
+ // "salt",
+ // 16777216,
+ // []byte{
+ // 0xee, 0xfe, 0x3d, 0x61, 0xcd, 0x4d, 0xa4, 0xe4,
+ // 0xe9, 0x94, 0x5b, 0x3d, 0x6b, 0xa2, 0x15, 0x8c,
+ // 0x26, 0x34, 0xe9, 0x84,
+ // },
+ // },
+ {
+ "passwordPASSWORDpassword",
+ "saltSALTsaltSALTsaltSALTsaltSALTsalt",
+ 4096,
+ []byte{
+ 0x3d, 0x2e, 0xec, 0x4f, 0xe4, 0x1c, 0x84, 0x9b,
+ 0x80, 0xc8, 0xd8, 0x36, 0x62, 0xc0, 0xe4, 0x4a,
+ 0x8b, 0x29, 0x1a, 0x96, 0x4c, 0xf2, 0xf0, 0x70,
+ 0x38,
+ },
+ },
+ {
+ "pass\000word",
+ "sa\000lt",
+ 4096,
+ []byte{
+ 0x56, 0xfa, 0x6a, 0xa7, 0x55, 0x48, 0x09, 0x9d,
+ 0xcc, 0x37, 0xd7, 0xf0, 0x34, 0x25, 0xe0, 0xc3,
+ },
+ },
+}
+
+// Test vectors from
+// http://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors
+var sha256TestVectors = []testVector{
+ {
+ "password",
+ "salt",
+ 1,
+ []byte{
+ 0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c,
+ 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37,
+ 0xa8, 0x65, 0x48, 0xc9,
+ },
+ },
+ {
+ "password",
+ "salt",
+ 2,
+ []byte{
+ 0xae, 0x4d, 0x0c, 0x95, 0xaf, 0x6b, 0x46, 0xd3,
+ 0x2d, 0x0a, 0xdf, 0xf9, 0x28, 0xf0, 0x6d, 0xd0,
+ 0x2a, 0x30, 0x3f, 0x8e,
+ },
+ },
+ {
+ "password",
+ "salt",
+ 4096,
+ []byte{
+ 0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41,
+ 0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d,
+ 0x96, 0x28, 0x93, 0xa0,
+ },
+ },
+ {
+ "passwordPASSWORDpassword",
+ "saltSALTsaltSALTsaltSALTsaltSALTsalt",
+ 4096,
+ []byte{
+ 0x34, 0x8c, 0x89, 0xdb, 0xcb, 0xd3, 0x2b, 0x2f,
+ 0x32, 0xd8, 0x14, 0xb8, 0x11, 0x6e, 0x84, 0xcf,
+ 0x2b, 0x17, 0x34, 0x7e, 0xbc, 0x18, 0x00, 0x18,
+ 0x1c,
+ },
+ },
+ {
+ "pass\000word",
+ "sa\000lt",
+ 4096,
+ []byte{
+ 0x89, 0xb6, 0x9d, 0x05, 0x16, 0xf8, 0x29, 0x89,
+ 0x3c, 0x69, 0x62, 0x26, 0x65, 0x0a, 0x86, 0x87,
+ },
+ },
+}
+
+func testHash(t *testing.T, h func() hash.Hash, hashName string, vectors []testVector) {
+ for i, v := range vectors {
+ o := Key([]byte(v.password), []byte(v.salt), v.iter, len(v.output), h)
+ if !bytes.Equal(o, v.output) {
+ t.Errorf("%s %d: expected %x, got %x", hashName, i, v.output, o)
+ }
+ }
+}
+
+func TestWithHMACSHA1(t *testing.T) {
+ testHash(t, sha1.New, "SHA1", sha1TestVectors)
+}
+
+func TestWithHMACSHA256(t *testing.T) {
+ testHash(t, sha256.New, "SHA256", sha256TestVectors)
+}
+
+var sink uint8
+
+func benchmark(b *testing.B, h func() hash.Hash) {
+ password := make([]byte, h().Size())
+ salt := make([]byte, 8)
+ for i := 0; i < b.N; i++ {
+ password = Key(password, salt, 4096, len(password), h)
+ }
+ sink += password[0]
+}
+
+func BenchmarkHMACSHA1(b *testing.B) {
+ benchmark(b, sha1.New)
+}
+
+func BenchmarkHMACSHA256(b *testing.B) {
+ benchmark(b, sha256.New)
+}
diff --git a/local_crypto_patch/contents/pkcs12/bmp-string.go b/local_crypto_patch/contents/pkcs12/bmp-string.go
new file mode 100644
index 0000000000..233b8b62cc
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/bmp-string.go
@@ -0,0 +1,50 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "errors"
+ "unicode/utf16"
+)
+
+// bmpString returns s encoded in UCS-2 with a zero terminator.
+func bmpString(s string) ([]byte, error) {
+ // References:
+ // https://tools.ietf.org/html/rfc7292#appendix-B.1
+ // https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
+ // - non-BMP characters are encoded in UTF 16 by using a surrogate pair of 16-bit codes
+ // EncodeRune returns 0xfffd if the rune does not need special encoding
+ // - the above RFC provides the info that BMPStrings are NULL terminated.
+
+ ret := make([]byte, 0, 2*len(s)+2)
+
+ for _, r := range s {
+ if t, _ := utf16.EncodeRune(r); t != 0xfffd {
+ return nil, errors.New("pkcs12: string contains characters that cannot be encoded in UCS-2")
+ }
+ ret = append(ret, byte(r/256), byte(r%256))
+ }
+
+ return append(ret, 0, 0), nil
+}
+
+func decodeBMPString(bmpString []byte) (string, error) {
+ if len(bmpString)%2 != 0 {
+ return "", errors.New("pkcs12: odd-length BMP string")
+ }
+
+ // strip terminator if present
+ if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 {
+ bmpString = bmpString[:l-2]
+ }
+
+ s := make([]uint16, 0, len(bmpString)/2)
+ for len(bmpString) > 0 {
+ s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1]))
+ bmpString = bmpString[2:]
+ }
+
+ return string(utf16.Decode(s)), nil
+}
diff --git a/local_crypto_patch/contents/pkcs12/bmp-string_test.go b/local_crypto_patch/contents/pkcs12/bmp-string_test.go
new file mode 100644
index 0000000000..7fca55f4e8
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/bmp-string_test.go
@@ -0,0 +1,63 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "bytes"
+ "encoding/hex"
+ "testing"
+)
+
+var bmpStringTests = []struct {
+ in string
+ expectedHex string
+ shouldFail bool
+}{
+ {"", "0000", false},
+ // Example from https://tools.ietf.org/html/rfc7292#appendix-B.
+ {"Beavis", "0042006500610076006900730000", false},
+ // Some characters from the "Letterlike Symbols Unicode block".
+ {"\u2115 - Double-struck N", "21150020002d00200044006f00750062006c0065002d00730074007200750063006b0020004e0000", false},
+ // any character outside the BMP should trigger an error.
+ {"\U0001f000 East wind (Mahjong)", "", true},
+}
+
+func TestBMPString(t *testing.T) {
+ for i, test := range bmpStringTests {
+ expected, err := hex.DecodeString(test.expectedHex)
+ if err != nil {
+ t.Fatalf("#%d: failed to decode expectation", i)
+ }
+
+ out, err := bmpString(test.in)
+ if err == nil && test.shouldFail {
+ t.Errorf("#%d: expected to fail, but produced %x", i, out)
+ continue
+ }
+
+ if err != nil && !test.shouldFail {
+ t.Errorf("#%d: failed unexpectedly: %s", i, err)
+ continue
+ }
+
+ if !test.shouldFail {
+ if !bytes.Equal(out, expected) {
+ t.Errorf("#%d: expected %s, got %x", i, test.expectedHex, out)
+ continue
+ }
+
+ roundTrip, err := decodeBMPString(out)
+ if err != nil {
+ t.Errorf("#%d: decoding output gave an error: %s", i, err)
+ continue
+ }
+
+ if roundTrip != test.in {
+ t.Errorf("#%d: decoding output resulted in %q, but it should have been %q", i, roundTrip, test.in)
+ continue
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/pkcs12/crypto.go b/local_crypto_patch/contents/pkcs12/crypto.go
new file mode 100644
index 0000000000..212538cb5a
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/crypto.go
@@ -0,0 +1,131 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "bytes"
+ "crypto/cipher"
+ "crypto/des"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "errors"
+
+ "golang.org/x/crypto/pkcs12/internal/rc2"
+)
+
+var (
+ oidPBEWithSHAAnd3KeyTripleDESCBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3})
+ oidPBEWithSHAAnd40BitRC2CBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 6})
+)
+
+// pbeCipher is an abstraction of a PKCS#12 cipher.
+type pbeCipher interface {
+ // create returns a cipher.Block given a key.
+ create(key []byte) (cipher.Block, error)
+ // deriveKey returns a key derived from the given password and salt.
+ deriveKey(salt, password []byte, iterations int) []byte
+ // deriveIV returns an IV derived from the given password and salt.
+ deriveIV(salt, password []byte, iterations int) []byte
+}
+
+type shaWithTripleDESCBC struct{}
+
+func (shaWithTripleDESCBC) create(key []byte) (cipher.Block, error) {
+ return des.NewTripleDESCipher(key)
+}
+
+func (shaWithTripleDESCBC) deriveKey(salt, password []byte, iterations int) []byte {
+ return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 24)
+}
+
+func (shaWithTripleDESCBC) deriveIV(salt, password []byte, iterations int) []byte {
+ return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
+}
+
+type shaWith40BitRC2CBC struct{}
+
+func (shaWith40BitRC2CBC) create(key []byte) (cipher.Block, error) {
+ return rc2.New(key, len(key)*8)
+}
+
+func (shaWith40BitRC2CBC) deriveKey(salt, password []byte, iterations int) []byte {
+ return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 5)
+}
+
+func (shaWith40BitRC2CBC) deriveIV(salt, password []byte, iterations int) []byte {
+ return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
+}
+
+type pbeParams struct {
+ Salt []byte
+ Iterations int
+}
+
+func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) {
+ var cipherType pbeCipher
+
+ switch {
+ case algorithm.Algorithm.Equal(oidPBEWithSHAAnd3KeyTripleDESCBC):
+ cipherType = shaWithTripleDESCBC{}
+ case algorithm.Algorithm.Equal(oidPBEWithSHAAnd40BitRC2CBC):
+ cipherType = shaWith40BitRC2CBC{}
+ default:
+ return nil, 0, NotImplementedError("algorithm " + algorithm.Algorithm.String() + " is not supported")
+ }
+
+ var params pbeParams
+ if err := unmarshal(algorithm.Parameters.FullBytes, ¶ms); err != nil {
+ return nil, 0, err
+ }
+
+ key := cipherType.deriveKey(params.Salt, password, params.Iterations)
+ iv := cipherType.deriveIV(params.Salt, password, params.Iterations)
+
+ block, err := cipherType.create(key)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ return cipher.NewCBCDecrypter(block, iv), block.BlockSize(), nil
+}
+
+func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error) {
+ cbc, blockSize, err := pbDecrypterFor(info.Algorithm(), password)
+ if err != nil {
+ return nil, err
+ }
+
+ encrypted := info.Data()
+ if len(encrypted) == 0 {
+ return nil, errors.New("pkcs12: empty encrypted data")
+ }
+ if len(encrypted)%blockSize != 0 {
+ return nil, errors.New("pkcs12: input is not a multiple of the block size")
+ }
+ decrypted = make([]byte, len(encrypted))
+ cbc.CryptBlocks(decrypted, encrypted)
+
+ psLen := int(decrypted[len(decrypted)-1])
+ if psLen == 0 || psLen > blockSize {
+ return nil, ErrDecryption
+ }
+
+ if len(decrypted) < psLen {
+ return nil, ErrDecryption
+ }
+ ps := decrypted[len(decrypted)-psLen:]
+ decrypted = decrypted[:len(decrypted)-psLen]
+ if !bytes.Equal(ps, bytes.Repeat([]byte{byte(psLen)}, psLen)) {
+ return nil, ErrDecryption
+ }
+
+ return
+}
+
+// decryptable abstracts an object that contains ciphertext.
+type decryptable interface {
+ Algorithm() pkix.AlgorithmIdentifier
+ Data() []byte
+}
diff --git a/local_crypto_patch/contents/pkcs12/crypto_test.go b/local_crypto_patch/contents/pkcs12/crypto_test.go
new file mode 100644
index 0000000000..eb4dae8fce
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/crypto_test.go
@@ -0,0 +1,125 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "bytes"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "testing"
+)
+
+var sha1WithTripleDES = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3})
+
+func TestPbDecrypterFor(t *testing.T) {
+ params, _ := asn1.Marshal(pbeParams{
+ Salt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
+ Iterations: 2048,
+ })
+ alg := pkix.AlgorithmIdentifier{
+ Algorithm: asn1.ObjectIdentifier([]int{1, 2, 3}),
+ Parameters: asn1.RawValue{
+ FullBytes: params,
+ },
+ }
+
+ pass, _ := bmpString("Sesame open")
+
+ _, _, err := pbDecrypterFor(alg, pass)
+ if _, ok := err.(NotImplementedError); !ok {
+ t.Errorf("expected not implemented error, got: %T %s", err, err)
+ }
+
+ alg.Algorithm = sha1WithTripleDES
+ cbc, blockSize, err := pbDecrypterFor(alg, pass)
+ if err != nil {
+ t.Errorf("unexpected error from pbDecrypterFor %v", err)
+ }
+ if blockSize != 8 {
+ t.Errorf("unexpected block size %d, wanted 8", blockSize)
+ }
+
+ plaintext := []byte{1, 2, 3, 4, 5, 6, 7, 8}
+ expectedCiphertext := []byte{185, 73, 135, 249, 137, 1, 122, 247}
+ ciphertext := make([]byte, len(plaintext))
+ cbc.CryptBlocks(ciphertext, plaintext)
+
+ if bytes.Compare(ciphertext, expectedCiphertext) != 0 {
+ t.Errorf("bad ciphertext, got %x but wanted %x", ciphertext, expectedCiphertext)
+ }
+}
+
+var pbDecryptTests = []struct {
+ in []byte
+ expected []byte
+ expectedError error
+}{
+ {
+ []byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\xa0\x9a\xdf\x5a\x58\xa0\xea\x46"), // 7 padding bytes
+ []byte("A secret!"),
+ nil,
+ },
+ {
+ []byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\x96\x24\x2f\x71\x7e\x32\x3f\xe7"), // 8 padding bytes
+ []byte("A secret"),
+ nil,
+ },
+ {
+ []byte("\x35\x0c\xc0\x8d\xab\xa9\x5d\x30\x7f\x9a\xec\x6a\xd8\x9b\x9c\xd9"), // 9 padding bytes, incorrect
+ nil,
+ ErrDecryption,
+ },
+ {
+ []byte("\xb2\xf9\x6e\x06\x60\xae\x20\xcf\x08\xa0\x7b\xd9\x6b\x20\xef\x41"), // incorrect padding bytes: [ ... 0x04 0x02 ]
+ nil,
+ ErrDecryption,
+ },
+}
+
+func TestPbDecrypt(t *testing.T) {
+ for i, test := range pbDecryptTests {
+ decryptable := testDecryptable{
+ data: test.in,
+ algorithm: pkix.AlgorithmIdentifier{
+ Algorithm: sha1WithTripleDES,
+ Parameters: pbeParams{
+ Salt: []byte("\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8"),
+ Iterations: 4096,
+ }.RawASN1(),
+ },
+ }
+ password, _ := bmpString("sesame")
+
+ plaintext, err := pbDecrypt(decryptable, password)
+ if err != test.expectedError {
+ t.Errorf("#%d: got error %q, but wanted %q", i, err, test.expectedError)
+ continue
+ }
+
+ if !bytes.Equal(plaintext, test.expected) {
+ t.Errorf("#%d: got %x, but wanted %x", i, plaintext, test.expected)
+ }
+ }
+}
+
+type testDecryptable struct {
+ data []byte
+ algorithm pkix.AlgorithmIdentifier
+}
+
+func (d testDecryptable) Algorithm() pkix.AlgorithmIdentifier { return d.algorithm }
+func (d testDecryptable) Data() []byte { return d.data }
+
+func (params pbeParams) RawASN1() (raw asn1.RawValue) {
+ asn1Bytes, err := asn1.Marshal(params)
+ if err != nil {
+ panic(err)
+ }
+ _, err = asn1.Unmarshal(asn1Bytes, &raw)
+ if err != nil {
+ panic(err)
+ }
+ return
+}
diff --git a/local_crypto_patch/contents/pkcs12/errors.go b/local_crypto_patch/contents/pkcs12/errors.go
new file mode 100644
index 0000000000..7377ce6fb2
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/errors.go
@@ -0,0 +1,23 @@
+// Copyright 2015 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 pkcs12
+
+import "errors"
+
+var (
+ // ErrDecryption represents a failure to decrypt the input.
+ ErrDecryption = errors.New("pkcs12: decryption error, incorrect padding")
+
+ // ErrIncorrectPassword is returned when an incorrect password is detected.
+ // Usually, P12/PFX data is signed to be able to verify the password.
+ ErrIncorrectPassword = errors.New("pkcs12: decryption password incorrect")
+)
+
+// NotImplementedError indicates that the input is not currently supported.
+type NotImplementedError string
+
+func (e NotImplementedError) Error() string {
+ return "pkcs12: " + string(e)
+}
diff --git a/local_crypto_patch/contents/pkcs12/internal/rc2/bench_test.go b/local_crypto_patch/contents/pkcs12/internal/rc2/bench_test.go
new file mode 100644
index 0000000000..3347f338c1
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/internal/rc2/bench_test.go
@@ -0,0 +1,27 @@
+// Copyright 2015 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 rc2
+
+import (
+ "testing"
+)
+
+func BenchmarkEncrypt(b *testing.B) {
+ r, _ := New([]byte{0, 0, 0, 0, 0, 0, 0, 0}, 64)
+ b.ResetTimer()
+ var src [8]byte
+ for i := 0; i < b.N; i++ {
+ r.Encrypt(src[:], src[:])
+ }
+}
+
+func BenchmarkDecrypt(b *testing.B) {
+ r, _ := New([]byte{0, 0, 0, 0, 0, 0, 0, 0}, 64)
+ b.ResetTimer()
+ var src [8]byte
+ for i := 0; i < b.N; i++ {
+ r.Decrypt(src[:], src[:])
+ }
+}
diff --git a/local_crypto_patch/contents/pkcs12/internal/rc2/rc2.go b/local_crypto_patch/contents/pkcs12/internal/rc2/rc2.go
new file mode 100644
index 0000000000..05de9cc2cd
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/internal/rc2/rc2.go
@@ -0,0 +1,268 @@
+// Copyright 2015 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 rc2 implements the RC2 cipher
+/*
+https://www.ietf.org/rfc/rfc2268.txt
+http://people.csail.mit.edu/rivest/pubs/KRRR98.pdf
+
+This code is licensed under the MIT license.
+*/
+package rc2
+
+import (
+ "crypto/cipher"
+ "encoding/binary"
+ "math/bits"
+)
+
+// The rc2 block size in bytes
+const BlockSize = 8
+
+type rc2Cipher struct {
+ k [64]uint16
+}
+
+// New returns a new rc2 cipher with the given key and effective key length t1
+func New(key []byte, t1 int) (cipher.Block, error) {
+ // TODO(dgryski): error checking for key length
+ return &rc2Cipher{
+ k: expandKey(key, t1),
+ }, nil
+}
+
+func (*rc2Cipher) BlockSize() int { return BlockSize }
+
+var piTable = [256]byte{
+ 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,
+ 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,
+ 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,
+ 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,
+ 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,
+ 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,
+ 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,
+ 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,
+ 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,
+ 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,
+ 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,
+ 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,
+ 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,
+ 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,
+ 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,
+ 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad,
+}
+
+func expandKey(key []byte, t1 int) [64]uint16 {
+
+ l := make([]byte, 128)
+ copy(l, key)
+
+ var t = len(key)
+ var t8 = (t1 + 7) / 8
+ var tm = byte(255 % uint(1<<(8+uint(t1)-8*uint(t8))))
+
+ for i := len(key); i < 128; i++ {
+ l[i] = piTable[l[i-1]+l[uint8(i-t)]]
+ }
+
+ l[128-t8] = piTable[l[128-t8]&tm]
+
+ for i := 127 - t8; i >= 0; i-- {
+ l[i] = piTable[l[i+1]^l[i+t8]]
+ }
+
+ var k [64]uint16
+
+ for i := range k {
+ k[i] = uint16(l[2*i]) + uint16(l[2*i+1])*256
+ }
+
+ return k
+}
+
+func (c *rc2Cipher) Encrypt(dst, src []byte) {
+
+ r0 := binary.LittleEndian.Uint16(src[0:])
+ r1 := binary.LittleEndian.Uint16(src[2:])
+ r2 := binary.LittleEndian.Uint16(src[4:])
+ r3 := binary.LittleEndian.Uint16(src[6:])
+
+ var j int
+
+ for j <= 16 {
+ // mix r0
+ r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1)
+ r0 = bits.RotateLeft16(r0, 1)
+ j++
+
+ // mix r1
+ r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2)
+ r1 = bits.RotateLeft16(r1, 2)
+ j++
+
+ // mix r2
+ r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3)
+ r2 = bits.RotateLeft16(r2, 3)
+ j++
+
+ // mix r3
+ r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0)
+ r3 = bits.RotateLeft16(r3, 5)
+ j++
+
+ }
+
+ r0 = r0 + c.k[r3&63]
+ r1 = r1 + c.k[r0&63]
+ r2 = r2 + c.k[r1&63]
+ r3 = r3 + c.k[r2&63]
+
+ for j <= 40 {
+ // mix r0
+ r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1)
+ r0 = bits.RotateLeft16(r0, 1)
+ j++
+
+ // mix r1
+ r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2)
+ r1 = bits.RotateLeft16(r1, 2)
+ j++
+
+ // mix r2
+ r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3)
+ r2 = bits.RotateLeft16(r2, 3)
+ j++
+
+ // mix r3
+ r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0)
+ r3 = bits.RotateLeft16(r3, 5)
+ j++
+
+ }
+
+ r0 = r0 + c.k[r3&63]
+ r1 = r1 + c.k[r0&63]
+ r2 = r2 + c.k[r1&63]
+ r3 = r3 + c.k[r2&63]
+
+ for j <= 60 {
+ // mix r0
+ r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1)
+ r0 = bits.RotateLeft16(r0, 1)
+ j++
+
+ // mix r1
+ r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2)
+ r1 = bits.RotateLeft16(r1, 2)
+ j++
+
+ // mix r2
+ r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3)
+ r2 = bits.RotateLeft16(r2, 3)
+ j++
+
+ // mix r3
+ r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0)
+ r3 = bits.RotateLeft16(r3, 5)
+ j++
+ }
+
+ binary.LittleEndian.PutUint16(dst[0:], r0)
+ binary.LittleEndian.PutUint16(dst[2:], r1)
+ binary.LittleEndian.PutUint16(dst[4:], r2)
+ binary.LittleEndian.PutUint16(dst[6:], r3)
+}
+
+func (c *rc2Cipher) Decrypt(dst, src []byte) {
+
+ r0 := binary.LittleEndian.Uint16(src[0:])
+ r1 := binary.LittleEndian.Uint16(src[2:])
+ r2 := binary.LittleEndian.Uint16(src[4:])
+ r3 := binary.LittleEndian.Uint16(src[6:])
+
+ j := 63
+
+ for j >= 44 {
+ // unmix r3
+ r3 = bits.RotateLeft16(r3, 16-5)
+ r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0)
+ j--
+
+ // unmix r2
+ r2 = bits.RotateLeft16(r2, 16-3)
+ r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3)
+ j--
+
+ // unmix r1
+ r1 = bits.RotateLeft16(r1, 16-2)
+ r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2)
+ j--
+
+ // unmix r0
+ r0 = bits.RotateLeft16(r0, 16-1)
+ r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1)
+ j--
+ }
+
+ r3 = r3 - c.k[r2&63]
+ r2 = r2 - c.k[r1&63]
+ r1 = r1 - c.k[r0&63]
+ r0 = r0 - c.k[r3&63]
+
+ for j >= 20 {
+ // unmix r3
+ r3 = bits.RotateLeft16(r3, 16-5)
+ r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0)
+ j--
+
+ // unmix r2
+ r2 = bits.RotateLeft16(r2, 16-3)
+ r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3)
+ j--
+
+ // unmix r1
+ r1 = bits.RotateLeft16(r1, 16-2)
+ r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2)
+ j--
+
+ // unmix r0
+ r0 = bits.RotateLeft16(r0, 16-1)
+ r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1)
+ j--
+
+ }
+
+ r3 = r3 - c.k[r2&63]
+ r2 = r2 - c.k[r1&63]
+ r1 = r1 - c.k[r0&63]
+ r0 = r0 - c.k[r3&63]
+
+ for j >= 0 {
+ // unmix r3
+ r3 = bits.RotateLeft16(r3, 16-5)
+ r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0)
+ j--
+
+ // unmix r2
+ r2 = bits.RotateLeft16(r2, 16-3)
+ r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3)
+ j--
+
+ // unmix r1
+ r1 = bits.RotateLeft16(r1, 16-2)
+ r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2)
+ j--
+
+ // unmix r0
+ r0 = bits.RotateLeft16(r0, 16-1)
+ r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1)
+ j--
+
+ }
+
+ binary.LittleEndian.PutUint16(dst[0:], r0)
+ binary.LittleEndian.PutUint16(dst[2:], r1)
+ binary.LittleEndian.PutUint16(dst[4:], r2)
+ binary.LittleEndian.PutUint16(dst[6:], r3)
+}
diff --git a/local_crypto_patch/contents/pkcs12/internal/rc2/rc2_test.go b/local_crypto_patch/contents/pkcs12/internal/rc2/rc2_test.go
new file mode 100644
index 0000000000..51a7efe506
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/internal/rc2/rc2_test.go
@@ -0,0 +1,92 @@
+// Copyright 2015 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 rc2
+
+import (
+ "bytes"
+ "encoding/hex"
+ "testing"
+)
+
+func TestEncryptDecrypt(t *testing.T) {
+ // TODO(dgryski): add the rest of the test vectors from the RFC
+ var tests = []struct {
+ key string
+ plain string
+ cipher string
+ t1 int
+ }{
+ {
+ "0000000000000000",
+ "0000000000000000",
+ "ebb773f993278eff",
+ 63,
+ },
+ {
+ "ffffffffffffffff",
+ "ffffffffffffffff",
+ "278b27e42e2f0d49",
+ 64,
+ },
+ {
+ "3000000000000000",
+ "1000000000000001",
+ "30649edf9be7d2c2",
+ 64,
+ },
+ {
+ "88",
+ "0000000000000000",
+ "61a8a244adacccf0",
+ 64,
+ },
+ {
+ "88bca90e90875a",
+ "0000000000000000",
+ "6ccf4308974c267f",
+ 64,
+ },
+ {
+ "88bca90e90875a7f0f79c384627bafb2",
+ "0000000000000000",
+ "1a807d272bbe5db1",
+ 64,
+ },
+ {
+ "88bca90e90875a7f0f79c384627bafb2",
+ "0000000000000000",
+ "2269552ab0f85ca6",
+ 128,
+ },
+ {
+ "88bca90e90875a7f0f79c384627bafb216f80a6f85920584c42fceb0be255daf1e",
+ "0000000000000000",
+ "5b78d3a43dfff1f1",
+ 129,
+ },
+ }
+
+ for _, tt := range tests {
+ k, _ := hex.DecodeString(tt.key)
+ p, _ := hex.DecodeString(tt.plain)
+ c, _ := hex.DecodeString(tt.cipher)
+
+ b, _ := New(k, tt.t1)
+
+ var dst [8]byte
+
+ b.Encrypt(dst[:], p)
+
+ if !bytes.Equal(dst[:], c) {
+ t.Errorf("encrypt failed: got % 2x wanted % 2x\n", dst, c)
+ }
+
+ b.Decrypt(dst[:], c)
+
+ if !bytes.Equal(dst[:], p) {
+ t.Errorf("decrypt failed: got % 2x wanted % 2x\n", dst, p)
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/pkcs12/mac.go b/local_crypto_patch/contents/pkcs12/mac.go
new file mode 100644
index 0000000000..5f38aa7de8
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/mac.go
@@ -0,0 +1,45 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "crypto/hmac"
+ "crypto/sha1"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+)
+
+type macData struct {
+ Mac digestInfo
+ MacSalt []byte
+ Iterations int `asn1:"optional,default:1"`
+}
+
+// from PKCS#7:
+type digestInfo struct {
+ Algorithm pkix.AlgorithmIdentifier
+ Digest []byte
+}
+
+var (
+ oidSHA1 = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
+)
+
+func verifyMac(macData *macData, message, password []byte) error {
+ if !macData.Mac.Algorithm.Algorithm.Equal(oidSHA1) {
+ return NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String())
+ }
+
+ key := pbkdf(sha1Sum, 20, 64, macData.MacSalt, password, macData.Iterations, 3, 20)
+
+ mac := hmac.New(sha1.New, key)
+ mac.Write(message)
+ expectedMAC := mac.Sum(nil)
+
+ if !hmac.Equal(macData.Mac.Digest, expectedMAC) {
+ return ErrIncorrectPassword
+ }
+ return nil
+}
diff --git a/local_crypto_patch/contents/pkcs12/mac_test.go b/local_crypto_patch/contents/pkcs12/mac_test.go
new file mode 100644
index 0000000000..1ed4ff21e1
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/mac_test.go
@@ -0,0 +1,42 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "encoding/asn1"
+ "testing"
+)
+
+func TestVerifyMac(t *testing.T) {
+ td := macData{
+ Mac: digestInfo{
+ Digest: []byte{0x18, 0x20, 0x3d, 0xff, 0x1e, 0x16, 0xf4, 0x92, 0xf2, 0xaf, 0xc8, 0x91, 0xa9, 0xba, 0xd6, 0xca, 0x9d, 0xee, 0x51, 0x93},
+ },
+ MacSalt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
+ Iterations: 2048,
+ }
+
+ message := []byte{11, 12, 13, 14, 15}
+ password, _ := bmpString("")
+
+ td.Mac.Algorithm.Algorithm = asn1.ObjectIdentifier([]int{1, 2, 3})
+ err := verifyMac(&td, message, password)
+ if _, ok := err.(NotImplementedError); !ok {
+ t.Errorf("err: %v", err)
+ }
+
+ td.Mac.Algorithm.Algorithm = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
+ err = verifyMac(&td, message, password)
+ if err != ErrIncorrectPassword {
+ t.Errorf("Expected incorrect password, got err: %v", err)
+ }
+
+ password, _ = bmpString("Sesame open")
+ err = verifyMac(&td, message, password)
+ if err != nil {
+ t.Errorf("err: %v", err)
+ }
+
+}
diff --git a/local_crypto_patch/contents/pkcs12/pbkdf.go b/local_crypto_patch/contents/pkcs12/pbkdf.go
new file mode 100644
index 0000000000..5c419d41e3
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/pbkdf.go
@@ -0,0 +1,170 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "bytes"
+ "crypto/sha1"
+ "math/big"
+)
+
+var (
+ one = big.NewInt(1)
+)
+
+// sha1Sum returns the SHA-1 hash of in.
+func sha1Sum(in []byte) []byte {
+ sum := sha1.Sum(in)
+ return sum[:]
+}
+
+// fillWithRepeats returns v*ceiling(len(pattern) / v) bytes consisting of
+// repeats of pattern.
+func fillWithRepeats(pattern []byte, v int) []byte {
+ if len(pattern) == 0 {
+ return nil
+ }
+ outputLen := v * ((len(pattern) + v - 1) / v)
+ return bytes.Repeat(pattern, (outputLen+len(pattern)-1)/len(pattern))[:outputLen]
+}
+
+func pbkdf(hash func([]byte) []byte, u, v int, salt, password []byte, r int, ID byte, size int) (key []byte) {
+ // implementation of https://tools.ietf.org/html/rfc7292#appendix-B.2 , RFC text verbatim in comments
+
+ // Let H be a hash function built around a compression function f:
+
+ // Z_2^u x Z_2^v -> Z_2^u
+
+ // (that is, H has a chaining variable and output of length u bits, and
+ // the message input to the compression function of H is v bits). The
+ // values for u and v are as follows:
+
+ // HASH FUNCTION VALUE u VALUE v
+ // MD2, MD5 128 512
+ // SHA-1 160 512
+ // SHA-224 224 512
+ // SHA-256 256 512
+ // SHA-384 384 1024
+ // SHA-512 512 1024
+ // SHA-512/224 224 1024
+ // SHA-512/256 256 1024
+
+ // Furthermore, let r be the iteration count.
+
+ // We assume here that u and v are both multiples of 8, as are the
+ // lengths of the password and salt strings (which we denote by p and s,
+ // respectively) and the number n of pseudorandom bits required. In
+ // addition, u and v are of course non-zero.
+
+ // For information on security considerations for MD5 [19], see [25] and
+ // [1], and on those for MD2, see [18].
+
+ // The following procedure can be used to produce pseudorandom bits for
+ // a particular "purpose" that is identified by a byte called "ID".
+ // This standard specifies 3 different values for the ID byte:
+
+ // 1. If ID=1, then the pseudorandom bits being produced are to be used
+ // as key material for performing encryption or decryption.
+
+ // 2. If ID=2, then the pseudorandom bits being produced are to be used
+ // as an IV (Initial Value) for encryption or decryption.
+
+ // 3. If ID=3, then the pseudorandom bits being produced are to be used
+ // as an integrity key for MACing.
+
+ // 1. Construct a string, D (the "diversifier"), by concatenating v/8
+ // copies of ID.
+ var D []byte
+ for i := 0; i < v; i++ {
+ D = append(D, ID)
+ }
+
+ // 2. Concatenate copies of the salt together to create a string S of
+ // length v(ceiling(s/v)) bits (the final copy of the salt may be
+ // truncated to create S). Note that if the salt is the empty
+ // string, then so is S.
+
+ S := fillWithRepeats(salt, v)
+
+ // 3. Concatenate copies of the password together to create a string P
+ // of length v(ceiling(p/v)) bits (the final copy of the password
+ // may be truncated to create P). Note that if the password is the
+ // empty string, then so is P.
+
+ P := fillWithRepeats(password, v)
+
+ // 4. Set I=S||P to be the concatenation of S and P.
+ I := append(S, P...)
+
+ // 5. Set c=ceiling(n/u).
+ c := (size + u - 1) / u
+
+ // 6. For i=1, 2, ..., c, do the following:
+ A := make([]byte, c*20)
+ var IjBuf []byte
+ for i := 0; i < c; i++ {
+ // A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1,
+ // H(H(H(... H(D||I))))
+ Ai := hash(append(D, I...))
+ for j := 1; j < r; j++ {
+ Ai = hash(Ai)
+ }
+ copy(A[i*20:], Ai[:])
+
+ if i < c-1 { // skip on last iteration
+ // B. Concatenate copies of Ai to create a string B of length v
+ // bits (the final copy of Ai may be truncated to create B).
+ var B []byte
+ for len(B) < v {
+ B = append(B, Ai[:]...)
+ }
+ B = B[:v]
+
+ // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit
+ // blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by
+ // setting I_j=(I_j+B+1) mod 2^v for each j.
+ {
+ Bbi := new(big.Int).SetBytes(B)
+ Ij := new(big.Int)
+
+ for j := 0; j < len(I)/v; j++ {
+ Ij.SetBytes(I[j*v : (j+1)*v])
+ Ij.Add(Ij, Bbi)
+ Ij.Add(Ij, one)
+ Ijb := Ij.Bytes()
+ // We expect Ijb to be exactly v bytes,
+ // if it is longer or shorter we must
+ // adjust it accordingly.
+ if len(Ijb) > v {
+ Ijb = Ijb[len(Ijb)-v:]
+ }
+ if len(Ijb) < v {
+ if IjBuf == nil {
+ IjBuf = make([]byte, v)
+ }
+ bytesShort := v - len(Ijb)
+ for i := 0; i < bytesShort; i++ {
+ IjBuf[i] = 0
+ }
+ copy(IjBuf[bytesShort:], Ijb)
+ Ijb = IjBuf
+ }
+ copy(I[j*v:(j+1)*v], Ijb)
+ }
+ }
+ }
+ }
+ // 7. Concatenate A_1, A_2, ..., A_c together to form a pseudorandom
+ // bit string, A.
+
+ // 8. Use the first n bits of A as the output of this entire process.
+ return A[:size]
+
+ // If the above process is being used to generate a DES key, the process
+ // should be used to create 64 random bits, and the key's parity bits
+ // should be set after the 64 bits have been produced. Similar concerns
+ // hold for 2-key and 3-key triple-DES keys, for CDMF keys, and for any
+ // similar keys with parity bits "built into them".
+}
diff --git a/local_crypto_patch/contents/pkcs12/pbkdf_test.go b/local_crypto_patch/contents/pkcs12/pbkdf_test.go
new file mode 100644
index 0000000000..262037d7eb
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/pbkdf_test.go
@@ -0,0 +1,34 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestThatPBKDFWorksCorrectlyForLongKeys(t *testing.T) {
+ cipherInfo := shaWithTripleDESCBC{}
+
+ salt := []byte("\xff\xff\xff\xff\xff\xff\xff\xff")
+ password, _ := bmpString("sesame")
+ key := cipherInfo.deriveKey(salt, password, 2048)
+
+ if expected := []byte("\x7c\xd9\xfd\x3e\x2b\x3b\xe7\x69\x1a\x44\xe3\xbe\xf0\xf9\xea\x0f\xb9\xb8\x97\xd4\xe3\x25\xd9\xd1"); bytes.Compare(key, expected) != 0 {
+ t.Fatalf("expected key '%x', but found '%x'", expected, key)
+ }
+}
+
+func TestThatPBKDFHandlesLeadingZeros(t *testing.T) {
+ // This test triggers a case where I_j (in step 6C) ends up with leading zero
+ // byte, meaning that len(Ijb) < v (leading zeros get stripped by big.Int).
+ // This was previously causing bug whereby certain inputs would break the
+ // derivation and produce the wrong output.
+ key := pbkdf(sha1Sum, 20, 64, []byte("\xf3\x7e\x05\xb5\x18\x32\x4b\x4b"), []byte("\x00\x00"), 2048, 1, 24)
+ expected := []byte("\x00\xf7\x59\xff\x47\xd1\x4d\xd0\x36\x65\xd5\x94\x3c\xb3\xc4\xa3\x9a\x25\x55\xc0\x2a\xed\x66\xe1")
+ if bytes.Compare(key, expected) != 0 {
+ t.Fatalf("expected key '%x', but found '%x'", expected, key)
+ }
+}
diff --git a/local_crypto_patch/contents/pkcs12/pkcs12.go b/local_crypto_patch/contents/pkcs12/pkcs12.go
new file mode 100644
index 0000000000..374d9facf8
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/pkcs12.go
@@ -0,0 +1,364 @@
+// Copyright 2015 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 pkcs12 implements some of PKCS#12.
+//
+// This implementation is distilled from [RFC 7292] and referenced documents.
+// It is intended for decoding P12/PFX-stored certificates and keys for use
+// with the crypto/tls package.
+//
+// The pkcs12 package is [frozen] and is not accepting new features.
+// If it's missing functionality you need, consider an alternative like
+// software.sslmate.com/src/go-pkcs12.
+//
+// [RFC 7292]: https://datatracker.ietf.org/doc/html/rfc7292
+// [frozen]: https://go.dev/wiki/Frozen
+package pkcs12
+
+import (
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "encoding/hex"
+ "encoding/pem"
+ "errors"
+)
+
+var (
+ oidDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 1})
+ oidEncryptedDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 6})
+
+ oidFriendlyName = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 20})
+ oidLocalKeyID = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 21})
+ oidMicrosoftCSPName = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 311, 17, 1})
+
+ errUnknownAttributeOID = errors.New("pkcs12: unknown attribute OID")
+)
+
+type pfxPdu struct {
+ Version int
+ AuthSafe contentInfo
+ MacData macData `asn1:"optional"`
+}
+
+type contentInfo struct {
+ ContentType asn1.ObjectIdentifier
+ Content asn1.RawValue `asn1:"tag:0,explicit,optional"`
+}
+
+type encryptedData struct {
+ Version int
+ EncryptedContentInfo encryptedContentInfo
+}
+
+type encryptedContentInfo struct {
+ ContentType asn1.ObjectIdentifier
+ ContentEncryptionAlgorithm pkix.AlgorithmIdentifier
+ EncryptedContent []byte `asn1:"tag:0,optional"`
+}
+
+func (i encryptedContentInfo) Algorithm() pkix.AlgorithmIdentifier {
+ return i.ContentEncryptionAlgorithm
+}
+
+func (i encryptedContentInfo) Data() []byte { return i.EncryptedContent }
+
+type safeBag struct {
+ Id asn1.ObjectIdentifier
+ Value asn1.RawValue `asn1:"tag:0,explicit"`
+ Attributes []pkcs12Attribute `asn1:"set,optional"`
+}
+
+type pkcs12Attribute struct {
+ Id asn1.ObjectIdentifier
+ Value asn1.RawValue `asn1:"set"`
+}
+
+type encryptedPrivateKeyInfo struct {
+ AlgorithmIdentifier pkix.AlgorithmIdentifier
+ EncryptedData []byte
+}
+
+func (i encryptedPrivateKeyInfo) Algorithm() pkix.AlgorithmIdentifier {
+ return i.AlgorithmIdentifier
+}
+
+func (i encryptedPrivateKeyInfo) Data() []byte {
+ return i.EncryptedData
+}
+
+// PEM block types
+const (
+ certificateType = "CERTIFICATE"
+ privateKeyType = "PRIVATE KEY"
+)
+
+// unmarshal calls asn1.Unmarshal, but also returns an error if there is any
+// trailing data after unmarshaling.
+func unmarshal(in []byte, out interface{}) error {
+ trailing, err := asn1.Unmarshal(in, out)
+ if err != nil {
+ return err
+ }
+ if len(trailing) != 0 {
+ return errors.New("pkcs12: trailing data found")
+ }
+ return nil
+}
+
+// ToPEM converts all "safe bags" contained in pfxData to PEM blocks.
+// Unknown attributes are discarded.
+//
+// Note that although the returned PEM blocks for private keys have type
+// "PRIVATE KEY", the bytes are not encoded according to PKCS #8, but according
+// to PKCS #1 for RSA keys and SEC 1 for ECDSA keys.
+func ToPEM(pfxData []byte, password string) ([]*pem.Block, error) {
+ encodedPassword, err := bmpString(password)
+ if err != nil {
+ return nil, ErrIncorrectPassword
+ }
+
+ bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword)
+
+ if err != nil {
+ return nil, err
+ }
+
+ blocks := make([]*pem.Block, 0, len(bags))
+ for _, bag := range bags {
+ block, err := convertBag(&bag, encodedPassword)
+ if err != nil {
+ return nil, err
+ }
+ blocks = append(blocks, block)
+ }
+
+ return blocks, nil
+}
+
+func convertBag(bag *safeBag, password []byte) (*pem.Block, error) {
+ block := &pem.Block{
+ Headers: make(map[string]string),
+ }
+
+ for _, attribute := range bag.Attributes {
+ k, v, err := convertAttribute(&attribute)
+ if err == errUnknownAttributeOID {
+ continue
+ }
+ if err != nil {
+ return nil, err
+ }
+ block.Headers[k] = v
+ }
+
+ switch {
+ case bag.Id.Equal(oidCertBag):
+ block.Type = certificateType
+ certsData, err := decodeCertBag(bag.Value.Bytes)
+ if err != nil {
+ return nil, err
+ }
+ block.Bytes = certsData
+ case bag.Id.Equal(oidPKCS8ShroundedKeyBag):
+ block.Type = privateKeyType
+
+ key, err := decodePkcs8ShroudedKeyBag(bag.Value.Bytes, password)
+ if err != nil {
+ return nil, err
+ }
+
+ switch key := key.(type) {
+ case *rsa.PrivateKey:
+ block.Bytes = x509.MarshalPKCS1PrivateKey(key)
+ case *ecdsa.PrivateKey:
+ block.Bytes, err = x509.MarshalECPrivateKey(key)
+ if err != nil {
+ return nil, err
+ }
+ default:
+ return nil, errors.New("found unknown private key type in PKCS#8 wrapping")
+ }
+ default:
+ return nil, errors.New("don't know how to convert a safe bag of type " + bag.Id.String())
+ }
+ return block, nil
+}
+
+func convertAttribute(attribute *pkcs12Attribute) (key, value string, err error) {
+ isString := false
+
+ switch {
+ case attribute.Id.Equal(oidFriendlyName):
+ key = "friendlyName"
+ isString = true
+ case attribute.Id.Equal(oidLocalKeyID):
+ key = "localKeyId"
+ case attribute.Id.Equal(oidMicrosoftCSPName):
+ // This key is chosen to match OpenSSL.
+ key = "Microsoft CSP Name"
+ isString = true
+ default:
+ return "", "", errUnknownAttributeOID
+ }
+
+ if isString {
+ if err := unmarshal(attribute.Value.Bytes, &attribute.Value); err != nil {
+ return "", "", err
+ }
+ if value, err = decodeBMPString(attribute.Value.Bytes); err != nil {
+ return "", "", err
+ }
+ } else {
+ var id []byte
+ if err := unmarshal(attribute.Value.Bytes, &id); err != nil {
+ return "", "", err
+ }
+ value = hex.EncodeToString(id)
+ }
+
+ return key, value, nil
+}
+
+// Decode extracts a certificate and private key from pfxData. This function
+// assumes that there is only one certificate and only one private key in the
+// pfxData; if there are more use ToPEM instead.
+func Decode(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, err error) {
+ encodedPassword, err := bmpString(password)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if len(bags) != 2 {
+ err = errors.New("pkcs12: expected exactly two safe bags in the PFX PDU")
+ return
+ }
+
+ for _, bag := range bags {
+ switch {
+ case bag.Id.Equal(oidCertBag):
+ if certificate != nil {
+ err = errors.New("pkcs12: expected exactly one certificate bag")
+ }
+
+ certsData, err := decodeCertBag(bag.Value.Bytes)
+ if err != nil {
+ return nil, nil, err
+ }
+ certs, err := x509.ParseCertificates(certsData)
+ if err != nil {
+ return nil, nil, err
+ }
+ if len(certs) != 1 {
+ err = errors.New("pkcs12: expected exactly one certificate in the certBag")
+ return nil, nil, err
+ }
+ certificate = certs[0]
+
+ case bag.Id.Equal(oidPKCS8ShroundedKeyBag):
+ if privateKey != nil {
+ err = errors.New("pkcs12: expected exactly one key bag")
+ return nil, nil, err
+ }
+
+ if privateKey, err = decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword); err != nil {
+ return nil, nil, err
+ }
+ }
+ }
+
+ if certificate == nil {
+ return nil, nil, errors.New("pkcs12: certificate missing")
+ }
+ if privateKey == nil {
+ return nil, nil, errors.New("pkcs12: private key missing")
+ }
+
+ return
+}
+
+func getSafeContents(p12Data, password []byte) (bags []safeBag, updatedPassword []byte, err error) {
+ pfx := new(pfxPdu)
+ if err := unmarshal(p12Data, pfx); err != nil {
+ return nil, nil, errors.New("pkcs12: error reading P12 data: " + err.Error())
+ }
+
+ if pfx.Version != 3 {
+ return nil, nil, NotImplementedError("can only decode v3 PFX PDU's")
+ }
+
+ if !pfx.AuthSafe.ContentType.Equal(oidDataContentType) {
+ return nil, nil, NotImplementedError("only password-protected PFX is implemented")
+ }
+
+ // unmarshal the explicit bytes in the content for type 'data'
+ if err := unmarshal(pfx.AuthSafe.Content.Bytes, &pfx.AuthSafe.Content); err != nil {
+ return nil, nil, err
+ }
+
+ if len(pfx.MacData.Mac.Algorithm.Algorithm) == 0 {
+ return nil, nil, errors.New("pkcs12: no MAC in data")
+ }
+
+ if err := verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes, password); err != nil {
+ if err == ErrIncorrectPassword && len(password) == 2 && password[0] == 0 && password[1] == 0 {
+ // some implementations use an empty byte array
+ // for the empty string password try one more
+ // time with empty-empty password
+ password = nil
+ err = verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes, password)
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ var authenticatedSafe []contentInfo
+ if err := unmarshal(pfx.AuthSafe.Content.Bytes, &authenticatedSafe); err != nil {
+ return nil, nil, err
+ }
+
+ if len(authenticatedSafe) != 2 {
+ return nil, nil, NotImplementedError("expected exactly two items in the authenticated safe")
+ }
+
+ for _, ci := range authenticatedSafe {
+ var data []byte
+
+ switch {
+ case ci.ContentType.Equal(oidDataContentType):
+ if err := unmarshal(ci.Content.Bytes, &data); err != nil {
+ return nil, nil, err
+ }
+ case ci.ContentType.Equal(oidEncryptedDataContentType):
+ var encryptedData encryptedData
+ if err := unmarshal(ci.Content.Bytes, &encryptedData); err != nil {
+ return nil, nil, err
+ }
+ if encryptedData.Version != 0 {
+ return nil, nil, NotImplementedError("only version 0 of EncryptedData is supported")
+ }
+ if data, err = pbDecrypt(encryptedData.EncryptedContentInfo, password); err != nil {
+ return nil, nil, err
+ }
+ default:
+ return nil, nil, NotImplementedError("only data and encryptedData content types are supported in authenticated safe")
+ }
+
+ var safeContents []safeBag
+ if err := unmarshal(data, &safeContents); err != nil {
+ return nil, nil, err
+ }
+ bags = append(bags, safeContents...)
+ }
+
+ return bags, password, nil
+}
diff --git a/local_crypto_patch/contents/pkcs12/pkcs12_test.go b/local_crypto_patch/contents/pkcs12/pkcs12_test.go
new file mode 100644
index 0000000000..68476a822f
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/pkcs12_test.go
@@ -0,0 +1,141 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "crypto/rsa"
+ "crypto/tls"
+ "encoding/base64"
+ "encoding/pem"
+ "testing"
+)
+
+func TestPfx(t *testing.T) {
+ for commonName, base64P12 := range testdata {
+ p12, _ := base64.StdEncoding.DecodeString(base64P12)
+
+ priv, cert, err := Decode(p12, "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := priv.(*rsa.PrivateKey).Validate(); err != nil {
+ t.Errorf("error while validating private key: %v", err)
+ }
+
+ if cert.Subject.CommonName != commonName {
+ t.Errorf("expected common name to be %q, but found %q", commonName, cert.Subject.CommonName)
+ }
+ }
+}
+
+func TestPEM(t *testing.T) {
+ for commonName, base64P12 := range testdata {
+ p12, _ := base64.StdEncoding.DecodeString(base64P12)
+
+ blocks, err := ToPEM(p12, "")
+ if err != nil {
+ t.Fatalf("error while converting to PEM: %s", err)
+ }
+
+ var pemData []byte
+ for _, b := range blocks {
+ pemData = append(pemData, pem.EncodeToMemory(b)...)
+ }
+
+ cert, err := tls.X509KeyPair(pemData, pemData)
+ if err != nil {
+ t.Errorf("err while converting to key pair: %v", err)
+ }
+ config := tls.Config{
+ Certificates: []tls.Certificate{cert},
+ }
+ config.BuildNameToCertificate()
+
+ if _, exists := config.NameToCertificate[commonName]; !exists {
+ t.Errorf("did not find our cert in PEM?: %v", config.NameToCertificate)
+ }
+ }
+}
+
+func ExampleToPEM() {
+ p12, _ := base64.StdEncoding.DecodeString(`MIIJzgIBAzCCCZQGCS ... CA+gwggPk==`)
+
+ blocks, err := ToPEM(p12, "password")
+ if err != nil {
+ panic(err)
+ }
+
+ var pemData []byte
+ for _, b := range blocks {
+ pemData = append(pemData, pem.EncodeToMemory(b)...)
+ }
+
+ // then use PEM data for tls to construct tls certificate:
+ cert, err := tls.X509KeyPair(pemData, pemData)
+ if err != nil {
+ panic(err)
+ }
+
+ config := &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ }
+
+ _ = config
+}
+
+var testdata = map[string]string{
+ // 'null' password test case
+ "Windows Azure Tools": `MIIKDAIBAzCCCcwGCSqGSIb3DQEHAaCCCb0Eggm5MIIJtTCCBe4GCSqGSIb3DQEHAaCCBd8EggXbMIIF1zCCBdMGCyqGSIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAhStUNnlTGV+gICB9AEggTIJ81JIossF6boFWpPtkiQRPtI6DW6e9QD4/WvHAVrM2bKdpMzSMsCML5NyuddANTKHBVq00Jc9keqGNAqJPKkjhSUebzQFyhe0E1oI9T4zY5UKr/I8JclOeccH4QQnsySzYUG2SnniXnQ+JrG3juetli7EKth9h6jLc6xbubPadY5HMB3wL/eG/kJymiXwU2KQ9Mgd4X6jbcV+NNCE/8jbZHvSTCPeYTJIjxfeX61Sj5kFKUCzERbsnpyevhY3X0eYtEDezZQarvGmXtMMdzf8HJHkWRdk9VLDLgjk8uiJif/+X4FohZ37ig0CpgC2+dP4DGugaZZ51hb8tN9GeCKIsrmWogMXDIVd0OACBp/EjJVmFB6y0kUCXxUE0TZt0XA1tjAGJcjDUpBvTntZjPsnH/4ZySy+s2d9OOhJ6pzRQBRm360TzkFdSwk9DLiLdGfv4pwMMu/vNGBlqjP/1sQtj+jprJiD1sDbCl4AdQZVoMBQHadF2uSD4/o17XG/Ci0r2h6Htc2yvZMAbEY4zMjjIn2a+vqIxD6onexaek1R3zbkS9j19D6EN9EWn8xgz80YRCyW65znZk8xaIhhvlU/mg7sTxeyuqroBZNcq6uDaQTehDpyH7bY2l4zWRpoj10a6JfH2q5shYz8Y6UZC/kOTfuGqbZDNZWro/9pYquvNNW0M847E5t9bsf9VkAAMHRGBbWoVoU9VpI0UnoXSfvpOo+aXa2DSq5sHHUTVY7A9eov3z5IqT+pligx11xcs+YhDWcU8di3BTJisohKvv5Y8WSkm/rloiZd4ig269k0jTRk1olP/vCksPli4wKG2wdsd5o42nX1yL7mFfXocOANZbB+5qMkiwdyoQSk+Vq+C8nAZx2bbKhUq2MbrORGMzOe0Hh0x2a0PeObycN1Bpyv7Mp3ZI9h5hBnONKCnqMhtyQHUj/nNvbJUnDVYNfoOEqDiEqqEwB7YqWzAKz8KW0OIqdlM8uiQ4JqZZlFllnWJUfaiDrdFM3lYSnFQBkzeVlts6GpDOOBjCYd7dcCNS6kq6pZC6p6HN60Twu0JnurZD6RT7rrPkIGE8vAenFt4iGe/yF52fahCSY8Ws4K0UTwN7bAS+4xRHVCWvE8sMRZsRCHizb5laYsVrPZJhE6+hux6OBb6w8kwPYXc+ud5v6UxawUWgt6uPwl8mlAtU9Z7Miw4Nn/wtBkiLL/ke1UI1gqJtcQXgHxx6mzsjh41+nAgTvdbsSEyU6vfOmxGj3Rwc1eOrIhJUqn5YjOWfzzsz/D5DzWKmwXIwdspt1p+u+kol1N3f2wT9fKPnd/RGCb4g/1hc3Aju4DQYgGY782l89CEEdalpQ/35bQczMFk6Fje12HykakWEXd/bGm9Unh82gH84USiRpeOfQvBDYoqEyrY3zkFZzBjhDqa+jEcAj41tcGx47oSfDq3iVYCdL7HSIjtnyEktVXd7mISZLoMt20JACFcMw+mrbjlug+eU7o2GR7T+LwtOp/p4LZqyLa7oQJDwde1BNZtm3TCK2P1mW94QDL0nDUps5KLtr1DaZXEkRbjSJub2ZE9WqDHyU3KA8G84Tq/rN1IoNu/if45jacyPje1Npj9IftUZSP22nV7HMwZtwQ4P4MYHRMBMGCSqGSIb3DQEJFTEGBAQBAAAAMFsGCSqGSIb3DQEJFDFOHkwAewBCADQAQQA0AEYARQBCADAALQBBADEAOABBAC0ANAA0AEIAQgAtAEIANQBGADIALQA0ADkAMQBFAEYAMQA1ADIAQgBBADEANgB9MF0GCSsGAQQBgjcRATFQHk4ATQBpAGMAcgBvAHMAbwBmAHQAIABTAG8AZgB0AHcAYQByAGUAIABLAGUAeQAgAFMAdABvAHIAYQBnAGUAIABQAHIAbwB2AGkAZABlAHIwggO/BgkqhkiG9w0BBwagggOwMIIDrAIBADCCA6UGCSqGSIb3DQEHATAcBgoqhkiG9w0BDAEGMA4ECEBk5ZAYpu0WAgIH0ICCA3hik4mQFGpw9Ha8TQPtk+j2jwWdxfF0+sTk6S8PTsEfIhB7wPltjiCK92Uv2tCBQnodBUmatIfkpnRDEySmgmdglmOCzj204lWAMRs94PoALGn3JVBXbO1vIDCbAPOZ7Z0Hd0/1t2hmk8v3//QJGUg+qr59/4y/MuVfIg4qfkPcC2QSvYWcK3oTf6SFi5rv9B1IOWFgN5D0+C+x/9Lb/myPYX+rbOHrwtJ4W1fWKoz9g7wwmGFA9IJ2DYGuH8ifVFbDFT1Vcgsvs8arSX7oBsJVW0qrP7XkuDRe3EqCmKW7rBEwYrFznhxZcRDEpMwbFoSvgSIZ4XhFY9VKYglT+JpNH5iDceYEBOQL4vBLpxNUk3l5jKaBNxVa14AIBxq18bVHJ+STInhLhad4u10v/Xbx7wIL3f9DX1yLAkPrpBYbNHS2/ew6H/ySDJnoIDxkw2zZ4qJ+qUJZ1S0lbZVG+VT0OP5uF6tyOSpbMlcGkdl3z254n6MlCrTifcwkzscysDsgKXaYQw06rzrPW6RDub+t+hXzGny799fS9jhQMLDmOggaQ7+LA4oEZsfT89HLMWxJYDqjo3gIfjciV2mV54R684qLDS+AO09U49e6yEbwGlq8lpmO/pbXCbpGbB1b3EomcQbxdWxW2WEkkEd/VBn81K4M3obmywwXJkw+tPXDXfBmzzaqqCR+onMQ5ME1nMkY8ybnfoCc1bDIupjVWsEL2Wvq752RgI6KqzVNr1ew1IdqV5AWN2fOfek+0vi3Jd9FHF3hx8JMwjJL9dZsETV5kHtYJtE7wJ23J68BnCt2eI0GEuwXcCf5EdSKN/xXCTlIokc4Qk/gzRdIZsvcEJ6B1lGovKG54X4IohikqTjiepjbsMWj38yxDmK3mtENZ9ci8FPfbbvIEcOCZIinuY3qFUlRSbx7VUerEoV1IP3clUwexVQo4lHFee2jd7ocWsdSqSapW7OWUupBtDzRkqVhE7tGria+i1W2d6YLlJ21QTjyapWJehAMO637OdbJCCzDs1cXbodRRE7bsP492ocJy8OX66rKdhYbg8srSFNKdb3pF3UDNbN9jhI/t8iagRhNBhlQtTr1me2E/c86Q18qcRXl4bcXTt6acgCeffK6Y26LcVlrgjlD33AEYRRUeyC+rpxbT0aMjdFderlndKRIyG23mSp0HaUwNzAfMAcGBSsOAwIaBBRlviCbIyRrhIysg2dc/KbLFTc2vQQUg4rfwHMM4IKYRD/fsd1x6dda+wQ=`,
+ // Windows IAS PEAP & LDAPS certificates test case
+ // Unknown OID 1.3.6.1.4.1.311.17.2 should be dropped
+ "Windows IAS PEAP & LDAPS certificates": `MIIHPQIBAzCCBwMGCSqGSIb3DQEHAaCCBvQEggbwMIIG7DCCAz8GCSqGSIb3DQEHBqCCAzAwggMsAgEAMIIDJQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIrosqK6kNi9sCAggAgIIC+IcOaLAkrLiBCnw06bFGOUMGkVsuiYZlkTBzW55DQS4JUefZ71CPMUofo7U4z7bL1JYGV2aO9REMnb8gm0jQYgVEFNQbsDDICZBA8Xfjki0MULw3kEyFxfk7AV51IMRVjAGImS2asDAWW+dVgLLbBV+Q8L+D917sS8pz0VLT4GzxZHLdGXVXKp2MHkHc3nx4eDeWkBAZoSqansgJXTM3JOWOSxUEFZA2Wb7UerykCLuzK+RmR2pkmV88JIFbneP/NjQg/nZDN4bGXGJf+3gRqq07T4q7QKzmZRrQgLJwSZ1wzhB2HoIfIm/ylOEUly5XzMbf6nzc94BrDXv6q4efXMApztTfAsq9hysMiImQrPGxYBj3CAxfWCfc7K4XlbdRwZTmbCutf5O93aYALVAkzPf4x2NWxcw5sLYfGH8ma9xF3VZk+h1DJw+6Iq0+g/8lZ7uGJPAZav40YIW+RZ3vsDx3uw7OkQNwP0b/lahgnftTa0WcF3OwocTVb1o3zbtAW+pQxTRvdvTX6jENVTJVk10probfq+iDoolGe382c9d5qo4Yh/AhZHWqL2YqU2ypq16rxz1RPGSpceHAtVVZYSTKk9VKg0fevz8P8wjUKboZmpLnSu2P5ABwkoSbrGQIKMtE3CSswxKQVzEreKbcyeNBt0A0vSTOrwSzDQxFE4Ur+lUnqJC8sHW2NpA84S+TCLEAzhPMIFo5MJ90jN8N3tfTYnXVZDk1mt0pJEmWRxRofVJm2/J6Slak6x51s+TKiss/rG3y1XpzCgN9Nzb7uOHs7G6l9pOP0Bd6Z4s4DIeddG5MgpZkdn+vQNuGNbhZretg80Wj0lNZ2Oor/q0TSE0UoGZNEK1bZ3SHWqtY4J87aBkKGDcBCMqyLU1pGXBtpdJ8xoW+Ya6nM+I47jUoAJi8ChKDY8ZSKBoYsi1OuFNWl9xdn382rvpYtXqqBtA+mCAGJXiSFXUNkhSjlIFU/87v/4gsdFcAxMZVYxJVLdx2ldSyBnuAv9AwggOlBgkqhkiG9w0BBwGgggOWBIIDkjCCA44wggOKBgsqhkiG9w0BDAoBAqCCAqYwggKiMBwGCiqGSIb3DQEMAQMwDgQI44fv4XLfEhoCAggABIICgC+Cc/yNrM3ovTargtsTI2Ut8MzmLSIVPOgc7K77xwz7daXkJ5ucDRVfYEOzIlY0NfKsWqiYc+2vfZRqm6fBrpj1/1zhC+A6wzxxNY1BxVXDdLVvigNBvPNxj5Z+K8kFApi3tqUOpz6uzj9B6PMywETQ/lKIQ0PUVa5KRbx3JztFfGIXq+zoGuUSxzzVpLQQE7ON7qtUJbkAA7x/vwq4fKKxC4nxXwPSFaUi+S4m6JDQ4XS02RcK/m2NEzKxPQBFQMSbfkqJd/HrjWbY9msebdTPI8Q+o2rrnQ5K225IZCxqcOwa//108rdx7fDJz28ywSv3rBgPynb9/1iSpeQ25C1gl+skTvgQmz5U/7DzSJkLNSwFIcEZUSyYM4uWjtKHSaTgCkh/D3+7AvloQKNgNSKJ9WM053jzYaYRs11BKCYm7UG9v0cgUbI84GJFomrzxRcOfX0ps2UVnXMTq6kJrGB/X1xM5Quvn7kvuK+S0ZMTn1yHpFaOxdn0Z1On/Y05XWz86Y316WfkSrBeuqbH5HTI74F2yWl4K4PEerIyqX14s3oEGdtlJ24o/kAQTbCrntPFu3ZKxF4z5bkpO3bZwaURRLCmT3sLenlthsLysE2riUbacFl33mkaGTvBeqUOofHfO5LNJcE/J8YBzekewLFBcOY59WZkZBbUasPzkOomdZtkrzlzMjJ1pTCd5RCyretHP6j681Wq3+tDvR/ycrgKO+JY8kwIk8HB3BX+xRn6rFULAcLsUhsGbsZ6ig9yeXTCx2xh97Rh5A0pzSkv9A7UFT155amZ3cVJuPdruWj9yLQ9JEIi83q1olMh7mbaA3qKbYDnou+Aj0OlDySAo+MxgdAwDQYJKwYBBAGCNxECMQAwIwYJKoZIhvcNAQkVMRYEFGclVjS+gkQdguj0myihwM1yC/1bMC8GCSqGSIb3DQEJFDEiHiAAUABFAEEAUAAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZTBpBgkrBgEEAYI3EQExXB5aAE0AaQBjAHIAbwBzAG8AZgB0ACAAUgBTAEEAIABTAEMAaABhAG4AbgBlAGwAIABDAHIAeQBwAHQAbwBnAHIAYQBwAGgAaQBjACAAUAByAG8AdgBpAGQAZQByMDEwITAJBgUrDgMCGgUABBSerVeCcXV8OLmAwfi2hYXAmA5I3gQIHpTh4gRG/3MCAggA`,
+ // empty string password test case
+ "testing@example.com": `MIIJzgIBAzCCCZQGCSqGSIb3DQEHAaCCCYUEggmBMIIJfTCCA/cGCSqGSIb3DQEHBqCCA+gwggPk
+AgEAMIID3QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIIszfRGqcmPcCAggAgIIDsOZ9Eg1L
+s5Wx8JhYoV3HAL4aRnkAWvTYB5NISZOgSgIQTssmt/3A7134dibTmaT/93LikkL3cTKLnQzJ4wDf
+YZ1bprpVJvUqz+HFT79m27bP9zYXFrvxWBJbxjYKTSjQMgz+h8LAEpXXGajCmxMJ1oCOtdXkhhzc
+LdZN6SAYgtmtyFnCdMEDskSggGuLb3fw84QEJ/Sj6FAULXunW/CPaS7Ce0TMsKmNU/jfFWj3yXXw
+ro0kwjKiVLpVFlnBlHo2OoVU7hmkm59YpGhLgS7nxLD3n7nBroQ0ID1+8R01NnV9XLGoGzxMm1te
+6UyTCkr5mj+kEQ8EP1Ys7g/TC411uhVWySMt/rcpkx7Vz1r9kYEAzJpONAfr6cuEVkPKrxpq4Fh0
+2fzlKBky0i/hrfIEUmngh+ERHUb/Mtv/fkv1j5w9suESbhsMLLiCXAlsP1UWMX+3bNizi3WVMEts
+FM2k9byn+p8IUD/A8ULlE4kEaWeoc+2idkCNQkLGuIdGUXUFVm58se0auUkVRoRJx8x4CkMesT8j
+b1H831W66YRWoEwwDQp2kK1lA2vQXxdVHWlFevMNxJeromLzj3ayiaFrfByeUXhR2S+Hpm+c0yNR
+4UVU9WED2kacsZcpRm9nlEa5sr28mri5JdBrNa/K02OOhvKCxr5ZGmbOVzUQKla2z4w+Ku9k8POm
+dfDNU/fGx1b5hcFWtghXe3msWVsSJrQihnN6q1ughzNiYZlJUGcHdZDRtiWwCFI0bR8h/Dmg9uO9
+4rawQQrjIRT7B8yF3UbkZyAqs8Ppb1TsMeNPHh1rxEfGVQknh/48ouJYsmtbnzugTUt3mJCXXiL+
+XcPMV6bBVAUu4aaVKSmg9+yJtY4/VKv10iw88ktv29fViIdBe3t6l/oPuvQgbQ8dqf4T8w0l/uKZ
+9lS1Na9jfT1vCoS7F5TRi+tmyj1vL5kr/amEIW6xKEP6oeAMvCMtbPAzVEj38zdJ1R22FfuIBxkh
+f0Zl7pdVbmzRxl/SBx9iIBJSqAvcXItiT0FIj8HxQ+0iZKqMQMiBuNWJf5pYOLWGrIyntCWwHuaQ
+wrx0sTGuEL9YXLEAsBDrsvzLkx/56E4INGZFrH8G7HBdW6iGqb22IMI4GHltYSyBRKbB0gadYTyv
+abPEoqww8o7/85aPSzOTJ/53ozD438Q+d0u9SyDuOb60SzCD/zPuCEd78YgtXJwBYTuUNRT27FaM
+3LGMX8Hz+6yPNRnmnA2XKPn7dx/IlaqAjIs8MIIFfgYJKoZIhvcNAQcBoIIFbwSCBWswggVnMIIF
+YwYLKoZIhvcNAQwKAQKgggTuMIIE6jAcBgoqhkiG9w0BDAEDMA4ECJr0cClYqOlcAgIIAASCBMhe
+OQSiP2s0/46ONXcNeVAkz2ksW3u/+qorhSiskGZ0b3dFa1hhgBU2Q7JVIkc4Hf7OXaT1eVQ8oqND
+uhqsNz83/kqYo70+LS8Hocj49jFgWAKrf/yQkdyP1daHa2yzlEw4mkpqOfnIORQHvYCa8nEApspZ
+wVu8y6WVuLHKU67mel7db2xwstQp7PRuSAYqGjTfAylElog8ASdaqqYbYIrCXucF8iF9oVgmb/Qo
+xrXshJ9aSLO4MuXlTPELmWgj07AXKSb90FKNihE+y0bWb9LPVFY1Sly3AX9PfrtkSXIZwqW3phpv
+MxGxQl/R6mr1z+hlTfY9Wdpb5vlKXPKA0L0Rt8d2pOesylFi6esJoS01QgP1kJILjbrV731kvDc0
+Jsd+Oxv4BMwA7ClG8w1EAOInc/GrV1MWFGw/HeEqj3CZ/l/0jv9bwkbVeVCiIhoL6P6lVx9pXq4t
+KZ0uKg/tk5TVJmG2vLcMLvezD0Yk3G2ZOMrywtmskrwoF7oAUpO9e87szoH6fEvUZlkDkPVW1NV4
+cZk3DBSQiuA3VOOg8qbo/tx/EE3H59P0axZWno2GSB0wFPWd1aj+b//tJEJHaaNR6qPRj4IWj9ru
+Qbc8eRAcVWleHg8uAehSvUXlFpyMQREyrnpvMGddpiTC8N4UMrrBRhV7+UbCOWhxPCbItnInBqgl
+1JpSZIP7iUtsIMdu3fEC2cdbXMTRul+4rdzUR7F9OaezV3jjvcAbDvgbK1CpyC+MJ1Mxm/iTgk9V
+iUArydhlR8OniN84GyGYoYCW9O/KUwb6ASmeFOu/msx8x6kAsSQHIkKqMKv0TUR3kZnkxUvdpBGP
+KTl4YCTvNGX4dYALBqrAETRDhua2KVBD/kEttDHwBNVbN2xi81+Mc7ml461aADfk0c66R/m2sjHB
+2tN9+wG12OIWFQjL6wF/UfJMYamxx2zOOExiId29Opt57uYiNVLOO4ourPewHPeH0u8Gz35aero7
+lkt7cZAe1Q0038JUuE/QGlnK4lESK9UkSIQAjSaAlTsrcfwtQxB2EjoOoLhwH5mvxUEmcNGNnXUc
+9xj3M5BD3zBz3Ft7G3YMMDwB1+zC2l+0UG0MGVjMVaeoy32VVNvxgX7jk22OXG1iaOB+PY9kdk+O
+X+52BGSf/rD6X0EnqY7XuRPkMGgjtpZeAYxRQnFtCZgDY4wYheuxqSSpdF49yNczSPLkgB3CeCfS
++9NTKN7aC6hBbmW/8yYh6OvSiCEwY0lFS/T+7iaVxr1loE4zI1y/FFp4Pe1qfLlLttVlkygga2UU
+SCunTQ8UB/M5IXWKkhMOO11dP4niWwb39Y7pCWpau7mwbXOKfRPX96cgHnQJK5uG+BesDD1oYnX0
+6frN7FOnTSHKruRIwuI8KnOQ/I+owmyz71wiv5LMQt+yM47UrEjB/EZa5X8dpEwOZvkdqL7utcyo
+l0XH5kWMXdW856LL/FYftAqJIDAmtX1TXF/rbP6mPyN/IlDC0gjP84Uzd/a2UyTIWr+wk49Ek3vQ
+/uDamq6QrwAxVmNh5Tset5Vhpc1e1kb7mRMZIzxSP8JcTuYd45oFKi98I8YjvueHVZce1g7OudQP
+SbFQoJvdT46iBg1TTatlltpOiH2mFaxWVS0xYjAjBgkqhkiG9w0BCRUxFgQUdA9eVqvETX4an/c8
+p8SsTugkit8wOwYJKoZIhvcNAQkUMS4eLABGAHIAaQBlAG4AZABsAHkAIABuAGEAbQBlACAAZgBv
+AHIAIABjAGUAcgB0MDEwITAJBgUrDgMCGgUABBRFsNz3Zd1O1GI8GTuFwCWuDOjEEwQIuBEfIcAy
+HQ8CAggA`,
+}
diff --git a/local_crypto_patch/contents/pkcs12/safebags.go b/local_crypto_patch/contents/pkcs12/safebags.go
new file mode 100644
index 0000000000..def1f7b98d
--- /dev/null
+++ b/local_crypto_patch/contents/pkcs12/safebags.go
@@ -0,0 +1,57 @@
+// Copyright 2015 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 pkcs12
+
+import (
+ "crypto/x509"
+ "encoding/asn1"
+ "errors"
+)
+
+var (
+ // see https://tools.ietf.org/html/rfc7292#appendix-D
+ oidCertTypeX509Certificate = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 22, 1})
+ oidPKCS8ShroundedKeyBag = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 10, 1, 2})
+ oidCertBag = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 10, 1, 3})
+)
+
+type certBag struct {
+ Id asn1.ObjectIdentifier
+ Data []byte `asn1:"tag:0,explicit"`
+}
+
+func decodePkcs8ShroudedKeyBag(asn1Data, password []byte) (privateKey interface{}, err error) {
+ pkinfo := new(encryptedPrivateKeyInfo)
+ if err = unmarshal(asn1Data, pkinfo); err != nil {
+ return nil, errors.New("pkcs12: error decoding PKCS#8 shrouded key bag: " + err.Error())
+ }
+
+ pkData, err := pbDecrypt(pkinfo, password)
+ if err != nil {
+ return nil, errors.New("pkcs12: error decrypting PKCS#8 shrouded key bag: " + err.Error())
+ }
+
+ ret := new(asn1.RawValue)
+ if err = unmarshal(pkData, ret); err != nil {
+ return nil, errors.New("pkcs12: error unmarshaling decrypted private key: " + err.Error())
+ }
+
+ if privateKey, err = x509.ParsePKCS8PrivateKey(pkData); err != nil {
+ return nil, errors.New("pkcs12: error parsing PKCS#8 private key: " + err.Error())
+ }
+
+ return privateKey, nil
+}
+
+func decodeCertBag(asn1Data []byte) (x509Certificates []byte, err error) {
+ bag := new(certBag)
+ if err := unmarshal(asn1Data, bag); err != nil {
+ return nil, errors.New("pkcs12: error decoding cert bag: " + err.Error())
+ }
+ if !bag.Id.Equal(oidCertTypeX509Certificate) {
+ return nil, NotImplementedError("only X509 certificates are supported")
+ }
+ return bag.Data, nil
+}
diff --git a/local_crypto_patch/contents/poly1305/poly1305_compat.go b/local_crypto_patch/contents/poly1305/poly1305_compat.go
new file mode 100644
index 0000000000..cb9207f300
--- /dev/null
+++ b/local_crypto_patch/contents/poly1305/poly1305_compat.go
@@ -0,0 +1,91 @@
+// 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.
+//
+// Deprecated: Poly1305 as implemented by this package is a cryptographic
+// building block that is not safe for general purpose use.
+// For encryption, use the full ChaCha20-Poly1305 construction implemented by
+// golang.org/x/crypto/chacha20poly1305. For authentication, use a general
+// purpose MAC such as HMAC implemented by crypto/hmac.
+package poly1305
+
+import "golang.org/x/crypto/internal/poly1305"
+
+// TagSize is the size, in bytes, of a poly1305 authenticator.
+//
+// For use with golang.org/x/crypto/chacha20poly1305, chacha20poly1305.Overhead
+// can be used instead.
+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) {
+ poly1305.Sum(out, m, key)
+}
+
+// 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 {
+ return poly1305.Verify(mac, m, key)
+}
+
+// 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 {
+ return &MAC{mac: poly1305.New(key)}
+}
+
+// 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 *poly1305.MAC
+}
+
+// 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) {
+ 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 {
+ return h.mac.Sum(b)
+}
+
+// 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 {
+ return h.mac.Verify(expected)
+}
diff --git a/local_crypto_patch/contents/ripemd160/ripemd160.go b/local_crypto_patch/contents/ripemd160/ripemd160.go
new file mode 100644
index 0000000000..b6d33ef074
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ripemd160/ripemd160_test.go b/local_crypto_patch/contents/ripemd160/ripemd160_test.go
new file mode 100644
index 0000000000..a1fbffdd5f
--- /dev/null
+++ b/local_crypto_patch/contents/ripemd160/ripemd160_test.go
@@ -0,0 +1,72 @@
+// 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
+
+// Test vectors are from:
+// http://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+
+import (
+ "fmt"
+ "io"
+ "testing"
+)
+
+type mdTest struct {
+ out string
+ in string
+}
+
+var vectors = [...]mdTest{
+ {"9c1185a5c5e9fc54612808977ee8f548b2258d31", ""},
+ {"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", "a"},
+ {"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc", "abc"},
+ {"5d0689ef49d2fae572b881b123a85ffa21595f36", "message digest"},
+ {"f71c27109c692c1b56bbdceb5b9d2865b3708dbc", "abcdefghijklmnopqrstuvwxyz"},
+ {"12a053384a9c0c88e405a06c27dcf49ada62eb2b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+ {"b0e20b6e3116640286ed3a87a5713079b21f5189", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+ {"9b752e45573d4b39f4dbd3323cab82bf63326bfb", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+}
+
+func TestVectors(t *testing.T) {
+ for i := 0; i < len(vectors); i++ {
+ tv := vectors[i]
+ md := New()
+ for j := 0; j < 3; j++ {
+ if j < 2 {
+ io.WriteString(md, tv.in)
+ } else {
+ io.WriteString(md, tv.in[0:len(tv.in)/2])
+ md.Sum(nil)
+ io.WriteString(md, tv.in[len(tv.in)/2:])
+ }
+ s := fmt.Sprintf("%x", md.Sum(nil))
+ if s != tv.out {
+ t.Fatalf("RIPEMD-160[%d](%s) = %s, expected %s", j, tv.in, s, tv.out)
+ }
+ md.Reset()
+ }
+ }
+}
+
+func millionA() string {
+ md := New()
+ for i := 0; i < 100000; i++ {
+ io.WriteString(md, "aaaaaaaaaa")
+ }
+ return fmt.Sprintf("%x", md.Sum(nil))
+}
+
+func TestMillionA(t *testing.T) {
+ const out = "52783243c1697bdbe16d37f97f68f08325dc1528"
+ if s := millionA(); s != out {
+ t.Fatalf("RIPEMD-160 (1 million 'a') = %s, expected %s", s, out)
+ }
+}
+
+func BenchmarkMillionA(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ millionA()
+ }
+}
diff --git a/local_crypto_patch/contents/ripemd160/ripemd160block.go b/local_crypto_patch/contents/ripemd160/ripemd160block.go
new file mode 100644
index 0000000000..e0edc02f0f
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/salsa20/salsa/hsalsa20.go b/local_crypto_patch/contents/salsa20/salsa/hsalsa20.go
new file mode 100644
index 0000000000..75df77406d
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa/hsalsa20.go
@@ -0,0 +1,150 @@
+// 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 salsa provides low-level access to functions in the Salsa family.
+//
+// Deprecated: this package exposes unsafe low-level operations. New applications
+// should consider using the AEAD construction in golang.org/x/crypto/chacha20poly1305
+// instead. Existing users should migrate to golang.org/x/crypto/salsa20.
+package salsa
+
+import "math/bits"
+
+// Sigma is the Salsa20 constant for 256-bit keys.
+var Sigma = [16]byte{'e', 'x', 'p', 'a', 'n', 'd', ' ', '3', '2', '-', 'b', 'y', 't', 'e', ' ', 'k'}
+
+// HSalsa20 applies the HSalsa20 core function to a 16-byte input in, 32-byte
+// key k, and 16-byte constant c, and puts the result into the 32-byte array
+// out.
+func HSalsa20(out *[32]byte, in *[16]byte, k *[32]byte, c *[16]byte) {
+ x0 := uint32(c[0]) | uint32(c[1])<<8 | uint32(c[2])<<16 | uint32(c[3])<<24
+ x1 := uint32(k[0]) | uint32(k[1])<<8 | uint32(k[2])<<16 | uint32(k[3])<<24
+ x2 := uint32(k[4]) | uint32(k[5])<<8 | uint32(k[6])<<16 | uint32(k[7])<<24
+ x3 := uint32(k[8]) | uint32(k[9])<<8 | uint32(k[10])<<16 | uint32(k[11])<<24
+ x4 := uint32(k[12]) | uint32(k[13])<<8 | uint32(k[14])<<16 | uint32(k[15])<<24
+ x5 := uint32(c[4]) | uint32(c[5])<<8 | uint32(c[6])<<16 | uint32(c[7])<<24
+ x6 := uint32(in[0]) | uint32(in[1])<<8 | uint32(in[2])<<16 | uint32(in[3])<<24
+ x7 := uint32(in[4]) | uint32(in[5])<<8 | uint32(in[6])<<16 | uint32(in[7])<<24
+ x8 := uint32(in[8]) | uint32(in[9])<<8 | uint32(in[10])<<16 | uint32(in[11])<<24
+ x9 := uint32(in[12]) | uint32(in[13])<<8 | uint32(in[14])<<16 | uint32(in[15])<<24
+ x10 := uint32(c[8]) | uint32(c[9])<<8 | uint32(c[10])<<16 | uint32(c[11])<<24
+ x11 := uint32(k[16]) | uint32(k[17])<<8 | uint32(k[18])<<16 | uint32(k[19])<<24
+ x12 := uint32(k[20]) | uint32(k[21])<<8 | uint32(k[22])<<16 | uint32(k[23])<<24
+ x13 := uint32(k[24]) | uint32(k[25])<<8 | uint32(k[26])<<16 | uint32(k[27])<<24
+ x14 := uint32(k[28]) | uint32(k[29])<<8 | uint32(k[30])<<16 | uint32(k[31])<<24
+ x15 := uint32(c[12]) | uint32(c[13])<<8 | uint32(c[14])<<16 | uint32(c[15])<<24
+
+ for i := 0; i < 20; i += 2 {
+ u := x0 + x12
+ x4 ^= bits.RotateLeft32(u, 7)
+ u = x4 + x0
+ x8 ^= bits.RotateLeft32(u, 9)
+ u = x8 + x4
+ x12 ^= bits.RotateLeft32(u, 13)
+ u = x12 + x8
+ x0 ^= bits.RotateLeft32(u, 18)
+
+ u = x5 + x1
+ x9 ^= bits.RotateLeft32(u, 7)
+ u = x9 + x5
+ x13 ^= bits.RotateLeft32(u, 9)
+ u = x13 + x9
+ x1 ^= bits.RotateLeft32(u, 13)
+ u = x1 + x13
+ x5 ^= bits.RotateLeft32(u, 18)
+
+ u = x10 + x6
+ x14 ^= bits.RotateLeft32(u, 7)
+ u = x14 + x10
+ x2 ^= bits.RotateLeft32(u, 9)
+ u = x2 + x14
+ x6 ^= bits.RotateLeft32(u, 13)
+ u = x6 + x2
+ x10 ^= bits.RotateLeft32(u, 18)
+
+ u = x15 + x11
+ x3 ^= bits.RotateLeft32(u, 7)
+ u = x3 + x15
+ x7 ^= bits.RotateLeft32(u, 9)
+ u = x7 + x3
+ x11 ^= bits.RotateLeft32(u, 13)
+ u = x11 + x7
+ x15 ^= bits.RotateLeft32(u, 18)
+
+ u = x0 + x3
+ x1 ^= bits.RotateLeft32(u, 7)
+ u = x1 + x0
+ x2 ^= bits.RotateLeft32(u, 9)
+ u = x2 + x1
+ x3 ^= bits.RotateLeft32(u, 13)
+ u = x3 + x2
+ x0 ^= bits.RotateLeft32(u, 18)
+
+ u = x5 + x4
+ x6 ^= bits.RotateLeft32(u, 7)
+ u = x6 + x5
+ x7 ^= bits.RotateLeft32(u, 9)
+ u = x7 + x6
+ x4 ^= bits.RotateLeft32(u, 13)
+ u = x4 + x7
+ x5 ^= bits.RotateLeft32(u, 18)
+
+ u = x10 + x9
+ x11 ^= bits.RotateLeft32(u, 7)
+ u = x11 + x10
+ x8 ^= bits.RotateLeft32(u, 9)
+ u = x8 + x11
+ x9 ^= bits.RotateLeft32(u, 13)
+ u = x9 + x8
+ x10 ^= bits.RotateLeft32(u, 18)
+
+ u = x15 + x14
+ x12 ^= bits.RotateLeft32(u, 7)
+ u = x12 + x15
+ x13 ^= bits.RotateLeft32(u, 9)
+ u = x13 + x12
+ x14 ^= bits.RotateLeft32(u, 13)
+ u = x14 + x13
+ x15 ^= bits.RotateLeft32(u, 18)
+ }
+ out[0] = byte(x0)
+ out[1] = byte(x0 >> 8)
+ out[2] = byte(x0 >> 16)
+ out[3] = byte(x0 >> 24)
+
+ out[4] = byte(x5)
+ out[5] = byte(x5 >> 8)
+ out[6] = byte(x5 >> 16)
+ out[7] = byte(x5 >> 24)
+
+ out[8] = byte(x10)
+ out[9] = byte(x10 >> 8)
+ out[10] = byte(x10 >> 16)
+ out[11] = byte(x10 >> 24)
+
+ out[12] = byte(x15)
+ out[13] = byte(x15 >> 8)
+ out[14] = byte(x15 >> 16)
+ out[15] = byte(x15 >> 24)
+
+ out[16] = byte(x6)
+ out[17] = byte(x6 >> 8)
+ out[18] = byte(x6 >> 16)
+ out[19] = byte(x6 >> 24)
+
+ out[20] = byte(x7)
+ out[21] = byte(x7 >> 8)
+ out[22] = byte(x7 >> 16)
+ out[23] = byte(x7 >> 24)
+
+ out[24] = byte(x8)
+ out[25] = byte(x8 >> 8)
+ out[26] = byte(x8 >> 16)
+ out[27] = byte(x8 >> 24)
+
+ out[28] = byte(x9)
+ out[29] = byte(x9 >> 8)
+ out[30] = byte(x9 >> 16)
+ out[31] = byte(x9 >> 24)
+}
diff --git a/local_crypto_patch/contents/salsa20/salsa/salsa208.go b/local_crypto_patch/contents/salsa20/salsa/salsa208.go
new file mode 100644
index 0000000000..7ec7bb39bc
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa/salsa208.go
@@ -0,0 +1,201 @@
+// 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 salsa
+
+import "math/bits"
+
+// Core208 applies the Salsa20/8 core function to the 64-byte array in and puts
+// the result into the 64-byte array out. The input and output may be the same array.
+func Core208(out *[64]byte, in *[64]byte) {
+ j0 := uint32(in[0]) | uint32(in[1])<<8 | uint32(in[2])<<16 | uint32(in[3])<<24
+ j1 := uint32(in[4]) | uint32(in[5])<<8 | uint32(in[6])<<16 | uint32(in[7])<<24
+ j2 := uint32(in[8]) | uint32(in[9])<<8 | uint32(in[10])<<16 | uint32(in[11])<<24
+ j3 := uint32(in[12]) | uint32(in[13])<<8 | uint32(in[14])<<16 | uint32(in[15])<<24
+ j4 := uint32(in[16]) | uint32(in[17])<<8 | uint32(in[18])<<16 | uint32(in[19])<<24
+ j5 := uint32(in[20]) | uint32(in[21])<<8 | uint32(in[22])<<16 | uint32(in[23])<<24
+ j6 := uint32(in[24]) | uint32(in[25])<<8 | uint32(in[26])<<16 | uint32(in[27])<<24
+ j7 := uint32(in[28]) | uint32(in[29])<<8 | uint32(in[30])<<16 | uint32(in[31])<<24
+ j8 := uint32(in[32]) | uint32(in[33])<<8 | uint32(in[34])<<16 | uint32(in[35])<<24
+ j9 := uint32(in[36]) | uint32(in[37])<<8 | uint32(in[38])<<16 | uint32(in[39])<<24
+ j10 := uint32(in[40]) | uint32(in[41])<<8 | uint32(in[42])<<16 | uint32(in[43])<<24
+ j11 := uint32(in[44]) | uint32(in[45])<<8 | uint32(in[46])<<16 | uint32(in[47])<<24
+ j12 := uint32(in[48]) | uint32(in[49])<<8 | uint32(in[50])<<16 | uint32(in[51])<<24
+ j13 := uint32(in[52]) | uint32(in[53])<<8 | uint32(in[54])<<16 | uint32(in[55])<<24
+ j14 := uint32(in[56]) | uint32(in[57])<<8 | uint32(in[58])<<16 | uint32(in[59])<<24
+ j15 := uint32(in[60]) | uint32(in[61])<<8 | uint32(in[62])<<16 | uint32(in[63])<<24
+
+ x0, x1, x2, x3, x4, x5, x6, x7, x8 := j0, j1, j2, j3, j4, j5, j6, j7, j8
+ x9, x10, x11, x12, x13, x14, x15 := j9, j10, j11, j12, j13, j14, j15
+
+ for i := 0; i < 8; i += 2 {
+ u := x0 + x12
+ x4 ^= bits.RotateLeft32(u, 7)
+ u = x4 + x0
+ x8 ^= bits.RotateLeft32(u, 9)
+ u = x8 + x4
+ x12 ^= bits.RotateLeft32(u, 13)
+ u = x12 + x8
+ x0 ^= bits.RotateLeft32(u, 18)
+
+ u = x5 + x1
+ x9 ^= bits.RotateLeft32(u, 7)
+ u = x9 + x5
+ x13 ^= bits.RotateLeft32(u, 9)
+ u = x13 + x9
+ x1 ^= bits.RotateLeft32(u, 13)
+ u = x1 + x13
+ x5 ^= bits.RotateLeft32(u, 18)
+
+ u = x10 + x6
+ x14 ^= bits.RotateLeft32(u, 7)
+ u = x14 + x10
+ x2 ^= bits.RotateLeft32(u, 9)
+ u = x2 + x14
+ x6 ^= bits.RotateLeft32(u, 13)
+ u = x6 + x2
+ x10 ^= bits.RotateLeft32(u, 18)
+
+ u = x15 + x11
+ x3 ^= bits.RotateLeft32(u, 7)
+ u = x3 + x15
+ x7 ^= bits.RotateLeft32(u, 9)
+ u = x7 + x3
+ x11 ^= bits.RotateLeft32(u, 13)
+ u = x11 + x7
+ x15 ^= bits.RotateLeft32(u, 18)
+
+ u = x0 + x3
+ x1 ^= bits.RotateLeft32(u, 7)
+ u = x1 + x0
+ x2 ^= bits.RotateLeft32(u, 9)
+ u = x2 + x1
+ x3 ^= bits.RotateLeft32(u, 13)
+ u = x3 + x2
+ x0 ^= bits.RotateLeft32(u, 18)
+
+ u = x5 + x4
+ x6 ^= bits.RotateLeft32(u, 7)
+ u = x6 + x5
+ x7 ^= bits.RotateLeft32(u, 9)
+ u = x7 + x6
+ x4 ^= bits.RotateLeft32(u, 13)
+ u = x4 + x7
+ x5 ^= bits.RotateLeft32(u, 18)
+
+ u = x10 + x9
+ x11 ^= bits.RotateLeft32(u, 7)
+ u = x11 + x10
+ x8 ^= bits.RotateLeft32(u, 9)
+ u = x8 + x11
+ x9 ^= bits.RotateLeft32(u, 13)
+ u = x9 + x8
+ x10 ^= bits.RotateLeft32(u, 18)
+
+ u = x15 + x14
+ x12 ^= bits.RotateLeft32(u, 7)
+ u = x12 + x15
+ x13 ^= bits.RotateLeft32(u, 9)
+ u = x13 + x12
+ x14 ^= bits.RotateLeft32(u, 13)
+ u = x14 + x13
+ x15 ^= bits.RotateLeft32(u, 18)
+ }
+ x0 += j0
+ x1 += j1
+ x2 += j2
+ x3 += j3
+ x4 += j4
+ x5 += j5
+ x6 += j6
+ x7 += j7
+ x8 += j8
+ x9 += j9
+ x10 += j10
+ x11 += j11
+ x12 += j12
+ x13 += j13
+ x14 += j14
+ x15 += j15
+
+ out[0] = byte(x0)
+ out[1] = byte(x0 >> 8)
+ out[2] = byte(x0 >> 16)
+ out[3] = byte(x0 >> 24)
+
+ out[4] = byte(x1)
+ out[5] = byte(x1 >> 8)
+ out[6] = byte(x1 >> 16)
+ out[7] = byte(x1 >> 24)
+
+ out[8] = byte(x2)
+ out[9] = byte(x2 >> 8)
+ out[10] = byte(x2 >> 16)
+ out[11] = byte(x2 >> 24)
+
+ out[12] = byte(x3)
+ out[13] = byte(x3 >> 8)
+ out[14] = byte(x3 >> 16)
+ out[15] = byte(x3 >> 24)
+
+ out[16] = byte(x4)
+ out[17] = byte(x4 >> 8)
+ out[18] = byte(x4 >> 16)
+ out[19] = byte(x4 >> 24)
+
+ out[20] = byte(x5)
+ out[21] = byte(x5 >> 8)
+ out[22] = byte(x5 >> 16)
+ out[23] = byte(x5 >> 24)
+
+ out[24] = byte(x6)
+ out[25] = byte(x6 >> 8)
+ out[26] = byte(x6 >> 16)
+ out[27] = byte(x6 >> 24)
+
+ out[28] = byte(x7)
+ out[29] = byte(x7 >> 8)
+ out[30] = byte(x7 >> 16)
+ out[31] = byte(x7 >> 24)
+
+ out[32] = byte(x8)
+ out[33] = byte(x8 >> 8)
+ out[34] = byte(x8 >> 16)
+ out[35] = byte(x8 >> 24)
+
+ out[36] = byte(x9)
+ out[37] = byte(x9 >> 8)
+ out[38] = byte(x9 >> 16)
+ out[39] = byte(x9 >> 24)
+
+ out[40] = byte(x10)
+ out[41] = byte(x10 >> 8)
+ out[42] = byte(x10 >> 16)
+ out[43] = byte(x10 >> 24)
+
+ out[44] = byte(x11)
+ out[45] = byte(x11 >> 8)
+ out[46] = byte(x11 >> 16)
+ out[47] = byte(x11 >> 24)
+
+ out[48] = byte(x12)
+ out[49] = byte(x12 >> 8)
+ out[50] = byte(x12 >> 16)
+ out[51] = byte(x12 >> 24)
+
+ out[52] = byte(x13)
+ out[53] = byte(x13 >> 8)
+ out[54] = byte(x13 >> 16)
+ out[55] = byte(x13 >> 24)
+
+ out[56] = byte(x14)
+ out[57] = byte(x14 >> 8)
+ out[58] = byte(x14 >> 16)
+ out[59] = byte(x14 >> 24)
+
+ out[60] = byte(x15)
+ out[61] = byte(x15 >> 8)
+ out[62] = byte(x15 >> 16)
+ out[63] = byte(x15 >> 24)
+}
diff --git a/local_crypto_patch/contents/salsa20/salsa/salsa20_amd64.go b/local_crypto_patch/contents/salsa20/salsa/salsa20_amd64.go
new file mode 100644
index 0000000000..e76b44fe59
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa/salsa20_amd64.go
@@ -0,0 +1,23 @@
+// 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 amd64 && !purego && gc
+
+package salsa
+
+//go:noescape
+
+// salsa2020XORKeyStream is implemented in salsa20_amd64.s.
+func salsa2020XORKeyStream(out, in *byte, n uint64, nonce, key *byte)
+
+// XORKeyStream crypts bytes from in to out using the given key and counters.
+// In and out must overlap entirely or not at all. Counter
+// contains the raw salsa20 counter bytes (both nonce and block counter).
+func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
+ if len(in) == 0 {
+ return
+ }
+ _ = out[len(in)-1]
+ salsa2020XORKeyStream(&out[0], &in[0], uint64(len(in)), &counter[0], &key[0])
+}
diff --git a/local_crypto_patch/contents/salsa20/salsa/salsa20_amd64.s b/local_crypto_patch/contents/salsa20/salsa/salsa20_amd64.s
new file mode 100644
index 0000000000..3883e0ec22
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa/salsa20_amd64.s
@@ -0,0 +1,880 @@
+// Code generated by command: go run salsa20_amd64_asm.go -out ../salsa20_amd64.s -pkg salsa. DO NOT EDIT.
+
+//go:build amd64 && !purego && gc
+
+// func salsa2020XORKeyStream(out *byte, in *byte, n uint64, nonce *byte, key *byte)
+// Requires: SSE2
+TEXT ·salsa2020XORKeyStream(SB), $456-40
+ // This needs up to 64 bytes at 360(R12); hence the non-obvious frame size.
+ MOVQ out+0(FP), DI
+ MOVQ in+8(FP), SI
+ MOVQ n+16(FP), DX
+ MOVQ nonce+24(FP), CX
+ MOVQ key+32(FP), R8
+ MOVQ SP, R12
+ ADDQ $0x1f, R12
+ ANDQ $-32, R12
+ MOVQ DX, R9
+ MOVQ CX, DX
+ MOVQ R8, R10
+ CMPQ R9, $0x00
+ JBE DONE
+ MOVL 20(R10), CX
+ MOVL (R10), R8
+ MOVL (DX), AX
+ MOVL 16(R10), R11
+ MOVL CX, (R12)
+ MOVL R8, 4(R12)
+ MOVL AX, 8(R12)
+ MOVL R11, 12(R12)
+ MOVL 8(DX), CX
+ MOVL 24(R10), R8
+ MOVL 4(R10), AX
+ MOVL 4(DX), R11
+ MOVL CX, 16(R12)
+ MOVL R8, 20(R12)
+ MOVL AX, 24(R12)
+ MOVL R11, 28(R12)
+ MOVL 12(DX), CX
+ MOVL 12(R10), DX
+ MOVL 28(R10), R8
+ MOVL 8(R10), AX
+ MOVL DX, 32(R12)
+ MOVL CX, 36(R12)
+ MOVL R8, 40(R12)
+ MOVL AX, 44(R12)
+ MOVQ $0x61707865, DX
+ MOVQ $0x3320646e, CX
+ MOVQ $0x79622d32, R8
+ MOVQ $0x6b206574, AX
+ MOVL DX, 48(R12)
+ MOVL CX, 52(R12)
+ MOVL R8, 56(R12)
+ MOVL AX, 60(R12)
+ CMPQ R9, $0x00000100
+ JB BYTESBETWEEN1AND255
+ MOVOA 48(R12), X0
+ PSHUFL $0x55, X0, X1
+ PSHUFL $0xaa, X0, X2
+ PSHUFL $0xff, X0, X3
+ PSHUFL $0x00, X0, X0
+ MOVOA X1, 64(R12)
+ MOVOA X2, 80(R12)
+ MOVOA X3, 96(R12)
+ MOVOA X0, 112(R12)
+ MOVOA (R12), X0
+ PSHUFL $0xaa, X0, X1
+ PSHUFL $0xff, X0, X2
+ PSHUFL $0x00, X0, X3
+ PSHUFL $0x55, X0, X0
+ MOVOA X1, 128(R12)
+ MOVOA X2, 144(R12)
+ MOVOA X3, 160(R12)
+ MOVOA X0, 176(R12)
+ MOVOA 16(R12), X0
+ PSHUFL $0xff, X0, X1
+ PSHUFL $0x55, X0, X2
+ PSHUFL $0xaa, X0, X0
+ MOVOA X1, 192(R12)
+ MOVOA X2, 208(R12)
+ MOVOA X0, 224(R12)
+ MOVOA 32(R12), X0
+ PSHUFL $0x00, X0, X1
+ PSHUFL $0xaa, X0, X2
+ PSHUFL $0xff, X0, X0
+ MOVOA X1, 240(R12)
+ MOVOA X2, 256(R12)
+ MOVOA X0, 272(R12)
+
+BYTESATLEAST256:
+ MOVL 16(R12), DX
+ MOVL 36(R12), CX
+ MOVL DX, 288(R12)
+ MOVL CX, 304(R12)
+ SHLQ $0x20, CX
+ ADDQ CX, DX
+ ADDQ $0x01, DX
+ MOVQ DX, CX
+ SHRQ $0x20, CX
+ MOVL DX, 292(R12)
+ MOVL CX, 308(R12)
+ ADDQ $0x01, DX
+ MOVQ DX, CX
+ SHRQ $0x20, CX
+ MOVL DX, 296(R12)
+ MOVL CX, 312(R12)
+ ADDQ $0x01, DX
+ MOVQ DX, CX
+ SHRQ $0x20, CX
+ MOVL DX, 300(R12)
+ MOVL CX, 316(R12)
+ ADDQ $0x01, DX
+ MOVQ DX, CX
+ SHRQ $0x20, CX
+ MOVL DX, 16(R12)
+ MOVL CX, 36(R12)
+ MOVQ R9, 352(R12)
+ MOVQ $0x00000014, DX
+ MOVOA 64(R12), X0
+ MOVOA 80(R12), X1
+ MOVOA 96(R12), X2
+ MOVOA 256(R12), X3
+ MOVOA 272(R12), X4
+ MOVOA 128(R12), X5
+ MOVOA 144(R12), X6
+ MOVOA 176(R12), X7
+ MOVOA 192(R12), X8
+ MOVOA 208(R12), X9
+ MOVOA 224(R12), X10
+ MOVOA 304(R12), X11
+ MOVOA 112(R12), X12
+ MOVOA 160(R12), X13
+ MOVOA 240(R12), X14
+ MOVOA 288(R12), X15
+
+MAINLOOP1:
+ MOVOA X1, 320(R12)
+ MOVOA X2, 336(R12)
+ MOVOA X13, X1
+ PADDL X12, X1
+ MOVOA X1, X2
+ PSLLL $0x07, X1
+ PXOR X1, X14
+ PSRLL $0x19, X2
+ PXOR X2, X14
+ MOVOA X7, X1
+ PADDL X0, X1
+ MOVOA X1, X2
+ PSLLL $0x07, X1
+ PXOR X1, X11
+ PSRLL $0x19, X2
+ PXOR X2, X11
+ MOVOA X12, X1
+ PADDL X14, X1
+ MOVOA X1, X2
+ PSLLL $0x09, X1
+ PXOR X1, X15
+ PSRLL $0x17, X2
+ PXOR X2, X15
+ MOVOA X0, X1
+ PADDL X11, X1
+ MOVOA X1, X2
+ PSLLL $0x09, X1
+ PXOR X1, X9
+ PSRLL $0x17, X2
+ PXOR X2, X9
+ MOVOA X14, X1
+ PADDL X15, X1
+ MOVOA X1, X2
+ PSLLL $0x0d, X1
+ PXOR X1, X13
+ PSRLL $0x13, X2
+ PXOR X2, X13
+ MOVOA X11, X1
+ PADDL X9, X1
+ MOVOA X1, X2
+ PSLLL $0x0d, X1
+ PXOR X1, X7
+ PSRLL $0x13, X2
+ PXOR X2, X7
+ MOVOA X15, X1
+ PADDL X13, X1
+ MOVOA X1, X2
+ PSLLL $0x12, X1
+ PXOR X1, X12
+ PSRLL $0x0e, X2
+ PXOR X2, X12
+ MOVOA 320(R12), X1
+ MOVOA X12, 320(R12)
+ MOVOA X9, X2
+ PADDL X7, X2
+ MOVOA X2, X12
+ PSLLL $0x12, X2
+ PXOR X2, X0
+ PSRLL $0x0e, X12
+ PXOR X12, X0
+ MOVOA X5, X2
+ PADDL X1, X2
+ MOVOA X2, X12
+ PSLLL $0x07, X2
+ PXOR X2, X3
+ PSRLL $0x19, X12
+ PXOR X12, X3
+ MOVOA 336(R12), X2
+ MOVOA X0, 336(R12)
+ MOVOA X6, X0
+ PADDL X2, X0
+ MOVOA X0, X12
+ PSLLL $0x07, X0
+ PXOR X0, X4
+ PSRLL $0x19, X12
+ PXOR X12, X4
+ MOVOA X1, X0
+ PADDL X3, X0
+ MOVOA X0, X12
+ PSLLL $0x09, X0
+ PXOR X0, X10
+ PSRLL $0x17, X12
+ PXOR X12, X10
+ MOVOA X2, X0
+ PADDL X4, X0
+ MOVOA X0, X12
+ PSLLL $0x09, X0
+ PXOR X0, X8
+ PSRLL $0x17, X12
+ PXOR X12, X8
+ MOVOA X3, X0
+ PADDL X10, X0
+ MOVOA X0, X12
+ PSLLL $0x0d, X0
+ PXOR X0, X5
+ PSRLL $0x13, X12
+ PXOR X12, X5
+ MOVOA X4, X0
+ PADDL X8, X0
+ MOVOA X0, X12
+ PSLLL $0x0d, X0
+ PXOR X0, X6
+ PSRLL $0x13, X12
+ PXOR X12, X6
+ MOVOA X10, X0
+ PADDL X5, X0
+ MOVOA X0, X12
+ PSLLL $0x12, X0
+ PXOR X0, X1
+ PSRLL $0x0e, X12
+ PXOR X12, X1
+ MOVOA 320(R12), X0
+ MOVOA X1, 320(R12)
+ MOVOA X4, X1
+ PADDL X0, X1
+ MOVOA X1, X12
+ PSLLL $0x07, X1
+ PXOR X1, X7
+ PSRLL $0x19, X12
+ PXOR X12, X7
+ MOVOA X8, X1
+ PADDL X6, X1
+ MOVOA X1, X12
+ PSLLL $0x12, X1
+ PXOR X1, X2
+ PSRLL $0x0e, X12
+ PXOR X12, X2
+ MOVOA 336(R12), X12
+ MOVOA X2, 336(R12)
+ MOVOA X14, X1
+ PADDL X12, X1
+ MOVOA X1, X2
+ PSLLL $0x07, X1
+ PXOR X1, X5
+ PSRLL $0x19, X2
+ PXOR X2, X5
+ MOVOA X0, X1
+ PADDL X7, X1
+ MOVOA X1, X2
+ PSLLL $0x09, X1
+ PXOR X1, X10
+ PSRLL $0x17, X2
+ PXOR X2, X10
+ MOVOA X12, X1
+ PADDL X5, X1
+ MOVOA X1, X2
+ PSLLL $0x09, X1
+ PXOR X1, X8
+ PSRLL $0x17, X2
+ PXOR X2, X8
+ MOVOA X7, X1
+ PADDL X10, X1
+ MOVOA X1, X2
+ PSLLL $0x0d, X1
+ PXOR X1, X4
+ PSRLL $0x13, X2
+ PXOR X2, X4
+ MOVOA X5, X1
+ PADDL X8, X1
+ MOVOA X1, X2
+ PSLLL $0x0d, X1
+ PXOR X1, X14
+ PSRLL $0x13, X2
+ PXOR X2, X14
+ MOVOA X10, X1
+ PADDL X4, X1
+ MOVOA X1, X2
+ PSLLL $0x12, X1
+ PXOR X1, X0
+ PSRLL $0x0e, X2
+ PXOR X2, X0
+ MOVOA 320(R12), X1
+ MOVOA X0, 320(R12)
+ MOVOA X8, X0
+ PADDL X14, X0
+ MOVOA X0, X2
+ PSLLL $0x12, X0
+ PXOR X0, X12
+ PSRLL $0x0e, X2
+ PXOR X2, X12
+ MOVOA X11, X0
+ PADDL X1, X0
+ MOVOA X0, X2
+ PSLLL $0x07, X0
+ PXOR X0, X6
+ PSRLL $0x19, X2
+ PXOR X2, X6
+ MOVOA 336(R12), X2
+ MOVOA X12, 336(R12)
+ MOVOA X3, X0
+ PADDL X2, X0
+ MOVOA X0, X12
+ PSLLL $0x07, X0
+ PXOR X0, X13
+ PSRLL $0x19, X12
+ PXOR X12, X13
+ MOVOA X1, X0
+ PADDL X6, X0
+ MOVOA X0, X12
+ PSLLL $0x09, X0
+ PXOR X0, X15
+ PSRLL $0x17, X12
+ PXOR X12, X15
+ MOVOA X2, X0
+ PADDL X13, X0
+ MOVOA X0, X12
+ PSLLL $0x09, X0
+ PXOR X0, X9
+ PSRLL $0x17, X12
+ PXOR X12, X9
+ MOVOA X6, X0
+ PADDL X15, X0
+ MOVOA X0, X12
+ PSLLL $0x0d, X0
+ PXOR X0, X11
+ PSRLL $0x13, X12
+ PXOR X12, X11
+ MOVOA X13, X0
+ PADDL X9, X0
+ MOVOA X0, X12
+ PSLLL $0x0d, X0
+ PXOR X0, X3
+ PSRLL $0x13, X12
+ PXOR X12, X3
+ MOVOA X15, X0
+ PADDL X11, X0
+ MOVOA X0, X12
+ PSLLL $0x12, X0
+ PXOR X0, X1
+ PSRLL $0x0e, X12
+ PXOR X12, X1
+ MOVOA X9, X0
+ PADDL X3, X0
+ MOVOA X0, X12
+ PSLLL $0x12, X0
+ PXOR X0, X2
+ PSRLL $0x0e, X12
+ PXOR X12, X2
+ MOVOA 320(R12), X12
+ MOVOA 336(R12), X0
+ SUBQ $0x02, DX
+ JA MAINLOOP1
+ PADDL 112(R12), X12
+ PADDL 176(R12), X7
+ PADDL 224(R12), X10
+ PADDL 272(R12), X4
+ MOVD X12, DX
+ MOVD X7, CX
+ MOVD X10, R8
+ MOVD X4, R9
+ PSHUFL $0x39, X12, X12
+ PSHUFL $0x39, X7, X7
+ PSHUFL $0x39, X10, X10
+ PSHUFL $0x39, X4, X4
+ XORL (SI), DX
+ XORL 4(SI), CX
+ XORL 8(SI), R8
+ XORL 12(SI), R9
+ MOVL DX, (DI)
+ MOVL CX, 4(DI)
+ MOVL R8, 8(DI)
+ MOVL R9, 12(DI)
+ MOVD X12, DX
+ MOVD X7, CX
+ MOVD X10, R8
+ MOVD X4, R9
+ PSHUFL $0x39, X12, X12
+ PSHUFL $0x39, X7, X7
+ PSHUFL $0x39, X10, X10
+ PSHUFL $0x39, X4, X4
+ XORL 64(SI), DX
+ XORL 68(SI), CX
+ XORL 72(SI), R8
+ XORL 76(SI), R9
+ MOVL DX, 64(DI)
+ MOVL CX, 68(DI)
+ MOVL R8, 72(DI)
+ MOVL R9, 76(DI)
+ MOVD X12, DX
+ MOVD X7, CX
+ MOVD X10, R8
+ MOVD X4, R9
+ PSHUFL $0x39, X12, X12
+ PSHUFL $0x39, X7, X7
+ PSHUFL $0x39, X10, X10
+ PSHUFL $0x39, X4, X4
+ XORL 128(SI), DX
+ XORL 132(SI), CX
+ XORL 136(SI), R8
+ XORL 140(SI), R9
+ MOVL DX, 128(DI)
+ MOVL CX, 132(DI)
+ MOVL R8, 136(DI)
+ MOVL R9, 140(DI)
+ MOVD X12, DX
+ MOVD X7, CX
+ MOVD X10, R8
+ MOVD X4, R9
+ XORL 192(SI), DX
+ XORL 196(SI), CX
+ XORL 200(SI), R8
+ XORL 204(SI), R9
+ MOVL DX, 192(DI)
+ MOVL CX, 196(DI)
+ MOVL R8, 200(DI)
+ MOVL R9, 204(DI)
+ PADDL 240(R12), X14
+ PADDL 64(R12), X0
+ PADDL 128(R12), X5
+ PADDL 192(R12), X8
+ MOVD X14, DX
+ MOVD X0, CX
+ MOVD X5, R8
+ MOVD X8, R9
+ PSHUFL $0x39, X14, X14
+ PSHUFL $0x39, X0, X0
+ PSHUFL $0x39, X5, X5
+ PSHUFL $0x39, X8, X8
+ XORL 16(SI), DX
+ XORL 20(SI), CX
+ XORL 24(SI), R8
+ XORL 28(SI), R9
+ MOVL DX, 16(DI)
+ MOVL CX, 20(DI)
+ MOVL R8, 24(DI)
+ MOVL R9, 28(DI)
+ MOVD X14, DX
+ MOVD X0, CX
+ MOVD X5, R8
+ MOVD X8, R9
+ PSHUFL $0x39, X14, X14
+ PSHUFL $0x39, X0, X0
+ PSHUFL $0x39, X5, X5
+ PSHUFL $0x39, X8, X8
+ XORL 80(SI), DX
+ XORL 84(SI), CX
+ XORL 88(SI), R8
+ XORL 92(SI), R9
+ MOVL DX, 80(DI)
+ MOVL CX, 84(DI)
+ MOVL R8, 88(DI)
+ MOVL R9, 92(DI)
+ MOVD X14, DX
+ MOVD X0, CX
+ MOVD X5, R8
+ MOVD X8, R9
+ PSHUFL $0x39, X14, X14
+ PSHUFL $0x39, X0, X0
+ PSHUFL $0x39, X5, X5
+ PSHUFL $0x39, X8, X8
+ XORL 144(SI), DX
+ XORL 148(SI), CX
+ XORL 152(SI), R8
+ XORL 156(SI), R9
+ MOVL DX, 144(DI)
+ MOVL CX, 148(DI)
+ MOVL R8, 152(DI)
+ MOVL R9, 156(DI)
+ MOVD X14, DX
+ MOVD X0, CX
+ MOVD X5, R8
+ MOVD X8, R9
+ XORL 208(SI), DX
+ XORL 212(SI), CX
+ XORL 216(SI), R8
+ XORL 220(SI), R9
+ MOVL DX, 208(DI)
+ MOVL CX, 212(DI)
+ MOVL R8, 216(DI)
+ MOVL R9, 220(DI)
+ PADDL 288(R12), X15
+ PADDL 304(R12), X11
+ PADDL 80(R12), X1
+ PADDL 144(R12), X6
+ MOVD X15, DX
+ MOVD X11, CX
+ MOVD X1, R8
+ MOVD X6, R9
+ PSHUFL $0x39, X15, X15
+ PSHUFL $0x39, X11, X11
+ PSHUFL $0x39, X1, X1
+ PSHUFL $0x39, X6, X6
+ XORL 32(SI), DX
+ XORL 36(SI), CX
+ XORL 40(SI), R8
+ XORL 44(SI), R9
+ MOVL DX, 32(DI)
+ MOVL CX, 36(DI)
+ MOVL R8, 40(DI)
+ MOVL R9, 44(DI)
+ MOVD X15, DX
+ MOVD X11, CX
+ MOVD X1, R8
+ MOVD X6, R9
+ PSHUFL $0x39, X15, X15
+ PSHUFL $0x39, X11, X11
+ PSHUFL $0x39, X1, X1
+ PSHUFL $0x39, X6, X6
+ XORL 96(SI), DX
+ XORL 100(SI), CX
+ XORL 104(SI), R8
+ XORL 108(SI), R9
+ MOVL DX, 96(DI)
+ MOVL CX, 100(DI)
+ MOVL R8, 104(DI)
+ MOVL R9, 108(DI)
+ MOVD X15, DX
+ MOVD X11, CX
+ MOVD X1, R8
+ MOVD X6, R9
+ PSHUFL $0x39, X15, X15
+ PSHUFL $0x39, X11, X11
+ PSHUFL $0x39, X1, X1
+ PSHUFL $0x39, X6, X6
+ XORL 160(SI), DX
+ XORL 164(SI), CX
+ XORL 168(SI), R8
+ XORL 172(SI), R9
+ MOVL DX, 160(DI)
+ MOVL CX, 164(DI)
+ MOVL R8, 168(DI)
+ MOVL R9, 172(DI)
+ MOVD X15, DX
+ MOVD X11, CX
+ MOVD X1, R8
+ MOVD X6, R9
+ XORL 224(SI), DX
+ XORL 228(SI), CX
+ XORL 232(SI), R8
+ XORL 236(SI), R9
+ MOVL DX, 224(DI)
+ MOVL CX, 228(DI)
+ MOVL R8, 232(DI)
+ MOVL R9, 236(DI)
+ PADDL 160(R12), X13
+ PADDL 208(R12), X9
+ PADDL 256(R12), X3
+ PADDL 96(R12), X2
+ MOVD X13, DX
+ MOVD X9, CX
+ MOVD X3, R8
+ MOVD X2, R9
+ PSHUFL $0x39, X13, X13
+ PSHUFL $0x39, X9, X9
+ PSHUFL $0x39, X3, X3
+ PSHUFL $0x39, X2, X2
+ XORL 48(SI), DX
+ XORL 52(SI), CX
+ XORL 56(SI), R8
+ XORL 60(SI), R9
+ MOVL DX, 48(DI)
+ MOVL CX, 52(DI)
+ MOVL R8, 56(DI)
+ MOVL R9, 60(DI)
+ MOVD X13, DX
+ MOVD X9, CX
+ MOVD X3, R8
+ MOVD X2, R9
+ PSHUFL $0x39, X13, X13
+ PSHUFL $0x39, X9, X9
+ PSHUFL $0x39, X3, X3
+ PSHUFL $0x39, X2, X2
+ XORL 112(SI), DX
+ XORL 116(SI), CX
+ XORL 120(SI), R8
+ XORL 124(SI), R9
+ MOVL DX, 112(DI)
+ MOVL CX, 116(DI)
+ MOVL R8, 120(DI)
+ MOVL R9, 124(DI)
+ MOVD X13, DX
+ MOVD X9, CX
+ MOVD X3, R8
+ MOVD X2, R9
+ PSHUFL $0x39, X13, X13
+ PSHUFL $0x39, X9, X9
+ PSHUFL $0x39, X3, X3
+ PSHUFL $0x39, X2, X2
+ XORL 176(SI), DX
+ XORL 180(SI), CX
+ XORL 184(SI), R8
+ XORL 188(SI), R9
+ MOVL DX, 176(DI)
+ MOVL CX, 180(DI)
+ MOVL R8, 184(DI)
+ MOVL R9, 188(DI)
+ MOVD X13, DX
+ MOVD X9, CX
+ MOVD X3, R8
+ MOVD X2, R9
+ XORL 240(SI), DX
+ XORL 244(SI), CX
+ XORL 248(SI), R8
+ XORL 252(SI), R9
+ MOVL DX, 240(DI)
+ MOVL CX, 244(DI)
+ MOVL R8, 248(DI)
+ MOVL R9, 252(DI)
+ MOVQ 352(R12), R9
+ SUBQ $0x00000100, R9
+ ADDQ $0x00000100, SI
+ ADDQ $0x00000100, DI
+ CMPQ R9, $0x00000100
+ JAE BYTESATLEAST256
+ CMPQ R9, $0x00
+ JBE DONE
+
+BYTESBETWEEN1AND255:
+ CMPQ R9, $0x40
+ JAE NOCOPY
+ MOVQ DI, DX
+ LEAQ 360(R12), DI
+ MOVQ R9, CX
+ REP; MOVSB
+ LEAQ 360(R12), DI
+ LEAQ 360(R12), SI
+
+NOCOPY:
+ MOVQ R9, 352(R12)
+ MOVOA 48(R12), X0
+ MOVOA (R12), X1
+ MOVOA 16(R12), X2
+ MOVOA 32(R12), X3
+ MOVOA X1, X4
+ MOVQ $0x00000014, CX
+
+MAINLOOP2:
+ PADDL X0, X4
+ MOVOA X0, X5
+ MOVOA X4, X6
+ PSLLL $0x07, X4
+ PSRLL $0x19, X6
+ PXOR X4, X3
+ PXOR X6, X3
+ PADDL X3, X5
+ MOVOA X3, X4
+ MOVOA X5, X6
+ PSLLL $0x09, X5
+ PSRLL $0x17, X6
+ PXOR X5, X2
+ PSHUFL $0x93, X3, X3
+ PXOR X6, X2
+ PADDL X2, X4
+ MOVOA X2, X5
+ MOVOA X4, X6
+ PSLLL $0x0d, X4
+ PSRLL $0x13, X6
+ PXOR X4, X1
+ PSHUFL $0x4e, X2, X2
+ PXOR X6, X1
+ PADDL X1, X5
+ MOVOA X3, X4
+ MOVOA X5, X6
+ PSLLL $0x12, X5
+ PSRLL $0x0e, X6
+ PXOR X5, X0
+ PSHUFL $0x39, X1, X1
+ PXOR X6, X0
+ PADDL X0, X4
+ MOVOA X0, X5
+ MOVOA X4, X6
+ PSLLL $0x07, X4
+ PSRLL $0x19, X6
+ PXOR X4, X1
+ PXOR X6, X1
+ PADDL X1, X5
+ MOVOA X1, X4
+ MOVOA X5, X6
+ PSLLL $0x09, X5
+ PSRLL $0x17, X6
+ PXOR X5, X2
+ PSHUFL $0x93, X1, X1
+ PXOR X6, X2
+ PADDL X2, X4
+ MOVOA X2, X5
+ MOVOA X4, X6
+ PSLLL $0x0d, X4
+ PSRLL $0x13, X6
+ PXOR X4, X3
+ PSHUFL $0x4e, X2, X2
+ PXOR X6, X3
+ PADDL X3, X5
+ MOVOA X1, X4
+ MOVOA X5, X6
+ PSLLL $0x12, X5
+ PSRLL $0x0e, X6
+ PXOR X5, X0
+ PSHUFL $0x39, X3, X3
+ PXOR X6, X0
+ PADDL X0, X4
+ MOVOA X0, X5
+ MOVOA X4, X6
+ PSLLL $0x07, X4
+ PSRLL $0x19, X6
+ PXOR X4, X3
+ PXOR X6, X3
+ PADDL X3, X5
+ MOVOA X3, X4
+ MOVOA X5, X6
+ PSLLL $0x09, X5
+ PSRLL $0x17, X6
+ PXOR X5, X2
+ PSHUFL $0x93, X3, X3
+ PXOR X6, X2
+ PADDL X2, X4
+ MOVOA X2, X5
+ MOVOA X4, X6
+ PSLLL $0x0d, X4
+ PSRLL $0x13, X6
+ PXOR X4, X1
+ PSHUFL $0x4e, X2, X2
+ PXOR X6, X1
+ PADDL X1, X5
+ MOVOA X3, X4
+ MOVOA X5, X6
+ PSLLL $0x12, X5
+ PSRLL $0x0e, X6
+ PXOR X5, X0
+ PSHUFL $0x39, X1, X1
+ PXOR X6, X0
+ PADDL X0, X4
+ MOVOA X0, X5
+ MOVOA X4, X6
+ PSLLL $0x07, X4
+ PSRLL $0x19, X6
+ PXOR X4, X1
+ PXOR X6, X1
+ PADDL X1, X5
+ MOVOA X1, X4
+ MOVOA X5, X6
+ PSLLL $0x09, X5
+ PSRLL $0x17, X6
+ PXOR X5, X2
+ PSHUFL $0x93, X1, X1
+ PXOR X6, X2
+ PADDL X2, X4
+ MOVOA X2, X5
+ MOVOA X4, X6
+ PSLLL $0x0d, X4
+ PSRLL $0x13, X6
+ PXOR X4, X3
+ PSHUFL $0x4e, X2, X2
+ PXOR X6, X3
+ SUBQ $0x04, CX
+ PADDL X3, X5
+ MOVOA X1, X4
+ MOVOA X5, X6
+ PSLLL $0x12, X5
+ PXOR X7, X7
+ PSRLL $0x0e, X6
+ PXOR X5, X0
+ PSHUFL $0x39, X3, X3
+ PXOR X6, X0
+ JA MAINLOOP2
+ PADDL 48(R12), X0
+ PADDL (R12), X1
+ PADDL 16(R12), X2
+ PADDL 32(R12), X3
+ MOVD X0, CX
+ MOVD X1, R8
+ MOVD X2, R9
+ MOVD X3, AX
+ PSHUFL $0x39, X0, X0
+ PSHUFL $0x39, X1, X1
+ PSHUFL $0x39, X2, X2
+ PSHUFL $0x39, X3, X3
+ XORL (SI), CX
+ XORL 48(SI), R8
+ XORL 32(SI), R9
+ XORL 16(SI), AX
+ MOVL CX, (DI)
+ MOVL R8, 48(DI)
+ MOVL R9, 32(DI)
+ MOVL AX, 16(DI)
+ MOVD X0, CX
+ MOVD X1, R8
+ MOVD X2, R9
+ MOVD X3, AX
+ PSHUFL $0x39, X0, X0
+ PSHUFL $0x39, X1, X1
+ PSHUFL $0x39, X2, X2
+ PSHUFL $0x39, X3, X3
+ XORL 20(SI), CX
+ XORL 4(SI), R8
+ XORL 52(SI), R9
+ XORL 36(SI), AX
+ MOVL CX, 20(DI)
+ MOVL R8, 4(DI)
+ MOVL R9, 52(DI)
+ MOVL AX, 36(DI)
+ MOVD X0, CX
+ MOVD X1, R8
+ MOVD X2, R9
+ MOVD X3, AX
+ PSHUFL $0x39, X0, X0
+ PSHUFL $0x39, X1, X1
+ PSHUFL $0x39, X2, X2
+ PSHUFL $0x39, X3, X3
+ XORL 40(SI), CX
+ XORL 24(SI), R8
+ XORL 8(SI), R9
+ XORL 56(SI), AX
+ MOVL CX, 40(DI)
+ MOVL R8, 24(DI)
+ MOVL R9, 8(DI)
+ MOVL AX, 56(DI)
+ MOVD X0, CX
+ MOVD X1, R8
+ MOVD X2, R9
+ MOVD X3, AX
+ XORL 60(SI), CX
+ XORL 44(SI), R8
+ XORL 28(SI), R9
+ XORL 12(SI), AX
+ MOVL CX, 60(DI)
+ MOVL R8, 44(DI)
+ MOVL R9, 28(DI)
+ MOVL AX, 12(DI)
+ MOVQ 352(R12), R9
+ MOVL 16(R12), CX
+ MOVL 36(R12), R8
+ ADDQ $0x01, CX
+ SHLQ $0x20, R8
+ ADDQ R8, CX
+ MOVQ CX, R8
+ SHRQ $0x20, R8
+ MOVL CX, 16(R12)
+ MOVL R8, 36(R12)
+ CMPQ R9, $0x40
+ JA BYTESATLEAST65
+ JAE BYTESATLEAST64
+ MOVQ DI, SI
+ MOVQ DX, DI
+ MOVQ R9, CX
+ REP; MOVSB
+
+BYTESATLEAST64:
+DONE:
+ RET
+
+BYTESATLEAST65:
+ SUBQ $0x40, R9
+ ADDQ $0x40, DI
+ ADDQ $0x40, SI
+ JMP BYTESBETWEEN1AND255
diff --git a/local_crypto_patch/contents/salsa20/salsa/salsa20_amd64_test.go b/local_crypto_patch/contents/salsa20/salsa/salsa20_amd64_test.go
new file mode 100644
index 0000000000..fe14604fc9
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa/salsa20_amd64_test.go
@@ -0,0 +1,31 @@
+// 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 amd64 && !purego && gc
+
+package salsa
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestCounterOverflow(t *testing.T) {
+ in := make([]byte, 4096)
+ key := &[32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}
+ for n, counter := range []*[16]byte{
+ &[16]byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0}, // zero counter
+ &[16]byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff}, // counter about to overflow 32 bits
+ &[16]byte{0, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff}, // counter above 32 bits
+ } {
+ out := make([]byte, 4096)
+ XORKeyStream(out, in, counter, key)
+ outGeneric := make([]byte, 4096)
+ genericXORKeyStream(outGeneric, in, counter, key)
+ if !bytes.Equal(out, outGeneric) {
+ t.Errorf("%d: assembly and go implementations disagree", n)
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/salsa20/salsa/salsa20_noasm.go b/local_crypto_patch/contents/salsa20/salsa/salsa20_noasm.go
new file mode 100644
index 0000000000..9448760f26
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa/salsa20_noasm.go
@@ -0,0 +1,14 @@
+// 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 !amd64 || purego || !gc
+
+package salsa
+
+// XORKeyStream crypts bytes from in to out using the given key and counters.
+// In and out must overlap entirely or not at all. Counter
+// contains the raw salsa20 counter bytes (both nonce and block counter).
+func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
+ genericXORKeyStream(out, in, counter, key)
+}
diff --git a/local_crypto_patch/contents/salsa20/salsa/salsa20_ref.go b/local_crypto_patch/contents/salsa20/salsa/salsa20_ref.go
new file mode 100644
index 0000000000..e5cdb9a25b
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa/salsa20_ref.go
@@ -0,0 +1,233 @@
+// 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 salsa
+
+import "math/bits"
+
+const rounds = 20
+
+// core applies the Salsa20 core function to 16-byte input in, 32-byte key k,
+// and 16-byte constant c, and puts the result into 64-byte array out.
+func core(out *[64]byte, in *[16]byte, k *[32]byte, c *[16]byte) {
+ j0 := uint32(c[0]) | uint32(c[1])<<8 | uint32(c[2])<<16 | uint32(c[3])<<24
+ j1 := uint32(k[0]) | uint32(k[1])<<8 | uint32(k[2])<<16 | uint32(k[3])<<24
+ j2 := uint32(k[4]) | uint32(k[5])<<8 | uint32(k[6])<<16 | uint32(k[7])<<24
+ j3 := uint32(k[8]) | uint32(k[9])<<8 | uint32(k[10])<<16 | uint32(k[11])<<24
+ j4 := uint32(k[12]) | uint32(k[13])<<8 | uint32(k[14])<<16 | uint32(k[15])<<24
+ j5 := uint32(c[4]) | uint32(c[5])<<8 | uint32(c[6])<<16 | uint32(c[7])<<24
+ j6 := uint32(in[0]) | uint32(in[1])<<8 | uint32(in[2])<<16 | uint32(in[3])<<24
+ j7 := uint32(in[4]) | uint32(in[5])<<8 | uint32(in[6])<<16 | uint32(in[7])<<24
+ j8 := uint32(in[8]) | uint32(in[9])<<8 | uint32(in[10])<<16 | uint32(in[11])<<24
+ j9 := uint32(in[12]) | uint32(in[13])<<8 | uint32(in[14])<<16 | uint32(in[15])<<24
+ j10 := uint32(c[8]) | uint32(c[9])<<8 | uint32(c[10])<<16 | uint32(c[11])<<24
+ j11 := uint32(k[16]) | uint32(k[17])<<8 | uint32(k[18])<<16 | uint32(k[19])<<24
+ j12 := uint32(k[20]) | uint32(k[21])<<8 | uint32(k[22])<<16 | uint32(k[23])<<24
+ j13 := uint32(k[24]) | uint32(k[25])<<8 | uint32(k[26])<<16 | uint32(k[27])<<24
+ j14 := uint32(k[28]) | uint32(k[29])<<8 | uint32(k[30])<<16 | uint32(k[31])<<24
+ j15 := uint32(c[12]) | uint32(c[13])<<8 | uint32(c[14])<<16 | uint32(c[15])<<24
+
+ x0, x1, x2, x3, x4, x5, x6, x7, x8 := j0, j1, j2, j3, j4, j5, j6, j7, j8
+ x9, x10, x11, x12, x13, x14, x15 := j9, j10, j11, j12, j13, j14, j15
+
+ for i := 0; i < rounds; i += 2 {
+ u := x0 + x12
+ x4 ^= bits.RotateLeft32(u, 7)
+ u = x4 + x0
+ x8 ^= bits.RotateLeft32(u, 9)
+ u = x8 + x4
+ x12 ^= bits.RotateLeft32(u, 13)
+ u = x12 + x8
+ x0 ^= bits.RotateLeft32(u, 18)
+
+ u = x5 + x1
+ x9 ^= bits.RotateLeft32(u, 7)
+ u = x9 + x5
+ x13 ^= bits.RotateLeft32(u, 9)
+ u = x13 + x9
+ x1 ^= bits.RotateLeft32(u, 13)
+ u = x1 + x13
+ x5 ^= bits.RotateLeft32(u, 18)
+
+ u = x10 + x6
+ x14 ^= bits.RotateLeft32(u, 7)
+ u = x14 + x10
+ x2 ^= bits.RotateLeft32(u, 9)
+ u = x2 + x14
+ x6 ^= bits.RotateLeft32(u, 13)
+ u = x6 + x2
+ x10 ^= bits.RotateLeft32(u, 18)
+
+ u = x15 + x11
+ x3 ^= bits.RotateLeft32(u, 7)
+ u = x3 + x15
+ x7 ^= bits.RotateLeft32(u, 9)
+ u = x7 + x3
+ x11 ^= bits.RotateLeft32(u, 13)
+ u = x11 + x7
+ x15 ^= bits.RotateLeft32(u, 18)
+
+ u = x0 + x3
+ x1 ^= bits.RotateLeft32(u, 7)
+ u = x1 + x0
+ x2 ^= bits.RotateLeft32(u, 9)
+ u = x2 + x1
+ x3 ^= bits.RotateLeft32(u, 13)
+ u = x3 + x2
+ x0 ^= bits.RotateLeft32(u, 18)
+
+ u = x5 + x4
+ x6 ^= bits.RotateLeft32(u, 7)
+ u = x6 + x5
+ x7 ^= bits.RotateLeft32(u, 9)
+ u = x7 + x6
+ x4 ^= bits.RotateLeft32(u, 13)
+ u = x4 + x7
+ x5 ^= bits.RotateLeft32(u, 18)
+
+ u = x10 + x9
+ x11 ^= bits.RotateLeft32(u, 7)
+ u = x11 + x10
+ x8 ^= bits.RotateLeft32(u, 9)
+ u = x8 + x11
+ x9 ^= bits.RotateLeft32(u, 13)
+ u = x9 + x8
+ x10 ^= bits.RotateLeft32(u, 18)
+
+ u = x15 + x14
+ x12 ^= bits.RotateLeft32(u, 7)
+ u = x12 + x15
+ x13 ^= bits.RotateLeft32(u, 9)
+ u = x13 + x12
+ x14 ^= bits.RotateLeft32(u, 13)
+ u = x14 + x13
+ x15 ^= bits.RotateLeft32(u, 18)
+ }
+ x0 += j0
+ x1 += j1
+ x2 += j2
+ x3 += j3
+ x4 += j4
+ x5 += j5
+ x6 += j6
+ x7 += j7
+ x8 += j8
+ x9 += j9
+ x10 += j10
+ x11 += j11
+ x12 += j12
+ x13 += j13
+ x14 += j14
+ x15 += j15
+
+ out[0] = byte(x0)
+ out[1] = byte(x0 >> 8)
+ out[2] = byte(x0 >> 16)
+ out[3] = byte(x0 >> 24)
+
+ out[4] = byte(x1)
+ out[5] = byte(x1 >> 8)
+ out[6] = byte(x1 >> 16)
+ out[7] = byte(x1 >> 24)
+
+ out[8] = byte(x2)
+ out[9] = byte(x2 >> 8)
+ out[10] = byte(x2 >> 16)
+ out[11] = byte(x2 >> 24)
+
+ out[12] = byte(x3)
+ out[13] = byte(x3 >> 8)
+ out[14] = byte(x3 >> 16)
+ out[15] = byte(x3 >> 24)
+
+ out[16] = byte(x4)
+ out[17] = byte(x4 >> 8)
+ out[18] = byte(x4 >> 16)
+ out[19] = byte(x4 >> 24)
+
+ out[20] = byte(x5)
+ out[21] = byte(x5 >> 8)
+ out[22] = byte(x5 >> 16)
+ out[23] = byte(x5 >> 24)
+
+ out[24] = byte(x6)
+ out[25] = byte(x6 >> 8)
+ out[26] = byte(x6 >> 16)
+ out[27] = byte(x6 >> 24)
+
+ out[28] = byte(x7)
+ out[29] = byte(x7 >> 8)
+ out[30] = byte(x7 >> 16)
+ out[31] = byte(x7 >> 24)
+
+ out[32] = byte(x8)
+ out[33] = byte(x8 >> 8)
+ out[34] = byte(x8 >> 16)
+ out[35] = byte(x8 >> 24)
+
+ out[36] = byte(x9)
+ out[37] = byte(x9 >> 8)
+ out[38] = byte(x9 >> 16)
+ out[39] = byte(x9 >> 24)
+
+ out[40] = byte(x10)
+ out[41] = byte(x10 >> 8)
+ out[42] = byte(x10 >> 16)
+ out[43] = byte(x10 >> 24)
+
+ out[44] = byte(x11)
+ out[45] = byte(x11 >> 8)
+ out[46] = byte(x11 >> 16)
+ out[47] = byte(x11 >> 24)
+
+ out[48] = byte(x12)
+ out[49] = byte(x12 >> 8)
+ out[50] = byte(x12 >> 16)
+ out[51] = byte(x12 >> 24)
+
+ out[52] = byte(x13)
+ out[53] = byte(x13 >> 8)
+ out[54] = byte(x13 >> 16)
+ out[55] = byte(x13 >> 24)
+
+ out[56] = byte(x14)
+ out[57] = byte(x14 >> 8)
+ out[58] = byte(x14 >> 16)
+ out[59] = byte(x14 >> 24)
+
+ out[60] = byte(x15)
+ out[61] = byte(x15 >> 8)
+ out[62] = byte(x15 >> 16)
+ out[63] = byte(x15 >> 24)
+}
+
+// genericXORKeyStream is the generic implementation of XORKeyStream to be used
+// when no assembly implementation is available.
+func genericXORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
+ var block [64]byte
+ var counterCopy [16]byte
+ copy(counterCopy[:], counter[:])
+
+ for len(in) >= 64 {
+ core(&block, &counterCopy, key, &Sigma)
+ for i, x := range block {
+ out[i] = in[i] ^ x
+ }
+ u := uint32(1)
+ for i := 8; i < 16; i++ {
+ u += uint32(counterCopy[i])
+ counterCopy[i] = byte(u)
+ u >>= 8
+ }
+ in = in[64:]
+ out = out[64:]
+ }
+
+ if len(in) > 0 {
+ core(&block, &counterCopy, key, &Sigma)
+ for i, v := range in {
+ out[i] = v ^ block[i]
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/salsa20/salsa/salsa_test.go b/local_crypto_patch/contents/salsa20/salsa/salsa_test.go
new file mode 100644
index 0000000000..f67e94eba4
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa/salsa_test.go
@@ -0,0 +1,54 @@
+// 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 salsa
+
+import "testing"
+
+func TestCore208(t *testing.T) {
+ in := [64]byte{
+ 0x7e, 0x87, 0x9a, 0x21, 0x4f, 0x3e, 0xc9, 0x86,
+ 0x7c, 0xa9, 0x40, 0xe6, 0x41, 0x71, 0x8f, 0x26,
+ 0xba, 0xee, 0x55, 0x5b, 0x8c, 0x61, 0xc1, 0xb5,
+ 0x0d, 0xf8, 0x46, 0x11, 0x6d, 0xcd, 0x3b, 0x1d,
+ 0xee, 0x24, 0xf3, 0x19, 0xdf, 0x9b, 0x3d, 0x85,
+ 0x14, 0x12, 0x1e, 0x4b, 0x5a, 0xc5, 0xaa, 0x32,
+ 0x76, 0x02, 0x1d, 0x29, 0x09, 0xc7, 0x48, 0x29,
+ 0xed, 0xeb, 0xc6, 0x8d, 0xb8, 0xb8, 0xc2, 0x5e}
+
+ out := [64]byte{
+ 0xa4, 0x1f, 0x85, 0x9c, 0x66, 0x08, 0xcc, 0x99,
+ 0x3b, 0x81, 0xca, 0xcb, 0x02, 0x0c, 0xef, 0x05,
+ 0x04, 0x4b, 0x21, 0x81, 0xa2, 0xfd, 0x33, 0x7d,
+ 0xfd, 0x7b, 0x1c, 0x63, 0x96, 0x68, 0x2f, 0x29,
+ 0xb4, 0x39, 0x31, 0x68, 0xe3, 0xc9, 0xe6, 0xbc,
+ 0xfe, 0x6b, 0xc5, 0xb7, 0xa0, 0x6d, 0x96, 0xba,
+ 0xe4, 0x24, 0xcc, 0x10, 0x2c, 0x91, 0x74, 0x5c,
+ 0x24, 0xad, 0x67, 0x3d, 0xc7, 0x61, 0x8f, 0x81,
+ }
+
+ Core208(&in, &in)
+ if in != out {
+ t.Errorf("expected %x, got %x", out, in)
+ }
+}
+
+func TestOutOfBoundsWrite(t *testing.T) {
+ // encrypted "0123456789"
+ cipherText := []byte{170, 166, 196, 104, 175, 121, 68, 44, 174, 51}
+ var counter [16]byte
+ var key [32]byte
+ want := "abcdefghij"
+ plainText := []byte(want)
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Error("XORKeyStream expected to panic on len(dst) < len(src), but didn't")
+ }
+ if plainText[3] == '3' {
+ t.Errorf("XORKeyStream did out of bounds write, want %v, got %v", want, string(plainText))
+ }
+ }()
+ XORKeyStream(plainText[:3], cipherText, &counter, &key)
+}
diff --git a/local_crypto_patch/contents/salsa20/salsa20.go b/local_crypto_patch/contents/salsa20/salsa20.go
new file mode 100644
index 0000000000..e75c9342a8
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa20.go
@@ -0,0 +1,58 @@
+// 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 salsa20 implements the Salsa20 stream cipher as specified in https://cr.yp.to/snuffle/spec.pdf.
+
+Salsa20 differs from many other stream ciphers in that it is message orientated
+rather than byte orientated. Keystream blocks are not preserved between calls,
+therefore each side must encrypt/decrypt data with the same segmentation.
+
+Another aspect of this difference is that part of the counter is exposed as
+a nonce in each call. Encrypting two different messages with the same (key,
+nonce) pair leads to trivial plaintext recovery. This is analogous to
+encrypting two different messages with the same key with a traditional stream
+cipher.
+
+This package also implements XSalsa20: a version of Salsa20 with a 24-byte
+nonce as specified in https://cr.yp.to/snuffle/xsalsa-20081128.pdf. Simply
+passing a 24-byte slice as the nonce triggers XSalsa20.
+*/
+package salsa20
+
+// TODO(agl): implement XORKeyStream12 and XORKeyStream8 - the reduced round variants of Salsa20.
+
+import (
+ "golang.org/x/crypto/internal/alias"
+ "golang.org/x/crypto/salsa20/salsa"
+)
+
+// XORKeyStream crypts bytes from in to out using the given key and nonce.
+// In and out must overlap entirely or not at all. Nonce must
+// be either 8 or 24 bytes long.
+func XORKeyStream(out, in []byte, nonce []byte, key *[32]byte) {
+ if len(out) < len(in) {
+ panic("salsa20: output smaller than input")
+ }
+ if alias.InexactOverlap(out[:len(in)], in) {
+ panic("salsa20: invalid buffer overlap")
+ }
+
+ var subNonce [16]byte
+
+ if len(nonce) == 24 {
+ var subKey [32]byte
+ var hNonce [16]byte
+ copy(hNonce[:], nonce[:16])
+ salsa.HSalsa20(&subKey, &hNonce, key, &salsa.Sigma)
+ copy(subNonce[:], nonce[16:])
+ key = &subKey
+ } else if len(nonce) == 8 {
+ copy(subNonce[:], nonce[:])
+ } else {
+ panic("salsa20: nonce must be 8 or 24 bytes")
+ }
+
+ salsa.XORKeyStream(out, in, &subNonce, key)
+}
diff --git a/local_crypto_patch/contents/salsa20/salsa20_test.go b/local_crypto_patch/contents/salsa20/salsa20_test.go
new file mode 100644
index 0000000000..0ef3328eb0
--- /dev/null
+++ b/local_crypto_patch/contents/salsa20/salsa20_test.go
@@ -0,0 +1,139 @@
+// 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 salsa20
+
+import (
+ "bytes"
+ "encoding/hex"
+ "testing"
+)
+
+func fromHex(s string) []byte {
+ ret, err := hex.DecodeString(s)
+ if err != nil {
+ panic(err)
+ }
+ return ret
+}
+
+// testVectors was taken from set 6 of the ECRYPT test vectors:
+// http://www.ecrypt.eu.org/stream/svn/viewcvs.cgi/ecrypt/trunk/submissions/salsa20/full/verified.test-vectors?logsort=rev&rev=210&view=markup
+var testVectors = []struct {
+ key []byte
+ iv []byte
+ numBytes int
+ xor []byte
+}{
+ {
+ fromHex("0053A6F94C9FF24598EB3E91E4378ADD3083D6297CCF2275C81B6EC11467BA0D"),
+ fromHex("0D74DB42A91077DE"),
+ 131072,
+ fromHex("C349B6A51A3EC9B712EAED3F90D8BCEE69B7628645F251A996F55260C62EF31FD6C6B0AEA94E136C9D984AD2DF3578F78E457527B03A0450580DD874F63B1AB9"),
+ },
+ {
+ fromHex("0558ABFE51A4F74A9DF04396E93C8FE23588DB2E81D4277ACD2073C6196CBF12"),
+ fromHex("167DE44BB21980E7"),
+ 131072,
+ fromHex("C3EAAF32836BACE32D04E1124231EF47E101367D6305413A0EEB07C60698A2876E4D031870A739D6FFDDD208597AFF0A47AC17EDB0167DD67EBA84F1883D4DFD"),
+ },
+ {
+ fromHex("0A5DB00356A9FC4FA2F5489BEE4194E73A8DE03386D92C7FD22578CB1E71C417"),
+ fromHex("1F86ED54BB2289F0"),
+ 131072,
+ fromHex("3CD23C3DC90201ACC0CF49B440B6C417F0DC8D8410A716D5314C059E14B1A8D9A9FB8EA3D9C8DAE12B21402F674AA95C67B1FC514E994C9D3F3A6E41DFF5BBA6"),
+ },
+ {
+ fromHex("0F62B5085BAE0154A7FA4DA0F34699EC3F92E5388BDE3184D72A7DD02376C91C"),
+ fromHex("288FF65DC42B92F9"),
+ 131072,
+ fromHex("E00EBCCD70D69152725F9987982178A2E2E139C7BCBE04CA8A0E99E318D9AB76F988C8549F75ADD790BA4F81C176DA653C1A043F11A958E169B6D2319F4EEC1A"),
+ },
+}
+
+func TestSalsa20(t *testing.T) {
+ var inBuf, outBuf []byte
+ var key [32]byte
+
+ for i, test := range testVectors {
+ if test.numBytes%64 != 0 {
+ t.Errorf("#%d: numBytes is not a multiple of 64", i)
+ continue
+ }
+
+ if test.numBytes > len(inBuf) {
+ inBuf = make([]byte, test.numBytes)
+ outBuf = make([]byte, test.numBytes)
+ }
+ in := inBuf[:test.numBytes]
+ out := outBuf[:test.numBytes]
+ copy(key[:], test.key)
+ XORKeyStream(out, in, test.iv, &key)
+
+ var xor [64]byte
+ for len(out) > 0 {
+ for i := 0; i < 64; i++ {
+ xor[i] ^= out[i]
+ }
+ out = out[64:]
+ }
+
+ if !bytes.Equal(xor[:], test.xor) {
+ t.Errorf("#%d: bad result", i)
+ }
+ }
+}
+
+var xSalsa20TestData = []struct {
+ in, nonce, key, out []byte
+}{
+ {
+ []byte("Hello world!"),
+ []byte("24-byte nonce for xsalsa"),
+ []byte("this is 32-byte key for xsalsa20"),
+ []byte{0x00, 0x2d, 0x45, 0x13, 0x84, 0x3f, 0xc2, 0x40, 0xc4, 0x01, 0xe5, 0x41},
+ },
+ {
+ make([]byte, 64),
+ []byte("24-byte nonce for xsalsa"),
+ []byte("this is 32-byte key for xsalsa20"),
+ []byte{0x48, 0x48, 0x29, 0x7f, 0xeb, 0x1f, 0xb5, 0x2f, 0xb6,
+ 0x6d, 0x81, 0x60, 0x9b, 0xd5, 0x47, 0xfa, 0xbc, 0xbe, 0x70,
+ 0x26, 0xed, 0xc8, 0xb5, 0xe5, 0xe4, 0x49, 0xd0, 0x88, 0xbf,
+ 0xa6, 0x9c, 0x08, 0x8f, 0x5d, 0x8d, 0xa1, 0xd7, 0x91, 0x26,
+ 0x7c, 0x2c, 0x19, 0x5a, 0x7f, 0x8c, 0xae, 0x9c, 0x4b, 0x40,
+ 0x50, 0xd0, 0x8c, 0xe6, 0xd3, 0xa1, 0x51, 0xec, 0x26, 0x5f,
+ 0x3a, 0x58, 0xe4, 0x76, 0x48},
+ },
+}
+
+func TestXSalsa20(t *testing.T) {
+ var key [32]byte
+
+ for i, test := range xSalsa20TestData {
+ out := make([]byte, len(test.in))
+ copy(key[:], test.key)
+ XORKeyStream(out, test.in, test.nonce, &key)
+ if !bytes.Equal(out, test.out) {
+ t.Errorf("%d: expected %x, got %x", i, test.out, out)
+ }
+ }
+}
+
+var (
+ keyArray [32]byte
+ key = &keyArray
+ nonce [8]byte
+ msg = make([]byte, 1<<10)
+)
+
+func BenchmarkXOR1K(b *testing.B) {
+ b.StopTimer()
+ out := make([]byte, 1024)
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ XORKeyStream(out, msg[:1024], nonce[:], key)
+ }
+ b.SetBytes(1024)
+}
diff --git a/local_crypto_patch/contents/scrypt/example_test.go b/local_crypto_patch/contents/scrypt/example_test.go
new file mode 100644
index 0000000000..6736479b19
--- /dev/null
+++ b/local_crypto_patch/contents/scrypt/example_test.go
@@ -0,0 +1,26 @@
+// 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 scrypt_test
+
+import (
+ "encoding/base64"
+ "fmt"
+ "log"
+
+ "golang.org/x/crypto/scrypt"
+)
+
+func Example() {
+ // DO NOT use this salt value; generate your own random salt. 8 bytes is
+ // a good length.
+ salt := []byte{0xc8, 0x28, 0xf2, 0x58, 0xa7, 0x6a, 0xad, 0x7b}
+
+ dk, err := scrypt.Key([]byte("some password"), salt, 1<<15, 8, 1, 32)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println(base64.StdEncoding.EncodeToString(dk))
+ // Output: lGnMz8io0AUkfzn6Pls1qX20Vs7PGN6sbYQ2TQgY12M=
+}
diff --git a/local_crypto_patch/contents/scrypt/scrypt.go b/local_crypto_patch/contents/scrypt/scrypt.go
new file mode 100644
index 0000000000..b422b7d7fd
--- /dev/null
+++ b/local_crypto_patch/contents/scrypt/scrypt.go
@@ -0,0 +1,215 @@
+// 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 scrypt implements the scrypt key derivation function as defined in
+// Colin Percival's paper "Stronger Key Derivation via Sequential Memory-Hard
+// Functions" (https://www.tarsnap.com/scrypt/scrypt.pdf).
+package scrypt
+
+import (
+ "crypto/sha256"
+ "encoding/binary"
+ "errors"
+ "math/bits"
+
+ "golang.org/x/crypto/pbkdf2"
+)
+
+const maxInt = int(^uint(0) >> 1)
+
+// blockCopy copies n numbers from src into dst.
+func blockCopy(dst, src []uint32, n int) {
+ copy(dst, src[:n])
+}
+
+// blockXOR XORs numbers from dst with n numbers from src.
+func blockXOR(dst, src []uint32, n int) {
+ for i, v := range src[:n] {
+ dst[i] ^= v
+ }
+}
+
+// salsaXOR applies Salsa20/8 to the XOR of 16 numbers from tmp and in,
+// and puts the result into both tmp and out.
+func salsaXOR(tmp *[16]uint32, in, out []uint32) {
+ w0 := tmp[0] ^ in[0]
+ w1 := tmp[1] ^ in[1]
+ w2 := tmp[2] ^ in[2]
+ w3 := tmp[3] ^ in[3]
+ w4 := tmp[4] ^ in[4]
+ w5 := tmp[5] ^ in[5]
+ w6 := tmp[6] ^ in[6]
+ w7 := tmp[7] ^ in[7]
+ w8 := tmp[8] ^ in[8]
+ w9 := tmp[9] ^ in[9]
+ w10 := tmp[10] ^ in[10]
+ w11 := tmp[11] ^ in[11]
+ w12 := tmp[12] ^ in[12]
+ w13 := tmp[13] ^ in[13]
+ w14 := tmp[14] ^ in[14]
+ w15 := tmp[15] ^ in[15]
+
+ x0, x1, x2, x3, x4, x5, x6, x7, x8 := w0, w1, w2, w3, w4, w5, w6, w7, w8
+ x9, x10, x11, x12, x13, x14, x15 := w9, w10, w11, w12, w13, w14, w15
+
+ for i := 0; i < 8; i += 2 {
+ x4 ^= bits.RotateLeft32(x0+x12, 7)
+ x8 ^= bits.RotateLeft32(x4+x0, 9)
+ x12 ^= bits.RotateLeft32(x8+x4, 13)
+ x0 ^= bits.RotateLeft32(x12+x8, 18)
+
+ x9 ^= bits.RotateLeft32(x5+x1, 7)
+ x13 ^= bits.RotateLeft32(x9+x5, 9)
+ x1 ^= bits.RotateLeft32(x13+x9, 13)
+ x5 ^= bits.RotateLeft32(x1+x13, 18)
+
+ x14 ^= bits.RotateLeft32(x10+x6, 7)
+ x2 ^= bits.RotateLeft32(x14+x10, 9)
+ x6 ^= bits.RotateLeft32(x2+x14, 13)
+ x10 ^= bits.RotateLeft32(x6+x2, 18)
+
+ x3 ^= bits.RotateLeft32(x15+x11, 7)
+ x7 ^= bits.RotateLeft32(x3+x15, 9)
+ x11 ^= bits.RotateLeft32(x7+x3, 13)
+ x15 ^= bits.RotateLeft32(x11+x7, 18)
+
+ x1 ^= bits.RotateLeft32(x0+x3, 7)
+ x2 ^= bits.RotateLeft32(x1+x0, 9)
+ x3 ^= bits.RotateLeft32(x2+x1, 13)
+ x0 ^= bits.RotateLeft32(x3+x2, 18)
+
+ x6 ^= bits.RotateLeft32(x5+x4, 7)
+ x7 ^= bits.RotateLeft32(x6+x5, 9)
+ x4 ^= bits.RotateLeft32(x7+x6, 13)
+ x5 ^= bits.RotateLeft32(x4+x7, 18)
+
+ x11 ^= bits.RotateLeft32(x10+x9, 7)
+ x8 ^= bits.RotateLeft32(x11+x10, 9)
+ x9 ^= bits.RotateLeft32(x8+x11, 13)
+ x10 ^= bits.RotateLeft32(x9+x8, 18)
+
+ x12 ^= bits.RotateLeft32(x15+x14, 7)
+ x13 ^= bits.RotateLeft32(x12+x15, 9)
+ x14 ^= bits.RotateLeft32(x13+x12, 13)
+ x15 ^= bits.RotateLeft32(x14+x13, 18)
+ }
+ x0 += w0
+ x1 += w1
+ x2 += w2
+ x3 += w3
+ x4 += w4
+ x5 += w5
+ x6 += w6
+ x7 += w7
+ x8 += w8
+ x9 += w9
+ x10 += w10
+ x11 += w11
+ x12 += w12
+ x13 += w13
+ x14 += w14
+ x15 += w15
+
+ out[0], tmp[0] = x0, x0
+ out[1], tmp[1] = x1, x1
+ out[2], tmp[2] = x2, x2
+ out[3], tmp[3] = x3, x3
+ out[4], tmp[4] = x4, x4
+ out[5], tmp[5] = x5, x5
+ out[6], tmp[6] = x6, x6
+ out[7], tmp[7] = x7, x7
+ out[8], tmp[8] = x8, x8
+ out[9], tmp[9] = x9, x9
+ out[10], tmp[10] = x10, x10
+ out[11], tmp[11] = x11, x11
+ out[12], tmp[12] = x12, x12
+ out[13], tmp[13] = x13, x13
+ out[14], tmp[14] = x14, x14
+ out[15], tmp[15] = x15, x15
+}
+
+func blockMix(tmp *[16]uint32, in, out []uint32, r int) {
+ blockCopy(tmp[:], in[(2*r-1)*16:], 16)
+ for i := 0; i < 2*r; i += 2 {
+ salsaXOR(tmp, in[i*16:], out[i*8:])
+ salsaXOR(tmp, in[i*16+16:], out[i*8+r*16:])
+ }
+}
+
+func integer(b []uint32, r int) uint64 {
+ j := (2*r - 1) * 16
+ return uint64(b[j]) | uint64(b[j+1])<<32
+}
+
+func smix(b []byte, r, N int, v, xy []uint32) {
+ var tmp [16]uint32
+ R := 32 * r
+ x := xy
+ y := xy[R:]
+
+ j := 0
+ for i := 0; i < R; i++ {
+ x[i] = binary.LittleEndian.Uint32(b[j:])
+ j += 4
+ }
+ for i := 0; i < N; i += 2 {
+ blockCopy(v[i*R:], x, R)
+ blockMix(&tmp, x, y, r)
+
+ blockCopy(v[(i+1)*R:], y, R)
+ blockMix(&tmp, y, x, r)
+ }
+ for i := 0; i < N; i += 2 {
+ j := int(integer(x, r) & uint64(N-1))
+ blockXOR(x, v[j*R:], R)
+ blockMix(&tmp, x, y, r)
+
+ j = int(integer(y, r) & uint64(N-1))
+ blockXOR(y, v[j*R:], R)
+ blockMix(&tmp, y, x, r)
+ }
+ j = 0
+ for _, v := range x[:R] {
+ binary.LittleEndian.PutUint32(b[j:], v)
+ j += 4
+ }
+}
+
+// Key derives a key from the password, salt, and cost parameters, returning
+// a byte slice of length keyLen that can be used as cryptographic key.
+//
+// N is a CPU/memory cost parameter, which must be a power of two greater than 1.
+// r and p must satisfy r * p < 2³⁰. If the parameters do not satisfy the
+// limits, the function returns a nil byte slice and an error.
+//
+// For example, you can get a derived key for e.g. AES-256 (which needs a
+// 32-byte key) by doing:
+//
+// dk, err := scrypt.Key([]byte("some password"), salt, 32768, 8, 1, 32)
+//
+// The recommended parameters for interactive logins as of 2017 are N=32768, r=8
+// and p=1. The parameters N, r, and p should be increased as memory latency and
+// CPU parallelism increases; consider setting N to the highest power of 2 you
+// can derive within 100 milliseconds. Remember to get a good random salt.
+func Key(password, salt []byte, N, r, p, keyLen int) ([]byte, error) {
+ if N <= 1 || N&(N-1) != 0 {
+ return nil, errors.New("scrypt: N must be > 1 and a power of 2")
+ }
+ if r <= 0 || p <= 0 {
+ return nil, errors.New("scrypt: parameters must be > 0")
+ }
+ if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r {
+ return nil, errors.New("scrypt: parameters are too large")
+ }
+
+ xy := make([]uint32, 64*r)
+ v := make([]uint32, 32*N*r)
+ b := pbkdf2.Key(password, salt, 1, p*128*r, sha256.New)
+
+ for i := 0; i < p; i++ {
+ smix(b[i*128*r:], r, N, v, xy)
+ }
+
+ return pbkdf2.Key(password, b, 1, keyLen, sha256.New), nil
+}
diff --git a/local_crypto_patch/contents/scrypt/scrypt_test.go b/local_crypto_patch/contents/scrypt/scrypt_test.go
new file mode 100644
index 0000000000..8a4637fd86
--- /dev/null
+++ b/local_crypto_patch/contents/scrypt/scrypt_test.go
@@ -0,0 +1,166 @@
+// 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 scrypt
+
+import (
+ "bytes"
+ "testing"
+)
+
+type testVector struct {
+ password string
+ salt string
+ N, r, p int
+ output []byte
+}
+
+var good = []testVector{
+ {
+ "password",
+ "salt",
+ 2, 10, 10,
+ []byte{
+ 0x48, 0x2c, 0x85, 0x8e, 0x22, 0x90, 0x55, 0xe6, 0x2f,
+ 0x41, 0xe0, 0xec, 0x81, 0x9a, 0x5e, 0xe1, 0x8b, 0xdb,
+ 0x87, 0x25, 0x1a, 0x53, 0x4f, 0x75, 0xac, 0xd9, 0x5a,
+ 0xc5, 0xe5, 0xa, 0xa1, 0x5f,
+ },
+ },
+ {
+ "password",
+ "salt",
+ 16, 100, 100,
+ []byte{
+ 0x88, 0xbd, 0x5e, 0xdb, 0x52, 0xd1, 0xdd, 0x0, 0x18,
+ 0x87, 0x72, 0xad, 0x36, 0x17, 0x12, 0x90, 0x22, 0x4e,
+ 0x74, 0x82, 0x95, 0x25, 0xb1, 0x8d, 0x73, 0x23, 0xa5,
+ 0x7f, 0x91, 0x96, 0x3c, 0x37,
+ },
+ },
+ {
+ "this is a long \000 password",
+ "and this is a long \000 salt",
+ 16384, 8, 1,
+ []byte{
+ 0xc3, 0xf1, 0x82, 0xee, 0x2d, 0xec, 0x84, 0x6e, 0x70,
+ 0xa6, 0x94, 0x2f, 0xb5, 0x29, 0x98, 0x5a, 0x3a, 0x09,
+ 0x76, 0x5e, 0xf0, 0x4c, 0x61, 0x29, 0x23, 0xb1, 0x7f,
+ 0x18, 0x55, 0x5a, 0x37, 0x07, 0x6d, 0xeb, 0x2b, 0x98,
+ 0x30, 0xd6, 0x9d, 0xe5, 0x49, 0x26, 0x51, 0xe4, 0x50,
+ 0x6a, 0xe5, 0x77, 0x6d, 0x96, 0xd4, 0x0f, 0x67, 0xaa,
+ 0xee, 0x37, 0xe1, 0x77, 0x7b, 0x8a, 0xd5, 0xc3, 0x11,
+ 0x14, 0x32, 0xbb, 0x3b, 0x6f, 0x7e, 0x12, 0x64, 0x40,
+ 0x18, 0x79, 0xe6, 0x41, 0xae,
+ },
+ },
+ {
+ "p",
+ "s",
+ 2, 1, 1,
+ []byte{
+ 0x48, 0xb0, 0xd2, 0xa8, 0xa3, 0x27, 0x26, 0x11, 0x98,
+ 0x4c, 0x50, 0xeb, 0xd6, 0x30, 0xaf, 0x52,
+ },
+ },
+
+ {
+ "",
+ "",
+ 16, 1, 1,
+ []byte{
+ 0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b,
+ 0x19, 0xca, 0x42, 0xc1, 0x8a, 0x04, 0x97, 0xf1, 0x6b,
+ 0x48, 0x44, 0xe3, 0x07, 0x4a, 0xe8, 0xdf, 0xdf, 0xfa,
+ 0x3f, 0xed, 0xe2, 0x14, 0x42, 0xfc, 0xd0, 0x06, 0x9d,
+ 0xed, 0x09, 0x48, 0xf8, 0x32, 0x6a, 0x75, 0x3a, 0x0f,
+ 0xc8, 0x1f, 0x17, 0xe8, 0xd3, 0xe0, 0xfb, 0x2e, 0x0d,
+ 0x36, 0x28, 0xcf, 0x35, 0xe2, 0x0c, 0x38, 0xd1, 0x89,
+ 0x06,
+ },
+ },
+ {
+ "password",
+ "NaCl",
+ 1024, 8, 16,
+ []byte{
+ 0xfd, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00, 0x78,
+ 0x56, 0xe7, 0x19, 0x0d, 0x01, 0xe9, 0xfe, 0x7c, 0x6a,
+ 0xd7, 0xcb, 0xc8, 0x23, 0x78, 0x30, 0xe7, 0x73, 0x76,
+ 0x63, 0x4b, 0x37, 0x31, 0x62, 0x2e, 0xaf, 0x30, 0xd9,
+ 0x2e, 0x22, 0xa3, 0x88, 0x6f, 0xf1, 0x09, 0x27, 0x9d,
+ 0x98, 0x30, 0xda, 0xc7, 0x27, 0xaf, 0xb9, 0x4a, 0x83,
+ 0xee, 0x6d, 0x83, 0x60, 0xcb, 0xdf, 0xa2, 0xcc, 0x06,
+ 0x40,
+ },
+ },
+ {
+ "pleaseletmein", "SodiumChloride",
+ 16384, 8, 1,
+ []byte{
+ 0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48, 0x46,
+ 0x1c, 0x06, 0xcd, 0x81, 0xfd, 0x38, 0xeb, 0xfd, 0xa8,
+ 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e, 0xa9, 0xb5, 0x43,
+ 0xf6, 0x54, 0x5d, 0xa1, 0xf2, 0xd5, 0x43, 0x29, 0x55,
+ 0x61, 0x3f, 0x0f, 0xcf, 0x62, 0xd4, 0x97, 0x05, 0x24,
+ 0x2a, 0x9a, 0xf9, 0xe6, 0x1e, 0x85, 0xdc, 0x0d, 0x65,
+ 0x1e, 0x40, 0xdf, 0xcf, 0x01, 0x7b, 0x45, 0x57, 0x58,
+ 0x87,
+ },
+ },
+ /*
+ // Disabled: needs 1 GiB RAM and takes too long for a simple test.
+ {
+ "pleaseletmein", "SodiumChloride",
+ 1048576, 8, 1,
+ []byte{
+ 0x21, 0x01, 0xcb, 0x9b, 0x6a, 0x51, 0x1a, 0xae, 0xad,
+ 0xdb, 0xbe, 0x09, 0xcf, 0x70, 0xf8, 0x81, 0xec, 0x56,
+ 0x8d, 0x57, 0x4a, 0x2f, 0xfd, 0x4d, 0xab, 0xe5, 0xee,
+ 0x98, 0x20, 0xad, 0xaa, 0x47, 0x8e, 0x56, 0xfd, 0x8f,
+ 0x4b, 0xa5, 0xd0, 0x9f, 0xfa, 0x1c, 0x6d, 0x92, 0x7c,
+ 0x40, 0xf4, 0xc3, 0x37, 0x30, 0x40, 0x49, 0xe8, 0xa9,
+ 0x52, 0xfb, 0xcb, 0xf4, 0x5c, 0x6f, 0xa7, 0x7a, 0x41,
+ 0xa4,
+ },
+ },
+ */
+}
+
+var bad = []testVector{
+ {"p", "s", 0, 1, 1, nil}, // N == 0
+ {"p", "s", 1, 1, 1, nil}, // N == 1
+ {"p", "s", 7, 8, 1, nil}, // N is not power of 2
+ {"p", "s", 16, maxInt / 2, maxInt / 2, nil}, // p * r too large
+ {"p", "s", 2, 0, 1, nil}, // r too small
+ {"p", "s", 2, 1, 0, nil}, // p too small
+ {"p", "s", 2, -1, 1, nil}, // r is negative
+ {"p", "s", 2, 1, -1, nil}, // p is negative
+}
+
+func TestKey(t *testing.T) {
+ for i, v := range good {
+ k, err := Key([]byte(v.password), []byte(v.salt), v.N, v.r, v.p, len(v.output))
+ if err != nil {
+ t.Errorf("%d: got unexpected error: %s", i, err)
+ }
+ if !bytes.Equal(k, v.output) {
+ t.Errorf("%d: expected %x, got %x", i, v.output, k)
+ }
+ }
+ for i, v := range bad {
+ _, err := Key([]byte(v.password), []byte(v.salt), v.N, v.r, v.p, 32)
+ if err == nil {
+ t.Errorf("%d: expected error, got nil", i)
+ }
+ }
+}
+
+var sink []byte
+
+func BenchmarkKey(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ sink, _ = Key([]byte("password"), []byte("salt"), 1<<15, 8, 1, 64)
+ }
+}
diff --git a/local_crypto_patch/contents/sha3/hashes.go b/local_crypto_patch/contents/sha3/hashes.go
new file mode 100644
index 0000000000..a51269d91a
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/sha3/legacy_hash.go b/local_crypto_patch/contents/sha3/legacy_hash.go
new file mode 100644
index 0000000000..b8784536e0
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/sha3/legacy_keccakf.go b/local_crypto_patch/contents/sha3/legacy_keccakf.go
new file mode 100644
index 0000000000..101588c16c
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/sha3/sha3_test.go b/local_crypto_patch/contents/sha3/sha3_test.go
new file mode 100644
index 0000000000..bee9b10d46
--- /dev/null
+++ b/local_crypto_patch/contents/sha3/sha3_test.go
@@ -0,0 +1,524 @@
+// 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
+
+// Tests include all the ShortMsgKATs provided by the Keccak team at
+// https://github.com/gvanas/KeccakCodePackage
+//
+// They only include the zero-bit case of the bitwise testvectors
+// published by NIST in the draft of FIPS-202.
+
+import (
+ "bytes"
+ "compress/flate"
+ "encoding"
+ "encoding/hex"
+ "encoding/json"
+ "hash"
+ "io"
+ "math/rand"
+ "os"
+ "strings"
+ "testing"
+)
+
+const (
+ testString = "brekeccakkeccak koax koax"
+ katFilename = "testdata/keccakKats.json.deflate"
+)
+
+// testDigests contains functions returning hash.Hash instances
+// with output-length equal to the KAT length for SHA-3, Keccak
+// and SHAKE instances.
+var testDigests = map[string]func() hash.Hash{
+ "SHA3-224": New224,
+ "SHA3-256": New256,
+ "SHA3-384": New384,
+ "SHA3-512": New512,
+ "Keccak-256": NewLegacyKeccak256,
+ "Keccak-512": NewLegacyKeccak512,
+}
+
+// testShakes contains functions that return sha3.ShakeHash instances for
+// with output-length equal to the KAT length.
+var testShakes = map[string]struct {
+ constructor func(N []byte, S []byte) ShakeHash
+ defAlgoName string
+ defCustomStr string
+}{
+ // NewCShake without customization produces same result as SHAKE
+ "SHAKE128": {NewCShake128, "", ""},
+ "SHAKE256": {NewCShake256, "", ""},
+ "cSHAKE128": {NewCShake128, "CSHAKE128", "CustomStrign"},
+ "cSHAKE256": {NewCShake256, "CSHAKE256", "CustomStrign"},
+}
+
+// decodeHex converts a hex-encoded string into a raw byte string.
+func decodeHex(s string) []byte {
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
+
+// structs used to marshal JSON test-cases.
+type KeccakKats struct {
+ Kats map[string][]struct {
+ Digest string `json:"digest"`
+ Length int64 `json:"length"`
+ Message string `json:"message"`
+
+ // Defined only for cSHAKE
+ N string `json:"N"`
+ S string `json:"S"`
+ }
+}
+
+// TestKeccakKats tests the SHA-3 and Shake implementations against all the
+// ShortMsgKATs from https://github.com/gvanas/KeccakCodePackage
+// (The testvectors are stored in keccakKats.json.deflate due to their length.)
+func TestKeccakKats(t *testing.T) {
+ // Read the KATs.
+ deflated, err := os.Open(katFilename)
+ if err != nil {
+ t.Errorf("error opening %s: %s", katFilename, err)
+ }
+ file := flate.NewReader(deflated)
+ dec := json.NewDecoder(file)
+ var katSet KeccakKats
+ err = dec.Decode(&katSet)
+ if err != nil {
+ t.Errorf("error decoding KATs: %s", err)
+ }
+
+ for algo, function := range testDigests {
+ d := function()
+ for _, kat := range katSet.Kats[algo] {
+ d.Reset()
+ in, err := hex.DecodeString(kat.Message)
+ if err != nil {
+ t.Errorf("error decoding KAT: %s", err)
+ }
+ d.Write(in[:kat.Length/8])
+ got := strings.ToUpper(hex.EncodeToString(d.Sum(nil)))
+ if got != kat.Digest {
+ t.Errorf("function=%s, length=%d\nmessage:\n %s\ngot:\n %s\nwanted:\n %s",
+ algo, kat.Length, kat.Message, got, kat.Digest)
+ t.Logf("wanted %+v", kat)
+ t.FailNow()
+ }
+ continue
+ }
+ }
+
+ for algo, v := range testShakes {
+ for _, kat := range katSet.Kats[algo] {
+ N, err := hex.DecodeString(kat.N)
+ if err != nil {
+ t.Errorf("error decoding KAT: %s", err)
+ }
+
+ S, err := hex.DecodeString(kat.S)
+ if err != nil {
+ t.Errorf("error decoding KAT: %s", err)
+ }
+ d := v.constructor(N, S)
+ in, err := hex.DecodeString(kat.Message)
+ if err != nil {
+ t.Errorf("error decoding KAT: %s", err)
+ }
+
+ d.Write(in[:kat.Length/8])
+ out := make([]byte, len(kat.Digest)/2)
+ d.Read(out)
+ got := strings.ToUpper(hex.EncodeToString(out))
+ if got != kat.Digest {
+ t.Errorf("function=%s, length=%d N:%s\n S:%s\nmessage:\n %s \ngot:\n %s\nwanted:\n %s",
+ algo, kat.Length, kat.N, kat.S, kat.Message, got, kat.Digest)
+ t.Logf("wanted %+v", kat)
+ t.FailNow()
+ }
+ continue
+ }
+ }
+}
+
+// TestKeccak does a basic test of the non-standardized Keccak hash functions.
+func TestKeccak(t *testing.T) {
+ tests := []struct {
+ fn func() hash.Hash
+ data []byte
+ want string
+ }{
+ {
+ NewLegacyKeccak256,
+ []byte("abc"),
+ "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45",
+ },
+ {
+ NewLegacyKeccak512,
+ []byte("abc"),
+ "18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96",
+ },
+ }
+
+ for _, u := range tests {
+ h := u.fn()
+ h.Write(u.data)
+ got := h.Sum(nil)
+ want := decodeHex(u.want)
+ if !bytes.Equal(got, want) {
+ t.Errorf("unexpected hash for size %d: got '%x' want '%s'", h.Size()*8, got, u.want)
+ }
+ }
+}
+
+// TestShakeSum tests that the output of Sum matches the output of Read.
+func TestShakeSum(t *testing.T) {
+ tests := [...]struct {
+ name string
+ hash ShakeHash
+ expectedLen int
+ }{
+ {"SHAKE128", NewShake128(), 32},
+ {"SHAKE256", NewShake256(), 64},
+ {"cSHAKE128", NewCShake128([]byte{'X'}, nil), 32},
+ {"cSHAKE256", NewCShake256([]byte{'X'}, nil), 64},
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ s := test.hash.Sum(nil)
+ if len(s) != test.expectedLen {
+ t.Errorf("Unexpected digest length: got %d, want %d", len(s), test.expectedLen)
+ }
+ r := make([]byte, test.expectedLen)
+ test.hash.Read(r)
+ if !bytes.Equal(s, r) {
+ t.Errorf("Mismatch between Sum and Read:\nSum: %s\nRead: %s", hex.EncodeToString(s), hex.EncodeToString(r))
+ }
+ })
+ }
+}
+
+// TestUnalignedWrite tests that writing data in an arbitrary pattern with
+// small input buffers.
+func TestUnalignedWrite(t *testing.T) {
+ buf := sequentialBytes(0x10000)
+ for alg, df := range testDigests {
+ d := df()
+ d.Reset()
+ d.Write(buf)
+ want := d.Sum(nil)
+ d.Reset()
+ for i := 0; i < len(buf); {
+ // Cycle through offsets which make a 137 byte sequence.
+ // Because 137 is prime this sequence should exercise all corner cases.
+ offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
+ for _, j := range offsets {
+ if v := len(buf) - i; v < j {
+ j = v
+ }
+ d.Write(buf[i : i+j])
+ i += j
+ }
+ }
+ got := d.Sum(nil)
+ if !bytes.Equal(got, want) {
+ t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want)
+ }
+ }
+
+ // Same for SHAKE
+ for alg, df := range testShakes {
+ want := make([]byte, 16)
+ got := make([]byte, 16)
+ d := df.constructor([]byte(df.defAlgoName), []byte(df.defCustomStr))
+
+ d.Reset()
+ d.Write(buf)
+ d.Read(want)
+ d.Reset()
+ for i := 0; i < len(buf); {
+ // Cycle through offsets which make a 137 byte sequence.
+ // Because 137 is prime this sequence should exercise all corner cases.
+ offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
+ for _, j := range offsets {
+ if v := len(buf) - i; v < j {
+ j = v
+ }
+ d.Write(buf[i : i+j])
+ i += j
+ }
+ }
+ d.Read(got)
+ if !bytes.Equal(got, want) {
+ t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want)
+ }
+ }
+}
+
+// TestAppend checks that appending works when reallocation is necessary.
+func TestAppend(t *testing.T) {
+ d := New224()
+
+ for capacity := 2; capacity <= 66; capacity += 64 {
+ // The first time around the loop, Sum will have to reallocate.
+ // The second time, it will not.
+ buf := make([]byte, 2, capacity)
+ d.Reset()
+ d.Write([]byte{0xcc})
+ buf = d.Sum(buf)
+ expected := "0000DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39"
+ if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected {
+ t.Errorf("got %s, want %s", got, expected)
+ }
+ }
+}
+
+// TestAppendNoRealloc tests that appending works when no reallocation is necessary.
+func TestAppendNoRealloc(t *testing.T) {
+ buf := make([]byte, 1, 200)
+ d := New224()
+ d.Write([]byte{0xcc})
+ buf = d.Sum(buf)
+ expected := "00DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39"
+ if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected {
+ t.Errorf("got %s, want %s", got, expected)
+ }
+}
+
+// TestSqueezing checks that squeezing the full output a single time produces
+// the same output as repeatedly squeezing the instance.
+func TestSqueezing(t *testing.T) {
+ for algo, v := range testShakes {
+ d0 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr))
+ d0.Write([]byte(testString))
+ ref := make([]byte, 32)
+ d0.Read(ref)
+
+ d1 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr))
+ d1.Write([]byte(testString))
+ var multiple []byte
+ for range ref {
+ one := make([]byte, 1)
+ d1.Read(one)
+ multiple = append(multiple, one...)
+ }
+ if !bytes.Equal(ref, multiple) {
+ t.Errorf("%s: squeezing %d bytes one at a time failed", algo, len(ref))
+ }
+ }
+}
+
+// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing.
+//
+// The alignment of each slice is intentionally randomized to detect alignment
+// issues in the implementation. See https://golang.org/issue/37644.
+// Ideally, the compiler should fuzz the alignment itself.
+// (See https://golang.org/issue/35128.)
+func sequentialBytes(size int) []byte {
+ alignmentOffset := rand.Intn(8)
+ result := make([]byte, size+alignmentOffset)[alignmentOffset:]
+ for i := range result {
+ result[i] = byte(i)
+ }
+ return result
+}
+
+func TestReset(t *testing.T) {
+ out1 := make([]byte, 32)
+ out2 := make([]byte, 32)
+
+ for _, v := range testShakes {
+ // Calculate hash for the first time
+ c := v.constructor(nil, []byte{0x99, 0x98})
+ c.Write(sequentialBytes(0x100))
+ c.Read(out1)
+
+ // Calculate hash again
+ c.Reset()
+ c.Write(sequentialBytes(0x100))
+ c.Read(out2)
+
+ if !bytes.Equal(out1, out2) {
+ t.Error("\nExpected:\n", out1, "\ngot:\n", out2)
+ }
+ }
+}
+
+func TestClone(t *testing.T) {
+ out1 := make([]byte, 16)
+ out2 := make([]byte, 16)
+
+ // Test for sizes smaller and larger than block size.
+ for _, size := range []int{0x1, 0x100} {
+ in := sequentialBytes(size)
+ for _, v := range testShakes {
+ h1 := v.constructor(nil, []byte{0x01})
+ h1.Write([]byte{0x01})
+
+ h2 := h1.Clone()
+
+ h1.Write(in)
+ h1.Read(out1)
+
+ h2.Write(in)
+ h2.Read(out2)
+
+ if !bytes.Equal(out1, out2) {
+ t.Error("\nExpected:\n", hex.EncodeToString(out1), "\ngot:\n", hex.EncodeToString(out2))
+ }
+ }
+ }
+}
+
+func TestCSHAKEAccumulated(t *testing.T) {
+ // Generated with pycryptodome@3.20.0
+ //
+ // from Crypto.Hash import cSHAKE128
+ // rng = cSHAKE128.new()
+ // acc = cSHAKE128.new()
+ // for n in range(200):
+ // N = rng.read(n)
+ // for s in range(200):
+ // S = rng.read(s)
+ // c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N)
+ // c.update(rng.read(100))
+ // acc.update(c.read(200))
+ // c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N)
+ // c.update(rng.read(168))
+ // acc.update(c.read(200))
+ // c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N)
+ // c.update(rng.read(200))
+ // acc.update(c.read(200))
+ // print(acc.read(32).hex())
+ //
+ // and with @noble/hashes@v1.5.0
+ //
+ // import { bytesToHex } from "@noble/hashes/utils";
+ // import { cshake128 } from "@noble/hashes/sha3-addons";
+ // const rng = cshake128.create();
+ // const acc = cshake128.create();
+ // for (let n = 0; n < 200; n++) {
+ // const N = rng.xof(n);
+ // for (let s = 0; s < 200; s++) {
+ // const S = rng.xof(s);
+ // let c = cshake128.create({ NISTfn: N, personalization: S });
+ // c.update(rng.xof(100));
+ // acc.update(c.xof(200));
+ // c = cshake128.create({ NISTfn: N, personalization: S });
+ // c.update(rng.xof(168));
+ // acc.update(c.xof(200));
+ // c = cshake128.create({ NISTfn: N, personalization: S });
+ // c.update(rng.xof(200));
+ // acc.update(c.xof(200));
+ // }
+ // }
+ // console.log(bytesToHex(acc.xof(32)));
+ //
+ t.Run("cSHAKE128", func(t *testing.T) {
+ testCSHAKEAccumulated(t, NewCShake128, rateK256,
+ "bb14f8657c6ec5403d0b0e2ef3d3393497e9d3b1a9a9e8e6c81dbaa5fd809252")
+ })
+ t.Run("cSHAKE256", func(t *testing.T) {
+ testCSHAKEAccumulated(t, NewCShake256, rateK512,
+ "0baaf9250c6e25f0c14ea5c7f9bfde54c8a922c8276437db28f3895bdf6eeeef")
+ })
+}
+
+func testCSHAKEAccumulated(t *testing.T, newCShake func(N, S []byte) ShakeHash, rate int64, exp string) {
+ rnd := newCShake(nil, nil)
+ acc := newCShake(nil, nil)
+ for n := 0; n < 200; n++ {
+ N := make([]byte, n)
+ rnd.Read(N)
+ for s := 0; s < 200; s++ {
+ S := make([]byte, s)
+ rnd.Read(S)
+
+ c := newCShake(N, S)
+ io.CopyN(c, rnd, 100 /* < rate */)
+ io.CopyN(acc, c, 200)
+
+ c.Reset()
+ io.CopyN(c, rnd, rate)
+ io.CopyN(acc, c, 200)
+
+ c.Reset()
+ io.CopyN(c, rnd, 200 /* > rate */)
+ io.CopyN(acc, c, 200)
+ }
+ }
+ if got := hex.EncodeToString(acc.Sum(nil)[:32]); got != exp {
+ t.Errorf("got %s, want %s", got, exp)
+ }
+}
+
+func TestCSHAKELargeS(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping test in short mode.")
+ }
+
+ // See https://go.dev/issue/66232.
+ const s = (1<<32)/8 + 1000 // s * 8 > 2^32
+ S := make([]byte, s)
+ rnd := NewShake128()
+ rnd.Read(S)
+ c := NewCShake128(nil, S)
+ io.CopyN(c, rnd, 1000)
+
+ // Generated with pycryptodome@3.20.0
+ //
+ // from Crypto.Hash import cSHAKE128
+ // rng = cSHAKE128.new()
+ // S = rng.read(536871912)
+ // c = cSHAKE128.new(custom=S)
+ // c.update(rng.read(1000))
+ // print(c.read(32).hex())
+ //
+ exp := "2cb9f237767e98f2614b8779cf096a52da9b3a849280bbddec820771ae529cf0"
+ if got := hex.EncodeToString(c.Sum(nil)); got != exp {
+ t.Errorf("got %s, want %s", got, exp)
+ }
+}
+
+func TestMarshalUnmarshal(t *testing.T) {
+ t.Run("SHA3-224", func(t *testing.T) { testMarshalUnmarshal(t, New224()) })
+ t.Run("SHA3-256", func(t *testing.T) { testMarshalUnmarshal(t, New256()) })
+ t.Run("SHA3-384", func(t *testing.T) { testMarshalUnmarshal(t, New384()) })
+ t.Run("SHA3-512", func(t *testing.T) { testMarshalUnmarshal(t, New512()) })
+ t.Run("SHAKE128", func(t *testing.T) { testMarshalUnmarshal(t, NewShake128()) })
+ t.Run("SHAKE256", func(t *testing.T) { testMarshalUnmarshal(t, NewShake256()) })
+ t.Run("cSHAKE128", func(t *testing.T) { testMarshalUnmarshal(t, NewCShake128([]byte("N"), []byte("S"))) })
+ t.Run("cSHAKE256", func(t *testing.T) { testMarshalUnmarshal(t, NewCShake256([]byte("N"), []byte("S"))) })
+ t.Run("Keccak-256", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak256()) })
+ t.Run("Keccak-512", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak512()) })
+}
+
+// TODO(filippo): move this to crypto/internal/cryptotest.
+func testMarshalUnmarshal(t *testing.T, h hash.Hash) {
+ buf := make([]byte, 200)
+ rand.Read(buf)
+ n := rand.Intn(200)
+ h.Write(buf)
+ want := h.Sum(nil)
+ h.Reset()
+ h.Write(buf[:n])
+ b, err := h.(encoding.BinaryMarshaler).MarshalBinary()
+ if err != nil {
+ t.Errorf("MarshalBinary: %v", err)
+ }
+ h.Write(bytes.Repeat([]byte{0}, 200))
+ if err := h.(encoding.BinaryUnmarshaler).UnmarshalBinary(b); err != nil {
+ t.Errorf("UnmarshalBinary: %v", err)
+ }
+ h.Write(buf[n:])
+ got := h.Sum(nil)
+ if !bytes.Equal(got, want) {
+ t.Errorf("got %x, want %x", got, want)
+ }
+}
diff --git a/local_crypto_patch/contents/sha3/shake.go b/local_crypto_patch/contents/sha3/shake.go
new file mode 100644
index 0000000000..6f3f70c265
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/sha3/testdata/keccakKats.json.deflate b/local_crypto_patch/contents/sha3/testdata/keccakKats.json.deflate
new file mode 100644
index 0000000000..7a94c2f8bc
Binary files /dev/null and b/local_crypto_patch/contents/sha3/testdata/keccakKats.json.deflate differ
diff --git a/local_crypto_patch/contents/ssh/agent/client.go b/local_crypto_patch/contents/ssh/agent/client.go
new file mode 100644
index 0000000000..2fc2aa9043
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/agent/client.go
@@ -0,0 +1,863 @@
+// 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)
+ }
+
+ for _, ext := range key.ConstraintExtensions {
+ constraints = append(constraints, ssh.Marshal(constrainExtensionAgentMsg{
+ ExtensionName: ext.ExtensionName,
+ ExtensionDetails: ext.ExtensionDetails,
+ })...)
+ }
+
+ 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/local_crypto_patch/contents/ssh/agent/client_test.go b/local_crypto_patch/contents/ssh/agent/client_test.go
new file mode 100644
index 0000000000..567c0d9cd2
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/agent/client_test.go
@@ -0,0 +1,656 @@
+// 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 (
+ "bytes"
+ "crypto/rand"
+ "encoding/binary"
+ "errors"
+ "io"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// startOpenSSHAgent executes ssh-agent, and returns an Agent interface to it.
+func startOpenSSHAgent(t *testing.T) (client ExtendedAgent, socket string, cleanup func()) {
+ if testing.Short() {
+ // ssh-agent is not always available, and the key
+ // types supported vary by platform.
+ t.Skip("skipping test due to -short")
+ }
+ if runtime.GOOS == "windows" {
+ t.Skip("skipping on windows, we don't support connecting to the ssh-agent via a named pipe")
+ }
+
+ bin, err := exec.LookPath("ssh-agent")
+ if err != nil {
+ t.Skip("could not find ssh-agent")
+ }
+
+ cmd := exec.Command(bin, "-s")
+ cmd.Env = []string{
+ // ssh-agent creates ~/.ssh and ~/.ssh/agent;
+ // ensure a writeable home directory.
+ "HOME=" + t.TempDir(),
+ } // Do not let the user's environment influence ssh-agent behavior.
+ cmd.Stderr = new(bytes.Buffer)
+ out, err := cmd.Output()
+ if err != nil {
+ t.Fatalf("%s failed: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr)
+ }
+
+ // Output looks like:
+ //
+ // SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_AUTH_SOCK;
+ // SSH_AGENT_PID=15542; export SSH_AGENT_PID;
+ // echo Agent pid 15542;
+
+ fields := bytes.Split(out, []byte(";"))
+ line := bytes.SplitN(fields[0], []byte("="), 2)
+ line[0] = bytes.TrimLeft(line[0], "\n")
+ if string(line[0]) != "SSH_AUTH_SOCK" {
+ t.Fatalf("could not find key SSH_AUTH_SOCK in %q", fields[0])
+ }
+ socket = string(line[1])
+
+ line = bytes.SplitN(fields[2], []byte("="), 2)
+ line[0] = bytes.TrimLeft(line[0], "\n")
+ if string(line[0]) != "SSH_AGENT_PID" {
+ t.Fatalf("could not find key SSH_AGENT_PID in %q", fields[2])
+ }
+ pidStr := line[1]
+ pid, err := strconv.Atoi(string(pidStr))
+ if err != nil {
+ t.Fatalf("Atoi(%q): %v", pidStr, err)
+ }
+
+ conn, err := net.Dial("unix", string(socket))
+ if err != nil {
+ t.Fatalf("net.Dial: %v", err)
+ }
+
+ ac := NewClient(conn)
+ return ac, socket, func() {
+ proc, _ := os.FindProcess(pid)
+ if proc != nil {
+ proc.Kill()
+ }
+ conn.Close()
+ os.RemoveAll(filepath.Dir(socket))
+ }
+}
+
+func startAgent(t *testing.T, agent Agent) (client ExtendedAgent, cleanup func()) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ go ServeAgent(agent, c2)
+
+ return NewClient(c1), func() {
+ c1.Close()
+ c2.Close()
+ }
+}
+
+// startKeyringAgent uses Keyring to simulate a ssh-agent Server and returns a client.
+func startKeyringAgent(t *testing.T) (client ExtendedAgent, cleanup func()) {
+ return startAgent(t, NewKeyring())
+}
+
+func testOpenSSHAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
+ agent, _, cleanup := startOpenSSHAgent(t)
+ defer cleanup()
+
+ testAgentInterface(t, agent, key, cert, lifetimeSecs)
+}
+
+func testKeyringAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
+ agent, cleanup := startKeyringAgent(t)
+ defer cleanup()
+
+ testAgentInterface(t, agent, key, cert, lifetimeSecs)
+}
+
+func testAgentInterface(t *testing.T, agent ExtendedAgent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
+ signer, err := ssh.NewSignerFromKey(key)
+ if err != nil {
+ t.Fatalf("NewSignerFromKey(%T): %v", key, err)
+ }
+ // The agent should start up empty.
+ if keys, err := agent.List(); err != nil {
+ t.Fatalf("RequestIdentities: %v", err)
+ } else if len(keys) > 0 {
+ t.Fatalf("got %d keys, want 0: %v", len(keys), keys)
+ }
+
+ // Attempt to insert the key, with certificate if specified.
+ var pubKey ssh.PublicKey
+ if cert != nil {
+ err = agent.Add(AddedKey{
+ PrivateKey: key,
+ Certificate: cert,
+ Comment: "comment",
+ LifetimeSecs: lifetimeSecs,
+ })
+ pubKey = cert
+ } else {
+ err = agent.Add(AddedKey{PrivateKey: key, Comment: "comment", LifetimeSecs: lifetimeSecs})
+ pubKey = signer.PublicKey()
+ }
+ if err != nil {
+ t.Fatalf("insert(%T): %v", key, err)
+ }
+
+ // Did the key get inserted successfully?
+ if keys, err := agent.List(); err != nil {
+ t.Fatalf("List: %v", err)
+ } else if len(keys) != 1 {
+ t.Fatalf("got %v, want 1 key", keys)
+ } else if keys[0].Comment != "comment" {
+ t.Fatalf("key comment: got %v, want %v", keys[0].Comment, "comment")
+ } else if !bytes.Equal(keys[0].Blob, pubKey.Marshal()) {
+ t.Fatalf("key mismatch")
+ }
+
+ // Can the agent make a valid signature?
+ data := []byte("hello")
+ sig, err := agent.Sign(pubKey, data)
+ if err != nil {
+ t.Logf("sign failed with key type %q", pubKey.Type())
+ // In integration tests ssh-rsa (SHA1 signatures) may be disabled for
+ // security reasons, we check SHA-2 variants later.
+ if pubKey.Type() != ssh.KeyAlgoRSA && pubKey.Type() != ssh.CertAlgoRSAv01 {
+ t.Fatalf("Sign(%s): %v", pubKey.Type(), err)
+ }
+ } else {
+ if err := pubKey.Verify(data, sig); err != nil {
+ t.Logf("verify failed with key type %q", pubKey.Type())
+ if pubKey.Type() != ssh.KeyAlgoRSA {
+ t.Fatalf("Verify(%s): %v", pubKey.Type(), err)
+ }
+ }
+ }
+
+ // For tests on RSA keys, try signing with SHA-256 and SHA-512 flags
+ if pubKey.Type() == ssh.KeyAlgoRSA {
+ sshFlagTest := func(flag SignatureFlags, expectedSigFormat string) {
+ sig, err = agent.SignWithFlags(pubKey, data, flag)
+ if err != nil {
+ t.Fatalf("SignWithFlags(%s): %v", pubKey.Type(), err)
+ }
+ if sig.Format != expectedSigFormat {
+ t.Fatalf("Signature format didn't match expected value: %s != %s", sig.Format, expectedSigFormat)
+ }
+ if err := pubKey.Verify(data, sig); err != nil {
+ t.Fatalf("Verify(%s): %v", pubKey.Type(), err)
+ }
+ }
+ sshFlagTest(SignatureFlagRsaSha256, ssh.KeyAlgoRSASHA256)
+ sshFlagTest(SignatureFlagRsaSha512, ssh.KeyAlgoRSASHA512)
+ }
+
+ // If the key has a lifetime, is it removed when it should be?
+ if lifetimeSecs > 0 {
+ time.Sleep(time.Second*time.Duration(lifetimeSecs) + 100*time.Millisecond)
+ keys, err := agent.List()
+ if err != nil {
+ t.Fatalf("List: %v", err)
+ }
+ if len(keys) > 0 {
+ t.Fatalf("key not expired")
+ }
+ }
+
+}
+
+func TestMalformedRequests(t *testing.T) {
+ keyringAgent := NewKeyring()
+
+ testCase := func(t *testing.T, requestBytes []byte, wantServerErr bool) {
+ c, s := net.Pipe()
+ defer c.Close()
+ defer s.Close()
+ go func() {
+ _, err := c.Write(requestBytes)
+ if err != nil {
+ t.Errorf("Unexpected error writing raw bytes on connection: %v", err)
+ }
+ c.Close()
+ }()
+ err := ServeAgent(keyringAgent, s)
+ if err == nil {
+ t.Error("ServeAgent should have returned an error to malformed input")
+ } else {
+ if (err != io.EOF) != wantServerErr {
+ t.Errorf("ServeAgent returned expected error: %v", err)
+ }
+ }
+ }
+
+ var testCases = []struct {
+ name string
+ requestBytes []byte
+ wantServerErr bool
+ }{
+ {"Empty request", []byte{}, false},
+ {"Short header", []byte{0x00}, true},
+ {"Empty body", []byte{0x00, 0x00, 0x00, 0x00}, true},
+ {"Short body", []byte{0x00, 0x00, 0x00, 0x01}, false},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) { testCase(t, tc.requestBytes, tc.wantServerErr) })
+ }
+}
+
+func TestAgent(t *testing.T) {
+ for _, keyType := range []string{"rsa", "ecdsa", "ed25519"} {
+ testOpenSSHAgent(t, testPrivateKeys[keyType], nil, 0)
+ testKeyringAgent(t, testPrivateKeys[keyType], nil, 0)
+ }
+}
+
+func TestCert(t *testing.T) {
+ cert := &ssh.Certificate{
+ Key: testPublicKeys["rsa"],
+ ValidBefore: ssh.CertTimeInfinity,
+ CertType: ssh.UserCert,
+ }
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+
+ testOpenSSHAgent(t, testPrivateKeys["rsa"], cert, 0)
+ testKeyringAgent(t, testPrivateKeys["rsa"], cert, 0)
+}
+
+// netListener creates a localhost network listener.
+func netListener() (net.Listener, error) {
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ listener, err = net.Listen("tcp", "[::1]:0")
+ if err != nil {
+ return nil, err
+ }
+ }
+ return listener, nil
+}
+
+// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
+// therefore is buffered (net.Pipe deadlocks if both sides start with
+// a write.)
+func netPipe() (net.Conn, net.Conn, error) {
+ listener, err := netListener()
+ if err != nil {
+ return nil, nil, err
+ }
+ defer listener.Close()
+ c1, err := net.Dial("tcp", listener.Addr().String())
+ if err != nil {
+ return nil, nil, err
+ }
+
+ c2, err := listener.Accept()
+ if err != nil {
+ c1.Close()
+ return nil, nil, err
+ }
+
+ return c1, c2, nil
+}
+
+func TestServerResponseTooLarge(t *testing.T) {
+ a, b, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ done := make(chan struct{})
+ defer func() { <-done }()
+
+ defer a.Close()
+ defer b.Close()
+
+ var response identitiesAnswerAgentMsg
+ response.NumKeys = 1
+ response.Keys = make([]byte, maxAgentResponseBytes+1)
+
+ agent := NewClient(a)
+ go func() {
+ defer close(done)
+ n, err := b.Write(ssh.Marshal(response))
+ if n < 4 {
+ if runtime.GOOS == "plan9" {
+ if e1, ok := err.(*net.OpError); ok {
+ if e2, ok := e1.Err.(*os.PathError); ok {
+ switch e2.Err.Error() {
+ case "Hangup", "i/o on hungup channel":
+ // syscall.Pwrite returns -1 in this case even when some data did get written.
+ return
+ }
+ }
+ }
+ }
+ t.Errorf("At least 4 bytes (the response size) should have been successfully written: %d < 4: %v", n, err)
+ }
+ }()
+ _, err = agent.List()
+ if err == nil {
+ t.Fatal("Did not get error result")
+ }
+ if err.Error() != "agent: client error: response too large" {
+ t.Fatal("Did not get expected error result")
+ }
+}
+
+func TestInvalidResponses(t *testing.T) {
+ a, b, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ done := make(chan struct{})
+ defer func() { <-done }()
+
+ defer a.Close()
+ defer b.Close()
+
+ agent := NewClient(a)
+ go func() {
+ defer close(done)
+
+ resp := []byte{agentSuccess}
+ msg := make([]byte, 4+len(resp))
+ binary.BigEndian.PutUint32(msg[:4], uint32(len(resp)))
+ copy(msg[4:], resp)
+
+ if _, err := b.Write(msg); err != nil {
+ t.Errorf("unexpected error sending agent reply: %v", err)
+ b.Close()
+ return
+ }
+
+ if _, err := b.Write(msg); err != nil {
+ t.Errorf("unexpected error sending agent reply: %v", err)
+ b.Close()
+ }
+ }()
+ _, err = agent.List()
+ if err == nil {
+ t.Fatal("error expected")
+ }
+ if !strings.Contains(err.Error(), "failed to list keys, unexpected message type") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ _, err = agent.Sign(testPublicKeys["rsa"], []byte("message"))
+ if err == nil {
+ t.Fatal("error expected")
+ }
+ if !strings.Contains(err.Error(), "failed to sign challenge, unexpected message type") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestAuth(t *testing.T) {
+ agent, _, cleanup := startOpenSSHAgent(t)
+ defer cleanup()
+
+ a, b, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+
+ defer a.Close()
+ defer b.Close()
+
+ if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment"}); err != nil {
+ t.Errorf("Add: %v", err)
+ }
+
+ serverConf := ssh.ServerConfig{}
+ serverConf.AddHostKey(testSigners["rsa"])
+ serverConf.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, nil
+ }
+
+ return nil, errors.New("pubkey rejected")
+ }
+
+ go func() {
+ conn, _, _, err := ssh.NewServerConn(a, &serverConf)
+ if err != nil {
+ t.Errorf("NewServerConn error: %v", err)
+ return
+ }
+ conn.Close()
+ }()
+
+ conf := ssh.ClientConfig{
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ }
+ conf.Auth = append(conf.Auth, ssh.PublicKeysCallback(agent.Signers))
+ conn, _, _, err := ssh.NewClientConn(b, "", &conf)
+ if err != nil {
+ t.Fatalf("NewClientConn: %v", err)
+ }
+ conn.Close()
+}
+
+func TestLockOpenSSHAgent(t *testing.T) {
+ agent, _, cleanup := startOpenSSHAgent(t)
+ defer cleanup()
+ testLockAgent(agent, t)
+}
+
+func TestLockKeyringAgent(t *testing.T) {
+ agent, cleanup := startKeyringAgent(t)
+ defer cleanup()
+ testLockAgent(agent, t)
+}
+
+func testLockAgent(agent Agent, t *testing.T) {
+ if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["rsa"], Comment: "comment 1"}); err != nil {
+ t.Errorf("Add: %v", err)
+ }
+ if err := agent.Add(AddedKey{PrivateKey: testPrivateKeys["ecdsa"], Comment: "comment ecdsa"}); err != nil {
+ t.Errorf("Add: %v", err)
+ }
+ if keys, err := agent.List(); err != nil {
+ t.Errorf("List: %v", err)
+ } else if len(keys) != 2 {
+ t.Errorf("Want 2 keys, got %v", keys)
+ }
+
+ passphrase := []byte("secret")
+ if err := agent.Lock(passphrase); err != nil {
+ t.Errorf("Lock: %v", err)
+ }
+
+ if keys, err := agent.List(); err != nil {
+ t.Errorf("List: %v", err)
+ } else if len(keys) != 0 {
+ t.Errorf("Want 0 keys, got %v", keys)
+ }
+
+ signer, _ := ssh.NewSignerFromKey(testPrivateKeys["rsa"])
+ if _, err := agent.Sign(signer.PublicKey(), []byte("hello")); err == nil {
+ t.Fatalf("Sign did not fail")
+ }
+
+ if err := agent.Remove(signer.PublicKey()); err == nil {
+ t.Fatalf("Remove did not fail")
+ }
+
+ if err := agent.RemoveAll(); err == nil {
+ t.Fatalf("RemoveAll did not fail")
+ }
+
+ if err := agent.Unlock(nil); err == nil {
+ t.Errorf("Unlock with wrong passphrase succeeded")
+ }
+ if err := agent.Unlock(passphrase); err != nil {
+ t.Errorf("Unlock: %v", err)
+ }
+
+ if err := agent.Remove(signer.PublicKey()); err != nil {
+ t.Fatalf("Remove: %v", err)
+ }
+
+ if keys, err := agent.List(); err != nil {
+ t.Errorf("List: %v", err)
+ } else if len(keys) != 1 {
+ t.Errorf("Want 1 keys, got %v", keys)
+ }
+}
+
+func testOpenSSHAgentLifetime(t *testing.T) {
+ agent, _, cleanup := startOpenSSHAgent(t)
+ defer cleanup()
+ testAgentLifetime(t, agent)
+}
+
+func testKeyringAgentLifetime(t *testing.T) {
+ agent, cleanup := startKeyringAgent(t)
+ defer cleanup()
+ testAgentLifetime(t, agent)
+}
+
+func testAgentLifetime(t *testing.T, agent Agent) {
+ for _, keyType := range []string{"rsa", "dsa", "ecdsa"} {
+ // Add private keys to the agent.
+ err := agent.Add(AddedKey{
+ PrivateKey: testPrivateKeys[keyType],
+ Comment: "comment",
+ LifetimeSecs: 1,
+ })
+ if err != nil {
+ t.Fatalf("add: %v", err)
+ }
+ // Add certs to the agent.
+ cert := &ssh.Certificate{
+ Key: testPublicKeys[keyType],
+ ValidBefore: ssh.CertTimeInfinity,
+ CertType: ssh.UserCert,
+ }
+ cert.SignCert(rand.Reader, testSigners[keyType])
+ err = agent.Add(AddedKey{
+ PrivateKey: testPrivateKeys[keyType],
+ Certificate: cert,
+ Comment: "comment",
+ LifetimeSecs: 1,
+ })
+ if err != nil {
+ t.Fatalf("add: %v", err)
+ }
+ }
+ time.Sleep(1100 * time.Millisecond)
+ if keys, err := agent.List(); err != nil {
+ t.Errorf("List: %v", err)
+ } else if len(keys) != 0 {
+ t.Errorf("Want 0 keys, got %v", len(keys))
+ }
+}
+
+type keyringExtended struct {
+ *keyring
+}
+
+func (r *keyringExtended) Extension(extensionType string, contents []byte) ([]byte, error) {
+ if extensionType != "my-extension@example.com" {
+ return []byte{agentExtensionFailure}, nil
+ }
+ return append([]byte{agentSuccess}, contents...), nil
+}
+
+func TestAgentExtensions(t *testing.T) {
+ agent, _, cleanup := startOpenSSHAgent(t)
+ defer cleanup()
+ _, err := agent.Extension("my-extension@example.com", []byte{0x00, 0x01, 0x02})
+ if err == nil {
+ t.Fatal("should have gotten agent extension failure")
+ }
+
+ agent, cleanup = startAgent(t, &keyringExtended{})
+ defer cleanup()
+ result, err := agent.Extension("my-extension@example.com", []byte{0x00, 0x01, 0x02})
+ if err != nil {
+ t.Fatalf("agent extension failure: %v", err)
+ }
+ if len(result) != 4 || !bytes.Equal(result, []byte{agentSuccess, 0x00, 0x01, 0x02}) {
+ t.Fatalf("agent extension result invalid: %v", result)
+ }
+
+ _, err = agent.Extension("bad-extension@example.com", []byte{0x00, 0x01, 0x02})
+ if err == nil {
+ t.Fatal("should have gotten agent extension failure")
+ }
+}
+
+// capturingAgent records the AddedKey observed on the server side of the
+// agent protocol. It forwards to a keyring with constraint fields stripped,
+// so the keyring's own rejection of unsupported constraints does not
+// interfere with what this test is measuring: the client-side wire
+// serialization of ConstraintExtensions.
+type capturingAgent struct {
+ Agent
+ lastAdd AddedKey
+}
+
+func newCapturingAgent() *capturingAgent {
+ return &capturingAgent{Agent: NewKeyring()}
+}
+
+func (a *capturingAgent) Add(key AddedKey) error {
+ a.lastAdd = key
+ stripped := key
+ stripped.ConstraintExtensions = nil
+ stripped.ConfirmBeforeUse = false
+ return a.Agent.Add(stripped)
+}
+
+// TestAddConstraintExtensionsWireFormat verifies that client.Add serializes
+// ConstraintExtensions into the SSH_AGENTC_ADD_IDENTITY payload and the
+// server deserializes them back into the AddedKey delivered to the backend.
+// Regressions in the client marshal loop (missing, swapped fields, wrong
+// framing) would be invisible to a keyring-based rejection test, which
+// signals only "extensions were present", not "the right ones arrived".
+func TestAddConstraintExtensionsWireFormat(t *testing.T) {
+ capturing := newCapturingAgent()
+ client, cleanup := startAgent(t, capturing)
+ defer cleanup()
+
+ constraints := []ConstraintExtension{
+ {ExtensionName: "ext-one@example.com", ExtensionDetails: []byte("details-one")},
+ {ExtensionName: "ext-two@example.com", ExtensionDetails: []byte("\x00\x01\x02\xff")},
+ }
+
+ if err := client.Add(AddedKey{
+ PrivateKey: testPrivateKeys["rsa"],
+ Comment: "wire-format-test",
+ ConstraintExtensions: constraints,
+ }); err != nil {
+ t.Fatalf("client.Add: %v", err)
+ }
+
+ got := capturing.lastAdd.ConstraintExtensions
+ if len(got) != len(constraints) {
+ t.Fatalf("server received %d extensions, want %d", len(got), len(constraints))
+ }
+ for i, want := range constraints {
+ if got[i].ExtensionName != want.ExtensionName {
+ t.Errorf("extension[%d] name: got %q, want %q", i, got[i].ExtensionName, want.ExtensionName)
+ }
+ if !bytes.Equal(got[i].ExtensionDetails, want.ExtensionDetails) {
+ t.Errorf("extension[%d] details: got %x, want %x", i, got[i].ExtensionDetails, want.ExtensionDetails)
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/agent/example_test.go b/local_crypto_patch/contents/ssh/agent/example_test.go
new file mode 100644
index 0000000000..1fedaea1d6
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/agent/example_test.go
@@ -0,0 +1,41 @@
+// 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 agent_test
+
+import (
+ "log"
+ "net"
+ "os"
+
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/agent"
+)
+
+func ExampleNewClient() {
+ // ssh-agent(1) provides a UNIX socket at $SSH_AUTH_SOCK.
+ socket := os.Getenv("SSH_AUTH_SOCK")
+ conn, err := net.Dial("unix", socket)
+ if err != nil {
+ log.Fatalf("Failed to open SSH_AUTH_SOCK: %v", err)
+ }
+
+ agentClient := agent.NewClient(conn)
+ config := &ssh.ClientConfig{
+ User: "gopher",
+ Auth: []ssh.AuthMethod{
+ // Use a callback rather than PublicKeys so we only consult the
+ // agent once the remote server wants it.
+ ssh.PublicKeysCallback(agentClient.Signers),
+ },
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ }
+
+ sshc, err := ssh.Dial("tcp", "localhost:22", config)
+ if err != nil {
+ log.Fatal(err)
+ }
+ // Use sshc...
+ sshc.Close()
+}
diff --git a/local_crypto_patch/contents/ssh/agent/forward.go b/local_crypto_patch/contents/ssh/agent/forward.go
new file mode 100644
index 0000000000..fd24ba900d
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/agent/keyring.go b/local_crypto_patch/contents/ssh/agent/keyring.go
new file mode 100644
index 0000000000..a9e29b6f84
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/agent/keyring.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 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.
+//
+// The returned Agent only supports the "lifetime" constraint.
+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
+}
+
+// Add adds a private key to the keyring. If a certificate is given, that
+// certificate is added as public key.
+//
+// Add returns an error if key contains ConstraintExtensions or
+// ConfirmBeforeUse.
+func (r *keyring) Add(key AddedKey) error {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.locked {
+ return errLocked
+ }
+
+ if key.ConfirmBeforeUse {
+ return errors.New("agent: confirm before use constraint is not supported")
+ }
+
+ if len(key.ConstraintExtensions) > 0 {
+ return errors.New("agent: constraint extensions are present but not supported")
+ }
+
+ 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/local_crypto_patch/contents/ssh/agent/keyring_test.go b/local_crypto_patch/contents/ssh/agent/keyring_test.go
new file mode 100644
index 0000000000..f120392630
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/agent/keyring_test.go
@@ -0,0 +1,161 @@
+// Copyright 2015 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 "testing"
+
+func addTestKey(t *testing.T, a Agent, keyName string) {
+ err := a.Add(AddedKey{
+ PrivateKey: testPrivateKeys[keyName],
+ Comment: keyName,
+ })
+ if err != nil {
+ t.Fatalf("failed to add key %q: %v", keyName, err)
+ }
+}
+
+func removeTestKey(t *testing.T, a Agent, keyName string) {
+ err := a.Remove(testPublicKeys[keyName])
+ if err != nil {
+ t.Fatalf("failed to remove key %q: %v", keyName, err)
+ }
+}
+
+func validateListedKeys(t *testing.T, a Agent, expectedKeys []string) {
+ listedKeys, err := a.List()
+ if err != nil {
+ t.Fatalf("failed to list keys: %v", err)
+ return
+ }
+ if len(listedKeys) != len(expectedKeys) {
+ t.Fatalf("expected %d key, got %d", len(expectedKeys), len(listedKeys))
+ return
+ }
+ actualKeys := make(map[string]bool)
+ for _, key := range listedKeys {
+ actualKeys[key.Comment] = true
+ }
+
+ matchedKeys := make(map[string]bool)
+ for _, expectedKey := range expectedKeys {
+ if !actualKeys[expectedKey] {
+ t.Fatalf("expected key %q, but was not found", expectedKey)
+ } else {
+ matchedKeys[expectedKey] = true
+ }
+ }
+
+ for actualKey := range actualKeys {
+ if !matchedKeys[actualKey] {
+ t.Fatalf("key %q was found, but was not expected", actualKey)
+ }
+ }
+}
+
+func TestKeyringAddingAndRemoving(t *testing.T) {
+ keyNames := []string{"dsa", "ecdsa", "rsa", "user"}
+
+ // add all test private keys
+ k := NewKeyring()
+ for _, keyName := range keyNames {
+ addTestKey(t, k, keyName)
+ }
+ validateListedKeys(t, k, keyNames)
+
+ // remove a key in the middle
+ keyToRemove := keyNames[1]
+ keyNames = append(keyNames[:1], keyNames[2:]...)
+
+ removeTestKey(t, k, keyToRemove)
+ validateListedKeys(t, k, keyNames)
+
+ // remove all keys
+ err := k.RemoveAll()
+ if err != nil {
+ t.Fatalf("failed to remove all keys: %v", err)
+ }
+ validateListedKeys(t, k, []string{})
+}
+
+func TestAddDuplicateKey(t *testing.T) {
+ keyNames := []string{"rsa", "user"}
+
+ k := NewKeyring()
+ for _, keyName := range keyNames {
+ addTestKey(t, k, keyName)
+ }
+ validateListedKeys(t, k, keyNames)
+ // Add the keys again.
+ for _, keyName := range keyNames {
+ addTestKey(t, k, keyName)
+ }
+ validateListedKeys(t, k, keyNames)
+ // Add an existing key with an updated comment.
+ keyName := keyNames[0]
+ addedKey := AddedKey{
+ PrivateKey: testPrivateKeys[keyName],
+ Comment: "comment updated",
+ }
+ err := k.Add(addedKey)
+ if err != nil {
+ t.Fatalf("failed to add key %q: %v", keyName, err)
+ }
+ // Check the that key is found and the comment was updated.
+ keys, err := k.List()
+ if err != nil {
+ t.Fatalf("failed to list keys: %v", err)
+ }
+ if len(keys) != len(keyNames) {
+ t.Fatalf("expected %d keys, got %d", len(keyNames), len(keys))
+ }
+ isFound := false
+ for _, key := range keys {
+ if key.Comment == addedKey.Comment {
+ isFound = true
+ }
+ }
+ if !isFound {
+ t.Fatal("key with the updated comment not found")
+ }
+}
+
+func TestAddKeyWithConstraints(t *testing.T) {
+ // Verifies the keyring refuses keys carrying constraint extensions it
+ // cannot enforce.
+ agent, cleanup := startKeyringAgent(t)
+ defer cleanup()
+
+ constraints := []ConstraintExtension{
+ {
+ ExtensionName: "extension1",
+ ExtensionDetails: []byte("details1"),
+ },
+ }
+
+ key := testPrivateKeys["rsa"]
+
+ err := agent.Add(AddedKey{
+ PrivateKey: key,
+ ConstraintExtensions: constraints,
+ })
+ if err == nil {
+ t.Fatal("adding a key with unsupported constraints succeeded")
+ }
+}
+
+func TestAddKeyWithConfirmBeforeUse(t *testing.T) {
+ agent, cleanup := startKeyringAgent(t)
+ defer cleanup()
+
+ key := testPrivateKeys["rsa"]
+
+ err := agent.Add(AddedKey{
+ PrivateKey: key,
+ ConfirmBeforeUse: true,
+ })
+ if err == nil {
+ t.Fatal("adding a key with confirm before use constraint succeeded")
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/agent/server.go b/local_crypto_patch/contents/ssh/agent/server.go
new file mode 100644
index 0000000000..248ab6a9f9
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/agent/server.go
@@ -0,0 +1,579 @@
+// 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
+ }
+ if len(k.Priv) != ed25519.PrivateKeySize {
+ return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
+ }
+ 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
+ }
+ if len(k.Priv) != ed25519.PrivateKeySize {
+ return nil, fmt.Errorf("agent: bad ED25519 key size: %d", len(k.Priv))
+ }
+ 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/local_crypto_patch/contents/ssh/agent/server_test.go b/local_crypto_patch/contents/ssh/agent/server_test.go
new file mode 100644
index 0000000000..9e790eb428
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/agent/server_test.go
@@ -0,0 +1,305 @@
+// 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"
+ "crypto/rand"
+ "fmt"
+ "io"
+ pseudorand "math/rand"
+ "reflect"
+ "strings"
+ "testing"
+
+ "golang.org/x/crypto/ssh"
+)
+
+func TestServer(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+ client := NewClient(c1)
+
+ go ServeAgent(NewKeyring(), c2)
+
+ testAgentInterface(t, client, testPrivateKeys["rsa"], nil, 0)
+}
+
+func TestLockServer(t *testing.T) {
+ testLockAgent(NewKeyring(), t)
+}
+
+func TestSetupForwardAgent(t *testing.T) {
+ a, b, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+
+ defer a.Close()
+ defer b.Close()
+
+ _, socket, cleanup := startOpenSSHAgent(t)
+ defer cleanup()
+
+ serverConf := ssh.ServerConfig{
+ NoClientAuth: true,
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+ incoming := make(chan *ssh.ServerConn, 1)
+ go func() {
+ conn, _, _, err := ssh.NewServerConn(a, &serverConf)
+ incoming <- conn
+ if err != nil {
+ t.Errorf("NewServerConn error: %v", err)
+ return
+ }
+ }()
+
+ conf := ssh.ClientConfig{
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ }
+ conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
+ if err != nil {
+ t.Fatalf("NewClientConn: %v", err)
+ }
+ client := ssh.NewClient(conn, chans, reqs)
+
+ if err := ForwardToRemote(client, socket); err != nil {
+ t.Fatalf("SetupForwardAgent: %v", err)
+ }
+ server := <-incoming
+ if server == nil {
+ t.Fatal("Unable to get server")
+ }
+ ch, reqs, err := server.OpenChannel(channelType, nil)
+ if err != nil {
+ t.Fatalf("OpenChannel(%q): %v", channelType, err)
+ }
+ go ssh.DiscardRequests(reqs)
+
+ agentClient := NewClient(ch)
+ testAgentInterface(t, agentClient, testPrivateKeys["rsa"], nil, 0)
+ conn.Close()
+}
+
+func TestV1ProtocolMessages(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+ c := NewClient(c1)
+
+ go ServeAgent(NewKeyring(), c2)
+
+ testV1ProtocolMessages(t, c.(*client))
+}
+
+func testV1ProtocolMessages(t *testing.T, c *client) {
+ reply, err := c.call([]byte{agentRequestV1Identities})
+ if err != nil {
+ t.Fatalf("v1 request all failed: %v", err)
+ }
+ if msg, ok := reply.(*agentV1IdentityMsg); !ok || msg.Numkeys != 0 {
+ t.Fatalf("invalid request all response: %#v", reply)
+ }
+
+ reply, err = c.call([]byte{agentRemoveAllV1Identities})
+ if err != nil {
+ t.Fatalf("v1 remove all failed: %v", err)
+ }
+ if _, ok := reply.(*successAgentMsg); !ok {
+ t.Fatalf("invalid remove all response: %#v", reply)
+ }
+}
+
+func verifyKey(sshAgent Agent) error {
+ keys, err := sshAgent.List()
+ if err != nil {
+ return fmt.Errorf("listing keys: %v", err)
+ }
+
+ if len(keys) != 1 {
+ return fmt.Errorf("bad number of keys found. expected 1, got %d", len(keys))
+ }
+
+ buf := make([]byte, 128)
+ if _, err := rand.Read(buf); err != nil {
+ return fmt.Errorf("rand: %v", err)
+ }
+
+ sig, err := sshAgent.Sign(keys[0], buf)
+ if err != nil {
+ return fmt.Errorf("sign: %v", err)
+ }
+
+ if err := keys[0].Verify(buf, sig); err != nil {
+ return fmt.Errorf("verify: %v", err)
+ }
+ return nil
+}
+
+func addKeyToAgent(key crypto.PrivateKey) error {
+ sshAgent := NewKeyring()
+ if err := sshAgent.Add(AddedKey{PrivateKey: key}); err != nil {
+ return fmt.Errorf("add: %v", err)
+ }
+ return verifyKey(sshAgent)
+}
+
+func TestKeyTypes(t *testing.T) {
+ for k, v := range testPrivateKeys {
+ if err := addKeyToAgent(v); err != nil {
+ t.Errorf("error adding key type %s, %v", k, err)
+ }
+ if err := addCertToAgentSock(v, nil); err != nil {
+ t.Errorf("error adding key type %s, %v", k, err)
+ }
+ }
+}
+
+func addCertToAgentSock(key crypto.PrivateKey, cert *ssh.Certificate) error {
+ a, b, err := netPipe()
+ if err != nil {
+ return err
+ }
+ agentServer := NewKeyring()
+ go ServeAgent(agentServer, a)
+
+ agentClient := NewClient(b)
+ if err := agentClient.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
+ return fmt.Errorf("add: %v", err)
+ }
+ return verifyKey(agentClient)
+}
+
+func addCertToAgent(key crypto.PrivateKey, cert *ssh.Certificate) error {
+ sshAgent := NewKeyring()
+ if err := sshAgent.Add(AddedKey{PrivateKey: key, Certificate: cert}); err != nil {
+ return fmt.Errorf("add: %v", err)
+ }
+ return verifyKey(sshAgent)
+}
+
+func TestCertTypes(t *testing.T) {
+ for keyType, key := range testPublicKeys {
+ cert := &ssh.Certificate{
+ ValidPrincipals: []string{"gopher1"},
+ ValidAfter: 0,
+ ValidBefore: ssh.CertTimeInfinity,
+ Key: key,
+ Serial: 1,
+ CertType: ssh.UserCert,
+ SignatureKey: testPublicKeys["rsa"],
+ Permissions: ssh.Permissions{
+ CriticalOptions: map[string]string{},
+ Extensions: map[string]string{},
+ },
+ }
+ if err := cert.SignCert(rand.Reader, testSigners["rsa"]); err != nil {
+ t.Fatalf("signcert: %v", err)
+ }
+ if err := addCertToAgent(testPrivateKeys[keyType], cert); err != nil {
+ t.Fatalf("%v", err)
+ }
+ if err := addCertToAgentSock(testPrivateKeys[keyType], cert); err != nil {
+ t.Fatalf("%v", err)
+ }
+ }
+}
+
+func TestParseConstraints(t *testing.T) {
+ // Test LifetimeSecs
+ var msg = constrainLifetimeAgentMsg{pseudorand.Uint32()}
+ lifetimeSecs, _, _, err := parseConstraints(ssh.Marshal(msg))
+ if err != nil {
+ t.Fatalf("parseConstraints: %v", err)
+ }
+ if lifetimeSecs != msg.LifetimeSecs {
+ t.Errorf("got lifetime %v, want %v", lifetimeSecs, msg.LifetimeSecs)
+ }
+
+ // Test ConfirmBeforeUse
+ _, confirmBeforeUse, _, err := parseConstraints([]byte{agentConstrainConfirm})
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ if !confirmBeforeUse {
+ t.Error("got comfirmBeforeUse == false")
+ }
+
+ // Test ConstraintExtensions
+ var data []byte
+ var expect []ConstraintExtension
+ for i := 0; i < 10; i++ {
+ var ext = ConstraintExtension{
+ ExtensionName: fmt.Sprintf("name%d", i),
+ ExtensionDetails: []byte(fmt.Sprintf("details: %d", i)),
+ }
+ expect = append(expect, ext)
+ if i%2 == 0 {
+ data = append(data, agentConstrainExtension)
+ } else {
+ data = append(data, agentConstrainExtensionV00)
+ }
+ data = append(data, ssh.Marshal(ext)...)
+ }
+ _, _, extensions, err := parseConstraints(data)
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ if !reflect.DeepEqual(expect, extensions) {
+ t.Errorf("got extension %v, want %v", extensions, expect)
+ }
+
+ // Test Malformed Constraint
+ _, _, _, err = parseConstraints([]byte{1})
+ if err != io.ErrUnexpectedEOF {
+ t.Errorf("got %v, want %v", err, io.ErrUnexpectedEOF)
+ }
+
+ // Test Unknown Constraint
+ _, _, _, err = parseConstraints([]byte{128})
+ if err == nil || !strings.Contains(err.Error(), "unknown constraint") {
+ t.Errorf("unexpected error: %v", err)
+ }
+}
+
+func TestParseEd25519KeyShortPanic(t *testing.T) {
+ msg := ssh.Marshal(ed25519KeyMsg{
+ Type: ssh.KeyAlgoED25519,
+ Pub: []byte{1, 2, 3},
+ Priv: []byte{1, 2, 3, 4, 5},
+ })
+
+ a, b, err := netPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer a.Close()
+ defer b.Close()
+
+ done := make(chan error, 1)
+ go func() { done <- ServeAgent(NewKeyring(), a) }()
+
+ c := NewClient(b)
+ _, err = c.(*client).call(msg)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ keys, err := c.List()
+ if err != nil {
+ t.Fatalf("agent died: %v", err)
+ }
+ if len(keys) != 0 {
+ t.Error("short ed25519 key was accepted into keyring")
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/agent/testdata_test.go b/local_crypto_patch/contents/ssh/agent/testdata_test.go
new file mode 100644
index 0000000000..cc42a87cb9
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/agent/testdata_test.go
@@ -0,0 +1,64 @@
+// 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.
+
+// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places:
+// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
+// instances.
+
+package agent
+
+import (
+ "crypto/rand"
+ "fmt"
+
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/testdata"
+)
+
+var (
+ testPrivateKeys map[string]interface{}
+ testSigners map[string]ssh.Signer
+ testPublicKeys map[string]ssh.PublicKey
+)
+
+func init() {
+ var err error
+
+ n := len(testdata.PEMBytes)
+ testPrivateKeys = make(map[string]interface{}, n)
+ testSigners = make(map[string]ssh.Signer, n)
+ testPublicKeys = make(map[string]ssh.PublicKey, n)
+ for t, k := range testdata.PEMBytes {
+ testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k)
+ if err != nil {
+ panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
+ }
+ testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t])
+ if err != nil {
+ panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
+ }
+ testPublicKeys[t] = testSigners[t].PublicKey()
+ }
+
+ // Create a cert and sign it for use in tests.
+ testCert := &ssh.Certificate{
+ Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
+ ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
+ ValidAfter: 0, // unix epoch
+ ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time.
+ Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
+ Key: testPublicKeys["ecdsa"],
+ SignatureKey: testPublicKeys["rsa"],
+ Permissions: ssh.Permissions{
+ CriticalOptions: map[string]string{},
+ Extensions: map[string]string{},
+ },
+ }
+ testCert.SignCert(rand.Reader, testSigners["rsa"])
+ testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
+ testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"])
+ if err != nil {
+ panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/benchmark_test.go b/local_crypto_patch/contents/ssh/benchmark_test.go
new file mode 100644
index 0000000000..b356330b46
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/benchmark_test.go
@@ -0,0 +1,127 @@
+// 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"
+ "net"
+ "testing"
+)
+
+type server struct {
+ *ServerConn
+ chans <-chan NewChannel
+}
+
+func newServer(c net.Conn, conf *ServerConfig) (*server, error) {
+ sconn, chans, reqs, err := NewServerConn(c, conf)
+ if err != nil {
+ return nil, err
+ }
+ go DiscardRequests(reqs)
+ return &server{sconn, chans}, nil
+}
+
+func (s *server) Accept() (NewChannel, error) {
+ n, ok := <-s.chans
+ if !ok {
+ return nil, io.EOF
+ }
+ return n, nil
+}
+
+func sshPipe() (Conn, *server, error) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ clientConf := ClientConfig{
+ User: "user",
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ serverConf := ServerConfig{
+ NoClientAuth: true,
+ }
+ serverConf.AddHostKey(testSigners["ecdsa"])
+ done := make(chan *server, 1)
+ go func() {
+ server, err := newServer(c2, &serverConf)
+ if err != nil {
+ done <- nil
+ }
+ done <- server
+ }()
+
+ client, _, reqs, err := NewClientConn(c1, "", &clientConf)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ server := <-done
+ if server == nil {
+ return nil, nil, errors.New("server handshake failed.")
+ }
+ go DiscardRequests(reqs)
+
+ return client, server, nil
+}
+
+func BenchmarkEndToEnd(b *testing.B) {
+ b.StopTimer()
+
+ client, server, err := sshPipe()
+ if err != nil {
+ b.Fatalf("sshPipe: %v", err)
+ }
+
+ defer client.Close()
+ defer server.Close()
+
+ size := (1 << 20)
+ input := make([]byte, size)
+ output := make([]byte, size)
+ b.SetBytes(int64(size))
+ done := make(chan int, 1)
+
+ go func() {
+ newCh, err := server.Accept()
+ if err != nil {
+ panic(fmt.Sprintf("Client: %v", err))
+ }
+ ch, incoming, err := newCh.Accept()
+ if err != nil {
+ panic(fmt.Sprintf("Accept: %v", err))
+ }
+ go DiscardRequests(incoming)
+ for i := 0; i < b.N; i++ {
+ if _, err := io.ReadFull(ch, output); err != nil {
+ panic(fmt.Sprintf("ReadFull: %v", err))
+ }
+ }
+ ch.Close()
+ done <- 1
+ }()
+
+ ch, in, err := client.OpenChannel("speed", nil)
+ if err != nil {
+ b.Fatalf("OpenChannel: %v", err)
+ }
+ go DiscardRequests(in)
+
+ b.ResetTimer()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ if _, err := ch.Write(input); err != nil {
+ b.Fatalf("WriteFull: %v", err)
+ }
+ }
+ ch.Close()
+ b.StopTimer()
+
+ <-done
+}
diff --git a/local_crypto_patch/contents/ssh/buffer.go b/local_crypto_patch/contents/ssh/buffer.go
new file mode 100644
index 0000000000..1ab07d078d
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/buffer_test.go b/local_crypto_patch/contents/ssh/buffer_test.go
new file mode 100644
index 0000000000..d5781cb3da
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/buffer_test.go
@@ -0,0 +1,87 @@
+// 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 (
+ "io"
+ "testing"
+)
+
+var alphabet = []byte("abcdefghijklmnopqrstuvwxyz")
+
+func TestBufferReadwrite(t *testing.T) {
+ b := newBuffer()
+ b.write(alphabet[:10])
+ r, _ := b.Read(make([]byte, 10))
+ if r != 10 {
+ t.Fatalf("Expected written == read == 10, written: 10, read %d", r)
+ }
+
+ b = newBuffer()
+ b.write(alphabet[:5])
+ r, _ = b.Read(make([]byte, 10))
+ if r != 5 {
+ t.Fatalf("Expected written == read == 5, written: 5, read %d", r)
+ }
+
+ b = newBuffer()
+ b.write(alphabet[:10])
+ r, _ = b.Read(make([]byte, 5))
+ if r != 5 {
+ t.Fatalf("Expected written == 10, read == 5, written: 10, read %d", r)
+ }
+
+ b = newBuffer()
+ b.write(alphabet[:5])
+ b.write(alphabet[5:15])
+ r, _ = b.Read(make([]byte, 10))
+ r2, _ := b.Read(make([]byte, 10))
+ if r != 10 || r2 != 5 || 15 != r+r2 {
+ t.Fatal("Expected written == read == 15")
+ }
+}
+
+func TestBufferClose(t *testing.T) {
+ b := newBuffer()
+ b.write(alphabet[:10])
+ b.eof()
+ _, err := b.Read(make([]byte, 5))
+ if err != nil {
+ t.Fatal("expected read of 5 to not return EOF")
+ }
+ b = newBuffer()
+ b.write(alphabet[:10])
+ b.eof()
+ r, err := b.Read(make([]byte, 5))
+ r2, err2 := b.Read(make([]byte, 10))
+ if r != 5 || r2 != 5 || err != nil || err2 != nil {
+ t.Fatal("expected reads of 5 and 5")
+ }
+
+ b = newBuffer()
+ b.write(alphabet[:10])
+ b.eof()
+ r, err = b.Read(make([]byte, 5))
+ r2, err2 = b.Read(make([]byte, 10))
+ r3, err3 := b.Read(make([]byte, 10))
+ if r != 5 || r2 != 5 || r3 != 0 || err != nil || err2 != nil || err3 != io.EOF {
+ t.Fatal("expected reads of 5 and 5 and 0, with EOF")
+ }
+
+ b = newBuffer()
+ b.write(make([]byte, 5))
+ b.write(make([]byte, 10))
+ b.eof()
+ r, err = b.Read(make([]byte, 9))
+ r2, err2 = b.Read(make([]byte, 3))
+ r3, err3 = b.Read(make([]byte, 3))
+ r4, err4 := b.Read(make([]byte, 10))
+ if err != nil || err2 != nil || err3 != nil || err4 != io.EOF {
+ t.Fatalf("Expected EOF on forth read only, err=%v, err2=%v, err3=%v, err4=%v", err, err2, err3, err4)
+ }
+ if r != 9 || r2 != 3 || r3 != 3 || r4 != 0 {
+ t.Fatal("Expected written == read == 15", r, r2, r3, r4)
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/certs.go b/local_crypto_patch/contents/ssh/certs.go
new file mode 100644
index 0000000000..6f75d77ec1
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/certs.go
@@ -0,0 +1,640 @@
+// 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 == nil {
+ return errors.New("ssh: cannot verify certificate, IsHostAuthority not set")
+ }
+ 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 == nil {
+ return nil, errors.New("ssh: cannot verify certificate, IsUserAuthority not set")
+ }
+ 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")
+ }
+ // Match OpenSSH: the SK user-presence flag is never enforced on a
+ // certificate's CA signature. OpenSSH calls sshkey_verify with
+ // detailsp==NULL in sshkey.c:cert_parse, so the UP/UV flags are
+ // not even extracted. The UP bit on a CA signature reflects the
+ // CA operator's presence at signing time, which has no bearing on
+ // whether the user being authenticated is present now; enforcing
+ // it here would only break interop with certificates issued by
+ // non-interactive SK CAs. skKeyWithoutUP is a no-op for non-SK
+ // keys (the common case).
+ caKey := skKeyWithoutUP(cert.SignatureKey)
+ if err := caKey.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/local_crypto_patch/contents/ssh/certs_test.go b/local_crypto_patch/contents/ssh/certs_test.go
new file mode 100644
index 0000000000..43280e34f7
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/certs_test.go
@@ -0,0 +1,532 @@
+// 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 (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/sha256"
+ "fmt"
+ "io"
+ "math/big"
+ "net"
+ "reflect"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/ssh/testdata"
+)
+
+func TestParseCert(t *testing.T) {
+ authKeyBytes := bytes.TrimSuffix(testdata.SSHCertificates["rsa"], []byte(" host.example.com\n"))
+
+ key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
+ if err != nil {
+ t.Fatalf("ParseAuthorizedKey: %v", err)
+ }
+ if len(rest) > 0 {
+ t.Errorf("rest: got %q, want empty", rest)
+ }
+
+ if _, ok := key.(*Certificate); !ok {
+ t.Fatalf("got %v (%T), want *Certificate", key, key)
+ }
+
+ marshaled := MarshalAuthorizedKey(key)
+ // Before comparison, remove the trailing newline that
+ // MarshalAuthorizedKey adds.
+ marshaled = marshaled[:len(marshaled)-1]
+ if !bytes.Equal(authKeyBytes, marshaled) {
+ t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
+ }
+}
+
+// Cert generated by ssh-keygen OpenSSH_6.8p1 OS X 10.10.3
+// % ssh-keygen -s ca -I testcert -O source-address=192.168.1.0/24 -O force-command=/bin/sleep user.pub
+// user.pub key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMN
+// Critical Options:
+//
+// force-command /bin/sleep
+// source-address 192.168.1.0/24
+//
+// Extensions:
+//
+// permit-X11-forwarding
+// permit-agent-forwarding
+// permit-port-forwarding
+// permit-pty
+// permit-user-rc
+const exampleSSHCertWithOptions = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgDyysCJY0XrO1n03EeRRoITnTPdjENFmWDs9X58PP3VUAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMNAAAAAAAAAAAAAAABAAAACHRlc3RjZXJ0AAAAAAAAAAAAAAAA//////////8AAABLAAAADWZvcmNlLWNvbW1hbmQAAAAOAAAACi9iaW4vc2xlZXAAAAAOc291cmNlLWFkZHJlc3MAAAASAAAADjE5Mi4xNjguMS4wLzI0AAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAwU+c5ui5A8+J/CFpjW8wCa52bEODA808WWQDCSuTG/eMXNf59v9Y8Pk0F1E9dGCosSNyVcB/hacUrc6He+i97+HJCyKavBsE6GDxrjRyxYqAlfcOXi/IVmaUGiO8OQ39d4GHrjToInKvExSUeleQyH4Y4/e27T/pILAqPFL3fyrvMLT5qU9QyIt6zIpa7GBP5+urouNavMprV3zsfIqNBbWypinOQAw823a5wN+zwXnhZrgQiHZ/USG09Y6k98y1dTVz8YHlQVR4D3lpTAsKDKJ5hCH9WU4fdf+lU8OyNGaJ/vz0XNqxcToe1l4numLTnaoSuH89pHryjqurB7lJKwAAAQ8AAAAHc3NoLXJzYQAAAQCaHvUIoPL1zWUHIXLvu96/HU1s/i4CAW2IIEuGgxCUCiFj6vyTyYtgxQxcmbfZf6eaITlS6XJZa7Qq4iaFZh75C1DXTX8labXhRSD4E2t//AIP9MC1rtQC5xo6FmbQ+BoKcDskr+mNACcbRSxs3IL3bwCfWDnIw2WbVox9ZdcthJKk4UoCW4ix4QwdHw7zlddlz++fGEEVhmTbll1SUkycGApPFBsAYRTMupUJcYPIeReBI/m8XfkoMk99bV8ZJQTAd7OekHY2/48Ff53jLmyDjP7kNw1F8OaPtkFs6dGJXta4krmaekPy87j+35In5hFj7yoOqvSbmYUkeX70/GGQ`
+
+func TestParseCertWithOptions(t *testing.T) {
+ opts := map[string]string{
+ "source-address": "192.168.1.0/24",
+ "force-command": "/bin/sleep",
+ }
+ exts := map[string]string{
+ "permit-X11-forwarding": "",
+ "permit-agent-forwarding": "",
+ "permit-port-forwarding": "",
+ "permit-pty": "",
+ "permit-user-rc": "",
+ }
+ authKeyBytes := []byte(exampleSSHCertWithOptions)
+
+ key, _, _, rest, err := ParseAuthorizedKey(authKeyBytes)
+ if err != nil {
+ t.Fatalf("ParseAuthorizedKey: %v", err)
+ }
+ if len(rest) > 0 {
+ t.Errorf("rest: got %q, want empty", rest)
+ }
+ cert, ok := key.(*Certificate)
+ if !ok {
+ t.Fatalf("got %v (%T), want *Certificate", key, key)
+ }
+ if !reflect.DeepEqual(cert.CriticalOptions, opts) {
+ t.Errorf("unexpected critical options - got %v, want %v", cert.CriticalOptions, opts)
+ }
+ if !reflect.DeepEqual(cert.Extensions, exts) {
+ t.Errorf("unexpected Extensions - got %v, want %v", cert.Extensions, exts)
+ }
+ marshaled := MarshalAuthorizedKey(key)
+ // Before comparison, remove the trailing newline that
+ // MarshalAuthorizedKey adds.
+ marshaled = marshaled[:len(marshaled)-1]
+ if !bytes.Equal(authKeyBytes, marshaled) {
+ t.Errorf("marshaled certificate does not match original: got %q, want %q", marshaled, authKeyBytes)
+ }
+}
+
+func TestValidateCert(t *testing.T) {
+ key, _, _, _, err := ParseAuthorizedKey(testdata.SSHCertificates["rsa-user-testcertificate"])
+ if err != nil {
+ t.Fatalf("ParseAuthorizedKey: %v", err)
+ }
+ validCert, ok := key.(*Certificate)
+ if !ok {
+ t.Fatalf("got %v (%T), want *Certificate", key, key)
+ }
+ checker := CertChecker{}
+ checker.IsUserAuthority = func(k PublicKey) bool {
+ return bytes.Equal(k.Marshal(), validCert.SignatureKey.Marshal())
+ }
+
+ if err := checker.CheckCert("testcertificate", validCert); err != nil {
+ t.Errorf("Unable to validate certificate: %v", err)
+ }
+ invalidCert := &Certificate{
+ Key: testPublicKeys["rsa"],
+ SignatureKey: testPublicKeys["ecdsa"],
+ ValidBefore: CertTimeInfinity,
+ Signature: &Signature{},
+ }
+ if err := checker.CheckCert("testcertificate", invalidCert); err == nil {
+ t.Error("Invalid cert signature passed validation")
+ }
+}
+
+// signSKCert builds an SK-ECDSA signature over cert.bytesForSigning()
+// using caKey as the simulated hardware token. The SK signing flags
+// byte (UP bit and others) is caller-controlled so tests can exercise
+// the user-presence enforcement paths. caApp is the SK application
+// string (typically "ssh:").
+func signSKCert(t *testing.T, cert *Certificate, caKey *ecdsa.PrivateKey, caApp string, flags byte) {
+ t.Helper()
+ cert.SignatureKey = &skECDSAPublicKey{
+ application: caApp,
+ PublicKey: caKey.PublicKey,
+ }
+ cert.Nonce = make([]byte, 32)
+ if _, err := rand.Read(cert.Nonce); err != nil {
+ t.Fatal(err)
+ }
+
+ h := sha256.New()
+ h.Write([]byte(caApp))
+ appDigest := h.Sum(nil)
+ h.Reset()
+ h.Write(cert.bytesForSigning())
+ dataDigest := h.Sum(nil)
+
+ var counter uint32 = 1
+ blob := struct {
+ ApplicationDigest []byte `ssh:"rest"`
+ Flags byte
+ Counter uint32
+ MessageDigest []byte `ssh:"rest"`
+ }{appDigest, flags, counter, dataDigest}
+ h.Reset()
+ h.Write(Marshal(blob))
+ digest := h.Sum(nil)
+
+ r, s, err := ecdsa.Sign(rand.Reader, caKey, digest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cert.Signature = &Signature{
+ Format: KeyAlgoSKECDSA256,
+ Blob: Marshal(struct{ R, S *big.Int }{r, s}),
+ Rest: Marshal(struct {
+ Flags byte
+ Counter uint32
+ }{flags, counter}),
+ }
+}
+
+// TestCheckCertSKAuthorityNoUPRequired pins the OpenSSH-parity
+// behavior of CertChecker.CheckCert for SK CA signatures: a
+// certificate signed by an SK CA that produced a UP=0 signature is
+// accepted, because the UP bit on a CA signature has no bearing on
+// the current user authentication (see sshkey.c:cert_parse, which
+// passes detailsp==NULL to sshkey_verify). Without this, certs issued
+// by non-interactive SK CAs would fail to verify.
+func TestCheckCertSKAuthorityNoUPRequired(t *testing.T) {
+ caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ userKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ userPub, err := NewPublicKey(&userKey.PublicKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ mkCert := func(flags byte) *Certificate {
+ c := &Certificate{
+ CertType: UserCert,
+ Key: userPub,
+ ValidBefore: CertTimeInfinity,
+ ValidPrincipals: []string{"user"},
+ }
+ signSKCert(t, c, caKey, "ssh:", flags)
+ return c
+ }
+
+ checker := CertChecker{
+ IsUserAuthority: func(k PublicKey) bool {
+ caSK := &skECDSAPublicKey{application: "ssh:", PublicKey: caKey.PublicKey}
+ return bytes.Equal(k.Marshal(), caSK.Marshal())
+ },
+ }
+
+ // Both UP=0 and UP=1 CA signatures verify: UP is not enforced
+ // here, matching OpenSSH.
+ if err := checker.CheckCert("user", mkCert(0)); err != nil {
+ t.Errorf("UP=0 CA signature must verify (OpenSSH parity): %v", err)
+ }
+ if err := checker.CheckCert("user", mkCert(flagUserPresence)); err != nil {
+ t.Errorf("UP=1 CA signature must verify: %v", err)
+ }
+}
+
+func TestValidateCertTime(t *testing.T) {
+ cert := Certificate{
+ ValidPrincipals: []string{"user"},
+ Key: testPublicKeys["rsa"],
+ ValidAfter: 50,
+ ValidBefore: 100,
+ }
+
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+
+ for ts, ok := range map[int64]bool{
+ 25: false,
+ 50: true,
+ 99: true,
+ 100: false,
+ 125: false,
+ } {
+ checker := CertChecker{
+ Clock: func() time.Time { return time.Unix(ts, 0) },
+ }
+ checker.IsUserAuthority = func(k PublicKey) bool {
+ return bytes.Equal(k.Marshal(),
+ testPublicKeys["ecdsa"].Marshal())
+ }
+
+ if v := checker.CheckCert("user", &cert); (v == nil) != ok {
+ t.Errorf("Authenticate(%d): %v", ts, v)
+ }
+ }
+}
+
+// TODO(hanwen): tests for
+//
+// host keys:
+// * fallbacks
+
+func TestHostKeyCert(t *testing.T) {
+ cert := &Certificate{
+ ValidPrincipals: []string{"hostname", "hostname.domain", "otherhost"},
+ Key: testPublicKeys["rsa"],
+ ValidBefore: CertTimeInfinity,
+ CertType: HostCert,
+ }
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+
+ checker := &CertChecker{
+ IsHostAuthority: func(p PublicKey, addr string) bool {
+ return addr == "hostname:22" && bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
+ },
+ }
+
+ certSigner, err := NewCertSigner(cert, testSigners["rsa"])
+ if err != nil {
+ t.Errorf("NewCertSigner: %v", err)
+ }
+
+ for _, test := range []struct {
+ addr string
+ succeed bool
+ certSignerAlgorithms []string // Empty means no algorithm restrictions.
+ clientHostKeyAlgorithms []string
+ }{
+ {addr: "hostname:22", succeed: true},
+ {
+ addr: "hostname:22",
+ succeed: true,
+ certSignerAlgorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
+ clientHostKeyAlgorithms: []string{CertAlgoRSASHA512v01},
+ },
+ {
+ addr: "hostname:22",
+ succeed: false,
+ certSignerAlgorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
+ clientHostKeyAlgorithms: []string{CertAlgoRSAv01},
+ },
+ {
+ addr: "hostname:22",
+ succeed: false,
+ certSignerAlgorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
+ clientHostKeyAlgorithms: []string{KeyAlgoRSASHA512}, // Not a certificate algorithm.
+ },
+ {addr: "otherhost:22", succeed: false}, // The certificate is valid for 'otherhost' as hostname, but we only recognize the authority of the signer for the address 'hostname:22'
+ {addr: "lasthost:22", succeed: false},
+ } {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ errc := make(chan error)
+
+ go func() {
+ conf := ServerConfig{
+ NoClientAuth: true,
+ }
+ if len(test.certSignerAlgorithms) > 0 {
+ mas, err := NewSignerWithAlgorithms(certSigner.(AlgorithmSigner), test.certSignerAlgorithms)
+ if err != nil {
+ errc <- err
+ return
+ }
+ conf.AddHostKey(mas)
+ } else {
+ conf.AddHostKey(certSigner)
+ }
+ _, _, _, err := NewServerConn(c1, &conf)
+ errc <- err
+ }()
+
+ config := &ClientConfig{
+ User: "user",
+ HostKeyCallback: checker.CheckHostKey,
+ HostKeyAlgorithms: test.clientHostKeyAlgorithms,
+ }
+ _, _, _, err = NewClientConn(c2, test.addr, config)
+
+ if (err == nil) != test.succeed {
+ t.Errorf("NewClientConn(%q): %v", test.addr, err)
+ }
+
+ err = <-errc
+ if (err == nil) != test.succeed {
+ t.Errorf("NewServerConn(%q): %v", test.addr, err)
+ }
+ }
+}
+
+type legacyRSASigner struct {
+ Signer
+}
+
+func (s *legacyRSASigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
+ v, ok := s.Signer.(AlgorithmSigner)
+ if !ok {
+ return nil, fmt.Errorf("invalid signer")
+ }
+ return v.SignWithAlgorithm(rand, data, KeyAlgoRSA)
+}
+
+func TestCertTypes(t *testing.T) {
+ algorithmSigner, ok := testSigners["rsa"].(AlgorithmSigner)
+ if !ok {
+ t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
+ }
+ multiAlgoSignerSHA256, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA256})
+ if err != nil {
+ t.Fatalf("unable to create multi algorithm signer SHA256: %v", err)
+ }
+ // Algorithms are in order of preference, we expect rsa-sha2-512 to be used.
+ multiAlgoSignerSHA512, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256})
+ if err != nil {
+ t.Fatalf("unable to create multi algorithm signer SHA512: %v", err)
+ }
+
+ var testVars = []struct {
+ name string
+ signer Signer
+ algo string
+ }{
+ {CertAlgoECDSA256v01, testSigners["ecdsap256"], ""},
+ {CertAlgoECDSA384v01, testSigners["ecdsap384"], ""},
+ {CertAlgoECDSA521v01, testSigners["ecdsap521"], ""},
+ {CertAlgoED25519v01, testSigners["ed25519"], ""},
+ {CertAlgoRSAv01, testSigners["rsa"], KeyAlgoRSASHA256},
+ {"legacyRSASigner", &legacyRSASigner{testSigners["rsa"]}, KeyAlgoRSA},
+ {"multiAlgoRSASignerSHA256", multiAlgoSignerSHA256, KeyAlgoRSASHA256},
+ {"multiAlgoRSASignerSHA512", multiAlgoSignerSHA512, KeyAlgoRSASHA512},
+ {InsecureCertAlgoDSAv01, testSigners["dsa"], ""},
+ }
+
+ k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatalf("error generating host key: %v", err)
+ }
+
+ signer, err := NewSignerFromKey(k)
+ if err != nil {
+ t.Fatalf("error generating signer for ssh listener: %v", err)
+ }
+
+ conf := &ServerConfig{
+ PublicKeyCallback: func(c ConnMetadata, k PublicKey) (*Permissions, error) {
+ return new(Permissions), nil
+ },
+ }
+ conf.AddHostKey(signer)
+
+ for _, m := range testVars {
+ t.Run(m.name, func(t *testing.T) {
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ go NewServerConn(c1, conf)
+
+ priv := m.signer
+ cert := &Certificate{
+ CertType: UserCert,
+ Key: priv.PublicKey(),
+ }
+ if err := cert.SignCert(rand.Reader, priv); err != nil {
+ t.Fatalf("error signing certificate: %v", err)
+ }
+
+ certSigner, err := NewCertSigner(cert, priv)
+ if err != nil {
+ t.Fatalf("error generating cert signer: %v", err)
+ }
+
+ if m.algo != "" && cert.Signature.Format != m.algo {
+ t.Errorf("expected %q signature format, got %q", m.algo, cert.Signature.Format)
+ }
+
+ config := &ClientConfig{
+ User: "user",
+ HostKeyCallback: func(h string, r net.Addr, k PublicKey) error { return nil },
+ Auth: []AuthMethod{PublicKeys(certSigner)},
+ }
+
+ _, _, _, err = NewClientConn(c2, "", config)
+ if err != nil {
+ t.Fatalf("error connecting: %v", err)
+ }
+ })
+ }
+}
+
+func TestCertSignWithMultiAlgorithmSigner(t *testing.T) {
+ type testcase struct {
+ sigAlgo string
+ algorithms []string
+ }
+ cases := []testcase{
+ {
+ sigAlgo: KeyAlgoRSA,
+ algorithms: []string{KeyAlgoRSA, KeyAlgoRSASHA512},
+ },
+ {
+ sigAlgo: KeyAlgoRSASHA256,
+ algorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSA, KeyAlgoRSASHA512},
+ },
+ {
+ sigAlgo: KeyAlgoRSASHA512,
+ algorithms: []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256},
+ },
+ }
+
+ cert := &Certificate{
+ Key: testPublicKeys["rsa"],
+ ValidBefore: CertTimeInfinity,
+ CertType: UserCert,
+ }
+
+ for _, c := range cases {
+ t.Run(c.sigAlgo, func(t *testing.T) {
+ signer, err := NewSignerWithAlgorithms(testSigners["rsa"].(AlgorithmSigner), c.algorithms)
+ if err != nil {
+ t.Fatalf("NewSignerWithAlgorithms error: %v", err)
+ }
+ if err := cert.SignCert(rand.Reader, signer); err != nil {
+ t.Fatalf("SignCert error: %v", err)
+ }
+ if cert.Signature.Format != c.sigAlgo {
+ t.Fatalf("got signature format %q, want %q", cert.Signature.Format, c.sigAlgo)
+ }
+ })
+ }
+}
+
+func TestCertSignWithCertificate(t *testing.T) {
+ cert := &Certificate{
+ Key: testPublicKeys["rsa"],
+ ValidBefore: CertTimeInfinity,
+ CertType: UserCert,
+ }
+ if err := cert.SignCert(rand.Reader, testSigners["ecdsa"]); err != nil {
+ t.Fatalf("SignCert: %v", err)
+ }
+ signer, err := NewSignerWithAlgorithms(testSigners["rsa"].(AlgorithmSigner), []string{KeyAlgoRSASHA256})
+ if err != nil {
+ t.Fatal(err)
+ }
+ certSigner, err := NewCertSigner(cert, signer)
+ if err != nil {
+ t.Fatalf("NewCertSigner: %v", err)
+ }
+
+ cert1 := &Certificate{
+ Key: testPublicKeys["ecdsa"],
+ ValidBefore: CertTimeInfinity,
+ CertType: UserCert,
+ }
+
+ if err := cert1.SignCert(rand.Reader, certSigner); err == nil {
+ t.Fatal("successfully signed a certificate using another certificate, it is expected to fail")
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/channel.go b/local_crypto_patch/contents/ssh/channel.go
new file mode 100644
index 0000000000..afc9aef185
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/channel.go
@@ -0,0 +1,701 @@
+// 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"
+ "sync/atomic"
+)
+
+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))
+}
+
+// minPayloadSize returns min(limit, length) clamped to a uint32. It is used
+// to compute the size of the next channel data packet from the remaining
+// payload. The comparison is done in int64 because length is an int — on
+// 64-bit systems len(data) can exceed 2^32, and a direct uint32(length)
+// cast would silently truncate to 0 at every multiple of 2^32, causing
+// WriteExtended's loop to spin without making progress.
+func minPayloadSize(limit uint32, length int) uint32 {
+ if int64(length) > int64(limit) {
+ return limit
+ }
+ return uint32(length)
+}
+
+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
+ // sentRequestPending is set to true while a SendRequest call with
+ // WantReply=true is in flight. handlePacket uses it as a gate: responses
+ // arriving while no request is pending are dropped to prevent a
+ // misbehaving peer from stalling the mux read loop by filling ch.msg
+ // with unsolicited channelRequestSuccess/Failure messages.
+ sentRequestPending atomic.Bool
+
+ 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 := minPayloadSize(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
+ case *channelRequestSuccessMsg, *channelRequestFailureMsg:
+ // Drop responses that arrive when no SendRequest is waiting, to
+ // prevent a malicious peer from filling ch.msg and stalling the
+ // mux read loop. The non-blocking send additionally protects the
+ // loop if a well-behaved caller is slow to read.
+ if !ch.sentRequestPending.Load() {
+ return nil
+ }
+ select {
+ case ch.msg <- msg:
+ default:
+ }
+ 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
+ err := ch.sendMessage(reject)
+
+ // Remove the channel from the mux to prevent memory leaks.
+ // Do not call ch.close() here: no goroutine holds a reference to a
+ // rejected channel's internal channels (msg, incomingRequests), so
+ // removing it from chanList is sufficient for GC. Calling close()
+ // would race with the mux loop goroutine (handlePacket or dropAll),
+ // causing a panic from closing an already-closed channel.
+ ch.mux.chanList.remove(ch.localId)
+
+ return err
+}
+
+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()
+
+ // Open the gate so that responses arriving while this request is in
+ // flight are allowed to reach ch.msg. Responses arriving while no
+ // request is pending are dropped by handlePacket.
+ ch.sentRequestPending.Store(true)
+ defer ch.sentRequestPending.Store(false)
+
+ // Drain any spurious responses that may have been buffered. This
+ // prevents a previously buffered unexpected response from being
+ // consumed instead of the actual response for this request.
+ drain:
+ for {
+ select {
+ case _, ok := <-ch.msg:
+ if !ok {
+ break drain
+ }
+ default:
+ break drain
+ }
+ }
+ }
+
+ 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/local_crypto_patch/contents/ssh/channel_test.go b/local_crypto_patch/contents/ssh/channel_test.go
new file mode 100644
index 0000000000..2266863064
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/channel_test.go
@@ -0,0 +1,140 @@
+// 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 ssh
+
+import (
+ "math/bits"
+ "testing"
+ "time"
+ "unsafe"
+)
+
+func TestMinPayloadSize(t *testing.T) {
+ // 4 GiB (2^32). Declared as a var (not a const) so that int(bigPayload)
+ // is a runtime conversion: a constant conversion would fail to compile
+ // on 32-bit platforms with "constant 4294967296 overflows int". On
+ // 32-bit the value truncates to 0 at runtime, but the is64Bit cases
+ // that reference it are skipped by the runtime check below.
+ var bigPayload int64 = 1 << 32
+
+ tests := []struct {
+ name string
+ maxPayload uint32
+ dataLen int
+ want uint32
+ is64Bit bool // Flag to run only on 64-bit architectures
+ }{
+ {
+ name: "Normal Case - Data fits in payload",
+ maxPayload: 32768,
+ dataLen: 1000,
+ want: 1000,
+ },
+ {
+ name: "Normal Case - Data larger than payload",
+ maxPayload: 32768,
+ dataLen: 50000,
+ want: 32768,
+ },
+ {
+ name: "Boundary Case - Data zero",
+ maxPayload: 32768,
+ dataLen: 0,
+ want: 0,
+ },
+ {
+ name: "Overflow Case - Data is exactly 4GB (1<<32)",
+ maxPayload: 32768,
+ dataLen: int(bigPayload),
+ want: 32768,
+ is64Bit: true,
+ },
+ {
+ name: "Overflow Case - Data is 4GB + small amount",
+ maxPayload: 32768,
+ dataLen: int(bigPayload + 100),
+ want: 32768,
+ is64Bit: true,
+ },
+ }
+
+ is64Bit := bits.UintSize == 64
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if tt.is64Bit && !is64Bit {
+ t.Skip("Skipping test requiring 64-bit int")
+ }
+ got := minPayloadSize(tt.maxPayload, tt.dataLen)
+ if got != tt.want {
+ t.Errorf("minPayloadSize(%d, %d) = %d; want %d", tt.maxPayload, tt.dataLen, got, tt.want)
+ }
+ })
+ }
+}
+
+// TestWriteExtendedNoInfiniteLoopOnLargeWrite is an end-to-end regression
+// test for the integer-overflow bug in WriteExtended. Before the fix, a
+// write whose len(data) was a multiple of 2^32 caused minPayloadSize to
+// return 0; WriteExtended then spun forever, reserving 0 bytes per
+// iteration and never advancing the data slice.
+//
+// We exercise the real WriteExtended path with a slice whose declared
+// length is exactly 2^32. Allocating 4 GiB is unnecessary: each iteration
+// only reads up to maxRemotePayload bytes from the head of the slice, and
+// the loop blocks in remoteWin.reserve() once the channel window is
+// exhausted — before the slice base advances past the underlying buffer.
+//
+// With the fix, the loop blocks in reserve(); we detect that via
+// waitWriterBlocked(), then close the window to let WriteExtended return.
+// With the bug, the loop never blocks and the test times out.
+//
+//go:nocheckptr
+func TestWriteExtendedNoInfiniteLoopOnLargeWrite(t *testing.T) {
+ if bits.UintSize < 64 {
+ t.Skip("test requires 64-bit int to construct a slice with len >= 2^32")
+ }
+
+ reader, writer, mux := channelPair(t)
+ defer reader.Close()
+ defer writer.Close()
+ defer mux.Close()
+
+ // Sized to hold the full pre-update remote window so that no iteration
+ // reads past the backing buffer before reserve() blocks.
+ backing := make([]byte, channelWindowSize)
+ var bigLen int64 = 1 << 32
+ bigSlice := unsafe.Slice(&backing[0], int(bigLen))
+
+ done := make(chan int, 1)
+ go func() {
+ n, _ := writer.Write(bigSlice)
+ done <- n
+ }()
+
+ blocked := make(chan struct{})
+ go func() {
+ writer.remoteWin.waitWriterBlocked()
+ close(blocked)
+ }()
+
+ select {
+ case <-blocked:
+ // Good — the loop made progress and is now blocked in reserve().
+ // Close the window to let WriteExtended return.
+ writer.remoteWin.close()
+ case <-time.After(2 * time.Second):
+ t.Fatal("WriteExtended did not block in reserve within 2s — minPayloadSize likely returned 0 (integer overflow regression)")
+ }
+
+ select {
+ case n := <-done:
+ if n == 0 {
+ t.Fatalf("WriteExtended returned n=0; expected progress")
+ }
+ case <-time.After(2 * time.Second):
+ t.Fatal("WriteExtended did not return after closing the window")
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/cipher.go b/local_crypto_patch/contents/ssh/cipher.go
new file mode 100644
index 0000000000..48d0199545
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/cipher_test.go b/local_crypto_patch/contents/ssh/cipher_test.go
new file mode 100644
index 0000000000..ac6da50429
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/cipher_test.go
@@ -0,0 +1,231 @@
+// 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"
+ "crypto"
+ "crypto/rand"
+ "encoding/binary"
+ "io"
+ "testing"
+
+ "golang.org/x/crypto/chacha20"
+ "golang.org/x/crypto/internal/poly1305"
+)
+
+func TestDefaultCiphersExist(t *testing.T) {
+ for _, cipherAlgo := range supportedCiphers {
+ if _, ok := cipherModes[cipherAlgo]; !ok {
+ t.Errorf("supported cipher %q is unknown", cipherAlgo)
+ }
+ }
+ for _, cipherAlgo := range defaultCiphers {
+ if _, ok := cipherModes[cipherAlgo]; !ok {
+ t.Errorf("preferred cipher %q is unknown", cipherAlgo)
+ }
+ }
+}
+
+func TestPacketCiphers(t *testing.T) {
+ defaultMac := HMACSHA256
+ defaultCipher := CipherAES128CTR
+ for cipher := range cipherModes {
+ t.Run("cipher="+cipher,
+ func(t *testing.T) { testPacketCipher(t, cipher, defaultMac) })
+ }
+ for mac := range macModes {
+ t.Run("mac="+mac,
+ func(t *testing.T) { testPacketCipher(t, defaultCipher, mac) })
+ }
+}
+
+func testPacketCipher(t *testing.T, cipher, mac string) {
+ kr := &kexResult{Hash: crypto.SHA1}
+ algs := DirectionAlgorithms{
+ Cipher: cipher,
+ MAC: mac,
+ compression: compressionNone,
+ }
+ client, err := newPacketCipher(clientKeys, algs, kr)
+ if err != nil {
+ t.Fatalf("newPacketCipher(client, %q, %q): %v", cipher, mac, err)
+ }
+ server, err := newPacketCipher(clientKeys, algs, kr)
+ if err != nil {
+ t.Fatalf("newPacketCipher(client, %q, %q): %v", cipher, mac, err)
+ }
+
+ want := "bla bla"
+ input := []byte(want)
+ buf := &bytes.Buffer{}
+ if err := client.writeCipherPacket(0, buf, rand.Reader, input); err != nil {
+ t.Fatalf("writeCipherPacket(%q, %q): %v", cipher, mac, err)
+ }
+
+ packet, err := server.readCipherPacket(0, buf)
+ if err != nil {
+ t.Fatalf("readCipherPacket(%q, %q): %v", cipher, mac, err)
+ }
+
+ if string(packet) != want {
+ t.Errorf("roundtrip(%q, %q): got %q, want %q", cipher, mac, packet, want)
+ }
+}
+
+func TestCBCOracleCounterMeasure(t *testing.T) {
+ kr := &kexResult{Hash: crypto.SHA1}
+ algs := DirectionAlgorithms{
+ Cipher: InsecureCipherAES128CBC,
+ MAC: HMACSHA1,
+ compression: compressionNone,
+ }
+ client, err := newPacketCipher(clientKeys, algs, kr)
+ if err != nil {
+ t.Fatalf("newPacketCipher(client): %v", err)
+ }
+
+ want := "bla bla"
+ input := []byte(want)
+ buf := &bytes.Buffer{}
+ if err := client.writeCipherPacket(0, buf, rand.Reader, input); err != nil {
+ t.Errorf("writeCipherPacket: %v", err)
+ }
+
+ packetSize := buf.Len()
+ buf.Write(make([]byte, 2*maxPacket))
+
+ // We corrupt each byte, but this usually will only test the
+ // 'packet too large' or 'MAC failure' cases.
+ lastRead := -1
+ for i := 0; i < packetSize; i++ {
+ server, err := newPacketCipher(clientKeys, algs, kr)
+ if err != nil {
+ t.Fatalf("newPacketCipher(client): %v", err)
+ }
+
+ fresh := &bytes.Buffer{}
+ fresh.Write(buf.Bytes())
+ fresh.Bytes()[i] ^= 0x01
+
+ before := fresh.Len()
+ _, err = server.readCipherPacket(0, fresh)
+ if err == nil {
+ t.Errorf("corrupt byte %d: readCipherPacket succeeded ", i)
+ continue
+ }
+ if _, ok := err.(cbcError); !ok {
+ t.Errorf("corrupt byte %d: got %v (%T), want cbcError", i, err, err)
+ continue
+ }
+
+ after := fresh.Len()
+ bytesRead := before - after
+ if bytesRead < maxPacket {
+ t.Errorf("corrupt byte %d: read %d bytes, want more than %d", i, bytesRead, maxPacket)
+ continue
+ }
+
+ if i > 0 && bytesRead != lastRead {
+ t.Errorf("corrupt byte %d: read %d bytes, want %d bytes read", i, bytesRead, lastRead)
+ }
+ lastRead = bytesRead
+ }
+}
+
+func TestCVE202143565(t *testing.T) {
+ tests := []struct {
+ cipher string
+ constructPacket func(packetCipher) io.Reader
+ }{
+ {
+ cipher: CipherAES128GCM,
+ constructPacket: func(client packetCipher) io.Reader {
+ internalCipher := client.(*gcmCipher)
+ b := &bytes.Buffer{}
+ prefix := [4]byte{}
+ if _, err := b.Write(prefix[:]); err != nil {
+ t.Fatal(err)
+ }
+ internalCipher.buf = internalCipher.aead.Seal(internalCipher.buf[:0], internalCipher.iv, []byte{}, prefix[:])
+ if _, err := b.Write(internalCipher.buf); err != nil {
+ t.Fatal(err)
+ }
+ internalCipher.incIV()
+
+ return b
+ },
+ },
+ {
+ cipher: CipherChaCha20Poly1305,
+ constructPacket: func(client packetCipher) io.Reader {
+ internalCipher := client.(*chacha20Poly1305Cipher)
+ b := &bytes.Buffer{}
+
+ nonce := make([]byte, 12)
+ s, err := chacha20.NewUnauthenticatedCipher(internalCipher.contentKey[:], nonce)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var polyKey, discardBuf [32]byte
+ s.XORKeyStream(polyKey[:], polyKey[:])
+ s.XORKeyStream(discardBuf[:], discardBuf[:]) // skip the next 32 bytes
+
+ internalCipher.buf = make([]byte, 4+poly1305.TagSize)
+ binary.BigEndian.PutUint32(internalCipher.buf, 0)
+ ls, err := chacha20.NewUnauthenticatedCipher(internalCipher.lengthKey[:], nonce)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ls.XORKeyStream(internalCipher.buf, internalCipher.buf[:4])
+ if _, err := io.ReadFull(rand.Reader, internalCipher.buf[4:4]); err != nil {
+ t.Fatal(err)
+ }
+
+ s.XORKeyStream(internalCipher.buf[4:], internalCipher.buf[4:4])
+
+ var tag [poly1305.TagSize]byte
+ poly1305.Sum(&tag, internalCipher.buf[:4], &polyKey)
+
+ copy(internalCipher.buf[4:], tag[:])
+
+ if _, err := b.Write(internalCipher.buf); err != nil {
+ t.Fatal(err)
+ }
+
+ return b
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ mac := HMACSHA256
+
+ kr := &kexResult{Hash: crypto.SHA1}
+ algs := DirectionAlgorithms{
+ Cipher: tc.cipher,
+ MAC: mac,
+ compression: compressionNone,
+ }
+ client, err := newPacketCipher(clientKeys, algs, kr)
+ if err != nil {
+ t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
+ }
+ server, err := newPacketCipher(clientKeys, algs, kr)
+ if err != nil {
+ t.Fatalf("newPacketCipher(client, %q, %q): %v", tc.cipher, mac, err)
+ }
+
+ b := tc.constructPacket(client)
+
+ wantErr := "ssh: empty packet"
+ _, err = server.readCipherPacket(0, b)
+ if err == nil {
+ t.Fatalf("readCipherPacket(%q, %q): didn't fail with empty packet", tc.cipher, mac)
+ } else if err.Error() != wantErr {
+ t.Fatalf("readCipherPacket(%q, %q): unexpected error, got %q, want %q", tc.cipher, mac, err, wantErr)
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/client.go b/local_crypto_patch/contents/ssh/client.go
new file mode 100644
index 0000000000..33079789bc
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/client_auth.go b/local_crypto_patch/contents/ssh/client_auth.go
new file mode 100644
index 0000000000..4f2f75c367
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/client_auth_test.go b/local_crypto_patch/contents/ssh/client_auth_test.go
new file mode 100644
index 0000000000..199d2078bc
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/client_auth_test.go
@@ -0,0 +1,1434 @@
+// 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"
+ "crypto/rand"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "os"
+ "runtime"
+ "slices"
+ "strings"
+ "testing"
+)
+
+type keyboardInteractive map[string]string
+
+func (cr keyboardInteractive) Challenge(user string, instruction string, questions []string, echos []bool) ([]string, error) {
+ var answers []string
+ for _, q := range questions {
+ answers = append(answers, cr[q])
+ }
+ return answers, nil
+}
+
+// reused internally by tests
+var clientPassword = "tiger"
+
+// tryAuth runs a handshake with a given config against an SSH server
+// with config serverConfig. Returns both client and server side errors.
+func tryAuth(t *testing.T, config *ClientConfig) error {
+ err, _ := tryAuthBothSides(t, config, nil)
+ return err
+}
+
+// tryAuthWithGSSAPIWithMICConfig runs a handshake with a given config against an SSH server
+// with a given GSSAPIWithMICConfig and config serverConfig. Returns both client and server side errors.
+func tryAuthWithGSSAPIWithMICConfig(t *testing.T, clientConfig *ClientConfig, gssAPIWithMICConfig *GSSAPIWithMICConfig) error {
+ err, _ := tryAuthBothSides(t, clientConfig, gssAPIWithMICConfig)
+ return err
+}
+
+// tryAuthBothSides runs the handshake and returns the resulting errors from both sides of the connection.
+func tryAuthBothSides(t *testing.T, config *ClientConfig, gssAPIWithMICConfig *GSSAPIWithMICConfig) (clientError error, serverAuthErrors []error) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ certChecker := CertChecker{
+ IsUserAuthority: func(k PublicKey) bool {
+ return bytes.Equal(k.Marshal(), testPublicKeys["ecdsa"].Marshal())
+ },
+ UserKeyFallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ if conn.User() == "testuser" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, nil
+ }
+
+ return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
+ },
+ IsRevoked: func(c *Certificate) bool {
+ return c.Serial == 666
+ },
+ }
+ serverConfig := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
+ if conn.User() == "testuser" && string(pass) == clientPassword {
+ return nil, nil
+ }
+ return nil, errors.New("password auth failed")
+ },
+ PublicKeyCallback: certChecker.Authenticate,
+ KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) {
+ ans, err := challenge("user",
+ "instruction",
+ []string{"question1", "question2"},
+ []bool{true, true})
+ if err != nil {
+ return nil, err
+ }
+ ok := conn.User() == "testuser" && ans[0] == "answer1" && ans[1] == "answer2"
+ if ok {
+ challenge("user", "motd", nil, nil)
+ return nil, nil
+ }
+ return nil, errors.New("keyboard-interactive failed")
+ },
+ GSSAPIWithMICConfig: gssAPIWithMICConfig,
+ }
+ serverConfig.AddHostKey(testSigners["rsa"])
+
+ serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
+ serverAuthErrors = append(serverAuthErrors, err)
+ }
+
+ go newServer(c1, serverConfig)
+ _, _, _, err = NewClientConn(c2, "", config)
+ return err, serverAuthErrors
+}
+
+type loggingAlgorithmSigner struct {
+ used []string
+ AlgorithmSigner
+}
+
+func (l *loggingAlgorithmSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
+ l.used = append(l.used, "[Sign]")
+ return l.AlgorithmSigner.Sign(rand, data)
+}
+
+func (l *loggingAlgorithmSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
+ l.used = append(l.used, algorithm)
+ return l.AlgorithmSigner.SignWithAlgorithm(rand, data, algorithm)
+}
+
+func TestClientAuthPublicKey(t *testing.T) {
+ signer := &loggingAlgorithmSigner{AlgorithmSigner: testSigners["rsa"].(AlgorithmSigner)}
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(signer),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+ if len(signer.used) != 1 || signer.used[0] != KeyAlgoRSASHA256 {
+ t.Errorf("unexpected Sign/SignWithAlgorithm calls: %q", signer.used)
+ }
+}
+
+// TestClientAuthNoSHA2 tests a ssh-rsa Signer that doesn't implement AlgorithmSigner.
+func TestClientAuthNoSHA2(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(&legacyRSASigner{testSigners["rsa"]}),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+}
+
+// TestClientAuthThirdKey checks that the third configured can succeed. If we
+// were to do three attempts for each key (rsa-sha2-256, rsa-sha2-512, ssh-rsa),
+// we'd hit the six maximum attempts before reaching it.
+func TestClientAuthThirdKey(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa-openssh-format"],
+ testSigners["rsa-openssh-format"], testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+}
+
+func TestAuthMethodPassword(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+}
+
+func TestAuthMethodFallback(t *testing.T) {
+ var passwordCalled bool
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ PasswordCallback(
+ func() (string, error) {
+ passwordCalled = true
+ return "WRONG", nil
+ }),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+
+ if passwordCalled {
+ t.Errorf("password auth tried before public-key auth.")
+ }
+}
+
+func TestAuthMethodWrongPassword(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ Password("wrong"),
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+}
+
+func TestAuthMethodKeyboardInteractive(t *testing.T) {
+ answers := keyboardInteractive(map[string]string{
+ "question1": "answer1",
+ "question2": "answer2",
+ })
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ KeyboardInteractive(answers.Challenge),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+}
+
+func TestAuthMethodWrongKeyboardInteractive(t *testing.T) {
+ answers := keyboardInteractive(map[string]string{
+ "question1": "answer1",
+ "question2": "WRONG",
+ })
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ KeyboardInteractive(answers.Challenge),
+ },
+ }
+
+ if err := tryAuth(t, config); err == nil {
+ t.Fatalf("wrong answers should not have authenticated with KeyboardInteractive")
+ }
+}
+
+// the mock server will only authenticate ssh-rsa keys
+func TestAuthMethodInvalidPublicKey(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["dsa"]),
+ },
+ }
+
+ if err := tryAuth(t, config); err == nil {
+ t.Fatalf("dsa private key should not have authenticated with rsa public key")
+ }
+}
+
+// the client should authenticate with the second key
+func TestAuthMethodRSAandDSA(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["dsa"], testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("client could not authenticate with rsa key: %v", err)
+ }
+}
+
+type invalidAlgSigner struct {
+ Signer
+}
+
+func (s *invalidAlgSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
+ sig, err := s.Signer.Sign(rand, data)
+ if sig != nil {
+ sig.Format = "invalid"
+ }
+ return sig, err
+}
+
+func TestMethodInvalidAlgorithm(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(&invalidAlgSigner{testSigners["rsa"]}),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ err, serverErrors := tryAuthBothSides(t, config, nil)
+ if err == nil {
+ t.Fatalf("login succeeded")
+ }
+
+ found := false
+ want := "algorithm \"invalid\""
+
+ var errStrings []string
+ for _, err := range serverErrors {
+ found = found || (err != nil && strings.Contains(err.Error(), want))
+ errStrings = append(errStrings, err.Error())
+ }
+ if !found {
+ t.Errorf("server got error %q, want substring %q", errStrings, want)
+ }
+}
+
+func TestClientHMAC(t *testing.T) {
+ supportedAlgos := SupportedAlgorithms()
+ insecureAlgos := InsecureAlgorithms()
+ supportedMACs := append(supportedAlgos.MACs, insecureAlgos.MACs...)
+ for _, mac := range supportedMACs {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ Config: Config{
+ MACs: []string{mac},
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("client could not authenticate with mac algo %s: %v", mac, err)
+ }
+ }
+}
+
+// issue 4285.
+func TestClientUnsupportedCipher(t *testing.T) {
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(),
+ },
+ Config: Config{
+ Ciphers: []string{"unsupported-cipher"}, // not currently supported
+ },
+ }
+ if err := tryAuth(t, config); err == nil {
+ t.Errorf("expected no ciphers in common")
+ }
+}
+
+func TestClientUnsupportedKex(t *testing.T) {
+ if os.Getenv("GO_BUILDER_NAME") != "" {
+ t.Skip("skipping known-flaky test on the Go build dashboard; see golang.org/issue/15198")
+ }
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(),
+ },
+ Config: Config{
+ KeyExchanges: []string{"non-existent-kex"},
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ if err := tryAuth(t, config); err == nil || !strings.Contains(err.Error(), "common algorithm") {
+ t.Errorf("got %v, expected 'common algorithm'", err)
+ }
+}
+
+func TestClientLoginCert(t *testing.T) {
+ cert := &Certificate{
+ Key: testPublicKeys["rsa"],
+ ValidBefore: CertTimeInfinity,
+ CertType: UserCert,
+ }
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ certSigner, err := NewCertSigner(cert, testSigners["rsa"])
+ if err != nil {
+ t.Fatalf("NewCertSigner: %v", err)
+ }
+
+ clientConfig := &ClientConfig{
+ User: "user",
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ clientConfig.Auth = append(clientConfig.Auth, PublicKeys(certSigner))
+
+ // should succeed
+ if err := tryAuth(t, clientConfig); err != nil {
+ t.Errorf("cert login failed: %v", err)
+ }
+
+ // corrupted signature
+ cert.Signature.Blob[0]++
+ if err := tryAuth(t, clientConfig); err == nil {
+ t.Errorf("cert login passed with corrupted sig")
+ }
+
+ // revoked
+ cert.Serial = 666
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ if err := tryAuth(t, clientConfig); err == nil {
+ t.Errorf("revoked cert login succeeded")
+ }
+ cert.Serial = 1
+
+ // sign with wrong key
+ cert.SignCert(rand.Reader, testSigners["dsa"])
+ if err := tryAuth(t, clientConfig); err == nil {
+ t.Errorf("cert login passed with non-authoritative key")
+ }
+
+ // host cert
+ cert.CertType = HostCert
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ if err := tryAuth(t, clientConfig); err == nil {
+ t.Errorf("cert login passed with wrong type")
+ }
+ cert.CertType = UserCert
+
+ // principal specified
+ cert.ValidPrincipals = []string{"user"}
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ if err := tryAuth(t, clientConfig); err != nil {
+ t.Errorf("cert login failed: %v", err)
+ }
+
+ // wrong principal specified
+ cert.ValidPrincipals = []string{"fred"}
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ if err := tryAuth(t, clientConfig); err == nil {
+ t.Errorf("cert login passed with wrong principal")
+ }
+ cert.ValidPrincipals = nil
+
+ // added critical option
+ cert.CriticalOptions = map[string]string{"root-access": "yes"}
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ if err := tryAuth(t, clientConfig); err == nil {
+ t.Errorf("cert login passed with unrecognized critical option")
+ }
+
+ // allowed source address
+ cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42/24,::42/120"}
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ if err := tryAuth(t, clientConfig); err != nil {
+ t.Errorf("cert login with source-address failed: %v", err)
+ }
+
+ // disallowed source address
+ cert.CriticalOptions = map[string]string{"source-address": "127.0.0.42,::42"}
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ if err := tryAuth(t, clientConfig); err == nil {
+ t.Errorf("cert login with source-address succeeded")
+ }
+}
+
+func testPermissionsPassing(withPermissions bool, t *testing.T) {
+ serverConfig := &ServerConfig{
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ if conn.User() == "nopermissions" {
+ return nil, nil
+ }
+ return &Permissions{}, nil
+ },
+ }
+ serverConfig.AddHostKey(testSigners["rsa"])
+
+ clientConfig := &ClientConfig{
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ if withPermissions {
+ clientConfig.User = "permissions"
+ } else {
+ clientConfig.User = "nopermissions"
+ }
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ go NewClientConn(c2, "", clientConfig)
+ serverConn, err := newServer(c1, serverConfig)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p := serverConn.Permissions; (p != nil) != withPermissions {
+ t.Fatalf("withPermissions is %t, but Permissions object is %#v", withPermissions, p)
+ }
+}
+
+func TestPermissionsPassing(t *testing.T) {
+ testPermissionsPassing(true, t)
+}
+
+func TestNoPermissionsPassing(t *testing.T) {
+ testPermissionsPassing(false, t)
+}
+
+func TestRetryableAuth(t *testing.T) {
+ n := 0
+ passwords := []string{"WRONG1", "WRONG2"}
+
+ config := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ RetryableAuthMethod(PasswordCallback(func() (string, error) {
+ p := passwords[n]
+ n++
+ return p, nil
+ }), 2),
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ if err := tryAuth(t, config); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+ if n != 2 {
+ t.Fatalf("Did not try all passwords")
+ }
+}
+
+func ExampleRetryableAuthMethod() {
+ user := "testuser"
+ NumberOfPrompts := 3
+
+ // Normally this would be a callback that prompts the user to answer the
+ // provided questions
+ Cb := func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
+ return []string{"answer1", "answer2"}, nil
+ }
+
+ config := &ClientConfig{
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ User: user,
+ Auth: []AuthMethod{
+ RetryableAuthMethod(KeyboardInteractiveChallenge(Cb), NumberOfPrompts),
+ },
+ }
+
+ host := "mysshserver"
+ netConn, err := net.Dial("tcp", host)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ sshConn, _, _, err := NewClientConn(netConn, host, config)
+ if err != nil {
+ log.Fatal(err)
+ }
+ _ = sshConn
+}
+
+// Test if username is received on server side when NoClientAuth is used
+func TestClientAuthNone(t *testing.T) {
+ user := "testuser"
+ serverConfig := &ServerConfig{
+ NoClientAuth: true,
+ }
+ serverConfig.AddHostKey(testSigners["rsa"])
+
+ clientConfig := &ClientConfig{
+ User: user,
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ go NewClientConn(c2, "", clientConfig)
+ serverConn, err := newServer(c1, serverConfig)
+ if err != nil {
+ t.Fatalf("newServer: %v", err)
+ }
+ if serverConn.User() != user {
+ t.Fatalf("server: got %q, want %q", serverConn.User(), user)
+ }
+}
+
+// Test if authentication attempts are limited on server when MaxAuthTries is set
+func TestClientAuthMaxAuthTries(t *testing.T) {
+ user := "testuser"
+
+ serverConfig := &ServerConfig{
+ MaxAuthTries: 2,
+ PasswordCallback: func(conn ConnMetadata, pass []byte) (*Permissions, error) {
+ if conn.User() == "testuser" && string(pass) == "right" {
+ return nil, nil
+ }
+ return nil, errors.New("password auth failed")
+ },
+ }
+ serverConfig.AddHostKey(testSigners["rsa"])
+
+ expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
+ Reason: 2,
+ Message: "too many authentication failures",
+ })
+
+ for tries := 2; tries < 4; tries++ {
+ n := tries
+ clientConfig := &ClientConfig{
+ User: user,
+ Auth: []AuthMethod{
+ RetryableAuthMethod(PasswordCallback(func() (string, error) {
+ n--
+ if n == 0 {
+ return "right", nil
+ }
+ return "wrong", nil
+ }), tries),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ errCh := make(chan error, 1)
+
+ go func() {
+ _, err := newServer(c1, serverConfig)
+ errCh <- err
+ }()
+ _, _, _, cliErr := NewClientConn(c2, "", clientConfig)
+ srvErr := <-errCh
+
+ if tries > serverConfig.MaxAuthTries {
+ if cliErr == nil {
+ t.Fatalf("client: got no error, want %s", expectedErr)
+ } else if cliErr.Error() != expectedErr.Error() {
+ t.Fatalf("client: got %s, want %s", err, expectedErr)
+ }
+ var authErr *ServerAuthError
+ if !errors.As(srvErr, &authErr) {
+ t.Errorf("expected ServerAuthError, got: %v", srvErr)
+ }
+ } else {
+ if cliErr != nil {
+ t.Fatalf("client: got %s, want no error", cliErr)
+ }
+ }
+ }
+}
+
+// Test if authentication attempts are correctly limited on server
+// when more public keys are provided then MaxAuthTries
+func TestClientAuthMaxAuthTriesPublicKey(t *testing.T) {
+ signers := []Signer{}
+ for i := 0; i < 6; i++ {
+ signers = append(signers, testSigners["dsa"])
+ }
+
+ validConfig := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(append([]Signer{testSigners["rsa"]}, signers...)...),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ if err := tryAuth(t, validConfig); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+
+ expectedErr := fmt.Errorf("ssh: handshake failed: %v", &disconnectMsg{
+ Reason: 2,
+ Message: "too many authentication failures",
+ })
+ invalidConfig := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ PublicKeys(append(signers, testSigners["rsa"])...),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ if err := tryAuth(t, invalidConfig); err == nil {
+ t.Fatalf("client: got no error, want %s", expectedErr)
+ } else if err.Error() != expectedErr.Error() {
+ // On Windows we can see a WSAECONNABORTED error
+ // if the client writes another authentication request
+ // before the client goroutine reads the disconnection
+ // message. See issue 50805.
+ if runtime.GOOS == "windows" && strings.Contains(err.Error(), "wsarecv: An established connection was aborted") {
+ // OK.
+ } else {
+ t.Fatalf("client: got %s, want %s", err, expectedErr)
+ }
+ }
+}
+
+// Test whether authentication errors are being properly logged if all
+// authentication methods have been exhausted
+func TestClientAuthErrorList(t *testing.T) {
+ publicKeyErr := errors.New("This is an error from PublicKeyCallback")
+
+ clientConfig := &ClientConfig{
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ serverConfig := &ServerConfig{
+ PublicKeyCallback: func(_ ConnMetadata, _ PublicKey) (*Permissions, error) {
+ return nil, publicKeyErr
+ },
+ }
+ serverConfig.AddHostKey(testSigners["rsa"])
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ go NewClientConn(c2, "", clientConfig)
+ _, err = newServer(c1, serverConfig)
+ if err == nil {
+ t.Fatal("newServer: got nil, expected errors")
+ }
+
+ authErrs, ok := err.(*ServerAuthError)
+ if !ok {
+ t.Fatalf("errors: got %T, want *ssh.ServerAuthError", err)
+ }
+ for i, e := range authErrs.Errors {
+ switch i {
+ case 0:
+ if e != ErrNoAuth {
+ t.Fatalf("errors: got error %v, want ErrNoAuth", e)
+ }
+ case 1:
+ if e != publicKeyErr {
+ t.Fatalf("errors: got %v, want %v", e, publicKeyErr)
+ }
+ default:
+ t.Fatalf("errors: got %v, expected 2 errors", authErrs.Errors)
+ }
+ }
+}
+
+func TestAuthMethodGSSAPIWithMIC(t *testing.T) {
+ type testcase struct {
+ config *ClientConfig
+ gssConfig *GSSAPIWithMICConfig
+ clientWantErr string
+ serverWantErr string
+ }
+ testcases := []*testcase{
+ {
+ config: &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ GSSAPIWithMICAuthMethod(
+ &FakeClient{
+ exchanges: []*exchange{
+ {
+ outToken: "client-valid-token-1",
+ },
+ {
+ expectedToken: "server-valid-token-1",
+ },
+ },
+ mic: []byte("valid-mic"),
+ maxRound: 2,
+ }, "testtarget",
+ ),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ },
+ gssConfig: &GSSAPIWithMICConfig{
+ AllowLogin: func(conn ConnMetadata, srcName string) (*Permissions, error) {
+ if srcName != conn.User()+"@DOMAIN" {
+ return nil, fmt.Errorf("srcName is %s, conn user is %s", srcName, conn.User())
+ }
+ return nil, nil
+ },
+ Server: &FakeServer{
+ exchanges: []*exchange{
+ {
+ outToken: "server-valid-token-1",
+ expectedToken: "client-valid-token-1",
+ },
+ },
+ maxRound: 1,
+ expectedMIC: []byte("valid-mic"),
+ srcName: "testuser@DOMAIN",
+ },
+ },
+ },
+ {
+ config: &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ GSSAPIWithMICAuthMethod(
+ &FakeClient{
+ exchanges: []*exchange{
+ {
+ outToken: "client-valid-token-1",
+ },
+ {
+ expectedToken: "server-valid-token-1",
+ },
+ },
+ mic: []byte("valid-mic"),
+ maxRound: 2,
+ }, "testtarget",
+ ),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ },
+ gssConfig: &GSSAPIWithMICConfig{
+ AllowLogin: func(conn ConnMetadata, srcName string) (*Permissions, error) {
+ return nil, fmt.Errorf("user is not allowed to login")
+ },
+ Server: &FakeServer{
+ exchanges: []*exchange{
+ {
+ outToken: "server-valid-token-1",
+ expectedToken: "client-valid-token-1",
+ },
+ },
+ maxRound: 1,
+ expectedMIC: []byte("valid-mic"),
+ srcName: "testuser@DOMAIN",
+ },
+ },
+ serverWantErr: "user is not allowed to login",
+ clientWantErr: "ssh: handshake failed: ssh: unable to authenticate",
+ },
+ {
+ config: &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ GSSAPIWithMICAuthMethod(
+ &FakeClient{
+ exchanges: []*exchange{
+ {
+ outToken: "client-valid-token-1",
+ },
+ {
+ expectedToken: "server-valid-token-1",
+ },
+ },
+ mic: []byte("valid-mic"),
+ maxRound: 2,
+ }, "testtarget",
+ ),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ },
+ gssConfig: &GSSAPIWithMICConfig{
+ AllowLogin: func(conn ConnMetadata, srcName string) (*Permissions, error) {
+ if srcName != conn.User() {
+ return nil, fmt.Errorf("srcName is %s, conn user is %s", srcName, conn.User())
+ }
+ return nil, nil
+ },
+ Server: &FakeServer{
+ exchanges: []*exchange{
+ {
+ outToken: "server-invalid-token-1",
+ expectedToken: "client-valid-token-1",
+ },
+ },
+ maxRound: 1,
+ expectedMIC: []byte("valid-mic"),
+ srcName: "testuser@DOMAIN",
+ },
+ },
+ clientWantErr: "ssh: handshake failed: got \"server-invalid-token-1\", want token \"server-valid-token-1\"",
+ },
+ {
+ config: &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ GSSAPIWithMICAuthMethod(
+ &FakeClient{
+ exchanges: []*exchange{
+ {
+ outToken: "client-valid-token-1",
+ },
+ {
+ expectedToken: "server-valid-token-1",
+ },
+ },
+ mic: []byte("invalid-mic"),
+ maxRound: 2,
+ }, "testtarget",
+ ),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ },
+ gssConfig: &GSSAPIWithMICConfig{
+ AllowLogin: func(conn ConnMetadata, srcName string) (*Permissions, error) {
+ if srcName != conn.User() {
+ return nil, fmt.Errorf("srcName is %s, conn user is %s", srcName, conn.User())
+ }
+ return nil, nil
+ },
+ Server: &FakeServer{
+ exchanges: []*exchange{
+ {
+ outToken: "server-valid-token-1",
+ expectedToken: "client-valid-token-1",
+ },
+ },
+ maxRound: 1,
+ expectedMIC: []byte("valid-mic"),
+ srcName: "testuser@DOMAIN",
+ },
+ },
+ serverWantErr: "got MICToken \"invalid-mic\", want \"valid-mic\"",
+ clientWantErr: "ssh: handshake failed: ssh: unable to authenticate",
+ },
+ }
+
+ for i, c := range testcases {
+ clientErr, serverErrs := tryAuthBothSides(t, c.config, c.gssConfig)
+ if (c.clientWantErr == "") != (clientErr == nil) {
+ t.Fatalf("client got %v, want %s, case %d", clientErr, c.clientWantErr, i)
+ }
+ if (c.serverWantErr == "") != (len(serverErrs) == 2 && serverErrs[1] == nil || len(serverErrs) == 1) {
+ t.Fatalf("server got err %v, want %s", serverErrs, c.serverWantErr)
+ }
+ if c.clientWantErr != "" {
+ if clientErr != nil && !strings.Contains(clientErr.Error(), c.clientWantErr) {
+ t.Fatalf("client got %v, want %s, case %d", clientErr, c.clientWantErr, i)
+ }
+ }
+ found := false
+ var errStrings []string
+ if c.serverWantErr != "" {
+ for _, err := range serverErrs {
+ found = found || (err != nil && strings.Contains(err.Error(), c.serverWantErr))
+ errStrings = append(errStrings, err.Error())
+ }
+ if !found {
+ t.Errorf("server got error %q, want substring %q, case %d", errStrings, c.serverWantErr, i)
+ }
+ }
+ }
+}
+
+func TestCompatibleAlgoAndSignatures(t *testing.T) {
+ type testcase struct {
+ algo string
+ sigFormat string
+ compatible bool
+ }
+ testcases := []*testcase{
+ {
+ KeyAlgoRSA,
+ KeyAlgoRSA,
+ true,
+ },
+ {
+ KeyAlgoRSA,
+ KeyAlgoRSASHA256,
+ true,
+ },
+ {
+ KeyAlgoRSA,
+ KeyAlgoRSASHA512,
+ true,
+ },
+ {
+ KeyAlgoRSASHA256,
+ KeyAlgoRSA,
+ true,
+ },
+ {
+ KeyAlgoRSASHA512,
+ KeyAlgoRSA,
+ true,
+ },
+ {
+ KeyAlgoRSASHA512,
+ KeyAlgoRSASHA256,
+ true,
+ },
+ {
+ KeyAlgoRSASHA256,
+ KeyAlgoRSASHA512,
+ true,
+ },
+ {
+ KeyAlgoRSASHA512,
+ KeyAlgoRSASHA512,
+ true,
+ },
+ {
+ CertAlgoRSAv01,
+ KeyAlgoRSA,
+ true,
+ },
+ {
+ CertAlgoRSAv01,
+ KeyAlgoRSASHA256,
+ true,
+ },
+ {
+ CertAlgoRSAv01,
+ KeyAlgoRSASHA512,
+ true,
+ },
+ {
+ CertAlgoRSASHA256v01,
+ KeyAlgoRSASHA512,
+ true,
+ },
+ {
+ CertAlgoRSASHA512v01,
+ KeyAlgoRSASHA512,
+ true,
+ },
+ {
+ CertAlgoRSASHA512v01,
+ KeyAlgoRSASHA256,
+ true,
+ },
+ {
+ CertAlgoRSASHA256v01,
+ CertAlgoRSAv01,
+ true,
+ },
+ {
+ CertAlgoRSAv01,
+ CertAlgoRSASHA512v01,
+ true,
+ },
+ {
+ KeyAlgoECDSA256,
+ KeyAlgoRSA,
+ false,
+ },
+ {
+ KeyAlgoECDSA256,
+ KeyAlgoECDSA521,
+ false,
+ },
+ {
+ KeyAlgoECDSA256,
+ KeyAlgoECDSA256,
+ true,
+ },
+ {
+ KeyAlgoECDSA256,
+ KeyAlgoED25519,
+ false,
+ },
+ {
+ KeyAlgoED25519,
+ KeyAlgoED25519,
+ true,
+ },
+ }
+
+ for _, c := range testcases {
+ if isAlgoCompatible(c.algo, c.sigFormat) != c.compatible {
+ t.Errorf("algorithm %q, signature format %q, expected compatible to be %t", c.algo, c.sigFormat, c.compatible)
+ }
+ }
+}
+
+func TestPickSignatureAlgorithm(t *testing.T) {
+ type testcase struct {
+ name string
+ extensions map[string][]byte
+ }
+ cases := []testcase{
+ {
+ name: "server with empty server-sig-algs",
+ extensions: map[string][]byte{
+ "server-sig-algs": []byte(``),
+ },
+ },
+ {
+ name: "server with no server-sig-algs",
+ extensions: nil,
+ },
+ }
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ signer, ok := testSigners["rsa"].(MultiAlgorithmSigner)
+ if !ok {
+ t.Fatalf("rsa test signer does not implement the MultiAlgorithmSigner interface")
+ }
+ // The signer supports the public key algorithm which is then returned.
+ _, algo, err := pickSignatureAlgorithm(signer, c.extensions)
+ if err != nil {
+ t.Fatalf("got %v, want no error", err)
+ }
+ if algo != signer.PublicKey().Type() {
+ t.Fatalf("got algo %q, want %q", algo, signer.PublicKey().Type())
+ }
+ // Test a signer that uses a certificate algorithm as the public key
+ // type.
+ cert := &Certificate{
+ CertType: UserCert,
+ Key: signer.PublicKey(),
+ }
+ cert.SignCert(rand.Reader, signer)
+
+ certSigner, err := NewCertSigner(cert, signer)
+ if err != nil {
+ t.Fatalf("error generating cert signer: %v", err)
+ }
+ // The signer supports the public key algorithm and the
+ // public key format is a certificate type so the certificate
+ // algorithm matching the key format must be returned
+ _, algo, err = pickSignatureAlgorithm(certSigner, c.extensions)
+ if err != nil {
+ t.Fatalf("got %v, want no error", err)
+ }
+ if algo != certSigner.PublicKey().Type() {
+ t.Fatalf("got algo %q, want %q", algo, certSigner.PublicKey().Type())
+ }
+ signer, err = NewSignerWithAlgorithms(signer.(AlgorithmSigner), []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256})
+ if err != nil {
+ t.Fatalf("unable to create signer with algorithms: %v", err)
+ }
+ // The signer does not support the public key algorithm so an error
+ // is returned.
+ _, _, err = pickSignatureAlgorithm(signer, c.extensions)
+ if err == nil {
+ t.Fatal("got no error, no common public key signature algorithm error expected")
+ }
+ })
+ }
+}
+
+func TestPickSignatureAlgorithmRespectsSignerPreference(t *testing.T) {
+ algoSigner, ok := testSigners["rsa"].(AlgorithmSigner)
+ if !ok {
+ t.Fatalf("rsa test signer does not implement the AlgorithmSigner interface")
+ }
+
+ serverExtensions := map[string][]byte{
+ "server-sig-algs": []byte(KeyAlgoRSASHA256 + "," + KeyAlgoRSASHA512),
+ }
+
+ tests := []struct {
+ name string
+ signerPrefs []string
+ expectedAlgo string
+ }{
+ {
+ name: "Signer prefers SHA512 then SHA256",
+ signerPrefs: []string{KeyAlgoRSASHA512, KeyAlgoRSASHA256},
+ expectedAlgo: KeyAlgoRSASHA512,
+ },
+ {
+ name: "Signer prefers SHA256 then SHA512",
+ signerPrefs: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
+ expectedAlgo: KeyAlgoRSASHA256,
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ orderedSigner, err := NewSignerWithAlgorithms(algoSigner, tc.signerPrefs)
+ if err != nil {
+ t.Fatalf("failed to create ordered signer: %v", err)
+ }
+
+ _, selectedAlgo, err := pickSignatureAlgorithm(orderedSigner, serverExtensions)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if selectedAlgo != tc.expectedAlgo {
+ t.Errorf("Algorithm mismatch; got %q want %q", selectedAlgo, tc.expectedAlgo)
+ }
+ })
+ }
+}
+
+// configurablePublicKeyCallback is a public key callback that allows to
+// configure the signature algorithm and format. This way we can emulate the
+// behavior of buggy clients.
+type configurablePublicKeyCallback struct {
+ signer AlgorithmSigner
+ signatureAlgo string
+ signatureFormat string
+}
+
+func (cb configurablePublicKeyCallback) method() string {
+ return "publickey"
+}
+
+func (cb configurablePublicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader, extensions map[string][]byte) (authResult, []string, error) {
+ pub := cb.signer.PublicKey()
+
+ ok, err := validateKey(pub, cb.signatureAlgo, user, c)
+ if err != nil {
+ return authFailure, nil, err
+ }
+ if !ok {
+ return authFailure, nil, fmt.Errorf("invalid public key")
+ }
+
+ pubKey := pub.Marshal()
+ data := buildDataSignedForAuth(session, userAuthRequestMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: cb.method(),
+ }, cb.signatureAlgo, pubKey)
+ sign, err := cb.signer.SignWithAlgorithm(rand, data, underlyingAlgo(cb.signatureFormat))
+ if err != nil {
+ return authFailure, nil, err
+ }
+
+ s := Marshal(sign)
+ sig := make([]byte, stringLength(len(s)))
+ marshalString(sig, s)
+ msg := publickeyAuthMsg{
+ User: user,
+ Service: serviceSSH,
+ Method: cb.method(),
+ HasSig: true,
+ Algoname: cb.signatureAlgo,
+ 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 success == authSuccess || !slices.Contains(methods, cb.method()) {
+ return success, methods, err
+ }
+
+ return authFailure, methods, nil
+}
+
+func TestPublicKeyAndAlgoCompatibility(t *testing.T) {
+ cert := &Certificate{
+ Key: testPublicKeys["rsa"],
+ ValidBefore: CertTimeInfinity,
+ CertType: UserCert,
+ }
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ certSigner, err := NewCertSigner(cert, testSigners["rsa"])
+ if err != nil {
+ t.Fatalf("NewCertSigner: %v", err)
+ }
+
+ clientConfig := &ClientConfig{
+ User: "user",
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ Auth: []AuthMethod{
+ configurablePublicKeyCallback{
+ signer: certSigner.(AlgorithmSigner),
+ signatureAlgo: KeyAlgoRSASHA256,
+ signatureFormat: KeyAlgoRSASHA256,
+ },
+ },
+ }
+ if err := tryAuth(t, clientConfig); err == nil {
+ t.Error("cert login passed with incompatible public key type and algorithm")
+ }
+}
+
+func TestClientAuthGPGAgentCompat(t *testing.T) {
+ clientConfig := &ClientConfig{
+ User: "testuser",
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ Auth: []AuthMethod{
+ // algorithm rsa-sha2-512 and signature format ssh-rsa.
+ configurablePublicKeyCallback{
+ signer: testSigners["rsa"].(AlgorithmSigner),
+ signatureAlgo: KeyAlgoRSASHA512,
+ signatureFormat: KeyAlgoRSA,
+ },
+ },
+ }
+ if err := tryAuth(t, clientConfig); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+}
+
+func TestCertAuthOpenSSHCompat(t *testing.T) {
+ cert := &Certificate{
+ Key: testPublicKeys["rsa"],
+ ValidBefore: CertTimeInfinity,
+ CertType: UserCert,
+ }
+ cert.SignCert(rand.Reader, testSigners["ecdsa"])
+ certSigner, err := NewCertSigner(cert, testSigners["rsa"])
+ if err != nil {
+ t.Fatalf("NewCertSigner: %v", err)
+ }
+
+ clientConfig := &ClientConfig{
+ User: "user",
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ Auth: []AuthMethod{
+ // algorithm ssh-rsa-cert-v01@openssh.com and signature format
+ // rsa-sha2-256.
+ configurablePublicKeyCallback{
+ signer: certSigner.(AlgorithmSigner),
+ signatureAlgo: CertAlgoRSAv01,
+ signatureFormat: KeyAlgoRSASHA256,
+ },
+ },
+ }
+ if err := tryAuth(t, clientConfig); err != nil {
+ t.Fatalf("unable to dial remote side: %s", err)
+ }
+}
+
+func TestKeyboardInteractiveAuthEarlyFail(t *testing.T) {
+ const maxAuthTries = 2
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ // Start testserver
+ serverConfig := &ServerConfig{
+ MaxAuthTries: maxAuthTries,
+ KeyboardInteractiveCallback: func(c ConnMetadata,
+ client KeyboardInteractiveChallenge) (*Permissions, error) {
+ // Fail keyboard-interactive authentication early before
+ // any prompt is sent to client.
+ return nil, errors.New("keyboard-interactive auth failed")
+ },
+ PasswordCallback: func(c ConnMetadata,
+ pass []byte) (*Permissions, error) {
+ if string(pass) == clientPassword {
+ return nil, nil
+ }
+ return nil, errors.New("password auth failed")
+ },
+ }
+ serverConfig.AddHostKey(testSigners["rsa"])
+
+ serverDone := make(chan struct{})
+ go func() {
+ defer func() { serverDone <- struct{}{} }()
+ conn, chans, reqs, err := NewServerConn(c2, serverConfig)
+ if err != nil {
+ return
+ }
+ _ = conn.Close()
+
+ discarderDone := make(chan struct{})
+ go func() {
+ defer func() { discarderDone <- struct{}{} }()
+ DiscardRequests(reqs)
+ }()
+ for newChannel := range chans {
+ newChannel.Reject(Prohibited,
+ "testserver not accepting requests")
+ }
+
+ <-discarderDone
+ }()
+
+ // Connect to testserver, expect KeyboardInteractive() to be not called,
+ // PasswordCallback() to be called and connection to succeed.
+ passwordCallbackCalled := false
+ clientConfig := &ClientConfig{
+ User: "testuser",
+ Auth: []AuthMethod{
+ RetryableAuthMethod(KeyboardInteractive(func(name,
+ instruction string, questions []string,
+ echos []bool) ([]string, error) {
+ t.Errorf("unexpected call to KeyboardInteractive()")
+ return []string{clientPassword}, nil
+ }), maxAuthTries),
+ RetryableAuthMethod(PasswordCallback(func() (secret string,
+ err error) {
+ t.Logf("PasswordCallback()")
+ passwordCallbackCalled = true
+ return clientPassword, nil
+ }), maxAuthTries),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ conn, _, _, err := NewClientConn(c1, "", clientConfig)
+ if err != nil {
+ t.Errorf("unexpected NewClientConn() error: %v", err)
+ }
+ if conn != nil {
+ conn.Close()
+ }
+
+ // Wait for server to finish.
+ <-serverDone
+
+ if !passwordCallbackCalled {
+ t.Errorf("expected PasswordCallback() to be called")
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/client_test.go b/local_crypto_patch/contents/ssh/client_test.go
new file mode 100644
index 0000000000..d583900bf5
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/client_test.go
@@ -0,0 +1,367 @@
+// 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 ssh
+
+import (
+ "bytes"
+ "crypto/rand"
+ "errors"
+ "fmt"
+ "net"
+ "strings"
+ "testing"
+)
+
+func TestClientVersion(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ version string
+ multiLine string
+ wantErr bool
+ }{
+ {
+ name: "default version",
+ version: packageVersion,
+ },
+ {
+ name: "custom version",
+ version: "SSH-2.0-CustomClientVersionString",
+ },
+ {
+ name: "good multi line version",
+ version: packageVersion,
+ multiLine: strings.Repeat("ignored\r\n", 20),
+ },
+ {
+ name: "bad multi line version",
+ version: packageVersion,
+ multiLine: "bad multi line version",
+ wantErr: true,
+ },
+ {
+ name: "long multi line version",
+ version: packageVersion,
+ multiLine: strings.Repeat("long multi line version\r\n", 50)[:256],
+ wantErr: true,
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+ go func() {
+ if tt.multiLine != "" {
+ c1.Write([]byte(tt.multiLine))
+ }
+ NewClientConn(c1, "", &ClientConfig{
+ ClientVersion: tt.version,
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ })
+ c1.Close()
+ }()
+ conf := &ServerConfig{NoClientAuth: true}
+ conf.AddHostKey(testSigners["rsa"])
+ conn, _, _, err := NewServerConn(c2, conf)
+ if err == nil == tt.wantErr {
+ t.Fatalf("got err %v; wantErr %t", err, tt.wantErr)
+ }
+ if tt.wantErr {
+ // Don't verify the version on an expected error.
+ return
+ }
+ if got := string(conn.ClientVersion()); got != tt.version {
+ t.Fatalf("got %q; want %q", got, tt.version)
+ }
+ })
+ }
+}
+
+func TestHostKeyCheck(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ wantError string
+ key PublicKey
+ }{
+ {"no callback", "must specify HostKeyCallback", nil},
+ {"correct key", "", testSigners["rsa"].PublicKey()},
+ {"mismatch", "mismatch", testSigners["ecdsa"].PublicKey()},
+ } {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+ serverConf := &ServerConfig{
+ NoClientAuth: true,
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ go NewServerConn(c1, serverConf)
+ clientConf := ClientConfig{
+ User: "user",
+ }
+ if tt.key != nil {
+ clientConf.HostKeyCallback = FixedHostKey(tt.key)
+ }
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err != nil {
+ if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
+ t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
+ }
+ } else if tt.wantError != "" {
+ t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
+ }
+ }
+}
+
+func TestVerifyHostKeySignature(t *testing.T) {
+ for _, tt := range []struct {
+ key string
+ signAlgo string
+ verifyAlgo string
+ wantError string
+ }{
+ {"rsa", KeyAlgoRSA, KeyAlgoRSA, ""},
+ {"rsa", KeyAlgoRSASHA256, KeyAlgoRSASHA256, ""},
+ {"rsa", KeyAlgoRSA, KeyAlgoRSASHA512, `ssh: invalid signature algorithm "ssh-rsa", expected "rsa-sha2-512"`},
+ {"ed25519", KeyAlgoED25519, KeyAlgoED25519, ""},
+ } {
+ key := testSigners[tt.key].PublicKey()
+ s, ok := testSigners[tt.key].(AlgorithmSigner)
+ if !ok {
+ t.Fatalf("needed an AlgorithmSigner")
+ }
+ sig, err := s.SignWithAlgorithm(rand.Reader, []byte("test"), tt.signAlgo)
+ if err != nil {
+ t.Fatalf("couldn't sign: %q", err)
+ }
+
+ b := bytes.Buffer{}
+ writeString(&b, []byte(sig.Format))
+ writeString(&b, sig.Blob)
+
+ result := kexResult{Signature: b.Bytes(), H: []byte("test")}
+
+ err = verifyHostKeySignature(key, tt.verifyAlgo, &result)
+ if err != nil {
+ if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
+ t.Errorf("got error %q, expecting %q", err.Error(), tt.wantError)
+ }
+ } else if tt.wantError != "" {
+ t.Errorf("succeeded, but want error string %q", tt.wantError)
+ }
+ }
+}
+
+func TestBannerCallback(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ return &Permissions{}, nil
+ },
+ BannerCallback: func(conn ConnMetadata) string {
+ return "Hello World"
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+ go NewServerConn(c1, serverConf)
+
+ var receivedBanner string
+ var bannerCount int
+ clientConf := ClientConfig{
+ Auth: []AuthMethod{
+ Password("123"),
+ },
+ User: "user",
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ BannerCallback: func(message string) error {
+ bannerCount++
+ receivedBanner = message
+ return nil
+ },
+ }
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if bannerCount != 1 {
+ t.Errorf("got %d banners; want 1", bannerCount)
+ }
+
+ expected := "Hello World"
+ if receivedBanner != expected {
+ t.Fatalf("got %s; want %s", receivedBanner, expected)
+ }
+}
+
+func TestNewClientConn(t *testing.T) {
+ errHostKeyMismatch := errors.New("host key mismatch")
+
+ for _, tt := range []struct {
+ name string
+ user string
+ simulateHostKeyMismatch HostKeyCallback
+ }{
+ {
+ name: "good user field for ConnMetadata",
+ user: "testuser",
+ },
+ {
+ name: "empty user field for ConnMetadata",
+ user: "",
+ },
+ {
+ name: "host key mismatch",
+ user: "testuser",
+ simulateHostKeyMismatch: func(hostname string, remote net.Addr, key PublicKey) error {
+ return fmt.Errorf("%w: %s", errHostKeyMismatch, bytes.TrimSpace(MarshalAuthorizedKey(key)))
+ },
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ return &Permissions{}, nil
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+ go NewServerConn(c1, serverConf)
+
+ clientConf := &ClientConfig{
+ User: tt.user,
+ Auth: []AuthMethod{
+ Password("testpw"),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ if tt.simulateHostKeyMismatch != nil {
+ clientConf.HostKeyCallback = tt.simulateHostKeyMismatch
+ }
+
+ clientConn, _, _, err := NewClientConn(c2, "", clientConf)
+ if err != nil {
+ if tt.simulateHostKeyMismatch != nil && errors.Is(err, errHostKeyMismatch) {
+ return
+ }
+ t.Fatal(err)
+ }
+
+ if userGot := clientConn.User(); userGot != tt.user {
+ t.Errorf("got user %q; want user %q", userGot, tt.user)
+ }
+ })
+ }
+}
+
+func TestUnsupportedAlgorithm(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ config Config
+ wantError string
+ }{
+ {
+ "unsupported KEX",
+ Config{
+ KeyExchanges: []string{"unsupported"},
+ },
+ "no common algorithm",
+ },
+ {
+ "unsupported and supported KEXs",
+ Config{
+ KeyExchanges: []string{"unsupported", KeyExchangeCurve25519},
+ },
+ "",
+ },
+ {
+ "unsupported cipher",
+ Config{
+ Ciphers: []string{"unsupported"},
+ },
+ "no common algorithm",
+ },
+ {
+ "unsupported and supported ciphers",
+ Config{
+ Ciphers: []string{"unsupported", CipherChaCha20Poly1305},
+ },
+ "",
+ },
+ {
+ "unsupported MAC",
+ Config{
+ MACs: []string{"unsupported"},
+ // MAC is used for non AAED ciphers.
+ Ciphers: []string{CipherAES256CTR},
+ },
+ "no common algorithm",
+ },
+ {
+ "unsupported and supported MACs",
+ Config{
+ MACs: []string{"unsupported", HMACSHA256ETM},
+ // MAC is used for non AAED ciphers.
+ Ciphers: []string{CipherAES256CTR},
+ },
+ "",
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ Config: tt.config,
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ return &Permissions{}, nil
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+ go NewServerConn(c1, serverConf)
+
+ clientConf := &ClientConfig{
+ User: "testuser",
+ Config: tt.config,
+ Auth: []AuthMethod{
+ Password("testpw"),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ _, _, _, err = NewClientConn(c2, "", clientConf)
+ if err != nil {
+ if tt.wantError == "" || !strings.Contains(err.Error(), tt.wantError) {
+ t.Errorf("%s: got error %q, missing %q", tt.name, err.Error(), tt.wantError)
+ }
+ } else if tt.wantError != "" {
+ t.Errorf("%s: succeeded, but want error string %q", tt.name, tt.wantError)
+ }
+ })
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/common.go b/local_crypto_patch/contents/ssh/common.go
new file mode 100644
index 0000000000..2e44e9c9ec
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/common_test.go b/local_crypto_patch/contents/ssh/common_test.go
new file mode 100644
index 0000000000..80aa2df73b
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/common_test.go
@@ -0,0 +1,196 @@
+// 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 ssh
+
+import (
+ "maps"
+ "reflect"
+ "slices"
+ "testing"
+)
+
+func TestFindAgreedAlgorithms(t *testing.T) {
+ initKex := func(k *kexInitMsg) {
+ if k.KexAlgos == nil {
+ k.KexAlgos = []string{"kex1"}
+ }
+ if k.ServerHostKeyAlgos == nil {
+ k.ServerHostKeyAlgos = []string{"hostkey1"}
+ }
+ if k.CiphersClientServer == nil {
+ k.CiphersClientServer = []string{"cipher1"}
+
+ }
+ if k.CiphersServerClient == nil {
+ k.CiphersServerClient = []string{"cipher1"}
+
+ }
+ if k.MACsClientServer == nil {
+ k.MACsClientServer = []string{"mac1"}
+
+ }
+ if k.MACsServerClient == nil {
+ k.MACsServerClient = []string{"mac1"}
+
+ }
+ if k.CompressionClientServer == nil {
+ k.CompressionClientServer = []string{"compression1"}
+
+ }
+ if k.CompressionServerClient == nil {
+ k.CompressionServerClient = []string{"compression1"}
+
+ }
+ if k.LanguagesClientServer == nil {
+ k.LanguagesClientServer = []string{"language1"}
+
+ }
+ if k.LanguagesServerClient == nil {
+ k.LanguagesServerClient = []string{"language1"}
+
+ }
+ }
+
+ initDirAlgs := func(a *DirectionAlgorithms) {
+ if a.Cipher == "" {
+ a.Cipher = "cipher1"
+ }
+ if a.MAC == "" {
+ a.MAC = "mac1"
+ }
+ if a.compression == "" {
+ a.compression = "compression1"
+ }
+ }
+
+ initAlgs := func(a *NegotiatedAlgorithms) {
+ if a.KeyExchange == "" {
+ a.KeyExchange = "kex1"
+ }
+ if a.HostKey == "" {
+ a.HostKey = "hostkey1"
+ }
+ initDirAlgs(&a.Read)
+ initDirAlgs(&a.Write)
+ }
+
+ type testcase struct {
+ name string
+ clientIn, serverIn kexInitMsg
+ wantClient, wantServer NegotiatedAlgorithms
+ wantErr bool
+ }
+
+ cases := []testcase{
+ {
+ name: "standard",
+ },
+
+ {
+ name: "no common hostkey",
+ serverIn: kexInitMsg{
+ ServerHostKeyAlgos: []string{"hostkey2"},
+ },
+ wantErr: true,
+ },
+
+ {
+ name: "no common kex",
+ serverIn: kexInitMsg{
+ KexAlgos: []string{"kex2"},
+ },
+ wantErr: true,
+ },
+
+ {
+ name: "no common cipher",
+ serverIn: kexInitMsg{
+ CiphersClientServer: []string{"cipher2"},
+ },
+ wantErr: true,
+ },
+
+ {
+ name: "client decides cipher",
+ serverIn: kexInitMsg{
+ CiphersClientServer: []string{"cipher1", "cipher2"},
+ CiphersServerClient: []string{"cipher2", "cipher3"},
+ },
+ clientIn: kexInitMsg{
+ CiphersClientServer: []string{"cipher2", "cipher1"},
+ CiphersServerClient: []string{"cipher3", "cipher2"},
+ },
+ wantClient: NegotiatedAlgorithms{
+ Read: DirectionAlgorithms{
+ Cipher: "cipher3",
+ },
+ Write: DirectionAlgorithms{
+ Cipher: "cipher2",
+ },
+ },
+ wantServer: NegotiatedAlgorithms{
+ Write: DirectionAlgorithms{
+ Cipher: "cipher3",
+ },
+ Read: DirectionAlgorithms{
+ Cipher: "cipher2",
+ },
+ },
+ },
+
+ // TODO(hanwen): fix and add tests for AEAD ignoring
+ // the MACs field
+ }
+
+ for i := range cases {
+ initKex(&cases[i].clientIn)
+ initKex(&cases[i].serverIn)
+ initAlgs(&cases[i].wantClient)
+ initAlgs(&cases[i].wantServer)
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ serverAlgs, serverErr := findAgreedAlgorithms(false, &c.clientIn, &c.serverIn)
+ clientAlgs, clientErr := findAgreedAlgorithms(true, &c.clientIn, &c.serverIn)
+
+ serverHasErr := serverErr != nil
+ clientHasErr := clientErr != nil
+ if c.wantErr != serverHasErr || c.wantErr != clientHasErr {
+ t.Fatalf("got client/server error (%v, %v), want hasError %v",
+ clientErr, serverErr, c.wantErr)
+
+ }
+ if c.wantErr {
+ return
+ }
+
+ if !reflect.DeepEqual(serverAlgs, &c.wantServer) {
+ t.Errorf("server: got algs %#v, want %#v", serverAlgs, &c.wantServer)
+ }
+ if !reflect.DeepEqual(clientAlgs, &c.wantClient) {
+ t.Errorf("server: got algs %#v, want %#v", clientAlgs, &c.wantClient)
+ }
+ })
+ }
+}
+
+func TestKeyFormatAlgorithms(t *testing.T) {
+ supportedAlgos := SupportedAlgorithms()
+ insecureAlgos := InsecureAlgorithms()
+ algoritms := append(supportedAlgos.PublicKeyAuths, insecureAlgos.PublicKeyAuths...)
+ algoritms = append(algoritms, slices.Collect(maps.Keys(certKeyAlgoNames))...)
+
+ for _, algo := range algoritms {
+ keyFormat := keyFormatForAlgorithm(algo)
+ if keyFormat == "" {
+ t.Errorf("got empty key format for algorithm %q", algo)
+ }
+ if !slices.Contains(algorithmsForKeyFormat(keyFormat), algo) {
+ t.Errorf("algorithms for key format %q, does not contain %q", keyFormat, algo)
+ }
+
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/connection.go b/local_crypto_patch/contents/ssh/connection.go
new file mode 100644
index 0000000000..613a71a7b3
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/doc.go b/local_crypto_patch/contents/ssh/doc.go
new file mode 100644
index 0000000000..5b4de9effc
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/example_test.go b/local_crypto_patch/contents/ssh/example_test.go
new file mode 100644
index 0000000000..653f2c8f50
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/example_test.go
@@ -0,0 +1,510 @@
+// 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_test
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/rand"
+ "crypto/rsa"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/terminal"
+)
+
+func ExampleNewServerConn() {
+ // Public key authentication is done by comparing
+ // the public key of a received connection
+ // with the entries in the authorized_keys file.
+ authorizedKeysBytes, err := os.ReadFile("authorized_keys")
+ if err != nil {
+ log.Fatalf("Failed to load authorized_keys, err: %v", err)
+ }
+
+ authorizedKeysMap := map[string]bool{}
+ for len(authorizedKeysBytes) > 0 {
+ pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ authorizedKeysMap[string(pubKey.Marshal())] = true
+ authorizedKeysBytes = rest
+ }
+
+ // An SSH server is represented by a ServerConfig, which holds
+ // certificate details and handles authentication of ServerConns.
+ config := &ssh.ServerConfig{
+ // Remove to disable password auth.
+ PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
+ // Should use constant-time compare (or better, salt+hash) in
+ // a production setting.
+ if c.User() == "testuser" && string(pass) == "tiger" {
+ return nil, nil
+ }
+ return nil, fmt.Errorf("password rejected for %q", c.User())
+ },
+
+ // Remove to disable public key auth.
+ PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
+ if authorizedKeysMap[string(pubKey.Marshal())] {
+ return &ssh.Permissions{
+ // Record the public key used for authentication.
+ Extensions: map[string]string{
+ "pubkey-fp": ssh.FingerprintSHA256(pubKey),
+ },
+ }, nil
+ }
+ return nil, fmt.Errorf("unknown public key for %q", c.User())
+ },
+ }
+
+ privateBytes, err := os.ReadFile("id_rsa")
+ if err != nil {
+ log.Fatal("Failed to load private key: ", err)
+ }
+
+ private, err := ssh.ParsePrivateKey(privateBytes)
+ if err != nil {
+ log.Fatal("Failed to parse private key: ", err)
+ }
+ config.AddHostKey(private)
+
+ // Once a ServerConfig has been configured, connections can be
+ // accepted.
+ listener, err := net.Listen("tcp", "0.0.0.0:2022")
+ if err != nil {
+ log.Fatal("failed to listen for connection: ", err)
+ }
+ nConn, err := listener.Accept()
+ if err != nil {
+ log.Fatal("failed to accept incoming connection: ", err)
+ }
+
+ // Before use, a handshake must be performed on the incoming
+ // net.Conn.
+ conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
+ if err != nil {
+ log.Fatal("failed to handshake: ", err)
+ }
+ log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"])
+
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ // The incoming Request channel must be serviced.
+ wg.Add(1)
+ go func() {
+ ssh.DiscardRequests(reqs)
+ wg.Done()
+ }()
+
+ // Service the incoming Channel channel.
+ for newChannel := range chans {
+ // Channels have a type, depending on the application level
+ // protocol intended. In the case of a shell, the type is
+ // "session" and ServerShell may be used to present a simple
+ // terminal interface.
+ if newChannel.ChannelType() != "session" {
+ newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
+ continue
+ }
+ channel, requests, err := newChannel.Accept()
+ if err != nil {
+ log.Fatalf("Could not accept channel: %v", err)
+ }
+
+ // Sessions have out-of-band requests such as "shell",
+ // "pty-req" and "env". Here we handle only the
+ // "shell" request.
+ wg.Add(1)
+ go func(in <-chan *ssh.Request) {
+ for req := range in {
+ req.Reply(req.Type == "shell", nil)
+ }
+ wg.Done()
+ }(requests)
+
+ term := terminal.NewTerminal(channel, "> ")
+
+ wg.Add(1)
+ go func() {
+ defer func() {
+ channel.Close()
+ wg.Done()
+ }()
+ for {
+ line, err := term.ReadLine()
+ if err != nil {
+ break
+ }
+ fmt.Println(line)
+ }
+ }()
+ }
+}
+
+func ExampleServerConfig() {
+ // Minimal ServerConfig with SHA-1 algorithms disabled and supporting only
+ // public key authentication.
+
+ // The algorithms returned by ssh.SupportedAlgorithms() are different from
+ // the default ones and do not include algorithms that are considered
+ // insecure, such as those using SHA-1, returned by
+ // ssh.InsecureAlgorithms().
+ algorithms := ssh.SupportedAlgorithms()
+
+ // Public key authentication is done by comparing
+ // the public key of a received connection
+ // with the entries in the authorized_keys file.
+ authorizedKeysBytes, err := os.ReadFile("authorized_keys")
+ if err != nil {
+ log.Fatalf("Failed to load authorized_keys, err: %v", err)
+ }
+
+ authorizedKeysMap := map[string]bool{}
+ for len(authorizedKeysBytes) > 0 {
+ pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ authorizedKeysMap[string(pubKey.Marshal())] = true
+ authorizedKeysBytes = rest
+ }
+
+ config := &ssh.ServerConfig{
+ Config: ssh.Config{
+ KeyExchanges: algorithms.KeyExchanges,
+ Ciphers: algorithms.Ciphers,
+ MACs: algorithms.MACs,
+ },
+ // The configured PublicKeyAuthAlgorithms do not contain SHA-1 based
+ // signature formats, so if the client attempts to use them the
+ // authentication will fail before calling the defined callback.
+ PublicKeyAuthAlgorithms: algorithms.PublicKeyAuths,
+ PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
+ if authorizedKeysMap[string(pubKey.Marshal())] {
+ return &ssh.Permissions{
+ // Record the public key used for authentication.
+ Extensions: map[string]string{
+ "pubkey-fp": ssh.FingerprintSHA256(pubKey),
+ },
+ }, nil
+ }
+ return nil, fmt.Errorf("unknown public key for %q", c.User())
+ },
+ }
+
+ privateBytes, err := os.ReadFile("id_rsa")
+ if err != nil {
+ log.Fatal("Failed to load private key: ", err)
+ }
+
+ private, err := ssh.ParsePrivateKey(privateBytes)
+ if err != nil {
+ log.Fatal("Failed to parse private key: ", err)
+ }
+ // Restrict host key algorithms to disable ssh-rsa.
+ signer, err := ssh.NewSignerWithAlgorithms(private.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512})
+ if err != nil {
+ log.Fatal("Failed to create private key with restricted algorithms: ", err)
+ }
+ config.AddHostKey(signer)
+}
+
+func ExampleServerConfig_AddHostKey() {
+ // Minimal ServerConfig supporting only password authentication.
+ config := &ssh.ServerConfig{
+ PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
+ // Should use constant-time compare (or better, salt+hash) in
+ // a production setting.
+ if c.User() == "testuser" && string(pass) == "tiger" {
+ return nil, nil
+ }
+ return nil, fmt.Errorf("password rejected for %q", c.User())
+ },
+ }
+
+ privateBytes, err := os.ReadFile("id_rsa")
+ if err != nil {
+ log.Fatal("Failed to load private key: ", err)
+ }
+
+ private, err := ssh.ParsePrivateKey(privateBytes)
+ if err != nil {
+ log.Fatal("Failed to parse private key: ", err)
+ }
+ // Restrict host key algorithms to disable ssh-rsa.
+ signer, err := ssh.NewSignerWithAlgorithms(private.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512})
+ if err != nil {
+ log.Fatal("Failed to create private key with restricted algorithms: ", err)
+ }
+ config.AddHostKey(signer)
+}
+
+func ExampleClientConfig_HostKeyCallback() {
+ // Every client must provide a host key check. Here is a
+ // simple-minded parse of OpenSSH's known_hosts file
+ host := "hostname"
+ file, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ var hostKey ssh.PublicKey
+ for scanner.Scan() {
+ fields := strings.Split(scanner.Text(), " ")
+ if len(fields) != 3 {
+ continue
+ }
+ if strings.Contains(fields[0], host) {
+ var err error
+ hostKey, _, _, _, err = ssh.ParseAuthorizedKey(scanner.Bytes())
+ if err != nil {
+ log.Fatalf("error parsing %q: %v", fields[2], err)
+ }
+ break
+ }
+ }
+
+ if hostKey == nil {
+ log.Fatalf("no hostkey for %s", host)
+ }
+
+ config := ssh.ClientConfig{
+ User: os.Getenv("USER"),
+ HostKeyCallback: ssh.FixedHostKey(hostKey),
+ }
+
+ _, err = ssh.Dial("tcp", host+":22", &config)
+ log.Println(err)
+}
+
+func ExampleDial() {
+ var hostKey ssh.PublicKey
+ // An SSH client is represented with a ClientConn.
+ //
+ // To authenticate with the remote server you must pass at least one
+ // implementation of AuthMethod via the Auth field in ClientConfig,
+ // and provide a HostKeyCallback.
+ config := &ssh.ClientConfig{
+ User: "username",
+ Auth: []ssh.AuthMethod{
+ ssh.Password("yourpassword"),
+ },
+ HostKeyCallback: ssh.FixedHostKey(hostKey),
+ }
+ client, err := ssh.Dial("tcp", "yourserver.com:22", config)
+ if err != nil {
+ log.Fatal("Failed to dial: ", err)
+ }
+ defer client.Close()
+
+ // Each ClientConn can support multiple interactive sessions,
+ // represented by a Session.
+ session, err := client.NewSession()
+ if err != nil {
+ log.Fatal("Failed to create session: ", err)
+ }
+ defer session.Close()
+
+ // Once a Session is created, you can execute a single command on
+ // the remote side using the Run method.
+ var b bytes.Buffer
+ session.Stdout = &b
+ if err := session.Run("/usr/bin/whoami"); err != nil {
+ log.Fatal("Failed to run: " + err.Error())
+ }
+ fmt.Println(b.String())
+}
+
+func ExampleClientConfig() {
+ var hostKey ssh.PublicKey
+ key, err := os.ReadFile("/home/user/.ssh/id_rsa")
+ if err != nil {
+ log.Fatalf("unable to read private key: %v", err)
+ }
+
+ // Create the Signer for this private key.
+ signer, err := ssh.ParsePrivateKey(key)
+ if err != nil {
+ log.Fatalf("unable to parse private key: %v", err)
+ }
+
+ // Minimal ClientConfig with SHA-1 algorithms disabled.
+ // The algorithms returned by ssh.SupportedAlgorithms() are different from
+ // the default ones and do not include algorithms that are considered
+ // insecure, such as those using SHA-1, returned by
+ // ssh.InsecureAlgorithms().
+ algorithms := ssh.SupportedAlgorithms()
+ config := &ssh.ClientConfig{
+ Config: ssh.Config{
+ KeyExchanges: algorithms.KeyExchanges,
+ Ciphers: algorithms.Ciphers,
+ MACs: algorithms.MACs,
+ },
+ User: "username",
+ Auth: []ssh.AuthMethod{
+ ssh.PublicKeys(signer),
+ },
+ HostKeyCallback: ssh.FixedHostKey(hostKey),
+ // We should check that hostKey algorithm is included in
+ // algorithms.HostKeys.
+ HostKeyAlgorithms: algorithms.HostKeys,
+ }
+ client, err := ssh.Dial("tcp", "yourserver.com:22", config)
+ if err != nil {
+ log.Fatal("Failed to dial: ", err)
+ }
+ defer client.Close()
+}
+
+func ExamplePublicKeys() {
+ var hostKey ssh.PublicKey
+ // A public key may be used to authenticate against the remote
+ // server by using an unencrypted PEM-encoded private key file.
+ //
+ // If you have an encrypted private key, the crypto/x509 package
+ // can be used to decrypt it.
+ key, err := os.ReadFile("/home/user/.ssh/id_rsa")
+ if err != nil {
+ log.Fatalf("unable to read private key: %v", err)
+ }
+
+ // Create the Signer for this private key.
+ signer, err := ssh.ParsePrivateKey(key)
+ if err != nil {
+ log.Fatalf("unable to parse private key: %v", err)
+ }
+
+ config := &ssh.ClientConfig{
+ User: "user",
+ Auth: []ssh.AuthMethod{
+ // Use the PublicKeys method for remote authentication.
+ ssh.PublicKeys(signer),
+ },
+ HostKeyCallback: ssh.FixedHostKey(hostKey),
+ }
+
+ // Connect to the remote server and perform the SSH handshake.
+ client, err := ssh.Dial("tcp", "host.com:22", config)
+ if err != nil {
+ log.Fatalf("unable to connect: %v", err)
+ }
+ defer client.Close()
+}
+
+func ExampleClient_Listen() {
+ var hostKey ssh.PublicKey
+ config := &ssh.ClientConfig{
+ User: "username",
+ Auth: []ssh.AuthMethod{
+ ssh.Password("password"),
+ },
+ HostKeyCallback: ssh.FixedHostKey(hostKey),
+ }
+ // Dial your ssh server.
+ conn, err := ssh.Dial("tcp", "localhost:22", config)
+ if err != nil {
+ log.Fatal("unable to connect: ", err)
+ }
+ defer conn.Close()
+
+ // Request the remote side to open port 8080 on all interfaces.
+ l, err := conn.Listen("tcp", "0.0.0.0:8080")
+ if err != nil {
+ log.Fatal("unable to register tcp forward: ", err)
+ }
+ defer l.Close()
+
+ // Serve HTTP with your SSH server acting as a reverse proxy.
+ http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ fmt.Fprintf(resp, "Hello world!\n")
+ }))
+}
+
+func ExampleSession_RequestPty() {
+ var hostKey ssh.PublicKey
+ // Create client config
+ config := &ssh.ClientConfig{
+ User: "username",
+ Auth: []ssh.AuthMethod{
+ ssh.Password("password"),
+ },
+ HostKeyCallback: ssh.FixedHostKey(hostKey),
+ }
+ // Connect to ssh server
+ conn, err := ssh.Dial("tcp", "localhost:22", config)
+ if err != nil {
+ log.Fatal("unable to connect: ", err)
+ }
+ defer conn.Close()
+ // Create a session
+ session, err := conn.NewSession()
+ if err != nil {
+ log.Fatal("unable to create session: ", err)
+ }
+ defer session.Close()
+ // Set up terminal modes
+ modes := ssh.TerminalModes{
+ ssh.ECHO: 0, // disable echoing
+ ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
+ ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
+ }
+ // Request pseudo terminal
+ if err := session.RequestPty("xterm", 40, 80, modes); err != nil {
+ log.Fatal("request for pseudo terminal failed: ", err)
+ }
+ // Start remote shell
+ if err := session.Shell(); err != nil {
+ log.Fatal("failed to start shell: ", err)
+ }
+}
+
+func ExampleCertificate_SignCert() {
+ // Sign a certificate with a specific algorithm.
+ privateKey, err := rsa.GenerateKey(rand.Reader, 3072)
+ if err != nil {
+ log.Fatal("unable to generate RSA key: ", err)
+ }
+ publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
+ if err != nil {
+ log.Fatal("unable to get RSA public key: ", err)
+ }
+ caKey, err := rsa.GenerateKey(rand.Reader, 3072)
+ if err != nil {
+ log.Fatal("unable to generate CA key: ", err)
+ }
+ signer, err := ssh.NewSignerFromKey(caKey)
+ if err != nil {
+ log.Fatal("unable to generate signer from key: ", err)
+ }
+ mas, err := ssh.NewSignerWithAlgorithms(signer.(ssh.AlgorithmSigner), []string{ssh.KeyAlgoRSASHA256})
+ if err != nil {
+ log.Fatal("unable to create signer with algorithms: ", err)
+ }
+ certificate := ssh.Certificate{
+ Key: publicKey,
+ CertType: ssh.UserCert,
+ }
+ if err := certificate.SignCert(rand.Reader, mas); err != nil {
+ log.Fatal("unable to sign certificate: ", err)
+ }
+ // Save the public key to a file and check that rsa-sha-256 is used for
+ // signing:
+ // ssh-keygen -L -f
+ fmt.Println(string(ssh.MarshalAuthorizedKey(&certificate)))
+}
diff --git a/local_crypto_patch/contents/ssh/handshake.go b/local_crypto_patch/contents/ssh/handshake.go
new file mode 100644
index 0000000000..4be3cbb6de
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/handshake_test.go b/local_crypto_patch/contents/ssh/handshake_test.go
new file mode 100644
index 0000000000..56f230cae9
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/handshake_test.go
@@ -0,0 +1,1366 @@
+// 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 (
+ "bytes"
+ "crypto/rand"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "reflect"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+)
+
+type testChecker struct {
+ calls []string
+}
+
+func (t *testChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
+ if dialAddr == "bad" {
+ return fmt.Errorf("dialAddr is bad")
+ }
+
+ if tcpAddr, ok := addr.(*net.TCPAddr); !ok || tcpAddr == nil {
+ return fmt.Errorf("testChecker: got %T want *net.TCPAddr", addr)
+ }
+
+ t.calls = append(t.calls, fmt.Sprintf("%s %v %s %x", dialAddr, addr, key.Type(), key.Marshal()))
+
+ return nil
+}
+
+// netPipe is analogous to net.Pipe, but it uses a real net.Conn, and
+// therefore is buffered (net.Pipe deadlocks if both sides start with
+// a write.)
+func netPipe() (net.Conn, net.Conn, error) {
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ listener, err = net.Listen("tcp", "[::1]:0")
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+ defer listener.Close()
+ c1, err := net.Dial("tcp", listener.Addr().String())
+ if err != nil {
+ return nil, nil, err
+ }
+
+ c2, err := listener.Accept()
+ if err != nil {
+ c1.Close()
+ return nil, nil, err
+ }
+
+ return c1, c2, nil
+}
+
+// noiseTransport inserts ignore messages to check that the read loop
+// and the key exchange filters out these messages.
+type noiseTransport struct {
+ keyingTransport
+}
+
+func (t *noiseTransport) writePacket(p []byte) error {
+ ignore := []byte{msgIgnore}
+ if err := t.keyingTransport.writePacket(ignore); err != nil {
+ return err
+ }
+ debug := []byte{msgDebug, 1, 2, 3}
+ if err := t.keyingTransport.writePacket(debug); err != nil {
+ return err
+ }
+
+ return t.keyingTransport.writePacket(p)
+}
+
+func addNoiseTransport(t keyingTransport) keyingTransport {
+ return &noiseTransport{t}
+}
+
+// handshakePair creates two handshakeTransports connected with each
+// other. If the noise argument is true, both transports will try to
+// confuse the other side by sending ignore and debug messages.
+func handshakePair(clientConf *ClientConfig, addr string, noise bool) (client *handshakeTransport, server *handshakeTransport, err error) {
+ a, b, err := netPipe()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var trC, trS keyingTransport
+
+ trC = newTransport(a, rand.Reader, true)
+ trS = newTransport(b, rand.Reader, false)
+ if noise {
+ trC = addNoiseTransport(trC)
+ trS = addNoiseTransport(trS)
+ }
+ clientConf.SetDefaults()
+
+ v := []byte("version")
+ client = newClientTransport(trC, v, v, clientConf, addr, a.RemoteAddr())
+
+ serverConf := &ServerConfig{}
+ serverConf.AddHostKey(testSigners["ecdsa"])
+ serverConf.AddHostKey(testSigners["rsa"])
+ serverConf.SetDefaults()
+ server = newServerTransport(trS, v, v, serverConf)
+
+ if err := server.waitSession(); err != nil {
+ return nil, nil, fmt.Errorf("server.waitSession: %v", err)
+ }
+ if err := client.waitSession(); err != nil {
+ return nil, nil, fmt.Errorf("client.waitSession: %v", err)
+ }
+
+ return client, server, nil
+}
+
+func TestHandshakeBasic(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("see golang.org/issue/7237")
+ }
+
+ checker := &syncChecker{
+ waitCall: make(chan int, 10),
+ called: make(chan int, 10),
+ }
+
+ checker.waitCall <- 1
+ trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
+ if err != nil {
+ t.Fatalf("handshakePair: %v", err)
+ }
+
+ defer trC.Close()
+ defer trS.Close()
+
+ // Let first kex complete normally.
+ <-checker.called
+
+ clientDone := make(chan int, 0)
+ gotHalf := make(chan int, 0)
+ const N = 20
+ errorCh := make(chan error, 1)
+
+ go func() {
+ defer close(clientDone)
+ // Client writes a bunch of stuff, and does a key
+ // change in the middle. This should not confuse the
+ // handshake in progress. We do this twice, so we test
+ // that the packet buffer is reset correctly.
+ for i := 0; i < N; i++ {
+ p := []byte{msgRequestSuccess, byte(i)}
+ if err := trC.writePacket(p); err != nil {
+ errorCh <- err
+ trC.Close()
+ return
+ }
+ if (i % 10) == 5 {
+ <-gotHalf
+ // halfway through, we request a key change.
+ trC.requestKeyExchange()
+
+ // Wait until we can be sure the key
+ // change has really started before we
+ // write more.
+ <-checker.called
+ }
+ if (i % 10) == 7 {
+ // write some packets until the kex
+ // completes, to test buffering of
+ // packets.
+ checker.waitCall <- 1
+ }
+ }
+ errorCh <- nil
+ }()
+
+ // Server checks that client messages come in cleanly
+ i := 0
+ for ; i < N; i++ {
+ p, err := trS.readPacket()
+ if err != nil && err != io.EOF {
+ t.Fatalf("server error: %v", err)
+ }
+ if (i % 10) == 5 {
+ gotHalf <- 1
+ }
+
+ want := []byte{msgRequestSuccess, byte(i)}
+ if bytes.Compare(p, want) != 0 {
+ t.Errorf("message %d: got %v, want %v", i, p, want)
+ }
+ }
+ <-clientDone
+ if err := <-errorCh; err != nil {
+ t.Fatalf("sendPacket: %v", err)
+ }
+ if i != N {
+ t.Errorf("received %d messages, want 10.", i)
+ }
+
+ close(checker.called)
+ if _, ok := <-checker.called; ok {
+ // If all went well, we registered exactly 2 key changes: one
+ // that establishes the session, and one that we requested
+ // additionally.
+ t.Fatalf("got another host key checks after 2 handshakes")
+ }
+}
+
+func TestForceFirstKex(t *testing.T) {
+ // like handshakePair, but must access the keyingTransport.
+ checker := &testChecker{}
+ clientConf := &ClientConfig{HostKeyCallback: checker.Check}
+ a, b, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+
+ var trC, trS keyingTransport
+
+ trC = newTransport(a, rand.Reader, true)
+
+ // This is the disallowed packet:
+ trC.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth}))
+
+ // Rest of the setup.
+ trS = newTransport(b, rand.Reader, false)
+ clientConf.SetDefaults()
+
+ v := []byte("version")
+ client := newClientTransport(trC, v, v, clientConf, "addr", a.RemoteAddr())
+
+ serverConf := &ServerConfig{}
+ serverConf.AddHostKey(testSigners["ecdsa"])
+ serverConf.AddHostKey(testSigners["rsa"])
+ serverConf.SetDefaults()
+ server := newServerTransport(trS, v, v, serverConf)
+
+ defer client.Close()
+ defer server.Close()
+
+ // We setup the initial key exchange, but the remote side
+ // tries to send serviceRequestMsg in cleartext, which is
+ // disallowed.
+
+ if err := server.waitSession(); err == nil {
+ t.Errorf("server first kex init should reject unexpected packet")
+ }
+}
+
+func TestHandshakeAutoRekeyWrite(t *testing.T) {
+ checker := &syncChecker{
+ called: make(chan int, 10),
+ waitCall: nil,
+ }
+ clientConf := &ClientConfig{HostKeyCallback: checker.Check}
+ clientConf.RekeyThreshold = 500
+ trC, trS, err := handshakePair(clientConf, "addr", false)
+ if err != nil {
+ t.Fatalf("handshakePair: %v", err)
+ }
+ defer trC.Close()
+ defer trS.Close()
+
+ input := make([]byte, 251)
+ input[0] = msgRequestSuccess
+
+ done := make(chan int, 1)
+ const numPacket = 5
+ go func() {
+ defer close(done)
+ j := 0
+ for ; j < numPacket; j++ {
+ if p, err := trS.readPacket(); err != nil {
+ break
+ } else if !bytes.Equal(input, p) {
+ t.Errorf("got packet type %d, want %d", p[0], input[0])
+ }
+ }
+
+ if j != numPacket {
+ t.Errorf("got %d, want 5 messages", j)
+ }
+ }()
+
+ <-checker.called
+
+ for i := 0; i < numPacket; i++ {
+ p := make([]byte, len(input))
+ copy(p, input)
+ if err := trC.writePacket(p); err != nil {
+ t.Errorf("writePacket: %v", err)
+ }
+ if i == 2 {
+ // Make sure the kex is in progress.
+ <-checker.called
+ }
+
+ }
+ <-done
+}
+
+type syncChecker struct {
+ waitCall chan int
+ called chan int
+}
+
+func (c *syncChecker) Check(dialAddr string, addr net.Addr, key PublicKey) error {
+ c.called <- 1
+ if c.waitCall != nil {
+ <-c.waitCall
+ }
+ return nil
+}
+
+func TestHandshakeAutoRekeyRead(t *testing.T) {
+ sync := &syncChecker{
+ called: make(chan int, 2),
+ waitCall: nil,
+ }
+ clientConf := &ClientConfig{
+ HostKeyCallback: sync.Check,
+ }
+ clientConf.RekeyThreshold = 500
+
+ trC, trS, err := handshakePair(clientConf, "addr", false)
+ if err != nil {
+ t.Fatalf("handshakePair: %v", err)
+ }
+ defer trC.Close()
+ defer trS.Close()
+
+ packet := make([]byte, 501)
+ packet[0] = msgRequestSuccess
+ if err := trS.writePacket(packet); err != nil {
+ t.Fatalf("writePacket: %v", err)
+ }
+
+ // While we read out the packet, a key change will be
+ // initiated.
+ errorCh := make(chan error, 1)
+ go func() {
+ _, err := trC.readPacket()
+ errorCh <- err
+ }()
+
+ if err := <-errorCh; err != nil {
+ t.Fatalf("readPacket(client): %v", err)
+ }
+
+ <-sync.called
+}
+
+// errorKeyingTransport generates errors after a given number of
+// read/write operations.
+type errorKeyingTransport struct {
+ packetConn
+ readLeft, writeLeft int
+}
+
+func (n *errorKeyingTransport) prepareKeyChange(*NegotiatedAlgorithms, *kexResult) error {
+ return nil
+}
+
+func (n *errorKeyingTransport) getSessionID() []byte {
+ return nil
+}
+
+func (n *errorKeyingTransport) writePacket(packet []byte) error {
+ if n.writeLeft == 0 {
+ n.Close()
+ return errors.New("barf")
+ }
+
+ n.writeLeft--
+ return n.packetConn.writePacket(packet)
+}
+
+func (n *errorKeyingTransport) readPacket() ([]byte, error) {
+ if n.readLeft == 0 {
+ n.Close()
+ return nil, errors.New("barf")
+ }
+
+ n.readLeft--
+ return n.packetConn.readPacket()
+}
+
+func (n *errorKeyingTransport) setStrictMode() error { return nil }
+
+func (n *errorKeyingTransport) setInitialKEXDone() {}
+
+func TestHandshakeErrorHandlingRead(t *testing.T) {
+ for i := 0; i < 20; i++ {
+ testHandshakeErrorHandlingN(t, i, -1, false)
+ }
+}
+
+func TestHandshakeErrorHandlingWrite(t *testing.T) {
+ for i := 0; i < 20; i++ {
+ testHandshakeErrorHandlingN(t, -1, i, false)
+ }
+}
+
+func TestHandshakeErrorHandlingReadCoupled(t *testing.T) {
+ for i := 0; i < 20; i++ {
+ testHandshakeErrorHandlingN(t, i, -1, true)
+ }
+}
+
+func TestHandshakeErrorHandlingWriteCoupled(t *testing.T) {
+ for i := 0; i < 20; i++ {
+ testHandshakeErrorHandlingN(t, -1, i, true)
+ }
+}
+
+// testHandshakeErrorHandlingN runs handshakes, injecting errors. If
+// handshakeTransport deadlocks, the go runtime will detect it and
+// panic.
+func testHandshakeErrorHandlingN(t *testing.T, readLimit, writeLimit int, coupled bool) {
+ if (runtime.GOOS == "js" || runtime.GOOS == "wasip1") && runtime.GOARCH == "wasm" {
+ t.Skipf("skipping on %s/wasm; see golang.org/issue/32840", runtime.GOOS)
+ }
+ msg := Marshal(&serviceRequestMsg{strings.Repeat("x", int(minRekeyThreshold)/4)})
+
+ a, b := memPipe()
+ defer a.Close()
+ defer b.Close()
+
+ key := testSigners["ecdsa"]
+ serverConf := Config{RekeyThreshold: minRekeyThreshold}
+ serverConf.SetDefaults()
+ serverConn := newHandshakeTransport(&errorKeyingTransport{a, readLimit, writeLimit}, &serverConf, []byte{'a'}, []byte{'b'})
+ serverConn.hostKeys = []Signer{key}
+ go serverConn.readLoop()
+ go serverConn.kexLoop()
+
+ clientConf := Config{RekeyThreshold: 10 * minRekeyThreshold}
+ clientConf.SetDefaults()
+ clientConn := newHandshakeTransport(&errorKeyingTransport{b, -1, -1}, &clientConf, []byte{'a'}, []byte{'b'})
+ clientConn.hostKeyAlgorithms = []string{key.PublicKey().Type()}
+ clientConn.hostKeyCallback = InsecureIgnoreHostKey()
+ go clientConn.readLoop()
+ go clientConn.kexLoop()
+
+ var wg sync.WaitGroup
+
+ for _, hs := range []packetConn{serverConn, clientConn} {
+ if !coupled {
+ wg.Add(2)
+ go func(c packetConn) {
+ for i := 0; ; i++ {
+ str := fmt.Sprintf("%08x", i) + strings.Repeat("x", int(minRekeyThreshold)/4-8)
+ err := c.writePacket(Marshal(&serviceRequestMsg{str}))
+ if err != nil {
+ break
+ }
+ }
+ wg.Done()
+ c.Close()
+ }(hs)
+ go func(c packetConn) {
+ for {
+ _, err := c.readPacket()
+ if err != nil {
+ break
+ }
+ }
+ wg.Done()
+ }(hs)
+ } else {
+ wg.Add(1)
+ go func(c packetConn) {
+ for {
+ _, err := c.readPacket()
+ if err != nil {
+ break
+ }
+ if err := c.writePacket(msg); err != nil {
+ break
+ }
+
+ }
+ wg.Done()
+ }(hs)
+ }
+ }
+ wg.Wait()
+}
+
+func TestDisconnect(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("see golang.org/issue/7237")
+ }
+ checker := &testChecker{}
+ trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
+ if err != nil {
+ t.Fatalf("handshakePair: %v", err)
+ }
+
+ defer trC.Close()
+ defer trS.Close()
+
+ trC.writePacket([]byte{msgRequestSuccess, 0, 0})
+ errMsg := &disconnectMsg{
+ Reason: 42,
+ Message: "such is life",
+ }
+ trC.writePacket(Marshal(errMsg))
+ trC.writePacket([]byte{msgRequestSuccess, 0, 0})
+
+ packet, err := trS.readPacket()
+ if err != nil {
+ t.Fatalf("readPacket 1: %v", err)
+ }
+ if packet[0] != msgRequestSuccess {
+ t.Errorf("got packet %v, want packet type %d", packet, msgRequestSuccess)
+ }
+
+ _, err = trS.readPacket()
+ if err == nil {
+ t.Errorf("readPacket 2 succeeded")
+ } else if !reflect.DeepEqual(err, errMsg) {
+ t.Errorf("got error %#v, want %#v", err, errMsg)
+ }
+
+ _, err = trS.readPacket()
+ if err == nil {
+ t.Errorf("readPacket 3 succeeded")
+ }
+}
+
+type mockKeyingTransport struct {
+ packetConn
+ kexInitAllowed chan struct{}
+ kexInitSent chan struct{}
+}
+
+func (n *mockKeyingTransport) prepareKeyChange(*NegotiatedAlgorithms, *kexResult) error {
+ return nil
+}
+
+func (n *mockKeyingTransport) writePacket(packet []byte) error {
+ if packet[0] == msgKexInit {
+ <-n.kexInitAllowed
+ n.kexInitSent <- struct{}{}
+ }
+ return n.packetConn.writePacket(packet)
+}
+
+func (n *mockKeyingTransport) readPacket() ([]byte, error) {
+ return n.packetConn.readPacket()
+}
+
+func (n *mockKeyingTransport) setStrictMode() error { return nil }
+
+func (n *mockKeyingTransport) setInitialKEXDone() {}
+
+func TestHandshakePendingPacketsWait(t *testing.T) {
+ a, b := memPipe()
+
+ trS := &mockKeyingTransport{
+ packetConn: a,
+ kexInitAllowed: make(chan struct{}, 2),
+ kexInitSent: make(chan struct{}, 2),
+ }
+ // Allow the first KEX.
+ trS.kexInitAllowed <- struct{}{}
+
+ trC := &mockKeyingTransport{
+ packetConn: b,
+ kexInitAllowed: make(chan struct{}, 2),
+ kexInitSent: make(chan struct{}, 2),
+ }
+ // Allow the first KEX.
+ trC.kexInitAllowed <- struct{}{}
+
+ clientConf := &ClientConfig{
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ clientConf.SetDefaults()
+
+ v := []byte("version")
+ client := newClientTransport(trC, v, v, clientConf, "addr", nil)
+
+ serverConf := &ServerConfig{}
+ serverConf.AddHostKey(testSigners["ecdsa"])
+ serverConf.AddHostKey(testSigners["rsa"])
+ serverConf.SetDefaults()
+ server := newServerTransport(trS, v, v, serverConf)
+
+ if err := server.waitSession(); err != nil {
+ t.Fatalf("server.waitSession: %v", err)
+ }
+ if err := client.waitSession(); err != nil {
+ t.Fatalf("client.waitSession: %v", err)
+ }
+
+ <-trC.kexInitSent
+ <-trS.kexInitSent
+
+ // Allow and request new KEX server side.
+ trS.kexInitAllowed <- struct{}{}
+ server.requestKeyExchange()
+ // Wait until the KEX init is sent.
+ <-trS.kexInitSent
+ // The client is not allowed to respond to the KEX, so writes will be
+ // blocked on the server side once the packets queue is full.
+ for i := 0; i < maxPendingPackets; i++ {
+ p := []byte{msgRequestSuccess, byte(i)}
+ if err := server.writePacket(p); err != nil {
+ t.Errorf("unexpected write error: %v", err)
+ }
+ }
+ // The packets queue is now full, the next write will block.
+ server.mu.Lock()
+ if len(server.pendingPackets) != maxPendingPackets {
+ t.Errorf("unexpected pending packets size; got: %d, want: %d", len(server.pendingPackets), maxPendingPackets)
+ }
+ server.mu.Unlock()
+
+ writeDone := make(chan struct{})
+ go func() {
+ defer close(writeDone)
+
+ p := []byte{msgRequestSuccess, byte(65)}
+ // This write will block until KEX completes.
+ err := server.writePacket(p)
+ if err != nil {
+ t.Errorf("unexpected write error: %v", err)
+ }
+ }()
+
+ // Consume packets on the client side
+ readDone := make(chan bool)
+ go func() {
+ defer close(readDone)
+
+ for {
+ if _, err := client.readPacket(); err != nil {
+ if err != io.EOF {
+ t.Errorf("unexpected read error: %v", err)
+ }
+ break
+ }
+ }
+ }()
+
+ // Allow the client to reply to the KEX and so unblock the write goroutine.
+ trC.kexInitAllowed <- struct{}{}
+ <-trC.kexInitSent
+ <-writeDone
+ // Close the client to unblock the read goroutine.
+ client.Close()
+ <-readDone
+ server.Close()
+}
+
+func TestHandshakePendingPacketsError(t *testing.T) {
+ a, b := memPipe()
+
+ trS := &mockKeyingTransport{
+ packetConn: a,
+ kexInitAllowed: make(chan struct{}, 2),
+ kexInitSent: make(chan struct{}, 2),
+ }
+ // Allow the first KEX.
+ trS.kexInitAllowed <- struct{}{}
+
+ trC := &mockKeyingTransport{
+ packetConn: b,
+ kexInitAllowed: make(chan struct{}, 2),
+ kexInitSent: make(chan struct{}, 2),
+ }
+ // Allow the first KEX.
+ trC.kexInitAllowed <- struct{}{}
+
+ clientConf := &ClientConfig{
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ clientConf.SetDefaults()
+
+ v := []byte("version")
+ client := newClientTransport(trC, v, v, clientConf, "addr", nil)
+
+ serverConf := &ServerConfig{}
+ serverConf.AddHostKey(testSigners["ecdsa"])
+ serverConf.AddHostKey(testSigners["rsa"])
+ serverConf.SetDefaults()
+ server := newServerTransport(trS, v, v, serverConf)
+
+ if err := server.waitSession(); err != nil {
+ t.Fatalf("server.waitSession: %v", err)
+ }
+ if err := client.waitSession(); err != nil {
+ t.Fatalf("client.waitSession: %v", err)
+ }
+
+ <-trC.kexInitSent
+ <-trS.kexInitSent
+
+ // Allow and request new KEX server side.
+ trS.kexInitAllowed <- struct{}{}
+ server.requestKeyExchange()
+ // Wait until the KEX init is sent.
+ <-trS.kexInitSent
+ // The client is not allowed to respond to the KEX, so writes will be
+ // blocked on the server side once the packets queue is full.
+ for i := 0; i < maxPendingPackets; i++ {
+ p := []byte{msgRequestSuccess, byte(i)}
+ if err := server.writePacket(p); err != nil {
+ t.Errorf("unexpected write error: %v", err)
+ }
+ }
+ // The packets queue is now full, the next write will block.
+ writeDone := make(chan struct{})
+ go func() {
+ defer close(writeDone)
+
+ p := []byte{msgRequestSuccess, byte(65)}
+ // This write will block until KEX completes.
+ err := server.writePacket(p)
+ if err != io.EOF {
+ t.Errorf("unexpected write error: %v", err)
+ }
+ }()
+
+ // Consume packets on the client side
+ readDone := make(chan bool)
+ go func() {
+ defer close(readDone)
+
+ for {
+ if _, err := client.readPacket(); err != nil {
+ if err != io.EOF {
+ t.Errorf("unexpected read error: %v", err)
+ }
+ break
+ }
+ }
+ }()
+
+ // Close the server to unblock the write after an error
+ server.Close()
+ <-writeDone
+ // Unblock the pending write and close the client to unblock the read
+ // goroutine.
+ trC.kexInitAllowed <- struct{}{}
+ client.Close()
+ <-readDone
+}
+
+func TestHandshakeRekeyDefault(t *testing.T) {
+ clientConf := &ClientConfig{
+ Config: Config{
+ Ciphers: []string{CipherAES128CTR},
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ trC, trS, err := handshakePair(clientConf, "addr", false)
+ if err != nil {
+ t.Fatalf("handshakePair: %v", err)
+ }
+ defer trC.Close()
+ defer trS.Close()
+
+ trC.writePacket([]byte{msgRequestSuccess, 0, 0})
+ trC.Close()
+
+ rgb := (1024 + trC.readBytesLeft) >> 30
+ wgb := (1024 + trC.writeBytesLeft) >> 30
+
+ if rgb != 64 {
+ t.Errorf("got rekey after %dG read, want 64G", rgb)
+ }
+ if wgb != 64 {
+ t.Errorf("got rekey after %dG write, want 64G", wgb)
+ }
+}
+
+func TestHandshakeAEADCipherNoMAC(t *testing.T) {
+ for _, cipher := range []string{CipherChaCha20Poly1305, CipherAES128GCM} {
+ checker := &syncChecker{
+ called: make(chan int, 1),
+ }
+ clientConf := &ClientConfig{
+ Config: Config{
+ Ciphers: []string{cipher},
+ MACs: []string{},
+ },
+ HostKeyCallback: checker.Check,
+ }
+ trC, trS, err := handshakePair(clientConf, "addr", false)
+ if err != nil {
+ t.Fatalf("handshakePair: %v", err)
+ }
+ defer trC.Close()
+ defer trS.Close()
+
+ <-checker.called
+ }
+}
+
+// TestNoSHA2Support tests a host key Signer that is not an AlgorithmSigner and
+// therefore can't do SHA-2 signatures. Ensures the server does not advertise
+// support for them in this case.
+func TestNoSHA2Support(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ return &Permissions{}, nil
+ },
+ }
+ serverConf.AddHostKey(&legacyRSASigner{testSigners["rsa"]})
+ go func() {
+ _, _, _, err := NewServerConn(c1, serverConf)
+ if err != nil {
+ t.Error(err)
+ }
+ }()
+
+ clientConf := &ClientConfig{
+ User: "test",
+ Auth: []AuthMethod{Password("testpw")},
+ HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
+ }
+
+ if _, _, _, err := NewClientConn(c2, "", clientConf); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMultiAlgoSignerHandshake(t *testing.T) {
+ algorithmSigner, ok := testSigners["rsa"].(AlgorithmSigner)
+ if !ok {
+ t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
+ }
+ multiAlgoSigner, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
+ if err != nil {
+ t.Fatalf("unable to create multi algorithm signer: %v", err)
+ }
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ return &Permissions{}, nil
+ },
+ }
+ serverConf.AddHostKey(multiAlgoSigner)
+ go NewServerConn(c1, serverConf)
+
+ clientConf := &ClientConfig{
+ User: "test",
+ Auth: []AuthMethod{Password("testpw")},
+ HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
+ HostKeyAlgorithms: []string{KeyAlgoRSASHA512},
+ }
+
+ if _, _, _, err := NewClientConn(c2, "", clientConf); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMultiAlgoSignerNoCommonHostKeyAlgo(t *testing.T) {
+ algorithmSigner, ok := testSigners["rsa"].(AlgorithmSigner)
+ if !ok {
+ t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
+ }
+ multiAlgoSigner, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
+ if err != nil {
+ t.Fatalf("unable to create multi algorithm signer: %v", err)
+ }
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ // ssh-rsa is disabled server side
+ serverConf := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ return &Permissions{}, nil
+ },
+ }
+ serverConf.AddHostKey(multiAlgoSigner)
+ go NewServerConn(c1, serverConf)
+
+ // the client only supports ssh-rsa
+ clientConf := &ClientConfig{
+ User: "test",
+ Auth: []AuthMethod{Password("testpw")},
+ HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
+ HostKeyAlgorithms: []string{KeyAlgoRSA},
+ }
+
+ _, _, _, err = NewClientConn(c2, "", clientConf)
+ if err == nil {
+ t.Fatal("succeeded connecting with no common hostkey algorithm")
+ }
+}
+
+func TestPickIncompatibleHostKeyAlgo(t *testing.T) {
+ algorithmSigner, ok := testSigners["rsa"].(AlgorithmSigner)
+ if !ok {
+ t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
+ }
+ multiAlgoSigner, err := NewSignerWithAlgorithms(algorithmSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
+ if err != nil {
+ t.Fatalf("unable to create multi algorithm signer: %v", err)
+ }
+ signer := pickHostKey([]Signer{multiAlgoSigner}, KeyAlgoRSA)
+ if signer != nil {
+ t.Fatal("incompatible signer returned")
+ }
+}
+
+func TestStrictKEXResetSeqFirstKEX(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("see golang.org/issue/7237")
+ }
+
+ checker := &syncChecker{
+ waitCall: make(chan int, 10),
+ called: make(chan int, 10),
+ }
+
+ checker.waitCall <- 1
+ trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
+ if err != nil {
+ t.Fatalf("handshakePair: %v", err)
+ }
+ <-checker.called
+
+ t.Cleanup(func() {
+ trC.Close()
+ trS.Close()
+ })
+
+ // Throw away the msgExtInfo packet sent during the handshake by the server
+ _, err = trC.readPacket()
+ if err != nil {
+ t.Fatalf("readPacket failed: %s", err)
+ }
+
+ // close the handshake transports before checking the sequence number to
+ // avoid races.
+ trC.Close()
+ trS.Close()
+
+ // check that the sequence number counters. We reset after msgNewKeys, but
+ // then the server immediately writes msgExtInfo, and we close the
+ // transports so we expect read 2, write 0 on the client and read 1, write 1
+ // on the server.
+ if trC.conn.(*transport).reader.seqNum != 2 || trC.conn.(*transport).writer.seqNum != 0 ||
+ trS.conn.(*transport).reader.seqNum != 1 || trS.conn.(*transport).writer.seqNum != 1 {
+ t.Errorf(
+ "unexpected sequence counters:\nclient: reader %d (expected 2), writer %d (expected 0)\nserver: reader %d (expected 1), writer %d (expected 1)",
+ trC.conn.(*transport).reader.seqNum,
+ trC.conn.(*transport).writer.seqNum,
+ trS.conn.(*transport).reader.seqNum,
+ trS.conn.(*transport).writer.seqNum,
+ )
+ }
+}
+
+func TestStrictKEXResetSeqSuccessiveKEX(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("see golang.org/issue/7237")
+ }
+
+ checker := &syncChecker{
+ waitCall: make(chan int, 10),
+ called: make(chan int, 10),
+ }
+
+ checker.waitCall <- 1
+ trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
+ if err != nil {
+ t.Fatalf("handshakePair: %v", err)
+ }
+ <-checker.called
+
+ t.Cleanup(func() {
+ trC.Close()
+ trS.Close()
+ })
+
+ // Throw away the msgExtInfo packet sent during the handshake by the server
+ _, err = trC.readPacket()
+ if err != nil {
+ t.Fatalf("readPacket failed: %s", err)
+ }
+
+ // write and read five packets on either side to bump the sequence numbers
+ for i := 0; i < 5; i++ {
+ if err := trC.writePacket([]byte{msgRequestSuccess}); err != nil {
+ t.Fatalf("writePacket failed: %s", err)
+ }
+ if _, err := trS.readPacket(); err != nil {
+ t.Fatalf("readPacket failed: %s", err)
+ }
+ if err := trS.writePacket([]byte{msgRequestSuccess}); err != nil {
+ t.Fatalf("writePacket failed: %s", err)
+ }
+ if _, err := trC.readPacket(); err != nil {
+ t.Fatalf("readPacket failed: %s", err)
+ }
+ }
+
+ // Request a key exchange, which should cause the sequence numbers to reset
+ checker.waitCall <- 1
+ trC.requestKeyExchange()
+ <-checker.called
+
+ // write a packet on the client, and then read it, to verify the key change has actually happened, since
+ // the HostKeyCallback is called _during_ the handshake, so isn't actually indicative of the handshake
+ // finishing.
+ dummyPacket := []byte{99}
+ if err := trS.writePacket(dummyPacket); err != nil {
+ t.Fatalf("writePacket failed: %s", err)
+ }
+ if p, err := trC.readPacket(); err != nil {
+ t.Fatalf("readPacket failed: %s", err)
+ } else if !bytes.Equal(p, dummyPacket) {
+ t.Fatalf("unexpected packet: got %x, want %x", p, dummyPacket)
+ }
+
+ // close the handshake transports before checking the sequence number to
+ // avoid races.
+ trC.Close()
+ trS.Close()
+
+ if trC.conn.(*transport).reader.seqNum != 2 || trC.conn.(*transport).writer.seqNum != 0 ||
+ trS.conn.(*transport).reader.seqNum != 1 || trS.conn.(*transport).writer.seqNum != 1 {
+ t.Errorf(
+ "unexpected sequence counters:\nclient: reader %d (expected 2), writer %d (expected 0)\nserver: reader %d (expected 1), writer %d (expected 1)",
+ trC.conn.(*transport).reader.seqNum,
+ trC.conn.(*transport).writer.seqNum,
+ trS.conn.(*transport).reader.seqNum,
+ trS.conn.(*transport).writer.seqNum,
+ )
+ }
+}
+
+func TestSeqNumIncrease(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("see golang.org/issue/7237")
+ }
+
+ checker := &syncChecker{
+ waitCall: make(chan int, 10),
+ called: make(chan int, 10),
+ }
+
+ checker.waitCall <- 1
+ trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: checker.Check}, "addr", false)
+ if err != nil {
+ t.Fatalf("handshakePair: %v", err)
+ }
+ <-checker.called
+
+ t.Cleanup(func() {
+ trC.Close()
+ trS.Close()
+ })
+
+ // Throw away the msgExtInfo packet sent during the handshake by the server
+ _, err = trC.readPacket()
+ if err != nil {
+ t.Fatalf("readPacket failed: %s", err)
+ }
+
+ // write and read five packets on either side to bump the sequence numbers
+ for i := 0; i < 5; i++ {
+ if err := trC.writePacket([]byte{msgRequestSuccess}); err != nil {
+ t.Fatalf("writePacket failed: %s", err)
+ }
+ if _, err := trS.readPacket(); err != nil {
+ t.Fatalf("readPacket failed: %s", err)
+ }
+ if err := trS.writePacket([]byte{msgRequestSuccess}); err != nil {
+ t.Fatalf("writePacket failed: %s", err)
+ }
+ if _, err := trC.readPacket(); err != nil {
+ t.Fatalf("readPacket failed: %s", err)
+ }
+ }
+
+ // close the handshake transports before checking the sequence number to
+ // avoid races.
+ trC.Close()
+ trS.Close()
+
+ if trC.conn.(*transport).reader.seqNum != 7 || trC.conn.(*transport).writer.seqNum != 5 ||
+ trS.conn.(*transport).reader.seqNum != 6 || trS.conn.(*transport).writer.seqNum != 6 {
+ t.Errorf(
+ "unexpected sequence counters:\nclient: reader %d (expected 7), writer %d (expected 5)\nserver: reader %d (expected 6), writer %d (expected 6)",
+ trC.conn.(*transport).reader.seqNum,
+ trC.conn.(*transport).writer.seqNum,
+ trS.conn.(*transport).reader.seqNum,
+ trS.conn.(*transport).writer.seqNum,
+ )
+ }
+}
+
+func TestStrictKEXUnexpectedMsg(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("see golang.org/issue/7237")
+ }
+
+ // Check that unexpected messages during the handshake cause failure
+ _, _, err := handshakePair(&ClientConfig{HostKeyCallback: func(hostname string, remote net.Addr, key PublicKey) error { return nil }}, "addr", true)
+ if err == nil {
+ t.Fatal("handshake should fail when there are unexpected messages during the handshake")
+ }
+
+ trC, trS, err := handshakePair(&ClientConfig{HostKeyCallback: func(hostname string, remote net.Addr, key PublicKey) error { return nil }}, "addr", false)
+ if err != nil {
+ t.Fatalf("handshake failed: %s", err)
+ }
+
+ // Check that ignore/debug pacekts are still ignored outside of the handshake
+ if err := trC.writePacket([]byte{msgIgnore}); err != nil {
+ t.Fatalf("writePacket failed: %s", err)
+ }
+ if err := trC.writePacket([]byte{msgDebug}); err != nil {
+ t.Fatalf("writePacket failed: %s", err)
+ }
+ dummyPacket := []byte{99}
+ if err := trC.writePacket(dummyPacket); err != nil {
+ t.Fatalf("writePacket failed: %s", err)
+ }
+
+ if p, err := trS.readPacket(); err != nil {
+ t.Fatalf("readPacket failed: %s", err)
+ } else if !bytes.Equal(p, dummyPacket) {
+ t.Fatalf("unexpected packet: got %x, want %x", p, dummyPacket)
+ }
+}
+
+func TestStrictKEXMixed(t *testing.T) {
+ // Test that we still support a mixed connection, where one side sends kex-strict but the other
+ // side doesn't.
+
+ a, b, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe failed: %s", err)
+ }
+
+ var trC, trS keyingTransport
+
+ trC = newTransport(a, rand.Reader, true)
+ trS = newTransport(b, rand.Reader, false)
+ trS = addNoiseTransport(trS)
+
+ clientConf := &ClientConfig{HostKeyCallback: func(hostname string, remote net.Addr, key PublicKey) error { return nil }}
+ clientConf.SetDefaults()
+
+ v := []byte("version")
+ client := newClientTransport(trC, v, v, clientConf, "addr", a.RemoteAddr())
+
+ serverConf := &ServerConfig{}
+ serverConf.AddHostKey(testSigners["ecdsa"])
+ serverConf.AddHostKey(testSigners["rsa"])
+ serverConf.SetDefaults()
+
+ transport := newHandshakeTransport(trS, &serverConf.Config, []byte("version"), []byte("version"))
+ transport.hostKeys = serverConf.hostKeys
+ transport.publicKeyAuthAlgorithms = serverConf.PublicKeyAuthAlgorithms
+
+ readOneFailure := make(chan error, 1)
+ go func() {
+ if _, err := transport.readOnePacket(true); err != nil {
+ readOneFailure <- err
+ }
+ }()
+
+ // Basically sendKexInit, but without the kex-strict extension algorithm
+ msg := &kexInitMsg{
+ KexAlgos: transport.config.KeyExchanges,
+ CiphersClientServer: transport.config.Ciphers,
+ CiphersServerClient: transport.config.Ciphers,
+ MACsClientServer: transport.config.MACs,
+ MACsServerClient: transport.config.MACs,
+ CompressionClientServer: supportedCompressions,
+ CompressionServerClient: supportedCompressions,
+ ServerHostKeyAlgos: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512, KeyAlgoRSA},
+ }
+ packet := Marshal(msg)
+ // writePacket destroys the contents, so save a copy.
+ packetCopy := make([]byte, len(packet))
+ copy(packetCopy, packet)
+ if err := transport.pushPacket(packetCopy); err != nil {
+ t.Fatalf("pushPacket: %s", err)
+ }
+ transport.sentInitMsg = msg
+ transport.sentInitPacket = packet
+
+ if err := transport.getWriteError(); err != nil {
+ t.Fatalf("getWriteError failed: %s", err)
+ }
+ var request *pendingKex
+ select {
+ case err = <-readOneFailure:
+ t.Fatalf("server readOnePacket failed: %s", err)
+ case request = <-transport.startKex:
+ break
+ }
+
+ // We expect the following calls to fail if the side which does not support
+ // kex-strict sends unexpected/ignored packets during the handshake, even if
+ // the other side does support kex-strict.
+
+ if err := transport.enterKeyExchange(request.otherInit); err != nil {
+ t.Fatalf("enterKeyExchange failed: %s", err)
+ }
+ if err := client.waitSession(); err != nil {
+ t.Fatalf("client.waitSession: %v", err)
+ }
+}
+
+func TestNegotiatedAlgorithms(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ var serverAlgorithms NegotiatedAlgorithms
+
+ serverConf := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if algorithmConn, ok := conn.(AlgorithmsConnMetadata); ok {
+ serverAlgorithms = algorithmConn.Algorithms()
+ return &Permissions{}, nil
+ }
+ return nil, errors.New("server conn does not implement AlgorithmsConnMetadata")
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+ go NewServerConn(c1, serverConf)
+
+ clientConf := &ClientConfig{
+ User: "test",
+ Auth: []AuthMethod{Password("testpw")},
+ HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
+ }
+
+ if conn, _, _, err := NewClientConn(c2, "", clientConf); err != nil {
+ t.Fatal(err)
+ } else {
+ if algorithmConn, ok := conn.(AlgorithmsConnMetadata); ok {
+ clientAlgorithms := algorithmConn.Algorithms()
+ if clientAlgorithms.HostKey == "" {
+ t.Fatal("negotiated client host key is empty")
+ }
+ if clientAlgorithms.KeyExchange == "" {
+ t.Fatal("negotiated client KEX is empty")
+ }
+ if clientAlgorithms.Read.Cipher == "" {
+ t.Fatal("negotiated client read cipher is empty")
+ }
+ if clientAlgorithms.Write.Cipher == "" {
+ t.Fatal("negotiated client write cipher is empty")
+ }
+ if !reflect.DeepEqual(clientAlgorithms, serverAlgorithms) {
+ t.Fatalf("negotiated client algorithms: %+v differs from negotiated server algorithms: %+v",
+ clientAlgorithms, serverAlgorithms)
+ }
+ } else {
+ t.Fatal("client conn does not implement AlgorithmsConnMetadata")
+ }
+ }
+}
+
+func TestAlgorithmNegotiationError(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ Config: Config{
+ Ciphers: []string{CipherAES128CTR, CipherAES256CTR},
+ },
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ return &Permissions{}, nil
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ srvErrCh := make(chan error, 1)
+ go func() {
+ _, _, _, err := NewServerConn(c1, serverConf)
+ srvErrCh <- err
+ }()
+
+ clientConf := &ClientConfig{
+ Config: Config{
+ Ciphers: []string{CipherAES128GCM, CipherAES256GCM},
+ },
+ User: "test",
+ Auth: []AuthMethod{Password("testpw")},
+ HostKeyCallback: FixedHostKey(testSigners["rsa"].PublicKey()),
+ }
+
+ _, _, _, err = NewClientConn(c2, "", clientConf)
+ if err == nil {
+ t.Fatal("client connection succeeded expected algorithm negotiation error")
+ }
+ var negotiationError *AlgorithmNegotiationError
+ if !errors.As(err, &negotiationError) {
+ t.Fatalf("expected algorithm negotiation error, got %v", err)
+ }
+ expectedErrorString := fmt.Sprintf("ssh: handshake failed: ssh: no common algorithm for client to server cipher; we offered: %v, peer offered: %v",
+ clientConf.Ciphers, serverConf.Ciphers)
+ if err.Error() != expectedErrorString {
+ t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
+ }
+ if !reflect.DeepEqual(negotiationError.RequestedAlgorithms, serverConf.Ciphers) {
+ t.Fatalf("expected requested algorithms %v, got %v", serverConf.Ciphers, negotiationError.RequestedAlgorithms)
+ }
+ if !reflect.DeepEqual(negotiationError.SupportedAlgorithms, clientConf.Ciphers) {
+ t.Fatalf("expected supported algorithms %v, got %v", clientConf.Ciphers, negotiationError.SupportedAlgorithms)
+ }
+ err = <-srvErrCh
+ negotiationError = nil
+ if !errors.As(err, &negotiationError) {
+ t.Fatalf("expected algorithm negotiation error, got %v", err)
+ }
+ expectedErrorString = fmt.Sprintf("ssh: no common algorithm for client to server cipher; we offered: %v, peer offered: %v",
+ serverConf.Ciphers, clientConf.Ciphers)
+ if err.Error() != expectedErrorString {
+ t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
+ }
+ if !reflect.DeepEqual(negotiationError.RequestedAlgorithms, clientConf.Ciphers) {
+ t.Fatalf("expected requested algorithms %v, got %v", clientConf.Ciphers, negotiationError.RequestedAlgorithms)
+ }
+ if !reflect.DeepEqual(negotiationError.SupportedAlgorithms, serverConf.Ciphers) {
+ t.Fatalf("expected supported algorithms %v, got %v", serverConf.Ciphers, negotiationError.SupportedAlgorithms)
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go b/local_crypto_patch/contents/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf.go
new file mode 100644
index 0000000000..af81d26654
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf_test.go b/local_crypto_patch/contents/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf_test.go
new file mode 100644
index 0000000000..20b7889bce
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/internal/bcrypt_pbkdf/bcrypt_pbkdf_test.go
@@ -0,0 +1,97 @@
+// 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
+
+import (
+ "bytes"
+ "testing"
+)
+
+// Test vectors generated by the reference implementation from OpenBSD.
+var golden = []struct {
+ rounds int
+ password, salt, result []byte
+}{
+ {
+ 12,
+ []byte("password"),
+ []byte("salt"),
+ []byte{
+ 0x1a, 0xe4, 0x2c, 0x05, 0xd4, 0x87, 0xbc, 0x02, 0xf6,
+ 0x49, 0x21, 0xa4, 0xeb, 0xe4, 0xea, 0x93, 0xbc, 0xac,
+ 0xfe, 0x13, 0x5f, 0xda, 0x99, 0x97, 0x4c, 0x06, 0xb7,
+ 0xb0, 0x1f, 0xae, 0x14, 0x9a,
+ },
+ },
+ {
+ 3,
+ []byte("passwordy\x00PASSWORD\x00"),
+ []byte("salty\x00SALT\x00"),
+ []byte{
+ 0x7f, 0x31, 0x0b, 0xd3, 0xe7, 0x8c, 0x32, 0x80, 0xc5,
+ 0x9c, 0xe4, 0x59, 0x52, 0x11, 0xa2, 0x92, 0x8e, 0x8d,
+ 0x4e, 0xc7, 0x44, 0xc1, 0xed, 0x2e, 0xfc, 0x9f, 0x76,
+ 0x4e, 0x33, 0x88, 0xe0, 0xad,
+ },
+ },
+ {
+ // See http://thread.gmane.org/gmane.os.openbsd.bugs/20542
+ 8,
+ []byte("секретное слово"),
+ []byte("посолить немножко"),
+ []byte{
+ 0x8d, 0xf4, 0x3f, 0xc6, 0xfe, 0x13, 0x1f, 0xc4, 0x7f,
+ 0x0c, 0x9e, 0x39, 0x22, 0x4b, 0xd9, 0x4c, 0x70, 0xb6,
+ 0xfc, 0xc8, 0xee, 0x81, 0x35, 0xfa, 0xdd, 0xf6, 0x11,
+ 0x56, 0xe6, 0xcb, 0x27, 0x33, 0xea, 0x76, 0x5f, 0x31,
+ 0x5a, 0x3e, 0x1e, 0x4a, 0xfc, 0x35, 0xbf, 0x86, 0x87,
+ 0xd1, 0x89, 0x25, 0x4c, 0x1e, 0x05, 0xa6, 0xfe, 0x80,
+ 0xc0, 0x61, 0x7f, 0x91, 0x83, 0xd6, 0x72, 0x60, 0xd6,
+ 0xa1, 0x15, 0xc6, 0xc9, 0x4e, 0x36, 0x03, 0xe2, 0x30,
+ 0x3f, 0xbb, 0x43, 0xa7, 0x6a, 0x64, 0x52, 0x3f, 0xfd,
+ 0xa6, 0x86, 0xb1, 0xd4, 0x51, 0x85, 0x43,
+ },
+ },
+}
+
+func TestKey(t *testing.T) {
+ for i, v := range golden {
+ k, err := Key(v.password, v.salt, v.rounds, len(v.result))
+ if err != nil {
+ t.Errorf("%d: %s", i, err)
+ continue
+ }
+ if !bytes.Equal(k, v.result) {
+ t.Errorf("%d: expected\n%x\n, got\n%x\n", i, v.result, k)
+ }
+ }
+}
+
+func TestBcryptHash(t *testing.T) {
+ good := []byte{
+ 0x87, 0x90, 0x48, 0x70, 0xee, 0xf9, 0xde, 0xdd, 0xf8, 0xe7,
+ 0x61, 0x1a, 0x14, 0x01, 0x06, 0xe6, 0xaa, 0xf1, 0xa3, 0x63,
+ 0xd9, 0xa2, 0xc5, 0x04, 0xdb, 0x35, 0x64, 0x43, 0x72, 0x1e,
+ 0xb5, 0x55,
+ }
+ var pass, salt [64]byte
+ var result [32]byte
+ for i := 0; i < 64; i++ {
+ pass[i] = byte(i)
+ salt[i] = byte(i + 64)
+ }
+ bcryptHash(result[:], pass[:], salt[:])
+ if !bytes.Equal(result[:], good) {
+ t.Errorf("expected %x, got %x", good, result)
+ }
+}
+
+func BenchmarkKey(b *testing.B) {
+ pass := []byte("password")
+ salt := []byte("salt")
+ for i := 0; i < b.N; i++ {
+ Key(pass, salt, 10, 32)
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/kex.go b/local_crypto_patch/contents/ssh/kex.go
new file mode 100644
index 0000000000..5f7fdd8514
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/kex_test.go b/local_crypto_patch/contents/ssh/kex_test.go
new file mode 100644
index 0000000000..cb7f66a509
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/kex_test.go
@@ -0,0 +1,106 @@
+// 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
+
+// Key exchange tests.
+
+import (
+ "crypto/rand"
+ "fmt"
+ "reflect"
+ "sync"
+ "testing"
+)
+
+// Runs multiple key exchanges concurrent to detect potential data races with
+// kex obtained from the global kexAlgoMap.
+// This test needs to be executed using the race detector in order to detect
+// race conditions.
+func TestKexes(t *testing.T) {
+ type kexResultErr struct {
+ result *kexResult
+ err error
+ }
+
+ for name, kex := range kexAlgoMap {
+ t.Run(name, func(t *testing.T) {
+ wg := sync.WaitGroup{}
+ for i := 0; i < 3; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ a, b := memPipe()
+
+ s := make(chan kexResultErr, 1)
+ c := make(chan kexResultErr, 1)
+ var magics handshakeMagics
+ go func() {
+ r, e := kex.Client(a, rand.Reader, &magics)
+ a.Close()
+ c <- kexResultErr{r, e}
+ }()
+ go func() {
+ r, e := kex.Server(b, rand.Reader, &magics, testSigners["ecdsa"].(AlgorithmSigner), testSigners["ecdsa"].PublicKey().Type())
+ b.Close()
+ s <- kexResultErr{r, e}
+ }()
+
+ clientRes := <-c
+ serverRes := <-s
+ if clientRes.err != nil {
+ t.Errorf("client: %v", clientRes.err)
+ }
+ if serverRes.err != nil {
+ t.Errorf("server: %v", serverRes.err)
+ }
+ if !reflect.DeepEqual(clientRes.result, serverRes.result) {
+ t.Errorf("kex %q: mismatch %#v, %#v", name, clientRes.result, serverRes.result)
+ }
+ }()
+ }
+ wg.Wait()
+ })
+ }
+}
+
+func BenchmarkKexes(b *testing.B) {
+ type kexResultErr struct {
+ result *kexResult
+ err error
+ }
+
+ for name, kex := range kexAlgoMap {
+ b.Run(name, func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ t1, t2 := memPipe()
+
+ s := make(chan kexResultErr, 1)
+ c := make(chan kexResultErr, 1)
+ var magics handshakeMagics
+
+ go func() {
+ r, e := kex.Client(t1, rand.Reader, &magics)
+ t1.Close()
+ c <- kexResultErr{r, e}
+ }()
+ go func() {
+ r, e := kex.Server(t2, rand.Reader, &magics, testSigners["ecdsa"].(AlgorithmSigner), testSigners["ecdsa"].PublicKey().Type())
+ t2.Close()
+ s <- kexResultErr{r, e}
+ }()
+
+ clientRes := <-c
+ serverRes := <-s
+
+ if clientRes.err != nil {
+ panic(fmt.Sprintf("client: %v", clientRes.err))
+ }
+ if serverRes.err != nil {
+ panic(fmt.Sprintf("server: %v", serverRes.err))
+ }
+ }
+ })
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/keys.go b/local_crypto_patch/contents/ssh/keys.go
new file mode 100644
index 0000000000..3482c4d2c4
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/keys.go
@@ -0,0 +1,1881 @@
+// 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
+ }
+
+ // 8192 bits is also the maximum RSA key size accepted by crypto/tls for
+ // signature verification:
+ // https://github.com/golang/go/blob/69801b25/src/crypto/tls/handshake_client.go#L1096
+ if w.N.BitLen() > 8192 {
+ return nil, nil, errors.New("ssh: rsa modulus too large")
+ }
+ 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)
+ }
+
+ // FIPS 186-2 specifies that Q must be exactly 160 bits. We must enforce
+ // this to prevent DoS attacks where an attacker sends a huge Q which makes
+ // verification slow.
+ if l := param.Q.BitLen(); l != 160 {
+ return fmt.Errorf("ssh: unsupported DSA sub-prime size %d", l)
+ }
+
+ // The generator G is an element of the group, so it must be strictly less
+ // than the modulus P.
+ if param.G.Cmp(param.P) >= 0 {
+ return errors.New("ssh: DSA generator larger than modulus")
+ }
+
+ // G must be positive.
+ if param.G.Sign() <= 0 {
+ return errors.New("ssh: DSA generator must be positive")
+ }
+
+ 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
+ }
+
+ // The public value Y must be a non-zero element of the group, i.e.
+ // strictly between 0 and P. crypto/dsa.Verify does not range-check Y,
+ // so we reject out-of-range values here to prevent a maliciously
+ // oversized Y from slowing verification.
+ if w.Y.Sign() <= 0 || w.Y.Cmp(w.P) >= 0 {
+ return nil, nil, errors.New("ssh: DSA public value Y out of range")
+ }
+
+ 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
+}
+
+// flagUserPresence is the "user present" bit (UP) in the SK signature
+// flags, matching the FIDO CTAP2 authenticatorData UP flag. See
+// openssh/PROTOCOL.u2f.
+const flagUserPresence = 0x01
+
+// errSKMissingUserPresence is returned by SK key Verify methods when
+// the signature does not assert user presence and the key was not
+// marked as no-touch-required.
+var errSKMissingUserPresence = errors.New("ssh: signature missing required user presence flag")
+
+type skECDSAPublicKey struct {
+ // application is a URL-like string, typically "ssh:" for SSH.
+ // see openssh/PROTOCOL.u2f for details.
+ application string
+ ecdsa.PublicKey
+ // noTouchRequired, when true, disables the default user-presence
+ // check in Verify. It is set by skKeyWithoutUP on a clone of the
+ // key, never on an instance shared across authentication attempts.
+ noTouchRequired bool
+}
+
+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
+ }
+
+ if skf.Flags&flagUserPresence == 0 && !k.noTouchRequired {
+ return errSKMissingUserPresence
+ }
+
+ 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
+ // noTouchRequired, when true, disables the default user-presence
+ // check in Verify. It is set by skKeyWithoutUP on a clone of the
+ // key, never on an instance shared across authentication attempts.
+ noTouchRequired bool
+}
+
+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
+ }
+
+ if skf.Flags&flagUserPresence == 0 && !k.noTouchRequired {
+ return errSKMissingUserPresence
+ }
+
+ 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/local_crypto_patch/contents/ssh/keys_test.go b/local_crypto_patch/contents/ssh/keys_test.go
new file mode 100644
index 0000000000..93a0a135df
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/keys_test.go
@@ -0,0 +1,1237 @@
+// 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 ssh
+
+import (
+ "bytes"
+ "crypto/dsa"
+ "crypto/ecdsa"
+ "crypto/ed25519"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/hex"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io"
+ "math/big"
+ "reflect"
+ "strings"
+ "testing"
+
+ "golang.org/x/crypto/ssh/testdata"
+)
+
+func rawKey(pub PublicKey) interface{} {
+ switch k := pub.(type) {
+ case *rsaPublicKey:
+ return (*rsa.PublicKey)(k)
+ case *dsaPublicKey:
+ return (*dsa.PublicKey)(k)
+ case *ecdsaPublicKey:
+ return (*ecdsa.PublicKey)(k)
+ case ed25519PublicKey:
+ return (ed25519.PublicKey)(k)
+ case *Certificate:
+ return k
+ }
+ panic("unknown key type")
+}
+
+func TestKeyMarshalParse(t *testing.T) {
+ for _, priv := range testSigners {
+ pub := priv.PublicKey()
+ roundtrip, err := ParsePublicKey(pub.Marshal())
+ if err != nil {
+ t.Errorf("ParsePublicKey(%T): %v", pub, err)
+ }
+
+ k1 := rawKey(pub)
+ k2 := rawKey(roundtrip)
+
+ if !reflect.DeepEqual(k1, k2) {
+ t.Errorf("got %#v in roundtrip, want %#v", k2, k1)
+ }
+ }
+}
+
+func TestParsePublicKeyWithSigningAlgoAsKeyFormat(t *testing.T) {
+ key := []byte(`rsa-sha2-256 AAAADHJzYS1zaGEyLTI1NgAAAAMBAAEAAAEBAJ7qMyjLXEJCCJmRknuCLo0uPi5GrPY5pQYr84lhlN8Gor5KVL2LKYCW4e70r5xzj7SrHHSCft1FMlYg1KDO9xrprJh733kQqAPWETmSuH0EfRtGtcH6EarKyVxk6As076/yNiiMKVBtG0RPa1L7FviTfcYK4vnCCVrbv3RmA5CCzuG5BSMbRLxzVb4Ri3p8jhxYT8N4QGe/2yqvJLys5vQ9szpZR3tcFp3DJIVZhBRfR6LnoY23XZniAAMQaUVBX86dXQ++dNwAwZSXSt9Og+AniOCiBYqhNVa5n3DID/H7YtEtG+CbZr3r2KD3fv8AfSLRar4XOp8rsRdD31h/kr8=`)
+ _, _, _, _, err := ParseAuthorizedKey(key)
+ if err == nil {
+ t.Fatal("parsing a public key using a signature algorithm as the key format succeeded unexpectedly")
+ }
+ if !strings.Contains(err.Error(), `signature algorithm "rsa-sha2-256" isn't a key format`) {
+ t.Errorf(`got %v, expected 'signature algorithm "rsa-sha2-256" isn't a key format'`, err)
+ }
+}
+
+func TestUnsupportedCurves(t *testing.T) {
+ raw, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
+ if err != nil {
+ t.Fatalf("GenerateKey: %v", err)
+ }
+
+ if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P-256") {
+ t.Fatalf("NewPrivateKey should not succeed with P-224, got: %v", err)
+ }
+
+ if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P-256") {
+ t.Fatalf("NewPublicKey should not succeed with P-224, got: %v", err)
+ }
+}
+
+func TestNewPublicKey(t *testing.T) {
+ for _, k := range testSigners {
+ raw := rawKey(k.PublicKey())
+ // Skip certificates, as NewPublicKey does not support them.
+ if _, ok := raw.(*Certificate); ok {
+ continue
+ }
+ pub, err := NewPublicKey(raw)
+ if err != nil {
+ t.Errorf("NewPublicKey(%#v): %v", raw, err)
+ }
+ if !reflect.DeepEqual(k.PublicKey(), pub) {
+ t.Errorf("NewPublicKey(%#v) = %#v, want %#v", raw, pub, k.PublicKey())
+ }
+ }
+}
+
+func TestKeySignVerify(t *testing.T) {
+ for _, priv := range testSigners {
+ pub := priv.PublicKey()
+
+ data := []byte("sign me")
+ sig, err := priv.Sign(rand.Reader, data)
+ if err != nil {
+ t.Fatalf("Sign(%T): %v", priv, err)
+ }
+
+ if err := pub.Verify(data, sig); err != nil {
+ t.Errorf("publicKey.Verify(%T): %v", priv, err)
+ }
+ sig.Blob[5]++
+ if err := pub.Verify(data, sig); err == nil {
+ t.Errorf("publicKey.Verify on broken sig did not fail")
+ }
+ }
+}
+
+func TestKeySignWithAlgorithmVerify(t *testing.T) {
+ for k, priv := range testSigners {
+ if algorithmSigner, ok := priv.(MultiAlgorithmSigner); !ok {
+ t.Errorf("Signers %q constructed by ssh package should always implement the MultiAlgorithmSigner interface: %T", k, priv)
+ } else {
+ pub := priv.PublicKey()
+ data := []byte("sign me")
+
+ signWithAlgTestCase := func(algorithm string, expectedAlg string) {
+ sig, err := algorithmSigner.SignWithAlgorithm(rand.Reader, data, algorithm)
+ if err != nil {
+ t.Fatalf("Sign(%T): %v", priv, err)
+ }
+ if sig.Format != expectedAlg {
+ t.Errorf("signature format did not match requested signature algorithm: %s != %s", sig.Format, expectedAlg)
+ }
+
+ if err := pub.Verify(data, sig); err != nil {
+ t.Errorf("publicKey.Verify(%T): %v", priv, err)
+ }
+ sig.Blob[5]++
+ if err := pub.Verify(data, sig); err == nil {
+ t.Errorf("publicKey.Verify on broken sig did not fail")
+ }
+ }
+
+ // Using the empty string as the algorithm name should result in the same signature format as the algorithm-free Sign method.
+ defaultSig, err := priv.Sign(rand.Reader, data)
+ if err != nil {
+ t.Fatalf("Sign(%T): %v", priv, err)
+ }
+ signWithAlgTestCase("", defaultSig.Format)
+
+ // RSA keys are the only ones which currently support more than one signing algorithm
+ if pub.Type() == KeyAlgoRSA {
+ for _, algorithm := range []string{KeyAlgoRSA, KeyAlgoRSASHA256, KeyAlgoRSASHA512} {
+ signWithAlgTestCase(algorithm, algorithm)
+ }
+ }
+ }
+ }
+}
+
+func TestKeySignWithShortSignature(t *testing.T) {
+ signer := testSigners["rsa"].(AlgorithmSigner)
+ pub := signer.PublicKey()
+ // Note: data obtained by empirically trying until a result
+ // starting with 0 appeared
+ tests := []struct {
+ algorithm string
+ data []byte
+ }{
+ {
+ algorithm: KeyAlgoRSA,
+ data: []byte("sign me92"),
+ },
+ {
+ algorithm: KeyAlgoRSASHA256,
+ data: []byte("sign me294"),
+ },
+ {
+ algorithm: KeyAlgoRSASHA512,
+ data: []byte("sign me60"),
+ },
+ }
+
+ for _, tt := range tests {
+ sig, err := signer.SignWithAlgorithm(rand.Reader, tt.data, tt.algorithm)
+ if err != nil {
+ t.Fatalf("Sign(%T): %v", signer, err)
+ }
+ if sig.Blob[0] != 0 {
+ t.Errorf("%s: Expected signature with a leading 0", tt.algorithm)
+ }
+ sig.Blob = sig.Blob[1:]
+ if err := pub.Verify(tt.data, sig); err != nil {
+ t.Errorf("publicKey.Verify(%s): %v", tt.algorithm, err)
+ }
+ }
+}
+
+func TestParseRSAPrivateKey(t *testing.T) {
+ key := testPrivateKeys["rsa"]
+
+ rsa, ok := key.(*rsa.PrivateKey)
+ if !ok {
+ t.Fatalf("got %T, want *rsa.PrivateKey", rsa)
+ }
+
+ if err := rsa.Validate(); err != nil {
+ t.Errorf("Validate: %v", err)
+ }
+}
+
+func TestParseRSAModulusTooLarge(t *testing.T) {
+ rsa16384 := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAIAQC0vyqtnpzMZ8Th6gv1t3+7dDVd2X+7MwjqKe08/wrKZaIuCAHX7jglC8FdHfiOcPhLHAreSJLZGXiSzCTUExbp9Zdt7tHluKMQxZCnbk02V89ggc4KpptQqfcmjMNgrEPF4PGJBe3eSYu9m7A1ptm0buWxaKtA17O1c2q2CVNYmuajSUAfi8+AZcdyRAX8eqga3u77DEj4O+CCuNA908NYv9Pc7SbaCwCgX3FXh7MvlaYcruTk529psNk1SK6uwBNak3CLRrXUBo5yTO7cCO0gmlvd8SwtY5mPaDLLCJa+Ed2t9OCt8HCPrdkH3aiQBTP3k/Iofy3NRv+/1lenJP1qWB2rnyagoj+osrLc6m8PRd7GBje7KdO6nyY1D+q1MvX4zV1qNQfD+38N6PixVX1MGKw72sD+oLCDjHKS24BHfnfaE7f8n+Alp2mxnSz9tYqsN3JXrbbwblzH8YFOz9MMKkTY5lQNQFoKdTHoPVXBqcMlGQGgC/kkH9TH110MDE3L+Z0i9kG53QLcHNVC0cJzo6X77PQF1WoS33Ssf1s21kUB6fe/5G2L2Tuy635ingtnQMUazwJMli+y/3U7QqqcuJzITbLw/zozRzbqFRE4HHOyGkJSptraH4/3cjmYro8JL20A7EUDiXxmn+z+eoxaBWaEP2Rs5LH0ocrwYmBmSUIrPy4e5ESlRRVuv+prNUB1pYGnFZUI9HRRFxRFg+SxXZZp4mq8jzroRLZFAqKY1/eDgsCGoV1HG3QW1t0jGNkv8BL/Zpz7Hhmt0lRr7rVN+PJaTQx8NNUJZ/l7nMXp/+7aTbkyI8PiNyemko0+joDrkBWdcK0vi8Du6k3oQRBUsJ0aM0iPxMWQq/oYyHqpf6revycYTUdOGeckGWXuXLNRIZQ/PsKuw/teqDaBkkMKaQH2oXWMsacL6WDu5VYUQidOtaTgHX8AKVHF/vobTQfw7HgPKlyRN41dAX+YIjRABYoqU7M2MYPuoPYbqzTz7iLZyFP8FCJ3dRQBTWSeer/s+Ih6Ev1EsznVmAw9SKb5q8QCSYk5mMugUTDVETOQo10CGAF6094/PcJRXgag/uWs4k+13c+/HaeNzrHQyqzWnf9bFFHIvs7Wqov9k+nWv/TwSGplkyBk4bHfhEZNbl+b6T8sZXsv8mTT+INzKT4DCVZ5a/G+NQ6Xo26Aa6Se5DOdzdXOLZ7yIhOi+olip8AOXIIFLb7Vee1+q7Ri6ScUJEKxdEvec5EceXEtWs0U4AnuHgiH0ewJSX9yUp8T4ImCzu00JhTMOH2nXU3PJuGgZgrDifNWXGpFruIBjHWRKpESpy+9HPGoNoxSO/hzSfuM5LKvwX+I96cMK5OC+fKsBh59cMpU4YnqFNh59ZP/kaMDxAXgrVku5s9oB3Rgc7t1rfm6dqycBiwn4QnYJpk7M+gzAbrF/nty8y84ajNdhINXgMgvv3JLC5YTqznBPm3koHSOEF8f4xtCZtzjNN6jNjX8gEvxoRpC9Z+X15c8XBSFyDMBoi0FMDor619XJKgTxNiOYmeH/DLhKEb8MIaug8IFu34FKbG81IsWD+zwA1A5xqXEVlgsfEOcML+Oe2rulkpwQBWnsmY+f/Z80004Mytreo/ME2TDuPJPOi15D0j0CbwG7xzE8qrn0EomBE9mrwH3uuH7df3lzx1HiLAmh2s4MO5R64AoWeAPQW9lWZPzp+2dNgk+0qWpoEsMN+r91yWxUZHgeoC+GJKs13LItH32sGInyUrquMYQjEh44fAEFrMREcS4A7l+875GUmWC6i2MSLyvtAzuPS2IV0t9GU0ooH8cfG5JUeDnsJcVanJTR+XuXPetM+cDyGFTGri3lsCndE9maLgs9iDHtJxzKUBUUakaIPLsXtZD51cS4jhSbbHuzgC21BRtnBhCE2phVYbz4Tj0t90wBmemBaP2eVwv9p4s/JJAHEELvV7k+Gro4FozOoC2WBdqdTPDFB00la08O6ADBdjd2el2pRA7HCTG6v3qwA6RQJfm4UHOXaFYhhiNngxHB4VHZMvpd+YMEqNmtYOb4lzWwI13iHU0Rh0Sj4IaY9EwEy/KjR5dc2DjWGj11SFFCP1uG24fccF+LhtdMNDItI1mBxfaeRelYlsCZwtbbVLnuVQ5izuTzXa48x7CBaHnD3i09BZCuQITLX4d7KkD6CLWWTHrc5onLFJKxRF/p5AEFoqtN3vP9CsLXSYNKxFV5UbxAY5TnAoLFCCo0WbgvtAV77jWSbru9Oq/7ORN9smog4zNuYBZE1uXjxM2xszDduOym7+CxwpJMc7XPknt2XwiANaf2QANMMMH6lmnjTH1RVR7oH4+ts4xVcsdiO/QOlMp+Th9/KIMYyUevR18vHbs+88uxzIG28/58xLZTs6rZc71g9mGw9Q21ugL2sfTc7e0VDMnAmdg+92s2RXQMvkmx+oRc+IsFqgOZzjYcTmnJxZaXsoIsydDVcTalgmhK6/dD0grPLaGgaeXMEw2hN8p8seAHrGRW8+6WBD63NBYaAG0/zwuGOCHUo/BeG9bz39Hsz9Yvs5KvJiLhmM5K+8RlnxIY329REqdFZVyuXyy0NpDueFQelnd0j47Quc7GJGX3QycJiKpLolMtDnpnjgYOvdfycM+JEMZGwpLsBBE8R6vJ3RVczT6DdMtpVQ4l7kOzsPSYlp4qAv5fiqUboyv5eP7G7MOD/qSUwBnMS1p9Vm4Wr8B9w=="
+ _, _, _, _, err := ParseAuthorizedKey([]byte(rsa16384))
+
+ if err == nil {
+ t.Fatal("ParseAuthorizedKey accepted a 16384-bit modulus; expected it to be rejected")
+ }
+
+ expectedError := "rsa modulus too large"
+ if !strings.Contains(err.Error(), expectedError) {
+ t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
+ }
+}
+
+func TestParseECPrivateKey(t *testing.T) {
+ key := testPrivateKeys["ecdsa"]
+
+ ecKey, ok := key.(*ecdsa.PrivateKey)
+ if !ok {
+ t.Fatalf("got %T, want *ecdsa.PrivateKey", ecKey)
+ }
+
+ if !validateECPublicKey(ecKey.Curve, ecKey.X, ecKey.Y) {
+ t.Fatalf("public key does not validate.")
+ }
+}
+
+func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
+ data := []byte("sign me")
+ for _, tt := range testdata.PEMEncryptedKeys {
+ t.Run(tt.Name, func(t *testing.T) {
+ _, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte("incorrect"))
+ if err != x509.IncorrectPasswordError {
+ t.Errorf("got %v want IncorrectPasswordError", err)
+ }
+
+ s, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
+ if err != nil {
+ t.Fatalf("ParsePrivateKeyWithPassphrase returned error: %s", err)
+ }
+
+ sig, err := s.Sign(rand.Reader, data)
+ if err != nil {
+ t.Fatalf("Signer.Sign: %v", err)
+ }
+ if err := s.PublicKey().Verify(data, sig); err != nil {
+ t.Errorf("Verify failed: %v", err)
+ }
+
+ _, err = ParsePrivateKey(tt.PEMBytes)
+ if err == nil {
+ t.Fatalf("ParsePrivateKey succeeded, expected an error")
+ }
+
+ if err, ok := err.(*PassphraseMissingError); !ok {
+ t.Errorf("got error %q, want PassphraseMissingError", err)
+ } else if tt.IncludesPublicKey {
+ if err.PublicKey == nil {
+ t.Fatalf("expected PassphraseMissingError.PublicKey not to be nil")
+ }
+ got, want := err.PublicKey.Marshal(), s.PublicKey().Marshal()
+ if !bytes.Equal(got, want) {
+ t.Errorf("error field %q doesn't match signer public key %q", got, want)
+ }
+ }
+ })
+ }
+}
+
+func TestParseEncryptedPrivateKeysWithUnsupportedCiphers(t *testing.T) {
+ for _, tt := range testdata.UnsupportedCipherData {
+ t.Run(tt.Name, func(t *testing.T) {
+ _, err := ParsePrivateKeyWithPassphrase(tt.PEMBytes, []byte(tt.EncryptionKey))
+ if err == nil {
+ t.Fatalf("expected 'unknown cipher' error for %q, got nil", tt.Name)
+ // If this cipher is now supported, remove it from testdata.UnsupportedCipherData
+ }
+ if !strings.Contains(err.Error(), "unknown cipher") {
+ t.Errorf("wanted 'unknown cipher' error, got %v", err.Error())
+ }
+ })
+ }
+}
+
+func TestParseEncryptedPrivateKeysWithIncorrectPassphrase(t *testing.T) {
+ pem := testdata.PEMEncryptedKeys[0].PEMBytes
+ for i := 0; i < 4096; i++ {
+ _, err := ParseRawPrivateKeyWithPassphrase(pem, []byte(fmt.Sprintf("%d", i)))
+ if !errors.Is(err, x509.IncorrectPasswordError) {
+ t.Fatalf("expected error: %v, got: %v", x509.IncorrectPasswordError, err)
+ }
+ }
+}
+
+func TestParseDSA(t *testing.T) {
+ // We actually exercise the ParsePrivateKey codepath here, as opposed to
+ // using the ParseRawPrivateKey+NewSignerFromKey path that testdata_test.go
+ // uses.
+ s, err := ParsePrivateKey(testdata.PEMBytes["dsa"])
+ if err != nil {
+ t.Fatalf("ParsePrivateKey returned error: %s", err)
+ }
+
+ data := []byte("sign me")
+ sig, err := s.Sign(rand.Reader, data)
+ if err != nil {
+ t.Fatalf("dsa.Sign: %v", err)
+ }
+
+ if err := s.PublicKey().Verify(data, sig); err != nil {
+ t.Errorf("Verify failed: %v", err)
+ }
+}
+
+// Tests for authorized_keys parsing.
+
+// getTestKey returns a public key, and its base64 encoding.
+func getTestKey() (PublicKey, string) {
+ k := testPublicKeys["rsa"]
+
+ b := &bytes.Buffer{}
+ e := base64.NewEncoder(base64.StdEncoding, b)
+ e.Write(k.Marshal())
+ e.Close()
+
+ return k, b.String()
+}
+
+func TestMarshalParsePublicKey(t *testing.T) {
+ pub, pubSerialized := getTestKey()
+ line := fmt.Sprintf("%s %s user@host", pub.Type(), pubSerialized)
+
+ authKeys := MarshalAuthorizedKey(pub)
+ actualFields := strings.Fields(string(authKeys))
+ if len(actualFields) == 0 {
+ t.Fatalf("failed authKeys: %v", authKeys)
+ }
+
+ // drop the comment
+ expectedFields := strings.Fields(line)[0:2]
+
+ if !reflect.DeepEqual(actualFields, expectedFields) {
+ t.Errorf("got %v, expected %v", actualFields, expectedFields)
+ }
+
+ actPub, _, _, _, err := ParseAuthorizedKey([]byte(line))
+ if err != nil {
+ t.Fatalf("cannot parse %v: %v", line, err)
+ }
+ if !reflect.DeepEqual(actPub, pub) {
+ t.Errorf("got %v, expected %v", actPub, pub)
+ }
+}
+
+func TestParseDSAHugeQ(t *testing.T) {
+ P := new(big.Int).Lsh(big.NewInt(1), 1023)
+ Q := new(big.Int).Lsh(big.NewInt(1), 20000) // very large
+ // G and Y: Dummy values, just needs to be < P to pass that specific check
+ G := big.NewInt(2)
+ Y := big.NewInt(5)
+
+ rawKey := struct {
+ P, Q, G, Y *big.Int
+ }{
+ P: P,
+ Q: Q,
+ G: G,
+ Y: Y,
+ }
+
+ inputBytes := Marshal(&rawKey)
+
+ _, _, err := parseDSA(inputBytes)
+ if err == nil {
+ t.Fatal("parseDSA accepted a DSA key with large Q")
+ }
+
+ expectedError := "ssh: unsupported DSA sub-prime size"
+ if !strings.Contains(err.Error(), expectedError) {
+ t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
+ }
+}
+
+func TestParseDSAYOutOfRange(t *testing.T) {
+ // Valid 1024/160 parameters (values don't need to be a real DSA group,
+ // they only need to pass the checkDSAParams bit-length checks and the
+ // G < P / G > 0 checks).
+ P := new(big.Int).Lsh(big.NewInt(1), 1023)
+ P.SetBit(P, 0, 1) // make P odd so it can pass as a prime candidate shape
+ Q := new(big.Int).Lsh(big.NewInt(1), 159)
+ Q.SetBit(Q, 0, 1)
+ G := big.NewInt(2)
+
+ for _, tc := range []struct {
+ name string
+ Y *big.Int
+ }{
+ {"Y_zero", big.NewInt(0)},
+ {"Y_negative", big.NewInt(-1)},
+ {"Y_equals_P", new(big.Int).Set(P)},
+ {"Y_greater_than_P", new(big.Int).Add(P, big.NewInt(1))},
+ {"Y_much_greater_than_P", new(big.Int).Lsh(big.NewInt(1), 20000)},
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ rawKey := struct {
+ P, Q, G, Y *big.Int
+ }{P: P, Q: Q, G: G, Y: tc.Y}
+
+ _, _, err := parseDSA(Marshal(&rawKey))
+ if err == nil {
+ t.Fatalf("parseDSA accepted a DSA key with Y=%s (P=%s)", tc.Y, P)
+ }
+ expectedError := "DSA public value Y out of range"
+ if !strings.Contains(err.Error(), expectedError) {
+ t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
+ }
+ })
+ }
+}
+
+func TestMarshalPrivateKey(t *testing.T) {
+ tests := []struct {
+ name string
+ }{
+ {"rsa-openssh-format"},
+ {"ed25519"},
+ {"p256-openssh-format"},
+ {"p384-openssh-format"},
+ {"p521-openssh-format"},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ expected, ok := testPrivateKeys[tt.name]
+ if !ok {
+ t.Fatalf("cannot find key %s", tt.name)
+ }
+
+ block, err := MarshalPrivateKey(expected, "test@golang.org")
+ if err != nil {
+ t.Fatalf("cannot marshal %s: %v", tt.name, err)
+ }
+
+ key, err := ParseRawPrivateKey(pem.EncodeToMemory(block))
+ if err != nil {
+ t.Fatalf("cannot parse %s: %v", tt.name, err)
+ }
+
+ if !reflect.DeepEqual(expected, key) {
+ t.Errorf("unexpected marshaled key %s", tt.name)
+ }
+ })
+ }
+}
+
+func TestMarshalPrivateKeyWithPassphrase(t *testing.T) {
+ tests := []struct {
+ name string
+ }{
+ {"rsa-openssh-format"},
+ {"ed25519"},
+ {"p256-openssh-format"},
+ {"p384-openssh-format"},
+ {"p521-openssh-format"},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ expected, ok := testPrivateKeys[tt.name]
+ if !ok {
+ t.Fatalf("cannot find key %s", tt.name)
+ }
+
+ block, err := MarshalPrivateKeyWithPassphrase(expected, "test@golang.org", []byte("test-passphrase"))
+ if err != nil {
+ t.Fatalf("cannot marshal %s: %v", tt.name, err)
+ }
+
+ key, err := ParseRawPrivateKeyWithPassphrase(pem.EncodeToMemory(block), []byte("test-passphrase"))
+ if err != nil {
+ t.Fatalf("cannot parse %s: %v", tt.name, err)
+ }
+
+ if !reflect.DeepEqual(expected, key) {
+ t.Errorf("unexpected marshaled key %s", tt.name)
+ }
+ })
+ }
+}
+
+type testAuthResult struct {
+ pubKey PublicKey
+ options []string
+ comments string
+ rest string
+ ok bool
+}
+
+func testAuthorizedKeys(t *testing.T, authKeys []byte, expected []testAuthResult) {
+ rest := authKeys
+ var values []testAuthResult
+ for len(rest) > 0 {
+ var r testAuthResult
+ var err error
+ r.pubKey, r.comments, r.options, rest, err = ParseAuthorizedKey(rest)
+ r.ok = (err == nil)
+ t.Log(err)
+ r.rest = string(rest)
+ values = append(values, r)
+ }
+
+ if !reflect.DeepEqual(values, expected) {
+ t.Errorf("got %#v, expected %#v", values, expected)
+ }
+}
+
+func TestAuthorizedKeyBasic(t *testing.T) {
+ pub, pubSerialized := getTestKey()
+ line := "ssh-rsa " + pubSerialized + " user@host"
+ testAuthorizedKeys(t, []byte(line),
+ []testAuthResult{
+ {pub, nil, "user@host", "", true},
+ })
+}
+
+func TestAuth(t *testing.T) {
+ pub, pubSerialized := getTestKey()
+ authWithOptions := []string{
+ `# comments to ignore before any keys...`,
+ ``,
+ `env="HOME=/home/root",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`,
+ `# comments to ignore, along with a blank line`,
+ ``,
+ `env="HOME=/home/root2" ssh-rsa ` + pubSerialized + ` user2@host2`,
+ ``,
+ `# more comments, plus a invalid entry`,
+ `ssh-rsa data-that-will-not-parse user@host3`,
+ }
+ for _, eol := range []string{"\n", "\r\n"} {
+ authOptions := strings.Join(authWithOptions, eol)
+ rest2 := strings.Join(authWithOptions[3:], eol)
+ rest3 := strings.Join(authWithOptions[6:], eol)
+ testAuthorizedKeys(t, []byte(authOptions), []testAuthResult{
+ {pub, []string{`env="HOME=/home/root"`, "no-port-forwarding"}, "user@host", rest2, true},
+ {pub, []string{`env="HOME=/home/root2"`}, "user2@host2", rest3, true},
+ {nil, nil, "", "", false},
+ })
+ }
+}
+
+func TestAuthWithQuotedSpaceInEnv(t *testing.T) {
+ pub, pubSerialized := getTestKey()
+ authWithQuotedSpaceInEnv := []byte(`env="HOME=/home/root dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
+ testAuthorizedKeys(t, []byte(authWithQuotedSpaceInEnv), []testAuthResult{
+ {pub, []string{`env="HOME=/home/root dir"`, "no-port-forwarding"}, "user@host", "", true},
+ })
+}
+
+func TestAuthWithQuotedCommaInEnv(t *testing.T) {
+ pub, pubSerialized := getTestKey()
+ authWithQuotedCommaInEnv := []byte(`env="HOME=/home/root,dir",no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host`)
+ testAuthorizedKeys(t, []byte(authWithQuotedCommaInEnv), []testAuthResult{
+ {pub, []string{`env="HOME=/home/root,dir"`, "no-port-forwarding"}, "user@host", "", true},
+ })
+}
+
+func TestAuthWithQuotedQuoteInEnv(t *testing.T) {
+ pub, pubSerialized := getTestKey()
+ authWithQuotedQuoteInEnv := []byte(`env="HOME=/home/\"root dir",no-port-forwarding` + "\t" + `ssh-rsa` + "\t" + pubSerialized + ` user@host`)
+ authWithDoubleQuotedQuote := []byte(`no-port-forwarding,env="HOME=/home/ \"root dir\"" ssh-rsa ` + pubSerialized + "\t" + `user@host`)
+ testAuthorizedKeys(t, []byte(authWithQuotedQuoteInEnv), []testAuthResult{
+ {pub, []string{`env="HOME=/home/\"root dir"`, "no-port-forwarding"}, "user@host", "", true},
+ })
+
+ testAuthorizedKeys(t, []byte(authWithDoubleQuotedQuote), []testAuthResult{
+ {pub, []string{"no-port-forwarding", `env="HOME=/home/ \"root dir\""`}, "user@host", "", true},
+ })
+}
+
+func TestAuthWithInvalidSpace(t *testing.T) {
+ _, pubSerialized := getTestKey()
+ authWithInvalidSpace := []byte(`env="HOME=/home/root dir", no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
+#more to follow but still no valid keys`)
+ testAuthorizedKeys(t, []byte(authWithInvalidSpace), []testAuthResult{
+ {nil, nil, "", "", false},
+ })
+}
+
+func TestAuthWithMissingQuote(t *testing.T) {
+ pub, pubSerialized := getTestKey()
+ authWithMissingQuote := []byte(`env="HOME=/home/root,no-port-forwarding ssh-rsa ` + pubSerialized + ` user@host
+env="HOME=/home/root",shared-control ssh-rsa ` + pubSerialized + ` user@host`)
+
+ testAuthorizedKeys(t, []byte(authWithMissingQuote), []testAuthResult{
+ {pub, []string{`env="HOME=/home/root"`, `shared-control`}, "user@host", "", true},
+ })
+}
+
+func TestInvalidEntry(t *testing.T) {
+ authInvalid := []byte(`ssh-rsa`)
+ _, _, _, _, err := ParseAuthorizedKey(authInvalid)
+ if err == nil {
+ t.Errorf("got valid entry for %q", authInvalid)
+ }
+}
+
+var knownHostsParseTests = []struct {
+ input string
+ err string
+
+ marker string
+ comment string
+ hosts []string
+ rest string
+}{
+ {
+ "",
+ "EOF",
+
+ "", "", nil, "",
+ },
+ {
+ "# Just a comment",
+ "EOF",
+
+ "", "", nil, "",
+ },
+ {
+ " \t ",
+ "EOF",
+
+ "", "", nil, "",
+ },
+ {
+ "localhost ssh-rsa {RSAPUB}",
+ "",
+
+ "", "", []string{"localhost"}, "",
+ },
+ {
+ "localhost\tssh-rsa {RSAPUB}",
+ "",
+
+ "", "", []string{"localhost"}, "",
+ },
+ {
+ "localhost\tssh-rsa {RSAPUB}\tcomment comment",
+ "",
+
+ "", "comment comment", []string{"localhost"}, "",
+ },
+ {
+ "localhost\tssh-rsa {RSAPUB}\tcomment comment\n",
+ "",
+
+ "", "comment comment", []string{"localhost"}, "",
+ },
+ {
+ "localhost\tssh-rsa {RSAPUB}\tcomment comment\r\n",
+ "",
+
+ "", "comment comment", []string{"localhost"}, "",
+ },
+ {
+ "localhost\tssh-rsa {RSAPUB}\tcomment comment\r\nnext line",
+ "",
+
+ "", "comment comment", []string{"localhost"}, "next line",
+ },
+ {
+ "localhost,[host2:123]\tssh-rsa {RSAPUB}\tcomment comment",
+ "",
+
+ "", "comment comment", []string{"localhost", "[host2:123]"}, "",
+ },
+ {
+ "@marker \tlocalhost,[host2:123]\tssh-rsa {RSAPUB}",
+ "",
+
+ "marker", "", []string{"localhost", "[host2:123]"}, "",
+ },
+ {
+ "@marker \tlocalhost,[host2:123]\tssh-rsa aabbccdd",
+ "short read",
+
+ "", "", nil, "",
+ },
+}
+
+func TestKnownHostsParsing(t *testing.T) {
+ rsaPub, rsaPubSerialized := getTestKey()
+
+ for i, test := range knownHostsParseTests {
+ var expectedKey PublicKey
+ const rsaKeyToken = "{RSAPUB}"
+
+ input := test.input
+ if strings.Contains(input, rsaKeyToken) {
+ expectedKey = rsaPub
+ input = strings.Replace(test.input, rsaKeyToken, rsaPubSerialized, -1)
+ }
+
+ marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input))
+ if err != nil {
+ if len(test.err) == 0 {
+ t.Errorf("#%d: unexpectedly failed with %q", i, err)
+ } else if !strings.Contains(err.Error(), test.err) {
+ t.Errorf("#%d: expected error containing %q, but got %q", i, test.err, err)
+ }
+ continue
+ } else if len(test.err) != 0 {
+ t.Errorf("#%d: succeeded but expected error including %q", i, test.err)
+ continue
+ }
+
+ if !reflect.DeepEqual(expectedKey, pubKey) {
+ t.Errorf("#%d: expected key %#v, but got %#v", i, expectedKey, pubKey)
+ }
+
+ if marker != test.marker {
+ t.Errorf("#%d: expected marker %q, but got %q", i, test.marker, marker)
+ }
+
+ if comment != test.comment {
+ t.Errorf("#%d: expected comment %q, but got %q", i, test.comment, comment)
+ }
+
+ if !reflect.DeepEqual(test.hosts, hosts) {
+ t.Errorf("#%d: expected hosts %#v, but got %#v", i, test.hosts, hosts)
+ }
+
+ if rest := string(rest); rest != test.rest {
+ t.Errorf("#%d: expected remaining input to be %q, but got %q", i, test.rest, rest)
+ }
+ }
+}
+
+func TestFingerprintLegacyMD5(t *testing.T) {
+ pub, _ := getTestKey()
+ fingerprint := FingerprintLegacyMD5(pub)
+ want := "b7:ef:d3:d5:89:29:52:96:9f:df:47:41:4d:15:37:f4" // ssh-keygen -lf -E md5 rsa
+ if fingerprint != want {
+ t.Errorf("got fingerprint %q want %q", fingerprint, want)
+ }
+}
+
+func TestFingerprintSHA256(t *testing.T) {
+ pub, _ := getTestKey()
+ fingerprint := FingerprintSHA256(pub)
+ want := "SHA256:fi5+D7UmDZDE9Q2sAVvvlpcQSIakN4DERdINgXd2AnE" // ssh-keygen -lf rsa
+ if fingerprint != want {
+ t.Errorf("got fingerprint %q want %q", fingerprint, want)
+ }
+}
+
+func TestInvalidKeys(t *testing.T) {
+ keyTypes := []string{
+ "RSA PRIVATE KEY",
+ "PRIVATE KEY",
+ "EC PRIVATE KEY",
+ "DSA PRIVATE KEY",
+ "OPENSSH PRIVATE KEY",
+ }
+
+ for _, keyType := range keyTypes {
+ for _, dataLen := range []int{0, 1, 2, 5, 10, 20} {
+ data := make([]byte, dataLen)
+ if _, err := io.ReadFull(rand.Reader, data); err != nil {
+ t.Fatal(err)
+ }
+
+ var buf bytes.Buffer
+ pem.Encode(&buf, &pem.Block{
+ Type: keyType,
+ Bytes: data,
+ })
+
+ // This test is just to ensure that the function
+ // doesn't panic so the return value is ignored.
+ ParseRawPrivateKey(buf.Bytes())
+ }
+ }
+}
+
+func TestSKKeys(t *testing.T) {
+ for _, d := range testdata.SKData {
+ pk, _, _, _, err := ParseAuthorizedKey(d.PubKey)
+ if err != nil {
+ t.Fatalf("parseAuthorizedKey returned error: %v", err)
+ }
+
+ sigBuf := make([]byte, hex.DecodedLen(len(d.HexSignature)))
+ if _, err := hex.Decode(sigBuf, d.HexSignature); err != nil {
+ t.Fatalf("hex.Decode() failed: %v", err)
+ }
+
+ dataBuf := make([]byte, hex.DecodedLen(len(d.HexData)))
+ if _, err := hex.Decode(dataBuf, d.HexData); err != nil {
+ t.Fatalf("hex.Decode() failed: %v", err)
+ }
+
+ sig, _, ok := parseSignature(sigBuf)
+ if !ok {
+ t.Fatalf("parseSignature(%v) failed", sigBuf)
+ }
+
+ // Test that good data and signature pass verification
+ if err := pk.Verify(dataBuf, sig); err != nil {
+ t.Errorf("%s: PublicKey.Verify(%v, %v) failed: %v", d.Name, dataBuf, sig, err)
+ }
+
+ // Invalid data being passed in
+ invalidData := []byte("INVALID DATA")
+ if err := pk.Verify(invalidData, sig); err == nil {
+ t.Errorf("%s with invalid data: PublicKey.Verify(%v, %v) passed unexpectedly", d.Name, invalidData, sig)
+ }
+
+ // Change byte in blob to corrup signature
+ sig.Blob[5] = byte('A')
+ // Corrupted data being passed in
+ if err := pk.Verify(dataBuf, sig); err == nil {
+ t.Errorf("%s with corrupted signature: PublicKey.Verify(%v, %v) passed unexpectedly", d.Name, dataBuf, sig)
+ }
+ }
+}
+
+// skTestHarness builds SK-formatted signatures over a fixed payload
+// using a caller-supplied signing function, letting tests vary the UP
+// flag byte without duplicating the wire-format scaffolding.
+type skTestHarness struct {
+ format string
+ application string
+ data []byte
+ // sign takes the SHA-256 digest of the marshalled SK blob and
+ // returns the value to embed in Signature.Blob. For ECDSA keys the
+ // helper feeds the digest to ecdsa.Sign; for ed25519 the helper
+ // feeds the raw marshalled blob to ed25519.Sign.
+ signDigest func(digest []byte) []byte
+ signBlob func(blob []byte) []byte
+}
+
+func (h skTestHarness) sign(t *testing.T, flags byte) *Signature {
+ t.Helper()
+ hsh := sha256.New()
+ hsh.Write([]byte(h.application))
+ appDigest := hsh.Sum(nil)
+
+ hsh.Reset()
+ hsh.Write(h.data)
+ dataDigest := hsh.Sum(nil)
+
+ var counter uint32 = 1
+ blob := struct {
+ ApplicationDigest []byte `ssh:"rest"`
+ Flags byte
+ Counter uint32
+ MessageDigest []byte `ssh:"rest"`
+ }{appDigest, flags, counter, dataDigest}
+ marshalled := Marshal(blob)
+
+ var sigBlob []byte
+ if h.signDigest != nil {
+ hsh.Reset()
+ hsh.Write(marshalled)
+ sigBlob = h.signDigest(hsh.Sum(nil))
+ } else {
+ sigBlob = h.signBlob(marshalled)
+ }
+
+ return &Signature{
+ Format: h.format,
+ Blob: sigBlob,
+ Rest: Marshal(struct {
+ Flags byte
+ Counter uint32
+ }{flags, counter}),
+ }
+}
+
+func TestSKUserPresence(t *testing.T) {
+ ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ h := skTestHarness{
+ format: "sk-ecdsa-sha2-nistp256@openssh.com",
+ application: "ssh:",
+ data: []byte("test data"),
+ signDigest: func(digest []byte) []byte {
+ r, s, err := ecdsa.Sign(rand.Reader, ecKey, digest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return Marshal(struct{ R, S *big.Int }{r, s})
+ },
+ }
+
+ pk := &skECDSAPublicKey{
+ application: h.application,
+ PublicKey: ecKey.PublicKey,
+ }
+
+ // Valid signature with UP=1 should pass.
+ if err := pk.Verify(h.data, h.sign(t, flagUserPresence)); err != nil {
+ t.Errorf("Verify failed with UP=1: %v", err)
+ }
+
+ // Valid signature with UP=0 should fail with the user-presence sentinel.
+ sigNoUP := h.sign(t, 0)
+ if err := pk.Verify(h.data, sigNoUP); !errors.Is(err, errSKMissingUserPresence) {
+ t.Errorf("expected errSKMissingUserPresence, got: %v", err)
+ }
+
+ // UV set but UP clear must still fail: we only waive UP, never UV-only.
+ if err := pk.Verify(h.data, h.sign(t, 0x04)); !errors.Is(err, errSKMissingUserPresence) {
+ t.Errorf("UV-only (flags=0x04): expected errSKMissingUserPresence, got: %v", err)
+ }
+
+ // With noTouchRequired, UP=0 passes; UP=1+UV=1 also passes.
+ pk.noTouchRequired = true
+ if err := pk.Verify(h.data, sigNoUP); err != nil {
+ t.Errorf("Verify with noTouchRequired failed: %v", err)
+ }
+ if err := pk.Verify(h.data, h.sign(t, flagUserPresence|0x04)); err != nil {
+ t.Errorf("Verify UP|UV with noTouchRequired failed: %v", err)
+ }
+}
+
+func TestSKKeyWithoutUP(t *testing.T) {
+ ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ edPub, _, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ skEC := &skECDSAPublicKey{PublicKey: ecKey.PublicKey}
+ skED := &skEd25519PublicKey{PublicKey: edPub}
+ certEC := &Certificate{Key: &skECDSAPublicKey{PublicKey: ecKey.PublicKey}}
+ certED := &Certificate{Key: &skEd25519PublicKey{PublicKey: edPub}}
+ rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rsaPub, err := NewPublicKey(&rsaKey.PublicKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+ certRSA := &Certificate{Key: rsaPub}
+
+ // SK raw keys: return a clone with the flag set, original untouched.
+ gotEC := skKeyWithoutUP(skEC)
+ if gotEC == skEC {
+ t.Error("skECDSAPublicKey: expected a clone, got the original pointer")
+ }
+ if !gotEC.(*skECDSAPublicKey).noTouchRequired {
+ t.Error("skECDSAPublicKey clone: noTouchRequired not set")
+ }
+ if skEC.noTouchRequired {
+ t.Error("skECDSAPublicKey: original mutated")
+ }
+
+ gotED := skKeyWithoutUP(skED)
+ if gotED == skED {
+ t.Error("skEd25519PublicKey: expected a clone, got the original pointer")
+ }
+ if !gotED.(*skEd25519PublicKey).noTouchRequired {
+ t.Error("skEd25519PublicKey clone: noTouchRequired not set")
+ }
+ if skED.noTouchRequired {
+ t.Error("skEd25519PublicKey: original mutated")
+ }
+
+ // Certificate wrapping SK: return a clone of the cert with a cloned
+ // SK key inside. Neither the original cert nor the original inner
+ // key must be mutated.
+ originalInnerEC := certEC.Key
+ gotCertEC := skKeyWithoutUP(certEC)
+ if gotCertEC == certEC {
+ t.Error("*Certificate(SK ecdsa): expected a clone, got the original pointer")
+ }
+ if got := gotCertEC.(*Certificate).Key.(*skECDSAPublicKey); !got.noTouchRequired {
+ t.Error("*Certificate(SK ecdsa): inner clone missing noTouchRequired")
+ }
+ if certEC.Key != originalInnerEC {
+ t.Error("*Certificate(SK ecdsa): original cert's Key pointer mutated")
+ }
+ if originalInnerEC.(*skECDSAPublicKey).noTouchRequired {
+ t.Error("*Certificate(SK ecdsa): original inner key mutated")
+ }
+
+ gotCertED := skKeyWithoutUP(certED)
+ if gotCertED == certED {
+ t.Error("*Certificate(SK ed25519): expected a clone, got the original pointer")
+ }
+ if got := gotCertED.(*Certificate).Key.(*skEd25519PublicKey); !got.noTouchRequired {
+ t.Error("*Certificate(SK ed25519): inner clone missing noTouchRequired")
+ }
+
+ // Non-SK key inside a cert: return original unchanged (nothing to clone).
+ if got := skKeyWithoutUP(certRSA); got != certRSA {
+ t.Error("*Certificate(RSA): expected the original pointer back")
+ }
+
+ // Plain non-SK key: return original unchanged.
+ if got := skKeyWithoutUP(rsaPub); got != rsaPub {
+ t.Error("rsaPublicKey: expected the original pointer back")
+ }
+
+ // Pathological: *Certificate whose Key is itself a *Certificate.
+ // The SSH cert format forbids this and parseCert rejects it, but
+ // a Go caller can still construct such a value. skKeyWithoutUP
+ // must not recurse into it (or panic); it returns the input
+ // unchanged. This also defends against a hypothetical cycle built
+ // from hand-constructed Certificate pointers.
+ nestedCert := &Certificate{Key: &Certificate{Key: skEC}}
+ if got := skKeyWithoutUP(nestedCert); got != nestedCert {
+ t.Error("*Certificate wrapping *Certificate: expected the original pointer back")
+ }
+ selfCycle := &Certificate{}
+ selfCycle.Key = selfCycle
+ if got := skKeyWithoutUP(selfCycle); got != selfCycle {
+ t.Error("self-referential *Certificate: expected the original pointer back")
+ }
+}
+
+func TestNoTouchAllowed(t *testing.T) {
+ ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sk := &skECDSAPublicKey{PublicKey: ecKey.PublicKey}
+ certNoExt := &Certificate{Key: sk}
+ certOptOut := &Certificate{
+ Key: sk,
+ Permissions: Permissions{Extensions: map[string]string{"no-touch-required": ""}},
+ }
+ // Non-empty value: OpenSSH writes "" but any value must be accepted
+ // because the check is presence-only.
+ certOptOutNonEmpty := &Certificate{
+ Key: sk,
+ Permissions: Permissions{Extensions: map[string]string{"no-touch-required": "yes"}},
+ }
+ // no-touch-required belongs in Extensions, never CriticalOptions.
+ // Putting it in CriticalOptions must NOT be treated as opt-out.
+ certCritOnly := &Certificate{
+ Key: sk,
+ Permissions: Permissions{CriticalOptions: map[string]string{"no-touch-required": ""}},
+ }
+
+ permsEmpty := &Permissions{}
+ permsOptOut := &Permissions{Extensions: map[string]string{"no-touch-required": ""}}
+ permsCritOnly := &Permissions{CriticalOptions: map[string]string{"no-touch-required": ""}}
+
+ cases := []struct {
+ name string
+ pub PublicKey
+ perms *Permissions
+ want bool
+ }{
+ {"nil perms, no cert", sk, nil, false},
+ {"empty perms, no cert", sk, permsEmpty, false},
+ {"perms opt-out, raw key", sk, permsOptOut, true},
+ {"nil perms, cert opt-out", certOptOut, nil, true},
+ {"nil perms, cert opt-out non-empty value", certOptOutNonEmpty, nil, true},
+ {"nil perms, cert no ext", certNoExt, nil, false},
+ {"perms opt-out, cert no ext", certNoExt, permsOptOut, true},
+ {"empty perms, cert opt-out", certOptOut, permsEmpty, true},
+ // Negative controls: CriticalOptions must not waive UP.
+ {"critical-options only, raw key", sk, permsCritOnly, false},
+ {"critical-options only, cert", certCritOnly, nil, false},
+ }
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ if got := noTouchAllowed(tc.pub, tc.perms); got != tc.want {
+ t.Errorf("noTouchAllowed = %v, want %v", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestSKUserPresenceEd25519(t *testing.T) {
+ pub, priv, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ h := skTestHarness{
+ format: "sk-ssh-ed25519@openssh.com",
+ application: "ssh:",
+ data: []byte("test data"),
+ signBlob: func(blob []byte) []byte {
+ return ed25519.Sign(priv, blob)
+ },
+ }
+
+ pk := &skEd25519PublicKey{
+ application: h.application,
+ PublicKey: pub,
+ }
+
+ if err := pk.Verify(h.data, h.sign(t, flagUserPresence)); err != nil {
+ t.Errorf("Verify failed with UP=1: %v", err)
+ }
+ sigNoUP := h.sign(t, 0)
+ if err := pk.Verify(h.data, sigNoUP); !errors.Is(err, errSKMissingUserPresence) {
+ t.Errorf("expected errSKMissingUserPresence, got: %v", err)
+ }
+ pk.noTouchRequired = true
+ if err := pk.Verify(h.data, sigNoUP); err != nil {
+ t.Errorf("Verify with noTouchRequired failed: %v", err)
+ }
+}
+
+func TestNewSignerWithAlgos(t *testing.T) {
+ algorithSigner, ok := testSigners["rsa"].(AlgorithmSigner)
+ if !ok {
+ t.Fatal("rsa test signer does not implement the AlgorithmSigner interface")
+ }
+ _, err := NewSignerWithAlgorithms(algorithSigner, nil)
+ if err == nil {
+ t.Error("signer with algos created with no algorithms")
+ }
+
+ _, err = NewSignerWithAlgorithms(algorithSigner, []string{KeyAlgoED25519})
+ if err == nil {
+ t.Error("signer with algos created with invalid algorithms")
+ }
+
+ _, err = NewSignerWithAlgorithms(algorithSigner, []string{CertAlgoRSASHA256v01})
+ if err == nil {
+ t.Error("signer with algos created with certificate algorithms")
+ }
+
+ mas, err := NewSignerWithAlgorithms(algorithSigner, []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512})
+ if err != nil {
+ t.Errorf("unable to create signer with valid algorithms: %v", err)
+ }
+
+ _, err = NewSignerWithAlgorithms(mas, []string{KeyAlgoRSA})
+ if err == nil {
+ t.Error("signer with algos created with restricted algorithms")
+ }
+}
+
+func TestCryptoPublicKey(t *testing.T) {
+ for _, priv := range testSigners {
+ p1 := priv.PublicKey()
+ key, ok := p1.(CryptoPublicKey)
+ if !ok {
+ continue
+ }
+ p2, err := NewPublicKey(key.CryptoPublicKey())
+ if err != nil {
+ t.Fatalf("NewPublicKey(CryptoPublicKey) failed for %s, got: %v", p1.Type(), err)
+ }
+ if !reflect.DeepEqual(p1, p2) {
+ t.Errorf("got %#v in NewPublicKey, want %#v", p2, p1)
+ }
+ }
+ for _, d := range testdata.SKData {
+ p1, _, _, _, err := ParseAuthorizedKey(d.PubKey)
+ if err != nil {
+ t.Fatalf("parseAuthorizedKey returned error: %v", err)
+ }
+ k1, ok := p1.(CryptoPublicKey)
+ if !ok {
+ t.Fatalf("%T does not implement CryptoPublicKey", p1)
+ }
+
+ var p2 PublicKey
+ switch pub := k1.CryptoPublicKey().(type) {
+ case *ecdsa.PublicKey:
+ p2 = &skECDSAPublicKey{
+ application: "ssh:",
+ PublicKey: *pub,
+ }
+ case ed25519.PublicKey:
+ p2 = &skEd25519PublicKey{
+ application: "ssh:",
+ PublicKey: pub,
+ }
+ default:
+ t.Fatalf("unexpected type %T from CryptoPublicKey()", pub)
+ }
+ if !reflect.DeepEqual(p1, p2) {
+ t.Errorf("got %#v, want %#v", p2, p1)
+ }
+ }
+}
+
+func TestParseCertWithCertSignatureKey(t *testing.T) {
+ certBytes := []byte(`-----BEGIN SSH CERTIFICATE-----
+AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIPSp27hvNSB0
+IotJnVhjC4zxNgNS8BHlUCxD0VJi4D/eAAAAIIJMi1e5qfx+IFuKD/p/Ssqcb3os
+CpOw/4wBs1pQ53zwAAAAAAAAAAEAAAACAAAAAAAAABMAAAAPZm9vLmV4YW1wbGUu
+Y29tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT0AAAAgc3NoLWVkMjU1
+MTktY2VydC12MDFAb3BlbnNzaC5jb20AAAAg+sNYhCO35mQT1UBMpmMk8ey+culd
+IU8vBlPEl4B07swAAAAggiv+RLnboS4znGCVl/n1jDg2uD0h15tW4s/04eS2mLQA
+AAAAAAAAAQAAAAIAAAAAAAAAEwAAAA9mb28uZXhhbXBsZS5jb20AAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCV2wETgLKL
+Kt0bRl3YUnd/ZYSlq0xJMbn4Jj3cdPWykQAAAFMAAAALc3NoLWVkMjU1MTkAAABA
+WOdbRGEzyRAhiIK227CLUQD5caXYMV8FvSIB7toEE2M/8HnWdG9H3Rsg/v3unruQ
+JrQldnuPJNe7KOP2+zvUDgAAAFMAAAALc3NoLWVkMjU1MTkAAABAm3bIPp85ZpIe
+D+izJcUqlcAOri7HO8bULFNHT6LVegvB06xQ5TLwMlrxWUF4cafl1tSe8JQck4a6
+cLYUOHfQDw==
+-----END SSH CERTIFICATE-----
+ `)
+ block, _ := pem.Decode(certBytes)
+ if block == nil {
+ t.Fatal("invalid test certificate")
+ }
+
+ if _, err := ParsePublicKey(block.Bytes); err == nil {
+ t.Fatal("parsing an SSH certificate using another certificate as signature key succeeded; expected failure")
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/knownhosts/knownhosts.go b/local_crypto_patch/contents/ssh/knownhosts/knownhosts.go
new file mode 100644
index 0000000000..e57cf5b471
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/knownhosts/knownhosts.go
@@ -0,0 +1,537 @@
+// 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 knownhosts implements a parser for the OpenSSH known_hosts
+// host key database, and provides utility functions for writing
+// OpenSSH compliant known_hosts files.
+package knownhosts
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/hmac"
+ "crypto/rand"
+ "crypto/sha1"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "strings"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// See the sshd manpage
+// (http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) for
+// background.
+
+type addr struct{ host, port string }
+
+func (a *addr) String() string {
+ h := a.host
+ if strings.Contains(h, ":") {
+ h = "[" + h + "]"
+ }
+ return h + ":" + a.port
+}
+
+type matcher interface {
+ match(addr) bool
+}
+
+type hostPattern struct {
+ negate bool
+ addr addr
+}
+
+func (p *hostPattern) String() string {
+ n := ""
+ if p.negate {
+ n = "!"
+ }
+
+ return n + p.addr.String()
+}
+
+type hostPatterns []hostPattern
+
+func (ps hostPatterns) match(a addr) bool {
+ matched := false
+ for _, p := range ps {
+ if !p.match(a) {
+ continue
+ }
+ if p.negate {
+ return false
+ }
+ matched = true
+ }
+ return matched
+}
+
+// See
+// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/addrmatch.c
+// The matching of * has no regard for separators, unlike filesystem globs
+func wildcardMatch(pat []byte, str []byte) bool {
+ for {
+ if len(pat) == 0 {
+ return len(str) == 0
+ }
+ if len(str) == 0 {
+ return false
+ }
+
+ if pat[0] == '*' {
+ if len(pat) == 1 {
+ return true
+ }
+
+ for j := range str {
+ if wildcardMatch(pat[1:], str[j:]) {
+ return true
+ }
+ }
+ return false
+ }
+
+ if pat[0] == '?' || pat[0] == str[0] {
+ pat = pat[1:]
+ str = str[1:]
+ } else {
+ return false
+ }
+ }
+}
+
+func (p *hostPattern) match(a addr) bool {
+ return wildcardMatch([]byte(p.addr.host), []byte(a.host)) && p.addr.port == a.port
+}
+
+type keyDBLine struct {
+ cert bool
+ matcher matcher
+ knownKey KnownKey
+}
+
+func serialize(k ssh.PublicKey) string {
+ return k.Type() + " " + base64.StdEncoding.EncodeToString(k.Marshal())
+}
+
+func (l *keyDBLine) match(a addr) bool {
+ return l.matcher.match(a)
+}
+
+type hostKeyDB struct {
+ // Serialized version of revoked keys
+ revoked map[string]*KnownKey
+ lines []keyDBLine
+}
+
+func newHostKeyDB() *hostKeyDB {
+ db := &hostKeyDB{
+ revoked: make(map[string]*KnownKey),
+ }
+
+ return db
+}
+
+func keyEq(a, b ssh.PublicKey) bool {
+ return bytes.Equal(a.Marshal(), b.Marshal())
+}
+
+// IsHostAuthority can be used as a callback in ssh.CertChecker
+func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
+ h, p, err := net.SplitHostPort(address)
+ if err != nil {
+ return false
+ }
+ a := addr{host: h, port: p}
+
+ for _, l := range db.lines {
+ if l.cert && keyEq(l.knownKey.Key, remote) && l.match(a) {
+ return true
+ }
+ }
+ return false
+}
+
+// IsRevoked can be used as a callback in ssh.CertChecker
+func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
+ if _, ok := db.revoked[string(key.Marshal())]; ok {
+ return true
+ }
+ if _, ok := db.revoked[string(key.SignatureKey.Marshal())]; ok {
+ return true
+ }
+ return false
+}
+
+const markerCert = "@cert-authority"
+const markerRevoked = "@revoked"
+
+func nextWord(line []byte) (string, []byte) {
+ i := bytes.IndexAny(line, "\t ")
+ if i == -1 {
+ return string(line), nil
+ }
+
+ return string(line[:i]), bytes.TrimSpace(line[i:])
+}
+
+func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
+ if w, next := nextWord(line); w == markerCert || w == markerRevoked {
+ marker = w
+ line = next
+ }
+
+ host, line = nextWord(line)
+ if len(line) == 0 {
+ return "", "", nil, errors.New("knownhosts: missing host pattern")
+ }
+
+ // ignore the keytype as it's in the key blob anyway.
+ _, line = nextWord(line)
+ if len(line) == 0 {
+ return "", "", nil, errors.New("knownhosts: missing key type pattern")
+ }
+
+ keyBlob, _ := nextWord(line)
+
+ keyBytes, err := base64.StdEncoding.DecodeString(keyBlob)
+ if err != nil {
+ return "", "", nil, err
+ }
+ key, err = ssh.ParsePublicKey(keyBytes)
+ if err != nil {
+ return "", "", nil, err
+ }
+
+ return marker, host, key, nil
+}
+
+func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error {
+ marker, pattern, key, err := parseLine(line)
+ if err != nil {
+ return err
+ }
+
+ if marker == markerRevoked {
+ db.revoked[string(key.Marshal())] = &KnownKey{
+ Key: key,
+ Filename: filename,
+ Line: linenum,
+ }
+
+ return nil
+ }
+
+ entry := keyDBLine{
+ cert: marker == markerCert,
+ knownKey: KnownKey{
+ Filename: filename,
+ Line: linenum,
+ Key: key,
+ },
+ }
+
+ if pattern[0] == '|' {
+ entry.matcher, err = newHashedHost(pattern)
+ } else {
+ entry.matcher, err = newHostnameMatcher(pattern)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ db.lines = append(db.lines, entry)
+ return nil
+}
+
+func newHostnameMatcher(pattern string) (matcher, error) {
+ var hps hostPatterns
+ for _, p := range strings.Split(pattern, ",") {
+ if len(p) == 0 {
+ continue
+ }
+
+ var a addr
+ var negate bool
+ if p[0] == '!' {
+ negate = true
+ p = p[1:]
+ }
+
+ if len(p) == 0 {
+ return nil, errors.New("knownhosts: negation without following hostname")
+ }
+
+ var err error
+ if p[0] == '[' {
+ a.host, a.port, err = net.SplitHostPort(p)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ a.host, a.port, err = net.SplitHostPort(p)
+ if err != nil {
+ a.host = p
+ a.port = "22"
+ }
+ }
+ hps = append(hps, hostPattern{
+ negate: negate,
+ addr: a,
+ })
+ }
+ return hps, nil
+}
+
+// KnownKey represents a key declared in a known_hosts file.
+type KnownKey struct {
+ Key ssh.PublicKey
+ Filename string
+ Line int
+}
+
+func (k *KnownKey) String() string {
+ return fmt.Sprintf("%s:%d: %s", k.Filename, k.Line, serialize(k.Key))
+}
+
+// KeyError is returned if we did not find the key in the host key
+// database, or there was a mismatch. Typically, in batch
+// applications, this should be interpreted as failure. Interactive
+// applications can offer an interactive prompt to the user.
+type KeyError struct {
+ // Want holds the accepted host keys. For each key algorithm,
+ // there can be multiple hostkeys. If Want is empty, the host
+ // is unknown. If Want is non-empty, there was a mismatch, which
+ // can signify a MITM attack.
+ Want []KnownKey
+}
+
+func (u *KeyError) Error() string {
+ if len(u.Want) == 0 {
+ return "knownhosts: key is unknown"
+ }
+ return "knownhosts: key mismatch"
+}
+
+// RevokedError is returned if we found a key that was revoked.
+type RevokedError struct {
+ Revoked KnownKey
+}
+
+func (r *RevokedError) Error() string {
+ return "knownhosts: key is revoked"
+}
+
+// check checks a key against the host database. This should not be
+// used for verifying certificates.
+func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
+ if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
+ return &RevokedError{Revoked: *revoked}
+ }
+
+ host, port, err := net.SplitHostPort(remote.String())
+ if err != nil {
+ return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", remote, err)
+ }
+
+ hostToCheck := addr{host, port}
+ if address != "" {
+ // Give preference to the hostname if available.
+ host, port, err := net.SplitHostPort(address)
+ if err != nil {
+ return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", address, err)
+ }
+
+ hostToCheck = addr{host, port}
+ }
+
+ return db.checkAddr(hostToCheck, remoteKey)
+}
+
+// checkAddr checks if we can find the given public key for the
+// given address. If we only find an entry for the IP address,
+// or only the hostname, then this still succeeds.
+func (db *hostKeyDB) checkAddr(a addr, remoteKey ssh.PublicKey) error {
+ // TODO(hanwen): are these the right semantics? What if there
+ // is just a key for the IP address, but not for the
+ // hostname?
+
+ keyErr := &KeyError{}
+
+ for _, l := range db.lines {
+ if !l.match(a) {
+ continue
+ }
+
+ keyErr.Want = append(keyErr.Want, l.knownKey)
+ if keyEq(l.knownKey.Key, remoteKey) {
+ return nil
+ }
+ }
+
+ return keyErr
+}
+
+// The Read function parses file contents.
+func (db *hostKeyDB) Read(r io.Reader, filename string) error {
+ scanner := bufio.NewScanner(r)
+
+ lineNum := 0
+ for scanner.Scan() {
+ lineNum++
+ line := scanner.Bytes()
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 || line[0] == '#' {
+ continue
+ }
+
+ if err := db.parseLine(line, filename, lineNum); err != nil {
+ return fmt.Errorf("knownhosts: %s:%d: %v", filename, lineNum, err)
+ }
+ }
+ return scanner.Err()
+}
+
+// New creates a host key callback from the given OpenSSH host key
+// files. The returned callback is for use in
+// ssh.ClientConfig.HostKeyCallback. By preference, the key check
+// operates on the hostname if available, i.e. if a server changes its
+// IP address, the host key check will still succeed, even though a
+// record of the new IP address is not available.
+func New(files ...string) (ssh.HostKeyCallback, error) {
+ db := newHostKeyDB()
+ for _, fn := range files {
+ f, err := os.Open(fn)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ if err := db.Read(f, fn); err != nil {
+ return nil, err
+ }
+ }
+
+ var certChecker ssh.CertChecker
+ certChecker.IsHostAuthority = db.IsHostAuthority
+ certChecker.IsRevoked = db.IsRevoked
+ certChecker.HostKeyFallback = db.check
+
+ return certChecker.CheckHostKey, nil
+}
+
+// Normalize normalizes an address into the form used in known_hosts. Supports
+// IPv4, hostnames, bracketed IPv6. Any other non-standard formats are returned
+// with minimal transformation.
+func Normalize(address string) string {
+ const defaultSSHPort = "22"
+
+ host, port, err := net.SplitHostPort(address)
+ if err != nil {
+ host = address
+ port = defaultSSHPort
+ }
+
+ if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
+ host = host[1 : len(host)-1]
+ }
+
+ if port == defaultSSHPort {
+ return host
+ }
+ return "[" + host + "]:" + port
+}
+
+// Line returns a line to add append to the known_hosts files.
+func Line(addresses []string, key ssh.PublicKey) string {
+ var trimmed []string
+ for _, a := range addresses {
+ trimmed = append(trimmed, Normalize(a))
+ }
+
+ return strings.Join(trimmed, ",") + " " + serialize(key)
+}
+
+// HashHostname hashes the given hostname. The hostname is not
+// normalized before hashing.
+func HashHostname(hostname string) string {
+ // TODO(hanwen): check if we can safely normalize this always.
+ salt := make([]byte, sha1.Size)
+
+ _, err := rand.Read(salt)
+ if err != nil {
+ panic(fmt.Sprintf("crypto/rand failure %v", err))
+ }
+
+ hash := hashHost(hostname, salt)
+ return encodeHash(sha1HashType, salt, hash)
+}
+
+func decodeHash(encoded string) (hashType string, salt, hash []byte, err error) {
+ if len(encoded) == 0 || encoded[0] != '|' {
+ err = errors.New("knownhosts: hashed host must start with '|'")
+ return
+ }
+ components := strings.Split(encoded, "|")
+ if len(components) != 4 {
+ err = fmt.Errorf("knownhosts: got %d components, want 3", len(components))
+ return
+ }
+
+ hashType = components[1]
+ if salt, err = base64.StdEncoding.DecodeString(components[2]); err != nil {
+ return
+ }
+ if hash, err = base64.StdEncoding.DecodeString(components[3]); err != nil {
+ return
+ }
+ return
+}
+
+func encodeHash(typ string, salt []byte, hash []byte) string {
+ return strings.Join([]string{"",
+ typ,
+ base64.StdEncoding.EncodeToString(salt),
+ base64.StdEncoding.EncodeToString(hash),
+ }, "|")
+}
+
+// See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
+func hashHost(hostname string, salt []byte) []byte {
+ mac := hmac.New(sha1.New, salt)
+ mac.Write([]byte(hostname))
+ return mac.Sum(nil)
+}
+
+type hashedHost struct {
+ salt []byte
+ hash []byte
+}
+
+const sha1HashType = "1"
+
+func newHashedHost(encoded string) (*hashedHost, error) {
+ typ, salt, hash, err := decodeHash(encoded)
+ if err != nil {
+ return nil, err
+ }
+
+ // The type field seems for future algorithm agility, but it's
+ // actually hardcoded in openssh currently, see
+ // https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
+ if typ != sha1HashType {
+ return nil, fmt.Errorf("knownhosts: got hash type %s, must be '1'", typ)
+ }
+
+ return &hashedHost{salt: salt, hash: hash}, nil
+}
+
+func (h *hashedHost) match(a addr) bool {
+ return bytes.Equal(hashHost(Normalize(a.String()), h.salt), h.hash)
+}
diff --git a/local_crypto_patch/contents/ssh/knownhosts/knownhosts_test.go b/local_crypto_patch/contents/ssh/knownhosts/knownhosts_test.go
new file mode 100644
index 0000000000..4d3ca59e92
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/knownhosts/knownhosts_test.go
@@ -0,0 +1,409 @@
+// 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 knownhosts
+
+import (
+ "bytes"
+ "crypto/ed25519"
+ "crypto/rand"
+ "fmt"
+ "net"
+ "reflect"
+ "testing"
+
+ "golang.org/x/crypto/ssh"
+)
+
+const edKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGBAarftlLeoyf+v+nVchEZII/vna2PCV8FaX4vsF5BX"
+const alternateEdKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIXffBYeYL+WVzVru8npl5JHt2cjlr4ornFTWzoij9sx"
+const ecKeyStr = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNLCu01+wpXe3xB5olXCN4SqU2rQu0qjSRKJO4Bg+JRCPU+ENcgdA5srTU8xYDz/GEa4dzK5ldPw4J/gZgSXCMs="
+
+var ecKey, alternateEdKey, edKey ssh.PublicKey
+var testAddr = &net.TCPAddr{
+ IP: net.IP{198, 41, 30, 196},
+ Port: 22,
+}
+
+var testAddr6 = &net.TCPAddr{
+ IP: net.IP{198, 41, 30, 196,
+ 1, 2, 3, 4,
+ 1, 2, 3, 4,
+ 1, 2, 3, 4,
+ },
+ Port: 22,
+}
+
+func init() {
+ var err error
+ ecKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(ecKeyStr))
+ if err != nil {
+ panic(err)
+ }
+ edKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(edKeyStr))
+ if err != nil {
+ panic(err)
+ }
+ alternateEdKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(alternateEdKeyStr))
+ if err != nil {
+ panic(err)
+ }
+}
+
+func testDB(t *testing.T, s string) *hostKeyDB {
+ db := newHostKeyDB()
+ if err := db.Read(bytes.NewBufferString(s), "testdb"); err != nil {
+ t.Fatalf("Read: %v", err)
+ }
+
+ return db
+}
+
+func TestRevoked(t *testing.T) {
+ db := testDB(t, "\n\n@revoked * "+edKeyStr+"\n")
+ want := &RevokedError{
+ Revoked: KnownKey{
+ Key: edKey,
+ Filename: "testdb",
+ Line: 3,
+ },
+ }
+ if err := db.check("", &net.TCPAddr{
+ Port: 42,
+ }, edKey); err == nil {
+ t.Fatal("no error for revoked key")
+ } else if !reflect.DeepEqual(want, err) {
+ t.Fatalf("got %#v, want %#v", want, err)
+ }
+}
+
+func TestHostAuthority(t *testing.T) {
+ for _, m := range []struct {
+ authorityFor string
+ address string
+
+ good bool
+ }{
+ {authorityFor: "localhost", address: "localhost:22", good: true},
+ {authorityFor: "localhost", address: "localhost", good: false},
+ {authorityFor: "localhost", address: "localhost:1234", good: false},
+ {authorityFor: "[localhost]:1234", address: "localhost:1234", good: true},
+ {authorityFor: "[localhost]:1234", address: "localhost:22", good: false},
+ {authorityFor: "[localhost]:1234", address: "localhost", good: false},
+ } {
+ db := testDB(t, `@cert-authority `+m.authorityFor+` `+edKeyStr)
+ if ok := db.IsHostAuthority(db.lines[0].knownKey.Key, m.address); ok != m.good {
+ t.Errorf("IsHostAuthority: authority %s, address %s, wanted good = %v, got good = %v",
+ m.authorityFor, m.address, m.good, ok)
+ }
+ }
+}
+
+func TestBracket(t *testing.T) {
+ db := testDB(t, `[git.eclipse.org]:29418,[198.41.30.196]:29418 `+edKeyStr)
+
+ if err := db.check("git.eclipse.org:29418", &net.TCPAddr{
+ IP: net.IP{198, 41, 30, 196},
+ Port: 29418,
+ }, edKey); err != nil {
+ t.Errorf("got error %v, want none", err)
+ }
+
+ if err := db.check("git.eclipse.org:29419", &net.TCPAddr{
+ Port: 42,
+ }, edKey); err == nil {
+ t.Fatalf("no error for unknown address")
+ } else if ke, ok := err.(*KeyError); !ok {
+ t.Fatalf("got type %T, want *KeyError", err)
+ } else if len(ke.Want) > 0 {
+ t.Fatalf("got Want %v, want []", ke.Want)
+ }
+}
+
+func TestNewKeyType(t *testing.T) {
+ str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
+ db := testDB(t, str)
+ if err := db.check("", testAddr, ecKey); err == nil {
+ t.Fatalf("no error for unknown address")
+ } else if ke, ok := err.(*KeyError); !ok {
+ t.Fatalf("got type %T, want *KeyError", err)
+ } else if len(ke.Want) == 0 {
+ t.Fatalf("got empty KeyError.Want")
+ }
+}
+
+func TestSameKeyType(t *testing.T) {
+ str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
+ db := testDB(t, str)
+ if err := db.check("", testAddr, alternateEdKey); err == nil {
+ t.Fatalf("no error for unknown address")
+ } else if ke, ok := err.(*KeyError); !ok {
+ t.Fatalf("got type %T, want *KeyError", err)
+ } else if len(ke.Want) == 0 {
+ t.Fatalf("got empty KeyError.Want")
+ } else if got, want := ke.Want[0].Key.Marshal(), edKey.Marshal(); !bytes.Equal(got, want) {
+ t.Fatalf("got key %q, want %q", got, want)
+ }
+}
+
+func TestIPAddress(t *testing.T) {
+ str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
+ db := testDB(t, str)
+ if err := db.check("", testAddr, edKey); err != nil {
+ t.Errorf("got error %q, want none", err)
+ }
+}
+
+func TestIPv6Address(t *testing.T) {
+ str := fmt.Sprintf("%s %s", testAddr6, edKeyStr)
+ db := testDB(t, str)
+
+ if err := db.check("", testAddr6, edKey); err != nil {
+ t.Errorf("got error %q, want none", err)
+ }
+}
+
+func TestBasic(t *testing.T) {
+ str := fmt.Sprintf("#comment\n\nserver.org,%s %s\notherhost %s", testAddr, edKeyStr, ecKeyStr)
+ db := testDB(t, str)
+ if err := db.check("server.org:22", testAddr, edKey); err != nil {
+ t.Errorf("got error %v, want none", err)
+ }
+
+ want := KnownKey{
+ Key: edKey,
+ Filename: "testdb",
+ Line: 3,
+ }
+ if err := db.check("server.org:22", testAddr, ecKey); err == nil {
+ t.Errorf("succeeded, want KeyError")
+ } else if ke, ok := err.(*KeyError); !ok {
+ t.Errorf("got %T, want *KeyError", err)
+ } else if len(ke.Want) != 1 {
+ t.Errorf("got %v, want 1 entry", ke)
+ } else if !reflect.DeepEqual(ke.Want[0], want) {
+ t.Errorf("got %v, want %v", ke.Want[0], want)
+ }
+}
+
+func TestHostNamePrecedence(t *testing.T) {
+ var evilAddr = &net.TCPAddr{
+ IP: net.IP{66, 66, 66, 66},
+ Port: 22,
+ }
+
+ str := fmt.Sprintf("server.org,%s %s\nevil.org,%s %s", testAddr, edKeyStr, evilAddr, ecKeyStr)
+ db := testDB(t, str)
+
+ if err := db.check("server.org:22", evilAddr, ecKey); err == nil {
+ t.Errorf("check succeeded")
+ } else if _, ok := err.(*KeyError); !ok {
+ t.Errorf("got %T, want *KeyError", err)
+ }
+}
+
+func TestNegate(t *testing.T) {
+ str := fmt.Sprintf("%s,!server.org %s", testAddr, edKeyStr)
+ db := testDB(t, str)
+ if err := db.check("server.org:22", testAddr, ecKey); err == nil {
+ t.Errorf("succeeded")
+ } else if ke, ok := err.(*KeyError); !ok {
+ t.Errorf("got error type %T, want *KeyError", err)
+ } else if len(ke.Want) != 0 {
+ t.Errorf("got expected keys %d (first of type %s), want []", len(ke.Want), ke.Want[0].Key.Type())
+ }
+}
+
+func TestWildcard(t *testing.T) {
+ str := fmt.Sprintf("server*.domain %s", edKeyStr)
+ db := testDB(t, str)
+
+ want := &KeyError{
+ Want: []KnownKey{{
+ Filename: "testdb",
+ Line: 1,
+ Key: edKey,
+ }},
+ }
+
+ got := db.check("server.domain:22", &net.TCPAddr{}, ecKey)
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got %s, want %s", got, want)
+ }
+}
+
+func TestLine(t *testing.T) {
+ for in, want := range map[string]string{
+ "server.org": "server.org " + edKeyStr,
+ "server.org:22": "server.org " + edKeyStr,
+ "server.org:23": "[server.org]:23 " + edKeyStr,
+ "[c629:1ec4:102:304:102:304:102:304]:22": "c629:1ec4:102:304:102:304:102:304 " + edKeyStr,
+ "[c629:1ec4:102:304:102:304:102:304]:23": "[c629:1ec4:102:304:102:304:102:304]:23 " + edKeyStr,
+ } {
+ if got := Line([]string{in}, edKey); got != want {
+ t.Errorf("Line(%q) = %q, want %q", in, got, want)
+ }
+ }
+}
+
+func TestWildcardMatch(t *testing.T) {
+ for _, c := range []struct {
+ pat, str string
+ want bool
+ }{
+ {"a?b", "abb", true},
+ {"ab", "abc", false},
+ {"abc", "ab", false},
+ {"a*b", "axxxb", true},
+ {"a*b", "axbxb", true},
+ {"a*b", "axbxbc", false},
+ {"a*?", "axbxc", true},
+ {"a*b*", "axxbxxxxxx", true},
+ {"a*b*c", "axxbxxxxxxc", true},
+ {"a*b*?", "axxbxxxxxxc", true},
+ {"a*b*z", "axxbxxbxxxz", true},
+ {"a*b*z", "axxbxxzxxxz", true},
+ {"a*b*z", "axxbxxzxxx", false},
+ } {
+ got := wildcardMatch([]byte(c.pat), []byte(c.str))
+ if got != c.want {
+ t.Errorf("wildcardMatch(%q, %q) = %v, want %v", c.pat, c.str, got, c.want)
+ }
+
+ }
+}
+
+func TestRevokedCA(t *testing.T) {
+ _, caPriv, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ caSigner, err := ssh.NewSignerFromKey(caPriv)
+ if err != nil {
+ t.Fatal(err)
+ }
+ caKey := caSigner.PublicKey()
+
+ _, hostPriv, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ hostKey, err := ssh.NewPublicKey(hostPriv.Public())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cert := &ssh.Certificate{
+ CertType: ssh.HostCert,
+ Key: hostKey,
+ ValidBefore: ssh.CertTimeInfinity,
+ ValidPrincipals: []string{"server.org"},
+ }
+ if err := cert.SignCert(rand.Reader, caSigner); err != nil {
+ t.Fatal(err)
+ }
+
+ caLine := "ssh-ed25519 " + serialize(caKey)[len("ssh-ed25519 "):]
+ knownHostsData := "@revoked server.org " + caLine + "\n" +
+ "@cert-authority server.org " + caLine + "\n"
+ db := testDB(t, knownHostsData)
+
+ if !db.IsRevoked(cert) {
+ t.Error("IsRevoked returned false for certificate signed by revoked CA")
+ }
+}
+
+const testHostname = "hostname"
+
+// generated with keygen -H -f
+const encodedTestHostnameHash = "|1|IHXZvQMvTcZTUU29+2vXFgx8Frs=|UGccIWfRVDwilMBnA3WJoRAC75Y="
+
+func TestHostHash(t *testing.T) {
+ testHostHash(t, testHostname, encodedTestHostnameHash)
+}
+
+func TestHashList(t *testing.T) {
+ encoded := HashHostname(testHostname)
+ testHostHash(t, testHostname, encoded)
+}
+
+func testHostHash(t *testing.T, hostname, encoded string) {
+ typ, salt, hash, err := decodeHash(encoded)
+ if err != nil {
+ t.Fatalf("decodeHash: %v", err)
+ }
+
+ if got := encodeHash(typ, salt, hash); got != encoded {
+ t.Errorf("got encoding %s want %s", got, encoded)
+ }
+
+ if typ != sha1HashType {
+ t.Fatalf("got hash type %q, want %q", typ, sha1HashType)
+ }
+
+ got := hashHost(hostname, salt)
+ if !bytes.Equal(got, hash) {
+ t.Errorf("got hash %x want %x", got, hash)
+ }
+}
+
+func TestNormalize(t *testing.T) {
+ for in, want := range map[string]string{
+ "127.0.0.1": "127.0.0.1",
+ "127.0.0.1:22": "127.0.0.1",
+ "[127.0.0.1]:22": "127.0.0.1",
+ "[127.0.0.1]:23": "[127.0.0.1]:23",
+ "127.0.0.1:23": "[127.0.0.1]:23",
+ "[a.b.c]:22": "a.b.c",
+ "[a.b.c]:23": "[a.b.c]:23",
+ "abcd::abcd:abcd:abcd": "abcd::abcd:abcd:abcd",
+ "[abcd::abcd:abcd:abcd]": "abcd::abcd:abcd:abcd",
+ "[abcd::abcd:abcd:abcd]:22": "abcd::abcd:abcd:abcd",
+ "[abcd::abcd:abcd:abcd]:23": "[abcd::abcd:abcd:abcd]:23",
+ "2001:db8::1": "2001:db8::1",
+ "2001:db8::1:22": "2001:db8::1:22",
+ "[2001:db8::1]:22": "2001:db8::1",
+ "2001:db8::1:2200": "2001:db8::1:2200",
+ "a.b.c.d.com:2200": "[a.b.c.d.com]:2200",
+ "2001::db8:1": "2001::db8:1",
+ "2001::db8:1:22": "2001::db8:1:22",
+ "2001::db8:1:2200": "2001::db8:1:2200",
+ } {
+ got := Normalize(in)
+ if got != want {
+ t.Errorf("Normalize(%q) = %q, want %q", in, got, want)
+ }
+ }
+}
+
+func TestHashedHostkeyCheck(t *testing.T) {
+ str := fmt.Sprintf("%s %s", HashHostname(testHostname), edKeyStr)
+ db := testDB(t, str)
+ if err := db.check(testHostname+":22", testAddr, edKey); err != nil {
+ t.Errorf("check(%s): %v", testHostname, err)
+ }
+ want := &KeyError{
+ Want: []KnownKey{{
+ Filename: "testdb",
+ Line: 1,
+ Key: edKey,
+ }},
+ }
+ if got := db.check(testHostname+":22", testAddr, alternateEdKey); !reflect.DeepEqual(got, want) {
+ t.Errorf("got error %v, want %v", got, want)
+ }
+}
+
+func TestIssue36126(t *testing.T) {
+ str := fmt.Sprintf("server.org,%s %s\nserver.org,%s %s", testAddr, edKeyStr, testAddr, alternateEdKeyStr)
+ db := testDB(t, str)
+
+ if err := db.check("server.org:22", testAddr, edKey); err != nil {
+ t.Errorf("should have passed the check, got %v", err)
+ }
+
+ if err := db.check("server.org:22", testAddr, alternateEdKey); err != nil {
+ t.Errorf("should have passed the check, got %v", err)
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/mac.go b/local_crypto_patch/contents/ssh/mac.go
new file mode 100644
index 0000000000..87d626fbbf
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/mempipe_test.go b/local_crypto_patch/contents/ssh/mempipe_test.go
new file mode 100644
index 0000000000..f27339c51a
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/mempipe_test.go
@@ -0,0 +1,124 @@
+// 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 (
+ "io"
+ "sync"
+ "testing"
+)
+
+// An in-memory packetConn. It is safe to call Close and writePacket
+// from different goroutines.
+type memTransport struct {
+ eof bool
+ pending [][]byte
+ write *memTransport
+ writeCount uint64
+ sync.Mutex
+ *sync.Cond
+}
+
+func (t *memTransport) readPacket() ([]byte, error) {
+ t.Lock()
+ defer t.Unlock()
+ for {
+ if len(t.pending) > 0 {
+ r := t.pending[0]
+ t.pending = t.pending[1:]
+ return r, nil
+ }
+ if t.eof {
+ return nil, io.EOF
+ }
+ t.Cond.Wait()
+ }
+}
+
+func (t *memTransport) closeSelf() error {
+ t.Lock()
+ defer t.Unlock()
+ if t.eof {
+ return io.EOF
+ }
+ t.eof = true
+ t.Cond.Broadcast()
+ return nil
+}
+
+func (t *memTransport) Close() error {
+ err := t.write.closeSelf()
+ t.closeSelf()
+ return err
+}
+
+func (t *memTransport) writePacket(p []byte) error {
+ t.write.Lock()
+ defer t.write.Unlock()
+ if t.write.eof {
+ return io.EOF
+ }
+ c := make([]byte, len(p))
+ copy(c, p)
+ t.write.pending = append(t.write.pending, c)
+ t.write.Cond.Signal()
+ t.writeCount++
+ return nil
+}
+
+func (t *memTransport) getWriteCount() uint64 {
+ t.write.Lock()
+ defer t.write.Unlock()
+ return t.writeCount
+}
+
+func memPipe() (a, b packetConn) {
+ t1 := memTransport{}
+ t2 := memTransport{}
+ t1.write = &t2
+ t2.write = &t1
+ t1.Cond = sync.NewCond(&t1.Mutex)
+ t2.Cond = sync.NewCond(&t2.Mutex)
+ return &t1, &t2
+}
+
+func TestMemPipe(t *testing.T) {
+ a, b := memPipe()
+ if err := a.writePacket([]byte{42}); err != nil {
+ t.Fatalf("writePacket: %v", err)
+ }
+ if wc := a.(*memTransport).getWriteCount(); wc != 1 {
+ t.Fatalf("got %v, want 1", wc)
+ }
+ if err := a.Close(); err != nil {
+ t.Fatal("Close: ", err)
+ }
+ p, err := b.readPacket()
+ if err != nil {
+ t.Fatal("readPacket: ", err)
+ }
+ if len(p) != 1 || p[0] != 42 {
+ t.Fatalf("got %v, want {42}", p)
+ }
+ p, err = b.readPacket()
+ if err != io.EOF {
+ t.Fatalf("got %v, %v, want EOF", p, err)
+ }
+ if wc := b.(*memTransport).getWriteCount(); wc != 0 {
+ t.Fatalf("got %v, want 0", wc)
+ }
+}
+
+func TestDoubleClose(t *testing.T) {
+ a, _ := memPipe()
+ err := a.Close()
+ if err != nil {
+ t.Errorf("Close: %v", err)
+ }
+ err = a.Close()
+ if err != io.EOF {
+ t.Errorf("expect EOF on double close.")
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/messages.go b/local_crypto_patch/contents/ssh/messages.go
new file mode 100644
index 0000000000..ab22c3d38d
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/messages_test.go b/local_crypto_patch/contents/ssh/messages_test.go
new file mode 100644
index 0000000000..ed346285bf
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/messages_test.go
@@ -0,0 +1,344 @@
+// 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"
+ "math/big"
+ "math/rand"
+ "reflect"
+ "testing"
+ "testing/quick"
+)
+
+var intLengthTests = []struct {
+ val, length int
+}{
+ {0, 4 + 0},
+ {1, 4 + 1},
+ {127, 4 + 1},
+ {128, 4 + 2},
+ {-1, 4 + 1},
+}
+
+func TestIntLength(t *testing.T) {
+ for _, test := range intLengthTests {
+ v := new(big.Int).SetInt64(int64(test.val))
+ length := intLength(v)
+ if length != test.length {
+ t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length)
+ }
+ }
+}
+
+type msgAllTypes struct {
+ Bool bool `sshtype:"21"`
+ Array [16]byte
+ Uint64 uint64
+ Uint32 uint32
+ Uint8 uint8
+ String string
+ Strings []string
+ Bytes []byte
+ Int *big.Int
+ Rest []byte `ssh:"rest"`
+}
+
+func (t *msgAllTypes) Generate(rand *rand.Rand, size int) reflect.Value {
+ m := &msgAllTypes{}
+ m.Bool = rand.Intn(2) == 1
+ randomBytes(m.Array[:], rand)
+ m.Uint64 = uint64(rand.Int63n(1<<63 - 1))
+ m.Uint32 = uint32(rand.Intn((1 << 31) - 1))
+ m.Uint8 = uint8(rand.Intn(1 << 8))
+ m.String = string(m.Array[:])
+ m.Strings = randomNameList(rand)
+ m.Bytes = m.Array[:]
+ m.Int = randomInt(rand)
+ m.Rest = m.Array[:]
+ return reflect.ValueOf(m)
+}
+
+func TestMarshalUnmarshal(t *testing.T) {
+ rand := rand.New(rand.NewSource(0))
+ iface := &msgAllTypes{}
+ ty := reflect.ValueOf(iface).Type()
+
+ n := 100
+ if testing.Short() {
+ n = 5
+ }
+ for j := 0; j < n; j++ {
+ v, ok := quick.Value(ty, rand)
+ if !ok {
+ t.Errorf("failed to create value")
+ break
+ }
+
+ m1 := v.Elem().Interface()
+ m2 := iface
+
+ marshaled := Marshal(m1)
+ if err := Unmarshal(marshaled, m2); err != nil {
+ t.Errorf("Unmarshal %#v: %s", m1, err)
+ break
+ }
+
+ if !reflect.DeepEqual(v.Interface(), m2) {
+ t.Errorf("got: %#v\nwant:%#v\n%x", m2, m1, marshaled)
+ break
+ }
+ }
+}
+
+func TestUnmarshalEmptyPacket(t *testing.T) {
+ var b []byte
+ var m channelRequestSuccessMsg
+ if err := Unmarshal(b, &m); err == nil {
+ t.Fatalf("unmarshal of empty slice succeeded")
+ }
+}
+
+func TestUnmarshalUnexpectedPacket(t *testing.T) {
+ type S struct {
+ I uint32 `sshtype:"43"`
+ S string
+ B bool
+ }
+
+ s := S{11, "hello", true}
+ packet := Marshal(s)
+ packet[0] = 42
+ roundtrip := S{}
+ err := Unmarshal(packet, &roundtrip)
+ if err == nil {
+ t.Fatal("expected error, not nil")
+ }
+}
+
+func TestMarshalPtr(t *testing.T) {
+ s := struct {
+ S string
+ }{"hello"}
+
+ m1 := Marshal(s)
+ m2 := Marshal(&s)
+ if !bytes.Equal(m1, m2) {
+ t.Errorf("got %q, want %q for marshaled pointer", m2, m1)
+ }
+}
+
+func TestBareMarshalUnmarshal(t *testing.T) {
+ type S struct {
+ I uint32
+ S string
+ B bool
+ }
+
+ s := S{42, "hello", true}
+ packet := Marshal(s)
+ roundtrip := S{}
+ Unmarshal(packet, &roundtrip)
+
+ if !reflect.DeepEqual(s, roundtrip) {
+ t.Errorf("got %#v, want %#v", roundtrip, s)
+ }
+}
+
+func TestBareMarshal(t *testing.T) {
+ type S2 struct {
+ I uint32
+ }
+ s := S2{42}
+ packet := Marshal(s)
+ i, rest, ok := parseUint32(packet)
+ if len(rest) > 0 || !ok {
+ t.Errorf("parseInt(%q): parse error", packet)
+ }
+ if i != s.I {
+ t.Errorf("got %d, want %d", i, s.I)
+ }
+}
+
+func TestUnmarshalShortKexInitPacket(t *testing.T) {
+ // This used to panic.
+ // Issue 11348
+ packet := []byte{0x14, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xff, 0xff, 0xff, 0xff}
+ kim := &kexInitMsg{}
+ if err := Unmarshal(packet, kim); err == nil {
+ t.Error("truncated packet unmarshaled without error")
+ }
+}
+
+func TestMarshalMultiTag(t *testing.T) {
+ var res struct {
+ A uint32 `sshtype:"1|2"`
+ }
+
+ good1 := struct {
+ A uint32 `sshtype:"1"`
+ }{
+ 1,
+ }
+ good2 := struct {
+ A uint32 `sshtype:"2"`
+ }{
+ 1,
+ }
+
+ if e := Unmarshal(Marshal(good1), &res); e != nil {
+ t.Errorf("error unmarshaling multipart tag: %v", e)
+ }
+
+ if e := Unmarshal(Marshal(good2), &res); e != nil {
+ t.Errorf("error unmarshaling multipart tag: %v", e)
+ }
+
+ bad1 := struct {
+ A uint32 `sshtype:"3"`
+ }{
+ 1,
+ }
+ if e := Unmarshal(Marshal(bad1), &res); e == nil {
+ t.Errorf("bad struct unmarshaled without error")
+ }
+}
+
+func TestDecode(t *testing.T) {
+ rnd := rand.New(rand.NewSource(0))
+ kexInit := new(kexInitMsg).Generate(rnd, 10).Interface()
+ kexDHInit := new(kexDHInitMsg).Generate(rnd, 10).Interface()
+ kexDHReply := new(kexDHReplyMsg)
+ kexDHReply.Y = randomInt(rnd)
+ // Note: userAuthSuccessMsg can't be tested directly since it
+ // doesn't have a field for sshtype. So it's tested separately
+ // at the end.
+ decodeMessageTypes := []interface{}{
+ new(disconnectMsg),
+ new(serviceRequestMsg),
+ new(serviceAcceptMsg),
+ new(extInfoMsg),
+ kexInit,
+ kexDHInit,
+ kexDHReply,
+ new(userAuthRequestMsg),
+ new(userAuthFailureMsg),
+ new(userAuthBannerMsg),
+ new(userAuthPubKeyOkMsg),
+ new(globalRequestMsg),
+ new(globalRequestSuccessMsg),
+ new(globalRequestFailureMsg),
+ new(channelOpenMsg),
+ new(channelDataMsg),
+ new(channelOpenConfirmMsg),
+ new(channelOpenFailureMsg),
+ new(windowAdjustMsg),
+ new(channelEOFMsg),
+ new(channelCloseMsg),
+ new(channelRequestMsg),
+ new(channelRequestSuccessMsg),
+ new(channelRequestFailureMsg),
+ new(userAuthGSSAPIToken),
+ new(userAuthGSSAPIMIC),
+ new(userAuthGSSAPIErrTok),
+ new(userAuthGSSAPIError),
+ }
+ for _, msg := range decodeMessageTypes {
+ decoded, err := decode(Marshal(msg))
+ if err != nil {
+ t.Errorf("error decoding %T", msg)
+ } else if reflect.TypeOf(msg) != reflect.TypeOf(decoded) {
+ t.Errorf("error decoding %T, unexpected %T", msg, decoded)
+ }
+ }
+
+ userAuthSuccess, err := decode([]byte{msgUserAuthSuccess})
+ if err != nil {
+ t.Errorf("error decoding userAuthSuccessMsg")
+ } else if _, ok := userAuthSuccess.(*userAuthSuccessMsg); !ok {
+ t.Errorf("error decoding userAuthSuccessMsg, unexpected %T", userAuthSuccess)
+ }
+}
+
+func randomBytes(out []byte, rand *rand.Rand) {
+ for i := 0; i < len(out); i++ {
+ out[i] = byte(rand.Int31())
+ }
+}
+
+func randomNameList(rand *rand.Rand) []string {
+ ret := make([]string, rand.Int31()&15)
+ for i := range ret {
+ s := make([]byte, 1+(rand.Int31()&15))
+ for j := range s {
+ s[j] = 'a' + uint8(rand.Int31()&15)
+ }
+ ret[i] = string(s)
+ }
+ return ret
+}
+
+func randomInt(rand *rand.Rand) *big.Int {
+ return new(big.Int).SetInt64(int64(int32(rand.Uint32())))
+}
+
+func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
+ ki := &kexInitMsg{}
+ randomBytes(ki.Cookie[:], rand)
+ ki.KexAlgos = randomNameList(rand)
+ ki.ServerHostKeyAlgos = randomNameList(rand)
+ ki.CiphersClientServer = randomNameList(rand)
+ ki.CiphersServerClient = randomNameList(rand)
+ ki.MACsClientServer = randomNameList(rand)
+ ki.MACsServerClient = randomNameList(rand)
+ ki.CompressionClientServer = randomNameList(rand)
+ ki.CompressionServerClient = randomNameList(rand)
+ ki.LanguagesClientServer = randomNameList(rand)
+ ki.LanguagesServerClient = randomNameList(rand)
+ if rand.Int31()&1 == 1 {
+ ki.FirstKexFollows = true
+ }
+ return reflect.ValueOf(ki)
+}
+
+func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
+ dhi := &kexDHInitMsg{}
+ dhi.X = randomInt(rand)
+ return reflect.ValueOf(dhi)
+}
+
+var (
+ _kexInitMsg = new(kexInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
+ _kexDHInitMsg = new(kexDHInitMsg).Generate(rand.New(rand.NewSource(0)), 10).Elem().Interface()
+
+ _kexInit = Marshal(_kexInitMsg)
+ _kexDHInit = Marshal(_kexDHInitMsg)
+)
+
+func BenchmarkMarshalKexInitMsg(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Marshal(_kexInitMsg)
+ }
+}
+
+func BenchmarkUnmarshalKexInitMsg(b *testing.B) {
+ m := new(kexInitMsg)
+ for i := 0; i < b.N; i++ {
+ Unmarshal(_kexInit, m)
+ }
+}
+
+func BenchmarkMarshalKexDHInitMsg(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Marshal(_kexDHInitMsg)
+ }
+}
+
+func BenchmarkUnmarshalKexDHInitMsg(b *testing.B) {
+ m := new(kexDHInitMsg)
+ for i := 0; i < b.N; i++ {
+ Unmarshal(_kexDHInit, m)
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/mlkem.go b/local_crypto_patch/contents/ssh/mlkem.go
new file mode 100644
index 0000000000..ddc0ed1fc0
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/mux.go b/local_crypto_patch/contents/ssh/mux.go
new file mode 100644
index 0000000000..5775881c6a
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/mux.go
@@ -0,0 +1,388 @@
+// 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
+ globalSentPending atomic.Bool
+ 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()
+
+ // Open the gate so that responses arriving while this request is in
+ // flight are allowed to reach globalResponses. Any response arriving
+ // while no request is pending is dropped by handleGlobalPacket.
+ m.globalSentPending.Store(true)
+ defer m.globalSentPending.Store(false)
+
+ // Drain any spurious responses that may have been buffered. This prevents
+ // a previously buffered unexpected response from being consumed instead
+ // of the actual response for this request.
+ drain:
+ for {
+ select {
+ case _, ok := <-m.globalResponses:
+ if !ok {
+ break drain
+ }
+ default:
+ break drain
+ }
+ }
+ }
+
+ 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:
+ // Drop responses that arrive when no SendRequest is waiting, to
+ // prevent a malicious peer from staging responses for a future
+ // caller.
+ if !m.globalSentPending.Load() {
+ return nil
+ }
+ select {
+ case m.globalResponses <- msg:
+ default:
+ }
+ 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/local_crypto_patch/contents/ssh/mux_test.go b/local_crypto_patch/contents/ssh/mux_test.go
new file mode 100644
index 0000000000..65d22be8d2
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/mux_test.go
@@ -0,0 +1,1368 @@
+// 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"
+ "sync"
+ "testing"
+)
+
+func muxPair() (*mux, *mux) {
+ a, b := memPipe()
+
+ s := newMux(a)
+ c := newMux(b)
+
+ return s, c
+}
+
+// Returns both ends of a channel, and the mux for the 2nd
+// channel.
+func channelPair(t *testing.T) (*channel, *channel, *mux) {
+ c, s := muxPair()
+
+ res := make(chan *channel, 1)
+ go func() {
+ newCh, ok := <-s.incomingChannels
+ if !ok {
+ t.Error("no incoming channel")
+ close(res)
+ return
+ }
+ if newCh.ChannelType() != "chan" {
+ t.Errorf("got type %q want chan", newCh.ChannelType())
+ newCh.Reject(Prohibited, fmt.Sprintf("got type %q want chan", newCh.ChannelType()))
+ close(res)
+ return
+ }
+ ch, _, err := newCh.Accept()
+ if err != nil {
+ t.Errorf("accept: %v", err)
+ close(res)
+ return
+ }
+ res <- ch.(*channel)
+ }()
+
+ ch, err := c.openChannel("chan", nil)
+ if err != nil {
+ t.Fatalf("OpenChannel: %v", err)
+ }
+ w := <-res
+ if w == nil {
+ t.Fatal("unable to get write channel")
+ }
+
+ return w, ch, c
+}
+
+// Test that stderr and stdout can be addressed from different
+// goroutines. This is intended for use with the race detector.
+func TestMuxChannelExtendedThreadSafety(t *testing.T) {
+ writer, reader, mux := channelPair(t)
+ defer writer.Close()
+ defer reader.Close()
+ defer mux.Close()
+
+ var wr, rd sync.WaitGroup
+ magic := "hello world"
+
+ wr.Add(2)
+ go func() {
+ io.WriteString(writer, magic)
+ wr.Done()
+ }()
+ go func() {
+ io.WriteString(writer.Stderr(), magic)
+ wr.Done()
+ }()
+
+ rd.Add(2)
+ go func() {
+ c, err := io.ReadAll(reader)
+ if string(c) != magic {
+ t.Errorf("stdout read got %q, want %q (error %s)", c, magic, err)
+ }
+ rd.Done()
+ }()
+ go func() {
+ c, err := io.ReadAll(reader.Stderr())
+ if string(c) != magic {
+ t.Errorf("stderr read got %q, want %q (error %s)", c, magic, err)
+ }
+ rd.Done()
+ }()
+
+ wr.Wait()
+ writer.CloseWrite()
+ rd.Wait()
+}
+
+func TestMuxReadWrite(t *testing.T) {
+ s, c, mux := channelPair(t)
+ defer s.Close()
+ defer c.Close()
+ defer mux.Close()
+
+ magic := "hello world"
+ magicExt := "hello stderr"
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ _, err := s.Write([]byte(magic))
+ if err != nil {
+ t.Errorf("Write: %v", err)
+ return
+ }
+ _, err = s.Extended(1).Write([]byte(magicExt))
+ if err != nil {
+ t.Errorf("Write: %v", err)
+ return
+ }
+ }()
+
+ var buf [1024]byte
+ n, err := c.Read(buf[:])
+ if err != nil {
+ t.Fatalf("server Read: %v", err)
+ }
+ got := string(buf[:n])
+ if got != magic {
+ t.Fatalf("server: got %q want %q", got, magic)
+ }
+
+ n, err = c.Extended(1).Read(buf[:])
+ if err != nil {
+ t.Fatalf("server Read: %v", err)
+ }
+
+ got = string(buf[:n])
+ if got != magicExt {
+ t.Fatalf("server: got %q want %q", got, magic)
+ }
+}
+
+func TestMuxChannelOverflow(t *testing.T) {
+ reader, writer, mux := channelPair(t)
+ defer reader.Close()
+ defer writer.Close()
+ defer mux.Close()
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
+ t.Errorf("could not fill window: %v", err)
+ }
+ writer.Write(make([]byte, 1))
+ }()
+ writer.remoteWin.waitWriterBlocked()
+
+ // Send 1 byte.
+ packet := make([]byte, 1+4+4+1)
+ packet[0] = msgChannelData
+ marshalUint32(packet[1:], writer.remoteId)
+ marshalUint32(packet[5:], uint32(1))
+ packet[9] = 42
+
+ if err := writer.mux.conn.writePacket(packet); err != nil {
+ t.Errorf("could not send packet")
+ }
+ if _, err := reader.SendRequest("hello", true, nil); err == nil {
+ t.Errorf("SendRequest succeeded.")
+ }
+}
+
+func TestMuxChannelReadUnblock(t *testing.T) {
+ reader, writer, mux := channelPair(t)
+ defer reader.Close()
+ defer writer.Close()
+ defer mux.Close()
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
+ t.Errorf("could not fill window: %v", err)
+ }
+ if _, err := writer.Write(make([]byte, 1)); err != nil {
+ t.Errorf("Write: %v", err)
+ }
+ writer.Close()
+ }()
+
+ writer.remoteWin.waitWriterBlocked()
+
+ buf := make([]byte, 32768)
+ for {
+ _, err := reader.Read(buf)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatalf("Read: %v", err)
+ }
+ }
+}
+
+func TestMuxChannelCloseWriteUnblock(t *testing.T) {
+ reader, writer, mux := channelPair(t)
+ defer reader.Close()
+ defer writer.Close()
+ defer mux.Close()
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
+ t.Errorf("could not fill window: %v", err)
+ }
+ if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
+ t.Errorf("got %v, want EOF for unblock write", err)
+ }
+ }()
+
+ writer.remoteWin.waitWriterBlocked()
+ reader.Close()
+}
+
+func TestMuxConnectionCloseWriteUnblock(t *testing.T) {
+ reader, writer, mux := channelPair(t)
+ defer reader.Close()
+ defer writer.Close()
+ defer mux.Close()
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if _, err := writer.Write(make([]byte, channelWindowSize)); err != nil {
+ t.Errorf("could not fill window: %v", err)
+ }
+ if _, err := writer.Write(make([]byte, 1)); err != io.EOF {
+ t.Errorf("got %v, want EOF for unblock write", err)
+ }
+ }()
+
+ writer.remoteWin.waitWriterBlocked()
+ mux.Close()
+}
+
+func TestMuxReject(t *testing.T) {
+ client, server := muxPair()
+ defer server.Close()
+ defer client.Close()
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ ch, ok := <-server.incomingChannels
+ if !ok {
+ t.Error("cannot accept channel")
+ return
+ }
+ if ch.ChannelType() != "ch" || string(ch.ExtraData()) != "extra" {
+ t.Errorf("unexpected channel: %q, %q", ch.ChannelType(), ch.ExtraData())
+ ch.Reject(RejectionReason(UnknownChannelType), UnknownChannelType.String())
+ return
+ }
+ ch.Reject(RejectionReason(42), "message")
+ }()
+
+ ch, err := client.openChannel("ch", []byte("extra"))
+ if ch != nil {
+ t.Fatal("openChannel not rejected")
+ }
+
+ ocf, ok := err.(*OpenChannelError)
+ if !ok {
+ t.Errorf("got %#v want *OpenChannelError", err)
+ } else if ocf.Reason != 42 || ocf.Message != "message" {
+ t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message")
+ }
+
+ want := "ssh: rejected: unknown reason 42 (message)"
+ if err.Error() != want {
+ t.Errorf("got %q, want %q", err.Error(), want)
+ }
+}
+
+func TestMuxChannelRequest(t *testing.T) {
+ client, server, mux := channelPair(t)
+ defer server.Close()
+ defer client.Close()
+ defer mux.Close()
+
+ var received int
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ for r := range server.incomingRequests {
+ received++
+ r.Reply(r.Type == "yes", nil)
+ }
+ wg.Done()
+ }()
+ _, err := client.SendRequest("yes", false, nil)
+ if err != nil {
+ t.Fatalf("SendRequest: %v", err)
+ }
+ ok, err := client.SendRequest("yes", true, nil)
+ if err != nil {
+ t.Fatalf("SendRequest: %v", err)
+ }
+
+ if !ok {
+ t.Errorf("SendRequest(yes): %v", ok)
+
+ }
+
+ ok, err = client.SendRequest("no", true, nil)
+ if err != nil {
+ t.Fatalf("SendRequest: %v", err)
+ }
+ if ok {
+ t.Errorf("SendRequest(no): %v", ok)
+ }
+
+ client.Close()
+ wg.Wait()
+
+ if received != 3 {
+ t.Errorf("got %d requests, want %d", received, 3)
+ }
+}
+
+func TestMuxUnknownChannelRequests(t *testing.T) {
+ clientPipe, serverPipe := memPipe()
+ client := newMux(clientPipe)
+ defer serverPipe.Close()
+ defer client.Close()
+
+ kDone := make(chan error, 1)
+ go func() {
+ // Ignore unknown channel messages that don't want a reply.
+ err := serverPipe.writePacket(Marshal(channelRequestMsg{
+ PeersID: 1,
+ Request: "keepalive@openssh.com",
+ WantReply: false,
+ RequestSpecificData: []byte{},
+ }))
+ if err != nil {
+ kDone <- fmt.Errorf("send: %w", err)
+ return
+ }
+
+ // Send a keepalive, which should get a channel failure message
+ // in response.
+ err = serverPipe.writePacket(Marshal(channelRequestMsg{
+ PeersID: 2,
+ Request: "keepalive@openssh.com",
+ WantReply: true,
+ RequestSpecificData: []byte{},
+ }))
+ if err != nil {
+ kDone <- fmt.Errorf("send: %w", err)
+ return
+ }
+
+ packet, err := serverPipe.readPacket()
+ if err != nil {
+ kDone <- fmt.Errorf("read packet: %w", err)
+ return
+ }
+ decoded, err := decode(packet)
+ if err != nil {
+ kDone <- fmt.Errorf("decode failed: %w", err)
+ return
+ }
+
+ switch msg := decoded.(type) {
+ case *channelRequestFailureMsg:
+ if msg.PeersID != 2 {
+ kDone <- fmt.Errorf("received response to wrong message: %v", msg)
+ return
+
+ }
+ default:
+ kDone <- fmt.Errorf("unexpected channel message: %v", msg)
+ return
+ }
+
+ kDone <- nil
+
+ // Receive and respond to the keepalive to confirm the mux is
+ // still processing requests.
+ packet, err = serverPipe.readPacket()
+ if err != nil {
+ kDone <- fmt.Errorf("read packet: %w", err)
+ return
+ }
+ if packet[0] != msgGlobalRequest {
+ kDone <- errors.New("expected global request")
+ return
+ }
+
+ err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
+ Data: []byte{},
+ }))
+ if err != nil {
+ kDone <- fmt.Errorf("failed to send failure msg: %w", err)
+ return
+ }
+
+ close(kDone)
+ }()
+
+ // Wait for the server to send the keepalive message and receive back a
+ // response.
+ if err := <-kDone; err != nil {
+ t.Fatal(err)
+ }
+
+ // Confirm client hasn't closed.
+ if _, _, err := client.SendRequest("keepalive@golang.org", true, nil); err != nil {
+ t.Fatalf("failed to send keepalive: %v", err)
+ }
+
+ // Wait for the server to shut down.
+ if err := <-kDone; err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMuxClosedChannel(t *testing.T) {
+ clientPipe, serverPipe := memPipe()
+ client := newMux(clientPipe)
+ defer serverPipe.Close()
+ defer client.Close()
+
+ kDone := make(chan error, 1)
+ go func() {
+ // Open the channel.
+ packet, err := serverPipe.readPacket()
+ if err != nil {
+ kDone <- fmt.Errorf("read packet: %w", err)
+ return
+ }
+ if packet[0] != msgChannelOpen {
+ kDone <- errors.New("expected chan open")
+ return
+ }
+
+ var openMsg channelOpenMsg
+ if err := Unmarshal(packet, &openMsg); err != nil {
+ kDone <- fmt.Errorf("unmarshal: %w", err)
+ return
+ }
+
+ // Send back the opened channel confirmation.
+ err = serverPipe.writePacket(Marshal(channelOpenConfirmMsg{
+ PeersID: openMsg.PeersID,
+ MyID: 0,
+ MyWindow: 0,
+ MaxPacketSize: channelMaxPacket,
+ }))
+ if err != nil {
+ kDone <- fmt.Errorf("send: %w", err)
+ return
+ }
+
+ // Close the channel.
+ err = serverPipe.writePacket(Marshal(channelCloseMsg{
+ PeersID: openMsg.PeersID,
+ }))
+ if err != nil {
+ kDone <- fmt.Errorf("send: %w", err)
+ return
+ }
+
+ // Send a keepalive message on the channel we just closed.
+ err = serverPipe.writePacket(Marshal(channelRequestMsg{
+ PeersID: openMsg.PeersID,
+ Request: "keepalive@openssh.com",
+ WantReply: true,
+ RequestSpecificData: []byte{},
+ }))
+ if err != nil {
+ kDone <- fmt.Errorf("send: %w", err)
+ return
+ }
+
+ // Receive the channel closed response.
+ packet, err = serverPipe.readPacket()
+ if err != nil {
+ kDone <- fmt.Errorf("read packet: %w", err)
+ return
+ }
+ if packet[0] != msgChannelClose {
+ kDone <- errors.New("expected channel close")
+ return
+ }
+
+ // Receive the keepalive response failure.
+ packet, err = serverPipe.readPacket()
+ if err != nil {
+ kDone <- fmt.Errorf("read packet: %w", err)
+ return
+ }
+ if packet[0] != msgChannelFailure {
+ kDone <- errors.New("expected channel failure")
+ return
+ }
+ kDone <- nil
+
+ // Receive and respond to the keepalive to confirm the mux is
+ // still processing requests.
+ packet, err = serverPipe.readPacket()
+ if err != nil {
+ kDone <- fmt.Errorf("read packet: %w", err)
+ return
+ }
+ if packet[0] != msgGlobalRequest {
+ kDone <- errors.New("expected global request")
+ return
+ }
+
+ err = serverPipe.writePacket(Marshal(globalRequestFailureMsg{
+ Data: []byte{},
+ }))
+ if err != nil {
+ kDone <- fmt.Errorf("failed to send failure msg: %w", err)
+ return
+ }
+
+ close(kDone)
+ }()
+
+ // Open a channel.
+ ch, err := client.openChannel("chan", nil)
+ if err != nil {
+ t.Fatalf("OpenChannel: %v", err)
+ }
+ defer ch.Close()
+
+ // Wait for the server to close the channel and send the keepalive.
+ <-kDone
+
+ // Make sure the channel closed.
+ if _, ok := <-ch.incomingRequests; ok {
+ t.Fatalf("channel not closed")
+ }
+
+ // Confirm client hasn't closed
+ if _, _, err := client.SendRequest("keepalive@golang.org", true, nil); err != nil {
+ t.Fatalf("failed to send keepalive: %v", err)
+ }
+
+ // Wait for the server to shut down.
+ <-kDone
+}
+
+func TestMuxGlobalRequest(t *testing.T) {
+ var sawPeek bool
+ var wg sync.WaitGroup
+ defer func() {
+ wg.Wait()
+ if !sawPeek {
+ t.Errorf("never saw 'peek' request")
+ }
+ }()
+
+ clientMux, serverMux := muxPair()
+ defer serverMux.Close()
+ defer clientMux.Close()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for r := range serverMux.incomingRequests {
+ sawPeek = sawPeek || r.Type == "peek"
+ if r.WantReply {
+ err := r.Reply(r.Type == "yes",
+ append([]byte(r.Type), r.Payload...))
+ if err != nil {
+ t.Errorf("AckRequest: %v", err)
+ }
+ }
+ }
+ }()
+
+ _, _, err := clientMux.SendRequest("peek", false, nil)
+ if err != nil {
+ t.Errorf("SendRequest: %v", err)
+ }
+
+ ok, data, err := clientMux.SendRequest("yes", true, []byte("a"))
+ if !ok || string(data) != "yesa" || err != nil {
+ t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
+ ok, data, err)
+ }
+ if ok, data, err := clientMux.SendRequest("yes", true, []byte("a")); !ok || string(data) != "yesa" || err != nil {
+ t.Errorf("SendRequest(\"yes\", true, \"a\"): %v %v %v",
+ ok, data, err)
+ }
+
+ if ok, data, err := clientMux.SendRequest("no", true, []byte("a")); ok || string(data) != "noa" || err != nil {
+ t.Errorf("SendRequest(\"no\", true, \"a\"): %v %v %v",
+ ok, data, err)
+ }
+}
+
+func TestMuxGlobalRequestUnblock(t *testing.T) {
+ clientMux, serverMux := muxPair()
+ defer serverMux.Close()
+ defer clientMux.Close()
+
+ result := make(chan error, 1)
+ go func() {
+ _, _, err := clientMux.SendRequest("hello", true, nil)
+ result <- err
+ }()
+
+ <-serverMux.incomingRequests
+ serverMux.conn.Close()
+ err := <-result
+
+ if err != io.EOF {
+ t.Errorf("want EOF, got %v", io.EOF)
+ }
+}
+
+func TestMuxChannelRequestUnblock(t *testing.T) {
+ a, b, connB := channelPair(t)
+ defer a.Close()
+ defer b.Close()
+ defer connB.Close()
+
+ result := make(chan error, 1)
+ go func() {
+ _, err := a.SendRequest("hello", true, nil)
+ result <- err
+ }()
+
+ <-b.incomingRequests
+ connB.conn.Close()
+ err := <-result
+
+ if err != io.EOF {
+ t.Errorf("want EOF, got %v", err)
+ }
+}
+
+func TestMuxCloseChannel(t *testing.T) {
+ r, w, mux := channelPair(t)
+ defer mux.Close()
+ defer r.Close()
+ defer w.Close()
+
+ result := make(chan error, 1)
+ go func() {
+ var b [1024]byte
+ _, err := r.Read(b[:])
+ result <- err
+ }()
+ if err := w.Close(); err != nil {
+ t.Errorf("w.Close: %v", err)
+ }
+
+ if _, err := w.Write([]byte("hello")); err != io.EOF {
+ t.Errorf("got err %v, want io.EOF after Close", err)
+ }
+
+ if err := <-result; err != io.EOF {
+ t.Errorf("got %v (%T), want io.EOF", err, err)
+ }
+}
+
+func TestMuxCloseWriteChannel(t *testing.T) {
+ r, w, mux := channelPair(t)
+ defer mux.Close()
+
+ result := make(chan error, 1)
+ go func() {
+ var b [1024]byte
+ _, err := r.Read(b[:])
+ result <- err
+ }()
+ if err := w.CloseWrite(); err != nil {
+ t.Errorf("w.CloseWrite: %v", err)
+ }
+
+ if _, err := w.Write([]byte("hello")); err != io.EOF {
+ t.Errorf("got err %v, want io.EOF after CloseWrite", err)
+ }
+
+ if err := <-result; err != io.EOF {
+ t.Errorf("got %v (%T), want io.EOF", err, err)
+ }
+}
+
+func TestMuxInvalidRecord(t *testing.T) {
+ a, b := muxPair()
+ defer a.Close()
+ defer b.Close()
+
+ packet := make([]byte, 1+4+4+1)
+ packet[0] = msgChannelData
+ marshalUint32(packet[1:], 29348723 /* invalid channel id */)
+ marshalUint32(packet[5:], 1)
+ packet[9] = 42
+
+ a.conn.writePacket(packet)
+ go a.SendRequest("hello", false, nil)
+ // 'a' wrote an invalid packet, so 'b' has exited.
+ req, ok := <-b.incomingRequests
+ if ok {
+ t.Errorf("got request %#v after receiving invalid packet", req)
+ }
+}
+
+func TestZeroWindowAdjust(t *testing.T) {
+ a, b, mux := channelPair(t)
+ defer a.Close()
+ defer b.Close()
+ defer mux.Close()
+
+ go func() {
+ io.WriteString(a, "hello")
+ // bogus adjust.
+ a.sendMessage(windowAdjustMsg{})
+ io.WriteString(a, "world")
+ a.Close()
+ }()
+
+ want := "helloworld"
+ c, _ := io.ReadAll(b)
+ if string(c) != want {
+ t.Errorf("got %q want %q", c, want)
+ }
+}
+
+func TestMuxMaxPacketSize(t *testing.T) {
+ a, b, mux := channelPair(t)
+ defer a.Close()
+ defer b.Close()
+ defer mux.Close()
+
+ large := make([]byte, a.maxRemotePayload+1)
+ packet := make([]byte, 1+4+4+1+len(large))
+ packet[0] = msgChannelData
+ marshalUint32(packet[1:], a.remoteId)
+ marshalUint32(packet[5:], uint32(len(large)))
+ packet[9] = 42
+
+ if err := a.mux.conn.writePacket(packet); err != nil {
+ t.Errorf("could not send packet")
+ }
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ a.SendRequest("hello", false, nil)
+ wg.Done()
+ }()
+
+ _, ok := <-b.incomingRequests
+ if ok {
+ t.Errorf("connection still alive after receiving large packet.")
+ }
+}
+
+func TestMuxChannelWindowDeferredUpdates(t *testing.T) {
+ s, c, mux := channelPair(t)
+ cTransport := mux.conn.(*memTransport)
+ defer s.Close()
+ defer c.Close()
+ defer mux.Close()
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+
+ data := make([]byte, 1024)
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ _, err := s.Write(data)
+ if err != nil {
+ t.Errorf("Write: %v", err)
+ return
+ }
+ }()
+ cWritesInit := cTransport.getWriteCount()
+ buf := make([]byte, 1)
+ for i := 0; i < len(data); i++ {
+ n, err := c.Read(buf)
+ if n != len(buf) || err != nil {
+ t.Fatalf("Read: %v, %v", n, err)
+ }
+ }
+ cWrites := cTransport.getWriteCount() - cWritesInit
+ // reading 1 KiB should not cause any window updates to be sent, but allow
+ // for some unexpected writes
+ if cWrites > 30 {
+ t.Fatalf("reading 1 KiB from channel caused %v writes", cWrites)
+ }
+}
+
+func TestMuxChannelRejectRemovesFromMux(t *testing.T) {
+ serverMux, clientMux := muxPair()
+ defer serverMux.Close()
+ defer clientMux.Close()
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+
+ go func() {
+ defer wg.Done()
+
+ // The server waits for the channel creation request
+ newCh, ok := <-serverMux.incomingChannels
+ if !ok {
+ t.Error("failed to accept channel")
+ return
+ }
+ ch := newCh.(*channel)
+
+ if serverMux.chanList.getChan(ch.localId) == nil {
+ t.Errorf("channel %d is not in the chanList before Reject", ch.localId)
+ }
+
+ if err := ch.Reject(Prohibited, "rejecting this channel"); err != nil {
+ t.Errorf("Reject failed: %v", err)
+ }
+
+ if serverMux.chanList.getChan(ch.localId) != nil {
+ t.Errorf("channel %d is still in the chanList after Reject", ch.localId)
+ }
+ }()
+
+ _, _, err := clientMux.OpenChannel("test_leak", nil)
+
+ if err == nil {
+ t.Fatal("expected an error (channel rejected), but got nil")
+ }
+
+ if _, ok := err.(*OpenChannelError); !ok {
+ t.Errorf("expected *OpenChannelError, got: %T", err)
+ }
+}
+
+// Don't ship code with debug=true.
+func TestDebug(t *testing.T) {
+ if debugMux {
+ t.Error("mux debug switched on")
+ }
+ if debugHandshake {
+ t.Error("handshake debug switched on")
+ }
+ if debugTransport {
+ t.Error("transport debug switched on")
+ }
+}
+
+func TestMuxUnexpectedGlobalResponsesDiscarded(t *testing.T) {
+ clientPipe, serverPipe := memPipe()
+ client := newMux(clientPipe)
+ defer serverPipe.Close()
+ defer client.Close()
+
+ done := make(chan error, 1)
+ go func() {
+ // Send multiple unexpected global responses, this should not block the
+ // globalResponses channel.
+ for i := range 5 {
+ err := serverPipe.writePacket(Marshal(globalRequestSuccessMsg{
+ Data: []byte{byte(i)},
+ }))
+ if err != nil {
+ done <- fmt.Errorf("send success msg %d: %w", i, err)
+ return
+ }
+ }
+ for i := range 5 {
+ err := serverPipe.writePacket(Marshal(globalRequestFailureMsg{
+ Data: []byte{byte(i)},
+ }))
+ if err != nil {
+ done <- fmt.Errorf("send failure msg %d: %w", i, err)
+ return
+ }
+ }
+
+ // Now send a global request and wait for the response. This
+ // verifies the mux is still processing packets.
+ err := serverPipe.writePacket(Marshal(globalRequestMsg{
+ Type: "keepalive@golang.org",
+ WantReply: true,
+ Data: nil,
+ }))
+ if err != nil {
+ done <- fmt.Errorf("send global request: %w", err)
+ return
+ }
+
+ packet, err := serverPipe.readPacket()
+ if err != nil {
+ done <- fmt.Errorf("read packet: %w", err)
+ return
+ }
+ decoded, err := decode(packet)
+ if err != nil {
+ done <- fmt.Errorf("decode: %w", err)
+ return
+ }
+ switch decoded.(type) {
+ case *globalRequestSuccessMsg, *globalRequestFailureMsg:
+ // Expected response
+ default:
+ done <- fmt.Errorf("unexpected packet type: %T", decoded)
+ return
+ }
+ done <- nil
+ }()
+
+ // Handle the incoming request from the server and reply
+ req, ok := <-client.incomingRequests
+ if !ok {
+ t.Fatal("incomingRequests channel closed unexpectedly")
+ }
+ if req.Type != "keepalive@golang.org" {
+ t.Fatalf("unexpected request type: %s", req.Type)
+ }
+ if err := req.Reply(true, nil); err != nil {
+ t.Fatalf("Reply: %v", err)
+ }
+
+ if err := <-done; err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMuxConcurrentGlobalRequests(t *testing.T) {
+ clientMux, serverMux := muxPair()
+ defer serverMux.Close()
+ defer clientMux.Close()
+
+ const numRequests = 50
+
+ serverDone := make(chan struct{})
+ go func() {
+ defer close(serverDone)
+ for r := range serverMux.incomingRequests {
+ if r.WantReply {
+ replyData := append([]byte("reply:"), r.Payload...)
+ r.Reply(true, replyData)
+ }
+ }
+ }()
+
+ var clientWg sync.WaitGroup
+ clientWg.Add(numRequests)
+
+ errCh := make(chan error, numRequests)
+
+ for i := range numRequests {
+ go func(id int) {
+ defer clientWg.Done()
+
+ payloadStr := fmt.Sprintf("req-%d", id)
+ payload := []byte(payloadStr)
+
+ // This call blocks until the globalSentMu is acquired.
+ // The mutex ensures that even with many concurrent attempts,
+ // the "drain" and "send" logic happens atomically per request.
+ ok, data, err := clientMux.SendRequest("echo", true, payload)
+ if err != nil {
+ errCh <- fmt.Errorf("req %d error: %v", id, err)
+ return
+ }
+ if !ok {
+ errCh <- fmt.Errorf("req %d failed (want success)", id)
+ return
+ }
+
+ expected := "reply:" + payloadStr
+ if string(data) != expected {
+ errCh <- fmt.Errorf("req %d mismatch: got %q, want %q", id, string(data), expected)
+ }
+ }(i)
+ }
+
+ clientWg.Wait()
+ close(errCh)
+
+ for err := range errCh {
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ clientMux.Close()
+ <-serverDone
+}
+
+func TestMuxGlobalResponseDroppedWhenIdle(t *testing.T) {
+ clientPipe, serverPipe := memPipe()
+ clientMux := newMux(clientPipe)
+ defer serverPipe.Close()
+ defer clientMux.Close()
+
+ errCh := make(chan error, 1)
+ go func() {
+ // Send a spurious response while no SendRequest is pending.
+ if err := serverPipe.writePacket(Marshal(globalRequestSuccessMsg{
+ Data: []byte("spurious"),
+ })); err != nil {
+ errCh <- fmt.Errorf("send spurious: %w", err)
+ return
+ }
+ // Follow with a global request; once the client observes this on
+ // incomingRequests, the mux loop has necessarily processed (and
+ // dropped) the prior spurious response.
+ if err := serverPipe.writePacket(Marshal(globalRequestMsg{
+ Type: "sync@example.com",
+ WantReply: false,
+ })); err != nil {
+ errCh <- fmt.Errorf("send sync request: %w", err)
+ return
+ }
+ errCh <- nil
+ }()
+
+ if err := <-errCh; err != nil {
+ t.Fatal(err)
+ }
+
+ req, ok := <-clientMux.incomingRequests
+ if !ok {
+ t.Fatal("incomingRequests closed unexpectedly")
+ }
+ if req.Type != "sync@example.com" {
+ t.Fatalf("unexpected sync request type %q", req.Type)
+ }
+
+ // The spurious response preceded the sync request, so by now the mux
+ // loop has processed it. The pending-gate must have caused it to be
+ // dropped rather than buffered.
+ if n := len(clientMux.globalResponses); n != 0 {
+ t.Fatalf("globalResponses buffer should be empty after idle drop, has %d entries", n)
+ }
+}
+
+func TestMuxStaleResponseDrained(t *testing.T) {
+ // Simulate a stale response sitting in globalResponses (e.g. a response
+ // that slipped in through the pending-gate on a prior SendRequest that
+ // exited without consuming it). The drain step in the next SendRequest
+ // must discard it so the caller receives the correct reply.
+ clientMux, serverMux := muxPair()
+ defer serverMux.Close()
+ defer clientMux.Close()
+
+ clientMux.globalResponses <- &globalRequestSuccessMsg{Data: []byte("stale")}
+
+ serverDone := make(chan struct{})
+ go func() {
+ defer close(serverDone)
+ for req := range serverMux.incomingRequests {
+ if req.WantReply {
+ req.Reply(true, append([]byte("reply:"), req.Payload...))
+ }
+ }
+ }()
+
+ ok, data, err := clientMux.SendRequest("test", true, []byte("hello"))
+ if err != nil {
+ t.Fatalf("request failed: %v", err)
+ }
+ if !ok {
+ t.Fatal("expected success response")
+ }
+ if string(data) != "reply:hello" {
+ t.Fatalf("got %q, want %q (drain did not remove stale response)", data, "reply:hello")
+ }
+
+ clientMux.Close()
+ <-serverDone
+}
+
+func TestMuxGlobalResponseAcceptedWhilePending(t *testing.T) {
+ // Positive control: when a SendRequest is actually pending, the
+ // response must be delivered (the gate is open).
+ clientMux, serverMux := muxPair()
+ defer serverMux.Close()
+ defer clientMux.Close()
+
+ serverDone := make(chan struct{})
+ go func() {
+ defer close(serverDone)
+ for req := range serverMux.incomingRequests {
+ if req.WantReply {
+ req.Reply(true, []byte("pong"))
+ }
+ }
+ }()
+
+ ok, data, err := clientMux.SendRequest("ping", true, nil)
+ if err != nil {
+ t.Fatalf("SendRequest: %v", err)
+ }
+ if !ok || string(data) != "pong" {
+ t.Fatalf("unexpected response: ok=%v data=%q", ok, data)
+ }
+
+ clientMux.Close()
+ <-serverDone
+}
+
+func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
+ // A malicious peer that spams channelRequestSuccess/Failure messages
+ // for an open, idle channel must not be able to stall the mux read
+ // loop by filling ch.msg. After the flood, the channel must still be
+ // usable: a subsequent legitimate SendRequest receives its reply.
+ clientMux, serverMux := muxPair()
+ defer serverMux.Close()
+ defer clientMux.Close()
+
+ serverRes := make(chan *channel, 1)
+ go func() {
+ newCh, ok := <-serverMux.incomingChannels
+ if !ok {
+ close(serverRes)
+ return
+ }
+ c, _, err := newCh.Accept()
+ if err != nil {
+ close(serverRes)
+ return
+ }
+ serverRes <- c.(*channel)
+ }()
+
+ clientCh, err := clientMux.openChannel("chan", nil)
+ if err != nil {
+ t.Fatalf("openChannel: %v", err)
+ }
+ serverCh := <-serverRes
+ if serverCh == nil {
+ t.Fatal("server did not accept channel")
+ }
+
+ // Spam many unsolicited success/failure responses. More than chanSize
+ // to ensure ch.msg would overflow without the pending-gate.
+ const spam = chanSize * 4
+ done := make(chan error, 1)
+ go func() {
+ for i := range spam {
+ if err := serverCh.ackRequest(i%2 == 0); err != nil {
+ done <- fmt.Errorf("ackRequest %d: %w", i, err)
+ return
+ }
+ }
+ // Echo any legitimate request back.
+ for req := range serverCh.incomingRequests {
+ if req.WantReply {
+ if err := req.Reply(true, append([]byte("reply:"), req.Payload...)); err != nil {
+ done <- fmt.Errorf("reply: %w", err)
+ return
+ }
+ }
+ }
+ done <- nil
+ }()
+
+ // If the flood had wedged the mux loop, this SendRequest would never
+ // receive a reply.
+ ok, err := clientCh.SendRequest("ping", true, []byte("hello"))
+ if err != nil {
+ t.Fatalf("SendRequest: %v", err)
+ }
+ if !ok {
+ t.Fatal("expected success reply")
+ }
+
+ // Clean up so the server goroutine can exit.
+ clientCh.Close()
+ serverCh.Close()
+ if err := <-done; err != nil {
+ if !errors.Is(err, io.EOF) {
+ t.Fatal(err)
+ }
+ }
+}
+
+func TestChannelConcurrentRequests(t *testing.T) {
+ writer, reader, mux := channelPair(t)
+ defer writer.Close()
+ defer reader.Close()
+ defer mux.Close()
+
+ serverDone := make(chan struct{})
+ go func() {
+ defer close(serverDone)
+ for req := range writer.incomingRequests {
+ if req.WantReply {
+ req.Reply(true, append([]byte("reply:"), req.Payload...))
+ }
+ }
+ }()
+
+ const numRequests = 50
+ var wg sync.WaitGroup
+ wg.Add(numRequests)
+ errCh := make(chan error, numRequests)
+
+ for i := 0; i < numRequests; i++ {
+ go func(id int) {
+ defer wg.Done()
+ payload := []byte(fmt.Sprintf("req-%d", id))
+ ok, err := reader.SendRequest("echo", true, payload)
+ if err != nil {
+ errCh <- fmt.Errorf("req %d: %v", id, err)
+ return
+ }
+ if !ok {
+ errCh <- fmt.Errorf("req %d: expected success", id)
+ }
+ }(i)
+ }
+
+ wg.Wait()
+ close(errCh)
+
+ for err := range errCh {
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ reader.Close()
+ writer.Close()
+ <-serverDone
+}
+
+func TestChannelResponseDroppedWhenIdle(t *testing.T) {
+ // A spurious response arriving while no SendRequest is pending must
+ // be dropped rather than buffered in ch.msg.
+ writer, reader, mux := channelPair(t)
+ defer writer.Close()
+ defer reader.Close()
+ defer mux.Close()
+
+ // Server sends an unsolicited reply, then a request so we can
+ // synchronise: once the client observes the request, the mux loop has
+ // necessarily processed (and dropped) the prior spurious reply.
+ errCh := make(chan error, 1)
+ go func() {
+ if err := writer.ackRequest(true); err != nil {
+ errCh <- err
+ return
+ }
+ if _, err := writer.SendRequest("sync", false, nil); err != nil {
+ errCh <- err
+ return
+ }
+ errCh <- nil
+ }()
+
+ req := <-reader.incomingRequests
+ if req.Type != "sync" {
+ t.Fatalf("unexpected request type %q", req.Type)
+ }
+
+ if n := len(reader.msg); n != 0 {
+ t.Fatalf("ch.msg should be empty after idle drop, has %d entries", n)
+ }
+
+ if err := <-errCh; err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestChannelStaleResponseDrained(t *testing.T) {
+ // Simulate a stale response sitting in ch.msg (e.g. a response that
+ // slipped through the pending-gate on a prior SendRequest that exited
+ // without consuming it). The drain step in the next SendRequest must
+ // discard it so the caller receives the correct reply.
+ writer, reader, mux := channelPair(t)
+ defer writer.Close()
+ defer reader.Close()
+ defer mux.Close()
+
+ reader.msg <- &channelRequestSuccessMsg{PeersID: reader.remoteId}
+
+ serverDone := make(chan struct{})
+ go func() {
+ defer close(serverDone)
+ for req := range writer.incomingRequests {
+ if req.WantReply {
+ req.Reply(false, append([]byte("nack:"), req.Payload...))
+ }
+ }
+ }()
+
+ ok, err := reader.SendRequest("test", true, []byte("hello"))
+ if err != nil {
+ t.Fatalf("SendRequest: %v", err)
+ }
+ // If the stale success had been consumed, ok would be true.
+ if ok {
+ t.Fatal("got stale success response; drain did not remove it")
+ }
+
+ reader.Close()
+ writer.Close()
+ <-serverDone
+}
+
+func TestChannelResponseAcceptedWhilePending(t *testing.T) {
+ // Positive control: when a SendRequest is actually pending, the
+ // response must be delivered (the gate is open).
+ writer, reader, mux := channelPair(t)
+ defer writer.Close()
+ defer reader.Close()
+ defer mux.Close()
+
+ serverDone := make(chan struct{})
+ go func() {
+ defer close(serverDone)
+ for req := range writer.incomingRequests {
+ if req.WantReply {
+ req.Reply(true, nil)
+ }
+ }
+ }()
+
+ ok, err := reader.SendRequest("ping", true, nil)
+ if err != nil {
+ t.Fatalf("SendRequest: %v", err)
+ }
+ if !ok {
+ t.Fatal("expected success")
+ }
+
+ reader.Close()
+ writer.Close()
+ <-serverDone
+}
diff --git a/local_crypto_patch/contents/ssh/server.go b/local_crypto_patch/contents/ssh/server.go
new file mode 100644
index 0000000000..0192a67503
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/server.go
@@ -0,0 +1,1056 @@
+// 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". In general the Go
+ // SSH library does not act on extensions and it is up to server
+ // implementations to honor them; extensions can also be used to pass data
+ // from the authentication callbacks to the server application layer.
+ //
+ // The one extension acted upon by this library is "no-touch-required",
+ // which applies only to security-key public keys
+ // (sk-ecdsa-sha2-nistp256@openssh.com and sk-ssh-ed25519@openssh.com).
+ // When present, it waives the default requirement that SK signatures
+ // assert user presence (i.e. a physical touch of the authenticator)
+ // during signature verification.
+ 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
+}
+
+// noTouchRequiredExtension is the extension name used by OpenSSH in
+// authorized_keys options and certificate extensions to mark keys
+// whose signatures do not need to assert user presence (touch). See
+// ssh-keygen(1) and sshd(8).
+const noTouchRequiredExtension = "no-touch-required"
+
+// noTouchAllowed reports whether the user presence requirement on
+// SK signatures should be waived for this authentication attempt. The
+// requirement is waived when the "no-touch-required" extension is
+// present either in the Permissions returned by the auth callback
+// (authorized_keys-level opt-out) or in the certificate's own
+// Extensions (CA-level opt-out), matching OpenSSH behavior. OpenSSH
+// reads the per-key opt-out only from cert Extensions and
+// authorized_keys options (never from CriticalOptions); we follow the
+// same rule.
+func noTouchAllowed(pubKey PublicKey, perms *Permissions) bool {
+ if perms != nil {
+ if _, ok := perms.Extensions[noTouchRequiredExtension]; ok {
+ return true
+ }
+ }
+ if cert, ok := pubKey.(*Certificate); ok {
+ if _, ok := cert.Extensions[noTouchRequiredExtension]; ok {
+ return true
+ }
+ }
+ return false
+}
+
+// skKeyWithoutUP returns a PublicKey equivalent to pubKey but whose
+// Verify accepts SK signatures with the user-presence flag clear. If
+// pubKey is not (and does not wrap) an SK key, pubKey is returned
+// unchanged. The returned value never mutates pubKey: for SK keys a
+// shallow copy is made so that the noTouchRequired flag is set only on
+// the clone.
+//
+// The implementation is iterative rather than recursive. When pubKey
+// is a *Certificate we unwrap exactly one level to look at the inner
+// key. The SSH cert format forbids Certificate.Key from being another
+// Certificate (parseCert rejects it), but nothing stops callers from
+// constructing such a value directly in Go; a recursive descent could
+// otherwise be driven to unbounded depth by a hand-crafted or cyclic
+// Certificate. A malformed input of that shape simply returns
+// unchanged here.
+func skKeyWithoutUP(pubKey PublicKey) PublicKey {
+ cert, isCert := pubKey.(*Certificate)
+ target := pubKey
+ if isCert {
+ target = cert.Key
+ }
+ var cloned PublicKey
+ switch k := target.(type) {
+ case *skECDSAPublicKey:
+ c := *k
+ c.noTouchRequired = true
+ cloned = &c
+ case *skEd25519PublicKey:
+ c := *k
+ c.noTouchRequired = true
+ cloned = &c
+ default:
+ // Not an SK key (or a pathological *Certificate wrapping
+ // another *Certificate): pubKey is already usable for Verify.
+ return pubKey
+ }
+ if !isCert {
+ return cloned
+ }
+ c := *cert
+ c.Key = cloned
+ return &c
+}
+
+// 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. These are the permissions from the final,
+ // successful authentication method. Permissions returned by callbacks that
+ // return PartialSuccessError are not preserved and must be nil.
+ 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)
+ // pubKey is reused below for VerifiedPublicKeyCallback and
+ // must remain the key as presented by the client; derive a
+ // separate value for Verify that carries any applicable
+ // no-touch-required opt-out.
+ pubKeyForVerify := pubKey
+ if noTouchAllowed(pubKey, candidate.perms) {
+ pubKeyForVerify = skKeyWithoutUP(pubKey)
+ }
+ if err := pubKeyForVerify.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)
+ }
+ if authErr == nil && perms != nil && perms.CriticalOptions != nil {
+ if saco := perms.CriticalOptions[sourceAddressCriticalOption]; saco != "" {
+ if err := checkSourceAddress(s.RemoteAddr(), saco); err != nil {
+ authErr = err
+ }
+ }
+ }
+ }
+ 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 {
+ // Permissions are not preserved between authentication steps. To
+ // avoid confusion about the final state of the connection, we
+ // disallow returning non-nil Permissions combined with
+ // PartialSuccessError.
+ if perms != nil {
+ return nil, errors.New("ssh: permissions must be nil when returning PartialSuccessError")
+ }
+ // 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/local_crypto_patch/contents/ssh/server_multi_auth_test.go b/local_crypto_patch/contents/ssh/server_multi_auth_test.go
new file mode 100644
index 0000000000..3b39802437
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/server_multi_auth_test.go
@@ -0,0 +1,412 @@
+// 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 (
+ "bytes"
+ "errors"
+ "fmt"
+ "strings"
+ "testing"
+)
+
+func doClientServerAuth(t *testing.T, serverConfig *ServerConfig, clientConfig *ClientConfig) ([]error, error) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ var serverAuthErrors []error
+
+ serverConfig.AddHostKey(testSigners["rsa"])
+ serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
+ serverAuthErrors = append(serverAuthErrors, err)
+ }
+ go newServer(c1, serverConfig)
+ c, _, _, err := NewClientConn(c2, "", clientConfig)
+ if err == nil {
+ c.Close()
+ }
+ return serverAuthErrors, err
+}
+
+func TestMultiStepAuth(t *testing.T) {
+ // This user can login with password, public key or public key + password.
+ username := "testuser"
+ // This user can login with public key + password only.
+ usernameSecondFactor := "testuser_second_factor"
+ errPwdAuthFailed := errors.New("password auth failed")
+ errWrongSequence := errors.New("wrong sequence")
+
+ serverConfig := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if conn.User() == usernameSecondFactor {
+ return nil, errWrongSequence
+ }
+ if conn.User() == username && string(password) == clientPassword {
+ return nil, nil
+ }
+ return nil, errPwdAuthFailed
+ },
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ if conn.User() == usernameSecondFactor {
+ return nil, &PartialSuccessError{
+ Next: ServerAuthCallbacks{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if string(password) == clientPassword {
+ return nil, nil
+ }
+ return nil, errPwdAuthFailed
+ },
+ },
+ }
+ }
+ return nil, nil
+ }
+ return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
+ },
+ }
+
+ clientConfig := &ClientConfig{
+ User: usernameSecondFactor,
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ serverAuthErrors, err := doClientServerAuth(t, serverConfig, clientConfig)
+ if err != nil {
+ t.Fatalf("client login error: %s", err)
+ }
+
+ // The error sequence is:
+ // - no auth passed yet
+ // - partial success
+ // - nil
+ if len(serverAuthErrors) != 3 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
+ t.Fatalf("expected partial success error, got: %v", serverAuthErrors[1])
+ }
+ // Now test a wrong sequence.
+ clientConfig.Auth = []AuthMethod{
+ Password(clientPassword),
+ PublicKeys(testSigners["rsa"]),
+ }
+
+ serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err == nil {
+ t.Fatal("client login with wrong sequence must fail")
+ }
+ // The error sequence is:
+ // - no auth passed yet
+ // - wrong sequence
+ // - partial success
+ if len(serverAuthErrors) != 3 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if serverAuthErrors[1] != errWrongSequence {
+ t.Fatal("server not returned wrong sequence")
+ }
+ if _, ok := serverAuthErrors[2].(*PartialSuccessError); !ok {
+ t.Fatalf("expected partial success error, got: %v", serverAuthErrors[2])
+ }
+ // Now test using a correct sequence but a wrong password before the right
+ // one.
+ n := 0
+ passwords := []string{"WRONG", "WRONG", clientPassword}
+ clientConfig.Auth = []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ RetryableAuthMethod(PasswordCallback(func() (string, error) {
+ p := passwords[n]
+ n++
+ return p, nil
+ }), 3),
+ }
+
+ serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err != nil {
+ t.Fatalf("client login error: %s", err)
+ }
+ // The error sequence is:
+ // - no auth passed yet
+ // - partial success
+ // - wrong password
+ // - wrong password
+ // - nil
+ if len(serverAuthErrors) != 5 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
+ t.Fatal("server not returned partial success")
+ }
+ if serverAuthErrors[2] != errPwdAuthFailed {
+ t.Fatal("server not returned password authentication failed")
+ }
+ if serverAuthErrors[3] != errPwdAuthFailed {
+ t.Fatal("server not returned password authentication failed")
+ }
+ // Only password authentication should fail.
+ clientConfig.Auth = []AuthMethod{
+ Password(clientPassword),
+ }
+
+ serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err == nil {
+ t.Fatal("client login with password only must fail")
+ }
+ // The error sequence is:
+ // - no auth passed yet
+ // - wrong sequence
+ if len(serverAuthErrors) != 2 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if serverAuthErrors[1] != errWrongSequence {
+ t.Fatal("server not returned wrong sequence")
+ }
+
+ // Only public key authentication should fail.
+ clientConfig.Auth = []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ }
+
+ serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err == nil {
+ t.Fatal("client login with public key only must fail")
+ }
+ // The error sequence is:
+ // - no auth passed yet
+ // - partial success
+ if len(serverAuthErrors) != 2 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
+ t.Fatal("server not returned partial success")
+ }
+
+ // Public key and wrong password.
+ clientConfig.Auth = []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ Password("WRONG"),
+ }
+
+ serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err == nil {
+ t.Fatal("client login with wrong password after public key must fail")
+ }
+ // The error sequence is:
+ // - no auth passed yet
+ // - partial success
+ // - password auth failed
+ if len(serverAuthErrors) != 3 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
+ t.Fatal("server not returned partial success")
+ }
+ if serverAuthErrors[2] != errPwdAuthFailed {
+ t.Fatal("server not returned password authentication failed")
+ }
+
+ // Public key, public key again and then correct password. Public key
+ // authentication is attempted only once because the partial success error
+ // returns only "password" as the allowed authentication method.
+ clientConfig.Auth = []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ PublicKeys(testSigners["rsa"]),
+ Password(clientPassword),
+ }
+
+ serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err != nil {
+ t.Fatalf("client login error: %s", err)
+ }
+ // The error sequence is:
+ // - no auth passed yet
+ // - partial success
+ // - nil
+ if len(serverAuthErrors) != 3 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if _, ok := serverAuthErrors[1].(*PartialSuccessError); !ok {
+ t.Fatal("server not returned partial success")
+ }
+
+ // The unrestricted username can do anything
+ clientConfig = &ClientConfig{
+ User: username,
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ _, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err != nil {
+ t.Fatalf("unrestricted client login error: %s", err)
+ }
+
+ clientConfig = &ClientConfig{
+ User: username,
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ _, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err != nil {
+ t.Fatalf("unrestricted client login error: %s", err)
+ }
+
+ clientConfig = &ClientConfig{
+ User: username,
+ Auth: []AuthMethod{
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ _, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err != nil {
+ t.Fatalf("unrestricted client login error: %s", err)
+ }
+}
+
+func TestDynamicAuthCallbacks(t *testing.T) {
+ user1 := "user1"
+ user2 := "user2"
+ errInvalidCredentials := errors.New("invalid credentials")
+
+ serverConfig := &ServerConfig{
+ NoClientAuth: true,
+ NoClientAuthCallback: func(conn ConnMetadata) (*Permissions, error) {
+ switch conn.User() {
+ case user1:
+ return nil, &PartialSuccessError{
+ Next: ServerAuthCallbacks{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if conn.User() == user1 && string(password) == clientPassword {
+ return nil, nil
+ }
+ return nil, errInvalidCredentials
+ },
+ },
+ }
+ case user2:
+ return nil, &PartialSuccessError{
+ Next: ServerAuthCallbacks{
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ if conn.User() == user2 {
+ return nil, nil
+ }
+ }
+ return nil, errInvalidCredentials
+ },
+ },
+ }
+ default:
+ return nil, errInvalidCredentials
+ }
+ },
+ }
+
+ clientConfig := &ClientConfig{
+ User: user1,
+ Auth: []AuthMethod{
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ serverAuthErrors, err := doClientServerAuth(t, serverConfig, clientConfig)
+ if err != nil {
+ t.Fatalf("client login error: %s", err)
+ }
+ // The error sequence is:
+ // - partial success
+ // - nil
+ if len(serverAuthErrors) != 2 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if _, ok := serverAuthErrors[0].(*PartialSuccessError); !ok {
+ t.Fatal("server not returned partial success")
+ }
+
+ clientConfig = &ClientConfig{
+ User: user2,
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err != nil {
+ t.Fatalf("client login error: %s", err)
+ }
+ // The error sequence is:
+ // - partial success
+ // - nil
+ if len(serverAuthErrors) != 2 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if _, ok := serverAuthErrors[0].(*PartialSuccessError); !ok {
+ t.Fatal("server not returned partial success")
+ }
+
+ // user1 cannot login with public key
+ clientConfig = &ClientConfig{
+ User: user1,
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err == nil {
+ t.Fatal("user1 login with public key must fail")
+ }
+ if !strings.Contains(err.Error(), "no supported methods remain") {
+ t.Errorf("got %v, expected 'no supported methods remain'", err)
+ }
+ if len(serverAuthErrors) != 1 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if _, ok := serverAuthErrors[0].(*PartialSuccessError); !ok {
+ t.Fatal("server not returned partial success")
+ }
+ // user2 cannot login with password
+ clientConfig = &ClientConfig{
+ User: user2,
+ Auth: []AuthMethod{
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ serverAuthErrors, err = doClientServerAuth(t, serverConfig, clientConfig)
+ if err == nil {
+ t.Fatal("user2 login with password must fail")
+ }
+ if !strings.Contains(err.Error(), "no supported methods remain") {
+ t.Errorf("got %v, expected 'no supported methods remain'", err)
+ }
+ if len(serverAuthErrors) != 1 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if _, ok := serverAuthErrors[0].(*PartialSuccessError); !ok {
+ t.Fatal("server not returned partial success")
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/server_test.go b/local_crypto_patch/contents/ssh/server_test.go
new file mode 100644
index 0000000000..2900b61a4d
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/server_test.go
@@ -0,0 +1,1072 @@
+// 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 ssh
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/sha256"
+ "errors"
+ "fmt"
+ "io"
+ "math/big"
+ "net"
+ "reflect"
+ "strings"
+ "sync/atomic"
+ "testing"
+ "time"
+)
+
+func TestClientAuthRestrictedPublicKeyAlgos(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ key Signer
+ wantError bool
+ }{
+ {"rsa", testSigners["rsa"], false},
+ {"dsa", testSigners["dsa"], true},
+ {"ed25519", testSigners["ed25519"], true},
+ } {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+ serverConf := &ServerConfig{
+ PublicKeyAuthAlgorithms: []string{KeyAlgoRSASHA256, KeyAlgoRSASHA512},
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ return nil, nil
+ },
+ }
+ serverConf.AddHostKey(testSigners["ecdsap256"])
+
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ NewServerConn(c1, serverConf)
+ }()
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(tt.key),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err != nil {
+ if !tt.wantError {
+ t.Errorf("%s: got unexpected error %q", tt.name, err.Error())
+ }
+ } else if tt.wantError {
+ t.Errorf("%s: succeeded, but want error", tt.name)
+ }
+ <-done
+ }
+}
+
+func TestMaxAuthTriesNoneMethod(t *testing.T) {
+ username := "testuser"
+ serverConfig := &ServerConfig{
+ MaxAuthTries: 2,
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if conn.User() == username && string(password) == clientPassword {
+ return nil, nil
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ }
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ var serverAuthErrors []error
+
+ serverConfig.AddHostKey(testSigners["rsa"])
+ serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
+ serverAuthErrors = append(serverAuthErrors, err)
+ }
+ go newServer(c1, serverConfig)
+
+ clientConfig := ClientConfig{
+ User: username,
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ clientConfig.SetDefaults()
+ // Our client will send 'none' auth only once, so we need to send the
+ // requests manually.
+ c := &connection{
+ sshConn: sshConn{
+ conn: c2,
+ user: username,
+ clientVersion: []byte(packageVersion),
+ },
+ }
+ c.serverVersion, err = exchangeVersions(c.sshConn.conn, c.clientVersion)
+ if err != nil {
+ t.Fatalf("unable to exchange version: %v", err)
+ }
+ c.transport = newClientTransport(
+ newTransport(c.sshConn.conn, clientConfig.Rand, true /* is client */),
+ c.clientVersion, c.serverVersion, &clientConfig, "", c.sshConn.RemoteAddr())
+ if err := c.transport.waitSession(); err != nil {
+ t.Fatalf("unable to wait session: %v", err)
+ }
+ c.sessionID = c.transport.getSessionID()
+ if err := c.transport.writePacket(Marshal(&serviceRequestMsg{serviceUserAuth})); err != nil {
+ t.Fatalf("unable to send ssh-userauth message: %v", err)
+ }
+ packet, err := c.transport.readPacket()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(packet) > 0 && packet[0] == msgExtInfo {
+ packet, err = c.transport.readPacket()
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ var serviceAccept serviceAcceptMsg
+ if err := Unmarshal(packet, &serviceAccept); err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i <= serverConfig.MaxAuthTries; i++ {
+ auth := new(noneAuth)
+ _, _, err := auth.auth(c.sessionID, clientConfig.User, c.transport, clientConfig.Rand, nil)
+ if i < serverConfig.MaxAuthTries {
+ if err != nil {
+ t.Fatal(err)
+ }
+ continue
+ }
+ if err == nil {
+ t.Fatal("client: got no error")
+ } else if !strings.Contains(err.Error(), "too many authentication failures") {
+ t.Fatalf("client: got unexpected error: %v", err)
+ }
+ }
+ if len(serverAuthErrors) != 3 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ for _, err := range serverAuthErrors {
+ if !errors.Is(err, ErrNoAuth) {
+ t.Errorf("go error: %v; want: %v", err, ErrNoAuth)
+ }
+ }
+}
+
+func TestMaxAuthTriesFirstNoneAuthErrorIgnored(t *testing.T) {
+ username := "testuser"
+ serverConfig := &ServerConfig{
+ MaxAuthTries: 1,
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if conn.User() == username && string(password) == clientPassword {
+ return nil, nil
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ }
+ clientConfig := &ClientConfig{
+ User: username,
+ Auth: []AuthMethod{
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ serverAuthErrors, err := doClientServerAuth(t, serverConfig, clientConfig)
+ if err != nil {
+ t.Fatalf("client login error: %s", err)
+ }
+ if len(serverAuthErrors) != 2 {
+ t.Fatalf("unexpected number of server auth errors: %v, errors: %+v", len(serverAuthErrors), serverAuthErrors)
+ }
+ if !errors.Is(serverAuthErrors[0], ErrNoAuth) {
+ t.Errorf("go error: %v; want: %v", serverAuthErrors[0], ErrNoAuth)
+ }
+ if serverAuthErrors[1] != nil {
+ t.Errorf("unexpected error: %v", serverAuthErrors[1])
+ }
+}
+
+func TestNewServerConnValidationErrors(t *testing.T) {
+ serverConf := &ServerConfig{
+ PublicKeyAuthAlgorithms: []string{CertAlgoRSAv01},
+ }
+ c := &markerConn{}
+ _, _, _, err := NewServerConn(c, serverConf)
+ if err == nil {
+ t.Fatal("NewServerConn with invalid public key auth algorithms succeeded")
+ }
+ if !c.isClosed() {
+ t.Fatal("NewServerConn with invalid public key auth algorithms left connection open")
+ }
+ if c.isUsed() {
+ t.Fatal("NewServerConn with invalid public key auth algorithms used connection")
+ }
+
+ serverConf = &ServerConfig{
+ Config: Config{
+ KeyExchanges: []string{KeyExchangeDHGEXSHA256},
+ },
+ }
+ c = &markerConn{}
+ _, _, _, err = NewServerConn(c, serverConf)
+ if err == nil {
+ t.Fatal("NewServerConn with unsupported key exchange succeeded")
+ }
+ if !c.isClosed() {
+ t.Fatal("NewServerConn with unsupported key exchange left connection open")
+ }
+ if c.isUsed() {
+ t.Fatal("NewServerConn with unsupported key exchange used connection")
+ }
+}
+
+func TestBannerError(t *testing.T) {
+ serverConfig := &ServerConfig{
+ BannerCallback: func(ConnMetadata) string {
+ return "banner from BannerCallback"
+ },
+ NoClientAuth: true,
+ NoClientAuthCallback: func(ConnMetadata) (*Permissions, error) {
+ err := &BannerError{
+ Err: errors.New("error from NoClientAuthCallback"),
+ Message: "banner from NoClientAuthCallback",
+ }
+ return nil, fmt.Errorf("wrapped: %w", err)
+ },
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ return &Permissions{}, nil
+ },
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ return nil, &BannerError{
+ Err: errors.New("error from PublicKeyCallback"),
+ Message: "banner from PublicKeyCallback",
+ }
+ },
+ KeyboardInteractiveCallback: func(conn ConnMetadata, client KeyboardInteractiveChallenge) (*Permissions, error) {
+ return nil, &BannerError{
+ Err: nil, // make sure that a nil inner error is allowed
+ Message: "banner from KeyboardInteractiveCallback",
+ }
+ },
+ }
+ serverConfig.AddHostKey(testSigners["rsa"])
+
+ var banners []string
+ clientConfig := &ClientConfig{
+ User: "test",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ KeyboardInteractive(func(name, instruction string, questions []string, echos []bool) ([]string, error) {
+ return []string{"letmein"}, nil
+ }),
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ BannerCallback: func(msg string) error {
+ banners = append(banners, msg)
+ return nil
+ },
+ }
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+ go newServer(c1, serverConfig)
+ c, _, _, err := NewClientConn(c2, "", clientConfig)
+ if err != nil {
+ t.Fatalf("client connection failed: %v", err)
+ }
+ defer c.Close()
+
+ wantBanners := []string{
+ "banner from BannerCallback",
+ "banner from NoClientAuthCallback",
+ "banner from PublicKeyCallback",
+ "banner from KeyboardInteractiveCallback",
+ }
+ if !reflect.DeepEqual(banners, wantBanners) {
+ t.Errorf("got banners:\n%q\nwant banners:\n%q", banners, wantBanners)
+ }
+}
+
+func TestPublicKeyCallbackLastSeen(t *testing.T) {
+ var lastSeenKey PublicKey
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+ serverConf := &ServerConfig{
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ lastSeenKey = key
+ fmt.Printf("seen %#v\n", key)
+ if _, ok := key.(*dsaPublicKey); !ok {
+ return nil, errors.New("nope")
+ }
+ return nil, nil
+ },
+ }
+ serverConf.AddHostKey(testSigners["ecdsap256"])
+
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ NewServerConn(c1, serverConf)
+ }()
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"], testSigners["dsa"], testSigners["ed25519"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ <-done
+
+ expectedPublicKey := testSigners["dsa"].PublicKey().Marshal()
+ lastSeenMarshalled := lastSeenKey.Marshal()
+ if !bytes.Equal(lastSeenMarshalled, expectedPublicKey) {
+ t.Errorf("unexpected key: got %#v, want %#v", lastSeenKey, testSigners["dsa"].PublicKey())
+ }
+}
+
+func TestPreAuthConnAndBanners(t *testing.T) {
+ testDone := make(chan struct{})
+ defer close(testDone)
+
+ authConnc := make(chan ServerPreAuthConn, 1)
+ serverConfig := &ServerConfig{
+ PreAuthConnCallback: func(c ServerPreAuthConn) {
+ t.Logf("got ServerPreAuthConn: %v", c)
+ authConnc <- c // for use later in the test
+ for _, s := range []string{"hello1", "hello2"} {
+ if err := c.SendAuthBanner(s); err != nil {
+ t.Errorf("failed to send banner %q: %v", s, err)
+ }
+ }
+ // Now start a goroutine to spam SendAuthBanner in hopes
+ // of hitting a race.
+ go func() {
+ for {
+ select {
+ case <-testDone:
+ return
+ default:
+ if err := c.SendAuthBanner("attempted-race"); err != nil && err != errSendBannerPhase {
+ t.Errorf("unexpected error from SendAuthBanner: %v", err)
+ }
+ time.Sleep(5 * time.Millisecond)
+ }
+ }
+ }()
+ },
+ NoClientAuth: true,
+ NoClientAuthCallback: func(ConnMetadata) (*Permissions, error) {
+ t.Logf("got NoClientAuthCallback")
+ return &Permissions{}, nil
+ },
+ }
+ serverConfig.AddHostKey(testSigners["rsa"])
+
+ var banners []string
+ clientConfig := &ClientConfig{
+ User: "test",
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ BannerCallback: func(msg string) error {
+ if msg != "attempted-race" {
+ banners = append(banners, msg)
+ }
+ return nil
+ },
+ }
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+ go newServer(c1, serverConfig)
+ c, _, _, err := NewClientConn(c2, "", clientConfig)
+ if err != nil {
+ t.Fatalf("client connection failed: %v", err)
+ }
+ defer c.Close()
+
+ wantBanners := []string{
+ "hello1",
+ "hello2",
+ }
+ if !reflect.DeepEqual(banners, wantBanners) {
+ t.Errorf("got banners:\n%q\nwant banners:\n%q", banners, wantBanners)
+ }
+
+ // Now that we're authenticated, verify that use of SendBanner
+ // is an error.
+ var bc ServerPreAuthConn
+ select {
+ case bc = <-authConnc:
+ default:
+ t.Fatal("expected ServerPreAuthConn")
+ }
+ if err := bc.SendAuthBanner("wrong-phase"); err == nil {
+ t.Error("unexpected success of SendAuthBanner after authentication")
+ } else if err != errSendBannerPhase {
+ t.Errorf("unexpected error: %v; want %v", err, errSendBannerPhase)
+ }
+}
+
+func TestVerifiedPublicKeyCallback(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ extraKey := "extra"
+ extraDataString := "just a string"
+
+ serverConf := &ServerConfig{
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ if permissions != nil && permissions.ExtraData != nil {
+ if !reflect.DeepEqual(map[any]any{extraKey: extraDataString}, permissions.ExtraData) {
+ t.Errorf("expected extra data: %v; got: %v", extraDataString, permissions.ExtraData)
+ }
+ } else {
+ t.Error("expected extra data is missing")
+ }
+ if signatureAlgorithm != KeyAlgoRSASHA256 {
+ t.Errorf("expected signature algorithm: %q; got: %q", KeyAlgoRSASHA256, signatureAlgorithm)
+ }
+ return permissions, nil
+ },
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ return &Permissions{ExtraData: map[any]any{extraKey: extraDataString}}, nil
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ conn, _, _, err := NewServerConn(c1, serverConf)
+ if err != nil {
+ t.Errorf("unexpected server error: %v", err)
+ }
+ if !reflect.DeepEqual(map[any]any{extraKey: extraDataString}, conn.Permissions.ExtraData) {
+ t.Errorf("expected extra data: %v; got: %v", extraDataString, conn.Permissions.ExtraData)
+ }
+ }()
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ <-done
+}
+
+func TestVerifiedPublicCallbackPartialSuccess(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, nil
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, &PartialSuccessError{
+ Next: ServerAuthCallbacks{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if string(password) == clientPassword {
+ return nil, nil
+ }
+ return nil, nil
+ },
+ },
+ }
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ go NewServerConn(c1, serverConf)
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err != nil {
+ t.Fatalf("client login error: %s", err)
+ }
+}
+
+func TestVerifiedPublicKeyCallbackPwdAndKey(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if string(password) == clientPassword {
+ return nil, &PartialSuccessError{
+ Next: ServerAuthCallbacks{
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, nil
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ },
+ }
+ }
+ return nil, errors.New("invalid credentials")
+
+ },
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, nil
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ Password(clientPassword),
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ go NewServerConn(c1, serverConf)
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err != nil {
+ t.Fatalf("client login error: %s", err)
+ }
+}
+
+func TestVerifiedPubKeyCallbackAuthMethods(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ return nil, nil
+ },
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, nil
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ go NewServerConn(c1, serverConf)
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err == nil {
+ t.Fatal("client login succeed with only VerifiedPublicKeyCallback defined")
+ }
+}
+
+func TestVerifiedPubKeyCallbackError(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ return nil, nil
+ },
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ return nil, errors.New("invalid credentials")
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ go NewServerConn(c1, serverConf)
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err == nil {
+ t.Fatal("client login succeed with VerifiedPublicKeyCallback returning an error")
+ }
+}
+
+func TestVerifiedPubKeyCallbackSourceAddress(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ return nil, nil
+ },
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ return &Permissions{
+ CriticalOptions: map[string]string{
+ sourceAddressCriticalOption: "192.168.99.99",
+ },
+ }, nil
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ go NewServerConn(c1, serverConf)
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err == nil {
+ t.Fatal("client login succeed with VerifiedPublicKeyCallback returning mismatching source-address")
+ }
+}
+
+func TestVerifiedPublicCallbackPartialSuccessBadUsage(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ // Returning PartialSuccessError is not permitted when
+ // VerifiedPublicKeyCallback is defined. This callback is
+ // invoked for both query requests and real authentications,
+ // while VerifiedPublicKeyCallback is only triggered if the
+ // client has proven control of the key.
+ return nil, &PartialSuccessError{
+ Next: ServerAuthCallbacks{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if string(password) == clientPassword {
+ return nil, nil
+ }
+ return nil, nil
+ },
+ },
+ }
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, &PartialSuccessError{
+ Next: ServerAuthCallbacks{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if string(password) == clientPassword {
+ return nil, nil
+ }
+ return nil, nil
+ },
+ },
+ }
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ go NewServerConn(c1, serverConf)
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err == nil {
+ t.Fatal("authentication succeeded with PartialSuccess returned from PublicKeyCallback and VerifiedPublicKeyCallback defined")
+ }
+}
+
+func TestVerifiedPublicKeyCallbackOnError(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ var verifiedCallbackCalled bool
+
+ serverConf := &ServerConfig{
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ verifiedCallbackCalled = true
+ return nil, nil
+ },
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ return nil, errors.New("invalid key")
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ NewServerConn(c1, serverConf)
+ }()
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err == nil {
+ t.Fatal("authentication should fail")
+ }
+ <-done
+ if verifiedCallbackCalled {
+ t.Error("VerifiedPublicKeyCallback called after PublicKeyCallback returned an error")
+ }
+}
+
+func TestVerifiedPublicKeyCallbackOnly(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ return nil, nil
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ NewServerConn(c1, serverConf)
+ }()
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err == nil {
+ t.Fatal("authentication succeeded with only VerifiedPublicKeyCallback defined")
+ }
+ <-done
+}
+
+func TestPartialSuccessWithNonNilPerms(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PublicKeyCallback: func(conn ConnMetadata, key PublicKey) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, nil
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ VerifiedPublicKeyCallback: func(conn ConnMetadata, key PublicKey, permissions *Permissions, signatureAlgorithm string) (*Permissions, error) {
+ if bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ // Intentionally return non-nil Permissions along with a
+ // PartialSuccessError. Since permissions are reset between
+ // authentication steps, this constitutes invalid library usage
+ // and the server is expected to reject the connection.
+ return &Permissions{Extensions: map[string]string{"permit-port-forwarding": ""}}, &PartialSuccessError{
+ Next: ServerAuthCallbacks{
+ PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
+ if string(password) == clientPassword {
+ return nil, nil
+ }
+ return nil, nil
+ },
+ },
+ }
+ }
+ return nil, errors.New("invalid credentials")
+ },
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+
+ clientConf := ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{
+ PublicKeys(testSigners["rsa"]),
+ Password(clientPassword),
+ },
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ go NewServerConn(c1, serverConf)
+
+ _, _, _, err = NewClientConn(c2, "", &clientConf)
+ if err == nil {
+ t.Fatal("authentication succeeded unexpectedly; server should have rejected non-nil Permissions combined with PartialSuccessError")
+ }
+}
+
+type markerConn struct {
+ closed uint32
+ used uint32
+}
+
+func (c *markerConn) isClosed() bool {
+ return atomic.LoadUint32(&c.closed) != 0
+}
+
+func (c *markerConn) isUsed() bool {
+ return atomic.LoadUint32(&c.used) != 0
+}
+
+func (c *markerConn) Close() error {
+ atomic.StoreUint32(&c.closed, 1)
+ return nil
+}
+
+func (c *markerConn) Read(b []byte) (n int, err error) {
+ atomic.StoreUint32(&c.used, 1)
+ if atomic.LoadUint32(&c.closed) != 0 {
+ return 0, net.ErrClosed
+ } else {
+ return 0, io.EOF
+ }
+}
+
+func (c *markerConn) Write(b []byte) (n int, err error) {
+ atomic.StoreUint32(&c.used, 1)
+ if atomic.LoadUint32(&c.closed) != 0 {
+ return 0, net.ErrClosed
+ } else {
+ return 0, io.ErrClosedPipe
+ }
+}
+
+func (*markerConn) LocalAddr() net.Addr { return nil }
+func (*markerConn) RemoteAddr() net.Addr { return nil }
+
+func (*markerConn) SetDeadline(t time.Time) error { return nil }
+func (*markerConn) SetReadDeadline(t time.Time) error { return nil }
+func (*markerConn) SetWriteDeadline(t time.Time) error { return nil }
+
+// skTestSigner is a Signer that produces SK-ECDSA signatures over
+// user-auth data, simulating a FIDO/U2F authenticator. The flags
+// byte (UP and other bits) is caller-controlled so tests can exercise
+// the server's user-presence enforcement and its opt-out paths. This
+// is test-only: the real Signer is hardware-backed.
+type skTestSigner struct {
+ priv *ecdsa.PrivateKey
+ pub PublicKey
+ flags byte
+ application string
+}
+
+func (s *skTestSigner) PublicKey() PublicKey { return s.pub }
+
+func (s *skTestSigner) Sign(r io.Reader, data []byte) (*Signature, error) {
+ h := sha256.New()
+ h.Write([]byte(s.application))
+ appDigest := h.Sum(nil)
+ h.Reset()
+ h.Write(data)
+ dataDigest := h.Sum(nil)
+ var counter uint32 = 1
+ blob := struct {
+ ApplicationDigest []byte `ssh:"rest"`
+ Flags byte
+ Counter uint32
+ MessageDigest []byte `ssh:"rest"`
+ }{appDigest, s.flags, counter, dataDigest}
+ h.Reset()
+ h.Write(Marshal(blob))
+ digest := h.Sum(nil)
+ x, y, err := ecdsa.Sign(r, s.priv, digest)
+ if err != nil {
+ return nil, err
+ }
+ return &Signature{
+ Format: KeyAlgoSKECDSA256,
+ Blob: Marshal(struct{ R, S *big.Int }{x, y}),
+ Rest: Marshal(struct {
+ Flags byte
+ Counter uint32
+ }{s.flags, counter}),
+ }, nil
+}
+
+// TestServerAuthSKUserPresence drives the full userAuthLoop with an SK
+// public-key client and verifies the server's wiring of the UP check
+// and its two opt-out paths: the per-key Permissions.Extensions route
+// and (via cert) the cert-level route. It also confirms that non-SK
+// clients are unaffected by the new code path.
+func TestServerAuthSKUserPresence(t *testing.T) {
+ userKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ skPub := &skECDSAPublicKey{application: "ssh:", PublicKey: userKey.PublicKey}
+
+ runAuth := func(t *testing.T, signer Signer, perms *Permissions) error {
+ t.Helper()
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverConf := &ServerConfig{
+ PublicKeyCallback: func(ConnMetadata, PublicKey) (*Permissions, error) {
+ return perms, nil
+ },
+ }
+ serverConf.AddHostKey(testSigners["ecdsa"])
+
+ serverErr := make(chan error, 1)
+ go func() {
+ _, _, _, err := NewServerConn(c1, serverConf)
+ serverErr <- err
+ }()
+
+ clientConf := &ClientConfig{
+ User: "user",
+ Auth: []AuthMethod{PublicKeys(signer)},
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ _, _, _, clientErr := NewClientConn(c2, "", clientConf)
+ <-serverErr
+ return clientErr
+ }
+
+ optOut := &Permissions{Extensions: map[string]string{noTouchRequiredExtension: ""}}
+
+ t.Run("UP=1, default perms accepts", func(t *testing.T) {
+ s := &skTestSigner{priv: userKey, pub: skPub, flags: flagUserPresence, application: "ssh:"}
+ if err := runAuth(t, s, nil); err != nil {
+ t.Errorf("expected auth to succeed: %v", err)
+ }
+ })
+ t.Run("UP=0, default perms rejects", func(t *testing.T) {
+ s := &skTestSigner{priv: userKey, pub: skPub, flags: 0, application: "ssh:"}
+ if err := runAuth(t, s, nil); err == nil {
+ t.Error("expected auth to fail with UP=0")
+ }
+ })
+ t.Run("UP=0, perms opt-out accepts", func(t *testing.T) {
+ s := &skTestSigner{priv: userKey, pub: skPub, flags: 0, application: "ssh:"}
+ if err := runAuth(t, s, optOut); err != nil {
+ t.Errorf("expected auth to succeed with opt-out: %v", err)
+ }
+ })
+ t.Run("UP=0, perms CriticalOptions does NOT opt out", func(t *testing.T) {
+ s := &skTestSigner{priv: userKey, pub: skPub, flags: 0, application: "ssh:"}
+ critOnly := &Permissions{CriticalOptions: map[string]string{noTouchRequiredExtension: ""}}
+ if err := runAuth(t, s, critOnly); err == nil {
+ t.Error("no-touch-required in CriticalOptions must not waive UP")
+ }
+ })
+ t.Run("non-SK RSA signer unaffected", func(t *testing.T) {
+ if err := runAuth(t, testSigners["rsa"], nil); err != nil {
+ t.Errorf("plain RSA auth must still work: %v", err)
+ }
+ })
+}
diff --git a/local_crypto_patch/contents/ssh/session.go b/local_crypto_patch/contents/ssh/session.go
new file mode 100644
index 0000000000..acef62259f
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/session_test.go b/local_crypto_patch/contents/ssh/session_test.go
new file mode 100644
index 0000000000..807a913e5a
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/session_test.go
@@ -0,0 +1,892 @@
+// 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 tests.
+
+import (
+ "bytes"
+ crypto_rand "crypto/rand"
+ "errors"
+ "io"
+ "math/rand"
+ "net"
+ "sync"
+ "testing"
+
+ "golang.org/x/crypto/ssh/terminal"
+)
+
+type serverType func(Channel, <-chan *Request, *testing.T)
+
+// dial constructs a new test server and returns a *ClientConn.
+func dial(handler serverType, t *testing.T) *Client {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ defer func() {
+ c1.Close()
+ wg.Done()
+ }()
+ conf := ServerConfig{
+ NoClientAuth: true,
+ }
+ conf.AddHostKey(testSigners["rsa"])
+
+ conn, chans, reqs, err := NewServerConn(c1, &conf)
+ if err != nil {
+ t.Errorf("Unable to handshake: %v", err)
+ return
+ }
+ wg.Add(1)
+ go func() {
+ DiscardRequests(reqs)
+ wg.Done()
+ }()
+
+ for newCh := range chans {
+ if newCh.ChannelType() != "session" {
+ newCh.Reject(UnknownChannelType, "unknown channel type")
+ continue
+ }
+
+ ch, inReqs, err := newCh.Accept()
+ if err != nil {
+ t.Errorf("Accept: %v", err)
+ continue
+ }
+ wg.Add(1)
+ go func() {
+ handler(ch, inReqs, t)
+ wg.Done()
+ }()
+ }
+ if err := conn.Wait(); err != io.EOF {
+ t.Logf("server exit reason: %v", err)
+ }
+ }()
+
+ config := &ClientConfig{
+ User: "testuser",
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+
+ conn, chans, reqs, err := NewClientConn(c2, "", config)
+ if err != nil {
+ t.Fatalf("unable to dial remote side: %v", err)
+ }
+
+ return NewClient(conn, chans, reqs)
+}
+
+// Test a simple string is returned to session.Stdout.
+func TestSessionShell(t *testing.T) {
+ conn := dial(shellHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+ stdout := new(bytes.Buffer)
+ session.Stdout = stdout
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %s", err)
+ }
+ if err := session.Wait(); err != nil {
+ t.Fatalf("Remote command did not exit cleanly: %v", err)
+ }
+ actual := stdout.String()
+ if actual != "golang" {
+ t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
+ }
+}
+
+// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
+
+// Test a simple string is returned via StdoutPipe.
+func TestSessionStdoutPipe(t *testing.T) {
+ conn := dial(shellHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+ stdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Fatalf("Unable to request StdoutPipe(): %v", err)
+ }
+ var buf bytes.Buffer
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %v", err)
+ }
+ done := make(chan bool, 1)
+ go func() {
+ if _, err := io.Copy(&buf, stdout); err != nil {
+ t.Errorf("Copy of stdout failed: %v", err)
+ }
+ done <- true
+ }()
+ if err := session.Wait(); err != nil {
+ t.Fatalf("Remote command did not exit cleanly: %v", err)
+ }
+ <-done
+ actual := buf.String()
+ if actual != "golang" {
+ t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
+ }
+}
+
+// Test that a simple string is returned via the Output helper,
+// and that stderr is discarded.
+func TestSessionOutput(t *testing.T) {
+ conn := dial(fixedOutputHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+
+ buf, err := session.Output("") // cmd is ignored by fixedOutputHandler
+ if err != nil {
+ t.Error("Remote command did not exit cleanly:", err)
+ }
+ w := "this-is-stdout."
+ g := string(buf)
+ if g != w {
+ t.Error("Remote command did not return expected string:")
+ t.Logf("want %q", w)
+ t.Logf("got %q", g)
+ }
+}
+
+// Test that both stdout and stderr are returned
+// via the CombinedOutput helper.
+func TestSessionCombinedOutput(t *testing.T) {
+ conn := dial(fixedOutputHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+
+ buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler
+ if err != nil {
+ t.Error("Remote command did not exit cleanly:", err)
+ }
+ const stdout = "this-is-stdout."
+ const stderr = "this-is-stderr."
+ g := string(buf)
+ if g != stdout+stderr && g != stderr+stdout {
+ t.Error("Remote command did not return expected string:")
+ t.Logf("want %q, or %q", stdout+stderr, stderr+stdout)
+ t.Logf("got %q", g)
+ }
+}
+
+// Test non-0 exit status is returned correctly.
+func TestExitStatusNonZero(t *testing.T) {
+ conn := dial(exitStatusNonZeroHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %v", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ e, ok := err.(*ExitError)
+ if !ok {
+ t.Fatalf("expected *ExitError but got %T", err)
+ }
+ if e.ExitStatus() != 15 {
+ t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
+ }
+}
+
+// Test 0 exit status is returned correctly.
+func TestExitStatusZero(t *testing.T) {
+ conn := dial(exitStatusZeroHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %v", err)
+ }
+ err = session.Wait()
+ if err != nil {
+ t.Fatalf("expected nil but got %v", err)
+ }
+}
+
+// Test exit signal and status are both returned correctly.
+func TestExitSignalAndStatus(t *testing.T) {
+ conn := dial(exitSignalAndStatusHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %v", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ e, ok := err.(*ExitError)
+ if !ok {
+ t.Fatalf("expected *ExitError but got %T", err)
+ }
+ if e.Signal() != "TERM" || e.ExitStatus() != 15 {
+ t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
+ }
+}
+
+// Test exit signal and status are both returned correctly.
+func TestKnownExitSignalOnly(t *testing.T) {
+ conn := dial(exitSignalHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %v", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ e, ok := err.(*ExitError)
+ if !ok {
+ t.Fatalf("expected *ExitError but got %T", err)
+ }
+ if e.Signal() != "TERM" || e.ExitStatus() != 143 {
+ t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
+ }
+}
+
+// Test exit signal and status are both returned correctly.
+func TestUnknownExitSignal(t *testing.T) {
+ conn := dial(exitSignalUnknownHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %v", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ e, ok := err.(*ExitError)
+ if !ok {
+ t.Fatalf("expected *ExitError but got %T", err)
+ }
+ if e.Signal() != "SYS" || e.ExitStatus() != 128 {
+ t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
+ }
+}
+
+func TestExitWithoutStatusOrSignal(t *testing.T) {
+ conn := dial(exitWithoutSignalOrStatus, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatalf("Unable to request new session: %v", err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %v", err)
+ }
+ err = session.Wait()
+ if err == nil {
+ t.Fatalf("expected command to fail but it didn't")
+ }
+ if _, ok := err.(*ExitMissingError); !ok {
+ t.Fatalf("got %T want *ExitMissingError", err)
+ }
+}
+
+// windowTestBytes is the number of bytes that we'll send to the SSH server.
+const windowTestBytes = 16000 * 200
+
+// TestServerWindow writes random data to the server. The server is expected to echo
+// the same data back, which is compared against the original.
+func TestServerWindow(t *testing.T) {
+ origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
+ io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes)
+ origBytes := origBuf.Bytes()
+
+ conn := dial(echoHandler, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer session.Close()
+
+ serverStdin, err := session.StdinPipe()
+ if err != nil {
+ t.Fatalf("StdinPipe failed: %v", err)
+ }
+
+ result := make(chan []byte)
+ go func() {
+ defer close(result)
+ echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
+ serverStdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Errorf("StdoutPipe failed: %v", err)
+ return
+ }
+ n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes)
+ if err != nil && err != io.EOF {
+ t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err)
+ }
+ result <- echoedBuf.Bytes()
+ }()
+
+ written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes)
+ if err != nil {
+ t.Errorf("failed to copy origBuf to serverStdin: %v", err)
+ } else if written != windowTestBytes {
+ t.Errorf("Wrote only %d of %d bytes to server", written, windowTestBytes)
+ }
+
+ echoedBytes := <-result
+
+ if !bytes.Equal(origBytes, echoedBytes) {
+ t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes))
+ }
+}
+
+// Verify the client can handle a keepalive packet from the server.
+func TestClientHandlesKeepalives(t *testing.T) {
+ conn := dial(channelKeepaliveSender, t)
+ defer conn.Close()
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer session.Close()
+ if err := session.Shell(); err != nil {
+ t.Fatalf("Unable to execute command: %v", err)
+ }
+ err = session.Wait()
+ if err != nil {
+ t.Fatalf("expected nil but got: %v", err)
+ }
+}
+
+type exitStatusMsg struct {
+ Status uint32
+}
+
+type exitSignalMsg struct {
+ Signal string
+ CoreDumped bool
+ Errmsg string
+ Lang string
+}
+
+func handleTerminalRequests(in <-chan *Request) {
+ for req := range in {
+ ok := false
+ switch req.Type {
+ case "shell":
+ ok = true
+ if len(req.Payload) > 0 {
+ // We don't accept any commands, only the default shell.
+ ok = false
+ }
+ case "env":
+ ok = true
+ }
+ req.Reply(ok, nil)
+ }
+}
+
+func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal {
+ term := terminal.NewTerminal(ch, prompt)
+ go handleTerminalRequests(in)
+ return term
+}
+
+func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ // this string is returned to stdout
+ shell := newServerShell(ch, in, "> ")
+ readLine(shell, t)
+ sendStatus(0, ch, t)
+}
+
+func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ shell := newServerShell(ch, in, "> ")
+ readLine(shell, t)
+ sendStatus(15, ch, t)
+}
+
+func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ shell := newServerShell(ch, in, "> ")
+ readLine(shell, t)
+ sendStatus(15, ch, t)
+ sendSignal("TERM", ch, t)
+}
+
+func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ shell := newServerShell(ch, in, "> ")
+ readLine(shell, t)
+ sendSignal("TERM", ch, t)
+}
+
+func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ shell := newServerShell(ch, in, "> ")
+ readLine(shell, t)
+ sendSignal("SYS", ch, t)
+}
+
+func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ shell := newServerShell(ch, in, "> ")
+ readLine(shell, t)
+}
+
+func shellHandler(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ // this string is returned to stdout
+ shell := newServerShell(ch, in, "golang")
+ readLine(shell, t)
+ sendStatus(0, ch, t)
+}
+
+// Ignores the command, writes fixed strings to stderr and stdout.
+// Strings are "this-is-stdout." and "this-is-stderr.".
+func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ _, err := ch.Read(nil)
+
+ req, ok := <-in
+ if !ok {
+ t.Fatalf("error: expected channel request, got: %#v", err)
+ return
+ }
+
+ // ignore request, always send some text
+ req.Reply(true, nil)
+
+ _, err = io.WriteString(ch, "this-is-stdout.")
+ if err != nil {
+ t.Fatalf("error writing on server: %v", err)
+ }
+ _, err = io.WriteString(ch.Stderr(), "this-is-stderr.")
+ if err != nil {
+ t.Fatalf("error writing on server: %v", err)
+ }
+ sendStatus(0, ch, t)
+}
+
+func readLine(shell *terminal.Terminal, t *testing.T) {
+ if _, err := shell.ReadLine(); err != nil && err != io.EOF {
+ t.Errorf("unable to read line: %v", err)
+ }
+}
+
+func sendStatus(status uint32, ch Channel, t *testing.T) {
+ msg := exitStatusMsg{
+ Status: status,
+ }
+ if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil {
+ t.Errorf("unable to send status: %v", err)
+ }
+}
+
+func sendSignal(signal string, ch Channel, t *testing.T) {
+ sig := exitSignalMsg{
+ Signal: signal,
+ CoreDumped: false,
+ Errmsg: "Process terminated",
+ Lang: "en-GB-oed",
+ }
+ if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil {
+ t.Errorf("unable to send signal: %v", err)
+ }
+}
+
+func discardHandler(ch Channel, t *testing.T) {
+ defer ch.Close()
+ io.Copy(io.Discard, ch)
+}
+
+func echoHandler(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil {
+ t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err)
+ }
+}
+
+// copyNRandomly copies n bytes from src to dst. It uses a variable, and random,
+// buffer size to exercise more code paths.
+func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) {
+ var (
+ buf = make([]byte, 32*1024)
+ written int
+ remaining = n
+ )
+ for remaining > 0 {
+ l := rand.Intn(1 << 15)
+ if remaining < l {
+ l = remaining
+ }
+ nr, er := src.Read(buf[:l])
+ nw, ew := dst.Write(buf[:nr])
+ remaining -= nw
+ written += nw
+ if ew != nil {
+ return written, ew
+ }
+ if nr != nw {
+ return written, io.ErrShortWrite
+ }
+ if er != nil && er != io.EOF {
+ return written, er
+ }
+ }
+ return written, nil
+}
+
+func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ shell := newServerShell(ch, in, "> ")
+ readLine(shell, t)
+ if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil {
+ t.Errorf("unable to send channel keepalive request: %v", err)
+ }
+ sendStatus(0, ch, t)
+}
+
+func TestClientWriteEOF(t *testing.T) {
+ conn := dial(simpleEchoHandler, t)
+ defer conn.Close()
+
+ session, err := conn.NewSession()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer session.Close()
+ stdin, err := session.StdinPipe()
+ if err != nil {
+ t.Fatalf("StdinPipe failed: %v", err)
+ }
+ stdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Fatalf("StdoutPipe failed: %v", err)
+ }
+
+ data := []byte(`0000`)
+ _, err = stdin.Write(data)
+ if err != nil {
+ t.Fatalf("Write failed: %v", err)
+ }
+ stdin.Close()
+
+ res, err := io.ReadAll(stdout)
+ if err != nil {
+ t.Fatalf("Read failed: %v", err)
+ }
+
+ if !bytes.Equal(data, res) {
+ t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res)
+ }
+}
+
+func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) {
+ defer ch.Close()
+ data, err := io.ReadAll(ch)
+ if err != nil {
+ t.Errorf("handler read error: %v", err)
+ }
+ _, err = ch.Write(data)
+ if err != nil {
+ t.Errorf("handler write error: %v", err)
+ }
+}
+
+func TestSessionID(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serverID := make(chan []byte, 1)
+ clientID := make(chan []byte, 1)
+
+ serverConf := &ServerConfig{
+ NoClientAuth: true,
+ }
+ serverConf.AddHostKey(testSigners["ecdsa"])
+ clientConf := &ClientConfig{
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ User: "user",
+ }
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+
+ srvErrCh := make(chan error, 1)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ conn, chans, reqs, err := NewServerConn(c1, serverConf)
+ srvErrCh <- err
+ if err != nil {
+ return
+ }
+ serverID <- conn.SessionID()
+ wg.Add(1)
+ go func() {
+ DiscardRequests(reqs)
+ wg.Done()
+ }()
+ for ch := range chans {
+ ch.Reject(Prohibited, "")
+ }
+ }()
+
+ cliErrCh := make(chan error, 1)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
+ cliErrCh <- err
+ if err != nil {
+ return
+ }
+ clientID <- conn.SessionID()
+ wg.Add(1)
+ go func() {
+ DiscardRequests(reqs)
+ wg.Done()
+ }()
+ for ch := range chans {
+ ch.Reject(Prohibited, "")
+ }
+ }()
+
+ if err := <-srvErrCh; err != nil {
+ t.Fatalf("server handshake: %v", err)
+ }
+
+ if err := <-cliErrCh; err != nil {
+ t.Fatalf("client handshake: %v", err)
+ }
+
+ s := <-serverID
+ c := <-clientID
+ if bytes.Compare(s, c) != 0 {
+ t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
+ } else if len(s) == 0 {
+ t.Errorf("client and server SessionID were empty.")
+ }
+}
+
+type noReadConn struct {
+ readSeen bool
+ net.Conn
+}
+
+func (c *noReadConn) Close() error {
+ return nil
+}
+
+func (c *noReadConn) Read(b []byte) (int, error) {
+ c.readSeen = true
+ return 0, errors.New("noReadConn error")
+}
+
+func TestInvalidServerConfiguration(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ serveConn := noReadConn{Conn: c1}
+ serverConf := &ServerConfig{}
+
+ NewServerConn(&serveConn, serverConf)
+ if serveConn.readSeen {
+ t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing host key")
+ }
+
+ serverConf.AddHostKey(testSigners["ecdsa"])
+
+ NewServerConn(&serveConn, serverConf)
+ if serveConn.readSeen {
+ t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method")
+ }
+}
+
+func TestHostKeyAlgorithms(t *testing.T) {
+ serverConf := &ServerConfig{
+ NoClientAuth: true,
+ }
+ serverConf.AddHostKey(testSigners["rsa"])
+ serverConf.AddHostKey(testSigners["ecdsa"])
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ connect := func(clientConf *ClientConfig, want string) {
+ var alg string
+ clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error {
+ alg = key.Type()
+ return nil
+ }
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ wg.Add(1)
+ go func() {
+ NewServerConn(c1, serverConf)
+ wg.Done()
+ }()
+ _, _, _, err = NewClientConn(c2, "", clientConf)
+ if err != nil {
+ t.Fatalf("NewClientConn: %v", err)
+ }
+ if alg != want {
+ t.Errorf("selected key algorithm %s, want %s", alg, want)
+ }
+ }
+
+ // By default, we get the preferred algorithm, which is ECDSA 256.
+
+ clientConf := &ClientConfig{
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ }
+ connect(clientConf, KeyAlgoECDSA256)
+
+ // Client asks for RSA explicitly.
+ clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA}
+ connect(clientConf, KeyAlgoRSA)
+
+ // Client asks for RSA-SHA2-512 explicitly.
+ clientConf.HostKeyAlgorithms = []string{KeyAlgoRSASHA512}
+ // We get back an "ssh-rsa" key but the verification happened
+ // with an RSA-SHA2-512 signature.
+ connect(clientConf, KeyAlgoRSA)
+
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ wg.Add(1)
+ go func() {
+ NewServerConn(c1, serverConf)
+ wg.Done()
+ }()
+ clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"}
+ _, _, _, err = NewClientConn(c2, "", clientConf)
+ if err == nil {
+ t.Fatal("succeeded connecting with unknown hostkey algorithm")
+ }
+}
+
+func TestServerClientAuthCallback(t *testing.T) {
+ c1, c2, err := netPipe()
+ if err != nil {
+ t.Fatalf("netPipe: %v", err)
+ }
+ defer c1.Close()
+ defer c2.Close()
+
+ userCh := make(chan string, 1)
+
+ serverConf := &ServerConfig{
+ NoClientAuth: true,
+ NoClientAuthCallback: func(conn ConnMetadata) (*Permissions, error) {
+ userCh <- conn.User()
+ return nil, nil
+ },
+ }
+ const someUsername = "some-username"
+
+ serverConf.AddHostKey(testSigners["ecdsa"])
+ clientConf := &ClientConfig{
+ HostKeyCallback: InsecureIgnoreHostKey(),
+ User: someUsername,
+ }
+
+ var wg sync.WaitGroup
+ t.Cleanup(wg.Wait)
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ _, chans, reqs, err := NewServerConn(c1, serverConf)
+ if err != nil {
+ t.Errorf("server handshake: %v", err)
+ userCh <- "error"
+ return
+ }
+ wg.Add(1)
+ go func() {
+ DiscardRequests(reqs)
+ wg.Done()
+ }()
+ for ch := range chans {
+ ch.Reject(Prohibited, "")
+ }
+ }()
+
+ conn, _, _, err := NewClientConn(c2, "", clientConf)
+ if err != nil {
+ t.Fatalf("client handshake: %v", err)
+ return
+ }
+ conn.Close()
+
+ got := <-userCh
+ if got != someUsername {
+ t.Errorf("username = %q; want %q", got, someUsername)
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/ssh_gss.go b/local_crypto_patch/contents/ssh/ssh_gss.go
new file mode 100644
index 0000000000..a6249a1227
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/ssh_gss_test.go b/local_crypto_patch/contents/ssh/ssh_gss_test.go
new file mode 100644
index 0000000000..9e3ea8c22c
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/ssh_gss_test.go
@@ -0,0 +1,140 @@
+package ssh
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestParseGSSAPIPayload(t *testing.T) {
+ payload := []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02}
+ res, err := parseGSSAPIPayload(payload)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ok := res.OIDS[0].Equal(krb5Mesh); !ok {
+ t.Fatalf("got %v, want %v", res, krb5Mesh)
+ }
+}
+
+func TestParseDubiousGSSAPIPayload(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ payload []byte
+ wanterr bool
+ }{
+ {
+ "num mechanisms is unrealistic",
+ []byte{0xFF, 0x00, 0x00, 0xFF,
+ 0x00, 0x00, 0x00, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02},
+ true,
+ },
+ {
+ "num mechanisms greater than payload",
+ []byte{0x00, 0x00, 0x00, 0x40, // 64, |rest| too small
+ 0x00, 0x00, 0x00, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02},
+ true,
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ _, err := parseGSSAPIPayload(tc.payload)
+ if tc.wanterr && err == nil {
+ t.Errorf("got nil, want error")
+ }
+ if !tc.wanterr && err != nil {
+ t.Errorf("got %v, want nil", err)
+ }
+ })
+ }
+}
+
+func TestBuildMIC(t *testing.T) {
+ sessionID := []byte{134, 180, 134, 194, 62, 145, 171, 82, 119, 149, 254, 196, 125, 173, 177, 145, 187, 85, 53,
+ 183, 44, 150, 219, 129, 166, 195, 19, 33, 209, 246, 175, 121}
+ username := "testuser"
+ service := "ssh-connection"
+ authMethod := "gssapi-with-mic"
+ expected := []byte{0, 0, 0, 32, 134, 180, 134, 194, 62, 145, 171, 82, 119, 149, 254, 196, 125, 173, 177, 145, 187, 85, 53, 183, 44, 150, 219, 129, 166, 195, 19, 33, 209, 246, 175, 121, 50, 0, 0, 0, 8, 116, 101, 115, 116, 117, 115, 101, 114, 0, 0, 0, 14, 115, 115, 104, 45, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 0, 0, 0, 15, 103, 115, 115, 97, 112, 105, 45, 119, 105, 116, 104, 45, 109, 105, 99}
+ result := buildMIC(string(sessionID), username, service, authMethod)
+ if string(result) != string(expected) {
+ t.Fatalf("buildMic: got %v, want %v", result, expected)
+ }
+}
+
+type exchange struct {
+ outToken string
+ expectedToken string
+}
+
+type FakeClient struct {
+ exchanges []*exchange
+ round int
+ mic []byte
+ maxRound int
+}
+
+func (f *FakeClient) InitSecContext(target string, token []byte, isGSSDelegCreds bool) (outputToken []byte, needContinue bool, err error) {
+ if token == nil {
+ if f.exchanges[f.round].expectedToken != "" {
+ err = fmt.Errorf("got empty token, want %q", f.exchanges[f.round].expectedToken)
+ } else {
+ outputToken = []byte(f.exchanges[f.round].outToken)
+ }
+ } else {
+ if string(token) != string(f.exchanges[f.round].expectedToken) {
+ err = fmt.Errorf("got %q, want token %q", token, f.exchanges[f.round].expectedToken)
+ } else {
+ outputToken = []byte(f.exchanges[f.round].outToken)
+ }
+ }
+ f.round++
+ needContinue = f.round < f.maxRound
+ return
+}
+
+func (f *FakeClient) GetMIC(micField []byte) ([]byte, error) {
+ return f.mic, nil
+}
+
+func (f *FakeClient) DeleteSecContext() error {
+ return nil
+}
+
+type FakeServer struct {
+ exchanges []*exchange
+ round int
+ expectedMIC []byte
+ srcName string
+ maxRound int
+}
+
+func (f *FakeServer) AcceptSecContext(token []byte) (outputToken []byte, srcName string, needContinue bool, err error) {
+ if token == nil {
+ if f.exchanges[f.round].expectedToken != "" {
+ err = fmt.Errorf("got empty token, want %q", f.exchanges[f.round].expectedToken)
+ } else {
+ outputToken = []byte(f.exchanges[f.round].outToken)
+ }
+ } else {
+ if string(token) != string(f.exchanges[f.round].expectedToken) {
+ err = fmt.Errorf("got %q, want token %q", token, f.exchanges[f.round].expectedToken)
+ } else {
+ outputToken = []byte(f.exchanges[f.round].outToken)
+ }
+ }
+ f.round++
+ needContinue = f.round < f.maxRound
+ srcName = f.srcName
+ return
+}
+
+func (f *FakeServer) VerifyMIC(micField []byte, micToken []byte) error {
+ if string(micToken) != string(f.expectedMIC) {
+ return fmt.Errorf("got MICToken %q, want %q", micToken, f.expectedMIC)
+ }
+ return nil
+}
+
+func (f *FakeServer) DeleteSecContext() error {
+ return nil
+}
diff --git a/local_crypto_patch/contents/ssh/streamlocal.go b/local_crypto_patch/contents/ssh/streamlocal.go
new file mode 100644
index 0000000000..152470fcb7
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/tcpip.go b/local_crypto_patch/contents/ssh/tcpip.go
new file mode 100644
index 0000000000..78c41fe5a1
--- /dev/null
+++ b/local_crypto_patch/contents/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/local_crypto_patch/contents/ssh/tcpip_test.go b/local_crypto_patch/contents/ssh/tcpip_test.go
new file mode 100644
index 0000000000..4d85114727
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/tcpip_test.go
@@ -0,0 +1,53 @@
+// 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 ssh
+
+import (
+ "context"
+ "net"
+ "testing"
+ "time"
+)
+
+func TestAutoPortListenBroken(t *testing.T) {
+ broken := "SSH-2.0-OpenSSH_5.9hh11"
+ works := "SSH-2.0-OpenSSH_6.1"
+ if !isBrokenOpenSSHVersion(broken) {
+ t.Errorf("version %q not marked as broken", broken)
+ }
+ if isBrokenOpenSSHVersion(works) {
+ t.Errorf("version %q marked as broken", works)
+ }
+}
+
+func TestClientImplementsDialContext(t *testing.T) {
+ type ContextDialer interface {
+ DialContext(context.Context, string, string) (net.Conn, error)
+ }
+ // Belt and suspenders assertion, since package net does not
+ // declare a ContextDialer type.
+ var _ ContextDialer = &net.Dialer{}
+ var _ ContextDialer = &Client{}
+}
+
+func TestClientDialContextWithCancel(t *testing.T) {
+ c := &Client{}
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+ _, err := c.DialContext(ctx, "tcp", "localhost:1000")
+ if err != context.Canceled {
+ t.Errorf("DialContext: got nil error, expected %v", context.Canceled)
+ }
+}
+
+func TestClientDialContextWithDeadline(t *testing.T) {
+ c := &Client{}
+ ctx, cancel := context.WithDeadline(context.Background(), time.Now())
+ defer cancel()
+ _, err := c.DialContext(ctx, "tcp", "localhost:1000")
+ if err != context.DeadlineExceeded {
+ t.Errorf("DialContext: got nil error, expected %v", context.DeadlineExceeded)
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/terminal/terminal.go b/local_crypto_patch/contents/ssh/terminal/terminal.go
new file mode 100644
index 0000000000..a4d1919a9e
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/terminal/terminal.go
@@ -0,0 +1,76 @@
+// 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 terminal provides support functions for dealing with terminals, as
+// commonly found on UNIX systems.
+//
+// Deprecated: this package moved to golang.org/x/term.
+package terminal
+
+import (
+ "io"
+
+ "golang.org/x/term"
+)
+
+// EscapeCodes contains escape sequences that can be written to the terminal in
+// order to achieve different styles of text.
+type EscapeCodes = term.EscapeCodes
+
+// Terminal contains the state for running a VT100 terminal that is capable of
+// reading lines of input.
+type Terminal = term.Terminal
+
+// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
+// a local terminal, that terminal must first have been put into raw mode.
+// prompt is a string that is written at the start of each input line (i.e.
+// "> ").
+func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
+ return term.NewTerminal(c, prompt)
+}
+
+// ErrPasteIndicator may be returned from ReadLine as the error, in addition
+// to valid line data. It indicates that bracketed paste mode is enabled and
+// that the returned line consists only of pasted data. Programs may wish to
+// interpret pasted data more literally than typed data.
+var ErrPasteIndicator = term.ErrPasteIndicator
+
+// State contains the state of a terminal.
+type State = term.State
+
+// IsTerminal returns whether the given file descriptor is a terminal.
+func IsTerminal(fd int) bool {
+ return term.IsTerminal(fd)
+}
+
+// ReadPassword reads a line of input from a terminal without local echo. This
+// is commonly used for inputting passwords and other sensitive data. The slice
+// returned does not include the \n.
+func ReadPassword(fd int) ([]byte, error) {
+ return term.ReadPassword(fd)
+}
+
+// MakeRaw puts the terminal connected to the given file descriptor into raw
+// mode and returns the previous state of the terminal so that it can be
+// restored.
+func MakeRaw(fd int) (*State, error) {
+ return term.MakeRaw(fd)
+}
+
+// Restore restores the terminal connected to the given file descriptor to a
+// previous state.
+func Restore(fd int, oldState *State) error {
+ return term.Restore(fd, oldState)
+}
+
+// GetState returns the current state of a terminal which may be useful to
+// restore the terminal after a signal.
+func GetState(fd int) (*State, error) {
+ return term.GetState(fd)
+}
+
+// GetSize returns the dimensions of the given terminal.
+func GetSize(fd int) (width, height int, err error) {
+ return term.GetSize(fd)
+}
diff --git a/local_crypto_patch/contents/ssh/test/agent_unix_test.go b/local_crypto_patch/contents/ssh/test/agent_unix_test.go
new file mode 100644
index 0000000000..fb99c9073b
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/agent_unix_test.go
@@ -0,0 +1,58 @@
+// 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.
+
+//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
+
+package test
+
+import (
+ "bytes"
+ "testing"
+
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/agent"
+)
+
+func TestAgentForward(t *testing.T) {
+ server := newServer(t)
+ conn := server.Dial(clientConfig())
+ defer conn.Close()
+
+ keyring := agent.NewKeyring()
+ if err := keyring.Add(agent.AddedKey{PrivateKey: testPrivateKeys["ecdsa"]}); err != nil {
+ t.Fatalf("Error adding key: %s", err)
+ }
+ if err := keyring.Add(agent.AddedKey{
+ PrivateKey: testPrivateKeys["ecdsa"],
+ LifetimeSecs: 3600,
+ }); err != nil {
+ t.Fatalf("Error adding key with constraints: %s", err)
+ }
+ pub := testPublicKeys["ecdsa"]
+
+ sess, err := conn.NewSession()
+ if err != nil {
+ skipIfIssue64959(t, err)
+ t.Fatalf("NewSession: %v", err)
+ }
+ if err := agent.RequestAgentForwarding(sess); err != nil {
+ t.Fatalf("RequestAgentForwarding: %v", err)
+ }
+
+ if err := agent.ForwardToAgent(conn, keyring); err != nil {
+ t.Fatalf("SetupForwardKeyring: %v", err)
+ }
+ out, err := sess.CombinedOutput("ssh-add -L")
+ if err != nil {
+ t.Fatalf("running ssh-add: %v, out %s", err, out)
+ }
+ key, _, _, _, err := ssh.ParseAuthorizedKey(out)
+ if err != nil {
+ t.Fatalf("ParseAuthorizedKey(%q): %v", out, err)
+ }
+
+ if !bytes.Equal(key.Marshal(), pub.Marshal()) {
+ t.Fatalf("got key %s, want %s", ssh.MarshalAuthorizedKey(key), ssh.MarshalAuthorizedKey(pub))
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/test/cert_test.go b/local_crypto_patch/contents/ssh/test/cert_test.go
new file mode 100644
index 0000000000..9555a097b0
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/cert_test.go
@@ -0,0 +1,76 @@
+// 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.
+
+//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
+
+package test
+
+import (
+ "bytes"
+ "crypto/rand"
+ "testing"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// Test both logging in with a cert, and also that the certificate presented by an OpenSSH host can be validated correctly
+func TestCertLogin(t *testing.T) {
+ s := newServer(t)
+
+ // Use a key different from the default.
+ clientKey := testSigners["ed25519"]
+ caAuthKey := testSigners["ecdsa"]
+ cert := &ssh.Certificate{
+ Key: clientKey.PublicKey(),
+ ValidPrincipals: []string{username()},
+ CertType: ssh.UserCert,
+ ValidBefore: ssh.CertTimeInfinity,
+ }
+ if err := cert.SignCert(rand.Reader, caAuthKey); err != nil {
+ t.Fatalf("SetSignature: %v", err)
+ }
+
+ certSigner, err := ssh.NewCertSigner(cert, clientKey)
+ if err != nil {
+ t.Fatalf("NewCertSigner: %v", err)
+ }
+
+ conf := &ssh.ClientConfig{
+ User: username(),
+ HostKeyCallback: (&ssh.CertChecker{
+ IsHostAuthority: func(pk ssh.PublicKey, addr string) bool {
+ return bytes.Equal(pk.Marshal(), testPublicKeys["ca"].Marshal())
+ },
+ }).CheckHostKey,
+ }
+ conf.Auth = append(conf.Auth, ssh.PublicKeys(certSigner))
+
+ for _, test := range []struct {
+ addr string
+ succeed bool
+ }{
+ {addr: "host.example.com:22", succeed: true},
+ {addr: "host.example.com:10000", succeed: true}, // non-standard port must be OK
+ {addr: "host.example.com", succeed: false}, // port must be specified
+ {addr: "host.ex4mple.com:22", succeed: false}, // wrong host
+ } {
+ client, err := s.TryDialWithAddr(conf, test.addr)
+
+ // Always close client if opened successfully
+ if err == nil {
+ client.Close()
+ }
+
+ // Now evaluate whether the test failed or passed
+ if test.succeed {
+ if err != nil {
+ t.Fatalf("TryDialWithAddr: %v", err)
+ }
+ } else {
+ if err == nil {
+ t.Fatalf("TryDialWithAddr, unexpected success")
+ }
+ }
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/test/dial_unix_test.go b/local_crypto_patch/contents/ssh/test/dial_unix_test.go
new file mode 100644
index 0000000000..7d6fb2877f
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/dial_unix_test.go
@@ -0,0 +1,132 @@
+// 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 !windows && !js && !wasip1
+
+package test
+
+// direct-tcpip and direct-streamlocal functional tests
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net"
+ "strings"
+ "testing"
+)
+
+type dialTester interface {
+ TestServerConn(t *testing.T, c net.Conn)
+ TestClientConn(t *testing.T, c net.Conn)
+}
+
+func testDial(t *testing.T, n, listenAddr string, x dialTester) {
+ server := newServer(t)
+ sshConn := server.Dial(clientConfig())
+ defer sshConn.Close()
+
+ l, err := net.Listen(n, listenAddr)
+ if err != nil {
+ t.Fatalf("Listen: %v", err)
+ }
+ defer l.Close()
+
+ testData := fmt.Sprintf("hello from %s, %s", n, listenAddr)
+ go func() {
+ for {
+ c, err := l.Accept()
+ if err != nil {
+ break
+ }
+ x.TestServerConn(t, c)
+
+ io.WriteString(c, testData)
+ c.Close()
+ }
+ }()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ conn, err := sshConn.DialContext(ctx, n, l.Addr().String())
+ // Canceling the context after dial should have no effect
+ // on the opened connection.
+ cancel()
+ if err != nil {
+ skipIfIssue64959(t, err)
+ t.Fatalf("Dial: %v", err)
+ }
+ x.TestClientConn(t, conn)
+ defer conn.Close()
+ b, err := io.ReadAll(conn)
+ if err != nil {
+ t.Fatalf("ReadAll: %v", err)
+ }
+ t.Logf("got %q", string(b))
+ if string(b) != testData {
+ t.Fatalf("expected %q, got %q", testData, string(b))
+ }
+}
+
+type tcpDialTester struct {
+ listenAddr string
+}
+
+func (x *tcpDialTester) TestServerConn(t *testing.T, c net.Conn) {
+ host := strings.Split(x.listenAddr, ":")[0]
+ prefix := host + ":"
+ if !strings.HasPrefix(c.LocalAddr().String(), prefix) {
+ t.Fatalf("expected to start with %q, got %q", prefix, c.LocalAddr().String())
+ }
+ if !strings.HasPrefix(c.RemoteAddr().String(), prefix) {
+ t.Fatalf("expected to start with %q, got %q", prefix, c.RemoteAddr().String())
+ }
+}
+
+func (x *tcpDialTester) TestClientConn(t *testing.T, c net.Conn) {
+ // we use zero addresses. see *Client.Dial.
+ if c.LocalAddr().String() != "0.0.0.0:0" {
+ t.Fatalf("expected \"0.0.0.0:0\", got %q", c.LocalAddr().String())
+ }
+ if c.RemoteAddr().String() != "0.0.0.0:0" {
+ t.Fatalf("expected \"0.0.0.0:0\", got %q", c.RemoteAddr().String())
+ }
+}
+
+func TestDialTCP(t *testing.T) {
+ x := &tcpDialTester{
+ listenAddr: "127.0.0.1:0",
+ }
+ testDial(t, "tcp", x.listenAddr, x)
+}
+
+type unixDialTester struct {
+ listenAddr string
+}
+
+func (x *unixDialTester) TestServerConn(t *testing.T, c net.Conn) {
+ if c.LocalAddr().String() != x.listenAddr {
+ t.Fatalf("expected %q, got %q", x.listenAddr, c.LocalAddr().String())
+ }
+ if c.RemoteAddr().String() != "@" && c.RemoteAddr().String() != "" {
+ t.Fatalf("expected \"@\" or \"\", got %q", c.RemoteAddr().String())
+ }
+}
+
+func (x *unixDialTester) TestClientConn(t *testing.T, c net.Conn) {
+ if c.RemoteAddr().String() != x.listenAddr {
+ t.Fatalf("expected %q, got %q", x.listenAddr, c.RemoteAddr().String())
+ }
+ if c.LocalAddr().String() != "@" {
+ t.Fatalf("expected \"@\", got %q", c.LocalAddr().String())
+ }
+}
+
+func TestDialUnix(t *testing.T) {
+ addr, cleanup := newTempSocket(t)
+ defer cleanup()
+ x := &unixDialTester{
+ listenAddr: addr,
+ }
+ testDial(t, "unix", x.listenAddr, x)
+}
diff --git a/local_crypto_patch/contents/ssh/test/doc.go b/local_crypto_patch/contents/ssh/test/doc.go
new file mode 100644
index 0000000000..865781c819
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/doc.go
@@ -0,0 +1,9 @@
+// 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 test contains integration tests for the
+// golang.org/x/crypto/ssh package.
+//
+// Deprecated: this package is for internal use only.
+package test
diff --git a/local_crypto_patch/contents/ssh/test/forward_unix_test.go b/local_crypto_patch/contents/ssh/test/forward_unix_test.go
new file mode 100644
index 0000000000..549d46cef4
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/forward_unix_test.go
@@ -0,0 +1,211 @@
+// 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
+
+package test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "math/rand"
+ "net"
+ "runtime"
+ "testing"
+ "time"
+)
+
+type closeWriter interface {
+ CloseWrite() error
+}
+
+func testPortForward(t *testing.T, n, listenAddr string) {
+ server := newServer(t)
+ conn := server.Dial(clientConfig())
+ defer conn.Close()
+
+ sshListener, err := conn.Listen(n, listenAddr)
+ if err != nil {
+ if runtime.GOOS == "darwin" && err == io.EOF {
+ t.Skipf("skipping test broken on some versions of macOS; see https://go.dev/issue/64959")
+ }
+ t.Fatal(err)
+ }
+
+ errCh := make(chan error, 1)
+
+ go func() {
+ defer close(errCh)
+ sshConn, err := sshListener.Accept()
+ if err != nil {
+ errCh <- fmt.Errorf("listen.Accept failed: %v", err)
+ return
+ }
+ defer sshConn.Close()
+
+ _, err = io.Copy(sshConn, sshConn)
+ if err != nil && err != io.EOF {
+ errCh <- fmt.Errorf("ssh client copy: %v", err)
+ }
+ }()
+
+ // The forwarded address match the listen address because we run the tests
+ // on the same host.
+ forwardedAddr := sshListener.Addr().String()
+ netConn, err := net.Dial(n, forwardedAddr)
+ if err != nil {
+ t.Fatalf("net dial failed: %v", err)
+ }
+
+ readChan := make(chan []byte)
+ go func() {
+ data, _ := io.ReadAll(netConn)
+ readChan <- data
+ }()
+
+ // Invent some data.
+ data := make([]byte, 100*1000)
+ for i := range data {
+ data[i] = byte(i % 255)
+ }
+
+ var sent []byte
+ for len(sent) < 1000*1000 {
+ // Send random sized chunks
+ m := rand.Intn(len(data))
+ n, err := netConn.Write(data[:m])
+ if err != nil {
+ break
+ }
+ sent = append(sent, data[:n]...)
+ }
+ if err := netConn.(closeWriter).CloseWrite(); err != nil {
+ t.Errorf("netConn.CloseWrite: %v", err)
+ }
+
+ // Check for errors on server goroutine
+ err = <-errCh
+ if err != nil {
+ t.Fatalf("server: %v", err)
+ }
+
+ read := <-readChan
+
+ if len(sent) != len(read) {
+ t.Fatalf("got %d bytes, want %d", len(read), len(sent))
+ }
+ if !bytes.Equal(sent, read) {
+ t.Fatalf("read back data does not match")
+ }
+
+ if err := sshListener.Close(); err != nil {
+ t.Fatalf("sshListener.Close: %v", err)
+ }
+
+ // Check that the forward disappeared.
+ netConn, err = net.Dial(n, forwardedAddr)
+ if err == nil {
+ netConn.Close()
+ t.Errorf("still listening to %s after closing", forwardedAddr)
+ }
+}
+
+func TestPortForwardTCP(t *testing.T) {
+ testPortForward(t, "tcp", ":0")
+ testPortForward(t, "tcp", "[::]:0")
+ testPortForward(t, "tcp", "localhost:0")
+}
+
+func TestPortForwardUnix(t *testing.T) {
+ addr, cleanup := newTempSocket(t)
+ defer cleanup()
+ testPortForward(t, "unix", addr)
+}
+
+func testAcceptClose(t *testing.T, n, listenAddr string) {
+ server := newServer(t)
+ conn := server.Dial(clientConfig())
+
+ sshListener, err := conn.Listen(n, listenAddr)
+ if err != nil {
+ if runtime.GOOS == "darwin" && err == io.EOF {
+ t.Skipf("skipping test broken on some versions of macOS; see https://go.dev/issue/64959")
+ }
+ t.Fatal(err)
+ }
+
+ quit := make(chan error, 1)
+ go func() {
+ for {
+ c, err := sshListener.Accept()
+ if err != nil {
+ quit <- err
+ break
+ }
+ c.Close()
+ }
+ }()
+ sshListener.Close()
+
+ select {
+ case <-time.After(1 * time.Second):
+ t.Errorf("timeout: listener did not close.")
+ case err := <-quit:
+ t.Logf("quit as expected (error %v)", err)
+ }
+}
+
+func TestAcceptCloseTCP(t *testing.T) {
+ testAcceptClose(t, "tcp", "localhost:0")
+}
+
+func TestAcceptCloseUnix(t *testing.T) {
+ addr, cleanup := newTempSocket(t)
+ defer cleanup()
+ testAcceptClose(t, "unix", addr)
+}
+
+// Check that listeners exit if the underlying client transport dies.
+func testPortForwardConnectionClose(t *testing.T, n, listenAddr string) {
+ server := newServer(t)
+ client := server.Dial(clientConfig())
+
+ sshListener, err := client.Listen(n, listenAddr)
+ if err != nil {
+ if runtime.GOOS == "darwin" && err == io.EOF {
+ t.Skipf("skipping test broken on some versions of macOS; see https://go.dev/issue/64959")
+ }
+ t.Fatal(err)
+ }
+
+ quit := make(chan error, 1)
+ go func() {
+ for {
+ c, err := sshListener.Accept()
+ if err != nil {
+ quit <- err
+ break
+ }
+ c.Close()
+ }
+ }()
+
+ // It would be even nicer if we closed the server side, but it
+ // is more involved as the fd for that side is dup()ed.
+ server.lastDialConn.Close()
+
+ err = <-quit
+ t.Logf("quit as expected (error %v)", err)
+}
+
+func TestPortForwardConnectionCloseTCP(t *testing.T) {
+ testPortForwardConnectionClose(t, "tcp", "localhost:0")
+}
+
+func TestPortForwardConnectionCloseUnix(t *testing.T) {
+ addr, cleanup := newTempSocket(t)
+ defer cleanup()
+ testPortForwardConnectionClose(t, "unix", addr)
+}
diff --git a/local_crypto_patch/contents/ssh/test/multi_auth_test.go b/local_crypto_patch/contents/ssh/test/multi_auth_test.go
new file mode 100644
index 0000000000..14cf1cce12
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/multi_auth_test.go
@@ -0,0 +1,143 @@
+// 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.
+
+// Tests for ssh client multi-auth
+//
+// These tests run a simple go ssh client against OpenSSH server
+// over unix domain sockets. The tests use multiple combinations
+// of password, keyboard-interactive and publickey authentication
+// methods.
+//
+// A wrapper library for making sshd PAM authentication use test
+// passwords is required in ./sshd_test_pw.so. If the library does
+// not exist these tests will be skipped. See compile instructions
+// (for linux) in file ./sshd_test_pw.c.
+
+//go:build linux
+
+package test
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// test cases
+type multiAuthTestCase struct {
+ authMethods []string
+ expectedPasswordCbs int
+ expectedKbdIntCbs int
+}
+
+// test context
+type multiAuthTestCtx struct {
+ password string
+ numPasswordCbs int
+ numKbdIntCbs int
+}
+
+// create test context
+func newMultiAuthTestCtx(t *testing.T) *multiAuthTestCtx {
+ password, err := randomPassword()
+ if err != nil {
+ t.Fatalf("Failed to generate random test password: %s", err.Error())
+ }
+
+ return &multiAuthTestCtx{
+ password: password,
+ }
+}
+
+// password callback
+func (ctx *multiAuthTestCtx) passwordCb() (secret string, err error) {
+ ctx.numPasswordCbs++
+ return ctx.password, nil
+}
+
+// keyboard-interactive callback
+func (ctx *multiAuthTestCtx) kbdIntCb(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
+ if len(questions) == 0 {
+ return nil, nil
+ }
+
+ ctx.numKbdIntCbs++
+ if len(questions) == 1 {
+ return []string{ctx.password}, nil
+ }
+
+ return nil, fmt.Errorf("unsupported keyboard-interactive flow")
+}
+
+// TestMultiAuth runs several subtests for different combinations of password, keyboard-interactive and publickey authentication methods
+func TestMultiAuth(t *testing.T) {
+ testCases := []multiAuthTestCase{
+ // Test password,publickey authentication, assert that password callback is called 1 time
+ {
+ authMethods: []string{"password", "publickey"},
+ expectedPasswordCbs: 1,
+ },
+ // Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
+ {
+ authMethods: []string{"keyboard-interactive", "publickey"},
+ expectedKbdIntCbs: 1,
+ },
+ // Test publickey,password authentication, assert that password callback is called 1 time
+ {
+ authMethods: []string{"publickey", "password"},
+ expectedPasswordCbs: 1,
+ },
+ // Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
+ {
+ authMethods: []string{"publickey", "keyboard-interactive"},
+ expectedKbdIntCbs: 1,
+ },
+ // Test password,password authentication, assert that password callback is called 2 times
+ {
+ authMethods: []string{"password", "password"},
+ expectedPasswordCbs: 2,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(strings.Join(testCase.authMethods, ","), func(t *testing.T) {
+ ctx := newMultiAuthTestCtx(t)
+
+ server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
+
+ clientConfig := clientConfig()
+ server.setTestPassword(clientConfig.User, ctx.password)
+
+ publicKeyAuthMethod := clientConfig.Auth[0]
+ clientConfig.Auth = nil
+ for _, authMethod := range testCase.authMethods {
+ switch authMethod {
+ case "publickey":
+ clientConfig.Auth = append(clientConfig.Auth, publicKeyAuthMethod)
+ case "password":
+ clientConfig.Auth = append(clientConfig.Auth,
+ ssh.RetryableAuthMethod(ssh.PasswordCallback(ctx.passwordCb), 5))
+ case "keyboard-interactive":
+ clientConfig.Auth = append(clientConfig.Auth,
+ ssh.RetryableAuthMethod(ssh.KeyboardInteractive(ctx.kbdIntCb), 5))
+ default:
+ t.Fatalf("Unknown authentication method %s", authMethod)
+ }
+ }
+
+ conn := server.Dial(clientConfig)
+ defer conn.Close()
+
+ if ctx.numPasswordCbs != testCase.expectedPasswordCbs {
+ t.Fatalf("passwordCallback was called %d times, expected %d times", ctx.numPasswordCbs, testCase.expectedPasswordCbs)
+ }
+
+ if ctx.numKbdIntCbs != testCase.expectedKbdIntCbs {
+ t.Fatalf("keyboardInteractiveCallback was called %d times, expected %d times", ctx.numKbdIntCbs, testCase.expectedKbdIntCbs)
+ }
+ })
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/test/recording_client_test.go b/local_crypto_patch/contents/ssh/test/recording_client_test.go
new file mode 100644
index 0000000000..b744bd332e
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/recording_client_test.go
@@ -0,0 +1,504 @@
+// 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 test
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/internal/testenv"
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/testdata"
+)
+
+// serverPort contains the port that OpenSSH will listen on. OpenSSH can't take
+// "0" as an argument here so we have to pick a number and hope that it's not in
+// use on the machine. Since this only occurs when -update is given and thus
+// when there's a human watching the test, this isn't too bad.
+const serverPort = 24222
+
+var (
+ storeUsernameOnce sync.Once
+)
+
+type clientTest struct {
+ // name is a freeform string identifying the test and the file in which
+ // the expected results will be stored.
+ name string
+ // config contains the client configuration to use for this test.
+ config *ssh.ClientConfig
+ // expectError defines the error string to check if the connection is
+ // expected to fail.
+ expectError string
+ // successCallback defines a callback to execute after the client connection
+ // is established.
+ successCallback func(t *testing.T, client *ssh.Client)
+}
+
+// connFromCommand starts the reference server process, connects to it and
+// returns a recordingConn for the connection. It must be closed before Waiting
+// for child.
+func (test *clientTest) connFromCommand(t *testing.T, config string) *recordingConn {
+ sshd, err := exec.LookPath("sshd")
+ if err != nil {
+ t.Skipf("sshd not found, skipping test: %v", err)
+ }
+ dir, err := os.MkdirTemp("", "sshtest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ f, err := os.Create(filepath.Join(dir, "sshd_config"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := configTmpl[config]; ok == false {
+ t.Fatal(fmt.Errorf("Invalid server config '%s'", config))
+ }
+ configVars := map[string]string{
+ "Dir": dir,
+ }
+ err = configTmpl[config].Execute(f, configVars)
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.Close()
+
+ writeFile(filepath.Join(dir, "banner"), []byte("Server Banner"))
+
+ for k, v := range testdata.PEMBytes {
+ filename := "id_" + k
+ writeFile(filepath.Join(dir, filename), v)
+ writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k]))
+ }
+
+ var authkeys bytes.Buffer
+ for k := range testdata.PEMBytes {
+ authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
+ }
+ writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes())
+ cmd := testenv.Command(t, sshd, "-D", "-e", "-f", f.Name(), "-p", strconv.Itoa(serverPort))
+ cmd.Stdin = nil
+ var output bytes.Buffer
+ cmd.Stdout = &output
+ cmd.Stderr = &output
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := os.RemoveAll(dir); err != nil {
+ t.Error(err)
+ }
+ // Don't check for errors; if it fails it's most
+ // likely "os: process already finished", and we don't
+ // care about that. Use os.Interrupt, so child
+ // processes are killed too.
+ cmd.Process.Signal(os.Interrupt)
+ cmd.Wait()
+ if t.Failed() {
+ t.Logf("OpenSSH output:\n\n%s", cmd.Stdout)
+ }
+ })
+ var tcpConn net.Conn
+ for i := uint(0); i < 5; i++ {
+ tcpConn, err = net.DialTCP("tcp", nil, &net.TCPAddr{
+ IP: net.IPv4(127, 0, 0, 1),
+ Port: serverPort,
+ })
+ if err == nil {
+ break
+ }
+ time.Sleep((1 << i) * 5 * time.Millisecond)
+ }
+ if err != nil {
+ t.Fatalf("error connecting to the OpenSSH server: %v (%v)\n\n%s", err, cmd.Wait(), output.Bytes())
+ }
+
+ record := &recordingConn{
+ Conn: tcpConn,
+ clientToServer: true,
+ }
+
+ return record
+}
+
+func (test *clientTest) dataPath() string {
+ return filepath.Join("..", "testdata", "Client-"+test.name)
+}
+
+func (test *clientTest) usernameDataPath() string {
+ return filepath.Join("..", "testdata", "Client-username")
+}
+
+func (test *clientTest) loadData() (flows [][]byte, err error) {
+ in, err := os.Open(test.dataPath())
+ if err != nil {
+ return nil, err
+ }
+ defer in.Close()
+ return parseTestData(in)
+}
+
+func (test *clientTest) storeUsername() (err error) {
+ storeUsernameOnce.Do(func() {
+ err = os.WriteFile(test.usernameDataPath(), []byte(username()), 0666)
+ })
+ return err
+}
+
+func (test *clientTest) loadUsername() (string, error) {
+ data, err := os.ReadFile(test.usernameDataPath())
+ return string(data), err
+}
+
+func (test *clientTest) run(t *testing.T, write bool) {
+ var clientConn net.Conn
+ var recordingConn *recordingConn
+
+ setDeterministicRandomSource(&test.config.Config)
+
+ if write {
+ // We store the username used when we record the connection so we can
+ // reuse the same username when running tests.
+ if err := test.storeUsername(); err != nil {
+ t.Fatalf("failed to store username to %q: %v", test.usernameDataPath(), err)
+ }
+ recordingConn = test.connFromCommand(t, "default")
+ clientConn = recordingConn
+ } else {
+ username, err := test.loadUsername()
+ if err != nil {
+ t.Fatalf("failed to load username from %q: %v", test.usernameDataPath(), err)
+ }
+ test.config.User = username
+ timer := time.AfterFunc(10*time.Second, func() {
+ fmt.Println("This test may be stuck, try running using -timeout 10s")
+ })
+ t.Cleanup(func() {
+ timer.Stop()
+ })
+ flows, err := test.loadData()
+ if err != nil {
+ t.Fatalf("failed to load data from %s: %v", test.dataPath(), err)
+ }
+ clientConn = newReplayingConn(t, flows)
+ }
+ c, chans, reqs, err := ssh.NewClientConn(clientConn, "", test.config)
+ if err != nil {
+ if test.expectError == "" {
+ t.Fatal(err)
+ } else {
+ if !strings.Contains(err.Error(), test.expectError) {
+ t.Fatalf("%q not found in %v", test.expectError, err)
+ }
+ }
+ } else {
+ if test.expectError != "" {
+ t.Error("dial should have failed.")
+ }
+ client := ssh.NewClient(c, chans, reqs)
+ if test.successCallback != nil {
+ test.successCallback(t, client)
+ }
+ if err := client.Close(); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ if write {
+ path := test.dataPath()
+ out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ t.Fatalf("Failed to create output file: %v", err)
+ }
+ defer out.Close()
+ recordingConn.Close()
+
+ recordingConn.WriteTo(out)
+ t.Logf("Wrote %s\n", path)
+ }
+}
+
+func recordingsClientConfig() *ssh.ClientConfig {
+ config := clientConfig()
+ config.SetDefaults()
+ // Remove ML-KEM since it only works with Go 1.24.
+ if config.KeyExchanges[0] == ssh.KeyExchangeMLKEM768X25519 {
+ config.KeyExchanges = config.KeyExchanges[1:]
+ }
+ config.Auth = []ssh.AuthMethod{
+ ssh.PublicKeys(testSigners["rsa"]),
+ }
+ return config
+}
+
+func TestClientKeyExchanges(t *testing.T) {
+ config := ssh.ClientConfig{}
+ config.SetDefaults()
+
+ var keyExchanges []string
+ for _, kex := range config.KeyExchanges {
+ // Exclude ecdh for now, to make them deterministic we should use see a
+ // stream of fixed bytes as the random source.
+ if !strings.HasPrefix(kex, "ecdh-") {
+ keyExchanges = append(keyExchanges, kex)
+ }
+ }
+ // Add diffie-hellman-group-exchange-sha256 and
+ // diffie-hellman-group16-sha512 as they are not enabled by default.
+ keyExchanges = append(keyExchanges, "diffie-hellman-group-exchange-sha256", "diffie-hellman-group16-sha512")
+
+ for _, kex := range keyExchanges {
+ c := recordingsClientConfig()
+ c.KeyExchanges = []string{kex}
+ test := clientTest{
+ name: "KEX-" + kex,
+ config: c,
+ }
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+ }
+}
+
+func TestClientCiphers(t *testing.T) {
+ config := ssh.ClientConfig{}
+ config.SetDefaults()
+
+ for _, ciph := range config.Ciphers {
+ c := recordingsClientConfig()
+ c.Ciphers = []string{ciph}
+ test := clientTest{
+ name: "Cipher-" + ciph,
+ config: c,
+ }
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+ }
+}
+
+func TestClientMACs(t *testing.T) {
+ config := ssh.ClientConfig{}
+ config.SetDefaults()
+
+ for _, mac := range config.MACs {
+ c := recordingsClientConfig()
+ c.MACs = []string{mac}
+ test := clientTest{
+ name: "MAC-" + mac,
+ config: c,
+ }
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+ }
+}
+
+func TestBannerCallback(t *testing.T) {
+ var receivedBanner string
+ config := recordingsClientConfig()
+ config.BannerCallback = func(message string) error {
+ receivedBanner = message
+ return nil
+ }
+ test := clientTest{
+ name: "BannerCallback",
+ config: config,
+ successCallback: func(t *testing.T, client *ssh.Client) {
+ expected := "Server Banner"
+ if receivedBanner != expected {
+ t.Fatalf("got %v; want %v", receivedBanner, expected)
+ }
+ },
+ }
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+}
+
+func TestRunCommandSuccess(t *testing.T) {
+ if runtime.GOARCH == "wasm" {
+ t.Skip("skipping test, executing a command, session.Run(), is not supported on wasm")
+ }
+ test := clientTest{
+ name: "RunCommandSuccess",
+ config: recordingsClientConfig(),
+ successCallback: func(t *testing.T, client *ssh.Client) {
+ session, err := client.NewSession()
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+ err = session.Run("true")
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ },
+ }
+
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+}
+
+func TestHostKeyCheck(t *testing.T) {
+ config := recordingsClientConfig()
+ hostDB := hostKeyDB()
+ config.HostKeyCallback = hostDB.Check
+
+ // change the keys.
+ hostDB.keys[ssh.KeyAlgoRSA][25]++
+ hostDB.keys[ssh.InsecureKeyAlgoDSA][25]++
+ hostDB.keys[ssh.KeyAlgoECDSA256][25]++
+
+ test := clientTest{
+ name: "HostKeyCheck",
+ config: config,
+ expectError: "host key mismatch",
+ }
+
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+}
+
+func TestRunCommandStdin(t *testing.T) {
+ if runtime.GOARCH == "wasm" {
+ t.Skip("skipping test, executing a command, session.Run(), is not supported on wasm")
+ }
+ test := clientTest{
+ name: "RunCommandStdin",
+ config: recordingsClientConfig(),
+ successCallback: func(t *testing.T, client *ssh.Client) {
+ session, err := client.NewSession()
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+
+ r, w := io.Pipe()
+ defer r.Close()
+ defer w.Close()
+ session.Stdin = r
+
+ err = session.Run("true")
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ },
+ }
+
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+}
+
+func TestRunCommandStdinError(t *testing.T) {
+ if runtime.GOARCH == "wasm" {
+ t.Skip("skipping test, executing a command, session.Run(), is not supported on wasm")
+ }
+ test := clientTest{
+ name: "RunCommandStdinError",
+ config: recordingsClientConfig(),
+ successCallback: func(t *testing.T, client *ssh.Client) {
+ session, err := client.NewSession()
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+
+ r, w := io.Pipe()
+ defer r.Close()
+ session.Stdin = r
+ pipeErr := errors.New("closing write end of pipe")
+ w.CloseWithError(pipeErr)
+
+ err = session.Run("true")
+ if err != pipeErr {
+ t.Fatalf("expected %v, found %v", pipeErr, err)
+ }
+ },
+ }
+
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+}
+
+func TestRunCommandFailed(t *testing.T) {
+ if runtime.GOARCH == "wasm" {
+ t.Skip("skipping test, executing a command, session.Run(), is not supported on wasm")
+ }
+ test := clientTest{
+ name: "RunCommandFailed",
+ config: recordingsClientConfig(),
+ successCallback: func(t *testing.T, client *ssh.Client) {
+ session, err := client.NewSession()
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+
+ // Trigger a failure by attempting to execute a non-existent
+ // command.
+ err = session.Run(`non-existent command`)
+ if err == nil {
+ t.Fatalf("session succeeded: %v", err)
+ }
+ },
+ }
+
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+}
+
+func TestWindowChange(t *testing.T) {
+ if runtime.GOARCH == "wasm" {
+ t.Skip("skipping test, stdin/out are not supported on wasm")
+ }
+ test := clientTest{
+ name: "WindowChange",
+ config: recordingsClientConfig(),
+ successCallback: func(t *testing.T, client *ssh.Client) {
+ session, err := client.NewSession()
+ if err != nil {
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+
+ stdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Fatalf("unable to acquire stdout pipe: %s", err)
+ }
+
+ stdin, err := session.StdinPipe()
+ if err != nil {
+ t.Fatalf("unable to acquire stdin pipe: %s", err)
+ }
+
+ tm := ssh.TerminalModes{ssh.ECHO: 0}
+ if err = session.RequestPty("xterm", 80, 40, tm); err != nil {
+ t.Fatalf("req-pty failed: %s", err)
+ }
+
+ if err := session.WindowChange(100, 100); err != nil {
+ t.Fatalf("window-change failed: %s", err)
+ }
+
+ err = session.Shell()
+ if err != nil {
+ t.Fatalf("session failed: %s", err)
+ }
+
+ stdin.Write([]byte("stty size && exit\n"))
+
+ var buf bytes.Buffer
+ if _, err := io.Copy(&buf, stdout); err != nil {
+ t.Fatalf("reading failed: %s", err)
+ }
+
+ if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "100 100") {
+ t.Fatalf("terminal WindowChange failure: expected \"100 100\" stty output, got %s", sttyOutput)
+ }
+ },
+ }
+
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+}
diff --git a/local_crypto_patch/contents/ssh/test/recording_server_test.go b/local_crypto_patch/contents/ssh/test/recording_server_test.go
new file mode 100644
index 0000000000..898a8478e5
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/recording_server_test.go
@@ -0,0 +1,284 @@
+// 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 test
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/crypto/internal/testenv"
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/testdata"
+)
+
+type serverTest struct {
+ // name is a freeform string identifying the test and the file in which
+ // the expected results will be stored.
+ name string
+ // config contains the server configuration to use for this test.
+ config *ssh.ServerConfig
+}
+
+// connFromCommand starts opens a listening socket and starts the reference
+// client to connect to it. It returns a recordingConn that wraps the resulting
+// connection.
+func (test *serverTest) connFromCommand(t *testing.T) (conn *recordingConn, err error) {
+ sshCLI, err := exec.LookPath("ssh")
+ if err != nil {
+ t.Skipf("skipping test: %v", err)
+ }
+ l, err := net.ListenTCP("tcp", &net.TCPAddr{
+ IP: net.IPv4(127, 0, 0, 1),
+ Port: 0,
+ })
+ if err != nil {
+ return nil, err
+ }
+ defer l.Close()
+
+ port := l.Addr().(*net.TCPAddr).Port
+ dir, err := os.MkdirTemp("", "sshtest")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ filename := "id_ed25519"
+ writeFile(filepath.Join(dir, filename), testdata.PEMBytes["ed25519"])
+ writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys["ed25519"]))
+ var args []string
+ args = append(args, "-v", "-i", filepath.Join(dir, filename), "-o", "StrictHostKeyChecking=no")
+ args = append(args, "-oKexAlgorithms=+diffie-hellman-group14-sha1")
+ args = append(args, "-p", strconv.Itoa(port))
+ args = append(args, "testuser@127.0.0.1")
+ args = append(args, "true")
+ cmd := testenv.Command(t, sshCLI, args...)
+ cmd.Stdin = nil
+ var output bytes.Buffer
+ cmd.Stdout = &output
+ cmd.Stderr = &output
+ if err := cmd.Start(); err != nil {
+ return nil, err
+ }
+
+ t.Cleanup(func() {
+ if err := os.RemoveAll(dir); err != nil {
+ t.Error(err)
+ }
+ // Don't check for errors; if it fails it's most
+ // likely "os: process already finished", and we don't
+ // care about that.
+ cmd.Process.Kill()
+ cmd.Wait()
+ if t.Failed() {
+ t.Logf("OpenSSH output:\n\n%s", cmd.Stdout)
+ }
+ })
+
+ connChan := make(chan any, 1)
+ go func() {
+ tcpConn, err := l.Accept()
+ if err != nil {
+ connChan <- err
+ return
+ }
+ connChan <- tcpConn
+ }()
+
+ var tcpConn net.Conn
+ select {
+ case connOrError := <-connChan:
+ if err, ok := connOrError.(error); ok {
+ return nil, err
+ }
+ tcpConn = connOrError.(net.Conn)
+ case <-time.After(2 * time.Second):
+ return nil, errors.New("timed out waiting for connection from child process")
+ }
+
+ record := &recordingConn{
+ Conn: tcpConn,
+ clientToServer: false,
+ }
+
+ return record, nil
+}
+
+func (test *serverTest) dataPath() string {
+ return filepath.Join("..", "testdata", "Server-"+test.name)
+}
+
+func (test *serverTest) loadData() (flows [][]byte, err error) {
+ in, err := os.Open(test.dataPath())
+ if err != nil {
+ return nil, err
+ }
+ defer in.Close()
+ return parseTestData(in)
+}
+
+func (test *serverTest) run(t *testing.T, write bool) {
+ var serverConn net.Conn
+ var recordingConn *recordingConn
+
+ setDeterministicRandomSource(&test.config.Config)
+
+ if write {
+ var err error
+ recordingConn, err = test.connFromCommand(t)
+ if err != nil {
+ t.Fatalf("Failed to start subcommand: %v", err)
+ }
+ serverConn = recordingConn
+ } else {
+ timer := time.AfterFunc(10*time.Second, func() {
+ fmt.Println("This test may be stuck, try running using -timeout 10s")
+ })
+ t.Cleanup(func() {
+ timer.Stop()
+ })
+ flows, err := test.loadData()
+ if err != nil {
+ t.Fatalf("Failed to load data from %s", test.dataPath())
+ }
+ serverConn = newReplayingConn(t, flows)
+ }
+
+ server, chans, reqs, err := ssh.NewServerConn(serverConn, test.config)
+ if err != nil {
+ t.Fatalf("Failed to create server conn: %v", err)
+ }
+ defer server.Close()
+
+ go ssh.DiscardRequests(reqs)
+
+ done := make(chan bool)
+
+ for newChannel := range chans {
+ if newChannel.ChannelType() != "session" {
+ newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
+ continue
+ }
+
+ channel, requests, err := newChannel.Accept()
+ if err != nil {
+ continue
+ }
+
+ go func(in <-chan *ssh.Request) {
+ for req := range in {
+ switch req.Type {
+ case "exec":
+ if req.WantReply {
+ req.Reply(true, nil)
+ }
+ channel.SendRequest("exit-status", false, ssh.Marshal(&exitStatusMsg{Status: 0}))
+ channel.Close()
+ done <- true
+ default:
+ if req.WantReply {
+ req.Reply(false, nil)
+ }
+ }
+ }
+ }(requests)
+ }
+
+ <-done
+
+ if write {
+ path := test.dataPath()
+ out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ t.Fatalf("Failed to create output file: %v", err)
+ }
+ defer out.Close()
+ recordingConn.Close()
+
+ recordingConn.WriteTo(out)
+ t.Logf("Wrote %s\n", path)
+ }
+}
+
+func recordingsServerConfig() *ssh.ServerConfig {
+ config := &ssh.ServerConfig{
+ PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+ return nil, nil
+ },
+ }
+ config.SetDefaults()
+ // Remove ML-KEM since it only works with Go 1.24.
+ config.SetDefaults()
+ if config.KeyExchanges[0] == ssh.KeyExchangeMLKEM768X25519 {
+ config.KeyExchanges = config.KeyExchanges[1:]
+ }
+ config.AddHostKey(testSigners["rsa"])
+ return config
+}
+
+func TestServerKeyExchanges(t *testing.T) {
+ config := ssh.ClientConfig{}
+ config.SetDefaults()
+
+ var keyExchanges []string
+ for _, kex := range config.KeyExchanges {
+ // Exclude ecdh for now, to make them deterministic we should use see a
+ // stream of fixed bytes as the random source.
+ // Exclude ML-KEM because server side is not deterministic.
+ if !strings.HasPrefix(kex, "ecdh-") && !strings.HasPrefix(kex, "mlkem") {
+ keyExchanges = append(keyExchanges, kex)
+ }
+ }
+ // Add diffie-hellman-group16-sha512 as it is not enabled by default.
+ keyExchanges = append(keyExchanges, "diffie-hellman-group16-sha512")
+
+ for _, kex := range keyExchanges {
+ c := recordingsServerConfig()
+ c.KeyExchanges = []string{kex}
+ test := serverTest{
+ name: "KEX-" + kex,
+ config: c,
+ }
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+ }
+}
+
+func TestServerCiphers(t *testing.T) {
+ config := ssh.ClientConfig{}
+ config.SetDefaults()
+
+ for _, ciph := range config.Ciphers {
+ c := recordingsServerConfig()
+ c.Ciphers = []string{ciph}
+ test := serverTest{
+ name: "Cipher-" + ciph,
+ config: c,
+ }
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+ }
+}
+
+func TestServerMACs(t *testing.T) {
+ config := ssh.ClientConfig{}
+ config.SetDefaults()
+
+ for _, mac := range config.MACs {
+ c := recordingsServerConfig()
+ c.MACs = []string{mac}
+ test := serverTest{
+ name: "MAC-" + mac,
+ config: c,
+ }
+ runTestAndUpdateIfNeeded(t, test.name, test.run)
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/test/recording_test.go b/local_crypto_patch/contents/ssh/test/recording_test.go
new file mode 100644
index 0000000000..4356345ca2
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/recording_test.go
@@ -0,0 +1,433 @@
+// 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 test
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/hex"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "os"
+ "os/user"
+ "strconv"
+ "strings"
+ "sync"
+ "testing"
+ "text/template"
+ "time"
+
+ "golang.org/x/crypto/sha3"
+ "golang.org/x/crypto/ssh"
+)
+
+const (
+ defaultSSHDConfig = `
+Protocol 2
+Banner {{.Dir}}/banner
+HostKey {{.Dir}}/id_rsa
+HostKey {{.Dir}}/id_dsa
+HostKey {{.Dir}}/id_ecdsa
+HostCertificate {{.Dir}}/id_rsa-sha2-512-cert.pub
+Pidfile {{.Dir}}/sshd.pid
+KeyRegenerationInterval 3600
+ServerKeyBits 768
+SyslogFacility AUTH
+LogLevel DEBUG2
+LoginGraceTime 120
+PermitRootLogin no
+StrictModes no
+RSAAuthentication yes
+PubkeyAuthentication yes
+AuthorizedKeysFile {{.Dir}}/authorized_keys
+TrustedUserCAKeys {{.Dir}}/id_ecdsa.pub
+IgnoreRhosts yes
+RhostsRSAAuthentication no
+HostbasedAuthentication no
+PubkeyAcceptedKeyTypes=*
+# In recent versions of OpenSSH, Diffie-Hellman key exchange algorithms
+# are disabled by default. However, they are still included in our default
+# Key Exchange (KEX) configuration. We explicitly enable them here to
+# maintain compatibility for our test cases.
+KexAlgorithms +diffie-hellman-group14-sha1,diffie-hellman-group14-sha256,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group-exchange-sha256
+`
+ multiAuthSshdConfigTail = `
+UsePAM yes
+PasswordAuthentication yes
+ChallengeResponseAuthentication yes
+AuthenticationMethods {{.AuthMethods}}
+`
+ maxAuthTriesSshdConfigTail = `
+PasswordAuthentication yes
+MaxAuthTries 1
+`
+)
+
+var configTmpl = map[string]*template.Template{
+ "default": template.Must(template.New("").Parse(defaultSSHDConfig)),
+ "MultiAuth": template.Must(template.New("").Parse(defaultSSHDConfig + multiAuthSshdConfigTail)),
+ "MaxAuthTries": template.Must(template.New("").Parse(defaultSSHDConfig + maxAuthTriesSshdConfigTail))}
+
+type server struct {
+ t *testing.T
+ configfile string
+
+ testUser string // test username for sshd
+ testPasswd string // test password for sshd
+ sshdTestPwSo string // dynamic library to inject a custom password into sshd
+
+ lastDialConn net.Conn
+}
+
+type storedHostKey struct {
+ // keys map from an algorithm string to binary key data.
+ keys map[string][]byte
+
+ // checkCount counts the Check calls. Used for testing
+ // rekeying.
+ checkCount int
+}
+
+func (k *storedHostKey) Add(key ssh.PublicKey) {
+ if k.keys == nil {
+ k.keys = map[string][]byte{}
+ }
+ k.keys[key.Type()] = key.Marshal()
+}
+
+func (k *storedHostKey) Check(addr string, remote net.Addr, key ssh.PublicKey) error {
+ k.checkCount++
+ algo := key.Type()
+
+ if k.keys == nil || !bytes.Equal(key.Marshal(), k.keys[algo]) {
+ return fmt.Errorf("host key mismatch. Got %q, want %q", key, k.keys[algo])
+ }
+ return nil
+}
+
+func hostKeyDB() *storedHostKey {
+ keyChecker := &storedHostKey{}
+ keyChecker.Add(testPublicKeys["ecdsa"])
+ keyChecker.Add(testPublicKeys["rsa"])
+ keyChecker.Add(testPublicKeys["dsa"])
+ return keyChecker
+}
+
+func clientConfig() *ssh.ClientConfig {
+ config := &ssh.ClientConfig{
+ User: username(),
+ Auth: []ssh.AuthMethod{
+ ssh.PublicKeys(testSigners["user"]),
+ },
+ HostKeyCallback: hostKeyDB().Check,
+ HostKeyAlgorithms: []string{ // by default, don't allow certs as this affects the hostKeyDB checker
+ ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521,
+ ssh.KeyAlgoRSA, ssh.InsecureKeyAlgoDSA,
+ ssh.KeyAlgoED25519,
+ },
+ }
+ return config
+}
+
+// SSH reference tests run a connection against a reference implementation
+// (OpenSSH) of SSH and record the bytes of the resulting connection. The Go
+// code, during a test, is configured with deterministic randomness and so the
+// reference test can be reproduced exactly in the future.
+//
+// In order to save everyone who wishes to run the tests from needing the
+// reference implementation installed, the reference connections are saved in
+// files in the testdata directory. Thus running the tests involves nothing
+// external, but creating and updating them requires the reference
+// implementation.
+//
+// Tests can be updated by running them with the -update flag. This will cause
+// the test files for failing tests to be regenerated. Since the reference
+// implementation will always generate fresh random numbers, large parts of the
+// reference connection will always change.
+
+var (
+ update = flag.Bool("update", false, "update golden files on failure")
+)
+
+func runTestAndUpdateIfNeeded(t *testing.T, name string, run func(t *testing.T, update bool)) {
+ success := t.Run(name, func(t *testing.T) {
+ if !*update {
+ t.Parallel()
+ }
+ run(t, false)
+ })
+
+ if !success && *update {
+ t.Run(name+"#update", func(t *testing.T) {
+ run(t, true)
+ })
+ }
+}
+
+// recordingConn is a net.Conn that records the traffic that passes through it.
+// WriteTo can be used to produce output that can be later be loaded with
+// ParseTestData.
+type recordingConn struct {
+ net.Conn
+ clientToServer bool
+ sync.Mutex
+ flows [][]byte
+ reading bool
+}
+
+func (r *recordingConn) Read(b []byte) (n int, err error) {
+ if n, err = r.Conn.Read(b); n == 0 {
+ return
+ }
+ b = b[:n]
+
+ r.Lock()
+ defer r.Unlock()
+
+ if l := len(r.flows); l == 0 || !r.reading {
+ buf := make([]byte, len(b))
+ copy(buf, b)
+ r.flows = append(r.flows, buf)
+ } else {
+ r.flows[l-1] = append(r.flows[l-1], b[:n]...)
+ }
+ r.reading = true
+ return
+}
+
+func (r *recordingConn) Write(b []byte) (n int, err error) {
+ if n, err = r.Conn.Write(b); n == 0 {
+ return
+ }
+ b = b[:n]
+
+ r.Lock()
+ defer r.Unlock()
+
+ if l := len(r.flows); l == 0 || r.reading {
+ buf := make([]byte, len(b))
+ copy(buf, b)
+ r.flows = append(r.flows, buf)
+ } else {
+ r.flows[l-1] = append(r.flows[l-1], b[:n]...)
+ }
+ r.reading = false
+ return
+}
+
+// WriteTo writes Go source code to w that contains the recorded traffic.
+func (r *recordingConn) WriteTo(w io.Writer) (int64, error) {
+ var written int64
+ for i, flow := range r.flows {
+ source, dest := "client", "server"
+ if !r.clientToServer {
+ source, dest = dest, source
+ }
+ n, err := fmt.Fprintf(w, ">>> Flow %d (%s to %s)\n", i+1, source, dest)
+ written += int64(n)
+ if err != nil {
+ return written, err
+ }
+ dumper := hex.Dumper(w)
+ n, err = dumper.Write(flow)
+ written += int64(n)
+ if err != nil {
+ return written, err
+ }
+ err = dumper.Close()
+ if err != nil {
+ return written, err
+ }
+ r.clientToServer = !r.clientToServer
+ }
+ return written, nil
+}
+
+func parseTestData(r io.Reader) (flows [][]byte, err error) {
+ var currentFlow []byte
+
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ line := scanner.Text()
+ // If the line starts with ">>> " then it marks the beginning
+ // of a new flow.
+ if strings.HasPrefix(line, ">>> ") {
+ if len(currentFlow) > 0 || len(flows) > 0 {
+ flows = append(flows, currentFlow)
+ currentFlow = nil
+ }
+ continue
+ }
+
+ // Otherwise the line is a line of hex dump that looks like:
+ // 00000170 fc f5 06 bf (...) |.....X{&?......!|
+ // (Some bytes have been omitted from the middle section.)
+ _, after, ok := strings.Cut(line, " ")
+ if !ok {
+ return nil, errors.New("invalid test data")
+ }
+ line = after
+
+ before, _, ok := strings.Cut(line, "|")
+ if !ok {
+ return nil, errors.New("invalid test data")
+ }
+ line = before
+
+ hexBytes := strings.Fields(line)
+ for _, hexByte := range hexBytes {
+ val, err := strconv.ParseUint(hexByte, 16, 8)
+ if err != nil {
+ return nil, errors.New("invalid hex byte in test data: " + err.Error())
+ }
+ currentFlow = append(currentFlow, byte(val))
+ }
+ }
+
+ if len(currentFlow) > 0 {
+ flows = append(flows, currentFlow)
+ }
+
+ return flows, nil
+}
+
+func newReplayingConn(t testing.TB, flows [][]byte) net.Conn {
+ r := &replayingConn{
+ t: t,
+ flows: flows,
+ reading: false,
+ }
+ r.readCond = sync.NewCond(&r.Mutex)
+ return r
+}
+
+// replayingConn is a net.Conn that replays flows recorded by recordingConn.
+type replayingConn struct {
+ t testing.TB
+ sync.Mutex
+ flows [][]byte
+ reading bool
+ // SSH channels use a read loop goroutine, we use this condition to wait
+ // until we are ready to read/write.
+ readCond *sync.Cond
+}
+
+var _ net.Conn = (*replayingConn)(nil)
+
+func (r *replayingConn) Read(b []byte) (n int, err error) {
+ r.Lock()
+ defer r.Unlock()
+
+ for !r.reading {
+ r.readCond.Wait()
+ }
+
+ // Some tests run commands that return no output.
+ if len(r.flows) == 0 {
+ return 0, nil
+ }
+
+ n = copy(b, r.flows[0])
+ r.flows[0] = r.flows[0][n:]
+ if len(r.flows[0]) == 0 {
+ r.flows = r.flows[1:]
+ r.reading = false
+ r.readCond.Broadcast()
+ if len(r.flows) == 0 {
+ return n, io.EOF
+ }
+ }
+ return n, nil
+}
+
+func (r *replayingConn) Write(b []byte) (n int, err error) {
+ r.Lock()
+ defer r.Unlock()
+
+ for r.reading {
+ r.readCond.Wait()
+ }
+
+ if !bytes.HasPrefix(r.flows[0], b) {
+ r.t.Errorf("write mismatch: expected %x, got %x", r.flows[0], b)
+ r.reading = true
+ r.readCond.Broadcast()
+ return 0, fmt.Errorf("write mismatch")
+ }
+ r.flows[0] = r.flows[0][len(b):]
+ if len(r.flows[0]) == 0 {
+ r.flows = r.flows[1:]
+ r.reading = true
+ r.readCond.Broadcast()
+ }
+ return len(b), nil
+}
+
+func (r *replayingConn) Close() error {
+ r.Lock()
+ defer r.Unlock()
+
+ if len(r.flows) > 0 {
+ r.t.Errorf("closed with unfinished flows: %d", len(r.flows))
+ return fmt.Errorf("unexpected close")
+ }
+ return nil
+}
+
+func (r *replayingConn) LocalAddr() net.Addr { return nil }
+func (r *replayingConn) RemoteAddr() net.Addr { return nil }
+func (r *replayingConn) SetDeadline(_ time.Time) error { return nil }
+func (r *replayingConn) SetReadDeadline(_ time.Time) error { return nil }
+func (r *replayingConn) SetWriteDeadline(_ time.Time) error { return nil }
+
+func username() string {
+ var username string
+ if user, err := user.Current(); err == nil {
+ username = user.Username
+ } else {
+ // user.Current() currently requires cgo. If an error is
+ // returned attempt to get the username from the environment.
+ log.Printf("user.Current: %v; falling back on $USER", err)
+ username = os.Getenv("USER")
+ }
+ if username == "" {
+ panic("Unable to get username")
+ }
+ return username
+}
+
+func writeFile(path string, contents []byte) {
+ f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ if _, err := f.Write(contents); err != nil {
+ panic(err)
+ }
+}
+
+// setDeterministicRandomSource sets a deterministic random source for the
+// provided ssh.Config. It is intended solely for use in test cases, as
+// deterministic randomness is insecure and should never be used in production
+// environments. A deterministic random source is required to enable consistent
+// testing against recorded session files.
+func setDeterministicRandomSource(config *ssh.Config) {
+ config.Rand = sha3.NewShake128()
+}
+
+func TestMain(m *testing.M) {
+ flag.Usage = func() {
+ fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args)
+ flag.PrintDefaults()
+ }
+
+ flag.Parse()
+ os.Exit(m.Run())
+}
diff --git a/local_crypto_patch/contents/ssh/test/server_test.go b/local_crypto_patch/contents/ssh/test/server_test.go
new file mode 100644
index 0000000000..5c04fba98c
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/server_test.go
@@ -0,0 +1,98 @@
+// 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 test
+
+import (
+ "net"
+
+ "golang.org/x/crypto/ssh"
+)
+
+type exitStatusMsg struct {
+ Status uint32
+}
+
+// goTestServer is a test Go SSH server that accepts public key and certificate
+// authentication and replies with a 0 exit status to any exec request without
+// running any commands.
+type goTestServer struct {
+ listener net.Listener
+ config *ssh.ServerConfig
+ done <-chan struct{}
+}
+
+func newTestServer(config *ssh.ServerConfig) (*goTestServer, error) {
+ server := &goTestServer{
+ config: config,
+ }
+ listener, err := net.Listen("tcp", "127.0.0.1:")
+ if err != nil {
+ return nil, err
+ }
+ server.listener = listener
+ done := make(chan struct{}, 1)
+ server.done = done
+ go server.acceptConnections(done)
+
+ return server, nil
+}
+
+func (s *goTestServer) port() (string, error) {
+ _, port, err := net.SplitHostPort(s.listener.Addr().String())
+ return port, err
+}
+
+func (s *goTestServer) acceptConnections(done chan<- struct{}) {
+ defer close(done)
+
+ for {
+ c, err := s.listener.Accept()
+ if err != nil {
+ return
+ }
+ _, chans, reqs, err := ssh.NewServerConn(c, s.config)
+ if err != nil {
+ return
+ }
+ go ssh.DiscardRequests(reqs)
+ defer c.Close()
+
+ for newChannel := range chans {
+ if newChannel.ChannelType() != "session" {
+ newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
+ continue
+ }
+
+ channel, requests, err := newChannel.Accept()
+ if err != nil {
+ continue
+ }
+
+ go func(in <-chan *ssh.Request) {
+ for req := range in {
+ ok := false
+ switch req.Type {
+ case "exec":
+ ok = true
+ go func() {
+ channel.SendRequest("exit-status", false, ssh.Marshal(&exitStatusMsg{Status: 0}))
+ channel.Close()
+ }()
+ }
+ if req.WantReply {
+ req.Reply(ok, nil)
+ }
+ }
+ }(requests)
+ }
+ }
+}
+
+func (s *goTestServer) Close() error {
+ err := s.listener.Close()
+ // wait for the accept loop to exit
+ <-s.done
+ return err
+}
diff --git a/local_crypto_patch/contents/ssh/test/session_test.go b/local_crypto_patch/contents/ssh/test/session_test.go
new file mode 100644
index 0000000000..fda8d8c7bb
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/session_test.go
@@ -0,0 +1,334 @@
+// 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 !windows && !js && !wasip1
+
+package test
+
+// Session functional tests.
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+ "testing"
+
+ "golang.org/x/crypto/ssh"
+)
+
+func skipIfIssue64959(t *testing.T, err error) {
+ if err != nil && runtime.GOOS == "darwin" && strings.Contains(err.Error(), "ssh: unexpected packet in response to channel open: ") {
+ t.Helper()
+ t.Skipf("skipping test broken on some versions of macOS; see https://go.dev/issue/64959")
+ }
+}
+
+func TestRunCommandWeClosed(t *testing.T) {
+ server := newServer(t)
+ conn := server.Dial(clientConfig())
+ defer conn.Close()
+
+ session, err := conn.NewSession()
+ if err != nil {
+ skipIfIssue64959(t, err)
+ t.Fatalf("session failed: %v", err)
+ }
+ err = session.Shell()
+ if err != nil {
+ t.Fatalf("shell failed: %v", err)
+ }
+ err = session.Close()
+ if err != nil {
+ t.Fatalf("shell failed: %v", err)
+ }
+}
+
+func TestFuncLargeRead(t *testing.T) {
+ server := newServer(t)
+ conn := server.Dial(clientConfig())
+ defer conn.Close()
+
+ session, err := conn.NewSession()
+ if err != nil {
+ skipIfIssue64959(t, err)
+ t.Fatalf("unable to create new session: %s", err)
+ }
+
+ stdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Fatalf("unable to acquire stdout pipe: %s", err)
+ }
+
+ err = session.Start("dd if=/dev/urandom bs=2048 count=1024")
+ if err != nil {
+ t.Fatalf("unable to execute remote command: %s", err)
+ }
+
+ buf := new(bytes.Buffer)
+ n, err := io.Copy(buf, stdout)
+ if err != nil {
+ t.Fatalf("error reading from remote stdout: %s", err)
+ }
+
+ if n != 2048*1024 {
+ t.Fatalf("Expected %d bytes but read only %d from remote command", 2048, n)
+ }
+}
+
+func TestKeyChange(t *testing.T) {
+ server := newServer(t)
+ conf := clientConfig()
+ hostDB := hostKeyDB()
+ conf.HostKeyCallback = hostDB.Check
+ conf.RekeyThreshold = 1024
+ conn := server.Dial(conf)
+ defer conn.Close()
+
+ for i := 0; i < 4; i++ {
+ session, err := conn.NewSession()
+ if err != nil {
+ skipIfIssue64959(t, err)
+ t.Fatalf("unable to create new session: %s", err)
+ }
+
+ stdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Fatalf("unable to acquire stdout pipe: %s", err)
+ }
+
+ err = session.Start("dd if=/dev/urandom bs=1024 count=1")
+ if err != nil {
+ t.Fatalf("unable to execute remote command: %s", err)
+ }
+ buf := new(bytes.Buffer)
+ n, err := io.Copy(buf, stdout)
+ if err != nil {
+ t.Fatalf("error reading from remote stdout: %s", err)
+ }
+
+ want := int64(1024)
+ if n != want {
+ t.Fatalf("Expected %d bytes but read only %d from remote command", want, n)
+ }
+ }
+
+ if changes := hostDB.checkCount; changes < 4 {
+ t.Errorf("got %d key changes, want 4", changes)
+ }
+}
+
+func TestValidTerminalMode(t *testing.T) {
+ if runtime.GOOS == "aix" {
+ // On AIX, sshd cannot acquire /dev/pts/* if launched as
+ // a non-root user.
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+ server := newServer(t)
+ conn := server.Dial(clientConfig())
+ defer conn.Close()
+
+ session, err := conn.NewSession()
+ if err != nil {
+ skipIfIssue64959(t, err)
+ t.Fatalf("session failed: %v", err)
+ }
+ defer session.Close()
+
+ stdout, err := session.StdoutPipe()
+ if err != nil {
+ t.Fatalf("unable to acquire stdout pipe: %s", err)
+ }
+
+ stdin, err := session.StdinPipe()
+ if err != nil {
+ t.Fatalf("unable to acquire stdin pipe: %s", err)
+ }
+
+ tm := ssh.TerminalModes{ssh.ECHO: 0}
+ if err = session.RequestPty("xterm", 80, 40, tm); err != nil {
+ t.Fatalf("req-pty failed: %s", err)
+ }
+
+ err = session.Shell()
+ if err != nil {
+ t.Fatalf("session failed: %s", err)
+ }
+
+ if _, err := io.WriteString(stdin, "echo && echo SHELL $SHELL && stty -a && exit\n"); err != nil {
+ t.Fatal(err)
+ }
+
+ buf := new(strings.Builder)
+ if _, err := io.Copy(buf, stdout); err != nil {
+ t.Fatalf("reading failed: %s", err)
+ }
+
+ if testing.Verbose() {
+ t.Logf("echo && echo SHELL $SHELL && stty -a && exit:\n%s", buf)
+ }
+
+ shellLine := regexp.MustCompile("(?m)^SHELL (.*)$").FindStringSubmatch(buf.String())
+ if len(shellLine) != 2 {
+ t.Fatalf("missing output from echo SHELL $SHELL")
+ }
+ switch shell := filepath.Base(strings.TrimSpace(shellLine[1])); shell {
+ case "sh", "bash":
+ default:
+ t.Skipf("skipping test on non-Bourne shell %q", shell)
+ }
+
+ if sttyOutput := buf.String(); !strings.Contains(sttyOutput, "-echo ") {
+ t.Fatal("terminal mode failure: expected -echo in stty output")
+ }
+}
+
+func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
+ server := newServer(t)
+ conf := clientConfig()
+ conf.Ciphers = []string{cipher}
+ // Don't fail if sshd doesn't have the cipher.
+ conf.Ciphers = append(conf.Ciphers, cipherOrder...)
+ conn, err := server.TryDial(conf)
+ if err != nil {
+ t.Fatalf("TryDial: %v", err)
+ }
+ defer conn.Close()
+
+ numBytes := 4096
+
+ // Exercise receiving data from the server
+ session, err := conn.NewSession()
+ if err != nil {
+ skipIfIssue64959(t, err)
+ t.Fatalf("NewSession: %v", err)
+ }
+
+ out, err := session.Output(fmt.Sprintf("dd if=/dev/zero bs=%d count=1", numBytes))
+ if err != nil {
+ t.Fatalf("Output: %v", err)
+ }
+
+ if len(out) != numBytes {
+ t.Fatalf("got %d bytes, want %d bytes", len(out), numBytes)
+ }
+
+ // Exercise sending data to the server
+ if _, _, err := conn.Conn.SendRequest("drop-me", false, make([]byte, numBytes)); err != nil {
+ t.Fatalf("SendRequest: %v", err)
+ }
+}
+
+var deprecatedCiphers = []string{
+ ssh.InsecureCipherAES128CBC, ssh.InsecureCipherTripleDESCBC,
+ ssh.InsecureCipherRC4128, ssh.InsecureCipherRC4256,
+}
+
+func TestCiphers(t *testing.T) {
+ var config ssh.Config
+ config.SetDefaults()
+ cipherOrder := append(config.Ciphers, deprecatedCiphers...)
+
+ for _, ciph := range cipherOrder {
+ t.Run(ciph, func(t *testing.T) {
+ testOneCipher(t, ciph, cipherOrder)
+ })
+ }
+}
+
+func TestClientAuthAlgorithms(t *testing.T) {
+ for _, key := range []string{
+ "rsa",
+ "ecdsa",
+ "ed25519",
+ } {
+ t.Run(key, func(t *testing.T) {
+ server := newServer(t)
+ conf := clientConfig()
+ conf.SetDefaults()
+ conf.Auth = []ssh.AuthMethod{
+ ssh.PublicKeys(testSigners[key]),
+ }
+
+ conn, err := server.TryDial(conf)
+ if err == nil {
+ conn.Close()
+ } else {
+ t.Errorf("failed for key %q", key)
+ }
+ })
+ }
+}
+
+func TestClientAuthDisconnect(t *testing.T) {
+ // Use a static key that is not accepted by server.
+ // This key has been generated with following ssh-keygen command and
+ // used exclusively in this unit test:
+ // $ ssh-keygen -t RSA -b 2048 -f /tmp/static_key \
+ // -C "Static RSA key for golang.org/x/crypto/ssh unit test"
+
+ const privKeyData = `-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAQEAwV1Zg3MqX27nIQQNWd8V09P4q4F1fx7H2xNJdL3Yg3y91GFLJ92+
+0IiGV8n1VMGL/71PPhzyqBpUYSTpWjiU2JZSfA+iTg1GJBcOaEOA6vrXsTtXTHZ//mOT4d
+mlvuP4+9NqfCBLGXN7ZJpT+amkD8AVW9YW9QN3ipY61ZWxPaAocVpDd8rVgJTk54KvaPa7
+t4ddOSQDQq61aubIDR1Z3P+XjkB4piWOsbck3HJL+veTALy12C09tAhwUnZUAXS+DjhxOL
+xpDVclF/yXYhAvBvsjwyk/OC3+nK9F799hpQZsjxmbP7oN+tGwz06BUcAKi7u7QstENvvk
+85SDZy1q1QAAA/A7ylbJO8pWyQAAAAdzc2gtcnNhAAABAQDBXVmDcypfbuchBA1Z3xXT0/
+irgXV/HsfbE0l0vdiDfL3UYUsn3b7QiIZXyfVUwYv/vU8+HPKoGlRhJOlaOJTYllJ8D6JO
+DUYkFw5oQ4Dq+texO1dMdn/+Y5Ph2aW+4/j702p8IEsZc3tkmlP5qaQPwBVb1hb1A3eKlj
+rVlbE9oChxWkN3ytWAlOTngq9o9ru3h105JANCrrVq5sgNHVnc/5eOQHimJY6xtyTcckv6
+95MAvLXYLT20CHBSdlQBdL4OOHE4vGkNVyUX/JdiEC8G+yPDKT84Lf6cr0Xv32GlBmyPGZ
+s/ug360bDPToFRwAqLu7tCy0Q2++TzlINnLWrVAAAAAwEAAQAAAQAIvPDHMiyIxgCksGPF
+uyv9F9U4XjVip8/abE9zkAMJWW5++wuT/bRlBOUPRrWIXZEM9ETbtsqswo3Wxah+7CjRIH
+qR7SdFlYTP1jPk4yIKXF4OvggBUPySkMpAGJ9hwOMW8Ymcb4gn77JJ4aMoWIcXssje+XiC
+8iO+4UWU3SV2i6K7flK1UDCI5JVCyBr3DVf3QhMOgvwJl9TgD7FzWy1hkjuZq/Pzdv+fA2
+OfrUFiSukLNolidNoI9+KWa1yxixE+B2oN4Xan3ZbqGbL6Wc1dB+K9h/bNcu+SKf7fXWRi
+/vVG44A61xGDZzen1+eQlqFp7narkKXoaU71+45VXDThAAAAgBPWUdQykEEm0yOS6hPIW+
+hS8z1LXWGTEcag9fMwJXKE7cQFO3LEk+dXMbClHdhD/ydswOZYGSNepxwvmo/a5LiO2ulp
+W+5tnsNhcK3skdaf71t+boUEXBNZ6u3WNTkU7tDN8h9tebI+xlNceDGSGjOlNoHQVMKZdA
+W9TA4ZqXUPAAAAgQDWU0UZVOSCAOODPz4PYsbFKdCfXNP8O4+t9txyc9E3hsLAsVs+CpVX
+Gr219MGLrublzAxojipyzuQb6Tp1l9nsu7VkcBrPL8I1tokz0AyTnmNF3A9KszBal7gGNS
+a2qYuf6JO4cub1KzonxUJQHZPZq9YhCxOtDwTd+uyHZiPy9QAAAIEA5vayd+nfVJgCKTdf
+z5MFsxBSUj/cAYg7JYPS/0bZ5bEkLosL22wl5Tm/ZftJa8apkyBPhguAWt6jEWLoDiK+kn
+Fv0SaEq1HUdXgWmISVnWzv2pxdAtq/apmbxTg3iIJyrAwEDo13iImR3k6rNPx1m3i/jX56
+HLcvWM4Y6bFzbGEAAAA0U3RhdGljIFJTQSBrZXkgZm9yIGdvbGFuZy5vcmcveC9jcnlwdG
+8vc3NoIHVuaXQgdGVzdAECAwQFBgc=
+-----END OPENSSH PRIVATE KEY-----`
+
+ signer, err := ssh.ParsePrivateKey([]byte(privKeyData))
+ if err != nil {
+ t.Fatalf("failed to create signer from key: %v", err)
+ }
+
+ // Start server with MaxAuthTries 1 and publickey and password auth
+ // enabled
+ server := newServerForConfig(t, "MaxAuthTries", map[string]string{})
+
+ // Connect to server, expect failure, that PublicKeysCallback is called
+ // and that PasswordCallback is not called.
+ publicKeysCallbackCalled := false
+ config := clientConfig()
+ config.Auth = []ssh.AuthMethod{
+ ssh.PublicKeysCallback(func() ([]ssh.Signer, error) {
+ publicKeysCallbackCalled = true
+ return []ssh.Signer{signer}, nil
+ }),
+ ssh.PasswordCallback(func() (string, error) {
+ t.Errorf("unexpected call to PasswordCallback()")
+ return "notaverygoodpassword", nil
+ }),
+ }
+ client, err := server.TryDial(config)
+ if err == nil {
+ t.Errorf("expected TryDial() to fail")
+ _ = client.Close()
+ }
+ if !publicKeysCallbackCalled {
+ t.Errorf("expected PublicKeysCallback() to be called")
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/test/sshcli_test.go b/local_crypto_patch/contents/ssh/test/sshcli_test.go
new file mode 100644
index 0000000000..767dd6c8f4
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/sshcli_test.go
@@ -0,0 +1,163 @@
+// 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 test
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "testing"
+
+ "golang.org/x/crypto/internal/testenv"
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/testdata"
+)
+
+func sshClient(t *testing.T) string {
+ if testing.Short() {
+ t.Skip("Skipping test that executes OpenSSH in -short mode")
+ }
+ sshCLI := os.Getenv("SSH_CLI_PATH")
+ if sshCLI == "" {
+ sshCLI = "ssh"
+ }
+ var err error
+ sshCLI, err = exec.LookPath(sshCLI)
+ if err != nil {
+ t.Skipf("Can't find an ssh(1) client to test against: %v", err)
+ }
+ return sshCLI
+}
+
+// setupSSHCLIKeys writes the provided key files to a temporary directory and
+// returns the path to the private key.
+func setupSSHCLIKeys(t *testing.T, keyFiles map[string][]byte, privKeyName string) string {
+ tmpDir := t.TempDir()
+ for fn, content := range keyFiles {
+ if err := os.WriteFile(filepath.Join(tmpDir, fn), content, 0600); err != nil {
+ t.Fatalf("WriteFile(%q): %v", fn, err)
+ }
+ }
+ return filepath.Join(tmpDir, privKeyName)
+}
+
+func TestSSHCLIAuth(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skipf("always fails on Windows, see #64403")
+ }
+ sshCLI := sshClient(t)
+ keyFiles := map[string][]byte{
+ "rsa": testdata.PEMBytes["rsa"],
+ "rsa.pub": ssh.MarshalAuthorizedKey(testPublicKeys["rsa"]),
+ "rsa-cert.pub": testdata.SSHCertificates["rsa-user-testcertificate"],
+ }
+ keyPrivPath := setupSSHCLIKeys(t, keyFiles, "rsa")
+
+ certChecker := ssh.CertChecker{
+ IsUserAuthority: func(k ssh.PublicKey) bool {
+ return bytes.Equal(k.Marshal(), testPublicKeys["ca"].Marshal())
+ },
+ UserKeyFallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+ if conn.User() == "testpubkey" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, nil
+ }
+
+ return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
+ },
+ }
+
+ config := &ssh.ServerConfig{
+ PublicKeyCallback: certChecker.Authenticate,
+ }
+ config.AddHostKey(testSigners["rsa"])
+
+ server, err := newTestServer(config)
+ if err != nil {
+ t.Fatalf("unable to start test server: %v", err)
+ }
+ defer server.Close()
+
+ port, err := server.port()
+ if err != nil {
+ t.Fatalf("unable to get server port: %v", err)
+ }
+
+ // test public key authentication.
+ cmd := testenv.Command(t, sshCLI, "-vvv", "-i", keyPrivPath, "-o", "StrictHostKeyChecking=no",
+ "-p", port, "testpubkey@127.0.0.1", "true")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("public key authentication failed, error: %v, command output %q", err, string(out))
+ }
+ // Test SSH user certificate authentication.
+ // The username must match one of the principals included in the certificate.
+ // The certificate "rsa-user-testcertificate" has "testcertificate" as principal.
+ cmd = testenv.Command(t, sshCLI, "-vvv", "-i", keyPrivPath, "-o", "StrictHostKeyChecking=no",
+ "-p", port, "testcertificate@127.0.0.1", "true")
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("user certificate authentication failed, error: %v, command output %q", err, string(out))
+ }
+}
+
+func TestSSHCLIKeyExchanges(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skipf("always fails on Windows, see #64403")
+ }
+ sshCLI := sshClient(t)
+ keyFiles := map[string][]byte{
+ "rsa": testdata.PEMBytes["rsa"],
+ "rsa.pub": ssh.MarshalAuthorizedKey(testPublicKeys["rsa"]),
+ }
+ keyPrivPath := setupSSHCLIKeys(t, keyFiles, "rsa")
+
+ keyExchanges := append(ssh.SupportedAlgorithms().KeyExchanges, ssh.InsecureAlgorithms().KeyExchanges...)
+ for _, kex := range keyExchanges {
+ t.Run(kex, func(t *testing.T) {
+ cmd := testenv.Command(t, sshCLI, "-Q", "kex")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("%s failed to check if the KEX is supported, error: %v, command output %q", kex, err, string(out))
+ }
+ if !bytes.Contains(out, []byte(kex)) {
+ t.Skipf("KEX %q is not supported in the installed ssh CLI", kex)
+ }
+ config := &ssh.ServerConfig{
+ Config: ssh.Config{
+ KeyExchanges: []string{kex},
+ },
+ PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
+ if conn.User() == "testpubkey" && bytes.Equal(key.Marshal(), testPublicKeys["rsa"].Marshal()) {
+ return nil, nil
+ }
+
+ return nil, fmt.Errorf("pubkey for %q not acceptable", conn.User())
+ },
+ }
+ config.AddHostKey(testSigners["rsa"])
+
+ server, err := newTestServer(config)
+ if err != nil {
+ t.Fatalf("unable to start test server: %v", err)
+ }
+ defer server.Close()
+
+ port, err := server.port()
+ if err != nil {
+ t.Fatalf("unable to get server port: %v", err)
+ }
+
+ cmd = testenv.Command(t, sshCLI, "-vvv", "-i", keyPrivPath, "-o", "StrictHostKeyChecking=no",
+ "-o", fmt.Sprintf("KexAlgorithms=%s", kex), "-p", port, "testpubkey@127.0.0.1", "true")
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("%s failed, error: %v, command output %q", kex, err, string(out))
+ }
+ })
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/test/sshd_test_pw.c b/local_crypto_patch/contents/ssh/test/sshd_test_pw.c
new file mode 100644
index 0000000000..1e48619994
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/sshd_test_pw.c
@@ -0,0 +1,173 @@
+// 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.
+
+// sshd_test_pw.c
+// Wrapper to inject test password data for sshd PAM authentication
+//
+// This wrapper implements custom versions of getpwnam, getpwnam_r,
+// getspnam and getspnam_r. These functions first call their real
+// libc versions, then check if the requested user matches test user
+// specified in env variable TEST_USER and if so replace the password
+// with crypted() value of TEST_PASSWD env variable.
+//
+// Compile:
+// gcc -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c
+//
+// Compile with debug:
+// gcc -DVERBOSE -Wall -shared -o sshd_test_pw.so -fPIC sshd_test_pw.c
+//
+// Run sshd:
+// LD_PRELOAD="sshd_test_pw.so" TEST_USER="..." TEST_PASSWD="..." sshd ...
+
+//go:build ignore
+
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef VERBOSE
+#define DEBUG(X...) fprintf(stderr, X)
+#else
+#define DEBUG(X...) while (0) { }
+#endif
+
+/* crypt() password */
+static char *
+pwhash(char *passwd) {
+ return strdup(crypt(passwd, "$6$"));
+}
+
+/* Pointers to real functions in libc */
+static struct passwd * (*real_getpwnam)(const char *) = NULL;
+static int (*real_getpwnam_r)(const char *, struct passwd *, char *, size_t, struct passwd **) = NULL;
+static struct spwd * (*real_getspnam)(const char *) = NULL;
+static int (*real_getspnam_r)(const char *, struct spwd *, char *, size_t, struct spwd **) = NULL;
+
+/* Cached test user and test password */
+static char *test_user = NULL;
+static char *test_passwd_hash = NULL;
+
+static void
+init(void) {
+ /* Fetch real libc function pointers */
+ real_getpwnam = dlsym(RTLD_NEXT, "getpwnam");
+ real_getpwnam_r = dlsym(RTLD_NEXT, "getpwnam_r");
+ real_getspnam = dlsym(RTLD_NEXT, "getspnam");
+ real_getspnam_r = dlsym(RTLD_NEXT, "getspnam_r");
+
+ /* abort if env variables are not defined */
+ if (getenv("TEST_USER") == NULL || getenv("TEST_PASSWD") == NULL) {
+ fprintf(stderr, "env variables TEST_USER and TEST_PASSWD are missing\n");
+ abort();
+ }
+
+ /* Fetch test user and test password from env */
+ test_user = strdup(getenv("TEST_USER"));
+ test_passwd_hash = pwhash(getenv("TEST_PASSWD"));
+
+ DEBUG("sshd_test_pw init():\n");
+ DEBUG("\treal_getpwnam: %p\n", real_getpwnam);
+ DEBUG("\treal_getpwnam_r: %p\n", real_getpwnam_r);
+ DEBUG("\treal_getspnam: %p\n", real_getspnam);
+ DEBUG("\treal_getspnam_r: %p\n", real_getspnam_r);
+ DEBUG("\tTEST_USER: '%s'\n", test_user);
+ DEBUG("\tTEST_PASSWD: '%s'\n", getenv("TEST_PASSWD"));
+ DEBUG("\tTEST_PASSWD_HASH: '%s'\n", test_passwd_hash);
+}
+
+static int
+is_test_user(const char *name) {
+ if (test_user != NULL && strcmp(test_user, name) == 0)
+ return 1;
+ return 0;
+}
+
+/* getpwnam */
+
+struct passwd *
+getpwnam(const char *name) {
+ struct passwd *pw;
+
+ DEBUG("sshd_test_pw getpwnam(%s)\n", name);
+
+ if (real_getpwnam == NULL)
+ init();
+ if ((pw = real_getpwnam(name)) == NULL)
+ return NULL;
+
+ if (is_test_user(name))
+ pw->pw_passwd = strdup(test_passwd_hash);
+
+ return pw;
+}
+
+/* getpwnam_r */
+
+int
+getpwnam_r(const char *name,
+ struct passwd *pwd,
+ char *buf,
+ size_t buflen,
+ struct passwd **result) {
+ int r;
+
+ DEBUG("sshd_test_pw getpwnam_r(%s)\n", name);
+
+ if (real_getpwnam_r == NULL)
+ init();
+ if ((r = real_getpwnam_r(name, pwd, buf, buflen, result)) != 0 || *result == NULL)
+ return r;
+
+ if (is_test_user(name))
+ pwd->pw_passwd = strdup(test_passwd_hash);
+
+ return 0;
+}
+
+/* getspnam */
+
+struct spwd *
+getspnam(const char *name) {
+ struct spwd *sp;
+
+ DEBUG("sshd_test_pw getspnam(%s)\n", name);
+
+ if (real_getspnam == NULL)
+ init();
+ if ((sp = real_getspnam(name)) == NULL)
+ return NULL;
+
+ if (is_test_user(name))
+ sp->sp_pwdp = strdup(test_passwd_hash);
+
+ return sp;
+}
+
+/* getspnam_r */
+
+int
+getspnam_r(const char *name,
+ struct spwd *spbuf,
+ char *buf,
+ size_t buflen,
+ struct spwd **spbufp) {
+ int r;
+
+ DEBUG("sshd_test_pw getspnam_r(%s)\n", name);
+
+ if (real_getspnam_r == NULL)
+ init();
+ if ((r = real_getspnam_r(name, spbuf, buf, buflen, spbufp)) != 0)
+ return r;
+
+ if (is_test_user(name))
+ spbuf->sp_pwdp = strdup(test_passwd_hash);
+
+ return r;
+}
diff --git a/local_crypto_patch/contents/ssh/test/test_unix_test.go b/local_crypto_patch/contents/ssh/test/test_unix_test.go
new file mode 100644
index 0000000000..89743d204c
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/test_unix_test.go
@@ -0,0 +1,248 @@
+// 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9 || solaris
+
+package test
+
+// functional test harness for unix.
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/base64"
+ "fmt"
+ "net"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+ "testing"
+
+ "golang.org/x/crypto/internal/testenv"
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/testdata"
+)
+
+// unixConnection creates two halves of a connected net.UnixConn. It
+// is used for connecting the Go SSH client with sshd without opening
+// ports.
+func unixConnection() (*net.UnixConn, *net.UnixConn, error) {
+ dir, err := os.MkdirTemp("", "unixConnection")
+ if err != nil {
+ return nil, nil, err
+ }
+ defer os.Remove(dir)
+
+ addr := filepath.Join(dir, "ssh")
+ listener, err := net.Listen("unix", addr)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer listener.Close()
+ c1, err := net.Dial("unix", addr)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ c2, err := listener.Accept()
+ if err != nil {
+ c1.Close()
+ return nil, nil, err
+ }
+
+ return c1.(*net.UnixConn), c2.(*net.UnixConn), nil
+}
+
+func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.Client, error) {
+ return s.TryDialWithAddr(config, "")
+}
+
+// addr is the user specified host:port. While we don't actually dial it,
+// we need to know this for host key matching
+func (s *server) TryDialWithAddr(config *ssh.ClientConfig, addr string) (client *ssh.Client, err error) {
+ sshd, err := exec.LookPath("sshd")
+ if err != nil {
+ s.t.Skipf("skipping test: %v", err)
+ }
+
+ c1, c2, err := unixConnection()
+ if err != nil {
+ s.t.Fatalf("unixConnection: %v", err)
+ }
+ defer func() {
+ // Close c2 after we've started the sshd command so that it won't prevent c1
+ // from returning EOF when the sshd command exits.
+ c2.Close()
+
+ // Leave c1 open if we're returning a client that wraps it.
+ // (The client is responsible for closing it.)
+ // Otherwise, close it to free up the socket.
+ if client == nil {
+ c1.Close()
+ }
+ }()
+
+ f, err := c2.File()
+ if err != nil {
+ s.t.Fatalf("UnixConn.File: %v", err)
+ }
+ defer f.Close()
+
+ cmd := testenv.Command(s.t, sshd, "-f", s.configfile, "-i", "-e")
+ cmd.Stdin = f
+ cmd.Stdout = f
+ cmd.Stderr = new(bytes.Buffer)
+
+ if s.sshdTestPwSo != "" {
+ if s.testUser == "" {
+ s.t.Fatal("user missing from sshd_test_pw.so config")
+ }
+ if s.testPasswd == "" {
+ s.t.Fatal("password missing from sshd_test_pw.so config")
+ }
+ cmd.Env = append(os.Environ(),
+ fmt.Sprintf("LD_PRELOAD=%s", s.sshdTestPwSo),
+ fmt.Sprintf("TEST_USER=%s", s.testUser),
+ fmt.Sprintf("TEST_PASSWD=%s", s.testPasswd))
+ }
+
+ if err := cmd.Start(); err != nil {
+ s.t.Fatalf("s.cmd.Start: %v", err)
+ }
+ s.lastDialConn = c1
+ s.t.Cleanup(func() {
+ // Don't check for errors; if it fails it's most
+ // likely "os: process already finished", and we don't
+ // care about that. Use os.Interrupt, so child
+ // processes are killed too.
+ cmd.Process.Signal(os.Interrupt)
+ cmd.Wait()
+ if s.t.Failed() || testing.Verbose() {
+ // log any output from sshd process
+ s.t.Logf("sshd:\n%s", cmd.Stderr)
+ }
+ })
+
+ conn, chans, reqs, err := ssh.NewClientConn(c1, addr, config)
+ if err != nil {
+ return nil, err
+ }
+ return ssh.NewClient(conn, chans, reqs), nil
+}
+
+func (s *server) Dial(config *ssh.ClientConfig) *ssh.Client {
+ conn, err := s.TryDial(config)
+ if err != nil {
+ s.t.Fatalf("ssh.Client: %v", err)
+ }
+ return conn
+}
+
+// generate random password
+func randomPassword() (string, error) {
+ b := make([]byte, 12)
+ _, err := rand.Read(b)
+ if err != nil {
+ return "", err
+ }
+ return base64.RawURLEncoding.EncodeToString(b), nil
+}
+
+// setTestPassword is used for setting user and password data for sshd_test_pw.so
+// This function also checks that ./sshd_test_pw.so exists and if not calls s.t.Skip()
+func (s *server) setTestPassword(user, passwd string) error {
+ wd, _ := os.Getwd()
+ wrapper := filepath.Join(wd, "sshd_test_pw.so")
+ if _, err := os.Stat(wrapper); err != nil {
+ s.t.Skip(fmt.Errorf("sshd_test_pw.so is not available"))
+ return err
+ }
+
+ s.sshdTestPwSo = wrapper
+ s.testUser = user
+ s.testPasswd = passwd
+ return nil
+}
+
+// newServer returns a new mock ssh server.
+func newServer(t *testing.T) *server {
+ return newServerForConfig(t, "default", map[string]string{})
+}
+
+// newServerForConfig returns a new mock ssh server.
+func newServerForConfig(t *testing.T, config string, configVars map[string]string) *server {
+ if testing.Short() {
+ t.Skip("skipping test due to -short")
+ }
+ u, err := user.Current()
+ if err != nil {
+ t.Fatalf("user.Current: %v", err)
+ }
+ uname := u.Name
+ if uname == "" {
+ // Check the value of u.Username as u.Name
+ // can be "" on some OSes like AIX.
+ uname = u.Username
+ }
+ if uname == "root" {
+ t.Skip("skipping test because current user is root")
+ }
+ dir, err := os.MkdirTemp("", "sshtest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ f, err := os.Create(filepath.Join(dir, "sshd_config"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := configTmpl[config]; ok == false {
+ t.Fatal(fmt.Errorf("Invalid server config '%s'", config))
+ }
+ configVars["Dir"] = dir
+ err = configTmpl[config].Execute(f, configVars)
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.Close()
+
+ writeFile(filepath.Join(dir, "banner"), []byte("Server Banner"))
+
+ for k, v := range testdata.PEMBytes {
+ filename := "id_" + k
+ writeFile(filepath.Join(dir, filename), v)
+ writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k]))
+ }
+
+ for k, v := range testdata.SSHCertificates {
+ filename := "id_" + k + "-cert.pub"
+ writeFile(filepath.Join(dir, filename), v)
+ }
+
+ var authkeys bytes.Buffer
+ for k := range testdata.PEMBytes {
+ authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
+ }
+ writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes())
+ t.Cleanup(func() {
+ if err := os.RemoveAll(dir); err != nil {
+ t.Error(err)
+ }
+ })
+
+ return &server{
+ t: t,
+ configfile: f.Name(),
+ }
+}
+
+func newTempSocket(t *testing.T) (string, func()) {
+ dir, err := os.MkdirTemp("", "socket")
+ if err != nil {
+ t.Fatal(err)
+ }
+ deferFunc := func() { os.RemoveAll(dir) }
+ addr := filepath.Join(dir, "sock")
+ return addr, deferFunc
+}
diff --git a/local_crypto_patch/contents/ssh/test/testdata_test.go b/local_crypto_patch/contents/ssh/test/testdata_test.go
new file mode 100644
index 0000000000..a053f67eab
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/test/testdata_test.go
@@ -0,0 +1,64 @@
+// 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.
+
+// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places:
+// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three
+// instances.
+
+package test
+
+import (
+ "crypto/rand"
+ "fmt"
+
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/testdata"
+)
+
+var (
+ testPrivateKeys map[string]interface{}
+ testSigners map[string]ssh.Signer
+ testPublicKeys map[string]ssh.PublicKey
+)
+
+func init() {
+ var err error
+
+ n := len(testdata.PEMBytes)
+ testPrivateKeys = make(map[string]interface{}, n)
+ testSigners = make(map[string]ssh.Signer, n)
+ testPublicKeys = make(map[string]ssh.PublicKey, n)
+ for t, k := range testdata.PEMBytes {
+ testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k)
+ if err != nil {
+ panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err))
+ }
+ testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t])
+ if err != nil {
+ panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err))
+ }
+ testPublicKeys[t] = testSigners[t].PublicKey()
+ }
+
+ // Create a cert and sign it for use in tests.
+ testCert := &ssh.Certificate{
+ Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
+ ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage
+ ValidAfter: 0, // unix epoch
+ ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time.
+ Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil
+ Key: testPublicKeys["ecdsa"],
+ SignatureKey: testPublicKeys["rsa"],
+ Permissions: ssh.Permissions{
+ CriticalOptions: map[string]string{},
+ Extensions: map[string]string{},
+ },
+ }
+ testCert.SignCert(rand.Reader, testSigners["rsa"])
+ testPrivateKeys["cert"] = testPrivateKeys["ecdsa"]
+ testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"])
+ if err != nil {
+ panic(fmt.Sprintf("Unable to create certificate signer: %v", err))
+ }
+}
diff --git a/local_crypto_patch/contents/ssh/testdata/Client-BannerCallback b/local_crypto_patch/contents/ssh/testdata/Client-BannerCallback
new file mode 100644
index 0000000000..f8e8b1d4bf
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/testdata/Client-BannerCallback
@@ -0,0 +1,298 @@
+>>> Flow 1 (client to server)
+00000000 53 53 48 2d 32 2e 30 2d 47 6f 0d 0a |SSH-2.0-Go..|
+>>> Flow 2 (server to client)
+00000000 53 53 48 2d 32 2e 30 2d 4f 70 65 6e 53 53 48 5f |SSH-2.0-OpenSSH_|
+00000010 39 2e 39 0d 0a |9.9..|
+>>> Flow 3 (client to server)
+00000000 00 00 03 2c 11 14 7f 9c 2b a4 e8 8f 82 7d 61 60 |...,....+....}a`|
+00000010 45 50 76 05 85 3e 00 00 00 c9 63 75 72 76 65 32 |EPv..>....curve2|
+00000020 35 35 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 |5519-sha256,curv|
+00000030 65 32 35 35 31 39 2d 73 68 61 32 35 36 40 6c 69 |e25519-sha256@li|
+00000040 62 73 73 68 2e 6f 72 67 2c 65 63 64 68 2d 73 68 |bssh.org,ecdh-sh|
+00000050 61 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 68 |a2-nistp256,ecdh|
+00000060 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+00000070 63 64 68 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 |cdh-sha2-nistp52|
+00000080 31 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e |1,diffie-hellman|
+00000090 2d 67 72 6f 75 70 31 34 2d 73 68 61 32 35 36 2c |-group14-sha256,|
+000000a0 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e 2d 67 |diffie-hellman-g|
+000000b0 72 6f 75 70 31 34 2d 73 68 61 31 2c 65 78 74 2d |roup14-sha1,ext-|
+000000c0 69 6e 66 6f 2d 63 2c 6b 65 78 2d 73 74 72 69 63 |info-c,kex-stric|
+000000d0 74 2d 63 2d 76 30 30 40 6f 70 65 6e 73 73 68 2e |t-c-v00@openssh.|
+000000e0 63 6f 6d 00 00 00 57 65 63 64 73 61 2d 73 68 61 |com...Wecdsa-sha|
+000000f0 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 73 61 |2-nistp256,ecdsa|
+00000100 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+00000110 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 |cdsa-sha2-nistp5|
+00000120 32 31 2c 73 73 68 2d 72 73 61 2c 73 73 68 2d 64 |21,ssh-rsa,ssh-d|
+00000130 73 73 2c 73 73 68 2d 65 64 32 35 35 31 39 00 00 |ss,ssh-ed25519..|
+00000140 00 6c 61 65 73 31 32 38 2d 67 63 6d 40 6f 70 65 |.laes128-gcm@ope|
+00000150 6e 73 73 68 2e 63 6f 6d 2c 61 65 73 32 35 36 2d |nssh.com,aes256-|
+00000160 67 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |gcm@openssh.com,|
+00000170 63 68 61 63 68 61 32 30 2d 70 6f 6c 79 31 33 30 |chacha20-poly130|
+00000180 35 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 65 |5@openssh.com,ae|
+00000190 73 31 32 38 2d 63 74 72 2c 61 65 73 31 39 32 2d |s128-ctr,aes192-|
+000001a0 63 74 72 2c 61 65 73 32 35 36 2d 63 74 72 00 00 |ctr,aes256-ctr..|
+000001b0 00 6c 61 65 73 31 32 38 2d 67 63 6d 40 6f 70 65 |.laes128-gcm@ope|
+000001c0 6e 73 73 68 2e 63 6f 6d 2c 61 65 73 32 35 36 2d |nssh.com,aes256-|
+000001d0 67 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |gcm@openssh.com,|
+000001e0 63 68 61 63 68 61 32 30 2d 70 6f 6c 79 31 33 30 |chacha20-poly130|
+000001f0 35 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 65 |5@openssh.com,ae|
+00000200 73 31 32 38 2d 63 74 72 2c 61 65 73 31 39 32 2d |s128-ctr,aes192-|
+00000210 63 74 72 2c 61 65 73 32 35 36 2d 63 74 72 00 00 |ctr,aes256-ctr..|
+00000220 00 6e 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2d |.nhmac-sha2-256-|
+00000230 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |etm@openssh.com,|
+00000240 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2d 65 74 |hmac-sha2-512-et|
+00000250 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d |m@openssh.com,hm|
+00000260 61 63 2d 73 68 61 32 2d 32 35 36 2c 68 6d 61 63 |ac-sha2-256,hmac|
+00000270 2d 73 68 61 32 2d 35 31 32 2c 68 6d 61 63 2d 73 |-sha2-512,hmac-s|
+00000280 68 61 31 2c 68 6d 61 63 2d 73 68 61 31 2d 39 36 |ha1,hmac-sha1-96|
+00000290 00 00 00 6e 68 6d 61 63 2d 73 68 61 32 2d 32 35 |...nhmac-sha2-25|
+000002a0 36 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f |6-etm@openssh.co|
+000002b0 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2d |m,hmac-sha2-512-|
+000002c0 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |etm@openssh.com,|
+000002d0 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2c 68 6d |hmac-sha2-256,hm|
+000002e0 61 63 2d 73 68 61 32 2d 35 31 32 2c 68 6d 61 63 |ac-sha2-512,hmac|
+000002f0 2d 73 68 61 31 2c 68 6d 61 63 2d 73 68 61 31 2d |-sha1,hmac-sha1-|
+00000300 39 36 00 00 00 04 6e 6f 6e 65 00 00 00 04 6e 6f |96....none....no|
+00000310 6e 65 00 00 00 00 00 00 00 00 00 00 00 00 00 d7 |ne..............|
+00000320 3b 80 93 f6 ef bc 88 eb 1a 6e ac fa 66 ef 26 3c |;........n..f.&<|
+>>> Flow 4 (server to client)
+00000000 00 00 04 9c 0a 14 c8 9f 47 8f 1d b3 6c e4 86 d8 |........G...l...|
+00000010 29 d2 db 83 c7 ee 00 00 01 7a 73 6e 74 72 75 70 |)........zsntrup|
+00000020 37 36 31 78 32 35 35 31 39 2d 73 68 61 35 31 32 |761x25519-sha512|
+00000030 2c 73 6e 74 72 75 70 37 36 31 78 32 35 35 31 39 |,sntrup761x25519|
+00000040 2d 73 68 61 35 31 32 40 6f 70 65 6e 73 73 68 2e |-sha512@openssh.|
+00000050 63 6f 6d 2c 6d 6c 6b 65 6d 37 36 38 78 32 35 35 |com,mlkem768x255|
+00000060 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 65 32 |19-sha256,curve2|
+00000070 35 35 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 |5519-sha256,curv|
+00000080 65 32 35 35 31 39 2d 73 68 61 32 35 36 40 6c 69 |e25519-sha256@li|
+00000090 62 73 73 68 2e 6f 72 67 2c 65 63 64 68 2d 73 68 |bssh.org,ecdh-sh|
+000000a0 61 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 68 |a2-nistp256,ecdh|
+000000b0 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+000000c0 63 64 68 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 |cdh-sha2-nistp52|
+000000d0 31 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e |1,diffie-hellman|
+000000e0 2d 67 72 6f 75 70 2d 65 78 63 68 61 6e 67 65 2d |-group-exchange-|
+000000f0 73 68 61 32 35 36 2c 64 69 66 66 69 65 2d 68 65 |sha256,diffie-he|
+00000100 6c 6c 6d 61 6e 2d 67 72 6f 75 70 31 36 2d 73 68 |llman-group16-sh|
+00000110 61 35 31 32 2c 64 69 66 66 69 65 2d 68 65 6c 6c |a512,diffie-hell|
+00000120 6d 61 6e 2d 67 72 6f 75 70 31 38 2d 73 68 61 35 |man-group18-sha5|
+00000130 31 32 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 |12,diffie-hellma|
+00000140 6e 2d 67 72 6f 75 70 31 34 2d 73 68 61 32 35 36 |n-group14-sha256|
+00000150 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e 2d |,diffie-hellman-|
+00000160 67 72 6f 75 70 31 34 2d 73 68 61 31 2c 65 78 74 |group14-sha1,ext|
+00000170 2d 69 6e 66 6f 2d 73 2c 6b 65 78 2d 73 74 72 69 |-info-s,kex-stri|
+00000180 63 74 2d 73 2d 76 30 30 40 6f 70 65 6e 73 73 68 |ct-s-v00@openssh|
+00000190 2e 63 6f 6d 00 00 00 2d 72 73 61 2d 73 68 61 32 |.com...-rsa-sha2|
+000001a0 2d 35 31 32 2c 72 73 61 2d 73 68 61 32 2d 32 35 |-512,rsa-sha2-25|
+000001b0 36 2c 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 |6,ecdsa-sha2-nis|
+000001c0 74 70 32 35 36 00 00 00 6c 63 68 61 63 68 61 32 |tp256...lchacha2|
+000001d0 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 6e 73 |0-poly1305@opens|
+000001e0 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
+000001f0 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
+00000200 32 35 36 2d 63 74 72 2c 61 65 73 31 32 38 2d 67 |256-ctr,aes128-g|
+00000210 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 |cm@openssh.com,a|
+00000220 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 73 |es256-gcm@openss|
+00000230 68 2e 63 6f 6d 00 00 00 6c 63 68 61 63 68 61 32 |h.com...lchacha2|
+00000240 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 6e 73 |0-poly1305@opens|
+00000250 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
+00000260 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
+00000270 32 35 36 2d 63 74 72 2c 61 65 73 31 32 38 2d 67 |256-ctr,aes128-g|
+00000280 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 |cm@openssh.com,a|
+00000290 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 73 |es256-gcm@openss|
+000002a0 68 2e 63 6f 6d 00 00 00 d5 75 6d 61 63 2d 36 34 |h.com....umac-64|
+000002b0 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-etm@openssh.com|
+000002c0 2c 75 6d 61 63 2d 31 32 38 2d 65 74 6d 40 6f 70 |,umac-128-etm@op|
+000002d0 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 |enssh.com,hmac-s|
+000002e0 68 61 32 2d 32 35 36 2d 65 74 6d 40 6f 70 65 6e |ha2-256-etm@open|
+000002f0 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 |ssh.com,hmac-sha|
+00000300 32 2d 35 31 32 2d 65 74 6d 40 6f 70 65 6e 73 73 |2-512-etm@openss|
+00000310 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 31 2d |h.com,hmac-sha1-|
+00000320 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |etm@openssh.com,|
+00000330 75 6d 61 63 2d 36 34 40 6f 70 65 6e 73 73 68 2e |umac-64@openssh.|
+00000340 63 6f 6d 2c 75 6d 61 63 2d 31 32 38 40 6f 70 65 |com,umac-128@ope|
+00000350 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 |nssh.com,hmac-sh|
+00000360 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 68 61 32 |a2-256,hmac-sha2|
+00000370 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 31 00 00 |-512,hmac-sha1..|
+00000380 00 d5 75 6d 61 63 2d 36 34 2d 65 74 6d 40 6f 70 |..umac-64-etm@op|
+00000390 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 2d 31 |enssh.com,umac-1|
+000003a0 32 38 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 |28-etm@openssh.c|
+000003b0 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 |om,hmac-sha2-256|
+000003c0 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-etm@openssh.com|
+000003d0 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2d 65 |,hmac-sha2-512-e|
+000003e0 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 |tm@openssh.com,h|
+000003f0 6d 61 63 2d 73 68 61 31 2d 65 74 6d 40 6f 70 65 |mac-sha1-etm@ope|
+00000400 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 2d 36 34 |nssh.com,umac-64|
+00000410 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 |@openssh.com,uma|
+00000420 63 2d 31 32 38 40 6f 70 65 6e 73 73 68 2e 63 6f |c-128@openssh.co|
+00000430 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2c |m,hmac-sha2-256,|
+00000440 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2c 68 6d |hmac-sha2-512,hm|
+00000450 61 63 2d 73 68 61 31 00 00 00 15 6e 6f 6e 65 2c |ac-sha1....none,|
+00000460 7a 6c 69 62 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |zlib@openssh.com|
+00000470 00 00 00 15 6e 6f 6e 65 2c 7a 6c 69 62 40 6f 70 |....none,zlib@op|
+00000480 65 6e 73 73 68 2e 63 6f 6d 00 00 00 00 00 00 00 |enssh.com.......|
+00000490 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+>>> Flow 5 (client to server)
+00000000 00 00 00 2c 06 1e 00 00 00 20 aa 80 4b 53 a8 4b |...,..... ..KS.K|
+00000010 4c 0f fa ac a3 b8 5f 64 7d 36 42 e7 1d 56 45 7e |L....._d}6B..VE~|
+00000020 2b ac e0 f9 e7 60 f5 d7 55 37 b8 cc 87 3c 23 dc |+....`..U7...<#.|
+>>> Flow 6 (server to client)
+00000000 00 00 01 04 0a 1f 00 00 00 68 00 00 00 13 65 63 |.........h....ec|
+00000010 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 |dsa-sha2-nistp25|
+00000020 36 00 00 00 08 6e 69 73 74 70 32 35 36 00 00 00 |6....nistp256...|
+00000030 41 04 8b d1 dd c3 a2 af 65 c5 b1 7e 0d 88 0e 10 |A.......e..~....|
+00000040 3b 52 4a 43 b7 3c ed e9 9a 89 5d 2b 05 74 b7 7e |;RJC.<....]+.t.~|
+00000050 2b 1e 12 dd 2c 78 71 53 be eb f6 4e 5d 19 cf 98 |+...,xqS...N]...|
+00000060 d0 25 2d 4a a3 4a 15 2c 50 10 67 80 6d 2e d9 fa |.%-J.J.,P.g.m...|
+00000070 84 a8 00 00 00 20 37 00 93 5e 60 8c 9f e2 e0 4b |..... 7..^`....K|
+00000080 59 97 5c 6f d4 5a 3e 25 f8 fb 08 72 92 05 4d 6c |Y.\o.Z>%...r..Ml|
+00000090 80 c4 27 0e ec 26 00 00 00 64 00 00 00 13 65 63 |..'..&...d....ec|
+000000a0 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 |dsa-sha2-nistp25|
+000000b0 36 00 00 00 49 00 00 00 21 00 ec c1 21 3e be 3c |6...I...!...!>.<|
+000000c0 7a 50 7b 47 eb c2 92 bf 0b 93 cf 59 b5 84 2c f5 |zP{G.......Y..,.|
+000000d0 bf 9b 2d 01 cc 2e 42 15 b0 04 00 00 00 20 61 99 |..-...B...... a.|
+000000e0 9e 54 45 c1 54 a4 da d6 0b 3a d1 09 24 0f a0 b1 |.TE.T....:..$...|
+000000f0 66 b9 6f 06 a4 f4 09 7e 51 3f 5a e2 98 6c 00 00 |f.o....~Q?Z..l..|
+00000100 00 00 00 00 00 00 00 00 00 00 00 0c 0a 15 00 00 |................|
+00000110 00 00 00 00 00 00 00 00 00 00 01 40 57 9a 3e 61 |...........@W.>a|
+00000120 04 0a 62 05 83 8d 82 84 8a e6 91 0b 2c 28 d5 ce |..b.........,(..|
+00000130 b8 06 56 59 54 af 80 60 6c 9b 8c a5 1c 8a 5a 30 |..VYT..`l.....Z0|
+00000140 e3 70 23 61 86 ed a3 6a b6 fc 8d b8 d0 c3 72 25 |.p#a...j......r%|
+00000150 1c 4c 5c 84 a5 77 59 83 31 52 1b f4 09 8c 9f a7 |.L\..wY.1R......|
+00000160 b5 b2 ca f2 2c 8d ca 8f 2f 49 b9 83 65 af 97 5d |....,.../I..e..]|
+00000170 a3 a9 02 97 8f ee 42 30 9a 6c 8a 38 2c 23 33 1b |......B0.l.8,#3.|
+00000180 16 08 cd 7e 5e 2a 9b b4 34 26 9e 3b cb ec 37 0b |...~^*..4&.;..7.|
+00000190 39 8e 39 62 80 57 a2 95 d7 0d b6 ce 02 d8 66 b0 |9.9b.W........f.|
+000001a0 15 c4 cf 77 01 80 7b 42 41 96 4d 92 d6 5d b3 03 |...w..{BA.M..]..|
+000001b0 f3 83 22 c8 fe a7 7a c3 a4 aa 6a 75 23 de 4a 30 |.."...z...ju#.J0|
+000001c0 76 e6 8d 2a c7 3a c8 1a 60 f3 db ce 8a 53 44 2f |v..*.:..`....SD/|
+000001d0 ff 02 38 3e 23 28 cf 45 08 ae b8 a8 24 db 6f 1e |..8>#(.E....$.o.|
+000001e0 77 7a 33 19 e8 d0 13 11 63 19 b6 71 8b 19 3b 5b |wz3.....c..q..;[|
+000001f0 ad b5 3d cc 08 6f bd 89 cb fa c9 d7 e6 af e0 82 |..=..o..........|
+00000200 25 8b 60 bb 08 64 d3 71 d0 71 05 f1 94 1e 9b 7b |%.`..d.q.q.....{|
+00000210 07 35 64 fa 05 c3 23 4d 95 d0 4d 49 7d 93 61 72 |.5d...#M..MI}.ar|
+00000220 11 14 07 94 fb 3e a4 0f 7c ce 24 e3 a5 24 3e ae |.....>..|.$..$>.|
+00000230 e5 11 f5 29 f1 aa 2d 6c a8 8d 47 fb cb 0c 62 e2 |...)..-l..G...b.|
+00000240 cc 80 89 bc a6 76 e6 60 5c bf 86 78 58 ea e3 b2 |.....v.`\..xX...|
+00000250 b3 61 35 c4 80 23 f5 c6 ac 45 fd 5f 5b 41 8d 59 |.a5..#...E._[A.Y|
+00000260 eb 4c b2 3c fa df 0a 78 c4 af 59 42 |.L.<...x..YB|
+>>> Flow 7 (client to server)
+00000000 00 00 00 0c 0a 15 62 b8 d2 60 16 9a fa 2f 75 ab |......b..`.../u.|
+00000010 00 00 00 20 ad 6d e9 f4 a6 ee b0 dc 51 d0 16 a8 |... .m......Q...|
+00000020 48 f5 0b cf 15 42 51 3a 8a 0a 3d 4b cd 20 82 ce |H....BQ:..=K. ..|
+00000030 3f ac d4 33 0d ff 29 ea f8 28 2c dc e2 c4 bc 46 |?..3..)..(,....F|
+00000040 c2 5e 8a ce |.^..|
+>>> Flow 8 (server to client)
+00000000 00 00 00 20 25 d5 f5 9e c7 e4 76 15 a5 03 f4 30 |... %.....v....0|
+00000010 02 2e 64 e7 b7 a8 33 12 d1 d1 e0 62 3d b5 23 12 |..d...3....b=.#.|
+00000020 eb 61 31 21 5c 07 bb 54 bc 4f 29 5d 80 1e 7c 88 |.a1!\..T.O)]..|.|
+00000030 b9 46 b0 56 |.F.V|
+>>> Flow 9 (client to server)
+00000000 00 00 00 30 dc 6c b3 a7 e2 60 9c a8 41 3c 12 4b |...0.l...`..A<.K|
+00000010 f0 90 69 20 65 08 60 50 b0 52 22 ed 50 cf 5f 5c |..i e.`P.R".P._\|
+00000020 d3 ed 35 13 88 32 2c 7c d5 5b 6c f3 09 1b 50 0e |..5..2,|.[l...P.|
+00000030 9b bb ac 21 73 94 51 93 73 de 71 6c 0a fd 11 a4 |...!s.Q.s.ql....|
+00000040 77 70 55 ef |wpU.|
+>>> Flow 10 (server to client)
+00000000 00 00 00 20 32 b6 e9 e4 79 15 00 f8 5a 72 08 38 |... 2...y...Zr.8|
+00000010 55 af 59 21 6e 7f f4 6f f2 0e e8 d8 a5 61 bb a4 |U.Y!n..o.....a..|
+00000020 ce 26 3b 94 a8 e7 d9 5d 69 d7 6d d7 70 5f b8 a9 |.&;....]i.m.p_..|
+00000030 e9 ee 02 e8 00 00 00 40 21 fe 1d 4e 06 ce c3 51 |.......@!..N...Q|
+00000040 34 9d ad 04 60 1b 5e b7 89 c9 8d f5 24 d7 ff 69 |4...`.^.....$..i|
+00000050 a0 89 fe 95 58 66 7f af 63 1b b2 f5 b0 ad 26 b8 |....Xf..c.....&.|
+00000060 b4 25 33 9d 47 02 69 98 a1 a2 5c 30 5b 1c da 9d |.%3.G.i...\0[...|
+00000070 7d 75 8c f3 be 05 dd b2 0f 65 5c 1e 17 08 d7 00 |}u.......e\.....|
+00000080 03 97 99 ca a4 ca a3 c2 |........|
+>>> Flow 11 (client to server)
+00000000 00 00 01 60 4d a5 d0 b6 1a b3 dc c9 f2 da 8e 8a |...`M...........|
+00000010 03 a4 d3 61 0b 95 e5 7e db cc 31 49 21 c7 fa 95 |...a...~..1I!...|
+00000020 43 d0 ee 93 d6 45 29 85 3c 9a 82 e4 6b 65 5d 7c |C....E).<...ke]||
+00000030 cc 52 e8 83 b2 5a 65 ad 2f 83 1f b0 e7 a4 aa c7 |.R...Ze./.......|
+00000040 49 40 98 10 d8 af c2 c0 0f a2 54 d0 30 37 13 06 |I@........T.07..|
+00000050 12 63 a6 3b 73 30 74 d3 47 56 51 ba 23 32 c8 6f |.c.;s0t.GVQ.#2.o|
+00000060 e7 8a b1 27 fa f6 21 6c 26 2a f7 00 cb 14 d6 9a |...'..!l&*......|
+00000070 c2 f7 45 51 c8 20 6c 96 24 b2 64 57 06 23 31 ed |..EQ. l.$.dW.#1.|
+00000080 3a b2 10 7f 1d 8e 48 25 db 95 6c 0e 30 90 aa 69 |:.....H%..l.0..i|
+00000090 92 ae a5 1b 36 40 43 62 95 b0 d8 f6 bf 5c c1 8e |....6@Cb.....\..|
+000000a0 48 ab 38 f9 75 52 e1 8e b2 4b 75 6e f4 ac 03 24 |H.8.uR...Kun...$|
+000000b0 73 f4 01 51 98 0c 6f ed 4f 19 29 88 d7 08 13 d2 |s..Q..o.O.).....|
+000000c0 84 d9 54 63 b3 e3 8a 0d 42 6c f0 67 ac dc 4b 93 |..Tc....Bl.g..K.|
+000000d0 79 f2 70 ff 53 1f 27 f3 70 7c bf 75 33 79 64 3c |y.p.S.'.p|.u3yd<|
+000000e0 eb 30 1b dc c4 ff 11 f9 6f 74 f3 d1 1a c0 6b 4e |.0......ot....kN|
+000000f0 f2 56 32 5f 0d 82 eb 49 10 a1 0a df 47 af 18 c6 |.V2_...I....G...|
+00000100 21 c3 77 b4 39 7a 62 c3 aa f4 fb 19 95 bc 2d d2 |!.w.9zb.......-.|
+00000110 36 ca 6b d6 bf 91 00 3d 73 e8 41 65 60 44 89 48 |6.k....=s.Ae`D.H|
+00000120 57 89 9b 69 a4 c8 5e c2 df b8 bb e8 da e4 09 f9 |W..i..^.........|
+00000130 29 17 39 c6 35 88 7f 26 9b c8 94 02 03 c4 03 e8 |).9.5..&........|
+00000140 f6 df 68 52 e2 6e 84 91 10 7b 06 23 b0 4b f5 75 |..hR.n...{.#.K.u|
+00000150 4f b6 b8 b4 f2 b3 72 8a bf 91 92 1f 73 0a de e4 |O.....r.....s...|
+00000160 20 62 3d 30 50 c3 b9 7b cd 59 d1 83 fd e7 f0 cd | b=0P..{.Y......|
+00000170 c6 a0 11 12 |....|
+>>> Flow 12 (server to client)
+00000000 00 00 01 40 57 32 56 54 ba 8e a5 73 bc 12 1e 11 |...@W2VT...s....|
+00000010 02 03 72 e9 23 4f b2 70 a5 23 ea de f6 1e 25 e3 |..r.#O.p.#....%.|
+00000020 6c d0 8d c5 db da 81 e5 04 71 7e 9b 75 99 1b 70 |l........q~.u..p|
+00000030 ef 04 fc 64 ef 83 2b a9 4b 87 32 ed 28 98 17 ec |...d..+.K.2.(...|
+00000040 c6 e9 bf 8f 85 8e 93 ac 91 a2 fe a0 78 bc e3 88 |............x...|
+00000050 97 a4 3a 2e 3e 11 f5 04 0e 6f 38 6d 33 7f b7 10 |..:.>....o8m3...|
+00000060 9f 1d 84 45 02 11 da 63 37 f7 86 c7 1b b3 36 ec |...E...c7.....6.|
+00000070 ea b7 1a 2d 38 39 0a a1 0d 44 84 6b ad d7 22 28 |...-89...D.k.."(|
+00000080 27 ab 24 b5 c3 85 77 31 b3 97 0c 12 35 7f 3e c7 |'.$...w1....5.>.|
+00000090 d4 0e 3f 16 07 70 e8 fc b1 f7 29 94 28 2c ae 1b |..?..p....).(,..|
+000000a0 b3 1a 25 8a 2e e7 5f 52 5b 17 dd e5 ec e9 db 93 |..%..._R[.......|
+000000b0 12 29 3d ba bf ac 90 d4 37 42 36 53 ae 3e 8c db |.)=.....7B6S.>..|
+000000c0 d7 7c 48 c5 43 a1 ee d3 b6 74 ac 01 27 c9 06 0c |.|H.C....t..'...|
+000000d0 ae 31 68 a7 a9 ff 40 9e d1 f3 c9 ee ca 3b 98 13 |.1h...@......;..|
+000000e0 aa 5d 00 67 a0 fb 68 32 64 07 ff 4c f3 e1 4c be |.].g..h2d..L..L.|
+000000f0 38 3e 26 10 5c 9d 4b fc 7f 3f 22 0f ee 12 af f9 |8>&.\.K..?".....|
+00000100 3d 51 83 b1 5c 65 63 f1 fa 92 0e 1d 76 b9 27 a4 |=Q..\ec.....v.'.|
+00000110 a4 a3 ec ba e9 43 b1 77 e1 c7 db 9e 25 bc 93 4d |.....C.w....%..M|
+00000120 0c 9c bd 64 a0 db f4 a1 2b 9a 24 99 a9 0d be 17 |...d....+.$.....|
+00000130 cd 99 31 da 3f 83 66 c0 4e 6f a5 1a 1e ec 4d 07 |..1.?.f.No....M.|
+00000140 a4 04 d7 23 03 f7 ec 2c 8a 12 96 85 c1 d0 cb 59 |...#...,.......Y|
+00000150 e8 6b 4a 6d |.kJm|
+>>> Flow 13 (client to server)
+00000000 00 00 02 80 5e 7e 95 09 c2 ec 6e cd 14 10 2c 4f |....^~....n...,O|
+00000010 d3 f5 0a 89 6c 4a 3a b0 26 e4 c4 bc 51 26 ec a2 |....lJ:.&...Q&..|
+00000020 1a c9 35 8e b6 7a a7 88 1d fe 04 04 73 f1 e7 e1 |..5..z......s...|
+00000030 86 4c 3d 85 61 02 cc 5d 24 bb 87 a2 3e b2 5d bd |.L=.a..]$...>.].|
+00000040 19 5f ce 47 5a 6c 6c ab 45 84 2d 3e 51 82 90 e7 |._.GZll.E.->Q...|
+00000050 76 41 b7 37 0a b8 57 d9 3c 43 3e 3b 3d ba 39 41 |vA.7..W.;=.9A|
+00000060 01 84 1b 3f 80 e0 f4 9b 32 32 12 02 c5 f5 23 4e |...?....22....#N|
+00000070 36 75 37 2b ce 24 5e f7 9b 5b 3c 6e f8 2c aa d5 |6u7+.$^..[S...|
+000001d0 c0 31 f3 d0 19 4d 51 a5 05 da 8d d3 9b a7 58 29 |.1...MQ.......X)|
+000001e0 1e 25 c1 2e f8 27 ad 05 3f 11 b6 1f 9c 6d a5 d0 |.%...'..?....m..|
+000001f0 28 db ba 57 8a 12 9f c5 f3 73 93 48 05 ba d2 0a |(..W.....s.H....|
+00000200 a8 6d a4 a8 96 02 de a8 77 fd 01 e4 fe 14 87 46 |.m......w......F|
+00000210 d4 df f4 fc a7 e7 40 c2 3a 64 52 98 b8 5e 0f 00 |......@.:dR..^..|
+00000220 06 bf 70 d9 5b 2d 14 22 3e 6a a8 e2 c5 9b 91 76 |..p.[-.">j.....v|
+00000230 50 c9 92 b5 c0 31 32 a8 77 c9 be 02 00 68 08 c4 |P....12.w....h..|
+00000240 1e 08 d1 fa d8 e8 32 97 5a 0a 56 a8 9b 78 94 20 |......2.Z.V..x. |
+00000250 84 dd 56 18 2e eb 5d d2 e3 fd 28 05 b7 15 32 87 |..V...]...(...2.|
+00000260 c4 78 71 f6 11 41 22 da 3b b5 4c 60 71 df 6c eb |.xq..A".;.L`q.l.|
+00000270 c3 7c 4a 9c b3 e3 c9 12 2b a3 79 c9 e2 99 2a 4d |.|J.....+.y...*M|
+00000280 6b 88 2b 42 48 79 c7 52 33 f5 09 2e 60 1f 96 b1 |k.+BHy.R3...`...|
+00000290 b5 35 94 5a |.5.Z|
+>>> Flow 14 (server to client)
+00000000 00 00 00 10 60 4c c8 0d e2 72 d3 60 e0 94 a4 06 |....`L...r.`....|
+00000010 79 85 52 74 0f 2d 35 96 99 61 f6 8d d4 01 6e e3 |y.Rt.-5..a....n.|
+00000020 b2 54 8a 0f |.T..|
diff --git a/local_crypto_patch/contents/ssh/testdata/Client-Cipher-aes128-ctr b/local_crypto_patch/contents/ssh/testdata/Client-Cipher-aes128-ctr
new file mode 100644
index 0000000000..fdce462602
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/testdata/Client-Cipher-aes128-ctr
@@ -0,0 +1,295 @@
+>>> Flow 1 (client to server)
+00000000 53 53 48 2d 32 2e 30 2d 47 6f 0d 0a |SSH-2.0-Go..|
+>>> Flow 2 (server to client)
+00000000 53 53 48 2d 32 2e 30 2d 4f 70 65 6e 53 53 48 5f |SSH-2.0-OpenSSH_|
+00000010 39 2e 39 0d 0a |9.9..|
+>>> Flow 3 (client to server)
+00000000 00 00 02 5c 05 14 7f 9c 2b a4 e8 8f 82 7d 61 60 |...\....+....}a`|
+00000010 45 50 76 05 85 3e 00 00 00 c9 63 75 72 76 65 32 |EPv..>....curve2|
+00000020 35 35 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 |5519-sha256,curv|
+00000030 65 32 35 35 31 39 2d 73 68 61 32 35 36 40 6c 69 |e25519-sha256@li|
+00000040 62 73 73 68 2e 6f 72 67 2c 65 63 64 68 2d 73 68 |bssh.org,ecdh-sh|
+00000050 61 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 68 |a2-nistp256,ecdh|
+00000060 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+00000070 63 64 68 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 |cdh-sha2-nistp52|
+00000080 31 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e |1,diffie-hellman|
+00000090 2d 67 72 6f 75 70 31 34 2d 73 68 61 32 35 36 2c |-group14-sha256,|
+000000a0 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e 2d 67 |diffie-hellman-g|
+000000b0 72 6f 75 70 31 34 2d 73 68 61 31 2c 65 78 74 2d |roup14-sha1,ext-|
+000000c0 69 6e 66 6f 2d 63 2c 6b 65 78 2d 73 74 72 69 63 |info-c,kex-stric|
+000000d0 74 2d 63 2d 76 30 30 40 6f 70 65 6e 73 73 68 2e |t-c-v00@openssh.|
+000000e0 63 6f 6d 00 00 00 57 65 63 64 73 61 2d 73 68 61 |com...Wecdsa-sha|
+000000f0 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 73 61 |2-nistp256,ecdsa|
+00000100 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+00000110 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 |cdsa-sha2-nistp5|
+00000120 32 31 2c 73 73 68 2d 72 73 61 2c 73 73 68 2d 64 |21,ssh-rsa,ssh-d|
+00000130 73 73 2c 73 73 68 2d 65 64 32 35 35 31 39 00 00 |ss,ssh-ed25519..|
+00000140 00 0a 61 65 73 31 32 38 2d 63 74 72 00 00 00 0a |..aes128-ctr....|
+00000150 61 65 73 31 32 38 2d 63 74 72 00 00 00 6e 68 6d |aes128-ctr...nhm|
+00000160 61 63 2d 73 68 61 32 2d 32 35 36 2d 65 74 6d 40 |ac-sha2-256-etm@|
+00000170 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 |openssh.com,hmac|
+00000180 2d 73 68 61 32 2d 35 31 32 2d 65 74 6d 40 6f 70 |-sha2-512-etm@op|
+00000190 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 |enssh.com,hmac-s|
+000001a0 68 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 68 61 |ha2-256,hmac-sha|
+000001b0 32 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 31 2c |2-512,hmac-sha1,|
+000001c0 68 6d 61 63 2d 73 68 61 31 2d 39 36 00 00 00 6e |hmac-sha1-96...n|
+000001d0 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2d 65 74 |hmac-sha2-256-et|
+000001e0 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d |m@openssh.com,hm|
+000001f0 61 63 2d 73 68 61 32 2d 35 31 32 2d 65 74 6d 40 |ac-sha2-512-etm@|
+00000200 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 |openssh.com,hmac|
+00000210 2d 73 68 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 |-sha2-256,hmac-s|
+00000220 68 61 32 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 |ha2-512,hmac-sha|
+00000230 31 2c 68 6d 61 63 2d 73 68 61 31 2d 39 36 00 00 |1,hmac-sha1-96..|
+00000240 00 04 6e 6f 6e 65 00 00 00 04 6e 6f 6e 65 00 00 |..none....none..|
+00000250 00 00 00 00 00 00 00 00 00 00 00 d7 3b 80 93 f6 |............;...|
+>>> Flow 4 (server to client)
+00000000 00 00 04 9c 0a 14 3c 7e 44 d5 52 42 76 d2 6a fa |......<~D.RBv.j.|
+00000010 8b b0 ea cc ef 95 00 00 01 7a 73 6e 74 72 75 70 |.........zsntrup|
+00000020 37 36 31 78 32 35 35 31 39 2d 73 68 61 35 31 32 |761x25519-sha512|
+00000030 2c 73 6e 74 72 75 70 37 36 31 78 32 35 35 31 39 |,sntrup761x25519|
+00000040 2d 73 68 61 35 31 32 40 6f 70 65 6e 73 73 68 2e |-sha512@openssh.|
+00000050 63 6f 6d 2c 6d 6c 6b 65 6d 37 36 38 78 32 35 35 |com,mlkem768x255|
+00000060 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 65 32 |19-sha256,curve2|
+00000070 35 35 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 |5519-sha256,curv|
+00000080 65 32 35 35 31 39 2d 73 68 61 32 35 36 40 6c 69 |e25519-sha256@li|
+00000090 62 73 73 68 2e 6f 72 67 2c 65 63 64 68 2d 73 68 |bssh.org,ecdh-sh|
+000000a0 61 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 68 |a2-nistp256,ecdh|
+000000b0 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+000000c0 63 64 68 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 |cdh-sha2-nistp52|
+000000d0 31 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e |1,diffie-hellman|
+000000e0 2d 67 72 6f 75 70 2d 65 78 63 68 61 6e 67 65 2d |-group-exchange-|
+000000f0 73 68 61 32 35 36 2c 64 69 66 66 69 65 2d 68 65 |sha256,diffie-he|
+00000100 6c 6c 6d 61 6e 2d 67 72 6f 75 70 31 36 2d 73 68 |llman-group16-sh|
+00000110 61 35 31 32 2c 64 69 66 66 69 65 2d 68 65 6c 6c |a512,diffie-hell|
+00000120 6d 61 6e 2d 67 72 6f 75 70 31 38 2d 73 68 61 35 |man-group18-sha5|
+00000130 31 32 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 |12,diffie-hellma|
+00000140 6e 2d 67 72 6f 75 70 31 34 2d 73 68 61 32 35 36 |n-group14-sha256|
+00000150 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e 2d |,diffie-hellman-|
+00000160 67 72 6f 75 70 31 34 2d 73 68 61 31 2c 65 78 74 |group14-sha1,ext|
+00000170 2d 69 6e 66 6f 2d 73 2c 6b 65 78 2d 73 74 72 69 |-info-s,kex-stri|
+00000180 63 74 2d 73 2d 76 30 30 40 6f 70 65 6e 73 73 68 |ct-s-v00@openssh|
+00000190 2e 63 6f 6d 00 00 00 2d 72 73 61 2d 73 68 61 32 |.com...-rsa-sha2|
+000001a0 2d 35 31 32 2c 72 73 61 2d 73 68 61 32 2d 32 35 |-512,rsa-sha2-25|
+000001b0 36 2c 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 |6,ecdsa-sha2-nis|
+000001c0 74 70 32 35 36 00 00 00 6c 63 68 61 63 68 61 32 |tp256...lchacha2|
+000001d0 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 6e 73 |0-poly1305@opens|
+000001e0 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
+000001f0 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
+00000200 32 35 36 2d 63 74 72 2c 61 65 73 31 32 38 2d 67 |256-ctr,aes128-g|
+00000210 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 |cm@openssh.com,a|
+00000220 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 73 |es256-gcm@openss|
+00000230 68 2e 63 6f 6d 00 00 00 6c 63 68 61 63 68 61 32 |h.com...lchacha2|
+00000240 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 6e 73 |0-poly1305@opens|
+00000250 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
+00000260 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
+00000270 32 35 36 2d 63 74 72 2c 61 65 73 31 32 38 2d 67 |256-ctr,aes128-g|
+00000280 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 |cm@openssh.com,a|
+00000290 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 73 |es256-gcm@openss|
+000002a0 68 2e 63 6f 6d 00 00 00 d5 75 6d 61 63 2d 36 34 |h.com....umac-64|
+000002b0 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-etm@openssh.com|
+000002c0 2c 75 6d 61 63 2d 31 32 38 2d 65 74 6d 40 6f 70 |,umac-128-etm@op|
+000002d0 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 |enssh.com,hmac-s|
+000002e0 68 61 32 2d 32 35 36 2d 65 74 6d 40 6f 70 65 6e |ha2-256-etm@open|
+000002f0 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 |ssh.com,hmac-sha|
+00000300 32 2d 35 31 32 2d 65 74 6d 40 6f 70 65 6e 73 73 |2-512-etm@openss|
+00000310 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 31 2d |h.com,hmac-sha1-|
+00000320 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |etm@openssh.com,|
+00000330 75 6d 61 63 2d 36 34 40 6f 70 65 6e 73 73 68 2e |umac-64@openssh.|
+00000340 63 6f 6d 2c 75 6d 61 63 2d 31 32 38 40 6f 70 65 |com,umac-128@ope|
+00000350 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 |nssh.com,hmac-sh|
+00000360 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 68 61 32 |a2-256,hmac-sha2|
+00000370 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 31 00 00 |-512,hmac-sha1..|
+00000380 00 d5 75 6d 61 63 2d 36 34 2d 65 74 6d 40 6f 70 |..umac-64-etm@op|
+00000390 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 2d 31 |enssh.com,umac-1|
+000003a0 32 38 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 |28-etm@openssh.c|
+000003b0 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 |om,hmac-sha2-256|
+000003c0 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-etm@openssh.com|
+000003d0 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2d 65 |,hmac-sha2-512-e|
+000003e0 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 |tm@openssh.com,h|
+000003f0 6d 61 63 2d 73 68 61 31 2d 65 74 6d 40 6f 70 65 |mac-sha1-etm@ope|
+00000400 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 2d 36 34 |nssh.com,umac-64|
+00000410 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 |@openssh.com,uma|
+00000420 63 2d 31 32 38 40 6f 70 65 6e 73 73 68 2e 63 6f |c-128@openssh.co|
+00000430 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2c |m,hmac-sha2-256,|
+00000440 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2c 68 6d |hmac-sha2-512,hm|
+00000450 61 63 2d 73 68 61 31 00 00 00 15 6e 6f 6e 65 2c |ac-sha1....none,|
+00000460 7a 6c 69 62 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |zlib@openssh.com|
+00000470 00 00 00 15 6e 6f 6e 65 2c 7a 6c 69 62 40 6f 70 |....none,zlib@op|
+00000480 65 6e 73 73 68 2e 63 6f 6d 00 00 00 00 00 00 00 |enssh.com.......|
+00000490 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+>>> Flow 5 (client to server)
+00000000 00 00 00 2c 06 1e 00 00 00 20 13 cf b6 0f c2 c9 |...,..... ......|
+00000010 08 d9 7b f6 60 d4 53 7f 4b b1 29 37 59 98 3c dd |..{.`.S.K.)7Y.<.|
+00000020 ab b1 51 12 94 92 eb 56 4c 6f e8 a3 63 9c a8 a1 |..Q....VLo..c...|
+>>> Flow 6 (server to client)
+00000000 00 00 01 04 09 1f 00 00 00 68 00 00 00 13 65 63 |.........h....ec|
+00000010 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 |dsa-sha2-nistp25|
+00000020 36 00 00 00 08 6e 69 73 74 70 32 35 36 00 00 00 |6....nistp256...|
+00000030 41 04 8b d1 dd c3 a2 af 65 c5 b1 7e 0d 88 0e 10 |A.......e..~....|
+00000040 3b 52 4a 43 b7 3c ed e9 9a 89 5d 2b 05 74 b7 7e |;RJC.<....]+.t.~|
+00000050 2b 1e 12 dd 2c 78 71 53 be eb f6 4e 5d 19 cf 98 |+...,xqS...N]...|
+00000060 d0 25 2d 4a a3 4a 15 2c 50 10 67 80 6d 2e d9 fa |.%-J.J.,P.g.m...|
+00000070 84 a8 00 00 00 20 e9 56 86 74 34 11 d7 d3 48 a0 |..... .V.t4...H.|
+00000080 a8 6d bd 3f 00 ae da 7d 63 fb 6f b2 2d 90 c8 53 |.m.?...}c.o.-..S|
+00000090 3f 14 42 43 88 12 00 00 00 65 00 00 00 13 65 63 |?.BC.....e....ec|
+000000a0 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 |dsa-sha2-nistp25|
+000000b0 36 00 00 00 4a 00 00 00 21 00 ed ac 80 ff 90 17 |6...J...!.......|
+000000c0 05 2b 9b 55 14 f1 04 45 f5 8e 59 7b 0f a9 69 51 |.+.U...E..Y{..iQ|
+000000d0 39 04 ef ab 75 03 27 8d b2 a6 00 00 00 21 00 fb |9...u.'......!..|
+000000e0 5c 9b 84 8f 26 00 81 c4 7a 58 c8 c1 e7 3c 15 b7 |\...&...zX...<..|
+000000f0 a3 06 27 f8 e2 a1 b5 02 5d 7f 73 29 8a e5 98 00 |..'.....].s)....|
+00000100 00 00 00 00 00 00 00 00 00 00 00 0c 0a 15 00 00 |................|
+00000110 00 00 00 00 00 00 00 00 00 00 01 40 fc d3 1f 73 |...........@...s|
+00000120 95 a6 4b 07 99 e0 76 21 16 09 1e 47 bb 69 67 42 |..K...v!...G.igB|
+00000130 5c f2 e4 d2 24 8c a4 92 56 65 11 8a 8e 58 64 fd |\...$...Ve...Xd.|
+00000140 20 69 4d fd a3 61 44 1d d6 b8 40 3e 9a 07 05 d5 | iM..aD...@>....|
+00000150 be 5d 02 bf a0 6e 9c 1b 5d 3f b1 10 21 88 6b 78 |.]...n..]?..!.kx|
+00000160 5b 36 a4 ba 01 a4 62 50 e7 e2 d1 d2 7a 47 a3 fc |[6....bP....zG..|
+00000170 27 38 15 89 10 13 ee d4 bc 72 d2 16 95 d5 3a 52 |'8.......r....:R|
+00000180 df 54 1e 9c 6e dd 01 2a 30 3e 5c 0a 5e 08 19 ef |.T..n..*0>\.^...|
+00000190 d4 60 a6 f0 85 34 da 53 f6 c0 51 8b 5a 6b 3e 28 |.`...4.S..Q.Zk>(|
+000001a0 4a 7b 41 72 89 73 c3 16 1e d4 62 fe 21 3a 0f 74 |J{Ar.s....b.!:.t|
+000001b0 45 6d 61 01 3c b1 95 a0 37 6e b5 5d 38 a8 54 be |Ema.<...7n.]8.T.|
+000001c0 f2 67 0d 99 cc 61 4d 11 4a 64 93 93 05 47 0f 08 |.g...aM.Jd...G..|
+000001d0 75 fc 41 25 24 ce a6 b4 e7 71 c8 f4 eb 77 00 82 |u.A%$....q...w..|
+000001e0 a9 8d 5d 7a f5 b9 d1 01 cb 3f d1 30 41 c4 35 56 |..]z.....?.0A.5V|
+000001f0 d6 7c 13 e5 1a d2 ae 59 3a a1 35 bc 0f ca 67 73 |.|.....Y:.5...gs|
+00000200 39 da 3f 33 d0 d4 ad 16 37 0d 23 52 45 c5 48 a1 |9.?3....7.#RE.H.|
+00000210 0a 07 44 34 f2 a4 f1 59 91 2f ec 46 2b 47 fd 7a |..D4...Y./.F+G.z|
+00000220 80 82 60 7b 22 e0 aa e2 be 9f ca 5f d0 da 58 23 |..`{"......_..X#|
+00000230 b3 f9 30 e0 f6 48 a6 97 99 55 76 79 fd cc c3 fd |..0..H...Uvy....|
+00000240 1b c3 91 51 f3 a2 17 c4 50 ad 34 c9 66 47 5d f2 |...Q....P.4.fG].|
+00000250 0a 77 81 a2 74 35 3e e0 e1 ce 7b 10 22 9c 4a 10 |.w..t5>...{.".J.|
+00000260 6b b8 5e 21 49 dc cb 02 85 7a 96 4f d6 39 b5 e5 |k.^!I....z.O.9..|
+00000270 a1 40 7a 1d d6 b3 c5 3e 08 65 31 ad |.@z....>.e1.|
+>>> Flow 7 (client to server)
+00000000 00 00 00 0c 0a 15 e3 f9 ae 57 e2 35 b8 cc 87 3c |.........W.5...<|
+00000010 00 00 00 20 33 e5 12 ab 62 1c 2c 69 88 93 54 76 |... 3...b.,i..Tv|
+00000020 f7 f8 fd dc 60 17 93 77 87 cd 82 b1 07 a3 b1 ab |....`..w........|
+00000030 3c 1a 85 46 bb 6d 43 54 d5 63 49 d9 1d 51 55 89 |<..F.mCT.cI..QU.|
+00000040 e9 5c 5f 7c 62 b8 76 b4 0b c6 c4 e9 be d2 b2 9b |.\_|b.v.........|
+00000050 d3 dc 15 b8 |....|
+>>> Flow 8 (server to client)
+00000000 00 00 00 20 93 ba c4 77 30 8a 7f d5 56 ed 0b b8 |... ...w0...V...|
+00000010 85 c8 49 71 3a a1 8b 8b 94 d4 b1 e2 2b 0f 54 12 |..Iq:.......+.T.|
+00000020 74 89 06 dc a5 92 37 aa 5e 3a 24 4d 82 df a6 40 |t.....7.^:$M...@|
+00000030 74 48 79 db 42 bf 49 49 67 61 a3 e5 96 c2 f8 11 |tHy.B.IIga......|
+00000040 a6 74 2c 1b |.t,.|
+>>> Flow 9 (client to server)
+00000000 00 00 00 30 78 7e e0 d6 9f ee 3f ac 47 ee a1 ab |...0x~....?.G...|
+00000010 1f 7a d5 00 e0 b8 0a 00 69 61 db 12 f2 d4 b2 c5 |.z......ia......|
+00000020 a0 09 12 08 c2 fa c1 2a 3b 21 18 03 4f be 96 0a |.......*;!..O...|
+00000030 fc b4 36 8f c6 4d 8d 13 c7 ca 48 80 65 db 25 da |..6..M....H.e.%.|
+00000040 4e 36 29 44 fb 19 b4 96 53 b9 2f 74 56 c9 8c 66 |N6)D....S./tV..f|
+00000050 85 03 eb 98 |....|
+>>> Flow 10 (server to client)
+00000000 00 00 00 20 36 ba 73 95 01 22 06 8d e9 7c c1 41 |... 6.s.."...|.A|
+00000010 92 85 b9 b8 f4 37 52 b0 b4 99 8f 47 f7 ef 38 10 |.....7R....G..8.|
+00000020 a6 b0 35 c4 44 f1 72 89 0a 67 75 09 de 70 68 d5 |..5.D.r..gu..ph.|
+00000030 4e 81 af 5b e4 5b 61 02 aa ed bb 31 fe 70 59 f5 |N..[.[a....1.pY.|
+00000040 fc bf 7a 44 00 00 00 40 e3 5b ae 48 03 59 c7 d0 |..zD...@.[.H.Y..|
+00000050 22 f6 34 db c7 f4 2a 09 7e 0d ad c0 e4 b6 52 c2 |".4...*.~.....R.|
+00000060 7d de 8e 63 04 a0 06 d6 74 76 51 57 a6 16 7b 39 |}..c....tvQW..{9|
+00000070 90 8c af 32 c3 c1 84 3f 65 39 15 d7 0e 8e b9 8e |...2...?e9......|
+00000080 8f 13 97 9a ff f8 9b aa 5f 09 11 4c 46 e4 3d f6 |........_..LF.=.|
+00000090 d1 e3 ac b7 d5 3e ae 68 6e ca 52 c6 f9 78 c4 ba |.....>.hn.R..x..|
+000000a0 f7 37 0c 28 a5 7e 2d 45 |.7.(.~-E|
+>>> Flow 11 (client to server)
+00000000 00 00 01 60 90 e3 71 e0 56 c9 9e 55 b4 59 3c 28 |...`..q.V..U.Y<(|
+00000010 9e 51 1c 69 16 61 e8 39 fd 0c 67 80 eb f4 c7 70 |.Q.i.a.9..g....p|
+00000020 1a f9 6a b4 f3 b7 65 25 28 f5 a5 1a 56 b7 3c dc |..j...e%(...V.<.|
+00000030 5b 6c 77 df 08 a2 31 e1 7f 77 17 24 fd 5a a3 e6 |[lw...1..w.$.Z..|
+00000040 42 cf 4b da 75 e0 8b bf f3 e5 59 cb 11 95 d7 87 |B.K.u.....Y.....|
+00000050 21 69 64 91 91 dc d8 b3 11 48 df 36 72 b1 05 43 |!id......H.6r..C|
+00000060 d5 09 95 92 90 b9 d8 e4 0f 84 dd 3b c7 55 3b 93 |...........;.U;.|
+00000070 d6 d0 3c 08 04 84 5a 25 55 76 ad 70 da 4d 17 6c |..<...Z%Uv.p.M.l|
+00000080 5b 5e 56 0f 96 3b 4a 89 0f 23 7a a5 69 e3 b1 61 |[^V..;J..#z.i..a|
+00000090 0d 19 a9 88 e9 79 bc 70 43 15 48 5e 7e 7f 76 75 |.....y.pC.H^~.vu|
+000000a0 5f 8f d4 dc f7 90 af 67 e0 0a 62 56 0c 46 69 5c |_......g..bV.Fi\|
+000000b0 92 bd 1d 3e bb 96 9e e9 a5 77 8a a4 70 ab df 5e |...>.....w..p..^|
+000000c0 cb 09 7c 84 43 c4 d1 0e 86 0f ad e2 b7 fe d1 24 |..|.C..........$|
+000000d0 e3 59 5c 22 00 07 77 a6 05 d0 53 90 47 ae c7 72 |.Y\"..w...S.G..r|
+000000e0 f2 6a 56 98 f9 1e 6e 4c b1 a6 b8 3f 9f 04 c0 60 |.jV...nL...?...`|
+000000f0 9a 79 14 75 4d 5f e4 c4 70 da f9 3e 88 71 94 04 |.y.uM_..p..>.q..|
+00000100 5e 07 67 a9 d8 4c 4d e2 af 51 13 d1 e5 65 15 fe |^.g..LM..Q...e..|
+00000110 b7 cd 7d 79 3c ba 3f 99 f3 4a 1f 2b f5 7f ff d6 |..}y<.?..J.+....|
+00000120 77 48 34 96 33 1d 87 c6 41 f8 cf f8 b5 94 e8 47 |wH4.3...A......G|
+00000130 d2 12 4d 21 9c 4a d8 ea 02 27 d5 dd 69 87 9a c6 |..M!.J...'..i...|
+00000140 86 71 9f 97 14 d1 a3 03 35 d3 02 ed dd 5d 51 5d |.q......5....]Q]|
+00000150 f5 12 47 b9 c5 39 eb fb 2d dc 56 99 da 6e e9 0a |..G..9..-.V..n..|
+00000160 e6 50 94 7d f3 2e c0 64 e8 32 89 dc 43 3f ed f2 |.P.}...d.2..C?..|
+00000170 99 21 c6 81 6b a6 05 90 9d 88 1e 28 d7 15 8e b8 |.!..k......(....|
+00000180 6e e1 4a de |n.J.|
+>>> Flow 12 (server to client)
+00000000 00 00 01 40 c2 47 39 65 3f a2 7e 94 17 b3 10 86 |...@.G9e?.~.....|
+00000010 f9 73 32 6e f3 ec ab e3 ac d0 8a fb 82 d3 b1 e5 |.s2n............|
+00000020 45 f9 cb 72 41 f8 23 ad f8 ce b6 7d 94 f6 2b 56 |E..rA.#....}..+V|
+00000030 4e 99 b0 0d d5 11 2f 55 c3 67 44 bf 0f 72 5c 5a |N...../U.gD..r\Z|
+00000040 6f 41 16 b2 46 f2 f5 1d fe d7 3b cc 47 3f d7 97 |oA..F.....;.G?..|
+00000050 4e 9b 2c 84 de 31 10 2a 98 91 5e 55 15 fb df 61 |N.,..1.*..^U...a|
+00000060 6d 6d 0e 59 dd 9c f4 f3 bf bc 8a 6b 37 ce 78 c6 |mm.Y.......k7.x.|
+00000070 cc 21 c9 56 42 11 04 65 01 6c 94 29 e3 0c 12 31 |.!.VB..e.l.)...1|
+00000080 7e cc f7 3c e3 9d 74 d4 2a 5a 35 ba 85 33 86 e1 |~..<..t.*Z5..3..|
+00000090 99 2f 79 1e 7e 42 ca c4 b4 fa e7 60 2f f4 6f 80 |./y.~B.....`/.o.|
+000000a0 28 f1 9c f8 94 89 90 7f ad ce fd d4 be cd af 66 |(..............f|
+000000b0 93 4e 53 29 2c 15 4d 5d d4 36 73 97 0e 9c 53 95 |.NS),.M].6s...S.|
+000000c0 42 69 1a 80 34 ab 46 2b 97 7f 42 ca d9 33 94 e2 |Bi..4.F+..B..3..|
+000000d0 35 b9 c8 ec 98 34 87 4f fe 14 ce cd 47 c4 77 22 |5....4.O....G.w"|
+000000e0 74 8b f6 c6 4a dd 95 b2 57 b1 ba cd 3e bd a2 cb |t...J...W...>...|
+000000f0 c7 0f e2 75 24 48 a8 75 83 35 45 8f 11 16 a8 6c |...u$H.u.5E....l|
+00000100 05 3d d3 bb 2c 81 4c 67 ae 33 32 ae 28 d9 dd dd |.=..,.Lg.32.(...|
+00000110 ea 73 b7 7c 88 7e ac 72 fa b9 e4 ed 37 2e 0c 0a |.s.|.~.r....7...|
+00000120 8f 24 f7 80 15 af b3 61 4b 1e 83 73 34 7f bd d7 |.$.....aK..s4...|
+00000130 8f 24 25 93 43 75 3c a7 e1 dc ff e1 f7 e7 9a b5 |.$%.Cu<.........|
+00000140 18 1c bd 8d 81 12 a3 1a 16 1d fa e5 72 9c c7 9d |............r...|
+00000150 f3 d2 1b 76 ca a6 76 50 c5 83 56 1b 20 e5 c9 99 |...v..vP..V. ...|
+00000160 c0 68 fb 99 |.h..|
+>>> Flow 13 (client to server)
+00000000 00 00 02 80 93 38 6e 30 08 53 2e 06 7f 0b 15 9f |.....8n0.S......|
+00000010 f0 12 11 34 be b5 e6 77 ef 32 d1 e5 ab b7 25 5a |...4...w.2....%Z|
+00000020 2a 97 6b 83 f2 08 35 d0 ae 3b 45 ec 47 72 60 b0 |*.k...5..;E.Gr`.|
+00000030 2c c1 2c 5e 66 23 a0 b0 b5 c4 77 04 97 fc 89 14 |,.,^f#....w.....|
+00000040 e6 d2 60 a6 40 d0 78 22 da c9 6a a4 72 8b 63 ad |..`.@.x"..j.r.c.|
+00000050 60 57 b4 27 15 8f 66 0b a0 0c c2 c0 31 a2 74 4b |`W.'..f.....1.tK|
+00000060 89 4c 8f d1 cd 45 a2 3d bf f9 8d 72 95 4f fa c8 |.L...E.=...r.O..|
+00000070 46 09 ec 4c cc b0 7b 69 dd 20 3e e7 b0 ac 2d a4 |F..L..{i. >...-.|
+00000080 f0 34 88 02 24 e1 5b 53 c7 aa 06 96 fe 35 ab d0 |.4..$.[S.....5..|
+00000090 47 95 be 42 2f 75 22 d4 14 1f c4 cb 40 89 6f a0 |G..B/u".....@.o.|
+000000a0 ff 73 01 dc 40 9d c7 3d 8b 96 f1 f9 89 f6 b1 fe |.s..@..=........|
+000000b0 00 35 53 9c f7 09 25 a6 90 90 bb c8 a7 a2 88 e0 |.5S...%.........|
+000000c0 ee 78 97 a8 d2 26 b7 b4 8c c6 3d 10 16 29 4b 6c |.x...&....=..)Kl|
+000000d0 ee 30 90 73 09 57 f2 e1 c3 90 5f bc a2 48 aa 7b |.0.s.W...._..H.{|
+000000e0 34 a3 95 1c 33 29 04 e4 97 f9 5f 62 5b 70 a0 c7 |4...3)...._b[p..|
+000000f0 35 0f 28 ea 66 f8 42 61 a5 09 c3 c3 f1 81 26 66 |5.(.f.Ba......&f|
+00000100 c3 c7 b9 b8 8e eb fa 22 30 a7 61 39 35 04 ba dc |......."0.a95...|
+00000110 ff ab 5c 4e f6 0a 1d dc da 1d 37 88 fb 13 83 9b |..\N......7.....|
+00000120 64 7c 5a 80 dc 05 df 3c 5a 36 16 67 6f 3f eb d1 |d|Z......U.7.J].....e|
+000001d0 94 66 7e 4e e2 94 b4 f0 5a 83 08 f6 e1 ce 62 e5 |.f~N....Z.....b.|
+000001e0 24 d6 98 6e a7 10 23 03 57 e8 03 b1 b0 84 53 0b |$..n..#.W.....S.|
+000001f0 74 85 df fd b2 e1 0d 52 af 51 e3 64 5b 9d ed 42 |t......R.Q.d[..B|
+00000200 88 a6 bd f1 e5 61 05 a7 40 f6 0b c2 97 26 ad 3c |.....a..@....&.<|
+00000210 60 4f 75 d9 fb 20 b1 4d 96 a0 e8 80 c1 fe 1c 2c |`Ou.. .M.......,|
+00000220 d5 b2 17 d5 8a 63 50 16 ae 67 3c dc 56 28 68 6d |.....cP..g<.V(hm|
+00000230 3d ec ca 11 58 f4 98 30 dc 92 f4 38 6b 7c ff 4d |=...X..0...8k|.M|
+00000240 e4 f0 63 66 b4 e6 20 56 3a d0 b7 e8 10 d0 13 44 |..cf.. V:......D|
+00000250 a7 ac 20 2a ca 8c 4f 73 12 36 8d 29 66 92 0c 5a |.. *..Os.6.)f..Z|
+00000260 db 11 49 8b c2 2a e4 7c 2d 4c 26 5c d7 a5 37 2f |..I..*.|-L&\..7/|
+00000270 4b 4e 95 90 1b de 4c 7d 78 6a 62 ac b0 a0 e8 93 |KN....L}xjb.....|
+00000280 46 65 ce c9 39 19 36 14 d4 22 28 75 23 3b 33 97 |Fe..9.6.."(u#;3.|
+00000290 95 46 f1 7a 87 ae d4 4b dd 2d 3b 7e 5b 3a e1 d2 |.F.z...K.-;~[:..|
+000002a0 17 85 9c 7e |...~|
+>>> Flow 14 (server to client)
+00000000 00 00 00 10 f5 bc 41 31 d1 0d d3 47 ed 2b 87 a0 |......A1...G.+..|
+00000010 53 67 74 1d 19 c6 af f6 59 6a 8c d2 0d 29 0a 63 |Sgt.....Yj...).c|
+00000020 b1 0d cc db d3 b4 d3 1b d3 d0 73 a6 f8 ec 16 06 |..........s.....|
+00000030 de e2 0e b8 |....|
diff --git a/local_crypto_patch/contents/ssh/testdata/Client-Cipher-aes128-gcm@openssh.com b/local_crypto_patch/contents/ssh/testdata/Client-Cipher-aes128-gcm@openssh.com
new file mode 100644
index 0000000000..53b9c0a97f
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/testdata/Client-Cipher-aes128-gcm@openssh.com
@@ -0,0 +1,287 @@
+>>> Flow 1 (client to server)
+00000000 53 53 48 2d 32 2e 30 2d 47 6f 0d 0a |SSH-2.0-Go..|
+>>> Flow 2 (server to client)
+00000000 53 53 48 2d 32 2e 30 2d 4f 70 65 6e 53 53 48 5f |SSH-2.0-OpenSSH_|
+00000010 39 2e 39 0d 0a |9.9..|
+>>> Flow 3 (client to server)
+00000000 00 00 02 7c 0d 14 7f 9c 2b a4 e8 8f 82 7d 61 60 |...|....+....}a`|
+00000010 45 50 76 05 85 3e 00 00 00 c9 63 75 72 76 65 32 |EPv..>....curve2|
+00000020 35 35 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 |5519-sha256,curv|
+00000030 65 32 35 35 31 39 2d 73 68 61 32 35 36 40 6c 69 |e25519-sha256@li|
+00000040 62 73 73 68 2e 6f 72 67 2c 65 63 64 68 2d 73 68 |bssh.org,ecdh-sh|
+00000050 61 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 68 |a2-nistp256,ecdh|
+00000060 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+00000070 63 64 68 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 |cdh-sha2-nistp52|
+00000080 31 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e |1,diffie-hellman|
+00000090 2d 67 72 6f 75 70 31 34 2d 73 68 61 32 35 36 2c |-group14-sha256,|
+000000a0 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e 2d 67 |diffie-hellman-g|
+000000b0 72 6f 75 70 31 34 2d 73 68 61 31 2c 65 78 74 2d |roup14-sha1,ext-|
+000000c0 69 6e 66 6f 2d 63 2c 6b 65 78 2d 73 74 72 69 63 |info-c,kex-stric|
+000000d0 74 2d 63 2d 76 30 30 40 6f 70 65 6e 73 73 68 2e |t-c-v00@openssh.|
+000000e0 63 6f 6d 00 00 00 57 65 63 64 73 61 2d 73 68 61 |com...Wecdsa-sha|
+000000f0 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 73 61 |2-nistp256,ecdsa|
+00000100 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+00000110 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 |cdsa-sha2-nistp5|
+00000120 32 31 2c 73 73 68 2d 72 73 61 2c 73 73 68 2d 64 |21,ssh-rsa,ssh-d|
+00000130 73 73 2c 73 73 68 2d 65 64 32 35 35 31 39 00 00 |ss,ssh-ed25519..|
+00000140 00 16 61 65 73 31 32 38 2d 67 63 6d 40 6f 70 65 |..aes128-gcm@ope|
+00000150 6e 73 73 68 2e 63 6f 6d 00 00 00 16 61 65 73 31 |nssh.com....aes1|
+00000160 32 38 2d 67 63 6d 40 6f 70 65 6e 73 73 68 2e 63 |28-gcm@openssh.c|
+00000170 6f 6d 00 00 00 6e 68 6d 61 63 2d 73 68 61 32 2d |om...nhmac-sha2-|
+00000180 32 35 36 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e |256-etm@openssh.|
+00000190 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 |com,hmac-sha2-51|
+000001a0 32 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f |2-etm@openssh.co|
+000001b0 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2c |m,hmac-sha2-256,|
+000001c0 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2c 68 6d |hmac-sha2-512,hm|
+000001d0 61 63 2d 73 68 61 31 2c 68 6d 61 63 2d 73 68 61 |ac-sha1,hmac-sha|
+000001e0 31 2d 39 36 00 00 00 6e 68 6d 61 63 2d 73 68 61 |1-96...nhmac-sha|
+000001f0 32 2d 32 35 36 2d 65 74 6d 40 6f 70 65 6e 73 73 |2-256-etm@openss|
+00000200 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d |h.com,hmac-sha2-|
+00000210 35 31 32 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e |512-etm@openssh.|
+00000220 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 |com,hmac-sha2-25|
+00000230 36 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2c |6,hmac-sha2-512,|
+00000240 68 6d 61 63 2d 73 68 61 31 2c 68 6d 61 63 2d 73 |hmac-sha1,hmac-s|
+00000250 68 61 31 2d 39 36 00 00 00 04 6e 6f 6e 65 00 00 |ha1-96....none..|
+00000260 00 04 6e 6f 6e 65 00 00 00 00 00 00 00 00 00 00 |..none..........|
+00000270 00 00 00 d7 3b 80 93 f6 ef bc 88 eb 1a 6e ac fa |....;........n..|
+>>> Flow 4 (server to client)
+00000000 00 00 04 9c 0a 14 5b f0 eb 4c e5 38 1d df 00 dc |......[..L.8....|
+00000010 d7 5e f9 2f 04 3b 00 00 01 7a 73 6e 74 72 75 70 |.^./.;...zsntrup|
+00000020 37 36 31 78 32 35 35 31 39 2d 73 68 61 35 31 32 |761x25519-sha512|
+00000030 2c 73 6e 74 72 75 70 37 36 31 78 32 35 35 31 39 |,sntrup761x25519|
+00000040 2d 73 68 61 35 31 32 40 6f 70 65 6e 73 73 68 2e |-sha512@openssh.|
+00000050 63 6f 6d 2c 6d 6c 6b 65 6d 37 36 38 78 32 35 35 |com,mlkem768x255|
+00000060 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 65 32 |19-sha256,curve2|
+00000070 35 35 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 |5519-sha256,curv|
+00000080 65 32 35 35 31 39 2d 73 68 61 32 35 36 40 6c 69 |e25519-sha256@li|
+00000090 62 73 73 68 2e 6f 72 67 2c 65 63 64 68 2d 73 68 |bssh.org,ecdh-sh|
+000000a0 61 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 68 |a2-nistp256,ecdh|
+000000b0 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+000000c0 63 64 68 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 |cdh-sha2-nistp52|
+000000d0 31 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e |1,diffie-hellman|
+000000e0 2d 67 72 6f 75 70 2d 65 78 63 68 61 6e 67 65 2d |-group-exchange-|
+000000f0 73 68 61 32 35 36 2c 64 69 66 66 69 65 2d 68 65 |sha256,diffie-he|
+00000100 6c 6c 6d 61 6e 2d 67 72 6f 75 70 31 36 2d 73 68 |llman-group16-sh|
+00000110 61 35 31 32 2c 64 69 66 66 69 65 2d 68 65 6c 6c |a512,diffie-hell|
+00000120 6d 61 6e 2d 67 72 6f 75 70 31 38 2d 73 68 61 35 |man-group18-sha5|
+00000130 31 32 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 |12,diffie-hellma|
+00000140 6e 2d 67 72 6f 75 70 31 34 2d 73 68 61 32 35 36 |n-group14-sha256|
+00000150 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e 2d |,diffie-hellman-|
+00000160 67 72 6f 75 70 31 34 2d 73 68 61 31 2c 65 78 74 |group14-sha1,ext|
+00000170 2d 69 6e 66 6f 2d 73 2c 6b 65 78 2d 73 74 72 69 |-info-s,kex-stri|
+00000180 63 74 2d 73 2d 76 30 30 40 6f 70 65 6e 73 73 68 |ct-s-v00@openssh|
+00000190 2e 63 6f 6d 00 00 00 2d 72 73 61 2d 73 68 61 32 |.com...-rsa-sha2|
+000001a0 2d 35 31 32 2c 72 73 61 2d 73 68 61 32 2d 32 35 |-512,rsa-sha2-25|
+000001b0 36 2c 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 |6,ecdsa-sha2-nis|
+000001c0 74 70 32 35 36 00 00 00 6c 63 68 61 63 68 61 32 |tp256...lchacha2|
+000001d0 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 6e 73 |0-poly1305@opens|
+000001e0 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
+000001f0 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
+00000200 32 35 36 2d 63 74 72 2c 61 65 73 31 32 38 2d 67 |256-ctr,aes128-g|
+00000210 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 |cm@openssh.com,a|
+00000220 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 73 |es256-gcm@openss|
+00000230 68 2e 63 6f 6d 00 00 00 6c 63 68 61 63 68 61 32 |h.com...lchacha2|
+00000240 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 6e 73 |0-poly1305@opens|
+00000250 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
+00000260 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
+00000270 32 35 36 2d 63 74 72 2c 61 65 73 31 32 38 2d 67 |256-ctr,aes128-g|
+00000280 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 |cm@openssh.com,a|
+00000290 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 73 |es256-gcm@openss|
+000002a0 68 2e 63 6f 6d 00 00 00 d5 75 6d 61 63 2d 36 34 |h.com....umac-64|
+000002b0 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-etm@openssh.com|
+000002c0 2c 75 6d 61 63 2d 31 32 38 2d 65 74 6d 40 6f 70 |,umac-128-etm@op|
+000002d0 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 |enssh.com,hmac-s|
+000002e0 68 61 32 2d 32 35 36 2d 65 74 6d 40 6f 70 65 6e |ha2-256-etm@open|
+000002f0 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 |ssh.com,hmac-sha|
+00000300 32 2d 35 31 32 2d 65 74 6d 40 6f 70 65 6e 73 73 |2-512-etm@openss|
+00000310 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 31 2d |h.com,hmac-sha1-|
+00000320 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |etm@openssh.com,|
+00000330 75 6d 61 63 2d 36 34 40 6f 70 65 6e 73 73 68 2e |umac-64@openssh.|
+00000340 63 6f 6d 2c 75 6d 61 63 2d 31 32 38 40 6f 70 65 |com,umac-128@ope|
+00000350 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 |nssh.com,hmac-sh|
+00000360 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 68 61 32 |a2-256,hmac-sha2|
+00000370 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 31 00 00 |-512,hmac-sha1..|
+00000380 00 d5 75 6d 61 63 2d 36 34 2d 65 74 6d 40 6f 70 |..umac-64-etm@op|
+00000390 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 2d 31 |enssh.com,umac-1|
+000003a0 32 38 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 |28-etm@openssh.c|
+000003b0 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 |om,hmac-sha2-256|
+000003c0 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-etm@openssh.com|
+000003d0 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2d 65 |,hmac-sha2-512-e|
+000003e0 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 |tm@openssh.com,h|
+000003f0 6d 61 63 2d 73 68 61 31 2d 65 74 6d 40 6f 70 65 |mac-sha1-etm@ope|
+00000400 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 2d 36 34 |nssh.com,umac-64|
+00000410 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 |@openssh.com,uma|
+00000420 63 2d 31 32 38 40 6f 70 65 6e 73 73 68 2e 63 6f |c-128@openssh.co|
+00000430 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2c |m,hmac-sha2-256,|
+00000440 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2c 68 6d |hmac-sha2-512,hm|
+00000450 61 63 2d 73 68 61 31 00 00 00 15 6e 6f 6e 65 2c |ac-sha1....none,|
+00000460 7a 6c 69 62 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |zlib@openssh.com|
+00000470 00 00 00 15 6e 6f 6e 65 2c 7a 6c 69 62 40 6f 70 |....none,zlib@op|
+00000480 65 6e 73 73 68 2e 63 6f 6d 00 00 00 00 00 00 00 |enssh.com.......|
+00000490 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+>>> Flow 5 (client to server)
+00000000 00 00 00 2c 06 1e 00 00 00 20 7e 4a 3b cc bf 9a |...,..... ~J;...|
+00000010 2e 84 5f cb bb 32 fa b6 67 2f 28 60 b3 d3 48 e8 |.._..2..g/(`..H.|
+00000020 f9 c9 38 6f ae b4 a3 c4 5b 73 ae 57 e2 35 b8 cc |..8o....[s.W.5..|
+>>> Flow 6 (server to client)
+00000000 00 00 01 04 09 1f 00 00 00 68 00 00 00 13 65 63 |.........h....ec|
+00000010 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 |dsa-sha2-nistp25|
+00000020 36 00 00 00 08 6e 69 73 74 70 32 35 36 00 00 00 |6....nistp256...|
+00000030 41 04 8b d1 dd c3 a2 af 65 c5 b1 7e 0d 88 0e 10 |A.......e..~....|
+00000040 3b 52 4a 43 b7 3c ed e9 9a 89 5d 2b 05 74 b7 7e |;RJC.<....]+.t.~|
+00000050 2b 1e 12 dd 2c 78 71 53 be eb f6 4e 5d 19 cf 98 |+...,xqS...N]...|
+00000060 d0 25 2d 4a a3 4a 15 2c 50 10 67 80 6d 2e d9 fa |.%-J.J.,P.g.m...|
+00000070 84 a8 00 00 00 20 24 91 84 75 c5 47 0e f0 65 f7 |..... $..u.G..e.|
+00000080 b4 24 f9 69 a6 ba b0 3c fa c7 6b 47 67 93 ed 68 |.$.i...<..kGg..h|
+00000090 c7 24 9f 85 d1 20 00 00 00 65 00 00 00 13 65 63 |.$... ...e....ec|
+000000a0 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 |dsa-sha2-nistp25|
+000000b0 36 00 00 00 4a 00 00 00 21 00 be d5 c3 20 93 2a |6...J...!.... .*|
+000000c0 6f 26 6e ff 6e 3b fc 85 55 ff 33 8a a9 ce 0a 46 |o&n.n;..U.3....F|
+000000d0 33 6f b8 b6 8c ee 3d dd 0c 81 00 00 00 21 00 c7 |3o....=......!..|
+000000e0 f1 e0 5b 89 be 5f c2 55 77 8a 0b 08 e8 d0 f3 e6 |..[.._.Uw.......|
+000000f0 ce d0 fe 20 4e 64 8e 8e 76 1d 2c 86 06 48 0a 00 |... Nd..v.,..H..|
+00000100 00 00 00 00 00 00 00 00 00 00 00 0c 0a 15 00 00 |................|
+00000110 00 00 00 00 00 00 00 00 00 00 01 40 7a e3 f5 9b |...........@z...|
+00000120 b1 a1 ce 3c 76 3c 19 bd fd 43 ae a0 cd 49 a6 2e |....|
+00000240 bb 65 12 af 72 f7 3d 26 cd 29 7e 12 ee 90 e4 f8 |.e..r.=&.)~.....|
+00000250 f9 b5 93 11 1f 96 a9 82 5f d0 c3 d3 8a 5c ba cc |........_....\..|
+00000260 6b 6f ae 87 92 4e ba 86 a8 dc 68 6f |ko...N....ho|
+>>> Flow 7 (client to server)
+00000000 00 00 00 0c 0a 15 87 3c 23 dc 62 b8 d2 60 16 9a |.......<#.b..`..|
+00000010 00 00 00 20 79 7f 77 85 3c a1 cd 5b 68 fb 0d 92 |... y.w.<..[h...|
+00000020 3c d9 58 4c a3 88 ff 1c 94 e5 50 cb 1d ab 83 ab |<.XL......P.....|
+00000030 7e b2 ff 51 e4 3a d2 c6 65 c8 42 f5 de 8c 90 71 |~..Q.:..e.B....q|
+00000040 1c 99 e1 09 |....|
+>>> Flow 8 (server to client)
+00000000 00 00 00 20 9e 31 c9 bd a5 a4 95 f8 67 2b 5e 5a |... .1......g+^Z|
+00000010 63 53 db 9d a9 ae ef 08 43 f9 2b 0a 15 b7 38 71 |cS......C.+...8q|
+00000020 7e d0 68 16 d6 03 c4 0c 66 b9 e8 0e 4b 0f e8 53 |~.h.....f...K..S|
+00000030 e4 bc c2 60 |...`|
+>>> Flow 9 (client to server)
+00000000 00 00 00 30 9f 76 ec e4 9c a3 55 3e 8a 83 03 54 |...0.v....U>...T|
+00000010 c9 d1 e4 20 91 6b d9 2c 28 28 a5 7f 82 92 3a 89 |... .k.,((....:.|
+00000020 d0 cd 98 e7 a4 85 d7 31 a8 3e be a3 b3 c0 65 65 |.......1.>....ee|
+00000030 e6 6c 74 e5 38 13 42 51 12 e2 d0 1b 53 39 f6 b3 |.lt.8.BQ....S9..|
+00000040 e8 b2 73 3f |..s?|
+>>> Flow 10 (server to client)
+00000000 00 00 00 20 e4 45 97 15 a1 64 3b ff 65 37 c4 2e |... .E...d;.e7..|
+00000010 03 d6 8e e7 91 ca 58 65 55 f5 b9 fa ef 3b 7d cc |......XeU....;}.|
+00000020 ad 11 3b 54 c8 e3 2c f8 de 42 55 b2 97 b4 a7 ec |..;T..,..BU.....|
+00000030 d8 a0 90 98 00 00 00 40 5c bc 96 d5 fe 09 4a e9 |.......@\.....J.|
+00000040 d1 a5 f9 2e 9f 4c 0f 28 0c 66 79 aa 53 bb 78 d5 |.....L.(.fy.S.x.|
+00000050 41 a5 8f 7d 53 09 98 fb be 3d 26 35 f3 8a 76 88 |A..}S....=&5..v.|
+00000060 3f e1 66 b3 93 9e c9 00 b6 33 0e 12 ac 37 50 8f |?.f......3...7P.|
+00000070 f7 b3 25 51 71 9f f7 e1 f9 b3 7b c8 4d 0f ca 92 |..%Qq.....{.M...|
+00000080 21 32 5c f4 22 c1 63 90 |!2\.".c.|
+>>> Flow 11 (client to server)
+00000000 00 00 01 60 98 3f bc 85 ef 21 41 c0 61 b1 ad d1 |...`.?...!A.a...|
+00000010 d9 73 3d 13 54 fe d7 c1 d3 d8 65 02 e4 3f 32 eb |.s=.T.....e..?2.|
+00000020 78 2f fc fe 5f fc 48 ea 7f d9 53 51 59 a8 ad c0 |x/.._.H...SQY...|
+00000030 a4 65 c8 e6 6b 11 44 e7 ab 9a 1a 84 4e 83 1c a9 |.e..k.D.....N...|
+00000040 48 95 0a d1 f2 4c 64 c8 43 c4 0c 15 5b 5b af e0 |H....Ld.C...[[..|
+00000050 3c 21 0d ff f2 a8 7c 91 a1 d9 cc 7a f1 ff 53 56 |>> Flow 12 (server to client)
+00000000 00 00 01 40 ef 68 cf f5 46 8a f8 c9 2f 84 67 34 |...@.h..F.../.g4|
+00000010 bf 28 5f 6c b4 f5 87 1c 93 4a 87 30 5c cf 3c f1 |.(_l.....J.0\.<.|
+00000020 2b 86 d7 28 26 00 ae e5 8b 68 e7 66 ed 14 e7 95 |+..(&....h.f....|
+00000030 c1 33 d1 34 e0 d2 d8 f7 eb 7d 94 0c e4 22 c9 69 |.3.4.....}...".i|
+00000040 c8 68 b6 e3 01 fc f0 32 a8 2f 82 56 2d 04 fd 55 |.h.....2./.V-..U|
+00000050 fd 7a 89 d9 b6 19 e0 32 34 27 82 c5 61 ff 20 95 |.z.....24'..a. .|
+00000060 44 c6 fd fd 8c 62 3c 39 16 9e 62 6d 5d 30 3c b4 |D....b<9..bm]0<.|
+00000070 43 47 72 6e b7 2a 61 5f 95 9b 4f 9b b3 0b 76 e3 |CGrn.*a_..O...v.|
+00000080 c3 5f d0 61 8d a1 7d c7 6d 5b f3 54 57 8a 43 aa |._.a..}.m[.TW.C.|
+00000090 03 19 06 06 1e cd d9 b1 52 1d 59 c8 38 60 da 12 |........R.Y.8`..|
+000000a0 52 35 52 41 b0 45 73 b3 c3 f2 d8 e8 cc a5 c4 84 |R5RA.Es.........|
+000000b0 88 cd 78 4b 92 ec 02 02 25 fd e9 ea 2f 1d 62 ec |..xK....%.../.b.|
+000000c0 17 af 3e 2e bd d2 d5 d5 94 f8 30 ad 75 9d 36 31 |..>.......0.u.61|
+000000d0 51 37 e7 d7 1d 06 fe 4b 06 f6 6a 90 0e 53 0b 2e |Q7.....K..j..S..|
+000000e0 5b d2 7d d3 b3 28 f7 22 31 29 47 b3 f5 8a 40 d4 |[.}..(."1)G...@.|
+000000f0 bd 90 fa cc 0e 43 66 fb 3b cb 12 9a e6 4c 84 84 |.....Cf.;....L..|
+00000100 55 0d 84 cc 71 10 55 79 a4 51 1a b5 f4 61 d0 bf |U...q.Uy.Q...a..|
+00000110 07 34 b0 a1 d8 96 81 fe 51 b1 63 fa a5 90 82 87 |.4......Q.c.....|
+00000120 0d bc 15 63 d8 7f 8e 5f 4d c3 be 7d 54 36 3c f4 |...c..._M..}T6<.|
+00000130 4c 88 5e 8d 41 6f f3 74 25 b3 3b f1 3d 4d 8c 14 |L.^.Ao.t%.;.=M..|
+00000140 83 5e 5e b7 78 e5 8a 8f 73 28 30 7d 64 0f 5c bd |.^^.x...s(0}d.\.|
+00000150 31 e4 9a c4 |1...|
+>>> Flow 13 (client to server)
+00000000 00 00 02 80 00 cb 00 7f f2 7f 90 a4 6b 52 c0 6c |............kR.l|
+00000010 7b 1b 0a a0 0e 61 e9 93 be a0 96 da 8b 25 10 2d |{....a.......%.-|
+00000020 1f 12 3b 87 cf 98 45 5c 59 91 c0 5c 37 a5 0c 5b |..;...E\Y..\7..[|
+00000030 df 23 6b b6 b4 8a c4 f0 c7 b5 28 2f e3 21 b7 10 |.#k.......(/.!..|
+00000040 fc 8d 32 04 1d e9 11 7d 68 1c 68 eb 8c a3 38 45 |..2....}h.h...8E|
+00000050 a7 a2 01 37 97 ac b2 fd 90 29 15 0e c6 13 ce 66 |...7.....).....f|
+00000060 85 ff a7 4e 48 4c ba d9 fc 15 ec e3 64 98 36 9d |...NHL......d.6.|
+00000070 0b dd fe 21 8b 1f ba 81 39 ac 79 f0 b9 01 aa 09 |...!....9.y.....|
+00000080 64 a0 8e da 7b d4 e1 13 28 57 fe 1c 05 ec ad ed |d...{...(W......|
+00000090 38 de 3e 79 a0 74 d5 01 4e fe 4b e6 e8 53 d3 d6 |8.>y.t..N.K..S..|
+000000a0 03 a6 79 46 77 c1 5f ee a7 c3 c5 9e 97 f6 57 1d |..yFw._.......W.|
+000000b0 5e 35 3e 8a a7 fc 91 f3 f1 91 dd 6c ea ae d8 04 |^5>........l....|
+000000c0 5a 03 69 b4 1e fb 40 be b9 39 56 08 7c 32 ca a2 |Z.i...@..9V.|2..|
+000000d0 c5 e0 ec 0a 66 61 25 49 5c 78 ce 08 8c b6 39 3a |....fa%I\x....9:|
+000000e0 5e 29 da f3 a1 9b 0b 0b 70 8f 54 fd 1e c7 36 26 |^)......p.T...6&|
+000000f0 9d ae 82 79 0e fb 18 27 ce 45 6d 04 ce 0e a7 c2 |...y...'.Em.....|
+00000100 81 27 e8 53 8e 03 a7 55 c4 ca c4 48 26 c5 8d 2b |.'.S...U...H&..+|
+00000110 3b dc 66 ca 98 35 bb b7 ab 22 e3 93 64 6d e7 10 |;.f..5..."..dm..|
+00000120 8a 9b fb e7 af 24 b1 ff 58 e4 01 90 18 d6 2c 38 |.....$..X.....,8|
+00000130 4c aa b1 44 15 cd 9d 76 9f 8f a4 1d 11 36 96 41 |L..D...v.....6.A|
+00000140 1b 33 a3 ee 1f 4a e5 23 8c bc 82 a5 10 6a 3c 27 |.3...J.#.....j<'|
+00000150 68 7f 8f 8a b8 cc 22 1b 0f 1c b5 ea 51 05 5c 43 |h.....".....Q.\C|
+00000160 2d 85 c3 4e 2d d9 6c b3 4c 2c 5d 45 c0 77 85 20 |-..N-.l.L,]E.w. |
+00000170 df 40 38 0a e7 ab 33 f0 60 66 fa 2a 94 46 3b 73 |.@8...3.`f.*.F;s|
+00000180 9a 80 2b 5a 8b ec eb 0a c6 93 e0 85 e2 78 99 9e |..+Z.........x..|
+00000190 5d 03 3a ea 9c 43 e4 a2 05 5f 6d 3b bb e2 8a 70 |].:..C..._m;...p|
+000001a0 f8 ce bc 36 f2 9f 58 b6 42 30 83 2c 45 3e 5b 6d |...6..X.B0.,E>[m|
+000001b0 23 f1 8c 86 4a 42 cf c9 50 88 9c 4c 28 18 64 4b |#...JB..P..L(.dK|
+000001c0 14 48 82 b3 35 42 82 c7 3b 4a bf cb 7e c1 4a e2 |.H..5B..;J..~.J.|
+000001d0 e8 de a3 55 19 94 3e 38 80 2d a9 60 16 73 7e 85 |...U..>8.-.`.s~.|
+000001e0 50 47 4f 0a 26 d8 e9 b8 14 ab 1a 8f 67 9a a1 a5 |PGO.&.......g...|
+000001f0 0e fe 67 59 4f f6 2c 57 01 98 c5 65 b3 7c f0 57 |..gYO.,W...e.|.W|
+00000200 42 c4 30 41 8b cc f0 c7 05 32 a3 e0 ef 97 bf 23 |B.0A.....2.....#|
+00000210 f0 d8 fc 87 fc 13 9f 3d 33 6d 80 a2 e1 75 df 93 |.......=3m...u..|
+00000220 34 1b 9a 7f 36 c6 80 f5 75 c5 4c 0b 43 75 5a 97 |4...6...u.L.CuZ.|
+00000230 f6 c0 bf 67 86 8e 2e 8a 15 ef 30 cc a8 d6 43 3f |...g......0...C?|
+00000240 aa 1a 7a 39 bf 9d 45 8d 60 97 02 2d 7d 2b c3 b7 |..z9..E.`..-}+..|
+00000250 fb ea d0 9e 99 69 26 05 f7 bb 79 5d 9a fa 86 fb |.....i&...y]....|
+00000260 96 fe 42 58 32 7a 7c d1 05 0b 1b 7a 12 35 ea be |..BX2z|....z.5..|
+00000270 dc 56 19 8f 32 4c 92 17 53 d9 33 00 04 e8 6f ba |.V..2L..S.3...o.|
+00000280 4f bb 52 83 ff ba e6 f9 87 3c aa 6f c1 58 93 23 |O.R......<.o.X.#|
+00000290 b0 5e 26 42 |.^&B|
+>>> Flow 14 (server to client)
+00000000 00 00 00 10 79 b4 da e8 8c a4 14 53 a6 e7 37 65 |....y......S..7e|
+00000010 37 76 0e 7d ef a8 d9 91 1d 99 2a 57 95 fc 2a 97 |7v.}......*W..*.|
+00000020 37 12 13 73 |7..s|
diff --git a/local_crypto_patch/contents/ssh/testdata/Client-Cipher-aes192-ctr b/local_crypto_patch/contents/ssh/testdata/Client-Cipher-aes192-ctr
new file mode 100644
index 0000000000..ceb6a43f15
--- /dev/null
+++ b/local_crypto_patch/contents/ssh/testdata/Client-Cipher-aes192-ctr
@@ -0,0 +1,295 @@
+>>> Flow 1 (client to server)
+00000000 53 53 48 2d 32 2e 30 2d 47 6f 0d 0a |SSH-2.0-Go..|
+>>> Flow 2 (server to client)
+00000000 53 53 48 2d 32 2e 30 2d 4f 70 65 6e 53 53 48 5f |SSH-2.0-OpenSSH_|
+00000010 39 2e 39 0d 0a |9.9..|
+>>> Flow 3 (client to server)
+00000000 00 00 02 5c 05 14 7f 9c 2b a4 e8 8f 82 7d 61 60 |...\....+....}a`|
+00000010 45 50 76 05 85 3e 00 00 00 c9 63 75 72 76 65 32 |EPv..>....curve2|
+00000020 35 35 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 |5519-sha256,curv|
+00000030 65 32 35 35 31 39 2d 73 68 61 32 35 36 40 6c 69 |e25519-sha256@li|
+00000040 62 73 73 68 2e 6f 72 67 2c 65 63 64 68 2d 73 68 |bssh.org,ecdh-sh|
+00000050 61 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 68 |a2-nistp256,ecdh|
+00000060 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+00000070 63 64 68 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 |cdh-sha2-nistp52|
+00000080 31 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e |1,diffie-hellman|
+00000090 2d 67 72 6f 75 70 31 34 2d 73 68 61 32 35 36 2c |-group14-sha256,|
+000000a0 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e 2d 67 |diffie-hellman-g|
+000000b0 72 6f 75 70 31 34 2d 73 68 61 31 2c 65 78 74 2d |roup14-sha1,ext-|
+000000c0 69 6e 66 6f 2d 63 2c 6b 65 78 2d 73 74 72 69 63 |info-c,kex-stric|
+000000d0 74 2d 63 2d 76 30 30 40 6f 70 65 6e 73 73 68 2e |t-c-v00@openssh.|
+000000e0 63 6f 6d 00 00 00 57 65 63 64 73 61 2d 73 68 61 |com...Wecdsa-sha|
+000000f0 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 73 61 |2-nistp256,ecdsa|
+00000100 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+00000110 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 |cdsa-sha2-nistp5|
+00000120 32 31 2c 73 73 68 2d 72 73 61 2c 73 73 68 2d 64 |21,ssh-rsa,ssh-d|
+00000130 73 73 2c 73 73 68 2d 65 64 32 35 35 31 39 00 00 |ss,ssh-ed25519..|
+00000140 00 0a 61 65 73 31 39 32 2d 63 74 72 00 00 00 0a |..aes192-ctr....|
+00000150 61 65 73 31 39 32 2d 63 74 72 00 00 00 6e 68 6d |aes192-ctr...nhm|
+00000160 61 63 2d 73 68 61 32 2d 32 35 36 2d 65 74 6d 40 |ac-sha2-256-etm@|
+00000170 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 |openssh.com,hmac|
+00000180 2d 73 68 61 32 2d 35 31 32 2d 65 74 6d 40 6f 70 |-sha2-512-etm@op|
+00000190 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 |enssh.com,hmac-s|
+000001a0 68 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 68 61 |ha2-256,hmac-sha|
+000001b0 32 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 31 2c |2-512,hmac-sha1,|
+000001c0 68 6d 61 63 2d 73 68 61 31 2d 39 36 00 00 00 6e |hmac-sha1-96...n|
+000001d0 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2d 65 74 |hmac-sha2-256-et|
+000001e0 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d |m@openssh.com,hm|
+000001f0 61 63 2d 73 68 61 32 2d 35 31 32 2d 65 74 6d 40 |ac-sha2-512-etm@|
+00000200 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 |openssh.com,hmac|
+00000210 2d 73 68 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 |-sha2-256,hmac-s|
+00000220 68 61 32 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 |ha2-512,hmac-sha|
+00000230 31 2c 68 6d 61 63 2d 73 68 61 31 2d 39 36 00 00 |1,hmac-sha1-96..|
+00000240 00 04 6e 6f 6e 65 00 00 00 04 6e 6f 6e 65 00 00 |..none....none..|
+00000250 00 00 00 00 00 00 00 00 00 00 00 d7 3b 80 93 f6 |............;...|
+>>> Flow 4 (server to client)
+00000000 00 00 04 9c 0a 14 e1 22 f6 a4 d0 5d ab 07 1b 53 |......."...]...S|
+00000010 71 9b 45 3a a8 4e 00 00 01 7a 73 6e 74 72 75 70 |q.E:.N...zsntrup|
+00000020 37 36 31 78 32 35 35 31 39 2d 73 68 61 35 31 32 |761x25519-sha512|
+00000030 2c 73 6e 74 72 75 70 37 36 31 78 32 35 35 31 39 |,sntrup761x25519|
+00000040 2d 73 68 61 35 31 32 40 6f 70 65 6e 73 73 68 2e |-sha512@openssh.|
+00000050 63 6f 6d 2c 6d 6c 6b 65 6d 37 36 38 78 32 35 35 |com,mlkem768x255|
+00000060 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 65 32 |19-sha256,curve2|
+00000070 35 35 31 39 2d 73 68 61 32 35 36 2c 63 75 72 76 |5519-sha256,curv|
+00000080 65 32 35 35 31 39 2d 73 68 61 32 35 36 40 6c 69 |e25519-sha256@li|
+00000090 62 73 73 68 2e 6f 72 67 2c 65 63 64 68 2d 73 68 |bssh.org,ecdh-sh|
+000000a0 61 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 68 |a2-nistp256,ecdh|
+000000b0 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
+000000c0 63 64 68 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 |cdh-sha2-nistp52|
+000000d0 31 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e |1,diffie-hellman|
+000000e0 2d 67 72 6f 75 70 2d 65 78 63 68 61 6e 67 65 2d |-group-exchange-|
+000000f0 73 68 61 32 35 36 2c 64 69 66 66 69 65 2d 68 65 |sha256,diffie-he|
+00000100 6c 6c 6d 61 6e 2d 67 72 6f 75 70 31 36 2d 73 68 |llman-group16-sh|
+00000110 61 35 31 32 2c 64 69 66 66 69 65 2d 68 65 6c 6c |a512,diffie-hell|
+00000120 6d 61 6e 2d 67 72 6f 75 70 31 38 2d 73 68 61 35 |man-group18-sha5|
+00000130 31 32 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 |12,diffie-hellma|
+00000140 6e 2d 67 72 6f 75 70 31 34 2d 73 68 61 32 35 36 |n-group14-sha256|
+00000150 2c 64 69 66 66 69 65 2d 68 65 6c 6c 6d 61 6e 2d |,diffie-hellman-|
+00000160 67 72 6f 75 70 31 34 2d 73 68 61 31 2c 65 78 74 |group14-sha1,ext|
+00000170 2d 69 6e 66 6f 2d 73 2c 6b 65 78 2d 73 74 72 69 |-info-s,kex-stri|
+00000180 63 74 2d 73 2d 76 30 30 40 6f 70 65 6e 73 73 68 |ct-s-v00@openssh|
+00000190 2e 63 6f 6d 00 00 00 2d 72 73 61 2d 73 68 61 32 |.com...-rsa-sha2|
+000001a0 2d 35 31 32 2c 72 73 61 2d 73 68 61 32 2d 32 35 |-512,rsa-sha2-25|
+000001b0 36 2c 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 |6,ecdsa-sha2-nis|
+000001c0 74 70 32 35 36 00 00 00 6c 63 68 61 63 68 61 32 |tp256...lchacha2|
+000001d0 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 6e 73 |0-poly1305@opens|
+000001e0 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
+000001f0 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
+00000200 32 35 36 2d 63 74 72 2c 61 65 73 31 32 38 2d 67 |256-ctr,aes128-g|
+00000210 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 |cm@openssh.com,a|
+00000220 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 73 |es256-gcm@openss|
+00000230 68 2e 63 6f 6d 00 00 00 6c 63 68 61 63 68 61 32 |h.com...lchacha2|
+00000240 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 6e 73 |0-poly1305@opens|
+00000250 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
+00000260 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
+00000270 32 35 36 2d 63 74 72 2c 61 65 73 31 32 38 2d 67 |256-ctr,aes128-g|
+00000280 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 |cm@openssh.com,a|
+00000290 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 73 |es256-gcm@openss|
+000002a0 68 2e 63 6f 6d 00 00 00 d5 75 6d 61 63 2d 36 34 |h.com....umac-64|
+000002b0 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-etm@openssh.com|
+000002c0 2c 75 6d 61 63 2d 31 32 38 2d 65 74 6d 40 6f 70 |,umac-128-etm@op|
+000002d0 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 |enssh.com,hmac-s|
+000002e0 68 61 32 2d 32 35 36 2d 65 74 6d 40 6f 70 65 6e |ha2-256-etm@open|
+000002f0 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 |ssh.com,hmac-sha|
+00000300 32 2d 35 31 32 2d 65 74 6d 40 6f 70 65 6e 73 73 |2-512-etm@openss|
+00000310 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 31 2d |h.com,hmac-sha1-|
+00000320 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |etm@openssh.com,|
+00000330 75 6d 61 63 2d 36 34 40 6f 70 65 6e 73 73 68 2e |umac-64@openssh.|
+00000340 63 6f 6d 2c 75 6d 61 63 2d 31 32 38 40 6f 70 65 |com,umac-128@ope|
+00000350 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 |nssh.com,hmac-sh|
+00000360 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 68 61 32 |a2-256,hmac-sha2|
+00000370 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 31 00 00 |-512,hmac-sha1..|
+00000380 00 d5 75 6d 61 63 2d 36 34 2d 65 74 6d 40 6f 70 |..umac-64-etm@op|
+00000390 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 2d 31 |enssh.com,umac-1|
+000003a0 32 38 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 |28-etm@openssh.c|
+000003b0 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 |om,hmac-sha2-256|
+000003c0 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-etm@openssh.com|
+000003d0 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2d 65 |,hmac-sha2-512-e|
+000003e0 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 |tm@openssh.com,h|
+000003f0 6d 61 63 2d 73 68 61 31 2d 65 74 6d 40 6f 70 65 |mac-sha1-etm@ope|
+00000400 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 2d 36 34 |nssh.com,umac-64|
+00000410 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 |@openssh.com,uma|
+00000420 63 2d 31 32 38 40 6f 70 65 6e 73 73 68 2e 63 6f |c-128@openssh.co|
+00000430 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2c |m,hmac-sha2-256,|
+00000440 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2c 68 6d |hmac-sha2-512,hm|
+00000450 61 63 2d 73 68 61 31 00 00 00 15 6e 6f 6e 65 2c |ac-sha1....none,|
+00000460 7a 6c 69 62 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |zlib@openssh.com|
+00000470 00 00 00 15 6e 6f 6e 65 2c 7a 6c 69 62 40 6f 70 |....none,zlib@op|
+00000480 65 6e 73 73 68 2e 63 6f 6d 00 00 00 00 00 00 00 |enssh.com.......|
+00000490 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+>>> Flow 5 (client to server)
+00000000 00 00 00 2c 06 1e 00 00 00 20 13 cf b6 0f c2 c9 |...,..... ......|
+00000010 08 d9 7b f6 60 d4 53 7f 4b b1 29 37 59 98 3c dd |..{.`.S.K.)7Y.<.|
+00000020 ab b1 51 12 94 92 eb 56 4c 6f e8 a3 63 9c a8 a1 |..Q....VLo..c...|
+>>> Flow 6 (server to client)
+00000000 00 00 01 04 0a 1f 00 00 00 68 00 00 00 13 65 63 |.........h....ec|
+00000010 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 |dsa-sha2-nistp25|
+00000020 36 00 00 00 08 6e 69 73 74 70 32 35 36 00 00 00 |6....nistp256...|
+00000030 41 04 8b d1 dd c3 a2 af 65 c5 b1 7e 0d 88 0e 10 |A.......e..~....|
+00000040 3b 52 4a 43 b7 3c ed e9 9a 89 5d 2b 05 74 b7 7e |;RJC.<....]+.t.~|
+00000050 2b 1e 12 dd 2c 78 71 53 be eb f6 4e 5d 19 cf 98 |+...,xqS...N]...|
+00000060 d0 25 2d 4a a3 4a 15 2c 50 10 67 80 6d 2e d9 fa |.%-J.J.,P.g.m...|
+00000070 84 a8 00 00 00 20 81 ce f7 63 d7 91 ec be e9 c0 |..... ...c......|
+00000080 0d 73 92 94 15 ef e2 e9 6d 47 38 ea 62 67 78 12 |.s......mG8.bgx.|
+00000090 07 2b 79 f3 27 77 00 00 00 64 00 00 00 13 65 63 |.+y.'w...d....ec|
+000000a0 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 32 35 |dsa-sha2-nistp25|
+000000b0 36 00 00 00 49 00 00 00 21 00 9e c5 62 22 31 5f |6...I...!...b"1_|
+000000c0 fd 0f bc 20 d6 20 0c 3f f3 68 26 38 6b 97 09 c2 |... . .?.h&8k...|
+000000d0 07 4f 1b 4f 7f 3c 56 cf 79 4b 00 00 00 20 22 b5 |.O.O...|
+00000190 23 05 c2 95 55 d3 57 12 7f 0e c4 a1 5a 75 46 9a |#...U.W.....ZuF.|
+000001a0 7a ba a1 ea 81 f4 e9 68 0c 4e a2 63 bf 67 01 97 |z......h.N.c.g..|
+000001b0 4d 95 98 64 4e ba 8a fd e0 c4 79 d7 9f 36 93 a8 |M..dN.....y..6..|
+000001c0 55 47 71 96 2b 7e a1 ae 6f 09 b2 d0 39 bd bd 23 |UGq.+~..o...9..#|
+000001d0 76 19 6f d9 40 6c 3d dd 1c 6e 12 5b a1 68 50 60 |v.o.@l=..n.[.hP`|
+000001e0 8d bb 28 2b bd 6b 06 8d 95 98 3d 6c 4a 4a 87 e2 |..(+.k....=lJJ..|
+000001f0 99 47 b4 1d 71 5e 4d 8b 99 f3 95 d9 21 e9 50 6a |.G..q^M.....!.Pj|
+00000200 c5 46 09 2d 42 47 9f a4 5a fb 23 12 09 f4 87 95 |.F.-BG..Z.#.....|
+00000210 1e 27 4d c6 9e 99 d8 69 3a e9 3c 70 47 f4 70 f2 |.'M....i:.