mirror of
https://github.com/YGGverse/ggemini.git
synced 2026-04-02 18:15:37 +00:00
use native TlsCertificate
This commit is contained in:
parent
2079bb1167
commit
e437d35acf
5 changed files with 4 additions and 201 deletions
|
|
@ -1,19 +1,18 @@
|
||||||
//! High-level client API to interact with Gemini Socket Server:
|
//! High-level client API to interact with Gemini Socket Server:
|
||||||
//! * https://geminiprotocol.net/docs/protocol-specification.gmi
|
//! * https://geminiprotocol.net/docs/protocol-specification.gmi
|
||||||
|
|
||||||
pub mod certificate;
|
|
||||||
pub mod connection;
|
pub mod connection;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
pub use certificate::Certificate;
|
|
||||||
pub use connection::Connection;
|
pub use connection::Connection;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use response::Response;
|
pub use response::Response;
|
||||||
|
|
||||||
use gio::{
|
use gio::{
|
||||||
prelude::{IOStreamExt, OutputStreamExt, SocketClientExt, TlsConnectionExt},
|
prelude::{IOStreamExt, OutputStreamExt, SocketClientExt, TlsConnectionExt},
|
||||||
Cancellable, SocketClient, SocketClientEvent, SocketProtocol, TlsClientConnection,
|
Cancellable, SocketClient, SocketClientEvent, SocketProtocol, TlsCertificate,
|
||||||
|
TlsClientConnection,
|
||||||
};
|
};
|
||||||
use glib::{object::Cast, Bytes, Priority, Uri};
|
use glib::{object::Cast, Bytes, Priority, Uri};
|
||||||
|
|
||||||
|
|
@ -64,7 +63,7 @@ impl Client {
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
priority: Option<Priority>,
|
priority: Option<Priority>,
|
||||||
cancellable: Option<Cancellable>,
|
cancellable: Option<Cancellable>,
|
||||||
certificate: Option<Certificate>,
|
certificate: Option<TlsCertificate>,
|
||||||
callback: impl Fn(Result<Response, Error>) + 'static,
|
callback: impl Fn(Result<Response, Error>) + 'static,
|
||||||
) {
|
) {
|
||||||
// Toggle socket mode
|
// Toggle socket mode
|
||||||
|
|
@ -84,10 +83,7 @@ impl Client {
|
||||||
Ok(connection) => {
|
Ok(connection) => {
|
||||||
match Connection::new_wrap(
|
match Connection::new_wrap(
|
||||||
&connection,
|
&connection,
|
||||||
match certificate {
|
certificate.as_ref(),
|
||||||
Some(ref certificate) => Some(&certificate.tls_certificate),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
Some(&network_address),
|
Some(&network_address),
|
||||||
) {
|
) {
|
||||||
Ok(result) => request_async(
|
Ok(result) => request_async(
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
pub mod error;
|
|
||||||
pub mod scope;
|
|
||||||
|
|
||||||
pub use error::Error;
|
|
||||||
pub use scope::Scope;
|
|
||||||
|
|
||||||
use gio::{prelude::TlsCertificateExt, TlsCertificate};
|
|
||||||
use glib::DateTime;
|
|
||||||
|
|
||||||
pub struct Certificate {
|
|
||||||
pub scope: Scope,
|
|
||||||
pub tls_certificate: TlsCertificate,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Certificate {
|
|
||||||
// Constructors
|
|
||||||
|
|
||||||
/// Create new `Self`
|
|
||||||
pub fn from_pem(pem: &str, scope_url: &str) -> Result<Self, Error> {
|
|
||||||
Ok(Self {
|
|
||||||
scope: match Scope::from_url(scope_url) {
|
|
||||||
Ok(scope) => scope,
|
|
||||||
Err(reason) => return Err(Error::Scope(reason)),
|
|
||||||
},
|
|
||||||
tls_certificate: match TlsCertificate::from_pem(&pem) {
|
|
||||||
Ok(tls_certificate) => {
|
|
||||||
// Validate expiration time
|
|
||||||
match DateTime::now_local() {
|
|
||||||
Ok(now_local) => {
|
|
||||||
match tls_certificate.not_valid_after() {
|
|
||||||
Some(not_valid_after) => {
|
|
||||||
if now_local > not_valid_after {
|
|
||||||
return Err(Error::Expired(not_valid_after));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => return Err(Error::ValidAfter),
|
|
||||||
}
|
|
||||||
match tls_certificate.not_valid_before() {
|
|
||||||
Some(not_valid_before) => {
|
|
||||||
if now_local < not_valid_before {
|
|
||||||
return Err(Error::Inactive(not_valid_before));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => return Err(Error::ValidBefore),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => return Err(Error::DateTime),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success
|
|
||||||
tls_certificate
|
|
||||||
}
|
|
||||||
Err(reason) => return Err(Error::Decode(reason)),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
use std::fmt::{Display, Formatter, Result};
|
|
||||||
|
|
||||||
use glib::gformat;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
DateTime,
|
|
||||||
Decode(glib::Error),
|
|
||||||
Expired(glib::DateTime),
|
|
||||||
Inactive(glib::DateTime),
|
|
||||||
Scope(crate::client::certificate::scope::Error),
|
|
||||||
ValidAfter,
|
|
||||||
ValidBefore,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
|
||||||
match self {
|
|
||||||
Self::DateTime => {
|
|
||||||
write!(f, "Could not parse local `DateTime`")
|
|
||||||
}
|
|
||||||
Self::Decode(reason) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Could not decode TLS certificate from PEM string: {reason}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::Expired(not_valid_after) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Certificate expired after: {}",
|
|
||||||
match not_valid_after.format_iso8601() {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(_) => gformat!("unknown"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::Inactive(not_valid_before) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Certificate inactive before: {}",
|
|
||||||
match not_valid_before.format_iso8601() {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(_) => gformat!("unknown"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::Scope(reason) => {
|
|
||||||
write!(f, "Certificate inactive before: {reason}")
|
|
||||||
}
|
|
||||||
Self::ValidAfter => write!(f, "Could not get `not_valid_after` value"),
|
|
||||||
Self::ValidBefore => write!(f, "Could not get `not_valid_before` value"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
pub mod error;
|
|
||||||
pub use error::Error;
|
|
||||||
|
|
||||||
use crate::DEFAULT_PORT;
|
|
||||||
use gio::NetworkAddress;
|
|
||||||
use glib::{GString, Uri, UriFlags, UriHideFlags};
|
|
||||||
|
|
||||||
/// Scope implement path prefix to apply TLS authorization for
|
|
||||||
/// * external validator MAY decline `Certificate` if `Scope` defined out of protocol range
|
|
||||||
/// * [read more](https://geminiprotocol.net/docs/protocol-specification.gmi#status-60)
|
|
||||||
pub struct Scope {
|
|
||||||
uri: Uri,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scope {
|
|
||||||
// Constructors
|
|
||||||
|
|
||||||
/// Create new `Self` for given `url`
|
|
||||||
pub fn from_url(url: &str) -> Result<Self, Error> {
|
|
||||||
match Uri::parse(url, UriFlags::NONE) {
|
|
||||||
Ok(uri) => {
|
|
||||||
if !uri.scheme().to_lowercase().contains("gemini") {
|
|
||||||
return Err(Error::Scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
if uri.host().is_none() {
|
|
||||||
return Err(Error::Host);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self { uri })
|
|
||||||
}
|
|
||||||
Err(reason) => Err(Error::Uri(reason)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
|
|
||||||
/// Get `Scope` string match [Specification](https://geminiprotocol.net/docs/protocol-specification.gmi#status-60)
|
|
||||||
pub fn to_string(&self) -> GString {
|
|
||||||
self.uri
|
|
||||||
.to_string_partial(UriHideFlags::QUERY | UriHideFlags::FRAGMENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get [NetworkAddress](https://docs.gtk.org/gio/class.NetworkAddress.html)
|
|
||||||
/// implement [SocketConnectable](https://docs.gtk.org/gio/iface.SocketConnectable.html) interface
|
|
||||||
/// * useful as [SNI](https://geminiprotocol.net/docs/protocol-specification.gmi#server-name-indication) in TLS context
|
|
||||||
pub fn to_network_address(&self) -> Result<NetworkAddress, Error> {
|
|
||||||
match crate::gio::network_address::from_uri(&self.uri, DEFAULT_PORT) {
|
|
||||||
Ok(network_address) => Ok(network_address),
|
|
||||||
Err(reason) => Err(Error::NetworkAddress(reason)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
use std::fmt::{Display, Formatter, Result};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
Host,
|
|
||||||
NetworkAddress(crate::gio::network_address::Error),
|
|
||||||
Scheme,
|
|
||||||
Uri(glib::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
|
||||||
match self {
|
|
||||||
Self::Host => {
|
|
||||||
write!(f, "Host required")
|
|
||||||
}
|
|
||||||
Self::NetworkAddress(reason) => {
|
|
||||||
write!(f, "Could not parse network address: {reason}")
|
|
||||||
}
|
|
||||||
Self::Scheme => {
|
|
||||||
write!(f, "Scope does not match `gemini`")
|
|
||||||
}
|
|
||||||
Self::Uri(reason) => {
|
|
||||||
write!(f, "Could not parse URI: {reason}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue