test(twin-github): stop generating rsa keys during tests

This commit is contained in:
Bryan Helmkamp 2026-04-05 11:02:54 -04:00
parent 1d7ef425ab
commit 1b49558f49
No known key found for this signature in database
12 changed files with 138 additions and 315 deletions

View file

@ -116,59 +116,22 @@ pub fn ensure_permission(
mod tests {
use super::*;
use crate::state::derive_public_key_pem;
fn test_rsa_key() -> String {
use std::process::Command;
let output = Command::new("openssl")
.args([
"genpkey",
"-algorithm",
"RSA",
"-pkeyopt",
"rsa_keygen_bits:2048",
])
.output()
.expect("openssl should be available");
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String {
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
iss: String,
iat: i64,
exp: i64,
}
let now = chrono::Utc::now().timestamp();
let claims = Claims {
iss: app_id.to_string(),
iat: now - 60,
exp: now + 600,
};
let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap();
encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap()
}
use crate::test_support::{sign_test_jwt, test_rsa_private_key, test_rsa_public_key};
#[test]
fn verify_valid_jwt() {
let private_pem = test_rsa_key();
let public_pem = derive_public_key_pem(&private_pem);
let jwt = sign_test_jwt("12345", &private_pem);
let result = verify_app_jwt(&jwt, &public_pem);
let private_pem = test_rsa_private_key();
let jwt = sign_test_jwt("12345", private_pem);
let result = verify_app_jwt(&jwt, test_rsa_public_key());
assert!(result.is_ok());
assert_eq!(result.unwrap(), "12345");
}
#[test]
fn reject_invalid_jwt() {
let private_pem = test_rsa_key();
let public_pem = derive_public_key_pem(&private_pem);
let result = verify_app_jwt("invalid.jwt.token", &public_pem);
let derived_public_pem = derive_public_key_pem(test_rsa_private_key());
assert_eq!(derived_public_pem, test_rsa_public_key());
let result = verify_app_jwt("invalid.jwt.token", test_rsa_public_key());
assert!(result.is_err());
}
}

View file

@ -372,21 +372,8 @@ fn next_pr_number(pull_requests: &HashMap<(String, String), Vec<PullRequest>>) -
}
#[cfg(test)]
fn test_rsa_key() -> String {
use std::process::Command;
let output = Command::new("openssl")
.args([
"genpkey",
"-algorithm",
"RSA",
"-pkeyopt",
"rsa_keygen_bits:2048",
])
.output()
.expect("openssl should be available");
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
fn test_rsa_key() -> &'static str {
crate::test_support::test_rsa_private_key()
}
#[cfg(test)]
@ -548,7 +535,7 @@ impl FixtureState {
slug: "fixture-app".to_string(),
owner_login: "acme".to_string(),
public: true,
private_key_pem: test_rsa_key(),
private_key_pem: test_rsa_key().to_string(),
webhook_secret: Some("whsec".to_string()),
}],
..Self::default()

View file

