Repository: blackbeam/rust-mysql-simple (language: rust)

Issue:
Add support for multiple CA certificates
[Edited to reflect behaviour for DER as well, and to clarify a little what the desired functionality is.]

From https://docs.rs/mysql/20.0.1/mysql/struct.SslOpts.html#method.with_root_cert_path :
"If you have multiple certificates in a .pem file, only the first one will be loaded." (Out of date? I get a TlsError in this case: "One or more parameters passed to a function were not valid.")

For a DER bundle, I get a TlsHandshakeError: "The certificate was not trusted." It would appear to me that some changes have been made in the native-tls crate since this bit in the mysql crate documentation was written.

The IT architecture for one of our customers however was designed with the understanding that cross-region (AWS) DB connections should be supported. Therefore we are keen to use bundle files, like those that AWS provides.

I can see that you're using add_root_certificate ( https://docs.rs/native-tls/0.2.4/native_tls/struct.TlsConnectorBuilder.html#method.add_root_certificate ) behind the scenes, but I see no way of accessing that functionality via the mysql crate as its API revolves around a single CA certificate.

Would it be possible for you to add support for specifying multiple CA certs and / or a bundle file?

Thank you in advance!


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 5d13959d..7f643944 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -39,6 +39,7 @@ lru = "0.6.0"
 mysql_common = "0.24.1"
 native-tls = "0.2.3"
 socket2 = "0.3.12"
+pem = "0.8.1"
 percent-encoding = "2.1.0"
 serde = "1"
 serde_json = "1"
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index a6e63266..d6bfbf8a 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -1,11 +1,11 @@
 trigger:
-- master
-- ci-*
+  - master
+  - ci-*
 
 jobs:
-  - job: 'TestBasicLinux'
+  - job: "TestBasicLinux"
     pool:
-      vmImage: 'ubuntu-latest'
+      vmImage: "ubuntu-latest"
     strategy:
       maxParallel: 10
       matrix:
@@ -16,101 +16,101 @@ jobs:
         nightly:
           RUST_TOOLCHAIN: nightly
     steps:
-    - bash: |
-        sudo apt-get update
-        sudo apt-get -y install mysql-server libmysqlclient-dev curl
-        sudo service mysql start
-        mysql -e "SET GLOBAL max_allowed_packet = 36700160;" -uroot -proot
-      displayName: Install MySql
-    - bash: |
-        curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $(RUST_TOOLCHAIN)
-        echo '##vso[task.setvariable variable=toolchain;isOutput=true]$(RUST_TOOLCHAIN)'
-      displayName: Install Rust
-      name: installRust
-    - bash: |
-        rustup component add rustfmt
-        cargo fmt -- --check
-      condition: and(succeeded(), eq(variables['installRust.toolchain'], 'stable'))
-      displayName: cargo fmt
-    - bash: |
-        SSL=false COMPRESS=false cargo test
-        SSL=true COMPRESS=false cargo test
-        SSL=false COMPRESS=true cargo test
-        SSL=true COMPRESS=true cargo test
-      env:
-        RUST_BACKTRACE: 1
-        DATABASE_URL: mysql://root:root@127.0.0.1:3306/mysql
-      displayName: Run tests
+      - bash: |
+          sudo apt-get update
+          sudo apt-get -y install mysql-server libmysqlclient-dev curl
+          sudo service mysql start
+          mysql -e "SET GLOBAL max_allowed_packet = 36700160;" -uroot -proot
+        displayName: Install MySql
+      - bash: |
+          curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $(RUST_TOOLCHAIN)
+          echo '##vso[task.setvariable variable=toolchain;isOutput=true]$(RUST_TOOLCHAIN)'
+        displayName: Install Rust
+        name: installRust
+      - bash: |
+          rustup component add rustfmt
+          cargo fmt -- --check
+        condition: and(succeeded(), eq(variables['installRust.toolchain'], 'stable'))
+        displayName: cargo fmt
+      - bash: |
+          SSL=false COMPRESS=false cargo test
+          SSL=true COMPRESS=false cargo test
+          SSL=false COMPRESS=true cargo test
+          SSL=true COMPRESS=true cargo test
+        env:
+          RUST_BACKTRACE: 1
+          DATABASE_URL: mysql://root:root@127.0.0.1:3306/mysql
+        displayName: Run tests
 
