1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-29 22:01:14 +03:00

Sign all the things

This commit is contained in:
Quentin Gliech
2022-08-26 14:00:33 +02:00
parent ca125a14c5
commit 956556b0ff
44 changed files with 1479 additions and 48 deletions

View File

@ -6,10 +6,9 @@ edition = "2021"
license = "Apache-2.0"
[dependencies]
async-signature = "0.2.0"
anyhow = "1.0.62"
async-trait = "0.1.57"
base64ct = { version = "1.5.1", features = ["std"] }
base64ct = { version = "1.5.2", features = ["std"] }
chrono = { version = "0.4.22", features = ["serde"] }
crypto-mac = { version = "0.11.1", features = ["std"] }
digest = "0.10.3"
@ -18,11 +17,13 @@ elliptic-curve = { version = "0.12.3", features = ["ecdh", "pem"] }
futures-util = "0.3.23"
hmac = "0.12.1"
http = "0.2.8"
k256 = { version = "0.11.2", features = ["ecdsa", "pem", "pkcs8"] }
p256 = { version = "0.11.1", features = ["ecdsa", "pem", "pkcs8"] }
p384 = { version = "0.11.2", features = ["ecdsa", "pem", "pkcs8"] }
pkcs1 = { version = "0.4.0", features = ["pem", "pkcs8"] }
pkcs8 = { version = "0.9.0", features = ["pem", "std"] }
rand = "0.8.5"
rsa = { git = "https://github.com/RustCrypto/RSA.git" }
rsa = { git = "https://github.com/RustCrypto/RSA.git", features = ["std"] }
schemars = "0.8.10"
sec1 = "0.3.0"
serde = { version = "1.0.144", features = ["derive"] }
@ -37,6 +38,3 @@ tracing = "0.1.36"
url = { version = "2.2.2", features = ["serde"] }
mas-iana = { path = "../iana" }
[dev-dependencies]
mas-http = { path = "../http" }

View File

@ -0,0 +1,280 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::HashSet;
use futures_util::future::Either;
use mas_iana::jose::{JsonWebKeyType, JsonWebKeyUse, JsonWebSignatureAlg};
use crate::JsonWebSignatureHeader;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Constraint<'a> {
Alg {
constraint_alg: JsonWebSignatureAlg,
},
Algs {
constraint_algs: &'a [JsonWebSignatureAlg],
},
Kid {
constraint_kid: &'a str,
},
Use {
constraint_use: JsonWebKeyUse,
},
Kty {
constraint_kty: JsonWebKeyType,
},
}
impl<'a> Constraint<'a> {
#[must_use]
pub fn alg(constraint_alg: JsonWebSignatureAlg) -> Self {
Constraint::Alg { constraint_alg }
}
#[must_use]
pub fn algs(constraint_algs: &'a [JsonWebSignatureAlg]) -> Self {
Constraint::Algs { constraint_algs }
}
#[must_use]
pub fn kid(constraint_kid: &'a str) -> Self {
Constraint::Kid { constraint_kid }
}
#[must_use]
pub fn use_(constraint_use: JsonWebKeyUse) -> Self {
Constraint::Use { constraint_use }
}
#[must_use]
pub fn kty(constraint_kty: JsonWebKeyType) -> Self {
Constraint::Kty { constraint_kty }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConstraintDecision {
Positive,
Neutral,
Negative,
}
pub trait Constrainable {
/// List of available algorithms for this key
fn algs(&self) -> Option<Vec<JsonWebSignatureAlg>> {
None
}
/// Key ID (`kid`) of this key
fn kid(&self) -> Option<&str> {
None
}
/// Usage specified for this key
fn use_(&self) -> Option<JsonWebKeyUse> {
None
}
/// Key type (`kty`) of this key
fn kty(&self) -> JsonWebKeyType;
}
impl<L, R> Constrainable for Either<L, R>
where
L: Constrainable,
R: Constrainable,
{
fn algs(&self) -> Option<Vec<JsonWebSignatureAlg>> {
match self {
Either::Left(l) => l.algs(),
Either::Right(r) => r.algs(),
}
}
fn kid(&self) -> Option<&str> {
match self {
Either::Left(l) => l.kid(),
Either::Right(r) => r.kid(),
}
}
fn use_(&self) -> Option<JsonWebKeyUse> {
match self {
Either::Left(l) => l.use_(),
Either::Right(r) => r.use_(),
}
}
fn kty(&self) -> JsonWebKeyType {
match self {
Either::Left(l) => l.kty(),
Either::Right(r) => r.kty(),
}
}
}
impl<'a> Constraint<'a> {
fn decide<T: Constrainable>(&self, constrainable: &T) -> ConstraintDecision {
match self {
Constraint::Alg { constraint_alg } => {
if let Some(algs) = constrainable.algs() {
if algs.contains(constraint_alg) {
ConstraintDecision::Positive
} else {
ConstraintDecision::Negative
}
} else {
ConstraintDecision::Neutral
}
}
Constraint::Algs { constraint_algs } => {
if let Some(algs) = constrainable.algs() {
if algs.iter().any(|alg| constraint_algs.contains(alg)) {
ConstraintDecision::Positive
} else {
ConstraintDecision::Negative
}
} else {
ConstraintDecision::Neutral
}
}
Constraint::Kid { constraint_kid } => {
if let Some(kid) = constrainable.kid() {
if kid == *constraint_kid {
ConstraintDecision::Positive
} else {
ConstraintDecision::Negative
}
} else {
ConstraintDecision::Neutral
}
}
Constraint::Use { constraint_use } => {
if let Some(use_) = constrainable.use_() {
if use_ == *constraint_use {
ConstraintDecision::Positive
} else {
ConstraintDecision::Negative
}
} else {
ConstraintDecision::Neutral
}
}
Constraint::Kty { constraint_kty } => {
if *constraint_kty == constrainable.kty() {
ConstraintDecision::Positive
} else {
ConstraintDecision::Negative
}
}
}
}
}
#[derive(Default)]
pub struct ConstraintSet<'a> {
constraints: HashSet<Constraint<'a>>,
}
impl<'a> FromIterator<Constraint<'a>> for ConstraintSet<'a> {
fn from_iter<T: IntoIterator<Item = Constraint<'a>>>(iter: T) -> Self {
Self {
constraints: HashSet::from_iter(iter),
}
}
}
#[allow(dead_code)]
impl<'a> ConstraintSet<'a> {
pub fn new(constraints: impl IntoIterator<Item = Constraint<'a>>) -> Self {
constraints.into_iter().collect()
}
pub fn filter<'b, T: Constrainable, I: IntoIterator<Item = &'b T>>(
&self,
constrainables: I,
) -> Vec<&'b T> {
let mut selected = Vec::new();
'outer: for constrainable in constrainables {
let mut score = 0;
for constraint in &self.constraints {
match constraint.decide(constrainable) {
ConstraintDecision::Positive => score += 1,
ConstraintDecision::Neutral => {}
// If any constraint was negative, don't add it to the candidates
ConstraintDecision::Negative => break 'outer,
}
}
selected.push((score, constrainable));
}
selected.sort_by_key(|(score, _)| *score);
selected
.into_iter()
.map(|(_score, constrainable)| constrainable)
.collect()
}
#[must_use]
pub fn alg(mut self, constraint_alg: JsonWebSignatureAlg) -> Self {
self.constraints.insert(Constraint::alg(constraint_alg));
self
}
#[must_use]
pub fn algs(mut self, constraint_algs: &'a [JsonWebSignatureAlg]) -> Self {
self.constraints.insert(Constraint::algs(constraint_algs));
self
}
#[must_use]
pub fn kid(mut self, constraint_kid: &'a str) -> Self {
self.constraints.insert(Constraint::kid(constraint_kid));
self
}
#[must_use]
pub fn use_(mut self, constraint_use: JsonWebKeyUse) -> Self {
self.constraints.insert(Constraint::use_(constraint_use));
self
}
#[must_use]
pub fn kty(mut self, constraint_kty: JsonWebKeyType) -> Self {
self.constraints.insert(Constraint::kty(constraint_kty));
self
}
}
impl<'a> From<&'a JsonWebSignatureHeader> for ConstraintSet<'a> {
fn from(header: &'a JsonWebSignatureHeader) -> Self {
let mut constraints = Self::default().alg(header.alg());
if let Some(kid) = header.kid() {
constraints = constraints.kid(kid);
}
constraints
}
}

109
crates/jose/src/hmac.rs Normal file
View File

