Compare commits

..
62 Commits
Author SHA1 Message Date
Gopher Robot 506e022208 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I47041f06d6a0c92919eaac5d727cbc41551ed2e1
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/734461
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2026-01-12 07:56:02 -08:00
Filippo Valsorda 7dacc380ba chacha20poly1305: error out in fips140=only mode
We don't guarantee fips140=only support in x/crypto, but
chacha20poly1305 is special in that it's vendored into the standard
library. We could wrap all the callsites, but it's more robust to
just error out at construction time.

Change-Id: I4b1e451bd250429c4c5c5b61c8b2141c6a6a6964
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/728480
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
2025-12-10 06:07:36 -08:00
Gopher Robot 19acf81bd7 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I81158fb078bccce57d8d46cac0cb87e6c4f8cff9
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/728181
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: David Chase <drchase@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-12-08 10:34:26 -08:00
Gopher Robot 3a1c6b4b61 x509roots/fallback: update bundle
This is an automated CL which updates the NSS root bundle.

[git-generate]
go generate ./x509roots

Change-Id: Icde363f2fa61d1cb85552e57d4cae30b33ec96ed
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/723803
Auto-Submit: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2025-12-03 12:57:53 -08:00
Michael Stapelberg f4602e4040 ssh/agent: fix flaky test by ensuring a writeable home directory
This fixes flakiness observed inside Google (b/465393996).

Change-Id: Ic3decc3206b470cddf22c441b0cf92bb2bebb075
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/724002
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-12-02 08:08:01 -08:00
Gopher Robot 4e0068c009 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I3923d98d88595230b12db261c48168b863dc2ce9
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/722000
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Neal Patel <nealpatel@google.com>
2025-11-19 11:55:48 -08:00
Neal Patel e79546e28b ssh: curb GSSAPI DoS risk by limiting number of specified OIDs
Previously, an attacker could specify an integer up to 0xFFFFFFFF
that would directly allocate memory despite the observability of
the rest of the payload. This change places a hard cap on the
amount of mechanisms that can be specified and encoded in the
payload. Additionally, it performs a small sanity check to deny
payloads whose stated size is contradictory to the observed payload.

Thank you to Jakub Ciolek for reporting this issue.

Fixes CVE-2025-58181
Fixes golang/go#76363

Change-Id: I0307ab3e906a3f2ae763b5f9f0310f7073f84485
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/721961
Auto-Submit: Roland Shoemaker <roland@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-11-19 11:28:37 -08:00
Neal Patel f91f7a7c31 ssh/agent: prevent panic on malformed constraint
An attacker could supply a malformed Constraint that
would trigger a panic in a serving agent, effectively
causing denial of service.

Thank you to Jakub Ciolek for reporting this issue.

Fixes CVE-2025-47914
Fixes golang/go#76364

Change-Id: I195bbc68b1560d4f04897722a6a653a7cbf086eb
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/721960
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Roland Shoemaker <roland@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
2025-11-19 11:28:34 -08:00
Sean Liao 2df4153a03 acme/autocert: let automatic renewal work with short lifetime certs
Fixes golang/go#64997
Fixes golang/go#36548

Change-Id: Idb7a426ad3bfa6ac3b796f4b466da6e3154f1ffa
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/719080
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Mark Freeman <markfreeman@google.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-11-17 10:17:16 -08:00
Sean Liao bcf6a849ef acme: pass context to request
Fixes golang/go#30183

Change-Id: Ic02b34bc87b9465f5c05b2ef5bec157c58809a91
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/719002
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-11-12 10:48:32 -08:00
Santhanam b4f2b62076 ssh: fix error message on unsupported cipher
Until now, when ssh keys using one of these[1] ciphers were passed, we were
giving a parse error "ssh: parse error in message type 0".

With this fix, we parse it successfully and return the correct error message.

[1] aes{128,256}-gcm@openssh.com and chacha20-poly1305@openssh.com

Fixes golang/go#52135

Change-Id: I3010fff43c48f29f21edb8d63f44e167861a054e
GitHub-Last-Rev: 14ac7e97306d41cba48053b9c60f2ffc7caded45
GitHub-Pull-Request: golang/crypto#324
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/709275
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-11-12 10:42:48 -08:00
Nicola Murino 79ec3a51fc ssh: allow to bind to a hostname in remote forwarding
To avoid breaking backwards compatibility, we fix Listen, which
receives the address as a string, while ListenTCP can still only
be used with IP addresses.

Fixes golang/go#33227
Fixes golang/go#37239

Change-Id: I4d45b40fdcb0d6012ed8da59a02149fa37e7db50
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/599995
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Bishakh Ghosh <ghoshbishakh@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
2025-11-12 10:42:45 -08:00
Gopher Robot 122a78f140 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I0f64669e7c813611f71b1381d9e6fdaba1a39712
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/719641
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: David Chase <drchase@google.com>
2025-11-11 10:21:23 -08:00
Sean Liao c0531f9c34 all: eliminate vet diagnostics
For golang/go#74011

Change-Id: I189c5aba554a578bee1fd351edc30cd5cf4d0ed6
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/714960
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: David Chase <drchase@google.com>
2025-10-28 06:00:51 -07:00
cuishuang 0997000b45 all: fix some comments
Change-Id: I0395c5db6edd7d90f9ec1dadbe881a77c906c732
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/713120
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: David Chase <drchase@google.com>
Auto-Submit: Sean Liao <sean@liao.dev>
Reviewed-by: Sean Liao <sean@liao.dev>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2025-10-27 09:33:07 -07:00
Sean Liao 017a1aaa2d chacha20poly1305: panic on dst and additionalData overlap
The cipher.AEAD interface specifies that these should not overlap.
This mirrors the check that the GCM implementation does.

Fixes golang/go#75968
Updates golang/go#21624

Change-Id: If5fbb8611ff6c0aae44d50079bad29f56ce00f5b
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/712860
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-10-22 18:37:21 -07:00
Filippo Valsorda cf29fa96f8 sha3: make it mostly a wrapper around crypto/sha3
crypto/sha3 was introduced in Go 1.24, which is now the minimum Go
version of this module.

Made the hashes go:fix inline wrappers, since the new types can be used
as hash.Hash directly.

The SHAKE instances need a wrapper for the methods we dropped from
crypto.XOF, so no go:fix inline there.

Kept the generic implementation for the legacy Keccak hashes we did not
bring to the standard library. We need to keep them working, but they
don't need to be fast.

Fixes golang/go#73681
Updates golang/go#65269

Change-Id: I6a6a69648b6353b153c70a2cec84864e64dcd61b
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/710115
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2025-10-22 18:01:01 -07:00
cuishuang 0b7aa0cfb0 ssh: use reflect.TypeFor instead of reflect.TypeOf
For golang/go#60088.

Change-Id: I58994c469a2793516214ab1a0072fb6137afc46e
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/709156
Auto-Submit: Sean Liao <sean@liao.dev>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Sean Liao <sean@liao.dev>
2025-10-09 11:10:29 -07:00
cuishuang 1faea2975c all: fix some typos in comment
Change-Id: Ia209f0a6d9b19d14e655c65d1287a1416b48c487
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/707535
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
Auto-Submit: Sean Liao <sean@liao.dev>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Sean Liao <sean@liao.dev>
2025-10-08 13:58:26 -07:00
Gopher Robot 627cb894b6 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: Icf986acf9290649488777328f470200bf9e11442
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/710098
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
2025-10-08 10:40:45 -07:00
Daniel McCarney dca4914afe acme: fix autocert TestHTTPHandlerDefaultFallback
The Go 1.25.2 release made net/url stricter about parsing bracketed IPv6
hostnames, and is rejecting some test URLs used in the autocert
TestHTTPHandlerDefaultFallback test with an error about the
colon-separated fields requiring at least one hex digit.

This commit replaces the invalid `xxxx` portion of some test URLS with
valid hex digits, fixing the test regression.

Change-Id: I84c192b1cd6daf53ef4199f7987437fd825f7041
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/710155
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-10-08 09:52:31 -07:00
Gopher Robot 1336e21bd6 x509roots/fallback: update bundle
This is an automated CL which updates the NSS root bundle.

[git-generate]
go generate ./x509roots

Change-Id: I9ab454c977013b2f6a42bc93fb0649612c54c6c0
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/709475
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2025-10-08 08:14:13 -07:00
Nicola Murino 2beaa59a3c ssh: add VerifiedPublicKeyCallback
Fixes golang/go#70795

Change-Id: I9b7c91f35f89495d1e9b5f6ec0c036c02a61d774
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/636335
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Ilia Mirkin <imirkin@alum.mit.edu>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jorge Hernández <jorgehcrda39@gmail.com>
2025-09-27 12:43:41 -07:00
Nicola Murino 66c3d8ce71 ssh: add support for FIPS mode
Unsupported algoritms are silently ignored and not negotiated, or
rejected

Fixes golang/go#75061

Change-Id: I08d50d10a97c08e78aedead89ca61beceff88918
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/698795
Reviewed-by: Mio Mio <miomio0086@gmail.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-09-27 12:36:52 -07:00
Nicola Murino ddb4e80c6a ssh: remove custom contains, use slices.Contains
Change-Id: If4784469e7285675bdd51399a76bdc16f0036a2e
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/703635
Reviewed-by: Mark Freeman <markfreeman@google.com>
Reviewed-by: Sean Liao <sean@liao.dev>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-09-15 23:33:16 -07:00
Nicola Murino f4d47b0db5 ssh: return clearer error when signature algorithm is used as key format
ParsePublicKey now returns a more specific error when a signature
algorithm like rsa-sha2-256 is mistakenly provided as a key format

Change-Id: Ic08286a5b2b326e99dd3e61594919203f0c36791
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/695075
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2025-09-15 23:32:56 -07:00
Michael Stapelberg 96dc232fbd x509roots/fallback/bundle: add bundle package to export root certs
Fixes golang/go#69898

Change-Id: Idbb1bbe48016a622414c84a56fe26f48bfe712c8
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/687155
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Roland Shoemaker <roland@golang.org>
Reviewed-by: Mateusz Poliwczak <mpoliwczak34@gmail.com>
2025-09-11 08:14:50 -07:00
Filippo Valsorda 8c9ba31836 all: freeze and deprecate more packages
Fixes golang/go#65250

Change-Id: I6a6a6964a2c87e529be50dd67fec462483b07b75
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/701535
Reviewed-by: Mark Freeman <markfreeman@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
2025-09-09 12:19:31 -07:00
Nicola Murino 559e062ce8 ssh/agent: return an error for unexpected message types
Previously, receiving an unexpected message type in response to a key
listing or a signing request could cause a panic due to a failed type
assertion.

This change adds a default case to the type switch in order to detect
and explicitly handle unknown or invalid message types, returning a
descriptive error instead of crashing.

Fixes golang/go#75178

Change-Id: Icbc3432adc79fe3c56b1ff23c6724d7a6f710f3a
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/700295
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Jakub Ciolek <jakub@ciolek.dev>
2025-09-09 02:56:19 -07:00
Gopher Robot 5307a0ce6d go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I75e16a930bfe42cc082df82ab67802c42ad56a97
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/701303
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
2025-09-08 08:14:28 -07:00
Daniel McCarney 9d779377cf acme: include order problem in OrderError
If client.WaitOrder or client.CreateOrderCert return an acme.OrderError
it's helpful to include the order's problem field (if available). This
will often have detailed information about why a particular order
became invalid that's invaluable for debugging (e.g. a challenge
response was incorrect, a name couldn't be resolved, etc).

While it's possible for a consumer to poll the order themselves as part
of handling the order to extract a fresh Order.Error field value, it
would take an extra round-trip network request. Since we have the
underlying error in-hand when we produce the OrderError we might as well
include it directly.

Since this field is a structured object with a number of sub-fields the
OrderError.Error() function isn't updated to include the order problem
error in the String description. Interested callers should instead use
errors.Is to extract the problem information directly.

Resolves golang/go#74430

Cq-Include-Trybots: luci.golang.try:x_crypto-gotip-linux-amd64-longtest
Change-Id: I3158f064793bbfdc292dd6b5e1a6bfd7729bd980
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/681037
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Ian Stapleton Cordasco <graffatcolmingov@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-09-04 07:39:59 -07:00
Nicola Murino 8f580defa0 ssh: remove Go 1.24 build tag for ML-KEM kex
Change-Id: Ia77ad1b6fef9919ab100fb10c42231725eb81c12
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/698775
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2025-08-26 00:42:33 -07:00
Nicola Murino a4d1237429 ssh/knownhosts: improve IPv6 support in Normalize
Correctly converts bracketed IPv6:

- [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

Fixes golang/go#53463

Change-Id: Id0a7460d8448a72e2a8c6d46137245bead9ecf9f
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/694575
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2025-08-19 13:12:03 -07:00
Filippo Valsorda b8d8dae13d curve25519: include potential fips140=only error in panic message
Updates golang/go#75061

Change-Id: I6a6a696474122a12c12696d8a2efec902572327d
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/696996
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2025-08-19 10:40:36 -07:00
Filippo Valsorda f5a2eabcab ssh: use curve25519.X25519 instead of curve25519.ScalarMult
This lets us surface an error message instead of panicking if running
in fips140=only mode, where ECDH on X25519 returns an error.

Updates golang/go#75061

Change-Id: I6a6a6964c0591f3dca2dc946c99d44364314a3ab
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/696995
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
2025-08-19 10:38:43 -07:00
Gopher Robot 44ecf3af99 all: upgrade go directive to at least 1.24.0 [generated]
By now Go 1.25.0 has been released, and Go 1.23 is no longer supported
per the Go Release Policy (see https://go.dev/doc/devel/release#policy).

For golang/go#69095.

[git-generate]
(cd . && go get go@1.24.0 && go mod tidy && go fix ./... && go mod edit -toolchain=none)
(cd x509roots/fallback && go get go@1.24.0 && go mod tidy && go fix ./... && go mod edit -toolchain=none)

Change-Id: Ia4c201e9611a2c13489e16d4ae81d7e3e32bf455
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/695715
Auto-Submit: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: David Chase <drchase@google.com>
2025-08-13 08:00:22 -07:00
Gopher Robot ef5341b706 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I93de641462a54b0ae565bb60e2a0e6e7c2c3b883
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/693999
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: David Chase <drchase@google.com>
2025-08-07 10:21:04 -07:00
Daniel McCarney b999374650 acme: fix pebble subprocess output data race
Wait for process completion before reading stdout/stderr buffers
to eliminate race between I/O Go routines and test cleanup.

Updates golang/go#74437

Cq-Include-Trybots: luci.golang.try:x_crypto-gotip-linux-amd64-longtest-race
Change-Id: I2e650c04db5be0d7a1e858ce40e25f13ad12223c
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/693596
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-08-06 13:33:03 -07:00
Mateusz Poliwczak c247dead11 x509roots/fallback: store bundle certs directly in DER
goos: linux
goarch: amd64
pkg: golang.org/x/crypto/x509roots/fallback
cpu: AMD Ryzen 5 4600G with Radeon Graphics
            │ /tmp/before │             /tmp/after              │
            │   sec/op    │   sec/op     vs base                │
InitTime-12   1.726m ± 0%   1.101m ± 1%  -36.20% (p=0.000 n=30)

            │  /tmp/before  │              /tmp/after              │
            │     B/op      │     B/op      vs base                │
InitTime-12   1178.2Ki ± 0%   779.8Ki ± 0%  -33.81% (p=0.000 n=30)

            │ /tmp/before │             /tmp/after             │
            │  allocs/op  │  allocs/op   vs base               │
InitTime-12   11.35k ± 0%   10.64k ± 0%  -6.32% (p=0.000 n=30)

Updates golang/go#73691

Change-Id: Ic33f2fdfc65001c41afeb3b6af8a383288d10de6
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/676217
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Mark Freeman <mark@golang.org>
2025-08-03 12:47:17 -07:00
Daniel McCarney 1fda73153f acme: increase pebble test waitForServer attempts
In CI it seems that occasionally we can't connect to the test servers
within 10 tries, and the test flakes. Let's give the process more
attempts.

Updates golang/go#74437

Change-Id: I74d6cea83468a3a572ec4b52ff7314c778c664cf
Cq-Include-Trybots: luci.golang.try:x_crypto-gotip-linux-amd64-longtest
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/692075
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Mark Freeman <mark@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-08-01 08:44:22 -07:00
Gopher Robot 1b4c3d2e8c x509roots/fallback: update bundle
This is an automated CL which updates the NSS root bundle.

[git-generate]
go generate ./x509roots

Change-Id: Ib30b702d41dedacce835628a9dab456098be0703
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/687895
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
2025-07-30 16:29:44 -07:00
Daniel McCarney b903b535d3 acme: capture pebble test subprocess stdout/stderr
When spawning the pebble and pebble-challtestserv processes redirect
stdout/stderr to bytes.Buffer instances and print their content at test
end as appropriate.

The stdout/stderr content for each process is printed if the test
failed, or if testing is being done in verbose mode. Otherwise the
output is swallowed.

This makes debugging test failures much easier as output from the
subprocesses from independent tests isn't intermingled.

Updates golang/go#74437

Cq-Include-Trybots: luci.golang.try:x_crypto-gotip-linux-amd64-longtest
Change-Id: Ia79a3609ce3522ef6248442de247554c39367162
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/686935
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-07-11 12:27:10 -07:00
Gopher Robot 459a9db11b go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I3a89b1890ad2f7d2b2c23e1efce60c19e43dd381
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/687017
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: David Chase <drchase@google.com>
2025-07-10 10:21:43 -07:00
Nicola Murino 74e709ad8a ssh: add AlgorithmNegotiationError
Fixes golang/go#61536

Change-Id: Id38cc6d46879dbe2bdea04dec061596387ec6cfe
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/559056
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2025-07-09 08:43:26 -07:00
Daniel McCarney b3790b8d91 acme: fix TLSALPN01ChallengeCert for IP address identifiers
When creating a TLS-ALPN-01 challenge response certificate for an IP
address identifier we need to configure the template IPAddresses field,
not the DNSNames/Subject.CommonName.

Along the way we can do some small tidying:
* Updating the draft TLS-ALPN-01 reference to the finalized RFC
* Adding a reference to the IP address identifier ACME RFC
* Adding a mention of the form the challenge validation request's SNI
  will take when verifying an IP address identifier
* Tidying the private tlsChallengeCert() function to take a single
  identifier as arg since the only call-sites provide singular values
  since the removal of the TLS-SNI-[01|02] challenge helpers.

This allows enabling an IP address identifier in the Pebble integration
tests that otherwise caused a validation failure for TLS-ALPN-01
challenge types because the IP address was used as a DNS SAN.

Updates golang/go#73914

Cq-Include-Trybots: luci.golang.try:x_crypto-gotip-linux-amd64-longtest
Change-Id: Ic671e41b585f424f821db65206c7ffcc6dd386a0
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/677576
Reviewed-by: Ian Stapleton Cordasco <graffatcolmingov@gmail.com>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2025-06-30 12:50:50 -07:00
Daniel McCarney 1dc4269656 acme: add Pebble integration testing
This commit adds integration test coverage for a complete TLS-ALPN-01
and HTTP-01 based issuance flow.

For each tested challenge type we:

* Spin up a pebble/pebble-challtestsrv environment
* Spin up a small challenge response server
* Create an ACME account
* Create an order for multiple DNS type identifiers
* Provision challenge responses based on the challenge type under test
* Wait for the order to become ready for issuance
* Finalize the order, issuing a certificate
* Check the newly issued certificate chain validates with the Pebble
  trust anchor, and that the certificate is valid for each of the names
  from our initial order

These tests are skipped in short mode (Pebble has variable delays for
validation requests).

The Pebble source is fetched through the Go module proxy (unless
a local directory is specified to aid development), similar to how the
stdlib crypto packages fetch BoGo tooling.

More test coverage for various other parts of the protocol (key
rollover, account/authz deactivation, revocation, etc) can be added as
follow-up work now that the groundwork for integration testing is laid.

Fixes golang/go#73914

Cq-Include-Trybots: luci.golang.try:x_crypto-gotip-linux-amd64-longtest
Change-Id: I4e79f4858f31ef290a0c91d345e15fbdc510e9ab
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/677575
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Ian Stapleton Cordasco <graffatcolmingov@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-06-30 09:51:57 -07:00
qiulaidongfeng 97bf787255 blake2b: implement hash.XOF
Fixes golang/go#69518

Change-Id: Id9989ac9b28262df77017e97f985f67c1571c3ce
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/644255
Reviewed-by: Austin Clements <austin@google.com>
Auto-Submit: Austin Clements <austin@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-06-30 09:39:26 -07:00
Gopher Robot 952517d181 x509roots/fallback: update bundle
This is an automated CL which updates the NSS root bundle.

[git-generate]
go generate ./x509roots

Change-Id: Icb71f9f7c509dc6f49ad4385aa287bd6a8966523
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/681915
Auto-Submit: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2025-06-30 09:08:05 -07:00
Nicola Murino c6fce02826 ssh: refuse to parse certificates that use a certificate as signing key
According to draft-miller-ssh-cert-01, Section 2.1.1, certificates with
certificate keys as signature keys are invalid

Change-Id: I474524ea444deb78f2fa7c2682e47c0fd057f0b8
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/678716
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2025-06-30 09:02:50 -07:00
Nicola Murino 0ae49b8145 ssh: reject certificate keys used as signature keys for SSH certs
As specified in draft-miller-ssh-cert-01, Section 2.1.1:
Implementations MUST NOT accept certificate keys as CA keys.

Change-Id: I2e559a8a58b7bceccd0d8c6b80803abdbe281067
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/678715
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: David Chase <drchase@google.com>
2025-06-30 09:02:45 -07:00
Nicola Murino 3bf9d2afd4 ssh/test: skip KEX test if unsupported by system SSH client
Skip the key exchange test when using the system's ssh CLI if the
required KEX algorithm (e.g., mlkem768x25519-sha256) is not supported.
This is determined by running ssh -Q kex and checking for the presence
of the target algorithm.
Prevents false test failures in CI environments with older or limited
SSH implementations.

Cq-Include-Trybots: luci.golang.try:x_crypto-gotip-darwin-amd64-longtest,x_crypto-gotip-linux-amd64-longtest,x_crypto-gotip-windows-amd64-longtest
Change-Id: I3fac703ec70559e18b30d5fff88274335a7c3952
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/679195
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2025-06-05 10:42:09 -07:00
Gopher Robot 9bab96736c go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: Icbe0bf922207aefa5ee047a35dfb71843ec75306
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/679135
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
2025-06-05 08:51:54 -07:00
Mateusz Poliwczak 4f9f0ca9fc x509roots/fallback: add init time benchmark
goos: linux
goarch: amd64
pkg: golang.org/x/crypto/x509roots/fallback
cpu: AMD Ryzen 5 4600G with Radeon Graphics
            │ /tmp/before │
            │   sec/op    │
InitTime-12   1.726m ± 0%

            │ /tmp/before  │
            │     B/op     │
InitTime-12   1.151Mi ± 0%

            │ /tmp/before │
            │  allocs/op  │
InitTime-12   11.35k ± 0%

For golang/go#73691

Change-Id: Ic932bd7835e50dd5c6adbdf684644afa49bddebc
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/676216
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Sean Liao <sean@liao.dev>
Auto-Submit: Sean Liao <sean@liao.dev>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2025-05-31 02:59:11 -07:00
Mateusz Poliwczak eac7cf0d78 x509roots/fallback: move parsing code to a non-generated file
For golang/go#73691

Change-Id: I3e2b09055c39286d863fe70ca3bd72a839e25d0a
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/676215
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Sean Liao <sean@liao.dev>
Auto-Submit: Sean Liao <sean@liao.dev>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-05-31 02:59:06 -07:00
Daniel McCarney 18228cd6f1 acme: return err from deprecated TLS-SNI-[01|02] functions
The TLSSNI01ChallengeCert and TLSSNI02ChallengeCert functions have been
marked deprecated since 2022. The package documentation indicates
pre-RFC 8555 functionality is retained for compilation success, but will
return errors. This commit makes these two deprecated functions match
that description.

No meaningful support for these draft standard challenge types exists
in the ACME ecosystem, and they are insecure for use in shared hosting
environments.

Change-Id: I1c17980a0630092c70eb971b3453a0f115834be0
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/676835
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2025-05-29 10:16:04 -07:00
Daniel McCarney 73f6362471 acme: remove dead code
The private encodePem() and unique() functions under the acme package
had no call-sites and so can be removed.

Change-Id: Ic617392e7cc9b9e795456626a623ba02df108694
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/676875
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2025-05-28 10:23:52 -07:00
Nicola Murino ebc8e46315 ssh: add server side support for Diffie Hellman Group Exchange
We add this support for the following reasons:

- We are planning to expose recommended (secure) vs. supported (works,
  not necessarily recommended) algorithms. The DHGEX kex is currently
  only exposed as a client-side kex. To simplify the calling convention
  for this follow-on, we expose the server side too.
- Some clients are quite inflexible with reference to kex algorithms
  choice, for example they offer:

  diffie-hellman-group-exchange-sha256, diffie-hellman-group-exchange-sha1,
  diffie-hellman-group14-sha1, diffie-hellman-group1-sha1

  therefore DHGEX helps interoperability.

We do not recommend the DHGEX kex as a whole:

- the negotiation requires an extra round trip
- the server must generate parameters (slow) or hardcode them, which
  defeats the security benefit over traditional DH.

In this implementation we hardcode sending Oakley Group 14, Oakley
Group 15 or Oakley Group 16 based on the requested max size.

Users that are concerned with security of classical DH kex should
migrate to kex based on EC or Ed25519.

Fixes golang/go#54743

Change-Id: I127822e90efc36821af4aca679931f40a2023021
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/532415
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-05-15 10:47:05 -07:00
Nicola Murino e944286e33 ssh: expose negotiated algorithms
Fixes golang/go#58523
Fixes golang/go#46638

Change-Id: Ic64bd2fdd6e9ec96acac3ed4be842e2fbb15231d
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/538235
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-05-15 10:44:04 -07:00
Nicola Murino 78a1fd7893 ssh: automatically add curve25519-sha256@libssh.org KEX alias
If the key exchange method curve25519-sha256 is configured, automatically
add the curve25519-sha256@libssh.org alias to ensure compatibility with
OpenSSH versions up to 7.2, which recognize only the older vendor-specific
name.

Change-Id: If50ab2c49179db949ba1b986f7bb0e153cc7f897
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/669716
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-05-15 10:43:39 -07:00
Nicola Murino ac58737d25 ssh: export supported algorithms
Fixes golang/go#61537

Change-Id: If3478121e3ae445391e3faeceeb889d75e9e3214
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/531935
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2025-05-15 10:43:30 -07:00
redwrasse 9dbbcf002b bcrypt: update InvalidCostError message to describe cost range inclusive
Change InvalidCostError string to explicitly state cost bounds
range as inclusive, rather than using parentheses that might
imply an exclusive range.

Change-Id: Ie5e7d53ef4217c8560e6a7fe23e3d65ecc24a2a3
GitHub-Last-Rev: b49002b6b2cff0939858553a28e3f255bde397b6
GitHub-Pull-Request: golang/crypto#300
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/609455
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2025-05-12 11:46:18 -07:00
Gopher Robot 9f6bf8449a x509roots/fallback: update bundle
This is an automated CL which updates the NSS root bundle.

[git-generate]
go generate ./x509roots

Change-Id: If1970af8da68ead595dc3fa7dd79a8555a5f09c5
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/668576
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
2025-05-12 08:41:11 -07:00
115 changed files with 4701 additions and 12667 deletions
+37 -55
View File
@@ -31,12 +31,11 @@ import (
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net"
"net/http"
"strings"
"sync"
@@ -471,7 +470,7 @@ func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorizat
// while waiting for a final authorization status.
d := retryAfter(res.Header.Get("Retry-After"))
if d == 0 {
// Given that the fastest challenges TLS-SNI and HTTP-01
// 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.
@@ -572,50 +571,28 @@ func (c *Client) HTTP01ChallengePath(token string) string {
}
// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response.
// Always returns an error.
//
// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec.
func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
ka, err := keyAuth(c.Key.Public(), token)
if err != nil {
return tls.Certificate{}, "", err
}
b := sha256.Sum256([]byte(ka))
h := hex.EncodeToString(b[:])
name = fmt.Sprintf("%s.%s.acme.invalid", h[:32], h[32:])
cert, err = tlsChallengeCert([]string{name}, opt)
if err != nil {
return tls.Certificate{}, "", err
}
return cert, name, nil
// 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 is unused in both draft-02 and RFC versions of the ACME spec.
func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) {
b := sha256.Sum256([]byte(token))
h := hex.EncodeToString(b[:])
sanA := fmt.Sprintf("%s.%s.token.acme.invalid", h[:32], h[32:])
ka, err := keyAuth(c.Key.Public(), token)
if err != nil {
return tls.Certificate{}, "", err
}
b = sha256.Sum256([]byte(ka))
h = hex.EncodeToString(b[:])
sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", h[:32], h[32:])
cert, err = tlsChallengeCert([]string{sanA, sanB}, opt)
if err != nil {
return tls.Certificate{}, "", err
}
return cert, sanA, nil
// 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 a domain name. For more details on TLS-ALPN-01 see
// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3
// 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,
@@ -623,9 +600,13 @@ func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tl
// 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 domain, and the special acme-tls/1 ALPN protocol
// the server name in the TLS ClientHello matches the identifier, and the special acme-tls/1 ALPN protocol
// has been specified.
func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) {
//
// 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
@@ -655,7 +636,7 @@ func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption)
}
tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension)
newOpt = append(newOpt, WithTemplate(tmpl))
return tlsChallengeCert([]string{domain}, newOpt)
return tlsChallengeCert(identifier, newOpt)
}
// popNonce returns a nonce value previously stored with c.addNonce
@@ -709,7 +690,7 @@ func (c *Client) addNonce(h http.Header) {
}
func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) {
r, err := http.NewRequest("HEAD", url, nil)
r, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
if err != nil {
return "", err
}
@@ -773,11 +754,15 @@ func defaultTLSChallengeCertTemplate() *x509.Certificate {
}
}
// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges
// with the given SANs and auto-generated public/private key pair.
// The Subject Common Name is set to the first SAN to aid debugging.
// 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(san []string, opt []CertOption) (tls.Certificate, error) {
func tlsChallengeCert(identifier string, opt []CertOption) (tls.Certificate, error) {
var key crypto.Signer
tmpl := defaultTLSChallengeCertTemplate()
for _, o := range opt {
@@ -801,9 +786,12 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
return tls.Certificate{}, err
}
}
tmpl.DNSNames = san
if len(san) > 0 {
tmpl.Subject.CommonName = san[0]
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)
@@ -816,11 +804,5 @@ func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) {
}, nil
}
// encodePEM returns b encoded as PEM with block of type typ.
func encodePEM(typ string, b []byte) []byte {
pb := &pem.Block{Type: typ, Bytes: b}
return pem.EncodeToMemory(pb)
}
// timeNow is time.Now, except in tests which can mess with it.
var timeNow = time.Now
+34 -110
View File
@@ -9,7 +9,6 @@ import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
@@ -22,8 +21,6 @@ import (
"net/http"
"net/http/httptest"
"reflect"
"sort"
"strings"
"testing"
"time"
)
@@ -692,71 +689,6 @@ func TestLinkHeader(t *testing.T) {
}
}
func TestTLSSNI01ChallengeCert(t *testing.T) {
const (
token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
// echo -n <token.testKeyECThumbprint> | shasum -a 256
san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid"
)
tlscert, name, err := newTestClient().TLSSNI01ChallengeCert(token)
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)
}
if len(cert.DNSNames) != 1 || cert.DNSNames[0] != san {
t.Fatalf("cert.DNSNames = %v; want %q", cert.DNSNames, san)
}
if cert.DNSNames[0] != name {
t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name)
}
if cn := cert.Subject.CommonName; cn != san {
t.Errorf("cert.Subject.CommonName = %q; want %q", cn, san)
}
}
func TestTLSSNI02ChallengeCert(t *testing.T) {
const (
token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
// echo -n evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA | shasum -a 256
sanA = "7ea0aaa69214e71e02cebb18bb867736.09b730209baabf60e43d4999979ff139.token.acme.invalid"
// echo -n <token.testKeyECThumbprint> | shasum -a 256
sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid"
)
tlscert, name, err := newTestClient().TLSSNI02ChallengeCert(token)
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{sanA, sanB}
if !reflect.DeepEqual(cert.DNSNames, names) {
t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names)
}
sort.Strings(cert.DNSNames)
i := sort.SearchStrings(cert.DNSNames, name)
if i >= len(cert.DNSNames) || cert.DNSNames[i] != name {
t.Errorf("%v doesn't have %q", cert.DNSNames, name)
}
if cn := cert.Subject.CommonName; cn != sanA {
t.Errorf("CommonName = %q; want %q", cn, sanA)
}
}
func TestTLSALPN01ChallengeCert(t *testing.T) {
const (
token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
@@ -813,6 +745,7 @@ func TestTLSChallengeCertOpt(t *testing.T) {
if err != nil {
t.Fatal(err)
}
domain := "example.com"
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(2),
Subject: pkix.Name{Organization: []string{"Test"}},
@@ -821,52 +754,43 @@ func TestTLSChallengeCertOpt(t *testing.T) {
opts := []CertOption{WithKey(key), WithTemplate(tmpl)}
client := newTestClient()
cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...)
if err != nil {
t.Fatal(err)
}
cert2, _, err := client.TLSSNI02ChallengeCert("token", opts...)
cert, err := client.TLSALPN01ChallengeCert("token", domain, opts...)
if err != nil {
t.Fatal(err)
}
for i, tlscert := range []tls.Certificate{cert1, cert2} {
// verify generated cert private key
tlskey, ok := tlscert.PrivateKey.(*rsa.PrivateKey)
if !ok {
t.Errorf("%d: tlscert.PrivateKey is %T; want *rsa.PrivateKey", i, tlscert.PrivateKey)
continue
}
if tlskey.D.Cmp(key.D) != 0 {
t.Errorf("%d: tlskey.D = %v; want %v", i, tlskey.D, key.D)
}
// verify generated cert public key
x509Cert, err := x509.ParseCertificate(tlscert.Certificate[0])
if err != nil {
t.Errorf("%d: %v", i, err)
continue
}
tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey)
if !ok {
t.Errorf("%d: x509Cert.PublicKey is %T; want *rsa.PublicKey", i, x509Cert.PublicKey)
continue
}
if tlspub.N.Cmp(key.N) != 0 {
t.Errorf("%d: tlspub.N = %v; want %v", i, tlspub.N, key.N)
}
// verify template option
sn := big.NewInt(2)
if x509Cert.SerialNumber.Cmp(sn) != 0 {
t.Errorf("%d: SerialNumber = %v; want %v", i, x509Cert.SerialNumber, sn)
}
org := []string{"Test"}
if !reflect.DeepEqual(x509Cert.Subject.Organization, org) {
t.Errorf("%d: Subject.Organization = %+v; want %+v", i, x509Cert.Subject.Organization, org)
}
for _, v := range x509Cert.DNSNames {
if !strings.HasSuffix(v, ".acme.invalid") {
t.Errorf("%d: invalid DNSNames element: %q", i, v)
}
// 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)
}
}
}
+6 -12
View File
@@ -134,7 +134,8 @@ type Manager struct {
// RenewBefore optionally specifies how early certificates should
// be renewed before they expire.
//
// If zero, they're renewed 30 days before expiration.
// 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
@@ -464,7 +465,7 @@ func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error
leaf: cert.Leaf,
}
m.state[ck] = s
m.startRenew(ck, s.key, s.leaf.NotAfter)
m.startRenew(ck, s.key, s.leaf.NotBefore, s.leaf.NotAfter)
return cert, nil
}
@@ -610,7 +611,7 @@ func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate,
}
state.cert = der
state.leaf = leaf
m.startRenew(ck, state.key, state.leaf.NotAfter)
m.startRenew(ck, state.key, state.leaf.NotBefore, state.leaf.NotAfter)
return state.tlscert()
}
@@ -908,7 +909,7 @@ func httpTokenCacheKey(tokenPath string) string {
//
// 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, exp time.Time) {
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 {
@@ -920,7 +921,7 @@ func (m *Manager) startRenew(ck certKey, key crypto.Signer, exp time.Time) {
}
dr := &domainRenewal{m: m, ck: ck, key: key}
m.renewal[ck] = dr
dr.start(exp)
dr.start(notBefore, notAfter)
}
// stopRenew stops all currently running cert renewal timers.
@@ -1028,13 +1029,6 @@ func (m *Manager) hostPolicy() HostPolicy {
return defaultHostPolicy
}
func (m *Manager) renewBefore() time.Duration {
if m.RenewBefore > renewJitter {
return m.RenewBefore
}
return 720 * time.Hour // 30 days
}
func (m *Manager) now() time.Time {
if m.nowFunc != nil {
return m.nowFunc()
+3 -3
View File
@@ -561,9 +561,9 @@ func TestHTTPHandlerDefaultFallback(t *testing.T) {
{"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:xxxx::c60a]:1234", 302, "https://[2602:d1:xxxx::c60a]:443/"},
{"GET", "http://[2602:d1:xxxx::c60a]", 302, "https://[2602:d1:xxxx::c60a]/"},
{"GET", "http://[2602:d1:xxxx::c60a]/foo?a=b", 302, "https://[2602:d1:xxxx::c60a]/foo?a=b"},
{"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/"},
+1 -13
View File
@@ -145,7 +145,7 @@ func (ca *CAServer) URL() string {
return ca.url
}
// Roots returns a pool cointaining the CA root.
// Roots returns a pool containing the CA root.
func (ca *CAServer) Roots() *x509.CertPool {
if ca.url == "" {
panic("Roots called before Start")
@@ -782,15 +782,3 @@ func decodePayload(v interface{}, r io.Reader) error {
func challengeToken(domain, challType string, authzID int) string {
return fmt.Sprintf("token-%s-%s-%d", domain, challType, authzID)
}
func unique(a []string) []string {
seen := make(map[string]bool)
var res []string
for _, s := range a {
if s != "" && !seen[s] {
seen[s] = true
res = append(res, s)
}
}
return res
}
+19 -17
View File
@@ -11,9 +11,6 @@ import (
"time"
)
// renewJitter is the maximum deviation from Manager.RenewBefore.
const renewJitter = time.Hour
// domainRenewal tracks the state used by the periodic timers
// renewing a single domain's cert.
type domainRenewal struct {
@@ -30,13 +27,13 @@ type domainRenewal struct {
// defined by the certificate expiration time exp.
//
// If the timer is already started, calling start is a noop.
func (dr *domainRenewal) start(exp time.Time) {
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(exp), dr.renew)
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
@@ -79,7 +76,7 @@ func (dr *domainRenewal) renew() {
// TODO: rotate dr.key at some point?
next, err := dr.do(ctx)
if err != nil {
next = renewJitter / 2
next = time.Hour / 2
next += time.Duration(pseudoRand.int63n(int64(next)))
}
testDidRenewLoop(next, err)
@@ -107,8 +104,8 @@ 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.NotAfter)
if next > dr.m.renewBefore()+renewJitter {
next := dr.next(tlscert.Leaf.NotBefore, tlscert.Leaf.NotAfter)
if next > 0 {
signer, ok := tlscert.PrivateKey.(crypto.Signer)
if ok {
state := &certState{
@@ -139,18 +136,23 @@ func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) {
return 0, err
}
dr.updateState(state)
return dr.next(leaf.NotAfter), nil
return dr.next(leaf.NotBefore, leaf.NotAfter), nil
}
func (dr *domainRenewal) next(expiry time.Time) time.Duration {
d := expiry.Sub(dr.m.now()) - dr.m.renewBefore()
// add a bit of randomness to renew deadline
n := pseudoRand.int63n(int64(renewJitter))
d -= time.Duration(n)
if d < 0 {
return 0
// 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)
}
return d
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) {}
+51 -18
View File
@@ -17,27 +17,60 @@ import (
func TestRenewalNext(t *testing.T) {
now := time.Now()
man := &Manager{
RenewBefore: 7 * 24 * time.Hour,
nowFunc: func() time.Time { return now },
}
defer man.stopRenew()
nowFn := func() time.Time { return now }
tt := []struct {
expiry time.Time
min, max time.Duration
name string
renewBefore time.Duration // arg to Manager
// leaf cert validity
notBefore time.Time
validFor time.Duration
// wait time
waitMin, waitMax time.Duration
}{
{now.Add(90 * 24 * time.Hour), 83*24*time.Hour - renewJitter, 83 * 24 * time.Hour},
{now.Add(time.Hour), 0, 1},
{now, 0, 1},
{now.Add(-time.Hour), 0, 1},
{"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},
}
dr := &domainRenewal{m: man}
for i, test := range tt {
next := dr.next(test.expiry)
if next < test.min || test.max < next {
t.Errorf("%d: next = %v; want between %v and %v", i, next, test.min, test.max)
}
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)
}
})
}
}
@@ -239,7 +272,7 @@ func TestRenewFromCacheAlreadyRenewed(t *testing.T) {
}
// trigger renew
man.startRenew(exampleCertKey, s.key, s.leaf.NotAfter)
man.startRenew(exampleCertKey, s.key, s.leaf.NotBefore, s.leaf.NotAfter)
<-renewed
func() {
man.renewalMu.Lock()
+2 -2
View File
@@ -128,7 +128,7 @@ func wantStatus(codes ...int) resOkay {
func (c *Client) get(ctx context.Context, url string, ok resOkay) (*http.Response, error) {
retry := c.retryTimer()
for {
req, err := http.NewRequest("GET", url, nil)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
@@ -228,7 +228,7 @@ func (c *Client) postNoRetry(ctx context.Context, key crypto.Signer, url string,
if err != nil {
return nil, nil, err
}
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(b))
if err != nil {
return nil, nil, err
}
+809
View File
@@ -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()))
}
}
})
}
+3 -3
View File
@@ -232,7 +232,7 @@ func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderO
return responseOrder(res)
}
// GetOrder retrives an order identified by the given URL.
// 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,
@@ -272,7 +272,7 @@ func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
case err != nil:
// Skip and retry.
case o.Status == StatusInvalid:
return nil, &OrderError{OrderURL: o.URI, Status: o.Status}
return nil, &OrderError{OrderURL: o.URI, Status: o.Status, Problem: o.Error}
case o.Status == StatusReady || o.Status == StatusValid:
return o, nil
}
@@ -369,7 +369,7 @@ func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bu
}
// The only acceptable status post finalize and WaitOrder is "valid".
if o.Status != StatusValid {
return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status}
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
+16 -3
View File
@@ -885,11 +885,17 @@ func TestRFC_WaitOrderError(t *testing.T) {
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 = StatusInvalid
// 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)
}
fmt.Fprintf(w, `{"status": %q}`, s)
count++
})
s.start()
@@ -910,6 +916,13 @@ func TestRFC_WaitOrderError(t *testing.T) {
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) {
+5 -2
View File
@@ -154,13 +154,16 @@ func (a *AuthorizationError) Error() string {
// OrderError is returned from Client's order related methods.
// It indicates the order is unusable and the clients should start over with
// AuthorizeOrder.
// 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 {
@@ -619,7 +622,7 @@ func (*certOptKey) privateCertOpt() {}
//
// In TLS ChallengeCert methods, the template is also used as parent,
// resulting in a self-signed certificate.
// The DNSNames field of t is always overwritten for tls-sni challenge certs.
// The DNSNames or IPAddresses fields of t are always overwritten for tls-alpn challenge certs.
func WithTemplate(t *x509.Certificate) CertOption {
return (*certOptTemplate)(t)
}
+11 -7
View File
@@ -6,7 +6,7 @@
// 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 [1].
// 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.
@@ -17,7 +17,7 @@
// 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 [2]) for non-interactive operations are time=3 and to
// parameters (taken from [RFC 9106 Section 7.3]) for non-interactive operations are time=3 and to
// use the maximum available memory.
//
// # Argon2id
@@ -27,11 +27,11 @@
// 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 [2]) are time=1 and to
// parameters for non-interactive operations (taken from [RFC 9106 Section 7.3]) are time=1 and to
// use the maximum available memory.
//
// [1] https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
// [2] https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03#section-9.3
// [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 (
@@ -59,7 +59,7 @@ const (
//
// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32)
//
// The draft RFC recommends[2] time=3, and memory=32*1024 is a sensible number.
// [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.
//
@@ -69,6 +69,8 @@ const (
// 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)
}
@@ -83,7 +85,7 @@ func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint3
//
// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32)
//
// The draft RFC recommends[2] time=1, and memory=64*1024 is a sensible number.
// [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.
//
@@ -93,6 +95,8 @@ func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint3
// 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)
}
+1 -1
View File
@@ -50,7 +50,7 @@ func (ih InvalidHashPrefixError) Error() string {
type InvalidCostError int
func (ic InvalidCostError) Error() string {
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), MinCost, MaxCost)
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed inclusive range %d..%d", int(ic), MinCost, MaxCost)
}
const (
+8
View File
@@ -12,6 +12,8 @@ import (
// 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.
@@ -47,6 +49,8 @@ const maxOutputLength = (1 << 32) * 64
//
// 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
@@ -93,6 +97,10 @@ func (x *xof) Clone() XOF {
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
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.25
package blake2b
import "hash"
var _ hash.XOF = (*xof)(nil)
+1 -1
View File
@@ -29,7 +29,7 @@ loop:
MOVD $NUM_ROUNDS, R21
VLD1 (R11), [V30.S4, V31.S4]
// load contants
// load constants
// VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4]
WORD $0x4D60E940
@@ -641,7 +641,7 @@ func hashADDone() {
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Implements the following function fignature:
// Implements the following function signature:
//
// func chacha20Poly1305Open(dst []byte, key []uint32, src []byte, ad []byte) bool
func chacha20Poly1305Open() {
@@ -2967,7 +2967,7 @@ func openAVX2Tail512HashEnd() {
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Implements the following function fignature:
// Implements the following function signature:
//
// func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte)
func chacha20Poly1305Seal() {
+3
View File
@@ -38,6 +38,9 @@ type chacha20poly1305 struct {
// 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")
}
+8 -2
View File
@@ -56,7 +56,10 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
ret, out := sliceForAppend(dst, len(plaintext)+16)
if alias.InexactOverlap(out, plaintext) {
panic("chacha20poly1305: invalid buffer overlap")
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
@@ -73,7 +76,10 @@ func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) (
ciphertext = ciphertext[:len(ciphertext)-16]
ret, out := sliceForAppend(dst, len(ciphertext))
if alias.InexactOverlap(out, ciphertext) {
panic("chacha20poly1305: invalid buffer overlap")
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 {
+8 -2
View File
@@ -31,7 +31,10 @@ func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []b
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")
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
@@ -67,7 +70,10 @@ func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []
ret, out := sliceForAppend(dst, len(ciphertext))
if alias.InexactOverlap(out, ciphertext) {
panic("chacha20poly1305: invalid buffer overlap")
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 {
+6 -6
View File
@@ -153,7 +153,7 @@ func TestRandom(t *testing.T) {
t.Run("X", func(t *testing.T) { f(t, NonceSizeX) })
}
func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) {
func benchmarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) {
b.ReportAllocs()
b.SetBytes(int64(len(buf)))
@@ -176,7 +176,7 @@ func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte, nonceSize int) {
}
}
func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
func benchmarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
b.ReportAllocs()
b.SetBytes(int64(len(buf)))
@@ -204,17 +204,17 @@ func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte, nonceSize int) {
func BenchmarkChacha20Poly1305(b *testing.B) {
for _, length := range []int{64, 1350, 8 * 1024} {
b.Run("Open-"+strconv.Itoa(length), func(b *testing.B) {
benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSize)
benchmarkChaCha20Poly1305Open(b, make([]byte, length), NonceSize)
})
b.Run("Seal-"+strconv.Itoa(length), func(b *testing.B) {
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize)
benchmarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSize)
})
b.Run("Open-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
benchamarkChaCha20Poly1305Open(b, make([]byte, length), NonceSizeX)
benchmarkChaCha20Poly1305Open(b, make([]byte, length), NonceSizeX)
})
b.Run("Seal-"+strconv.Itoa(length)+"-X", func(b *testing.B) {
benchamarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX)
benchmarkChaCha20Poly1305Seal(b, make([]byte, length), NonceSizeX)
})
}
}
+9
View File
@@ -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 }
+11
View File
@@ -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() }
+3
View File
@@ -22,6 +22,9 @@ type xchacha20poly1305 struct {
// 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")
}
+8 -5
View File
@@ -3,11 +3,14 @@
// 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.
// See RFC 7748.
// performs scalar multiplication on the elliptic curve known as Curve25519
// according to [RFC 7748].
//
// This package is a wrapper for the X25519 implementation
// in the crypto/ecdh package.
// 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"
@@ -36,7 +39,7 @@ func ScalarBaseMult(dst, scalar *[32]byte) {
curve := ecdh.X25519()
priv, err := curve.NewPrivateKey(scalar[:])
if err != nil {
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
panic("curve25519: " + err.Error())
}
copy(dst[:], priv.PublicKey().Bytes())
}
+7 -4
View File
@@ -2,16 +2,19 @@
// 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. See
// https://ed25519.cr.yp.to/.
// 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
// [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”.
//
// This package is a wrapper around the standard library crypto/ed25519 package.
// 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 (
+5 -5
View File
@@ -1,11 +1,11 @@
module golang.org/x/crypto
go 1.23.0
go 1.24.0
require (
golang.org/x/net v0.21.0 // tagx:ignore
golang.org/x/sys v0.33.0
golang.org/x/term v0.32.0
golang.org/x/net v0.48.0 // tagx:ignore
golang.org/x/sys v0.40.0
golang.org/x/term v0.39.0
)
require golang.org/x/text v0.25.0 // indirect
require golang.org/x/text v0.33.0 // indirect
+8 -8
View File
@@ -1,8 +1,8 @@
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
+1 -1
View File
@@ -141,7 +141,7 @@ func TestRsaPss(t *testing.T) {
}
// Run all the tests twice: the first time with the salt length
// as PSSSaltLengthAuto, and the second time with the salt length
// explictily set to tg.SLen.
// explicitly set to tg.SLen.
for i := 0; i < 2; i++ {
for _, sig := range tg.Tests {
h.Reset()
+10 -19
View File
@@ -2,25 +2,16 @@
// 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.
The Sum function, viewed as a function of the message for a uniform random
key, is designed to meet the standard notion of unforgeability. This means
that an attacker cannot find authenticators for any messages not authenticated
by the sender, even if the attacker has adaptively influenced the messages
authenticated by the sender. For a formal definition see, e.g., Section 2.4
of Bellare, Kilian, and Rogaway, "The security of the cipher block chaining
message authentication code," Journal of Computer and System Sciences 61 (2000),
362–399; http://www-cse.ucsd.edu/~mihir/papers/cbc.html.
auth does not make any promises regarding "strong" unforgeability; perhaps
one valid authenticator can be converted into another valid authenticator for
the same message. NaCl also does not make any promises regarding "truncated
unforgeability."
This package is interoperable with NaCl: https://nacl.cr.yp.to/auth.html.
*/
// 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 (
+1 -1
View File
@@ -114,7 +114,7 @@ func TestSealOpenAnonymous(t *testing.T) {
t.Fatal("expected out to be unchanged")
}
if !bytes.HasPrefix(box, orig) {
t.Fatal("expected out to be coppied to returned slice")
t.Fatal("expected out to be copied to returned slice")
}
_, ok = OpenAnonymous(nil, box[len(out):], publicKey, privateKey)
if !ok {
+8 -13
View File
@@ -4,20 +4,15 @@
// Package sign signs small messages using public-key cryptography.
//
// Sign uses Ed25519 to sign messages. The length of messages is not hidden.
// 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 process
// plaintext without verifying the signature. This is very dangerous, and this API
// discourages it, but a protocol that uses excessive message sizes might present
// some implementations with no other choice.
// 3. 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.
// This package is interoperable with [libsodium], as well as [TweetNaCl].
//
// This package is not interoperable with the current release of NaCl
// (https://nacl.cr.yp.to/sign.html), which does not support Ed25519 yet. However,
// it is compatible with the NaCl fork libsodium (https://www.libsodium.org), as well
// as TweetNaCl (https://tweetnacl.cr.yp.to/).
// 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 (
+1 -1
View File
@@ -69,6 +69,6 @@ func TestGenerateSignOpen(t *testing.T) {
}
if !bytes.Equal(message, testMessage) {
t.Fatalf("verified message does not match signed messge, got\n%x\n, expected\n%x", message, testMessage)
t.Fatalf("verified message does not match signed message, got\n%x\n, expected\n%x", message, testMessage)
}
}
+1 -1
View File
@@ -51,7 +51,7 @@ func TestOpaqueParseReason(t *testing.T) {
const expectedBad = 3
// Test post-conditions, make sure we actually parsed packets as expected.
if badPackets != expectedBad {
t.Errorf("unexpected # unparseable packets: %d (want %d)", 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")
+1 -1
View File
@@ -53,7 +53,7 @@ func (c *Config) hash() crypto.Hash {
func (c *Config) encodedCount() uint8 {
if c == nil || c.S2KCount == 0 {
return 96 // The common case. Correspoding to 65536
return 96 // The common case. Corresponding to 65536
}
i := c.S2KCount
+4
View File
@@ -8,6 +8,10 @@
// 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 (
+3 -3
View File
@@ -75,8 +75,8 @@ const libOTRPrivateKey = `(privkeys
(account
(name "foo@example.com")
(protocol prpl-jabber)
(private-key
(dsa
(private-key
(dsa
(p #00FC07ABCF0DC916AFF6E9AE47BEF60C7AB9B4D6B2469E436630E36F8A489BE812486A09F30B71224508654940A835301ACC525A4FF133FC152CC53DCC59D65C30A54F1993FE13FE63E5823D4C746DB21B90F9B9C00B49EC7404AB1D929BA7FBA12F2E45C6E0A651689750E8528AB8C031D3561FECEE72EBB4A090D450A9B7A857#)
(q #00997BD266EF7B1F60A5C23F3A741F2AEFD07A2081#)
(g #535E360E8A95EBA46A4F7DE50AD6E9B2A6DB785A66B64EB9F20338D2A3E8FB0E94725848F1AA6CC567CB83A1CC517EC806F2E92EAE71457E80B2210A189B91250779434B41FC8A8873F6DB94BEA7D177F5D59E7E114EE10A49CFD9CEF88AE43387023B672927BA74B04EB6BBB5E57597766A2F9CE3857D7ACE3E1E3BC1FC6F26#)
@@ -402,7 +402,7 @@ 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.
return
t.Skip("This test requires otr.c.test to be built as /tmp/a.out")
alicePrivateKey, _ := hex.DecodeString(alicePrivateKeyHex)
var alice Conversation
+9 -5
View File
@@ -4,12 +4,16 @@
// Package pkcs12 implements some of PKCS#12.
//
// This implementation is distilled from https://tools.ietf.org/html/rfc7292
// and referenced documents. It is intended for decoding P12/PFX-stored
// certificates and keys for use with the crypto/tls package.
// 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.
//
// This package is frozen. If it's missing functionality you need, consider
// an alternative like software.sslmate.com/src/go-pkcs12.
// 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 (
+4
View File
@@ -3,6 +3,10 @@
// 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"
-15
View File
@@ -1,15 +0,0 @@
module sha3/_asm
go 1.22
require (
github.com/mmcloughlin/avo v0.6.0
golang.org/x/crypto v0.33.0
)
require (
golang.org/x/mod v0.19.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/tools v0.23.0 // indirect
)
-12
View File
@@ -1,12 +0,0 @@
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
-438
View File
@@ -1,438 +0,0 @@
// 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.
// This code was translated into a form compatible with 6a from the public
// domain sources at https://github.com/gvanas/KeccakCodePackage
package main
import (
. "github.com/mmcloughlin/avo/build"
. "github.com/mmcloughlin/avo/operand"
. "github.com/mmcloughlin/avo/reg"
_ "golang.org/x/crypto/sha3"
)
//go:generate go run . -out ../keccakf_amd64.s -pkg sha3
// Round Constants for use in the ι step.
var RoundConstants = [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,
}
var (
// Temporary registers
rT1 GPPhysical = RAX
// Round vars
rpState = Mem{Base: RDI}
rpStack = Mem{Base: RSP}
rDa = RBX
rDe = RCX
rDi = RDX
rDo = R8
rDu = R9
rBa = R10
rBe = R11
rBi = R12
rBo = R13
rBu = R14
rCa = RSI
rCe = RBP
rCi = rBi
rCo = rBo
rCu = R15
)
const (
_ba = iota * 8
_be
_bi
_bo
_bu
_ga
_ge
_gi
_go
_gu
_ka
_ke
_ki
_ko
_ku
_ma
_me
_mi
_mo
_mu
_sa
_se
_si
_so
_su
)
func main() {
Package("golang.org/x/crypto/sha3")
ConstraintExpr("amd64,!purego,gc")
keccakF1600()
Generate()
}
func MOVQ_RBI_RCE() { MOVQ(rBi, rCe) }
func XORQ_RT1_RCA() { XORQ(rT1, rCa) }
func XORQ_RT1_RCE() { XORQ(rT1, rCe) }
func XORQ_RBA_RCU() { XORQ(rBa, rCu) }
func XORQ_RBE_RCU() { XORQ(rBe, rCu) }
func XORQ_RDU_RCU() { XORQ(rDu, rCu) }
func XORQ_RDA_RCA() { XORQ(rDa, rCa) }
func XORQ_RDE_RCE() { XORQ(rDe, rCe) }
type ArgMacro func()
func mKeccakRound(
iState, oState Mem,
rc U64,
B_RBI_RCE, G_RT1_RCA, G_RT1_RCE, G_RBA_RCU,
K_RT1_RCA, K_RT1_RCE, K_RBA_RCU, M_RT1_RCA,
M_RT1_RCE, M_RBE_RCU, S_RDU_RCU, S_RDA_RCA,
S_RDE_RCE ArgMacro,
) {
Comment("Prepare round")
MOVQ(rCe, rDa)
ROLQ(Imm(1), rDa)
MOVQ(iState.Offset(_bi), rCi)
XORQ(iState.Offset(_gi), rDi)
XORQ(rCu, rDa)
XORQ(iState.Offset(_ki), rCi)
XORQ(iState.Offset(_mi), rDi)
XORQ(rDi, rCi)
MOVQ(rCi, rDe)
ROLQ(Imm(1), rDe)
MOVQ(iState.Offset(_bo), rCo)
XORQ(iState.Offset(_go), rDo)
XORQ(rCa, rDe)
XORQ(iState.Offset(_ko), rCo)
XORQ(iState.Offset(_mo), rDo)
XORQ(rDo, rCo)
MOVQ(rCo, rDi)
ROLQ(Imm(1), rDi)
MOVQ(rCu, rDo)
XORQ(rCe, rDi)
ROLQ(Imm(1), rDo)
MOVQ(rCa, rDu)
XORQ(rCi, rDo)
ROLQ(Imm(1), rDu)
Comment("Result b")
MOVQ(iState.Offset(_ba), rBa)
MOVQ(iState.Offset(_ge), rBe)
XORQ(rCo, rDu)
MOVQ(iState.Offset(_ki), rBi)
MOVQ(iState.Offset(_mo), rBo)
MOVQ(iState.Offset(_su), rBu)
XORQ(rDe, rBe)
ROLQ(Imm(44), rBe)
XORQ(rDi, rBi)
XORQ(rDa, rBa)
ROLQ(Imm(43), rBi)
MOVQ(rBe, rCa)
MOVQ(rc, rT1)
ORQ(rBi, rCa)
XORQ(rBa, rT1)
XORQ(rT1, rCa)
MOVQ(rCa, oState.Offset(_ba))
XORQ(rDu, rBu)
ROLQ(Imm(14), rBu)
MOVQ(rBa, rCu)
ANDQ(rBe, rCu)
XORQ(rBu, rCu)
MOVQ(rCu, oState.Offset(_bu))
XORQ(rDo, rBo)
ROLQ(Imm(21), rBo)
MOVQ(rBo, rT1)
ANDQ(rBu, rT1)
XORQ(rBi, rT1)
MOVQ(rT1, oState.Offset(_bi))
NOTQ(rBi)
ORQ(rBa, rBu)
ORQ(rBo, rBi)
XORQ(rBo, rBu)
XORQ(rBe, rBi)
MOVQ(rBu, oState.Offset(_bo))
MOVQ(rBi, oState.Offset(_be))
B_RBI_RCE()
Comment("Result g")
MOVQ(iState.Offset(_gu), rBe)
XORQ(rDu, rBe)
MOVQ(iState.Offset(_ka), rBi)
ROLQ(Imm(20), rBe)
XORQ(rDa, rBi)
ROLQ(Imm(3), rBi)
MOVQ(iState.Offset(_bo), rBa)
MOVQ(rBe, rT1)
ORQ(rBi, rT1)
XORQ(rDo, rBa)
MOVQ(iState.Offset(_me), rBo)
MOVQ(iState.Offset(_si), rBu)
ROLQ(Imm(28), rBa)
XORQ(rBa, rT1)
MOVQ(rT1, oState.Offset(_ga))
G_RT1_RCA()
XORQ(rDe, rBo)
ROLQ(Imm(45), rBo)
MOVQ(rBi, rT1)
ANDQ(rBo, rT1)
XORQ(rBe, rT1)
MOVQ(rT1, oState.Offset(_ge))
G_RT1_RCE()
XORQ(rDi, rBu)
ROLQ(Imm(61), rBu)
MOVQ(rBu, rT1)
ORQ(rBa, rT1)
XORQ(rBo, rT1)
MOVQ(rT1, oState.Offset(_go))
ANDQ(rBe, rBa)
XORQ(rBu, rBa)
MOVQ(rBa, oState.Offset(_gu))
NOTQ(rBu)
G_RBA_RCU()
ORQ(rBu, rBo)
XORQ(rBi, rBo)
MOVQ(rBo, oState.Offset(_gi))
Comment("Result k")
MOVQ(iState.Offset(_be), rBa)
MOVQ(iState.Offset(_gi), rBe)
MOVQ(iState.Offset(_ko), rBi)
MOVQ(iState.Offset(_mu), rBo)
MOVQ(iState.Offset(_sa), rBu)
XORQ(rDi, rBe)
ROLQ(Imm(6), rBe)
XORQ(rDo, rBi)
ROLQ(Imm(25), rBi)
MOVQ(rBe, rT1)
ORQ(rBi, rT1)
XORQ(rDe, rBa)
ROLQ(Imm(1), rBa)
XORQ(rBa, rT1)
MOVQ(rT1, oState.Offset(_ka))
K_RT1_RCA()
XORQ(rDu, rBo)
ROLQ(Imm(8), rBo)
MOVQ(rBi, rT1)
ANDQ(rBo, rT1)
XORQ(rBe, rT1)
MOVQ(rT1, oState.Offset(_ke))
K_RT1_RCE()
XORQ(rDa, rBu)
ROLQ(Imm(18), rBu)
NOTQ(rBo)
MOVQ(rBo, rT1)
ANDQ(rBu, rT1)
XORQ(rBi, rT1)
MOVQ(rT1, oState.Offset(_ki))
MOVQ(rBu, rT1)
ORQ(rBa, rT1)
XORQ(rBo, rT1)
MOVQ(rT1, oState.Offset(_ko))
ANDQ(rBe, rBa)
XORQ(rBu, rBa)
MOVQ(rBa, oState.Offset(_ku))
K_RBA_RCU()
Comment("Result m")
MOVQ(iState.Offset(_ga), rBe)
XORQ(rDa, rBe)
MOVQ(iState.Offset(_ke), rBi)
ROLQ(Imm(36), rBe)
XORQ(rDe, rBi)
MOVQ(iState.Offset(_bu), rBa)
ROLQ(Imm(10), rBi)
MOVQ(rBe, rT1)
MOVQ(iState.Offset(_mi), rBo)
ANDQ(rBi, rT1)
XORQ(rDu, rBa)
MOVQ(iState.Offset(_so), rBu)
ROLQ(Imm(27), rBa)
XORQ(rBa, rT1)
MOVQ(rT1, oState.Offset(_ma))
M_RT1_RCA()
XORQ(rDi, rBo)
ROLQ(Imm(15), rBo)
MOVQ(rBi, rT1)
ORQ(rBo, rT1)
XORQ(rBe, rT1)
MOVQ(rT1, oState.Offset(_me))
M_RT1_RCE()
XORQ(rDo, rBu)
ROLQ(Imm(56), rBu)
NOTQ(rBo)
MOVQ(rBo, rT1)
ORQ(rBu, rT1)
XORQ(rBi, rT1)
MOVQ(rT1, oState.Offset(_mi))
ORQ(rBa, rBe)
XORQ(rBu, rBe)
MOVQ(rBe, oState.Offset(_mu))
ANDQ(rBa, rBu)
XORQ(rBo, rBu)
MOVQ(rBu, oState.Offset(_mo))
M_RBE_RCU()
Comment("Result s")
MOVQ(iState.Offset(_bi), rBa)
MOVQ(iState.Offset(_go), rBe)
MOVQ(iState.Offset(_ku), rBi)
XORQ(rDi, rBa)
MOVQ(iState.Offset(_ma), rBo)
ROLQ(Imm(62), rBa)
XORQ(rDo, rBe)
MOVQ(iState.Offset(_se), rBu)
ROLQ(Imm(55), rBe)
XORQ(rDu, rBi)
MOVQ(rBa, rDu)
XORQ(rDe, rBu)
ROLQ(Imm(2), rBu)
ANDQ(rBe, rDu)
XORQ(rBu, rDu)
MOVQ(rDu, oState.Offset(_su))
ROLQ(Imm(39), rBi)
S_RDU_RCU()
NOTQ(rBe)
XORQ(rDa, rBo)
MOVQ(rBe, rDa)
ANDQ(rBi, rDa)
XORQ(rBa, rDa)
MOVQ(rDa, oState.Offset(_sa))
S_RDA_RCA()
ROLQ(Imm(41), rBo)
MOVQ(rBi, rDe)
ORQ(rBo, rDe)
XORQ(rBe, rDe)
MOVQ(rDe, oState.Offset(_se))
S_RDE_RCE()
MOVQ(rBo, rDi)
MOVQ(rBu, rDo)
ANDQ(rBu, rDi)
ORQ(rBa, rDo)
XORQ(rBi, rDi)
XORQ(rBo, rDo)
MOVQ(rDi, oState.Offset(_si))
MOVQ(rDo, oState.Offset(_so))
}
// keccakF1600 applies the Keccak permutation to a 1600b-wide
// state represented as a slice of 25 uint64s.
func keccakF1600() {
Implement("keccakF1600")
AllocLocal(200)
Load(Param("a"), rpState.Base)
Comment("Convert the user state into an internal state")
NOTQ(rpState.Offset(_be))
NOTQ(rpState.Offset(_bi))
NOTQ(rpState.Offset(_go))
NOTQ(rpState.Offset(_ki))
NOTQ(rpState.Offset(_mi))
NOTQ(rpState.Offset(_sa))
Comment("Execute the KeccakF permutation")
MOVQ(rpState.Offset(_ba), rCa)
MOVQ(rpState.Offset(_be), rCe)
MOVQ(rpState.Offset(_bu), rCu)
XORQ(rpState.Offset(_ga), rCa)
XORQ(rpState.Offset(_ge), rCe)
XORQ(rpState.Offset(_gu), rCu)
XORQ(rpState.Offset(_ka), rCa)
XORQ(rpState.Offset(_ke), rCe)
XORQ(rpState.Offset(_ku), rCu)
XORQ(rpState.Offset(_ma), rCa)
XORQ(rpState.Offset(_me), rCe)
XORQ(rpState.Offset(_mu), rCu)
XORQ(rpState.Offset(_sa), rCa)
XORQ(rpState.Offset(_se), rCe)
MOVQ(rpState.Offset(_si), rDi)
MOVQ(rpState.Offset(_so), rDo)
XORQ(rpState.Offset(_su), rCu)
for i, rc := range RoundConstants[:len(RoundConstants)-1] {
var iState, oState Mem
if i%2 == 0 {
iState, oState = rpState, rpStack
} else {
iState, oState = rpStack, rpState
}
mKeccakRound(iState, oState, U64(rc), MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE)
}
mKeccakRound(rpStack, rpState, U64(RoundConstants[len(RoundConstants)-1]), NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP)
Comment("Revert the internal state to the user state")
NOTQ(rpState.Offset(_be))
NOTQ(rpState.Offset(_bi))
NOTQ(rpState.Offset(_go))
NOTQ(rpState.Offset(_ki))
NOTQ(rpState.Offset(_mi))
NOTQ(rpState.Offset(_sa))
RET()
}
-61
View File
@@ -1,61 +0,0 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !noopt
package sha3_test
import (
"runtime"
"testing"
"golang.org/x/crypto/sha3"
)
var sink byte
func TestAllocations(t *testing.T) {
want := 0.0
if runtime.GOARCH == "s390x" {
// On s390x the returned hash.Hash is conditional so it escapes.
want = 3.0
}
t.Run("New", func(t *testing.T) {
if allocs := testing.AllocsPerRun(10, func() {
h := sha3.New256()
b := []byte("ABC")
h.Write(b)
out := make([]byte, 0, 32)
out = h.Sum(out)
sink ^= out[0]
}); allocs > want {
t.Errorf("expected zero allocations, got %0.1f", allocs)
}
})
t.Run("NewShake", func(t *testing.T) {
if allocs := testing.AllocsPerRun(10, func() {
h := sha3.NewShake128()
b := []byte("ABC")
h.Write(b)
out := make([]byte, 0, 32)
out = h.Sum(out)
sink ^= out[0]
h.Read(out)
sink ^= out[0]
}); allocs > want {
t.Errorf("expected zero allocations, got %0.1f", allocs)
}
})
t.Run("Sum", func(t *testing.T) {
if allocs := testing.AllocsPerRun(10, func() {
b := []byte("ABC")
out := sha3.Sum256(b)
sink ^= out[0]
}); allocs > want {
t.Errorf("expected zero allocations, got %0.1f", allocs)
}
})
}
-66
View File
@@ -1,66 +0,0 @@
// 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 fixed-output-length hash functions and
// the SHAKE variable-output-length hash functions defined by FIPS-202.
//
// All types in this package also implement [encoding.BinaryMarshaler],
// [encoding.BinaryAppender] and [encoding.BinaryUnmarshaler] to marshal and
// unmarshal the internal state of the hash.
//
// Both types of hash function use the "sponge" construction and the Keccak
// permutation. For a detailed specification see http://keccak.noekeon.org/
//
// # Guidance
//
// If you aren't sure what function you need, use SHAKE256 with at least 64
// bytes of output. The SHAKE instances are faster than the SHA3 instances;
// the latter have to allocate memory to conform to the hash.Hash interface.
//
// If you need a secret-key MAC (message authentication code), prepend the
// secret key to the input, hash with SHAKE256 and read at least 32 bytes of
// output.
//
// # Security strengths
//
// The SHA3-x (x equals 224, 256, 384, or 512) functions have a security
// strength against preimage attacks of x bits. Since they only produce "x"
// bits of output, their collision-resistance is only "x/2" bits.
//
// The SHAKE-256 and -128 functions have a generic security strength of 256 and
// 128 bits against all attacks, provided that at least 2x bits of their output
// is used. Requesting more than 64 or 32 bytes of output, respectively, does
// not increase the collision-resistance of the SHAKE functions.
//
// # The sponge construction
//
// A sponge builds a pseudo-random function from a public pseudo-random
// permutation, by applying the permutation to a state of "rate + capacity"
// bytes, but hiding "capacity" of the bytes.
//
// A sponge starts out with a zero state. To hash an input using a sponge, up
// to "rate" bytes of the input are XORed into the sponge's state. The sponge
// is then "full" and the permutation is applied to "empty" it. This process is
// repeated until all the input has been "absorbed". The input is then padded.
// The digest is "squeezed" from the sponge in the same way, except that output
// is copied out instead of input being XORed in.
//
// A sponge is parameterized by its generic security strength, which is equal
// to half its capacity; capacity + rate is equal to the permutation's width.
// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means
// that the security strength of a sponge instance is equal to (1600 - bitrate) / 2.
//
// # Recommendations
//
// The SHAKE functions are recommended for most new uses. They can produce
// output of arbitrary length. SHAKE256, with an output length of at least
// 64 bytes, provides 256-bit security against all attacks. The Keccak team
// recommends it for most applications upgrading from SHA2-512. (NIST chose a
// much stronger, but much slower, sponge instance for SHA3-512.)
//
// The SHA-3 functions are "drop-in" replacements for the SHA-2 functions.
// They produce output of the same length, with the same security strengths
// against all attacks. This means, in particular, that SHA3-256 only has
// 128-bit collision resistance, because its output length is 32 bytes.
package sha3
+50 -83
View File
@@ -2,127 +2,94 @@
// 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
// This file provides functions for creating instances of the SHA-3
// and SHAKE hash functions, as well as utility functions for hashing
// bytes.
import (
"crypto"
"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 new224()
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 new256()
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 new384()
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 new512()
}
func init() {
crypto.RegisterHash(crypto.SHA3_224, New224)
crypto.RegisterHash(crypto.SHA3_256, New256)
crypto.RegisterHash(crypto.SHA3_384, New384)
crypto.RegisterHash(crypto.SHA3_512, New512)
}
const (
dsbyteSHA3 = 0b00000110
dsbyteKeccak = 0b00000001
dsbyteShake = 0b00011111
dsbyteCShake = 0b00000100
// 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
rateK448 = (1600 - 448) / 8
rateK512 = (1600 - 512) / 8
rateK768 = (1600 - 768) / 8
rateK1024 = (1600 - 1024) / 8
)
func new224Generic() *state {
return &state{rate: rateK448, outputLen: 28, dsbyte: dsbyteSHA3}
}
func new256Generic() *state {
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteSHA3}
}
func new384Generic() *state {
return &state{rate: rateK768, outputLen: 48, dsbyte: dsbyteSHA3}
}
func new512Generic() *state {
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteSHA3}
}
// 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}
return sha3.New512()
}
// Sum224 returns the SHA3-224 digest of the data.
func Sum224(data []byte) (digest [28]byte) {
h := New224()
h.Write(data)
h.Sum(digest[:0])
return
//
// 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.
func Sum256(data []byte) (digest [32]byte) {
h := New256()
h.Write(data)
h.Sum(digest[:0])
return
//
// 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.
func Sum384(data []byte) (digest [48]byte) {
h := New384()
h.Write(data)
h.Sum(digest[:0])
return
//
// 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.
func Sum512(data []byte) (digest [64]byte) {
h := New512()
h.Write(data)
h.Sum(digest[:0])
return
//
// 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)
}
-23
View File
@@ -1,23 +0,0 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !gc || purego || !s390x
package sha3
func new224() *state {
return new224Generic()
}
func new256() *state {
return new256Generic()
}
func new384() *state {
return new384Generic()
}
func new512() *state {
return new512Generic()
}
-13
View File
@@ -1,13 +0,0 @@
// 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.
//go:build amd64 && !purego && gc
package sha3
// This function is implemented in keccakf_amd64.s.
//go:noescape
func keccakF1600(a *[25]uint64)
-5419
View File
File diff suppressed because it is too large Load Diff
+34 -15
View File
@@ -4,15 +4,46 @@
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
@@ -173,12 +204,9 @@ func (d *state) Sum(in []byte) []byte {
}
const (
magicSHA3 = "sha\x08"
magicShake = "sha\x09"
magicCShake = "sha\x0a"
magicKeccak = "sha\x0b"
// magic || rate || main state || n || sponge direction
marshaledSize = len(magicSHA3) + 1 + 200 + 1 + 1
marshaledSize = len(magicKeccak) + 1 + 200 + 1 + 1
)
func (d *state) MarshalBinary() ([]byte, error) {
@@ -187,12 +215,6 @@ func (d *state) MarshalBinary() ([]byte, error) {
func (d *state) AppendBinary(b []byte) ([]byte, error) {
switch d.dsbyte {
case dsbyteSHA3:
b = append(b, magicSHA3...)
case dsbyteShake:
b = append(b, magicShake...)
case dsbyteCShake:
b = append(b, magicCShake...)
case dsbyteKeccak:
b = append(b, magicKeccak...)
default:
@@ -210,12 +232,9 @@ func (d *state) UnmarshalBinary(b []byte) error {
return errors.New("sha3: invalid hash state")
}
magic := string(b[:len(magicSHA3)])
b = b[len(magicSHA3):]
magic := string(b[:len(magicKeccak)])
b = b[len(magicKeccak):]
switch {
case magic == magicSHA3 && d.dsbyte == dsbyteSHA3:
case magic == magicShake && d.dsbyte == dsbyteShake:
case magic == magicCShake && d.dsbyte == dsbyteCShake:
case magic == magicKeccak && d.dsbyte == dsbyteKeccak:
default:
return errors.New("sha3: invalid hash state identifier")
+4 -2
View File
@@ -2,10 +2,12 @@
// 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 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.
-303
View File
@@ -1,303 +0,0 @@
// 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 gc && !purego
package sha3
// This file contains code for using the 'compute intermediate
// message digest' (KIMD) and 'compute last message digest' (KLMD)
// instructions to compute SHA-3 and SHAKE hashes on IBM Z.
import (
"hash"
"golang.org/x/sys/cpu"
)
// codes represent 7-bit KIMD/KLMD function codes as defined in
// the Principles of Operation.
type code uint64
const (
// function codes for KIMD/KLMD
sha3_224 code = 32
sha3_256 = 33
sha3_384 = 34
sha3_512 = 35
shake_128 = 36
shake_256 = 37
nopad = 0x100
)
// kimd is a wrapper for the 'compute intermediate message digest' instruction.
// src must be a multiple of the rate for the given function code.
//
//go:noescape
func kimd(function code, chain *[200]byte, src []byte)
// klmd is a wrapper for the 'compute last message digest' instruction.
// src padding is handled by the instruction.
//
//go:noescape
func klmd(function code, chain *[200]byte, dst, src []byte)
type asmState struct {
a [200]byte // 1600 bit state
buf []byte // care must be taken to ensure cap(buf) is a multiple of rate
rate int // equivalent to block size
storage [3072]byte // underlying storage for buf
outputLen int // output length for full security
function code // KIMD/KLMD function code
state spongeDirection // whether the sponge is absorbing or squeezing
}
func newAsmState(function code) *asmState {
var s asmState
s.function = function
switch function {
case sha3_224:
s.rate = 144
s.outputLen = 28
case sha3_256:
s.rate = 136
s.outputLen = 32
case sha3_384:
s.rate = 104
s.outputLen = 48
case sha3_512:
s.rate = 72
s.outputLen = 64
case shake_128:
s.rate = 168
s.outputLen = 32
case shake_256:
s.rate = 136
s.outputLen = 64
default:
panic("sha3: unrecognized function code")
}
// limit s.buf size to a multiple of s.rate
s.resetBuf()
return &s
}
func (s *asmState) clone() *asmState {
c := *s
c.buf = c.storage[:len(s.buf):cap(s.buf)]
return &c
}
// copyIntoBuf copies b into buf. It will panic if there is not enough space to
// store all of b.
func (s *asmState) copyIntoBuf(b []byte) {
bufLen := len(s.buf)
s.buf = s.buf[:len(s.buf)+len(b)]
copy(s.buf[bufLen:], b)
}
// resetBuf points buf at storage, sets the length to 0 and sets cap to be a
// multiple of the rate.
func (s *asmState) resetBuf() {
max := (cap(s.storage) / s.rate) * s.rate
s.buf = s.storage[:0:max]
}
// Write (via the embedded io.Writer interface) adds more data to the running hash.
// It never returns an error.
func (s *asmState) Write(b []byte) (int, error) {
if s.state != spongeAbsorbing {
panic("sha3: Write after Read")
}
length := len(b)
for len(b) > 0 {
if len(s.buf) == 0 && len(b) >= cap(s.buf) {
// Hash the data directly and push any remaining bytes
// into the buffer.
remainder := len(b) % s.rate
kimd(s.function, &s.a, b[:len(b)-remainder])
if remainder != 0 {
s.copyIntoBuf(b[len(b)-remainder:])
}
return length, nil
}
if len(s.buf) == cap(s.buf) {
// flush the buffer
kimd(s.function, &s.a, s.buf)
s.buf = s.buf[:0]
}
// copy as much as we can into the buffer
n := len(b)
if len(b) > cap(s.buf)-len(s.buf) {
n = cap(s.buf) - len(s.buf)
}
s.copyIntoBuf(b[:n])
b = b[n:]
}
return length, nil
}
// Read squeezes an arbitrary number of bytes from the sponge.
func (s *asmState) Read(out []byte) (n int, err error) {
// The 'compute last message digest' instruction only stores the digest
// at the first operand (dst) for SHAKE functions.
if s.function != shake_128 && s.function != shake_256 {
panic("sha3: can only call Read for SHAKE functions")
}
n = len(out)
// need to pad if we were absorbing
if s.state == spongeAbsorbing {
s.state = spongeSqueezing
// write hash directly into out if possible
if len(out)%s.rate == 0 {
klmd(s.function, &s.a, out, s.buf) // len(out) may be 0
s.buf = s.buf[:0]
return
}
// write hash into buffer
max := cap(s.buf)
if max > len(out) {
max = (len(out)/s.rate)*s.rate + s.rate
}
klmd(s.function, &s.a, s.buf[:max], s.buf)
s.buf = s.buf[:max]
}
for len(out) > 0 {
// flush the buffer
if len(s.buf) != 0 {
c := copy(out, s.buf)
out = out[c:]
s.buf = s.buf[c:]
continue
}
// write hash directly into out if possible
if len(out)%s.rate == 0 {
klmd(s.function|nopad, &s.a, out, nil)
return
}
// write hash into buffer
s.resetBuf()
if cap(s.buf) > len(out) {
s.buf = s.buf[:(len(out)/s.rate)*s.rate+s.rate]
}
klmd(s.function|nopad, &s.a, s.buf, nil)
}
return
}
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
func (s *asmState) Sum(b []byte) []byte {
if s.state != spongeAbsorbing {
panic("sha3: Sum after Read")
}
// Copy the state to preserve the original.
a := s.a
// Hash the buffer. Note that we don't clear it because we
// aren't updating the state.
switch s.function {
case sha3_224, sha3_256, sha3_384, sha3_512:
klmd(s.function, &a, nil, s.buf)
return append(b, a[:s.outputLen]...)
case shake_128, shake_256:
d := make([]byte, s.outputLen, 64)
klmd(s.function, &a, d, s.buf)
return append(b, d[:s.outputLen]...)
default:
panic("sha3: unknown function")
}
}
// Reset resets the Hash to its initial state.
func (s *asmState) Reset() {
for i := range s.a {
s.a[i] = 0
}
s.resetBuf()
s.state = spongeAbsorbing
}
// Size returns the number of bytes Sum will return.
func (s *asmState) Size() int {
return s.outputLen
}
// BlockSize returns the hash's underlying block size.
// The Write method must be able to accept any amount
// of data, but it may operate more efficiently if all writes
// are a multiple of the block size.
func (s *asmState) BlockSize() int {
return s.rate
}
// Clone returns a copy of the ShakeHash in its current state.
func (s *asmState) Clone() ShakeHash {
return s.clone()
}
// new224 returns an assembly implementation of SHA3-224 if available,
// otherwise it returns a generic implementation.
func new224() hash.Hash {
if cpu.S390X.HasSHA3 {
return newAsmState(sha3_224)
}
return new224Generic()
}
// new256 returns an assembly implementation of SHA3-256 if available,
// otherwise it returns a generic implementation.
func new256() hash.Hash {
if cpu.S390X.HasSHA3 {
return newAsmState(sha3_256)
}
return new256Generic()
}
// new384 returns an assembly implementation of SHA3-384 if available,
// otherwise it returns a generic implementation.
func new384() hash.Hash {
if cpu.S390X.HasSHA3 {
return newAsmState(sha3_384)
}
return new384Generic()
}
// new512 returns an assembly implementation of SHA3-512 if available,
// otherwise it returns a generic implementation.
func new512() hash.Hash {
if cpu.S390X.HasSHA3 {
return newAsmState(sha3_512)
}
return new512Generic()
}
// newShake128 returns an assembly implementation of SHAKE-128 if available,
// otherwise it returns a generic implementation.
func newShake128() ShakeHash {
if cpu.S390X.HasSHA3 {
return newAsmState(shake_128)
}
return newShake128Generic()
}
// newShake256 returns an assembly implementation of SHAKE-256 if available,
// otherwise it returns a generic implementation.
func newShake256() ShakeHash {
if cpu.S390X.HasSHA3 {
return newAsmState(shake_256)
}
return newShake256Generic()
}
-33
View File
@@ -1,33 +0,0 @@
// 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 gc && !purego
#include "textflag.h"
// func kimd(function code, chain *[200]byte, src []byte)
TEXT ·kimd(SB), NOFRAME|NOSPLIT, $0-40
MOVD function+0(FP), R0
MOVD chain+8(FP), R1
LMG src+16(FP), R2, R3 // R2=base, R3=len
continue:
WORD $0xB93E0002 // KIMD --, R2
BVS continue // continue if interrupted
MOVD $0, R0 // reset R0 for pre-go1.8 compilers
RET
// func klmd(function code, chain *[200]byte, dst, src []byte)
TEXT ·klmd(SB), NOFRAME|NOSPLIT, $0-64
// TODO: SHAKE support
MOVD function+0(FP), R0
MOVD chain+8(FP), R1
LMG dst+16(FP), R2, R3 // R2=base, R3=len
LMG src+40(FP), R4, R5 // R4=base, R5=len
continue:
WORD $0xB93F0024 // KLMD R2, R4
BVS continue // continue if interrupted
MOVD $0, R0 // reset R0 for pre-go1.8 compilers
RET
-122
View File
@@ -16,7 +16,6 @@ import (
"encoding"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"io"
"math/rand"
@@ -523,124 +522,3 @@ func testMarshalUnmarshal(t *testing.T, h hash.Hash) {
t.Errorf("got %x, want %x", got, want)
}
}
// BenchmarkPermutationFunction measures the speed of the permutation function
// with no input data.
func BenchmarkPermutationFunction(b *testing.B) {
b.SetBytes(int64(200))
var lanes [25]uint64
for i := 0; i < b.N; i++ {
keccakF1600(&lanes)
}
}
// benchmarkHash tests the speed to hash num buffers of buflen each.
func benchmarkHash(b *testing.B, h hash.Hash, size, num int) {
b.StopTimer()
h.Reset()
data := sequentialBytes(size)
b.SetBytes(int64(size * num))
b.StartTimer()
var state []byte
for i := 0; i < b.N; i++ {
for j := 0; j < num; j++ {
h.Write(data)
}
state = h.Sum(state[:0])
}
b.StopTimer()
h.Reset()
}
// benchmarkShake is specialized to the Shake instances, which don't
// require a copy on reading output.
func benchmarkShake(b *testing.B, h ShakeHash, size, num int) {
b.StopTimer()
h.Reset()
data := sequentialBytes(size)
d := make([]byte, 32)
b.SetBytes(int64(size * num))
b.StartTimer()
for i := 0; i < b.N; i++ {
h.Reset()
for j := 0; j < num; j++ {
h.Write(data)
}
h.Read(d)
}
}
func BenchmarkSha3_512_MTU(b *testing.B) { benchmarkHash(b, New512(), 1350, 1) }
func BenchmarkSha3_384_MTU(b *testing.B) { benchmarkHash(b, New384(), 1350, 1) }
func BenchmarkSha3_256_MTU(b *testing.B) { benchmarkHash(b, New256(), 1350, 1) }
func BenchmarkSha3_224_MTU(b *testing.B) { benchmarkHash(b, New224(), 1350, 1) }
func BenchmarkShake128_MTU(b *testing.B) { benchmarkShake(b, NewShake128(), 1350, 1) }
func BenchmarkShake256_MTU(b *testing.B) { benchmarkShake(b, NewShake256(), 1350, 1) }
func BenchmarkShake256_16x(b *testing.B) { benchmarkShake(b, NewShake256(), 16, 1024) }
func BenchmarkShake256_1MiB(b *testing.B) { benchmarkShake(b, NewShake256(), 1024, 1024) }
func BenchmarkSha3_512_1MiB(b *testing.B) { benchmarkHash(b, New512(), 1024, 1024) }
func Example_sum() {
buf := []byte("some data to hash")
// A hash needs to be 64 bytes long to have 256-bit collision resistance.
h := make([]byte, 64)
// Compute a 64-byte hash of buf and put it in h.
ShakeSum256(h, buf)
fmt.Printf("%x\n", h)
// Output: 0f65fe41fc353e52c55667bb9e2b27bfcc8476f2c413e9437d272ee3194a4e3146d05ec04a25d16b8f577c19b82d16b1424c3e022e783d2b4da98de3658d363d
}
func Example_mac() {
k := []byte("this is a secret key; you should generate a strong random key that's at least 32 bytes long")
buf := []byte("and this is some data to authenticate")
// A MAC with 32 bytes of output has 256-bit security strength -- if you use at least a 32-byte-long key.
h := make([]byte, 32)
d := NewShake256()
// Write the key into the hash.
d.Write(k)
// Now write the data.
d.Write(buf)
// Read 32 bytes of output from the hash into h.
d.Read(h)
fmt.Printf("%x\n", h)
// Output: 78de2974bd2711d5549ffd32b753ef0f5fa80a0db2556db60f0987eb8a9218ff
}
func ExampleNewCShake256() {
out := make([]byte, 32)
msg := []byte("The quick brown fox jumps over the lazy dog")
// Example 1: Simple cshake
c1 := NewCShake256([]byte("NAME"), []byte("Partition1"))
c1.Write(msg)
c1.Read(out)
fmt.Println(hex.EncodeToString(out))
// Example 2: Different customization string produces different digest
c1 = NewCShake256([]byte("NAME"), []byte("Partition2"))
c1.Write(msg)
c1.Read(out)
fmt.Println(hex.EncodeToString(out))
// Example 3: Longer output length produces longer digest
out = make([]byte, 64)
c1 = NewCShake256([]byte("NAME"), []byte("Partition1"))
c1.Write(msg)
c1.Read(out)
fmt.Println(hex.EncodeToString(out))
// Example 4: Next read produces different result
c1.Read(out)
fmt.Println(hex.EncodeToString(out))
// Output:
//a90a4c6ca9af2156eba43dc8398279e6b60dcd56fb21837afe6c308fd4ceb05b
//a8db03e71f3e4da5c4eee9d28333cdd355f51cef3c567e59be5beb4ecdbb28f0
//a90a4c6ca9af2156eba43dc8398279e6b60dcd56fb21837afe6c308fd4ceb05b9dd98c6ee866ca7dc5a39d53e960f400bcd5a19c8a2d6ec6459f63696543a0d8
//85e73a72228d08b46515553ca3a29d47df3047e5d84b12d6c2c63e579f4fd1105716b7838e92e981863907f434bfd4443c9e56ea09da998d2f9b47db71988109
}
+49 -123
View File
@@ -4,24 +4,10 @@
package sha3
// This file defines the ShakeHash interface, and provides
// functions for creating SHAKE and cSHAKE instances, as well as utility
// functions for hashing bytes to arbitrary-length output.
//
//
// SHAKE implementation is based on FIPS PUB 202 [1]
// cSHAKE implementations is based on NIST SP 800-185 [2]
//
// [1] https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
// [2] https://doi.org/10.6028/NIST.SP.800-185
import (
"bytes"
"encoding/binary"
"errors"
"crypto/sha3"
"hash"
"io"
"math/bits"
)
// ShakeHash defines the interface to hash functions that support
@@ -32,7 +18,7 @@ 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)
// 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
@@ -41,115 +27,18 @@ type ShakeHash interface {
Clone() ShakeHash
}
// cSHAKE specific context
type cshakeState struct {
*state // SHA-3 state context and Read/Write operations
// initBlock is the cSHAKE specific initialization set of bytes. It is initialized
// by newCShake function and stores concatenation of N followed by S, encoded
// by the method specified in 3.3 of [1].
// It is stored here in order for Reset() to be able to put context into
// initial state.
initBlock []byte
}
func bytepad(data []byte, rate int) []byte {
out := make([]byte, 0, 9+len(data)+rate-1)
out = append(out, leftEncode(uint64(rate))...)
out = append(out, data...)
if padlen := rate - len(out)%rate; padlen < rate {
out = append(out, make([]byte, padlen)...)
}
return out
}
func leftEncode(x uint64) []byte {
// Let n be the smallest positive integer for which 2^(8n) > x.
n := (bits.Len64(x) + 7) / 8
if n == 0 {
n = 1
}
// Return n || x with n as a byte and x an n bytes in big-endian order.
b := make([]byte, 9)
binary.BigEndian.PutUint64(b[1:], x)
b = b[9-n-1:]
b[0] = byte(n)
return b
}
func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) ShakeHash {
c := cshakeState{state: &state{rate: rate, outputLen: outputLen, dsbyte: dsbyte}}
c.initBlock = make([]byte, 0, 9+len(N)+9+len(S)) // leftEncode returns max 9 bytes
c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...)
c.initBlock = append(c.initBlock, N...)
c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...)
c.initBlock = append(c.initBlock, S...)
c.Write(bytepad(c.initBlock, c.rate))
return &c
}
// Reset resets the hash to initial state.
func (c *cshakeState) Reset() {
c.state.Reset()
c.Write(bytepad(c.initBlock, c.rate))
}
// Clone returns copy of a cSHAKE context within its current state.
func (c *cshakeState) Clone() ShakeHash {
b := make([]byte, len(c.initBlock))
copy(b, c.initBlock)
return &cshakeState{state: c.clone(), initBlock: b}
}
// Clone returns copy of SHAKE context within its current state.
func (c *state) Clone() ShakeHash {
return c.clone()
}
func (c *cshakeState) MarshalBinary() ([]byte, error) {
return c.AppendBinary(make([]byte, 0, marshaledSize+len(c.initBlock)))
}
func (c *cshakeState) AppendBinary(b []byte) ([]byte, error) {
b, err := c.state.AppendBinary(b)
if err != nil {
return nil, err
}
b = append(b, c.initBlock...)
return b, nil
}
func (c *cshakeState) UnmarshalBinary(b []byte) error {
if len(b) <= marshaledSize {
return errors.New("sha3: invalid hash state")
}
if err := c.state.UnmarshalBinary(b[:marshaledSize]); err != nil {
return err
}
c.initBlock = bytes.Clone(b[marshaledSize:])
return nil
}
// 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 newShake128()
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 newShake256()
}
func newShake128Generic() *state {
return &state{rate: rateK256, outputLen: 32, dsbyte: dsbyteShake}
}
func newShake256Generic() *state {
return &state{rate: rateK512, outputLen: 64, dsbyte: dsbyteShake}
return &shakeWrapper{sha3.NewSHAKE256(), 64, false, sha3.NewSHAKE256}
}
// NewCShake128 creates a new instance of cSHAKE128 variable-output-length ShakeHash,
@@ -159,10 +48,9 @@ func newShake256Generic() *state {
// 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 {
if len(N) == 0 && len(S) == 0 {
return NewShake128()
}
return newCShake(N, S, rateK256, 32, dsbyteCShake)
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,
@@ -172,10 +60,9 @@ func NewCShake128(N, S []byte) ShakeHash {
// 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 {
if len(N) == 0 && len(S) == 0 {
return NewShake256()
}
return newCShake(N, S, rateK512, 64, dsbyteCShake)
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.
@@ -191,3 +78,42 @@ func ShakeSum256(hash, data []byte) {
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...)
}
-15
View File
@@ -1,15 +0,0 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !gc || purego || !s390x
package sha3
func newShake128() *state {
return newShake128Generic()
}
func newShake256() *state {
return newShake256Generic()
}
+15 -13
View File
@@ -430,8 +430,9 @@ func (c *client) List() ([]*Key, error) {
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)
}
panic("unreachable")
}
// Sign has the agent sign the data using a protocol 2 key as defined
@@ -462,8 +463,9 @@ func (c *client) SignWithFlags(key ssh.PublicKey, data []byte, flags SignatureFl
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)
}
panic("unreachable")
}
// unmarshal parses an agent message in packet, returning the parsed
@@ -555,7 +557,7 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er
})
case *dsa.PrivateKey:
req = ssh.Marshal(dsaKeyMsg{
Type: ssh.KeyAlgoDSA,
Type: ssh.InsecureKeyAlgoDSA,
P: k.P,
Q: k.Q,
G: k.G,
@@ -803,16 +805,16 @@ var _ ssh.AlgorithmSigner = &agentKeyringSigner{}
//
// 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.CertAlgoDSAv01: ssh.KeyAlgoDSA,
ssh.CertAlgoECDSA256v01: ssh.KeyAlgoECDSA256,
ssh.CertAlgoECDSA384v01: ssh.KeyAlgoECDSA384,
ssh.CertAlgoECDSA521v01: ssh.KeyAlgoECDSA521,
ssh.CertAlgoSKECDSA256v01: ssh.KeyAlgoSKECDSA256,
ssh.CertAlgoED25519v01: ssh.KeyAlgoED25519,
ssh.CertAlgoSKED25519v01: ssh.KeyAlgoSKED25519,
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
+53 -1
View File
@@ -7,6 +7,7 @@ package agent
import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"io"
"net"
@@ -39,7 +40,11 @@ func startOpenSSHAgent(t *testing.T) (client ExtendedAgent, socket string, clean
}
cmd := exec.Command(bin, "-s")
cmd.Env = []string{} // Do not let the user's environment influence ssh-agent behavior.
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 {
@@ -347,6 +352,53 @@ func TestServerResponseTooLarge(t *testing.T) {
}
}
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()
+1 -1
View File
@@ -112,7 +112,7 @@ func (r *keyring) Unlock(passphrase []byte) error {
}
// expireKeysLocked removes expired keys from the keyring. If a key was added
// with a lifetimesecs contraint and seconds >= lifetimesecs seconds have
// 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 {
+1 -1
View File
@@ -30,7 +30,7 @@ func validateListedKeys(t *testing.T, a Agent, expectedKeys []string) {
return
}
if len(listedKeys) != len(expectedKeys) {
t.Fatalf("expeted %d key, got %d", len(expectedKeys), len(listedKeys))
t.Fatalf("expected %d key, got %d", len(expectedKeys), len(listedKeys))
return
}
actualKeys := make(map[string]bool)
+5 -2
View File
@@ -203,6 +203,9 @@ func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse
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:
@@ -506,7 +509,7 @@ func (s *server) insertIdentity(req []byte) error {
switch record.Type {
case ssh.KeyAlgoRSA:
addedKey, err = parseRSAKey(req)
case ssh.KeyAlgoDSA:
case ssh.InsecureKeyAlgoDSA:
addedKey, err = parseDSAKey(req)
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
addedKey, err = parseECDSAKey(req)
@@ -514,7 +517,7 @@ func (s *server) insertIdentity(req []byte) error {
addedKey, err = parseEd25519Key(req)
case ssh.CertAlgoRSAv01:
addedKey, err = parseRSACert(req)
case ssh.CertAlgoDSAv01:
case ssh.InsecureCertAlgoDSAv01:
addedKey, err = parseDSACert(req)
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
addedKey, err = parseECDSACert(req)
+7
View File
@@ -8,6 +8,7 @@ import (
"crypto"
"crypto/rand"
"fmt"
"io"
pseudorand "math/rand"
"reflect"
"strings"
@@ -258,6 +259,12 @@ func TestParseConstraints(t *testing.T) {
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") {
+40 -27
View File
@@ -20,14 +20,19 @@ import (
// returned by MultiAlgorithmSigner and don't appear in the Signature.Format
// field.
const (
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
CertAlgoDSAv01 = "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"
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
@@ -228,7 +233,11 @@ func parseCert(in []byte, privAlgo string) (*Certificate, error) {
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 {
@@ -296,16 +305,13 @@ type CertChecker struct {
SupportedCriticalOptions []string
// IsUserAuthority should return true if the key is recognized as an
// authority for the given user certificate. This allows for
// certificates to be signed by other certificates. This must be set
// if this CertChecker will be checking user certificates.
// 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 allows for certificates to be
// signed by other keys, and for those other keys to only be valid
// signers for particular hostnames. This must be set if this
// CertChecker will be checking host certificates.
// 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
@@ -442,12 +448,19 @@ func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
// 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.
// 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 {
@@ -485,16 +498,16 @@ func (c *Certificate) SignCert(rand io.Reader, authority Signer) error {
//
// 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,
CertAlgoDSAv01: KeyAlgoDSA,
CertAlgoECDSA256v01: KeyAlgoECDSA256,
CertAlgoECDSA384v01: KeyAlgoECDSA384,
CertAlgoECDSA521v01: KeyAlgoECDSA521,
CertAlgoSKECDSA256v01: KeyAlgoSKECDSA256,
CertAlgoED25519v01: KeyAlgoED25519,
CertAlgoSKED25519v01: KeyAlgoSKED25519,
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
+33 -2
View File
@@ -297,7 +297,7 @@ func TestCertTypes(t *testing.T) {
{"legacyRSASigner", &legacyRSASigner{testSigners["rsa"]}, KeyAlgoRSA},
{"multiAlgoRSASignerSHA256", multiAlgoSignerSHA256, KeyAlgoRSASHA256},
{"multiAlgoRSASignerSHA512", multiAlgoSignerSHA512, KeyAlgoRSASHA512},
{CertAlgoDSAv01, testSigners["dsa"], ""},
{InsecureCertAlgoDSAv01, testSigners["dsa"], ""},
}
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -338,7 +338,9 @@ func TestCertTypes(t *testing.T) {
CertType: UserCert,
Key: priv.PublicKey(),
}
cert.SignCert(rand.Reader, priv)
if err := cert.SignCert(rand.Reader, priv); err != nil {
t.Fatalf("error signing certificate: %v", err)
}
certSigner, err := NewCertSigner(cert, priv)
if err != nil {
@@ -404,3 +406,32 @@ func TestCertSignWithMultiAlgorithmSigner(t *testing.T) {
})
}
}
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")
}
}
+37 -37
View File
@@ -8,6 +8,7 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/fips140"
"crypto/rc4"
"crypto/subtle"
"encoding/binary"
@@ -15,6 +16,7 @@ import (
"fmt"
"hash"
"io"
"slices"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/internal/poly1305"
@@ -58,11 +60,11 @@ func newRC4(key, iv []byte) (cipher.Stream, error) {
type cipherMode struct {
keySize int
ivSize int
create func(key, iv []byte, macKey []byte, algs directionAlgorithms) (packetCipher, error)
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) {
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
@@ -93,41 +95,41 @@ func streamCipherMode(skip int, createFunc func(key, iv []byte) (cipher.Stream,
}
// cipherModes documents properties of supported ciphers. Ciphers not included
// are not supported and will not be negotiated, even if explicitly requested in
// ClientConfig.Crypto.Ciphers.
var cipherModes = map[string]*cipherMode{
// Ciphers from RFC 4344, which introduced many CTR-based ciphers. Algorithms
// are defined in the order specified in the RFC.
"aes128-ctr": {16, aes.BlockSize, streamCipherMode(0, newAESCTR)},
"aes192-ctr": {24, aes.BlockSize, streamCipherMode(0, newAESCTR)},
"aes256-ctr": {32, aes.BlockSize, streamCipherMode(0, newAESCTR)},
// 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{}
// Ciphers from RFC 4345, which introduces security-improved arcfour ciphers.
// They are defined in the order specified in the RFC.
"arcfour128": {16, 0, streamCipherMode(1536, newRC4)},
"arcfour256": {32, 0, streamCipherMode(1536, newRC4)},
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}
// Cipher defined in RFC 4253, which describes SSH Transport Layer Protocol.
// Note that this cipher is not safe, as stated in RFC 4253: "Arcfour (and
// RC4) has problems with weak keys, and should be used with caution."
// RFC 4345 introduces improved versions of Arcfour.
"arcfour": {16, 0, streamCipherMode(0, newRC4)},
// AEAD ciphers
gcm128CipherID: {16, 12, newGCMCipher},
gcm256CipherID: {32, 12, newGCMCipher},
chacha20Poly1305ID: {64, 0, newChaCha20Cipher},
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.
aes128cbcID: {16, aes.BlockSize, newAESCBCCipher},
// 3des-cbc is insecure and is not included in the default
// config.
tripledescbcID: {24, des.BlockSize, newTripleDESCBCCipher},
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
@@ -307,7 +309,7 @@ type gcmCipher struct {
buf []byte
}
func newGCMCipher(key, iv, unusedMacKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
func newGCMCipher(key, iv, unusedMacKey []byte, unusedAlgs DirectionAlgorithms) (packetCipher, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
@@ -429,7 +431,7 @@ type cbcCipher struct {
oracleCamouflage uint32
}
func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
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),
@@ -443,7 +445,7 @@ func newCBCCipher(c cipher.Block, key, iv, macKey []byte, algs directionAlgorith
return cbc, nil
}
func newAESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
func newAESCBCCipher(key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
@@ -457,7 +459,7 @@ func newAESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCi
return cbc, nil
}
func newTripleDESCBCCipher(key, iv, macKey []byte, algs directionAlgorithms) (packetCipher, error) {
func newTripleDESCBCCipher(key, iv, macKey []byte, algs DirectionAlgorithms) (packetCipher, error) {
c, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
@@ -635,8 +637,6 @@ func (c *cbcCipher) writeCipherPacket(seqNum uint32, w io.Writer, rand io.Reader
return nil
}
const chacha20Poly1305ID = "chacha20-poly1305@openssh.com"
// chacha20Poly1305Cipher implements the chacha20-poly1305@openssh.com
// AEAD, which is described here:
//
@@ -650,7 +650,7 @@ type chacha20Poly1305Cipher struct {
buf []byte
}
func newChaCha20Cipher(key, unusedIV, unusedMACKey []byte, unusedAlgs directionAlgorithms) (packetCipher, error) {
func newChaCha20Cipher(key, unusedIV, unusedMACKey []byte, unusedAlgs DirectionAlgorithms) (packetCipher, error) {
if len(key) != 64 {
panic(len(key))
}
+14 -14
View File
@@ -22,7 +22,7 @@ func TestDefaultCiphersExist(t *testing.T) {
t.Errorf("supported cipher %q is unknown", cipherAlgo)
}
}
for _, cipherAlgo := range preferredCiphers {
for _, cipherAlgo := range defaultCiphers {
if _, ok := cipherModes[cipherAlgo]; !ok {
t.Errorf("preferred cipher %q is unknown", cipherAlgo)
}
@@ -30,8 +30,8 @@ func TestDefaultCiphersExist(t *testing.T) {
}
func TestPacketCiphers(t *testing.T) {
defaultMac := "hmac-sha2-256"
defaultCipher := "aes128-ctr"
defaultMac := HMACSHA256
defaultCipher := CipherAES128CTR
for cipher := range cipherModes {
t.Run("cipher="+cipher,
func(t *testing.T) { testPacketCipher(t, cipher, defaultMac) })
@@ -44,10 +44,10 @@ func TestPacketCiphers(t *testing.T) {
func testPacketCipher(t *testing.T, cipher, mac string) {
kr := &kexResult{Hash: crypto.SHA1}
algs := directionAlgorithms{
algs := DirectionAlgorithms{
Cipher: cipher,
MAC: mac,
Compression: "none",
compression: compressionNone,
}
client, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
@@ -77,10 +77,10 @@ func testPacketCipher(t *testing.T, cipher, mac string) {
func TestCBCOracleCounterMeasure(t *testing.T) {
kr := &kexResult{Hash: crypto.SHA1}
algs := directionAlgorithms{
Cipher: aes128cbcID,
MAC: "hmac-sha1",
Compression: "none",
algs := DirectionAlgorithms{
Cipher: InsecureCipherAES128CBC,
MAC: HMACSHA1,
compression: compressionNone,
}
client, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
@@ -141,7 +141,7 @@ func TestCVE202143565(t *testing.T) {
constructPacket func(packetCipher) io.Reader
}{
{
cipher: gcm128CipherID,
cipher: CipherAES128GCM,
constructPacket: func(client packetCipher) io.Reader {
internalCipher := client.(*gcmCipher)
b := &bytes.Buffer{}
@@ -159,7 +159,7 @@ func TestCVE202143565(t *testing.T) {
},
},
{
cipher: chacha20Poly1305ID,
cipher: CipherChaCha20Poly1305,
constructPacket: func(client packetCipher) io.Reader {
internalCipher := client.(*chacha20Poly1305Cipher)
b := &bytes.Buffer{}
@@ -201,13 +201,13 @@ func TestCVE202143565(t *testing.T) {
}
for _, tc := range tests {
mac := "hmac-sha2-256"
mac := HMACSHA256
kr := &kexResult{Hash: crypto.SHA1}
algs := directionAlgorithms{
algs := DirectionAlgorithms{
Cipher: tc.cipher,
MAC: mac,
Compression: "none",
compression: compressionNone,
}
client, err := newPacketCipher(clientKeys, algs, kr)
if err != nil {
+1
View File
@@ -110,6 +110,7 @@ func (c *connection) clientHandshake(dialAddress string, config *ClientConfig) e
}
c.sessionID = c.transport.getSessionID()
c.algorithms = c.transport.getAlgorithms()
return c.clientAuthenticate(config)
}
+9 -17
View File
@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"slices"
"strings"
)
@@ -83,7 +84,7 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
// success
return nil
} else if ok == authFailure {
if m := auth.method(); !contains(tried, m) {
if m := auth.method(); !slices.Contains(tried, m) {
tried = append(tried, m)
}
}
@@ -97,7 +98,7 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
findNext:
for _, a := range config.Auth {
candidateMethod := a.method()
if contains(tried, candidateMethod) {
if slices.Contains(tried, candidateMethod) {
continue
}
for _, meth := range methods {
@@ -117,15 +118,6 @@ func (c *connection) clientAuthenticate(config *ClientConfig) error {
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", tried)
}
func contains(list []string, e string) bool {
for _, s := range list {
if s == e {
return true
}
}
return false
}
// An AuthMethod represents an instance of an RFC 4252 authentication method.
type AuthMethod interface {
// auth authenticates user over transport t.
@@ -255,7 +247,7 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
// 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 !contains(as.Algorithms(), underlyingAlgo(keyFormat)) {
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())
}
@@ -284,12 +276,12 @@ func pickSignatureAlgorithm(signer Signer, extensions map[string][]byte) (MultiA
// Filter algorithms based on those supported by MultiAlgorithmSigner.
var keyAlgos []string
for _, algo := range algorithmsForKeyFormat(keyFormat) {
if contains(as.Algorithms(), underlyingAlgo(algo)) {
if slices.Contains(as.Algorithms(), underlyingAlgo(algo)) {
keyAlgos = append(keyAlgos, algo)
}
}
algo, err := findCommon("public key signature algorithm", keyAlgos, serverAlgos)
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.
@@ -334,7 +326,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
// 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 contains(as.Algorithms(), KeyAlgoRSA) {
if slices.Contains(as.Algorithms(), KeyAlgoRSA) {
// We retry using the compat algorithm after all signers have
// been tried normally.
signers = append(signers, &multiAlgorithmSigner{
@@ -385,7 +377,7 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand
// 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 || !contains(methods, cb.method()) {
if success == authSuccess || !slices.Contains(methods, cb.method()) {
return success, methods, err
}
}
@@ -434,7 +426,7 @@ func confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
// 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 !contains(algorithmsForKeyFormat(key.Type()), msg.Algo) {
if !slices.Contains(algorithmsForKeyFormat(key.Type()), msg.Algo) {
return false, nil
}
if !bytes.Equal(msg.PubKey, pubKey) {
+7 -3
View File
@@ -14,6 +14,7 @@ import (
"net"
"os"
"runtime"
"slices"
"strings"
"testing"
)
@@ -324,6 +325,9 @@ func TestMethodInvalidAlgorithm(t *testing.T) {
}
func TestClientHMAC(t *testing.T) {
supportedAlgos := SupportedAlgorithms()
insecureAlgos := InsecureAlgorithms()
supportedMACs := append(supportedAlgos.MACs, insecureAlgos.MACs...)
for _, mac := range supportedMACs {
config := &ClientConfig{
User: "testuser",
@@ -349,7 +353,7 @@ func TestClientUnsupportedCipher(t *testing.T) {
PublicKeys(),
},
Config: Config{
Ciphers: []string{"aes128-cbc"}, // not currently supported
Ciphers: []string{"unsupported-cipher"}, // not currently supported
},
}
if err := tryAuth(t, config); err == nil {
@@ -1132,7 +1136,7 @@ func TestPickSignatureAlgorithm(t *testing.T) {
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 cerificate
// 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 {
@@ -1211,7 +1215,7 @@ func (cb configurablePublicKeyCallback) auth(session []byte, user string, c pack
if err != nil {
return authFailure, nil, err
}
if success == authSuccess || !contains(methods, cb.method()) {
if success == authSuccess || !slices.Contains(methods, cb.method()) {
return success, methods, err
}
+5 -5
View File
@@ -292,7 +292,7 @@ func TestUnsupportedAlgorithm(t *testing.T) {
{
"unsupported and supported KEXs",
Config{
KeyExchanges: []string{"unsupported", kexAlgoCurve25519SHA256},
KeyExchanges: []string{"unsupported", KeyExchangeCurve25519},
},
"",
},
@@ -306,7 +306,7 @@ func TestUnsupportedAlgorithm(t *testing.T) {
{
"unsupported and supported ciphers",
Config{
Ciphers: []string{"unsupported", chacha20Poly1305ID},
Ciphers: []string{"unsupported", CipherChaCha20Poly1305},
},
"",
},
@@ -315,16 +315,16 @@ func TestUnsupportedAlgorithm(t *testing.T) {
Config{
MACs: []string{"unsupported"},
// MAC is used for non AAED ciphers.
Ciphers: []string{"aes256-ctr"},
Ciphers: []string{CipherAES256CTR},
},
"no common algorithm",
},
{
"unsupported and supported MACs",
Config{
MACs: []string{"unsupported", "hmac-sha2-256-etm@openssh.com"},
MACs: []string{"unsupported", HMACSHA256ETM},
// MAC is used for non AAED ciphers.
Ciphers: []string{"aes256-ctr"},
Ciphers: []string{CipherAES256CTR},
},
"",
},
+363 -112
View File
@@ -6,10 +6,12 @@ package ssh
import (
"crypto"
"crypto/fips140"
"crypto/rand"
"fmt"
"io"
"math"
"slices"
"sync"
_ "crypto/sha1"
@@ -24,88 +26,298 @@ const (
serviceSSH = "ssh-connection"
)
// supportedCiphers lists ciphers we support but might not recommend.
var supportedCiphers = []string{
"aes128-ctr", "aes192-ctr", "aes256-ctr",
"aes128-gcm@openssh.com", gcm256CipherID,
chacha20Poly1305ID,
"arcfour256", "arcfour128", "arcfour",
aes128cbcID,
tripledescbcID,
// 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
}
// preferredCiphers specifies the default preference for ciphers.
var preferredCiphers = []string{
"aes128-gcm@openssh.com", gcm256CipherID,
chacha20Poly1305ID,
"aes128-ctr", "aes192-ctr", "aes256-ctr",
// 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
}
// supportedKexAlgos specifies the supported key-exchange algorithms in
// preference order.
var supportedKexAlgos = []string{
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
// P384 and P521 are not constant-time yet, but since we don't
// reuse ephemeral keys, using them for ECDH should be OK.
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
kexAlgoDH14SHA256, kexAlgoDH16SHA512, kexAlgoDH14SHA1,
kexAlgoDH1SHA1,
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
})
}
}
// serverForbiddenKexAlgos contains key exchange algorithms, that are forbidden
// for the server half.
var serverForbiddenKexAlgos = map[string]struct{}{
kexAlgoDHGEXSHA1: {}, // server half implementation is only minimal to satisfy the automated tests
kexAlgoDHGEXSHA256: {}, // server half implementation is only minimal to satisfy the automated tests
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)
}
}
// preferredKexAlgos specifies the default preference for key-exchange
// algorithms in preference order. The diffie-hellman-group16-sha512 algorithm
// is disabled by default because it is a bit slower than the others.
var preferredKexAlgos = []string{
kexAlgoCurve25519SHA256, kexAlgoCurve25519SHA256LibSSH,
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
kexAlgoDH14SHA256, kexAlgoDH14SHA1,
// 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),
}
}
// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods
// of authenticating servers) in preference order.
var supportedHostKeyAlgos = []string{
CertAlgoRSASHA256v01, CertAlgoRSASHA512v01,
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01,
CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01,
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
KeyAlgoRSASHA256, KeyAlgoRSASHA512,
KeyAlgoRSA, KeyAlgoDSA,
KeyAlgoED25519,
}
// supportedMACs specifies a default set of MAC algorithms in preference order.
// This is based on RFC 4253, section 6.4, but with hmac-md5 variants removed
// because they have reached the end of their useful life.
var supportedMACs = []string{
"hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-sha1-96",
// 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}
// hashFuncs keeps the mapping of supported signature algorithms to their
// respective hashes needed for signing and verification.
var hashFuncs = map[string]crypto.Hash{
KeyAlgoRSA: crypto.SHA1,
KeyAlgoRSASHA256: crypto.SHA256,
KeyAlgoRSASHA512: crypto.SHA512,
KeyAlgoDSA: crypto.SHA1,
KeyAlgoECDSA256: crypto.SHA256,
KeyAlgoECDSA384: crypto.SHA384,
KeyAlgoECDSA521: crypto.SHA512,
// KeyAlgoED25519 doesn't pre-hash.
KeyAlgoSKECDSA256: crypto.SHA256,
KeyAlgoSKED25519: crypto.SHA256,
}
// 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.
@@ -120,11 +332,40 @@ func algorithmsForKeyFormat(keyFormat string) []string {
}
}
// 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 contains(algos, underlyingAlgo(algo))
return slices.Contains(algos, underlyingAlgo(algo))
}
func isRSACert(algo string) bool {
@@ -135,18 +376,6 @@ func isRSACert(algo string) bool {
return isRSA(algo)
}
// supportedPubKeyAuthAlgos specifies the supported client public key
// authentication algorithms. Note that this doesn't 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.
var supportedPubKeyAuthAlgos = []string{
KeyAlgoED25519,
KeyAlgoSKED25519, KeyAlgoSKECDSA256,
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
KeyAlgoRSASHA256, KeyAlgoRSASHA512, KeyAlgoRSA,
KeyAlgoDSA,
}
// unexpectedMessageError results when the SSH message that we received didn't
// match what we wanted.
func unexpectedMessageError(expected, got uint8) error {
@@ -158,7 +387,7 @@ func parseError(tag uint8) error {
return fmt.Errorf("ssh: parse error in message type %d", tag)
}
func findCommon(what string, client []string, server []string) (common string, err error) {
func findCommon(what string, client []string, server []string, isClient bool) (string, error) {
for _, c := range client {
for _, s := range server {
if c == s {
@@ -166,23 +395,49 @@ func findCommon(what string, client []string, server []string) (common string, e
}
}
}
return "", fmt.Errorf("ssh: no common algorithm for %s; client offered: %v, server offered: %v", what, client, server)
err := &AlgorithmNegotiationError{
What: what,
}
if isClient {
err.SupportedAlgorithms = client
err.RequestedAlgorithms = server
} else {
err.SupportedAlgorithms = server
err.RequestedAlgorithms = client
}
return "", err
}
// directionAlgorithms records algorithm choices in one direction (either read or write)
type directionAlgorithms struct {
// 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
compression string
}
// rekeyBytes returns a rekeying intervals in bytes.
func (a *directionAlgorithms) rekeyBytes() int64 {
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 "aes128-ctr", "aes192-ctr", "aes256-ctr", gcm128CipherID, gcm256CipherID, aes128cbcID:
case CipherAES128CTR, CipherAES192CTR, CipherAES256CTR, CipherAES128GCM, CipherAES256GCM, InsecureCipherAES128CBC:
return 16 * (1 << 32)
}
@@ -192,66 +447,59 @@ func (a *directionAlgorithms) rekeyBytes() int64 {
}
var aeadCiphers = map[string]bool{
gcm128CipherID: true,
gcm256CipherID: true,
chacha20Poly1305ID: true,
CipherAES128GCM: true,
CipherAES256GCM: true,
CipherChaCha20Poly1305: true,
}
type algorithms struct {
kex string
hostKey string
w directionAlgorithms
r directionAlgorithms
}
func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMsg) (algs *NegotiatedAlgorithms, err error) {
result := &NegotiatedAlgorithms{}
func findAgreedAlgorithms(isClient bool, clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms, err error) {
result := &algorithms{}
result.kex, err = findCommon("key exchange", clientKexInit.KexAlgos, serverKexInit.KexAlgos)
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)
result.HostKey, err = findCommon("host key", clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos, isClient)
if err != nil {
return
}
stoc, ctos := &result.w, &result.r
stoc, ctos := &result.Write, &result.Read
if isClient {
ctos, stoc = stoc, ctos
}
ctos.Cipher, err = findCommon("client to server cipher", clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
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)
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)
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)
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)
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)
stoc.compression, err = findCommon("server to client compression", clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient, isClient)
if err != nil {
return
}
@@ -297,7 +545,7 @@ func (c *Config) SetDefaults() {
c.Rand = rand.Reader
}
if c.Ciphers == nil {
c.Ciphers = preferredCiphers
c.Ciphers = defaultCiphers
}
var ciphers []string
for _, c := range c.Ciphers {
@@ -309,19 +557,22 @@ func (c *Config) SetDefaults() {
c.Ciphers = ciphers
if c.KeyExchanges == nil {
c.KeyExchanges = preferredKexAlgos
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 = supportedMACs
c.MACs = defaultMACs
}
var macs []string
for _, m := range c.MACs {
+37 -17
View File
@@ -5,7 +5,9 @@
package ssh
import (
"maps"
"reflect"
"slices"
"testing"
)
@@ -51,33 +53,33 @@ func TestFindAgreedAlgorithms(t *testing.T) {
}
}
initDirAlgs := func(a *directionAlgorithms) {
initDirAlgs := func(a *DirectionAlgorithms) {
if a.Cipher == "" {
a.Cipher = "cipher1"
}
if a.MAC == "" {
a.MAC = "mac1"
}
if a.Compression == "" {
a.Compression = "compression1"
if a.compression == "" {
a.compression = "compression1"
}
}
initAlgs := func(a *algorithms) {
if a.kex == "" {
a.kex = "kex1"
initAlgs := func(a *NegotiatedAlgorithms) {
if a.KeyExchange == "" {
a.KeyExchange = "kex1"
}
if a.hostKey == "" {
a.hostKey = "hostkey1"
if a.HostKey == "" {
a.HostKey = "hostkey1"
}
initDirAlgs(&a.r)
initDirAlgs(&a.w)
initDirAlgs(&a.Read)
initDirAlgs(&a.Write)
}
type testcase struct {
name string
clientIn, serverIn kexInitMsg
wantClient, wantServer algorithms
wantClient, wantServer NegotiatedAlgorithms
wantErr bool
}
@@ -120,19 +122,19 @@ func TestFindAgreedAlgorithms(t *testing.T) {
CiphersClientServer: []string{"cipher2", "cipher1"},
CiphersServerClient: []string{"cipher3", "cipher2"},
},
wantClient: algorithms{
r: directionAlgorithms{
wantClient: NegotiatedAlgorithms{
Read: DirectionAlgorithms{
Cipher: "cipher3",
},
w: directionAlgorithms{
Write: DirectionAlgorithms{
Cipher: "cipher2",
},
},
wantServer: algorithms{
w: directionAlgorithms{
wantServer: NegotiatedAlgorithms{
Write: DirectionAlgorithms{
Cipher: "cipher3",
},
r: directionAlgorithms{
Read: DirectionAlgorithms{
Cipher: "cipher2",
},
},
@@ -174,3 +176,21 @@ func TestFindAgreedAlgorithms(t *testing.T) {
})
}
}
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)
}
}
}
+12
View File
@@ -74,6 +74,13 @@ type Conn interface {
// 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) {
@@ -106,6 +113,7 @@ type sshConn struct {
sessionID []byte
clientVersion []byte
serverVersion []byte
algorithms NegotiatedAlgorithms
}
func dup(src []byte) []byte {
@@ -141,3 +149,7 @@ func (c *sshConn) ClientVersion() []byte {
func (c *sshConn) ServerVersion() []byte {
return dup(c.serverVersion)
}
func (c *sshConn) Algorithms() NegotiatedAlgorithms {
return c.algorithms
}
+11
View File
@@ -16,8 +16,19 @@ 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
+110
View File
@@ -154,6 +154,75 @@ func ExampleNewServerConn() {
}
}
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{
@@ -262,6 +331,47 @@ func ExampleDial() {
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
+19 -14
View File
@@ -10,6 +10,7 @@ import (
"io"
"log"
"net"
"slices"
"strings"
"sync"
)
@@ -38,7 +39,7 @@ type keyingTransport interface {
// prepareKeyChange sets up a key change. The key change for a
// direction will be effected if a msgNewKeys message is sent
// or received.
prepareKeyChange(*algorithms, *kexResult) error
prepareKeyChange(*NegotiatedAlgorithms, *kexResult) error
// setStrictMode sets the strict KEX mode, notably triggering
// sequence number resets on sending or receiving msgNewKeys.
@@ -115,7 +116,7 @@ type handshakeTransport struct {
bannerCallback BannerCallback
// Algorithms agreed in the last key exchange.
algorithms *algorithms
algorithms *NegotiatedAlgorithms
// Counters exclusively owned by readLoop.
readPacketsLeft uint32
@@ -164,7 +165,7 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
if config.HostKeyAlgorithms != nil {
t.hostKeyAlgorithms = config.HostKeyAlgorithms
} else {
t.hostKeyAlgorithms = supportedHostKeyAlgos
t.hostKeyAlgorithms = defaultHostKeyAlgos
}
go t.readLoop()
go t.kexLoop()
@@ -184,6 +185,10 @@ 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 {
@@ -290,7 +295,7 @@ func (t *handshakeTransport) resetWriteThresholds() {
if t.config.RekeyThreshold > 0 {
t.writeBytesLeft = int64(t.config.RekeyThreshold)
} else if t.algorithms != nil {
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
t.writeBytesLeft = t.algorithms.Write.rekeyBytes()
} else {
t.writeBytesLeft = 1 << 30
}
@@ -407,7 +412,7 @@ func (t *handshakeTransport) resetReadThresholds() {
if t.config.RekeyThreshold > 0 {
t.readBytesLeft = int64(t.config.RekeyThreshold)
} else if t.algorithms != nil {
t.readBytesLeft = t.algorithms.r.rekeyBytes()
t.readBytesLeft = t.algorithms.Read.rekeyBytes()
} else {
t.readBytesLeft = 1 << 30
}
@@ -523,7 +528,7 @@ func (t *handshakeTransport) sendKexInit() error {
switch s := k.(type) {
case MultiAlgorithmSigner:
for _, algo := range algorithmsForKeyFormat(keyFormat) {
if contains(s.Algorithms(), underlyingAlgo(algo)) {
if slices.Contains(s.Algorithms(), underlyingAlgo(algo)) {
msg.ServerHostKeyAlgos = append(msg.ServerHostKeyAlgos, algo)
}
}
@@ -675,7 +680,7 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
return err
}
if t.sessionID == nil && ((isClient && contains(serverInit.KexAlgos, kexStrictServer)) || (!isClient && contains(clientInit.KexAlgos, kexStrictClient))) {
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
@@ -700,9 +705,9 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
}
}
kex, ok := kexAlgoMap[t.algorithms.kex]
kex, ok := kexAlgoMap[t.algorithms.KeyExchange]
if !ok {
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.kex)
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", t.algorithms.KeyExchange)
}
var result *kexResult
@@ -732,7 +737,7 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {
// 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 && contains(clientInit.KexAlgos, "ext-info-c") {
if !isClient && firstKeyExchange && slices.Contains(clientInit.KexAlgos, "ext-info-c") {
supportedPubKeyAuthAlgosList := strings.Join(t.publicKeyAuthAlgorithms, ",")
extInfo := &extInfoMsg{
NumExtensions: 2,
@@ -786,7 +791,7 @@ func (a algorithmSignerWrapper) SignWithAlgorithm(rand io.Reader, data []byte, a
func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner {
for _, k := range hostKeys {
if s, ok := k.(MultiAlgorithmSigner); ok {
if !contains(s.Algorithms(), underlyingAlgo(algo)) {
if !slices.Contains(s.Algorithms(), underlyingAlgo(algo)) {
continue
}
}
@@ -809,12 +814,12 @@ func pickHostKey(hostKeys []Signer, algo string) AlgorithmSigner {
}
func (t *handshakeTransport) server(kex kexAlgorithm, magics *handshakeMagics) (*kexResult, error) {
hostKey := pickHostKey(t.hostKeys, t.algorithms.hostKey)
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)
r, err := kex.Server(t.conn, t.config.Rand, magics, hostKey, t.algorithms.HostKey)
return r, err
}
@@ -829,7 +834,7 @@ func (t *handshakeTransport) client(kex kexAlgorithm, magics *handshakeMagics) (
return nil, err
}
if err := verifyHostKeySignature(hostKey, t.algorithms.hostKey, result); err != nil {
if err := verifyHostKeySignature(hostKey, t.algorithms.HostKey, result); err != nil {
return nil, err
}
+129 -4
View File
@@ -367,7 +367,7 @@ type errorKeyingTransport struct {
readLeft, writeLeft int
}
func (n *errorKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error {
func (n *errorKeyingTransport) prepareKeyChange(*NegotiatedAlgorithms, *kexResult) error {
return nil
}
@@ -545,7 +545,7 @@ type mockKeyingTransport struct {
kexInitSent chan struct{}
}
func (n *mockKeyingTransport) prepareKeyChange(*algorithms, *kexResult) error {
func (n *mockKeyingTransport) prepareKeyChange(*NegotiatedAlgorithms, *kexResult) error {
return nil
}
@@ -762,7 +762,7 @@ func TestHandshakePendingPacketsError(t *testing.T) {
func TestHandshakeRekeyDefault(t *testing.T) {
clientConf := &ClientConfig{
Config: Config{
Ciphers: []string{"aes128-ctr"},
Ciphers: []string{CipherAES128CTR},
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
@@ -788,7 +788,7 @@ func TestHandshakeRekeyDefault(t *testing.T) {
}
func TestHandshakeAEADCipherNoMAC(t *testing.T) {
for _, cipher := range []string{chacha20Poly1305ID, gcm128CipherID} {
for _, cipher := range []string{CipherChaCha20Poly1305, CipherAES128GCM} {
checker := &syncChecker{
called: make(chan int, 1),
}
@@ -1239,3 +1239,128 @@ func TestStrictKEXMixed(t *testing.T) {
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)
}
}
+88 -67
View File
@@ -8,33 +8,31 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/fips140"
"crypto/rand"
"crypto/subtle"
"encoding/binary"
"errors"
"fmt"
"io"
"math/big"
"slices"
"golang.org/x/crypto/curve25519"
)
const (
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
kexAlgoDH14SHA256 = "diffie-hellman-group14-sha256"
kexAlgoDH16SHA512 = "diffie-hellman-group16-sha512"
kexAlgoECDH256 = "ecdh-sha2-nistp256"
kexAlgoECDH384 = "ecdh-sha2-nistp384"
kexAlgoECDH521 = "ecdh-sha2-nistp521"
kexAlgoCurve25519SHA256LibSSH = "curve25519-sha256@libssh.org"
kexAlgoCurve25519SHA256 = "curve25519-sha256"
// For the following kex only the client half contains a production
// ready implementation. The server half only consists of a minimal
// implementation to satisfy the automated tests.
kexAlgoDHGEXSHA1 = "diffie-hellman-group-exchange-sha1"
kexAlgoDHGEXSHA256 = "diffie-hellman-group-exchange-sha256"
// 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.
@@ -399,56 +397,64 @@ func ecHash(curve elliptic.Curve) crypto.Hash {
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() {
// This is the group called diffie-hellman-group1-sha1 in
// RFC 4253 and Oakley Group 2 in RFC 2409.
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16)
kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{
// 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,
}
// This are the groups called diffie-hellman-group14-sha1 and
// diffie-hellman-group14-sha256 in RFC 4253 and RFC 8268,
// and Oakley Group 14 in RFC 3526.
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
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[kexAlgoDH14SHA1] = &dhGroup{
kexAlgoMap[InsecureKeyExchangeDH14SHA1] = &dhGroup{
g: group14.g, p: group14.p, pMinus1: group14.pMinus1,
hashFunc: crypto.SHA1,
}
kexAlgoMap[kexAlgoDH14SHA256] = &dhGroup{
kexAlgoMap[KeyExchangeDH14SHA256] = &dhGroup{
g: group14.g, p: group14.p, pMinus1: group14.pMinus1,
hashFunc: crypto.SHA256,
}
// This is the group called diffie-hellman-group16-sha512 in RFC
// 8268 and Oakley Group 16 in RFC 3526.
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF", 16)
p, _ = new(big.Int).SetString(oakleyGroup16, 16)
kexAlgoMap[kexAlgoDH16SHA512] = &dhGroup{
kexAlgoMap[KeyExchangeDH16SHA512] = &dhGroup{
g: new(big.Int).SetInt64(2),
p: p,
pMinus1: new(big.Int).Sub(p, bigOne),
hashFunc: crypto.SHA512,
}
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
kexAlgoMap[kexAlgoCurve25519SHA256] = &curve25519sha256{}
kexAlgoMap[kexAlgoCurve25519SHA256LibSSH] = &curve25519sha256{}
kexAlgoMap[kexAlgoDHGEXSHA1] = &dhGEXSHA{hashFunc: crypto.SHA1}
kexAlgoMap[kexAlgoDHGEXSHA256] = &dhGEXSHA{hashFunc: crypto.SHA256}
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
@@ -464,15 +470,17 @@ func (kp *curve25519KeyPair) generate(rand io.Reader) error {
if _, err := io.ReadFull(rand, kp.priv[:]); err != nil {
return err
}
curve25519.ScalarBaseMult(&kp.pub, &kp.priv)
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
}
// curve25519Zeros is just an array of 32 zero bytes so that we have something
// convenient to compare against in order to reject curve25519 points with the
// wrong order.
var curve25519Zeros [32]byte
func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
var kp curve25519KeyPair
if err := kp.generate(rand); err != nil {
@@ -495,11 +503,9 @@ func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handsh
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
}
var servPub, secret [32]byte
copy(servPub[:], reply.EphemeralPubKey)
curve25519.ScalarMult(&secret, &kp.priv, &servPub)
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
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()
@@ -541,11 +547,9 @@ func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handsh
return nil, err
}
var clientPub, secret [32]byte
copy(clientPub[:], kexInit.ClientPubKey)
curve25519.ScalarMult(&secret, &kp.priv, &clientPub)
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
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()
@@ -601,9 +605,9 @@ const (
func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
// Send GexRequest
kexDHGexRequest := kexDHGexRequestMsg{
MinBits: dhGroupExchangeMinimumBits,
PreferedBits: dhGroupExchangePreferredBits,
MaxBits: dhGroupExchangeMaximumBits,
MinBits: dhGroupExchangeMinimumBits,
PreferredBits: dhGroupExchangePreferredBits,
MaxBits: dhGroupExchangeMaximumBits,
}
if err := c.writePacket(Marshal(&kexDHGexRequest)); err != nil {
return nil, err
@@ -690,9 +694,7 @@ func (gex *dhGEXSHA) Client(c packetConn, randSource io.Reader, magics *handshak
}
// Server half implementation of the Diffie Hellman Key Exchange with SHA1 and SHA256.
//
// This is a minimal implementation to satisfy the automated tests.
func (gex dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv AlgorithmSigner, algo string) (result *kexResult, err error) {
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 {
@@ -702,13 +704,32 @@ func (gex dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshake
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)
}
// Send GexGroup
// This is the group called diffie-hellman-group14-sha1 in RFC
// 4253 and Oakley Group 14 in RFC 3526.
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
g := big.NewInt(2)
msg := &kexDHGexGroupMsg{
P: p,
G: g,
@@ -746,9 +767,9 @@ func (gex dhGEXSHA) Server(c packetConn, randSource io.Reader, magics *handshake
h := gex.hashFunc.New()
magics.write(h)
writeString(h, hostKeyBytes)
binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMinimumBits))
binary.Write(h, binary.BigEndian, uint32(dhGroupExchangePreferredBits))
binary.Write(h, binary.BigEndian, uint32(dhGroupExchangeMaximumBits))
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)
+71 -26
View File
@@ -27,6 +27,7 @@ import (
"fmt"
"io"
"math/big"
"slices"
"strings"
"golang.org/x/crypto/ssh/internal/bcrypt_pbkdf"
@@ -36,14 +37,19 @@ import (
// ClientConfig.HostKeyAlgorithms, Signature.Format, or as AlgorithmSigner
// arguments.
const (
KeyAlgoRSA = "ssh-rsa"
KeyAlgoDSA = "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"
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
@@ -67,7 +73,7 @@ func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err err
switch algo {
case KeyAlgoRSA:
return parseRSA(in)
case KeyAlgoDSA:
case InsecureKeyAlgoDSA:
return parseDSA(in)
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
return parseECDSA(in)
@@ -77,13 +83,18 @@ func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err err
return parseED25519(in)
case KeyAlgoSKED25519:
return parseSKEd25519(in)
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01:
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)
}
@@ -186,9 +197,10 @@ func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey
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.
// 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 {
@@ -217,6 +229,8 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str
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
@@ -259,16 +273,22 @@ func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []str
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 formatted for use in
// 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)
@@ -390,11 +410,11 @@ func NewSignerWithAlgorithms(signer AlgorithmSigner, algorithms []string) (Multi
}
for _, algo := range algorithms {
if !contains(supportedAlgos, algo) {
if !slices.Contains(supportedAlgos, algo) {
return nil, fmt.Errorf("ssh: algorithm %q is not supported for key type %q",
algo, signer.PublicKey().Type())
}
if !contains(signerAlgos, algo) {
if !slices.Contains(signerAlgos, algo) {
return nil, fmt.Errorf("ssh: algorithm %q is restricted for the provided signer", algo)
}
}
@@ -481,10 +501,13 @@ func (r *rsaPublicKey) Marshal() []byte {
func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
supportedAlgos := algorithmsForKeyFormat(r.Type())
if !contains(supportedAlgos, sig.Format) {
if !slices.Contains(supportedAlgos, sig.Format) {
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
}
hash := hashFuncs[sig.Format]
hash, err := hashFunc(sig.Format)
if err != nil {
return err
}
h := hash.New()
h.Write(data)
digest := h.Sum(nil)
@@ -601,7 +624,11 @@ 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())
}
h := hashFuncs[sig.Format].New()
hash, err := hashFunc(sig.Format)
if err != nil {
return err
}
h := hash.New()
h.Write(data)
digest := h.Sum(nil)
@@ -646,7 +673,11 @@ func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
}
h := hashFuncs[k.PublicKey().Type()].New()
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)
@@ -796,8 +827,11 @@ 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())
}
h := hashFuncs[sig.Format].New()
hash, err := hashFunc(sig.Format)
if err != nil {
return err
}
h := hash.New()
h.Write(data)
digest := h.Sum(nil)
@@ -900,8 +934,11 @@ 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())
}
h := hashFuncs[sig.Format].New()
hash, err := hashFunc(sig.Format)
if err != nil {
return err
}
h := hash.New()
h.Write([]byte(k.application))
appDigest := h.Sum(nil)
@@ -1004,7 +1041,11 @@ func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
return fmt.Errorf("invalid size %d for Ed25519 public key", l)
}
h := hashFuncs[sig.Format].New()
hash, err := hashFunc(sig.Format)
if err != nil {
return err
}
h := hash.New()
h.Write([]byte(k.application))
appDigest := h.Sum(nil)
@@ -1107,11 +1148,14 @@ func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm
algorithm = s.pubKey.Type()
}
if !contains(s.Algorithms(), algorithm) {
if !slices.Contains(s.Algorithms(), algorithm) {
return nil, fmt.Errorf("ssh: unsupported signature algorithm %q for key format %q", algorithm, s.pubKey.Type())
}
hashFunc := hashFuncs[algorithm]
hashFunc, err := hashFunc(algorithm)
if err != nil {
return nil, err
}
var digest []byte
if hashFunc != 0 {
h := hashFunc.New()
@@ -1446,6 +1490,7 @@ type openSSHEncryptedPrivateKey struct {
NumKeys uint32
PubKey []byte
PrivKeyBlock []byte
Rest []byte `ssh:"rest"`
}
type openSSHPrivateKey struct {
+53
View File
@@ -59,6 +59,17 @@ func TestKeyMarshalParse(t *testing.T) {
}
}
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 {
@@ -260,6 +271,21 @@ func TestParseEncryptedPrivateKeysWithPassphrase(t *testing.T) {
}
}
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++ {
@@ -810,3 +836,30 @@ func TestCryptoPublicKey(t *testing.T) {
}
}
}
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")
}
}
+14 -8
View File
@@ -421,20 +421,26 @@ func New(files ...string) (ssh.HostKeyCallback, error) {
return certChecker.CheckHostKey, nil
}
// Normalize normalizes an address into the form used in known_hosts
// 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 = "22"
port = defaultSSHPort
}
entry := host
if port != "22" {
entry = "[" + entry + "]:" + port
} else if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
entry = "[" + entry + "]"
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
host = host[1 : len(host)-1]
}
return entry
if port == defaultSSHPort {
return host
}
return "[" + host + "]:" + port
}
// Line returns a line to add append to the known_hosts files.
+20 -9
View File
@@ -236,7 +236,7 @@ func TestLine(t *testing.T) {
"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]: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 {
@@ -310,14 +310,25 @@ func testHostHash(t *testing.T, hostname, encoded string) {
func TestNormalize(t *testing.T) {
for in, want := range map[string]string{
"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",
"[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",
"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 {
+29 -13
View File
@@ -7,11 +7,13 @@ package ssh
// Message authentication support
import (
"crypto/fips140"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"hash"
"slices"
)
type macMode struct {
@@ -46,23 +48,37 @@ func (t truncatingMAC) Size() int {
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
var macModes = map[string]*macMode{
"hmac-sha2-512-etm@openssh.com": {64, true, func(key []byte) hash.Hash {
// 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)
}},
"hmac-sha2-256-etm@openssh.com": {32, true, func(key []byte) hash.Hash {
}}
macModes[HMACSHA256ETM] = &macMode{32, true, func(key []byte) hash.Hash {
return hmac.New(sha256.New, key)
}},
"hmac-sha2-512": {64, false, func(key []byte) hash.Hash {
}}
macModes[HMACSHA512] = &macMode{64, false, func(key []byte) hash.Hash {
return hmac.New(sha512.New, key)
}},
"hmac-sha2-256": {32, false, func(key []byte) hash.Hash {
}}
macModes[HMACSHA256] = &macMode{32, false, func(key []byte) hash.Hash {
return hmac.New(sha256.New, key)
}},
"hmac-sha1": {20, false, func(key []byte) hash.Hash {
}}
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)
}},
"hmac-sha1-96": {20, false, func(key []byte) hash.Hash {
}}
macModes[InsecureHMACSHA196] = &macMode{20, false, func(key []byte) hash.Hash {
return truncatingMAC{12, hmac.New(sha1.New, key)}
}},
}}
}
+4 -4
View File
@@ -122,9 +122,9 @@ type kexDHGexReplyMsg struct {
const msgKexDHGexRequest = 34
type kexDHGexRequestMsg struct {
MinBits uint32 `sshtype:"34"`
PreferedBits uint32
MaxBits uint32
MinBits uint32 `sshtype:"34"`
PreferredBits uint32
MaxBits uint32
}
// See RFC 4253, section 10.
@@ -792,7 +792,7 @@ func marshalString(to []byte, s []byte) []byte {
return to[len(s):]
}
var bigIntType = reflect.TypeOf((*big.Int)(nil))
var bigIntType = reflect.TypeFor[*big.Int]()
// Decode a packet into its corresponding message.
func decode(packet []byte) (interface{}, error) {
+1 -1
View File
@@ -257,7 +257,7 @@ func TestDecode(t *testing.T) {
userAuthSuccess, err := decode([]byte{msgUserAuthSuccess})
if err != nil {
t.Errorf("error decoding userAuthSuccessMsg")
} else if reflect.TypeOf(userAuthSuccess) != reflect.TypeOf((*userAuthSuccessMsg)(nil)) {
} else if _, ok := userAuthSuccess.(*userAuthSuccessMsg); !ok {
t.Errorf("error decoding userAuthSuccessMsg, unexpected %T", userAuthSuccess)
}
}
-19
View File
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.24
package ssh
import (
@@ -13,27 +11,10 @@ import (
"errors"
"fmt"
"io"
"runtime"
"slices"
"golang.org/x/crypto/curve25519"
)
const (
kexAlgoMLKEM768xCurve25519SHA256 = "mlkem768x25519-sha256"
)
func init() {
// After Go 1.24rc1 mlkem swapped the order of return values of Encapsulate.
// See #70950.
if runtime.Version() == "go1.24rc1" {
return
}
supportedKexAlgos = slices.Insert(supportedKexAlgos, 0, kexAlgoMLKEM768xCurve25519SHA256)
preferredKexAlgos = slices.Insert(preferredKexAlgos, 0, kexAlgoMLKEM768xCurve25519SHA256)
kexAlgoMap[kexAlgoMLKEM768xCurve25519SHA256] = &mlkem768WithCurve25519sha256{}
}
// 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.
+34 -12
View File
@@ -10,6 +10,7 @@ import (
"fmt"
"io"
"net"
"slices"
"strings"
)
@@ -43,6 +44,9 @@ type Permissions struct {
// pass data from the authentication callbacks to the server
// application layer.
Extensions map[string]string
// ExtraData allows to store user defined data.
ExtraData map[any]any
}
type GSSAPIWithMICConfig struct {
@@ -126,6 +130,21 @@ type ServerConfig struct {
// 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
@@ -243,22 +262,15 @@ func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewCha
fullConf.MaxAuthTries = 6
}
if len(fullConf.PublicKeyAuthAlgorithms) == 0 {
fullConf.PublicKeyAuthAlgorithms = supportedPubKeyAuthAlgos
fullConf.PublicKeyAuthAlgorithms = defaultPubKeyAuthAlgos
} else {
for _, algo := range fullConf.PublicKeyAuthAlgorithms {
if !contains(supportedPubKeyAuthAlgos, algo) {
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)
}
}
}
// Check if the config contains any unsupported key exchanges
for _, kex := range fullConf.KeyExchanges {
if _, ok := serverForbiddenKexAlgos[kex]; ok {
c.Close()
return nil, nil, nil, fmt.Errorf("ssh: unsupported key exchange %s for server", kex)
}
}
s := &connection{
sshConn: sshConn{conn: c},
@@ -315,6 +327,7 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
// 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 {
@@ -637,7 +650,7 @@ userAuthLoop:
return nil, parseError(msgUserAuthRequest)
}
algo := string(algoBytes)
if !contains(config.PublicKeyAuthAlgorithms, underlyingAlgo(algo)) {
if !slices.Contains(config.PublicKeyAuthAlgorithms, underlyingAlgo(algo)) {
authErr = fmt.Errorf("ssh: algorithm %q not accepted", algo)
break
}
@@ -658,6 +671,9 @@ userAuthLoop:
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 &&
@@ -701,7 +717,7 @@ userAuthLoop:
// 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 !contains(algorithmsForKeyFormat(pubKey.Type()), algo) {
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
@@ -711,7 +727,7 @@ userAuthLoop:
// algorithm name that corresponds to algo with
// sig.Format. This is usually the same, but
// for certs, the names differ.
if !contains(config.PublicKeyAuthAlgorithms, sig.Format) {
if !slices.Contains(config.PublicKeyAuthAlgorithms, sig.Format) {
authErr = fmt.Errorf("ssh: algorithm %q not accepted", sig.Format)
break
}
@@ -728,6 +744,12 @@ userAuthLoop:
authErr = candidate.result
perms = candidate.perms
if authErr == nil && config.VerifiedPublicKeyCallback != nil {
// Only call VerifiedPublicKeyCallback after the key has been accepted
// and successfully verified. If authErr is non-nil, the key is not
// considered verified and the callback must not run.
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, algo)
}
}
case "gssapi-with-mic":
if authConfig.GSSAPIWithMICConfig == nil {
+375 -1
View File
@@ -212,7 +212,7 @@ func TestNewServerConnValidationErrors(t *testing.T) {
serverConf = &ServerConfig{
Config: Config{
KeyExchanges: []string{kexAlgoDHGEXSHA256},
KeyExchanges: []string{KeyExchangeDHGEXSHA256},
},
}
c = &markerConn{}
@@ -434,6 +434,380 @@ func TestPreAuthConnAndBanners(t *testing.T) {
}
}
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 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
}
type markerConn struct {
closed uint32
used uint32
+7 -1
View File
@@ -106,6 +106,13 @@ func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) {
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),
@@ -122,7 +129,6 @@ func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) {
if rest, err = asn1.Unmarshal(desiredMech, &s.OIDS[i]); err != nil {
return nil, err
}
}
return s, nil
}
+31
View File
@@ -17,6 +17,37 @@ func TestParseGSSAPIPayload(t *testing.T) {
}
}
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}
+2 -2
View File
@@ -44,7 +44,7 @@ func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
if !ok {
return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer")
}
ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"})
ch := c.forwards.add("unix", socketPath)
return &unixListener{socketPath, c, ch}, nil
}
@@ -96,7 +96,7 @@ func (l *unixListener) Accept() (net.Conn, error) {
// Close closes the listener.
func (l *unixListener) Close() error {
// this also closes the listener.
l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"})
l.conn.forwards.remove("unix", l.socketPath)
m := streamLocalChannelForwardMsg{
l.socketPath,
}
+80 -44
View File
@@ -11,6 +11,7 @@ import (
"io"
"math/rand"
"net"
"net/netip"
"strconv"
"strings"
"sync"
@@ -22,14 +23,21 @@ import (
// 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":
laddr, err := net.ResolveTCPAddr(n, addr)
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return c.ListenTCP(laddr)
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:
@@ -102,15 +110,24 @@ func (c *Client) handleForwards() {
// 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{
laddr.IP.String(),
uint32(laddr.Port),
host,
uint32(port),
}
// send message
ok, resp, err := c.SendRequest("tcpip-forward", true, Marshal(&m))
@@ -123,20 +140,33 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
// If the original port was 0, then the remote side will
// supply a real port number in the response.
if laddr.Port == 0 {
if port == 0 {
var p struct {
Port uint32
}
if err := Unmarshal(resp, &p); err != nil {
return nil, err
}
laddr.Port = int(p.Port)
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)
// Register this forward, using the port number we obtained.
ch := c.forwards.add(laddr)
return &tcpListener{laddr, c, ch}, nil
return &tcpListener{laddr, addr, c, ch}, nil
}
// forwardList stores a mapping between remote
@@ -149,8 +179,9 @@ type forwardList struct {
// forwardEntry represents an established mapping of a laddr on a
// remote ssh server to a channel connected to a tcpListener.
type forwardEntry struct {
laddr net.Addr
c chan forward
addr string // host:port or socket path
network string // tcp or unix
c chan forward
}
// forward represents an incoming forwarded tcpip connection. The
@@ -161,12 +192,13 @@ type forward struct {
raddr net.Addr // the raddr of the incoming connection
}
func (l *forwardList) add(addr net.Addr) chan forward {
func (l *forwardList) add(n, addr string) chan forward {
l.Lock()
defer l.Unlock()
f := forwardEntry{
laddr: addr,
c: make(chan forward, 1),
addr: addr,
network: n,
c: make(chan forward, 1),
}
l.entries = append(l.entries, f)
return f.c
@@ -185,19 +217,20 @@ 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 := net.ParseIP(string(addr))
if ip == nil {
ip, err := netip.ParseAddr(addr)
if err != nil {
return nil, fmt.Errorf("ssh: cannot parse IP address %q", addr)
}
return &net.TCPAddr{IP: ip, Port: int(port)}, nil
return &net.TCPAddr{IP: net.IP(ip.AsSlice()), Port: int(port)}, nil
}
func (l *forwardList) handleChannels(in <-chan NewChannel) {
for ch := range in {
var (
laddr net.Addr
raddr net.Addr
err error
addr string
network string
raddr net.Addr
err error
)
switch channelType := ch.ChannelType(); channelType {
case "forwarded-tcpip":
@@ -207,40 +240,34 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) {
continue
}
// RFC 4254 section 7.2 specifies that incoming
// addresses should list the address, in string
// format. It is implied that this should be an IP
// address, as it would be impossible to connect to it
// otherwise.
laddr, err = parseTCPAddr(payload.Addr, payload.Port)
if err != nil {
ch.Reject(ConnectionFailed, 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
}
laddr = &net.UnixAddr{
Name: payload.SocketPath,
Net: "unix",
}
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(laddr, raddr, ch); !ok {
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")
@@ -252,11 +279,11 @@ func (l *forwardList) handleChannels(in <-chan NewChannel) {
// remove removes the forward entry, and the channel feeding its
// listener.
func (l *forwardList) remove(addr net.Addr) {
func (l *forwardList) remove(n, addr string) {
l.Lock()
defer l.Unlock()
for i, f := range l.entries {
if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() {
if n == f.network && addr == f.addr {
l.entries = append(l.entries[:i], l.entries[i+1:]...)
close(f.c)
return
@@ -274,11 +301,11 @@ func (l *forwardList) closeAll() {
l.entries = nil
}
func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
func (l *forwardList) forward(n, addr string, raddr net.Addr, ch NewChannel) bool {
l.Lock()
defer l.Unlock()
for _, f := range l.entries {
if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() {
if n == f.network && addr == f.addr {
f.c <- forward{newCh: ch, raddr: raddr}
return true
}
@@ -288,6 +315,7 @@ func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool {
type tcpListener struct {
laddr *net.TCPAddr
addr string
conn *Client
in <-chan forward
@@ -314,13 +342,21 @@ func (l *tcpListener) Accept() (net.Conn, error) {
// 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{
l.laddr.IP.String(),
uint32(l.laddr.Port),
host,
uint32(rport),
}
// this also closes the listener.
l.conn.forwards.remove(l.laddr)
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")
+2
View File
@@ -4,4 +4,6 @@
// Package test contains integration tests for the
// golang.org/x/crypto/ssh package.
//
// Deprecated: this package is for internal use only.
package test
+4
View File
@@ -51,6 +51,8 @@ func testPortForward(t *testing.T, n, listenAddr string) {
}
}()
// 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 {
@@ -111,6 +113,8 @@ func testPortForward(t *testing.T, n, listenAddr string) {
}
func TestPortForwardTCP(t *testing.T) {
testPortForward(t, "tcp", ":0")
testPortForward(t, "tcp", "[::]:0")
testPortForward(t, "tcp", "localhost:0")
}
+3 -3
View File
@@ -236,7 +236,7 @@ func recordingsClientConfig() *ssh.ClientConfig {
config := clientConfig()
config.SetDefaults()
// Remove ML-KEM since it only works with Go 1.24.
if config.KeyExchanges[0] == "mlkem768x25519-sha256" {
if config.KeyExchanges[0] == ssh.KeyExchangeMLKEM768X25519 {
config.KeyExchanges = config.KeyExchanges[1:]
}
config.Auth = []ssh.AuthMethod{
@@ -251,7 +251,7 @@ func TestClientKeyExchanges(t *testing.T) {
var keyExchanges []string
for _, kex := range config.KeyExchanges {
// Exclude ecdh for now, to make them determistic we should use see a
// 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)
@@ -352,7 +352,7 @@ func TestHostKeyCheck(t *testing.T) {
// change the keys.
hostDB.keys[ssh.KeyAlgoRSA][25]++
hostDB.keys[ssh.KeyAlgoDSA][25]++
hostDB.keys[ssh.InsecureKeyAlgoDSA][25]++
hostDB.keys[ssh.KeyAlgoECDSA256][25]++
test := clientTest{
+2 -2
View File
@@ -219,7 +219,7 @@ func recordingsServerConfig() *ssh.ServerConfig {
config.SetDefaults()
// Remove ML-KEM since it only works with Go 1.24.
config.SetDefaults()
if config.KeyExchanges[0] == "mlkem768x25519-sha256" {
if config.KeyExchanges[0] == ssh.KeyExchangeMLKEM768X25519 {
config.KeyExchanges = config.KeyExchanges[1:]
}
config.AddHostKey(testSigners["rsa"])
@@ -232,7 +232,7 @@ func TestServerKeyExchanges(t *testing.T) {
var keyExchanges []string
for _, kex := range config.KeyExchanges {
// Exclude ecdh for now, to make them determistic we should use see a
// 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") {
+1 -1
View File
@@ -128,7 +128,7 @@ func clientConfig() *ssh.ClientConfig {
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.KeyAlgoDSA,
ssh.KeyAlgoRSA, ssh.InsecureKeyAlgoDSA,
ssh.KeyAlgoED25519,
},
}
+2 -2
View File
@@ -224,8 +224,8 @@ func testOneCipher(t *testing.T, cipher string, cipherOrder []string) {
}
var deprecatedCiphers = []string{
"aes128-cbc", "3des-cbc",
"arcfour128", "arcfour256",
ssh.InsecureCipherAES128CBC, ssh.InsecureCipherTripleDESCBC,
ssh.InsecureCipherRC4128, ssh.InsecureCipherRC4256,
}
func TestCiphers(t *testing.T) {
+74 -11
View File
@@ -34,23 +34,29 @@ func sshClient(t *testing.T) string {
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)
dir := t.TempDir()
keyPrivPath := filepath.Join(dir, "rsa")
for fn, content := range map[string][]byte{
keyPrivPath: testdata.PEMBytes["rsa"],
keyPrivPath + ".pub": ssh.MarshalAuthorizedKey(testPublicKeys["rsa"]),
filepath.Join(dir, "rsa-cert.pub"): testdata.SSHCertificates["rsa-user-testcertificate"],
} {
if err := os.WriteFile(fn, content, 0600); err != nil {
t.Fatalf("WriteFile(%q): %v", fn, err)
}
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 {
@@ -98,3 +104,60 @@ func TestSSHCLIAuth(t *testing.T) {
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))
}
})
}
}
+254 -255
View File
@@ -2,58 +2,59 @@
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..|
00000010 31 30 2e 30 0d 0a |10.0..|
>>> Flow 3 (client to server)
00000000 00 00 02 9c 11 14 7f 9c 2b a4 e8 8f 82 7d 61 60 |........+....}a`|
00000010 45 50 76 05 85 3e 00 00 00 39 63 75 72 76 65 32 |EPv..>...9curve2|
00000020 35 35 31 39 2d 73 68 61 32 35 36 2c 65 78 74 2d |5519-sha256,ext-|
00000030 69 6e 66 6f 2d 63 2c 6b 65 78 2d 73 74 72 69 63 |info-c,kex-stric|
00000040 74 2d 63 2d 76 30 30 40 6f 70 65 6e 73 73 68 2e |t-c-v00@openssh.|
00000050 63 6f 6d 00 00 00 57 65 63 64 73 61 2d 73 68 61 |com...Wecdsa-sha|
00000060 32 2d 6e 69 73 74 70 32 35 36 2c 65 63 64 73 61 |2-nistp256,ecdsa|
00000070 2d 73 68 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 |-sha2-nistp384,e|
00000080 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 |cdsa-sha2-nistp5|
00000090 32 31 2c 73 73 68 2d 72 73 61 2c 73 73 68 2d 64 |21,ssh-rsa,ssh-d|
000000a0 73 73 2c 73 73 68 2d 65 64 32 35 35 31 39 00 00 |ss,ssh-ed25519..|
000000b0 00 6c 61 65 73 31 32 38 2d 67 63 6d 40 6f 70 65 |.laes128-gcm@ope|
000000c0 6e 73 73 68 2e 63 6f 6d 2c 61 65 73 32 35 36 2d |nssh.com,aes256-|
000000d0 67 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |gcm@openssh.com,|
000000e0 63 68 61 63 68 61 32 30 2d 70 6f 6c 79 31 33 30 |chacha20-poly130|
000000f0 35 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 65 |5@openssh.com,ae|
00000100 73 31 32 38 2d 63 74 72 2c 61 65 73 31 39 32 2d |s128-ctr,aes192-|
00000110 63 74 72 2c 61 65 73 32 35 36 2d 63 74 72 00 00 |ctr,aes256-ctr..|
00000120 00 6c 61 65 73 31 32 38 2d 67 63 6d 40 6f 70 65 |.laes128-gcm@ope|
00000130 6e 73 73 68 2e 63 6f 6d 2c 61 65 73 32 35 36 2d |nssh.com,aes256-|
00000140 67 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |gcm@openssh.com,|
00000150 63 68 61 63 68 61 32 30 2d 70 6f 6c 79 31 33 30 |chacha20-poly130|
00000160 35 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 65 |5@openssh.com,ae|
00000170 73 31 32 38 2d 63 74 72 2c 61 65 73 31 39 32 2d |s128-ctr,aes192-|
00000180 63 74 72 2c 61 65 73 32 35 36 2d 63 74 72 00 00 |ctr,aes256-ctr..|
00000190 00 6e 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2d |.nhmac-sha2-256-|
000001a0 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |etm@openssh.com,|
000001b0 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2d 65 74 |hmac-sha2-512-et|
000001c0 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d |m@openssh.com,hm|
000001d0 61 63 2d 73 68 61 32 2d 32 35 36 2c 68 6d 61 63 |ac-sha2-256,hmac|
000001e0 2d 73 68 61 32 2d 35 31 32 2c 68 6d 61 63 2d 73 |-sha2-512,hmac-s|
000001f0 68 61 31 2c 68 6d 61 63 2d 73 68 61 31 2d 39 36 |ha1,hmac-sha1-96|
00000200 00 00 00 6e 68 6d 61 63 2d 73 68 61 32 2d 32 35 |...nhmac-sha2-25|
00000210 36 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f |6-etm@openssh.co|
00000220 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2d |m,hmac-sha2-512-|
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 32 35 36 2c 68 6d |hmac-sha2-256,hm|
00000250 61 63 2d 73 68 61 32 2d 35 31 32 2c 68 6d 61 63 |ac-sha2-512,hmac|
00000260 2d 73 68 61 31 2c 68 6d 61 63 2d 73 68 61 31 2d |-sha1,hmac-sha1-|
00000270 39 36 00 00 00 04 6e 6f 6e 65 00 00 00 04 6e 6f |96....none....no|
00000280 6e 65 00 00 00 00 00 00 00 00 00 00 00 00 00 d7 |ne..............|
00000290 3b 80 93 f6 ef bc 88 eb 1a 6e ac fa 66 ef 26 3c |;........n..f.&<|
00000000 00 00 02 ac 04 14 7f 9c 2b a4 e8 8f 82 7d 61 60 |........+....}a`|
00000010 45 50 76 05 85 3e 00 00 00 56 63 75 72 76 65 32 |EPv..>...Vcurve2|
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 78 74 2d 69 6e 66 |bssh.org,ext-inf|
00000050 6f 2d 63 2c 6b 65 78 2d 73 74 72 69 63 74 2d 63 |o-c,kex-strict-c|
00000060 2d 76 30 30 40 6f 70 65 6e 73 73 68 2e 63 6f 6d |-v00@openssh.com|
00000070 00 00 00 57 65 63 64 73 61 2d 73 68 61 32 2d 6e |...Wecdsa-sha2-n|
00000080 69 73 74 70 32 35 36 2c 65 63 64 73 61 2d 73 68 |istp256,ecdsa-sh|
00000090 61 32 2d 6e 69 73 74 70 33 38 34 2c 65 63 64 73 |a2-nistp384,ecds|
000000a0 61 2d 73 68 61 32 2d 6e 69 73 74 70 35 32 31 2c |a-sha2-nistp521,|
000000b0 73 73 68 2d 72 73 61 2c 73 73 68 2d 64 73 73 2c |ssh-rsa,ssh-dss,|
000000c0 73 73 68 2d 65 64 32 35 35 31 39 00 00 00 6c 61 |ssh-ed25519...la|
000000d0 65 73 31 32 38 2d 67 63 6d 40 6f 70 65 6e 73 73 |es128-gcm@openss|
000000e0 68 2e 63 6f 6d 2c 61 65 73 32 35 36 2d 67 63 6d |h.com,aes256-gcm|
000000f0 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 63 68 61 |@openssh.com,cha|
00000100 63 68 61 32 30 2d 70 6f 6c 79 31 33 30 35 40 6f |cha20-poly1305@o|
00000110 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 65 73 31 32 |penssh.com,aes12|
00000120 38 2d 63 74 72 2c 61 65 73 31 39 32 2d 63 74 72 |8-ctr,aes192-ctr|
00000130 2c 61 65 73 32 35 36 2d 63 74 72 00 00 00 6c 61 |,aes256-ctr...la|
00000140 65 73 31 32 38 2d 67 63 6d 40 6f 70 65 6e 73 73 |es128-gcm@openss|
00000150 68 2e 63 6f 6d 2c 61 65 73 32 35 36 2d 67 63 6d |h.com,aes256-gcm|
00000160 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 63 68 61 |@openssh.com,cha|
00000170 63 68 61 32 30 2d 70 6f 6c 79 31 33 30 35 40 6f |cha20-poly1305@o|
00000180 70 65 6e 73 73 68 2e 63 6f 6d 2c 61 65 73 31 32 |penssh.com,aes12|
00000190 38 2d 63 74 72 2c 61 65 73 31 39 32 2d 63 74 72 |8-ctr,aes192-ctr|
000001a0 2c 61 65 73 32 35 36 2d 63 74 72 00 00 00 6e 68 |,aes256-ctr...nh|
000001b0 6d 61 63 2d 73 68 61 32 2d 32 35 36 2d 65 74 6d |mac-sha2-256-etm|
000001c0 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 |@openssh.com,hma|
000001d0 63 2d 73 68 61 32 2d 35 31 32 2d 65 74 6d 40 6f |c-sha2-512-etm@o|
000001e0 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d |penssh.com,hmac-|
000001f0 73 68 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 68 |sha2-256,hmac-sh|
00000200 61 32 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 31 |a2-512,hmac-sha1|
00000210 2c 68 6d 61 63 2d 73 68 61 31 2d 39 36 00 00 00 |,hmac-sha1-96...|
00000220 6e 68 6d 61 63 2d 73 68 61 32 2d 32 35 36 2d 65 |nhmac-sha2-256-e|
00000230 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 |tm@openssh.com,h|
00000240 6d 61 63 2d 73 68 61 32 2d 35 31 32 2d 65 74 6d |mac-sha2-512-etm|
00000250 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 |@openssh.com,hma|
00000260 63 2d 73 68 61 32 2d 32 35 36 2c 68 6d 61 63 2d |c-sha2-256,hmac-|
00000270 73 68 61 32 2d 35 31 32 2c 68 6d 61 63 2d 73 68 |sha2-512,hmac-sh|
00000280 61 31 2c 68 6d 61 63 2d 73 68 61 31 2d 39 36 00 |a1,hmac-sha1-96.|
00000290 00 00 04 6e 6f 6e 65 00 00 00 04 6e 6f 6e 65 00 |...none....none.|
000002a0 00 00 00 00 00 00 00 00 00 00 00 00 d7 3b 80 93 |.............;..|
>>> Flow 4 (server to client)
00000000 00 00 04 9c 0a 14 86 1d 13 33 bb 65 c5 7e 3d 8b |.........3.e.~=.|
00000010 4f 63 27 f5 d1 59 00 00 01 7a 73 6e 74 72 75 70 |Oc'..Y...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|
00000000 00 00 04 7c 08 14 d7 da a8 cd 40 76 e6 8a 12 b5 |...|......@v....|
00000010 5f ff a4 a5 57 4b 00 00 01 5c 6d 6c 6b 65 6d 37 |_...WK...\mlkem7|
00000020 36 38 78 32 35 35 31 39 2d 73 68 61 32 35 36 2c |68x25519-sha256,|
00000030 73 6e 74 72 75 70 37 36 31 78 32 35 35 31 39 2d |sntrup761x25519-|
00000040 73 68 61 35 31 32 2c 73 6e 74 72 75 70 37 36 31 |sha512,sntrup761|
00000050 78 32 35 35 31 39 2d 73 68 61 35 31 32 40 6f 70 |x25519-sha512@op|
00000060 65 6e 73 73 68 2e 63 6f 6d 2c 63 75 72 76 65 32 |enssh.com,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|
@@ -61,229 +62,227 @@
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|
000000e0 2d 67 72 6f 75 70 31 34 2d 73 68 61 31 2c 64 69 |-group14-sha1,di|
000000f0 66 66 69 65 2d 68 65 6c 6c 6d 61 6e 2d 67 72 6f |ffie-hellman-gro|
00000100 75 70 31 34 2d 73 68 61 32 35 36 2c 64 69 66 66 |up14-sha256,diff|
00000110 69 65 2d 68 65 6c 6c 6d 61 6e 2d 67 72 6f 75 70 |ie-hellman-group|
00000120 31 36 2d 73 68 61 35 31 32 2c 64 69 66 66 69 65 |16-sha512,diffie|
00000130 2d 68 65 6c 6c 6d 61 6e 2d 67 72 6f 75 70 2d 65 |-hellman-group-e|
00000140 78 63 68 61 6e 67 65 2d 73 68 61 32 35 36 2c 65 |xchange-sha256,e|
00000150 78 74 2d 69 6e 66 6f 2d 73 2c 6b 65 78 2d 73 74 |xt-info-s,kex-st|
00000160 72 69 63 74 2d 73 2d 76 30 30 40 6f 70 65 6e 73 |rict-s-v00@opens|
00000170 73 68 2e 63 6f 6d 00 00 00 2d 72 73 61 2d 73 68 |sh.com...-rsa-sh|
00000180 61 32 2d 35 31 32 2c 72 73 61 2d 73 68 61 32 2d |a2-512,rsa-sha2-|
00000190 32 35 36 2c 65 63 64 73 61 2d 73 68 61 32 2d 6e |256,ecdsa-sha2-n|
000001a0 69 73 74 70 32 35 36 00 00 00 6c 63 68 61 63 68 |istp256...lchach|
000001b0 61 32 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 |a20-poly1305@ope|
000001c0 6e 73 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d |nssh.com,aes128-|
000001d0 67 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |gcm@openssh.com,|
000001e0 61 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 |aes256-gcm@opens|
000001f0 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
00000200 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
00000210 32 35 36 2d 63 74 72 00 00 00 6c 63 68 61 63 68 |256-ctr...lchach|
00000220 61 32 30 2d 70 6f 6c 79 31 33 30 35 40 6f 70 65 |a20-poly1305@ope|
00000230 6e 73 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d |nssh.com,aes128-|
00000240 67 63 6d 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c |gcm@openssh.com,|
00000250 61 65 73 32 35 36 2d 67 63 6d 40 6f 70 65 6e 73 |aes256-gcm@opens|
00000260 73 68 2e 63 6f 6d 2c 61 65 73 31 32 38 2d 63 74 |sh.com,aes128-ct|
00000270 72 2c 61 65 73 31 39 32 2d 63 74 72 2c 61 65 73 |r,aes192-ctr,aes|
00000280 32 35 36 2d 63 74 72 00 00 00 d5 75 6d 61 63 2d |256-ctr....umac-|
00000290 36 34 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 |64-etm@openssh.c|
000002a0 6f 6d 2c 75 6d 61 63 2d 31 32 38 2d 65 74 6d 40 |om,umac-128-etm@|
000002b0 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 |openssh.com,hmac|
000002c0 2d 73 68 61 32 2d 32 35 36 2d 65 74 6d 40 6f 70 |-sha2-256-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|
000002e0 68 61 32 2d 35 31 32 2d 65 74 6d 40 6f 70 65 6e |ha2-512-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|
00000300 31 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 6f |1-etm@openssh.co|
00000310 6d 2c 75 6d 61 63 2d 36 34 40 6f 70 65 6e 73 73 |m,umac-64@openss|
00000320 68 2e 63 6f 6d 2c 75 6d 61 63 2d 31 32 38 40 6f |h.com,umac-128@o|
00000330 70 65 6e 73 73 68 2e 63 6f 6d 2c 68 6d 61 63 2d |penssh.com,hmac-|
00000340 73 68 61 32 2d 32 35 36 2c 68 6d 61 63 2d 73 68 |sha2-256,hmac-sh|
00000350 61 32 2d 35 31 32 2c 68 6d 61 63 2d 73 68 61 31 |a2-512,hmac-sha1|
00000360 00 00 00 d5 75 6d 61 63 2d 36 34 2d 65 74 6d 40 |....umac-64-etm@|
00000370 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 |openssh.com,umac|
00000380 2d 31 32 38 2d 65 74 6d 40 6f 70 65 6e 73 73 68 |-128-etm@openssh|
00000390 2e 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 |.com,hmac-sha2-2|
000003a0 35 36 2d 65 74 6d 40 6f 70 65 6e 73 73 68 2e 63 |56-etm@openssh.c|
000003b0 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 |om,hmac-sha2-512|
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 |................|
000003d0 2c 68 6d 61 63 2d 73 68 61 31 2d 65 74 6d 40 6f |,hmac-sha1-etm@o|
000003e0 70 65 6e 73 73 68 2e 63 6f 6d 2c 75 6d 61 63 2d |penssh.com,umac-|
000003f0 36 34 40 6f 70 65 6e 73 73 68 2e 63 6f 6d 2c 75 |64@openssh.com,u|
00000400 6d 61 63 2d 31 32 38 40 6f 70 65 6e 73 73 68 2e |mac-128@openssh.|
00000410 63 6f 6d 2c 68 6d 61 63 2d 73 68 61 32 2d 32 35 |com,hmac-sha2-25|
00000420 36 2c 68 6d 61 63 2d 73 68 61 32 2d 35 31 32 2c |6,hmac-sha2-512,|
00000430 68 6d 61 63 2d 73 68 61 31 00 00 00 15 6e 6f 6e |hmac-sha1....non|
00000440 65 2c 7a 6c 69 62 40 6f 70 65 6e 73 73 68 2e 63 |e,zlib@openssh.c|
00000450 6f 6d 00 00 00 15 6e 6f 6e 65 2c 7a 6c 69 62 40 |om....none,zlib@|
00000460 6f 70 65 6e 73 73 68 2e 63 6f 6d 00 00 00 00 00 |openssh.com.....|
00000470 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...<#.|
00000000 00 00 00 2c 06 1e 00 00 00 20 13 87 32 34 3e 68 |...,..... ..24>h|
00000010 e6 9b 9a cb 23 22 06 11 e8 24 71 e5 d7 96 79 83 |....#"...$q...y.|
00000020 48 59 1a 95 f2 b0 86 1c 76 54 58 e8 a3 63 9c a8 |HY......vTX..c..|
>>> Flow 6 (server to client)
00000000 00 00 01 04 0b 1f 00 00 00 68 00 00 00 13 65 63 |.........h....ec|
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 e8 01 e7 b0 7b 47 62 2e ac 12 |..... ....{Gb...|
00000080 80 ba 70 5b a4 65 6a 25 50 1c 59 6c f3 12 ed 64 |..p[.ej%P.Yl...d|
00000090 2c f3 7a 3b 0e 53 00 00 00 63 00 00 00 13 65 63 |,.z;.S...c....ec|
00000070 84 a8 00 00 00 20 85 12 a8 ca dd 22 7d 74 89 87 |..... ....."}t..|
00000080 df 36 23 6e fb 59 c0 75 4b 38 1e 49 d6 fa 5e 8c |.6#n.Y.uK8.I..^.|
00000090 cd 6c eb fd 26 34 00 00 00 64 00 00 00 13 65 63 |.l..&4...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 48 00 00 00 1f 1f dd 4b cc 7e f3 af |6...H......K.~..|
000000c0 63 93 77 64 79 a8 77 02 96 98 cb 96 59 a6 a6 a2 |c.wdy.w.....Y...|
000000d0 9b 23 08 d0 f5 2e 13 04 00 00 00 21 00 e9 d8 dc |.#.........!....|
000000e0 27 de b1 e8 bc ee 5e ca 9e 62 20 09 d3 08 43 f6 |'.....^..b ...C.|
000000f0 d0 7d a1 6a f7 6d 64 64 81 ea 63 82 f8 00 00 00 |.}.j.mdd..c.....|
000000b0 36 00 00 00 49 00 00 00 21 00 f4 8e 10 b5 77 04 |6...I...!.....w.|
000000c0 3e 1b 59 06 4f ef 48 08 2b 15 07 4a 69 0f 30 a2 |>.Y.O.H.+..Ji.0.|
000000d0 a4 37 7b 5e f9 de 15 0a ca 1a 00 00 00 20 6a 97 |.7{^......... j.|
000000e0 a3 23 e3 7c 92 67 28 0d 02 9e 36 e0 ae b1 f3 37 |.#.|.g(...6....7|
000000f0 19 34 12 cd bf db 84 31 8e 97 72 a5 e1 3f 00 00 |.4.....1..r..?..|
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 62 ac b9 7c |...........@b..||
00000120 06 dc 0b 16 24 e2 56 cd b2 3a f9 51 ab e8 62 3e |....$.V..:.Q..b>|
00000130 72 a8 fa 95 2d af 2d d0 54 e1 de 08 a3 97 11 08 |r...-.-.T.......|
00000140 52 6b d5 dd 7e 81 be a5 f9 71 83 9e 68 f8 7e d1 |Rk..~....q..h.~.|
00000150 e8 22 8b b3 ff b2 2e 67 16 af d8 df 74 3f bf d6 |.".....g....t?..|
00000160 aa 46 3d 49 90 3d fa 12 6a 6f 75 98 1b b8 67 22 |.F=I.=..jou...g"|
00000170 d0 06 31 61 eb 29 aa 21 fb 7d e4 7e c7 ff b4 e5 |..1a.).!.}.~....|
00000180 a1 0f da 75 74 6f c0 45 31 3f e1 7a 29 d0 ff 25 |...uto.E1?.z)..%|
00000190 64 8c 49 5c c5 bf 9c 44 36 67 e1 86 5c e9 13 23 |d.I\...D6g..\..#|
000001a0 b8 d5 85 5d 33 4b 19 a5 fa bc 50 86 08 32 08 48 |...]3K....P..2.H|
000001b0 5e 0e 2e 68 9a ff 96 6a 00 51 d9 16 87 a6 88 c1 |^..h...j.Q......|
000001c0 dd b5 9c 18 3b fc 66 6a ff a7 60 f6 4a 29 f8 5d |....;.fj..`.J).]|
000001d0 fd 8d 5e 6a f0 1a 70 21 d6 4e c0 92 e3 95 96 b8 |..^j..p!.N......|
000001e0 fa 50 90 58 c5 59 6a 20 12 3d ce d4 49 0e da 2e |.P.X.Yj .=..I...|
000001f0 1d 2c b9 28 d1 71 52 49 43 25 90 24 b2 16 a9 58 |.,.(.qRIC%.$...X|
00000200 0f f3 07 94 92 83 8e 1a 3f 2a 92 a3 42 80 0e 3c |........?*..B..<|
00000210 42 7a 7c 65 3a f0 86 cf 40 49 9b f7 e6 a5 3d 36 |Bz|e:...@I....=6|
00000220 f6 ae db 6e 6c 39 cc 5e 47 f4 eb d2 c6 61 5e 61 |...nl9.^G....a^a|
00000230 e0 58 6c 16 13 c4 74 db e1 be 29 fc 84 be 42 22 |.Xl...t...)...B"|
00000240 14 72 ab 29 f1 ea 10 2c 34 1c 5e d2 a4 19 0d cb |.r.)...,4.^.....|
00000250 23 e0 d3 db 4d 5d 50 c7 a0 82 11 ed 1d 49 d3 4e |#...M]P......I.N|
00000260 32 be db 14 c1 fc 4b dc 97 3a 38 9b |2.....K..:8.|
00000110 00 00 00 00 00 00 00 00 00 00 01 40 03 fb e9 4c |...........@...L|
00000120 15 76 5c bf 4b d7 6a 8b 2a 83 6f df bd 5e f6 45 |.v\.K.j.*.o..^.E|
00000130 b2 2b bc bf 38 9f f0 30 13 e1 03 67 d3 4f e6 6f |.+..8..0...g.O.o|
00000140 88 97 39 8e a6 2d df 6f a7 d2 90 df c2 9a 87 93 |..9..-.o........|
00000150 ad 91 12 2e 92 c1 c4 bb 93 b7 7d 6e fd 7c c2 10 |..........}n.|..|
00000160 fe fa b6 22 7e a5 a7 64 c4 25 ce cc bb 5d a5 f0 |..."~..d.%...]..|
00000170 ad 41 c7 8c a1 59 c3 a9 23 f0 69 38 4a 64 2a a1 |.A...Y..#.i8Jd*.|
00000180 62 8d bc 69 1c c1 01 ce ac a9 30 58 a1 d0 68 92 |b..i......0X..h.|
00000190 35 e8 7b 3a bf 1b 68 82 f6 90 8e dd 01 e6 92 4b |5.{:..h........K|
000001a0 bc ce 50 a8 96 2a 7d a5 4d 61 13 98 67 83 11 70 |..P..*}.Ma..g..p|
000001b0 ef f2 af 94 a6 0c 34 78 05 a8 39 d3 44 f6 c4 13 |......4x..9.D...|
000001c0 76 59 77 38 72 83 c4 92 a7 4c 7f 5e 25 08 9b a0 |vYw8r....L.^%...|
000001d0 6f eb 02 4d 4d c6 0a 07 09 2d c6 80 d3 05 8b c2 |o..MM....-......|
000001e0 e7 de 37 16 5a db 1c 02 2e 35 fb 3a 93 ce 79 4e |..7.Z....5.:..yN|
000001f0 0c 25 05 c9 b7 be b7 47 31 8c 18 d8 37 ba 66 34 |.%.....G1...7.f4|
00000200 bb 03 15 0d 0d cc da ad 2d 2c 36 0f 88 3f 5a 70 |........-,6..?Zp|
00000210 21 5f f1 cc 8a 21 e7 5a af c7 7c a0 e3 51 04 4a |!_...!.Z..|..Q.J|
00000220 d3 cb bb a4 60 2f d1 28 7b 6f 52 10 e8 76 fa c2 |....`/.({oR..v..|
00000230 86 9b 08 3f b2 72 e5 f0 0a 7c e0 19 ce 6f d2 74 |...?.r...|...o.t|
00000240 3e 12 8f 7d 1e 21 73 25 57 b8 c9 96 ad 35 f3 b6 |>..}.!s%W....5..|
00000250 5f b0 ae 82 90 4b 05 10 de 11 46 ea ad d7 3b 08 |_....K....F...;.|
00000260 a6 0d b5 24 f6 e3 ae 3b 67 3c a0 44 |...$...;g<.D|
>>> 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 d9 dc 27 14 2c 6d 93 10 bd 87 c5 06 |... ..'.,m......|
00000020 90 80 21 40 13 e5 86 f4 39 f1 5b b5 75 ed 31 19 |..!@....9.[.u.1.|
00000030 87 05 83 a7 5f 28 81 02 d0 6e b4 f0 bd 89 0a e5 |...._(...n......|
00000040 fa 1e ac 07 |....|
00000000 00 00 00 0c 0a 15 a1 e3 f9 ae 57 e2 35 b8 cc 87 |..........W.5...|
00000010 00 00 00 20 ba d9 4a 00 05 48 df ec d8 c5 bf f8 |... ..J..H......|
00000020 7d ed c8 ac a8 5f 1c 46 a0 21 c3 26 7e c6 51 2d |}...._.F.!.&~.Q-|
00000030 b8 e8 12 50 38 3a 08 8c a8 0e 98 e7 8c 27 4f 4d |...P8:.......'OM|
00000040 76 42 94 1f |vB..|
>>> Flow 8 (server to client)
00000000 00 00 00 20 03 52 b6 aa 7d 30 65 ad 08 b8 dd d1 |... .R..}0e.....|
00000010 7f f0 3e b5 69 43 fa 0f 06 2a 18 5d 9d 4e b0 13 |..>.iC...*.].N..|
00000020 d8 4a 06 ca e1 d7 99 f9 59 14 d0 ef 0b a1 1a 47 |.J......Y......G|
00000030 8f c5 4b e5 |..K.|
00000000 00 00 00 20 53 c2 11 dc 60 cb f1 e0 55 eb f8 50 |... S...`...U..P|
00000010 38 11 aa 98 fc 4c 2a d1 36 0f 57 c2 c9 55 cb 04 |8....L*.6.W..U..|
00000020 dc 2f 79 36 28 5d fa 67 08 01 0a 1b fc a5 57 cd |./y6(].g......W.|
00000030 7f 67 ea 81 |.g..|
>>> Flow 9 (client to server)
00000000 00 00 00 30 34 a3 2c 36 49 de 9d 6c 4b 74 c4 0d |...04.,6I..lKt..|
00000010 2d 25 98 e4 6b 48 58 c5 e0 e6 04 90 31 11 59 4f |-%..kHX.....1.YO|
00000020 f0 33 8d e8 1a 9b e9 b5 02 fb 26 32 0e 5a 09 7b |.3........&2.Z.{|
00000030 5f 2a 80 dd 72 17 cd ed cb 54 ca 3b 50 44 ed 1f |_*..r....T.;PD..|
00000040 d4 4b b4 9b |.K..|
00000000 00 00 00 30 c5 dd dd 05 d4 df 9e 48 d6 58 76 a1 |...0.......H.Xv.|
00000010 8a 7e 04 c6 51 26 44 1a cb 85 8b 9a 51 44 bd 57 |.~..Q&D.....QD.W|
00000020 bc 4f 2e a0 0a b9 e0 ee 00 b6 57 d3 2f 52 71 8e |.O........W./Rq.|
00000030 b6 4e f6 f7 34 7c 9f fe c7 c7 ec ca 08 6f d9 b0 |.N..4|.......o..|
00000040 e3 85 81 49 |...I|
>>> Flow 10 (server to client)
00000000 00 00 00 20 7e c0 e9 9e 89 d7 16 2d 48 c0 e0 bf |... ~......-H...|
00000010 dd c3 89 1e 08 3e ee ab 73 77 77 2e 5a 62 14 02 |.....>..sww.Zb..|
00000020 6a 7f c2 e9 19 c0 df 69 43 60 82 d6 ea ff da f0 |j......iC`......|
00000030 fa 40 2b b6 00 00 00 40 3a 06 de c5 ac 63 46 77 |.@+....@:....cFw|
00000040 a8 8e a1 d0 be 35 cb 27 34 d1 63 a1 0c b2 c3 30 |.....5.'4.c....0|
00000050 c6 d8 bd 92 7a f7 48 4b 95 a5 5f 6e 4e ca 6f 60 |....z.HK.._nN.o`|
00000060 32 11 4d d0 15 2e 31 22 03 1a 89 fa 91 e0 ea 10 |2.M...1"........|
00000070 7b e4 4e d8 a8 a7 a4 35 3c b0 76 39 6e e9 ef c9 |{.N....5<.v9n...|
00000080 69 02 c8 01 51 bc fb ad |i...Q...|
00000000 00 00 00 20 4e d8 10 08 39 ab 47 f6 49 42 4e bd |... N...9.G.IBN.|
00000010 2f 69 f7 bc 48 26 1b 5f ee ee 08 25 2e b8 8d 1f |/i..H&._...%....|
00000020 a0 e0 fa 11 28 26 9c af f0 6b 7e e0 f0 00 8f 92 |....(&...k~.....|
00000030 08 96 73 64 00 00 00 40 60 bb 40 d2 c4 8f 5e 09 |..sd...@`.@...^.|
00000040 e6 d9 13 04 fc 4e 21 81 51 dc ad 06 48 51 fc 35 |.....N!.Q...HQ.5|
00000050 b2 cd bb ba df 66 c4 20 b1 c0 b0 7e 7b c0 a7 41 |.....f. ...~{..A|
00000060 6f 16 7a fe 01 fe 3a be 45 4a b9 94 d1 46 a1 82 |o.z...:.EJ...F..|
00000070 09 24 97 57 17 83 9e e6 1b 4e 04 83 a0 73 55 08 |.$.W.....N...sU.|
00000080 2f e6 de 6b 43 77 84 31 |/..kCw.1|
>>> Flow 11 (client to server)
00000000 00 00 01 60 3a fe b0 e3 19 9e ef b8 4b 47 ac 18 |...`:.......KG..|
00000010 52 0f 93 e5 04 69 58 d5 51 b9 82 ab 56 41 5c 73 |R....iX.Q...VA\s|
00000020 e2 e7 d1 98 41 f2 73 30 13 b5 7d ca 0e 0d 78 7b |....A.s0..}...x{|
00000030 01 83 ab e7 ce 1d 3b 8c 14 ed c5 0d 39 cf 82 f3 |......;.....9...|
00000040 f6 eb 9e 17 c4 36 61 78 5b 50 66 2d 8c 37 cf fb |.....6ax[Pf-.7..|
00000050 42 44 45 fc 94 a8 e2 d2 9e 5e 5f 12 85 f2 4a 46 |BDE......^_...JF|
00000060 50 fc 5c 04 80 2e 17 ce 7e f2 a6 ee 94 7c e7 99 |P.\.....~....|..|
00000070 6c a3 b3 5d 08 23 f6 10 6f 79 8b a0 56 20 d3 ed |l..].#..oy..V ..|
00000080 e0 44 07 fb dc d3 b6 64 5b c2 d4 66 77 3e 39 a2 |.D.....d[..fw>9.|
00000090 57 4b 44 95 96 33 4b 51 51 40 3f f0 9a db 95 96 |WKD..3KQQ@?.....|
000000a0 41 3c f2 d6 7c d3 bb 6b a5 b1 b3 67 0b 94 38 e1 |A<..|..k...g..8.|
000000b0 4d e5 68 68 f4 e0 93 57 d7 01 7b 4d 9c 8c 18 32 |M.hh...W..{M...2|
000000c0 44 3a 93 7b 38 3b 19 0f 63 98 04 15 22 1e ce 09 |D:.{8;..c..."...|
000000d0 db f5 80 2e 42 bb 22 59 9d 04 05 7e 67 32 c8 52 |....B."Y...~g2.R|
000000e0 9f 2d d0 02 e9 ad d0 7f 1d b3 f8 64 f2 4b ff 03 |.-.........d.K..|
000000f0 56 6b 28 b5 bb 2e 3c eb 47 5b 95 96 26 64 8a 8e |Vk(...<.G[..&d..|
00000100 96 e6 fe bd ee 71 e7 53 6b 27 a6 b7 38 ba 8c 5b |.....q.Sk'..8..[|
00000110 14 14 08 ec 59 e7 a5 42 b4 0e 0e 39 09 c1 25 bc |....Y..B...9..%.|
00000120 6f 9b 94 85 14 ae 76 6c 94 f2 58 d5 ad 73 01 fc |o.....vl..X..s..|
00000130 f1 6c 0d e7 13 f1 9b 86 54 33 b3 d2 c0 78 3e 9b |.l......T3...x>.|
00000140 fb aa e2 ce c8 1f 3e f5 19 2e 22 78 fb 02 4d ab |......>..."x..M.|
00000150 49 52 3e 17 6c 22 a7 4a 19 aa 68 a1 12 d6 24 4e |IR>.l".J..h...$N|
00000160 e7 0d 39 cc a5 1a 90 f6 26 15 75 9f 80 88 74 11 |..9.....&.u...t.|
00000170 9f d8 2b 2e |..+.|
00000000 00 00 01 60 f2 7d b2 4f d9 db ec 78 6b a2 c2 77 |...`.}.O...xk..w|
00000010 6a a1 f8 d0 3c 5c e0 3b d3 99 33 ac 54 4b f7 e2 |j...<\.;..3.TK..|
00000020 3d 1f 6e 51 6f 4c c2 7e d5 3a 79 1e 9f 51 70 75 |=.nQoL.~.:y..Qpu|
00000030 67 35 8c f8 5d 62 35 9a ff fe b2 46 6f 72 47 0b |g5..]b5....ForG.|
00000040 5b 78 30 5b ce 54 5c ab 46 a4 a5 12 8b ef 32 2e |[x0[.T\.F.....2.|
00000050 1e a9 4a 25 07 9e a3 87 42 f9 69 ae c6 9d 83 68 |..J%....B.i....h|
00000060 e8 dd 19 a3 ef 4e 97 c9 ef 19 be a7 08 77 28 7d |.....N.......w(}|
00000070 ee aa b4 a4 3a e5 c1 4e 78 ee b2 b7 50 6d f2 eb |....:..Nx...Pm..|
00000080 2b df 38 22 a3 c9 be f7 ea 36 2f 7d f7 25 fd f4 |+.8".....6/}.%..|
00000090 9f 54 ec cf d5 82 5c 4b e5 e0 3f 74 da 9d d0 32 |.T....\K..?t...2|
000000a0 06 f6 a1 13 15 f4 11 24 e9 74 4c 9b 5e e2 c8 5b |.......$.tL.^..[|
000000b0 32 6b 89 87 d4 d4 ee e3 0c 81 16 d4 54 b7 97 ef |2k..........T...|
000000c0 c6 a4 d7 16 2d 87 be 2c 19 b8 48 9d d6 82 3c 82 |....-..,..H...<.|
000000d0 88 de bc 18 5b 64 ab aa d7 ac 8b 64 6a ad a8 4d |....[d.....dj..M|
000000e0 5a 20 a3 6e ee 7b 7f bf dc 6d 69 6a b0 8b ee 26 |Z .n.{...mij...&|
000000f0 0b 34 f0 22 a6 81 0f d3 b5 15 e8 86 1b 71 1a 60 |.4.".........q.`|
00000100 16 dc 2f 6b ca 63 6c 52 b3 33 42 46 c0 ea 63 50 |../k.clR.3BF..cP|
00000110 bb 23 29 69 79 0c 23 3f fb da 29 dd 94 44 f1 7e |.#)iy.#?..)..D.~|
00000120 58 1c 12 0f df a7 80 3c 69 8a 44 f7 4d 2e 7b db |X......<i.D.M.{.|
00000130 c2 5a 01 c0 0c 57 8e ff 4f 98 74 71 c3 3b 61 fc |.Z...W..O.tq.;a.|
00000140 1b e6 fb 4c 75 33 ff 6b 0e 83 52 40 4a 7f 35 2f |...Lu3.k..R@J.5/|
00000150 82 38 40 22 22 bc 51 6e 21 ce f9 bb 02 bf 26 a5 |.8@"".Qn!.....&.|
00000160 63 9b 91 c3 37 46 6d f6 fe 19 27 5f 91 68 a7 29 |c...7Fm...'_.h.)|
00000170 b7 00 27 37 |..'7|
>>> Flow 12 (server to client)
00000000 00 00 01 40 07 30 22 23 53 70 62 fd 53 15 21 49 |...@.0"#Spb.S.!I|
00000010 b7 ff 50 fc c0 4e 4d 8f ef 4b 09 8a 9f e0 0e de |..P..NM..K......|
00000020 de 3d 06 cf e2 8c f1 cc 92 c4 84 78 6f 1e 4a cd |.=.........xo.J.|
00000030 8b 8a 61 a9 ff 6e 3c c2 3c d0 0e 70 5d 6e 4e 40 |..a..n<.<..p]nN@|
00000040 c0 8a 4f 3b e4 25 05 15 21 63 09 fd f7 fd ee d9 |..O;.%..!c......|
00000050 92 10 e1 e6 6a 27 ae ff fe 1e 66 7a 00 e7 27 95 |....j'....fz..'.|
00000060 d2 fe 32 5f f8 79 89 ea 25 19 62 44 04 aa 84 8c |..2_.y..%.bD....|
00000070 2b 46 b4 73 e3 4f 9b d9 bd 39 7d 13 91 5c 55 44 |+F.s.O...9}..\UD|
00000080 70 67 8e 50 5b 12 23 b4 39 82 cd 15 ac 77 91 bf |pg.P[.#.9....w..|
00000090 4d ea d7 4c e2 ef 7b 0a f6 4e 77 aa 47 49 fd a2 |M..L..{..Nw.GI..|
000000a0 bd ae 0e 06 d8 23 df f2 c7 62 8a c0 13 24 3e 30 |.....#...b...$>0|
000000b0 5d ba 3e e1 3f 5d 80 34 54 ab e0 00 7c 61 5b 17 |].>.?].4T...|a[.|
000000c0 23 c6 7e c6 d2 d1 01 0a 32 a5 d5 85 3e ec dd a8 |#.~.....2...>...|
000000d0 13 dc 52 e8 c4 85 56 04 3d 98 2d 72 6a f2 bb 52 |..R...V.=.-rj..R|
000000e0 ae f0 86 ff 99 9e a9 b9 23 4c 27 65 41 5b 27 37 |........#L'eA['7|
000000f0 93 ee ac 33 f8 c9 23 e9 3e 5a 4d d3 58 51 e8 e4 |...3..#.>ZM.XQ..|
00000100 7e 3f 45 d3 d0 02 ab 5b 9d 12 5d 16 34 7b 01 dd |~?E....[..].4{..|
00000110 fb 77 cf 24 55 f5 0d 00 56 9c 94 4c 96 af eb 68 |.w.$U...V..L...h|
00000120 af 80 d8 20 8a 7f 26 6b ec 47 64 0c 9d 05 9c 8f |... ..&k.Gd.....|
00000130 93 3a 2c 89 81 ae 37 6f f5 53 43 19 cf 7d c7 95 |.:,...7o.SC..}..|
00000140 6d f1 25 ff c1 8f 26 5b 4f b1 a0 99 cf 29 b7 cc |m.%...&[O....)..|
00000150 72 b2 49 83 |r.I.|
00000000 00 00 01 40 ed 8c ad 60 63 f2 de d8 77 4e ea e5 |...@...`c...wN..|
00000010 7f 0e e7 65 d5 07 51 66 b5 f6 60 1f ba f0 00 59 |...e..Qf..`....Y|
00000020 ed b3 87 97 9f 52 71 a1 60 9b 3b ef 16 5c 69 2b |.....Rq.`.;..\i+|
00000030 73 1b 35 01 61 52 d6 36 50 5b 61 04 da e3 eb 2f |s.5.aR.6P[a..../|
00000040 b1 5f e4 b2 20 fa f9 52 76 ff 90 66 fe 69 98 26 |._.. ..Rv..f.i.&|
00000050 c2 72 9f 93 aa e3 bb 9a b4 28 62 e3 ea 7f 94 c6 |.r.......(b.....|
00000060 92 e3 2a 81 99 6c 08 f1 df 99 dc d1 a0 cd d0 e6 |..*..l..........|
00000070 31 21 7b d4 c0 e7 85 66 2c 3f 0e 9e 8f 8b 49 11 |1!{....f,?....I.|
00000080 64 6b 61 c4 39 15 d2 c0 83 76 f5 cf f5 5d 0c dc |dka.9....v...]..|
00000090 03 37 22 a7 fa f5 3c 8b b2 a4 2f 1b c3 57 7f 9a |.7"...<.../..W..|
000000a0 4b eb 80 dc e2 17 fd 8f 55 90 b9 03 2d 7f 1e e8 |K.......U...-...|
000000b0 5a 2b 08 57 76 a1 c7 7b 46 97 68 6e af b4 42 8a |Z+.Wv..{F.hn..B.|
000000c0 ea 13 b1 d7 7a 88 c1 f1 9d 45 ec 41 aa ed ef a0 |....z....E.A....|
000000d0 40 3b b8 74 e4 80 88 a9 6a bf d4 a8 9c 27 3d 21 |@;.t....j....'=!|
000000e0 65 8f 28 8a e5 4e 33 a7 fd 9b 78 08 07 ee d0 cf |e.(..N3...x.....|
000000f0 51 f9 b7 5c e3 6c cb 07 5a 74 55 f6 0f ef 42 2b |Q..\.l..ZtU...B+|
00000100 da 28 ac 34 e7 70 64 9b 4d 35 dd a0 d6 94 4f 48 |.(.4.pd.M5....OH|
00000110 d0 91 d5 6b 36 64 9e ce 88 c7 07 9b b7 c4 2e e3 |...k6d..........|
00000120 3b a9 0e 2e b2 63 f6 f6 0c 38 48 71 a0 a4 de 1b |;....c...8Hq....|
00000130 42 61 82 1e d8 1f d0 6b 83 71 ff e8 d5 c6 bc 7b |Ba.....k.q.....{|
00000140 ee 53 5b 87 b5 38 cf 07 5d 73 6e 59 cb d5 d7 d6 |.S[..8..]snY....|
00000150 bc 76 4e e8 |.vN.|
>>> Flow 13 (client to server)
00000000 00 00 02 80 28 72 8e 75 6f ff ec ad 60 fe ff b9 |....(r.uo...`...|
00000010 e9 92 32 54 9f 41 16 05 7d ea 7d 65 36 d9 90 d1 |..2T.A..}.}e6...|
00000020 e1 0b 5d 7c c9 86 08 de 9b fb 7d 45 6f 08 3f b0 |..]|......}Eo.?.|
00000030 74 54 75 53 0f d2 03 fa d8 d4 d7 98 db e3 ed 2c |tTuS...........,|
00000040 76 21 10 53 84 ed 08 53 98 50 5b 93 38 cd 82 35 |v!.S...S.P[.8..5|
00000050 f3 f6 06 cb fd 67 06 4f 4c a9 86 1a bd 27 40 77 |.....g.OL....'@w|
00000060 87 04 da b8 d5 3a bc c6 18 4b 41 54 d4 bc fc 49 |.....:...KAT...I|
00000070 26 e0 4b e1 e9 3b 1a 05 c9 22 94 8e 52 40 30 1d |&.K..;..."..R@0.|
00000080 bb 53 c6 96 2a 38 24 3b e1 23 4d 9f 53 27 b1 3b |.S..*8$;.#M.S'.;|
00000090 39 49 89 a8 d3 02 59 90 fa ee 6f e1 63 d1 6a 86 |9I....Y...o.c.j.|
000000a0 c0 c0 f0 78 2f 5c d7 19 42 59 9d 72 03 96 f8 cd |...x/\..BY.r....|
000000b0 d1 4f 0a 7f c2 e7 cb 87 6e 9a 68 45 05 03 b5 66 |.O......n.hE...f|
000000c0 4f d1 de 9b 97 b4 79 d3 ad b9 b9 ab 49 9a fb 38 |O.....y.....I..8|
000000d0 1e cc bb ce 8a 14 f5 0c 4b b5 6e ce 87 18 d0 bd |........K.n.....|
000000e0 4f 57 28 bf a5 06 1a d2 85 92 61 45 d8 38 c4 5b |OW(.......aE.8.[|
000000f0 6e 0e 65 64 4e e9 dc f6 a4 2c 1c 31 c5 d9 28 fd |n.edN....,.1..(.|
00000100 71 19 c3 2b f0 23 6f f7 c8 2a 6e 4d 1d 5c 93 94 |q..+.#o..*nM.\..|
00000110 ff 9b 5f 33 7f 6c 18 e2 de f4 08 ea 99 ac 83 39 |.._3.l.........9|
00000120 25 6c 16 eb 7f da 70 f8 9e 89 76 5f 4c b1 86 6f |%l....p...v_L..o|
00000130 0e 0e cb 62 af dc 35 e4 20 83 7e e3 37 ca 67 2e |...b..5. .~.7.g.|
00000140 9c e3 97 e3 d0 09 fd 6a 1c 70 e0 ca c8 a4 1e 9d |.......j.p......|
00000150 67 10 07 ef ec 98 c9 7e c2 b2 7f f6 ba 30 34 e0 |g......~.....04.|
00000160 a7 1c da 9b cf f5 5b 11 5a 43 97 47 ad e6 d7 da |......[.ZC.G....|
00000170 28 4a 17 92 58 1a ae a7 c2 29 2c 29 5c c4 fd 41 |(J..X....),)\..A|
00000180 43 99 27 dc a9 5a 06 fd d8 50 36 95 73 42 8a e6 |C.'..Z...P6.sB..|
00000190 a9 9b 66 a6 d4 b6 56 4b 3e b7 a5 cb f4 82 e6 93 |..f...VK>.......|
000001a0 40 e6 f2 0c ed d5 d0 bf be 30 5a 3c c2 18 9f 8d |@........0Z<....|
000001b0 35 f8 d8 26 60 a4 84 d8 3b 22 96 ad 58 6e 62 15 |5..&`...;"..Xnb.|
000001c0 aa 23 66 4b d7 78 63 7a 95 02 41 45 9d 70 47 ef |.#fK.xcz..AE.pG.|
000001d0 54 0e 60 61 9a 18 f9 6e 1e e4 35 55 ef cf d2 33 |T.`a...n..5U...3|
000001e0 92 0f 28 a2 74 81 c1 62 4e 14 a3 6a 4b b1 d7 32 |..(.t..bN..jK..2|
000001f0 95 3a e8 4b 00 61 19 3a 57 a0 a0 a6 a2 85 3d d9 |.:.K.a.:W.....=.|
00000200 47 5f 79 26 c6 21 c3 94 27 c2 9e 57 57 2f 43 1a |G_y&.!..'..WW/C.|
00000210 5e ea da 9b c3 69 29 90 d4 17 63 41 4b 24 cc ed |^....i)...cAK$..|
00000220 37 3c d2 d3 4a 87 29 25 4a 83 03 0f 48 9e 86 80 |7<..J.)%J...H...|
00000230 54 e1 01 df 83 8c 83 49 c1 18 ff a8 22 12 73 da |T......I....".s.|
00000240 e9 d7 d1 76 04 74 f3 eb 6e f8 15 07 92 21 67 28 |...v.t..n....!g(|
00000250 b9 9f 07 71 e5 83 78 1f 27 6e 92 82 48 7b 70 ce |...q..x.'n..H{p.|
00000260 46 d9 e3 6a e2 9c 77 cf 45 e3 5f e5 ab 44 ca 51 |F..j..w.E._..D.Q|
00000270 ea 40 81 4a 85 89 0d 7c ab 4d 09 81 2e d7 7d 3e |.@.J...|.M....}>|
00000280 fc 43 ca ad 9f 4f fc f4 52 cb 4e 40 97 9f e4 3d |.C...O..R.N@...=|
00000290 ef 02 58 d5 |..X.|
00000000 00 00 02 80 62 66 4c ce e6 ed b9 56 ed 11 4a 46 |....bfL....V..JF|
00000010 93 45 c7 16 71 c0 e2 c4 fc bf 06 58 fb 20 3d ca |.E..q......X. =.|
00000020 2f 85 8f 6f da 51 9a 0d 90 3e 0b e0 af d5 00 5d |/..o.Q...>.....]|
00000030 f2 17 db fb 3c e9 4a 93 df a7 f2 a9 e1 d0 65 33 |....<.J.......e3|
00000040 18 f2 45 09 28 4e eb d2 58 22 b7 9e a9 df b4 48 |..E.(N..X".....H|
00000050 8e 60 e0 b3 b6 78 e9 e5 1c 37 26 1a a1 82 4b e8 |.`...x...7&...K.|
00000060 c3 9d 06 69 f5 13 59 18 2a 78 f6 2c 36 f0 96 0a |...i..Y.*x.,6...|
00000070 99 c5 f0 90 de 38 c4 58 b7 0d 42 0f e1 5d 11 2f |.....8.X..B..]./|
00000080 1f 56 da d6 d9 94 86 68 df bb 4b df ce ea 31 86 |.V.....h..K...1.|
00000090 32 cc 7f 50 75 ac 03 f3 48 c1 7b 58 7f db f8 3b |2..Pu...H.{X...;|
000000a0 5e 02 79 9d 59 38 9d 7f 7c ee 24 8b a1 22 92 a0 |^.y.Y8..|.$.."..|
000000b0 a1 52 2a 09 3a a1 b8 fb 3e be 40 63 7d af cf 21 |.R*.:...>.@c}..!|
000000c0 c8 a0 b6 86 94 af d8 bc 21 3b 83 77 55 c4 0a d0 |........!;.wU...|
000000d0 8e ea 4b 19 04 e7 88 2d 6f ff 9f 79 32 8a 7a 7d |..K....-o..y2.z}|
000000e0 20 09 9b 52 05 d7 a2 95 53 0d 7b 57 b0 92 be 0e | ..R....S.{W....|
000000f0 52 c2 0e c2 71 a4 d2 67 f5 80 e9 89 7b 6c d1 ea |R...q..g....{l..|
00000100 7c 83 f1 af 13 3e b6 69 62 e7 10 ed 75 c8 f8 cf ||....>.ib...u...|
00000110 60 07 d8 9a 46 e6 4f b4 22 f2 f7 ca ba ed 36 f9 |`...F.O.".....6.|
00000120 f3 ac a9 e0 f3 e5 37 b0 b0 7d 23 ed b9 7f 1f b3 |......7..}#.....|
00000130 57 38 7c c3 34 b0 78 a5 e8 29 a9 cb 35 17 68 4d |W8|.4.x..)..5.hM|
00000140 dd 8f 35 9c f1 b5 e6 e6 29 00 3b 1c ac 5d d0 cd |..5.....).;..]..|
00000150 1f a0 a8 a1 25 b0 97 53 ed 9a 72 a8 f0 61 0b 66 |....%..S..r..a.f|
00000160 2c 67 c9 e1 dd 86 6a 14 54 00 7b 90 7e 3c d2 5c |,g....j.T.{.~<.\|
00000170 d8 d3 a2 a0 cd c9 b2 82 8f 48 a0 22 49 63 54 6d |.........H."IcTm|
00000180 9f 04 f9 47 27 db 4f 3d 41 82 4a 1a be 4a b7 2f |...G'.O=A.J..J./|
00000190 81 c5 ad 12 cc 1e 45 f8 bb 42 93 ea 4b 24 97 44 |......E..B..K$.D|
000001a0 6b c4 4c 4d f4 61 08 0c a3 37 20 48 d3 be 47 71 |k.LM.a...7 H..Gq|
000001b0 1b b2 8a a1 eb 9f b9 17 61 07 64 15 29 6e fc 93 |........a.d.)n..|
000001c0 7d 21 fd 7d f2 09 6b 9c 5f 05 79 2b dc e0 c6 d4 |}!.}..k._.y+....|
000001d0 03 d4 9e 23 f2 3e 4b 23 11 5e 58 ec a8 5e 96 63 |...#.>K#.^X..^.c|
000001e0 6d 51 22 d5 3e e3 de ee 1a c5 d5 85 c3 c5 c4 05 |mQ".>...........|
000001f0 38 4c bd 54 b4 1e 6a 64 5a 9a 3e 48 82 ec e7 fe |8L.T..jdZ.>H....|
00000200 6a 67 a5 30 c0 fc fd c6 7e d4 d0 68 a7 30 40 e0 |jg.0....~..h.0@.|
00000210 87 c1 5d 75 0b a0 4f 90 f7 46 51 18 8b 02 36 c5 |..]u..O..FQ...6.|
00000220 fb b5 54 bd 32 75 dc 63 84 6f 61 a5 36 fe a6 23 |..T.2u.c.oa.6..#|
00000230 f8 a5 b9 cc af 00 e4 83 c0 11 50 ad 81 2b e2 bd |..........P..+..|
00000240 20 08 85 11 40 5e 66 25 d8 ac 06 a3 a2 10 dc 52 | ...@^f%.......R|
00000250 7a d5 13 61 82 b5 48 83 02 32 7b 20 96 78 40 14 |z..a..H..2{ .x@.|
00000260 38 10 9f 88 25 d3 60 3c 7e e4 11 da a9 6e 1e 58 |8...%.`<~....n.X|
00000270 13 de d8 d4 74 78 b2 5f 8c ce 9c ce 33 5c 1a 6e |....tx._....3\.n|
00000280 76 56 49 a4 54 0c ab 20 b9 c6 20 d4 fa b8 6f 41 |vVI.T.. .. ...oA|
00000290 81 1a 7e f4 |..~.|
>>> Flow 14 (server to client)
00000000 00 00 00 10 bc 56 ff 49 8f 1b c2 79 5b 65 67 84 |.....V.I...y[eg.|
00000010 fb 19 0c 51 ef 4f 2c e8 58 01 84 90 5c 31 78 d4 |...Q.O,.X...\1x.|
00000020 ab 3e cb 21 |.>.!|
00000000 00 00 00 10 6e 70 e8 37 1b 64 e5 98 df 83 52 b2 |....np.7.d....R.|
00000010 40 ca 82 24 96 8c 21 c4 df d2 ec a8 d8 c7 b1 48 |@..$..!........H|
00000020 c2 48 90 90 |.H..|

Some files were not shown because too many files have changed in this diff Show More