-  - job: 'TestBasicMacOs'
+  - job: "TestBasicMacOs"
     pool:
-      vmImage: 'macOS-10.15'
+      vmImage: "macOS-10.15"
     strategy:
       maxParallel: 10
       matrix:
         stable:
           RUST_TOOLCHAIN: stable
     steps:
-    - bash: |
-        brew update
-        brew install mysql
-        brew services start mysql
-        brew services stop mysql
-        sleep 3
-        echo 'local_infile=1' >> /usr/local/etc/my.cnf
-        brew services start mysql
-        sleep 2
-        /usr/local/Cellar/mysql/*/bin/mysql -e "SET GLOBAL max_allowed_packet = 36700160;" -uroot
-      displayName: Install MySql
-    - bash: |
-        curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUST_TOOLCHAIN
-      displayName: Install rust (MacOs)
-    - bash: |
-        SSL=false COMPRESS=false cargo test
-        SSL=true COMPRESS=false cargo test
-        SSL=false COMPRESS=true cargo test
-        SSL=true COMPRESS=true cargo test
-      env:
-        RUST_BACKTRACE: 1
-        DATABASE_URL: mysql://root@127.0.0.1/mysql
-      displayName: Run tests
+      - bash: |
+          brew update
+          brew install mysql
+          brew services start mysql
+          brew services stop mysql
+          sleep 3
+          echo 'local_infile=1' >> /usr/local/etc/my.cnf
+          echo 'socket=/tmp/mysql.sock' >> /usr/local/etc/my.cnf
+          brew services start mysql
+          sleep 5
+          /usr/local/Cellar/mysql/*/bin/mysql -e "SET GLOBAL max_allowed_packet = 36700160;" -uroot
+        displayName: Install MySql
+      - bash: |
+          curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUST_TOOLCHAIN
+        displayName: Install rust (MacOs)
+      - bash: |
+          SSL=false COMPRESS=false cargo test
+          SSL=true COMPRESS=false cargo test
+          SSL=false COMPRESS=true cargo test
+          SSL=true COMPRESS=true cargo test
+        env:
+          RUST_BACKTRACE: 1
+          DATABASE_URL: mysql://root@127.0.0.1/mysql
+        displayName: Run tests
 
-  - job: 'TestBasicWindows'
+  - job: "TestBasicWindows"
     pool:
-      vmImage: 'vs2017-win2016'
+      vmImage: "vs2017-win2016"
     strategy:
       maxParallel: 10
       matrix:
         stable:
           RUST_TOOLCHAIN: stable
     steps:
-    - script: |
-        choco install 7zip
-        mkdir C:\mysql
-        CD /D C:\mysql
-        curl -fsS --retry 3 --retry-connrefused -o mysql.msi https://cdn.mysql.com/archives/mysql-installer/mysql-installer-community-8.0.11.0.msi
-        msiexec /q /log install.txt /i mysql.msi datadir=C:\mysql installdir=C:\mysql
-        call "C:\Program Files (x86)\MySQL\MySQL Installer for Windows\MySQLInstallerConsole.exe" community install server;8.0.11;x64:*:port=3306;rootpasswd=password;servicename=MySQL -silent
-        netsh advfirewall firewall add rule name="Allow mysql" dir=in action=allow edge=yes remoteip=any protocol=TCP localport=80,8080,3306
-        "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SET GLOBAL max_allowed_packet = 36700160;" -uroot -ppassword
-      displayName: Install MySql
-    - bash: |
-        rustup install $RUST_TOOLCHAIN
-      displayName: Install Rust (Windows)
-    - bash: |
-        SSL=false COMPRESS=false cargo test
-        SSL=true COMPRESS=false cargo test
-        SSL=false COMPRESS=true cargo test
-        SSL=true COMPRESS=true cargo test
-      env:
-        RUST_BACKTRACE: 1
-        DATABASE_URL: mysql://root:password@127.0.0.1/mysql
-      displayName: Run tests
+      - script: |
+          choco install 7zip
+          mkdir C:\mysql
+          CD /D C:\mysql
+          curl -fsS --retry 3 --retry-connrefused -o mysql.msi https://cdn.mysql.com/archives/mysql-installer/mysql-installer-community-8.0.11.0.msi
+          msiexec /q /log install.txt /i mysql.msi datadir=C:\mysql installdir=C:\mysql
+          call "C:\Program Files (x86)\MySQL\MySQL Installer for Windows\MySQLInstallerConsole.exe" community install server;8.0.11;x64:*:port=3306;rootpasswd=password;servicename=MySQL -silent
+          netsh advfirewall firewall add rule name="Allow mysql" dir=in action=allow edge=yes remoteip=any protocol=TCP localport=80,8080,3306
+          "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SET GLOBAL max_allowed_packet = 36700160;" -uroot -ppassword
+        displayName: Install MySql
+      - bash: |
+          rustup install $RUST_TOOLCHAIN
+        displayName: Install Rust (Windows)
+      - bash: |
+          SSL=false COMPRESS=false cargo test
+          SSL=true COMPRESS=false cargo test
+          SSL=false COMPRESS=true cargo test
+          SSL=true COMPRESS=true cargo test
+        env:
+          RUST_BACKTRACE: 1
+          DATABASE_URL: mysql://root:password@127.0.0.1/mysql
+        displayName: Run tests
 
