[
  {
    "path": ".cargo/config",
    "content": "[build]\nrustflags = [\"-Ctarget-feature=+aes,+ssse3\"]\nrustdocflags = [\"-Ctarget-feature=+aes,+ssse3\"]\n[test]\nrustflags = [\"-Ctarget-feature=+aes,+ssse3\"]\n\n"
  },
  {
    "path": ".github/workflows/cfntsci.yml",
    "content": "---\nname: cfntsCI\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  Testing:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checking out\n        uses: actions/checkout@v3\n      - name: Setting up Rust\n        uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n          toolchain: stable\n          components: clippy, rustfmt\n          override: true\n      - name: Rust cache\n        uses: Swatinem/rust-cache@v1\n      - name: Linting\n        run: cargo clippy --all-targets -- -D warnings\n      - name: Format\n        run: cargo fmt --all --check\n      - name: Building\n        run: cargo build --release\n      - name: Testing\n        run: cargo test -- --nocapture\n  E2E:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checking out\n        uses: actions/checkout@v3\n      - name: Run integration tests\n        uses: isbang/compose-action@v1.4.1\n        with:\n          compose-file: \"./docker-compose.yaml\"\n          up-flags: \"--build --abort-on-container-exit --exit-code-from client\"\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n**/*.rs.bk\n**/*.swp\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nWe welcome your contributions. Note that your contributions must be licensed under the BSD-style license found in LICENSE.\n\nTo make our lives as well as yours easier please indicate when a PR is a work in progress vs. ready for review.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname        = \"cfnts\"\nversion     = \"2019.6.0\"\nauthors = [\n    \"Watson Ladd <watson@cloudflare.com>\",\n    \"Gabbi Fisher <gabbi@cloudflare.com>\",\n    \"Tanya Verma <tverma@cloudflare.com>\",\n    \"Suphanat Chunhapanya <pop@cloudflare.com>\",\n]\nedition     = \"2018\"\n\n[dependencies]\n\nbyteorder   = \"1.3.2\"\n\n# Used for command-line parsing and validation.\nclap        = \"2.33.0\"\n\nconfig      = \"0.9.3\"\ncrossbeam   = \"0.7.3\"\nlazy_static = \"1.4.0\"\nlibc        = \"0.2.65\"\nlog         = \"0.4.8\"\nmemcache    = \"0.13.1\"\nmio         = \"0.6.19\"\nmiscreant   = \"0.4.2\"\nsocket2     = \"0.4.7\"\nnix         = \"0.13.0\"\nprometheus  = \"0.7.0\"\nrand        = \"0.7.2\"\nring        = \"0.16.9\"\nrustls      = \"0.16.0\"\nsimple_logger = \"1.3.0\"\n\n# More advanced logging system than `log`.\nslog = { version = \"2.5.2\", features = [\n    \"max_level_trace\",\n    \"release_max_level_debug\",\n]} # We configure at runtime\n\n# Add scopes to the logging system.\nslog-scope  = \"4.3.0\"\n\n# Used for fowarding all the `log` crate logging to `slog_scope::logger()`.\nslog-stdlog = \"~4.0.0\"\n\n# A wrapper of `slog` to make logging more convenient. If you want to increase a version here,\n# please make sure that `TerminalLoggerBuilder::build` doesn't return an error.\nsloggers    = \"=0.3.4\"\n\nwebpki      = \"0.21.0\"\nwebpki-roots = \"0.18.0\"\n"
  },
  {
    "path": "Dockerfile.cfnts",
    "content": "FROM rust:1.69.0-bookworm as builder\n\nCOPY src    src\nCOPY .cargo .cargo\nCOPY Cargo.toml Cargo.lock ./\n\nRUN cargo build --release\n\nFROM debian:bookworm\n\nCOPY --from=builder ./target/release/cfnts ./target/release/cfnts\n"
  },
  {
    "path": "Dockerfile.memcache",
    "content": "FROM debian:bookworm\n\nRUN apt-get update && \\\n    apt-get -y install memcached python3-memcache\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2019, Cloudflare. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the\n   distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n"
  },
  {
    "path": "Makefile",
    "content": "SHELL=/bin/bash\n\nTARGET_ARCHS ?= x86_64-unknown-linux-gnu\n\nrelease:\n\t@git diff --quiet || { echo \"Run in a clean repo\"; exit 1; }\n\tcargo bump $(shell cfsetup release next-tag)\n\tcargo update\n\tgit add Cargo.toml Cargo.lock\n\tgit commit -m \"Bump version in Cargo.toml to release tag\"\n\tcfsetup release update\n\ncf-package:\n\tfor TARGET_ARCH in $(TARGET_ARCHS); do \\\n\t\techo $$TARGET_ARCH && \\\n\t\tcargo deb --target $$TARGET_ARCH && \\\n\t\tmv target/$$TARGET_ARCH/debian/*.deb ./ || \\\n\t\texit 1; \\\n\tdone\n\n"
  },
  {
    "path": "README.md",
    "content": "# cfnts\n\n## DEPRECATION NOTICE\n**This software is no longer maintained. Consider using an alternative NTS implementation such as [chrony](https://chrony-project.org) or [ntpd-rs](https://github.com/pendulum-project/ntpd-rs).**\n\ncfnts is an implementation of the NTS protocol written in Rust.\n\n**Prereqs**:\nRust\n\n**Building**:\n\nWe use cargo to build the software. `docker-compose up` will spawn several Docker containers that run tests.\n\n**Running**\nRun the NTS client using `./target/release/cfnts client [--4 | --6] [-p <server-port>] [-c <trusted-cert>] [-n <other name>]  <server-hostname>`\n\nDefault port is `4460`. \n\nUsing `-4` forces the use of ipv4 for all connections to the server, and using `-6` forces the use of ipv6. \nThese two arguments are mutually exclusive. If neither of them is used, then the client will use whichever one\nis supported by the server (preference for ipv6 if supported).\n\nTo run a server you will need a memcached compatible server, together with a script based on fill-memcached.py that will write\na new random key into /nts/nts-keys/ every hour and delete old ones. Then you can run the ntp server and the nts server.\n\nThis split and use of memcached exists to enable deployments where a small dedicated device serves NTP, while a bigger server carries\nout the key exchange.\n\n**Examples**:\n\n1. `./target/release/cfnts client time.cloudflare.com`\n2. `./target/release/cfnts client kong.rellim.com -p 123`\n"
  },
  {
    "path": "RELEASE_NOTES",
    "content": "2019.6.0\n- 2019-06-04 CRYPTO-1040: Conform with HTTP 1.1 in metrics\n- 2019-06-04 Bump version in Cargo.toml to release tag\n\n2019.5.2\n- 2019-05-31 CRYPTO-1016: Test with chain, avoid races with logger\n- 2019-05-31 Bump version in Cargo.toml to release tag\n\n2019.5.1\n- 2019-05-29 CRYPTO-1007: Do not respond to non client mode packets\n- 2019-05-29 CRYPTO-1006: Expose version in metrics\n- 2019-05-30 Fix makefile to make release\n- 2019-05-30 Bump version in Cargo.toml to release tag\n\n2019.5.0\n- 2019-02-26 Initial Commit\n- 2019-02-26 Functioning Server\n- 2019-04-08 Vendored deps\n- 2019-04-08 Change config to use the vendored sources\n- 2019-04-09 Starting point from NTS Hackathon\n- 2019-04-09 Tell client what port to use\n- 2019-04-09 Ensure the port is in our test config\n- 2019-04-10 Change over to mio and import prometheus for metrics\n- 2019-04-10 Silence warnings\n- 2019-04-18 Undo type magic required by tokio\n- 2019-04-18 Add logging and more error messages/codes. Also handle blocking.\n- 2019-04-18 Include vendor changes\n- 2019-04-12 Key rotation and cfsetup compose execution\n- 2019-04-19 UDP portion\n- 2019-04-23 Serve metrics\n- 2019-04-24 Switch to slog for logging\n- 2019-05-03 Various improvements\n- 2019-05-06 CRYPTO-924: Smaller cookies\n- 2019-05-09 CRYPTO-940: Change log level at runtime\n- 2019-05-09 CRYPTO-922 Build debian packages\n- 2019-05-08 Support specification of multiple listening addressess\n- 2019-04-29 Implement connection to upstream process\n- 2019-05-10 Use kernel timestamping for more accurate timing\n- 2019-05-06 CRYPTO-891: Timeouts for nts_ke connections\n- 2019-05-28 Use correct nonce length according to RFC 5116\n- 2019-05-22 CRYPTO-957/CRYPTO-979: set socket options on our listening sockets\n- 2019-05-24 CRYPTO-986 Use TLS 1.3 only for NTS\n- 2019-05-23 CRYPTO-960 Better handling of configuration errors\n\n"
  },
  {
    "path": "docker-compose.yaml",
    "content": "version: \"3.8\"\nservices:\n  server:\n    build:\n      context: .\n      dockerfile: Dockerfile.cfnts\n    depends_on:\n      - memcache\n    volumes:\n      - ./tests:/tests\n      - ./scripts:/scripts\n    entrypoint: [\"/scripts/run_server.sh\"]\n  client:\n    build:\n      context: .\n      dockerfile: Dockerfile.cfnts\n    depends_on:\n      - server\n    volumes:\n      - ./tests:/tests\n      - ./scripts:/scripts\n    entrypoint: [\"/scripts/run_client.sh\"]\n  memcache:\n    build:\n      context: .\n      dockerfile: Dockerfile.memcache\n    volumes:\n      - ./scripts:/scripts\n    entrypoint: [\"/scripts/run_memcached.sh\"]\n"
  },
  {
    "path": "scripts/fill-memcached.py",
    "content": "import memcache\nimport time\nimport math\n\nprint(\"filling memcache\")\nservers = [\"localhost:11211\"]\nmc = memcache.Client(servers)\nrand = open(\"/dev/urandom\", \"rb\")\n\ninterval = 3600\nnow = int(math.floor(time.time()))\nfor i in range(-50, 4):\n    epoch = int((math.floor(now/interval)+i)*interval)\n    key = \"/nts/nts-keys/%s\"%epoch\n    mc.set(key, rand.read(16))\n"
  },
  {
    "path": "scripts/run_client.sh",
    "content": "#!/bin/bash\n\n# Retry for 10 times.\nfor i in $(seq 1 10); do\n    if ./target/release/cfnts client server -c tests/ca.pem; then\n        exit 0\n    else\n        echo \"The server is unavailable - sleeping\"\n        sleep 1\n    fi\ndone\n\nexit 1\n"
  },
  {
    "path": "scripts/run_memcached.sh",
    "content": "#!/bin/bash\necho \"Running memcache\"\ndate \"+%s\"\nmemcached -u root &\nsleep 2\npython3 scripts/fill-memcached.py\necho \"done\"\nwait $!\n"
  },
  {
    "path": "scripts/run_server.sh",
    "content": "#!/bin/bash\nsleep 5\ndate \"+%s\"\n\nRUST_BACKTRACE=1 ./target/release/cfnts ke-server -f tests/nts-ke-config.yaml &\nRUST_BACKTRACE=1 ./target/release/cfnts ntp-server -f tests/ntp-upstream-config.yaml &\nRUST_BACKTRACE=1 ./target/release/cfnts ntp-server -f tests/ntp-config.yaml\n"
  },
  {
    "path": "src/cfsock.rs",
    "content": "use libc::*;\nuse socket2::{Domain, Socket, Type};\nuse std::net::SocketAddr;\nuse std::os::unix::io::AsRawFd;\n\n#[cfg(target_os = \"linux\")]\nfn set_freebind(fd: c_int) -> Result<(), std::io::Error> {\n    use std::io::{Error, ErrorKind};\n    const IP_FREEBIND: libc::c_int = 0xf;\n    match unsafe {\n        setsockopt(\n            fd,\n            SOL_IP,\n            IP_FREEBIND,\n            &1u32 as *const u32 as *const c_void,\n            std::mem::size_of::<u32>() as u32,\n        )\n    } {\n        -1 => Err(std::io::Error::new(\n            ErrorKind::Other,\n            Error::last_os_error(),\n        )),\n        _ => Ok(()),\n    }\n}\n\n#[cfg(not(target_os = \"linux\"))]\nfn set_freebind(_fd: c_int) -> Result<(), std::io::Error> {\n    Ok(()) // no op for mac build\n}\n\npub fn tcp_listener(addr: &SocketAddr) -> Result<std::net::TcpListener, std::io::Error> {\n    let domain = match addr {\n        SocketAddr::V4(..) => Domain::IPV4,\n        SocketAddr::V6(..) => Domain::IPV6,\n    };\n    let socket = Socket::new(domain, Type::STREAM, None)?;\n    socket.set_reuse_address(true)?;\n    set_freebind(socket.as_raw_fd())?;\n    socket.bind(&(*addr).into())?;\n    socket.listen(128)?;\n    Ok(socket.into())\n}\n\npub fn udp_listen(addr: &SocketAddr) -> Result<std::net::UdpSocket, std::io::Error> {\n    let domain = match addr {\n        SocketAddr::V4(..) => Domain::IPV4,\n        SocketAddr::V6(..) => Domain::IPV6,\n    };\n    let socket = Socket::new(domain, Type::DGRAM, None)?;\n    socket.set_reuse_address(true)?;\n    set_freebind(socket.as_raw_fd())?;\n    socket.bind(&(*addr).into())?;\n    Ok(socket.into())\n}\n"
  },
  {
    "path": "src/cmd.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! Command line argument definitions and validations.\n\nuse clap::{App, Arg, SubCommand};\n\n/// Create the subcommand `client`.\nfn create_clap_client_subcommand<'a, 'b>() -> App<'a, 'b> {\n    // Arguments for `client` subcommand.\n    let args = [\n        // The hostname is always required and will immediately\n        // follow the subcommand string.\n        Arg::with_name(\"host\")\n            .index(1)\n            .required(true)\n            .help(\"NTS server's hostname (do not include port)\"),\n        // The rest will be passed as unrequired command-line options.\n        Arg::with_name(\"port\")\n            .long(\"port\")\n            .short(\"p\")\n            .takes_value(true)\n            .required(false)\n            .help(\"Specifies NTS server's port. The default port number is 4460.\"),\n        Arg::with_name(\"cert\")\n            .long(\"cert\")\n            .short(\"c\")\n            .takes_value(true)\n            .required(false)\n            .help(\"Specifies a path to the trusted certificate in PEM format.\"),\n        Arg::with_name(\"ipv4\")\n            .long(\"ipv4\")\n            .short(\"4\")\n            .conflicts_with(\"ipv6\")\n            .help(\"Forces use of IPv4 only\"),\n        Arg::with_name(\"ipv6\")\n            .long(\"ipv6\")\n            .short(\"6\")\n            .conflicts_with(\"ipv4\")\n            .help(\"Forces use of IPv6 only\"),\n    ];\n\n    // Create a new subcommand.\n    SubCommand::with_name(\"client\")\n        .about(\"Initiates an NTS connection with the remote server\")\n        .args(&args)\n}\n\n/// Create the subcommand `ke-server`.\nfn create_clap_ke_server_subcommand<'a, 'b>() -> App<'a, 'b> {\n    // Arguments for `ke-server` subcommand.\n    let args = [Arg::with_name(\"configfile\")\n        .long(\"file\")\n        .short(\"f\")\n        .takes_value(true)\n        .required(false)\n        .help(\n            \"Specifies a path to the configuration file. If the path is not specified, \\\n                   the system-wide configuration file (/etc/cfnts/ke-server.config) will be \\\n                   used instead\",\n        )];\n\n    // Create a new subcommand.\n    SubCommand::with_name(\"ke-server\")\n        .about(\"Runs NTS-KE server over TLS/TCP\")\n        .args(&args)\n}\n\n/// Create the subcommand `ntp-server`.\nfn create_clap_ntp_server_subcommand<'a, 'b>() -> App<'a, 'b> {\n    // Arguments for `ntp-server` subcommand.\n    let args = [Arg::with_name(\"configfile\")\n        .long(\"file\")\n        .short(\"f\")\n        .takes_value(true)\n        .required(false)\n        .help(\n            \"Specifies a path to the configuration file. If the path is not specified, \\\n                   the system-wide configuration file (/etc/cfnts/ntp-server.config) will be \\\n                   used instead\",\n        )];\n\n    // Create a new subcommand.\n    SubCommand::with_name(\"ntp-server\")\n        .about(\"Interfaces with NTP using UDP\")\n        .args(&args)\n}\n\n/// Create the whole command-line configuration.\npub fn create_clap_command() -> App<'static, 'static> {\n    App::new(env!(\"CARGO_PKG_NAME\"))\n        .about(env!(\"CARGO_PKG_DESCRIPTION\"))\n        .version(env!(\"CARGO_PKG_VERSION\"))\n        .arg(\n            Arg::with_name(\"debug\")\n                .long(\"debug\")\n                .short(\"d\")\n                .help(\"Turns on debug logging\"),\n        )\n        .subcommands(vec![\n            // List of all available subcommands.\n            create_clap_client_subcommand(),\n            create_clap_ke_server_subcommand(),\n            create_clap_ntp_server_subcommand(),\n        ])\n}\n"
  },
  {
    "path": "src/cookie.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\nuse miscreant::aead;\nuse miscreant::aead::Aead;\nuse rand::Rng;\n\nuse std::convert::TryInto;\nuse std::fs::File;\nuse std::io;\nuse std::io::Read;\n\nuse crate::key_rotator::KeyId;\n\npub const COOKIE_SIZE: usize = 100;\n#[derive(Debug, Copy, Clone)]\npub struct NTSKeys {\n    pub c2s: [u8; 32],\n    pub s2c: [u8; 32],\n}\n\n/// Cookie key.\n#[derive(Clone, Debug)]\npub struct CookieKey(Vec<u8>);\n\nimpl CookieKey {\n    /// Parse a cookie key from a file.\n    ///\n    /// # Errors\n    ///\n    /// There will be an error, if we cannot open the file.\n    ///\n    pub fn parse(filename: &str) -> Result<CookieKey, io::Error> {\n        let mut file = File::open(filename)?;\n        let mut buffer = Vec::new();\n\n        file.read_to_end(&mut buffer)?;\n        Ok(CookieKey(buffer))\n    }\n\n    /// Return a byte slice of a cookie key content.\n    pub fn as_bytes(&self) -> &[u8] {\n        self.0.as_slice()\n    }\n}\n\n// Only used in test.\n#[cfg(test)]\nimpl From<&[u8]> for CookieKey {\n    fn from(bytes: &[u8]) -> CookieKey {\n        CookieKey(Vec::from(bytes))\n    }\n}\n\npub fn make_cookie(keys: NTSKeys, master_key: &[u8], key_id: KeyId) -> Vec<u8> {\n    let mut nonce = [0; 16];\n    rand::thread_rng().fill(&mut nonce);\n    let mut plaintext = [0; 64];\n    plaintext[..32].copy_from_slice(&keys.c2s[..32]);\n    plaintext[32..64].copy_from_slice(&keys.s2c[..32]);\n    let mut aead = aead::Aes128SivAead::new(master_key);\n    let mut ciphertext = aead.seal(&nonce, &[], &plaintext);\n    let mut out = Vec::new();\n    out.extend(&key_id.to_be_bytes());\n    out.extend(&nonce);\n    out.append(&mut ciphertext);\n    out\n}\n\npub fn get_keyid(cookie: &[u8]) -> Option<KeyId> {\n    if cookie.len() < 4 {\n        None\n    } else {\n        Some(KeyId::from_be_bytes((&cookie[0..4]).try_into().unwrap()))\n    }\n}\n\nfn unpack(pt: Vec<u8>) -> Option<NTSKeys> {\n    if pt.len() != 64 {\n        None\n    } else {\n        let mut key = NTSKeys {\n            c2s: [0; 32],\n            s2c: [0; 32],\n        };\n        key.c2s[..32].copy_from_slice(&pt[..32]);\n        key.s2c[..32].copy_from_slice(&pt[32..64]);\n        Some(key)\n    }\n}\n\npub fn eat_cookie(cookie: &[u8], key: &[u8]) -> Option<NTSKeys> {\n    if cookie.len() < 40 {\n        return None;\n    }\n    let ciphertext = &cookie[4..];\n    let mut aead = aead::Aes128SivAead::new(key);\n    let answer = aead.open(&ciphertext[0..16], &[], &ciphertext[16..]);\n    match answer {\n        Err(_) => None,\n        Ok(buf) => unpack(buf),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn check_eq(a: NTSKeys, b: NTSKeys) {\n        for i in 0..32 {\n            assert_eq!(a.c2s[i], b.c2s[i]);\n            assert_eq!(a.s2c[i], b.s2c[i]);\n        }\n    }\n\n    #[test]\n    fn check_cookie() {\n        let test = NTSKeys {\n            s2c: [9; 32],\n            c2s: [10; 32],\n        };\n\n        let master_key = [0x07; 32];\n        let key_id = KeyId::from_be_bytes([0x03; 4]);\n        let mut cookie = make_cookie(test, &master_key, key_id);\n        assert_eq!(cookie.len(), COOKIE_SIZE);\n        assert_eq!(get_keyid(&cookie).unwrap(), key_id);\n        check_eq(eat_cookie(&cookie, &master_key).unwrap(), test);\n\n        cookie[9] = 0xff;\n        cookie[10] = 0xff;\n        assert!(eat_cookie(&cookie, &master_key).is_none());\n    }\n}\n"
  },
  {
    "path": "src/error.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! Traits for working with errors.\n\nuse std::error::Error;\n\n/// `WrapError` allows the implementor to wrap its own error type in another error type.\npub trait WrapError<T: Error> {\n    /// The returned type in case that the result has no error.\n    type Item;\n\n    /// Wrapping an error in the error type `T`.\n    fn wrap_err(self) -> Result<Self::Item, T>;\n}\n\n/// Trait implementation for `config::ConfigError`.\n// The reason that we have a lifetime bound 'static is that we want T to either contain no lifetime\n// parameter or contain only the 'static lifetime parameter.\nimpl<S, T> WrapError<config::ConfigError> for Result<S, T>\nwhere\n    T: 'static + Error + Send + Sync,\n{\n    /// Don't change the returned type, in case there is no error.\n    type Item = S;\n\n    fn wrap_err(self) -> Result<S, config::ConfigError> {\n        self.map_err(|error| config::ConfigError::Foreign(Box::new(error)))\n    }\n}\n\n/// Trait implementation for `std::io::Error`.\n// The reason that we have a lifetime bound 'static is that we want T to either contain no lifetime\n// parameter or contain only the 'static lifetime parameter.\nimpl<S, T> WrapError<std::io::Error> for Result<S, T>\nwhere\n    T: 'static + Error + Send + Sync,\n{\n    /// Don't change the returned type, in case there is no error.\n    type Item = S;\n\n    fn wrap_err(self) -> Result<S, std::io::Error> {\n        self.map_err(|error| std::io::Error::new(std::io::ErrorKind::Other, error))\n    }\n}\n"
  },
  {
    "path": "src/key_rotator.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! Key rotator implementation, which provides key synchronization with Memcached server.\n\nuse lazy_static::lazy_static;\n\n#[cfg(not(test))]\nuse memcache::MemcacheError;\n\nuse prometheus::{opts, register_counter, register_int_counter, IntCounter};\n\nuse ring::hmac;\n\nuse std::collections::HashMap;\nuse std::sync::{Arc, RwLock};\nuse std::thread;\n#[cfg(not(test))]\nuse std::time::SystemTime;\nuse std::time::{Duration, UNIX_EPOCH};\n\nuse crate::cookie::CookieKey;\n\nlazy_static! {\n    static ref ROTATION_COUNTER: IntCounter =\n        register_int_counter!(\"ntp_key_rotations_total\", \"Number of key rotations\").unwrap();\n    static ref FAILURE_COUNTER: IntCounter = register_int_counter!(\n        \"ntp_key_rotations_failed_total\",\n        \"Number of failures in key rotation\"\n    )\n    .unwrap();\n}\n\n/// Key id for `KeyRotator`.\n// This struct should be `Clone` and `Copy` because the internal representation is just a `u32`.\n#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]\npub struct KeyId(u32);\n\nimpl KeyId {\n    /// Create `KeyId` from raw `u32`.\n    pub fn new(key_id: u32) -> KeyId {\n        KeyId(key_id)\n    }\n\n    /// Create `KeyId` from a `u64` epoch. The 32 most significant bits of the parameter will be\n    /// discarded.\n    pub fn from_epoch(epoch: u64) -> KeyId {\n        // This will discard the 32 most significant bits.\n        let epoch_residue = epoch as u32;\n        KeyId(epoch_residue)\n    }\n\n    /// Create `KeyId` from its representation as a byte array in big endian.\n    pub fn from_be_bytes(bytes: [u8; 4]) -> KeyId {\n        KeyId(u32::from_be_bytes(bytes))\n    }\n\n    /// Return the memory representation of this `KeyId` as a byte array in big endian.\n    pub fn to_be_bytes(self) -> [u8; 4] {\n        self.0.to_be_bytes()\n    }\n}\n\n/// Error struct returned from `KeyRotator::rotate` method.\n#[derive(Debug)]\npub enum RotateError {\n    /// Error from Memcached server.\n    MemcacheError(MemcacheError),\n    /// Error when the Memcached server doesn't have a specified `KeyId`.\n    KeyIdNotFound(KeyId),\n}\n\nimpl From<MemcacheError> for RotateError {\n    /// Wrap MemcacheError.\n    fn from(error: MemcacheError) -> RotateError {\n        RotateError::MemcacheError(error)\n    }\n}\n\n/// Key rotator.\npub struct KeyRotator {\n    /// URL of the Memcached server.\n    memcached_url: String,\n\n    /// Prefix for the Memcached key.\n    prefix: String,\n\n    // This property type needs to fit an Epoch time in seconds.\n    /// Length of each period in seconds.\n    duration: u64,\n\n    // The number of forward and backward periods are `u64` because the timestamp is `u64` and the\n    // duration can be as small as 1.\n    /// The number of future periods that the rotator must cache their values from the\n    /// Memcached server.\n    number_of_forward_periods: u64,\n\n    /// The number of previous periods that the rotator must cache their values from the\n    /// Memcached server.\n    number_of_backward_periods: u64,\n\n    /// Cookie key that will be used as a MAC key of the rotator.\n    master_key: CookieKey,\n\n    /// Key id of the current period.\n    latest_key_id: KeyId,\n\n    /// Cache store.\n    cache: HashMap<KeyId, hmac::Tag>,\n\n    /// Logger.\n    // TODO: since we don't use the logger now, I will put an `allow(dead_code)` here first. I will\n    // remove it when it's used.\n    #[allow(dead_code)]\n    logger: slog::Logger,\n}\n\nimpl KeyRotator {\n    /// Connect to the Memcached server and sync some inital keys.\n    pub fn connect(\n        prefix: String,\n        memcached_url: String,\n        master_key: CookieKey,\n        logger: slog::Logger,\n    ) -> Result<KeyRotator, RotateError> {\n        let mut rotator = KeyRotator {\n            // Zero shouldn't be a valid KeyId. This is just a temporary value.\n            latest_key_id: KeyId::new(0),\n            // The cache should never be empty. This is just a temporary value.\n            cache: HashMap::new(),\n\n            // It seems that currently we don't have to customize the following three properties,\n            // so I will just put default values.\n            duration: 3600,\n            number_of_forward_periods: 2,\n            number_of_backward_periods: 24,\n\n            // From parameters.\n            prefix,\n            memcached_url,\n            master_key,\n            logger,\n        };\n\n        // Maximum number of times that we want to try rotating the keys.\n        let maximum_try = 5;\n\n        // Try to rotate the keys up to 5 times to make sure that the rotator has some keys in it.\n        // If it doesn't, we will not have any key to use.\n        for try_number in 1.. {\n            match rotator.rotate() {\n                Err(error) => {\n                    // Side-effect. Logging.\n                    // Disable the log for now because the Error trait is not implemented for\n                    // RotateError yet.\n                    // error!(rotator.logger, \"failure to initialize key rotation: {}\", error);\n\n                    // If it already tried a lot of times already, it may be a time to give up.\n                    if try_number == maximum_try {\n                        return Err(error);\n                    }\n\n                    // Wait for 5 seconds before retrying key rotation.\n                    std::thread::sleep(std::time::Duration::from_secs(5));\n                }\n                // If it's a success, stop retrying.\n                Ok(()) => break,\n            }\n        }\n\n        Ok(rotator)\n    }\n\n    /// Rotate keys.\n    ///\n    /// # Panics\n    ///\n    /// If the system time is before the UNIX Epoch time.\n    ///\n    /// # Errors\n    ///\n    /// There is an error, if there is a connection problem with Memcached server or the Memcached\n    /// server doesn't contain a key id it supposed to contain.\n    ///\n    pub fn rotate(&mut self) -> Result<(), RotateError> {\n        // Side-effect. It's not related to the operation.\n        ROTATION_COUNTER.inc();\n\n        let duration = SystemTime::now()\n            .duration_since(UNIX_EPOCH)\n            .expect(\"The system time must be after the UNIX Epoch time.\");\n\n        // The number of seconds since the Epoch time.\n        let timestamp = duration.as_secs();\n\n        // The current period number of the timestamp.\n        let current_period = timestamp / self.duration;\n        // The timestamp at the beginning of the current period.\n        let current_epoch = current_period * self.duration;\n\n        // The first period number that we want to iterate through.\n        let first_period = current_period.saturating_sub(self.number_of_backward_periods);\n\n        // The last period number that we want to iterate through.\n        let last_period = current_period.saturating_add(self.number_of_forward_periods);\n\n        let removed_period = first_period.saturating_sub(1);\n        let removed_epoch = removed_period * self.duration;\n        self.cache_remove(KeyId::from_epoch(removed_epoch));\n\n        // Connecting to memcached. I have to add [..] because it seems that Rust is not smart\n        // enough to do auto-dereference.\n        let mut client = memcache::Client::connect(&self.memcached_url[..])?;\n\n        for period_number in first_period..=last_period {\n            // The timestamp at the beginning of the period.\n            let epoch = period_number * self.duration;\n\n            let memcached_key = format!(\"{}/{}\", self.prefix, epoch);\n            let memcached_value: Option<Vec<u8>> = client.get(&memcached_key)?;\n\n            let key_id = KeyId::from_epoch(epoch);\n            match memcached_value {\n                Some(value) => self.cache_insert(key_id, value.as_slice()),\n                None => {\n                    FAILURE_COUNTER.inc();\n                    return Err(RotateError::KeyIdNotFound(key_id));\n                }\n            }\n        }\n\n        // Not all of our friends may have gotten the same forwards keys as we did.\n        self.latest_key_id = KeyId::from_epoch(current_epoch);\n\n        Ok(())\n    }\n\n    /// Add an entry to the cache.\n    // It should be private. Don't make it public.\n    fn cache_insert(&mut self, key_id: KeyId, value: &[u8]) {\n        // Create a MAC key.\n        let mac_key = hmac::Key::new(hmac::HMAC_SHA256, self.master_key.as_bytes());\n        // Generating a MAC tag with a MAC key.\n        let tag = hmac::sign(&mac_key, value);\n\n        self.cache.insert(key_id, tag);\n    }\n\n    /// Remove an entry from the cache.\n    // It should be private. Don't make it public.\n    fn cache_remove(&mut self, key_id: KeyId) {\n        self.cache.remove(&key_id);\n    }\n\n    /// Return the latest key id and hmac tag of the rotator.\n    pub fn latest_key_value(&self) -> (KeyId, &hmac::Tag) {\n        // This unwrap cannot panic because the HashMap will always contain the latest key id.\n        (self.latest_key_id, self.get(self.latest_key_id).unwrap())\n    }\n\n    /// Return an entry in the cache using a key id.\n    pub fn get(&self, key_id: KeyId) -> Option<&hmac::Tag> {\n        self.cache.get(&key_id)\n    }\n}\n\npub fn periodic_rotate(rotor: Arc<RwLock<KeyRotator>>) {\n    let mut rotor = rotor;\n    thread::spawn(move || loop {\n        inner(&mut rotor);\n        let restlen = read_sleep(&rotor);\n        thread::sleep(Duration::from_secs(restlen));\n    });\n}\n\nfn inner(rotor: &mut Arc<RwLock<KeyRotator>>) {\n    let _ = rotor.write().unwrap().rotate();\n}\n\nfn read_sleep(rotor: &Arc<RwLock<KeyRotator>>) -> u64 {\n    rotor.read().unwrap().duration\n}\n\n// ------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------\n\n#[cfg(test)]\nuse ::memcache::MemcacheError;\n#[cfg(test)]\nuse test::memcache;\n#[cfg(test)]\nuse test::SystemTime;\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    use ::memcache::MemcacheError;\n    use lazy_static::lazy_static;\n    use sloggers::null::NullLoggerBuilder;\n    use sloggers::Build;\n    use std::sync::Mutex;\n    use std::time::Duration;\n\n    // Mocking memcache.\n    pub mod memcache {\n        use super::*;\n        use std::collections::HashMap;\n\n        lazy_static! {\n            pub static ref HASH_MAP: Mutex<HashMap<String, Vec<u8>>> = Mutex::new(HashMap::new());\n        }\n        pub struct Client;\n        impl Client {\n            pub fn connect(_url: &str) -> Result<Client, MemcacheError> {\n                Ok(Client)\n            }\n            pub fn get(&mut self, key: &str) -> Result<Option<Vec<u8>>, MemcacheError> {\n                Ok(HASH_MAP.lock().unwrap().get(&String::from(key)).cloned())\n            }\n        }\n    }\n\n    // Mocking SystemTime.\n    lazy_static! {\n        pub static ref NOW: Mutex<u64> = Mutex::new(0);\n    }\n    pub struct SystemTime;\n    impl SystemTime {\n        pub fn now() -> std::time::SystemTime {\n            let now = NOW.lock().unwrap();\n            let duration = Duration::new(*now, 0);\n            UNIX_EPOCH.checked_add(duration).unwrap()\n        }\n    }\n\n    #[test]\n    fn test_rotation() {\n        use self::memcache::HASH_MAP;\n\n        let mut hash_map = HASH_MAP.lock().unwrap();\n        hash_map.insert(\"test/1\".to_string(), vec![1; 32]);\n        hash_map.insert(\"test/2\".to_string(), vec![2; 32]);\n        hash_map.insert(\"test/3\".to_string(), vec![3; 32]);\n        hash_map.insert(\"test/4\".to_string(), vec![4; 32]);\n        drop(hash_map);\n\n        let mut rotator = KeyRotator {\n            memcached_url: String::from(\"unused\"),\n            prefix: String::from(\"test\"),\n            duration: 1,\n            number_of_forward_periods: 1,\n            number_of_backward_periods: 1,\n            master_key: CookieKey::from(&[0, 32][..]),\n            latest_key_id: KeyId::from_be_bytes([1, 2, 3, 4]),\n            cache: HashMap::new(),\n            logger: NullLoggerBuilder.build().unwrap(),\n        };\n\n        *NOW.lock().unwrap() = 2;\n        // No error because the hash map has \"test/1\", \"test/2\", and \"test/3\".\n        rotator.rotate().unwrap();\n        let old_latest = rotator.latest_key_id;\n\n        *NOW.lock().unwrap() = 3;\n        // No error because the hash map has \"test/2\", \"test/3\", and \"test/4\".\n        rotator.rotate().unwrap();\n        let new_latest = rotator.latest_key_id;\n\n        // The key id should change.\n        assert_ne!(old_latest, new_latest);\n\n        *NOW.lock().unwrap() = 1;\n        // Return error because the hash map doesn't have \"test/0\".\n        rotator.rotate().unwrap_err();\n\n        *NOW.lock().unwrap() = 4;\n        // Return error because the hash map doesn't have \"test/5\".\n        rotator.rotate().unwrap_err();\n    }\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\nextern crate lazy_static;\nextern crate log;\nextern crate prometheus;\nextern crate slog;\nextern crate slog_scope;\nextern crate slog_stdlog;\nextern crate sloggers;\n\nmod cfsock;\nmod cmd;\nmod cookie;\nmod error;\nmod key_rotator;\nmod metrics;\nmod ntp;\nmod nts_ke;\nmod sub_command;\n\nuse sloggers::terminal::{Destination, TerminalLoggerBuilder};\nuse sloggers::types::Severity;\nuse sloggers::Build;\n\nuse std::process;\n\n/// Create a logger to be used throughout cfnts.\nfn create_logger(matches: &clap::ArgMatches<'_>) -> slog::Logger {\n    let mut builder = TerminalLoggerBuilder::new();\n\n    // Default severity level is info.\n    builder.level(Severity::Info);\n    // Write all logs to stderr.\n    builder.destination(Destination::Stderr);\n\n    // If in debug mode, change severity level to debug.\n    if matches.is_present(\"debug\") {\n        builder.level(Severity::Debug);\n    }\n\n    // According to `sloggers-0.3.2` source code, the function doesn't return an error at all.\n    // There should be no problem unwrapping here. It has a return type `Result` because it's a\n    // signature for `sloggers::Build` trait.\n    builder\n        .build()\n        .expect(\"BUG: TerminalLoggerBuilder::build shouldn't return an error.\")\n}\n\n/// The entry point of cfnts.\nfn main() {\n    // According to the documentation of `get_matches`, if the parsing fails, an error will be\n    // displayed to the user and the process will exit with an error code.\n    let matches = cmd::create_clap_command().get_matches();\n\n    let logger = create_logger(&matches);\n\n    // After calling this, slog_stdlog will forward all the `log` crate logging to\n    // `slog_scope::logger()`.\n    //\n    // The returned error type is `SetLoggerError` which, according to the lib doc, will be\n    // returned only when `set_logger` has been called already which should be our bug if it\n    // has already been called.\n    //\n    slog_stdlog::init().expect(\"BUG: `set_logger` has already been called\");\n\n    // _scope_guard can be used to reset the global logger. You can do it by just dropping it.\n    let _scope_guard = slog_scope::set_global_logger(logger.clone());\n\n    if matches.subcommand.is_none() {\n        eprintln!(\n            \"please specify a valid subcommand: only client, ke-server, and ntp-server \\\n                   are supported.\"\n        );\n        process::exit(1);\n    }\n\n    if let Some(ke_server_matches) = matches.subcommand_matches(\"ke-server\") {\n        sub_command::ke_server::run(ke_server_matches);\n    }\n    if let Some(ntp_server_matches) = matches.subcommand_matches(\"ntp-server\") {\n        sub_command::ntp_server::run(ntp_server_matches);\n    }\n    if let Some(client_matches) = matches.subcommand_matches(\"client\") {\n        sub_command::client::run(client_matches);\n    }\n}\n"
  },
  {
    "path": "src/metrics.rs",
    "content": "// Our goal is to shove data at prometheus in response to requests.\nuse lazy_static::lazy_static;\nuse prometheus::{self, register_int_gauge, Encoder, __register_gauge, labels, opts};\nuse std::io;\nuse std::io::{BufRead, BufReader, Write};\nuse std::net;\nuse std::thread;\n\nuse slog::error;\n\n#[derive(Clone, Debug)]\npub struct MetricsConfig {\n    pub port: u16,\n    pub addr: String,\n}\n\nconst VERSION: &str = env!(\"CARGO_PKG_VERSION\");\n\nlazy_static! {\n    static ref VERSION_INFO: prometheus::IntGauge = register_int_gauge!(opts!(\n        \"build_info\",\n        \"Build and version information\",\n        labels! {\n            \"version\" => VERSION,\n        }\n    ))\n    .unwrap();\n}\n\nfn wait_for_req_or_eof(dest: &net::TcpStream, logger: slog::Logger) -> Result<(), io::Error> {\n    let mut reader = BufReader::new(dest);\n    let mut req_line = String::new();\n    let mut done = false;\n    while !done {\n        req_line.clear();\n        let res = reader.read_line(&mut req_line);\n        if let Err(e) = res {\n            error!(logger, \"failure to read request {:?}\", e);\n            return Err(e);\n        }\n        if let Ok(0) = res {\n            // We got EOF ahead of request coming in\n            // but will try to answer anyway\n            done = true;\n        }\n        if req_line == \"\\r\\n\" {\n            done = true; // terminates the request\n        }\n    }\n    Ok(())\n}\n\nfn scrape_result() -> String {\n    let mut buffer = Vec::new();\n    let encoder = prometheus::TextEncoder::new();\n    let families = prometheus::gather();\n    encoder.encode(&families, &mut buffer).unwrap();\n    \"HTTP/1.1 200 OK\\r\\nContent-Type: text/plain; version=0.0.4\\r\\n\\r\\n\".to_owned()\n        + &String::from_utf8(buffer).unwrap()\n}\n\nfn serve_metrics(mut dest: net::TcpStream, logger: slog::Logger) {\n    if let Err(e) = wait_for_req_or_eof(&dest, logger.clone()) {\n        error!(\n            logger,\n            \"error in wait_for_req_or_eof: {:?}, unable to serve metrics\", e\n        );\n        if let Err(e) = dest.shutdown(net::Shutdown::Both) {\n            error!(logger, \"shutting down TcpStream failed with error: {:?}\", e);\n        }\n        return;\n    }\n    if let Err(e) = dest.write(scrape_result().as_bytes()) {\n        error!(\n            logger,\n            \"write to TcpStream failed with error: {:?}, unable to serve metrics\", e\n        );\n    }\n    if let Err(e) = dest.shutdown(net::Shutdown::Write) {\n        error!(logger, \"failure to shut down {:?}\", e);\n    }\n}\n\n/// Runs the metric server on the address and port set in config\npub fn run_metrics(conf: MetricsConfig, logger: &slog::Logger) -> Result<(), std::io::Error> {\n    VERSION_INFO.set(1);\n    let accept = net::TcpListener::bind((conf.addr.as_str(), conf.port))?;\n    for stream in accept.incoming() {\n        match stream {\n            Ok(conn) => {\n                let log_metrics = logger.new(slog::o!(\"component\"=>\"serve_metrics\"));\n                thread::spawn(move || {\n                    serve_metrics(conn, log_metrics);\n                });\n            }\n            Err(err) => return Err(err),\n        }\n    }\n    Err(io::Error::new(io::ErrorKind::Other, \"unreachable\"))\n}\n"
  },
  {
    "path": "src/ntp/client.rs",
    "content": "use crate::nts_ke::client::NtsKeResult;\n\nuse miscreant::aead::Aead;\nuse miscreant::aead::Aes128SivAead;\nuse rand::Rng;\nuse slog::debug;\nuse std::error::Error;\nuse std::fmt;\n\nuse std::net::{ToSocketAddrs, UdpSocket};\nuse std::time::{Duration, SystemTime};\n\nuse super::protocol::parse_nts_packet;\nuse super::protocol::serialize_nts_packet;\nuse super::protocol::LeapState;\nuse super::protocol::NtpExtension;\nuse super::protocol::NtpExtensionType::*;\nuse super::protocol::NtpPacketHeader;\nuse super::protocol::NtsPacket;\nuse super::protocol::PacketMode::Client;\nuse super::protocol::TWO_POW_32;\nuse super::protocol::UNIX_OFFSET;\n\nuse self::NtpClientError::*;\n\nconst BUFF_SIZE: usize = 2048;\nconst TIMEOUT: Duration = Duration::from_secs(10);\n\npub struct NtpResult {\n    pub stratum: u8,\n    pub time_diff: f64,\n}\n\n#[derive(Debug, Clone)]\npub enum NtpClientError {\n    NoIpv4AddrFound,\n    NoIpv6AddrFound,\n    InvalidUid,\n}\n\nimpl std::error::Error for NtpClientError {\n    fn description(&self) -> &str {\n        match self {\n            Self::NoIpv4AddrFound => {\n                \"Connection to server failed: IPv4 address could not be resolved\"\n            }\n            Self::NoIpv6AddrFound => {\n                \"Connection to server failed: IPv6 address could not be resolved\"\n            }\n            Self::InvalidUid => {\n                \"Connection to server failed: server response UID did not match client request UID\"\n            }\n        }\n    }\n    fn cause(&self) -> Option<&dyn std::error::Error> {\n        None\n    }\n}\n\nimpl std::fmt::Display for NtpClientError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"Ntp Client Error \")\n    }\n}\n\n/// Returns a float representing the system time as NTP\nfn system_to_ntpfloat(time: SystemTime) -> f64 {\n    let unix_time = time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); // Safe absent time machines\n    let unix_offset = Duration::new(UNIX_OFFSET, 0);\n    let epoch_time = unix_offset + unix_time;\n    epoch_time.as_secs() as f64 + (epoch_time.subsec_nanos() as f64) / 1.0e9\n}\n\n/// Returns a float representing the ntp timestamp\nfn timestamp_to_float(time: u64) -> f64 {\n    let ts_secs = time >> 32;\n    let ts_frac = time - (ts_secs << 32);\n    (ts_secs as f64) + (ts_frac as f64) / TWO_POW_32\n}\n\n/// Run the NTS client with the given data from key exchange\npub fn run_nts_ntp_client(\n    logger: &slog::Logger,\n    state: NtsKeResult,\n) -> Result<NtpResult, Box<dyn Error>> {\n    let mut ip_addrs = (state.next_server.as_str(), state.next_port).to_socket_addrs()?;\n    let addr;\n    let socket;\n    if let Some(use_ipv4) = state.use_ipv4 {\n        if use_ipv4 {\n            // mandated to use ipv4\n            addr = ip_addrs.find(|&x| x.is_ipv4());\n            if addr.is_none() {\n                return Err(Box::new(NoIpv4AddrFound));\n            }\n            socket = UdpSocket::bind(\"0.0.0.0:0\");\n        } else {\n            // mandated to use ipv6\n            addr = ip_addrs.find(|&x| x.is_ipv6());\n            if addr.is_none() {\n                return Err(Box::new(NoIpv6AddrFound));\n            }\n            socket = UdpSocket::bind(\"[::]:0\");\n        }\n    } else {\n        // sniff whichever one is supported\n        addr = ip_addrs.next();\n        // check if this address is ipv4 or ipv6\n        if addr.unwrap().is_ipv6() {\n            socket = UdpSocket::bind(\"[::]:0\");\n        } else {\n            socket = UdpSocket::bind(\"0.0.0.0:0\");\n        }\n    }\n\n    let socket = socket.unwrap();\n    socket.set_read_timeout(Some(TIMEOUT))?;\n    socket.set_write_timeout(Some(TIMEOUT))?;\n    let mut send_aead = Aes128SivAead::new(&state.keys.c2s);\n    let mut recv_aead = Aes128SivAead::new(&state.keys.s2c);\n    let header = NtpPacketHeader {\n        leap_indicator: LeapState::NoLeap,\n        version: 4,\n        mode: Client,\n        stratum: 0,\n        poll: 0,\n        precision: 0x20,\n        root_delay: 0,\n        root_dispersion: 0,\n        reference_id: 0,\n        reference_timestamp: 0xdeadbeef,\n        origin_timestamp: 0,\n        receive_timestamp: 0,\n        transmit_timestamp: 0,\n    };\n    let mut unique_id: Vec<u8> = vec![0; 32];\n    rand::thread_rng().fill(&mut unique_id[..]);\n    let auth_exts = vec![\n        NtpExtension {\n            ext_type: UniqueIdentifier,\n            contents: unique_id.clone(),\n        },\n        NtpExtension {\n            ext_type: NTSCookie,\n            contents: state.cookies[0].clone(),\n        },\n    ];\n    let packet = NtsPacket {\n        header,\n        auth_exts,\n        auth_enc_exts: vec![],\n    };\n    socket.connect(addr.unwrap())?;\n    let wire_packet = &serialize_nts_packet::<Aes128SivAead>(packet, &mut send_aead);\n    let t1 = system_to_ntpfloat(SystemTime::now());\n    socket.send(wire_packet)?;\n    debug!(logger, \"transmitting packet\");\n    let mut buff = [0; BUFF_SIZE];\n    let (size, _origin) = socket.recv_from(&mut buff)?;\n    let t4 = system_to_ntpfloat(SystemTime::now());\n    debug!(logger, \"received packet\");\n    let received = parse_nts_packet::<Aes128SivAead>(&buff[0..size], &mut recv_aead);\n    match received {\n        Err(x) => Err(Box::new(x)),\n        Ok(packet) => {\n            // check if server response contains the same UniqueIdentifier as client request\n            let resp_unique_id = packet.auth_exts[0].clone().contents;\n            if resp_unique_id != unique_id {\n                return Err(Box::new(InvalidUid));\n            }\n\n            Ok(NtpResult {\n                stratum: packet.header.stratum,\n                time_diff: ((timestamp_to_float(packet.header.receive_timestamp) - t1)\n                    + (timestamp_to_float(packet.header.transmit_timestamp) - t4))\n                    / 2.0,\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "src/ntp/mod.rs",
    "content": "pub mod client;\npub mod protocol;\npub mod server;\n"
  },
  {
    "path": "src/ntp/protocol.rs",
    "content": "use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};\nuse miscreant::aead::Aead;\nuse rand::Rng;\n\nuse std::io::{Cursor, Error, ErrorKind, Read, Write};\nuse std::panic;\n\nuse self::LeapState::*;\nuse self::NtpExtensionType::*;\nuse self::PacketMode::*;\n\n/// These numbers are from RFC 5905\npub const VERSION: u8 = 4;\npub const UNIX_OFFSET: u64 = 2_208_988_800;\npub const PHI: f64 = 15e-6;\n/// TWO_POW_32 is a floating point power of two (2**32)\npub const TWO_POW_32: f64 = 4294967296.0;\n\nconst HEADER_SIZE: u64 = 48;\nconst NONCE_LEN: usize = 16;\nconst EXT_TYPE_UNIQUE_IDENTIFIER: u16 = 0x0104;\nconst EXT_TYPE_NTS_COOKIE: u16 = 0x0204;\nconst EXT_TYPE_NTS_COOKIE_PLACEHOLDER: u16 = 0x0304;\nconst EXT_TYPE_NTS_AUTHENTICATOR: u16 = 0x0404;\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum LeapState {\n    NoLeap = 0,\n    Positive = 1,\n    Negative = 2,\n    Unknown = 3,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum PacketMode {\n    SymmetricActive = 1,\n    SymmetricPassive = 2,\n    Client = 3, // We send Mode 3 packets and recieve Mode 4. Check the errata on 5905!\n    Server = 4,\n    Broadcast = 5,\n    Invalid,\n}\n\n#[derive(Debug, Clone, Copy, Eq, PartialEq)]\npub enum NtpExtensionType {\n    UniqueIdentifier,\n    NTSCookie,\n    NTSCookiePlaceholder,\n    NTSAuthenticator,\n    Unknown(u16),\n}\n\nfn wire_type(x: NtpExtensionType) -> u16 {\n    match x {\n        UniqueIdentifier => EXT_TYPE_UNIQUE_IDENTIFIER,\n        NTSCookie => EXT_TYPE_NTS_COOKIE,\n        NTSCookiePlaceholder => EXT_TYPE_NTS_COOKIE_PLACEHOLDER,\n        NTSAuthenticator => EXT_TYPE_NTS_AUTHENTICATOR,\n        NtpExtensionType::Unknown(y) => y,\n    }\n}\n\nfn type_from_wire(ext: u16) -> NtpExtensionType {\n    match ext {\n        EXT_TYPE_UNIQUE_IDENTIFIER => UniqueIdentifier,\n        EXT_TYPE_NTS_COOKIE => NTSCookie,\n        EXT_TYPE_NTS_COOKIE_PLACEHOLDER => NTSCookiePlaceholder,\n        EXT_TYPE_NTS_AUTHENTICATOR => NTSAuthenticator,\n        y => NtpExtensionType::Unknown(y),\n    }\n}\n\n/// Header of an NTP and NTS packet\n/// See RFC 5905 for meaning of these fields\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct NtpPacketHeader {\n    pub leap_indicator: LeapState,\n    pub version: u8,\n    pub mode: PacketMode,\n    pub stratum: u8,\n    pub poll: i8,\n    pub precision: i8,\n    pub root_delay: u32,\n    pub root_dispersion: u32,\n    pub reference_id: u32,\n    pub reference_timestamp: u64,\n    pub origin_timestamp: u64,\n    pub receive_timestamp: u64,\n    pub transmit_timestamp: u64,\n}\n\n/// The authenticating extension needs to be treated\n/// differently from all other extensions. We can't write it out\n/// until we know the data it authenticates, so the nts parsing\n/// and writing functions are a bit more complicated.\n\n/// It is up to the constructor to ensure that the contents of\n/// extensions are padded to length a multiple of 4 greater then or\n/// equal to 16, or 28 if they are the last extension.\n#[derive(Debug, Clone)]\npub struct NtpExtension {\n    pub ext_type: NtpExtensionType,\n    pub contents: Vec<u8>,\n}\n\n/// An NTS packet has authenticated extensions and authenticated and encrypted\n/// extensions. All other extensions are ignored.\n#[derive(Debug, Clone)]\npub struct NtsPacket {\n    pub header: NtpPacketHeader,\n    pub auth_exts: Vec<NtpExtension>,\n    pub auth_enc_exts: Vec<NtpExtension>,\n}\n\n/// An NTP packet has a header and optional numbers of extensions. We ignore\n/// legacy mac entirely.\n#[derive(Debug, Clone)]\npub struct NtpPacket {\n    pub header: NtpPacketHeader,\n    pub exts: Vec<NtpExtension>,\n}\n\n/// The first byte encodes these three fields in a bitpacked format.\n/// These 4 helper functions deal with that.\n/// See RFC 5905 Figure 8.\nfn parse_leap_indicator(first: u8) -> LeapState {\n    match first >> 6 {\n        0 => NoLeap,\n        1 => Positive,\n        2 => Negative,\n        _ => LeapState::Unknown,\n    }\n}\n\nfn parse_version(first: u8) -> u8 {\n    (first & 0x38) >> 3\n}\n\nfn parse_mode(first: u8) -> PacketMode {\n    let modnum = first & 0x07;\n    match modnum {\n        1 => SymmetricActive,\n        2 => SymmetricPassive,\n        3 => Client,\n        4 => Server,\n        5 => Broadcast,\n        _ => Invalid,\n    }\n}\n\n/// The first byte packs 3 fields in.\nfn create_first(leap: LeapState, version: u8, mode: PacketMode) -> u8 {\n    ((leap as u8) << 6) | ((version << 3) & 0x38) | ((mode as u8) & 0x07)\n}\n\n/// Extract an NTP packet header from packet and return an error if it cannot be done.\npub fn parse_packet_header(packet: &[u8]) -> Result<NtpPacketHeader, std::io::Error> {\n    let mut buff = Cursor::new(packet);\n    if packet.len() < 48 {\n        Err(Error::new(ErrorKind::InvalidInput, \"Too short\"))\n    } else {\n        let first = buff.read_u8()?;\n        let stratum = buff.read_u8()?;\n        let poll = buff.read_i8()?;\n        let precision = buff.read_i8()?;\n        let root_delay = buff.read_u32::<BigEndian>()?;\n        let root_dispersion = buff.read_u32::<BigEndian>()?;\n        let reference_id = buff.read_u32::<BigEndian>()?;\n        let reference_timestamp = buff.read_u64::<BigEndian>()?;\n        let origin_timestamp = buff.read_u64::<BigEndian>()?;\n        let receive_timestamp = buff.read_u64::<BigEndian>()?;\n        let transmit_timestamp = buff.read_u64::<BigEndian>()?;\n        Ok(NtpPacketHeader {\n            leap_indicator: parse_leap_indicator(first),\n            version: parse_version(first),\n            mode: parse_mode(first),\n            stratum,\n            poll,\n            precision,\n            root_delay,\n            root_dispersion,\n            reference_id,\n            reference_timestamp,\n            origin_timestamp,\n            receive_timestamp,\n            transmit_timestamp,\n        })\n    }\n}\n\n/// serialize_header returns a Vec<u8> containing the wire\n/// format of the header.\npub fn serialize_header(head: NtpPacketHeader) -> Vec<u8> {\n    let mut buff = Cursor::new(Vec::new());\n    let first = create_first(head.leap_indicator, head.version, head.mode);\n    buff.write_u8(first)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_u8(head.stratum)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_i8(head.poll)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_i8(head.precision)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_u32::<BigEndian>(head.root_delay)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_u32::<BigEndian>(head.root_dispersion)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_u32::<BigEndian>(head.reference_id)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_u64::<BigEndian>(head.reference_timestamp)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_u64::<BigEndian>(head.origin_timestamp)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_u64::<BigEndian>(head.receive_timestamp)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.write_u64::<BigEndian>(head.transmit_timestamp)\n        .expect(\"write to buffer failed, unable to serialize NtpPacketHeader\");\n    buff.into_inner()\n}\n\n/// parse_ntp_packet parses an NTP packet\npub fn parse_ntp_packet(buff: &[u8]) -> Result<NtpPacket, std::io::Error> {\n    let header = parse_packet_header(buff)?;\n    let exts = parse_extensions(&buff[48..])?;\n    Ok(NtpPacket { header, exts })\n}\n\n/// Properly parsing NTP extensions in accordance with RFC 7822 is not necessary\n/// since the legacy MAC will never be used by this code.\nfn parse_extensions(buff: &[u8]) -> Result<Vec<NtpExtension>, std::io::Error> {\n    let mut reader = Cursor::new(buff);\n    let mut retval = Vec::new();\n    while buff.len() - reader.position() as usize >= 4 {\n        let ext_type = reader.read_u16::<BigEndian>()?;\n        let ext_len = reader.read_u16::<BigEndian>()?;\n        if ext_len % 4 != 0 {\n            return Err(Error::new(\n                ErrorKind::InvalidInput,\n                \"extension not on word boundary\",\n            ));\n        }\n        if ext_len < 4 {\n            return Err(Error::new(ErrorKind::InvalidInput, \"extension too short\"));\n        }\n        let mut contents: Vec<u8> = vec![0; (ext_len - 4) as usize];\n        reader.read_exact(&mut contents)?;\n        retval.push(NtpExtension {\n            ext_type: type_from_wire(ext_type),\n            contents,\n        })\n    }\n    Ok(retval)\n}\n\n/// serialize_ntp_packet returns the packet in wire format.\npub fn serialize_ntp_packet(pack: NtpPacket) -> Vec<u8> {\n    let mut buff = Cursor::new(Vec::new());\n    buff.write_all(&serialize_header(pack.header))\n        .expect(\"buffer write failed; can't serialize NtpPacket\");\n    buff.write_all(&serialize_extensions(pack.exts))\n        .expect(\"buffer write failed; can't serialize NtpPacket\");\n    buff.into_inner()\n}\n\nfn serialize_extensions(exts: Vec<NtpExtension>) -> Vec<u8> {\n    let mut buff = Cursor::new(Vec::new());\n    for ext in exts {\n        if ext.contents.len() % 4 != 0 {\n            panic!(\"extension is the wrong length\")\n        }\n        buff.write_u16::<BigEndian>(wire_type(ext.ext_type))\n            .expect(\"buffer write failed; can't serialize Ntp Extensions\");\n        buff.write_u16::<BigEndian>((ext.contents.len() + 4) as u16)\n            .expect(\"buffer write failed; can't serialize Ntp Extensions\"); // The length includes the header\n        buff.write_all(&ext.contents)\n            .expect(\"buffer write failed; can't serialize Ntp Extensions\");\n    }\n    buff.into_inner()\n}\n\n/// has_extension returns true if the packet has an extension of the right kind\npub fn has_extension(pack: &NtpPacket, kind: NtpExtensionType) -> bool {\n    pack.exts\n        .clone()\n        .into_iter()\n        .any(|ext| ext.ext_type == kind)\n}\n\n/// is_nts_packet returns true if this packet is plausibly an NTS packet.\n/// TODO: enforce rules tighter about uniqueness of some of these extensions.\npub fn is_nts_packet(pack: &NtpPacket) -> bool {\n    has_extension(pack, NTSCookie)\n        && has_extension(pack, NTSAuthenticator)\n        && has_extension(pack, UniqueIdentifier)\n}\n\n/// extract_extension retrieves the extension if it exists, and else none.\npub fn extract_extension(pack: &NtpPacket, kind: NtpExtensionType) -> Option<NtpExtension> {\n    pack.exts\n        .clone()\n        .into_iter()\n        .find(|ext| ext.ext_type == kind)\n}\n\n/// parse_nts_packet parses an NTS packet.\npub fn parse_nts_packet<T: Aead>(\n    buff: &[u8],\n    decryptor: &mut T,\n) -> Result<NtsPacket, std::io::Error> {\n    let header = parse_packet_header(buff)?;\n    let mut reader = Cursor::new(buff);\n    let mut auth_exts = Vec::new();\n    reader.set_position(HEADER_SIZE);\n    while buff.len() - reader.position() as usize >= 4 {\n        let ext_type = reader.read_u16::<BigEndian>()?;\n        let ext_len = (reader.read_u16::<BigEndian>()? - 4) as usize; // RFC 7822\n        match type_from_wire(ext_type) {\n            NTSAuthenticator => {\n                let mut auth_ext_contents = vec![0; ext_len];\n                reader.read_exact(&mut auth_ext_contents)?;\n                let oldpos = (reader.position() - 4 - (ext_len as u64)) as usize;\n                let enc_ext_data =\n                    parse_decrypt_auth_ext::<T>(&buff[0..oldpos], &auth_ext_contents, decryptor)?;\n                let auth_enc_exts = parse_extensions(&enc_ext_data)?;\n                return Ok(NtsPacket {\n                    header,\n                    auth_exts,\n                    auth_enc_exts,\n                });\n            }\n            _ => {\n                let mut contents: Vec<u8> = vec![0; ext_len];\n                reader.read_exact(&mut contents)?;\n                auth_exts.push(NtpExtension {\n                    ext_type: type_from_wire(ext_type),\n                    contents,\n                });\n            }\n        }\n    }\n    Err(Error::new(\n        ErrorKind::InvalidInput,\n        \"never saw the authenticator\",\n    ))\n}\n\nfn parse_decrypt_auth_ext<T: Aead>(\n    auth_dat: &[u8],\n    auth_ext_contents: &[u8],\n    decryptor: &mut T,\n) -> Result<Vec<u8>, std::io::Error> {\n    let mut reader = Cursor::new(auth_ext_contents);\n    if auth_ext_contents.len() - (reader.position() as usize) < 4 {\n        return Err(Error::new(ErrorKind::InvalidInput, \"insufficient length\"));\n    }\n    let nonce_len = reader.read_u16::<BigEndian>()? as usize;\n    let cipher_len = reader.read_u16::<BigEndian>()? as usize;\n    let nonce_pad_len = nonce_len + ((4 - (nonce_len % 4)) % 4);\n    let cipher_pad_len = cipher_len + ((4 - (cipher_len % 4)) % 4);\n    if nonce_pad_len + cipher_pad_len + 4 > auth_ext_contents.len() {\n        return Err(Error::new(\n            ErrorKind::InvalidInput,\n            \"length of data exceeds wrapper\",\n        ));\n    }\n    let nonce = &auth_ext_contents[4..(4 + nonce_len)];\n    let ciphertext = &auth_ext_contents[(4 + nonce_pad_len)..(4 + nonce_pad_len + cipher_len)];\n    let res = decryptor.open(nonce, auth_dat, ciphertext);\n    if res.is_err() {\n        return Err(Error::new(ErrorKind::InvalidInput, \"authentication failed\"));\n    }\n    Ok(res.unwrap())\n}\n\n/// serialize_nts_packet serializes the packet and does all the encryption\npub fn serialize_nts_packet<T: Aead>(packet: NtsPacket, encryptor: &mut T) -> Vec<u8> {\n    let mut buff = Cursor::new(Vec::new());\n    buff.write_all(&serialize_header(packet.header))\n        .expect(\"Nts header could not be written, failed to serialize NtsPacket\");\n    buff.write_all(&serialize_extensions(packet.auth_exts))\n        .expect(\"Nts extensions could not be written, failed to serialize NtsPacket\");\n    let plaintext = serialize_extensions(packet.auth_enc_exts);\n    let mut nonce = [0; NONCE_LEN];\n    rand::thread_rng().fill(&mut nonce);\n    let ciphertext = encryptor.seal(&nonce, buff.get_ref(), &plaintext);\n\n    let mut authent_buffer = Cursor::new(Vec::new());\n    authent_buffer\n        .write_u16::<BigEndian>(NONCE_LEN as u16)\n        .expect(\"Nonce length could not be written, failed to serialize NtsPacket\"); // length of the nonce\n    authent_buffer\n        .write_u16::<BigEndian>(ciphertext.len() as u16)\n        .expect(\"Ciphertext length could not be written, failed to serialize NtsPacket\");\n    authent_buffer\n        .write_all(&nonce)\n        .expect(\"Nonce could not be written, failed to serialize NtsPacket\"); // 16 bytes so no padding\n    authent_buffer\n        .write_all(&ciphertext)\n        .expect(\"Ciphertext could not be written, failed to serialize NtsPacket\");\n    let padlen = (4 - (ciphertext.len() % 4)) % 4;\n    for _i in 0..padlen {\n        // pad with zeros: probably cleaner way exists\n        authent_buffer\n            .write_u8(0)\n            .expect(\"Padding could not be written, failed to serialize NtsPacket\");\n    }\n    let last_ext = NtpExtension {\n        ext_type: NTSAuthenticator,\n        contents: authent_buffer.into_inner(),\n    };\n    let res = serialize_extensions(vec![last_ext]);\n    buff.write_all(&res)\n        .expect(\"Extensions could not be written, failed to serialize NtsPacket\");\n    buff.into_inner()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use miscreant::aead::Aes128SivAead;\n    #[test]\n    fn test_ntp_header_parse() {\n        let leaps = vec![NoLeap, Positive, Negative, LeapState::Unknown];\n        let versions = vec![1, 2, 3, 4, 5, 6, 7];\n        let modes = vec![SymmetricActive, SymmetricPassive, Client, Server, Broadcast];\n        for leap in &leaps {\n            for version in &versions {\n                for mode in &modes {\n                    let start_header = NtpPacketHeader {\n                        leap_indicator: *leap,\n                        version: *version,\n                        mode: *mode,\n                        stratum: 0,\n                        poll: 0,\n                        precision: 0,\n                        root_delay: 0,\n                        root_dispersion: 0,\n                        reference_id: 0,\n                        reference_timestamp: 0,\n                        origin_timestamp: 0,\n                        receive_timestamp: 0,\n                        transmit_timestamp: 0,\n                    };\n                    let ret_header = parse_packet_header(&serialize_header(start_header)).unwrap();\n                    assert_eq!(ret_header, start_header)\n                }\n            }\n        }\n    }\n\n    fn check_eq_ext(a: &NtpExtension, b: &NtpExtension) {\n        assert_eq!(a.ext_type, b.ext_type);\n        assert_eq!(a.contents.len(), b.contents.len());\n        for i in 0..a.contents.len() {\n            assert_eq!(a.contents[i], b.contents[i]);\n        }\n    }\n    fn check_ext_array_eq(exts1: Vec<NtpExtension>, exts2: Vec<NtpExtension>) {\n        assert_eq!(exts1.len(), exts2.len());\n        for i in 0..exts1.len() {\n            check_eq_ext(&exts1[i], &exts2[i]);\n        }\n    }\n    fn check_nts_match(pkt1: NtsPacket, pkt2: NtsPacket) {\n        assert_eq!(pkt1.header, pkt2.header);\n        check_ext_array_eq(pkt1.auth_enc_exts, pkt2.auth_enc_exts);\n        check_ext_array_eq(pkt1.auth_exts, pkt2.auth_exts);\n    }\n    fn roundtrip_test<T: Aead>(input: NtsPacket, enc: &mut T) {\n        let mut packet = serialize_nts_packet::<T>(input.clone(), enc);\n        let decrypt = parse_nts_packet(&packet, enc).unwrap();\n        check_nts_match(input, decrypt);\n        packet[0] = 0xde;\n        packet[1] = 0xad;\n        packet[2] = 0xbe;\n        packet[3] = 0xef;\n        if parse_nts_packet(&packet, enc).is_ok() {\n            panic!(\"success when we should have failed\");\n        }\n    }\n    #[test]\n    fn test_nts_parse() {\n        let key = [0; 32];\n        let mut test_aead = Aes128SivAead::new(&key);\n        let header = NtpPacketHeader {\n            leap_indicator: NoLeap,\n            version: 4,\n            mode: Client,\n            stratum: 1,\n            poll: 0,\n            precision: 0,\n            root_delay: 0,\n            root_dispersion: 0,\n            reference_id: 0,\n            reference_timestamp: 0,\n            origin_timestamp: 0,\n            receive_timestamp: 0,\n            transmit_timestamp: 0,\n        };\n\n        let packet = NtsPacket {\n            header,\n            auth_exts: vec![\n                NtpExtension {\n                    ext_type: UniqueIdentifier,\n                    contents: vec![0; 32],\n                },\n                NtpExtension {\n                    ext_type: NTSCookie,\n                    contents: vec![0; 32],\n                },\n            ],\n            auth_enc_exts: vec![NtpExtension {\n                ext_type: NTSCookiePlaceholder,\n                contents: vec![0xfe; 32],\n            }],\n        };\n        roundtrip_test::<Aes128SivAead>(packet, &mut test_aead);\n    }\n}\n"
  },
  {
    "path": "src/ntp/server/config.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! NTP server configuration.\n\nuse sloggers::terminal::TerminalLoggerBuilder;\nuse sloggers::Build;\n\nuse std::convert::TryFrom;\nuse std::net::{IpAddr, SocketAddr};\nuse std::str::FromStr;\n\nuse crate::cookie::CookieKey;\nuse crate::error::WrapError;\nuse crate::metrics::MetricsConfig;\n\nfn get_metrics_config(settings: &config::Config) -> Option<MetricsConfig> {\n    let mut metrics = None;\n    if let Ok(addr) = settings.get_str(\"metrics_addr\") {\n        if let Ok(port) = settings.get_int(\"metrics_port\") {\n            metrics = Some(MetricsConfig {\n                port: port as u16,\n                addr,\n            });\n        }\n    }\n    metrics\n}\n\n/// Configuration for running an NTP server.\n#[derive(Debug)]\npub struct NtpServerConfig {\n    /// List of addresses and ports to the server will be listening to.\n    // Each of the elements can be either IPv4 or IPv6 address. It cannot be a UNIX socket address.\n    addrs: Vec<SocketAddr>,\n\n    pub cookie_key: CookieKey,\n\n    /// The logger that will be used throughout the application, while the server is running.\n    /// This property is mandatory because logging is very important for debugging.\n    logger: slog::Logger,\n\n    pub memcached_url: String,\n    pub metrics_config: Option<MetricsConfig>,\n    pub upstream_addr: Option<SocketAddr>,\n}\n\n/// We decided to make NtpServerConfig mutable so that you can add more address after you parse\n/// the config file.\nimpl NtpServerConfig {\n    /// Create a NTP server config object with the given cookie key, memcached url, the metrics\n    /// config, and the upstream address port.\n    pub fn new(\n        cookie_key: CookieKey,\n        memcached_url: String,\n        metrics_config: Option<MetricsConfig>,\n        upstream_addr: Option<SocketAddr>,\n    ) -> NtpServerConfig {\n        NtpServerConfig {\n            addrs: Vec::new(),\n\n            // Use terminal logger as a default logger. The users can override it using\n            // `set_logger` later, if they want.\n            //\n            // According to `sloggers-0.3.2` source code, the function doesn't return an error at\n            // all. There should be no problem unwrapping here.\n            logger: TerminalLoggerBuilder::new()\n                .build()\n                .expect(\"BUG: TerminalLoggerBuilder::build shouldn't return an error.\"),\n\n            // From parameters.\n            cookie_key,\n            memcached_url,\n            metrics_config,\n            upstream_addr,\n        }\n    }\n\n    /// Add an address into the config.\n    pub fn add_address(&mut self, addr: SocketAddr) {\n        self.addrs.push(addr);\n    }\n\n    /// Return a list of addresses.\n    pub fn addrs(&self) -> &[SocketAddr] {\n        self.addrs.as_slice()\n    }\n\n    /// Set a new logger to the config.\n    pub fn set_logger(&mut self, logger: slog::Logger) {\n        self.logger = logger;\n    }\n\n    /// Return the logger of the config.\n    pub fn logger(&self) -> &slog::Logger {\n        &self.logger\n    }\n\n    /// Parse a config from a file.\n    ///\n    /// # Errors\n    ///\n    /// Currently we return `config::ConfigError` which is returned from functions in the\n    /// `config` crate itself.\n    ///\n    /// For any error from any file specified in the configuration, `std::io::Error` which is\n    /// wrapped inside `config::ConfigError::Foreign` will be returned.\n    ///\n    /// For any address parsing error, `std::io::Error` wrapped inside\n    /// `config::ConfigError::Foreign` will also be returned.\n    ///\n    /// In addition, it also returns some custom `config::ConfigError::Message` errors, for the\n    /// following cases:\n    ///\n    /// * The upstream port in the configuration file is a valid `i64` but not a valid `u16`.\n    ///\n    // Returning a `Message` object here is not a good practice. I will figure out a good practice\n    // later.\n    pub fn parse(filename: &str) -> Result<NtpServerConfig, config::ConfigError> {\n        let mut settings = config::Config::new();\n        settings.merge(config::File::with_name(filename))?;\n\n        let memcached_url = settings.get_str(\"memc_url\")?;\n\n        // Resolves metrics configuration.\n        let metrics_config = get_metrics_config(&settings);\n\n        // XXX: The code of parsing a next port here is quite ugly due to the `get_int` interface.\n        // Please don't be surprised :)\n        let upstream_port = match settings.get_int(\"upstream_port\") {\n            // If it's a not-found error, we can just leave it empty.\n            Err(config::ConfigError::NotFound(_)) => None,\n\n            // If it's other error, for example, unparseable error, it means that the user intended\n            // to enter the port number but it just fails.\n            Err(error) => return Err(error),\n\n            Ok(val) => {\n                let port = match u16::try_from(val) {\n                    Ok(val) => val,\n                    // The error will happen when the port number is not in a range of `u16`.\n                    Err(_) => {\n                        // Returning a custom message is not a good practice, but we can improve\n                        // it later when we don't have to depend on `config` crate.\n                        return Err(config::ConfigError::Message(String::from(\n                            \"the upstream port is not a valid u64\",\n                        )));\n                    }\n                };\n                Some(port)\n            }\n        };\n\n        let upstream_addr = match settings.get_str(\"upstream_addr\") {\n            // If it's a not-found error, we can just leave it empty.\n            Err(config::ConfigError::NotFound(_)) => None,\n\n            // If it's other error, for example, unparseable error, it means that the user intended\n            // to enter the address but it just fails.\n            Err(error) => return Err(error),\n\n            Ok(addr) => Some(addr),\n        };\n\n        let upstream_sock_addr =\n            if let (Some(upstream_addr), Some(upstream_port)) = (upstream_addr, upstream_port) {\n                Some(SocketAddr::from((\n                    IpAddr::from_str(&upstream_addr).wrap_err()?,\n                    upstream_port,\n                )))\n            } else {\n                None\n            };\n\n        // Note that all of the file reading stuffs should be at the end of the function so that\n        // all the not-file-related stuffs can fail fast.\n\n        let cookie_key_filename = settings.get_str(\"cookie_key_file\")?;\n        let cookie_key = CookieKey::parse(&cookie_key_filename).wrap_err()?;\n\n        let mut config = NtpServerConfig::new(\n            cookie_key,\n            memcached_url,\n            metrics_config,\n            upstream_sock_addr,\n        );\n\n        let addrs = settings.get_array(\"addr\")?;\n        for addr in addrs {\n            // Parse SocketAddr from a string.\n            let sock_addr = addr.to_string().parse().wrap_err()?;\n            config.add_address(sock_addr);\n        }\n\n        Ok(config)\n    }\n}\n"
  },
  {
    "path": "src/ntp/server/mod.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! NTP server implementation.\n\nmod config;\nmod ntp_server;\n\npub use self::config::NtpServerConfig;\npub use self::ntp_server::start_ntp_server;\n"
  },
  {
    "path": "src/ntp/server/ntp_server.rs",
    "content": "use super::config::NtpServerConfig;\nuse crate::cfsock;\nuse crate::cookie::{eat_cookie, get_keyid, make_cookie, NTSKeys, COOKIE_SIZE};\nuse crate::key_rotator::{periodic_rotate, KeyRotator};\nuse crate::metrics;\n\nuse lazy_static::lazy_static;\nuse prometheus::{opts, register_counter, register_int_counter, IntCounter};\nuse slog::{error, info};\n\nuse std::io::{Error, ErrorKind};\nuse std::net::{SocketAddr, ToSocketAddrs, UdpSocket};\nuse std::os::unix::io::AsRawFd;\nuse std::sync::{Arc, RwLock};\nuse std::thread;\nuse std::time;\nuse std::time::{Duration, SystemTime};\nuse std::vec;\n\nuse crossbeam::sync::WaitGroup;\nuse libc::{in6_pktinfo, in_pktinfo};\n/// Miscreant calls Aes128SivAead what IANA calls AEAD_AES_SIV_CMAC_256\nuse miscreant::aead::Aead;\nuse miscreant::aead::Aes128SivAead;\nuse nix::sys::socket::{\n    recvmsg, sendmsg, setsockopt, sockopt, CmsgSpace, ControlMessage, MsgFlags,\n};\nuse nix::sys::time::{TimeVal, TimeValLike};\nuse nix::sys::uio::IoVec;\n\nuse crate::ntp::protocol;\nuse crate::ntp::protocol::{\n    extract_extension, has_extension, is_nts_packet, parse_ntp_packet, parse_nts_packet,\n    serialize_header, serialize_ntp_packet, serialize_nts_packet, LeapState, LeapState::*,\n    NtpExtension, NtpExtensionType::NTSCookie, NtpExtensionType::UniqueIdentifier, NtpPacket,\n    NtpPacketHeader, NtsPacket, PacketMode, PHI, UNIX_OFFSET,\n};\n\nconst BUF_SIZE: usize = 1280; // Anything larger might fragment.\nconst TWO_POW_32: f64 = 4294967296.0;\nconst TWO_POW_16: f64 = 65536.0;\n\nlazy_static! {\n    static ref QUERY_COUNTER: IntCounter =\n        register_int_counter!(\"ntp_queries_total\", \"Number of NTP queries\").unwrap();\n    static ref NTS_COUNTER: IntCounter = register_int_counter!(\n        \"ntp_nts_queries_total\",\n        \"Number of queries we thought were NTS\"\n    )\n    .unwrap();\n    static ref KOD_COUNTER: IntCounter =\n        register_int_counter!(\"ntp_kod_total\", \"Number of Kiss of Death packets sent\").unwrap();\n    static ref MALFORMED_COOKIE_COUNTER: IntCounter = register_int_counter!(\n        \"ntp_malformed_cookie_total\",\n        \"Number of cookies with malformations\"\n    )\n    .unwrap();\n    static ref MANGLED_PACKET_COUNTER: IntCounter = register_int_counter!(\n        \"ntp_mangled_packet_total\",\n        \"Number of packets without valid ntp headers\"\n    )\n    .unwrap();\n    static ref MISSING_KEY_COUNTER: IntCounter =\n        register_int_counter!(\"ntp_missing_key_total\", \"Number of keys we could not find\").unwrap();\n    static ref UNDECRYPTABLE_COOKIE_COUNTER: IntCounter = register_int_counter!(\n        \"ntp_undecryptable_cookie_total\",\n        \"Number of cookies we could not decrypt\"\n    )\n    .unwrap();\n    static ref UPSTREAM_QUERY_COUNTER: IntCounter = register_int_counter!(\n        \"ntp_upstream_queries_total\",\n        \"Number of upstream queries sent\"\n    )\n    .unwrap();\n    static ref UPSTREAM_FAILURE_COUNTER: IntCounter = register_int_counter!(\n        \"ntp_upstream_failures_total\",\n        \"Number of failed upstream queries\"\n    )\n    .unwrap();\n}\n\n#[derive(Clone, Copy, Debug)]\nstruct ServerState {\n    leap: LeapState,\n    stratum: u8,\n    version: u8,\n    poll: i8,\n    precision: i8,\n    root_delay: u32,\n    root_dispersion: u32,\n    refid: u32,\n    refstamp: u64,\n    taken: SystemTime,\n}\n\ntype TheCmsgSpace = CmsgSpace<(TimeVal, CmsgSpace<(in_pktinfo, CmsgSpace<in6_pktinfo>)>)>;\n\n/// run_server runs the ntp server on the given socket.\n/// The caller has to set up the socket options correctly\nfn run_server(\n    socket: UdpSocket,\n    keys: Arc<RwLock<KeyRotator>>,\n    servstate: Arc<RwLock<ServerState>>,\n    logger: slog::Logger,\n    ipv4: bool,\n) -> Result<(), std::io::Error> {\n    let sockfd = socket.as_raw_fd();\n    setsockopt(sockfd, sockopt::ReceiveTimestamp, &true)\n        .expect(\"setsockopt failed; can't run ntp server\");\n    if ipv4 {\n        setsockopt(sockfd, sockopt::Ipv4PacketInfo, &true)\n            .expect(\"setsockopt failed; can't run ntp server\");\n    } else {\n        setsockopt(sockfd, sockopt::Ipv6RecvPacketInfo, &true)\n            .expect(\"setsockopt failed; can't run ntp server\");\n    }\n    // The following is adapted from the example in the nix crate docs:\n    // https://docs.rs/nix/0.13.0/nix/sys/socket/enum.ControlMessage.html#variant.ScmTimestamp\n    // Most of these functions are documented in manpages, and nix is a thin wrapper around them.\n    loop {\n        // Receive and respond to packets\n        let mut buf = [0; BUF_SIZE];\n        let flags = MsgFlags::empty();\n        let mut cmsgspace = TheCmsgSpace::new();\n        let iov = [IoVec::from_mut_slice(&mut buf)];\n        let r = recvmsg(sockfd, &iov, Some(&mut cmsgspace), flags);\n        if let Err(_err) = r {\n            error!(logger, \"error receiving message: {:?}\", _err);\n            continue;\n        }\n        let r = r.unwrap(); // this is safe because of previous if\n        if r.address.is_none() {\n            // No return address => we can't do anything\n            continue;\n        }\n        let src = r.address.unwrap();\n        // We should only have a single cmsg of known type.\n        // The nix crate implements a typesafe interface to cmsg,\n        // hence some of the matching here.\n        let mut r_time = TimeVal::nanoseconds(0);\n        let mut msgs: Vec<ControlMessage> = Vec::new();\n        for msg in r.cmsgs() {\n            match msg {\n                ControlMessage::ScmTimestamp(&r_timestamp) => r_time = r_timestamp,\n                ControlMessage::Ipv4PacketInfo(_inf) => {\n                    if ipv4 {\n                        msgs.push(msg);\n                    } else {\n                        error!(logger, \"v6 connection got v4 info\");\n                        continue;\n                    }\n                }\n                ControlMessage::Ipv6PacketInfo(_inf) => {\n                    if !ipv4 {\n                        msgs.push(msg);\n                    } else {\n                        error!(logger, \"v4 connection got v6 info\");\n                        continue;\n                    }\n                }\n                _ => {\n                    error!(logger, \"unexpected control message\");\n                    continue;\n                }\n            }\n        }\n\n        let r_system = SystemTime::UNIX_EPOCH\n            + Duration::new(r_time.tv_sec() as u64, r_time.tv_usec() as u32 * 1000);\n        let t_system = SystemTime::now();\n        // We now have the receive times and the current time as SystemTimes\n        let resp = response(\n            &buf[..r.bytes],\n            r_system,\n            t_system,\n            keys.clone(),\n            servstate.clone(),\n            logger.clone(),\n        );\n        match resp {\n            Ok(data) => {\n                let resp = sendmsg(\n                    sockfd,\n                    &[IoVec::from_slice(&data)],\n                    &msgs,\n                    flags,\n                    Some(&src),\n                );\n                if let Err(err) = resp {\n                    error!(logger, \"error sending response: {:}\", err);\n                }\n            }\n            Err(_) => {\n                MANGLED_PACKET_COUNTER.inc(); // The packet is too mangled to do much with.\n                error!(logger, \"mangled packet\");\n            }\n        };\n    }\n}\n\n/// start_ntp_server runs the ntp server with the config specified in config_filename\npub fn start_ntp_server(config: NtpServerConfig) -> Result<(), Box<dyn std::error::Error>> {\n    let logger = config.logger().clone();\n\n    info!(logger, \"Initializing keys with memcached\");\n\n    let key_rotator = KeyRotator::connect(\n        String::from(\"/nts/nts-keys\"), // prefix\n        config.memcached_url.clone(),  // memcached_url\n        config.cookie_key.clone(),     // master_key\n        logger.clone(),                // logger\n    )\n    .expect(\"error connecting to the memcached server\");\n\n    let keys = Arc::new(RwLock::new(key_rotator));\n    periodic_rotate(keys.clone());\n\n    let servstate_struct = ServerState {\n        leap: Unknown,\n        stratum: 16,\n        version: protocol::VERSION,\n        poll: 7,\n        precision: -18,\n        root_delay: 10,\n        root_dispersion: 10,\n        refid: 0,\n        refstamp: 0,\n        taken: SystemTime::now(),\n    };\n\n    let servstate = Arc::new(RwLock::new(servstate_struct));\n    match config.upstream_addr {\n        Some(upstream_addr) => {\n            info!(logger, \"connecting to upstream\");\n            let servstate = servstate.clone();\n            let rot_logger = logger.new(slog::o!(\"task\"=>\"refereshing servstate\"));\n            let socket = UdpSocket::bind(\"127.0.0.1:0\")?; // we only go to local\n            socket.set_read_timeout(Some(time::Duration::from_secs(1)))?;\n            thread::spawn(move || {\n                refresh_servstate(servstate, rot_logger, socket, &upstream_addr);\n            });\n        }\n        None => {\n            let mut state_guard = servstate.write().unwrap();\n            info!(logger, \"setting stratum to 1\");\n            state_guard.leap = NoLeap;\n            state_guard.stratum = 1;\n        }\n    }\n\n    if let Some(metrics_config) = config.metrics_config.clone() {\n        info!(logger, \"spawning metrics\");\n        let log_metrics = logger.new(slog::o!(\"component\"=>\"metrics\"));\n        thread::spawn(move || {\n            metrics::run_metrics(metrics_config, &log_metrics)\n                .expect(\"metrics could not be run; starting ntp server failed\");\n        });\n    }\n\n    let wg = WaitGroup::new();\n    for addr in config.addrs() {\n        let addr = addr.to_socket_addrs().unwrap().next().unwrap();\n        let socket = cfsock::udp_listen(&addr)?;\n        let wg = wg.clone();\n        let logger = logger.new(slog::o!(\"listen_addr\"=>addr));\n        let keys = keys.clone();\n        let servstate = servstate.clone();\n        info!(logger, \"Listening on: {}\", socket.local_addr()?);\n        let mut use_ipv4 = true;\n        if let SocketAddr::V6(_) = addr {\n            use_ipv4 = false;\n        }\n        thread::spawn(move || {\n            run_server(socket, keys, servstate, logger, use_ipv4).expect(\"server could not be run\");\n            drop(wg);\n        });\n    }\n    wg.wait();\n    Ok(())\n}\n\n/// Compute the current dispersion to within 1 ULP.\nfn fix_dispersion(disp: u32, now: SystemTime, taken: SystemTime) -> u32 {\n    let disp_frac = (disp & 0x0000ffff) as f64;\n    let disp_secs = ((disp & 0xffff0000) >> 16) as f64;\n    let dispf = disp_secs + disp_frac / TWO_POW_16;\n    let diff = now.duration_since(taken);\n    match diff {\n        Ok(secs) => {\n            let curdispf = dispf + (secs.as_secs() as f64) * PHI;\n            let curdisp_secs = curdispf.floor() as u32;\n            let curdisp_frac = (curdispf * 65336.0).floor() as u32;\n            (curdisp_secs << 16) + curdisp_frac\n        }\n        Err(_) => disp,\n    }\n}\n\nfn ntp_timestamp(time: SystemTime) -> u64 {\n    let unix_time = time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); // Safe absent time machines\n    let unix_offset = Duration::new(UNIX_OFFSET, 0);\n    let epoch_time = unix_offset + unix_time;\n    let ts_secs = epoch_time.as_secs();\n    let ts_nanos = epoch_time.subsec_nanos() as f64;\n    let ts_frac = ((ts_nanos * TWO_POW_32) / 1.0e9).round() as u32;\n    // RFC 5905  Figure 3\n    (ts_secs << 32) + ts_frac as u64\n}\n\nfn create_header(\n    query_packet: &NtpPacket,\n    received: SystemTime,\n    transmit: SystemTime,\n    servstate: Arc<RwLock<ServerState>>,\n) -> NtpPacketHeader {\n    let servstate = servstate.read().unwrap();\n    let receive_timestamp = ntp_timestamp(received);\n    let transmit_timestamp = ntp_timestamp(transmit);\n    NtpPacketHeader {\n        leap_indicator: servstate.leap,\n        version: servstate.version,\n        mode: PacketMode::Server,\n        poll: servstate.poll,\n        precision: servstate.precision,\n        stratum: servstate.stratum,\n        root_delay: servstate.root_delay,\n        root_dispersion: fix_dispersion(servstate.root_dispersion, transmit, servstate.taken),\n        reference_id: servstate.refid,\n        reference_timestamp: servstate.refstamp,\n        origin_timestamp: query_packet.header.transmit_timestamp,\n        receive_timestamp,\n        transmit_timestamp,\n    }\n}\n\nfn response(\n    query: &[u8],\n    r_time: SystemTime,\n    t_time: SystemTime,\n    cookie_keys: Arc<RwLock<KeyRotator>>,\n    servstate: Arc<RwLock<ServerState>>,\n    logger: slog::Logger,\n) -> Result<Vec<u8>, std::io::Error> {\n    let query_packet = parse_ntp_packet(query)?; // Should try to send a KOD if this happens\n    let resp_header = create_header(&query_packet, r_time, t_time, servstate);\n\n    QUERY_COUNTER.inc();\n\n    if query_packet.header.mode != PacketMode::Client {\n        return Err(Error::new(ErrorKind::InvalidData, \"not client mode\"));\n    }\n    if is_nts_packet(&query_packet) {\n        NTS_COUNTER.inc();\n        let cookie = extract_extension(&query_packet, NTSCookie).unwrap();\n        let keyid_maybe = get_keyid(&cookie.contents);\n        match keyid_maybe {\n            Some(keyid) => {\n                let point = cookie_keys.read().unwrap();\n                let key_maybe = (*point).get(keyid);\n                match key_maybe {\n                    Some(key) => {\n                        let nts_keys = eat_cookie(&cookie.contents, key.as_ref());\n                        match nts_keys {\n                            Some(nts_dir_keys) => Ok(process_nts(\n                                resp_header,\n                                nts_dir_keys,\n                                cookie_keys.clone(),\n                                query,\n                            )),\n                            None => {\n                                UNDECRYPTABLE_COOKIE_COUNTER.inc();\n                                error!(logger, \"undecryptable cookie with keyid {:x?}\", keyid);\n                                send_kiss_of_death(query_packet)\n                            }\n                        }\n                    }\n                    None => {\n                        MISSING_KEY_COUNTER.inc();\n                        error!(logger, \"cannot access key {:x?}\", keyid);\n                        send_kiss_of_death(query_packet)\n                    }\n                }\n            }\n            None => {\n                MALFORMED_COOKIE_COUNTER.inc();\n                error!(logger, \"malformed cookie\");\n                send_kiss_of_death(query_packet)\n            }\n        }\n    } else {\n        Ok(serialize_header(resp_header))\n    }\n}\n\nfn process_nts(\n    resp_header: NtpPacketHeader,\n    keys: NTSKeys,\n    cookie_keys: Arc<RwLock<KeyRotator>>,\n    query_raw: &[u8],\n) -> Vec<u8> {\n    let mut recv_aead = Aes128SivAead::new(&keys.c2s);\n    let mut send_aead = Aes128SivAead::new(&keys.s2c);\n    let query = parse_nts_packet::<Aes128SivAead>(query_raw, &mut recv_aead);\n    match query {\n        Ok(packet) => serialize_nts_packet(\n            nts_response(packet, resp_header, keys, cookie_keys),\n            &mut send_aead,\n        ),\n        Err(_) => serialize_ntp_packet(kiss_of_death(parse_ntp_packet(query_raw).unwrap())),\n    }\n}\n\nfn nts_response(\n    query: NtsPacket,\n    header: NtpPacketHeader,\n    keys: NTSKeys,\n    cookie_keys: Arc<RwLock<KeyRotator>>,\n) -> NtsPacket {\n    let mut resp_packet = NtsPacket {\n        header,\n        auth_exts: vec![],\n        auth_enc_exts: vec![],\n    };\n    for ext in query.auth_exts {\n        match ext.ext_type {\n            protocol::NtpExtensionType::UniqueIdentifier => resp_packet.auth_exts.push(ext),\n            protocol::NtpExtensionType::NTSCookiePlaceholder => {\n                if ext.contents.len() >= COOKIE_SIZE {\n                    // Avoid amplification\n                    let keymaker = cookie_keys.read().unwrap();\n                    let (key_id, curr_key) = keymaker.latest_key_value();\n                    let cookie = make_cookie(keys, curr_key.as_ref(), key_id);\n                    resp_packet.auth_enc_exts.push(NtpExtension {\n                        ext_type: NTSCookie,\n                        contents: cookie,\n                    })\n                }\n            }\n            _ => {}\n        }\n    }\n    // This is a free cookie to replace the one consumed in the packet\n    let keymaker = cookie_keys.read().unwrap();\n    let (key_id, curr_key) = keymaker.latest_key_value();\n    let cookie = make_cookie(keys, curr_key.as_ref(), key_id);\n    resp_packet.auth_enc_exts.push(NtpExtension {\n        ext_type: NTSCookie,\n        contents: cookie,\n    });\n    resp_packet.header.transmit_timestamp = ntp_timestamp(SystemTime::now()); // Update at last possible time\n    resp_packet\n}\n\nfn send_kiss_of_death(query_packet: NtpPacket) -> Result<Vec<u8>, std::io::Error> {\n    let resp = kiss_of_death(query_packet);\n    Ok(serialize_ntp_packet(resp))\n}\n\n/// The kiss of death tells the client it has done something wrong.\n/// draft-ietf-ntp-using-nts-for-ntp-18 and RFC 5905 specify the format.\nfn kiss_of_death(query_packet: NtpPacket) -> NtpPacket {\n    KOD_COUNTER.inc();\n    let kod_header = NtpPacketHeader {\n        leap_indicator: LeapState::Unknown,\n        version: 4,\n        mode: PacketMode::Server,\n        poll: 0,\n        precision: 0,\n        stratum: 0,\n        root_delay: 0,\n        root_dispersion: 0,\n        reference_id: 0x4e54534e, // NTSN\n        reference_timestamp: 0,\n        origin_timestamp: query_packet.header.transmit_timestamp,\n        receive_timestamp: 0,\n        transmit_timestamp: 0,\n    };\n\n    let mut kod_packet = NtpPacket {\n        header: kod_header,\n        exts: vec![],\n    };\n    if has_extension(&query_packet, UniqueIdentifier) {\n        kod_packet\n            .exts\n            .push(extract_extension(&query_packet, UniqueIdentifier).unwrap());\n    }\n    kod_packet\n}\n\nfn refresh_servstate(\n    servstate: Arc<RwLock<ServerState>>,\n    logger: slog::Logger,\n    sock: std::net::UdpSocket,\n    addr: &SocketAddr,\n) {\n    loop {\n        let query_packet = NtpPacket {\n            header: NtpPacketHeader {\n                leap_indicator: LeapState::Unknown,\n                version: 4,\n                mode: PacketMode::Client,\n                poll: 0,\n                precision: 0,\n                stratum: 0,\n                root_delay: 0,\n                root_dispersion: 0,\n                reference_id: 0x0,\n                reference_timestamp: 0,\n                origin_timestamp: 0,\n                receive_timestamp: 0,\n                transmit_timestamp: 0,\n            },\n            exts: vec![],\n        };\n        sock.connect(addr)\n            .expect(\"socket connection to server failed, failed to refresh server state\");\n        sock.send(&serialize_ntp_packet(query_packet))\n            .expect(\"sending ntp packet to server failed, failed to refresh server state\");\n        UPSTREAM_QUERY_COUNTER.inc();\n        let mut buff = [0; 2048];\n        let res = sock.recv_from(&mut buff);\n        match res {\n            Ok((size, _sender)) => {\n                let response = parse_ntp_packet(&buff[0..size]);\n                match response {\n                    Ok(packet) => {\n                        let mut state = servstate.write().unwrap();\n                        state.leap = packet.header.leap_indicator;\n                        state.version = 4;\n                        state.poll = packet.header.poll;\n                        state.precision = packet.header.precision;\n                        state.stratum = packet.header.stratum;\n                        state.root_delay = packet.header.root_delay;\n                        state.root_dispersion = packet.header.root_dispersion;\n                        state.refid = packet.header.reference_id;\n                        state.refstamp = packet.header.reference_timestamp;\n                        state.taken = SystemTime::now();\n                        info!(logger, \"set server state with stratum {:}\", state.stratum);\n                    }\n                    Err(err) => {\n                        UPSTREAM_FAILURE_COUNTER.inc();\n                        error!(logger, \"failure to parse response: {}\", err);\n                    }\n                }\n            }\n            Err(err) => {\n                UPSTREAM_FAILURE_COUNTER.inc();\n                error!(logger, \"read error: {}\", err);\n            }\n        }\n        thread::sleep(time::Duration::from_secs(1));\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/client.rs",
    "content": "use slog::{debug, info};\nuse std::error::Error;\nuse std::io::{Read, Write};\nuse std::net::{Shutdown, TcpStream, ToSocketAddrs};\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse rustls;\nuse webpki;\nuse webpki_roots;\n\nuse super::records;\n\nuse crate::cookie::NTSKeys;\nuse crate::nts_ke::records::{\n    deserialize,\n    process_record,\n\n    // Functions.\n    serialize,\n    // Records.\n    AeadAlgorithmRecord,\n    // Errors.\n    DeserializeError,\n\n    EndOfMessageRecord,\n\n    // Enums.\n    KnownAeadAlgorithm,\n    KnownNextProtocol,\n    NextProtocolRecord,\n    NtsKeParseError,\n    Party,\n\n    // Structs.\n    ReceivedNtsKeRecordState,\n\n    // Constants.\n    HEADER_SIZE,\n};\nuse crate::sub_command::client::ClientConfig;\n\ntype Cookie = Vec<u8>;\n\nconst DEFAULT_NTP_PORT: u16 = 123;\nconst DEFAULT_KE_PORT: u16 = 4460;\nconst DEFAULT_SCHEME: u16 = 0;\nconst TIMEOUT: Duration = Duration::from_secs(15);\n\n#[derive(Clone, Debug)]\npub struct NtsKeResult {\n    pub cookies: Vec<Cookie>,\n    pub next_protocols: Vec<u16>,\n    pub aead_scheme: u16,\n    pub next_server: String,\n    pub next_port: u16,\n    pub keys: NTSKeys,\n    pub use_ipv4: Option<bool>,\n}\n\n/// run_nts_client executes the nts client with the config in config file\npub fn run_nts_ke_client(\n    logger: &slog::Logger,\n    client_config: ClientConfig,\n) -> Result<NtsKeResult, Box<dyn Error>> {\n    let mut tls_config = rustls::ClientConfig::new();\n    let alpn_proto = String::from(\"ntske/1\");\n    let alpn_bytes = alpn_proto.into_bytes();\n    tls_config.set_protocols(&[alpn_bytes]);\n\n    match client_config.trusted_cert {\n        Some(cert) => {\n            info!(logger, \"loading custom trust root\");\n            tls_config.root_store.add(&cert)?;\n        }\n        None => {\n            tls_config\n                .root_store\n                .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);\n        }\n    }\n\n    let rc_config = Arc::new(tls_config);\n    let hostname = webpki::DNSNameRef::try_from_ascii_str(client_config.host.as_str())\n        .expect(\"server hostname is invalid\");\n    let mut client = rustls::ClientSession::new(&rc_config, hostname);\n    debug!(logger, \"Connecting\");\n    let mut port = DEFAULT_KE_PORT;\n    if let Some(p) = client_config.port {\n        port = p.parse::<u16>()?;\n    }\n\n    let mut ip_addrs = (client_config.host.as_str(), port).to_socket_addrs()?;\n    let addr;\n    if let Some(use_ipv4) = client_config.use_ipv4 {\n        if use_ipv4 {\n            // mandated to use ipv4\n            addr = ip_addrs.find(|&x| x.is_ipv4());\n            if addr.is_none() {\n                return Err(Box::new(NtsKeParseError::NoIpv4AddrFound));\n            }\n        } else {\n            // mandated to use ipv6\n            addr = ip_addrs.find(|&x| x.is_ipv6());\n            if addr.is_none() {\n                return Err(Box::new(NtsKeParseError::NoIpv6AddrFound));\n            }\n        }\n    } else {\n        // sniff whichever one is supported\n        addr = ip_addrs.next();\n    }\n    let mut stream = TcpStream::connect_timeout(&addr.unwrap(), TIMEOUT)?;\n    stream.set_read_timeout(Some(TIMEOUT))?;\n    stream.set_write_timeout(Some(TIMEOUT))?;\n\n    let mut tls_stream = rustls::Stream::new(&mut client, &mut stream);\n\n    let next_protocol_record = NextProtocolRecord::from(vec![KnownNextProtocol::Ntpv4]);\n    let aead_record = AeadAlgorithmRecord::from(vec![KnownAeadAlgorithm::AeadAesSivCmac256]);\n    let end_record = EndOfMessageRecord;\n\n    let clientrec = &mut serialize(next_protocol_record);\n    clientrec.append(&mut serialize(aead_record));\n    clientrec.append(&mut serialize(end_record));\n    tls_stream.write_all(clientrec)?;\n    tls_stream.flush()?;\n    debug!(logger, \"Request transmitted\");\n    let keys = records::gen_key(tls_stream.sess).unwrap();\n\n    let mut state = ReceivedNtsKeRecordState {\n        finished: false,\n        next_protocols: Vec::new(),\n        aead_scheme: Vec::new(),\n        cookies: Vec::new(),\n        next_server: None,\n        next_port: None,\n    };\n\n    while !state.finished {\n        let mut header: [u8; HEADER_SIZE] = [0; HEADER_SIZE];\n\n        // We should use `read_exact` here because we always need to read 4 bytes to get the\n        // header.\n        if let Err(error) = tls_stream.read_exact(&mut header[..]) {\n            return Err(Box::new(error));\n        }\n\n        // Retrieve a body length from the 3rd and 4th bytes of the header.\n        let body_length = u16::from_be_bytes([header[2], header[3]]);\n        let mut body = vec![0; body_length as usize];\n\n        // `read_exact` the length of the body.\n        if let Err(error) = tls_stream.read_exact(body.as_mut_slice()) {\n            return Err(Box::new(error));\n        }\n\n        // Reconstruct the whole record byte array to let the `records` module deserialize it.\n        let mut record_bytes = Vec::from(&header[..]);\n        record_bytes.append(&mut body);\n\n        // `deserialize` has an invariant that the slice needs to be long enough to make it a\n        // valid record, which in this case our slice is exactly as long as specified in the\n        // length field.\n        match deserialize(Party::Client, record_bytes.as_slice()) {\n            Ok(record) => {\n                let status = process_record(record, &mut state);\n                match status {\n                    Ok(_) => {}\n                    Err(err) => {\n                        return Err(err);\n                    }\n                }\n            }\n            Err(DeserializeError::UnknownNotCriticalRecord) => {\n                // If it's not critical, just ignore the error.\n                debug!(logger, \"unknown record type\");\n            }\n            Err(DeserializeError::UnknownCriticalRecord) => {\n                // TODO: This should propertly handled by sending an Error record.\n                debug!(logger, \"error: unknown critical record\");\n                return Err(Box::new(std::io::Error::new(\n                    std::io::ErrorKind::Other,\n                    \"unknown critical record\",\n                )));\n            }\n            Err(DeserializeError::Parsing(error)) => {\n                // TODO: This shouldn't be wrapped as a trait object.\n                debug!(logger, \"error: {}\", error);\n                return Err(Box::new(std::io::Error::new(\n                    std::io::ErrorKind::Other,\n                    error,\n                )));\n            }\n        }\n    }\n    debug!(logger, \"saw the end of the response\");\n    stream.shutdown(Shutdown::Write)?;\n\n    let aead_scheme = if state.aead_scheme.is_empty() {\n        DEFAULT_SCHEME\n    } else {\n        state.aead_scheme[0]\n    };\n\n    Ok(NtsKeResult {\n        aead_scheme,\n        cookies: state.cookies,\n        next_protocols: state.next_protocols,\n        next_server: state.next_server.unwrap_or(client_config.host.clone()),\n        next_port: state.next_port.unwrap_or(DEFAULT_NTP_PORT),\n        keys,\n        use_ipv4: client_config.use_ipv4,\n    })\n}\n"
  },
  {
    "path": "src/nts_ke/mod.rs",
    "content": "pub mod client;\npub mod records;\npub mod server;\n"
  },
  {
    "path": "src/nts_ke/records/aead_algorithm.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! AEAD Algorithm Negotiation record representation.\n\nuse std::convert::TryFrom;\n\nuse super::KeRecordTrait;\nuse super::Party;\n\n#[derive(Clone, Copy)]\npub enum KnownAeadAlgorithm {\n    AeadAesSivCmac256,\n}\n\nimpl KnownAeadAlgorithm {\n    pub fn as_algorithm_id(&self) -> u16 {\n        match self {\n            KnownAeadAlgorithm::AeadAesSivCmac256 => 15,\n        }\n    }\n}\n\npub struct AeadAlgorithmRecord(Vec<KnownAeadAlgorithm>);\n\nimpl AeadAlgorithmRecord {\n    pub fn algorithms(&self) -> &[KnownAeadAlgorithm] {\n        self.0.as_slice()\n    }\n}\n\nimpl From<Vec<KnownAeadAlgorithm>> for AeadAlgorithmRecord {\n    fn from(algorithms: Vec<KnownAeadAlgorithm>) -> AeadAlgorithmRecord {\n        AeadAlgorithmRecord(algorithms)\n    }\n}\n\nimpl KeRecordTrait for AeadAlgorithmRecord {\n    fn critical(&self) -> bool {\n        // According to the spec, this critical bit is optional, but it's good to assign it as\n        // critical.\n        true\n    }\n\n    fn record_type() -> u16 {\n        4\n    }\n\n    fn len(&self) -> u16 {\n        // Because each protocol takes 2 bytes, we need to multiply it by 2.\n        u16::try_from(self.0.len())\n            .ok()\n            .and_then(|length| length.checked_mul(2))\n            .expect(\"the number of AEAD algorithms are too large\")\n    }\n\n    fn into_bytes(self) -> Vec<u8> {\n        let mut bytes = Vec::new();\n        for algorithm in self.0.iter() {\n            // The spec said that the protocol id must be in network byte order, so we have to\n            // convert it to the big endian order here.\n            let algorithm_bytes = &algorithm.as_algorithm_id().to_be_bytes()[..];\n\n            bytes.append(&mut Vec::from(algorithm_bytes))\n        }\n\n        bytes\n    }\n\n    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {\n        // The body length must be even because each algorithm code take 2 bytes, so it's not\n        // reasonable for the length to be odd.\n        if bytes.len() % 2 != 0 {\n            return Err(String::from(\n                \"the body length of AEAD Algorithm Negotiation\n                                     must be even.\",\n            ));\n        }\n\n        let mut algorithms = Vec::new();\n\n        for word in bytes.chunks_exact(2) {\n            let algorithm_code = u16::from_be_bytes([word[0], word[1]]);\n\n            let algorithm = KnownAeadAlgorithm::AeadAesSivCmac256;\n            if algorithm.as_algorithm_id() == algorithm_code {\n                algorithms.push(algorithm);\n            } else {\n                return Err(String::from(\"unknown AEAD algorithm id\"));\n            }\n        }\n\n        Ok(AeadAlgorithmRecord(algorithms))\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/records/end_of_message.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! End Of Message record representation.\n\nuse super::KeRecordTrait;\nuse super::Party;\n\npub struct EndOfMessageRecord;\n\nimpl KeRecordTrait for EndOfMessageRecord {\n    fn critical(&self) -> bool {\n        true\n    }\n\n    fn record_type() -> u16 {\n        0\n    }\n\n    fn len(&self) -> u16 {\n        0\n    }\n\n    fn into_bytes(self) -> Vec<u8> {\n        Vec::new()\n    }\n\n    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {\n        if !bytes.is_empty() {\n            Err(String::from(\n                \"the body length of End Of Message must be zero.\",\n            ))\n        } else {\n            Ok(EndOfMessageRecord)\n        }\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/records/error.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! Error record representation.\n\nuse super::KeRecordTrait;\nuse super::Party;\n\nenum ErrorKind {\n    UnrecognizedCriticalRecord,\n    BadRequest,\n}\n\nimpl ErrorKind {\n    fn as_code(&self) -> u16 {\n        match self {\n            ErrorKind::UnrecognizedCriticalRecord => 0,\n            ErrorKind::BadRequest => 1,\n        }\n    }\n}\n\npub struct ErrorRecord(ErrorKind);\n\nimpl KeRecordTrait for ErrorRecord {\n    fn critical(&self) -> bool {\n        true\n    }\n\n    fn record_type() -> u16 {\n        2\n    }\n\n    fn len(&self) -> u16 {\n        2\n    }\n\n    fn into_bytes(self) -> Vec<u8> {\n        let error_code = &self.0.as_code().to_be_bytes()[..];\n        Vec::from(error_code)\n    }\n\n    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {\n        if bytes.len() != 2 {\n            return Err(String::from(\"the body length of Error must be two.\"));\n        }\n\n        let error_code = u16::from_be_bytes([bytes[0], bytes[1]]);\n\n        let kind = ErrorKind::UnrecognizedCriticalRecord;\n        if kind.as_code() == error_code {\n            return Ok(ErrorRecord(kind));\n        }\n\n        let kind = ErrorKind::BadRequest;\n        if kind.as_code() == error_code {\n            return Ok(ErrorRecord(kind));\n        }\n\n        Err(String::from(\"unknown error code\"))\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/records/mod.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! NTS-KE record representation.\n\nmod aead_algorithm;\nmod end_of_message;\nmod error;\nmod new_cookie;\nmod next_protocol;\nmod port;\nmod server;\nmod warning;\n\n// We pub use everything in the submodules. You can limit the scope of usage by putting it the\n// submodule itself.\npub use self::aead_algorithm::*;\npub use self::end_of_message::*;\npub use self::error::*;\npub use self::new_cookie::*;\npub use self::next_protocol::*;\npub use self::port::*;\npub use self::server::*;\npub use self::warning::*;\n\nuse rustls::TLSError;\nuse std::fmt;\n\nuse crate::cookie::NTSKeys;\n\npub const HEADER_SIZE: usize = 4;\n\npub enum KeRecord {\n    EndOfMessage(EndOfMessageRecord),\n    NextProtocol(NextProtocolRecord),\n    Error(ErrorRecord),\n    Warning(WarningRecord),\n    AeadAlgorithm(AeadAlgorithmRecord),\n    NewCookie(NewCookieRecord),\n    Server(ServerRecord),\n    Port(PortRecord),\n}\n\n#[derive(Clone, Copy)]\npub enum Party {\n    Client,\n    Server,\n}\n\npub trait KeRecordTrait: Sized {\n    fn critical(&self) -> bool;\n\n    fn record_type() -> u16;\n\n    fn len(&self) -> u16;\n\n    // This function has to consume the object to avoid additional memory consumption.\n    fn into_bytes(self) -> Vec<u8>;\n\n    fn from_bytes(sender: Party, bytes: &[u8]) -> Result<Self, String>;\n}\n\n// ------------------------------------------------------------------------\n// Serialization\n// ------------------------------------------------------------------------\n\n/// Serialize the record into the network-ready format.\npub fn serialize<T: KeRecordTrait>(record: T) -> Vec<u8> {\n    let mut result = Vec::new();\n\n    // The first 16 bits will comprise a critical bit and the record type.\n    let first_word: u16 = (u16::from(record.critical()) << 15) + T::record_type();\n    result.append(&mut Vec::from(&first_word.to_be_bytes()[..]));\n\n    // The second 16 bits will be the length of the record body.\n    result.append(&mut Vec::from(&record.len().to_be_bytes()[..]));\n\n    // The rest is the content of the record.\n    result.append(&mut record.into_bytes());\n\n    result\n}\n\n// ------------------------------------------------------------------------\n// Deserialization\n// ------------------------------------------------------------------------\n\n#[derive(Clone, Debug)]\npub enum DeserializeError {\n    Parsing(String),\n    UnknownCriticalRecord,\n    UnknownNotCriticalRecord,\n}\n\n/// Deserialize the network bytes into the record.\n///\n/// # Panics\n///\n/// If slice is shorter than the length specified in the length field.\n///\npub fn deserialize(sender: Party, bytes: &[u8]) -> Result<KeRecord, DeserializeError> {\n    // The first bit of the first byte is the critical bit.\n    let critical = bytes[0] >> 7 == 1;\n\n    // The following 15 bits are the record type number.\n    let record_type = u16::from_be_bytes([bytes[0] & 0x7, bytes[1]]);\n\n    // The third and fourth bytes are the body length.\n    let length = u16::from_be_bytes([bytes[2], bytes[3]]);\n\n    // The body.\n    let body = &bytes[4..4 + usize::from(length)];\n\n    macro_rules! deserialize_body {\n        ( $( ($variant:ident, $record:ident) ),* ) => {\n            if false {\n                // Loop returns ! type.\n                loop { }\n            } $( else if record_type == $record::record_type() {\n                match $record::from_bytes(sender, body) {\n                    Ok(record) => KeRecord::$variant(record),\n                    Err(error) => return Err(DeserializeError::Parsing(error)),\n                }\n            } )* else {\n                if critical {\n                    return Err(DeserializeError::UnknownCriticalRecord);\n                } else {\n                    return Err(DeserializeError::UnknownNotCriticalRecord);\n                }\n            }\n        };\n    }\n\n    let record = deserialize_body!(\n        (EndOfMessage, EndOfMessageRecord),\n        (NextProtocol, NextProtocolRecord),\n        (Error, ErrorRecord),\n        (Warning, WarningRecord),\n        (AeadAlgorithm, AeadAlgorithmRecord),\n        (NewCookie, NewCookieRecord),\n        (Server, ServerRecord),\n        (Port, PortRecord)\n    );\n\n    Ok(record)\n}\n\n/// gen_key computes the client and server keys using exporters.\n/// https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-28#section-4.3\npub fn gen_key<T: rustls::Session>(session: &T) -> Result<NTSKeys, TLSError> {\n    let mut keys: NTSKeys = NTSKeys {\n        c2s: [0; 32],\n        s2c: [0; 32],\n    };\n    let c2s_con = [0, 0, 0, 15, 0];\n    let s2c_con = [0, 0, 0, 15, 1];\n    let context_c2s = Some(&c2s_con[..]);\n    let context_s2c = Some(&s2c_con[..]);\n    let label = \"EXPORTER-network-time-security\".as_bytes();\n    session.export_keying_material(&mut keys.c2s, label, context_c2s)?;\n    session.export_keying_material(&mut keys.s2c, label, context_s2c)?;\n\n    Ok(keys)\n}\n\n// ------------------------------------------------------------------------\n// Record Process\n// ------------------------------------------------------------------------\n\ntype Cookie = Vec<u8>;\n\n#[derive(Clone, Debug)]\npub struct ReceivedNtsKeRecordState {\n    pub finished: bool,\n    pub next_protocols: Vec<u16>,\n    pub aead_scheme: Vec<u16>,\n    pub cookies: Vec<Cookie>,\n    pub next_server: Option<String>,\n    pub next_port: Option<u16>,\n}\n\n#[derive(Debug, Clone)]\npub enum NtsKeParseError {\n    RecordAfterEnd,\n    ErrorRecord,\n    NoIpv4AddrFound,\n    NoIpv6AddrFound,\n}\n\nimpl std::error::Error for NtsKeParseError {\n    fn description(&self) -> &str {\n        match self {\n            Self::RecordAfterEnd => \"Received record after connection finished\",\n            Self::ErrorRecord => \"Received NTS error record\",\n            Self::NoIpv4AddrFound => {\n                \"Connection to server failed: IPv4 address could not be resolved\"\n            }\n            Self::NoIpv6AddrFound => {\n                \"Connection to server failed: IPv6 address could not be resolved\"\n            }\n        }\n    }\n    fn cause(&self) -> Option<&dyn std::error::Error> {\n        None\n    }\n}\n\nimpl fmt::Display for NtsKeParseError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"NTS-KE Record Parse Error\")\n    }\n}\n\n/// Read https://datatracker.ietf.org/doc/html/rfc8915#section-4\npub fn process_record(\n    record: KeRecord,\n    state: &mut ReceivedNtsKeRecordState,\n) -> Result<(), Box<dyn std::error::Error>> {\n    if state.finished {\n        return Err(Box::new(NtsKeParseError::RecordAfterEnd));\n    }\n\n    match record {\n        KeRecord::EndOfMessage(_) => state.finished = true,\n        KeRecord::NextProtocol(record) => {\n            state.next_protocols = record\n                .protocols()\n                .iter()\n                .map(|protocol| protocol.as_protocol_id())\n                .collect();\n        }\n        KeRecord::Error(_) => return Err(Box::new(NtsKeParseError::ErrorRecord)),\n        KeRecord::Warning(_) => return Ok(()),\n        KeRecord::AeadAlgorithm(record) => {\n            state.aead_scheme = record\n                .algorithms()\n                .iter()\n                .map(|algorithm| algorithm.as_algorithm_id())\n                .collect();\n        }\n        KeRecord::NewCookie(record) => state.cookies.push(record.into_bytes()),\n        KeRecord::Server(record) => state.next_server = Some(record.into_string()),\n        KeRecord::Port(record) => state.next_port = Some(record.port()),\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/nts_ke/records/new_cookie.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! New Cookie record representation.\n\nuse std::convert::TryFrom;\n\nuse super::KeRecordTrait;\nuse super::Party;\n\npub struct NewCookieRecord(Vec<u8>);\n\nimpl From<Vec<u8>> for NewCookieRecord {\n    fn from(bytes: Vec<u8>) -> NewCookieRecord {\n        NewCookieRecord(bytes)\n    }\n}\n\nimpl KeRecordTrait for NewCookieRecord {\n    fn critical(&self) -> bool {\n        false\n    }\n\n    fn record_type() -> u16 {\n        5\n    }\n\n    fn len(&self) -> u16 {\n        u16::try_from(self.0.len()).expect(\"the cookie is too large to fit in the record\")\n    }\n\n    fn into_bytes(self) -> Vec<u8> {\n        self.0\n    }\n\n    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {\n        // There is error for New Cookie record, because any byte slice is considered a valid\n        // cookie.\n        Ok(NewCookieRecord::from(Vec::from(bytes)))\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/records/next_protocol.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! NTS Next Protocol Negotiation record representation.\n\nuse std::convert::TryFrom;\n\nuse super::KeRecordTrait;\nuse super::Party;\n\n#[derive(Clone, Copy)]\npub enum KnownNextProtocol {\n    Ntpv4,\n}\n\nimpl KnownNextProtocol {\n    pub fn as_protocol_id(&self) -> u16 {\n        match self {\n            KnownNextProtocol::Ntpv4 => 0,\n        }\n    }\n}\n\npub struct NextProtocolRecord(Vec<KnownNextProtocol>);\n\nimpl NextProtocolRecord {\n    pub fn protocols(&self) -> &[KnownNextProtocol] {\n        self.0.as_slice()\n    }\n}\n\nimpl From<Vec<KnownNextProtocol>> for NextProtocolRecord {\n    fn from(protocols: Vec<KnownNextProtocol>) -> NextProtocolRecord {\n        NextProtocolRecord(protocols)\n    }\n}\n\nimpl KeRecordTrait for NextProtocolRecord {\n    fn critical(&self) -> bool {\n        true\n    }\n\n    fn record_type() -> u16 {\n        1\n    }\n\n    fn len(&self) -> u16 {\n        // Because each protocol takes 2 bytes, we need to multiply it by 2.\n        u16::try_from(self.0.len())\n            .ok()\n            .and_then(|length| length.checked_mul(2))\n            .expect(\"the number of next protocols are too large\")\n    }\n\n    fn into_bytes(self) -> Vec<u8> {\n        let mut bytes = Vec::new();\n        for protocol in self.0.iter() {\n            // The spec said that the protocol id must be in network byte order, so we have to\n            // convert it to the big endian order here.\n            let protocol_bytes = &protocol.as_protocol_id().to_be_bytes()[..];\n\n            bytes.append(&mut Vec::from(protocol_bytes))\n        }\n\n        bytes\n    }\n\n    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {\n        // The body length must be even because each protocol code take 2 bytes, so it's not\n        // reasonable for the length to be odd.\n        if bytes.len() % 2 != 0 {\n            return Err(String::from(\n                \"the body length of Next Protocol Negotiation\n                                     must be even.\",\n            ));\n        }\n\n        let mut protocols = Vec::new();\n\n        for word in bytes.chunks_exact(2) {\n            let protocol_code = u16::from_be_bytes([word[0], word[1]]);\n\n            let protocol = KnownNextProtocol::Ntpv4;\n            if protocol.as_protocol_id() == protocol_code {\n                protocols.push(protocol);\n            } else {\n                return Err(String::from(\"unknown Next Protocol id\"));\n            }\n        }\n\n        Ok(NextProtocolRecord(protocols))\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/records/port.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! Port negotiation record representation.\n/// This Port negotiation will not be sent from the server because currently, we are not\n/// interested in running an NTP server on different port.\nuse super::KeRecordTrait;\nuse super::Party;\n\npub struct PortRecord {\n    sender: Party,\n    port: u16,\n}\n\nimpl PortRecord {\n    pub fn new(sender: Party, port: u16) -> PortRecord {\n        PortRecord { sender, port }\n    }\n\n    pub fn port(&self) -> u16 {\n        self.port\n    }\n}\n\nimpl KeRecordTrait for PortRecord {\n    fn critical(&self) -> bool {\n        match self.sender {\n            Party::Client => false,\n            Party::Server => true,\n        }\n    }\n\n    fn record_type() -> u16 {\n        7\n    }\n\n    fn len(&self) -> u16 {\n        2\n    }\n\n    fn into_bytes(self) -> Vec<u8> {\n        Vec::from(&self.port.to_be_bytes()[..])\n    }\n\n    fn from_bytes(sender: Party, bytes: &[u8]) -> Result<Self, String> {\n        if bytes.len() != 2 {\n            Err(String::from(\"the body length of Port must be two.\"))\n        } else {\n            let port = u16::from_be_bytes([bytes[0], bytes[1]]);\n\n            Ok(PortRecord { sender, port })\n        }\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/records/server.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! Server negotiation record representation.\n/// This Server negotiation will not be sent from the server because currently, we are not\n/// interested in running an NTP server on different IP address.\nuse std::convert::TryFrom;\nuse std::net::Ipv4Addr;\nuse std::net::Ipv6Addr;\nuse std::str::FromStr;\n\nuse super::KeRecordTrait;\nuse super::Party;\n\nenum Address {\n    Hostname(String),\n    Ipv4Addr(Ipv4Addr),\n    Ipv6Addr(Ipv6Addr),\n}\n\npub struct ServerRecord {\n    sender: Party,\n    address: Address,\n}\n\nimpl ServerRecord {\n    pub fn into_string(self) -> String {\n        match self.address {\n            Address::Hostname(name) => name,\n            Address::Ipv4Addr(addr) => addr.to_string(),\n            Address::Ipv6Addr(addr) => addr.to_string(),\n        }\n    }\n}\n\nimpl KeRecordTrait for ServerRecord {\n    fn critical(&self) -> bool {\n        match self.sender {\n            Party::Client => false,\n            Party::Server => true,\n        }\n    }\n\n    fn record_type() -> u16 {\n        6\n    }\n\n    fn len(&self) -> u16 {\n        match &self.address {\n            // We cannot just use `name.len()` because we want to count the bytes not just the\n            // runes.\n            Address::Hostname(name) => u16::try_from(name.as_bytes().len())\n                .expect(\"the hostname is too long to fix in the record\"),\n            // Both IPv4 and IPv6 address cannot be too long to fix in the record. It's okay to\n            // just cast them here.\n            Address::Ipv4Addr(addr) => addr.to_string().len() as u16,\n            Address::Ipv6Addr(addr) => addr.to_string().len() as u16,\n        }\n    }\n\n    fn into_bytes(self) -> Vec<u8> {\n        Vec::from(self.into_string())\n    }\n\n    fn from_bytes(sender: Party, bytes: &[u8]) -> Result<Self, String> {\n        let body = match String::from_utf8(Vec::from(bytes)) {\n            Ok(body) => body,\n            Err(_) => return Err(String::from(\"the body is an invalid ascii string\")),\n        };\n\n        if !body.is_ascii() {\n            return Err(String::from(\"the body is an invalid ascii string\"));\n        }\n\n        let address = if let Ok(address) = Ipv4Addr::from_str(&body) {\n            Address::Ipv4Addr(address)\n        } else if let Ok(address) = Ipv6Addr::from_str(&body) {\n            Address::Ipv6Addr(address)\n        } else {\n            // If the body is a valid ascii string, but not a valid IPv4 or IPv6, it must be a\n            // hostname.\n            Address::Hostname(body)\n        };\n\n        Ok(ServerRecord { sender, address })\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/records/warning.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! Warning record representation.\n\nuse super::KeRecordTrait;\nuse super::Party;\n\nenum WarningKind {\n    // There is currently no warning specified in the spec, but we need to put something here to\n    // make the code compiles. Please remove this Dummy when there is a warning specified in the\n    // spec.\n    Dummy,\n}\n\nimpl WarningKind {\n    fn as_code(&self) -> u16 {\n        match self {\n            // Put the max value for Dummy just to avoid colliding with the future warning code.\n            WarningKind::Dummy => u16::max_value(),\n        }\n    }\n}\n\npub struct WarningRecord(WarningKind);\n\nimpl KeRecordTrait for WarningRecord {\n    fn critical(&self) -> bool {\n        true\n    }\n\n    fn record_type() -> u16 {\n        3\n    }\n\n    fn len(&self) -> u16 {\n        2\n    }\n\n    fn into_bytes(self) -> Vec<u8> {\n        let error_code = &self.0.as_code().to_be_bytes()[..];\n        Vec::from(error_code)\n    }\n\n    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {\n        if bytes.len() != 2 {\n            return Err(String::from(\"the body length of Warning must be two.\"));\n        }\n\n        let warning_code = u16::from_be_bytes([bytes[0], bytes[1]]);\n\n        let kind = WarningKind::Dummy;\n        if kind.as_code() == warning_code {\n            return Ok(WarningRecord(kind));\n        }\n\n        Err(String::from(\"unknown warning code\"))\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/server/config.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! NTS-KE server configuration.\n\nuse rustls::internal::pemfile;\nuse rustls::{Certificate, PrivateKey};\n\nuse sloggers::terminal::TerminalLoggerBuilder;\nuse sloggers::Build;\n\nuse std::convert::TryFrom;\nuse std::fs::File;\nuse std::net::SocketAddr;\n\nuse crate::cookie::CookieKey;\nuse crate::error::WrapError;\nuse crate::metrics::MetricsConfig;\n\nfn get_metrics_config(settings: &config::Config) -> Option<MetricsConfig> {\n    let mut metrics = None;\n    if let Ok(addr) = settings.get_str(\"metrics_addr\") {\n        if let Ok(port) = settings.get_int(\"metrics_port\") {\n            metrics = Some(MetricsConfig {\n                port: port as u16,\n                addr,\n            });\n        }\n    }\n    metrics\n}\n\n/// Configuration for running an NTS-KE server.\n#[derive(Debug)]\npub struct KeServerConfig {\n    /// List of addresses and ports to the server will be listening to.\n    // Each of the elements can be either IPv4 or IPv6 address. It cannot be a UNIX socket address.\n    addrs: Vec<SocketAddr>,\n\n    /// The initial cookie key for the NTS-KE server.\n    cookie_key: CookieKey,\n\n    // If you don't to have a timeout, just set it to a very high value.\n    timeout: u64,\n\n    /// The logger that will be used throughout the application, while the server is running.\n    /// This property is mandatory because logging is very important for debugging.\n    logger: slog::Logger,\n\n    /// The url of the memcached server. The memcached server is used to sync data between the\n    /// NTS-KE server and the NTP server.\n    memcached_url: String,\n\n    pub metrics_config: Option<MetricsConfig>,\n    pub next_port: u16,\n    pub tls_certs: Vec<Certificate>,\n    pub tls_secret_keys: Vec<PrivateKey>,\n}\n\n/// We decided to make KeServerConfig mutable so that you can add more cert, private key, or\n/// address after you parse the config file.\nimpl KeServerConfig {\n    /// Create a NTS-KE server config object with the given next port, memcached url, connection\n    /// timeout, and the metrics config.\n    pub fn new(\n        timeout: u64,\n        cookie_key: CookieKey,\n        memcached_url: String,\n        metrics_config: Option<MetricsConfig>,\n        next_port: u16,\n    ) -> KeServerConfig {\n        KeServerConfig {\n            addrs: Vec::new(),\n\n            // Use terminal logger as a default logger. The users can override it using\n            // `set_logger` later, if they want.\n            //\n            // According to `sloggers-0.3.2` source code, the function doesn't return an error at\n            // all. There should be no problem unwrapping here.\n            logger: TerminalLoggerBuilder::new()\n                .build()\n                .expect(\"BUG: TerminalLoggerBuilder::build shouldn't return an error.\"),\n\n            tls_certs: Vec::new(),\n            tls_secret_keys: Vec::new(),\n\n            // From parameters.\n            cookie_key,\n            timeout,\n            memcached_url,\n            metrics_config,\n            next_port,\n        }\n    }\n\n    /// Add a TLS certificate into the config.\n    // Because the order of `tls_certs` has to correspond to the order of `tls_secret_keys`, this\n    // method has to be private for now.\n    fn add_tls_cert(&mut self, cert: Certificate) {\n        self.tls_certs.push(cert);\n    }\n\n    /// Add a TLS private key into the config.\n    // Because the order of `tls_certs` has to correspond to the order of `tls_secret_keys`, this\n    // method has to be private for now.\n    fn add_tls_secret_key(&mut self, secret_key: PrivateKey) {\n        self.tls_secret_keys.push(secret_key);\n    }\n\n    /// Add an address into the config.\n    pub fn add_address(&mut self, addr: SocketAddr) {\n        self.addrs.push(addr);\n    }\n\n    /// Return a list of addresses.\n    pub fn addrs(&self) -> &[SocketAddr] {\n        self.addrs.as_slice()\n    }\n\n    /// Return the cookie key of the config.\n    pub fn cookie_key(&self) -> &CookieKey {\n        &self.cookie_key\n    }\n\n    /// Set a new logger to the config.\n    pub fn set_logger(&mut self, logger: slog::Logger) {\n        self.logger = logger;\n    }\n\n    /// Return the logger of the config.\n    pub fn logger(&self) -> &slog::Logger {\n        &self.logger\n    }\n\n    /// Return the memcached url of the config.\n    pub fn memcached_url(&self) -> &str {\n        &self.memcached_url\n    }\n\n    /// Return the connection timeout of the config.\n    pub fn timeout(&self) -> u64 {\n        self.timeout\n    }\n\n    /// Import TLS certificates from a file.\n    ///\n    /// # Errors\n    ///\n    /// There will be an error if we cannot open the file or the content is not parsable to get\n    /// certificates.\n    ///\n    // Because the order of `tls_certs` has to correspond to the order of `tls_secret_keys`, this\n    // method has to be private for now.\n    fn import_tls_certs(&mut self, filename: &str) -> Result<(), std::io::Error> {\n        // Open a file. If there is any error, return it immediately.\n        let file = File::open(filename)?;\n\n        match pemfile::certs(&mut std::io::BufReader::new(file)) {\n            Ok(certs) => {\n                // Add all parsed certificates.\n                for cert in certs {\n                    self.add_tls_cert(cert);\n                }\n                // Return success.\n                Ok(())\n            }\n            // We don't use Err(_) here because if the error type of `rustls` changes in the\n            // future, we will get noticed.\n            //\n            // The `std::io` module has an error kind of `InvalidData` which is perfectly\n            // suitable for our kind of error.\n            Err(()) => Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidData,\n                format!(\"cannot parse TLS certificates from {}\", filename),\n            )),\n        }\n    }\n\n    /// Import TLS private keys from a file.\n    ///\n    /// # Errors\n    ///\n    /// There will be an error if we cannot open the file or the content is not parsable to get\n    /// private keys.\n    ///\n    // Because the order of `tls_certs` has to correspond to the order of `tls_secret_keys`, this\n    // method has to be private for now.\n    fn import_tls_secret_keys(&mut self, filename: &str) -> Result<(), std::io::Error> {\n        // Open a file. If there is any error, return it immediately.\n        let file = File::open(filename)?;\n\n        match pemfile::pkcs8_private_keys(&mut std::io::BufReader::new(file)) {\n            Ok(secret_keys) => {\n                // Add all parsed secret keys.\n                for secret_key in secret_keys {\n                    self.add_tls_secret_key(secret_key);\n                }\n                // Return success.\n                Ok(())\n            }\n            // We don't use Err(_) here because if the error type of `rustls` changes in the\n            // future, we will get noticed.\n            //\n            // The `std::io` module has an error kind of `InvalidData` which is perfectly\n            // suitable for our kind of error.\n            Err(()) => Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidData,\n                format!(\"cannot parse TLS private keys from {}\", filename),\n            )),\n        }\n    }\n\n    /// Parse a config from a file.\n    ///\n    /// # Errors\n    ///\n    /// Currently we return `config::ConfigError` which is returned from functions in the\n    /// `config` crate itself.\n    ///\n    /// For any error from any file specified in the configuration, `std::io::Error` which is\n    /// wrapped inside `config::ConfigError::Foreign` will be returned.\n    ///\n    /// For any address parsing error, `std::io::Error` wrapped inside\n    /// `config::ConfigError::Foreign` will also be returned.\n    ///\n    /// In addition, it also returns some custom `config::ConfigError::Message` errors, for the\n    /// following cases:\n    ///\n    /// * The next port in the configuration file is a valid `i64` but not a valid `u16`.\n    /// * The connection timeout in the configuration file is a valid `i64` but not a valid `u64`.\n    ///\n    // Returning a `Message` object here is not a good practice. I will figure out a good practice\n    // later.\n    pub fn parse(filename: &str) -> Result<KeServerConfig, config::ConfigError> {\n        let mut settings = config::Config::new();\n        settings.merge(config::File::with_name(filename))?;\n\n        // XXX: The code of parsing a next port here is quite ugly due to the `get_int` interface.\n        // Please don't be surprised :)\n        let next_port = match u16::try_from(settings.get_int(\"next_port\")?) {\n            Ok(port) => port,\n            // The error will happen when the port number is not in a range of `u16`.\n            Err(_) => {\n                // Returning a custom message is not a good practice, but we can improve it later\n                // when we don't have to depend on `config` crate.\n                return Err(config::ConfigError::Message(String::from(\n                    \"the next port is not a valid u16\",\n                )));\n            }\n        };\n        let memcached_url = settings.get_str(\"memc_url\")?;\n\n        // XXX: The code of parsing a connection timeout here is quite ugly due to the `get_int`\n        // interface. Please don't be surprised :)\n\n        // Resolves the connection timeout.\n        let timeout = match settings.get_int(\"conn_timeout\") {\n            // If it's a not-found error, we just set it to the default value of 30 seconds.\n            Err(config::ConfigError::NotFound(_)) => 30,\n\n            // If it's other error, for example, unparseable error, it means that the user intended\n            // to enter the timeout but it just fails.\n            Err(error) => return Err(error),\n\n            Ok(val) => {\n                match u64::try_from(val) {\n                    Ok(val) => val,\n                    // The error will happen when the timeout is not in a range of `u64`.\n                    Err(_) => {\n                        // Returning a custom message is not a good practice, but we can improve\n                        // it later when we don't have to depend on `config` crate.\n                        return Err(config::ConfigError::Message(String::from(\n                            \"the connection timeout is not a valid u64\",\n                        )));\n                    }\n                }\n            }\n        };\n\n        // Resolves metrics configuration.\n        let metrics_config = get_metrics_config(&settings);\n\n        // Note that all of the file reading stuffs should be at the end of the function so that\n        // all the not-file-related stuffs can fail fast.\n\n        // All config filenames must be given with relative paths to where the server is run.\n        // Otherwise, cfnts will try to open the file while in the incorrect directory.\n        let certs_filename = settings.get_str(\"tls_cert_file\")?;\n        let secret_keys_filename = settings.get_str(\"tls_key_file\")?;\n\n        let cookie_key_filename = settings.get_str(\"cookie_key_file\")?;\n        let cookie_key = CookieKey::parse(&cookie_key_filename).wrap_err()?;\n\n        let mut config = KeServerConfig::new(\n            timeout,\n            cookie_key,\n            memcached_url,\n            metrics_config,\n            next_port,\n        );\n\n        config.import_tls_certs(&certs_filename).wrap_err()?;\n        config\n            .import_tls_secret_keys(&secret_keys_filename)\n            .wrap_err()?;\n\n        let addrs = settings.get_array(\"addr\")?;\n        for addr in addrs {\n            // Parse SocketAddr from a string.\n            let sock_addr = addr.to_string().parse().wrap_err()?;\n            config.add_address(sock_addr);\n        }\n\n        Ok(config)\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/server/connection.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! NTS-KE server connection.\n\nuse mio::tcp::{Shutdown, TcpStream};\n\nuse rustls::Session;\n\nuse slog::{debug, error, info};\n\nuse std::io::{Read, Write};\nuse std::sync::{Arc, RwLock};\n\nuse crate::cookie::{make_cookie, NTSKeys};\nuse crate::key_rotator::KeyRotator;\nuse crate::nts_ke::records::gen_key;\nuse crate::nts_ke::records::{\n    deserialize,\n    process_record,\n\n    // Functions.\n    serialize,\n    // Records.\n    AeadAlgorithmRecord,\n    // Errors.\n    DeserializeError,\n\n    EndOfMessageRecord,\n    // Enums.\n    KnownAeadAlgorithm,\n    KnownNextProtocol,\n    NewCookieRecord,\n    NextProtocolRecord,\n    Party,\n\n    PortRecord,\n\n    // Structs.\n    ReceivedNtsKeRecordState,\n\n    // Constants.\n    HEADER_SIZE,\n};\n\nuse super::ke_server::KeServerState;\nuse super::listener::KeServerListener;\n\n// response uses the configuration and the keys and computes the response\n// sent to the client.\nfn response(keys: NTSKeys, rotator: &Arc<RwLock<KeyRotator>>, port: u16) -> Vec<u8> {\n    let mut response: Vec<u8> = Vec::new();\n\n    let next_protocol_record = NextProtocolRecord::from(vec![KnownNextProtocol::Ntpv4]);\n    let aead_record = AeadAlgorithmRecord::from(vec![KnownAeadAlgorithm::AeadAesSivCmac256]);\n    let port_record = PortRecord::new(Party::Server, port);\n    let end_record = EndOfMessageRecord;\n\n    response.append(&mut serialize(next_protocol_record));\n    response.append(&mut serialize(aead_record));\n\n    let rotor = rotator.read().unwrap();\n    let (key_id, actual_key) = rotor.latest_key_value();\n\n    // According to the spec, if the next protocol is NTPv4, we should send eight cookies to the\n    // client.\n    for _ in 0..8 {\n        let cookie = make_cookie(keys, actual_key.as_ref(), key_id);\n        let cookie_record = NewCookieRecord::from(cookie);\n        response.append(&mut serialize(cookie_record));\n    }\n    response.append(&mut serialize(port_record));\n    response.append(&mut serialize(end_record));\n    response\n}\n\n#[derive(Clone, Copy, Eq, PartialEq)]\npub enum KeServerConnState {\n    /// The connection is just connected. The TLS handshake is not done yet.\n    Connected,\n    /// Doing the TLS handshake,\n    TlsHandshaking,\n    /// The TLS handshake is done. It's opened for requests now.\n    Opened,\n    /// The response is sent after getting a good request.\n    ResponseSent,\n    /// The connection is closed.\n    Closed,\n}\n\n/// NTS-KE server TCP connection.\npub struct KeServerConn {\n    /// Reference back to the corresponding `KeServer` state.\n    server_state: Arc<KeServerState>,\n\n    /// Kernel TCP stream.\n    tcp_stream: TcpStream,\n\n    /// The mio token for this connection.\n    token: mio::Token,\n\n    /// TLS session for this connection.\n    tls_session: rustls::ServerSession,\n\n    /// The status of the connection.\n    state: KeServerConnState,\n\n    /// The state of NTS-KE.\n    ntske_state: ReceivedNtsKeRecordState,\n\n    /// The buffer of NTS-KE Stream.\n    ntske_buffer: Vec<u8>,\n\n    /// Logger.\n    logger: slog::Logger,\n}\n\nimpl KeServerConn {\n    pub fn new(\n        tcp_stream: TcpStream,\n        token: mio::Token,\n        listener: &KeServerListener,\n    ) -> KeServerConn {\n        let server_state = listener.state();\n\n        // Create a TLS session from a server-wide configuration.\n        let tls_session = rustls::ServerSession::new(&server_state.tls_server_config);\n        // Create a child logger for the connection.\n        let logger = listener\n            .logger()\n            .new(slog::o!(\"client\" => listener.addr().to_string()));\n\n        let ntske_state = ReceivedNtsKeRecordState {\n            finished: false,\n            next_protocols: Vec::new(),\n            aead_scheme: Vec::new(),\n            cookies: Vec::new(),\n            next_server: None,\n            next_port: None,\n        };\n\n        KeServerConn {\n            // Create an `Arc` reference.\n            server_state: server_state.clone(),\n            tcp_stream,\n            tls_session,\n            token,\n            state: KeServerConnState::Connected,\n            ntske_state,\n            ntske_buffer: Vec::new(),\n            logger,\n        }\n    }\n\n    /// The handler when the connection is ready to ready or write.\n    pub fn ready(&mut self, poll: &mut mio::Poll, event: &mio::Event) {\n        if event.readiness().is_readable() {\n            self.read_ready();\n        }\n\n        if event.readiness().is_writable() {\n            self.write_ready();\n        }\n\n        if self.state() != KeServerConnState::Closed {\n            // TODO: Fix unwrap later.\n            self.reregister(poll).unwrap();\n        }\n    }\n\n    fn read_ready(&mut self) {\n        // If this is the first time that `read_ready` is called, it means that we start reading\n        // some TLS client hello from the client. So we need to change the state to TlsHandshaking.\n        if self.state == KeServerConnState::Connected {\n            self.state = KeServerConnState::TlsHandshaking;\n        }\n\n        // Read some data from the stream and feed it to the TLS stream.\n        let result = self.tls_session.read_tls(&mut self.tcp_stream);\n\n        let read_count = match result {\n            Ok(value) => value,\n            Err(error) => {\n                // If it's a WouldBlock, it's not actually an error. So we don't need to close the\n                // connection and return silently.\n                if let std::io::ErrorKind::WouldBlock = error.kind() {\n                    return;\n                }\n\n                // Close the connection on error.\n                error!(self.logger, \"read error: {}\", error);\n                self.shutdown();\n                return;\n            }\n        };\n\n        // If we reach the end-of-file, just close the connection.\n        if read_count == 0 {\n            info!(self.logger, \"eof\");\n            self.shutdown();\n            return;\n        }\n\n        // Process newly received TLS messages.\n        let processed = self.tls_session.process_new_packets();\n\n        if let Err(error) = processed {\n            error!(self.logger, \"cannot process packet: {}\", error);\n            self.shutdown();\n        }\n\n        let mut buf = Vec::new();\n        let result = self.tls_session.read_to_end(&mut buf);\n\n        if let Err(error) = result {\n            error!(self.logger, \"read failed: {}\", error);\n            self.shutdown();\n            return;\n        }\n\n        if !buf.is_empty() {\n            debug!(self.logger, \"plaintext read {},\", buf.len());\n            self.ntske_buffer.append(&mut buf);\n            let mut reader = &self.ntske_buffer[..];\n\n            // The plaintext is not empty. It means that the handshake is also done. We can change\n            // the state now.\n            if self.state == KeServerConnState::TlsHandshaking {\n                self.state = KeServerConnState::Opened;\n            }\n\n            let keys = gen_key(&self.tls_session).unwrap();\n\n            while !self.ntske_state.finished {\n                // need to read 4 bytes to get the header.\n                if reader.len() < HEADER_SIZE {\n                    info!(\n                        self.logger,\n                        \"readable nts-ke stream is not enough to read header\"\n                    );\n                    self.ntske_buffer = Vec::from(reader);\n                    return;\n                }\n\n                // need to read the body_length to get the body.\n                let body_length = u16::from_be_bytes([reader[2], reader[3]]) as usize;\n                if reader.len() < HEADER_SIZE + body_length {\n                    info!(\n                        self.logger,\n                        \"readable nts-ke stream is not enough to read body\"\n                    );\n                    self.ntske_buffer = Vec::from(reader);\n                    return;\n                }\n\n                // Reconstruct the whole record byte array to let the `records` module deserialize it.\n                let mut record_bytes = vec![0; HEADER_SIZE + body_length];\n                reader.read_exact(&mut record_bytes).unwrap();\n\n                match deserialize(Party::Server, record_bytes.as_slice()) {\n                    Ok(record) => {\n                        let status = process_record(record, &mut self.ntske_state);\n                        match status {\n                            Ok(_) => {}\n                            Err(err) => {\n                                error!(self.logger, \"process nts-ke record: {}\", err);\n                                self.shutdown();\n                                return;\n                            }\n                        }\n                    }\n                    Err(DeserializeError::UnknownNotCriticalRecord) => {\n                        // If it's not critical, just ignore the error.\n                        debug!(self.logger, \"unknown record type\");\n                    }\n                    Err(DeserializeError::UnknownCriticalRecord) => {\n                        // TODO: This should propertly handled by sending an Error record.\n                        debug!(self.logger, \"error: unknown critical record\");\n                        self.shutdown();\n                        return;\n                    }\n                    Err(DeserializeError::Parsing(error)) => {\n                        // TODO: This shouldn't be wrapped as a trait object.\n                        debug!(self.logger, \"error: {}\", error);\n                        self.shutdown();\n                        return;\n                    }\n                }\n            }\n\n            // We have to make sure that the response is not sent yet.\n            if self.state == KeServerConnState::Opened {\n                // TODO: Fix unwrap later.\n                self.tls_session\n                    .write_all(&response(\n                        keys,\n                        &self.server_state.rotator,\n                        self.server_state.config.next_port,\n                    ))\n                    .unwrap();\n                // Mark that the response is sent.\n                self.state = KeServerConnState::ResponseSent;\n            }\n        }\n    }\n\n    fn write_ready(&mut self) {\n        if let Err(error) = self.tls_session.write_tls(&mut self.tcp_stream) {\n            error!(self.logger, \"write failed: {}\", error);\n            self.shutdown();\n        }\n    }\n\n    /// Register the connection with Poll.\n    pub fn register(&self, poll: &mut mio::Poll) -> Result<(), std::io::Error> {\n        poll.register(\n            &self.tcp_stream,\n            self.token,\n            self.interest(),\n            mio::PollOpt::level(),\n        )\n    }\n\n    /// Re-register the connection with Poll.\n    pub fn reregister(&self, poll: &mut mio::Poll) -> Result<(), std::io::Error> {\n        poll.reregister(\n            &self.tcp_stream,\n            self.token,\n            self.interest(),\n            mio::PollOpt::level(),\n        )\n    }\n\n    fn interest(&self) -> mio::Ready {\n        let mut ready = mio::Ready::empty();\n\n        if self.tls_session.wants_read() {\n            ready |= mio::Ready::readable();\n        }\n        if self.tls_session.wants_write() {\n            ready |= mio::Ready::writable();\n        }\n        ready\n    }\n\n    pub fn state(&self) -> KeServerConnState {\n        self.state\n    }\n\n    pub fn shutdown(&mut self) {\n        // TODO: Fix unwrap later.\n        self.tcp_stream.shutdown(Shutdown::Both).unwrap();\n        self.state = KeServerConnState::Closed;\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/server/ke_server.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! NTS-KE server instantiation.\n\nuse slog::info;\n\nuse std::sync::{Arc, RwLock};\n\nuse crate::key_rotator::periodic_rotate;\nuse crate::key_rotator::KeyRotator;\nuse crate::key_rotator::RotateError;\nuse crate::metrics;\n\nuse super::config::KeServerConfig;\nuse super::listener::KeServerListener;\n\n/// NTS-KE server state that will be shared among listeners.\npub(super) struct KeServerState {\n    /// Configuration for the NTS-KE server.\n    // You can see that I don't expand the config's properties here because, by keeping it like\n    // this, we will know what is the config and what is the state.\n    pub(super) config: KeServerConfig,\n\n    /// Key rotator. Read this property to get latest keys.\n    // The internal state of this rotator can be changed even if the KeServer instance is\n    // immutable. That's because of the nature of RwLock. This property is normally used by\n    // KeServer to read the state only.\n    pub(super) rotator: Arc<RwLock<KeyRotator>>,\n\n    /// TLS server configuration which will be used among listeners.\n    // We use `Arc` here so that every thread can read the config, but the drawback of using `Arc`\n    // is that it uses garbage collection.\n    pub(super) tls_server_config: Arc<rustls::ServerConfig>,\n}\n\n/// NTS-KE server instance.\npub struct KeServer {\n    /// State shared among listerners.\n    // We use `Arc` so that all the KeServerListener's can reference back to this object.\n    state: Arc<KeServerState>,\n\n    /// List of listeners associated with the server.\n    /// Each listener is associated with each address in the config. You can check if the server\n    /// already started or not by checking that this vector is empty.\n    // We use `Arc` because the listener will listen in another thread.\n    listeners: Vec<Arc<RwLock<KeServerListener>>>,\n}\n\nimpl KeServer {\n    /// Create a new `KeServer` instance, connect to the Memcached server, and rotate initial keys.\n    ///\n    /// This doesn't start the server yet. It just makes to the state that it's ready to start.\n    /// Please run `start` to start the server.\n    pub fn connect(config: KeServerConfig) -> Result<KeServer, RotateError> {\n        let rotator = KeyRotator::connect(\n            String::from(\"/nts/nts-keys\"),\n            String::from(config.memcached_url()),\n            // We need to clone all of the following properties because the key rotator also\n            // has to own them.\n            config.cookie_key().clone(),\n            config.logger().clone(),\n        )?;\n\n        // Putting it in a block just to make it easier to read :)\n        let tls_server_config = {\n            // No client auth for TLS server.\n            let client_auth = rustls::NoClientAuth::new();\n            // TLS server configuration.\n            let mut server_config = rustls::ServerConfig::new(client_auth);\n\n            // We support only TLS1.3\n            server_config.versions = vec![rustls::ProtocolVersion::TLSv1_3];\n\n            // Set the certificate chain and its corresponding private key.\n            server_config\n                .set_single_cert(\n                    // rustls::ServerConfig wants to own both of them.\n                    config.tls_certs.clone(),\n                    config.tls_secret_keys[0].clone(),\n                )\n                .expect(\"invalid key or certificate\");\n\n            // According to the NTS specification, ALPN protocol must be \"ntske/1\".\n            server_config.set_protocols(&[Vec::from(\"ntske/1\".as_bytes())]);\n\n            server_config\n        };\n\n        let state = Arc::new(KeServerState {\n            config,\n            rotator: Arc::new(RwLock::new(rotator)),\n            tls_server_config: Arc::new(tls_server_config),\n        });\n\n        Ok(KeServer {\n            state,\n            listeners: Vec::new(),\n        })\n    }\n\n    /// Start the server.\n    pub fn start(&mut self) -> Result<(), std::io::Error> {\n        let logger = self.state.config.logger();\n\n        // Side-effect. Logging.\n        info!(logger, \"initializing keys with memcached\");\n\n        // Create another reference to the lock so that we can pass it to another thread and\n        // periodically rotate the keys.\n        let mutable_rotator = self.state.rotator.clone();\n\n        // Create a new thread and periodically rotate the keys.\n        periodic_rotate(mutable_rotator);\n\n        // We need to clone the metrics config here because we need to move it to another thread.\n        if let Some(metrics_config) = self.state.config.metrics_config.clone() {\n            info!(logger, \"spawning metrics\");\n\n            // Create a child logger to use inside the metric server.\n            let log_metrics = logger.new(slog::o!(\"component\" => \"metrics\"));\n\n            // Start a metric server.\n            std::thread::spawn(move || {\n                metrics::run_metrics(metrics_config, &log_metrics)\n                    .expect(\"metrics could not be run; starting ntp server failed\");\n            });\n        }\n\n        // For each address in the config, we will create a listener that will listen on that\n        // address. After the creation, we will create another thread and start listening inside\n        // that thread.\n\n        for addr in self.state.config.addrs() {\n            // Side-effect. Logging.\n            info!(logger, \"starting NTS-KE server over TCP/TLS on {}\", addr);\n\n            // Instantiate a listener.\n            // If there is an error here just return an error immediately so that we don't have to\n            // start a thread for other address.\n            let listener = KeServerListener::bind(*addr, self)?;\n\n            // It needs to be referenced by this thread and the new thread.\n            let atomic_listener = Arc::new(RwLock::new(listener));\n\n            self.listeners.push(atomic_listener);\n        }\n\n        // Join handles for the listeners.\n        let mut handles = Vec::new();\n\n        for listener in self.listeners.iter() {\n            // The listener reference that will be moved into the thread.\n            let cloned_listener = listener.clone();\n\n            let handle = std::thread::spawn(move || {\n                // Unwrapping should be fine here because there is no a write lock while we are\n                // trying to lock it and we will wait for the thread to finish before returning\n                // from this `start` method.\n                //\n                // If you don't want to wait for this thread to finish before returning from the\n                // `start` method, you have to look at this `unwrap` and handle it carefully.\n                //\n                // TODO: figure what to do later when the listen fails.\n                cloned_listener.write().unwrap().listen().unwrap();\n            });\n\n            // Add it into the list of listeners.\n            handles.push(handle);\n        }\n\n        // We need to wait for the listeners to finish. If you don't want to wait for the listeners\n        // anymore, please don't forget to take care an `unwrap` in the thread a few lines above.\n        for handle in handles {\n            // We don't care it's a normal exit or it's a panic from the thread, so we just ignore\n            // the result here.\n            let _ = handle.join();\n        }\n\n        Ok(())\n    }\n\n    /// Return the state of the server.\n    pub(super) fn state(&self) -> &Arc<KeServerState> {\n        &self.state\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/server/listener.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! NTS-KE server listener.\n\nuse mio::net::TcpListener;\n\nuse slog::{error, info};\n\nuse std::cmp::Reverse;\nuse std::collections::BinaryHeap;\nuse std::collections::HashMap;\nuse std::net::SocketAddr;\nuse std::sync::Arc;\nuse std::time::{Duration, SystemTime};\n\nuse crate::cfsock;\n\nuse super::connection::KeServerConn;\nuse super::connection::KeServerConnState;\nuse super::ke_server::KeServer;\nuse super::ke_server::KeServerState;\n\nconst LISTENER_MIO_TOKEN_ID: usize = 0;\nconst CONNECTION_MIO_TOKEN_ID_MIN: usize = LISTENER_MIO_TOKEN_ID + 1;\n// `usize::max_value()` is reserved for mio internal use, so we need to minus one here.\nconst CONNECTION_MIO_TOKEN_ID_MAX: usize = usize::max_value() - 1;\n\n/// The token used to associate the mio event with the lister event.\nconst LISTENER_MIO_TOKEN: mio::Token = mio::Token(LISTENER_MIO_TOKEN_ID);\n\n/// NTS-KE server internal listener for a specific listened address.\n/// One listener will correspond to one kernel listening socket.\npub struct KeServerListener {\n    /// Reference back to the corresponding `KeServer` state.\n    state: Arc<KeServerState>,\n\n    /// TCP listener for incoming connections.\n    tcp_listener: TcpListener,\n\n    /// List of connections accepted by this listener.\n    connections: HashMap<mio::Token, KeServerConn>,\n\n    /// Deadline indices for connections.\n    // We use `Reverse` because we want a min heap.\n    deadlines: BinaryHeap<Reverse<(SystemTime, mio::Token)>>,\n\n    /// The next mio token id for a new connection.\n    next_conn_token_id: usize,\n\n    /// Address and port that this listener will listen to.\n    addr: SocketAddr,\n\n    /// Polling object from mio.\n    poll: mio::Poll,\n\n    /// Logger.\n    logger: slog::Logger,\n}\n\nimpl KeServerListener {\n    /// Bind a new listener with the specified address and server.\n    ///\n    /// # Errors\n    ///\n    /// All the errors here are from the kernel which we don't have to know about for now.\n    pub fn bind(addr: SocketAddr, server: &KeServer) -> Result<KeServerListener, std::io::Error> {\n        let state = server.state();\n        let poll = mio::Poll::new()?;\n\n        // Create a listening std tcp listener.\n        let std_tcp_listener = cfsock::tcp_listener(&addr)?;\n\n        // Transform a std tcp listener to a mio tcp listener.\n        let mio_tcp_listener = TcpListener::from_std(std_tcp_listener)?;\n\n        // Register for the event that the listener is readable.\n        poll.register(\n            &mio_tcp_listener,\n            LISTENER_MIO_TOKEN,\n            mio::Ready::readable(),\n            mio::PollOpt::level(),\n        )?;\n\n        Ok(KeServerListener {\n            // Create an `Arc` reference.\n            state: state.clone(),\n            tcp_listener: mio_tcp_listener,\n            connections: HashMap::new(),\n            deadlines: BinaryHeap::new(),\n            next_conn_token_id: CONNECTION_MIO_TOKEN_ID_MIN,\n            addr,\n            // In the future, we may want to use the child logger instead the logger itself.\n            logger: state.config.logger().clone(),\n            poll,\n        })\n    }\n\n    /// Block the thread and start polling the events.\n    pub fn listen(&mut self) -> Result<(), std::io::Error> {\n        // Holding up to 2048 events.\n        let mut events = mio::Events::with_capacity(2048);\n\n        loop {\n            // The error returned here is from the kernel select.\n            self.poll.poll(&mut events, None)?;\n\n            for event in events.iter() {\n                // Close all expired connections.\n                self.close_expired_connections();\n                let token = event.token();\n\n                // If the event is the listener event.\n                if token == LISTENER_MIO_TOKEN {\n                    // Start accepting a new connection.\n                    if let Err(error) = self.accept() {\n                        error!(\n                            self.logger,\n                            \"accept failed unrecoverably with error: {}\", error\n                        );\n                    }\n                    continue;\n                };\n\n                // If the event is not the listener event, it must be a connection event.\n\n                // The connection associated with the token may not exist for some reason. In which\n                // case, we just ignore it.\n                if let Some(connection) = self.connections.get_mut(&token) {\n                    connection.ready(&mut self.poll, &event);\n\n                    if connection.state() == KeServerConnState::Closed {\n                        self.connections.remove(&token);\n                    }\n                }\n            }\n        }\n    }\n\n    /// Accepting a new connection. This will not block the thread, if it's called after receiving\n    /// the `LISTENER_MIO_TOKEN` event. But it will block, if it's not.\n    fn accept(&mut self) -> Result<(), std::io::Error> {\n        let (tcp_stream, addr) = match self.tcp_listener.accept() {\n            Ok(value) => value,\n            Err(error) => {\n                // If it's WouldBlock, just treat it like a success because there isn't an actual\n                // error. It's just in a non-blocking mode.\n                if error.kind() == std::io::ErrorKind::WouldBlock {\n                    return Ok(());\n                }\n\n                // If it's not WouldBlock, it's an error.\n                error!(\n                    self.logger,\n                    \"encountered error while accepting connection; err={}\", error\n                );\n\n                // TODO: I don't understand why we need another tcp listener and register a new\n                // event here. I will figure it out after I finish refactoring everything.\n                self.tcp_listener = TcpListener::bind(&self.addr)?;\n                // TODO: Ignore error first. I wil figure out what to do later if there is an\n                // error.\n                self.poll.register(\n                    &self.tcp_listener,\n                    LISTENER_MIO_TOKEN,\n                    mio::Ready::readable(),\n                    mio::PollOpt::level(),\n                )?;\n\n                // TODO: I will figure why it returns Ok later.\n                return Ok(());\n            }\n        };\n\n        // Successfully accepting a connection.\n\n        info!(self.logger, \"accepting new connection from {}\", addr);\n\n        let token = mio::Token(self.next_conn_token_id);\n        self.increment_next_conn_token_id();\n\n        let timeout_duration = Duration::new(self.state.config.timeout(), 0);\n\n        // If the timeout is so large that we cannot put it in SystemTime, we can assume that\n        // it doesn't have a timeout and just don't add it into the map.\n        if let Some(timeout_systime) = SystemTime::now().checked_add(timeout_duration) {\n            self.deadlines.push(Reverse((timeout_systime, token)));\n        }\n\n        // Create a new connection instance.\n        let connection = KeServerConn::new(tcp_stream, token, self);\n        // TODO: Fix the unwrap later.\n        connection.register(&mut self.poll).unwrap();\n\n        self.connections.insert(token, connection);\n\n        Ok(())\n    }\n\n    /// Increment next_conn_token_id.\n    fn increment_next_conn_token_id(&mut self) {\n        match self.next_conn_token_id.checked_add(1) {\n            Some(value) => self.next_conn_token_id = value,\n            // If it overflows just set it to the minimum value.\n            None => self.next_conn_token_id = CONNECTION_MIO_TOKEN_ID_MIN,\n        }\n\n        // If it exceeds the maximum, we also set it to the minimum value.\n        if self.next_conn_token_id > CONNECTION_MIO_TOKEN_ID_MAX {\n            self.next_conn_token_id = CONNECTION_MIO_TOKEN_ID_MIN;\n        }\n    }\n\n    /// Closes the expired timeouts, looping until they are all gone.\n    /// We remove the timeout from the heap, and kill the connection if it exists.\n    fn close_expired_connections(&mut self) {\n        let now = SystemTime::now();\n\n        while let Some(earliest) = self.deadlines.peek() {\n            let Reverse((deadline, token)) = earliest;\n\n            if deadline < &now {\n                // If the deadline is already elapsed, close the connection and pop the heap.\n                // The connection associated with the token may not exist because, when we close\n                // the connection, it's not possible to find an entry in the heap. In which case,\n                // we can just pop the deadline heap.\n                if let Some(mut connection) = self.connections.remove(token) {\n                    error!(self.logger, \"forcible shutdown after timeout\");\n                    connection.shutdown();\n                }\n                self.deadlines.pop();\n\n                // In this case, this means that there may be more elapsed deadline. Continue the\n                // loop.\n            } else {\n                // If not, it means there is no more elapsed deadline in the heap. So we can just\n                // stop the loop.\n                break;\n            }\n        }\n    }\n\n    /// Return the state of the corresponding server.\n    pub(super) fn state(&self) -> &Arc<KeServerState> {\n        &self.state\n    }\n\n    /// Return the logger of this listener.\n    pub(super) fn logger(&self) -> &slog::Logger {\n        &self.logger\n    }\n\n    /// Return the address-port of this listener.\n    pub(super) fn addr(&self) -> &SocketAddr {\n        &self.addr\n    }\n}\n"
  },
  {
    "path": "src/nts_ke/server/mod.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! NTS-KE server implementation.\n\nmod config;\nmod connection;\nmod ke_server;\nmod listener;\n\n// We expose only two structs: KeServer and KeServerConfig. KeServer is used to run an instant of\n// the NTS-KE server and KeServerConfig is used to instantiate KeServer.\npub use self::config::KeServerConfig;\npub use self::ke_server::KeServer;\n"
  },
  {
    "path": "src/sub_command/client.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! The client subcommand.\n\nuse slog::debug;\n\nuse std::fs;\nuse std::io::BufReader;\nuse std::process;\n\nuse rustls::{internal::pemfile::certs, Certificate};\n\nuse crate::error::WrapError;\nuse crate::ntp::client::run_nts_ntp_client;\nuse crate::nts_ke::client::run_nts_ke_client;\n\n#[derive(Debug)]\npub struct ClientConfig {\n    pub host: String,\n    pub port: Option<String>,\n    pub trusted_cert: Option<Certificate>,\n    pub use_ipv4: Option<bool>,\n}\n\npub fn load_tls_certs(path: String) -> Result<Vec<Certificate>, config::ConfigError> {\n    certs(&mut BufReader::new(fs::File::open(&path).wrap_err()?)).map_err(|()| {\n        config::ConfigError::Message(format!(\"could not load certificate from {}\", &path))\n    })\n}\n\n/// The entry point of `client`.\npub fn run(matches: &clap::ArgMatches<'_>) {\n    // This should return the clone of `logger` in the main function.\n    let logger = slog_scope::logger();\n\n    let host = matches.value_of(\"host\").map(String::from).unwrap();\n    let port = matches.value_of(\"port\").map(String::from);\n    let cert_file = matches.value_of(\"cert\").map(String::from);\n\n    // By default, use_ipv4 is None (no preference for using either ipv4 or ipv6\n    // so client sniffs which one to use based on support)\n    // However, if a user specifies the ipv4 flag, we set use_ipv4 = Some(true)\n    // If they specify ipv6 (only one can be specified as they are mutually exclusive\n    // args), set use_ipv4 = Some(false)\n    let ipv4 = matches.is_present(\"ipv4\");\n    let mut use_ipv4 = None;\n    if ipv4 {\n        use_ipv4 = Some(true);\n    } else {\n        // Now need to check whether ipv6 is being used, since ipv4 has not been mandated\n        if matches.is_present(\"ipv6\") {\n            use_ipv4 = Some(false);\n        }\n    }\n\n    let mut trusted_cert = None;\n    if let Some(file) = cert_file {\n        if let Ok(certs) = load_tls_certs(file) {\n            trusted_cert = Some(certs[0].clone());\n        }\n    }\n\n    let client_config = ClientConfig {\n        host,\n        port,\n        trusted_cert,\n        use_ipv4,\n    };\n\n    let res = run_nts_ke_client(&logger, client_config);\n\n    if let Err(err) = res {\n        eprintln!(\"failure of tls stage: {}\", err);\n        process::exit(1)\n    }\n    let state = res.unwrap();\n    debug!(logger, \"running UDP client with state {:x?}\", state);\n    let res = run_nts_ntp_client(&logger, state);\n    match res {\n        Err(err) => {\n            eprintln!(\"failure of client: {}\", err);\n            process::exit(1)\n        }\n        Ok(result) => {\n            println!(\"stratum: {:}\", result.stratum);\n            println!(\"offset: {:.6}\", result.time_diff);\n        }\n    }\n}\n"
  },
  {
    "path": "src/sub_command/ke_server.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! The ke-server subcommand.\n\nuse std::process;\n\nuse crate::nts_ke::server::{KeServer, KeServerConfig};\n\n/// Get a configuration file path for `ke-server`.\n///\n/// If the path is not specified, the system-wide configuration file (/etc/cfnts/ke-server.config)\n/// will be used instead.\n///\nfn resolve_config_filename(matches: &clap::ArgMatches<'_>) -> String {\n    match matches.value_of(\"configfile\") {\n        // If the config file is specified in the arguments, just use it.\n        Some(filename) => String::from(filename),\n        // If not, use the system-wide configuration file.\n        None => String::from(\"/etc/cfnts/ke-server.config\"),\n    }\n}\n\n/// The entry point of `ke-server`.\npub fn run(matches: &clap::ArgMatches<'_>) {\n    // This should return the clone of `logger` in the main function.\n    let global_logger = slog_scope::logger();\n\n    // Get the config file path.\n    let filename = resolve_config_filename(matches);\n    let mut config = match KeServerConfig::parse(&filename) {\n        Ok(val) => val,\n        // If there is an error, display it.\n        Err(err) => {\n            eprintln!(\"{}\", err);\n            process::exit(1);\n        }\n    };\n\n    let logger = global_logger.new(slog::o!(\"component\" => \"nts_ke\"));\n    // Let the parsed config use the child logger of the global logger.\n    config.set_logger(logger);\n\n    // Try to connect to the Memcached server.\n    let mut server = match KeServer::connect(config) {\n        Ok(server) => server,\n        Err(_error) => {\n            // Disable the log for now because the Error trait is not implemented for\n            // RotateError yet.\n            // eprintln!(\"starting NTS-KE server failed: {}\", error);\n            process::exit(1);\n        }\n    };\n\n    // Start listening for incoming connections.\n    if let Err(error) = server.start() {\n        eprintln!(\"starting NTS-KE server failed: {}\", error);\n        process::exit(1);\n    }\n}\n"
  },
  {
    "path": "src/sub_command/mod.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! Subcommand collections.\n\npub mod client;\npub mod ke_server;\npub mod ntp_server;\n"
  },
  {
    "path": "src/sub_command/ntp_server.rs",
    "content": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing information.\n\n//! The ntp-server subcommand.\n\nuse std::process;\n\nuse crate::ntp::server::start_ntp_server;\nuse crate::ntp::server::NtpServerConfig;\n\n/// Get a configuration file path for `ntp-server`.\n///\n/// If the path is not specified, the system-wide configuration file (/etc/cfnts/ntp-server.config)\n/// will be used instead.\n///\nfn resolve_config_filename(matches: &clap::ArgMatches<'_>) -> String {\n    match matches.value_of(\"configfile\") {\n        // If the config file is specified in the arguments, just use it.\n        Some(filename) => String::from(filename),\n        // If not, use the system-wide configuration file.\n        None => String::from(\"/etc/cfnts/ntp-server.config\"),\n    }\n}\n\n/// The entry point of `ntp-server`.\npub fn run(matches: &clap::ArgMatches<'_>) {\n    // This should return the clone of `logger` in the main function.\n    let global_logger = slog_scope::logger();\n\n    // Get the config file path.\n    let filename = resolve_config_filename(matches);\n    let mut config = match NtpServerConfig::parse(&filename) {\n        Ok(val) => val,\n        // If there is an error, display it.\n        Err(err) => {\n            eprintln!(\"{}\", err);\n            process::exit(1);\n        }\n    };\n\n    let logger = global_logger.new(slog::o!(\"component\" => \"ntp\"));\n    // Let the parsed config use the child logger of the global logger.\n    config.set_logger(logger);\n\n    if let Err(err) = start_ntp_server(config) {\n        eprintln!(\"starting NTP server failed: {}\", err);\n        process::exit(1);\n    }\n}\n"
  },
  {
    "path": "tests/ca-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCzTB9ETn6RgGHT\nEXkvXtUxCRtN5oz8eh1hD98OBOYrqC9gpw90xurkpvrSKA/XGi2er+b+fDaMZnvO\nrUAmO5tkBeUv5VRArKAW1lTocTTFCXbbS1pMd4fxCePXnDed81MWFThYLr9zJ8KU\neamYMBWq6lziAynTT9+bVaj5zkLC23u9EUqPFn8Kg3hdfLE5FPzeWqYREVrst++f\nnpjrt6ZWlHohA6P6BAplubBTKtcOcyDT1Y8Wg7OxgLex+gq5X+4YxzpuvPzRL0uc\nl4210FhPY+55zJ4Vw6lqQmY7lSloQQO2PbtkRvfpphy7J1aPaVpWXvVK2mv4tZtX\nzplOK3Uf1m4hVCA/fN1A5w89RCzdOacYSEvvFRhpFo4Iw0L3u2jvO71Rxpe+ATZk\nKzhk0TnwyRCQGYGYjRsNRAnGHbe10fPaT2NoBDmojg4S1LF6plwYYxTXvUsIWo9c\nKizMAYB6p6jCIcJUupulyChnSyTxvLOLKJr0YuU3ug4WEH1fw2JVgYi4X8TeGRgz\nLqBA9NudPJZjXh+fCcmdfIqNpuVujWJGqFjzG6NyixGJ5SqnIByFJd1UhF5UgJPL\nyseSrAn1Zqk4wKDtjPnahgDIqoLNjBh8vc2jbwTShwWWxnZK2iA+VzG4Y9l7I1Fp\nANkJp+oug6tiCiBwYzMzP6rJjDBI6wIDAQABAoICAD3Tpwh/5Mc5tQH6iYZbNjrF\ngCPZt44sccsRlQIZkGFHiqbSlNLY8RDNv7oOVIABJ/ALiiUBIjJB+LlpJrDIZyoT\nmldsxiPTIxUc7YSF3QOA4vp1vnqV0Uu99FJaLReLW4BG6voFjMEh2cgnN+Mh2abp\nUAQjwR178oh2/mC9zmmxE7c7qjEzObWfZjcek2IyqYvnSFKkYG02dCvfna3S00oR\nwxd1UOsaz5cKdBIJuMTj0FMb1k6WNbWkxDNcHKyVtt3WfYDILInZvEIQRK6IXJtr\nw0U+2Nh6cwYQRX6QTgoEOUpzeRX4Hu7z9/5Vb1TeqGcWMZGRRiAqR5n8xQKem7E9\n40uQdTr1GY+89x5G/AkzD/IvKCrVZLBYjzXwNZmIztoVMu0bWDwijrR2c17lQHvA\nhWz4DOaONxRBxCbva7dG4FZZRj3F1q3aN7cpvFMRP7QzVtkDz1k7j8q01O1hlzY8\nezGXcHeu60Dy+/TcLvJVwxJ6/uaGcwTpsS88/ybVlJ2V24JjcoFE5YoiJEBlLW93\nUBHGmaS9zd8HAIkbNugpQCHFJRVfaaINLbchpV6g/ETlGyUPRcXMZmtpuqm72esu\neWyYxoPxQ99u6Y4zoIRqx/H2eDmhxluLLwstIMA3uFD0AgcLpDHOAOlvY6QWkQuE\n7TkuPcbAwGo6J3SSbBpJAoIBAQDhyzGB5ZNbVhMlbYhQiBTgQ0twBn9xLKH3sQiS\niIru/IHJxBAgz/Cg3x7CQqoz2HgZw8+kWYmwwt/icFEnCv35jErwSelpGHLx74cm\nZpzEM6hPOjtDG5Afqc6YOr5dKTHv9Gidpz0uQAKZ20awHUmkLNsOy6zfB9RhMT5y\nBx8mictEFweOjbrn7qSwm6cssoZcoqbU52sgJ/2JlSdqBbajSFkO0hPerZh/Z/x6\nF/xNQR11DCsBHPMRlvUhxvk4sAjUEPhp6Pw1NUJvdsewQmIdNAE3cH+p/w4uVmvo\nu+qY8X5+cz097rq9ElZxOQqaGaGe7Pvid1Y4u8NC/BjDefoPAoIBAQDLSI6ywzQK\nL8YzpiIhE5Q9VDLlolv3+s02US+MNN8djMI5D9/2tvaoXaA5ax3OcvYyv0WLTddX\nWOiCWAe14gIX3jTPHWbzzLfE3AYQDu7P7C3u6bripcRMVIHQ4z7Juk2xrFQjsW9Y\nwoq4nc6CrVJhcH5koFpn+CEU/A0vzmWfw2w/5rSZ7Mi74upPDElKwx6L8wth1sGK\nbKhD4+tMbVA3fYu2PqxY8LcFjyeh9mDZJSyFSIi4Itb1e1QPCD2TQTHiQdo0Lap0\ngk5ylCQaeLJDbFGVioeUfmfngvNP3n8ye/bc3h6OrM4sAz6lXMhCJfB6TE2IuAKE\nvKmmry5Guk9lAoIBAHTigQBjXcLcbhDkALrflx749yZI1tQ5bKcSSAPDF1jb8jwG\neOrjegdtOTkK1Zz9JD8CNI05pKOSXd+UkQ4LDKqQS4LUYDX9aBOCEY55dBHFRA2v\ncVot/I/HkaEQV9dWKfmzpixmlK9Kh44qCw/EOYj5h3TDTvwty2181nyk3yVOE6Ft\n4oWTLPw/d5XNHd9vk0qFEKQKIFSHHyKHyd2Ck6c3HpMjgRG2/8iEhhiWLg+3843R\n/LkYyWODp+YSYJVN22QcXNxGtbi9l2SoMns2AiBn+XE/lXblB+xI5JeYH7uI2BiR\ng1R6LsUNpx35j1lyh04EE+iKKmI4IL6eThtzG1UCggEBAMCZAfoET+3GzbZplLRZ\n5H0mpQJEDXapPHxV9wKTpUBN+EYv8DXDq3ZhHkjIX/kVmoUCC1WsbnXnWoMD/Goq\ns2kBsm74oG4ka4gsHeJhA4ojbnGJKPNLsuvOtR+/7eEajjnj1+PpXGFwEBZSDTJq\nHD8NYfLcqksPH+jN1YCRwF7ZvFnerwWW/ahlmTFDpr0amHpnz0TnP39y6wlHi8th\nVjr8y73jK08o4X5230noMGILgl7VFhO/joIOUtnbKNu3TRfc5GvDSFgSjVipWntq\nFxsiKTnRghsCmFcUDoqBd2nRYVZpa/Ipbzzr5hKuEV36rBhy6pK6JEi2ptWx69o+\n8rECggEBALzsK4L46sHkJOl15yxjXPV6ZmtxhHnMCPLsNrKGJZRti53JUqCrWF+v\n9SBOCLnUmwijY5tbmi+CdQkpnV5VxF4nmN65KH8BonRL2pKDnGionqdqBlr7hupj\nTdLlhQqTJQGLsJcsRhGiLbjyLuJMDvtQaGC7F3vPQwYqdsj5iPRFJbLS7OPkcpud\nOkfm6GhBCOaMjBM2WRgWJwmAnwt9t/YiU1DhPKrUCPb5pzYXEA1DC0IWbDISLCTt\nSpbR/hX6jw/IhuvCQ0vPP+4NeL9GjVNo8iF6NYIwV9swu22yQhtklooDiuQ8fzCw\nxnbbOtvjxJ1sG5mW9Dblwe3GAIoI35c=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "tests/ca.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIEizCCAnMCAQAwRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQH\nDA1TYW4gRnJhbmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3\nDQEBAQUAA4ICDwAwggIKAoICAQCzTB9ETn6RgGHTEXkvXtUxCRtN5oz8eh1hD98O\nBOYrqC9gpw90xurkpvrSKA/XGi2er+b+fDaMZnvOrUAmO5tkBeUv5VRArKAW1lTo\ncTTFCXbbS1pMd4fxCePXnDed81MWFThYLr9zJ8KUeamYMBWq6lziAynTT9+bVaj5\nzkLC23u9EUqPFn8Kg3hdfLE5FPzeWqYREVrst++fnpjrt6ZWlHohA6P6BAplubBT\nKtcOcyDT1Y8Wg7OxgLex+gq5X+4YxzpuvPzRL0ucl4210FhPY+55zJ4Vw6lqQmY7\nlSloQQO2PbtkRvfpphy7J1aPaVpWXvVK2mv4tZtXzplOK3Uf1m4hVCA/fN1A5w89\nRCzdOacYSEvvFRhpFo4Iw0L3u2jvO71Rxpe+ATZkKzhk0TnwyRCQGYGYjRsNRAnG\nHbe10fPaT2NoBDmojg4S1LF6plwYYxTXvUsIWo9cKizMAYB6p6jCIcJUupulyChn\nSyTxvLOLKJr0YuU3ug4WEH1fw2JVgYi4X8TeGRgzLqBA9NudPJZjXh+fCcmdfIqN\npuVujWJGqFjzG6NyixGJ5SqnIByFJd1UhF5UgJPLyseSrAn1Zqk4wKDtjPnahgDI\nqoLNjBh8vc2jbwTShwWWxnZK2iA+VzG4Y9l7I1FpANkJp+oug6tiCiBwYzMzP6rJ\njDBI6wIDAQABoAAwDQYJKoZIhvcNAQELBQADggIBAI+EU8ck+zgruCBjtfzqhkJ9\nPtHmaRMG5ziq5tUFzHe3O++N09DKt0R+mvKxYLDDj9w61qPy0MH1UDsWaftE/LWE\nujIACIbg6Ium3iU4KViQUXMoTYveLjhh8f2i+IKDSEnORgmDBwX6Xg153SZNLZHh\nhn8xiJ34bJRrsyOJM1zwGjXiD6ikNALv7OLtL45H+mpdk6BzpcJEUKBdGpa1pp1p\niCQwPbvkA/vi+OOAaUuAafrt2RaPLVOpHgvNj7PlWX6qNmUe52tTFegBIb30qtrL\nDS+yqbFeHcVQ7ypV2hbqOs3uumFMUBMM0yDPPopBb0xINfKd+IOm7uVwLrHUm3sU\nkOzEJRdYN6n0LQFjbpQnAM7nhu31RCtsyeUStAfdmlQCetg0vhmir0hpkMLTg/ln\n/bIcDx5S2ODVS8tlfD5ggugHThdxrC2xjfvSlOUu9y9zQZnxlOscwQW9vJDKn9mV\nzWXqf4SjJks1yEg57XG9WkzchiEvtQpQu2d0fZpW4+O8qQlUBIxXc3di4whHYx0j\nWWxO27NFPSlTZb6TrjZW4N02vfWMbczWOqrkKBp61EpZYg6rDsZQd7t0UYTjIZDz\nEl1QyEbof28R9bskiwC7EGwR9DguodFH7l2K+DIjpl8FxysLFxMU2YGJOu81j5Pn\nodps4ahbCad6c5cgkeJW\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "tests/ca.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFEzCCAvsCFEXBseLI9/DDsUJVNhy2Nn2ORSZFMA0GCSqGSIb3DQEBCwUAMEYx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDQwOTA3MjYyNFoXDTM0MDQwNzA3\nMjYyNFowRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4g\nRnJhbmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUA\nA4ICDwAwggIKAoICAQCzTB9ETn6RgGHTEXkvXtUxCRtN5oz8eh1hD98OBOYrqC9g\npw90xurkpvrSKA/XGi2er+b+fDaMZnvOrUAmO5tkBeUv5VRArKAW1lTocTTFCXbb\nS1pMd4fxCePXnDed81MWFThYLr9zJ8KUeamYMBWq6lziAynTT9+bVaj5zkLC23u9\nEUqPFn8Kg3hdfLE5FPzeWqYREVrst++fnpjrt6ZWlHohA6P6BAplubBTKtcOcyDT\n1Y8Wg7OxgLex+gq5X+4YxzpuvPzRL0ucl4210FhPY+55zJ4Vw6lqQmY7lSloQQO2\nPbtkRvfpphy7J1aPaVpWXvVK2mv4tZtXzplOK3Uf1m4hVCA/fN1A5w89RCzdOacY\nSEvvFRhpFo4Iw0L3u2jvO71Rxpe+ATZkKzhk0TnwyRCQGYGYjRsNRAnGHbe10fPa\nT2NoBDmojg4S1LF6plwYYxTXvUsIWo9cKizMAYB6p6jCIcJUupulyChnSyTxvLOL\nKJr0YuU3ug4WEH1fw2JVgYi4X8TeGRgzLqBA9NudPJZjXh+fCcmdfIqNpuVujWJG\nqFjzG6NyixGJ5SqnIByFJd1UhF5UgJPLyseSrAn1Zqk4wKDtjPnahgDIqoLNjBh8\nvc2jbwTShwWWxnZK2iA+VzG4Y9l7I1FpANkJp+oug6tiCiBwYzMzP6rJjDBI6wID\nAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCmo1FG5Dudyy7Z0MgHY/dHe9EHWMl8FPIK\nzRxFrsAUivNMaXG+rUmsPgd0tNUdqEYQOpDYyu61ayo9dZUfjfoiePp/h6jiZrUa\nOxWtC53Em/UDoVz/hElRFwOYCz5O3ZQRC+c/CjSb+hsB93gi3bJIq3mIGCe9+jf1\nYD2GkaD99V5gZq5U5cTGsD9rxdAOT4AMEsxsUAUVULzhA+nQw4uqxFDe2AC8ZY9j\nAerCXu5BiLDcB3YnwnHaZ7MXbpWROSYQCmgojxUoiycAnZNJssFF2c/PVrRI3z0J\nvKhO7ViGs+JOqfp5jdgZO0SdKYT+n/TpF3Eqn/ugcXbyBBqS+Vj9liwTKNLqw7Um\nGWPOXoczYOp0iIv7qH6HbqpmZgwt4j1Xn7oMkZMv2fjYFCIS4PjifTVBaIm6FBg8\nXE0wGlWoMQtfxy56lPNub8Rnq6SyYtKJZfap8ukaPgL6mMJ7RYL1tjiHM9FRXS+7\nMrDo1bsQUoCqh6ZmWMyMfGycDflZg1DuAwwwJ06OmEBSqvkZ1sjZi/LGIAwKC2cw\nYsIJsKNw/rdE+ph7reH9RiDLJe+I2WehDZCqZ3dA5d8NjK2wGEo6G54YxKARpndl\nAlUeB/KJNFwLU74FPL2Jn9yfXTcqJbGs+AIpAu/OtwhPEkIizoeRO0cwqhURa2f/\nq5Qa16K2Bw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tests/chain.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICoDCCAkegAwIBAgIUW5W4GNGYJwryph3KHKkdLaeFdvMwCgYIKoZIzj0EAwIw\ngY4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRgwFgYDVQQKEw9IYXBweUNlcnQsIEluYy4xHzAdBgNVBAsT\nFkhhcHB5Q2VydCBJbnRlcm1lZGlhdGUxFzAVBgNVBAMTDihkZXYgdXNlIG9ubHkp\nMCAXDTI0MDQwOTA3MjEwMFoYDzIxMjQwMzE2MDcyMTAwWjB2MQswCQYDVQQGEwJV\nUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGDAWBgNVBAoT\nD0Nsb3VkZmxhcmUgdGVzdDEUMBIGA1UECxMLQ3J5cHRvIHRlYW0xEjAQBgNVBAMT\nCWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHG0wqxNChNemkM/\nAw05RBB0vs9adyC1tIm+pobqB0T6T50HN59NeDxMsfeALWBN/i23FKphwdNGIzO3\nNkNhMg2jgZcwgZQwDgYDVR0PAQH/BAQDAgGGMAwGA1UdEwEB/wQCMAAwHQYDVR0O\nBBYEFJw0MuxNY1BtEB3oNMbPiwxDNrqZMB8GA1UdIwQYMBaAFOu3ANbLR8TJecGR\nAAuxL7juFmVyMDQGA1UdEQQtMCuCBnNlcnZlcoIJbG9jYWxob3N0gglib2d1cy5j\nb22CCyoubG9jYWxob3N0MAoGCCqGSM49BAMCA0cAMEQCIAJ0Os/bYUfH6nPO8f1E\nvVateUJXKaPuS6jD3i0eWQYbAiAyC4TPPr4S0wUXGf6RYwbTPG3sAvGAnuxpxlqB\nP0sZrQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID3zCCAcegAwIBAgIUalsEFgf4uPaULSbhh3By7ebBUSQwDQYJKoZIhvcNAQEN\nBQAwRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjQwNDA5MDcyMTAwWhgPMjEy\nNDAzMTYwNzIxMDBaMIGOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p\nYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEYMBYGA1UEChMPSGFwcHlDZXJ0LCBJ\nbmMuMR8wHQYDVQQLExZIYXBweUNlcnQgSW50ZXJtZWRpYXRlMRcwFQYDVQQDEw4o\nZGV2IHVzZSBvbmx5KTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJABtGVq08uw\nOV99fDjxN38bWMAe2SeZeBHxF/TEsjtc7jvZtcFv6SECzu9qq6ktUsymCtSDYnP1\nbJ2VZLnEQ4SjRTBDMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEA\nMB0GA1UdDgQWBBTrtwDWy0fEyXnBkQALsS+47hZlcjANBgkqhkiG9w0BAQ0FAAOC\nAgEAl9aRyMaYMJSyWAI4BZmPSxzs8cIVa7lOnCLI3AevDOw4AEXTwK6VFZaehuWB\nVfodav9LrNQ8m/Po3K7AQwQYBghLwaQu7ISlI4pIGUeAZaIo90Bv0H2BJb3foHvi\n4+RI+CjVHXucKkgNU998RG6edwDsmdp963kKs3/AiU0vUgyUbuEzhzH4Dgqzt99w\n0ekf33fDGRvJ6k45oWZ7gkeT1gcbhCFafQJrMRKgoXcxPxwGxvn+usSd0EUuvMeF\nsoQJYZ/nMtSahC5qR2TRDunsUAtDtWk7LhdKQF9c+z8IHupxga8x1qxsAcu0abae\nNQFUwoyEVUxafMuUdPS8D/br+A2RxaiohAISHLCT7gZVxDkGAT6j8z+nrpvQU/UN\nWMLQizGjv7qxXBNHCzo62mZGoZEJNdDP+FzNBdZ3cvYf16t7AWGd7X95I4gj+Muu\nJ+/VqdqDd17JFTvZ9czc05AsksPwxTMYrXRqfcn9CZeMqinr0kcJ727WtRU6I5wW\n52G21D52BCrBZJfTvh+SEoZyTlvV43mt7VIRxB+xxd3zP3OH7a0amTH9f33O6E9u\n23r00qyBiluwLGnD2Jca+8AhwsP9uDH8MkTlidPQXGwjrkVhs5+uKC9Zug7G0jEs\nqzjuEdhe2UGCaK30J/AMxR3brzIDTTxdJAwn7ZnvqQ6+7YU=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFEzCCAvsCFEXBseLI9/DDsUJVNhy2Nn2ORSZFMA0GCSqGSIb3DQEBCwUAMEYx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDQwOTA3MjYyNFoXDTM0MDQwNzA3\nMjYyNFowRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4g\nRnJhbmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUA\nA4ICDwAwggIKAoICAQCzTB9ETn6RgGHTEXkvXtUxCRtN5oz8eh1hD98OBOYrqC9g\npw90xurkpvrSKA/XGi2er+b+fDaMZnvOrUAmO5tkBeUv5VRArKAW1lTocTTFCXbb\nS1pMd4fxCePXnDed81MWFThYLr9zJ8KUeamYMBWq6lziAynTT9+bVaj5zkLC23u9\nEUqPFn8Kg3hdfLE5FPzeWqYREVrst++fnpjrt6ZWlHohA6P6BAplubBTKtcOcyDT\n1Y8Wg7OxgLex+gq5X+4YxzpuvPzRL0ucl4210FhPY+55zJ4Vw6lqQmY7lSloQQO2\nPbtkRvfpphy7J1aPaVpWXvVK2mv4tZtXzplOK3Uf1m4hVCA/fN1A5w89RCzdOacY\nSEvvFRhpFo4Iw0L3u2jvO71Rxpe+ATZkKzhk0TnwyRCQGYGYjRsNRAnGHbe10fPa\nT2NoBDmojg4S1LF6plwYYxTXvUsIWo9cKizMAYB6p6jCIcJUupulyChnSyTxvLOL\nKJr0YuU3ug4WEH1fw2JVgYi4X8TeGRgzLqBA9NudPJZjXh+fCcmdfIqNpuVujWJG\nqFjzG6NyixGJ5SqnIByFJd1UhF5UgJPLyseSrAn1Zqk4wKDtjPnahgDIqoLNjBh8\nvc2jbwTShwWWxnZK2iA+VzG4Y9l7I1FpANkJp+oug6tiCiBwYzMzP6rJjDBI6wID\nAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCmo1FG5Dudyy7Z0MgHY/dHe9EHWMl8FPIK\nzRxFrsAUivNMaXG+rUmsPgd0tNUdqEYQOpDYyu61ayo9dZUfjfoiePp/h6jiZrUa\nOxWtC53Em/UDoVz/hElRFwOYCz5O3ZQRC+c/CjSb+hsB93gi3bJIq3mIGCe9+jf1\nYD2GkaD99V5gZq5U5cTGsD9rxdAOT4AMEsxsUAUVULzhA+nQw4uqxFDe2AC8ZY9j\nAerCXu5BiLDcB3YnwnHaZ7MXbpWROSYQCmgojxUoiycAnZNJssFF2c/PVrRI3z0J\nvKhO7ViGs+JOqfp5jdgZO0SdKYT+n/TpF3Eqn/ugcXbyBBqS+Vj9liwTKNLqw7Um\nGWPOXoczYOp0iIv7qH6HbqpmZgwt4j1Xn7oMkZMv2fjYFCIS4PjifTVBaIm6FBg8\nXE0wGlWoMQtfxy56lPNub8Rnq6SyYtKJZfap8ukaPgL6mMJ7RYL1tjiHM9FRXS+7\nMrDo1bsQUoCqh6ZmWMyMfGycDflZg1DuAwwwJ06OmEBSqvkZ1sjZi/LGIAwKC2cw\nYsIJsKNw/rdE+ph7reH9RiDLJe+I2WehDZCqZ3dA5d8NjK2wGEo6G54YxKARpndl\nAlUeB/KJNFwLU74FPL2Jn9yfXTcqJbGs+AIpAu/OtwhPEkIizoeRO0cwqhURa2f/\nq5Qa16K2Bw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tests/cookie.key",
    "content": "Td\u001a>!鼽v"
  },
  {
    "path": "tests/generate.sh",
    "content": "openssl req -newkey rsa:4096 -keyout ca-key.pem -out ca.csr -days 3650 -nodes -subj \"/C=US/ST=CA/L=San Francisco/CN=localhost\"\nopenssl x509 -in ca.csr -out ca.pem -req -signkey ca-key.pem -days 3650\ncfssl gencert -config=int-config.json -ca=ca.pem -ca-key=ca-key.pem intermediate.json | cfssljson -bare intermediate\ncfssl gencert -config=test-config.json -ca intermediate.pem -ca-key intermediate-key.pem test.json | cfssljson -bare tls\nopenssl pkcs8 -topk8 -nocrypt -in tls-key.pem -out tls-pkcs8.pem\ncat tls.pem intermediate.pem ca.pem > chain.pem\n"
  },
  {
    "path": "tests/int-config.json",
    "content": "{\n  \"signing\": {\n    \"default\": {\n      \"ca_constraint\": {\n        \"is_ca\": true,\n        \"max_path_len\": 0,\n        \"max_path_len_zero\": true\n      },\n      \"expiry\": \"876000h\",\n      \"usages\": [\n        \"digital signature\",\n        \"cert sign\",\n        \"crl sign\",\n        \"signing\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "tests/intermediate-key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIBTkESHvag8yy5dO8xza5Zo52TRDDQgmqWBMpWsBRjmPoAoGCCqGSM49\nAwEHoUQDQgAEkAG0ZWrTy7A5X318OPE3fxtYwB7ZJ5l4EfEX9MSyO1zuO9m1wW/p\nIQLO72qrqS1SzKYK1INic/VsnZVkucRDhA==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/intermediate.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBSTCB8QIBADCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx\nFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0hhcHB5Q2VydCwgSW5j\nLjEfMB0GA1UECxMWSGFwcHlDZXJ0IEludGVybWVkaWF0ZTEXMBUGA1UEAxMOKGRl\ndiB1c2Ugb25seSkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASQAbRlatPLsDlf\nfXw48Td/G1jAHtknmXgR8Rf0xLI7XO472bXBb+khAs7vaqupLVLMpgrUg2Jz9Wyd\nlWS5xEOEoAAwCgYIKoZIzj0EAwIDRwAwRAIgWi05qNqepbhZRiPAK5zhqpbGWOXQ\n2V+lganS10JrHRkCIBlcIxyKDSAdsVDbAHe8Pk/V7bqeSzEMH9LkOQi8Xq2O\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "tests/intermediate.json",
    "content": "{\n  \"key\": {\n    \"algo\": \"ecdsa\",\n    \"size\": 256\n  },\n  \"names\": [\n    {\n      \"C\": \"US\",\n      \"L\": \"San Francisco\",\n      \"ST\": \"California\",\n      \"O\": \"HappyCert, Inc.\",\n      \"OU\": \"HappyCert Intermediate\"\n    }\n  ],\n  \"cn\": \"(dev use only)\"\n}\n"
  },
  {
    "path": "tests/intermediate.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIID3zCCAcegAwIBAgIUalsEFgf4uPaULSbhh3By7ebBUSQwDQYJKoZIhvcNAQEN\nBQAwRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjQwNDA5MDcyMTAwWhgPMjEy\nNDAzMTYwNzIxMDBaMIGOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p\nYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEYMBYGA1UEChMPSGFwcHlDZXJ0LCBJ\nbmMuMR8wHQYDVQQLExZIYXBweUNlcnQgSW50ZXJtZWRpYXRlMRcwFQYDVQQDEw4o\nZGV2IHVzZSBvbmx5KTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJABtGVq08uw\nOV99fDjxN38bWMAe2SeZeBHxF/TEsjtc7jvZtcFv6SECzu9qq6ktUsymCtSDYnP1\nbJ2VZLnEQ4SjRTBDMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEA\nMB0GA1UdDgQWBBTrtwDWy0fEyXnBkQALsS+47hZlcjANBgkqhkiG9w0BAQ0FAAOC\nAgEAl9aRyMaYMJSyWAI4BZmPSxzs8cIVa7lOnCLI3AevDOw4AEXTwK6VFZaehuWB\nVfodav9LrNQ8m/Po3K7AQwQYBghLwaQu7ISlI4pIGUeAZaIo90Bv0H2BJb3foHvi\n4+RI+CjVHXucKkgNU998RG6edwDsmdp963kKs3/AiU0vUgyUbuEzhzH4Dgqzt99w\n0ekf33fDGRvJ6k45oWZ7gkeT1gcbhCFafQJrMRKgoXcxPxwGxvn+usSd0EUuvMeF\nsoQJYZ/nMtSahC5qR2TRDunsUAtDtWk7LhdKQF9c+z8IHupxga8x1qxsAcu0abae\nNQFUwoyEVUxafMuUdPS8D/br+A2RxaiohAISHLCT7gZVxDkGAT6j8z+nrpvQU/UN\nWMLQizGjv7qxXBNHCzo62mZGoZEJNdDP+FzNBdZ3cvYf16t7AWGd7X95I4gj+Muu\nJ+/VqdqDd17JFTvZ9czc05AsksPwxTMYrXRqfcn9CZeMqinr0kcJ727WtRU6I5wW\n52G21D52BCrBZJfTvh+SEoZyTlvV43mt7VIRxB+xxd3zP3OH7a0amTH9f33O6E9u\n23r00qyBiluwLGnD2Jca+8AhwsP9uDH8MkTlidPQXGwjrkVhs5+uKC9Zug7G0jEs\nqzjuEdhe2UGCaK30J/AMxR3brzIDTTxdJAwn7ZnvqQ6+7YU=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tests/ntp-config.yaml",
    "content": "addr:\n  - \"0.0.0.0:123\"\n  - \"0.0.0.0:789\"\n  - \"[::]:123\"\ncookie_key_file: tests/cookie.key # TODO: store and read as pem files, or read bytes directly from file?\nmemc_url: memcache://memcache:11211\nmetrics_addr: server\nmetrics_port: 8000\nupstream_host: localhost\nupstream_port: 456\n"
  },
  {
    "path": "tests/ntp-upstream-config.yaml",
    "content": "addr:\n  - 127.0.0.1:456\ncookie_key_file: tests/cookie.key # TODO: store and read as pem files, or read bytes directly from file?\nmemc_url: memcache://memcache:11211\nmetrics_addr: server\nmetrics_port: 8002\n"
  },
  {
    "path": "tests/nts-ke-config.yaml",
    "content": "addr:\n  - \"[::]:4460\"\ntls_key_file: tests/tls-pkcs8.pem\ntls_cert_file: tests/chain.pem # Expect PEM.\ncookie_key_file: tests/cookie.key # TODO: store and read as pem files, or read bytes directly from file?\nmemc_url: memcache://memcache:11211\nnext_port: 123\nmetrics_addr: server\nmetrics_port: 8001\n"
  },
  {
    "path": "tests/test-config.json",
    "content": "{\n  \"signing\": {\n    \"default\": {\n      \"expiry\": \"876000h\",\n      \"usages\": [\n        \"digital signature\",\n        \"cert sign\",\n        \"crl sign\",\n        \"signing\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "tests/test.json",
    "content": "{\n    \"CN\": \"localhost\",\n    \"hosts\": [\n        \"server\",\n        \"localhost\",\n        \"bogus.com\",\n        \"*.localhost\"\n    ],\n    \"key\": {\n        \"algo\": \"ecdsa\",\n        \"size\": 256\n    },\n    \"names\": [\n        {\n            \"C\": \"US\",\n            \"ST\": \"CA\",\n            \"L\": \"San Francisco\",\n            \"O\": \"Cloudflare test\",\n            \"OU\": \"Crypto team\"\n        }\n    ]\n}\n\n"
  },
  {
    "path": "tests/tls-key.pem",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIBvLtCg67XQkDzWZDS4peNXy8r4Dguv+KYUVoOZEcCjGoAoGCCqGSM49\nAwEHoUQDQgAEcbTCrE0KE16aQz8DDTlEEHS+z1p3ILW0ib6mhuoHRPpPnQc3n014\nPEyx94AtYE3+LbcUqmHB00YjM7c2Q2EyDQ==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/tls-pkcs8.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgG8u0KDrtdCQPNZkN\nLil41fLyvgOC6/4phRWg5kRwKMahRANCAARxtMKsTQoTXppDPwMNOUQQdL7PWncg\ntbSJvqaG6gdE+k+dBzefTXg8TLH3gC1gTf4ttxSqYcHTRiMztzZDYTIN\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "tests/tls.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBeDCCAR8CAQAwdjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2NvMRgwFgYDVQQKEw9DbG91ZGZsYXJlIHRlc3QxFDASBgNV\nBAsTC0NyeXB0byB0ZWFtMRIwEAYDVQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIB\nBggqhkjOPQMBBwNCAARxtMKsTQoTXppDPwMNOUQQdL7PWncgtbSJvqaG6gdE+k+d\nBzefTXg8TLH3gC1gTf4ttxSqYcHTRiMztzZDYTINoEcwRQYJKoZIhvcNAQkOMTgw\nNjA0BgNVHREELTArggZzZXJ2ZXKCCWxvY2FsaG9zdIIJYm9ndXMuY29tggsqLmxv\nY2FsaG9zdDAKBggqhkjOPQQDAgNHADBEAiBGUmLvyO5eqzQIsEB2v4ysI8vDLrDV\nlRSABgL6YpJPOwIgSeSy73gwaBWRk/EVlahptbUSGcNPYa3m2rlAtTKX2Vo=\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "tests/tls.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICoDCCAkegAwIBAgIUW5W4GNGYJwryph3KHKkdLaeFdvMwCgYIKoZIzj0EAwIw\ngY4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMRgwFgYDVQQKEw9IYXBweUNlcnQsIEluYy4xHzAdBgNVBAsT\nFkhhcHB5Q2VydCBJbnRlcm1lZGlhdGUxFzAVBgNVBAMTDihkZXYgdXNlIG9ubHkp\nMCAXDTI0MDQwOTA3MjEwMFoYDzIxMjQwMzE2MDcyMTAwWjB2MQswCQYDVQQGEwJV\nUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGDAWBgNVBAoT\nD0Nsb3VkZmxhcmUgdGVzdDEUMBIGA1UECxMLQ3J5cHRvIHRlYW0xEjAQBgNVBAMT\nCWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHG0wqxNChNemkM/\nAw05RBB0vs9adyC1tIm+pobqB0T6T50HN59NeDxMsfeALWBN/i23FKphwdNGIzO3\nNkNhMg2jgZcwgZQwDgYDVR0PAQH/BAQDAgGGMAwGA1UdEwEB/wQCMAAwHQYDVR0O\nBBYEFJw0MuxNY1BtEB3oNMbPiwxDNrqZMB8GA1UdIwQYMBaAFOu3ANbLR8TJecGR\nAAuxL7juFmVyMDQGA1UdEQQtMCuCBnNlcnZlcoIJbG9jYWxob3N0gglib2d1cy5j\nb22CCyoubG9jYWxob3N0MAoGCCqGSM49BAMCA0cAMEQCIAJ0Os/bYUfH6nPO8f1E\nvVateUJXKaPuS6jD3i0eWQYbAiAyC4TPPr4S0wUXGf6RYwbTPG3sAvGAnuxpxlqB\nP0sZrQ==\n-----END CERTIFICATE-----\n"
  }
]