Check-in [d3097967d6]
Not logged in
Overview
Comment:More updates to the Harpy script signing tool.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: d3097967d6dd962eaecffd59bcf8b72a01e446ea
User & Date: mistachkin on 2025-09-09 02:10:52
Other Links: manifest | tags
Context
2025-09-09
15:00
Another update to the Harpy script signing tool. Leaf check-in: 84d535d19a user: mistachkin tags: trunk
02:10
More updates to the Harpy script signing tool. check-in: d3097967d6 user: mistachkin tags: trunk
2025-09-08
23:29
Pickup further upstream updates to the Harpy script signing tool. check-in: c9eebf0366 user: mistachkin tags: trunk
Changes

Modified externals/Harpy/Tools/sign.eagle from [3eb0b347ba] to [6f37b8a208].

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# RCS: @(#) $Id: $
#
###############################################################################

proc usage { error } {
  if {[string length $error] > 0} then {puts stdout $error}

  puts stdout "usage:\
[file tail [getShellExecutableName]]\
[file tail [info script]] <fileName> \[vendor\] \[embed\]\
\[duration\] \[entityType\] \[encoding\] \[publicKeyFile\]\
\[privateKeyFile\] \[importFirst\] \[quantity\] \[id\]\
\[timeStamp\]"

  #







|

|







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# RCS: @(#) $Id: $
#
###############################################################################

proc usage { error } {
  if {[string length $error] > 0} then {emitOutput $error}

  emitOutput "usage:\
[file tail [getShellExecutableName]]\
[file tail [info script]] <fileName> \[vendor\] \[embed\]\
\[duration\] \[entityType\] \[encoding\] \[publicKeyFile\]\
\[privateKeyFile\] \[importFirst\] \[quantity\] \[id\]\
\[timeStamp\]"

  #
39
40
41
42
43
44
45





46
47
48
49
50
51
52
      emitNotice [appendArgs $name " hook ok: " $result]
    } else {
      if {[string length $result] == 0} then {set result <none>}
      emitWarning [appendArgs $name " hook error: " $result]
    }
  }
}






proc emitNotice { message } {
  set message [string trim $message]
  catch {host result Ok [appendArgs $message \n]}
}

proc emitWarning { message } {







>
>
>
>
>







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
      emitNotice [appendArgs $name " hook ok: " $result]
    } else {
      if {[string length $result] == 0} then {set result <none>}
      emitWarning [appendArgs $name " hook error: " $result]
    }
  }
}

proc emitOutput { message } {
  set message [string trim $message]
  catch {puts stdout $message}
}

proc emitNotice { message } {
  set message [string trim $message]
  catch {host result Ok [appendArgs $message \n]}
}

proc emitWarning { message } {
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

129
130
131
132
133

134

135
136
137

138

139
140
141

142

143
144
145

146

147
148

149
150
151
152
153
154
155
156
157
158
159
160
161
162
163

164



165
166
167
168
169

170



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230








231
232
233
234
235
236
237
      [string match "keyRing*" $rootName]} then {
    return true
  }

  return false
}

