aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/src/util')
-rw-r--r--crates/shirabe/src/util/forgejo.rs2
-rw-r--r--crates/shirabe/src/util/mod.rs2
-rw-r--r--crates/shirabe/src/util/tls_helper.rs149
3 files changed, 152 insertions, 1 deletions
diff --git a/crates/shirabe/src/util/forgejo.rs b/crates/shirabe/src/util/forgejo.rs
index 4ca4710..3a51be5 100644
--- a/crates/shirabe/src/util/forgejo.rs
+++ b/crates/shirabe/src/util/forgejo.rs
@@ -124,7 +124,7 @@ impl Forgejo {
Err(e) => {
let code = e
.downcast_ref::<crate::downloader::TransportException>()
- .and_then(|te| te.get_status_code())
+ .map(|te| te.code)
.unwrap_or(0);
if [403, 401, 404].contains(&code) {
self.io.write_error3(
diff --git a/crates/shirabe/src/util/mod.rs b/crates/shirabe/src/util/mod.rs
index d34fce5..bd51b66 100644
--- a/crates/shirabe/src/util/mod.rs
+++ b/crates/shirabe/src/util/mod.rs
@@ -27,6 +27,7 @@ pub mod stream_context_factory;
pub mod svn;
pub mod sync_helper;
pub mod tar;
+pub mod tls_helper;
pub mod url;
pub mod zip;
@@ -58,5 +59,6 @@ pub use stream_context_factory::*;
pub use svn::*;
pub use sync_helper::*;
pub use tar::*;
+pub use tls_helper::*;
pub use url::*;
pub use zip::*;
diff --git a/crates/shirabe/src/util/tls_helper.rs b/crates/shirabe/src/util/tls_helper.rs
new file mode 100644
index 0000000..6422be3
--- /dev/null
+++ b/crates/shirabe/src/util/tls_helper.rs
@@ -0,0 +1,149 @@
+//! ref: composer/src/Composer/Util/TlsHelper.php
+
+use shirabe_external_packages::composer::ca_bundle::ca_bundle::CaBundle;
+use shirabe_external_packages::composer::pcre::Preg;
+use shirabe_php_shim::{
+ PhpMixed, ltrim, preg_quote, str_replace, strtolower, substr, substr_count,
+};
+
+/// Extracted certificate names. Mirrors PHP's `array{cn: string, san: string[]}`.
+#[derive(Debug, Clone)]
+pub struct CertificateNames {
+ pub cn: String,
+ pub san: Vec<String>,
+}
+
+/// Match hostname against a certificate.
+///
+/// @deprecated Use composer/ca-bundle and composer/composer 2.2 if you still need PHP 5
+/// compatibility, this class will be removed in Composer 3.0
+#[derive(Debug)]
+pub struct TlsHelper;
+
+impl TlsHelper {
+ /// Match hostname against a certificate. Sets `cn` to the common name of the
+ /// certificate iff a match is found.
+ pub fn check_certificate_host(
+ certificate: &PhpMixed,
+ hostname: &str,
+ cn: &mut Option<String>,
+ ) -> bool {
+ let names = Self::get_certificate_names(certificate);
+
+ let Some(names) = names else {
+ return false;
+ };
+
+ let mut combined_names = names.san.clone();
+ combined_names.push(names.cn.clone());
+ let hostname = strtolower(hostname);
+
+ for cert_name in &combined_names {
+ let matcher = Self::cert_name_matcher(cert_name);
+
+ if let Some(matcher) = matcher
+ && matcher(&hostname)
+ {
+ *cn = Some(names.cn.clone());
+
+ return true;
+ }
+ }
+
+ false
+ }
+
+ /// Extract DNS names out of an X.509 certificate.
+ pub fn get_certificate_names(certificate: &PhpMixed) -> Option<CertificateNames> {
+ let info: Option<&PhpMixed> = if certificate.as_array().is_some() {
+ Some(certificate)
+ } else if CaBundle::is_openssl_parse_safe() {
+ // TODO(phase-c): openssl_x509_parse on a PEM string certificate.
+ todo!("openssl_x509_parse for non-array certificates")
+ } else {
+ None
+ };
+
+ let info = info?.as_array()?;
+
+ let common_name = info
+ .get("subject")
+ .and_then(|s| s.as_array())
+ .and_then(|s| s.get("commonName"))
+ .and_then(|c| c.as_string());
+
+ let common_name = strtolower(common_name?);
+ let mut subject_alt_names: Vec<String> = Vec::new();
+
+ if let Some(san) = info
+ .get("extensions")
+ .and_then(|e| e.as_array())
+ .and_then(|e| e.get("subjectAltName"))
+ .and_then(|s| s.as_string())
+ {
+ let split = Preg::split("{\\s*,\\s*}", san);
+ subject_alt_names = split
+ .into_iter()
+ .filter_map(|name| {
+ if name.starts_with("DNS:") {
+ Some(strtolower(&ltrim(&substr(&name, 4, None), None)))
+ } else {
+ None
+ }
+ })
+ .collect();
+ }
+
+ Some(CertificateNames {
+ cn: common_name,
+ san: subject_alt_names,
+ })
+ }
+
+ /// Get the certificate pin.
+ pub fn get_certificate_fingerprint(_certificate: &str) -> String {
+ todo!("openssl public key extraction and sha1 fingerprint")
+ }
+
+ /// Test if it is safe to use the PHP function openssl_x509_parse().
+ pub fn is_openssl_parse_safe() -> bool {
+ CaBundle::is_openssl_parse_safe()
+ }
+
+ /// Convert certificate name into matching function.
+ fn cert_name_matcher(cert_name: &str) -> Option<Box<dyn Fn(&str) -> bool>> {
+ let wildcards = substr_count(cert_name, "*");
+
+ if wildcards == 0 {
+ // Literal match.
+ let cert_name = cert_name.to_string();
+ return Some(Box::new(move |hostname: &str| hostname == cert_name));
+ }
+
+ if wildcards == 1 {
+ let components: Vec<&str> = cert_name.split('.').collect();
+
+ if components.len() < 3 {
+ // Must have 3+ components
+ return None;
+ }
+
+ let first_component = components[0];
+
+ // Wildcard must be the last character.
+ if !first_component.ends_with('*') {
+ return None;
+ }
+
+ let mut wildcard_regex = preg_quote(cert_name, None);
+ wildcard_regex = str_replace("\\*", "[a-z0-9-]+", &wildcard_regex);
+ let wildcard_regex = format!("{{^{}$}}", wildcard_regex);
+
+ return Some(Box::new(move |hostname: &str| {
+ Preg::is_match(&wildcard_regex, hostname)
+ }));
+ }
+
+ None
+ }
+}