Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,21 @@ jobs:
with:
toolchain: ${{ matrix.rustc }}

- name: Install OpenSSL (Windows)
if: runner.os == 'Windows'
shell: powershell
run: |
echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
vcpkg install openssl:x64-windows-static-md

- name: Build (debug)
run: cargo build --locked
- name: Run tests (debug)
run: cargo test --locked
- name: Check FFI header
run: git diff --exit-code -- upki-ffi/upki.h
run: |
git diff --exit-code -- upki-ffi/upki.h
git diff --exit-code -- upki-openssl/upki-openssl.h

- name: Build (release)
run: cargo build --locked --release
Expand Down
38 changes: 38 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["upki", "upki-cli", "upki-mirror", "revoke-test", "rustls-upki", "upki-ffi"]
members = ["upki", "upki-cli", "upki-mirror", "revoke-test", "rustls-upki", "upki-ffi", "upki-openssl"]
resolver = "3"

[workspace.package]
Expand All @@ -22,6 +22,7 @@ hex = { version = "0.4", features = ["serde"] }
http = "1"
insta = { version = "1.44.3", features = ["filters"] }
insta-cmd = "0.6.0"
openssl-sys = "0"
reqwest = { version = "0.13", default-features = false, features = ["charset", "default-tls", "h2", "http2", "json"] }
rand = "0.10"
regex = "1.12"
Expand Down
3 changes: 3 additions & 0 deletions revoke-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ x509-parser.workspace = true
[dev-dependencies]
criterion.workspace = true
insta-cmd.workspace = true
openssl-sys.workspace = true
rustls-pki-types.workspace = true
rustls-upki.workspace = true
upki = { path = "../upki" }
upki-ffi = { path = "../upki-ffi" }
upki-openssl = { path = "../upki-openssl" }

[features]
__bench_codspeed = ["dep:codspeed-criterion-compat"]
Expand Down
20 changes: 20 additions & 0 deletions revoke-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,26 @@ impl CertificateDetail {
scts,
})
}

pub fn end_entity_cert_der(&self) -> Result<CertificateDer<'static>> {
Ok(CertificateDer::from(
BASE64_STANDARD
.decode(&self.end_entity_cert)
.map_err(|e| eyre::eyre!("cannot base64-decode certificate {e:?}"))?,
))
}

pub fn intermediates_der(&self) -> Result<Vec<CertificateDer<'static>>> {
let mut certs = Vec::new();

for i in &self.intermediates {
certs.push(CertificateDer::from(BASE64_STANDARD.decode(i).map_err(
|e| eyre::eyre!("cannot base64-decode certificate {e:?}"),
)?));
}

Ok(certs)
}
}

