Skip to content
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.6.1
- Accept RSA public keys encoded as either PKCS#1 (`BEGIN RSA PUBLIC KEY`) or
SubjectPublicKeyInfo (`BEGIN PUBLIC KEY`) PEM.

# 0.6.0
- Normalize provided private key string to the correct PEM format.
- Update thiserror to version 2.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mauth-core"
version = "0.6.0"
version = "0.6.1"
edition = "2021"
authors = ["Medidata Solutions <support@mdsol.com>"]
description = "Generate and verify Medidata MAuth protocol signatures"
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ Add the following to your `Cargo.toml`:

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

`Signer::new` accepts PKCS#1 RSA private key PEM. `Verifier::new` accepts RSA
public key PEM encoded as either SubjectPublicKeyInfo (`BEGIN PUBLIC KEY`) or
PKCS#1 (`BEGIN RSA PUBLIC KEY`).

Here is an example of generating and verifying a signature:

```rust
Expand Down
3 changes: 3 additions & 0 deletions factbook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ spec:
type: library
lifecycle: production
owner: ae@mdsol.com
experience: enablement
metadata:
json_schema: https://github.com/mdsol/platform-standards/tree/main/schemas/v1alpha1.schema.json
name: mauth-core
Expand All @@ -15,3 +16,5 @@ metadata:
people:
- role: technical owner
email: ykitamura@mdsol.com
annotations:
product: N/A
8 changes: 4 additions & 4 deletions src/signable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ impl<'a> Signable<'a> {
self.verb,
Self::normalize_url(&self.path),
body_digest,
&self.app_uuid,
&self.timestamp,
&encoded_query
self.app_uuid,
self.timestamp,
encoded_query
)
.into_bytes())
}
Expand All @@ -88,7 +88,7 @@ impl<'a> Signable<'a> {
.join("&"))
}

fn normalize_url(url: &str) -> Cow<str> {
fn normalize_url(url: &str) -> Cow<'_, str> {
if url.contains("//") || url.contains('%') || url.contains("/.") {
let url = SQUEEZE_REGEX.replace_all(url, "/");
let url = PERCENT_CASE_REGEX.replace_all(&url, |c: &Captures| c[0].to_uppercase());
Expand Down
70 changes: 64 additions & 6 deletions src/verifier.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{error::Error, signable::Signable};
use base64::{engine::general_purpose, Engine as _};
use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::pkcs1v15::Signature;
use rsa::pkcs8::DecodePublicKey;
use rsa::RsaPublicKey;
Expand All @@ -14,11 +15,14 @@ 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.
/// Initialize a new verifier with the source app UUID and public key.
///
/// The public key can be either SubjectPublicKeyInfo PEM (`BEGIN PUBLIC KEY`), as generated by
/// `openssl rsa -pubout`, or PKCS#1 PEM (`BEGIN RSA PUBLIC KEY`), as generated by
/// `openssl rsa -RSAPublicKey_out`. An error will be returned if the input data cannot be parsed
/// as either public key format. 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;
Expand All @@ -27,7 +31,9 @@ impl Verifier {
/// 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 public_key = RsaPublicKey::from_public_key_pem(&public_key_data).or_else(|_| {
RsaPublicKey::from_pkcs1_pem(&public_key_data).map_err(spki::Error::from)
})?;
let verifying_key = rsa::pkcs1v15::VerifyingKey::<Sha512>::new(public_key.to_owned());

Ok(Self {
Expand Down Expand Up @@ -93,3 +99,55 @@ impl Verifier {
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use rsa::pkcs1::EncodeRsaPublicKey;
use rsa::pkcs8::{EncodePublicKey, LineEnding};
use rsa::BigUint;

const APP_UUID: &str = "101c139a-236c-11ef-b5e3-125eb8485a60";

fn public_key() -> RsaPublicKey {
let modulus = BigUint::from_bytes_be(&[0xff; 256]);
let public_exponent = BigUint::from(65_537_u32);
RsaPublicKey::new(modulus, public_exponent).unwrap()
}

#[test]
fn new_accepts_subject_public_key_info_pem() {
let public_key = public_key();
let public_key_pem = public_key.to_public_key_pem(LineEnding::LF).unwrap();

let verifier = Verifier::new(APP_UUID, public_key_pem).unwrap();

assert_eq!(verifier.public_key, public_key);
}

#[test]
fn new_accepts_pkcs1_public_key_pem() {
let public_key = public_key();
let public_key_pem = public_key.to_pkcs1_pem(LineEnding::LF).unwrap();

let verifier = Verifier::new(APP_UUID, public_key_pem).unwrap();

assert_eq!(verifier.public_key, public_key);
}

#[test]
fn new_rejects_malformed_public_key_pem_as_a_public_key_error() {
let malformed_public_keys = [
"not a PEM key",
"-----BEGIN PUBLIC KEY-----\ninvalid\n-----END PUBLIC KEY-----",
"-----BEGIN RSA PUBLIC KEY-----\ninvalid\n-----END RSA PUBLIC KEY-----",
];

for malformed_public_key in malformed_public_keys {
assert!(matches!(
Verifier::new(APP_UUID, malformed_public_key.to_owned()),
Err(Error::PublicKeyDecodeError(_))
));
}
}
}
Loading