@ -0,0 +1,109 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::marker::PhantomData;
use digest::{
crypto_common::BlockSizeUser,
generic_array::{ArrayLength, GenericArray},
Digest, Mac, OutputSizeUser,
};
use signature::{Signer, Verifier};
use thiserror::Error;
pub struct Signature<S: ArrayLength<u8>> {
signature: GenericArray<u8, S>,
}
impl<S: ArrayLength<u8>> PartialEq for Signature<S> {
fn eq(&self, other: &Self) -> bool {
self.signature == other.signature
}
}
impl<S: ArrayLength<u8>> Eq for Signature<S> {}
impl<S: ArrayLength<u8>> std::fmt::Debug for Signature<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.signature)
}
}
impl<S: ArrayLength<u8>> signature::Signature for Signature<S> {
fn from_bytes(bytes: &[u8]) -> Result<Self, signature::Error> {
if bytes.len() != S::to_usize() {
return Err(signature::Error::new());
}
Ok(Self {
signature: GenericArray::from_slice(bytes).clone(),
})
}
}
impl<S: ArrayLength<u8>> AsRef<[u8]> for Signature<S> {
fn as_ref(&self) -> &[u8] {
self.signature.as_ref()
}
}
pub struct Hmac<D> {
key: Vec<u8>,
digest: PhantomData<D>,
}
#[derive(Error, Debug)]
#[error("invalid length")]
pub struct InvalidLength;
impl<D> From<Vec<u8>> for Hmac<D> {
fn from(key: Vec<u8>) -> Self {
Self {
key,
digest: PhantomData::default(),
}
}
}
impl<D: Digest + BlockSizeUser>
Signer<Signature<<hmac::SimpleHmac<D> as OutputSizeUser>::OutputSize>> for Hmac<D>
{
fn try_sign(
&self,
msg: &[u8],
) -> Result<Signature<<hmac::SimpleHmac<D> as OutputSizeUser>::OutputSize>, signature::Error>
{
let mut mac = <hmac::SimpleHmac<D> as Mac>::new_from_slice(&self.key)
.map_err(signature::Error::from_source)?;
mac.update(msg);
let signature = mac.finalize().into_bytes();
Ok(Signature { signature })
}
}
impl<D: Digest + BlockSizeUser>
Verifier<Signature<<hmac::SimpleHmac<D> as OutputSizeUser>::OutputSize>> for Hmac<D>
{
fn verify(
&self,
msg: &[u8],
signature: &Signature<<hmac::SimpleHmac<D> as OutputSizeUser>::OutputSize>,
) -> Result<(), signature::Error> {
let new_signature = self.try_sign(msg)?;
if &new_signature != signature {
return Err(signature::Error::new());
}
Ok(())
}
}

View File

