blob: 130c45ee98482549e7edc8ff12014714973bb6d1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.kafka.connect.runtime.rest;
import org.apache.kafka.connect.errors.ConnectException;
import org.apache.kafka.connect.runtime.distributed.Crypto;
import org.apache.kafka.connect.runtime.rest.errors.BadRequestException;
import org.eclipse.jetty.client.api.Request;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.ws.rs.core.HttpHeaders;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class InternalRequestSignatureTest {
private static final byte[] REQUEST_BODY =
"[{\"config\":\"value\"},{\"config\":\"other_value\"}]".getBytes();
private static final String SIGNATURE_ALGORITHM = "HmacSHA256";
private static final SecretKey KEY = new SecretKeySpec(
new byte[] {
109, 116, -111, 49, -94, 25, -103, 44, -99, -118, 53, -69, 87, -124, 5, 48,
89, -105, -2, 58, -92, 87, 67, 49, -125, -79, -39, -126, -51, -53, -85, 57
}, "HmacSHA256"
);
private static final byte[] SIGNATURE = new byte[] {
42, -3, 127, 57, 43, 49, -51, -43, 72, -62, -10, 120, 123, 125, 26, -65,
36, 72, 86, -71, -32, 13, -8, 115, 85, 73, -65, -112, 6, 68, 41, -50
};
private static final String ENCODED_SIGNATURE = Base64.getEncoder().encodeToString(SIGNATURE);
private final Crypto crypto = Crypto.SYSTEM;
@Test
public void fromHeadersShouldReturnNullOnNullHeaders() {
assertNull(InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, null));
}
@Test
public void fromHeadersShouldReturnNullIfSignatureHeaderMissing() {
assertNull(InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, internalRequestHeaders(null, SIGNATURE_ALGORITHM)));
}
@Test
public void fromHeadersShouldReturnNullIfSignatureAlgorithmHeaderMissing() {
assertNull(InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, internalRequestHeaders(ENCODED_SIGNATURE, null)));
}
@Test
public void fromHeadersShouldThrowExceptionOnInvalidSignatureAlgorithm() {
assertThrows(BadRequestException.class, () -> InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY,
internalRequestHeaders(ENCODED_SIGNATURE, "doesn'texist")));
}
@Test
public void fromHeadersShouldThrowExceptionOnInvalidBase64Signature() {
assertThrows(BadRequestException.class, () -> InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY,
internalRequestHeaders("not valid base 64", SIGNATURE_ALGORITHM)));
}
@Test
public void fromHeadersShouldReturnNonNullResultOnValidSignatureAndSignatureAlgorithm() {
InternalRequestSignature signature =
InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, internalRequestHeaders(ENCODED_SIGNATURE, SIGNATURE_ALGORITHM));
assertNotNull(signature);
assertNotNull(signature.keyAlgorithm());
}
@Test
public void addToRequestShouldThrowExceptionOnInvalidSignatureAlgorithm() throws NoSuchAlgorithmException {
Request request = mock(Request.class);
Crypto crypto = mock(Crypto.class);
when(crypto.mac(anyString())).thenThrow(new NoSuchAlgorithmException("doesn'texist"));
assertThrows(ConnectException.class, () -> InternalRequestSignature.addToRequest(crypto, KEY, REQUEST_BODY, "doesn'texist", request));
}
@Test
public void addToRequestShouldAddHeadersOnValidSignatureAlgorithm() {
Request request = mock(Request.class);
ArgumentCaptor<String> signatureCapture = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> signatureAlgorithmCapture = ArgumentCaptor.forClass(String.class);
when(request.header(
eq(InternalRequestSignature.SIGNATURE_HEADER),
signatureCapture.capture()
)).thenReturn(request);
when(request.header(
eq(InternalRequestSignature.SIGNATURE_ALGORITHM_HEADER),
signatureAlgorithmCapture.capture()
)).thenReturn(request);
InternalRequestSignature.addToRequest(crypto, KEY, REQUEST_BODY, SIGNATURE_ALGORITHM, request);
assertEquals(
"Request should have valid base 64-encoded signature added as header",
ENCODED_SIGNATURE,
signatureCapture.getValue()
);
assertEquals(
"Request should have provided signature algorithm added as header",
SIGNATURE_ALGORITHM,
signatureAlgorithmCapture.getValue()
);
}
@Test
public void testSignatureValidation() throws Exception {
Mac mac = Mac.getInstance(SIGNATURE_ALGORITHM);
InternalRequestSignature signature = new InternalRequestSignature(REQUEST_BODY, mac, SIGNATURE);
assertTrue(signature.isValid(KEY));
signature = InternalRequestSignature.fromHeaders(crypto, REQUEST_BODY, internalRequestHeaders(ENCODED_SIGNATURE, SIGNATURE_ALGORITHM));
assertTrue(signature.isValid(KEY));
signature = new InternalRequestSignature("[{\"different_config\":\"different_value\"}]".getBytes(), mac, SIGNATURE);
assertFalse(signature.isValid(KEY));
signature = new InternalRequestSignature(REQUEST_BODY, mac, "bad signature".getBytes());
assertFalse(signature.isValid(KEY));
}
private static HttpHeaders internalRequestHeaders(String signature, String signatureAlgorithm) {
HttpHeaders result = mock(HttpHeaders.class);
when(result.getHeaderString(eq(InternalRequestSignature.SIGNATURE_HEADER)))
.thenReturn(signature);
when(result.getHeaderString(eq(InternalRequestSignature.SIGNATURE_ALGORITHM_HEADER)))
.thenReturn(signatureAlgorithm);
return result;
}
}