-
-  - job: 'TestMySql'
+  - job: "TestMySql"
     pool:
-      vmImage: 'ubuntu-latest'
+      vmImage: "ubuntu-latest"
     strategy:
       maxParallel: 10
       matrix:
@@ -121,36 +121,36 @@ jobs:
         v56:
           DB_VERSION: "5.6"
     steps:
-    - bash: |
-        sudo apt-get update
-        sudo apt-get install docker.io netcat grep
-        sudo systemctl unmask docker
-        sudo systemctl start docker
-        docker --version
-      displayName: Install docker
-    - bash: |
-        docker run --rm --name container -v `pwd`:/root -p 3307:3306 -d -e MYSQL_ROOT_PASSWORD=password mysql:$(DB_VERSION) --max-allowed-packet=36700160 --local-infile
-        while ! nc -W 1 localhost 3307 | grep -q -P '.+'; do sleep 1; done
-      displayName: Run MySql in Docker
-    - bash: |
-        docker exec container bash -l -c "apt-get update"
-        docker exec container bash -l -c "apt-get install -y curl clang libssl-dev pkg-config"
-        docker exec container bash -l -c "curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable"
-      displayName: Install Rust in docker
-    - bash: |
-        if [[ "5.6" != "$(DB_VERSION)" ]]; then SSL=true; fi
-        docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL cargo test"
-        docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL COMPRESS=true cargo test"
-        docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=$SSL cargo test"
-        docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=$SSL COMPRESS=true cargo test"
-      env:
-        RUST_BACKTRACE: 1
-        DATABASE_URL: mysql://root:password@127.0.0.1/mysql
-      displayName: Run tests in Docker
+      - bash: |
+          sudo apt-get update
+          sudo apt-get install docker.io netcat grep
+          sudo systemctl unmask docker
+          sudo systemctl start docker
+          docker --version
+        displayName: Install docker
+      - bash: |
+          docker run --rm --name container -v `pwd`:/root -p 3307:3306 -d -e MYSQL_ROOT_PASSWORD=password mysql:$(DB_VERSION) --max-allowed-packet=36700160 --local-infile
+          while ! nc -W 1 localhost 3307 | grep -q -P '.+'; do sleep 1; done
+        displayName: Run MySql in Docker
+      - bash: |
+          docker exec container bash -l -c "apt-get update"
+          docker exec container bash -l -c "apt-get install -y curl clang libssl-dev pkg-config"
+          docker exec container bash -l -c "curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable"
+        displayName: Install Rust in docker
+      - bash: |
+          if [[ "5.6" != "$(DB_VERSION)" ]]; then SSL=true; fi
+          docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL cargo test"
+          docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL COMPRESS=true cargo test"
+          docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=$SSL cargo test"
+          docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=$SSL COMPRESS=true cargo test"
+        env:
+          RUST_BACKTRACE: 1
+          DATABASE_URL: mysql://root:password@127.0.0.1/mysql
+        displayName: Run tests in Docker
 