@ -30,6 +30,8 @@ use serde_with::{
};
use url::Url;
use crate::constraints::Constrainable;
#[serde_as]
#[skip_serializing_none]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@ -109,20 +111,6 @@ impl JsonWebKey {
self
}
#[must_use]
pub const fn kty(&self) -> JsonWebKeyType {
match self.parameters {
JsonWebKeyParameters::Ec { .. } => JsonWebKeyType::Ec,
JsonWebKeyParameters::Rsa { .. } => JsonWebKeyType::Rsa,
JsonWebKeyParameters::Okp { .. } => JsonWebKeyType::Okp,
}
}
#[must_use]
pub fn kid(&self) -> Option<&str> {
self.kid.as_deref()
}
#[must_use]
pub const fn alg(&self) -> Option<JsonWebSignatureAlg> {
self.alg
@ -134,6 +122,58 @@ impl JsonWebKey {
}
}
impl Constrainable for JsonWebKey {
fn kid(&self) -> Option<&str> {
self.kid.as_deref()
}
fn kty(&self) -> JsonWebKeyType {
match self.parameters {
JsonWebKeyParameters::Ec { .. } => JsonWebKeyType::Ec,
JsonWebKeyParameters::Rsa { .. } => JsonWebKeyType::Rsa,
JsonWebKeyParameters::Okp { .. } => JsonWebKeyType::Okp,
}
}
fn algs(&self) -> Option<Vec<JsonWebSignatureAlg>> {
if let Some(alg) = self.alg {
Some(vec![alg])
} else {
match self.parameters {
JsonWebKeyParameters::Rsa { .. } => Some(vec![
JsonWebSignatureAlg::Rs256,
JsonWebSignatureAlg::Rs384,
JsonWebSignatureAlg::Rs512,
JsonWebSignatureAlg::Ps256,
JsonWebSignatureAlg::Ps384,
JsonWebSignatureAlg::Ps512,
]),
JsonWebKeyParameters::Ec {
crv: JsonWebKeyEcEllipticCurve::P256,
..
} => Some(vec![JsonWebSignatureAlg::Es256]),
JsonWebKeyParameters::Ec {
crv: JsonWebKeyEcEllipticCurve::P384,
..
} => Some(vec![JsonWebSignatureAlg::Es384]),
JsonWebKeyParameters::Ec {
crv: JsonWebKeyEcEllipticCurve::P521,
..
} => Some(vec![JsonWebSignatureAlg::Es512]),
JsonWebKeyParameters::Ec {
crv: JsonWebKeyEcEllipticCurve::Secp256K1,
..
} => Some(vec![JsonWebSignatureAlg::Es256K]),
JsonWebKeyParameters::Okp { .. } => Some(vec![JsonWebSignatureAlg::EdDsa]),
}
}
}
fn use_(&self) -> Option<JsonWebKeyUse> {
self.r#use
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct JsonWebKeySet {
keys: Vec<JsonWebKey>,
@ -251,10 +291,11 @@ impl From<rsa::RsaPublicKey> for JsonWebKeyParameters {
#[cfg(test)]
mod tests {
use super::*;
use crate::constraints::ConstraintSet;
#[test]
fn load_google_keys() {
let jwks = r#"{
let jwks = serde_json::json!({
"keys": [
{
"alg": "RS256",
@ -273,19 +314,33 @@ mod tests {
"alg": "RS256"
}
]
}"#;
});
let jwks: JsonWebKeySet = serde_json::from_str(jwks).unwrap();
let jwks: JsonWebKeySet = serde_json::from_value(jwks).unwrap();
// Both keys are RSA public keys
for jwk in jwks.keys {
rsa::RsaPublicKey::try_from(jwk.parameters).unwrap();
for jwk in &jwks.keys {
rsa::RsaPublicKey::try_from(jwk.parameters.clone()).unwrap();
}
let constraints = ConstraintSet::default()
.use_(JsonWebKeyUse::Sig)
.kty(JsonWebKeyType::Rsa)
.alg(JsonWebSignatureAlg::Rs256);
let candidates = constraints.filter(&jwks.keys);
assert_eq!(candidates.len(), 2);
let constraints = ConstraintSet::default()
.use_(JsonWebKeyUse::Sig)
.kty(JsonWebKeyType::Rsa)
.kid("03e84aed4ef4431014e8617567864c4efaaaede9");
let candidates = constraints.filter(&jwks.keys);
assert_eq!(candidates.len(), 1);
}
#[allow(clippy::too_many_lines)]
#[test]
fn load_keycloak_keys() {
let jwks = r#"{
let jwks = serde_json::json!({
"keys": [
{
"kid": "SuGUPE9Sr-1Gha2NLse33r5NQu3XoS_I3Qds3bcmfQE",
@ -393,9 +448,9 @@ mod tests {
"y": "Vu3rTFNn_9zWTki95UGT1Bd9PN84KDXmttCrJ1bsYHTWQCaEONk8iwA3U6mEDrg4xtZSTXXKCFdFP13ONWB9oZ4"
}
]
}"#;
});
let jwks: JsonWebKeySet = serde_json::from_str(jwks).unwrap();
let jwks: JsonWebKeySet = serde_json::from_value(jwks).unwrap();
// The first 6 keys are RSA, 7th is P-256
let mut keys = jwks.keys.into_iter();
rsa::RsaPublicKey::try_from(keys.next().unwrap().parameters).unwrap();

View File

@ -115,14 +115,17 @@ where
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
let raw = RawJwt::try_from(value)?;
let header_reader =
base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.header().as_bytes())
.map_err(JwtDecodeError::decode_header)?;
let header =
Base64UrlUnpadded::decode_vec(raw.header()).map_err(JwtDecodeError::decode_header)?;
let header = serde_json::from_slice(&header).map_err(JwtDecodeError::deserialize_header)?;
serde_json::from_reader(header_reader).map_err(JwtDecodeError::deserialize_header)?;
let payload_reader =
base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.payload().as_bytes())
.map_err(JwtDecodeError::decode_payload)?;
let payload =
Base64UrlUnpadded::decode_vec(raw.payload()).map_err(JwtDecodeError::decode_payload)?;
let payload =
serde_json::from_slice(&payload).map_err(JwtDecodeError::deserialize_payload)?;
serde_json::from_reader(payload_reader).map_err(JwtDecodeError::deserialize_payload)?;
let signature = Base64UrlUnpadded::decode_vec(raw.signature())
.map_err(JwtDecodeError::decode_signature)?;
@ -162,6 +165,14 @@ impl JwtVerificationError {
}
impl<'a, T> Jwt<'a, T> {
pub fn header(&self) -> &JsonWebSignatureHeader {
&self.header
}
pub fn payload(&self) -> &T {
&self.payload
}
pub fn verify<K, S>(&self, key: &K) -> Result<(), JwtVerificationError>
where
K: Verifier<S>,

View File

@ -21,7 +21,10 @@ use sha2::{Sha256, Sha384, Sha512};
use signature::{Signature, Verifier};
use thiserror::Error;
use crate::{JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader, VerifyingKeystore};
use crate::{
constraints::Constrainable, JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader,
VerifyingKeystore,
};
#[derive(Debug, Error)]
pub enum Error {

View File

@ -18,9 +18,13 @@
#![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)]
pub mod claims;
pub mod constraints;
pub mod hmac;
pub(crate) mod jwk;
pub(crate) mod jwt;
mod keystore;
pub(crate) mod rsa;
pub mod verifier;
pub use futures_util::future::Either;

146
crates/jose/src/rsa.rs Normal file
View File

@ -0,0 +1,146 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This is a temporary wrapper until the RSA crate actually hashes the input
// See <https://github.com/RustCrypto/RSA/pull/174#issuecomment-1227330296>
pub(crate) mod pkcs1v15 {
use std::marker::PhantomData;
use digest::Digest;
use rsa::RsaPublicKey;
use sha2::{Sha256, Sha384, Sha512};
pub struct VerifyingKey<H> {
inner: rsa::pkcs1v15::VerifyingKey,
hash: PhantomData<H>,
}
impl From<RsaPublicKey> for VerifyingKey<Sha256> {
fn from(key: RsaPublicKey) -> Self {
let inner = rsa::pkcs1v15::VerifyingKey::new_with_hash(key, rsa::Hash::SHA2_256);
ensure_verifier(Self {
inner,
hash: PhantomData::default(),
})
}
}
impl From<RsaPublicKey> for VerifyingKey<Sha384> {
fn from(key: RsaPublicKey) -> Self {
let inner = rsa::pkcs1v15::VerifyingKey::new_with_hash(key, rsa::Hash::SHA2_384);
ensure_verifier(Self {
inner,
hash: PhantomData::default(),
})
}
}
impl From<RsaPublicKey> for VerifyingKey<Sha512> {
fn from(key: RsaPublicKey) -> Self {
let inner = rsa::pkcs1v15::VerifyingKey::new_with_hash(key, rsa::Hash::SHA2_512);
ensure_verifier(Self {
inner,
hash: PhantomData::default(),
})
}
}
#[inline]
fn ensure_verifier<T>(t: T) -> T
where
T: signature::Verifier<rsa::pkcs1v15::Signature>,
{
t
}
impl<H> signature::Verifier<rsa::pkcs1v15::Signature> for VerifyingKey<H>
where
H: Digest,
{
fn verify(
&self,
msg: &[u8],
signature: &rsa::pkcs1v15::Signature,
) -> Result<(), signature::Error> {
let digest = H::digest(msg);
self.inner.verify(&digest, signature)
}
}
}
pub(crate) mod pss {
use std::marker::PhantomData;
use digest::Digest;
use rsa::RsaPublicKey;
use sha2::{Sha256, Sha384, Sha512};
pub struct VerifyingKey<H> {
inner: rsa::pss::VerifyingKey,
hash: PhantomData<H>,
}
impl From<RsaPublicKey> for VerifyingKey<Sha256> {
fn from(key: RsaPublicKey) -> Self {
let inner = rsa::pss::VerifyingKey::new(key, Box::new(Sha256::new()));
ensure_verifier(Self {
inner,
hash: PhantomData::default(),
})
}
}
impl From<RsaPublicKey> for VerifyingKey<Sha384> {
fn from(key: RsaPublicKey) -> Self {
let inner = rsa::pss::VerifyingKey::new(key, Box::new(Sha384::new()));
ensure_verifier(Self {
inner,
hash: PhantomData::default(),
})
}
}
impl From<RsaPublicKey> for VerifyingKey<Sha512> {
fn from(key: RsaPublicKey) -> Self {
let inner = rsa::pss::VerifyingKey::new(key, Box::new(Sha512::new()));
ensure_verifier(Self {
inner,
hash: PhantomData::default(),
})
}
}
#[inline]
fn ensure_verifier<T>(t: T) -> T
where
T: signature::Verifier<rsa::pss::Signature>,
{
t
}
impl<H> signature::Verifier<rsa::pss::Signature> for VerifyingKey<H>
where
H: Digest,
{
fn verify(
&self,
msg: &[u8],
signature: &rsa::pss::Signature,
) -> Result<(), signature::Error> {
let digest = H::digest(msg);
self.inner.verify(&digest, signature)
}
}
}

330
crates/jose/src/verifier.rs Normal file
View File

@ -0,0 +1,330 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use mas_iana::jose::{JsonWebKeyEcEllipticCurve, JsonWebSignatureAlg};
use rsa::BigUint;
use sha2::{Sha256, Sha384, Sha512};
use signature::Signature;
use thiserror::Error;
use crate::jwk::JsonWebKeyParameters;
pub enum Verifier {
Hs256 {
key: crate::hmac::Hmac<Sha256>,
},
Hs384 {
key: crate::hmac::Hmac<Sha384>,
},
Hs512 {
key: crate::hmac::Hmac<Sha512>,
},
Rs256 {
key: crate::rsa::pkcs1v15::VerifyingKey<Sha256>,
},
Rs384 {
key: crate::rsa::pkcs1v15::VerifyingKey<Sha384>,
},
Rs512 {
key: crate::rsa::pkcs1v15::VerifyingKey<Sha512>,
},
Ps256 {
key: crate::rsa::pss::VerifyingKey<Sha256>,
},
Ps384 {
key: crate::rsa::pss::VerifyingKey<Sha384>,
},
Ps512 {
key: crate::rsa::pss::VerifyingKey<Sha512>,
},
Es256 {
key: ecdsa::VerifyingKey<p256::NistP256>,
},
Es384 {
key: ecdsa::VerifyingKey<p384::NistP384>,
},
Es256K {
key: ecdsa::VerifyingKey<k256::Secp256k1>,
},
}
#[derive(Debug, Error)]
pub enum VerifierFromJwkError {
#[error("invalid RSA key")]
InvalidRsaKey {
#[from]
inner: rsa::errors::Error,
},
#[error("invalid elliptic curve key")]
InvalidEcKey {
#[from]
inner: ecdsa::Error,
},
#[error("invalid curve parameter X")]
InvalidCurveParameterX,
#[error("invalid curve parameter Y")]
InvalidCurveParameterY,
#[error("algorithm {algorithm} is not supported")]
UnsupportedAlgorithm { algorithm: JsonWebSignatureAlg },
#[error("key is not suitable for algorithm {algorithm}")]
KeyNotSuitable { algorithm: JsonWebSignatureAlg },
}
#[derive(Debug, Error)]
pub enum VerifierFromOctError {
#[error("algorithm {algorithm} is not supported")]
UnsupportedAlgorithm { algorithm: JsonWebSignatureAlg },
}
impl Verifier {
pub fn for_oct_and_alg(
key: Vec<u8>,
alg: JsonWebSignatureAlg,
) -> Result<Self, VerifierFromOctError> {
match alg {
JsonWebSignatureAlg::Hs256 => Ok(Self::Hs256 { key: key.into() }),
JsonWebSignatureAlg::Hs384 => Ok(Self::Hs384 { key: key.into() }),
JsonWebSignatureAlg::Hs512 => Ok(Self::Hs512 { key: key.into() }),
algorithm => Err(VerifierFromOctError::UnsupportedAlgorithm { algorithm }),
}
}
#[allow(clippy::too_many_lines)]
pub fn for_jwk_and_alg(
key: &JsonWebKeyParameters,
alg: JsonWebSignatureAlg,
) -> Result<Self, VerifierFromJwkError> {
match (key, alg) {
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Rs256) => {
let n = BigUint::from_bytes_be(n);
let e = BigUint::from_bytes_be(e);
let key = rsa::RsaPublicKey::new(n, e)?;
Ok(Self::Rs256 { key: key.into() })
}
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Rs384) => {
let n = BigUint::from_bytes_be(n);
let e = BigUint::from_bytes_be(e);
let key = rsa::RsaPublicKey::new(n, e)?;
Ok(Self::Rs384 { key: key.into() })
}
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Rs512) => {
let n = BigUint::from_bytes_be(n);
let e = BigUint::from_bytes_be(e);
let key = rsa::RsaPublicKey::new(n, e)?;
Ok(Self::Rs512 { key: key.into() })
}
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Ps256) => {
let n = BigUint::from_bytes_be(n);
let e = BigUint::from_bytes_be(e);
let key = rsa::RsaPublicKey::new(n, e)?;
Ok(Self::Ps256 { key: key.into() })
}
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Ps384) => {
let n = BigUint::from_bytes_be(n);
let e = BigUint::from_bytes_be(e);
let key = rsa::RsaPublicKey::new(n, e)?;
Ok(Self::Ps384 { key: key.into() })
}
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Ps512) => {
let n = BigUint::from_bytes_be(n);
let e = BigUint::from_bytes_be(e);
let key = rsa::RsaPublicKey::new(n, e)?;
Ok(Self::Ps512 { key: key.into() })
}
(
JsonWebKeyParameters::Ec {
crv: JsonWebKeyEcEllipticCurve::P256,
x,
y,
},
JsonWebSignatureAlg::Es256,
) => {
let x: &[u8; 32] = x
.as_slice()
.try_into()
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterX)?;
let y: &[u8; 32] = y
.as_slice()
.try_into()
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterY)?;
let point = sec1::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false);
let key = ecdsa::VerifyingKey::from_encoded_point(&point)?;
Ok(Self::Es256 { key })
}
(
JsonWebKeyParameters::Ec {
crv: JsonWebKeyEcEllipticCurve::P384,
x,
y,
},
JsonWebSignatureAlg::Es384,
) => {
let x: &[u8; 48] = x
.as_slice()
.try_into()
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterX)?;
let y: &[u8; 48] = y
.as_slice()
.try_into()
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterY)?;
let point = sec1::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false);
let key = ecdsa::VerifyingKey::from_encoded_point(&point)?;
Ok(Self::Es384 { key })
}
(
JsonWebKeyParameters::Ec {
crv: JsonWebKeyEcEllipticCurve::P521,
x: _,
y: _,
},
JsonWebSignatureAlg::Es512,
) => Err(VerifierFromJwkError::UnsupportedAlgorithm {
algorithm: JsonWebSignatureAlg::Es512,
}),
(
JsonWebKeyParameters::Ec {
crv: JsonWebKeyEcEllipticCurve::Secp256K1,
x,
y,
},
JsonWebSignatureAlg::Es256K,
) => {
let x: &[u8; 32] = x
.as_slice()
.try_into()
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterX)?;
let y: &[u8; 32] = y
.as_slice()
.try_into()
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterY)?;
let point = sec1::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false);
let key = ecdsa::VerifyingKey::from_encoded_point(&point)?;
Ok(Self::Es256K { key })
}
(JsonWebKeyParameters::Okp { crv: _, x: _ }, JsonWebSignatureAlg::EdDsa) => {
Err(VerifierFromJwkError::UnsupportedAlgorithm {
algorithm: JsonWebSignatureAlg::EdDsa,
})
}
(_, algorithm) => Err(VerifierFromJwkError::KeyNotSuitable { algorithm }),
}
}
}
#[derive(Debug)]
pub struct GenericSignature {
bytes: Vec<u8>,
}
impl AsRef<[u8]> for GenericSignature {
fn as_ref(&self) -> &[u8] {
&self.bytes
}
}
impl signature::Signature for GenericSignature {
fn from_bytes(bytes: &[u8]) -> Result<Self, signature::Error> {
Ok(Self {
bytes: bytes.to_vec(),
})
}
}
impl signature::Verifier<GenericSignature> for Verifier {
fn verify(&self, msg: &[u8], signature: &GenericSignature) -> Result<(), signature::Error> {
match self {
Verifier::Hs256 { key } => {
let signature = crate::hmac::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Hs384 { key } => {
let signature = crate::hmac::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Hs512 { key } => {
let signature = crate::hmac::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Rs256 { key } => {
let signature = rsa::pkcs1v15::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Rs384 { key } => {
let signature = rsa::pkcs1v15::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Rs512 { key } => {
let signature = rsa::pkcs1v15::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Ps256 { key } => {
let signature = rsa::pss::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Ps384 { key } => {
let signature = rsa::pss::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Ps512 { key } => {
let signature = rsa::pss::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Es256 { key } => {
let signature = ecdsa::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Es384 { key } => {
let signature = ecdsa::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
Verifier::Es256K { key } => {
let signature = ecdsa::Signature::from_bytes(signature.as_bytes())?;
key.verify(msg, &signature)?;
Ok(())
}
}
}
}

View File

@ -0,0 +1,138 @@
# Generates test keys, JWKS and JWTs
# Required the `openssl` binary and the `authlib` python library
import json
import subprocess
from pathlib import Path
from typing import List
from authlib.jose import JsonWebKey, JsonWebSignature, KeySet
output_path = Path(__file__).parent
keys_path = output_path / "keys"
keys_path.mkdir(parents=True, exist_ok=True)
jwts_path = output_path / "jwts"
jwts_path.mkdir(parents=True, exist_ok=True)
def gen_key(
name: str,
priv_command: List[str],
pub_command: List[str],
):
"""Generate a keypair
Args:
name: Name
priv_command: Command to generate the private key. This must write the
key to stdout.
pub_command: Command to convert the private key to a public one. This
must read the private key from stdin and write the public key to
stdout.
"""
priv_path = keys_path / f"{name}.priv.pem"
pub_path = keys_path / f"{name}.pub.pem"
with open(priv_path, "wb") as f:
subprocess.run(priv_command, stdout=f, stderr=subprocess.DEVNULL)
with open(priv_path, "rb") as priv, open(pub_path, "wb") as pub:
subprocess.run(pub_command, stdin=priv, stdout=pub, stderr=subprocess.DEVNULL)
def import_key(name: str, kty: str) -> JsonWebKey:
"""Import a key from a file"""
with open(keys_path / name, "r") as f:
pem = f.read()
return JsonWebKey.import_key(pem, {"kty": kty})
def sign_jwt(alg: str, filename: str, key: JsonWebKey):
"""Sign a JWT for the given key"""
path = jwts_path / filename
protected = {"alg": alg, "kid": key.thumbprint()}
payload = '{"hello":"world"}'
jws = JsonWebSignature(algorithms=[alg])
jwt = jws.serialize_compact(protected, payload, key)
with open(path, "wb") as f:
f.write(jwt)
with open(keys_path / "oct.bin", "wb") as f:
subprocess.run(
["openssl", "rand", "-hex", "64"], stdout=f, stderr=subprocess.DEVNULL
)
gen_key("rsa", ["openssl", "genrsa", "2048"], ["openssl", "rsa", "-pubout"])
gen_key(
"p256",
["openssl", "ecparam", "-genkey", "-name", "prime256v1"],
["openssl", "ec", "-pubout"],
)
gen_key(
"p384",
["openssl", "ecparam", "-genkey", "-name", "secp384r1"],
["openssl", "ec", "-pubout"],
)
gen_key(
"p521",
["openssl", "ecparam", "-genkey", "-name", "secp521r1"],
["openssl", "ec", "-pubout"],
)
gen_key(
"k256",
["openssl", "ecparam", "-genkey", "-name", "secp256k1"],
["openssl", "ec", "-pubout"],
)
gen_key(
"ed25519",
["openssl", "genpkey", "-algorithm", "ed25519"],
["openssl", "pkey", "-pubout"],
)
gen_key(
"ed448",
["openssl", "genpkey", "-algorithm", "ed448"],
["openssl", "pkey", "-pubout"],
)
oct_key = import_key("oct.bin", "oct")
rsa_key = import_key("rsa.priv.pem", "RSA")
p256_key = import_key("p256.priv.pem", "EC")
p384_key = import_key("p384.priv.pem", "EC")
p521_key = import_key("p521.priv.pem", "EC")
k256_key = import_key("k256.priv.pem", "EC")
ed25519_key = import_key("ed25519.priv.pem", "OKP")
ed448_key = import_key("ed448.priv.pem", "OKP")
key_set = KeySet(
[rsa_key, p256_key, p384_key, p521_key, k256_key, ed25519_key, ed448_key]
)
with open(keys_path / "jwks.pub.json", "w", encoding="utf8") as f:
json.dump(key_set.as_dict(is_private=False), f, indent=2, sort_keys=True)
key_set.keys.insert(0, oct_key)
with open(keys_path / "jwks.priv.json", "w", encoding="utf8") as f:
json.dump(key_set.as_dict(is_private=True), f, indent=2, sort_keys=True)
sign_jwt("HS256", "hs256.jwt", oct_key)
sign_jwt("HS384", "hs384.jwt", oct_key)
sign_jwt("HS512", "hs512.jwt", oct_key)
sign_jwt("RS256", "rs256.jwt", rsa_key)
sign_jwt("RS384", "rs384.jwt", rsa_key)
sign_jwt("RS512", "rs512.jwt", rsa_key)
sign_jwt("RS256", "rs256.jwt", rsa_key)
sign_jwt("RS384", "rs384.jwt", rsa_key)
sign_jwt("RS512", "rs512.jwt", rsa_key)
sign_jwt("PS256", "ps256.jwt", rsa_key)
sign_jwt("PS384", "ps384.jwt", rsa_key)
sign_jwt("PS512", "ps512.jwt", rsa_key)
sign_jwt("ES256", "es256.jwt", p256_key)
sign_jwt("ES384", "es384.jwt", p384_key)
sign_jwt("ES512", "es512.jwt", p521_key)
sign_jwt("ES256K", "es256k.jwt", k256_key)
sign_jwt("EdDSA", "eddsa-ed25519.jwt", ed25519_key)
sign_jwt("EdDSA", "eddsa-ed448.jwt", ed448_key)

107
crates/jose/tests/jws.rs Normal file
View File

@ -0,0 +1,107 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::ops::Deref;
use mas_jose::{constraints::ConstraintSet, JsonWebKeySet, Jwt};
use serde::Deserialize;
static HS256_JWT: &str = include_str!("./jwts/hs256.jwt");
static HS384_JWT: &str = include_str!("./jwts/hs384.jwt");
static HS512_JWT: &str = include_str!("./jwts/hs512.jwt");
static RS256_JWT: &str = include_str!("./jwts/rs256.jwt");
static RS384_JWT: &str = include_str!("./jwts/rs384.jwt");
static RS512_JWT: &str = include_str!("./jwts/rs512.jwt");
static PS256_JWT: &str = include_str!("./jwts/ps256.jwt");
static PS384_JWT: &str = include_str!("./jwts/ps384.jwt");
static PS512_JWT: &str = include_str!("./jwts/ps512.jwt");
static ES256_JWT: &str = include_str!("./jwts/es256.jwt");
static ES384_JWT: &str = include_str!("./jwts/es384.jwt");
static ES512_JWT: &str = include_str!("./jwts/es512.jwt");
static ES256K_JWT: &str = include_str!("./jwts/es256k.jwt");
static EDDSA_ED25519_JWT: &str = include_str!("./jwts/eddsa-ed25519.jwt");
static EDDSA_ED448_JWT: &str = include_str!("./jwts/eddsa-ed448.jwt");
static OCT_KEY: &[u8] = include_bytes!("./keys/oct.bin");
fn public_jwks() -> JsonWebKeySet {
serde_json::from_str(include_str!("./keys/jwks.pub.json")).unwrap()
}
fn oct_key() -> Vec<u8> {
OCT_KEY.to_vec()
}
#[derive(Deserialize)]
struct Payload {
hello: String,
}
macro_rules! asymetric_jwt_test {
($test_name:ident, $jwt:ident) => {
asymetric_jwt_test!($test_name, $jwt, verify = true);
};
($test_name:ident, $jwt:ident, verify = $verify:ident) => {
#[test]
fn $test_name() {
let jwks = public_jwks();
let jwt: Jwt<'_, Payload> = Jwt::try_from($jwt).unwrap();
assert_eq!(jwt.payload().hello, "world");
let constraints = ConstraintSet::from(jwt.header());
let candidates = constraints.filter(jwks.deref());
assert_eq!(candidates.len(), 1);
let candidate = candidates[0];
if $verify {
let verifier = mas_jose::verifier::Verifier::for_jwk_and_alg(
candidate.params(),
jwt.header().alg(),
)
.unwrap();
jwt.verify(&verifier).unwrap();
}
}
};
}
macro_rules! symetric_jwt_test {
($test_name:ident, $jwt:ident) => {
#[test]
fn $test_name() {
let jwt: Jwt<'_, Payload> = Jwt::try_from($jwt).unwrap();
let verifier =
mas_jose::verifier::Verifier::for_oct_and_alg(oct_key(), jwt.header().alg())
.unwrap();
assert_eq!(jwt.payload().hello, "world");
jwt.verify(&verifier).unwrap();
}
};
}
symetric_jwt_test!(test_hs256, HS256_JWT);
symetric_jwt_test!(test_hs384, HS384_JWT);
symetric_jwt_test!(test_hs512, HS512_JWT);
asymetric_jwt_test!(test_rs256, RS256_JWT);
asymetric_jwt_test!(test_rs384, RS384_JWT);
asymetric_jwt_test!(test_rs512, RS512_JWT);
asymetric_jwt_test!(test_ps256, PS256_JWT);
asymetric_jwt_test!(test_ps384, PS384_JWT);
asymetric_jwt_test!(test_ps512, PS512_JWT);
asymetric_jwt_test!(test_es256, ES256_JWT);
asymetric_jwt_test!(test_es384, ES384_JWT);
asymetric_jwt_test!(test_es512, ES512_JWT, verify = false);
asymetric_jwt_test!(test_es256k, ES256K_JWT);
asymetric_jwt_test!(test_eddsa_ed25519, EDDSA_ED25519_JWT, verify = false);
asymetric_jwt_test!(test_eddsa_ed448, EDDSA_ED448_JWT, verify = false);

View File

@ -0,0 +1 @@
eyJhbGciOiJFZERTQSIsImtpZCI6ImlYa2l5aEVoNkU3VS1hWDBmZzd3LWVzSFdxUHZ2eFdkNmdIMUpHMnU3TjAifQ.eyJoZWxsbyI6IndvcmxkIn0.ZFiNWsheqUC_mQNztHpZXLnyb5LtvyT1dTGcMSCgG97Cobju83xCIkbJwfjOSgZrI2CpEVobVM_mfnmFIAUfBg

View File

@ -0,0 +1 @@
eyJhbGciOiJFZERTQSIsImtpZCI6IlFsdGEycVZsaEhoZzNqcmlKcDBIc0lCUXFHVkIxWkgycEVueVBIemwxTXMifQ.eyJoZWxsbyI6IndvcmxkIn0.7EqBc73c8UjbZnW5LkkDmPlAnlgjVdDzfABvssoLE3FoFX3uUr1dPdX3I9Hu_rtOIdRtTLfN9eeABuG5cugUoshrYSFuHF6vy2Nim7uM3GWa6mVZx6fzOBq6goCK4JpNfwkJ3a4VyslHU7wQBfXAOxcA

View File

@ -0,0 +1 @@
eyJhbGciOiJFUzI1NiIsImtpZCI6ImxNYlNJNjlhanNCSEhrSXBWQUZLUktZblI2NmtHZEd0ZWcyb3FNenAwX0UifQ.eyJoZWxsbyI6IndvcmxkIn0.YckCGhpak2hpO9EiR-X2MD6CVBnUAmQbRVKvKoYCbRnydOOksNlzWaOl0S-C4KZxGTuKG-spzFQJov5h_ob5nw

View File

@ -0,0 +1 @@
eyJhbGciOiJFUzI1NksiLCJraWQiOiJuOWI0Z3lkNU5nSHY3cEo3UzI3QUtCcmhCUEhhM0g1cHRjaXhtWWVyU1VnIn0.eyJoZWxsbyI6IndvcmxkIn0.e0XIMec0_gvlxS8je5hVpYQGls2A5r2TUJ9eJNmdwZQbo1alRB93dgbh3yd4fh8bDOmmLhRfMKti93c7-ljPVg

View File

@ -0,0 +1 @@
eyJhbGciOiJFUzM4NCIsImtpZCI6IkoxRVpKR1AxTHloWDJabHo4eFBjc3BNUUVsOFczYllHMngzTnFpTWJQeVkifQ.eyJoZWxsbyI6IndvcmxkIn0.XK3AIs0TQ1r5Wbpd14MkVIp3rvisQEb_8wlp3F4usveL23GH15y5TQ8mcU5NrxNFFylclwikyz4ozM2zmU7fkCYfjKD8AoEABOTlfjH3DRQnynVcpkvB47CsSgt8QpGe

View File

@ -0,0 +1 @@
eyJhbGciOiJFUzUxMiIsImtpZCI6Il94R3lJM21zOTBBdmdGNjU4d3o5NzF3c3dTeVluR1NHX0EwZEFnbXJBTTAifQ.eyJoZWxsbyI6IndvcmxkIn0.AJ9YcP56d-1Z1wsZL0ikFRY_4Q6du7YEWsqtQDOloCLMYQ-3citw6Fm35t4kg8E5aoe8QrEj8kTqsQLloWv0eBMFAWh-Uyrupmz0Kzllc6xbOEVoWuM5DWc6AJ6Da6k0f6XHsZ_MVcayQpdmZTLcM_pyo1U6olqwLYqv1YNx-2M2GdCl

View File

@ -0,0 +1 @@
eyJhbGciOiJIUzI1NiIsImtpZCI6ImRqSEtvV1Uzck9sV2c1RTBFSV80RmxiRVRmZDRPRlFnVjk4REZYRW1HZmcifQ.eyJoZWxsbyI6IndvcmxkIn0.GBxkJdc15D26siv1Ov_a2jgQSIsgLwiF2ZDFSUdzoFY

View File

@ -0,0 +1 @@
eyJhbGciOiJIUzM4NCIsImtpZCI6ImRqSEtvV1Uzck9sV2c1RTBFSV80RmxiRVRmZDRPRlFnVjk4REZYRW1HZmcifQ.eyJoZWxsbyI6IndvcmxkIn0.pOZkiI4HMCNHgUf9diq6CkFxsMIMCNADvDPHmtkjerSYWy16dmlZy-FT9ZxyyD_1

View File

@ -0,0 +1 @@
eyJhbGciOiJIUzUxMiIsImtpZCI6ImRqSEtvV1Uzck9sV2c1RTBFSV80RmxiRVRmZDRPRlFnVjk4REZYRW1HZmcifQ.eyJoZWxsbyI6IndvcmxkIn0.1kVwcE7LajF4Ph3yl2cKhJRs4FtZUMT6mxVCbtfttLPLqkxX-WAlZ0Hd7zg1JAzxNUmkeF8bsgZ9P0bPxBDSyw

View File

@ -0,0 +1 @@
eyJhbGciOiJQUzI1NiIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.JWY1HZhLrDngEV7-V7to489hsX3muDOeCr4cedGUY2cpDNgJs0CgTe1pknXws9msZSlG4C-oA08UqgousBA2FWbcuVDhSEmSyNWM2rHekFuYcLlAupP8wucMQ3yzP425V2PzlgWV85xRe18PifNaTldMHLArbTKplMQgHHHopz28kuP1Uko99lHxpZrDVMHSLXNTyYaoQeOd81Hbx8uSx5wZO6tVIErV1RhKhSFGLP9DsbOKKW6jRgam_tKNh35VYBQZ6CIQkgsZCruDP7KFHHqC4xHTbkNQ6VlxHHHOpHz-SuRcBS901EN6NVCSPRSc0oYp1ChQCPgUeH_SrloCMg

View File

@ -0,0 +1 @@
eyJhbGciOiJQUzM4NCIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.XLe8Fxg1wALfGIYBtGtYCSxneiReNMRsUiXukYPS3KWvIH6xcLV93GflNRBHRE1aijy1GPnqZv-mZoKjfZr4PoZMX0MalE0j0bFqrLJvfoyxlZLTIzjfyYm81JtPwlB3iU3DvqKGAYBE8aknTOnv65nyprdhGuJhFEW-_7omDzXqE03DofIGQOu-F3nkVP5Om28VKY6Vdr7PswJhKawP97VXrhN5aIubSjldv5-LcKlVwjV9_3RTiEbVGCgluyhzUUhoa-y0Y1oplJC4GMzvQ1YCYQeYJOn0bB1FjpOryJ2mxlIf8qNzlDHnpyr5MVRJ2PAlhZ31GB5JGr_ZQYTRUA

View File

@ -0,0 +1 @@
eyJhbGciOiJQUzUxMiIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.aQ6sXJsU-U14WW7cD344aZi72Hf_XNq9LBi0_feKRVQOO98gV-jlBKWer-n_FI1qLcOUoHfitOciTOVLgvYxeJUwePCwUm9JhQ186CLAc6i_AhqpeKRlDkVOF_viQeZTFwEadHT2KMIe0ImZnPqGUb07arUdzGO67Lwsts2ob7qgG_uWgVbjXMkTUwt-JSHdXUcGIz1FgCJaFgGygfQE_I_doNiApWr2okiuIMs_4Q5BfxIlvPR-uaOcpqxk7ldukvQgUjv4rTfOGE12fCx5eLDVF4P1OXgMjgmcXH1yaV89DgTBgDPP11tQrbsFbANX004VLF9MQWoVF6esl6xwQw

View File

@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.JLzSM5NDbAIb5vpbnKJeHUgU-uJ46616qzDjWXRbIAdxPk8WUqpRDRTlPoRUBXsAKn7E14r_CZmwvGAgJipS7EY0PbJYOkA_6oi8sYWykMUT1F2BlqKQGv2BvRR0LGu0tmm4XYZT2nOLRiEa4bs9l-D2jA5GRTKjDnmgUBHXpX4vIICtnkHHvZilMf1Fjsdm-3X9NFmxjtvQChg-w0h6hM3NZAt6Gd5AG8MaFf-mj3sLa40c51XXz1J1WE9iOWF8lGC6EfP5MSWunKnhyHf2xPQiH4C_Tvm529p2EiEBjjoL1f2A8WH8EYruHF8AXsz2F8HxN_7ryGmjrqLGwuw7iQ

View File

@ -0,0 +1 @@
eyJhbGciOiJSUzM4NCIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.Qf9DbTCoisWvPvGYahn-dMe9r-escYv5cTL-5X2tz5uPRUmAEJ6D6cn0VtLCCPmTIuzSYDzeMqdEx1Is-AVkzvWMKdFRXNVL_E54bhS6Dg04a75bL4YGQOg8iaTTdRlHMaLLfClf8sXttpHmnOFhQ9C6pLcmtT5cfle8qrAw9x7Ivri7jkcjydWcR2WKsYHJxEWDwdhDiBK461F2fi9YtbZOL4qdKEoYpg08v4jH7hFf5G60W_k2oKvPQnbVJe0VcnGcEXvItMAEi8omMn3_OxIGNH-mxBf9DOpOu8Vj-kvvWuE03f31goWLqiL6-Eq8ykqqFZ3sKb23WfGPd26pDw

View File

@ -0,0 +1 @@
eyJhbGciOiJSUzUxMiIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.UevGIlEIlrQWvLLm3Iouq6cxjWf7CtFwaDXQOUEQzdQxa3Mg9H0KD7Ztc1LRS36RFd0rnh9dWsXmeDbQ9yWNepnRvv0QP8Vxq3ty7wOHZtLn2kG1SjDQqgaU743p4n-YUpVugzSha0RHTiRN1TU4zufpx26jQBuO7ihOFof6trc8E2UG98Pgd1w3kv20Glwo_cWauhAefgDRhS-sOaH_SsOFWSBNCa8ISeIOiuKLFOEp2o1m2sla0yCDHVptERYDp3D_LHTLX-BP0dyaxpKwfQ7EuECGK1r7_yyiSq_pOwPrainC3lBKYovOgj8tYGTJxfw4Au_QSY57J96M7N4TmA

View File

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIIutDmtMjMBKXN/Oxmfvxw3cNwtqgcyR2awtQYH/OS/5
-----END PRIVATE KEY-----

View File

@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAnVo63sClAQ8qwBAZW0tttHFhXdrLiKqJnFeJ+j3nA3U=
-----END PUBLIC KEY-----

View File

@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MEcCAQAwBQYDK2VxBDsEOdwdrXdIIxmkz/6pi3/JeOemGYvMECA+CvW5CAGXCvwi
VXFdnXxUt22BpU8Hl1jl1+kuGe3Mx5Pt3w==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MEMwBQYDK2VxAzoAKgaYHB+xIpPPvmH2PdbnWT+67/CfJhuD3U90sv+i5CZmGdwt
WOErsowzNYSvuFWk8vztPOERjn4A
-----END PUBLIC KEY-----

View File

@ -0,0 +1,67 @@
{
"keys": [
{
"k": "MjNlYWE0MzdjMmFjZTA0ZTM1YTNhNzdiODEzMjEwMGYzNDM4YWE2ODk0NmEzMDU1M2NiZjhmZTgwZTJhNWYzY2E3MDA4MjA1M2Y0YmQ0NDU3NjUzYmJiMTU4NzdkODQ3YzIzMjk0OGVhNzUxMmNjZDY3OWY3MWMyNjhmMWRkMDgK",
"kid": "djHKoWU3rOlWg5E0EI_4FlbETfd4OFQgV98DFXEmGfg",
"kty": "oct"
},
{
"d": "PvWpVR1APf5Yxf0ZO20wx7jQXOFusJ5nijKAzyWAPFNx_pjoXD82BTaM0h7odiUGP8Bhc8VqctAQudQ_eEga-YXdpbKXL5wHZG0QzEQlZ_MT8tP_S4z_3rWoUyT9S1rPqWUuQvnb-YsfMWR3RuLGpS4a-3f_zUKbUT_RA5aJUWOMSMSOqMCuh4wdhK7GJKOQlWr8yXF6STEOx7nYxWpIxRdcJK3WinBXPZ5B88wgdDpcR0Yv32u-f7h4R5GHmjPPC2NXsWPpNMe8G6CbDQapgk_1mYViq6zpo4NgkrvBUsMRhFkI__Wlcfz12Ds9dBGxg1R-T7lIPY6wIZKUKLYVQQ",
"dp": "PlzIdqVNjdf5bJaRhW9-d3kF-sQJVV1x2prqRKr-FJK79M39fCmrlhsnyxVc7iqt6gSb3Mz_N1rsCbdn1TYUq6RFed3gJO2Bl57_K8L1N7LKjIxm2TpjLTtrMyxwQx72igxdjdvQytN1l6nmoAI76tv4dsw02dyQIsJ3BR7JiGE",
"dq": "rYGSaiGBnWhIJfI3ajRPy5Icuq7dO3pCgdoDPTMPgib-tHxegjx-v4frCq5Uqc9s94FsgR5_FsY3sttmMrYA7MCqGT3n2Peo718DharXhaScWabONJ8wAt2LPbXh74D3931F3hU9cKLWWg9L6Vckh5QPNgSu2ICEH6f0BxCiTUE",
"e": "AQAB",
"kid": "ljAwFsW32expyA0RjrKoOHuZxfk7KLSej8zldO9z4iU",
"kty": "RSA",
"n": "qNYHjNfiXl2SPu7NYSkd5RiF45bo4c_WW4K1NwH-iBwOOb970RvwlcyhctsvrtrUAJ046Z-3LgW27MR73NdcBP6z756XWIQ6CV6XlowG9NlgnEmOolh3XujZuNig-_05anzhTJr6Xl7uxh8o61VQgBjOgDma7cnEJNz2s89nu9f-WOMNage63O02ecA17TsZjU2jYcOCnV5UhsIyVRUcB3S2Jtk8FtRGBIydbkFHCX61atyh8GjzXYpneVPxTm1fRr-qUvGgNJqvK2HhuOzP6wRpcyKS6cl47I9Mu4L36pavtB_WOTXNhtduSYbUvMnkifAGuYTJHpT2e5QzSm2p7Q",
"p": "1djfJgLXpMaNuUaXd7V4bnkN38XC1Of8dubiGO5bFAym1qnExUQmJSq6Uubs3bRLSaa1i1EQqvIB_bBLm6we4m_jQbuNl8-m6dODaZQ2XVWmMWK84zDsRyJL2U-qDbSwt346AI3b3p-rtWlQLftx3qwj0-neAU4Jopfdz6R4HN0",
"q": "yh3QLx9gsrVF3crgejhJiEf4DLiGAglGAJpekeSllPhXWGF_0hw3YurhTm9swLumHhj0MvDNjVHz_fm-W5uLvkO0CW2hfoOtG3cj3AbnVu34v323EK27wTsp3ADMmykKk7Vc7rRtKUNuTawL-rL7ImvRhgiA_1La0NKkIk_yKFE",
"qi": "asGs7u5s2MXkwm0DHcyC9290wmlufSFOUrBgG-o8iahLFnYl9fCazTZJtBrSNQdfSPzr_htSSDav082q1Khaj1dCl_Jn84wuW2zTXgKhWbIvnCHM0GqgWGJ2HKHW3MKhGgitO5xlkv0nuv8znrkJtkQdxdw8x22eSNHUIPh_NtU"
},
{
"crv": "P-256",
"d": "V9_Lc6jc0hyqFuqSHVaMzXcmWMj34Ib3pG5vls3EbaM",
"kid": "lMbSI69ajsBHHkIpVAFKRKYnR66kGdGteg2oqMzp0_E",
"kty": "EC",
"x": "4rnALl_X1zeOJtDmxz-YiUR1-9QGBfRE90qy_rqe0N0",
"y": "qGl3Telg02usgXK9jQTwcNQRLLovo07vffwaaZ3Dc5o"
},
{
"crv": "P-384",
"d": "qLmbibr7wHPvUolsAC6I2rD_a78oCA3SvNiOh4C_WvZmtVJtIBDeQ3IGhz8q5lF0",
"kid": "J1EZJGP1LyhX2Zlz8xPcspMQEl8W3bYG2x3NqiMbPyY",
"kty": "EC",
"x": "dT7_3-Wp3kgewAiAyoarKQ2_rL4dketqaUti8nHOIT9K0dGtMGDt5W9uThc4mALN",
"y": "LHdC5G42RYTZcdnnEMftzcx3DOOaeTErrcdMpVTdu0gdjAQDrDwtHGu73E3AeHGp"
},
{
"crv": "P-521",
"d": "Ae6RJFt67feYXDBIsWEv32WL7MsuiNJgO3A7WEyidaHN1-CP5nIjFN5urf8MvD8fBWqaxdwrAEIA6uPfc8f2U1J8",
"kid": "_xGyI3ms90AvgF658wz971wswSyYnGSG_A0dAgmrAM0",
"kty": "EC",
"x": "ARv-BW1gxTWyey_wil6Sc2iaPuu_iBsEKji2B8UBgW-vIp-JSHzTut1dR1UpkFnZe53EkS6P5kTNm5cBA-r9282J",
"y": "AeVtvHkJslwuPji-M71Zp3DzOostp_keWMB0f1zljl0P1CVhcpC5x4T_D3nEO_zRduL1R0Fv5gE6zaLm8X4cZD5m"
},
{
"crv": "secp256k1",
"d": "_BuT_AckwrIi8AJpUTkqbxTMaViLXK5z0oIePWf9kkg",
"kid": "n9b4gyd5NgHv7pJ7S27AKBrhBPHa3H5ptcixmYerSUg",
"kty": "EC",
"x": "t-mBmz-Rvh0n3W_bRL_TSOc3Vv0ZB0oGaPZBEqu4sTQ",
"y": "PLm8asqUtHw5gVZN09vA5giJkDIPDOZ4zaG7NR77qSg"
},
{
"crv": "Ed25519",
"d": "i60Oa0yMwEpc387GZ-_HDdw3C2qBzJHZrC1Bgf85L_k",
"kid": "iXkiyhEh6E7U-aX0fg7w-esHWqPvvxWd6gH1JG2u7N0",
"kty": "OKP",
"x": "nVo63sClAQ8qwBAZW0tttHFhXdrLiKqJnFeJ-j3nA3U"
},
{
"crv": "Ed448",
"d": "3B2td0gjGaTP_qmLf8l456YZi8wQID4K9bkIAZcK_CJVcV2dfFS3bYGlTweXWOXX6S4Z7czHk-3f",
"kid": "Qlta2qVlhHhg3jriJp0HsIBQqGVB1ZH2pEnyPHzl1Ms",
"kty": "OKP",
"x": "KgaYHB-xIpPPvmH2PdbnWT-67_CfJhuD3U90sv-i5CZmGdwtWOErsowzNYSvuFWk8vztPOERjn4A"
}
]
}

View File

@ -0,0 +1,50 @@
{
"keys": [
{
"e": "AQAB",
"kid": "ljAwFsW32expyA0RjrKoOHuZxfk7KLSej8zldO9z4iU",
"kty": "RSA",
"n": "qNYHjNfiXl2SPu7NYSkd5RiF45bo4c_WW4K1NwH-iBwOOb970RvwlcyhctsvrtrUAJ046Z-3LgW27MR73NdcBP6z756XWIQ6CV6XlowG9NlgnEmOolh3XujZuNig-_05anzhTJr6Xl7uxh8o61VQgBjOgDma7cnEJNz2s89nu9f-WOMNage63O02ecA17TsZjU2jYcOCnV5UhsIyVRUcB3S2Jtk8FtRGBIydbkFHCX61atyh8GjzXYpneVPxTm1fRr-qUvGgNJqvK2HhuOzP6wRpcyKS6cl47I9Mu4L36pavtB_WOTXNhtduSYbUvMnkifAGuYTJHpT2e5QzSm2p7Q"
},
{
"crv": "P-256",
"kid": "lMbSI69ajsBHHkIpVAFKRKYnR66kGdGteg2oqMzp0_E",
"kty": "EC",
"x": "4rnALl_X1zeOJtDmxz-YiUR1-9QGBfRE90qy_rqe0N0",
"y": "qGl3Telg02usgXK9jQTwcNQRLLovo07vffwaaZ3Dc5o"
},
{
"crv": "P-384",
"kid": "J1EZJGP1LyhX2Zlz8xPcspMQEl8W3bYG2x3NqiMbPyY",
"kty": "EC",
"x": "dT7_3-Wp3kgewAiAyoarKQ2_rL4dketqaUti8nHOIT9K0dGtMGDt5W9uThc4mALN",
"y": "LHdC5G42RYTZcdnnEMftzcx3DOOaeTErrcdMpVTdu0gdjAQDrDwtHGu73E3AeHGp"
},
{
"crv": "P-521",
"kid": "_xGyI3ms90AvgF658wz971wswSyYnGSG_A0dAgmrAM0",
"kty": "EC",
"x": "ARv-BW1gxTWyey_wil6Sc2iaPuu_iBsEKji2B8UBgW-vIp-JSHzTut1dR1UpkFnZe53EkS6P5kTNm5cBA-r9282J",
"y": "AeVtvHkJslwuPji-M71Zp3DzOostp_keWMB0f1zljl0P1CVhcpC5x4T_D3nEO_zRduL1R0Fv5gE6zaLm8X4cZD5m"
},
{
"crv": "secp256k1",
"kid": "n9b4gyd5NgHv7pJ7S27AKBrhBPHa3H5ptcixmYerSUg",
"kty": "EC",
"x": "t-mBmz-Rvh0n3W_bRL_TSOc3Vv0ZB0oGaPZBEqu4sTQ",
"y": "PLm8asqUtHw5gVZN09vA5giJkDIPDOZ4zaG7NR77qSg"
},
{
"crv": "Ed25519",
"kid": "iXkiyhEh6E7U-aX0fg7w-esHWqPvvxWd6gH1JG2u7N0",
"kty": "OKP",
"x": "nVo63sClAQ8qwBAZW0tttHFhXdrLiKqJnFeJ-j3nA3U"
},
{
"crv": "Ed448",
"kid": "Qlta2qVlhHhg3jriJp0HsIBQqGVB1ZH2pEnyPHzl1Ms",
"kty": "OKP",
"x": "KgaYHB-xIpPPvmH2PdbnWT-67_CfJhuD3U90sv-i5CZmGdwtWOErsowzNYSvuFWk8vztPOERjn4A"
}
]
}

View File

@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIPwbk/wHJMKyIvACaVE5Km8UzGlYi1yuc9KCHj1n/ZJIoAcGBSuBBAAK
oUQDQgAEt+mBmz+Rvh0n3W/bRL/TSOc3Vv0ZB0oGaPZBEqu4sTQ8ubxqypS0fDmB
Vk3T28DmCImQMg8M5njNobs1HvupKA==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEt+mBmz+Rvh0n3W/bRL/TSOc3Vv0ZB0oG
aPZBEqu4sTQ8ubxqypS0fDmBVk3T28DmCImQMg8M5njNobs1HvupKA==
-----END PUBLIC KEY-----

View File

@ -0,0 +1 @@
23eaa437c2ace04e35a3a77b8132100f3438aa68946a30553cbf8fe80e2a5f3ca70082053f4bd4457653bbb15877d847c232948ea7512ccd679f71c268f1dd08

View File

@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIFffy3Oo3NIcqhbqkh1WjM13JljI9+CG96Rub5bNxG2joAoGCCqGSM49
AwEHoUQDQgAE4rnALl/X1zeOJtDmxz+YiUR1+9QGBfRE90qy/rqe0N2oaXdN6WDT
a6yBcr2NBPBw1BEsui+jTu99/BppncNzmg==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4rnALl/X1zeOJtDmxz+YiUR1+9QG
BfRE90qy/rqe0N2oaXdN6WDTa6yBcr2NBPBw1BEsui+jTu99/BppncNzmg==
-----END PUBLIC KEY-----

View File

@ -0,0 +1,9 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQAIg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDCouZuJuvvAc+9SiWwALojasP9rvygIDdK82I6HgL9a9ma1Um0gEN5D
cgaHPyrmUXSgBwYFK4EEACKhZANiAAR1Pv/f5aneSB7ACIDKhqspDb+svh2R62pp
S2Lycc4hP0rR0a0wYO3lb25OFziYAs0sd0LkbjZFhNlx2ecQx+3NzHcM45p5MSut
x0ylVN27SB2MBAOsPC0ca7vcTcB4cak=
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,5 @@
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEdT7/3+Wp3kgewAiAyoarKQ2/rL4dketq
aUti8nHOIT9K0dGtMGDt5W9uThc4mALNLHdC5G42RYTZcdnnEMftzcx3DOOaeTEr
rcdMpVTdu0gdjAQDrDwtHGu73E3AeHGp
-----END PUBLIC KEY-----

View File

@ -0,0 +1,10 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQAIw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIB7pEkW3rt95hcMEixYS/fZYvsyy6I0mA7cDtYTKJ1oc3X4I/mciMU
3m6t/wy8Px8FaprF3CsAQgDq499zx/ZTUnygBwYFK4EEACOhgYkDgYYABAEb/gVt
YMU1snsv8IpeknNomj7rv4gbBCo4tgfFAYFvryKfiUh807rdXUdVKZBZ2XudxJEu
j+ZEzZuXAQPq/dvNiQHlbbx5CbJcLj44vjO9Wadw8zqLLaf5HljAdH9c5Y5dD9Ql
YXKQuceE/w95xDv80Xbi9UdBb+YBOs2i5vF+HGQ+Zg==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBG/4FbWDFNbJ7L/CKXpJzaJo+67+I
GwQqOLYHxQGBb68in4lIfNO63V1HVSmQWdl7ncSRLo/mRM2blwED6v3bzYkB5W28
eQmyXC4+OL4zvVmncPM6iy2n+R5YwHR/XOWOXQ/UJWFykLnHhP8PecQ7/NF24vVH
QW/mATrNoubxfhxkPmY=
-----END PUBLIC KEY-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAqNYHjNfiXl2SPu7NYSkd5RiF45bo4c/WW4K1NwH+iBwOOb97
0RvwlcyhctsvrtrUAJ046Z+3LgW27MR73NdcBP6z756XWIQ6CV6XlowG9NlgnEmO
olh3XujZuNig+/05anzhTJr6Xl7uxh8o61VQgBjOgDma7cnEJNz2s89nu9f+WOMN
age63O02ecA17TsZjU2jYcOCnV5UhsIyVRUcB3S2Jtk8FtRGBIydbkFHCX61atyh
8GjzXYpneVPxTm1fRr+qUvGgNJqvK2HhuOzP6wRpcyKS6cl47I9Mu4L36pavtB/W
OTXNhtduSYbUvMnkifAGuYTJHpT2e5QzSm2p7QIDAQABAoIBAD71qVUdQD3+WMX9
GTttMMe40FzhbrCeZ4oygM8lgDxTcf6Y6Fw/NgU2jNIe6HYlBj/AYXPFanLQELnU
P3hIGvmF3aWyly+cB2RtEMxEJWfzE/LT/0uM/961qFMk/Utaz6llLkL52/mLHzFk
d0bixqUuGvt3/81Cm1E/0QOWiVFjjEjEjqjAroeMHYSuxiSjkJVq/MlxekkxDse5
2MVqSMUXXCSt1opwVz2eQfPMIHQ6XEdGL99rvn+4eEeRh5ozzwtjV7Fj6TTHvBug
mw0GqYJP9ZmFYqus6aODYJK7wVLDEYRZCP/1pXH89dg7PXQRsYNUfk+5SD2OsCGS
lCi2FUECgYEA1djfJgLXpMaNuUaXd7V4bnkN38XC1Of8dubiGO5bFAym1qnExUQm
JSq6Uubs3bRLSaa1i1EQqvIB/bBLm6we4m/jQbuNl8+m6dODaZQ2XVWmMWK84zDs
RyJL2U+qDbSwt346AI3b3p+rtWlQLftx3qwj0+neAU4Jopfdz6R4HN0CgYEAyh3Q
Lx9gsrVF3crgejhJiEf4DLiGAglGAJpekeSllPhXWGF/0hw3YurhTm9swLumHhj0
MvDNjVHz/fm+W5uLvkO0CW2hfoOtG3cj3AbnVu34v323EK27wTsp3ADMmykKk7Vc
7rRtKUNuTawL+rL7ImvRhgiA/1La0NKkIk/yKFECgYA+XMh2pU2N1/lslpGFb353
eQX6xAlVXXHamupEqv4Ukrv0zf18KauWGyfLFVzuKq3qBJvczP83WuwJt2fVNhSr
pEV53eAk7YGXnv8rwvU3ssqMjGbZOmMtO2szLHBDHvaKDF2N29DK03WXqeagAjvq
2/h2zDTZ3JAiwncFHsmIYQKBgQCtgZJqIYGdaEgl8jdqNE/Lkhy6rt07ekKB2gM9
Mw+CJv60fF6CPH6/h+sKrlSpz2z3gWyBHn8Wxjey22YytgDswKoZPefY96jvXwOF
qteFpJxZps40nzAC3Ys9teHvgPf3fUXeFT1wotZaD0vpVySHlA82BK7YgIQfp/QH
EKJNQQKBgGrBrO7ubNjF5MJtAx3MgvdvdMJpbn0hTlKwYBvqPImoSxZ2JfXwms02
SbQa0jUHX0j86/4bUkg2r9PNqtSoWo9XQpfyZ/OMLlts014CoVmyL5whzNBqoFhi
dhyh1tzCoRoIrTucZZL9J7r/M565CbZEHcXcPMdtnkjR1CD4fzbV
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqNYHjNfiXl2SPu7NYSkd
5RiF45bo4c/WW4K1NwH+iBwOOb970RvwlcyhctsvrtrUAJ046Z+3LgW27MR73Ndc
BP6z756XWIQ6CV6XlowG9NlgnEmOolh3XujZuNig+/05anzhTJr6Xl7uxh8o61VQ
gBjOgDma7cnEJNz2s89nu9f+WOMNage63O02ecA17TsZjU2jYcOCnV5UhsIyVRUc
B3S2Jtk8FtRGBIydbkFHCX61atyh8GjzXYpneVPxTm1fRr+qUvGgNJqvK2HhuOzP
6wRpcyKS6cl47I9Mu4L36pavtB/WOTXNhtduSYbUvMnkifAGuYTJHpT2e5QzSm2p
7QIDAQAB
-----END PUBLIC KEY-----