@ -113,59 +113,23 @@ pub async fn patch_webhook_config(
mod tests {
use crate::server::TestServer;
use crate::state::{AppOptions, AppState};
fn test_rsa_key() -> String {
use std::process::Command;
let output = Command::new("openssl")
.args([
"genpkey",
"-algorithm",
"RSA",
"-pkeyopt",
"rsa_keygen_bits:2048",
])
.output()
.expect("openssl should be available");
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String {
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
iss: String,
iat: i64,
exp: i64,
}
let now = chrono::Utc::now().timestamp();
let claims = Claims {
iss: app_id.to_string(),
iat: now - 60,
exp: now + 600,
};
let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap();
encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap()
}
use crate::test_support::{sign_test_jwt, test_rsa_private_key};
#[tokio::test]
async fn get_app_returns_app_info() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "my-app".to_string(),
owner_login: "my-org".to_string(),
public: true,
private_key_pem: pem.clone(),
private_key_pem: pem.to_string(),
webhook_secret: None,
});
let server = TestServer::start(state).await;
let jwt = sign_test_jwt("12345", &pem);
let jwt = sign_test_jwt("12345", pem);
let client = reqwest::Client::new();
let resp = client
.get(&format!("{}/app", server.url()))
@ -185,14 +149,14 @@ mod tests {
#[tokio::test]
async fn get_app_rejects_invalid_jwt() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "my-app".to_string(),
owner_login: "my-org".to_string(),
public: true,
private_key_pem: pem,
private_key_pem: pem.to_string(),
webhook_secret: None,
});
let server = TestServer::start(state).await;
@ -211,14 +175,14 @@ mod tests {
#[tokio::test]
async fn get_apps_slug_public() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "my-app".to_string(),
owner_login: "my-org".to_string(),
public: true,
private_key_pem: pem,
private_key_pem: pem.to_string(),
webhook_secret: None,
});
let server = TestServer::start(state).await;
@ -238,14 +202,14 @@ mod tests {
#[tokio::test]
async fn get_apps_slug_private_returns_404() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "private-app".to_string(),
owner_login: "my-org".to_string(),
public: false,
private_key_pem: pem,
private_key_pem: pem.to_string(),
webhook_secret: None,
});
let server = TestServer::start(state).await;

View file

