Compare commits

..

183 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
Jakub Jelen
f333d95013 ci: Avoid repetitive definitions
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-19 17:31:53 +01:00
Jakub Jelen
92d0f8aba6 ci: Remove GSSAPI from minimal build
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-19 17:31:37 +01:00
Jakub Jelen
66460578b1 ci: Remove marco from the whitelist
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-19 17:31:37 +01:00
Jakub Jelen
b93db6c3d1 ci: Replace ad-hoc exports with variables
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-19 17:31:37 +01:00
Jakub Jelen
1c3143ff00 ci: Add cmocka.cfg to avoid false positives reports from csbuild
Based on cmocka changes:

https://gitlab.com/cmocka/cmocka/-/blob/master/cppcheck/cmocka.cfg

https://gitlab.com/cmocka/cmocka/-/blob/master/.gitlab-ci.yml#L148

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-19 17:31:31 +01:00
Praneeth Sarode
47305a2f72 docs(fido2): add FIDO2/U2F security key support chapter to documentation
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:28:23 +05:30
Praneeth Sarode
5bbaecfaa7 feat(pki): extend the sshsig API to support security keys along with tests
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:28:14 +05:30
Praneeth Sarode
6e5d0a935f tests(fido2): add tests for SK ECDSA and SK Ed25519 public key authentication
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:27:48 +05:30
Praneeth Sarode
5d4d9f8208 tests(rsa): add test for RSA key generation using the newer ssh_pki_generate_key API
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:27:48 +05:30
Praneeth Sarode
c128cf8807 tests(pki): add torture tests for pki_sk functions
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:27:48 +05:30
Praneeth Sarode
5937b5ba4e feat(torture_sk): add functions to validate security key signatures and to create PKI context
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:27:48 +05:30
Praneeth Sarode
1241a3a8c9 tests(fido2): add sk-dummy support to the testing infrastructure
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:27:48 +05:30
Praneeth Sarode
21d338737a tests(fido2): add sk key files to the testing infrastructure
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:27:48 +05:30
Praneeth Sarode
d91630308d pki: add security key identities to session options
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:27:48 +05:30
Praneeth Sarode
37f0e91814 feat(pki): add security key support with enrollment, signing, and resident key loading functions
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:27:36 +05:30
Praneeth Sarode
32a256e157 feat(pki): add ssh_key getters to retrieve security key flags, application, and user ID
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:11:53 +05:30
Praneeth Sarode
14bd26e71c feat(pki): add support for user ID in ssh_key structure
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:11:53 +05:30
Praneeth Sarode
97e71606e0 feat(pki): add ssh_pki_ctx to ssh_session
The session struct now contains an ssh_pki_ctx struct as its member to allow for passing user configured pki options across many functions.
The ssh_options_set API has been extended to allow users to set this member.

Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:11:53 +05:30
Praneeth Sarode
d4b0de702b feat(pki): implement PKI context API
A new generic struct is introduced which contains the various configuration options that can be used by pki operations.
API functions have been provided to configure all the options.

Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:11:53 +05:30
Praneeth Sarode
acc080ac03 tests(fido2): add tests for the usb-hid security key callbacks
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-13 15:11:46 +05:30
Praneeth Sarode
e56af9fa79 feat(torture_sk): add validation functions for security key callback responses and resident keys
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:52:48 +05:30
Praneeth Sarode
c4b2bd34a8 feat(torture): add torture_get_sk_pin function to retrieve PIN from environment
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:52:48 +05:30
Praneeth Sarode
50ee6411f2 fido2: implement the default sk_callbacks for FIDO2/U2F keys using the usb-hid protocol
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:52:45 +05:30
Praneeth Sarode
c1dd30b47b fido2: add helper functions for writing FIDO2/U2F callbacks
Add some common helper functions that can be used by any developer
writing callbacks for interacting with FIDO2/U2F devices.

Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:04:38 +05:30
Praneeth Sarode
8ba9e931e8 fido2: declare callbacks for sk operations
Declare ssh_sk_callbacks_struct so that the users can define custom functions as callbacks for interacting with FIDO2/U2F devices.

Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:04:38 +05:30
Praneeth Sarode
eda5c6576b tests(torture_sk): validate sk_flags against allowed security key flags
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:04:38 +05:30
Praneeth Sarode
302d868875 fido2: add sk_api.h
The sk_api.h file added is a copy of the sk-api.h file in openSSH, including only the struct and constant definitions.
This has been done to ensure compatibility with any security key middleware developed for openSSH.

Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:04:38 +05:30
Praneeth Sarode
7db75e8fd0 ci: enable FIDO2/U2F support in some images
Build with WITH_FIDO2=ON in the default fedora, tumbleweed, centos, ubuntu, and visualstudio images.

Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:04:38 +05:30
Praneeth Sarode
ebe632cf8f cmake: add build option to enable FIDO2/U2F support
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:04:38 +05:30
Praneeth Sarode
150d606db7 cmake: add cmake module to find libfido2
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Eshan Kelkar <eshankelkar@galorithm.com>
2025-11-09 05:04:37 +05:30
Jakub Jelen
63fbf00efe pki: Use constant for minimal RSA key size in FIPS
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-06 16:25:25 +01:00
Jakub Jelen
ae33ced0dc coverage: Ignore parse errors again
Without this, the gcov is crashing with some suspicious coverage reports on
functions like `uint32_divmod_uint14()` from internal sntrup implementation.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-06 16:25:25 +01:00
Jakub Jelen
ee6e2c69e1 Bump minimal RSA key size to 1024
Fixes: #326

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-06 16:25:25 +01:00
Jakub Jelen
cefc4f8c97 pkd: Run tests with ecdsa and ed25519 keys with dropbear
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-06 16:25:25 +01:00
Jakub Jelen
b64e7f67d3 pkd: Run ed25519 tests with dropbear
Resolves: #336

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-06 16:25:25 +01:00
Jakub Jelen
491cd81a32 kex: Place PQC KEX methods first
The ML-KEMx25519 is now preferred algorithm in OpenSSH so follow the suit

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-06 16:24:47 +01:00
Jakub Jelen
3444f4c449 Remove references to (unused) pre-release ssh messages SSH2_MSG_ECMQV_*
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-06 16:24:47 +01:00
Pavol Žáčik
80541ab828 mlkem768: Fix missing jumps in error handling
Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-06 12:10:03 +01:00
Jakub Jelen
b042477f83 Suppress remaining OpenSSL 3.5 memory leaks
Reported as

https://github.com/openssl/openssl/issues/29077

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-05 17:52:24 +01:00
Jakub Jelen
950abbbd81 tests: Remove the -E which is overridden by followed -E on ctest CLI
The threads_pki_rsa was running and working under valgrind for some
time already without anyone noticing this syntax does not work.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-05 17:52:24 +01:00
Jakub Jelen
b9c6701c68 tests: Avoid needless pthread_exit()
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-05 17:52:24 +01:00
Jakub Jelen
a94df4bb8f tests: Adjust valgrind supressions for Fedora 43
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-05 17:52:24 +01:00
Pavol Žáčik
41b8b3326c client: Reset session packet state on disconnect
When reusing session structures for multiple
connections, the packet state could be SIZE_READ
before disconnect, causing initial packets of the
next connection to be misinterpreted.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-05 17:44:33 +01:00
Sahana Prasad
a9c8f942a5 kex: Implement mlkem768x25519-sha256
The implementation largely follows that of sntrup761x25519-sha512.

Most of the work was done by Sahana with the help of Claude,
Pavol provided fixes to match specs and did a final clean up.

Co-Authored-By: Sahana Prasad <sahana@redhat.com>
Co-Authored-By: Pavol Žáčik <pzacik@redhat.com>
Co-Authored-By: Claude <noreply@anthropic.com>

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-05 17:44:33 +01:00
Jakub Jelen
d307bfa239 pki_crypto: Avoid potential memory leak if malloc fails
Thanks oss-fuzz and nalloc.

https://issues.oss-fuzz.com/issues/449101878

Thanks Andreas for review and nugging into rewriting it to something readable.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-11-04 16:08:58 +01:00
Mike Frysinger
66e8491f73 ttyopts: make non-POSIX defines optional
This file uses a bunch of defines that, while common, are not in POSIX.
https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/termios.h.html

Add more ifdef checks around them to fix building on platforms that omit
them.

Signed-off-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-04 14:46:58 +01:00
Mike Frysinger
e93c1f6a61 libcrypto: update EVP API usage
The EVP_CIPHER_CTX_init API is deprecated and doesn't exist in some
OpenSSL versions.  Switch to EVP_CIPHER_CTX_reset which works with
1.1.x which is the min version libssh requires.

Signed-off-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-11-04 14:45:39 +01:00
Jakub Jelen
358553e976 scp: Workaround for Cisco devices not handling single quotes
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-10 16:20:54 +02:00
Jakub Jelen
07d099f652 examples: Support passing port to libssh_scp to simplify testing
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-10 16:20:53 +02:00
Praneeth Sarode
f3d70e54e9 tests(string): add tests for ssh_string_from_data function
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-10-10 14:00:22 +02:00
Praneeth Sarode
74d1bf51b5 feat(string): add ssh_string_from_data function to create ssh_string from data buffer
Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-10-10 14:00:22 +02:00
Jakub Jelen
00f1d6fac2 Add RequiredRsaSize configuration option
to both client and server configuration file

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:06:43 +02:00
Jakub Jelen
029754efb3 examples: Reformat
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:04:59 +02:00
Jakub Jelen
a49e0c2a84 examples: Replace magic numbers with FD constants
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:04:58 +02:00
Jakub Jelen
8966e577ab connector: Improve logging
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:04:57 +02:00
Jakub Jelen
dc45b8f3f1 channels: Improve logging information about channels
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:04:56 +02:00
Jakub Jelen
c932790b82 connector: Fix default connector flags
Originally reported by Jeremy Cross <jcross@beyondtrust.com> in #461

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:04:55 +02:00
Jakub Jelen
8a0aa17bca connector: Reformat
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:04:55 +02:00
Jakub Jelen
ecb11f1a18 tests: Fix wording in comment to make sense
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:04:54 +02:00
Jakub Jelen
6aea779918 sftpserver: Fix loop termination
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:03:46 +02:00
Jakub Jelen
a51384fe4e sftpserver: Remove some needless parts
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:03:45 +02:00
Jakub Jelen
c55140272f examples: Add more flexibility to set verbosity
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-10-01 16:03:44 +02:00
Jakub Jelen
607dad040b mbedtls: Warn about missing featues of mbedTLS build
The libssh requires mbedTLS to have support for threading. Given the
way how the mbedTLS builds are configured (at least to my limited
understanding), by modifying mbedtls_config.h header file, this
changes the silent failure to a message on stderr pointing the
user in the right direction.

Fixes: #304

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-09-30 13:16:34 +02:00
Praneeth Sarode
55bb909252 refactor(pki): separate the sk signature buffer packing to a separate function
The logic for creating the buffer to be verified from an sk signature from ssh_pki_signature_verify has been separated into a new function named pki_prepare_sk_signature_buffer to allow for convenient reuse of this logic.

Signed-off-by: Praneeth Sarode <praneethsarode@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2025-09-30 12:42:56 +02:00
215 changed files with 29387 additions and 4501 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

@@ -17,7 +17,7 @@ stages:
- analysis
# This is some black magic to select between branch pipelines and
# merge request pipelines to avoid running same pipelines in twice
# merge request pipelines to avoid running same pipelines twice
workflow:
rules:
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"'
@@ -31,7 +31,7 @@ workflow:
variables:
CMAKE_DEFAULT_OPTIONS: "-DCMAKE_BUILD_TYPE=RelWithDebInfo -DPICKY_DEVELOPER=ON"
CMAKE_DEFAULT_DEBUG_OPTIONS: "-DCMAKE_C_FLAGS='-O0 -g -ggdb' -DPICKY_DEVELOPER=ON"
CMAKE_BUILD_OPTIONS: "-DWITH_BLOWFISH_CIPHER=ON -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_DEBUG_PACKET=ON -DWITH_DEBUG_CALLTRACE=ON"
CMAKE_BUILD_OPTIONS: "-DWITH_BLOWFISH_CIPHER=ON -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DWITH_FIDO2=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_DEBUG_PACKET=ON -DWITH_DEBUG_CALLTRACE=ON"
CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DGSSAPI_TESTING=ON -DWITH_BENCHMARKS=ON -DFUZZ_TESTING=ON"
CMAKE_OPTIONS: $CMAKE_DEFAULT_OPTIONS $CMAKE_BUILD_OPTIONS $CMAKE_TEST_OPTIONS
@@ -83,6 +83,42 @@ 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
variables:
OPENSSL_ENABLE_SHA1_SIGNATURES: 1
.centos10:
extends: .centos
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS10_BUILD
.centos9:
extends: .centos
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD
# Unit testing only, no client and pkd testing, because cwrap is not available
# for MinGW
.mingw:
extends: .tests
variables:
WINEDEBUG: -all
script:
- $WINEBIN $CMAKE_DEFAULT_OPTIONS
-DWITH_SFTP=ON
-DWITH_SERVER=ON
-DWITH_ZLIB=ON
-DWITH_PCAP=ON
-DWITH_FIDO2=ON
-DUNIT_TESTING=ON .. &&
make -j$(nproc) &&
ctest --output-on-failure -E torture_rand
.fips:
extends: .tests
@@ -126,12 +162,10 @@ review:
# CentOS builds #
###############################################################################
centos10s/openssl_3.5.x/x86_64:
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS10_BUILD
extends: .tests
extends: .centos10
variables:
CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON
script:
- export OPENSSL_ENABLE_SHA1_SIGNATURES=1
- cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. &&
make -j$(nproc) &&
ctest --output-on-failure
@@ -141,19 +175,17 @@ centos10s/openssl_3.5.x/x86_64/fips:
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS10_BUILD
variables:
CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON
OPENSSL_ENABLE_SHA1_SIGNATURES: 1
script:
- export OPENSSL_ENABLE_SHA1_SIGNATURES=1
- cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. &&
make -j$(nproc) &&
OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure
centos9s/openssl_3.5.x/x86_64:
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD
extends: .tests
extends: .centos9
variables:
CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON
CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON
script:
- export OPENSSL_ENABLE_SHA1_SIGNATURES=1
- cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. &&
make -j$(nproc) &&
ctest --output-on-failure
@@ -167,11 +199,14 @@ centos9s/mbedtls_2.x/x86_64:
centos9s/openssl_3.5.x/x86_64/fips:
extends: .fips
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD
variables:
OPENSSL_ENABLE_SHA1_SIGNATURES: 1
script:
- export OPENSSL_ENABLE_SHA1_SIGNATURES=1
# 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
@@ -187,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 #
@@ -202,14 +239,16 @@ 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:
extends: .fedora
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD
variables:
CTEST_OUTPUT_ON_FAILURE: 1
script:
- cmake -G Ninja $CMAKE_OPTIONS ../ && ninja && CTEST_OUTPUT_ON_FAILURE=1 ninja test
- cmake -G Ninja $CMAKE_OPTIONS ../ && ninja && ninja test
fedora/coverage:
extends: .fedora
@@ -247,9 +286,10 @@ fedora/openssl_3.x/x86_64/minimal:
-DWITH_SERVER=OFF
-DWITH_ZLIB=OFF
-DWITH_PCAP=OFF
-DWITH_GSSAPI=OFF
-DWITH_GEX=OFF
-DUNIT_TESTING=ON
-DCLIENT_TESTING=ON
-DWITH_GEX=OFF .. &&
-DCLIENT_TESTING=ON .. &&
make -j$(nproc)
.valgrind:
@@ -261,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:
@@ -331,47 +402,26 @@ fedora/undefined-sanitizer:
fedora/libgcrypt/x86_64:
extends: .fedora
variables:
CMAKE_ADDITIONAL_OPTIONS: "-DWITH_GCRYPT=ON -DWITH_DEBUG_CRYPTO=ON"
CMAKE_ADDITIONAL_OPTIONS: "-DWITH_GCRYPT=ON"
fedora/mbedtls_2.x/x86_64:
fedora/mbedtls_3.x/x86_64:
extends: .fedora
variables:
CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON "
CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON"
# Unit testing only, no client and pkd testing, because cwrap is not available
# for MinGW
fedora/mingw64:
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$MINGW_BUILD
extends: .tests
script:
- export WINEPATH=/usr/x86_64-w64-mingw32/sys-root/mingw/bin
- export WINEDEBUG=-all
- mingw64-cmake $CMAKE_DEFAULT_OPTIONS
-DWITH_SFTP=ON
-DWITH_SERVER=ON
-DWITH_ZLIB=ON
-DWITH_PCAP=ON
-DUNIT_TESTING=ON .. &&
make -j$(nproc) &&
ctest --output-on-failure -E torture_rand
extends: .mingw
variables:
WINEPATH: /usr/x86_64-w64-mingw32/sys-root/mingw/bin
WINEBIN: mingw64-cmake
# Unit testing only, no client and pkd testing, because cwrap is not available
# for MinGW
fedora/mingw32:
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$MINGW_BUILD
extends: .tests
script:
- export WINEPATH=/usr/i686-w64-mingw32/sys-root/mingw/bin
- export WINEDEBUG=-all
- mingw32-cmake $CMAKE_DEFAULT_OPTIONS
-DWITH_SFTP=ON
-DWITH_SERVER=ON
-DWITH_ZLIB=ON
-DWITH_PCAP=ON
-DUNIT_TESTING=ON .. &&
make -j$(nproc) &&
ctest --output-on-failure -E torture_rand
extends: .mingw
variables:
WINEPATH: /usr/i686-w64-mingw32/sys-root/mingw/bin
WINEBIN: mingw32-cmake
###############################################################################
# Fedora csbuild #
@@ -380,6 +430,8 @@ fedora/mingw32:
stage: analysis
variables:
GIT_DEPTH: "100"
CSCPPC_ADD_OPTS: "--library=./.gitlab-ci/cmocka.cfg"
CMAKE_OPTIONS: "-DCMAKE_BUILD_TYPE=Debug -DPICKY_DEVELOPER=ON -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DFUZZ_TESTING=ON"
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD
before_script:
- |
@@ -397,6 +449,14 @@ fedora/mingw32:
git branch --contains $CI_COMMIT_BEFORE_SHA 2>/dev/null || export CI_COMMIT_BEFORE_SHA=$(git rev-parse "${CI_COMMIT_SHA}~20")
export CI_COMMIT_RANGE="$CI_COMMIT_BEFORE_SHA..$CI_COMMIT_SHA"
script:
- csbuild
--build-dir=obj-csbuild
--build-cmd "rm -rf CMakeFiles CMakeCache.txt && cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS @SRCDIR@ && make clean && make -j$(nproc)"
--git-commit-range $CI_COMMIT_RANGE
--color
--print-current --print-fixed
tags:
- saas-linux-small-amd64
except:
@@ -411,33 +471,16 @@ fedora/mingw32:
fedora/csbuild/openssl_3.x:
extends: .csbuild
script:
- csbuild
--build-dir=obj-csbuild
--build-cmd "rm -rf CMakeFiles CMakeCache.txt && cmake -DCMAKE_BUILD_TYPE=Debug -DPICKY_DEVELOPER=ON -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DFUZZ_TESTING=ON @SRCDIR@ && make clean && make -j$(nproc)"
--git-commit-range $CI_COMMIT_RANGE
--color
--print-current --print-fixed
fedora/csbuild/libgcrypt:
extends: .csbuild
script:
- csbuild
--build-dir=obj-csbuild
--build-cmd "rm -rf CMakeFiles CMakeCache.txt && cmake -DCMAKE_BUILD_TYPE=Debug -DPICKY_DEVELOPER=ON -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DFUZZ_TESTING=ON -DWITH_GCRYPT=ON @SRCDIR@ && make clean && make -j$(nproc)"
--git-commit-range $CI_COMMIT_RANGE
--color
--print-current --print-fixed
variables:
CMAKE_ADDITIONAL_OPTIONS: -DWITH_GCRYPT=ON
fedora/csbuild/mbedtls:
extends: .csbuild
script:
- csbuild
--build-dir=obj-csbuild
--build-cmd "rm -rf CMakeFiles CMakeCache.txt && cmake -DCMAKE_BUILD_TYPE=Debug -DPICKY_DEVELOPER=ON -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DFUZZ_TESTING=ON -DWITH_MBEDTLS=ON @SRCDIR@ && make clean && make -j$(nproc)"
--git-commit-range $CI_COMMIT_RANGE
--color
--print-current --print-fixed
variables:
CMAKE_ADDITIONAL_OPTIONS: -DWITH_MBEDTLS=ON
###############################################################################
# Fedora abidiff #
@@ -502,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$"
###############################################################################
@@ -576,9 +625,10 @@ tumbleweed/static-analysis:
extends: .tests
stage: analysis
image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD
variables:
CCC_CC: clang
CCC_CXX: clang++
script:
- export CCC_CC=clang
- export CCC_CXX=clang++
- scan-build cmake
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_C_COMPILER=clang
@@ -602,19 +652,12 @@ tumbleweed/static-analysis:
freebsd/openssl_1.1.1/x86_64:
image:
extends: .tests
variables:
CMAKE_OPTIONS: $CMAKE_DEFAULT_OPTIONS -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DUNIT_TESTING=ON
before_script:
- mkdir -p obj && cd obj && cmake
-DCMAKE_BUILD_TYPE=RelWithDebInfo
-DPICKY_DEVELOPER=ON
-DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON
-DUNIT_TESTING=ON ..
- mkdir -p obj && cd obj && cmake $CMAKE_OPTIONS ..
script:
- cmake $CMAKE_DEFAULT_OPTIONS
-DWITH_SFTP=ON
-DWITH_SERVER=ON
-DWITH_ZLIB=ON
-DWITH_PCAP=ON
-DUNIT_TESTING=ON .. &&
- cmake $CMAKE_OPTIONS .. &&
make &&
ctest --output-on-failure
tags:
@@ -624,7 +667,6 @@ freebsd/openssl_1.1.1/x86_64:
- branches@libssh/libssh-mirror
- branches@cryptomilk/libssh-mirror
- branches@jjelen/libssh-mirror
- branches@marco.fortina/libssh-mirror
###############################################################################
@@ -663,13 +705,14 @@ freebsd/openssl_1.1.1/x86_64:
- vcpkg install cmocka
- vcpkg install openssl
- vcpkg install zlib
- vcpkg install libfido2
- vcpkg integrate install
- mkdir -p obj; if ($?) {cd obj}; if (! $?) {exit 1}
- cmake
-A $PLATFORM
-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
-DPICKY_DEVELOPER=ON
-DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON
-DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DWITH_FIDO2=ON
-DUNIT_TESTING=ON ..
visualstudio/x86_64:
@@ -730,6 +773,7 @@ coverity:
# MacOS #
###############################################################################
.macos:
extends: .build_options
tags:
- saas-macos-medium-m1
image: macos-14-xcode-15
@@ -742,17 +786,13 @@ coverity:
- branches@libssh/libssh-mirror
- branches@cryptomilk/libssh-mirror
- branches@jjelen/libssh-mirror
- branches@marco.fortina/libssh-mirror
# TODO add -DFUZZ_TESTING=ON clang cant find _LLVMFuzzerInitialize on arm64
macos-m1:
extends: .macos
variables:
HOMEBREW_NO_AUTO_UPDATE: 1
CMAKE_DEFAULT_OPTIONS: "-DCMAKE_BUILD_TYPE=RelWithDebInfo -DPICKY_DEVELOPER=ON"
CMAKE_BUILD_OPTIONS: "-DWITH_BLOWFISH_CIPHER=ON -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_DEBUG_PACKET=ON -DWITH_DEBUG_CALLTRACE=ON"
CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON"
CMAKE_OPTIONS: $CMAKE_DEFAULT_OPTIONS $CMAKE_BUILD_OPTIONS $CMAKE_TEST_OPTIONS
stage: test
script:
- cmake $CMAKE_OPTIONS .. &&

475
.gitlab-ci/cmocka.cfg Normal file
View File

