Skip to content

Commit 7c51434

Browse files
author
William Tisäter
committed
Add verify method
1 parent 8ccb854 commit 7c51434

File tree

7 files changed

+130
-26
lines changed

7 files changed

+130
-26
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"[rust]": {
3+
"editor.formatOnSave": true
4+
}
5+
}

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rsmime"
3-
version = "0.3.1"
3+
version = "0.4.0"
44
edition = "2021"
55

66
[lib]
@@ -14,4 +14,4 @@ pyo3 = { version = "0.20", features = ["extension-module"] }
1414

1515
[features]
1616
abi3 = ["pyo3/abi3-py37", "generate-import-lib"]
17-
generate-import-lib = ["pyo3/generate-import-lib"]
17+
generate-import-lib = ["pyo3/generate-import-lib"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import rsmime
1414
raw_data = b'data to sign'
1515

1616
try:
17-
signed_data = rsmime.sign(cert_file, key_file, raw_data)
17+
signed_data = rsmime.sign('some.crt', 'some.key', raw_data)
1818
except rsmime.SignError as e:
1919
print("Failed to sign:", e)
2020

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "rsmime"
7-
version = "0.3.1"
7+
version = "0.4.0"
88
classifiers = [
99
"License :: OSI Approved :: MIT License",
1010
"Development Status :: 3 - Alpha",

rsmime.pyi

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ class LoadCertificateError(Exception):
77
class SignError(Exception):
88
...
99

10-
def sign(cert_file: str, key_file: str, data_to_sign: bytes) -> str:
11-
...
10+
def sign(cert_file: str, key_file: str, data_to_sign: bytes) -> bytes:
11+
...
12+
13+
def verify(cert_file: str, data_to_verify: bytes, throw_on_expiry: bool = False) -> bytes:
14+
...

src/lib.rs

Lines changed: 115 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,148 @@
11
extern crate openssl;
22

3+
use std::io::{Error, ErrorKind};
4+
5+
use openssl::nid::Nid;
6+
use openssl::pkcs7::{Pkcs7, Pkcs7Flags};
37
use openssl::pkey;
48
use openssl::rsa::Rsa;
5-
use pyo3::prelude::*;
6-
use openssl::pkcs7::{Pkcs7, Pkcs7Flags};
7-
use openssl::stack::Stack;
8-
use openssl::x509::X509;
9+
use openssl::stack::{Stack, StackRef};
10+
use openssl::x509::store::X509StoreBuilder;
11+
use openssl::x509::{X509Ref, X509};
912
use pyo3::create_exception;
1013
use pyo3::exceptions::PyException;
14+
use pyo3::prelude::*;
15+
use pyo3::types::PyBytes;
1116

1217
create_exception!(rsmime, ReadCertificateError, PyException);
1318
create_exception!(rsmime, LoadCertificateError, PyException);
1419
create_exception!(rsmime, SignError, PyException);
20+
create_exception!(rsmime, VerifyError, PyException);
1521

16-
pub fn _sign(cert_file: &str, key_file: &str, data_to_sign: &[u8]) -> PyResult<Vec<u8>> {
22+
fn _sign(cert_file: &str, key_file: &str, data_to_sign: &[u8]) -> PyResult<Vec<u8>> {
1723
let certs = Stack::new().expect("Failed to create stack");
1824

1925
if data_to_sign.is_empty() {
2026
return Err(SignError::new_err("Cannot sign empty data"));
2127
}
22-
23-
let cert_data = std::fs::read(cert_file).map_err(|err| ReadCertificateError::new_err(err.to_string()))?;
24-
let key_data = std::fs::read(key_file).map_err(|err| ReadCertificateError::new_err(err.to_string()))?;
2528

26-
let cert = X509::from_pem(&cert_data).map_err(|err| LoadCertificateError::new_err(err.to_string()))?;
27-
let rsa = Rsa::private_key_from_pem(&key_data).map_err(|err| LoadCertificateError::new_err(err.to_string()))?;
28-
let pkey = pkey::PKey::from_rsa(rsa).map_err(|err| LoadCertificateError::new_err(err.to_string()))?;
29+
let cert_data =
30+
std::fs::read(cert_file).map_err(|err| ReadCertificateError::new_err(err.to_string()))?;
31+
let key_data =
32+
std::fs::read(key_file).map_err(|err| ReadCertificateError::new_err(err.to_string()))?;
33+
34+
let cert =
35+
X509::from_pem(&cert_data).map_err(|err| LoadCertificateError::new_err(err.to_string()))?;
36+
let rsa = Rsa::private_key_from_pem(&key_data)
37+
.map_err(|err| LoadCertificateError::new_err(err.to_string()))?;
38+
let pkey =
39+
pkey::PKey::from_rsa(rsa).map_err(|err| LoadCertificateError::new_err(err.to_string()))?;
40+
41+
let pkcs7 = Pkcs7::sign(
42+
cert.as_ref(),
43+
pkey.as_ref(),
44+
certs.as_ref(),
45+
data_to_sign,
46+
Pkcs7Flags::empty(),
47+
)
48+
.map_err(|err| SignError::new_err(err.to_string()))?;
49+
let out = pkcs7
50+
.to_smime(data_to_sign, Pkcs7Flags::empty())
51+
.map_err(|err| SignError::new_err(err.to_string()))?;
52+
53+
Ok(out)
54+
}
55+
56+
fn cert_subject_to_string(cert: &X509Ref, nid: Nid) -> String {
57+
let nid_entry = cert.subject_name().entries_by_nid(nid).next().unwrap();
58+
nid_entry.data().as_utf8().unwrap().to_string()
59+
}
60+
61+
fn validate_expiry(certs: &StackRef<X509>) -> Result<(), Error> {
62+
for cert in certs.iter() {
63+
let expire = cert.not_after();
64+
if expire.le(&openssl::asn1::Asn1Time::days_from_now(0).unwrap()) {
65+
let expire_string = expire.to_string();
66+
let subject_name = cert_subject_to_string(cert, Nid::COMMONNAME);
67+
return Err(Error::new(
68+
ErrorKind::Other,
69+
format!("Certificate {subject_name} expired {expire_string}"),
70+
));
71+
}
72+
}
73+
Ok(())
74+
}
75+
76+
fn _verify(cert_file: &str, data_to_verify: &[u8], throw_on_expiry: bool) -> PyResult<Vec<u8>> {
77+
let cert_data =
78+
std::fs::read(cert_file).map_err(|err| ReadCertificateError::new_err(err.to_string()))?;
79+
let cert =
80+
X509::from_pem(&cert_data).map_err(|err| LoadCertificateError::new_err(err.to_string()))?;
81+
82+
let mut certs = Stack::new().expect("Failed to create stack");
83+
certs
84+
.push(cert)
85+
.map_err(|err| LoadCertificateError::new_err(err.to_string()))?;
2986

30-
let flags = Pkcs7Flags::STREAM;
31-
let pkcs7 = Pkcs7::sign(cert.as_ref(), pkey.as_ref(), certs.as_ref(), data_to_sign, flags).map_err(|err| SignError::new_err(err.to_string()))?;
32-
let encrypted = pkcs7.to_smime(data_to_sign, flags).map_err(|err| SignError::new_err(err.to_string()))?;
87+
let mut out: Vec<u8> = Vec::new();
88+
let store = X509StoreBuilder::new().unwrap().build();
89+
90+
let x = Pkcs7::from_smime(data_to_verify);
91+
let x = x.map_err(|err| VerifyError::new_err(err.to_string()))?;
92+
let (pkcs7, _) = x;
93+
94+
if throw_on_expiry {
95+
validate_expiry(certs.as_ref()).map_err(|err| VerifyError::new_err(err.to_string()))?;
96+
}
3397

34-
Ok(encrypted)
98+
pkcs7
99+
.verify(
100+
certs.as_ref(),
101+
store.as_ref(),
102+
None,
103+
Some(out.as_mut()),
104+
Pkcs7Flags::NOVERIFY,
105+
)
106+
.map_err(|err| VerifyError::new_err(err.to_string()))?;
107+
108+
Ok(out)
35109
}
36110

37111
#[pyfunction]
38-
fn sign(cert_file: &str, key_file: &str, data_to_sign: Vec<u8>) -> PyResult<String> {
112+
fn sign(py: Python, cert_file: &str, key_file: &str, data_to_sign: Vec<u8>) -> PyResult<PyObject> {
39113
match _sign(cert_file, key_file, &data_to_sign) {
40-
Ok(signed_data) => Ok(String::from_utf8(signed_data).expect("Failed to convert to string")),
114+
Ok(data) => Ok(PyBytes::new(py, &data).into()),
115+
Err(err) => Err(err),
116+
}
117+
}
118+
119+
#[pyfunction]
120+
#[pyo3(signature = (cert_file, data_to_verify, *, throw_on_expiry = false))]
121+
fn verify(
122+
py: Python,
123+
cert_file: &str,
124+
data_to_verify: Vec<u8>,
125+
throw_on_expiry: bool,
126+
) -> PyResult<PyObject> {
127+
match _verify(cert_file, &data_to_verify, throw_on_expiry) {
128+
Ok(data) => Ok(PyBytes::new(py, &data).into()),
41129
Err(err) => Err(err),
42130
}
43131
}
44132

45133
#[pymodule]
46134
fn rsmime(py: Python, m: &PyModule) -> PyResult<()> {
47-
m.add("ReadCertificateError", py.get_type::<ReadCertificateError>())?;
48-
m.add("LoadCertificateError", py.get_type::<LoadCertificateError>())?;
135+
m.add(
136+
"ReadCertificateError",
137+
py.get_type::<ReadCertificateError>(),
138+
)?;
139+
m.add(
140+
"LoadCertificateError",
141+
py.get_type::<LoadCertificateError>(),
142+
)?;
49143
m.add("SignError", py.get_type::<SignError>())?;
144+
m.add("VerifyError", py.get_type::<VerifyError>())?;
50145
m.add_function(wrap_pyfunction!(sign, m)?)?;
146+
m.add_function(wrap_pyfunction!(verify, m)?)?;
51147
Ok(())
52148
}

0 commit comments

Comments
 (0)