Full Code of cloudflare/cfnts for AI

master fa53b9844c36 cached
69 files
180.5 KB
52.0k tokens
276 symbols
1 requests
Download .txt
Repository: cloudflare/cfnts
Branch: master
Commit: fa53b9844c36
Files: 69
Total size: 180.5 KB

Directory structure:
gitextract_mvp4c08z/

├── .cargo/
│   └── config
├── .github/
│   └── workflows/
│       └── cfntsci.yml
├── .gitignore
├── CONTRIBUTING.md
├── Cargo.toml
├── Dockerfile.cfnts
├── Dockerfile.memcache
├── LICENSE
├── Makefile
├── README.md
├── RELEASE_NOTES
├── docker-compose.yaml
├── scripts/
│   ├── fill-memcached.py
│   ├── run_client.sh
│   ├── run_memcached.sh
│   └── run_server.sh
├── src/
│   ├── cfsock.rs
│   ├── cmd.rs
│   ├── cookie.rs
│   ├── error.rs
│   ├── key_rotator.rs
│   ├── main.rs
│   ├── metrics.rs
│   ├── ntp/
│   │   ├── client.rs
│   │   ├── mod.rs
│   │   ├── protocol.rs
│   │   └── server/
│   │       ├── config.rs
│   │       ├── mod.rs
│   │       └── ntp_server.rs
│   ├── nts_ke/
│   │   ├── client.rs
│   │   ├── mod.rs
│   │   ├── records/
│   │   │   ├── aead_algorithm.rs
│   │   │   ├── end_of_message.rs
│   │   │   ├── error.rs
│   │   │   ├── mod.rs
│   │   │   ├── new_cookie.rs
│   │   │   ├── next_protocol.rs
│   │   │   ├── port.rs
│   │   │   ├── server.rs
│   │   │   └── warning.rs
│   │   └── server/
│   │       ├── config.rs
│   │       ├── connection.rs
│   │       ├── ke_server.rs
│   │       ├── listener.rs
│   │       └── mod.rs
│   └── sub_command/
│       ├── client.rs
│       ├── ke_server.rs
│       ├── mod.rs
│       └── ntp_server.rs
└── tests/
    ├── ca-key.pem
    ├── ca.csr
    ├── ca.pem
    ├── chain.pem
    ├── cookie.key
    ├── generate.sh
    ├── int-config.json
    ├── intermediate-key.pem
    ├── intermediate.csr
    ├── intermediate.json
    ├── intermediate.pem
    ├── ntp-config.yaml
    ├── ntp-upstream-config.yaml
    ├── nts-ke-config.yaml
    ├── test-config.json
    ├── test.json
    ├── tls-key.pem
    ├── tls-pkcs8.pem
    ├── tls.csr
    └── tls.pem

================================================
FILE CONTENTS
================================================

================================================
FILE: .cargo/config
================================================
[build]
rustflags = ["-Ctarget-feature=+aes,+ssse3"]
rustdocflags = ["-Ctarget-feature=+aes,+ssse3"]
[test]
rustflags = ["-Ctarget-feature=+aes,+ssse3"]



================================================
FILE: .github/workflows/cfntsci.yml
================================================
---
name: cfntsCI

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  Testing:
    runs-on: ubuntu-latest
    steps:
      - name: Checking out
        uses: actions/checkout@v3
      - name: Setting up Rust
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          components: clippy, rustfmt
          override: true
      - name: Rust cache
        uses: Swatinem/rust-cache@v1
      - name: Linting
        run: cargo clippy --all-targets -- -D warnings
      - name: Format
        run: cargo fmt --all --check
      - name: Building
        run: cargo build --release
      - name: Testing
        run: cargo test -- --nocapture
  E2E:
    runs-on: ubuntu-latest
    steps:
      - name: Checking out
        uses: actions/checkout@v3
      - name: Run integration tests
        uses: isbang/compose-action@v1.4.1
        with:
          compose-file: "./docker-compose.yaml"
          up-flags: "--build --abort-on-container-exit --exit-code-from client"


================================================
FILE: .gitignore
================================================
/target
**/*.rs.bk
**/*.swp


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

We welcome your contributions. Note that your contributions must be licensed under the BSD-style license found in LICENSE.

To make our lives as well as yours easier please indicate when a PR is a work in progress vs. ready for review.


================================================
FILE: Cargo.toml
================================================
[package]
name        = "cfnts"
version     = "2019.6.0"
authors = [
    "Watson Ladd <watson@cloudflare.com>",
    "Gabbi Fisher <gabbi@cloudflare.com>",
    "Tanya Verma <tverma@cloudflare.com>",
    "Suphanat Chunhapanya <pop@cloudflare.com>",
]
edition     = "2018"

[dependencies]

byteorder   = "1.3.2"

# Used for command-line parsing and validation.
clap        = "2.33.0"

config      = "0.9.3"
crossbeam   = "0.7.3"
lazy_static = "1.4.0"
libc        = "0.2.65"
log         = "0.4.8"
memcache    = "0.13.1"
mio         = "0.6.19"
miscreant   = "0.4.2"
socket2     = "0.4.7"
nix         = "0.13.0"
prometheus  = "0.7.0"
rand        = "0.7.2"
ring        = "0.16.9"
rustls      = "0.16.0"
simple_logger = "1.3.0"

# More advanced logging system than `log`.
slog = { version = "2.5.2", features = [
    "max_level_trace",
    "release_max_level_debug",
]} # We configure at runtime

# Add scopes to the logging system.
slog-scope  = "4.3.0"

# Used for fowarding all the `log` crate logging to `slog_scope::logger()`.
slog-stdlog = "~4.0.0"

# A wrapper of `slog` to make logging more convenient. If you want to increase a version here,
# please make sure that `TerminalLoggerBuilder::build` doesn't return an error.
sloggers    = "=0.3.4"

webpki      = "0.21.0"
webpki-roots = "0.18.0"


================================================
FILE: Dockerfile.cfnts
================================================
FROM rust:1.69.0-bookworm as builder

COPY src    src
COPY .cargo .cargo
COPY Cargo.toml Cargo.lock ./

RUN cargo build --release

FROM debian:bookworm

COPY --from=builder ./target/release/cfnts ./target/release/cfnts


================================================
FILE: Dockerfile.memcache
================================================
FROM debian:bookworm

RUN apt-get update && \
    apt-get -y install memcached python3-memcache


================================================
FILE: LICENSE
================================================
Copyright (c) 2019, Cloudflare. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the
   distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



================================================
FILE: Makefile
================================================
SHELL=/bin/bash

TARGET_ARCHS ?= x86_64-unknown-linux-gnu

release:
	@git diff --quiet || { echo "Run in a clean repo"; exit 1; }
	cargo bump $(shell cfsetup release next-tag)
	cargo update
	git add Cargo.toml Cargo.lock
	git commit -m "Bump version in Cargo.toml to release tag"
	cfsetup release update

