Compare commits

..
49 Commits
Author SHA1 Message Date
Xiaolin Zhao 7a4a4d6bea salsa20: add loong64 SIMD implementation
Benchmark results on Loongson-3C6000 @ 2.20GHz:

goos: linux
goarch: loong64
pkg: golang.org/x/crypto/salsa20
cpu: Loongson-3C6000/S @ 2200.00MHz
      |  old.bench   |              new.bench              |
      |    sec/op    |   sec/op     vs base                |
XOR1K   3835.5n ± 0%   493.9n ± 0%  -87.12% (p=0.000 n=10)

      |  old.bench   |               new.bench                |
      |     B/s      |      B/s       vs base                 |
XOR1K   254.6Mi ± 0%   1977.4Mi ± 0%  +676.64% (p=0.000 n=10)

Change-Id: I9f795e7f4e2c467ba9a41c9e6f460612536021ba
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/683255
Reviewed-by: abner chenc <chenguoqi@loongson.cn>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2026-09-21 00:02:45 -07:00
Xiaolin Zhao 1f7c531b64 blake2b: add loong64 SIMD implementation
Benchmark results on Loongson-3C6000 @ 2.20GHz:

goos: linux
goarch: loong64
pkg: golang.org/x/crypto/blake2b
cpu: Loongson-3C6000/S @ 2200.00MHz
         |  old.bench  |              new.bench              |
         |   sec/op    |   sec/op     vs base                |
Write128   350.0n ± 0%   178.3n ± 0%  -49.06% (p=0.000 n=10)
Write1K    2.687µ ± 0%   1.286µ ± 0%  -52.14% (p=0.000 n=10)
Sum128     362.6n ± 0%   183.7n ± 0%  -49.34% (p=0.000 n=10)
Sum1K      2.788µ ± 0%   1.295µ ± 0%  -53.54% (p=0.000 n=10)
geomean    987.4n        483.3n       -51.06%

         |  old.bench   |               new.bench               |
         |     B/s      |     B/s       vs base                 |
Write128   348.8Mi ± 0%   684.8Mi ± 0%   +96.31% (p=0.000 n=10)
Write1K    363.5Mi ± 0%   759.6Mi ± 0%  +108.99% (p=0.000 n=10)
Sum128     336.6Mi ± 0%   664.4Mi ± 0%   +97.37% (p=0.000 n=10)
Sum1K      350.3Mi ± 0%   754.0Mi ± 0%  +115.23% (p=0.000 n=10)
geomean    349.7Mi        714.5Mi       +104.32%

Change-Id: I181a0284c5913cca34f074466f7d83439554e450
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/659775
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: abner chenc <chenguoqi@loongson.cn>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2026-09-19 18:40:00 -07:00
Xiaolin Zhao 234d0b5a5f chacha20: add loong64 SIMD implementation
Add an assembly implementation of ChaCha20 for Loong64 using LSX SIMD
instructions. This significantly improves performance across all message
sizes on LoongArch machines.

Benchmark results on Loongson-3C6000 @ 2.20GHz:

goos: linux
goarch: loong64
pkg: golang.org/x/crypto/chacha20
cpu: Loongson-3C6000/S @ 2200.00MHz
                 │  old.bench   │              new.bench              │
                 │    sec/op    │   sec/op     vs base                │
ChaCha20/64         188.4n ± 0%   156.9n ± 0%  -16.72% (p=0.000 n=10)
ChaCha20/256        666.4n ± 0%   151.5n ± 0%  -77.27% (p=0.000 n=10)
ChaCha20/10x25     1037.0n ± 0%   578.9n ± 0%  -44.18% (p=0.000 n=10)
ChaCha20/4096      10.207µ ± 0%   1.955µ ± 0%  -80.85% (p=0.000 n=10)
ChaCha20/100x40    11.856µ ± 0%   5.785µ ± 0%  -51.21% (p=0.000 n=10)
ChaCha20/65536     162.99µ ± 0%   31.60µ ± 0%  -80.61% (p=0.000 n=10)
ChaCha20/1000x65   165.57µ ± 0%   41.93µ ± 0%  -74.67% (p=0.000 n=10)
geomean             6.369µ        2.141µ       -66.38%

                 │  old.bench   │               new.bench                │
                 │     B/s      │      B/s       vs base                 │
ChaCha20/64        323.9Mi ± 0%    389.0Mi ± 0%   +20.10% (p=0.000 n=10)
ChaCha20/256       366.4Mi ± 0%   1611.2Mi ± 0%  +339.80% (p=0.000 n=10)
ChaCha20/10x25     229.8Mi ± 0%    411.8Mi ± 0%   +79.19% (p=0.000 n=10)
ChaCha20/4096      382.7Mi ± 0%   1998.1Mi ± 0%  +422.08% (p=0.000 n=10)
ChaCha20/100x40    321.8Mi ± 0%    659.4Mi ± 0%  +104.94% (p=0.000 n=10)
ChaCha20/65536     383.5Mi ± 0%   1977.8Mi ± 0%  +415.78% (p=0.000 n=10)
ChaCha20/1000x65   374.4Mi ± 0%   1478.4Mi ± 0%  +294.86% (p=0.000 n=10)
geomean            335.9Mi         999.2Mi       +197.50%

Change-Id: I6b8d243819d0ab1a1d6540648ed9bd00ecad5a2e
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/683196
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: abner chenc <chenguoqi@loongson.cn>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2026-09-19 18:39:20 -07:00
XiaolinZhao a083cb74b6 argon2: add loong64 LSX implementation of blamka
Benchmark results on Loongson-3C6000 @ 2.20GHz:

goos: linux
goarch: loong64
pkg: golang.org/x/crypto/argon2
cpu: Loongson-3C6000/S @ 2200.00MHz
                                             |  old.bench   |              new.bench              |
                                             |    sec/op    |   sec/op     vs base                |
Argon2i/_Time:_3_Memory:_32_MB,_Threads:_1     123.36m ± 0%   76.67m ± 0%  -37.85% (p=0.000 n=10)
Argon2i/_Time:_4_Memory:_32_MB,_Threads:_1      163.7m ± 0%   101.3m ± 0%  -38.09% (p=0.000 n=10)
Argon2i/_Time:_5_Memory:_32_MB,_Threads:_1      195.0m ± 0%   116.5m ± 8%  -40.24% (p=0.000 n=10)
Argon2i/_Time:_3_Memory:_64_MB,_Threads:_4      259.4m ± 0%   165.3m ± 0%  -36.28% (p=0.000 n=10)
Argon2i/_Time:_4_Memory:_64_MB,_Threads:_4      349.9m ± 0%   220.8m ± 0%  -36.92% (p=0.000 n=10)
Argon2i/_Time:_5_Memory:_64_MB,_Threads:_4      437.6m ± 0%   276.0m ± 0%  -36.92% (p=0.000 n=10)
Argon2d/_Time:_3,_Memory:_32_MB,_Threads:_1    116.34m ± 0%   72.07m ± 0%  -38.06% (p=0.000 n=10)
Argon2d/_Time:_4,_Memory:_32_MB,_Threads:_1    154.42m ± 0%   95.28m ± 0%  -38.30% (p=0.000 n=10)
Argon2d/_Time:_5,_Memory:_32_MB,_Threads:_1     192.4m ± 0%   115.9m ± 2%  -39.73% (p=0.000 n=10)
Argon2d/_Time:_3,_Memory:_64_MB,_Threads:_4     254.4m ± 0%   165.2m ± 0%  -35.06% (p=0.000 n=10)
Argon2d/_Time:_4,_Memory:_64_MB,_Threads:_4     345.5m ± 1%   221.0m ± 0%  -36.04% (p=0.000 n=10)
Argon2d/_Time:_5,_Memory:_64_MB,_Threads:_4     432.1m ± 0%   276.2m ± 0%  -36.09% (p=0.000 n=10)
Argon2id/_Time:_3,_Memory:_32_MB,_Threads:_1   116.49m ± 0%   72.38m ± 0%  -37.86% (p=0.000 n=10)
Argon2id/_Time:_4,_Memory:_32_MB,_Threads:_1   154.59m ± 0%   95.71m ± 3%  -38.09% (p=0.000 n=10)
Argon2id/_Time:_5,_Memory:_32_MB,_Threads:_1    192.5m ± 0%   115.0m ± 0%  -40.25% (p=0.000 n=10)
Argon2id/_Time:_3,_Memory:_64_MB,_Threads:_4    255.6m ± 0%   165.7m ± 0%  -35.18% (p=0.000 n=10)
Argon2id/_Time:_4,_Memory:_64_MB,_Threads:_4    345.4m ± 0%   221.5m ± 0%  -35.87% (p=0.000 n=10)
Argon2id/_Time:_5,_Memory:_64_MB,_Threads:_4    431.9m ± 0%   276.6m ± 0%  -35.96% (p=0.000 n=10)
geomean                                         227.7m        142.6m       -37.40%

                                             |  old.bench   |               new.bench               |
                                             |     B/op     |     B/op      vs base                 |
Argon2i/_Time:_3_Memory:_32_MB,_Threads:_1     32.00Mi ± 0%   32.00Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_4_Memory:_32_MB,_Threads:_1     32.00Mi ± 0%   32.00Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_5_Memory:_32_MB,_Threads:_1     32.00Mi ± 0%   32.00Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_3_Memory:_64_MB,_Threads:_4     64.01Mi ± 0%   64.01Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_4_Memory:_64_MB,_Threads:_4     64.01Mi ± 0%   64.01Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_5_Memory:_64_MB,_Threads:_4     64.01Mi ± 0%   64.01Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_3,_Memory:_32_MB,_Threads:_1    32.00Mi ± 0%   32.00Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_4,_Memory:_32_MB,_Threads:_1    32.00Mi ± 0%   32.00Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_5,_Memory:_32_MB,_Threads:_1    32.00Mi ± 0%   32.00Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_3,_Memory:_64_MB,_Threads:_4    64.01Mi ± 0%   64.01Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_4,_Memory:_64_MB,_Threads:_4    64.01Mi ± 0%   64.01Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_5,_Memory:_64_MB,_Threads:_4    64.01Mi ± 0%   64.01Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_3,_Memory:_32_MB,_Threads:_1   32.00Mi ± 0%   32.00Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_4,_Memory:_32_MB,_Threads:_1   32.00Mi ± 0%   32.00Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_5,_Memory:_32_MB,_Threads:_1   32.00Mi ± 0%   32.00Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_3,_Memory:_64_MB,_Threads:_4   64.01Mi ± 0%   64.01Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_4,_Memory:_64_MB,_Threads:_4   64.01Mi ± 0%   64.01Mi ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_5,_Memory:_64_MB,_Threads:_4   64.01Mi ± 0%   64.01Mi ± 0%       ~ (p=1.000 n=10) ¹
geomean                                        45.26Mi        45.26Mi       +0.00%
¹ all samples are equal

                                             | old.bench  |              new.bench              |
                                             | allocs/op  | allocs/op   vs base                 |
Argon2i/_Time:_3_Memory:_32_MB,_Threads:_1     31.00 ± 0%   31.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_4_Memory:_32_MB,_Threads:_1     39.00 ± 0%   39.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_5_Memory:_32_MB,_Threads:_1     47.00 ± 0%   47.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_3_Memory:_64_MB,_Threads:_4     73.00 ± 0%   73.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_4_Memory:_64_MB,_Threads:_4     93.00 ± 0%   93.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2i/_Time:_5_Memory:_64_MB,_Threads:_4     113.0 ± 0%   113.0 ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_3,_Memory:_32_MB,_Threads:_1    31.00 ± 0%   31.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_4,_Memory:_32_MB,_Threads:_1    39.00 ± 0%   39.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_5,_Memory:_32_MB,_Threads:_1    47.00 ± 0%   47.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_3,_Memory:_64_MB,_Threads:_4    73.00 ± 0%   73.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_4,_Memory:_64_MB,_Threads:_4    93.00 ± 0%   93.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2d/_Time:_5,_Memory:_64_MB,_Threads:_4    113.0 ± 0%   113.0 ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_3,_Memory:_32_MB,_Threads:_1   31.00 ± 0%   31.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_4,_Memory:_32_MB,_Threads:_1   39.00 ± 0%   39.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_5,_Memory:_32_MB,_Threads:_1   47.00 ± 0%   47.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_3,_Memory:_64_MB,_Threads:_4   73.00 ± 0%   73.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_4,_Memory:_64_MB,_Threads:_4   93.00 ± 0%   93.00 ± 0%       ~ (p=1.000 n=10) ¹
Argon2id/_Time:_5,_Memory:_64_MB,_Threads:_4   113.0 ± 0%   113.0 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                        59.32        59.32       +0.00%
¹ all samples are equal

Change-Id: Ia814057ddfdefbe1d23431fa366189ffe5852b96
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/826304
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: abner chenc <chenguoqi@loongson.cn>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2026-09-19 18:38:21 -07:00
Daniel McCarney b4dcfb54b8 acme: support standards-compliant order finalization
When an order finalization response is not immediately valid,
Client.CreateOrderCert depends on the ACME server returning a Location
header. The header value is used as the URL for polling the updated
order status. RFC 8555 does not require this header, so CreateOrderCert
does not interoperate with ACME servers that omit it (e.g. Pebble
v2.9.0+).

This commit adds Client.CreateCertFromOrder, which accepts an *Order and
uses its URI for polling instead of relying on the finalization
response's Location header. It also updates existing callers and
documentation to use the new method and updates the integration tests to
Pebble v2.10.1 (the latest available version), which omits the
finalization Location header.

Fixes golang/go#77704

Change-Id: I0852f19c5d18d6debc9c9686fa2c14a2e39663c2
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/817000
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2026-09-18 12:05:15 -07:00
Gopher Robot b853e4b620 x509roots/fallback: update bundle
This is an automated CL which updates the NSS root bundle.

[git-generate]
go generate ./x509roots

Change-Id: I975f934aabe6eaf6b9f256db2b6942c45c9bd7b4
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/833664
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2026-09-16 08:43:51 -07:00
Gopher Robot 3f62bf119e go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I39f495ef42016b2ab0c91f54b792ac16b967006f
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/829324
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2026-09-08 11:05:01 -07:00
Nicola Murino 86efde54dc ssh: reject unexpected message types on established channels
ch.msg is only read while the channel open or a channel request with a
reply is pending, so anything the default arm of channel.handlePacket
delivered to it was never consumed. The blocking send there let a
misbehaving peer fill the buffer with well-formed but unexpected message
types carrying a valid channel id and stall the mux read loop,
deadlocking the whole connection.

No conforming peer sends such messages during the connection protocol.
Treat them as a protocol error and tear the connection down, as
handleUnknownChannelPacket already does for the same messages when the
channel id is not in use.

Fixes CVE-2026-56855
Fixes golang/go#81317

Change-Id: I87420dfe68fcb62a17df4b47dc5ffb6ccd72ba26
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/826524
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Neal Patel <nealpatel@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
2026-09-02 11:02:47 -07:00
Nicola Murino a6cdac6084 ssh: drop traffic on undecided channels
A channel in the mux's chanList is not usable until it is established:
an outbound channel has no confirmed remote id until the peer's open
confirmation, and an inbound channel is not serviced by the application
until it is accepted. handlePacket processed any channel message on it,
so a misbehaving peer could flood channel requests and block the mux
read loop on the send to incomingRequests, deadlocking the connection,
or close an outbound channel before confirming it, making the victim
tear down a half-initialized channel and emit a close for remote id 0,
an unrelated channel of the peer.

No such packet can be legitimate: the peer learns an inbound channel's
local id only from the confirmation we have not sent yet, and on an
outbound channel RFC 4254 lets it answer the open request only with a
confirmation or a failure.

Add an established flag, set when the channel becomes usable: for an
outbound channel when the open response is received, for an inbound
channel by Accept before the confirmation is sent. Until then
handlePacket drops every packet other than the open response. The flag
is separate from decided, which Reject also sets: a rejected channel is
decided but must never carry traffic.

Fixes CVE-2026-78662
Fixes golang/go#81316

Change-Id: Ib0983bb216a49808a2db1f4a4d92ee9fe38a3c51
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/826504
Auto-Submit: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
2026-09-02 10:48:31 -07:00
Nicola Murino 39dc44e69c ssh: don't skip the source-address critical option in CheckCert
CertChecker.CheckCert ignored source-address on the assumption that
serverAuthenticate would enforce it, but that only happens in the
server-side user authentication path. Nothing enforced it in the host key
path, so CheckHostKey accepted CA-signed host certificates carrying
source-address regardless of the server's address, and the same applied to
applications calling CheckCert directly.

Drop the special case: every critical option must be listed in
SupportedCriticalOptions, host and user certificates alike. Authenticate,
the only path where serverAuthenticate does enforce source-address, checks
against a copy of the CertChecker with that option appended.

Fixes golang/go#80872

Change-Id: I3c3554b71ea2a4ce4b17696a9596b973b7d74b00
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/816840
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
2026-08-30 20:04:51 -07:00
Dmitri Shuralyov afebf4cb4e x509roots/fallback/bundle: make subjectsEqual stricter on Go 1.27+
This gradual step towards removing/inlining subjectsEqual can happen
after Go 1.27.0 is released and the bundle starts being generated
with it.

Change-Id: Ib315bae69c45922c3f0aac5d2c0155fb5d94229f
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/775782
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
2026-08-26 07:40:58 -07:00
Gopher Robot 89f4e9bb5b x509roots/fallback: update bundle
This is an automated CL which updates the NSS root bundle.

[git-generate]
go generate ./x509roots

Change-Id: I3be04cd4cb6e44856beaa074fdb62a58ff8ce35b
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/820300
Auto-Submit: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2026-08-24 13:54:08 -07:00
Nicola Murino 71488c48c2 ssh/knownhosts: compare only public key portions for revocation
Revocation matching compared full marshaled key blobs, so a @revoked
line containing a plain host key did not revoke a certificate
certifying that key: when the server presented such a certificate,
IsRevoked only matched the whole certificate blob or the CA key, and
verification succeeded even though the underlying host key was revoked.

OpenSSH's check_key_not_revoked compares keys with sshkey_equal_public,
which considers only the public portions and explicitly allows
comparisons between certificates and plain keys. Match that behavior by
normalizing both @revoked entries and lookups to the underlying public
key: for a certificate, the certified key rather than the certificate
blob. This also makes a @revoked line containing a certificate revoke
the plain key it certifies

Change-Id: I3029d59e68fb01f2340763e2eae25cac23c6193d
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/816841
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2026-08-23 06:53:19 -07:00
Daniel McCarney 82adefa711 ssh: synchronize unexpected response test
Wait for an ordered channel request after sending the unsolicited
response flood. This ensures that the client mux has discarded the
entire flood before SendRequest opens its response gate, avoiding
confusion between an in-flight unsolicited response and the expected
reply.

This fixes TestChannelUnexpectedResponsesDiscarded from flaking, where
otherwise running 1000000 iterations of the test with GOMAXPROCS=16
produced ~15 distinct failures of the form  "expected success reply".

