summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2026-05-30 23:54:05 +0200
committerLudovic Courtès <ludo@gnu.org>2026-08-17 16:47:36 +0200
commit64d4de2a920445e5992f020e56490f5fcbdbba7c (patch)
treea869360b32fbc5fb0f49d297708ddde801aa3a95
parent8cb871e0c69030599fa8e8410f1ecaee21ddbbd0 (diff)
daemon: Bypass authentication when importing content-addressed store items.
This puts ‘importPaths’ on par with ‘addToStore’ and ‘addTextToStore’: since the two latter RPCs let anyone add content-addressed items in the store, there’s no reason for ‘importPaths’ to require signatures by authorized keys on these content-addressed items. This will allow for things like ‘guix copy’ of .drv items without authorization, or ‘guix deploy’ with (build-locally? #f) without authorization. * nix/libstore/store-api.hh (isContentAddressedPath): New prototype. * nix/libstore/store-api.cc (isContentAddressedPath): New function. * nix/libutil/util.hh (isPlainFile): New prototype. * nix/libutil/util.cc (isPlainFile): New function. * nix/libstore/local-store.cc (LocalStore::importPath): Define ‘narHash’ and ‘contentAddressed’. Allow unsigned imports when ‘contentAddressed’ is true; bypass signature verification when ‘contentAddressed’ is true. * tests/store.scm ("import not signed"): Rewrite to not use a content-addressed store item. ("import signed by unauthorized key"): Likewise. ("import not signed but content-addressed tree"): New test. ("import not signed but content-addressed regular file"): New test. ("import signed by unauthorized key but content-addressed"): New test. ("import with corrupt signature"): New test. ("import signed by authorized key but hash doesn't match"): New test. ("import with corrupt signature but content-addressed"): New test. ("import signed by authorized key, hash doesn't match, but content-addressed"): New test. * doc/guix.texi (Invoking guix archive): Document the exception for content-addressed store items. Add anchor for ‘--authorize’. (Invoking guix deploy): Document the benefit of (build-locally? #f). Add cross-reference for ‘authorize?’. Co-authored-by: Reepca Russelstein <reepca@russelstein.xyz> Signed-off-by: Ludovic Courtès <ludo@gnu.org> Merges: #8979
-rw-r--r--doc/guix.texi12
-rw-r--r--nix/libstore/local-store.cc45
-rw-r--r--nix/libstore/store-api.cc23
-rw-r--r--nix/libstore/store-api.hh6
-rw-r--r--nix/libutil/util.cc7
-rw-r--r--nix/libutil/util.hh3
-rw-r--r--tests/store.scm295
7 files changed, 360 insertions, 31 deletions
diff --git a/doc/guix.texi b/doc/guix.texi
index c5fed97da08..e7f1826542d 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -5674,7 +5674,11 @@ references, corresponding derivation, and a digital signature.
5674When exporting, the daemon digitally signs the contents of the archive, 5674When exporting, the daemon digitally signs the contents of the archive,
5675and that digital signature is appended. When importing, the daemon 5675and that digital signature is appended. When importing, the daemon
5676verifies the signature and rejects the import in case of an invalid 5676verifies the signature and rejects the import in case of an invalid
5677signature or if the signing key is not authorized. 5677signature or if the signing key is not authorized; as an exception,
5678store items that are @dfn{content-addressed}---source files and
5679@file{.drv} files (@pxref{Derivations})---can be imported even if they
5680lack an authorized signature because adding these to the store is not
5681creating any new risk.
5678@c FIXME: Add xref to daemon doc about signatures. 5682@c FIXME: Add xref to daemon doc about signatures.
5679 5683
5680The main options are: 5684The main options are:
@@ -5724,6 +5728,7 @@ Alternatively, @var{parameters} can specify
5724public-key related Functions, @code{gcry_pk_genkey},, gcrypt, The 5728public-key related Functions, @code{gcry_pk_genkey},, gcrypt, The
5725Libgcrypt Reference Manual}). 5729Libgcrypt Reference Manual}).
5726 5730
5731@anchor{archive-authorization}
5727@item --authorize 5732@item --authorize
5728@cindex authorizing, archives 5733@cindex authorizing, archives
5729Authorize imports signed by the public key passed on standard input. 5734Authorize imports signed by the public key passed on standard input.
@@ -51032,12 +51037,15 @@ with an @code{environment} of @code{managed-host-environment-type}.
51032@item @code{host-name} 51037@item @code{host-name}
51033@item @code{build-locally?} (default: @code{#t}) 51038@item @code{build-locally?} (default: @code{#t})
51034If false, system derivations will be built on the machine being deployed to. 51039If false, system derivations will be built on the machine being deployed to.
51040As a bonus, when false, one does not need the target machine to have the
51041key of the deployment machine among its authorized keys and thus the
51042@code{authorize?} field below can remain @code{#f}.
51035@item @code{system} 51043@item @code{system}
51036The system type describing the architecture of the machine being deployed 51044The system type describing the architecture of the machine being deployed
51037to---e.g., @code{"x86_64-linux"}. 51045to---e.g., @code{"x86_64-linux"}.
51038@item @code{authorize?} (default: @code{#t}) 51046@item @code{authorize?} (default: @code{#t})
51039If true, the coordinator's signing key will be added to the remote's ACL 51047If true, the coordinator's signing key will be added to the remote's ACL
51040keyring. 51048keyring (@pxref{archive-authorization, authorizing imports}).
51041@item @code{port} (default: @code{22}) 51049@item @code{port} (default: @code{22})
51042@item @code{user} (default: @code{"root"}) 51050@item @code{user} (default: @code{"root"})
51043@item @code{identity} (default: @code{#f}) 51051@item @code{identity} (default: @code{#f})
diff --git a/nix/libstore/local-store.cc b/nix/libstore/local-store.cc
index 6b5c388efc9..c38dd2072d4 100644
--- a/nix/libstore/local-store.cc
+++ b/nix/libstore/local-store.cc
@@ -1336,6 +1336,8 @@ Path LocalStore::importPath(Source & source)
1336 1336
1337 restorePath(unpacked, hashAndReadSource); 1337 restorePath(unpacked, hashAndReadSource);
1338 1338
1339 Hash narHash = hashAndReadSource.hashSink.currentHash().first;
1340
1339 unsigned int magic = readInt(hashAndReadSource); 1341 unsigned int magic = readInt(hashAndReadSource);
1340 if (magic != EXPORT_MAGIC) 1342 if (magic != EXPORT_MAGIC)
1341 throw Error("normalized archive cannot be imported; wrong format"); 1343 throw Error("normalized archive cannot be imported; wrong format");
@@ -1355,21 +1357,36 @@ Path LocalStore::importPath(Source & source)
1355 hashAndReadSource.hashing = false; 1357 hashAndReadSource.hashing = false;
1356 1358
1357 bool haveSignature = readInt(hashAndReadSource) == 1; 1359 bool haveSignature = readInt(hashAndReadSource) == 1;
1360 string signature;
1361 if (haveSignature)
1362 signature = readString(hashAndReadSource);
1358 1363
1359 if (!haveSignature) 1364 try {
1360 throw Error(std::format("imported archive of `{}' lacks a signature", dstPath)); 1365 /* First, check whether there is a valid, authorized, matching
1361 1366 signature. */
1362 string signature = readString(hashAndReadSource); 1367 if (haveSignature) {
1363 string hash2 = verifySignature(signature); 1368 string hash2 = verifySignature(signature);
1364 1369 if (printHash(hash) != hash2)
1365 /* Note: runProgram() throws an exception if the signature 1370 throw AuthenticationError(
1366 is invalid. */ 1371 "signed hash doesn't match actual contents of imported "
1367 1372 "archive; archive could be corrupt, or someone is trying "
1368 if (printHash(hash) != hash2) 1373 "to import a Trojan horse");
1369 throw Error( 1374 }
1370 "signed hash doesn't match actual contents of imported " 1375 else
1371 "archive; archive could be corrupt, or someone is trying " 1376 throw AuthenticationError(std::format("imported archive of `{}' lacks a signature", dstPath));
1372 "to import a Trojan horse"); 1377 }
1378 catch (AuthenticationError &e) {
1379 /* Second, check whether 'dstPath' is content-addressed--e.g., a store
1380 item created by 'addToStore' or 'addTextToStore'. XXX: This can
1381 yield an extra 'hashFile' call and is limited to SHA256. */
1382 if (!(deriver == ""
1383 && (isContentAddressedPath(dstPath, narHash, references, true)
1384 || (isPlainFile(unpacked)
1385 && isContentAddressedPath(dstPath, hashFile(htSHA256, unpacked),
1386 references, false)))))
1387 /* Rethrow. */
1388 throw;
1389 }
1373 1390
1374 /* Do the actual import. */ 1391 /* Do the actual import. */
1375 1392
diff --git a/nix/libstore/store-api.cc b/nix/libstore/store-api.cc
index feabc821678..43f46176b5e 100644
--- a/nix/libstore/store-api.cc
+++ b/nix/libstore/store-api.cc
@@ -258,6 +258,29 @@ Path computeStorePathForText(const string & name, const string & s,
258 return makeStorePath(type, hash, name); 258 return makeStorePath(type, hash, name);
259} 259}
260 260
261bool isContentAddressedPath(const Path & path, const Hash & hash,
262 const PathSet & references, bool recursive)
263{
264 /* Check whether PATH corresponds to something introduced by 'addToStore'
265 or by 'addTextToStore'. For simplicity, anything with a hash other
266 than SHA256 is omitted: this returns false even though they are
267 content-addressed as well. */
268 string name = storePathToName(path);
269 if (recursive) {
270 /* HASH is interpreted as the nar hash. This can only come from
271 'addToStore'. */
272 return references.empty() &&
273 path == makeFixedOutputPath(true, htSHA256, hash, name);
274 } else {
275 /* HASH is interpreted as the content hash. This can come from
276 'addTextToStore' ("text" type, possibly with references) or from
277 'addToStore' ("output:out" type). */
278 string type = textTypeWithReferences(references);
279 return path == makeStorePath(type, hash, name)
280 || (references.empty() &&
281 path == makeFixedOutputPath(false, htSHA256, hash, name));
282 }
283}
261 284
262/* Return a string accepted by decodeValidPathInfo() that 285/* Return a string accepted by decodeValidPathInfo() that
263 registers the specified paths as valid. Note: it's the 286 registers the specified paths as valid. Note: it's the
diff --git a/nix/libstore/store-api.hh b/nix/libstore/store-api.hh
index 29b33c0be9c..398ccd260ea 100644
--- a/nix/libstore/store-api.hh
+++ b/nix/libstore/store-api.hh
@@ -362,6 +362,12 @@ Path makeFixedOutputPath(bool recursive,
362Path computeStorePathForText(const string & name, const string & s, 362Path computeStorePathForText(const string & name, const string & s,
363 const PathSet & references); 363 const PathSet & references);
364 364
365/* Return true if PATH, whose content has the given HASH, refers to a
366 content-addressed file as added by 'addTextToStore' or 'addToStore'. HASH
367 is interpreted as a SHA256 nar hash when RECURSIVE is true, and as a SHA256
368 file content hash when RECURSIVE is false. */
369bool isContentAddressedPath(const Path & path, const Hash & hash,
370 const PathSet & references, bool recursive);
365 371
366/* Remove the temporary roots file for this process. Any temporary 372/* Remove the temporary roots file for this process. Any temporary
367 root becomes garbage after this point unless it has been registered 373 root becomes garbage after this point unless it has been registered
diff --git a/nix/libutil/util.cc b/nix/libutil/util.cc
index 95f293ff10f..72c8d38bd50 100644
--- a/nix/libutil/util.cc
+++ b/nix/libutil/util.cc
@@ -287,6 +287,13 @@ unsigned char getFileType(const Path & path)
287 return DT_UNKNOWN; 287 return DT_UNKNOWN;
288} 288}
289 289
290bool isPlainFile(const Path & path)
291{
292 struct stat st = lstat(path);
293 return S_ISREG(st.st_mode)
294 && ((st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0);
295}
296
290 297
291string readFile(int fd) 298string readFile(int fd)
292{ 299{
diff --git a/nix/libutil/util.hh b/nix/libutil/util.hh
index 44d579f3d6a..ee8e5461b42 100644
--- a/nix/libutil/util.hh
+++ b/nix/libutil/util.hh
@@ -65,6 +65,9 @@ Path readLink(const Path & path);
65 65
66bool isLink(const Path & path); 66bool isLink(const Path & path);
67 67
68/* Return true if the file at PATH is a regular, non-executable file. */
69bool isPlainFile(const Path & path);
70
68/* Read the contents of a directory. The entries `.' and `..' are 71/* Read the contents of a directory. The entries `.' and `..' are
69 removed. */ 72 removed. */
70struct DirEntry 73struct DirEntry
diff --git a/tests/store.scm b/tests/store.scm
index e7ba7469047..ba634ce2205 100644
--- a/tests/store.scm
+++ b/tests/store.scm
@@ -30,7 +30,8 @@
30 #:use-module (guix derivations) 30 #:use-module (guix derivations)
31 #:use-module ((guix modules) 31 #:use-module ((guix modules)
32 #:select (source-module-closure)) 32 #:select (source-module-closure))
33 #:use-module (guix serialization) 33 #:use-module ((guix serialization)
34 #:hide (store-path read-string))
34 #:use-module (guix build utils) 35 #:use-module (guix build utils)
35 #:use-module ((gnu build linux-container) 36 #:use-module ((gnu build linux-container)
36 #:select (unprivileged-user-namespace-supported?)) 37 #:select (unprivileged-user-namespace-supported?))
@@ -1456,18 +1457,25 @@ System: x86_64-linux~%"
1456 1457
1457 1458
1458(test-assert "import not signed" 1459(test-assert "import not signed"
1459 (let* ((text (random-text)) 1460 (let* ((content (random-text))
1460 (file (add-file-tree-to-store %store 1461 (name "fake-derivation")
1461 `("tree" directory 1462 (store-item (output-path "out"
1462 ("text" regular (data ,text)) 1463 (gcrypt:sha256 #vu8())
1463 ("link" symlink "text")))) 1464 name))
1464 (dump (call-with-bytevector-output-port 1465 (dump (call-with-bytevector-output-port
1465 (lambda (port) 1466 (lambda (port)
1466 (write-int 1 port) ;start 1467 (write-int 1 port) ;start
1467 1468
1468 (write-file file port) ;contents 1469 (write-file-tree name port ;contents
1470 #:file-type+size
1471 (lambda (_)
1472 (values 'regular
1473 (string-length content)))
1474 #:file-port
1475 (lambda (_)
1476 (open-input-string content)))
1469 (write-int #x4558494e port) ;%export-magic 1477 (write-int #x4558494e port) ;%export-magic
1470 (write-string file port) ;store item 1478 (write-string store-item port) ;store item
1471 (write-string-list '() port) ;references 1479 (write-string-list '() port) ;references
1472 (write-string "" port) ;deriver 1480 (write-string "" port) ;deriver
1473 (write-int 0 port) ;not signed 1481 (write-int 0 port) ;not signed
@@ -1485,11 +1493,11 @@ System: x86_64-linux~%"
1485 #f)))) 1493 #f))))
1486 1494
1487(test-assert "import signed by unauthorized key" 1495(test-assert "import signed by unauthorized key"
1488 (let* ((text (random-text)) 1496 (let* ((content (random-text))
1489 (file (add-file-tree-to-store %store 1497 (name "fake-derivation")
1490 `("tree" directory 1498 (store-item (output-path "out"
1491 ("text" regular (data ,text)) 1499 (gcrypt:sha256 #vu8())
1492 ("link" symlink "text")))) 1500 name))
1493 (key (gcrypt:generate-key 1501 (key (gcrypt:generate-key
1494 (gcrypt:string->canonical-sexp 1502 (gcrypt:string->canonical-sexp
1495 "(genkey (ecdsa (curve Ed25519) (flags rfc6979)))"))) 1503 "(genkey (ecdsa (curve Ed25519) (flags rfc6979)))")))
@@ -1497,9 +1505,16 @@ System: x86_64-linux~%"
1497 (lambda (port) 1505 (lambda (port)
1498 (write-int 1 port) ;start 1506 (write-int 1 port) ;start
1499 1507
1500 (write-file file port) ;contents 1508 (write-file-tree name port ;contents
1509 #:file-type+size
1510 (lambda (_)
1511 (values 'regular
1512 (string-length content)))
1513 #:file-port
1514 (lambda (_)
1515 (open-input-string content)))
1501 (write-int #x4558494e port) ;%export-magic 1516 (write-int #x4558494e port) ;%export-magic
1502 (write-string file port) ;store item 1517 (write-string store-item port) ;store item
1503 (write-string-list '() port) ;references 1518 (write-string-list '() port) ;references
1504 (write-string "" port) ;deriver 1519 (write-string "" port) ;deriver
1505 (write-int 1 port) ;signed 1520 (write-int 1 port) ;signed
@@ -1524,6 +1539,256 @@ System: x86_64-linux~%"
1524 (pk 'unauthorized-imported imported) 1539 (pk 'unauthorized-imported imported)
1525 #f)))) 1540 #f))))
1526 1541
1542(test-assert "import with corrupt signature"
1543 (let* ((content (random-text))
1544 (name "fake-derivation")
1545 (store-item (output-path "out"
1546 (gcrypt:sha256 #vu8())
1547 name))
1548 (dump (call-with-bytevector-output-port
1549 (lambda (port)
1550 (write-int 1 port) ;start
1551
1552 (write-file-tree name port ;contents
1553 #:file-type+size
1554 (lambda (_)
1555 (values 'regular
1556 (string-length content)))
1557 #:file-port
1558 (lambda (_)
1559 (open-input-string content)))
1560 (write-int #x4558494e port) ;%export-magic
1561 (write-string store-item port) ;store item
1562 (write-string-list '() port) ;references
1563 (write-string "" port) ;deriver
1564 (write-int 1 port) ;signed
1565 (write-string (object->string '(signature broken))
1566 port)
1567
1568 (write-int 0 port))))) ;done
1569
1570 ;; Ensure 'import-paths' raises an exception.
1571 (guard (c ((store-protocol-error? c)
1572 (and (not (zero? (store-protocol-error-status c)))
1573 (string-contains (store-protocol-error-message c)
1574 "corrupt signature"))))
1575 (let* ((source (open-bytevector-input-port dump))
1576 (imported (import-paths %store source)))
1577 (pk 'corrupt-signature-imported imported)
1578 #f))))
1579
1580(test-assert "import signed by authorized key but hash doesn't match"
1581 (let* ((content (random-text))
1582 (name "fake-derivation")
1583 (store-item (output-path "out"
1584 (gcrypt:sha256 #vu8())
1585 name))
1586 ;; This key is known to be in the ACL by default.
1587 (public-key
1588 (call-with-input-file (string-append %config-directory "/signing-key.pub")
1589 (compose gcrypt:string->canonical-sexp get-string-all)))
1590 (private-key
1591 (call-with-input-file (string-append %config-directory "/signing-key.sec")
1592 (compose gcrypt:string->canonical-sexp get-string-all)))
1593
1594 (dump (call-with-bytevector-output-port
1595 (lambda (port)
1596 (write-int 1 port) ;start
1597
1598 (write-file-tree name port ;contents
1599 #:file-type+size
1600 (lambda (_)
1601 (values 'regular
1602 (string-length content)))
1603 #:file-port
1604 (lambda (_)
1605 (open-input-string content)))
1606 (write-int #x4558494e port) ;%export-magic
1607 (write-string store-item port) ;store item
1608 (write-string-list '() port) ;references
1609 (write-string "" port) ;deriver
1610 (write-int 1 port) ;signed
1611 (write-string (gcrypt:canonical-sexp->string
1612 (signature-sexp
1613 (gcrypt:bytevector->hash-data
1614 (gcrypt:sha256 #vu8(0 1 2))
1615 #:key-type 'rsa)
1616 private-key
1617 public-key))
1618 port)
1619
1620 (write-int 0 port))))) ;done
1621
1622 ;; Ensure 'import-paths' raises an exception.
1623 (guard (c ((store-protocol-error? c)
1624 (and (not (zero? (store-protocol-error-status c)))
1625 (string-contains (store-protocol-error-message c)
1626 "hash doesn't match"))))
1627 (let* ((source (open-bytevector-input-port dump))
1628 (imported (import-paths %store source)))
1629 (pk 'hash-mismatch-imported imported)
1630 #f))))
1631
1632(test-assert "import not signed but content-addressed tree"
1633 (let* ((text (random-text))
1634 (file (add-file-tree-to-store %store
1635 `("tree" directory
1636 ("text" regular (data ,text))
1637 ("link" symlink "text"))))
1638 (dump (call-with-bytevector-output-port
1639 (lambda (port)
1640 (write-int 1 port) ;start
1641
1642 (write-file file port) ;contents
1643 (write-int #x4558494e port) ;%export-magic
1644 (write-string file port) ;store item
1645 (write-string-list '() port) ;references
1646 (write-string "" port) ;deriver
1647 (write-int 0 port) ;not signed
1648
1649 (write-int 0 port))))) ;done
1650
1651 ;; Ensure 'import-paths' completes despite the lack of signature.
1652 (let ((source (open-bytevector-input-port dump)))
1653 (match (import-paths %store source)
1654 ((imported)
1655 (string=? (pk 'imported imported) file))))))
1656
1657(test-assert "import not signed but content-addressed regular file"
1658 (let* ((name "content-addressed")
1659 (content (random-text))
1660 (store-item (store-path "text"
1661 (gcrypt:sha256 (string->utf8 content))
1662 name))
1663 (dump (call-with-bytevector-output-port
1664 (lambda (port)
1665 (write-int 1 port) ;start
1666 (write-file-tree name port ;contents
1667 #:file-type+size
1668 (lambda (_)
1669 (values 'regular
1670 (string-length content)))
1671 #:file-port
1672 (lambda (_)
1673 (open-input-string content)))
1674 (write-int #x4558494e port) ;%export-magic
1675 (write-string store-item port)
1676 (write-string-list '() port) ;references
1677 (write-string "" port) ;deriver
1678 (write-int 0 port) ;not signed
1679 (write-int 0 port))))) ;done
1680
1681 ;; Ensure 'import-paths' completes despite the lack of signature.
1682 (let ((source (open-bytevector-input-port dump)))
1683 (match (import-paths %store source)
1684 ((imported)
1685 (and (string=? (pk 'imported imported) store-item)
1686 (string=? (call-with-input-file imported get-string-all)
1687 content)))))))
1688
1689(test-assert "import signed by unauthorized key but content-addressed"
1690 (let* ((text (random-text))
1691 (file (add-file-tree-to-store %store
1692 `("tree" directory
1693 ("text" regular (data ,text))
1694 ("link" symlink "text"))))
1695 (key (gcrypt:generate-key
1696 (gcrypt:string->canonical-sexp
1697 "(genkey (ecdsa (curve Ed25519) (flags rfc6979)))")))
1698 (dump (call-with-bytevector-output-port
1699 (lambda (port)
1700 (write-int 1 port) ;start
1701
1702 (write-file file port) ;contents
1703 (write-int #x4558494e port) ;%export-magic
1704 (write-string file port) ;store item
1705 (write-string-list '() port) ;references
1706 (write-string "" port) ;deriver
1707 (write-int 1 port) ;signed
1708 (write-string (gcrypt:canonical-sexp->string
1709 (signature-sexp
1710 (gcrypt:bytevector->hash-data
1711 (gcrypt:sha256 #vu8(0 1 2))
1712 #:key-type 'ecc)
1713 (gcrypt:find-sexp-token key 'private-key)
1714 (gcrypt:find-sexp-token key 'public-key)))
1715 port)
1716
1717 (write-int 0 port))))) ;done
1718
1719 ;; Ensure 'import-paths' succeeds despite the unauthorized signature.
1720 (let ((source (open-bytevector-input-port dump)))
1721 (match (import-paths %store source)
1722 ((imported)
1723 (string=? imported file))))))
1724
1725(test-assert "import with corrupt signature but content-addressed"
1726 (let* ((text (random-text))
1727 (file (add-file-tree-to-store %store
1728 `("tree" directory
1729 ("text" regular (data ,text))
1730 ("link" symlink "text"))))
1731 (dump (call-with-bytevector-output-port
1732 (lambda (port)
1733 (write-int 1 port) ;start
1734
1735 (write-file file port) ;contents
1736 (write-int #x4558494e port) ;%export-magic
1737 (write-string file port) ;store item
1738 (write-string-list '() port) ;references
1739 (write-string "" port) ;deriver
1740 (write-int 1 port) ;signed
1741 (write-string (object->string '(signature broken))
1742 port)
1743
1744 (write-int 0 port))))) ;done
1745
1746 ;; Ensure 'import-paths' succeeds despite the corrupt signature.
1747 (let ((source (open-bytevector-input-port dump)))
1748 (match (import-paths %store source)
1749 ((imported)
1750 (string=? imported file))))))
1751
1752(test-assert "import signed by authorized key, hash doesn't match, but content-addressed"
1753 (let* ((text (random-text))
1754 (file (add-file-tree-to-store %store
1755 `("tree" directory
1756 ("text" regular (data ,text))
1757 ("link" symlink "text"))))
1758 ;; This key is known to be in the ACL by default.
1759 (public-key
1760 (call-with-input-file (string-append %config-directory "/signing-key.pub")
1761 (compose gcrypt:string->canonical-sexp get-string-all)))
1762 (private-key
1763 (call-with-input-file (string-append %config-directory "/signing-key.sec")
1764 (compose gcrypt:string->canonical-sexp get-string-all)))
1765 (dump (call-with-bytevector-output-port
1766 (lambda (port)
1767 (write-int 1 port) ;start
1768
1769 (write-file file port) ;contents
1770 (write-int #x4558494e port) ;%export-magic
1771 (write-string file port) ;store item
1772 (write-string-list '() port) ;references
1773 (write-string "" port) ;deriver
1774 (write-int 1 port) ;signed
1775 (write-string (gcrypt:canonical-sexp->string
1776 (signature-sexp
1777 (gcrypt:bytevector->hash-data
1778 (gcrypt:sha256 #vu8(0 1 2))
1779 #:key-type 'rsa)
1780 private-key
1781 public-key))
1782 port)
1783
1784 (write-int 0 port))))) ;done
1785
1786 ;; Ensure 'import-paths' succeeds despite the hash mismatch.
1787 (let ((source (open-bytevector-input-port dump)))
1788 (match (import-paths %store source)
1789 ((imported)
1790 (string=? imported file))))))
1791
1527(test-assert "import corrupt path" 1792(test-assert "import corrupt path"
1528 (let* ((text (random-text)) 1793 (let* ((text (random-text))
1529 (file (add-text-to-store %store "text" text)) 1794 (file (add-text-to-store %store "text" text))