@ -93,43 +93,7 @@ pub async fn get_branch(
mod tests {
use crate::server::TestServer;
use crate::state::{AppOptions, AppState};
fn test_rsa_key() -> String {
use std::process::Command;
let output = Command::new("openssl")
.args([
"genpkey",
"-algorithm",
"RSA",
"-pkeyopt",
"rsa_keygen_bits:2048",
])
.output()
.expect("openssl should be available");
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String {
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
iss: String,
iat: i64,
exp: i64,
}
let now = chrono::Utc::now().timestamp();
let claims = Claims {
iss: app_id.to_string(),
iat: now - 60,
exp: now + 600,
};
let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap();
encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap()
}
use crate::test_support::{sign_test_jwt, test_rsa_private_key};
async fn get_installation_token(
client: &reqwest::Client,
@ -167,14 +131,14 @@ mod tests {
#[tokio::test]
async fn branch_exists_returns_200() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
public: true,
private_key_pem: pem.clone(),
private_key_pem: pem.to_string(),
webhook_secret: None,
});
state.add_installation("100", "owner", vec!["repo".to_string()], false);
@ -186,7 +150,7 @@ mod tests {
);
let server = TestServer::start(state).await;
let jwt = sign_test_jwt("100", &pem);
let jwt = sign_test_jwt("100", pem);
let client = reqwest::Client::new();
let token = get_installation_token(&client, &jwt, "owner", "repo", server.url()).await;
@ -210,21 +174,21 @@ mod tests {
#[tokio::test]
async fn branch_not_found_returns_404() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
public: true,
private_key_pem: pem.clone(),
private_key_pem: pem.to_string(),
webhook_secret: None,
});
state.add_installation("100", "owner", vec!["repo".to_string()], false);
state.add_repository("owner", "repo", vec!["main".to_string()], false);
let server = TestServer::start(state).await;
let jwt = sign_test_jwt("100", &pem);
let jwt = sign_test_jwt("100", pem);
let client = reqwest::Client::new();
let token = get_installation_token(&client, &jwt, "owner", "repo", server.url()).await;

View file

@ -614,43 +614,7 @@ fn extract_unquoted_value(s: &str, key: &str) -> Option<String> {
mod tests {
use crate::server::TestServer;
use crate::state::{AppOptions, AppState, PullRequest};
fn test_rsa_key() -> String {
use std::process::Command;
let output = Command::new("openssl")
.args([
"genpkey",
"-algorithm",
"RSA",
"-pkeyopt",
"rsa_keygen_bits:2048",
])
.output()
.expect("openssl should be available");
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String {
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
iss: String,
iat: i64,
exp: i64,
}
let now = chrono::Utc::now().timestamp();
let claims = Claims {
iss: app_id.to_string(),
iat: now - 60,
exp: now + 600,
};
let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap();
encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap()
}
use crate::test_support::{sign_test_jwt, test_rsa_private_key};
async fn get_installation_token(
client: &reqwest::Client,
@ -718,10 +682,10 @@ mod tests {
#[tokio::test]
async fn viewer_query_returns_id() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.viewer_id = "U_testviewer".to_string();
let (server, client, token) = setup_with_token(&mut state, &pem).await;
let (server, client, token) = setup_with_token(&mut state, pem).await;
let resp = client
.post(&format!("{}/graphql", server.url()))
@ -742,7 +706,7 @@ mod tests {
#[tokio::test]
async fn enable_auto_merge_mutation() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
// Pre-seed a PR
state
@ -768,7 +732,7 @@ mod tests {
updated_at: "2026-01-01T00:00:00Z".to_string(),
auto_merge: None,
});
let (server, client, token) = setup_with_token(&mut state, &pem).await;
let (server, client, token) = setup_with_token(&mut state, pem).await;
let query = r#"mutation {
enablePullRequestAutoMerge(input: {pullRequestId: "PR_test123", mergeMethod: SQUASH}) {
@ -824,7 +788,7 @@ mod tests {
#[tokio::test]
async fn org_project_query_returns_node_id() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.projects.push(crate::state::Project {
node_id: "PVT_org123".to_string(),
@ -862,7 +826,7 @@ mod tests {
},
}],
});
let (server, client, token) = setup_with_token(&mut state, &pem).await;
let (server, client, token) = setup_with_token(&mut state, pem).await;
// Query org project
let query = r#"
@ -894,7 +858,7 @@ mod tests {
#[tokio::test]
async fn project_items_query_returns_paginated() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.projects.push(crate::state::Project {
node_id: "PVT_test".to_string(),
@ -919,7 +883,7 @@ mod tests {
},
}],
});
let (server, client, token) = setup_with_token(&mut state, &pem).await;
let (server, client, token) = setup_with_token(&mut state, pem).await;
let query = r#"
query($projectId: ID!, $cursor: String) {
@ -978,7 +942,7 @@ mod tests {
#[tokio::test]
async fn update_project_item_field_value() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.projects.push(crate::state::Project {
node_id: "PVT_test".to_string(),
@ -1012,7 +976,7 @@ mod tests {
},
}],
});
let (server, client, token) = setup_with_token(&mut state, &pem).await;
let (server, client, token) = setup_with_token(&mut state, pem).await;
let query = r#"
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {

View file

@ -178,61 +178,25 @@ pub async fn create_access_token(
mod tests {
use crate::server::TestServer;
use crate::state::{AppOptions, AppState};
fn test_rsa_key() -> String {
use std::process::Command;
let output = Command::new("openssl")
.args([
"genpkey",
"-algorithm",
"RSA",
"-pkeyopt",
"rsa_keygen_bits:2048",
])
.output()
.expect("openssl should be available");
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String {
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
iss: String,
iat: i64,
exp: i64,
}
let now = chrono::Utc::now().timestamp();
let claims = Claims {
iss: app_id.to_string(),
iat: now - 60,
exp: now + 600,
};
let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap();
encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap()
}
use crate::test_support::{sign_test_jwt, test_rsa_private_key};
#[tokio::test]
async fn get_installation_returns_id() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
public: true,
private_key_pem: pem.clone(),
private_key_pem: pem.to_string(),
webhook_secret: None,
});
state.add_installation("100", "owner", vec!["repo".to_string()], false);
state.add_repository("owner", "repo", vec!["main".to_string()], false);
let server = TestServer::start(state).await;
let jwt = sign_test_jwt("100", &pem);
let jwt = sign_test_jwt("100", pem);
let client = reqwest::Client::new();
let resp = client
.get(&format!("{}/repos/owner/repo/installation", server.url()))
@ -250,20 +214,20 @@ mod tests {
#[tokio::test]
async fn get_installation_returns_404_when_not_installed() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
public: true,
private_key_pem: pem.clone(),
private_key_pem: pem.to_string(),
webhook_secret: None,
});
// No installation added
let server = TestServer::start(state).await;
let jwt = sign_test_jwt("100", &pem);
let jwt = sign_test_jwt("100", pem);
let client = reqwest::Client::new();
let resp = client
.get(&format!("{}/repos/owner/repo/installation", server.url()))
@ -278,20 +242,20 @@ mod tests {
#[tokio::test]
async fn get_installation_returns_403_when_suspended() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
public: true,
private_key_pem: pem.clone(),
private_key_pem: pem.to_string(),
webhook_secret: None,
});
state.add_installation("100", "owner", vec!["repo".to_string()], true); // suspended
let server = TestServer::start(state).await;
let jwt = sign_test_jwt("100", &pem);
let jwt = sign_test_jwt("100", pem);
let client = reqwest::Client::new();
let resp = client
.get(&format!("{}/repos/owner/repo/installation", server.url()))
@ -306,20 +270,20 @@ mod tests {
#[tokio::test]
async fn create_access_token_returns_201() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
public: true,
private_key_pem: pem.clone(),
private_key_pem: pem.to_string(),
webhook_secret: None,
});
let install_id = state.add_installation("100", "owner", vec!["repo".to_string()], false);
let server = TestServer::start(state).await;
let jwt = sign_test_jwt("100", &pem);
let jwt = sign_test_jwt("100", pem);
let client = reqwest::Client::new();
let resp = client
.post(&format!(
@ -344,20 +308,20 @@ mod tests {
#[tokio::test]
async fn create_access_token_returns_422_for_unauthorized_repo() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "100".to_string(),
slug: "test-app".to_string(),
owner_login: "owner".to_string(),
public: true,
private_key_pem: pem.clone(),
private_key_pem: pem.to_string(),
webhook_secret: None,
});
let install_id = state.add_installation("100", "owner", vec!["repo".to_string()], false);
let server = TestServer::start(state).await;
let jwt = sign_test_jwt("100", &pem);
let jwt = sign_test_jwt("100", pem);
let client = reqwest::Client::new();
let resp = client
.post(&format!(

View file

@ -303,43 +303,7 @@ fn pr_to_json(pr: &PullRequest) -> serde_json::Value {
mod tests {
use crate::server::TestServer;
use crate::state::{AppOptions, AppState};
fn test_rsa_key() -> String {
use std::process::Command;
let output = Command::new("openssl")
.args([
"genpkey",
"-algorithm",
"RSA",
"-pkeyopt",
"rsa_keygen_bits:2048",
])
.output()
.expect("openssl should be available");
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String {
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
iss: String,
iat: i64,
exp: i64,
}
let now = chrono::Utc::now().timestamp();
let claims = Claims {
iss: app_id.to_string(),
iat: now - 60,
exp: now + 600,
};
let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap();
encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap()
}
use crate::test_support::{sign_test_jwt, test_rsa_private_key};
async fn get_installation_token(
client: &reqwest::Client,
@ -405,9 +369,9 @@ mod tests {
#[tokio::test]
async fn create_pr_returns_201() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
let (server, client, token) = setup_and_get_token(&mut state, &pem).await;
let (server, client, token) = setup_and_get_token(&mut state, pem).await;
let resp = client
.post(&format!("{}/repos/owner/repo/pulls", server.url()))
@ -435,9 +399,9 @@ mod tests {
#[tokio::test]
async fn get_pr_returns_detail() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
let (server, client, token) = setup_and_get_token(&mut state, &pem).await;
let (server, client, token) = setup_and_get_token(&mut state, pem).await;
// Create a PR first
let create_resp = client
@ -485,9 +449,9 @@ mod tests {
#[tokio::test]
async fn merge_pr_returns_200() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
let (server, client, token) = setup_and_get_token(&mut state, &pem).await;
let (server, client, token) = setup_and_get_token(&mut state, pem).await;
// Create a PR
let create_resp = client
@ -530,9 +494,9 @@ mod tests {
#[tokio::test]
async fn close_pr_returns_200() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
let (server, client, token) = setup_and_get_token(&mut state, &pem).await;
let (server, client, token) = setup_and_get_token(&mut state, pem).await;
// Create a PR
let create_resp = client
@ -562,9 +526,9 @@ mod tests {
#[tokio::test]
async fn merge_nonexistent_pr_returns_404() {
let pem = test_rsa_key();
let pem = test_rsa_private_key();
let mut state = AppState::new();
let (server, client, token) = setup_and_get_token(&mut state, &pem).await;
let (server, client, token) = setup_and_get_token(&mut state, pem).await;
let resp = client
.put(&format!(

View file

@ -10,6 +10,8 @@ pub mod fixtures;
pub mod handlers;
pub mod server;
pub mod state;
#[cfg(test)]
pub mod test_support;
pub use server::TestServer;
pub use state::AppState;

View file

@ -429,6 +429,7 @@ pub fn init_git_repos(state: &mut AppState, git_root: &std::path::Path) -> Resul
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::{test_rsa_private_key, test_rsa_public_key};
#[test]
fn empty_state_has_no_apps() {
@ -436,22 +437,6 @@ mod tests {
assert!(state.apps.is_empty());
}
fn test_rsa_key() -> String {
use std::process::Command;
let output = Command::new("openssl")
.args([
"genpkey",
"-algorithm",
"RSA",
"-pkeyopt",
"rsa_keygen_bits:2048",
])
.output()
.expect("openssl should be available");
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
#[test]
fn repository_has_private_field() {
let mut state = AppState::new();
@ -475,19 +460,17 @@ mod tests {
#[test]
fn can_register_app() {
let pem = test_rsa_key();
let mut state = AppState::new();
state.register_app(AppOptions {
app_id: "12345".to_string(),
slug: "test-app".to_string(),
owner_login: "test-owner".to_string(),
public: true,
private_key_pem: pem,
private_key_pem: test_rsa_private_key().to_string(),
webhook_secret: Some("secret".to_string()),
});
assert_eq!(state.apps.len(), 1);
assert_eq!(state.apps["12345"].config.slug, "test-app");
// Verify public key was derived
assert!(state.apps["12345"].public_key_pem.contains("PUBLIC KEY"));
assert_eq!(state.apps["12345"].public_key_pem, test_rsa_public_key());
}
}

View file

@ -0,0 +1,31 @@
#[cfg(test)]
pub fn test_rsa_private_key() -> &'static str {
include_str!("testdata/rsa_private.pem")
}
#[cfg(test)]
pub fn test_rsa_public_key() -> &'static str {
include_str!("testdata/rsa_public.pem")
}
#[cfg(test)]
pub fn sign_test_jwt(app_id: &str, private_key_pem: &str) -> String {
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
#[derive(Serialize)]
struct Claims {
iss: String,
iat: i64,
exp: i64,
}
let now = chrono::Utc::now().timestamp();
let claims = Claims {
iss: app_id.to_string(),
iat: now - 60,
exp: now + 600,
};
let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap();
encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap()
}

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCty51n9z4E+fGI
7m/AC2ZS5PoLgi3ZR8XyUwy10XdbVKQvCCJ04cUgml2TCqA5dFEFsj9a+Ooyz9hE
J70g9U8iFBq53lx3Og3cvZUy1vdnwInMQhdXzmJAdkqZl4/aW7V/571yXfDPZBb5
295Iof3FMEwWzYQMiQm7kZrshRirQBfvlvpIu0N30ZwmyC1MbH0ESBq86owLiVzf
0IBO4dOxmvSTX12Qd1v7+N3hPqMyP0F4PQJM7uLRfHYIO8NVclpS+crj4GJMp9Cj
r1BJL0GvRDZsWazzLg/PVt0sEFJMmY0HoSQCZ2ybqXiD46VXDVL/q0ioxje9bE8X
ZGEUWM1ZAgMBAAECgf8PerJhVry3lzYbrvmXH6FRLRcRsPsSYNcRAYXdASih+C6G
mNVYAAV8rwf4otCfObgEQakkkFKAN1uG3eJocS9HdacMjmC+XpmlrrMz1aYwOfRA
hvE0sUxNU/o6BeuG1HMFkI+iwon6FjAWg+LdzQyGrKdies4BhFPyNI38AvkDM+BR
0z8xOESh+GkMjRcArIxKnzlFaj+BOOza6Ju3cAZfK7Sw+YvxIu7ECB8Mf+0uEX5Y
95AtnD7v3bA/kij1Ate3po9KWr7jfBBFwlNuqYFZ28Y9nSg+p69EMmZK9YQseFwo
72t4+lM60J1MuK08khVvWHsHyZzbCquKKyoVlE0CgYEA19UUbCbwIi2KgJA+5YFF
DWx5KQXo8t6tDa2jWcCorzm/y3r6jCf7Ja6zIjyZFGtqZw+5z6pD7/T2QUc/GIHO
JXo6CDJwyrmcPYb0qmqunCApfpSdXy22Dk43sDcanLF0+cLYF13T25WiebNxswtC
6knS8Nj2gIMdLGqwhgXxalUCgYEAziPDhKbh0GVTE09JLbZXvETuzOoPIQVSHq52
0/eTxxp5DpNhERn/GGFkXYlg9XoYye+cvf7t3vhYUdefxl1qbnLp/aaq/pH4m4Zb
sXeTH+fcyG8e+H1gCnKL7mLc733JcQ/PkT2VemWwE59nobqC8a4S3jSVLgb830J8
Tj+z4vUCgYBlmRYt+v5a+FkUP9J7sqhQs1ktaGzJtux7XdthKoG9Sk/wdwlt5Hwi
tuP4H9JnfFDwBsUO7jVgxU0OWX1oCeKCXdDnfub9ZvYXOGn/toxPTgx6W3bhEOF8
RnGMb8QFMru/vmp+RVqvKPb8lu3UvF5ZbzruyRLwV8fp2qcRz8ZeqQKBgAvF8SG/
YkBDuTXuHlDVDDe9fCIJ8ClDo4gLENBUYIVLlsRlWHLgmDgtP80zzmzWt1m5LKuR
RsaoSmsKNJ7gsPZz67dH8JYvghJn3w7ItIdq96oe5pbsmjKeji1kShgHiBzD+Wfj
K4ldC2xje0i6XUYuWfeE60xAq+qYYn8Zlk2NAoGACNwVYYdj7YkjfclzggBn08qV
K8GAQQ/p6Cnsq1/2YHtugEeAtASazR8GqOzV6wy0nGi6XaH6Kw5cDZ7ACETocSX+
wM30YkPJBg0WUsCzsLg6B1ri8gnJCUBp2IwMeSU+Av5GG09+oqEwawI5WoQO1+TD
rUZ9gbbIAXA79BD11L4=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArcudZ/c+BPnxiO5vwAtm
UuT6C4It2UfF8lMMtdF3W1SkLwgidOHFIJpdkwqgOXRRBbI/WvjqMs/YRCe9IPVP
IhQaud5cdzoN3L2VMtb3Z8CJzEIXV85iQHZKmZeP2lu1f+e9cl3wz2QW+dveSKH9
xTBMFs2EDIkJu5Ga7IUYq0AX75b6SLtDd9GcJsgtTGx9BEgavOqMC4lc39CATuHT
sZr0k19dkHdb+/jd4T6jMj9BeD0CTO7i0Xx2CDvDVXJaUvnK4+BiTKfQo69QSS9B
r0Q2bFms8y4Pz1bdLBBSTJmNB6EkAmdsm6l4g+OlVw1S/6tIqMY3vWxPF2RhFFjN
WQIDAQAB
-----END PUBLIC KEY-----