Skip to content

Commit

Permalink
Upgrade hyper to 1.x; add Config::add_root_certificate for trusting c…
Browse files Browse the repository at this point in the history
…ustom certificate authorities
  • Loading branch information
iamjpotts committed Jan 31, 2024
1 parent 6c4ac77 commit b28ddc1
Show file tree
Hide file tree
Showing 17 changed files with 419 additions and 7 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ tag-message = "dkregistry v{{version}}"
[dependencies]
base64 = "0.13"
futures = "0.3"
http = "0.2"

# Pin libflate <1.3.0
# https://github.com/sile/libflate/commit/aba829043f8a2d527b6c4984034fbe5e7adb0da6
Expand Down Expand Up @@ -55,7 +54,9 @@ url = "2.1.1"
[dev-dependencies]
dirs = "4.0"
env_logger = "0.8"
hyper = "1.1"
mockito = "0.30"
native-tls = "0.2"
spectral = "0.6"
test-case = "1.0.0"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
Expand Down
3 changes: 3 additions & 0 deletions certificate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Don't commit the binaries; they are only needed to occasionally regenerate the certificates.
cfssl
cfssljson
32 changes: 32 additions & 0 deletions certificate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

# Certificate Generation and Persistence for Tests

While it is possible to automagically generate certificates using the [rcgen](https://github.com/est31/rcgen)
crate, that library (as of version 0.10.0) has a dependency on the [ring](https://github.com/briansmith/ring)
crate, which has a non-trivial set of licenses.

To avoid potential problems with the licenses applying to `ring`, `rcgen` is not used to generate
test certificates.

## Generating and Persisting Test Certificates

The tests require a self-signed certificate authority, and a private key / server certificate pair signed by
that same CA.

Certificates are defined in json files, generated using [cfssl](https://github.com/cloudflare/cfssl), and
committed into git.

### Install `cfssl` and Re-generate Certificates

$ ./download-cfssl.sh
$ ./create-ca.sh
$ ./create-localhost.sh

Note: You should not have to regenerate any certificates unless they expire, the ciphers become insecure,
or the certificates otherwise become rejected by future versions of cryptography libraries.

### Definitions

* [profiles.json](profiles.json)
* [ca.json](ca.json)
* [localhost.json](localhost.json)
13 changes: 13 additions & 0 deletions certificate/ca.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"CN": "Automated Testing CA",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "USA"
}
]
}

13 changes: 13 additions & 0 deletions certificate/create-ca.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

set -e

pushd output

../cfssl gencert \
-config ../profiles.json \
-initca ../ca.json \
| ../cfssljson -bare ca

popd

27 changes: 27 additions & 0 deletions certificate/create-localhost.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

set -e

pushd output

../cfssl gencert \
-ca ca.pem \
-ca-key ca-key.pem \
-config ../profiles.json \
-profile=server \
../localhost.json \
| ../cfssljson -bare localhost

cat localhost.pem ca.pem > localhost.crt

openssl \
pkcs8 \
-topk8 \
-inform PEM \
-outform PEM \
-nocrypt \
-in localhost-key.pem \
-out localhost-key-pkcs8.pem

popd

12 changes: 12 additions & 0 deletions certificate/download-cfssl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

version=1.6.1

rm -f cfssl
wget -O cfssl https://github.com/cloudflare/cfssl/releases/download/v${version}/cfssl_${version}_linux_amd64
chmod +x cfssl

rm -f cfssljson
wget -O cfssljson https://github.com/cloudflare/cfssl/releases/download/v${version}/cfssljson_${version}_linux_amd64
chmod +x cfssljson

15 changes: 15 additions & 0 deletions certificate/localhost.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"CN": "localhost",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "USA"
}
],
"hosts": [
"localhost"
]
}
11 changes: 11 additions & 0 deletions certificate/output/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# For CA, only need public key after server certificate is created
ca.csr
ca-key.pem

# For server, only need private key and chained public key
localhost.csr
localhost.pem

# Throw away pkcs1 flavor and keep pkcs8 flavor
localhost-key.pem