proc checkAndMatchKeyFile { varName {keyFile ""} } {
  global env

  if {[info exists env($varName)]} then {
    set fileName $env($varName)

    if {[string length $fileName] > 0} then {
      if {[file exists $fileName]} then {
        if {[catch {keypair token $fileName} token] == 0 && \
            [string length $token] > 0} then {
          if {[string length $keyFile] == 0 || \
              [matchKeyFileTokens $fileName $keyFile]} then {
            return true
          } else {

            puts stdout [appendArgs \
                "file \"" $fileName \
                "\" mismatches public key token from file \"" \
                $keyFile \"]
          }

        } else {

          puts stdout [appendArgs \
              "file \"" $fileName "\" is probably not a key file"]
        }

      } else {

        puts stdout [appendArgs \
            "file \"" $fileName "\" does not exist"]
      }

    } else {

      puts stdout [appendArgs \
          "environment variable \"" $varName "\" has no value"]
    }

  } else {

    puts stdout [appendArgs \
        "environment variable \"" $varName "\" was not found"]

  }

  return false
}

proc matchKeyFileTokens { keyFile1 keyFile2 } {
  if {[string length $keyFile1] == 0 || \
      [string length $keyFile2] == 0} then {
    return false
  }

  if {![file exists $keyFile1] || ![file exists $keyFile2]} then {
    return false
  }


  set token1 [keypair token $keyFile1]




  if {[string length $token1] == 0} then {
    return false
  }


  set token2 [keypair token $keyFile2]




  if {[string length $token2] == 0} then {
    return false
  }

  return [expr {$token1 eq $token2}]
}

proc readEntityValue { fileName } {
  return [readFile $fileName]
}

proc readCertificateFile { fileName } {
  return [readFile $fileName]
}

proc writeCertificateFile { fileName data } {
  return [writeFile $fileName $data]
}

proc removeEmbeddedCertificate { fileName } {
  set prefixWithSpacing [appendArgs $::embedSpacing $::embedPrefix]

  set data [readFile $fileName]
  set beginIndex [string first $prefixWithSpacing $data]

  if {$beginIndex == -1} then {
    set prefixWithSpacing $::embedPrefix
    set beginIndex [string last $prefixWithSpacing $data]
  }

  if {$beginIndex != -1} then {
    set endIndex [string first $::embedSuffix $data \
        [expr {$beginIndex + [string length $prefixWithSpacing]}]]

    if {$endIndex != -1} then {
      writeFile $fileName [string replace $data $beginIndex \
          [expr {$endIndex + [string length $::embedSuffix]}]]

      puts stdout [appendArgs \
          "removed embedded certificate from file \"" $fileName \"]
    }
  }
}

if {[llength $argv] >= 1 && [llength $argv] <= 12} then {
  #
  # NOTE: This tool requires Eagle.
  #
  package require Eagle

  #
  # NOTE: Needed for the [getTemporaryPath] script procedure.
  #
  package require Eagle.Test

  #
  # NOTE: This tool requires the enterprise license features.
  #
  package require Licensing.Enterprise









  #
  # NOTE: If the tool base path does not already exist, set it
  #       to the directory where this script is running from.
  #
  if {![info exists path]} then {
    set path [file normalize [file dirname [info script]]]







|













>
|
|
|
|
|
>

>
|
|
|
>

>
|
|
|
>

>
|
|
|
>

>
|
|
>















>
|
>
>
>





>
|
>
>
>











<
<
<
<
<
<
<
<




















|




















>
>
>
>
>
>
>
>







113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204








205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
      [string match "keyRing*" $rootName]} then {
    return true
  }

  return false
}

proc checkAndMatchKeyFile { varName {keyFile ""} {quiet false} } {
  global env

  if {[info exists env($varName)]} then {
    set fileName $env($varName)

    if {[string length $fileName] > 0} then {
      if {[file exists $fileName]} then {
        if {[catch {keypair token $fileName} token] == 0 && \
            [string length $token] > 0} then {
          if {[string length $keyFile] == 0 || \
              [matchKeyFileTokens $fileName $keyFile]} then {
            return true
          } else {
            if {!$quiet} then {
              emitOutput [appendArgs \
                  "file \"" $fileName \
                  "\" mismatches public key token from file \"" \
                  $keyFile \"]
            }
          }
        } else {
          if {!$quiet} then {
            emitOutput [appendArgs \
                "file \"" $fileName "\" is probably not a key file"]
          }
        }
      } else {
        if {!$quiet} then {
          emitOutput [appendArgs \
              "file \"" $fileName "\" does not exist"]
        }
      }
    } else {
      if {!$quiet} then {
        emitOutput [appendArgs \
            "environment variable \"" $varName "\" has no value"]
      }
    }
  } else {
    if {!$quiet} then {
      emitOutput [appendArgs \
          "environment variable \"" $varName "\" was not found"]
    }
  }

  return false
}

proc matchKeyFileTokens { keyFile1 keyFile2 } {
  if {[string length $keyFile1] == 0 || \
      [string length $keyFile2] == 0} then {
    return false
  }

  if {![file exists $keyFile1] || ![file exists $keyFile2]} then {
    return false
  }

  if {[catch {
    keypair token $keyFile1
  } token1] != 0 || [string length $token1] == 0} then {
    return false
  }

  if {[string length $token1] == 0} then {
    return false
  }

  if {[catch {
    keypair token $keyFile2
  } token2] != 0 || [string length $token2] == 0} then {
    return false
  }

  if {[string length $token2] == 0} then {
    return false
  }

  return [expr {$token1 eq $token2}]
}

proc readEntityValue { fileName } {
  return [readFile $fileName]
}









proc removeEmbeddedCertificate { fileName } {
  set prefixWithSpacing [appendArgs $::embedSpacing $::embedPrefix]

  set data [readFile $fileName]
  set beginIndex [string first $prefixWithSpacing $data]

  if {$beginIndex == -1} then {
    set prefixWithSpacing $::embedPrefix
    set beginIndex [string last $prefixWithSpacing $data]
  }

  if {$beginIndex != -1} then {
    set endIndex [string first $::embedSuffix $data \
        [expr {$beginIndex + [string length $prefixWithSpacing]}]]

    if {$endIndex != -1} then {
      writeFile $fileName [string replace $data $beginIndex \
          [expr {$endIndex + [string length $::embedSuffix]}]]

      emitOutput [appendArgs \
          "removed embedded certificate from file \"" $fileName \"]
    }
  }
}

if {[llength $argv] >= 1 && [llength $argv] <= 12} then {
  #
  # NOTE: This tool requires Eagle.
  #
  package require Eagle

  #
  # NOTE: Needed for the [getTemporaryPath] script procedure.
  #
  package require Eagle.Test

  #
  # NOTE: This tool requires the enterprise license features.
  #
  package require Licensing.Enterprise

  #
  # NOTE: Should emitting of extra diagnostics be skipped?
  #
  if {![info exists quiet]} then {
    set quiet false
    set quiet_set true
  }

  #
  # NOTE: If the tool base path does not already exist, set it
  #       to the directory where this script is running from.
  #
  if {![info exists path]} then {
    set path [file normalize [file dirname [info script]]]
323
324
325
326
327
328
329
330
331
332
333

334
335

336
337

338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
  #
  if {[llength $argv] >= 8} then {
    #
    # NOTE: Use the private key file name supplied on the command
    #       line.
    #
    set privateKeyFile [string trim [lindex $argv 7]]
  } elseif {[isKeyRingFile $fileName] && \
      [checkAndMatchKeyFile EagleEnterpriseTrustRootPrivateKey]} then {
    set privateKeyFile $env(EagleEnterpriseTrustRootPrivateKey)
  } elseif {[checkAndMatchKeyFile EagleEnterpriseScriptPrivateKey]} then {

    set privateKeyFile $env(EagleEnterpriseScriptPrivateKey)
  } elseif {[checkAndMatchKeyFile EagleEnterprisePersonalPrivateKey]} then {

    set privateKeyFile $env(EagleEnterprisePersonalPrivateKey)
  } elseif {[checkAndMatchKeyFile EagleEnterprisePrivateKey]} then {

    set privateKeyFile $env(EagleEnterprisePrivateKey)
  } elseif {[info exists env(Eagle)]} then {
    set privateKeyFile [file join \
        $env(Eagle) Keys EagleEnterprisePluginRootPrivate.snk]
  } else {
    #
    # NOTE: Default to "EagleEnterprisePluginRootPrivate.snk" in the current
    #       directory (which may not actually exist).
    #
    set privateKeyFile EagleEnterprisePluginRootPrivate.snk
  }

  #
  # NOTE: Figure out the file name for the public strong name key
  #       file that will be used to verify the data file.
  #
  if {[llength $argv] >= 7} then {
    #
    # NOTE: Use the public key file name supplied on the command
    #       line.
    #
    set publicKeyFile [string trim [lindex $argv 6]]
  } elseif {[checkAndMatchKeyFile EagleEnterpriseTrustRootPublicKey \
      $privateKeyFile]} then {
    set publicKeyFile $env(EagleEnterpriseTrustRootPublicKey)
  } elseif {[checkAndMatchKeyFile EagleEnterpriseScriptPublicKey \
      $privateKeyFile]} then {
    set publicKeyFile $env(EagleEnterpriseScriptPublicKey)
  } elseif {[checkAndMatchKeyFile EagleEnterprisePersonalPublicKey \
      $privateKeyFile]} then {
    set publicKeyFile $env(EagleEnterprisePersonalPublicKey)
  } elseif {[checkAndMatchKeyFile EagleEnterprisePublicKey \
      $privateKeyFile]} then {
    set publicKeyFile $env(EagleEnterprisePublicKey)
  } elseif {[info exists env(Eagle)]} then {
    set publicKeyFile [file join \
        $env(Eagle) Keys EagleEnterprisePluginRootPublic.snk]
  } else {
    #
    # NOTE: Default to "EagleEnterprisePluginRootPublic.snk" in the current
    #       directory (which may not actually exist).
    #
    set publicKeyFile EagleEnterprisePluginRootPublic.snk
  }

  #
  # NOTE: Do we want to import the existing certificate first?
  #







|
|

|
>

|
>

|
>






|
|














|
|

|
|

|
|

|
|






|
|







346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
  #
  if {[llength $argv] >= 8} then {
    #
    # NOTE: Use the private key file name supplied on the command
    #       line.
    #
    set privateKeyFile [string trim [lindex $argv 7]]
  } elseif {[isKeyRingFile $fileName] && [checkAndMatchKeyFile \
      EagleEnterpriseTrustRootPrivateKey "" $quiet]} then {
    set privateKeyFile $env(EagleEnterpriseTrustRootPrivateKey)
  } elseif {[checkAndMatchKeyFile \
      EagleEnterpriseScriptPrivateKey "" $quiet]} then {
    set privateKeyFile $env(EagleEnterpriseScriptPrivateKey)
  } elseif {[checkAndMatchKeyFile \
      EagleEnterprisePersonalPrivateKey "" $quiet]} then {
    set privateKeyFile $env(EagleEnterprisePersonalPrivateKey)
  } elseif {[checkAndMatchKeyFile \
      EagleEnterprisePrivateKey "" $quiet]} then {
    set privateKeyFile $env(EagleEnterprisePrivateKey)
  } elseif {[info exists env(Eagle)]} then {
    set privateKeyFile [file join \
        $env(Eagle) Keys EagleEnterprisePluginRootPrivate.snk]
  } else {
    #
    # NOTE: Default to "EagleEnterprisePluginRootPrivate.snk" in the
    #       current directory (which may not actually exist).
    #
    set privateKeyFile EagleEnterprisePluginRootPrivate.snk
  }

  #
  # NOTE: Figure out the file name for the public strong name key
  #       file that will be used to verify the data file.
  #
  if {[llength $argv] >= 7} then {
    #
    # NOTE: Use the public key file name supplied on the command
    #       line.
    #
    set publicKeyFile [string trim [lindex $argv 6]]
  } elseif {[checkAndMatchKeyFile \
      EagleEnterpriseTrustRootPublicKey $privateKeyFile $quiet]} then {
    set publicKeyFile $env(EagleEnterpriseTrustRootPublicKey)
  } elseif {[checkAndMatchKeyFile \
      EagleEnterpriseScriptPublicKey $privateKeyFile $quiet]} then {
    set publicKeyFile $env(EagleEnterpriseScriptPublicKey)
  } elseif {[checkAndMatchKeyFile \
      EagleEnterprisePersonalPublicKey $privateKeyFile $quiet]} then {
    set publicKeyFile $env(EagleEnterprisePersonalPublicKey)
  } elseif {[checkAndMatchKeyFile \
      EagleEnterprisePublicKey $privateKeyFile $quiet]} then {
    set publicKeyFile $env(EagleEnterprisePublicKey)
  } elseif {[info exists env(Eagle)]} then {
    set publicKeyFile [file join \
        $env(Eagle) Keys EagleEnterprisePluginRootPublic.snk]
  } else {
    #
    # NOTE: Default to "EagleEnterprisePluginRootPublic.snk" in the
    #       current directory (which may not actually exist).
    #
    set publicKeyFile EagleEnterprisePluginRootPublic.snk
  }

  #
  # NOTE: Do we want to import the existing certificate first?
  #
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
          " \"" $fileName \"]
    }

    #
    # NOTE: Attempt to re-verify the license certificate file.
    #
    if {[certificate verify -encoding $encoding $certificate \
            $privateKey] ne "VerifiedOk"} then {
      emitError [appendArgs \
          "failed to verify signature for " $fileType " \"" $fileName \"]
    }
  } else {
    #
    # NOTE: Set the file type for error messages.
    #







|







555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
          " \"" $fileName \"]
    }

    #
    # NOTE: Attempt to re-verify the license certificate file.
    #
    if {[certificate verify -encoding $encoding $certificate \
            $publicKey] ne "VerifiedOk"} then {
      emitError [appendArgs \
          "failed to verify signature for " $fileType " \"" $fileName \"]
    }
  } else {
    #
    # NOTE: Set the file type for error messages.
    #
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768

769
770
771
772
773
774
775
  #
  package require Harpy.Test; reformatCertificateSpacing $certificateFile

  #
  # NOTE: Show that we signed it.
  #
  if {$importFirst} then {
    puts stdout [appendArgs \
        "re-signed " $fileType " \"" $fileName "\" (as \"" $entityType \
        "\") using encoding \"" $encoding "\" with key " [keypair token \
        $privateKeyFile]]
  } else {
    puts stdout [appendArgs \
        "signed " $fileType " \"" $fileName "\" (as \"" $entityType \
        "\") using encoding \"" $encoding "\" with key " [keypair token \
        $privateKeyFile]]
  }

  #
  # NOTE: Do we need to embed the certificate at the end of the script
  #       file?
  #
  if {$shouldEmbed} then {
    #
    # NOTE: Make sure any carriage-returns are removed as they impact
    #       the line splitting algorithm being used.
    #

    set data [string map [list [info newline] \n] $data]

    #
    # NOTE: Start out with the embedding prefix on a line by itself.
    #
    set lines [list $embedPrefix]








|




|














>







768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
  #
  package require Harpy.Test; reformatCertificateSpacing $certificateFile

  #
  # NOTE: Show that we signed it.
  #
  if {$importFirst} then {
    emitOutput [appendArgs \
        "re-signed " $fileType " \"" $fileName "\" (as \"" $entityType \
        "\") using encoding \"" $encoding "\" with key " [keypair token \
        $privateKeyFile]]
  } else {
    emitOutput [appendArgs \
        "signed " $fileType " \"" $fileName "\" (as \"" $entityType \
        "\") using encoding \"" $encoding "\" with key " [keypair token \
        $privateKeyFile]]
  }

  #
  # NOTE: Do we need to embed the certificate at the end of the script
  #       file?
  #
  if {$shouldEmbed} then {
    #
    # NOTE: Make sure any carriage-returns are removed as they impact
    #       the line splitting algorithm being used.
    #
    set data [readFile $certificateFile]
    set data [string map [list [info newline] \n] $data]

    #
    # NOTE: Start out with the embedding prefix on a line by itself.
    #
    set lines [list $embedPrefix]

793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828




829
830
831
    #       platform.
    #
    appendFile $fileName [join $lines [info newline]]

    #
    # NOTE: Show that we embedded it.
    #
    puts stdout [appendArgs \
        "added embedded certificate to " $fileType " \"" $fileName \"]
  }

  #
  # HOOK: Script completion.
  #
  maybeInvokeHook certificate phase3

  #
  # NOTE: Cleanup the external embedded certificate file, if any.
  #
  if {$shouldEmbed} then {
    catch {file delete -force $certificateFile}
  }

  #
  # NOTE: Play nice and cleanup all the variables we created during the
  #       whole the signing process.
  #
  unset -nocomplain fileName vendor embed duration entityType encoding \
      privateKeyFile publicKeyFile importFirst configFileName embedSpacing \
      embedPrefix embedSuffix publicKey privateKey shouldEmbed fileType \
      certificate certificateFile data xmlNs xmlNsXsi xmlNsXsd spaces \
      lines line

  if {[info exists path_set]} then {
    unset -nocomplain path path_set
  }




} else {
  usage ""
}







|




















|
|
|
|




>
>
>
>



820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
    #       platform.
    #
    appendFile $fileName [join $lines [info newline]]

    #
    # NOTE: Show that we embedded it.
    #
    emitOutput [appendArgs \
        "added embedded certificate to " $fileType " \"" $fileName \"]
  }

  #
  # HOOK: Script completion.
  #
  maybeInvokeHook certificate phase3

  #
  # NOTE: Cleanup the external embedded certificate file, if any.
  #
  if {$shouldEmbed} then {
    catch {file delete -force $certificateFile}
  }

  #
  # NOTE: Play nice and cleanup all the variables we created during the
  #       whole the signing process.
  #
  unset -nocomplain fileName vendor embed duration entityType encoding \
      privateKeyFile publicKeyFile importFirst quantity id timeStamp \
      configFileName embedSpacing embedPrefix embedSuffix publicKey \
      privateKey shouldEmbed harpyIsSpecial fileType certificateFile \
      certificate data lines line

  if {[info exists path_set]} then {
    unset -nocomplain path path_set
  }

  if {[info exists quiet_set]} then {
    unset -nocomplain quiet quiet_set
  }
} else {
  usage ""
}

Modified externals/Harpy/Tools/sign.eagle.asc from [b2c8b5a439] to [83adee01d4].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-----BEGIN PGP SIGNATURE-----
Comment: Eagle Package Repository

iQIzBAABCAAdFiEEw8dROIPu3TrtH+QlUCyWr0ldwtkFAmi/ZrYACgkQUCyWr0ld
wtnZNxAAmsM9JzzPJdG0lb1e9fmJl223zuVt+alOJrYS+GgsSVz7mfxKPPdX9sDK
puoiUw24mnscjk9DealbJzwkqkqmhwxEFvGqfRVdIoi2TARgUC7ilTEd7FXfCCsl
+GFhyrkT3lIqIFOLpG1CftTJ/YKUcpe9YatOykTr3MDbKmi+dN31T6U0bdYfCeM/
KeOjzHFDaj9XIRZ03PSIKa1+fN8Gy7V7zBPp+019BfuSuvrfEw1AKC+efqbvKiE8
cEw7vsh5qxKscrERAsPRnhXYVRIc8+DaZO+2NspEKWEdf3tde2LWVkh8ZLYfbtrV
8nsHVuDaEvX4jeg5i7Pzrmcq1cDgGrhE3X+PXGhesLErZou1jaxrjQeYO4gz8IoN
ZN6ybJBss30gXwwoGoQ/mGBtCdc2NWdGNOtyb0cO4yIlubVih2bNlsKevJOImXTh
W6p4xp8Cmx+Focx2aHHKIJ3iFMCn0R+jRQEKi1ybN7YWAYwm3/20wkPgv85jJ3K8
l71u56ZnkrCxsYAq4O83jOMeNZCECD5OoWS//9I3Kepe+eeADzJ6nkDT7IXFZYxl
tOSXJSTxMO0sAit+2kqOp+qMU9C9hWcxdgAQxIyvX5f4mLj+430fvycOBoC7b0cc
abwfjOj5xNrHH2SW1hezJtJOwKKiXrc4jeLydLzHYjaKchZEygs=
=BGDU
-----END PGP SIGNATURE-----



|
|
|
|
|
|
|
|
|
|
|
|
|

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-----BEGIN PGP SIGNATURE-----
Comment: Eagle Package Repository

iQIzBAABCAAdFiEEw8dROIPu3TrtH+QlUCyWr0ldwtkFAmi/jJ8ACgkQUCyWr0ld
wtlL9A//dGZf4BvZufSdAGHNrdB4C9zzczIay4Zn2w1gINeNpPQdfV1XsE7XQ5OC
9f1Dd/9bTsAnbT8UGK/UnxrnsNh9QpAYzdfSq7mtmAkzRZVtJiAsGw2iMfAQjz59
I1BRWrG/sMcpE3GEtWvPGaNcKsAiqWSLiXgYnSbIZNtI5+LUkWgNzfFIIxiWopXB
ZSuexZ2EeLvyn9Pnm3nNS7HOwUH/+MddFQh+GAqBS5p0GIimrzkpf7uoyRKoQuaV
OrtOWmHqIyj2KjsfU39dJux1lyMYmEYQBZwZf1at63YqT93eAvrgreZlTVcQKaXr
rQrPoYAL1gjfne72HAlMyTso5FsLwP7Qfx+4EY2n49ClWFu+xSCK1xQ03QMRow+j
5uQtzy9L2v8zMia/i5fKDO0dpvRBR9hkSH2bcUjxUxLGJ1NiPVY76godU/ZNXBfR
KiFO0PDKmqv4EJq/mMFO1Tnr9mm/uCTJ7RwzBBwV0TN++QeODkm+LmjIpYmNM61w
VMwQpAmGytGI+BRHP/HWqg5IcEveSgraMgAlf4vSu9nbXAVD8Wo+nyJcxXUZjDYe
cSNy4O48drDNTRg/5iMlgE+d4/0Q4RNKzarcAn6w1U/w8MtSeqAk4aSSfTXUbY/J
KWw9F5XBwlzsjT69QEAzTpg9D+RyoefPB7R8EmpRN8P6Kp2yj0k=
=1gfh
-----END PGP SIGNATURE-----

Modified externals/Harpy/Tools/sign.eagle.harpy from [6cf18f625d] to [3b9b179dd1].

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

    By using this file and/or the associated software, you agree to abide
    by the terms of the license agreement.

    PLEASE DO NOT EDIT THIS FILE.
    THE ASSOCIATED SOFTWARE MAY NOT WORK PROPERLY IF THIS FILE IS ALTERED.

    SHA512 HASH: bd469e2cf7a58e9aca6c6fb3863f0c8b790021e1a87e8b4b63d116903
    ad7e9952dc13e0e442f855eca06e7532946999573886cb38f3ce3dfc052157b1563cd9b
-->
<Certificate xmlns="https://eagle.to/2011/harpy"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Protocol>None</Protocol>
  <Vendor>Mistachkin Systems</Vendor>
  <Id>3649e9d2-931e-406d-a045-1eb51bde5843</Id>
  <HashAlgorithm>SHA512</HashAlgorithm>
  <EntityType>Script</EntityType>
  <TimeStamp>2025-09-08T23:12:59.9978681Z</TimeStamp>
  <Duration>-1.00:00:00</Duration>
  <Key>0x9559f6017247e3e2</Key>
  <Signature>
    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
  </Signature>
</Certificate>







|
|






|


|



|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|


12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

    By using this file and/or the associated software, you agree to abide
    by the terms of the license agreement.

    PLEASE DO NOT EDIT THIS FILE.
    THE ASSOCIATED SOFTWARE MAY NOT WORK PROPERLY IF THIS FILE IS ALTERED.

    SHA512 HASH: 3a5ca7386ebbba875f2b71bf133723db80dd629b0a3b3644fe201e9d3
    4a686cdbf2f5700b8e782ac127e15644da4959214e9ea64bf0c829b1e2bf8575f1d491a
-->
<Certificate xmlns="https://eagle.to/2011/harpy"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Protocol>None</Protocol>
  <Vendor>Mistachkin Systems</Vendor>
  <Id>a61be4df-c1b2-4292-a130-f1dd5e92e02b</Id>
  <HashAlgorithm>SHA512</HashAlgorithm>
  <EntityType>Script</EntityType>
  <TimeStamp>2025-09-09T02:07:18.1081550Z</TimeStamp>
  <Duration>-1.00:00:00</Duration>
  <Key>0x9559f6017247e3e2</Key>
  <Signature>
    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
  </Signature>
</Certificate>

Modified externals/Harpy/Tools/sign.eagle.harpy.asc from [3a7800c944] to [75ffc8688a].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-----BEGIN PGP SIGNATURE-----
Comment: Eagle Package Repository

iQIzBAABCAAdFiEEw8dROIPu3TrtH+QlUCyWr0ldwtkFAmi/ZroACgkQUCyWr0ld
wtn4fg/9HmH9ydom2KcUxMP7JuyoeXdu4nn+IvWEFVCw4srZ1k4GNl2VQjVtjcVg
5s4HvIF7YrBY1RU4ZvsnDVZ9GP0qzMCqXN0FRkG2r3Alp16sFICGfm90F3k7HylK
47EZTrhCAPrdzEQzKqJBswjwj2EjFnej21u5M9J2H1b/lJYqOhROcigeEryUjA+e
F85RuL+rcQk09lckG+cs2ApFkKNf2Q1fcAF2Abc8vVPZ/4RY4QqNjMPZlArbIs4s
kfrldYAxR/1HDfqefs0lJ85UZ8dPdjq/x9ZPx6UqJWtJPMMOIpNYr6VKS4p7FeDZ
8FZLjtGgY6u+BlYNB0r5sNrE7cFUjk25uDGUedu4duJvF5mpSJRxBbrX3HLXZIGQ
m9we1tM3HUr/Qu/rFCknU62GZsMP3RuEzFlBf5y7LQiZqlVsL62kpzKt3JUX9ayh
6IV+GsEEV1H79tNYGUazeVNETswDmwjTnnmZFZY5phXU42uVptpmCt7+4c5WiiXO
y9m+UFiFZVsDIEXaWYAt8T41LvuekMvF1ScEDoqjuC8LKA/2a78mtIGU8+pNnfsh
y3xdCTpGnqUQReLdEowB6/EJum7Ev6paMFVLeGNeipb8xTYg4C7N5yJrT8evpbeg
CeoMyOkvudWEtEnlxQuVEU6twlpTfPodfjn2ViUD7RehhbdiJ8s=
=2Z6m
-----END PGP SIGNATURE-----



|
|
|
|
|
|
|
|
|
|
|
|
|

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-----BEGIN PGP SIGNATURE-----
Comment: Eagle Package Repository

iQIzBAABCAAdFiEEw8dROIPu3TrtH+QlUCyWr0ldwtkFAmi/jKEACgkQUCyWr0ld
wtlrlw//eULyQqBErkYs7+F4lGBK1Wd7nitsa8L0RsG9N3kIQOED3zki0xr/vjRp
L8dLeDZ0v1pyjH+ZH0/f1odQu9CuI97FVNSq0hf7hdfpSc3vt7Dy35iCPnPhiUZS
oYNM//VhJzynf5bhY9+/vZnyplXMujk0Ok8XvVvN+m0Czxf35UXkKmwefmwZfxzr
OiFjxQzZYVUkwgLycL5czDBTfbx4pa1vV0/7eFnUSwSkpdTmsNqQB8mnbQ5fNLOj
zlGx+/0Y9PVTKf3RSIAJ1nnxJzcC6S9/UmvOb+leLBhoaUpfcQCmtOQM3X/SF4od
aD8hILK2PwUe3DyJwnhn9cyHrdaVJJhUl9sQUsexB7WxUOckT0+hQGKywbtbJmVa
xmY5T/Njv5R7BDDdApkmCUsIFZNvOXuMS2o9clbSRlx9BgOxR6mcmsWsIQ/zCeAk
+i7k63siROWWA+hsm6CnApyfjjpvAiXew15ugOYn3hucXD3zCQXPVWzTUm3+p+uq
HCt/2KMUsd51+N2pkHFlysDxg+LMIly2sDgdq3rY7HDYLEBvkZqoXkzNdAeTzHYQ
Ba9dZoBrjS0m28q+82JjmnpY7JGZOcHHtk/to+d6LOClB1s+EG2NGCThPQaGDfx3
0s7rJ3A6xZVr41H67pD+qYLj9oTjx5MgqbxZdgALSgGcJPmL4Ck=
=/ytu
-----END PGP SIGNATURE-----