Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert Poly1305 to Rust #8788

Merged
merged 1 commit into from
May 26, 2023
Merged
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: 0 additions & 11 deletions src/cryptography/hazmat/backends/openssl/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@
_EllipticCurvePrivateKey,
_EllipticCurvePublicKey,
)
from cryptography.hazmat.backends.openssl.poly1305 import (
_POLY1305_KEY_SIZE,
_Poly1305Context,
)
from cryptography.hazmat.backends.openssl.rsa import (
_RSAPrivateKey,
_RSAPublicKey,
Expand Down Expand Up @@ -1949,13 +1945,6 @@ def poly1305_supported(self) -> bool:
return False
return self._lib.Cryptography_HAS_POLY1305 == 1

def create_poly1305_ctx(self, key: bytes) -> _Poly1305Context:
utils._check_byteslike("key", key)
if len(key) != _POLY1305_KEY_SIZE:
raise ValueError("A poly1305 key is 32 bytes long")

return _Poly1305Context(self, key)

def pkcs7_supported(self) -> bool:
return not self._lib.CRYPTOGRAPHY_IS_BORINGSSL

Expand Down
69 changes: 0 additions & 69 deletions src/cryptography/hazmat/backends/openssl/poly1305.py

This file was deleted.

2 changes: 2 additions & 0 deletions src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ from cryptography.hazmat.bindings._rust.openssl import (
hashes,
hmac,
kdf,
poly1305,
x448,
x25519,
)
Expand All @@ -24,6 +25,7 @@ __all__ = [
"kdf",
"ed448",
"ed25519",
"poly1305",
"x448",
"x25519",
]
Expand Down
13 changes: 13 additions & 0 deletions src/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 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.

class Poly1305:
def __init__(self, key: bytes) -> None: ...
@staticmethod
def generate_tag(key: bytes, data: bytes) -> bytes: ...
@staticmethod
def verify_tag(key: bytes, data: bytes, tag: bytes) -> None: ...
def update(self, data: bytes) -> None: ...
def finalize(self) -> bytes: ...
def verify(self, tag: bytes) -> None: ...
57 changes: 3 additions & 54 deletions src/cryptography/hazmat/primitives/poly1305.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,8 @@

from __future__ import annotations

import typing
from cryptography.hazmat.bindings._rust import openssl as rust_openssl

from cryptography import utils
from cryptography.exceptions import (
AlreadyFinalized,
UnsupportedAlgorithm,
_Reasons,
)
from cryptography.hazmat.backends.openssl.poly1305 import _Poly1305Context
__all__ = ["Poly1305"]


class Poly1305:
_ctx: typing.Optional[_Poly1305Context]

def __init__(self, key: bytes):
from cryptography.hazmat.backends.openssl.backend import backend

if not backend.poly1305_supported():
raise UnsupportedAlgorithm(
"poly1305 is not supported by this version of OpenSSL.",
_Reasons.UNSUPPORTED_MAC,
)
self._ctx = backend.create_poly1305_ctx(key)

def update(self, data: bytes) -> None:
if self._ctx is None:
raise AlreadyFinalized("Context was already finalized.")
utils._check_byteslike("data", data)
self._ctx.update(data)

def finalize(self) -> bytes:
if self._ctx is None:
raise AlreadyFinalized("Context was already finalized.")
mac = self._ctx.finalize()
self._ctx = None
return mac

def verify(self, tag: bytes) -> None:
utils._check_bytes("tag", tag)
if self._ctx is None:
raise AlreadyFinalized("Context was already finalized.")

ctx, self._ctx = self._ctx, None
ctx.verify(tag)

@classmethod
def generate_tag(cls, key: bytes, data: bytes) -> bytes:
p = Poly1305(key)
p.update(data)
return p.finalize()

@classmethod
def verify_tag(cls, key: bytes, data: bytes, tag: bytes) -> None:
p = Poly1305(key)
p.update(data)
p.verify(tag)
Poly1305 = rust_openssl.poly1305.Poly1305
2 changes: 1 addition & 1 deletion src/rust/src/backend/hmac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl Hmac {
let actual = self.finalize(py)?.as_bytes();
if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) {
return Err(CryptographyError::from(
exceptions::InvalidSignature::new_err(("Signature did not match digest.",)),
exceptions::InvalidSignature::new_err("Signature did not match digest."),
));
}

Expand Down
3 changes: 3 additions & 0 deletions src/rust/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub(crate) mod ed448;
pub(crate) mod hashes;
pub(crate) mod hmac;
pub(crate) mod kdf;
pub(crate) mod poly1305;
pub(crate) mod utils;
#[cfg(any(not(CRYPTOGRAPHY_IS_LIBRESSL), CRYPTOGRAPHY_LIBRESSL_370_OR_GREATER))]
pub(crate) mod x25519;
Expand All @@ -29,6 +30,8 @@ pub(crate) fn add_to_module(module: &pyo3::prelude::PyModule) -> pyo3::PyResult<
#[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))]
module.add_submodule(x448::create_module(module.py())?)?;

module.add_submodule(poly1305::create_module(module.py())?)?;

module.add_submodule(hashes::create_module(module.py())?)?;
module.add_submodule(hmac::create_module(module.py())?)?;
module.add_submodule(kdf::create_module(module.py())?)?;
Expand Down
127 changes: 127 additions & 0 deletions src/rust/src/backend/poly1305.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// 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 crate::backend::hashes::already_finalized_error;
use crate::buf::CffiBuf;
use crate::error::{CryptographyError, CryptographyResult};
use crate::exceptions;

#[pyo3::prelude::pyclass(module = "cryptography.hazmat.bindings._rust.openssl.poly1305")]
struct Poly1305 {
signer: Option<openssl::sign::Signer<'static>>,
}

impl Poly1305 {
fn get_mut_signer(&mut self) -> CryptographyResult<&mut openssl::sign::Signer<'static>> {
if let Some(signer) = self.signer.as_mut() {
return Ok(signer);
};
Err(already_finalized_error())
}
}