Change-Id: Ic1125c1f44bd919a5cf32b889d331bbc94c06c25
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/817140
Reviewed-by: Neal Patel <neal@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: David Chase <drchase@google.com>
2026-08-19 13:42:46 -07:00
Gopher Robot c757c9851f all: upgrade go directive to at least 1.26.0 [generated]
By now Go 1.27.0 has been released, and Go 1.25 is no longer supported
per the Go Release Policy (see https://go.dev/doc/devel/release#policy).

See https://go.dev/doc/godebug#go-126 for GODEBUG setting changes
relevant to Go 1.26.

For golang/go#69095.

[git-generate]
(cd . && go get go@1.26.0 && go mod tidy)
(cd x509roots/fallback && go get go@1.26.0 && go mod tidy)

Change-Id: I54826e019edd4e12ad821915b1d102de719bfb31
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/817981
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Gopher Robot <gobot@golang.org>
2026-08-19 10:16:24 -07:00
Damien Miller 593c81af8a ssh: correctly ignore pre-banner lines
Reading the peer's benner in readVersion() was incorrectly counting
lines sent by the server before the actual `SSH-2.0` banner against
the line limit for the banner itself.

This is incorrect per RFC 4253 section 4.2, where the limit of 255
characters is written to apply only to the `SSH-` banner string
itself.

This instead limits pre-banner lines to 255 characters each but
instead allows a maximum of 1024 lines.

Change-Id: Ief014bb3413e26e8d2077d3eb204ceaea9dc9444
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/811320
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2026-08-17 08:34:06 -07:00
Daniel McCarney 46efc8bc62 acme: add crypto.SignMessage test coverage
This commit updates the pre-existing TestJWSEncodeJSONCustom test to
also verify JWS signature production works with a crypto.MessageSigner.

Change-Id: I5156cb4da9a7feee98340ff4fb75848da5827055
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/815480
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2026-08-17 08:22:44 -07:00
Daniel Tang e0a54fdc4b acme: use crypto.SignMessage instead of calling (crypto.Signer).Sign
crypto.SignMessage allows using crypto.Signers that only usefully
implement SignMessage to be used for the ACME account key.

This would be useful for opening up integrations with opinionated
remote signing APIs that don't support pre-hashing.

Fixes golang/go#80877

Change-Id: I86bb76cf5865d3d2b8606a6dbd813cbb26447c86
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/815340
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Sean Liao <sean@liao.dev>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2026-08-17 08:21:42 -07:00
Gopher Robot f44d03d253 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I6e7a078f1a8ffe71bace5615ad4fc03a12466985
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/813280
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
2026-08-11 10:56:31 -07:00
Julian Zhu 5ed494470b crypto/internal/poly1305: provide optimised assembly for riscv64
goos: linux
goarch: riscv64
pkg: golang.org/x/crypto/internal/poly1305
                   │ sum1305-old  │            sum1305-new              │
                   │    sec/op    │   sec/op     vs base                │
64-4                  195.9n ± 1%   144.8n ± 0%  -26.07% (p=0.000 n=10)
1K-4                  1.853µ ± 0%   1.012µ ± 0%  -45.39% (p=0.000 n=10)
2M-4                  3.627m ± 0%   1.880m ± 0%  -48.17% (p=0.000 n=10)
64Unaligned-4         196.4n ± 1%   174.7n ± 0%  -11.05% (p=0.000 n=10)
1KUnaligned-4         1.853µ ± 0%   1.491µ ± 0%  -19.54% (p=0.000 n=10)
2MUnaligned-4         3.643m ± 0%   2.850m ± 0%  -21.78% (p=0.000 n=10)
Write64-4            126.85n ± 0%   77.40n ± 0%  -38.98% (p=0.000 n=10)
Write1K-4            1784.5n ± 1%   937.2n ± 0%  -47.48% (p=0.000 n=10)
Write2M-4             3.654m ± 2%   1.880m ± 0%  -48.55% (p=0.000 n=10)
Write64Unaligned-4    125.7n ± 0%   107.5n ± 0%  -14.48% (p=0.000 n=10)
Write1KUnaligned-4    1.782µ ± 0%   1.424µ ± 0%  -20.09% (p=0.000 n=10)
Write2MUnaligned-4    3.637m ± 0%   2.850m ± 0%  -21.64% (p=0.000 n=10)
geomean               10.14µ        6.925µ       -31.68%

                   │ sum1305-old  │             sum1305-new-3             │
                   │     B/s      │      B/s       vs base                │
64-4                 311.7Mi ± 1%    421.5Mi ± 0%  +35.25% (p=0.000 n=10)
1K-4                 527.0Mi ± 0%    965.0Mi ± 0%  +83.10% (p=0.000 n=10)
2M-4                 551.4Mi ± 0%   1063.8Mi ± 0%  +92.94% (p=0.000 n=10)
64Unaligned-4        310.8Mi ± 1%    349.3Mi ± 0%  +12.38% (p=0.000 n=10)
1KUnaligned-4        526.9Mi ± 0%    654.9Mi ± 0%  +24.30% (p=0.000 n=10)
2MUnaligned-4        548.9Mi ± 0%    701.8Mi ± 0%  +27.85% (p=0.000 n=10)
Write64-4            481.2Mi ± 0%    788.6Mi ± 0%  +63.89% (p=0.000 n=10)
Write1K-4            547.2Mi ± 1%   1042.0Mi ± 0%  +90.42% (p=0.000 n=10)
Write2M-4            547.4Mi ± 2%   1064.0Mi ± 0%  +94.38% (p=0.000 n=10)
Write64Unaligned-4   485.5Mi ± 0%    567.7Mi ± 0%  +16.94% (p=0.000 n=10)
Write1KUnaligned-4   547.9Mi ± 0%    685.8Mi ± 0%  +25.17% (p=0.000 n=10)
Write2MUnaligned-4   550.0Mi ± 0%    701.9Mi ± 0%  +27.62% (p=0.000 n=10)
geomean              485.5Mi         710.7Mi       +46.37%

Change-Id: I33a3df03382eb42ef062b0366521f02559290482
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/738760
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Meng Zhuo <mengzhuo1203@gmail.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
2026-08-03 22:01:12 -07:00
Nicola Murino b07833c067 ssh: return window credit for discarded extended data
handleData subtracts the length of every data packet from the receive
window, but extended data with a type code other than
SSH2_EXTENDED_DATA_STDERR is then silently discarded and, since no API
exists to read it, its window credit was never returned: adjustWindow
only runs as a side effect of reading a stream. A peer sending
extended data with an unknown type code could therefore permanently
shrink the channel window, down to a complete stall of the channel.

Credit the discarded bytes back through adjustWindow. adjustWindow
returns io.EOF if the local side has already sent a channel close;
ignore it like ReadExtended does, since an error returned here would
terminate the mux read loop and tear down the whole connection. For
comparison, OpenSSH rejects extended data with unexpected type codes
outright and never buffers it, so the dropped bytes are simply never
credited back to the misbehaving peer.

Fixes golang/go#80333

Change-Id: Ia311ce3ad008e7c89ce9752f1937c91374a4e74f
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/802901
Reviewed-by: Mark Freeman <markfreeman@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2026-08-03 14:00:09 -07:00
Brad Fitzpatrick d701c51f7e acme: fix nil pointer dereference in pebble test error reporting
The WaitOrder failure path used errors.Is with a nil *OrderError
target, which never matches and left orderErr nil at the point its
Problem field was dereferenced. Use errors.As to populate the target
instead.

Found by govet's nilness check.

Change-Id: Ieef849fcbf04c85ef32651059db2b5fe162af325
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/803181
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2026-07-23 08:25:44 -07:00
Brad Fitzpatrick 999d053994 ssh: fix parsing of GSSAPI payloads offering multiple mechanisms
parseGSSAPIPayload overwrote the remaining payload with the trailing
bytes returned by asn1.Unmarshal after decoding each OID, so any
request offering more than one mechanism failed to parse after the
first one. Track the remaining payload across iterations instead, and
be strict: reject payloads with trailing bytes after an OID or after
the mechanism list.

Add a test with a two-mechanism payload (Kerberos V5 followed by
SPNEGO) that failed before this change.

Change-Id: If0984efcf103da76323d5d03ad7443d8ec9d3aed
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/803180
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2026-07-23 08:19:57 -07:00
Nicola Murino 90f76b8ffe ssh: reject certificate signature keys before recursing
parseCert parsed a certificate's SignatureKey by calling ParsePublicKey
again, and only afterwards rejected a certificate used as a signing key.
For a chain of nested certificates this recursed once per level, so a key
with enough nesting exhausted the goroutine stack and crashed with a fatal
stack overflow that recover cannot catch.

Reject the signature key by its declared algorithm before parsing it, which
bounds parsing.

Fixes golang/go#80414

Change-Id: Id51401aae15bfbec006c03ab97635a3b68bd6058
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/802145
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2026-07-22 10:37:06 -07:00
database64128 b53964a1ca ssh: permit empty but non-nil HostKeyAlgorithms, KeyExchanges, Ciphers, MACs
The documentation of ClientConfig.HostKeyAlgorithms says:

  If empty, a reasonable default is used.

However, if HostKeyAlgorithms is empty but non-nil, the reasonable
default won't be applied, and the SSH handshake will fail. Fix this by
changing the nil check to a length check.

While here, do the same for KeyExchanges, Ciphers, MACs in Config.

Change-Id: I1b3d2d2159f3c7d57a9a690ad04f1f6818b39a5c
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/800080
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
2026-07-22 10:37:01 -07:00
Nicola Murino 626e40fc98 ssh: drain stderr on forwarded TCP and Unix channels
tcpListener.Accept, unixListener.Accept, Client.dial and
Client.dialStreamLocal only read the main stream of the channels they
return. Data sent by the peer on the extended (stderr) stream
accumulates in the extPending buffer and its window credit is only
returned by ReadExtended, which is never called. Since the window is
shared between the two streams, a misbehaving peer can pin up to 2 MiB
per channel and eventually stall it; well-behaved peers never send
stderr on these channel types.

Drain the stderr stream into io.Discard, mirroring CL 783720 which
fixed the same issue in the agent forwarders.

The drain takes precedence over reading Stderr through a type assertion
on the returned net.Conn, which was never a supported use. For
comparison, OpenSSH refuses extended data on these channel types and
drops the payload without buffering it.

Updates golang/go#80333

Change-Id: I4f1445eee9ce1b56ca62cec342812d63cfdd3e13
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/802900
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2026-07-22 10:34:36 -07:00
Gopher Robot 31914c699b x509roots/fallback: update bundle
This is an automated CL which updates the NSS root bundle.

[git-generate]
go generate ./x509roots

Change-Id: Ic89f2ab75500d9b03a76236f2228657508a4d16d
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/803060
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Gopher Robot <gobot@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2026-07-21 14:38:23 -07:00
Brad Fitzpatrick f2135b814c all: clean up minor issues found by staticcheck
Use bytes.Equal instead of comparing bytes.Compare results to zero,
avoid Yoda conditions, drop a redundant zero slice capacity, use
strings.ReplaceAll, build acme error strings with a strings.Builder,
and lowercase an error string in ocsp. No behavior changes.

Change-Id: I5d2b3fa5c241adc75e61be0f2d2d7af1eb9e8d66
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/803200
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2026-07-21 14:30:59 -07:00
Akira ff03dafdb0 ssh: report verified signature format to callback
VerifiedPublicKeyCallback currently receives the public-key algorithm
declared in the outer authentication request. For RSA authentication,
that value can differ from the format of the signature that was
successfully verified because compatible RSA algorithm combinations are
accepted.

This can cause post-verification policy or audit decisions to use the
request algorithm instead of the signature format actually verified.

Pass sig.Format to VerifiedPublicKeyCallback so signatureAlgorithm
identifies the successfully verified signature format. Clarify the
callback documentation and add a regression test covering compatible
but different RSA request algorithms and signature formats.

Fixes golang/go#80411

Change-Id: Ib405378d75367a90536e0814c9d26d4dec9aaa25
GitHub-Last-Rev: 1a4acd4e639361be1f2948a4aef1013436a75ecf
GitHub-Pull-Request: golang/crypto#364
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/800740
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: David Chase <drchase@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2026-07-17 15:41:46 -07:00
Nicola Murino 10b54ffa51 ssh: raise the RSA modulus limit to 16384 bits
the fix for CVE-2026-39829 capped the accepted RSA modulus at 8192 bits,
matching crypto/tls, to bound the CPU cost of verifying an
attacker-supplied key. That rejected keys OpenSSH generates: ssh-keygen
allows -b up to 16384. Raise the limit to 16384 so those keys interoperate,
both in the public key parser and the OpenSSH private key parser, and
correspondingly raise the prime-factor bound to 8192 bits (each prime is
about half the modulus). The agent's checkRSAKeyParams is updated to match.

Verifying a 16384-bit key costs roughly 3x an 8192-bit one.

Fixes golang/go#80075

Change-Id: Ib08ddd7b48552e692c48dbdf105bbc8579120bbc
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/795421
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
2026-07-13 20:33:21 -07:00
Nicholas S. Husin 0f039f3a4a ssh: sanitize various error messages
Similar to CL 780180, sanitize other error messages that are controlled
by the remote peer, to avoid potential injection of control characters.

Also remove sanitizeString usage as it is not strictly needed to escape
error messages safely, and for consistency with how we sanitize other
error messages throughout the codebase.

BannerDisplayStderr is left unsanitized for now, as it is one instance
where we might actually want control characters for valid reasons (see
golang/go#80302).

Updates golang/go#80302
Fixes golang/go#80301

Change-Id: Ie40f4ac2f76afed740dceb548a4b0fa96a6a6964
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/798420
Auto-Submit: Nicholas Husin <nsh@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Nicholas Husin <husin@google.com>
2026-07-13 12:23:17 -07:00
Nicola Murino b27e1504e3 ssh: don't close the connection on an unparseable public key
In the server's publickey authentication path, a public key blob that
ParsePublicKey could not decode caused serverAuthenticate to return an
error, tearing down the whole connection. A key we cannot parse is part
of an individual authentication attempt, not a transport-level framing
error, so it must fail only that attempt. This lets the client fall
back to other keys or authentication methods.

Updates golang/go#80075

Change-Id: I8d882d08bb50eb33e0054d9d9c2bd9e0b3039443
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/795420
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
2026-07-13 12:20:29 -07:00
Nicola Murino c1a3b97d70 ssh: document and test source-address behavior for non-IP transports
The source-address critical option is a comma-separated list of IP
addresses and CIDR blocks, so it can never match a connection whose
remote address is not IP-based, such as a Unix domain socket.

Change-Id: I45e09fe5c8c13922cb0735278acf33320857322c
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/797042
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2026-07-12 08:19:47 -07:00
Nicola Murino b9229ecc70 ssh: reject a present but empty source-address critical option
checkSourceAddressCriticalOption treated a source-address critical
option with an empty value as if the option was absent, allowing
authentication from any address. A callback (or certificate authority)
building the value dynamically from an empty allowlist would intend to
deny all sources, not to lift the restriction. This also contradicts
the documented contract that the option is validated whenever it is
present, and differs from OpenSSH, where an empty address list matches
nothing.

Change-Id: I1c0de27cbcab8817a7ff446019c25cd19957a7d7
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/797041
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
2026-07-12 08:19:43 -07:00
Nicola Murino e557b08ec2 ssh: enforce the source-address critical option for all auth callbacks
CVE-2026-46595 extended source-address validation, historically applied
only to the Permissions returned by PublicKeyCallback, to the ones
returned by VerifiedPublicKeyCallback. The documented contract of
Permissions.CriticalOptions does not restrict enforcement to a specific
authentication method, so move the check to a single point at the end of
each authentication attempt, where it covers the Permissions returned by
any callback (password, keyboard-interactive, none and GSSAPI included).

The check at public key cache insertion time is kept: it remains
authoritative for the Permissions returned by PublicKeyCallback, which
VerifiedPublicKeyCallback may replace before the check at the end of the
authentication attempt runs and which are not re-checked on partial
success. It also still makes public key queries fail before the client
produces a signature when PublicKeyCallback supplies the restriction.

Fixes CVE-2026-56854
Fixes golang/go#80213

Change-Id: Ibd612a8e4240bd710e33754f3ceb95bb29169d9a
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/797040
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
2026-07-12 08:19:37 -07:00
Nicola Murino 4044611da8 ssh: fix data race in the doClientServerAuth test helper
The helper returned the auth errors collected via AuthLogCallback
without waiting for the server goroutine that appends to them. Close
the client side of the connection on handshake failure, to unblock a
server still reading from it, and join the server goroutine before
returning.

Change-Id: Ibe8c3b236491769f3433f7457d4f2b9a07a65be0
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/797043
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
2026-07-12 08:19:29 -07:00
Nicola Murino e8ad5cb7aa ssh: initialize new channels fully before adding them to chanList
newChannel published the channel into the mux's chanList (via
chanList.add) before assigning its localId, and openChannel/Accept set
maxIncomingPayload only after newChannel returned. For an outbound
channel, whose newChannel runs on an application goroutine, the mux
loop could observe the channel through getChan before those fields
were initialized, reading a zero localId or maxIncomingPayload with no
happens-before relationship.

Assign localId inside add while the chanList lock is held, and set
maxIncomingPayload (always channelMaxPacket) in newChannel itself, so
a channel returned by getChan is always fully initialized.

Change-Id: Ib54a5d10dcb5c1ad8ed4dabe5d8d2790778be298
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/797061
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2026-07-12 08:19:12 -07:00
Nicola Murino fe1af3a418 ssh: make channel.close idempotent
channel.close closes the internal Go channels (msg, incomingRequests);
calling it twice panics with "close of closed channel". Both current
callers run on the mux loop and are serialized, so the panic is not
reachable today. Harden close by guarding it with sync.Once, so that
the connection does not become fragile to any future bug that reaches
close twice.

Change-Id: I5d8d948367538ea5561724033c4ee4daa668e221
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/797060
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2026-07-12 08:19:06 -07:00
Daniel McCarney 243e02a382 ocsp: update ParseRequest docs & error conditions
ParseRequest was documented as erroring for inputs that contained
an optional request signature, and as only supporting requests for
a single certificate.

However, prior to this commit DER inputs that contained a signature
succeeded without verifying the signature, and inputs that contained
multiple certificate identifiers succeeded but only the first
certificate identifier was unmarshalled.

This commit updates the function behaviour to return an error for the
signature case as described in the docs, and to clarify the
documentation for inputs with multiple certificate identifiers.

Fixes golang/go#80300

Change-Id: I7446422d6c98bd406b8c43e55fea9a16ace9d4a6
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/798220
Reviewed-by: Roland Shoemaker <roland@golang.org>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
2026-07-09 11:40:58 -07:00
Gopher Robot cdce021fa6 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I431b878ec8830bf1894ae19176b5984b00954557
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/798280
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
2026-07-08 11:22:26 -07:00
Roland Shoemaker d9474cc485 openpgp: make the deprecation message more explicit
The package is unmaintained, unsafe at any speed, and does not receive
security fixes. Document that, and point at the ProtonMail fork for
those that _must_ still use OpenPGP.

Change-Id: Ia602eaaf9e08bd25389400ebe7f346b60bdb9d99
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/794200
Auto-Submit: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
2026-07-07 10:06:58 -07:00
Nicola Murino 7626c50256 ssh: verify declared key type matches decoded key in authorized_keys
ParseAuthorizedKey and ParseKnownHosts previously ignored the key type
field (e.g. "ssh-rsa") in each entry, relying solely on the type
information embedded within the base64-encoded key blob.

For ParseAuthorizedKey this also caused a single-token option to be
silently dropped: a line such as "restrict <key>" (with the key type
omitted) was parsed as an unrestricted key, because the option token
landed in the key type position and was discarded together with its
meaning. The same happens for no-pty, no-port-forwarding and the other
single-word options. OpenSSH's sshkey_read rejects such lines outright,
as the option token cannot be read as a key type.

OpenSSH's sshkey_read also explicitly verifies that the key type
declared in the text matches the type of the parsed key, returning
SSH_ERR_KEY_TYPE_MISMATCH if they differ.

This change adds, in both functions, a check that the declared key type
matches the decoded key's type, returning an error for malformed lines
where they diverge. This mirrors the fix already applied to the
ssh/knownhosts package in CL 782427.

Change-Id: I9163108d964de4be8a415fe0efd68e6b71f77990
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/792840
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2026-06-30 10:24:32 -07:00
Nicola Murino 0471e7969e ssh/agent: enforce strict limits on DSA key parameters
The parseDSAKey function constructed a *dsa.PrivateKey directly from the
add-identity request without validating the key parameters. Unlike DSA
certificates, whose parameters are checked by the ssh package when the
certificate's public key is parsed, raw DSA keys added to the agent were
not validated at all.

Align the raw DSA key parsing with the validation already performed by
the main ssh package.

Fixes golang/go#79725

Change-Id: I537cc2175d35c19848c90c68739cf94ba7b50e10
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/795422
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2026-06-30 10:06:28 -07:00
Neal Patel 6435c37ab6 ssh: sanitize client disconnect messages
A malicious peer can send control characters
and escape sequences in SSH_MSG_DISCONNECT;
strip these characters and %q escape it.

Change-Id: I266f78630a7a816a79ce4dda568ce57d7c3f6fb0
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/780180
Commit-Queue: Nicola Murino <nicola.murino@gmail.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-by: Nicola Murino <nicola.murino@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Nicola Murino <nicola.murino@gmail.com>
2026-06-29 10:23:17 -07:00
Nicola Murino 7d695da948 ssh/agent: drain channel stderr in agent forwarders
ForwardToAgent and ForwardToRemote only read the main stream of the
auth-agent@openssh.com channels they accept. If a peer sends data on
the channel's extended (stderr) stream the bytes accumulate in the
client-side extPending buffer and the receive window is never
replenished, because the window is only adjusted as a side effect of
ReadExtended. That can pin up to channelWindowSize (2 MiB) of memory
per channel and silently stalls any stderr traffic once the window is
exhausted.

The auth-agent protocol does not use stderr, so a well-behaved peer
never sends anything on it. To stay tolerant of misbehaving peers
without leaving the channel half-stuck, drain the stderr stream into
io.Discard, mirroring the existing DiscardRequests pattern. The
goroutine exits when the channel is closed because Stderr().Read
returns io.EOF.

Add a regression test that opens an agent-forwarding channel and
writes more than the default window on the stderr stream from the
server side. Without the fix the write blocks once the remote window
is exhausted; with the fix the bytes are drained and the agent stream
remains usable.

Change-Id: Iadf8ea6ca726c058421bbc39f92e0100579fda17
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/783720
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2026-06-29 06:36:26 -07:00
Daniel McCarney 5b7f841599 acme/autocert: fix data race in Manager.createCert
Previously Manager.createCert() read state.locked without holding any
lock, while the owner goroutine wrote state.locked = false under the
state's write lock. Concurrent goroutines for the same domain raced on
that field.

We fix the issue by removing the certState.locked field and instead
having Manager.certState() return an additional owner bool computed
under the m.stateMu lock. This allows the Manager.createCert() caller to
know whether it must block on the read lock and return the state handled
by another goroutine, or instead do the work itself and release the
write lock.

A regression unit test is included that fails under -race without the
fix, and passes with the fix in-place.

Fixes golang/go#80119

Change-Id: I0f4c5c98f7e6bcf1ab71a3c0707bb0c7ce73415b
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/793840
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
2026-06-26 08:59:20 -07:00
harjoth 0b316e7ee4 argon2: update RFC 9106 parameter recommendations
Update the IDKey example to use RFC 9106's first recommended Argon2id
option and clarify the relationship between the first and second
recommended options.

Fixes golang/go#79823

Change-Id: If44a86a5a8112b02fab97b4d5e325fe91d6049ee
GitHub-Last-Rev: a764ae65875f12d7422865c4e83f345a8a4f246e
GitHub-Pull-Request: golang/crypto#357
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/789020
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2026-06-18 09:22:19 -07:00
Gopher Robot 55aec0a86b x509roots/fallback: update bundle
This is an automated CL which updates the NSS root bundle.

[git-generate]
go generate ./x509roots

Change-Id: I448f9962a67bf042b04a2e34f16ae02a004d6edd
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/791040
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2026-06-15 14:38:55 -07:00
Daniel McCarney 5f2de1a9f1 internal: remove wycheproof tests
Equivalent coverage using newer Wycheproof vectors has been added to the
standard library, alongside the implementations under test.

In the case where the algorithm doesn't exist in the stdlib, but is
intended to be supported there one day (e.g. chacha20poly1305) we flip
the relationship and run the Wycheproof tests from the stdlib on the
vendored /x/crypto. As a result the legacy Wycheproof coverage in this
repo can be removed.

Change-Id: Ic07d559e42599b715906f5bcc39be10944a8dde4
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/758420
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Roland Shoemaker <roland@golang.org>
2026-06-09 11:23:32 -07:00
97 changed files with 3791 additions and 2256 deletions
+1 -1
View File
@@ -221,7 +221,7 @@ func (c *Client) directoryURL() string {
// CreateCert was part of the old version of ACME. It is incompatible with RFC 8555.
//
// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert.
// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateCertFromOrder.
func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) {
return nil, "", errPreRFC
}
+1 -1
View File
@@ -734,7 +734,7 @@ func TestTLSALPN01ChallengeCert(t *testing.T) {
if !acmeExts[0].Critical {
t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical)
}
if bytes.Compare(acmeExts[0].Value, extValue) != 0 {
if !bytes.Equal(acmeExts[0].Value, extValue) {
t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue)
}
+22 -24
View File
@@ -564,24 +564,21 @@ func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
// If the domain is already being verified, it waits for the existing verification to complete.
// Either way, createCert blocks for the duration of the whole process.
func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate, error) {
// TODO: maybe rewrite this whole piece using sync.Once
state, err := m.certState(ck)
state, owner, err := m.certState(ck)
if err != nil {
return nil, err
}
// state may exist if another goroutine is already working on it
// in which case just wait for it to finish
if !state.locked {
// If another goroutine is already working on this state, wait for it
// to finish by taking the read lock
if !owner {
state.RLock()
defer state.RUnlock()
return state.tlscert()
}
// We are the first; state is locked.
// Unblock the readers when domain ownership is verified
// and we got the cert or the process failed.
// We are the first to work on this certKey, so state is write-locked.
// Unblock the readers when our work is complete.
defer state.Unlock()
state.locked = false
der, leaf, err := m.authorizedCert(ctx, state.key, ck)
if err != nil {
@@ -611,10 +608,15 @@ func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate,
return state.tlscert()
}
// certState returns a new or existing certState.
// If a new certState is returned, state.exist is false and the state is locked.
// certState returns a new or existing certState along with a boolean
// indicating whether the caller is the owner of the state.
//
// The owner of the state is responsible for performing the ACME work and
// must unlock the state's write lock when done. Non-owner callers should
// wait on the state's read lock for the owner to finish.
//
// The returned error is non-nil only in the case where a new state could not be created.
func (m *Manager) certState(ck certKey) (*certState, error) {
func (m *Manager) certState(ck certKey) (*certState, bool, error) {
m.stateMu.Lock()
defer m.stateMu.Unlock()
if m.state == nil {
@@ -622,7 +624,7 @@ func (m *Manager) certState(ck certKey) (*certState, error) {
}
// existing state
if state, ok := m.state[ck]; ok {
return state, nil
return state, false, nil
}
// new locked state
@@ -636,16 +638,13 @@ func (m *Manager) certState(ck certKey) (*certState, error) {
key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}
if err != nil {
return nil, err
return nil, false, err
}
state := &certState{
key: key,
locked: true,
}
state := &certState{key: key}
state.Lock() // will be unlocked by m.certState caller
m.state[ck] = state
return state, nil
return state, true, nil
}
// authorizedCert starts the domain ownership verification process and requests a new cert upon success.
@@ -672,7 +671,7 @@ func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck cert
if err != nil {
return nil, nil, err
}
chain, _, err := client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
chain, _, err := client.CreateCertFromOrder(ctx, o, csr, true)
if err != nil {
return nil, nil, err
}
@@ -1035,10 +1034,9 @@ func (m *Manager) now() time.Time {
// certState is ready when its mutex is unlocked for reading.
type certState struct {
sync.RWMutex
locked bool // locked for read/write
key crypto.Signer // private key for cert
cert [][]byte // DER encoding
leaf *x509.Certificate // parsed cert[0]; always non-nil if cert != nil
key crypto.Signer // private key for cert
cert [][]byte // DER encoding
leaf *x509.Certificate // parsed cert[0]; always non-nil if cert != nil
}
// tlscert creates a tls.Certificate from s.key and s.cert.
+27
View File
@@ -513,6 +513,33 @@ func TestGetCertificate_failedAttempt(t *testing.T) {
}
}
// TestGetCertificate_concurrent guards against a data race on certState
// fields between the goroutine performing the ACME work for a domain and
// the other goroutines waiting on the result. See golang/go#80119.
func TestGetCertificate_concurrent(t *testing.T) {
// An unreachable directory URL makes the owner goroutine fail
// quickly inside createCert, so its writes overlap with the
// non-owner goroutines reaching the same code path.
m := &Manager{
Prompt: AcceptTOS,
Client: &acme.Client{DirectoryURL: "http://127.0.0.1:1/"},
}
defer m.stopRenew()
start := make(chan struct{})
var wg sync.WaitGroup
for i := 0; i < 64; i++ {
wg.Add(1)
go func() {
defer wg.Done()
<-start
m.GetCertificate(clientHelloInfo(exampleDomain, algECDSA))
}()
}
close(start)
wg.Wait()
}
func TestRevokeFailedAuthz(t *testing.T) {
ca := acmetest.NewCAServer(t)
// Make the authz unfulfillable on the client side, so it will be left
+4 -4
View File
@@ -182,9 +182,9 @@ func (p *prober) runOrder(ctx context.Context, identifiers []acme.AuthzID) {
log.Fatalf("WaitOrder(%q): %v", o.URI, err)
}
csr, certkey := newCSR(identifiers)
der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
der, curl, err := p.client.CreateCertFromOrder(ctx, o, csr, true)
if err != nil {
log.Fatalf("CreateOrderCert: %v", err)
log.Fatalf("CreateCertFromOrder: %v", err)
}
log.Printf("cert URL: %s", curl)
if err := checkCert(der, identifiers); err != nil {
@@ -247,9 +247,9 @@ func (p *prober) runPreauthz(ctx context.Context, identifiers []acme.AuthzID) {
log.Fatalf("WaitOrder(%q): %v", o.URI, err)
}
csr, certkey := newCSR(identifiers)
der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true)
der, curl, err := p.client.CreateCertFromOrder(ctx, o, csr, true)
if err != nil {
log.Fatalf("CreateOrderCert: %v", err)
log.Fatalf("CreateCertFromOrder: %v", err)
}
log.Printf("cert URL: %s", curl)
if err := checkCert(der, identifiers); err != nil {
+5 -8
View File
@@ -103,9 +103,7 @@ func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, ur
}
payload = base64.RawURLEncoding.EncodeToString(cs)
}
hash := sha.New()
hash.Write([]byte(phead + "." + payload))
sig, err := jwsSign(key, sha, hash.Sum(nil))
sig, err := jwsSign(key, sha, []byte(phead+"."+payload))
if err != nil {
return nil, err
}
@@ -195,14 +193,13 @@ func jwkEncode(pub crypto.PublicKey) (string, error) {
return "", ErrUnsupportedKey
}
// jwsSign signs the digest using the given key.
// The hash is unused for ECDSA keys.
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
// jwsSign signs the payload using the given key.
func jwsSign(key crypto.Signer, hash crypto.Hash, payload []byte) ([]byte, error) {
switch pub := key.Public().(type) {
case *rsa.PublicKey:
return key.Sign(rand.Reader, digest, hash)
return crypto.SignMessage(key, rand.Reader, payload, hash)
case *ecdsa.PublicKey:
sigASN1, err := key.Sign(rand.Reader, digest, hash)
sigASN1, err := crypto.SignMessage(key, rand.Reader, payload, hash)
if err != nil {
return nil, err
}
+39 -5
View File
@@ -14,6 +14,7 @@ import (
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
@@ -353,6 +354,22 @@ func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, e
return s.sig, nil
}
type customTestMessageSigner struct {
customTestSigner
msg []byte
opts crypto.SignerOpts
}
func (s *customTestMessageSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, error) {
return nil, errors.New("customTestSigner: Sign() called instead of SignMessage()")
}
func (s *customTestMessageSigner) SignMessage(_ io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
s.msg = msg
s.opts = opts
return s.sig, nil
}
func TestJWSEncodeJSONCustom(t *testing.T) {
claims := struct{ Msg string }{"hello"}
const (
@@ -391,24 +408,33 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
)
tt := []struct {
alg, phead string
name, phead string
pub crypto.PublicKey
stdsig, jwsig string
msgSigner bool
}{
{"ES256", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig},
{"RS256", rs256phead, testKey.Public(), testsig, testsig},
{"ES256", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig, false},
{"RS256", rs256phead, testKey.Public(), testsig, testsig, false},
{"ES256-MessageSigner", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig, true},
{"RS256-MessageSigner", rs256phead, testKey.Public(), testsig, testsig, true},
}
for _, tc := range tt {
tc := tc
t.Run(tc.alg, func(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
stdsig, err := base64.RawStdEncoding.DecodeString(tc.stdsig)
if err != nil {
t.Errorf("couldn't decode test vector: %v", err)
}
signer := &customTestSigner{
cs := customTestSigner{
sig: stdsig,
pub: tc.pub,
}
var signer crypto.Signer = &cs
var ms *customTestMessageSigner
if tc.msgSigner {
ms = &customTestMessageSigner{customTestSigner: cs}
signer = ms
}
b, err := jwsEncodeJSON(claims, signer, noKeyID, "nonce", "url")
if err != nil {
@@ -427,6 +453,14 @@ func TestJWSEncodeJSONCustom(t *testing.T) {
if j.Sig != tc.jwsig {
t.Errorf("j.Sig = %q\nwant %q", j.Sig, tc.jwsig)
}
if ms != nil {
if want := tc.phead + "." + payload; string(ms.msg) != want {
t.Errorf("msg = %q; want %q", ms.msg, want)
}
if ms.opts != crypto.SHA256 {
t.Errorf("opts = %v; want %v", ms.opts, crypto.SHA256)
}
}
})
}
}
+4 -4
View File
@@ -37,7 +37,7 @@ import (
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"
pebbleModVersion = "v2.10.1"
// startingPort is the first port number used for binding interface
// addresses. Each call to takeNextPort() will increment a port number
// starting at this value.
@@ -383,7 +383,7 @@ func testIssuance(t *testing.T, env *environment, challSrv challengeServer) {
order, err = client.WaitOrder(ctx, order.URI)
if err != nil {
var orderErr *acme.OrderError
if errors.Is(err, orderErr) {
if errors.As(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)
@@ -420,7 +420,7 @@ func testIssuance(t *testing.T, env *environment, challSrv challengeServer) {
}
// Finalize the order by creating a certificate with our CSR.
chain, _, err := client.CreateOrderCert(ctx, order.FinalizeURL, csrDer, true)
chain, _, err := client.CreateCertFromOrder(ctx, order, csrDer, true)
if err != nil {
t.Fatalf("failed to finalize order %s with finalize URL %s: %v",
orderURL, order.FinalizeURL, err)
@@ -638,7 +638,7 @@ func startPebbleEnvironment(t *testing.T, config *environmentConfig) environment
// 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),
"-dnsserver", fmt.Sprintf(":%d", config.dnsPort),
"-defaultIPv6", "",
"-management", fmt.Sprintf(":%d", takeNextPort()),
"-doh", "",
+47 -5
View File
@@ -197,7 +197,7 @@ func (c *Client) accountKeyRollover(ctx context.Context, newKey crypto.Signer) e
// identify those with StatusPending status and fulfill a challenge using Accept.
// Once all authorizations are satisfied, the caller will typically want to poll
// order status using WaitOrder until it's in StatusReady state.
// To finalize the order and obtain a certificate, the caller submits a CSR with CreateOrderCert.
// To finalize the order and obtain a certificate, the caller submits a CSR with CreateCertFromOrder.
func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderOption) (*Order, error) {
dir, err := c.Discover(ctx)
if err != nil {
@@ -250,7 +250,11 @@ func (c *Client) GetOrder(ctx context.Context, url string) (*Order, error) {
return nil, err
}
defer res.Body.Close()
return responseOrder(res)
o, err := responseOrder(res)
if err == nil && o.URI == "" {
o.URI = url
}
return o, err
}
// WaitOrder polls an order from the given URL until it is in one of the final states,
@@ -271,6 +275,9 @@ func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) {
}
o, err := responseOrder(res)
res.Body.Close()
if err == nil && o.URI == "" {
o.URI = url
}
switch {
case err != nil:
// Skip and retry.
@@ -338,11 +345,43 @@ func responseOrder(res *http.Response) (*Order, error) {
// certificate chain. Otherwise, only a leaf certificate is returned.
// The returned URL can be used to re-fetch the certificate using FetchCert.
//
// This method is only supported by CAs implementing RFC 8555. See CreateCert for pre-RFC CAs.
// This method is only supported by CAs implementing RFC 8555 that return a
// Location header for order finalization responses. Most users should prefer
// CreateCertFromOrder, an alternative that has better interoperation with RFC 8555
// compliant CAs. See CreateCert for pre-RFC CAs.
//
// CreateOrderCert returns an error if the CA's response is unreasonably large.
// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) {
return c.createOrderCert(ctx, url, "", csr, bundle)
}
// CreateCertFromOrder submits the CSR (Certificate Signing Request) to a CA for
// the specified Order created with AuthorizeOrder.
//
// If the bundle argument is true, the returned value also contain the CA (issuer)
// certificate chain. Otherwise, only a leaf certificate is returned.
// The returned URL can be used to re-fetch the certificate using FetchCert.
//
// This method is only supported by CAs implementing RFC 8555.
// See CreateCert for pre-RFC CAs.
//
// CreateCertFromOrder returns an error if the CA's response is unreasonably large.
// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features.
func (c *Client) CreateCertFromOrder(ctx context.Context, order *Order, csr []byte, bundle bool) (der [][]byte, certURL string, err error) {
if order == nil {
return nil, "", errors.New("acme: nil order")
}
if order.URI == "" {
return nil, "", errors.New("acme: order URI is empty")
}
if order.FinalizeURL == "" {
return nil, "", errors.New("acme: order finalize URL is empty")
}
return c.createOrderCert(ctx, order.FinalizeURL, order.URI, csr, bundle)
}
func (c *Client) createOrderCert(ctx context.Context, finalizeURL, orderURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) {
if _, err := c.Discover(ctx); err != nil { // required by c.accountKID
return nil, "", err
}
@@ -353,7 +392,7 @@ func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bu
}{
CSR: base64.RawURLEncoding.EncodeToString(csr),
}
res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK))
res, err := c.post(ctx, nil, finalizeURL, req, wantStatus(http.StatusOK))
if err != nil {
return nil, "", err
}
@@ -365,7 +404,10 @@ func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bu
// Wait for CA to issue the cert if they haven't.
if o.Status != StatusValid {
o, err = c.WaitOrder(ctx, o.URI)
if orderURL == "" {
orderURL = o.URI
}
o, err = c.WaitOrder(ctx, orderURL)
}
if err != nil {
return nil, "", err
+74
View File
@@ -979,6 +979,80 @@ func TestRFC_CreateOrderCert(t *testing.T) {
}
}
// TestRFC_CreateCertFromOrder tests that finalization responses work even when
// they omit a Location header. Such a header isn't required by RFC 8555 in this
// context and we should be able to complete an order without needing it.
func TestRFC_CreateCertFromOrder(t *testing.T) {
q := &x509.CertificateRequest{
Subject: pkix.Name{CommonName: "example.org"},
}
csr, err := x509.CreateCertificateRequest(rand.Reader, q, testKeyEC)
if err != nil {
t.Fatal(err)
}
tmpl := &x509.Certificate{SerialNumber: big.NewInt(1)}
leaf, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testKeyEC.PublicKey, testKeyEC)
if err != nil {
t.Fatal(err)
}
s := newACMEServer()
s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", s.url("/accounts/1"))
w.Write([]byte(`{"status": "valid"}`))
})
s.handle("/finalize", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{"status":"processing"}`)
})
s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"status":"valid", "certificate":%q}`, s.url("/crt"))
})
s.handle("/crt", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/pem-certificate-chain")
pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: leaf})
})
s.start()
defer s.close()
cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")}
order := &Order{URI: s.url("/orders/1"), FinalizeURL: s.url("/finalize")}
cert, curl, err := cl.CreateCertFromOrder(context.Background(), order, csr, true)
if err != nil {
t.Fatalf("CreateCertFromOrder: %v", err)
}
if _, err := x509.ParseCertificate(cert[0]); err != nil {
t.Errorf("ParseCertificate: %v", err)
}
if !reflect.DeepEqual(cert[0], leaf) {
t.Errorf("cert and leaf bytes don't match")
}
if u := s.url("/crt"); curl != u {
t.Errorf("curl = %q; want %q", curl, u)
}
}
// TestRFC_CreateCertFromOrderInvalidOrder tests that invalid CreateCertFromOrder
// calls fail before making a request.
func TestRFC_CreateCertFromOrderInvalidOrder(t *testing.T) {
cl := new(Client)
tests := []struct {
name string
order *Order
}{
{"nil", nil},
{"empty URI", &Order{FinalizeURL: "https://example.com/finalize"}},
{"empty finalize URL", &Order{URI: "https://example.com/order/1"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if _, _, err := cl.CreateCertFromOrder(context.Background(), tt.order, nil, false); err == nil {
t.Fatal("CreateCertFromOrder returned nil error")
}
})
}
}
func TestRFC_AlreadyRevokedCert(t *testing.T) {
s := newACMEServer()
s.handle("/acme/revoke-cert", func(w http.ResponseWriter, r *http.Request) {
+6 -5
View File
@@ -114,14 +114,15 @@ type Error struct {
}
func (e *Error) Error() string {
str := fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
var sb strings.Builder
fmt.Fprintf(&sb, "%d %s: %s", e.StatusCode, e.ProblemType, e.Detail)
if len(e.Subproblems) > 0 {
str += fmt.Sprintf("; subproblems:")
sb.WriteString("; subproblems:")
for _, sp := range e.Subproblems {
str += fmt.Sprintf("\n\t%s", sp)
fmt.Fprintf(&sb, "\n\t%s", sp)
}
}
return str
return sb.String()
}
// AuthorizationError indicates that an authorization for an identifier
@@ -329,7 +330,7 @@ type Order struct {
// Possible values are StatusPending, StatusReady, StatusProcessing, StatusValid and StatusInvalid.
// Pending means the CA does not believe that the client has fulfilled the requirements.
// Ready indicates that the client has fulfilled all the requirements and can submit a CSR
// to obtain a certificate. This is done with Client's CreateOrderCert.
// to obtain a certificate. This is done with Client's CreateCertFromOrder.
// Processing means the certificate is being issued.
// Valid indicates the CA has issued the certificate. It can be downloaded
// from the Order's CertURL. This is done with Client's FetchCert.
+18 -17
View File
@@ -17,8 +17,8 @@
// It uses data-independent memory access, which is preferred for password
// hashing and password-based key derivation. Argon2i requires more passes over
// memory than Argon2id to protect from trade-off attacks. The recommended
// parameters (taken from [RFC 9106 Section 7.3]) for non-interactive operations are time=3 and to
// use the maximum available memory.
// parameters (taken from [RFC 9106 Section 7.3]) for non-interactive
// operations are time=3 and to use the maximum available memory.
//
// # Argon2id
//
@@ -26,11 +26,14 @@
// Argon2i and Argon2d. It uses data-independent memory access for the first
// half of the first iteration over the memory and data-dependent memory access
// for the rest. Argon2id is side-channel resistant and provides better brute-
// force cost savings due to time-memory tradeoffs than Argon2i. The recommended
// parameters for non-interactive operations (taken from [RFC 9106 Section 7.3]) are time=1 and to
// use the maximum available memory.
// force cost savings due to time-memory tradeoffs than Argon2i. [RFC 9106
// Section 4] recommends time=1, memory=2*1024*1024 KiB (2 GiB), and threads=4
// as the first recommended option. If much less memory is available, it
// recommends time=3, memory=64*1024 KiB (64 MiB), and threads=4 as the second
// recommended option.
//
// [argon2-specs.pdf]: https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf
// [RFC 9106 Section 4]: https://www.rfc-editor.org/rfc/rfc9106.html#section-4
// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
package argon2
@@ -59,9 +62,9 @@ const (
//
// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32)
//
// [RFC 9106 Section 7.3] recommends time=3, and memory=32*1024 as a sensible number.
// If using that amount of memory (32 MB) is not possible in some contexts then
// the time parameter can be increased to compensate.
// The example above uses time=3 and memory=32*1024. Argon2i generally
// requires more passes over memory than Argon2id. If in doubt, prefer IDKey
// and its Argon2id parameter recommendations.
//
// The time parameter specifies the number of passes over the memory and the
// memory parameter specifies the size of the memory in KiB. For example
@@ -69,8 +72,6 @@ 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,20 +84,20 @@ func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint3
// For example, you can get a derived key for e.g. AES-256 (which needs a
// 32-byte key) by doing:
//
// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32)
// key := argon2.IDKey([]byte("some password"), salt, 1, 2*1024*1024, 4, 32)
//
// [RFC 9106 Section 7.3] recommends time=1, and memory=64*1024 as a sensible number.
// If using that amount of memory (64 MB) is not possible in some contexts then
// the time parameter can be increased to compensate.
// The example above uses the first [RFC 9106 Section 4] recommended option.
// If much less memory is available, the second recommended option is time=3,
// memory=64*1024 KiB (64 MiB), and threads=4.
//
// The time parameter specifies the number of passes over the memory and the
// memory parameter specifies the size of the memory in KiB. For example
// memory=64*1024 sets the memory cost to ~64 MB. The number of threads can be
// adjusted to the numbers of available CPUs. The cost parameters should be
// memory=2*1024*1024 sets the memory cost to ~2 GiB. The number of threads can
// be adjusted to the numbers of available CPUs. The cost parameters should be
// increased as memory latency and CPU parallelism increases. Remember to get a
// good random salt.
//
// [RFC 9106 Section 7.3]: https://www.rfc-editor.org/rfc/rfc9106.html#section-7.3
// [RFC 9106 Section 4]: https://www.rfc-editor.org/rfc/rfc9106.html#section-4
func IDKey(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {
return deriveKey(argon2id, password, salt, nil, nil, time, memory, threads, keyLen)
}
+9
View File
@@ -24,6 +24,7 @@ var (
func TestArgon2(t *testing.T) {
defer func(sse4 bool) { useSSE4 = sse4 }(useSSE4)
defer func(lsx bool) { useLSX = lsx }(useLSX)
if useSSE4 {
t.Log("SSE4.1 version")
@@ -32,6 +33,14 @@ func TestArgon2(t *testing.T) {
testArgon2id(t)
useSSE4 = false
}
if useLSX {
t.Log("LSX version")
testArgon2i(t)
testArgon2d(t)
testArgon2id(t)
useLSX = false
}
t.Log("generic version")
testArgon2i(t)
testArgon2d(t)
+4
View File
@@ -6,6 +6,10 @@ package argon2
var useSSE4 bool
// useLSX reports whether the loong64 LSX vector implementation should be
// used. It is set from cpu.Loong64.HasLSX in blamka_loong64.go's init().
var useLSX bool
func processBlockGeneric(out, in1, in2 *block, xor bool) {
var t block
for i := range t {
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build loong64 && gc && !purego
package argon2
import "golang.org/x/sys/cpu"
func init() {
// useLSX (declared in blamka_generic.go) gates the LSX vector path.
// If the running CPU lacks LSX support, processBlockVX falls back
// to processBlockGeneric.
useLSX = cpu.Loong64.HasLSX
}
//go:noescape
func blamkaLOONG64(b *block)
//go:noescape
func mixBlocksVX(out, a, b, c *block)
//go:noescape
func xorBlocksVX(out, a, b, c *block)
func processBlock(out, in1, in2 *block) {
processBlockVX(out, in1, in2, false)
}
func processBlockXOR(out, in1, in2 *block) {
processBlockVX(out, in1, in2, true)
}
func processBlockVX(out, in1, in2 *block, xor bool) {
if !useLSX {
processBlockGeneric(out, in1, in2, xor)
return
}
var t block
mixBlocksVX(&t, in1, in2, &t)
blamkaLOONG64(&t)
if xor {
xorBlocksVX(out, in1, in2, &t)
} else {
mixBlocksVX(out, in1, in2, &t)
}
}
+240
View File
@@ -0,0 +1,240 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build loong64 && gc && !purego
#include "textflag.h"
// HALF_ROUND applies one BlaMka half-round (mul-add-xor-rotate) to two
// groups of 4 columns, each packed 2-wide into V registers (v0/v2/v4/v6
// hold one pair of columns in lane0/lane1, v1/v3/v5/v7 hold the other).
// t0, t1 are scratch registers, one per independent column group.
#define HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, t0, t1) \
VMULWEVVWU v0, v2, t0; \
VADDV t0, t0, t0; \
VADDV v0, v2, v0; \
VADDV v0, t0, v0; \
VXORV v6, v0, v6; \
VROTRV $32, v6, v6; \
VMULWEVVWU v1, v3, t1; \
VADDV t1, t1, t1; \
VADDV v1, v3, v1; \
VADDV v1, t1, v1; \
VXORV v7, v1, v7; \
VROTRV $32, v7, v7; \
VMULWEVVWU v4, v6, t0; \
VADDV t0, t0, t0; \
VADDV v4, v6, v4; \
VADDV v4, t0, v4; \
VXORV v2, v4, v2; \
VROTRV $24, v2, v2; \
VMULWEVVWU v5, v7, t1; \
VADDV t1, t1, t1; \
VADDV v5, v7, v5; \
VADDV v5, t1, v5; \
VXORV v3, v5, v3; \
VROTRV $24, v3, v3; \
VMULWEVVWU v0, v2, t0; \
VADDV t0, t0, t0; \
VADDV v0, v2, v0; \
VADDV v0, t0, v0; \
VXORV v6, v0, v6; \
VROTRV $16, v6, v6; \
VMULWEVVWU v1, v3, t1; \
VADDV t1, t1, t1; \
VADDV v1, v3, v1; \
VADDV v1, t1, v1; \
VXORV v7, v1, v7; \
VROTRV $16, v7, v7; \
VMULWEVVWU v4, v6, t0; \
VADDV t0, t0, t0; \
VADDV v4, v6, v4; \
VADDV v4, t0, v4; \
VXORV v2, v4, v2; \
VROTRV $63, v2, v2; \
VMULWEVVWU v5, v7, t1; \
VADDV t1, t1, t1; \
VADDV v5, v7, v5; \
VADDV v5, t1, v5; \
VXORV v3, v5, v3; \
VROTRV $63, v3, v3
// SHUFFLE / SHUFFLE_INV: reused verbatim from blake2b_loong64.s -- amd64
// avo source confirms argon2's SHUFFLE/SHUFFLE_INV and blake2b's SHUFFLE
// are byte-for-byte identical macros (diagonalize / undiagonalize),
// operating only on V2..V7 (V0/V1 untouched), with V13 held at zero.
#define SHUFFLE \
VADDV V2, V13, V12; \
VADDV V6, V13, V16; \
VADDV V4, V13, V14; \
VADDV V5, V13, V4; \
VADDV V14, V13, V5; \
VSHUF4IV $9, V3, V2; \
VSHUF4IV $9, V12, V3; \
VSHUF4IV $3, V7, V6; \
VSHUF4IV $3, V16, V7
#define SHUFFLE_INV \
VADDV V2, V13, V12; \
VADDV V6, V13, V16; \
VADDV V4, V13, V14; \
VADDV V5, V13, V4; \
VADDV V14, V13, V5; \
VSHUF4IV $3, V3, V2; \
VSHUF4IV $3, V12, V3; \
VSHUF4IV $9, V7, V6; \
VSHUF4IV $9, V16, V7
// LOAD_MSG_0 / STORE_MSG_0: row pass. `off` is the byte offset of the
// 128-byte (16-uint64) row group; within the macro the 8 loads step by
// 16B (2 uint64 per V register). Called from blamkaLOONG64 with off
// stepping by 128B across the 8 row groups -- do not confuse the two
// strides.
#define LOAD_MSG_0(off) \
VMOVQ (off+0)(R4), V0; \
VMOVQ (off+16)(R4), V1; \
VMOVQ (off+32)(R4), V2; \
VMOVQ (off+48)(R4), V3; \
VMOVQ (off+64)(R4), V4; \
VMOVQ (off+80)(R4), V5; \
VMOVQ (off+96)(R4), V6; \
VMOVQ (off+112)(R4), V7
#define STORE_MSG_0(off) \
VMOVQ V0, (off+0)(R4); \
VMOVQ V1, (off+16)(R4); \
VMOVQ V2, (off+32)(R4); \
VMOVQ V3, (off+48)(R4); \
VMOVQ V4, (off+64)(R4); \
VMOVQ V5, (off+80)(R4); \
VMOVQ V6, (off+96)(R4); \
VMOVQ V7, (off+112)(R4)
// LOAD_MSG_1 / STORE_MSG_1: diagonal pass. `off` is the byte offset within
// a 128-byte row group; within the macro the 8 loads step by 128B (one
// element from each of the 8 row groups, mirroring blamka_generic.go's
// t[off], t[128+off], t[256+off], ...). Called from blamkaLOONG64 with
// off stepping by 16B (2 uint64) across the 8 diagonal groups -- the
// inner/outer strides are swapped relative to LOAD_MSG_0/STORE_MSG_0.
#define LOAD_MSG_1(off) \
VMOVQ (off+0)(R4), V0; \
VMOVQ (off+128)(R4), V1; \
VMOVQ (off+256)(R4), V2; \
VMOVQ (off+384)(R4), V3; \
VMOVQ (off+512)(R4), V4; \
VMOVQ (off+640)(R4), V5; \
VMOVQ (off+768)(R4), V6; \
VMOVQ (off+896)(R4), V7
#define STORE_MSG_1(off) \
VMOVQ V0, (off+0)(R4); \
VMOVQ V1, (off+128)(R4); \
VMOVQ V2, (off+256)(R4); \
VMOVQ V3, (off+384)(R4); \
VMOVQ V4, (off+512)(R4); \
VMOVQ V5, (off+640)(R4); \
VMOVQ V6, (off+768)(R4); \
VMOVQ V7, (off+896)(R4)
// BLAMKA_ROUND_0(off): one full row pass over the 16-uint64 group at
// byte offset off -- corresponds to a single call to blamkaGeneric
// with the "row" argument order (t[off]..t[off+15]).
#define BLAMKA_ROUND_0(off) \
LOAD_MSG_0(off); \
HALF_ROUND(V0, V1, V2, V3, V4, V5, V6, V7, V8, V9); \
SHUFFLE; \
HALF_ROUND(V0, V1, V2, V3, V4, V5, V6, V7, V8, V9); \
SHUFFLE_INV; \
STORE_MSG_0(off)
// BLAMKA_ROUND_1(off): one full diagonal pass over the 16 uint64s at
// diagonal offset off -- corresponds to a single call to blamkaGeneric
// with the "diagonal" argument order (t[off], t[off+1], t[16+off], ...).
#define BLAMKA_ROUND_1(off) \
LOAD_MSG_1(off); \
HALF_ROUND(V0, V1, V2, V3, V4, V5, V6, V7, V8, V9); \
SHUFFLE; \
HALF_ROUND(V0, V1, V2, V3, V4, V5, V6, V7, V8, V9); \
SHUFFLE_INV; \
STORE_MSG_1(off)
// func blamkaLOONG64(b *block)
TEXT ·blamkaLOONG64(SB), NOSPLIT, $0-8
MOVV b+0(FP), R4
VXORV V13, V13, V13 // V13 = 0, used by SHUFFLE/SHUFFLE_INV
// Round 0: 8 row passes, stride 128 bytes (16 uint64s).
BLAMKA_ROUND_0(0)
BLAMKA_ROUND_0(128)
BLAMKA_ROUND_0(256)
BLAMKA_ROUND_0(384)
BLAMKA_ROUND_0(512)
BLAMKA_ROUND_0(640)
BLAMKA_ROUND_0(768)
BLAMKA_ROUND_0(896)
// Round 1: 8 diagonal passes, stride 16 bytes (2 uint64s).
BLAMKA_ROUND_1(0)
BLAMKA_ROUND_1(16)
BLAMKA_ROUND_1(32)
BLAMKA_ROUND_1(48)
BLAMKA_ROUND_1(64)
BLAMKA_ROUND_1(80)
BLAMKA_ROUND_1(96)
BLAMKA_ROUND_1(112)
RET
// func mixBlocksVX(out, a, b, c *block)
TEXT ·mixBlocksVX(SB), NOSPLIT, $0-32
MOVV out+0(FP), R4
MOVV a+8(FP), R5
MOVV b+16(FP), R6
MOVV c+24(FP), R7
MOVV $1024, R8
PCALIGN $16
mixloop:
VMOVQ (R5), V0
VMOVQ (R6), V1
VMOVQ (R7), V2
VXORV V0, V1, V0
VXORV V0, V2, V0
VMOVQ V0, (R4)
ADDV $16, R4
ADDV $16, R5
ADDV $16, R6
ADDV $16, R7
SUBV $16, R8
BNE R8, R0, mixloop
RET
// func xorBlocksVX(out, a, b, c *block)
TEXT ·xorBlocksVX(SB), NOSPLIT, $0-32
MOVV out+0(FP), R4
MOVV a+8(FP), R5
MOVV b+16(FP), R6
MOVV c+24(FP), R7
MOVV $1024, R8
PCALIGN $16
xorloop:
VMOVQ (R5), V0
VMOVQ (R6), V1
VMOVQ (R7), V2
VMOVQ (R4), V3
VXORV V0, V1, V0
VXORV V0, V2, V0
VXORV V0, V3, V0
VMOVQ V0, (R4)
ADDV $16, R4
ADDV $16, R5
ADDV $16, R6
ADDV $16, R7
SUBV $16, R8
BNE R8, R0, xorloop
RET
+1 -1
View File
@@ -2,7 +2,7 @@
// 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
//go:build (!amd64 && !loong64) || purego || !gc
package argon2
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build loong64 && gc && !purego
package blake2b
import "golang.org/x/sys/cpu"
//go:noescape
func hashBlocksVX(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte)
func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) {
if cpu.Loong64.HasLSX {
hashBlocksVX(h, c, flag, blocks)
} else {
hashBlocksGeneric(h, c, flag, blocks)
}
}
+252
View File
@@ -0,0 +1,252 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build loong64 && gc && !purego
#include "textflag.h"
DATA ·iv0<>+0(SB)/8, $0x6a09e667f3bcc908
DATA ·iv0<>+8(SB)/8, $0xbb67ae8584caa73b
GLOBL ·iv0<>(SB), RODATA|NOPTR, $16
DATA ·iv1<>+0(SB)/8, $0x3c6ef372fe94f82b
DATA ·iv1<>+8(SB)/8, $0xa54ff53a5f1d36f1
GLOBL ·iv1<>(SB), RODATA|NOPTR, $16
DATA ·iv2<>+0(SB)/8, $0x510e527fade682d1
DATA ·iv2<>+8(SB)/8, $0x9b05688c2b3e6c1f
GLOBL ·iv2<>(SB), RODATA|NOPTR, $16
DATA ·iv3<>+0(SB)/8, $0x1f83d9abfb41bd6b
DATA ·iv3<>+8(SB)/8, $0x5be0cd19137e2179
GLOBL ·iv3<>(SB), RODATA|NOPTR, $16
#define SHUFFLE_1 \
VADDV V2, V13, V12; \ // V12 = V2
VADDV V6, V13, V16; \ // V16 = V6
VADDV V4, V13, V14; \ // V14 = V4
VADDV V5, V13, V4; \ // V4 = V5
VADDV V14, V13, V5; \ // V5 = V4
VSHUF4IV $9, V3, V2; \
VSHUF4IV $9, V12, V3; \
VSHUF4IV $3, V7, V6; \
VSHUF4IV $3, V16, V7; \
#define SHUFFLE_2 \
VADDV V2, V13, V12; \ // V12 = V2
VADDV V6, V13, V16; \ // V16 = V6
VADDV V4, V13, V14; \ // V14 = V4
VADDV V5, V13, V4; \ // V4 = V5
VADDV V14, V13, V5; \ // V5 = V4
VSHUF4IV $3, V3, V2; \
VSHUF4IV $3, V12, V3; \
VSHUF4IV $9, V7, V6; \
VSHUF4IV $9, V16, V7; \
#define LOAD_M(a, b, c, d, e, f, g, h) \
VMOVQ a, V8.V[0]; \
VMOVQ b, V8.V[1]; \
VMOVQ c, V9.V[0]; \
VMOVQ d, V9.V[1]; \
VMOVQ e, V10.V[0]; \
VMOVQ f, V10.V[1]; \
VMOVQ g, V11.V[0]; \
VMOVQ h, V11.V[1]; \
#define ROUND_0 \
VADDV V0, V8, V0; \
VADDV V1, V9, V1; \
VADDV V0, V2, V0; \
VADDV V1, V3, V1; \
VXORV V6, V0, V6; \
VXORV V7, V1, V7; \
VROTRV $32, V6, V6; \
VROTRV $32, V7, V7; \
VADDV V4, V6, V4; \
VADDV V5, V7, V5; \
VXORV V2, V4, V2; \
VXORV V3, V5, V3; \
VROTRV $24, V2, V2; \
VROTRV $24, V3, V3; \
VADDV V0, V10, V0; \
VADDV V1, V11, V1; \
VADDV V0, V2, V0; \
VADDV V1, V3, V1; \
VXORV V6, V0, V6; \
VXORV V7, V1, V7; \
VROTRV $16, V6, V6; \
VROTRV $16, V7, V7; \
VADDV V4, V6, V4; \
VADDV V5, V7, V5; \
VXORV V2, V4, V2; \
VXORV V3, V5, V3; \
VROTRV $63, V2, V2; \
VROTRV $63, V3, V3; \
#define ROUND_8 ROUND_0
// func hashBlocksVX(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte)
TEXT ·hashBlocksVX(SB), NOSPLIT, $0-48
MOVV h+0(FP), R4
MOVV c+8(FP), R5
MOVV flag+16(FP), R6
MOVV blocks_base+24(FP), R7
MOVV blocks_len+32(FP), R8
MOVV (R5), R9 // c0
MOVV 8(R5), R10 // c1
loop:
ADDV $0x80, R9
SGTU $0x80, R9, R11
ADDV R10, R11, R10
VMOVQ R9, V8.V[0]
VMOVQ R10, V8.V[1]
MOVV $·iv0<>(SB), R11
MOVV $·iv1<>(SB), R12
MOVV $·iv2<>(SB), R13
MOVV $·iv3<>(SB), R14
VMOVQ (R13), V6
VMOVQ (R11), V4
VMOVQ (R12), V5
MOVV (R14), R15 // v14
MOVV 8(R14), R16
VXORV V6, V8, V6
XOR R15, R6, R15
VMOVQ R16, V7.V[1]
VMOVQ R15, V7.V[0]
VMOVQ (R4), V0
VMOVQ 16(R4), V1
VMOVQ 32(R4), V2
VMOVQ 48(R4), V3
MOVV 0(R7), R11
MOVV 8(R7), R12
MOVV 16(R7), R13
MOVV 24(R7), R14
MOVV 32(R7), R15
MOVV 40(R7), R16
MOVV 48(R7), R17
MOVV 56(R7), R18
MOVV 64(R7), R19
MOVV 72(R7), R24
MOVV 80(R7), R25
MOVV 88(R7), R26
MOVV 96(R7), R27
MOVV 104(R7), R28
MOVV 112(R7), R29
MOVV 120(R7), R30
VXORV V13, V13, V13 // V13=0
LOAD_M(R11, R13, R15, R17, R12, R14, R16, R18)
ROUND_0
SHUFFLE_1
LOAD_M(R19, R25, R27, R29, R24, R26, R28, R30)
ROUND_8
SHUFFLE_2
LOAD_M(R29, R15, R24, R28, R25, R19, R30, R17)
ROUND_0
SHUFFLE_1
LOAD_M(R12, R11, R26, R16, R27, R13, R18, R14)
ROUND_8
SHUFFLE_2
LOAD_M(R26, R27, R16, R30, R19, R11, R13, R28)
ROUND_0
SHUFFLE_1
LOAD_M(R25, R14, R18, R24, R29, R17, R12, R15)
ROUND_8
SHUFFLE_2
LOAD_M(R18, R14, R28, R26, R24, R12, R27, R29)
ROUND_0
SHUFFLE_1
LOAD_M(R13, R16, R15, R30, R17, R25, R11, R19)
ROUND_8
SHUFFLE_2
LOAD_M(R24, R16, R13, R25, R11, R18, R15, R30)
ROUND_0
SHUFFLE_1
LOAD_M(R29, R26, R17, R14, R12, R27, R19, R28)
ROUND_8
SHUFFLE_2
LOAD_M(R13, R17, R11, R19, R27, R25, R26, R14)
ROUND_0
SHUFFLE_1
LOAD_M(R15, R18, R30, R12, R28, R16, R29, R24)
ROUND_8
SHUFFLE_2
LOAD_M(R27, R12, R29, R15, R16, R30, R28, R25)
ROUND_0
SHUFFLE_1
LOAD_M(R11, R17, R24, R19, R18, R14, R13, R26)
ROUND_8
SHUFFLE_2
LOAD_M(R28, R18, R27, R14, R26, R29, R12, R24)
ROUND_0
SHUFFLE_1
LOAD_M(R16, R30, R19, R13, R11, R15, R17, R25)
ROUND_8
SHUFFLE_2
LOAD_M(R17, R29, R26, R11, R30, R24, R14, R19)
ROUND_0
SHUFFLE_1
LOAD_M(R27, R28, R12, R25, R13, R18, R15, R16)
ROUND_8
SHUFFLE_2
LOAD_M(R25, R19, R18, R12, R13, R15, R17, R16)
ROUND_0
SHUFFLE_1
LOAD_M(R30, R24, R14, R28, R26, R29, R27, R11)
ROUND_8
SHUFFLE_2
LOAD_M(R11, R13, R15, R17, R12, R14, R16, R18)
ROUND_0
SHUFFLE_1
LOAD_M(R19, R25, R27, R29, R24, R26, R28, R30)
ROUND_8
SHUFFLE_2
LOAD_M(R29, R15, R24, R28, R25, R19, R30, R17)
ROUND_0
SHUFFLE_1
LOAD_M(R12, R11, R26, R16, R27, R13, R18, R14)
ROUND_8
SHUFFLE_2
VMOVQ (R4), V8
VMOVQ 16(R4), V9
VMOVQ 32(R4), V10
VMOVQ 48(R4), V11
VXORV V0, V4, V0
VXORV V1, V5, V1
VXORV V2, V6, V2
VXORV V3, V7, V3
VXORV V0, V8, V0
VXORV V1, V9, V1
VXORV V2, V10, V2
VXORV V3, V11, V3
VMOVQ V0, (R4)
VMOVQ V1, 16(R4)
VMOVQ V2, 32(R4)
VMOVQ V3, 48(R4)
SUBV $128, R8
ADDV $128, R7
BNE R8, R0, loop
MOVV R9, (R5)
MOVV R10, 8(R5)
RET
+1 -1
View File
@@ -2,7 +2,7 @@
// 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
//go:build (!amd64 && !loong64) || purego || !gc
package blake2b
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
package chacha20
import "golang.org/x/sys/cpu"
const bufSize = 256
//go:noescape
func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32)
func (c *Cipher) xorKeyStreamBlocks(dst, src []byte) {
if cpu.Loong64.HasLSX {
xorKeyStreamVX(dst, src, &c.key, &c.nonce, &c.counter)
} else {
c.xorKeyStreamBlocksGeneric(dst, src)
}
}
+342
View File
@@ -0,0 +1,342 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// derived from chacha_arm64.s
//go:build gc && !purego
#include "textflag.h"
DATA ·constants+0x00(SB)/4, $0x61707865
DATA ·constants+0x04(SB)/4, $0x3320646e
DATA ·constants+0x08(SB)/4, $0x79622d32
DATA ·constants+0x0c(SB)/4, $0x6b206574
GLOBL ·constants(SB), NOPTR|RODATA, $32
DATA ·incRotMatrix+0x00(SB)/4, $0x00000000
DATA ·incRotMatrix+0x04(SB)/4, $0x00000001
DATA ·incRotMatrix+0x08(SB)/4, $0x00000002
DATA ·incRotMatrix+0x0c(SB)/4, $0x00000003
GLOBL ·incRotMatrix(SB), NOPTR|RODATA, $32
#define NUM_ROUNDS 10
// func xorKeyStreamVX(dst, src []byte, key *[8]uint32, nonce *[3]uint32, counter *uint32)
TEXT ·xorKeyStreamVX(SB), NOSPLIT, $0
MOVV dst+0(FP), R4
MOVV src+24(FP), R5
MOVV src_len+32(FP), R6
MOVV key+48(FP), R7
MOVV nonce+56(FP), R8
MOVV counter+64(FP), R9
MOVV $·constants(SB), R10
MOVV $·incRotMatrix(SB), R11
MOVW (R9), R12
loop:
MOVV $NUM_ROUNDS, R15
// load 4-32bit data from incRotMatrix added to counter
VMOVQ (R11), V30
// load contants
VMOVQ (R10), V0.W4
VMOVQ 4(R10), V1.W4
VMOVQ 8(R10), V2.W4
VMOVQ 12(R10), V3.W4
// load keys
VMOVQ (R7), V4.W4
VMOVQ 4(R7), V5.W4
VMOVQ 8(R7), V6.W4
VMOVQ 12(R7), V7.W4
VMOVQ 16(R7), V8.W4
VMOVQ 20(R7), V9.W4
VMOVQ 24(R7), V10.W4
VMOVQ 28(R7), V11.W4
// load counter + nonce
VMOVQ (R9), V12.W4
VMOVQ (R8), V13.W4
VMOVQ 4(R8), V14.W4
VMOVQ 8(R8), V15.W4
// update counter
VADDW V30, V12, V12
chacha:
// V0..V3 += V4..V7
// V12..V15 <<<= ((V12..V15 XOR V0..V3), 16)
VADDW V0, V4, V0
VADDW V1, V5, V1
VADDW V2, V6, V2
VADDW V3, V7, V3
VXORV V12, V0, V12
VXORV V13, V1, V13
VXORV V14, V2, V14
VXORV V15, V3, V15
VROTRW $16, V12, V12
VROTRW $16, V13, V13
VROTRW $16, V14, V14
VROTRW $16, V15, V15
// V8..V11 += V12..V15
// V4..V7 <<<= ((V4..V7 XOR V8..V11), 12)
VADDW V8, V12, V8
VADDW V9, V13, V9
VADDW V10, V14, V10
VADDW V11, V15, V11
VXORV V4, V8, V4
VXORV V5, V9, V5
VXORV V6, V10, V6
VXORV V7, V11, V7
VROTRW $20, V4, V4
VROTRW $20, V5, V5
VROTRW $20, V6, V6
VROTRW $20, V7, V7
// V0..V3 += V4..V7
// V12..V15 <<<= ((V12..V15 XOR V0..V3), 8)
VADDW V0, V4, V0
VADDW V1, V5, V1
VADDW V2, V6, V2
VADDW V3, V7, V3
VXORV V12, V0, V12
VXORV V13, V1, V13
VXORV V14, V2, V14
VXORV V15, V3, V15
VROTRW $24, V12, V12
VROTRW $24, V13, V13
VROTRW $24, V14, V14
VROTRW $24, V15, V15
// V8..V11 += V12..V15
// V4..V7 <<<= ((V4..V7 XOR V8..V11), 7)
VADDW V12, V8, V8
VADDW V13, V9, V9
VADDW V14, V10, V10
VADDW V15, V11, V11
VXORV V4, V8, V4
VXORV V5, V9, V5
VXORV V6, V10, V6
VXORV V7, V11, V7
VROTRW $25, V4, V4
VROTRW $25, V5, V5
VROTRW $25, V6, V6
VROTRW $25, V7, V7
// V0..V3 += V5..V7, V4
// V15,V12-V14 <<<= ((V15,V12-V14 XOR V0..V3), 16)
VADDW V0, V5, V0
VADDW V1, V6, V1
VADDW V2, V7, V2
VADDW V3, V4, V3
VXORV V15, V0, V15
VXORV V12, V1, V12
VXORV V13, V2, V13
VXORV V14, V3, V14
VROTRW $16, V15, V15
VROTRW $16, V12, V12
VROTRW $16, V13, V13
VROTRW $16, V14, V14
// V10,V11,V8,V9 += V15,V12,V13,V14
// V5,V6,V7,V4 <<<= ((V5,V6,V7,V4 XOR V10,V11,V8,V9), 12)
VADDW V10, V15, V10
VADDW V11, V12, V11
VADDW V8, V13, V8
VADDW V9, V14, V9
VXORV V5, V10, V5
VXORV V6, V11, V6
VXORV V7, V8, V7
VXORV V4, V9, V4
VROTRW $20, V5, V5
VROTRW $20, V6, V6
VROTRW $20, V7, V7
VROTRW $20, V4, V4
// V0..V3 += V5..V7, V4
// V15,V12-V14 <<<= ((V15,V12-V14 XOR V0..V3), 8)
VADDW V5, V0, V0
VADDW V6, V1, V1
VADDW V7, V2, V2
VADDW V4, V3, V3
VXORV V15, V0, V15
VXORV V12, V1, V12
VXORV V13, V2, V13
VXORV V14, V3, V14
VROTRW $24, V15, V15
VROTRW $24, V12, V12
VROTRW $24, V13, V13
VROTRW $24, V14, V14
// V10,V11,V8,V9 += V15,V12,V13,V14
// V5,V6,V7,V4 <<<= ((V5,V6,V7,V4 XOR V10,V11,V8,V9), 7)
VADDW V15, V10, V10
VADDW V12, V11, V11
VADDW V13, V8, V8
VADDW V14, V9, V9
VXORV V5, V10, V5
VXORV V6, V11, V6
VXORV V7, V8, V7
VXORV V4, V9, V4
VROTRW $25, V5, V5
VROTRW $25, V6, V6
VROTRW $25, V7, V7
VROTRW $25, V4, V4
SUBV $1, R15
BNE R15, R0, chacha
// load origin contants
VMOVQ (R10), V16.W4
VMOVQ 4(R10), V17.W4
VMOVQ 8(R10), V18.W4
VMOVQ 12(R10), V19.W4
// load origin keys
VMOVQ (R7), V20.W4
VMOVQ 4(R7), V21.W4
VMOVQ 8(R7), V22.W4
VMOVQ 12(R7), V23.W4
VMOVQ 16(R7), V24.W4
VMOVQ 20(R7), V25.W4
VMOVQ 24(R7), V26.W4
VMOVQ 28(R7), V27.W4
// add back the initial state to generate the key stream
VADDW V30, V12, V12 // update counter in advance to prevent V30 from being overwritten
VADDW V16, V0, V0
VADDW V17, V1, V1
VADDW V18, V2, V2
VADDW V19, V3, V3
// load origin counter + nonce
VMOVQ (R9), V28.W4
VMOVQ (R8), V29.W4
VMOVQ 4(R8), V30.W4
VMOVQ 8(R8), V31.W4
VADDW V20, V4, V4
VADDW V21, V5, V5
VADDW V22, V6, V6
VADDW V23, V7, V7
VADDW V24, V8, V8
VADDW V25, V9, V9
VADDW V26, V10, V10
VADDW V27, V11, V11
VADDW V28, V12, V12
VADDW V29, V13, V13
VADDW V30, V14, V14
VADDW V31, V15, V15
// shuffle
VILVLW V0, V1, V16
VILVHW V0, V1, V17
VILVLW V2, V3, V18
VILVHW V2, V3, V19
VILVLW V4, V5 ,V20
VILVHW V4, V5, V21
VILVLW V6, V7, V22
VILVHW V6, V7, V23
VILVLW V8, V9, V24
VILVHW V8, V9, V25
VILVLW V10, V11, V26
VILVHW V10, V11, V27
VILVLW V12, V13, V28
VILVHW V12, V13, V29
VILVLW V14, V15, V30
VILVHW V14, V15, V31
VILVLV V16, V18, V0
VILVHV V16, V18, V4
VILVLV V17, V19, V8
VILVHV V17, V19, V12
// load src data from R5
VMOVQ 0(R5), V16
VMOVQ 16(R5), V17
VMOVQ 32(R5), V18
VMOVQ 48(R5), V19
VILVLV V20, V22, V1
VILVHV V20, V22, V5
VILVLV V21, V23, V9
VILVHV V21, V23, V13
VMOVQ 64(R5), V20
VMOVQ 80(R5), V21
VMOVQ 96(R5), V22
VMOVQ 112(R5), V23
VILVLV V24, V26, V2
VILVHV V24, V26, V6
VILVLV V25, V27, V10
VILVHV V25, V27, V14
VMOVQ 128(R5), V24
VMOVQ 144(R5), V25
VMOVQ 160(R5), V26
VMOVQ 176(R5), V27
VILVLV V28, V30, V3
VILVHV V28, V30, V7
VILVLV V29, V31, V11
VILVHV V29, V31, V15
VMOVQ 192(R5), V28
VMOVQ 208(R5), V29
VMOVQ 224(R5), V30
VMOVQ 240(R5), V31
VXORV V0, V16, V16
VXORV V1, V17, V17
VXORV V2, V18, V18
VXORV V3, V19, V19
VMOVQ V16, 0(R4)
VMOVQ V17, 16(R4)
VMOVQ V18, 32(R4)
VMOVQ V19, 48(R4)
VXORV V4, V20, V20
VXORV V5, V21, V21
VXORV V6, V22, V22
VXORV V7, V23, V23
VMOVQ V20, 64(R4)
VMOVQ V21, 80(R4)
VMOVQ V22, 96(R4)
VMOVQ V23, 112(R4)
VXORV V8, V24, V24
VXORV V9, V25, V25
VXORV V10, V26, V26
VXORV V11, V27, V27
VMOVQ V24, 128(R4)
VMOVQ V25, 144(R4)
VMOVQ V26, 160(R4)
VMOVQ V27, 176(R4)
VXORV V12, V28, V28
VXORV V13, V29, V29
VXORV V14, V30, V30
VXORV V15, V31, V31
VMOVQ V28, 192(R4)
VMOVQ V29, 208(R4)
VMOVQ V30, 224(R4)
VMOVQ V31, 240(R4)
ADD $4, R12, R12
MOVW R12, (R9) // update counter
ADDV $256, R4, R4
ADDV $256, R5, R5
SUBV $256, R6, R6
BNE R6, R0, loop
RET
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (!arm64 && !s390x && !ppc64 && !ppc64le) || !gc || purego
//go:build (!arm64 && !loong64 && !s390x && !ppc64 && !ppc64le) || !gc || purego
package chacha20
+5 -5
View File
@@ -1,11 +1,11 @@
module golang.org/x/crypto
go 1.25.0
go 1.26.0
require (
golang.org/x/net v0.55.0 // tagx:ignore
golang.org/x/sys v0.46.0
golang.org/x/term v0.44.0
golang.org/x/net v0.58.0 // tagx:ignore
golang.org/x/sys v0.48.0
golang.org/x/term v0.46.0
)
require golang.org/x/text v0.38.0 // indirect
require golang.org/x/text v0.42.0 // indirect
+8 -8
View File
@@ -1,8 +1,8 @@
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (!amd64 && !loong64 && !ppc64le && !ppc64 && !s390x) || !gc || purego
//go:build (!amd64 && !loong64 && !ppc64le && !ppc64 && !riscv64 && !s390x) || !gc || purego
package poly1305
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego && (amd64 || loong64 || ppc64 || ppc64le)
//go:build gc && !purego && (amd64 || loong64 || ppc64 || ppc64le || riscv64)
package poly1305
+158
View File
@@ -0,0 +1,158 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
#define LOAD64U(base, offset, t0, t1, t2, t3, dst) \
MOVBU (offset+0*1)(base), t0; \
MOVBU (offset+1*1)(base), t1; \
MOVBU (offset+2*1)(base), t2; \
MOVBU (offset+3*1)(base), t3; \
SLL $8, t1; \
SLL $16, t2; \
SLL $24, t3; \
OR t1, t0; \
OR t3, t2; \
OR t2, t0, dst; \
MOVBU (offset+4*1)(base), t0; \
MOVBU (offset+5*1)(base), t1; \
MOVBU (offset+6*1)(base), t2; \
MOVBU (offset+7*1)(base), t3; \
SLL $32, t0; \
SLL $40, t1; \
SLL $48, t2; \
SLL $56, t3; \
OR t1, t0; \
OR t3, t2; \
OR t2, t0; \
OR t0, dst
// func update(state *macState, msg []byte)
TEXT ·update(SB), $0-32
MOV state+0(FP), X5
MOV msg_base+8(FP), X6
MOV msg_len+16(FP), X7
MOV $16, X8
AND $7, X6, X28
MOV (0*8)(X5), X9 // h0
MOV (1*8)(X5), X10 // h1
MOV (2*8)(X5), X11 // h2
MOV (3*8)(X5), X12 // r0
MOV (4*8)(X5), X13 // r1
BLT X7, X8, tail
loop:
BEQZ X28, aligned_load
LOAD64U(X6, 0*8, X16, X18, X19, X20, X15) // msg[0:8]
LOAD64U(X6, 1*8, X16, X18, X19, X20, X17) // msg[8:16]
JMP block
aligned_load:
MOV (0*8)(X6), X15 // msg[0:8]
MOV (1*8)(X6), X17 // msg[8:16]
block:
ADD X15, X9 // h0 (x1 + y1 = z1', if z1' < x1 then z1' overflow)
SLTU X15, X9, X19 // h0.carry
ADD X17, X10, X22
SLTU X17, X22, X23
ADD X22, X19, X10 // h1
SLTU X22, X10, X19
OR X23, X19 // h1.carry
ADD $1, X19
ADD X19, X11 // h2
ADD $16, X6 // msg = msg[16:]
multiply:
MULHU X9, X12, X16 // h0r0.hi
MUL X9, X12, X15 // h0r0.lo
MULHU X10, X12, X17 // h1r0.hi
MUL X10, X12, X14 // h1r0.lo
ADD X14, X16
SLTU X14, X16, X19
ADD X19, X17
MUL X11, X12, X20
ADD X17, X20
MULHU X9, X13, X17 // h0r1.hi
MUL X9, X13, X14 // h0r1.lo
ADD X14, X16
SLTU X14, X16, X19
ADD X19, X17
MOV X17, X9
MUL X11, X13, X21 // h2r1
MULHU X10, X13, X17 // h1r1.hi
MUL X10, X13, X14 // h1r1.lo
ADD X14, X20
ADD X17, X21, X22
SLTU X14, X20, X19
ADD X22, X19, X21
ADD X9, X20
SLTU X9, X20, X19
ADD X19, X21
AND $3, X20, X11
AND $-4, X20, X18
ADD X18, X15, X9
ADD X21, X16, X22
SLTU X18, X9, X19
SLTU X21, X22, X23
ADD X22, X19, X10
SLTU X22, X10, X19
OR X19, X23, X19
ADD X19, X11
SLL $62, X21, X22
SRL $2, X20, X23
SRL $2, X21, X21
OR X22, X23, X20
ADD X20, X9, X9
ADD X21, X10, X22
SLTU X20, X9, X19
SLTU X21, X22, X23
ADD X22, X19, X10
SLTU X22, X10, X19
OR X19, X23, X19
ADD X19, X11, X11
SUB $16, X7, X7
BGE X7, X8, loop
tail:
BEQ X7, X0, done
MOV $1, X15
MOV $0, X16
ADD X7, X6, X6
flush_buffer:
MOVBU -1(X6), X20
SRL $56, X15, X19
SLL $8, X16, X23
SLL $8, X15, X15
OR X19, X23, X16
XOR X20, X15
SUB $1, X7, X7
SUB $1, X6, X6
BNE X7, X0, flush_buffer
ADD X15, X9
SLTU X15, X9, X19
ADD X16, X10, X22
SLTU X16, X22, X23
ADD X22, X19, X10
SLTU X22, X10, X19
OR X23, X19
ADD X19, X11
MOV $16, X7
JMP multiply
done:
MOV X9, (0*8)(X5) // h0
MOV X10, (1*8)(X5)
MOV X11, (2*8)(X5)
RET
-12
View File
@@ -1,12 +0,0 @@
This package runs a set of the Wycheproof tests provided by
https://github.com/google/wycheproof.
The JSON test files live in
https://github.com/google/wycheproof/tree/master/testvectors
and are being fetched and cached at a pinned version every time
these tests are run. To change the version of the wycheproof
repository that is being used for testing, update wycheproofModVer.
The structs for these tests are generated from the
schemas provided in https://github.com/google/wycheproof/tree/master/schemas
using https://github.com/a-h/generate.
-176
View File
@@ -1,176 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
"testing"
"golang.org/x/crypto/chacha20poly1305"
)
func TestAEAD(t *testing.T) {
// AeadTestVector
type AeadTestVector struct {
// additional authenticated data
Aad string `json:"aad,omitempty"`
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// the ciphertext (without iv and tag)
Ct string `json:"ct,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// the nonce
Iv string `json:"iv,omitempty"`
// the key
Key string `json:"key,omitempty"`
// the plaintext
Msg string `json:"msg,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// the authentication tag
Tag string `json:"tag,omitempty"`
// Identifier of the test case
TcId int `json:"tcId,omitempty"`
}
// Notes a description of the labels used in the test vectors
type Notes struct {
}
// AeadTestGroup
type AeadTestGroup struct {
// the IV size in bits
IvSize int `json:"ivSize,omitempty"`
// the keySize in bits
KeySize int `json:"keySize,omitempty"`
// the expected size of the tag in bits
TagSize int `json:"tagSize,omitempty"`
Tests []*AeadTestVector `json:"tests,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// Root
type Root struct {
// the primitive tested in the test file
Algorithm string `json:"algorithm,omitempty"`
// the version of the test vectors.
GeneratorVersion string `json:"generatorVersion,omitempty"`
// additional documentation
Header []string `json:"header,omitempty"`
// a description of the labels used in the test vectors
Notes *Notes `json:"notes,omitempty"`
// the number of test vectors in this test
NumberOfTests int `json:"numberOfTests,omitempty"`
Schema interface{} `json:"schema,omitempty"`
TestGroups []*AeadTestGroup `json:"testGroups,omitempty"`
}
testSealOpen := func(t *testing.T, aead cipher.AEAD, tv *AeadTestVector, recoverBadNonce func()) {
defer recoverBadNonce()
iv, tag, ct, msg, aad := decodeHex(tv.Iv), decodeHex(tv.Tag), decodeHex(tv.Ct), decodeHex(tv.Msg), decodeHex(tv.Aad)
genCT := aead.Seal(nil, iv, msg, aad)
genMsg, err := aead.Open(nil, iv, genCT, aad)
if err != nil {
t.Errorf("failed to decrypt generated ciphertext: %s", err)
}
if !bytes.Equal(genMsg, msg) {
t.Errorf("unexpected roundtripped plaintext: got %x, want %x", genMsg, msg)
}
ctWithTag := append(ct, tag...)
msg2, err := aead.Open(nil, iv, ctWithTag, aad)
wantPass := shouldPass(tv.Result, tv.Flags, nil)
if !wantPass && err == nil {
t.Error("decryption succeeded when it should've failed")
} else if wantPass {
if err != nil {
t.Fatalf("decryption failed: %s", err)
}
if !bytes.Equal(genCT, ctWithTag) {
t.Errorf("generated ciphertext doesn't match expected: got %x, want %x", genCT, ctWithTag)
}
if !bytes.Equal(msg, msg2) {
t.Errorf("decrypted ciphertext doesn't match expected: got %x, want %x", msg2, msg)
}
}
}
vectors := map[string]func(*testing.T, []byte) cipher.AEAD{
"aes_gcm_test.json": func(t *testing.T, key []byte) cipher.AEAD {
aesCipher, err := aes.NewCipher(key)
if err != nil {
t.Fatalf("failed to construct cipher: %s", err)
}
aead, err := cipher.NewGCM(aesCipher)
if err != nil {
t.Fatalf("failed to construct cipher: %s", err)
}
return aead
},
"chacha20_poly1305_test.json": func(t *testing.T, key []byte) cipher.AEAD {
aead, err := chacha20poly1305.New(key)
if err != nil {
t.Fatalf("failed to construct cipher: %s", err)
}
return aead
},
"xchacha20_poly1305_test.json": func(t *testing.T, key []byte) cipher.AEAD {
aead, err := chacha20poly1305.NewX(key)
if err != nil {
t.Fatalf("failed to construct cipher: %s", err)
}
return aead
},
}
for file, cipherInit := range vectors {
var root Root
readTestVector(t, file, &root)
for _, tg := range root.TestGroups {
for _, tv := range tg.Tests {
testName := fmt.Sprintf("%s #%d", file, tv.TcId)
if tv.Comment != "" {
testName += fmt.Sprintf(" %s", tv.Comment)
}
t.Run(testName, func(t *testing.T) {
aead := cipherInit(t, decodeHex(tv.Key))
testSealOpen(t, aead, tv, func() {
// A bad nonce causes a panic in AEAD.Seal and AEAD.Open,
// so should be recovered. Fail the test if it broke for
// some other reason.
if r := recover(); r != nil {
if tg.IvSize/8 == aead.NonceSize() {
t.Error("unexpected panic")
}
}
})
})
}
}
}
}
-127
View File
@@ -1,127 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"fmt"
"testing"
)
func TestAesCbc(t *testing.T) {
// IndCpaTestVector
type IndCpaTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// the raw ciphertext (without IV)
Ct string `json:"ct,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// the initialization vector
Iv string `json:"iv,omitempty"`
// the key
Key string `json:"key,omitempty"`
// the plaintext
Msg string `json:"msg,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// Identifier of the test case
TcId int `json:"tcId,omitempty"`
}
// Notes a description of the labels used in the test vectors
type Notes struct {
}
// IndCpaTestGroup
type IndCpaTestGroup struct {
// the IV size in bits
IvSize int `json:"ivSize,omitempty"`
// the keySize in bits
KeySize int `json:"keySize,omitempty"`
// the expected size of the tag in bits
TagSize int `json:"tagSize,omitempty"`
Tests []*IndCpaTestVector `json:"tests,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// Root
type Root struct {
// the primitive tested in the test file
Algorithm string `json:"algorithm,omitempty"`
// the version of the test vectors.
GeneratorVersion string `json:"generatorVersion,omitempty"`
// additional documentation
Header []string `json:"header,omitempty"`
// a description of the labels used in the test vectors
Notes *Notes `json:"notes,omitempty"`
// the number of test vectors in this test
NumberOfTests int `json:"numberOfTests,omitempty"`
Schema interface{} `json:"schema,omitempty"`
TestGroups []*IndCpaTestGroup `json:"testGroups,omitempty"`
}
var root Root
readTestVector(t, "aes_cbc_pkcs5_test.json", &root)
for _, tg := range root.TestGroups {
tests:
for _, tv := range tg.Tests {
block, err := aes.NewCipher(decodeHex(tv.Key))
if err != nil {
t.Fatalf("#%d: %v", tv.TcId, err)
}
mode := cipher.NewCBCDecrypter(block, decodeHex(tv.Iv))
ct := decodeHex(tv.Ct)
if len(ct)%aes.BlockSize != 0 {
panic(fmt.Sprintf("#%d: ciphertext is not a multiple of the block size", tv.TcId))
}
mode.CryptBlocks(ct, ct) // decrypt the block in place
// Skip the tests that are broken due to bad padding. Panic if there are any
// tests left that are invalid for some other reason in the future, to
// evaluate what to do with those tests.
for _, flag := range tv.Flags {
if flag == "BadPadding" {
continue tests
}
}
if !shouldPass(tv.Result, tv.Flags, nil) {
panic(fmt.Sprintf("#%d: found an invalid test that is broken for some reason other than bad padding", tv.TcId))
}
// Remove the PKCS#5 padding from the given ciphertext to validate it
padding := ct[len(ct)-1]
paddingNum := int(padding)
for i := paddingNum; i > 0; i-- {
if ct[len(ct)-i] != padding { // panic if the padding is unexpectedly bad
panic(fmt.Sprintf("#%d: bad padding at index=%d of %v", tv.TcId, i, ct))
}
}
ct = ct[:len(ct)-paddingNum]
if got, want := hex.EncodeToString(ct), tv.Msg; got != want {
t.Errorf("#%d, type: %s, comment: %q, decoded ciphertext not equal: %s, want %s", tv.TcId, tv.Result, tv.Comment, got, want)
}
}
}
}
-9
View File
@@ -1,9 +0,0 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build boringcrypto
package wycheproof
const boringcryptoEnabled = true
-123
View File
@@ -1,123 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"crypto/dsa"
"testing"
wdsa "golang.org/x/crypto/internal/wycheproof/internal/dsa"
)
func TestDsa(t *testing.T) {
// AsnSignatureTestVector
type AsnSignatureTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// The message to sign
Msg string `json:"msg,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// An ASN encoded signature for msg
Sig string `json:"sig,omitempty"`
// Identifier of the test case
TcId int `json:"tcId,omitempty"`
}
// DsaPublicKey
type DsaPublicKey struct {
// the generator of the multiplicative subgroup
G string `json:"g,omitempty"`
// the key size in bits
KeySize int `json:"keySize,omitempty"`
// the modulus p
P string `json:"p,omitempty"`
// the order of the generator g
Q string `json:"q,omitempty"`
// the key type
Type string `json:"type,omitempty"`
// the public key value
Y string `json:"y,omitempty"`
}
// DsaTestGroup
type DsaTestGroup struct {
// unenocded DSA public key
Key *DsaPublicKey `json:"key,omitempty"`
// DER encoded public key
KeyDer string `json:"keyDer,omitempty"`
// Pem encoded public key
KeyPem string `json:"keyPem,omitempty"`
// the hash function used for DSA
Sha string `json:"sha,omitempty"`
Tests []*AsnSignatureTestVector `json:"tests,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// Notes a description of the labels used in the test vectors
type Notes struct {
}
// Root
type Root struct {
// the primitive tested in the test file
Algorithm string `json:"algorithm,omitempty"`
// the version of the test vectors.
GeneratorVersion string `json:"generatorVersion,omitempty"`
// additional documentation
Header []string `json:"header,omitempty"`
// a description of the labels used in the test vectors
Notes *Notes `json:"notes,omitempty"`
// the number of test vectors in this test
NumberOfTests int `json:"numberOfTests,omitempty"`
Schema interface{} `json:"schema,omitempty"`
TestGroups []*DsaTestGroup `json:"testGroups,omitempty"`
}
flagsShouldPass := map[string]bool{
// An encoded ASN.1 integer missing a leading zero is invalid, but accepted by some implementations.
"NoLeadingZero": false,
}
var root Root
readTestVector(t, "dsa_test.json", &root)
for _, tg := range root.TestGroups {
pub := decodePublicKey(tg.KeyDer).(*dsa.PublicKey)
h := parseHash(tg.Sha).New()
for _, sig := range tg.Tests {
h.Reset()
h.Write(decodeHex(sig.Msg))
hashed := h.Sum(nil)
hashed = hashed[:pub.Q.BitLen()/8] // Truncate to the byte-length of the subgroup (Q)
got := wdsa.VerifyASN1(pub, hashed, decodeHex(sig.Sig))
if want := shouldPass(sig.Result, sig.Flags, flagsShouldPass); got != want {
t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
}
}
}
}
-140
View File
@@ -1,140 +0,0 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"bytes"
"crypto/ecdh"
"fmt"
"testing"
)
func TestECDHStdLib(t *testing.T) {
type ECDHTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// the private key
Private string `json:"private,omitempty"`
// Encoded public key
Public string `json:"public,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// The shared secret key
Shared string `json:"shared,omitempty"`
// Identifier of the test case
TcID int `json:"tcId,omitempty"`
}
type ECDHTestGroup struct {
Curve string `json:"curve,omitempty"`
Tests []*ECDHTestVector `json:"tests,omitempty"`
}
type Root struct {
TestGroups []*ECDHTestGroup `json:"testGroups,omitempty"`
}
flagsShouldPass := map[string]bool{
// We don't support compressed points.
"CompressedPoint": false,
// We don't support decoding custom curves.
"UnnamedCurve": false,
// WrongOrder and UnusedParam are only found with UnnamedCurve.
"WrongOrder": false,
"UnusedParam": false,
// X25519 specific flags
"Twist": true,
"SmallPublicKey": false,
"LowOrderPublic": false,
"ZeroSharedSecret": false,
"NonCanonicalPublic": true,
}
// curveToCurve is a map of all elliptic curves supported
// by crypto/elliptic, which can subsequently be parsed and tested.
curveToCurve := map[string]ecdh.Curve{
"secp256r1": ecdh.P256(),
"secp384r1": ecdh.P384(),
"secp521r1": ecdh.P521(),
"curve25519": ecdh.X25519(),
}
curveToKeySize := map[string]int{
"secp256r1": 32,
"secp384r1": 48,
"secp521r1": 66,
"curve25519": 32,
}
for _, f := range []string{
"ecdh_secp256r1_ecpoint_test.json",
"ecdh_secp384r1_ecpoint_test.json",
"ecdh_secp521r1_ecpoint_test.json",
"x25519_test.json",
} {
var root Root
readTestVector(t, f, &root)
for _, tg := range root.TestGroups {
if _, ok := curveToCurve[tg.Curve]; !ok {
continue
}
for _, tt := range tg.Tests {
tg, tt := tg, tt
t.Run(fmt.Sprintf("%s/%d", tg.Curve, tt.TcID), func(t *testing.T) {
t.Logf("Type: %v", tt.Result)
t.Logf("Flags: %q", tt.Flags)
t.Log(tt.Comment)
shouldPass := shouldPass(tt.Result, tt.Flags, flagsShouldPass)
curve := curveToCurve[tg.Curve]
p := decodeHex(tt.Public)
pub, err := curve.NewPublicKey(p)
if err != nil {
if shouldPass {
t.Errorf("NewPublicKey: %v", err)
}
return
}
privBytes := decodeHex(tt.Private)
if len(privBytes) != curveToKeySize[tg.Curve] {
t.Skipf("non-standard key size %d", len(privBytes))
}
priv, err := curve.NewPrivateKey(privBytes)
if err != nil {
if shouldPass {
t.Errorf("NewPrivateKey: %v", err)
}
return
}
shared := decodeHex(tt.Shared)
x, err := priv.ECDH(pub)
if err != nil {
if tg.Curve == "curve25519" && !shouldPass {
// ECDH is expected to only return an error when using X25519,
// in all other cases an error is unexpected.
return
}
t.Fatalf("ECDH: %v", err)
}
if bytes.Equal(shared, x) != shouldPass {
if shouldPass {
t.Errorf("ECDH = %x, want %x", shared, x)
} else {
t.Errorf("ECDH = %x, want anything else", shared)
}
}
})
}
}
}
}
-163
View File
@@ -1,163 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/asn1"
"errors"
"fmt"
"testing"
"golang.org/x/crypto/cryptobyte"
casn1 "golang.org/x/crypto/cryptobyte/asn1"
)
func TestECDH(t *testing.T) {
type ECDHTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// the private key
Private string `json:"private,omitempty"`
// Encoded public key
Public string `json:"public,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// The shared secret key
Shared string `json:"shared,omitempty"`
// Identifier of the test case
TcID int `json:"tcId,omitempty"`
}
type ECDHTestGroup struct {
Curve string `json:"curve,omitempty"`
Tests []*ECDHTestVector `json:"tests,omitempty"`
}
type Root struct {
TestGroups []*ECDHTestGroup `json:"testGroups,omitempty"`
}
flagsShouldPass := map[string]bool{
// ParsePKIXPublicKey doesn't support compressed points, but we test
// them against UnmarshalCompressed anyway.
"CompressedPoint": true,
// We don't support decoding custom curves.
"UnnamedCurve": false,
// WrongOrder and UnusedParam are only found with UnnamedCurve.
"WrongOrder": false,
"UnusedParam": false,
}
// supportedCurves is a map of all elliptic curves supported
// by crypto/elliptic, which can subsequently be parsed and tested.
supportedCurves := map[string]bool{
"secp224r1": true,
"secp256r1": true,
"secp384r1": true,
"secp521r1": true,
}
var root Root
readTestVector(t, "ecdh_test.json", &root)
for _, tg := range root.TestGroups {
if !supportedCurves[tg.Curve] {
continue
}
for _, tt := range tg.Tests {
tg, tt := tg, tt
t.Run(fmt.Sprintf("%s/%d", tg.Curve, tt.TcID), func(t *testing.T) {
t.Logf("Type: %v", tt.Result)
t.Logf("Flags: %q", tt.Flags)
t.Log(tt.Comment)
shouldPass := shouldPass(tt.Result, tt.Flags, flagsShouldPass)
p := decodeHex(tt.Public)
pp, err := x509.ParsePKIXPublicKey(p)
if err != nil {
pp, err = decodeCompressedPKIX(p)
}
if err != nil {
if shouldPass {
t.Errorf("unexpected parsing error: %s", err)
}
return
}
pub := pp.(*ecdsa.PublicKey)
priv := decodeHex(tt.Private)
shared := decodeHex(tt.Shared)
x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, priv)
xBytes := make([]byte, (pub.Curve.Params().BitSize+7)/8)
got := bytes.Equal(shared, x.FillBytes(xBytes))
if want := shouldPass; got != want {
t.Errorf("wanted success %v, got %v", want, got)
}
})
}
}
}
func decodeCompressedPKIX(der []byte) (interface{}, error) {
s := cryptobyte.String(der)
var s1, s2 cryptobyte.String
var algoOID, namedCurveOID asn1.ObjectIdentifier
var pointDER []byte
if !s.ReadASN1(&s1, casn1.SEQUENCE) || !s.Empty() ||
!s1.ReadASN1(&s2, casn1.SEQUENCE) ||
!s2.ReadASN1ObjectIdentifier(&algoOID) ||
!s2.ReadASN1ObjectIdentifier(&namedCurveOID) || !s2.Empty() ||
!s1.ReadASN1BitStringAsBytes(&pointDER) || !s1.Empty() {
return nil, errors.New("failed to parse PKIX structure")
}
if !algoOID.Equal(oidPublicKeyECDSA) {
return nil, errors.New("wrong algorithm OID")
}
namedCurve := namedCurveFromOID(namedCurveOID)
if namedCurve == nil {
return nil, errors.New("unsupported elliptic curve")
}
x, y := elliptic.UnmarshalCompressed(namedCurve, pointDER)
if x == nil {
return nil, errors.New("failed to unmarshal elliptic curve point")
}
pub := &ecdsa.PublicKey{
Curve: namedCurve,
X: x,
Y: y,
}
return pub, nil
}
var (
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33}
oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34}
oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35}
)
func namedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve {
switch {
case oid.Equal(oidNamedCurveP224):
return elliptic.P224()
case oid.Equal(oidNamedCurveP256):
return elliptic.P256()
case oid.Equal(oidNamedCurveP384):
return elliptic.P384()
case oid.Equal(oidNamedCurveP521):
return elliptic.P521()
}
return nil
}
-105
View File
@@ -1,105 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"crypto/ecdsa"
"math/big"
"testing"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)
func TestECDSA(t *testing.T) {
type ASNSignatureTestVector struct {
// A brief description of the test case
Comment string `json:"comment"`
// A list of flags
Flags []string `json:"flags"`
// The message to sign
Msg string `json:"msg"`
// Test result
Result string `json:"result"`
// An ASN.1 encoded signature for msg
Sig string `json:"sig"`
// Identifier of the test case
TcID int `json:"tcId"`
}
type ECPublicKey struct {
// The EC group used by this public key
Curve interface{} `json:"curve"`
}
type ECDSATestGroup struct {
// Unencoded EC public key
Key *ECPublicKey `json:"key"`
// DER encoded public key
KeyDER string `json:"keyDer"`
// the hash function used for ECDSA
SHA string `json:"sha"`
Tests []*ASNSignatureTestVector `json:"tests"`
}
type Root struct {
TestGroups []*ECDSATestGroup `json:"testGroups"`
}
flagsShouldPass := map[string]bool{
// An encoded ASN.1 integer missing a leading zero is invalid, but
// accepted by some implementations.
"MissingZero": false,
// A signature using a weaker hash than the EC params is not a security
// risk, as long as the hash is secure.
// https://www.imperialviolet.org/2014/05/25/strengthmatching.html
"WeakHash": true,
}
// supportedCurves is a map of all elliptic curves supported
// by crypto/elliptic, which can subsequently be parsed and tested.
supportedCurves := map[string]bool{
"secp224r1": true,
"secp256r1": true,
"secp384r1": true,
"secp521r1": true,
}
var root Root
readTestVector(t, "ecdsa_test.json", &root)
for _, tg := range root.TestGroups {
curve := tg.Key.Curve.(string)
if !supportedCurves[curve] {
continue
}
pub := decodePublicKey(tg.KeyDER).(*ecdsa.PublicKey)
h := parseHash(tg.SHA).New()
for _, sig := range tg.Tests {
h.Reset()
h.Write(decodeHex(sig.Msg))
hashed := h.Sum(nil)
sigBytes := decodeHex(sig.Sig)
got := ecdsa.VerifyASN1(pub, hashed, sigBytes)
if want := shouldPass(sig.Result, sig.Flags, flagsShouldPass); got != want {
t.Errorf("tcid: %d, type: %s, comment: %q, VerifyASN1 wanted success: %t", sig.TcID, sig.Result, sig.Comment, want)
}
var r, s big.Int
var inner cryptobyte.String
input := cryptobyte.String(sigBytes)
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
!input.Empty() ||
!inner.ReadASN1Integer(&r) ||
!inner.ReadASN1Integer(&s) ||
!inner.Empty() {
continue
}
got = ecdsa.Verify(pub, hashed, &r, &s)
if want := shouldPass(sig.Result, sig.Flags, flagsShouldPass); got != want {
t.Errorf("tcid: %d, type: %s, comment: %q, Verify wanted success: %t", sig.TcID, sig.Result, sig.Comment, want)
}
}
}
}
-97
View File
@@ -1,97 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"crypto/ed25519"
"testing"
)
func TestEddsa(t *testing.T) {
// Jwk the private key in webcrypto format
type Jwk struct {
}
// Key unencoded key pair
type Key struct {
}
// Notes a description of the labels used in the test vectors
type Notes struct {
}
// SignatureTestVector
type SignatureTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// The message to sign
Msg string `json:"msg,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// A signature for msg
Sig string `json:"sig,omitempty"`
// Identifier of the test case
TcId int `json:"tcId,omitempty"`
}
// EddsaTestGroup
type EddsaTestGroup struct {
// the private key in webcrypto format
Jwk *Jwk `json:"jwk,omitempty"`
// unencoded key pair
Key *Key `json:"key,omitempty"`
// Asn encoded public key
KeyDer string `json:"keyDer,omitempty"`
// Pem encoded public key
KeyPem string `json:"keyPem,omitempty"`
Tests []*SignatureTestVector `json:"tests,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// Root
type Root struct {
// the primitive tested in the test file
Algorithm string `json:"algorithm,omitempty"`
// the version of the test vectors.
GeneratorVersion string `json:"generatorVersion,omitempty"`
// additional documentation
Header []string `json:"header,omitempty"`
// a description of the labels used in the test vectors
Notes *Notes `json:"notes,omitempty"`
// the number of test vectors in this test
NumberOfTests int `json:"numberOfTests,omitempty"`
Schema interface{} `json:"schema,omitempty"`
TestGroups []*EddsaTestGroup `json:"testGroups,omitempty"`
}
var root Root
readTestVector(t, "eddsa_test.json", &root)
for _, tg := range root.TestGroups {
pub := decodePublicKey(tg.KeyDer).(ed25519.PublicKey)
for _, sig := range tg.Tests {
got := ed25519.Verify(pub, decodeHex(sig.Msg), decodeHex(sig.Sig))
if want := shouldPass(sig.Result, sig.Flags, nil); got != want {
t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
}
}
}
}
-111
View File
@@ -1,111 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"bytes"
"io"
"testing"
"golang.org/x/crypto/hkdf"
)
func TestHkdf(t *testing.T) {
// HkdfTestVector
type HkdfTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// the key (input key material)
Ikm string `json:"ikm,omitempty"`
// additional information used in the key derivation
Info string `json:"info,omitempty"`
// the generated bytes (output key material)
Okm string `json:"okm,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// the salt for the key derivation
Salt string `json:"salt,omitempty"`
// the size of the output in bytes
Size int `json:"size,omitempty"`
// Identifier of the test case
TcId int `json:"tcId,omitempty"`
}
// Notes a description of the labels used in the test vectors
type Notes struct {
}
// HkdfTestGroup
type HkdfTestGroup struct {
// the size of the ikm in bits
KeySize int `json:"keySize,omitempty"`
Tests []*HkdfTestVector `json:"tests,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// Root
type Root struct {
// the primitive tested in the test file
Algorithm string `json:"algorithm,omitempty"`
// the version of the test vectors.
GeneratorVersion string `json:"generatorVersion,omitempty"`
// additional documentation
Header []string `json:"header,omitempty"`
// a description of the labels used in the test vectors
Notes *Notes `json:"notes,omitempty"`
// the number of test vectors in this test
NumberOfTests int `json:"numberOfTests,omitempty"`
Schema interface{} `json:"schema,omitempty"`
TestGroups []*HkdfTestGroup `json:"testGroups,omitempty"`
}
fileHashAlgorithms := map[string]string{
"hkdf_sha1_test.json": "SHA-1",
"hkdf_sha256_test.json": "SHA-256",
"hkdf_sha384_test.json": "SHA-384",
"hkdf_sha512_test.json": "SHA-512",
}
for f := range fileHashAlgorithms {
var root Root
readTestVector(t, f, &root)
for _, tg := range root.TestGroups {
for _, tv := range tg.Tests {
h := parseHash(fileHashAlgorithms[f]).New
hkdf := hkdf.New(h, decodeHex(tv.Ikm), decodeHex(tv.Salt), decodeHex(tv.Info))
key := make([]byte, tv.Size)
wantPass := shouldPass(tv.Result, tv.Flags, nil)
_, err := io.ReadFull(hkdf, key)
if (err == nil) != wantPass {
t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t, got: %v", tv.TcId, tv.Result, tv.Comment, wantPass, err)
}
if err != nil {
continue // don't validate output text if reading failed
}
if got, want := key, decodeHex(tv.Okm); !bytes.Equal(got, want) {
t.Errorf("tcid: %d, type: %s, comment: %q, output bytes don't match", tv.TcId, tv.Result, tv.Comment)
}
}
}
}
}
-105
View File
@@ -1,105 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"crypto/hmac"
"testing"
)
func TestHMAC(t *testing.T) {
// MacTestVector
type MacTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// the key
Key string `json:"key,omitempty"`
// the plaintext
Msg string `json:"msg,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// the authentication tag
Tag string `json:"tag,omitempty"`
// Identifier of the test case
TcId int `json:"tcId,omitempty"`
}
// MacTestGroup
type MacTestGroup struct {
// the keySize in bits
KeySize int `json:"keySize,omitempty"`
// the expected size of the tag in bits
TagSize int `json:"tagSize,omitempty"`
Tests []*MacTestVector `json:"tests,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// Notes a description of the labels used in the test vectors
type Notes struct {
}
// Root
type Root struct {
// the primitive tested in the test file
Algorithm string `json:"algorithm,omitempty"`
// the version of the test vectors.
GeneratorVersion string `json:"generatorVersion,omitempty"`
// additional documentation
Header []string `json:"header,omitempty"`
// a description of the labels used in the test vectors
Notes *Notes `json:"notes,omitempty"`
// the number of test vectors in this test
NumberOfTests int `json:"numberOfTests,omitempty"`
Schema interface{} `json:"schema,omitempty"`
TestGroups []*MacTestGroup `json:"testGroups,omitempty"`
}
fileHashAlgs := map[string]string{
"hmac_sha1_test.json": "SHA-1",
"hmac_sha224_test.json": "SHA-224",
"hmac_sha256_test.json": "SHA-256",
"hmac_sha384_test.json": "SHA-384",
"hmac_sha512_test.json": "SHA-512",
}
for f := range fileHashAlgs {
var root Root
readTestVector(t, f, &root)
for _, tg := range root.TestGroups {
h := parseHash(fileHashAlgs[f])
// Skip test vectors where the tag length does not equal the
// hash length, since crypto/hmac does not support generating
// these truncated tags.
if tg.TagSize/8 != h.Size() {
continue
}
for _, tv := range tg.Tests {
hm := hmac.New(h.New, decodeHex(tv.Key))
hm.Write(decodeHex(tv.Msg))
tag := hm.Sum(nil)
got := hmac.Equal(decodeHex(tv.Tag), tag)
if want := shouldPass(tv.Result, tv.Flags, nil); want != got {
t.Errorf("%s, tcid: %d, type: %s, comment: %q, unexpected result", f, tv.TcId, tv.Result, tv.Comment)
}
}
}
}
}
-33
View File
@@ -1,33 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package dsa provides an internal version of dsa.Verify
// that is used for the Wycheproof tests.
package dsa
import (
"crypto/dsa"
"math/big"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)
// VerifyASN1 verifies the ASN1 encoded signature, sig, of hash using the
// public key, pub. Its return value records whether the signature is valid.
func VerifyASN1(pub *dsa.PublicKey, hash, sig []byte) bool {
var (
r, s = &big.Int{}, &big.Int{}
inner cryptobyte.String
)
input := cryptobyte.String(sig)
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
!input.Empty() ||
!inner.ReadASN1Integer(r) ||
!inner.ReadASN1Integer(s) ||
!inner.Empty() {
return false
}
return dsa.Verify(pub, hash, r, s)
}
-9
View File
@@ -1,9 +0,0 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !boringcrypto
package wycheproof
const boringcryptoEnabled = false
@@ -1,149 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"bytes"
"crypto/rsa"
"crypto/x509"
"fmt"
"testing"
)
func TestRSAOAEPDecrypt(t *testing.T) {
// Notes a description of the labels used in the test vectors
type Notes struct {
}
// RsaesOaepTestVector
type RsaesOaepTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// An encryption of msg
Ct string `json:"ct,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// The label used for the encryption
Label string `json:"label,omitempty"`
// The encrypted message
Msg string `json:"msg,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// Identifier of the test case
TcId int `json:"tcId,omitempty"`
}
// RsaesOaepTestGroup
type RsaesOaepTestGroup struct {
// The private exponent
D string `json:"d,omitempty"`
// The public exponent
E string `json:"e,omitempty"`
// the message generating function (e.g. MGF1)
Mgf string `json:"mgf,omitempty"`
// The hash function used for the message generating function.
MgfSha string `json:"mgfSha,omitempty"`
// The modulus of the key
N string `json:"n,omitempty"`
// Pem encoded private key
PrivateKeyPem string `json:"privateKeyPem,omitempty"`
// Pkcs 8 encoded private key.
PrivateKeyPkcs8 string `json:"privateKeyPkcs8,omitempty"`
// The hash function for hashing the label.
Sha string `json:"sha,omitempty"`
Tests []*RsaesOaepTestVector `json:"tests,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// Root
type Root struct {
// the primitive tested in the test file
Algorithm string `json:"algorithm,omitempty"`
// the version of the test vectors.
GeneratorVersion string `json:"generatorVersion,omitempty"`
// additional documentation
Header []string `json:"header,omitempty"`
// a description of the labels used in the test vectors
Notes *Notes `json:"notes,omitempty"`
// the number of test vectors in this test
NumberOfTests int `json:"numberOfTests,omitempty"`
Schema interface{} `json:"schema,omitempty"`
TestGroups []*RsaesOaepTestGroup `json:"testGroups,omitempty"`
}
// rsa.DecryptOAEP doesn't support using a different hash for the
// MGF and the label, so skip all of the test vectors that use
// these unbalanced constructions. rsa_oaep_misc_test.json contains
// both balanced and unbalanced constructions so in that case
// we just filter out any test groups where MgfSha != Sha
files := []string{
"rsa_oaep_2048_sha1_mgf1sha1_test.json",
"rsa_oaep_2048_sha224_mgf1sha224_test.json",
"rsa_oaep_2048_sha256_mgf1sha256_test.json",
"rsa_oaep_2048_sha384_mgf1sha384_test.json",
"rsa_oaep_2048_sha512_mgf1sha512_test.json",
"rsa_oaep_3072_sha256_mgf1sha256_test.json",
"rsa_oaep_3072_sha512_mgf1sha512_test.json",
"rsa_oaep_4096_sha256_mgf1sha256_test.json",
"rsa_oaep_4096_sha512_mgf1sha512_test.json",
"rsa_oaep_misc_test.json",
}
flagsShouldPass := map[string]bool{
// rsa.DecryptOAEP happily supports small key sizes
"SmallModulus": true,
}
for _, f := range files {
var root Root
readTestVector(t, f, &root)
for _, tg := range root.TestGroups {
if tg.MgfSha != tg.Sha {
continue
}
priv, err := x509.ParsePKCS8PrivateKey(decodeHex(tg.PrivateKeyPkcs8))
if err != nil {
t.Fatalf("%s failed to parse PKCS #8 private key: %s", f, err)
}
hash := parseHash(tg.Sha)
for _, tv := range tg.Tests {
t.Run(fmt.Sprintf("%s #%d", f, tv.TcId), func(t *testing.T) {
wantPass := shouldPass(tv.Result, tv.Flags, flagsShouldPass)
plaintext, err := rsa.DecryptOAEP(hash.New(), nil, priv.(*rsa.PrivateKey), decodeHex(tv.Ct), decodeHex(tv.Label))
if wantPass {
if err != nil {
t.Fatalf("comment: %s, expected success: %s", tv.Comment, err)
}
if !bytes.Equal(plaintext, decodeHex(tv.Msg)) {
t.Errorf("comment: %s, unexpected plaintext: got %x, want %s", tv.Comment, plaintext, tv.Msg)
}
} else if err == nil {
t.Errorf("comment: %s, expected failure", tv.Comment)
}
})
}
}
}
}
-169
View File
@@ -1,169 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"crypto/rsa"
"testing"
)
func TestRsaPss(t *testing.T) {
// KeyJwk Public key in JWK format
type KeyJwk struct {
}
// Notes a description of the labels used in the test vectors
type Notes struct {
}
// SignatureTestVector
type SignatureTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// The message to sign
Msg string `json:"msg,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// A signature for msg
Sig string `json:"sig,omitempty"`
// Identifier of the test case
TcId int `json:"tcId,omitempty"`
}
// RsassaPkcs1TestGroup
type RsassaPkcs1TestGroup struct {
// The private exponent
D string `json:"d,omitempty"`
// The public exponent
E string `json:"e,omitempty"`
// ASN encoding of the sequence [n, e]
KeyAsn string `json:"keyAsn,omitempty"`
// ASN encoding of the public key
KeyDer string `json:"keyDer,omitempty"`
// Public key in JWK format
KeyJwk *KeyJwk `json:"keyJwk,omitempty"`
// Pem encoded public key
KeyPem string `json:"keyPem,omitempty"`
// the size of the modulus in bits
KeySize int `json:"keySize,omitempty"`
// The modulus of the key
N string `json:"n,omitempty"`
// The salt length
SLen int `json:"sLen,omitempty"`
// the hash function used for the message
Sha string `json:"sha,omitempty"`
Tests []*SignatureTestVector `json:"tests,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// Root
type Root struct {
// the primitive tested in the test file
Algorithm string `json:"algorithm,omitempty"`
// the version of the test vectors.
GeneratorVersion string `json:"generatorVersion,omitempty"`
// additional documentation
Header []string `json:"header,omitempty"`
// a description of the labels used in the test vectors
Notes *Notes `json:"notes,omitempty"`
// the number of test vectors in this test
NumberOfTests int `json:"numberOfTests,omitempty"`
Schema interface{} `json:"schema,omitempty"`
TestGroups []*RsassaPkcs1TestGroup `json:"testGroups,omitempty"`
}
flagsShouldPass := map[string]bool{
// A signature using a weaker hash than the EC params is not a security risk, as long as the hash is secure.
// https://www.imperialviolet.org/2014/05/25/strengthmatching.html
"WeakHash": true,
}
// filesOverrideToPassZeroSLen is a map of all test files
// and which TcIds that should be overridden to pass if the
// rsa.PSSOptions.SaltLength is zero.
// These tests expect a failure with a PSSOptions.SaltLength: 0
// and a signature that uses a different salt length. However,
// a salt length of 0 is defined as rsa.PSSSaltLengthAuto which
// works deterministically to auto-detect the length when
// verifying, so these tests actually pass as they should.
filesOverrideToPassZeroSLen := map[string][]int{
"rsa_pss_2048_sha1_mgf1_20_test.json": []int{46, 47},
"rsa_pss_2048_sha256_mgf1_0_test.json": []int{67, 68},
"rsa_pss_2048_sha256_mgf1_32_test.json": []int{67, 68},
"rsa_pss_3072_sha256_mgf1_32_test.json": []int{67, 68},
"rsa_pss_4096_sha256_mgf1_32_test.json": []int{67, 68},
"rsa_pss_4096_sha512_mgf1_32_test.json": []int{136, 137},
// "rsa_pss_misc_test.json": nil, // TODO: This ones seems to be broken right now, but can enable later on.
}
if !boringcryptoEnabled {
// boringcrypto doesn't support the truncated SHA-512 hashes, so only
// test them if boringcrypto isn't enabled.
filesOverrideToPassZeroSLen["rsa_pss_2048_sha512_256_mgf1_28_test.json"] = []int{13, 14, 15}
filesOverrideToPassZeroSLen["rsa_pss_2048_sha512_256_mgf1_32_test.json"] = []int{13, 14}
}
for f := range filesOverrideToPassZeroSLen {
var root Root
readTestVector(t, f, &root)
for _, tg := range root.TestGroups {
pub := decodePublicKey(tg.KeyDer).(*rsa.PublicKey)
ch := parseHash(tg.Sha)
h := ch.New()
opts := &rsa.PSSOptions{
Hash: ch,
SaltLength: rsa.PSSSaltLengthAuto,
}
// Run all the tests twice: the first time with the salt length
// as PSSSaltLengthAuto, and the second time with the salt length
// explicitly set to tg.SLen.
for i := 0; i < 2; i++ {
for _, sig := range tg.Tests {
h.Reset()
h.Write(decodeHex(sig.Msg))
hashed := h.Sum(nil)
err := rsa.VerifyPSS(pub, ch, hashed, decodeHex(sig.Sig), opts)
want := shouldPass(sig.Result, sig.Flags, flagsShouldPass)
if opts.SaltLength == 0 {
for _, id := range filesOverrideToPassZeroSLen[f] {
if sig.TcId == id {
want = true
break
}
}
}
if (err == nil) != want {
t.Errorf("file: %v, tcid: %d, type: %s, opts.SaltLength: %v, comment: %q, wanted success: %t", f, sig.TcId, sig.Result, opts.SaltLength, sig.Comment, want)
}
}
// Update opts.SaltLength for the second run of the tests.
opts.SaltLength = tg.SLen
}
}
}
}
-123
View File
@@ -1,123 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wycheproof
import (
"crypto/rsa"
"testing"
)
func TestRsa(t *testing.T) {
// KeyJwk Public key in JWK format
type KeyJwk struct {
}
// Notes a description of the labels used in the test vectors
type Notes struct {
}
// SignatureTestVector
type SignatureTestVector struct {
// A brief description of the test case
Comment string `json:"comment,omitempty"`
// A list of flags
Flags []string `json:"flags,omitempty"`
// The message to sign
Msg string `json:"msg,omitempty"`
// Test result
Result string `json:"result,omitempty"`
// A signature for msg
Sig string `json:"sig,omitempty"`
// Identifier of the test case
TcId int `json:"tcId,omitempty"`
}
// RsassaPkcs1TestGroup
type RsassaPkcs1TestGroup struct {
// The private exponent
D string `json:"d,omitempty"`
// The public exponent
E string `json:"e,omitempty"`
// ASN encoding of the sequence [n, e]
KeyAsn string `json:"keyAsn,omitempty"`
// ASN encoding of the public key
KeyDer string `json:"keyDer,omitempty"`
// Public key in JWK format
KeyJwk *KeyJwk `json:"keyJwk,omitempty"`
// Pem encoded public key
KeyPem string `json:"keyPem,omitempty"`
// the size of the modulus in bits
KeySize int `json:"keySize,omitempty"`
// The modulus of the key
N string `json:"n,omitempty"`
// the hash function used for the message
Sha string `json:"sha,omitempty"`
Tests []*SignatureTestVector `json:"tests,omitempty"`
Type interface{} `json:"type,omitempty"`
}
// Root
type Root struct {
// the primitive tested in the test file
Algorithm string `json:"algorithm,omitempty"`
// the version of the test vectors.
GeneratorVersion string `json:"generatorVersion,omitempty"`
// additional documentation
Header []string `json:"header,omitempty"`
// a description of the labels used in the test vectors
Notes *Notes `json:"notes,omitempty"`
// the number of test vectors in this test
NumberOfTests int `json:"numberOfTests,omitempty"`
Schema interface{} `json:"schema,omitempty"`
TestGroups []*RsassaPkcs1TestGroup `json:"testGroups,omitempty"`
}
flagsShouldPass := map[string]bool{
// Omitting the parameter field in an ASN encoded integer is a legacy behavior.
"MissingNull": false,
// Keys with a modulus less than 2048 bits are supported by crypto/rsa.
"SmallModulus": true,
// Small public keys are supported by crypto/rsa.
"SmallPublicKey": true,
}
var root Root
readTestVector(t, "rsa_signature_test.json", &root)
for _, tg := range root.TestGroups {
pub := decodePublicKey(tg.KeyDer).(*rsa.PublicKey)
ch := parseHash(tg.Sha)
h := ch.New()
for _, sig := range tg.Tests {
h.Reset()
h.Write(decodeHex(sig.Msg))
hashed := h.Sum(nil)
err := rsa.VerifyPKCS1v15(pub, ch, hashed, decodeHex(sig.Sig))
want := shouldPass(sig.Result, sig.Flags, flagsShouldPass)
if (err == nil) != want {
t.Errorf("tcid: %d, type: %s, comment: %q, wanted success: %t", sig.TcId, sig.Result, sig.Comment, want)
}
}
}
}
-141
View File
@@ -1,141 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package wycheproof runs a set of the Wycheproof tests
// provided by https://github.com/google/wycheproof.
package wycheproof
import (
"crypto"
"crypto/x509"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"testing"
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"
)
const wycheproofModVer = "v0.0.0-20191219022705-2196000605e4"
var wycheproofTestVectorsDir string
func TestMain(m *testing.M) {
flag.Parse()
if flag.Lookup("test.short").Value.(flag.Getter).Get().(bool) {
log.Println("skipping test that downloads testdata via 'go mod download' in short mode")
os.Exit(0)
}
if _, err := exec.LookPath("go"); err != nil {
log.Printf("skipping test because 'go' command is unavailable: %v", err)
os.Exit(0)
}
if os.Getenv("GO_BUILDER_FLAKY_NET") != "" {
log.Printf("skipping test because GO_BUILDER_FLAKY_NET is set")
os.Exit(0)
}
// Download the JSON test files from github.com/google/wycheproof
// using `go mod download -json` so the cached source of the testdata
// can be used in the following tests.
path := "github.com/google/wycheproof@" + wycheproofModVer
cmd := exec.Command("go", "mod", "download", "-json", path)
output, err := cmd.Output()
if err != nil {
log.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output)
}
var dm struct {
Dir string // absolute path to cached source root directory
}
if err := json.Unmarshal(output, &dm); err != nil {
log.Fatal(err)
}
// Now that the module has been downloaded, use the absolute path of the
// cached source as the root directory for all tests going forward.
wycheproofTestVectorsDir = filepath.Join(dm.Dir, "testvectors")
os.Exit(m.Run())
}
func readTestVector(t *testing.T, f string, dest interface{}) {
b, err := os.ReadFile(filepath.Join(wycheproofTestVectorsDir, f))
if err != nil {
t.Fatalf("failed to read json file: %v", err)
}
if err := json.Unmarshal(b, &dest); err != nil {
t.Fatalf("failed to unmarshal json file: %v", err)
}
}
func decodeHex(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}
func decodePublicKey(der string) interface{} {
d := decodeHex(der)
pub, err := x509.ParsePKIXPublicKey(d)
if err != nil {
panic(fmt.Sprintf("failed to parse DER encoded public key: %v", err))
}
return pub
}
func parseHash(h string) crypto.Hash {
switch h {
case "SHA-1":
return crypto.SHA1
case "SHA-256":
return crypto.SHA256
case "SHA-224":
return crypto.SHA224
case "SHA-384":
return crypto.SHA384
case "SHA-512":
return crypto.SHA512
case "SHA-512/224":
return crypto.SHA512_224
case "SHA-512/256":
return crypto.SHA512_256
default:
panic(fmt.Sprintf("could not identify SHA hash algorithm: %q", h))
}
}
// shouldPass returns whether or not the test should pass.
// flagsShouldPass is a map associated with whether or not
// a flag for an "acceptable" result should pass.
// Every possible flag value that's associated with an
// "acceptable" result should be explicitly specified,
// otherwise the test will panic.
func shouldPass(result string, flags []string, flagsShouldPass map[string]bool) bool {
switch result {
case "valid":
return true
case "invalid":
return false
case "acceptable":
for _, flag := range flags {
pass, ok := flagsShouldPass[flag]
if !ok {
panic(fmt.Sprintf("unspecified flag: %q", flag))
}
if !pass {
return false
}
}
return true // There are no flags, or all are meant to pass.
default:
panic(fmt.Sprintf("unexpected result: %v", result))
}
}
+12 -5
View File
@@ -85,7 +85,8 @@ type certID struct {
// https://tools.ietf.org/html/rfc2560#section-4.1.1
type ocspRequest struct {
TBSRequest tbsRequest
TBSRequest tbsRequest
OptionalSignature asn1.RawValue `asn1:"explicit,tag:0,optional"`
}
type tbsRequest struct {
@@ -321,10 +322,10 @@ type Request struct {
func (req *Request) Marshal() ([]byte, error) {
hashAlg := getOIDFromHashAlgorithm(req.HashAlgorithm)
if hashAlg == nil {
return nil, errors.New("Unknown hash algorithm")
return nil, errors.New("unknown hash algorithm")
}
return asn1.Marshal(ocspRequest{
tbsRequest{
TBSRequest: tbsRequest{
Version: 0,
RequestList: []request{
{
@@ -418,8 +419,10 @@ func (p ParseError) Error() string {
}
// ParseRequest parses an OCSP request in DER form. It only supports
// requests for a single certificate. Signed requests are not supported.
// If a request includes a signature, it will result in a ParseError.
// requests for a single certificate identifier. If a request includes
// multiple certificate identifiers, only the first will be included in
// the parsed Request. Signed requests are not supported. If a request
// includes a signature, it will result in a ParseError.
func ParseRequest(bytes []byte) (*Request, error) {
var req ocspRequest
rest, err := asn1.Unmarshal(bytes, &req)
@@ -430,6 +433,10 @@ func ParseRequest(bytes []byte) (*Request, error) {
return nil, ParseError("trailing data in OCSP request")
}
if len(req.OptionalSignature.FullBytes) > 0 {
return nil, ParseError("signed OCSP requests are not supported")
}
if len(req.TBSRequest.RequestList) == 0 {
return nil, ParseError("OCSP request contains no request body")
}
+127 -1
View File
@@ -15,10 +15,15 @@ import (
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"errors"
"math/big"
"reflect"
"strings"
"testing"
"time"
"golang.org/x/crypto/cryptobyte"
cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
)
func TestOCSPDecode(t *testing.T) {
@@ -190,7 +195,7 @@ func TestOCSPRequest(t *testing.T) {
t.Fatal(err)
}
if bytes.Compare(expectedBytes, marshaledRequest) != 0 {
if !bytes.Equal(expectedBytes, marshaledRequest) {
t.Errorf(
"Marshaled request doesn't match expected: wanted %x, got %x",
expectedBytes,
@@ -199,6 +204,127 @@ func TestOCSPRequest(t *testing.T) {
}
}
func TestOCSPRequestErrors(t *testing.T) {
tests := []struct {
name string
input []byte
wantErr string
}{
{
name: "signed request",
input: testOCSPRequest(1, true),
wantErr: "signed",
},
{
name: "trailing data",
input: append(testOCSPRequest(1, false), 0x00),
wantErr: "trailing",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := ParseRequest(tc.input)
if err == nil {
t.Fatalf(
"ParseRequest succeeded, want ParseError containing %q",
tc.wantErr,
)
}
var parseError ParseError
if !errors.As(err, &parseError) {
t.Fatalf("ParseRequest returned %T, want ParseError", err)
}
if !strings.Contains(parseError.Error(), tc.wantErr) {
t.Fatalf(
"ParseRequest error = %q, want it to contain %q",
parseError.Error(), tc.wantErr,
)
}
})
}
}
func TestOCSPMultipleCertRequest(t *testing.T) {
req, err := ParseRequest(testOCSPRequest(2, false))
if err != nil {
t.Fatalf("ParseRequest errored for multiple request input: %q", err)
}
// testOCSPRequest uses the request cert counter as the issuerNameHash,
// issuerKeyHash and serial. We expect the first request cert to be used
// (counter=1), not the second (counter=2).
wantHash := []byte{0x01}
if actual := req.IssuerNameHash; !bytes.Equal(actual, wantHash) {
t.Errorf(
"ParseRequest.IssuerNameHash returned = %q, wanted %q",
actual, wantHash,
)
}
if actual := req.IssuerKeyHash; !bytes.Equal(actual, wantHash) {
t.Errorf("ParseRequest.IssuerKeyHash returned = %q, wanted %q",
actual, wantHash)
}
wantSerial := big.NewInt(0x01)
if actual := req.SerialNumber; actual.Cmp(wantSerial) != 0 {
t.Errorf("ParseRequest.SerialNumber returned = %q, wanted %q",
actual, wantHash)
}
}
// testOCSPRequest constructs an unsupported OCSP request in DER form for testing.
//
// We build these by hand using cryptobyte to specifically target shapes that are
// not supported by ParseRequest, and can't be created with CreateRequest.
//
// The resulting requestList will contain numCerts RequestList entries. If
// includeSignature is true, the request will carry a signature field (with an
// arbitrary signatureAlgorithm and signature).
//
// See RFC 6960, §4.1.1.
func testOCSPRequest(numCerts int, includeSignature bool) []byte {
sha1AlgID, err := asn1.Marshal(pkix.AlgorithmIdentifier{
Algorithm: asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26},
Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
})
if err != nil {
panic(err)
}
var b cryptobyte.Builder
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // OCSPRequest
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // tbsRequest
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // requestList
for i := 0; i < numCerts; i++ {
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // Request
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // CertID
b.AddBytes(sha1AlgID) // hashAlgorithm
b.AddASN1OctetString([]byte{byte(i + 1)}) // issuerNameHash
b.AddASN1OctetString([]byte{byte(i + 1)}) // issuerKeyHash
b.AddASN1Int64(int64(i + 1)) // serialNumber
})
})
}
})
})
if includeSignature {
// optionalSignature [0] EXPLICIT Signature
b.AddASN1(cbasn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) {
b.AddASN1(cbasn1.SEQUENCE, func(b *cryptobyte.Builder) { // Signature
b.AddBytes(sha1AlgID) // signatureAlgorithm
b.AddASN1BitString([]byte{0x09, 0x0A, 0x0B, 0x0C}) // signature
})
})
}
})
der, err := b.Bytes()
if err != nil {
panic(err)
}
return der
}
func TestOCSPResponse(t *testing.T) {
leafCert, _ := hex.DecodeString(leafCertHex)
leaf, err := x509.ParseCertificate(leafCert)
+6 -5
View File
@@ -5,11 +5,12 @@
// Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
// very similar to PEM except that it has an additional CRC checksum.
//
// Deprecated: this package is unmaintained except for security fixes. New
// applications should consider a more focused, modern alternative to OpenPGP
// for their specific task. If you are required to interoperate with OpenPGP
// systems and need a maintained package, consider a community fork.
// See https://golang.org/issue/44226.
// Deprecated: this package is unsafe by design, and has numerous known security
// issues. It is not maintained, and should not be used. New applications should
// consider a more focused, modern alternative to OpenPGP for their specific
// task. If you are required to interoperate with OpenPGP systems and need a
// maintained package, consider github.com/ProtonMail/go-crypto/openpgp, which
// is a maintained fork that aims to be a drop-in replacement for this package.
package armor
import (
+6 -5
View File
@@ -8,11 +8,12 @@
// Clearsigned messages are cryptographically signed, but the contents of the
// message are kept in plaintext so that it can be read without special tools.
//
// Deprecated: this package is unmaintained except for security fixes. New
// applications should consider a more focused, modern alternative to OpenPGP
// for their specific task. If you are required to interoperate with OpenPGP
// systems and need a maintained package, consider a community fork.
// See https://golang.org/issue/44226.
// Deprecated: this package is unsafe by design, and has numerous known security
// issues. It is not maintained, and should not be used. New applications should
// consider a more focused, modern alternative to OpenPGP for their specific
// task. If you are required to interoperate with OpenPGP systems and need a
// maintained package, consider github.com/ProtonMail/go-crypto/openpgp, which
// is a maintained fork that aims to be a drop-in replacement for this package.
package clearsign
import (
+6 -5
View File
@@ -4,11 +4,12 @@
// Package errors contains common error types for the OpenPGP packages.
//
// Deprecated: this package is unmaintained except for security fixes. New
// applications should consider a more focused, modern alternative to OpenPGP
// for their specific task. If you are required to interoperate with OpenPGP
// systems and need a maintained package, consider a community fork.
// See https://golang.org/issue/44226.
// Deprecated: this package is unsafe by design, and has numerous known security
// issues. It is not maintained, and should not be used. New applications should
// consider a more focused, modern alternative to OpenPGP for their specific
// task. If you are required to interoperate with OpenPGP systems and need a
// maintained package, consider github.com/ProtonMail/go-crypto/openpgp, which
// is a maintained fork that aims to be a drop-in replacement for this package.
package errors
import (
+6 -5
View File
@@ -5,11 +5,12 @@
// Package packet implements parsing and serialization of OpenPGP packets, as
// specified in RFC 4880.
//
// Deprecated: this package is unmaintained except for security fixes. New
// applications should consider a more focused, modern alternative to OpenPGP
// for their specific task. If you are required to interoperate with OpenPGP
// systems and need a maintained package, consider a community fork.
// See https://golang.org/issue/44226.
// Deprecated: this package is unsafe by design, and has numerous known security
// issues. It is not maintained, and should not be used. New applications should
// consider a more focused, modern alternative to OpenPGP for their specific
// task. If you are required to interoperate with OpenPGP systems and need a
// maintained package, consider github.com/ProtonMail/go-crypto/openpgp, which
// is a maintained fork that aims to be a drop-in replacement for this package.
package packet
import (
+6 -5
View File
@@ -4,11 +4,12 @@
// Package openpgp implements high level operations on OpenPGP messages.
//
// Deprecated: this package is unmaintained except for security fixes. New
// applications should consider a more focused, modern alternative to OpenPGP
// for their specific task. If you are required to interoperate with OpenPGP
// systems and need a maintained package, consider a community fork.
// See https://golang.org/issue/44226.
// Deprecated: this package is unsafe by design, and has numerous known security
// issues. It is not maintained, and should not be used. New applications should
// consider a more focused, modern alternative to OpenPGP for their specific
// task. If you are required to interoperate with OpenPGP systems and need a
// maintained package, consider github.com/ProtonMail/go-crypto/openpgp, which
// is a maintained fork that aims to be a drop-in replacement for this package.
package openpgp
import (
+6 -5
View File
@@ -5,11 +5,12 @@
// Package s2k implements the various OpenPGP string-to-key transforms as
// specified in RFC 4800 section 3.7.1.
//
// Deprecated: this package is unmaintained except for security fixes. New
// applications should consider a more focused, modern alternative to OpenPGP
// for their specific task. If you are required to interoperate with OpenPGP
// systems and need a maintained package, consider a community fork.
// See https://golang.org/issue/44226.
// Deprecated: this package is unsafe by design, and has numerous known security
// issues. It is not maintained, and should not be used. New applications should
// consider a more focused, modern alternative to OpenPGP for their specific
// task. If you are required to interoperate with OpenPGP systems and need a
// maintained package, consider github.com/ProtonMail/go-crypto/openpgp, which
// is a maintained fork that aims to be a drop-in replacement for this package.
package s2k
import (
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build loong64 && !purego && gc
package salsa
import "golang.org/x/sys/cpu"
// xorKeyStreamVX is implemented in salsa20_loong64.s.
//
//go:noescape
func xorKeyStreamVX(out, in *byte, n uint64, nonce, key *byte)
// XORKeyStream crypts bytes from in to out using the given key and counters.
// In and out must overlap entirely or not at all. Counter
// contains the raw salsa20 counter bytes (both nonce and block counter).
func XORKeyStream(out, in []byte, counter *[16]byte, key *[32]byte) {
if len(in) == 0 {
return
}
_ = out[len(in)-1]
if cpu.Loong64.HasLSX {
xorKeyStreamVX(&out[0], &in[0], uint64(len(in)), &counter[0], &key[0])
} else {
genericXORKeyStream(out, in, counter, key)
}
}
+454
View File
@@ -0,0 +1,454 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !purego && gc
#include "textflag.h"
DATA ·constants+0x00(SB)/4, $0x61707865
DATA ·constants+0x04(SB)/4, $0x3320646e
DATA ·constants+0x08(SB)/4, $0x79622d32
DATA ·constants+0x0c(SB)/4, $0x6b206574
GLOBL ·constants(SB), NOPTR|RODATA, $32
#define NUM_ROUNDS 10
// func xorKeyStreamVX(out, in *byte, n uint64, counter, key *byte)
TEXT ·xorKeyStreamVX(SB), $0-40
MOVV out+0(FP), R4
MOVV in+8(FP), R5
MOVV n+16(FP), R6
MOVV counter+24(FP), R7
MOVV key+32(FP), R8
MOVV $·constants(SB), R10
BGE R0, R6, ret
MOVV 8(R7), R9 // counter[8:16]
loop256:
MOVV $NUM_ROUNDS, R15
VXORV V30, V30, V30 // V30 = 0
// load contants
VMOVQ (R10), V0.W4
VMOVQ 4(R10), V5.W4
VMOVQ 8(R10), V10.W4
VMOVQ 12(R10), V15.W4
// load keys
VMOVQ (R8), V1.W4
VMOVQ 4(R8), V2.W4
VMOVQ 8(R8), V3.W4
VMOVQ 12(R8), V4.W4
VMOVQ 16(R8), V11.W4
VMOVQ 20(R8), V12.W4
VMOVQ 24(R8), V13.W4
VMOVQ 28(R8), V14.W4
// load and update counter
VMOVQ (R7), V6.W4
VMOVQ 4(R7), V7.W4
ADDV $1, R9, R11
ADDV $2, R9, R12
ADDV $3, R9, R13
VMOVQ R9, V8.W[0]
VMOVQ R11, V8.W[1]
VMOVQ R12, V8.W[2]
VMOVQ R13, V8.W[3]
SRLV $32, R9, R14
SRLV $32, R11, R11
SRLV $32, R12, R12
SRLV $32, R13, R13
VMOVQ R14, V9.W[0]
VMOVQ R11, V9.W[1]
VMOVQ R12, V9.W[2]
VMOVQ R13, V9.W[3]
// backup V8 and V9
VADDV V8, V30, V24 // V21 = V8
VADDV V9, V30, V25 // V22 = V9
salsa20_256:
VADDW V0, V12, V26
VADDW V5, V1, V27
VADDW V10, V6, V28
VADDW V15, V11, V29
VROTRW $25, V26, V26
VROTRW $25, V27, V27
VROTRW $25, V28, V28
VROTRW $25, V29, V29
VXORV V4, V26, V4
VXORV V9, V27, V9
VXORV V14, V28, V14
VXORV V3, V29, V3
VADDW V4, V0, V26
VADDW V9, V5, V27
VADDW V14, V10, V28
VADDW V3, V15, V29
VROTRW $23, V26, V26
VROTRW $23, V27, V27
VROTRW $23, V28, V28
VROTRW $23, V29, V29
VXORV V8, V26, V8
VXORV V13, V27, V13
VXORV V2, V28, V2
VXORV V7, V29, V7
VADDW V8, V4, V26
VADDW V13, V9, V27
VADDW V2, V14, V28
VADDW V7, V3, V29
VROTRW $19, V26, V26
VROTRW $19, V27, V27
VROTRW $19, V28, V28
VROTRW $19, V29, V29
VXORV V12, V26, V12
VXORV V1, V27, V1
VXORV V6, V28, V6
VXORV V11, V29, V11
VADDW V12, V8, V26
VADDW V1, V13, V27
VADDW V6, V2, V28
VADDW V11, V7, V29
VROTRW $14, V26, V26
VROTRW $14, V27, V27
VROTRW $14, V28, V28
VROTRW $14, V29, V29
VXORV V0, V26, V0
VXORV V5, V27, V5
VXORV V10, V28, V10
VXORV V15, V29, V15
VADDW V0, V3, V26
VADDW V5, V4, V27
VADDW V10, V9, V28
VADDW V15, V14, V29
VROTRW $25, V26, V26
VROTRW $25, V27, V27
VROTRW $25, V28, V28
VROTRW $25, V29, V29
VXORV V1, V26, V1
VXORV V6, V27, V6
VXORV V11, V28, V11
VXORV V12, V29, V12
VADDW V1, V0, V26
VADDW V6, V5, V27
VADDW V11, V10, V28
VADDW V12, V15, V29
VROTRW $23, V26, V26
VROTRW $23, V27, V27
VROTRW $23, V28, V28
VROTRW $23, V29, V29
VXORV V2, V26, V2
VXORV V7, V27, V7
VXORV V8, V28, V8
VXORV V13, V29, V13
VADDW V2, V1, V26
VADDW V7, V6, V27
VADDW V8, V11, V28
VADDW V13, V12, V29
VROTRW $19, V26, V26
VROTRW $19, V27, V27
VROTRW $19, V28, V28
VROTRW $19, V29, V29
VXORV V3, V26, V3
VXORV V4, V27, V4
VXORV V9, V28, V9
VXORV V14, V29, V14
VADDW V3, V2, V26
VADDW V4, V7, V27
VADDW V9, V8, V28
VADDW V14, V13, V29
VROTRW $14, V26, V26
VROTRW $14, V27, V27
VROTRW $14, V28, V28
VROTRW $14, V29, V29
VXORV V0, V26, V0
VXORV V5, V27, V5
VXORV V10, V28, V10
VXORV V15, V29, V15
SUBV $1, R15
BNE R15, R0, salsa20_256
// load origin contants
VMOVQ (R10), V16.W4
VMOVQ 4(R10), V21.W4
VMOVQ 8(R10), V26.W4
VMOVQ 12(R10), V31.W4
// load origin keys
VMOVQ (R8), V17.W4
VMOVQ 4(R8), V18.W4
VMOVQ 8(R8), V19.W4
VMOVQ 12(R8), V20.W4
VMOVQ 16(R8), V27.W4
VMOVQ 20(R8), V28.W4
VMOVQ 24(R8), V29.W4
VMOVQ 28(R8), V30.W4
// load origin counter
VMOVQ (R7), V22.W4
VMOVQ 4(R7), V23.W4
// add back the initial state to generate the key stream
VADDW V0, V16, V0
VADDW V1, V17, V1
VADDW V2, V18, V2
VADDW V3, V19, V3
VADDW V4, V20, V4
VADDW V5, V21, V5
VADDW V6, V22, V6
VADDW V7, V23, V7
VADDW V8, V24, V8
VADDW V9, V25, V9
VADDW V10, V26, V10
VADDW V11, V27, V11
VADDW V12, V28, V12
VADDW V13, V29, V13
VADDW V14, V30, V14
VADDW V15, V31, V15
// shuffle
VILVLW V0, V1, V16
VILVHW V0, V1, V17
VILVLW V2, V3, V18
VILVHW V2, V3, V19
VILVLW V4, V5 ,V20
VILVHW V4, V5, V21
VILVLW V6, V7, V22
VILVHW V6, V7, V23
VILVLW V8, V9, V24
VILVHW V8, V9, V25
VILVLW V10, V11, V26
VILVHW V10, V11, V27
VILVLW V12, V13, V28
VILVHW V12, V13, V29
VILVLW V14, V15, V30
VILVHW V14, V15, V31
VILVLV V16, V18, V0
VILVHV V16, V18, V4
VILVLV V17, V19, V8
VILVHV V17, V19, V12
VILVLV V20, V22, V1
VILVHV V20, V22, V5
VILVLV V21, V23, V9
VILVHV V21, V23, V13
VILVLV V24, V26, V2
VILVHV V24, V26, V6
VILVLV V25, V27, V10
VILVHV V25, V27, V14
VILVLV V28, V30, V3
VILVHV V28, V30, V7
VILVLV V29, V31, V11
VILVHV V29, V31, V15
SGTU $256, R6, R11
BNE R11, R0, less_than_256
// load src data from R5
VMOVQ 0(R5), V16
VMOVQ 16(R5), V17
VMOVQ 32(R5), V18
VMOVQ 48(R5), V19
VMOVQ 64(R5), V20
VMOVQ 80(R5), V21
VMOVQ 96(R5), V22
VMOVQ 112(R5), V23
VMOVQ 128(R5), V24
VMOVQ 144(R5), V25
VMOVQ 160(R5), V26
VMOVQ 176(R5), V27
VMOVQ 192(R5), V28
VMOVQ 208(R5), V29
VMOVQ 224(R5), V30
VMOVQ 240(R5), V31
VXORV V0, V16, V16
VXORV V1, V17, V17
VXORV V2, V18, V18
VXORV V3, V19, V19
VXORV V4, V20, V20
VXORV V5, V21, V21
VXORV V6, V22, V22
VXORV V7, V23, V23
VXORV V8, V24, V24
VXORV V9, V25, V25
VXORV V10, V26, V26
VXORV V11, V27, V27
VXORV V12, V28, V28
VXORV V13, V29, V29
VXORV V14, V30, V30
VXORV V15, V31, V31
VMOVQ V16, 0(R4)
VMOVQ V17, 16(R4)
VMOVQ V18, 32(R4)
VMOVQ V19, 48(R4)
VMOVQ V20, 64(R4)
VMOVQ V21, 80(R4)
VMOVQ V22, 96(R4)
VMOVQ V23, 112(R4)
VMOVQ V24, 128(R4)
VMOVQ V25, 144(R4)
VMOVQ V26, 160(R4)
VMOVQ V27, 176(R4)
VMOVQ V28, 192(R4)
VMOVQ V29, 208(R4)
VMOVQ V30, 224(R4)
VMOVQ V31, 240(R4)
ADDV $4, R9, R9 // update counter
SUBV $256, R6, R6
ADDV $256, R4, R4
ADDV $256, R5, R5
SGTU $256, R6, R11
BEQ R11, R0, loop256
BEQ R6, R0, ret
less_than_256:
VXORV V30, V30, V30 // V30=0
SGTU $128, R6, R11
BNE R11, R0, less_than_128
SUBV $128, R6
VMOVQ (R5), V16
VMOVQ 16(R5), V17
VMOVQ 32(R5), V18
VMOVQ 48(R5), V19
VMOVQ 64(R5), V20
VMOVQ 80(R5), V21
VMOVQ 96(R5), V22
VMOVQ 112(R5), V23
VXORV V0, V16, V16
VXORV V1, V17, V17
VXORV V2, V18, V18
VXORV V3, V19, V19
VXORV V4, V20, V20
VXORV V5, V21, V21
VXORV V6, V22, V22
VXORV V7, V23, V23
VMOVQ V16, (R4)
VMOVQ V17, 16(R4)
VMOVQ V18, 32(R4)
VMOVQ V19, 48(R4)
VMOVQ V20, 64(R4)
VMOVQ V21, 80(R4)
VMOVQ V22, 96(R4)
VMOVQ V23, 112(R4)
BEQ R6, R0, ret
ADDV $128, R5, R5
ADDV $128, R4, R4
VADDV V8, V30, V0
VADDV V9, V30, V1
VADDV V10, V30, V2
VADDV V11, V30, V3
VADDV V12, V30, V4
VADDV V13, V30, V5
VADDV V14, V30, V6
VADDV V15, V30, V7
less_than_128:
SGTU $64, R6, R11
BNE R11, R0, less_than_64
SUBV $64, R6
VMOVQ (R5), V16
VMOVQ 16(R5), V17
VMOVQ 32(R5), V18
VMOVQ 48(R5), V19
VXORV V0, V16, V16
VXORV V1, V17, V17
VXORV V2, V18, V18
VXORV V3, V19, V19
VMOVQ V16, (R4)
VMOVQ V17, 16(R4)
VMOVQ V18, 32(R4)
VMOVQ V19, 48(R4)
BEQ R6, R0, ret
ADDV $64, R5
ADDV $64, R4
VADDV V4, V30, V0
VADDV V5, V30, V1
VADDV V6, V30, V2
VADDV V7, V30, V3
less_than_64:
SGTU $32, R6, R11
BNE R11, R0, less_than_32
SUBV $32, R6
VMOVQ (R5), V16
VMOVQ 16(R5), V17
VXORV V0, V16, V16
VXORV V1, V17, V17
VMOVQ V16, (R4)
VMOVQ V17, 16(R4)
BEQ R6, R0, ret
ADDV $32, R5
ADDV $32, R4
VADDV V2, V30, V0
VADDV V3, V30, V1
less_than_32:
SGTU $16, R6, R11
BNE R11, R0, less_than_16
SUBV $16, R6
VMOVQ (R5), V16
VXORV V16, V0, V16
VMOVQ V16, (R4)
BEQ R6, R0, ret
ADDV $16, R5
ADDV $16, R4
VADDV V1, V30, V0
less_than_16:
SGTU $8, R6, R11
BNE R11, R0, less_than_8
SUBV $8, R6
VMOVQ V0.V[0], R11
VMOVQ V0.V[1], R13
MOVV (R5), R12
XOR R11, R12, R12
MOVV R12, (R4)
BEQ R6, R0, ret
ADDV $8, R5
ADDV $8, R4
VMOVQ R13, V0.V[0]
less_than_8:
SGTU $4, R6, R11
BNE R11, R0, less_than_4
SUBV $4, R6
VMOVQ V0.W[0], R11
VMOVQ V0.W[1], R13
MOVWU (R5), R12
XOR R11, R12, R12
MOVWU R12, (R4)
BEQ R6, R0, ret
ADDV $4, R5
ADDV $4, R4
VMOVQ R13, V0.W[0]
less_than_4:
SGTU $2, R6, R11
BNE R11, R0, less_than_2
SUBV $2, R6
VMOVQ V0.H[0], R11
VMOVQ V0.H[1], R13
MOVHU (R5), R12
XOR R11, R12, R12
MOVHU R12, (R4)
BEQ R6, R0, ret
ADDV $2, R5
ADDV $2, R4
VMOVQ R13, V0.H[0]
less_than_2:
VMOVQ V0.B[0], R11
MOVBU (R5), R12
XOR R11, R12, R12
MOVBU R12, (R4)
ret:
RET
+1 -1
View File
@@ -2,7 +2,7 @@
// 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
//go:build (!amd64 && !loong64) || purego || !gc
package salsa
@@ -2,7 +2,7 @@
// 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
//go:build (amd64 || loong64) && !purego && gc
package salsa
+2
View File
@@ -41,6 +41,7 @@ func ForwardToAgent(client *ssh.Client, keyring Agent) error {
continue
}
go ssh.DiscardRequests(reqs)
go io.Copy(io.Discard, channel.Stderr())
go func() {
ServeAgent(keyring, channel)
channel.Close()
@@ -72,6 +73,7 @@ func ForwardToRemote(client *ssh.Client, addr string) error {
continue
}
go ssh.DiscardRequests(reqs)
go io.Copy(io.Discard, channel.Stderr())
go forwardUnixSocket(channel, addr)
}
}()
+1 -1
View File
@@ -104,7 +104,7 @@ func (r *keyring) Unlock(passphrase []byte) error {
if !r.locked {
return errors.New("agent: not locked")
}
if 1 != subtle.ConstantTimeCompare(passphrase, r.passphrase) {
if subtle.ConstantTimeCompare(passphrase, r.passphrase) != 1 {
return fmt.Errorf("agent: incorrect passphrase")
}
+49 -8
View File
@@ -246,10 +246,10 @@ func setConstraints(key *AddedKey, constraintBytes []byte) error {
// on arbitrary inputs; the CRT coefficient recomputation is cubic in
// |p| and can consume excessive CPU on oversized keys.
func checkRSAKeyParams(N, E, P, Q *big.Int) error {
if N.BitLen() > 8192 {
if N.BitLen() > 16384 {
return errors.New("agent: RSA modulus too large")
}
if P.BitLen() > 4096 || Q.BitLen() > 4096 {
if P.BitLen() > 8192 || Q.BitLen() > 8192 {
return errors.New("agent: RSA prime too large")
}
if E.BitLen() > 24 {
@@ -304,19 +304,60 @@ func parseEd25519Key(req []byte) (*AddedKey, error) {
return addedKey, nil
}
func checkDSAParams(param *dsa.Parameters) error {
// SSH specifies FIPS 186-2, which only provided a single size
// (1024 bits) DSA key. FIPS 186-3 allows for larger key
// sizes, which would confuse SSH.
if l := param.P.BitLen(); l != 1024 {
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
}
// FIPS 186-2 specifies that Q must be exactly 160 bits. We must enforce
// this to prevent DoS attacks where an attacker sends a huge Q which makes
// verification slow.
if l := param.Q.BitLen(); l != 160 {
return fmt.Errorf("ssh: unsupported DSA sub-prime size %d", l)
}
// The generator G is an element of the group, so it must be strictly less
// than the modulus P.
if param.G.Cmp(param.P) >= 0 {
return errors.New("ssh: DSA generator larger than modulus")
}
// G must be positive.
if param.G.Sign() <= 0 {
return errors.New("ssh: DSA generator must be positive")
}
return nil
}
func parseDSAKey(req []byte) (*AddedKey, error) {
var k dsaKeyMsg
if err := ssh.Unmarshal(req, &k); err != nil {
return nil, err
}
params := dsa.Parameters{
P: k.P,
Q: k.Q,
G: k.G,
}
if err := checkDSAParams(&params); err != nil {
return nil, err
}
// The public value Y must be a non-zero element of the group, i.e. strictly
// between 0 and P, to prevent a maliciously oversized Y from slowing
// signature operations.
if k.Y.Sign() <= 0 || k.Y.Cmp(k.P) >= 0 {
return nil, errors.New("agent: DSA public value Y out of range")
}
priv := &dsa.PrivateKey{
PublicKey: dsa.PublicKey{
Parameters: dsa.Parameters{
P: k.P,
Q: k.Q,
G: k.G,
},
Y: k.Y,
Parameters: params,
Y: k.Y,
},
X: k.X,
}
+161
View File
@@ -9,10 +9,12 @@ import (
"crypto/rand"
"fmt"
"io"
"math/big"
pseudorand "math/rand"
"reflect"
"strings"
"testing"
"time"
"golang.org/x/crypto/ssh"
)
@@ -88,6 +90,96 @@ func TestSetupForwardAgent(t *testing.T) {
conn.Close()
}
func TestForwardAgentDiscardsStderr(t *testing.T) {
a, b, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer a.Close()
defer b.Close()
serverConf := ssh.ServerConfig{
NoClientAuth: true,
}
serverConf.AddHostKey(testSigners["rsa"])
incoming := make(chan *ssh.ServerConn, 1)
go func() {
conn, _, _, err := ssh.NewServerConn(a, &serverConf)
incoming <- conn
if err != nil {
t.Errorf("NewServerConn error: %v", err)
return
}
}()
conf := ssh.ClientConfig{
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, chans, reqs, err := ssh.NewClientConn(b, "", &conf)
if err != nil {
t.Fatalf("NewClientConn: %v", err)
}
client := ssh.NewClient(conn, chans, reqs)
defer client.Close()
if err := ForwardToAgent(client, NewKeyring()); err != nil {
t.Fatalf("ForwardToAgent: %v", err)
}
server := <-incoming
if server == nil {
t.Fatal("Unable to get server")
}
defer server.Close()
ch, chanReqs, err := server.OpenChannel(channelType, nil)
if err != nil {
t.Fatalf("OpenChannel(%q): %v", channelType, err)
}
go ssh.DiscardRequests(chanReqs)
defer ch.Close()
// Write more than the default channel receive window (2 MiB) on the
// stderr stream. If the forwarder did not drain it the remote window
// would be exhausted and this write would block forever, because no
// window-adjust messages are emitted for an extended stream that is
// never read.
const payload = 4 * 1024 * 1024
done := make(chan error, 1)
go func() {
buf := make([]byte, 32*1024)
remaining := payload
for remaining > 0 {
n := len(buf)
if remaining < n {
n = remaining
}
w, err := ch.Stderr().Write(buf[:n])
if err != nil {
done <- err
return
}
remaining -= w
}
done <- nil
}()
select {
case err := <-done:
if err != nil {
t.Fatalf("write to stderr: %v", err)
}
case <-time.After(10 * time.Second):
t.Fatal("write to stderr blocked: ForwardToAgent is not draining channel.Stderr()")
}
// The agent must still be reachable on the main stream after the
// stderr traffic.
if _, err := NewClient(ch).List(); err != nil {
t.Fatalf("List after stderr flood: %v", err)
}
}
func TestV1ProtocolMessages(t *testing.T) {
c1, c2, err := netPipe()
if err != nil {
@@ -154,6 +246,75 @@ func addKeyToAgent(key crypto.PrivateKey) error {
return verifyKey(sshAgent)
}
func TestParseDSAKeyHugeQ(t *testing.T) {
P := new(big.Int).Lsh(big.NewInt(1), 1023)
Q := new(big.Int).Lsh(big.NewInt(1), 20000) // very large
// G and Y: dummy values, just needs to be < P to pass that specific check.
G := big.NewInt(2)
Y := big.NewInt(5)
req := ssh.Marshal(dsaKeyMsg{
Type: ssh.InsecureKeyAlgoDSA,
P: P,
Q: Q,
G: G,
Y: Y,
X: big.NewInt(1),
})
_, err := parseDSAKey(req)
if err == nil {
t.Fatal("parseDSAKey accepted a DSA key with large Q")
}
expectedError := "unsupported DSA sub-prime size"
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
}
}
func TestParseDSAKeyYOutOfRange(t *testing.T) {
// Valid 1024/160 parameters (values don't need to be a real DSA group,
// they only need to pass the checkDSAParams bit-length checks and the
// G < P / G > 0 checks).
P := new(big.Int).Lsh(big.NewInt(1), 1023)
P.SetBit(P, 0, 1) // make P odd so it can pass as a prime candidate shape
Q := new(big.Int).Lsh(big.NewInt(1), 159)
Q.SetBit(Q, 0, 1)
G := big.NewInt(2)
for _, tc := range []struct {
name string
Y *big.Int
}{
{"Y_zero", big.NewInt(0)},
{"Y_negative", big.NewInt(-1)},
{"Y_equals_P", new(big.Int).Set(P)},
{"Y_greater_than_P", new(big.Int).Add(P, big.NewInt(1))},
{"Y_much_greater_than_P", new(big.Int).Lsh(big.NewInt(1), 20000)},
} {
t.Run(tc.name, func(t *testing.T) {
req := ssh.Marshal(dsaKeyMsg{
Type: ssh.InsecureKeyAlgoDSA,
P: P,
Q: Q,
G: G,
Y: tc.Y,
X: big.NewInt(1),
})
_, err := parseDSAKey(req)
if err == nil {
t.Fatalf("parseDSAKey accepted a DSA key with Y=%s (P=%s)", tc.Y, P)
}
expectedError := "DSA public value Y out of range"
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
}
})
}
}
func TestKeyTypes(t *testing.T) {
for k, v := range testPrivateKeys {
if err := addKeyToAgent(v); err != nil {
+1 -1
View File
@@ -38,7 +38,7 @@ func TestBufferReadwrite(t *testing.T) {
b.write(alphabet[5:15])
r, _ = b.Read(make([]byte, 10))
r2, _ := b.Read(make([]byte, 10))
if r != 10 || r2 != 5 || 15 != r+r2 {
if r != 10 || r2 != 5 || r+r2 != 15 {
t.Fatal("Expected written == read == 15")
}
}
+27 -26
View File
@@ -10,6 +10,7 @@ import (
"fmt"
"io"
"net"
"slices"
"sort"
"time"
)
@@ -229,15 +230,20 @@ func parseCert(in []byte, privAlgo string) (*Certificate, error) {
return nil, err
}
c.Reserved = g.Reserved
// Reject a certificate whose signature key is itself a certificate before
// parsing it. Certificates signed by certificates are not supported (see
// PROTOCOL.certkeys), and rejecting after ParsePublicKey returns would allow
// a chain of nested certificates to recurse once per level, exhausting the
// goroutine stack.
if sigAlgo, _, ok := parseString(g.SignatureKey); !ok {
return nil, errShortRead
} else if _, ok := certKeyAlgoNames[string(sigAlgo)]; ok {
return nil, fmt.Errorf("ssh: the signature key type %q is invalid for certificates", sigAlgo)
}
k, err := ParsePublicKey(g.SignatureKey)
if err != nil {
return nil, err
}
// The Type() function is intended to return only certificate key types, but
// we use certKeyAlgoNames anyway for safety, to match [Certificate.Type].
if _, ok := certKeyAlgoNames[k.Type()]; ok {
return nil, fmt.Errorf("ssh: the signature key type %q is invalid for certificates", k.Type())
}
c.SignatureKey = k
c.Signature, rest, ok = parseSignatureBody(g.Signature)
if !ok || len(rest) > 0 {
@@ -300,8 +306,11 @@ const sourceAddressCriticalOption = "source-address"
// minimally, the IsAuthority callback should be set.
type CertChecker struct {
// SupportedCriticalOptions lists the CriticalOptions that the
// server application layer understands. These are only used
// for user certificates.
// application layer understands. A certificate carrying a critical
// option that is not listed here is rejected.
// CertChecker.Authenticate additionally accepts the source-address
// option, which the server enforces on the Permissions that
// Authenticate returns.
SupportedCriticalOptions []string
// IsUserAuthority should return true if the key is recognized as an
@@ -364,8 +373,9 @@ func (c *CertChecker) CheckHostKey(addr string, remote net.Addr, key PublicKey)
return c.CheckCert(hostname, cert)
}
// Authenticate checks a user certificate. Authenticate can be used as
// a value for ServerConfig.PublicKeyCallback.
// Authenticate checks a user certificate. Authenticate can be used as a value
// for ServerConfig.PublicKeyCallback. The source-address critical option is
// allowed, as it will be enforced by the server.
func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permissions, error) {
cert, ok := pubKey.(*Certificate)
if !ok {
@@ -384,8 +394,11 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
if !c.IsUserAuthority(cert.SignatureKey) {
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority")
}
if err := c.CheckCert(conn.User(), cert); err != nil {
// The source-address critical option is enforced by serverAuthenticate,
// so it is supported regardless of SupportedCriticalOptions
cc := *c
cc.SupportedCriticalOptions = append(slices.Clip(cc.SupportedCriticalOptions), sourceAddressCriticalOption)
if err := cc.CheckCert(conn.User(), cert); err != nil {
return nil, err
}
@@ -393,27 +406,15 @@ func (c *CertChecker) Authenticate(conn ConnMetadata, pubKey PublicKey) (*Permis
}
// CheckCert checks CriticalOptions, ValidPrincipals, revocation, timestamp and
// the signature of the certificate.
// the signature of the certificate. Critical options that are not listed in
// SupportedCriticalOptions are rejected.
func (c *CertChecker) CheckCert(principal string, cert *Certificate) error {
if c.IsRevoked != nil && c.IsRevoked(cert) {
return fmt.Errorf("ssh: certificate serial %d revoked", cert.Serial)
}
for opt := range cert.CriticalOptions {
// sourceAddressCriticalOption will be enforced by
// serverAuthenticate
if opt == sourceAddressCriticalOption {
continue
}
found := false
for _, supp := range c.SupportedCriticalOptions {
if supp == opt {
found = true
break
}
}
if !found {
if !slices.Contains(c.SupportedCriticalOptions, opt) {
return fmt.Errorf("ssh: unsupported critical option %q in certificate", opt)
}
}
+105
View File
@@ -15,6 +15,7 @@ import (
"math/big"
"net"
"reflect"
"strings"
"testing"
"time"
@@ -45,6 +46,34 @@ func TestParseCert(t *testing.T) {
}
}
func TestParseCertNestedSignatureKey(t *testing.T) {
signer, err := NewSignerFromKey(testPrivateKeys["ed25519"])
if err != nil {
t.Fatal(err)
}
cert := &Certificate{
Key: signer.PublicKey(),
CertType: UserCert,
ValidBefore: CertTimeInfinity,
}
if err := cert.SignCert(rand.Reader, signer); err != nil {
t.Fatal(err)
}
inner := *cert
cert.SignatureKey = &inner
blob := cert.Marshal()
_, err = ParsePublicKey(blob)
if err == nil {
t.Fatal("ParsePublicKey: expected error for certificate signed by a certificate, got nil")
}
if !strings.Contains(err.Error(), "invalid for certificates") {
t.Errorf("ParsePublicKey: got error %q, want it to mention the signature key is invalid for certificates", err)
}
}
// Cert generated by ssh-keygen OpenSSH_6.8p1 OS X 10.10.3
// % ssh-keygen -s ca -I testcert -O source-address=192.168.1.0/24 -O force-command=/bin/sleep user.pub
// user.pub key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDACh1rt2DXfV3hk6fszSQcQ/rueMId0kVD9U7nl8cfEnFxqOCrNT92g4laQIGl2mn8lsGZfTLg8ksHq3gkvgO3oo/0wHy4v32JeBOHTsN5AL4gfHNEhWeWb50ev47hnTsRIt9P4dxogeUo/hTu7j9+s9lLpEQXCvq6xocXQt0j8MV9qZBBXFLXVT3cWIkSqOdwt/5ZBg+1GSrc7WfCXVWgTk4a20uPMuJPxU4RQwZW6X3+O8Pqo8C3cW0OzZRFP6gUYUKUsTI5WntlS+LAxgw1mZNsozFGdbiOPRnEryE3SRldh9vjDR3tin1fGpA5P7+CEB/bqaXtG3V+F2OkqaMN
@@ -356,6 +385,82 @@ func TestHostKeyCert(t *testing.T) {
}
}
type testConnMeta struct {
ConnMetadata
user string
}
func (c testConnMeta) User() string { return c.user }
func TestCertCriticalOptions(t *testing.T) {
appended := make([]string, 2)
appended[0], appended[1] = "supported-option", "appended-option"
supported := appended[:1]
checker := &CertChecker{
SupportedCriticalOptions: supported,
IsHostAuthority: func(p PublicKey, addr string) bool {
return bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
},
IsUserAuthority: func(p PublicKey) bool {
return bytes.Equal(testPublicKeys["ecdsa"].Marshal(), p.Marshal())
},
}
for _, test := range []struct {
name string
opts map[string]string
// succeed is the expected outcome of CheckHostKey, for a host
// certificate, and of CheckCert, for a user certificate.
succeed bool
// authSucceed is the expected outcome of Authenticate.
authSucceed bool
}{
{name: "no critical options", opts: nil, succeed: true, authSucceed: true},
{name: "source-address", opts: map[string]string{sourceAddressCriticalOption: "192.168.1.0/24"}, authSucceed: true},
{name: "unknown option", opts: map[string]string{"unknown-option": ""}},
{name: "supported option", opts: map[string]string{"supported-option": ""}, succeed: true, authSucceed: true},
} {
hostCert := &Certificate{
ValidPrincipals: []string{"hostname"},
Key: testPublicKeys["rsa"],
ValidBefore: CertTimeInfinity,
CertType: HostCert,
Permissions: Permissions{CriticalOptions: test.opts},
}
if err := hostCert.SignCert(rand.Reader, testSigners["ecdsa"]); err != nil {
t.Fatalf("SignCert: %v", err)
}
if err := checker.CheckHostKey("hostname:22", nil, hostCert); (err == nil) != test.succeed {
t.Errorf("CheckHostKey(%s): got %v, want success=%v", test.name, err, test.succeed)
}
userCert := &Certificate{
ValidPrincipals: []string{"user"},
Key: testPublicKeys["rsa"],
ValidBefore: CertTimeInfinity,
CertType: UserCert,
Permissions: Permissions{CriticalOptions: test.opts},
}
if err := userCert.SignCert(rand.Reader, testSigners["ecdsa"]); err != nil {
t.Fatalf("SignCert: %v", err)
}
if err := checker.CheckCert("user", userCert); (err == nil) != test.succeed {
t.Errorf("CheckCert(%s): got %v, want success=%v", test.name, err, test.succeed)
}
if _, err := checker.Authenticate(testConnMeta{user: "user"}, userCert); (err == nil) != test.authSucceed {
t.Errorf("Authenticate(%s): got %v, want success=%v", test.name, err, test.authSucceed)
}
}
if appended[1] != "appended-option" {
t.Errorf("Authenticate overwrote the caller's slice: got %q, want %q", appended[1], "appended-option")
}
}
type legacyRSASigner struct {
Signer
}
+61 -26
View File
@@ -173,6 +173,12 @@ type channel struct {
// (for outbound channels) or received (for inbound channels).
decided bool
// established is set to true once the channel is open and may carry normal
// channel traffic: for an outbound channel when the peer's open
// confirmation is received, for an inbound channel when the local side
// accepts it. It is set and read from different goroutines.
established atomic.Bool
// direction contains either channelOutbound, for channels created
// locally, or channelInbound, for channels created by the peer.
direction channelDirection
@@ -216,6 +222,10 @@ type channel struct {
// packetPool has a buffer for each extended channel ID to
// save allocations during writes.
packetPool map[uint32][]byte
// closeOnce guards close so it is idempotent: closing the internal Go
// channels (msg, incomingRequests) more than once would panic.
closeOnce sync.Once
}
// writePacket sends a packet. If the packet is a channel close, it updates
@@ -340,7 +350,18 @@ func (ch *channel) handleData(packet []byte) error {
if extended == 1 {
ch.extPending.write(data)
} else if extended > 0 {
// discard other extended data.
// RFC 4254, Section 5.2 defines no extended data types other
// than stderr (type 1, handled above) and this package provides
// no API to read them, so the data is discarded. Credit its
// window back immediately: it can never be read, so the
// deduction above would otherwise shrink the window permanently.
// adjustWindow returns io.EOF if the local side has already
// sent a channel close; ignore it like ReadExtended does, since
// an error returned here would terminate the mux read loop and
// tear down the whole connection.
if err := ch.adjustWindow(length); err != nil && err != io.EOF {
return err
}
} else {
ch.pending.write(data)
}
@@ -393,17 +414,19 @@ func (c *channel) ReadExtended(data []byte, extended uint32) (n int, err error)
}
func (c *channel) close() {
c.pending.eof()
c.extPending.eof()
close(c.msg)
close(c.incomingRequests)
c.writeMu.Lock()
// This is not necessary for a normal channel teardown, but if
// there was another error, it is.
c.sentClose = true
c.writeMu.Unlock()
// Unblock writers.
c.remoteWin.close()
c.closeOnce.Do(func() {
c.pending.eof()
c.extPending.eof()
close(c.msg)
close(c.incomingRequests)
c.writeMu.Lock()
// This is not necessary for a normal channel teardown, but if
// there was another error, it is.
c.sentClose = true
c.writeMu.Unlock()
// Unblock writers.
c.remoteWin.close()
})
}
// responseMessageReceived is called when a success or failure message is
@@ -417,10 +440,20 @@ func (ch *channel) responseMessageReceived() error {
return errors.New("ssh: duplicate response received for channel")
}
ch.decided = true
ch.established.Store(true)
return nil
}
func (ch *channel) handlePacket(packet []byte) error {
// Only the open response is expected before the channel is established.
if !ch.established.Load() {
switch packet[0] {
case msgChannelOpenConfirm, msgChannelOpenFailure:
default:
return nil
}
}
switch packet[0] {
case msgChannelData, msgChannelExtendedData:
return ch.handleData(packet)
@@ -486,26 +519,28 @@ func (ch *channel) handlePacket(packet []byte) error {
default:
}
default:
ch.msg <- msg
// No other message type is expected on an established channel.
return fmt.Errorf("ssh: unexpected message type %d on channel %d", packet[0], ch.localId)
}
return nil
}
func (m *mux) newChannel(chanType string, direction channelDirection, extraData []byte) *channel {
ch := &channel{
remoteWin: window{Cond: newCond()},
myWindow: channelWindowSize,
pending: newBuffer(),
extPending: newBuffer(),
direction: direction,
incomingRequests: make(chan *Request, chanSize),
msg: make(chan interface{}, chanSize),
chanType: chanType,
extraData: extraData,
mux: m,
packetPool: make(map[uint32][]byte),
remoteWin: window{Cond: newCond()},
myWindow: channelWindowSize,
maxIncomingPayload: channelMaxPacket,
pending: newBuffer(),
extPending: newBuffer(),
direction: direction,
incomingRequests: make(chan *Request, chanSize),
msg: make(chan interface{}, chanSize),
chanType: chanType,
extraData: extraData,
mux: m,
packetPool: make(map[uint32][]byte),
}
ch.localId = m.chanList.add(ch)
m.chanList.add(ch)
return ch
}
@@ -529,7 +564,6 @@ func (ch *channel) Accept() (Channel, <-chan *Request, error) {
if ch.decided {
return nil, nil, errDecidedAlready
}
ch.maxIncomingPayload = channelMaxPacket
confirm := channelOpenConfirmMsg{
PeersID: ch.remoteId,
MyID: ch.localId,
@@ -537,6 +571,7 @@ func (ch *channel) Accept() (Channel, <-chan *Request, error) {
MaxPacketSize: ch.maxIncomingPayload,
}
ch.decided = true
ch.established.Store(true)
if err := ch.sendMessage(confirm); err != nil {
return nil, nil, err
}
+110
View File
@@ -5,6 +5,8 @@
package ssh
import (
"encoding/binary"
"io"
"math/bits"
"testing"
"time"
@@ -138,3 +140,111 @@ func TestWriteExtendedNoInfiniteLoopOnLargeWrite(t *testing.T) {
t.Fatal("WriteExtended did not return after closing the window")
}
}
func TestDiscardedExtendedDataReturnsWindowCredit(t *testing.T) {
client, _, serverChans := forwardingPair(t)
clientCh, reqs, err := client.OpenChannel("test", nil)
if err != nil {
t.Fatalf("OpenChannel: %v", err)
}
go DiscardRequests(reqs)
defer clientCh.Close()
serverCh := (<-serverChans).(*channel)
defer serverCh.Close()
// Write more than the default channel receive window (2 MiB) of
// extended data with a type code other than stderr. The peer
// discards such data: if it did not return the window credit the
// write would block forever once the window is exhausted.
const payload = 4 * 1024 * 1024
done := make(chan error, 1)
go func() {
buf := make([]byte, 32*1024)
remaining := payload
for remaining > 0 {
n := min(len(buf), remaining)
nw, err := serverCh.WriteExtended(buf[:n], 2)
if err != nil {
done <- err
return
}
remaining -= nw
}
done <- nil
}()
select {
case err := <-done:
if err != nil {
t.Fatalf("write of discarded extended data: %v", err)
}
case <-time.After(10 * time.Second):
t.Fatal("write of discarded extended data blocked: window credit is not returned")
}
// The main stream must still be usable.
want := []byte("main stream after extended data")
if _, err := serverCh.Write(want); err != nil {
t.Fatalf("write to main stream: %v", err)
}
got := make([]byte, len(want))
if _, err := io.ReadFull(clientCh, got); err != nil {
t.Fatalf("read from main stream: %v", err)
}
if string(got) != string(want) {
t.Fatalf("read %q from main stream, want %q", got, want)
}
// Reading the main stream payload above orders the test after all
// preceding packets: everything received has now been either read or
// discarded, so the full window credit must have been accounted.
// Anything less indicates a partial-credit leak that the liveness
// check above would not detect.
cc := clientCh.(*channel)
cc.windowMu.Lock()
window := cc.myWindow + cc.myConsumed
cc.windowMu.Unlock()
if window != channelWindowSize {
t.Errorf("myWindow+myConsumed = %d, want %d", window, channelWindowSize)
}
}
func TestDiscardedExtendedDataAfterClose(t *testing.T) {
client, _, serverChans := forwardingPair(t)
clientCh, reqs, err := client.OpenChannel("test", nil)
if err != nil {
t.Fatalf("OpenChannel: %v", err)
}
go DiscardRequests(reqs)
serverCh := <-serverChans
defer serverCh.Close()
ch := clientCh.(*channel)
// Close the channel locally: sentClose is now set, but the channel
// stays in the mux chanList until the peer's close arrives, so
// in-flight data packets are still dispatched to handleData.
if err := ch.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
// Deliver extended data packets with an unknown type code, enough to
// cross the adjustWindow send threshold of 3*maxIncomingPayload.
// The window adjust message cannot be sent after the close and
// adjustWindow fails with io.EOF: handleData must swallow that
// error, since any error it returns terminates the mux read loop
// and tears down every channel on the connection.
const packetLen = 32 * 1024
packet := make([]byte, 13+packetLen)
packet[0] = msgChannelExtendedData
binary.BigEndian.PutUint32(packet[1:], ch.localId)
binary.BigEndian.PutUint32(packet[5:], 2)
binary.BigEndian.PutUint32(packet[9:], packetLen)
for range 5 {
if err := ch.handleData(packet); err != nil {
t.Fatalf("handleData after local close: %v", err)
}
}
}
+1 -1
View File
@@ -798,7 +798,7 @@ func (g *gssAPIWithMICCallback) auth(session []byte, user string, c packetConn,
return authFailure, nil, fmt.Errorf("GSS-API Error:\n"+
"Major Status: %d\n"+
"Minor Status: %d\n"+
"Error Message: %s\n", userAuthGSSAPIErrorResp.MajorStatus, userAuthGSSAPIErrorResp.MinorStatus,
"Error Message: %q\n", userAuthGSSAPIErrorResp.MajorStatus, userAuthGSSAPIErrorResp.MinorStatus,
userAuthGSSAPIErrorResp.Message)
case msgUserAuthGSSAPIToken:
userAuthGSSAPITokenReq := &userAuthGSSAPIToken{}
+36
View File
@@ -7,10 +7,12 @@ package ssh
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io"
"log"
"math/big"
"net"
"os"
"runtime"
@@ -169,6 +171,40 @@ func TestClientAuthThirdKey(t *testing.T) {
}
}
// invalidRSASigner offers an RSA public key the server cannot parse: its public
// exponent is even, which parseRSA rejects regardless of any key size limit.
// The key is intentionally invalid for a structural reason rather than for its
// size, so the test stays meaningful even if the accepted modulus size changes.
// Its Sign method is never reached, because the key is rejected during the
// initial public key query.
type invalidRSASigner struct{}
func (invalidRSASigner) PublicKey() PublicKey {
n := new(big.Int).Lsh(big.NewInt(1), 2048)
pub, err := NewPublicKey(&rsa.PublicKey{N: n, E: 2}) // incorrect exponent.
if err != nil {
panic(err)
}
return pub
}
func (invalidRSASigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
return nil, errors.New("ssh: invalid test key must not be used to sign")
}
func TestClientAuthInvalidPublicKey(t *testing.T) {
config := &ClientConfig{
User: "testuser",
Auth: []AuthMethod{
PublicKeys(invalidRSASigner{}, testSigners["rsa"]),
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
if err := tryAuth(t, config); err != nil {
t.Fatalf("client auth failed but should have fallen back to a valid key: %s", err)
}
}
// partialSuccessPublicKeyAndKbdInteractiveServer returns a server config
// where the rsa key gets partial success and the server offers both publickey
// and keyboard-interactive as next methods. The VerifiedPublicKeyCallback
+4 -4
View File
@@ -419,7 +419,7 @@ type AlgorithmNegotiationError struct {
}
func (a *AlgorithmNegotiationError) Error() string {
return fmt.Sprintf("ssh: no common algorithm for %s; we offered: %v, peer offered: %v",
return fmt.Sprintf("ssh: no common algorithm for %s; we offered: %q, peer offered: %q",
a.What, a.SupportedAlgorithms, a.RequestedAlgorithms)
}
@@ -544,7 +544,7 @@ func (c *Config) SetDefaults() {
if c.Rand == nil {
c.Rand = rand.Reader
}
if c.Ciphers == nil {
if len(c.Ciphers) == 0 {
c.Ciphers = defaultCiphers
}
var ciphers []string
@@ -556,7 +556,7 @@ func (c *Config) SetDefaults() {
}
c.Ciphers = ciphers
if c.KeyExchanges == nil {
if len(c.KeyExchanges) == 0 {
c.KeyExchanges = defaultKexAlgos
}
var kexs []string
@@ -571,7 +571,7 @@ func (c *Config) SetDefaults() {
}
c.KeyExchanges = kexs
if c.MACs == nil {
if len(c.MACs) == 0 {
c.MACs = defaultMACs
}
var macs []string
+1 -1
View File
@@ -17,7 +17,7 @@ type OpenChannelError struct {
}
func (e *OpenChannelError) Error() string {
return fmt.Sprintf("ssh: rejected: %s (%s)", e.Reason, e.Message)
return fmt.Sprintf("ssh: rejected: %s (%q)", e.Reason, e.Message)
}
// ConnMetadata holds metadata for the connection.
+1 -1
View File
@@ -162,7 +162,7 @@ func newClientTransport(conn keyingTransport, clientVersion, serverVersion []byt
t.remoteAddr = addr
t.hostKeyCallback = config.HostKeyCallback
t.bannerCallback = config.BannerCallback
if config.HostKeyAlgorithms != nil {
if len(config.HostKeyAlgorithms) > 0 {
t.hostKeyAlgorithms = config.HostKeyAlgorithms
} else {
t.hostKeyAlgorithms = defaultHostKeyAlgos
+3 -3
View File
@@ -195,7 +195,7 @@ func TestHandshakeBasic(t *testing.T) {
}
want := []byte{msgRequestSuccess, byte(i)}
if bytes.Compare(p, want) != 0 {
if !bytes.Equal(p, want) {
t.Errorf("message %d: got %v, want %v", i, p, want)
}
}
@@ -1336,7 +1336,7 @@ func TestAlgorithmNegotiationError(t *testing.T) {
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",
expectedErrorString := fmt.Sprintf("ssh: handshake failed: ssh: no common algorithm for client to server cipher; we offered: %q, peer offered: %q",
clientConf.Ciphers, serverConf.Ciphers)
if err.Error() != expectedErrorString {
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
@@ -1352,7 +1352,7 @@ func TestAlgorithmNegotiationError(t *testing.T) {
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",
expectedErrorString = fmt.Sprintf("ssh: no common algorithm for client to server cipher; we offered: %q, peer offered: %q",
serverConf.Ciphers, clientConf.Ciphers)
if err.Error() != expectedErrorString {
t.Fatalf("expected error string %q, got %q", expectedErrorString, err.Error())
+35 -18
View File
@@ -182,14 +182,19 @@ func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey
}
hosts := string(keyFields[0])
// keyFields[1] contains the key type (e.g. “ssh-rsa”).
// However, that information is duplicated inside the
// base64-encoded key and so is ignored here.
// keyFields[1] contains the key type (e.g. "ssh-rsa"). This information
// is duplicated within the base64-encoded key blob. As OpenSSH's
// sshkey_read does, we verify that the declared key type matches the
// type embedded in the key blob.
wantType := string(keyFields[1])
key := bytes.Join(keyFields[2:], []byte(" "))
if pubKey, comment, err = parseAuthorizedKey(key); err != nil {
return "", nil, nil, "", nil, err
}
if pubKey.Type() != wantType {
return "", nil, nil, "", nil, fmt.Errorf("ssh: known hosts key type mismatch: human-readable type %q, encoded type %q", wantType, pubKey.Type())
}
return marker, strings.Split(hosts, ","), pubKey, comment, rest, nil
}
@@ -228,10 +233,17 @@ 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
// The first field contains the declared key type. As OpenSSH's
// sshkey_read does, we verify that it matches the type embedded in
// the key blob. Without this check, a single-token option (e.g.
// "restrict") appearing in the key type position could be silently
// discarded along with its intended effect.
if string(in[:i]) == out.Type() {
return out, comment, options, rest, nil
}
err = fmt.Errorf("ssh: authorized keys key type mismatch: human-readable type %q, encoded type %q", in[:i], out.Type())
}
lastErr = err
// No key type recognised. Maybe there's an options field at
// the beginning.
@@ -271,11 +283,15 @@ 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
// As above, the declared key type (here following the options
// field) must match the type embedded in the key blob.
if string(in[:i]) == out.Type() {
options = candidateOptions
return out, comment, options, rest, nil
}
err = fmt.Errorf("ssh: authorized keys key type mismatch: human-readable type %q, encoded type %q", in[:i], out.Type())
}
lastErr = err
in = rest
continue
@@ -469,10 +485,11 @@ func parseRSA(in []byte) (out PublicKey, rest []byte, err error) {
return nil, nil, err
}
// 8192 bits is also the maximum RSA key size accepted by crypto/tls for
// signature verification:
// https://github.com/golang/go/blob/69801b25/src/crypto/tls/handshake_client.go#L1096
if w.N.BitLen() > 8192 {
// 16384 bits is the largest RSA key OpenSSH will generate (ssh-keygen
// caps -b at 16384), so it is the practical upper bound for keys seen on
// the wire. Rejecting anything larger bounds the CPU spent verifying an
// attacker-supplied key and signature, mitigating a denial of service.
if w.N.BitLen() > 16384 {
return nil, nil, errors.New("ssh: rsa modulus too large")
}
if w.E.BitLen() > 24 {
@@ -1653,13 +1670,13 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
}
// Mirror the validation done in parseRSA for public keys: cap the
// modulus at the same limit enforced by crypto/tls, reject oversized
// or invalid exponents, and additionally bound the prime factors to
// modulus at the OpenSSH-generated maximum, reject oversized or
// invalid exponents, and additionally bound the prime factors to
// avoid the expensive CRT coefficient recomputation in pk.Precompute.
if key.N.BitLen() > 8192 {
if key.N.BitLen() > 16384 {
return nil, errors.New("ssh: rsa modulus too large")
}
if key.P.BitLen() > 4096 || key.Q.BitLen() > 4096 {
if key.P.BitLen() > 8192 || key.Q.BitLen() > 8192 {
return nil, errors.New("ssh: rsa prime too large")
}
if key.E.BitLen() > 24 {
+88 -15
View File
@@ -218,21 +218,32 @@ func TestParseRSAPrivateKey(t *testing.T) {
}
}
func TestParseRSAModulusTooLarge(t *testing.T) {
func TestParseRSAModulusLimit(t *testing.T) {
// A 16384-bit modulus is the largest OpenSSH will generate and must be
// accepted.
rsa16384 := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAIAQC0vyqtnpzMZ8Th6gv1t3+7dDVd2X+7MwjqKe08/wrKZaIuCAHX7jglC8FdHfiOcPhLHAreSJLZGXiSzCTUExbp9Zdt7tHluKMQxZCnbk02V89ggc4KpptQqfcmjMNgrEPF4PGJBe3eSYu9m7A1ptm0buWxaKtA17O1c2q2CVNYmuajSUAfi8+AZcdyRAX8eqga3u77DEj4O+CCuNA908NYv9Pc7SbaCwCgX3FXh7MvlaYcruTk529psNk1SK6uwBNak3CLRrXUBo5yTO7cCO0gmlvd8SwtY5mPaDLLCJa+Ed2t9OCt8HCPrdkH3aiQBTP3k/Iofy3NRv+/1lenJP1qWB2rnyagoj+osrLc6m8PRd7GBje7KdO6nyY1D+q1MvX4zV1qNQfD+38N6PixVX1MGKw72sD+oLCDjHKS24BHfnfaE7f8n+Alp2mxnSz9tYqsN3JXrbbwblzH8YFOz9MMKkTY5lQNQFoKdTHoPVXBqcMlGQGgC/kkH9TH110MDE3L+Z0i9kG53QLcHNVC0cJzo6X77PQF1WoS33Ssf1s21kUB6fe/5G2L2Tuy635ingtnQMUazwJMli+y/3U7QqqcuJzITbLw/zozRzbqFRE4HHOyGkJSptraH4/3cjmYro8JL20A7EUDiXxmn+z+eoxaBWaEP2Rs5LH0ocrwYmBmSUIrPy4e5ESlRRVuv+prNUB1pYGnFZUI9HRRFxRFg+SxXZZp4mq8jzroRLZFAqKY1/eDgsCGoV1HG3QW1t0jGNkv8BL/Zpz7Hhmt0lRr7rVN+PJaTQx8NNUJZ/l7nMXp/+7aTbkyI8PiNyemko0+joDrkBWdcK0vi8Du6k3oQRBUsJ0aM0iPxMWQq/oYyHqpf6revycYTUdOGeckGWXuXLNRIZQ/PsKuw/teqDaBkkMKaQH2oXWMsacL6WDu5VYUQidOtaTgHX8AKVHF/vobTQfw7HgPKlyRN41dAX+YIjRABYoqU7M2MYPuoPYbqzTz7iLZyFP8FCJ3dRQBTWSeer/s+Ih6Ev1EsznVmAw9SKb5q8QCSYk5mMugUTDVETOQo10CGAF6094/PcJRXgag/uWs4k+13c+/HaeNzrHQyqzWnf9bFFHIvs7Wqov9k+nWv/TwSGplkyBk4bHfhEZNbl+b6T8sZXsv8mTT+INzKT4DCVZ5a/G+NQ6Xo26Aa6Se5DOdzdXOLZ7yIhOi+olip8AOXIIFLb7Vee1+q7Ri6ScUJEKxdEvec5EceXEtWs0U4AnuHgiH0ewJSX9yUp8T4ImCzu00JhTMOH2nXU3PJuGgZgrDifNWXGpFruIBjHWRKpESpy+9HPGoNoxSO/hzSfuM5LKvwX+I96cMK5OC+fKsBh59cMpU4YnqFNh59ZP/kaMDxAXgrVku5s9oB3Rgc7t1rfm6dqycBiwn4QnYJpk7M+gzAbrF/nty8y84ajNdhINXgMgvv3JLC5YTqznBPm3koHSOEF8f4xtCZtzjNN6jNjX8gEvxoRpC9Z+X15c8XBSFyDMBoi0FMDor619XJKgTxNiOYmeH/DLhKEb8MIaug8IFu34FKbG81IsWD+zwA1A5xqXEVlgsfEOcML+Oe2rulkpwQBWnsmY+f/Z80004Mytreo/ME2TDuPJPOi15D0j0CbwG7xzE8qrn0EomBE9mrwH3uuH7df3lzx1HiLAmh2s4MO5R64AoWeAPQW9lWZPzp+2dNgk+0qWpoEsMN+r91yWxUZHgeoC+GJKs13LItH32sGInyUrquMYQjEh44fAEFrMREcS4A7l+875GUmWC6i2MSLyvtAzuPS2IV0t9GU0ooH8cfG5JUeDnsJcVanJTR+XuXPetM+cDyGFTGri3lsCndE9maLgs9iDHtJxzKUBUUakaIPLsXtZD51cS4jhSbbHuzgC21BRtnBhCE2phVYbz4Tj0t90wBmemBaP2eVwv9p4s/JJAHEELvV7k+Gro4FozOoC2WBdqdTPDFB00la08O6ADBdjd2el2pRA7HCTG6v3qwA6RQJfm4UHOXaFYhhiNngxHB4VHZMvpd+YMEqNmtYOb4lzWwI13iHU0Rh0Sj4IaY9EwEy/KjR5dc2DjWGj11SFFCP1uG24fccF+LhtdMNDItI1mBxfaeRelYlsCZwtbbVLnuVQ5izuTzXa48x7CBaHnD3i09BZCuQITLX4d7KkD6CLWWTHrc5onLFJKxRF/p5AEFoqtN3vP9CsLXSYNKxFV5UbxAY5TnAoLFCCo0WbgvtAV77jWSbru9Oq/7ORN9smog4zNuYBZE1uXjxM2xszDduOym7+CxwpJMc7XPknt2XwiANaf2QANMMMH6lmnjTH1RVR7oH4+ts4xVcsdiO/QOlMp+Th9/KIMYyUevR18vHbs+88uxzIG28/58xLZTs6rZc71g9mGw9Q21ugL2sfTc7e0VDMnAmdg+92s2RXQMvkmx+oRc+IsFqgOZzjYcTmnJxZaXsoIsydDVcTalgmhK6/dD0grPLaGgaeXMEw2hN8p8seAHrGRW8+6WBD63NBYaAG0/zwuGOCHUo/BeG9bz39Hsz9Yvs5KvJiLhmM5K+8RlnxIY329REqdFZVyuXyy0NpDueFQelnd0j47Quc7GJGX3QycJiKpLolMtDnpnjgYOvdfycM+JEMZGwpLsBBE8R6vJ3RVczT6DdMtpVQ4l7kOzsPSYlp4qAv5fiqUboyv5eP7G7MOD/qSUwBnMS1p9Vm4Wr8B9w=="
_, _, _, _, err := ParseAuthorizedKey([]byte(rsa16384))
if err == nil {
t.Fatal("ParseAuthorizedKey accepted a 16384-bit modulus; expected it to be rejected")
if _, _, _, _, err := ParseAuthorizedKey([]byte(rsa16384)); err != nil {
t.Fatalf("ParseAuthorizedKey rejected a 16384-bit modulus: %v", err)
}
// A modulus larger than 16384 bits must be rejected to bound the cost of
// verifying an attacker-supplied key.
n := new(big.Int).Lsh(big.NewInt(1), 16384) // 16385 bits
pub, err := NewPublicKey(&rsa.PublicKey{N: n, E: 65537})
if err != nil {
t.Fatalf("NewPublicKey: %v", err)
}
_, err = ParsePublicKey(pub.Marshal())
if err == nil {
t.Fatal("ParsePublicKey accepted a modulus larger than 16384 bits; expected it to be rejected")
}
expectedError := "rsa modulus too large"
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
}
}
func TestParsePrivateKeyRSAModulusTooLarge(t *testing.T) {
func TestParsePrivateKeyRSAModulus16384(t *testing.T) {
rsa16384 := []byte(`-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAIFwAAAAdzc2gtcn
NhAAAAAwEAAQAACAEAy58lBbrFRB4r18MqVA625r4nzxjTkMlN5LxoG7ARC9L7px5lPs4o
@@ -415,14 +426,10 @@ OthateBQGfnsGpTLxkpydh4jlYzTCp9bVRK8YXwsocglOcaRQH5ghfmgsAAAALbmljb2xh
QHAxNnM=
-----END OPENSSH PRIVATE KEY-----
`)
_, err := ParseRawPrivateKey(rsa16384)
if err == nil {
t.Fatal("ParseRawPrivateKey accepted a 16384-bit modulus; expected it to be rejected")
}
expectedError := "rsa modulus too large"
if !strings.Contains(err.Error(), expectedError) {
t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError)
// A 16384-bit modulus is the largest OpenSSH will generate and must be
// accepted.
if _, err := ParseRawPrivateKey(rsa16384); err != nil {
t.Fatalf("ParseRawPrivateKey rejected a 16384-bit modulus: %v", err)
}
}
@@ -848,6 +855,65 @@ func TestInvalidEntry(t *testing.T) {
}
}
func TestAuthorizedKeyOptionWithoutKeyType(t *testing.T) {
_, pubSerialized := getTestKey()
for _, opt := range []string{"restrict", "no-pty", "no-port-forwarding", "no-agent-forwarding"} {
// The key type ("ssh-rsa") is intentionally omitted.
line := opt + " " + pubSerialized
testAuthorizedKeys(t, []byte(line), []testAuthResult{
{nil, nil, "", "", false},
})
}
}
func TestAuthorizedKeyTypeMismatch(t *testing.T) {
_, pubSerialized := getTestKey() // an ssh-rsa key
lines := []string{
"ssh-ed25519 " + pubSerialized + " user@host",
"restrict ssh-ed25519 " + pubSerialized + " user@host",
"rsa-sha2-512 " + pubSerialized + " user@host",
}
for _, line := range lines {
testAuthorizedKeys(t, []byte(line), []testAuthResult{
{nil, nil, "", "", false},
})
}
}
func TestAuthorizedKeyCertificate(t *testing.T) {
certLine := testdata.SSHCertificates["rsa-user-testcertificate"]
key, _, options, _, err := ParseAuthorizedKey(certLine)
if err != nil {
t.Fatalf("ParseAuthorizedKey on certificate: %v", err)
}
if _, ok := key.(*Certificate); !ok {
t.Fatalf("got %T, want *Certificate", key)
}
if len(options) != 0 {
t.Errorf("got options %v, want none", options)
}
key, _, options, _, err = ParseAuthorizedKey(append([]byte("cert-authority "), certLine...))
if err != nil {
t.Fatalf("ParseAuthorizedKey on cert-authority certificate: %v", err)
}
if _, ok := key.(*Certificate); !ok {
t.Fatalf("got %T, want *Certificate", key)
}
if !reflect.DeepEqual(options, []string{"cert-authority"}) {
t.Errorf("got options %v, want [cert-authority]", options)
}
}
func TestAuthorizedKeyOptionWithKeyType(t *testing.T) {
pub, pubSerialized := getTestKey()
line := "restrict ssh-rsa " + pubSerialized + " user@host"
testAuthorizedKeys(t, []byte(line), []testAuthResult{
{pub, []string{"restrict"}, "user@host", "", true},
})
}
var knownHostsParseTests = []struct {
input string
err string
@@ -927,6 +993,13 @@ var knownHostsParseTests = []struct {
"@marker \tlocalhost,[host2:123]\tssh-rsa aabbccdd",
"short read",
"", "", nil, "",
},
{
// Declared key type does not match the type embedded in the blob.
"localhost ssh-ed25519 {RSAPUB}",
"key type mismatch",
"", "", nil, "",
},
}
@@ -941,7 +1014,7 @@ func TestKnownHostsParsing(t *testing.T) {
input := test.input
if strings.Contains(input, rsaKeyToken) {
expectedKey = rsaPub
input = strings.Replace(test.input, rsaKeyToken, rsaPubSerialized, -1)
input = strings.ReplaceAll(test.input, rsaKeyToken, rsaPubSerialized)
}
marker, hosts, pubKey, comment, rest, err := ParseKnownHosts([]byte(input))
+13 -4
View File
@@ -142,6 +142,15 @@ func keyEq(a, b ssh.PublicKey) bool {
return bytes.Equal(a.Marshal(), b.Marshal())
}
// plainKeyBlob returns the serialized public portion of key: for a
// certificate this is the certified key, otherwise the key itself.
func plainKeyBlob(key ssh.PublicKey) string {
if cert, ok := key.(*ssh.Certificate); ok {
return string(cert.Key.Marshal())
}
return string(key.Marshal())
}
// IsHostAuthority can be used as a callback in ssh.CertChecker
func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
h, p, err := net.SplitHostPort(address)
@@ -160,10 +169,10 @@ func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool
// IsRevoked can be used as a callback in ssh.CertChecker
func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
if _, ok := db.revoked[string(key.Marshal())]; ok {
if _, ok := db.revoked[plainKeyBlob(key)]; ok {
return true
}
if _, ok := db.revoked[string(key.SignatureKey.Marshal())]; ok {
if _, ok := db.revoked[plainKeyBlob(key.SignatureKey)]; ok {
return true
}
return false
@@ -228,7 +237,7 @@ func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error
}
if marker == markerRevoked {
db.revoked[string(key.Marshal())] = &KnownKey{
db.revoked[plainKeyBlob(key)] = &KnownKey{
Key: key,
Filename: filename,
Line: linenum,
@@ -341,7 +350,7 @@ func (r *RevokedError) Error() string {
// check checks a key against the host database. This should not be
// used for verifying certificates.
func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
if revoked := db.revoked[plainKeyBlob(remoteKey)]; revoked != nil {
return &RevokedError{Revoked: *revoked}
}
+62
View File
@@ -316,6 +316,68 @@ func TestRevokedCA(t *testing.T) {
}
}
func TestRevokedCertifiedKey(t *testing.T) {
_, caPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
caSigner, err := ssh.NewSignerFromKey(caPriv)
if err != nil {
t.Fatal(err)
}
_, hostPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
hostKey, err := ssh.NewPublicKey(hostPriv.Public())
if err != nil {
t.Fatal(err)
}
signCert := func(serial uint64, principal string) *ssh.Certificate {
cert := &ssh.Certificate{
CertType: ssh.HostCert,
Key: hostKey,
Serial: serial,
ValidBefore: ssh.CertTimeInfinity,
ValidPrincipals: []string{principal},
}
if err := cert.SignCert(rand.Reader, caSigner); err != nil {
t.Fatal(err)
}
return cert
}
cert := signCert(1, "server.org")
// Revoking the host key revokes any certificate for it, even when
// the CA is trusted, matching OpenSSH's check_key_not_revoked.
db := testDB(t, "@revoked server.org "+serialize(hostKey)+"\n"+
"@cert-authority server.org "+serialize(caSigner.PublicKey())+"\n")
if !db.IsRevoked(cert) {
t.Error("IsRevoked returned false for certificate with revoked certified key")
}
// Conversely, a @revoked line containing a certificate revokes the
// plain key it certifies.
db = testDB(t, "@revoked server.org "+serialize(cert)+"\n")
if err := db.check("server.org:22", testAddr, hostKey); err == nil {
t.Error("no error for plain key revoked via certificate")
} else if _, ok := err.(*RevokedError); !ok {
t.Errorf("got %T (%v), want *RevokedError", err, err)
}
// Since only public portions are compared, revoking a certificate
// also revokes any sibling certificate certifying the same key,
// whatever its serial number and principals.
sibling := signCert(2, "other.org")
db = testDB(t, "@revoked * "+serialize(cert)+"\n"+
"@cert-authority * "+serialize(caSigner.PublicKey())+"\n")
if !db.IsRevoked(sibling) {
t.Error("IsRevoked returned false for sibling certificate of a revoked certificate")
}
}
const testHostname = "hostname"
// generated with keygen -H -f
+1 -1
View File
@@ -44,7 +44,7 @@ type disconnectMsg struct {
}
func (d *disconnectMsg) Error() string {
return fmt.Sprintf("ssh: disconnect, reason %d: %s", d.Reason, d.Message)
return fmt.Sprintf("ssh: disconnect, reason %d: %q", d.Reason, d.Message)
}
// See RFC 4253, section 7.1.
+28 -2
View File
@@ -9,6 +9,7 @@ import (
"math/big"
"math/rand"
"reflect"
"strings"
"testing"
"testing/quick"
)
@@ -262,6 +263,31 @@ func TestDecode(t *testing.T) {
}
}
func TestDisconnectMsgSanitizesMessage(t *testing.T) {
for _, tc := range []struct {
in string
want string
}{
{"clean message", `"clean message"`},
{"line1\nline2\nline3", `"line1\nline2\nline3"`},
{"has\x00null\x00bytes", `"has\x00null\x00bytes"`},
{"\x1b[31mred\x1b[0m", `"\x1b[31mred\x1b[0m"`},
{"newline\r\nCRLF", `"newline\r\nCRLF"`},
{"tab\there", `"tab\there"`},
} {
t.Run(tc.in, func(t *testing.T) {
d := &disconnectMsg{Reason: 11, Message: tc.in}
got := d.Error()
if !strings.Contains(got, tc.want) {
t.Errorf("got %q, want %q", got, tc.want)
}
if bad := "\n\r\x00\x1b"; strings.ContainsAny(got, bad) {
t.Errorf("got %q, want none of %q", got, bad)
}
})
}
}
func randomBytes(out []byte, rand *rand.Rand) {
for i := 0; i < len(out); i++ {
out[i] = byte(rand.Int31())
@@ -318,7 +344,7 @@ var (
)
func BenchmarkMarshalKexInitMsg(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
Marshal(_kexInitMsg)
}
}
@@ -331,7 +357,7 @@ func BenchmarkUnmarshalKexInitMsg(b *testing.B) {
}
func BenchmarkMarshalKexDHInitMsg(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
Marshal(_kexDHInitMsg)
}
}
+7 -6
View File
@@ -32,18 +32,21 @@ type chanList struct {
offset uint32
}
// Assigns a channel ID to the given channel.
func (c *chanList) add(ch *channel) uint32 {
// add stores the given channel and assigns its localId while holding the
// lock, so that getChan can never return a channel whose localId is not yet
// initialized.
func (c *chanList) add(ch *channel) {
c.Lock()
defer c.Unlock()
for i := range c.chans {
if c.chans[i] == nil {
c.chans[i] = ch
return uint32(i) + c.offset
ch.localId = uint32(i) + c.offset
return
}
}
c.chans = append(c.chans, ch)
return uint32(len(c.chans)-1) + c.offset
ch.localId = uint32(len(c.chans)-1) + c.offset
}
// getChan returns the channel for the given ID.
@@ -343,8 +346,6 @@ func (m *mux) OpenChannel(chanType string, extra []byte) (Channel, <-chan *Reque
func (m *mux) openChannel(chanType string, extra []byte) (*channel, error) {
ch := m.newChannel(chanType, channelOutbound, extra)
ch.maxIncomingPayload = channelMaxPacket
open := channelOpenMsg{
ChanType: chanType,
PeersWindow: ch.myWindow,
+258 -2
View File
@@ -5,9 +5,11 @@
package ssh
import (
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
"sync"
"testing"
"time"
@@ -299,7 +301,7 @@ func TestMuxReject(t *testing.T) {
t.Errorf("got %#v, want {Reason: 42, Message: %q}", ocf, "message")
}
want := "ssh: rejected: unknown reason 42 (message)"
want := "ssh: rejected: unknown reason 42 (\"message\")"
if err.Error() != want {
t.Errorf("got %q, want %q", err.Error(), want)
}
@@ -1181,6 +1183,13 @@ func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
return
}
}
// Follow the flood with a request so the client can synchronize with
// its mux loop. Once the client receives this request, the mux loop has
// necessarily processed (and discarded) all the preceding responses.
if _, err := serverCh.SendRequest("sync", false, nil); err != nil {
done <- fmt.Errorf("send sync request: %w", err)
return
}
// Echo any legitimate request back.
for req := range serverCh.incomingRequests {
if req.WantReply {
@@ -1193,9 +1202,17 @@ func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
done <- nil
}()
req, ok := <-clientCh.incomingRequests
if !ok {
t.Fatal("channel closed before sync request")
}
if req.Type != "sync" {
t.Fatalf("unexpected sync request type %q", req.Type)
}
// If the flood had wedged the mux loop, this SendRequest would never
// receive a reply.
ok, err := clientCh.SendRequest("ping", true, []byte("hello"))
ok, err = clientCh.SendRequest("ping", true, []byte("hello"))
if err != nil {
t.Fatalf("SendRequest: %v", err)
}
@@ -1213,6 +1230,245 @@ func TestChannelUnexpectedResponsesDiscarded(t *testing.T) {
}
}
func TestChannelUndecidedOutboundDropsTraffic(t *testing.T) {
// Traffic on an outbound channel the peer has not confirmed yet is dropped;
// the open confirmation is still processed.
a, b := memPipe()
defer a.Close()
defer b.Close()
// Drain the peer end so that a write would not block.
go func() {
for {
if _, err := b.readPacket(); err != nil {
return
}
}
}()
m := newMux(a)
ch := m.newChannel("session", channelOutbound, nil)
if ch.decided {
t.Fatal("freshly opened outbound channel should not be decided")
}
// Flooding channel requests must not block.
reqPacket := Marshal(channelRequestMsg{PeersID: ch.localId, Request: "x"})
for range chanSize * 4 {
if err := ch.handlePacket(reqPacket); err != nil {
t.Fatalf("handlePacket(channelRequest): %v", err)
}
}
if n := len(ch.incomingRequests); n != 0 {
t.Fatalf("incomingRequests should be empty, has %d entries", n)
}
// A close before the confirmation must not remove or close the channel, and
// must not emit a close for the still-unknown remote id.
if err := ch.handlePacket(Marshal(channelCloseMsg{PeersID: ch.localId})); err != nil {
t.Fatalf("handlePacket(channelClose): %v", err)
}
if m.chanList.getChan(ch.localId) != ch {
t.Fatal("premature close removed the channel from the chanList")
}
// The open confirmation is still processed and decides the channel.
confirm := Marshal(channelOpenConfirmMsg{
PeersID: ch.localId,
MyID: 42,
MyWindow: 1 << 20,
MaxPacketSize: 1 << 15,
})
if err := ch.handlePacket(confirm); err != nil {
t.Fatalf("handlePacket(openConfirm): %v", err)
}
if !ch.decided {
t.Fatal("channel not decided after open confirmation")
}
if _, ok := (<-ch.msg).(*channelOpenConfirmMsg); !ok {
t.Fatal("open confirmation was not delivered on ch.msg")
}
}
func TestChannelUndecidedInboundDropsRequests(t *testing.T) {
// Channel requests on an inbound channel the local application has not
// accepted or rejected yet are dropped.
serverMux, clientMux := muxPair()
defer serverMux.Close()
defer clientMux.Close()
go DiscardRequests(serverMux.incomingRequests)
// The server assigns its first inbound channel id 0; open such a channel
// and flood it before it is decided. The channel is left undecided, as it
// would be in the window before the server accepts or rejects it: its
// NewChannel is never received from incomingChannels.
ch := clientMux.newChannel("chan", channelOutbound, nil)
if err := clientMux.sendMessage(channelOpenMsg{
ChanType: "chan",
PeersWindow: ch.myWindow,
MaxPacketSize: ch.maxIncomingPayload,
PeersID: ch.localId,
}); err != nil {
t.Fatalf("send open: %v", err)
}
for range chanSize * 4 {
if err := clientMux.sendMessage(channelRequestMsg{PeersID: 0, Request: "x"}); err != nil {
break // the server may have closed the connection
}
}
// The server loop keeps reading: closing the client makes it observe EOF
// and return from Wait.
clientMux.Close()
serverMux.Wait()
}
func TestChannelRejectedInboundDropsTraffic(t *testing.T) {
// A rejected channel is decided but is never established, and traffic on it
// is dropped.
a, b := memPipe()
defer a.Close()
defer b.Close()
go func() {
for {
if _, err := b.readPacket(); err != nil {
return
}
}
}()
m := newMux(a)
ch := m.newChannel("session", channelInbound, nil)
ch.remoteId = 42
if err := ch.Reject(ConnectionFailed, "no thanks"); err != nil {
t.Fatalf("Reject: %v", err)
}
if !ch.decided {
t.Fatal("a rejected channel should be decided")
}
if ch.established.Load() {
t.Fatal("a rejected channel should not be established")
}
// Flooding channel requests must not block.
reqPacket := Marshal(channelRequestMsg{PeersID: ch.localId, Request: "x"})
for range chanSize * 4 {
if err := ch.handlePacket(reqPacket); err != nil {
t.Fatalf("handlePacket(channelRequest): %v", err)
}
}
if n := len(ch.incomingRequests); n != 0 {
t.Fatalf("incomingRequests should be empty, has %d entries", n)
}
}
func TestChannelBogusMessageTearsDownMux(t *testing.T) {
// A message that is well-formed on the wire but is not a type expected on
// an established channel is a protocol error that tears the mux down.
clientMux, serverMux := muxPair()
defer serverMux.Close()
defer clientMux.Close()
serverRes := make(chan *channel, 1)
go func() {
newCh, ok := <-serverMux.incomingChannels
if !ok {
close(serverRes)
return
}
c, _, err := newCh.Accept()
if err != nil {
close(serverRes)
return
}
serverRes <- c.(*channel)
}()
if _, err := clientMux.openChannel("chan", nil); err != nil {
t.Fatalf("openChannel: %v", err)
}
serverCh := <-serverRes
if serverCh == nil {
t.Fatal("server did not accept channel")
}
// Craft a packet that the client mux routes to clientCh (bytes 1:5 hold the
// peer's channel id) and that decode accepts, but whose type is unexpected
// on a channel. The service-string length equals the channel id, so the
// same four bytes serve both as the routing id and as the string length.
id := serverCh.remoteId
packet := []byte{msgServiceRequest, 0, 0, 0, 0}
binary.BigEndian.PutUint32(packet[1:], id)
packet = append(packet, make([]byte, id)...)
if err := serverMux.conn.writePacket(packet); err != nil {
t.Fatalf("writePacket: %v", err)
}
// The mux loop exits with the protocol error.
if err := clientMux.Wait(); err == nil || !strings.Contains(err.Error(), "unexpected message type") {
t.Fatalf("mux error = %v, want unexpected message type", err)
}
}
func TestChannelCloseIdempotent(t *testing.T) {
a, b := memPipe()
defer a.Close()
defer b.Close()
m := newMux(a)
ch := m.newChannel("session", channelOutbound, nil)
ch.close()
ch.close() // must be a no-op, not a panic
}
func TestNewChannelFullyInitializedWhenPublished(t *testing.T) {
a, b := memPipe()
defer a.Close()
defer b.Close()
m := newMux(a)
const channels = 100
stop := make(chan struct{})
readerDone := make(chan struct{})
go func() {
// Mimic the mux loop: fetch channels by id and read the fields it
// uses to route and validate packets.
defer close(readerDone)
for {
select {
case <-stop:
return
default:
}
for id := uint32(0); id < channels; id++ {
ch := m.chanList.getChan(id)
if ch == nil {
continue
}
if got := ch.localId; got != id {
t.Errorf("channel registered under id %d has localId %d", id, got)
return
}
if got := ch.maxIncomingPayload; got != channelMaxPacket {
t.Errorf("channel %d: maxIncomingPayload = %d, want %d", id, got, channelMaxPacket)
return
}
}
}
}()
for range channels {
ch := m.newChannel("session", channelOutbound, nil)
if m.chanList.getChan(ch.localId) != ch {
t.Fatalf("channel %d not registered under its localId", ch.localId)
}
}
close(stop)
<-readerDone
}
func TestChannelConcurrentRequests(t *testing.T) {
writer, reader, mux := channelPair(t)
defer writer.Close()
+56 -23
View File
@@ -26,10 +26,16 @@ type Permissions struct {
// defines "force-command" (only allow the given command to
// execute) and "source-address" (only allow connections from
// the given address). The SSH package currently only enforces
// the "source-address" critical option. It is up to server
// implementations to enforce other critical options, such as
// "force-command", by checking them after the SSH handshake
// is successful. In general, SSH servers should reject
// the "source-address" critical option: it is validated against
// the client's remote address whenever it is present in the
// Permissions returned by any authentication callback. Its value
// is a comma-separated list of IP addresses and CIDR blocks;
// consistently with OpenSSH, a connection whose remote address is
// not an IP address, such as a Unix domain socket, never matches
// the list and is rejected when the option is present. It is up
// to server implementations to enforce other critical options,
// such as "force-command", by checking them after the SSH
// handshake is successful. In general, SSH servers should reject
// connections that specify critical options that are unknown
// or not supported.
CriticalOptions map[string]string
@@ -223,7 +229,9 @@ type ServerConfig struct {
// 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.
// can instead be returned by VerifiedPublicKeyCallback. The
// signatureAlgorithm argument is the format of the signature that was
// successfully verified.
//
// VerifiedPublicKeyCallback does not affect which authentication methods
// are included in the list of methods that can be attempted by the client.
@@ -442,6 +450,10 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error)
return perms, err
}
// checkSourceAddress matches addr against sourceAddrs, a comma-separated list
// of IP addresses and CIDR blocks. Consistently with OpenSSH, a remote address
// that is not IP-based, such as a Unix domain socket, never matches the list
// and is rejected.
func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
if addr == nil {
return errors.New("ssh: no address known for client, but source-address match required")
@@ -449,7 +461,7 @@ func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
tcpAddr, ok := addr.(*net.TCPAddr)
if !ok {
return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
return fmt.Errorf("ssh: remote address %v is not a TCP address when checking source-address match", addr)
}
for _, sourceAddr := range strings.Split(sourceAddrs, ",") {
@@ -472,6 +484,21 @@ func checkSourceAddress(addr net.Addr, sourceAddrs string) error {
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
}
// checkSourceAddressCriticalOption enforces the source-address critical
// option, if present in perms, as documented in Permissions.CriticalOptions.
// A present but empty value matches no address, so it denies authentication,
// consistently with OpenSSH, rather than being treated as absent.
func checkSourceAddressCriticalOption(addr net.Addr, perms *Permissions) error {
if perms == nil {
return nil
}
saco, ok := perms.CriticalOptions[sourceAddressCriticalOption]
if !ok {
return nil
}
return checkSourceAddress(addr, saco)
}
func gssExchangeToken(gssapiConfig *GSSAPIWithMICConfig, token []byte, s *connection,
sessionID []byte, userAuthReq userAuthRequestMsg) (authErr error, perms *Permissions, err error) {
gssAPIServer := gssapiConfig.Server
@@ -685,7 +712,7 @@ userAuthLoop:
}
if userAuthReq.Service != serviceSSH {
return nil, errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
return nil, fmt.Errorf("ssh: client attempted to negotiate for unknown service: %q", userAuthReq.Service)
}
if s.user != userAuthReq.User && partialSuccessReturned {
@@ -771,7 +798,8 @@ userAuthLoop:
pubKey, err := ParsePublicKey(pubKeyData)
if err != nil {
return nil, err
authErr = err
break
}
candidate, ok := cache.get(s.user, pubKeyData)
@@ -784,13 +812,14 @@ userAuthLoop:
return nil, errors.New("ssh: invalid library usage: PublicKeyCallback must not return partial success when VerifiedPublicKeyCallback is defined")
}
if (candidate.result == nil || isPartialSuccessError) &&
candidate.perms != nil &&
candidate.perms.CriticalOptions != nil &&
candidate.perms.CriticalOptions[sourceAddressCriticalOption] != "" {
if err := checkSourceAddress(
s.RemoteAddr(),
candidate.perms.CriticalOptions[sourceAddressCriticalOption]); err != nil {
// This check is authoritative for the Permissions returned by
// PublicKeyCallback: the check at the end of the auth loop sees
// the final Permissions, which VerifiedPublicKeyCallback may
// have replaced, and is skipped on partial success. It also
// makes public key queries fail before the client signs when
// PublicKeyCallback supplies the restriction.
if candidate.result == nil || isPartialSuccessError {
if err := checkSourceAddressCriticalOption(s.RemoteAddr(), candidate.perms); err != nil {
candidate.result = err
}
}
@@ -864,14 +893,7 @@ userAuthLoop:
// Only call VerifiedPublicKeyCallback after the key has been accepted
// and successfully verified. If authErr is non-nil, the key is not
// considered verified and the callback must not run.
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, algo)
}
if authErr == nil && perms != nil && perms.CriticalOptions != nil {
if saco := perms.CriticalOptions[sourceAddressCriticalOption]; saco != "" {
if err := checkSourceAddress(s.RemoteAddr(), saco); err != nil {
authErr = err
}
}
perms, authErr = config.VerifiedPublicKeyCallback(s, pubKey, perms, sig.Format)
}
}
case "gssapi-with-mic":
@@ -925,6 +947,17 @@ userAuthLoop:
authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method)
}
// The source-address critical option is enforced on the Permissions
// returned by any authentication callback. Permissions returned
// together with a PartialSuccessError skip this check: that is safe
// because they are required to be nil, as enforced in the partial
// success handling below.
if authErr == nil {
if err := checkSourceAddressCriticalOption(s.RemoteAddr(), perms); err != nil {
authErr = err
}
}
authErrs = append(authErrs, authErr)
if config.AuthLogCallback != nil {
+11 -1
View File
@@ -26,11 +26,21 @@ func doClientServerAuth(t *testing.T, serverConfig *ServerConfig, clientConfig *
serverConfig.AuthLogCallback = func(conn ConnMetadata, method string, err error) {
serverAuthErrors = append(serverAuthErrors, err)
}
go newServer(c1, serverConfig)
serverDone := make(chan struct{})
go func() {
defer close(serverDone)
newServer(c1, serverConfig)
}()
c, _, _, err := NewClientConn(c2, "", clientConfig)
if err == nil {
c.Close()
} else {
// Unblock the server if it is still reading from the connection.
c2.Close()
}
// Wait for the server side to finish before reading serverAuthErrors:
// AuthLogCallback appends to it from the server goroutine.
<-serverDone
return serverAuthErrors, err
}
+157
View File
@@ -544,6 +544,62 @@ func TestVerifiedPublicKeyCallback(t *testing.T) {
<-done
}
func TestVerifiedPublicKeyCallbackSignatureFormat(t *testing.T) {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
defer c1.Close()
defer c2.Close()
var gotSignatureAlgorithm string
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) {
gotSignatureAlgorithm = signatureAlgorithm
return permissions, nil
},
}
serverConf.AddHostKey(testSigners["rsa"])
serverDone := make(chan error, 1)
go func() {
conn, _, _, err := NewServerConn(c1, serverConf)
if err == nil {
conn.Close()
}
serverDone <- err
}()
clientConf := &ClientConfig{
User: "user",
Auth: []AuthMethod{
// The selected algorithm and signature format differ for
// compatibility with old RSA clients.
configurablePublicKeyCallback{
signer: testSigners["rsa"].(AlgorithmSigner),
signatureAlgo: KeyAlgoRSASHA256,
signatureFormat: KeyAlgoRSA,
},
},
HostKeyCallback: InsecureIgnoreHostKey(),
}
clientConn, _, _, clientErr := NewClientConn(c2, "", clientConf)
if clientErr == nil {
clientConn.Close()
}
serverErr := <-serverDone
if clientErr != nil || serverErr != nil {
t.Fatalf("connection failed: client error: %v; server error: %v", clientErr, serverErr)
}
if gotSignatureAlgorithm != KeyAlgoRSA {
t.Errorf("signature algorithm = %q; want verified signature format %q", gotSignatureAlgorithm, KeyAlgoRSA)
}
}
func TestVerifiedPublicCallbackPartialSuccess(t *testing.T) {
c1, c2, err := netPipe()
if err != nil {
@@ -760,6 +816,107 @@ func TestVerifiedPubKeyCallbackSourceAddress(t *testing.T) {
}
}
func TestAuthCallbacksSourceAddress(t *testing.T) {
permsWithSourceAddress := func(sourceAddress string) *Permissions {
return &Permissions{
CriticalOptions: map[string]string{
sourceAddressCriticalOption: sourceAddress,
},
}
}
methods := []struct {
name string
serverConfig func(sourceAddress string) *ServerConfig
clientAuth []AuthMethod
}{
{
name: "password",
serverConfig: func(sourceAddress string) *ServerConfig {
return &ServerConfig{
PasswordCallback: func(conn ConnMetadata, password []byte) (*Permissions, error) {
return permsWithSourceAddress(sourceAddress), nil
},
}
},
clientAuth: []AuthMethod{Password(clientPassword)},
},
{
name: "keyboard-interactive",
serverConfig: func(sourceAddress string) *ServerConfig {
return &ServerConfig{
KeyboardInteractiveCallback: func(conn ConnMetadata, challenge KeyboardInteractiveChallenge) (*Permissions, error) {
return permsWithSourceAddress(sourceAddress), nil
},
}
},
clientAuth: []AuthMethod{
KeyboardInteractive(func(name, instruction string, questions []string, echos []bool) ([]string, error) {
return nil, nil
}),
},
},
{
name: "none",
serverConfig: func(sourceAddress string) *ServerConfig {
return &ServerConfig{
NoClientAuth: true,
NoClientAuthCallback: func(conn ConnMetadata) (*Permissions, error) {
return permsWithSourceAddress(sourceAddress), nil
},
}
},
},
}
for _, method := range methods {
for _, tc := range []struct {
name string
sourceAddress string
wantErr bool
}{
{"mismatching", "192.168.99.99", true},
{"matching", "127.0.0.0/8,::1/128", false},
{"present but empty", "", true},
} {
t.Run(method.name+"/"+tc.name, func(t *testing.T) {
clientConf := &ClientConfig{
User: "user",
Auth: method.clientAuth,
HostKeyCallback: InsecureIgnoreHostKey(),
}
serverAuthErrors, err := doClientServerAuth(t, method.serverConfig(tc.sourceAddress), clientConf)
if tc.wantErr {
if err == nil {
t.Fatalf("client login succeeded with %s callback returning mismatching source-address", method.name)
}
var sourceAddressErrors int
for _, err := range serverAuthErrors {
if err != nil && strings.Contains(err.Error(), "source-address restriction") {
sourceAddressErrors++
}
}
if sourceAddressErrors == 0 {
t.Fatalf("no server auth error mentions the source-address restriction, got %v", serverAuthErrors)
}
} else if err != nil {
t.Fatalf("client login failed with matching source-address: %v", err)
}
})
}
}
}
func TestCheckSourceAddressNonIP(t *testing.T) {
for _, addr := range []net.Addr{
&net.UnixAddr{Name: "/run/test.sock", Net: "unix"},
&net.UnixAddr{},
nil,
} {
if err := checkSourceAddress(addr, "127.0.0.0/8,::1/128"); err == nil {
t.Errorf("source-address check succeeded for non-IP remote address %v", addr)
}
}
}
func TestVerifiedPublicCallbackPartialSuccessBadUsage(t *testing.T) {
c1, c2, err := netPipe()
if err != nil {
+1 -1
View File
@@ -745,7 +745,7 @@ func TestSessionID(t *testing.T) {
s := <-serverID
c := <-clientID
if bytes.Compare(s, c) != 0 {
if !bytes.Equal(s, c) {
t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
} else if len(s) == 0 {
t.Errorf("client and server SessionID were empty.")
+10 -6
View File
@@ -118,24 +118,28 @@ func parseGSSAPIPayload(payload []byte) (*userAuthRequestGSSAPI, error) {
OIDS: make([]asn1.ObjectIdentifier, n),
}
for i := 0; i < int(n); i++ {
var (
desiredMech []byte
err error
)
var desiredMech []byte
desiredMech, rest, ok = parseString(rest)
if !ok {
return nil, errors.New("parse string failed")
}
if rest, err = asn1.Unmarshal(desiredMech, &s.OIDS[i]); err != nil {
trailing, err := asn1.Unmarshal(desiredMech, &s.OIDS[i])
if err != nil {
return nil, err
}
if len(trailing) != 0 {
return nil, errors.New("trailing bytes after OID")
}
}
if len(rest) != 0 {
return nil, errors.New("trailing bytes after mechanisms")
}
return s, nil
}
// See RFC 4462 section 3.6.
func buildMIC(sessionID string, username string, service string, authMethod string) []byte {
out := make([]byte, 0, 0)
out := make([]byte, 0)
out = appendString(out, sessionID)
out = append(out, msgUserAuthRequest)
out = appendString(out, username)
+29
View File
@@ -1,6 +1,7 @@
package ssh
import (
"encoding/asn1"
"fmt"
"testing"
)
@@ -17,6 +18,34 @@ func TestParseGSSAPIPayload(t *testing.T) {
}
}
func TestParseGSSAPIPayloadMultipleMechs(t *testing.T) {
// A payload offering two mechanisms: Kerberos V5 (1.2.840.113554.1.2.2)
// followed by SPNEGO (1.3.6.1.5.5.2). The parser must track the
// remaining payload across loop iterations, not the trailing bytes
// returned by asn1.Unmarshal.
payload := []byte{
0x00, 0x00, 0x00, 0x02, // two mechanisms
0x00, 0x00, 0x00, 0x0b, // length of the first OID
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02,
0x00, 0x00, 0x00, 0x08, // length of the second OID
0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02,
}
res, err := parseGSSAPIPayload(payload)
if err != nil {
t.Fatal(err)
}
if len(res.OIDS) != 2 {
t.Fatalf("got %d OIDs, want 2", len(res.OIDS))
}
if !res.OIDS[0].Equal(krb5Mesh) {
t.Errorf("OIDS[0]: got %v, want %v", res.OIDS[0], krb5Mesh)
}
spnego := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2}
if !res.OIDS[1].Equal(spnego) {
t.Errorf("OIDS[1]: got %v, want %v", res.OIDS[1], spnego)
}
}
func TestParseDubiousGSSAPIPayload(t *testing.T) {
for _, tc := range []struct {
name string
+2
View File
@@ -58,6 +58,7 @@ func (c *Client) dialStreamLocal(socketPath string) (Channel, error) {
return nil, err
}
go DiscardRequests(in)
go io.Copy(io.Discard, ch.Stderr())
return ch, err
}
@@ -79,6 +80,7 @@ func (l *unixListener) Accept() (net.Conn, error) {
return nil, err
}
go DiscardRequests(incoming)
go io.Copy(io.Discard, ch.Stderr())
return &chanConn{
Channel: ch,
+2
View File
@@ -332,6 +332,7 @@ func (l *tcpListener) Accept() (net.Conn, error) {
return nil, err
}
go DiscardRequests(incoming)
go io.Copy(io.Discard, ch.Stderr())
return &chanConn{
Channel: ch,
@@ -495,6 +496,7 @@ func (c *Client) dial(laddr string, lport int, raddr string, rport int) (Channel
return nil, err
}
go DiscardRequests(in)
go io.Copy(io.Discard, ch.Stderr())
return ch, nil
}
+211
View File
@@ -6,6 +6,7 @@ package ssh
import (
"context"
"io"
"net"
"testing"
"time"
@@ -51,3 +52,213 @@ func TestClientDialContextWithDeadline(t *testing.T) {
t.Errorf("DialContext: got nil error, expected %v", context.DeadlineExceeded)
}
}
// forwardingPair establishes a client/server connection pair over an
// in-memory pipe. The server grants all global requests and accepts all
// channels opened by the client, delivering them on the returned channel.
func forwardingPair(t *testing.T) (*Client, *ServerConn, <-chan Channel) {
t.Helper()
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
serverDone := make(chan struct{})
t.Cleanup(func() { <-serverDone })
t.Cleanup(func() {
c1.Close()
c2.Close()
})
serverConf := ServerConfig{
NoClientAuth: true,
}
serverConf.AddHostKey(testSigners["rsa"])
incoming := make(chan *ServerConn, 1)
serverChans := make(chan Channel, 1)
go func() {
defer close(serverDone)
conn, chans, reqs, err := NewServerConn(c1, &serverConf)
incoming <- conn
if err != nil {
t.Errorf("NewServerConn error: %v", err)
return
}
go func() {
for r := range reqs {
r.Reply(true, nil)
}
}()
for newCh := range chans {
ch, chanReqs, err := newCh.Accept()
if err != nil {
t.Errorf("Accept: %v", err)
continue
}
go DiscardRequests(chanReqs)
serverChans <- ch
}
}()
conf := ClientConfig{
HostKeyCallback: InsecureIgnoreHostKey(),
}
conn, chans, reqs, err := NewClientConn(c2, "", &conf)
if err != nil {
t.Fatalf("NewClientConn: %v", err)
}
client := NewClient(conn, chans, reqs)
t.Cleanup(func() { client.Close() })
server := <-incoming
if server == nil {
t.Fatal("Unable to get server")
}
t.Cleanup(func() { server.Close() })
return client, server, serverChans
}
// floodStderr writes more than the default channel receive window (2 MiB)
// to w, which must be the stderr stream of an SSH channel. Stderr data is
// buffered and its window credit is only returned when the stream is
// read, so if the peer does not drain the stream the write blocks forever
// once the remote window is exhausted.
func floodStderr(t *testing.T, w io.Writer) {
t.Helper()
const payload = 4 * 1024 * 1024
done := make(chan error, 1)
go func() {
buf := make([]byte, 32*1024)
remaining := payload
for remaining > 0 {
n := min(len(buf), remaining)
nw, err := w.Write(buf[:n])
if err != nil {
done <- err
return
}
remaining -= nw
}
done <- nil
}()
select {
case err := <-done:
if err != nil {
t.Fatalf("write to stderr: %v", err)
}
case <-time.After(10 * time.Second):
t.Fatal("write to stderr blocked: the stderr stream is not drained")
}
}
// verifyMainStream checks that the main stream is still usable after
// stderr traffic by sending a payload from the server channel and
// reading it back from the client connection.
func verifyMainStream(t *testing.T, serverCh Channel, clientConn net.Conn) {
t.Helper()
want := []byte("main stream after stderr flood")
if _, err := serverCh.Write(want); err != nil {
t.Fatalf("write to main stream: %v", err)
}
got := make([]byte, len(want))
if _, err := io.ReadFull(clientConn, got); err != nil {
t.Fatalf("read from main stream: %v", err)
}
if string(got) != string(want) {
t.Fatalf("read %q from main stream, want %q", got, want)
}
}
func TestClientDialDiscardsStderr(t *testing.T) {
for _, network := range []string{"tcp", "unix"} {
t.Run(network, func(t *testing.T) {
client, _, serverChans := forwardingPair(t)
addr := "127.0.0.1:8022"
if network == "unix" {
addr = "/tmp/forwarded.sock"
}
conn, err := client.Dial(network, addr)
if err != nil {
t.Fatalf("Dial(%q, %q): %v", network, addr, err)
}
defer conn.Close()
serverCh := <-serverChans
defer serverCh.Close()
floodStderr(t, serverCh.Stderr())
verifyMainStream(t, serverCh, conn)
})
}
}
func TestClientListenerDiscardsStderr(t *testing.T) {
for _, network := range []string{"tcp", "unix"} {
t.Run(network, func(t *testing.T) {
client, server, _ := forwardingPair(t)
var (
ln net.Listener
payload []byte
chType string
err error
)
if network == "tcp" {
ln, err = client.Listen("tcp", "127.0.0.1:8022")
if err != nil {
t.Fatalf("Listen: %v", err)
}
chType = "forwarded-tcpip"
payload = Marshal(&forwardedTCPPayload{
Addr: "127.0.0.1",
Port: 8022,
OriginAddr: "127.0.0.5",
OriginPort: 1234,
})
} else {
ln, err = client.ListenUnix("/tmp/forwarded.sock")
if err != nil {
t.Fatalf("ListenUnix: %v", err)
}
chType = "forwarded-streamlocal@openssh.com"
payload = Marshal(&forwardedStreamLocalPayload{
SocketPath: "/tmp/forwarded.sock",
})
}
defer ln.Close()
// OpenChannel does not return until the channel is
// accepted, so run Accept concurrently.
accepted := make(chan net.Conn, 1)
go func() {
conn, err := ln.Accept()
if err != nil {
t.Errorf("Accept: %v", err)
close(accepted)
return
}
accepted <- conn
}()
serverCh, reqs, err := server.OpenChannel(chType, payload)
if err != nil {
t.Fatalf("OpenChannel(%q): %v", chType, err)
}
go DiscardRequests(reqs)
defer serverCh.Close()
conn := <-accepted
if conn == nil {
t.Fatal("no connection accepted")
}
defer conn.Close()
floodStderr(t, serverCh.Stderr())
verifyMainStream(t, serverCh, conn)
})
}
}
+9 -3
View File
@@ -331,13 +331,19 @@ func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err er
// chars
const maxVersionStringBytes = 255
// maxPreVersionLines is the maximum number of lines sent by the peer
// before the version string. Each of these lines is limited to a maximum
// of maxVersionStringBytes chars. Lines sent before the version string
// are silently ignored.
const maxPreVersionLines = 1024
// Read version string as specified by RFC 4253, section 4.2.
func readVersion(r io.Reader) ([]byte, error) {
versionString := make([]byte, 0, 64)
var ok bool
var buf [1]byte
for length := 0; length < maxVersionStringBytes; length++ {
for lines := 0; len(versionString) < maxVersionStringBytes && lines < maxPreVersionLines; {
_, err := io.ReadFull(r, buf[:])
if err != nil {
return nil, err
@@ -347,9 +353,9 @@ func readVersion(r io.Reader) ([]byte, error) {
if buf[0] == '\n' {
if !bytes.HasPrefix(versionString, []byte("SSH-")) {
// RFC 4253 says we need to ignore all version string lines
// except the one containing the SSH version (provided that
// all the lines do not exceed 255 bytes in total).
// except the one containing the SSH version.
versionString = versionString[:0]
lines++
continue
}
ok = true
+16 -4
View File
@@ -35,11 +35,15 @@ func TestReadVersion(t *testing.T) {
}
func TestReadVersionError(t *testing.T) {
longVersion := strings.Repeat("SSH-2.0-bla", 50)[:253]
multiLineVersion := strings.Repeat("ignored\r\n", 50) + "SSH-2.0-bla\r\n"
tooLongVersion := strings.Repeat("SSH-2.0-bla", 60) + "\r\n"
multiLine := strings.Repeat("ignored\r\n", 50)
tooManyLines := strings.Repeat("ignored\r\n", 2000)
tooLongLine := strings.Repeat("blah ", 100) + "\r\n"
cases := []string{
longVersion + "too-long\r\n",
multiLineVersion,
tooLongVersion,
multiLine + tooLongVersion,
tooManyLines + "SSH-2.0-bla\r\n",
tooLongLine + "SSH-2.0-bla\r\n",
}
for _, in := range cases {
if _, err := readVersion(bytes.NewBufferString(in)); err == nil {
@@ -48,6 +52,14 @@ func TestReadVersionError(t *testing.T) {
}
}
func TestIgnorePreVersion(t *testing.T) {
longishLine := strings.Repeat("blah ", 30) + "\r\n"
multiLineVersion := strings.Repeat(longishLine, 100) + "SSH-2.0-bla\r\n"
if _, err := readVersion(bytes.NewBufferString(multiLineVersion)); err != nil {
t.Errorf("readVersion() got error %v, expected success", err)
}
}
func TestExchangeVersionsBasic(t *testing.T) {
v := "SSH-2.0-bla"
buf := bytes.NewBufferString(v + "\r\n")
Binary file not shown.
+141 -142
View File
@@ -4,7 +4,7 @@ package bundle
var unparsedCertificates = []unparsedCertificate{
{
cn: "CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS,OU=Ceres,O=FNMT-RCM,C=ES,2.5.4.97=#130f56415445532d51323832363030344a",
cn: "CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS,OU=Ceres,O=FNMT-RCM,C=ES,2.5.4.97=VATES-Q2826004J",
sha256Hash: "554153b13d2cf9ddb753bfbe1a4e0ae08d0aa4187058fe60a2b862b2e4b87bcb",
certStartOff: 0,
certLength: 626,
@@ -45,684 +45,683 @@ var unparsedCertificates = []unparsedCertificate{
certStartOff: 6732,
certLength: 502,
},
{
cn: "CN=Atos TrustedRoot 2011,O=Atos,C=DE",
sha256Hash: "f356bea244b7a91eb35d53ca9ad7864ace018e2d35d5f8f96ddf68a6f41aa474",
certStartOff: 7234,
certLength: 891,
},
{
cn: "CN=Atos TrustedRoot Root CA ECC TLS 2021,O=Atos,C=DE",
sha256Hash: "b2fae53e14ccd7ab9212064701ae279c1d8988facb775fa8a008914e663988a8",
certStartOff: 8125,
certStartOff: 7234,
certLength: 537,
},
{
cn: "CN=Atos TrustedRoot Root CA RSA TLS 2021,O=Atos,C=DE",
sha256Hash: "81a9088ea59fb364c548a6f85559099b6f0405efbf18e5324ec9f457ba00112f",
certStartOff: 8662,
certStartOff: 7771,
certLength: 1384,
},
{
cn: "CN=Autoridad de Certificacion Firmaprofesional CIF A62634068,C=ES",
sha256Hash: "57de0583efd2b26e0361da99da9df4648def7ee8441c3b728afa9bcde0f9b26a",
certStartOff: 10046,
certStartOff: 9155,
certLength: 1560,
},
{
cn: "CN=BJCA Global Root CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
sha256Hash: "f3896f88fe7c0a882766a7fa6ad2749fb57a7f3e98fb769c1fa7b09c2c44d5ae",
certStartOff: 11606,
certStartOff: 10715,
certLength: 1400,
},
{
cn: "CN=BJCA Global Root CA2,O=BEIJING CERTIFICATE AUTHORITY,C=CN",
sha256Hash: "574df6931e278039667b720afdc1600fc27eb66dd3092979fb73856487212882",
certStartOff: 13006,
certStartOff: 12115,
certLength: 553,
},
{
cn: "CN=Buypass Class 2 Root CA,O=Buypass AS-983163327,C=NO",
sha256Hash: "9a114025197c5bb95d94e63d55cd43790847b646b23cdf11ada4a00eff15fb48",
certStartOff: 13559,
certStartOff: 12668,
certLength: 1373,
},
{
cn: "CN=Buypass Class 3 Root CA,O=Buypass AS-983163327,C=NO",
sha256Hash: "edf7ebbca27a2a384d387b7d4010c666e2edb4843e4c29b4ae1d5b9332e6b24d",
certStartOff: 14932,
certStartOff: 14041,
certLength: 1373,
},
{
cn: "CN=CA Disig Root R2,O=Disig a.s.,L=Bratislava,C=SK",
sha256Hash: "e23d4a036d7b70e9f595b1422079d2b91edfbb1fb651a0633eaa8a9dc5f80703",
certStartOff: 16305,
certStartOff: 15414,
certLength: 1389,
},
{
cn: "CN=CFCA EV ROOT,O=China Financial Certification Authority,C=CN",
sha256Hash: "5cc3d78e4e1d5e45547a04e6873e64f90cf9536d1ccc2ef800f355c4c5fd70fd",
certStartOff: 17694,
certStartOff: 16803,
certLength: 1425,
},
{
cn: "CN=COMODO ECC Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
sha256Hash: "1793927a0614549789adce2f8f34f7f0b66d0f3ae3a3b84d21ec15dbba4fadc7",
certStartOff: 19119,
certStartOff: 18228,
certLength: 653,
},
{
cn: "CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB",
sha256Hash: "52f0e1c4e58ec629291b60317f074671b85d7ea80d5b07273463534b32b40234",
certStartOff: 19772,
certStartOff: 18881,
certLength: 1500,
},
{
cn: "CN=Certainly Root E1,O=Certainly,C=US",
sha256Hash: "b4585f22e4ac756a4e8612a1361c5d9d031a93fd84febb778fa3068b0fc42dc2",
certStartOff: 21272,
certStartOff: 20381,
certLength: 507,
},
{
cn: "CN=Certainly Root R1,O=Certainly,C=US",
sha256Hash: "77b82cd8644c4305f7acc5cb156b45675004033d51c60c6202a8e0c33467d3a0",
certStartOff: 21779,
certStartOff: 20888,
certLength: 1355,
},
{
cn: "CN=Certigna Root CA,OU=0002 48146308100036,O=Dhimyotis,C=FR",
sha256Hash: "d48d3d23eedb50a459e55197601c27774b9d7b18c94d5a059511a10250b93168",
certStartOff: 23134,
certStartOff: 22243,
certLength: 1631,
},
{
cn: "CN=Certum EC-384 CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
sha256Hash: "6b328085625318aa50d173c98d8bda09d57e27413d114cf787a0f5d06c030cf6",
certStartOff: 24765,
certStartOff: 23874,
certLength: 617,
},
{
cn: "CN=Certum Trusted Network CA 2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
sha256Hash: "b676f2eddae8775cd36cb0f63cd1d4603961f49e6265ba013a2f0307b6d0b804",
certStartOff: 25382,
certStartOff: 24491,
certLength: 1494,
},
{
cn: "CN=Certum Trusted Network CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL",
sha256Hash: "5c58468d55f58e497e743982d2b50010b6d165374acf83a7d4a32db768c4408e",
certStartOff: 26876,
certStartOff: 25985,
certLength: 959,
},
{
cn: "CN=Certum Trusted Root CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL",
sha256Hash: "fe7696573855773e37a95e7ad4d9cc96c30157c15d31765ba9b15704e1ae78fd",
certStartOff: 27835,
certStartOff: 26944,
certLength: 1476,
},
{
cn: "CN=D-TRUST BR Root CA 1 2020,O=D-Trust GmbH,C=DE",
sha256Hash: "e59aaa816009c22bff5b25bad37df306f049797c1f81d85ab089e657bd8f0044",
certStartOff: 29311,
certStartOff: 28420,
certLength: 735,
},
{
cn: "CN=D-TRUST BR Root CA 2 2023,O=D-Trust GmbH,C=DE",
sha256Hash: "0552e6f83fdf65e8fa9670e666df28a4e21340b510cbe52566f97c4fb94b2bd1",
certStartOff: 30046,
certStartOff: 29155,
certLength: 1453,
},
{
cn: "CN=D-TRUST EV Root CA 1 2020,O=D-Trust GmbH,C=DE",
sha256Hash: "08170d1aa36453901a2f959245e347db0c8d37abaabc56b81aa100dc958970db",
certStartOff: 31499,
certStartOff: 30608,
certLength: 735,
},
{
cn: "CN=D-TRUST EV Root CA 2 2023,O=D-Trust GmbH,C=DE",
sha256Hash: "8e8221b2e7d4007836a1672f0dcc299c33bc07d316f132fa1a206d587150f1ce",
certStartOff: 32234,
certStartOff: 31343,
certLength: 1453,
},
{
cn: "CN=D-TRUST Root Class 3 CA 2 2009,O=D-Trust GmbH,C=DE",
sha256Hash: "49e7a442acf0ea6287050054b52564b650e4f49e42e348d6aa38e039e957b1c1",
certStartOff: 33687,
certStartOff: 32796,
certLength: 1079,
},
{
cn: "CN=D-TRUST Root Class 3 CA 2 EV 2009,O=D-Trust GmbH,C=DE",
sha256Hash: "eec5496b988ce98625b934092eec2908bed0b0f316c2d4730c84eaf1f3d34881",
certStartOff: 34766,
certStartOff: 33875,
certLength: 1095,
},
{
cn: "CN=DigiCert Assured ID Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
sha256Hash: "7d05ebb682339f8c9451ee094eebfefa7953a114edb2f44949452fab7d2fc185",
certStartOff: 35861,
certStartOff: 34970,
certLength: 922,
},
{
cn: "CN=DigiCert Assured ID Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
sha256Hash: "7e37cb8b4c47090cab36551ba6f45db840680fba166a952db100717f43053fc2",
certStartOff: 36783,
certStartOff: 35892,
certLength: 586,
},
{
cn: "CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US",
sha256Hash: "cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f",
certStartOff: 37369,
certStartOff: 36478,
certLength: 914,
},
{
cn: "CN=DigiCert Global Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US",
sha256Hash: "31ad6648f8104138c738f39ea4320133393e3a18cc02296ef97c2ac9ef6731d0",
certStartOff: 38283,
certStartOff: 37392,
certLength: 579,
},
{
cn: "CN=DigiCert TLS ECC P384 Root G5,O=DigiCert\\, Inc.,C=US",
sha256Hash: "018e13f0772532cf809bd1b17281867283fc48c6e13be9c69812854a490c1b05",
certStartOff: 38862,
certStartOff: 37971,
certLength: 541,
},
{
cn: "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US",
sha256Hash: "371a00dc0533b3721a7eeb40e8419e70799d2b0a0f2c1d80693165f7cec4ad75",
certStartOff: 39403,
certStartOff: 38512,
certLength: 1386,
},
{
cn: "CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US",
sha256Hash: "552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988",
certStartOff: 40789,
certStartOff: 39898,
certLength: 1428,
},
{
cn: "CN=Entrust Root Certification Authority,OU=www.entrust.net/CPS is incorporated by reference+OU=(c) 2006 Entrust\\, Inc.,O=Entrust\\, Inc.,C=US",
sha256Hash: "73c176434f1bc6d5adf45b0e76e727287c8de57616c1e6e6141a2b2cbc7d8e4c",
certStartOff: 42217,
certLength: 1173,
distrustAfter: "2024-11-30T23:59:59Z",
},
{
cn: "CN=GDCA TrustAUTH R5 ROOT,O=GUANG DONG CERTIFICATE AUTHORITY CO.\\,LTD.,C=CN",
sha256Hash: "bfff8fd04433487d6a8aa60c1a29767a9fc2bbb05e420f713a13b992891d3893",
certStartOff: 43390,
certStartOff: 41326,
certLength: 1420,
},
{
cn: "CN=GTS Root R1,O=Google Trust Services LLC,C=US",
sha256Hash: "d947432abde7b7fa90fc2e6b59101b1280e0e1c7e4e40fa3c6887fff57a7f4cf",
certStartOff: 44810,
certStartOff: 42746,
certLength: 1371,
},
{
cn: "CN=GTS Root R3,O=Google Trust Services LLC,C=US",
sha256Hash: "34d8a73ee208d9bcdb0d956520934b4e40e69482596e8b6f73c8426b010a6f48",
certStartOff: 46181,
certStartOff: 44117,
certLength: 525,
},
{
cn: "CN=GTS Root R4,O=Google Trust Services LLC,C=US",
sha256Hash: "349dfa4058c5e263123b398ae795573c4e1313c83fe68f93556cd5e8031b3c7d",
certStartOff: 46706,
certStartOff: 44642,
certLength: 525,
},
{
cn: "CN=GlobalSign Root E46,O=GlobalSign nv-sa,C=BE",
sha256Hash: "cbb9c44d84b8043e1050ea31a69f514955d7bfd2e2c6b49301019ad61d9f5058",
certStartOff: 47231,
certStartOff: 45167,
certLength: 527,
},
{
cn: "CN=GlobalSign Root R46,O=GlobalSign nv-sa,C=BE",
sha256Hash: "4fa3126d8d3a11d1c4855a4f807cbad6cf919d3a5a88b03bea2c6372d93c40c9",
certStartOff: 47758,
certStartOff: 45694,
certLength: 1374,
},
{
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R4,O=GlobalSign",
sha256Hash: "b085d70b964f191a73e4af0d54ae7a0e07aafdaf9b71dd0862138ab7325a24a2",
certStartOff: 49132,
certStartOff: 47068,
certLength: 480,
},
{
cn: "CN=GlobalSign,OU=GlobalSign ECC Root CA - R5,O=GlobalSign",
sha256Hash: "179fbc148a3dd00fd24ea13458cc43bfa7f59c8182d783a513f6ebec100c8924",
certStartOff: 49612,
certStartOff: 47548,
certLength: 546,
},
{
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R3,O=GlobalSign",
sha256Hash: "cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b",
certStartOff: 50158,
certStartOff: 48094,
certLength: 867,
},
{
cn: "CN=GlobalSign,OU=GlobalSign Root CA - R6,O=GlobalSign",
sha256Hash: "2cabeafe37d06ca22aba7391c0033d25982952c453647349763a3ab5ad6ccf69",
certStartOff: 51025,
certStartOff: 48961,
certLength: 1415,
},
{
cn: "CN=Go Daddy Root Certificate Authority - G2,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
sha256Hash: "45140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda",
certStartOff: 52440,
certStartOff: 50376,
certLength: 969,
},
{
cn: "CN=HARICA TLS ECC Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
sha256Hash: "3f99cc474acfce4dfed58794665e478d1547739f2e780f1bb4ca9b133097d401",
certStartOff: 53409,
certStartOff: 51345,
certLength: 600,
},
{
cn: "CN=HARICA TLS RSA Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR",
sha256Hash: "d95d0e8eda79525bf9beb11b14d2100d3294985f0c62d9fabd9cd999eccb7b1d",
certStartOff: 54009,
certStartOff: 51945,
certLength: 1448,
},
{
cn: "CN=Hellenic Academic and Research Institutions ECC RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
sha256Hash: "44b545aa8a25e65a73ca15dc27fc36d24c1cb9953a066539b11582dc487b4833",
certStartOff: 55457,
certStartOff: 53393,
certLength: 711,
},
{
cn: "CN=Hellenic Academic and Research Institutions RootCA 2015,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR",
sha256Hash: "a040929a02ce53b4acf4f2ffc6981ce4496f755e6d45fe0b2a692bcd52523f36",
certStartOff: 56168,
certStartOff: 54104,
certLength: 1551,
},
{
cn: "CN=HiPKI Root CA - G1,O=Chunghwa Telecom Co.\\, Ltd.,C=TW",
sha256Hash: "f015ce3cc239bfef064be9f1d2c417e1a0264a0a94be1f0c8d121864eb6949cc",
certStartOff: 57719,
certStartOff: 55655,
certLength: 1390,
},
{
cn: "CN=Hongkong Post Root CA 3,O=Hongkong Post,L=Hong Kong,ST=Hong Kong,C=HK",
sha256Hash: "5a2fc03f0c83b090bbfa40604b0988446c7636183df9846e17101a447fb8efd6",
certStartOff: 59109,
certStartOff: 57045,
certLength: 1491,
},
{
cn: "CN=ISRG Root X1,O=Internet Security Research Group,C=US",
sha256Hash: "96bcec06264976f37460779acf28c5a7cfe8a3c0aae11a8ffcee05c0bddf08c6",
certStartOff: 60600,
certStartOff: 58536,
certLength: 1391,
},
{
cn: "CN=ISRG Root X2,O=Internet Security Research Group,C=US",
sha256Hash: "69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470",
certStartOff: 61991,
certStartOff: 59927,
certLength: 543,
},
{
cn: "CN=IdenTrust Commercial Root CA 1,O=IdenTrust,C=US",
sha256Hash: "5d56499be4d2e08bcfcad08a3e38723d50503bde706948e42f55603019e528ae",
certStartOff: 62534,
certStartOff: 60470,
certLength: 1380,
},
{
cn: "CN=IdenTrust Public Sector Root CA 1,O=IdenTrust,C=US",
sha256Hash: "30d0895a9a448a262091635522d1f52010b5867acae12c78ef958fd4f4389f2f",
certStartOff: 63914,
certStartOff: 61850,
certLength: 1386,
},
{
cn: "CN=Izenpe.com,O=IZENPE S.A.,C=ES",
sha256Hash: "2530cc8e98321502bad96f9b1fba1b099e2d299e0f4548bb914f363bc0d4531f",
certStartOff: 65300,
certLength: 1525,
cn: "CN=Izenpe.com,O=IZENPE S.A.,C=ES",
sha256Hash: "2530cc8e98321502bad96f9b1fba1b099e2d299e0f4548bb914f363bc0d4531f",
certStartOff: 63236,
certLength: 1525,
distrustAfter: "2026-04-15T23:59:59Z",
},
{
cn: "CN=Microsec e-Szigno Root CA 2009,O=Microsec Ltd.,L=Budapest,C=HU,1.2.840.113549.1.9.1=#0c10696e666f40652d737a69676e6f2e6875",
cn: "CN=Microsec e-Szigno Root CA 2009,O=Microsec Ltd.,L=Budapest,C=HU,1.2.840.113549.1.9.1=info@e-szigno.hu",
sha256Hash: "3c5f81fea5fab82c64bfa2eaecafcde8e077fc8620a7cae537163df36edbf378",
certStartOff: 66825,
certStartOff: 64761,
certLength: 1038,
},
{
cn: "CN=Microsoft ECC Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
sha256Hash: "358df39d764af9e1b766e9c972df352ee15cfac227af6ad1d70e8e4a6edcba02",
certStartOff: 67863,
certStartOff: 65799,
certLength: 605,
},
{
cn: "CN=Microsoft RSA Root Certificate Authority 2017,O=Microsoft Corporation,C=US",
sha256Hash: "c741f70f4b2a8d88bf2e71c14122ef53ef10eba0cfa5e64cfa20f418853073e0",
certStartOff: 68468,
certStartOff: 66404,
certLength: 1452,
},
{
cn: "CN=NAVER Global Root Certification Authority,O=NAVER BUSINESS PLATFORM Corp.,C=KR",
sha256Hash: "88f438dcf8ffd1fa8f429115ffe5f82ae1e06e0c70c375faad717b34a49e7265",
certStartOff: 69920,
certStartOff: 67856,
certLength: 1446,
},
{
cn: "CN=NetLock Arany (Class Gold) Főtanúsítvány,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU",
sha256Hash: "6c61dac3a2def031506be036d2a6fe401994fbd13df9c8d466599274c446ec98",
certStartOff: 71366,
certStartOff: 69302,
certLength: 1049,
},
{
cn: "CN=OISTE Server Root ECC G1,O=OISTE Foundation,C=CH",
sha256Hash: "eec997c0c30f216f7e3b8b307d2bae42412d753fc8219dafd1520b2572850f49",
certStartOff: 72415,
certStartOff: 70351,
certLength: 569,
},
{
cn: "CN=OISTE Server Root RSA G1,O=OISTE Foundation,C=CH",
sha256Hash: "9ae36232a5189ffddb353dfd26520c015395d22777dac59db57b98c089a651e6",
certStartOff: 72984,
certStartOff: 70920,
certLength: 1415,
},
{
cn: "CN=OISTE WISeKey Global Root GB CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
sha256Hash: "6b9c08e86eb0f767cfad65cd98b62149e5494a67f5845e7bd1ed019f27b86bd6",
certStartOff: 74399,
certStartOff: 72335,
certLength: 953,
},
{
cn: "CN=OISTE WISeKey Global Root GC CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH",
sha256Hash: "8560f91c3624daba9570b5fea0dbe36ff11a8323be9486854fb3f34a5571198d",
certStartOff: 75352,
certStartOff: 73288,
certLength: 621,
},
{
cn: "CN=QuoVadis Root CA 1 G3,O=QuoVadis Limited,C=BM",
sha256Hash: "8a866fd1b276b57e578e921c65828a2bed58e9f2f288054134b7f1f4bfc9cc74",
certStartOff: 75973,
certStartOff: 73909,
certLength: 1380,
},
{
cn: "CN=QuoVadis Root CA 2 G3,O=QuoVadis Limited,C=BM",
sha256Hash: "8fe4fb0af93a4d0d67db0bebb23e37c71bf325dcbcdd240ea04daf58b47e1840",
certStartOff: 77353,
certStartOff: 75289,
certLength: 1380,
},
{
cn: "CN=QuoVadis Root CA 3 G3,O=QuoVadis Limited,C=BM",
sha256Hash: "88ef81de202eb018452e43f864725cea5fbd1fc2d9d205730709c5d8b8690f46",
certStartOff: 78733,
certStartOff: 76669,
certLength: 1380,
},
{
cn: "CN=SECOM TLS ECC Root CA 2024,O=SECOM Trust Systems Co.\\, Ltd.,C=JP",
sha256Hash: "6ab2ab75f51cb4f4f0156203fbf6f646232f514be059f62833308b82b4d72db1",
certStartOff: 78049,
certLength: 592,
},
{
cn: "CN=SECOM TLS RSA Root CA 2024,O=SECOM Trust Systems Co.\\, Ltd.,C=JP",
sha256Hash: "1435f225c5d252d7a21948cc3ce62aecfa88001e3dd72d1cc3555100eb372f93",
certStartOff: 78641,
certLength: 1438,
},
{
cn: "CN=SSL.com EV Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
sha256Hash: "22a2c1f7bded704cc1e701b5f408c310880fe956b5de2a4a44f99c873a25a7c8",
certStartOff: 80113,
certStartOff: 80079,
certLength: 664,
},
{
cn: "CN=SSL.com EV Root Certification Authority RSA R2,O=SSL Corporation,L=Houston,ST=Texas,C=US",
sha256Hash: "2e7bf16cc22485a7bbe2aa8696750761b0ae39be3b2fe9d0cc6d4ef73491425c",
certStartOff: 80777,
certStartOff: 80743,
certLength: 1519,
},
{
cn: "CN=SSL.com Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US",
sha256Hash: "3417bb06cc6007da1b961c920b8ab4ce3fad820e4aa30b9acbc4a74ebdcebc65",
certStartOff: 82296,
certStartOff: 82262,
certLength: 657,
},
{
cn: "CN=SSL.com Root Certification Authority RSA,O=SSL Corporation,L=Houston,ST=Texas,C=US",
sha256Hash: "85666a562ee0be5ce925c1d8890a6f76a87ec16d4d7d5f29ea7419cf20123b69",
certStartOff: 82953,
certStartOff: 82919,
certLength: 1505,
},
{
cn: "CN=SSL.com TLS ECC Root CA 2022,O=SSL Corporation,C=US",
sha256Hash: "c32ffd9f46f936d16c3673990959434b9ad60aafbb9e7cf33654f144cc1ba143",
certStartOff: 84458,
certStartOff: 84424,
certLength: 574,
},
{
cn: "CN=SSL.com TLS RSA Root CA 2022,O=SSL Corporation,C=US",
sha256Hash: "8faf7d2e2cb4709bb8e0b33666bf75a5dd45b5de480f8ea8d4bfe6bebc17f2ed",
certStartOff: 85032,
certStartOff: 84998,
certLength: 1421,
},
{
cn: "CN=SZAFIR ROOT CA2,O=Krajowa Izba Rozliczeniowa S.A.,C=PL",
sha256Hash: "a1339d33281a0b56e557d3d32b1ce7f9367eb094bd5fa72a7e5004c8ded7cafe",
certStartOff: 86453,
certStartOff: 86419,
certLength: 886,
},
{
cn: "CN=Sectigo Public Server Authentication Root E46,O=Sectigo Limited,C=GB",
sha256Hash: "c90f26f0fb1b4018b22227519b5ca2b53e2ca5b3be5cf18efe1bef47380c5383",
certStartOff: 87339,
certStartOff: 87305,
certLength: 574,
},
{
cn: "CN=Sectigo Public Server Authentication Root R46,O=Sectigo Limited,C=GB",
sha256Hash: "7bb647a62aeeac88bf257aa522d01ffea395e0ab45c73f93f65654ec38f25a06",
certStartOff: 87913,
certStartOff: 87879,
certLength: 1422,
},
{
cn: "CN=SecureSign Root CA12,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
sha256Hash: "3f034bb5704d44b2d08545a02057de93ebf3905fce721acbc730c06ddaee904e",
certStartOff: 89335,
certLength: 886,
},
{
cn: "CN=SecureSign Root CA14,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
sha256Hash: "4b009c1034494f9ab56bba3ba1d62731fc4d20d8955adcec10a925607261e338",
certStartOff: 90221,
certStartOff: 89301,
certLength: 1398,
},
{
cn: "CN=SecureSign Root CA15,O=Cybertrust Japan Co.\\, Ltd.,C=JP",
sha256Hash: "e778f0f095fe843729cd1a0082179e5314a9c291442805e1fb1d8fb6b8886c3a",
certStartOff: 91619,
certStartOff: 90699,
certLength: 551,
},
{
cn: "CN=Security Communication ECC RootCA1,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
sha256Hash: "e74fbda55bd564c473a36b441aa799c8a68e077440e8288b9fa1e50e4bbaca11",
certStartOff: 92170,
certStartOff: 91250,
certLength: 572,
},
{
cn: "CN=Starfield Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
sha256Hash: "2ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f5",
certStartOff: 92742,
certStartOff: 91822,
certLength: 993,
},
{
cn: "CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US",
sha256Hash: "568d6905a2c88708a4b3025190edcfedb1974a606a13c6e5290fcb2ae63edab5",
certStartOff: 93735,
certStartOff: 92815,
certLength: 1011,
},
{
cn: "CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH",
sha256Hash: "193144f431e0fddb740717d4de926a571133884b4360d30e272913cbe660ce41",
certStartOff: 94746,
certStartOff: 93826,
certLength: 1431,
},
{
cn: "CN=T-TeleSec GlobalRoot Class 2,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
sha256Hash: "91e2f5788d5810eba7ba58737de1548a8ecacd014598bc0b143e041b17052552",
certStartOff: 96177,
certStartOff: 95257,
certLength: 967,
},
{
cn: "CN=T-TeleSec GlobalRoot Class 3,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE",
sha256Hash: "fd73dad31c644ff1b43bef0ccdda96710b9cd9875eca7e31707af3e96d522bbd",
certStartOff: 97144,
certStartOff: 96224,
certLength: 967,
},
{
cn: "CN=TWCA CYBER Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
sha256Hash: "3f63bb2814be174ec8b6439cf08d6d56f0b7c405883a5648a334424d6b3ec558",
certStartOff: 98111,
certStartOff: 97191,
certLength: 1425,
},
{
cn: "CN=TWCA Global Root CA,OU=Root CA,O=TAIWAN-CA,C=TW",
sha256Hash: "59769007f7685d0fcd50872f9f95d5755a5b2b457d81f3692b610a98672f0e1b",
certStartOff: 99536,
certStartOff: 98616,
certLength: 1349,
},
{
cn: "CN=TWCA Root Certification Authority,OU=Root CA,O=TAIWAN-CA,C=TW",
sha256Hash: "bfd88fe1101c41ae3e801bf8be56350ee9bad1a6b9bd515edc5c6d5b8711ac44",
certStartOff: 100885,
certStartOff: 99965,
certLength: 895,
},
{
cn: "CN=Telekom Security TLS ECC Root 2020,O=Deutsche Telekom Security GmbH,C=DE",
sha256Hash: "578af4ded0853f4e5998db4aeaf9cbea8d945f60b620a38d1a3c13b2bc7ba8e1",
certStartOff: 101780,
certStartOff: 100860,
certLength: 582,
},
{
cn: "CN=Telekom Security TLS RSA Root 2023,O=Deutsche Telekom Security GmbH,C=DE",
sha256Hash: "efc65cadbb59adb6efe84da22311b35624b71b3b1ea0da8b6655174ec8978646",
certStartOff: 102362,
certStartOff: 101442,
certLength: 1463,
},
{
cn: "CN=Telia EC TLS Root CA v3,O=Telia Company AB,C=SE",
sha256Hash: "098e08a91dbbf77478b96cceb89b1413a5da37b7c862606a955deb07179f4326",
certStartOff: 102905,
certLength: 566,
},
{
cn: "CN=Telia RSA TLS Root CA v3,O=Telia Company AB,C=SE",
sha256Hash: "d13db1294c45ebc6fc86c6bbf69fa29bdfe692dff7c713c243c7a956c6a2284c",
certStartOff: 103471,
certLength: 1414,
},
{
cn: "CN=Telia Root CA v2,O=Telia Finland Oyj,C=FI",
sha256Hash: "242b69742fcb1e5b2abf98898b94572187544e5b4d9911786573621f6a74b82c",
certStartOff: 103825,
certStartOff: 104885,
certLength: 1400,
},
{
cn: "CN=TrustAsia Global Root CA G3,O=TrustAsia Technologies\\, Inc.,C=CN",
sha256Hash: "e0d3226aeb1163c2e48ff9be3b50b4c6431be7bb1eacc5c36b5d5ec509039a08",
certStartOff: 105225,
certStartOff: 106285,
certLength: 1449,
},
{
cn: "CN=TrustAsia Global Root CA G4,O=TrustAsia Technologies\\, Inc.,C=CN",
sha256Hash: "be4b56cb5056c0136a526df444508daa36a0b54f42e4ac38f72af470e479654c",
certStartOff: 106674,
certStartOff: 107734,
certLength: 601,
},
{
cn: "CN=TrustAsia TLS ECC Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
sha256Hash: "c0076b9ef0531fb1a656d67c4ebe97cd5dbaa41ef44598acc2489878c92d8711",
certStartOff: 107275,
certStartOff: 108335,
certLength: 565,
},
{
cn: "CN=TrustAsia TLS RSA Root CA,O=TrustAsia Technologies\\, Inc.,C=CN",
sha256Hash: "06c08d7dafd876971eb1124fe67f847ec0c7a158d3ea53cbe940e2ea9791f4c3",
certStartOff: 107840,
certStartOff: 108900,
certLength: 1412,
},
{
cn: "CN=TunTrust Root CA,O=Agence Nationale de Certification Electronique,C=TN",
sha256Hash: "2e44102ab58cb85419451c8e19d9acf3662cafbc614b6a53960a30f7d0e2eb41",
certStartOff: 109252,
certStartOff: 110312,
certLength: 1463,
},
{
cn: "CN=UCA Extended Validation Root,O=UniTrust,C=CN",
sha256Hash: "d43af9b35473755c9684fc06d7d8cb70ee5c28e773fb294eb41ee71722924d24",
certStartOff: 110715,
certStartOff: 111775,
certLength: 1374,
},
{
cn: "CN=UCA Global G2 Root,O=UniTrust,C=CN",
sha256Hash: "9bea11c976fe014764c1be56a6f914b5a560317abd9988393382e5161aa0493c",
certStartOff: 112089,
certStartOff: 113149,
certLength: 1354,
},
{
cn: "CN=USERTrust ECC Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
sha256Hash: "4ff460d54b9c86dabfbcfc5712e0400d2bed3fbc4d4fbdaa86e06adcd2a9ad7a",
certStartOff: 113443,
certStartOff: 114503,
certLength: 659,
},
{
cn: "CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US",
sha256Hash: "e793c9b02fd8aa13e21c31228accb08119643b749c898964b1746d46c3d4cbd2",
certStartOff: 114102,
certStartOff: 115162,
certLength: 1506,
},
{
cn: "CN=e-Szigno Root CA 2017,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=#130e56415448552d3233353834343937",
cn: "CN=e-Szigno Root CA 2017,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=VATHU-23584497",
sha256Hash: "beb00b30839b9bc32c32e4447905950641f26421b15ed089198b518ae2ea1b99",
certStartOff: 115608,
certStartOff: 116668,
certLength: 580,
},
{
cn: "CN=e-Szigno TLS Root CA 2023,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=#130e56415448552d3233353834343937",
cn: "CN=e-Szigno TLS Root CA 2023,O=Microsec Ltd.,L=Budapest,C=HU,2.5.4.97=VATHU-23584497",
sha256Hash: "b49141502d00663d740f2e7ec340c52800962666121a36d09cf7dd2b90384fb4",
certStartOff: 116188,
certStartOff: 117248,
certLength: 723,
},
{
cn: "CN=emSign ECC Root CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US",
sha256Hash: "bc4d809b15189d78db3e1d8cf4f9726a795da1643ca5f1358e1ddb0edc0d7eb3",
certStartOff: 116911,
certStartOff: 117971,
certLength: 559,
},
{
cn: "CN=emSign ECC Root CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
sha256Hash: "86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b",
certStartOff: 117470,
certStartOff: 118530,
certLength: 594,
},
{
cn: "CN=emSign Root CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US",
sha256Hash: "125609aa301da0a249b97a8239cb6a34216f44dcac9f3954b14292f2e8c8608f",
certStartOff: 118064,
certStartOff: 119124,
certLength: 887,
},
{
cn: "CN=emSign Root CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN",
sha256Hash: "40f6af0346a99aa1cd1d555a4e9cce62c7f9634603ee406615833dc8c8d00367",
certStartOff: 118951,
certStartOff: 120011,
certLength: 920,
},
{
cn: "CN=vTrus ECC Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
sha256Hash: "30fbba2c32238e2a98547af97931e550428b9b3f1c8eeb6633dcfa86c5b27dd3",
certStartOff: 119871,
certStartOff: 120931,
certLength: 531,
},
{
cn: "CN=vTrus Root CA,O=iTrusChina Co.\\,Ltd.,C=CN",
sha256Hash: "8a71de6559336f426c26e53880d00d88a18da4c6a91f0dcb6194e206c5c96387",
certStartOff: 120402,
certStartOff: 121462,
certLength: 1370,
},
{
cn: "OU=AC RAIZ FNMT-RCM,O=FNMT-RCM,C=ES",
sha256Hash: "ebc5570c29018c4d67b1aa127baf12f703b4611ebc17b7dab5573894179b93fa",
certStartOff: 121772,
certStartOff: 122832,
certLength: 1415,
},
{
cn: "OU=Security Communication RootCA2,O=SECOM Trust Systems CO.\\,LTD.,C=JP",
sha256Hash: "513b2cecb810d4cde5dd85391adfc6c2dd60d87bb736d2b521484aa47a0ebef6",
certStartOff: 123187,
certStartOff: 124247,
certLength: 891,
},
{
cn: "OU=certSIGN ROOT CA G2,O=CERTSIGN SA,C=RO",
sha256Hash: "657cfe2fa73faa38462571f332a2363a46fce7020951710702cdfbb6eeda3305",
certStartOff: 124078,
certStartOff: 125138,
certLength: 1355,
},
{
cn: "OU=ePKI Root Certification Authority,O=Chunghwa Telecom Co.\\, Ltd.,C=TW",
sha256Hash: "c0a6f4dc63a24bfdcf54ef2a6a082a0a72de35803e2ff5ff527ae5d87206dfd5",
certStartOff: 125433,
certLength: 1460,
distrustAfter: "2025-04-15T23:59:59Z",
},
{
cn: "SERIALNUMBER=G63287510,CN=ANF Secure Server Root CA,OU=ANF CA Raiz,O=ANF Autoridad de Certificacion,C=ES",
sha256Hash: "fb8fec759169b9106b1e511644c618c51304373f6c0643088d8beffd1b997599",
certStartOff: 126893,
certStartOff: 126493,
certLength: 1523,
},
}
@@ -0,0 +1,47 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.27
package bundle
import (
"encoding/asn1"
"encoding/hex"
"regexp"
)
// subjectsEqual reports whether two RFC 2253 DN strings match.
//
// When testing with Go 1.27 or newer, it requires subjects to match exactly.
// When testing with Go 1.26, it tolerates the rendering difference introduced in Go 1.27,
// where string-typed attribute values for OIDs outside attributeTypeNames are rendered
// as strings rather than hex-encoded DER (see Go CL 773800).
//
// When Go 1.26 is no longer supported,
// the Go 1.26 version can be removed and
// the Go 1.27+ version can be inlined.
func subjectsEqual(a, b string) bool {
return a == b || normalizeHexValues(a) == normalizeHexValues(b)
}
// normalizeHexValues rewrites any "oid=#hex" to the equivalent "oid=value"
// rendering produced by Go 1.27+.
func normalizeHexValues(s string) string {
return hexAttrRE.ReplaceAllStringFunc(s, func(match string) string {
m := hexAttrRE.FindStringSubmatch(match)
der, err := hex.DecodeString(m[2])
if err != nil {
return match
}
var v string
if rest, err := asn1.Unmarshal(der, &v); err != nil || len(rest) != 0 {
return match
}
return m[1] + "=" + v
})
}
// hexAttrRE matches a "oid=#hex" attribute value in an RFC 2253 DN string.
var hexAttrRE = regexp.MustCompile(`([\d.]+)=#([[:xdigit:]]+)`)
@@ -0,0 +1,21 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.27
package bundle
// subjectsEqual reports whether two RFC 2253 DN strings match.
//
// When testing with Go 1.27 or newer, it requires subjects to match exactly.
// When testing with Go 1.26, it tolerates the rendering difference introduced in Go 1.27,
// where string-typed attribute values for OIDs outside attributeTypeNames are rendered
// as strings rather than hex-encoded DER (see Go CL 773800).
//
// When Go 1.26 is no longer supported,
// the Go 1.26 version can be removed and
// the Go 1.27+ version can be inlined.
func subjectsEqual(a, b string) bool {
return a == b
}
-33
View File
@@ -7,9 +7,7 @@ package bundle
import (
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/hex"
"regexp"
"testing"
)
@@ -32,34 +30,3 @@ func TestBundle(t *testing.T) {
}
}
}
// subjectsEqual reports whether two RFC 2253 DN strings match.
//
// It tolerates the rendering difference introduced in Go 1.27, where
// string-typed attribute values for OIDs outside attributeTypeNames are
// rendered as strings rather than hex-encoded DER (see Go CL 773800).
//
// This can be removed when Go 1.25/1.26 are no longer supported.
func subjectsEqual(a, b string) bool {
return a == b || normalizeHexValues(a) == normalizeHexValues(b)
}
// normalizeHexValues rewrites any "oid=#hex" to the equivalent "oid=value"
// rendering produced by Go 1.27+.
func normalizeHexValues(s string) string {
return hexAttrRE.ReplaceAllStringFunc(s, func(match string) string {
m := hexAttrRE.FindStringSubmatch(match)
der, err := hex.DecodeString(m[2])
if err != nil {
return match
}
var v string
if rest, err := asn1.Unmarshal(der, &v); err != nil || len(rest) != 0 {
return match
}
return m[1] + "=" + v
})
}
// hexAttrRE matches a "oid=#hex" attribute value in an RFC 2253 DN string.
var hexAttrRE = regexp.MustCompile(`([\d.]+)=#([[:xdigit:]]+)`)
+1 -1
View File
@@ -1,3 +1,3 @@
module golang.org/x/crypto/x509roots/fallback
go 1.25.0
go 1.26.0