@@ -0,0 +1,475 @@
<?xml version="1.0"?>
<!--
cppcheck library configuration for cmocka
This file provides cppcheck with information about cmocka's assertion functions
and their behavior, particularly that assertion failures do not return.
This helps cppcheck understand that after assert_non_null(ptr), the pointer
is guaranteed to be non-NULL, eliminating false positives like:
- nullPointerArithmeticOutOfMemory
- nullPointer
- etc.
Usage:
cppcheck --library=cmocka.cfg [other options] <source files>
For more information on cppcheck library format, see:
https://cppcheck.sourceforge.io/manual.html#library-configuration
-->
<def format="2">
<!-- Core functions -->
<function name="_fail">
<noreturn>true</noreturn>
<arg nr="1" direction="in">
<not-null/>
</arg>
<arg nr="2" direction="in"/>
</function>
<!-- Boolean assertions -->
<function name="_assert_true">
<arg nr="1" direction="in">
<valid>arg1 != 0</valid>
</arg>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
</function>
<function name="_assert_false">
<arg nr="1" direction="in">
<valid>arg1 == 0</valid>
</arg>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
</function>
<!-- Pointer assertions -->
<function name="_assert_ptr_equal_msg">
<arg nr="1" direction="in">
<valid>arg1 == arg2</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
<arg nr="5" direction="in"/>
</function>
<function name="_assert_ptr_not_equal_msg">
<arg nr="1" direction="in">
<valid>arg1 != arg2</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
<arg nr="5" direction="in"/>
</function>
<!-- Integer assertions -->
<function name="_assert_int_equal">
<arg nr="1" direction="in">
<valid>arg1 == arg2</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
</function>
<function name="_assert_int_not_equal">
<arg nr="1" direction="in">
<valid>arg1 != arg2</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
</function>
<function name="_assert_uint_equal">
<arg nr="1" direction="in">
<valid>arg1 == arg2</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
</function>
<function name="_assert_uint_not_equal">
<arg nr="1" direction="in">
<valid>arg1 != arg2</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
</function>
<!-- Float/double assertions -->
<function name="_assert_float_equal">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<function name="_assert_float_not_equal">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<function name="_assert_double_equal">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<function name="_assert_double_not_equal">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<!-- String assertions -->
<function name="_assert_string_equal">
<arg nr="1" direction="in">
<not-null/>
</arg>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
</function>
<function name="_assert_string_not_equal">
<arg nr="1" direction="in">
<not-null/>
</arg>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in"/>
</function>
<!-- Memory assertions -->
<function name="_assert_memory_equal">
<arg nr="1" direction="in">
<not-null/>
</arg>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<function name="_assert_memory_not_equal">
<arg nr="1" direction="in">
<not-null/>
</arg>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<!-- Range assertions -->
<function name="_assert_int_in_range">
<arg nr="1" direction="in">
<valid>arg2 &lt;= arg1 &amp;&amp; arg1 &lt;= arg3</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<function name="_assert_int_not_in_range">
<arg nr="1" direction="in">
<valid>arg1 &lt; arg2 || arg3 &lt; arg1</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<function name="_assert_uint_in_range">
<arg nr="1" direction="in">
<valid>arg2 &lt;= arg1 &amp;&amp; arg1 &lt;= arg3</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<function name="_assert_uint_not_in_range">
<arg nr="1" direction="in">
<valid>arg1 &lt; arg2 || arg3 &lt; arg1</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<function name="_assert_float_in_range">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in"/>
<arg nr="5" direction="in">
<not-null/>
</arg>
<arg nr="6" direction="in"/>
</function>
<function name="_assert_float_not_in_range">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in"/>
<arg nr="5" direction="in">
<not-null/>
</arg>
<arg nr="6" direction="in"/>
</function>
<!-- Set assertions -->
<function name="_assert_int_in_set">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in">
<not-null/>
</arg>
<arg nr="6" direction="in"/>
</function>
<function name="_assert_int_not_in_set">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in">
<not-null/>
</arg>
<arg nr="6" direction="in"/>
</function>
<function name="_assert_uint_in_set">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in">
<not-null/>
</arg>
<arg nr="6" direction="in"/>
</function>
<function name="_assert_uint_not_in_set">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in">
<not-null/>
</arg>
<arg nr="6" direction="in"/>
</function>
<function name="_assert_not_in_set">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in">
<not-null/>
</arg>
<arg nr="6" direction="in"/>
</function>
<function name="_assert_float_in_set">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in"/>
<arg nr="5" direction="in">
<not-null/>
</arg>
<arg nr="6" direction="in">
<not-null/>
</arg>
<arg nr="7" direction="in"/>
</function>
<function name="_assert_float_not_in_set">
<arg nr="1" direction="in"/>
<arg nr="2" direction="in">
<not-null/>
</arg>
<arg nr="3" direction="in"/>
<arg nr="4" direction="in"/>
<arg nr="5" direction="in">
<not-null/>
</arg>
<arg nr="6" direction="in">
<not-null/>
</arg>
<arg nr="7" direction="in"/>
</function>
<!-- Return code assertion -->
<function name="_assert_return_code">
<arg nr="1" direction="in">
<valid>0 &lt;= arg1</valid>
</arg>
<arg nr="2" direction="in"/>
<arg nr="3" direction="in">
<not-null/>
</arg>
<arg nr="4" direction="in">
<not-null/>
</arg>
<arg nr="5" direction="in"/>
</function>
<!-- Macro definitions -->
<!-- Boolean macros -->
<define name="assert_true(c)" value="_assert_true(cast_to_uintmax_type(c), #c, __FILE__, __LINE__)"/>
<define name="assert_false(c)" value="_assert_false(cast_to_uintmax_type(c), #c, __FILE__, __LINE__)"/>
<!-- Pointer macros -->
<define name="assert_non_null(c)" value="assert_ptr_not_equal((c), NULL)"/>
<define name="assert_non_null_msg(c, msg)" value="assert_ptr_not_equal_msg((c), NULL, (msg))"/>
<define name="assert_null(c)" value="assert_ptr_equal((c), NULL)"/>
<define name="assert_null_msg(c, msg)" value="assert_ptr_equal_msg((c), NULL, (msg))"/>
<define name="assert_ptr_equal(a, b)" value="assert_ptr_equal_msg((a), (b), NULL)"/>
<define name="assert_ptr_equal_msg(a, b, msg)" value="_assert_ptr_equal_msg((const void*)(a), (const void*)(b), __FILE__, __LINE__, (msg))"/>
<define name="assert_ptr_not_equal(a, b)" value="_assert_ptr_not_equal_msg((const void*)(a), (const void*)(b), __FILE__, __LINE__, NULL)"/>
<define name="assert_ptr_not_equal_msg(a, b, msg)" value="_assert_ptr_not_equal_msg((const void*)(a), (const void*)(b), __FILE__, __LINE__, (msg))"/>
<!-- Integer macros -->
<define name="assert_int_equal(a, b)" value="_assert_int_equal(cast_to_intmax_type(a), cast_to_intmax_type(b), __FILE__, __LINE__)"/>
<define name="assert_int_not_equal(a, b)" value="_assert_int_not_equal(cast_to_intmax_type(a), cast_to_intmax_type(b), __FILE__, __LINE__)"/>
<define name="assert_uint_equal(a, b)" value="_assert_uint_equal(cast_to_uintmax_type(a), cast_to_uintmax_type(b), __FILE__, __LINE__)"/>
<define name="assert_uint_not_equal(a, b)" value="_assert_uint_not_equal(cast_to_uintmax_type(a), cast_to_uintmax_type(b), __FILE__, __LINE__)"/>
<!-- Float/double macros -->
<define name="assert_float_equal(a, b, epsilon)" value="_assert_float_equal((float)(a), (float)(b), (float)(epsilon), __FILE__, __LINE__)"/>
<define name="assert_float_not_equal(a, b, epsilon)" value="_assert_float_not_equal((float)(a), (float)(b), (float)(epsilon), __FILE__, __LINE__)"/>
<define name="assert_double_equal(a, b, epsilon)" value="_assert_double_equal((double)(a), (double)(b), (double)(epsilon), __FILE__, __LINE__)"/>
<define name="assert_double_not_equal(a, b, epsilon)" value="_assert_double_not_equal((double)(a), (double)(b), (double)(epsilon), __FILE__, __LINE__)"/>
<!-- String macros -->
<define name="assert_string_equal(a, b)" value="_assert_string_equal((const char*)(a), (const char*)(b), __FILE__, __LINE__)"/>
<define name="assert_string_not_equal(a, b)" value="_assert_string_not_equal((const char*)(a), (const char*)(b), __FILE__, __LINE__)"/>
<!-- Memory macros -->
<define name="assert_memory_equal(a, b, size)" value="_assert_memory_equal((const void*)(a), (const void*)(b), size, __FILE__, __LINE__)"/>
<define name="assert_memory_not_equal(a, b, size)" value="_assert_memory_not_equal((const void*)(a), (const void*)(b), size, __FILE__, __LINE__)"/>
<!-- Range macros -->
<define name="assert_int_in_range(value, minimum, maximum)" value="_assert_int_in_range(cast_to_intmax_type(value), cast_to_intmax_type(minimum), cast_to_intmax_type(maximum), __FILE__, __LINE__)"/>
<define name="assert_int_not_in_range(value, minimum, maximum)" value="_assert_int_not_in_range(cast_to_intmax_type(value), cast_to_intmax_type(minimum), cast_to_intmax_type(maximum), __FILE__, __LINE__)"/>
<define name="assert_uint_in_range(value, minimum, maximum)" value="_assert_uint_in_range(cast_to_uintmax_type(value), cast_to_uintmax_type(minimum), cast_to_uintmax_type(maximum), __FILE__, __LINE__)"/>
<define name="assert_uint_not_in_range(value, minimum, maximum)" value="_assert_uint_not_in_range(cast_to_uintmax_type(value), cast_to_uintmax_type(minimum), cast_to_uintmax_type(maximum), __FILE__, __LINE__)"/>
<define name="assert_in_range(value, minimum, maximum)" value="assert_uint_in_range(value, minimum, maximum)"/>
<define name="assert_not_in_range(value, minimum, maximum)" value="assert_uint_not_in_range(value, minimum, maximum)"/>
<define name="assert_float_in_range(value, minimum, maximum, epsilon)" value="_assert_float_in_range((double)(value), (double)(minimum), (double)(maximum), (double)(epsilon), __FILE__, __LINE__)"/>
<define name="assert_float_not_in_range(value, minimum, maximum, epsilon)" value="_assert_float_not_in_range((double)(value), (double)(minimum), (double)(maximum), (double)(epsilon), __FILE__, __LINE__)"/>
<!-- Set macros -->
<define name="assert_in_set(value, values, number_of_values)" value="_assert_not_in_set(cast_to_uintmax_type(value), (uintmax_t*)(values), number_of_values, __FILE__, __LINE__, 0)"/>
<define name="assert_not_in_set(value, values, number_of_values)" value="_assert_not_in_set(cast_to_uintmax_type(value), (uintmax_t*)(values), number_of_values, __FILE__, __LINE__, 1)"/>
<define name="assert_int_in_set(value, values, number_of_values)" value="_assert_int_in_set(cast_to_intmax_type(value), (intmax_t*)(values), number_of_values, __FILE__, __LINE__, 0)"/>
<define name="assert_int_not_in_set(value, values, number_of_values)" value="_assert_int_not_in_set(cast_to_intmax_type(value), (intmax_t*)(values), number_of_values, __FILE__, __LINE__, 1)"/>
<define name="assert_uint_in_set(value, values, number_of_values)" value="_assert_uint_in_set(cast_to_uintmax_type(value), (uintmax_t*)(values), number_of_values, __FILE__, __LINE__, 0)"/>
<define name="assert_uint_not_in_set(value, values, number_of_values)" value="_assert_uint_not_in_set(cast_to_uintmax_type(value), (uintmax_t*)(values), number_of_values, __FILE__, __LINE__, 1)"/>
<define name="assert_float_in_set(value, values, number_of_values, epsilon)" value="_assert_float_in_set((double)(value), (double*)(values), number_of_values, (double)(epsilon), __FILE__, __LINE__, 0)"/>
<define name="assert_float_not_in_set(value, values, number_of_values, epsilon)" value="_assert_float_not_in_set((double)(value), (double*)(values), number_of_values, (double)(epsilon), __FILE__, __LINE__, 1)"/>
<!-- Return code macro -->
<define name="assert_return_code(rc, error)" value="_assert_return_code(cast_to_intmax_type(rc), (int32_t)(error), #rc, __FILE__, __LINE__)"/>
</def>

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

@@ -78,9 +78,19 @@ if (WITH_NACL)
endif (NOT NACL_FOUND)
endif (WITH_NACL)
if (WITH_FIDO2)
find_package(libfido2)
if (LIBFIDO2_FOUND)
set(HAVE_LIBFIDO2 ON)
else (LIBFIDO2_FOUND)
set(HAVE_LIBFIDO2 OFF)
message(WARNING "libfido2 was not found. Internal support for interacting with FIDO2/U2F devices using the USB HID protocol will not be available.")
endif (LIBFIDO2_FOUND)
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)
@@ -171,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")
@@ -183,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
@@ -199,7 +213,7 @@ if (WITH_COVERAGE)
NAME "coverage"
EXECUTABLE make test
DEPENDENCIES ssh tests)
set(GCOVR_ADDITIONAL_ARGS --xml-pretty --exclude-unreachable-branches --print-summary)
set(GCOVR_ADDITIONAL_ARGS --xml-pretty --exclude-unreachable-branches --print-summary --gcov-ignore-parse-errors)
setup_target_for_coverage_gcovr_xml(
NAME "coverage_xml"
EXECUTABLE make test
@@ -238,6 +252,10 @@ message(STATUS "Client code testing: ${CLIENT_TESTING}")
message(STATUS "Blowfish cipher support: ${HAVE_BLOWFISH}")
message(STATUS "PKCS #11 URI support: ${WITH_PKCS11_URI}")
message(STATUS "With PKCS #11 provider support: ${WITH_PKCS11_PROVIDER}")
message(STATUS "With FIDO2/U2F support: ${WITH_FIDO2}")
if (WITH_FIDO2)
message(STATUS "With libfido2 (internal usb-hid support): ${HAVE_LIBFIDO2}")
endif (WITH_FIDO2)
set(_SERVER_TESTING OFF)
if (WITH_SERVER)
set(_SERVER_TESTING ${SERVER_TESTING})

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,6 +104,12 @@ 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-KEM availability (OpenSSL 3.5+)
if (OPENSSL_VERSION VERSION_GREATER_EQUAL "3.5.0")
set(HAVE_OPENSSL_MLKEM 1)
set(HAVE_MLKEM1024 1)
endif ()
unset(CMAKE_REQUIRED_INCLUDES)
unset(CMAKE_REQUIRED_LIBRARIES)
endif()
@@ -134,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)
@@ -228,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

@@ -13,6 +13,7 @@ option(WITH_INTERNAL_DOC "Compile doxygen internal documentation" OFF)
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
option(WITH_PKCS11_URI "Build with PKCS#11 URI support" OFF)
option(WITH_PKCS11_PROVIDER "Use the PKCS#11 provider for accessing pkcs11 objects" OFF)
option(WITH_FIDO2 "Build with FIDO2/U2F support" OFF)
option(UNIT_TESTING "Build with unit tests" OFF)
option(CLIENT_TESTING "Build with client tests; requires openssh" OFF)
option(SERVER_TESTING "Build with server tests; requires openssh and dropbear" OFF)

View File