-  - job: 'TestMariaDb'
+  - job: "TestMariaDb"
     pool:
-      vmImage: 'ubuntu-latest'
+      vmImage: "ubuntu-latest"
     strategy:
       maxParallel: 10
       matrix:
@@ -165,43 +165,43 @@ jobs:
         v101:
           DB_VERSION: "10.1"
     steps:
-    - bash: |
-        sudo apt-get update
-        sudo apt-get install docker.io netcat grep
-        sudo systemctl unmask docker
-        sudo systemctl start docker
-        docker --version
-      displayName: Install docker
-    - bash: |
-        docker run --rm -d \
-            --name container \
-            -v `pwd`:/root \
-            -p 3307:3306 \
-            -e MYSQL_ROOT_PASSWORD=password \
-            mariadb:$(DB_VERSION) \
-                --max-allowed-packet=36700160 \
-                --local-infile \
-                --performance-schema=on \
-                --ssl \
-                --ssl-ca=/root/tests/ca-cert.pem \
-                --ssl-cert=/root/tests/server-cert.pem \
-                --ssl-key=/root/tests/server-key.pem
-        while ! nc -W 1 localhost 3307 | grep -q -P '.+'; do sleep 1; done
-        docker logs container
-        docker exec container bash -l -c "mysql -uroot -ppassword -e 'SHOW VARIABLES LIKE \"%ssl%\"'"
-        docker exec container bash -l -c "ls -la /root/tests"
-      displayName: Run MariaDb in Docker
-    - bash: |
-        docker exec container bash -l -c "apt-get update"
-        docker exec container bash -l -c "apt-get install -y curl clang libssl-dev pkg-config"
-        docker exec container bash -l -c "curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable"
-      displayName: Install Rust in docker
-    - bash: |
-        docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL cargo test"
-        docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL COMPRESS=true cargo test"
-        docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true cargo test"
-        docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true COMPRESS=true cargo test"
-      env:
-        RUST_BACKTRACE: 1
-        DATABASE_URL: mysql://root:password@127.0.0.1/mysql
-      displayName: Run tests in Docker
+      - bash: |
+          sudo apt-get update
+          sudo apt-get install docker.io netcat grep
+          sudo systemctl unmask docker
+          sudo systemctl start docker
+          docker --version
+        displayName: Install docker
+      - bash: |
+          docker run --rm -d \
+              --name container \
+              -v `pwd`:/root \
+              -p 3307:3306 \
+              -e MYSQL_ROOT_PASSWORD=password \
+              mariadb:$(DB_VERSION) \
+                  --max-allowed-packet=36700160 \
+                  --local-infile \
+                  --performance-schema=on \
+                  --ssl \
+                  --ssl-ca=/root/tests/ca-cert.pem \
+                  --ssl-cert=/root/tests/server-cert.pem \
+                  --ssl-key=/root/tests/server-key.pem
+          while ! nc -W 1 localhost 3307 | grep -q -P '.+'; do sleep 1; done
+          docker logs container
+          docker exec container bash -l -c "mysql -uroot -ppassword -e 'SHOW VARIABLES LIKE \"%ssl%\"'"
+          docker exec container bash -l -c "ls -la /root/tests"
+        displayName: Run MariaDb in Docker
+      - bash: |
+          docker exec container bash -l -c "apt-get update"
+          docker exec container bash -l -c "apt-get install -y curl clang libssl-dev pkg-config"
+          docker exec container bash -l -c "curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable"
+        displayName: Install Rust in docker
+      - bash: |
+          docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL cargo test"
+          docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL COMPRESS=true cargo test"
+          docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true cargo test"
+          docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true COMPRESS=true cargo test"
+        env:
+          RUST_BACKTRACE: 1
+          DATABASE_URL: mysql://root:password@127.0.0.1/mysql
+        displayName: Run tests in Docker
diff --git a/src/conn/mod.rs b/src/conn/mod.rs
index d3aee7d7..f83ceea1 100644
--- a/src/conn/mod.rs
+++ b/src/conn/mod.rs
@@ -1090,7 +1090,7 @@ mod test {
             Conn,
             DriverError::{MissingNamedParameter, NamedParamsForPositionalQuery},
             Error::DriverError,
-            LocalInfileHandler, Opts, OptsBuilder, Params, Pool, TxOpts,
+            LocalInfileHandler, Opts, OptsBuilder, Pool, TxOpts,
             Value::{self, Bytes, Date, Float, Int, NULL},
         };
 
