Skip to content

Commit

Permalink
support tls
Browse files Browse the repository at this point in the history
  • Loading branch information
shiyuhang0 committed Apr 26, 2023
1 parent aaee923 commit 4caa9c4
Show file tree
Hide file tree
Showing 20 changed files with 362 additions and 37 deletions.
29 changes: 18 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ jobs:
DENO_VERSION:
- v1.x
DB_VERSION:
- mysql:5.5
- mysql:5.6
# - mysql:5.5
# - mysql:5.6
- mysql:5.7
- mysql:8
- mysql:latest
- mariadb:5.5
- mariadb:10.0
- mariadb:10.1
- mariadb:10.2
- mariadb:10.3
- mariadb:10.4
- mariadb:latest
# - mysql:latest
# - mariadb:5.5
# - mariadb:10.0
# - mariadb:10.1
# - mariadb:10.2
# - mariadb:10.3
# - mariadb:10.4
# - mariadb:latest

steps:
- uses: actions/checkout@v1
Expand All @@ -45,16 +45,23 @@ jobs:
- name: Start ${{ matrix.DB_VERSION }}
run: |
sudo mkdir -p /var/run/mysqld/tmp
sudo mkdir -p /etc/mysql
sudo cp -r tls/cert /etc/cert
sudo cp -r tls/conf.d /etc/conf.d
sudo chmod -R 644 /etc/mysql
sudo chmod -R 777 /var/run/mysqld
docker container run --name mysql --rm -d -p 3306:3306 \
-v /var/run/mysqld:/var/run/mysqld \
-v /var/run/mysqld/tmp:/tmp \
-v /etc/cert:/etc/mysql/cert \
-v /etc/conf.d:/etc/mysql/conf.d \
-e MYSQL_ROOT_PASSWORD=root \
${{ matrix.DB_VERSION }}
./.github/workflows/wait-for-mysql.sh
- name: Run tests (TCP)
run: |
deno test --allow-env --allow-net=127.0.0.1:3306 ./test.ts
mysql -uroot -h 127.0.0.1 -proot -e "show variables like '%ssl%'"
deno test --allow-env --allow-net=127.0.0.1:3306,127.0.0.1:0 --allow-read=tls ./test.ts
- name: Run tests (--unstable) (UNIX domain socket)
run: |
SOCKPATH=/var/run/mysqld/mysqld.sock
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ node_modules
mysql.log
docs
.DS_Store
.idea