19 changes: 19 additions & 0 deletions certificate/output/ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDKjCCAhKgAwIBAgIUIm0u2dDryGQArfPLSadKLGAJ+MAwDQYJKoZIhvcNAQEL
BQAwLTEMMAoGA1UEBhMDVVNBMR0wGwYDVQQDExRBdXRvbWF0ZWQgVGVzdGluZyBD
QTAeFw0yMjExMDgxMzE2MDBaFw0yNzExMDcxMzE2MDBaMC0xDDAKBgNVBAYTA1VT
QTEdMBsGA1UEAxMUQXV0b21hdGVkIFRlc3RpbmcgQ0EwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDBxBtMTvxybYrSrPbka3xD+Pzoj7MG6Fldh5j2vPsw
Nz+SFwIGvU8XeJcSKIcAwFBCJ/GkYF8Uoa1/l6AXvafn1SmtricV3AYxYq40vXL+
P1WY2HlXP4pwjbMF6uPiOm5r5HBpK5uptgJZRxMhbdqtoJP1/Acbrn62DYy4eqZN
i9f+eiVKewn7Z40TONigzNyz1J1ffH3fA18MmcrXGfWF0figbSL3XpSn4nu3R2mm
0rVpPQf6E+OHRS2NF0ekN7Xn8oMCQYHOXme3V8i2Sth6jyv9bhlvAGGJVY6XgIKa
URSIjm9M87S0bYzi3YSMP6p2rxmHV/gOxnZ3e0wBihivAgMBAAGjQjBAMA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR4TW/Tbj2b74Fi
a2kaPkCKj+JZUjANBgkqhkiG9w0BAQsFAAOCAQEAnRIkllxB6vtIxoV7HYizdxEo
biS5dB0ErqMYFkOOYyLA9RCgqaFNmEvwzxg+yE9AggGs3Me68hma8Oe+1iydGUjv
Emhh3XK/0ZCKJ63071wBAr5I9kOzbtPytyF6gaxPtpqqUcp6WyE0snFQt/1Vq/S8
AMxPvU60thYUR1xPSSaPa3cEHMcgC/O4DCjmoJaILlrNShqvPcV2QD75D+HjcK68
EIKhqluRwZsh/LrH8btUgtl5nAPNFRe4QiEeLCJHGPZ29mBCSeQXTKeRaSQe3Ixp
q/ObtXVbTanhQG5WAvxJAb7MdFD+N4q0C3D6HmlXL1g/zyMF9PTHRtCBjp7ytA==
-----END CERTIFICATE-----
5 changes: 5 additions & 0 deletions certificate/output/localhost-key-pkcs8.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgQeEZs5zOQRA/JcoZ
eX9hLSUk9CNqbb3fmAhqn5f7q8WhRANCAAQhtz6gl0uLfATyk1B9AhFsXgHEDMpQ
Poa0UUrNkJye3LZe6iGnCTWHAS3Qr/hecohUY6mNQplnufBtdAE9jJd1
-----END PRIVATE KEY-----
36 changes: 36 additions & 0 deletions certificate/output/localhost.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-----BEGIN CERTIFICATE-----
MIICnzCCAYegAwIBAgIUG2PchR5jyYocFHa+6LWgMO1MTJIwDQYJKoZIhvcNAQEL
BQAwLTEMMAoGA1UEBhMDVVNBMR0wGwYDVQQDExRBdXRvbWF0ZWQgVGVzdGluZyBD
QTAeFw0yMjExMDgxMzE2MDBaFw0zMjExMDUxMzE2MDBaMCIxDDAKBgNVBAYTA1VT
QTESMBAGA1UEAxMJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
Ibc+oJdLi3wE8pNQfQIRbF4BxAzKUD6GtFFKzZCcnty2Xuohpwk1hwEt0K/4XnKI
VGOpjUKZZ7nwbXQBPYyXdaOBjDCBiTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAww
CgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzBknP8vwv3KAPEcE
q3OYho/q3FAwHwYDVR0jBBgwFoAUeE1v0249m++BYmtpGj5Aio/iWVIwFAYDVR0R
BA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQCeQ4rwIp6wZBTZDflm
0Olj4czaOfsLMhoTYVoarfAzB57uV1yP87kOMFHaMycLViZzPi+T1rOjDCIQWNLh
h6EMoGDkPLNZSG2KxVRKnOFQgE50CPobgEGZFmAIuBNjHX7MG8I1J/HO0X9Krzz6
wqdyy0IBtv64W7wrty2ab+okBiNPlgV1mxzWlRJk8zcPY/aLOkJ+5Gd40YQNtWAd
dPPevJIF/Dh+OadvUXtkiwmoJzn6pWwFwzyTp9kcSYVZYo5LWzV5U6l/HJVFNq/f
a3U1Grw2T4Nb33G1cGn5xfEqnMvaWEAmDK7bb/smY/dTocnUUD3FGBmkNMXqE4FK
nC9q
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDKjCCAhKgAwIBAgIUIm0u2dDryGQArfPLSadKLGAJ+MAwDQYJKoZIhvcNAQEL
BQAwLTEMMAoGA1UEBhMDVVNBMR0wGwYDVQQDExRBdXRvbWF0ZWQgVGVzdGluZyBD
QTAeFw0yMjExMDgxMzE2MDBaFw0yNzExMDcxMzE2MDBaMC0xDDAKBgNVBAYTA1VT
QTEdMBsGA1UEAxMUQXV0b21hdGVkIFRlc3RpbmcgQ0EwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDBxBtMTvxybYrSrPbka3xD+Pzoj7MG6Fldh5j2vPsw
Nz+SFwIGvU8XeJcSKIcAwFBCJ/GkYF8Uoa1/l6AXvafn1SmtricV3AYxYq40vXL+
P1WY2HlXP4pwjbMF6uPiOm5r5HBpK5uptgJZRxMhbdqtoJP1/Acbrn62DYy4eqZN
i9f+eiVKewn7Z40TONigzNyz1J1ffH3fA18MmcrXGfWF0figbSL3XpSn4nu3R2mm
0rVpPQf6E+OHRS2NF0ekN7Xn8oMCQYHOXme3V8i2Sth6jyv9bhlvAGGJVY6XgIKa
URSIjm9M87S0bYzi3YSMP6p2rxmHV/gOxnZ3e0wBihivAgMBAAGjQjBAMA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR4TW/Tbj2b74Fi
a2kaPkCKj+JZUjANBgkqhkiG9w0BAQsFAAOCAQEAnRIkllxB6vtIxoV7HYizdxEo
biS5dB0ErqMYFkOOYyLA9RCgqaFNmEvwzxg+yE9AggGs3Me68hma8Oe+1iydGUjv
Emhh3XK/0ZCKJ63071wBAr5I9kOzbtPytyF6gaxPtpqqUcp6WyE0snFQt/1Vq/S8
AMxPvU60thYUR1xPSSaPa3cEHMcgC/O4DCjmoJaILlrNShqvPcV2QD75D+HjcK68
EIKhqluRwZsh/LrH8btUgtl5nAPNFRe4QiEeLCJHGPZ29mBCSeQXTKeRaSQe3Ixp
q/ObtXVbTanhQG5WAvxJAb7MdFD+N4q0C3D6HmlXL1g/zyMF9PTHRtCBjp7ytA==
-----END CERTIFICATE-----
19 changes: 19 additions & 0 deletions certificate/profiles.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"server": {
"usages": [
"signing",
"digital signing",
"key encipherment",
"server auth"
],
"expiry": "87600h"
}
}
}
}