diff --git a/src/conn/opts.rs b/src/conn/opts.rs
index 77ebebd7..76e4ba04 100644
--- a/src/conn/opts.rs
+++ b/src/conn/opts.rs
@@ -52,8 +52,7 @@ impl SslOpts {
     /// Sets path to a certificate of the root that connector will trust.
     ///
     /// Supported certificate formats are .der and .pem.
-    /// If you have multiple certificates in a .pem file, only the first one will
-    /// be loaded.
+    /// Multiple certs are allowed in .pem files.
     pub fn with_root_cert_path<T: Into<Cow<'static, Path>>>(
         mut self,
         root_cert_path: Option<T>,
diff --git a/src/conn/pool.rs b/src/conn/pool.rs
index 9dba8588..c2517280 100644
--- a/src/conn/pool.rs
+++ b/src/conn/pool.rs
@@ -446,7 +446,7 @@ mod test {
                 .unwrap()
                 .unwrap();
 
-            conn.exec_drop("KILL CONNECTION ?", (id,)).unwrap();
+            conn.query_drop(&*format!("KILL {}", id)).unwrap();
             thread::sleep(Duration::from_millis(250));
             pool.get_conn()
                 .unwrap()
@@ -466,7 +466,7 @@ mod test {
                 .unwrap()
                 .unwrap();
 
-            conn.exec_drop("KILL CONNECTION ?", (id,)).unwrap();
+            conn.query_drop(&*format!("KILL {}", id)).unwrap();
             thread::sleep(Duration::from_millis(250));
             pool.get_conn()
                 .unwrap()
@@ -485,7 +485,7 @@ mod test {
                 .unwrap()
                 .unwrap();
 
-            conn.exec_drop("KILL CONNECTION ?", (id,)).unwrap();
+            conn.query_drop(&*format!("KILL {}", id)).unwrap();
             thread::sleep(Duration::from_millis(250));
             pool.start_transaction(TxOpts::default()).unwrap();
         }
diff --git a/src/io/mod.rs b/src/io/mod.rs
index 74fbdd2c..7e1293cb 100644
--- a/src/io/mod.rs
+++ b/src/io/mod.rs
@@ -155,9 +155,20 @@ impl Stream {
             let mut root_cert_data = vec![];
             let mut root_cert_file = File::open(root_cert_path)?;
             root_cert_file.read_to_end(&mut root_cert_data)?;
-            let root_cert = Certificate::from_pem(&*root_cert_data)
-                .or_else(|_| Certificate::from_der(&*root_cert_data))?;
-            builder.add_root_certificate(root_cert);
+
+            let root_certs = Certificate::from_der(&*root_cert_data)
+                .map(|x| vec![x])
+                .or_else(|_| {
+                    pem::parse_many(&*root_cert_data)
+                        .iter()
+                        .map(pem::encode)
+                        .map(|s| Certificate::from_pem(s.as_bytes()))
+                        .collect()
+                })?;
+
+            for root_cert in root_certs {
+                builder.add_root_certificate(root_cert);
+            }
         }
         if let Some(pkcs12_path) = ssl_opts.pkcs12_path() {
             let der = std::fs::read(pkcs12_path)?;
diff --git a/src/lib.rs b/src/lib.rs
index 3323332a..0b1db356 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -423,7 +423,7 @@
 //!
 //! // More than 12 parameters:
 //! let row: Option<u8> = conn.exec_first(
-//!     "SELECT ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ?",
+//!     "SELECT CONVERT(? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ?, UNSIGNED)",
 //!     (0..16).collect::<Vec<_>>(),
 //! )?;
 //! assert_eq!(row.unwrap(), 120);


Test patch: