gavinchou commented on code in PR #16340:
URL: https://github.com/apache/doris/pull/16340#discussion_r1111449982
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlChannel.java:
##########
@@ -120,38 +156,70 @@ public void close() {
}
}
- protected int readAll(ByteBuffer dstBuf) throws IOException {
+ // all packet header is not encrypted, packet body is not sure.
+ protected int readAll(ByteBuffer dstBuf, boolean isHeader) throws
IOException {
int readLen = 0;
while (dstBuf.remaining() != 0) {
int ret = channel.read(dstBuf);
// return -1 when remote peer close the channel
if (ret == -1) {
+ decryptData(dstBuf, isHeader);
return readLen;
}
readLen += ret;
}
+ // if use ssl mode, wo need to decrypt received net data(ciphertext)
to app data(plaintext).
+ decryptData(dstBuf,isHeader);
return readLen;
}
+ protected void decryptData(ByteBuffer dstBuf, boolean isHeader) throws
SSLException {
+ // after decrypt we get a mysql packet with mysql header.
+ if (isSslMode && !isHeader) {
+ dstBuf.flip();
+ ByteBuffer appData =
ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize());
+ // unwrap will remove ssl header.
+ sslEngine.unwrap(dstBuf, appData);
+ appData.flip();
+ dstBuf.clear();
+ dstBuf.put(appData);
+ dstBuf.flip();
+
+ }
+ }
+
// read one logical mysql protocol packet
// null for channel is closed.
// NOTE: all of the following code is assumed that the channel is in block
mode.
+ // if in handshaking mode we return a packet with header otherwise without
header.
public ByteBuffer fetchOnePacket() throws IOException {
int readLen;
ByteBuffer result = defaultBuffer;
result.clear();
while (true) {
- headerByteBuffer.clear();
- readLen = readAll(headerByteBuffer);
- if (readLen != PACKET_HEADER_LEN) {
- // remote has close this channel
- LOG.debug("Receive packet header failed, remote may close the
channel.");
- return null;
- }
- if (packetId() != sequenceId) {
- LOG.warn("receive packet sequence id[" + packetId() + "] want
to get[" + sequenceId + "]");
- throw new IOException("Bad packet sequence.");
+ if(isSslMode || isHandshaking){
Review Comment:
Code style, need a space after `if` or `)`
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlChannel.java:
##########
@@ -120,38 +156,70 @@ public void close() {
}
}
- protected int readAll(ByteBuffer dstBuf) throws IOException {
+ // all packet header is not encrypted, packet body is not sure.
+ protected int readAll(ByteBuffer dstBuf, boolean isHeader) throws
IOException {
int readLen = 0;
while (dstBuf.remaining() != 0) {
int ret = channel.read(dstBuf);
// return -1 when remote peer close the channel
if (ret == -1) {
+ decryptData(dstBuf, isHeader);
return readLen;
}
readLen += ret;
}
+ // if use ssl mode, wo need to decrypt received net data(ciphertext)
to app data(plaintext).
+ decryptData(dstBuf,isHeader);
return readLen;
}
+ protected void decryptData(ByteBuffer dstBuf, boolean isHeader) throws
SSLException {
+ // after decrypt we get a mysql packet with mysql header.
+ if (isSslMode && !isHeader) {
+ dstBuf.flip();
+ ByteBuffer appData =
ByteBuffer.allocate(sslEngine.getSession().getApplicationBufferSize());
+ // unwrap will remove ssl header.
+ sslEngine.unwrap(dstBuf, appData);
+ appData.flip();
+ dstBuf.clear();
+ dstBuf.put(appData);
+ dstBuf.flip();
+
+ }
+ }
+
// read one logical mysql protocol packet
// null for channel is closed.
// NOTE: all of the following code is assumed that the channel is in block
mode.
+ // if in handshaking mode we return a packet with header otherwise without
header.
public ByteBuffer fetchOnePacket() throws IOException {
int readLen;
ByteBuffer result = defaultBuffer;
result.clear();
while (true) {
- headerByteBuffer.clear();
- readLen = readAll(headerByteBuffer);
- if (readLen != PACKET_HEADER_LEN) {
- // remote has close this channel
- LOG.debug("Receive packet header failed, remote may close the
channel.");
- return null;
- }
- if (packetId() != sequenceId) {
- LOG.warn("receive packet sequence id[" + packetId() + "] want
to get[" + sequenceId + "]");
- throw new IOException("Bad packet sequence.");
+ if(isSslMode || isHandshaking){
+ sslHeaderByteBuffer.clear();
+ readLen = readAll(sslHeaderByteBuffer, true);
+ if (readLen != SSL_PACKET_HEADER_LEN) {
+ // remote has close this channel
+ LOG.debug("Receive ssl packet header failed, remote may
close the channel.");
+ return null;
+ }
+ // when handshaking and ssl mode, sslengine unwrap need a
packet with header.
+ result.put(sslHeaderByteBuffer.array());
+ }else{
Review Comment:
Code style, need a space after `}` or before`{`
##########
regression-test/conf/regression-conf.groovy:
##########
@@ -24,7 +24,6 @@ defaultDb = "regression_test"
// init cmd like: select @@session.tx_read_only
// at each time we connect.
// add allowLoadLocalInfile so that the jdbc can execute mysql load data from
client.
-jdbcUrl =
"jdbc:mysql://127.0.0.1:9030/?useLocalSessionState=true&allowLoadLocalInfile=true"
Review Comment:
Regression test may change this line accordingly, which will skip testing
tls if it is deleted.
##########
regression-test/framework/src/main/groovy/org/apache/doris/regression/Config.groovy:
##########
@@ -267,7 +268,15 @@ class Config {
if (config.jdbcUrl == null) {
//jdbcUrl needs parameter here. Refer to function: buildUrl(String
dbName)
- config.jdbcUrl =
"jdbc:mysql://127.0.0.1:9030/?useLocalSessionState=true&allowLoadLocalInfile=true"
Review Comment:
We also have to support the config which sets a jdbcUrl.
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlChannel.java:
##########
@@ -171,13 +239,25 @@ public ByteBuffer fetchOnePacket() throws IOException {
// read one physical packet
// before read, set limit to make read only one packet
result.limit(result.position() + packetLen);
- readLen = readAll(result);
+ readLen = readAll(result,false);
+ if(isSslMode){
Review Comment:
Code style, space issue.
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlSslContext.java:
##########
@@ -0,0 +1,275 @@
+// 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.doris.mysql;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+public class MysqlSslContext {
+
+ private static final Logger LOG =
LogManager.getLogger(MysqlSslContext.class);
+ private SSLEngine sslEngine;
+ private SSLContext sslContext;
+ private String protocol;
+ private ByteBuffer serverAppData;
+ private ByteBuffer serverNetData;
+ private static final String keyStoreFile =
"../../../regression-test/certificate.p12";
+ private static final String trustStoreFile =
"../../../regression-test/certificate.p12";
Review Comment:
We should get the paths from Config.java -- make them configurable.
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlSslContext.java:
##########
@@ -0,0 +1,275 @@
+// 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.doris.mysql;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+public class MysqlSslContext {
+
+ private static final Logger LOG =
LogManager.getLogger(MysqlSslContext.class);
+ private SSLEngine sslEngine;
+ private SSLContext sslContext;
+ private String protocol;
+ private ByteBuffer serverAppData;
+ private ByteBuffer serverNetData;
+ private static final String keyStoreFile =
"../../../regression-test/certificate.p12";
+ private static final String trustStoreFile =
"../../../regression-test/certificate.p12";
+ private ByteBuffer clientAppData;
+ private ByteBuffer clientNetData;
+
+ public MysqlSslContext(String protocol) {
+ this.protocol = protocol;
+ initSslContext();
+ initSslEngine();
+ }
+
+ private void initSslContext() {
+ try {
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ KeyStore ts = KeyStore.getInstance("PKCS12");
+
+ char[] password = "doris".toCharArray();
+
+ ks.load(Files.newInputStream(Paths.get(keyStoreFile)), password);
+ ts.load(Files.newInputStream(Paths.get(trustStoreFile)), password);
+
+ KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, password);
+
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ts);
+ sslContext = SSLContext.getInstance(protocol);
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
null);
+ } catch (NoSuchAlgorithmException | KeyManagementException |
KeyStoreException | IOException
+ | CertificateException | UnrecoverableKeyException e) {
+ LOG.error("Failed to initialize SSL because", e);
+ }
+ }
+
+ private void initSslEngine() {
+ sslEngine = sslContext.createSSLEngine();
+ // set to server mode
+ sslEngine.setUseClientMode(false);
+ sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
+ }
+
+ public SSLEngine getSslEngine() {
+ return sslEngine;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+
+ /*
+ There may several steps for a successful handshake,
+ so it's typical to see the following series of operations:
+
+ client server message
+ ====== ====== =======
+ wrap() ... ClientHello
+ ... unwrap() ClientHello
+ ... wrap() ServerHello/Certificate
+ unwrap() ... ServerHello/Certificate
+ wrap() ... ClientKeyExchange
+ wrap() ... ChangeCipherSpec
+ wrap() ... Finished
+ ... unwrap() ClientKeyExchange
+ ... unwrap() ChangeCipherSpec
+ ... unwrap() Finished
+ ... wrap() ChangeCipherSpec
+ ... wrap() Finished
+ unwrap() ... ChangeCipherSpec
+ unwrap() ... Finished
+ reference:
https://docs.oracle.com/javase/10/security/java-secure-socket-extension-jsse-reference-guide.htm#JSSEC-GUID-7FCC21CB-158B-440C-B5E4-E4E5A2D7352B
+ */
+ public boolean sslExchange(MysqlChannel channel) throws Exception {
+ // long startTime = System.currentTimeMillis();
+ // init data buffer
+ initDataBuffer();
+ // set channel sslengine.
+ channel.setSslEngine(sslEngine);
+ // begin handshake.
+ sslEngine.beginHandshake();
+ while (sslEngine.getHandshakeStatus() != HandshakeStatus.FINISHED
+ && sslEngine.getHandshakeStatus() !=
HandshakeStatus.NOT_HANDSHAKING) {
+ // if ((System.currentTimeMillis() - startTime) >
10000) {
+ // throw new Exception("try to establish SSL
connection failed, timeout!");
+ // }
+ switch (sslEngine.getHandshakeStatus()) {
+ case NEED_WRAP:
+ handleNeedWrap(channel);
+ break;
+ case NEED_UNWRAP:
+ handleNeedUnwrap(channel);
+ break;
+ case NEED_TASK:
+ handleNeedTask();
+ break;
+ // Under normal circumstances, the following states will not
appear
+ case NOT_HANDSHAKING:
+ throw new Exception("impossible HandshakeStatus: " +
HandshakeStatus.NOT_HANDSHAKING);
+ case FINISHED:
+ throw new Exception("impossible HandshakeStatus: " +
HandshakeStatus.FINISHED);
+ default:
+ throw new IllegalStateException("invalid HandshakeStatus:
" + sslEngine.getHandshakeStatus());
+ }
+ }
+ return true;
+ }
+
+ private void initDataBuffer() {
+ int appLength = sslEngine.getSession().getApplicationBufferSize();
+ int netLength = sslEngine.getSession().getPacketBufferSize();
+ serverAppData = clientAppData = ByteBuffer.allocate(appLength);
+ serverNetData = clientNetData = ByteBuffer.allocate(netLength);
+ }
+
+ private void handleNeedTask() throws Exception {
+ Runnable runnable;
+ while ((runnable = sslEngine.getDelegatedTask()) != null) {
+ runnable.run();
+ }
+ HandshakeStatus hsStatus = sslEngine.getHandshakeStatus();
+ if (hsStatus == HandshakeStatus.NEED_TASK) {
+ throw new Exception("handshake shouldn't need additional tasks");
+ }
+ }
+
+ private void handleNeedWrap(MysqlChannel channel) {
+ try {
+ serverAppData.flip();
+ serverAppData.clear();
Review Comment:
It seems these 4 lines are redundant.
##########
fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlSslContext.java:
##########
@@ -0,0 +1,275 @@
+// 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.doris.mysql;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+public class MysqlSslContext {
+
+ private static final Logger LOG =
LogManager.getLogger(MysqlSslContext.class);
+ private SSLEngine sslEngine;
+ private SSLContext sslContext;
+ private String protocol;
+ private ByteBuffer serverAppData;
+ private ByteBuffer serverNetData;
+ private static final String keyStoreFile =
"../../../regression-test/certificate.p12";
+ private static final String trustStoreFile =
"../../../regression-test/certificate.p12";
+ private ByteBuffer clientAppData;
+ private ByteBuffer clientNetData;
+
+ public MysqlSslContext(String protocol) {
+ this.protocol = protocol;
+ initSslContext();
+ initSslEngine();
+ }
+
+ private void initSslContext() {
+ try {
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ KeyStore ts = KeyStore.getInstance("PKCS12");
+
+ char[] password = "doris".toCharArray();
+
+ ks.load(Files.newInputStream(Paths.get(keyStoreFile)), password);
+ ts.load(Files.newInputStream(Paths.get(trustStoreFile)), password);
+
+ KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, password);
+
+ TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ts);
+ sslContext = SSLContext.getInstance(protocol);
+ sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
null);
+ } catch (NoSuchAlgorithmException | KeyManagementException |
KeyStoreException | IOException
+ | CertificateException | UnrecoverableKeyException e) {
+ LOG.error("Failed to initialize SSL because", e);
+ }
+ }
+
+ private void initSslEngine() {
+ sslEngine = sslContext.createSSLEngine();
+ // set to server mode
+ sslEngine.setUseClientMode(false);
+ sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());
+ }
+
+ public SSLEngine getSslEngine() {
+ return sslEngine;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+
+ /*
+ There may several steps for a successful handshake,
+ so it's typical to see the following series of operations:
+
+ client server message
+ ====== ====== =======
+ wrap() ... ClientHello
+ ... unwrap() ClientHello
+ ... wrap() ServerHello/Certificate
+ unwrap() ... ServerHello/Certificate
+ wrap() ... ClientKeyExchange
+ wrap() ... ChangeCipherSpec
+ wrap() ... Finished
+ ... unwrap() ClientKeyExchange
+ ... unwrap() ChangeCipherSpec
+ ... unwrap() Finished
+ ... wrap() ChangeCipherSpec
+ ... wrap() Finished
+ unwrap() ... ChangeCipherSpec
+ unwrap() ... Finished
+ reference:
https://docs.oracle.com/javase/10/security/java-secure-socket-extension-jsse-reference-guide.htm#JSSEC-GUID-7FCC21CB-158B-440C-B5E4-E4E5A2D7352B
+ */
+ public boolean sslExchange(MysqlChannel channel) throws Exception {
+ // long startTime = System.currentTimeMillis();
+ // init data buffer
+ initDataBuffer();
+ // set channel sslengine.
+ channel.setSslEngine(sslEngine);
+ // begin handshake.
+ sslEngine.beginHandshake();
+ while (sslEngine.getHandshakeStatus() != HandshakeStatus.FINISHED
+ && sslEngine.getHandshakeStatus() !=
HandshakeStatus.NOT_HANDSHAKING) {
+ // if ((System.currentTimeMillis() - startTime) >
10000) {
+ // throw new Exception("try to establish SSL
connection failed, timeout!");
Review Comment:
Remove if unused.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]