#[pyo3::pymethods]
impl Poly1305 {
#[new]
fn new(key: CffiBuf<'_>) -> CryptographyResult<Poly1305> {
#[cfg(any(CRYPTOGRAPHY_IS_LIBRESSL, CRYPTOGRAPHY_IS_BORINGSSL))]
{
return Err(CryptographyError::from(
exceptions::UnsupportedAlgorithm::new_err((
"poly1305 is not supported by this version of OpenSSL.",
exceptions::Reasons::UNSUPPORTED_MAC,
)),
));
}

#[cfg(all(not(CRYPTOGRAPHY_IS_LIBRESSL), not(CRYPTOGRAPHY_IS_BORINGSSL)))]
{
if cryptography_openssl::fips::is_enabled() {
return Err(CryptographyError::from(
exceptions::UnsupportedAlgorithm::new_err((
"poly1305 is not supported by this version of OpenSSL.",
exceptions::Reasons::UNSUPPORTED_MAC,
)),
));
}

let pkey = openssl::pkey::PKey::private_key_from_raw_bytes(
key.as_bytes(),
openssl::pkey::Id::POLY1305,
)
.map_err(|_| {
pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long")
})?;

Ok(Poly1305 {
signer: Some(
openssl::sign::Signer::new_without_digest(&pkey).map_err(|_| {
pyo3::exceptions::PyValueError::new_err("A poly1305 key is 32 bytes long")
})?,
),
})
}
}

#[staticmethod]
fn generate_tag<'p>(
py: pyo3::Python<'p>,
key: CffiBuf<'_>,
data: CffiBuf<'_>,
) -> CryptographyResult<&'p pyo3::types::PyBytes> {
let mut p = Poly1305::new(key)?;
p.update(data)?;
p.finalize(py)
}

#[staticmethod]
fn verify_tag(
py: pyo3::Python<'_>,
key: CffiBuf<'_>,
data: CffiBuf<'_>,
tag: &[u8],
) -> CryptographyResult<()> {
let mut p = Poly1305::new(key)?;
p.update(data)?;
p.verify(py, tag)
}

fn update(&mut self, data: CffiBuf<'_>) -> CryptographyResult<()> {
self.get_mut_signer()?.update(data.as_bytes())?;
Ok(())
}

fn finalize<'p>(
&mut self,
py: pyo3::Python<'p>,
) -> CryptographyResult<&'p pyo3::types::PyBytes> {
let signer = self.get_mut_signer()?;
let result = pyo3::types::PyBytes::new_with(py, signer.len()?, |b| {
let n = signer.sign(b).unwrap();
assert_eq!(n, b.len());
Ok(())
})?;
self.signer = None;
Ok(result)
}

fn verify(&mut self, py: pyo3::Python<'_>, signature: &[u8]) -> CryptographyResult<()> {
let actual = self.finalize(py)?.as_bytes();
if actual.len() != signature.len() || !openssl::memcmp::eq(actual, signature) {
return Err(CryptographyError::from(
exceptions::InvalidSignature::new_err("Value did not match computed tag."),
));
}

Ok(())
}
}

pub(crate) fn create_module(py: pyo3::Python<'_>) -> pyo3::PyResult<&pyo3::prelude::PyModule> {
let m = pyo3::prelude::PyModule::new(py, "poly1305")?;

m.add_class::<Poly1305>()?;

Ok(m)
}