├── proto └── microservice.capnp ├── .gitignore ├── src ├── errors.rs ├── rpc.rs └── lib.rs ├── README.md ├── LICENSE ├── Cargo.toml ├── examples ├── cli.yaml └── main.rs ├── tests └── lib.rs └── .travis.yml /proto/microservice.capnp: -------------------------------------------------------------------------------- 1 | @0xaf46030081f7e82d; 2 | 3 | interface Microservice { 4 | hello @0 (request :Text) -> (response :Text); 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | *.pfx 13 | *.pem 14 | *.der 15 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Basic error handling mechanisms 2 | #![allow(unused_doc_comment)] 3 | 4 | use std::io; 5 | use {capnp, native_tls, openssl}; 6 | 7 | error_chain! { 8 | foreign_links { 9 | Capnp(capnp::Error) #[doc = "A Cap'n Proto error."]; 10 | Io(io::Error) #[doc = "A I/O error."]; 11 | OpenSSL(openssl::error::ErrorStack) #[doc = "An OpenSSL error."]; 12 | Tls(native_tls::Error) #[doc = "A TLS error."]; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/rpc.rs: -------------------------------------------------------------------------------- 1 | use capnp; 2 | use capnp::capability::Promise; 3 | use microservice_capnp::microservice; 4 | 5 | /// The main RPC implementation structure 6 | pub struct Rpc; 7 | 8 | impl microservice::Server for Rpc { 9 | fn hello( 10 | &mut self, 11 | params: microservice::HelloParams, 12 | mut results: microservice::HelloResults, 13 | ) -> Promise<(), capnp::Error> { 14 | // Get the request 15 | let request = pry!(pry!(params.get()).get_request()); 16 | info!("Got request: {}", request); 17 | 18 | // Create the response 19 | let response: String = request.chars().rev().collect(); 20 | results.get().set_response(&response); 21 | info!("Returned reponse: {}", response); 22 | 23 | // Finish the future 24 | Promise::ok(()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microservice-rs 2 | [![Build Status](https://travis-ci.org/saschagrunert/microservice-rs.svg)](https://travis-ci.org/saschagrunert/microservice-rs) [![Coverage Status](https://coveralls.io/repos/github/saschagrunert/microservice-rs/badge.svg?branch=master)](https://coveralls.io/github/saschagrunert/microservice-rs?branch=master) [![Crates.io](https://img.shields.io/crates/v/microservice.svg)](https://crates.io/crates/microservice) [![doc.rs](https://docs.rs/microservice/badge.svg)](https://docs.rs/microservice) [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/saschagrunert/microservice-rs/blob/master/LICENSE) 3 | ## A basic microservice template using Rust and Cap'n Proto RPCs 4 | 5 | Target of the project is to show how to implement a secure and fast microservice architecture using Rust and an 6 | appropriate RPC framework. 7 | 8 | ## Contributing 9 | [contributing]: #contributing 10 | 11 | You want to contribute to this project? Wow, thanks! So please just fork it and send me a pull request. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sascha Grunert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "microservice" 3 | version = "0.1.0" 4 | authors = ["Sascha Grunert "] 5 | license = "MIT" 6 | readme = "README.md" 7 | keywords = ["microservice", "tokio", "capnp", "capnproto", "rpc"] 8 | repository = "https://github.com/saschagrunert/microservice-rs" 9 | homepage = "https://github.com/saschagrunert/microservice-rs" 10 | documentation = "https://docs.rs/microservice-rs" 11 | description = "A microservice template using Cap'n Proto" 12 | categories = ["network-programming"] 13 | 14 | [badges] 15 | travis-ci = { repository = "saschagrunert/microservice-rs", branch = "master" } 16 | appveyor = { repository = "saschagrunert/microservice-rs", branch = "master", service = "github" } 17 | 18 | [build-dependencies] 19 | capnpc = "0.8" 20 | 21 | [dependencies] 22 | capnp = "0.8" 23 | capnp-rpc = "0.8" 24 | clap = { version = "2", features = ["yaml"] } 25 | clippy = {version = "0", optional = true} 26 | error-chain = "0" 27 | futures = "0.1" 28 | log = "0.3" 29 | mowl = "1" 30 | native-tls = "0.1" 31 | openssl = "0.9" 32 | tokio-core = "0.1" 33 | tokio-io = "0.1" 34 | tokio-tls = "0.1" 35 | 36 | [features] 37 | default = [] 38 | -------------------------------------------------------------------------------- /examples/cli.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: microservice-rs 3 | bin_name: microservice 4 | author: Sascha Grunert 5 | about: A microservice template using Cap'n Proto 6 | after_help: 'More info at: https://github.com/saschagrunert/microservice-rs' 7 | global_settings: 8 | - VersionlessSubcommands 9 | - ColoredHelp 10 | 11 | args: 12 | - verbose: 13 | short: v 14 | help: Set the verbosity level (maximum 2x `v`) 15 | multiple: true 16 | - test: 17 | short: t 18 | long: test 19 | help: Create a test client and talk to a server instance. 20 | - address: 21 | short: a 22 | long: address 23 | help: The address for the client/server. 24 | takes_value: true 25 | default_value: 127.0.0.1:30080 26 | - serverdomains: 27 | long: server-domain 28 | help: The server alt names for the TLS certificate generation 29 | multiple: true 30 | takes_value: true 31 | default_value: 127.0.0.1 32 | - clientcertfile: 33 | long: client-cert-file 34 | help: The location of the TLS certificate file for the client 35 | takes_value: true 36 | default_value: cert.pem 37 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | extern crate microservice; 3 | 4 | use futures::Future; 5 | use microservice::Microservice; 6 | use std::{thread, time}; 7 | 8 | #[test] 9 | fn hello_success() { 10 | let addr = "127.0.0.1:30080"; 11 | let server_cert = "tests/certificate.pfx"; 12 | let client_cert = "tests/certificate.der"; 13 | 14 | // Run the server in a differenc instance 15 | thread::spawn(move || { 16 | Microservice::new(addr).unwrap().serve(server_cert).unwrap(); 17 | }); 18 | 19 | // Wait for the server to become ready 20 | let time = time::Duration::from_secs(1); 21 | thread::sleep(time); 22 | 23 | // Get a client to the microservice 24 | let (client, mut rpc) = Microservice::new(addr) 25 | .unwrap() 26 | .get_client(client_cert) 27 | .unwrap(); 28 | 29 | // Assemble the request 30 | let mut request = client.hello_request(); 31 | request.get().set_request("Hello"); 32 | 33 | // Run the RPC 34 | rpc.run(request.send().promise.and_then(|message| { 35 | // Get the response content 36 | let response = message.get()?.get_response()?; 37 | 38 | // Check the result 39 | assert_eq!(response, "olleH"); 40 | Ok(()) 41 | })).unwrap(); 42 | } 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: rust 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | matrix: 9 | allow_failures: 10 | - rust: nightly 11 | 12 | before_script: 13 | - curl -O https://capnproto.org/capnproto-c++-0.5.3.tar.gz 14 | - tar zxf capnproto-c++-0.5.3.tar.gz 15 | - cd capnproto-c++-0.5.3 16 | - ./configure --prefix=$HOME 17 | - make -j3 18 | - make install 19 | - cd .. 20 | - pip install 'travis-cargo<0.2' --user --verbose 21 | - export PATH=$HOME/.local/bin:$PATH 22 | - export PATH=$HOME/Library/Python/2.7/bin:$PATH 23 | - cargo install cargo-kcov 24 | 25 | script: 26 | - export CARGO_TARGET_DIR=`pwd`/target 27 | - travis-cargo build 28 | - travis-cargo test 29 | - cargo doc --no-deps 30 | 31 | after_success: 32 | - if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then travis-cargo doc-upload; fi 33 | - if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then cargo kcov --print-install-kcov-sh | sh; fi 34 | - if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then cargo kcov --coveralls --kcov ./kcov-33/build/src/kcov; fi 35 | 36 | notifications: 37 | email: 38 | on_success: never 39 | 40 | os: 41 | - linux 42 | 43 | addons: 44 | apt: 45 | sources: 46 | - kalakris-cmake 47 | packages: 48 | - cmake 49 | - libcurl4-openssl-dev 50 | - libiberty-dev 51 | - libelf-dev 52 | - libdw-dev 53 | - binutils-dev 54 | env: 55 | global: 56 | - TRAVIS_CARGO_NIGHTLY_FEATURE="" 57 | - secure: WC9zSfV0u90iqsZD5477le2r7f0PwPEXQffN2fVbiV1kOWYZa7czyMeLQdCadfROf9K5SMgDBvRdB+FEyVWDMsatp5hIfeLyxug/xrktP7onzcWIImX+zFXV/Z+RDWOH+ojDsgmtR9PmP+k1+8DAq+w7d2e9uWkrVXhuY2xwvoUi0Ry3Ilw3Kji4EkzAV+sTCHV0wYM95Q1OOeYgIxf69Az73DqC6H254lud+EHculiBKaT+rnLvTdWJKP97xLuZ9p7wB368D3I4LGSQvtuGFynO+FRhjPbWKljeu6D1Q/Zj/CzX8Us0OXY0z4oGPsJTfLeSEJsIwTPFnqCyzmMBkpV4GC63Prso8pYmSe9a+226OAtLTyhWTWuHpw78BO23lsSLHpRIpHleNeMJhdAirEld7OWV3R3gYSNfuYKEQ6moQfpLdpWQbAddmE5qDsp9T5WIeFRu+aUPR45h6H4ma3w0txGiNSJKKzopIolnI3TZoI05B+dOGYoxPAhx5r1adDsqhZiXMgAni/NmUAN+nY19qBOLGQKhrqdqzRyz9QeNUiirPiL1ezyKQazTqjuubsgrbGk3hz9nyFrVgfEGbacYL6wCRzZuoOhBjcSePhymUXjSI+7PB5Ew4GfFNi5Jqxx47/Ba+uM5Gk8CTft/CjQ6jBOH2YxZeVHmwXKbaNI= 58 | -------------------------------------------------------------------------------- /examples/main.rs: -------------------------------------------------------------------------------- 1 | //! The main command line interface to the microservice 2 | #![cfg_attr(feature="clippy", feature(plugin))] 3 | 4 | #[macro_use] 5 | extern crate clap; 6 | 7 | #[macro_use] 8 | extern crate log; 9 | extern crate futures; 10 | extern crate microservice; 11 | extern crate mowl; 12 | 13 | use std::error::Error; 14 | use std::process::exit; 15 | 16 | use clap::App; 17 | use futures::Future; 18 | use log::LogLevel; 19 | 20 | use microservice::Microservice; 21 | use microservice::errors::*; 22 | 23 | fn error_and_exit(string: &str, error: &Error) { 24 | error!("{}: {}", string, error); 25 | exit(1); 26 | } 27 | 28 | pub fn main() { 29 | if let Err(error) = run() { 30 | error_and_exit("Main", &error); 31 | } 32 | } 33 | 34 | fn run() -> Result<()> { 35 | // Load the CLI parameters from the yaml file 36 | let yaml = load_yaml!("cli.yaml"); 37 | let app = App::from_yaml(yaml).version(crate_version!()); 38 | let matches = app.clone().get_matches(); 39 | 40 | // Set the verbosity level 41 | let log_level = match matches.occurrences_of("verbose") { 42 | 0 => LogLevel::Info, // Default value 43 | 1 => LogLevel::Debug, 44 | _ => LogLevel::Trace, 45 | }; 46 | 47 | // Init the logging 48 | match mowl::init_with_level(log_level) { 49 | Err(_) => warn!("Log level already set"), 50 | Ok(_) => info!("Log level set to: {}", log_level), 51 | } 52 | 53 | // Check the CLI parameters 54 | let address = matches.value_of("address").ok_or_else( 55 | || "No CLI 'address' provided", 56 | )?; 57 | let server_domains: Vec<&str> = matches 58 | .values_of("serverdomains") 59 | .ok_or_else(|| "No server domains provided")? 60 | .collect(); 61 | let client_cert_file = matches.value_of("clientcertfile").ok_or_else( 62 | || "No client certificate provided", 63 | )?; 64 | 65 | // Create the microservice instance 66 | let microservice = Microservice::new(address)?; 67 | 68 | // Check if testing is enabled 69 | if matches.is_present("test") { 70 | // Get a client to the microservice 71 | let (client, mut rpc) = microservice.get_client(client_cert_file)?; 72 | 73 | // Assemble the request 74 | let mut request = client.hello_request(); 75 | request.get().set_request("Hello"); 76 | 77 | // Run the RPC 78 | info!("Running the RPC."); 79 | rpc.run(request.send().promise.and_then(|message| { 80 | // Get the response content 81 | let response = message.get()?.get_response()?; 82 | info!("Got response: {}", response); 83 | 84 | // Check the result 85 | assert_eq!(response, "olleH"); 86 | Ok(()) 87 | }))?; 88 | 89 | info!("Test passed."); 90 | } else { 91 | // Start the server 92 | microservice.serve(&server_domains)?; 93 | } 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Microservice Template 2 | //! 3 | //! This crate contains the library for the basic microservice template using Cap'n Proto and Rust. 4 | //! 5 | #![cfg_attr(feature="clippy", feature(plugin))] 6 | #![deny(missing_docs)] 7 | 8 | #[macro_use] 9 | extern crate capnp_rpc; 10 | extern crate capnp; 11 | extern crate futures; 12 | extern crate native_tls; 13 | extern crate openssl; 14 | extern crate tokio_core; 15 | extern crate tokio_io; 16 | extern crate tokio_tls; 17 | 18 | #[macro_use] 19 | extern crate error_chain; 20 | 21 | #[macro_use] 22 | extern crate log; 23 | 24 | #[macro_use] 25 | pub mod errors; 26 | pub mod microservice_capnp { 27 | #![allow(missing_docs)] 28 | include!(concat!(env!("OUT_DIR"), "/proto/microservice_capnp.rs")); 29 | } 30 | mod rpc; 31 | 32 | use std::env::current_dir; 33 | use std::net::ToSocketAddrs; 34 | use std::fs::File; 35 | use std::io::{self, Read, Write}; 36 | 37 | use capnp_rpc::{RpcSystem, twoparty, rpc_twoparty_capnp, Server}; 38 | use futures::{Future, Stream}; 39 | use native_tls::{Certificate, Pkcs12, TlsAcceptor, TlsConnector}; 40 | use openssl::asn1::Asn1Time; 41 | use openssl::bn::{BigNum, MSB_MAYBE_ZERO}; 42 | use openssl::hash::MessageDigest; 43 | use openssl::pkey::PKey; 44 | use openssl::rsa::Rsa; 45 | use openssl::x509::extension::{KeyUsage, SubjectAlternativeName}; 46 | use openssl::x509::{X509, X509Builder, X509NameBuilder}; 47 | use tokio_core::{net, reactor}; 48 | use tokio_io::AsyncRead; 49 | use tokio_tls::{TlsAcceptorExt, TlsConnectorExt}; 50 | 51 | use errors::*; 52 | use microservice_capnp::microservice; 53 | 54 | const PKCS12_PASSWORD: &str = ""; 55 | const CERT_FILENAME: &str = "cert.pem"; 56 | 57 | /// The main microservice structure 58 | pub struct Microservice { 59 | socket_addr: std::net::SocketAddr, 60 | } 61 | 62 | impl Microservice { 63 | /// Create a new microservice instance 64 | pub fn new(address: &str) -> Result { 65 | // Parse socket address 66 | let parsed_address = address.to_socket_addrs()?.next().ok_or_else( 67 | || "Could not parse socket address.", 68 | )?; 69 | info!("Parsed socket address: {}", parsed_address); 70 | 71 | // Return service 72 | Ok(Microservice { socket_addr: parsed_address }) 73 | } 74 | 75 | /// Runs the server 76 | pub fn serve(&self, domains: &[&str]) -> Result<()> { 77 | info!("Creating server and binding socket."); 78 | let mut core = reactor::Core::new()?; 79 | let handle = core.handle(); 80 | let socket = net::TcpListener::bind(&self.socket_addr, &handle)?; 81 | 82 | // Generate TLS server certificate 83 | let cert = Self::generate_cert(domains)?; 84 | info!("Created new server ceritifacte"); 85 | 86 | // Create the acceptor 87 | let tls_acceptor = TlsAcceptor::builder(cert)?.build()?; 88 | 89 | let server_impl = microservice::ToClient::new(rpc::Rpc).from_server::(); 90 | let connections = socket.incoming(); 91 | 92 | let tls_handshake = connections.map(|(socket, _addr)| { 93 | if let Err(e) = socket.set_nodelay(true) { 94 | error!("Unable to set socket to nodelay: {:}", e); 95 | } 96 | tls_acceptor.accept_async(socket) 97 | }); 98 | 99 | let server = tls_handshake.map(|acceptor| { 100 | let handle = handle.clone(); 101 | let server_impl = server_impl.clone(); 102 | acceptor.and_then(move |socket| { 103 | let (reader, writer) = socket.split(); 104 | 105 | let network = twoparty::VatNetwork::new( 106 | reader, 107 | writer, 108 | rpc_twoparty_capnp::Side::Server, 109 | Default::default(), 110 | ); 111 | 112 | let rpc_system = RpcSystem::new(Box::new(network), Some(server_impl.client)); 113 | handle.spawn(rpc_system.map_err(|e| error!("{}", e))); 114 | Ok(()) 115 | }) 116 | }); 117 | 118 | info!("Running server"); 119 | Ok(core.run(server.for_each(|client| { 120 | handle.spawn(client.map_err(|e| error!("{}", e))); 121 | Ok(()) 122 | }))?) 123 | } 124 | 125 | /// Retrieve a client to the microservice instance 126 | pub fn get_client(&self, client_cert_file: &str) -> Result<(microservice::Client, reactor::Core)> { 127 | info!("Opening TLS client certificate."); 128 | let mut bytes = vec![]; 129 | File::open(client_cert_file)?.read_to_end(&mut bytes)?; 130 | 131 | let der_cert = X509::from_pem(&bytes)?.to_der()?; 132 | let cert = Certificate::from_der(&der_cert)?; 133 | 134 | info!("Creating client."); 135 | let mut core = reactor::Core::new()?; 136 | let handle = core.handle(); 137 | 138 | let socket = net::TcpStream::connect(&self.socket_addr, &handle); 139 | let mut builder = TlsConnector::builder()?; 140 | builder.add_root_certificate(cert)?; 141 | let cx = builder.build()?; 142 | let tls_handshake = socket.and_then(|socket| { 143 | if let Err(e) = socket.set_nodelay(true) { 144 | error!("Unable to set socket to nodelay: {:}", e); 145 | } 146 | cx.connect_async("localhost", socket).map_err(|e| { 147 | io::Error::new(io::ErrorKind::Other, e) 148 | }) 149 | }); 150 | 151 | let stream = core.run(tls_handshake)?; 152 | let (reader, writer) = stream.split(); 153 | 154 | let network = Box::new(twoparty::VatNetwork::new( 155 | reader, 156 | writer, 157 | rpc_twoparty_capnp::Side::Client, 158 | Default::default(), 159 | )); 160 | let mut rpc_system = RpcSystem::new(network, None); 161 | let client: microservice::Client = rpc_system.bootstrap(rpc_twoparty_capnp::Side::Server); 162 | handle.spawn(rpc_system.map_err(|e| error!("{}", e))); 163 | 164 | info!("Client creation successful."); 165 | Ok((client, core)) 166 | } 167 | 168 | fn get_cert_builder_and_private_key() -> Result<(X509Builder, PKey)> { 169 | // Create private key 170 | let private_key = PKey::from_rsa(Rsa::generate(4096)?)?; 171 | 172 | // Create cert builder 173 | let mut cert_builder = X509Builder::new()?; 174 | cert_builder.set_version(3)?; 175 | cert_builder.set_pubkey(&private_key)?; 176 | 177 | // Generate serial number 178 | let mut serial = BigNum::new()?; 179 | serial.rand(128, MSB_MAYBE_ZERO, false)?; 180 | let asn1_serial = serial.to_asn1_integer()?; 181 | cert_builder.set_serial_number(&asn1_serial)?; 182 | 183 | // Set certificate validity 184 | let now = Asn1Time::days_from_now(0)?; 185 | let then = Asn1Time::days_from_now(365 * 10)?; 186 | cert_builder.set_not_before(&now)?; 187 | cert_builder.set_not_after(&then)?; 188 | 189 | Ok((cert_builder, private_key)) 190 | } 191 | 192 | fn generate_cert(domains: &[&str]) -> Result { 193 | // Get the default cert builder and private key 194 | let (mut cert_builder, private_key) = Self::get_cert_builder_and_private_key()?; 195 | 196 | // Set certificate subject 197 | let mut subject_builder = X509NameBuilder::new()?; 198 | let first_domain = domains.iter().nth(0).unwrap_or(&""); 199 | info!("Setting server certificate common name: {}", first_domain); 200 | subject_builder.append_entry_by_text("CN", first_domain)?; 201 | let subject = subject_builder.build(); 202 | cert_builder.set_subject_name(&subject)?; 203 | cert_builder.set_issuer_name(&subject)?; 204 | 205 | // Set key usage extension 206 | let mut key_usage = KeyUsage::new(); 207 | key_usage.non_repudiation(); 208 | key_usage.digital_signature(); 209 | key_usage.key_encipherment(); 210 | let key_ext = key_usage.build()?; 211 | cert_builder.append_extension(key_ext)?; 212 | 213 | // Set subject alt names if needed 214 | if domains.len() > 1 { 215 | let mut san = SubjectAlternativeName::new(); 216 | for domain in domains.iter().skip(1) { 217 | info!("Setting server subject alt name: {}", domain); 218 | san.dns(domain); 219 | } 220 | let san_ext = san.build(&cert_builder.x509v3_context(None, None))?; 221 | cert_builder.append_extension(san_ext)?; 222 | } 223 | 224 | // Sign the ceritifacte 225 | cert_builder.sign(&private_key, MessageDigest::sha256())?; 226 | 227 | // Create the Pkcs12 bundle 228 | let cert = cert_builder.build(); 229 | let openssl_pkcs12 = openssl::pkcs12::Pkcs12::builder().build( 230 | PKCS12_PASSWORD, 231 | "server", 232 | &private_key, 233 | &cert, 234 | )?; 235 | let der_bytes = openssl_pkcs12.to_der()?; 236 | let native_tls_pkcs12 = Pkcs12::from_der(&der_bytes, PKCS12_PASSWORD)?; 237 | 238 | // Save the cert and key 239 | let cert_pem = cert.to_pem()?; 240 | let key_pem = private_key.public_key_to_pem()?; 241 | let filename = current_dir()?.join(CERT_FILENAME); 242 | let mut file = File::create(&filename)?; 243 | file.write_all(&cert_pem)?; 244 | file.write_all(&key_pem)?; 245 | info!("Wrote certificate to: {}", filename.display()); 246 | 247 | Ok(native_tls_pkcs12) 248 | } 249 | } 250 | --------------------------------------------------------------------------------