@@ -0,0 +1,63 @@
# - Try to find libfido2
# Once done this will define
#
# LIBFIDO2_ROOT_DIR - Set this variable to the root installation of libfido2
#
# Read-Only variables:
# LIBFIDO2_FOUND - system has libfido2
# LIBFIDO2_INCLUDE_DIR - the libfido2 include directory
# LIBFIDO2_LIBRARIES - Link these to use libfido2
#
# The libfido2 library provides support for communicating
# with FIDO2/U2F devices over USB/NFC.
#
# Copyright (c) 2025 Praneeth Sarode <praneethsarode@gmail.com>
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
set(_LIBFIDO2_ROOT_HINTS
$ENV{LIBFIDO2_ROOT_DIR}
${LIBFIDO2_ROOT_DIR}
)
set(_LIBFIDO2_ROOT_PATHS
"$ENV{PROGRAMFILES}/libfido2"
)
set(_LIBFIDO2_ROOT_HINTS_AND_PATHS
HINTS ${_LIBFIDO2_ROOT_HINTS}
PATHS ${_LIBFIDO2_ROOT_PATHS}
)
find_path(LIBFIDO2_INCLUDE_DIR
NAMES
fido.h
HINTS
${_LIBFIDO2_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
include
)
find_library(LIBFIDO2_LIBRARY
NAMES
fido2
HINTS
${_LIBFIDO2_ROOT_HINTS_AND_PATHS}
PATH_SUFFIXES
lib
lib64
)
set(LIBFIDO2_LIBRARIES
${LIBFIDO2_LIBRARY}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(libfido2 DEFAULT_MSG LIBFIDO2_LIBRARIES LIBFIDO2_INCLUDE_DIR)
# show the LIBFIDO2_INCLUDE_DIR and LIBFIDO2_LIBRARIES variables only in the advanced view
mark_as_advanced(LIBFIDO2_INCLUDE_DIR LIBFIDO2_LIBRARIES)

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,6 +194,15 @@
/* Define to 1 if we have support for blowfish */
#cmakedefine HAVE_BLOWFISH 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 ***************************/
/* Define to 1 if you have the `crypto' library (-lcrypto). */
@@ -208,6 +220,10 @@
/* Define to 1 if you have the `cmocka' library (-lcmocka). */
#cmakedefine HAVE_CMOCKA 1
/* Define to 1 if you have the `libfido2' library (-lfido2).
* This is required for interacting with FIDO2/U2F devices over USB-HID. */
#cmakedefine HAVE_LIBFIDO2 1
/**************************** OPTIONS ****************************/
#cmakedefine HAVE_GCC_THREAD_LOCAL_STORAGE 1
@@ -274,6 +290,9 @@
/* Define to 1 if we want to build a support for PKCS #11 provider. */
#cmakedefine WITH_PKCS11_PROVIDER 1
/* Define to 1 if you want to enable FIDO2/U2F support */
#cmakedefine WITH_FIDO2 1
/*************************** ENDIAN *****************************/
/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most

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()

601
doc/fido2.dox Normal file
View File

@@ -0,0 +1,601 @@
/**
@page libssh_tutor_fido2 Chapter 11: FIDO2/U2F Keys Support
@section fido2_intro Introduction
The traditional SSH public key model stores the private key on disk
and anyone who obtains that file (and possibly its passphrase) can impersonate
the user. FIDO2 authenticators, such as USB security keys, are hardware tokens
that generate or securely store private key material within a secure element
and may require explicit user interaction such as a touch, PIN, or biometric
verification for use. Hence, security keys are far safer from theft or
exfiltration than traditional file-based SSH keys. libssh provides support
for FIDO2/U2F security keys as hardware-backed SSH authentication credentials.
This chapter explains the concepts, build prerequisites, the API, and
usage patterns for enrolling (creating) and using security key-backed SSH
keys, including resident (discoverable) credentials.
@subsection fido2_resident_keys Resident Keys
Two credential storage modes exist for security keys:
- Non-resident (default): A credential ID (key handle) and metadata are
stored on the client-side in a key file. This key handle must be
presented to the FIDO2/U2F device while signing. This is somewhat
similar to traditional SSH keys, except that the key handle is not the
private key itself, but used in combination with the device's master key
to derive the actual private key.
- Resident (discoverable): The credential (and metadata like user id) is
stored on the device. No local file is needed; the device can enumerate or
locate the credential internally when queried.
Advantages of resident keys include portability (using the same device
across hosts) and resilience (no loss if the local machine is destroyed).
Although, they may be limited by the storage of the authenticator.
@subsection fido2_presence_verification User Presence vs. User Verification
FIDO2 distinguishes between:
- User Presence (UP): A simple physical interaction (touch) to confirm a
human is present.
- User Verification (UV): Verification of the users identity through
biometric authentication or a PIN.
Requiring UV provides additional protection if the device is stolen
and used without the PIN/biometric.
libssh exposes flags controlling these requirements (see below).
@subsection fido2_callbacks The Callback Abstraction
Different environments may need to access security keys through different
transport layers (e.g., USB-HID, NFC, Bluetooth, etc.). To accommodate
this variability, libssh does not hard-code a single implementation.
Instead, it defines a small callback interface (`ssh_sk_callbacks`) used for all
security key operations. Any implementation of this callback interface can be used
by higher-level PKI functions to perform enroll/sign/load_resident_keys
operations without needing to know the transport specifics. Hence, users can
define their own implementations for these callbacks to support different
transport protocols or custom hardware. Refer @ref fido2_custom_callbacks
for additional details.
The callback interface is defined in `libssh/callbacks.h` and the behaviour
and return values are specified by `libssh/sk_api.h`, which is the same
interface defined by OpenSSH for its security key support. This means that
any callback implementations (also called "middleware" in OpenSSH terminology)
developed for OpenSSH can be adapted to libssh with minimal changes.
The following operations are abstracted by the callback interface:
- api_version(): Report the version of the SK API that the callback implementation
is based on, so that libssh can check whether this implementation would be
compatible with the SK API version that it supports.
Refer @ref fido2_custom_callbacks_version for additional details.
- enroll(): Create (enroll) a new credential, returning public key, key
handle, attestation data.
- sign(): Produce a signature for supplied inputs using an existing key
handle.
- load_resident_keys(): Enumerate resident (discoverable) credentials stored
on the authenticator.
libssh provides a default implementation of the `ssh_sk_callbacks` using
the libfido2 library for the USB-HID transport protocol. Hence, by default,
libssh can interact with any FIDO2/U2F device that supports USB-HID and is
compatible with libfido2, without requiring any additional modifications.
@subsection fido2_build Building with FIDO2 Support
To enable FIDO2/U2F support, libssh must be built with the WITH_FIDO2
build option as follows:
@verbatim
cmake -DWITH_FIDO2=ON <other options> ..
@endverbatim
libssh will also build the default USB-HID `ssh_sk_callbacks`, if the
libfido2 library and headers are installed on your system.
@warning If built without libfido2, support for interacting with FIDO2/U2F
devices over USB-HID will not be available.
@subsection fido2_api_overview API Overview
Security key operations are configured through the `ssh_pki_ctx`
which allows to specify both general PKI options and FIDO2-specific
options such as the sk_callbacks, challenge data, application string, flags, etc.
The following sections describe the options that can be configured and how
the `ssh_pki_ctx` is used in conjunction with `ssh_key` to perform
enrollment, signing, and resident key loading operations.
@subsection fido2_key_objects Security Key Objects & Metadata
Security keys are surfaced as `ssh_key` objects of type
`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519` (corresponding to the
OpenSSH public key algorithm names `sk-ecdsa-sha2-nistp256@openssh.com` and
`sk-ssh-ed25519@openssh.com`). In addition to standard key handling, libssh
exposes the following helper functions to retrieve embedded SK metadata:
- ssh_key_get_sk_application(): Returns the relying party / application
(RP ID) string. The Relying Party ID (RP ID) is a string
that identifies the application or service requesting key enrollment. It
ensures that a credential is bound to a specific origin, preventing
phishing across sites. During registration, the authenticator associates
the credential with this RP ID so that it can later only be used for
authentication requests from the same relying party. For SSH keys, the
common format is "ssh:user@host".
- ssh_key_get_sk_user_id(): Returns a copy of the user ID associated with a key
which represents a unique identifier for the user within the relying
party (application) context. It is typically a string (such as an
email, or a random identifier) that helps distinguish credentials
belonging to different users for the same application.
Though the user ID can be binary data according to the FIDO2 spec, libssh only
supports NUL-terminated strings for enrolling new keys in order to remain compatible
with the OpenSSH's sk-api interface.
However, libssh does support loading existing resident keys with user IDs containing
arbitrary binary data. It does so by using an `ssh_string` to store the loaded key's
user_id, and an `ssh_string` can contain arbitrary binary data that can not be stored
in a traditional NUL-terminated string (like null bytes).
@note The user_id is NOT stored in the key file for non-resident keys. It is only
available for resident (discoverable) keys loaded from the authenticator via
ssh_sk_resident_keys_load(). For keys imported from files, this function returns
NULL.
- ssh_key_get_sk_flags(): Returns the flags associated with the key. The
following are the supported flags and they can be combined using
bitwise OR:
- SSH_SK_USER_PRESENCE_REQD : Require user presence (touch).
- SSH_SK_USER_VERIFICATION_REQD : Require user verification
(PIN/biometric).
- SSH_SK_RESIDENT_KEY : Request a resident discoverable credential.
- SSH_SK_FORCE_OPERATION : Force resident (discoverable) credential
creation even if one with same application and user_id already
exists.
These functions perform no additional communication with the
authenticator, this metadata is captured during enrollment/loading and
cached in the `ssh_key`.
@subsection fido2_options Setting Security Key Context Options
Options are set via ssh_pki_ctx_options_set().
Representative security key options:
- SSH_PKI_OPTION_SK_APPLICATION (const char *): Required relying party ID
If not set, a default value of "ssh:" is used.
- SSH_PKI_OPTION_SK_FLAGS (uint8_t *): Flags described above. If not set,
defaults to SSH_SK_USER_PRESENCE_REQD. This is because OpenSSH `sshd`
requires user presence for security key authentication by default.
- SSH_PKI_OPTION_SK_USER_ID (const char *): Represents a unique identifier
for the user within the relying party (application) context.
It is typically a string (such as an email, or a random identifier) that
helps distinguish credentials belonging to different users for the same
application. If not set, defaults to 64 zeros.
- SSH_PKI_OPTION_SK_CHALLENGE (ssh_buffer): Custom challenge; if omitted a
random 32-byte challenge is generated.
- SSH_PKI_OPTION_SK_CALLBACKS (ssh_sk_callbacks): Replace the default
callbacks with custom callbacks.
PIN callback: Use ssh_pki_ctx_set_sk_pin_callback() to register a function
matching `ssh_auth_callback` to prompt for and supply a PIN. The callback may
be called multiple times to ask for the pin depending on the authenticator policy.
Callback options: Callback implementations may accept additional configuration
name/value options such as the path to the fido device. These options can be provided via
`ssh_pki_ctx_sk_callbacks_option_set()`. Refer @ref fido2_custom_callbacks_options
for additional details.
The built-in callback implementation provided by libssh supports additional options,
with their names defined in `libssh.h` prefixed with `SSH_SK_OPTION_NAME_*`, such as:
SSH_SK_OPTION_NAME_DEVICE_PATH: Used for specifying a device path.
If the device path is not specified and multiple devices are connected, then
depending upon the operation and the flags set, the callback implementation may
automatically select a suitable device, or the user may be prompted to touch the
device they want to use.
SSH_SK_OPTION_NAME_USER_ID: Used for setting the user ID.
Note that the user ID can also be set using the ssh_pki_ctx_options_set() API.
@subsection fido2_enrollment Enrollment Example
An enrollment operation creates a new credential on the authenticator and
returns an ssh_key object representing it. The application and user_id
fields are required for creating the credential. The other options are
optional. A successful enrollment returns the public key, key handle, and
metadata which are stored in the ssh_key object, and may optionally return
attestation data which is used for verifying the authenticator model and
firmware version.
Below is a simple example enrolling an Ed25519 security key (non-resident)
requiring user presence only:
@code
#include <libssh/libssh.h>
#include <string.h>
static int pin_cb(const char *prompt,
char *buf,
size_t len,
int echo,
int verify,
void *userdata)
{
(void)prompt;
(void)echo;
(void)verify;
(void)userdata;
/* In a real application, the user would be prompted to enter the PIN */
const char *pin = "4242";
size_t l = strlen(pin);
if (l + 1 > len) {
return SSH_ERROR;
}
memcpy(buf, pin, l + 1);
return SSH_OK;
}
int enroll_sk_key()
{
const char *app = "ssh:user@host";
const char *user_id = "alice";
uint8_t flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD;
const char *device_path = "/dev/hidraw6"; /* Optional device path */
ssh_pki_ctx pki_ctx = ssh_pki_ctx_new();
ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_APPLICATION, app);
ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_USER_ID, user_id);
ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_FLAGS, &flags);
ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL);
ssh_pki_ctx_sk_callbacks_option_set(pki_ctx,
SSH_SK_OPTION_NAME_DEVICE_PATH,
device_path,
true);
ssh_key enrolled = NULL;
int rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ED25519,
pki_ctx,
&enrolled); /* produces sk-ed25519 key */
/* Save enrolled key using ssh_pki_export_privkey_file, retrieve attestation
* buffer etc. */
/* Free context and key when done */
}
@endcode
After a successful enrollment, you can retrieve the attestation buffer
(if provided by the authenticator) from the PKI context:
@code
ssh_buffer att_buf = NULL;
rc = ssh_pki_ctx_get_sk_attestation_buffer(pki_ctx, &att_buf);
if (rc == SSH_OK && att_buf != NULL) {
/* att_buf now contains the serialized attestation
* ("ssh-sk-attest-v01"). You can inspect, save, or
* parse the buffer as needed
*/
ssh_buffer_free(att_buf);
}
@endcode
Notes:
- The attestation buffer is only populated if the enrollment operation
succeeds and the authenticator provides attestation data.
- `ssh_pki_ctx_get_sk_attestation_buffer()` returns a copy of the attestation
buffer; the caller must free it with `ssh_buffer_free()`.
@subsection fido2_signing Authenticating with a Stored Security Key Public Key
To authenticate using a security key, the application typically loads the
previously enrolled sk-* private key, establishes an SSH connection, and
calls `ssh_userauth_publickey()`. libssh automatically recognizes security
key types and transparently handles the required hardware-backed
authentication steps such as prompting for a touch or PIN using the
configured security key callbacks.
Example:
@code
#include <libssh/libssh.h>
#include <stdio.h>
int auth_with_sk_file(const char *host,
const char *user,
const char *privkey_path)
{
ssh_session session = NULL;
ssh_key privkey = NULL;
int rc = SSH_ERROR;
session = ssh_new();
ssh_options_set(session, SSH_OPTIONS_HOST, host);
ssh_options_set(session, SSH_OPTIONS_USER, user);
ssh_connect(session);
ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, &privkey);
ssh_pki_ctx pki_ctx = ssh_pki_ctx_new();
/* Optionally set PIN callback, device path, etc. */
/* ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); */
ssh_options_set(session, SSH_OPTIONS_PKI_CONTEXT, pki_ctx);
rc = ssh_userauth_publickey(session, user, privkey);
if (rc == SSH_AUTH_SUCCESS) {
printf("Authenticated with security key.\n");
rc = SSH_OK;
} else {
fprintf(stderr,
"Authentication failed rc=%d err=%s\n",
rc,
ssh_get_error(session));
rc = SSH_ERROR;
}
/* Free resources */
}
@endcode
@subsection fido2_resident Resident Key Enumeration
Resident keys stored on the device can be discovered and loaded with
ssh_sk_resident_keys_load() which takes a PKI context (configured with
a PIN callback) and returns each key as an ssh_key and the number of keys loaded.
Example:
@code
#include <libssh/libssh.h>
#include <stdio.h>
#include <string.h>
static int pin_cb(const char *prompt,
char *buf,
size_t len,
int echo,
int verify,
void *userdata)
{
(void)prompt;
(void)echo;
(void)verify;
(void)userdata;
const char *pin = "4242";
size_t l = strlen(pin);
if (l + 1 > len) {
return SSH_ERROR;
}
memcpy(buf, pin, l + 1);
return SSH_OK;
}
int auth_with_resident(const char *host,
const char *user,
const char *application,
const char *user_id)
{
ssh_pki_ctx pki_ctx = NULL;
size_t num_found = 0;
ssh_key *keys = NULL;
ssh_key final_key = NULL;
int rc = SSH_ERROR;
ssh_string cur_application = NULL;
ssh_string cur_user_id = NULL;
ssh_string expected_application = NULL;
ssh_string expected_user_id = NULL;
pki_ctx = ssh_pki_ctx_new();
ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL);
expected_application = ssh_string_from_char(application);
expected_user_id = ssh_string_from_char(user_id);
rc = ssh_sk_resident_keys_load(pki_ctx, &keys, &num_found);
for (size_t i = 0; i < num_found; i++) {
cur_application = ssh_key_get_sk_application(keys[i]);
cur_user_id = ssh_key_get_sk_user_id(keys[i]);
if (ssh_string_cmp(cur_application, expected_application) == 0 &&
ssh_string_cmp(cur_user_id, expected_user_id) == 0) {
SSH_STRING_FREE(cur_application);
SSH_STRING_FREE(cur_user_id);
final_key = keys[i];
break;
}
SSH_STRING_FREE(cur_application);
SSH_STRING_FREE(cur_user_id);
}
SSH_STRING_FREE(expected_application);
SSH_STRING_FREE(expected_user_id);
/* Continue with authentication using the ssh_key with
* ssh_userauth_publickey as usual, and free resources when done. */
}
@endcode
@subsection fido2_sshsig Signing using the sshsig API
Security keys can also be used for general-purpose signing of arbitrary data
(without SSH authentication) using the existing `sshsig_sign()` and `sshsig_verify()`
functions. These functions work seamlessly with security key types
(`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519`) and will automatically
invoke the configured security key callbacks to perform hardware-backed signing
operations.
@subsection fido2_custom_callbacks Implementing Custom Callback Implementations
Users may need to implement custom callback implementations to support
different transport protocols (e.g., NFC, Bluetooth) beyond the default USB-HID
support. This section describes how to implement and integrate custom callback
implementations.
To implement custom callbacks, you must include the following headers:
@code
#include <libssh/callbacks.h> /* For ssh_sk_callbacks_struct */
#include <libssh/sk_api.h> /* For SK API constants and data structures */
@endcode
The `libssh/sk_api.h` header provides the complete interface specification including
request/response structures, flags, and version macros.
@subsubsection fido2_custom_callbacks_version API Version Compatibility
libssh validates callback implementations by checking the API version returned by
the `api_version()` callback. To ensure compatibility, libssh compares the major
version (upper 16 bits) of the returned value with `LIBSSH_SK_API_VERSION_MAJOR`.
If they don't match, libssh will reject the callback implementation.
This ensures that the callbacks' SK API matches the major version expected by libssh,
while allowing minor version differences.
@subsubsection fido2_custom_callbacks_implementation Implementation Example
Here's a minimal example of defining and using custom callbacks:
@code
#include <libssh/libssh.h>
#include <libssh/callbacks.h>
#include <libssh/sk_api.h>
/* Your custom API version callback */
static uint32_t my_sk_api_version(void)
{
/* Match the major version, set your own minor version */
return SSH_SK_VERSION_MAJOR | 0x0001;
}
/* Your custom enroll callback */
static int my_sk_enroll(uint32_t alg,
const uint8_t *challenge,
size_t challenge_len,
const char *application,
uint8_t flags,
const char *pin,
struct sk_option **options,
struct sk_enroll_response **enroll_response)
{
/* Parse options array to extract custom parameters */
if (options != NULL) {
for (size_t i = 0; options[i] != NULL; i++) {
if (strcmp(options[i]->name, "my_custom_option") == 0) {
/* Use options[i]->value */
}
}
}
/* Implement your enroll logic here */
/* ... */
return SSH_SK_ERR_GENERAL; /* Return appropriate error code */
}
/* Implement other required callbacks: sign, load_resident_keys */
/* ... */
/* Define your callback structure */
static struct ssh_sk_callbacks_struct my_sk_callbacks = {
.size = sizeof(struct ssh_sk_callbacks_struct),
.api_version = my_sk_api_version,
.enroll = my_sk_enroll,
.sign = my_sk_sign, /* Your implementation */
.load_resident_keys = my_sk_load_resident_keys, /* Your implementation */
};
/* Usage example */
void use_custom_callbacks(void)
{
ssh_pki_ctx pki_ctx = ssh_pki_ctx_new();
/* Set your custom callbacks */
ssh_pki_ctx_options_set(pki_ctx,
SSH_PKI_OPTION_SK_CALLBACKS,
&my_sk_callbacks);
/* Pass custom options to your callbacks */
ssh_pki_ctx_sk_callbacks_option_set(pki_ctx,
"my_custom_option",
"my_custom_value",
false);
/* Use the context for enrollment, signing, etc. */
}
@endcode
@subsubsection fido2_custom_callbacks_options Passing Custom Options
The `ssh_pki_ctx_sk_callbacks_option_set()` function allows you to pass
implementation-specific options as name/value string pairs:
@code
ssh_pki_ctx_sk_callbacks_option_set(pki_ctx,
"option_name",
"option_value",
required);
@endcode
Parameters:
- `option_name`: The name of the option (e.g., "device_path", "my_custom_param")
- `option_value`: The string value for this option
- `required`: If true, this option must be processed by the callback implementation
and cannot be ignored. If false, the option is advisory and can be skipped if the
callback implementation does not support it.
These options are passed to your callbacks in the `struct sk_option **options`
parameter as a NULL-terminated array. Each `sk_option` has the following fields:
- `name`: The option name (char *)
- `value`: The option value (char *)
- `required`: Whether the option must be processed (uint8_t, non-zero = required)
@subsubsection fido2_custom_callbacks_openssh OpenSSH Middleware Compatibility
Since libssh uses the same SK API as OpenSSH, middleware implementations developed
for OpenSSH can be adapted with minimal changes.
To adapt an OpenSSH middleware for libssh, create a wrapper that populates
`ssh_sk_callbacks_struct` with pointers to the middleware's functions.
@subsection fido2_testing Testing and Environment Variables
Unit tests covering USB-HID enroll/sign/load_resident_keys operations can be found
in the `tests/unittests/torture_sk_usbhid.c` file. To run these tests you
must have libfido2 installed and the WITH_FIDO2=ON build option set.
Additionally, you must ensure the following:
- An actual FIDO2 device must be connected to the test machine.
- The TORTURE_SK_USBHID environment variable must be set.
- The environment variable TORTURE_SK_PIN=<device PIN> must be set.
If these are not set, the tests are skipped.
The higher level PKI integration tests can be found in
`tests/unittests/torture_pki_sk.c` and the tests related to the sshsig API
can be found in `tests/unittests/torture_pki_sshsig.c`.
These use the callback implementation provided by OpenSSH's sk-dummy.so,
which simulates an authenticator without requiring any hardware. Hence, these tests
can be run in the CI environment.
However, these tests can also be configured to use the default USB-HID callbacks
by setting the same environment variables as described above.
The following devices were tested during development:
- Yubico Security Key NFC - USB-A
*/

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

