Skip to content

Commit d6fe931

Browse files
committed
JNI: add SRTP processing using external cisco libsrtp2
Expand the nativeAdd method to take a cipher and the in and out keys. These are negotiated in the session description exchange. They should either all be set or none of them (indicating no encryption). Note the session is negotiated and tested with asterisk which doesn't do key lifetime or multiple master keys, so this is a very simple SRTP setup. Note that the encryption for SRTP includes authentication, thus the packet expands on encryption and contracts on decryption, which is why the length is adjusted coming out of srtp_unprotect/protect. We also need to allocate additional space in the buffers to cope with this expansion. Change-Id: Ic438756774a5f4eb2986a28a75f4066ceae111c1 Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
1 parent ed2d8da commit d6fe931

3 files changed

Lines changed: 138 additions & 14 deletions

File tree

‎src/java/android/net/rtp/AudioGroup.java‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ synchronized void add(AudioStream stream) {
168168
stream.getRemoteAddress().getHostAddress(),
169169
stream.getRemotePort(), codecSpec, stream.getDtmfType(),
170170
mContext != null ? mContext.getOpPackageName()
171-
: ActivityThread.currentOpPackageName());
171+
: ActivityThread.currentOpPackageName(),
172+
stream.getCipher(), stream.getLocalKey(),
173+
stream.getRemoteKey());
172174
mStreams.put(stream, id);
173175
} catch (NullPointerException e) {
174176
throw new IllegalStateException(e);
@@ -177,7 +179,8 @@ synchronized void add(AudioStream stream) {
177179
}
178180

179181
private native long nativeAdd(int mode, int socket, String remoteAddress,
180-
int remotePort, String codecSpec, int dtmfType, String opPackageName);
182+
int remotePort, String codecSpec, int dtmfType,
183+
String opPackageName, String cipher, byte[] inkey, byte[] outkey);
181184

182185
// Package-private method used by AudioStream.join().
183186
synchronized void remove(AudioStream stream) {

‎src/jni/rtp/Android.bp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ cc_library_shared {
4444
"libgsm",
4545
"libstagefright_amrnbdec",
4646
"libstagefright_amrnbenc",
47+
"libsrtp2",
4748
],
4849
include_dirs: [
4950
"frameworks/av/media/libstagefright/codecs/amrnb/enc/src",

‎src/jni/rtp/AudioGroup.cpp‎

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include <arpa/inet.h>
2929
#include <netinet/in.h>
3030

31+
#include <srtp2/srtp.h>
32+
3133
// #define LOG_NDEBUG 0
3234
#define LOG_TAG "AudioGroup"
3335
#include <cutils/atomic.h>
@@ -100,6 +102,9 @@ class AudioStream
100102
AudioCodec *codec, int sampleRate, int sampleCount,
101103
int codecType, int dtmfType);
102104

105+
const char *setCipher(const char *cipher, unsigned char *inkey,
106+
unsigned char *outkey);
107+
103108
void sendDtmf(int event);
104109
bool mix(int32_t *output, int head, int tail, int sampleRate);
105110
void encode(int tick, AudioStream *chain);
@@ -143,6 +148,9 @@ class AudioStream
143148

144149
AudioStream *mNext;
145150

151+
srtp_ctx_t *srtpIn;
152+
srtp_ctx_t *srtpOut;
153+
146154
friend class AudioGroup;
147155
};
148156

@@ -152,16 +160,70 @@ AudioStream::AudioStream()
152160
mCodec = NULL;
153161
mBuffer = NULL;
154162
mNext = NULL;
163+
srtpIn = NULL;
164+
srtpOut = NULL;
155165
}
156166

157167
AudioStream::~AudioStream()
158168
{
159169
close(mSocket);
160170
delete mCodec;
161171
delete [] mBuffer;
172+
if (srtpIn || srtpOut) {
173+
srtp_dealloc(srtpIn);
174+
srtp_dealloc(srtpOut);
175+
}
162176
ALOGD("stream[%d] is dead", mSocket);
163177
}
164178

