]> Cypherpunks repositories - dsc.git/commitdiff
Configuration checksumming
authorSergey Matveev <stargrave@stargrave.org>
Tue, 23 Dec 2025 11:30:25 +0000 (14:30 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Tue, 23 Dec 2025 13:07:04 +0000 (16:07 +0300)
CHECKSUM [new file with mode: 0644]
dsc
t/bin.t
t/csum.t [new file with mode: 0755]
t/export.t
t/import-path-validation.t
t/setup.rc

diff --git a/CHECKSUM b/CHECKSUM
new file mode 100644 (file)
index 0000000..55a57da
--- /dev/null
+++ b/CHECKSUM
@@ -0,0 +1,14 @@
+There is ability to integrity protect the exported configuration.
+Exported txtar contains trailing ".csum" pseudo file with one or
+multiple hashes of the digested data.
+
+DSC_HASHES environment variables specified what hashes must be included
+and invoked. For example "blake2b-512:b2sum,skein-512:skein512" value
+means, that BLAKE2b-512 is calculated by calling "b2sum" utility, and
+Skein-512 by "skein512".
+
+"dsc csum" command can be used to calculate hashes over the arbitrary
+data. "dsc import-check" can be used to verify piped in configuration
+integrity. "dsc import-pipe" does not do any checks, so import-check
+should be called before. "dsc import FILE" just calls import-check
+followed by import-pipe.
diff --git a/dsc b/dsc
index fadc0fdb2df3c1655b6b97b636cf747b49251a9bfd103d89471b3e45960deb47..50186ebedaff0f22de910167563702aa5e719992950d085f42da12ad80728882 100755 (executable)
--- a/dsc
+++ b/dsc
@@ -31,14 +31,19 @@ if {$argc == 0} {
     dsc revert opt    -- revert opt's configuration
     dsc commit        -- commit (save) configuration
     dsc export [prefix] >file.txtar -- export (whole by default) configuration
-    dsc import          <file.txtar -- import it
+    dsc import-check    <file.txtar -- verify configuration's checksum
+    dsc import-pipe     <file.txtar -- import configuration
+    dsc import           file.txtar -- verify and import configuration
     dsc path opt      -- get full path to option's value (if set)
     dsc apply prefix  -- apply configuration on given prefix
+    dsc csum <data    -- compute data's checksum
 
 Environmental variables:
     $DSC_SCHEMA -- path to the schema definition
     $DSC_STASH  -- path to stashed/unsaved state
     $DSC_SAVED  -- path to committed/saved state
+    $DSC_HASHES -- sha2-512:sha512 by default
+                   Comma-separated list of hash-name:cmd pairs
 
 There are two kinds of options:
     * array/list ones, which are identified with /*/ in "list"'s
@@ -48,9 +53,22 @@ There are two kinds of options:
     exit 1
 }
 
-if {[catch {set Schema $env(DSC_SCHEMA)}]} {set Schema schema}
-if {[catch {set Stash $env(DSC_STASH)}]} {set Stash stash}
-if {[catch {set Saved $env(DSC_SAVED)}]} {set Saved saved}
+set CopyBufLen [expr {128 * 1024}]
+if {[info exists env(DSC_SCHEMA)]} {set Schema $env(DSC_SCHEMA)} {set Schema schema}
+if {[info exists env(DSC_STASH)]} {set Stash $env(DSC_STASH)} {set Stash stash}
+if {[info exists env(DSC_SAVED)]} {set Saved $env(DSC_SAVED)} {set Saved saved}
+if {[info exists env(DSC_HASHES)]} {
+    set Hashes [dict create]
+    foreach pair [split $env(DSC_HASHES) ,] {
+        set cols [split $pair :]
+        if {[llength $cols] != 2} {
+            error "bad DSC_HASHES"
+        }
+        dict set Hashes [lindex $cols 0] [lindex $cols 1]
+    }
+} {
+    set Hashes [dict create sha2-512 sha512]
+}
 
 proc readents {root typ} {
     set ents [glob -directory $root -tails -nocomplain -- *]
@@ -330,7 +348,32 @@ switch [lindex $argv 0] {
         file rename $tmp $Saved
         file delete -force $Saved.bak
     }
-    export {
+    csum {
+        set hsh [dict create]
+        dict for {name cmd} $Hashes {
+            lassign [pipe] r w
+            set fh [open [list |$cmd >@$w] w]
+            fconfigure $fh -translation binary
+            dict set hsh $name [list $fh $r $w]
+        }
+        fconfigure stdin -translation binary
+        while {![eof stdin]} {
+            set buf [read stdin $CopyBufLen]
+            dict for {_ fhs} $hsh {
+                lassign $fhs fh
+                puts -nonewline $fh $buf
+            }
+        }
+        foreach name [lsort [dict keys $hsh]] {
+            lassign [dict get $hsh $name] fh r w
+            close $fh
+            close $w
+            gets $r v
+            set v [split $v " "]
+            puts "$name [lindex $v 0]"
+        }
+    }
+    export-raw {
         set dirs [walk $Saved/$opt directory]
         if {$opt != ""} {
             set dirs [list $Saved/$opt {*}$dirs]
@@ -361,7 +404,24 @@ switch [lindex $argv 0] {
             }
         }
     }
-    import {
+    export {
+        set exporter [open [list |$argv0 export-raw $opt]]
+        fconfigure $exporter -translation binary
+        lassign [pipe] r w
+        set hasher [open [list |$argv0 csum >@$w] w]
+        fconfigure $hasher -translation binary
+        fconfigure stdout -translation binary
+        while {![eof $exporter]} {
+            set buf [read $exporter $CopyBufLen]
+            puts -nonewline stdout $buf
+            puts -nonewline $hasher $buf
+        }
+        close $hasher
+        close $w
+        puts "-- .csum --"
+        puts -nonewline [read $r]
+    }
+    import-pipe {
         fconfigure stdin -translation binary
         while {[gets stdin line] >= 0} {
             set fn [txtar-fn $line]
@@ -404,6 +464,8 @@ switch [lindex $argv 0] {
                 puts $fh $line
             } elseif {[string range $fn 0 2] == "-- "} {
                 puts $fh $fn
+            } elseif {$fn == ".csum"} {
+                break
             } else {
                 close $fh
                 set fh [openfh $fn]
@@ -412,6 +474,34 @@ switch [lindex $argv 0] {
         close $fh
         exec sync
     }
+    import-check {
+        lassign [pipe] r w
+        set hasher [open [list |$argv0 csum >@$w] w]
+        fconfigure $hasher -translation binary
+        fconfigure stdin -translation binary
+        while {[gets stdin line] >= 0} {
+            if {$line == "-- .csum --"} {
+                break
+            }
+            puts $hasher $line
+        }
+        close $hasher
+        close $w
+        set got [read $r]
+        set exp [read stdin]
+        if {$got == $exp} {
+            exit 0
+        }
+        puts stderr "integrity failure"
+        puts -nonewline stderr $got
+        puts stderr "\t!="
+        puts -nonewline stderr $exp
+        exit 1
+    }
+    import {
+        exec $argv0 import-check <$opt
+        exec $argv0 import-pipe <$opt
+    }
     path {
         if {[file exists $Stash/$opt]} {
             puts [file normalize $Stash/$opt]
diff --git a/t/bin.t b/t/bin.t
index 38ace6c42421b4c99596ad747e739fdbecaca2d246456b77787173291236caaa..66ca0176f53bce500516352870d9564b7f395b198e095690424bb47b137e5f16 100755 (executable)
--- a/t/bin.t
+++ b/t/bin.t
@@ -12,7 +12,7 @@ test_expect_success "commit" "dsc commit"
 test_expect_success "export" "dsc export >out"
 test_expect_success "has base64" "grep -q :base64 out"
 test_expect_success "del *" 'dsc del ""'
-test_expect_success "import" "dsc import <out"
+test_expect_success "import" "dsc import-pipe <out"
 test_expect_success "get" "dsc get ssh/prv >out"
 test_expect_success "cmp" "test_cmp out prv"
 
diff --git a/t/csum.t b/t/csum.t
new file mode 100755 (executable)
index 0000000..803b639
--- /dev/null
+++ b/t/csum.t
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description="$(basename $0)"
+. $SHARNESS_TEST_SRCDIR/sharness.sh
+. $SHARNESS_TEST_DIRECTORY/setup.rc
+
+test_expect_success "hostname" "dsc set sys/hostname mein"
+test_expect_success "commit" "dsc commit"
+test_expect_success "export-raw" "dsc export-raw >out"
+test_expect_success "csum" "dsc csum <out >detached"
+cp out expected
+printf "%s\n" "-- .csum --" >>expected
+cat detached >>expected
+test_expect_success "export" "dsc export >out"
+test_expect_success "cmp" "test_cmp out expected"
+test_expect_success "import-check" "dsc import-check <out"
+test_expect_success "import" "dsc import out"
+
+dsc export-raw >out
+test_expect_success "without csum -check" "! dsc import-check <out"
+test_expect_success "without csum import" "! dsc import out"
+printf "%s\n" "-- .csum --" >>expected
+echo "whatever hash" >>expected
+test_expect_success "wrong csum" "! dsc import-check <out"
+
+DSC_HASHES="sha2-512:sha512"
+test_expect_success "export sha512" "dsc export >out"
+cat >expected <<EOF
+-- .dirs --
+sys
+-- sys/hostname --
+mein
+EOF
+sha512 <expected >hsh
+printf "%s\n" "-- .csum --" >>expected
+echo -n "sha2-512 " >>expected
+cat hsh >>expected
+test_expect_success "cmp" "test_cmp out expected"
+
+test_done
index f6d582e75feb710bbd2395ed578d594bf00a8c17c1c897f086ae8c3299f5b1e0..922b99d28799489b1c0f31a9f4ef757d18d2021da8049822c22fe326d01fee90 100755 (executable)
@@ -73,7 +73,7 @@ test_expect_success "cmp" "test_cmp out expected"
 test_expect_success "commit" "dsc commit"
 test_expect_success "diff" "dsc diff >out"
 test_expect_success "diff is empty" "! [ -s out ]"
-test_expect_success "export" "dsc export >out"
+test_expect_success "export" "dsc export-raw >out"
 cat >expected <<EOF
 -- .dirs --
 net
@@ -104,9 +104,8 @@ EOF
 test_expect_success "cmp" "test_cmp out expected"
 
 rm -r $DSC_STASH
-test_expect_success "import" "dsc import <out"
+test_expect_success "import" "dsc import-pipe <out"
 test_expect_success "diff" "dsc diff >out"
-cat out
 test_expect_success "diff is empty" "! [ -s out ]"
 
 test_done
index 4f25209624876f72c2e7df37296b39a989a626f20a0de714615a0b8415c7fa1a..18a613e2accb6f8717e6816683b0c76f3dad40892b4bac83f922956d337f038e 100755 (executable)
@@ -9,7 +9,7 @@ cat >in <<EOF
 -- /abs/path --
 gotcha
 EOF
-test_expect_success "import abs" "! dsc import <in >out 2>&1"
+test_expect_success "import abs" "! dsc import-pipe <in >out 2>&1"
 test_expect_success "import abs msg" \
     '[ "$(cat out)" = "absolute paths are forbidden" ]'
 
@@ -18,7 +18,7 @@ cat >in <<EOF
 -- path/../rel --
 gotcha
 EOF
-test_expect_success "import rel" "! dsc import <in >out 2>&1"
+test_expect_success "import rel" "! dsc import-pipe <in >out 2>&1"
 test_expect_success "import rel msg" \
     '[ "$(cat out)" = "relative paths are forbidden" ]'
 
@@ -27,7 +27,7 @@ cat >in <<EOF
 /abs/path
 gotcha
 EOF
-test_expect_success "import abs" "! dsc import <in >out 2>&1"
+test_expect_success "import abs" "! dsc import-pipe <in >out 2>&1"
 test_expect_success "import abs msg" \
     '[ "$(cat out)" = "absolute paths are forbidden" ]'
 
@@ -35,7 +35,7 @@ cat >in <<EOF
 -- .dirs --
 path/../rel
 EOF
-test_expect_success "import rel" "! dsc import <in >out 2>&1"
+test_expect_success "import rel" "! dsc import-pipe <in >out 2>&1"
 test_expect_success "import rel msg" \
     '[ "$(cat out)" = "relative paths are forbidden" ]'
 
index 0a7f392cedb4a1efbf43182ee92b96e752929f2562e4d116d860f4e6ef8005b5..56d94e27212a5f69baedc6ba1cc0364344b3cec1ac1ff732e73a012d9a8fd7a0 100644 (file)
@@ -3,3 +3,10 @@ export DSC_STASH=stash DSC_SAVED=saved
 export JIMLIB="$SHARNESS_TEST_DIRECTORY/../jimlib:$JIMLIB"
 PATH="$SHARNESS_TEST_DIRECTORY/..:$PATH"
 mkdir saved
+export DSC_HASHES="sha2-512:sha512"
+if command -v skein512 2>/dev/null >/dev/null ; then
+    DSC_HASHES="skein-512:skein512,$DSC_HASHES"
+fi
+if command -v b2sum 2>/dev/null >/dev/null ; then
+    DSC_HASHES="blake2b-512:b2sum,$DSC_HASHES"
+fi