@@ -48,6 +48,8 @@ Table of contents:
@subpage libssh_tutor_sftp_aio
@subpage libssh_tutor_fido2
@subpage libssh_tutor_todo
*/

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

@@ -21,7 +21,7 @@ clients must be made or how a client should react.
#include "examples_common.h"
#include <stdio.h>
ssh_session connect_ssh(const char *host, const char *user, int verbosity)
ssh_session connect_ssh(const char *host, const char *port, const char *user, int verbosity)
{
ssh_session session = NULL;
int auth = 0;
@@ -38,6 +38,13 @@ ssh_session connect_ssh(const char *host, const char *user, int verbosity)
}
}
if (port != NULL) {
if (ssh_options_set(session, SSH_OPTIONS_PORT_STR, port) < 0) {
ssh_free(session);
return NULL;
}
}
if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) {
ssh_free(session);
return NULL;

View File

@@ -21,6 +21,6 @@ clients must be made or how a client should react.
int authenticate_console(ssh_session session);
int authenticate_kbdint(ssh_session session, const char *password);
int verify_knownhost(ssh_session session);
ssh_session connect_ssh(const char *hostname, const char *user, int verbosity);
ssh_session connect_ssh(const char *hostname, const char *port, const char *user, int verbosity);
#endif /* EXAMPLES_COMMON_H_ */

View File

@@ -11,7 +11,7 @@ int main(void) {
int rbytes, wbytes, total = 0;
int rc;
session = connect_ssh("localhost", NULL, 0);
session = connect_ssh("localhost", NULL, NULL, 0);
if (session == NULL) {
ssh_finalize();
return 1;

View File

@@ -30,6 +30,7 @@ static char **sources = NULL;
static int nsources;
static char *destination = NULL;
static int verbosity = 0;
static char *port = NULL;
struct location {
int is_ssh;
@@ -49,9 +50,10 @@ enum {
static void usage(const char *argv0) {
fprintf(stderr, "Usage : %s [options] [[user@]host1:]file1 ... \n"
" [[user@]host2:]destination\n"
"sample scp client - libssh-%s\n",
// "Options :\n",
// " -r : use RSA to verify host public key\n",
"sample scp client - libssh-%s\n"
"Options :\n"
" -P : use port to connect to remote host\n"
" -v : increase verbosity of libssh. Can be used multiple times\n",
argv0,
ssh_version(0));
exit(0);
@@ -60,11 +62,14 @@ static void usage(const char *argv0) {
static int opts(int argc, char **argv) {
int i;
while((i = getopt(argc, argv, "v")) != -1) {
while((i = getopt(argc, argv, "P:v")) != -1) {
switch(i) {
case 'v':
verbosity++;
break;
case 'P':
port = optarg;
break;
default:
fprintf(stderr, "unknown option %c\n", optopt);
usage(argv[0]);
@@ -183,7 +188,7 @@ static void close_location(struct location *loc) {
static int open_location(struct location *loc, int flag) {
if (loc->is_ssh && flag == WRITE) {
loc->session = connect_ssh(loc->host, loc->user, verbosity);
loc->session = connect_ssh(loc->host, port, loc->user, verbosity);
if (!loc->session) {
fprintf(stderr, "Couldn't connect to %s\n", loc->host);
return -1;
@@ -209,7 +214,7 @@ static int open_location(struct location *loc, int flag) {
}
return 0;
} else if (loc->is_ssh && flag == READ) {
loc->session = connect_ssh(loc->host, loc->user, verbosity);
loc->session = connect_ssh(loc->host, port, loc->user, verbosity);
if (!loc->session) {
fprintf(stderr, "Couldn't connect to %s\n", loc->host);
return -1;

View File

@@ -148,6 +148,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
ssh_bind sshbind = state->input;
static int no_default_keys = 0;
static int rsa_already_set = 0, ecdsa_already_set = 0;
static int verbosity = 0;
switch (key)
{
@@ -176,8 +177,10 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1);
break;
case 'v':
ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR,
"3");
verbosity++;
ssh_bind_options_set(sshbind,
SSH_BIND_OPTIONS_LOG_VERBOSITY,
&verbosity);
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 1)
@@ -213,10 +216,7 @@ static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL};
#endif /* HAVE_ARGP_H */
/* A userdata struct for channel. */
struct channel_data_struct
{
/* Event which is used to poll the above descriptors. */
ssh_event event;
struct channel_data_struct {
sftp_session sftp;
};
@@ -378,18 +378,11 @@ static void handle_session(ssh_event event, ssh_session session)
do {
/* Poll the main event which takes care of the session, the channel and
* even our child process's stdout/stderr (once it's started). */
if (ssh_event_dopoll(event, -1) == SSH_ERROR) {
if (ssh_event_dopoll(event, 100) == SSH_ERROR) {
ssh_channel_close(sdata.channel);
}
/* If child process's stdout/stderr has been registered with the event,
* or the child process hasn't started yet, continue. */
if (cdata.event != NULL) {
continue;
}
/* FIXME The server keeps hanging in the poll above when the client
* closes the channel */
} while (ssh_channel_is_open(sdata.channel));
} while (ssh_channel_is_open(sdata.channel) &&
!ssh_channel_is_eof(sdata.channel));
ssh_channel_send_eof(sdata.channel);
ssh_channel_close(sdata.channel);

View File

@@ -182,7 +182,7 @@ int main(int argc, char **argv)
ssh_session session = NULL;
if (opts(argc, argv) < 0)
return EXIT_FAILURE;
session = connect_ssh(host, NULL, verbosity);
session = connect_ssh(host, NULL, NULL, verbosity);
if (session == NULL)
return EXIT_FAILURE;
create_files(session);

View File

@@ -13,7 +13,7 @@ int main(void)
int rc;
uint64_t total = 0;
uint64_t lastshown = 4096;
session = connect_ssh("localhost", NULL, 0);
session = connect_ssh("localhost", NULL, NULL, 0);
if (session == NULL) {
return 1;
}

View File

@@ -39,8 +39,6 @@
#include <libssh/callbacks.h>
#include <libssh/libssh.h>
#include <libssh/sftp.h>
#include "examples_common.h"
#define MAXCMD 10
@@ -88,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);
}
@@ -112,8 +112,8 @@ static int opts(int argc, char **argv)
{
int i;
while((i = getopt(argc,argv,"T:P:F:")) != -1) {
switch(i){
while ((i = getopt(argc, argv, "T:P:F:")) != -1) {
switch (i) {
case 'P':
pcap_file = optarg;
break;
@@ -159,16 +159,14 @@ static void cfmakeraw(struct termios *termios_p)
static void do_cleanup(int i)
{
/* unused variable */
(void) i;
(void)i;
tcsetattr(0, TCSANOW, &terminal);
tcsetattr(0, TCSANOW, &terminal);
}
static void do_exit(int i)
{
/* unused variable */
(void) i;
(void)i;
do_cleanup(0);
exit(0);
@@ -179,7 +177,7 @@ static int signal_delayed = 0;
#ifdef SIGWINCH
static void sigwindowchanged(int i)
{
(void) i;
(void)i;
signal_delayed = 1;
}
#endif
@@ -213,18 +211,18 @@ static void select_loop(ssh_session session,ssh_channel channel)
/* stdin */
connector_in = ssh_connector_new(session);
ssh_connector_set_out_channel(connector_in, channel, SSH_CONNECTOR_STDINOUT);
ssh_connector_set_in_fd(connector_in, 0);
ssh_connector_set_in_fd(connector_in, STDIN_FILENO);
ssh_event_add_connector(event, connector_in);
/* stdout */
connector_out = ssh_connector_new(session);
ssh_connector_set_out_fd(connector_out, 1);
ssh_connector_set_out_fd(connector_out, STDOUT_FILENO);
ssh_connector_set_in_channel(connector_out, channel, SSH_CONNECTOR_STDINOUT);
ssh_event_add_connector(event, connector_out);
/* stderr */
connector_err = ssh_connector_new(session);
ssh_connector_set_out_fd(connector_err, 2);
ssh_connector_set_out_fd(connector_err, STDERR_FILENO);
ssh_connector_set_in_channel(connector_err, channel, SSH_CONNECTOR_STDERR);
ssh_event_add_connector(event, connector_err);
@@ -253,7 +251,7 @@ static void shell(ssh_session session)
{
ssh_channel channel = NULL;
struct termios terminal_local;
int interactive=isatty(0);
int interactive = isatty(0);
channel = ssh_channel_new(session);
if (channel == NULL) {
@@ -357,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

@@ -29,6 +29,13 @@ if (WITH_SERVER)
endif (WITH_SFTP)
endif (WITH_SERVER)
if (WITH_FIDO2)
set(libssh_HDRS
${libssh_HDRS}
sk_api.h
)
endif (WITH_FIDO2)
install(
FILES
${libssh_HDRS}

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

@@ -52,6 +52,7 @@ enum ssh_bind_config_opcode_e {
BIND_CFG_MATCH,
BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES,
BIND_CFG_HOSTKEY_ALGORITHMS,
BIND_CFG_REQUIRED_RSA_SIZE,
BIND_CFG_MAX /* Keep this one last in the list */
};

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
@@ -1154,6 +1179,177 @@ struct ssh_jump_callbacks_struct {
ssh_jump_authenticate_callback authenticate;
};
/* Security key callbacks */
/*
* Forward declarations for structs that have been defined in sk_api.h.
* If you need to work with the fields inside them, please include
* libssh/sk_api.h
*/
struct sk_enroll_response;
struct sk_sign_response;
struct sk_resident_key;
struct sk_option;
#define LIBSSH_SK_API_VERSION_MAJOR 0x000a0000
/**
* @brief FIDO2/U2F SK API version callback.
*
* Returns the version of the FIDO2/U2F API that the callbacks implement.
* This callback allows custom callback implementations to specify their
* SK API version for compatibility checking with libssh's security key
* interface.
*
* @details Version compatibility is determined by comparing the major version
* portion (upper 16 bits) of the returned value with SSH_SK_VERSION_MAJOR.
*
* For compatibility, implementations should return a version where:
* (returned_version & SSH_SK_VERSION_MAJOR_MASK) == SSH_SK_VERSION_MAJOR
*
* This ensures that the callbacks' SK API matches the major version expected
* by libssh, while allowing minor version differences for backward
* compatibility.
*
* @see LIBSSH_SK_API_VERSION_MAJOR Current expected major API version
* @see SSH_SK_VERSION_MAJOR_MASK Mask for extracting major version (0xffff0000)
*/
typedef uint32_t (*sk_api_version_callback)(void);
/**
* @brief FIDO2/U2F key enrollment callback.
*
* Enrolls a new FIDO2/U2F security key credential (private key generation).
* This callback handles the creation of new FIDO2/U2F credentials, including
* both resident and non-resident keys.
*
* @param[in] alg The cryptographic algorithm to use
* @param[in] challenge Random challenge data for enrollment
* @param[in] challenge_len Length of the challenge data
* @param[in] application Application identifier (relying party ID)
* @param[in] flags Enrollment flags
* @param[in] pin PIN for user verification (may be NULL)
* @param[in] options Array of enrollment options (device path, user ID, etc.)
* @param[out] enroll_response Enrollment response containing public key,
* key handle, signature, and attestation data
*
* @returns SSH_OK on success, SSH_SK_ERR_* codes on failure.
*/
typedef int (*sk_enroll_callback)(uint32_t alg,
const uint8_t *challenge,
size_t challenge_len,
const char *application,
uint8_t flags,
const char *pin,
struct sk_option **options,
struct sk_enroll_response **enroll_response);
/**
* @brief FIDO2/U2F security key signing callback.
*
* Signs data using a FIDO2 security key credential. This callback performs
* cryptographic signing operations using previously enrolled FIDO2/U2F
* credentials.
*
* @param[in] alg The cryptographic algorithm used by the key
* @param[in] data Data to be signed
* @param[in] data_len Length of the data to sign
* @param[in] application Application identifier (relying party ID)
* @param[in] key_handle Key handle identifying the credential
* @param[in] key_handle_len Length of the key handle
* @param[in] flags Signing flags
* @param[in] pin PIN for user verification (may be NULL)
* @param[in] options Array of signing options (device path, etc.)
* @param[out] sign_response Signature response containing signature data,
* flags, and counter information
*
* @returns SSH_OK on success, SSH_SK_ERR_* codes on failure.
*/
typedef int (*sk_sign_callback)(uint32_t alg,
const uint8_t *data,
size_t data_len,
const char *application,
const uint8_t *key_handle,
size_t key_handle_len,
uint8_t flags,
const char *pin,
struct sk_option **options,
struct sk_sign_response **sign_response);
/**
* @brief FIDO2 security key resident keys loading callback.
*
* Enumerates and loads all resident keys (discoverable credentials) stored
* on FIDO2 devices. Resident keys are credentials stored directly on
* the device itself and can be discovered without prior knowledge
* of key handles.
*
* @param[in] pin PIN for accessing resident keys (required for most operations)
* @param[in] options Array of options (device path, etc.)
* @param[out] resident_keys Array of resident key structures containing key
* data, application IDs, user information, and metadata
* @param[out] num_keys_found Number of resident keys found and loaded
*
* @returns SSH_OK on success, SSH_SK_ERR_* codes on failure.
*/
typedef int (*sk_load_resident_keys_callback)(
const char *pin,
struct sk_option **options,
struct sk_resident_key ***resident_keys,
size_t *num_keys_found);
/**
* @brief FIDO2/U2F security key callbacks structure.
*
* This structure contains callbacks for FIDO2/U2F operations.
* It allows applications to provide custom implementations of FIDO2/U2F
* operations to override the default libfido2-based implementation.
*
* @warning These callbacks will only be called if libssh was built with
* FIDO2/U2F support enabled. (WITH_FIDO2 = ON).
*/
struct ssh_sk_callbacks_struct {
/** DON'T SET THIS use ssh_callbacks_init() instead. */
size_t size;
/**
* This callback returns the SK API version used by the callback
* implementation.
*
* @see sk_api_version_callback for detailed documentation
*/
sk_api_version_callback api_version;
/**
* This callback enrolls a new FIDO2/U2F credential, generating
* a new key pair and optionally storing it on the device itself
* (resident keys).
*
* @see sk_enroll_callback for detailed documentation
*/
sk_enroll_callback enroll;
/**
* This callback performs cryptographic signing operations using a
* previously enrolled FIDO2/U2F credential.
*
* @see sk_sign_callback for detailed documentation
*/
sk_sign_callback sign;
/**
* This callback enumerates and loads all resident keys (discoverable
* credentials) stored on the FIDO2 device.
*
* @see sk_load_resident_keys_callback for detailed documentation
*/
sk_load_resident_keys_callback load_resident_keys;
};
typedef struct ssh_sk_callbacks_struct *ssh_sk_callbacks;
const struct ssh_sk_callbacks_struct *ssh_sk_get_default_callbacks(void);
#ifdef __cplusplus
}
#endif

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 */
@@ -66,7 +67,14 @@ enum ssh_config_opcode_e {
SOC_CONTROLMASTER,
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

@@ -87,6 +87,22 @@ enum ssh_key_exchange_e {
SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM,
/* sntrup761x25519-sha512 */
SSH_KEX_SNTRUP761X25519_SHA512,
/* mlkem768x25519-sha256 */
SSH_KEX_MLKEM768X25519_SHA256,
/* 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 {
@@ -110,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 */
@@ -140,6 +159,14 @@ struct ssh_crypto_struct {
ssh_curve25519_pubkey curve25519_client_pubkey;
ssh_curve25519_pubkey curve25519_server_pubkey;
#endif
#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
@@ -107,6 +107,7 @@ typedef struct ssh_session_struct* ssh_session;
typedef struct ssh_string_struct* ssh_string;
typedef struct ssh_event_struct* ssh_event;
typedef struct ssh_connector_struct * ssh_connector;
typedef struct ssh_pki_ctx_struct *ssh_pki_ctx;
typedef void* ssh_gssapi_creds;
/* Socket type */
@@ -151,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 {
@@ -370,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,
@@ -420,6 +428,10 @@ enum ssh_options_e {
SSH_OPTIONS_CERTIFICATE,
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 {
@@ -721,9 +733,17 @@ LIBSSH_API int ssh_key_cmp(const ssh_key k1,
const ssh_key k2,
enum ssh_keycmp_e what);
LIBSSH_API ssh_key ssh_key_dup(const ssh_key key);
LIBSSH_API uint32_t ssh_key_get_sk_flags(const ssh_key key);
LIBSSH_API ssh_string ssh_key_get_sk_application(const ssh_key key);
LIBSSH_API ssh_string ssh_key_get_sk_user_id(const ssh_key key);
SSH_DEPRECATED LIBSSH_API int
ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey);
LIBSSH_API int ssh_pki_generate_key(enum ssh_keytypes_e type,
ssh_pki_ctx pki_context,
ssh_key *pkey);
LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter,
ssh_key *pkey);
LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key,
const char *passphrase,
ssh_auth_callback auth_fn,
@@ -844,6 +864,7 @@ LIBSSH_API int ssh_string_fill(ssh_string str, const void *data, size_t len);
do { if ((x) != NULL) { ssh_string_free(x); x = NULL; } } while(0)
LIBSSH_API void ssh_string_free(ssh_string str);
LIBSSH_API ssh_string ssh_string_from_char(const char *what);
LIBSSH_API ssh_string ssh_string_from_data(const void *data, size_t len);
LIBSSH_API size_t ssh_string_len(ssh_string str);
LIBSSH_API ssh_string ssh_string_new(size_t size);
LIBSSH_API const char *ssh_string_get_char(ssh_string str);
@@ -901,6 +922,7 @@ enum sshsig_digest_e {
LIBSSH_API int sshsig_sign(const void *data,
size_t data_length,
ssh_key privkey,
ssh_pki_ctx pki_context,
const char *sig_namespace,
enum sshsig_digest_e hash_alg,
char **signature);
@@ -910,6 +932,83 @@ LIBSSH_API int sshsig_verify(const void *data,
const char *sig_namespace,
ssh_key *sign_key);
/* PKI context API */
enum ssh_pki_options_e {
SSH_PKI_OPTION_RSA_KEY_SIZE,
/* Security Key options */
SSH_PKI_OPTION_SK_APPLICATION,
SSH_PKI_OPTION_SK_FLAGS,
SSH_PKI_OPTION_SK_USER_ID,
SSH_PKI_OPTION_SK_CHALLENGE,
SSH_PKI_OPTION_SK_CALLBACKS,
};
/* FIDO2/U2F Operation Flags */
/** Requires user presence confirmation (tap/touch) */
#ifndef SSH_SK_USER_PRESENCE_REQD
#define SSH_SK_USER_PRESENCE_REQD 0x01
#endif
/** Requires user verification (PIN/biometric) - FIDO2 only */
#ifndef SSH_SK_USER_VERIFICATION_REQD
#define SSH_SK_USER_VERIFICATION_REQD 0x04
#endif
/** Force resident key enrollment even if a resident key with given user ID
* already exists - FIDO2 only */
#ifndef SSH_SK_FORCE_OPERATION
#define SSH_SK_FORCE_OPERATION 0x10
#endif
/** Create/use resident key stored on authenticator - FIDO2 only */
#ifndef SSH_SK_RESIDENT_KEY
#define SSH_SK_RESIDENT_KEY 0x20
#endif
LIBSSH_API ssh_pki_ctx ssh_pki_ctx_new(void);
LIBSSH_API int ssh_pki_ctx_options_set(ssh_pki_ctx context,
enum ssh_pki_options_e option,
const void *value);
LIBSSH_API int ssh_pki_ctx_set_sk_pin_callback(ssh_pki_ctx context,
ssh_auth_callback pin_callback,
void *userdata);
#define SSH_SK_OPTION_NAME_DEVICE_PATH "device"
#define SSH_SK_OPTION_NAME_USER_ID "user"
LIBSSH_API int ssh_pki_ctx_sk_callbacks_option_set(ssh_pki_ctx context,
const char *name,
const char *value,
bool required);
LIBSSH_API int ssh_pki_ctx_sk_callbacks_options_clear(ssh_pki_ctx context);
LIBSSH_API int
ssh_pki_ctx_get_sk_attestation_buffer(const struct ssh_pki_ctx_struct *context,
ssh_buffer *attestation_buffer);
LIBSSH_API void ssh_pki_ctx_free(ssh_pki_ctx context);
#define SSH_PKI_CTX_FREE(x) \
do { \
if ((x) != NULL) { \
ssh_pki_ctx_free(x); \
x = NULL; \
} \
} while (0)
/* Security key resident keys API */
LIBSSH_API int
ssh_sk_resident_keys_load(const struct ssh_pki_ctx_struct *pki_context,
ssh_key **resident_keys_result,
size_t *num_keys_found_result);
#ifndef LIBSSH_LEGACY_0_4
#include "libssh/legacy.h"
#endif

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

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

@@ -46,8 +46,9 @@
#define MAX_PUBKEY_SIZE 0x100000 /* 1M */
#define MAX_PRIVKEY_SIZE 0x400000 /* 4M */
#define RSA_MIN_KEY_SIZE 768
#define RSA_DEFAULT_KEY_SIZE 3072
#define RSA_MIN_KEY_SIZE 1024
#define RSA_MIN_FIPS_KEY_SIZE 2048
#define RSA_DEFAULT_KEY_SIZE 3072
#define SSH_KEY_FLAG_EMPTY 0x0
#define SSH_KEY_FLAG_PUBLIC 0x0001
@@ -94,6 +95,9 @@ struct ssh_key_struct {
uint8_t sk_flags;
ssh_string sk_key_handle;
ssh_string sk_reserved;
/* Resident key specific metadata */
ssh_string sk_user_id;
};
struct ssh_signature_struct {

View File

@@ -0,0 +1,103 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 Praneeth Sarode <praneethsarode@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, 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 PKI_CONTEXT_H
#define PKI_CONTEXT_H
#include "libssh/callbacks.h"
#include "libssh/libssh.h"
/**
* @brief Security key context structure
*
* Context structure containing all parameters and callbacks
* needed for FIDO2/U2F security key operations.
*/
struct ssh_pki_ctx_struct {
/** @brief Desired RSA modulus size in bits
*
* Specified size of RSA keys to generate. If set to 0, defaults to 3072
* bits. Must be greater than or equal to 1024, as anything below is
* considered insecure.
*/
int rsa_key_size;
/** @brief Security key callbacks
*
* Provides enroll/sign/load_resident_keys operations.
*/
const struct ssh_sk_callbacks_struct *sk_callbacks;
/** @brief Application identifier string for the security key credential
*
* FIDO2 relying party identifier, typically "ssh:user@hostname" format.
* This is required for all security key operations.
*/
char *sk_application;
/** @brief FIDO2 operation flags
*
* Bitfield controlling authenticator behavior. Combine with bitwise OR:
* - SSH_SK_USER_PRESENCE_REQD (0x01): Require user touch
* - SSH_SK_USER_VERIFICATION_REQD (0x04): Require PIN/biometric
* - SSH_SK_FORCE_OPERATION (0x10): Override duplicate detection
* - SSH_SK_RESIDENT_KEY (0x20): Create discoverable credential
*/
uint8_t sk_flags;
/** @brief PIN callback for authenticator user verification (optional)
*
* Callback invoked to obtain a PIN or perform user verification when
* SSH_SK_USER_VERIFICATION_REQD is set or the authenticator requires it.
* If NULL, no interactive PIN retrieval is performed.
*/
ssh_auth_callback sk_pin_callback;
/** @brief User supplied pointer passed to callbacks (optional)
*
* Generic pointer set by the application and forwarded to
* interactive callbacks (e.g. PIN callback) to allow applications to
* carry state context.
*/
void *sk_userdata;
/** @brief Custom challenge data for enrollment (optional)
*
* Buffer containing challenge data signed by the authenticator.
* If NULL, a random 32-byte challenge is automatically generated.
*/
ssh_buffer sk_challenge_buffer;
/** @brief Options to be passed to the sk_callbacks (optional)
*
* NULL-terminated array of sk_option pointers owned by this context.
*/
struct sk_option **sk_callbacks_options;
/** @brief The buffer used to store attestation information returned in a
* key enrollment operation
*/
ssh_buffer sk_attestation_buffer;
};
/* Internal PKI context functions */
ssh_pki_ctx ssh_pki_ctx_dup(const ssh_pki_ctx context);
#endif /* PKI_CONTEXT_H */

View File

@@ -168,6 +168,11 @@ bool ssh_key_size_allowed_rsa(int min_size, ssh_key key);
/* Security Key Helper Functions */
int pki_buffer_pack_sk_priv_data(ssh_buffer buffer, const ssh_key key);
int pki_buffer_unpack_sk_priv_data(ssh_buffer buffer, ssh_key key);
int pki_sk_signature_buffer_prepare(const ssh_key key,
const ssh_signature sig,
const unsigned char *input,
size_t input_len,
ssh_buffer *sk_buffer_out);
#ifdef __cplusplus
}

90
include/libssh/pki_sk.h Normal file
View File

@@ -0,0 +1,90 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 Praneeth Sarode <praneethsarode@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, 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 PKI_SK_H
#define PKI_SK_H
#include "libssh/libssh.h"
#include "libssh/pki.h"
#include <stdint.h>
#define SSH_SK_MAX_USER_ID_LEN 64
/**
* @brief Enroll a new security key using a U2F/FIDO2 authenticator
*
* Creates a new security key credential configured according to the parameters
* in the PKI context. This function handles key enrollment for both ECDSA and
* Ed25519 algorithms, generates appropriate challenges, and returns the
* enrolled key with optional attestation data.
*
* The PKI context must be configured with appropriate security key parameters
* using ssh_pki_ctx_options_set() before calling this function. Required
* options include SSH_PKI_OPTION_SK_APPLICATION, SSH_PKI_OPTION_SK_USER_ID, and
* SSH_PKI_OPTION_SK_CALLBACKS.
*
* @param[in] context The PKI context containing security key configuration and
* parameters
* @param[in] key_type The type of key to enroll (SSH_KEYTYPE_SK_ECDSA or
* SSH_KEYTYPE_SK_ED25519)
* @param[out] enrolled_key_result Pointer to store the enrolled ssh_key
*
* @return SSH_OK on success, SSH_ERROR on failure
*
* @see ssh_pki_ctx_new()
* @see ssh_pki_ctx_options_set()
* @see ssh_pki_ctx_get_sk_attestation_buffer()
*/
int pki_sk_enroll_key(ssh_pki_ctx context,
enum ssh_keytypes_e key_type,
ssh_key *enrolled_key_result);
/**
* @brief Sign arbitrary data using a security key and a PKI context
*
* This function performs signing operations configured according to the
* parameters in the PKI context and returns a properly formatted
* ssh_signature. The caller must free the signature when it is no longer
* needed.
*
* The PKI context should be configured with appropriate security key parameters
* using ssh_pki_ctx_options_set() before calling this function. The security
* key must have been previously enrolled or loaded.
*
* @param[in] context The PKI context containing security key configuration and
* parameters
* @param[in] key The security key to use for signing
* @param[in] data The data to sign
* @param[in] data_len Length of data to sign
*
* @return A valid ssh_signature on success, NULL on failure
*
* @see ssh_pki_ctx_new()
* @see ssh_pki_ctx_options_set()
* @see pki_sk_enroll_key()
* @see ssh_signature_free()
*/
ssh_signature pki_sk_do_sign(ssh_pki_ctx context,
const ssh_key key,
const uint8_t *data,
size_t data_len);
#endif /* PKI_SK_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 */
@@ -89,6 +90,9 @@ enum ssh_pending_call_e {
#define SSH_SESSION_FLAG_KEX_STRICT 0x0010
/* Unexpected packets have been sent while the session was still unencrypted */
#define SSH_SESSION_FLAG_KEX_TAINTED 0x0020
/* The scp on server can not handle quoted paths. Skip the mitigation for
* CVE-2019-14889 when using scp */
#define SSH_SESSION_FLAG_SCP_QUOTING_BROKEN 0x0040
/* codes to use with ssh_handle_packets*() */
/* Infinite timeout */
@@ -198,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;
@@ -262,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;
@@ -274,6 +282,7 @@ struct ssh_session_struct {
bool identities_only;
int control_master;
char *control_path;
int address_family;
} opts;
/* server options */
@@ -285,6 +294,10 @@ struct ssh_session_struct {
/* counters */
ssh_counter socket_counter;
ssh_counter raw_counter;
/* PKI context structure containing various parameters to configure PKI
* operations */
struct ssh_pki_ctx_struct *pki_context;
};
/** @internal

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;

283
include/libssh/sk_api.h Normal file
View File

@@ -0,0 +1,283 @@
/*
* Copyright (c) 2019 Google LLC
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file is a copy of the OpenSSH project's sk-api.h file pulled from
* https://github.com/openssh/openssh-portable/commit/a9cbe10da2be5be76755af0cea029db0f9c1f263
* with only the flags, algorithms, error codes, and struct definitions. The
* function declarations and other OpenSSH-specific code have been removed.
*/
#ifndef SK_API_H
#define SK_API_H 1
#include <stddef.h>
#include <stdint.h>
/* FIDO2/U2F Operation Flags */
/** Requires user presence confirmation (tap/touch) */
#ifndef SSH_SK_USER_PRESENCE_REQD
#define SSH_SK_USER_PRESENCE_REQD 0x01
#endif
/** Requires user verification (PIN/biometric) - FIDO2 only */
#ifndef SSH_SK_USER_VERIFICATION_REQD
#define SSH_SK_USER_VERIFICATION_REQD 0x04
#endif
/** Force resident key enrollment even if a resident key with given user ID
* already exists - FIDO2 only */
#ifndef SSH_SK_FORCE_OPERATION
#define SSH_SK_FORCE_OPERATION 0x10
#endif
/** Create/use resident key stored on authenticator - FIDO2 only */
#ifndef SSH_SK_RESIDENT_KEY
#define SSH_SK_RESIDENT_KEY 0x20
#endif
/* Algorithms */
/** ECDSA with P-256 curve */
#define SSH_SK_ECDSA 0x00
/** Ed25519 - FIDO2 only */
#define SSH_SK_ED25519 0x01
/* Error codes */
/** General unspecified failure */
#define SSH_SK_ERR_GENERAL -1
/** Requested algorithm/feature/option not supported */
#define SSH_SK_ERR_UNSUPPORTED -2
/** PIN (or other user verification) required but either missing or invalid */
#define SSH_SK_ERR_PIN_REQUIRED -3
/** No suitable security key / authenticator device was found */
#define SSH_SK_ERR_DEVICE_NOT_FOUND -4
/** Attempt to create a resident key that already exists (duplicate) */
#define SSH_SK_ERR_CREDENTIAL_EXISTS -5
/**
* @brief Response structure for FIDO2/U2F key enrollment operations
*
* Contains all data returned by a FIDO2/U2F authenticator after successful
* enrollment of a new credential.
*/
struct sk_enroll_response {
/** @brief FIDO2/U2F authenticator flags from the enrollment operation
*
* Contains flags indicating authenticator capabilities and state during
* enrollment, such as user presence (UP), user verification
* (UV), and resident key.
*/
uint8_t flags;
/** @brief Public key data in standard format
*
* For ECDSA (P-256): 65 bytes in SEC1 uncompressed point format
* (0x04 prefix + 32-byte X coordinate + 32-byte Y coordinate)
* For Ed25519: 32 bytes containing the raw public key (FIDO2 only)
*/
uint8_t *public_key;
/** @brief Length of public_key buffer in bytes
*
* Expected values: 65 for ECDSA P-256, 32 for Ed25519
*/
size_t public_key_len;
/** @brief Opaque credential handle/ID used to identify this key
*
* Authenticator-generated binary data that uniquely identifies this
* credential. Used in subsequent sign operations to specify which
* key to use. Format and contents are authenticator-specific.
*/
uint8_t *key_handle;
/** @brief Length of key_handle buffer in bytes
*
* Length varies by authenticator.
*/
size_t key_handle_len;
/** @brief Enrollment signature over the enrollment data
*
* FIDO2/U2F authenticator signature proving the credential was created
* by this specific authenticator. Used for enrollment verification.
* Format depends on algorithm.
*/
uint8_t *signature;
/** @brief Length of signature buffer in bytes
*
* Length varies by algorithm.
*/
size_t signature_len;
/** @brief X.509 attestation certificate
*
* Certificate that attests to the authenticity of the authenticator
* and the enrollment operation. Used to verify the authenticator's
* identity and manufacturer.
*/
uint8_t *attestation_cert;
/** @brief Length of attestation_cert buffer in bytes */
size_t attestation_cert_len;
/** @brief FIDO2/U2F authenticator data from enrollment
*
* CBOR-encoded authenticator data containing RP ID hash, flags,
* counter, and attested credential data. Used for attestation
* verification according to the FIDO2 specification.
*/
uint8_t *authdata;
/** @brief Length of authdata buffer in bytes
*
* Length varies depending on credential data and extensions.
*/
size_t authdata_len;
};
/**
* @brief Response structure for FIDO2/U2F key signing operations
*
* Contains signature components and metadata returned by a FIDO2/U2F
* authenticator after a successful signing operation.
*/
struct sk_sign_response {
/** @brief FIDO2/U2F authenticator flags from the signing operation
*
* Contains flags indicating authenticator state during signing,
* including user presence (UP) and user verification (UV) flags.
* Used to verify that proper user interaction occurred while signing.
*/
uint8_t flags;
/** @brief Authenticator signature counter value
*
* Monotonically increasing counter maintained by the authenticator.
* Incremented on each successful signing operation. Used to detect
* cloned or duplicated authenticators.
*/
uint32_t counter;
/** @brief R component of ECDSA signature or Ed25519 signature */
uint8_t *sig_r;
/** @brief Length of sig_r buffer in bytes */
size_t sig_r_len;
/** @brief S component of ECDSA signature */
uint8_t *sig_s;
/** @brief Length of sig_s buffer in bytes */
size_t sig_s_len;
};
/**
* @brief Structure representing a resident/discoverable credential
*
* Represents a FIDO2 resident key (discoverable credential) that is
* stored on the authenticator and can be discovered without providing
* a credential ID.
*/
struct sk_resident_key {
/** @brief Cryptographic algorithm identifier for this key
*
* SSH_SK_ECDSA (0x00): ECDSA with P-256 curve
* SSH_SK_ED25519 (0x01): Ed25519 signature algorithm
*/
uint32_t alg;
/** @brief Slot/index number of this key on the authenticator
*
* Zero-based index indicating the position of this resident key
* in the authenticator's internal storage. Used for key management
* and identification when multiple resident keys exist.
*/
size_t slot;
/** @brief Relying Party (application) identifier string
*
* The RP ID (typically a domain name) that this resident key
* is associated with. Determines which application/service
* this key can be used for.
*/
char *application;
/** @brief Embedded enrollment response containing key material
*
* Contains the same data as returned during initial enrollment,
* including public key, key handle, and associated metadata.
*/
struct sk_enroll_response key;
/** @brief Flags associated with this resident key
*
* SSH_SK_USER_PRESENCE_REQD: Requires user presence for operations
* SSH_SK_USER_VERIFICATION_REQD: Requires user verification
* (PIN/biometric)
*/
uint8_t flags;
/** @brief User identifier associated with this resident key
*
* Binary user ID that was provided during key enrollment.
* Used to identify which user account this key belongs to.
*/
uint8_t *user_id;
/** @brief Length of user_id buffer in bytes
*
* Length of the user identifier.
*/
size_t user_id_len;
};
/**
* @brief Configuration option structure for FIDO2/U2F operations
*
* Represents a single configuration parameter that can be passed
* to FIDO2/U2F middleware.
*/
struct sk_option {
/** @brief Option name/key identifier */
char *name;
/** @brief Option value as bytes */
char *value;
/** @brief Indicates if this option is required for the operation
*
* Non-zero if this option must be processed and cannot be ignored.
* Zero if this option is advisory and can be skipped if the
* middleware does not support it.
*/
uint8_t required;
};
/** Current SK API version */
#define SSH_SK_VERSION_MAJOR 0x000a0000
#define SSH_SK_VERSION_MAJOR_MASK 0xffff0000
#endif /* SK_API_H */

213
include/libssh/sk_common.h Normal file
View File

@@ -0,0 +1,213 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 Praneeth Sarode <praneethsarode@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, 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 SK_COMMON_H
#define SK_COMMON_H
#include "libssh/callbacks.h"
#include "libssh/sk_api.h"
#include <stdbool.h>
#define SK_MAX_USER_ID_LEN 64
#define SK_NOT_SUPPORTED_MSG \
"Security Key functionality is not supported in this build of libssh. " \
"Please enable support by building using the WITH_FIDO2 build option."
/**
* @brief Convert security key error code to human-readable string
*
* Converts a security key error code to a descriptive string representation
* that can be used for logging user-facing error messages.
*
* @param[in] sk_err The security key error code to convert.
*
* @return Constant string describing the error. Never returns NULL.
* Returns "Unknown error" for unrecognized error codes.
*
* @note The returned string is statically allocated and should not be freed.
*/
const char *ssh_sk_err_to_string(int sk_err);
/**
* @brief Securely clear the contents of an sk_enroll_response structure
*
* Overwrites sensitive data within the enrollment response structure with
* zeros to prevent information leakage. This function only clears and frees the
* contents and does not free the structure itself.
*
* @param[in] enroll_response The enrollment response structure to clear.
* Can be NULL (no operation performed).
*
* @note This function only frees the memory for the contents and does not free
* memory for the structure itself. Use sk_enroll_response_free() for complete
* cleanup, which also performs secure clearing internally.
*/
void sk_enroll_response_burn(struct sk_enroll_response *enroll_response);
/**
* @brief Securely free an sk_enroll_response structure
*
* Performs secure clearing of sensitive data within the enrollment response
* structure before freeing the allocated memory. This function internally
* calls sk_enroll_response_burn() before deallocation.
*
* @param[in] enroll_response The enrollment response structure to free.
* Can be NULL (no operation performed).
*
* @note Developers do not need to call sk_enroll_response_burn() before
* calling this function, as secure clearing is performed automatically.
*/
void sk_enroll_response_free(struct sk_enroll_response *enroll_response);
/**
* @brief Free an sk_sign_response structure
*
* Frees the memory allocated for a sign response structure and all its
* associated data. This function performs secure clearing of sensitive
* data before deallocation.
*
* @param[in] sign_response The sign response structure to free.
* Can be NULL (no operation performed).
*
* @note This is a secure free operation that clears sensitive data before
* memory deallocation to prevent information leakage.
*/
void sk_sign_response_free(struct sk_sign_response *sign_response);
/**
* @brief Free an sk_resident_key structure
*
* Frees the memory allocated for a resident key structure and all its
* associated data. This function performs secure clearing of sensitive
* data before deallocation.
*
* @param[in] resident_key The resident key structure to free.
* Can be NULL (no operation performed).
*
* @note This is a secure free operation that clears sensitive data before
* memory deallocation to prevent information leakage.
*/
void sk_resident_key_free(struct sk_resident_key *resident_key);
/**
* @brief Free an sk_option array and all its contents
*
* Frees a NULL-terminated array of sk_option structures, including all
* allocated memory for option names and values within each structure.
*
* @param[in] options NULL-terminated array of sk_option pointers to free.
* Can be NULL (no operation performed).
*
* @note The options array must be NULL-terminated for proper freeing.
* Each sk_option structure and its name/value strings will be freed.
*/
void sk_options_free(struct sk_option **options);
/**
* @brief Validate options and extract values for specific keys
*
* Validates that all required options are supported and extracts values
* for the specified keys. This function is primarily intended for use
* by the SK callback implementations.
*
* @param[in] options NULL-terminated array of sk_option pointers to validate.
* @param[in] keys NULL-terminated array of supported option keys.
* @param[out] values Pointer to array that will be allocated and filled with
* copied values (same order as keys). The caller must free
* this array and all contained strings when done.
*
* @return SSH_OK on success, SSH_ERROR if unsupported required options found
* or memory allocation fails.
*
* @note The values array is allocated by this function and contains copies
* of the option values. The caller must free both the array and all
* non-NULL string values within it. Values for keys not found in
* options will be set to NULL.
*/
int sk_options_validate_get(const struct sk_option **options,
const char **keys,
char ***values);
/**
* @brief Duplicate an array of sk_option structures
*
* Creates a deep copy of an array of security key options. Each option
* structure and its string fields are duplicated.
*
* @param[in] options The array of options to duplicate. Must be
* NULL-terminated array of struct sk_option pointers.
* Can be NULL.
*
* @return A newly allocated array of duplicated options on success,
* NULL on failure or if options is NULL.
* The returned array should be freed with SK_OPTIONS_FREE().
*/
struct sk_option **sk_options_dup(const struct sk_option **options);
/**
* @brief Check version compatibility of security key callbacks
*
* Validates that the provided security key callbacks use an SK API
* version whose major portion is the same as the major version that libssh
* supports.
*
* @param[in] callbacks Pointer to the sk_callbacks structure to check.
*
* @return true if the callbacks are compatible, false otherwise.
*/
bool sk_callbacks_check_compatibility(
const struct ssh_sk_callbacks_struct *callbacks);
/* Convenience macros for secure freeing with NULL checks and pointer reset */
#define SK_ENROLL_RESPONSE_FREE(x) \
do { \
if ((x) != NULL) { \
sk_enroll_response_free(x); \
x = NULL; \
} \
} while (0)
#define SK_SIGN_RESPONSE_FREE(x) \
do { \
if ((x) != NULL) { \
sk_sign_response_free(x); \
x = NULL; \
} \
} while (0)
#define SK_RESIDENT_KEY_FREE(x) \
do { \
if ((x) != NULL) { \
sk_resident_key_free(x); \
x = NULL; \
} \
} while (0)
#define SK_OPTIONS_FREE(x) \
do { \
if ((x) != NULL) { \
sk_options_free(x); \
x = NULL; \
} \
} while (0)
#endif /* SK_COMMON_H */

View File

@@ -0,0 +1,37 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2025 Praneeth Sarode <praneethsarode@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, 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 SK_USBHID_H
#define SK_USBHID_H
/**
* @brief Get the USB-HID security key callbacks.
*
* This function returns a pointer to the implementation of
* security key callbacks for FIDO2/U2F devices using the USB-HID
* protocol.
*
* @return Pointer to the ssh_sk_callbacks_struct
*
* @see ssh_sk_callbacks_struct
*/
const struct ssh_sk_callbacks_struct *ssh_sk_get_usbhid_callbacks(void);
#endif /* SK_USBHID_H */

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

@@ -16,14 +16,23 @@
#define SSH2_MSG_KEXDH_REPLY 31
#define SSH2_MSG_KEX_ECDH_INIT 30
#define SSH2_MSG_KEX_ECDH_REPLY 31
#define SSH2_MSG_ECMQV_INIT 30
#define SSH2_MSG_ECMQV_REPLY 31
#define SSH2_MSG_KEX_HYBRID_INIT 30
#define SSH2_MSG_KEX_HYBRID_REPLY 31
#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30
#define SSH2_MSG_KEX_DH_GEX_GROUP 31
#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

@@ -56,6 +56,18 @@ if (MINGW AND Threads_FOUND)
)
endif()
if (HAVE_LIBFIDO2)
set(LIBSSH_PRIVATE_INCLUDE_DIRS
${LIBSSH_PRIVATE_INCLUDE_DIRS}
${LIBFIDO2_INCLUDE_DIR}
)
set(LIBSSH_LINK_LIBRARIES
${LIBSSH_LINK_LIBRARIES}
${LIBFIDO2_LIBRARIES}
)
endif (HAVE_LIBFIDO2)
# The ws2_32 needs to be last for mingw to build
# https://gitlab.com/libssh/libssh-mirror/-/issues/84
if (WIN32)
@@ -93,6 +105,7 @@ set(libssh_SRCS
error.c
getpass.c
gzip.c
hybrid_mlkem.c
init.c
kdf.c
kex.c
@@ -103,12 +116,14 @@ set(libssh_SRCS
match.c
messages.c
misc.c
mlkem.c
options.c
packet.c
packet_cb.c
packet_crypt.c
pcap.c
pki.c
pki_context.c
pki_container_openssh.c
poll.c
session.c
@@ -182,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}
@@ -235,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)
@@ -273,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)
@@ -286,6 +315,34 @@ if (NOT WITH_NACL)
endif()
endif (NOT WITH_NACL)
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
${libssh_SRCS}
sk_common.c
pki_sk.c
)
if (HAVE_LIBFIDO2)
set(libssh_SRCS
${libssh_SRCS}
sk_usbhid.c
)
endif (HAVE_LIBFIDO2)
endif (WITH_FIDO2)
# Set the path to the default map file
set(MAP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.map")

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

@@ -104,6 +104,11 @@ ssh_bind_config_keyword_table[] = {
.opcode = BIND_CFG_HOSTKEY_ALGORITHMS,
.allowed_in_match = true
},
{
.name = "requiredrsasize",
.opcode = BIND_CFG_REQUIRED_RSA_SIZE,
.allowed_in_match = true
},
{
.opcode = BIND_CFG_UNKNOWN,
}
@@ -293,6 +298,7 @@ ssh_bind_config_parse_line(ssh_bind bind,
const char *p = NULL;
char *s = NULL, *x = NULL;
char *keyword = NULL;
long l;
size_t len;
int rc = 0;
@@ -594,6 +600,19 @@ 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 && 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'",
count,
l);
}
}
break;
case BIND_CFG_NOT_ALLOWED_IN_MATCH:
SSH_LOG(SSH_LOG_DEBUG, "Option not allowed in Match block: %s, line: %d",
keyword, count);

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)
@@ -636,11 +636,13 @@ SSH_PACKET_CALLBACK(channel_rcv_data)
SSH_LOG(SSH_LOG_PACKET,
"Channel receiving %" PRIu32 " bytes data%s (local win=%" PRIu32
" remote win=%" PRIu32 ")",
" remote win=%" PRIu32 ") on channel %" PRIu32 ":%" PRIu32,
len,
is_stderr ? " in stderr" : "",
is_stderr ? " in stderr" : "",
channel->local_window,
channel->remote_window);
channel->remote_window,
channel->local_channel,
channel->remote_channel);
if (len > channel->local_window) {
SSH_LOG(SSH_LOG_RARE,
@@ -831,8 +833,10 @@ SSH_PACKET_CALLBACK(channel_rcv_request)
channel->exit.status = true;
SSH_LOG(SSH_LOG_PACKET,
"received exit-status %u",
channel->exit.code);
"received exit-status %u on channel %" PRIu32 ":%" PRIu32,
channel->exit.code,
channel->local_channel,
channel->remote_channel);
ssh_callbacks_execute_list(channel->callbacks,
ssh_channel_callbacks,
@@ -1055,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()
@@ -1080,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()
@@ -1106,7 +1110,6 @@ int ssh_channel_open_auth_agent(ssh_channel channel)
NULL);
}
/**
* @brief Open a TCP/IP forwarding channel.
*
@@ -1123,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)
@@ -1195,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,
@@ -1355,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
@@ -1432,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()
@@ -1520,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)
{
@@ -1715,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()
*/
@@ -1921,7 +1924,10 @@ static int channel_request(ssh_channel channel, const char *request,
}
SSH_LOG(SSH_LOG_PACKET,
"Sent a SSH_MSG_CHANNEL_REQUEST %s", request);
"Sent a SSH_MSG_CHANNEL_REQUEST %s on channel %" PRIu32 ":%" PRIu32,
request,
channel->local_channel,
channel->remote_channel);
if (reply == 0) {
channel->request_state = SSH_CHANNEL_REQ_STATE_NONE;
return SSH_OK;
@@ -1941,13 +1947,20 @@ pending:
rc=SSH_ERROR;
break;
case SSH_CHANNEL_REQ_STATE_DENIED:
ssh_set_error(session, SSH_REQUEST_DENIED,
"Channel request %s failed", request);
ssh_set_error(session,
SSH_REQUEST_DENIED,
"Channel request %s failed on channel %" PRIu32 ":%" PRIu32,
request,
channel->local_channel,
channel->remote_channel);
rc=SSH_ERROR;
break;
case SSH_CHANNEL_REQ_STATE_ACCEPTED:
SSH_LOG(SSH_LOG_DEBUG,
"Channel request %s success",request);
"Channel request %s success on channel %" PRIu32 ":%" PRIu32,
request,
channel->local_channel,
channel->remote_channel);
rc=SSH_OK;
break;
case SSH_CHANNEL_REQ_STATE_PENDING:
@@ -1983,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,
@@ -2041,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)
{
@@ -2063,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()
@@ -2084,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
@@ -2125,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)
@@ -2146,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().
@@ -2196,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.
@@ -2252,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,
@@ -2387,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) {
@@ -2476,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,
@@ -2570,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.
*
@@ -2585,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,
@@ -2694,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,
@@ -2745,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.
*/
@@ -2801,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:
@@ -2866,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.
@@ -2892,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)
{
@@ -2925,7 +2952,6 @@ error:
return rc;
}
/**
* @brief Send a break signal to the server (as described in RFC 4335).
*
@@ -2937,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)
{
@@ -2975,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
@@ -3084,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)
{
@@ -3113,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.
@@ -3224,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()
*/
@@ -3282,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()
*/
@@ -3329,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()
*/
@@ -3441,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!
@@ -3510,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.
@@ -3627,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)
@@ -3826,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)
@@ -3891,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)
@@ -3952,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)
{
@@ -3996,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,6 +47,7 @@
#include "libssh/misc.h"
#include "libssh/pki.h"
#include "libssh/kex.h"
#include "libssh/hybrid_mlkem.h"
#ifndef _WIN32
#ifdef HAVE_PTHREAD
@@ -264,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:
@@ -296,6 +306,13 @@ int dh_handshake(ssh_session session)
rc = ssh_client_sntrup761x25519_init(session);
break;
#endif
case SSH_KEX_MLKEM768X25519_SHA256:
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;
}
@@ -845,6 +862,7 @@ error:
session->opts.fd = SSH_INVALID_SOCKET;
session->session_state = SSH_SESSION_STATE_DISCONNECTED;
session->pending_call_state = SSH_PENDING_CALL_NONE;
session->packet_state = PACKET_STATE_INIT;
while ((it = ssh_list_get_iterator(session->channels)) != NULL) {
ssh_channel_do_free(ssh_iterator_value(ssh_channel, it));
@@ -904,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,100 +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},
{ 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 {
@@ -188,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
@@ -734,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;
@@ -755,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;
}
@@ -780,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 &&
@@ -1050,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:
@@ -1145,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) {
@@ -1193,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);
}
@@ -1200,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;
@@ -1220,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 */
@@ -1228,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) {
@@ -1267,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;
@@ -1288,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) {
@@ -1341,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);
@@ -1354,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) {
@@ -1418,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);
}
@@ -1435,10 +1552,64 @@ 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);
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:
ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d",
opcode);
@@ -1451,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)
@@ -166,12 +228,21 @@ int ssh_connector_set_out_channel(ssh_connector connector,
/* Fallback to default value for invalid flags */
if (!(flags & SSH_CONNECTOR_STDOUT) && !(flags & SSH_CONNECTOR_STDERR)) {
connector->in_flags = SSH_CONNECTOR_STDOUT;
connector->out_flags = SSH_CONNECTOR_STDOUT;
}
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
*
@@ -382,15 +466,13 @@ ssh_connector_fd_out_cb(ssh_connector connector)
*
* @returns 0
*/
static int ssh_connector_fd_cb(ssh_poll_handle p,
static int ssh_connector_fd_cb(UNUSED_PARAM(ssh_poll_handle p),
socket_t fd,
int revents,
void *userdata)
{
ssh_connector connector = userdata;
(void)p;
if (revents & POLLERR) {
ssh_connector_except(connector, fd);
} else if((revents & (POLLIN|POLLHUP)) && fd == connector->in_fd) {
@@ -409,6 +491,10 @@ static int ssh_connector_fd_cb(ssh_poll_handle p,
*
* @brief Callback called when data is received on channel.
*
* @param[in] session The SSH session
*
* @param[in] channel The channel data came from
*
* @param[in] data Pointer to the data
*
* @param[in] len Length of data
@@ -420,7 +506,7 @@ static int ssh_connector_fd_cb(ssh_poll_handle p,
* @returns Amount of data bytes consumed
*/
static int ssh_connector_channel_data_cb(ssh_session session,
ssh_channel channel,
UNUSED_PARAM(ssh_channel channel),
void *data,
uint32_t len,
int is_stderr,
@@ -430,11 +516,11 @@ static int ssh_connector_channel_data_cb(ssh_session session,
int w;
uint32_t window;
(void) session;
(void) channel;
(void) is_stderr;
SSH_LOG(SSH_LOG_TRACE,"connector data on channel");
SSH_LOG(SSH_LOG_TRACE,
"Received data (%" PRIu32 ") on channel (%" PRIu32 ":%" PRIu32 ")",
len,
channel->local_channel,
channel->remote_channel);
if (is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDERR)) {
/* ignore stderr */
@@ -448,6 +534,7 @@ static int ssh_connector_channel_data_cb(ssh_session session,
}
if (connector->out_wontblock) {
SSH_LOG(SSH_LOG_TRACE, "Writing won't block");
if (connector->out_channel != NULL) {
uint32_t window_len;
@@ -496,6 +583,7 @@ static int ssh_connector_channel_data_cb(ssh_session session,
return w;
} else {
SSH_LOG(SSH_LOG_TRACE, "Writing would block: wait?");
connector->in_available = 1;
return 0;
@@ -513,10 +601,11 @@ static int ssh_connector_channel_data_cb(ssh_session session,
*
* @returns Amount of data bytes consumed
*/
static int ssh_connector_channel_write_wontblock_cb(ssh_session session,
ssh_channel channel,
uint32_t bytes,
void *userdata)
static int
ssh_connector_channel_write_wontblock_cb(ssh_session session,
UNUSED_PARAM(ssh_channel channel),
uint32_t bytes,
void *userdata)
{
ssh_connector connector = userdata;
uint8_t buffer[CHUNKSIZE];
@@ -524,7 +613,12 @@ static int ssh_connector_channel_write_wontblock_cb(ssh_session session,
(void) channel;
SSH_LOG(SSH_LOG_TRACE, "Channel write won't block");
SSH_LOG(SSH_LOG_TRACE,
"Write won't block (%" PRIu32 ") on channel (%" PRIu32 ":%" PRIu32 ")",
bytes,
channel->local_channel,
channel->remote_channel);
if (connector->in_available) {
if (connector->in_channel != NULL) {
uint32_t len = MIN(CHUNKSIZE, bytes);
@@ -535,7 +629,7 @@ static int ssh_connector_channel_write_wontblock_cb(ssh_session session,
0);
if (r == SSH_ERROR) {
ssh_connector_except_channel(connector, connector->in_channel);
} else if(r == 0 && ssh_channel_is_eof(connector->in_channel)){
} else if (r == 0 && ssh_channel_is_eof(connector->in_channel)) {
ssh_channel_send_eof(connector->out_channel);
} else if (r > 0) {
w = ssh_channel_write(connector->out_channel, buffer, r);
@@ -606,15 +700,15 @@ int ssh_connector_set_event(ssh_connector connector, ssh_event event)
}
}
if (connector->in_channel != NULL) {
rc = ssh_event_add_session(event,
ssh_channel_get_session(connector->in_channel));
ssh_session session = ssh_channel_get_session(connector->in_channel);
rc = ssh_event_add_session(event, session);
if (rc != SSH_OK)
goto error;
if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0){
connector->in_available = 1;
}
}
if(connector->out_channel != NULL) {
if (connector->out_channel != NULL) {
ssh_session session = ssh_channel_get_session(connector->out_channel);
rc = ssh_event_add_session(event, session);

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;
}

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