added signatures

This commit is contained in:
Hlars 2025-01-08 17:30:35 +01:00
parent 4ebb2a21d2
commit 47af1e62b7
12 changed files with 609 additions and 31 deletions

View File

@ -7,6 +7,7 @@
"Conn",
"dotenv",
"hmac",
"minisign",
"oneshot",
"openapi",
"recv",

120
Cargo.lock generated
View File

@ -215,10 +215,12 @@ dependencies = [
"error-stack",
"hmac",
"ldap3",
"minisign",
"once_cell",
"rand",
"rust-argon2",
"serde",
"serde_json",
"sha2",
"sqlx",
"strum",
@ -396,6 +398,16 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "4.5.23"
@ -600,6 +612,12 @@ dependencies = [
"typenum",
]
[[package]]
name = "ct-codecs"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b916ba8ce9e4182696896f015e8a5ae6081b305f74690baa8465e35f5a142ea4"
[[package]]
name = "der"
version = "0.7.9"
@ -1264,6 +1282,15 @@ dependencies = [
"serde",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@ -1464,6 +1491,18 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "minisign"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26541387415a1e829df5d532aad019fb11bc723e2b5bc99edefa4cf5bfad0de7"
dependencies = [
"ct-codecs",
"getrandom",
"rpassword",
"scrypt",
]
[[package]]
name = "miniz_oxide"
version = "0.8.2"
@ -1709,6 +1748,16 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
"hmac",
]
[[package]]
name = "pem"
version = "3.0.4"
@ -1961,6 +2010,17 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "rpassword"
version = "7.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
dependencies = [
"libc",
"rtoolbox",
"windows-sys 0.48.0",
]
[[package]]
name = "rsa"
version = "0.9.7"
@ -1981,6 +2041,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rtoolbox"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "rust-argon2"
version = "2.1.0"
@ -2077,6 +2147,15 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
"cipher",
]
[[package]]
name = "same-file"
version = "1.0.6"
@ -2101,6 +2180,17 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scrypt"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
dependencies = [
"pbkdf2",
"salsa20",
"sha2",
]
[[package]]
name = "security-framework"
version = "2.11.1"
@ -3053,8 +3143,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "utoipa"
version = "5.3.0"
source = "git+https://github.com/juhaku/utoipa#3ffad4bed73e5caeddc311bab70f810d1d772079"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0"
dependencies = [
"indexmap",
"serde",
@ -3064,8 +3155,9 @@ dependencies = [
[[package]]
name = "utoipa-axum"
version = "0.1.3"
source = "git+https://github.com/juhaku/utoipa#3ffad4bed73e5caeddc311bab70f810d1d772079"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff0d605008ed085986e1803fd5c81d18c0f8503b1e4bbb21ea75b3fc20dd1efb"
dependencies = [
"axum",
"paste",
@ -3076,8 +3168,9 @@ dependencies = [
[[package]]
name = "utoipa-gen"
version = "5.3.0"
source = "git+https://github.com/juhaku/utoipa#3ffad4bed73e5caeddc311bab70f810d1d772079"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7"
dependencies = [
"proc-macro2",
"quote",
@ -3087,8 +3180,9 @@ dependencies = [
[[package]]
name = "utoipa-redoc"
version = "5.0.0"
source = "git+https://github.com/juhaku/utoipa#3ffad4bed73e5caeddc311bab70f810d1d772079"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33749b636458b2ed3e8ebc633febffb3e4ed7298d9f749e9b71cc25ea2f0eb9f"
dependencies = [
"axum",
"serde",
@ -3098,8 +3192,9 @@ dependencies = [
[[package]]
name = "utoipa-scalar"
version = "0.2.0"
source = "git+https://github.com/juhaku/utoipa#3ffad4bed73e5caeddc311bab70f810d1d772079"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "088e93bf19f6bd06e0aacb02ca432b3c5a449c4aec2e4aa9fc333a667f2b2c55"
dependencies = [
"axum",
"serde",
@ -3109,8 +3204,9 @@ dependencies = [
[[package]]
name = "utoipa-swagger-ui"
version = "8.1.0"
source = "git+https://github.com/juhaku/utoipa#3ffad4bed73e5caeddc311bab70f810d1d772079"
version = "8.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "040cad8bd8de63f3d028e08e5b39be49d68f8a646e99f4aea2e2d4d82c34b21f"
dependencies = [
"axum",
"base64 0.22.1",

View File

@ -26,6 +26,7 @@ config = "0.15.4"
uuid = { version = "1.11.0", features = ["v4"] }
sha2 = "0.10.8"
hmac = "0.12.1"
minisign = "0.7.9"
# axum-jwt-login = { path = "../axum-login-jwt" }
axum-jwt-login = { version = "0.1.0", registry = "kellnr" }
rust-argon2 = "2.1.0"
@ -38,27 +39,17 @@ windows-service = "0.7.0"
axum = { version = "0.8.1", features = ["macros"] }
strum = { version = "0.26", features = ["derive"] }
# utoipa = { version = "5.3.0", features = ["axum_extras"] }
utoipa = { git = "https://github.com/juhaku/utoipa", features = [
"axum_extras",
] }
# utoipa-axum = "0.1.3"
utoipa-axum = { git = "https://github.com/juhaku/utoipa" }
# utoipa-swagger-ui = { version = "8.1.0", features = ["axum"] }
# utoipa-redoc = { version = "*", features = ["axum"] }
# utoipa-scalar = { version = "*", features = ["axum"] }
utoipa-swagger-ui = { git = "https://github.com/juhaku/utoipa", features = [
"axum",
] }
utoipa-redoc = { git = "https://github.com/juhaku/utoipa", features = ["axum"] }
utoipa-scalar = { git = "https://github.com/juhaku/utoipa", features = [
"axum",
] }
utoipa = { version = "5.3.1", features = ["axum_extras"] }
utoipa-axum = "0.1.4"
utoipa-swagger-ui = { version = "8.1.1", features = ["axum"] }
utoipa-redoc = { version = "*", features = ["axum"] }
utoipa-scalar = { version = "*", features = ["axum"] }
ts-rs = { version = "10.1.0", features = ["chrono-impl"] }
# Utilities
# ========================================
serde = { version = "1.0.216", features = ["derive"] }
serde_json = "1.0.134"
tokio = { version = "1.42.0", features = ["full"] }
tokio-util = { version = "0.7.13", features = ["rt"] }
once_cell = "1.20.2"

View File

@ -7,6 +7,7 @@ pub const AUTH_TAG: &str = "Authentication";
pub const USERS_TAG: &str = "Users";
pub const ORDER_TAG: &str = "order";
pub const API_KEY_TAG: &str = "API Keys";
pub const SIGNATURE_TAG: &str = "Signature";
#[derive(OpenApi)]
#[openapi(

View File

@ -1,5 +1,6 @@
pub mod api_keys;
pub mod auth;
mod signature;
pub mod users;
use api_keys::models::ApiKey as APIKey;
@ -27,8 +28,8 @@ macro_rules! login_required {
macro_rules! permission_required {
($($perm:expr),+ $(,)?) => {
axum_jwt_login::permission_required!(
User,
ApiBackend,
crate::api::routes::User,
crate::api::ApiBackend,
crate::api::routes::APIKey,
$($perm),+
)
@ -42,6 +43,7 @@ pub fn create_routes(session: AuthBackendType) -> Router {
.nest(API_BASE, auth::router())
.nest(API_BASE, users::router())
.nest(API_BASE, api_keys::router())
.nest(API_BASE, signature::router())
// .nest(
// "/api/order",
// // order::router().route_layer(crate::login_required!(AuthenticationBackend<ApiKey>)),

View File

@ -0,0 +1,275 @@
use std::io::Cursor;
use axum::{debug_handler, Json};
use chrono::Utc;
use minisign::{KeyPair, PublicKeyBox, SecretKeyBox, SignatureBox};
use models::{
PublicKeyID, SignatureTest, SigningKeyRequest, SigningKeyStatus, VerificationRequest,
};
use utoipa_axum::{router::OpenApiRouter, routes};
use crate::{
api::{
description::SIGNATURE_TAG,
routes::users::permissions::{Permission, PermissionDetail},
},
errors::ApiError,
permission_required,
};
use super::AuthBackendType;
pub mod models;
pub mod sql;
// expose the OpenAPI to parent module
pub fn router() -> OpenApiRouter {
let verify = OpenApiRouter::new().routes(routes!(verify));
let write = OpenApiRouter::new()
.routes(routes!(
check_signing_key,
create_signing_key,
verify_signing_key
))
.routes(routes!(sign))
.route_layer(permission_required!(Permission::Write(
PermissionDetail::Signature
)));
OpenApiRouter::new().merge(write).merge(verify)
}
#[debug_handler]
#[utoipa::path(
get,
path = "/signature/key",
summary = "Check for signing key",
description = "Check if the logged in user has configured a signing key.",
responses(
(status = OK, body = SigningKeyStatus, description = "Signature Status"),
),
security(
("user_auth" = ["write:signature",]),
),
tag = SIGNATURE_TAG)]
pub async fn check_signing_key(
auth_session: AuthBackendType,
) -> Result<Json<SigningKeyStatus>, ApiError> {
let backend = auth_session.backend();
let user = auth_session
.is_authenticated()
.ok_or(ApiError::AccessDenied)?;
let private_key = sql::get_private_key(backend.pool(), &user.user_id).await?;
Ok(Json(SigningKeyStatus {
configured: private_key.key.is_some(),
verified: None,
}))
}
#[debug_handler]
#[utoipa::path(
post,
path = "/signature/key",
summary = "Create signing key",
description = "Create signing key for the currently logged in user.",
request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
responses(
(status = OK, description = "Successfully created signing key"),
),
security(
("user_auth" = ["write:signature",]),
),
tag = SIGNATURE_TAG)]
pub async fn create_signing_key(
auth_session: AuthBackendType,
Json(key_request): Json<SigningKeyRequest>,
) -> Result<(), ApiError> {
let backend = auth_session.backend();
let user = auth_session
.is_authenticated()
.ok_or(ApiError::AccessDenied)?;
// create Key Pair
let KeyPair { pk, sk } = KeyPair::generate_encrypted_keypair(Some(key_request.secret))?;
let public_key = pk.to_box()?.to_string();
let private_key = sk
.to_box(Some(&format!(
"{} {} - {}",
user.name,
user.surname,
Utc::now()
)))? // Optional comment about the key
.to_string();
// store keys in database
sql::update_private_key(backend.pool(), &user.user_id, private_key).await?;
sql::store_public_key(backend.pool(), &user.user_id, public_key).await?;
Ok(())
}
#[debug_handler]
#[utoipa::path(
put,
path = "/signature/key",
summary = "Verify signing key",
description = "Verify signing key for the currently logged in user.",
request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
responses(
(status = OK, body = SigningKeyStatus, description = "Signature Status"),
),
security(
("user_auth" = ["write:signature",]),
),
tag = SIGNATURE_TAG)]
pub async fn verify_signing_key(
auth_session: AuthBackendType,
Json(key_request): Json<SigningKeyRequest>,
) -> Result<Json<SigningKeyStatus>, ApiError> {
let backend = auth_session.backend();
let user = auth_session
.is_authenticated()
.ok_or(ApiError::AccessDenied)?;
// get private key
let status = match key_request
.to_private_key(backend.pool(), &user.user_id)
.await?
{
Some(_key) => SigningKeyStatus {
configured: true,
verified: Some(true),
},
None => SigningKeyStatus {
configured: false,
verified: None,
},
};
Ok(Json(status))
}
#[debug_handler]
#[utoipa::path(
post,
path = "/signature/sign",
summary = "Sign something with your signing key",
description = "Sign something as the currently logged in user.",
request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
responses(
(status = OK, description = "Successfully signed"),
),
security(
("user_auth" = ["write:signature",]),
),
tag = SIGNATURE_TAG)]
pub async fn sign(
auth_session: AuthBackendType,
Json(key_request): Json<SigningKeyRequest>,
) -> Result<Json<VerificationRequest>, ApiError> {
let backend = auth_session.backend();
let user = auth_session
.is_authenticated()
.ok_or(ApiError::AccessDenied)?;
// get private key
let private_key = key_request
.to_private_key(backend.pool(), &user.user_id)
.await?
.ok_or(ApiError::AccessDenied)?;
// get current public key
let public_key_id = sql::get_current_public_key(backend.pool(), &user.user_id)
.await?
.ok_or(ApiError::InternalError(
"No corresponding public key found".to_string(),
))?;
let data = SignatureTest {
value1: "blfjdljfa".to_string(),
value2: false,
value3: vec!["bladfa".to_string(), "noch bla".to_string()],
};
let data_reader = Cursor::new(data.calculate_hash().to_be_bytes());
let signature_box = minisign::sign(
None,
&private_key,
data_reader,
None,
Some(&serde_json::to_string(&public_key_id)?),
)?;
// We have a signature! Let's inspect it a little bit.
println!(
"Untrusted comment: [{}]",
signature_box.untrusted_comment().unwrap()
);
println!(
"Trusted comment: [{}]",
signature_box.trusted_comment().unwrap()
);
// store signature
// Converting the signature box to a string in order to save it is easy.
let signature = signature_box.into_string();
println!("BLA");
println!("{signature}");
Ok(Json(VerificationRequest {
signature,
signed_from: "".to_string(),
}))
}
#[debug_handler]
#[utoipa::path(
get,
path = "/signature/verify",
summary = "Verify a signature",
description = "Verify the signature.",
request_body(content = SigningKeyRequest, description = "Signing Key Request", content_type = "application/json"),
responses(
(status = OK, description = "Successfully signed"),
),
tag = SIGNATURE_TAG)]
pub async fn verify(
auth_session: AuthBackendType,
Json(verification_request): Json<VerificationRequest>,
) -> Result<(), ApiError> {
let backend = auth_session.backend();
// Now, let's verify the signature.
// Assuming we just loaded it into `signature_box_str`, get the box back.
let signature_box = SignatureBox::from_string(&verification_request.signature)?;
// get public key id from signature
let public_key_id: PublicKeyID = serde_json::from_str(&signature_box.untrusted_comment()?)?;
// Load the public key of the user that signed.
let public_key_str = sql::get_public_key_by_id(backend.pool(), public_key_id)
.await?
.key
.ok_or(ApiError::InternalError(
"No corresponding Public Key was found.".to_string(),
))?;
let public_key_box = PublicKeyBox::from_string(&public_key_str)?;
let public_key = public_key_box.into_public_key()?;
// And verify the data.
let data = SignatureTest {
value1: "blfjdljfa".to_string(),
value2: false,
value3: vec!["bladfa".to_string(), "noch bla".to_string()],
};
let data_reader = Cursor::new(data.calculate_hash().to_be_bytes());
let verified = minisign::verify(&public_key, &signature_box, data_reader, true, false, false);
match verified {
Ok(()) => println!("Success!"),
Err(_) => println!("Verification failed"),
};
Ok(())
}

View File

@ -0,0 +1,82 @@
use std::{
fmt::Display,
hash::{DefaultHasher, Hash, Hasher},
};
use chrono::NaiveDateTime;
use minisign::{SecretKey, SecretKeyBox};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use ts_rs::TS;
use utoipa::ToSchema;
use crate::{api::routes::signature::sql, errors::ApiError};
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
#[ts(export)]
pub struct SigningKeyStatus {
pub configured: bool,
pub verified: Option<bool>,
}
pub struct KeyData {
pub key: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicKeyID {
pub timestamp: NaiveDateTime,
pub user_id: String,
}
#[derive(Debug, Clone, Deserialize, TS, ToSchema)]
#[ts(export)]
pub struct SigningKeyRequest {
pub secret: String,
}
impl SigningKeyRequest {
pub async fn to_private_key(
self,
pool: &PgPool,
user_id: &str,
) -> Result<Option<SecretKey>, ApiError> {
// try to get private key from database
let private_key = match sql::get_private_key(pool, user_id).await?.key {
Some(key) => key,
None => return Ok(None),
};
// create MinSign Box from key string
let private_key_box = SecretKeyBox::from_string(&private_key)?;
// and the box can be opened using the password to reveal the original secret key:
let secret_key = private_key_box.into_secret_key(Some(self.secret))?;
Ok(Some(secret_key))
}
}
#[derive(Debug, Clone, Deserialize, Serialize, TS, ToSchema)]
#[ts(export)]
pub struct VerificationRequest {
pub signature: String,
pub signed_from: String,
}
#[derive(Debug, Hash)]
pub struct SignatureTest {
pub value1: String,
pub value2: bool,
pub value3: Vec<String>,
}
impl SignatureTest
where
Self: Hash,
{
pub fn calculate_hash(&self) -> u64 {
let mut s = DefaultHasher::new();
self.hash(&mut s);
s.finish()
}
}

View File

@ -0,0 +1,93 @@
use sqlx::PgPool;
use crate::{
api::routes::signature::models::{KeyData, PublicKeyID},
errors::ApiError,
};
pub async fn get_private_key(pool: &PgPool, user_id: &str) -> Result<KeyData, ApiError> {
Ok(sqlx::query_as!(
KeyData,
r#"SELECT
USERS."PrivateKey" as key
FROM
users
WHERE USERS."UserID" = $1"#,
user_id
)
.fetch_one(pool)
.await?)
}
pub async fn update_private_key(
pool: &PgPool,
user_id: &str,
private_key: String,
) -> Result<(), ApiError> {
sqlx::query!(
r#"UPDATE users SET
"PrivateKey" = $2
WHERE "UserID" = $1"#,
user_id,
private_key
)
.execute(pool)
.await?;
Ok(())
}
pub async fn store_public_key(
pool: &PgPool,
user_id: &str,
public_key: String,
) -> Result<(), ApiError> {
sqlx::query!(
r#"INSERT INTO "PublicKeys"
("UserID", "PublicKey")
VALUES ($1, $2)"#,
user_id,
public_key
)
.execute(pool)
.await?;
Ok(())
}
pub async fn get_current_public_key(
pool: &PgPool,
user_id: &str,
) -> Result<Option<PublicKeyID>, ApiError> {
Ok(sqlx::query_as!(
PublicKeyID,
r#"SELECT
"PublicKeys"."Timestamp" as timestamp,
"PublicKeys"."UserID" as user_id
FROM
"PublicKeys"
WHERE
"PublicKeys"."UserID" = $1
ORDER BY timestamp DESC"#,
user_id,
)
.fetch_optional(pool)
.await?)
}
pub async fn get_public_key_by_id(pool: &PgPool, key_id: PublicKeyID) -> Result<KeyData, ApiError> {
Ok(sqlx::query_as!(
KeyData,
r#"SELECT
"PublicKeys"."PublicKey" as key
FROM
"PublicKeys"
WHERE
"PublicKeys"."UserID" = $1
AND "PublicKeys"."Timestamp" = $2"#,
key_id.user_id,
key_id.timestamp,
)
.fetch_one(pool)
.await?)
}

View File

@ -61,6 +61,7 @@ impl Display for Permission {
pub enum PermissionDetail {
Users,
APIKeys,
Signature,
#[default]
None,
}

View File

@ -6,6 +6,7 @@ use axum::{
Json,
};
use error_stack::Context;
use minisign::PError;
use serde::Serialize;
use sha2::digest::InvalidLength;
use tracing::error;
@ -110,6 +111,18 @@ impl From<InvalidLength> for ApiError {
}
}
impl From<PError> for ApiError {
fn from(value: PError) -> Self {
Self::InternalError(format!("MiniSign Error: {value}"))
}
}
impl From<serde_json::Error> for ApiError {
fn from(value: serde_json::Error) -> Self {
Self::InternalError(format!("Error on (de)serialization: {value}"))
}
}
impl Display for ApiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let error_info = self.as_error_info().1;

View File

@ -6,6 +6,7 @@ CREATE TABLE public.users
"Surname" character varying(250) NOT NULL DEFAULT '',
"Email" character varying(500) NOT NULL DEFAULT '',
"Password" character varying(255) NOT NULL DEFAULT '',
"PrivateKey" text COLLATE,
"CreationDate" timestamp without time zone NOT NULL DEFAULT NOW(),
"LastChanged" timestamp without time zone NOT NULL DEFAULT NOW(),
"StatusFlag" smallint NOT NULL,
@ -17,3 +18,6 @@ ALTER TABLE IF EXISTS public.users
COMMENT ON TABLE public.users
IS 'Table containing user information';
COMMENT ON COLUMN public.users."PrivateKey"
IS 'Private Key of the user with which the user can sign things';

View File

@ -0,0 +1,19 @@
CREATE TABLE IF NOT EXISTS public."PublicKeys"
(
"Timestamp" timestamp without time zone NOT NULL DEFAULT now(),
"UserID" character varying COLLATE pg_catalog."default" NOT NULL,
"PublicKey" text COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT "PublicKeys_pkey" PRIMARY KEY ("Timestamp", "UserID"),
CONSTRAINT "PublicKeyUserID" FOREIGN KEY ("UserID")
REFERENCES public.users ("UserID") MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public."PublicKeys"
OWNER to postgres;
COMMENT ON TABLE public."PublicKeys"
IS 'Public Key Storage for Signatures';