29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,35 @@ const users = await client.transaction(async (conn) => {
console.log(users.length);
```

### TLS

TLS configuration:

- caCerts([]string): A list of root certificates (must be PEM format) that will
be used in addition to the default root certificates to verify the peer's
certificate.
- mode(string): The TLS mode to use. Valid values are "disabled",
"verify_identity". Defaults to "disabled".

You usually need not specify the caCert, unless the certificate is not included
in the default root certificates.

```ts
import { Client } from "https://deno.land/x/mysql/mod.ts";
const client = await new Client().connect({
hostname: "127.0.0.1",
username: "root",
db: "dbname",
password: "password",
tls: {
mode: "verify_identity",
caCerts: [
await Deno.readTextFile("capath"),
],
},
});
```

### close

```ts
Expand Down
17 changes: 17 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ export interface ClientConfig {
idleTimeout?: number;
/** charset */
charset?: string;
/** tls config */
tls?: TLSConfig;
}

export enum TLSMode {
DISABLED = "disabled",
VERIFY_IDENTITY = "verify_identity",
}
/**
* TLS Config
*/
export interface TLSConfig {
/** mode of tls. only support disabled and verify_identity now*/
mode?: string;
/** A list of root certificates (must be PEM format) that will be used in addition to the
* default root certificates to verify the peer's certificate. */
caCerts?: string[];
}

/** Transaction processor */
Expand Down
45 changes: 43 additions & 2 deletions src/connection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ClientConfig } from "./client.ts";
import { ClientConfig, TLSMode } from "./client.ts";
import {
ConnnectionError,
ProtocolError,
Expand All @@ -21,6 +21,7 @@ import authPlugin from "./auth_plugin/index.ts";
import { parseAuthSwitch } from "./packets/parsers/authswitch.ts";
import auth from "./auth.ts";
import ServerCapabilities from "./constant/capabilities.ts";
import { buildSSLRequest } from "./packets/builders/tls.ts";

/**
* Connection state
Expand Down Expand Up @@ -62,6 +63,13 @@ export class Connection {

private async _connect() {
// TODO: implement connect timeout
if (
this.config.tls?.mode &&
this.config.tls.mode.toLocaleLowerCase() !== TLSMode.DISABLED &&
this.config.tls.mode.toLocaleLowerCase() !== TLSMode.VERIFY_IDENTITY
) {
throw new Error("unsupported tls mode");
}
const { hostname, port = 3306, socketPath, username = "", password } =
this.config;
log.info(`connecting ${this.remoteAddr}`);
Expand All @@ -79,13 +87,46 @@ export class Connection {
try {
let receive = await this.nextPacket();
const handshakePacket = parseHandshake(receive.body);

let handshakeSequenceNumber = receive.header.no;

// Deno.startTls() only supports VERIFY_IDENTITY now.
let isSSL = false;
if (
this.config.tls?.mode?.toLocaleLowerCase() === TLSMode.VERIFY_IDENTITY
) {
if (
(handshakePacket.serverCapabilities &
ServerCapabilities.CLIENT_SSL) === 0
) {
throw new Error("Server does not support TLS");
}
if (
(handshakePacket.serverCapabilities &
ServerCapabilities.CLIENT_SSL) !== 0
) {
const tlsData = buildSSLRequest(handshakePacket, {
db: this.config.db,
});
await new SendPacket(tlsData, ++handshakeSequenceNumber).send(
this.conn,
);
this.conn = await Deno.startTls(this.conn, {
hostname,
caCerts: this.config.tls?.caCerts,
});
}
isSSL = true;
}

const data = buildAuth(handshakePacket, {
username,
password,
db: this.config.db,
ssl: isSSL,
});

await new SendPacket(data, 0x1).send(this.conn);
await new SendPacket(data, ++handshakeSequenceNumber).send(this.conn);

this.state = ConnectionState.CONNECTING;
this.serverVersion = handshakePacket.serverVersion;
Expand Down
27 changes: 17 additions & 10 deletions src/constant/capabilities.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
enum ServerCapabilities {
CLIENT_PROTOCOL_41 = 0x00000200,
CLIENT_CONNECT_WITH_DB = 0x00000008,
CLIENT_LONG_FLAG = 0x00000004,
CLIENT_DEPRECATE_EOF = 0x01000000,
CLIENT_LONG_PASSWORD = 0x00000001,
CLIENT_TRANSACTIONS = 0x00002000,
CLIENT_MULTI_RESULTS = 0x00020000,
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = 0x00200000,
CLIENT_PLUGIN_AUTH = 0x80000,
CLIENT_SECURE_CONNECTION = 0x8000,
CLIENT_FOUND_ROWS = 0x00000002,
CLIENT_CONNECT_ATTRS = 0x00100000,
CLIENT_LONG_FLAG = 0x00000004,
CLIENT_CONNECT_WITH_DB = 0x00000008,
CLIENT_NO_SCHEMA = 0x00000010,
CLIENT_COMPRESS = 0x00000020,
CLIENT_ODBC = 0x00000040,
CLIENT_LOCAL_FILES = 0x00000080,
CLIENT_IGNORE_SPACE = 0x00000100,
CLIENT_PROTOCOL_41 = 0x00000200,
CLIENT_INTERACTIVE = 0x00000400,
CLIENT_SSL = 0x00000800,
CLIENT_IGNORE_SIGPIPE = 0x00001000,
CLIENT_TRANSACTIONS = 0x00002000,
CLIENT_RESERVED = 0x00004000,
CLIENT_SECURE_CONNECTION = 0x00008000,
CLIENT_MULTI_STATEMENTS = 0x00010000,
CLIENT_MULTI_RESULTS = 0x00020000,
CLIENT_PS_MULTI_RESULTS = 0x00040000,
CLIENT_PLUGIN_AUTH = 0x00080000,
CLIENT_CONNECT_ATTRS = 0x00100000,
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = 0x00200000,
CLIENT_DEPRECATE_EOF = 0x01000000,
}

export default ServerCapabilities;
2 changes: 1 addition & 1 deletion src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function configLogger(config: LoggerConfig) {

if (!enable) {
logger = new log.Logger("fakeLogger", "NOTSET", {});
logger.level = 100;
logger.level = 0;
} else {
if (!config.logger) {
await log.setup({
Expand Down
19 changes: 6 additions & 13 deletions src/packets/builders/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,17 @@ import { BufferWriter } from "../../buffer.ts";
import ServerCapabilities from "../../constant/capabilities.ts";
import { Charset } from "../../constant/charset.ts";
import type { HandshakeBody } from "../parsers/handshake.ts";
import { clientCapabilities } from "./client_capabilities.ts";

/** @ignore */
export function buildAuth(
packet: HandshakeBody,
params: { username: string; password?: string; db?: string },
params: { username: string; password?: string; db?: string; ssl?: boolean },
): Uint8Array {
const clientParam: number =
(params.db ? ServerCapabilities.CLIENT_CONNECT_WITH_DB : 0) |
ServerCapabilities.CLIENT_PLUGIN_AUTH |
ServerCapabilities.CLIENT_LONG_PASSWORD |
ServerCapabilities.CLIENT_PROTOCOL_41 |
ServerCapabilities.CLIENT_TRANSACTIONS |
ServerCapabilities.CLIENT_MULTI_RESULTS |
ServerCapabilities.CLIENT_SECURE_CONNECTION |
(ServerCapabilities.CLIENT_LONG_FLAG & packet.serverCapabilities) |
(ServerCapabilities.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA &
packet.serverCapabilities) |
(ServerCapabilities.CLIENT_DEPRECATE_EOF & packet.serverCapabilities);
const clientParam: number = clientCapabilities(packet, {
db: params.db,
ssl: params.ssl,
});

if (packet.serverCapabilities & ServerCapabilities.CLIENT_PLUGIN_AUTH) {
const writer = new BufferWriter(new Uint8Array(1000));
Expand Down
20 changes: 20 additions & 0 deletions src/packets/builders/client_capabilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import ServerCapabilities from "../../constant/capabilities.ts";
import type { HandshakeBody } from "../parsers/handshake.ts";

export function clientCapabilities(
packet: HandshakeBody,
params: { db?: string; ssl?: boolean },
): number {
return (params.db ? ServerCapabilities.CLIENT_CONNECT_WITH_DB : 0) |
ServerCapabilities.CLIENT_PLUGIN_AUTH |
ServerCapabilities.CLIENT_LONG_PASSWORD |
ServerCapabilities.CLIENT_PROTOCOL_41 |
ServerCapabilities.CLIENT_TRANSACTIONS |
ServerCapabilities.CLIENT_MULTI_RESULTS |
ServerCapabilities.CLIENT_SECURE_CONNECTION |
(ServerCapabilities.CLIENT_LONG_FLAG & packet.serverCapabilities) |
(ServerCapabilities.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA &
packet.serverCapabilities) |
(ServerCapabilities.CLIENT_DEPRECATE_EOF & packet.serverCapabilities) |
(params.ssl ? ServerCapabilities.CLIENT_SSL : 0);
}
21 changes: 21 additions & 0 deletions src/packets/builders/tls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BufferWriter } from "../../buffer.ts";
import { Charset } from "../../constant/charset.ts";
import type { HandshakeBody } from "../parsers/handshake.ts";
import { clientCapabilities } from "./client_capabilities.ts";

export function buildSSLRequest(
packet: HandshakeBody,
params: { db?: string },
): Uint8Array {
const clientParam: number = clientCapabilities(packet, {
db: params.db,
ssl: true,
});
const writer = new BufferWriter(new Uint8Array(32));
writer
.writeUint32(clientParam)
.writeUint32(2 ** 24 - 1)
.write(Charset.UTF8_GENERAL_CI)
.skip(23);
return writer.wroteData;
}
11 changes: 11 additions & 0 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,17 @@ testWithClient(async function testDropUserWithMysqlNativePassword(client) {
await client.execute(`DROP USER 'testuser'@'%'`);
});

testWithClient(async function testTLS(client) {
await client.execute(`show databases`);
}, {
tls: {
mode: "verify_identity",
caCerts: [
await Deno.readTextFile("tls/cert/ca.pem"),
],
},
});

registerTests();

Deno.test("configLogger()", async () => {
Expand Down
27 changes: 27 additions & 0 deletions tls/cert/ca-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvBn5IVf97VJvdG7Jy/lun30oX6yEOJe4XsJ5C9bcxn6pX5lJ
h+Ayon7Bd0HKsJiT4eG9ecyHeytruAJWdb0ARjg5y3UZO1RpPzt9BNedCkG0aD1b
EYwkBA0nfpmkdQjr4/0CjlK8pFAlwIqKqdYbxiAw9lmQFup90uzcswBigB+WoHK+
dBMSISS+TvEPyoVEHxBORn7jDm1wYNNPuORFPrI/dArK3VXH4hbCZSkwtInLFzEG
HrgNjIt66lowA50V4nGcGVWMD6ipO6XouL7HFM4FGiaxvYan0J5tLEtNf2v3IfJo
7Rqh6jGh6Qy971KS1wOdGToOsZve9PIi6t54vQIDAQABAoIBADWb2WrtXwtiMS2n
3Y9qmWKPExChZFWUuBEZr9H1/Jn9w1vhnhlBhmzVX2ITuCa4dX0tDwlFh19NMrgQ
wn9vzEI3CBG6X+SO3CgPVkQpBzLDIx2KTwgjPqiA7z6fn0VTs5cYpr/VSLoztW64
jWh2AxhmWE39nJlLX2zb4NKLS4dj+/GV89hztieVVAxuJsrIaon0bopwmVpgCcLi
olyAA4863Azr74JSCd2jT/yqWnX8s8voeEU3KcesREFB1dcJkGDrlhAihrDqPBwJ
hVgB2DJIXLFwby/tUtAEbIrB4goJ4vOYcBRI2+hPT4v/tw7C1JBHYnBh+lUu73iv
NkCnPikCgYEA6zbfFzFBNVYqMBbRCAmY27TDKYY9gJ085Kqm7aiEH7WttK6tbDxt
aeCxHqh/hR1NMuYW0T+YufWGZJbGNvn7CvxfqciMy1U1ZLk3O3lRJ80P4yiBuLS8
FMStVSRiL2kuAPwhfLglOl8e1zofMjNAboOq4Al/hWsMEfbtQkrKvp8CgYEAzLlK
xdjg9UgJ/lZblGQzI9NUnsbmoGE3gaqLmFL0OZxyckSHYt+28OIMjlQb6GLBG1J1
g2I2xwmZ4VF+VGrC+Ojr4qPHiQ/2eXcy0U0JIJSgIW0Ck3DXCWSaN8H6Ghz8hAL0
3Trk5ly7BUyqkGNeDgZD1z42rNAf83vbnBHl9yMCgYAtbDtIz0o4cptTDhTv6GqM
gyvtKO9XlwXbYtk5rAFX3k3dRp5W+JRojeumcAOwQShXW/esEQv9XVzGsBc3Jq9E
P9h5gTEvUxUsjlgMNDFn7kHTLE9gzAZGPHT8rDoJzdYEeqw82ZLW4uehyKedmign
L2YgPbVSnomGLplC5A6jIQKBgFliVVlItf+h8msPvXBuxz1PGdUxNqSzjY6ZrdVV
8rfsrLNjZFExKCjIKX9DDeRKqdGKETDKAuyoLn2IHUYyTupqmAMeSxJ228Bw4Mkt
f1ywR7IQbF1/mQPK3uKVWONp4H6q48pr2mER/73ymU3tdLVe2uPxj+GoBStD2sCr
t+JNAoGABHm6F51ID5PAMDIqY4t6GptrZYXKGQ878rJv/RUw1b6ZTfB7dLPSMiiR
QT8Qt587WmkhiBXuGY257FbbTmE79qJsrUe7IfBcvtp6ETHEFaJ8AHMFteBEDhch
ABpzlHGdtpRxupXcQFuZrLXUrM67T4LHi82VWE2HJsqkhHwAwnc=
-----END RSA PRIVATE KEY-----
19 changes: 19 additions & 0 deletions tls/cert/ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDAzCCAeugAwIBAgIBATANBgkqhkiG9w0BAQsFADA8MTowOAYDVQQDDDFNeVNR
TF9TZXJ2ZXJfOC4wLjMyX0F1dG9fR2VuZXJhdGVkX0NBX0NlcnRpZmljYXRlMB4X
DTIzMDQyNjExNTQxOFoXDTMzMDQyMzExNTQxOFowPDE6MDgGA1UEAwwxTXlTUUxf
U2VydmVyXzguMC4zMl9BdXRvX0dlbmVyYXRlZF9DQV9DZXJ0aWZpY2F0ZTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALwZ+SFX/e1Sb3Ruycv5bp99KF+s
hDiXuF7CeQvW3MZ+qV+ZSYfgMqJ+wXdByrCYk+HhvXnMh3sra7gCVnW9AEY4Oct1
GTtUaT87fQTXnQpBtGg9WxGMJAQNJ36ZpHUI6+P9Ao5SvKRQJcCKiqnWG8YgMPZZ
kBbqfdLs3LMAYoAflqByvnQTEiEkvk7xD8qFRB8QTkZ+4w5tcGDTT7jkRT6yP3QK
yt1Vx+IWwmUpMLSJyxcxBh64DYyLeupaMAOdFeJxnBlVjA+oqTul6Li+xxTOBRom
sb2Gp9CebSxLTX9r9yHyaO0aoeoxoekMve9SktcDnRk6DrGb3vTyIureeL0CAwEA
AaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAGD6aeaiDt4sO
wAdRonwZGdLpwzTXtksPskJtl8qI/94EWqlFMkyeUfGcVHqGTvIxyfwM9Vk546j7
ep3SpEfGFIGXV/1szD8Zi+2ocOAMUd4FqfaHaFS7gbp76eNbELgitqaJjKJX25sV
AatWSJysK0qiJJNTNL5SR9NsEouzKYCFSbv+AeiVIpF7tK3JjZ9JGPI5BMW1mPvc
9uC1hwKWXUuEfyH2WYi4EbSjEIry6/bDWcDWNAUKN1kb6Ms7zHcV9u9XwWmF+G8J
TN2Z5QwQYH3T2Ao7D+0aGamRwBnaZz4SSiR4CArDDl8YkI7lsEjkZJb6f9DOzhh+
BNv5lkkyYg==
-----END CERTIFICATE-----
Loading

0 comments on commit 4caa9c4

Please sign in to comment.