cf-package:
	for TARGET_ARCH in $(TARGET_ARCHS); do \
		echo $$TARGET_ARCH && \
		cargo deb --target $$TARGET_ARCH && \
		mv target/$$TARGET_ARCH/debian/*.deb ./ || \
		exit 1; \
	done



================================================
FILE: README.md
================================================
# cfnts

## DEPRECATION NOTICE
**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).**

cfnts is an implementation of the NTS protocol written in Rust.

**Prereqs**:
Rust

**Building**:

We use cargo to build the software. `docker-compose up` will spawn several Docker containers that run tests.

**Running**
Run the NTS client using `./target/release/cfnts client [--4 | --6] [-p <server-port>] [-c <trusted-cert>] [-n <other name>]  <server-hostname>`

Default port is `4460`. 

Using `-4` forces the use of ipv4 for all connections to the server, and using `-6` forces the use of ipv6. 
These two arguments are mutually exclusive. If neither of them is used, then the client will use whichever one
is supported by the server (preference for ipv6 if supported).

To run a server you will need a memcached compatible server, together with a script based on fill-memcached.py that will write
a new random key into /nts/nts-keys/ every hour and delete old ones. Then you can run the ntp server and the nts server.

This split and use of memcached exists to enable deployments where a small dedicated device serves NTP, while a bigger server carries
out the key exchange.

**Examples**:

1. `./target/release/cfnts client time.cloudflare.com`
2. `./target/release/cfnts client kong.rellim.com -p 123`


================================================
FILE: RELEASE_NOTES
================================================
2019.6.0
- 2019-06-04 CRYPTO-1040: Conform with HTTP 1.1 in metrics
- 2019-06-04 Bump version in Cargo.toml to release tag

2019.5.2
- 2019-05-31 CRYPTO-1016: Test with chain, avoid races with logger
- 2019-05-31 Bump version in Cargo.toml to release tag

2019.5.1
- 2019-05-29 CRYPTO-1007: Do not respond to non client mode packets
- 2019-05-29 CRYPTO-1006: Expose version in metrics
- 2019-05-30 Fix makefile to make release
- 2019-05-30 Bump version in Cargo.toml to release tag

2019.5.0
- 2019-02-26 Initial Commit
- 2019-02-26 Functioning Server
- 2019-04-08 Vendored deps
- 2019-04-08 Change config to use the vendored sources
- 2019-04-09 Starting point from NTS Hackathon
- 2019-04-09 Tell client what port to use
- 2019-04-09 Ensure the port is in our test config
- 2019-04-10 Change over to mio and import prometheus for metrics
- 2019-04-10 Silence warnings
- 2019-04-18 Undo type magic required by tokio
- 2019-04-18 Add logging and more error messages/codes. Also handle blocking.
- 2019-04-18 Include vendor changes
- 2019-04-12 Key rotation and cfsetup compose execution
- 2019-04-19 UDP portion
- 2019-04-23 Serve metrics
- 2019-04-24 Switch to slog for logging
- 2019-05-03 Various improvements
- 2019-05-06 CRYPTO-924: Smaller cookies
- 2019-05-09 CRYPTO-940: Change log level at runtime
- 2019-05-09 CRYPTO-922 Build debian packages
- 2019-05-08 Support specification of multiple listening addressess
- 2019-04-29 Implement connection to upstream process
- 2019-05-10 Use kernel timestamping for more accurate timing
- 2019-05-06 CRYPTO-891: Timeouts for nts_ke connections
- 2019-05-28 Use correct nonce length according to RFC 5116
- 2019-05-22 CRYPTO-957/CRYPTO-979: set socket options on our listening sockets
- 2019-05-24 CRYPTO-986 Use TLS 1.3 only for NTS
- 2019-05-23 CRYPTO-960 Better handling of configuration errors



================================================
FILE: docker-compose.yaml
================================================
version: "3.8"
services:
  server:
    build:
      context: .
      dockerfile: Dockerfile.cfnts
    depends_on:
      - memcache
    volumes:
      - ./tests:/tests
      - ./scripts:/scripts
    entrypoint: ["/scripts/run_server.sh"]
  client:
    build:
      context: .
      dockerfile: Dockerfile.cfnts
    depends_on:
      - server
    volumes:
      - ./tests:/tests
      - ./scripts:/scripts
    entrypoint: ["/scripts/run_client.sh"]
  memcache:
    build:
      context: .
      dockerfile: Dockerfile.memcache
    volumes:
      - ./scripts:/scripts
    entrypoint: ["/scripts/run_memcached.sh"]


================================================
FILE: scripts/fill-memcached.py
================================================
import memcache
import time
import math

print("filling memcache")
servers = ["localhost:11211"]
mc = memcache.Client(servers)
rand = open("/dev/urandom", "rb")

interval = 3600
now = int(math.floor(time.time()))
for i in range(-50, 4):
    epoch = int((math.floor(now/interval)+i)*interval)
    key = "/nts/nts-keys/%s"%epoch
    mc.set(key, rand.read(16))


================================================
FILE: scripts/run_client.sh
================================================
#!/bin/bash

# Retry for 10 times.
for i in $(seq 1 10); do
    if ./target/release/cfnts client server -c tests/ca.pem; then
        exit 0
    else
        echo "The server is unavailable - sleeping"
        sleep 1
    fi
done

exit 1


================================================
FILE: scripts/run_memcached.sh
================================================
#!/bin/bash
echo "Running memcache"
date "+%s"
memcached -u root &
sleep 2
python3 scripts/fill-memcached.py
echo "done"
wait $!


================================================
FILE: scripts/run_server.sh
================================================
#!/bin/bash
sleep 5
date "+%s"

RUST_BACKTRACE=1 ./target/release/cfnts ke-server -f tests/nts-ke-config.yaml &
RUST_BACKTRACE=1 ./target/release/cfnts ntp-server -f tests/ntp-upstream-config.yaml &
RUST_BACKTRACE=1 ./target/release/cfnts ntp-server -f tests/ntp-config.yaml


================================================
FILE: src/cfsock.rs
================================================
use libc::*;
use socket2::{Domain, Socket, Type};
use std::net::SocketAddr;
use std::os::unix::io::AsRawFd;

#[cfg(target_os = "linux")]
fn set_freebind(fd: c_int) -> Result<(), std::io::Error> {
    use std::io::{Error, ErrorKind};
    const IP_FREEBIND: libc::c_int = 0xf;
    match unsafe {
        setsockopt(
            fd,
            SOL_IP,
            IP_FREEBIND,
            &1u32 as *const u32 as *const c_void,
            std::mem::size_of::<u32>() as u32,
        )
    } {
        -1 => Err(std::io::Error::new(
            ErrorKind::Other,
            Error::last_os_error(),
        )),
        _ => Ok(()),
    }
}

#[cfg(not(target_os = "linux"))]
fn set_freebind(_fd: c_int) -> Result<(), std::io::Error> {
    Ok(()) // no op for mac build
}

pub fn tcp_listener(addr: &SocketAddr) -> Result<std::net::TcpListener, std::io::Error> {
    let domain = match addr {
        SocketAddr::V4(..) => Domain::IPV4,
        SocketAddr::V6(..) => Domain::IPV6,
    };
    let socket = Socket::new(domain, Type::STREAM, None)?;
    socket.set_reuse_address(true)?;
    set_freebind(socket.as_raw_fd())?;
    socket.bind(&(*addr).into())?;
    socket.listen(128)?;
    Ok(socket.into())
}

pub fn udp_listen(addr: &SocketAddr) -> Result<std::net::UdpSocket, std::io::Error> {
    let domain = match addr {
        SocketAddr::V4(..) => Domain::IPV4,
        SocketAddr::V6(..) => Domain::IPV6,
    };
    let socket = Socket::new(domain, Type::DGRAM, None)?;
    socket.set_reuse_address(true)?;
    set_freebind(socket.as_raw_fd())?;
    socket.bind(&(*addr).into())?;
    Ok(socket.into())
}


================================================
FILE: src/cmd.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! Command line argument definitions and validations.

use clap::{App, Arg, SubCommand};

/// Create the subcommand `client`.
fn create_clap_client_subcommand<'a, 'b>() -> App<'a, 'b> {
    // Arguments for `client` subcommand.
    let args = [
        // The hostname is always required and will immediately
        // follow the subcommand string.
        Arg::with_name("host")
            .index(1)
            .required(true)
            .help("NTS server's hostname (do not include port)"),
        // The rest will be passed as unrequired command-line options.
        Arg::with_name("port")
            .long("port")
            .short("p")
            .takes_value(true)
            .required(false)
            .help("Specifies NTS server's port. The default port number is 4460."),
        Arg::with_name("cert")
            .long("cert")
            .short("c")
            .takes_value(true)
            .required(false)
            .help("Specifies a path to the trusted certificate in PEM format."),
        Arg::with_name("ipv4")
            .long("ipv4")
            .short("4")
            .conflicts_with("ipv6")
            .help("Forces use of IPv4 only"),
        Arg::with_name("ipv6")
            .long("ipv6")
            .short("6")
            .conflicts_with("ipv4")
            .help("Forces use of IPv6 only"),
    ];

    // Create a new subcommand.
    SubCommand::with_name("client")
        .about("Initiates an NTS connection with the remote server")
        .args(&args)
}

/// Create the subcommand `ke-server`.
fn create_clap_ke_server_subcommand<'a, 'b>() -> App<'a, 'b> {
    // Arguments for `ke-server` subcommand.
    let args = [Arg::with_name("configfile")
        .long("file")
        .short("f")
        .takes_value(true)
        .required(false)
        .help(
            "Specifies a path to the configuration file. If the path is not specified, \
                   the system-wide configuration file (/etc/cfnts/ke-server.config) will be \
                   used instead",
        )];

    // Create a new subcommand.
    SubCommand::with_name("ke-server")
        .about("Runs NTS-KE server over TLS/TCP")
        .args(&args)
}

/// Create the subcommand `ntp-server`.
fn create_clap_ntp_server_subcommand<'a, 'b>() -> App<'a, 'b> {
    // Arguments for `ntp-server` subcommand.
    let args = [Arg::with_name("configfile")
        .long("file")
        .short("f")
        .takes_value(true)
        .required(false)
        .help(
            "Specifies a path to the configuration file. If the path is not specified, \
                   the system-wide configuration file (/etc/cfnts/ntp-server.config) will be \
                   used instead",
        )];

    // Create a new subcommand.
    SubCommand::with_name("ntp-server")
        .about("Interfaces with NTP using UDP")
        .args(&args)
}

/// Create the whole command-line configuration.
pub fn create_clap_command() -> App<'static, 'static> {
    App::new(env!("CARGO_PKG_NAME"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .version(env!("CARGO_PKG_VERSION"))
        .arg(
            Arg::with_name("debug")
                .long("debug")
                .short("d")
                .help("Turns on debug logging"),
        )
        .subcommands(vec![
            // List of all available subcommands.
            create_clap_client_subcommand(),
            create_clap_ke_server_subcommand(),
            create_clap_ntp_server_subcommand(),
        ])
}


================================================
FILE: src/cookie.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

use miscreant::aead;
use miscreant::aead::Aead;
use rand::Rng;

use std::convert::TryInto;
use std::fs::File;
use std::io;
use std::io::Read;

use crate::key_rotator::KeyId;

pub const COOKIE_SIZE: usize = 100;
#[derive(Debug, Copy, Clone)]
pub struct NTSKeys {
    pub c2s: [u8; 32],
    pub s2c: [u8; 32],
}

/// Cookie key.
#[derive(Clone, Debug)]
pub struct CookieKey(Vec<u8>);

impl CookieKey {
    /// Parse a cookie key from a file.
    ///
    /// # Errors
    ///
    /// There will be an error, if we cannot open the file.
    ///
    pub fn parse(filename: &str) -> Result<CookieKey, io::Error> {
        let mut file = File::open(filename)?;
        let mut buffer = Vec::new();

        file.read_to_end(&mut buffer)?;
        Ok(CookieKey(buffer))
    }

    /// Return a byte slice of a cookie key content.
    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_slice()
    }
}

// Only used in test.
#[cfg(test)]
impl From<&[u8]> for CookieKey {
    fn from(bytes: &[u8]) -> CookieKey {
        CookieKey(Vec::from(bytes))
    }
}

pub fn make_cookie(keys: NTSKeys, master_key: &[u8], key_id: KeyId) -> Vec<u8> {
    let mut nonce = [0; 16];
    rand::thread_rng().fill(&mut nonce);
    let mut plaintext = [0; 64];
    plaintext[..32].copy_from_slice(&keys.c2s[..32]);
    plaintext[32..64].copy_from_slice(&keys.s2c[..32]);
    let mut aead = aead::Aes128SivAead::new(master_key);
    let mut ciphertext = aead.seal(&nonce, &[], &plaintext);
    let mut out = Vec::new();
    out.extend(&key_id.to_be_bytes());
    out.extend(&nonce);
    out.append(&mut ciphertext);
    out
}

pub fn get_keyid(cookie: &[u8]) -> Option<KeyId> {
    if cookie.len() < 4 {
        None
    } else {
        Some(KeyId::from_be_bytes((&cookie[0..4]).try_into().unwrap()))
    }
}

fn unpack(pt: Vec<u8>) -> Option<NTSKeys> {
    if pt.len() != 64 {
        None
    } else {
        let mut key = NTSKeys {
            c2s: [0; 32],
            s2c: [0; 32],
        };
        key.c2s[..32].copy_from_slice(&pt[..32]);
        key.s2c[..32].copy_from_slice(&pt[32..64]);
        Some(key)
    }
}

pub fn eat_cookie(cookie: &[u8], key: &[u8]) -> Option<NTSKeys> {
    if cookie.len() < 40 {
        return None;
    }
    let ciphertext = &cookie[4..];
    let mut aead = aead::Aes128SivAead::new(key);
    let answer = aead.open(&ciphertext[0..16], &[], &ciphertext[16..]);
    match answer {
        Err(_) => None,
        Ok(buf) => unpack(buf),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn check_eq(a: NTSKeys, b: NTSKeys) {
        for i in 0..32 {
            assert_eq!(a.c2s[i], b.c2s[i]);
            assert_eq!(a.s2c[i], b.s2c[i]);
        }
    }

    #[test]
    fn check_cookie() {
        let test = NTSKeys {
            s2c: [9; 32],
            c2s: [10; 32],
        };

        let master_key = [0x07; 32];
        let key_id = KeyId::from_be_bytes([0x03; 4]);
        let mut cookie = make_cookie(test, &master_key, key_id);
        assert_eq!(cookie.len(), COOKIE_SIZE);
        assert_eq!(get_keyid(&cookie).unwrap(), key_id);
        check_eq(eat_cookie(&cookie, &master_key).unwrap(), test);

        cookie[9] = 0xff;
        cookie[10] = 0xff;
        assert!(eat_cookie(&cookie, &master_key).is_none());
    }
}


================================================
FILE: src/error.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! Traits for working with errors.

use std::error::Error;

/// `WrapError` allows the implementor to wrap its own error type in another error type.
pub trait WrapError<T: Error> {
    /// The returned type in case that the result has no error.
    type Item;

    /// Wrapping an error in the error type `T`.
    fn wrap_err(self) -> Result<Self::Item, T>;
}

/// Trait implementation for `config::ConfigError`.
// The reason that we have a lifetime bound 'static is that we want T to either contain no lifetime
// parameter or contain only the 'static lifetime parameter.
impl<S, T> WrapError<config::ConfigError> for Result<S, T>
where
    T: 'static + Error + Send + Sync,
{
    /// Don't change the returned type, in case there is no error.
    type Item = S;

    fn wrap_err(self) -> Result<S, config::ConfigError> {
        self.map_err(|error| config::ConfigError::Foreign(Box::new(error)))
    }
}

/// Trait implementation for `std::io::Error`.
// The reason that we have a lifetime bound 'static is that we want T to either contain no lifetime
// parameter or contain only the 'static lifetime parameter.
impl<S, T> WrapError<std::io::Error> for Result<S, T>
where
    T: 'static + Error + Send + Sync,
{
    /// Don't change the returned type, in case there is no error.
    type Item = S;

    fn wrap_err(self) -> Result<S, std::io::Error> {
        self.map_err(|error| std::io::Error::new(std::io::ErrorKind::Other, error))
    }
}


================================================
FILE: src/key_rotator.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! Key rotator implementation, which provides key synchronization with Memcached server.

use lazy_static::lazy_static;

#[cfg(not(test))]
use memcache::MemcacheError;

use prometheus::{opts, register_counter, register_int_counter, IntCounter};

use ring::hmac;

use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::thread;
#[cfg(not(test))]
use std::time::SystemTime;
use std::time::{Duration, UNIX_EPOCH};

use crate::cookie::CookieKey;

lazy_static! {
    static ref ROTATION_COUNTER: IntCounter =
        register_int_counter!("ntp_key_rotations_total", "Number of key rotations").unwrap();
    static ref FAILURE_COUNTER: IntCounter = register_int_counter!(
        "ntp_key_rotations_failed_total",
        "Number of failures in key rotation"
    )
    .unwrap();
}

/// Key id for `KeyRotator`.
// This struct should be `Clone` and `Copy` because the internal representation is just a `u32`.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct KeyId(u32);

impl KeyId {
    /// Create `KeyId` from raw `u32`.
    pub fn new(key_id: u32) -> KeyId {
        KeyId(key_id)
    }

    /// Create `KeyId` from a `u64` epoch. The 32 most significant bits of the parameter will be
    /// discarded.
    pub fn from_epoch(epoch: u64) -> KeyId {
        // This will discard the 32 most significant bits.
        let epoch_residue = epoch as u32;
        KeyId(epoch_residue)
    }

    /// Create `KeyId` from its representation as a byte array in big endian.
    pub fn from_be_bytes(bytes: [u8; 4]) -> KeyId {
        KeyId(u32::from_be_bytes(bytes))
    }

    /// Return the memory representation of this `KeyId` as a byte array in big endian.
    pub fn to_be_bytes(self) -> [u8; 4] {
        self.0.to_be_bytes()
    }
}

/// Error struct returned from `KeyRotator::rotate` method.
#[derive(Debug)]
pub enum RotateError {
    /// Error from Memcached server.
    MemcacheError(MemcacheError),
    /// Error when the Memcached server doesn't have a specified `KeyId`.
    KeyIdNotFound(KeyId),
}

impl From<MemcacheError> for RotateError {
    /// Wrap MemcacheError.
    fn from(error: MemcacheError) -> RotateError {
        RotateError::MemcacheError(error)
    }
}

/// Key rotator.
pub struct KeyRotator {
    /// URL of the Memcached server.
    memcached_url: String,

    /// Prefix for the Memcached key.
    prefix: String,

    // This property type needs to fit an Epoch time in seconds.
    /// Length of each period in seconds.
    duration: u64,

    // The number of forward and backward periods are `u64` because the timestamp is `u64` and the
    // duration can be as small as 1.
    /// The number of future periods that the rotator must cache their values from the
    /// Memcached server.
    number_of_forward_periods: u64,

    /// The number of previous periods that the rotator must cache their values from the
    /// Memcached server.
    number_of_backward_periods: u64,

    /// Cookie key that will be used as a MAC key of the rotator.
    master_key: CookieKey,

    /// Key id of the current period.
    latest_key_id: KeyId,

    /// Cache store.
    cache: HashMap<KeyId, hmac::Tag>,

    /// Logger.
    // TODO: since we don't use the logger now, I will put an `allow(dead_code)` here first. I will
    // remove it when it's used.
    #[allow(dead_code)]
    logger: slog::Logger,
}

impl KeyRotator {
    /// Connect to the Memcached server and sync some inital keys.
    pub fn connect(
        prefix: String,
        memcached_url: String,
        master_key: CookieKey,
        logger: slog::Logger,
    ) -> Result<KeyRotator, RotateError> {
        let mut rotator = KeyRotator {
            // Zero shouldn't be a valid KeyId. This is just a temporary value.
            latest_key_id: KeyId::new(0),
            // The cache should never be empty. This is just a temporary value.
            cache: HashMap::new(),

            // It seems that currently we don't have to customize the following three properties,
            // so I will just put default values.
            duration: 3600,
            number_of_forward_periods: 2,
            number_of_backward_periods: 24,

            // From parameters.
            prefix,
            memcached_url,
            master_key,
            logger,
        };

        // Maximum number of times that we want to try rotating the keys.
        let maximum_try = 5;

        // Try to rotate the keys up to 5 times to make sure that the rotator has some keys in it.
        // If it doesn't, we will not have any key to use.
        for try_number in 1.. {
            match rotator.rotate() {
                Err(error) => {
                    // Side-effect. Logging.
                    // Disable the log for now because the Error trait is not implemented for
                    // RotateError yet.
                    // error!(rotator.logger, "failure to initialize key rotation: {}", error);

                    // If it already tried a lot of times already, it may be a time to give up.
                    if try_number == maximum_try {
                        return Err(error);
                    }

                    // Wait for 5 seconds before retrying key rotation.
                    std::thread::sleep(std::time::Duration::from_secs(5));
                }
                // If it's a success, stop retrying.
                Ok(()) => break,
            }
        }

        Ok(rotator)
    }

    /// Rotate keys.
    ///
    /// # Panics
    ///
    /// If the system time is before the UNIX Epoch time.
    ///
    /// # Errors
    ///
    /// There is an error, if there is a connection problem with Memcached server or the Memcached
    /// server doesn't contain a key id it supposed to contain.
    ///
    pub fn rotate(&mut self) -> Result<(), RotateError> {
        // Side-effect. It's not related to the operation.
        ROTATION_COUNTER.inc();

        let duration = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("The system time must be after the UNIX Epoch time.");

        // The number of seconds since the Epoch time.
        let timestamp = duration.as_secs();

        // The current period number of the timestamp.
        let current_period = timestamp / self.duration;
        // The timestamp at the beginning of the current period.
        let current_epoch = current_period * self.duration;

        // The first period number that we want to iterate through.
        let first_period = current_period.saturating_sub(self.number_of_backward_periods);

        // The last period number that we want to iterate through.
        let last_period = current_period.saturating_add(self.number_of_forward_periods);

        let removed_period = first_period.saturating_sub(1);
        let removed_epoch = removed_period * self.duration;
        self.cache_remove(KeyId::from_epoch(removed_epoch));

        // Connecting to memcached. I have to add [..] because it seems that Rust is not smart
        // enough to do auto-dereference.
        let mut client = memcache::Client::connect(&self.memcached_url[..])?;

        for period_number in first_period..=last_period {
            // The timestamp at the beginning of the period.
            let epoch = period_number * self.duration;

            let memcached_key = format!("{}/{}", self.prefix, epoch);
            let memcached_value: Option<Vec<u8>> = client.get(&memcached_key)?;

            let key_id = KeyId::from_epoch(epoch);
            match memcached_value {
                Some(value) => self.cache_insert(key_id, value.as_slice()),
                None => {
                    FAILURE_COUNTER.inc();
                    return Err(RotateError::KeyIdNotFound(key_id));
                }
            }
        }

        // Not all of our friends may have gotten the same forwards keys as we did.
        self.latest_key_id = KeyId::from_epoch(current_epoch);

        Ok(())
    }

    /// Add an entry to the cache.
    // It should be private. Don't make it public.
    fn cache_insert(&mut self, key_id: KeyId, value: &[u8]) {
        // Create a MAC key.
        let mac_key = hmac::Key::new(hmac::HMAC_SHA256, self.master_key.as_bytes());
        // Generating a MAC tag with a MAC key.
        let tag = hmac::sign(&mac_key, value);

        self.cache.insert(key_id, tag);
    }

    /// Remove an entry from the cache.
    // It should be private. Don't make it public.
    fn cache_remove(&mut self, key_id: KeyId) {
        self.cache.remove(&key_id);
    }

    /// Return the latest key id and hmac tag of the rotator.
    pub fn latest_key_value(&self) -> (KeyId, &hmac::Tag) {
        // This unwrap cannot panic because the HashMap will always contain the latest key id.
        (self.latest_key_id, self.get(self.latest_key_id).unwrap())
    }

    /// Return an entry in the cache using a key id.
    pub fn get(&self, key_id: KeyId) -> Option<&hmac::Tag> {
        self.cache.get(&key_id)
    }
}

pub fn periodic_rotate(rotor: Arc<RwLock<KeyRotator>>) {
    let mut rotor = rotor;
    thread::spawn(move || loop {
        inner(&mut rotor);
        let restlen = read_sleep(&rotor);
        thread::sleep(Duration::from_secs(restlen));
    });
}

fn inner(rotor: &mut Arc<RwLock<KeyRotator>>) {
    let _ = rotor.write().unwrap().rotate();
}

fn read_sleep(rotor: &Arc<RwLock<KeyRotator>>) -> u64 {
    rotor.read().unwrap().duration
}

// ------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------

#[cfg(test)]
use ::memcache::MemcacheError;
#[cfg(test)]
use test::memcache;
#[cfg(test)]
use test::SystemTime;

#[cfg(test)]
mod test {
    use super::*;

    use ::memcache::MemcacheError;
    use lazy_static::lazy_static;
    use sloggers::null::NullLoggerBuilder;
    use sloggers::Build;
    use std::sync::Mutex;
    use std::time::Duration;

    // Mocking memcache.
    pub mod memcache {
        use super::*;
        use std::collections::HashMap;

        lazy_static! {
            pub static ref HASH_MAP: Mutex<HashMap<String, Vec<u8>>> = Mutex::new(HashMap::new());
        }
        pub struct Client;
        impl Client {
            pub fn connect(_url: &str) -> Result<Client, MemcacheError> {
                Ok(Client)
            }
            pub fn get(&mut self, key: &str) -> Result<Option<Vec<u8>>, MemcacheError> {
                Ok(HASH_MAP.lock().unwrap().get(&String::from(key)).cloned())
            }
        }
    }

    // Mocking SystemTime.
    lazy_static! {
        pub static ref NOW: Mutex<u64> = Mutex::new(0);
    }
    pub struct SystemTime;
    impl SystemTime {
        pub fn now() -> std::time::SystemTime {
            let now = NOW.lock().unwrap();
            let duration = Duration::new(*now, 0);
            UNIX_EPOCH.checked_add(duration).unwrap()
        }
    }

    #[test]
    fn test_rotation() {
        use self::memcache::HASH_MAP;

        let mut hash_map = HASH_MAP.lock().unwrap();
        hash_map.insert("test/1".to_string(), vec![1; 32]);
        hash_map.insert("test/2".to_string(), vec![2; 32]);
        hash_map.insert("test/3".to_string(), vec![3; 32]);
        hash_map.insert("test/4".to_string(), vec![4; 32]);
        drop(hash_map);

        let mut rotator = KeyRotator {
            memcached_url: String::from("unused"),
            prefix: String::from("test"),
            duration: 1,
            number_of_forward_periods: 1,
            number_of_backward_periods: 1,
            master_key: CookieKey::from(&[0, 32][..]),
            latest_key_id: KeyId::from_be_bytes([1, 2, 3, 4]),
            cache: HashMap::new(),
            logger: NullLoggerBuilder.build().unwrap(),
        };

        *NOW.lock().unwrap() = 2;
        // No error because the hash map has "test/1", "test/2", and "test/3".
        rotator.rotate().unwrap();
        let old_latest = rotator.latest_key_id;

        *NOW.lock().unwrap() = 3;
        // No error because the hash map has "test/2", "test/3", and "test/4".
        rotator.rotate().unwrap();
        let new_latest = rotator.latest_key_id;

        // The key id should change.
        assert_ne!(old_latest, new_latest);

        *NOW.lock().unwrap() = 1;
        // Return error because the hash map doesn't have "test/0".
        rotator.rotate().unwrap_err();

        *NOW.lock().unwrap() = 4;
        // Return error because the hash map doesn't have "test/5".
        rotator.rotate().unwrap_err();
    }
}


================================================
FILE: src/main.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

extern crate lazy_static;
extern crate log;
extern crate prometheus;
extern crate slog;
extern crate slog_scope;
extern crate slog_stdlog;
extern crate sloggers;

mod cfsock;
mod cmd;
mod cookie;
mod error;
mod key_rotator;
mod metrics;
mod ntp;
mod nts_ke;
mod sub_command;

use sloggers::terminal::{Destination, TerminalLoggerBuilder};
use sloggers::types::Severity;
use sloggers::Build;

use std::process;

/// Create a logger to be used throughout cfnts.
fn create_logger(matches: &clap::ArgMatches<'_>) -> slog::Logger {
    let mut builder = TerminalLoggerBuilder::new();

    // Default severity level is info.
    builder.level(Severity::Info);
    // Write all logs to stderr.
    builder.destination(Destination::Stderr);

    // If in debug mode, change severity level to debug.
    if matches.is_present("debug") {
        builder.level(Severity::Debug);
    }

    // According to `sloggers-0.3.2` source code, the function doesn't return an error at all.
    // There should be no problem unwrapping here. It has a return type `Result` because it's a
    // signature for `sloggers::Build` trait.
    builder
        .build()
        .expect("BUG: TerminalLoggerBuilder::build shouldn't return an error.")
}

/// The entry point of cfnts.
fn main() {
    // According to the documentation of `get_matches`, if the parsing fails, an error will be
    // displayed to the user and the process will exit with an error code.
    let matches = cmd::create_clap_command().get_matches();

    let logger = create_logger(&matches);

    // After calling this, slog_stdlog will forward all the `log` crate logging to
    // `slog_scope::logger()`.
    //
    // The returned error type is `SetLoggerError` which, according to the lib doc, will be
    // returned only when `set_logger` has been called already which should be our bug if it
    // has already been called.
    //
    slog_stdlog::init().expect("BUG: `set_logger` has already been called");

    // _scope_guard can be used to reset the global logger. You can do it by just dropping it.
    let _scope_guard = slog_scope::set_global_logger(logger.clone());

    if matches.subcommand.is_none() {
        eprintln!(
            "please specify a valid subcommand: only client, ke-server, and ntp-server \
                   are supported."
        );
        process::exit(1);
    }

    if let Some(ke_server_matches) = matches.subcommand_matches("ke-server") {
        sub_command::ke_server::run(ke_server_matches);
    }
    if let Some(ntp_server_matches) = matches.subcommand_matches("ntp-server") {
        sub_command::ntp_server::run(ntp_server_matches);
    }
    if let Some(client_matches) = matches.subcommand_matches("client") {
        sub_command::client::run(client_matches);
    }
}


================================================
FILE: src/metrics.rs
================================================
// Our goal is to shove data at prometheus in response to requests.
use lazy_static::lazy_static;
use prometheus::{self, register_int_gauge, Encoder, __register_gauge, labels, opts};
use std::io;
use std::io::{BufRead, BufReader, Write};
use std::net;
use std::thread;

use slog::error;

#[derive(Clone, Debug)]
pub struct MetricsConfig {
    pub port: u16,
    pub addr: String,
}

const VERSION: &str = env!("CARGO_PKG_VERSION");

lazy_static! {
    static ref VERSION_INFO: prometheus::IntGauge = register_int_gauge!(opts!(
        "build_info",
        "Build and version information",
        labels! {
            "version" => VERSION,
        }
    ))
    .unwrap();
}

fn wait_for_req_or_eof(dest: &net::TcpStream, logger: slog::Logger) -> Result<(), io::Error> {
    let mut reader = BufReader::new(dest);
    let mut req_line = String::new();
    let mut done = false;
    while !done {
        req_line.clear();
        let res = reader.read_line(&mut req_line);
        if let Err(e) = res {
            error!(logger, "failure to read request {:?}", e);
            return Err(e);
        }
        if let Ok(0) = res {
            // We got EOF ahead of request coming in
            // but will try to answer anyway
            done = true;
        }
        if req_line == "\r\n" {
            done = true; // terminates the request
        }
    }
    Ok(())
}

fn scrape_result() -> String {
    let mut buffer = Vec::new();
    let encoder = prometheus::TextEncoder::new();
    let families = prometheus::gather();
    encoder.encode(&families, &mut buffer).unwrap();
    "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\n\r\n".to_owned()
        + &String::from_utf8(buffer).unwrap()
}

fn serve_metrics(mut dest: net::TcpStream, logger: slog::Logger) {
    if let Err(e) = wait_for_req_or_eof(&dest, logger.clone()) {
        error!(
            logger,
            "error in wait_for_req_or_eof: {:?}, unable to serve metrics", e
        );
        if let Err(e) = dest.shutdown(net::Shutdown::Both) {
            error!(logger, "shutting down TcpStream failed with error: {:?}", e);
        }
        return;
    }
    if let Err(e) = dest.write(scrape_result().as_bytes()) {
        error!(
            logger,
            "write to TcpStream failed with error: {:?}, unable to serve metrics", e
        );
    }
    if let Err(e) = dest.shutdown(net::Shutdown::Write) {
        error!(logger, "failure to shut down {:?}", e);
    }
}

/// Runs the metric server on the address and port set in config
pub fn run_metrics(conf: MetricsConfig, logger: &slog::Logger) -> Result<(), std::io::Error> {
    VERSION_INFO.set(1);
    let accept = net::TcpListener::bind((conf.addr.as_str(), conf.port))?;
    for stream in accept.incoming() {
        match stream {
            Ok(conn) => {
                let log_metrics = logger.new(slog::o!("component"=>"serve_metrics"));
                thread::spawn(move || {
                    serve_metrics(conn, log_metrics);
                });
            }
            Err(err) => return Err(err),
        }
    }
    Err(io::Error::new(io::ErrorKind::Other, "unreachable"))
}


================================================
FILE: src/ntp/client.rs
================================================
use crate::nts_ke::client::NtsKeResult;

use miscreant::aead::Aead;
use miscreant::aead::Aes128SivAead;
use rand::Rng;
use slog::debug;
use std::error::Error;
use std::fmt;

use std::net::{ToSocketAddrs, UdpSocket};
use std::time::{Duration, SystemTime};

use super::protocol::parse_nts_packet;
use super::protocol::serialize_nts_packet;
use super::protocol::LeapState;
use super::protocol::NtpExtension;
use super::protocol::NtpExtensionType::*;
use super::protocol::NtpPacketHeader;
use super::protocol::NtsPacket;
use super::protocol::PacketMode::Client;
use super::protocol::TWO_POW_32;
use super::protocol::UNIX_OFFSET;

use self::NtpClientError::*;

const BUFF_SIZE: usize = 2048;
const TIMEOUT: Duration = Duration::from_secs(10);

pub struct NtpResult {
    pub stratum: u8,
    pub time_diff: f64,
}

#[derive(Debug, Clone)]
pub enum NtpClientError {
    NoIpv4AddrFound,
    NoIpv6AddrFound,
    InvalidUid,
}

impl std::error::Error for NtpClientError {
    fn description(&self) -> &str {
        match self {
            Self::NoIpv4AddrFound => {
                "Connection to server failed: IPv4 address could not be resolved"
            }
            Self::NoIpv6AddrFound => {
                "Connection to server failed: IPv6 address could not be resolved"
            }
            Self::InvalidUid => {
                "Connection to server failed: server response UID did not match client request UID"
            }
        }
    }
    fn cause(&self) -> Option<&dyn std::error::Error> {
        None
    }
}

impl std::fmt::Display for NtpClientError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Ntp Client Error ")
    }
}

/// Returns a float representing the system time as NTP
fn system_to_ntpfloat(time: SystemTime) -> f64 {
    let unix_time = time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); // Safe absent time machines
    let unix_offset = Duration::new(UNIX_OFFSET, 0);
    let epoch_time = unix_offset + unix_time;
    epoch_time.as_secs() as f64 + (epoch_time.subsec_nanos() as f64) / 1.0e9
}

/// Returns a float representing the ntp timestamp
fn timestamp_to_float(time: u64) -> f64 {
    let ts_secs = time >> 32;
    let ts_frac = time - (ts_secs << 32);
    (ts_secs as f64) + (ts_frac as f64) / TWO_POW_32
}

/// Run the NTS client with the given data from key exchange
pub fn run_nts_ntp_client(
    logger: &slog::Logger,
    state: NtsKeResult,
) -> Result<NtpResult, Box<dyn Error>> {
    let mut ip_addrs = (state.next_server.as_str(), state.next_port).to_socket_addrs()?;
    let addr;
    let socket;
    if let Some(use_ipv4) = state.use_ipv4 {
        if use_ipv4 {
            // mandated to use ipv4
            addr = ip_addrs.find(|&x| x.is_ipv4());
            if addr.is_none() {
                return Err(Box::new(NoIpv4AddrFound));
            }
            socket = UdpSocket::bind("0.0.0.0:0");
        } else {
            // mandated to use ipv6
            addr = ip_addrs.find(|&x| x.is_ipv6());
            if addr.is_none() {
                return Err(Box::new(NoIpv6AddrFound));
            }
            socket = UdpSocket::bind("[::]:0");
        }
    } else {
        // sniff whichever one is supported
        addr = ip_addrs.next();
        // check if this address is ipv4 or ipv6
        if addr.unwrap().is_ipv6() {
            socket = UdpSocket::bind("[::]:0");
        } else {
            socket = UdpSocket::bind("0.0.0.0:0");
        }
    }

    let socket = socket.unwrap();
    socket.set_read_timeout(Some(TIMEOUT))?;
    socket.set_write_timeout(Some(TIMEOUT))?;
    let mut send_aead = Aes128SivAead::new(&state.keys.c2s);
    let mut recv_aead = Aes128SivAead::new(&state.keys.s2c);
    let header = NtpPacketHeader {
        leap_indicator: LeapState::NoLeap,
        version: 4,
        mode: Client,
        stratum: 0,
        poll: 0,
        precision: 0x20,
        root_delay: 0,
        root_dispersion: 0,
        reference_id: 0,
        reference_timestamp: 0xdeadbeef,
        origin_timestamp: 0,
        receive_timestamp: 0,
        transmit_timestamp: 0,
    };
    let mut unique_id: Vec<u8> = vec![0; 32];
    rand::thread_rng().fill(&mut unique_id[..]);
    let auth_exts = vec![
        NtpExtension {
            ext_type: UniqueIdentifier,
            contents: unique_id.clone(),
        },
        NtpExtension {
            ext_type: NTSCookie,
            contents: state.cookies[0].clone(),
        },
    ];
    let packet = NtsPacket {
        header,
        auth_exts,
        auth_enc_exts: vec![],
    };
    socket.connect(addr.unwrap())?;
    let wire_packet = &serialize_nts_packet::<Aes128SivAead>(packet, &mut send_aead);
    let t1 = system_to_ntpfloat(SystemTime::now());
    socket.send(wire_packet)?;
    debug!(logger, "transmitting packet");
    let mut buff = [0; BUFF_SIZE];
    let (size, _origin) = socket.recv_from(&mut buff)?;
    let t4 = system_to_ntpfloat(SystemTime::now());
    debug!(logger, "received packet");
    let received = parse_nts_packet::<Aes128SivAead>(&buff[0..size], &mut recv_aead);
    match received {
        Err(x) => Err(Box::new(x)),
        Ok(packet) => {
            // check if server response contains the same UniqueIdentifier as client request
            let resp_unique_id = packet.auth_exts[0].clone().contents;
            if resp_unique_id != unique_id {
                return Err(Box::new(InvalidUid));
            }

            Ok(NtpResult {
                stratum: packet.header.stratum,
                time_diff: ((timestamp_to_float(packet.header.receive_timestamp) - t1)
                    + (timestamp_to_float(packet.header.transmit_timestamp) - t4))
                    / 2.0,
            })
        }
    }
}


================================================
FILE: src/ntp/mod.rs
================================================
pub mod client;
pub mod protocol;
pub mod server;


================================================
FILE: src/ntp/protocol.rs
================================================
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use miscreant::aead::Aead;
use rand::Rng;

use std::io::{Cursor, Error, ErrorKind, Read, Write};
use std::panic;

use self::LeapState::*;
use self::NtpExtensionType::*;
use self::PacketMode::*;

/// These numbers are from RFC 5905
pub const VERSION: u8 = 4;
pub const UNIX_OFFSET: u64 = 2_208_988_800;
pub const PHI: f64 = 15e-6;
/// TWO_POW_32 is a floating point power of two (2**32)
pub const TWO_POW_32: f64 = 4294967296.0;

const HEADER_SIZE: u64 = 48;
const NONCE_LEN: usize = 16;
const EXT_TYPE_UNIQUE_IDENTIFIER: u16 = 0x0104;
const EXT_TYPE_NTS_COOKIE: u16 = 0x0204;
const EXT_TYPE_NTS_COOKIE_PLACEHOLDER: u16 = 0x0304;
const EXT_TYPE_NTS_AUTHENTICATOR: u16 = 0x0404;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LeapState {
    NoLeap = 0,
    Positive = 1,
    Negative = 2,
    Unknown = 3,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PacketMode {
    SymmetricActive = 1,
    SymmetricPassive = 2,
    Client = 3, // We send Mode 3 packets and recieve Mode 4. Check the errata on 5905!
    Server = 4,
    Broadcast = 5,
    Invalid,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum NtpExtensionType {
    UniqueIdentifier,
    NTSCookie,
    NTSCookiePlaceholder,
    NTSAuthenticator,
    Unknown(u16),
}

fn wire_type(x: NtpExtensionType) -> u16 {
    match x {
        UniqueIdentifier => EXT_TYPE_UNIQUE_IDENTIFIER,
        NTSCookie => EXT_TYPE_NTS_COOKIE,
        NTSCookiePlaceholder => EXT_TYPE_NTS_COOKIE_PLACEHOLDER,
        NTSAuthenticator => EXT_TYPE_NTS_AUTHENTICATOR,
        NtpExtensionType::Unknown(y) => y,
    }
}

fn type_from_wire(ext: u16) -> NtpExtensionType {
    match ext {
        EXT_TYPE_UNIQUE_IDENTIFIER => UniqueIdentifier,
        EXT_TYPE_NTS_COOKIE => NTSCookie,
        EXT_TYPE_NTS_COOKIE_PLACEHOLDER => NTSCookiePlaceholder,
        EXT_TYPE_NTS_AUTHENTICATOR => NTSAuthenticator,
        y => NtpExtensionType::Unknown(y),
    }
}

/// Header of an NTP and NTS packet
/// See RFC 5905 for meaning of these fields
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NtpPacketHeader {
    pub leap_indicator: LeapState,
    pub version: u8,
    pub mode: PacketMode,
    pub stratum: u8,
    pub poll: i8,
    pub precision: i8,
    pub root_delay: u32,
    pub root_dispersion: u32,
    pub reference_id: u32,
    pub reference_timestamp: u64,
    pub origin_timestamp: u64,
    pub receive_timestamp: u64,
    pub transmit_timestamp: u64,
}

/// The authenticating extension needs to be treated
/// differently from all other extensions. We can't write it out
/// until we know the data it authenticates, so the nts parsing
/// and writing functions are a bit more complicated.

/// It is up to the constructor to ensure that the contents of
/// extensions are padded to length a multiple of 4 greater then or
/// equal to 16, or 28 if they are the last extension.
#[derive(Debug, Clone)]
pub struct NtpExtension {
    pub ext_type: NtpExtensionType,
    pub contents: Vec<u8>,
}

/// An NTS packet has authenticated extensions and authenticated and encrypted
/// extensions. All other extensions are ignored.
#[derive(Debug, Clone)]
pub struct NtsPacket {
    pub header: NtpPacketHeader,
    pub auth_exts: Vec<NtpExtension>,
    pub auth_enc_exts: Vec<NtpExtension>,
}

/// An NTP packet has a header and optional numbers of extensions. We ignore
/// legacy mac entirely.
#[derive(Debug, Clone)]
pub struct NtpPacket {
    pub header: NtpPacketHeader,
    pub exts: Vec<NtpExtension>,
}

/// The first byte encodes these three fields in a bitpacked format.
/// These 4 helper functions deal with that.
/// See RFC 5905 Figure 8.
fn parse_leap_indicator(first: u8) -> LeapState {
    match first >> 6 {
        0 => NoLeap,
        1 => Positive,
        2 => Negative,
        _ => LeapState::Unknown,
    }
}

fn parse_version(first: u8) -> u8 {
    (first & 0x38) >> 3
}

fn parse_mode(first: u8) -> PacketMode {
    let modnum = first & 0x07;
    match modnum {
        1 => SymmetricActive,
        2 => SymmetricPassive,
        3 => Client,
        4 => Server,
        5 => Broadcast,
        _ => Invalid,
    }
}

/// The first byte packs 3 fields in.
fn create_first(leap: LeapState, version: u8, mode: PacketMode) -> u8 {
    ((leap as u8) << 6) | ((version << 3) & 0x38) | ((mode as u8) & 0x07)
}

/// Extract an NTP packet header from packet and return an error if it cannot be done.
pub fn parse_packet_header(packet: &[u8]) -> Result<NtpPacketHeader, std::io::Error> {
    let mut buff = Cursor::new(packet);
    if packet.len() < 48 {
        Err(Error::new(ErrorKind::InvalidInput, "Too short"))
    } else {
        let first = buff.read_u8()?;
        let stratum = buff.read_u8()?;
        let poll = buff.read_i8()?;
        let precision = buff.read_i8()?;
        let root_delay = buff.read_u32::<BigEndian>()?;
        let root_dispersion = buff.read_u32::<BigEndian>()?;
        let reference_id = buff.read_u32::<BigEndian>()?;
        let reference_timestamp = buff.read_u64::<BigEndian>()?;
        let origin_timestamp = buff.read_u64::<BigEndian>()?;
        let receive_timestamp = buff.read_u64::<BigEndian>()?;
        let transmit_timestamp = buff.read_u64::<BigEndian>()?;
        Ok(NtpPacketHeader {
            leap_indicator: parse_leap_indicator(first),
            version: parse_version(first),
            mode: parse_mode(first),
            stratum,
            poll,
            precision,
            root_delay,
            root_dispersion,
            reference_id,
            reference_timestamp,
            origin_timestamp,
            receive_timestamp,
            transmit_timestamp,
        })
    }
}

/// serialize_header returns a Vec<u8> containing the wire
/// format of the header.
pub fn serialize_header(head: NtpPacketHeader) -> Vec<u8> {
    let mut buff = Cursor::new(Vec::new());
    let first = create_first(head.leap_indicator, head.version, head.mode);
    buff.write_u8(first)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_u8(head.stratum)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_i8(head.poll)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_i8(head.precision)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_u32::<BigEndian>(head.root_delay)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_u32::<BigEndian>(head.root_dispersion)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_u32::<BigEndian>(head.reference_id)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_u64::<BigEndian>(head.reference_timestamp)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_u64::<BigEndian>(head.origin_timestamp)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_u64::<BigEndian>(head.receive_timestamp)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.write_u64::<BigEndian>(head.transmit_timestamp)
        .expect("write to buffer failed, unable to serialize NtpPacketHeader");
    buff.into_inner()
}

/// parse_ntp_packet parses an NTP packet
pub fn parse_ntp_packet(buff: &[u8]) -> Result<NtpPacket, std::io::Error> {
    let header = parse_packet_header(buff)?;
    let exts = parse_extensions(&buff[48..])?;
    Ok(NtpPacket { header, exts })
}

/// Properly parsing NTP extensions in accordance with RFC 7822 is not necessary
/// since the legacy MAC will never be used by this code.
fn parse_extensions(buff: &[u8]) -> Result<Vec<NtpExtension>, std::io::Error> {
    let mut reader = Cursor::new(buff);
    let mut retval = Vec::new();
    while buff.len() - reader.position() as usize >= 4 {
        let ext_type = reader.read_u16::<BigEndian>()?;
        let ext_len = reader.read_u16::<BigEndian>()?;
        if ext_len % 4 != 0 {
            return Err(Error::new(
                ErrorKind::InvalidInput,
                "extension not on word boundary",
            ));
        }
        if ext_len < 4 {
            return Err(Error::new(ErrorKind::InvalidInput, "extension too short"));
        }
        let mut contents: Vec<u8> = vec![0; (ext_len - 4) as usize];
        reader.read_exact(&mut contents)?;
        retval.push(NtpExtension {
            ext_type: type_from_wire(ext_type),
            contents,
        })
    }
    Ok(retval)
}

/// serialize_ntp_packet returns the packet in wire format.
pub fn serialize_ntp_packet(pack: NtpPacket) -> Vec<u8> {
    let mut buff = Cursor::new(Vec::new());
    buff.write_all(&serialize_header(pack.header))
        .expect("buffer write failed; can't serialize NtpPacket");
    buff.write_all(&serialize_extensions(pack.exts))
        .expect("buffer write failed; can't serialize NtpPacket");
    buff.into_inner()
}

fn serialize_extensions(exts: Vec<NtpExtension>) -> Vec<u8> {
    let mut buff = Cursor::new(Vec::new());
    for ext in exts {
        if ext.contents.len() % 4 != 0 {
            panic!("extension is the wrong length")
        }
        buff.write_u16::<BigEndian>(wire_type(ext.ext_type))
            .expect("buffer write failed; can't serialize Ntp Extensions");
        buff.write_u16::<BigEndian>((ext.contents.len() + 4) as u16)
            .expect("buffer write failed; can't serialize Ntp Extensions"); // The length includes the header
        buff.write_all(&ext.contents)
            .expect("buffer write failed; can't serialize Ntp Extensions");
    }
    buff.into_inner()
}

/// has_extension returns true if the packet has an extension of the right kind
pub fn has_extension(pack: &NtpPacket, kind: NtpExtensionType) -> bool {
    pack.exts
        .clone()
        .into_iter()
        .any(|ext| ext.ext_type == kind)
}

/// is_nts_packet returns true if this packet is plausibly an NTS packet.
/// TODO: enforce rules tighter about uniqueness of some of these extensions.
pub fn is_nts_packet(pack: &NtpPacket) -> bool {
    has_extension(pack, NTSCookie)
        && has_extension(pack, NTSAuthenticator)
        && has_extension(pack, UniqueIdentifier)
}

/// extract_extension retrieves the extension if it exists, and else none.
pub fn extract_extension(pack: &NtpPacket, kind: NtpExtensionType) -> Option<NtpExtension> {
    pack.exts
        .clone()
        .into_iter()
        .find(|ext| ext.ext_type == kind)
}

/// parse_nts_packet parses an NTS packet.
pub fn parse_nts_packet<T: Aead>(
    buff: &[u8],
    decryptor: &mut T,
) -> Result<NtsPacket, std::io::Error> {
    let header = parse_packet_header(buff)?;
    let mut reader = Cursor::new(buff);
    let mut auth_exts = Vec::new();
    reader.set_position(HEADER_SIZE);
    while buff.len() - reader.position() as usize >= 4 {
        let ext_type = reader.read_u16::<BigEndian>()?;
        let ext_len = (reader.read_u16::<BigEndian>()? - 4) as usize; // RFC 7822
        match type_from_wire(ext_type) {
            NTSAuthenticator => {
                let mut auth_ext_contents = vec![0; ext_len];
                reader.read_exact(&mut auth_ext_contents)?;
                let oldpos = (reader.position() - 4 - (ext_len as u64)) as usize;
                let enc_ext_data =
                    parse_decrypt_auth_ext::<T>(&buff[0..oldpos], &auth_ext_contents, decryptor)?;
                let auth_enc_exts = parse_extensions(&enc_ext_data)?;
                return Ok(NtsPacket {
                    header,
                    auth_exts,
                    auth_enc_exts,
                });
            }
            _ => {
                let mut contents: Vec<u8> = vec![0; ext_len];
                reader.read_exact(&mut contents)?;
                auth_exts.push(NtpExtension {
                    ext_type: type_from_wire(ext_type),
                    contents,
                });
            }
        }
    }
    Err(Error::new(
        ErrorKind::InvalidInput,
        "never saw the authenticator",
    ))
}

fn parse_decrypt_auth_ext<T: Aead>(
    auth_dat: &[u8],
    auth_ext_contents: &[u8],
    decryptor: &mut T,
) -> Result<Vec<u8>, std::io::Error> {
    let mut reader = Cursor::new(auth_ext_contents);
    if auth_ext_contents.len() - (reader.position() as usize) < 4 {
        return Err(Error::new(ErrorKind::InvalidInput, "insufficient length"));
    }
    let nonce_len = reader.read_u16::<BigEndian>()? as usize;
    let cipher_len = reader.read_u16::<BigEndian>()? as usize;
    let nonce_pad_len = nonce_len + ((4 - (nonce_len % 4)) % 4);
    let cipher_pad_len = cipher_len + ((4 - (cipher_len % 4)) % 4);
    if nonce_pad_len + cipher_pad_len + 4 > auth_ext_contents.len() {
        return Err(Error::new(
            ErrorKind::InvalidInput,
            "length of data exceeds wrapper",
        ));
    }
    let nonce = &auth_ext_contents[4..(4 + nonce_len)];
    let ciphertext = &auth_ext_contents[(4 + nonce_pad_len)..(4 + nonce_pad_len + cipher_len)];
    let res = decryptor.open(nonce, auth_dat, ciphertext);
    if res.is_err() {
        return Err(Error::new(ErrorKind::InvalidInput, "authentication failed"));
    }
    Ok(res.unwrap())
}

/// serialize_nts_packet serializes the packet and does all the encryption
pub fn serialize_nts_packet<T: Aead>(packet: NtsPacket, encryptor: &mut T) -> Vec<u8> {
    let mut buff = Cursor::new(Vec::new());
    buff.write_all(&serialize_header(packet.header))
        .expect("Nts header could not be written, failed to serialize NtsPacket");
    buff.write_all(&serialize_extensions(packet.auth_exts))
        .expect("Nts extensions could not be written, failed to serialize NtsPacket");
    let plaintext = serialize_extensions(packet.auth_enc_exts);
    let mut nonce = [0; NONCE_LEN];
    rand::thread_rng().fill(&mut nonce);
    let ciphertext = encryptor.seal(&nonce, buff.get_ref(), &plaintext);

    let mut authent_buffer = Cursor::new(Vec::new());
    authent_buffer
        .write_u16::<BigEndian>(NONCE_LEN as u16)
        .expect("Nonce length could not be written, failed to serialize NtsPacket"); // length of the nonce
    authent_buffer
        .write_u16::<BigEndian>(ciphertext.len() as u16)
        .expect("Ciphertext length could not be written, failed to serialize NtsPacket");
    authent_buffer
        .write_all(&nonce)
        .expect("Nonce could not be written, failed to serialize NtsPacket"); // 16 bytes so no padding
    authent_buffer
        .write_all(&ciphertext)
        .expect("Ciphertext could not be written, failed to serialize NtsPacket");
    let padlen = (4 - (ciphertext.len() % 4)) % 4;
    for _i in 0..padlen {
        // pad with zeros: probably cleaner way exists
        authent_buffer
            .write_u8(0)
            .expect("Padding could not be written, failed to serialize NtsPacket");
    }
    let last_ext = NtpExtension {
        ext_type: NTSAuthenticator,
        contents: authent_buffer.into_inner(),
    };
    let res = serialize_extensions(vec![last_ext]);
    buff.write_all(&res)
        .expect("Extensions could not be written, failed to serialize NtsPacket");
    buff.into_inner()
}

#[cfg(test)]
mod tests {
    use super::*;
    use miscreant::aead::Aes128SivAead;
    #[test]
    fn test_ntp_header_parse() {
        let leaps = vec![NoLeap, Positive, Negative, LeapState::Unknown];
        let versions = vec![1, 2, 3, 4, 5, 6, 7];
        let modes = vec![SymmetricActive, SymmetricPassive, Client, Server, Broadcast];
        for leap in &leaps {
            for version in &versions {
                for mode in &modes {
                    let start_header = NtpPacketHeader {
                        leap_indicator: *leap,
                        version: *version,
                        mode: *mode,
                        stratum: 0,
                        poll: 0,
                        precision: 0,
                        root_delay: 0,
                        root_dispersion: 0,
                        reference_id: 0,
                        reference_timestamp: 0,
                        origin_timestamp: 0,
                        receive_timestamp: 0,
                        transmit_timestamp: 0,
                    };
                    let ret_header = parse_packet_header(&serialize_header(start_header)).unwrap();
                    assert_eq!(ret_header, start_header)
                }
            }
        }
    }

    fn check_eq_ext(a: &NtpExtension, b: &NtpExtension) {
        assert_eq!(a.ext_type, b.ext_type);
        assert_eq!(a.contents.len(), b.contents.len());
        for i in 0..a.contents.len() {
            assert_eq!(a.contents[i], b.contents[i]);
        }
    }
    fn check_ext_array_eq(exts1: Vec<NtpExtension>, exts2: Vec<NtpExtension>) {
        assert_eq!(exts1.len(), exts2.len());
        for i in 0..exts1.len() {
            check_eq_ext(&exts1[i], &exts2[i]);
        }
    }
    fn check_nts_match(pkt1: NtsPacket, pkt2: NtsPacket) {
        assert_eq!(pkt1.header, pkt2.header);
        check_ext_array_eq(pkt1.auth_enc_exts, pkt2.auth_enc_exts);
        check_ext_array_eq(pkt1.auth_exts, pkt2.auth_exts);
    }
    fn roundtrip_test<T: Aead>(input: NtsPacket, enc: &mut T) {
        let mut packet = serialize_nts_packet::<T>(input.clone(), enc);
        let decrypt = parse_nts_packet(&packet, enc).unwrap();
        check_nts_match(input, decrypt);
        packet[0] = 0xde;
        packet[1] = 0xad;
        packet[2] = 0xbe;
        packet[3] = 0xef;
        if parse_nts_packet(&packet, enc).is_ok() {
            panic!("success when we should have failed");
        }
    }
    #[test]
    fn test_nts_parse() {
        let key = [0; 32];
        let mut test_aead = Aes128SivAead::new(&key);
        let header = NtpPacketHeader {
            leap_indicator: NoLeap,
            version: 4,
            mode: Client,
            stratum: 1,
            poll: 0,
            precision: 0,
            root_delay: 0,
            root_dispersion: 0,
            reference_id: 0,
            reference_timestamp: 0,
            origin_timestamp: 0,
            receive_timestamp: 0,
            transmit_timestamp: 0,
        };

        let packet = NtsPacket {
            header,
            auth_exts: vec![
                NtpExtension {
                    ext_type: UniqueIdentifier,
                    contents: vec![0; 32],
                },
                NtpExtension {
                    ext_type: NTSCookie,
                    contents: vec![0; 32],
                },
            ],
            auth_enc_exts: vec![NtpExtension {
                ext_type: NTSCookiePlaceholder,
                contents: vec![0xfe; 32],
            }],
        };
        roundtrip_test::<Aes128SivAead>(packet, &mut test_aead);
    }
}


================================================
FILE: src/ntp/server/config.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! NTP server configuration.

use sloggers::terminal::TerminalLoggerBuilder;
use sloggers::Build;

use std::convert::TryFrom;
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;

use crate::cookie::CookieKey;
use crate::error::WrapError;
use crate::metrics::MetricsConfig;

fn get_metrics_config(settings: &config::Config) -> Option<MetricsConfig> {
    let mut metrics = None;
    if let Ok(addr) = settings.get_str("metrics_addr") {
        if let Ok(port) = settings.get_int("metrics_port") {
            metrics = Some(MetricsConfig {
                port: port as u16,
                addr,
            });
        }
    }
    metrics
}

/// Configuration for running an NTP server.
#[derive(Debug)]
pub struct NtpServerConfig {
    /// List of addresses and ports to the server will be listening to.
    // Each of the elements can be either IPv4 or IPv6 address. It cannot be a UNIX socket address.
    addrs: Vec<SocketAddr>,

    pub cookie_key: CookieKey,

    /// The logger that will be used throughout the application, while the server is running.
    /// This property is mandatory because logging is very important for debugging.
    logger: slog::Logger,

    pub memcached_url: String,
    pub metrics_config: Option<MetricsConfig>,
    pub upstream_addr: Option<SocketAddr>,
}

/// We decided to make NtpServerConfig mutable so that you can add more address after you parse
/// the config file.
impl NtpServerConfig {
    /// Create a NTP server config object with the given cookie key, memcached url, the metrics
    /// config, and the upstream address port.
    pub fn new(
        cookie_key: CookieKey,
        memcached_url: String,
        metrics_config: Option<MetricsConfig>,
        upstream_addr: Option<SocketAddr>,
    ) -> NtpServerConfig {
        NtpServerConfig {
            addrs: Vec::new(),

            // Use terminal logger as a default logger. The users can override it using
            // `set_logger` later, if they want.
            //
            // According to `sloggers-0.3.2` source code, the function doesn't return an error at
            // all. There should be no problem unwrapping here.
            logger: TerminalLoggerBuilder::new()
                .build()
                .expect("BUG: TerminalLoggerBuilder::build shouldn't return an error."),

            // From parameters.
            cookie_key,
            memcached_url,
            metrics_config,
            upstream_addr,
        }
    }

    /// Add an address into the config.
    pub fn add_address(&mut self, addr: SocketAddr) {
        self.addrs.push(addr);
    }

    /// Return a list of addresses.
    pub fn addrs(&self) -> &[SocketAddr] {
        self.addrs.as_slice()
    }

    /// Set a new logger to the config.
    pub fn set_logger(&mut self, logger: slog::Logger) {
        self.logger = logger;
    }

    /// Return the logger of the config.
    pub fn logger(&self) -> &slog::Logger {
        &self.logger
    }

    /// Parse a config from a file.
    ///
    /// # Errors
    ///
    /// Currently we return `config::ConfigError` which is returned from functions in the
    /// `config` crate itself.
    ///
    /// For any error from any file specified in the configuration, `std::io::Error` which is
    /// wrapped inside `config::ConfigError::Foreign` will be returned.
    ///
    /// For any address parsing error, `std::io::Error` wrapped inside
    /// `config::ConfigError::Foreign` will also be returned.
    ///
    /// In addition, it also returns some custom `config::ConfigError::Message` errors, for the
    /// following cases:
    ///
    /// * The upstream port in the configuration file is a valid `i64` but not a valid `u16`.
    ///
    // Returning a `Message` object here is not a good practice. I will figure out a good practice
    // later.
    pub fn parse(filename: &str) -> Result<NtpServerConfig, config::ConfigError> {
        let mut settings = config::Config::new();
        settings.merge(config::File::with_name(filename))?;

        let memcached_url = settings.get_str("memc_url")?;

        // Resolves metrics configuration.
        let metrics_config = get_metrics_config(&settings);

        // XXX: The code of parsing a next port here is quite ugly due to the `get_int` interface.
        // Please don't be surprised :)
        let upstream_port = match settings.get_int("upstream_port") {
            // If it's a not-found error, we can just leave it empty.
            Err(config::ConfigError::NotFound(_)) => None,

            // If it's other error, for example, unparseable error, it means that the user intended
            // to enter the port number but it just fails.
            Err(error) => return Err(error),

            Ok(val) => {
                let port = match u16::try_from(val) {
                    Ok(val) => val,
                    // The error will happen when the port number is not in a range of `u16`.
                    Err(_) => {
                        // Returning a custom message is not a good practice, but we can improve
                        // it later when we don't have to depend on `config` crate.
                        return Err(config::ConfigError::Message(String::from(
                            "the upstream port is not a valid u64",
                        )));
                    }
                };
                Some(port)
            }
        };

        let upstream_addr = match settings.get_str("upstream_addr") {
            // If it's a not-found error, we can just leave it empty.
            Err(config::ConfigError::NotFound(_)) => None,

            // If it's other error, for example, unparseable error, it means that the user intended
            // to enter the address but it just fails.
            Err(error) => return Err(error),

            Ok(addr) => Some(addr),
        };

        let upstream_sock_addr =
            if let (Some(upstream_addr), Some(upstream_port)) = (upstream_addr, upstream_port) {
                Some(SocketAddr::from((
                    IpAddr::from_str(&upstream_addr).wrap_err()?,
                    upstream_port,
                )))
            } else {
                None
            };

        // Note that all of the file reading stuffs should be at the end of the function so that
        // all the not-file-related stuffs can fail fast.

        let cookie_key_filename = settings.get_str("cookie_key_file")?;
        let cookie_key = CookieKey::parse(&cookie_key_filename).wrap_err()?;

        let mut config = NtpServerConfig::new(
            cookie_key,
            memcached_url,
            metrics_config,
            upstream_sock_addr,
        );

        let addrs = settings.get_array("addr")?;
        for addr in addrs {
            // Parse SocketAddr from a string.
            let sock_addr = addr.to_string().parse().wrap_err()?;
            config.add_address(sock_addr);
        }

        Ok(config)
    }
}


================================================
FILE: src/ntp/server/mod.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! NTP server implementation.

mod config;
mod ntp_server;

pub use self::config::NtpServerConfig;
pub use self::ntp_server::start_ntp_server;


================================================
FILE: src/ntp/server/ntp_server.rs
================================================
use super::config::NtpServerConfig;
use crate::cfsock;
use crate::cookie::{eat_cookie, get_keyid, make_cookie, NTSKeys, COOKIE_SIZE};
use crate::key_rotator::{periodic_rotate, KeyRotator};
use crate::metrics;

use lazy_static::lazy_static;
use prometheus::{opts, register_counter, register_int_counter, IntCounter};
use slog::{error, info};

use std::io::{Error, ErrorKind};
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, RwLock};
use std::thread;
use std::time;
use std::time::{Duration, SystemTime};
use std::vec;

use crossbeam::sync::WaitGroup;
use libc::{in6_pktinfo, in_pktinfo};
/// Miscreant calls Aes128SivAead what IANA calls AEAD_AES_SIV_CMAC_256
use miscreant::aead::Aead;
use miscreant::aead::Aes128SivAead;
use nix::sys::socket::{
    recvmsg, sendmsg, setsockopt, sockopt, CmsgSpace, ControlMessage, MsgFlags,
};
use nix::sys::time::{TimeVal, TimeValLike};
use nix::sys::uio::IoVec;

use crate::ntp::protocol;
use crate::ntp::protocol::{
    extract_extension, has_extension, is_nts_packet, parse_ntp_packet, parse_nts_packet,
    serialize_header, serialize_ntp_packet, serialize_nts_packet, LeapState, LeapState::*,
    NtpExtension, NtpExtensionType::NTSCookie, NtpExtensionType::UniqueIdentifier, NtpPacket,
    NtpPacketHeader, NtsPacket, PacketMode, PHI, UNIX_OFFSET,
};

const BUF_SIZE: usize = 1280; // Anything larger might fragment.
const TWO_POW_32: f64 = 4294967296.0;
const TWO_POW_16: f64 = 65536.0;

lazy_static! {
    static ref QUERY_COUNTER: IntCounter =
        register_int_counter!("ntp_queries_total", "Number of NTP queries").unwrap();
    static ref NTS_COUNTER: IntCounter = register_int_counter!(
        "ntp_nts_queries_total",
        "Number of queries we thought were NTS"
    )
    .unwrap();
    static ref KOD_COUNTER: IntCounter =
        register_int_counter!("ntp_kod_total", "Number of Kiss of Death packets sent").unwrap();
    static ref MALFORMED_COOKIE_COUNTER: IntCounter = register_int_counter!(
        "ntp_malformed_cookie_total",
        "Number of cookies with malformations"
    )
    .unwrap();
    static ref MANGLED_PACKET_COUNTER: IntCounter = register_int_counter!(
        "ntp_mangled_packet_total",
        "Number of packets without valid ntp headers"
    )
    .unwrap();
    static ref MISSING_KEY_COUNTER: IntCounter =
        register_int_counter!("ntp_missing_key_total", "Number of keys we could not find").unwrap();
    static ref UNDECRYPTABLE_COOKIE_COUNTER: IntCounter = register_int_counter!(
        "ntp_undecryptable_cookie_total",
        "Number of cookies we could not decrypt"
    )
    .unwrap();
    static ref UPSTREAM_QUERY_COUNTER: IntCounter = register_int_counter!(
        "ntp_upstream_queries_total",
        "Number of upstream queries sent"
    )
    .unwrap();
    static ref UPSTREAM_FAILURE_COUNTER: IntCounter = register_int_counter!(
        "ntp_upstream_failures_total",
        "Number of failed upstream queries"
    )
    .unwrap();
}

#[derive(Clone, Copy, Debug)]
struct ServerState {
    leap: LeapState,
    stratum: u8,
    version: u8,
    poll: i8,
    precision: i8,
    root_delay: u32,
    root_dispersion: u32,
    refid: u32,
    refstamp: u64,
    taken: SystemTime,
}

type TheCmsgSpace = CmsgSpace<(TimeVal, CmsgSpace<(in_pktinfo, CmsgSpace<in6_pktinfo>)>)>;

/// run_server runs the ntp server on the given socket.
/// The caller has to set up the socket options correctly
fn run_server(
    socket: UdpSocket,
    keys: Arc<RwLock<KeyRotator>>,
    servstate: Arc<RwLock<ServerState>>,
    logger: slog::Logger,
    ipv4: bool,
) -> Result<(), std::io::Error> {
    let sockfd = socket.as_raw_fd();
    setsockopt(sockfd, sockopt::ReceiveTimestamp, &true)
        .expect("setsockopt failed; can't run ntp server");
    if ipv4 {
        setsockopt(sockfd, sockopt::Ipv4PacketInfo, &true)
            .expect("setsockopt failed; can't run ntp server");
    } else {
        setsockopt(sockfd, sockopt::Ipv6RecvPacketInfo, &true)
            .expect("setsockopt failed; can't run ntp server");
    }
    // The following is adapted from the example in the nix crate docs:
    // https://docs.rs/nix/0.13.0/nix/sys/socket/enum.ControlMessage.html#variant.ScmTimestamp
    // Most of these functions are documented in manpages, and nix is a thin wrapper around them.
    loop {
        // Receive and respond to packets
        let mut buf = [0; BUF_SIZE];
        let flags = MsgFlags::empty();
        let mut cmsgspace = TheCmsgSpace::new();
        let iov = [IoVec::from_mut_slice(&mut buf)];
        let r = recvmsg(sockfd, &iov, Some(&mut cmsgspace), flags);
        if let Err(_err) = r {
            error!(logger, "error receiving message: {:?}", _err);
            continue;
        }
        let r = r.unwrap(); // this is safe because of previous if
        if r.address.is_none() {
            // No return address => we can't do anything
            continue;
        }
        let src = r.address.unwrap();
        // We should only have a single cmsg of known type.
        // The nix crate implements a typesafe interface to cmsg,
        // hence some of the matching here.
        let mut r_time = TimeVal::nanoseconds(0);
        let mut msgs: Vec<ControlMessage> = Vec::new();
        for msg in r.cmsgs() {
            match msg {
                ControlMessage::ScmTimestamp(&r_timestamp) => r_time = r_timestamp,
                ControlMessage::Ipv4PacketInfo(_inf) => {
                    if ipv4 {
                        msgs.push(msg);
                    } else {
                        error!(logger, "v6 connection got v4 info");
                        continue;
                    }
                }
                ControlMessage::Ipv6PacketInfo(_inf) => {
                    if !ipv4 {
                        msgs.push(msg);
                    } else {
                        error!(logger, "v4 connection got v6 info");
                        continue;
                    }
                }
                _ => {
                    error!(logger, "unexpected control message");
                    continue;
                }
            }
        }

        let r_system = SystemTime::UNIX_EPOCH
            + Duration::new(r_time.tv_sec() as u64, r_time.tv_usec() as u32 * 1000);
        let t_system = SystemTime::now();
        // We now have the receive times and the current time as SystemTimes
        let resp = response(
            &buf[..r.bytes],
            r_system,
            t_system,
            keys.clone(),
            servstate.clone(),
            logger.clone(),
        );
        match resp {
            Ok(data) => {
                let resp = sendmsg(
                    sockfd,
                    &[IoVec::from_slice(&data)],
                    &msgs,
                    flags,
                    Some(&src),
                );
                if let Err(err) = resp {
                    error!(logger, "error sending response: {:}", err);
                }
            }
            Err(_) => {
                MANGLED_PACKET_COUNTER.inc(); // The packet is too mangled to do much with.
                error!(logger, "mangled packet");
            }
        };
    }
}

/// start_ntp_server runs the ntp server with the config specified in config_filename
pub fn start_ntp_server(config: NtpServerConfig) -> Result<(), Box<dyn std::error::Error>> {
    let logger = config.logger().clone();

    info!(logger, "Initializing keys with memcached");

    let key_rotator = KeyRotator::connect(
        String::from("/nts/nts-keys"), // prefix
        config.memcached_url.clone(),  // memcached_url
        config.cookie_key.clone(),     // master_key
        logger.clone(),                // logger
    )
    .expect("error connecting to the memcached server");

    let keys = Arc::new(RwLock::new(key_rotator));
    periodic_rotate(keys.clone());

    let servstate_struct = ServerState {
        leap: Unknown,
        stratum: 16,
        version: protocol::VERSION,
        poll: 7,
        precision: -18,
        root_delay: 10,
        root_dispersion: 10,
        refid: 0,
        refstamp: 0,
        taken: SystemTime::now(),
    };

    let servstate = Arc::new(RwLock::new(servstate_struct));
    match config.upstream_addr {
        Some(upstream_addr) => {
            info!(logger, "connecting to upstream");
            let servstate = servstate.clone();
            let rot_logger = logger.new(slog::o!("task"=>"refereshing servstate"));
            let socket = UdpSocket::bind("127.0.0.1:0")?; // we only go to local
            socket.set_read_timeout(Some(time::Duration::from_secs(1)))?;
            thread::spawn(move || {
                refresh_servstate(servstate, rot_logger, socket, &upstream_addr);
            });
        }
        None => {
            let mut state_guard = servstate.write().unwrap();
            info!(logger, "setting stratum to 1");
            state_guard.leap = NoLeap;
            state_guard.stratum = 1;
        }
    }

    if let Some(metrics_config) = config.metrics_config.clone() {
        info!(logger, "spawning metrics");
        let log_metrics = logger.new(slog::o!("component"=>"metrics"));
        thread::spawn(move || {
            metrics::run_metrics(metrics_config, &log_metrics)
                .expect("metrics could not be run; starting ntp server failed");
        });
    }

    let wg = WaitGroup::new();
    for addr in config.addrs() {
        let addr = addr.to_socket_addrs().unwrap().next().unwrap();
        let socket = cfsock::udp_listen(&addr)?;
        let wg = wg.clone();
        let logger = logger.new(slog::o!("listen_addr"=>addr));
        let keys = keys.clone();
        let servstate = servstate.clone();
        info!(logger, "Listening on: {}", socket.local_addr()?);
        let mut use_ipv4 = true;
        if let SocketAddr::V6(_) = addr {
            use_ipv4 = false;
        }
        thread::spawn(move || {
            run_server(socket, keys, servstate, logger, use_ipv4).expect("server could not be run");
            drop(wg);
        });
    }
    wg.wait();
    Ok(())
}

/// Compute the current dispersion to within 1 ULP.
fn fix_dispersion(disp: u32, now: SystemTime, taken: SystemTime) -> u32 {
    let disp_frac = (disp & 0x0000ffff) as f64;
    let disp_secs = ((disp & 0xffff0000) >> 16) as f64;
    let dispf = disp_secs + disp_frac / TWO_POW_16;
    let diff = now.duration_since(taken);
    match diff {
        Ok(secs) => {
            let curdispf = dispf + (secs.as_secs() as f64) * PHI;
            let curdisp_secs = curdispf.floor() as u32;
            let curdisp_frac = (curdispf * 65336.0).floor() as u32;
            (curdisp_secs << 16) + curdisp_frac
        }
        Err(_) => disp,
    }
}

fn ntp_timestamp(time: SystemTime) -> u64 {
    let unix_time = time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); // Safe absent time machines
    let unix_offset = Duration::new(UNIX_OFFSET, 0);
    let epoch_time = unix_offset + unix_time;
    let ts_secs = epoch_time.as_secs();
    let ts_nanos = epoch_time.subsec_nanos() as f64;
    let ts_frac = ((ts_nanos * TWO_POW_32) / 1.0e9).round() as u32;
    // RFC 5905  Figure 3
    (ts_secs << 32) + ts_frac as u64
}

fn create_header(
    query_packet: &NtpPacket,
    received: SystemTime,
    transmit: SystemTime,
    servstate: Arc<RwLock<ServerState>>,
) -> NtpPacketHeader {
    let servstate = servstate.read().unwrap();
    let receive_timestamp = ntp_timestamp(received);
    let transmit_timestamp = ntp_timestamp(transmit);
    NtpPacketHeader {
        leap_indicator: servstate.leap,
        version: servstate.version,
        mode: PacketMode::Server,
        poll: servstate.poll,
        precision: servstate.precision,
        stratum: servstate.stratum,
        root_delay: servstate.root_delay,
        root_dispersion: fix_dispersion(servstate.root_dispersion, transmit, servstate.taken),
        reference_id: servstate.refid,
        reference_timestamp: servstate.refstamp,
        origin_timestamp: query_packet.header.transmit_timestamp,
        receive_timestamp,
        transmit_timestamp,
    }
}

fn response(
    query: &[u8],
    r_time: SystemTime,
    t_time: SystemTime,
    cookie_keys: Arc<RwLock<KeyRotator>>,
    servstate: Arc<RwLock<ServerState>>,
    logger: slog::Logger,
) -> Result<Vec<u8>, std::io::Error> {
    let query_packet = parse_ntp_packet(query)?; // Should try to send a KOD if this happens
    let resp_header = create_header(&query_packet, r_time, t_time, servstate);

    QUERY_COUNTER.inc();

    if query_packet.header.mode != PacketMode::Client {
        return Err(Error::new(ErrorKind::InvalidData, "not client mode"));
    }
    if is_nts_packet(&query_packet) {
        NTS_COUNTER.inc();
        let cookie = extract_extension(&query_packet, NTSCookie).unwrap();
        let keyid_maybe = get_keyid(&cookie.contents);
        match keyid_maybe {
            Some(keyid) => {
                let point = cookie_keys.read().unwrap();
                let key_maybe = (*point).get(keyid);
                match key_maybe {
                    Some(key) => {
                        let nts_keys = eat_cookie(&cookie.contents, key.as_ref());
                        match nts_keys {
                            Some(nts_dir_keys) => Ok(process_nts(
                                resp_header,
                                nts_dir_keys,
                                cookie_keys.clone(),
                                query,
                            )),
                            None => {
                                UNDECRYPTABLE_COOKIE_COUNTER.inc();
                                error!(logger, "undecryptable cookie with keyid {:x?}", keyid);
                                send_kiss_of_death(query_packet)
                            }
                        }
                    }
                    None => {
                        MISSING_KEY_COUNTER.inc();
                        error!(logger, "cannot access key {:x?}", keyid);
                        send_kiss_of_death(query_packet)
                    }
                }
            }
            None => {
                MALFORMED_COOKIE_COUNTER.inc();
                error!(logger, "malformed cookie");
                send_kiss_of_death(query_packet)
            }
        }
    } else {
        Ok(serialize_header(resp_header))
    }
}

fn process_nts(
    resp_header: NtpPacketHeader,
    keys: NTSKeys,
    cookie_keys: Arc<RwLock<KeyRotator>>,
    query_raw: &[u8],
) -> Vec<u8> {
    let mut recv_aead = Aes128SivAead::new(&keys.c2s);
    let mut send_aead = Aes128SivAead::new(&keys.s2c);
    let query = parse_nts_packet::<Aes128SivAead>(query_raw, &mut recv_aead);
    match query {
        Ok(packet) => serialize_nts_packet(
            nts_response(packet, resp_header, keys, cookie_keys),
            &mut send_aead,
        ),
        Err(_) => serialize_ntp_packet(kiss_of_death(parse_ntp_packet(query_raw).unwrap())),
    }
}

fn nts_response(
    query: NtsPacket,
    header: NtpPacketHeader,
    keys: NTSKeys,
    cookie_keys: Arc<RwLock<KeyRotator>>,
) -> NtsPacket {
    let mut resp_packet = NtsPacket {
        header,
        auth_exts: vec![],
        auth_enc_exts: vec![],
    };
    for ext in query.auth_exts {
        match ext.ext_type {
            protocol::NtpExtensionType::UniqueIdentifier => resp_packet.auth_exts.push(ext),
            protocol::NtpExtensionType::NTSCookiePlaceholder => {
                if ext.contents.len() >= COOKIE_SIZE {
                    // Avoid amplification
                    let keymaker = cookie_keys.read().unwrap();
                    let (key_id, curr_key) = keymaker.latest_key_value();
                    let cookie = make_cookie(keys, curr_key.as_ref(), key_id);
                    resp_packet.auth_enc_exts.push(NtpExtension {
                        ext_type: NTSCookie,
                        contents: cookie,
                    })
                }
            }
            _ => {}
        }
    }
    // This is a free cookie to replace the one consumed in the packet
    let keymaker = cookie_keys.read().unwrap();
    let (key_id, curr_key) = keymaker.latest_key_value();
    let cookie = make_cookie(keys, curr_key.as_ref(), key_id);
    resp_packet.auth_enc_exts.push(NtpExtension {
        ext_type: NTSCookie,
        contents: cookie,
    });
    resp_packet.header.transmit_timestamp = ntp_timestamp(SystemTime::now()); // Update at last possible time
    resp_packet
}

fn send_kiss_of_death(query_packet: NtpPacket) -> Result<Vec<u8>, std::io::Error> {
    let resp = kiss_of_death(query_packet);
    Ok(serialize_ntp_packet(resp))
}

/// The kiss of death tells the client it has done something wrong.
/// draft-ietf-ntp-using-nts-for-ntp-18 and RFC 5905 specify the format.
fn kiss_of_death(query_packet: NtpPacket) -> NtpPacket {
    KOD_COUNTER.inc();
    let kod_header = NtpPacketHeader {
        leap_indicator: LeapState::Unknown,
        version: 4,
        mode: PacketMode::Server,
        poll: 0,
        precision: 0,
        stratum: 0,
        root_delay: 0,
        root_dispersion: 0,
        reference_id: 0x4e54534e, // NTSN
        reference_timestamp: 0,
        origin_timestamp: query_packet.header.transmit_timestamp,
        receive_timestamp: 0,
        transmit_timestamp: 0,
    };

    let mut kod_packet = NtpPacket {
        header: kod_header,
        exts: vec![],
    };
    if has_extension(&query_packet, UniqueIdentifier) {
        kod_packet
            .exts
            .push(extract_extension(&query_packet, UniqueIdentifier).unwrap());
    }
    kod_packet
}

fn refresh_servstate(
    servstate: Arc<RwLock<ServerState>>,
    logger: slog::Logger,
    sock: std::net::UdpSocket,
    addr: &SocketAddr,
) {
    loop {
        let query_packet = NtpPacket {
            header: NtpPacketHeader {
                leap_indicator: LeapState::Unknown,
                version: 4,
                mode: PacketMode::Client,
                poll: 0,
                precision: 0,
                stratum: 0,
                root_delay: 0,
                root_dispersion: 0,
                reference_id: 0x0,
                reference_timestamp: 0,
                origin_timestamp: 0,
                receive_timestamp: 0,
                transmit_timestamp: 0,
            },
            exts: vec![],
        };
        sock.connect(addr)
            .expect("socket connection to server failed, failed to refresh server state");
        sock.send(&serialize_ntp_packet(query_packet))
            .expect("sending ntp packet to server failed, failed to refresh server state");
        UPSTREAM_QUERY_COUNTER.inc();
        let mut buff = [0; 2048];
        let res = sock.recv_from(&mut buff);
        match res {
            Ok((size, _sender)) => {
                let response = parse_ntp_packet(&buff[0..size]);
                match response {
                    Ok(packet) => {
                        let mut state = servstate.write().unwrap();
                        state.leap = packet.header.leap_indicator;
                        state.version = 4;
                        state.poll = packet.header.poll;
                        state.precision = packet.header.precision;
                        state.stratum = packet.header.stratum;
                        state.root_delay = packet.header.root_delay;
                        state.root_dispersion = packet.header.root_dispersion;
                        state.refid = packet.header.reference_id;
                        state.refstamp = packet.header.reference_timestamp;
                        state.taken = SystemTime::now();
                        info!(logger, "set server state with stratum {:}", state.stratum);
                    }
                    Err(err) => {
                        UPSTREAM_FAILURE_COUNTER.inc();
                        error!(logger, "failure to parse response: {}", err);
                    }
                }
            }
            Err(err) => {
                UPSTREAM_FAILURE_COUNTER.inc();
                error!(logger, "read error: {}", err);
            }
        }
        thread::sleep(time::Duration::from_secs(1));
    }
}


================================================
FILE: src/nts_ke/client.rs
================================================
use slog::{debug, info};
use std::error::Error;
use std::io::{Read, Write};
use std::net::{Shutdown, TcpStream, ToSocketAddrs};
use std::sync::Arc;
use std::time::Duration;

use rustls;
use webpki;
use webpki_roots;

use super::records;

use crate::cookie::NTSKeys;
use crate::nts_ke::records::{
    deserialize,
    process_record,

    // Functions.
    serialize,
    // Records.
    AeadAlgorithmRecord,
    // Errors.
    DeserializeError,

    EndOfMessageRecord,

    // Enums.
    KnownAeadAlgorithm,
    KnownNextProtocol,
    NextProtocolRecord,
    NtsKeParseError,
    Party,

    // Structs.
    ReceivedNtsKeRecordState,

    // Constants.
    HEADER_SIZE,
};
use crate::sub_command::client::ClientConfig;

type Cookie = Vec<u8>;

const DEFAULT_NTP_PORT: u16 = 123;
const DEFAULT_KE_PORT: u16 = 4460;
const DEFAULT_SCHEME: u16 = 0;
const TIMEOUT: Duration = Duration::from_secs(15);

#[derive(Clone, Debug)]
pub struct NtsKeResult {
    pub cookies: Vec<Cookie>,
    pub next_protocols: Vec<u16>,
    pub aead_scheme: u16,
    pub next_server: String,
    pub next_port: u16,
    pub keys: NTSKeys,
    pub use_ipv4: Option<bool>,
}

/// run_nts_client executes the nts client with the config in config file
pub fn run_nts_ke_client(
    logger: &slog::Logger,
    client_config: ClientConfig,
) -> Result<NtsKeResult, Box<dyn Error>> {
    let mut tls_config = rustls::ClientConfig::new();
    let alpn_proto = String::from("ntske/1");
    let alpn_bytes = alpn_proto.into_bytes();
    tls_config.set_protocols(&[alpn_bytes]);

    match client_config.trusted_cert {
        Some(cert) => {
            info!(logger, "loading custom trust root");
            tls_config.root_store.add(&cert)?;
        }
        None => {
            tls_config
                .root_store
                .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
        }
    }

    let rc_config = Arc::new(tls_config);
    let hostname = webpki::DNSNameRef::try_from_ascii_str(client_config.host.as_str())
        .expect("server hostname is invalid");
    let mut client = rustls::ClientSession::new(&rc_config, hostname);
    debug!(logger, "Connecting");
    let mut port = DEFAULT_KE_PORT;
    if let Some(p) = client_config.port {
        port = p.parse::<u16>()?;
    }

    let mut ip_addrs = (client_config.host.as_str(), port).to_socket_addrs()?;
    let addr;
    if let Some(use_ipv4) = client_config.use_ipv4 {
        if use_ipv4 {
            // mandated to use ipv4
            addr = ip_addrs.find(|&x| x.is_ipv4());
            if addr.is_none() {
                return Err(Box::new(NtsKeParseError::NoIpv4AddrFound));
            }
        } else {
            // mandated to use ipv6
            addr = ip_addrs.find(|&x| x.is_ipv6());
            if addr.is_none() {
                return Err(Box::new(NtsKeParseError::NoIpv6AddrFound));
            }
        }
    } else {
        // sniff whichever one is supported
        addr = ip_addrs.next();
    }
    let mut stream = TcpStream::connect_timeout(&addr.unwrap(), TIMEOUT)?;
    stream.set_read_timeout(Some(TIMEOUT))?;
    stream.set_write_timeout(Some(TIMEOUT))?;

    let mut tls_stream = rustls::Stream::new(&mut client, &mut stream);

    let next_protocol_record = NextProtocolRecord::from(vec![KnownNextProtocol::Ntpv4]);
    let aead_record = AeadAlgorithmRecord::from(vec![KnownAeadAlgorithm::AeadAesSivCmac256]);
    let end_record = EndOfMessageRecord;

    let clientrec = &mut serialize(next_protocol_record);
    clientrec.append(&mut serialize(aead_record));
    clientrec.append(&mut serialize(end_record));
    tls_stream.write_all(clientrec)?;
    tls_stream.flush()?;
    debug!(logger, "Request transmitted");
    let keys = records::gen_key(tls_stream.sess).unwrap();

    let mut state = ReceivedNtsKeRecordState {
        finished: false,
        next_protocols: Vec::new(),
        aead_scheme: Vec::new(),
        cookies: Vec::new(),
        next_server: None,
        next_port: None,
    };

    while !state.finished {
        let mut header: [u8; HEADER_SIZE] = [0; HEADER_SIZE];

        // We should use `read_exact` here because we always need to read 4 bytes to get the
        // header.
        if let Err(error) = tls_stream.read_exact(&mut header[..]) {
            return Err(Box::new(error));
        }

        // Retrieve a body length from the 3rd and 4th bytes of the header.
        let body_length = u16::from_be_bytes([header[2], header[3]]);
        let mut body = vec![0; body_length as usize];

        // `read_exact` the length of the body.
        if let Err(error) = tls_stream.read_exact(body.as_mut_slice()) {
            return Err(Box::new(error));
        }

        // Reconstruct the whole record byte array to let the `records` module deserialize it.
        let mut record_bytes = Vec::from(&header[..]);
        record_bytes.append(&mut body);

        // `deserialize` has an invariant that the slice needs to be long enough to make it a
        // valid record, which in this case our slice is exactly as long as specified in the
        // length field.
        match deserialize(Party::Client, record_bytes.as_slice()) {
            Ok(record) => {
                let status = process_record(record, &mut state);
                match status {
                    Ok(_) => {}
                    Err(err) => {
                        return Err(err);
                    }
                }
            }
            Err(DeserializeError::UnknownNotCriticalRecord) => {
                // If it's not critical, just ignore the error.
                debug!(logger, "unknown record type");
            }
            Err(DeserializeError::UnknownCriticalRecord) => {
                // TODO: This should propertly handled by sending an Error record.
                debug!(logger, "error: unknown critical record");
                return Err(Box::new(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    "unknown critical record",
                )));
            }
            Err(DeserializeError::Parsing(error)) => {
                // TODO: This shouldn't be wrapped as a trait object.
                debug!(logger, "error: {}", error);
                return Err(Box::new(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    error,
                )));
            }
        }
    }
    debug!(logger, "saw the end of the response");
    stream.shutdown(Shutdown::Write)?;

    let aead_scheme = if state.aead_scheme.is_empty() {
        DEFAULT_SCHEME
    } else {
        state.aead_scheme[0]
    };

    Ok(NtsKeResult {
        aead_scheme,
        cookies: state.cookies,
        next_protocols: state.next_protocols,
        next_server: state.next_server.unwrap_or(client_config.host.clone()),
        next_port: state.next_port.unwrap_or(DEFAULT_NTP_PORT),
        keys,
        use_ipv4: client_config.use_ipv4,
    })
}


================================================
FILE: src/nts_ke/mod.rs
================================================
pub mod client;
pub mod records;
pub mod server;


================================================
FILE: src/nts_ke/records/aead_algorithm.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! AEAD Algorithm Negotiation record representation.

use std::convert::TryFrom;

use super::KeRecordTrait;
use super::Party;

#[derive(Clone, Copy)]
pub enum KnownAeadAlgorithm {
    AeadAesSivCmac256,
}

impl KnownAeadAlgorithm {
    pub fn as_algorithm_id(&self) -> u16 {
        match self {
            KnownAeadAlgorithm::AeadAesSivCmac256 => 15,
        }
    }
}

pub struct AeadAlgorithmRecord(Vec<KnownAeadAlgorithm>);

impl AeadAlgorithmRecord {
    pub fn algorithms(&self) -> &[KnownAeadAlgorithm] {
        self.0.as_slice()
    }
}

impl From<Vec<KnownAeadAlgorithm>> for AeadAlgorithmRecord {
    fn from(algorithms: Vec<KnownAeadAlgorithm>) -> AeadAlgorithmRecord {
        AeadAlgorithmRecord(algorithms)
    }
}

impl KeRecordTrait for AeadAlgorithmRecord {
    fn critical(&self) -> bool {
        // According to the spec, this critical bit is optional, but it's good to assign it as
        // critical.
        true
    }

    fn record_type() -> u16 {
        4
    }

    fn len(&self) -> u16 {
        // Because each protocol takes 2 bytes, we need to multiply it by 2.
        u16::try_from(self.0.len())
            .ok()
            .and_then(|length| length.checked_mul(2))
            .expect("the number of AEAD algorithms are too large")
    }

    fn into_bytes(self) -> Vec<u8> {
        let mut bytes = Vec::new();
        for algorithm in self.0.iter() {
            // The spec said that the protocol id must be in network byte order, so we have to
            // convert it to the big endian order here.
            let algorithm_bytes = &algorithm.as_algorithm_id().to_be_bytes()[..];

            bytes.append(&mut Vec::from(algorithm_bytes))
        }

        bytes
    }

    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {
        // The body length must be even because each algorithm code take 2 bytes, so it's not
        // reasonable for the length to be odd.
        if bytes.len() % 2 != 0 {
            return Err(String::from(
                "the body length of AEAD Algorithm Negotiation
                                     must be even.",
            ));
        }

        let mut algorithms = Vec::new();

        for word in bytes.chunks_exact(2) {
            let algorithm_code = u16::from_be_bytes([word[0], word[1]]);

            let algorithm = KnownAeadAlgorithm::AeadAesSivCmac256;
            if algorithm.as_algorithm_id() == algorithm_code {
                algorithms.push(algorithm);
            } else {
                return Err(String::from("unknown AEAD algorithm id"));
            }
        }

        Ok(AeadAlgorithmRecord(algorithms))
    }
}


================================================
FILE: src/nts_ke/records/end_of_message.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! End Of Message record representation.

use super::KeRecordTrait;
use super::Party;

pub struct EndOfMessageRecord;

impl KeRecordTrait for EndOfMessageRecord {
    fn critical(&self) -> bool {
        true
    }

    fn record_type() -> u16 {
        0
    }

    fn len(&self) -> u16 {
        0
    }

    fn into_bytes(self) -> Vec<u8> {
        Vec::new()
    }

    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {
        if !bytes.is_empty() {
            Err(String::from(
                "the body length of End Of Message must be zero.",
            ))
        } else {
            Ok(EndOfMessageRecord)
        }
    }
}


================================================
FILE: src/nts_ke/records/error.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! Error record representation.

use super::KeRecordTrait;
use super::Party;

enum ErrorKind {
    UnrecognizedCriticalRecord,
    BadRequest,
}

impl ErrorKind {
    fn as_code(&self) -> u16 {
        match self {
            ErrorKind::UnrecognizedCriticalRecord => 0,
            ErrorKind::BadRequest => 1,
        }
    }
}

pub struct ErrorRecord(ErrorKind);

impl KeRecordTrait for ErrorRecord {
    fn critical(&self) -> bool {
        true
    }

    fn record_type() -> u16 {
        2
    }

    fn len(&self) -> u16 {
        2
    }

    fn into_bytes(self) -> Vec<u8> {
        let error_code = &self.0.as_code().to_be_bytes()[..];
        Vec::from(error_code)
    }

    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {
        if bytes.len() != 2 {
            return Err(String::from("the body length of Error must be two."));
        }

        let error_code = u16::from_be_bytes([bytes[0], bytes[1]]);

        let kind = ErrorKind::UnrecognizedCriticalRecord;
        if kind.as_code() == error_code {
            return Ok(ErrorRecord(kind));
        }

        let kind = ErrorKind::BadRequest;
        if kind.as_code() == error_code {
            return Ok(ErrorRecord(kind));
        }

        Err(String::from("unknown error code"))
    }
}


================================================
FILE: src/nts_ke/records/mod.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! NTS-KE record representation.

mod aead_algorithm;
mod end_of_message;
mod error;
mod new_cookie;
mod next_protocol;
mod port;
mod server;
mod warning;

// We pub use everything in the submodules. You can limit the scope of usage by putting it the
// submodule itself.
pub use self::aead_algorithm::*;
pub use self::end_of_message::*;
pub use self::error::*;
pub use self::new_cookie::*;
pub use self::next_protocol::*;
pub use self::port::*;
pub use self::server::*;
pub use self::warning::*;

use rustls::TLSError;
use std::fmt;

use crate::cookie::NTSKeys;

pub const HEADER_SIZE: usize = 4;

pub enum KeRecord {
    EndOfMessage(EndOfMessageRecord),
    NextProtocol(NextProtocolRecord),
    Error(ErrorRecord),
    Warning(WarningRecord),
    AeadAlgorithm(AeadAlgorithmRecord),
    NewCookie(NewCookieRecord),
    Server(ServerRecord),
    Port(PortRecord),
}

#[derive(Clone, Copy)]
pub enum Party {
    Client,
    Server,
}

pub trait KeRecordTrait: Sized {
    fn critical(&self) -> bool;

    fn record_type() -> u16;

    fn len(&self) -> u16;

    // This function has to consume the object to avoid additional memory consumption.
    fn into_bytes(self) -> Vec<u8>;

    fn from_bytes(sender: Party, bytes: &[u8]) -> Result<Self, String>;
}

// ------------------------------------------------------------------------
// Serialization
// ------------------------------------------------------------------------

/// Serialize the record into the network-ready format.
pub fn serialize<T: KeRecordTrait>(record: T) -> Vec<u8> {
    let mut result = Vec::new();

    // The first 16 bits will comprise a critical bit and the record type.
    let first_word: u16 = (u16::from(record.critical()) << 15) + T::record_type();
    result.append(&mut Vec::from(&first_word.to_be_bytes()[..]));

    // The second 16 bits will be the length of the record body.
    result.append(&mut Vec::from(&record.len().to_be_bytes()[..]));

    // The rest is the content of the record.
    result.append(&mut record.into_bytes());

    result
}

// ------------------------------------------------------------------------
// Deserialization
// ------------------------------------------------------------------------

#[derive(Clone, Debug)]
pub enum DeserializeError {
    Parsing(String),
    UnknownCriticalRecord,
    UnknownNotCriticalRecord,
}

/// Deserialize the network bytes into the record.
///
/// # Panics
///
/// If slice is shorter than the length specified in the length field.
///
pub fn deserialize(sender: Party, bytes: &[u8]) -> Result<KeRecord, DeserializeError> {
    // The first bit of the first byte is the critical bit.
    let critical = bytes[0] >> 7 == 1;

    // The following 15 bits are the record type number.
    let record_type = u16::from_be_bytes([bytes[0] & 0x7, bytes[1]]);

    // The third and fourth bytes are the body length.
    let length = u16::from_be_bytes([bytes[2], bytes[3]]);

    // The body.
    let body = &bytes[4..4 + usize::from(length)];

    macro_rules! deserialize_body {
        ( $( ($variant:ident, $record:ident) ),* ) => {
            if false {
                // Loop returns ! type.
                loop { }
            } $( else if record_type == $record::record_type() {
                match $record::from_bytes(sender, body) {
                    Ok(record) => KeRecord::$variant(record),
                    Err(error) => return Err(DeserializeError::Parsing(error)),
                }
            } )* else {
                if critical {
                    return Err(DeserializeError::UnknownCriticalRecord);
                } else {
                    return Err(DeserializeError::UnknownNotCriticalRecord);
                }
            }
        };
    }

    let record = deserialize_body!(
        (EndOfMessage, EndOfMessageRecord),
        (NextProtocol, NextProtocolRecord),
        (Error, ErrorRecord),
        (Warning, WarningRecord),
        (AeadAlgorithm, AeadAlgorithmRecord),
        (NewCookie, NewCookieRecord),
        (Server, ServerRecord),
        (Port, PortRecord)
    );

    Ok(record)
}

/// gen_key computes the client and server keys using exporters.
/// https://tools.ietf.org/html/draft-ietf-ntp-using-nts-for-ntp-28#section-4.3
pub fn gen_key<T: rustls::Session>(session: &T) -> Result<NTSKeys, TLSError> {
    let mut keys: NTSKeys = NTSKeys {
        c2s: [0; 32],
        s2c: [0; 32],
    };
    let c2s_con = [0, 0, 0, 15, 0];
    let s2c_con = [0, 0, 0, 15, 1];
    let context_c2s = Some(&c2s_con[..]);
    let context_s2c = Some(&s2c_con[..]);
    let label = "EXPORTER-network-time-security".as_bytes();
    session.export_keying_material(&mut keys.c2s, label, context_c2s)?;
    session.export_keying_material(&mut keys.s2c, label, context_s2c)?;

    Ok(keys)
}

// ------------------------------------------------------------------------
// Record Process
// ------------------------------------------------------------------------

type Cookie = Vec<u8>;

#[derive(Clone, Debug)]
pub struct ReceivedNtsKeRecordState {
    pub finished: bool,
    pub next_protocols: Vec<u16>,
    pub aead_scheme: Vec<u16>,
    pub cookies: Vec<Cookie>,
    pub next_server: Option<String>,
    pub next_port: Option<u16>,
}

#[derive(Debug, Clone)]
pub enum NtsKeParseError {
    RecordAfterEnd,
    ErrorRecord,
    NoIpv4AddrFound,
    NoIpv6AddrFound,
}

impl std::error::Error for NtsKeParseError {
    fn description(&self) -> &str {
        match self {
            Self::RecordAfterEnd => "Received record after connection finished",
            Self::ErrorRecord => "Received NTS error record",
            Self::NoIpv4AddrFound => {
                "Connection to server failed: IPv4 address could not be resolved"
            }
            Self::NoIpv6AddrFound => {
                "Connection to server failed: IPv6 address could not be resolved"
            }
        }
    }
    fn cause(&self) -> Option<&dyn std::error::Error> {
        None
    }
}

impl fmt::Display for NtsKeParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "NTS-KE Record Parse Error")
    }
}

/// Read https://datatracker.ietf.org/doc/html/rfc8915#section-4
pub fn process_record(
    record: KeRecord,
    state: &mut ReceivedNtsKeRecordState,
) -> Result<(), Box<dyn std::error::Error>> {
    if state.finished {
        return Err(Box::new(NtsKeParseError::RecordAfterEnd));
    }

    match record {
        KeRecord::EndOfMessage(_) => state.finished = true,
        KeRecord::NextProtocol(record) => {
            state.next_protocols = record
                .protocols()
                .iter()
                .map(|protocol| protocol.as_protocol_id())
                .collect();
        }
        KeRecord::Error(_) => return Err(Box::new(NtsKeParseError::ErrorRecord)),
        KeRecord::Warning(_) => return Ok(()),
        KeRecord::AeadAlgorithm(record) => {
            state.aead_scheme = record
                .algorithms()
                .iter()
                .map(|algorithm| algorithm.as_algorithm_id())
                .collect();
        }
        KeRecord::NewCookie(record) => state.cookies.push(record.into_bytes()),
        KeRecord::Server(record) => state.next_server = Some(record.into_string()),
        KeRecord::Port(record) => state.next_port = Some(record.port()),
    }

    Ok(())
}


================================================
FILE: src/nts_ke/records/new_cookie.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! New Cookie record representation.

use std::convert::TryFrom;

use super::KeRecordTrait;
use super::Party;

pub struct NewCookieRecord(Vec<u8>);

impl From<Vec<u8>> for NewCookieRecord {
    fn from(bytes: Vec<u8>) -> NewCookieRecord {
        NewCookieRecord(bytes)
    }
}

impl KeRecordTrait for NewCookieRecord {
    fn critical(&self) -> bool {
        false
    }

    fn record_type() -> u16 {
        5
    }

    fn len(&self) -> u16 {
        u16::try_from(self.0.len()).expect("the cookie is too large to fit in the record")
    }

    fn into_bytes(self) -> Vec<u8> {
        self.0
    }

    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {
        // There is error for New Cookie record, because any byte slice is considered a valid
        // cookie.
        Ok(NewCookieRecord::from(Vec::from(bytes)))
    }
}


================================================
FILE: src/nts_ke/records/next_protocol.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! NTS Next Protocol Negotiation record representation.

use std::convert::TryFrom;

use super::KeRecordTrait;
use super::Party;

#[derive(Clone, Copy)]
pub enum KnownNextProtocol {
    Ntpv4,
}

impl KnownNextProtocol {
    pub fn as_protocol_id(&self) -> u16 {
        match self {
            KnownNextProtocol::Ntpv4 => 0,
        }
    }
}

pub struct NextProtocolRecord(Vec<KnownNextProtocol>);

impl NextProtocolRecord {
    pub fn protocols(&self) -> &[KnownNextProtocol] {
        self.0.as_slice()
    }
}

impl From<Vec<KnownNextProtocol>> for NextProtocolRecord {
    fn from(protocols: Vec<KnownNextProtocol>) -> NextProtocolRecord {
        NextProtocolRecord(protocols)
    }
}

impl KeRecordTrait for NextProtocolRecord {
    fn critical(&self) -> bool {
        true
    }

    fn record_type() -> u16 {
        1
    }

    fn len(&self) -> u16 {
        // Because each protocol takes 2 bytes, we need to multiply it by 2.
        u16::try_from(self.0.len())
            .ok()
            .and_then(|length| length.checked_mul(2))
            .expect("the number of next protocols are too large")
    }

    fn into_bytes(self) -> Vec<u8> {
        let mut bytes = Vec::new();
        for protocol in self.0.iter() {
            // The spec said that the protocol id must be in network byte order, so we have to
            // convert it to the big endian order here.
            let protocol_bytes = &protocol.as_protocol_id().to_be_bytes()[..];

            bytes.append(&mut Vec::from(protocol_bytes))
        }

        bytes
    }

    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {
        // The body length must be even because each protocol code take 2 bytes, so it's not
        // reasonable for the length to be odd.
        if bytes.len() % 2 != 0 {
            return Err(String::from(
                "the body length of Next Protocol Negotiation
                                     must be even.",
            ));
        }

        let mut protocols = Vec::new();

        for word in bytes.chunks_exact(2) {
            let protocol_code = u16::from_be_bytes([word[0], word[1]]);

            let protocol = KnownNextProtocol::Ntpv4;
            if protocol.as_protocol_id() == protocol_code {
                protocols.push(protocol);
            } else {
                return Err(String::from("unknown Next Protocol id"));
            }
        }

        Ok(NextProtocolRecord(protocols))
    }
}


================================================
FILE: src/nts_ke/records/port.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! Port negotiation record representation.
/// This Port negotiation will not be sent from the server because currently, we are not
/// interested in running an NTP server on different port.
use super::KeRecordTrait;
use super::Party;

pub struct PortRecord {
    sender: Party,
    port: u16,
}

impl PortRecord {
    pub fn new(sender: Party, port: u16) -> PortRecord {
        PortRecord { sender, port }
    }

    pub fn port(&self) -> u16 {
        self.port
    }
}

impl KeRecordTrait for PortRecord {
    fn critical(&self) -> bool {
        match self.sender {
            Party::Client => false,
            Party::Server => true,
        }
    }

    fn record_type() -> u16 {
        7
    }

    fn len(&self) -> u16 {
        2
    }

    fn into_bytes(self) -> Vec<u8> {
        Vec::from(&self.port.to_be_bytes()[..])
    }

    fn from_bytes(sender: Party, bytes: &[u8]) -> Result<Self, String> {
        if bytes.len() != 2 {
            Err(String::from("the body length of Port must be two."))
        } else {
            let port = u16::from_be_bytes([bytes[0], bytes[1]]);

            Ok(PortRecord { sender, port })
        }
    }
}


================================================
FILE: src/nts_ke/records/server.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! Server negotiation record representation.
/// This Server negotiation will not be sent from the server because currently, we are not
/// interested in running an NTP server on different IP address.
use std::convert::TryFrom;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::str::FromStr;

use super::KeRecordTrait;
use super::Party;

enum Address {
    Hostname(String),
    Ipv4Addr(Ipv4Addr),
    Ipv6Addr(Ipv6Addr),
}

pub struct ServerRecord {
    sender: Party,
    address: Address,
}

impl ServerRecord {
    pub fn into_string(self) -> String {
        match self.address {
            Address::Hostname(name) => name,
            Address::Ipv4Addr(addr) => addr.to_string(),
            Address::Ipv6Addr(addr) => addr.to_string(),
        }
    }
}

impl KeRecordTrait for ServerRecord {
    fn critical(&self) -> bool {
        match self.sender {
            Party::Client => false,
            Party::Server => true,
        }
    }

    fn record_type() -> u16 {
        6
    }

    fn len(&self) -> u16 {
        match &self.address {
            // We cannot just use `name.len()` because we want to count the bytes not just the
            // runes.
            Address::Hostname(name) => u16::try_from(name.as_bytes().len())
                .expect("the hostname is too long to fix in the record"),
            // Both IPv4 and IPv6 address cannot be too long to fix in the record. It's okay to
            // just cast them here.
            Address::Ipv4Addr(addr) => addr.to_string().len() as u16,
            Address::Ipv6Addr(addr) => addr.to_string().len() as u16,
        }
    }

    fn into_bytes(self) -> Vec<u8> {
        Vec::from(self.into_string())
    }

    fn from_bytes(sender: Party, bytes: &[u8]) -> Result<Self, String> {
        let body = match String::from_utf8(Vec::from(bytes)) {
            Ok(body) => body,
            Err(_) => return Err(String::from("the body is an invalid ascii string")),
        };

        if !body.is_ascii() {
            return Err(String::from("the body is an invalid ascii string"));
        }

        let address = if let Ok(address) = Ipv4Addr::from_str(&body) {
            Address::Ipv4Addr(address)
        } else if let Ok(address) = Ipv6Addr::from_str(&body) {
            Address::Ipv6Addr(address)
        } else {
            // If the body is a valid ascii string, but not a valid IPv4 or IPv6, it must be a
            // hostname.
            Address::Hostname(body)
        };

        Ok(ServerRecord { sender, address })
    }
}


================================================
FILE: src/nts_ke/records/warning.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! Warning record representation.

use super::KeRecordTrait;
use super::Party;

enum WarningKind {
    // There is currently no warning specified in the spec, but we need to put something here to
    // make the code compiles. Please remove this Dummy when there is a warning specified in the
    // spec.
    Dummy,
}

impl WarningKind {
    fn as_code(&self) -> u16 {
        match self {
            // Put the max value for Dummy just to avoid colliding with the future warning code.
            WarningKind::Dummy => u16::max_value(),
        }
    }
}

pub struct WarningRecord(WarningKind);

impl KeRecordTrait for WarningRecord {
    fn critical(&self) -> bool {
        true
    }

    fn record_type() -> u16 {
        3
    }

    fn len(&self) -> u16 {
        2
    }

    fn into_bytes(self) -> Vec<u8> {
        let error_code = &self.0.as_code().to_be_bytes()[..];
        Vec::from(error_code)
    }

    fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {
        if bytes.len() != 2 {
            return Err(String::from("the body length of Warning must be two."));
        }

        let warning_code = u16::from_be_bytes([bytes[0], bytes[1]]);

        let kind = WarningKind::Dummy;
        if kind.as_code() == warning_code {
            return Ok(WarningRecord(kind));
        }

        Err(String::from("unknown warning code"))
    }
}


================================================
FILE: src/nts_ke/server/config.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! NTS-KE server configuration.

use rustls::internal::pemfile;
use rustls::{Certificate, PrivateKey};

use sloggers::terminal::TerminalLoggerBuilder;
use sloggers::Build;

use std::convert::TryFrom;
use std::fs::File;
use std::net::SocketAddr;

use crate::cookie::CookieKey;
use crate::error::WrapError;
use crate::metrics::MetricsConfig;

fn get_metrics_config(settings: &config::Config) -> Option<MetricsConfig> {
    let mut metrics = None;
    if let Ok(addr) = settings.get_str("metrics_addr") {
        if let Ok(port) = settings.get_int("metrics_port") {
            metrics = Some(MetricsConfig {
                port: port as u16,
                addr,
            });
        }
    }
    metrics
}

/// Configuration for running an NTS-KE server.
#[derive(Debug)]
pub struct KeServerConfig {
    /// List of addresses and ports to the server will be listening to.
    // Each of the elements can be either IPv4 or IPv6 address. It cannot be a UNIX socket address.
    addrs: Vec<SocketAddr>,

    /// The initial cookie key for the NTS-KE server.
    cookie_key: CookieKey,

    // If you don't to have a timeout, just set it to a very high value.
    timeout: u64,

    /// The logger that will be used throughout the application, while the server is running.
    /// This property is mandatory because logging is very important for debugging.
    logger: slog::Logger,

    /// The url of the memcached server. The memcached server is used to sync data between the
    /// NTS-KE server and the NTP server.
    memcached_url: String,

    pub metrics_config: Option<MetricsConfig>,
    pub next_port: u16,
    pub tls_certs: Vec<Certificate>,
    pub tls_secret_keys: Vec<PrivateKey>,
}

/// We decided to make KeServerConfig mutable so that you can add more cert, private key, or
/// address after you parse the config file.
impl KeServerConfig {
    /// Create a NTS-KE server config object with the given next port, memcached url, connection
    /// timeout, and the metrics config.
    pub fn new(
        timeout: u64,
        cookie_key: CookieKey,
        memcached_url: String,
        metrics_config: Option<MetricsConfig>,
        next_port: u16,
    ) -> KeServerConfig {
        KeServerConfig {
            addrs: Vec::new(),

            // Use terminal logger as a default logger. The users can override it using
            // `set_logger` later, if they want.
            //
            // According to `sloggers-0.3.2` source code, the function doesn't return an error at
            // all. There should be no problem unwrapping here.
            logger: TerminalLoggerBuilder::new()
                .build()
                .expect("BUG: TerminalLoggerBuilder::build shouldn't return an error."),

            tls_certs: Vec::new(),
            tls_secret_keys: Vec::new(),

            // From parameters.
            cookie_key,
            timeout,
            memcached_url,
            metrics_config,
            next_port,
        }
    }

    /// Add a TLS certificate into the config.
    // Because the order of `tls_certs` has to correspond to the order of `tls_secret_keys`, this
    // method has to be private for now.
    fn add_tls_cert(&mut self, cert: Certificate) {
        self.tls_certs.push(cert);
    }

    /// Add a TLS private key into the config.
    // Because the order of `tls_certs` has to correspond to the order of `tls_secret_keys`, this
    // method has to be private for now.
    fn add_tls_secret_key(&mut self, secret_key: PrivateKey) {
        self.tls_secret_keys.push(secret_key);
    }

    /// Add an address into the config.
    pub fn add_address(&mut self, addr: SocketAddr) {
        self.addrs.push(addr);
    }

    /// Return a list of addresses.
    pub fn addrs(&self) -> &[SocketAddr] {
        self.addrs.as_slice()
    }

    /// Return the cookie key of the config.
    pub fn cookie_key(&self) -> &CookieKey {
        &self.cookie_key
    }

    /// Set a new logger to the config.
    pub fn set_logger(&mut self, logger: slog::Logger) {
        self.logger = logger;
    }

    /// Return the logger of the config.
    pub fn logger(&self) -> &slog::Logger {
        &self.logger
    }

    /// Return the memcached url of the config.
    pub fn memcached_url(&self) -> &str {
        &self.memcached_url
    }

    /// Return the connection timeout of the config.
    pub fn timeout(&self) -> u64 {
        self.timeout
    }

    /// Import TLS certificates from a file.
    ///
    /// # Errors
    ///
    /// There will be an error if we cannot open the file or the content is not parsable to get
    /// certificates.
    ///
    // Because the order of `tls_certs` has to correspond to the order of `tls_secret_keys`, this
    // method has to be private for now.
    fn import_tls_certs(&mut self, filename: &str) -> Result<(), std::io::Error> {
        // Open a file. If there is any error, return it immediately.
        let file = File::open(filename)?;

        match pemfile::certs(&mut std::io::BufReader::new(file)) {
            Ok(certs) => {
                // Add all parsed certificates.
                for cert in certs {
                    self.add_tls_cert(cert);
                }
                // Return success.
                Ok(())
            }
            // We don't use Err(_) here because if the error type of `rustls` changes in the
            // future, we will get noticed.
            //
            // The `std::io` module has an error kind of `InvalidData` which is perfectly
            // suitable for our kind of error.
            Err(()) => Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("cannot parse TLS certificates from {}", filename),
            )),
        }
    }

    /// Import TLS private keys from a file.
    ///
    /// # Errors
    ///
    /// There will be an error if we cannot open the file or the content is not parsable to get
    /// private keys.
    ///
    // Because the order of `tls_certs` has to correspond to the order of `tls_secret_keys`, this
    // method has to be private for now.
    fn import_tls_secret_keys(&mut self, filename: &str) -> Result<(), std::io::Error> {
        // Open a file. If there is any error, return it immediately.
        let file = File::open(filename)?;

        match pemfile::pkcs8_private_keys(&mut std::io::BufReader::new(file)) {
            Ok(secret_keys) => {
                // Add all parsed secret keys.
                for secret_key in secret_keys {
                    self.add_tls_secret_key(secret_key);
                }
                // Return success.
                Ok(())
            }
            // We don't use Err(_) here because if the error type of `rustls` changes in the
            // future, we will get noticed.
            //
            // The `std::io` module has an error kind of `InvalidData` which is perfectly
            // suitable for our kind of error.
            Err(()) => Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("cannot parse TLS private keys from {}", filename),
            )),
        }
    }

    /// Parse a config from a file.
    ///
    /// # Errors
    ///
    /// Currently we return `config::ConfigError` which is returned from functions in the
    /// `config` crate itself.
    ///
    /// For any error from any file specified in the configuration, `std::io::Error` which is
    /// wrapped inside `config::ConfigError::Foreign` will be returned.
    ///
    /// For any address parsing error, `std::io::Error` wrapped inside
    /// `config::ConfigError::Foreign` will also be returned.
    ///
    /// In addition, it also returns some custom `config::ConfigError::Message` errors, for the
    /// following cases:
    ///
    /// * The next port in the configuration file is a valid `i64` but not a valid `u16`.
    /// * The connection timeout in the configuration file is a valid `i64` but not a valid `u64`.
    ///
    // Returning a `Message` object here is not a good practice. I will figure out a good practice
    // later.
    pub fn parse(filename: &str) -> Result<KeServerConfig, config::ConfigError> {
        let mut settings = config::Config::new();
        settings.merge(config::File::with_name(filename))?;

        // XXX: The code of parsing a next port here is quite ugly due to the `get_int` interface.
        // Please don't be surprised :)
        let next_port = match u16::try_from(settings.get_int("next_port")?) {
            Ok(port) => port,
            // The error will happen when the port number is not in a range of `u16`.
            Err(_) => {
                // Returning a custom message is not a good practice, but we can improve it later
                // when we don't have to depend on `config` crate.
                return Err(config::ConfigError::Message(String::from(
                    "the next port is not a valid u16",
                )));
            }
        };
        let memcached_url = settings.get_str("memc_url")?;

        // XXX: The code of parsing a connection timeout here is quite ugly due to the `get_int`
        // interface. Please don't be surprised :)

        // Resolves the connection timeout.
        let timeout = match settings.get_int("conn_timeout") {
            // If it's a not-found error, we just set it to the default value of 30 seconds.
            Err(config::ConfigError::NotFound(_)) => 30,

            // If it's other error, for example, unparseable error, it means that the user intended
            // to enter the timeout but it just fails.
            Err(error) => return Err(error),

            Ok(val) => {
                match u64::try_from(val) {
                    Ok(val) => val,
                    // The error will happen when the timeout is not in a range of `u64`.
                    Err(_) => {
                        // Returning a custom message is not a good practice, but we can improve
                        // it later when we don't have to depend on `config` crate.
                        return Err(config::ConfigError::Message(String::from(
                            "the connection timeout is not a valid u64",
                        )));
                    }
                }
            }
        };

        // Resolves metrics configuration.
        let metrics_config = get_metrics_config(&settings);

        // Note that all of the file reading stuffs should be at the end of the function so that
        // all the not-file-related stuffs can fail fast.

        // All config filenames must be given with relative paths to where the server is run.
        // Otherwise, cfnts will try to open the file while in the incorrect directory.
        let certs_filename = settings.get_str("tls_cert_file")?;
        let secret_keys_filename = settings.get_str("tls_key_file")?;

        let cookie_key_filename = settings.get_str("cookie_key_file")?;
        let cookie_key = CookieKey::parse(&cookie_key_filename).wrap_err()?;

        let mut config = KeServerConfig::new(
            timeout,
            cookie_key,
            memcached_url,
            metrics_config,
            next_port,
        );

        config.import_tls_certs(&certs_filename).wrap_err()?;
        config
            .import_tls_secret_keys(&secret_keys_filename)
            .wrap_err()?;

        let addrs = settings.get_array("addr")?;
        for addr in addrs {
            // Parse SocketAddr from a string.
            let sock_addr = addr.to_string().parse().wrap_err()?;
            config.add_address(sock_addr);
        }

        Ok(config)
    }
}


================================================
FILE: src/nts_ke/server/connection.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! NTS-KE server connection.

use mio::tcp::{Shutdown, TcpStream};

use rustls::Session;

use slog::{debug, error, info};

use std::io::{Read, Write};
use std::sync::{Arc, RwLock};

use crate::cookie::{make_cookie, NTSKeys};
use crate::key_rotator::KeyRotator;
use crate::nts_ke::records::gen_key;
use crate::nts_ke::records::{
    deserialize,
    process_record,

    // Functions.
    serialize,
    // Records.
    AeadAlgorithmRecord,
    // Errors.
    DeserializeError,

    EndOfMessageRecord,
    // Enums.
    KnownAeadAlgorithm,
    KnownNextProtocol,
    NewCookieRecord,
    NextProtocolRecord,
    Party,

    PortRecord,

    // Structs.
    ReceivedNtsKeRecordState,

    // Constants.
    HEADER_SIZE,
};

use super::ke_server::KeServerState;
use super::listener::KeServerListener;

// response uses the configuration and the keys and computes the response
// sent to the client.
fn response(keys: NTSKeys, rotator: &Arc<RwLock<KeyRotator>>, port: u16) -> Vec<u8> {
    let mut response: Vec<u8> = Vec::new();

    let next_protocol_record = NextProtocolRecord::from(vec![KnownNextProtocol::Ntpv4]);
    let aead_record = AeadAlgorithmRecord::from(vec![KnownAeadAlgorithm::AeadAesSivCmac256]);
    let port_record = PortRecord::new(Party::Server, port);
    let end_record = EndOfMessageRecord;

    response.append(&mut serialize(next_protocol_record));
    response.append(&mut serialize(aead_record));

    let rotor = rotator.read().unwrap();
    let (key_id, actual_key) = rotor.latest_key_value();

    // According to the spec, if the next protocol is NTPv4, we should send eight cookies to the
    // client.
    for _ in 0..8 {
        let cookie = make_cookie(keys, actual_key.as_ref(), key_id);
        let cookie_record = NewCookieRecord::from(cookie);
        response.append(&mut serialize(cookie_record));
    }
    response.append(&mut serialize(port_record));
    response.append(&mut serialize(end_record));
    response
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum KeServerConnState {
    /// The connection is just connected. The TLS handshake is not done yet.
    Connected,
    /// Doing the TLS handshake,
    TlsHandshaking,
    /// The TLS handshake is done. It's opened for requests now.
    Opened,
    /// The response is sent after getting a good request.
    ResponseSent,
    /// The connection is closed.
    Closed,
}

/// NTS-KE server TCP connection.
pub struct KeServerConn {
    /// Reference back to the corresponding `KeServer` state.
    server_state: Arc<KeServerState>,

    /// Kernel TCP stream.
    tcp_stream: TcpStream,

    /// The mio token for this connection.
    token: mio::Token,

    /// TLS session for this connection.
    tls_session: rustls::ServerSession,

    /// The status of the connection.
    state: KeServerConnState,

    /// The state of NTS-KE.
    ntske_state: ReceivedNtsKeRecordState,

    /// The buffer of NTS-KE Stream.
    ntske_buffer: Vec<u8>,

    /// Logger.
    logger: slog::Logger,
}

impl KeServerConn {
    pub fn new(
        tcp_stream: TcpStream,
        token: mio::Token,
        listener: &KeServerListener,
    ) -> KeServerConn {
        let server_state = listener.state();

        // Create a TLS session from a server-wide configuration.
        let tls_session = rustls::ServerSession::new(&server_state.tls_server_config);
        // Create a child logger for the connection.
        let logger = listener
            .logger()
            .new(slog::o!("client" => listener.addr().to_string()));

        let ntske_state = ReceivedNtsKeRecordState {
            finished: false,
            next_protocols: Vec::new(),
            aead_scheme: Vec::new(),
            cookies: Vec::new(),
            next_server: None,
            next_port: None,
        };

        KeServerConn {
            // Create an `Arc` reference.
            server_state: server_state.clone(),
            tcp_stream,
            tls_session,
            token,
            state: KeServerConnState::Connected,
            ntske_state,
            ntske_buffer: Vec::new(),
            logger,
        }
    }

    /// The handler when the connection is ready to ready or write.
    pub fn ready(&mut self, poll: &mut mio::Poll, event: &mio::Event) {
        if event.readiness().is_readable() {
            self.read_ready();
        }

        if event.readiness().is_writable() {
            self.write_ready();
        }

        if self.state() != KeServerConnState::Closed {
            // TODO: Fix unwrap later.
            self.reregister(poll).unwrap();
        }
    }

    fn read_ready(&mut self) {
        // If this is the first time that `read_ready` is called, it means that we start reading
        // some TLS client hello from the client. So we need to change the state to TlsHandshaking.
        if self.state == KeServerConnState::Connected {
            self.state = KeServerConnState::TlsHandshaking;
        }

        // Read some data from the stream and feed it to the TLS stream.
        let result = self.tls_session.read_tls(&mut self.tcp_stream);

        let read_count = match result {
            Ok(value) => value,
            Err(error) => {
                // If it's a WouldBlock, it's not actually an error. So we don't need to close the
                // connection and return silently.
                if let std::io::ErrorKind::WouldBlock = error.kind() {
                    return;
                }

                // Close the connection on error.
                error!(self.logger, "read error: {}", error);
                self.shutdown();
                return;
            }
        };

        // If we reach the end-of-file, just close the connection.
        if read_count == 0 {
            info!(self.logger, "eof");
            self.shutdown();
            return;
        }

        // Process newly received TLS messages.
        let processed = self.tls_session.process_new_packets();

        if let Err(error) = processed {
            error!(self.logger, "cannot process packet: {}", error);
            self.shutdown();
        }

        let mut buf = Vec::new();
        let result = self.tls_session.read_to_end(&mut buf);

        if let Err(error) = result {
            error!(self.logger, "read failed: {}", error);
            self.shutdown();
            return;
        }

        if !buf.is_empty() {
            debug!(self.logger, "plaintext read {},", buf.len());
            self.ntske_buffer.append(&mut buf);
            let mut reader = &self.ntske_buffer[..];

            // The plaintext is not empty. It means that the handshake is also done. We can change
            // the state now.
            if self.state == KeServerConnState::TlsHandshaking {
                self.state = KeServerConnState::Opened;
            }

            let keys = gen_key(&self.tls_session).unwrap();

            while !self.ntske_state.finished {
                // need to read 4 bytes to get the header.
                if reader.len() < HEADER_SIZE {
                    info!(
                        self.logger,
                        "readable nts-ke stream is not enough to read header"
                    );
                    self.ntske_buffer = Vec::from(reader);
                    return;
                }

                // need to read the body_length to get the body.
                let body_length = u16::from_be_bytes([reader[2], reader[3]]) as usize;
                if reader.len() < HEADER_SIZE + body_length {
                    info!(
                        self.logger,
                        "readable nts-ke stream is not enough to read body"
                    );
                    self.ntske_buffer = Vec::from(reader);
                    return;
                }

                // Reconstruct the whole record byte array to let the `records` module deserialize it.
                let mut record_bytes = vec![0; HEADER_SIZE + body_length];
                reader.read_exact(&mut record_bytes).unwrap();

                match deserialize(Party::Server, record_bytes.as_slice()) {
                    Ok(record) => {
                        let status = process_record(record, &mut self.ntske_state);
                        match status {
                            Ok(_) => {}
                            Err(err) => {
                                error!(self.logger, "process nts-ke record: {}", err);
                                self.shutdown();
                                return;
                            }
                        }
                    }
                    Err(DeserializeError::UnknownNotCriticalRecord) => {
                        // If it's not critical, just ignore the error.
                        debug!(self.logger, "unknown record type");
                    }
                    Err(DeserializeError::UnknownCriticalRecord) => {
                        // TODO: This should propertly handled by sending an Error record.
                        debug!(self.logger, "error: unknown critical record");
                        self.shutdown();
                        return;
                    }
                    Err(DeserializeError::Parsing(error)) => {
                        // TODO: This shouldn't be wrapped as a trait object.
                        debug!(self.logger, "error: {}", error);
                        self.shutdown();
                        return;
                    }
                }
            }

            // We have to make sure that the response is not sent yet.
            if self.state == KeServerConnState::Opened {
                // TODO: Fix unwrap later.
                self.tls_session
                    .write_all(&response(
                        keys,
                        &self.server_state.rotator,
                        self.server_state.config.next_port,
                    ))
                    .unwrap();
                // Mark that the response is sent.
                self.state = KeServerConnState::ResponseSent;
            }
        }
    }

    fn write_ready(&mut self) {
        if let Err(error) = self.tls_session.write_tls(&mut self.tcp_stream) {
            error!(self.logger, "write failed: {}", error);
            self.shutdown();
        }
    }

    /// Register the connection with Poll.
    pub fn register(&self, poll: &mut mio::Poll) -> Result<(), std::io::Error> {
        poll.register(
            &self.tcp_stream,
            self.token,
            self.interest(),
            mio::PollOpt::level(),
        )
    }

    /// Re-register the connection with Poll.
    pub fn reregister(&self, poll: &mut mio::Poll) -> Result<(), std::io::Error> {
        poll.reregister(
            &self.tcp_stream,
            self.token,
            self.interest(),
            mio::PollOpt::level(),
        )
    }

    fn interest(&self) -> mio::Ready {
        let mut ready = mio::Ready::empty();

        if self.tls_session.wants_read() {
            ready |= mio::Ready::readable();
        }
        if self.tls_session.wants_write() {
            ready |= mio::Ready::writable();
        }
        ready
    }

    pub fn state(&self) -> KeServerConnState {
        self.state
    }

    pub fn shutdown(&mut self) {
        // TODO: Fix unwrap later.
        self.tcp_stream.shutdown(Shutdown::Both).unwrap();
        self.state = KeServerConnState::Closed;
    }
}


================================================
FILE: src/nts_ke/server/ke_server.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! NTS-KE server instantiation.

use slog::info;

use std::sync::{Arc, RwLock};

use crate::key_rotator::periodic_rotate;
use crate::key_rotator::KeyRotator;
use crate::key_rotator::RotateError;
use crate::metrics;

use super::config::KeServerConfig;
use super::listener::KeServerListener;

/// NTS-KE server state that will be shared among listeners.
pub(super) struct KeServerState {
    /// Configuration for the NTS-KE server.
    // You can see that I don't expand the config's properties here because, by keeping it like
    // this, we will know what is the config and what is the state.
    pub(super) config: KeServerConfig,

    /// Key rotator. Read this property to get latest keys.
    // The internal state of this rotator can be changed even if the KeServer instance is
    // immutable. That's because of the nature of RwLock. This property is normally used by
    // KeServer to read the state only.
    pub(super) rotator: Arc<RwLock<KeyRotator>>,

    /// TLS server configuration which will be used among listeners.
    // We use `Arc` here so that every thread can read the config, but the drawback of using `Arc`
    // is that it uses garbage collection.
    pub(super) tls_server_config: Arc<rustls::ServerConfig>,
}

/// NTS-KE server instance.
pub struct KeServer {
    /// State shared among listerners.
    // We use `Arc` so that all the KeServerListener's can reference back to this object.
    state: Arc<KeServerState>,

    /// List of listeners associated with the server.
    /// Each listener is associated with each address in the config. You can check if the server
    /// already started or not by checking that this vector is empty.
    // We use `Arc` because the listener will listen in another thread.
    listeners: Vec<Arc<RwLock<KeServerListener>>>,
}

impl KeServer {
    /// Create a new `KeServer` instance, connect to the Memcached server, and rotate initial keys.
    ///
    /// This doesn't start the server yet. It just makes to the state that it's ready to start.
    /// Please run `start` to start the server.
    pub fn connect(config: KeServerConfig) -> Result<KeServer, RotateError> {
        let rotator = KeyRotator::connect(
            String::from("/nts/nts-keys"),
            String::from(config.memcached_url()),
            // We need to clone all of the following properties because the key rotator also
            // has to own them.
            config.cookie_key().clone(),
            config.logger().clone(),
        )?;

        // Putting it in a block just to make it easier to read :)
        let tls_server_config = {
            // No client auth for TLS server.
            let client_auth = rustls::NoClientAuth::new();
            // TLS server configuration.
            let mut server_config = rustls::ServerConfig::new(client_auth);

            // We support only TLS1.3
            server_config.versions = vec![rustls::ProtocolVersion::TLSv1_3];

            // Set the certificate chain and its corresponding private key.
            server_config
                .set_single_cert(
                    // rustls::ServerConfig wants to own both of them.
                    config.tls_certs.clone(),
                    config.tls_secret_keys[0].clone(),
                )
                .expect("invalid key or certificate");

            // According to the NTS specification, ALPN protocol must be "ntske/1".
            server_config.set_protocols(&[Vec::from("ntske/1".as_bytes())]);

            server_config
        };

        let state = Arc::new(KeServerState {
            config,
            rotator: Arc::new(RwLock::new(rotator)),
            tls_server_config: Arc::new(tls_server_config),
        });

        Ok(KeServer {
            state,
            listeners: Vec::new(),
        })
    }

    /// Start the server.
    pub fn start(&mut self) -> Result<(), std::io::Error> {
        let logger = self.state.config.logger();

        // Side-effect. Logging.
        info!(logger, "initializing keys with memcached");

        // Create another reference to the lock so that we can pass it to another thread and
        // periodically rotate the keys.
        let mutable_rotator = self.state.rotator.clone();

        // Create a new thread and periodically rotate the keys.
        periodic_rotate(mutable_rotator);

        // We need to clone the metrics config here because we need to move it to another thread.
        if let Some(metrics_config) = self.state.config.metrics_config.clone() {
            info!(logger, "spawning metrics");

            // Create a child logger to use inside the metric server.
            let log_metrics = logger.new(slog::o!("component" => "metrics"));

            // Start a metric server.
            std::thread::spawn(move || {
                metrics::run_metrics(metrics_config, &log_metrics)
                    .expect("metrics could not be run; starting ntp server failed");
            });
        }

        // For each address in the config, we will create a listener that will listen on that
        // address. After the creation, we will create another thread and start listening inside
        // that thread.

        for addr in self.state.config.addrs() {
            // Side-effect. Logging.
            info!(logger, "starting NTS-KE server over TCP/TLS on {}", addr);

            // Instantiate a listener.
            // If there is an error here just return an error immediately so that we don't have to
            // start a thread for other address.
            let listener = KeServerListener::bind(*addr, self)?;

            // It needs to be referenced by this thread and the new thread.
            let atomic_listener = Arc::new(RwLock::new(listener));

            self.listeners.push(atomic_listener);
        }

        // Join handles for the listeners.
        let mut handles = Vec::new();

        for listener in self.listeners.iter() {
            // The listener reference that will be moved into the thread.
            let cloned_listener = listener.clone();

            let handle = std::thread::spawn(move || {
                // Unwrapping should be fine here because there is no a write lock while we are
                // trying to lock it and we will wait for the thread to finish before returning
                // from this `start` method.
                //
                // If you don't want to wait for this thread to finish before returning from the
                // `start` method, you have to look at this `unwrap` and handle it carefully.
                //
                // TODO: figure what to do later when the listen fails.
                cloned_listener.write().unwrap().listen().unwrap();
            });

            // Add it into the list of listeners.
            handles.push(handle);
        }

        // We need to wait for the listeners to finish. If you don't want to wait for the listeners
        // anymore, please don't forget to take care an `unwrap` in the thread a few lines above.
        for handle in handles {
            // We don't care it's a normal exit or it's a panic from the thread, so we just ignore
            // the result here.
            let _ = handle.join();
        }

        Ok(())
    }

    /// Return the state of the server.
    pub(super) fn state(&self) -> &Arc<KeServerState> {
        &self.state
    }
}


================================================
FILE: src/nts_ke/server/listener.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! NTS-KE server listener.

use mio::net::TcpListener;

use slog::{error, info};

use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, SystemTime};

use crate::cfsock;

use super::connection::KeServerConn;
use super::connection::KeServerConnState;
use super::ke_server::KeServer;
use super::ke_server::KeServerState;

const LISTENER_MIO_TOKEN_ID: usize = 0;
const CONNECTION_MIO_TOKEN_ID_MIN: usize = LISTENER_MIO_TOKEN_ID + 1;
// `usize::max_value()` is reserved for mio internal use, so we need to minus one here.
const CONNECTION_MIO_TOKEN_ID_MAX: usize = usize::max_value() - 1;

/// The token used to associate the mio event with the lister event.
const LISTENER_MIO_TOKEN: mio::Token = mio::Token(LISTENER_MIO_TOKEN_ID);

/// NTS-KE server internal listener for a specific listened address.
/// One listener will correspond to one kernel listening socket.
pub struct KeServerListener {
    /// Reference back to the corresponding `KeServer` state.
    state: Arc<KeServerState>,

    /// TCP listener for incoming connections.
    tcp_listener: TcpListener,

    /// List of connections accepted by this listener.
    connections: HashMap<mio::Token, KeServerConn>,

    /// Deadline indices for connections.
    // We use `Reverse` because we want a min heap.
    deadlines: BinaryHeap<Reverse<(SystemTime, mio::Token)>>,

    /// The next mio token id for a new connection.
    next_conn_token_id: usize,

    /// Address and port that this listener will listen to.
    addr: SocketAddr,

    /// Polling object from mio.
    poll: mio::Poll,

    /// Logger.
    logger: slog::Logger,
}

impl KeServerListener {
    /// Bind a new listener with the specified address and server.
    ///
    /// # Errors
    ///
    /// All the errors here are from the kernel which we don't have to know about for now.
    pub fn bind(addr: SocketAddr, server: &KeServer) -> Result<KeServerListener, std::io::Error> {
        let state = server.state();
        let poll = mio::Poll::new()?;

        // Create a listening std tcp listener.
        let std_tcp_listener = cfsock::tcp_listener(&addr)?;

        // Transform a std tcp listener to a mio tcp listener.
        let mio_tcp_listener = TcpListener::from_std(std_tcp_listener)?;

        // Register for the event that the listener is readable.
        poll.register(
            &mio_tcp_listener,
            LISTENER_MIO_TOKEN,
            mio::Ready::readable(),
            mio::PollOpt::level(),
        )?;

        Ok(KeServerListener {
            // Create an `Arc` reference.
            state: state.clone(),
            tcp_listener: mio_tcp_listener,
            connections: HashMap::new(),
            deadlines: BinaryHeap::new(),
            next_conn_token_id: CONNECTION_MIO_TOKEN_ID_MIN,
            addr,
            // In the future, we may want to use the child logger instead the logger itself.
            logger: state.config.logger().clone(),
            poll,
        })
    }

    /// Block the thread and start polling the events.
    pub fn listen(&mut self) -> Result<(), std::io::Error> {
        // Holding up to 2048 events.
        let mut events = mio::Events::with_capacity(2048);

        loop {
            // The error returned here is from the kernel select.
            self.poll.poll(&mut events, None)?;

            for event in events.iter() {
                // Close all expired connections.
                self.close_expired_connections();
                let token = event.token();

                // If the event is the listener event.
                if token == LISTENER_MIO_TOKEN {
                    // Start accepting a new connection.
                    if let Err(error) = self.accept() {
                        error!(
                            self.logger,
                            "accept failed unrecoverably with error: {}", error
                        );
                    }
                    continue;
                };

                // If the event is not the listener event, it must be a connection event.

                // The connection associated with the token may not exist for some reason. In which
                // case, we just ignore it.
                if let Some(connection) = self.connections.get_mut(&token) {
                    connection.ready(&mut self.poll, &event);

                    if connection.state() == KeServerConnState::Closed {
                        self.connections.remove(&token);
                    }
                }
            }
        }
    }

    /// Accepting a new connection. This will not block the thread, if it's called after receiving
    /// the `LISTENER_MIO_TOKEN` event. But it will block, if it's not.
    fn accept(&mut self) -> Result<(), std::io::Error> {
        let (tcp_stream, addr) = match self.tcp_listener.accept() {
            Ok(value) => value,
            Err(error) => {
                // If it's WouldBlock, just treat it like a success because there isn't an actual
                // error. It's just in a non-blocking mode.
                if error.kind() == std::io::ErrorKind::WouldBlock {
                    return Ok(());
                }

                // If it's not WouldBlock, it's an error.
                error!(
                    self.logger,
                    "encountered error while accepting connection; err={}", error
                );

                // TODO: I don't understand why we need another tcp listener and register a new
                // event here. I will figure it out after I finish refactoring everything.
                self.tcp_listener = TcpListener::bind(&self.addr)?;
                // TODO: Ignore error first. I wil figure out what to do later if there is an
                // error.
                self.poll.register(
                    &self.tcp_listener,
                    LISTENER_MIO_TOKEN,
                    mio::Ready::readable(),
                    mio::PollOpt::level(),
                )?;

                // TODO: I will figure why it returns Ok later.
                return Ok(());
            }
        };

        // Successfully accepting a connection.

        info!(self.logger, "accepting new connection from {}", addr);

        let token = mio::Token(self.next_conn_token_id);
        self.increment_next_conn_token_id();

        let timeout_duration = Duration::new(self.state.config.timeout(), 0);

        // If the timeout is so large that we cannot put it in SystemTime, we can assume that
        // it doesn't have a timeout and just don't add it into the map.
        if let Some(timeout_systime) = SystemTime::now().checked_add(timeout_duration) {
            self.deadlines.push(Reverse((timeout_systime, token)));
        }

        // Create a new connection instance.
        let connection = KeServerConn::new(tcp_stream, token, self);
        // TODO: Fix the unwrap later.
        connection.register(&mut self.poll).unwrap();

        self.connections.insert(token, connection);

        Ok(())
    }

    /// Increment next_conn_token_id.
    fn increment_next_conn_token_id(&mut self) {
        match self.next_conn_token_id.checked_add(1) {
            Some(value) => self.next_conn_token_id = value,
            // If it overflows just set it to the minimum value.
            None => self.next_conn_token_id = CONNECTION_MIO_TOKEN_ID_MIN,
        }

        // If it exceeds the maximum, we also set it to the minimum value.
        if self.next_conn_token_id > CONNECTION_MIO_TOKEN_ID_MAX {
            self.next_conn_token_id = CONNECTION_MIO_TOKEN_ID_MIN;
        }
    }

    /// Closes the expired timeouts, looping until they are all gone.
    /// We remove the timeout from the heap, and kill the connection if it exists.
    fn close_expired_connections(&mut self) {
        let now = SystemTime::now();

        while let Some(earliest) = self.deadlines.peek() {
            let Reverse((deadline, token)) = earliest;

            if deadline < &now {
                // If the deadline is already elapsed, close the connection and pop the heap.
                // The connection associated with the token may not exist because, when we close
                // the connection, it's not possible to find an entry in the heap. In which case,
                // we can just pop the deadline heap.
                if let Some(mut connection) = self.connections.remove(token) {
                    error!(self.logger, "forcible shutdown after timeout");
                    connection.shutdown();
                }
                self.deadlines.pop();

                // In this case, this means that there may be more elapsed deadline. Continue the
                // loop.
            } else {
                // If not, it means there is no more elapsed deadline in the heap. So we can just
                // stop the loop.
                break;
            }
        }
    }

    /// Return the state of the corresponding server.
    pub(super) fn state(&self) -> &Arc<KeServerState> {
        &self.state
    }

    /// Return the logger of this listener.
    pub(super) fn logger(&self) -> &slog::Logger {
        &self.logger
    }

    /// Return the address-port of this listener.
    pub(super) fn addr(&self) -> &SocketAddr {
        &self.addr
    }
}


================================================
FILE: src/nts_ke/server/mod.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! NTS-KE server implementation.

mod config;
mod connection;
mod ke_server;
mod listener;

// We expose only two structs: KeServer and KeServerConfig. KeServer is used to run an instant of
// the NTS-KE server and KeServerConfig is used to instantiate KeServer.
pub use self::config::KeServerConfig;
pub use self::ke_server::KeServer;


================================================
FILE: src/sub_command/client.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! The client subcommand.

use slog::debug;

use std::fs;
use std::io::BufReader;
use std::process;

use rustls::{internal::pemfile::certs, Certificate};

use crate::error::WrapError;
use crate::ntp::client::run_nts_ntp_client;
use crate::nts_ke::client::run_nts_ke_client;

#[derive(Debug)]
pub struct ClientConfig {
    pub host: String,
    pub port: Option<String>,
    pub trusted_cert: Option<Certificate>,
    pub use_ipv4: Option<bool>,
}

pub fn load_tls_certs(path: String) -> Result<Vec<Certificate>, config::ConfigError> {
    certs(&mut BufReader::new(fs::File::open(&path).wrap_err()?)).map_err(|()| {
        config::ConfigError::Message(format!("could not load certificate from {}", &path))
    })
}

/// The entry point of `client`.
pub fn run(matches: &clap::ArgMatches<'_>) {
    // This should return the clone of `logger` in the main function.
    let logger = slog_scope::logger();

    let host = matches.value_of("host").map(String::from).unwrap();
    let port = matches.value_of("port").map(String::from);
    let cert_file = matches.value_of("cert").map(String::from);

    // By default, use_ipv4 is None (no preference for using either ipv4 or ipv6
    // so client sniffs which one to use based on support)
    // However, if a user specifies the ipv4 flag, we set use_ipv4 = Some(true)
    // If they specify ipv6 (only one can be specified as they are mutually exclusive
    // args), set use_ipv4 = Some(false)
    let ipv4 = matches.is_present("ipv4");
    let mut use_ipv4 = None;
    if ipv4 {
        use_ipv4 = Some(true);
    } else {
        // Now need to check whether ipv6 is being used, since ipv4 has not been mandated
        if matches.is_present("ipv6") {
            use_ipv4 = Some(false);
        }
    }

    let mut trusted_cert = None;
    if let Some(file) = cert_file {
        if let Ok(certs) = load_tls_certs(file) {
            trusted_cert = Some(certs[0].clone());
        }
    }

    let client_config = ClientConfig {
        host,
        port,
        trusted_cert,
        use_ipv4,
    };

    let res = run_nts_ke_client(&logger, client_config);

    if let Err(err) = res {
        eprintln!("failure of tls stage: {}", err);
        process::exit(1)
    }
    let state = res.unwrap();
    debug!(logger, "running UDP client with state {:x?}", state);
    let res = run_nts_ntp_client(&logger, state);
    match res {
        Err(err) => {
            eprintln!("failure of client: {}", err);
            process::exit(1)
        }
        Ok(result) => {
            println!("stratum: {:}", result.stratum);
            println!("offset: {:.6}", result.time_diff);
        }
    }
}


================================================
FILE: src/sub_command/ke_server.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! The ke-server subcommand.

use std::process;

use crate::nts_ke::server::{KeServer, KeServerConfig};

/// Get a configuration file path for `ke-server`.
///
/// If the path is not specified, the system-wide configuration file (/etc/cfnts/ke-server.config)
/// will be used instead.
///
fn resolve_config_filename(matches: &clap::ArgMatches<'_>) -> String {
    match matches.value_of("configfile") {
        // If the config file is specified in the arguments, just use it.
        Some(filename) => String::from(filename),
        // If not, use the system-wide configuration file.
        None => String::from("/etc/cfnts/ke-server.config"),
    }
}

/// The entry point of `ke-server`.
pub fn run(matches: &clap::ArgMatches<'_>) {
    // This should return the clone of `logger` in the main function.
    let global_logger = slog_scope::logger();

    // Get the config file path.
    let filename = resolve_config_filename(matches);
    let mut config = match KeServerConfig::parse(&filename) {
        Ok(val) => val,
        // If there is an error, display it.
        Err(err) => {
            eprintln!("{}", err);
            process::exit(1);
        }
    };

    let logger = global_logger.new(slog::o!("component" => "nts_ke"));
    // Let the parsed config use the child logger of the global logger.
    config.set_logger(logger);

    // Try to connect to the Memcached server.
    let mut server = match KeServer::connect(config) {
        Ok(server) => server,
        Err(_error) => {
            // Disable the log for now because the Error trait is not implemented for
            // RotateError yet.
            // eprintln!("starting NTS-KE server failed: {}", error);
            process::exit(1);
        }
    };

    // Start listening for incoming connections.
    if let Err(error) = server.start() {
        eprintln!("starting NTS-KE server failed: {}", error);
        process::exit(1);
    }
}


================================================
FILE: src/sub_command/mod.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! Subcommand collections.

pub mod client;
pub mod ke_server;
pub mod ntp_server;


================================================
FILE: src/sub_command/ntp_server.rs
================================================
// This file is part of cfnts.
// Copyright (c) 2019, Cloudflare. All rights reserved.
// See LICENSE for licensing information.

//! The ntp-server subcommand.

use std::process;

use crate::ntp::server::start_ntp_server;
use crate::ntp::server::NtpServerConfig;

/// Get a configuration file path for `ntp-server`.
///
/// If the path is not specified, the system-wide configuration file (/etc/cfnts/ntp-server.config)
/// will be used instead.
///
fn resolve_config_filename(matches: &clap::ArgMatches<'_>) -> String {
    match matches.value_of("configfile") {
        // If the config file is specified in the arguments, just use it.
        Some(filename) => String::from(filename),
        // If not, use the system-wide configuration file.
        None => String::from("/etc/cfnts/ntp-server.config"),
    }
}

/// The entry point of `ntp-server`.
pub fn run(matches: &clap::ArgMatches<'_>) {
    // This should return the clone of `logger` in the main function.
    let global_logger = slog_scope::logger();

    // Get the config file path.
    let filename = resolve_config_filename(matches);
    let mut config = match NtpServerConfig::parse(&filename) {
        Ok(val) => val,
        // If there is an error, display it.
        Err(err) => {
            eprintln!("{}", err);
            process::exit(1);
        }
    };

    let logger = global_logger.new(slog::o!("component" => "ntp"));
    // Let the parsed config use the child logger of the global logger.
    config.set_logger(logger);

    if let Err(err) = start_ntp_server(config) {
        eprintln!("starting NTP server failed: {}", err);
        process::exit(1);
    }
}


================================================
FILE: tests/ca-key.pem
================================================
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCzTB9ETn6RgGHT
EXkvXtUxCRtN5oz8eh1hD98OBOYrqC9gpw90xurkpvrSKA/XGi2er+b+fDaMZnvO
rUAmO5tkBeUv5VRArKAW1lTocTTFCXbbS1pMd4fxCePXnDed81MWFThYLr9zJ8KU
eamYMBWq6lziAynTT9+bVaj5zkLC23u9EUqPFn8Kg3hdfLE5FPzeWqYREVrst++f
npjrt6ZWlHohA6P6BAplubBTKtcOcyDT1Y8Wg7OxgLex+gq5X+4YxzpuvPzRL0uc
l4210FhPY+55zJ4Vw6lqQmY7lSloQQO2PbtkRvfpphy7J1aPaVpWXvVK2mv4tZtX
zplOK3Uf1m4hVCA/fN1A5w89RCzdOacYSEvvFRhpFo4Iw0L3u2jvO71Rxpe+ATZk
Kzhk0TnwyRCQGYGYjRsNRAnGHbe10fPaT2NoBDmojg4S1LF6plwYYxTXvUsIWo9c
KizMAYB6p6jCIcJUupulyChnSyTxvLOLKJr0YuU3ug4WEH1fw2JVgYi4X8TeGRgz
LqBA9NudPJZjXh+fCcmdfIqNpuVujWJGqFjzG6NyixGJ5SqnIByFJd1UhF5UgJPL
yseSrAn1Zqk4wKDtjPnahgDIqoLNjBh8vc2jbwTShwWWxnZK2iA+VzG4Y9l7I1Fp
ANkJp+oug6tiCiBwYzMzP6rJjDBI6wIDAQABAoICAD3Tpwh/5Mc5tQH6iYZbNjrF
gCPZt44sccsRlQIZkGFHiqbSlNLY8RDNv7oOVIABJ/ALiiUBIjJB+LlpJrDIZyoT
mldsxiPTIxUc7YSF3QOA4vp1vnqV0Uu99FJaLReLW4BG6voFjMEh2cgnN+Mh2abp
UAQjwR178oh2/mC9zmmxE7c7qjEzObWfZjcek2IyqYvnSFKkYG02dCvfna3S00oR
wxd1UOsaz5cKdBIJuMTj0FMb1k6WNbWkxDNcHKyVtt3WfYDILInZvEIQRK6IXJtr
w0U+2Nh6cwYQRX6QTgoEOUpzeRX4Hu7z9/5Vb1TeqGcWMZGRRiAqR5n8xQKem7E9
40uQdTr1GY+89x5G/AkzD/IvKCrVZLBYjzXwNZmIztoVMu0bWDwijrR2c17lQHvA
hWz4DOaONxRBxCbva7dG4FZZRj3F1q3aN7cpvFMRP7QzVtkDz1k7j8q01O1hlzY8
ezGXcHeu60Dy+/TcLvJVwxJ6/uaGcwTpsS88/ybVlJ2V24JjcoFE5YoiJEBlLW93
UBHGmaS9zd8HAIkbNugpQCHFJRVfaaINLbchpV6g/ETlGyUPRcXMZmtpuqm72esu
eWyYxoPxQ99u6Y4zoIRqx/H2eDmhxluLLwstIMA3uFD0AgcLpDHOAOlvY6QWkQuE
7TkuPcbAwGo6J3SSbBpJAoIBAQDhyzGB5ZNbVhMlbYhQiBTgQ0twBn9xLKH3sQiS
iIru/IHJxBAgz/Cg3x7CQqoz2HgZw8+kWYmwwt/icFEnCv35jErwSelpGHLx74cm
ZpzEM6hPOjtDG5Afqc6YOr5dKTHv9Gidpz0uQAKZ20awHUmkLNsOy6zfB9RhMT5y
Bx8mictEFweOjbrn7qSwm6cssoZcoqbU52sgJ/2JlSdqBbajSFkO0hPerZh/Z/x6
F/xNQR11DCsBHPMRlvUhxvk4sAjUEPhp6Pw1NUJvdsewQmIdNAE3cH+p/w4uVmvo
u+qY8X5+cz097rq9ElZxOQqaGaGe7Pvid1Y4u8NC/BjDefoPAoIBAQDLSI6ywzQK
L8YzpiIhE5Q9VDLlolv3+s02US+MNN8djMI5D9/2tvaoXaA5ax3OcvYyv0WLTddX
WOiCWAe14gIX3jTPHWbzzLfE3AYQDu7P7C3u6bripcRMVIHQ4z7Juk2xrFQjsW9Y
woq4nc6CrVJhcH5koFpn+CEU/A0vzmWfw2w/5rSZ7Mi74upPDElKwx6L8wth1sGK
bKhD4+tMbVA3fYu2PqxY8LcFjyeh9mDZJSyFSIi4Itb1e1QPCD2TQTHiQdo0Lap0
gk5ylCQaeLJDbFGVioeUfmfngvNP3n8ye/bc3h6OrM4sAz6lXMhCJfB6TE2IuAKE
vKmmry5Guk9lAoIBAHTigQBjXcLcbhDkALrflx749yZI1tQ5bKcSSAPDF1jb8jwG
eOrjegdtOTkK1Zz9JD8CNI05pKOSXd+UkQ4LDKqQS4LUYDX9aBOCEY55dBHFRA2v
cVot/I/HkaEQV9dWKfmzpixmlK9Kh44qCw/EOYj5h3TDTvwty2181nyk3yVOE6Ft
4oWTLPw/d5XNHd9vk0qFEKQKIFSHHyKHyd2Ck6c3HpMjgRG2/8iEhhiWLg+3843R
/LkYyWODp+YSYJVN22QcXNxGtbi9l2SoMns2AiBn+XE/lXblB+xI5JeYH7uI2BiR
g1R6LsUNpx35j1lyh04EE+iKKmI4IL6eThtzG1UCggEBAMCZAfoET+3GzbZplLRZ
5H0mpQJEDXapPHxV9wKTpUBN+EYv8DXDq3ZhHkjIX/kVmoUCC1WsbnXnWoMD/Goq
s2kBsm74oG4ka4gsHeJhA4ojbnGJKPNLsuvOtR+/7eEajjnj1+PpXGFwEBZSDTJq
HD8NYfLcqksPH+jN1YCRwF7ZvFnerwWW/ahlmTFDpr0amHpnz0TnP39y6wlHi8th
Vjr8y73jK08o4X5230noMGILgl7VFhO/joIOUtnbKNu3TRfc5GvDSFgSjVipWntq
FxsiKTnRghsCmFcUDoqBd2nRYVZpa/Ipbzzr5hKuEV36rBhy6pK6JEi2ptWx69o+
8rECggEBALzsK4L46sHkJOl15yxjXPV6ZmtxhHnMCPLsNrKGJZRti53JUqCrWF+v
9SBOCLnUmwijY5tbmi+CdQkpnV5VxF4nmN65KH8BonRL2pKDnGionqdqBlr7hupj
TdLlhQqTJQGLsJcsRhGiLbjyLuJMDvtQaGC7F3vPQwYqdsj5iPRFJbLS7OPkcpud
Okfm6GhBCOaMjBM2WRgWJwmAnwt9t/YiU1DhPKrUCPb5pzYXEA1DC0IWbDISLCTt
SpbR/hX6jw/IhuvCQ0vPP+4NeL9GjVNo8iF6NYIwV9swu22yQhtklooDiuQ8fzCw
xnbbOtvjxJ1sG5mW9Dblwe3GAIoI35c=
-----END PRIVATE KEY-----


================================================
FILE: tests/ca.csr
================================================
-----BEGIN CERTIFICATE REQUEST-----
MIIEizCCAnMCAQAwRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQH
DA1TYW4gRnJhbmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQCzTB9ETn6RgGHTEXkvXtUxCRtN5oz8eh1hD98O
BOYrqC9gpw90xurkpvrSKA/XGi2er+b+fDaMZnvOrUAmO5tkBeUv5VRArKAW1lTo
cTTFCXbbS1pMd4fxCePXnDed81MWFThYLr9zJ8KUeamYMBWq6lziAynTT9+bVaj5
zkLC23u9EUqPFn8Kg3hdfLE5FPzeWqYREVrst++fnpjrt6ZWlHohA6P6BAplubBT
KtcOcyDT1Y8Wg7OxgLex+gq5X+4YxzpuvPzRL0ucl4210FhPY+55zJ4Vw6lqQmY7
lSloQQO2PbtkRvfpphy7J1aPaVpWXvVK2mv4tZtXzplOK3Uf1m4hVCA/fN1A5w89
RCzdOacYSEvvFRhpFo4Iw0L3u2jvO71Rxpe+ATZkKzhk0TnwyRCQGYGYjRsNRAnG
Hbe10fPaT2NoBDmojg4S1LF6plwYYxTXvUsIWo9cKizMAYB6p6jCIcJUupulyChn
SyTxvLOLKJr0YuU3ug4WEH1fw2JVgYi4X8TeGRgzLqBA9NudPJZjXh+fCcmdfIqN
puVujWJGqFjzG6NyixGJ5SqnIByFJd1UhF5UgJPLyseSrAn1Zqk4wKDtjPnahgDI
qoLNjBh8vc2jbwTShwWWxnZK2iA+VzG4Y9l7I1FpANkJp+oug6tiCiBwYzMzP6rJ
jDBI6wIDAQABoAAwDQYJKoZIhvcNAQELBQADggIBAI+EU8ck+zgruCBjtfzqhkJ9
PtHmaRMG5ziq5tUFzHe3O++N09DKt0R+mvKxYLDDj9w61qPy0MH1UDsWaftE/LWE
ujIACIbg6Ium3iU4KViQUXMoTYveLjhh8f2i+IKDSEnORgmDBwX6Xg153SZNLZHh
hn8xiJ34bJRrsyOJM1zwGjXiD6ikNALv7OLtL45H+mpdk6BzpcJEUKBdGpa1pp1p
iCQwPbvkA/vi+OOAaUuAafrt2RaPLVOpHgvNj7PlWX6qNmUe52tTFegBIb30qtrL
DS+yqbFeHcVQ7ypV2hbqOs3uumFMUBMM0yDPPopBb0xINfKd+IOm7uVwLrHUm3sU
kOzEJRdYN6n0LQFjbpQnAM7nhu31RCtsyeUStAfdmlQCetg0vhmir0hpkMLTg/ln
/bIcDx5S2ODVS8tlfD5ggugHThdxrC2xjfvSlOUu9y9zQZnxlOscwQW9vJDKn9mV
zWXqf4SjJks1yEg57XG9WkzchiEvtQpQu2d0fZpW4+O8qQlUBIxXc3di4whHYx0j
WWxO27NFPSlTZb6TrjZW4N02vfWMbczWOqrkKBp61EpZYg6rDsZQd7t0UYTjIZDz
El1QyEbof28R9bskiwC7EGwR9DguodFH7l2K+DIjpl8FxysLFxMU2YGJOu81j5Pn
odps4ahbCad6c5cgkeJW
-----END CERTIFICATE REQUEST-----


================================================
FILE: tests/ca.pem
================================================
-----BEGIN CERTIFICATE-----
MIIFEzCCAvsCFEXBseLI9/DDsUJVNhy2Nn2ORSZFMA0GCSqGSIb3DQEBCwUAMEYx
CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj
bzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDQwOTA3MjYyNFoXDTM0MDQwNzA3
MjYyNFowRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4g
RnJhbmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQCzTB9ETn6RgGHTEXkvXtUxCRtN5oz8eh1hD98OBOYrqC9g
pw90xurkpvrSKA/XGi2er+b+fDaMZnvOrUAmO5tkBeUv5VRArKAW1lTocTTFCXbb
S1pMd4fxCePXnDed81MWFThYLr9zJ8KUeamYMBWq6lziAynTT9+bVaj5zkLC23u9
EUqPFn8Kg3hdfLE5FPzeWqYREVrst++fnpjrt6ZWlHohA6P6BAplubBTKtcOcyDT
1Y8Wg7OxgLex+gq5X+4YxzpuvPzRL0ucl4210FhPY+55zJ4Vw6lqQmY7lSloQQO2
PbtkRvfpphy7J1aPaVpWXvVK2mv4tZtXzplOK3Uf1m4hVCA/fN1A5w89RCzdOacY
SEvvFRhpFo4Iw0L3u2jvO71Rxpe+ATZkKzhk0TnwyRCQGYGYjRsNRAnGHbe10fPa
T2NoBDmojg4S1LF6plwYYxTXvUsIWo9cKizMAYB6p6jCIcJUupulyChnSyTxvLOL
KJr0YuU3ug4WEH1fw2JVgYi4X8TeGRgzLqBA9NudPJZjXh+fCcmdfIqNpuVujWJG
qFjzG6NyixGJ5SqnIByFJd1UhF5UgJPLyseSrAn1Zqk4wKDtjPnahgDIqoLNjBh8
vc2jbwTShwWWxnZK2iA+VzG4Y9l7I1FpANkJp+oug6tiCiBwYzMzP6rJjDBI6wID
AQABMA0GCSqGSIb3DQEBCwUAA4ICAQCmo1FG5Dudyy7Z0MgHY/dHe9EHWMl8FPIK
zRxFrsAUivNMaXG+rUmsPgd0tNUdqEYQOpDYyu61ayo9dZUfjfoiePp/h6jiZrUa
OxWtC53Em/UDoVz/hElRFwOYCz5O3ZQRC+c/CjSb+hsB93gi3bJIq3mIGCe9+jf1
YD2GkaD99V5gZq5U5cTGsD9rxdAOT4AMEsxsUAUVULzhA+nQw4uqxFDe2AC8ZY9j
AerCXu5BiLDcB3YnwnHaZ7MXbpWROSYQCmgojxUoiycAnZNJssFF2c/PVrRI3z0J
vKhO7ViGs+JOqfp5jdgZO0SdKYT+n/TpF3Eqn/ugcXbyBBqS+Vj9liwTKNLqw7Um
GWPOXoczYOp0iIv7qH6HbqpmZgwt4j1Xn7oMkZMv2fjYFCIS4PjifTVBaIm6FBg8
XE0wGlWoMQtfxy56lPNub8Rnq6SyYtKJZfap8ukaPgL6mMJ7RYL1tjiHM9FRXS+7
MrDo1bsQUoCqh6ZmWMyMfGycDflZg1DuAwwwJ06OmEBSqvkZ1sjZi/LGIAwKC2cw
YsIJsKNw/rdE+ph7reH9RiDLJe+I2WehDZCqZ3dA5d8NjK2wGEo6G54YxKARpndl
AlUeB/KJNFwLU74FPL2Jn9yfXTcqJbGs+AIpAu/OtwhPEkIizoeRO0cwqhURa2f/
q5Qa16K2Bw==
-----END CERTIFICATE-----


================================================
FILE: tests/chain.pem
================================================
-----BEGIN CERTIFICATE-----
MIICoDCCAkegAwIBAgIUW5W4GNGYJwryph3KHKkdLaeFdvMwCgYIKoZIzj0EAwIw
gY4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T
YW4gRnJhbmNpc2NvMRgwFgYDVQQKEw9IYXBweUNlcnQsIEluYy4xHzAdBgNVBAsT
FkhhcHB5Q2VydCBJbnRlcm1lZGlhdGUxFzAVBgNVBAMTDihkZXYgdXNlIG9ubHkp
MCAXDTI0MDQwOTA3MjEwMFoYDzIxMjQwMzE2MDcyMTAwWjB2MQswCQYDVQQGEwJV
UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGDAWBgNVBAoT
D0Nsb3VkZmxhcmUgdGVzdDEUMBIGA1UECxMLQ3J5cHRvIHRlYW0xEjAQBgNVBAMT
CWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHG0wqxNChNemkM/
Aw05RBB0vs9adyC1tIm+pobqB0T6T50HN59NeDxMsfeALWBN/i23FKphwdNGIzO3
NkNhMg2jgZcwgZQwDgYDVR0PAQH/BAQDAgGGMAwGA1UdEwEB/wQCMAAwHQYDVR0O
BBYEFJw0MuxNY1BtEB3oNMbPiwxDNrqZMB8GA1UdIwQYMBaAFOu3ANbLR8TJecGR
AAuxL7juFmVyMDQGA1UdEQQtMCuCBnNlcnZlcoIJbG9jYWxob3N0gglib2d1cy5j
b22CCyoubG9jYWxob3N0MAoGCCqGSM49BAMCA0cAMEQCIAJ0Os/bYUfH6nPO8f1E
vVateUJXKaPuS6jD3i0eWQYbAiAyC4TPPr4S0wUXGf6RYwbTPG3sAvGAnuxpxlqB
P0sZrQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID3zCCAcegAwIBAgIUalsEFgf4uPaULSbhh3By7ebBUSQwDQYJKoZIhvcNAQEN
BQAwRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh
bmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjQwNDA5MDcyMTAwWhgPMjEy
NDAzMTYwNzIxMDBaMIGOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEYMBYGA1UEChMPSGFwcHlDZXJ0LCBJ
bmMuMR8wHQYDVQQLExZIYXBweUNlcnQgSW50ZXJtZWRpYXRlMRcwFQYDVQQDEw4o
ZGV2IHVzZSBvbmx5KTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJABtGVq08uw
OV99fDjxN38bWMAe2SeZeBHxF/TEsjtc7jvZtcFv6SECzu9qq6ktUsymCtSDYnP1
bJ2VZLnEQ4SjRTBDMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEA
MB0GA1UdDgQWBBTrtwDWy0fEyXnBkQALsS+47hZlcjANBgkqhkiG9w0BAQ0FAAOC
AgEAl9aRyMaYMJSyWAI4BZmPSxzs8cIVa7lOnCLI3AevDOw4AEXTwK6VFZaehuWB
Vfodav9LrNQ8m/Po3K7AQwQYBghLwaQu7ISlI4pIGUeAZaIo90Bv0H2BJb3foHvi
4+RI+CjVHXucKkgNU998RG6edwDsmdp963kKs3/AiU0vUgyUbuEzhzH4Dgqzt99w
0ekf33fDGRvJ6k45oWZ7gkeT1gcbhCFafQJrMRKgoXcxPxwGxvn+usSd0EUuvMeF
soQJYZ/nMtSahC5qR2TRDunsUAtDtWk7LhdKQF9c+z8IHupxga8x1qxsAcu0abae
NQFUwoyEVUxafMuUdPS8D/br+A2RxaiohAISHLCT7gZVxDkGAT6j8z+nrpvQU/UN
WMLQizGjv7qxXBNHCzo62mZGoZEJNdDP+FzNBdZ3cvYf16t7AWGd7X95I4gj+Muu
J+/VqdqDd17JFTvZ9czc05AsksPwxTMYrXRqfcn9CZeMqinr0kcJ727WtRU6I5wW
52G21D52BCrBZJfTvh+SEoZyTlvV43mt7VIRxB+xxd3zP3OH7a0amTH9f33O6E9u
23r00qyBiluwLGnD2Jca+8AhwsP9uDH8MkTlidPQXGwjrkVhs5+uKC9Zug7G0jEs
qzjuEdhe2UGCaK30J/AMxR3brzIDTTxdJAwn7ZnvqQ6+7YU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFEzCCAvsCFEXBseLI9/DDsUJVNhy2Nn2ORSZFMA0GCSqGSIb3DQEBCwUAMEYx
CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj
bzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDQwOTA3MjYyNFoXDTM0MDQwNzA3
MjYyNFowRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4g
RnJhbmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQCzTB9ETn6RgGHTEXkvXtUxCRtN5oz8eh1hD98OBOYrqC9g
pw90xurkpvrSKA/XGi2er+b+fDaMZnvOrUAmO5tkBeUv5VRArKAW1lTocTTFCXbb
S1pMd4fxCePXnDed81MWFThYLr9zJ8KUeamYMBWq6lziAynTT9+bVaj5zkLC23u9
EUqPFn8Kg3hdfLE5FPzeWqYREVrst++fnpjrt6ZWlHohA6P6BAplubBTKtcOcyDT
1Y8Wg7OxgLex+gq5X+4YxzpuvPzRL0ucl4210FhPY+55zJ4Vw6lqQmY7lSloQQO2
PbtkRvfpphy7J1aPaVpWXvVK2mv4tZtXzplOK3Uf1m4hVCA/fN1A5w89RCzdOacY
SEvvFRhpFo4Iw0L3u2jvO71Rxpe+ATZkKzhk0TnwyRCQGYGYjRsNRAnGHbe10fPa
T2NoBDmojg4S1LF6plwYYxTXvUsIWo9cKizMAYB6p6jCIcJUupulyChnSyTxvLOL
KJr0YuU3ug4WEH1fw2JVgYi4X8TeGRgzLqBA9NudPJZjXh+fCcmdfIqNpuVujWJG
qFjzG6NyixGJ5SqnIByFJd1UhF5UgJPLyseSrAn1Zqk4wKDtjPnahgDIqoLNjBh8
vc2jbwTShwWWxnZK2iA+VzG4Y9l7I1FpANkJp+oug6tiCiBwYzMzP6rJjDBI6wID
AQABMA0GCSqGSIb3DQEBCwUAA4ICAQCmo1FG5Dudyy7Z0MgHY/dHe9EHWMl8FPIK
zRxFrsAUivNMaXG+rUmsPgd0tNUdqEYQOpDYyu61ayo9dZUfjfoiePp/h6jiZrUa
OxWtC53Em/UDoVz/hElRFwOYCz5O3ZQRC+c/CjSb+hsB93gi3bJIq3mIGCe9+jf1
YD2GkaD99V5gZq5U5cTGsD9rxdAOT4AMEsxsUAUVULzhA+nQw4uqxFDe2AC8ZY9j
AerCXu5BiLDcB3YnwnHaZ7MXbpWROSYQCmgojxUoiycAnZNJssFF2c/PVrRI3z0J
vKhO7ViGs+JOqfp5jdgZO0SdKYT+n/TpF3Eqn/ugcXbyBBqS+Vj9liwTKNLqw7Um
GWPOXoczYOp0iIv7qH6HbqpmZgwt4j1Xn7oMkZMv2fjYFCIS4PjifTVBaIm6FBg8
XE0wGlWoMQtfxy56lPNub8Rnq6SyYtKJZfap8ukaPgL6mMJ7RYL1tjiHM9FRXS+7
MrDo1bsQUoCqh6ZmWMyMfGycDflZg1DuAwwwJ06OmEBSqvkZ1sjZi/LGIAwKC2cw
YsIJsKNw/rdE+ph7reH9RiDLJe+I2WehDZCqZ3dA5d8NjK2wGEo6G54YxKARpndl
AlUeB/KJNFwLU74FPL2Jn9yfXTcqJbGs+AIpAu/OtwhPEkIizoeRO0cwqhURa2f/
q5Qa16K2Bw==
-----END CERTIFICATE-----


================================================
FILE: tests/cookie.key
================================================
Td>!鼽v

================================================
FILE: tests/generate.sh
================================================
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"
openssl x509 -in ca.csr -out ca.pem -req -signkey ca-key.pem -days 3650
cfssl gencert -config=int-config.json -ca=ca.pem -ca-key=ca-key.pem intermediate.json | cfssljson -bare intermediate
cfssl gencert -config=test-config.json -ca intermediate.pem -ca-key intermediate-key.pem test.json | cfssljson -bare tls
openssl pkcs8 -topk8 -nocrypt -in tls-key.pem -out tls-pkcs8.pem
cat tls.pem intermediate.pem ca.pem > chain.pem


================================================
FILE: tests/int-config.json
================================================
{
  "signing": {
    "default": {
      "ca_constraint": {
        "is_ca": true,
        "max_path_len": 0,
        "max_path_len_zero": true
      },
      "expiry": "876000h",
      "usages": [
        "digital signature",
        "cert sign",
        "crl sign",
        "signing"
      ]
    }
  }
}


================================================
FILE: tests/intermediate-key.pem
================================================
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIBTkESHvag8yy5dO8xza5Zo52TRDDQgmqWBMpWsBRjmPoAoGCCqGSM49
AwEHoUQDQgAEkAG0ZWrTy7A5X318OPE3fxtYwB7ZJ5l4EfEX9MSyO1zuO9m1wW/p
IQLO72qrqS1SzKYK1INic/VsnZVkucRDhA==
-----END EC PRIVATE KEY-----


================================================
FILE: tests/intermediate.csr
================================================
-----BEGIN CERTIFICATE REQUEST-----
MIIBSTCB8QIBADCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx
FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0hhcHB5Q2VydCwgSW5j
LjEfMB0GA1UECxMWSGFwcHlDZXJ0IEludGVybWVkaWF0ZTEXMBUGA1UEAxMOKGRl
diB1c2Ugb25seSkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASQAbRlatPLsDlf
fXw48Td/G1jAHtknmXgR8Rf0xLI7XO472bXBb+khAs7vaqupLVLMpgrUg2Jz9Wyd
lWS5xEOEoAAwCgYIKoZIzj0EAwIDRwAwRAIgWi05qNqepbhZRiPAK5zhqpbGWOXQ
2V+lganS10JrHRkCIBlcIxyKDSAdsVDbAHe8Pk/V7bqeSzEMH9LkOQi8Xq2O
-----END CERTIFICATE REQUEST-----


================================================
FILE: tests/intermediate.json
================================================
{
  "key": {
    "algo": "ecdsa",
    "size": 256
  },
  "names": [
    {
      "C": "US",
      "L": "San Francisco",
      "ST": "California",
      "O": "HappyCert, Inc.",
      "OU": "HappyCert Intermediate"
    }
  ],
  "cn": "(dev use only)"
}


================================================
FILE: tests/intermediate.pem
================================================
-----BEGIN CERTIFICATE-----
MIID3zCCAcegAwIBAgIUalsEFgf4uPaULSbhh3By7ebBUSQwDQYJKoZIhvcNAQEN
BQAwRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh
bmNpc2NvMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjQwNDA5MDcyMTAwWhgPMjEy
NDAzMTYwNzIxMDBaMIGOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEYMBYGA1UEChMPSGFwcHlDZXJ0LCBJ
bmMuMR8wHQYDVQQLExZIYXBweUNlcnQgSW50ZXJtZWRpYXRlMRcwFQYDVQQDEw4o
ZGV2IHVzZSBvbmx5KTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJABtGVq08uw
OV99fDjxN38bWMAe2SeZeBHxF/TEsjtc7jvZtcFv6SECzu9qq6ktUsymCtSDYnP1
bJ2VZLnEQ4SjRTBDMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEA
MB0GA1UdDgQWBBTrtwDWy0fEyXnBkQALsS+47hZlcjANBgkqhkiG9w0BAQ0FAAOC
AgEAl9aRyMaYMJSyWAI4BZmPSxzs8cIVa7lOnCLI3AevDOw4AEXTwK6VFZaehuWB
Vfodav9LrNQ8m/Po3K7AQwQYBghLwaQu7ISlI4pIGUeAZaIo90Bv0H2BJb3foHvi
4+RI+CjVHXucKkgNU998RG6edwDsmdp963kKs3/AiU0vUgyUbuEzhzH4Dgqzt99w
0ekf33fDGRvJ6k45oWZ7gkeT1gcbhCFafQJrMRKgoXcxPxwGxvn+usSd0EUuvMeF
soQJYZ/nMtSahC5qR2TRDunsUAtDtWk7LhdKQF9c+z8IHupxga8x1qxsAcu0abae
NQFUwoyEVUxafMuUdPS8D/br+A2RxaiohAISHLCT7gZVxDkGAT6j8z+nrpvQU/UN
WMLQizGjv7qxXBNHCzo62mZGoZEJNdDP+FzNBdZ3cvYf16t7AWGd7X95I4gj+Muu
J+/VqdqDd17JFTvZ9czc05AsksPwxTMYrXRqfcn9CZeMqinr0kcJ727WtRU6I5wW
52G21D52BCrBZJfTvh+SEoZyTlvV43mt7VIRxB+xxd3zP3OH7a0amTH9f33O6E9u
23r00qyBiluwLGnD2Jca+8AhwsP9uDH8MkTlidPQXGwjrkVhs5+uKC9Zug7G0jEs
qzjuEdhe2UGCaK30J/AMxR3brzIDTTxdJAwn7ZnvqQ6+7YU=
-----END CERTIFICATE-----


================================================
FILE: tests/ntp-config.yaml
================================================
addr:
  - "0.0.0.0:123"
  - "0.0.0.0:789"
  - "[::]:123"
cookie_key_file: tests/cookie.key # TODO: store and read as pem files, or read bytes directly from file?
memc_url: memcache://memcache:11211
metrics_addr: server
metrics_port: 8000
upstream_host: localhost
upstream_port: 456


================================================
FILE: tests/ntp-upstream-config.yaml
================================================
addr:
  - 127.0.0.1:456
cookie_key_file: tests/cookie.key # TODO: store and read as pem files, or read bytes directly from file?
memc_url: memcache://memcache:11211
metrics_addr: server
metrics_port: 8002


================================================
FILE: tests/nts-ke-config.yaml
================================================
addr:
  - "[::]:4460"
tls_key_file: tests/tls-pkcs8.pem
tls_cert_file: tests/chain.pem # Expect PEM.
cookie_key_file: tests/cookie.key # TODO: store and read as pem files, or read bytes directly from file?
memc_url: memcache://memcache:11211
next_port: 123
metrics_addr: server
metrics_port: 8001


================================================
FILE: tests/test-config.json
================================================
{
  "signing": {
    "default": {
      "expiry": "876000h",
      "usages": [
        "digital signature",
        "cert sign",
        "crl sign",
        "signing"
      ]
    }
  }
}


================================================
FILE: tests/test.json
================================================
{
    "CN": "localhost",
    "hosts": [
        "server",
        "localhost",
        "bogus.com",
        "*.localhost"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    },
    "names": [
        {
            "C": "US",
            "ST": "CA",
            "L": "San Francisco",
            "O": "Cloudflare test",
            "OU": "Crypto team"
        }
    ]
}



================================================
FILE: tests/tls-key.pem
================================================
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIBvLtCg67XQkDzWZDS4peNXy8r4Dguv+KYUVoOZEcCjGoAoGCCqGSM49
AwEHoUQDQgAEcbTCrE0KE16aQz8DDTlEEHS+z1p3ILW0ib6mhuoHRPpPnQc3n014
PEyx94AtYE3+LbcUqmHB00YjM7c2Q2EyDQ==
-----END EC PRIVATE KEY-----


================================================
FILE: tests/tls-pkcs8.pem
================================================
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgG8u0KDrtdCQPNZkN
Lil41fLyvgOC6/4phRWg5kRwKMahRANCAARxtMKsTQoTXppDPwMNOUQQdL7PWncg
tbSJvqaG6gdE+k+dBzefTXg8TLH3gC1gTf4ttxSqYcHTRiMztzZDYTIN
-----END PRIVATE KEY-----


================================================
FILE: tests/tls.csr
================================================
-----BEGIN CERTIFICATE REQUEST-----
MIIBeDCCAR8CAQAwdjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQH
Ew1TYW4gRnJhbmNpc2NvMRgwFgYDVQQKEw9DbG91ZGZsYXJlIHRlc3QxFDASBgNV
BAsTC0NyeXB0byB0ZWFtMRIwEAYDVQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARxtMKsTQoTXppDPwMNOUQQdL7PWncgtbSJvqaG6gdE+k+d
BzefTXg8TLH3gC1gTf4ttxSqYcHTRiMztzZDYTINoEcwRQYJKoZIhvcNAQkOMTgw
NjA0BgNVHREELTArggZzZXJ2ZXKCCWxvY2FsaG9zdIIJYm9ndXMuY29tggsqLmxv
Y2FsaG9zdDAKBggqhkjOPQQDAgNHADBEAiBGUmLvyO5eqzQIsEB2v4ysI8vDLrDV
lRSABgL6YpJPOwIgSeSy73gwaBWRk/EVlahptbUSGcNPYa3m2rlAtTKX2Vo=
-----END CERTIFICATE REQUEST-----


================================================
FILE: tests/tls.pem
================================================
-----BEGIN CERTIFICATE-----
MIICoDCCAkegAwIBAgIUW5W4GNGYJwryph3KHKkdLaeFdvMwCgYIKoZIzj0EAwIw
gY4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T
YW4gRnJhbmNpc2NvMRgwFgYDVQQKEw9IYXBweUNlcnQsIEluYy4xHzAdBgNVBAsT
FkhhcHB5Q2VydCBJbnRlcm1lZGlhdGUxFzAVBgNVBAMTDihkZXYgdXNlIG9ubHkp
MCAXDTI0MDQwOTA3MjEwMFoYDzIxMjQwMzE2MDcyMTAwWjB2MQswCQYDVQQGEwJV
UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGDAWBgNVBAoT
D0Nsb3VkZmxhcmUgdGVzdDEUMBIGA1UECxMLQ3J5cHRvIHRlYW0xEjAQBgNVBAMT
CWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHG0wqxNChNemkM/
Aw05RBB0vs9adyC1tIm+pobqB0T6T50HN59NeDxMsfeALWBN/i23FKphwdNGIzO3
NkNhMg2jgZcwgZQwDgYDVR0PAQH/BAQDAgGGMAwGA1UdEwEB/wQCMAAwHQYDVR0O
BBYEFJw0MuxNY1BtEB3oNMbPiwxDNrqZMB8GA1UdIwQYMBaAFOu3ANbLR8TJecGR
AAuxL7juFmVyMDQGA1UdEQQtMCuCBnNlcnZlcoIJbG9jYWxob3N0gglib2d1cy5j
b22CCyoubG9jYWxob3N0MAoGCCqGSM49BAMCA0cAMEQCIAJ0Os/bYUfH6nPO8f1E
vVateUJXKaPuS6jD3i0eWQYbAiAyC4TPPr4S0wUXGf6RYwbTPG3sAvGAnuxpxlqB
P0sZrQ==
-----END CERTIFICATE-----
Download .txt
gitextract_mvp4c08z/

├── .cargo/
│   └── config
├── .github/
│   └── workflows/
│       └── cfntsci.yml
├── .gitignore
├── CONTRIBUTING.md
├── Cargo.toml
├── Dockerfile.cfnts
├── Dockerfile.memcache
├── LICENSE
├── Makefile
├── README.md
├── RELEASE_NOTES
├── docker-compose.yaml
├── scripts/
│   ├── fill-memcached.py
│   ├── run_client.sh
│   ├── run_memcached.sh
│   └── run_server.sh
├── src/
│   ├── cfsock.rs
│   ├── cmd.rs
│   ├── cookie.rs
│   ├── error.rs
│   ├── key_rotator.rs
│   ├── main.rs
│   ├── metrics.rs
│   ├── ntp/
│   │   ├── client.rs
│   │   ├── mod.rs
│   │   ├── protocol.rs
│   │   └── server/
│   │       ├── config.rs
│   │       ├── mod.rs
│   │       └── ntp_server.rs
│   ├── nts_ke/
│   │   ├── client.rs
│   │   ├── mod.rs
│   │   ├── records/
│   │   │   ├── aead_algorithm.rs
│   │   │   ├── end_of_message.rs
│   │   │   ├── error.rs
│   │   │   ├── mod.rs
│   │   │   ├── new_cookie.rs
│   │   │   ├── next_protocol.rs
│   │   │   ├── port.rs
│   │   │   ├── server.rs
│   │   │   └── warning.rs
│   │   └── server/
│   │       ├── config.rs
│   │       ├── connection.rs
│   │       ├── ke_server.rs
│   │       ├── listener.rs
│   │       └── mod.rs
│   └── sub_command/
│       ├── client.rs
│       ├── ke_server.rs
│       ├── mod.rs
│       └── ntp_server.rs
└── tests/
    ├── ca-key.pem
    ├── ca.csr
    ├── ca.pem
    ├── chain.pem
    ├── cookie.key
    ├── generate.sh
    ├── int-config.json
    ├── intermediate-key.pem
    ├── intermediate.csr
    ├── intermediate.json
    ├── intermediate.pem
    ├── ntp-config.yaml
    ├── ntp-upstream-config.yaml
    ├── nts-ke-config.yaml
    ├── test-config.json
    ├── test.json
    ├── tls-key.pem
    ├── tls-pkcs8.pem
    ├── tls.csr
    └── tls.pem
Download .txt
SYMBOL INDEX (276 symbols across 28 files)

FILE: src/cfsock.rs
  function set_freebind (line 7) | fn set_freebind(fd: c_int) -> Result<(), std::io::Error> {
  function set_freebind (line 28) | fn set_freebind(_fd: c_int) -> Result<(), std::io::Error> {
  function tcp_listener (line 32) | pub fn tcp_listener(addr: &SocketAddr) -> Result<std::net::TcpListener, ...
  function udp_listen (line 45) | pub fn udp_listen(addr: &SocketAddr) -> Result<std::net::UdpSocket, std:...

FILE: src/cmd.rs
  function create_clap_client_subcommand (line 10) | fn create_clap_client_subcommand<'a, 'b>() -> App<'a, 'b> {
  function create_clap_ke_server_subcommand (line 51) | fn create_clap_ke_server_subcommand<'a, 'b>() -> App<'a, 'b> {
  function create_clap_ntp_server_subcommand (line 71) | fn create_clap_ntp_server_subcommand<'a, 'b>() -> App<'a, 'b> {
  function create_clap_command (line 91) | pub fn create_clap_command() -> App<'static, 'static> {

FILE: src/cookie.rs
  constant COOKIE_SIZE (line 16) | pub const COOKIE_SIZE: usize = 100;
  type NTSKeys (line 18) | pub struct NTSKeys {
  type CookieKey (line 25) | pub struct CookieKey(Vec<u8>);
    method parse (line 34) | pub fn parse(filename: &str) -> Result<CookieKey, io::Error> {
    method as_bytes (line 43) | pub fn as_bytes(&self) -> &[u8] {
    method from (line 51) | fn from(bytes: &[u8]) -> CookieKey {
  function make_cookie (line 56) | pub fn make_cookie(keys: NTSKeys, master_key: &[u8], key_id: KeyId) -> V...
  function get_keyid (line 71) | pub fn get_keyid(cookie: &[u8]) -> Option<KeyId> {
  function unpack (line 79) | fn unpack(pt: Vec<u8>) -> Option<NTSKeys> {
  function eat_cookie (line 93) | pub fn eat_cookie(cookie: &[u8], key: &[u8]) -> Option<NTSKeys> {
  function check_eq (line 110) | fn check_eq(a: NTSKeys, b: NTSKeys) {
  function check_cookie (line 118) | fn check_cookie() {

FILE: src/error.rs
  type WrapError (line 10) | pub trait WrapError<T: Error> {
    method wrap_err (line 15) | fn wrap_err(self) -> Result<Self::Item, T>;
  type Item (line 26) | type Item = S;
  function wrap_err (line 28) | fn wrap_err(self) -> Result<S, config::ConfigError> {
  type Item (line 41) | type Item = S;
  function wrap_err (line 43) | fn wrap_err(self) -> Result<S, std::io::Error> {

FILE: src/key_rotator.rs
  type KeyId (line 38) | pub struct KeyId(u32);
    method new (line 42) | pub fn new(key_id: u32) -> KeyId {
    method from_epoch (line 48) | pub fn from_epoch(epoch: u64) -> KeyId {
    method from_be_bytes (line 55) | pub fn from_be_bytes(bytes: [u8; 4]) -> KeyId {
    method to_be_bytes (line 60) | pub fn to_be_bytes(self) -> [u8; 4] {
  type RotateError (line 67) | pub enum RotateError {
    method from (line 76) | fn from(error: MemcacheError) -> RotateError {
  type KeyRotator (line 82) | pub struct KeyRotator {
    method connect (line 121) | pub fn connect(
    method rotate (line 186) | pub fn rotate(&mut self) -> Result<(), RotateError> {
    method cache_insert (line 241) | fn cache_insert(&mut self, key_id: KeyId, value: &[u8]) {
    method cache_remove (line 252) | fn cache_remove(&mut self, key_id: KeyId) {
    method latest_key_value (line 257) | pub fn latest_key_value(&self) -> (KeyId, &hmac::Tag) {
    method get (line 263) | pub fn get(&self, key_id: KeyId) -> Option<&hmac::Tag> {
  function periodic_rotate (line 268) | pub fn periodic_rotate(rotor: Arc<RwLock<KeyRotator>>) {
  function inner (line 277) | fn inner(rotor: &mut Arc<RwLock<KeyRotator>>) {
  function read_sleep (line 281) | fn read_sleep(rotor: &Arc<RwLock<KeyRotator>>) -> u64 {
  type Client (line 315) | pub struct Client;
    method connect (line 317) | pub fn connect(_url: &str) -> Result<Client, MemcacheError> {
    method get (line 320) | pub fn get(&mut self, key: &str) -> Result<Option<Vec<u8>>, MemcacheEr...
  type SystemTime (line 330) | pub struct SystemTime;
    method now (line 332) | pub fn now() -> std::time::SystemTime {
  function test_rotation (line 340) | fn test_rotation() {

FILE: src/main.rs
  function create_logger (line 30) | fn create_logger(matches: &clap::ArgMatches<'_>) -> slog::Logger {
  function main (line 52) | fn main() {

FILE: src/metrics.rs
  type MetricsConfig (line 12) | pub struct MetricsConfig {
  constant VERSION (line 17) | const VERSION: &str = env!("CARGO_PKG_VERSION");
  function wait_for_req_or_eof (line 30) | fn wait_for_req_or_eof(dest: &net::TcpStream, logger: slog::Logger) -> R...
  function scrape_result (line 53) | fn scrape_result() -> String {
  function serve_metrics (line 62) | fn serve_metrics(mut dest: net::TcpStream, logger: slog::Logger) {
  function run_metrics (line 85) | pub fn run_metrics(conf: MetricsConfig, logger: &slog::Logger) -> Result...

FILE: src/ntp/client.rs
  constant BUFF_SIZE (line 26) | const BUFF_SIZE: usize = 2048;
  constant TIMEOUT (line 27) | const TIMEOUT: Duration = Duration::from_secs(10);
  type NtpResult (line 29) | pub struct NtpResult {
  type NtpClientError (line 35) | pub enum NtpClientError {
    method description (line 42) | fn description(&self) -> &str {
    method cause (line 55) | fn cause(&self) -> Option<&dyn std::error::Error> {
    method fmt (line 61) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  function system_to_ntpfloat (line 67) | fn system_to_ntpfloat(time: SystemTime) -> f64 {
  function timestamp_to_float (line 75) | fn timestamp_to_float(time: u64) -> f64 {
  function run_nts_ntp_client (line 82) | pub fn run_nts_ntp_client(

FILE: src/ntp/protocol.rs
  constant VERSION (line 13) | pub const VERSION: u8 = 4;
  constant UNIX_OFFSET (line 14) | pub const UNIX_OFFSET: u64 = 2_208_988_800;
  constant PHI (line 15) | pub const PHI: f64 = 15e-6;
  constant TWO_POW_32 (line 17) | pub const TWO_POW_32: f64 = 4294967296.0;
  constant HEADER_SIZE (line 19) | const HEADER_SIZE: u64 = 48;
  constant NONCE_LEN (line 20) | const NONCE_LEN: usize = 16;
  constant EXT_TYPE_UNIQUE_IDENTIFIER (line 21) | const EXT_TYPE_UNIQUE_IDENTIFIER: u16 = 0x0104;
  constant EXT_TYPE_NTS_COOKIE (line 22) | const EXT_TYPE_NTS_COOKIE: u16 = 0x0204;
  constant EXT_TYPE_NTS_COOKIE_PLACEHOLDER (line 23) | const EXT_TYPE_NTS_COOKIE_PLACEHOLDER: u16 = 0x0304;
  constant EXT_TYPE_NTS_AUTHENTICATOR (line 24) | const EXT_TYPE_NTS_AUTHENTICATOR: u16 = 0x0404;
  type LeapState (line 27) | pub enum LeapState {
  type PacketMode (line 35) | pub enum PacketMode {
  type NtpExtensionType (line 45) | pub enum NtpExtensionType {
  function wire_type (line 53) | fn wire_type(x: NtpExtensionType) -> u16 {
  function type_from_wire (line 63) | fn type_from_wire(ext: u16) -> NtpExtensionType {
  type NtpPacketHeader (line 76) | pub struct NtpPacketHeader {
  type NtpExtension (line 101) | pub struct NtpExtension {
  type NtsPacket (line 109) | pub struct NtsPacket {
  type NtpPacket (line 118) | pub struct NtpPacket {
  function parse_leap_indicator (line 126) | fn parse_leap_indicator(first: u8) -> LeapState {
  function parse_version (line 135) | fn parse_version(first: u8) -> u8 {
  function parse_mode (line 139) | fn parse_mode(first: u8) -> PacketMode {
  function create_first (line 152) | fn create_first(leap: LeapState, version: u8, mode: PacketMode) -> u8 {
  function parse_packet_header (line 157) | pub fn parse_packet_header(packet: &[u8]) -> Result<NtpPacketHeader, std...
  function serialize_header (line 193) | pub fn serialize_header(head: NtpPacketHeader) -> Vec<u8> {
  function parse_ntp_packet (line 222) | pub fn parse_ntp_packet(buff: &[u8]) -> Result<NtpPacket, std::io::Error> {
  function parse_extensions (line 230) | fn parse_extensions(buff: &[u8]) -> Result<Vec<NtpExtension>, std::io::E...
  function serialize_ntp_packet (line 256) | pub fn serialize_ntp_packet(pack: NtpPacket) -> Vec<u8> {
  function serialize_extensions (line 265) | fn serialize_extensions(exts: Vec<NtpExtension>) -> Vec<u8> {
  function has_extension (line 282) | pub fn has_extension(pack: &NtpPacket, kind: NtpExtensionType) -> bool {
  function is_nts_packet (line 291) | pub fn is_nts_packet(pack: &NtpPacket) -> bool {
  function extract_extension (line 298) | pub fn extract_extension(pack: &NtpPacket, kind: NtpExtensionType) -> Op...
  function parse_nts_packet (line 306) | pub fn parse_nts_packet<T: Aead>(
  function parse_decrypt_auth_ext (line 347) | fn parse_decrypt_auth_ext<T: Aead>(
  function serialize_nts_packet (line 376) | pub fn serialize_nts_packet<T: Aead>(packet: NtsPacket, encryptor: &mut ...
  function test_ntp_header_parse (line 422) | fn test_ntp_header_parse() {
  function check_eq_ext (line 451) | fn check_eq_ext(a: &NtpExtension, b: &NtpExtension) {
  function check_ext_array_eq (line 458) | fn check_ext_array_eq(exts1: Vec<NtpExtension>, exts2: Vec<NtpExtension>) {
  function check_nts_match (line 464) | fn check_nts_match(pkt1: NtsPacket, pkt2: NtsPacket) {
  function roundtrip_test (line 469) | fn roundtrip_test<T: Aead>(input: NtsPacket, enc: &mut T) {
  function test_nts_parse (line 482) | fn test_nts_parse() {

FILE: src/ntp/server/config.rs
  function get_metrics_config (line 18) | fn get_metrics_config(settings: &config::Config) -> Option<MetricsConfig> {
  type NtpServerConfig (line 33) | pub struct NtpServerConfig {
    method new (line 54) | pub fn new(
    method add_address (line 81) | pub fn add_address(&mut self, addr: SocketAddr) {
    method addrs (line 86) | pub fn addrs(&self) -> &[SocketAddr] {
    method set_logger (line 91) | pub fn set_logger(&mut self, logger: slog::Logger) {
    method logger (line 96) | pub fn logger(&self) -> &slog::Logger {
    method parse (line 120) | pub fn parse(filename: &str) -> Result<NtpServerConfig, config::Config...

FILE: src/ntp/server/ntp_server.rs
  constant BUF_SIZE (line 39) | const BUF_SIZE: usize = 1280;
  constant TWO_POW_32 (line 40) | const TWO_POW_32: f64 = 4294967296.0;
  constant TWO_POW_16 (line 41) | const TWO_POW_16: f64 = 65536.0;
  type ServerState (line 83) | struct ServerState {
  type TheCmsgSpace (line 96) | type TheCmsgSpace = CmsgSpace<(TimeVal, CmsgSpace<(in_pktinfo, CmsgSpace...
  function run_server (line 100) | fn run_server(
  function start_ntp_server (line 202) | pub fn start_ntp_server(config: NtpServerConfig) -> Result<(), Box<dyn s...
  function fix_dispersion (line 283) | fn fix_dispersion(disp: u32, now: SystemTime, taken: SystemTime) -> u32 {
  function ntp_timestamp (line 299) | fn ntp_timestamp(time: SystemTime) -> u64 {
  function create_header (line 310) | fn create_header(
  function response (line 336) | fn response(
  function process_nts (line 395) | fn process_nts(
  function nts_response (line 413) | fn nts_response(
  function send_kiss_of_death (line 454) | fn send_kiss_of_death(query_packet: NtpPacket) -> Result<Vec<u8>, std::i...
  function kiss_of_death (line 461) | fn kiss_of_death(query_packet: NtpPacket) -> NtpPacket {
  function refresh_servstate (line 491) | fn refresh_servstate(

FILE: src/nts_ke/client.rs
  type Cookie (line 43) | type Cookie = Vec<u8>;
  constant DEFAULT_NTP_PORT (line 45) | const DEFAULT_NTP_PORT: u16 = 123;
  constant DEFAULT_KE_PORT (line 46) | const DEFAULT_KE_PORT: u16 = 4460;
  constant DEFAULT_SCHEME (line 47) | const DEFAULT_SCHEME: u16 = 0;
  constant TIMEOUT (line 48) | const TIMEOUT: Duration = Duration::from_secs(15);
  type NtsKeResult (line 51) | pub struct NtsKeResult {
  function run_nts_ke_client (line 62) | pub fn run_nts_ke_client(

FILE: src/nts_ke/records/aead_algorithm.rs
  type KnownAeadAlgorithm (line 13) | pub enum KnownAeadAlgorithm {
    method as_algorithm_id (line 18) | pub fn as_algorithm_id(&self) -> u16 {
  type AeadAlgorithmRecord (line 25) | pub struct AeadAlgorithmRecord(Vec<KnownAeadAlgorithm>);
    method algorithms (line 28) | pub fn algorithms(&self) -> &[KnownAeadAlgorithm] {
    method from (line 34) | fn from(algorithms: Vec<KnownAeadAlgorithm>) -> AeadAlgorithmRecord {
  method critical (line 40) | fn critical(&self) -> bool {
  method record_type (line 46) | fn record_type() -> u16 {
  method len (line 50) | fn len(&self) -> u16 {
  method into_bytes (line 58) | fn into_bytes(self) -> Vec<u8> {
  method from_bytes (line 71) | fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {

FILE: src/nts_ke/records/end_of_message.rs
  type EndOfMessageRecord (line 10) | pub struct EndOfMessageRecord;
  method critical (line 13) | fn critical(&self) -> bool {
  method record_type (line 17) | fn record_type() -> u16 {
  method len (line 21) | fn len(&self) -> u16 {
  method into_bytes (line 25) | fn into_bytes(self) -> Vec<u8> {
  method from_bytes (line 29) | fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {

FILE: src/nts_ke/records/error.rs
  type ErrorKind (line 10) | enum ErrorKind {
    method as_code (line 16) | fn as_code(&self) -> u16 {
  type ErrorRecord (line 24) | pub struct ErrorRecord(ErrorKind);
  method critical (line 27) | fn critical(&self) -> bool {
  method record_type (line 31) | fn record_type() -> u16 {
  method len (line 35) | fn len(&self) -> u16 {
  method into_bytes (line 39) | fn into_bytes(self) -> Vec<u8> {
  method from_bytes (line 44) | fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {

FILE: src/nts_ke/records/mod.rs
  constant HEADER_SIZE (line 32) | pub const HEADER_SIZE: usize = 4;
  type KeRecord (line 34) | pub enum KeRecord {
  type Party (line 46) | pub enum Party {
  type KeRecordTrait (line 51) | pub trait KeRecordTrait: Sized {
    method critical (line 52) | fn critical(&self) -> bool;
    method record_type (line 54) | fn record_type() -> u16;
    method len (line 56) | fn len(&self) -> u16;
    method into_bytes (line 59) | fn into_bytes(self) -> Vec<u8>;
    method from_bytes (line 61) | fn from_bytes(sender: Party, bytes: &[u8]) -> Result<Self, String>;
  function serialize (line 69) | pub fn serialize<T: KeRecordTrait>(record: T) -> Vec<u8> {
  type DeserializeError (line 90) | pub enum DeserializeError {
  function deserialize (line 102) | pub fn deserialize(sender: Party, bytes: &[u8]) -> Result<KeRecord, Dese...
  function gen_key (line 151) | pub fn gen_key<T: rustls::Session>(session: &T) -> Result<NTSKeys, TLSEr...
  type Cookie (line 171) | type Cookie = Vec<u8>;
  type ReceivedNtsKeRecordState (line 174) | pub struct ReceivedNtsKeRecordState {
  type NtsKeParseError (line 184) | pub enum NtsKeParseError {
    method description (line 192) | fn description(&self) -> &str {
    method cause (line 204) | fn cause(&self) -> Option<&dyn std::error::Error> {
    method fmt (line 210) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  function process_record (line 216) | pub fn process_record(

FILE: src/nts_ke/records/new_cookie.rs
  type NewCookieRecord (line 12) | pub struct NewCookieRecord(Vec<u8>);
    method from (line 15) | fn from(bytes: Vec<u8>) -> NewCookieRecord {
  method critical (line 21) | fn critical(&self) -> bool {
  method record_type (line 25) | fn record_type() -> u16 {
  method len (line 29) | fn len(&self) -> u16 {
  method into_bytes (line 33) | fn into_bytes(self) -> Vec<u8> {
  method from_bytes (line 37) | fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {

FILE: src/nts_ke/records/next_protocol.rs
  type KnownNextProtocol (line 13) | pub enum KnownNextProtocol {
    method as_protocol_id (line 18) | pub fn as_protocol_id(&self) -> u16 {
  type NextProtocolRecord (line 25) | pub struct NextProtocolRecord(Vec<KnownNextProtocol>);
    method protocols (line 28) | pub fn protocols(&self) -> &[KnownNextProtocol] {
    method from (line 34) | fn from(protocols: Vec<KnownNextProtocol>) -> NextProtocolRecord {
  method critical (line 40) | fn critical(&self) -> bool {
  method record_type (line 44) | fn record_type() -> u16 {
  method len (line 48) | fn len(&self) -> u16 {
  method into_bytes (line 56) | fn into_bytes(self) -> Vec<u8> {
  method from_bytes (line 69) | fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {

FILE: src/nts_ke/records/port.rs
  type PortRecord (line 11) | pub struct PortRecord {
    method new (line 17) | pub fn new(sender: Party, port: u16) -> PortRecord {
    method port (line 21) | pub fn port(&self) -> u16 {
  method critical (line 27) | fn critical(&self) -> bool {
  method record_type (line 34) | fn record_type() -> u16 {
  method len (line 38) | fn len(&self) -> u16 {
  method into_bytes (line 42) | fn into_bytes(self) -> Vec<u8> {
  method from_bytes (line 46) | fn from_bytes(sender: Party, bytes: &[u8]) -> Result<Self, String> {

FILE: src/nts_ke/records/server.rs
  type Address (line 16) | enum Address {
  type ServerRecord (line 22) | pub struct ServerRecord {
    method into_string (line 28) | pub fn into_string(self) -> String {
  method critical (line 38) | fn critical(&self) -> bool {
  method record_type (line 45) | fn record_type() -> u16 {
  method len (line 49) | fn len(&self) -> u16 {
  method into_bytes (line 62) | fn into_bytes(self) -> Vec<u8> {
  method from_bytes (line 66) | fn from_bytes(sender: Party, bytes: &[u8]) -> Result<Self, String> {

FILE: src/nts_ke/records/warning.rs
  type WarningKind (line 10) | enum WarningKind {
    method as_code (line 18) | fn as_code(&self) -> u16 {
  type WarningRecord (line 26) | pub struct WarningRecord(WarningKind);
  method critical (line 29) | fn critical(&self) -> bool {
  method record_type (line 33) | fn record_type() -> u16 {
  method len (line 37) | fn len(&self) -> u16 {
  method into_bytes (line 41) | fn into_bytes(self) -> Vec<u8> {
  method from_bytes (line 46) | fn from_bytes(_: Party, bytes: &[u8]) -> Result<Self, String> {

FILE: src/nts_ke/server/config.rs
  function get_metrics_config (line 21) | fn get_metrics_config(settings: &config::Config) -> Option<MetricsConfig> {
  type KeServerConfig (line 36) | pub struct KeServerConfig {
    method new (line 66) | pub fn new(
    method add_tls_cert (line 100) | fn add_tls_cert(&mut self, cert: Certificate) {
    method add_tls_secret_key (line 107) | fn add_tls_secret_key(&mut self, secret_key: PrivateKey) {
    method add_address (line 112) | pub fn add_address(&mut self, addr: SocketAddr) {
    method addrs (line 117) | pub fn addrs(&self) -> &[SocketAddr] {
    method cookie_key (line 122) | pub fn cookie_key(&self) -> &CookieKey {
    method set_logger (line 127) | pub fn set_logger(&mut self, logger: slog::Logger) {
    method logger (line 132) | pub fn logger(&self) -> &slog::Logger {
    method memcached_url (line 137) | pub fn memcached_url(&self) -> &str {
    method timeout (line 142) | pub fn timeout(&self) -> u64 {
    method import_tls_certs (line 155) | fn import_tls_certs(&mut self, filename: &str) -> Result<(), std::io::...
    method import_tls_secret_keys (line 189) | fn import_tls_secret_keys(&mut self, filename: &str) -> Result<(), std...
    method parse (line 235) | pub fn parse(filename: &str) -> Result<KeServerConfig, config::ConfigE...

FILE: src/nts_ke/server/connection.rs
  function response (line 52) | fn response(keys: NTSKeys, rotator: &Arc<RwLock<KeyRotator>>, port: u16)...
  type KeServerConnState (line 79) | pub enum KeServerConnState {
  type KeServerConn (line 93) | pub struct KeServerConn {
    method new (line 120) | pub fn new(
    method ready (line 157) | pub fn ready(&mut self, poll: &mut mio::Poll, event: &mio::Event) {
    method read_ready (line 172) | fn read_ready(&mut self) {
    method write_ready (line 308) | fn write_ready(&mut self) {
    method register (line 316) | pub fn register(&self, poll: &mut mio::Poll) -> Result<(), std::io::Er...
    method reregister (line 326) | pub fn reregister(&self, poll: &mut mio::Poll) -> Result<(), std::io::...
    method interest (line 335) | fn interest(&self) -> mio::Ready {
    method state (line 347) | pub fn state(&self) -> KeServerConnState {
    method shutdown (line 351) | pub fn shutdown(&mut self) {

FILE: src/nts_ke/server/ke_server.rs
  type KeServerState (line 20) | pub(super) struct KeServerState {
  type KeServer (line 39) | pub struct KeServer {
    method connect (line 56) | pub fn connect(config: KeServerConfig) -> Result<KeServer, RotateError> {
    method start (line 104) | pub fn start(&mut self) -> Result<(), std::io::Error> {
    method state (line 185) | pub(super) fn state(&self) -> &Arc<KeServerState> {

FILE: src/nts_ke/server/listener.rs
  constant LISTENER_MIO_TOKEN_ID (line 25) | const LISTENER_MIO_TOKEN_ID: usize = 0;
  constant CONNECTION_MIO_TOKEN_ID_MIN (line 26) | const CONNECTION_MIO_TOKEN_ID_MIN: usize = LISTENER_MIO_TOKEN_ID + 1;
  constant CONNECTION_MIO_TOKEN_ID_MAX (line 28) | const CONNECTION_MIO_TOKEN_ID_MAX: usize = usize::max_value() - 1;
  constant LISTENER_MIO_TOKEN (line 31) | const LISTENER_MIO_TOKEN: mio::Token = mio::Token(LISTENER_MIO_TOKEN_ID);
  type KeServerListener (line 35) | pub struct KeServerListener {
    method bind (line 68) | pub fn bind(addr: SocketAddr, server: &KeServer) -> Result<KeServerLis...
    method listen (line 101) | pub fn listen(&mut self) -> Result<(), std::io::Error> {
    method accept (line 143) | fn accept(&mut self) -> Result<(), std::io::Error> {
    method increment_next_conn_token_id (line 202) | fn increment_next_conn_token_id(&mut self) {
    method close_expired_connections (line 217) | fn close_expired_connections(&mut self) {
    method state (line 245) | pub(super) fn state(&self) -> &Arc<KeServerState> {
    method logger (line 250) | pub(super) fn logger(&self) -> &slog::Logger {
    method addr (line 255) | pub(super) fn addr(&self) -> &SocketAddr {

FILE: src/sub_command/client.rs
  type ClientConfig (line 20) | pub struct ClientConfig {
  function load_tls_certs (line 27) | pub fn load_tls_certs(path: String) -> Result<Vec<Certificate>, config::...
  function run (line 34) | pub fn run(matches: &clap::ArgMatches<'_>) {

FILE: src/sub_command/ke_server.rs
  function resolve_config_filename (line 16) | fn resolve_config_filename(matches: &clap::ArgMatches<'_>) -> String {
  function run (line 26) | pub fn run(matches: &clap::ArgMatches<'_>) {

FILE: src/sub_command/ntp_server.rs
  function resolve_config_filename (line 17) | fn resolve_config_filename(matches: &clap::ArgMatches<'_>) -> String {
  function run (line 27) | pub fn run(matches: &clap::ArgMatches<'_>) {
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (194K chars).
[
  {
    "path": ".cargo/config",
    "chars": 154,
    "preview": "[build]\nrustflags = [\"-Ctarget-feature=+aes,+ssse3\"]\nrustdocflags = [\"-Ctarget-feature=+aes,+ssse3\"]\n[test]\nrustflags = "
  },
  {
    "path": ".github/workflows/cfntsci.yml",
    "chars": 1036,
    "preview": "---\nname: cfntsCI\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  Testing:\n    runs-on: ubuntu-latest"
  },
  {
    "path": ".gitignore",
    "chars": 28,
    "preview": "/target\n**/*.rs.bk\n**/*.swp\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 252,
    "preview": "# Contributing\n\nWe welcome your contributions. Note that your contributions must be licensed under the BSD-style license"
  },
  {
    "path": "Cargo.toml",
    "chars": 1294,
    "preview": "[package]\nname        = \"cfnts\"\nversion     = \"2019.6.0\"\nauthors = [\n    \"Watson Ladd <watson@cloudflare.com>\",\n    \"Gab"
  },
  {
    "path": "Dockerfile.cfnts",
    "chars": 219,
    "preview": "FROM rust:1.69.0-bookworm as builder\n\nCOPY src    src\nCOPY .cargo .cargo\nCOPY Cargo.toml Cargo.lock ./\n\nRUN cargo build "
  },
  {
    "path": "Dockerfile.memcache",
    "chars": 96,
    "preview": "FROM debian:bookworm\n\nRUN apt-get update && \\\n    apt-get -y install memcached python3-memcache\n"
  },
  {
    "path": "LICENSE",
    "chars": 1303,
    "preview": "Copyright (c) 2019, Cloudflare. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without"
  },
  {
    "path": "Makefile",
    "chars": 491,
    "preview": "SHELL=/bin/bash\n\nTARGET_ARCHS ?= x86_64-unknown-linux-gnu\n\nrelease:\n\t@git diff --quiet || { echo \"Run in a clean repo\"; "
  },
  {
    "path": "README.md",
    "chars": 1439,
    "preview": "# cfnts\n\n## DEPRECATION NOTICE\n**This software is no longer maintained. Consider using an alternative NTS implementation"
  },
  {
    "path": "RELEASE_NOTES",
    "chars": 1848,
    "preview": "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 t"
  },
  {
    "path": "docker-compose.yaml",
    "chars": 611,
    "preview": "version: \"3.8\"\nservices:\n  server:\n    build:\n      context: .\n      dockerfile: Dockerfile.cfnts\n    depends_on:\n      "
  },
  {
    "path": "scripts/fill-memcached.py",
    "chars": 358,
    "preview": "import memcache\nimport time\nimport math\n\nprint(\"filling memcache\")\nservers = [\"localhost:11211\"]\nmc = memcache.Client(se"
  },
  {
    "path": "scripts/run_client.sh",
    "chars": 238,
    "preview": "#!/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;"
  },
  {
    "path": "scripts/run_memcached.sh",
    "chars": 129,
    "preview": "#!/bin/bash\necho \"Running memcache\"\ndate \"+%s\"\nmemcached -u root &\nsleep 2\npython3 scripts/fill-memcached.py\necho \"done\""
  },
  {
    "path": "scripts/run_server.sh",
    "chars": 275,
    "preview": "#!/bin/bash\nsleep 5\ndate \"+%s\"\n\nRUST_BACKTRACE=1 ./target/release/cfnts ke-server -f tests/nts-ke-config.yaml &\nRUST_BAC"
  },
  {
    "path": "src/cfsock.rs",
    "chars": 1606,
    "preview": "use libc::*;\nuse socket2::{Domain, Socket, Type};\nuse std::net::SocketAddr;\nuse std::os::unix::io::AsRawFd;\n\n#[cfg(targe"
  },
  {
    "path": "src/cmd.rs",
    "chars": 3628,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/cookie.rs",
    "chars": 3387,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/error.rs",
    "chars": 1580,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/key_rotator.rs",
    "chars": 12679,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/main.rs",
    "chars": 2900,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/metrics.rs",
    "chars": 3154,
    "preview": "// Our goal is to shove data at prometheus in response to requests.\nuse lazy_static::lazy_static;\nuse prometheus::{self,"
  },
  {
    "path": "src/ntp/client.rs",
    "chars": 5750,
    "preview": "use crate::nts_ke::client::NtsKeResult;\n\nuse miscreant::aead::Aead;\nuse miscreant::aead::Aes128SivAead;\nuse rand::Rng;\nu"
  },
  {
    "path": "src/ntp/mod.rs",
    "chars": 50,
    "preview": "pub mod client;\npub mod protocol;\npub mod server;\n"
  },
  {
    "path": "src/ntp/protocol.rs",
    "chars": 18937,
    "preview": "use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};\nuse miscreant::aead::Aead;\nuse rand::Rng;\n\nuse std::io::{Cursor"
  },
  {
    "path": "src/ntp/server/config.rs",
    "chars": 7093,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/ntp/server/mod.rs",
    "chars": 274,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/ntp/server/ntp_server.rs",
    "chars": 20309,
    "preview": "use super::config::NtpServerConfig;\nuse crate::cfsock;\nuse crate::cookie::{eat_cookie, get_keyid, make_cookie, NTSKeys, "
  },
  {
    "path": "src/nts_ke/client.rs",
    "chars": 6958,
    "preview": "use slog::{debug, info};\nuse std::error::Error;\nuse std::io::{Read, Write};\nuse std::net::{Shutdown, TcpStream, ToSocket"
  },
  {
    "path": "src/nts_ke/mod.rs",
    "chars": 49,
    "preview": "pub mod client;\npub mod records;\npub mod server;\n"
  },
  {
    "path": "src/nts_ke/records/aead_algorithm.rs",
    "chars": 2775,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/records/end_of_message.rs",
    "chars": 782,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/records/error.rs",
    "chars": 1416,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/records/mod.rs",
    "chars": 7491,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/records/new_cookie.rs",
    "chars": 977,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/records/next_protocol.rs",
    "chars": 2588,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/records/port.rs",
    "chars": 1291,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/records/server.rs",
    "chars": 2665,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/records/warning.rs",
    "chars": 1505,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/server/config.rs",
    "chars": 11797,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/server/connection.rs",
    "chars": 11567,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/server/ke_server.rs",
    "chars": 7520,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/server/listener.rs",
    "chars": 9541,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/nts_ke/server/mod.rs",
    "chars": 467,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/sub_command/client.rs",
    "chars": 2788,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/sub_command/ke_server.rs",
    "chars": 2061,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/sub_command/mod.rs",
    "chars": 214,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "src/sub_command/ntp_server.rs",
    "chars": 1653,
    "preview": "// This file is part of cfnts.\n// Copyright (c) 2019, Cloudflare. All rights reserved.\n// See LICENSE for licensing info"
  },
  {
    "path": "tests/ca-key.pem",
    "chars": 3272,
    "preview": "-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCzTB9ETn6RgGHT\nEXkvXtUxCRtN5oz8eh1hD98OBOY"
  },
  {
    "path": "tests/ca.csr",
    "chars": 1651,
    "preview": "-----BEGIN CERTIFICATE REQUEST-----\nMIIEizCCAnMCAQAwRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQH\nDA1TYW4gRnJhbmNpc2N"
  },
  {
    "path": "tests/ca.pem",
    "chars": 1822,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIFEzCCAvsCFEXBseLI9/DDsUJVNhy2Nn2ORSZFMA0GCSqGSIb3DQEBCwUAMEYx\nCzAJBgNVBAYTAlVTMQswCQYDVQQ"
  },
  {
    "path": "tests/chain.pem",
    "chars": 4198,
    "preview": "-----BEGIN CERTIFICATE-----\nMIICoDCCAkegAwIBAgIUW5W4GNGYJwryph3KHKkdLaeFdvMwCgYIKoZIzj0EAwIw\ngY4xCzAJBgNVBAYTAlVTMRMwEQY"
  },
  {
    "path": "tests/cookie.key",
    "chars": 7,
    "preview": "Td\u001a>!鼽v"
  },
  {
    "path": "tests/generate.sh",
    "chars": 550,
    "preview": "openssl req -newkey rsa:4096 -keyout ca-key.pem -out ca.csr -days 3650 -nodes -subj \"/C=US/ST=CA/L=San Francisco/CN=loca"
  },
  {
    "path": "tests/int-config.json",
    "chars": 305,
    "preview": "{\n  \"signing\": {\n    \"default\": {\n      \"ca_constraint\": {\n        \"is_ca\": true,\n        \"max_path_len\": 0,\n        \"ma"
  },
  {
    "path": "tests/intermediate-key.pem",
    "chars": 227,
    "preview": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIBTkESHvag8yy5dO8xza5Zo52TRDDQgmqWBMpWsBRjmPoAoGCCqGSM49\nAwEHoUQDQgAEkAG0ZWrTy7A5"
  },
  {
    "path": "tests/intermediate.csr",
    "chars": 521,
    "preview": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBSTCB8QIBADCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx\nFjAUBgNVBAcTDVNhbiB"
  },
  {
    "path": "tests/intermediate.json",
    "chars": 250,
    "preview": "{\n  \"key\": {\n    \"algo\": \"ecdsa\",\n    \"size\": 256\n  },\n  \"names\": [\n    {\n      \"C\": \"US\",\n      \"L\": \"San Francisco\",\n "
  },
  {
    "path": "tests/intermediate.pem",
    "chars": 1403,
    "preview": "-----BEGIN CERTIFICATE-----\nMIID3zCCAcegAwIBAgIUalsEFgf4uPaULSbhh3By7ebBUSQwDQYJKoZIhvcNAQEN\nBQAwRjELMAkGA1UEBhMCVVMxCzA"
  },
  {
    "path": "tests/ntp-config.yaml",
    "chars": 282,
    "preview": "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"
  },
  {
    "path": "tests/ntp-upstream-config.yaml",
    "chars": 205,
    "preview": "addr:\n  - 127.0.0.1:456\ncookie_key_file: tests/cookie.key # TODO: store and read as pem files, or read bytes directly fr"
  },
  {
    "path": "tests/nts-ke-config.yaml",
    "chars": 297,
    "preview": "addr:\n  - \"[::]:4460\"\ntls_key_file: tests/tls-pkcs8.pem\ntls_cert_file: tests/chain.pem # Expect PEM.\ncookie_key_file: te"
  },
  {
    "path": "tests/test-config.json",
    "chars": 187,
    "preview": "{\n  \"signing\": {\n    \"default\": {\n      \"expiry\": \"876000h\",\n      \"usages\": [\n        \"digital signature\",\n        \"cer"
  },
  {
    "path": "tests/test.json",
    "chars": 387,
    "preview": "{\n    \"CN\": \"localhost\",\n    \"hosts\": [\n        \"server\",\n        \"localhost\",\n        \"bogus.com\",\n        \"*.localhost"
  },
  {
    "path": "tests/tls-key.pem",
    "chars": 227,
    "preview": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIBvLtCg67XQkDzWZDS4peNXy8r4Dguv+KYUVoOZEcCjGoAoGCCqGSM49\nAwEHoUQDQgAEcbTCrE0KE16a"
  },
  {
    "path": "tests/tls-pkcs8.pem",
    "chars": 241,
    "preview": "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgG8u0KDrtdCQPNZkN\nLil41fLyvgOC6/4phRWg5kRwKMa"
  },
  {
    "path": "tests/tls.csr",
    "chars": 586,
    "preview": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBeDCCAR8CAQAwdjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQH\nEw1TYW4gRnJhbmNpc2N"
  },
  {
    "path": "tests/tls.pem",
    "chars": 973,
    "preview": "-----BEGIN CERTIFICATE-----\nMIICoDCCAkegAwIBAgIUW5W4GNGYJwryph3KHKkdLaeFdvMwCgYIKoZIzj0EAwIw\ngY4xCzAJBgNVBAYTAlVTMRMwEQY"
  }
]

About this extraction

This page contains the full source code of the cloudflare/cfnts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (180.5 KB), approximately 52.0k tokens, and a symbol index with 276 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!