Skip to content

Commit

Permalink
4.x: Support Http.Header.X_HELIDON_CN (helidon-io#7345)
Browse files Browse the repository at this point in the history
* Support X_HELIDON_CN (rebase)
Signed-off-by: tvallin <[email protected]>
  • Loading branch information
tvallin authored and dalexandrov committed Aug 26, 2023
1 parent f9f0191 commit 7c7dfc4
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 97 deletions.
54 changes: 54 additions & 0 deletions common/tls/src/main/java/io/helidon/common/tls/TlsUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed 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 io.helidon.common.tls;

import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Optional;

/**
* Utility class for TLS.
*/
public class TlsUtils {

private TlsUtils() {
}

/**
* Parse the Common Name (CN) from the first certificate if present.
*
* @param certificates certificates
* @return Common Name value
*/
public static Optional<String> parseCn(Certificate[] certificates) {
if (certificates.length >= 1) {
Certificate certificate = certificates[0];
X509Certificate cert = (X509Certificate) certificate;
Principal principal = cert.getSubjectX500Principal();
int i = 0;
String[] segments = principal.getName().split("=|,");
while (i + 1 < segments.length) {
if ("CN".equals(segments[i])) {
return Optional.of(segments[i + 1]);
}
i += 2;
}
return Optional.of("Unknown CN");
}
return Optional.empty();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,19 @@
*/
package io.helidon.examples.webserver.mtls;

import java.security.Principal;

import io.helidon.http.Http;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.HttpService;

import static io.helidon.http.Http.HeaderNames.X_HELIDON_CN;

class SecureService implements HttpService {
@Override
public void routing(HttpRules rules) {
rules.any((req, res) -> {
String cn = req.remotePeer()
.tlsPrincipal()
.map(Principal::getName)
.flatMap(CertificateHelper::clientCertificateName)
.orElse("Unknown CN");

// close to avoid re-using cached connections on the client side
res.header(Http.Headers.CONNECTION_CLOSE);
res.send("Hello " + cn + "!");
res.send("Hello " + req.headers().get(X_HELIDON_CN).value() + "!");
});
}
}
25 changes: 22 additions & 3 deletions http/http2/src/main/java/io/helidon/http/http2/Http2Headers.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ private Http2Headers(Headers httpHeaders, PseudoHeaders pseudoHeaders) {
* @param stream stream that owns these headers
* @param table dynamic table for this connection
* @param huffman huffman decoder
* @param headers http2 headers
* @param frames frames of the headers
* @return new headers parsed from the frames
* @throws Http2Exception in case of protocol errors
*/
public static Http2Headers create(Http2Stream stream,
DynamicTable table,
Http2HuffmanDecoder huffman,
Http2Headers headers,
Http2FrameData... frames) {

if (frames.length == 0) {
Expand All @@ -135,7 +137,7 @@ public static Http2Headers create(Http2Stream stream,
stream.priority(priority);
}

WritableHeaders<?> headers = WritableHeaders.create();
WritableHeaders<?> writableHeaders = WritableHeaders.create(headers.httpHeaders());

BufferData[] buffers = new BufferData[frames.length];
for (int i = 0; i < frames.length; i++) {
Expand All @@ -151,17 +153,34 @@ public static Http2Headers create(Http2Stream stream,
if (padLength > 0) {
data.skip(padLength);
}
return create(ServerRequestHeaders.create(headers), pseudoHeaders);
return create(ServerRequestHeaders.create(writableHeaders), pseudoHeaders);
}

if (data.available() == 0) {
throw new Http2Exception(Http2ErrorCode.PROTOCOL, "Expecting more header bytes");
}

lastIsPseudoHeader = readHeader(headers, pseudoHeaders, table, huffman, data, lastIsPseudoHeader);
lastIsPseudoHeader = readHeader(writableHeaders, pseudoHeaders, table, huffman, data, lastIsPseudoHeader);
}
}

/**
* Create headers from HTTP request.
*
* @param stream stream that owns these headers
* @param table dynamic table for this connection
* @param huffman huffman decoder
* @param frames frames of the headers
* @return new headers parsed from the frames
* @throws Http2Exception in case of protocol errors
*/
public static Http2Headers create(Http2Stream stream,
DynamicTable table,
Http2HuffmanDecoder huffman,
Http2FrameData... frames) {
return create(stream, table, huffman, Http2Headers.create(WritableHeaders.create()), frames);
}

/**
* Create HTTP/2 headers from HTTP headers.
*
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package io.helidon.webclient.tests;

import java.io.UncheckedIOException;
import java.security.Principal;
import java.util.concurrent.atomic.AtomicBoolean;

import io.helidon.http.Http;
Expand All @@ -32,6 +31,8 @@

import org.junit.jupiter.api.Test;

import static io.helidon.http.Http.HeaderNames.X_HELIDON_CN;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.is;
Expand Down Expand Up @@ -181,15 +182,10 @@ private static void plainRouting(HttpRouting.Builder routing) {

private static void mTlsRouting(HttpRouting.Builder routing) {
routing.get("/", (req, res) -> {
String cn = req.remotePeer()
.tlsPrincipal()
.map(Principal::getName)
.flatMap(CertificateHelper::clientCertificateName)
.orElse("Unknown CN");

// close to avoid re-using cached connections on the client side
res.header(Http.Headers.CONNECTION_CLOSE);
res.send("Hello " + cn + "!");
res.send("Hello " + req.headers().value(X_HELIDON_CN).orElse("Unknown CN") + "!");
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
import io.helidon.common.buffers.BufferData;
import io.helidon.common.buffers.DataReader;
import io.helidon.common.task.InterruptableTask;
import io.helidon.common.tls.TlsUtils;
import io.helidon.http.Http;
import io.helidon.http.Http.HeaderNames;
import io.helidon.http.HttpPrologue;
import io.helidon.http.WritableHeaders;
import io.helidon.http.http2.ConnectionFlowControl;
import io.helidon.http.http2.Http2ConnectionWriter;
import io.helidon.http.http2.Http2ErrorCode;
Expand Down Expand Up @@ -61,6 +63,7 @@
import io.helidon.webserver.http2.spi.Http2SubProtocolSelector;
import io.helidon.webserver.spi.ServerConnection;

import static io.helidon.http.Http.HeaderNames.X_HELIDON_CN;
import static io.helidon.http.http2.Http2Util.PREFACE_LENGTH;
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.TRACE;
Expand Down Expand Up @@ -92,6 +95,7 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
private final Http2Settings serverSettings;
private final boolean sendErrorDetails;
private final ConnectionFlowControl flowControl;
private final WritableHeaders<?> connectionHeaders;

// initial client settings, until we receive real ones
private Http2Settings clientSettings = Http2Settings.builder()
Expand All @@ -106,6 +110,7 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
private int continuationExpectedStreamId;
private int lastStreamId;
private long maxClientConcurrentStreams;
private boolean initConnectionHeaders;
private volatile ZonedDateTime lastRequestTimestamp;
private volatile Thread myThread;
private volatile boolean canRun = true;
Expand Down Expand Up @@ -136,6 +141,8 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
.maxFrameSize(http2Config.maxFrameSize())
.build();
this.lastRequestTimestamp = Http.DateTime.timestamp();
this.connectionHeaders = WritableHeaders.create();
this.initConnectionHeaders = true;
}

private static void settingsUpdate(Http2Config config, Http2Settings.Builder builder) {
Expand Down Expand Up @@ -588,12 +595,19 @@ private void doHeaders(Semaphore requestSemaphore) {
boolean endOfStream;
Http2Headers headers;
Http2ServerStream stream = streamContext.stream();
if (initConnectionHeaders) {
ctx.remotePeer().tlsCertificates()
.flatMap(TlsUtils::parseCn)
.ifPresent(cn -> connectionHeaders.add(X_HELIDON_CN, cn));
initConnectionHeaders = false;
}

if (frameHeader.type() == Http2FrameType.CONTINUATION) {
// end of continuations with header frames
headers = Http2Headers.create(stream,
requestDynamicTable,
requestHuffman,
Http2Headers.create(connectionHeaders),
streamContext.contData());
endOfStream = streamContext.contHeader().flags(Http2FrameTypes.HEADERS).endOfStream();
streamContext.clearContinuations();
Expand All @@ -603,6 +617,7 @@ private void doHeaders(Semaphore requestSemaphore) {
headers = Http2Headers.create(stream,
requestDynamicTable,
requestHuffman,
Http2Headers.create(connectionHeaders),
new Http2FrameData(frameHeader, inProgressFrame()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.helidon.common.buffers.DataWriter;
import io.helidon.common.mapper.MapperException;
import io.helidon.common.task.InterruptableTask;
import io.helidon.common.tls.TlsUtils;
import io.helidon.http.BadRequestException;
import io.helidon.http.DirectHandler;
import io.helidon.http.DirectHandler.EventType;
Expand All @@ -49,6 +50,7 @@
import io.helidon.webserver.http1.spi.Http1Upgrader;
import io.helidon.webserver.spi.ServerConnection;

import static io.helidon.http.Http.HeaderNames.X_HELIDON_CN;
import static java.lang.System.Logger.Level.TRACE;
import static java.lang.System.Logger.Level.WARNING;

Expand Down Expand Up @@ -136,6 +138,9 @@ public void handle(Semaphore requestSemaphore) throws InterruptedException {
currentEntitySizeRead = 0;

WritableHeaders<?> headers = http1headers.readHeaders(prologue);
ctx.remotePeer().tlsCertificates()
.flatMap(TlsUtils::parseCn)
.ifPresent(name -> headers.set(X_HELIDON_CN, name));
recvListener.headers(ctx, headers);

if (canUpgrade) {
Expand Down

0 comments on commit 7c7dfc4

Please sign in to comment.