Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private boolean clientDataWorker(AppTCPSession session, ByteBuffer data) throws

if (sniHostname == null){
try{
sniHostname = HttpUtility.extractSniHostname(data.duplicate());
sniHostname = HttpUtility.extractSniHostname(data.duplicate(),false);
}catch (Exception exn) {
// The client is almost certainly sending us a bad TLS packet.
session.release();
Expand Down
51 changes: 45 additions & 6 deletions http-casing/src/com/untangle/app/http/HttpUtility.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.apache.hc.core5.net.URIBuilder;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.commons.lang3.StringUtils;

/**
* Utility class for Extract host name
Expand All @@ -23,6 +24,8 @@ public class HttpUtility {
private static int CLIENT_HELLO = 0x01;
private static int SERVER_NAME = 0x0000;
private static int HOST_NAME = 0x00;
private static int ENCRYPTED_CLIENT_HELLO = 0xfe0d;
public static final String ECH_BLOCKED= "encrypted_client_hello";

private static final HttpUtility INSTANCE = new HttpUtility();

Expand All @@ -45,10 +48,11 @@ public static HttpUtility getInstance()
/**
* Function for extracting the SNI hostname from the client request.
* @param data
* @param isEchBlocked
* @return The SNI hostname extracted from the client request, or null
* @throws Exception
*/
public static String extractSniHostname(ByteBuffer data) throws Exception{
public static String extractSniHostname(ByteBuffer data, boolean isEchBlocked) throws Exception{
int counter = 0;
int pos=0;

Expand Down Expand Up @@ -99,7 +103,7 @@ public static String extractSniHostname(ByteBuffer data) throws Exception{
logger.debug("No extensions found in TLS handshake message");
return (null);
}
return extractSniHostNameFromExtensions(data, counter);
return extractSniHostNameFromExtensions(data, counter, isEchBlocked);
}

/**
Expand Down Expand Up @@ -144,13 +148,18 @@ public static void setDataPositions(ByteBuffer data, int pos) throws Exception{
* Process all extensions to find the SNI signature.
* @param data
* @param counter
* @param isEchBlocked
* @return The extracted SNI hostname, or null if not found.
*/
public static String extractSniHostNameFromExtensions(ByteBuffer data, int counter){
public static String extractSniHostNameFromExtensions(ByteBuffer data, int counter, boolean isEchBlocked) {

// get the total size of extension data block
int extensionLength = Math.abs(data.getShort());

boolean encryptedClientHelloFound = false;
// if ECH check enbled check for ech extention
if(isEchBlocked){
encryptedClientHelloFound = checkEchExtension(extensionLength, data.duplicate(),counter);
}
// walk through all of the extensions looking for SNI signature
while (counter < extensionLength) {
if (data.remaining() < 2) throw new BufferUnderflowException();
Expand Down Expand Up @@ -185,12 +194,42 @@ public static String extractSniHostNameFromExtensions(ByteBuffer data, int count
// found a valid host name so adjust the position to skip over
// the list length and name type info we directly accessed above
if (data.remaining() < 5) throw new BufferUnderflowException();

return extractedSNIHostname(data, nameLength);
String hostname = extractedSNIHostname(data, nameLength);
//check for ech extention and encrypted hostname, if found return encrypted_client_hello
if(encryptedClientHelloFound && StringUtils.isEmpty(hostname)){
return ECH_BLOCKED;
}
return hostname;
}
return null;
}

/**
* Check for ECH extention
* @param extensionLength
* @param data
* @param counter
* @return encryptedClientHelloFound
*/
public static boolean checkEchExtension(int extensionLength, ByteBuffer data, int counter){
while (counter < extensionLength) {
if (data.remaining() < 2) throw new BufferUnderflowException();

int extType = data.getShort() & 0xFFFF;
int extSize = Math.abs(data.getShort());
// Check for "encrypted client hello" extension first
if (extType == ENCRYPTED_CLIENT_HELLO) {
return true;
}

// If not "encrypted client hello", process other extensions
if (data.remaining() < extSize) throw new BufferUnderflowException();
data.position(data.position() + extSize);
counter += (extSize + 4);
}
return false;
}

/**
* Extracth hostName based on nameLength
* @param data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ public void setDataMode(boolean argMode)
*/
public String extractSNIhostname(ByteBuffer data) throws Exception
{
return HttpUtility.extractSniHostname(data);
return HttpUtility.extractSniHostname(data, false);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ private void checkClientRequest(AppTCPSession sess, ByteBuffer data)

// scan the buffer for the SNI hostname
try {
domain = HttpUtility.extractSniHostname(buff.duplicate());
domain = HttpUtility.extractSniHostname(buff.duplicate(), false);
}

// on underflow exception we stuff the partial packet into a buffer
Expand Down
1 change: 1 addition & 0 deletions uvm/js/common/util/Renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ Ext.define('Ung.util.Renderer', {
B: 'in Temporary Unblocked list'.t(),
F: 'in Rules list'.t(),
K: 'Kid-friendly redirect'.t(),
X: 'ECH blocked'.t(),
default: 'no rule applied'.t()
},
httpReason: function( value, metaData ) {
Expand Down
1 change: 1 addition & 0 deletions uvm/js/common/util/_Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -1600,6 +1600,7 @@ Ext.define('Ung.util.Map', {
B: 'in Temporary Unblocked list'.t(),
F: 'in Rules list'.t(),
K: 'Kid-friendly redirect'.t(),
X: 'ECH blocked'.t(),
default: 'no rule applied'.t()
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public abstract class DecisionEngine
{
private Map<String, String> i18nMap;
Long i18nMapLastUpdated = 0L;
private static final String ECH_HOSTNAME = "encrypted_client_hello";

private final Logger logger = LogManager.getLogger(getClass());

Expand Down Expand Up @@ -162,6 +163,21 @@ public HttpRedirect checkRequest(AppTCPSession sess, InetAddress clientIp, int p
header.addField("X-GoogApps-Allowed-Domains", allowedDomains);
}
}
// If ECH request then directly block the request and raise event
if (host.equals(ECH_HOSTNAME)) {
WebFilterEvent hbe = new WebFilterEvent(requestLine.getRequestLine(), sess.sessionEvent(), Boolean.TRUE, Boolean.TRUE, Reason.BLOCK_ECH, bestCategory.getId(), 0, "ECH BLOCKED", app.getName());
if (logger.isDebugEnabled()) logger.debug("LOG: ECH block : " + requestLine.getRequestLine());
app.logEvent(hbe);
app.incrementFlagCount();

return (
new HttpRedirect(
app.generateBlockResponse(
new WebFilterRedirectDetails( app.getSettings(), "", "", "ECH blocked", clientIp, app.getAppTitle(), Reason.BLOCK_ECH, "ECH REQUEST"),
sess, uri.toString(), header),
HttpRedirect.RedirectType.BLOCK));

}

// check client IP pass list
// If a client is on the pass list is is passed regardless of any other settings
Expand Down
3 changes: 2 additions & 1 deletion web-filter-base/src/com/untangle/app/web_filter/Reason.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public enum Reason
PASS_UNBLOCK('B', "in Bypass list"),
FILTER_RULE('F', "matched Rule list"),
REDIRECT_KIDS('K', "redirected to kid-friendly search engine"),
DEFAULT('N', "no rule applied");
DEFAULT('N', "no rule applied"),
BLOCK_ECH('X',"ECH blocked");

// THIS IS FOR ECLIPSE - @formatter:on

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ private void checkClientRequest(AppTCPSession sess, ByteBuffer data)

logger.debug("HANDLE_CHUNK = " + buff.toString());
app.incrementScanCount();

boolean isEchBlock = app.getSettings().getBlockECH();
// scan the buffer for the SNI hostname
try {
domain = HttpUtility.extractSniHostname(buff.duplicate());
domain = HttpUtility.extractSniHostname(buff.duplicate(), isEchBlock);
}

// on underflow exception we stuff the partial packet into a buffer
Expand Down