rtsp_rsa.c 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #include "rtsp_rsa.h"
  2. #include <string.h>
  3. #include "esp_log.h"
  4. #include "mbedtls/ctr_drbg.h"
  5. #include "mbedtls/entropy.h"
  6. #include "mbedtls/pk.h"
  7. #include "mbedtls/rsa.h"
  8. #include "sodium/utils.h" // sodium_base642bin, sodium_bin2base64
  9. static const char *TAG = "rtsp_rsa";
  10. // Well-known AirPlay RSA private key (public knowledge, used by all receivers)
  11. static const char airplay_rsa_private_key[] =
  12. "-----BEGIN RSA PRIVATE KEY-----\n"
  13. "MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Q\n"
  14. "g90u3sG/1CUtwC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6\n"
  15. "DZHJ2YCCLlDRKSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLv\n"
  16. "qr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M\n"
  17. "+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEp\n"
  18. "VviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfWBLmk\n"
  19. "zkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14n\n"
  20. "DY4TFQAaLlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPsc\n"
  21. "EsA5ltpxOgUGCY7b7ez5NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZu\n"
  22. "NGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jmlpPHr0O/KnPQtzI3eguhe0TwUem/eYSd\n"
  23. "yzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurcizaaA/L0HIgAmOit1GJA2s\n"
  24. "aMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFua39GLS99\n"
  25. "ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrn\n"
  26. "jndMoPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v\n"
  27. "//mU8eVkQaoANf0ZoMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtk\n"
  28. "rfa7ef+AUb69DNggq4mHQAYBp7L+k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQ\n"
  29. "NepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hLAoGBANDrr7xAJbqBjHVwIzQ4\n"
  30. "To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvAcJyRM9SJ7OKl\n"
  31. "Gt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
  32. "54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TF\n"
  33. "BVmD7fV0Zhov17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLa\n"
  34. "lpGSwomSNYJcB9HNMlmhkGzc1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9\n"
  35. "+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gILAuE4Pu13aKiJnfft7hIjbK+5kyb\n"
  36. "3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ2gG0N5hvJpzwwhbh\n"
  37. "XqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
  38. "-----END RSA PRIVATE KEY-----";
  39. // Parsed RSA context and RNG (initialized once)
  40. static mbedtls_pk_context s_pk_ctx;
  41. static mbedtls_entropy_context s_entropy;
  42. static mbedtls_ctr_drbg_context s_ctr_drbg;
  43. static bool s_pk_initialized = false;
  44. static int ensure_pk_initialized(void) {
  45. if (s_pk_initialized) {
  46. return 0;
  47. }
  48. // Initialize RNG (required by mbedtls 3.x for key parsing and RSA ops)
  49. mbedtls_entropy_init(&s_entropy);
  50. mbedtls_ctr_drbg_init(&s_ctr_drbg);
  51. int ret = mbedtls_ctr_drbg_seed(&s_ctr_drbg, mbedtls_entropy_func, &s_entropy,
  52. NULL, 0);
  53. if (ret != 0) {
  54. ESP_LOGE(TAG, "Failed to seed RNG: -0x%04x", -ret);
  55. mbedtls_ctr_drbg_free(&s_ctr_drbg);
  56. mbedtls_entropy_free(&s_entropy);
  57. return -1;
  58. }
  59. mbedtls_pk_init(&s_pk_ctx);
  60. ret = mbedtls_pk_parse_key(&s_pk_ctx,
  61. (const unsigned char *)airplay_rsa_private_key,
  62. sizeof(airplay_rsa_private_key), NULL, 0,
  63. mbedtls_ctr_drbg_random, &s_ctr_drbg);
  64. if (ret != 0) {
  65. ESP_LOGE(TAG, "Failed to parse RSA private key: -0x%04x", -ret);
  66. mbedtls_pk_free(&s_pk_ctx);
  67. mbedtls_ctr_drbg_free(&s_ctr_drbg);
  68. mbedtls_entropy_free(&s_entropy);
  69. return -1;
  70. }
  71. s_pk_initialized = true;
  72. return 0;
  73. }
  74. // Simple base64 decode using libsodium
  75. static int b64_decode(const char *b64, uint8_t *out, size_t out_size,
  76. size_t *out_len) {
  77. // Apple base64 may lack padding — libsodium handles that with _IGNORE variant
  78. if (sodium_base642bin(out, out_size, b64, strlen(b64), "\r\n \t", out_len,
  79. NULL, sodium_base64_VARIANT_ORIGINAL_NO_PADDING) != 0) {
  80. // Try with padding variant
  81. if (sodium_base642bin(out, out_size, b64, strlen(b64), "\r\n \t", out_len,
  82. NULL, sodium_base64_VARIANT_ORIGINAL) != 0) {
  83. return -1;
  84. }
  85. }
  86. return 0;
  87. }
  88. // Simple base64 encode using libsodium, strip trailing '='
  89. static int b64_encode(const uint8_t *data, size_t data_len, char *out,
  90. size_t out_size) {
  91. char *result = sodium_bin2base64(out, out_size, data, data_len,
  92. sodium_base64_VARIANT_ORIGINAL_NO_PADDING);
  93. return result ? 0 : -1;
  94. }
  95. int rsa_apple_challenge_response(const char *challenge_b64, uint32_t ip_addr,
  96. const uint8_t mac[6], char *out_b64,
  97. size_t out_b64_size) {
  98. if (ensure_pk_initialized() != 0) {
  99. return -1;
  100. }
  101. // Decode challenge
  102. uint8_t challenge[32];
  103. size_t challenge_len = 0;
  104. if (b64_decode(challenge_b64, challenge, sizeof(challenge), &challenge_len) !=
  105. 0) {
  106. ESP_LOGE(TAG, "Failed to decode Apple-Challenge");
  107. return -1;
  108. }
  109. // Build response data: challenge + IP(4) + MAC(6), padded to 32 bytes
  110. uint8_t data[32];
  111. memset(data, 0, sizeof(data));
  112. size_t pos = 0;
  113. if (challenge_len > 22) {
  114. challenge_len = 22; // Max room for challenge if IP + MAC must fit
  115. }
  116. memcpy(data + pos, challenge, challenge_len);
  117. pos += challenge_len;
  118. memcpy(data + pos, &ip_addr, 4);
  119. pos += 4;
  120. memcpy(data + pos, mac, 6);
  121. // pos is now at most 32, rest is zero-padded
  122. // RSA PKCS1 v1.5 "private encrypt" — equivalent to OpenSSL's
  123. // RSA_private_encrypt(..., RSA_PKCS1_PADDING).
  124. // mbedtls_rsa_pkcs1_sign with MBEDTLS_MD_NONE applies type-1 padding
  125. // (0x00 0x01 0xFF..0xFF 0x00 <data>) then the private key operation.
  126. mbedtls_rsa_context *rsa = mbedtls_pk_rsa(s_pk_ctx);
  127. mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
  128. size_t rsa_len = mbedtls_rsa_get_len(rsa);
  129. uint8_t *rsa_out = malloc(rsa_len);
  130. if (!rsa_out) {
  131. return -1;
  132. }
  133. int ret = mbedtls_rsa_pkcs1_sign(rsa, mbedtls_ctr_drbg_random, &s_ctr_drbg,
  134. MBEDTLS_MD_NONE, 32, data, rsa_out);
  135. if (ret != 0) {
  136. ESP_LOGE(TAG, "RSA sign failed: -0x%04x", -ret);
  137. free(rsa_out);
  138. return -1;
  139. }
  140. // Base64 encode result without padding
  141. ret = b64_encode(rsa_out, rsa_len, out_b64, out_b64_size);
  142. free(rsa_out);
  143. if (ret != 0) {
  144. ESP_LOGE(TAG, "Failed to base64-encode RSA response");
  145. return -1;
  146. }
  147. return 0;
  148. }
  149. int rsa_decrypt_aes_key(const char *encrypted_b64, uint8_t *out_key,
  150. size_t out_key_size, size_t *out_key_len) {
  151. if (ensure_pk_initialized() != 0) {
  152. return -1;
  153. }
  154. // Decode the base64 RSA-encrypted key
  155. uint8_t encrypted[512];
  156. size_t encrypted_len = 0;
  157. if (b64_decode(encrypted_b64, encrypted, sizeof(encrypted), &encrypted_len) !=
  158. 0) {
  159. ESP_LOGE(TAG, "Failed to decode RSA-encrypted AES key");
  160. return -1;
  161. }
  162. ESP_LOGI(TAG, "RSA-encrypted AES key: %zu bytes (expected 256)",
  163. encrypted_len);
  164. // RSA OAEP-SHA1 decrypt (RAOP uses OAEP padding for the AES key)
  165. mbedtls_rsa_context *rsa = mbedtls_pk_rsa(s_pk_ctx);
  166. mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
  167. size_t olen = 0;
  168. int ret = mbedtls_rsa_pkcs1_decrypt(rsa, mbedtls_ctr_drbg_random, &s_ctr_drbg,
  169. &olen, encrypted, out_key, out_key_size);
  170. if (ret != 0) {
  171. ESP_LOGE(TAG, "RSA AES key decrypt failed: -0x%04x", -ret);
  172. return -1;
  173. }
  174. *out_key_len = olen;
  175. ESP_LOGI(TAG, "Decrypted AES key: %zu bytes", olen);
  176. return 0;
  177. }