[READ-ONLY] Mirror of https://github.com/VibeDevelopers/atheme. Modified fork of atheme IRC Services www.vibetalk.net/
0

Configure Feed

Select the types of activity you want to include in your feed.

Add a Curve25519-based ECDH challenge-response SASL mechanism

Please see NEWS.md for details.

Aaron Jones (Mar 17, 2019, 10:03 PM UTC) 67881d19 f3c3e4e7

+1872 -12
+29 -3
NEWS.md
··· 80 80 SASL 81 81 ---- 82 82 - SASLServ and its modules have been almost entirely re-written 83 - - Add support for SASL SCRAM-SHA logins (see `doc/SASL-SCRAM-SHA`) 84 83 - Advertise SASL mechanism list to UnrealIRCd servers 85 84 - Use a parameter vector to allow an arbitrary number of S2S arguments 86 - - Indicate whether the client is on a plaintext connection or not. This can 87 - be used by user_can_login hooks. 85 + - Indicate whether the client is on a plaintext connection or not. 86 + - This can be used by user_can_login hooks. 87 + - Add support for SASL SCRAM-SHA logins (see `doc/SASL-SCRAM-SHA`) 88 + - Add support for Curve25519 ECDH-based challenge-response logins 89 + - This is a private SASL mechanism that does not have widespread client 90 + support yet, but it is expected to eventually replace the older 91 + ECDSA-NIST256P-CHALLENGE mechanism, due to concerns within the 92 + cryptographic community about the safety of the NIST curves. 93 + - A complete mechanism documentation, including the protocol, and a design 94 + rationale, is located in `modules/saslserv/ecdh-x25519-challenge.c`. This 95 + will enable client authors to integrate the functionality into their IRC 96 + clients. 97 + - A tool, `atheme-ecdh-x25519-tool`, is provided (and is installed into 98 + `bin/` by `make install`) to enable users to generate private keys, obtain 99 + their public keys in base64 format (to pass to `NickServ SET`), encode 100 + keypairs as a QR-Code (should mobile clients end up implementing this 101 + functionality, and users wish to easily transfer their private keys), and 102 + serve as a reference tool to generate server challenges, and responses to 103 + server challenges, for client authors to verify their implementations 104 + against. Note that the QR-Code printing support requires a UTF-8-capable 105 + terminal emulator, with a monospace font supporting Unicode box-drawing 106 + characters. 107 + - If you wish to build and install the tool without building and installing 108 + everything, simply execute the following commands in the source directory: 109 + - ./configure --with-libmowgli=no 110 + - make -C libmowgli-2/ install 111 + - make -C libathemecore/ install 112 + - make -C src/ecdh-x25519-tool/ install 113 + - ~/atheme/bin/atheme-ecdh-x25519-tool -h 88 114 89 115 MemoServ 90 116 --------
+233 -2
configure
··· 660 660 LIBIDN_LIBS 661 661 LIBIDN_CFLAGS 662 662 ECDSA_TOOLS_COND_D 663 + ECDH_X25519_TOOL_COND_D 663 664 LIBCRYPTO_LIBS 664 665 LIBCRYPTO_CFLAGS 665 666 LIBCRYPT_LIBS ··· 10268 10269 rm -f core conftest.err conftest.$ac_objext \ 10269 10270 conftest$ac_exeext conftest.$ac_ext 10270 10271 10271 - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libcrypto has usable elliptic curve key construction and signature verification functions" >&5 10272 - $as_echo_n "checking if libcrypto has usable elliptic curve key construction and signature verification functions... " >&6; } 10272 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libcrypto can provide SASL ECDH-X25519-CHALLENGE" >&5 10273 + $as_echo_n "checking if libcrypto can provide SASL ECDH-X25519-CHALLENGE... " >&6; } 10274 + cat confdefs.h - <<_ACEOF >conftest.$ac_ext 10275 + /* end confdefs.h. */ 10276 + 10277 + 10278 + #ifdef HAVE_STDDEF_H 10279 + # include <stddef.h> 10280 + #endif 10281 + #include <openssl/curve25519.h> 10282 + 10283 + int 10284 + main () 10285 + { 10286 + 10287 + (void) X25519_keypair(NULL, NULL); 10288 + (void) X25519(NULL, NULL, NULL); 10289 + 10290 + ; 10291 + return 0; 10292 + } 10293 + 10294 + _ACEOF 10295 + if ac_fn_c_try_link "$LINENO"; then : 10296 + 10297 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 10298 + $as_echo "yes" >&6; } 10299 + 10300 + $as_echo "#define HAVE_LIBCRYPTO_ECDH_X25519 1" >>confdefs.h 10301 + 10302 + 10303 + 10304 + ECDH_X25519_TOOL_COND_D="ecdh-x25519-tool" 10305 + 10306 + 10307 + 10308 + $as_echo "#define HAVE_ANYLIB_ECDH_X25519 1" >>confdefs.h 10309 + 10310 + 10311 + LIBCRYPTO_USABLE="Yes" 10312 + 10313 + else 10314 + 10315 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 10316 + $as_echo "no" >&6; } 10317 + 10318 + fi 10319 + rm -f core conftest.err conftest.$ac_objext \ 10320 + conftest$ac_exeext conftest.$ac_ext 10321 + 10322 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libcrypto can provide SASL ECDSA-NIST256P-CHALLENGE" >&5 10323 + $as_echo_n "checking if libcrypto can provide SASL ECDSA-NIST256P-CHALLENGE... " >&6; } 10273 10324 cat confdefs.h - <<_ACEOF >conftest.$ac_ext 10274 10325 /* end confdefs.h. */ 10275 10326 ··· 11164 11215 rm -f core conftest.err conftest.$ac_objext \ 11165 11216 conftest$ac_exeext conftest.$ac_ext 11166 11217 11218 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libmbedcrypto can provide SASL ECDH-X25519-CHALLENGE" >&5 11219 + $as_echo_n "checking if libmbedcrypto can provide SASL ECDH-X25519-CHALLENGE... " >&6; } 11220 + cat confdefs.h - <<_ACEOF >conftest.$ac_ext 11221 + /* end confdefs.h. */ 11222 + 11223 + 11224 + #ifdef HAVE_STDDEF_H 11225 + # include <stddef.h> 11226 + #endif 11227 + #ifdef MBEDTLS_CONFIG_FILE 11228 + # include MBEDTLS_CONFIG_FILE 11229 + #else 11230 + # include <mbedtls/config.h> 11231 + #endif 11232 + #include <mbedtls/bignum.h> 11233 + #include <mbedtls/ecdh.h> 11234 + #include <mbedtls/ecp.h> 11235 + #ifndef MBEDTLS_BIGNUM_C 11236 + # error "MBEDTLS_BIGNUM_C is not enabled" 11237 + #endif 11238 + #ifndef MBEDTLS_ECDH_C 11239 + # error "MBEDTLS_ECDH_C is not enabled" 11240 + #endif 11241 + #ifndef MBEDTLS_ECP_C 11242 + # error "MBEDTLS_ECP_C is not enabled" 11243 + #endif 11244 + 11245 + int 11246 + main () 11247 + { 11248 + 11249 + (void) mbedtls_ecdh_compute_shared(NULL, NULL, NULL, NULL, NULL, NULL); 11250 + (void) mbedtls_ecdh_gen_public(NULL, NULL, NULL, NULL, NULL); 11251 + (void) mbedtls_ecp_check_pubkey(NULL, NULL); 11252 + (void) mbedtls_ecp_group_free(NULL); 11253 + (void) mbedtls_ecp_group_init(NULL); 11254 + (void) mbedtls_ecp_group_load(NULL, 0); 11255 + (void) mbedtls_ecp_point_free(NULL); 11256 + (void) mbedtls_ecp_point_init(NULL); 11257 + (void) mbedtls_mpi_free(NULL); 11258 + (void) mbedtls_mpi_init(NULL); 11259 + (void) mbedtls_mpi_lset(NULL, 0); 11260 + (void) mbedtls_mpi_read_binary(NULL, NULL, 0); 11261 + (void) mbedtls_mpi_write_binary(NULL, NULL, 0); 11262 + 11263 + ; 11264 + return 0; 11265 + } 11266 + 11267 + _ACEOF 11268 + if ac_fn_c_try_link "$LINENO"; then : 11269 + 11270 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 11271 + $as_echo "yes" >&6; } 11272 + 11273 + $as_echo "#define HAVE_LIBMBEDCRYPTO_ECDH_X25519 1" >>confdefs.h 11274 + 11275 + 11276 + 11277 + ECDH_X25519_TOOL_COND_D="ecdh-x25519-tool" 11278 + 11279 + 11280 + 11281 + $as_echo "#define HAVE_ANYLIB_ECDH_X25519 1" >>confdefs.h 11282 + 11283 + 11284 + LIBMBEDCRYPTO_USABLE="Yes" 11285 + 11286 + else 11287 + 11288 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 11289 + $as_echo "no" >&6; } 11290 + 11291 + fi 11292 + rm -f core conftest.err conftest.$ac_objext \ 11293 + conftest$ac_exeext conftest.$ac_ext 11294 + 11167 11295 fi 11168 11296 11169 11297 if test "${LIBMBEDCRYPTO_USABLE}" = "Yes"; then : ··· 11402 11530 $as_echo "yes" >&6; } 11403 11531 11404 11532 $as_echo "#define HAVE_LIBNETTLE_MEMEQL 1" >>confdefs.h 11533 + 11534 + LIBNETTLE_USABLE="Yes" 11535 + 11536 + else 11537 + 11538 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 11539 + $as_echo "no" >&6; } 11540 + 11541 + fi 11542 + rm -f core conftest.err conftest.$ac_objext \ 11543 + conftest$ac_exeext conftest.$ac_ext 11544 + 11545 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libnettle can provide SASL ECDH-X25519-CHALLENGE" >&5 11546 + $as_echo_n "checking if libnettle can provide SASL ECDH-X25519-CHALLENGE... " >&6; } 11547 + cat confdefs.h - <<_ACEOF >conftest.$ac_ext 11548 + /* end confdefs.h. */ 11549 + 11550 + 11551 + #ifdef HAVE_STDDEF_H 11552 + # include <stddef.h> 11553 + #endif 11554 + #include <nettle/curve25519.h> 11555 + #ifndef NETTLE_CURVE25519_RFC7748 11556 + # error "NETTLE_CURVE25519_RFC7748 is not set" 11557 + #endif 11558 + 11559 + int 11560 + main () 11561 + { 11562 + 11563 + (void) nettle_curve25519_mul_g(NULL, NULL); 11564 + (void) nettle_curve25519_mul(NULL, NULL, NULL); 11565 + 11566 + ; 11567 + return 0; 11568 + } 11569 + 11570 + _ACEOF 11571 + if ac_fn_c_try_link "$LINENO"; then : 11572 + 11573 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 11574 + $as_echo "yes" >&6; } 11575 + 11576 + $as_echo "#define HAVE_LIBNETTLE_ECDH_X25519 1" >>confdefs.h 11577 + 11578 + 11579 + 11580 + ECDH_X25519_TOOL_COND_D="ecdh-x25519-tool" 11581 + 11582 + 11583 + 11584 + $as_echo "#define HAVE_ANYLIB_ECDH_X25519 1" >>confdefs.h 11585 + 11405 11586 11406 11587 LIBNETTLE_USABLE="Yes" 11407 11588 ··· 12424 12605 $as_echo "yes" >&6; } 12425 12606 12426 12607 $as_echo "#define HAVE_LIBSODIUM_SCRYPT 1" >>confdefs.h 12608 + 12609 + LIBSODIUM_USABLE="Yes" 12610 + 12611 + else 12612 + 12613 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 12614 + $as_echo "no" >&6; } 12615 + 12616 + fi 12617 + rm -f core conftest.err conftest.$ac_objext \ 12618 + conftest$ac_exeext conftest.$ac_ext 12619 + 12620 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libsodium can provide SASL ECDH-X25519-CHALLENGE" >&5 12621 + $as_echo_n "checking if libsodium can provide SASL ECDH-X25519-CHALLENGE... " >&6; } 12622 + cat confdefs.h - <<_ACEOF >conftest.$ac_ext 12623 + /* end confdefs.h. */ 12624 + 12625 + 12626 + #ifdef HAVE_STDDEF_H 12627 + # include <stddef.h> 12628 + #endif 12629 + #include <sodium/crypto_scalarmult_curve25519.h> 12630 + 12631 + int 12632 + main () 12633 + { 12634 + 12635 + (void) crypto_scalarmult_curve25519_base(NULL, NULL); 12636 + (void) crypto_scalarmult_curve25519(NULL, NULL, NULL); 12637 + 12638 + ; 12639 + return 0; 12640 + } 12641 + 12642 + _ACEOF 12643 + if ac_fn_c_try_link "$LINENO"; then : 12644 + 12645 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 12646 + $as_echo "yes" >&6; } 12647 + 12648 + $as_echo "#define HAVE_LIBSODIUM_ECDH_X25519 1" >>confdefs.h 12649 + 12650 + 12651 + 12652 + ECDH_X25519_TOOL_COND_D="ecdh-x25519-tool" 12653 + 12654 + 12655 + 12656 + $as_echo "#define HAVE_ANYLIB_ECDH_X25519 1" >>confdefs.h 12657 + 12427 12658 12428 12659 LIBSODIUM_USABLE="Yes" 12429 12660
+8
dist/atheme.conf.example
··· 591 591 * 592 592 * Core components modules/saslserv/main 593 593 * AUTHCOOKIE mechanism (for IRIS) modules/saslserv/authcookie 594 + * ECDH-X25519-CHALLENGE mechanism modules/saslserv/ecdh-x25519-challenge 594 595 * ECDSA-NIST256P-CHALLENGE mechanism modules/saslserv/ecdsa-nist256p-challenge 595 596 * EXTERNAL mechanism (IRCv3.1+) modules/saslserv/external 596 597 * PLAIN mechanism modules/saslserv/plain 597 598 * SCRAM-SHA-* mechanisms modules/saslserv/scram-sha 598 599 * 600 + * ECDH-X25519-CHALLENGE support requires that Atheme be compiled against a 601 + * cryptographic library that provides X25519 ECDH support (BoringSSL, 602 + * LibreSSL, ARM mbedTLS, Nettle, Sodium). This will be checked while running 603 + * ./configure. 604 + * 599 605 * ECDSA-NIST256P-CHALLENGE support requires that Atheme be compiled against 600 606 * an OpenSSL with ECDSA support (not RHEL etc. unless you compile your own). 607 + * This will be checked while running ./configure. 601 608 * 602 609 * You MUST read doc/SASL-SCRAM-SHA before loading modules/saslserv/scram-sha! 603 610 */ 604 611 loadmodule "modules/saslserv/main"; 605 612 loadmodule "modules/saslserv/authcookie"; 613 + #loadmodule "modules/saslserv/ecdh-x25519-challenge"; 606 614 #loadmodule "modules/saslserv/ecdsa-nist256p-challenge"; 607 615 #loadmodule "modules/saslserv/external"; 608 616 loadmodule "modules/saslserv/plain";
+1
extra.mk.in
··· 93 93 94 94 # Conditionally-Compiled Directories 95 95 CONTRIB_COND_D ?= @CONTRIB_COND_D@ 96 + ECDH_X25519_TOOL_COND_D ?= @ECDH_X25519_TOOL_COND_D@ 96 97 ECDSA_TOOLS_COND_D ?= @ECDSA_TOOLS_COND_D@ 97 98 LIBMOWGLI_COND_D ?= @LIBMOWGLI_COND_D@ 98 99 PERL_COND_D ?= @PERL_COND_D@
+10
help/default/nickserv/set_x25519_pubkey
··· 1 + Help for SET X25519-PUBKEY: 2 + 3 + SET X25519-PUBKEY allows you to change your 4 + public key used for SASL ECDH-X25519-CHALLENGE 5 + authentication. 6 + 7 + If no pubkey is given, your public key will 8 + be removed from the services database. 9 + 10 + Syntax: SET X25519-PUBKEY [<pubkey>]
+16 -2
include/atheme/sysconf.h.in
··· 50 50 language is requested. */ 51 51 #undef ENABLE_NLS 52 52 53 + /* Define to 1 if any library can provide ECDH-X25519-CHALLENGE */ 54 + #undef HAVE_ANYLIB_ECDH_X25519 55 + 53 56 /* Define to 1 if any password quality library appears to be usable */ 54 57 #undef HAVE_ANY_PASSWORD_QUALITY_LIBRARY 55 58 ··· 140 143 /* Define to 1 if libcrypto appears to be usable */ 141 144 #undef HAVE_LIBCRYPTO 142 145 143 - /* Define to 1 if libcrypto has usable elliptic curve key construction and 144 - signature verification functions */ 146 + /* Define to 1 if libcrypto can provide SASL ECDH-X25519-CHALLENGE */ 147 + #undef HAVE_LIBCRYPTO_ECDH_X25519 148 + 149 + /* Define to 1 if libcrypto can provide SASL ECDSA-NIST256P-CHALLENGE */ 145 150 #undef HAVE_LIBCRYPTO_ECDSA 146 151 147 152 /* Define to 1 if libcrypto has HMAC_CTX_new() & HMAC_CTX_free() */ ··· 166 171 /* Define to 1 if libmbedcrypto has usable CTR-DRBG functions */ 167 172 #undef HAVE_LIBMBEDCRYPTO_CTR_DRBG 168 173 174 + /* Define to 1 if libmbedcrypto can provide SASL ECDH-X25519-CHALLENGE */ 175 + #undef HAVE_LIBMBEDCRYPTO_ECDH_X25519 176 + 169 177 /* Define to 1 if libmbedcrypto has usable HMAC-DRBG functions */ 170 178 #undef HAVE_LIBMBEDCRYPTO_HMAC_DRBG 171 179 172 180 /* Define to 1 if libnettle appears to be usable */ 173 181 #undef HAVE_LIBNETTLE 182 + 183 + /* Define to 1 if libnettle can provide SASL ECDH-X25519-CHALLENGE */ 184 + #undef HAVE_LIBNETTLE_ECDH_X25519 174 185 175 186 /* Define to 1 if libnettle has a usable constant-time memory comparison 176 187 function */ ··· 187 198 188 199 /* Define to 1 if libsodium appears to be usable */ 189 200 #undef HAVE_LIBSODIUM 201 + 202 + /* Define to 1 if libsodium can provide SASL ECDH-X25519-CHALLENGE */ 203 + #undef HAVE_LIBSODIUM_ECDH_X25519 190 204 191 205 /* Define to 1 if libsodium has a usable constant-time memory comparison 192 206 function */
+8
m4/atheme-conditional-compilation.m4
··· 4 4 AC_SUBST([CONTRIB_COND_D]) 5 5 ]) 6 6 7 + AC_DEFUN([ATHEME_COND_ECDH_X25519_TOOL_ENABLE], [ 8 + 9 + ECDH_X25519_TOOL_COND_D="ecdh-x25519-tool" 10 + AC_SUBST([ECDH_X25519_TOOL_COND_D]) 11 + 12 + AC_DEFINE([HAVE_ANYLIB_ECDH_X25519], [1], [Define to 1 if any library can provide ECDH-X25519-CHALLENGE]) 13 + ]) 14 + 7 15 AC_DEFUN([ATHEME_COND_ECDSA_TOOLS_ENABLE], [ 8 16 9 17 ECDSA_TOOLS_COND_D="ecdsadecode ecdsakeygen ecdsasign"
+22 -2
m4/atheme-libtest-crypto.m4
··· 132 132 AC_MSG_RESULT([no]) 133 133 ]) 134 134 135 - AC_MSG_CHECKING([if libcrypto has usable elliptic curve key construction and signature verification functions]) 135 + AC_MSG_CHECKING([if libcrypto can provide SASL ECDH-X25519-CHALLENGE]) 136 + AC_LINK_IFELSE([ 137 + AC_LANG_PROGRAM([[ 138 + #ifdef HAVE_STDDEF_H 139 + # include <stddef.h> 140 + #endif 141 + #include <openssl/curve25519.h> 142 + ]], [[ 143 + (void) X25519_keypair(NULL, NULL); 144 + (void) X25519(NULL, NULL, NULL); 145 + ]]) 146 + ], [ 147 + AC_MSG_RESULT([yes]) 148 + AC_DEFINE([HAVE_LIBCRYPTO_ECDH_X25519], [1], [Define to 1 if libcrypto can provide SASL ECDH-X25519-CHALLENGE]) 149 + ATHEME_COND_ECDH_X25519_TOOL_ENABLE 150 + LIBCRYPTO_USABLE="Yes" 151 + ], [ 152 + AC_MSG_RESULT([no]) 153 + ]) 154 + 155 + AC_MSG_CHECKING([if libcrypto can provide SASL ECDSA-NIST256P-CHALLENGE]) 136 156 AC_LINK_IFELSE([ 137 157 AC_LANG_PROGRAM([[ 138 158 #ifdef HAVE_STDDEF_H ··· 149 169 ]]) 150 170 ], [ 151 171 AC_MSG_RESULT([yes]) 152 - AC_DEFINE([HAVE_LIBCRYPTO_ECDSA], [1], [Define to 1 if libcrypto has usable elliptic curve key construction and signature verification functions]) 172 + AC_DEFINE([HAVE_LIBCRYPTO_ECDSA], [1], [Define to 1 if libcrypto can provide SASL ECDSA-NIST256P-CHALLENGE]) 153 173 ATHEME_COND_ECDSA_TOOLS_ENABLE 154 174 LIBCRYPTO_USABLE="Yes" 155 175 ], [
+47
m4/atheme-libtest-mbedcrypto.m4
··· 213 213 ], [ 214 214 AC_MSG_RESULT([no]) 215 215 ]) 216 + 217 + AC_MSG_CHECKING([if libmbedcrypto can provide SASL ECDH-X25519-CHALLENGE]) 218 + AC_LINK_IFELSE([ 219 + AC_LANG_PROGRAM([[ 220 + #ifdef HAVE_STDDEF_H 221 + # include <stddef.h> 222 + #endif 223 + #ifdef MBEDTLS_CONFIG_FILE 224 + # include MBEDTLS_CONFIG_FILE 225 + #else 226 + # include <mbedtls/config.h> 227 + #endif 228 + #include <mbedtls/bignum.h> 229 + #include <mbedtls/ecdh.h> 230 + #include <mbedtls/ecp.h> 231 + #ifndef MBEDTLS_BIGNUM_C 232 + # error "MBEDTLS_BIGNUM_C is not enabled" 233 + #endif 234 + #ifndef MBEDTLS_ECDH_C 235 + # error "MBEDTLS_ECDH_C is not enabled" 236 + #endif 237 + #ifndef MBEDTLS_ECP_C 238 + # error "MBEDTLS_ECP_C is not enabled" 239 + #endif 240 + ]], [[ 241 + (void) mbedtls_ecdh_compute_shared(NULL, NULL, NULL, NULL, NULL, NULL); 242 + (void) mbedtls_ecdh_gen_public(NULL, NULL, NULL, NULL, NULL); 243 + (void) mbedtls_ecp_check_pubkey(NULL, NULL); 244 + (void) mbedtls_ecp_group_free(NULL); 245 + (void) mbedtls_ecp_group_init(NULL); 246 + (void) mbedtls_ecp_group_load(NULL, 0); 247 + (void) mbedtls_ecp_point_free(NULL); 248 + (void) mbedtls_ecp_point_init(NULL); 249 + (void) mbedtls_mpi_free(NULL); 250 + (void) mbedtls_mpi_init(NULL); 251 + (void) mbedtls_mpi_lset(NULL, 0); 252 + (void) mbedtls_mpi_read_binary(NULL, NULL, 0); 253 + (void) mbedtls_mpi_write_binary(NULL, NULL, 0); 254 + ]]) 255 + ], [ 256 + AC_MSG_RESULT([yes]) 257 + AC_DEFINE([HAVE_LIBMBEDCRYPTO_ECDH_X25519], [1], [Define to 1 if libmbedcrypto can provide SASL ECDH-X25519-CHALLENGE]) 258 + ATHEME_COND_ECDH_X25519_TOOL_ENABLE 259 + LIBMBEDCRYPTO_USABLE="Yes" 260 + ], [ 261 + AC_MSG_RESULT([no]) 262 + ]) 216 263 ]) 217 264 218 265 AS_IF([test "${LIBMBEDCRYPTO_USABLE}" = "Yes"], [
+23
m4/atheme-libtest-nettle.m4
··· 86 86 ], [ 87 87 AC_MSG_RESULT([no]) 88 88 ]) 89 + 90 + AC_MSG_CHECKING([if libnettle can provide SASL ECDH-X25519-CHALLENGE]) 91 + AC_LINK_IFELSE([ 92 + AC_LANG_PROGRAM([[ 93 + #ifdef HAVE_STDDEF_H 94 + # include <stddef.h> 95 + #endif 96 + #include <nettle/curve25519.h> 97 + #ifndef NETTLE_CURVE25519_RFC7748 98 + # error "NETTLE_CURVE25519_RFC7748 is not set" 99 + #endif 100 + ]], [[ 101 + (void) nettle_curve25519_mul_g(NULL, NULL); 102 + (void) nettle_curve25519_mul(NULL, NULL, NULL); 103 + ]]) 104 + ], [ 105 + AC_MSG_RESULT([yes]) 106 + AC_DEFINE([HAVE_LIBNETTLE_ECDH_X25519], [1], [Define to 1 if libnettle can provide SASL ECDH-X25519-CHALLENGE]) 107 + ATHEME_COND_ECDH_X25519_TOOL_ENABLE 108 + LIBNETTLE_USABLE="Yes" 109 + ], [ 110 + AC_MSG_RESULT([no]) 111 + ]) 89 112 ]) 90 113 91 114 AS_IF([test "${LIBNETTLE_USABLE}" = "Yes"], [
+20
m4/atheme-libtest-sodium.m4
··· 158 158 ], [ 159 159 AC_MSG_RESULT([no]) 160 160 ]) 161 + 162 + AC_MSG_CHECKING([if libsodium can provide SASL ECDH-X25519-CHALLENGE]) 163 + AC_LINK_IFELSE([ 164 + AC_LANG_PROGRAM([[ 165 + #ifdef HAVE_STDDEF_H 166 + # include <stddef.h> 167 + #endif 168 + #include <sodium/crypto_scalarmult_curve25519.h> 169 + ]], [[ 170 + (void) crypto_scalarmult_curve25519_base(NULL, NULL); 171 + (void) crypto_scalarmult_curve25519(NULL, NULL, NULL); 172 + ]]) 173 + ], [ 174 + AC_MSG_RESULT([yes]) 175 + AC_DEFINE([HAVE_LIBSODIUM_ECDH_X25519], [1], [Define to 1 if libsodium can provide SASL ECDH-X25519-CHALLENGE]) 176 + ATHEME_COND_ECDH_X25519_TOOL_ENABLE 177 + LIBSODIUM_USABLE="Yes" 178 + ], [ 179 + AC_MSG_RESULT([no]) 180 + ]) 161 181 ]) 162 182 163 183 AS_IF([test "${LIBSODIUM_USABLE}" = "Yes"], [
+3 -2
modules/saslserv/Makefile
··· 7 7 8 8 SRCS = \ 9 9 authcookie.c \ 10 + ecdh-x25519-challenge.c \ 10 11 ecdsa-nist256p-challenge.c \ 11 12 external.c \ 12 13 main.c \ ··· 19 20 20 21 CPPFLAGS += -I../../include 21 22 LDFLAGS += -L../../libathemecore 22 - CFLAGS += $(LIBCRYPTO_CFLAGS) 23 - LIBS += $(LIBCRYPTO_LIBS) -lathemecore 23 + CFLAGS += $(LIBCRYPTO_CFLAGS) $(LIBNETTLE_CFLAGS) $(LIBSODIUM_CFLAGS) 24 + LIBS += $(LIBCRYPTO_LIBS) $(LIBMBEDCRYPTO_LIBS) $(LIBNETTLE_LIBS) $(LIBSODIUM_LIBS) -lathemecore
+388
modules/saslserv/ecdh-x25519-challenge-shared.c
··· 1 + /* 2 + * SPDX-License-Identifier: ISC 3 + * SPDX-URL: https://spdx.org/licenses/ISC.html 4 + * 5 + * Copyright (C) 2019 Aaron M. D. Jones <aaronmdjones@gmail.com> 6 + * 7 + * ECDH-X25519-CHALLENGE mechanism shared routines. 8 + */ 9 + 10 + #ifndef HAVE_ANYLIB_ECDH_X25519 11 + # error "Do not compile me directly; compile ecdh-x25519-challenge.c instead" 12 + #endif 13 + 14 + #include "ecdh-x25519-challenge.h" 15 + 16 + #ifdef HAVE_LIBSODIUM_ECDH_X25519 17 + # define ECDH_X25519_USE_LIBSODIUM 1 18 + #else /* HAVE_LIBSODIUM_ECDH_X25519 */ 19 + # ifdef HAVE_LIBNETTLE_ECDH_X25519 20 + # define ECDH_X25519_USE_LIBNETTLE 1 21 + # else /* HAVE_LIBNETTLE_ECDH_X25519 */ 22 + # ifdef HAVE_LIBCRYPTO_ECDH_X25519 23 + # define ECDH_X25519_USE_LIBCRYPTO 1 24 + # else /* HAVE_LIBCRYPTO_ECDH_X25519 */ 25 + # ifdef HAVE_LIBMBEDCRYPTO_ECDH_X25519 26 + # define ECDH_X25519_USE_LIBMBEDCRYPTO 1 27 + # else /* HAVE_LIBMBEDCRYPTO_ECDH_X25519 */ 28 + # error "No ECDH X25519 cryptographic library implementation is available" 29 + # endif /* !HAVE_LIBMBEDCRYPTO_ECDH_X25519 */ 30 + # endif /* !HAVE_LIBCRYPTO_ECDH_X25519 */ 31 + # endif /* !HAVE_LIBNETTLE_ECDH_X25519 */ 32 + #endif /* !HAVE_LIBSODIUM_ECDH_X25519 */ 33 + 34 + static const unsigned char ecdh_x25519_zeroes[] = { 35 + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 36 + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 37 + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 38 + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 39 + }; 40 + 41 + 42 + 43 + #ifdef ECDH_X25519_LOG_TO_STDERR 44 + 45 + static void ATHEME_FATTR_PRINTF(2, 3) 46 + ecdh_x25519_log_error(const unsigned int ATHEME_VATTR_UNUSED loglevel, const char *const restrict format, ...) 47 + { 48 + va_list ap; 49 + va_start(ap, format); 50 + (void) vfprintf(stderr, format, ap); 51 + (void) fprintf(stderr, "\n"); 52 + va_end(ap); 53 + } 54 + 55 + #else /* ECDH_X25519_LOG_TO_STDERR */ 56 + 57 + static void ATHEME_FATTR_PRINTF(2, 3) 58 + ecdh_x25519_log_error(const unsigned int loglevel, const char *const restrict format, ...) 59 + { 60 + char message[BUFSIZE]; 61 + va_list ap; 62 + va_start(ap, format); 63 + (void) vsnprintf(message, sizeof message, format, ap); 64 + (void) slog(loglevel, "%s", message); 65 + va_end(ap); 66 + } 67 + 68 + #endif /* !ECDH_X25519_LOG_TO_STDERR */ 69 + 70 + 71 + 72 + #ifdef ECDH_X25519_USE_LIBSODIUM 73 + 74 + #include <sodium/crypto_scalarmult_curve25519.h> 75 + 76 + static bool ATHEME_FATTR_WUR 77 + ecdh_x25519_create_keypair(unsigned char seckey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 78 + unsigned char pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN]) 79 + { 80 + (void) atheme_random_buf(seckey, ATHEME_ECDH_X25519_XKEY_LEN); 81 + 82 + // Clamp the secret key 83 + seckey[0x00U] &= 0xF8U; 84 + seckey[0x1FU] &= 0x7FU; 85 + seckey[0x1FU] |= 0x40U; 86 + 87 + if (crypto_scalarmult_curve25519_base(pubkey, seckey) != 0) 88 + { 89 + (void) ecdh_x25519_log_error(LG_ERROR, "%s: crypto_scalarmult_curve25519_base() failed (BUG?)", 90 + MOWGLI_FUNC_NAME); 91 + return false; 92 + } 93 + 94 + return true; 95 + } 96 + 97 + static bool ATHEME_FATTR_WUR 98 + ecdh_x25519_compute_shared(const unsigned char seckey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 99 + const unsigned char pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 100 + unsigned char shared_secret[const restrict static ATHEME_ECDH_X25519_XKEY_LEN]) 101 + { 102 + if (crypto_scalarmult_curve25519(shared_secret, seckey, pubkey) != 0) 103 + { 104 + (void) ecdh_x25519_log_error(LG_ERROR, "%s: crypto_scalarmult_curve25519() failed (BUG?)", 105 + MOWGLI_FUNC_NAME); 106 + return false; 107 + } 108 + if (smemcmp(shared_secret, ecdh_x25519_zeroes, sizeof ecdh_x25519_zeroes) == 0) 109 + { 110 + (void) ecdh_x25519_log_error(LG_DEBUG, "%s: secret is all zeroes (bad pubkey?)", MOWGLI_FUNC_NAME); 111 + return false; 112 + } 113 + 114 + return true; 115 + } 116 + 117 + #endif /* ECDH_X25519_USE_LIBSODIUM */ 118 + 119 + 120 + 121 + #ifdef ECDH_X25519_USE_LIBNETTLE 122 + 123 + #include <nettle/curve25519.h> 124 + 125 + static bool ATHEME_FATTR_WUR 126 + ecdh_x25519_create_keypair(unsigned char seckey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 127 + unsigned char pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN]) 128 + { 129 + (void) atheme_random_buf(seckey, ATHEME_ECDH_X25519_XKEY_LEN); 130 + 131 + // Clamp the secret key 132 + seckey[0x00U] &= 0xF8U; 133 + seckey[0x1FU] &= 0x7FU; 134 + seckey[0x1FU] |= 0x40U; 135 + 136 + (void) nettle_curve25519_mul_g(pubkey, seckey); 137 + return true; 138 + } 139 + 140 + static bool ATHEME_FATTR_WUR 141 + ecdh_x25519_compute_shared(const unsigned char seckey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 142 + const unsigned char pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 143 + unsigned char shared_secret[const restrict static ATHEME_ECDH_X25519_XKEY_LEN]) 144 + { 145 + (void) nettle_curve25519_mul(shared_secret, seckey, pubkey); 146 + 147 + if (smemcmp(shared_secret, ecdh_x25519_zeroes, sizeof ecdh_x25519_zeroes) == 0) 148 + { 149 + (void) ecdh_x25519_log_error(LG_DEBUG, "%s: secret is all zeroes (bad pubkey?)", MOWGLI_FUNC_NAME); 150 + return false; 151 + } 152 + 153 + return true; 154 + } 155 + 156 + #endif /* ECDH_X25519_USE_LIBNETTLE */ 157 + 158 + 159 + 160 + #ifdef ECDH_X25519_USE_LIBCRYPTO 161 + 162 + #include <openssl/curve25519.h> 163 + 164 + static bool ATHEME_FATTR_WUR 165 + ecdh_x25519_create_keypair(unsigned char seckey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 166 + unsigned char pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN]) 167 + { 168 + (void) X25519_keypair(pubkey, seckey); 169 + return true; 170 + } 171 + 172 + static bool ATHEME_FATTR_WUR 173 + ecdh_x25519_compute_shared(const unsigned char seckey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 174 + const unsigned char pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 175 + unsigned char shared_secret[const restrict static ATHEME_ECDH_X25519_XKEY_LEN]) 176 + { 177 + if (X25519(shared_secret, seckey, pubkey) != 1) 178 + { 179 + (void) ecdh_x25519_log_error(LG_ERROR, "%s: X25519() failed (BUG?)", MOWGLI_FUNC_NAME); 180 + return false; 181 + } 182 + if (smemcmp(shared_secret, ecdh_x25519_zeroes, sizeof ecdh_x25519_zeroes) == 0) 183 + { 184 + (void) ecdh_x25519_log_error(LG_DEBUG, "%s: secret is all zeroes (bad pubkey?)", MOWGLI_FUNC_NAME); 185 + return false; 186 + } 187 + 188 + return true; 189 + } 190 + 191 + #endif /* ECDH_X25519_USE_LIBCRYPTO */ 192 + 193 + 194 + 195 + #ifdef ECDH_X25519_USE_LIBMBEDCRYPTO 196 + 197 + #include <mbedtls/bignum.h> 198 + #include <mbedtls/ecdh.h> 199 + #include <mbedtls/ecp.h> 200 + 201 + #define X25519_MBEDTLS_CHK(loglevel, cond) \ 202 + do { \ 203 + if ((ret = (cond)) != 0) { \ 204 + (void) ecdh_x25519_log_error(loglevel, "%s: %s: -0x%08X", MOWGLI_FUNC_NAME, #cond, (unsigned int) -ret); \ 205 + goto cleanup; \ 206 + } \ 207 + } while (0) 208 + 209 + #define X25519_MBEDTLS_CHK_D(cond) X25519_MBEDTLS_CHK(LG_DEBUG, cond) 210 + #define X25519_MBEDTLS_CHK_E(cond) X25519_MBEDTLS_CHK(LG_ERROR, cond) 211 + 212 + // This provides a random number generator function with a function signature that ARM mbedTLS requires 213 + static int 214 + ecdh_x25519_rng_func(void ATHEME_VATTR_UNUSED *const restrict p_rng, 215 + unsigned char *const restrict buf, const size_t len) 216 + { 217 + (void) atheme_random_buf(buf, len); 218 + return 0; 219 + } 220 + 221 + /* The canonical representation for Curve25519 private keys, public keys, and shared secrets, 222 + * is little-endian; but libmbedcrypto's MPI subsystem reads and writes them in big-endian... 223 + */ 224 + static void 225 + ecdh_x25519_array_reverse(unsigned char *const restrict ptr, const size_t len) 226 + { 227 + for (size_t i = 0; i < (len / 2); i++) 228 + { 229 + unsigned char tmpval = ptr[i]; 230 + ptr[i] = ptr[len - i - 1]; 231 + ptr[len - i - 1] = tmpval; 232 + } 233 + } 234 + 235 + static bool ATHEME_FATTR_WUR 236 + ecdh_x25519_create_keypair(unsigned char seckey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 237 + unsigned char pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN]) 238 + { 239 + bool retval = false; 240 + 241 + mbedtls_ecp_group G; 242 + mbedtls_ecp_point Q; 243 + mbedtls_mpi d; 244 + int ret; 245 + 246 + (void) mbedtls_ecp_group_init(&G); 247 + (void) mbedtls_ecp_point_init(&Q); 248 + (void) mbedtls_mpi_init(&d); 249 + 250 + X25519_MBEDTLS_CHK_E(mbedtls_ecp_group_load(&G, MBEDTLS_ECP_DP_CURVE25519)); 251 + X25519_MBEDTLS_CHK_E(mbedtls_ecdh_gen_public(&G, &d, &Q, &ecdh_x25519_rng_func, NULL)); 252 + X25519_MBEDTLS_CHK_E(mbedtls_mpi_write_binary(&d, seckey, ATHEME_ECDH_X25519_XKEY_LEN)); 253 + X25519_MBEDTLS_CHK_E(mbedtls_mpi_write_binary(&Q.X, pubkey, ATHEME_ECDH_X25519_XKEY_LEN)); 254 + 255 + (void) ecdh_x25519_array_reverse(seckey, ATHEME_ECDH_X25519_XKEY_LEN); 256 + (void) ecdh_x25519_array_reverse(pubkey, ATHEME_ECDH_X25519_XKEY_LEN); 257 + 258 + retval = true; 259 + 260 + cleanup: 261 + (void) mbedtls_mpi_free(&d); 262 + (void) mbedtls_ecp_point_free(&Q); 263 + (void) mbedtls_ecp_group_free(&G); 264 + return retval; 265 + } 266 + 267 + static bool ATHEME_FATTR_WUR 268 + ecdh_x25519_compute_shared(const unsigned char seckey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 269 + const unsigned char pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 270 + unsigned char shared_secret[const restrict static ATHEME_ECDH_X25519_XKEY_LEN]) 271 + { 272 + bool retval = false; 273 + 274 + unsigned char seckey_be[ATHEME_ECDH_X25519_XKEY_LEN]; 275 + unsigned char pubkey_be[ATHEME_ECDH_X25519_XKEY_LEN]; 276 + 277 + mbedtls_ecp_group G; 278 + mbedtls_ecp_point Q; 279 + mbedtls_mpi d; 280 + mbedtls_mpi z; 281 + int ret; 282 + 283 + (void) memcpy(seckey_be, seckey, sizeof seckey_be); 284 + (void) memcpy(pubkey_be, pubkey, sizeof pubkey_be); 285 + (void) ecdh_x25519_array_reverse(seckey_be, sizeof seckey_be); 286 + (void) ecdh_x25519_array_reverse(pubkey_be, sizeof pubkey_be); 287 + 288 + (void) mbedtls_ecp_group_init(&G); 289 + (void) mbedtls_ecp_point_init(&Q); 290 + (void) mbedtls_mpi_init(&d); 291 + (void) mbedtls_mpi_init(&z); 292 + 293 + X25519_MBEDTLS_CHK_E(mbedtls_ecp_group_load(&G, MBEDTLS_ECP_DP_CURVE25519)); 294 + X25519_MBEDTLS_CHK_E(mbedtls_mpi_read_binary(&d, seckey_be, sizeof seckey_be)); 295 + X25519_MBEDTLS_CHK_E(mbedtls_mpi_read_binary(&Q.X, pubkey_be, sizeof pubkey_be)); 296 + X25519_MBEDTLS_CHK_E(mbedtls_mpi_lset(&Q.Z, 1)); 297 + X25519_MBEDTLS_CHK_D(mbedtls_ecp_check_pubkey(&G, &Q)); 298 + X25519_MBEDTLS_CHK_D(mbedtls_ecdh_compute_shared(&G, &z, &Q, &d, &ecdh_x25519_rng_func, NULL)); 299 + X25519_MBEDTLS_CHK_E(mbedtls_mpi_write_binary(&z, shared_secret, ATHEME_ECDH_X25519_XKEY_LEN)); 300 + 301 + (void) ecdh_x25519_array_reverse(shared_secret, ATHEME_ECDH_X25519_XKEY_LEN); 302 + 303 + if (smemcmp(shared_secret, ecdh_x25519_zeroes, sizeof ecdh_x25519_zeroes) == 0) 304 + { 305 + (void) ecdh_x25519_log_error(LG_DEBUG, "%s: secret is all zeroes (bad pubkey?)", MOWGLI_FUNC_NAME); 306 + goto cleanup; 307 + } 308 + 309 + retval = true; 310 + 311 + cleanup: 312 + (void) mbedtls_mpi_free(&z); 313 + (void) mbedtls_mpi_free(&d); 314 + (void) mbedtls_ecp_point_free(&Q); 315 + (void) mbedtls_ecp_group_free(&G); 316 + (void) smemzero(seckey_be, sizeof seckey_be); 317 + return retval; 318 + } 319 + 320 + #endif /* ECDH_X25519_USE_LIBMBEDCRYPTO */ 321 + 322 + 323 + 324 + static bool ATHEME_FATTR_WUR 325 + ecdh_x25519_kdf(const unsigned char shared_secret[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 326 + const unsigned char client_pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 327 + const unsigned char server_pubkey[const restrict static ATHEME_ECDH_X25519_XKEY_LEN], 328 + const unsigned char server_salt[const restrict static ATHEME_ECDH_X25519_SALT_LEN], 329 + unsigned char better_secret[const restrict static ATHEME_ECDH_X25519_CHAL_LEN]) 330 + { 331 + const struct digest_vector secret_vec[] = { 332 + { shared_secret, ATHEME_ECDH_X25519_XKEY_LEN }, 333 + { client_pubkey, ATHEME_ECDH_X25519_XKEY_LEN }, 334 + { server_pubkey, ATHEME_ECDH_X25519_XKEY_LEN }, 335 + }; 336 + 337 + const size_t secret_vec_len = (sizeof secret_vec) / (sizeof secret_vec[0]); 338 + 339 + unsigned char ikm[DIGEST_MDLEN_SHA2_256]; 340 + 341 + if (! digest_oneshot_vector(DIGALG_SHA2_256, secret_vec, secret_vec_len, ikm, NULL)) 342 + { 343 + (void) ecdh_x25519_log_error(LG_ERROR, "%s: digest_oneshot_vector() failed (BUG?)", MOWGLI_FUNC_NAME); 344 + return false; 345 + } 346 + if (! ECDH_X25519_KDF(ikm, server_salt, better_secret)) 347 + { 348 + (void) ecdh_x25519_log_error(LG_ERROR, "%s: digest_oneshot_hkdf() failed (BUG?)", MOWGLI_FUNC_NAME); 349 + return false; 350 + } 351 + 352 + (void) smemzero(ikm, sizeof ikm); 353 + return true; 354 + } 355 + 356 + 357 + 358 + #include "ecdh-x25519-challenge-vectors.h" 359 + 360 + static bool ATHEME_FATTR_WUR 361 + ecdh_x25519_selftest(void) 362 + { 363 + unsigned char ecdh_x25519_secret_ab[sizeof ecdh_x25519_secret]; 364 + unsigned char ecdh_x25519_secret_ba[sizeof ecdh_x25519_secret]; 365 + 366 + if (! ecdh_x25519_compute_shared(ecdh_x25519_alice_sk, ecdh_x25519_bob_pk, ecdh_x25519_secret_ab)) 367 + // This function logs messages on failure 368 + return false; 369 + 370 + if (! ecdh_x25519_compute_shared(ecdh_x25519_bob_sk, ecdh_x25519_alice_pk, ecdh_x25519_secret_ba)) 371 + // This function logs messages on failure 372 + return false; 373 + 374 + if (memcmp(ecdh_x25519_secret_ab, ecdh_x25519_secret, sizeof ecdh_x25519_secret) != 0) 375 + { 376 + (void) ecdh_x25519_log_error(LG_ERROR, "%s: memcmp(3) mismatch (alice_sk, bob_pk) (BUG?)", 377 + MOWGLI_FUNC_NAME); 378 + return false; 379 + } 380 + if (memcmp(ecdh_x25519_secret_ba, ecdh_x25519_secret, sizeof ecdh_x25519_secret) != 0) 381 + { 382 + (void) ecdh_x25519_log_error(LG_ERROR, "%s: memcmp(3) mismatch (bob_sk, alice_pk) (BUG?)", 383 + MOWGLI_FUNC_NAME); 384 + return false; 385 + } 386 + 387 + return true; 388 + }
+55
modules/saslserv/ecdh-x25519-challenge-vectors.h
··· 1 + /* 2 + * SPDX-License-Identifier: CC0-1.0 3 + * SPDX-URL: https://spdx.org/licenses/CC0-1.0.html 4 + * 5 + * Copyright (C) 2009 Daniel J. Bernstein <djb@cr.yp.to> 6 + * 7 + * X25519 ECDH test vectors. 8 + * 9 + * These were taken from [1], but with the secret key octets clamped as 10 + * described in [2]. They are also located in Section 6.1 of RFC7748 [3]. 11 + * 12 + * [1] <https://cr.yp.to/highspeed/naclcrypto-20090310.pdf> 13 + * [2] <https://cr.yp.to/ecdh.html> 14 + * [3] <https://tools.ietf.org/html/rfc7748#section-6.1> 15 + */ 16 + 17 + #ifndef ATHEME_MOD_SASL_ECDH_X25519_CHALLENGE_VECTORS_H 18 + #define ATHEME_MOD_SASL_ECDH_X25519_CHALLENGE_VECTORS_H 1 19 + 20 + static const unsigned char ecdh_x25519_alice_sk[] = { 21 + 0x70U, 0x07U, 0x6DU, 0x0AU, 0x73U, 0x18U, 0xA5U, 0x7DU, 22 + 0x3CU, 0x16U, 0xC1U, 0x72U, 0x51U, 0xB2U, 0x66U, 0x45U, 23 + 0xDFU, 0x4CU, 0x2FU, 0x87U, 0xEBU, 0xC0U, 0x99U, 0x2AU, 24 + 0xB1U, 0x77U, 0xFBU, 0xA5U, 0x1DU, 0xB9U, 0x2CU, 0x6AU, 25 + }; 26 + 27 + static const unsigned char ecdh_x25519_alice_pk[] = { 28 + 0x85U, 0x20U, 0xF0U, 0x09U, 0x89U, 0x30U, 0xA7U, 0x54U, 29 + 0x74U, 0x8BU, 0x7DU, 0xDCU, 0xB4U, 0x3EU, 0xF7U, 0x5AU, 30 + 0x0DU, 0xBFU, 0x3AU, 0x0DU, 0x26U, 0x38U, 0x1AU, 0xF4U, 31 + 0xEBU, 0xA4U, 0xA9U, 0x8EU, 0xAAU, 0x9BU, 0x4EU, 0x6AU, 32 + }; 33 + 34 + static const unsigned char ecdh_x25519_bob_sk[] = { 35 + 0x58U, 0xABU, 0x08U, 0x7EU, 0x62U, 0x4AU, 0x8AU, 0x4BU, 36 + 0x79U, 0xE1U, 0x7FU, 0x8BU, 0x83U, 0x80U, 0x0EU, 0xE6U, 37 + 0x6FU, 0x3BU, 0xB1U, 0x29U, 0x26U, 0x18U, 0xB6U, 0xFDU, 38 + 0x1CU, 0x2FU, 0x8BU, 0x27U, 0xFFU, 0x88U, 0xE0U, 0xEBU, 39 + }; 40 + 41 + static const unsigned char ecdh_x25519_bob_pk[] = { 42 + 0xDEU, 0x9EU, 0xDBU, 0x7DU, 0x7BU, 0x7DU, 0xC1U, 0xB4U, 43 + 0xD3U, 0x5BU, 0x61U, 0xC2U, 0xECU, 0xE4U, 0x35U, 0x37U, 44 + 0x3FU, 0x83U, 0x43U, 0xC8U, 0x5BU, 0x78U, 0x67U, 0x4DU, 45 + 0xADU, 0xFCU, 0x7EU, 0x14U, 0x6FU, 0x88U, 0x2BU, 0x4FU, 46 + }; 47 + 48 + static const unsigned char ecdh_x25519_secret[] = { 49 + 0x4AU, 0x5DU, 0x9DU, 0x5BU, 0xA4U, 0xCEU, 0x2DU, 0xE1U, 50 + 0x72U, 0x8EU, 0x3BU, 0xF4U, 0x80U, 0x35U, 0x0FU, 0x25U, 51 + 0xE0U, 0x7EU, 0x21U, 0xC9U, 0x47U, 0xD1U, 0x9EU, 0x33U, 52 + 0x76U, 0xF0U, 0x9BU, 0x3CU, 0x1EU, 0x16U, 0x17U, 0x42U, 53 + }; 54 + 55 + #endif /* ! ATHEME_MOD_SASL_ECDH_X25519_CHALLENGE_VECTORS_H */
+439
modules/saslserv/ecdh-x25519-challenge.c
··· 1 + /* 2 + * SPDX-License-Identifier: ISC 3 + * SPDX-URL: https://spdx.org/licenses/ISC.html 4 + * 5 + * Copyright (C) 2019 Aaron M. D. Jones <aaronmdjones@gmail.com> 6 + * 7 + * ECDH-X25519-CHALLENGE mechanism provider. 8 + * 9 + * This is a client-first SASL mechanism. 10 + * 11 + * The first message from the client to the server consists of an authcid (or, 12 + * optionally, an authcid, followed by a null octet, followed by an authzid). 13 + * The latter allows SASL impersonation. Clients MAY set both the authcid and 14 + * the authzid to the same value. 15 + * 16 + * client_message = authcid [ 0x00 authzid ] 17 + * 18 + * The server then does the following: 19 + * 20 + * client_pubkey = fetch_configured_pubkey(authcid) 21 + * ++ server_seckey = Curve25519_clamp(CSPRNG(32)) 22 + * ++ server_pubkey = Curve25519(server_seckey, basepoint) 23 + * shared_secret = Curve25519(server_seckey, client_pubkey) 24 + * session_salt = CSPRNG(32) 25 + * better_secret = ECDH-X25519-KDF() 26 + * session_challenge = CSPRNG(32) 27 + * masked_challenge = (session_challenge XOR better_secret) 28 + * server_response = server_pubkey || session_salt || masked_challenge 29 + * 30 + * The client then does the following: 31 + * 32 + * client_seckey = fetch_configured_seckey() 33 + * client_pubkey = Curve25519(client_seckey, basepoint) 34 + * shared_secret = Curve25519(client_seckey, server_pubkey) 35 + * better_secret = ECDH-X25519-KDF() 36 + * session_challenge = (masked_challenge XOR better_secret) 37 + * client_response = session_challenge 38 + * 39 + * If the client possesses the correct private key, the secret derived on both 40 + * sides will be identical, and thus the client response will be the correct 41 + * session challenge, which is verified by the server against the session 42 + * challenge it generated. 43 + * 44 + * If the challenges match, the client is logged in as the authzid, or, if an 45 + * authzid was not provided, the authcid. 46 + * 47 + * For the definitions of Curve25519_clamp(), Curve25519(), and basepoint, 48 + * please see [1][2]. 49 + * 50 + * This mechanism is intended to eventually replace ECDSA-NIST256P-CHALLENGE, 51 + * due to concerns within the cryptographic community about the continued 52 + * safety and reliability of the NIST curves, and P-256 in particular [3][4]. 53 + * 54 + * Curve25519 ECDH was chosen over Ed25519 EdDSA as the basis for this 55 + * mechanism for three primary reasons: 56 + * 57 + * * Curve25519 ECDH was carefully-designed to allow all possible values as 58 + * a valid public key, removing the need for the server to validate the 59 + * keys, or to worry about operating with potentially-malicious key data. 60 + * 61 + * * Verification of the client's response is a simple constant-time memory 62 + * comparison operation, avoiding exposing the server's cryptographic guts 63 + * to potentially-malicious client data. 64 + * 65 + * * More crypto libraries implement Curve25519 ECDH than Ed25519 EdDSA as 66 + * of this writing. 67 + * 68 + * Additionally, key agreement is faster than signature verification, but that 69 + * does not turn out to be of any consequence here because of the HKDF below: 70 + * 71 + * The ECDH-X25519-KDF() function is defined as follows: 72 + * 73 + * ECDH-X25519-KDF() = { 74 + * IKM = Hash(shared_secret || client_pubkey || server_pubkey) 75 + * PRK = HKDF-Extract(session_salt, IKM) 76 + * OKM = HKDF-Expand(PRK, "ECDH-X25519-CHALLENGE", L=32) 77 + * // The string above is 21 ASCII octets 78 + * return OKM 79 + * } 80 + * 81 + * The digest algorithm used for Hash(), HKDF-Extract(), and HKDF-Expand(), is 82 + * SHA2-256. The HKDF-Extract() and HKDF-Expand() functions, and the order and 83 + * meaning of their arguments, are as described in Section 2 of RFC5869 [5]. 84 + * 85 + * Note that both the client's and server's Curve25519 public keys are used in 86 + * the key derivation process in, and the Curve25519 shared secret is derived 87 + * in, and the server's public key is transmitted to the client in, the 88 + * canonical Curve25519 key representation format of a string of 32 octets, 89 + * representing a **little-endian** 256-bit integer. This diverges from the 90 + * canonical network octet-order (big-endian), but it is specified as such in 91 + * Section 5 of RFC7748 [6]. If your cryptographic library reads and writes 92 + * secret keys, public keys, and shared secrets, in big-endian format, you 93 + * will need to reverse the string's octets; swap the 1st and 32nd octets, 94 + * swap the 2nd and 31st octets, swap the 3rd and 30th octets, ... etc. As of 95 + * this writing, the only known cryptographic library with this octet-order 96 + * requirement that supports Curve25519 ECDH key agreement is ARM mbedTLS [7]. 97 + * 98 + * The first half of the shared secret derivation process is justified in [8]. 99 + * The second half of the shared secret derivation process is to ensure that 100 + * the derived shared secret is cryptographically bound to this application 101 + * (by using the mechanism name as an input), should the client end up reusing 102 + * their private key for something else, and to ensure that it is safe for the 103 + * server to reuse its private key across sessions (by using the session salt 104 + * as an input -- this also ensures contributory behaviour on the part of the 105 + * server), should it wish to do so for efficiency: 106 + * 107 + * ++ These steps are not necessary for each session; the server MAY perform 108 + * them at a regular interval instead, if it wishes to forgo the expense of 109 + * deriving a new key-pair for each and every session. Servers SHOULD NOT 110 + * perform this only once (on startup, or as statically-configured); a 111 + * RECOMMENDED keypair regeneration interval is once per hour. 112 + * 113 + * 114 + * 115 + * [1] <https://cr.yp.to/ecdh.html> 116 + * [2] <https://cr.yp.to/ecdh/curve25519-20060209.pdf> 117 + * [3] <https://safecurves.cr.yp.to/> 118 + * [4] <https://cr.yp.to/talks/2013.05.31/slides-dan+tanja-20130531-4x3.pdf> 119 + * [5] <https://tools.ietf.org/html/rfc5869#section-2> 120 + * [6] <https://tools.ietf.org/html/rfc7748#section-5> 121 + * [7] <https://github.com/ARMmbed/mbedTLS/issues/2490> 122 + * [8] <https://download.libsodium.org/doc/advanced/scalar_multiplication> 123 + * 124 + * The number of possible keys is limited to the group size (~~ 2^252), 125 + * which is smaller than the key space. For this reason, and to mitigate 126 + * subtle attacks due to the fact many (p, n) pairs produce the same 127 + * result, using the output of the multiplication q directly as a shared 128 + * key is not recommended. A better way to compute a shared key is 129 + * H(q || pk1 || pk2), with pk1 and pk2 being the public keys. 130 + */ 131 + 132 + #include <atheme.h> 133 + 134 + #ifdef HAVE_ANYLIB_ECDH_X25519 135 + 136 + #include "ecdh-x25519-challenge-shared.c" 137 + 138 + #define ATHEME_ECDH_X25519_KEY_REGEN_INTERVAL 3600 /* 1 hour */ 139 + #define ATHEME_ECDH_X25519_PUBKEY_MDNAME "private:x25519pubkey" 140 + 141 + static mowgli_patricia_t **ns_set_cmdtree = NULL; 142 + static const struct sasl_core_functions *sasl_core_functions = NULL; 143 + 144 + static mowgli_eventloop_timer_t *ecdh_x25519_keypair_regen_timer = NULL; 145 + static unsigned char ecdh_x25519_server_seckey[ATHEME_ECDH_X25519_XKEY_LEN]; 146 + static unsigned char ecdh_x25519_server_pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 147 + 148 + static const char ecdh_x25519_mkdf_salt[] = "ECDH-X25519-CHALLENGE SASL Mechanism HKDF Salt Generator"; 149 + static const char ecdh_x25519_mkdf_challenge[] = "ECDH-X25519-CHALLENGE SASL Mechanism HKDF Challenge Generator"; 150 + 151 + static bool 152 + ecdh_x25519_keypair_regen(void) 153 + { 154 + unsigned char seckey[ATHEME_ECDH_X25519_XKEY_LEN]; 155 + unsigned char pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 156 + 157 + if (! ecdh_x25519_create_keypair(seckey, pubkey)) 158 + // This function logs messages on failure 159 + return false; 160 + 161 + (void) memcpy(ecdh_x25519_server_seckey, seckey, sizeof seckey); 162 + (void) memcpy(ecdh_x25519_server_pubkey, pubkey, sizeof pubkey); 163 + (void) smemzero(seckey, sizeof seckey); 164 + (void) smemzero(pubkey, sizeof pubkey); 165 + return true; 166 + } 167 + 168 + static void 169 + ecdh_x25519_keypair_regen_cb(void ATHEME_VATTR_UNUSED *const restrict vptr) 170 + { 171 + // This function logs messages on failure 172 + (void) ecdh_x25519_keypair_regen(); 173 + } 174 + 175 + static enum sasl_mechanism_result ATHEME_FATTR_WUR 176 + ecdh_x25519_sasl_step_account_names(struct sasl_session *const restrict p, 177 + const struct sasl_input_buf *const restrict in, 178 + struct sasl_output_buf *const restrict out) 179 + { 180 + unsigned char client_pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 181 + unsigned char shared_secret[ATHEME_ECDH_X25519_XKEY_LEN]; 182 + unsigned char better_secret[ATHEME_ECDH_X25519_CHAL_LEN]; 183 + unsigned char challenge[ATHEME_ECDH_X25519_CHAL_LEN]; 184 + static union ecdh_x25519_server_response resp; 185 + 186 + enum sasl_mechanism_result retval = ASASL_MRESULT_ERROR; 187 + struct metadata *md = NULL; 188 + struct myuser *mu = NULL; 189 + 190 + const char *const end = memchr(in->buf, 0x00, in->len); 191 + 192 + if (! end) 193 + { 194 + if (in->len > NICKLEN) 195 + { 196 + (void) slog(LG_DEBUG, "%s: in->len (%zu) is unacceptable", MOWGLI_FUNC_NAME, in->len); 197 + goto cleanup; 198 + } 199 + if (! sasl_core_functions->authcid_can_login(p, in->buf, &mu)) 200 + { 201 + (void) slog(LG_DEBUG, "%s: authcid_can_login failed", MOWGLI_FUNC_NAME); 202 + goto cleanup; 203 + } 204 + } 205 + else 206 + { 207 + const char *const bufchrptr = in->buf; 208 + const size_t authcid_length = (size_t) (end - bufchrptr); 209 + const size_t authzid_length = in->len - 1 - authcid_length; 210 + 211 + if (! authcid_length || authcid_length > NICKLEN) 212 + { 213 + (void) slog(LG_DEBUG, "%s: authcid_length (%zu) is unacceptable", 214 + MOWGLI_FUNC_NAME, authcid_length); 215 + goto cleanup; 216 + } 217 + if (! authzid_length || authzid_length > NICKLEN) 218 + { 219 + (void) slog(LG_DEBUG, "%s: authzid_length (%zu) is unacceptable", 220 + MOWGLI_FUNC_NAME, authzid_length); 221 + goto cleanup; 222 + } 223 + if (! sasl_core_functions->authzid_can_login(p, end + 1, NULL)) 224 + { 225 + (void) slog(LG_DEBUG, "%s: authzid_can_login failed", MOWGLI_FUNC_NAME); 226 + goto cleanup; 227 + } 228 + if (! sasl_core_functions->authcid_can_login(p, in->buf, &mu)) 229 + { 230 + (void) slog(LG_DEBUG, "%s: authcid_can_login failed", MOWGLI_FUNC_NAME); 231 + goto cleanup; 232 + } 233 + } 234 + if (! mu) 235 + { 236 + (void) slog(LG_ERROR, "%s: authcid_can_login did not set 'mu' (BUG)", MOWGLI_FUNC_NAME); 237 + goto cleanup; 238 + } 239 + if (! (md = metadata_find(mu, ATHEME_ECDH_X25519_PUBKEY_MDNAME))) 240 + { 241 + (void) slog(LG_DEBUG, "%s: metadata_find() failed", MOWGLI_FUNC_NAME); 242 + goto cleanup; 243 + } 244 + if (base64_decode(md->value, client_pubkey, sizeof client_pubkey) != sizeof client_pubkey) 245 + { 246 + (void) slog(LG_ERROR, "%s: base64_decode() failed (BUG!)", MOWGLI_FUNC_NAME); 247 + goto cleanup; 248 + } 249 + 250 + if (! ecdh_x25519_compute_shared(ecdh_x25519_server_seckey, client_pubkey, shared_secret)) 251 + // This function logs messages on failure 252 + goto cleanup; 253 + 254 + (void) memcpy(resp.field.pubkey, ecdh_x25519_server_pubkey, sizeof ecdh_x25519_server_pubkey); 255 + (void) atheme_random_buf(resp.field.salt, sizeof resp.field.salt); 256 + 257 + if (! ecdh_x25519_kdf(shared_secret, client_pubkey, resp.field.pubkey, resp.field.salt, better_secret)) 258 + // This function logs messages on failure 259 + goto cleanup; 260 + 261 + (void) atheme_random_buf(challenge, sizeof challenge); 262 + 263 + p->mechdata = smemdup(challenge, sizeof challenge); 264 + 265 + for (size_t x = 0; x < sizeof resp.field.challenge; x++) 266 + resp.field.challenge[x] = challenge[x] ^ better_secret[x]; 267 + 268 + out->buf = resp.octets; 269 + out->len = sizeof resp.octets; 270 + 271 + retval = ASASL_MRESULT_CONTINUE; 272 + 273 + cleanup: 274 + (void) smemzero(shared_secret, sizeof shared_secret); 275 + (void) smemzero(better_secret, sizeof better_secret); 276 + (void) smemzero(challenge, sizeof challenge); 277 + return retval; 278 + } 279 + 280 + static enum sasl_mechanism_result ATHEME_FATTR_WUR 281 + ecdh_x25519_sasl_step_verify_challenge(struct sasl_session *const restrict p, 282 + const struct sasl_input_buf *const restrict in) 283 + { 284 + if (in->len != ATHEME_ECDH_X25519_CHAL_LEN) 285 + { 286 + (void) slog(LG_DEBUG, "%s: in->len (%zu) is unacceptable", MOWGLI_FUNC_NAME, in->len); 287 + return ASASL_MRESULT_ERROR; 288 + } 289 + if (smemcmp(p->mechdata, in->buf, in->len) != 0) 290 + { 291 + (void) slog(LG_DEBUG, "%s: challenge is incorrect", MOWGLI_FUNC_NAME); 292 + return ASASL_MRESULT_FAILURE; 293 + } 294 + 295 + (void) slog(LG_DEBUG, "%s: authentication successful", MOWGLI_FUNC_NAME); 296 + return ASASL_MRESULT_SUCCESS; 297 + } 298 + 299 + static enum sasl_mechanism_result ATHEME_FATTR_WUR 300 + ecdh_x25519_sasl_step(struct sasl_session *const restrict p, 301 + const struct sasl_input_buf *const restrict in, 302 + struct sasl_output_buf *const restrict out) 303 + { 304 + if (! (in && in->buf && in->len)) 305 + { 306 + (void) slog(LG_DEBUG, "%s: client did not send any data", MOWGLI_FUNC_NAME); 307 + return ASASL_MRESULT_ERROR; 308 + } 309 + 310 + if (p->mechdata == NULL) 311 + return ecdh_x25519_sasl_step_account_names(p, in, out); 312 + else 313 + return ecdh_x25519_sasl_step_verify_challenge(p, in); 314 + } 315 + 316 + static void 317 + ecdh_x25519_sasl_finish(struct sasl_session *const restrict p) 318 + { 319 + if (! (p && p->mechdata)) 320 + return; 321 + 322 + (void) sfree(p->mechdata); 323 + 324 + p->mechdata = NULL; 325 + } 326 + 327 + static void 328 + ns_cmd_set_x25519_pubkey_func(struct sourceinfo *const restrict si, const int parc, char **const restrict parv) 329 + { 330 + struct metadata *const md = metadata_find(si->smu, ATHEME_ECDH_X25519_PUBKEY_MDNAME); 331 + 332 + if (! parc) 333 + { 334 + if (! md) 335 + { 336 + (void) command_fail(si, fault_nochange, _("Public key was not set")); 337 + return; 338 + } 339 + 340 + (void) metadata_delete(si->smu, ATHEME_ECDH_X25519_PUBKEY_MDNAME); 341 + (void) logcommand(si, CMDLOG_SET, "SET:X25519-PUBKEY:REMOVE"); 342 + (void) command_success_nodata(si, _("Public key entry has been deleted.")); 343 + return; 344 + } 345 + 346 + const char *const newkey = parv[0]; 347 + unsigned char client_pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 348 + 349 + if (base64_decode(newkey, client_pubkey, sizeof client_pubkey) != sizeof client_pubkey) 350 + { 351 + (void) command_fail(si, fault_badparams, _("The public key specified is not valid.")); 352 + return; 353 + } 354 + if (md && strcmp(md->value, newkey) == 0) 355 + { 356 + (void) command_fail(si, fault_nochange, _("Your public key is already set to \2%s\2."), newkey); 357 + return; 358 + } 359 + 360 + (void) metadata_add(si->smu, ATHEME_ECDH_X25519_PUBKEY_MDNAME, newkey); 361 + (void) logcommand(si, CMDLOG_SET, "SET:X25519-PUBKEY: \2%s\2", newkey); 362 + (void) command_success_nodata(si, _("Your public key is now set to \2%s\2."), newkey); 363 + } 364 + 365 + static const struct sasl_mechanism sasl_mech_ecdh_x25519_challenge = { 366 + .name = "ECDH-X25519-CHALLENGE", 367 + .mech_start = NULL, 368 + .mech_step = &ecdh_x25519_sasl_step, 369 + .mech_finish = &ecdh_x25519_sasl_finish, 370 + }; 371 + 372 + static struct command ns_cmd_set_x25519_pubkey = { 373 + .name = "X25519-PUBKEY", 374 + .desc = N_("Changes your ECDH-X25519-CHALLENGE public key."), 375 + .access = AC_AUTHENTICATED, 376 + .maxparc = 1, 377 + .cmd = &ns_cmd_set_x25519_pubkey_func, 378 + .help = { .path = "nickserv/set_x25519_pubkey" }, 379 + }; 380 + 381 + static void 382 + mod_init(struct module *const restrict m) 383 + { 384 + MODULE_TRY_REQUEST_SYMBOL(m, ns_set_cmdtree, "nickserv/set_core", "ns_set_cmdtree"); 385 + MODULE_TRY_REQUEST_SYMBOL(m, sasl_core_functions, "saslserv/main", "sasl_core_functions"); 386 + 387 + if (! ecdh_x25519_selftest()) 388 + { 389 + (void) slog(LG_ERROR, "%s: self-test failed (BUG?); refusing to load", m->name); 390 + m->mflags |= MODFLAG_FAIL; 391 + return; 392 + } 393 + if (! ecdh_x25519_keypair_regen()) 394 + { 395 + (void) slog(LG_ERROR, "%s: keypair generation failed (BUG?); refusing to load", m->name); 396 + m->mflags |= MODFLAG_FAIL; 397 + return; 398 + } 399 + if (! (ecdh_x25519_keypair_regen_timer = mowgli_timer_add(base_eventloop, "ecdh_x25519_keypair_regen", 400 + &ecdh_x25519_keypair_regen_cb, NULL, ATHEME_ECDH_X25519_KEY_REGEN_INTERVAL))) 401 + { 402 + (void) slog(LG_ERROR, "%s: mowgli_timer_add() failed (BUG?); refusing to load", m->name); 403 + m->mflags |= MODFLAG_FAIL; 404 + return; 405 + } 406 + 407 + (void) sasl_core_functions->mech_register(&sasl_mech_ecdh_x25519_challenge); 408 + (void) command_add(&ns_cmd_set_x25519_pubkey, *ns_set_cmdtree); 409 + } 410 + 411 + static void 412 + mod_deinit(const enum module_unload_intent ATHEME_VATTR_UNUSED intent) 413 + { 414 + (void) smemzero(ecdh_x25519_server_seckey, sizeof ecdh_x25519_server_seckey); 415 + (void) mowgli_timer_destroy(base_eventloop, ecdh_x25519_keypair_regen_timer); 416 + (void) sasl_core_functions->mech_unregister(&sasl_mech_ecdh_x25519_challenge); 417 + (void) command_delete(&ns_cmd_set_x25519_pubkey, *ns_set_cmdtree); 418 + } 419 + 420 + #else /* HAVE_ANYLIB_ECDH_X25519 */ 421 + 422 + static void 423 + mod_init(struct module *const restrict m) 424 + { 425 + (void) slog(LG_ERROR, "Module %s requires a library with usable Curve25519 key generation " 426 + "& ECDH key agreement functions, refusing to load.", m->name); 427 + 428 + m->mflags |= MODFLAG_FAIL; 429 + } 430 + 431 + static void 432 + mod_deinit(const enum module_unload_intent ATHEME_VATTR_UNUSED intent) 433 + { 434 + // Nothing To Do 435 + } 436 + 437 + #endif /* !HAVE_ANYLIB_ECDH_X25519 */ 438 + 439 + SIMPLE_DECLARE_MODULE_V1("saslserv/ecdh-x25519-challenge", MODULE_UNLOAD_CAPABILITY_OK)
+40
modules/saslserv/ecdh-x25519-challenge.h
··· 1 + /* 2 + * SPDX-License-Identifier: ISC 3 + * SPDX-URL: https://spdx.org/licenses/ISC.html 4 + * 5 + * Copyright (C) 2019 Aaron M. D. Jones <aaronmdjones@gmail.com> 6 + * 7 + * ECDH-X25519-CHALLENGE mechanism shared routines. 8 + */ 9 + 10 + #ifndef ATHEME_MOD_SASL_ECDH_X25519_CHALLENGE_H 11 + #define ATHEME_MOD_SASL_ECDH_X25519_CHALLENGE_H 1 12 + 13 + #define ATHEME_ECDH_X25519_KDF_INFO_CTX "ECDH-X25519-CHALLENGE" 14 + #define ATHEME_ECDH_X25519_KDF_INFO_LEN 21U 15 + 16 + #define ATHEME_ECDH_X25519_XKEY_LEN 32U 17 + #define ATHEME_ECDH_X25519_SALT_LEN 32U 18 + #define ATHEME_ECDH_X25519_CHAL_LEN 32U 19 + #define ATHEME_ECDH_X25519_RAND_LEN 32U 20 + 21 + #define ECDH_X25519_KDF(ikm, salt, okm) \ 22 + digest_oneshot_hkdf(DIGALG_SHA2_256, ikm, DIGEST_MDLEN_SHA2_256, \ 23 + salt, ATHEME_ECDH_X25519_SALT_LEN, \ 24 + ATHEME_ECDH_X25519_KDF_INFO_CTX, ATHEME_ECDH_X25519_KDF_INFO_LEN, \ 25 + okm, ATHEME_ECDH_X25519_CHAL_LEN) 26 + 27 + struct ecdh_x25519_server_response_fields 28 + { 29 + unsigned char pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 30 + unsigned char salt[ATHEME_ECDH_X25519_SALT_LEN]; 31 + unsigned char challenge[ATHEME_ECDH_X25519_CHAL_LEN]; 32 + } ATHEME_SATTR_PACKED; 33 + 34 + union ecdh_x25519_server_response 35 + { 36 + unsigned char octets[sizeof(struct ecdh_x25519_server_response_fields)]; 37 + struct ecdh_x25519_server_response_fields field; 38 + } ATHEME_SATTR_PACKED; 39 + 40 + #endif /* !ATHEME_MOD_SASL_ECDH_X25519_CHALLENGE_H */
+1 -1
src/Makefile
··· 1 - SUBDIRS = dbverify $(ECDSA_TOOLS_COND_D) services 1 + SUBDIRS = dbverify $(ECDH_X25519_TOOL_COND_D) $(ECDSA_TOOLS_COND_D) services 2 2 3 3 include ../extra.mk 4 4 include ../buildsys.mk
+1
src/ecdh-x25519-tool/.gitignore
··· 1 + /atheme-ecdh-x25519-tool
+17
src/ecdh-x25519-tool/Makefile
··· 1 + # SPDX-License-Identifier: CC0-1.0 2 + # SPDX-URL: https://spdx.org/licenses/CC0-1.0.html 3 + # 4 + # Copyright (C) 2019 Aaron M. D. Jones <aaronmdjones@gmail.com> 5 + 6 + PROG = $(PACKAGE)-ecdh-x25519-tool$(PROG_SUFFIX) 7 + SRCS = main.c qrcode.c 8 + 9 + include ../../extra.mk 10 + include ../../buildsys.mk 11 + 12 + CPPFLAGS += -I../../include -I../../modules/saslserv 13 + LDFLAGS += -L../../libathemecore 14 + CFLAGS += $(LIBCRYPTO_CFLAGS) $(LIBNETTLE_CFLAGS) $(LIBQRENCODE_CFLAGS) $(LIBSODIUM_CFLAGS) 15 + LIBS += $(LIBCRYPTO_LIBS) $(LIBMBEDCRYPTO_LIBS) $(LIBNETTLE_LIBS) $(LIBQRENCODE_LIBS) $(LIBSODIUM_LIBS) -lathemecore 16 + 17 + build: all
+23
src/ecdh-x25519-tool/ecdh-x25519-tool.h
··· 1 + /* 2 + * SPDX-License-Identifier: ISC 3 + * SPDX-URL: https://spdx.org/licenses/ISC.html 4 + * 5 + * Copyright (C) 2019 Aaron M. D. Jones <aaronmdjones@gmail.com> 6 + * 7 + * Helper utility for the SASL ECDH-X25519-CHALLENGE mechanism. 8 + */ 9 + 10 + #ifndef ATHEME_SRC_ECDH_X25519_TOOL_H 11 + #define ATHEME_SRC_ECDH_X25519_TOOL_H 1 12 + 13 + #define X25519TOOL_OPTFLAG_NONE 0x00000000U 14 + #define X25519TOOL_OPTFLAG_SELFTEST_ONLY 0x00000001U 15 + #define X25519TOOL_OPTFLAG_CREATE_KEYPAIR 0x00000002U 16 + #define X25519TOOL_OPTFLAG_PRINT_PUBKEY 0x00000004U 17 + #define X25519TOOL_OPTFLAG_PRINT_QRCODE 0x00000008U 18 + #define X25519TOOL_OPTFLAG_RESPOND 0x00000010U 19 + #define X25519TOOL_OPTFLAG_SERVER 0x00000020U 20 + 21 + extern int ecdh_x25519_tool_print_qrcode(const char *); 22 + 23 + #endif /* !ATHEME_SRC_ECDH_X25519_TOOL_H */
+382
src/ecdh-x25519-tool/main.c
··· 1 + /* 2 + * SPDX-License-Identifier: ISC 3 + * SPDX-URL: https://spdx.org/licenses/ISC.html 4 + * 5 + * Copyright (C) 2019 Aaron M. D. Jones <aaronmdjones@gmail.com> 6 + * 7 + * Helper utility for the SASL ECDH-X25519-CHALLENGE mechanism. 8 + */ 9 + 10 + #include <atheme.h> 11 + 12 + #define ECDH_X25519_LOG_TO_STDERR 1 13 + 14 + #include "ecdh-x25519-challenge-shared.c" 15 + #include "ecdh-x25519-tool.h" 16 + 17 + #include <atheme/libathemecore.h> 18 + #include <ext/getopt_long.h> 19 + 20 + static int 21 + ecdh_x25519_tool_create_keypair(const char *const restrict keyfile_path) 22 + { 23 + unsigned char seckey[ATHEME_ECDH_X25519_XKEY_LEN]; 24 + unsigned char pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 25 + char pubkey_b64[BASE64_SIZE_STR(sizeof pubkey)]; 26 + 27 + (void) umask(0066); 28 + 29 + if (keyfile_path != NULL && access(keyfile_path, F_OK) == 0) 30 + { 31 + (void) fprintf(stderr, "Key file '%s' already exists\n", keyfile_path); 32 + return EXIT_FAILURE; 33 + } 34 + 35 + FILE *const fh = ((keyfile_path != NULL) ? fopen(keyfile_path, "wb") : stdout); 36 + 37 + if (! fh) 38 + { 39 + (void) fprintf(stderr, "fopen('%s', 'wb'): %s\n", keyfile_path, strerror(errno)); 40 + return EXIT_FAILURE; 41 + } 42 + 43 + if (! ecdh_x25519_create_keypair(seckey, pubkey)) 44 + // This function prints error messages on failure 45 + return EXIT_FAILURE; 46 + 47 + if (fwrite(seckey, sizeof seckey, 1, fh) != 1) 48 + { 49 + (void) fprintf(stderr, "Could not write private key\n"); 50 + return EXIT_FAILURE; 51 + } 52 + if (fwrite(pubkey, sizeof pubkey, 1, fh) != 1) 53 + { 54 + (void) fprintf(stderr, "Could not write public key\n"); 55 + return EXIT_FAILURE; 56 + } 57 + 58 + (void) fflush(fh); 59 + (void) fclose(fh); 60 + 61 + if (keyfile_path != NULL) 62 + { 63 + if (base64_encode(pubkey, sizeof pubkey, pubkey_b64, sizeof pubkey_b64) == (size_t) -1) 64 + { 65 + (void) fprintf(stderr, "base64_encode() failed (BUG?)\n"); 66 + return EXIT_FAILURE; 67 + } 68 + 69 + (void) fprintf(stdout, "%s\n", pubkey_b64); 70 + } 71 + 72 + return EXIT_SUCCESS; 73 + } 74 + 75 + static int 76 + ecdh_x25519_tool_print_pubkey(const char *const restrict keyfile_path) 77 + { 78 + unsigned char seckey[ATHEME_ECDH_X25519_XKEY_LEN]; 79 + unsigned char pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 80 + char pubkey_b64[BASE64_SIZE_STR(sizeof pubkey)]; 81 + 82 + FILE *const fh = ((keyfile_path != NULL) ? fopen(keyfile_path, "rb") : stdin); 83 + 84 + if (! fh) 85 + { 86 + (void) fprintf(stderr, "fopen('%s', 'rb'): %s\n", keyfile_path, strerror(errno)); 87 + return EXIT_FAILURE; 88 + } 89 + if (fread(seckey, sizeof seckey, 1, fh) != 1) 90 + { 91 + (void) fprintf(stderr, "Could not read private key\n"); 92 + return EXIT_FAILURE; 93 + } 94 + if (fread(pubkey, sizeof pubkey, 1, fh) != 1) 95 + { 96 + (void) fprintf(stderr, "Could not read public key\n"); 97 + return EXIT_FAILURE; 98 + } 99 + 100 + (void) fclose(fh); 101 + 102 + if (base64_encode(pubkey, sizeof pubkey, pubkey_b64, sizeof pubkey_b64) == (size_t) -1) 103 + { 104 + (void) fprintf(stderr, "base64_encode() failed (BUG?)\n"); 105 + return EXIT_FAILURE; 106 + } 107 + 108 + (void) fprintf(stdout, "%s\n", pubkey_b64); 109 + return EXIT_SUCCESS; 110 + } 111 + 112 + static int 113 + ecdh_x25519_tool_respond(const char *const restrict keyfile_path, const char *const restrict server_message_str) 114 + { 115 + unsigned char seckey[ATHEME_ECDH_X25519_XKEY_LEN]; 116 + unsigned char pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 117 + unsigned char shared_secret[ATHEME_ECDH_X25519_XKEY_LEN]; 118 + unsigned char better_secret[ATHEME_ECDH_X25519_CHAL_LEN]; 119 + unsigned char challenge[ATHEME_ECDH_X25519_CHAL_LEN]; 120 + char challenge_b64[BASE64_SIZE_STR(sizeof challenge)]; 121 + union ecdh_x25519_server_response resp; 122 + 123 + FILE *const fh = ((keyfile_path != NULL) ? fopen(keyfile_path, "rb") : stdin); 124 + 125 + if (! fh) 126 + { 127 + (void) fprintf(stderr, "fopen('%s', 'rb'): %s\n", keyfile_path, strerror(errno)); 128 + return EXIT_FAILURE; 129 + } 130 + if (fread(seckey, sizeof seckey, 1, fh) != 1) 131 + { 132 + (void) fprintf(stderr, "Could not read private key\n"); 133 + return EXIT_FAILURE; 134 + } 135 + if (fread(pubkey, sizeof pubkey, 1, fh) != 1) 136 + { 137 + (void) fprintf(stderr, "Could not read public key\n"); 138 + return EXIT_FAILURE; 139 + } 140 + 141 + (void) fclose(fh); 142 + 143 + if (base64_decode(server_message_str, resp.octets, sizeof resp.octets) != sizeof resp.octets) 144 + { 145 + (void) fprintf(stderr, "Server Message is invalid\n"); 146 + return EXIT_FAILURE; 147 + } 148 + 149 + if (! ecdh_x25519_compute_shared(seckey, resp.field.pubkey, shared_secret)) 150 + // This function prints error messages on failure 151 + return EXIT_FAILURE; 152 + 153 + if (! ecdh_x25519_kdf(shared_secret, pubkey, resp.field.pubkey, resp.field.salt, better_secret)) 154 + // This function prints error messages on failure 155 + return EXIT_FAILURE; 156 + 157 + for (size_t x = 0; x < sizeof challenge; x++) 158 + challenge[x] = resp.field.challenge[x] ^ better_secret[x]; 159 + 160 + if (base64_encode(challenge, sizeof challenge, challenge_b64, sizeof challenge_b64) == (size_t) -1) 161 + { 162 + (void) fprintf(stderr, "base64_encode() failed (BUG?)\n"); 163 + return EXIT_FAILURE; 164 + } 165 + 166 + (void) fprintf(stdout, "%s\n", challenge_b64); 167 + return EXIT_SUCCESS; 168 + } 169 + 170 + static int 171 + ecdh_x25519_tool_server(const char *const restrict keyfile_path) 172 + { 173 + unsigned char client_seckey[ATHEME_ECDH_X25519_XKEY_LEN]; 174 + unsigned char client_pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 175 + unsigned char server_seckey[ATHEME_ECDH_X25519_XKEY_LEN]; 176 + unsigned char server_pubkey[ATHEME_ECDH_X25519_XKEY_LEN]; 177 + unsigned char shared_secret[ATHEME_ECDH_X25519_XKEY_LEN]; 178 + unsigned char better_secret[ATHEME_ECDH_X25519_CHAL_LEN]; 179 + unsigned char challenge[ATHEME_ECDH_X25519_CHAL_LEN]; 180 + union ecdh_x25519_server_response resp; 181 + char challenge_b64[BASE64_SIZE_STR(sizeof challenge)]; 182 + char resp_octets_b64[BASE64_SIZE_STR(sizeof resp.octets)]; 183 + 184 + FILE *const fh = ((keyfile_path != NULL) ? fopen(keyfile_path, "rb") : stdin); 185 + 186 + if (! fh) 187 + { 188 + (void) fprintf(stderr, "fopen('%s', 'rb'): %s\n", keyfile_path, strerror(errno)); 189 + return EXIT_FAILURE; 190 + } 191 + if (fread(client_seckey, sizeof client_seckey, 1, fh) != 1) 192 + { 193 + (void) fprintf(stderr, "Could not read private key\n"); 194 + return EXIT_FAILURE; 195 + } 196 + if (fread(client_pubkey, sizeof client_pubkey, 1, fh) != 1) 197 + { 198 + (void) fprintf(stderr, "Could not read public key\n"); 199 + return EXIT_FAILURE; 200 + } 201 + 202 + (void) fclose(fh); 203 + 204 + if (! ecdh_x25519_create_keypair(server_seckey, server_pubkey)) 205 + // This function prints error messages on failure 206 + return EXIT_FAILURE; 207 + 208 + if (! ecdh_x25519_compute_shared(server_seckey, client_pubkey, shared_secret)) 209 + // This function prints error messages on failure 210 + return EXIT_FAILURE; 211 + 212 + (void) memcpy(resp.field.pubkey, server_pubkey, sizeof server_pubkey); 213 + (void) atheme_random_buf(resp.field.salt, sizeof resp.field.salt); 214 + (void) atheme_random_buf(challenge, sizeof challenge); 215 + 216 + if (! ecdh_x25519_kdf(shared_secret, client_pubkey, server_pubkey, resp.field.salt, better_secret)) 217 + // This function prints error messages on failure 218 + return EXIT_FAILURE; 219 + 220 + for (size_t x = 0; x < sizeof resp.field.challenge; x++) 221 + resp.field.challenge[x] = challenge[x] ^ better_secret[x]; 222 + 223 + if (base64_encode(challenge, sizeof challenge, challenge_b64, sizeof challenge_b64) == (size_t) -1) 224 + { 225 + (void) fprintf(stderr, "base64_encode() failed (BUG?)\n"); 226 + return EXIT_FAILURE; 227 + } 228 + if (base64_encode(resp.octets, sizeof resp.octets, resp_octets_b64, sizeof resp_octets_b64) == (size_t) -1) 229 + { 230 + (void) fprintf(stderr, "base64_encode() failed (BUG?)\n"); 231 + return EXIT_FAILURE; 232 + } 233 + 234 + (void) fprintf(stdout, "Original challenge: %s\n", challenge_b64); 235 + (void) fprintf(stdout, "Server response: %s\n", resp_octets_b64); 236 + return EXIT_SUCCESS; 237 + } 238 + 239 + static void 240 + ecdh_x25519_tool_print_help(void) 241 + { 242 + (void) fprintf(stdout, "" 243 + " \n" 244 + " Usage: ecdh-x25519-tool [-h | -v | -T]\n" 245 + " ecdh-x25519-tool [-f <path>] [-c | -p | -q | -r <msg> | -s]\n" 246 + " \n" 247 + " -h / --help Print this message\n" 248 + " -v / --version Print version information\n" 249 + " -T / --selftest-only Exit after running X25519 ECDH self-test\n" 250 + " -f / --key-file <path> Path to key file for reading or writing\n" 251 + " -c / --create-keypair Write new key pair to file, print public key\n" 252 + " -p / --print-pubkey Read existing key file, print public key\n" 253 + #ifdef HAVE_LIBQRENCODE 254 + " -q / --qrcode Print PRIVATE AND PUBLIC KEY as a QR-Code\n" 255 + #else 256 + " -q / --qrcode (Not supported on your system)\n" 257 + #endif 258 + " -r / --respond <msg> Generate response to server message\n" 259 + " -s / --server Generate a server message for testing\n" 260 + " \n" 261 + " If \"-f\" is not given, \"-f -\" is assumed. This means use stdin/stdout.\n" 262 + " \n" 263 + " If \"-c -f -\" is given, the entire RAW key pair will be written to stdout.\n" 264 + " Please ensure that stdout is NOT a terminal if this is the case!\n" 265 + " \n"); 266 + } 267 + 268 + int 269 + main(int argc, char **argv) 270 + { 271 + if (! libathemecore_early_init()) 272 + // This function prints error messages on failure 273 + return EXIT_FAILURE; 274 + 275 + const char short_opts[] = "hvTf:cpqr:s"; 276 + 277 + const mowgli_getopt_option_t long_opts[] = { 278 + { "help", no_argument, NULL, 'h', 0 }, 279 + { "version", no_argument, NULL, 'v', 0 }, 280 + { "selftest-only", no_argument, NULL, 'T', 0 }, 281 + { "key-file", required_argument, NULL, 'f', 0 }, 282 + { "create-keypair", no_argument, NULL, 'c', 0 }, 283 + { "print-pubkey", no_argument, NULL, 'p', 0 }, 284 + { "qrcode", no_argument, NULL, 'q', 0 }, 285 + { "respond", required_argument, NULL, 'r', 0 }, 286 + { "server", no_argument, NULL, 's', 0 }, 287 + { NULL, 0, NULL, 0 , 0 }, 288 + }; 289 + 290 + const char *server_message_str = NULL; 291 + const char *keyfile_path = NULL; 292 + 293 + unsigned int options = X25519TOOL_OPTFLAG_NONE; 294 + 295 + int r; 296 + 297 + while ((r = mowgli_getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) 298 + { 299 + switch (r) 300 + { 301 + case 'h': 302 + (void) ecdh_x25519_tool_print_help(); 303 + return EXIT_SUCCESS; 304 + 305 + case 'v': 306 + (void) fprintf(stdout, "This is ecdh-x25519-tool from %s (%s)\n", PACKAGE_STRING, SERNO); 307 + return EXIT_SUCCESS; 308 + 309 + case 'T': 310 + options |= X25519TOOL_OPTFLAG_SELFTEST_ONLY; 311 + break; 312 + 313 + case 'f': 314 + keyfile_path = sstrdup(mowgli_optarg); 315 + break; 316 + 317 + case 'c': 318 + options |= X25519TOOL_OPTFLAG_CREATE_KEYPAIR; 319 + break; 320 + 321 + case 'p': 322 + options |= X25519TOOL_OPTFLAG_PRINT_PUBKEY; 323 + break; 324 + 325 + case 'q': 326 + options |= X25519TOOL_OPTFLAG_PRINT_QRCODE; 327 + break; 328 + 329 + case 'r': 330 + server_message_str = sstrdup(mowgli_optarg); 331 + options |= X25519TOOL_OPTFLAG_RESPOND; 332 + break; 333 + 334 + case 's': 335 + options |= X25519TOOL_OPTFLAG_SERVER; 336 + break; 337 + 338 + default: 339 + (void) ecdh_x25519_tool_print_help(); 340 + return EXIT_FAILURE; 341 + } 342 + } 343 + 344 + if (keyfile_path != NULL && strcmp(keyfile_path, "-") == 0) 345 + keyfile_path = NULL; 346 + 347 + if (! options || (options & (options - 1))) 348 + { 349 + (void) ecdh_x25519_tool_print_help(); 350 + return EXIT_FAILURE; 351 + } 352 + 353 + if (! ecdh_x25519_selftest()) 354 + // This function prints error messages on failure 355 + return EXIT_FAILURE; 356 + 357 + if ((options & X25519TOOL_OPTFLAG_SELFTEST_ONLY) == X25519TOOL_OPTFLAG_SELFTEST_ONLY) 358 + return EXIT_SUCCESS; 359 + 360 + if ((options & X25519TOOL_OPTFLAG_CREATE_KEYPAIR) == X25519TOOL_OPTFLAG_CREATE_KEYPAIR) 361 + // This function prints error messages on failure 362 + return ecdh_x25519_tool_create_keypair(keyfile_path); 363 + 364 + if ((options & X25519TOOL_OPTFLAG_PRINT_PUBKEY) == X25519TOOL_OPTFLAG_PRINT_PUBKEY) 365 + // This function prints error messages on failure 366 + return ecdh_x25519_tool_print_pubkey(keyfile_path); 367 + 368 + if ((options & X25519TOOL_OPTFLAG_PRINT_QRCODE) == X25519TOOL_OPTFLAG_PRINT_QRCODE) 369 + // This function prints error messages on failure 370 + return ecdh_x25519_tool_print_qrcode(keyfile_path); 371 + 372 + if ((options & X25519TOOL_OPTFLAG_RESPOND) == X25519TOOL_OPTFLAG_RESPOND) 373 + // This function prints error messages on failure 374 + return ecdh_x25519_tool_respond(keyfile_path, server_message_str); 375 + 376 + if ((options & X25519TOOL_OPTFLAG_SERVER) == X25519TOOL_OPTFLAG_SERVER) 377 + // This function prints error messages on failure 378 + return ecdh_x25519_tool_server(keyfile_path); 379 + 380 + (void) ecdh_x25519_tool_print_help(); 381 + return EXIT_FAILURE; 382 + }
+106
src/ecdh-x25519-tool/qrcode.c
··· 1 + /* 2 + * SPDX-License-Identifier: ISC 3 + * SPDX-URL: https://spdx.org/licenses/ISC.html 4 + * 5 + * Copyright (C) 2019 Aaron M. D. Jones <aaronmdjones@gmail.com> 6 + * 7 + * Helper utility for the SASL ECDH-X25519-CHALLENGE mechanism. 8 + */ 9 + 10 + #include <atheme.h> 11 + 12 + #include "ecdh-x25519-challenge.h" 13 + #include "ecdh-x25519-tool.h" 14 + 15 + #ifdef HAVE_LIBQRENCODE 16 + 17 + #include <qrencode.h> 18 + 19 + static void 20 + ecdh_x25519_tool_print_qrcode_stdout(QRcode *const restrict qrcode) 21 + { 22 + const char empty[] = " "; 23 + const char beginline[] = "\n "; 24 + const char lowhalf[] = "\342\226\204"; 25 + const char tophalf[] = "\342\226\200"; 26 + const char full[] = "\342\226\210"; 27 + 28 + const size_t width = (size_t) qrcode->width; 29 + const size_t realwidth = width + 2; 30 + 31 + (void) fputs(beginline, stdout); 32 + 33 + for (size_t x = 0; x < realwidth; x++) 34 + (void) fputs(lowhalf, stdout); 35 + 36 + for (size_t x = 0; x < width; x += 2) 37 + { 38 + unsigned char *row1 = qrcode->data + (width * x); 39 + unsigned char *row2 = row1 + width; 40 + 41 + (void) fputs(beginline, stdout); 42 + (void) fputs(full, stdout); 43 + 44 + for (size_t y = 0; y < width; y++) 45 + { 46 + if (row1[y] & 0x01U) 47 + { 48 + if (x < (width - 1) && row2[y] & 0x01U) 49 + (void) fputs(empty, stdout); 50 + else 51 + (void) fputs(lowhalf, stdout); 52 + } 53 + else if (x < (width - 1) && row2[y] & 0x01U) 54 + (void) fputs(tophalf, stdout); 55 + else 56 + (void) fputs(full, stdout); 57 + } 58 + 59 + (void) fputs(full, stdout); 60 + } 61 + 62 + (void) fputs("\n", stdout); 63 + } 64 + 65 + int 66 + ecdh_x25519_tool_print_qrcode(const char *const restrict keyfile_path) 67 + { 68 + unsigned char secpubkey[ATHEME_ECDH_X25519_XKEY_LEN * 2U]; 69 + 70 + FILE *const fh = ((keyfile_path != NULL) ? fopen(keyfile_path, "rb") : stdin); 71 + 72 + if (! fh) 73 + { 74 + (void) fprintf(stderr, "fopen('%s', 'rb'): %s\n", keyfile_path, strerror(errno)); 75 + return EXIT_FAILURE; 76 + } 77 + if (fread(secpubkey, sizeof secpubkey, 1, fh) != 1) 78 + { 79 + (void) fprintf(stderr, "Could not read private and public keys\n"); 80 + return EXIT_FAILURE; 81 + } 82 + 83 + (void) fclose(fh); 84 + 85 + QRcode *const qrcode = QRcode_encodeData((int) sizeof secpubkey, secpubkey, 0, QR_ECLEVEL_L); 86 + 87 + if (! qrcode) 88 + { 89 + (void) fprintf(stderr, "Unable to encode keypair: %s\n", strerror(errno)); 90 + return EXIT_FAILURE; 91 + } 92 + 93 + (void) ecdh_x25519_tool_print_qrcode_stdout(qrcode); 94 + return EXIT_SUCCESS; 95 + } 96 + 97 + #else /* HAVE_LIBQRENCODE */ 98 + 99 + int 100 + ecdh_x25519_tool_print_qrcode(const char ATHEME_VATTR_UNUSED *const restrict keyfile_path) 101 + { 102 + (void) fprintf(stderr, "This program was not built with QR-Code support!\n"); 103 + return EXIT_FAILURE; 104 + } 105 + 106 + #endif /* !HAVE_LIBQRENCODE */