blob: 818faacfbaf03c955dca664de237ca73bb6cf183 [file] [edit]
// This file is dual licensed under the terms of the Apache License, Version
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.
use std::fmt;
use pyo3::types::PyListMethods;
use crate::exceptions;
pub enum CryptographyError {
Asn1Parse(asn1::ParseError),
Asn1Write(asn1::WriteError),
KeyParsing(asn1::ParseError),
Py(pyo3::PyErr),
OpenSSL(openssl_bridge::Error),
}
impl From<asn1::ParseError> for CryptographyError {
fn from(e: asn1::ParseError) -> CryptographyError {
CryptographyError::Asn1Parse(e)
}
}
impl From<asn1::WriteError> for CryptographyError {
fn from(e: asn1::WriteError) -> CryptographyError {
CryptographyError::Asn1Write(e)
}
}
impl From<pyo3::PyErr> for CryptographyError {
fn from(e: pyo3::PyErr) -> CryptographyError {
CryptographyError::Py(e)
}
}
impl From<pyo3::CastError<'_, '_>> for CryptographyError {
fn from(e: pyo3::CastError<'_, '_>) -> CryptographyError {
CryptographyError::Py(e.into())
}
}
impl From<pyo3::CastIntoError<'_>> for CryptographyError {
fn from(e: pyo3::CastIntoError<'_>) -> CryptographyError {
CryptographyError::Py(e.into())
}
}
impl From<pyo3::pyclass::PyClassGuardError<'_, '_>> for CryptographyError {
fn from(e: pyo3::pyclass::PyClassGuardError<'_, '_>) -> CryptographyError {
CryptographyError::Py(e.into())
}
}
impl From<openssl_bridge::Error> for CryptographyError {
fn from(error: openssl_bridge::Error) -> Self {
Self::OpenSSL(error)
}
}
impl From<openssl_bridge::containers::Pkcs12Error> for CryptographyError {
fn from(error: openssl_bridge::containers::Pkcs12Error) -> Self {
use openssl_bridge::containers::Pkcs12Error;
match error {
Pkcs12Error::Encoding(_) => {
pyo3::exceptions::PyValueError::new_err("Could not deserialize PKCS12 data").into()
}
Pkcs12Error::PasswordOrData(_) => {
pyo3::exceptions::PyValueError::new_err("Invalid password or PKCS12 data").into()
}
Pkcs12Error::Output(error) => error.into(),
}
}
}
impl From<pem::PemError> for CryptographyError {
fn from(e: pem::PemError) -> CryptographyError {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(format!(
"Unable to load PEM file. See https://cryptography.io/en/latest/faq/#why-can-t-i-import-my-pem-file for more details. {e:?}"
)))
}
}
impl From<cryptography_key_parsing::KeyParsingError> for CryptographyError {
fn from(e: cryptography_key_parsing::KeyParsingError) -> CryptographyError {
match e {
cryptography_key_parsing::KeyParsingError::Parse(e) => CryptographyError::KeyParsing(e),
cryptography_key_parsing::KeyParsingError::Bridge(openssl_bridge::Error::InvalidInput(message)) => {
pyo3::exceptions::PyValueError::new_err(message).into()
}
cryptography_key_parsing::KeyParsingError::Bridge(e) => e.into(),
cryptography_key_parsing::KeyParsingError::InvalidKey => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err("Invalid key"))
}
cryptography_key_parsing::KeyParsingError::ExplicitCurveUnsupported => {
CryptographyError::Py(exceptions::UnsupportedAlgorithm::new_err((
"ECDSA keys with explicit parameters are only supported when they map to secp256r1, secp384r1, or secp521r1. No custom curves are supported.",
exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE,
)))
}
cryptography_key_parsing::KeyParsingError::UnsupportedKeyType(oid) => {
CryptographyError::Py(exceptions::UnsupportedAlgorithm::new_err((
format!("Unknown key type: {oid}"),
exceptions::Reasons::UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
)))
}
cryptography_key_parsing::KeyParsingError::UnsupportedEllipticCurve(oid) => {
CryptographyError::Py(exceptions::UnsupportedAlgorithm::new_err((
format!("Curve {oid} is not supported"),
exceptions::Reasons::UNSUPPORTED_ELLIPTIC_CURVE,
)))
}
cryptography_key_parsing::KeyParsingError::UnsupportedEncryptionAlgorithm(oid) => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(format!(
"Unknown key encryption algorithm: {oid}"
)))
}
cryptography_key_parsing::KeyParsingError::EncryptedKeyWithoutPassword => {
CryptographyError::Py(pyo3::exceptions::PyTypeError::new_err(
"Password was not given but private key is encrypted",
))
}
cryptography_key_parsing::KeyParsingError::IncorrectPassword => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(
"Incorrect password, could not decrypt key",
))
}
cryptography_key_parsing::KeyParsingError::TruncatedEcPrivateKey => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(
"EC private key is not encoded properly: private key value is too short. Please file an issue at https://github.com/pyca/cryptography/issues explaining how your private key was created.",
))
}
cryptography_key_parsing::KeyParsingError::PemMissingDekInfo => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(
"Encrypted PEM doesn't have a DEK-Info header.",
))
}
cryptography_key_parsing::KeyParsingError::PemInvalidDekInfo => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(
"Encrypted PEM's DEK-Info header is not valid.",
))
}
cryptography_key_parsing::KeyParsingError::PemInvalidIv => CryptographyError::Py(
pyo3::exceptions::PyValueError::new_err("DEK-Info IV is not valid hex"),
),
cryptography_key_parsing::KeyParsingError::PemUnableToDeriveKey => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(
"Unable to derive key from password (are you in FIPS mode?)",
))
}
cryptography_key_parsing::KeyParsingError::PemUnsupportedCipher => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(
"Key encrypted with unknown cipher.",
))
}
cryptography_key_parsing::KeyParsingError::PemInvalidProcType => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(
"Proc-Type PEM header is not valid, key could not be decrypted.",
))
}
}
}
}
impl From<cryptography_key_parsing::KeySerializationError> for CryptographyError {
fn from(e: cryptography_key_parsing::KeySerializationError) -> CryptographyError {
match e {
cryptography_key_parsing::KeySerializationError::PasswordMustBeUtf8 => {
CryptographyError::Py(pyo3::exceptions::PyValueError::new_err(
"password must be valid UTF-8",
))
}
cryptography_key_parsing::KeySerializationError::Write(e) => {
CryptographyError::Asn1Write(e)
}
cryptography_key_parsing::KeySerializationError::Bridge(e) => e.into(),
}
}
}
pub(crate) fn list_from_bridge_error<'p>(
py: pyo3::Python<'p>,
error: &openssl_bridge::Error,
) -> pyo3::PyResult<pyo3::Bound<'p, pyo3::types::PyList>> {
let errors = pyo3::types::PyList::empty(py);
if let openssl_bridge::Error::Native(native) = error {
for e in native {
errors.append(pyo3::Bound::new(py, OpenSSLError { e: e.clone() })?)?;
}
}
Ok(errors)
}
impl fmt::Display for CryptographyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CryptographyError::Asn1Parse(asn1_error) => {
write!(f, "error parsing asn1 value: {asn1_error:?}")
}
CryptographyError::Asn1Write(asn1::WriteError::AllocationError) => {
write!(
f,
"failed to allocate memory while performing ASN.1 serialization"
)
}
CryptographyError::Asn1Write(asn1::WriteError::InvalidSetOrdering) => {
write!(
f,
"invalid SET ordering while performing ASN.1 serialization"
)
}
CryptographyError::KeyParsing(asn1_error) => {
write!(
f,
"Could not deserialize key data. The data may be in an incorrect format, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters). Details: {asn1_error}",
)
}
CryptographyError::Py(py_error) => write!(f, "{py_error}"),
CryptographyError::OpenSSL(error_stack) => {
write!(
f,
"Unknown OpenSSL error. This error is commonly encountered
when another library is not cleaning up the OpenSSL error
stack. If you are using cryptography with another library
that uses OpenSSL try disabling it before reporting a bug.
Otherwise please file an issue at
https://github.com/pyca/cryptography/issues with
information on how to reproduce this. ({error_stack})"
)
}
}
}
}
impl From<CryptographyError> for pyo3::PyErr {
fn from(e: CryptographyError) -> pyo3::PyErr {
match e {
CryptographyError::Asn1Parse(_) | CryptographyError::KeyParsing(_) => {
pyo3::exceptions::PyValueError::new_err(e.to_string())
}
CryptographyError::Asn1Write(asn1::WriteError::AllocationError) => {
pyo3::exceptions::PyMemoryError::new_err(e.to_string())
}
CryptographyError::Asn1Write(asn1::WriteError::InvalidSetOrdering) => {
pyo3::exceptions::PyValueError::new_err(e.to_string())
}
CryptographyError::Py(py_error) => py_error,
CryptographyError::OpenSSL(ref error_stack) => {
pyo3::Python::attach(|py| match list_from_bridge_error(py, error_stack) {
Ok(errors) => {
exceptions::InternalError::new_err((e.to_string(), errors.unbind()))
}
// NO-COVERAGE-START
// Building the native error list can fail only while allocating
// Python-owned error records.
Err(error) => error,
// NO-COVERAGE-END
})
}
}
}
}
impl CryptographyError {
pub(crate) fn add_location(self, loc: asn1::ParseLocation) -> Self {
match self {
CryptographyError::Py(e) => CryptographyError::Py(e),
CryptographyError::Asn1Parse(e) => CryptographyError::Asn1Parse(e.add_location(loc)),
CryptographyError::KeyParsing(e) => CryptographyError::KeyParsing(e.add_location(loc)),
CryptographyError::Asn1Write(e) => CryptographyError::Asn1Write(e),
CryptographyError::OpenSSL(e) => CryptographyError::OpenSSL(e),
}
}
pub(crate) fn add_note(self, py: pyo3::Python<'_>, note: &str) -> Self {
let pyerr: pyo3::PyErr = self.into();
#[cfg(Py_3_11)]
{
// If we fail to add a note, silently ignore it.
_ = pyerr.add_note(py, note);
}
#[cfg(not(Py_3_11))]
{
_ = py;
_ = note;
}
Self::Py(pyerr)
}
}
// The primary purpose of this alias is for brevity to keep function signatures
// to a single-line as a work around for coverage issues. See
// https://github.com/pyca/cryptography/pull/6173
pub(crate) type CryptographyResult<T> = Result<T, CryptographyError>;
#[pyo3::pyfunction]
pub(crate) fn raise_openssl_error() -> crate::error::CryptographyResult<()> {
Err(CryptographyError::OpenSSL(openssl_bridge::Error::Native(
openssl_bridge::error::take_error_queue(),
)))
}
#[pyo3::pyclass(frozen, module = "cryptography.hazmat.bindings._rust.openssl")]
pub(crate) struct OpenSSLError {
e: openssl_bridge::error::NativeError,
}
#[pyo3::pymethods]
impl OpenSSLError {
#[getter]
fn lib(&self) -> i32 {
self.e.library
}
#[getter]
fn reason(&self) -> i32 {
self.e.reason
}
#[getter]
fn reason_text(&self) -> &[u8] {
self.e.reason_text.as_bytes()
}
fn __repr__<'py>(
&self,
py: pyo3::Python<'py>,
) -> pyo3::PyResult<pyo3::Bound<'py, pyo3::types::PyString>> {
pyo3::types::PyString::from_fmt(
py,
format_args!(
"<OpenSSLError(code={}, lib={}, reason={}, reason_text={})>",
self.e.code, self.e.library, self.e.reason, self.e.reason_text
),
)
}
}
#[pyo3::pyfunction]
pub(crate) fn capture_error_stack(
py: pyo3::Python<'_>,
) -> pyo3::PyResult<pyo3::Bound<'_, pyo3::types::PyList>> {
list_from_bridge_error(
py,
&openssl_bridge::Error::Native(openssl_bridge::error::take_error_queue()),
)
}
#[cfg(test)]
mod tests {
use pyo3::types::PyAnyMethods;
use pyo3::PyTypeInfo;
use super::CryptographyError;
#[test]
fn pkcs12_export_errors_preserve_backend_diagnostics() {
let native = openssl_bridge::Error::InvalidState("export failed");
let error: CryptographyError =
openssl_bridge::containers::Pkcs12Error::Output(native.clone()).into();
assert!(matches!(error, CryptographyError::OpenSSL(e) if e == native));
}
#[test]
fn test_cryptographyerror_display() {
pyo3::Python::initialize();
pyo3::Python::attach(|py| {
let py_error = pyo3::exceptions::PyRuntimeError::new_err("abc");
let e: CryptographyError = py_error.clone_ref(py).into();
assert!(e.to_string() == py_error.to_string());
let ordering_error = CryptographyError::Asn1Write(asn1::WriteError::InvalidSetOrdering);
assert!(
"invalid SET ordering while performing ASN.1 serialization"
== ordering_error.to_string()
);
})
}
#[test]
fn test_cryptographyerror_from() {
pyo3::Python::initialize();
pyo3::Python::attach(|py| {
let e: CryptographyError = asn1::WriteError::AllocationError.into();
assert!(matches!(
e,
CryptographyError::Asn1Write(asn1::WriteError::AllocationError)
));
let py_e: pyo3::PyErr = e.into();
assert!(py_e.is_instance_of::<pyo3::exceptions::PyMemoryError>(py));
let e: CryptographyError = asn1::WriteError::InvalidSetOrdering.into();
assert!(matches!(
e,
CryptographyError::Asn1Write(asn1::WriteError::InvalidSetOrdering)
));
let py_e: pyo3::PyErr = e.into();
assert!(py_e.is_instance_of::<pyo3::exceptions::PyValueError>(py));
let e: CryptographyError = pyo3::CastError::new(
py.None().bind(py).as_borrowed(),
pyo3::types::PyString::type_object(py).into_any(),
)
.into();
assert!(matches!(e, CryptographyError::Py(_)));
let e = cryptography_key_parsing::KeyParsingError::Bridge(
openssl_bridge::Error::Native(Vec::new()),
)
.into();
assert!(matches!(e, CryptographyError::OpenSSL(_)));
let e = pyo3::CastIntoError::new(
py.None().into_bound(py),
pyo3::types::PyString::type_object(py).into_any(),
)
.into();
assert!(matches!(e, CryptographyError::Py(_)));
let none = py.None();
let guard_err = none
.bind(py)
.extract::<crate::serialization::PrivateFormat>();
assert!(guard_err.is_err());
let e: CryptographyError = guard_err.err().unwrap().into();
assert!(matches!(e, CryptographyError::Py(_)));
})
}
#[test]
fn test_cryptographyerror_from_key_serialization_error() {
let e = cryptography_key_parsing::KeySerializationError::Write(
asn1::WriteError::AllocationError,
);
assert!(matches!(
CryptographyError::from(e),
CryptographyError::Asn1Write(asn1::WriteError::AllocationError)
));
pyo3::Python::initialize();
let e = cryptography_key_parsing::KeySerializationError::Bridge(
openssl_bridge::Error::Native(Vec::new()),
);
assert!(matches!(
CryptographyError::from(e),
CryptographyError::OpenSSL(_)
));
}
#[test]
fn test_cryptographyerror_add_location() {
let py_err = pyo3::PyErr::new::<pyo3::exceptions::PyValueError, _>("Error!");
CryptographyError::Py(py_err).add_location(asn1::ParseLocation::Field("meh"));
let asn1_write_err = asn1::WriteError::AllocationError;
CryptographyError::Asn1Write(asn1_write_err)
.add_location(asn1::ParseLocation::Field("meh"));
let openssl_error =
openssl_bridge::Error::Native(openssl_bridge::error::take_error_queue());
CryptographyError::OpenSSL(openssl_error).add_location(asn1::ParseLocation::Field("meh"));
let asn1_parse_error = asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue);
CryptographyError::KeyParsing(asn1_parse_error)
.add_location(asn1::ParseLocation::Field("meh"));
}
}