9 changes: 9 additions & 0 deletions certificate/reset.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

set -e

pushd output

rm -f *.csr *.pem *.crt

popd
8 changes: 4 additions & 4 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub enum Error {
#[error("base64 decode error")]
Base64Decode(#[from] base64::DecodeError),
#[error("header parse error")]
HeaderParse(#[from] http::header::ToStrError),
HeaderParse(#[from] reqwest::header::ToStrError),
#[error("json error")]
Json(#[from] serde_json::Error),
#[error("http transport error: {0}")]
Expand All @@ -28,7 +28,7 @@ pub enum Error {
#[error("missing authentication header {0}")]
MissingAuthHeader(&'static str),
#[error("unexpected HTTP status {0}")]
UnexpectedHttpStatus(http::StatusCode),
UnexpectedHttpStatus(reqwest::StatusCode),
#[error("invalid auth token '{0}'")]
InvalidAuthToken(String),
#[error("API V2 not supported")]
Expand All @@ -38,9 +38,9 @@ pub enum Error {
#[error("www-authenticate header parse error")]
Www(#[from] crate::v2::WwwHeaderParseError),
#[error("request failed with status {status}")]
Client { status: http::StatusCode },
Client { status: reqwest::StatusCode },
#[error("request failed with status {status}")]
Server { status: http::StatusCode },
Server { status: reqwest::StatusCode },
#[error("content digest error")]
ContentDigestParse(#[from] crate::v2::ContentDigestError),
#[error("no header Content-Type given and no workaround to apply")]
Expand Down
20 changes: 18 additions & 2 deletions src/v2/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use reqwest::Certificate;
use crate::{mediatypes::MediaTypes, v2::*};

/// Configuration for a `Client`.
Expand All @@ -9,6 +10,7 @@ pub struct Config {
username: Option<String>,
password: Option<String>,
accept_invalid_certs: bool,
root_certificates: Vec<Certificate>,
accepted_types: Option<Vec<(MediaTypes, Option<f64>)>>,
}

Expand All @@ -31,6 +33,12 @@ impl Config {
self
}

/// Add a root certificate the client should trust for TLS verification
pub fn add_root_certificate(mut self, certificate: Certificate) -> Self {
self.root_certificates.push(certificate);
self
}

/// Set custom Accept headers
pub fn accepted_types(
mut self,
Expand Down Expand Up @@ -87,8 +95,15 @@ impl Config {
p.unwrap_or_else(|| "".into()),
)),
};
let client = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(self.accept_invalid_certs)

let mut builder = reqwest::ClientBuilder::new()
.danger_accept_invalid_certs(self.accept_invalid_certs);

for ca in self.root_certificates {
builder = builder.add_root_certificate(ca)
}

let client = builder
.build()?;

let accepted_types = match self.accepted_types {
Expand Down Expand Up @@ -130,6 +145,7 @@ impl Default for Config {
index: "registry-1.docker.io".into(),
insecure_registry: false,
accept_invalid_certs: false,
root_certificates: Default::default(),
accepted_types: None,
user_agent: Some(crate::USER_AGENT.to_owned()),
username: None,
Expand Down
Loading

0 comments on commit b28ddc1

Please sign in to comment.