Remove unnecessary SecureRandom from UrlFetchUtils

We're only using it for generating multiparty boundaries, and there's no real need for the random boundary values to be cryptographically secure.  The point of the randomness is just to make collisions with content in the payload sufficiently unlikely.  The app itself controls the payload contents, and while it might be derived from user-submitted content, in practice it would be nearly infeasible to get the payload to contain arbitrary boundary values even if the RNG-produced boundaries could be determined in advance.

To further insulate against this, I've increased the boundary size (from 40 bits to 192) and added an actual check that the boundary isn't present in the input data, so that in the extremely unlikely event of a collision, we fail rather than producing an invalid multipart request.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=142784289
This commit is contained in:
nickfelt 2016-12-22 11:33:52 -08:00 committed by Ben McIlwain
parent 9d9c527917
commit 0405a427f1
2 changed files with 54 additions and 51 deletions

View file

@ -17,27 +17,30 @@ package google.registry.util;
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static com.google.common.net.MediaType.CSV_UTF_8;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.util.UrlFetchUtils.setPayloadMultipart;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPRequest;
import google.registry.testing.AppEngineRule;
import google.registry.testing.ExceptionRule;
import google.registry.testing.InjectRule;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@ -49,55 +52,57 @@ public class UrlFetchUtilsTest {
public final AppEngineRule appEngine = AppEngineRule.builder()
.build();
@Rule
public final ExceptionRule thrown = new ExceptionRule();
@Rule
public final InjectRule inject = new InjectRule();
@Before
public void setupRandomZeroes() throws Exception {
SecureRandom secureRandom = mock(SecureRandom.class);
inject.setStaticField(UrlFetchUtils.class, "secureRandom", secureRandom);
Random random = mock(Random.class);
inject.setStaticField(UrlFetchUtils.class, "random", random);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock info) throws Throwable {
byte[] bytes = (byte[]) info.getArguments()[0];
Arrays.fill(bytes, (byte) 0);
Arrays.fill((byte[]) info.getArguments()[0], (byte) 0);
return null;
}}).when(secureRandom).nextBytes(any(byte[].class));
}}).when(random).nextBytes(any(byte[].class));
}
@Test
public void testSetPayloadMultipart() throws Exception {
String payload = "--------------------------------AAAAAAAA\r\n"
HTTPRequest request = mock(HTTPRequest.class);
setPayloadMultipart(
request, "lol", "cat", CSV_UTF_8, "The nice people at the store say hello. ヘ(◕。◕ヘ)");
ArgumentCaptor<HTTPHeader> headerCaptor = ArgumentCaptor.forClass(HTTPHeader.class);
verify(request, times(2)).addHeader(headerCaptor.capture());
List<HTTPHeader> addedHeaders = headerCaptor.getAllValues();
assertThat(addedHeaders.get(0).getName()).isEqualTo(CONTENT_TYPE);
assertThat(addedHeaders.get(0).getValue())
.isEqualTo(
"multipart/form-data; "
+ "boundary=\"------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"");
assertThat(addedHeaders.get(1).getName()).isEqualTo(CONTENT_LENGTH);
assertThat(addedHeaders.get(1).getValue()).isEqualTo("292");
String payload = "--------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n"
+ "Content-Disposition: form-data; name=\"lol\"; filename=\"cat\"\r\n"
+ "Content-Type: text/csv; charset=utf-8\r\n"
+ "\r\n"
+ "The nice people at the store say hello. ヘ(◕。◕ヘ)\r\n"
+ "--------------------------------AAAAAAAA--";
HTTPRequest request = mock(HTTPRequest.class);
setPayloadMultipart(
request, "lol", "cat", CSV_UTF_8, "The nice people at the store say hello. ヘ(◕。◕ヘ)");
verify(request).addHeader(argThat(new HTTPHeaderMatcher(
CONTENT_TYPE, "multipart/form-data; boundary=------------------------------AAAAAAAA")));
verify(request).addHeader(argThat(new HTTPHeaderMatcher(CONTENT_LENGTH, "244")));
+ "--------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA--";
verify(request).setPayload(payload.getBytes(UTF_8));
verifyNoMoreInteractions(request);
}
/** Mockito matcher for {@link HTTPHeader}. */
public static class HTTPHeaderMatcher extends ArgumentMatcher<HTTPHeader> {
private final String name;
private final String value;
public HTTPHeaderMatcher(String name, String value) {
this.name = name;
this.value = value;
}
@Override
public boolean matches(Object arg) {
HTTPHeader header = (HTTPHeader) arg;
return name.equals(header.getName())
&& value.equals(header.getValue());
}
@Test
public void testSetPayloadMultipart_boundaryInPayload() throws Exception {
HTTPRequest request = mock(HTTPRequest.class);
String payload = "I screamed------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHHH";
thrown.expect(
IllegalStateException.class,
"Multipart data contains autogenerated boundary: "
+ "------------------------------AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
setPayloadMultipart(request, "lol", "cat", CSV_UTF_8, payload);
}
}