179+
const char *AudioStream::setCipher(const char *cipher, unsigned char *inkey,
180+
unsigned char *outkey)
181+
{
182+
srtp_policy_t policy;
183+
184+
memset(&policy, 0, sizeof(policy));
185+
186+
/*
187+
* Cipher list here must match strings in Crypto.java
188+
*/
189+
if (strcmp(cipher, "AES_CM_128_HMAC_SHA1_32") == 0) {
190+
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp);
191+
} else if (strcmp(cipher, "AES_CM_128_HMAC_SHA1_80") == 0) {
192+
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp);
193+
} else if (strcmp(cipher, "AEAD_AES_128_GCM") == 0) {
194+
srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtp);
195+
} else if (strcmp(cipher, "AEAD_AES_128_GCM_8") == 0) {
196+
srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy.rtp);
197+
} else {
198+
ALOGE("stream[%d] unrecognized cipher %s", mSocket, cipher);
199+
return "Unrecognized cipher";
200+
}
201+
202+
// use the policy twice (it gets copied not pointed to)
203+
policy.ssrc.type = ssrc_any_inbound;
204+
policy.ssrc.value = 0;
205+
policy.key = inkey;
206+
policy.allow_repeat_tx = 1;
207+
auto ret = srtp_create(&srtpIn, &policy);
208+
if (ret != srtp_err_status_ok) {
209+
ALOGE("stream[%d] failed to allocate ctx for cipher %s", mSocket,
210+
cipher);
211+
return "Failed to allocate srtp ctx for inkey";
212+
}
213+
policy.ssrc.type = ssrc_any_outbound;
214+
policy.ssrc.value = 0;
215+
policy.key = outkey;
216+
policy.allow_repeat_tx = 1;
217+
ret = srtp_create(&srtpOut, &policy);
218+
if (ret != srtp_err_status_ok) {
219+
ALOGE("stream[%d] failed to allocate ctx for cipher %s", mSocket,
220+
cipher);
221+
return "Failed to allocate srtp ctx for outkey";
222+
}
223+
ALOGD("stream[%d] set cipher %s", mSocket, cipher);
224+
return NULL;
225+
}
226+
165227
bool AudioStream::set(int mode, int socket, sockaddr_storage *remote,
166228
AudioCodec *codec, int sampleRate, int sampleCount,
167229
int codecType, int dtmfType)
@@ -283,7 +345,8 @@ void AudioStream::encode(int tick, AudioStream *chain)
283345
// Make sure duration is reasonable.
284346
if (duration >= 0 && duration < mSampleRate * DTMF_PERIOD) {
285347
duration += mSampleCount;
286-
int32_t buffer[4] = {
348+
// 5 words of slop for encryption
349+
int32_t buffer[9] = {
287350
static_cast<int32_t>(htonl(mDtmfMagic | mSequence)),
288351
static_cast<int32_t>(htonl(mDtmfStart)),
289352
static_cast<int32_t>(mSsrc),
@@ -293,14 +356,27 @@ void AudioStream::encode(int tick, AudioStream *chain)
293356
buffer[3] |= htonl(1 << 23);
294357
mDtmfEvent = -1;
295358
}
296-
sendto(mSocket, buffer, sizeof(buffer), MSG_DONTWAIT,
297-
(sockaddr *)&mRemote, sizeof(mRemote));
359+
int length = 16; // size of the unencrypted buffer
360+
if (srtpOut) {
361+
int len = length;
362+
auto ret = srtp_protect(srtpOut, buffer, &len);
363+
if (ret != srtp_err_status_ok) {
364+
ALOGE("stream[%d] failed on encrypt %d", mSocket, ret);
365+
return;
366+
}
367+
length = len;
368+
}
369+
sendto(mSocket, buffer, length, MSG_DONTWAIT,
370+
(sockaddr *)&mRemote, sizeof(mRemote));
298371
return;
299372
}
300373
mDtmfEvent = -1;
301374
}
302-
303-
int32_t buffer[mSampleCount + 3];
375+
/*
376+
* For encrypted SRTP we need slop in the buffer for the authentication
377+
* tag and padding, so reserve 5 words
378+
*/
379+
int32_t buffer[mSampleCount + 3 + (srtpOut ? 5 : 0)];
304380
bool data = false;
305381
if (mMode != RECEIVE_ONLY) {
306382
// Mix all other streams.
@@ -353,6 +429,15 @@ void AudioStream::encode(int tick, AudioStream *chain)
353429
ALOGV("stream[%d] encoder error", mSocket);
354430
return;
355431
}
432+
if (srtpOut) {
433+
int len = length + 12;
434+
auto ret = srtp_protect(srtpOut, buffer, &len);
435+
if (ret != srtp_err_status_ok) {
436+
ALOGE("stream[%d] failed on encrypt %d", mSocket, ret);
437+
return;
438+
}
439+
length = len - 12;
440+
}
356441
sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote,
357442
sizeof(mRemote));
358443
}
@@ -386,15 +471,15 @@ void AudioStream::decode(int tick)
386471
mLatencyScore = score;
387472
mLatencyTimer = tick;
388473
} else if (tick - mLatencyTimer >= MEASURE_PERIOD) {
389-
ALOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore);
474+
ALOGD("stream[%d] reduces latency of %dms", mSocket, mLatencyScore);
390475
mBufferTail -= mLatencyScore;
391476
mLatencyScore = -1;
392477
}
393478

