1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! An implementation of Paseto v2 "public" tokens, or tokens that
//! are signed with a public/private key pair.

use crate::errors::GenericError;
use crate::pae::pae;

use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
use failure::Error;
use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals;
use ring::signature::{Ed25519KeyPair, UnparsedPublicKey, ED25519};

const HEADER: &str = "v2.public.";

/// Sign a "v2.public" paseto token.
///
/// Returns a result of a string if signing was successful.
pub fn public_paseto(msg: &str, footer: Option<&str>, key_pair: &Ed25519KeyPair) -> Result<String, Error> {
  let footer_frd = footer.unwrap_or("");

  let pre_auth = pae(&[HEADER.as_bytes(), msg.as_bytes(), footer_frd.as_bytes()]);

  let sig = key_pair.sign(&pre_auth);
  let mut m_and_sig = Vec::from(msg.as_bytes());
  m_and_sig.extend_from_slice(sig.as_ref());

  let token = if footer_frd.is_empty() {
    format!("{}{}", HEADER, encode_config(&m_and_sig, URL_SAFE_NO_PAD))
  } else {
    format!(
      "{}{}.{}",
      HEADER,
      encode_config(&m_and_sig, URL_SAFE_NO_PAD),
      encode_config(footer_frd.as_bytes(), URL_SAFE_NO_PAD)
    )
  };

  Ok(token)
}

/// Verifies a "v2.public" paseto token based on a given key pair.
///
/// Returns the message if verification was successful, otherwise an Err().
pub fn verify_paseto(token: &str, footer: Option<&str>, public_key: &[u8]) -> Result<String, Error> {
  let token_parts = token.split(".").collect::<Vec<_>>();
  if token_parts.len() < 3 {
    return Err(GenericError::InvalidToken {})?;
  }

  let has_provided_footer = footer.is_some();
  let footer_as_str = footer.unwrap_or("");

  if has_provided_footer {
    if token_parts.len() < 4 {
      return Err(GenericError::InvalidFooter {})?;
    }
    let footer_encoded = encode_config(footer_as_str.as_bytes(), URL_SAFE_NO_PAD);

    if ConstantTimeEquals(footer_encoded.as_bytes(), token_parts[3].as_bytes()).is_err() {
      return Err(GenericError::InvalidFooter {})?;
    }
  }

  if token_parts[0] != "v2" || token_parts[1] != "public" {
    return Err(GenericError::InvalidToken {})?;
  }

  let decoded = decode_config(token_parts[2].as_bytes(), URL_SAFE_NO_PAD)?;
  let decoded_len = decoded.len();
  let (msg, sig) = decoded.split_at(decoded_len - 64);

  let pre_auth = pae(&[HEADER.as_bytes(), msg, footer_as_str.as_bytes()]);

  let pk_unparsed = UnparsedPublicKey::new(&ED25519, public_key);
  let verify_res = pk_unparsed.verify(&pre_auth, sig);
  if verify_res.is_err() {
    return Err(GenericError::InvalidToken {})?;
  }

  Ok(String::from_utf8(Vec::from(msg))?)
}

#[cfg(test)]
mod unit_tests {
  use super::*;

  use ring::rand::SystemRandom;
  use ring::signature::KeyPair;

  #[test]
  fn paseto_public_verify() {
    let sys_rand = SystemRandom::new();
    let key_pkcs8 = Ed25519KeyPair::generate_pkcs8(&sys_rand).expect("Failed to generate pkcs8 key!");
    let as_key = Ed25519KeyPair::from_pkcs8(key_pkcs8.as_ref()).expect("Failed to parse keypair");

    // Test messages without footers.
    let public_token_one = public_paseto("msg", None, &as_key).expect("Failed to public encode msg with no footer!");
    // NOTE: This test is just ensuring we can encode a json object, remember these internal impls
    // don't check for expires being valid!
    let public_token_two = public_paseto(
      "{\"data\": \"yo bro\", \"expires\": \"2018-01-01T00:00:00+00:00\"}",
      None,
      &as_key,
    )
    .expect("Failed to public encode json blob with no footer!");

    assert!(public_token_one.starts_with("v2.public."));
    assert!(public_token_two.starts_with("v2.public."));

    let verified_one = verify_paseto(&public_token_one.clone(), None, as_key.public_key().as_ref());
    let verified_two = verify_paseto(&public_token_two, None, as_key.public_key().as_ref());

    // Verify the above tokens.
    assert!(verified_one.is_ok());
    assert!(verified_two.is_ok());
    assert_eq!(verified_one.unwrap(), "msg");
    assert_eq!(
      verified_two.unwrap(),
      "{\"data\": \"yo bro\", \"expires\": \"2018-01-01T00:00:00+00:00\"}"
    );

    let should_not_verify_one = verify_paseto(&public_token_one, Some("data"), as_key.public_key().as_ref());

    // Verify if it doesn't have a footer in public that it won't pass a verification with a footer.
    assert!(should_not_verify_one.is_err());

    // Now lets verify with footers.
    let public_token_three =
      public_paseto("msg", Some("footer"), &as_key).expect("Failed to public encode msg with footer!");
    let public_token_four = public_paseto(
      "{\"data\": \"yo bro\", \"expires\": \"2018-01-01T00:00:00+00:00\"}",
      Some("footer"),
      &as_key,
    )
    .expect("Failed to public encode json blob with footer!");

    assert!(public_token_three.starts_with("v2.public."));
    assert!(public_token_four.starts_with("v2.public."));

    let verified_three = verify_paseto(&public_token_three, Some("footer"), as_key.public_key().as_ref());
    let verified_four = verify_paseto(&public_token_four, Some("footer"), as_key.public_key().as_ref());

    // Verify the footer tokens.
    assert!(verified_three.is_ok());
    assert!(verified_four.is_ok());
    assert_eq!(verified_three.unwrap(), "msg");
    assert_eq!(
      verified_four.unwrap(),
      "{\"data\": \"yo bro\", \"expires\": \"2018-01-01T00:00:00+00:00\"}"
    );

    // Validate no footer + invalid footer both fail on tokens encode with footer.
    let should_not_verify_two = verify_paseto(&public_token_three, None, as_key.public_key().as_ref());
    let should_not_verify_three = verify_paseto(&public_token_three, Some("bleh"), as_key.public_key().as_ref());

    assert!(should_not_verify_two.is_err());
    assert!(should_not_verify_three.is_err());
  }
}