From 75eb403d15026521c0878c7675a22c59783da91b Mon Sep 17 00:00:00 2001 From: Christian Glombek Date: Wed, 21 Jun 2023 06:15:39 +0200 Subject: [PATCH] module-raop: Add SRP utils Adds MIT-licensed SRP helper utilities, taken from https://github.com/cocagne/csrp. Extensively adapted for usage in PipeWire. Upgraded to OpenSSL 3. --- src/modules/meson.build | 1 + src/modules/module-raop/srp.c | 437 ++++++++++++++++++++++++++++++++++ src/modules/module-raop/srp.h | 31 +++ 3 files changed, 469 insertions(+) create mode 100644 src/modules/module-raop/srp.c create mode 100644 src/modules/module-raop/srp.h diff --git a/src/modules/meson.build b/src/modules/meson.build index 22f1cce85..a48ac3cbd 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -606,6 +606,7 @@ if build_module_raop pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink', [ 'module-raop-sink.c', 'module-raop/rtsp-client.c', + 'module-raop/srp.c', 'module-raop/tlv.c' ], include_directories : [configinc], install : true, diff --git a/src/modules/module-raop/srp.c b/src/modules/module-raop/srp.c new file mode 100644 index 000000000..39ce9145f --- /dev/null +++ b/src/modules/module-raop/srp.c @@ -0,0 +1,437 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2013 Tom Cocagne */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "srp.h" + +#ifndef SHA512_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH 64 +#endif + +#ifndef SHA_DIGEST_LENGTH +#define SHA_DIGEST_LENGTH 20 +#endif + +#define SRP_SALT_BYTES 16 + +typedef struct { + int N_len; + BIGNUM *N; + BIGNUM *g; +} NGConstant; + +struct NGHex { + int N_len; + const char *n_hex; + const char *g_hex; +}; + +// These constants here were pulled from Appendix A of RFC 5054 +static struct NGHex Ng_constants[] = { + { /* 2048 */ + 256, + "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4" + "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60" + "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF" + "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907" + "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861" + "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB" + "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", + "2" + }, + { /* 3072 */ + 384, + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B" + "139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485" + "B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1F" + "E649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23" + "DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32" + "905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558" + "17183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521" + "ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D7" + "1E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B1817" + "7B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82" + "D120A93AD2CAFFFFFFFFFFFFFFFF", + "5" + }, +}; + +static NGConstant *new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex) +{ + NGConstant *ng = calloc(1, sizeof(NGConstant)); + + n_hex = Ng_constants[ng_type].n_hex; + g_hex = Ng_constants[ng_type].g_hex; + + BN_hex2bn(&ng->N, n_hex); + BN_hex2bn(&ng->g, g_hex); + + ng->N_len = BN_num_bytes(ng->N); + + assert(ng->N_len == Ng_constants[ng_type].N_len); + + return ng; +} + +int get_N_len(SRP_NGType ng_type) +{ + return Ng_constants[ng_type].N_len; +} + +struct SRPUser { + SRP_HashAlgorithm hash_alg; + NGConstant *ng; + + BIGNUM *a; + BIGNUM *A; + BIGNUM *S; + + const unsigned char *bytes_A; + int len_A; + int authenticated; + + char *username; + unsigned char *password; + int password_len; + + unsigned char M[SHA512_DIGEST_LENGTH]; + unsigned char H_AMK[SHA512_DIGEST_LENGTH]; + unsigned char session_key[SHA512_DIGEST_LENGTH]; + int session_key_len; +}; + +const EVP_MD *hash_func(SRP_HashAlgorithm alg) +{ + switch (alg) { + case SRP_SHA1: + return EVP_sha1(); + case SRP_SHA512: + return EVP_sha512(); + default: + return NULL; + }; +} + +static int hash_length(SRP_HashAlgorithm alg) +{ + switch (alg) { + case SRP_SHA1: + return SHA_DIGEST_LENGTH; + case SRP_SHA512: + return SHA512_DIGEST_LENGTH; + default: + return -1; + }; +} + +static BIGNUM *H_nn_pad(SRP_HashAlgorithm alg, const BIGNUM *n1, const BIGNUM *n2, int padded_len) +{ + unsigned char buff[SHA512_DIGEST_LENGTH]; + int len_n1 = BN_num_bytes(n1); + int len_n2 = BN_num_bytes(n2); + int nbytes = 2 * padded_len; + int offset_n1 = padded_len - len_n1; + int offset_n2 = nbytes - len_n2; + unsigned char *bin = (unsigned char *) malloc(nbytes); + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + + pw_log_debug("srp: H_nn_pad"); + + if (!bin) + return 0; + + assert(len_n1 <= padded_len); + assert(len_n2 <= padded_len); + + BN_bn2bin(n1, bin + offset_n1); + BN_bn2bin(n2, bin + offset_n2); + EVP_DigestInit_ex(ctx, hash_func(alg), NULL); + EVP_DigestUpdate(ctx, bin, nbytes); + EVP_DigestFinal_ex(ctx, buff, NULL); + EVP_MD_CTX_free(ctx); + free(bin); + + return BN_bin2bn(buff, hash_length(alg), NULL); +} + +static BIGNUM *calculate_x(SRP_HashAlgorithm alg, const uint8_t *salt, int salt_len, + const char *username, const unsigned char *password, int password_len) +{ + unsigned char x[SHA512_DIGEST_LENGTH]; + EVP_MD_CTX *ctx; + + assert(salt_len == SRP_SALT_BYTES); + + pw_log_debug("srp: calculate_x"); + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, hash_func(alg), NULL); + EVP_DigestUpdate(ctx, username, strlen(username)); + EVP_DigestUpdate(ctx, (const uint8_t *) ":", 1); + EVP_DigestUpdate(ctx, password, password_len); + EVP_DigestFinal_ex(ctx, x, NULL); + EVP_MD_CTX_free(ctx); + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, hash_func(alg), NULL); + EVP_DigestUpdate(ctx, (const uint8_t*) salt, salt_len); + EVP_DigestUpdate(ctx, x, hash_length(alg)); + EVP_DigestFinal_ex(ctx, x, NULL); + EVP_MD_CTX_free(ctx); + + return BN_bin2bn(x, hash_length(alg), NULL); +} + +static void hash_num(SRP_HashAlgorithm alg, const BIGNUM *n, unsigned char *dest) +{ + int nbytes = BN_num_bytes(n); + unsigned char *bin = (unsigned char *) malloc(nbytes); + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + + if (!bin) + return; + + BN_bn2bin(n, bin); + EVP_DigestInit_ex(ctx, hash_func(alg), NULL); + EVP_DigestUpdate(ctx, bin, nbytes); + EVP_DigestFinal_ex(ctx, dest, NULL); + EVP_MD_CTX_free(ctx); + free(bin); +} + +static void calculate_M(SRP_HashAlgorithm alg, NGConstant *ng, unsigned char *dest, const char *I, + const uint8_t *salt, int salt_len, const uint8_t *A, int A_len, const uint8_t *B, int B_len, + const unsigned char *K) +{ + unsigned char H_N[SHA512_DIGEST_LENGTH]; + unsigned char H_g[SHA512_DIGEST_LENGTH]; + unsigned char H_I[SHA512_DIGEST_LENGTH]; + unsigned char H_xor[SHA512_DIGEST_LENGTH]; + EVP_MD_CTX *ctx; + int i = 0; + int hash_len = hash_length(alg); + + pw_log_debug("srp: calculate_M"); + + hash_num(alg, ng->N, H_N); + hash_num(alg, ng->g, H_g); + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, hash_func(alg), NULL); + EVP_DigestUpdate(ctx, I, strlen(I)); + EVP_DigestFinal_ex(ctx, H_I, NULL); + EVP_MD_CTX_free(ctx); + + for (i=0; i < hash_len; i++) + H_xor[i] = H_N[i] ^ H_g[i]; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, hash_func(alg), NULL); + + EVP_DigestUpdate(ctx, H_xor, hash_len); + EVP_DigestUpdate(ctx, H_I, hash_len); + EVP_DigestUpdate(ctx, salt, salt_len); + EVP_DigestUpdate(ctx, A, A_len); + EVP_DigestUpdate(ctx, B, B_len); + EVP_DigestUpdate(ctx, K, hash_len); + + EVP_DigestFinal_ex(ctx, dest, NULL); + EVP_MD_CTX_free(ctx); +} + +static void calculate_H_AMK(SRP_HashAlgorithm alg, unsigned char *dest, const uint8_t *A, int A_len, + const unsigned char *M, const unsigned char *K) +{ + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + + EVP_DigestInit_ex(ctx, hash_func(alg), NULL); + + EVP_DigestUpdate(ctx, A, A_len); + EVP_DigestUpdate(ctx, M, hash_length(alg)); + EVP_DigestUpdate(ctx, K, hash_length(alg)); + + EVP_DigestFinal_ex(ctx, dest, NULL); + EVP_MD_CTX_free(ctx); +} + +struct SRPUser *srp_user_new(SRP_HashAlgorithm alg, SRP_NGType ng_type, const char *username, + const unsigned char *bytes_password, int len_password, const char *n_hex, const char *g_hex) +{ + struct SRPUser *usr = (struct SRPUser *) malloc(sizeof(struct SRPUser)); + int ulen = strlen(username) + 1; + + if (!usr) + goto err_exit; + + usr->hash_alg = alg; + usr->ng = new_ng(ng_type, n_hex, g_hex); + + usr->a = BN_new(); + usr->A = BN_new(); + usr->S = BN_new(); + + if (!usr->ng || !usr->a || !usr->A || !usr->S) + goto err_exit; + + usr->username = (char *) malloc(ulen); + usr->password = (unsigned char *) malloc(len_password); + usr->password_len = len_password; + + if (!usr->username || !usr->password) + goto err_exit; + + memcpy(usr->username, username, ulen); + memcpy(usr->password, bytes_password, len_password); + + usr->authenticated = 0; + usr->bytes_A = 0; + usr->len_A = 0; + + return usr; + +err_exit: + if (!usr) + return NULL; + + BN_free(usr->a); + BN_free(usr->A); + BN_free(usr->S); + if (usr->username) + free(usr->username); + if (usr->password) { + memset(usr->password, 0, usr->password_len); + free(usr->password); + } + free(usr); + + return 0; +} + +void srp_user_start_authentication(struct SRPUser *usr, const char **username, const unsigned char **bytes_A, int *len_A) +{ + BN_CTX *bn_ctx = BN_CTX_new(); + + pw_log_debug("srp: srp_user_start_authentication"); + + BN_rand(usr->a, 256, -1, 0); + BN_mod_exp(usr->A, usr->ng->g, usr->a, usr->ng->N, bn_ctx); + + BN_CTX_free(bn_ctx); + + *len_A = BN_num_bytes(usr->A); + *bytes_A = (const unsigned char *) malloc(*len_A); + + if (!*bytes_A) { + pw_log_debug("srp: srp_user_start_authentication something went wrong"); + *len_A = 0; + *bytes_A = 0; + *username = 0; + return; + } + + BN_bn2bin(usr->A, (unsigned char *) *bytes_A); + + usr->bytes_A = *bytes_A; + usr->len_A = *len_A; + *username = usr->username; +} + +void srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s, + const unsigned char *bytes_B, int len_B, const unsigned char **bytes_M, int *len_M) +{ + BIGNUM *B = BN_bin2bn(bytes_B, len_B, NULL); + BIGNUM *u = 0; + BIGNUM *x = 0; + BIGNUM *k = 0; + BIGNUM *v = BN_new(); + BIGNUM *tmp1 = BN_new(); + BIGNUM *tmp2 = BN_new(); + BIGNUM *tmp3 = BN_new(); + BN_CTX *bn_ctx = BN_CTX_new(); + + pw_log_debug("srp: srp_user_process_challenge"); + + *len_M = 0; + *bytes_M = 0; + + if (!bytes_s || !B || !v || !tmp1 || !tmp2 || !tmp3 || !bn_ctx) { + pw_log_debug("srp: BIGNUM init failed"); + goto cleanup_and_exit; + } + k = H_nn_pad(usr->hash_alg, usr->ng->N, usr->ng->g, usr->ng->N_len); + if (!k) { + pw_log_debug("srp: couldn't calc k"); + goto cleanup_and_exit; + } + u = H_nn_pad(usr->hash_alg, usr->A, B, usr->ng->N_len); + if (!u) { + pw_log_debug("srp: couldn't calc u"); + goto cleanup_and_exit; + } + x = calculate_x(usr->hash_alg, bytes_s, len_s, usr->username, usr->password, usr->password_len); + if (!x) { + pw_log_debug("srp: couldn't calc x"); + goto cleanup_and_exit; + } + /* SRP-6a safety check */ + if (!BN_is_zero(B) && !BN_is_zero(u)) { + BN_mod_exp(v, usr->ng->g, x, usr->ng->N, bn_ctx); + + /* S = (B - k*(g^x)) ^ (a + ux) */ + BN_mul(tmp1, u, x, bn_ctx); + BN_add(tmp2, usr->a, tmp1); /* tmp2 = (a + ux) */ + BN_mod_exp(tmp1, usr->ng->g, x, usr->ng->N, bn_ctx); + BN_mul(tmp3, k, tmp1, bn_ctx); /* tmp3 = k*(g^x) */ + BN_sub(tmp1, B, tmp3); /* tmp1 = (B - K*(g^x)) */ + BN_mod_exp(usr->S, tmp1, tmp2, usr->ng->N, bn_ctx); + + hash_num(usr->hash_alg, usr->S, usr->session_key); + + calculate_M(usr->hash_alg, usr->ng, usr->M, usr->username, bytes_s, len_s, usr->bytes_A, usr->len_A, bytes_B, len_B, usr->session_key); + calculate_H_AMK(usr->hash_alg, usr->H_AMK, usr->bytes_A, usr->len_A, usr->M, usr->session_key); + + *bytes_M = usr->M; + if (len_M) + *len_M = hash_length(usr->hash_alg); + } + +cleanup_and_exit: + BN_free(B); + BN_free(u); + BN_free(x); + BN_free(k); + BN_free(v); + BN_free(tmp1); + BN_free(tmp2); + BN_free(tmp3); + BN_CTX_free(bn_ctx); +} + +int srp_user_is_authenticated(struct SRPUser *usr) +{ + return usr->authenticated; +} + +const unsigned char *srp_user_get_session_key(struct SRPUser *usr, int *key_length) +{ + if (key_length) + *key_length = hash_length(usr->hash_alg); + + return usr->session_key; +} + +void srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK) +{ + if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->hash_alg)) == 0 ) + usr->authenticated = 1; +} diff --git a/src/modules/module-raop/srp.h b/src/modules/module-raop/srp.h new file mode 100644 index 000000000..9671252b4 --- /dev/null +++ b/src/modules/module-raop/srp.h @@ -0,0 +1,31 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2013 Tom Cocagne */ +/* SPDX-License-Identifier: MIT */ + +struct SRPUser; + +typedef enum { + SRP_NG_2048, + SRP_NG_3072 +} SRP_NGType; + +typedef enum { + SRP_SHA1, + SRP_SHA512 +} SRP_HashAlgorithm; + +struct SRPUser *srp_user_new(SRP_HashAlgorithm alg, SRP_NGType ng_type, const char *username, + const unsigned char *bytes_password, int len_password, const char *n_hex, const char *g_hex); + +int get_N_len(SRP_NGType ng_type); + +void srp_user_start_authentication(struct SRPUser *usr, const char **username, const unsigned char **bytes_A, int *len_A); + +void srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s, + const unsigned char *bytes_B, int len_B, const unsigned char **bytes_M, int *len_M); + +int srp_user_is_authenticated(struct SRPUser *usr); + +const unsigned char *srp_user_get_session_key(struct SRPUser *usr, int *key_length); + +void srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK);