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

Doc improvements #18

Merged
merged 5 commits into from
Jun 6, 2024
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
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@ permissions:
contents: read

jobs:
rustfmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: rustfmt
- name: Rustfmt Check
uses: actions-rust-lang/rustfmt@v1

clippy:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: clippy
- name: Clippy Check
run: cargo clippy --all-targets --all-features

test:
runs-on: ubuntu-latest
timeout-minutes: 10
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[package]
name = "mauth-core"
version = "0.4.1"
version = "0.5.0"
edition = "2021"
authors = ["Medidata Solutions <support@mdsol.com>"]
description = "Generate and verify Medidata MAuth protocol signatures"
readme = "README.md"
license = "MIT"
homepage = "https://github.com/mdsol/mauth-core"
repository = "https://github.com/mdsol/mauth-core"
documentation = "https://docs.rs/mauth-core/"
keywords = ["security", "authentication"]
categories = ["authentication"]

Expand Down
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,35 @@ Add the following to your `Cargo.toml`:

```toml
[dependencies]
mauth-core = "0.4"
mauth-core = "0.5"
```

Here is an example of generating and verifying a signature:

```rust
use mauth_core::signer::Signer;
use mauth_core::verifier::Verifier;

let mauth_version = 2

let signer = Signer::new(app_uuid, private_key_data)?;
let signiture = signer.sign_string(mauth_version, verb, path, query, body, timestamp)?

let verifier = Verifier::new(app_uuid, public_key_data)?;
let is_valid = verifier.verify_signature(mauth_version, verb, path, query, body, timestamp, signature)?;
use mauth_core::error::Error;

let mauth_version = 2;
let private_key_data = std::fs::read_to_string("tests/mauth-protocol-test-suite/signing-params/rsa-key").unwrap();
let public_key_data = std::fs::read_to_string("tests/mauth-protocol-test-suite/signing-params/rsa-key-pub").unwrap();
let app_uuid = "101c139a-236c-11ef-b5e3-125eb8485a60".to_string();
let verb = "GET";
let path = "/item";
let query = "page=2";
let body = b"";
let timestamp = "2024-01-28T19:11:35.000";

let signer = Signer::new(app_uuid.clone(), private_key_data);
assert!(signer.is_ok());
let signature = signer.unwrap().sign_string(mauth_version, verb, path, query, body, timestamp);
assert!(signature.is_ok());

let verifier = Verifier::new(app_uuid.clone(), public_key_data);
assert!(verifier.is_ok());
let result = verifier.unwrap().verify_signature(mauth_version, verb, path, query, body, timestamp, signature.unwrap());
assert!(result.is_ok());
```

You can find an example of binding MAuth Core to Ruby [here](./doc/binding_to_ruby.md).
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

/// Error types
pub mod error;
pub(crate) mod signable;
Expand Down
24 changes: 24 additions & 0 deletions src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use rsa::pkcs1::DecodeRsaPrivateKey;
use rsa::RsaPrivateKey;
use sha2::Sha512;

/// Used to sign outgoing requests. Struct can be initialized once and used to sign many requests.
#[derive(Debug, Clone)]
pub struct Signer {
app_uuid: String,
Expand All @@ -12,6 +13,19 @@ pub struct Signer {
}

impl Signer {
/// Initialize a new signer with the app UUID and the private key. The format of the private key
/// should be a raw RSA private key in the PKCS1 PEM format, as generated by `openssl genrsa`. An
/// error will be returned if the input data is unable to be parsed as a private key. The `app_uuid`
/// is expected to be a valid UUID, however this is not checked. If you pass something other than
/// a valid UUID, no error will be returned, but none of the created signatures will be able to
/// be validated by other MAuth verifiers.
///
/// ```
/// # use mauth_core::signer::Signer;
/// # let private_key = std::fs::read_to_string("tests/mauth-protocol-test-suite/signing-params/rsa-key").unwrap();
/// let signer = Signer::new("101c139a-236c-11ef-b5e3-125eb8485a60", private_key);
/// assert!(signer.is_ok());
/// ```
pub fn new(app_uuid: impl Into<String>, private_key_data: String) -> Result<Self, Error> {
let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key_data)?;
let signing_key = rsa::pkcs1v15::SigningKey::<Sha512>::new(private_key.to_owned());
Expand All @@ -23,6 +37,16 @@ impl Signer {
})
}

/// This function will generate a valid MAuth signature string of the specified version, or error
/// if it is unable to.
///
/// ```
/// # use mauth_core::signer::Signer;
/// # let private_key = std::fs::read_to_string("tests/mauth-protocol-test-suite/signing-params/rsa-key").unwrap();
/// # let signer = Signer::new("101c139a-236c-11ef-b5e3-125eb8485a60", private_key).unwrap();
/// let result = signer.sign_string(2, "GET", "/item", "page=2", b"", "2024-01-28T19:11:35.000");
/// assert!(result.is_ok());
/// ```
pub fn sign_string(
&self,
version: u8,
Expand Down
41 changes: 31 additions & 10 deletions src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rsa::pkcs8::DecodePublicKey;
use rsa::RsaPublicKey;
use sha2::Sha512;

/// Used to verify incoming requests. Struct can be initialized once and used to verify many requests.
#[derive(Debug, Clone)]
pub struct Verifier {
app_uuid: String,
Expand All @@ -13,6 +14,18 @@ pub struct Verifier {
}

impl Verifier {
/// Initialize a new verifier with the source app UUID and the public key. The format of the public key
/// should be a raw RSA public key in the public key PEM format, as generated by `openssl rsa -pubout -out`.
/// An error will be returned if the input data is unable to be parsed as a public key. The `app_uuid`
/// is expected to be a valid UUID, however this is not checked. If you pass something other than
/// a valid UUID, no error will be returned, but none of the signatures will be able to be validated.
///
/// ```
/// # use mauth_core::verifier::Verifier;
/// # let public_key = std::fs::read_to_string("tests/mauth-protocol-test-suite/signing-params/rsa-key-pub").unwrap();
/// let verifier = Verifier::new("101c139a-236c-11ef-b5e3-125eb8485a60", public_key);
/// assert!(verifier.is_ok());
/// ```
pub fn new(app_uuid: impl Into<String>, public_key_data: String) -> Result<Self, Error> {
let public_key = RsaPublicKey::from_public_key_pem(&public_key_data)?;
let verifying_key = rsa::pkcs1v15::VerifyingKey::<Sha512>::new(public_key.to_owned());
Expand All @@ -24,6 +37,22 @@ impl Verifier {
})
}

/// This function will verify that a provided signature is valid given the uuid and public key the
/// struct was constructed with, the request properties passed into the function, and the signature
/// passed in. It will return Ok(()) if the signature validates successfully, and Err if it does
/// not. It is the responsibility of the consuming crate and application to use these cases to
/// determine whether to process a request further, or return error information.
///
/// ```
/// # use mauth_core::verifier::Verifier;
/// # use mauth_core::error::Error;
/// # let public_key = std::fs::read_to_string("tests/mauth-protocol-test-suite/signing-params/rsa-key-pub").unwrap();
/// # let verifier = Verifier::new("101c139a-236c-11ef-b5e3-125eb8485a60", public_key).unwrap();
/// let result = verifier.verify_signature(2, "GET", "/item", "page=2", b"", "2024-01-28T19:11:35.000", "");
/// // Passing in an empty signature, so it will result in a verification error
/// assert!(matches!(result, Err(Error::SignatureVerifyError(_))));
/// ```
#[allow(clippy::too_many_arguments)]
pub fn verify_signature(
&self,
version: u8,
Expand All @@ -43,11 +72,7 @@ impl Verifier {
}
}

fn verify_signature_v1(
&self,
signable: &Signable,
signature: String,
) -> Result<(), Error> {
fn verify_signature_v1(&self, signable: &Signable, signature: String) -> Result<(), Error> {
self.public_key.verify(
rsa::Pkcs1v15Sign::new_unprefixed(),
&signable.signing_string_v1()?,
Expand All @@ -57,11 +82,7 @@ impl Verifier {
Ok(())
}

fn verify_signature_v2(
&self,
signable: &Signable,
signature: String,
) -> Result<(), Error> {
fn verify_signature_v2(&self, signable: &Signable, signature: String) -> Result<(), Error> {
use rsa::signature::Verifier;

let signature =
Expand Down