Compare commits

..

119 Commits

Author SHA1 Message Date
Jakub Jelen
1b3c061aae Reproducer for memory leak from parsing knonw hosts
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Norbert Pocs <norbertpocs0@gmail.com>
2026-02-03 18:01:36 +01:00
Jakub Jelen
1525ea3dda knownhosts: Avoid memory leaks on invalid entries
When `known_hosts` file contained matching valid entry followed by
invalid entry, the first record was already allocated in
`ssh_known_hosts_read_entries()`, but not freed on error.

This could cause possible memory leaks in client, but we do not
consider them as security relevant as the leaks do not add up and
successful exploitaition is hard or impossible.

Originally reported by Kang Yang.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Norbert Pocs <norbertpocs0@gmail.com>
2026-02-03 18:01:35 +01:00
Jakub Jelen
a189c2ef4d gssapi: Sanitize input parameters
Originally reported with this patch by Brian Carpenter from Deep Fork Cyber.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
2026-02-03 12:09:17 +01:00
Jakub Jelen
b2abcf8534 cmake: Propagate WITH_FINAL to abimap conditionally
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
2026-02-02 19:32:16 +01:00
Jakub Jelen
809f9b7729 Require abimap 0.4.0
The version 0.4.0 fixed the issues of multi-digit version numbers
which we hit with releaseing libssh ABI version 4_10 with last
release.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
2026-02-02 19:32:16 +01:00
Jakub Jelen
d297621c33 tests: Workaround softhsm-2.7.0 bug in hashed ECDSA
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
2026-02-02 19:32:16 +01:00
Jakub Jelen
d936b7e81d mlkem: Use fprintf instead of internal logging function
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
2026-02-02 19:32:16 +01:00
Shreyas Mahajan
971d44107e ci: Test against latest LibreSSL
Signed-off-by: Shreyas Mahajan <shreyasmahajan05@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-02 18:37:54 +01:00
Shreyas Mahajan
a1e49728ba crypto: Add support for Poly1305 from LibreSSL
Signed-off-by: Shreyas Mahajan <shreyasmahajan05@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-02 18:37:54 +01:00
Shreyas Mahajan
6c5459e7fc reformat libcrypto.c
Signed-off-by: Shreyas Mahajan <shreyasmahajan05@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-02 18:37:53 +01:00
Shreyas Mahajan
f47d1c797a ci: add CLI helper to run GitLab CI jobs locally
Signed-off-by: Shreyas Mahajan <shreyasmahajan05@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-02 18:37:00 +01:00
Madhav Vasisth
da27d23125 docs: document sftp_session public API type
Signed-off-by: Madhav Vasisth <mv2363@srmist.edu.in>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-02 18:35:35 +01:00
Jakub Jelen
34db488e4d Native ML-KEM768 implementation
for cryptographic backends that do not have support for ML-KEM (old
OpenSSL and Gcrypt; MbedTLS).

Based on the libcrux implementation used in OpenSSH, taken from this
revision:

https://github.com/openssh/openssh-portable/blob/6aba700/libcrux_mlkem768_sha3.h

But refactored to separate C and header file to support testing and
removed unused functions (to make compiler happy).

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
2026-01-15 12:48:06 +01:00
Jakub Jelen
9780fa2f01 tests: Apply verbosity also for the ssh_bind
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
2026-01-15 12:23:42 +01:00
Jakub Jelen
5a795ce47c Add missing check in ML-KEM implementation of gcrypt
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
2026-01-15 12:23:42 +01:00
Jakub Jelen
b33a90d20b tests: Provide minimal openssl configuration file
When we use empty configuration file, some stuff go south in c10s
and for example fips mode detection does not work anymore.

Providing minimal configuration file avoids the issues of loading
the provider too early, while keeping fips mode activation working
and tests happy.

It also configures the pkcs11-provider to assume the token provides
FIPS approved crypto so the tests can work.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
ef45b8ae8c options: Fix doc string
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
3c2b254206 config: Pass the right types to OPTIONS_RSA_MIN_SIZE
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
7dea005729 tests: Avoid needless skip in testcases
the whole unit is skipped in fips mode

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
ad8d0c1e03 ci: Use pkcs11-provider on c9s
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
cb0f7d963e tests: Remove trailing whitespace
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
c90b239230 tests: Skip agent forwarding test if we are too deep in filesystem
The maximal lenght of unix domain socket path is 108 characters. When
the build directory (and UID wrapper home directories) are too deep
in the filesystem, OpenSSH will fail to create the socket file,
which is failing this test.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
18ec01c980 tests: Authentication with Ed25519 pkcs11 key
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
a983142a07 tests: Set explicit kex algorithm
without explicitly setting the algorithms, they might be set by
some other configuration file, for example crypto policies pulled
from `/etc/libssh/libssh_server.config` during RPM build.

Log also the generated configuration file and change the other case
to use standard logging mechanism instead of fprintf.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
89d51ced0d tests: Log server messages to separate files
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
16771cc574 tests: Remove needless goto
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
247ebb4d7f tests: Remove unused variable
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
acd5dace66 tests: Print read bytes to debug failures
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
5545b8808b sntrup: Avoid linking issues in external_override tests
This linking worked only in CI and local builds, but not during
the build in RPM as it fails on missing symbols that were defined
only in the main library. This is solved as with the other digest
dependencies in external crypto by removing the intermediate
function. We are already linking the md_*.o objects.

The error was like this

sh: symbol lookup error: /path/libssh/libssh-0.12.0-build/libssh-0.12.0/redhat-linux-build/lib/libsntrup761_override.so: undefined symbol: crypto_hash_sha512

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Jakub Jelen
be5a900ed0 tests: Reformat torture_auth_pkcs11
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-15 12:22:10 +01:00
Andreas Schneider
40ba3c6c80 cmake: Download doxygen theme during build not configure run
Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-01-12 15:25:40 +01:00
Jakub Jelen
57225a7168 ci: Include internal docs in the docs coverage reports
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 16:10:19 +01:00
Jakub Jelen
02ae2ace35 cmake: Make the WITH_INTERNAL_DOC function do something
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 16:10:19 +01:00
Jakub Jelen
76c6ee9ccf Add ML-KEM implementation for gcrypt
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 15:46:33 +01:00
Jakub Jelen
9a3351934b gssapi: Fix typo
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 15:44:52 +01:00
Jakub Jelen
1f1309c915 pki: Improve documentation about pubkey import functions
Resolves: #253 and #254

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 15:44:52 +01:00
Jakub Jelen
a8ca282033 dh-gex: Initialize best_size to make the code mode straight-forward
Coverity thought that the best_nlines could underflow, but the best_size is
initialized to 0 before calling this function so its moot. Adjusting the code
to be better understandable to static analyzers by initializing the variable
inside of the function.

Thanks coverity!

CID 1548873

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 14:46:07 +01:00
Jakub Jelen
b61bb3f8ac connector: Avoid possible underflow ...
... if underlying functions read or write more than expected.

This should never happen, but static analysis tools are inventive.

Thanks coverity!

CID 1548868

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 14:46:07 +01:00
Jakub Jelen
c9abf5ebbb connect: Avoid calling close with negative argument
The `first` is intialized to -1 and if we reach this without setting this, we
needlessly call close(-1). It should be no-op, but better be safe.

Thanks coverity!

CID 1644001

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 14:46:07 +01:00
Jakub Jelen
48fdf4b80a gssapi: Avoid possible memory leak on error condition
Thanks coverity!

CID 1643999

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 14:46:07 +01:00
Jakub Jelen
f5eb3e532b gssapi: Check return value from ssh_gssapi_init()
Checking the session->gssapi is resulting in the very same results, but this
approach is more direct and makes static analysis tools more happy.

Thanks coverity!

CID 1644000

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-07 14:46:07 +01:00
anshul agrawal
3f0007895c Add Keyboard Interactive
Signed-off-by: anshul agrawal <anshulagrawal2902@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-01-06 22:56:44 +05:30
nikhil-nari
06186279a8 feat: Add interoperability tests for PuTTY
Signed-off-by: Nikhil V <nikhilgreyshines@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-01-05 17:28:15 +01:00
Jakub Jelen
c36bd2304a connect: Close possibly leaking socket
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 13:32:01 +01:00
Jakub Jelen
82db6a7ab3 tests: Test proxyjump configuration parsing
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 13:32:01 +01:00
Jakub Jelen
deffea5ad2 socket: Properly close the proxyjump FD when proxy connection fails
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 13:32:01 +01:00
Jakub Jelen
320844669a config: Allow setting username from configuration
... file, even if it was already set before. The options
level handles what was already set.

The proxyJump implementation sets the username from the proxyjump, which
is setting it to NULL, effectively writing the current username to the
new session, which was not possible to override due to the following check.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 13:32:01 +01:00
Pavol Žáčik
d0d45c8915 gssapi: free session->gssapi->user before assigning
To prevent memory leaks with multiple authentication attempts.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
65abae059e ci: Add bug links as reasoning why some tests are not run
Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
7c2574682c tests: test pubkey auth after gssapi-keyex with null host key
We want to make sure it suceeds because it could fail if
the client tries to send a hostbound public key authentication
request.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
d2bb1ba889 auth: do not prefer hostbound auth if there is no host key
If there is no host key (e.g., because we are doing
gssapi-keyex with "null" host key algorithm), it does not
make sense to use host bound authentication.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
9b4ee9c6d4 gssapi: enable gssapi-keyex in FIPS mode
All gssapi-keyex tests have to be disabled in Centos Stream 8
because the KEX is not allowed in FIPS. In Centos Stream 9,
only tests against OpenSSH have to be disabled because
OpenSSH only enables gssapi-keyex since Centos Stream 10.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
d3e80d9a19 tests: test fallback to regular key exchange from gssapi-keyex
If the parties cannot agree on a gssapi-keyex method.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
4d3da7819c bind: adjust hostkey error messages to be more precise
Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
b79a681ebb auth: check for strdup allocation failure in ssh_userauth_gssapi_keyex
Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
f7cad4245a tests: reenable wait in torture_gssapi_server_key_exchange_null
And setup a KDC server before pinging the server so we
can connect.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
11c4b29e20 packet_cb: adjust response to NEWKEYS w.r.t. GSSAPI
Do not try to verify mic if gssapi-keyex was not performed,
and fix a memory leak of the mic on error.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
e04d753ace gssapi: add null checks for session->gssapi before using it
These are not strictly necessary because we always check
that we performed GSSAPI KEX, but they won't hurt us.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
06eea93ded packet: complete GSSAPI packet filter
Reject all GSSAPI-related messages when compiled
without GSSAPI support.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
06edb2db5e options: replace SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS example
The ECDH-based GSS KEX methods are more modern.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
ced98d41cf doc: document support for gssapi-keyex and related KEX methods
Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
88c2ea6752 gssapi: Add support for ECDH GSSAPI KEX
In particular, gss-nistp256-sha256-* and
gss-curve25519-sha256-*.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
5fed1bc8be torture_packet: use SSH2_MSG_IGNORE type of test packet
With packet filtering now implemented for type 65,
the current test packet would be rejected, resulting
in failed tests.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
a30ba0091f libgcrypt: make bignum_dup usable with const_bignum
Both gcry_mpi_copy and gcry_mpi_set take a pointer to
const gcry_mpi, which const_bignum is not.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
ad23fe8c27 curve25519: Make ssh_curve25519_build_k public
This is necessary to reuse the function
in gss-curve25519-sha256-* KEX.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
3710b31d24 session: Refactor ssh_get_publickey_hash
Make it use the one-shot API of hash functions,
and remove the FIPS restriction for OpenSSL 3.5+
where we can fetch the MD5 implementation from
a non-FIPS provider to use for non-crypto purposes.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Pavol Žáčik
2c5bb17211 md: Implement one-shot md5
Which can be used for non-cryptographic purposes
even in FIPS mode.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Jakub Jelen
83ae6b3f0a gssapi: reformat parts
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Jakub Jelen
06cefe1d67 packet: Implement packet filter for non-implemented GSSAPI messages
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Jakub Jelen
043b1fb133 Move GSSAPI KEX messages to be numerically sorted
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
f1490170f3 tests: add test for gssapi server key exchange with null hostkey and no tgt
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
4ba0746135 fix: some possible memory leaks
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
e94fd6ccd1 tests: add config tests for SSH_OPTIONS_GSSAPI_KEY_EXCHANGE
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
83114b636f fix: move ssh_gssapi_check_client_config() from ssh_options_set to ssh_options_apply
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
5a99cf9c7f refactor: remove extra else if branch for disable_hostkeys
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
213556ce01 reformat: some nits
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
5d06ee459b refactor: remove issue link from .gitlab-ci.yml
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
96807b9313 tests: add valgrind suppressions
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
6d81ecddbe fix: replace pthread_exit in gssapi tests
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
d0e5cf78d0 fix: use strcmp instead of strncmp to avoid prefix match
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
a0707afc3e reformat: gssapi key exchange
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
06b61f75fa feat: implement packet filter for SSH2_MSG_KEXGSS_COMPLETE
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
f9d7cadf4b fix: create fopen wrapper and block default hostkey paths
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
c1aab9903f feat: add null hostkey for server
fix: skip gssapi tests in fips mode

fix: skip gssapi_key_exchange_null test on ubuntu and tumbleweed

fix: return early when rc != 0 to show error

tests: replace int asserts by ssh return code asserts

fix: add fatal error when hostkeys are not found and gssapi kex is not enabled

ci: add comment linking gssapi null kex bug in ubuntu and tumbleweed

fix: don't specify hostkeys in config instead of deleting files

tests: assert kex method was null

refactor: remove redundant include

refactor: better error message

fix: check null before accessing in gssapi.c

fix: allow setting no hostkeys
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
fd1c3e8878 feat: test null hostkey on ci
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
d730b40b91 feat: add SSH2_MSG_KEXGSS_HOSTKEY support to client and server
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
9044fcdb52 feat: add "gssapi-keyex" for server
feat: add negative auth client tests, and more key exchange server tests

feat: add function for checkinf if GSSAPI key exchange was performed
Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:13 +01:00
Gauravsingh Sisodia
bc5211d055 feat: add gssapi key exchange
feat: add generic functions for importing name and initializing ctx

feat: add suffix to gsskex algs dynamically

feat: move gssapi key exchange to another file

feat: add gssapi key exchange for server

refactor: remove unnecessary fields in gssapi struct

refactor: add some documentation and improve logging

fix: remove gss_dh callbacks

feat: add a check to see if GSSAPI is configured correctly

fix: memory leaks

feat: add client side "gssapi-keyex" auth

feat: add gssapi_key_exchange_algs for server

fix: some memory issues

feat: add gssapi kex options to config

feat: add check to see if GSSAPI key exchange was performed

feat: add more tests for gssapi key exchange

fix: add valgrind supp

Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:24:00 +01:00
Jakub Jelen
701a2155a7 tests: Improve test coverage of comparing certificates
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:21:43 +01:00
Jakub Jelen
38f3d158f6 pki: Fix comparing public key of certificate
When the first key object is a certificate object, this match will
fall through to the generic key comparison that is unable to handle
the ed25519 keys and fails.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:21:43 +01:00
Jakub Jelen
0d5a2652b4 pki: Avoild false positive matches when comparing certificates in mbedtls and gcrypt
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:21:43 +01:00
Jakub Jelen
5c496acef7 pkd: Run openssh client with SK keys
Fixes: #331

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:20:28 +01:00
Jakub Jelen
3e074a3fba tests: Use standard way of setting cmake variables
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-05 12:20:28 +01:00
Samir Benmendil
98a844ceb2 tidy(unittests): zero-init config string pointers
Signed-off-by: Samir Benmendil <me@rmz.io>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-02 21:44:45 +00:00
Samir Benmendil
ce45ba8c61 tests: suppress leaks from NSS modules
Signed-off-by: Samir Benmendil <me@rmz.io>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-02 14:34:11 +00:00
Samir Benmendil
62c85a59a9 ssh_client: Return non-zero on config parsing failure
Signed-off-by: Samir Benmendil <me@rmz.io>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-02 14:34:11 +00:00
Samir Benmendil
c4f1a70a89 connect: Support AddressFamily option
* allow parsing of AddressFamily in config and cli
  * supports options "any", "inet" and "inet6"
* introduce SSH_OPTIONS_ADDRESS_FAMILY

Signed-off-by: Samir Benmendil <me@rmz.io>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-02 14:34:11 +00:00
Jakub Jelen
f52be27114 connect: Improve logging around the connection code
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-02 14:34:11 +00:00
Jakub Jelen
228208af5e Happy new year 2026!
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2026-01-02 14:36:19 +01:00
Jakub Jelen
163373c9d9 tests: Reproducer for missing value to LogLevel
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-12-19 22:08:15 +01:00
Jakub Jelen
e82677a923 config: Fix error paths of configuration parsing
Thanks coverity, oss-fuzz and Ram-Z reporting this independently.

CID 1643770

https://oss-fuzz.com/issue/4969113899565056
https://oss-fuzz.com/issue/6448013813022720

Fixes up 1833ce86f9.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-12-19 22:08:15 +01:00
Nikhil V
79966eb924 fix : modify ssh_connector_free to accept NULL values
Signed-off-by: Nikhil V <nikhilgreyshines@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-12-18 13:44:05 +01:00
Nikhil V
4feb0dd79d Improve doxygen documentation
Signed-off-by: Nikhil V <nikhilgreyshines@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-12-18 13:44:05 +01:00
nikhil-nari
f8d943afda Improve doxygen docs
Signed-off-by: Nikhil V <nikhilgreyshines@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-12-18 13:44:05 +01:00
Pavol Žáčik
4bad7cc08f hybrid_mlkem: Convert ECDH shared secret to a fixed-size string
The shared secret is derived as bignum, and draft-ietf-sshm-mlkem-hybrid-kex
mandates that it is converted to a fixed-size byte array. Not doing this
would lead to incompatibilities with other implementations when the derived
shared secret happens to start with zero bytes.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-12-17 14:51:29 +01:00
Mike Frysinger
3526e02dee use standard O_NONBLOCK naming
Systems define O_NONBLOCK & O_NDELAY as the same thing.  POSIX however
only defines O_NONBLOCK.  Rename the current define to be portable.

Signed-off-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-12-12 18:18:02 +01:00
abdallah elhdad
ecea5b6052 Support new '-o' option parsing to client
Signed-off-by: abdallah elhdad <abdallahselhdad@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-12-12 18:15:42 +01:00
abdallah elhdad
1833ce86f9 refactor auth options handler
Signed-off-by: abdallah elhdad <abdallahselhdad@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-12-12 18:15:41 +01:00
abdallah elhdad
3938e5e850 set log level when debug option is increased
Signed-off-by: abdallah elhdad <abdallahselhdad@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-12-12 18:15:40 +01:00
Norbert Pocs
dd80a56029 libcrypto.c: Use openssl const algorithm names
Use the openssl constants algorithm names instead of string
representations. They should not change, but it's clearer to have it
this way.

Signed-off-by: Norbert Pocs <norbertpocs0@gmail.com>
Signed-off-by: Norbert Pocs <norbertp@openssl.org>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-12-12 18:12:13 +01:00
Jakub Jelen
9d6df9d0fa ssh_known_hosts_get_algorithms: Simplify cleanup ...
...  and prevent memory leak of host_port on memory allocation failure.

Thanks Xiaoke Wang for the report!

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-12-12 18:06:47 +01:00
Jakub Jelen
ee180c660e server: Check strdup allocation failure
Thanks Xiaoke Wang for the report!

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-12-12 18:06:45 +01:00
abdallah elhdad
541cd39f14 zeroize sensitive buffers in ssh_sntrup761x25519_build_k
Signed-off-by: abdallah elhdad <abdallahselhdad@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-12-12 18:03:21 +01:00
abdallah elhdad
64f72ed55f Replace explicit_bzero with ssh_burn
Signed-off-by: abdallah elhdad <abdallahselhdad@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-12-12 18:03:19 +01:00
Pavol Žáčik
0ef79018b3 kex: Implement remaining hybrid ML-KEM methods
This builds on top of a9c8f94. The pure ML-KEM
code is now separated from the hybrid parts,
with the hybrid implementation generalized to
support NIST curves.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-25 17:57:42 +01:00
Pavol Žáčik
7911580304 ecdh: Factor out keypair generation
This adds a new internal API function (ssh_ecdh_init),
similar to how it's done in curve25519 implementation.
The new function can be used in hybrid key exchange
constructions.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-25 17:57:41 +01:00
Andreas Schneider
e5108f2ffc docs: Use a modern doxygen theme
Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-21 17:49:52 +01:00
Andreas Schneider
5ce4b65abb cmake: Add .cmake-format.yaml
Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-21 17:49:52 +01:00
Andreas Schneider
b62675b435 chore(editorconfig): Put CMakeLists.txt in its own section
This is read by neocmakelsp for formatting.

Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-21 17:49:52 +01:00
171 changed files with 20152 additions and 4960 deletions

1
.clang-format-ignore Normal file
View File

@@ -0,0 +1 @@
src/external/*

6
.cmake-format.yaml Normal file
View File

@@ -0,0 +1,6 @@
---
line_width: 80
tab_size: 4
use_tabchars: false
separate_ctrl_name_with_space: true
separate_fn_name_with_space: false

View File

@@ -12,7 +12,12 @@ indent_style = space
indent_size = 4
tab_width = 4
[{CMakeLists.txt,*.cmake}]
[CMakeLists.txt]
indent_style = space
indent_size = 4
tab_width = 4
tab_width = 4
[*.cmake]
indent_style = space
indent_size = 4
tab_width = 4

View File

@@ -83,6 +83,12 @@ workflow:
.tumbleweed:
extends: .tests
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD
script:
# torture_gssapi_key_exchange_null is excluded because of a bug
# https://bugzilla.opensuse.org/show_bug.cgi?id=1254680
- cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. &&
make -j$(nproc) &&
ctest --output-on-failure -E "^torture_gssapi_key_exchange_null$"
.centos:
extends: .tests
@@ -178,7 +184,7 @@ centos10s/openssl_3.5.x/x86_64/fips:
centos9s/openssl_3.5.x/x86_64:
extends: .centos9
variables:
CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON
CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON
script:
- cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. &&
make -j$(nproc) &&
@@ -196,9 +202,11 @@ centos9s/openssl_3.5.x/x86_64/fips:
variables:
OPENSSL_ENABLE_SHA1_SIGNATURES: 1
script:
# torture_gssapi_key_exchange_* tests are excluded because gssapi-keyex is disabled
# by OpenSSH in FIPS mode in RHEL 9
- cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. &&
make -j$(nproc) &&
OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure
OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure -E "^torture_gssapi_key_exchange.*"
centos8s/openssl_1.1.1/x86_64:
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS8_BUILD
@@ -214,9 +222,11 @@ centos8s/openssl_1.1.1/x86_64/fips:
extends: .fips
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS8_BUILD
script:
# torture_gssapi_key_exchange_* and torture_gssapi_server_key_exchange_* tests are excluded
# because gssapi-keyex is not allowed in FIPS mode in RHEL 8
- cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. &&
make -j$(nproc) &&
OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure
OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure -E "^torture_gssapi.*key_exchange.*"
###############################################################################
# Fedora builds #
@@ -229,7 +239,7 @@ fedora/docs:
extends: .build
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD
script:
- cmake .. && make docs_coverage && make docs
- cmake -DWITH_INTERNAL_DOC=ON .. && make docs_coverage && make docs
coverage: '/^Documentation coverage is \d+.\d+%/'
fedora/ninja:
@@ -291,6 +301,37 @@ fedora/openssl_3.x/x86_64/minimal:
make test_memcheck
- cat Testing/Temporary/MemoryChecker.*.log | wc -l | grep "^0$"
fedora/libressl/x86_64:
extends: .fedora
stage: test
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD
variables:
LIBRESSL_VERSION: "4.2.1"
CMAKE_ADDITIONAL_OPTIONS: >
-DCMAKE_C_FLAGS="-I/opt/libressl/include"
-DOPENSSL_ROOT_DIR=/opt/libressl
-DOPENSSL_INCLUDE_DIR=/opt/libressl/include
-DOPENSSL_CRYPTO_LIBRARY=/opt/libressl/lib/libcrypto.so
-DOPENSSL_SSL_LIBRARY=/opt/libressl/lib/libssl.so
-DWITH_GSSAPI=OFF
-DWITH_FIDO2=OFF
before_script:
- *build
- dnf install -y perl-core autoconf automake libtool pkgconf-pkg-config
- curl -LO https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz
- tar xf libressl-${LIBRESSL_VERSION}.tar.gz
- cd libressl-${LIBRESSL_VERSION}
- ./configure --prefix=/opt/libressl
- make -j$(nproc)
- make install
- cd ..
script:
- export PKG_CONFIG_PATH=/opt/libressl/lib/pkgconfig
- export LD_LIBRARY_PATH=/opt/libressl/lib
- cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. &&
make -j$(nproc) &&
ctest --output-on-failure
# The PKCS#11 support is turned off as it brings dozens of memory issues from
# engine_pkcs11 or openssl itself
fedora/valgrind/openssl:
@@ -504,6 +545,12 @@ fedora/abidiff:
ubuntu/openssl_3.0.x/x86_64:
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$UBUNTU_BUILD
extends: .tests
script:
# torture_gssapi_key_exchange_null is excluded because of a bug
# https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/2134527
- cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. &&
make -j$(nproc) &&
ctest --output-on-failure -E "^torture_gssapi_key_exchange_null$"
###############################################################################

116
.gitlab-ci/local-ci.sh Executable file
View File

@@ -0,0 +1,116 @@
#!/usr/bin/env bash
set -e
RED="\033[1;31m"
GREEN="\033[1;32m"
YELLOW="\033[1;33m"
BLUE="\033[1;34m"
RESET="\033[0m"
export GCL_IGNORE_PREDEFINED_VARS=CI_REGISTRY
BASE_SHA=$(git merge-base HEAD origin/master 2>/dev/null || git rev-parse HEAD~1)
COMMON_ARGS=(
--variable "CI_MERGE_REQUEST_DIFF_BASE_SHA=$BASE_SHA"
--variable "CI_REGISTRY=registry.gitlab.com"
--json-schema-validation=false
)
check_requirements() {
for cmd in docker git gitlab-ci-local; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo -e "${RED}Missing dependency: $cmd${RESET}"
exit 1
fi
echo -e "${GREEN}Found: $cmd${RESET}"
done
if ! docker info >/dev/null 2>&1; then
echo -e "${RED}Docker daemon is not running or permission denied${RESET}"
exit 1
fi
}
list_jobs() {
gitlab-ci-local --list --json-schema-validation=false | awk 'NR>1 {print $1}'
}
run_job() {
JOB="$1"
echo -e "${YELLOW}Running CI job: $JOB${RESET}"
gitlab-ci-local "$JOB" "${COMMON_ARGS[@]}"
}
cleanup_images() {
echo -e "${BLUE}Removing libssh CI images only...${RESET}"
docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}" \
| grep "$CI_REGISTRY/$BUILD_IMAGES_PROJECT" \
| awk '{print $2}' \
| xargs -r docker rmi -f
}
usage() {
echo
echo -e "${BLUE}Usage:${RESET}"
echo " $0 --list"
echo " $0 --run <job-name>"
echo " $0 --all"
echo " $0 --run <job-name> --clean"
echo " $0 --all --clean"
echo
exit 1
}
check_requirements
CLEAN=0
MODE=""
JOB=""
while [[ $# -gt 0 ]]; do
case "$1" in
--list)
MODE="list"
shift
;;
--run)
MODE="run"
JOB="$2"
shift 2
;;
--all)
MODE="all"
shift
;;
--clean)
CLEAN=1
shift
;;
*)
usage
;;
esac
done
case "$MODE" in
list)
list_jobs
;;
run)
[[ -z "$JOB" ]] && usage
run_job "$JOB"
[[ "$CLEAN" -eq 1 ]] && cleanup_images
;;
all)
for job in $(list_jobs); do
run_job "$job"
[[ "$CLEAN" -eq 1 ]] && cleanup_images
done
;;
*)
usage
;;
esac
echo -e "${GREEN}Done.${RESET}"

View File

@@ -90,7 +90,7 @@ endif (WITH_FIDO2)
# Disable symbol versioning in non UNIX platforms
if (UNIX)
find_package(ABIMap 0.3.1)
find_package(ABIMap 0.4.0)
else (UNIX)
set(WITH_SYMBOL_VERSIONING OFF)
endif (UNIX)
@@ -181,6 +181,10 @@ if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND)
set(ALLOW_ABI_BREAK "BREAK_ABI")
endif()
if (WITH_FINAL)
set(FINAL "FINAL")
endif()
# Target we can depend on in 'make dist'
set(_SYMBOL_TARGET "${PROJECT_NAME}.map")
@@ -193,7 +197,7 @@ if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND)
RELEASE_NAME_VERSION ${PROJECT_NAME}_${LIBRARY_VERSION}
CURRENT_MAP ${MAP_PATH}
COPY_TO ${MAP_PATH}
FINAL
${FINAL}
${ALLOW_ABI_BREAK})
# Write the current version to the source

View File

@@ -137,6 +137,33 @@ The script exceeded the maximum execution time set for the job
Note, that the built dependencies are cached so after successful build in your
namespace, the rebuilds should be much faster.
## Running GitLab CI locally (optional helper)
For contributors working on CI, build system changes, or adding new CI jobs, it can be useful to run GitLab CI pipelines locally before pushing.
libssh provides a small helper script based on `gitlab-ci-local` that can:
- List all jobs defined in `.gitlab-ci.yml`
- Run a specific job or the full pipeline locally
- Automatically pick up new jobs when they are added to the CI configuration
- Optionally clean up CI Docker images after execution
### Requirements
- Docker (daemon running)
- git
- gitlab-ci-local
https://github.com/firecow/gitlab-ci-local
### Usage
```bash
./.gitlab-ci/local-ci.sh --list
./.gitlab-ci/local-ci.sh --run fedora/libressl/x86_64
./.gitlab-ci/local-ci.sh --all
./.gitlab-ci/local-ci.sh --run fedora/libressl/x86_64 --clean
```
# Coding conventions in the libssh tree
## Quick Start

View File

@@ -104,9 +104,10 @@ if (OPENSSL_FOUND)
check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES)
check_function_exists(EVP_chacha20 HAVE_OPENSSL_EVP_CHACHA20)
# Check for ML-KEM768 availability (OpenSSL 3.5+)
# Check for ML-KEM availability (OpenSSL 3.5+)
if (OPENSSL_VERSION VERSION_GREATER_EQUAL "3.5.0")
set(HAVE_MLKEM 1)
set(HAVE_OPENSSL_MLKEM 1)
set(HAVE_MLKEM1024 1)
endif ()
unset(CMAKE_REQUIRED_INCLUDES)
@@ -139,6 +140,7 @@ check_function_exists(strncpy HAVE_STRNCPY)
check_function_exists(strndup HAVE_STRNDUP)
check_function_exists(strtoull HAVE_STRTOULL)
check_function_exists(explicit_bzero HAVE_EXPLICIT_BZERO)
check_function_exists(memset_explicit HAVE_MEMSET_EXPLICIT)
check_function_exists(memset_s HAVE_MEMSET_S)
if (HAVE_GLOB_H)
@@ -233,6 +235,10 @@ if (GCRYPT_FOUND)
set(HAVE_GCRYPT_CHACHA_POLY 1)
set(HAVE_GCRYPT_CURVE25519 1)
endif (NOT GCRYPT_VERSION VERSION_LESS "1.7.0")
if (GCRYPT_VERSION VERSION_GREATER_EQUAL "1.10.1")
set(HAVE_GCRYPT_MLKEM 1)
set(HAVE_MLKEM1024 1)
endif ()
endif (GCRYPT_FOUND)
if (MBEDTLS_FOUND)

View File

@@ -179,6 +179,9 @@
/* Define to 1 if you have the `explicit_bzero' function. */
#cmakedefine HAVE_EXPLICIT_BZERO 1
/* Define to 1 if you have the `memset_explicit' function. */
#cmakedefine HAVE_MEMSET_EXPLICIT 1
/* Define to 1 if you have the `memset_s' function. */
#cmakedefine HAVE_MEMSET_S 1
@@ -191,8 +194,14 @@
/* Define to 1 if we have support for blowfish */
#cmakedefine HAVE_BLOWFISH 1
/* Define to 1 if we have support for ML-KEM */
#cmakedefine HAVE_MLKEM 1
/* Define to 1 if we have support for ML-KEM in libgcrypt */
#cmakedefine HAVE_GCRYPT_MLKEM 1
/* Define to 1 if we have support for ML-KEM in OpenSSL */
#cmakedefine HAVE_OPENSSL_MLKEM 1
/* Define to 1 if we have support for ML-KEM1024 in either backend */
#cmakedefine HAVE_MLKEM1024 1
/*************************** LIBRARIES ***************************/

View File

@@ -1,91 +1,247 @@
#
# Build the documentation
#
if (${CMAKE_VERSION} VERSION_GREATER "3.8.99")
# To build the documentation with a local doxygen-awesome-css directory:
#
# cmake -S . -B obj \
# -DDOXYGEN_AWESOME_CSS_DIR=/path/to/doxygen-awesome-css
# cmake --build obj --target docs
#
# The tarball can be downloaded from:
# https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v2.4.1.tar.gz
#
find_package(Doxygen)
if (DOXYGEN_FOUND)
set(DOXYGEN_AWESOME_CSS_PROJECT
"https://github.com/jothepro/doxygen-awesome-css")
set(DOXYGEN_AWESOME_CSS_VERSION "2.4.1")
set(DOXYGEN_AWESOME_CSS_URL
"${DOXYGEN_AWESOME_CSS_PROJECT}/archive/refs/tags/v${DOXYGEN_AWESOME_CSS_VERSION}.tar.gz"
)
# Allow specifying a local doxygen-awesome-css directory (useful for
# packaging)
if (NOT DEFINED DOXYGEN_AWESOME_CSS_DIR)
# Custom target to download doxygen-awesome-css at build time
add_custom_target(
doxygen-awesome-css
COMMAND
${CMAKE_COMMAND} -DURL=${DOXYGEN_AWESOME_CSS_URL}
-DDEST_DIR=${CMAKE_CURRENT_BINARY_DIR}
-DVERSION=${DOXYGEN_AWESOME_CSS_VERSION} -P
${CMAKE_CURRENT_SOURCE_DIR}/fetch_doxygen_awesome.cmake
COMMENT "Fetching doxygen-awesome-css theme")
set(AWESOME_CSS_DIR
"${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css-${DOXYGEN_AWESOME_CSS_VERSION}"
)
else ()
message(
STATUS
"Using doxygen-awesome-css from ${DOXYGEN_AWESOME_CSS_DIR}")
set(AWESOME_CSS_DIR "${DOXYGEN_AWESOME_CSS_DIR}")
endif ()
# Project title shown in documentation
set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME})
# Project version number shown in documentation
set(DOXYGEN_PROJECT_NUMBER ${PROJECT_VERSION})
# Brief description shown below project name
set(DOXYGEN_PROJECT_BRIEF "The SSH library")
# Project favicon (browser tab icon)
set(DOXYGEN_PROJECT_ICON ${CMAKE_CURRENT_SOURCE_DIR}/favicon.png)
# Number of spaces used for indentation in code blocks
set(DOXYGEN_TAB_SIZE 4)
# Generate output optimized for C (vs C++)
set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES)
# Enable parsing of markdown in comments
set(DOXYGEN_MARKDOWN_SUPPORT YES)
set(DOXYGEN_FULL_PATH_NAMES NO)
set(DOXYGEN_GENERATE_TAGFILE "tags.xml")
# Warn about undocumented members to improve documentation quality
set(DOXYGEN_WARN_IF_UNDOCUMENTED YES)
# Do not extract private class members
set(DOXYGEN_EXTRACT_PRIVATE NO)
if (WITH_INTERNAL_DOC)
# Include internal documentation
set(DOXYGEN_INTERNAL_DOCS YES)
else ()
# Do not include internal documentation
set(DOXYGEN_INTERNAL_DOCS NO)
endif( WITH_INTERNAL_DOC)
# Disable built-in clipboard (using doxygen-awesome extension instead)
set(DOXYGEN_HTML_COPY_CLIPBOARD NO)
# Disable page outline panel (using interactive TOC extension instead)
set(DOXYGEN_PAGE_OUTLINE_PANEL NO)
set(DOXYGEN_PREDEFINED DOXYGEN
WITH_SERVER
WITH_SFTP
# Required configuration for doxygen-awesome-css theme Generate treeview
# sidebar for navigation
set(DOXYGEN_GENERATE_TREEVIEW YES)
# Enable default index pages
set(DOXYGEN_DISABLE_INDEX NO)
# Use top navigation bar instead of full sidebar (required for theme
# compatibility)
set(DOXYGEN_FULL_SIDEBAR NO)
# Use light color style (required for Doxygen >= 1.9.5)
set(DOXYGEN_HTML_COLORSTYLE LIGHT)
# Disable diagram generation (not relevant for C projects)
set(DOXYGEN_HAVE_DOT NO)
set(DOXYGEN_CLASS_DIAGRAMS NO)
set(DOXYGEN_CALL_GRAPH NO)
set(DOXYGEN_CALLER_GRAPH NO)
# Preprocessor defines to use when parsing code
set(DOXYGEN_PREDEFINED DOXYGEN WITH_SERVER WITH_SFTP
PRINTF_ATTRIBUTE\(x,y\))
set(DOXYGEN_DOT_GRAPH_MAX_NODES 100)
set(DOXYGEN_EXCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/that_style)
set(DOXYGEN_HTML_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/that_style/header.html)
set(DOXYGEN_HTML_EXTRA_STYLESHEET ${CMAKE_CURRENT_SOURCE_DIR}/that_style/that_style.css)
set(DOXYGEN_HTML_EXTRA_FILES ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_left.svg
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_right.svg
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_inter.svg
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/sync_off.png
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/sync_on.png
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/splitbar_handle.svg
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/doc.svg
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/mag_glass.svg
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/folderclosed.svg
${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/folderopen.svg
${CMAKE_CURRENT_SOURCE_DIR}/that_style/js/striped_bg.js)
set(DOXYGEN_EXCLUDE_PATTERNS */src/external/* fe25519.h ge25519.h sc25519.h
blf.h)
set(DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS chacha20_poly1305_keysched,dh_ctx,dh_ctx,dh_keypair,error_struct,
packet_struct,pem_get_password_struct,ssh_tokens_st,
sftp_attributes_struct,sftp_client_message_struct,
sftp_dir_struct,sftp_ext_struct,sftp_file_struct,sftp_message_struct,
sftp_packet_struct,sftp_request_queue_struct,sftp_session_struct,
sftp_status_message_struct,ssh_agent_state_struct,
ssh_agent_struct,ssh_auth_auto_state_struct,ssh_auth_request,
ssh_bind_config_keyword_table_s,ssh_bind_config_match_keyword_table_s,
ssh_bind_struct,ssh_buffer_struct,ssh_channel_callbacks_struct,
ssh_channel_read_termination_struct,ssh_channel_request,
ssh_channel_request_open,ssh_channel_struct,ssh_cipher_struct,
ssh_common_struct,ssh_config_keyword_table_s,
ssh_config_match_keyword_table_s,ssh_connector_struct,
ssh_counter_struct,ssh_crypto_struct,ssh_event_fd_wrapper,
ssh_event_struct,ssh_global_request,ssh_gssapi_struct,ssh_hmac_struct,
ssh_iterator,ssh_kbdint_struct,ssh_kex_struct,ssh_key_struct,
ssh_knownhosts_entry,ssh_list,ssh_mac_ctx_struct,ssh_message_struct,
ssh_packet_callbacks_struct,ssh_packet_header,ssh_poll_ctx_struct,
ssh_poll_handle_struct,ssh_pollfd_struct,ssh_private_key_struct,
ssh_public_key_struct,ssh_scp_struct,ssh_service_request,
ssh_session_struct,ssh_signature_struct,ssh_socket_struct,
ssh_string_struct,ssh_threads_callbacks_struct,ssh_timestamp,)
set(DOXYGEN_EXCLUDE_SYMBOLS_MACRO SSH_FXP*,SSH_SOCKET*,SERVERBANNER,SOCKOPT_TYPE_ARG4,SSH_FILEXFER*,
SSH_FXF*,SSH_S_*,SFTP_*,NSS_BUFLEN_PASSWD,CLOCK,MAX_LINE_SIZE,
PKCS11_URI,KNOWNHOSTS_MAXTYPES,)
set(DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS sftp_attributes,sftp_client_message,sftp_dir,sftp_ext,sftp_file,
sftp_message,sftp_packet,sftp_request_queue,sftp_session,
sftp_status_message,sftp_statvfs_t,poll_fn,ssh_callback_int,
ssh_callback_data,ssh_callback_int_int,ssh_message_callback,
ssh_channel_callback_int,ssh_channel_callback_data,ssh_callbacks,
ssh_gssapi_select_oid_callback,ssh_gssapi_accept_sec_ctx_callback,
ssh_gssapi_verify_mic_callback,ssh_server_callbacks,ssh_socket_callbacks,
ssh_packet_callbacks,ssh_channel_callbacks,ssh_bind,ssh_bind_callbacks,)
set(DOXYGEN_EXCLUDE_SYMBOLS ${DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS}
${DOXYGEN_EXCLUDE_SYMBOLS_MACRO}
${DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS})
# Exclude patterns for files we don't want to document
set(DOXYGEN_EXCLUDE_PATTERNS */src/external/* fe25519.h ge25519.h sc25519.h
blf.h)
# Exclude internal structures from documentation
set(DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS
chacha20_poly1305_keysched,
dh_ctx,
dh_ctx,
dh_keypair,
error_struct,
packet_struct,
pem_get_password_struct,
ssh_tokens_st,
sftp_attributes_struct,
sftp_client_message_struct,
sftp_dir_struct,
sftp_ext_struct,
sftp_file_struct,
sftp_message_struct,
sftp_packet_struct,
sftp_request_queue_struct,
sftp_session_struct,
sftp_status_message_struct,
ssh_agent_state_struct,
ssh_agent_struct,
ssh_auth_auto_state_struct,
ssh_auth_request,
ssh_bind_config_keyword_table_s,
ssh_bind_config_match_keyword_table_s,
ssh_bind_struct,
ssh_buffer_struct,
ssh_channel_callbacks_struct,
ssh_channel_read_termination_struct,
ssh_channel_request,
ssh_channel_request_open,
ssh_channel_struct,
ssh_cipher_struct,
ssh_common_struct,
ssh_config_keyword_table_s,
ssh_config_match_keyword_table_s,
ssh_connector_struct,
ssh_counter_struct,
ssh_crypto_struct,
ssh_event_fd_wrapper,
ssh_event_struct,
ssh_global_request,
ssh_gssapi_struct,
ssh_hmac_struct,
ssh_iterator,
ssh_kbdint_struct,
ssh_kex_struct,
ssh_key_struct,
ssh_knownhosts_entry,
ssh_list,
ssh_mac_ctx_struct,
ssh_message_struct,
ssh_packet_callbacks_struct,
ssh_packet_header,
ssh_poll_ctx_struct,
ssh_poll_handle_struct,
ssh_pollfd_struct,
ssh_private_key_struct,
ssh_public_key_struct,
ssh_scp_struct,
ssh_service_request,
ssh_session_struct,
ssh_signature_struct,
ssh_socket_struct,
ssh_string_struct,
ssh_threads_callbacks_struct,
ssh_timestamp)
set(DOXYGEN_EXCLUDE_SYMBOLS_MACRO
SSH_FXP*,
SSH_SOCKET*,
SERVERBANNER,
SOCKOPT_TYPE_ARG4,
SSH_FILEXFER*,
SSH_FXF*,
SSH_S_*,
SFTP_*,
NSS_BUFLEN_PASSWD,
CLOCK,
MAX_LINE_SIZE,
PKCS11_URI,
KNOWNHOSTS_MAXTYPES)
set(DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS
sftp_attributes,
sftp_client_message,
sftp_dir,
sftp_ext,
sftp_file,
sftp_message,
sftp_packet,
sftp_request_queue,
sftp_status_message,
sftp_statvfs_t,
poll_fn,
ssh_callback_int,
ssh_callback_data,
ssh_callback_int_int,
ssh_message_callback,
ssh_channel_callback_int,
ssh_channel_callback_data,
ssh_callbacks,
ssh_gssapi_select_oid_callback,
ssh_gssapi_accept_sec_ctx_callback,
ssh_gssapi_verify_mic_callback,
ssh_server_callbacks,
ssh_socket_callbacks,
ssh_packet_callbacks,
ssh_channel_callbacks,
ssh_bind,
ssh_bind_callbacks)
set(DOXYGEN_EXCLUDE_SYMBOLS
${DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS} ${DOXYGEN_EXCLUDE_SYMBOLS_MACRO}
${DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS})
# Custom layout file to rename "Topics" to "API Reference" and simplify
# navigation
set(DOXYGEN_LAYOUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml)
# Custom HTML header with doxygen-awesome extension initialization
set(DOXYGEN_HTML_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/header.html)
# Modern CSS theme for documentation with custom libssh.org color scheme
set(DOXYGEN_HTML_EXTRA_STYLESHEET
${AWESOME_CSS_DIR}/doxygen-awesome.css
${CMAKE_CURRENT_SOURCE_DIR}/doxygen-custom.css)
# JavaScript extensions: dark mode toggle, copy button, paragraph links,
# interactive TOC
set(DOXYGEN_HTML_EXTRA_FILES
${AWESOME_CSS_DIR}/doxygen-awesome-darkmode-toggle.js
${AWESOME_CSS_DIR}/doxygen-awesome-fragment-copy-button.js
${AWESOME_CSS_DIR}/doxygen-awesome-paragraph-link.js
${AWESOME_CSS_DIR}/doxygen-awesome-interactive-toc.js)
# This updates the Doxyfile if we do changes here
set(_doxyfile_template "${CMAKE_BINARY_DIR}/CMakeDoxyfile.in")
set(_target_doxyfile "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.docs")
configure_file("${_doxyfile_template}" "${_target_doxyfile}")
doxygen_add_docs(docs
${CMAKE_SOURCE_DIR}/include/libssh
${CMAKE_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR})
doxygen_add_docs(docs ${CMAKE_SOURCE_DIR}/include/libssh
${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR})
add_custom_target(docs_coverage COMMAND ${CMAKE_SOURCE_DIR}/doc/doc_coverage.sh ${CMAKE_BINARY_DIR})
endif() # DOXYGEN_FOUND
# Make docs depend on doxygen-awesome-css download (if not using local dir)
if (TARGET doxygen-awesome-css)
add_dependencies(docs doxygen-awesome-css)
endif ()
endif() # CMAKE_VERSION
add_custom_target(
docs_coverage COMMAND ${CMAKE_SOURCE_DIR}/doc/doc_coverage.sh
${CMAKE_BINARY_DIR})
endif (DOXYGEN_FOUND)

242
doc/DoxygenLayout.xml Normal file
View File

@@ -0,0 +1,242 @@
<?xml version="1.0" encoding="UTF-8"?>
<doxygenlayout version="2.0">
<!-- Generated by doxygen 1.14.0 -->
<!-- Navigation index tabs for HTML output -->
<navindex>
<tab type="mainpage" visible="yes" title=""/>
<tab type="topics" visible="yes" title="API Reference" intro=""/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="files" visible="yes" title="">
<tab type="filelist" visible="yes" title="" intro=""/>
<tab type="globals" visible="yes" title="" intro=""/>
</tab>
<tab type="structs" visible="yes" title="">
<tab type="structlist" visible="yes" title="" intro=""/>
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
</tab>
</navindex>
<!-- Layout definition for a class page -->
<class>
<briefdescription visible="yes"/>
<includes visible="$SHOW_HEADERFILE"/>
<inheritancegraph visible="yes"/>
<collaborationgraph visible="yes"/>
<memberdecl>
<nestedclasses visible="yes" title=""/>
<publictypes visible="yes" title=""/>
<services visible="yes" title=""/>
<interfaces visible="yes" title=""/>
<publicslots visible="yes" title=""/>
<signals visible="yes" title=""/>
<publicmethods visible="yes" title=""/>
<publicstaticmethods visible="yes" title=""/>
<publicattributes visible="yes" title=""/>
<publicstaticattributes visible="yes" title=""/>
<protectedtypes visible="yes" title=""/>
<protectedslots visible="yes" title=""/>
<protectedmethods visible="yes" title=""/>
<protectedstaticmethods visible="yes" title=""/>
<protectedattributes visible="yes" title=""/>
<protectedstaticattributes visible="yes" title=""/>
<packagetypes visible="yes" title=""/>
<packagemethods visible="yes" title=""/>
<packagestaticmethods visible="yes" title=""/>
<packageattributes visible="yes" title=""/>
<packagestaticattributes visible="yes" title=""/>
<properties visible="yes" title=""/>
<events visible="yes" title=""/>
<privatetypes visible="yes" title=""/>
<privateslots visible="yes" title=""/>
<privatemethods visible="yes" title=""/>
<privatestaticmethods visible="yes" title=""/>
<privateattributes visible="yes" title=""/>
<privatestaticattributes visible="yes" title=""/>
<friends visible="yes" title=""/>
<related visible="yes" title="" subtitle=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription visible="yes" title=""/>
<memberdef>
<inlineclasses visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<enums visible="yes" title=""/>
<services visible="yes" title=""/>
<interfaces visible="yes" title=""/>
<constructors visible="yes" title=""/>
<functions visible="yes" title=""/>
<related visible="yes" title=""/>
<variables visible="yes" title=""/>
<properties visible="yes" title=""/>
<events visible="yes" title=""/>
</memberdef>
<allmemberslink visible="yes"/>
<usedfiles visible="$SHOW_USED_FILES"/>
<authorsection visible="yes"/>
</class>
<!-- Layout definition for a namespace page -->
<namespace>
<briefdescription visible="yes"/>
<memberdecl>
<nestednamespaces visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<concepts visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<sequences visible="yes" title=""/>
<dictionaries visible="yes" title=""/>
<enums visible="yes" title=""/>
<functions visible="yes" title=""/>
<variables visible="yes" title=""/>
<properties visible="yes" title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription visible="yes" title=""/>
<memberdef>
<inlineclasses visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<sequences visible="yes" title=""/>
<dictionaries visible="yes" title=""/>
<enums visible="yes" title=""/>
<functions visible="yes" title=""/>
<variables visible="yes" title=""/>
<properties visible="yes" title=""/>
</memberdef>
<authorsection visible="yes"/>
</namespace>
<!-- Layout definition for a concept page -->
<concept>
<briefdescription visible="yes"/>
<includes visible="$SHOW_HEADERFILE"/>
<definition visible="yes" title=""/>
<detaileddescription visible="yes" title=""/>
<authorsection visible="yes"/>
</concept>
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<includegraph visible="yes"/>
<includedbygraph visible="yes"/>
<sourcelink visible="yes"/>
<memberdecl>
<interfaces visible="yes" title=""/>
<classes visible="yes" title=""/>
<structs visible="yes" title=""/>
<exceptions visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<concepts visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
<defines visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<sequences visible="yes" title=""/>
<dictionaries visible="yes" title=""/>
<enums visible="yes" title=""/>
<functions visible="yes" title=""/>
<variables visible="yes" title=""/>
<properties visible="yes" title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription visible="yes" title=""/>
<memberdef>
<inlineclasses visible="yes" title=""/>
<defines visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<sequences visible="yes" title=""/>
<dictionaries visible="yes" title=""/>
<enums visible="yes" title=""/>
<functions visible="yes" title=""/>
<variables visible="yes" title=""/>
<properties visible="yes" title=""/>
</memberdef>
<authorsection/>
</file>
<!-- Layout definition for a group page -->
<group>
<briefdescription visible="yes"/>
<groupgraph visible="yes"/>
<memberdecl>
<nestedgroups visible="yes" title=""/>
<modules visible="yes" title=""/>
<dirs visible="yes" title=""/>
<files visible="yes" title=""/>
<namespaces visible="yes" title=""/>
<concepts visible="yes" title=""/>
<classes visible="yes" title=""/>
<defines visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<sequences visible="yes" title=""/>
<dictionaries visible="yes" title=""/>
<enums visible="yes" title=""/>
<enumvalues visible="yes" title=""/>
<functions visible="yes" title=""/>
<variables visible="yes" title=""/>
<signals visible="yes" title=""/>
<publicslots visible="yes" title=""/>
<protectedslots visible="yes" title=""/>
<privateslots visible="yes" title=""/>
<events visible="yes" title=""/>
<properties visible="yes" title=""/>
<friends visible="yes" title=""/>
<membergroups visible="yes"/>
</memberdecl>
<detaileddescription visible="yes" title=""/>
<memberdef>
<pagedocs/>
<inlineclasses visible="yes" title=""/>
<defines visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<sequences visible="yes" title=""/>
<dictionaries visible="yes" title=""/>
<enums visible="yes" title=""/>
<enumvalues visible="yes" title=""/>
<functions visible="yes" title=""/>
<variables visible="yes" title=""/>
<signals visible="yes" title=""/>
<publicslots visible="yes" title=""/>
<protectedslots visible="yes" title=""/>
<privateslots visible="yes" title=""/>
<events visible="yes" title=""/>
<properties visible="yes" title=""/>
<friends visible="yes" title=""/>
</memberdef>
<authorsection visible="yes"/>
</group>
<!-- Layout definition for a C++20 module page -->
<module>
<briefdescription visible="yes"/>
<exportedmodules visible="yes"/>
<memberdecl>
<concepts visible="yes" title=""/>
<classes visible="yes" title=""/>
<enums visible="yes" title=""/>
<typedefs visible="yes" title=""/>
<functions visible="yes" title=""/>
<variables visible="yes" title=""/>
<membergroups visible="yes" title=""/>
</memberdecl>
<detaileddescription visible="yes" title=""/>
<memberdecl>
<files visible="yes"/>
</memberdecl>
</module>
<!-- Layout definition for a directory page -->
<directory>
<briefdescription visible="yes"/>
<directorygraph visible="yes"/>
<memberdecl>
<dirs visible="yes"/>
<files visible="yes"/>
</memberdecl>
<detaileddescription visible="yes" title=""/>
</directory>
</doxygenlayout>

127
doc/doxygen-custom.css Normal file
View File

@@ -0,0 +1,127 @@
/**
* Custom color scheme for libssh documentation
* Based on libssh.org color palette
*/
html {
/* Primary colors - using libssh.org orange accent */
--primary-color: #F78C40;
--primary-dark-color: #f57900;
--primary-light-color: #fab889;
/* Accent color - neutral gray */
--primary-lighter-color: #5A5A5A;
/* Page colors - clean white background */
--page-background-color: #ffffff;
--page-foreground-color: #333333;
--page-secondary-foreground-color: #666666;
/* Links - use the warm orange color */
--link-color: #F78C40;
--link-hover-color: #f0690a;
/* Code blocks and fragments - very light background */
--code-background: #f9f9f9;
--fragment-background: #f9f9f9;
/* Borders - subtle light grey */
--separator-color: #e0e0e0;
--border-light-color: #f0f0f0;
/* Side navigation - pure white */
--side-nav-background: #ffffff;
/* Menu colors - warm orange accent */
--menu-selected-background: #F78C40;
/* Tables and boxes - lighter */
--tablehead-background: #fbc7a2;
--tablehead-foreground: #333333;
}
/* Header styling with libssh brand colors */
#titlearea {
background-color: #5A5A5A;
background-image: linear-gradient(to right, #5A5A5A, #6a6a6a);
border-bottom: 3px solid #F78C40;
}
#projectname {
color: #ffffff !important;
}
#projectbrief {
color: #fab889 !important;
}
/* Top navigation tabs */
#top {
background: linear-gradient(to bottom, #5A5A5A 0%, #6a6a6a 100%);
}
.tabs, .tabs2, .tabs3 {
background-image: none;
background-color: transparent;
}
.tablist li {
background: rgba(255, 255, 255, 0.1);
border-right: 1px solid rgba(255, 255, 255, 0.2);
}
.tablist li:hover {
background: rgba(255, 255, 255, 0.2);
}
.tablist li.current {
background: #F78C40;
border-bottom: 3px solid #f57900;
}
/* Tab text colors - comprehensive selectors */
#nav-path ul li a,
.tabs a,
.tabs2 a,
.tabs3 a,
.tablist a,
.tablist a:link,
.tablist a:visited,
.tablist li a,
#main-nav a,
.sm > li > a,
.sm > li > a .sub-arrow {
color: #ffffff !important;
text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3);
}
/* Active/current tab text */
#nav-path ul li.current a,
.tabs .current a,
.tabs2 .current a,
.tabs3 .current a,
.tablist .current a,
.tablist .current a:link,
.tablist .current a:visited,
.tablist li.current a,
#main-nav .current a,
.sm .current a {
color: #333333 !important;
text-shadow: none;
}
/* Dropdown arrow - white color for top menu */
.sm-dox a span.sub-arrow {
border-right-color: #ffffff !important;
border-bottom-color: #ffffff !important;
}
/* Dropdown menu text - must be dark on white background */
/* Make this as specific as possible to override white color */
.sm-dox > li > ul > li > a,
.sm-dox li ul li a,
.sm-dox ul li a,
#main-menu ul li a {
color: #333333 !important;
text-shadow: none !important;
}

BIN
doc/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

View File

@@ -0,0 +1,41 @@
# Script to download doxygen-awesome-css at build time
#
# Usage:
# cmake -P fetch_doxygen_awesome.cmake \
# -DURL=<download_url> \
# -DDEST_DIR=<destination_directory> \
# -DVERSION=<version>
if(NOT DEFINED URL)
message(FATAL_ERROR "URL not specified")
endif()
if(NOT DEFINED DEST_DIR)
message(FATAL_ERROR "DEST_DIR not specified")
endif()
if(NOT DEFINED VERSION)
message(FATAL_ERROR "VERSION not specified")
endif()
set(EXTRACT_DIR "${DEST_DIR}/doxygen-awesome-css-${VERSION}")
if(NOT EXISTS "${EXTRACT_DIR}/doxygen-awesome.css")
message(STATUS "Downloading doxygen-awesome-css ${VERSION}...")
set(TARBALL "${DEST_DIR}/doxygen-awesome-css.tar.gz")
file(DOWNLOAD
"${URL}"
"${TARBALL}"
STATUS download_status
SHOW_PROGRESS
)
list(GET download_status 0 status_code)
if(NOT status_code EQUAL 0)
list(GET download_status 1 error_msg)
message(FATAL_ERROR "Download failed: ${error_msg}")
endif()
message(STATUS "Extracting doxygen-awesome-css...")
file(ARCHIVE_EXTRACT
INPUT "${TARBALL}"
DESTINATION "${DEST_DIR}"
)
file(REMOVE "${TARBALL}")
endif()

92
doc/header.html Normal file
View File

@@ -0,0 +1,92 @@
<!-- HTML header for doxygen 1.14.0-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="$langISO">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<!--BEGIN PROJECT_ICON-->
<link rel="icon" href="$relpath^$projecticon" type="image/x-icon" />
<!--END PROJECT_ICON-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<!--BEGIN FULL_SIDEBAR-->
<script type="text/javascript">var page_layout=1;</script>
<!--END FULL_SIDEBAR-->
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
<!--BEGIN COPY_CLIPBOARD-->
<script type="text/javascript" src="$relpath^clipboard.js"></script>
<!--END COPY_CLIPBOARD-->
$treeview
$search
$mathjax
$darkmode
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
$extrastylesheet
<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
<script type="text/javascript">
DoxygenAwesomeDarkModeToggle.init()
</script>
<script type="text/javascript" src="$relpath^doxygen-awesome-fragment-copy-button.js"></script>
<script type="text/javascript">
DoxygenAwesomeFragmentCopyButton.init()
</script>
<script type="text/javascript" src="$relpath^doxygen-awesome-paragraph-link.js"></script>
<script type="text/javascript">
DoxygenAwesomeParagraphLink.init()
</script>
<script type="text/javascript" src="$relpath^doxygen-awesome-interactive-toc.js"></script>
<script type="text/javascript">
DoxygenAwesomeInteractiveToc.init()
</script>
</head>
<body>
<!--BEGIN FULL_SIDEBAR-->
<div id="side-nav" class="ui-resizable side-nav-resizable"><!-- do not remove this div, it is closed by doxygen! -->
<!--END FULL_SIDEBAR-->
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr id="projectrow">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"$logosize/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign">
<div id="projectname">$projectname<!--BEGIN PROJECT_NUMBER--><span id="projectnumber">&#160;$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td>
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<!--BEGIN !FULL_SIDEBAR-->
<td>$searchbox</td>
<!--END !FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
<!--BEGIN SEARCHENGINE-->
<!--BEGIN FULL_SIDEBAR-->
<tr><td colspan="2">$searchbox</td></tr>
<!--END FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

View File

@@ -19,12 +19,13 @@ the interesting functions as you go.
The libssh library provides:
- <strong>Key Exchange Methods</strong>: <i>sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com, mlkem768x25519-sha256, curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521</i>, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1
- <strong>Key Exchange Methods</strong>: <i>sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com, mlkem768x25519-sha256, mlkem768nistp256-sha256, mlkem1024nistp384-sha384, curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521</i>, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1
- <strong>GSSAPI Key Exchange Methods</strong>: gss-group14-sha256-*, gss-group16-sha512-*, gss-nistp256-sha256-*, gss-curve25519-sha256-*
- <strong>Public Key Algorithms</strong>: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256
- <strong>Ciphers</strong>: <i>aes256-ctr, aes192-ctr, aes128-ctr</i>, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc
- <strong>Compression Schemes</strong>: zlib, <i>zlib@openssh.com</i>, none
- <strong>MAC hashes</strong>: hmac-sha1, hmac-sha2-256, hmac-sha2-512, hmac-md5
- <strong>Authentication</strong>: none, password, public-key, keyboard-interactive, <i>gssapi-with-mic</i>
- <strong>Authentication</strong>: none, password, public-key, keyboard-interactive, <i>gssapi-with-mic, gssapi-keyex</i>
- <strong>Channels</strong>: shell, exec (incl. SCP wrapper), direct-tcpip, subsystem, <i>auth-agent-req@openssh.com</i>
- <strong>Global Requests</strong>: tcpip-forward, forwarded-tcpip
- <strong>Channel Requests</strong>: x11, pty, <i>exit-status, signal, exit-signal, keepalive@openssh.com, auth-agent-req@openssh.com</i>

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Jan-Lukas Wynen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,22 +0,0 @@
# that style
A plain, more modern HTML style for Doxygen
## Requirements
- Doxygen (tested with version 1.8.13)
- *optional*: a sass/scss compiler if you want to modify the style
## Simple usage
Tell Doxygen about the files for that style as shown in [doxyfile.conf](doxyfile.conf). You might need to adjust the
paths depending on where you installed that style.
When you run Doxygen, all files are copied into to generated HTML folder. So you don't need to keep the originals around
unless you want to re-generate the documentation.
## Advanced
that style uses a custom javascript to hack some nice stripes into some tables. It has to be loaded from HTML. Hence you need
to use the provided custom header. Since its default content may change when Doxygen is updated, there might be syntax error in
the generated HTML. If this is the case, you can remove the custom header (adjust your doxyfile.conf). This has no
disadvantages other than removing the stripes.
[that_style.css](that_style.css) was generated from the scss files in the folder [sass](sass). If you want to change the style,
use those files in order to have better control. For instance, you can easily change most colors by modifying the variables
in the beginning of [that_style.scss](sass/that_style.scss).

View File

@@ -1,56 +0,0 @@
<!-- HTML header for doxygen 1.8.13-->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen $doxygenversion"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview
$search
$mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
<script src="$relpath^striped_bg.js"></script>
$extrastylesheet
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign" style="padding-left: 0.5em;">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em;">
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

View File

@@ -1,97 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="22"
viewBox="0 0 6.3499999 5.8208335"
version="1.1"
id="svg8"
sodipodi:docname="doc.svg"
inkscape:version="0.92.1 r">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="11.139212"
inkscape:cy="14.811193"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:showpageshadow="false"
units="px"
inkscape:window-width="2560"
inkscape:window-height="1357"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-291.17915)">
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#4d4d4d;stroke-width:0.26458329;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 3.315043,291.8406 H 1.4552083 v 4.49792 h 3.1749999 v -3.10055 z"
id="path5095"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#4d4d4d;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 3.1837239,291.84114 v 1.71186 h 1.4472656 v -0.31418 H 3.4473958 v -1.39768 z"
id="path5128"
inkscape:connector-curvature="0" />
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect5132"
width="2.1166668"
height="0.26458332"
x="1.8520833"
y="293.82498" />
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect5136"
width="1.0583334"
height="0.26458332"
x="1.8520832"
y="294.35416" />
<rect
y="294.88333"
x="1.8520832"
height="0.26458332"
width="1.8520833"
id="rect5138"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect4543"
width="1.5875"
height="0.26458332"
x="1.8520832"
y="295.41248" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="22"
viewBox="0 0 6.3499998 5.8208335"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r"
sodipodi:docname="folderclosed.svg"
inkscape:export-filename="/home/jl/Prog/doxygen_style/folderclosed.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="51.113139"
inkscape:cx="7.7057751"
inkscape:cy="12.584171"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-global="false"
units="px"
inkscape:showpageshadow="false"
inkscape:window-width="2560"
inkscape:window-height="1357"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:measure-start="0,0"
inkscape:measure-end="0,0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-291.17915)">
<path
inkscape:connector-curvature="0"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 0.52916667,292.2374 -0.26458334,0.52925 v 3.43958 H 4.7625001 v -3.43958 H 2.38125 L 2.1166667,292.2374 Z"
id="rect4498"
sodipodi:nodetypes="cccccccc" />
<path
inkscape:connector-curvature="0"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cccccc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.66145831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 2.9104167,292.76665 2.38125,293.56034 H 0.26458333 v 0.26464 H 2.38125 l 0.5291667,-0.79375 h 1.8520834 v -0.26458 z"
id="rect4500"
sodipodi:nodetypes="ccccccccc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,83 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="22"
viewBox="0 0 6.3499998 5.8208335"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r"
sodipodi:docname="folderopen.svg"
inkscape:export-filename="/home/jl/Prog/doxygen_style/folderopen.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="43.725861"
inkscape:cx="8.2043861"
inkscape:cy="13.464183"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-global="false"
units="px"
inkscape:showpageshadow="false"
inkscape:window-width="2560"
inkscape:window-height="1357"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:measure-start="0,0"
inkscape:measure-end="0,0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-291.17915)">
<path
inkscape:connector-curvature="0"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.66145831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 0.52916667,292.23748 -0.26458334,0.52917 v 3.43958 H 4.762461 l 7.8e-5,-3.43958 H 2.38125 l -0.2645833,-0.52917 z"
id="path5228"
sodipodi:nodetypes="cccccccc" />
<path
inkscape:connector-curvature="0"
id="path5279"
d="M 1.0583333,293.5604 H 5.55625 L 4.7625,296.20603 H 0.26458333 Z"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ececec;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.66145831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:nodetypes="ccccc" />
<path
sodipodi:nodetypes="ccccccc"
inkscape:connector-curvature="0"
id="path5234"
d="M 1.0583333,294.35415 H 3.175 l 0.5291667,-0.52917 H 5.55625 L 4.7625,296.20603 H 0.26458333 Z"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.66145831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208332 5.8208335"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r"
sodipodi:docname="mag_glass.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="8.961936"
inkscape:cy="10.205344"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:showpageshadow="false"
inkscape:snap-bbox="false"
inkscape:bbox-nodes="true"
inkscape:window-width="2560"
inkscape:window-height="1357"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:snap-global="false" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-291.17915)">
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.99999988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 6.9101562 2.4082031 C 3.1105656 2.4082031 -5.9211895e-16 5.5081643 0 9.3027344 C 0 13.097342 3.1105656 16.197266 6.9101562 16.197266 C 8.2869348 16.197266 9.5698699 15.787508 10.650391 15.087891 L 15.162109 19.587891 L 16.636719 18.115234 L 12.214844 13.707031 C 13.214837 12.510659 13.818359 10.974238 13.818359 9.3027344 C 13.818359 5.5081643 10.709747 2.4082031 6.9101562 2.4082031 z M 6.9101562 4.9101562 C 9.3624717 4.9101562 11.324219 6.8631249 11.324219 9.3027344 C 11.324219 11.742382 9.3624717 13.695312 6.9101562 13.695312 C 4.4578408 13.695312 2.5019531 11.742382 2.5019531 9.3027344 C 2.5019531 6.8631249 4.4578408 4.9101562 6.9101562 4.9101562 z "
transform="matrix(0.26458333,0,0,0.26458333,0,291.17915)"
id="rect4524" />
<path
transform="matrix(0.99422295,0,0,0.68955299,-0.83134947,91.755588)"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.63466448;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
inkscape:transform-center-y="0.25905895"
d="m 5.6074138,294.49889 -1.0836583,-1.87695 2.1673165,0 z"
id="path4491" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="10.53333"
height="32"
viewBox="0 0 9.8749964 30"
id="svg2"
version="1.1"
inkscape:version="0.92.1 r"
sodipodi:docname="nav_edge_inter.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="8.6823304"
inkscape:cy="16.225639"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="false"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:object-nodes="true"
inkscape:window-width="2560"
inkscape:window-height="1357"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1022.3622)">
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 0,1022.3622 v 15 15 l 8,-15 z"
id="path4143"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#333333;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.9375px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 1.2910156,1022.3496 -0.82421872,0.4473 7.87890622,14.5527 -7.87890622,14.5527 0.82421872,0.4473 8.1210938,-15 z"
id="path5240"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="8.5333338"
height="32"
viewBox="0 0 8.0000001 30"
id="svg2"
version="1.1"
inkscape:version="0.92.1 r"
sodipodi:docname="nav_edge_left.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="5.3721385"
inkscape:cy="14.16429"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="false"
inkscape:bbox-nodes="false"
inkscape:snap-bbox-edge-midpoints="false"
inkscape:object-nodes="true"
inkscape:window-width="2560"
inkscape:window-height="1357"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1022.3622)">
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 0 0 L 0 32 L 8.5332031 16 L 0 0 z "
transform="matrix(0.93749998,0,0,0.93749998,0,1022.3622)"
id="rect4586" />
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 0,1022.3622 v 15 15 l 8,-15 z"
id="path4143"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="8"
height="30"
viewBox="0 0 8.0000001 30"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="nav_edge.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="5.3721385"
inkscape:cy="14.16429"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="false"
inkscape:bbox-nodes="false"
inkscape:snap-bbox-edge-midpoints="false"
inkscape:object-nodes="true"
inkscape:window-width="2560"
inkscape:window-height="1357"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1022.3622)">
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 0,1022.3622 0,15 0,15 8,-15 -8,-15 z"
id="path4143"
inkscape:connector-curvature="0" />
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 1e-8,1022.3622 7.99999999,15 0,-15 -8,0 z m 7.99999999,15 -8,15 8,0 0,-15 z"
id="rect4136"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="6"
height="9"
viewBox="0 0 1.5875 2.3812501"
version="1.1"
id="svg8"
inkscape:version="0.92.1 r"
sodipodi:docname="splitbar_handle.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="8.7681488"
inkscape:cy="-2.7929517"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:showpageshadow="false"
showguides="false"
inkscape:window-width="2560"
inkscape:window-height="1357"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid4487" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-294.61873)">
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect4485"
width="0.26458335"
height="0.26458332"
x="0.26458332"
y="294.8833" />
<rect
y="294.8833"
x="1.0583333"
height="0.26458332"
width="0.26458335"
id="rect4489"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<rect
y="295.41248"
x="0.26458329"
height="0.26458332"
width="0.26458335"
id="rect4491"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect4493"
width="0.26458335"
height="0.26458332"
x="1.0583333"
y="295.41248" />
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect4495"
width="0.26458335"
height="0.26458332"
x="0.26458332"
y="295.94165" />
<rect
y="295.94165"
x="1.0583333"
height="0.26458332"
width="0.26458335"
id="rect4497"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<rect
y="296.47079"
x="0.26458329"
height="0.26458332"
width="0.26458335"
id="rect4499"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52916664;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect4501"
width="0.26458335"
height="0.26458332"
x="1.0583333"
y="296.47079" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 B

View File

@@ -1,32 +0,0 @@
// Adds extra CSS classes "even" and "odd" to .memberdecls to allow
// striped backgrounds.
function MemberDeclsStriper () {
var counter = 0;
this.stripe = function() {
$(".memberdecls tbody").children().each(function(i) {
// reset counter at every heading -> always start with even
if ($(this).is(".heading")) {
counter = 0;
}
// add extra classes
if (counter % 2 == 1) {
$(this).addClass("odd");
}
else {
$(this).addClass("even");
}
// advance counter at every separator
// this is the only way to reliably detect which table rows belong together
if ($(this).is('[class^="separator"]')) {
counter++;
}
});
}
}
// execute the function
$(document).ready(new MemberDeclsStriper().stripe);

File diff suppressed because it is too large Load Diff

View File

@@ -86,22 +86,24 @@ static void add_cmd(char *cmd)
static void usage(void)
{
fprintf(stderr,
"Usage : ssh [options] [login@]hostname\n"
"sample client - libssh-%s\n"
"Options :\n"
" -l user : log in as user\n"
" -p port : connect to port\n"
" -r : use RSA to verify host public key\n"
" -F file : parse configuration file instead of default one\n"
fprintf(
stderr,
"Usage : ssh [options] [login@]hostname\n"
"sample client - libssh-%s\n"
"Options :\n"
" -l user : log in as user\n"
" -p port : connect to port\n"
" -o option : set configuration option (e.g., -o Compression=yes)\n"
" -r : use RSA to verify host public key\n"
" -F file : parse configuration file instead of default one\n"
#ifdef WITH_PCAP
" -P file : create a pcap debugging file\n"
" -P file : create a pcap debugging file\n"
#endif
#ifndef _WIN32
" -T proxycommand : command to execute as a socket proxy\n"
" -T proxycommand : command to execute as a socket proxy\n"
#endif
"\n",
ssh_version(0));
"\n",
ssh_version(0));
exit(0);
}
@@ -353,10 +355,8 @@ static int client(ssh_session session)
}
/* Parse configuration file if specified: The command-line options will
* overwrite items loaded from configuration file */
if (config_file != NULL) {
ssh_options_parse_config(session, config_file);
} else {
ssh_options_parse_config(session, NULL);
if (ssh_options_parse_config(session, config_file) < 0) {
return -1;
}
if (ssh_connect(session)) {

View File

@@ -631,6 +631,58 @@ auth_publickey(ssh_session session,
return SSH_AUTH_DENIED;
}
static int kbdint_check_response(ssh_session session)
{
int count, cmp;
const char *answer = NULL;
count = ssh_userauth_kbdint_getnanswers(session);
if (count != 2) {
return 0;
}
answer = ssh_userauth_kbdint_getanswer(session, 0);
cmp = strcasecmp("omnitrix", answer);
if (cmp != 0) {
return 0;
}
answer = ssh_userauth_kbdint_getanswer(session, 1);
cmp = strcmp("000", answer);
if (cmp != 0) {
return 0;
}
return 1;
}
static int
auth_kbdint(ssh_message message, ssh_session session, void *userdata)
{
struct session_data_struct *sdata = (struct session_data_struct *)userdata;
const char *name = "\n\nKeyboard-Interactive Fancy Authentication\n";
const char *instruction = "Most powerful weapon in the galaxy";
const char *prompts[2] = {"Name of the weapon: ", "Destruct Code: "};
char echo[] = {1, 0};
if (!ssh_message_auth_kbdint_is_response(message)) {
printf("User %s wants to auth with kbdint\n",
ssh_message_auth_user(message));
ssh_message_auth_interactive_request(message,
name,
instruction,
2,
prompts,
echo);
return SSH_AUTH_INFO;
} else {
if (kbdint_check_response(session)) {
sdata->authenticated = 1;
return SSH_AUTH_SUCCESS;
}
return SSH_AUTH_DENIED;
}
}
static ssh_channel
channel_open(ssh_session session, void *userdata)
{
@@ -720,14 +772,15 @@ handle_session(ssh_event event, ssh_session session)
struct ssh_server_callbacks_struct server_cb = {
.userdata = &sdata,
.auth_password_function = auth_password,
.auth_kbdint_function = auth_kbdint,
.channel_open_request_session_function = channel_open,
};
if (authorizedkeys[0]) {
server_cb.auth_pubkey_function = auth_publickey;
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY);
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_INTERACTIVE);
} else
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_INTERACTIVE);
ssh_callbacks_init(&server_cb);
ssh_callbacks_init(&channel_cb);

View File

@@ -52,42 +52,45 @@ typedef struct ssh_kbdint_struct* ssh_kbdint;
ssh_kbdint ssh_kbdint_new(void);
void ssh_kbdint_clean(ssh_kbdint kbd);
void ssh_kbdint_free(ssh_kbdint kbd);
int ssh_userauth_gssapi_keyex(ssh_session session);
/** @internal
* States of authentication in the client-side. They describe
* what was the last response from the server
*/
enum ssh_auth_state_e {
/** No authentication asked */
SSH_AUTH_STATE_NONE=0,
/** Last authentication response was a partial success */
SSH_AUTH_STATE_PARTIAL,
/** Last authentication response was a success */
SSH_AUTH_STATE_SUCCESS,
/** Last authentication response was failed */
SSH_AUTH_STATE_FAILED,
/** Last authentication was erroneous */
SSH_AUTH_STATE_ERROR,
/** Last state was a keyboard-interactive ask for info */
SSH_AUTH_STATE_INFO,
/** Last state was a public key accepted for authentication */
SSH_AUTH_STATE_PK_OK,
/** We asked for a keyboard-interactive authentication */
SSH_AUTH_STATE_KBDINT_SENT,
/** We have sent an userauth request with gssapi-with-mic */
SSH_AUTH_STATE_GSSAPI_REQUEST_SENT,
/** We are exchanging tokens until authentication */
SSH_AUTH_STATE_GSSAPI_TOKEN,
/** We have sent the MIC and expecting to be authenticated */
SSH_AUTH_STATE_GSSAPI_MIC_SENT,
/** We have offered a pubkey to check if it is supported */
SSH_AUTH_STATE_PUBKEY_OFFER_SENT,
/** We have sent pubkey and signature expecting to be authenticated */
SSH_AUTH_STATE_PUBKEY_AUTH_SENT,
/** We have sent a password expecting to be authenticated */
SSH_AUTH_STATE_PASSWORD_AUTH_SENT,
/** We have sent a request without auth information (method 'none') */
SSH_AUTH_STATE_AUTH_NONE_SENT,
/** No authentication asked */
SSH_AUTH_STATE_NONE = 0,
/** Last authentication response was a partial success */
SSH_AUTH_STATE_PARTIAL,
/** Last authentication response was a success */
SSH_AUTH_STATE_SUCCESS,
/** Last authentication response was failed */
SSH_AUTH_STATE_FAILED,
/** Last authentication was erroneous */
SSH_AUTH_STATE_ERROR,
/** Last state was a keyboard-interactive ask for info */
SSH_AUTH_STATE_INFO,
/** Last state was a public key accepted for authentication */
SSH_AUTH_STATE_PK_OK,
/** We asked for a keyboard-interactive authentication */
SSH_AUTH_STATE_KBDINT_SENT,
/** We have sent an userauth request with gssapi-with-mic */
SSH_AUTH_STATE_GSSAPI_REQUEST_SENT,
/** We are exchanging tokens until authentication */
SSH_AUTH_STATE_GSSAPI_TOKEN,
/** We have sent the MIC and expecting to be authenticated */
SSH_AUTH_STATE_GSSAPI_MIC_SENT,
/** We have offered a pubkey to check if it is supported */
SSH_AUTH_STATE_PUBKEY_OFFER_SENT,
/** We have sent pubkey and signature expecting to be authenticated */
SSH_AUTH_STATE_PUBKEY_AUTH_SENT,
/** We have sent a password expecting to be authenticated */
SSH_AUTH_STATE_PASSWORD_AUTH_SENT,
/** We have sent a request without auth information (method 'none') */
SSH_AUTH_STATE_AUTH_NONE_SENT,
/** We have sent the MIC and expecting to be authenticated */
SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT,
};
/** @internal

View File

@@ -54,6 +54,8 @@ struct ssh_bind_struct {
char *pubkey_accepted_key_types;
char* moduli_file;
int rsa_min_size;
bool gssapi_key_exchange;
char *gssapi_key_exchange_algs;
};
struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct

View File

@@ -220,36 +220,41 @@ typedef struct ssh_callbacks_struct *ssh_callbacks;
* @param user User that wants to authenticate
* @param password Password used for authentication
* @param userdata Userdata to be passed to the callback function.
* @returns SSH_AUTH_SUCCESS Authentication is accepted.
* @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed.
* @returns SSH_AUTH_DENIED Authentication failed.
* @returns `SSH_AUTH_SUCCESS` Authentication is accepted.
* @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means
* are needed.
* @returns `SSH_AUTH_DENIED` Authentication failed.
*/
typedef int (*ssh_auth_password_callback) (ssh_session session, const char *user, const char *password,
void *userdata);
/**
* @brief SSH authentication callback. Tries to authenticates user with the "none" method
* which is anonymous or passwordless.
* @brief SSH authentication callback. Tries to authenticates user with the
* "none" method which is anonymous or passwordless.
* @param session Current session handler
* @param user User that wants to authenticate
* @param userdata Userdata to be passed to the callback function.
* @returns SSH_AUTH_SUCCESS Authentication is accepted.
* @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed.
* @returns SSH_AUTH_DENIED Authentication failed.
* @returns `SSH_AUTH_SUCCESS` Authentication is accepted.
* @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means
* are needed.
* @returns `SSH_AUTH_DENIED` Authentication failed.
*/
typedef int (*ssh_auth_none_callback) (ssh_session session, const char *user, void *userdata);
/**
* @brief SSH authentication callback. Tries to authenticates user with the "gssapi-with-mic" method
* @brief SSH authentication callback. Tries to authenticates user with the
* "gssapi-with-mic" method
* @param session Current session handler
* @param user Username of the user (can be spoofed)
* @param principal Authenticated principal of the user, including realm.
* @param userdata Userdata to be passed to the callback function.
* @returns SSH_AUTH_SUCCESS Authentication is accepted.
* @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed.
* @returns SSH_AUTH_DENIED Authentication failed.
* @warning Implementations should verify that parameter user matches in some way the principal.
* user and principal can be different. Only the latter is guaranteed to be safe.
* @returns `SSH_AUTH_SUCCESS` Authentication is accepted.
* @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means
* are needed.
* @returns `SSH_AUTH_DENIED` Authentication failed.
* @warning Implementations should verify that parameter user matches in some
* way the principal. user and principal can be different. Only the latter is
* guaranteed to be safe.
*/
typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *user, const char *principal,
void *userdata);
@@ -259,17 +264,29 @@ typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *us
* @param session Current session handler
* @param user User that wants to authenticate
* @param pubkey public key used for authentication
* @param signature_state SSH_PUBLICKEY_STATE_NONE if the key is not signed (simple public key probe),
* SSH_PUBLICKEY_STATE_VALID if the signature is valid. Others values should be
* replied with a SSH_AUTH_DENIED.
* @param signature_state `SSH_PUBLICKEY_STATE_NONE` if the key is not signed
* (simple public key probe), `SSH_PUBLICKEY_STATE_VALID` if the signature is
* valid. Others values should be replied with a `SSH_AUTH_DENIED`.
* @param userdata Userdata to be passed to the callback function.
* @returns SSH_AUTH_SUCCESS Authentication is accepted.
* @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed.
* @returns SSH_AUTH_DENIED Authentication failed.
* @returns `SSH_AUTH_SUCCESS` Authentication is accepted.
* @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means
* are needed.
* @returns `SSH_AUTH_DENIED` Authentication failed.
*/
typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user, struct ssh_key_struct *pubkey,
char signature_state, void *userdata);
/**
* @brief SSH authentication callback. Tries to authenticates user with the "keyboard-interactive" method
* @param message Current message
* @param session Current session handler
* @param userdata Userdata to be passed to the callback function.
* @returns SSH_AUTH_SUCCESS Authentication is accepted.
* @returns SSH_AUTH_INFO More info required for authentication.
* @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed.
* @returns SSH_AUTH_DENIED Authentication failed.
*/
typedef int (*ssh_auth_kbdint_callback) (ssh_message message, ssh_session session, void *userdata);
/**
* @brief Handles an SSH service request
@@ -279,7 +296,6 @@ typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user,
* @returns 0 if the request is to be allowed
* @returns -1 if the request should not be allowed
*/
typedef int (*ssh_service_request_callback) (ssh_session session, const char *service, void *userdata);
/**
@@ -292,7 +308,7 @@ typedef int (*ssh_service_request_callback) (ssh_session session, const char *se
*/
typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session session, void *userdata);
/*
/**
* @brief handle the beginning of a GSSAPI authentication, server side.
* Callback should select the oid and also acquire the server credential.
* @param session current session handler
@@ -307,29 +323,29 @@ typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session se
typedef ssh_string (*ssh_gssapi_select_oid_callback) (ssh_session session, const char *user,
int n_oid, ssh_string *oids, void *userdata);
/*
/**
* @brief handle the negotiation of a security context, server side.
* @param session current session handler
* @param[in] input_token input token provided by client
* @param[out] output_token output of the gssapi accept_sec_context method,
* NULL after completion.
* @returns SSH_OK if the token was generated correctly or accept_sec_context
* @returns `SSH_OK` if the token was generated correctly or accept_sec_context
* returned GSS_S_COMPLETE
* @returns SSH_ERROR in case of error
* @returns `SSH_ERROR` in case of error
* @warning It is not necessary to fill this callback in if libssh is linked
* with libgssapi.
*/
typedef int (*ssh_gssapi_accept_sec_ctx_callback) (ssh_session session,
ssh_string input_token, ssh_string *output_token, void *userdata);
/*
/**
* @brief Verify and authenticates a MIC, server side.
* @param session current session handler
* @param[in] mic input mic to be verified provided by client
* @param[in] mic_buffer buffer of data to be signed.
* @param[in] mic_buffer_size size of mic_buffer
* @returns SSH_OK if the MIC was authenticated correctly
* @returns SSH_ERROR in case of error
* @returns `SSH_OK` if the MIC was authenticated correctly
* @returns `SSH_ERROR` in case of error
* @warning It is not necessary to fill this callback in if libssh is linked
* with libgssapi.
*/
@@ -405,7 +421,7 @@ struct ssh_server_callbacks_struct {
/** This function will be called when a gssapi token comes in.
*/
ssh_gssapi_accept_sec_ctx_callback gssapi_accept_sec_ctx_function;
/* This function will be called when a MIC needs to be verified.
/** This function will be called when a MIC needs to be verified.
*/
ssh_gssapi_verify_mic_callback gssapi_verify_mic_function;
/**
@@ -414,6 +430,12 @@ struct ssh_server_callbacks_struct {
*/
ssh_channel_open_request_direct_tcpip_callback
channel_open_request_direct_tcpip_function;
/** This function gets called when a client tries to authenticate through
* keyboard interactive method.
*/
ssh_auth_kbdint_callback auth_kbdint_function;
};
typedef struct ssh_server_callbacks_struct *ssh_server_callbacks;
@@ -439,7 +461,7 @@ typedef struct ssh_server_callbacks_struct *ssh_server_callbacks;
*
* @param cb The callback structure itself.
*
* @return SSH_OK on success, SSH_ERROR on error.
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*/
LIBSSH_API int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb);
@@ -570,14 +592,17 @@ typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks;
} \
} while(0)
/** @brief Prototype for a packet callback, to be called when a new packet arrives
/** @brief Prototype for a packet callback, to be called when a new packet
* arrives
* @param session The current session of the packet
* @param type packet type (see ssh2.h)
* @param packet buffer containing the packet, excluding size, type and padding fields
* @param packet buffer containing the packet, excluding size, type and padding
* fields
* @param user user argument to the callback
* and are called each time a packet shows up
* @returns SSH_PACKET_USED Packet was parsed and used
* @returns SSH_PACKET_NOT_USED Packet was not used or understood, processing must continue
* @returns `SSH_PACKET_USED` Packet was parsed and used
* @returns `SSH_PACKET_NOT_USED` Packet was not used or understood, processing
* must continue
*/
typedef int (*ssh_packet_callback) (ssh_session session, uint8_t type, ssh_buffer packet, void *user);
@@ -636,7 +661,7 @@ typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks;
*
* @param cb The callback structure itself.
*
* @return SSH_OK on success, SSH_ERROR on error.
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*/
LIBSSH_API int ssh_set_callbacks(ssh_session session, ssh_callbacks cb);
@@ -988,7 +1013,7 @@ typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks;
*
* @param cb The callback structure itself.
*
* @return SSH_OK on success, SSH_ERROR on error.
* @return `SSH_OK` on success, `SSH_ERROR` on error.
* @warning this function will not replace existing callbacks but set the
* new one atop of them.
*/
@@ -1007,7 +1032,7 @@ LIBSSH_API int ssh_set_channel_callbacks(ssh_channel channel,
*
* @param cb The callback structure itself.
*
* @return SSH_OK on success, SSH_ERROR on error.
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*
* @see ssh_set_channel_callbacks
*/
@@ -1024,7 +1049,7 @@ LIBSSH_API int ssh_add_channel_callbacks(ssh_channel channel,
*
* @param cb The callback structure to remove
*
* @returns SSH_OK on success, SSH_ERROR on error.
* @returns `SSH_OK` on success, `SSH_ERROR` on error.
*/
LIBSSH_API int ssh_remove_channel_callbacks(ssh_channel channel,
ssh_channel_callbacks cb);
@@ -1057,7 +1082,7 @@ struct ssh_threads_callbacks_struct {
* @param[in] cb A pointer to a ssh_threads_callbacks_struct structure, which
* contains the different callbacks to be set.
*
* @returns Always returns SSH_OK.
* @returns Always returns `SSH_OK`.
*
* @see ssh_threads_callbacks_struct
* @see SSH_THREADS_PTHREAD

View File

@@ -24,6 +24,7 @@
#ifndef LIBSSH_CONFIG_H_
#define LIBSSH_CONFIG_H_
#include "libssh/libssh.h"
enum ssh_config_opcode_e {
/* Unknown opcode */
@@ -67,7 +68,13 @@ enum ssh_config_opcode_e {
SOC_CONTROLPATH,
SOC_CERTIFICATE,
SOC_REQUIRED_RSA_SIZE,
SOC_ADDRESSFAMILY,
SOC_GSSAPIKEYEXCHANGE,
SOC_GSSAPIKEXALGORITHMS,
SOC_MAX /* Keep this one last in the list */
};
enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword);
int ssh_config_parse_line_cli(ssh_session session, const char *line);
#endif /* LIBSSH_CONFIG_H_ */

View File

@@ -50,9 +50,6 @@
#include "libssh/ecdh.h"
#include "libssh/kex.h"
#include "libssh/sntrup761.h"
#ifdef HAVE_MLKEM
#include "libssh/mlkem768.h"
#endif
#define DIGEST_MAX_LEN 64
@@ -90,10 +87,22 @@ enum ssh_key_exchange_e {
SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM,
/* sntrup761x25519-sha512 */
SSH_KEX_SNTRUP761X25519_SHA512,
#ifdef HAVE_MLKEM
/* mlkem768x25519-sha256 */
SSH_KEX_MLKEM768X25519_SHA256,
#endif /* HAVE_MLKEM */
/* mlkem768nistp256-sha256 */
SSH_KEX_MLKEM768NISTP256_SHA256,
#ifdef HAVE_MLKEM1024
/* mlkem1024nistp384-sha384 */
SSH_KEX_MLKEM1024NISTP384_SHA384,
#endif /* HAVE_MLKEM1024 */
/* gss-group14-sha256-* */
SSH_GSS_KEX_DH_GROUP14_SHA256,
/* gss-group16-sha512-* */
SSH_GSS_KEX_DH_GROUP16_SHA512,
/* gss-nistp256-sha256-* */
SSH_GSS_KEX_ECDH_NISTP256_SHA256,
/* gss-curve25519-sha256-* */
SSH_GSS_KEX_CURVE25519_SHA256,
};
enum ssh_cipher_e {
@@ -117,6 +126,9 @@ struct dh_ctx;
struct ssh_crypto_struct {
bignum shared_secret;
ssh_string hybrid_client_init;
ssh_string hybrid_server_reply;
ssh_string hybrid_shared_secret;
struct dh_ctx *dh_ctx;
#ifdef WITH_GEX
size_t dh_pmin; size_t dh_pn; size_t dh_pmax; /* preferred group parameters */
@@ -147,11 +159,14 @@ struct ssh_crypto_struct {
ssh_curve25519_pubkey curve25519_client_pubkey;
ssh_curve25519_pubkey curve25519_server_pubkey;
#endif
#ifdef HAVE_MLKEM
ssh_mlkem768_privkey mlkem768_client_privkey;
ssh_mlkem768_pubkey mlkem768_client_pubkey;
ssh_mlkem768_ciphertext mlkem768_ciphertext;
#ifdef HAVE_OPENSSL_MLKEM
EVP_PKEY *mlkem_privkey;
#else
unsigned char *mlkem_privkey;
size_t mlkem_privkey_len;
#endif
ssh_string mlkem_client_pubkey;
ssh_string mlkem_ciphertext;
#ifdef HAVE_SNTRUP761
ssh_sntrup761_privkey sntrup761_privkey;
ssh_sntrup761_pubkey sntrup761_client_pubkey;

View File

@@ -53,6 +53,7 @@ typedef unsigned char ssh_curve25519_privkey[CURVE25519_PRIVKEY_SIZE];
int ssh_curve25519_init(ssh_session session);
int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k);
int ssh_curve25519_create_k(ssh_session session, ssh_curve25519_pubkey k);
int ssh_curve25519_build_k(ssh_session session);
int ssh_client_curve25519_init(ssh_session session);
void ssh_client_curve25519_remove_callbacks(ssh_session session);

View File

@@ -48,6 +48,7 @@ extern "C" {
extern struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks;
/* Backend-specific functions. */
int ssh_ecdh_init(ssh_session session);
int ssh_client_ecdh_init(ssh_session session);
void ssh_client_ecdh_remove_callbacks(ssh_session session);
int ecdh_build_k(ssh_session session);

View File

@@ -29,12 +29,41 @@
* @{
*/
/** @internal
* @brief ED25519 public key.
* Ed25519 public key consist of 32 bytes.
*/
#define ED25519_PK_LEN 32
/** @internal
* @brief ED25519 secret key.
* Ed25519 secret key consist of 64 bytes.
*/
#define ED25519_SK_LEN 64
/** @internal
* @brief ED25519 signature.
* Ed25519 signatures consist of 64 bytes.
*/
#define ED25519_SIG_LEN 64
/** @internal
* @brief ED25519 public key.
* The public key consists of 32 bytes and can be used for signature
* verification.
*/
typedef uint8_t ed25519_pubkey[ED25519_PK_LEN];
/** @internal
* @brief ED25519 private key.
* The private key consists of 64 bytes and should be kept secret.
*/
typedef uint8_t ed25519_privkey[ED25519_SK_LEN];
/** @internal
* @brief ED25519 signature.
* Ed25519 signatures consists of 64 bytes.
*/
typedef uint8_t ed25519_signature[ED25519_SIG_LEN];
#ifdef __cplusplus
@@ -46,7 +75,7 @@ extern "C" {
* @param[out] pk generated public key
* @param[out] sk generated secret key
* @return 0 on success, -1 on error.
* */
*/
int crypto_sign_ed25519_keypair(ed25519_pubkey pk, ed25519_privkey sk);
/** @internal

View File

@@ -29,6 +29,11 @@
/* all OID begin with the tag identifier + length */
#define SSH_OID_TAG 06
#define GSSAPI_KEY_EXCHANGE_SUPPORTED "gss-group14-sha256-," \
"gss-group16-sha512-," \
"gss-nistp256-sha256-," \
"gss-curve25519-sha256-"
typedef struct ssh_gssapi_struct *ssh_gssapi;
#ifdef __cplusplus
@@ -44,14 +49,12 @@ enum ssh_gssapi_state_e {
struct ssh_gssapi_struct{
enum ssh_gssapi_state_e state; /* current state */
struct gss_OID_desc_struct mech; /* mechanism being elected for auth */
gss_cred_id_t server_creds; /* credentials of server */
gss_cred_id_t client_creds; /* creds delegated by the client */
gss_ctx_id_t ctx; /* the authentication context */
gss_name_t client_name; /* Identity of the client */
char *user; /* username of client */
char *canonic_user; /* canonic form of the client's username */
char *service; /* name of the service */
struct {
gss_name_t server_name; /* identity of server */
OM_uint32 flags; /* flags used for init context */
@@ -65,6 +68,7 @@ struct ssh_gssapi_struct{
int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids);
SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server);
SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic);
int ssh_gssapi_server_oids(gss_OID_set *selected);
#endif /* WITH_SERVER */
SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token);
@@ -76,7 +80,20 @@ int ssh_gssapi_init(ssh_session session);
void ssh_gssapi_log_error(int verb, const char *msg_a, int maj_stat, int min_stat);
int ssh_gssapi_auth_mic(ssh_session session);
void ssh_gssapi_free(ssh_session session);
int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids);
char *ssh_gssapi_name_to_char(gss_name_t name);
int ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host);
OM_uint32 ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi,
gss_buffer_desc *input_token,
gss_buffer_desc *output_token,
OM_uint32 *ret_flags);
char *ssh_gssapi_oid_hash(ssh_string oid);
char *ssh_gssapi_kex_mechs(ssh_session session);
int ssh_gssapi_check_client_config(ssh_session session);
ssh_buffer ssh_gssapi_build_mic(ssh_session session, const char *context);
int ssh_gssapi_auth_keyex_mic(ssh_session session,
gss_buffer_desc *mic_token_buf);
#ifdef __cplusplus
}

View File

@@ -0,0 +1,51 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Sahana Prasad <sahana@redhat.com>
* Author: Pavol Žáčik <pzacik@redhat.com>
* Author: Claude (Anthropic)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HYBRID_MLKEM_H_
#define HYBRID_MLKEM_H_
#include "libssh/mlkem.h"
#include "libssh/wrapper.h"
#include "config.h"
#ifdef __cplusplus
extern "C" {
#endif
#define NISTP256_SHARED_SECRET_SIZE 32
#define NISTP384_SHARED_SECRET_SIZE 48
int ssh_client_hybrid_mlkem_init(ssh_session session);
void ssh_client_hybrid_mlkem_remove_callbacks(ssh_session session);
#ifdef WITH_SERVER
void ssh_server_hybrid_mlkem_init(ssh_session session);
#endif /* WITH_SERVER */
#ifdef __cplusplus
}
#endif
#endif /* HYBRID_MLKEM_H_ */

36
include/libssh/kex-gss.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* kex-gss.h - GSSAPI key exchange
*
* This file is part of the SSH Library
*
* Copyright (c) 2024 by Gauravsingh Sisodia <xaerru@gmail.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#ifndef KEX_GSS_H_
#define KEX_GSS_H_
#include "config.h"
#ifdef WITH_GSSAPI
int ssh_client_gss_kex_init(ssh_session session);
void ssh_server_gss_kex_init(ssh_session session);
int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet);
void ssh_client_gss_kex_remove_callbacks(ssh_session session);
void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session);
#endif /* WITH_GSSAPI */
#endif /* KEX_GSS_H_ */

View File

@@ -31,6 +31,9 @@ struct ssh_kex_struct {
char *methods[SSH_KEX_METHODS];
};
/* crypto.h needs ssh_kex_struct so it is included below the struct definition */
#include "libssh/crypto.h"
#ifdef __cplusplus
extern "C" {
#endif
@@ -64,6 +67,7 @@ int ssh_make_sessionid(ssh_session session);
int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie);
int ssh_hashbufout_add_cookie(ssh_session session);
int ssh_generate_session_keys(ssh_session session);
bool ssh_kex_is_gss(struct ssh_crypto_struct *crypto);
#ifdef __cplusplus
}

View File

@@ -98,9 +98,9 @@ int ssh_gcry_rand_range(bignum rnd, bignum max);
#define bignum_rand_range(rnd, max) ssh_gcry_rand_range(rnd, max);
#define bignum_dup(orig, dest) do { \
if (*(dest) == NULL) { \
*(dest) = gcry_mpi_copy(orig); \
*(dest) = gcry_mpi_copy((const gcry_mpi_t)orig); \
} else { \
gcry_mpi_set(*(dest), orig); \
gcry_mpi_set(*(dest), (const gcry_mpi_t)orig); \
} \
} while(0)
/* Helper functions for data conversions. */

View File

@@ -1,7 +1,7 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2003-2025 by Aris Adamantiadis and the libssh team
* Copyright (c) 2003-2026 by Aris Adamantiadis and the libssh team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -152,13 +152,14 @@ enum ssh_auth_e {
};
/* auth flags */
#define SSH_AUTH_METHOD_UNKNOWN 0x0000u
#define SSH_AUTH_METHOD_NONE 0x0001u
#define SSH_AUTH_METHOD_PASSWORD 0x0002u
#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u
#define SSH_AUTH_METHOD_HOSTBASED 0x0008u
#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u
#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u
#define SSH_AUTH_METHOD_UNKNOWN 0x0000u
#define SSH_AUTH_METHOD_NONE 0x0001u
#define SSH_AUTH_METHOD_PASSWORD 0x0002u
#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u
#define SSH_AUTH_METHOD_HOSTBASED 0x0008u
#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u
#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u
#define SSH_AUTH_METHOD_GSSAPI_KEYEX 0x0040u
/* messages */
enum ssh_requests_e {
@@ -371,6 +372,12 @@ enum ssh_control_master_options_e {
SSH_CONTROL_MASTER_AUTOASK
};
enum ssh_address_family_options_e {
SSH_ADDRESS_FAMILY_ANY,
SSH_ADDRESS_FAMILY_INET,
SSH_ADDRESS_FAMILY_INET6
};
enum ssh_options_e {
SSH_OPTIONS_HOST,
SSH_OPTIONS_PORT,
@@ -422,6 +429,9 @@ enum ssh_options_e {
SSH_OPTIONS_PROXYJUMP,
SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND,
SSH_OPTIONS_PKI_CONTEXT,
SSH_OPTIONS_ADDRESS_FAMILY,
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE,
SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS,
};
enum {

73
include/libssh/mlkem.h Normal file
View File

@@ -0,0 +1,73 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Pavol Žáčik <pzacik@redhat.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#ifndef MLKEM_H_
#define MLKEM_H_
#include "libssh/crypto.h"
#include "libssh/libssh.h"
#include "libssh/session.h"
#include "config.h"
#ifdef __cplusplus
extern "C" {
#endif
struct mlkem_type_info {
size_t pubkey_size;
size_t ciphertext_size;
#ifdef HAVE_GCRYPT_MLKEM
size_t privkey_size;
enum gcry_kem_algos alg;
#elif defined(HAVE_OPENSSL_MLKEM)
const char *name;
#else
size_t privkey_size;
#endif
};
extern const struct mlkem_type_info MLKEM768_INFO;
#ifdef HAVE_MLKEM1024
extern const struct mlkem_type_info MLKEM1024_INFO;
#endif
#define MLKEM_SHARED_SECRET_SIZE 32
typedef unsigned char ssh_mlkem_shared_secret[MLKEM_SHARED_SECRET_SIZE];
const struct mlkem_type_info *
kex_type_to_mlkem_info(enum ssh_key_exchange_e kex_type);
int ssh_mlkem_init(ssh_session session);
int ssh_mlkem_encapsulate(ssh_session session,
ssh_mlkem_shared_secret shared_secret);
int ssh_mlkem_decapsulate(const ssh_session session,
ssh_mlkem_shared_secret shared_secret);
#ifdef __cplusplus
}
#endif
#endif /* MLKEM_H_ */

View File

@@ -1,63 +0,0 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Sahana Prasad <sahana@redhat.com>
* Author: Claude (Anthropic)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef MLKEM768_H_
#define MLKEM768_H_
#include "config.h"
/* ML-KEM768 key and ciphertext sizes as defined in FIPS 203 */
#define MLKEM768_PUBLICKEY_SIZE 1184
#define MLKEM768_SECRETKEY_SIZE 2400
#define MLKEM768_CIPHERTEXT_SIZE 1088
#define MLKEM768_SHARED_SECRET_SIZE 32
/* Hybrid ML-KEM768x25519 combined sizes */
#define MLKEM768X25519_CLIENT_PUBKEY_SIZE \
(MLKEM768_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE)
#define MLKEM768X25519_SERVER_RESPONSE_SIZE \
(MLKEM768_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE)
#define MLKEM768X25519_SHARED_SECRET_SIZE \
(MLKEM768_SHARED_SECRET_SIZE + CURVE25519_PUBKEY_SIZE)
typedef unsigned char ssh_mlkem768_pubkey[MLKEM768_PUBLICKEY_SIZE];
typedef unsigned char ssh_mlkem768_privkey[MLKEM768_SECRETKEY_SIZE];
typedef unsigned char ssh_mlkem768_ciphertext[MLKEM768_CIPHERTEXT_SIZE];
#ifdef __cplusplus
extern "C" {
#endif
/* ML-KEM768x25519 key exchange functions */
int ssh_client_mlkem768x25519_init(ssh_session session);
void ssh_client_mlkem768x25519_remove_callbacks(ssh_session session);
#ifdef WITH_SERVER
void ssh_server_mlkem768x25519_init(ssh_session session);
#endif /* WITH_SERVER */
#ifdef __cplusplus
}
#endif
#endif /* MLKEM768_H_ */

View File

@@ -0,0 +1,127 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Jakub Jelen <jjelen@redhat.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#ifndef MLKEM_NATIVE_H_
#define MLKEM_NATIVE_H_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
A monomorphic instance of libcrux_ml_kem.types.MlKemPrivateKey
with const generics
- $2400size_t
*/
typedef struct libcrux_ml_kem_types_MlKemPrivateKey_d9_s {
uint8_t value[2400U];
} libcrux_ml_kem_types_MlKemPrivateKey_d9;
/**
A monomorphic instance of libcrux_ml_kem.types.MlKemPublicKey
with const generics
- $1184size_t
*/
typedef struct libcrux_ml_kem_types_MlKemPublicKey_30_s {
uint8_t value[1184U];
} libcrux_ml_kem_types_MlKemPublicKey_30;
typedef struct libcrux_ml_kem_mlkem768_MlKem768KeyPair_s {
libcrux_ml_kem_types_MlKemPrivateKey_d9 sk;
libcrux_ml_kem_types_MlKemPublicKey_30 pk;
} libcrux_ml_kem_mlkem768_MlKem768KeyPair;
typedef struct libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s {
uint8_t value[1088U];
} libcrux_ml_kem_mlkem768_MlKem768Ciphertext;
/**
A monomorphic instance of K.
with types libcrux_ml_kem_types_MlKemCiphertext[[$1088size_t]],
uint8_t[32size_t]
*/
typedef struct tuple_c2_s {
libcrux_ml_kem_mlkem768_MlKem768Ciphertext fst;
uint8_t snd[32U];
} tuple_c2;
/**
Generate ML-KEM 768 Key Pair
*/
libcrux_ml_kem_mlkem768_MlKem768KeyPair
libcrux_ml_kem_mlkem768_portable_generate_key_pair(uint8_t randomness[64U]);
/**
Validate a public key.
Returns `true` if valid, and `false` otherwise.
*/
bool libcrux_ml_kem_mlkem768_portable_validate_public_key(
libcrux_ml_kem_types_MlKemPublicKey_30 *public_key);
/**
Encapsulate ML-KEM 768
Generates an ([`MlKem768Ciphertext`], [`MlKemSharedSecret`]) tuple.
The input is a reference to an [`MlKem768PublicKey`] and [`SHARED_SECRET_SIZE`]
bytes of `randomness`.
*/
tuple_c2 libcrux_ml_kem_mlkem768_portable_encapsulate(
libcrux_ml_kem_types_MlKemPublicKey_30 *public_key,
uint8_t randomness[32U]);
/**
Decapsulate ML-KEM 768
Generates an [`MlKemSharedSecret`].
The input is a reference to an [`MlKem768PrivateKey`] and an
[`MlKem768Ciphertext`].
*/
void libcrux_ml_kem_mlkem768_portable_decapsulate(
libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key,
libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext,
uint8_t ret[32U]);
/* rename some types to be a bit more ergonomic */
#define libcrux_mlkem768_keypair libcrux_ml_kem_mlkem768_MlKem768KeyPair_s
#define libcrux_mlkem768_pk libcrux_ml_kem_types_MlKemPublicKey_30_s
#define libcrux_mlkem768_sk libcrux_ml_kem_types_MlKemPrivateKey_d9_s
#define libcrux_mlkem768_ciphertext libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s
#define libcrux_mlkem768_enc_result tuple_c2_s
/* defines for PRNG inputs */
#define LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN 64U
#define LIBCRUX_ML_KEM_ENC_PRNG_LEN 32
#ifdef __cplusplus
}
#endif
#endif /* MLKEM_NATIVE_H_ */

View File

@@ -365,9 +365,29 @@ int ssh_connector_remove_event(ssh_connector connector);
/** Get the size of an array */
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
#ifndef HAVE_EXPLICIT_BZERO
void explicit_bzero(void *s, size_t n);
#endif /* !HAVE_EXPLICIT_BZERO */
/** Securely zero memory in a way that won't be optimized away */
#if defined(HAVE_MEMSET_EXPLICIT)
#define ssh_burn(ptr, len) memset_explicit((ptr), '\0', (len))
#elif defined(HAVE_EXPLICIT_BZERO)
#define ssh_burn(ptr, len) explicit_bzero((ptr), (len))
#elif defined(HAVE_MEMSET_S)
#define ssh_burn(ptr, len) memset_s((ptr), (len), '\0', (len))
#elif defined(HAVE_SECURE_ZERO_MEMORY)
#define ssh_burn(ptr, len) SecureZeroMemory((ptr), (len))
#else
#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION)
#define ssh_burn(ptr, len) \
do { \
memset((ptr), '\0', (len)); \
__asm__ volatile("" : : "g"(ptr) : "memory"); \
} while (0)
#else
#define ssh_burn(ptr, len) \
do { \
memset((ptr), '\0', (len)); \
} while (0)
#endif
#endif
void burn_free(void *ptr, size_t len);

View File

@@ -59,6 +59,8 @@ enum ssh_bind_options_e {
SSH_BIND_OPTIONS_MODULI,
SSH_BIND_OPTIONS_RSA_MIN_SIZE,
SSH_BIND_OPTIONS_IMPORT_KEY_STR,
SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE,
SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS,
};
typedef struct ssh_bind_struct* ssh_bind;
@@ -118,7 +120,7 @@ LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o);
*
* @param[in] userdata A pointer to private data to pass to the callbacks.
*
* @return SSH_OK on success, SSH_ERROR if an error occurred.
* @return `SSH_OK` on success, `SSH_ERROR` if an error occurred.
*
* @code
* struct ssh_callbacks_struct cb = {
@@ -172,7 +174,7 @@ LIBSSH_API void ssh_bind_fd_toaccept(ssh_bind ssh_bind_o);
* @param ssh_bind_o The ssh server bind to accept a connection.
* @param session A preallocated ssh session
* @see ssh_new
* @return SSH_OK when a connection is established
* @return `SSH_OK` when a connection is established
*/
LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session);
@@ -186,7 +188,7 @@ LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session);
* inbound connection
* @see ssh_new
* @see ssh_bind_accept
* @return SSH_OK when a connection is established
* @return `SSH_OK` when a connection is established
*/
LIBSSH_API int ssh_bind_accept_fd(ssh_bind ssh_bind_o, ssh_session session,
socket_t fd);
@@ -198,7 +200,7 @@ LIBSSH_API ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session);
*
* @param session A connected ssh session
* @see ssh_bind_accept
* @return SSH_OK if the key exchange was successful
* @return `SSH_OK` if the key exchange was successful
*/
LIBSSH_API int ssh_handle_key_exchange(ssh_session session);
@@ -215,9 +217,8 @@ LIBSSH_API int ssh_handle_key_exchange(ssh_session session);
* @see ssh_handle_key_exchange
* @see ssh_options_set
*
* @return SSH_OK if initialization succeeds.
* @return `SSH_OK` if initialization succeeds.
*/
LIBSSH_API int ssh_server_init_kex(ssh_session session);
/**
@@ -246,6 +247,7 @@ LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o);
* SSH_AUTH_METHOD_HOSTBASED
* SSH_AUTH_METHOD_INTERACTIVE
* SSH_AUTH_METHOD_GSSAPI_MIC
* SSH_AUTH_METHOD_GSSAPI_KEYEX
*/
LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods);
@@ -257,7 +259,7 @@ LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods);
*
* @param[in] banner The server's banner.
*
* @return SSH_OK on success, SSH_ERROR on error.
* @return `SSH_OK` on success, `SSH_ERROR` on error.
*/
LIBSSH_API int ssh_send_issue_banner(ssh_session session, const ssh_string banner);

View File

@@ -58,16 +58,17 @@ enum ssh_dh_state_e {
};
enum ssh_pending_call_e {
SSH_PENDING_CALL_NONE = 0,
SSH_PENDING_CALL_CONNECT,
SSH_PENDING_CALL_AUTH_NONE,
SSH_PENDING_CALL_AUTH_PASSWORD,
SSH_PENDING_CALL_AUTH_OFFER_PUBKEY,
SSH_PENDING_CALL_AUTH_PUBKEY,
SSH_PENDING_CALL_AUTH_AGENT,
SSH_PENDING_CALL_AUTH_KBDINT_INIT,
SSH_PENDING_CALL_AUTH_KBDINT_SEND,
SSH_PENDING_CALL_AUTH_GSSAPI_MIC
SSH_PENDING_CALL_NONE = 0,
SSH_PENDING_CALL_CONNECT,
SSH_PENDING_CALL_AUTH_NONE,
SSH_PENDING_CALL_AUTH_PASSWORD,
SSH_PENDING_CALL_AUTH_OFFER_PUBKEY,
SSH_PENDING_CALL_AUTH_PUBKEY,
SSH_PENDING_CALL_AUTH_AGENT,
SSH_PENDING_CALL_AUTH_KBDINT_INIT,
SSH_PENDING_CALL_AUTH_KBDINT_SEND,
SSH_PENDING_CALL_AUTH_GSSAPI_MIC,
SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX,
};
/* libssh calls may block an undefined amount of time */
@@ -201,6 +202,8 @@ struct ssh_session_struct {
*/
bool first_kex_follows_guess_wrong;
ssh_string gssapi_key_exchange_mic;
ssh_buffer in_hashbuf;
ssh_buffer out_hashbuf;
struct ssh_crypto_struct *current_crypto;
@@ -265,6 +268,8 @@ struct ssh_session_struct {
char compressionlevel;
char *gss_server_identity;
char *gss_client_identity;
bool gssapi_key_exchange;
char *gssapi_key_exchange_algs;
int gss_delegate_creds;
int flags;
int exp_flags;
@@ -277,6 +282,7 @@ struct ssh_session_struct {
bool identities_only;
int control_master;
char *control_path;
int address_family;
} opts;
/* server options */

View File

@@ -74,6 +74,21 @@ typedef struct sftp_file_struct* sftp_file;
typedef struct sftp_message_struct* sftp_message;
typedef struct sftp_packet_struct* sftp_packet;
typedef struct sftp_request_queue_struct* sftp_request_queue;
/**
* @brief SFTP session handle.
*
* This type represents an active SFTP session associated with an SSH channel.
* It is created and destroyed via the libssh SFTP API and is internally
* managed by libssh. It is used by applications to perform SFTP operations
* such as file access and directory management.
*
* The internal structure of this type is opaque and must not be accessed
* directly by applications.
*
* @see sftp_new
* @see sftp_free
*/
typedef struct sftp_session_struct* sftp_session;
typedef struct sftp_status_message_struct* sftp_status_message;
typedef struct sftp_statvfs_struct* sftp_statvfs_t;

View File

@@ -35,10 +35,6 @@ extern "C" {
#define HAVE_SNTRUP761 1
#endif
extern void crypto_hash_sha512(unsigned char *out,
const unsigned char *in,
unsigned long long inlen);
/*
* Derived from public domain source, written by (in alphabetical order):
* - Daniel J. Bernstein

View File

@@ -24,6 +24,15 @@
#define SSH2_MSG_KEX_DH_GEX_INIT 32
#define SSH2_MSG_KEX_DH_GEX_REPLY 33
#define SSH2_MSG_KEX_DH_GEX_REQUEST 34
#define SSH2_MSG_KEXGSS_INIT 30
#define SSH2_MSG_KEXGSS_CONTINUE 31
#define SSH2_MSG_KEXGSS_COMPLETE 32
#define SSH2_MSG_KEXGSS_HOSTKEY 33
#define SSH2_MSG_KEXGSS_ERROR 34
#define SSH2_MSG_KEXGSS_GROUPREQ 40
#define SSH2_MSG_KEXGSS_GROUP 41
#define SSH2_MSG_USERAUTH_REQUEST 50
#define SSH2_MSG_USERAUTH_FAILURE 51
#define SSH2_MSG_USERAUTH_SUCCESS 52

View File

@@ -75,6 +75,7 @@ MD5CTX md5_init(void);
void md5_ctx_free(MD5CTX);
int md5_update(MD5CTX c, const void *data, size_t len);
int md5_final(unsigned char *md, MD5CTX c);
int md5(const unsigned char *digest, size_t len, unsigned char *hash);
SHACTX sha1_init(void);
void sha1_ctx_free(SHACTX);

View File

@@ -105,6 +105,7 @@ set(libssh_SRCS
error.c
getpass.c
gzip.c
hybrid_mlkem.c
init.c
kdf.c
kex.c
@@ -115,6 +116,7 @@ set(libssh_SRCS
match.c
messages.c
misc.c
mlkem.c
options.c
packet.c
packet_cb.c
@@ -195,6 +197,13 @@ if (WITH_GCRYPT)
curve25519_gcrypt.c
)
endif(HAVE_GCRYPT_CURVE25519)
if (HAVE_GCRYPT_MLKEM)
set(libssh_SRCS
${libssh_SRCS}
mlkem_gcrypt.c
)
endif (HAVE_GCRYPT_MLKEM)
elseif (WITH_MBEDTLS)
set(libssh_SRCS
${libssh_SRCS}
@@ -248,6 +257,12 @@ else (WITH_GCRYPT)
chachapoly.c
)
endif (NOT HAVE_OPENSSL_EVP_CHACHA20)
if (HAVE_OPENSSL_MLKEM)
set(libssh_SRCS
${libssh_SRCS}
mlkem_crypto.c
)
endif (HAVE_OPENSSL_MLKEM)
endif (WITH_GCRYPT)
if (WITH_SFTP)
@@ -286,6 +301,7 @@ if (WITH_GSSAPI AND GSSAPI_FOUND)
set(libssh_SRCS
${libssh_SRCS}
gssapi.c
kex-gss.c
)
endif (WITH_GSSAPI AND GSSAPI_FOUND)
@@ -299,12 +315,18 @@ if (NOT WITH_NACL)
endif()
endif (NOT WITH_NACL)
if (HAVE_MLKEM)
set(libssh_SRCS
${libssh_SRCS}
mlkem768.c
)
endif (HAVE_MLKEM)
if (NOT HAVE_MLKEM1024)
set(libssh_SRCS
${libssh_SRCS}
mlkem_native.c
external/libcrux_mlkem768_sha3.c
)
if (WITH_WERROR_DECLARATION_AFTER_STATEMENT_FLAG)
set_source_files_properties(external/libcrux_mlkem768_sha3.c
PROPERTIES
COMPILE_FLAGS -Wno-error=declaration-after-statement)
endif()
endif()
if (WITH_FIDO2)
set(libssh_SRCS

View File

@@ -32,19 +32,19 @@
#include <arpa/inet.h>
#endif
#include "libssh/priv.h"
#include "libssh/crypto.h"
#include "libssh/ssh2.h"
#include "libssh/buffer.h"
#include "libssh/agent.h"
#include "libssh/auth.h"
#include "libssh/buffer.h"
#include "libssh/crypto.h"
#include "libssh/gssapi.h"
#include "libssh/keys.h"
#include "libssh/legacy.h"
#include "libssh/misc.h"
#include "libssh/packet.h"
#include "libssh/session.h"
#include "libssh/keys.h"
#include "libssh/auth.h"
#include "libssh/pki.h"
#include "libssh/gssapi.h"
#include "libssh/legacy.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include "libssh/ssh2.h"
/**
* @defgroup libssh_auth The SSH authentication functions
@@ -88,6 +88,7 @@ static int ssh_auth_response_termination(void *user)
case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT:
case SSH_AUTH_STATE_GSSAPI_TOKEN:
case SSH_AUTH_STATE_GSSAPI_MIC_SENT:
case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT:
case SSH_AUTH_STATE_PUBKEY_AUTH_SENT:
case SSH_AUTH_STATE_PUBKEY_OFFER_SENT:
case SSH_AUTH_STATE_PASSWORD_AUTH_SENT:
@@ -118,9 +119,14 @@ static const char *ssh_auth_get_current_method(ssh_session session)
case SSH_AUTH_METHOD_INTERACTIVE:
method = "keyboard interactive";
break;
#ifdef WITH_GSSAPI
case SSH_AUTH_METHOD_GSSAPI_MIC:
method = "gssapi";
break;
case SSH_AUTH_METHOD_GSSAPI_KEYEX:
method = "gssapi-keyex";
break;
#endif
default:
break;
}
@@ -175,6 +181,7 @@ static int ssh_userauth_get_response(ssh_session session)
case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT:
case SSH_AUTH_STATE_GSSAPI_TOKEN:
case SSH_AUTH_STATE_GSSAPI_MIC_SENT:
case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT:
case SSH_AUTH_STATE_PUBKEY_OFFER_SENT:
case SSH_AUTH_STATE_PUBKEY_AUTH_SENT:
case SSH_AUTH_STATE_PASSWORD_AUTH_SENT:
@@ -269,9 +276,14 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) {
if (strstr(auth_methods, "hostbased") != NULL) {
session->auth.supported_methods |= SSH_AUTH_METHOD_HOSTBASED;
}
#ifdef WITH_GSSAPI
if (strstr(auth_methods, "gssapi-with-mic") != NULL) {
session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_MIC;
}
if (strstr(auth_methods, "gssapi-keyex") != NULL) {
session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_KEYEX;
}
#endif
end:
session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN;
@@ -536,7 +548,8 @@ static int build_pubkey_auth_request(ssh_session session,
int rc;
const char *auth_method = "publickey";
if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND) {
if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND &&
session->current_crypto->server_pubkey != NULL) {
auth_method = "publickey-hostbound-v00@openssh.com";
}
@@ -555,7 +568,8 @@ static int build_pubkey_auth_request(ssh_session session,
return SSH_ERROR;
}
if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND) {
if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND &&
session->current_crypto->server_pubkey != NULL) {
rc = add_hostbound_pubkey(session);
if (rc < 0) {
return SSH_ERROR;
@@ -1845,7 +1859,7 @@ void ssh_kbdint_free(ssh_kbdint kbd)
if (kbd->prompts) {
for (i = 0; i < n; i++) {
if (kbd->prompts[i] != NULL) {
explicit_bzero(kbd->prompts[i], strlen(kbd->prompts[i]));
ssh_burn(kbd->prompts[i], strlen(kbd->prompts[i]));
}
SAFE_FREE(kbd->prompts[i]);
}
@@ -1856,7 +1870,7 @@ void ssh_kbdint_free(ssh_kbdint kbd)
if (kbd->answers) {
for (i = 0; i < n; i++) {
if (kbd->answers[i] != NULL) {
explicit_bzero(kbd->answers[i], strlen(kbd->answers[i]));
ssh_burn(kbd->answers[i], strlen(kbd->answers[i]));
}
SAFE_FREE(kbd->answers[i]);
}
@@ -1881,7 +1895,7 @@ void ssh_kbdint_clean(ssh_kbdint kbd)
n = kbd->nprompts;
if (kbd->prompts) {
for (i = 0; i < n; i++) {
explicit_bzero(kbd->prompts[i], strlen(kbd->prompts[i]));
ssh_burn(kbd->prompts[i], strlen(kbd->prompts[i]));
SAFE_FREE(kbd->prompts[i]);
}
SAFE_FREE(kbd->prompts);
@@ -1891,7 +1905,7 @@ void ssh_kbdint_clean(ssh_kbdint kbd)
if (kbd->answers) {
for (i = 0; i < n; i++) {
explicit_bzero(kbd->answers[i], strlen(kbd->answers[i]));
ssh_burn(kbd->answers[i], strlen(kbd->answers[i]));
SAFE_FREE(kbd->answers[i]);
}
SAFE_FREE(kbd->answers);
@@ -2372,8 +2386,8 @@ ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i,
}
if (session->kbdint->answers[i]) {
explicit_bzero(session->kbdint->answers[i],
strlen(session->kbdint->answers[i]));
ssh_burn(session->kbdint->answers[i],
strlen(session->kbdint->answers[i]));
SAFE_FREE(session->kbdint->answers[i]);
}
@@ -2446,4 +2460,111 @@ pending:
return rc;
}
/**
* @brief Try to authenticate through the "gssapi-keyex" method.
*
* @param[in] session The ssh session to use.
*
* @returns
* - `SSH_AUTH_ERROR`: A serious error happened.
* - `SSH_AUTH_DENIED`: Authentication failed : use another method.
* - `SSH_AUTH_PARTIAL`: You've been partially authenticated, you still
* have to use another method.
* - `SSH_AUTH_SUCCESS`: Authentication success.
* - `SSH_AUTH_AGAIN`: In nonblocking mode, you've got to call this again
* later.
*/
int ssh_userauth_gssapi_keyex(ssh_session session)
{
int rc = SSH_AUTH_DENIED;
#ifdef WITH_GSSAPI
OM_uint32 min_stat;
gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER;
switch (session->pending_call_state) {
case SSH_PENDING_CALL_NONE:
break;
case SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX:
goto pending;
default:
ssh_set_error(session,
SSH_FATAL,
"Wrong state (%d) during pending SSH call",
session->pending_call_state);
return SSH_ERROR;
}
/* Check if GSSAPI Key exchange was performed */
if (!ssh_kex_is_gss(session->current_crypto)) {
ssh_set_error(session,
SSH_FATAL,
"Attempt to authenticate with gssapi-keyex without "
"doing GSSAPI Key exchange.");
return SSH_ERROR;
}
if (session->gssapi == NULL) {
ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized");
return SSH_ERROR;
}
rc = ssh_userauth_request_service(session);
if (rc == SSH_AGAIN) {
return SSH_AUTH_AGAIN;
} else if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-keyex");
session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_KEYEX;
session->auth.state = SSH_AUTH_STATE_NONE;
session->pending_call_state = SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX;
SAFE_FREE(session->gssapi->user);
session->gssapi->user = strdup(session->opts.username);
if (session->gssapi->user == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
rc = ssh_gssapi_auth_keyex_mic(session, &mic_token_buf);
if (rc != SSH_OK) {
session->auth.state = SSH_AUTH_STATE_NONE;
session->pending_call_state = SSH_PENDING_CALL_NONE;
return rc;
}
rc = ssh_buffer_pack(session->out_buffer,
"bsssdP",
SSH2_MSG_USERAUTH_REQUEST,
session->opts.username,
"ssh-connection",
"gssapi-keyex",
mic_token_buf.length,
(size_t)mic_token_buf.length,
mic_token_buf.value);
if (rc != SSH_OK) {
ssh_set_error_oom(session);
session->auth.state = SSH_AUTH_STATE_NONE;
session->pending_call_state = SSH_PENDING_CALL_NONE;
gss_release_buffer(&min_stat, &mic_token_buf);
return rc;
}
gss_release_buffer(&min_stat, &mic_token_buf);
session->auth.state = SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT;
ssh_packet_send(session);
pending:
rc = ssh_userauth_get_response(session);
if (rc != SSH_AUTH_AGAIN) {
session->pending_call_state = SSH_PENDING_CALL_NONE;
}
#else
(void)session; /* unused */
#endif
return rc;
}
/** @} */

View File

@@ -245,8 +245,13 @@ int ssh_bind_listen(ssh_bind sshbind)
sshbind->ecdsa == NULL &&
sshbind->ed25519 == NULL) {
rc = ssh_bind_import_keys(sshbind);
if (rc != SSH_OK) {
return SSH_ERROR;
if (rc == SSH_ERROR) {
if (!sshbind->gssapi_key_exchange) {
ssh_set_error(sshbind, SSH_FATAL, "No usable hostkeys found");
return SSH_ERROR;
}
SSH_LOG(SSH_LOG_DEBUG,
"No usable hostkeys found: Using \"null\" hostkey algorithm");
}
}
@@ -386,6 +391,7 @@ void ssh_bind_free(ssh_bind sshbind){
SAFE_FREE(sshbind->rsakey);
SAFE_FREE(sshbind->ecdsakey);
SAFE_FREE(sshbind->ed25519key);
SAFE_FREE(sshbind->gssapi_key_exchange_algs);
ssh_key_free(sshbind->rsa);
sshbind->rsa = NULL;
@@ -463,6 +469,17 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd)
}
session->common.log_verbosity = sshbind->common.log_verbosity;
session->opts.gssapi_key_exchange = sshbind->gssapi_key_exchange;
if (sshbind->gssapi_key_exchange_algs != NULL) {
SAFE_FREE(session->opts.gssapi_key_exchange_algs);
session->opts.gssapi_key_exchange_algs =
strdup(sshbind->gssapi_key_exchange_algs);
if (session->opts.gssapi_key_exchange_algs == NULL) {
ssh_set_error_oom(sshbind);
return SSH_ERROR;
}
}
if (sshbind->banner != NULL) {
session->server_opts.custombanner = strdup(sshbind->banner);
@@ -509,8 +526,13 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd)
sshbind->ecdsa == NULL &&
sshbind->ed25519 == NULL) {
rc = ssh_bind_import_keys(sshbind);
if (rc != SSH_OK) {
return SSH_ERROR;
if (rc == SSH_ERROR) {
if (!sshbind->gssapi_key_exchange) {
ssh_set_error(sshbind, SSH_FATAL, "No usable hostkeys found");
return SSH_ERROR;
}
SSH_LOG(SSH_LOG_DEBUG,
"No usable hostkeys found: Using \"null\" hostkey algorithm");
}
}

View File

@@ -602,8 +602,9 @@ ssh_bind_config_parse_line(ssh_bind bind,
break;
case BIND_CFG_REQUIRED_RSA_SIZE:
l = ssh_config_get_long(&s, -1);
if (l >= 0 && (*parser_flags & PARSING)) {
rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &l);
if (l >= 0 && l <= INT_MAX && (*parser_flags & PARSING)) {
int i = (int)l;
rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &i);
if (rc != 0) {
SSH_LOG(SSH_LOG_TRACE,
"line %d: Failed to set RequiredRSASize value '%ld'",

View File

@@ -156,10 +156,10 @@ void ssh_buffer_free(struct ssh_buffer_struct *buffer)
if (buffer->secure && buffer->allocated > 0) {
/* burn the data */
explicit_bzero(buffer->data, buffer->allocated);
ssh_burn(buffer->data, buffer->allocated);
SAFE_FREE(buffer->data);
explicit_bzero(buffer, sizeof(struct ssh_buffer_struct));
ssh_burn(buffer, sizeof(struct ssh_buffer_struct));
} else {
SAFE_FREE(buffer->data);
}
@@ -205,7 +205,7 @@ static int realloc_buffer(struct ssh_buffer_struct *buffer, uint32_t needed)
return -1;
}
memcpy(new, buffer->data, buffer->used);
explicit_bzero(buffer->data, buffer->used);
ssh_burn(buffer->data, buffer->used);
SAFE_FREE(buffer->data);
} else {
new = realloc(buffer->data, needed);
@@ -241,7 +241,7 @@ static void buffer_shift(ssh_buffer buffer)
if (buffer->secure) {
void *ptr = buffer->data + buffer->used;
explicit_bzero(ptr, burn_pos);
ssh_burn(ptr, burn_pos);
}
buffer_verify(buffer);
@@ -266,7 +266,7 @@ int ssh_buffer_reinit(struct ssh_buffer_struct *buffer)
buffer_verify(buffer);
if (buffer->secure && buffer->allocated > 0) {
explicit_bzero(buffer->data, buffer->allocated);
ssh_burn(buffer->data, buffer->allocated);
}
buffer->used = 0;
buffer->pos = 0;
@@ -1352,28 +1352,28 @@ cleanup:
case 'b':
o.byte = va_arg(ap_copy, uint8_t *);
if (buffer->secure) {
explicit_bzero(o.byte, sizeof(uint8_t));
ssh_burn(o.byte, sizeof(uint8_t));
break;
}
break;
case 'w':
o.word = va_arg(ap_copy, uint16_t *);
if (buffer->secure) {
explicit_bzero(o.word, sizeof(uint16_t));
ssh_burn(o.word, sizeof(uint16_t));
break;
}
break;
case 'd':
o.dword = va_arg(ap_copy, uint32_t *);
if (buffer->secure) {
explicit_bzero(o.dword, sizeof(uint32_t));
ssh_burn(o.dword, sizeof(uint32_t));
break;
}
break;
case 'q':
o.qword = va_arg(ap_copy, uint64_t *);
if (buffer->secure) {
explicit_bzero(o.qword, sizeof(uint64_t));
ssh_burn(o.qword, sizeof(uint64_t));
break;
}
break;
@@ -1391,7 +1391,7 @@ cleanup:
case 's':
o.cstring = va_arg(ap_copy, char **);
if (buffer->secure) {
explicit_bzero(*o.cstring, strlen(*o.cstring));
ssh_burn(*o.cstring, strlen(*o.cstring));
}
SAFE_FREE(*o.cstring);
break;
@@ -1399,7 +1399,7 @@ cleanup:
len = va_arg(ap_copy, size_t);
o.data = va_arg(ap_copy, void **);
if (buffer->secure) {
explicit_bzero(*o.data, len);
ssh_burn(*o.data, len);
}
SAFE_FREE(*o.data);
break;

View File

@@ -321,7 +321,7 @@ static int ssh_channel_open_termination(void *c)
*
* @param[in] payload The buffer containing additional payload for the query.
*
* @return SSH_OK if successful; SSH_ERROR otherwise.
* @return `SSH_OK` if successful; `SSH_ERROR` otherwise.
*/
static int
channel_open(ssh_channel channel,
@@ -433,7 +433,7 @@ ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id)
* @brief grows the local window and sends a packet to the other party
* @param session SSH session
* @param channel SSH channel
* @return SSH_OK if successful; SSH_ERROR otherwise.
* @return `SSH_OK` if successful; `SSH_ERROR` otherwise.
*/
static int grow_window(ssh_session session,
ssh_channel channel)
@@ -1059,9 +1059,9 @@ int channel_default_bufferize(ssh_channel channel,
*
* @param[in] channel An allocated channel.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*
* @see ssh_channel_open_forward()
@@ -1084,15 +1084,15 @@ int ssh_channel_open_session(ssh_channel channel)
/**
* @brief Open an agent authentication forwarding channel. This type of channel
* can be opened by a server towards a client in order to provide SSH-Agent services
* to the server-side process. This channel can only be opened if the client
* claimed support by sending a channel request beforehand.
* can be opened by a server towards a client in order to provide SSH-Agent
* services to the server-side process. This channel can only be opened if the
* client claimed support by sending a channel request beforehand.
*
* @param[in] channel An allocated channel.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*
* @see ssh_channel_open_forward()
@@ -1110,7 +1110,6 @@ int ssh_channel_open_auth_agent(ssh_channel channel)
NULL);
}
/**
* @brief Open a TCP/IP forwarding channel.
*
@@ -1127,14 +1126,14 @@ int ssh_channel_open_auth_agent(ssh_channel channel)
* @param[in] localport The port on the host from where the connection
* originated. This is mostly for logging purposes.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*
* @warning This function does not bind the local port and does not automatically
* forward the content of a socket to the channel. You still have to
* use ssh_channel_read and ssh_channel_write for this.
* @warning This function does not bind the local port and does not
* automatically forward the content of a socket to the channel. You still have
* to use ssh_channel_read and ssh_channel_write for this.
*/
int ssh_channel_open_forward(ssh_channel channel, const char *remotehost,
int remoteport, const char *sourcehost, int localport)
@@ -1199,16 +1198,16 @@ error:
* @param[in] localport The port on the host from where the connection
* originated. This is mostly for logging purposes.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK on` success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*
* @warning This function does not bind the local port and does not
* automatically forward the content of a socket to the channel.
* You still have to use ssh_channel_read and ssh_channel_write for this.
* automatically forward the content of a socket to the channel.
* You still have to use ssh_channel_read and ssh_channel_write for this.
* @warning Requires support of OpenSSH for UNIX domain socket forwarding.
*/
*/
int ssh_channel_open_forward_unix(ssh_channel channel,
const char *remotepath,
const char *sourcehost,
@@ -1359,7 +1358,7 @@ void ssh_channel_do_free(ssh_channel channel)
*
* @param[in] channel The channel to send the eof to.
*
* @return SSH_OK on success, SSH_ERROR if an error occurred.
* @return `SSH_OK` on success, `SSH_ERROR` if an error occurred.
*
* Example:
@code
@@ -1436,7 +1435,7 @@ error:
*
* @param[in] channel The channel to close.
*
* @return SSH_OK on success, SSH_ERROR if an error occurred.
* @return `SSH_OK` on success, `SSH_ERROR` if an error occurred.
*
* @see ssh_channel_free()
* @see ssh_channel_is_eof()
@@ -1524,11 +1523,11 @@ static int ssh_waitsession_unblocked(void *s)
/**
* @internal
* @brief Flushes a channel (and its session) until the output buffer
* is empty, or timeout elapsed.
* is empty, or timeout elapsed.
* @param channel SSH channel
* @return SSH_OK On success,
* SSH_ERROR On error.
* SSH_AGAIN Timeout elapsed (or in nonblocking mode).
* @return `SSH_OK` On success,
* `SSH_ERROR` On error.
* `SSH_AGAIN` Timeout elapsed (or in nonblocking mode).
*/
int ssh_channel_flush(ssh_channel channel)
{
@@ -1719,7 +1718,7 @@ uint32_t ssh_channel_window_size(ssh_channel channel)
*
* @param[in] len The length of the buffer to write to.
*
* @return The number of bytes written, SSH_ERROR on error.
* @return The number of bytes written, `SSH_ERROR` on error.
*
* @see ssh_channel_read()
*/
@@ -1997,9 +1996,9 @@ error:
*
* @param[in] modes_len Number of bytes in 'modes'
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*/
int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *terminal,
@@ -2055,6 +2054,19 @@ error:
return rc;
}
/**
* @brief Request a PTY with a specific size using current TTY modes.
*
* Encodes @p terminal modes from the current TTY and sends a PTY request
* for the given channel, terminal type, and size in columns/rows.
*
* @param[in] channel The channel to send the request on.
* @param[in] terminal The terminal type (e.g. "xterm").
* @param[in] col Number of columns.
* @param[in] row Number of rows.
*
* @return `SSH_OK` on success; `SSH_ERROR` on failure.
*/
int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal,
int col, int row)
{
@@ -2077,9 +2089,9 @@ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal,
*
* @param[in] channel The channel to send the request.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*
* @see ssh_channel_request_pty_size()
@@ -2098,7 +2110,7 @@ int ssh_channel_request_pty(ssh_channel channel)
*
* @param[in] rows The new number of rows.
*
* @return SSH_OK on success, SSH_ERROR if an error occurred.
* @return `SSH_OK` on success, `SSH_ERROR` if an error occurred.
*
* @warning Do not call it from a signal handler if you are not sure any other
* libssh function using the same channel/session is running at the
@@ -2139,9 +2151,9 @@ error:
*
* @param[in] channel The channel to send the request.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*/
int ssh_channel_request_shell(ssh_channel channel)
@@ -2160,9 +2172,9 @@ int ssh_channel_request_shell(ssh_channel channel)
*
* @param[in] subsys The subsystem to request (for example "sftp").
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*
* @warning You normally don't have to call it for sftp, see sftp_new().
@@ -2210,9 +2222,9 @@ error:
*
* @param[in] channel The channel to request the sftp subsystem.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*
* @note You should use sftp_new() which does this for you.
@@ -2266,9 +2278,9 @@ static char *generate_cookie(void)
*
* @param[in] screen_number The screen number.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*/
int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol,
@@ -2401,15 +2413,16 @@ ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms)
}
/**
* @brief Send an "auth-agent-req" channel request over an existing session channel.
* @brief Send an "auth-agent-req" channel request over an existing session
* channel.
*
* This client-side request will enable forwarding the agent over an secure tunnel.
* When the server is ready to open one authentication agent channel, an
* This client-side request will enable forwarding the agent over an secure
* tunnel. When the server is ready to open one authentication agent channel, an
* ssh_channel_open_request_auth_agent_callback event will be generated.
*
* @param[in] channel The channel to send signal.
*
* @return SSH_OK on success, SSH_ERROR if an error occurred
* @return `SSH_OK` on success, `SSH_ERROR` if an error occurred
*/
int ssh_channel_request_auth_agent(ssh_channel channel) {
if (channel == NULL) {
@@ -2490,9 +2503,9 @@ static int ssh_global_request_termination(void *s)
*
* @param[in] reply Set if you expect a reply from server.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*/
int ssh_global_request(ssh_session session,
@@ -2584,7 +2597,7 @@ error:
/**
* @brief Sends the "tcpip-forward" global request to ask the server to begin
* listening for inbound connections.
* listening for inbound connections.
*
* @param[in] session The ssh session to send the request.
*
@@ -2599,9 +2612,9 @@ error:
* @param[in] bound_port The pointer to get actual bound port. Pass NULL to
* ignore.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
**/
int ssh_channel_listen_forward(ssh_session session,
@@ -2708,9 +2721,9 @@ ssh_channel ssh_channel_open_forward_port(ssh_session session, int timeout_ms, i
*
* @param[in] port The bound port on the server.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*/
int ssh_channel_cancel_forward(ssh_session session,
@@ -2759,9 +2772,9 @@ int ssh_forward_cancel(ssh_session session, const char *address, int port)
*
* @param[in] value The value to set.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
* @warning Some environment variables may be refused by security reasons.
*/
@@ -2815,9 +2828,9 @@ error:
* @param[in] cmd The command to execute
* (e.g. "ls ~/ -al | grep -i reports").
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*
* Example:
@@ -2880,9 +2893,9 @@ error:
return rc;
}
/**
* @brief Send a signal to remote process (as described in RFC 4254, section 6.9).
* @brief Send a signal to remote process (as described in RFC 4254,
* section 6.9).
*
* Sends a signal 'sig' to the remote process.
* Note, that remote system may not support signals concept.
@@ -2906,7 +2919,7 @@ error:
* SIGUSR1 -> USR1 \n
* SIGUSR2 -> USR2 \n
*
* @return SSH_OK on success, SSH_ERROR if an error occurred.
* @return `SSH_OK` on success, `SSH_ERROR` if an error occurred.
*/
int ssh_channel_request_send_signal(ssh_channel channel, const char *sig)
{
@@ -2939,7 +2952,6 @@ error:
return rc;
}
/**
* @brief Send a break signal to the server (as described in RFC 4335).
*
@@ -2951,7 +2963,7 @@ error:
*
* @param[in] length The break-length in milliseconds to send.
*
* @return SSH_OK on success, SSH_ERROR if an error occurred
* @return `SSH_OK` on success, `SSH_ERROR` if an error occurred
*/
int ssh_channel_request_send_break(ssh_channel channel, uint32_t length)
{
@@ -2989,13 +3001,14 @@ error:
* @param[out] buffer The buffer which will get the data.
*
* @param[in] count The count of bytes to be read. If it is bigger than 0,
* the exact size will be read, else (bytes=0) it will
* return once anything is available.
* the exact size will be read, else (bytes=0) it will return once anything is
* available.
*
* @param is_stderr A boolean value to mark reading from the stderr stream.
*
* @return The number of bytes read, 0 on end of file, SSH_AGAIN on
* timeout and SSH_ERROR on error.
* @return The number of bytes read, 0 on end of file, `SSH_AGAIN`
* on timeout and `SSH_ERROR` on error.
*
* @deprecated Please use ssh_channel_read instead
* @warning This function doesn't work in nonblocking/timeout mode
* @see ssh_channel_read
@@ -3098,11 +3111,11 @@ static int ssh_channel_read_termination(void *s)
*
* @param[in] is_stderr A boolean value to mark reading from the stderr flow.
*
* @return The number of bytes read, 0 on end of file, SSH_AGAIN on
* timeout and SSH_ERROR on error.
* @return The number of bytes read, 0 on end of file, `SSH_AGAIN`
* on timeout and `SSH_ERROR` on error.
*
* @warning This function may return less than count bytes of data, and won't
* block until count bytes have been read.
* block until count bytes have been read.
*/
int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr)
{
@@ -3127,8 +3140,8 @@ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_std
* @param[in] timeout_ms A timeout in milliseconds. A value of -1 means
* infinite timeout.
*
* @return The number of bytes read, 0 on end of file, SSH_AGAIN on
* timeout, SSH_ERROR on error.
* @return The number of bytes read, 0 on end of file, `SSH_AGAIN`
* on timeout, `SSH_ERROR` on error.
*
* @warning This function may return less than count bytes of data, and won't
* block until count bytes have been read.
@@ -3238,8 +3251,8 @@ int ssh_channel_read_timeout(ssh_channel channel,
*
* @param[in] is_stderr A boolean to select the stderr stream.
*
* @return The number of bytes read, SSH_AGAIN if nothing is
* available, SSH_ERROR on error, and SSH_EOF if the channel is EOF.
* @return The number of bytes read, `SSH_AGAIN` if nothing is
* available, `SSH_ERROR` on error, and `SSH_EOF` if the channel is EOF.
*
* @see ssh_channel_is_eof()
*/
@@ -3296,11 +3309,11 @@ int ssh_channel_read_nonblocking(ssh_channel channel,
* @param[in] is_stderr A boolean to select the stderr stream.
*
* @return The number of bytes available for reading, 0 if nothing
* is available or SSH_ERROR on error.
* is available or `SSH_ERROR` on error.
* When a channel is freed the function returns
* SSH_ERROR immediately.
* `SSH_ERROR` immediately.
*
* @warning When the channel is in EOF state, the function returns SSH_EOF.
* @warning When the channel is in EOF state, the function returns `SSH_EOF`.
*
* @see ssh_channel_is_eof()
*/
@@ -3343,18 +3356,19 @@ int ssh_channel_poll(ssh_channel channel, int is_stderr)
*
* @param[in] channel The channel to poll.
* @param[in] timeout Set an upper limit on the time for which this function
* will block, in milliseconds. Specifying a negative value
* means an infinite timeout. This parameter is passed to
* the poll() function.
* will block, in milliseconds. Specifying a negative
* value means an infinite timeout. This parameter is
* passed to the poll() function.
* @param[in] is_stderr A boolean to select the stderr stream.
*
* @return The number of bytes available for reading,
* 0 if nothing is available (timeout elapsed),
* SSH_EOF on end of file,
* SSH_ERROR on error.
* `SSH_EOF` on end of file,
* `SSH_ERROR` on error.
*
* @warning When the channel is in EOF state, the function returns SSH_EOF.
* When a channel is freed the function returns SSH_ERROR immediately.
* @warning When the channel is in EOF state, the function returns `SSH_EOF`.
* When a channel is freed the function returns `SSH_ERROR`
* immediately.
*
* @see ssh_channel_is_eof()
*/
@@ -3455,12 +3469,12 @@ static int ssh_channel_exit_status_termination(void *c)
* @param[out] pcore_dumped A pointer to store a boolean value if it dumped a
* core.
*
* @return SSH_OK on success, SSH_AGAIN if we don't have a status
* or an SSH error.
* @return `SSH_OK` on success, `SSH_AGAIN` if we don't have a
* status or an SSH error.
* @warning This function may block until a timeout (or never)
* if the other side is not willing to close the channel.
* When a channel is freed the function returns
* SSH_ERROR immediately.
* `SSH_ERROR` immediately.
*
* If you're looking for an async handling of this register a callback for the
* exit status!
@@ -3524,11 +3538,11 @@ int ssh_channel_get_exit_state(ssh_channel channel,
* @param[in] channel The channel to get the status from.
*
* @return The exit status, -1 if no exit status has been returned
* (yet), or SSH_ERROR on error.
* (yet), or `SSH_ERROR` on error.
* @warning This function may block until a timeout (or never)
* if the other side is not willing to close the channel.
* When a channel is freed the function returns
* SSH_ERROR immediately.
* `SSH_ERROR` immediately.
*
* If you're looking for an async handling of this register a callback for the
* exit status.
@@ -3641,9 +3655,9 @@ static size_t count_ptrs(ssh_channel *ptrs)
*
* @param[in] timeout Timeout as defined by select(2).
*
* @return SSH_OK on a successful operation, SSH_EINTR if the
* @return `SSH_OK` on a successful operation, `SSH_EINTR` if the
* select(2) syscall was interrupted, then relaunch the
* function, or SSH_ERROR on error.
* function, or `SSH_ERROR` on error.
*/
int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans,
ssh_channel *exceptchans, struct timeval * timeout)
@@ -3840,14 +3854,14 @@ int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len
* @param[in] localport The source port (your local computer). It's optional
* and for logging purpose.
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
*
* @warning This function does not bind the local port and does not automatically
* forward the content of a socket to the channel. You still have to
* use ssh_channel_read and ssh_channel_write for this.
* @warning This function does not bind the local port and does not
* automatically forward the content of a socket to the channel. You
* still have to use ssh_channel_read and ssh_channel_write for this.
*/
int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost,
int remoteport, const char *sourcehost, int localport)
@@ -3905,13 +3919,13 @@ error:
*
* @param[in] orig_port The source port (the local server).
*
* @return SSH_OK on success,
* SSH_ERROR if an error occurred,
* SSH_AGAIN if in nonblocking mode and call has
* @return `SSH_OK` on success,
* `SSH_ERROR` if an error occurred,
* `SSH_AGAIN` if in nonblocking mode and call has
* to be done again.
* @warning This function does not bind the local port and does not automatically
* forward the content of a socket to the channel. You still have to
* use shh_channel_read and ssh_channel_write for this.
* @warning This function does not bind the local port and does not
* automatically forward the content of a socket to the channel. You
* still have to use shh_channel_read and ssh_channel_write for this.
*/
int ssh_channel_open_x11(ssh_channel channel,
const char *orig_addr, int orig_port)
@@ -3966,7 +3980,7 @@ error:
*
* @param[in] exit_status The exit status to send
*
* @return SSH_OK on success, SSH_ERROR if an error occurred.
* @return `SSH_OK` on success, `SSH_ERROR` if an error occurred.
*/
int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status)
{
@@ -4010,7 +4024,7 @@ error:
* @param[in] errmsg A CRLF explanation text about the error condition
* @param[in] lang The language used in the message (format: RFC 3066)
*
* @return SSH_OK on success, SSH_ERROR if an error occurred
* @return `SSH_OK` on success, `SSH_ERROR` if an error occurred
*/
int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig,
int core, const char *errmsg, const char *lang)

View File

@@ -30,14 +30,15 @@
#include <arpa/inet.h>
#endif
#include "libssh/priv.h"
#include "libssh/ssh2.h"
#include "libssh/buffer.h"
#include "libssh/packet.h"
#include "libssh/options.h"
#include "libssh/socket.h"
#include "libssh/session.h"
#include "libssh/kex-gss.h"
#include "libssh/dh.h"
#include "libssh/options.h"
#include "libssh/packet.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include "libssh/socket.h"
#include "libssh/ssh2.h"
#ifdef WITH_GEX
#include "libssh/dh-gex.h"
#endif /* WITH_GEX */
@@ -46,9 +47,7 @@
#include "libssh/misc.h"
#include "libssh/pki.h"
#include "libssh/kex.h"
#ifdef HAVE_MLKEM
#include "libssh/mlkem768.h"
#endif
#include "libssh/hybrid_mlkem.h"
#ifndef _WIN32
#ifdef HAVE_PTHREAD
@@ -267,6 +266,14 @@ int dh_handshake(ssh_session session)
switch (session->dh_handshake_state) {
case DH_STATE_INIT:
switch (session->next_crypto->kex_type) {
#ifdef WITH_GSSAPI
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_GSS_KEX_CURVE25519_SHA256:
rc = ssh_client_gss_kex_init(session);
break;
#endif
case SSH_KEX_DH_GROUP1_SHA1:
case SSH_KEX_DH_GROUP14_SHA1:
case SSH_KEX_DH_GROUP14_SHA256:
@@ -299,11 +306,13 @@ int dh_handshake(ssh_session session)
rc = ssh_client_sntrup761x25519_init(session);
break;
#endif
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
rc = ssh_client_mlkem768x25519_init(session);
break;
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
rc = ssh_client_hybrid_mlkem_init(session);
break;
default:
rc = SSH_ERROR;
}
@@ -913,7 +922,7 @@ error:
*/
const char *ssh_copyright(void)
{
return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2025 "
return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2026 "
"Aris Adamantiadis, Andreas Schneider "
"and libssh contributors. "
"Distributed under the LGPL, please refer to COPYING "

View File

@@ -60,101 +60,104 @@
struct ssh_config_keyword_table_s {
const char *name;
enum ssh_config_opcode_e opcode;
bool cli_supported;
};
static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = {
{ "host", SOC_HOST },
{ "match", SOC_MATCH },
{ "hostname", SOC_HOSTNAME },
{ "port", SOC_PORT },
{ "user", SOC_USERNAME },
{ "identityfile", SOC_IDENTITY },
{ "ciphers", SOC_CIPHERS },
{ "macs", SOC_MACS },
{ "compression", SOC_COMPRESSION },
{ "connecttimeout", SOC_TIMEOUT },
{ "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK },
{ "userknownhostsfile", SOC_KNOWNHOSTS },
{ "proxycommand", SOC_PROXYCOMMAND },
{ "gssapiserveridentity", SOC_GSSAPISERVERIDENTITY },
{ "gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY },
{ "gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS },
{ "include", SOC_INCLUDE },
{ "bindaddress", SOC_BINDADDRESS},
{ "globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE},
{ "loglevel", SOC_LOGLEVEL},
{ "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS},
{ "kexalgorithms", SOC_KEXALGORITHMS},
{ "gssapiauthentication", SOC_GSSAPIAUTHENTICATION},
{ "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION},
{ "passwordauthentication", SOC_PASSWORDAUTHENTICATION},
{ "pubkeyauthentication", SOC_PUBKEYAUTHENTICATION},
{ "addkeystoagent", SOC_UNSUPPORTED},
{ "addressfamily", SOC_UNSUPPORTED},
{ "batchmode", SOC_UNSUPPORTED},
{ "canonicaldomains", SOC_UNSUPPORTED},
{ "canonicalizefallbacklocal", SOC_UNSUPPORTED},
{ "canonicalizehostname", SOC_UNSUPPORTED},
{ "canonicalizemaxdots", SOC_UNSUPPORTED},
{ "canonicalizepermittedcnames", SOC_UNSUPPORTED},
{ "certificatefile", SOC_CERTIFICATE},
{ "kbdinteractiveauthentication", SOC_UNSUPPORTED},
{ "checkhostip", SOC_UNSUPPORTED},
{ "connectionattempts", SOC_UNSUPPORTED},
{ "enablesshkeysign", SOC_UNSUPPORTED},
{ "fingerprinthash", SOC_UNSUPPORTED},
{ "forwardagent", SOC_UNSUPPORTED},
{ "hashknownhosts", SOC_UNSUPPORTED},
{ "hostbasedauthentication", SOC_UNSUPPORTED},
{ "hostbasedacceptedalgorithms", SOC_UNSUPPORTED},
{ "hostkeyalias", SOC_UNSUPPORTED},
{ "identitiesonly", SOC_IDENTITIESONLY},
{ "identityagent", SOC_IDENTITYAGENT},
{ "ipqos", SOC_UNSUPPORTED},
{ "kbdinteractivedevices", SOC_UNSUPPORTED},
{ "nohostauthenticationforlocalhost", SOC_UNSUPPORTED},
{ "numberofpasswordprompts", SOC_UNSUPPORTED},
{ "pkcs11provider", SOC_UNSUPPORTED},
{ "preferredauthentications", SOC_UNSUPPORTED},
{ "proxyjump", SOC_PROXYJUMP},
{ "proxyusefdpass", SOC_UNSUPPORTED},
{ "pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES},
{ "rekeylimit", SOC_REKEYLIMIT},
{ "remotecommand", SOC_UNSUPPORTED},
{ "revokedhostkeys", SOC_UNSUPPORTED},
{ "serveralivecountmax", SOC_UNSUPPORTED},
{ "serveraliveinterval", SOC_UNSUPPORTED},
{ "streamlocalbindmask", SOC_UNSUPPORTED},
{ "streamlocalbindunlink", SOC_UNSUPPORTED},
{ "syslogfacility", SOC_UNSUPPORTED},
{ "tcpkeepalive", SOC_UNSUPPORTED},
{ "updatehostkeys", SOC_UNSUPPORTED},
{ "verifyhostkeydns", SOC_UNSUPPORTED},
{ "visualhostkey", SOC_UNSUPPORTED},
{ "clearallforwardings", SOC_NA},
{ "controlmaster", SOC_NA},
{ "controlpersist", SOC_NA},
{ "controlpath", SOC_NA},
{ "dynamicforward", SOC_NA},
{ "escapechar", SOC_NA},
{ "exitonforwardfailure", SOC_NA},
{ "forwardx11", SOC_NA},
{ "forwardx11timeout", SOC_NA},
{ "forwardx11trusted", SOC_NA},
{ "gatewayports", SOC_NA},
{ "ignoreunknown", SOC_NA},
{ "localcommand", SOC_NA},
{ "localforward", SOC_NA},
{ "permitlocalcommand", SOC_NA},
{ "remoteforward", SOC_NA},
{ "requesttty", SOC_NA},
{ "sendenv", SOC_NA},
{ "tunnel", SOC_NA},
{ "tunneldevice", SOC_NA},
{ "xauthlocation", SOC_NA},
{ "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES},
{ "requiredrsasize", SOC_REQUIRED_RSA_SIZE},
{ NULL, SOC_UNKNOWN }
{"host", SOC_HOST, true},
{"match", SOC_MATCH, false},
{"hostname", SOC_HOSTNAME, true},
{"port", SOC_PORT, true},
{"user", SOC_USERNAME, true},
{"identityfile", SOC_IDENTITY, true},
{"ciphers", SOC_CIPHERS, true},
{"macs", SOC_MACS, true},
{"compression", SOC_COMPRESSION, true},
{"connecttimeout", SOC_TIMEOUT, true},
{"stricthostkeychecking", SOC_STRICTHOSTKEYCHECK, true},
{"userknownhostsfile", SOC_KNOWNHOSTS, true},
{"proxycommand", SOC_PROXYCOMMAND, true},
{"gssapiserveridentity", SOC_GSSAPISERVERIDENTITY, false},
{"gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY, false},
{"gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS, true},
{"include", SOC_INCLUDE, true},
{"bindaddress", SOC_BINDADDRESS, true},
{"globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE, true},
{"loglevel", SOC_LOGLEVEL, true},
{"hostkeyalgorithms", SOC_HOSTKEYALGORITHMS, true},
{"kexalgorithms", SOC_KEXALGORITHMS, true},
{"gssapiauthentication", SOC_GSSAPIAUTHENTICATION, true},
{"kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION, true},
{"passwordauthentication", SOC_PASSWORDAUTHENTICATION, true},
{"pubkeyauthentication", SOC_PUBKEYAUTHENTICATION, true},
{"addkeystoagent", SOC_UNSUPPORTED, true},
{"addressfamily", SOC_ADDRESSFAMILY, true},
{"batchmode", SOC_UNSUPPORTED, true},
{"canonicaldomains", SOC_UNSUPPORTED, true},
{"canonicalizefallbacklocal", SOC_UNSUPPORTED, true},
{"canonicalizehostname", SOC_UNSUPPORTED, true},
{"canonicalizemaxdots", SOC_UNSUPPORTED, true},
{"canonicalizepermittedcnames", SOC_UNSUPPORTED, true},
{"certificatefile", SOC_CERTIFICATE, true},
{"kbdinteractiveauthentication", SOC_UNSUPPORTED, true},
{"checkhostip", SOC_UNSUPPORTED, true},
{"connectionattempts", SOC_UNSUPPORTED, true},
{"enablesshkeysign", SOC_UNSUPPORTED, true},
{"fingerprinthash", SOC_UNSUPPORTED, true},
{"forwardagent", SOC_UNSUPPORTED, true},
{"hashknownhosts", SOC_UNSUPPORTED, true},
{"hostbasedauthentication", SOC_UNSUPPORTED, true},
{"hostbasedacceptedalgorithms", SOC_UNSUPPORTED, true},
{"hostkeyalias", SOC_UNSUPPORTED, true},
{"identitiesonly", SOC_IDENTITIESONLY, true},
{"identityagent", SOC_IDENTITYAGENT, true},
{"ipqos", SOC_UNSUPPORTED, true},
{"kbdinteractivedevices", SOC_UNSUPPORTED, true},
{"nohostauthenticationforlocalhost", SOC_UNSUPPORTED, true},
{"numberofpasswordprompts", SOC_UNSUPPORTED, true},
{"pkcs11provider", SOC_UNSUPPORTED, true},
{"preferredauthentications", SOC_UNSUPPORTED, true},
{"proxyjump", SOC_PROXYJUMP, true},
{"proxyusefdpass", SOC_UNSUPPORTED, true},
{"pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES, true},
{"rekeylimit", SOC_REKEYLIMIT, true},
{"remotecommand", SOC_UNSUPPORTED, true},
{"revokedhostkeys", SOC_UNSUPPORTED, true},
{"serveralivecountmax", SOC_UNSUPPORTED, true},
{"serveraliveinterval", SOC_UNSUPPORTED, true},
{"streamlocalbindmask", SOC_UNSUPPORTED, true},
{"streamlocalbindunlink", SOC_UNSUPPORTED, true},
{"syslogfacility", SOC_UNSUPPORTED, true},
{"tcpkeepalive", SOC_UNSUPPORTED, true},
{"updatehostkeys", SOC_UNSUPPORTED, true},
{"verifyhostkeydns", SOC_UNSUPPORTED, true},
{"visualhostkey", SOC_UNSUPPORTED, true},
{"clearallforwardings", SOC_NA, true},
{"controlmaster", SOC_NA, true},
{"controlpersist", SOC_NA, true},
{"controlpath", SOC_NA, true},
{"dynamicforward", SOC_NA, true},
{"escapechar", SOC_NA, true},
{"exitonforwardfailure", SOC_NA, true},
{"forwardx11", SOC_NA, true},
{"forwardx11timeout", SOC_NA, true},
{"forwardx11trusted", SOC_NA, true},
{"gatewayports", SOC_NA, true},
{"ignoreunknown", SOC_NA, true},
{"localcommand", SOC_NA, true},
{"localforward", SOC_NA, true},
{"permitlocalcommand", SOC_NA, true},
{"remoteforward", SOC_NA, true},
{"requesttty", SOC_NA, true},
{"sendenv", SOC_NA, true},
{"tunnel", SOC_NA, true},
{"tunneldevice", SOC_NA, true},
{"xauthlocation", SOC_NA, true},
{"pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES, true},
{"requiredrsasize", SOC_REQUIRED_RSA_SIZE, true},
{"gssapikeyexchange", SOC_GSSAPIKEYEXCHANGE, true},
{"gssapikexalgorithms", SOC_GSSAPIKEXALGORITHMS, true},
{NULL, SOC_UNKNOWN, false},
};
enum ssh_config_match_e {
@@ -189,19 +192,48 @@ static struct ssh_config_match_keyword_table_s
{NULL, MATCH_UNKNOWN},
};
static int ssh_config_parse_line(ssh_session session, const char *line,
unsigned int count, int *parsing, unsigned int depth, bool global);
int ssh_config_parse_line(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global);
static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) {
int i;
static int ssh_config_parse_line_internal(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global,
bool is_cli,
bool fail_on_unknown);
for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
return ssh_config_keyword_table[i].opcode;
int ssh_config_parse_line_cli(ssh_session session, const char *line);
enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword)
{
int i;
for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) {
return ssh_config_keyword_table[i].opcode;
}
}
}
return SOC_UNKNOWN;
return SOC_UNKNOWN;
}
static bool ssh_config_is_cli_supported(enum ssh_config_opcode_e opcode)
{
int i;
for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) {
if (opcode == ssh_config_keyword_table[i].opcode) {
return ssh_config_keyword_table[i].cli_supported;
}
}
return false;
}
#define LIBSSH_CONF_MAX_DEPTH 16
@@ -735,13 +767,76 @@ ssh_match_localnetwork(const char *addrlist, bool negate)
}
#endif /* HAVE_IFADDRS_H */
static int
ssh_config_parse_line(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global)
static enum ssh_options_e
ssh_config_get_auth_option(enum ssh_config_opcode_e opcode)
{
struct auth_option_map {
enum ssh_config_opcode_e opcode;
const char *name;
enum ssh_options_e option;
};
static struct auth_option_map auth_options[] = {
{
SOC_GSSAPIAUTHENTICATION,
"GSSAPIAuthentication",
SSH_OPTIONS_GSSAPI_AUTH,
},
{
SOC_KBDINTERACTIVEAUTHENTICATION,
"KbdInteractiveAuthentication",
SSH_OPTIONS_KBDINT_AUTH,
},
{
SOC_PASSWORDAUTHENTICATION,
"PasswordAuthentication",
SSH_OPTIONS_PASSWORD_AUTH,
},
{
SOC_PUBKEYAUTHENTICATION,
"PubkeyAuthentication",
SSH_OPTIONS_PUBKEY_AUTH,
},
{0, NULL, 0},
};
for (struct auth_option_map *map = auth_options; map->name != NULL; map++) {
if (map->opcode == opcode) {
return map->option;
}
}
return -1;
}
#define CHECK_COND_OR_FAIL(cond, error_message) \
if ((cond)) { \
SSH_LOG(SSH_LOG_DEBUG, \
"line %d: %s: %s", \
count, \
error_message, \
keyword); \
if (fail_on_unknown) { \
ssh_set_error(session, \
SSH_FATAL, \
is_cli ? "%s '%s' value on CLI" \
: "%s '%s' value at line %d", \
error_message, \
keyword, \
is_cli ? 0 : count); \
SAFE_FREE(x); \
return SSH_ERROR; \
} \
break; \
}
static int ssh_config_parse_line_internal(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global,
bool is_cli,
bool fail_on_unknown)
{
enum ssh_config_opcode_e opcode;
const char *p = NULL, *p2 = NULL;
@@ -756,6 +851,9 @@ ssh_config_parse_line(ssh_session session,
/* Ignore empty lines */
if (line == NULL || *line == '\0') {
if (is_cli) {
return SSH_ERROR;
}
return 0;
}
@@ -781,6 +879,16 @@ ssh_config_parse_line(ssh_session session,
}
opcode = ssh_config_get_opcode(keyword);
if (is_cli && !ssh_config_is_cli_supported(opcode)) {
ssh_set_error(
session,
SSH_FATAL,
"Option '%s' is not supported in command-line configuration",
keyword);
SAFE_FREE(x);
return SSH_ERROR;
}
if (*parsing == 1 &&
opcode != SOC_HOST &&
opcode != SOC_MATCH &&
@@ -1051,82 +1159,91 @@ ssh_config_parse_line(ssh_session session,
}
case SOC_HOSTNAME:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
char *z = ssh_path_expand_escape(session, p);
if (z == NULL) {
z = strdup(p);
}
ssh_options_set(session, SSH_OPTIONS_HOST, z);
free(z);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
char *z = ssh_path_expand_escape(session, p);
if (z == NULL) {
z = strdup(p);
}
ssh_options_set(session, SSH_OPTIONS_HOST, z);
free(z);
}
break;
case SOC_PORT:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_PORT_STR, p);
}
break;
case SOC_USERNAME:
if (session->opts.username == NULL) {
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
p = ssh_config_get_str_tok(&s, NULL);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_USER, p);
}
}
break;
}
break;
case SOC_IDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p);
}
break;
case SOC_CIPHERS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p);
ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p);
}
break;
case SOC_MACS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p);
ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p);
ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p);
}
break;
case SOC_COMPRESSION:
i = ssh_config_get_yesno(&s, -1);
if (i >= 0 && *parsing) {
if (i) {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
} else {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no");
}
CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
if (*parsing) {
if (i) {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
} else {
ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no");
}
}
break;
case SOC_TIMEOUT:
l = ssh_config_get_long(&s, -1);
if (l >= 0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l);
CHECK_COND_OR_FAIL(l < 0, "Invalid argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l);
}
break;
case SOC_STRICTHOSTKEYCHECK:
i = ssh_config_get_yesno(&s, -1);
if (i >= 0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i);
CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i);
}
break;
case SOC_KNOWNHOSTS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p);
}
break;
case SOC_PROXYCOMMAND:
p = ssh_config_get_cmd(&s);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
/* We share the seen value with the ProxyJump */
if (p && *parsing && !seen[SOC_PROXYJUMP]) {
ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
if (*parsing && !seen[SOC_PROXYJUMP]) {
ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p);
}
break;
case SOC_PROXYJUMP:
@@ -1146,37 +1263,43 @@ ssh_config_parse_line(ssh_session session,
break;
case SOC_GSSAPISERVERIDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p);
}
break;
case SOC_GSSAPICLIENTIDENTITY:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p);
}
break;
case SOC_GSSAPIDELEGATECREDENTIALS:
i = ssh_config_get_yesno(&s, -1);
if (i >=0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i);
CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i);
}
break;
case SOC_BINDADDRESS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_BINDADDR, p);
}
break;
case SOC_GLOBALKNOWNHOSTSFILE:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p);
}
break;
case SOC_LOGLEVEL:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
int value = -1;
if (strcasecmp(p, "quiet") == 0) {
@@ -1194,6 +1317,7 @@ ssh_config_parse_line(ssh_session session,
strcasecmp(p, "DEBUG3") == 0) {
value = SSH_LOG_TRACE;
}
CHECK_COND_OR_FAIL(value == -1, "Invalid value");
if (value != -1) {
ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value);
}
@@ -1201,19 +1325,22 @@ ssh_config_parse_line(ssh_session session,
break;
case SOC_HOSTKEYALGORITHMS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p);
}
break;
case SOC_PUBKEYACCEPTEDKEYTYPES:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p);
}
break;
case SOC_KEXALGORITHMS:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p);
}
break;
@@ -1221,6 +1348,7 @@ ssh_config_parse_line(ssh_session session,
/* Parse the data limit */
p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL) {
CHECK_COND_OR_FAIL(1, "Missing data limit");
break;
} else if (strcmp(p, "default") == 0) {
/* Default rekey limits enforced automatically */
@@ -1229,8 +1357,7 @@ ssh_config_parse_line(ssh_session session,
char *endp = NULL;
ll = strtoll(p, &endp, 10);
if (p == endp || ll < 0) {
/* No number or negative */
SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit");
CHECK_COND_OR_FAIL(1, "Invalid data limit");
break;
}
switch (*endp) {
@@ -1268,19 +1395,19 @@ ssh_config_parse_line(ssh_session session,
break;
}
if (*endp != ' ' && *endp != '\0') {
SSH_LOG(SSH_LOG_TRACE,
"Invalid trailing characters after the rekey limit: %s",
endp);
CHECK_COND_OR_FAIL(1, "Invalid trailing characters");
break;
}
}
if (ll > -1 && *parsing) {
CHECK_COND_OR_FAIL(ll < 0, "Invalid data limit");
if (*parsing) {
uint64_t v = (uint64_t)ll;
ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v);
}
/* Parse the time limit */
p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL) {
CHECK_COND_OR_FAIL(1, "Missing time limit");
break;
} else if (strcmp(p, "none") == 0) {
ll = 0;
@@ -1289,7 +1416,7 @@ ssh_config_parse_line(ssh_session session,
ll = strtoll(p, &endp, 10);
if (p == endp || ll < 0) {
/* No number or negative */
SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit");
CHECK_COND_OR_FAIL(1, "Invalid time limit");
break;
}
switch (*endp) {
@@ -1342,11 +1469,11 @@ ssh_config_parse_line(ssh_session session,
break;
}
if (*endp != '\0') {
SSH_LOG(SSH_LOG_TRACE, "Invalid trailing characters after the"
" rekey limit: %s", endp);
CHECK_COND_OR_FAIL(1, "Invalid trailing characters");
break;
}
}
CHECK_COND_OR_FAIL(ll < 0, "Invalid time limit");
if (ll > -1 && *parsing) {
uint32_t v = (uint32_t)ll;
ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v);
@@ -1355,56 +1482,44 @@ ssh_config_parse_line(ssh_session session,
case SOC_GSSAPIAUTHENTICATION:
case SOC_KBDINTERACTIVEAUTHENTICATION:
case SOC_PASSWORDAUTHENTICATION:
case SOC_PUBKEYAUTHENTICATION:
case SOC_PUBKEYAUTHENTICATION: {
enum ssh_options_e option = ssh_config_get_auth_option(opcode);
i = ssh_config_get_yesno(&s, 0);
if (i>=0 && *parsing) {
switch(opcode){
case SOC_GSSAPIAUTHENTICATION:
ssh_options_set(session, SSH_OPTIONS_GSSAPI_AUTH, &i);
break;
case SOC_KBDINTERACTIVEAUTHENTICATION:
ssh_options_set(session, SSH_OPTIONS_KBDINT_AUTH, &i);
break;
case SOC_PASSWORDAUTHENTICATION:
ssh_options_set(session, SSH_OPTIONS_PASSWORD_AUTH, &i);
break;
case SOC_PUBKEYAUTHENTICATION:
ssh_options_set(session, SSH_OPTIONS_PUBKEY_AUTH, &i);
break;
/* make gcc happy */
default:
break;
}
CHECK_COND_OR_FAIL(i < 0, "Authentication option");
if (*parsing) {
ssh_options_set(session, option, &i);
}
break;
}
case SOC_NA:
SSH_LOG(SSH_LOG_TRACE, "Unapplicable option: %s, line: %d",
keyword, count);
break;
CHECK_COND_OR_FAIL(1, "Unapplicable option");
break;
case SOC_UNSUPPORTED:
SSH_LOG(SSH_LOG_RARE, "Unsupported option: %s, line: %d",
keyword, count);
break;
CHECK_COND_OR_FAIL(1, "Unsupported option");
break;
case SOC_UNKNOWN:
SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d",
keyword, count);
break;
CHECK_COND_OR_FAIL(1, "Unknown option");
break;
case SOC_IDENTITYAGENT:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p);
}
break;
case SOC_IDENTITIESONLY:
i = ssh_config_get_yesno(&s, -1);
if (i >= 0 && *parsing) {
bool b = i;
ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b);
CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
if (*parsing) {
bool b = i;
ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b);
}
break;
case SOC_CONTROLMASTER:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "ControlMaster");
if (*parsing) {
int value = -1;
if (strcasecmp(p, "auto") == 0) {
@@ -1419,6 +1534,7 @@ ssh_config_parse_line(ssh_session session,
value = SSH_CONTROL_MASTER_ASK;
}
CHECK_COND_OR_FAIL(value == -1, "Invalid argument");
if (value != -1) {
ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value);
}
@@ -1436,14 +1552,62 @@ ssh_config_parse_line(ssh_session session,
break;
case SOC_CERTIFICATE:
p = ssh_config_get_str_tok(&s, NULL);
if (p && *parsing) {
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p);
}
break;
case SOC_GSSAPIKEYEXCHANGE: {
i = ssh_config_get_yesno(&s, -1);
CHECK_COND_OR_FAIL(i < 0, "Invalid argument");
if (*parsing) {
bool b = (i == 1) ? true : false;
ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &b);
}
break;
}
case SOC_GSSAPIKEXALGORITHMS:
p = ssh_config_get_str_tok(&s, NULL);
CHECK_COND_OR_FAIL(p == NULL, "Missing argument");
if (*parsing) {
ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, p);
}
break;
case SOC_REQUIRED_RSA_SIZE:
l = ssh_config_get_long(&s, -1);
if (l >= 0 && *parsing) {
ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &l);
CHECK_COND_OR_FAIL(l < 0 || l > INT_MAX, "Invalid argument");
if (*parsing) {
i = (int)l;
ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &i);
}
break;
case SOC_ADDRESSFAMILY:
p = ssh_config_get_str_tok(&s, NULL);
if (p == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"line %d: no argument after keyword \"addressfamily\"",
count);
SAFE_FREE(x);
return SSH_ERROR;
}
if (*parsing) {
int value = -1;
if (strcasecmp(p, "any") == 0) {
value = SSH_ADDRESS_FAMILY_ANY;
} else if (strcasecmp(p, "inet") == 0) {
value = SSH_ADDRESS_FAMILY_INET;
} else if (strcasecmp(p, "inet6") == 0) {
value = SSH_ADDRESS_FAMILY_INET6;
} else {
SSH_LOG(SSH_LOG_WARNING,
"line %d: invalid argument \"%s\"",
count,
p);
SAFE_FREE(x);
return SSH_ERROR;
}
ssh_options_set(session, SSH_OPTIONS_ADDRESS_FAMILY, &value);
}
break;
default:
@@ -1458,6 +1622,38 @@ ssh_config_parse_line(ssh_session session,
return 0;
}
#undef CHECK_COND_OR_FAIL
int ssh_config_parse_line(ssh_session session,
const char *line,
unsigned int count,
int *parsing,
unsigned int depth,
bool global)
{
return ssh_config_parse_line_internal(session,
line,
count,
parsing,
depth,
global,
false,
false);
}
int ssh_config_parse_line_cli(ssh_session session, const char *line)
{
int parsing = 1;
return ssh_config_parse_line_internal(session,
line,
0,
&parsing,
0,
false,
true,
true);
}
/* @brief Parse configuration from a file pointer
*
* @params[in] session The ssh session

View File

@@ -109,7 +109,8 @@ static int ssh_connect_socket_close(socket_t s)
#endif
}
static int getai(const char *host, int port, struct addrinfo **ai)
static int
getai(const char *host, int port, int ai_family, struct addrinfo **ai)
{
const char *service = NULL;
struct addrinfo hints;
@@ -118,7 +119,7 @@ static int getai(const char *host, int port, struct addrinfo **ai)
ZERO_STRUCT(hints);
hints.ai_protocol = IPPROTO_TCP;
hints.ai_family = PF_UNSPEC;
hints.ai_family = ai_family;
hints.ai_socktype = SOCK_STREAM;
if (port == 0) {
@@ -165,14 +166,39 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
{
socket_t s = -1, first = -1;
int rc;
int ai_family;
static const char *ai_family_str = NULL;
struct addrinfo *ai = NULL;
struct addrinfo *itr = NULL;
char addrname[NI_MAXHOST], portname[NI_MAXSERV];
rc = getai(host, port, &ai);
switch (session->opts.address_family) {
case SSH_ADDRESS_FAMILY_INET:
ai_family = PF_INET;
ai_family_str = "inet";
break;
case SSH_ADDRESS_FAMILY_INET6:
ai_family = PF_INET6;
ai_family_str = "inet6";
break;
case SSH_ADDRESS_FAMILY_ANY:
default:
ai_family = PF_UNSPEC;
ai_family_str = "any";
}
SSH_LOG(SSH_LOG_PACKET,
"Resolve target hostname %s port %d (%s)",
host,
port,
ai_family_str);
rc = getai(host, port, ai_family, &ai);
if (rc != 0) {
ssh_set_error(session, SSH_FATAL,
"Failed to resolve hostname %s (%s)",
host, gai_strerror(rc));
ssh_set_error(session,
SSH_FATAL,
"Failed to resolve hostname %s (%s): %s",
host,
ai_family_str,
gai_strerror(rc));
return -1;
}
@@ -192,13 +218,18 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
struct addrinfo *bind_ai = NULL;
struct addrinfo *bind_itr = NULL;
SSH_LOG(SSH_LOG_PACKET, "Resolving %s", bind_addr);
SSH_LOG(SSH_LOG_PACKET,
"Resolving bind address %s (%s)",
bind_addr,
ai_family_str);
rc = getai(bind_addr, 0, &bind_ai);
rc = getai(bind_addr, 0, ai_family, &bind_ai);
if (rc != 0) {
ssh_set_error(session, SSH_FATAL,
"Failed to resolve bind address %s (%s)",
ssh_set_error(session,
SSH_FATAL,
"Failed to resolve bind address %s (%s): %s",
bind_addr,
ai_family_str,
gai_strerror(rc));
ssh_connect_socket_close(s);
s = -1;
@@ -252,7 +283,28 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
}
}
rc = getnameinfo(itr->ai_addr,
itr->ai_addrlen,
addrname,
sizeof(addrname),
portname,
sizeof(portname),
NI_NUMERICHOST | NI_NUMERICSERV);
if (rc != 0) {
ssh_set_error(session, SSH_FATAL,
"getnameinfo failed: %s",
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
ssh_connect_socket_close(s);
s = -1;
continue;
}
errno = 0;
SSH_LOG(SSH_LOG_PACKET,
"Connecting to host %s [%s] port %s",
host,
addrname,
portname);
rc = connect(s, itr->ai_addr, itr->ai_addrlen);
if (rc == -1) {
if ((errno != 0) && (errno != EINPROGRESS)) {
@@ -263,6 +315,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
s = -1;
} else {
if (first == -1) {
SSH_LOG(SSH_LOG_PACKET, "EINPROGRESS => Store for later.");
first = s;
} else { /* errno == EINPROGRESS */
/* save only the first "working" socket */
@@ -282,6 +335,9 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
* connection, otherwise return the first address without error or error */
if (s == -1) {
s = first;
} else if (s != first && first != -1) {
/* Clean up the saved socket if any */
ssh_connect_socket_close(first);
}
return s;

View File

@@ -83,6 +83,20 @@ static ssize_t ssh_connector_fd_write(ssh_connector connector,
uint32_t len);
static bool ssh_connector_fd_is_socket(socket_t socket);
/**
* @brief Create a new SSH connector.
*
* Allocates and initializes a new connector object for moving data between
* an SSH session and file descriptors. The connector is created with invalid
* file descriptors and callback structures initialized, but not yet attached
* to any channels or sockets.
*
* @param[in] session The SSH session to associate with the connector.
*
* @return A newly allocated connector on success, or NULL if an
* error occurred. On error, an out-of-memory error is
* set on the session.
*/
ssh_connector ssh_connector_new(ssh_session session)
{
ssh_connector connector;
@@ -112,8 +126,20 @@ ssh_connector ssh_connector_new(ssh_session session)
return connector;
}
/**
* @brief Free an SSH connector.
*
* Cleans up and deallocates a connector created by ssh_connector_new().
* Any channel callbacks and poll objects associated with the @p connector
* are removed and freed before the connector structure itself is released.
*
* @param[in] connector The connector to free.
*/
void ssh_connector_free (ssh_connector connector)
{
if (connector == NULL) {
return;
}
if (connector->in_channel != NULL) {
ssh_remove_channel_callbacks(connector->in_channel,
&connector->in_channel_cb);
@@ -140,6 +166,24 @@ void ssh_connector_free (ssh_connector connector)
free(connector);
}
/**
* @brief Set the input channel for a connector.
*
* Associates an SSH channel with the @p connector as its input source and
* installs the internal channel callbacks used for reading data. Any
* configured input file descriptor is disabled and the connector will
* receive data from the given channel only.
*
* If neither `SSH_CONNECTOR_STDOUT` nor `SSH_CONNECTOR_STDERR` is specified
* in @p flags, `SSH_CONNECTOR_STDOUT` is used as the default.
*
* @param[in] connector The connector to configure.
* @param[in] channel The SSH channel to use as input.
* @param[in] flags A combination of ssh_connector_flags_e values
* selecting which channel streams to read from.
*
* @return `SSH_OK` on success, `SSH_ERROR` on failure.
*/
int ssh_connector_set_in_channel(ssh_connector connector,
ssh_channel channel,
enum ssh_connector_flags_e flags)
@@ -156,6 +200,24 @@ int ssh_connector_set_in_channel(ssh_connector connector,
return ssh_add_channel_callbacks(channel, &connector->in_channel_cb);
}
/**
* @brief Set the output channel for a connector.
*
* Associates an SSH channel with the @p connector as its output target and
* installs the internal channel callbacks used for writing data. Any
* configured output file descriptor is disabled and the connector will
* send data to the given channel only.
*
* If neither `SSH_CONNECTOR_STDOUT` nor `SSH_CONNECTOR_STDERR` is specified
* in @p flags, `SSH_CONNECTOR_STDOUT` is used as the default.
*
* @param[in] connector The connector to configure.
* @param[in] channel The SSH channel to use as output.
* @param[in] flags A combination of ssh_connector_flags_e values
* selecting which channel streams to write to.
*
* @return `SSH_OK` on success, `SSH_ERROR` on failure.
*/
int ssh_connector_set_out_channel(ssh_connector connector,
ssh_channel channel,
enum ssh_connector_flags_e flags)
@@ -172,6 +234,15 @@ int ssh_connector_set_out_channel(ssh_connector connector,
return ssh_add_channel_callbacks(channel, &connector->out_channel_cb);
}
/**
* @brief Set the connector's input file descriptor.
*
* Sets the @p fd (file descriptor) to be used as the input source for the
* @p connector , replacing any previously configured input channel.
*
* @param[in] connector The connector to configure.
* @param[in] fd The file descriptor (socket or regular).
*/
void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd)
{
connector->in_fd = fd;
@@ -179,6 +250,15 @@ void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd)
connector->in_channel = NULL;
}
/**
* @brief Set the connector's output file descriptor.
*
* Sets the @p fd (file descriptor) to be used as the output target for the
* @p connector , replacing any previously configured output channel.
*
* @param[in] connector The connector to configure.
* @param[in] fd The file descriptor (socket or regular).
*/
void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd)
{
connector->out_fd = fd;
@@ -250,7 +330,9 @@ static void ssh_connector_fd_in_cb(ssh_connector connector)
}
r = ssh_connector_fd_read(connector, buffer, toread);
if (r < 0) {
/* Sanity: Make sure we do not get too large return value to make static
* analysis tools happy */
if (r < 0 || r > (ssize_t)toread) {
ssh_connector_except(connector, connector->in_fd);
return;
}
@@ -295,7 +377,9 @@ static void ssh_connector_fd_in_cb(ssh_connector connector)
w = ssh_connector_fd_write(connector,
buffer + total,
(uint32_t)(r - total));
if (w < 0) {
/* Sanity: Make sure we do not get too large return value
* to make static analysis tools happy */
if (w < 0 || w > (r - total)) {
ssh_connector_except(connector, connector->out_fd);
return;
}
@@ -372,7 +456,7 @@ ssh_connector_fd_out_cb(ssh_connector connector)
*
* @brief Callback called when a poll event is received on a file descriptor.
*
* This is for (input or output.
* This is for input or output.
*
* @param[in] fd file descriptor receiving the event
*

View File

@@ -101,7 +101,7 @@ void ssh_client_curve25519_remove_callbacks(ssh_session session)
ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks);
}
static int ssh_curve25519_build_k(ssh_session session)
int ssh_curve25519_build_k(ssh_session session)
{
ssh_curve25519_pubkey k;
int rc;

View File

@@ -409,6 +409,7 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli,
size_t line = 0;
size_t best_nlines = 0;
*best_size = 0;
for(;;) {
line++;
firstbyte = getc(moduli);

View File

@@ -26,6 +26,10 @@
#include "config.h"
#include <stdio.h>
#ifdef WITH_GSSAPI
#include "libssh/gssapi.h"
#include <gssapi/gssapi.h>
#endif
#include "libssh/priv.h"
#include "libssh/crypto.h"
@@ -36,6 +40,7 @@
#include "libssh/ssh2.h"
#include "libssh/pki.h"
#include "libssh/bignum.h"
#include "libssh/string.h"
static unsigned char p_group1_value[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,

View File

@@ -424,9 +424,11 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto)
break;
case SSH_KEX_DH_GROUP14_SHA1:
case SSH_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP14_SHA256:
rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator);
break;
case SSH_KEX_DH_GROUP16_SHA512:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator);
break;
case SSH_KEX_DH_GROUP18_SHA512:

View File

@@ -253,9 +253,11 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto)
break;
case SSH_KEX_DH_GROUP14_SHA1:
case SSH_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP14_SHA256:
rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator);
break;
case SSH_KEX_DH_GROUP16_SHA512:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator);
break;
case SSH_KEX_DH_GROUP18_SHA512:

View File

@@ -51,18 +51,25 @@ static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) {
#else
static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) {
#endif /* OPENSSL_VERSION_NUMBER */
if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) {
switch (kex_type) {
case SSH_KEX_ECDH_SHA2_NISTP256:
case SSH_KEX_MLKEM768NISTP256_SHA256:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
return NISTP256;
} else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) {
return NISTP384;
} else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) {
return NISTP521;
}
#if OPENSSL_VERSION_NUMBER < 0x30000000L
return SSH_ERROR;
#else
return NULL;
case SSH_KEX_ECDH_SHA2_NISTP384:
#if HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
return NISTP384;
case SSH_KEX_ECDH_SHA2_NISTP521:
return NISTP521;
default:
#if OPENSSL_VERSION_NUMBER < 0x30000000L
return SSH_ERROR;
#else
return NULL;
#endif
}
}
/* @internal
@@ -206,12 +213,36 @@ static ssh_string ssh_ecdh_generate(ssh_session session)
return pubkey_string;
}
/** @internal
* @brief Set up a nistp{256,384,521} key pair for ECDH key exchange.
*/
int ssh_ecdh_init(ssh_session session)
{
ssh_string pubkey = NULL;
ssh_string *pubkey_loc = NULL;
pubkey = ssh_ecdh_generate(session);
if (pubkey == NULL) {
return SSH_ERROR;
}
if (session->server) {
pubkey_loc = &session->next_crypto->ecdh_server_pubkey;
} else {
pubkey_loc = &session->next_crypto->ecdh_client_pubkey;
}
ssh_string_free(*pubkey_loc);
*pubkey_loc = pubkey;
return SSH_OK;
}
/** @internal
* @brief Starts ecdh-sha2-nistp256 key exchange
*/
int ssh_client_ecdh_init(ssh_session session)
{
ssh_string client_pubkey = NULL;
int rc;
rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT);
@@ -219,19 +250,16 @@ int ssh_client_ecdh_init(ssh_session session)
return SSH_ERROR;
}
client_pubkey = ssh_ecdh_generate(session);
if (client_pubkey == NULL) {
return SSH_ERROR;
}
rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey);
rc = ssh_ecdh_init(session);
if (rc < 0) {
ssh_string_free(client_pubkey);
return SSH_ERROR;
}
ssh_string_free(session->next_crypto->ecdh_client_pubkey);
session->next_crypto->ecdh_client_pubkey = client_pubkey;
rc = ssh_buffer_add_ssh_string(session->out_buffer,
session->next_crypto->ecdh_client_pubkey);
if (rc < 0) {
return SSH_ERROR;
}
/* register the packet callbacks */
ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks);
@@ -454,7 +482,6 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init)
{
/* ECDH keys */
ssh_string q_c_string = NULL;
ssh_string q_s_string = NULL;
/* SSH host keys (rsa, ed25519 and ecdsa) */
ssh_key privkey = NULL;
enum ssh_digest_e digest = SSH_DIGEST_AUTO;
@@ -475,13 +502,11 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init)
}
session->next_crypto->ecdh_client_pubkey = q_c_string;
q_s_string = ssh_ecdh_generate(session);
if (q_s_string == NULL) {
rc = ssh_ecdh_init(session);
if (rc < 0) {
goto error;
}
session->next_crypto->ecdh_server_pubkey = q_s_string;
/* build k and session_id */
rc = ecdh_build_k(session);
if (rc < 0) {
@@ -518,7 +543,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init)
"bSSS",
SSH2_MSG_KEXDH_REPLY,
pubkey_blob, /* host's pubkey */
q_s_string, /* ecdh public key */
session->next_crypto->ecdh_server_pubkey, /* ecdh public key */
sig_blob); /* signature blob */
SSH_STRING_FREE(sig_blob);

View File

@@ -36,28 +36,46 @@
/** @internal
* @brief Map the given key exchange enum value to its curve name.
*/
static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) {
if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) {
static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type)
{
switch (kex_type) {
case SSH_KEX_ECDH_SHA2_NISTP256:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_KEX_MLKEM768NISTP256_SHA256:
return "NIST P-256";
} else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) {
case SSH_KEX_ECDH_SHA2_NISTP384:
#if HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
return "NIST P-384";
} else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) {
case SSH_KEX_ECDH_SHA2_NISTP521:
return "NIST P-521";
default:
return NULL;
}
return NULL;
}
/** @internal
* @brief Starts ecdh-sha2-nistp{256,384,521} key exchange.
* @brief Set up a nistp{256,384,521} key pair for ECDH key exchange.
*/
int ssh_client_ecdh_init(ssh_session session)
int ssh_ecdh_init(ssh_session session)
{
int rc;
int rc = SSH_OK;
const char *curve = NULL;
const char *genstring = NULL;
gpg_error_t err;
ssh_string client_pubkey = NULL;
gcry_sexp_t param = NULL;
gcry_sexp_t key = NULL;
const char *curve = NULL;
ssh_string pubkey = NULL;
ssh_string *pubkey_loc = NULL;
if (session->server) {
pubkey_loc = &session->next_crypto->ecdh_server_pubkey;
genstring = "(genkey(ecdh(curve %s) (flags transient-key)))";
} else {
pubkey_loc = &session->next_crypto->ecdh_client_pubkey;
genstring = "(genkey(ecdh(curve %s)))";
}
curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type);
if (curve == NULL) {
@@ -65,16 +83,7 @@ int ssh_client_ecdh_init(ssh_session session)
goto out;
}
rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT);
if (rc < 0) {
rc = SSH_ERROR;
goto out;
}
err = gcry_sexp_build(&param,
NULL,
"(genkey(ecdh(curve %s)))",
curve);
err = gcry_sexp_build(&param, NULL, genstring, curve);
if (err) {
rc = SSH_ERROR;
goto out;
@@ -86,17 +95,8 @@ int ssh_client_ecdh_init(ssh_session session)
goto out;
}
client_pubkey = ssh_sexp_extract_mpi(key,
"q",
GCRYMPI_FMT_USG,
GCRYMPI_FMT_STD);
if (client_pubkey == NULL) {
rc = SSH_ERROR;
goto out;
}
rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey);
if (rc < 0) {
pubkey = ssh_sexp_extract_mpi(key, "q", GCRYMPI_FMT_USG, GCRYMPI_FMT_STD);
if (pubkey == NULL) {
rc = SSH_ERROR;
goto out;
}
@@ -109,20 +109,45 @@ int ssh_client_ecdh_init(ssh_session session)
session->next_crypto->ecdh_privkey = key;
key = NULL;
SSH_STRING_FREE(session->next_crypto->ecdh_client_pubkey);
session->next_crypto->ecdh_client_pubkey = client_pubkey;
client_pubkey = NULL;
SSH_STRING_FREE(*pubkey_loc);
*pubkey_loc = pubkey;
pubkey = NULL;
out:
gcry_sexp_release(param);
gcry_sexp_release(key);
SSH_STRING_FREE(pubkey);
return rc;
}
/** @internal
* @brief Starts ecdh-sha2-nistp{256,384,521} key exchange.
*/
int ssh_client_ecdh_init(ssh_session session)
{
int rc;
rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT);
if (rc < 0) {
return SSH_ERROR;
}
rc = ssh_ecdh_init(session);
if (rc < 0) {
return SSH_ERROR;
}
rc = ssh_buffer_add_ssh_string(session->out_buffer,
session->next_crypto->ecdh_client_pubkey);
if (rc < 0) {
return SSH_ERROR;
}
/* register the packet callbacks */
ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks);
session->dh_handshake_state = DH_STATE_INIT_SENT;
rc = ssh_packet_send(session);
out:
gcry_sexp_release(param);
gcry_sexp_release(key);
SSH_STRING_FREE(client_pubkey);
return rc;
}
@@ -272,27 +297,16 @@ int ecdh_build_k(ssh_session session)
* SSH_MSG_KEXDH_REPLY
*/
SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){
gpg_error_t err;
/* ECDH keys */
ssh_string q_c_string = NULL;
ssh_string q_s_string = NULL;
gcry_sexp_t param = NULL;
gcry_sexp_t key = NULL;
/* SSH host keys (rsa, ed25519 and ecdsa) */
ssh_key privkey = NULL;
enum ssh_digest_e digest = SSH_DIGEST_AUTO;
ssh_string sig_blob = NULL;
ssh_string pubkey_blob = NULL;
int rc = SSH_ERROR;
const char *curve = NULL;
(void)type;
(void)user;
ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks);
curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type);
if (curve == NULL) {
goto out;
}
/* Extract the client pubkey from the init packet */
q_c_string = ssh_buffer_get_ssh_string(packet);
@@ -302,29 +316,12 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){
}
session->next_crypto->ecdh_client_pubkey = q_c_string;
/* Build server's key pair */
err = gcry_sexp_build(&param, NULL, "(genkey(ecdh(curve %s) (flags transient-key)))",
curve);
if (err) {
rc = ssh_ecdh_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate a key pair");
goto out;
}
err = gcry_pk_genkey(&key, param);
if (err)
goto out;
q_s_string = ssh_sexp_extract_mpi(key,
"q",
GCRYMPI_FMT_USG,
GCRYMPI_FMT_STD);
if (q_s_string == NULL) {
goto out;
}
session->next_crypto->ecdh_privkey = key;
key = NULL;
session->next_crypto->ecdh_server_pubkey = q_s_string;
/* build k and session_id */
rc = ecdh_build_k(session);
if (rc != SSH_OK) {
@@ -362,7 +359,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){
"bSSS",
SSH2_MSG_KEXDH_REPLY,
pubkey_blob, /* host's pubkey */
q_s_string, /* ecdh public key */
session->next_crypto->ecdh_server_pubkey, /* ecdh public key */
sig_blob); /* signature blob */
SSH_STRING_FREE(sig_blob);
@@ -387,8 +384,6 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){
}
out:
gcry_sexp_release(param);
gcry_sexp_release(key);
if (rc == SSH_ERROR) {
ssh_buffer_reinit(session->out_buffer);
session->session_state = SSH_SESSION_STATE_ERROR;

View File

@@ -38,25 +38,39 @@
#ifdef HAVE_ECDH
static mbedtls_ecp_group_id ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) {
if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) {
static mbedtls_ecp_group_id
ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type)
{
switch (kex_type) {
case SSH_KEX_ECDH_SHA2_NISTP256:
case SSH_KEX_MLKEM768NISTP256_SHA256:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
return MBEDTLS_ECP_DP_SECP256R1;
} else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) {
case SSH_KEX_ECDH_SHA2_NISTP384:
return MBEDTLS_ECP_DP_SECP384R1;
} else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) {
case SSH_KEX_ECDH_SHA2_NISTP521:
return MBEDTLS_ECP_DP_SECP521R1;
default:
return MBEDTLS_ECP_DP_NONE;
}
return MBEDTLS_ECP_DP_NONE;
}
int ssh_client_ecdh_init(ssh_session session)
int ssh_ecdh_init(ssh_session session)
{
ssh_string client_pubkey = NULL;
mbedtls_ecp_group grp;
int rc;
mbedtls_ecp_group grp;
mbedtls_ecp_group_id curve;
mbedtls_ctr_drbg_context *ctr_drbg = NULL;
mbedtls_ecp_keypair *ecdh_privkey = NULL;
ssh_string pubkey = NULL;
ssh_string *pubkey_loc = NULL;
if (session->server) {
pubkey_loc = &session->next_crypto->ecdh_server_pubkey;
} else {
pubkey_loc = &session->next_crypto->ecdh_client_pubkey;
}
ctr_drbg = ssh_get_mbedtls_ctr_drbg_context();
@@ -65,11 +79,6 @@ int ssh_client_ecdh_init(ssh_session session)
return SSH_ERROR;
}
rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT);
if (rc < 0) {
return SSH_ERROR;
}
/* Free any previously allocated privkey */
if (session->next_crypto->ecdh_privkey != NULL) {
mbedtls_ecp_keypair_free(session->next_crypto->ecdh_privkey);
@@ -93,42 +102,56 @@ int ssh_client_ecdh_init(ssh_session session)
}
rc = mbedtls_ecp_gen_keypair(&grp,
&ecdh_privkey->MBEDTLS_PRIVATE(d),
&ecdh_privkey->MBEDTLS_PRIVATE(Q),
mbedtls_ctr_drbg_random,
ctr_drbg);
&ecdh_privkey->MBEDTLS_PRIVATE(d),
&ecdh_privkey->MBEDTLS_PRIVATE(Q),
mbedtls_ctr_drbg_random,
ctr_drbg);
if (rc != 0) {
rc = SSH_ERROR;
goto out;
}
client_pubkey = make_ecpoint_string(&grp,
&ecdh_privkey->MBEDTLS_PRIVATE(Q));
if (client_pubkey == NULL) {
pubkey = make_ecpoint_string(&grp, &ecdh_privkey->MBEDTLS_PRIVATE(Q));
if (pubkey == NULL) {
rc = SSH_ERROR;
goto out;
}
rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey);
SSH_STRING_FREE(*pubkey_loc);
*pubkey_loc = pubkey;
pubkey = NULL;
out:
mbedtls_ecp_group_free(&grp);
SSH_STRING_FREE(pubkey);
return rc;
}
int ssh_client_ecdh_init(ssh_session session)
{
int rc;
rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT);
if (rc < 0) {
rc = SSH_ERROR;
goto out;
return SSH_ERROR;
}
SSH_STRING_FREE(session->next_crypto->ecdh_client_pubkey);
session->next_crypto->ecdh_client_pubkey = client_pubkey;
client_pubkey = NULL;
rc = ssh_ecdh_init(session);
if (rc < 0) {
return SSH_ERROR;
}
rc = ssh_buffer_add_ssh_string(session->out_buffer,
session->next_crypto->ecdh_client_pubkey);
if (rc < 0) {
return SSH_ERROR;
}
/* register the packet callbacks */
ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks);
session->dh_handshake_state = DH_STATE_INIT_SENT;
rc = ssh_packet_send(session);
out:
mbedtls_ecp_group_free(&grp);
SSH_STRING_FREE(client_pubkey);
return rc;
}
@@ -204,24 +227,15 @@ out:
SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){
ssh_string q_c_string = NULL;
ssh_string q_s_string = NULL;
mbedtls_ecp_group grp;
mbedtls_ctr_drbg_context *ctr_drbg = NULL;
mbedtls_ecp_keypair *ecdh_privkey = NULL;
ssh_key privkey = NULL;
enum ssh_digest_e digest = SSH_DIGEST_AUTO;
ssh_string sig_blob = NULL;
ssh_string pubkey_blob = NULL;
int rc;
mbedtls_ecp_group_id curve;
(void)type;
(void)user;
ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks);
curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type);
if (curve == MBEDTLS_ECP_DP_NONE) {
return SSH_ERROR;
}
q_c_string = ssh_buffer_get_ssh_string(packet);
if (q_c_string == NULL) {
@@ -229,45 +243,13 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){
return SSH_ERROR;
}
session->next_crypto->ecdh_privkey = malloc(sizeof(mbedtls_ecp_keypair));
if (session->next_crypto->ecdh_privkey == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
ecdh_privkey = session->next_crypto->ecdh_privkey;
session->next_crypto->ecdh_client_pubkey = q_c_string;
ctr_drbg = ssh_get_mbedtls_ctr_drbg_context();
mbedtls_ecp_group_init(&grp);
mbedtls_ecp_keypair_init(ecdh_privkey);
rc = mbedtls_ecp_group_load(&grp, curve);
if (rc != 0) {
rc = SSH_ERROR;
goto out;
rc = ssh_ecdh_init(session);
if (rc < 0) {
return SSH_ERROR;
}
rc = mbedtls_ecp_gen_keypair(&grp,
&ecdh_privkey->MBEDTLS_PRIVATE(d),
&ecdh_privkey->MBEDTLS_PRIVATE(Q),
mbedtls_ctr_drbg_random,
ctr_drbg);
if (rc != 0) {
rc = SSH_ERROR;
goto out;
}
q_s_string = make_ecpoint_string(&grp, &ecdh_privkey->MBEDTLS_PRIVATE(Q));
if (q_s_string == NULL) {
rc = SSH_ERROR;
goto out;
}
session->next_crypto->ecdh_server_pubkey = q_s_string;
/* build k and session_id */
rc = ecdh_build_k(session);
if (rc != SSH_OK) {
@@ -306,7 +288,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){
rc = ssh_buffer_pack(session->out_buffer, "bSSS",
SSH2_MSG_KEXDH_REPLY,
pubkey_blob, /* host's pubkey */
q_s_string, /* ecdh public key */
session->next_crypto->ecdh_server_pubkey, /* ecdh public key */
sig_blob); /* signature blob */
SSH_STRING_FREE(sig_blob);
@@ -333,7 +315,6 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){
}
out:
mbedtls_ecp_group_free(&grp);
if (rc == SSH_ERROR) {
ssh_buffer_reinit(session->out_buffer);
session->session_state = SSH_SESSION_STATE_ERROR;

View File

@@ -97,8 +97,8 @@ bcrypt_hash(ssh_blf_ctx *state, uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *o
}
/* zap */
explicit_bzero(ciphertext, sizeof(ciphertext));
explicit_bzero(cdata, sizeof(cdata));
ssh_burn(ciphertext, sizeof(ciphertext));
ssh_burn(cdata, sizeof(cdata));
}
int
@@ -180,12 +180,12 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl
}
/* zap */
explicit_bzero(out, sizeof(out));
explicit_bzero(state, sizeof(*state));
ssh_burn(out, sizeof(out));
ssh_burn(state, sizeof(*state));
free(state);
free(countsalt);
free(state);
free(countsalt);
return 0;
return 0;
}
#endif /* HAVE_BCRYPT_PBKDF */

8897
src/external/libcrux_mlkem768_sha3.c vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -27,9 +27,7 @@ sntrup761_enc (uint8_t *c, uint8_t *k, const uint8_t *pk,
void
sntrup761_dec (uint8_t *k, const uint8_t *c, const uint8_t *sk);
extern void crypto_hash_sha512 (unsigned char *out,
const unsigned char *in,
unsigned long long inlen);
extern int sha512(const unsigned char *digest, size_t len, unsigned char *hash);
#define MAX_LEN 761
@@ -701,7 +699,7 @@ Hash_prefix (unsigned char *out, int b, const unsigned char *in, int inlen)
x[0] = b;
for (i = 0; i < inlen; ++i)
x[i + 1] = in[i];
crypto_hash_sha512 (h, x, inlen + 1);
sha512 (x, inlen + 1, h);
for (i = 0; i < 32; ++i)
out[i] = h[i];
}

View File

@@ -88,7 +88,7 @@ static int ssh_gets(const char *prompt, char *buf, size_t len, int verify)
fprintf(stdout, "\nVerifying, please re-enter. %s", prompt);
fflush(stdout);
if (!fgets(key_string, (int)len, stdin)) {
explicit_bzero(key_string, len);
ssh_burn(key_string, len);
SAFE_FREE(key_string);
clearerr(stdin);
continue;
@@ -99,17 +99,17 @@ static int ssh_gets(const char *prompt, char *buf, size_t len, int verify)
fprintf(stdout, "\n");
if (strcmp(buf, key_string)) {
printf("\n\07\07Mismatch - try again\n");
explicit_bzero(key_string, len);
ssh_burn(key_string, len);
SAFE_FREE(key_string);
fflush(stdout);
continue;
}
explicit_bzero(key_string, len);
ssh_burn(key_string, len);
SAFE_FREE(key_string);
}
ok = 1;
}
explicit_bzero(tmp, len);
ssh_burn(tmp, len);
free(tmp);
return ok;
@@ -152,7 +152,7 @@ int ssh_getpass(const char *prompt,
SetConsoleMode(h, mode);
if (!ok) {
explicit_bzero(buf, len);
ssh_burn(buf, len);
return -1;
}
@@ -257,8 +257,8 @@ int ssh_getpass(const char *prompt,
}
/* disable nonblocking I/O */
if (fd & O_NDELAY) {
ok = fcntl(0, F_SETFL, fd & ~O_NDELAY);
if (fd & O_NONBLOCK) {
ok = fcntl(0, F_SETFL, fd & ~O_NONBLOCK);
if (ok < 0) {
perror("fcntl");
return -1;
@@ -273,7 +273,7 @@ int ssh_getpass(const char *prompt,
}
/* close fd */
if (fd & O_NDELAY) {
if (fd & O_NONBLOCK) {
ok = fcntl(0, F_SETFL, fd);
if (ok < 0) {
perror("fcntl");
@@ -282,7 +282,7 @@ int ssh_getpass(const char *prompt,
}
if (!ok) {
explicit_bzero(buf, len);
ssh_burn(buf, len);
return -1;
}

View File

@@ -21,6 +21,7 @@
#include "config.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
@@ -29,14 +30,17 @@
#include <gssapi/gssapi.h>
#include <libssh/buffer.h>
#include <libssh/callbacks.h>
#include <libssh/crypto.h>
#include <libssh/gssapi.h>
#include <libssh/libssh.h>
#include <libssh/ssh2.h>
#include <libssh/buffer.h>
#include <libssh/crypto.h>
#include <libssh/callbacks.h>
#include <libssh/string.h>
#include <libssh/server.h>
#include <libssh/ssh2.h>
#include <libssh/string.h>
#include <libssh/token.h>
static gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"};
/** @internal
* @initializes a gssapi context for authentication
@@ -110,7 +114,6 @@ ssh_gssapi_free(ssh_session session)
gss_release_oid(&min, &session->gssapi->client.oid);
gss_delete_sec_context(&min, &session->gssapi->ctx, GSS_C_NO_BUFFER);
SAFE_FREE(session->gssapi->mech.elements);
SAFE_FREE(session->gssapi->canonic_user);
SAFE_FREE(session->gssapi);
}
@@ -147,6 +150,47 @@ static int ssh_gssapi_send_response(ssh_session session, ssh_string oid)
#ifdef WITH_SERVER
/** @internal
* @brief get all the oids server supports
* @param[out] selected OID set of supported oids
* @returns SSH_OK if successful, SSH_ERROR otherwise
*/
int ssh_gssapi_server_oids(gss_OID_set *selected)
{
OM_uint32 maj_stat, min_stat;
size_t i;
char *ptr = NULL;
gss_OID_set supported; /* oids supported by server */
maj_stat = gss_indicate_mechs(&min_stat, &supported);
if (maj_stat != GSS_S_COMPLETE) {
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"indicate mechs",
maj_stat,
min_stat);
return SSH_ERROR;
}
for (i = 0; i < supported->count; ++i) {
ptr = ssh_get_hexa(supported->elements[i].elements,
supported->elements[i].length);
/* According to RFC 4462 we MUST NOT use SPNEGO */
if (supported->elements[i].length == spnego_oid.length &&
memcmp(supported->elements[i].elements,
spnego_oid.elements,
supported->elements[i].length) == 0) {
SAFE_FREE(ptr);
continue;
}
SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr);
SAFE_FREE(ptr);
}
*selected = supported;
return SSH_OK;
}
/** @internal
* @brief handles an user authentication using GSSAPI
*/
@@ -154,12 +198,9 @@ int
ssh_gssapi_handle_userauth(ssh_session session, const char *user,
uint32_t n_oid, ssh_string *oids)
{
char service_name[] = "host";
gss_buffer_desc name_buf;
gss_name_t server_name; /* local server fqdn */
char hostname[NI_MAXHOST] = {0};
OM_uint32 maj_stat, min_stat;
size_t i;
char *ptr = NULL;
gss_OID_set supported; /* oids supported by server */
gss_OID_set both_supported; /* oids supported by both client and server */
gss_OID_set selected; /* oid selected for authentication */
@@ -167,12 +208,22 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
size_t oid_count=0;
struct gss_OID_desc_struct oid;
int rc;
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
rc = gethostname(hostname, 64);
if (rc != 0) {
SSH_LOG(SSH_LOG_TRACE,
"Error getting hostname: %s",
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
return SSH_ERROR;
}
/* Destroy earlier GSSAPI context if any */
ssh_gssapi_free(session);
rc = ssh_gssapi_init(session);
if (rc == SSH_ERROR)
if (rc == SSH_ERROR) {
return rc;
}
/* Callback should select oid and acquire credential */
if (ssh_callbacks_exists(session->server_callbacks,
@@ -197,23 +248,13 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
/* Default implementation for selecting oid and acquiring credential */
gss_create_empty_oid_set(&min_stat, &both_supported);
maj_stat = gss_indicate_mechs(&min_stat, &supported);
if (maj_stat != GSS_S_COMPLETE) {
SSH_LOG(SSH_LOG_DEBUG, "indicate mechs %d, %d", maj_stat, min_stat);
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"indicate mechs",
maj_stat,
min_stat);
gss_release_oid_set(&min_stat, &both_supported);
/* Get the server supported oids */
rc = ssh_gssapi_server_oids(&supported);
if (rc != SSH_OK) {
return SSH_ERROR;
}
for (i=0; i < supported->count; ++i){
ptr = ssh_get_hexa(supported->elements[i].elements, supported->elements[i].length);
SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr);
free(ptr);
}
/* Loop through client supported oids */
for (i=0 ; i< n_oid ; ++i){
unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]);
size_t len = ssh_string_len(oids[i]);
@@ -225,8 +266,10 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID");
continue;
}
/* Convert oid from string to gssapi format */
oid.elements = &oid_s[2];
oid.length = len - 2;
/* Check if this client oid is supported by server */
gss_test_oid_set_member(&min_stat,&oid,supported,&present);
if(present){
gss_add_oid_set_member(&min_stat,&oid,&both_supported);
@@ -241,28 +284,23 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
return SSH_OK;
}
name_buf.value = service_name;
name_buf.length = strlen(name_buf.value) + 1;
maj_stat = gss_import_name(&min_stat, &name_buf,
(gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name);
if (maj_stat != GSS_S_COMPLETE) {
SSH_LOG(SSH_LOG_DEBUG, "importing name %d, %d", maj_stat, min_stat);
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"importing name",
maj_stat,
min_stat);
rc = ssh_gssapi_import_name(session->gssapi, hostname);
if (rc != SSH_OK) {
ssh_auth_reply_default(session, 0);
gss_release_oid_set(&min_stat, &both_supported);
return -1;
return SSH_ERROR;
}
maj_stat = gss_acquire_cred(&min_stat, server_name, 0,
both_supported, GSS_C_ACCEPT,
&session->gssapi->server_creds, &selected, NULL);
gss_release_name(&min_stat, &server_name);
maj_stat = gss_acquire_cred(&min_stat,
session->gssapi->client.server_name,
0,
both_supported,
GSS_C_ACCEPT,
&session->gssapi->server_creds,
&selected,
NULL);
gss_release_oid_set(&min_stat, &both_supported);
if (maj_stat != GSS_S_COMPLETE) {
SSH_LOG(SSH_LOG_TRACE, "error acquiring credentials %d, %d", maj_stat, min_stat);
ssh_gssapi_log_error(SSH_LOG_TRACE,
"acquiring creds",
maj_stat,
@@ -270,8 +308,7 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
ssh_auth_reply_default(session,0);
return SSH_ERROR;
}
SSH_LOG(SSH_LOG_DEBUG, "acquiring credentials %d, %d", maj_stat, min_stat);
SSH_LOG(SSH_LOG_DEBUG, "acquired credentials");
/* finding which OID from client we selected */
for (i=0 ; i< n_oid ; ++i){
@@ -293,17 +330,8 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user,
break;
}
}
session->gssapi->mech.length = oid.length;
session->gssapi->mech.elements = malloc(oid.length);
if (session->gssapi->mech.elements == NULL){
ssh_set_error_oom(session);
gss_release_oid_set(&min_stat, &selected);
return SSH_ERROR;
}
memcpy(session->gssapi->mech.elements, oid.elements, oid.length);
gss_release_oid_set(&min_stat, &selected);
session->gssapi->user = strdup(user);
session->gssapi->service = service_name;
session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN;
return ssh_gssapi_send_response(session, oids[i]);
}
@@ -381,7 +409,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server)
return SSH_PACKET_USED;
}
hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token));
SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa);
SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s", hexa);
SAFE_FREE(hexa);
input_token.length = ssh_string_len(token);
input_token.value = ssh_string_data(token);
@@ -400,7 +428,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server)
}
if (GSS_ERROR(maj_stat)){
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"Gssapi error",
"accepting token failed",
maj_stat,
min_stat);
gss_release_buffer(&min_stat, &output_token);
@@ -428,7 +456,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server)
gss_release_buffer(&min_stat, &output_token);
gss_release_name(&min_stat, &client_name);
if(maj_stat == GSS_S_COMPLETE){
if (maj_stat == GSS_S_COMPLETE) {
session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC;
}
return SSH_PACKET_USED;
@@ -436,7 +464,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server)
#endif /* WITH_SERVER */
static ssh_buffer ssh_gssapi_build_mic(ssh_session session)
ssh_buffer ssh_gssapi_build_mic(ssh_session session, const char *context)
{
struct ssh_crypto_struct *crypto = NULL;
ssh_buffer mic_buffer = NULL;
@@ -456,11 +484,12 @@ static ssh_buffer ssh_gssapi_build_mic(ssh_session session)
rc = ssh_buffer_pack(mic_buffer,
"dPbsss",
crypto->session_id_len,
crypto->session_id_len, crypto->session_id,
crypto->session_id_len,
crypto->session_id,
SSH2_MSG_USERAUTH_REQUEST,
session->gssapi->user,
"ssh-connection",
"gssapi-with-mic");
context);
if (rc != SSH_OK) {
ssh_set_error_oom(session);
SSH_BUFFER_FREE(mic_buffer);
@@ -483,24 +512,27 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic)
(void)user;
(void)type;
SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_USERAUTH_GSSAPI_MIC");
SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_USERAUTH_GSSAPI_MIC");
mic_token = ssh_buffer_get_ssh_string(packet);
if (mic_token == NULL) {
ssh_set_error(session, SSH_FATAL, "Missing MIC in packet");
goto error;
}
if (session->gssapi == NULL
|| session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) {
ssh_set_error(session, SSH_FATAL, "Received SSH_MSG_USERAUTH_GSSAPI_MIC in invalid state");
if (session->gssapi == NULL ||
session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) {
ssh_set_error(session,
SSH_FATAL,
"Received SSH_MSG_USERAUTH_GSSAPI_MIC in invalid state");
goto error;
}
mic_buffer = ssh_gssapi_build_mic(session);
mic_buffer = ssh_gssapi_build_mic(session, "gssapi-with-mic");
if (mic_buffer == NULL) {
ssh_set_error_oom(session);
goto error;
}
if (ssh_callbacks_exists(session->server_callbacks, gssapi_verify_mic_function)){
if (ssh_callbacks_exists(session->server_callbacks,
gssapi_verify_mic_function)) {
int rc = session->server_callbacks->gssapi_verify_mic_function(session, mic_token,
ssh_buffer_get(mic_buffer), ssh_buffer_get_len(mic_buffer),
session->server_callbacks->userdata);
@@ -513,7 +545,11 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic)
mic_token_buf.length = ssh_string_len(mic_token);
mic_token_buf.value = ssh_string_data(mic_token);
maj_stat = gss_verify_mic(&min_stat, session->gssapi->ctx, &mic_buf, &mic_token_buf, NULL);
maj_stat = gss_verify_mic(&min_stat,
session->gssapi->ctx,
&mic_buf,
&mic_token_buf,
NULL);
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"verifying MIC",
maj_stat,
@@ -580,12 +616,14 @@ ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session)
*/
void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds)
{
int rc;
if (session == NULL) {
return;
}
if (session->gssapi == NULL) {
ssh_gssapi_init(session);
if (session->gssapi == NULL) {
rc = ssh_gssapi_init(session);
if (rc == SSH_ERROR) {
return;
}
}
@@ -626,9 +664,182 @@ fail:
return SSH_ERROR;
}
/** @brief returns the OIDs of the mechs that have usable credentials
/** @internal
* @brief Get the base64 encoding of md5 of the oid to add as suffix to GSSAPI
* key exchange algorithms.
*
* @param[in] oid The OID as a ssh_string
*
* @returns the hash or NULL on error
*/
static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids)
char *ssh_gssapi_oid_hash(ssh_string oid)
{
unsigned char *h = NULL;
int rc;
char *base64 = NULL;
h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char));
if (h == NULL) {
return NULL;
}
rc = md5(ssh_string_data(oid), ssh_string_len(oid), h);
if (rc != SSH_OK) {
SAFE_FREE(h);
return NULL;
}
base64 = (char *)bin_to_base64(h, 16);
SAFE_FREE(h);
return base64;
}
/** @internal
* @brief Check if client has GSSAPI mechanisms configured
*
* @param[in] session The SSH session
*
* @returns SSH_OK if any one of the mechanisms is configured or NULL
*/
int ssh_gssapi_check_client_config(ssh_session session)
{
OM_uint32 maj_stat, min_stat;
size_t i;
char *ptr = NULL;
gss_OID_set supported = GSS_C_NO_OID_SET;
gss_name_t client_id = GSS_C_NO_NAME;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
OM_uint32 oflags;
struct ssh_gssapi_struct *gssapi = NULL;
int ret = SSH_ERROR;
gss_OID_set one_oidset = GSS_C_NO_OID_SET;
maj_stat = gss_indicate_mechs(&min_stat, &supported);
if (maj_stat != GSS_S_COMPLETE) {
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"indicate mechs",
maj_stat,
min_stat);
return SSH_ERROR;
}
for (i = 0; i < supported->count; ++i) {
gssapi = calloc(1, sizeof(struct ssh_gssapi_struct));
if (gssapi == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
gssapi->server_creds = GSS_C_NO_CREDENTIAL;
gssapi->client_creds = GSS_C_NO_CREDENTIAL;
gssapi->ctx = GSS_C_NO_CONTEXT;
gssapi->state = SSH_GSSAPI_STATE_NONE;
/* According to RFC 4462 we MUST NOT use SPNEGO */
if (supported->elements[i].length == spnego_oid.length &&
memcmp(supported->elements[i].elements,
spnego_oid.elements,
supported->elements[i].length) == 0) {
ret = SSH_ERROR;
goto end;
}
gss_create_empty_oid_set(&min_stat, &one_oidset);
gss_add_oid_set_member(&min_stat, &supported->elements[i], &one_oidset);
if (session->opts.gss_client_identity != NULL) {
namebuf.value = (void *)session->opts.gss_client_identity;
namebuf.length = strlen(session->opts.gss_client_identity);
maj_stat = gss_import_name(&min_stat,
&namebuf,
GSS_C_NT_USER_NAME,
&client_id);
if (GSS_ERROR(maj_stat)) {
ret = SSH_ERROR;
goto end;
}
}
maj_stat = gss_acquire_cred(&min_stat,
client_id,
GSS_C_INDEFINITE,
one_oidset,
GSS_C_INITIATE,
&gssapi->client.creds,
NULL,
NULL);
if (GSS_ERROR(maj_stat)) {
ssh_gssapi_log_error(SSH_LOG_WARN,
"acquiring credential",
maj_stat,
min_stat);
ret = SSH_ERROR;
goto end;
}
ret = ssh_gssapi_import_name(gssapi, session->opts.host);
if (ret != SSH_OK) {
goto end;
}
maj_stat =
ssh_gssapi_init_ctx(gssapi, &input_token, &output_token, &oflags);
if (GSS_ERROR(maj_stat)) {
ssh_gssapi_log_error(SSH_LOG_WARN,
"initializing context",
maj_stat,
min_stat);
ret = SSH_ERROR;
goto end;
}
ptr = ssh_get_hexa(supported->elements[i].elements,
supported->elements[i].length);
SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr);
free(ptr);
/* If at least one mechanism is configured then return successfully */
ret = SSH_OK;
end:
if (ret == SSH_ERROR) {
SSH_LOG(SSH_LOG_WARN, "GSSAPI not configured correctly");
}
SAFE_FREE(gssapi->user);
gss_release_oid_set(&min_stat, &one_oidset);
gss_release_name(&min_stat, &gssapi->client.server_name);
gss_release_cred(&min_stat, &gssapi->server_creds);
gss_release_cred(&min_stat, &gssapi->client.creds);
gss_release_oid(&min_stat, &gssapi->client.oid);
gss_release_buffer(&min_stat, &output_token);
gss_delete_sec_context(&min_stat, &gssapi->ctx, GSS_C_NO_BUFFER);
SAFE_FREE(gssapi->canonic_user);
SAFE_FREE(gssapi);
if (ret == SSH_OK) {
break;
}
}
gss_release_oid_set(&min_stat, &supported);
return ret;
}
/** @internal
* @brief acquires a credential and returns a set of mechanisms for which it is
* valid
*
* @param[in] session The SSH session
* @param[out] valid_oids The set of OIDs for which the credential is valid
*
* @returns SSH_OK if successful, SSH_ERROR otherwise
*/
int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids)
{
OM_uint32 maj_stat, min_stat, lifetime;
gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
@@ -639,6 +850,10 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids)
char *ptr = NULL;
int ret;
if (session == NULL || session->gssapi == NULL) {
return SSH_ERROR;
}
if (session->gssapi->client.client_deleg_creds == NULL) {
if (session->opts.gss_client_identity != NULL) {
namebuf.value = (void *)session->opts.gss_client_identity;
@@ -675,6 +890,7 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids)
goto end;
}
}
SSH_LOG(SSH_LOG_DEBUG, "acquired credentials");
gss_create_empty_oid_set(&min_stat, valid_oids);
@@ -702,6 +918,189 @@ end:
return ret;
}
/** @internal
* @brief Add suffixes of oid hash to each GSSAPI key exchange algorithm
* @param[in] session current session handler
* @returns string suffixed kex algorithms or NULL on error
*/
char *ssh_gssapi_kex_mechs(ssh_session session)
{
size_t i, j;
/* oid selected for authentication */
gss_OID_set selected = GSS_C_NO_OID_SET;
ssh_string *oids = NULL;
int rc;
size_t n_oids = 0;
struct ssh_tokens_st *algs = NULL;
char *oid_hash = NULL;
const char *gss_algs = session->opts.gssapi_key_exchange_algs;
char *new_gss_algs = NULL;
char gss_kex_algs[8000] = {0};
OM_uint32 min_stat;
size_t offset = 0;
/* Get supported oids */
if (session->server) {
#ifdef WITH_SERVER
rc = ssh_gssapi_server_oids(&selected);
if (rc == SSH_ERROR) {
return NULL;
}
#endif
} else {
rc = ssh_gssapi_client_identity(session, &selected);
if (rc == SSH_ERROR) {
return NULL;
}
}
ssh_gssapi_free(session);
n_oids = selected->count;
SSH_LOG(SSH_LOG_DEBUG, "Sending %zu oids", n_oids);
oids = calloc(n_oids, sizeof(ssh_string));
if (oids == NULL) {
ssh_set_error_oom(session);
return NULL;
}
/* Check if algorithms are valid */
new_gss_algs =
ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, gss_algs);
if (gss_algs == NULL) {
ssh_set_error(
session,
SSH_FATAL,
"GSSAPI key exchange algorithms not supported or invalid");
rc = SSH_ERROR;
goto out;
}
algs = ssh_tokenize(new_gss_algs, ',');
if (algs == NULL) {
ssh_set_error(session,
SSH_FATAL,
"Couldn't tokenize GSSAPI key exchange algs");
rc = SSH_ERROR;
goto out;
}
for (i = 0; i < n_oids; ++i) {
oids[i] = ssh_string_new(selected->elements[i].length + 2);
if (oids[i] == NULL) {
ssh_set_error_oom(session);
rc = SSH_ERROR;
goto out;
}
((unsigned char *)oids[i]->data)[0] = SSH_OID_TAG;
((unsigned char *)oids[i]->data)[1] = selected->elements[i].length;
memcpy((unsigned char *)oids[i]->data + 2,
selected->elements[i].elements,
selected->elements[i].length);
/* Get the algorithm suffix */
oid_hash = ssh_gssapi_oid_hash(oids[i]);
if (oid_hash == NULL) {
ssh_set_error_oom(session);
rc = SSH_ERROR;
goto out;
}
/* For each oid loop through the algorithms, append the oid and append
* the algorithms to a string */
for (j = 0; algs->tokens[j]; j++) {
if (sizeof(gss_kex_algs) < offset) {
ssh_set_error(session, SSH_FATAL, "snprintf failed");
rc = SSH_ERROR;
goto out;
}
rc = snprintf(&gss_kex_algs[offset],
sizeof(gss_kex_algs) - offset,
"%s%s,",
algs->tokens[j],
oid_hash);
if (rc < 0 || rc >= (ssize_t)sizeof(gss_kex_algs)) {
ssh_set_error(session, SSH_FATAL, "snprintf failed");
rc = SSH_ERROR;
goto out;
}
/* + 1 for ',' */
offset += strlen(algs->tokens[j]) + strlen(oid_hash) + 1;
}
SAFE_FREE(oid_hash);
SSH_STRING_FREE(oids[i]);
}
rc = SSH_OK;
out:
SAFE_FREE(oid_hash);
SAFE_FREE(oids);
SAFE_FREE(new_gss_algs);
gss_release_oid_set(&min_stat, &selected);
ssh_tokens_free(algs);
if (rc != SSH_OK) {
return NULL;
}
return strdup(gss_kex_algs);
}
int ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host)
{
gss_buffer_desc hostname;
char name_buf[256] = {0};
OM_uint32 maj_stat, min_stat;
/* import target host name */
snprintf(name_buf, sizeof(name_buf), "host@%s", host);
hostname.value = name_buf;
hostname.length = strlen(name_buf) + 1;
maj_stat = gss_import_name(&min_stat,
&hostname,
(gss_OID)GSS_C_NT_HOSTBASED_SERVICE,
&gssapi->client.server_name);
SSH_LOG(SSH_LOG_DEBUG, "importing name: %s", name_buf);
if (maj_stat != GSS_S_COMPLETE) {
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"error importing name",
maj_stat,
min_stat);
}
return maj_stat;
}
OM_uint32 ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi,
gss_buffer_desc *input_token,
gss_buffer_desc *output_token,
OM_uint32 *ret_flags)
{
OM_uint32 maj_stat, min_stat;
maj_stat = gss_init_sec_context(&min_stat,
gssapi->client.creds,
&gssapi->ctx,
gssapi->client.server_name,
gssapi->client.oid,
gssapi->client.flags,
0,
NULL,
input_token,
NULL,
output_token,
ret_flags,
NULL);
if (GSS_ERROR(maj_stat)) {
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"initializing gssapi context",
maj_stat,
min_stat);
}
return maj_stat;
}
/**
* @brief launches a gssapi-with-mic auth request
* @returns SSH_AUTH_ERROR: A serious error happened\n
@@ -716,9 +1115,7 @@ int ssh_gssapi_auth_mic(ssh_session session)
ssh_string *oids = NULL;
int rc;
size_t n_oids = 0;
OM_uint32 maj_stat, min_stat;
char name_buf[256] = {0};
gss_buffer_desc hostname;
OM_uint32 min_stat;
const char *gss_host = session->opts.host;
/* Destroy earlier GSSAPI context if any */
@@ -731,20 +1128,9 @@ int ssh_gssapi_auth_mic(ssh_session session)
if (session->opts.gss_server_identity != NULL) {
gss_host = session->opts.gss_server_identity;
}
/* import target host name */
snprintf(name_buf, sizeof(name_buf), "host@%s", gss_host);
hostname.value = name_buf;
hostname.length = strlen(name_buf) + 1;
maj_stat = gss_import_name(&min_stat, &hostname,
(gss_OID)GSS_C_NT_HOSTBASED_SERVICE,
&session->gssapi->client.server_name);
if (maj_stat != GSS_S_COMPLETE) {
SSH_LOG(SSH_LOG_DEBUG, "importing name %d, %d", maj_stat, min_stat);
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"importing name",
maj_stat,
min_stat);
rc = ssh_gssapi_import_name(session->gssapi, gss_host);
if (rc != SSH_OK) {
return SSH_AUTH_DENIED;
}
@@ -757,7 +1143,7 @@ int ssh_gssapi_auth_mic(ssh_session session)
SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi to host %s with user %s",
session->opts.host, session->gssapi->user);
rc = ssh_gssapi_match(session, &selected);
rc = ssh_gssapi_client_identity(session, &selected);
if (rc == SSH_ERROR) {
return SSH_AUTH_DENIED;
}
@@ -800,6 +1186,50 @@ out:
return SSH_AUTH_ERROR;
}
/**
* @brief Get the MIC for "gssapi-keyex" authentication.
* @returns SSH_ERROR: A serious error happened\n
* SSH_OK: MIC token is stored in mic_token_buf
*/
int ssh_gssapi_auth_keyex_mic(ssh_session session,
gss_buffer_desc *mic_token_buf)
{
ssh_buffer buf = NULL;
gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER;
OM_uint32 maj_stat, min_stat;
if (session->gssapi == NULL || session->gssapi->ctx == NULL) {
ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized");
return SSH_ERROR;
}
buf = ssh_gssapi_build_mic(session, "gssapi-keyex");
if (buf == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
mic_buf.length = ssh_buffer_get_len(buf);
mic_buf.value = ssh_buffer_get(buf);
maj_stat = gss_get_mic(&min_stat,
session->gssapi->ctx,
GSS_C_QOP_DEFAULT,
&mic_buf,
mic_token_buf);
if (GSS_ERROR(maj_stat)) {
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"generating MIC",
maj_stat,
min_stat);
SSH_BUFFER_FREE(buf);
return SSH_ERROR;
}
SSH_BUFFER_FREE(buf);
return SSH_OK;
}
static gss_OID ssh_gssapi_oid_from_string(ssh_string oid_s)
{
gss_OID ret = NULL;
@@ -869,22 +1299,12 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){
session->gssapi->client.flags |= GSS_C_DELEG_FLAG;
}
/* prepare the first TOKEN response */
maj_stat = gss_init_sec_context(&min_stat,
session->gssapi->client.creds,
&session->gssapi->ctx,
session->gssapi->client.server_name,
session->gssapi->client.oid,
session->gssapi->client.flags,
0, NULL, &input_token, NULL,
&output_token, NULL, NULL);
if(GSS_ERROR(maj_stat)){
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"Initializing gssapi context",
maj_stat,
min_stat);
maj_stat =
ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL);
if (GSS_ERROR(maj_stat)) {
goto error;
}
if (output_token.length != 0){
hexa = ssh_get_hexa(output_token.value, output_token.length);
SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s", hexa);
@@ -920,7 +1340,7 @@ static int ssh_gssapi_send_mic(ssh_session session)
SSH_LOG(SSH_LOG_PACKET,"Sending SSH_MSG_USERAUTH_GSSAPI_MIC");
mic_buffer = ssh_gssapi_build_mic(session);
mic_buffer = ssh_gssapi_build_mic(session, "gssapi-with-mic");
if (mic_buffer == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
@@ -984,27 +1404,13 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client)
hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token));
SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa);
SAFE_FREE(hexa);
input_token.length = ssh_string_len(token);
input_token.value = ssh_string_data(token);
maj_stat = gss_init_sec_context(&min_stat,
session->gssapi->client.creds,
&session->gssapi->ctx,
session->gssapi->client.server_name,
session->gssapi->client.oid,
session->gssapi->client.flags,
0, NULL, &input_token, NULL,
&output_token, NULL, NULL);
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"accepting token",
maj_stat,
min_stat);
maj_stat =
ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL);
SSH_STRING_FREE(token);
if (GSS_ERROR(maj_stat)){
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"Gssapi error",
maj_stat,
min_stat);
if (GSS_ERROR(maj_stat)) {
goto error;
}

908
src/hybrid_mlkem.c Normal file
View File

@@ -0,0 +1,908 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Sahana Prasad <sahana@redhat.com>
* Author: Pavol Žáčik <pzacik@redhat.com>
* Author: Claude (Anthropic)
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include "libssh/buffer.h"
#include "libssh/hybrid_mlkem.h"
#include "libssh/pki.h"
#include "libssh/ssh2.h"
/* sorry, this needs to come last to avoid header dependency issues */
#include "libssh/bignum.h"
static SSH_PACKET_CALLBACK(ssh_packet_client_hybrid_mlkem_reply);
static ssh_packet_callback dh_client_callbacks[] = {
ssh_packet_client_hybrid_mlkem_reply,
};
static struct ssh_packet_callbacks_struct ssh_hybrid_mlkem_client_callbacks = {
.start = SSH2_MSG_KEX_HYBRID_REPLY,
.n_callbacks = 1,
.callbacks = dh_client_callbacks,
.user = NULL,
};
static ssh_string derive_curve25519_secret(ssh_session session)
{
ssh_string secret = NULL;
int rc;
secret = ssh_string_new(CURVE25519_PUBKEY_SIZE);
if (secret == NULL) {
ssh_set_error_oom(session);
return NULL;
}
rc = ssh_curve25519_create_k(session, ssh_string_data(secret));
if (rc != SSH_OK) {
ssh_set_error(session,
SSH_FATAL,
"Curve25519 secret derivation failed");
ssh_string_free(secret);
return NULL;
}
return secret;
}
static ssh_string derive_nist_curve_secret(ssh_session session,
size_t secret_size)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_string secret = NULL;
int rc;
rc = ecdh_build_k(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "ECDH secret derivation failed");
return NULL;
}
secret = ssh_make_padded_bignum_string(crypto->shared_secret, secret_size);
if (secret == NULL) {
ssh_set_error(session, SSH_FATAL, "Failed to encode the shared secret");
}
bignum_safe_free(crypto->shared_secret);
return secret;
}
static ssh_string derive_ecdh_secret(ssh_session session)
{
ssh_string secret = NULL;
switch (session->next_crypto->kex_type) {
case SSH_KEX_MLKEM768X25519_SHA256:
secret = derive_curve25519_secret(session);
break;
case SSH_KEX_MLKEM768NISTP256_SHA256:
secret = derive_nist_curve_secret(session, NISTP256_SHARED_SECRET_SIZE);
break;
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
secret = derive_nist_curve_secret(session, NISTP384_SHARED_SECRET_SIZE);
break;
#endif
default:
ssh_set_error(session, SSH_FATAL, "Unsupported KEX type");
return NULL;
}
return secret;
}
static int derive_hybrid_secret(ssh_session session,
ssh_mlkem_shared_secret mlkem_shared_secret,
ssh_string ecdh_shared_secret)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_buffer combined_secret = NULL;
int (*digest)(const unsigned char *, size_t, unsigned char *) = NULL;
size_t digest_len;
int rc, ret = SSH_ERROR;
switch (crypto->kex_type) {
case SSH_KEX_MLKEM768X25519_SHA256:
case SSH_KEX_MLKEM768NISTP256_SHA256:
digest = sha256;
digest_len = SHA256_DIGEST_LEN;
break;
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
digest = sha384;
digest_len = SHA384_DIGEST_LEN;
break;
#endif
default:
ssh_set_error(session, SSH_FATAL, "Unsupported KEX type");
goto cleanup;
}
/* Concatenate the two shared secrets */
combined_secret = ssh_buffer_new();
if (combined_secret == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
ssh_buffer_set_secure(combined_secret);
rc = ssh_buffer_pack(combined_secret,
"PP",
MLKEM_SHARED_SECRET_SIZE,
mlkem_shared_secret,
ssh_string_len(ecdh_shared_secret),
ssh_string_data(ecdh_shared_secret));
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to concatenate shared secrets");
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("Concatenated shared secrets",
ssh_buffer_get(combined_secret),
ssh_buffer_get_len(combined_secret));
#endif
/* Store the hashed combined shared secrets */
ssh_string_burn(crypto->hybrid_shared_secret);
ssh_string_free(crypto->hybrid_shared_secret);
crypto->hybrid_shared_secret = ssh_string_new(digest_len);
if (crypto->hybrid_shared_secret == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
rc = digest(ssh_buffer_get(combined_secret),
ssh_buffer_get_len(combined_secret),
ssh_string_data(crypto->hybrid_shared_secret));
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Shared secret hashing failed");
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("Hybrid shared secret",
ssh_string_data(crypto->hybrid_shared_secret),
digest_len);
#endif
ret = SSH_OK;
cleanup:
ssh_buffer_free(combined_secret);
return ret;
}
int ssh_client_hybrid_mlkem_init(ssh_session session)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_buffer client_init_buffer = NULL;
int rc, ret = SSH_ERROR;
SSH_LOG(SSH_LOG_TRACE, "Initializing hybrid ML-KEM key exchange");
/* Prepare a buffer to concatenate ML-KEM + ECDH public keys */
client_init_buffer = ssh_buffer_new();
if (client_init_buffer == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
/* Generate an ML-KEM keypair */
rc = ssh_mlkem_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate an ML-KEM keypair");
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ML-KEM client pubkey",
ssh_string_data(crypto->mlkem_client_pubkey),
ssh_string_len(crypto->mlkem_client_pubkey));
#endif
/* Generate an ECDH keypair and concatenate the public keys */
switch (crypto->kex_type) {
case SSH_KEX_MLKEM768X25519_SHA256:
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
ssh_set_error(session,
SSH_FATAL,
"Failed to generate a Curve25519 ECDH keypair");
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("Curve25519 client pubkey",
crypto->curve25519_client_pubkey,
CURVE25519_PUBKEY_SIZE);
#endif
rc = ssh_buffer_pack(client_init_buffer,
"PP",
ssh_string_len(crypto->mlkem_client_pubkey),
ssh_string_data(crypto->mlkem_client_pubkey),
CURVE25519_PUBKEY_SIZE,
crypto->curve25519_client_pubkey);
break;
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
rc = ssh_ecdh_init(session);
if (rc != SSH_OK) {
ssh_set_error(session,
SSH_FATAL,
"Failed to generate a NIST-curve ECDH keypair");
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ECDH client pubkey",
ssh_string_data(crypto->ecdh_client_pubkey),
ssh_string_len(crypto->ecdh_client_pubkey));
#endif
rc = ssh_buffer_pack(client_init_buffer,
"PP",
ssh_string_len(crypto->mlkem_client_pubkey),
ssh_string_data(crypto->mlkem_client_pubkey),
ssh_string_len(crypto->ecdh_client_pubkey),
ssh_string_data(crypto->ecdh_client_pubkey));
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported KEX type");
goto cleanup;
}
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to construct client init buffer");
goto cleanup;
}
/* Convert the client init buffer to an SSH string */
ssh_string_free(crypto->hybrid_client_init);
crypto->hybrid_client_init = ssh_string_new(ssh_buffer_get_len(client_init_buffer));
if (crypto->hybrid_client_init == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
rc = ssh_string_fill(crypto->hybrid_client_init,
ssh_buffer_get(client_init_buffer),
ssh_buffer_get_len(client_init_buffer));
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to convert client init to string");
goto cleanup;
}
rc = ssh_buffer_pack(session->out_buffer,
"bS",
SSH2_MSG_KEX_HYBRID_INIT,
crypto->hybrid_client_init);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to construct SSH_MSG_KEX_HYBRID_INIT");
goto cleanup;
}
ssh_packet_set_callbacks(session, &ssh_hybrid_mlkem_client_callbacks);
session->dh_handshake_state = DH_STATE_INIT_SENT;
rc = ssh_packet_send(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_HYBRID_INIT");
goto cleanup;
}
ret = SSH_OK;
cleanup:
ssh_buffer_free(client_init_buffer);
return ret;
}
static SSH_PACKET_CALLBACK(ssh_packet_client_hybrid_mlkem_reply)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
const struct mlkem_type_info *mlkem_info = NULL;
ssh_string pubkey_blob = NULL;
ssh_string signature = NULL;
ssh_mlkem_shared_secret mlkem_shared_secret;
ssh_string ecdh_shared_secret = NULL;
ssh_buffer server_reply_buffer = NULL;
size_t read_len;
size_t ecdh_server_pubkey_size;
int rc;
(void)type;
(void)user;
SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM hybrid server reply");
ssh_client_hybrid_mlkem_remove_callbacks(session);
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
ssh_set_error(session, SSH_FATAL, "Unknown ML-KEM type");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
pubkey_blob = ssh_buffer_get_ssh_string(packet);
if (pubkey_blob == NULL) {
ssh_set_error(session, SSH_FATAL, "No public key in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to import public key");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Get server reply containing ML-KEM ciphertext + ECDH public key */
ssh_string_free(crypto->hybrid_server_reply);
crypto->hybrid_server_reply = ssh_buffer_get_ssh_string(packet);
if (crypto->hybrid_server_reply == NULL) {
ssh_set_error(session, SSH_FATAL, "No server reply in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
server_reply_buffer = ssh_buffer_new();
if (server_reply_buffer == NULL) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_buffer_add_data(server_reply_buffer,
ssh_string_data(crypto->hybrid_server_reply),
ssh_string_len(crypto->hybrid_server_reply));
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to pack server reply to a buffer");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Store ML-KEM ciphertext for decapsulation and sessionid calculation */
ssh_string_free(crypto->mlkem_ciphertext);
crypto->mlkem_ciphertext = ssh_string_new(mlkem_info->ciphertext_size);
if (crypto->mlkem_ciphertext == NULL) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
read_len = ssh_buffer_get_data(server_reply_buffer,
ssh_string_data(crypto->mlkem_ciphertext),
mlkem_info->ciphertext_size);
if (read_len != mlkem_info->ciphertext_size) {
ssh_set_error(session,
SSH_FATAL,
"Could not read ML-KEM ciphertext from "
"the server reply buffer, buffer too short");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ML-KEM ciphertext",
ssh_string_data(crypto->mlkem_ciphertext),
ssh_string_len(crypto->mlkem_ciphertext));
#endif
/* Extract server ECDH public key */
switch (crypto->kex_type) {
case SSH_KEX_MLKEM768X25519_SHA256:
read_len = ssh_buffer_get_data(server_reply_buffer,
crypto->curve25519_server_pubkey,
CURVE25519_PUBKEY_SIZE);
if (read_len != CURVE25519_PUBKEY_SIZE) {
ssh_set_error(session,
SSH_FATAL,
"Could not read Curve25519 pubkey from "
"the server reply buffer, buffer too short");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
if (ssh_buffer_get_len(server_reply_buffer) > 0) {
ssh_set_error(session,
SSH_FATAL,
"Unrecognized data in the server reply buffer");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("Curve25519 server pubkey",
crypto->curve25519_server_pubkey,
CURVE25519_PUBKEY_SIZE);
#endif
break;
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
ecdh_server_pubkey_size = ssh_buffer_get_len(server_reply_buffer);
ssh_string_free(crypto->ecdh_server_pubkey);
crypto->ecdh_server_pubkey = ssh_string_new(ecdh_server_pubkey_size);
if (crypto->ecdh_server_pubkey == NULL) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
ssh_buffer_get_data(server_reply_buffer,
ssh_string_data(crypto->ecdh_server_pubkey),
ecdh_server_pubkey_size);
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ECDH server pubkey",
ssh_string_data(crypto->ecdh_server_pubkey),
ssh_string_len(crypto->ecdh_server_pubkey));
#endif
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported KEX type");
goto cleanup;
}
/* Decapsulate ML-KEM shared secret */
rc = ssh_mlkem_decapsulate(session, mlkem_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "ML-KEM decapsulation failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ML-KEM shared secret",
mlkem_shared_secret,
MLKEM_SHARED_SECRET_SIZE);
#endif
/* Derive the classical ECDH shared secret */
ecdh_shared_secret = derive_ecdh_secret(session);
if (ecdh_shared_secret == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ECDH shared secret",
ssh_string_data(ecdh_shared_secret),
ssh_string_len(ecdh_shared_secret));
#endif
/* Derive the final shared secret */
rc = derive_hybrid_secret(session, mlkem_shared_secret, ecdh_shared_secret);
if (rc != SSH_OK) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Get signature for verification */
signature = ssh_buffer_get_ssh_string(packet);
if (signature == NULL) {
ssh_set_error(session, SSH_FATAL, "No signature in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
crypto->dh_server_signature = signature;
/* Send the MSG_NEWKEYS */
rc = ssh_packet_send_newkeys(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
cleanup:
ssh_burn(mlkem_shared_secret, sizeof(mlkem_shared_secret));
ssh_string_burn(ecdh_shared_secret);
ssh_string_free(ecdh_shared_secret);
ssh_string_free(pubkey_blob);
ssh_buffer_free(server_reply_buffer);
return SSH_PACKET_USED;
}
void ssh_client_hybrid_mlkem_remove_callbacks(ssh_session session)
{
ssh_packet_remove_callbacks(session, &ssh_hybrid_mlkem_client_callbacks);
}
#ifdef WITH_SERVER
static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init);
static ssh_packet_callback dh_server_callbacks[] = {
ssh_packet_server_hybrid_mlkem_init,
};
static struct ssh_packet_callbacks_struct ssh_hybrid_mlkem_server_callbacks = {
.start = SSH2_MSG_KEX_HYBRID_INIT,
.n_callbacks = 1,
.callbacks = dh_server_callbacks,
.user = NULL,
};
static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
const struct mlkem_type_info *mlkem_info = NULL;
ssh_string ecdh_shared_secret = NULL;
ssh_mlkem_shared_secret mlkem_shared_secret;
ssh_buffer server_reply_buffer = NULL;
ssh_buffer client_init_buffer = NULL;
ssh_key privkey = NULL;
enum ssh_digest_e digest = SSH_DIGEST_AUTO;
ssh_string signature = NULL;
ssh_string pubkey_blob = NULL;
size_t ecdh_client_pubkey_size;
size_t read_len;
int rc;
(void)type;
(void)user;
SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM hybrid client init");
ssh_packet_remove_callbacks(session, &ssh_hybrid_mlkem_server_callbacks);
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
ssh_set_error(session, SSH_FATAL, "Unknown ML-KEM type");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Generate an ECDH keypair */
switch (crypto->kex_type) {
case SSH_KEX_MLKEM768X25519_SHA256:
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
ssh_set_error(session,
SSH_FATAL,
"Failed to generate a Curve25519 ECDH keypair");
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("Curve25519 server pubkey",
crypto->curve25519_server_pubkey,
CURVE25519_PUBKEY_SIZE);
#endif
break;
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
rc = ssh_ecdh_init(session);
if (rc != SSH_OK) {
ssh_set_error(session,
SSH_FATAL,
"Failed to generate a NIST-curve ECDH keypair");
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ECDH server pubkey",
ssh_string_data(crypto->ecdh_server_pubkey),
ssh_string_len(crypto->ecdh_server_pubkey));
#endif
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported KEX type");
goto cleanup;
}
/* Get client init: ML-KEM public key + ECDH public key */
ssh_string_free(crypto->hybrid_client_init);
crypto->hybrid_client_init = ssh_buffer_get_ssh_string(packet);
if (crypto->hybrid_client_init == NULL) {
ssh_set_error(session, SSH_FATAL, "No client public keys in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
client_init_buffer = ssh_buffer_new();
if (client_init_buffer == NULL) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_buffer_add_data(client_init_buffer,
ssh_string_data(crypto->hybrid_client_init),
ssh_string_len(crypto->hybrid_client_init));
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to pack client init to a buffer");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Extract client ML-KEM public key */
ssh_string_free(crypto->mlkem_client_pubkey);
crypto->mlkem_client_pubkey = ssh_string_new(mlkem_info->pubkey_size);
if (crypto->mlkem_client_pubkey == NULL) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
read_len = ssh_buffer_get_data(client_init_buffer,
ssh_string_data(crypto->mlkem_client_pubkey),
mlkem_info->pubkey_size);
if (read_len != mlkem_info->pubkey_size) {
ssh_set_error(session,
SSH_FATAL,
"Could not read ML-KEM pubkey from "
"the client init buffer, buffer too short");
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ML-KEM client pubkey",
ssh_string_data(crypto->mlkem_client_pubkey),
ssh_string_len(crypto->mlkem_client_pubkey));
#endif
/* Extract client ECDH public key */
switch (crypto->kex_type) {
case SSH_KEX_MLKEM768X25519_SHA256:
read_len = ssh_buffer_get_data(client_init_buffer,
crypto->curve25519_client_pubkey,
CURVE25519_PUBKEY_SIZE);
if (read_len != CURVE25519_PUBKEY_SIZE) {
ssh_set_error(session,
SSH_FATAL,
"Could not read Curve25519 pubkey from "
"the client init buffer, buffer too short");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
if (ssh_buffer_get_len(client_init_buffer) > 0) {
ssh_set_error(session,
SSH_FATAL,
"Unrecognized data in the client init buffer");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("Curve25519 client pubkey",
crypto->curve25519_client_pubkey,
CURVE25519_PUBKEY_SIZE);
#endif
break;
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
ecdh_client_pubkey_size = ssh_buffer_get_len(client_init_buffer);
ssh_string_free(crypto->ecdh_client_pubkey);
crypto->ecdh_client_pubkey = ssh_string_new(ecdh_client_pubkey_size);
if (crypto->ecdh_client_pubkey == NULL) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
ssh_buffer_get_data(client_init_buffer,
ssh_string_data(crypto->ecdh_client_pubkey),
ecdh_client_pubkey_size);
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ECDH client pubkey",
ssh_string_data(crypto->ecdh_client_pubkey),
ssh_string_len(crypto->ecdh_client_pubkey));
#endif
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported KEX type");
goto cleanup;
}
/* Encapsulate an ML-KEM shared secret using client's ML-KEM public key */
rc = ssh_mlkem_encapsulate(session, mlkem_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "ML-KEM encapsulation failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ML-KEM shared secret",
mlkem_shared_secret,
MLKEM_SHARED_SECRET_SIZE);
ssh_log_hexdump("ML-KEM ciphertext",
ssh_string_data(crypto->mlkem_ciphertext),
ssh_string_len(crypto->mlkem_ciphertext));
#endif
/* Derive the classical ECDH shared secret */
ecdh_shared_secret = derive_ecdh_secret(session);
if (ecdh_shared_secret == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("ECDH shared secret",
ssh_string_data(ecdh_shared_secret),
ssh_string_len(ecdh_shared_secret));
#endif
/* Derive the final shared secret */
rc = derive_hybrid_secret(session, mlkem_shared_secret, ecdh_shared_secret);
if (rc != SSH_OK) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Create server reply: ML-KEM ciphertext + ECDH public key */
server_reply_buffer = ssh_buffer_new();
if (server_reply_buffer == NULL) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
switch (crypto->kex_type) {
case SSH_KEX_MLKEM768X25519_SHA256:
rc = ssh_buffer_pack(server_reply_buffer,
"PP",
ssh_string_len(crypto->mlkem_ciphertext),
ssh_string_data(crypto->mlkem_ciphertext),
CURVE25519_PUBKEY_SIZE,
crypto->curve25519_server_pubkey);
break;
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
rc = ssh_buffer_pack(server_reply_buffer,
"PP",
ssh_string_len(crypto->mlkem_ciphertext),
ssh_string_data(crypto->mlkem_ciphertext),
ssh_string_len(crypto->ecdh_server_pubkey),
ssh_string_data(crypto->ecdh_server_pubkey));
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported KEX type");
goto cleanup;
}
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to construct server reply buffer");
goto cleanup;
}
/* Convert the reply buffer to an SSH string for sending */
ssh_string_free(crypto->hybrid_server_reply);
crypto->hybrid_server_reply = ssh_string_new(ssh_buffer_get_len(server_reply_buffer));
if (crypto->hybrid_server_reply == NULL) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_string_fill(crypto->hybrid_server_reply,
ssh_buffer_get(server_reply_buffer),
ssh_buffer_get_len(server_reply_buffer));
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to convert reply buffer to string");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add MSG_KEX_ECDH_REPLY header */
rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_HYBRID_REPLY);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to add MSG_KEX_HYBRID_REPLY to buffer");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Get server host key */
rc = ssh_get_key_params(session, &privkey, &digest);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not get server key params");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Build session ID */
rc = ssh_make_sessionid(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not create a session id");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not export server public key");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add server public key to output */
rc = ssh_buffer_add_ssh_string(session->out_buffer, pubkey_blob);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to add server hostkey to buffer");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add server reply */
rc = ssh_buffer_add_ssh_string(session->out_buffer, crypto->hybrid_server_reply);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to add server reply to buffer");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Sign the exchange hash */
signature = ssh_srv_pki_do_sign_sessionid(session, privkey, digest);
if (signature == NULL) {
ssh_set_error(session, SSH_FATAL, "Could not sign the session id");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add signature */
rc = ssh_buffer_add_ssh_string(session->out_buffer, signature);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to add signature to buffer");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_packet_send(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_ECDH_REPLY");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Send the MSG_NEWKEYS */
rc = ssh_packet_send_newkeys(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
cleanup:
ssh_burn(mlkem_shared_secret, sizeof(mlkem_shared_secret));
ssh_string_burn(ecdh_shared_secret);
ssh_string_free(ecdh_shared_secret);
ssh_string_free(pubkey_blob);
ssh_string_free(signature);
ssh_buffer_free(client_init_buffer);
ssh_buffer_free(server_reply_buffer);
return SSH_PACKET_USED;
}
void ssh_server_hybrid_mlkem_init(ssh_session session)
{
SSH_LOG(SSH_LOG_TRACE, "Setting up ML-KEM hybrid server callbacks");
ssh_packet_set_callbacks(session, &ssh_hybrid_mlkem_server_callbacks);
}
#endif /* WITH_SERVER */

670
src/kex-gss.c Normal file
View File

@@ -0,0 +1,670 @@
/*
* kex-gss.c - GSSAPI key exchange
*
* This file is part of the SSH Library
*
* Copyright (c) 2024 by Gauravsingh Sisodia <xaerru@gmail.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include "libssh/gssapi.h"
#include <errno.h>
#include <gssapi/gssapi.h>
#include <stdio.h>
#include "libssh/buffer.h"
#include "libssh/crypto.h"
#include "libssh/kex-gss.h"
#include "libssh/bignum.h"
#include "libssh/curve25519.h"
#include "libssh/ecdh.h"
#include "libssh/dh.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include "libssh/ssh2.h"
static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply);
static ssh_packet_callback gss_kex_client_callbacks[] = {
ssh_packet_client_gss_kex_reply,
};
static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callbacks = {
.start = SSH2_MSG_KEXGSS_COMPLETE,
.n_callbacks = 1,
.callbacks = gss_kex_client_callbacks,
.user = NULL,
};
static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey);
static ssh_packet_callback gss_kex_client_callback_hostkey[] = {
ssh_packet_client_gss_kex_hostkey,
};
static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callback_hostkey = {
.start = SSH2_MSG_KEXGSS_HOSTKEY,
.n_callbacks = 1,
.callbacks = gss_kex_client_callback_hostkey,
.user = NULL,
};
static ssh_string dh_init(ssh_session session)
{
int rc, keypair;
#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L
const_bignum const_pubkey;
#endif
bignum pubkey = NULL;
ssh_string pubkey_string = NULL;
struct ssh_crypto_struct *crypto = session->next_crypto;
if (session->server) {
keypair = DH_SERVER_KEYPAIR;
} else {
keypair = DH_CLIENT_KEYPAIR;
}
rc = ssh_dh_init_common(crypto);
if (rc != SSH_OK) {
goto end;
}
rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, keypair);
if (rc != SSH_OK) {
goto end;
}
#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L
rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &const_pubkey);
bignum_dup(const_pubkey, &pubkey);
#else
rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &pubkey);
#endif
if (rc != SSH_OK) {
goto end;
}
pubkey_string = ssh_make_bignum_string(pubkey);
end:
bignum_safe_free(pubkey);
return pubkey_string;
}
static int dh_import_peer_key(ssh_session session, ssh_string peer_key)
{
int rc, keypair;
bignum peer_key_bn;
struct ssh_crypto_struct *crypto = session->next_crypto;
if (session->server) {
keypair = DH_CLIENT_KEYPAIR;
} else {
keypair = DH_SERVER_KEYPAIR;
}
peer_key_bn = ssh_make_string_bn(peer_key);
rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, keypair, NULL, peer_key_bn);
if (rc != SSH_OK) {
bignum_safe_free(peer_key_bn);
}
return rc;
}
/** @internal
* @brief Starts gssapi key exchange
*/
int ssh_client_gss_kex_init(ssh_session session)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
int rc, ret = SSH_ERROR;
/* oid selected for authentication */
gss_OID_set selected = GSS_C_NO_OID_SET;
OM_uint32 maj_stat, min_stat;
const char *gss_host = session->opts.host;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
OM_uint32 oflags;
ssh_string pubkey = NULL;
switch (crypto->kex_type) {
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
pubkey = dh_init(session);
if (pubkey == NULL) {
ssh_set_error(session, SSH_FATAL, "Failed to generate DH keypair");
goto out;
}
break;
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
rc = ssh_ecdh_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate ECDH keypair");
goto out;
}
pubkey = ssh_string_copy(crypto->ecdh_client_pubkey);
break;
case SSH_GSS_KEX_CURVE25519_SHA256:
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate Curve25519 keypair");
goto out;
}
pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE);
if (pubkey == NULL) {
ssh_set_error_oom(session);
goto out;
}
rc = ssh_string_fill(pubkey,
crypto->curve25519_client_pubkey,
CURVE25519_PUBKEY_SIZE);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey");
goto out;
}
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method");
goto out;
}
rc = ssh_gssapi_init(session);
if (rc != SSH_OK) {
goto out;
}
if (session->opts.gss_server_identity != NULL) {
gss_host = session->opts.gss_server_identity;
}
rc = ssh_gssapi_import_name(session->gssapi, gss_host);
if (rc != SSH_OK) {
goto out;
}
rc = ssh_gssapi_client_identity(session, &selected);
if (rc != SSH_OK) {
goto out;
}
session->gssapi->client.flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
maj_stat = ssh_gssapi_init_ctx(session->gssapi,
&input_token,
&output_token,
&oflags);
gss_release_oid_set(&min_stat, &selected);
if (GSS_ERROR(maj_stat)) {
ssh_gssapi_log_error(SSH_LOG_WARN,
"Initializing gssapi context",
maj_stat,
min_stat);
goto out;
}
if (!(oflags & GSS_C_INTEG_FLAG) || !(oflags & GSS_C_MUTUAL_FLAG)) {
SSH_LOG(SSH_LOG_WARN,
"GSSAPI(init) integrity and mutual flags were not set");
goto out;
}
rc = ssh_buffer_pack(session->out_buffer,
"bdPS",
SSH2_MSG_KEXGSS_INIT,
output_token.length,
(size_t)output_token.length,
output_token.value,
pubkey);
if (rc != SSH_OK) {
goto out;
}
/* register the packet callbacks */
ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callbacks);
ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callback_hostkey);
session->dh_handshake_state = DH_STATE_INIT_SENT;
rc = ssh_packet_send(session);
if (rc != SSH_OK) {
goto out;
}
ret = SSH_OK;
out:
gss_release_buffer(&min_stat, &output_token);
ssh_string_free(pubkey);
return ret;
}
void ssh_client_gss_kex_remove_callbacks(ssh_session session)
{
ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callbacks);
}
void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session)
{
ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callback_hostkey);
}
SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_string mic = NULL, otoken = NULL, server_pubkey = NULL;
uint8_t b;
int rc;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
OM_uint32 oflags;
OM_uint32 maj_stat;
(void)type;
(void)user;
ssh_client_gss_kex_remove_callbacks(session);
rc = ssh_buffer_unpack(packet, "SSbS", &server_pubkey, &mic, &b, &otoken);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "No public key in server reply");
goto error;
}
SSH_STRING_FREE(session->gssapi_key_exchange_mic);
session->gssapi_key_exchange_mic = mic;
input_token.length = ssh_string_len(otoken);
input_token.value = ssh_string_data(otoken);
maj_stat = ssh_gssapi_init_ctx(session->gssapi,
&input_token,
&output_token,
&oflags);
if (maj_stat != GSS_S_COMPLETE) {
goto error;
}
SSH_STRING_FREE(otoken);
switch (crypto->kex_type) {
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
rc = dh_import_peer_key(session, server_pubkey);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not import server pubkey");
goto error;
}
rc = ssh_dh_compute_shared_secret(crypto->dh_ctx,
DH_CLIENT_KEYPAIR,
DH_SERVER_KEYPAIR,
&crypto->shared_secret);
ssh_dh_debug_crypto(crypto);
break;
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
crypto->ecdh_server_pubkey = ssh_string_copy(server_pubkey);
rc = ecdh_build_k(session);
break;
case SSH_GSS_KEX_CURVE25519_SHA256:
memcpy(crypto->curve25519_server_pubkey,
ssh_string_data(server_pubkey),
CURVE25519_PUBKEY_SIZE);
rc = ssh_curve25519_build_k(session);
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method");
goto error;
}
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not derive shared secret");
goto error;
}
/* Send the MSG_NEWKEYS */
rc = ssh_packet_send_newkeys(session);
if (rc == SSH_ERROR) {
goto error;
}
ssh_string_free(server_pubkey);
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
return SSH_PACKET_USED;
error:
ssh_string_free(server_pubkey);
session->session_state = SSH_SESSION_STATE_ERROR;
return SSH_PACKET_USED;
}
SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey)
{
ssh_string pubkey_blob = NULL;
int rc;
(void)type;
(void)user;
ssh_client_gss_kex_remove_callback_hostkey(session);
rc = ssh_buffer_unpack(packet, "S", &pubkey_blob);
if (rc == SSH_ERROR) {
ssh_set_error(session,
SSH_FATAL,
"Invalid SSH2_MSG_KEXGSS_HOSTKEY packet");
goto error;
}
rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob);
SSH_STRING_FREE(pubkey_blob);
if (rc != 0) {
goto error;
}
return SSH_PACKET_USED;
error:
session->session_state = SSH_SESSION_STATE_ERROR;
return SSH_PACKET_USED;
}
#ifdef WITH_SERVER
static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init);
static ssh_packet_callback gss_kex_server_callbacks[] = {
ssh_packet_server_gss_kex_init,
};
static struct ssh_packet_callbacks_struct ssh_gss_kex_server_callbacks = {
.start = SSH2_MSG_KEXGSS_INIT,
.n_callbacks = 1,
.callbacks = gss_kex_server_callbacks,
.user = NULL,
};
/** @internal
* @brief sets up the gssapi kex callbacks
*/
void ssh_server_gss_kex_init(ssh_session session)
{
/* register the packet callbacks */
ssh_packet_set_callbacks(session, &ssh_gss_kex_server_callbacks);
}
/** @internal
* @brief processes a SSH_MSG_KEXGSS_INIT and sends
* the appropriate SSH_MSG_KEXGSS_COMPLETE
*/
int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_key privkey = NULL;
enum ssh_digest_e digest = SSH_DIGEST_AUTO;
ssh_string client_pubkey = NULL;
ssh_string server_pubkey = NULL;
int rc;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
ssh_string otoken = NULL;
ssh_string server_pubkey_blob = NULL;
OM_uint32 maj_stat, min_stat;
gss_name_t client_name = GSS_C_NO_NAME;
OM_uint32 ret_flags = 0;
gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER;
char hostname[NI_MAXHOST] = {0};
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
rc = ssh_buffer_unpack(packet, "S", &otoken);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "No token in client request");
goto error;
}
input_token.length = ssh_string_len(otoken);
input_token.value = ssh_string_data(otoken);
rc = ssh_buffer_unpack(packet, "S", &client_pubkey);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "No public key in client request");
goto error;
}
switch (crypto->kex_type) {
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
server_pubkey = dh_init(session);
if (server_pubkey == NULL) {
ssh_set_error(session, SSH_FATAL, "Could not generate a DH keypair");
goto error;
}
rc = dh_import_peer_key(session, client_pubkey);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not import client pubkey");
goto error;
}
rc = ssh_dh_compute_shared_secret(crypto->dh_ctx,
DH_SERVER_KEYPAIR,
DH_CLIENT_KEYPAIR,
&crypto->shared_secret);
ssh_dh_debug_crypto(crypto);
break;
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
rc = ssh_ecdh_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not generate an ECDH keypair");
goto error;
}
crypto->ecdh_client_pubkey = ssh_string_copy(client_pubkey);
server_pubkey = ssh_string_copy(crypto->ecdh_server_pubkey);
rc = ecdh_build_k(session);
break;
case SSH_GSS_KEX_CURVE25519_SHA256:
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not generate a Curve25519 keypair");
goto error;
}
server_pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE);
if (server_pubkey == NULL) {
ssh_set_error_oom(session);
goto error;
}
rc = ssh_string_fill(server_pubkey,
crypto->curve25519_server_pubkey,
CURVE25519_PUBKEY_SIZE);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey");
goto error;
}
memcpy(crypto->curve25519_client_pubkey,
ssh_string_data(client_pubkey),
CURVE25519_PUBKEY_SIZE);
rc = ssh_curve25519_build_k(session);
break;
default:
ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method");
goto error;
}
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not derive shared secret");
goto error;
}
/* Also imports next_crypto->server_pubkey
* Can give error when using null hostkey */
ssh_get_key_params(session, &privkey, &digest);
rc = ssh_make_sessionid(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not create a session id");
goto error;
}
if (strcmp(crypto->kex_methods[SSH_HOSTKEYS], "null") != 0) {
rc =
ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob);
if (rc != SSH_OK) {
goto error;
}
rc = ssh_buffer_pack(session->out_buffer,
"bS",
SSH2_MSG_KEXGSS_HOSTKEY,
server_pubkey_blob);
if (rc != SSH_OK) {
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
goto error;
}
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
goto error;
}
SSH_LOG(SSH_LOG_DEBUG, "Sent SSH2_MSG_KEXGSS_HOSTKEY");
SSH_STRING_FREE(server_pubkey_blob);
}
rc = ssh_gssapi_init(session);
if (rc == SSH_ERROR) {
goto error;
}
rc = gethostname(hostname, 64);
if (rc != 0) {
SSH_LOG(SSH_LOG_TRACE,
"Error getting hostname: %s",
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
goto error;
}
rc = ssh_gssapi_import_name(session->gssapi, hostname);
if (rc != SSH_OK) {
goto error;
}
maj_stat = gss_acquire_cred(&min_stat,
session->gssapi->client.server_name,
0,
GSS_C_NO_OID_SET,
GSS_C_ACCEPT,
&session->gssapi->server_creds,
NULL,
NULL);
if (maj_stat != GSS_S_COMPLETE) {
ssh_gssapi_log_error(SSH_LOG_TRACE,
"acquiring credentials",
maj_stat,
min_stat);
goto error;
}
maj_stat = gss_accept_sec_context(&min_stat,
&session->gssapi->ctx,
session->gssapi->server_creds,
&input_token,
GSS_C_NO_CHANNEL_BINDINGS,
&client_name,
NULL /*mech_oid*/,
&output_token,
&ret_flags,
NULL /*time*/,
&session->gssapi->client_creds);
if (GSS_ERROR(maj_stat)) {
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"accepting token failed",
maj_stat,
min_stat);
goto error;
}
SSH_STRING_FREE(otoken);
gss_release_name(&min_stat, &client_name);
if (!(ret_flags & GSS_C_INTEG_FLAG) || !(ret_flags & GSS_C_MUTUAL_FLAG)) {
SSH_LOG(SSH_LOG_WARN,
"GSSAPI(accept) integrity and mutual flags were not set");
goto error;
}
SSH_LOG(SSH_LOG_DEBUG, "token accepted");
msg.length = session->next_crypto->digest_len;
msg.value = session->next_crypto->secret_hash;
maj_stat = gss_get_mic(&min_stat,
session->gssapi->ctx,
GSS_C_QOP_DEFAULT,
&msg,
&mic);
if (GSS_ERROR(maj_stat)) {
ssh_gssapi_log_error(SSH_LOG_DEBUG,
"creating mic failed",
maj_stat,
min_stat);
goto error;
}
rc = ssh_buffer_pack(session->out_buffer,
"bSdPbdP",
SSH2_MSG_KEXGSS_COMPLETE,
server_pubkey,
mic.length,
(size_t)mic.length,
mic.value,
1,
output_token.length,
(size_t)output_token.length,
output_token.value);
if (rc != SSH_OK) {
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
goto error;
}
gss_release_buffer(&min_stat, &output_token);
gss_release_buffer(&min_stat, &mic);
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
goto error;
}
SSH_LOG(SSH_LOG_DEBUG, "Sent SSH2_MSG_KEXGSS_COMPLETE");
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
/* Send the MSG_NEWKEYS */
rc = ssh_packet_send_newkeys(session);
if (rc == SSH_ERROR) {
goto error;
}
ssh_string_free(server_pubkey);
ssh_string_free(client_pubkey);
return SSH_OK;
error:
SSH_STRING_FREE(server_pubkey_blob);
ssh_string_free(server_pubkey);
ssh_string_free(client_pubkey);
session->session_state = SSH_SESSION_STATE_ERROR;
return SSH_ERROR;
}
/** @internal
* @brief parse an incoming SSH_MSG_KEXGSS_INIT packet and complete
* Diffie-Hellman key exchange
**/
static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init)
{
(void)type;
(void)user;
SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEXGSS_INIT");
ssh_packet_remove_callbacks(session, &ssh_gss_kex_server_callbacks);
ssh_server_gss_kex_process_init(session, packet);
return SSH_PACKET_USED;
}
#endif /* WITH_SERVER */

185
src/kex.c
View File

@@ -41,14 +41,14 @@
#include "libssh/string.h"
#include "libssh/curve25519.h"
#include "libssh/sntrup761.h"
#ifdef HAVE_MLKEM
#include "libssh/mlkem768.h"
#endif
#include "libssh/hybrid_mlkem.h"
#include "libssh/kex-gss.h"
#include "libssh/knownhosts.h"
#include "libssh/misc.h"
#include "libssh/pki.h"
#include "libssh/bignum.h"
#include "libssh/token.h"
#include "libssh/gssapi.h"
#ifdef HAVE_BLOWFISH
# define BLOWFISH ",blowfish-cbc"
@@ -105,11 +105,14 @@
#define SNTRUP761X25519 ""
#endif /* HAVE_SNTRUP761 */
#ifdef HAVE_MLKEM
#define MLKEM768X25519 "mlkem768x25519-sha256,"
#ifdef HAVE_MLKEM1024
#define HYBRID_MLKEM "mlkem768x25519-sha256," \
"mlkem768nistp256-sha256," \
"mlkem1024nistp384-sha384,"
#else
#define MLKEM768X25519 ""
#endif /* HAVE_MLKEM */
#define HYBRID_MLKEM "mlkem768x25519-sha256," \
"mlkem768nistp256-sha256,"
#endif /* HAVE_MLKEM1024 */
#ifdef HAVE_ECC
#define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,"
@@ -174,7 +177,7 @@
#define CHACHA20 "chacha20-poly1305@openssh.com,"
#define DEFAULT_KEY_EXCHANGE \
MLKEM768X25519 \
HYBRID_MLKEM \
SNTRUP761X25519 \
CURVE25519 \
ECDH \
@@ -792,6 +795,8 @@ int ssh_set_client_kex(ssh_session session)
const char *wanted = NULL;
int ok;
int i;
bool gssapi_null_alg = false;
char *hostkeys = NULL;
/* Skip if already set, for example for the rekey or when we do the guessing
* it could have been already used to make some protocol decisions. */
@@ -804,6 +809,42 @@ int ssh_set_client_kex(ssh_session session)
ssh_set_error(session, SSH_FATAL, "PRNG error");
return SSH_ERROR;
}
#ifdef WITH_GSSAPI
if (session->opts.gssapi_key_exchange) {
char *gssapi_algs = NULL;
ok = ssh_gssapi_init(session);
if (ok != SSH_OK) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
ok = ssh_gssapi_import_name(session->gssapi, session->opts.host);
if (ok != SSH_OK) {
return SSH_ERROR;
}
gssapi_algs = ssh_gssapi_kex_mechs(session);
if (gssapi_algs == NULL) {
return SSH_ERROR;
}
/* Prefix the default algorithms with gsskex algs */
if (ssh_fips_mode()) {
session->opts.wanted_methods[SSH_KEX] =
ssh_prefix_without_duplicates(fips_methods[SSH_KEX],
gssapi_algs);
} else {
session->opts.wanted_methods[SSH_KEX] =
ssh_prefix_without_duplicates(default_methods[SSH_KEX],
gssapi_algs);
}
gssapi_null_alg = true;
SAFE_FREE(gssapi_algs);
}
#endif
/* Set the list of allowed algorithms in order of preference, if it hadn't
* been set yet. */
@@ -817,6 +858,16 @@ int ssh_set_client_kex(ssh_session session)
ssh_set_error_oom(session);
return SSH_ERROR;
}
if (gssapi_null_alg) {
hostkeys =
ssh_append_without_duplicates(client->methods[i], "null");
if (hostkeys == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
SAFE_FREE(client->methods[i]);
client->methods[i] = hostkeys;
}
continue;
}
@@ -908,6 +959,14 @@ kex_select_kex_type(const char *kex)
{
if (strcmp(kex, "diffie-hellman-group1-sha1") == 0) {
return SSH_KEX_DH_GROUP1_SHA1;
} else if (strncmp(kex, "gss-group14-sha256-", 19) == 0) {
return SSH_GSS_KEX_DH_GROUP14_SHA256;
} else if (strncmp(kex, "gss-group16-sha512-", 19) == 0) {
return SSH_GSS_KEX_DH_GROUP16_SHA512;
} else if (strncmp(kex, "gss-nistp256-sha256-", 20) == 0) {
return SSH_GSS_KEX_ECDH_NISTP256_SHA256;
} else if (strncmp(kex, "gss-curve25519-sha256-", 22) == 0) {
return SSH_GSS_KEX_CURVE25519_SHA256;
} else if (strcmp(kex, "diffie-hellman-group14-sha1") == 0) {
return SSH_KEX_DH_GROUP14_SHA1;
} else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) {
@@ -936,9 +995,13 @@ kex_select_kex_type(const char *kex)
return SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM;
} else if (strcmp(kex, "sntrup761x25519-sha512") == 0) {
return SSH_KEX_SNTRUP761X25519_SHA512;
#ifdef HAVE_MLKEM
} else if (strcmp(kex, "mlkem768x25519-sha256") == 0) {
return SSH_KEX_MLKEM768X25519_SHA256;
} else if (strcmp(kex, "mlkem768nistp256-sha256") == 0) {
return SSH_KEX_MLKEM768NISTP256_SHA256;
#ifdef HAVE_MLKEM1024
} else if (strcmp(kex, "mlkem1024nistp384-sha384") == 0) {
return SSH_KEX_MLKEM1024NISTP384_SHA384;
#endif
}
/* should not happen. We should be getting only valid names at this stage */
@@ -961,6 +1024,14 @@ static void revert_kex_callbacks(ssh_session session)
case SSH_KEX_DH_GROUP18_SHA512:
ssh_client_dh_remove_callbacks(session);
break;
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_GSS_KEX_CURVE25519_SHA256:
#ifdef WITH_GSSAPI
ssh_client_gss_kex_remove_callbacks(session);
#endif /* WITH_GSSAPI */
break;
#ifdef WITH_GEX
case SSH_KEX_DH_GEX_SHA1:
case SSH_KEX_DH_GEX_SHA256:
@@ -986,11 +1057,13 @@ static void revert_kex_callbacks(ssh_session session)
ssh_client_sntrup761x25519_remove_callbacks(session);
break;
#endif
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
ssh_client_mlkem768x25519_remove_callbacks(session);
break;
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
ssh_client_hybrid_mlkem_remove_callbacks(session);
break;
}
}
@@ -1428,6 +1501,18 @@ int ssh_make_sessionid(ssh_session session)
goto error;
}
if (server_pubkey_blob == NULL) {
if ((session->server && ssh_kex_is_gss(session->next_crypto)) ||
session->opts.gssapi_key_exchange) {
server_pubkey_blob = ssh_string_new(0);
if (server_pubkey_blob == NULL) {
ssh_set_error_oom(session);
rc = SSH_ERROR;
goto error;
}
}
}
rc = ssh_buffer_pack(buf,
"dPdPS",
ssh_buffer_get_len(client_hash),
@@ -1449,7 +1534,9 @@ int ssh_make_sessionid(ssh_session session)
case SSH_KEX_DH_GROUP1_SHA1:
case SSH_KEX_DH_GROUP14_SHA1:
case SSH_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_KEX_DH_GROUP16_SHA512:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
case SSH_KEX_DH_GROUP18_SHA512:
rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx,
DH_CLIENT_KEYPAIR, NULL, &client_pubkey);
@@ -1515,6 +1602,7 @@ int ssh_make_sessionid(ssh_session session)
case SSH_KEX_ECDH_SHA2_NISTP256:
case SSH_KEX_ECDH_SHA2_NISTP384:
case SSH_KEX_ECDH_SHA2_NISTP521:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
if (session->next_crypto->ecdh_client_pubkey == NULL ||
session->next_crypto->ecdh_server_pubkey == NULL) {
SSH_LOG(SSH_LOG_TRACE, "ECDH parameter missing");
@@ -1533,6 +1621,7 @@ int ssh_make_sessionid(ssh_session session)
#ifdef HAVE_CURVE25519
case SSH_KEX_CURVE25519_SHA256:
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
case SSH_GSS_KEX_CURVE25519_SHA256:
rc = ssh_buffer_pack(buf,
"dPdP",
CURVE25519_PUBKEY_SIZE,
@@ -1574,28 +1663,22 @@ int ssh_make_sessionid(ssh_session session)
}
break;
#endif /* HAVE_SNTRUP761 */
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
rc = ssh_buffer_pack(buf,
"dPPdPP",
MLKEM768X25519_CLIENT_PUBKEY_SIZE,
(size_t)MLKEM768_PUBLICKEY_SIZE,
session->next_crypto->mlkem768_client_pubkey,
(size_t)CURVE25519_PUBKEY_SIZE,
session->next_crypto->curve25519_client_pubkey,
MLKEM768X25519_SERVER_RESPONSE_SIZE,
(size_t)MLKEM768_CIPHERTEXT_SIZE,
session->next_crypto->mlkem768_ciphertext,
(size_t)CURVE25519_PUBKEY_SIZE,
session->next_crypto->curve25519_server_pubkey);
"SS",
session->next_crypto->hybrid_client_init,
session->next_crypto->hybrid_server_reply);
if (rc != SSH_OK) {
ssh_set_error(session,
SSH_FATAL,
"Failed to pack ML-KEM768 individual components");
"Failed to pack ML-KEM individual components");
goto error;
}
break;
#endif /* HAVE_MLKEM */
default:
/* Handle unsupported kex types - this should not happen in normal operation */
rc = SSH_ERROR;
@@ -1610,14 +1693,13 @@ int ssh_make_sessionid(ssh_session session)
session->next_crypto->shared_secret,
SHA512_DIGEST_LEN);
break;
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
rc = ssh_buffer_pack(buf,
"F",
session->next_crypto->shared_secret,
SHA256_DIGEST_LEN);
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
rc = ssh_buffer_pack(buf, "S", session->next_crypto->hybrid_shared_secret);
break;
#endif /* HAVE_MLKEM */
default:
rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret);
break;
@@ -1650,12 +1732,14 @@ int ssh_make_sessionid(ssh_session session)
session->next_crypto->secret_hash);
break;
case SSH_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_KEX_ECDH_SHA2_NISTP256:
case SSH_KEX_CURVE25519_SHA256:
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
#endif
case SSH_KEX_MLKEM768NISTP256_SHA256:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_GSS_KEX_CURVE25519_SHA256:
#ifdef WITH_GEX
case SSH_KEX_DH_GEX_SHA256:
#endif /* WITH_GEX */
@@ -1670,6 +1754,9 @@ int ssh_make_sessionid(ssh_session session)
session->next_crypto->secret_hash);
break;
case SSH_KEX_ECDH_SHA2_NISTP384:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
session->next_crypto->digest_len = SHA384_DIGEST_LENGTH;
session->next_crypto->digest_type = SSH_KDF_SHA384;
session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len);
@@ -1681,6 +1768,7 @@ int ssh_make_sessionid(ssh_session session)
session->next_crypto->secret_hash);
break;
case SSH_KEX_DH_GROUP16_SHA512:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
case SSH_KEX_DH_GROUP18_SHA512:
case SSH_KEX_ECDH_SHA2_NISTP521:
case SSH_KEX_SNTRUP761X25519_SHA512:
@@ -1831,12 +1919,16 @@ int ssh_generate_session_keys(ssh_session session)
switch (session->next_crypto->kex_type) {
case SSH_KEX_SNTRUP761X25519_SHA512:
case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM:
#ifdef HAVE_MLKEM
case SSH_KEX_MLKEM768X25519_SHA256:
#endif /* HAVE_MLKEM */
k_string = ssh_make_padded_bignum_string(crypto->shared_secret,
crypto->digest_len);
break;
case SSH_KEX_MLKEM768X25519_SHA256:
case SSH_KEX_MLKEM768NISTP256_SHA256:
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
#endif
k_string = ssh_string_copy(crypto->hybrid_shared_secret);
break;
default:
k_string = ssh_make_bignum_string(crypto->shared_secret);
break;
@@ -1952,3 +2044,22 @@ error:
return rc;
}
/** @internal
* @brief Check if a given crypto context has a GSSAPI KEX set
*
* @param[in] crypto The SSH crypto context
* @return true if the KEX of the context is a GSSAPI KEX, false otherwise
*/
bool ssh_kex_is_gss(struct ssh_crypto_struct *crypto)
{
switch (crypto->kex_type) {
case SSH_GSS_KEX_DH_GROUP14_SHA256:
case SSH_GSS_KEX_DH_GROUP16_SHA512:
case SSH_GSS_KEX_ECDH_NISTP256_SHA256:
case SSH_GSS_KEX_CURVE25519_SHA256:
return true;
default:
return false;
}
}

View File

@@ -216,11 +216,22 @@ ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry *k1,
return 0;
}
/* This method reads the known_hosts file referenced by the path
/**
* @internal
*
* @brief Read entries from filename to provided list
*
* This method reads the known_hosts file referenced by the path
* in filename argument, and entries matching the match argument
* will be added to the list in entries argument.
* If the entries list is NULL, it will allocate a new list. Caller
* is responsible to free it even if an error occurs.
*
* @param match[in] The host name (with port) to match against
* @param filename[in] The known hosts file to parse
* @param entries[in,out] The list of entries to append matching ones
* @return `SSH_OK` on missing file or success parsing,
* `SSH_ERROR` on error
*/
static int ssh_known_hosts_read_entries(const char *match,
const char *filename,
@@ -346,6 +357,33 @@ static char *ssh_session_get_host_port(ssh_session session)
/**
* @internal
*
* @brief Free known hosts entries list
*
* @param[in] entry_list The list of ssh_knownhosts_entry items
*/
static void ssh_knownhosts_entries_free(struct ssh_list *entry_list)
{
struct ssh_iterator *it = NULL;
if (entry_list == NULL) {
return;
}
for (it = ssh_list_get_iterator(entry_list);
it != NULL;
it = ssh_list_get_iterator(entry_list)) {
struct ssh_knownhosts_entry *entry = NULL;
entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
ssh_knownhosts_entry_free(entry);
ssh_list_remove(entry_list, it);
}
ssh_list_free(entry_list);
}
/**
* @internal
*
* @brief Check which host keys should be preferred for the session.
*
* This checks the known_hosts file to find out which algorithms should be
@@ -376,25 +414,23 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session)
}
}
host_port = ssh_session_get_host_port(session);
if (host_port == NULL) {
return NULL;
}
list = ssh_list_new();
if (list == NULL) {
ssh_set_error_oom(session);
SAFE_FREE(host_port);
return NULL;
}
host_port = ssh_session_get_host_port(session);
if (host_port == NULL) {
goto error;
}
rc = ssh_known_hosts_read_entries(host_port,
session->opts.knownhosts,
&entry_list);
if (rc != 0) {
ssh_list_free(entry_list);
ssh_list_free(list);
return NULL;
SAFE_FREE(host_port);
goto error;
}
rc = ssh_known_hosts_read_entries(host_port,
@@ -402,21 +438,16 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session)
&entry_list);
SAFE_FREE(host_port);
if (rc != 0) {
ssh_list_free(entry_list);
ssh_list_free(list);
return NULL;
goto error;
}
if (entry_list == NULL) {
ssh_list_free(list);
return NULL;
goto error;
}
count = ssh_list_count(entry_list);
if (count == 0) {
ssh_list_free(list);
ssh_list_free(entry_list);
return NULL;
goto error;
}
for (it = ssh_list_get_iterator(entry_list);
@@ -460,6 +491,7 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session)
return list;
error:
ssh_knownhosts_entries_free(entry_list);
ssh_list_free(list);
return NULL;
}
@@ -511,6 +543,7 @@ static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e typ
/**
* @internal
*
* @brief Get the host keys algorithms identifiers from the known_hosts files
*
* This expands the signatures types that can be generated from the keys types
@@ -555,7 +588,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session)
&entry_list);
if (rc != 0) {
SAFE_FREE(host_port);
ssh_list_free(entry_list);
ssh_knownhosts_entries_free(entry_list);
return NULL;
}
@@ -564,7 +597,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session)
&entry_list);
SAFE_FREE(host_port);
if (rc != 0) {
ssh_list_free(entry_list);
ssh_knownhosts_entries_free(entry_list);
return NULL;
}
@@ -805,7 +838,6 @@ out:
enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
{
struct ssh_list *entry_list = NULL;
struct ssh_iterator *it = NULL;
char *host_port = NULL;
bool global_known_hosts_found = false;
bool known_hosts_found = false;
@@ -866,7 +898,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
&entry_list);
if (rc != 0) {
SAFE_FREE(host_port);
ssh_list_free(entry_list);
ssh_knownhosts_entries_free(entry_list);
return SSH_KNOWN_HOSTS_ERROR;
}
}
@@ -877,7 +909,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
&entry_list);
if (rc != 0) {
SAFE_FREE(host_port);
ssh_list_free(entry_list);
ssh_knownhosts_entries_free(entry_list);
return SSH_KNOWN_HOSTS_ERROR;
}
}
@@ -889,16 +921,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session)
return SSH_KNOWN_HOSTS_UNKNOWN;
}
for (it = ssh_list_get_iterator(entry_list);
it != NULL;
it = ssh_list_get_iterator(entry_list)) {
struct ssh_knownhosts_entry *entry = NULL;
entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
ssh_knownhosts_entry_free(entry);
ssh_list_remove(entry_list, it);
}
ssh_list_free(entry_list);
ssh_knownhosts_entries_free(entry_list);
return SSH_KNOWN_HOSTS_OK;
}
@@ -1085,13 +1108,13 @@ ssh_known_hosts_check_server_key(const char *hosts_entry,
filename,
&entry_list);
if (rc != 0) {
ssh_list_free(entry_list);
ssh_knownhosts_entries_free(entry_list);
return SSH_KNOWN_HOSTS_UNKNOWN;
}
it = ssh_list_get_iterator(entry_list);
if (it == NULL) {
ssh_list_free(entry_list);
ssh_knownhosts_entries_free(entry_list);
return SSH_KNOWN_HOSTS_UNKNOWN;
}
@@ -1121,16 +1144,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry,
}
}
for (it = ssh_list_get_iterator(entry_list);
it != NULL;
it = ssh_list_get_iterator(entry_list)) {
struct ssh_knownhosts_entry *entry = NULL;
entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it);
ssh_knownhosts_entry_free(entry);
ssh_list_remove(entry_list, it);
}
ssh_list_free(entry_list);
ssh_knownhosts_entries_free(entry_list);
return found;
}
@@ -1202,6 +1216,8 @@ ssh_session_get_known_hosts_entry(ssh_session session,
}
/**
* @internal
*
* @brief Get the known_hosts entry for the current connected session
* from the given known_hosts file.
*

View File

@@ -40,7 +40,9 @@
#endif
#ifdef HAVE_LIBCRYPTO
#ifdef LIBRESSL_VERSION_NUMBER
#include <openssl/poly1305.h>
#endif
#include <openssl/err.h>
#include <openssl/md5.h>
#include <openssl/opensslv.h>
@@ -572,12 +574,11 @@ static void evp_cipher_cleanup(struct ssh_cipher_struct *cipher) {
}
}
static int
evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher,
void *in,
uint8_t *out,
size_t len,
uint64_t seq)
static int evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher,
void *in,
uint8_t *out,
size_t len,
uint64_t seq)
{
(void)cipher;
(void)seq;
@@ -588,13 +589,12 @@ evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher,
return SSH_OK;
}
static void
evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher,
void *in,
void *out,
size_t len,
uint8_t *tag,
uint64_t seq)
static void evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher,
void *in,
void *out,
size_t len,
uint8_t *tag,
uint64_t seq)
{
size_t authlen, aadlen;
uint8_t lastiv[1];
@@ -608,10 +608,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher,
authlen = cipher->tag_size;
/* increment IV */
rc = EVP_CIPHER_CTX_ctrl(cipher->ctx,
EVP_CTRL_GCM_IV_GEN,
1,
lastiv);
rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv);
if (rc == 0) {
SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed");
return;
@@ -643,9 +640,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher,
}
/* compute tag */
rc = EVP_EncryptFinal(cipher->ctx,
NULL,
&tmplen);
rc = EVP_EncryptFinal(cipher->ctx, NULL, &tmplen);
if (rc < 0) {
SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal failed: Failed to create a tag");
return;
@@ -661,12 +656,11 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher,
}
}
static int
evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher,
void *complete_packet,
uint8_t *out,
size_t encrypted_size,
uint64_t seq)
static int evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher,
void *complete_packet,
uint8_t *out,
size_t encrypted_size,
uint64_t seq)
{
size_t authlen, aadlen;
uint8_t lastiv[1];
@@ -679,10 +673,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher,
authlen = cipher->tag_size;
/* increment IV */
rc = EVP_CIPHER_CTX_ctrl(cipher->ctx,
EVP_CTRL_GCM_IV_GEN,
1,
lastiv);
rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv);
if (rc == 0) {
SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed");
return SSH_ERROR;
@@ -692,7 +683,8 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher,
rc = EVP_CIPHER_CTX_ctrl(cipher->ctx,
EVP_CTRL_GCM_SET_TAG,
(int)authlen,
(unsigned char *)complete_packet + aadlen + encrypted_size);
(unsigned char *)complete_packet + aadlen +
encrypted_size);
if (rc == 0) {
SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_TAG failed");
return SSH_ERROR;
@@ -731,11 +723,10 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher,
}
/* verify tag */
rc = EVP_DecryptFinal(cipher->ctx,
NULL,
&outlen);
rc = EVP_DecryptFinal(cipher->ctx, NULL, &outlen);
if (rc < 0) {
SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptFinal failed: Failed authentication");
SSH_LOG(SSH_LOG_TRACE,
"EVP_DecryptFinal failed: Failed authentication");
return SSH_ERROR;
}
@@ -749,7 +740,10 @@ struct chacha20_poly1305_keysched {
EVP_CIPHER_CTX *main_evp;
/* cipher handle used for encrypting the length field */
EVP_CIPHER_CTX *header_evp;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
#if defined(LIBRESSL_VERSION_NUMBER)
/* LibreSSL Poly1305 context */
poly1305_context poly_ctx;
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
/* mac handle used for authenticating the packets */
EVP_PKEY_CTX *pctx;
/* Poly1305 key */
@@ -762,8 +756,7 @@ struct chacha20_poly1305_keysched {
#endif /* OPENSSL_VERSION_NUMBER */
};
static void
chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher)
static void chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher)
{
struct chacha20_poly1305_keysched *ctx = NULL;
@@ -774,10 +767,12 @@ chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher)
ctx = cipher->chacha20_schedule;
EVP_CIPHER_CTX_free(ctx->main_evp);
ctx->main_evp = NULL;
ctx->main_evp = NULL;
EVP_CIPHER_CTX_free(ctx->header_evp);
ctx->header_evp = NULL;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
#if defined(LIBRESSL_VERSION_NUMBER)
/* nothing to free */
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
/* ctx->pctx is freed as part of MD context */
EVP_PKEY_free(ctx->key);
ctx->key = NULL;
@@ -791,10 +786,9 @@ chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher)
SAFE_FREE(cipher->chacha20_schedule);
}
static int
chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher,
void *key,
UNUSED_PARAM(void *IV))
static int chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher,
void *key,
UNUSED_PARAM(void *IV))
{
struct chacha20_poly1305_keysched *ctx = NULL;
uint8_t *u8key = key;
@@ -841,14 +835,16 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher,
/* The Poly1305 key initialization is delayed to the time we know
* the actual key for packet so we do not need to create a bogus keys
*/
#if OPENSSL_VERSION_NUMBER < 0x30000000L
#if defined(LIBRESSL_VERSION_NUMBER)
/* nothing, poly1305_context is stack based */
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
ctx->mctx = EVP_MD_CTX_new();
if (ctx->mctx == NULL) {
SSH_LOG(SSH_LOG_TRACE, "EVP_MD_CTX_new failed");
return SSH_ERROR;
}
#else
mac = EVP_MAC_fetch(NULL, "poly1305", NULL);
mac = EVP_MAC_fetch(NULL, SN_poly1305, NULL);
if (mac == NULL) {
SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_fetch failed");
goto out;
@@ -873,10 +869,9 @@ out:
static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0};
static int
chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher,
uint64_t seq,
int do_encrypt)
static int chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher,
uint64_t seq,
int do_encrypt)
{
struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
uint8_t seqbuf[16] = {0};
@@ -906,10 +901,9 @@ chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher,
return SSH_OK;
}
static int
chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher,
uint64_t seq,
int do_encrypt)
static int chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher,
uint64_t seq,
int do_encrypt)
{
struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
uint8_t poly_key[CHACHA20_BLOCKSIZE];
@@ -935,12 +929,17 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher,
ssh_log_hexdump("poly_key", poly_key, POLY1305_KEYLEN);
#endif /* DEBUG_CRYPTO */
/* Set the Poly1305 key */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
/* LibreSSL path: use direct Poly1305 implementation */
#if defined(LIBRESSL_VERSION_NUMBER)
CRYPTO_poly1305_init(&ctx->poly_ctx, poly_key);
/* Set the Poly1305 key */
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
if (ctx->key == NULL) {
/* Poly1305 Initialization needs to know the actual key */
ctx->key = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305, NULL,
poly_key, POLY1305_KEYLEN);
ctx->key = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305,
NULL,
poly_key,
POLY1305_KEYLEN);
if (ctx->key == NULL) {
SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_new_mac_key failed");
goto out;
@@ -952,9 +951,12 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher,
}
} else {
/* Updating the key is easier but less obvious */
rv = EVP_PKEY_CTX_ctrl(ctx->pctx, -1, EVP_PKEY_OP_SIGNCTX,
EVP_PKEY_CTRL_SET_MAC_KEY,
POLY1305_KEYLEN, (void *)poly_key);
rv = EVP_PKEY_CTX_ctrl(ctx->pctx,
-1,
EVP_PKEY_OP_SIGNCTX,
EVP_PKEY_CTRL_SET_MAC_KEY,
POLY1305_KEYLEN,
(void *)poly_key);
if (rv <= 0) {
SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_CTX_ctrl failed");
goto out;
@@ -970,7 +972,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher,
ret = SSH_OK;
out:
explicit_bzero(poly_key, sizeof(poly_key));
ssh_burn(poly_key, sizeof(poly_key));
return ret;
}
@@ -1017,20 +1019,21 @@ chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher,
return SSH_OK;
}
static int
chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher,
void *complete_packet,
uint8_t *out,
size_t encrypted_size,
uint64_t seq)
static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher,
void *complete_packet,
uint8_t *out,
size_t encrypted_size,
uint64_t seq)
{
struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) +
encrypted_size;
uint8_t *mac =
(uint8_t *)complete_packet + sizeof(uint32_t) + encrypted_size;
uint8_t tag[POLY1305_TAGLEN] = {0};
int ret = SSH_ERROR;
int rv, cmp, len = 0;
#if !defined(LIBRESSL_VERSION_NUMBER)
size_t taglen = POLY1305_TAGLEN;
#endif
/* Prepare the Poly1305 key */
rv = chacha20_poly1305_packet_setup(cipher, seq, 0);
@@ -1044,7 +1047,13 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher,
#endif /* DEBUG_CRYPTO */
/* Calculate MAC of received data */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
#if defined(LIBRESSL_VERSION_NUMBER)
CRYPTO_poly1305_update(&ctx->poly_ctx,
complete_packet,
encrypted_size + sizeof(uint32_t));
CRYPTO_poly1305_finish(&ctx->poly_ctx, tag);
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
rv = EVP_DigestSignUpdate(ctx->mctx, complete_packet,
encrypted_size + sizeof(uint32_t));
if (rv != 1) {
@@ -1058,7 +1067,8 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher,
goto out;
}
#else
rv = EVP_MAC_update(ctx->mctx, complete_packet,
rv = EVP_MAC_update(ctx->mctx,
complete_packet,
encrypted_size + sizeof(uint32_t));
if (rv != 1) {
SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed");
@@ -1106,17 +1116,18 @@ out:
return ret;
}
static void
chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
void *in,
void *out,
size_t len,
uint8_t *tag,
uint64_t seq)
static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
void *in,
void *out,
size_t len,
uint8_t *tag,
uint64_t seq)
{
struct ssh_packet_header *in_packet = in, *out_packet = out;
struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule;
#if !defined(LIBRESSL_VERSION_NUMBER)
size_t taglen = POLY1305_TAGLEN;
#endif
int ret, outlen = 0;
/* Prepare the Poly1305 key */
@@ -1128,7 +1139,8 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("plaintext length",
(unsigned char *)&in_packet->length, sizeof(uint32_t));
(unsigned char *)&in_packet->length,
sizeof(uint32_t));
#endif /* DEBUG_CRYPTO */
/* step 2, encrypt length field */
ret = EVP_CipherUpdate(ctx->header_evp,
@@ -1142,7 +1154,8 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
}
#ifdef DEBUG_CRYPTO
ssh_log_hexdump("encrypted length",
(unsigned char *)&out_packet->length, outlen);
(unsigned char *)&out_packet->length,
outlen);
#endif /* DEBUG_CRYPTO */
ret = EVP_CipherFinal_ex(ctx->header_evp, (uint8_t *)out + outlen, &outlen);
if (ret != 1 || outlen != 0) {
@@ -1163,7 +1176,13 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
}
/* step 4, compute the MAC */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
#if defined(LIBRESSL_VERSION_NUMBER)
CRYPTO_poly1305_update(&ctx->poly_ctx,
(const unsigned char *)out_packet,
len);
CRYPTO_poly1305_finish(&ctx->poly_ctx, tag);
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
ret = EVP_DigestSignUpdate(ctx->mctx, out_packet, len);
if (ret <= 0) {
SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed");
@@ -1175,7 +1194,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
return;
}
#else
ret = EVP_MAC_update(ctx->mctx, (void*)out_packet, len);
ret = EVP_MAC_update(ctx->mctx, (void *)out_packet, len);
if (ret != 1) {
SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed");
return;
@@ -1191,11 +1210,10 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
#endif /* HAVE_OPENSSL_EVP_CHACHA20 */
#ifdef WITH_INSECURE_NONE
static void
none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher),
void *in,
void *out,
size_t len)
static void none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher),
void *in,
void *out,
size_t len)
{
memcpy(out, in, len);
}
@@ -1206,163 +1224,163 @@ none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher),
*/
static struct ssh_cipher_struct ssh_ciphertab[] = {
#ifdef HAVE_BLOWFISH
{
.name = "blowfish-cbc",
.blocksize = 8,
.ciphertype = SSH_BLOWFISH_CBC,
.keysize = 128,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "blowfish-cbc",
.blocksize = 8,
.ciphertype = SSH_BLOWFISH_CBC,
.keysize = 128,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup,
},
#endif /* HAVE_BLOWFISH */
#ifdef HAS_AES
{
.name = "aes128-ctr",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES128_CTR,
.keysize = 128,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "aes192-ctr",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES192_CTR,
.keysize = 192,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "aes256-ctr",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES256_CTR,
.keysize = 256,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "aes128-cbc",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES128_CBC,
.keysize = 128,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "aes192-cbc",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES192_CBC,
.keysize = 192,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "aes256-cbc",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES256_CBC,
.keysize = 256,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "aes128-gcm@openssh.com",
.blocksize = AES_BLOCK_SIZE,
.lenfield_blocksize = 4, /* not encrypted, but authenticated */
.ciphertype = SSH_AEAD_AES128_GCM,
.keysize = 128,
.tag_size = AES_GCM_TAGLEN,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.aead_encrypt = evp_cipher_aead_encrypt,
.aead_decrypt_length = evp_cipher_aead_get_length,
.aead_decrypt = evp_cipher_aead_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "aes256-gcm@openssh.com",
.blocksize = AES_BLOCK_SIZE,
.lenfield_blocksize = 4, /* not encrypted, but authenticated */
.ciphertype = SSH_AEAD_AES256_GCM,
.keysize = 256,
.tag_size = AES_GCM_TAGLEN,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.aead_encrypt = evp_cipher_aead_encrypt,
.aead_decrypt_length = evp_cipher_aead_get_length,
.aead_decrypt = evp_cipher_aead_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "aes128-ctr",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES128_CTR,
.keysize = 128,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup,
},
{
.name = "aes192-ctr",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES192_CTR,
.keysize = 192,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup,
},
{
.name = "aes256-ctr",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES256_CTR,
.keysize = 256,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup,
},
{
.name = "aes128-cbc",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES128_CBC,
.keysize = 128,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup,
},
{
.name = "aes192-cbc",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES192_CBC,
.keysize = 192,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup,
},
{
.name = "aes256-cbc",
.blocksize = AES_BLOCK_SIZE,
.ciphertype = SSH_AES256_CBC,
.keysize = 256,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup,
},
{
.name = "aes128-gcm@openssh.com",
.blocksize = AES_BLOCK_SIZE,
.lenfield_blocksize = 4, /* not encrypted, but authenticated */
.ciphertype = SSH_AEAD_AES128_GCM,
.keysize = 128,
.tag_size = AES_GCM_TAGLEN,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.aead_encrypt = evp_cipher_aead_encrypt,
.aead_decrypt_length = evp_cipher_aead_get_length,
.aead_decrypt = evp_cipher_aead_decrypt,
.cleanup = evp_cipher_cleanup,
},
{
.name = "aes256-gcm@openssh.com",
.blocksize = AES_BLOCK_SIZE,
.lenfield_blocksize = 4, /* not encrypted, but authenticated */
.ciphertype = SSH_AEAD_AES256_GCM,
.keysize = 256,
.tag_size = AES_GCM_TAGLEN,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.aead_encrypt = evp_cipher_aead_encrypt,
.aead_decrypt_length = evp_cipher_aead_get_length,
.aead_decrypt = evp_cipher_aead_decrypt,
.cleanup = evp_cipher_cleanup,
},
#endif /* HAS_AES */
#ifdef HAS_DES
{
.name = "3des-cbc",
.blocksize = 8,
.ciphertype = SSH_3DES_CBC,
.keysize = 192,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup
},
{
.name = "3des-cbc",
.blocksize = 8,
.ciphertype = SSH_3DES_CBC,
.keysize = 192,
.set_encrypt_key = evp_cipher_set_encrypt_key,
.set_decrypt_key = evp_cipher_set_decrypt_key,
.encrypt = evp_cipher_encrypt,
.decrypt = evp_cipher_decrypt,
.cleanup = evp_cipher_cleanup,
},
#endif /* HAS_DES */
{
{
#ifdef HAVE_OPENSSL_EVP_CHACHA20
.ciphertype = SSH_AEAD_CHACHA20_POLY1305,
.name = "chacha20-poly1305@openssh.com",
.blocksize = CHACHA20_BLOCKSIZE/8,
.lenfield_blocksize = 4,
.keylen = sizeof(struct chacha20_poly1305_keysched),
.keysize = 2 * CHACHA20_KEYLEN * 8,
.tag_size = POLY1305_TAGLEN,
.set_encrypt_key = chacha20_poly1305_set_key,
.set_decrypt_key = chacha20_poly1305_set_key,
.aead_encrypt = chacha20_poly1305_aead_encrypt,
.aead_decrypt_length = chacha20_poly1305_aead_decrypt_length,
.aead_decrypt = chacha20_poly1305_aead_decrypt,
.cleanup = chacha20_poly1305_cleanup
.ciphertype = SSH_AEAD_CHACHA20_POLY1305,
.name = "chacha20-poly1305@openssh.com",
.blocksize = CHACHA20_BLOCKSIZE / 8,
.lenfield_blocksize = 4,
.keylen = sizeof(struct chacha20_poly1305_keysched),
.keysize = 2 * CHACHA20_KEYLEN * 8,
.tag_size = POLY1305_TAGLEN,
.set_encrypt_key = chacha20_poly1305_set_key,
.set_decrypt_key = chacha20_poly1305_set_key,
.aead_encrypt = chacha20_poly1305_aead_encrypt,
.aead_decrypt_length = chacha20_poly1305_aead_decrypt_length,
.aead_decrypt = chacha20_poly1305_aead_decrypt,
.cleanup = chacha20_poly1305_cleanup
#else
.name = "chacha20-poly1305@openssh.com"
.name = "chacha20-poly1305@openssh.com"
#endif /* HAVE_OPENSSL_EVP_CHACHA20 */
},
},
#ifdef WITH_INSECURE_NONE
{
.name = "none",
.blocksize = 8,
.keysize = 0,
.encrypt = none_crypt,
.decrypt = none_crypt,
},
{
.name = "none",
.blocksize = 8,
.keysize = 0,
.encrypt = none_crypt,
.decrypt = none_crypt,
},
#endif /* WITH_INSECURE_NONE */
{
.name = NULL
}
{
.name = NULL,
},
};
struct ssh_cipher_struct *ssh_get_ciphertab(void)
{
return ssh_ciphertab;
return ssh_ciphertab;
}
/**
@@ -1378,19 +1396,19 @@ int ssh_crypto_init(void)
if (libcrypto_initialized) {
return SSH_OK;
}
if (OpenSSL_version_num() != OPENSSL_VERSION_NUMBER){
SSH_LOG(SSH_LOG_DEBUG, "libssh compiled with %s "
"headers, currently running with %s.",
OPENSSL_VERSION_TEXT,
OpenSSL_version(OpenSSL_version_num())
);
if (OpenSSL_version_num() != OPENSSL_VERSION_NUMBER) {
SSH_LOG(SSH_LOG_DEBUG,
"libssh compiled with %s "
"headers, currently running with %s.",
OPENSSL_VERSION_TEXT,
OpenSSL_version(OpenSSL_version_num()));
}
#ifdef CAN_DISABLE_AESNI
/*
* disable AES-NI when running within Valgrind, because they generate
* too many "uninitialized memory access" false positives
*/
if (RUNNING_ON_VALGRIND){
if (RUNNING_ON_VALGRIND) {
SSH_LOG(SSH_LOG_INFO, "Running within Valgrind, disabling AES-NI");
/* Bit #57 denotes AES-NI instruction set extension */
OPENSSL_ia32cap &= ~(1LL << 57);
@@ -1453,7 +1471,8 @@ void ssh_crypto_finalize(void)
* @internal
* @brief Create EVP_PKEY from parameters
*
* @param[in] name Algorithm to use. For more info see manpage of EVP_PKEY_CTX_new_from_name
* @param[in] name Algorithm to use. For more info see manpage of
* EVP_PKEY_CTX_new_from_name
*
* @param[in] param_bld Constructed param builder for the pkey
*
@@ -1463,8 +1482,10 @@ void ssh_crypto_finalize(void)
*
* @return 0 on success, -1 on error
*/
int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld,
EVP_PKEY **pkey, int selection)
int evp_build_pkey(const char *name,
OSSL_PARAM_BLD *param_bld,
EVP_PKEY **pkey,
int selection)
{
int rc;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL);
@@ -1581,7 +1602,7 @@ evp_dup_pkey(const char *name, const ssh_key key, int demote, ssh_key new_key)
int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote)
{
return evp_dup_pkey("RSA", key, demote, new_key);
return evp_dup_pkey(SN_rsa, key, demote, new_key);
}
int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote)
@@ -1591,13 +1612,12 @@ int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote)
int evp_dup_ed25519_pkey(const ssh_key key, ssh_key new_key, int demote)
{
return evp_dup_pkey("ED25519", key, demote, new_key);
return evp_dup_pkey(SN_ED25519, key, demote, new_key);
}
#endif /* OPENSSL_VERSION_NUMBER */
ssh_string
pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p)
ssh_string pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p)
{
ssh_string s = NULL;
size_t len;
@@ -1633,15 +1653,14 @@ pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p)
int pki_key_ecgroup_name_to_nid(const char *group)
{
if (strcmp(group, NISTP256) == 0 ||
strcmp(group, "secp256r1") == 0 ||
strcmp(group, "prime256v1") == 0) {
if (strcmp(group, NISTP256) == 0 || strcmp(group, "secp256r1") == 0 ||
strcmp(group, SN_X9_62_prime256v1) == 0) {
return NID_X9_62_prime256v1;
} else if (strcmp(group, NISTP384) == 0 ||
strcmp(group, "secp384r1") == 0) {
strcmp(group, SN_secp384r1) == 0) {
return NID_secp384r1;
} else if (strcmp(group, NISTP521) == 0 ||
strcmp(group, "secp521r1") == 0) {
strcmp(group, SN_secp521r1) == 0) {
return NID_secp521r1;
}
return -1;

View File

@@ -602,7 +602,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher,
}
out:
explicit_bzero(poly_key, sizeof(poly_key));
ssh_burn(poly_key, sizeof(poly_key));
}
static int chacha20_poly1305_aead_decrypt_length(
@@ -714,7 +714,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher,
ret = SSH_OK;
out:
explicit_bzero(poly_key, sizeof(poly_key));
ssh_burn(poly_key, sizeof(poly_key));
return ret;
}
#endif /* HAVE_GCRYPT_CHACHA_POLY */

View File

@@ -711,7 +711,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher,
ret = SSH_OK;
out:
explicit_bzero(poly_key, sizeof(poly_key));
ssh_burn(poly_key, sizeof(poly_key));
return ret;
}

View File

@@ -322,3 +322,52 @@ md5_final(unsigned char *md, MD5CTX c)
}
return SSH_OK;
}
/**
* @ brief One-shot MD5. Not intended for use in security-relevant contexts.
*/
int
md5(const unsigned char *digest, size_t len, unsigned char *hash)
{
int rc, ret = SSH_ERROR;
unsigned int mdlen = 0;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
EVP_MD *md5 = NULL;
#endif
MD5CTX c = EVP_MD_CTX_new();
if (c == NULL) {
goto out;
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
md5 = EVP_MD_fetch(NULL, "MD5", "provider=default,-fips");
if (md5 == NULL) {
goto out;
}
rc = EVP_DigestInit(c, md5);
#else
rc = EVP_DigestInit_ex(c, EVP_md5(), NULL);
#endif
if (rc == 0) {
goto out;
}
rc = EVP_DigestUpdate(c, digest, len);
if (rc != 1) {
goto out;
}
rc = EVP_DigestFinal(c, hash, &mdlen);
if (rc != 1) {
goto out;
}
ret = SSH_OK;
out:
EVP_MD_CTX_free(c);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
EVP_MD_free(md5);
#endif
return ret;
}

View File

@@ -244,3 +244,9 @@ md5_final(unsigned char *md, MD5CTX c)
gcry_md_close(c);
return SSH_OK;
}
int md5(const unsigned char *digest, size_t len, unsigned char *hash)
{
gcry_md_hash_buffer(GCRY_MD_MD5, hash, digest, len);
return SSH_OK;
}

View File

@@ -405,3 +405,51 @@ md5_final(unsigned char *md, MD5CTX c)
}
return SSH_OK;
}
int md5(const unsigned char *digest, size_t len, unsigned char *hash)
{
MD5CTX ctx = NULL;
int rc;
const mbedtls_md_info_t *md_info =
mbedtls_md_info_from_type(MBEDTLS_MD_MD5);
if (md_info == NULL) {
return SSH_ERROR;
}
ctx = malloc(sizeof(mbedtls_md_context_t));
if (ctx == NULL) {
return SSH_ERROR;
}
mbedtls_md_init(ctx);
rc = mbedtls_md_setup(ctx, md_info, 0);
if (rc != 0) {
mbedtls_md_free(ctx);
SAFE_FREE(ctx);
return SSH_ERROR;
}
rc = mbedtls_md_starts(ctx);
if (rc != 0) {
mbedtls_md_free(ctx);
SAFE_FREE(ctx);
return SSH_ERROR;
}
rc = mbedtls_md_update(ctx, digest, len);
if (rc != 0) {
mbedtls_md_free(ctx);
SAFE_FREE(ctx);
return SSH_ERROR;
}
rc = mbedtls_md_finish(ctx, hash);
mbedtls_md_free(ctx);
SAFE_FREE(ctx);
if (rc != 0) {
return SSH_ERROR;
}
return SSH_OK;
}

View File

@@ -184,6 +184,19 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg)
ssh_message_reply_default(msg);
}
return SSH_OK;
} else if (msg->auth_request.method == SSH_AUTH_METHOD_INTERACTIVE &&
ssh_callbacks_exists(session->server_callbacks, auth_kbdint_function)) {
rc = session->server_callbacks->auth_kbdint_function(msg,
session,
session->server_callbacks->userdata);
if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) {
ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL);
} else if (rc == SSH_AUTH_INFO) {
return SSH_OK;
} else {
ssh_message_reply_default(msg);
}
return SSH_OK;
}
break;
@@ -670,9 +683,9 @@ void ssh_message_free(ssh_message msg){
SAFE_FREE(msg->auth_request.username);
SAFE_FREE(msg->auth_request.sigtype);
if (msg->auth_request.password) {
explicit_bzero(msg->auth_request.password,
strlen(msg->auth_request.password));
SAFE_FREE(msg->auth_request.password);
ssh_burn(msg->auth_request.password,
strlen(msg->auth_request.password));
SAFE_FREE(msg->auth_request.password);
}
ssh_key_free(msg->auth_request.pubkey);
ssh_key_free(msg->auth_request.server_pubkey);
@@ -1145,6 +1158,75 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request)
SAFE_FREE(method);
SSH_MESSAGE_FREE(msg);
return SSH_PACKET_USED;
}
if (strcmp(method, "gssapi-keyex") == 0) {
gss_buffer_desc received_mic = GSS_C_EMPTY_BUFFER;
gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER;
ssh_string mic_token_string = NULL;
OM_uint32 maj_stat, min_stat;
ssh_buffer buf = NULL;
if (!ssh_kex_is_gss(session->current_crypto)) {
ssh_set_error(session,
SSH_FATAL,
"Attempt to authenticate with gssapi-keyex without "
"doing GSSAPI Key Exchange.");
ssh_auth_reply_default(session, 0);
goto error;
}
if (session->gssapi == NULL || session->gssapi->ctx == NULL) {
ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized");
ssh_auth_reply_default(session, 0);
goto error;
}
rc = ssh_buffer_unpack(packet, "S", &mic_token_string);
if (rc != SSH_OK) {
ssh_auth_reply_default(session, 0);
goto error;
}
received_mic.length = ssh_string_len(mic_token_string);
received_mic.value = ssh_string_data(mic_token_string);
SAFE_FREE(session->gssapi->user);
session->gssapi->user = strdup(msg->auth_request.username);
buf = ssh_gssapi_build_mic(session, "gssapi-keyex");
if (buf == NULL) {
ssh_set_error_oom(session);
SSH_STRING_FREE(mic_token_string);
ssh_auth_reply_default(session, 0);
goto error;
}
mic_buf.length = ssh_buffer_get_len(buf);
mic_buf.value = ssh_buffer_get(buf);
maj_stat = gss_verify_mic(&min_stat,
session->gssapi->ctx,
&mic_buf,
&received_mic,
NULL);
if (maj_stat != GSS_S_COMPLETE) {
ssh_set_error(session,
SSH_FATAL,
"Failed to verify MIC for gssapi-keyex auth");
SSH_BUFFER_FREE(buf);
SSH_STRING_FREE(mic_token_string);
ssh_auth_reply_default(session, 0);
goto error;
}
ssh_auth_reply_success(session, 0);
/* bypass the message queue thing */
SAFE_FREE(service);
SAFE_FREE(method);
SSH_BUFFER_FREE(buf);
SSH_MESSAGE_FREE(msg);
SSH_STRING_FREE(mic_token_string);
return SSH_PACKET_USED;
}
#endif
@@ -1236,9 +1318,9 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){
uint32_t n;
for (n = 0; n < session->kbdint->nanswers; n++) {
explicit_bzero(session->kbdint->answers[n],
strlen(session->kbdint->answers[n]));
SAFE_FREE(session->kbdint->answers[n]);
ssh_burn(session->kbdint->answers[n],
strlen(session->kbdint->answers[n]));
SAFE_FREE(session->kbdint->answers[n]);
}
SAFE_FREE(session->kbdint->answers);
session->kbdint->nanswers = 0;

View File

@@ -1608,23 +1608,6 @@ int ssh_timeout_update(struct ssh_timestamp *ts, int timeout)
return ret >= 0 ? ret: 0;
}
#if !defined(HAVE_EXPLICIT_BZERO)
void explicit_bzero(void *s, size_t n)
{
#if defined(HAVE_MEMSET_S)
memset_s(s, n, '\0', n);
#elif defined(HAVE_SECURE_ZERO_MEMORY)
SecureZeroMemory(s, n);
#else
memset(s, '\0', n);
#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION)
/* See http://llvm.org/bugs/show_bug.cgi?id=15495 */
__asm__ volatile("" : : "g"(s) : "memory");
#endif /* HAVE_GCC_VOLATILE_MEMORY_PROTECTION */
#endif
}
#endif /* !HAVE_EXPLICIT_BZERO */
/**
* @brief Securely free memory by overwriting it before deallocation
*
@@ -1642,7 +1625,7 @@ void burn_free(void *ptr, size_t len)
return;
}
explicit_bzero(ptr, len);
ssh_burn(ptr, len);
free(ptr);
}

40
src/mlkem.c Normal file
View File

@@ -0,0 +1,40 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Pavol Žáčik <pzacik@redhat.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include "libssh/mlkem.h"
const struct mlkem_type_info *kex_type_to_mlkem_info(enum ssh_key_exchange_e kex_type)
{
switch (kex_type) {
case SSH_KEX_MLKEM768X25519_SHA256:
case SSH_KEX_MLKEM768NISTP256_SHA256:
return &MLKEM768_INFO;
#ifdef HAVE_MLKEM1024
case SSH_KEX_MLKEM1024NISTP384_SHA384:
return &MLKEM1024_INFO;
#endif
default:
return NULL;
}
}

View File

@@ -1,679 +0,0 @@
/*
* mlkem768x25519.c - ML-KEM768x25519 hybrid key exchange
* mlkem768x25519-sha256
*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Sahana Prasad <sahana@redhat.com>
* Author: Claude (Anthropic)
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include "libssh/bignum.h"
#include "libssh/buffer.h"
#include "libssh/crypto.h"
#include "libssh/curve25519.h"
#include "libssh/dh.h"
#include "libssh/mlkem768.h"
#include "libssh/pki.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include "libssh/ssh2.h"
#include <string.h>
#include <openssl/evp.h>
#include <openssl/err.h>
static SSH_PACKET_CALLBACK(ssh_packet_client_mlkem768x25519_reply);
static ssh_packet_callback dh_client_callbacks[] = {
ssh_packet_client_mlkem768x25519_reply,
};
static struct ssh_packet_callbacks_struct ssh_mlkem768x25519_client_callbacks =
{
.start = SSH2_MSG_KEX_HYBRID_REPLY,
.n_callbacks = 1,
.callbacks = dh_client_callbacks,
.user = NULL,
};
/* Generate ML-KEM768 keypair using OpenSSL */
static int mlkem768_keypair_gen(ssh_mlkem768_pubkey pubkey,
ssh_mlkem768_privkey privkey)
{
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY *pkey = NULL;
int rc, ret = SSH_ERROR;
size_t pubkey_len = MLKEM768_PUBLICKEY_SIZE;
size_t privkey_len = MLKEM768_SECRETKEY_SIZE;
ctx = EVP_PKEY_CTX_new_from_name(NULL, "ML-KEM-768", NULL);
if (ctx == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_keygen_init(ctx);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to initialize ML-KEM-768 keygen: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_keygen(ctx, &pkey);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to perform ML-KEM-768 keygen: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_get_raw_public_key(pkey, pubkey, &pubkey_len);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to extract ML-KEM-768 public key: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_get_raw_private_key(pkey, privkey, &privkey_len);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to extract ML-KEM-768 private key: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ret = SSH_OK;
cleanup:
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
return ret;
}
/* Encapsulate shared secret using ML-KEM768 - used by server side */
static int mlkem768_encapsulate(const ssh_mlkem768_pubkey pubkey,
ssh_mlkem768_ciphertext ciphertext,
unsigned char *shared_secret)
{
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *ctx = NULL;
int rc, ret = SSH_ERROR;
size_t ct_len = MLKEM768_CIPHERTEXT_SIZE;
size_t ss_len = MLKEM768_SHARED_SECRET_SIZE;
pkey = EVP_PKEY_new_raw_public_key_ex(NULL,
"ML-KEM-768",
NULL,
pubkey,
MLKEM768_PUBLICKEY_SIZE);
if (pkey == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 public key from raw data: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
if (ctx == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_encapsulate_init(ctx, NULL);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to initialize ML-KEM-768 encapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_encapsulate(ctx, ciphertext, &ct_len, shared_secret, &ss_len);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to perform ML-KEM-768 encapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ret = SSH_OK;
cleanup:
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
return ret;
}
/* Decapsulate shared secret using ML-KEM768 - used by client side */
static int mlkem768_decapsulate(const ssh_mlkem768_privkey privkey,
const ssh_mlkem768_ciphertext ciphertext,
unsigned char *shared_secret)
{
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *ctx = NULL;
int rc, ret = SSH_ERROR;
size_t ss_len = MLKEM768_SHARED_SECRET_SIZE;
pkey = EVP_PKEY_new_raw_private_key_ex(NULL,
"ML-KEM-768",
NULL,
privkey,
MLKEM768_SECRETKEY_SIZE);
if (pkey == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
if (ctx == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM-768 context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_decapsulate_init(ctx, NULL);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to initialize ML-KEM-768 decapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_decapsulate(ctx,
shared_secret,
&ss_len,
ciphertext,
MLKEM768_CIPHERTEXT_SIZE);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to perform ML-KEM-768 decapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ret = SSH_OK;
cleanup:
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
return ret;
}
int ssh_client_mlkem768x25519_init(ssh_session session)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_buffer client_pubkey = NULL;
ssh_string pubkey_blob = NULL;
int rc;
SSH_LOG(SSH_LOG_TRACE, "Initializing ML-KEM768x25519 key exchange");
/* Initialize Curve25519 component first */
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
return rc;
}
/* Generate ML-KEM768 keypair */
rc = mlkem768_keypair_gen(crypto->mlkem768_client_pubkey,
crypto->mlkem768_client_privkey);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate ML-KEM768 keypair");
return SSH_ERROR;
}
/* Create hybrid client public key: ML-KEM768 + Curve25519 */
client_pubkey = ssh_buffer_new();
if (client_pubkey == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
rc = SSH_ERROR;
goto cleanup;
}
rc = ssh_buffer_pack(client_pubkey,
"PP",
MLKEM768_PUBLICKEY_SIZE,
crypto->mlkem768_client_pubkey,
CURVE25519_PUBKEY_SIZE,
crypto->curve25519_client_pubkey);
if (rc != SSH_OK) {
session->session_state = SSH_SESSION_STATE_ERROR;
rc = SSH_ERROR;
goto cleanup;
}
/* Convert to string for sending */
pubkey_blob = ssh_string_new(ssh_buffer_get_len(client_pubkey));
if (pubkey_blob == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
rc = SSH_ERROR;
goto cleanup;
}
ssh_string_fill(pubkey_blob,
ssh_buffer_get(client_pubkey),
ssh_buffer_get_len(client_pubkey));
/* Send the hybrid public key to server */
rc = ssh_buffer_pack(session->out_buffer,
"bS",
SSH2_MSG_KEX_HYBRID_INIT,
pubkey_blob);
if (rc != SSH_OK) {
session->session_state = SSH_SESSION_STATE_ERROR;
rc = SSH_ERROR;
goto cleanup;
}
session->dh_handshake_state = DH_STATE_INIT_SENT;
ssh_packet_set_callbacks(session, &ssh_mlkem768x25519_client_callbacks);
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_ECDH_INIT");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
cleanup:
ssh_buffer_free(client_pubkey);
ssh_string_free(pubkey_blob);
return rc;
}
static SSH_PACKET_CALLBACK(ssh_packet_client_mlkem768x25519_reply)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_string s_server_blob = NULL;
ssh_string s_pubkey_blob = NULL;
ssh_string s_signature = NULL;
const unsigned char *server_data = NULL;
unsigned char mlkem_shared_secret[MLKEM768_SHARED_SECRET_SIZE];
unsigned char curve25519_shared_secret[CURVE25519_PUBKEY_SIZE];
unsigned char combined_secret[MLKEM768X25519_SHARED_SECRET_SIZE];
unsigned char hashed_secret[SHA256_DIGEST_LEN];
size_t server_blob_len;
int rc;
(void)type;
(void)user;
SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM768x25519 server reply");
ssh_client_mlkem768x25519_remove_callbacks(session);
s_pubkey_blob = ssh_buffer_get_ssh_string(packet);
if (s_pubkey_blob == NULL) {
ssh_set_error(session, SSH_FATAL, "No public key in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_dh_import_next_pubkey_blob(session, s_pubkey_blob);
if (rc != 0) {
ssh_set_error(session, SSH_FATAL, "Failed to import next public key");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Get server blob containing ML-KEM768 ciphertext + Curve25519 pubkey */
s_server_blob = ssh_buffer_get_ssh_string(packet);
if (s_server_blob == NULL) {
ssh_set_error(session, SSH_FATAL, "No server blob in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
server_data = ssh_string_data(s_server_blob);
server_blob_len = ssh_string_len(s_server_blob);
/* Expect ML-KEM768 ciphertext + Curve25519 pubkey */
if (server_blob_len != MLKEM768X25519_SERVER_RESPONSE_SIZE) {
ssh_set_error(session, SSH_FATAL, "Invalid server blob size");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Store ML-KEM768 ciphertext for sessionid calculation */
memcpy(crypto->mlkem768_ciphertext, server_data, MLKEM768_CIPHERTEXT_SIZE);
/* Decapsulate ML-KEM768 shared secret */
rc = mlkem768_decapsulate(crypto->mlkem768_client_privkey,
crypto->mlkem768_ciphertext,
mlkem_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "ML-KEM768 decapsulation failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Store server Curve25519 public key for shared secret computation */
memcpy(crypto->curve25519_server_pubkey,
server_data + MLKEM768_CIPHERTEXT_SIZE,
CURVE25519_PUBKEY_SIZE);
/* Derive Curve25519 shared secret using existing libssh function */
rc = ssh_curve25519_create_k(session, curve25519_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Curve25519 ECDH failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Combine secrets: ML-KEM768 + Curve25519 for hybrid approach */
memcpy(combined_secret, mlkem_shared_secret, MLKEM768_SHARED_SECRET_SIZE);
memcpy(combined_secret + MLKEM768_SHARED_SECRET_SIZE,
curve25519_shared_secret,
CURVE25519_PUBKEY_SIZE);
sha256(combined_secret, MLKEM768X25519_SHARED_SECRET_SIZE, hashed_secret);
bignum_bin2bn(hashed_secret, SHA256_DIGEST_LEN, &crypto->shared_secret);
if (crypto->shared_secret == NULL) {
ssh_set_error(session, SSH_FATAL, "Failed to create shared secret bignum");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Get signature for verification */
s_signature = ssh_buffer_get_ssh_string(packet);
if (s_signature == NULL) {
ssh_set_error(session, SSH_FATAL, "No signature in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
crypto->dh_server_signature = s_signature;
s_signature = NULL;
/* Send the MSG_NEWKEYS */
rc = ssh_packet_send_newkeys(session);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
cleanup:
/* Clear sensitive data */
explicit_bzero(mlkem_shared_secret, sizeof(mlkem_shared_secret));
explicit_bzero(curve25519_shared_secret, sizeof(curve25519_shared_secret));
explicit_bzero(combined_secret, sizeof(combined_secret));
explicit_bzero(hashed_secret, sizeof(hashed_secret));
ssh_string_free(s_pubkey_blob);
ssh_string_free(s_server_blob);
ssh_string_free(s_signature);
return SSH_PACKET_USED;
}
void ssh_client_mlkem768x25519_remove_callbacks(ssh_session session)
{
ssh_packet_remove_callbacks(session, &ssh_mlkem768x25519_client_callbacks);
}
#ifdef WITH_SERVER
static SSH_PACKET_CALLBACK(ssh_packet_server_mlkem768x25519_init);
static ssh_packet_callback dh_server_callbacks[] = {
ssh_packet_server_mlkem768x25519_init,
};
static struct ssh_packet_callbacks_struct ssh_mlkem768x25519_server_callbacks =
{
.start = SSH2_MSG_KEX_HYBRID_INIT,
.n_callbacks = 1,
.callbacks = dh_server_callbacks,
.user = NULL,
};
static SSH_PACKET_CALLBACK(ssh_packet_server_mlkem768x25519_init)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_string client_pubkey_blob = NULL;
ssh_string server_pubkey_blob = NULL;
ssh_buffer server_response = NULL;
const unsigned char *client_data = NULL;
unsigned char mlkem_shared_secret[MLKEM768_SHARED_SECRET_SIZE];
unsigned char curve25519_shared_secret[CURVE25519_PUBKEY_SIZE];
unsigned char combined_secret[MLKEM768X25519_SHARED_SECRET_SIZE];
unsigned char mlkem_ciphertext[MLKEM768_CIPHERTEXT_SIZE];
unsigned char hashed_secret[SHA256_DIGEST_LEN];
size_t client_blob_len;
ssh_key privkey = NULL;
enum ssh_digest_e digest = SSH_DIGEST_AUTO;
ssh_string sig_blob = NULL;
ssh_string server_hostkey_blob = NULL;
int rc = SSH_ERROR;
(void)type;
(void)user;
SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM768x25519 client init");
ssh_packet_remove_callbacks(session, &ssh_mlkem768x25519_server_callbacks);
/* Get client hybrid public key: ML-KEM768 + Curve25519 */
client_pubkey_blob = ssh_buffer_get_ssh_string(packet);
if (client_pubkey_blob == NULL) {
ssh_set_error(session, SSH_FATAL, "No client public key in packet");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
client_data = ssh_string_data(client_pubkey_blob);
client_blob_len = ssh_string_len(client_pubkey_blob);
/* Expect ML-KEM768 pubkey + Curve25519 pubkey */
if (client_blob_len != MLKEM768X25519_CLIENT_PUBKEY_SIZE) {
ssh_set_error(session, SSH_FATAL, "Invalid client public key size");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Extract client ML-KEM768 public key */
memcpy(crypto->mlkem768_client_pubkey,
client_data,
MLKEM768_PUBLICKEY_SIZE);
/* Extract client Curve25519 public key */
memcpy(crypto->curve25519_client_pubkey,
client_data + MLKEM768_PUBLICKEY_SIZE,
CURVE25519_PUBKEY_SIZE);
/* Generate server Curve25519 keypair */
rc = ssh_curve25519_init(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Failed to generate server Curve25519 key");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Derive Curve25519 shared secret */
rc = ssh_curve25519_create_k(session, curve25519_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Curve25519 ECDH failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Encapsulate ML-KEM768 shared secret using client's public key */
rc = mlkem768_encapsulate(client_data, mlkem_ciphertext, mlkem_shared_secret);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "ML-KEM768 encapsulation failed");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Store ML-KEM768 ciphertext for sessionid calculation */
memcpy(crypto->mlkem768_ciphertext, mlkem_ciphertext, MLKEM768_CIPHERTEXT_SIZE);
/* Combine secrets: ML-KEM768 + Curve25519 for hybrid approach */
memcpy(combined_secret, mlkem_shared_secret, MLKEM768_SHARED_SECRET_SIZE);
memcpy(combined_secret + MLKEM768_SHARED_SECRET_SIZE,
curve25519_shared_secret,
CURVE25519_PUBKEY_SIZE);
sha256(combined_secret, MLKEM768X25519_SHARED_SECRET_SIZE, hashed_secret);
/* Store the combined secret */
bignum_bin2bn(hashed_secret, SHA256_DIGEST_LEN, &crypto->shared_secret);
if (crypto->shared_secret == NULL) {
ssh_set_error(session, SSH_FATAL, "Failed to create shared secret bignum");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Create server response: ML-KEM768 ciphertext + Curve25519 pubkey */
server_response = ssh_buffer_new();
if (server_response == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_buffer_pack(server_response,
"PP",
MLKEM768_CIPHERTEXT_SIZE,
mlkem_ciphertext,
CURVE25519_PUBKEY_SIZE,
crypto->curve25519_server_pubkey);
if (rc != SSH_OK) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Convert to string for sending */
server_pubkey_blob = ssh_string_new(ssh_buffer_get_len(server_response));
if (server_pubkey_blob == NULL) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
ssh_string_fill(server_pubkey_blob,
ssh_buffer_get(server_response),
ssh_buffer_get_len(server_response));
/* Add MSG_KEX_ECDH_REPLY header */
rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_HYBRID_REPLY);
if (rc < 0) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Get server host key */
rc = ssh_get_key_params(session, &privkey, &digest);
if (rc == SSH_ERROR) {
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Build session ID */
rc = ssh_make_sessionid(session);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Could not create a session id");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_dh_get_next_server_publickey_blob(session, &server_hostkey_blob);
if (rc != 0) {
ssh_set_error(session, SSH_FATAL, "Could not export server public key");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add server host key to output */
rc = ssh_buffer_add_ssh_string(session->out_buffer, server_hostkey_blob);
SSH_STRING_FREE(server_hostkey_blob);
if (rc < 0) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add server response (ciphertext + pubkey) */
rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob);
if (rc < 0) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Sign the exchange hash */
sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest);
if (sig_blob == NULL) {
ssh_set_error(session, SSH_FATAL, "Could not sign the session id");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Add signature */
rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob);
SSH_STRING_FREE(sig_blob);
if (rc < 0) {
ssh_set_error_oom(session);
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_ECDH_REPLY");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
/* Send the MSG_NEWKEYS */
rc = ssh_packet_send_newkeys(session);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS");
session->session_state = SSH_SESSION_STATE_ERROR;
goto cleanup;
}
session->dh_handshake_state = DH_STATE_NEWKEYS_SENT;
cleanup:
/* Clear sensitive data */
explicit_bzero(mlkem_shared_secret, sizeof(mlkem_shared_secret));
explicit_bzero(curve25519_shared_secret, sizeof(curve25519_shared_secret));
explicit_bzero(combined_secret, sizeof(combined_secret));
explicit_bzero(hashed_secret, sizeof(hashed_secret));
ssh_string_free(client_pubkey_blob);
ssh_string_free(server_pubkey_blob);
ssh_buffer_free(server_response);
return SSH_PACKET_USED;
}
void ssh_server_mlkem768x25519_init(ssh_session session)
{
SSH_LOG(SSH_LOG_TRACE, "Setting up ML-KEM768x25519 server callbacks");
ssh_packet_set_callbacks(session, &ssh_mlkem768x25519_server_callbacks);
}
#endif /* WITH_SERVER */

238
src/mlkem_crypto.c Normal file
View File

@@ -0,0 +1,238 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Pavol Žáčik <pzacik@redhat.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include "libssh/crypto.h"
#include "libssh/mlkem.h"
#include "libssh/session.h"
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ml_kem.h>
const struct mlkem_type_info MLKEM768_INFO = {
.pubkey_size = OSSL_ML_KEM_768_PUBLIC_KEY_BYTES,
.ciphertext_size = OSSL_ML_KEM_768_CIPHERTEXT_BYTES,
.name = LN_ML_KEM_768,
};
const struct mlkem_type_info MLKEM1024_INFO = {
.pubkey_size = OSSL_ML_KEM_1024_PUBLIC_KEY_BYTES,
.ciphertext_size = OSSL_ML_KEM_1024_CIPHERTEXT_BYTES,
.name = LN_ML_KEM_1024,
};
int ssh_mlkem_init(ssh_session session)
{
struct ssh_crypto_struct *crypto = session->next_crypto;
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY *pkey = NULL;
int rc, ret = SSH_ERROR;
const struct mlkem_type_info *mlkem_info = NULL;
ssh_string pubkey = NULL;
size_t pubkey_size;
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
goto cleanup;
}
ctx = EVP_PKEY_CTX_new_from_name(NULL, mlkem_info->name, NULL);
if (ctx == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_keygen_init(ctx);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to initialize ML-KEM keygen: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_keygen(ctx, &pkey);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to perform ML-KEM keygen: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
EVP_PKEY_free(crypto->mlkem_privkey);
crypto->mlkem_privkey = pkey;
pubkey_size = mlkem_info->pubkey_size;
pubkey = ssh_string_new(pubkey_size);
if (pubkey == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
rc = EVP_PKEY_get_raw_public_key(pkey,
ssh_string_data(pubkey),
&pubkey_size);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to extract ML-KEM public key: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ssh_string_free(crypto->mlkem_client_pubkey);
crypto->mlkem_client_pubkey = pubkey;
pubkey = NULL;
ret = SSH_OK;
cleanup:
ssh_string_free(pubkey);
EVP_PKEY_CTX_free(ctx);
return ret;
}
int ssh_mlkem_encapsulate(ssh_session session,
ssh_mlkem_shared_secret shared_secret)
{
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *ctx = NULL;
int rc, ret = SSH_ERROR;
const struct mlkem_type_info *mlkem_info = NULL;
struct ssh_crypto_struct *crypto = session->next_crypto;
const unsigned char *pubkey = ssh_string_data(crypto->mlkem_client_pubkey);
const size_t pubkey_len = ssh_string_len(crypto->mlkem_client_pubkey);
size_t shared_secret_size = MLKEM_SHARED_SECRET_SIZE;
ssh_string ciphertext = NULL;
size_t ciphertext_size;
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
goto cleanup;
}
pkey = EVP_PKEY_new_raw_public_key_ex(NULL,
mlkem_info->name,
NULL,
pubkey,
pubkey_len);
if (pkey == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM public key from raw data: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL);
if (ctx == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_encapsulate_init(ctx, NULL);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to initialize ML-KEM encapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ciphertext_size = mlkem_info->ciphertext_size;
ciphertext = ssh_string_new(ciphertext_size);
if (ciphertext == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
rc = EVP_PKEY_encapsulate(ctx,
ssh_string_data(ciphertext),
&ciphertext_size,
shared_secret,
&shared_secret_size);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to perform ML-KEM encapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ssh_string_free(crypto->mlkem_ciphertext);
crypto->mlkem_ciphertext = ciphertext;
ciphertext = NULL;
ret = SSH_OK;
cleanup:
ssh_string_free(ciphertext);
EVP_PKEY_free(pkey);
EVP_PKEY_CTX_free(ctx);
return ret;
}
int ssh_mlkem_decapsulate(const ssh_session session,
ssh_mlkem_shared_secret shared_secret)
{
EVP_PKEY_CTX *ctx = NULL;
int rc, ret = SSH_ERROR;
size_t shared_secret_size = MLKEM_SHARED_SECRET_SIZE;
struct ssh_crypto_struct *crypto = session->next_crypto;
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, crypto->mlkem_privkey, NULL);
if (ctx == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to create ML-KEM context: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_decapsulate_init(ctx, NULL);
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to initialize ML-KEM decapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
rc = EVP_PKEY_decapsulate(ctx,
shared_secret,
&shared_secret_size,
ssh_string_data(crypto->mlkem_ciphertext),
ssh_string_len(crypto->mlkem_ciphertext));
if (rc != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to perform ML-KEM decapsulation: %s",
ERR_error_string(ERR_get_error(), NULL));
goto cleanup;
}
ret = SSH_OK;
cleanup:
EVP_PKEY_CTX_free(ctx);
return ret;
}

207
src/mlkem_gcrypt.c Normal file
View File

@@ -0,0 +1,207 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Jakub Jelen <jjelen@redhat.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include "libssh/crypto.h"
#include "libssh/mlkem.h"
#include "libssh/session.h"
#include <gcrypt.h>
const struct mlkem_type_info MLKEM768_INFO = {
.pubkey_size = GCRY_KEM_MLKEM768_PUBKEY_LEN,
.privkey_size = GCRY_KEM_MLKEM768_SECKEY_LEN,
.ciphertext_size = GCRY_KEM_MLKEM768_CIPHER_LEN,
.alg = GCRY_KEM_MLKEM768,
};
const struct mlkem_type_info MLKEM1024_INFO = {
.pubkey_size = GCRY_KEM_MLKEM1024_PUBKEY_LEN,
.privkey_size = GCRY_KEM_MLKEM1024_SECKEY_LEN,
.ciphertext_size = GCRY_KEM_MLKEM1024_CIPHER_LEN,
.alg = GCRY_KEM_MLKEM1024,
};
int ssh_mlkem_init(ssh_session session)
{
int ret = SSH_ERROR;
struct ssh_crypto_struct *crypto = session->next_crypto;
const struct mlkem_type_info *mlkem_info = NULL;
ssh_string pubkey = NULL;
unsigned char *privkey = NULL, *pubkey_data = NULL;
gcry_error_t err;
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
goto cleanup;
}
privkey = malloc(mlkem_info->privkey_size);
if (privkey == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
pubkey = ssh_string_new(mlkem_info->pubkey_size);
if (pubkey == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
pubkey_data = ssh_string_data(pubkey);
err = gcry_kem_keypair(mlkem_info->alg,
pubkey_data,
mlkem_info->pubkey_size,
privkey,
mlkem_info->privkey_size);
if (err) {
SSH_LOG(SSH_LOG_TRACE,
"Failed to generate ML-KEM key: %s",
gpg_strerror(err));
goto cleanup;
}
ssh_string_free(crypto->mlkem_client_pubkey);
crypto->mlkem_client_pubkey = pubkey;
pubkey = NULL;
free(crypto->mlkem_privkey);
crypto->mlkem_privkey = privkey;
crypto->mlkem_privkey_len = mlkem_info->privkey_size;
privkey = NULL;
ret = SSH_OK;
cleanup:
ssh_string_free(pubkey);
if (privkey != NULL) {
ssh_burn(privkey, mlkem_info->privkey_size);
free(privkey);
}
return ret;
}
int ssh_mlkem_encapsulate(ssh_session session,
ssh_mlkem_shared_secret shared_secret)
{
int ret = SSH_ERROR;
const struct mlkem_type_info *mlkem_info = NULL;
struct ssh_crypto_struct *crypto = session->next_crypto;
const unsigned char *pubkey_data = NULL;
unsigned char *ciphertext_data = NULL;
ssh_string ciphertext = NULL;
ssh_string pubkey = crypto->mlkem_client_pubkey;
gcry_error_t err;
if (pubkey == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Missing pubkey in session");
return SSH_ERROR;
}
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
return SSH_ERROR;
}
ciphertext = ssh_string_new(mlkem_info->ciphertext_size);
if (ciphertext == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
pubkey_data = ssh_string_data(pubkey);
ciphertext_data = ssh_string_data(ciphertext);
err = gcry_kem_encap(mlkem_info->alg,
pubkey_data,
mlkem_info->pubkey_size,
ciphertext_data,
mlkem_info->ciphertext_size,
shared_secret,
MLKEM_SHARED_SECRET_SIZE,
NULL,
0);
if (err) {
SSH_LOG(SSH_LOG_TRACE,
"Failed to encapsulate ML-KEM shared secret: %s",
gpg_strerror(err));
goto cleanup;
}
ssh_string_free(crypto->mlkem_ciphertext);
crypto->mlkem_ciphertext = ciphertext;
ciphertext = NULL;
ret = SSH_OK;
cleanup:
ssh_string_free(ciphertext);
return ret;
}
int ssh_mlkem_decapsulate(const ssh_session session,
ssh_mlkem_shared_secret shared_secret)
{
const struct mlkem_type_info *mlkem_info = NULL;
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_string ciphertext = NULL;
unsigned char *ciphertext_data = NULL;
gcry_error_t err;
ciphertext = crypto->mlkem_ciphertext;
if (ciphertext == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Missing ciphertext in session");
return SSH_ERROR;
}
if (crypto->mlkem_privkey == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Missing ML-KEM private key in session");
return SSH_ERROR;
}
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
return SSH_ERROR;
}
ciphertext_data = ssh_string_data(ciphertext);
err = gcry_kem_decap(mlkem_info->alg,
crypto->mlkem_privkey,
mlkem_info->privkey_size,
ciphertext_data,
mlkem_info->ciphertext_size,
shared_secret,
MLKEM_SHARED_SECRET_SIZE,
NULL,
0);
if (err) {
SSH_LOG(SSH_LOG_TRACE,
"Failed to decapsulate ML-KEM shared secret: %s",
gpg_strerror(err));
return SSH_ERROR;
}
return SSH_OK;
}

202
src/mlkem_native.c Normal file
View File

@@ -0,0 +1,202 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 by Red Hat, Inc.
*
* Author: Jakub Jelen <jjelen@redhat.com>
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1 of the License.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include "libssh/crypto.h"
#include "libssh/mlkem.h"
#include "libssh/mlkem_native.h"
#include "libssh/session.h"
#define crypto_kem_mlkem768_PUBLICKEYBYTES 1184
#define crypto_kem_mlkem768_SECRETKEYBYTES 2400
#define crypto_kem_mlkem768_CIPHERTEXTBYTES 1088
const struct mlkem_type_info MLKEM768_INFO = {
.pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES,
.privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES,
.ciphertext_size = crypto_kem_mlkem768_CIPHERTEXTBYTES,
};
int ssh_mlkem_init(ssh_session session)
{
int ret = SSH_ERROR;
struct ssh_crypto_struct *crypto = session->next_crypto;
const struct mlkem_type_info *mlkem_info = NULL;
unsigned char rnd[LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN];
struct libcrux_mlkem768_keypair keypair;
int err;
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
goto cleanup;
}
err = ssh_get_random(rnd, sizeof(rnd), 0);
if (err != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to generate random data for ML-KEM keygen");
goto cleanup;
}
keypair = libcrux_ml_kem_mlkem768_portable_generate_key_pair(rnd);
if (ssh_string_len(crypto->mlkem_client_pubkey) < mlkem_info->pubkey_size) {
SSH_STRING_FREE(crypto->mlkem_client_pubkey);
}
if (crypto->mlkem_client_pubkey == NULL) {
crypto->mlkem_client_pubkey = ssh_string_new(mlkem_info->pubkey_size);
if (crypto->mlkem_client_pubkey == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
}
err = ssh_string_fill(crypto->mlkem_client_pubkey,
keypair.pk.value,
mlkem_info->pubkey_size);
if (err) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to fill the string with client pubkey");
goto cleanup;
}
if (crypto->mlkem_privkey == NULL) {
crypto->mlkem_privkey = malloc(mlkem_info->privkey_size);
if (crypto->mlkem_privkey == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
}
memcpy(crypto->mlkem_privkey, keypair.sk.value, mlkem_info->privkey_size);
crypto->mlkem_privkey_len = mlkem_info->privkey_size;
ret = SSH_OK;
cleanup:
ssh_burn(&keypair, sizeof(keypair));
ssh_burn(rnd, sizeof(rnd));
return ret;
}
int ssh_mlkem_encapsulate(ssh_session session,
ssh_mlkem_shared_secret shared_secret)
{
int ret = SSH_ERROR;
const struct mlkem_type_info *mlkem_info = NULL;
struct ssh_crypto_struct *crypto = session->next_crypto;
const unsigned char *pubkey_data = NULL;
ssh_string pubkey = crypto->mlkem_client_pubkey;
struct libcrux_mlkem768_enc_result enc;
struct libcrux_mlkem768_pk mlkem_pub = {0};
unsigned char rnd[LIBCRUX_ML_KEM_ENC_PRNG_LEN];
int err;
if (pubkey == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Missing pubkey in session");
return SSH_ERROR;
}
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
return SSH_ERROR;
}
pubkey_data = ssh_string_data(pubkey);
memcpy(mlkem_pub.value, pubkey_data, mlkem_info->pubkey_size);
err = libcrux_ml_kem_mlkem768_portable_validate_public_key(&mlkem_pub);
if (err == 0) {
SSH_LOG(SSH_LOG_WARNING, "Invalid public key");
return SSH_ERROR;
}
err = ssh_get_random(rnd, sizeof(rnd), 0);
if (err != 1) {
SSH_LOG(SSH_LOG_WARNING,
"Failed to generate random data for ML-KEM keygen");
goto cleanup;
}
enc = libcrux_ml_kem_mlkem768_portable_encapsulate(&mlkem_pub, rnd);
if (ssh_string_len(crypto->mlkem_ciphertext) < mlkem_info->ciphertext_size) {
SSH_STRING_FREE(crypto->mlkem_ciphertext);
}
if (crypto->mlkem_ciphertext == NULL) {
crypto->mlkem_ciphertext = ssh_string_new(mlkem_info->ciphertext_size);
if (crypto->mlkem_ciphertext == NULL) {
ssh_set_error_oom(session);
goto cleanup;
}
}
err = ssh_string_fill(crypto->mlkem_ciphertext,
enc.fst.value,
sizeof(enc.fst.value));
if (err != SSH_OK) {
SSH_LOG(SSH_LOG_WARNING, "Failed to fill the string with ciphertext");
goto cleanup;
}
memcpy(shared_secret, enc.snd, sizeof(enc.snd));
ret = SSH_OK;
cleanup:
ssh_burn(rnd, sizeof(rnd));
ssh_burn(&enc, sizeof(enc));
return ret;
}
int ssh_mlkem_decapsulate(const ssh_session session,
ssh_mlkem_shared_secret shared_secret)
{
const struct mlkem_type_info *mlkem_info = NULL;
struct ssh_crypto_struct *crypto = session->next_crypto;
ssh_string ciphertext = NULL;
unsigned char *ciphertext_data = NULL;
struct libcrux_mlkem768_sk mlkem_priv = {0};
struct libcrux_mlkem768_ciphertext mlkem_ciphertext = {0};
mlkem_info = kex_type_to_mlkem_info(crypto->kex_type);
if (mlkem_info == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type");
return SSH_ERROR;
}
ciphertext = crypto->mlkem_ciphertext;
if (ciphertext == NULL) {
SSH_LOG(SSH_LOG_WARNING, "Missing ciphertext in session");
return SSH_ERROR;
}
ciphertext_data = ssh_string_data(ciphertext);
memcpy(mlkem_ciphertext.value,
ciphertext_data,
sizeof(mlkem_ciphertext.value));
memcpy(mlkem_priv.value, crypto->mlkem_privkey, crypto->mlkem_privkey_len);
libcrux_ml_kem_mlkem768_portable_decapsulate(&mlkem_priv,
&mlkem_ciphertext,
shared_secret);
return SSH_OK;
}

View File

@@ -31,6 +31,7 @@
#else
#include <winsock2.h>
#endif
#include "libssh/config.h"
#include "libssh/config_parser.h"
#include "libssh/misc.h"
#include "libssh/options.h"
@@ -40,6 +41,11 @@
#include "libssh/priv.h"
#include "libssh/session.h"
#include <sys/types.h>
#include "libssh/misc.h"
#include "libssh/options.h"
#include "libssh/config_parser.h"
#include "libssh/gssapi.h"
#include "libssh/token.h"
#ifdef WITH_SERVER
#include "libssh/server.h"
#include "libssh/bind.h"
@@ -255,6 +261,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest)
new->opts.nodelay = src->opts.nodelay;
new->opts.config_processed = src->opts.config_processed;
new->opts.control_master = src->opts.control_master;
new->opts.address_family = src->opts.address_family;
new->common.log_verbosity = src->common.log_verbosity;
new->common.callbacks = src->common.callbacks;
@@ -558,6 +565,16 @@ int ssh_options_set_algo(ssh_session session,
* Set it to specify that GSSAPI should delegate credentials
* to the server (int, 0 = false).
*
* - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE
* Set to true to allow GSSAPI key exchange (bool).
*
* - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS
* Set the GSSAPI key exchange method to be used (const char *,
* comma-separated list). ex:
* "gss-curve25519-sha256-,gss-nistp256-sha256-"
* These will prefix the default algorithms if
* SSH_OPTIONS_GSSAPI_KEY_EXCHANGE is true.
*
* - SSH_OPTIONS_PASSWORD_AUTH
* Set it if password authentication should be used
* in ssh_userauth_auto_pubkey(). (int, 0=false).
@@ -610,12 +627,12 @@ int ssh_options_set_algo(ssh_session session,
* Setting 0 will revert the value to defaults.
* Default is 3072 bits or 2048 bits in FIPS mode.
* (int)
*
* - SSH_OPTIONS_IDENTITY_AGENT
* Set the path to the SSH agent socket. If unset, the
* SSH_AUTH_SOCK environment is consulted.
* (const char *)
*
* - SSH_OPTIONS_IDENTITIES_ONLY
* Use only keys specified in the SSH config, even if agent
* offers more.
@@ -649,6 +666,13 @@ int ssh_options_set_algo(ssh_session session,
* context and can free it after this call.
* (ssh_pki_ctx)
*
* - SSH_OPTIONS_ADDRESS_FAMILY
* Specify which address family to use when connecting.
*
* Possible options:
* - SSH_ADDRESS_FAMILY_ANY: use any address family
* - SSH_ADDRESS_FAMILY_INET: IPv4 only
* - SSH_ADDRESS_FAMILY_INET6: IPv6 only
*
* @param value The value to set. This is a generic pointer and the
* datatype which is used should be set according to the
@@ -1231,6 +1255,37 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
session->opts.gss_delegate_creds = (x & 0xff);
}
break;
#ifdef WITH_GSSAPI
case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
bool *x = (bool *)value;
session->opts.gssapi_key_exchange = *x;
}
break;
case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS:
v = value;
if (v == NULL || v[0] == '\0') {
ssh_set_error_invalid(session);
return -1;
} else {
/* Check if algorithms are supported */
char *ret =
ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, v);
if (ret == NULL) {
ssh_set_error(session,
SSH_FATAL,
"GSSAPI key exchange algorithms not "
"supported or invalid");
return -1;
}
SAFE_FREE(session->opts.gssapi_key_exchange_algs);
session->opts.gssapi_key_exchange_algs = ret;
}
break;
#endif
case SSH_OPTIONS_PASSWORD_AUTH:
case SSH_OPTIONS_PUBKEY_AUTH:
case SSH_OPTIONS_KBDINT_AUTH:
@@ -1392,6 +1447,20 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
return -1;
}
break;
case SSH_OPTIONS_ADDRESS_FAMILY:
if (value == NULL) {
ssh_set_error_invalid(session);
return -1;
} else {
int *x = (int *)value;
if (*x < SSH_ADDRESS_FAMILY_ANY ||
*x > SSH_ADDRESS_FAMILY_INET6) {
ssh_set_error_invalid(session);
return -1;
}
session->opts.address_family = *x;
}
break;
default:
ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type);
return -1;
@@ -1698,6 +1767,7 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
int compress = 0;
int cont = 1;
size_t current = 0;
int opt_rc = 0;
int saveoptind = optind; /* need to save 'em */
int saveopterr = opterr;
int opt;
@@ -1708,7 +1778,7 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
}
opterr = 0; /* shut up getopt */
while((opt = getopt(argc, argv, "c:i:Cl:p:vb:r12")) != -1) {
while ((opt = getopt(argc, argv, "c:i:o:Cl:p:vb:r12")) != -1) {
switch(opt) {
case 'l':
user = optarg;
@@ -1718,6 +1788,7 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
break;
case 'v':
debuglevel++;
ssh_set_log_level(debuglevel);
break;
case 'r':
break;
@@ -1730,6 +1801,9 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
case 'C':
compress++;
break;
case 'o':
opt_rc = ssh_config_parse_line_cli(session, optarg);
break;
case '2':
break;
case '1':
@@ -1761,6 +1835,9 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
}
}
} /* switch */
if (opt_rc == SSH_ERROR) {
break;
}
} /* while */
opterr = saveopterr;
tmp = realloc(save, (current + (argc - optind)) * sizeof(char*));
@@ -1783,10 +1860,13 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv)
optind++;
}
ssh_set_log_level(debuglevel);
optind = saveoptind;
if (opt_rc == SSH_ERROR) {
SAFE_FREE(save);
return SSH_ERROR;
}
if(!cont) {
SAFE_FREE(save);
return -1;
@@ -2061,6 +2141,16 @@ int ssh_options_apply(ssh_session session)
}
}
#ifdef WITH_GSSAPI
if (session->opts.gssapi_key_exchange) {
rc = ssh_gssapi_check_client_config(session);
if (rc != SSH_OK) {
SSH_LOG(SSH_LOG_WARN, "Disabled GSSAPI key exchange");
session->opts.gssapi_key_exchange = false;
}
}
#endif
return 0;
}
@@ -2243,6 +2333,14 @@ static int ssh_bind_set_algo(ssh_bind sshbind,
* Default is 3072 bits or 2048 bits in FIPS mode.
* (int)
*
* - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE
* Set true to enable GSSAPI key exchange,
* false to disable GSSAPI key exchange. (bool)
*
* - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS
* Set the GSSAPI key exchange method to be used
* (const char *, comma-separated list).
* ex: "gss-group14-sha256-,gss-group16-sha512-"
*
* @param value The value to set. This is a generic pointer and the
* datatype which should be used is described at the
@@ -2640,6 +2738,35 @@ ssh_bind_options_set(ssh_bind sshbind,
sshbind->rsa_min_size = *x;
}
break;
#ifdef WITH_GSSAPI
case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
bool *x = (bool *)value;
sshbind->gssapi_key_exchange = *x;
}
break;
case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS:
if (value == NULL) {
ssh_set_error_invalid(sshbind);
return -1;
} else {
char *ret = NULL;
SAFE_FREE(sshbind->gssapi_key_exchange_algs);
ret = ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, value);
if (ret == NULL) {
ssh_set_error(
sshbind,
SSH_REQUEST_DENIED,
"GSSAPI key exchange algorithms not supported or invalid");
return -1;
}
sshbind->gssapi_key_exchange_algs = ret;
}
break;
#endif /* WITH_GSSAPI */
default:
ssh_set_error(sshbind,
SSH_REQUEST_DENIED,

View File

@@ -422,35 +422,67 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
rc = SSH_PACKET_ALLOWED;
break;
case SSH2_MSG_KEX_DH_GEX_INIT: // 32
/* Server only */
// SSH2_MSG_KEXGSS_COMPLETE: // 32
if (ssh_kex_is_gss(session->next_crypto)) {
/* SSH2_MSG_KEXGSS_COMPLETE */
/* Client only */
/*
* States required:
* - session_state == SSH_SESSION_STATE_DH
* - dh_handshake_state == DH_STATE_GROUP_SENT
*
* Transitions:
* - session->dh_handshake_state = DH_STATE_GROUP_SENT
* then calls ssh_packet_server_dhgex_init which triggers:
* - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT
* */
/*
* States required:
* - session_state == SSH_SESSION_STATE_DH
* - dh_handshake_state == DH_STATE_INIT_SENT
*
* Transitions:
* - session->dh_handshake_state = DH_STATE_INIT_SENT
* then calls ssh_packet_client_gss_kex_reply which triggers:
* - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT
* */
if (session->client) {
rc = SSH_PACKET_DENIED;
break;
if (!session->client) {
rc = SSH_PACKET_DENIED;
break;
}
if (session->session_state != SSH_SESSION_STATE_DH) {
rc = SSH_PACKET_DENIED;
break;
}
if (session->dh_handshake_state != DH_STATE_INIT_SENT) {
rc = SSH_PACKET_DENIED;
break;
}
} else {
/* SSH2_MSG_KEX_DH_GEX_INIT */
/* Server only */
/*
* States required:
* - session_state == SSH_SESSION_STATE_DH
* - dh_handshake_state == DH_STATE_GROUP_SENT
*
* Transitions:
* - session->dh_handshake_state = DH_STATE_GROUP_SENT
* then calls ssh_packet_server_dhgex_init which triggers:
* - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT
* */
if (session->client) {
rc = SSH_PACKET_DENIED;
break;
}
if (session->session_state != SSH_SESSION_STATE_DH) {
rc = SSH_PACKET_DENIED;
break;
}
/* Only allowed if dh_handshake_state is in initial state */
if (session->dh_handshake_state != DH_STATE_GROUP_SENT) {
rc = SSH_PACKET_DENIED;
break;
}
}
if (session->session_state != SSH_SESSION_STATE_DH) {
rc = SSH_PACKET_DENIED;
break;
}
/* Only allowed if dh_handshake_state is in initial state */
if (session->dh_handshake_state != DH_STATE_GROUP_SENT) {
rc = SSH_PACKET_DENIED;
break;
}
rc = SSH_PACKET_ALLOWED;
break;
case SSH2_MSG_KEX_DH_GEX_REPLY: // 33
@@ -594,6 +626,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
* or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT
* or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT
* or session->auth.state == SSH_AUTH_STATE_GSSAPI_MIC_SENT
* or session->auth.state == SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT
* or session->auth.state == SSH_AUTH_STATE_AUTH_NONE_SENT
*
* Transitions:
@@ -623,8 +656,8 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
(session->auth.state != SSH_AUTH_STATE_PUBKEY_AUTH_SENT) &&
(session->auth.state != SSH_AUTH_STATE_PASSWORD_AUTH_SENT) &&
(session->auth.state != SSH_AUTH_STATE_GSSAPI_MIC_SENT) &&
(session->auth.state != SSH_AUTH_STATE_AUTH_NONE_SENT))
{
(session->auth.state != SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT) &&
(session->auth.state != SSH_AUTH_STATE_AUTH_NONE_SENT)) {
rc = SSH_PACKET_DENIED;
break;
}
@@ -716,17 +749,83 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
rc = SSH_PACKET_ALLOWED;
break;
case SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE: // 63
/* TODO Not filtered */
/* Server only */
/*
* States required:
* - session_state == SSH_SESSION_STATE_AUTHENTICATING
* - session->gssapi->state == SSH_GSSAPI_STATE_RCV_MIC
*
* Transitions:
* - None
*/
#ifdef WITH_GSSAPI
if (session->client) {
rc = SSH_PACKET_DENIED;
break;
}
if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) {
rc = SSH_PACKET_DENIED;
break;
}
if (session->gssapi == NULL) {
rc = SSH_PACKET_DENIED;
break;
}
if (session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) {
rc = SSH_PACKET_DENIED;
break;
}
rc = SSH_PACKET_ALLOWED;
break;
#else
rc = SSH_PACKET_DENIED;
break;
#endif /* WITH_GSSAPI */
case SSH2_MSG_USERAUTH_GSSAPI_ERROR: // 64
/* TODO Not filtered */
/* Client only */
/*
* States required:
* - session_state == SSH_SESSION_STATE_AUTHENTICATING
*
* Transitions:
* - None
*/
#ifdef WITH_GSSAPI
if (session->server) {
rc = SSH_PACKET_DENIED;
break;
}
if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) {
rc = SSH_PACKET_DENIED;
break;
}
rc = SSH_PACKET_ALLOWED;
break;
#else
rc = SSH_PACKET_DENIED;
break;
#endif /* WITH_GSSAPI */
case SSH2_MSG_USERAUTH_GSSAPI_ERRTOK: // 65
/* TODO Not filtered */
/*
* States required:
* - session_state == SSH_SESSION_STATE_AUTHENTICATING
*
* Transitions:
* - None
*/
#ifdef WITH_GSSAPI
if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) {
rc = SSH_PACKET_DENIED;
break;
}
rc = SSH_PACKET_ALLOWED;
break;
#else
rc = SSH_PACKET_DENIED;
break;
#endif /* WITH_GSSAPI */
case SSH2_MSG_USERAUTH_GSSAPI_MIC: // 66
/* Server only */
@@ -746,7 +845,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
* - any other case:
* - None
* */
#ifdef WITH_GSSAPI
/* If this is a client, reject the message */
if (session->client) {
rc = SSH_PACKET_DENIED;
@@ -765,6 +864,10 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
rc = SSH_PACKET_ALLOWED;
break;
#else
rc = SSH_PACKET_DENIED;
break;
#endif /* WITH_GSSAPI */
case SSH2_MSG_GLOBAL_REQUEST: // 80
/*
* States required:

View File

@@ -27,6 +27,10 @@
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef WITH_GSSAPI
#include "libssh/gssapi.h"
#include <gssapi/gssapi.h>
#endif
#include "libssh/priv.h"
#include "libssh/buffer.h"
@@ -173,53 +177,93 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys)
/* server things are done in server.c */
session->dh_handshake_state=DH_STATE_FINISHED;
} else {
ssh_key server_key = NULL;
#ifdef WITH_GSSAPI
if (ssh_kex_is_gss(session->next_crypto)) {
OM_uint32 maj_stat, min_stat;
gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER;
/* client */
/* Verify the host's signature. FIXME do it sooner */
sig_blob = session->next_crypto->dh_server_signature;
session->next_crypto->dh_server_signature = NULL;
/* get the server public key */
server_key = ssh_dh_get_next_server_publickey(session);
if (server_key == NULL) {
goto error;
}
rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig);
ssh_string_burn(sig_blob);
SSH_STRING_FREE(sig_blob);
if (rc != SSH_OK) {
goto error;
}
/* Check if signature from server matches user preferences */
if (session->opts.wanted_methods[SSH_HOSTKEYS]) {
rc = match_group(session->opts.wanted_methods[SSH_HOSTKEYS],
sig->type_c);
if (rc == 0) {
ssh_set_error(session,
SSH_FATAL,
"Public key from server (%s) doesn't match user "
"preference (%s)",
sig->type_c,
session->opts.wanted_methods[SSH_HOSTKEYS]);
if (session->gssapi == NULL || session->gssapi->ctx == NULL) {
ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized");
goto error;
}
}
rc = ssh_pki_signature_verify(session,
sig,
server_key,
session->next_crypto->secret_hash,
session->next_crypto->digest_len);
SSH_SIGNATURE_FREE(sig);
if (rc == SSH_ERROR) {
ssh_set_error(session,
SSH_FATAL,
"Failed to verify server hostkey signature");
goto error;
if (session->gssapi_key_exchange_mic == NULL) {
ssh_set_error(session,
SSH_FATAL,
"GSSAPI mic not set");
goto error;
}
mic.length = ssh_string_len(session->gssapi_key_exchange_mic);
mic.value = ssh_string_data(session->gssapi_key_exchange_mic);
msg.length = session->next_crypto->digest_len;
msg.value = session->next_crypto->secret_hash;
maj_stat = gss_verify_mic(&min_stat,
session->gssapi->ctx,
&msg,
&mic,
NULL);
if (maj_stat != GSS_S_COMPLETE) {
ssh_set_error(session,
SSH_FATAL,
"Failed to verify mic after GSSAPI Key Exchange");
goto error;
}
SSH_STRING_FREE(session->gssapi_key_exchange_mic);
} else
#endif
{
ssh_key server_key = NULL;
/* client */
/* Verify the host's signature. FIXME do it sooner */
sig_blob = session->next_crypto->dh_server_signature;
session->next_crypto->dh_server_signature = NULL;
/* get the server public key */
server_key = ssh_dh_get_next_server_publickey(session);
if (server_key == NULL) {
goto error;
}
rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig);
ssh_string_burn(sig_blob);
SSH_STRING_FREE(sig_blob);
if (rc != SSH_OK) {
goto error;
}
/* Check if signature from server matches user preferences */
if (session->opts.wanted_methods[SSH_HOSTKEYS]) {
rc = match_group(session->opts.wanted_methods[SSH_HOSTKEYS],
sig->type_c);
if (rc == 0) {
ssh_set_error(
session,
SSH_FATAL,
"Public key from server (%s) doesn't match user "
"preference (%s)",
sig->type_c,
session->opts.wanted_methods[SSH_HOSTKEYS]);
goto error;
}
}
rc = ssh_pki_signature_verify(session,
sig,
server_key,
session->next_crypto->secret_hash,
session->next_crypto->digest_len);
SSH_SIGNATURE_FREE(sig);
if (rc == SSH_ERROR) {
ssh_set_error(session,
SSH_FATAL,
"Failed to verify server hostkey signature");
goto error;
}
}
SSH_LOG(SSH_LOG_DEBUG, "Signature verified and valid");
@@ -234,6 +278,9 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys)
return SSH_PACKET_USED;
error:
#ifdef WITH_GSSAPI
SSH_STRING_FREE(session->gssapi_key_exchange_mic);
#endif
SSH_SIGNATURE_FREE(sig);
ssh_string_burn(sig_blob);
SSH_STRING_FREE(sig_blob);

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