fn parse_octet_string(data: &[u8]) -> Result<&[u8]> {
Expand Down
58 changes: 58 additions & 0 deletions revoke-test/tests/api/ffi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use core::ptr;
use std::ffi::CString;

use revoke_test::CertificateDetail;
use upki_ffi::{
upki_certificate_der, upki_check_revocation, upki_config, upki_config_free, upki_config_new,
upki_result,
};

use super::{TEST_CONFIG_PATH, TestResult};

pub(super) fn ffi(detail: &CertificateDetail) -> TestResult {
let certs = [detail.end_entity_cert_der().unwrap()]
.into_iter()
.chain(detail.intermediates_der().unwrap())
.collect::<Vec<_>>();

let mut cert_pointers = Vec::new();
for c in &certs {
cert_pointers.push(upki_certificate_der {
data: c.as_ptr(),
len: c.len(),
});
}

let mut config = OwnedConfig(ptr::null_mut());
assert!(matches!(
unsafe {
upki_config_new(
CString::new(TEST_CONFIG_PATH)
.unwrap()
.as_ptr(),
&mut config.0,
)
},
upki_result::UPKI_OK
));
let rc =
unsafe { upki_check_revocation(config.0, cert_pointers.as_ptr(), cert_pointers.len()) };

drop(certs); // extend lifetime for benefit of cert_pointers

match rc {
upki_result::UPKI_REVOCATION_REVOKED => TestResult::CorrectlyRevoked,
upki_result::UPKI_REVOCATION_NOT_COVERED | upki_result::UPKI_REVOCATION_NOT_REVOKED => {
TestResult::IncorrectlyNotRevoked
}
e => panic!("upki_check_revocation() failed with {:?}", e as usize),
}
}

struct OwnedConfig(*mut upki_config);

impl Drop for OwnedConfig {
fn drop(&mut self) {
unsafe { upki_config_free(self.0) };
}
}
153 changes: 153 additions & 0 deletions revoke-test/tests/api/openssl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use core::ffi::{c_int, c_long, c_void};
use core::{mem, ptr};
use std::ffi::CString;

use openssl_sys::{
OPENSSL_STACK, OPENSSL_sk_free, OPENSSL_sk_new_null, OPENSSL_sk_push, SSL, SSL_CTX,
SSL_CTX_free, SSL_CTX_new, SSL_free, SSL_get_ex_data_X509_STORE_CTX_idx, SSL_new,
TLS_client_method, X509_STORE_CTX, X509_STORE_CTX_free, X509_STORE_CTX_get_error,
X509_STORE_CTX_new, X509_V_ERR_CERT_REVOKED, d2i_X509, stack_st_X509,
};
use revoke_test::CertificateDetail;
use upki_ffi::{upki_config_new, upki_result};
use upkiopenssl::{upki_openssl_set_config, upki_openssl_verify_callback};

use super::{TEST_CONFIG_PATH, TestResult};

pub(super) fn openssl(detail: &CertificateDetail) -> TestResult {
let mut chain = Chain::new();

for cert in [detail.end_entity_cert_der().unwrap()]
.into_iter()
.chain(detail.intermediates_der().unwrap())
{
chain.push(&cert);
}

let mut config = ptr::null_mut();
assert!(matches!(
unsafe {
upki_config_new(
CString::new(TEST_CONFIG_PATH)
.unwrap()
.as_ptr(),
&mut config,
)
},
upki_result::UPKI_OK
));

let ssl_ctx = SslCtx::new();
unsafe { upki_openssl_set_config(ssl_ctx.0, config) };

let ssl = ssl_ctx.new_ssl();

let mut store_ctx = StoreCtx::new();
store_ctx.attach_ssl(ssl.0);
store_ctx.set_error_depth(0);
store_ctx.set_verified_chain(chain);

let rc = unsafe { upki_openssl_verify_callback(1, store_ctx.0) };

drop(ssl); // extend lifetime over store_ctx

match rc {
1 => TestResult::IncorrectlyNotRevoked,
0 if store_ctx.error() == X509_V_ERR_CERT_REVOKED => TestResult::CorrectlyRevoked,
_ => panic!(
"upki_openssl_verify_callback failed with rc={rc:?} store_ctx.error={:?}",
store_ctx.error()
),
}
}

struct SslCtx(*mut SSL_CTX);

impl SslCtx {
fn new() -> Self {
Self(unsafe { SSL_CTX_new(TLS_client_method()) })
}

fn new_ssl(&self) -> Ssl {
Ssl(unsafe { SSL_new(self.0) })
}
}

impl Drop for SslCtx {
fn drop(&mut self) {
unsafe { SSL_CTX_free(self.0) };
}
}

struct Ssl(*mut SSL);

impl Drop for Ssl {
fn drop(&mut self) {
unsafe { SSL_free(self.0) };
}
}

struct StoreCtx(*mut X509_STORE_CTX);

impl StoreCtx {
fn new() -> Self {
Self(unsafe { X509_STORE_CTX_new() })
}

fn error(&self) -> c_int {
unsafe { X509_STORE_CTX_get_error(self.0) }
}

fn set_error_depth(&mut self, depth: i32) {
unsafe { X509_STORE_CTX_set_error_depth(self.0, depth) };
}

fn set_verified_chain(&mut self, mut chain: Chain) {
unsafe { X509_STORE_CTX_set0_verified_chain(self.0, chain.steal()) };
}

fn attach_ssl(&mut self, ssl: *mut SSL) {
unsafe {
X509_STORE_CTX_set_ex_data(self.0, SSL_get_ex_data_X509_STORE_CTX_idx(), ssl.cast())
};
}
}

impl Drop for StoreCtx {
fn drop(&mut self) {
unsafe { X509_STORE_CTX_free(self.0) }
}
}

struct Chain(*mut stack_st_X509);

impl Chain {
fn new() -> Self {
Self(unsafe { OPENSSL_sk_new_null() as *mut stack_st_X509 })
}

fn push(&mut self, der_bytes: &[u8]) {
unsafe {
let mut ptr = der_bytes.as_ptr();
let x509 = d2i_X509(ptr::null_mut(), &mut ptr, der_bytes.len() as c_long);
assert!(!x509.is_null());
OPENSSL_sk_push(self.0 as *mut OPENSSL_STACK, x509 as *mut c_void);
}
}

fn steal(&mut self) -> *mut stack_st_X509 {
mem::replace(&mut self.0, ptr::null_mut())
}
}

impl Drop for Chain {
fn drop(&mut self) {
unsafe { OPENSSL_sk_free(self.0 as *mut OPENSSL_STACK) }
}
}

unsafe extern "C" {
fn X509_STORE_CTX_set_error_depth(ctx: *mut X509_STORE_CTX, depth: c_int);
fn X509_STORE_CTX_set0_verified_chain(ctx: *mut X509_STORE_CTX, chain: *mut stack_st_X509);
fn X509_STORE_CTX_set_ex_data(ctx: *mut X509_STORE_CTX, idx: c_int, ptr: *mut c_void);
}
Loading
Loading