394479
int count = (BUFFER_SIZE - (mBufferTail - mBufferHead)) * mSampleRate;
395480
if (count < mSampleCount) {
396481
// Buffer overflow. Drop the packet.
397-
ALOGV("stream[%d] buffer overflow", mSocket);
482+
ALOGD("stream[%d] buffer overflow", mSocket);
398483
recv(mSocket, &c, 1, MSG_DONTWAIT);
399484
return;
400485
}
@@ -418,21 +503,30 @@ void AudioStream::decode(int tick)
418503
// reliable but at least they can be used to identify duplicates?
419504
if (length < 12 || length > bufferSize ||
420505
(ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) {
421-
ALOGV("stream[%d] malformed packet", mSocket);
506+
ALOGD("stream[%d] malformed packet", mSocket);
422507
return;
423508
}
424509
int offset = 12 + ((buffer[0] & 0x0F) << 2);
425510
if (offset+2 >= bufferSize) {
426-
ALOGV("invalid buffer offset: %d", offset+2);
511+
ALOGD("invalid buffer offset: %d", offset+2);
427512
return;
428513
}
429514
if ((buffer[0] & 0x10) != 0) {
430515
offset += 4 + (ntohs(*(uint16_t *)&buffer[offset + 2]) << 2);
431516
}
432517
if (offset >= bufferSize) {
433-
ALOGV("invalid buffer offset: %d", offset);
518+
ALOGD("invalid buffer offset: %d", offset);
434519
return;
435520
}
521+
if (srtpIn) {
522+
int len = length;
523+
auto ret = srtp_unprotect(srtpIn, buffer, &len);
524+
if (ret != srtp_err_status_ok) {
525+
ALOGE("stream[%d] decrypt failed %d", mSocket, ret);
526+
return;
527+
}
528+
length = len;
529+
}
436530
if ((buffer[0] & 0x20) != 0) {
437531
length -= buffer[length - 1];
438532
}
@@ -953,7 +1047,8 @@ static jfieldID gMode;
9531047

9541048
jlong add(JNIEnv *env, jobject thiz, jint mode,
9551049
jint socket, jstring jRemoteAddress, jint remotePort,
956-
jstring jCodecSpec, jint dtmfType, jstring opPackageNameStr)
1050+
jstring jCodecSpec, jint dtmfType, jstring opPackageNameStr,
1051+
jstring jCipher, jbyteArray local_key, jbyteArray remote_key)
9571052
{
9581053
AudioCodec *codec = NULL;
9591054
AudioStream *stream = NULL;
@@ -1005,6 +1100,25 @@ jlong add(JNIEnv *env, jobject thiz, jint mode,
10051100
"cannot initialize audio stream");
10061101
goto error;
10071102
}
1103+
if (jCipher) {
1104+
const char *cipher = env->GetStringUTFChars(jCipher, NULL);
1105+
if (!cipher) {
1106+
// Exception already thrown.
1107+
return 0;
1108+
}
1109+
jbyte *inkey = env->GetByteArrayElements(remote_key, NULL);
1110+
jbyte *outkey = env->GetByteArrayElements(local_key, NULL);
1111+
1112+
const char *ret = stream->setCipher(cipher, (unsigned char *)inkey,
1113+
(unsigned char *)outkey);
1114+
env->ReleaseByteArrayElements(remote_key, inkey, JNI_ABORT);
1115+
env->ReleaseByteArrayElements(local_key, outkey, JNI_ABORT);
1116+
env->ReleaseStringUTFChars(jCipher, cipher);
1117+
if (ret) {
1118+
jniThrowException(env, "java/lang/IllegalStateException", ret);
1119+
return 0;
1120+
}
1121+
}
10081122
socket = -1;
10091123
codec = NULL;
10101124

@@ -1068,7 +1182,7 @@ void sendDtmf(JNIEnv *env, jobject thiz, jint event)
10681182
}
10691183

10701184
JNINativeMethod gMethods[] = {
1071-
{"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;ILjava/lang/String;)J", (void *)add},
1185+
{"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;[B[B)J", (void *)add},
10721186
{"nativeRemove", "(J)V", (void *)remove},
10731187
{"nativeSetMode", "(I)V", (void *)setMode},
10741188
{"nativeSendDtmf", "(I)V", (void *)sendDtmf},
@@ -1092,5 +1206,11 @@ int registerAudioGroup(JNIEnv *env)
10921206
ALOGE("JNI registration failed");
10931207
return -1;
10941208
}
1209+
auto err = srtp_init();
1210+
if (err != srtp_err_status_ok) {
1211+
ALOGE("SRTP initialization failed");
1212+
return -1;
1213+
}
1214+
10951215
return 0;
10961216
}

0 commit comments

Comments
 (0)