|
| 1 | +/* |
| 2 | + * SPDX-License-Identifier: Apache-2.0 |
| 3 | + * |
| 4 | + * The OpenSearch Contributors require contributions made to |
| 5 | + * this file be licensed under the Apache-2.0 license or a |
| 6 | + * compatible open source license. |
| 7 | + */ |
| 8 | + |
| 9 | +package org.opensearch.http.reactor.netty4.http3; |
| 10 | + |
| 11 | +import org.opensearch.common.Randomness; |
| 12 | + |
| 13 | +import javax.crypto.Mac; |
| 14 | +import javax.crypto.spec.SecretKeySpec; |
| 15 | + |
| 16 | +import java.net.InetSocketAddress; |
| 17 | +import java.security.InvalidKeyException; |
| 18 | +import java.security.MessageDigest; |
| 19 | +import java.security.NoSuchAlgorithmException; |
| 20 | + |
| 21 | +import io.netty.buffer.ByteBuf; |
| 22 | +import io.netty.buffer.Unpooled; |
| 23 | +import io.netty.handler.codec.quic.Quic; |
| 24 | +import io.netty.handler.codec.quic.QuicTokenHandler; |
| 25 | +import io.netty.util.CharsetUtil; |
| 26 | +import io.netty.util.NetUtil; |
| 27 | + |
| 28 | +/** |
| 29 | + * Secure {@link QuicTokenHandler} which uses HMAC_SHA256. |
| 30 | + */ |
| 31 | +public class SecureQuicTokenHandler implements QuicTokenHandler { |
| 32 | + private static final int HMAC_KEY_LEN = 32; |
| 33 | + private static final int HMAC_TAG_LEN = 32; |
| 34 | + private static final String HMAC_SHA_256 = "HmacSHA256"; |
| 35 | + |
| 36 | + private static final String SERVER_NAME = "opensearch-reactor-netty"; |
| 37 | + private static final byte[] SERVER_NAME_BYTES = SERVER_NAME.getBytes(CharsetUtil.US_ASCII); |
| 38 | + private static final ByteBuf SERVER_NAME_BUFFER = Unpooled.unreleasableBuffer(Unpooled.wrappedBuffer(SERVER_NAME_BYTES)).asReadOnly(); |
| 39 | + |
| 40 | + private static final int MAX_TOKEN_LEN = HMAC_TAG_LEN + Quic.MAX_CONN_ID_LEN + NetUtil.LOCALHOST6.getAddress().length |
| 41 | + + SERVER_NAME_BYTES.length; |
| 42 | + |
| 43 | + private final byte[] key; |
| 44 | + |
| 45 | + /** |
| 46 | + * Constructs SecureQuicTokenHandler instance |
| 47 | + */ |
| 48 | + public SecureQuicTokenHandler() { |
| 49 | + this.key = new byte[HMAC_KEY_LEN]; |
| 50 | + Randomness.createSecure().nextBytes(key); |
| 51 | + } |
| 52 | + |
| 53 | + /** |
| 54 | + * Generate a new token for the given destination connection id and address. This token is written to {@code out}. |
| 55 | + * If no token should be generated and so no token validation should take place at all this method should return |
| 56 | + * {@code false}. |
| 57 | + * |
| 58 | + * @param out {@link ByteBuf} into which the token will be written. |
| 59 | + * @param dcid the destination connection id. The {@link ByteBuf#readableBytes()} will be at most |
| 60 | + * {@link Quic#MAX_CONN_ID_LEN}. |
| 61 | + * @param address the {@link InetSocketAddress} of the sender. |
| 62 | + * @return {@code true} if a token was written and so validation should happen, {@code false} otherwise. |
| 63 | + */ |
| 64 | + @Override |
| 65 | + public boolean writeToken(ByteBuf out, ByteBuf dcid, InetSocketAddress address) { |
| 66 | + final byte[] addr = address.getAddress().getAddress(); |
| 67 | + final byte[] buffer = new byte[HMAC_TAG_LEN + addr.length + dcid.readableBytes()]; |
| 68 | + |
| 69 | + System.arraycopy(addr, 0, buffer, HMAC_TAG_LEN, addr.length); |
| 70 | + dcid.getBytes(dcid.readerIndex(), buffer, HMAC_TAG_LEN + addr.length, dcid.readableBytes()); |
| 71 | + |
| 72 | + try { |
| 73 | + final Mac mac = Mac.getInstance(HMAC_SHA_256); |
| 74 | + mac.init(new SecretKeySpec(key, HMAC_SHA_256)); |
| 75 | + mac.update(buffer, HMAC_TAG_LEN, addr.length + dcid.readableBytes()); |
| 76 | + System.arraycopy(mac.doFinal(), 0, buffer, 0, HMAC_TAG_LEN); |
| 77 | + } catch (final InvalidKeyException | NoSuchAlgorithmException ex) { |
| 78 | + return false; |
| 79 | + } |
| 80 | + |
| 81 | + out.writeBytes(SERVER_NAME_BYTES).writeBytes(buffer); |
| 82 | + return true; |
| 83 | + } |
| 84 | + |
| 85 | + /** |
| 86 | + * Validate the token and return the offset, {@code -1} is returned if the token is not valid. |
| 87 | + * |
| 88 | + * @param token the {@link ByteBuf} that contains the token. The ownership is not transferred. |
| 89 | + * @param address the {@link InetSocketAddress} of the sender. |
| 90 | + * @return the start index after the token or {@code -1} if the token was not valid. |
| 91 | + */ |
| 92 | + @Override |
| 93 | + public int validateToken(ByteBuf token, InetSocketAddress address) { |
| 94 | + final byte[] addr = address.getAddress().getAddress(); |
| 95 | + final int minLength = SERVER_NAME_BYTES.length + HMAC_TAG_LEN + addr.length; |
| 96 | + if (token.readableBytes() <= minLength) { |
| 97 | + return -1; |
| 98 | + } |
| 99 | + |
| 100 | + if (!SERVER_NAME_BUFFER.equals(token.slice(0, SERVER_NAME_BYTES.length))) { |
| 101 | + return -1; |
| 102 | + } |
| 103 | + |
| 104 | + final ByteBuf tag = token.slice(SERVER_NAME_BYTES.length, HMAC_TAG_LEN); |
| 105 | + final int length = token.readableBytes() - HMAC_TAG_LEN - SERVER_NAME_BYTES.length; |
| 106 | + final ByteBuf payload = token.slice(SERVER_NAME_BYTES.length + HMAC_TAG_LEN, length); |
| 107 | + try { |
| 108 | + final Mac mac = Mac.getInstance(HMAC_SHA_256); |
| 109 | + mac.init(new SecretKeySpec(key, HMAC_SHA_256)); |
| 110 | + for (int i = 0; i < payload.readableBytes(); ++i) { |
| 111 | + mac.update(payload.getByte(payload.readerIndex() + i)); |
| 112 | + } |
| 113 | + |
| 114 | + final byte[] actual = new byte[tag.readableBytes()]; |
| 115 | + tag.getBytes(tag.readerIndex(), actual, 0, tag.readableBytes()); |
| 116 | + |
| 117 | + final byte[] expected = mac.doFinal(); |
| 118 | + if (!MessageDigest.isEqual(actual, expected)) { |
| 119 | + return -1; |
| 120 | + } |
| 121 | + } catch (final InvalidKeyException | NoSuchAlgorithmException ex) { |
| 122 | + return -1; |
| 123 | + } |
| 124 | + |
| 125 | + return minLength; |
| 126 | + } |
| 127 | + |
| 128 | + /** |
| 129 | + * Return the maximal token length. |
| 130 | + * |
| 131 | + * @return the maximal supported token length. |
| 132 | + */ |
| 133 | + @Override |
| 134 | + public int maxTokenLength() { |
| 135 | + return MAX_TOKEN_LEN; |
| 136 | + } |
| 137 | +} |
0 commit comments