<pre style='margin:0'>
Joshua Root (jmroot) pushed a commit to branch master
in repository macports-base.

</pre>
<p><a href="https://github.com/macports/macports-base/commit/ce4af0f6fa16002f3fb77c5080a28839c74a5f3d">https://github.com/macports/macports-base/commit/ce4af0f6fa16002f3fb77c5080a28839c74a5f3d</a></p>
<pre style="white-space: pre; background: #F8F8F8"><span style='display:block; white-space:pre;color:#808000;'>commit ce4af0f6fa16002f3fb77c5080a28839c74a5f3d
</span>Author: Joshua Root <jmr@macports.org>
AuthorDate: Fri May 9 01:48:57 2025 +1000

<span style='display:block; white-space:pre;color:#404040;'>    Add signify support for archives
</span>---
 src/macports1.0/macports.tcl        |   6 +-
 src/package1.0/portarchivefetch.tcl | 137 ++++++++++++++++++++++++++----------
 2 files changed, 105 insertions(+), 38 deletions(-)

<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/macports1.0/macports.tcl b/src/macports1.0/macports.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 6c1b9dc68..28ec8cf56 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/macports1.0/macports.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/macports1.0/macports.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -2071,6 +2071,10 @@ proc macports::worker_init {workername portpath porturl portbuildpath options va
</span>     # tool path cache
     $workername alias get_tool_path macports::get_tool_path
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    # Signature verification
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    $workername alias verify_signature_openssl macports::verify_signature_openssl
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    $workername alias verify_signature_signify macports::verify_signature_signify
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>     $workername alias get_compatible_xcode_versions macports::get_compatible_xcode_versions
 
     $workername alias curlwrap macports::curlwrap
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -6656,7 +6660,7 @@ proc macports::get_archive_sites_conf_values {} {
</span>         variable os_platform; variable os_major
         set archive_sites_conf_values [list]
         set all_names [list]
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        set defaults_list [list applications_dir /Applications/MacPorts prefix /opt/local type tbz2]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set defaults_list [list applications_dir /Applications/MacPorts prefix /opt/local type tbz2 sigtype rmd160]
</span>         if {$os_platform eq "darwin" && $os_major <= 12} {
             lappend defaults_list cxx_stdlib libstdc++ delete_la_files no
         } else {
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/package1.0/portarchivefetch.tcl b/src/package1.0/portarchivefetch.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 5da0ad702..2119ec35d 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/package1.0/portarchivefetch.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/package1.0/portarchivefetch.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -72,7 +72,9 @@ proc portarchivefetch::filter_sites {} {
</span>         portfetch::mirror_sites::archive_frameworks_dir \
         portfetch::mirror_sites::archive_applications_dir \
         portfetch::mirror_sites::archive_cxx_stdlib \
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        portfetch::mirror_sites::archive_delete_la_files
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        portfetch::mirror_sites::archive_delete_la_files  \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        portfetch::mirror_sites::archive_sigtype \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        portfetch::mirror_sites::archive_pubkey
</span> 
     # get defaults from ports tree resources
     set mirrorfile [get_full_archive_sites_path]
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -164,6 +166,69 @@ proc portarchivefetch::checkfiles {urls} {
</span> }
 
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+# Return all signature types that may be used with the configured sites
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+proc portarchivefetch::get_all_sigtypes {} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    global archive_sites portfetch::mirror_sites::archive_sigtype
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set sigtypes [dict create]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    foreach site $archive_sites {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # If the entry is a URL rather than a mirror site name then
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # this will actually extract the URL scheme, but that's OK
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # since it won't exist in the array and will be skipped.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set site [lindex [split $site :] 0]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[info exists archive_sigtype($site)]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            dict set sigtypes $archive_sigtype($site) 1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if {[dict size $sigtypes] > 0} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return [dict keys $sigtypes]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # Legacy default
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return rmd160
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# Verify signature for a fetched archive using any of the public keys
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# set for each archive site or in pubkeys.conf.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+proc portarchivefetch::verify_signature {archive_path sig_path} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    global archive_sites archivefetch.pubkeys \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+           portfetch::mirror_sites::archive_sigtype \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+           portfetch::mirror_sites::archive_pubkey
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # Chop off the .TMP before getting extension
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set archivetype [file extension [file rootname $archive_path]]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set sigtype [file extension $sig_path]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set pubkeys [dict create]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    foreach site $archive_sites {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set site_split [split $site :]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set site [lindex $site_split 0]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set tag [lindex $site_split end]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {".$tag" eq $archivetype && [info exists archive_sigtype($site)]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            && [info exists archive_pubkey($site)]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            && ".$archive_sigtype($site)" eq $sigtype
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        } then {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # Use dict to avoid duplicates if a key is added in both
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # the archive site definition and pubkeys.conf.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            dict set pubkeys $archive_pubkey($site) 1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    foreach pubkey ${archivefetch.pubkeys} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set keytype [file extension $pubkey]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {($keytype eq ".pub" && $sigtype eq ".sig") || ($keytype eq ".pem" && $sigtype eq ".rmd160")} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            dict set pubkeys $pubkey 1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    # Succeed if the signature can be verified with any of the keys
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    foreach pubkey [dict keys $pubkeys] {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {($sigtype eq ".sig" && [verify_signature_signify $archive_path $pubkey $sig_path])
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            || ($sigtype eq ".rmd160" && [verify_signature_openssl $archive_path $pubkey $sig_path])
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        } then {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            return 1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return 0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> # Perform a standard fetch, assembling fetch urls from
 # the listed url variable and associated archive file
 proc portarchivefetch::fetchfiles {args} {
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -232,11 +297,13 @@ proc portarchivefetch::fetchfiles {args} {
</span>                 ui_error [format [msgcat::mc "No defined site for tag: %s, using archive_sites"] $url_var]
                 set urlmap($url_var) $urlmap(archive_sites)
             }
<span style='display:block; white-space:pre;background:#e0ffe0;'>+            if {![info exists sigtypes]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set sigtypes [get_all_sigtypes]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set archive_tmp_path ${incoming_path}/${archive}.TMP
</span>             set failed_sites 0
             set archive_fetched 0
             set lastError ""
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            # there should be an rmd160 digest of the archive signed with one of the trusted keys
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            set signature ${incoming_path}/${archive}.rmd160
</span>             set sig_fetched 0
             foreach site $urlmap($url_var) {
                 set orig_site $site
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -249,20 +316,20 @@ proc portarchivefetch::fetchfiles {args} {
</span>                 if {!$archive_fetched} {
                     ui_msg "$UI_PREFIX [format [msgcat::mc "Attempting to fetch %s from %s"] $archive ${site}]"
                     try {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                        curlwrap fetch $orig_site $credentials {*}$fetch_options $file_url ${incoming_path}/${archive}.TMP
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        curlwrap fetch $orig_site $credentials {*}$fetch_options $file_url $archive_tmp_path
</span>                         set archive_fetched 1
                     } trap {POSIX SIG SIGINT} {_ eOptions} {
                         ui_debug [msgcat::mc "Aborted fetching archive due to SIGINT"]
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                        file delete -force ${incoming_path}/${archive}.TMP
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        file delete -force $archive_tmp_path
</span>                         throw [dict get $eOptions -errorcode] [dict get $eOptions -errorinfo]
                     } trap {POSIX SIG SIGTERM} {_ eOptions} {
                         ui_debug [msgcat::mc "Aborted fetching archive due to SIGTERM"]
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                        file delete -force ${incoming_path}/${archive}.TMP
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        file delete -force $archive_tmp_path
</span>                         throw [dict get $eOptions -errorcode] [dict get $eOptions -errorinfo]
                     } on error {eMessage} {
                         ui_debug [msgcat::mc "Fetching archive failed: %s" $eMessage]
                         set lastError $eMessage
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                        file delete -force ${incoming_path}/${archive}.TMP
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        file delete -force $archive_tmp_path
</span>                         incr failed_sites
                         if {$failed_sites > 2 && ![tbool ports_binary_only] && ![_archive_available]} {
                             break
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -271,46 +338,42 @@ proc portarchivefetch::fetchfiles {args} {
</span>                 }
                 # fetch signature
                 if {$archive_fetched} {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                    ui_msg "$UI_PREFIX [format [msgcat::mc "Attempting to fetch %s from %s"] ${archive}.rmd160 $site]"
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    try {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        curlwrap fetch $orig_site $credentials {*}$fetch_options ${file_url}.rmd160 $signature
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        set sig_fetched 1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    # TODO: record signature type for each URL somehow
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    foreach sigtype $sigtypes {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        set signature ${incoming_path}/${archive}.${sigtype}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        ui_msg "$UI_PREFIX [format [msgcat::mc "Attempting to fetch %s from %s"] ${archive}.${sigtype} $site]"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        try {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            curlwrap fetch $orig_site $credentials {*}$fetch_options ${file_url}.${sigtype} $signature
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            set sig_fetched 1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            break
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        } trap {POSIX SIG SIGINT} {_ eOptions} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            ui_debug [msgcat::mc "Aborted fetching archive due to SIGINT"]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            file delete -force $archive_tmp_path $signature
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            throw [dict get $eOptions -errorcode] [dict get $eOptions -errorinfo]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        } trap {POSIX SIG SIGTERM} {_ eOptions} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            ui_debug [msgcat::mc "Aborted fetching archive due to SIGTERM"]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            file delete -force $archive_tmp_path $signature
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            throw [dict get $eOptions -errorcode] [dict get $eOptions -errorinfo]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        } on error {eMessage} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            ui_debug [msgcat::mc "Fetching archive signature failed: %s" $eMessage]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            set lastError $eMessage
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            file delete -force $signature
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    if {$sig_fetched} {
</span>                         break
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                    } trap {POSIX SIG SIGINT} {_ eOptions} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        ui_debug [msgcat::mc "Aborted fetching archive due to SIGINT"]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        file delete -force ${incoming_path}/${archive}.TMP $signature
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        throw [dict get $eOptions -errorcode] [dict get $eOptions -errorinfo]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    } trap {POSIX SIG SIGTERM} {_ eOptions} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        ui_debug [msgcat::mc "Aborted fetching archive due to SIGTERM"]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        file delete -force ${incoming_path}/${archive}.TMP $signature
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        throw [dict get $eOptions -errorcode] [dict get $eOptions -errorinfo]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    } on error {eMessage} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        ui_debug [msgcat::mc "Fetching archive signature failed: %s" $eMessage]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        set lastError $eMessage
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        file delete -force $signature
</span>                     }
                 }
             }
             if {$archive_fetched && $sig_fetched} {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                set openssl [findBinary openssl $portutil::autoconf::openssl_path]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                set verified 0
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                global archivefetch.pubkeys
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                foreach pubkey ${archivefetch.pubkeys} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    if {![catch {exec $openssl dgst -ripemd160 -verify $pubkey -signature $signature "${incoming_path}/${archive}.TMP"} result]} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        set verified 1
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        break
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    } else {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        ui_debug "failed verification with key $pubkey"
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        ui_debug "openssl output: $result"
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    }
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                set verified [verify_signature $archive_tmp_path $signature]
</span>                 file delete -force $signature
                 if {!$verified} {
                     # fall back to building from source (or error out later if binary only mode)
                     ui_warn "Failed to verify signature for archive!"
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                    file delete -force "${incoming_path}/${archive}.TMP"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    file delete -force $archive_tmp_path
</span>                     break
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                } elseif {[catch {file rename -force "${incoming_path}/${archive}.TMP" "${archivefetch.fulldestpath}/${archive}"} result]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                } elseif {[catch {file rename -force $archive_tmp_path ${archivefetch.fulldestpath}/${archive}} result]} {
</span>                     ui_debug "$::errorInfo"
                     return -code error "Failed to move downloaded archive into place: $result"
                 }
</pre><pre style='margin:0'>

</pre>