| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- #include "rtsp_rsa.h"
- #include <string.h>
- #include "esp_log.h"
- #include "mbedtls/ctr_drbg.h"
- #include "mbedtls/entropy.h"
- #include "mbedtls/pk.h"
- #include "mbedtls/rsa.h"
- #include "sodium/utils.h" // sodium_base642bin, sodium_bin2base64
- static const char *TAG = "rtsp_rsa";
- // Well-known AirPlay RSA private key (public knowledge, used by all receivers)
- static const char airplay_rsa_private_key[] =
- "-----BEGIN RSA PRIVATE KEY-----\n"
- "MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Q\n"
- "g90u3sG/1CUtwC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6\n"
- "DZHJ2YCCLlDRKSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLv\n"
- "qr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M\n"
- "+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEp\n"
- "VviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfWBLmk\n"
- "zkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14n\n"
- "DY4TFQAaLlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPsc\n"
- "EsA5ltpxOgUGCY7b7ez5NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZu\n"
- "NGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jmlpPHr0O/KnPQtzI3eguhe0TwUem/eYSd\n"
- "yzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurcizaaA/L0HIgAmOit1GJA2s\n"
- "aMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFua39GLS99\n"
- "ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrn\n"
- "jndMoPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v\n"
- "//mU8eVkQaoANf0ZoMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtk\n"
- "rfa7ef+AUb69DNggq4mHQAYBp7L+k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQ\n"
- "NepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hLAoGBANDrr7xAJbqBjHVwIzQ4\n"
- "To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvAcJyRM9SJ7OKl\n"
- "Gt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
- "54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TF\n"
- "BVmD7fV0Zhov17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLa\n"
- "lpGSwomSNYJcB9HNMlmhkGzc1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9\n"
- "+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gILAuE4Pu13aKiJnfft7hIjbK+5kyb\n"
- "3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ2gG0N5hvJpzwwhbh\n"
- "XqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
- "-----END RSA PRIVATE KEY-----";
- // Parsed RSA context and RNG (initialized once)
- static mbedtls_pk_context s_pk_ctx;
- static mbedtls_entropy_context s_entropy;
- static mbedtls_ctr_drbg_context s_ctr_drbg;
- static bool s_pk_initialized = false;
- static int ensure_pk_initialized(void) {
- if (s_pk_initialized) {
- return 0;
- }
- // Initialize RNG (required by mbedtls 3.x for key parsing and RSA ops)
- mbedtls_entropy_init(&s_entropy);
- mbedtls_ctr_drbg_init(&s_ctr_drbg);
- int ret = mbedtls_ctr_drbg_seed(&s_ctr_drbg, mbedtls_entropy_func, &s_entropy,
- NULL, 0);
- if (ret != 0) {
- ESP_LOGE(TAG, "Failed to seed RNG: -0x%04x", -ret);
- mbedtls_ctr_drbg_free(&s_ctr_drbg);
- mbedtls_entropy_free(&s_entropy);
- return -1;
- }
- mbedtls_pk_init(&s_pk_ctx);
- ret = mbedtls_pk_parse_key(&s_pk_ctx,
- (const unsigned char *)airplay_rsa_private_key,
- sizeof(airplay_rsa_private_key), NULL, 0,
- mbedtls_ctr_drbg_random, &s_ctr_drbg);
- if (ret != 0) {
- ESP_LOGE(TAG, "Failed to parse RSA private key: -0x%04x", -ret);
- mbedtls_pk_free(&s_pk_ctx);
- mbedtls_ctr_drbg_free(&s_ctr_drbg);
- mbedtls_entropy_free(&s_entropy);
- return -1;
- }
- s_pk_initialized = true;
- return 0;
- }
- // Simple base64 decode using libsodium
- static int b64_decode(const char *b64, uint8_t *out, size_t out_size,
- size_t *out_len) {
- // Apple base64 may lack padding — libsodium handles that with _IGNORE variant
- if (sodium_base642bin(out, out_size, b64, strlen(b64), "\r\n \t", out_len,
- NULL, sodium_base64_VARIANT_ORIGINAL_NO_PADDING) != 0) {
- // Try with padding variant
- if (sodium_base642bin(out, out_size, b64, strlen(b64), "\r\n \t", out_len,
- NULL, sodium_base64_VARIANT_ORIGINAL) != 0) {
- return -1;
- }
- }
- return 0;
- }
- // Simple base64 encode using libsodium, strip trailing '='
- static int b64_encode(const uint8_t *data, size_t data_len, char *out,
- size_t out_size) {
- char *result = sodium_bin2base64(out, out_size, data, data_len,
- sodium_base64_VARIANT_ORIGINAL_NO_PADDING);
- return result ? 0 : -1;
- }
- int rsa_apple_challenge_response(const char *challenge_b64, uint32_t ip_addr,
- const uint8_t mac[6], char *out_b64,
- size_t out_b64_size) {
- if (ensure_pk_initialized() != 0) {
- return -1;
- }
- // Decode challenge
- uint8_t challenge[32];
- size_t challenge_len = 0;
- if (b64_decode(challenge_b64, challenge, sizeof(challenge), &challenge_len) !=
- 0) {
- ESP_LOGE(TAG, "Failed to decode Apple-Challenge");
- return -1;
- }
- // Build response data: challenge + IP(4) + MAC(6), padded to 32 bytes
- uint8_t data[32];
- memset(data, 0, sizeof(data));
- size_t pos = 0;
- if (challenge_len > 22) {
- challenge_len = 22; // Max room for challenge if IP + MAC must fit
- }
- memcpy(data + pos, challenge, challenge_len);
- pos += challenge_len;
- memcpy(data + pos, &ip_addr, 4);
- pos += 4;
- memcpy(data + pos, mac, 6);
- // pos is now at most 32, rest is zero-padded
- // RSA PKCS1 v1.5 "private encrypt" — equivalent to OpenSSL's
- // RSA_private_encrypt(..., RSA_PKCS1_PADDING).
- // mbedtls_rsa_pkcs1_sign with MBEDTLS_MD_NONE applies type-1 padding
- // (0x00 0x01 0xFF..0xFF 0x00 <data>) then the private key operation.
- mbedtls_rsa_context *rsa = mbedtls_pk_rsa(s_pk_ctx);
- mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
- size_t rsa_len = mbedtls_rsa_get_len(rsa);
- uint8_t *rsa_out = malloc(rsa_len);
- if (!rsa_out) {
- return -1;
- }
- int ret = mbedtls_rsa_pkcs1_sign(rsa, mbedtls_ctr_drbg_random, &s_ctr_drbg,
- MBEDTLS_MD_NONE, 32, data, rsa_out);
- if (ret != 0) {
- ESP_LOGE(TAG, "RSA sign failed: -0x%04x", -ret);
- free(rsa_out);
- return -1;
- }
- // Base64 encode result without padding
- ret = b64_encode(rsa_out, rsa_len, out_b64, out_b64_size);
- free(rsa_out);
- if (ret != 0) {
- ESP_LOGE(TAG, "Failed to base64-encode RSA response");
- return -1;
- }
- return 0;
- }
- int rsa_decrypt_aes_key(const char *encrypted_b64, uint8_t *out_key,
- size_t out_key_size, size_t *out_key_len) {
- if (ensure_pk_initialized() != 0) {
- return -1;
- }
- // Decode the base64 RSA-encrypted key
- uint8_t encrypted[512];
- size_t encrypted_len = 0;
- if (b64_decode(encrypted_b64, encrypted, sizeof(encrypted), &encrypted_len) !=
- 0) {
- ESP_LOGE(TAG, "Failed to decode RSA-encrypted AES key");
- return -1;
- }
- ESP_LOGI(TAG, "RSA-encrypted AES key: %zu bytes (expected 256)",
- encrypted_len);
- // RSA OAEP-SHA1 decrypt (RAOP uses OAEP padding for the AES key)
- mbedtls_rsa_context *rsa = mbedtls_pk_rsa(s_pk_ctx);
- mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
- size_t olen = 0;
- int ret = mbedtls_rsa_pkcs1_decrypt(rsa, mbedtls_ctr_drbg_random, &s_ctr_drbg,
- &olen, encrypted, out_key, out_key_size);
- if (ret != 0) {
- ESP_LOGE(TAG, "RSA AES key decrypt failed: -0x%04x", -ret);
- return -1;
- }
- *out_key_len = olen;
- ESP_LOGI(TAG, "Decrypted AES key: %zu bytes", olen);
- return 0;
- }
|