├── .gitignore ├── CHANGELOG.md ├── COPYING ├── INSTALL.md ├── Makefile ├── README.md ├── archive.rkt ├── artifact.rkt ├── cli-flag.rkt ├── cli.rkt ├── cmdline.rkt ├── codec.rkt ├── crypto.rkt ├── crypto ├── .gitignore ├── CMakeLists.txt ├── Makefile ├── README.md ├── compile.sh ├── crypto.c ├── mingw_toolchain.cmake ├── x86_64-macosx │ └── crypto.dylib ├── x86_64-unix │ └── crypto.so └── x86_64-windows │ ├── crypto.dll │ └── crypto.dll.a ├── dig.rkt ├── dig ├── filesystem.rkt ├── http.rkt └── memory.rkt ├── docs ├── guide │ ├── creating-packages.scrbl │ ├── denxi-guide.scrbl │ ├── introduction.scrbl │ ├── launchers.scrbl │ ├── outroduction.scrbl │ ├── project.scrbl │ └── setup.scrbl ├── index │ ├── denxi-index.scrbl │ └── doc-logo.png ├── journal │ ├── 10-jan-2021.scrbl │ └── denxi-journal.scrbl ├── reference │ ├── background.scrbl │ ├── denxi-reference.scrbl │ ├── maintenance.scrbl │ ├── maintenance │ │ ├── affirmations.scrbl │ │ └── basics.scrbl │ └── modules │ │ ├── archive.scrbl │ │ ├── artifact.scrbl │ │ ├── cli-flag.scrbl │ │ ├── cli.scrbl │ │ ├── cmdline.scrbl │ │ ├── codec.scrbl │ │ ├── crypto.scrbl │ │ ├── dig.scrbl │ │ ├── dig │ │ ├── filesystem.scrbl │ │ ├── http.scrbl │ │ └── memory.scrbl │ │ ├── file.scrbl │ │ ├── format.scrbl │ │ ├── input.scrbl │ │ ├── integrity.scrbl │ │ ├── integrity │ │ ├── base.scrbl │ │ └── ffi.scrbl │ │ ├── l10n.scrbl │ │ ├── launcher.scrbl │ │ ├── main.scrbl │ │ ├── message.scrbl │ │ ├── monad.scrbl │ │ ├── notary.scrbl │ │ ├── output.scrbl │ │ ├── package.scrbl │ │ ├── pkgdef.scrbl │ │ ├── pkgdef │ │ └── static.scrbl │ │ ├── port.scrbl │ │ ├── printer.scrbl │ │ ├── query.scrbl │ │ ├── racket-module.scrbl │ │ ├── security.scrbl │ │ ├── setting.scrbl │ │ ├── signature.scrbl │ │ ├── signature │ │ ├── base.scrbl │ │ ├── ffi.scrbl │ │ └── snake-oil.scrbl │ │ ├── source.scrbl │ │ ├── state.scrbl │ │ ├── string.scrbl │ │ ├── subprogram.scrbl │ │ ├── system.scrbl │ │ ├── url.scrbl │ │ └── version.scrbl ├── shared.rkt └── white-paper │ └── denxi-white-paper.scrbl ├── examples ├── .gitignore ├── README.md ├── abstract-inputs │ ├── README.md │ ├── defn.rkt │ └── launcher.rkt ├── abstract-outputs │ ├── README.md │ ├── install-first.rkt │ ├── install-second.rkt │ └── launcher.rkt ├── allow-racket-versions │ ├── defn.rkt │ └── launcher.rkt ├── cryptography-backends │ ├── README.md │ └── openssl-subprocess-backend.rkt ├── determinism │ ├── README.md │ ├── defn.rkt │ ├── launcher.rkt │ ├── lock.rkt │ └── server.rkt ├── extend-ln │ ├── README.md │ └── lnx ├── fetch-command │ ├── README.md │ └── download-racket-8.1.sh ├── from-guide │ ├── definition.rkt │ └── my-denxi.rkt ├── generated-racket-bindings │ ├── README.md │ ├── defn.rkt │ ├── info.rkt │ ├── launch.rkt │ ├── program.rkt │ └── sources │ │ ├── symlinked.rkt │ │ ├── v1.rkt │ │ └── v2.rkt ├── input-checking │ ├── README.md │ ├── defn.rkt │ └── launch.rkt ├── input-output-selection │ ├── README.md │ ├── defn.rkt │ ├── index.html │ └── launch.rkt ├── input-overriding │ ├── README.md │ ├── defn.rkt │ └── launcher.rkt ├── input-proliferation │ ├── defn.rkt │ └── launcher.rkt ├── integrity-checking │ ├── README.md │ ├── defn.rkt │ └── launcher.rkt ├── locks │ ├── README.md │ ├── defn.rkt │ └── launch.rkt ├── make-verification-files │ ├── README.md │ ├── certify │ └── verify ├── maximum-trust │ └── dangerous.rkt ├── output-conflicts │ ├── README.md │ ├── dub.rkt │ ├── launcher.rkt │ └── lub.rkt ├── package-dependencies │ ├── README.md │ ├── defn.rkt │ ├── info.rkt │ ├── launch.rkt │ ├── other.rkt │ └── program.rkt ├── racket-installation-manager │ ├── README.md │ └── rim ├── racket-modules │ ├── README.md │ ├── defn.rkt │ ├── info.rkt │ ├── launch.rkt │ └── program.rkt ├── self-hosting │ ├── defn.rkt │ └── launch.rkt ├── signature-checking │ ├── README.md │ ├── defn.rkt │ ├── launcher.rkt │ ├── public-key.pem │ ├── sign.rkt │ └── signature.bin ├── versioning │ ├── README.md │ ├── launcher.rkt │ ├── loud0.rkt │ ├── loud1.rkt │ └── quiet0.rkt └── workspaces │ ├── .gitignore │ ├── README.md │ ├── definition.rkt │ ├── launcher.rkt │ └── launcher2.rkt ├── file.rkt ├── format.rkt ├── info.rkt ├── input.rkt ├── integrity.rkt ├── integrity ├── base.rkt └── ffi.rkt ├── l10n.rkt ├── l10n ├── en-us.rkt └── shared.rkt ├── launcher.rkt ├── logo.png ├── main.rkt ├── message.rkt ├── monad.rkt ├── notary.rkt ├── output.rkt ├── package.rkt ├── path.rkt ├── pkgdef.rkt ├── pkgdef └── static.rkt ├── port.rkt ├── printer.rkt ├── query.rkt ├── racket-module.rkt ├── racket-version.rkt ├── rfc4648.rkt ├── security.rkt ├── setting.rkt ├── signature.rkt ├── signature ├── base.rkt ├── ffi.rkt └── snake-oil.rkt ├── source.rkt ├── state.rkt ├── string.rkt ├── subprogram.rkt ├── system.rkt ├── transaction.rkt ├── url.rkt └── version.rkt /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#* 3 | .\#* 4 | .denxi 5 | .DS_Store 6 | compiled 7 | coverage 8 | html 9 | *.log 10 | doc 11 | workspace 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format of this file is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 6 | This project follows [these versioning rules](https://sagegerard.com/edition-revision-versioning.html). 7 | 8 | 9 | ## Unreleased 10 | 11 | - Rename to Denxi 12 | - Allow per-artifact installations 13 | - Change default branch from `master` to `default` 14 | - Allow use of custom cryptographic backends to unblock users when the 15 | bundled library doesn't load. 16 | - Merge non-guide and non-reference documentation into examples that 17 | double as functional tests. 18 | - Add draft functional test API 19 | - Switch to launcher-centric workflow for users 20 | - Add default CHF that is subject to change 21 | - Support `file://` URLs in `http-source` 22 | - Make catalogs more useful: Request packages using a base URL and/or 23 | highly-abbreviated queries 24 | - Add setting for trusting server certificates outside of the OS trust store. 25 | - Refactor to allow dynamically-computed inputs 26 | 27 | 28 | ## [draft:beta] - 2021-03-17 29 | 30 | - Can now extract from any archive format using a plugin 31 | - Can now extract from both `.tar.gz` and `.tgz`, not just the latter. 32 | - Rewrite project to support transactions and content addressable package outputs 33 | - Add `tags`, `home-page`, `racket-versions`, and `description` fields to package definition 34 | - Switch to `denxi/pkgdef` and `#lang denxi` as canonical languages. 35 | - Add `mkint` command 36 | - Help authors make input expressions 37 | - Redirected downloads with no limit now work properly 38 | - Distinguish between abstract and concrete inputs 39 | - Add missing flags to CLI 40 | - Add cycle detection 41 | - Make revision number conversions more explicit 42 | - Loosen requirement for well-formed package queries 43 | - `make-digest` expands user paths before opening files. 44 | - Add `fetch` command 45 | - Simplify `mkinput` command 46 | - Support source-level caching 47 | - Allow integrity and signature information to use sources 48 | - Require trust in cryptographic hash functions 49 | - Allow signature verification via plugin 50 | - Merge rcfile functionality into plugin 51 | 52 | 53 | ## [draft:alpha] - 2020-08-02 54 | 55 | - Define a namespace-specific string format for packages 56 | - Use SHA-384 for subresource integrity checking 57 | - Verify digital signatures (No restrictions on asymmetric crypto yet. Just bring your own keys). 58 | - Capture digests, installed packages, and `denxi` configuration for collaborators. 59 | - Create a new package from a template 60 | - Start a sandboxed REPL inside of a package 61 | - Show a diff between a capture and a workspace 62 | - Reproduce a workspace from a capture 63 | - Apply stored configuration 64 | - Run install commands 65 | - Create a direct link to a package 66 | - Bundle a package for use on a server 67 | - Print informative output on demand 68 | - Basic operations 69 | - Install a package (as a link) from a directory 70 | - Install a package from a service 71 | - Uninstall a package 72 | - Serve package artifacts 73 | - Run server 74 | - GET file 75 | - GET directory listing 76 | - GET file from URN's NSS 77 | 78 | 79 | [Unreleased]: https://github.com/zyrolasting/denxi/compare/beta...HEAD 80 | [draft:alpha]: https://github.com/zyrolasting/denxi/releases/tag/alpha 81 | [draft:beta]: https://github.com/zyrolasting/denxi/releases/tag/beta 82 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | After installing Denxi, you will have a `denxi` command available for 4 | your system. 5 | 6 | First, make sure the following programs are available in your `PATH`. 7 | 8 | * SQLite 3.24.0+. Verify with `sqlite3 -version` 9 | 10 | * Racket 7.0+. Verify with `racket -v` 11 | 12 | > To build from source, clone or download Denxi from 13 | > [GitHub](https://github.com/zyrolasting/denxi.git) and run `make` in the 14 | > source directory. 15 | 16 | Next, run `raco pkg install denxi`. If this fails, then make sure you do 17 | not have a conflicting version installed. 18 | 19 | The `denxi` command should now be available. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export SHELL=/bin/bash 2 | 3 | .PHONY: default test clean 4 | 5 | default: setup 6 | 7 | install: 8 | raco pkg install -i --skip-installed 9 | 10 | setup: install 11 | raco setup --check-pkg-deps --doc-index --fail-fast -j 8 denxi 12 | 13 | setup-doc: 14 | raco scribble +m --markdown --dest-name INSTALL docs/guide/setup.scrbl 15 | 16 | test: setup 17 | raco test -j 8 -c denxi 18 | 19 | exe: build test 20 | raco exe -o denxi -l --exf -U cli.rkt 21 | 22 | clean: 23 | git clean -fdX 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository has been archived. Denxi remains under active 2 | development on a private host. Join the Racket community Discord 3 | or Discourse for announcements about new releases. 4 | 5 |
6 | 7 | ![](./logo.png) 8 | [![Racket](https://img.shields.io/badge/-Made%20with%20Racket-darkred?logo=racket)](https://racket-lang.org) 9 | [![Link to AGPLv3 License](https://img.shields.io/badge/license-AGPLv3-yellowgreen)](./COPYING) 10 | [![Link to Denxi white paper](https://img.shields.io/badge/doc-white%20paper-lightgrey)](https://docs.racket-lang.org/denxi-white-paper/index.html) 11 | [![Link to Documentation](https://img.shields.io/badge/doc-index-blue.svg)](https://docs.racket-lang.org/denxi-index/index.html) 12 | [![Link to Youtube Presentation](https://img.shields.io/badge/youtube-RacketCon%202020-red)](https://youtu.be/bIi-tUzOwdw?t=2330) 13 | [![Link to support page for the author](https://img.shields.io/badge/%24-donate-success)](https://sagegerard.com/show-support.html) 14 | 15 | Denxi is a programming model for distributing data. It reduces the 16 | cost of producing package managers, storefronts, operating systems, 17 | and CI/CD systems. 18 | -------------------------------------------------------------------------------- /codec.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ; Decode and encode byte strings with prescribed algorithms. 4 | 5 | (require file/sha1 6 | net/base64 7 | racket/contract 8 | racket/string 9 | "rfc4648.rkt") 10 | 11 | (define abbreviated-decode-procedure/c 12 | (-> (or/c non-empty-string? bytes?) bytes?)) 13 | 14 | (provide (contract-out 15 | [abbreviated-decode-procedure/c 16 | chaperone-contract?] 17 | [coerce-string 18 | (-> (or/c string? bytes?) string?)] 19 | [coerce-bytes 20 | (-> (or/c string? bytes?) bytes?)] 21 | [denxi-encodings 22 | (non-empty-listof symbol?)] 23 | [denxi-encoding/c 24 | flat-contract?] 25 | [encoded-file-name 26 | (-> (or/c bytes? string?) 27 | string?)] 28 | [encode 29 | (-> denxi-encoding/c 30 | (or/c bytes? string?) 31 | (or/c bytes? string?))] 32 | [decode 33 | (-> denxi-encoding/c 34 | (or/c bytes? string?) 35 | (or/c bytes? string?))] 36 | [base32 abbreviated-decode-procedure/c] 37 | [base64 abbreviated-decode-procedure/c] 38 | [hex abbreviated-decode-procedure/c])) 39 | 40 | 41 | (define (coerce-string v) 42 | (if (string? v) 43 | v 44 | (bytes->string/utf-8 v))) 45 | 46 | 47 | (define (coerce-bytes v) 48 | (if (bytes? v) 49 | v 50 | (string->bytes/utf-8 v))) 51 | 52 | 53 | (define denxi-encodings 54 | '(base64 base32 hex colon-separated-hex)) 55 | 56 | 57 | (define denxi-encoding/c 58 | (apply or/c denxi-encodings)) 59 | 60 | 61 | (define (encoded-file-name variant) 62 | (let ([as-string (coerce-string (encode 'base32 variant))]) 63 | (substring (coerce-string as-string) 0 64 | (min (string-length as-string) 32)))) 65 | 66 | 67 | (define (encode encoding variant) 68 | (define bstr (coerce-bytes variant)) 69 | (define output 70 | (case encoding 71 | [(hex) 72 | (bytes->hex-string bstr)] 73 | [(colon-separated-hex) 74 | (define hexed (bytes->hex-string bstr)) 75 | (string-join 76 | (for/list ([i (in-range 0 (sub1 (string-length hexed)) 2)]) 77 | (string (string-ref hexed i) (string-ref hexed (add1 i)))) 78 | ":")] 79 | [(base32) (base32-encode bstr)] 80 | [(base64) (base64-encode bstr #"")])) 81 | (if (bytes? variant) 82 | (coerce-bytes output) 83 | (coerce-string output))) 84 | 85 | 86 | (define (decode encoding encoded) 87 | (case encoding 88 | [(hex) 89 | (hex-string->bytes (coerce-string encoded))] 90 | [(colon-separated-hex) 91 | (unless (regexp-match? #px"^([0-9A-Fa-f]{2}:)*[0-9A-Fa-f]{2}$" encoded) 92 | (raise-user-error 'decode "~v is not a valid colon-separated hex string." encoded)) 93 | (decode 'hex (string-replace (coerce-string encoded) ":" ""))] 94 | [(base32) 95 | (base32-decode (coerce-bytes encoded))] 96 | [(base64) 97 | (base64-decode (coerce-bytes encoded))])) 98 | 99 | 100 | (define (base32 v) 101 | (decode 'base32 v)) 102 | 103 | 104 | (define (base64 v) 105 | (decode 'base64 v)) 106 | 107 | 108 | (define (hex variant) 109 | (decode (if (string-contains? (coerce-string variant) ":") 110 | 'colon-separated-hex 111 | 'hex) 112 | variant)) 113 | 114 | 115 | (module+ test 116 | (require rackunit) 117 | (for ([encoding (in-list denxi-encodings)]) 118 | (test-case (format "Encode and decode a message using ~a" encoding) 119 | (define bstr (encode encoding #"abc")) 120 | (check-equal? (decode encoding bstr) #"abc") 121 | (check-equal? (decode encoding (bytes->string/utf-8 bstr)) #"abc")))) 122 | -------------------------------------------------------------------------------- /crypto.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require racket/contract 4 | racket/format 5 | racket/runtime-path 6 | racket/string 7 | (rename-in ffi/unsafe [-> -->]) 8 | "message.rkt") 9 | 10 | (define+provide-message $crypto ()) 11 | (define+provide-message $crypto:error $crypto (queue)) 12 | (define+provide-message $crypto:unavailable $crypto ()) 13 | (define-runtime-path crypto/ "crypto") 14 | 15 | (provide 16 | (contract-out 17 | [crypto-clear-ffi-cache! 18 | (-> void?)] 19 | [crypto-get-lib! 20 | (-> (or/c ffi-lib? exn?))] 21 | [crypto-get-obj! 22 | (-> (or/c bytes? symbol? string?) 23 | ctype? 24 | any/c)] 25 | [crypto-dump-error-queue! 26 | (-> (or/c #f list?))] 27 | [crypto-translate-error! 28 | (-> exact-integer? 29 | (or/c #f string?))] 30 | [assert-crypto-availability 31 | procedure?] 32 | [crypto-raise! 33 | procedure?])) 34 | 35 | (define ffi-cache (make-hash)) 36 | 37 | (define (crypto-clear-ffi-cache!) 38 | (hash-clear! ffi-cache)) 39 | 40 | (define (crypto-get-lib!) 41 | (define key '||) 42 | 43 | (define (attempt!) 44 | (define arch 45 | (let ([subpath (~a (system-library-subpath))]) 46 | (if (equal? (system-path-convention-type) 'windows) 47 | (cadr (string-split subpath "\\")) 48 | (car (string-split subpath "-"))))) 49 | 50 | (define local-directory-name 51 | (~a arch "-" (system-type 'os))) 52 | 53 | (hash-set! ffi-cache key 54 | (with-handlers ([values values]) 55 | (ffi-lib 56 | (path-replace-extension 57 | (build-path crypto/ 58 | local-directory-name 59 | "crypto") 60 | (system-type 'so-suffix))))) 61 | 62 | (hash-ref ffi-cache key)) 63 | 64 | (if (hash-has-key? ffi-cache key) 65 | (if (exn? (hash-ref ffi-cache key)) 66 | (attempt!) 67 | (hash-ref ffi-cache key)) 68 | (attempt!))) 69 | 70 | (define (crypto-get-obj! sym type) 71 | (define l (crypto-get-lib!)) 72 | (hash-ref! ffi-cache sym 73 | (λ () 74 | (and (not (exn? l)) 75 | (get-ffi-obj sym l type))))) 76 | 77 | (define (assert-crypto-availability) 78 | (when (exn? (crypto-get-lib!)) 79 | (raise ($crypto:unavailable)))) 80 | 81 | (define (crypto-dump-error-queue!) 82 | (define get (crypto-get-obj! #"ERR_get_error" (_fun --> _ulong))) 83 | (and get 84 | (let loop ([q null]) 85 | (define code (get)) 86 | (if (zero? code) 87 | (reverse q) 88 | (loop (cons code q)))))) 89 | 90 | 91 | (define crypto-translate-error! 92 | (let ([translation-buffer (make-bytes 256)]) 93 | (λ (code) 94 | (define translate 95 | (crypto-get-obj! #"ERR_error_string" 96 | (_fun _ulong _pointer --> _string))) 97 | (and translate 98 | (begin (translate code translation-buffer) 99 | (bytes->string/utf-8 translation-buffer)))))) 100 | 101 | 102 | (define (crypto-raise!) 103 | (raise ($crypto:error (crypto-dump-error-queue!)))) 104 | -------------------------------------------------------------------------------- /crypto/.gitignore: -------------------------------------------------------------------------------- 1 | openssl 2 | dist 3 | *.o 4 | a.out 5 | -------------------------------------------------------------------------------- /crypto/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.3 FATAL_ERROR) 2 | option(WINDOWS "Set the target build system to windows" OFF) 3 | 4 | project(crypto LANGUAGES C) 5 | 6 | set(BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) 7 | 8 | # Include relevant libraries and directories based on platform 9 | if(${WINDOWS}) 10 | message("Windows Build Initiated") 11 | find_library(ws2_32 lib REQUIRED) 12 | find_library(crypt32 lib REQUIRED) 13 | set(INCLUDE_DIR "${BASE_DIR}/dist/windows/include") 14 | set(LIB_CRYPTO_DIR "${BASE_DIR}/dist/windows/lib/libcrypto.a") 15 | set(LIB_SSL_DIR "${BASE_DIR}/dist/windows/lib/libssl.a") 16 | set(INSTALL_DIR "${BASE_DIR}/lib/windows") 17 | else() 18 | message("Host build initiated") 19 | set(INCLUDE_DIR "${BASE_DIR}/dist/host/include") 20 | set(LIB_CRYPTO_DIR "${BASE_DIR}/dist/host/lib/libcrypto.a") 21 | set(LIB_SSL_DIR "${BASE_DIR}/dist/host/lib/libssl.a") 22 | set(INSTALL_DIR "${BASE_DIR}/lib/host") 23 | endif() 24 | 25 | message("SSL Includes: " ${INCLUDE_DIR}) 26 | message("libcrypto.a: " ${LIB_CRYPTO_DIR}) 27 | message("libssl.a: " ${LIB_SSL_DIR}) 28 | message("Install Dir: " ${INSTALL_DIR}) 29 | 30 | 31 | set(CMAKE_INSTALL_PREFIX ${INSTALL_DIR}) 32 | add_library(ssl_crypto STATIC IMPORTED GLOBAL) 33 | set_property(TARGET ssl_crypto PROPERTY IMPORTED_LOCATION ${LIB_CRYPTO_DIR}) 34 | 35 | add_library(ssl64 STATIC IMPORTED GLOBAL) 36 | set_property(TARGET ssl64 PROPERTY IMPORTED_LOCATION ${LIB_SSL_DIR}) 37 | 38 | include_directories(${INCLUDE_DIR}) 39 | add_library(crypto SHARED crypto.c) 40 | 41 | if(${WINDOWS}) 42 | target_link_libraries(crypto 43 | PUBLIC ssl64 44 | PUBLIC ssl_crypto 45 | PRIVATE ws2_32 46 | PRIVATE crypt32) 47 | else() 48 | target_link_libraries(crypto 49 | PUBLIC ssl64 50 | PUBLIC ssl_crypto) 51 | endif() 52 | 53 | install(TARGETS crypto) -------------------------------------------------------------------------------- /crypto/Makefile: -------------------------------------------------------------------------------- 1 | export SHELL=/bin/bash 2 | 3 | .PHONY: clean 4 | 5 | cwd := $(shell pwd) 6 | 7 | openssl: 8 | git clone \ 9 | --recursive \ 10 | --jobs 4 \ 11 | --branch master \ 12 | git://git.openssl.org/openssl.git \ 13 | openssl 14 | 15 | dist: openssl 16 | cd openssl && \ 17 | git fetch && \ 18 | git checkout 3f987381929ee725daf4746591144dde18f313e1 && \ 19 | perl Configure \ 20 | --prefix=$(cwd)/dist \ 21 | --openssldir=$(cwd)/dist \ 22 | -DPEDANTIC \ 23 | -DOPENSSL_USE_IPV6=0 \ 24 | no-comp \ 25 | no-dtls \ 26 | no-nextprotoneg \ 27 | no-psk \ 28 | no-srp \ 29 | no-ssl2 \ 30 | no-ssl3 \ 31 | no-weak-ssl-ciphers \ 32 | shared && \ 33 | make && make test && make install_sw install_ssldirs 34 | 35 | crypto.so: crypto.c 36 | gcc -shared -o crypto.so -fPIC -Idist/include crypto.c \ 37 | -Ldist/lib -lpthread -lc -l:libcrypto.a -l:libssl.a -ldl 38 | 39 | a.out: crypto.c 40 | gcc -D DENXI_CRYPTO_MAIN -Idist/include crypto.c \ 41 | -Ldist/lib -lc -l:libcrypto.a -l:libssl.a -ldl -lpthread 42 | 43 | clean: 44 | rm -rf openssl 45 | -------------------------------------------------------------------------------- /crypto/README.md: -------------------------------------------------------------------------------- 1 | This product includes software developed by the OpenSSL Project 2 | for use in the OpenSSL Toolkit. (http://www.openssl.org/) 3 | 4 | --- 5 | 6 | This subsystem statically-links OpenSSL libraries across platforms to 7 | provide a simplified interface for creating message digests, creating 8 | signatures using assymetric keys, and verifying signatures. Each 9 | architecture and operating system is represented using its own library. 10 | -------------------------------------------------------------------------------- /crypto/compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | for i in "$@" 5 | do 6 | case $i in 7 | -t=*|--target=*) 8 | TARGET="${i#*=}" 9 | shift 10 | ;; 11 | *) 12 | echo "Unkown param passed" 13 | exit 1 14 | ;; 15 | esac 16 | done 17 | 18 | CWD=$PWD 19 | CROSS_COMPILE_SSL="--cross-compile-prefix=" 20 | 21 | if [[ -z "$TARGET" ]] 22 | then 23 | TARGET="host" 24 | CROSS_COMPILE_SSL="" 25 | elif [[ $TARGET == "windows" ]] 26 | then 27 | COMPILER="x86_64-w64-mingw32-" 28 | CROSS_COMPILE_SSL="mingw64 "$CROSS_COMPILE_SSL$COMPILER 29 | CMAKE_TOOLCHAIN_FILE="-DCMAKE_TOOLCHAIN_FILE=${CWD}/mingw_toolchain.cmake" 30 | elif [[ $TARGET == "osx" ]] 31 | then 32 | COMPILER="" 33 | echo "Cross Compilation for OSX is not supported!" 34 | exit 1 35 | else 36 | echo "Selected target is unknown" 37 | exit 1 38 | fi 39 | 40 | BUILD_DIR=${CWD}/dist/${TARGET} 41 | LIB_CRYPTO_BUILD_DIR=${CWD}/build/${TARGET} 42 | 43 | echo "CWD=$CWD" 44 | echo "Build Directory=$BUILD_DIR" 45 | echo "Selected Target=$TARGET" 46 | echo "Final Build Dir=$LIB_CRYPTO_BUILD_DIR" 47 | if [[ ! -z $COMPILER ]] 48 | then 49 | echo "Selected Compiler=$COMPILER" 50 | echo "OpenSSL Params=$CROSS_COMPILE_SSL" 51 | fi 52 | 53 | 54 | read -p "Continue? Y/n " -n 1 -r 55 | echo # move to new line to prevent ugly exit 56 | if [[ $REPLY =~ ^[Yy]$ || -z "$REPLY" ]] 57 | then 58 | if [[ ! -d "openssl" ]] 59 | then 60 | git clone \ 61 | --recursive \ 62 | --jobs 4 \ 63 | --branch master \ 64 | git://git.openssl.org/openssl.git \ 65 | openssl 66 | fi 67 | 68 | cd openssl && \ 69 | git fetch && \ 70 | git checkout 3f987381929ee725daf4746591144dde18f313e1 71 | read -p "Reconfigure openssl makefile? Y/n " -n 1 -r 72 | echo # move to new line to prevent ugly exit 73 | if [[ $REPLY =~ ^[Yy]$ || -z "$REPLY" ]] 74 | then 75 | perl Configure ${CROSS_COMPILE_SSL} \ 76 | --prefix=${BUILD_DIR} \ 77 | --openssldir=${BUILD_DIR} \ 78 | -static 79 | -DPEDANTIC \ 80 | -DOPENSSL_USE_IPV6=0 \ 81 | no-comp \ 82 | no-dtls \ 83 | no-nextprotoneg \ 84 | no-psk \ 85 | no-srp \ 86 | no-ssl2 \ 87 | no-ssl3 \ 88 | no-weak-ssl-ciphers \ 89 | no-shared 90 | fi 91 | 92 | read -p "Compile openssl? Y/n " -n 1 -r 93 | echo # move to new line to prevent ugly exit 94 | if [[ $REPLY =~ ^[Yy]$ || -z "$REPLY" ]] 95 | then 96 | make && make install_sw install_ssldirs 97 | fi 98 | 99 | read -p "Compile libcrypto for racket? Y/n " -n 1 -r 100 | echo # move to new line to prevent ugly exit 101 | if [[ $REPLY =~ ^[Yy]$ || -z "$REPLY" ]] 102 | then 103 | mkdir -p ${LIB_CRYPTO_BUILD_DIR} 104 | cd ${LIB_CRYPTO_BUILD_DIR} 105 | echo "Working Dir: $(pwd)" 106 | echo "CMAKE TOOLCHAIN: ${CMAKE_TOOLCHAIN_FILE}" 107 | if [[ ! -z $CMAKE_TOOLCHAIN_FILE ]] 108 | then 109 | if [[ $TARGET == "windows" ]] 110 | then 111 | cmake ${CMAKE_TOOLCHAIN_FILE} -DWINDOWS=ON ${CWD} 112 | fi 113 | else 114 | cmake ${CWD} 115 | fi 116 | make && make install && make clean 117 | fi 118 | cd ${CWD}/openssl 119 | make clean 120 | fi 121 | -------------------------------------------------------------------------------- /crypto/mingw_toolchain.cmake: -------------------------------------------------------------------------------- 1 | # the name of the target operating system 2 | SET(CMAKE_SYSTEM_NAME Windows) 3 | 4 | # which compilers to use for C and C++ 5 | SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) 6 | SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) 7 | 8 | # here is the target environment located 9 | SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) 10 | 11 | # adjust the default behaviour of the FIND_XXX() commands: 12 | # search headers and libraries in the target environment, search 13 | # programs in the host environment 14 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 15 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 16 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 17 | -------------------------------------------------------------------------------- /crypto/x86_64-macosx/crypto.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyrolasting/denxi/2547f0b3c016e0c5761267fa61ae1be591cc8e7b/crypto/x86_64-macosx/crypto.dylib -------------------------------------------------------------------------------- /crypto/x86_64-unix/crypto.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyrolasting/denxi/2547f0b3c016e0c5761267fa61ae1be591cc8e7b/crypto/x86_64-unix/crypto.so -------------------------------------------------------------------------------- /crypto/x86_64-windows/crypto.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyrolasting/denxi/2547f0b3c016e0c5761267fa61ae1be591cc8e7b/crypto/x86_64-windows/crypto.dll -------------------------------------------------------------------------------- /crypto/x86_64-windows/crypto.dll.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyrolasting/denxi/2547f0b3c016e0c5761267fa61ae1be591cc8e7b/crypto/x86_64-windows/crypto.dll.a -------------------------------------------------------------------------------- /dig.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require racket/contract) 4 | 5 | (define shovel/c 6 | (-> any/c (subprogram/c artifact?))) 7 | 8 | (provide 9 | (contract-out 10 | [install-found-artifact 11 | (->* (any/c 12 | path-string?) 13 | (shovel/c) 14 | (subprogram/c (cons/c path-record? path-record?)))] 15 | [find-artifact 16 | (->* (any/c) 17 | (shovel/c) 18 | (subprogram/c artifact?))] 19 | [shovel/c 20 | chaperone-contract?] 21 | [broken-shovel 22 | shovel/c] 23 | [dig-failure 24 | (-> (or/c symbol? string?) 25 | any/c 26 | subprogram?)] 27 | [current-shovel 28 | (parameter/c shovel/c)] 29 | [shovel-cons 30 | (-> shovel/c shovel/c shovel/c)] 31 | [shovel-list 32 | (->* () #:rest (listof shovel/c) shovel/c)])) 33 | 34 | 35 | (require "artifact.rkt" 36 | "monad.rkt" 37 | "setting.rkt" 38 | "state.rkt" 39 | "subprogram.rkt" 40 | "message.rkt" 41 | "query.rkt") 42 | 43 | 44 | (define+provide-message $dig ()) 45 | (define+provide-message $dig:no-artifact $dig (shovel-name hint)) 46 | 47 | (define+provide-setting DENXI_INSTALL_ARTIFACTS 48 | (listof (list/c string? string?)) null) 49 | 50 | 51 | (define (dig-failure name hint) 52 | (subprogram-failure ($dig:no-artifact name hint))) 53 | 54 | (define (broken-shovel v) 55 | (dig-failure (object-name broken-shovel) v)) 56 | 57 | 58 | (define ((shovel-cons a b) k) 59 | (subprogram (λ (m) 60 | (define-values (r m*) (run-subprogram (a k) m)) 61 | (if (artifact? r) 62 | (values r m*) 63 | (run-subprogram (b k) m*))))) 64 | 65 | 66 | (define (shovel-list . xs) 67 | (if (null? xs) 68 | broken-shovel 69 | (shovel-cons 70 | (car xs) 71 | (apply shovel-list 72 | (cdr xs))))) 73 | 74 | 75 | (define current-shovel 76 | (make-parameter broken-shovel)) 77 | 78 | 79 | (define (install-found-artifact plinth link-path [dig (current-shovel)]) 80 | (mdo arti := (find-artifact plinth dig) 81 | records := (install-artifact arti link-path) 82 | (subprogram-unit records))) 83 | 84 | 85 | (define-subprogram (find-artifact plinth [dig (current-shovel)]) 86 | (if (artifact? plinth) 87 | ($use plinth) 88 | ($run! (dig plinth)))) 89 | 90 | 91 | (module+ test 92 | (require rackunit 93 | (submod "subprogram.rkt" test)) 94 | 95 | (test-case "Combine shovels" 96 | (define ((bind-shovel v) k) 97 | (if (eq? k v) 98 | (subprogram-unit (make-artifact v)) 99 | (dig-failure v k))) 100 | 101 | (define shovel 102 | (shovel-list 103 | (bind-shovel #"a") 104 | (bind-shovel #"b") 105 | (bind-shovel #"c"))) 106 | 107 | (define (check expected) 108 | (check-equal? (artifact-source (get-subprogram-value (shovel expected))) 109 | expected)) 110 | 111 | (check #"a") 112 | (check #"b") 113 | (check #"c") 114 | 115 | (define-values (should-be-failure messages) 116 | (run-subprogram (shovel #"d"))) 117 | 118 | (check-eq? should-be-failure FAILURE) 119 | (check-equal? messages 120 | (list ($dig:no-artifact 'broken-shovel #"d") 121 | ($dig:no-artifact #"c" #"d") 122 | ($dig:no-artifact #"b" #"d") 123 | ($dig:no-artifact #"a" #"d")))) 124 | 125 | (test-subprogram-value 126 | "Trivially find provided artifact" 127 | (find-artifact (make-artifact #"") broken-shovel) 128 | (λ (result) 129 | (check-equal? (artifact-source result) #""))) 130 | 131 | (test-subprogram 132 | "Do not find an artifact by default when none is available" 133 | (find-artifact #f) 134 | (λ (result messages) 135 | (check-equal? result FAILURE) 136 | (check-equal? messages 137 | (list ($dig:no-artifact 'broken-shovel #f))))) 138 | 139 | (test-case "Find an artifact using the right shovel" 140 | (define (indy req) 141 | (subprogram-unit 142 | (make-artifact 143 | (if req #"t" #"f")))) 144 | 145 | (define (check v expected) 146 | (check-subprogram-value 147 | (find-artifact v indy) 148 | (λ (result) 149 | (check-equal? (artifact-source result) expected)))) 150 | 151 | (check #t #"t") 152 | (check #f #"f"))) 153 | -------------------------------------------------------------------------------- /dig/http.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require racket/contract) 4 | (provide 5 | (contract-out 6 | [make-http-shovel 7 | (->* (url-variant?) 8 | ((or/c #f symbol?) 9 | (or/c #f source-variant?)) 10 | shovel/c)])) 11 | 12 | (require racket/format 13 | racket/list 14 | "../artifact.rkt" 15 | "../crypto.rkt" 16 | "../dig.rkt" 17 | "../integrity.rkt" 18 | "../subprogram.rkt" 19 | "../signature.rkt" 20 | "../source.rkt" 21 | "../url.rkt") 22 | 23 | 24 | (define (make-http-shovel base-url-variant [chf #f] [public-key-source #f]) 25 | (let ([base-url (coerce-url base-url-variant)]) 26 | (λ (key) 27 | (if (url-variant? key) 28 | (let* ([ext-url (extend-base-url base-url (coerce-url key))]) 29 | (subprogram-unit 30 | (artifact (http-source ext-url) 31 | (and chf 32 | (integrity chf 33 | (http-source 34 | (add-url-extname ext-url (~a "." chf))))) 35 | (and public-key-source 36 | (signature public-key-source 37 | (http-source 38 | (add-url-extname ext-url (~a "." chf ".sig")))))))) 39 | (dig-failure 'http-shovel key))))) 40 | 41 | (define (add-url-extname ext-url extname) 42 | (struct-copy url ext-url 43 | [path 44 | (let ([pps (url-path ext-url)]) 45 | (if (null? pps) 46 | (list (path/param extname null)) 47 | (let* ([last-index (sub1 (length pps))] 48 | [last-pp (list-ref pps last-index)]) 49 | (list-set pps 50 | last-index 51 | (struct-copy path/param last-pp 52 | [path (~a (path/param-path last-pp) 53 | extname)])))))])) 54 | 55 | (define (extend-base-url base-url ext-url) 56 | (struct-copy url base-url 57 | [path 58 | (append (url-path base-url) 59 | (url-path ext-url))] 60 | [query 61 | (append (url-query base-url) 62 | (url-query ext-url))])) 63 | 64 | (module+ test 65 | (require rackunit) 66 | 67 | (define (check-source src expected) 68 | (check-pred http-source? src) 69 | (check-equal? (url->string (http-source-request-url src)) 70 | expected)) 71 | 72 | (test-case "Create artifacts expecting only content" 73 | (define dig (make-http-shovel "https://example.com/pkg")) 74 | (define arti (get-subprogram-value (dig "/cool/stuff?a=b"))) 75 | (check-pred artifact? arti) 76 | (check-false (artifact-integrity arti)) 77 | (check-false (artifact-signature arti)) 78 | (check-source (artifact-source arti) 79 | "https://example.com/pkg/cool/stuff?a=b")) 80 | 81 | (test-case "Create artifacts expecting content and integrity info" 82 | (define dig (make-http-shovel "https://example.com/pkg" 'md5)) 83 | (define arti (get-subprogram-value (dig "x"))) 84 | (check-pred artifact? arti) 85 | (check-pred integrity? (artifact-integrity arti)) 86 | (check-false (artifact-signature arti)) 87 | 88 | (check-source (artifact-source arti) 89 | "https://example.com/pkg/x") 90 | (check-source (integrity-digest (artifact-integrity arti)) 91 | "https://example.com/pkg/x.md5")) 92 | 93 | (test-case "Create artifacts expecting all info" 94 | (define dig (make-http-shovel "https://example.com/pkg" 'md5 empty-source)) 95 | (define arti (get-subprogram-value (dig "x"))) 96 | (check-pred artifact? arti) 97 | (check-pred integrity? (artifact-integrity arti)) 98 | (check-pred signature? (artifact-signature arti)) 99 | 100 | (check-source (artifact-source arti) 101 | "https://example.com/pkg/x") 102 | (check-source (integrity-digest (artifact-integrity arti)) 103 | "https://example.com/pkg/x.md5") 104 | 105 | (check-eq? (signature-public-key (artifact-signature arti)) 106 | empty-source) 107 | 108 | (check-source (signature-body (artifact-signature arti)) 109 | "https://example.com/pkg/x.md5.sig"))) 110 | 111 | -------------------------------------------------------------------------------- /docs/guide/creating-packages.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../shared.rkt" 4 | @for-label[@except-in[denxi/pkgdef #%module-begin] 5 | denxi/integrity 6 | denxi/signature 7 | racket/base]] 8 | 9 | @title[#:tag "new-pkg"]{Package Definitions} 10 | 11 | A @deftech{package definition} is a program, and this one creates a 12 | familiar greeting in a text file. 13 | 14 | @racketmod[#:file "definition.rkt" 15 | denxi 16 | 17 | (input "hello.txt" 18 | (artifact (text-source "Hello, world!") #f #f)) 19 | 20 | (output "default" 21 | (keep-input "hello.txt")) 22 | ] 23 | 24 | We define an @racket[input] as a named source of data. You'll later 25 | learn what the other terms mean, and how they can be adjusted to use 26 | the network, verify downloads, and even use your own notation for 27 | external resources. 28 | 29 | We define an @racket[output] as a named subprogram that turns inputs 30 | into files. We'll install that output in the next section. 31 | -------------------------------------------------------------------------------- /docs/guide/denxi-guide.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base] 4 | "../shared.rkt"] 5 | 6 | @title[#:style '(toc)]{Denxi Guide} 7 | @author[(author+email "Sage L. Gerard" "sage@sagegerard.com" #:obfuscate? #t)] 8 | 9 | Here you learn what Denxi is, what it can do for you, and how to 10 | practice using it. 11 | 12 | For all documentation, see @other-doc[denxi-index]. 13 | 14 | @include-section{introduction.scrbl} 15 | @include-section{setup.scrbl} 16 | @include-section{creating-packages.scrbl} 17 | @include-section{launchers.scrbl} 18 | @include-section{outroduction.scrbl} 19 | -------------------------------------------------------------------------------- /docs/guide/introduction.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../shared.rkt" @for-label[racket/base]] 4 | 5 | @title{Introduction} 6 | 7 | @margin-note{@other-doc[denxi-white-paper] covers the thinking behind 8 | Denxi.} 9 | 10 | Denxi is a programming model for distributing data. It reduces the 11 | cost of producing package managers, storefronts, operating systems, 12 | and CI/CD systems. Denxi can work as a library, as a framework, or as 13 | a standalone application. 14 | 15 | @other-doc[denxi-reference] covers hundreds of functions. This guide 16 | briefly introduces core concepts and directs you to example 17 | programs. Use them to understand @other-doc[denxi-reference]. 18 | 19 | By doing this you'll gain a few benefits. 20 | 21 | 22 | @itemlist[ 23 | @item{Denxi practices zero-trust principles to avoid 24 | @hyperlink["http://www.ranum.com/security/computer_security/editorials/dumb/"]{many 25 | problems}. Denxi only performs side-effects that it can trace back to 26 | your explicit, informed consent.} 27 | @item{Denxi can manage dependencies for projects written in any language, or your whole operating system.} 28 | @item{If you don't like how a software installation affects your system, you can override it.} 29 | @item{If you don't like Denxi itself, you can use it to create and distribute an alternative. It creates package managers like Racket creates languages.} 30 | @item{(@litchar{npm} users) You'll have more safety checks to protect your system.} 31 | @item{(@litchar{raco pkg} users) You won't mutate your Racket installation when installing software.} 32 | @item{(PLaneT users) You can install multiple versions of a project without generating non-@racket[eq?] bindings.} 33 | @item{(Guix/Nix users) You get the benefits you expect on Windows, macOS, and GNU/Linux distributions out of the box.} 34 | ] 35 | -------------------------------------------------------------------------------- /docs/guide/launchers.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../shared.rkt" 4 | @for-label[racket/base 5 | denxi/cli 6 | denxi/integrity]] 7 | 8 | @title[#:tag "launchers"]{Launchers} 9 | 10 | This @deftech{launcher} program starts and controls Denxi. 11 | 12 | @racketmod[#:file "my-denxi.rkt" 13 | denxi/launcher 14 | (module+ main (launch-denxi!)) 15 | ] 16 | 17 | Denxi built-in launcher is called @litchar{denxi}, and it actually has 18 | the same implementation! The difference is that we control our 19 | launchers. Save the above code into @litchar{my-denxi.rkt} and give it 20 | the definition from the previous section. 21 | 22 | @verbatim[#:indent 2]|{ 23 | $ racket my-denxi.rkt do ++install-abbreviated definition.rkt 24 | }| 25 | 26 | This command will give you first of many reasons for why it won't 27 | work, because all launchers start with @deftech{zero-trust}. You must 28 | explicitly allow @italic{all relevant details} for security. We won't 29 | neglect security, but we'll use this production-unsafe launcher until 30 | you can work with zero-trust. 31 | 32 | @racketmod[#:file "my-denxi.rkt" 33 | denxi/launcher 34 | 35 | (current-chfs (list snake-oil-chf)) 36 | (DENXI_TRUST_BAD_DIGEST #t) 37 | (module+ main (launch-denxi!)) 38 | ] 39 | 40 | This launcher has a low bar, so run the same @litchar{do} command. A 41 | symbolic link will appear. Compare the definition to output to start 42 | connecting some dots. To uninstall the output, delete the link and 43 | run the garbage collection command. 44 | 45 | @verbatim[#:indent 2]|{ 46 | $ rm my-first-package && racket my-denxi.rkt gc 47 | Recovered 13 bytes 48 | }| 49 | 50 | To recap, launchers build software from package definitions and issue 51 | links to their outputs. 52 | 53 | To be fair, this is a toy example, and toy examples are illustrative 54 | at best. The good news is that you are now equipped to review working 55 | examples. As the examples become more advanced, they incorporate more 56 | real-world information. 57 | -------------------------------------------------------------------------------- /docs/guide/outroduction.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../shared.rkt"] 4 | 5 | @title[#:tag "outroduction"]{Practice} 6 | 7 | @(define ex0 "https://github.com/zyrolasting/denxi/tree/master/examples/generated-racket-bindings") 8 | @(define ex1 "https://github.com/zyrolasting/denxi/tree/master/examples/cryptography-backends") 9 | @(define ex2 "https://github.com/zyrolasting/denxi/tree/master/examples/output-conflicts") 10 | @(define ex3 "https://github.com/zyrolasting/denxi/tree/master/examples/racket-installation-manager") 11 | 12 | You installed Denxi, wrote a package definition, wrote a launcher, 13 | then used the launcher to (un)install something. 14 | 15 | This guide leaves out a lot of detail to make one point: 16 | @italic{everything boils down to launchers and package 17 | definitions}. Now that you know what both are, you can read built-in 18 | examples and write your own. Doing so will help you understand 19 | @other-doc[denxi-reference]. 20 | 21 | The toy examples we covered in this guide are for context only. The 22 | built-in examples are more interesting. You will learn how to 23 | @hyperlink[ex1]{define a host's OpenSSL instance as a cryptographic 24 | backend}, @hyperlink[ex2]{resolve package conflicts}, 25 | @hyperlink[ex3]{manage multiple Racket versions}, and 26 | @hyperlink[ex0]{prevent two lexically identical structs from 27 | generating non-@racket[eq?] bindings}. 28 | 29 | You may review the examples through the links above, but all of the 30 | examples have been shipped to you in Denxi's source. Run this command 31 | to print the examples directory on your disk. Go to that directory and 32 | look for a README. The README picks up from immediately after this 33 | guide, and shows you a suggested reading order. 34 | 35 | @verbatim[#:indent 2]|{ 36 | racket -e '(~a (collection-file-path "examples" "denxi"))' 37 | }| 38 | 39 | With enough practice you will write a custom launcher that is useful 40 | to others. You can distribute that custom launcher using 41 | @litchar{denxi}, which creates more confidence in both Denxi and your 42 | launcher. 43 | 44 | Thank you for trying Denxi, and I hope it brings you good fortune. My 45 | name is Sage, and you can reach me through my 46 | @hyperlink["https://sagegerard.com"]{website}. 47 | -------------------------------------------------------------------------------- /docs/guide/project.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../shared.rkt"] 4 | 5 | @title[#:tag "project"]{Project Information} 6 | 7 | @hyperlink["https://github.com/zyrolasting/denxi"]{GitHub} hosts the source 8 | code for Denxi. @bold{It is not yet ready for production use.} 9 | Dependency management is a hard problem that takes time to handle well. 10 | 11 | 12 | @section{Contributing} 13 | 14 | To contribute to Denxi, you can do at least one of the following. 15 | 16 | @itemlist[ 17 | @item{@hyperlink["https://www.paypal.com/paypalme/sagegerard"]{Contribute funds to support development}} 18 | @item{Open a pull request against the main repository.} 19 | @item{@hyperlink["https://github.com/zyrolasting/denxi/issues"]{Open an issue}} 20 | ] 21 | 22 | 23 | @subsection{A Note For Programmers} 24 | 25 | The tools in use are Racket, SQLite3, OpenSSL, Make, and C. I list them in the 26 | order of prominence, so expertise in an earlier item will help you understand a 27 | larger volume of source code. 28 | 29 | While there are no style rules in place, please make your code visually similar 30 | to surrounding code. Please take care to produce lean diffs. 31 | 32 | 33 | @subsection{High-value Contributions} 34 | 35 | @margin-note{Denxi's Racket source would ideally know nothing about the underlying platform.} 36 | @itemlist[ 37 | 38 | @item{ 39 | @bold{Windows support}. 40 | This will entail some knowledge of C, MSVC, and the Windows API for the best end-user experience. 41 | } 42 | 43 | @item{ 44 | @bold{Security audits}. 45 | If you are trained in information security, pointing out vulnerabilities is greatly appreciated. 46 | } 47 | 48 | @item{ 49 | @bold{Translations}. 50 | Denxi is designed to load user-facing strings lazily given locale information, make it relatively easy to offer a translation to a different human language. 51 | } 52 | 53 | ] 54 | -------------------------------------------------------------------------------- /docs/guide/setup.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../shared.rkt"] 4 | 5 | @title[#:tag "setup"]{Setup} 6 | 7 | After installing Denxi, you will have a @litchar{denxi} command 8 | available for your system. 9 | 10 | First, make sure the following programs are available in your @tt{PATH}. 11 | 12 | @itemlist[ 13 | @item{SQLite 3.24.0+. Verify with @litchar{sqlite3 -version}} 14 | @item{Racket 7.0+. Verify with @litchar{racket -v}} 15 | ] 16 | 17 | @margin-note{To build from source, clone or download Denxi from 18 | @hyperlink["https://github.com/zyrolasting/denxi.git"]{GitHub} and run 19 | @litchar{make} in the source directory.} 20 | 21 | Next, run @litchar|{raco pkg install denxi}|. If this fails, then 22 | make sure you do not have a conflicting version installed. 23 | 24 | The @tt{denxi} command should now be available. 25 | -------------------------------------------------------------------------------- /docs/index/denxi-index.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[racket/format 4 | racket/runtime-path 5 | scribble/core 6 | scribble/html-properties 7 | "../shared.rkt"] 8 | 9 | @title[#:style '(toc)]{Denxi Documentation} 10 | @author[(author+email "Sage L. Gerard" "sage@sagegerard.com" #:obfuscate? #t)] 11 | 12 | @(define logo-element 13 | (elem #:style 14 | (style #f 15 | (list (alt-tag "img") 16 | (attributes 17 | `((src . "https://github.com/zyrolasting/denxi/raw/default/docs/index/doc-logo.png") 18 | (style . "max-width: 100%"))))))) 19 | 20 | @logo-element 21 | 22 | @centered{ 23 | @other-doc[denxi-guide] - @other-doc[denxi-reference] - @other-doc[denxi-white-paper] 24 | } 25 | -------------------------------------------------------------------------------- /docs/index/doc-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyrolasting/denxi/2547f0b3c016e0c5761267fa61ae1be591cc8e7b/docs/index/doc-logo.png -------------------------------------------------------------------------------- /docs/journal/10-jan-2021.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/report 2 | 3 | @require["../shared.rkt" scribble/manual] 4 | @title{10 January 2021} 5 | @by-slg 6 | 7 | I've started this development journal to track activity that does not 8 | fit in a comment or a changelog. Note that everything I write is in my 9 | own words, with the expectation that you understand what Denxi is and 10 | what I am trying to do by making it. 11 | 12 | I invite @italic{constructive} criticism in terms of what you read 13 | here. That will help improve plans before any code changes. I 14 | emphasize “constructive,” because I am not being paid to work on 15 | Denxi. Respect little red hens, get cake. 16 | 17 | 18 | @section{Denxi to Lose Fixed Taxonomy, Gain Configurable Canon} 19 | 20 | Denxi uses fixed taxa: provider, brand, edition, and revision. The 21 | user may freely define the meaning of strings used in each taxon. 22 | This wasn't good enough to keep naming methods flexible because the 23 | taxa was prescribed, and not necessarily consistent with a user's 24 | logic. 25 | 26 | This is a large, breaking change that I intend to have finished by 31 27 | March 2021. Users will experience a smaller interface from Denxi, but 28 | will no longer be able to construct package queries. Instead, all 29 | discovery information for packages become user-defined. 30 | 31 | 32 | @section{Client-side Refactoring of Racket Packages} 33 | 34 | After the above refactor, I intend to design a Denxi launcher that 35 | downloads Racket packages and refactors the source code such that 36 | third-party collections are not installation-specific. 37 | 38 | The consequence is that users use Racket packages, but completely 39 | escape the Racket package management system in doing so. I'm excited 40 | about this use case because it involves removing one key element of 41 | uncertainty when predicting what a Racket program will do when you run 42 | it. 43 | 44 | A collection path that isn't built into your Racket installation can 45 | mean just about anything. Users do thankfully control what collection 46 | paths resolve to, but other Racket programmers decide them 47 | @italic{first}. I can, in theory, download a Racket package that 48 | claims every possible collection path in an installation such that all 49 | future installations encounter conflicts. That and a few other reasons 50 | made me avoid use of @litchar{raco pkg} and third party collections in 51 | my future projects. Hopefully with this launcher, others can do the 52 | same. 53 | -------------------------------------------------------------------------------- /docs/journal/denxi-journal.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/report 2 | 3 | @require["../shared.rkt"] 4 | @title[#:style '(toc)]{Denxi Journal} 5 | @by-slg 6 | 7 | This is a development journal for Denxi, with entries sorted in 8 | reverse chronological order. 9 | 10 | This log does not maintain links to other Denxi documents, because a 11 | claim about Denxi in an entry applies only to the context of that 12 | entry. It is possible for a change in Denxi to break the logic of at 13 | least zero log entries, and such transitions will be captured in a new 14 | log entry. 15 | 16 | For all documentation, see @other-doc[denxi-index]. 17 | 18 | @table-of-contents[] 19 | @include-section{10-jan-2021.scrbl} 20 | -------------------------------------------------------------------------------- /docs/reference/background.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @title{Prerequisite Knowledge} 4 | 5 | @require[@for-label[racket 6 | denxi/integrity] 7 | "../shared.rkt"] 8 | 9 | To understand this reference, you should have a working understanding 10 | of @deftech{cryptographic hash functions}, or @deftech{CHF}s. A CHF 11 | turns a variable-length value into a fixed-length value called a 12 | @deftech{message digest}, a.k.a. @deftech{digest}, @deftech{checksum}, 13 | or @deftech{hash}. All terms refer to the output of a CHF in Denxi's 14 | documentation. 15 | 16 | Sometimes download links have a “SHA-256” or some such name by a 17 | digest. Before version 8.2, Racket installers come with with SHA-1 18 | digests as hex strings. If you download one of those installers, you 19 | should create your own digest using SHA-1 using the installer's 20 | contents. If the digests match, you have reason to believe the 21 | installer has not been altered in transit. In other words, it 22 | maintained its integrity. 23 | 24 | @verbatim|{ 25 | $ wget -O install-racket.exe \ 26 | https://download.racket-lang.org/releases/8.1/installers/racket-minimal-8.1-i386-win32-bc.exe 27 | 28 | # This digest matches corresponding digest on the 8.1 releases page, so the file is correct. 29 | $ openssl dgst -sha1 install-racket.exe 30 | SHA1(install-racket.exe)= 78e19d25cb2a26264aa58771413653d9d8b5a9dc 31 | }| 32 | 33 | There's a caveat. Even when digests match, we can only assume the 34 | installers has good integrity if we trust the 35 | CHF. @hyperlink["https://www.schneier.com/blog/archives/2020/01/new_sha-1_attac.html"]{Smart 36 | people induced a collision in SHA-1}, meaning that it is possible to 37 | trick you into thinking that a harmful file is safe when you check 38 | integrity using SHA-1. This is why the word “cryptographic” in 39 | “cryptographic hash function” carries a lot of weight. If a CHF works 40 | well, it is hard to reproduce a known digest with doctored content. 41 | 42 | When CHFs aren't good enough, Denxi uses @deftech{asymmetric 43 | cryptography} to verify that a file came from a trusted party. 44 | Assymetric cryptography involves two keys. One key is public, and the 45 | other is private (that is, known only by you). The public key can 46 | scramble a message such that only the private key holder can read it. 47 | 48 | Private keys can also sign data, such that only the corresponding 49 | public key can verify that the holder of the private key created the 50 | signature. That way, if you trust the private key holder, you can 51 | trust the signed data. Denxi verifies signatures this way. 52 | 53 | All of this only works if your private key stays secret, and we know 54 | it was you who shared your public key. There are 55 | @hyperlink["https://en.wikipedia.org/wiki/Key_signing_party"]{gatherings 56 | for that}, if you are inclined to attend. 57 | 58 | As you read this reference, you can assume that any abstractions that 59 | mention CHFs or signatures are intimately tied to Denxi's trust model. 60 | -------------------------------------------------------------------------------- /docs/reference/denxi-reference.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base] 4 | "../shared.rkt"] 5 | 6 | @title[#:style '(toc)]{Denxi Reference} 7 | @author[(author+email "Sage L. Gerard" "sage@sagegerard.com" #:obfuscate? #t)] 8 | 9 | For all documentation, see @other-doc[denxi-index]. 10 | 11 | @table-of-contents[] 12 | 13 | @include-section{background.scrbl} 14 | @include-section{modules/cli.scrbl} 15 | @include-section{modules/cmdline.scrbl} 16 | @include-section{modules/cli-flag.scrbl} 17 | @include-section{modules/package.scrbl} 18 | @include-section{modules/pkgdef.scrbl} 19 | @include-section{modules/query.scrbl} 20 | @include-section{modules/launcher.scrbl} 21 | @include-section{modules/subprogram.scrbl} 22 | @include-section{modules/artifact.scrbl} 23 | @include-section{modules/message.scrbl} 24 | @include-section{modules/input.scrbl} 25 | @include-section{modules/output.scrbl} 26 | @include-section{modules/integrity.scrbl} 27 | @include-section{modules/signature.scrbl} 28 | @include-section{modules/dig.scrbl} 29 | @include-section{modules/main.scrbl} 30 | @include-section{modules/source.scrbl} 31 | @include-section{modules/version.scrbl} 32 | @include-section{modules/state.scrbl} 33 | @include-section{modules/setting.scrbl} 34 | @include-section{modules/format.scrbl} 35 | @include-section{modules/url.scrbl} 36 | @include-section{modules/string.scrbl} 37 | @include-section{modules/l10n.scrbl} 38 | @include-section{modules/printer.scrbl} 39 | @include-section{modules/archive.scrbl} 40 | @include-section{modules/system.scrbl} 41 | @include-section{modules/monad.scrbl} 42 | @include-section{modules/file.scrbl} 43 | @include-section{modules/crypto.scrbl} 44 | @include-section{modules/port.scrbl} 45 | @include-section{modules/security.scrbl} 46 | @include-section{modules/racket-module.scrbl} 47 | @include-section{modules/codec.scrbl} 48 | @include-section{modules/notary.scrbl} 49 | -------------------------------------------------------------------------------- /docs/reference/maintenance.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../shared.rkt"] 4 | 5 | @title{Maintainance Information} 6 | 7 | This section covers development and maintenance information pertinent 8 | to Denxi. This document will not try to repeat what the code says, but 9 | will instead explain decisions that cannot be adequately summarized in 10 | code comments. 11 | 12 | @include-section{maintenance/basics.scrbl} 13 | @include-section{maintenance/affirmations.scrbl} 14 | -------------------------------------------------------------------------------- /docs/reference/maintenance/affirmations.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base] "../../shared.rkt"] 4 | 5 | @title{Affirmations} 6 | 7 | Each security-critical check is implemented using a composition of 8 | @deftech{affirmations}. An @tech{affirmation} is a Racket procedure 9 | that meets several requirements. 10 | 11 | @itemlist[ 12 | @item{The procedure is pure} 13 | @item{The procedure uses continuation-passing style} 14 | @item{The procedure has a cyclomatic complexity of exactly @racket[2].} 15 | @item{The procedure has no default arguments} 16 | @item{If the procedure returns a value without applying a continuation, 17 | then that value contains the procedure's name.} 18 | ] 19 | 20 | For example, let's look at a procedure that returns @racket[#t] if a 21 | real number is an element of an exclusive interval. 22 | 23 | @racketblock[ 24 | (define (num-between n lo hi) 25 | (and (> n lo) (< n hi))) 26 | ] 27 | 28 | If we define the computation with @tech{affirmations}, the code 29 | becomes far more verbose. 30 | 31 | @racketblock[ 32 | (define-message $between (ok? stage)) 33 | 34 | (define (affirm-< n hi continue) 35 | (if (< n hi) 36 | (continue) 37 | ($between #f (object-name affirm-<)))) 38 | 39 | (define (affirm-> n lo continue) 40 | (if (> n lo) 41 | (continue) 42 | ($between #f (object-name affirm->)))) 43 | 44 | (define (num-between n lo hi) 45 | (affirm-> n lo 46 | (lambda () 47 | (affirm-< n hi 48 | (lambda () 49 | ($between #t (object-name num-between))))))) 50 | ] 51 | 52 | This form returns an answer, any desired context, and the name of the 53 | procedure responsible for the answer in a single value. The original 54 | @racket[num-between] would tell us if a number is not an element of 55 | @litchar{(lo, hi)}, but the modified version tells us @italic{why} it 56 | isn't. If we see @racket[(object-name affirm-<)], then we know that 57 | the number was too large for the interval without using @racket[<] or 58 | keeping a reference to @racketid[hi]. 59 | 60 | In the event an @tech{affirmation} allows a dangerous operation, a 61 | program log will point to it. The goal is to offer transparent logs, 62 | reduced debugging time, and more accessible testing at the expense of 63 | more verbose code. 64 | -------------------------------------------------------------------------------- /docs/reference/maintenance/basics.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../../shared.rkt"] 4 | 5 | @title{Basic Information} 6 | 7 | To build all project deliverables, run @litchar{make} or @litchar{make 8 | build}. To run the tests, run @litchar{make test}. To do both, run 9 | @litchar{make build test}. 10 | 11 | Denxi is versioned according to @secref["versioning" #:doc 12 | denxi-guide] in published package definitions. Do not follow the 13 | versioning scheme defined in @secref["Package_Concepts" #:doc '(lib 14 | "pkg/scribblings/pkg.scrbl")], even in the @litchar{info.rkt} file. 15 | 16 | 17 | @section{User Experience Notice} 18 | 19 | Instead of an @tt{install} or @tt{uninstall} command, Denxi expects a 20 | user to define transactions with the filesystem and request links (read: 21 | references) to the exact files they need. Some users might not appreciate an 22 | unconventional interface, so some push-back is expected. 23 | 24 | Maintainers should refrain from adding aliases that conform to traditional 25 | package manager commands to Denxi, because they would not contribute 26 | functionality. They should instead suggest defining such aliases as shell 27 | scripts, which is a better medium for user preferences. 28 | 29 | A more actionable complaint would address Denxi's current requirement 30 | for Racket values by every flag, including boolean short flags 31 | (e.g. @litchar{-U '#t'}). This is a combination of 32 | @racket[parse-command-line]'s interpretation of procedure arity and 33 | Denxi's insistence on full ability to override the runtime 34 | configuration through several means. 35 | 36 | 37 | @section{Security Notice} 38 | 39 | Like any system that runs code from the Internet, Denxi can be used for 40 | nefarious purposes. This attack surface is made complicated due to differences 41 | in how Windows and Unix-like systems model permissions, and the flexibility of 42 | Denxi's runtime configuration. Denxi offers security 43 | features to mitigate some risks, but administrators should configure their 44 | operating system to restrict the behavior of any user or group running a 45 | Denxi process. Failing to do so allows room for arbitrary code 46 | execution. 47 | -------------------------------------------------------------------------------- /docs/reference/modules/archive.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[ 4 | @for-label[racket/base 5 | racket/contract 6 | file/untar 7 | file/untgz 8 | file/unzip 9 | racket/contract 10 | denxi/archive 11 | denxi/input 12 | denxi/file 13 | denxi/subprogram] 14 | "../../shared.rkt"] 15 | 16 | @title{Archives} 17 | 18 | @defmodule[denxi/archive] 19 | 20 | @racketmodname[denxi/archive] extracts files from a single archive file. 21 | 22 | 23 | @defthing[current-find-extract-procedure 24 | (parameter/c (-> path-string? 25 | (or/c #f (-> input-port? any))))]{ 26 | A parameter used to find an extraction procedure for an archive 27 | located at a given path. The procedure in the parameter may return 28 | @racket[#f] if no other procedure is available. 29 | 30 | The default value always returns @racket[#f]. 31 | } 32 | 33 | 34 | @defproc[(extract [in (or/c path-string? input-port?)]) 35 | (subprogram/c void?)]{ 36 | @margin-note{@racket[extract] offers no control over the destination 37 | paths of extracted files. Use @racket[in-paths] or 38 | @racket[path-matching] to select extracted items.} 39 | 40 | Returns a @tech{subprogram} @racketid[P] that extracts files from 41 | @racket[in] with respect to @racket[(current-directory)]. 42 | 43 | If @racket[in] is a @racket[path-string?], then @racket[(extract in)] 44 | is equivalent to @racket[(call-with-input-file in extract)]. The rest 45 | of this entry assumes @racket[in] is an input port. 46 | 47 | If @racketid[P] cannot figure out how to extract files, it will return 48 | @racket[FAILURE] and log @racket[($extract-report 'unsupported 49 | (object-name in))]. 50 | 51 | If @racketid[P] succeeds, it will return @racket[(void)] and log 52 | @racket[($extract-report 'done (object-name in))]. 53 | 54 | The extraction process infers the archive format from the file 55 | extension in @racket[(object-name in)]. If this is @racket{.tar}, 56 | then @racketid[P] behaves like @racket[untar]. If @racket{.tgz} or 57 | @racket{.tar.gz}, then @racket[untgz]. If @racket{.zip}, 58 | @racket[unzip]. Otherwise, @racketid[P] will attempt to use 59 | @racket[current-find-extract-procedure]. 60 | } 61 | 62 | 63 | @defproc[(extract-input [name string?] [#:keep? any/c #f]) subprogram?]{ 64 | Like @racket[extract], except archive data is drawn from the input 65 | found using @racket[(input-ref name)] and @racket[resolve-input]. If 66 | @racket[keep?] is @racket[#f], the input is released using 67 | @racket[release-input] after extraction. 68 | } 69 | 70 | 71 | @defstruct*[$extract-report ([status symbol?] [target any/c])]{ 72 | Describes the behavior of a call to @racket[extract]. @racket[target] 73 | is @racket[eq?] to the @racket[object-name] of the input port used in 74 | the corresponding @racket[extract] call. 75 | 76 | @racket[status] is 77 | 78 | @itemlist[ 79 | @item{@racket['unsupported] if there was no known way to extract the archive.} 80 | @item{@racket['done] if the extraction finished successfully.} 81 | ] 82 | 83 | } 84 | -------------------------------------------------------------------------------- /docs/reference/modules/codec.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | denxi/codec 6 | denxi/string 7 | @except-in[denxi/pkgdef #%module-begin]] 8 | denxi/codec 9 | racket/pretty 10 | "../../shared.rkt"] 11 | 12 | @title{Codec} 13 | 14 | @defmodule[denxi/codec] 15 | 16 | The @racketmodname[denxi/codec] module encodes and decodes byte 17 | strings. Note that @tech/reference{ports} are not used, so all data 18 | sits entirely in memory. 19 | 20 | @section{High-Level Interface} 21 | 22 | @defproc[(encode [encoding denxi-encoding/c] [variant (or/c bytes? string?)]) (or/c bytes? string?)]{ 23 | Encodes a byte string or UTF-8 encoded string according to 24 | @racket[encoding]. The return value type matches the type of 25 | @racket[variant]. 26 | } 27 | 28 | @defproc[(decode [encoding denxi-encoding/c] [encoded (or/c bytes? string?)]) (or/c bytes? string?)]{ 29 | Decodes a byte string or UTF-8 encoded string according to 30 | @racket[encoding]. The return value type matches the type of 31 | @racket[encoded]. 32 | } 33 | 34 | @defthing[denxi-encodings (listof symbol?)]{ 35 | A list of supported encodings of byte strings. 36 | 37 | Bound to @typeset-code[(pretty-format #:mode 'print denxi-encodings)] 38 | } 39 | 40 | @defthing[denxi-encoding/c flat-contract? #:value (apply or/c denxi-encodings)]{ 41 | A contract that accepts a value in @racket[denxi-encodings]. 42 | } 43 | 44 | @defproc[(encoded-file-name [variant (or/c bytes? string?)]) path-string?]{ 45 | Returns up to the first 32 characters of @racket[(coerce-string 46 | (encode 'base32 variant))], which is suitable for use as a file name 47 | across platforms. 48 | } 49 | 50 | @section{UTF-8 Conversions} 51 | 52 | @defproc[(coerce-string [v (or/c string? bytes?)]) string?]{ 53 | Equivalent to 54 | 55 | @racketblock[(if (string? v) v (bytes->string/utf-8 v))] 56 | } 57 | 58 | @defproc[(coerce-bytes [v (or/c string? bytes?)]) bytes?]{ 59 | Equivalent to 60 | 61 | @racketblock[(if (bytes? v) v (string->bytes/utf-8 v))] 62 | } 63 | 64 | 65 | @section{Abbreviated Decoding Procedures} 66 | 67 | @defthing[abbreviated-decode-procedure/c chaperone-contract? #:value (-> (or/c non-empty-string? bytes?) bytes?)]{ 68 | 69 | An @deftech{abbreviated decoding procedure} (or @deftech{ADP}) helps a 70 | user represent a byte string with a more convenient notation. 71 | 72 | e.g. 73 | 74 | @racketblock[ 75 | (integrity 'sha384 (base64 "z/2YR1yTDC0YBvgQFpGUzXGtqZdPx+E//C2tyYNDENnLf7NGexuuDc/3yOhoGqBn")) 76 | ] 77 | 78 | } 79 | 80 | @defthing[hex abbreviated-decode-procedure/c]{ 81 | Decodes a hex-encoded string to bytes. The process is case-insensitive. 82 | 83 | @racket[encoded] may separate each pair of hex digits with @racket{:}. 84 | 85 | This implies @racket[(equal? (hex "deadbeef") (hex "de:ad:be:ef"))]. 86 | } 87 | 88 | @defthing[base64 abbreviated-decode-procedure/c]{ 89 | Decodes a Base64-encoded string to bytes. 90 | } 91 | 92 | @defthing[base32 abbreviated-decode-procedure/c]{ 93 | Decodes a Base32-encoded string to bytes. 94 | } 95 | -------------------------------------------------------------------------------- /docs/reference/modules/crypto.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @title{Cryptography} 4 | 5 | @require[@for-label[racket/base 6 | racket/contract 7 | racket/pretty 8 | @rename-in[ffi/unsafe (-> -->)] 9 | denxi/crypto] 10 | racket/pretty 11 | denxi/crypto 12 | "../../shared.rkt"] 13 | 14 | @defmodule[denxi/crypto] 15 | 16 | @racketmodname[denxi/crypto] interacts with a bundled derivative of an 17 | OpenSSL library. This module has no side-effects on instantiation, but 18 | will mutate either a FFI object in the Racket runtime, or an error 19 | code cache in the C runtime. To reset the Racket runtime cache, call 20 | @racket[crypto-clear-ffi-cache!]. To dump the error code cache, call 21 | @racket[crypto-dump-error-queue!]. 22 | 23 | @defproc[(crypto-clear-ffi-cache!) void?]{ 24 | Clears an internal cache for the low-level library and its 25 | objects. The cache operates independently of accumulated errors. 26 | } 27 | 28 | @defproc[(crypto-get-lib!) (or/c ffi-lib? exn?)]{ 29 | Returns a @tech/foreign{foreign-library value} for the bundled 30 | cryptographic library, or an exception explaining why it failed to 31 | load. 32 | 33 | The value returned from this function is cached in Racket-managed 34 | memory. If the cached value is an exception, then 35 | @racket[(get-crypto-lib!)] will attempt to load the library 36 | again. This will replace the cached exception. 37 | } 38 | 39 | 40 | @defproc[(crypto-get-obj! [objname (or/c bytes? symbol? string?)] 41 | [type ctype?]) 42 | any/c]{ 43 | Behaves like @racket[get-ffi-obj] when @racket[(get-crypto-lib!)] is 44 | a @tech/foreign{foreign-library value}. 45 | 46 | Returns @racket[#f] if the cryptography library is cached as an 47 | exception, or if @racket[objname] was not found in the library. 48 | 49 | The value returned from this function is cached in Racket-managed 50 | memory. 51 | } 52 | 53 | @defproc[(crypto-dump-error-queue!) (or/c #f list?)]{ 54 | Empties the error queue in the C runtime, then returns the error codes 55 | as a Racket list. 56 | 57 | If the cryptography library is currently cached as an exception, this 58 | function will always return @racket[#f]. 59 | } 60 | 61 | @defproc[(crypto-translate-error! [code exact-integer?]) (or/c #f string?)]{ 62 | Returns a human-readable string of the given error code from the 63 | underlying C library, or @racket[#f] if the cryptography library is 64 | currently cached as an exception. 65 | 66 | This function operates independently from 67 | @racketmodname[denxi/l10n]. If the output string does not respect the 68 | user's locale, then supply translations to the correct 69 | @racketmodname[denxi/l10n] extention and use that extension instead. 70 | } 71 | 72 | @defproc[(crypto-raise!) any]{ 73 | Raises @racket[($crypto:error (crypto-dump-error-queue!))]. 74 | 75 | Note that the side-effect implies movement of the entire error queue 76 | from the C runtime into the raised Racket value. Leverage this when 77 | you have reason to expect that the queue contains @italic{only} the 78 | errors that pertain to a failed operation. 79 | } 80 | 81 | @defstruct*[$crypto ()]{ 82 | A @tech{message} about a cryptographic operation. 83 | } 84 | 85 | @defstruct*[($crypto:error $crypto) 86 | ([queue (listof exact-integer?)])]{ 87 | A low-level cryptographic option failed. @racket[queue] holds all 88 | error codes on the bundled @tt{libcrypto} error queue at the time the 89 | instance was constructed. In the unlikely event of @racket[(null? 90 | queue)], a context trace may be necessary to interpret the root cause. 91 | } 92 | -------------------------------------------------------------------------------- /docs/reference/modules/dig/memory.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket 4 | denxi/artifact 5 | denxi/subprogram 6 | denxi/dig 7 | denxi/dig/memory 8 | denxi/artifact 9 | denxi/query 10 | denxi/source]] 11 | 12 | 13 | @title{Memory Shovels} 14 | 15 | @defmodule[denxi/dig/memory] 16 | 17 | @defproc[(make-memory-shovel [contents (hash/c any/c artifact?)]) shovel/c]{ 18 | Returns a @tech{shovel} that behaves similarly to @racket[(curry 19 | hash-ref contents)]. If the returned procedure cannot find an artifact 20 | in @racket[contents] for some @racketid[key], then it will behave like 21 | @racket[(broken-shovel key)]. 22 | 23 | @racketblock[ 24 | (define dig (make-memory-shovel (hash 'a (artifact (text-source "hello"))))) 25 | 26 | (dig 'a) (code:comment "OK") 27 | (dig 'b) (code:comment "Fails") 28 | ] 29 | } 30 | 31 | @defproc[(make-memory-shovel/pkgdef [contents hash?] 32 | [defaults package-query-defaults-implementation/c]) 33 | shovel/c]{ 34 | Returns a @tech{shovel} that searches @racket[contents] for artifacts 35 | using @tech{package queries}. 36 | 37 | @racket[contents] is a nested hash table keyed first by provider 38 | names, then package names, then edition names, then both revision 39 | names and numbers as shown. 40 | 41 | @racketblock[ 42 | (define providers contents) 43 | (define packages (hash-ref providers "example.com")) 44 | (define editions (hash-ref packages "rpg")) 45 | (define revisions (hash-ref editions "directors-cut")) 46 | (artifact? (hash-ref revisions 0)) 47 | (artifact? (hash-ref revisions 28)) 48 | (artifact? (hash-ref revisions (hash-ref revisions "re-release"))) 49 | ] 50 | 51 | As indicated by the last line, each hash table may map keys to other 52 | keys in the same table. 53 | 54 | e.g. 55 | 56 | @racketblock[ 57 | (hash-ref providers (hash-ref providers "alias.example.com")) 58 | ] 59 | 60 | The shovel will recursively resolve keys in this way until it 61 | encounters a value of a type not used as a key, in observance of the 62 | @tech{digsite metaphor}. If the search yields cyclic keys, this 63 | function will not terminate. 64 | 65 | Package queries are autocompleted using @racket[defaults], and 66 | converted to canonical form with respect to a canon based on the hash 67 | table's shape. 68 | 69 | Example: 70 | 71 | @racketblock[ 72 | (define dig 73 | (make-memory-shovel/pkgdef 74 | (hash "default" "jon" 75 | "jon" (hash "calculator" 76 | (hash "scientific" 77 | (hash 0 (artifact #"...") 78 | 8 (artifact #"...") 79 | "initial" 0 80 | "beta" 8)))))) 81 | 82 | (dig ":calculator:scientific:initial:beta") 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /docs/reference/modules/file.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | racket/sequence 6 | file/glob 7 | denxi/file 8 | denxi/subprogram] 9 | "../../shared.rkt"] 10 | 11 | @title{Files} 12 | 13 | @defmodule[denxi/file] 14 | 15 | @racketmodname[denxi/file] extends and reprovides 16 | @racketmodname[racket/file]. 17 | 18 | @defproc[(in-paths [pattern (or/c regexp? pregexp? byte-regexp? byte-pregexp? string?)] 19 | [start directory-exists? (current-directory)]) 20 | (sequence/c path?)]{ 21 | Returns a sequence of paths relative to @racket[start] that match 22 | @racket[pattern]. 23 | 24 | If @racket[pattern] is a string and not a regular expression object, 25 | then @racket[pattern] is used as a glob pattern for use in 26 | @racket[glob-match?]. 27 | } 28 | 29 | @defproc[(path-matching [pattern (or/c regexp? pregexp? byte-regexp? byte-pregexp? string?)] 30 | [start directory-exists? (current-directory)]) 31 | (subprogram/c path?)]{ 32 | Like @racket[in-paths], except this returns a @tech{subprogram} that 33 | fails with @racket[$path-not-found] on the @tech{subprogram log} if no 34 | paths are found. Otherwise, the procedure uses the first matching 35 | path. 36 | } 37 | 38 | 39 | @defproc[(directory-empty? [path path-string?]) boolean?]{ 40 | Returns @racket[(null? (directory-list path))]. 41 | } 42 | 43 | 44 | @defproc[(something-exists? [path path-string?]) boolean?]{ 45 | @racketblock[ 46 | (or (file-exists? path) 47 | (directory-exists? path) 48 | (link-exists? path))] 49 | } 50 | 51 | 52 | @defproc[(linked? [link-path path-string?] [path path-string?]) boolean?]{ 53 | Returns @racket[#t] if @racket[link-path] refers to an existing link, 54 | a file, directory, or link exists at @racket[path], and both paths 55 | resolve to the same filesystem identifier. 56 | } 57 | 58 | @defproc[(file-link-exists? [link-path path-string?]) boolean?]{ 59 | Returns @racket[#t] if @racket[link-path] refers to an existing link, 60 | and the link points to a regular file. 61 | } 62 | 63 | @defstruct*[($path-not-found $message) ([pattern (or/c regexp? pregexp? byte-regexp? byte-pregexp? string?)] [wrt path-string?])]{ 64 | A @tech{message} reporting if @racket[(path-matching pattern wrt)] found no path. 65 | } 66 | -------------------------------------------------------------------------------- /docs/reference/modules/format.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | racket/match 6 | racket/string 7 | denxi/format 8 | denxi/message] 9 | "../../shared.rkt"] 10 | 11 | 12 | @title{Formatting} 13 | 14 | @defmodule[denxi/format] 15 | 16 | @racketmodname[denxi/format] provides all bindings from 17 | @racketmodname[racket/format], including the below bindings. 18 | 19 | @section{Conventional Formatting Procedures} 20 | 21 | @defproc[(format-symbol-for-display [sym symbol?]) string?]{ 22 | Like @racket[symbol->string], except the resulting string wraps the 23 | symbol's string content in conventional quotes for display in 24 | human-readable output. 25 | } 26 | 27 | @defproc[(indent-lines [lines (listof string?)]) (listof string?)]{ 28 | Returns a new version of @racket[lines] where each element has two 29 | leading spaces. 30 | } 31 | 32 | @defproc[(join-lines [#:trailing? trailing? any/c #f] 33 | [#:suffix suffix (or/c char? string? #f)] 34 | [lines (listof string?)]) string?]{ 35 | Combines @racket[lines] into a single string. 36 | 37 | If @racket[suffix] is @racket[#f], then @racket[join-lines] will use a 38 | platform-specific suffix. 39 | 40 | If @racket[trailing?] is @racket[#t], then the last line will also end 41 | in the suffix. 42 | } 43 | 44 | @defproc[(join-lines* [#:trailing? trailing? any/c #f] 45 | [#:suffix suffix (or/c char? string? #f)] 46 | [line string?] ...) 47 | string?]{ 48 | Like @racket[join-lines], except lines are accumulated from formal 49 | arguments. 50 | } 51 | 52 | 53 | @section{Message Formatting} 54 | 55 | @tech{Messages} are useful for exchanging information without 56 | formatting concerns, but program output must include human-readable 57 | strings. This section therefore defines a @deftech{message formatter} 58 | as a procedure that translates a @tech{message} to a string. The 59 | bindings documented in this section pertain exclusively to message 60 | formatters. 61 | 62 | @defthing[message-formatter/c chaperone-contract? #:value (-> $message? string?)]{ 63 | A contract that matches a @tech{message formatter}. 64 | } 65 | 66 | @defform[(message-formatter patts ...)]{ 67 | Expands to @racket[(λ (m) (match m patts ...))]. 68 | 69 | @racketblock[ 70 | (define-message $foo (a b c)) 71 | 72 | (code:comment "\"1 2 3\"") 73 | ((message-formatter [($foo x y z) (format "~a ~a ~a" x y z)]) ($foo 1 2 3)) 74 | ] 75 | } 76 | 77 | @defform[(define-message-formatter id patts ...)]{ 78 | Expands to @racket[(define id (message-formatter patts ...))] 79 | } 80 | 81 | @defform[(define+provide-message-formatter id patts ...)]{ 82 | Expands to 83 | 84 | @racketblock[ 85 | (begin (provide (contract-out [id message-formatter/c])) 86 | (define-message-formatter id patts ...))] 87 | } 88 | 89 | @defproc[(combine-message-formatters [formatter message-formatter/c] ...) message-formatter/c]{ 90 | Returns a @tech{message formatter} that uses each @racket[formatter] 91 | in the order passed. 92 | } 93 | 94 | @defthing[default-message-formatter message-formatter/c]{ 95 | A @tech{message formatter} useful only for producing locale-independent fallback strings. 96 | } 97 | 98 | @defthing[current-message-formatter (parameter/c message-formatter/c) #:value default-message-formatter]{ 99 | A @tech/reference{parameter} holding the @tech{message formatter} for 100 | use with @racket[format-message]. 101 | } 102 | 103 | @defproc[(format-message [m $message?]) string?]{ 104 | Equivalent to @racket[((current-message-formatter) m)]. 105 | } 106 | -------------------------------------------------------------------------------- /docs/reference/modules/input.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | racket/path 6 | denxi/artifact 7 | denxi/dig 8 | denxi/subprogram 9 | denxi/message 10 | denxi/integrity 11 | denxi/input 12 | denxi/monad 13 | denxi/signature 14 | denxi/source 15 | denxi/string] 16 | "../../shared.rkt"] 17 | 18 | @title{Package Inputs} 19 | 20 | @defmodule[denxi/input] 21 | 22 | @defstruct*[package-input ([name string?] [plinth (or/c any/c artifact?)])]{ 23 | A @deftech{package input} is an instance of @racket[package-input]. 24 | Each instance represents data that a package may lazily fetch. 25 | 26 | The @racket[plinth] acts as a placeholder for a value used to retrieve 27 | an @tech{artifact}. If the plinth does not hold an instance of 28 | @racket[artifact], then we use that value in the @tech{digsite 29 | metaphor} to find an artifact. 30 | } 31 | 32 | @defproc[(make-package-input [name string?] 33 | [plinth (or/c any/c artifact?) 34 | #f]) 35 | package-input?]{ 36 | A contracted constructor for @racket[package-input]. 37 | } 38 | 39 | @defthing[abstract-package-input? predicate/c]{ 40 | Returns @racket[#t] if the argument is an @deftech{abstract package 41 | input}, meaning that @racket[package-input-plinth] is @racket[#f]. 42 | } 43 | 44 | @defthing[current-inputs (parameter/c (listof package-input?))]{ 45 | A @tech/reference{parameter} bound to inputs to use with @racket[input-ref]. 46 | } 47 | 48 | @defproc[(input-ref [name string?]) (subprogram/c package-input?)]{ 49 | Returns the first element of @racket[(current-inputs)] with an 50 | @racket[package-input-name] @racket[equal?] to @racket[name]. 51 | } 52 | 53 | @defproc[(find-input [inputs (listof package-input?)] [name string?]) (subprogram/c package-input?)]{ 54 | Returns a @tech{subprogram} @racketid[P] that either returns the 55 | first input in @racket[inputs] with the given name, or fails with 56 | @racket[$input:not-found] on the program log. 57 | } 58 | 59 | @defproc[(release-input [input package-input?]) (subprogram/c void?)]{ 60 | Returns a @tech{subprogram} @racketid[P] that deletes the 61 | symbolic link derived from @racket[input]. 62 | } 63 | 64 | @defproc[(resolve-input [input package-input?]) (subprogram/c path-string?)]{ 65 | Returns a @tech{subprogram} @racketid[P] that, when applied, 66 | acquires and verifies the bytes for @racket[input]. @racketid[P] 67 | returns a relative path to a symbolic link in 68 | @racket[(current-directory)]. 69 | 70 | @racketid[P] may fail for many possible reasons, which will appear in 71 | the program log. 72 | } 73 | 74 | @defproc[(keep-input [name string?]) (subprogram/c path-string?)]{ 75 | Equivalent to @racket[(mdo i := (input-ref name) (resolve-input i))]. 76 | 77 | Use for inputs that you do not intend to release. 78 | } 79 | 80 | @defstruct*[($input $message) ([name string?])]{ 81 | A @tech{message} regarding an input with a given name. 82 | } 83 | 84 | @defstruct*[($input:not-found $input) ()]{ 85 | Represents a failure to find an input using @racket[find-input]. 86 | } 87 | 88 | @defstruct[untrusted-source ([input package-input?])]{ 89 | A @tech{source} that, when @tech{tapped}, yields bytes found using the 90 | input. If no bytes are found or do not pass security checks, the 91 | source is @tech{exhausted}. @racket[$transfer] messages use the 92 | input's name. 93 | 94 | This is the only @tech{source} that includes all possible safety 95 | checks and customizations defined by the @tech{launcher}, making it 96 | suitable for use with arbitrary data. 97 | } 98 | 99 | @defproc[(find-artifact-for-input [in package-input?]) (subprogram/c artifact?)]{ 100 | Returns a @tech{subprogram} that attempts to create an @tech{artifact} 101 | from @racket[in]. 102 | } 103 | -------------------------------------------------------------------------------- /docs/reference/modules/integrity/ffi.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | denxi/integrity/ffi] 6 | "../../../shared.rkt"] 7 | 8 | @title{Integrity FFI} 9 | 10 | @defmodule[denxi/integrity/ffi] 11 | 12 | @racketmodname[denxi/integrity/ffi] is a private module that defines 13 | FFI bindings for a bundled library dedicated to integrity checking. 14 | 15 | @defproc[(integrity-ffi-available?!) boolean?]{ 16 | Returns @racket[#t] if the FFI dynamically linked against the bundled 17 | foreign library for the purposes of integrity checking operations. 18 | } 19 | 20 | @defthing[integrity-ffi-chf-available?! predicate/c]{ 21 | Returns @racket[#t] if the input argument is a symbol, and can be used 22 | to load a CHF implemented in the foreign library. 23 | } 24 | 25 | @defproc[(integrity-ffi-get-c-chfs!) (or/c #f (listof symbol?))]{ 26 | Returns a list of symbols that suitable for use with other functions 27 | that accept symbols as inputs. 28 | } 29 | 30 | @defproc[(integrity-ffi-get-default-chf-index!) (or/c #f exact-nonnegative-integer?)]{ 31 | Returns a foreign value representing the index of 32 | @racket[(integrity-ffi-get-supported-chfs!)] suggested for use as a 33 | default CHF. 34 | 35 | Do not confuse this index with @racket[get-default-chf]. The default 36 | is a suggestion by the library among its own implementations, and does 37 | not apply as a default for the runtime unless it is installed using 38 | @racket[current-chfs]. 39 | } 40 | 41 | @defproc[(integrity-ffi-get-load-chf!) any/c]{ 42 | Returns a foreign function for loading a CHF implementation in the C 43 | runtime. In OpenSSL, the C runtime uses the default implementation 44 | provider. 45 | } 46 | 47 | @defproc[(integrity-ffi-get-get-digest-size!) any/c]{ 48 | Returns a foreign function used to report the expected size of a 49 | message digest from a given CHF implementation. 50 | } 51 | 52 | @defproc[(integrity-ffi-get-make-digest!) any/c]{ 53 | Returns a foreign function used to create a message digest using a CHF 54 | implementation. 55 | } 56 | 57 | @defproc[(integrity-ffi-get-chf-count!) (or/c #f exact-positive-integer?)]{ 58 | Returns a Racket positive integer, or @racket[#f] on load failure. 59 | 60 | The integer represents the number of CHFs supported by the C runtime, 61 | not the number of implementations available. 62 | } 63 | 64 | @defproc[(integrity-ffi-get-supported-chfs!) any/c]{ 65 | Returns a FFI value as a C string array. Each element represents the 66 | name of a CHF. There are @racket[(integrity-ffi-get-chf-count!)] 67 | elements in the array. 68 | } 69 | 70 | 71 | @section{Integrity Foreign Functions} 72 | 73 | To be included. 74 | 75 | -------------------------------------------------------------------------------- /docs/reference/modules/l10n.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | denxi/format 6 | denxi/l10n 7 | denxi/subprogram 8 | denxi/message 9 | denxi/printer] 10 | "../../shared.rkt"] 11 | 12 | @title{Localization} 13 | 14 | @defmodule[denxi/l10n] 15 | 16 | @(define-syntax-rule (defn sym str ...) 17 | (item (racket 'sym) ": " str ...)) 18 | 19 | @racketmodname[denxi/l10n] uses @tech{messages} to communicate with 20 | the user according to the value of @racket[(system-language+country)]. 21 | Currently, the only supported locale is @tt{en-US}. 22 | 23 | 24 | @defproc[(get-message-formatter) message-formatter/c]{ 25 | Returns a @tech{message formatter} for translating @tech{messages} 26 | to strings in the user's locale.} 27 | 28 | 29 | @defproc[(get-localized-string [sym symbol?]) string?]{ 30 | Returns a string for the user's locale. 31 | 32 | @itemlist[ 33 | @defn[top-level-cli-help]{A list of available @litchar{denxi} subcommands} 34 | @defn[backwards-racket-version-interval]{An error message for when a user expresses a backwards Racket version interval in a syntax class. Mimic this phrasing: “minimum Racket version cannot exceed maximum Racket version”.} 35 | @defn[show-command-help]{A list of available @litchar{denxi show} subcommands} 36 | @item{@italic{}: A short description of the named setting.} 37 | ]} 38 | 39 | 40 | @defproc[(run+print-subprogram [l subprogram?]) any/c]{ 41 | Returns the first value from @racket[(run-subprogram l)]. 42 | 43 | Before returning control, each @tech{message} @racketid[M] from 44 | @racket[run-subprogram] is printed using 45 | 46 | @racketblock[(write-message M (get-message-formatter) (current-output-port))] 47 | } 48 | -------------------------------------------------------------------------------- /docs/reference/modules/launcher.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | denxi/launcher] 5 | "../../shared.rkt"] 6 | 7 | @title{Launchers} 8 | 9 | @(defmodulelang* (denxi/launcher)) 10 | 11 | A @deftech{launcher} is a Racket module defined by a user for the 12 | purposes of extending, configuring, then launching Denxi. A launcher 13 | starts with the same level of privilege as the OS-level user because 14 | it executes in advance of Denxi's restrictions, so a programmer must 15 | exercise caution with untrusted data. 16 | 17 | The @racketmodname[denxi/launcher] language is a superset of 18 | @racketmodname[racket/base] that reprovides all modules in the 19 | @tt{denxi} collection except @racketmodname[denxi/pkgdef]. 20 | -------------------------------------------------------------------------------- /docs/reference/modules/main.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[@except-in[denxi/pkgdef #%module-begin] syntax/module-reader] 4 | "../../shared.rkt"] 5 | 6 | @title{Reader Extension} 7 | 8 | @(defmodulelang* (denxi)) 9 | 10 | @racketmodname[denxi], as a reader extension, defines a 11 | @racketmodname[denxi/pkgdef] module. The grammar matches that of a 12 | @racketmodname[denxi/pkgdef] module body, because the reader is 13 | defined as @racket[(module reader syntax/module-reader denxi/pkgdef)] 14 | -------------------------------------------------------------------------------- /docs/reference/modules/message.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | racket/match 6 | denxi/subprogram 7 | denxi/format 8 | denxi/message 9 | denxi/port] 10 | "../../shared.rkt"] 11 | 12 | @title{Messages} 13 | 14 | @defmodule[denxi/message] 15 | 16 | A @deftech{message} is an instance of the @racket[$message] 17 | @tech/reference{prefab} @tech/reference{structure} used to share 18 | information in the Denxi runtime, in logs, and between 19 | processes. When the term “@tech{message}” is ambiguous, then 20 | @deftech{Denxi message} applies the context of this section. 21 | 22 | @tech{Message} types may form a heirarchy using colon-separated 23 | identifiers that start with @racket[$]. The @racket[$message] type 24 | itself has no semantics beyond serving as the root type, and its 25 | identifier does not appear in other structure type identifiers. For 26 | example, identifiers pertaining to command line messages start with 27 | @tt{$cli}, not @tt{$message:cli}. 28 | 29 | The type heirarchy serves only to organize statements under a given 30 | topic. Meaning that a @racket[$transfer:budget?] would capture 31 | messages pertaining to a budget for a byte transfer, but an instance 32 | of @racket[$transfer:budget:exceeded] is an exact statement reporting 33 | an overrun. 34 | 35 | A message may require another message to serve as context. A type 36 | heirarchy is unhelpful for this purpose, since 37 | @racket[$transfer:budget:exceeded] could apply to a download in the 38 | context of a build process or a verification process. It is difficult 39 | to define a single type to capture this kind of variation, so 40 | Denxi instead composes messages into machine-readable 41 | documents. 42 | 43 | 44 | @defstruct*[$message () #:prefab]{ 45 | The base type for all @tech{messages}. 46 | } 47 | 48 | @defform*[((define-message id [field-expr ...]) 49 | (define-message id super-id [field-expr ...]))]{ 50 | Like @racket[struct], in that @racket[(define-message foo (a b c))] is 51 | equivalent to @racket[(struct foo $message (a b c) #:prefab)]. 52 | 53 | The second form allows declaration of a supertype, but that supertype 54 | must be a subtype of @racket[$message]. 55 | } 56 | 57 | @defform[(define+provide-message id form ...)]{ 58 | Like @racket[define-message], with an included @racket[(provide (struct-out id))]. 59 | } 60 | 61 | @defstruct*[($show-datum $message) ([value any/c]) #:prefab]{ 62 | Represents a request to show the user the given Racket value. 63 | } 64 | 65 | @defstruct*[($show-string $message) ([message any/c]) #:prefab]{ 66 | Represents a request to show the user the given string. 67 | } 68 | 69 | @defstruct*[($regarding $message) ([subject $message?] [body $message?]) #:prefab]{ 70 | Represents a request to show one message in the context of another. 71 | } 72 | 73 | 74 | @defproc[(scope-message [m $message?] [scope (listof $message?) (get-message-scope)]) $message?]{ 75 | Returns @racket[m] if @racket[scope] is @racket[null]. 76 | 77 | Otherwise, returns @racket[(scope-message ($regarding (car scope) m) (cdr scope))]. 78 | 79 | Use to wrap a @tech{message} with @racket[$regarding], where the exact 80 | structure of the output may depend on the current 81 | @tech/reference{dynamic extent}. 82 | } 83 | 84 | 85 | @defproc[(call-in-message-scope [m $message] [proc (-> any)]) any]{ 86 | Equivalent to @racket[(call-in-message-scope* (cons m (get-message-scope)) proc)]. 87 | } 88 | 89 | 90 | @defproc[(call-in-message-scope* [ms (listof $message?)] [proc (-> any)]) any]{ 91 | Returns @racket[(proc)]. While @racket[proc] has control, 92 | @racket[(get-message-scope)] is @racket[eq?] to @racket[ms]. 93 | } 94 | 95 | 96 | @defform[(in-message-scope m body ...)]{ 97 | Expands to @racket[(call-in-message-scope m (lambda () body ...))]. 98 | } 99 | 100 | @defproc[(get-message-scope) (listof $message?)]{ 101 | Returns a list of @tech{messages} representing a dynamic scope 102 | for other messages. See @racket[call-in-message-scope]. 103 | } 104 | -------------------------------------------------------------------------------- /docs/reference/modules/notary.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket 4 | denxi/artifact 5 | denxi/integrity 6 | denxi/notary 7 | denxi/crypto 8 | denxi/signature 9 | denxi/source 10 | denxi/subprogram] 11 | "../../shared.rkt"] 12 | 13 | @title{Notaries} 14 | 15 | @defmodule[denxi/notary] 16 | 17 | @defstruct*[notary ([chf symbol?] 18 | [public-key-source (or/c #f source-variant?)] 19 | [private-key-path (or/c #f path-string?)] 20 | [private-key-password-path (or/c #f path-string?)])]{ 21 | A @deftech{notary} is an instance of @racket[notary]. Notaries attach 22 | integrity information and a private party's signature to artifacts. 23 | 24 | Unlike many other abstractions in Denxi, notaries depend on secrets to 25 | perform complete work. Those secrets must be available on the file 26 | system. 27 | } 28 | 29 | @defproc[(make-notary [#:chf chf symbol? (get-default-chf)] 30 | [#:public-key-source public-key-source (or/c #f path-string?) #f] 31 | [#:private-key-path private-key-path (or/c #f path-string?) #f] 32 | [#:private-key-password-path 33 | private-key-password-path 34 | (or/c #f path-string?) 35 | #f]) 36 | notary?]{ 37 | Returns a new @racket[notary], with contract enforcement and default 38 | values for fields. 39 | } 40 | 41 | @defthing[lazy-notary notary?]{ 42 | A @tech{notary} that only creates integrity information using 43 | @racket[(get-default-chf)]. 44 | } 45 | 46 | @defproc[(make-fraudulent-notary [chf-name symbol? (get-default-chf)]) notary?]{ 47 | A @tech{notary} that creates complete artifacts using the 48 | implementation of the named cryptographic hash functions, and the 49 | @racketmodname[denxi/signature/snake-oil] keypair. @racket[(notarize 50 | (make-fraudulent-notary chf-name) trusted-content)] values are 51 | implicitly compromised for all values of @racket[trusted-content] and 52 | @racket[chf-name]. 53 | 54 | Use only for prototyping signature verification. 55 | } 56 | 57 | @defproc[(notarize [the-notary notary?] 58 | [trusted-content (or/c artifact? source-variant?)]) 59 | (subprogram/c artifact?)]{ 60 | Returns a @tech{subprogram} that computes a new @tech{artifact} in 61 | terms of the @tech{source} accessible through 62 | @racket[trusted-content]. 63 | 64 | The output artifact's data will be in parity with the information 65 | available in @racket[the-notary]: If there is no defined CHF, then the 66 | output artifact will lack integrity and signature information. If the 67 | notary lacks a complete keypair, then the output artifact will lack 68 | signature information. The output artifact only shares the primary 69 | content @tech{source} accessible from @racket[trusted-content], and 70 | will not validate or use input integrity/signature information. 71 | 72 | @racket[trusted-content] is, as the name implies, assumed to be 73 | trusted by the caller. No safety limits will be in place when drawing 74 | bytes from its @tech{source} to compute a digest. 75 | 76 | If integrity information @racketid[I] is in the output artifact, then 77 | @racket[(integrity-chf-symbol I)] is @racket[eq?] to 78 | @racket[(notary-chf the-notary)]. @racket[(integrity-digest I)] 79 | is the digest computed using @racket[trusted-content]. 80 | 81 | If signature information @racketid[S] is in the output artifact, then 82 | @racket[(signature-public-key S)] is @racket[eq?] to 83 | @racket[(notary-public-key-source 84 | the-notary)]. @racket[(signature-body S)] is a signature computed 85 | using @racket[(integrity-digest I)]. 86 | } 87 | -------------------------------------------------------------------------------- /docs/reference/modules/output.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | denxi/output 6 | denxi/monad 7 | denxi/subprogram 8 | denxi/string 9 | @except-in[denxi/pkgdef #%module-begin]] 10 | "../../shared.rkt"] 11 | 12 | @title{Package Outputs} 13 | 14 | @defmodule[denxi/output] 15 | 16 | @defstruct*[package-output ([name string?] [steps list?] [make-subprogram (-> subprogram?)])]{ 17 | A @deftech{package output} is an instance of @racket[package-output]. 18 | Each instance represents a named subprogram that claims to follow the 19 | instructions shown in @racket[steps]. 20 | 21 | Specifically, @racket[steps] is a list of syntax objects or data that 22 | are suitable for use in @racket[mdo]. 23 | 24 | @racket[make-subprogram] returns a subprogram that creates files. The 25 | subprogram does not need to be atomic, but it should mirror the 26 | content of @racket[steps]. 27 | } 28 | 29 | @defproc[(find-package-output [name string?] [steps (listof package-output?)]) 30 | (or/c #f package-output?)]{ 31 | Like @racket[findf], except the input list must consist of 32 | @tech{package outputs}, and the search will only compare names. 33 | } 34 | 35 | 36 | @defproc[(encode-package-output [to-encode package-output?]) list?]{ 37 | @margin-note{Any output created using @racket[output] in the 38 | @racketmodname[denxi] or @racketmodname[denxi/pkgdef] languages are 39 | suitable for use with @racket[encode-package-output].} 40 | Returns a quoted @racket[output] term suitable for use in a 41 | @tech{package definition}. The term will use the elements of 42 | @racket[(package-output-steps to-encode)], which means the correctness 43 | of the returned list depends on if @racket[(package-output-steps 44 | to-encode)] and @racket[(package-output-make-subprogram to-encode)] are 45 | equivalent. 46 | } 47 | 48 | @defform[(transparent-package-output name step ...) 49 | #:contracts ([name non-empty-string?])]{ 50 | Expands to a new @racket[package-output] where the 51 | @racket[package-output-steps] and 52 | @racket[package-output-make-subprogram] fields both use the provided 53 | sequence of @racketid[step]s. 54 | 55 | Specifically, @racket[package-output-steps] is @racket[(quote (mdo 56 | step ...))] and @racket[package-output-make-subprogram] is 57 | @racket[(lambda () (mdo step ...))]. 58 | } 59 | -------------------------------------------------------------------------------- /docs/reference/modules/pkgdef/static.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | racket/path 6 | racket/pretty 7 | racket/string 8 | syntax/modread 9 | denxi/codec 10 | denxi/integrity 11 | denxi/crypto 12 | denxi/pkgdef/static 13 | denxi/racket-module 14 | denxi/string] 15 | "../../../shared.rkt"] 16 | 17 | @title{Static Operations for Package Definitions} 18 | 19 | @defmodule[denxi/pkgdef/static] 20 | 21 | @deftogether[( 22 | @defthing[PACKAGE_DEFINITION_MODULE_LANG symbol?] 23 | @defthing[PACKAGE_DEFINITION_READER_LANG symbol?] 24 | )]{ 25 | Collection paths for a module language and reader extension used to 26 | write package definitions. 27 | } 28 | 29 | @defproc[(make-package-definition-datum [#:id id symbol? 'pkgdef] [body list?]) package-definition-datum?]{ 30 | Equivalent to @racket[(make-racket-module-datum #:id id PACKAGE_DEFINITION_MODULE_LANG body)] 31 | } 32 | 33 | @defproc[(get-package-definition-body [datum package-definition-datum?]) list?]{ 34 | Returns the top-level forms of the module code in @racket[datum]. 35 | } 36 | 37 | @defthing[bare-pkgdef? flat-contract? 38 | #:value (struct/c bare-racket-module symbol? 39 | PACKAGE_DEFINITION_MODULE_LANG 40 | list?)]{ 41 | A contract that matches a @tech{bare} @tech{package definition}. 42 | } 43 | 44 | 45 | @defthing[package-definition-datum? predicate/c]{ 46 | Equivalent to @racket[(racket-module-code? PACKAGE_DEFINITION_MODULE_LANG v)]. 47 | } 48 | 49 | @defproc[(get-static-exact-package-query [pkgdef bare-pkgdef?] 50 | [defaults 51 | package-query-defaults-implementation/c 52 | default-package-query-defaults]) 53 | exact-package-query?]{ 54 | Returns an @tech{exact package query} by reading the contents of the 55 | package definition. Undefined names are given default values from 56 | @racket[defaults]. 57 | } 58 | 59 | 60 | @defproc[(get-static-abbreviated-query [pkgdef bare-pkgdef?]) package-query?]{ 61 | Returns a @tech{package query} containing the provider, package, 62 | edition, and revision number in @racket[pkgdef]. 63 | } 64 | 65 | @defproc[(get-static-simple-string [pkgdef bare-pkgdef?] [id symbol?]) any/c]{ 66 | Equivalent to @racket[(get-static-simple-value stripped id "default")]. 67 | 68 | The return value is assumed to be a string, but might not be. 69 | } 70 | 71 | @defproc[(get-static-inputs [pkgdef bare-pkgdef?]) list?]{ 72 | Returns a list of all input expressions in @racket[pkgdef]. 73 | } 74 | 75 | @defproc[(get-static-simple-value [pkgdef bare-pkgdef?] [id symbol?] [default any/c]) any/c]{ 76 | Searches the top-level code of @racket[pkgdef] for a S-expression of 77 | form @racket[(id val)]. Returns the datum in @racket[val]'s position, 78 | or @racket[default] if no such expression exists. 79 | } 80 | 81 | @defproc[(get-static-list-value [pkgdef bare-pkgdef?] [id symbol?] [default any/c]) any/c]{ 82 | Searches the top-level code of @racket[pkgdef] for a S-expression of 83 | form @racket[(id . xs)]. Returns @racket[xs], or @racket[default] if 84 | no such expression exists. 85 | } 86 | 87 | 88 | @defproc[(override-inputs [pkgdef bare-pkgdef?] [input-exprs list?]) bare-pkgdef?]{ 89 | Functionally replaces any @tech{package inputs} in @racket[pkgdef] 90 | that share a name with at least one element in @racket[input-exprs]. 91 | 92 | If multiple expressions in @racket[input-exprs] share a name, only the 93 | last occurrance will be used in the output. 94 | } 95 | 96 | @defproc[(replace-input-expression [pkgdef bare-pkgdef?] 97 | [input-name non-empty-string?] 98 | [replacement any/c]) 99 | bare-pkgdef?]{ 100 | Functionally replaces the first @racket[input] form 101 | in @racket[pkgdef] with @racket[replacement], where the input's name 102 | is @racket[input-name]. 103 | } 104 | -------------------------------------------------------------------------------- /docs/reference/modules/printer.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base 4 | racket/contract 5 | racket/fasl 6 | racket/match 7 | racket/pretty 8 | racket/serialize 9 | denxi/l10n 10 | denxi/subprogram 11 | denxi/format 12 | denxi/message 13 | denxi/port 14 | denxi/printer] 15 | @for-syntax[denxi/printer] 16 | denxi/printer 17 | "../../shared.rkt"] 18 | 19 | @title{Printer} 20 | 21 | @defmodule[denxi/printer] 22 | 23 | @racketmodname[denxi/printer] writes @tech{messages} as bytes on 24 | output ports. 25 | 26 | 27 | @defstruct*[($verbose $message) ([message $message?]) #:prefab]{ 28 | A wrapper for a message that only appears to a user if 29 | @racket[(DENXI_VERBOSE)] is @racket[#t]. 30 | } 31 | 32 | 33 | @defsetting*[DENXI_FASL_OUTPUT]{ 34 | When true, each value @racket[v] printed on STDOUT is first transformed using 35 | @racket[(s-exp->fasl (serialize v))]. 36 | } 37 | 38 | @defsetting*[DENXI_READER_FRIENDLY_OUTPUT]{ 39 | When true, each program output value @racket[v] is printed on STDOUT using 40 | @racket[pretty-write] without being translated to a human-readable message. 41 | 42 | Use this to produce @racket[(read)]able logs. If it aids read performance, 43 | combine with @racket[DENXI_FASL_OUTPUT]. 44 | } 45 | 46 | @defsetting*[DENXI_VERBOSE]{ 47 | When true, emit more detailed program output. 48 | } 49 | 50 | @defproc[(write-message-log [messages (or/c $message? subprogram-log/c)] [format-message message-formatter/c]) void?]{ 51 | Writes every message @racket[m] in @racket[messages] using 52 | @racket[(write-message m format-message (current-output-port))]. 53 | } 54 | 55 | 56 | @defproc[(write-message [m $message?] [#:newline? newline? any/c #t] [format-message message-formatter/c] [out output-port? (current-output-port)]) void?]{ 57 | Writes a @tech{message} to @racket[out] according to the values of 58 | @racket[(DENXI_READER_FRIENDLY_OUTPUT)], @racket[(DENXI_FASL_OUTPUT)], 59 | and @racket[(DENXI_VERBOSE)]. 60 | 61 | Given @racket[(and (not (DENXI_VERBOSE)) ($verbose? m))], 62 | @racket[write-message] does nothing. 63 | 64 | Otherwise, @racket[write-message] does the following: 65 | 66 | @racketblock[ 67 | (let ([to-send (if (DENXI_READER_FRIENDLY_OUTPUT) m (format-message m))]) 68 | (if (DENXI_FASL_OUTPUT) 69 | (s-exp->fasl (serialize to-send) out) 70 | (if (DENXI_READER_FRIENDLY_OUTPUT) 71 | (pretty-write #:newline? newline? to-send out) 72 | ((if newline? displayln display) to-send out))))] 73 | 74 | Note that @racket[newline?] is ignored when using fasl output. 75 | } 76 | -------------------------------------------------------------------------------- /docs/reference/modules/signature.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../../shared.rkt" 4 | @for-label[racket/base 5 | racket/contract 6 | denxi/message 7 | denxi/integrity 8 | denxi/signature 9 | denxi/signature/base 10 | denxi/source] 11 | @for-syntax[denxi/signature] 12 | denxi/signature] 13 | 14 | @title{Signature Checking} 15 | 16 | @defmodule[denxi/signature] 17 | 18 | @racketmodname[denxi/signature] uses asymmetric cryptography to verify 19 | if a digest was signed by a private key. The quality of signature 20 | verification is therefore dependent on the quality of the CHF used to 21 | create the digest. 22 | 23 | 24 | @deftogether[( 25 | @defthing[sourced-signature? flat-contract?] 26 | @defthing[well-formed-signature? flat-contract? #:value (or/c raw-signature? sourced-signature?)] 27 | @defthing[malformed-signature? flat-contract? #:value (not/c well-formed-signature?)] 28 | )]{ 29 | Duck typing contracts for @racket[signature] instances. 30 | 31 | @racket[sourced-signature?] returns @racket[#t] if at least one of the 32 | fields of the instance is a @tech{source}. This is unlike 33 | @racket[sourced-integrity?], which only checks if the digest field is 34 | a @tech{source}. 35 | } 36 | 37 | 38 | @defproc[(fetch-signature-payload [src source-variant?] [exhaust exhaust/c]) any/c]{ 39 | Like @racket[fetch], except transfer limits are capped to 40 | @racket[MAX_EXPECTED_SIGNATURE_PAYLOAD_LENGTH] and no transfer status 41 | information is reported. When the source has been successfully 42 | @tech{tapped}, the return value is a byte string representing the full 43 | content of the requested resource. 44 | 45 | In practice, the fetched bytes are expected to contain either a public 46 | key or a signature. In any case, the output is assumed to be 47 | compatible with the tool used to verify signatures. 48 | } 49 | 50 | 51 | @defproc[(lock-signature [#:public-key-budget 52 | public-key-budget 53 | budget/c 54 | MAX_EXPECTED_SIGNATURE_PAYLOAD_LENGTH] 55 | [#:signature-budget 56 | signature-budget 57 | budget/c 58 | MAX_EXPECTED_SIGNATURE_PAYLOAD_LENGTH] 59 | [siginfo well-formed-signature?] 60 | [exhaust exhaust/c]) 61 | signature?]{ 62 | Like @racket[lock-integrity] for @racket[signature]s. 63 | } 64 | 65 | @defproc[(make-snake-oil-signature [digest bytes?] 66 | [chf-name symbol? (get-default-chf)]) 67 | raw-signature?]{ 68 | Return a new signature using @racket[snake-oil-private-key]. 69 | 70 | Do not use in production code. 71 | } 72 | 73 | @defthing[MAX_EXPECTED_SIGNATURE_PAYLOAD_LENGTH budget/c]{ 74 | An estimated maximum number of bytes (chosen empirically) for a public 75 | key or signature. 76 | } 77 | 78 | @defproc[(call-with-snake-oil-cipher-trust [thunk (-> any)]) any]{ 79 | Calls @racket[thunk] in tail position. While control is in the 80 | @racket[thunk], @racket[(DENXI_TRUST_PUBLIC_KEYS)] is @racket[(list 81 | snake-oil-public-key)]. 82 | 83 | Implies @racket[call-with-snake-oil-chf-trust]. 84 | } 85 | 86 | @defsetting*[DENXI_TRUST_ANY_PUBLIC_KEY]{ 87 | @bold{Dangerous}. When true, trust any public key used to verify a signature. 88 | } 89 | 90 | @defsetting*[DENXI_TRUST_UNSIGNED]{ 91 | @bold{Dangerous}. When true, trust any input that lacks a valid signature. 92 | } 93 | 94 | @defsetting*[DENXI_TRUST_BAD_SIGNATURE]{ 95 | @bold{Dangerous}. When true, trust any input that has a signature that 96 | does not match the input's integrity information. 97 | } 98 | 99 | @defsetting[DENXI_TRUST_PUBLIC_KEYS (listof well-formed-integrity?)]{ 100 | A list of integrity information for public keys. Trusts public keys 101 | that can be used to reproduce an element of this list. 102 | } 103 | 104 | @define[pk-read-url 105 | "https://www.openssl.org/docs/man1.1.0/man3/PEM_read_bio_PrivateKey.html"] 106 | 107 | @include-section{signature/base.scrbl} 108 | @include-section{signature/ffi.scrbl} 109 | @include-section{signature/snake-oil.scrbl} 110 | -------------------------------------------------------------------------------- /docs/reference/modules/signature/base.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../../../shared.rkt" 4 | @for-label[racket 5 | denxi/message 6 | denxi/integrity 7 | denxi/signature/base 8 | denxi/signature/ffi 9 | denxi/source]] 10 | 11 | @title{Signature Checking Primitives} 12 | 13 | @defmodule[denxi/signature/base] 14 | 15 | @defstruct*[signature ([public-key any/c] [body any/c]) #:transparent]{ 16 | Represents a claim that the bytes in @racket[body] are a signature 17 | that can be verified using @racket[public-key]. 18 | } 19 | 20 | @defthing[raw-signature? flat-contract? #:value (struct/c signature bytes? bytes?)]{ 21 | A @tech/reference{flat contract} for signature claims that contain 22 | only unencoded bytes in memory. 23 | } 24 | 25 | @defthing[make-signature/c 26 | chaperone-contract? 27 | #:value (-> bytes? 28 | symbol? 29 | bytes? 30 | (or/c #f bytes?) 31 | bytes?)]{ 32 | A contract for procedures that return new signatures as unencoded 33 | bytes. 34 | 35 | Arguments 36 | @itemlist[#:style 'ordered 37 | @item{A digest as unencoded bytes} 38 | @item{A symbol representing the name of the cryptographic hash function used to create the first argument.} 39 | @item{A private key of some encoding.} 40 | @item{A password for the private key, or @racket[#f] if there is no password.} 41 | ] 42 | } 43 | 44 | @defthing[verify-signature/c 45 | chaperone-contract? 46 | #:value (-> bytes? 47 | symbol? 48 | bytes? 49 | bytes? 50 | boolean?)]{ 51 | A contract for procedures that return @racket[#t] for trusted 52 | signatures. 53 | 54 | Arguments 55 | @itemlist[#:style 'ordered 56 | @item{A digest as unencoded bytes} 57 | @item{A symbol representing the name of the cryptographic hash function used to create the first argument.} 58 | @item{A public key of some encoding.} 59 | @item{An unencoded signature} 60 | ] 61 | } 62 | 63 | 64 | @margin-note{Allowing @racket[#f] in the arguments is intentional due 65 | to the possibility of missing information.} 66 | @defproc[(check-signature [#:trust-public-key? trust-public-key? (-> input-port? any/c)] 67 | [#:trust-unsigned trust-unsigned any/c] 68 | [#:verify-signature verify-signature verify-signature/c] 69 | [#:trust-bad-digest trust-bad-digest any/c] 70 | [sig (or/c #f signature?)] 71 | [int (or/c #f integrity?)]) 72 | symbol?]{ 73 | @margin-note{@racket['skip] and @racket['skip-unsigned] are not 74 | equivalent. @racket[check-signature] only handles a lack of a 75 | signature when @racket[sig] or @racket[int] is malformed.} 76 | 77 | Returns 78 | 79 | @itemlist[ 80 | @item{ 81 | @racket['skip] if the check was skipped. 82 | } 83 | 84 | @item{ 85 | @racket['signature-verified] when trusting the public key and the signature. 86 | } 87 | 88 | @item{ 89 | @racket['signature-unverified] when trusting the public key but not the signature. 90 | } 91 | 92 | @item{ 93 | @racket['blocked-public-key] when distrusting the public key. 94 | } 95 | 96 | @item{ 97 | @racket['unsigned] if @racket[sig] or @racket[int] are missing too much information to conclude that a signature is present. 98 | } 99 | 100 | @item{ 101 | @racket['skip-unsigned] is a combination of @racket['skip] and 102 | @racket['unsigned]. @racket[int] and/or @racket[sig] are missing 103 | information, but this is considered permissable when 104 | @racket[trust-unsigned] is @racket[#t]. 105 | } 106 | 107 | ] 108 | } 109 | 110 | @defthing[signature-check-passed? predicate/c]{ 111 | Returns @racket[#t] if the argument is in the range of 112 | @racket[check-signature], and you can interpret it as permission to 113 | proceed in a larger procedure. 114 | } 115 | 116 | 117 | @defthing[current-verify-signature 118 | (parameter/c verify-signature/c)]{ 119 | A parameter holding the current way to verify a signature. 120 | 121 | If @racket[(signature-ffi-available?!)] is @racket[#t], the default 122 | value is @racket[signature-ffi-verify-signature]. Otherwise, 123 | @racket[(const #f)]. 124 | } 125 | 126 | 127 | @defthing[current-make-signature 128 | (parameter/c make-signature/c)]{ 129 | A parameter holding the current way to make a signature. The default 130 | value returns an empty byte string. 131 | } 132 | -------------------------------------------------------------------------------- /docs/reference/modules/signature/ffi.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require["../../../shared.rkt" 4 | @for-label[racket 5 | denxi/message 6 | denxi/integrity 7 | denxi/signature 8 | denxi/signature/base 9 | denxi/signature/ffi 10 | denxi/source]] 11 | 12 | @title{Signature Checking FFI} 13 | 14 | @defmodule[denxi/signature/ffi] 15 | 16 | @racketmodname[denxi/signature/ffi] is a private module that defines 17 | FFI bindings for a bundled library. 18 | 19 | 20 | @defproc[(signature-ffi-available?!) boolean?]{ 21 | Returns @racket[#t] if the FFI dynamically linked against the bundled 22 | foreign library for the purposes of integrity checking operations. 23 | } 24 | 25 | 26 | @defthing[signature-ffi-make-signature! make-signature/c]{ 27 | Returns bytes for a new signature. 28 | } 29 | 30 | 31 | @defthing[signature-ffi-verify-signature! verify-signature/c]{ 32 | Returns @racket[#t] if a signature was verified by a public key. 33 | } 34 | 35 | 36 | @defproc[(signature-ffi-get-find-signature-size!) any/c]{ 37 | Returns a foreign function for computing the expected size of 38 | a signature, or @racket[#f] if the function could not load. 39 | } 40 | 41 | 42 | @defproc[(signature-ffi-get-make-md-context!) any/c]{ 43 | Returns a foreign function for allocating a message digest context, or 44 | @racket[#f] if the function could not load. 45 | } 46 | 47 | 48 | @defproc[(signature-ffi-get-start-signature!) any/c]{ 49 | Returns a foreign function for starting use of a cipher algorithm, or 50 | @racket[#f] if the function could not load. 51 | 52 | Each call must be paired with a call to the function returned from 53 | @racket[(signature-ffi-get-end-signature!)]. 54 | } 55 | 56 | 57 | @defproc[(signature-ffi-get-end-signature!) any/c]{ 58 | Returns a foreign function for concluding use of a cipher algorithm, 59 | or @racket[#f] if the function could not load. 60 | } 61 | 62 | 63 | @defproc[(signature-ffi-get-verify-signature!) any/c]{ 64 | Returns a foreign function for verifying a signature using 65 | library-specific data types, or @racket[#f] if the function could not 66 | load. 67 | } 68 | 69 | 70 | @section{Signature Foreign Functions} 71 | 72 | To be included. 73 | 74 | -------------------------------------------------------------------------------- /docs/reference/modules/signature/snake-oil.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base denxi/signature/snake-oil]] 4 | 5 | @title{Signature Prototyping} 6 | 7 | @defmodule[denxi/signature/snake-oil] 8 | 9 | @deftogether[( 10 | @defthing[snake-oil-public-key bytes?] 11 | @defthing[snake-oil-private-key bytes?] 12 | @defthing[snake-oil-private-key-password bytes?] 13 | )]{ 14 | An intentionally-leaked RSA keypair, with a password for the private 15 | key. The private key is encrypted using AES-128 (CBC). 16 | 17 | Each key is PEM-encoded. 18 | 19 | Use @bold{only} for prototyping signature creation and 20 | verification. Distrust for all other purposes. 21 | } 22 | -------------------------------------------------------------------------------- /docs/reference/modules/string.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[denxi/string racket/base racket/contract]] 4 | 5 | @title{Strings} 6 | 7 | @defmodule[denxi/string] 8 | 9 | @racketmodname[denxi/string] extends and reprovides 10 | @racketmodname[racket/string]. 11 | 12 | This module defines useful regular expressions and string operations 13 | needed by other modules. 14 | 15 | @defthing[DEFAULT_STRING non-empty-string? #:value "default"]{ 16 | The conventional default of the non empty strings. 17 | } 18 | 19 | @defproc[(whole/pattstr [s string?]) string?]{ 20 | Returns @racket[(string-append "^" s "$")]. 21 | } 22 | 23 | @defproc[(group/pattstr [s string?]) string?]{ 24 | Returns @racket[(string-append "(" s ")")]. 25 | } 26 | 27 | @defproc[(or/pattstr [opt string?] ...) string?]{ 28 | Returns @racket[(string-append "(?:" (string-join opts "|") ")")]. 29 | } 30 | 31 | @defproc[(make-extension-pattern-string [exts string?] ...) string?]{ 32 | Returns a pattern string suitable for @racket[pregexp] that matches 33 | a dot, followed by one of the given @racket[exts] at the end of the subject. 34 | } 35 | 36 | @defthing[unix-reserved-character-pattern-string string?]{ 37 | A pattern string suitable for @racket[pregexp] that 38 | matches @litchar|{/}| or null characters. 39 | } 40 | 41 | @deftogether[( 42 | @defthing[windows-reserved-character-pattern-string string?] 43 | @defthing[windows-reserved-name-pattern-string string?] 44 | )]{ 45 | These are pattern strings suitable for @racket[pregexp]. 46 | They track @hyperlink["https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file"]{Windows reserved file names}. 47 | 48 | @racket[windows-reserved-character-pattern-string] matches any character 49 | reserved by Windows for file 50 | names. @racket[windows-reserved-name-pattern-string] matches any character 51 | sequence that looks like a reserved Windows file name. 52 | } 53 | 54 | 55 | @defthing[maybe-spaces-pattern-string string?]{ 56 | A pattern string suitable for @racket[pregexp] 57 | that matches zero or more whitespace characters. 58 | } 59 | 60 | @defthing[non-empty-bytes? predicate/c]{ 61 | Returns @racket[#t] when applied to a non-empty byte string. 62 | } 63 | 64 | @defproc[(make-rx-matcher [pattern-string string?] [#:whole whole? any/c #t]) (-> string? (or/c #f list?))]{ 65 | Returns a procedure. That procedure returns @racket[(and (string? s) 66 | (regexp-match (pregexp (if whole? (whole/pattstr p) p))) s)] when applied to a 67 | string @racket[s]. 68 | } 69 | 70 | @defthing[file-name-string? predicate/c]{ 71 | Returns @racket[#t] if the value is a string, and that string is useable as a 72 | cross-platform file name. 73 | } 74 | 75 | @defproc[(get-shortest-string [strings (listof string?)]) string?]{ 76 | Returns the shortest element of @racket[strings]. 77 | } 78 | 79 | @defproc[(string->value [s string?]) any/c]{ 80 | @racket[read]s a Racket value from the string, treating the string as 81 | untrusted input. 82 | } 83 | 84 | @defidform[#:kind "syntax-class" non-empty-string]{ 85 | A syntax class for matching non-empty strings in macros. 86 | } 87 | -------------------------------------------------------------------------------- /docs/reference/modules/url.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base racket/contract denxi/url] 4 | "../../shared.rkt"] 5 | 6 | @title{URLs} 7 | 8 | @defmodule[denxi/url] 9 | 10 | @racketmodname[denxi/url] reprovides @racketmodname[net/url], along 11 | with the following bindings. 12 | 13 | @defproc[(url-variant? [s any/c]) boolean?]{ 14 | Returns @racket[(or (url? s) (url-string? s))]. 15 | } 16 | 17 | 18 | @defproc[(url-string? [s any/c]) boolean?]{ 19 | Returns @racket[#t] if @racket[s] is a string that can be parsed as an 20 | URL using @racket[string->url]. 21 | } 22 | 23 | @defproc[(coerce-url [s url-variant?]) url?]{ 24 | Returns an instance of @racket[url] in terms of a variant type. 25 | } 26 | 27 | @defproc[(coerce-url-string [s url-variant?]) url-string?]{ 28 | Returns a @racket[url-string?] in terms of a variant type. 29 | } 30 | 31 | @defidform[#:kind "syntax class" url-string]{ 32 | A syntax class for @racket[url-string?] string literals. 33 | } 34 | -------------------------------------------------------------------------------- /docs/reference/modules/version.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @require[@for-label[racket/base racket/contract denxi/version] 4 | "../../shared.rkt"] 5 | 6 | @title[#:tag "versions"]{Versions} 7 | 8 | @defmodule[denxi/version] 9 | 10 | A @tech{package definition} has a @tech{version}. 11 | 12 | A @deftech{version} is a combination of an @tech{edition} and a 13 | @tech{revision}. 14 | 15 | An @deftech{edition} is a non-empty string that names a target 16 | audience. 17 | 18 | An @tech{edition} has at least one @tech{revision}. 19 | 20 | A @deftech{revision} is an implementation of an @tech{edition}. We 21 | select a revision using a @tech{revision number} or a @tech{revision 22 | name}. 23 | 24 | A @deftech{revision number} is an exact non-negative integer that 25 | starts at @racket[0]. 26 | 27 | A @deftech{revision name} is a string alias for a @tech{revision 28 | number} that contains at least one non-digit. A revision name may 29 | repeat across but not within @tech{editions}. 30 | 31 | 32 | @defthing[revision-number? predicate/c]{ 33 | Returns @racket[#t] if the input value is useable as a @tech{revision number}. 34 | } 35 | 36 | @defthing[revision-number-string? predicate/c]{ 37 | Returns @racket[#t] if the input value is a string that, when converted to a 38 | number, is useable as a @tech{revision number}. 39 | } 40 | 41 | @defproc[(revision-number-variant? [v any/c]) boolean?]{ 42 | Equivalent to @racket[(or/c revision-number? revision-number-string?)]. 43 | } 44 | 45 | @defproc[(coerce-revision-number [v revision-number-variant?]) revision-number?]{ 46 | Returns a revision number in terms of a variant type. 47 | } 48 | 49 | @defproc[(coerce-revision-number-string [v revision-number-variant?]) revision-number-string?]{ 50 | Returns a revision number string in terms of a variant type. 51 | } 52 | 53 | 54 | @defproc[(make-minimum-revision-number [boundary revision-number?] 55 | [#:exclusive? exclusive? any/c]) 56 | revision-number?]{ 57 | Returns a revision number, interpreting another as a minimum boundary 58 | for an integer interval. If @racket[exclusive?] is @racket[#f], then 59 | the return value is simply @racket[boundary]. Otherwise, it's 60 | @racket[(add1 boundary)]. 61 | } 62 | 63 | @defproc[(make-maximum-revision-number [boundary revision-number?] 64 | [#:exclusive? exclusive? any/c]) 65 | revision-number?]{ 66 | Returns a revision number, interpreting another as a maximum boundary 67 | for an integer interval. If @racket[exclusive?] is @racket[#f], then 68 | the return value is simply @racket[boundary]. Otherwise, it's 69 | @racket[(max 0 (sub1 boundary))]. 70 | } 71 | 72 | @defproc[(find-latest-available-revision-number [available? (-> revision-number? any/c)] 73 | [lo revision-number?] 74 | [hi revision-number?]) 75 | (or/c #f revision-number?)]{ 76 | Finds the largest element of the inclusive interval @litchar|{{lo 77 | .. hi}}| where @racket[available?] returns a true value. Returns 78 | @racket[#f] if no such element exists. 79 | } 80 | 81 | @defproc[(find-oldest-available-revision-number [available? (-> revision-number? any/c)] 82 | [lo revision-number?] 83 | [hi revision-number?]) 84 | (or/c #f revision-number?)]{ 85 | Finds the smallest element of the inclusive interval @litchar|{{lo 86 | .. hi}}| where @racket[available?] returns a true value. Returns 87 | @racket[#f] if no such element exists. 88 | } 89 | -------------------------------------------------------------------------------- /docs/shared.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | (require scribble/manual 6 | syntax/strip-context 7 | racket/format 8 | (for-syntax racket 9 | syntax/stx 10 | syntax/strip-context 11 | denxi/setting 12 | denxi/cli-flag 13 | denxi/package) 14 | denxi/setting 15 | denxi/cli-flag) 16 | 17 | 18 | (define (visible-hyperlink s) 19 | (hyperlink s s)) 20 | 21 | (define denxi-index 22 | '(lib "denxi/docs/index/denxi-index.scrbl")) 23 | 24 | (define denxi-reference 25 | '(lib "denxi/docs/reference/denxi-reference.scrbl")) 26 | 27 | (define denxi-guide 28 | '(lib "denxi/docs/guide/denxi-guide.scrbl")) 29 | 30 | (define denxi-white-paper 31 | '(lib "denxi/docs/white-paper/denxi-white-paper.scrbl")) 32 | 33 | (define foreign 34 | '(lib "scribblings/foreign/foreign.scrbl")) 35 | 36 | (define-for-syntax (reformat-syntax stx v) 37 | (replace-context stx 38 | (read-syntax #f (open-input-string (with-output-to-string (λ () (pretty-write v))))))) 39 | 40 | (define-syntax (by-slg stx) 41 | (syntax-case stx () 42 | [_ 43 | #'(para "Sage L. Gerard" 44 | (tt " (base64-decode #\"c2FnZUBzYWdlZ2VyYXJkLmNvbQ\")"))])) 45 | 46 | (define (tech/reference tag) 47 | (tech #:doc '(lib "scribblings/reference/reference.scrbl") tag)) 48 | 49 | (define (tech/denxi-guide tag) 50 | (tech #:doc denxi-guide tag)) 51 | 52 | (define (tech/denxi-reference tag) 53 | (tech #:doc denxi-reference tag)) 54 | 55 | (define (tech/foreign tag) 56 | (tech #:doc foreign tag)) 57 | 58 | (define-for-syntax (infer-contract-expr stx s) 59 | (define proc (setting-valid? s)) 60 | (define formatted (~v proc)) 61 | (reformat-syntax stx 62 | (if (string-prefix? formatted "#<") 63 | (object-name proc) 64 | (read (open-input-string formatted))))) 65 | 66 | (define-syntax (defsetting stx) 67 | (syntax-case stx () 68 | [(_ s cnt pre-content ...) 69 | #`(let ([cf (find-cli-flag s)]) 70 | (defthing 71 | #:kind "setting" 72 | s cnt 73 | #:value #,(datum->syntax stx (eval #'(s)) stx) 74 | (para "CLI Flags: " 75 | (if cf 76 | (litchar (format-cli-flags cf)) 77 | "N/A")) 78 | pre-content ...))])) 79 | 80 | (define-syntax (defsetting* stx) 81 | (syntax-case stx () 82 | [(_ s pre-content ...) 83 | #`(defsetting s #,(infer-contract-expr stx (eval #'s)) pre-content ...)])) 84 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | var/ 2 | tmp/ 3 | default 4 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Denxi Examples 2 | 3 | If you've finished the guide, then you've been sent here to 4 | practice. You'll see what launchers and package definitions can 5 | do. Every now and then an example will ask you to look up something 6 | with `raco docs`. Do so. It will give you experience with the 7 | reference material. Once you can work with the reference, you will 8 | know how to write your own examples. :) 9 | 10 | The examples start by asking you to execute toy code in your shell. 11 | As you progress, the examples will not spend as much time holding your 12 | hand. You will eventually encounter exercises that ask you to work 13 | your way through real-world problems. 14 | 15 | 16 | ## After the Guide 17 | 1. [From Guide](./from-guide): The example from the guide, repeated 18 | for convenience and for comparison to later 19 | examples. (@zyrolasting) 20 | 1. [Workspaces](./workspaces): How Denxi stores state. (@zyrolasting) 21 | 1. [Input and Output Selection](./input-output-selection): Package 22 | definitions can have any number of inputs, but outputs decide what 23 | make it to disk. (@zyrolasting) 24 | 1. [Integrity Checking](./integrity-checking): Verify that content is 25 | correct. (@zyrolasting) 26 | 1. [Signature Checking](./signature-checking): Verify that content 27 | came from someone you trust. (@zyrolasting) 28 | 1. [Versioning](./versioning): Add version information to package 29 | definitions. (@zyrolasting) 30 | 1. [Output Conflicts](./output-conflicts): Deal with conflicts in 31 | installed outputs. (@zyrolasting) 32 | 1. [Input Proliferation](./input-proliferation): Dealing with uncached 33 | inputs. (@zyrolasting) 34 | 35 | 36 | ## Package Definitions 37 | 1. [Input Overriding](./input-overriding): Substitute inputs at 38 | runtime. (@zyrolasting) 39 | 1. [Abstract Inputs](./abstract-inputs): How to work with inputs that 40 | only have names. (@zyrolasting) 41 | 1. [Abstract Outputs](./abstract-outputs): How to work with outputs 42 | that only have names. (@zyrolasting) 43 | 1. [Artifact Deterministim](./determinism): How to guarentee that an 44 | artifact is always useable. (@zyrolasting) 45 | 1. [Allow Racket Versions](./allow-racket-versions): Ask Denxi to 46 | process a definition depending on the current Racket 47 | version. (@zyrolasting) 48 | 1. [Generated Racket Bindings](./generated-racket-bindings): Fix a 49 | problem with non-`eq?` generated bindings that plagues Racket 50 | programs distributed on PLaneT, and sometimes `raco pkg`. (@zyrolasting) 51 | 1. [Self-hosted Denxi](./self-hosting): Install Racket with it's own 52 | copy of Denxi. (@zyrolasting) 53 | 54 | 55 | ## Launchers 56 | 1. [The `fetch` Command](./fetch-command): Using the default `fetch` 57 | command. (@zyrolasting) 58 | 1. [Maximum Trust](./maximum-trust): What's the _worst_ way to use 59 | Denxi? (@zyrolasting) 60 | 1. [Cryptography Backends](./cryptography-backends): Selecting a back 61 | end for cryptographic operations. (@zyrolasting) 62 | 1. [Racket Installation Manager](./racket-installation-manager): 63 | Install and manage multiple versions of Racket. (@zyrolasting) 64 | 1. [Extending `ln`](./extend-ln): 65 | Create `ln` commands that get to the point of dependency management. (@zyrolasting) 66 | 1. [Creating Digest and Signature Files](./make-verification-files): 67 | Use a cryptographic backend to create digests and signatures (@zyrolasting) 68 | -------------------------------------------------------------------------------- /examples/abstract-inputs/README.md: -------------------------------------------------------------------------------- 1 | A package definition can define _abstract inputs_, which only have a 2 | name. They _must_ be overridden before the definition is useable. 3 | Try overriding the input in this example's package definition. 4 | -------------------------------------------------------------------------------- /examples/abstract-inputs/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | ; "Abstract inputs" are inputs without names. They depend on a 4 | ; launcher for elaboration. 5 | (input "data") 6 | (output "default" (keep-input "data")) 7 | -------------------------------------------------------------------------------- /examples/abstract-inputs/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | (current-chfs (list snake-oil-chf)) 3 | (DENXI_TRUST_BAD_DIGEST #t) 4 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 5 | (module+ main (launch-denxi!)) 6 | -------------------------------------------------------------------------------- /examples/abstract-outputs/README.md: -------------------------------------------------------------------------------- 1 | Blank output terms like `(output "default")` are only really good for 2 | inducing cache hits if you lose access to a link. 3 | 4 | racket launcher.rkt do +a install-first.rkt 5 | rm default 6 | racket launcher.rkt do +a install-second.rkt 7 | 8 | You can also induce the collision in one transaction, without deleting 9 | the link. 10 | 11 | racket launcher.rkt do \ 12 | +a install-first.rkt \ 13 | +d cached install-second.rkt 14 | -------------------------------------------------------------------------------- /examples/abstract-outputs/install-first.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | (input "data" (artifact (byte-source #"_\n") #f #f)) 3 | (output "default" (keep-input "data")) 4 | -------------------------------------------------------------------------------- /examples/abstract-outputs/install-second.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | ; Abstract outputs create nothing, so are only really useful for 4 | ; inducing cache hits when you know a workspace has data available. 5 | (output "default") 6 | -------------------------------------------------------------------------------- /examples/abstract-outputs/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (current-chfs (list snake-oil-chf)) 4 | (DENXI_TRUST_BAD_DIGEST #t) 5 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 6 | 7 | (module+ main 8 | (launch-denxi!)) 9 | 10 | (module+ test 11 | (require (submod denxi/cli test)) 12 | ; Don't bother verifying the second, because the database would not 13 | ; exist for it in a new workspace. 14 | (define-runtime-path install-first.rkt "install-first.rkt") 15 | (functional-test/install-all install-first.rkt)) 16 | -------------------------------------------------------------------------------- /examples/allow-racket-versions/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | ; `racket-versions` defines accepted Racket runtime versions when 4 | ; using this definition. 5 | ; 6 | ; To be clear: The restriction applies to the processing of the 7 | ; package definition, not what it creates. You can try to have the 8 | ; former mean the latter if you are shipping Racket programs, but 9 | ; values lower than what Denxi supports will not even make it to the 10 | ; check. :) 11 | 12 | 13 | ; Allow any version (no bounds) 14 | ; (racket-versions "*") 15 | ; (racket-versions ["*" "*"]) 16 | 17 | ; Racket <=5.0 (boundless on left side of interval) 18 | ; (racket-versions ["*" "5.0"]) 19 | 20 | ; Racket >=5.0 (right side) 21 | ; (racket-versions ["5.0" "*"]) 22 | 23 | ; Exactly 6.7 (interval contains exactly one element) 24 | ; (racket-versions "6.7") 25 | ; (racket-versions ["6.7" "6.7"]) 26 | 27 | ; (>=5.0 AND <=5.5) OR 5.8 28 | ; (racket-versions ["5.0" "5.5"] "5.8"]) 29 | 30 | ;;;;; 31 | 32 | ; Run `racket -e '(version)'` to see your version, then try installing 33 | ; the definition with different values of this. 34 | (racket-versions "7.9" "7.4" ("7.6" "7.7")) 35 | 36 | (input "data" (artifact #"_" #f #f)) 37 | (output "default" (keep-input "data")) 38 | -------------------------------------------------------------------------------- /examples/allow-racket-versions/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (module+ main (launch-denxi!)) 4 | 5 | ; Defeat the output cache to prevent conflicts. 6 | ; Helps you iterate in this example. 7 | (current-package-editor sxs) 8 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 9 | (current-chfs (list snake-oil-chf)) 10 | (DENXI_TRUST_BAD_DIGEST #t) 11 | 12 | (module+ test 13 | (require (submod denxi/cli test)) 14 | (define-runtime-path defn.rkt "defn.rkt") 15 | (parameterize ([current-package-editor values]) 16 | (DENXI_ALLOW_UNSUPPORTED_RACKET 17 | #t (λ _ (functional-test/install-all defn.rkt))))) 18 | -------------------------------------------------------------------------------- /examples/cryptography-backends/README.md: -------------------------------------------------------------------------------- 1 | Denxi comes bundled with an OpenSSL derivative so that everyone who 2 | installs it has access to the same CHF and cipher implementations. 3 | 4 | In the event the library fails to load, of if you simply do not trust 5 | it, you can define your own implementations for cryptographic 6 | operations. 7 | 8 | `openssl-subprocess-backend.rkt` is a launcher that ignores the 9 | bundled library in favor of the host OpenSSL binary. 10 | -------------------------------------------------------------------------------- /examples/determinism/README.md: -------------------------------------------------------------------------------- 1 | First start `server.rkt` in the background. For any HTTP request, the 2 | server has a 1 in 5 chance of responding with `200 OK`. 3 | 4 | Keep trying to install this definition until it works. Then, pipe the 5 | content of the installed file to `lock.rkt`'s standard input. This 6 | will print a locked artifact datum with (demo) integrity and signature 7 | information. 8 | 9 | The generated artifact is useful for lock files and reproducible 10 | builds. 11 | 12 | The output artifact has integrity and signature information. As an 13 | added exercise, edit the launcher's configuration to leverage that. 14 | -------------------------------------------------------------------------------- /examples/determinism/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | (input "maybe" (artifact (http-source "http://localhost:9147") #f #f)) 3 | (output "default" (keep-input "maybe")) 4 | -------------------------------------------------------------------------------- /examples/determinism/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (current-chfs (list snake-oil-chf)) 4 | (DENXI_FETCH_TOTAL_SIZE_MB +inf.0) 5 | (DENXI_TRUST_BAD_DIGEST #t) 6 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 7 | 8 | (module+ main 9 | (launch-denxi!)) 10 | -------------------------------------------------------------------------------- /examples/determinism/lock.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | ; Read content from STDIN and generate a complete artifact. 4 | (module+ main 5 | (require denxi/artifact 6 | denxi/integrity 7 | denxi/signature 8 | denxi/subprogram 9 | denxi/notary) 10 | 11 | (current-chfs (list snake-oil-chf)) 12 | 13 | (match-define (artifact content (integrity chf dgst) (signature pk sig)) 14 | (get-subprogram-value 15 | (notarize (make-fraudulent-notary (chf-canonical-name snake-oil-chf)) 16 | (lock-artifact (artifact (port->bytes (current-input-port)) #f #f))))) 17 | 18 | (pretty-write #:newline? #t 19 | `(artifact ,content 20 | (integrity ,chf ,dgst) 21 | (signature ,pk ,sig)))) 22 | -------------------------------------------------------------------------------- /examples/determinism/server.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require web-server/web-server 4 | web-server/http 5 | web-server/dispatchers/dispatch-lift) 6 | 7 | (module+ main 8 | (define stop 9 | (serve #:port 9147 10 | #:dispatch 11 | (make 12 | (λ _ 13 | (if (zero? (random 0 5)) 14 | (response/output (λ (o) (write-bytes #"abc" o))) 15 | (response/empty #:code 500)))))) 16 | (with-handlers ([values (λ _ (stop))]) 17 | (sync/enable-break never-evt))) 18 | -------------------------------------------------------------------------------- /examples/extend-ln/README.md: -------------------------------------------------------------------------------- 1 | This launcher behaves like `ln -s` in terms of HTTPS URLs. It 2 | bypasses Denxi's package management subsystem entirely, and issues 3 | links to individual data files using `denxi/state`. 4 | 5 | Useful for data that you intend to verify yourself. Unsafe for 6 | security-critical automations. 7 | 8 | e.g. `./lnx https://www.iana.org/assignments/media-types/media-types.txt` 9 | 10 | **Exercise**: Add integrity checking support. Hint: `raco docs current-chfs` 11 | 12 | **Exercise**: Add signature checking support. 13 | 14 | If you add signature checking, the launcher will only use data you 15 | trust assuming that you trust all system binaries and any relevant 16 | public keys. 17 | -------------------------------------------------------------------------------- /examples/extend-ln/lnx: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env racket 2 | #lang denxi/launcher 3 | 4 | (require racket/match net/url) 5 | 6 | (module+ main 7 | (void (DENXI_TRUST_BAD_DIGEST #t 8 | (λ () 9 | (parameterize ([current-shovel shovel] 10 | [current-chfs (list snake-oil-chf)]) 11 | (interpret-command-line (current-command-line-arguments))))))) 12 | 13 | (define (shovel url-string) 14 | (subprogram-unit (artifact (http-source url-string) #f #f))) 15 | 16 | (define (interpret-command-line args) 17 | (match args 18 | [(vector) 19 | (raise-user-error "usage: lnx https://example.com/path/to/file /path/to/link")] 20 | [(vector url-string) 21 | (interpret-command-line (vector url-string (file-name-from-url-string url-string)))] 22 | [(vector url-string link-name) 23 | (match (parse-url url-string) 24 | [(url "https" _ _ _ _ _ _ _) 25 | (make-addressable-link 26 | (run+print-subprogram 27 | (mdo arti := (find-artifact url-string) 28 | (fetch-artifact url-string arti))) 29 | link-name)] 30 | [_ (raise-user-error "URL must use https, and include a path.")])])) 31 | 32 | (define (file-name-from-url-string url-string) 33 | (match (parse-url url-string) 34 | [(url _ _ _ _ _ (list _ ... (path/param file-name _)) _ _) 35 | file-name] 36 | [_ DEFAULT_STRING])) 37 | 38 | (define (parse-url url-string) 39 | (with-handlers ([(λ (e) (not (exn:break? e))) 40 | (λ _ (string->url ""))]) 41 | (string->url url-string))) 42 | -------------------------------------------------------------------------------- /examples/fetch-command/README.md: -------------------------------------------------------------------------------- 1 | The fetch command uses Denxi's content fulfillment features. Give it 2 | a source as you've seen it in package definitions up until now. 3 | 4 | ``` 5 | denxi fetch '(text-source "Hello, world!")' 6 | ``` 7 | 8 | Data transfer safety limits are active. Downloads like this one may 9 | fail if estimated size of the payload is too high, or not known (which 10 | Denxi takes to mean "unlimited"). 11 | 12 | ``` 13 | source='(http-source "https://racket-lang.org/")' 14 | denxi fetch "$source" # may fail 15 | denxi fetch -m '+inf.0' "$source" 16 | ``` 17 | 18 | Warning: Data is dumped directly to standard output. To avoid messing 19 | up your terminal emulator, pipe it somewhere else. 20 | 21 | ``` 22 | denxi fetch '(byte-source "#\1\2\3")' >data 23 | ``` 24 | 25 | You can use this command to download data you see in package 26 | definitions, but it will not verify the output data. 27 | -------------------------------------------------------------------------------- /examples/fetch-command/download-racket-8.1.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | url='https://mirror.racket-lang.org/installers/8.1/racket-8.1-x86_64-linux-cs.sh' 4 | source="(http-source \"$url\")" 5 | >racket-8.1-x86_64-linux-cs.sh denxi fetch -m 300 "$source" 6 | -------------------------------------------------------------------------------- /examples/from-guide/definition.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | ; Here's the definition from the guide. Let's dig a little deeper. 4 | 5 | 6 | ; Here's the sole input. Its (artifact) has three values: 7 | ; 8 | ; - A source for content 9 | ; - Integrity information to make sure the content is correct 10 | ; - Signature information to make sure the content came from a trusted party. 11 | ; 12 | ; Here we only have the content, expressed as inline text. This makes 13 | ; it easy to trust for a demo. 14 | ; 15 | ; Run `raco docs denxi/artifact` for more information. 16 | 17 | (input "hello.txt" 18 | (artifact (text-source "Hello, world!") #f #f)) 19 | 20 | 21 | ; Here's the sole output. 22 | ; 23 | ; The "default" name is special in that you do not have to specify it 24 | ; in an installation using `do +a` or `do ++install-abbreviated`. 25 | ; 26 | ; (keep-input) looks up a defined input and creates a link on the file 27 | ; system pointing to the input's data. 28 | ; 29 | ; Keep leaning on `raco docs`, because I won't always repeat details 30 | ; from the reference. 31 | 32 | (output "default" 33 | (keep-input "hello.txt")) 34 | -------------------------------------------------------------------------------- /examples/from-guide/my-denxi.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | ; Don't use these settings in production. 4 | (current-chfs (list snake-oil-chf)) 5 | (DENXI_TRUST_BAD_DIGEST #t) 6 | 7 | (module+ main 8 | (launch-denxi!)) 9 | 10 | ; This wasn't in the guide. It's a functional test. It does what you 11 | ; would do at the command line to help us verify examples. 12 | (module+ test 13 | (require (submod denxi/cli test)) 14 | (define-runtime-path definition.rkt "definition.rkt") 15 | (functional-test/install-all definition.rkt)) 16 | -------------------------------------------------------------------------------- /examples/generated-racket-bindings/README.md: -------------------------------------------------------------------------------- 1 | Racket programs that fetch dependencies struggle with generated 2 | bindings, because their values are never `eq?` when you expect them to 3 | be. 4 | 5 | `program.rkt` imports modules with the same functionality, but Racket 6 | sees them as providing different bindings. We can use Denxi to control 7 | the bindings Racket sees without changing `program.rkt`. 8 | 9 | 1. Run `racket launch.rkt do +a defn.rkt` in this directory. You should see a symlink appear. 10 | 2. Run `racket program.rkt`. You should see it print `different`, indicating the bindings are different. 11 | 12 | 13 | # Method: Input Overriding 14 | 15 | Here we use an input override to force the `"v2.rkt"` input to use the 16 | same source as the `"v1.rkt"` input. 17 | 18 | ``` 19 | override='(input "v2.rkt" (artifact (file-source (from-file "sources/v1.rkt")) #f #f))' 20 | match_everything='#rx""' 21 | racket launch.rkt do +a defn.rkt +o "$match_everything" "$override" 22 | ``` 23 | 24 | After performing the above setup, run `racket program.rkt` again. It 25 | will print `same`. 26 | 27 | 28 | # Method: Duplicate Inputs 29 | 30 | Denxi wil not duplicate inputs, so you can leverage its cache such 31 | that the same files of two different names result in links pointing to 32 | the same Racket module file. 33 | 34 | ``` 35 | cp sources/v1.rkt sources/v2.rkt 36 | racket launch.rkt do +a defn.rkt` 37 | ``` 38 | 39 | Run `racket program.rkt` again. It will print `same`. 40 | -------------------------------------------------------------------------------- /examples/generated-racket-bindings/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | (output "default" 4 | (keep-standalone-racket-module "v1.rkt") 5 | (keep-standalone-racket-module "v2.rkt") 6 | (keep-standalone-racket-module "symlinked.rkt")) 7 | 8 | (input "v1.rkt" (artifact (file-source (from-file "sources/v1.rkt")) #f #f)) 9 | (input "v2.rkt" (artifact (file-source (from-file "sources/v2.rkt")) #f #f)) 10 | (input "symlinked.rkt" (artifact (file-source (from-file "sources/symlinked.rkt")) #f #f)) 11 | -------------------------------------------------------------------------------- /examples/generated-racket-bindings/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | (define test-omit-paths '("program.rkt")) 3 | (define compile-omit-paths '("program.rkt")) 4 | -------------------------------------------------------------------------------- /examples/generated-racket-bindings/launch.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (DENXI_TRUST_BAD_DIGEST #t) 4 | (DENXI_TRUST_HOST_EXECUTABLES '("raco")) 5 | 6 | (module+ main (launch-denxi!)) 7 | -------------------------------------------------------------------------------- /examples/generated-racket-bindings/program.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require "output/symlinked.rkt" 4 | (only-in (symlinked "example01-output/v1.rkt") my-struct?) 5 | (only-in (symlinked "example01-output/v2.rkt") my-struct)) 6 | 7 | (displayln (if (my-struct? (my-struct)) 8 | 'same 9 | 'different)) 10 | -------------------------------------------------------------------------------- /examples/generated-racket-bindings/sources/symlinked.rkt: -------------------------------------------------------------------------------- 1 | ; Resolves symlinks at compile time. 2 | #lang racket/base 3 | 4 | (provide symlinked) 5 | 6 | (require (for-syntax racket/base 7 | racket/path 8 | racket/require-transform 9 | syntax/location)) 10 | 11 | (define-syntax symlinked 12 | (make-require-transformer 13 | (lambda (stx) 14 | (syntax-case stx () 15 | [(_ path-string) 16 | (expand-import 17 | (datum->syntax stx 18 | `(file 19 | ,(path->string (normalize-path (syntax-e #'path-string) 20 | (or (syntax-source-directory stx) 21 | (current-directory)))))))])))) 22 | -------------------------------------------------------------------------------- /examples/generated-racket-bindings/sources/v1.rkt: -------------------------------------------------------------------------------- 1 | ; v1 2 | #lang racket/base 3 | (provide (struct-out my-struct)) 4 | (struct my-struct ()) 5 | -------------------------------------------------------------------------------- /examples/generated-racket-bindings/sources/v2.rkt: -------------------------------------------------------------------------------- 1 | ; v2 2 | #lang racket/base 3 | (provide (struct-out my-struct)) 4 | (struct my-struct ()) 5 | -------------------------------------------------------------------------------- /examples/input-checking/README.md: -------------------------------------------------------------------------------- 1 | This example walks you through how to affirm trust in specifics 2 | starting from a zero trust configuration. 3 | 4 | 1. Run `racket launch.rkt do +a defn.rkt`. It will fail because you didn't consent to use the given cryptographic hash function. It will tell you the name (e.g. `sha384`). 5 | 2. Open `launch.rkt` and add the function name from Step 1. A comment in that file will tell you where to put it. 6 | 3. Run `racket launch.rkt do +a defn.rkt` again. It will fail because you didn't say you trusted the public key meant for this tutorial. 7 | 4. Copy the `(integrity ...)` expression to your clipboard from the output of Step 3. 8 | 5. Open `launch.rkt` and add the `(integrity ...)` expression where instructed. 9 | 6. Run `racket launch.rkt do +a defn.rkt`. It will now work. 10 | 11 | We didn't do all this in earlier examples because those examples shut 12 | off Denxi's safeguards. Harmless demos are one thing, but you should 13 | not turn off the safeguards without good reason. 14 | 15 | You'll only repeat one of these steps whenever you encounter a new, 16 | untrusted instance of data. 17 | -------------------------------------------------------------------------------- /examples/input-checking/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | (name "example03-output") 4 | 5 | (output "default" 6 | inp := (input-ref "data") 7 | (resolve-input inp)) 8 | 9 | (input "data" 10 | (artifact 11 | (sources (text-source "code code code\n")) 12 | (integrity 'sha384 (base64 "qAhYrIsnCp+iSA1sPg74sfLbv/PsRSUVL0K6krxwRAwvdOEVxloL089YFXw1xukS")) 13 | (signature "https://sagegerard.com/public.pem" 14 | (base64 "eOCgSyQw+fumSQsdVHqHMjDhQWgvHV6tTsPDie/kMs3KJPfPn0xycuyFiTq7ig6s3D4TBmWWKQqJEqfr/A0nHcOeLVI+TCPNTE4q+6AHvVLmVYjoSV61k8Vpn6b5ixm4lmlACE5RLUYymC2xKLT6AGnZPToeJLwOUX9quPKAIoOCObJh3EFE3il6ZBeeAYxh+hdXOPdZ2O4Zkqn5HS8IMUhrlc24CXSUqjrsI+V7tB+qBmR33O+1wZ1+nLUgKRhPXDf0p/SbfQddnQmESrZn/ylnl+Ua6R81hX40mKi9O2HXshMLD9j9IuA337ESheMKGMPRuHlzSdpq/0DM3zSybKzjmyDmiZ0qX6OF+6E7qR4Ss+7nbW1vWl1q2rk1p/n4GJ8DYIxKyaq4lPqJPePmGXF2HUmwyJFj85l3QdWekzH9yu+yrPpDz3zp0L5+2T2gtPsQ7zKTHyjedMAbwtBfUPTOvWSb1X+hXw14RiAF6Xf6lMsZNvIGQOp7ag/bo4l1DwBJul7J9gFH6sHe6dvo9AzCHHMVuJR4n0ikUql3wFc73ydRv5lkvVoVbkxsRBeJWvDXGbhy50K1UBh+tID/MWRUjX91RUYzyrKpTvB+BU8Q8gnbeTb7oCb+Vn1T3rEpnwVNGvSOCoFUwe29J6WBt5HcybL7Z0a0O0jbNqebJI8=")))) 15 | -------------------------------------------------------------------------------- /examples/input-checking/launch.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | ; To complete Step 2, add the name of the crypto hash function Denxi 4 | ; mentioned in Step 1 to this list. It should look like '(sha384), 5 | ; not '("sha384"). 6 | (DENXI_TRUST_CHFS '()) 7 | 8 | ; Paste the expression from Step 4 into this list. This is how you 9 | ; tell Denxi that you trust a public key. Note that this list is not 10 | ; quoted! 11 | (DENXI_TRUST_PUBLIC_KEYS (list)) 12 | 13 | (module+ main (launch-denxi!)) 14 | -------------------------------------------------------------------------------- /examples/input-output-selection/README.md: -------------------------------------------------------------------------------- 1 | This example installs HTML and CSS files embedded directly in the 2 | package definition. Selecting an output impacts the page's appearance 3 | despite using one reference to the same HTML file. 4 | 5 | 1. Run `racket launch.rkt do +s dark-mode dark defn.rkt`. You will see 6 | a symlink named `dark-mode` appear. The HTML page inside appears 7 | black (If your browser has trouble with the symbolic link, just 8 | copy the HTML file somewhere else). 9 | 10 | 2. Run `racket launch.rkt do +s light-mode light defn.rkt`. You will a 11 | symlink named `light-mode` appear. Inside is the a link to the 12 | _exact same_ HTML file, but no CSS file with it. The HTML page 13 | appears white. 14 | 15 | When I say _exact same_, I mean that the page is a symbolic link to 16 | the same `index.html` file in Denxi's state, because that's the only 17 | HTML file we defined. This is how inputs are cached. 18 | -------------------------------------------------------------------------------- /examples/input-output-selection/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | (input "light.css" 4 | (artifact (text-source "body { background: white }\n") 5 | #f 6 | #f)) 7 | 8 | (input "index.html" 9 | (artifact 10 | (lines-source 11 | #f 12 | '("" 13 | "" 14 | " " 15 | " " 16 | " " 17 | " Colored by output" 18 | " " 19 | "")) 20 | #f 21 | #f)) 22 | 23 | (output "light" 24 | (keep-input "index.html") 25 | (keep-input "light.css")) 26 | 27 | ; dark = absense of light 28 | (output "dark" 29 | (keep-input "index.html")) 30 | -------------------------------------------------------------------------------- /examples/input-output-selection/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Colored by output 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/input-output-selection/launch.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (current-chfs (list snake-oil-chf)) 4 | (DENXI_TRUST_BAD_DIGEST #t) 5 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 6 | 7 | (module+ main (launch-denxi!)) 8 | -------------------------------------------------------------------------------- /examples/input-overriding/README.md: -------------------------------------------------------------------------------- 1 | You may override package inputs for some launcher. One way is to use 2 | the command line interface. 3 | 4 | ``` 5 | racket launcher.rkt \ 6 | +a defn.rkt \ 7 | +o '#px""' '(input "data" (artifact (byte-source #"override") #f #f))' 8 | ``` 9 | 10 | This command line adds overrides to `DENXI_INPUT_OVERRIDES`, which 11 | applies to all packages in the scope of a transaction. Each override 12 | takes two arguments. The first is a readable regular expression that 13 | matches against a package query. The second is the code for an input 14 | expression. 15 | 16 | The example uses an empty pattern to match all package definitions as 17 | eligible for overriding. We then replace any inputs in those 18 | definitions with the same name as the input expression we provided. 19 | 20 | The override applies to all eligible packages, and all inputs of the 21 | same name. This allows you to standardize dependencies in the event 22 | you end up with something like multiple slightly different copies of 23 | Ruby. 24 | 25 | ``` 26 | RUBY='(input "ruby" ...)' racket launcher.rkt do \ 27 | +a ... \ 28 | +o 'syntax-highlighting' "$RUBY" \ 29 | +o 'images' "$RUBY" 30 | ``` 31 | -------------------------------------------------------------------------------- /examples/input-overriding/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | (input "data" (artifact #"original" #f #f)) 3 | (output "default" (keep-input "data")) 4 | -------------------------------------------------------------------------------- /examples/input-overriding/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | (current-chfs (list snake-oil-chf)) 3 | (DENXI_TRUST_BAD_DIGEST #t) 4 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 5 | (module+ main (launch-denxi!)) 6 | -------------------------------------------------------------------------------- /examples/input-proliferation/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | ; Denxi assumes that outputs with different names necessarily produce 4 | ; different content. This definition will therefore create duplicate 5 | ; data despite starting from the same input. This will install without 6 | ; issue, but you should not create equivalent outputs. 7 | 8 | ; A G-zipped TAR in byte string form. 9 | (input "archive.tgz" 10 | (artifact (byte-source #"\37\213\b\0\0\0\0\0\0\3\355\321Q\n\2!\20\6`\217\342\21\224\\=\217E\364\336n\367\317\36\202\236ZXH\b\276\17\206\1\35\230\201\277\207\337KC\255\345\325s[\322g\177\v\271\244\332rK\243\306{\253\345\24b\232p[x\254[\277\307\30\326~\273~\233\333\373\377S\347\t;\16\345_\344?\303e\302\216C\371/\362\a\0\0\0\0\0\0\0\200=O\177\203}\e\0(\0\0") 11 | #f 12 | #f)) 13 | 14 | (output "tweedledee" (extract-input "archive.tgz")) 15 | (output "tweedledum" (extract-input "archive.tgz")) 16 | -------------------------------------------------------------------------------- /examples/input-proliferation/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | (current-chfs (list snake-oil-chf)) 3 | (DENXI_TRUST_BAD_DIGEST #t) 4 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 5 | (module+ main (launch-denxi!)) 6 | -------------------------------------------------------------------------------- /examples/integrity-checking/README.md: -------------------------------------------------------------------------------- 1 | To understand this example, you need a working understanding of 2 | cryptographic hash functions (CHFs). 3 | 4 | By default, Denxi always verifies if data produces a specific digest. 5 | You need to trust the relevant CHF for Denxi to trust the data enough 6 | to perform an integrity check in the first place. The launcher uses a 7 | prototype CHF to enable integrity checking for toy examples. 8 | -------------------------------------------------------------------------------- /examples/integrity-checking/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | ; Integrity information makes sure that an artifact's content is 4 | ; correct. This is especially important if the data comes from the 5 | ; network. 6 | 7 | ; Notice that we can express the digests using different encodings 8 | ; and names for SHA-1. The launcher shows how this works. 9 | 10 | ; If you mess with the integrity information OR the content, the 11 | ; installation will fail integerity checks. Try it! 12 | 13 | (input "food" 14 | (artifact (byte-source #"cookie") 15 | (integrity 'SHA-1 16 | (hex "59c826fc854197cbd4d1083bce8fc00d0761e8b3")) 17 | #f)) 18 | 19 | (input "drink" 20 | (artifact (byte-source #"milk") 21 | (integrity 'sha1 22 | (base64 "z12/DsV9/12lbYbEWzvdEYSaBlo=")) 23 | #f)) 24 | 25 | (output "default" 26 | (keep-input "food") 27 | (keep-input "drink")) 28 | -------------------------------------------------------------------------------- /examples/integrity-checking/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (module+ main (launch-denxi!)) 4 | 5 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 6 | 7 | ; Here we give a SHA-1 implementation to Denxi with a pattern to 8 | ; recognize variations in names (sha1, SHA-1, ...). `sha1-bytes` is 9 | ; not a good implementation for production, but it is compatible with 10 | ; every Racket installation. 11 | (require file/sha1) 12 | (current-chfs (list (chf 'sha1 #"^(?i:sha-_?1)$" sha1-bytes))) 13 | 14 | ; Previously we used no integrity information in our artifact, so we 15 | ; no longer shut off integrity checking using DENXI_TRUST_BAD_DIGEST. 16 | ; In practice, we should respond to missing integrity information 17 | ; by adding that information, NOT by shutting off safety checks! 18 | ; 19 | ; However, we are still missing a signature. We'll trust that 20 | ; scenario for now. 21 | (DENXI_TRUST_UNSIGNED #t) 22 | -------------------------------------------------------------------------------- /examples/locks/README.md: -------------------------------------------------------------------------------- 1 | This example uses a pre-built workspace as a lock for reproducible 2 | builds. Run `racket launch.rkt do +a defn.rkt`. You will see a symlink 3 | appear with exact content despite the package definition itself 4 | containing no useful information. 5 | -------------------------------------------------------------------------------- /examples/locks/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | (name "example06-output") 3 | (output "default") 4 | -------------------------------------------------------------------------------- /examples/locks/launch.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (require racket/runtime-path) 4 | 5 | (define-runtime-path workspace "workspace") 6 | 7 | (DENXI_TRUST_BAD_DIGEST #t) 8 | (DENXI_WORKSPACE (path->complete-path workspace)) 9 | 10 | (current-string->source file-source) 11 | 12 | (module+ main (launch-denxi!)) 13 | -------------------------------------------------------------------------------- /examples/make-verification-files/README.md: -------------------------------------------------------------------------------- 1 | `certify` creates digest and signature files in terms of Denxi's 2 | cryptographic backend. `verify` checks those files against content. 3 | 4 | You can use Denxi's intentionally-leaked RSA keypair to practice. Run 5 | `racket -l denxi/signature/snake-oil` in this directory to create a 6 | copy of the leaked private key, leaked password, and public key. 7 | 8 | Now let's create arbitrary files. 9 | 10 | ``` 11 | echo 'some data' > some.junk 12 | echo 'more data' > more.junk 13 | ``` 14 | 15 | You should now have two `.junk` files, one `.txt` file, and two `.pem` 16 | files. 17 | 18 | Run this command to create digests and signatures for the `.junk` 19 | files. 20 | 21 | ``` 22 | ./certify sha3-512 \ 23 | LEAKED-private-key.pem \ 24 | LEAKED-private-key-password.txt \ 25 | *.junk 26 | ``` 27 | 28 | You can then verify the integrity and signature information. Run this 29 | command to see checkmarks indicating which check passed. 30 | 31 | ``` 32 | ./verify sha3-512 public-key.pem *.junk 33 | ``` 34 | 35 | **Exercise:** Change the contents of the junk files. How does this 36 | impact the output of the `verify` command? 37 | 38 | **Exercise:** How might you adapt this example to output signatures 39 | only, such as `.asc` files for GPG users? 40 | 41 | **Exercise:** Create a directory for use with 42 | `make-filesystem-shovel`. Use `certify` to populate that directory, 43 | such that `(input "story" "story.txt")` works in a package definition. 44 | -------------------------------------------------------------------------------- /examples/make-verification-files/certify: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env racket 2 | #lang denxi/launcher 3 | 4 | (module+ main 5 | (require racket/cmdline 6 | racket/path 7 | racket/port 8 | denxi/dig/filesystem) 9 | 10 | (define (write-bytes-to-file! path byte-string) 11 | (call-with-output-file path 12 | (λ (to-file) 13 | (copy-port (open-input-bytes byte-string) 14 | to-file)))) 15 | 16 | (command-line #:args (user-chf-string private-key-path private-key-password-path . user-paths) 17 | (for ([user-path (in-list user-paths)]) 18 | (parameterize ([current-chfs (build-builtin-chf-trust (list (string->symbol user-chf-string)))]) 19 | (define chf (get-default-chf)) 20 | (define expanded-user-path (expand-user-path user-path)) 21 | (define digest-file-path (make-digest-file-path expanded-user-path chf)) 22 | (define signature-file-path (make-signature-file-path digest-file-path)) 23 | (define digest (make-digest expanded-user-path)) 24 | (define signature (make-signature digest chf (file->bytes private-key-path) (file->bytes private-key-password-path))) 25 | (write-bytes-to-file! digest-file-path digest) 26 | (write-bytes-to-file! signature-file-path signature))))) 27 | -------------------------------------------------------------------------------- /examples/make-verification-files/verify: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env racket 2 | #lang denxi/launcher 3 | 4 | (module+ main 5 | (require racket/cmdline 6 | racket/path 7 | racket/port 8 | denxi/dig/filesystem) 9 | 10 | (define (write-bytes-to-file! path byte-string) 11 | (call-with-output-file path 12 | (λ (to-file) 13 | (copy-port (open-input-bytes byte-string) 14 | to-file)))) 15 | 16 | (command-line #:args (user-chf-string public-key-path . user-paths) 17 | (for ([user-path (in-list user-paths)]) 18 | (parameterize ([current-chfs (build-builtin-chf-trust (list (string->symbol user-chf-string)))]) 19 | (define chf 20 | (get-default-chf)) 21 | 22 | (define expanded-user-path 23 | (expand-user-path user-path)) 24 | 25 | (define digest-file-path 26 | (make-digest-file-path expanded-user-path chf)) 27 | 28 | (define signature-file-path 29 | (make-signature-file-path digest-file-path)) 30 | 31 | (define integrity-info 32 | (integrity chf (file->bytes digest-file-path))) 33 | 34 | (define signature-info 35 | (signature (file->bytes public-key-path) 36 | (file->bytes signature-file-path))) 37 | 38 | (define integrity-result 39 | (check-integrity 40 | #:trust-bad-digest #f 41 | (λ _ #t) 42 | integrity-info 43 | (make-digest expanded-user-path))) 44 | 45 | (define signature-result 46 | (check-signature #:trust-bad-digest #f 47 | #:trust-unsigned #f 48 | #:trust-public-key? (λ _ #t) 49 | #:verify-signature (current-verify-signature) 50 | signature-info 51 | integrity-info)) 52 | 53 | (printf "digest ~a, signature ~a: ~a~n" 54 | (if (integrity-check-passed? integrity-result) "🗸" "x") 55 | (if (signature-check-passed? signature-result) "🗸" "x") 56 | user-path))))) 57 | 58 | -------------------------------------------------------------------------------- /examples/maximum-trust/dangerous.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | #||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 4 | 5 | 6 | .-. 7 | (0.0) 8 | '=.|m|.=' 9 | .='`"``=. 10 | 11 | /!\ DANGER /!\ - MAXIMUM TRUST LAUNCHER - /!\ DANGER /!\ 12 | 13 | This launcher allows every dangerous operation so that you can see 14 | Denxi's attack surface. 15 | 16 | High trust makes Denxi's built-in CLI convenient, at the cost of giving 17 | package definitons more power. Package definitions are a publishable 18 | format that controls I/O operations on a host, so they must be treated 19 | with the same regard as shell scripts. 20 | 21 | Even with zero-trust, be sure you check your operating system 22 | permissions for a Denxi process no matter what. Running this 23 | file as an administrator on an untrusted package definition is 24 | like running `curl -k ... | sudo sh`. 25 | 26 | This extreme exists as a consequence of Denxi's deeply-configurable 27 | model. To understand what "deeply" means, look how little code there 28 | is here. The gap from zero to maximum trust is not small, but trusting 29 | _just the right things_ can defeat entire subsystems. Take 30 | `DENXI_TRUST_BAD_DIGEST`, which we've used for many examples. That 31 | trust defeats all data verification, effectively implying trust for 32 | all settings in `DENXI_TRUST_*`! That one boolean creates a path to 33 | arbitrary code execution when paired with a malicious package 34 | definition. 35 | 36 | Why allow the risk? Because it lets us talk about security when it's 37 | relevant. When we're just dealing with a stupid little string in a 38 | package definition, no network access is necessary for an installation. 39 | Trusting toy data makes it a lot easier to focus on what the example 40 | is about. Subtle changes in user experience like that make these 41 | scary settings worth having around. 42 | 43 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||# 44 | 45 | ; Run any executable that a package definition wants to run. 46 | (DENXI_TRUST_ANY_EXECUTABLE #t) 47 | 48 | ; Trust any content 49 | (DENXI_TRUST_BAD_DIGEST #t) 50 | 51 | ; I'm okay with HTTP, and bad certificates over HTTPS. 52 | (DENXI_TRUST_UNVERIFIED_HOST #t) 53 | 54 | ; Push my computer as hard as you want. 55 | (DENXI_MEMORY_LIMIT_MB +inf.0) 56 | (DENXI_TIME_LIMIT_S +inf.0) 57 | 58 | ; Download without limits on space or time. 59 | (DENXI_FETCH_TOTAL_SIZE_MB +inf.0) 60 | (DENXI_FETCH_TIMEOUT_MS +inf.0) 61 | 62 | ; Run at your own risk. Maybe even as an admin if you are okay with 63 | ; your own computer betraying you. 64 | (module+ main (launch-denxi!)) 65 | -------------------------------------------------------------------------------- /examples/output-conflicts/README.md: -------------------------------------------------------------------------------- 1 | Denxi caches installed package outputs. The cache uses _exact package 2 | queries_ as keys. This creates a shared namespace for all installed 3 | software in a workspace directory. If keys conflict, Denxi will hand 4 | you a link to previously installed software. We can override that when 5 | prototyping. 6 | 7 | There are two definitions in this example: `lub.rkt`, and 8 | `dub.rkt`. They both use the package name `heart`, and they both 9 | produce a file called `sound`. The definitions differ only in the 10 | content they write to `sound`, so we can reproduce a conflict easily. 11 | 12 | 1. Run `racket launcher.rkt do +a lub.rkt`. A `heart` link will appear. 13 | 2. Run `cat heart/sound`. You'll see `lub`. 14 | 3. Delete the `heart` link. 15 | 4. Run `racket launcher.rkt do +a dub.rkt`. A `heart` link will appear, but `heart/sound` shows `lub` and not `dub` because of a cache hit. 16 | 17 | Here are all of the ways to avoid a cache hit: 18 | 19 | - The definitions can distinguish themselves more by declaring different editions, revisions, providers, or package names. 20 | - A launcher can force side-by-side installations for all definitions using `(current-package-editor sxs)`. 21 | - Install each package in its own workspace. 22 | - Collect garbage using `racket launcher gc` before Step 4. This will 23 | make Denxi delete the installed `heart` package because it has no 24 | incoming links. That would make the name available again. 25 | -------------------------------------------------------------------------------- /examples/output-conflicts/dub.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | (name "heart") 3 | (input "sound" (artifact (text-source "DUB\n") #f #f)) 4 | (output "default" (keep-input "sound")) 5 | -------------------------------------------------------------------------------- /examples/output-conflicts/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | (module+ main (launch-denxi!)) 3 | (current-chfs (list snake-oil-chf)) 4 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 5 | (DENXI_TRUST_BAD_DIGEST #t) 6 | -------------------------------------------------------------------------------- /examples/output-conflicts/lub.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | (name "heart") 3 | (input "sound" (artifact (text-source "lub\n") #f #f)) 4 | (output "default" (keep-input "sound")) 5 | -------------------------------------------------------------------------------- /examples/package-dependencies/README.md: -------------------------------------------------------------------------------- 1 | This example shows how package definitions can depend on other package 2 | definitions. 3 | 4 | 1. Run `racket launch.rkt do +a defn.rkt` 5 | 2. Run `racket program.rkt`. It will print `It works!` 6 | 7 | Depending on a package means naming another package definition as an 8 | input. Once that input is resolved, the dependent package 9 | programmatically installs the dependency. 10 | -------------------------------------------------------------------------------- /examples/package-dependencies/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | (output "default" 4 | inp := (input-ref "pkgdef.rkt") 5 | path := (resolve-input inp) 6 | (install "nested" "default" path) 7 | (release-input inp)) 8 | 9 | (input "pkgdef.rkt" 10 | (artifact (file-source (from-file "other.rkt")) 11 | #f 12 | #f)) 13 | -------------------------------------------------------------------------------- /examples/package-dependencies/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | (define test-omit-paths '("program.rkt")) 3 | (define compile-omit-paths '("program.rkt")) 4 | -------------------------------------------------------------------------------- /examples/package-dependencies/launch.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (current-chfs (list snake-oil-chf)) 4 | (DENXI_TRUST_BAD_DIGEST #t) 5 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 6 | 7 | (module+ main (launch-denxi!)) 8 | -------------------------------------------------------------------------------- /examples/package-dependencies/other.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | (name "other") 4 | 5 | (output "default" 6 | inp := (input-ref "module.rkt") 7 | (resolve-input inp)) 8 | 9 | (input "module.rkt" 10 | (artifact (text-source 11 | "(module anon racket/base (provide msg) (define msg \"It works!\"))") 12 | #f 13 | #f)) 14 | -------------------------------------------------------------------------------- /examples/package-dependencies/program.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require "default/nested/module.rkt") 4 | 5 | (displayln msg) 6 | -------------------------------------------------------------------------------- /examples/racket-installation-manager/README.md: -------------------------------------------------------------------------------- 1 | `rim` stands for Racket Installation Manager. 2 | 3 | Run it with a Racket version number. 4 | -------------------------------------------------------------------------------- /examples/racket-modules/README.md: -------------------------------------------------------------------------------- 1 | This is an introductory example that says `Hello!` using one dependency. 2 | 3 | In this case, the dependency is expressed directly in the package 4 | definition file, which we trust implicitly in the launcher. Implicit 5 | trust is a bad habit we will break in future examples. 6 | 7 | 1. Run `racket program.rkt`. You will see an error about not finding a `hello.rkt`. 8 | 2. Run `racket launch.rkt do +a defn.rkt`. You will see status messages. 9 | 3. Run `racket program.rkt` again. You will see `Hello!` 10 | -------------------------------------------------------------------------------- /examples/racket-modules/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | (name "example00-output") 4 | 5 | (output "default" 6 | module-input := (input-ref "hello.rkt") 7 | (resolve-input module-input)) 8 | 9 | (input "hello.rkt" 10 | (artifact 11 | (lines-source #f 12 | '("(module hello racket/base" 13 | " (provide say-hello)" 14 | " (define (say-hello)" 15 | " (displayln \"Hello!\")))")) 16 | #f 17 | #f)) 18 | -------------------------------------------------------------------------------- /examples/racket-modules/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | (define test-omit-paths '("program.rkt")) 3 | (define compile-omit-paths '("program.rkt")) 4 | -------------------------------------------------------------------------------- /examples/racket-modules/launch.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (DENXI_TRUST_BAD_DIGEST #t) 4 | 5 | (module+ main (call-with-snake-oil-chf-trust launch-denxi!)) 6 | -------------------------------------------------------------------------------- /examples/racket-modules/program.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | (require "example00-output/hello.rkt") 3 | (module+ main (say-hello)) 4 | -------------------------------------------------------------------------------- /examples/self-hosting/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | (os-support unix) 4 | 5 | (output "default" 6 | inp := (input-ref "install-racket.sh") 7 | path := (resolve-input inp) 8 | (run "sh" path "--in-place" "--dest" "racket") 9 | (release-input inp) 10 | (run "./racket/bin/racket" "-U" "-A" "./addon" 11 | "-l" "raco/main" "--" "pkg" "install" "-i" "--auto" "denxi")) 12 | 13 | (input "install-racket.sh" 14 | (artifact (http-source "https://download.racket-lang.org/releases/8.1/installers/racket-minimal-8.1-x86_64-linux-cs.sh") 15 | (integrity 'sha1 16 | (hex "be40b5de3447679f3ad672f46aa2f2784c6002c2")) 17 | #f)) 18 | -------------------------------------------------------------------------------- /examples/self-hosting/launch.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | ; Trust any `sh' executable that can be found in the host's PATH. 4 | (DENXI_TRUST_HOST_EXECUTABLES '("sh")) 5 | 6 | (DENXI_TRUST_UNSIGNED #t) 7 | 8 | (module+ main (launch-denxi!)) 9 | -------------------------------------------------------------------------------- /examples/signature-checking/README.md: -------------------------------------------------------------------------------- 1 | Denxi uses asymmetric cryptography to verify that digests are signed 2 | by trusted parties. This couples signatures to integrity, and makes it 3 | all more important to use a trustworthy CHF. 4 | -------------------------------------------------------------------------------- /examples/signature-checking/defn.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | ; Here we include signature information. If you trust the public key, 4 | ; then you can perform a signature verification. 5 | 6 | ; The content, integrity information, and signature are mathematically 7 | ; linked. If you change the content, you change the integrity 8 | ; information. If you change the integrity information, you change the 9 | ; signature. 10 | ; 11 | ; How useful this is depends on the CHF and cipher you use. 12 | 13 | (input "food" 14 | (artifact (byte-source #"cookie") 15 | (integrity 'sha1 16 | (hex "59c826fc854197cbd4d1083bce8fc00d0761e8b3")) 17 | (signature (file-source (from-file "public-key.pem")) 18 | (file-source (from-file "signature.bin"))))) 19 | 20 | (output "default" 21 | (keep-input "food")) 22 | -------------------------------------------------------------------------------- /examples/signature-checking/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | (module+ main (launch-denxi!)) 4 | 5 | ; This launcher checks integrity and signatures, 6 | ; but uses an untrustworthy CHF and public key. 7 | ; You can use this as a template to substitute 8 | ; public keys and CHFs you actually trust. 9 | 10 | (require racket/runtime-path) 11 | (define-runtime-path public-key "public-key.pem") 12 | 13 | (current-chfs 14 | (list snake-oil-chf)) 15 | 16 | (DENXI_TRUST_PUBLIC_KEYS 17 | (list (make-trusted-integrity public-key))) 18 | 19 | (DENXI_WORKSPACE 20 | (build-path (current-directory) "workspace")) 21 | -------------------------------------------------------------------------------- /examples/signature-checking/public-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1CVq+Lq4jwGBeX2XHjNt 3 | dMjhc/GwxmnYLbXhoseRsihvO33aO2Sd75Suhq1gSdSofwVWjlFJf7qEfu/DyCAY 4 | 3561cZnDtV4pWtGbC4sqb3IxkFzToI6ceNT9wXceaR4Zuo8FuLVM3HXvyWtwKT6W 5 | GIxNqXFs/MyHvgYoifmy0xksuN0S/GQ3dpAP0FHXB+/LP7AzkOfu3NaP6/Bt73tx 6 | rt35VzFR6DmH6KotBF2CA9SLSxkDXXc/rr3noCSMvlmmgup7LFGw51HcosPOcq4R 7 | LLaVAaDiYQDW0CxIEihP9bYEHP+338BcYpIYYv5E0JlmsXSQKI2gv7cLoO3j4ioS 8 | VQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /examples/signature-checking/sign.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | ; Bonus: This program creates a signature for you using your own 4 | ; private key. Using this, try to make your own keypair for the 5 | ; example. 6 | 7 | (module+ main 8 | (require (only-in denxi/codec 9 | hex) 10 | (only-in denxi/integrity 11 | current-chfs 12 | chf-canonical-name 13 | snake-oil-chf) 14 | (only-in denxi/signature 15 | make-signature) 16 | (only-in denxi/signature/ffi 17 | signature-ffi-available?!)) 18 | 19 | (current-chfs 20 | (list snake-oil-chf)) 21 | 22 | (command-line 23 | #:args (private-key-path) 24 | (if (signature-ffi-available?!) 25 | (call-with-output-file "signature.bin" 26 | (λ (out) 27 | (define sig 28 | (make-signature (hex "59c826fc854197cbd4d1083bce8fc00d0761e8b3") 29 | (chf-canonical-name snake-oil-chf) 30 | (file->bytes private-key-path) 31 | #f)) 32 | (copy-port (open-input-bytes sig) out))) 33 | (displayln 34 | (~a "Sorry, but this system could not load Denxi's built-in crypto library.\n" 35 | "You can still do the bonus for this example using a custom\n" 36 | "cryptographic backend. See the related example!"))))) 37 | -------------------------------------------------------------------------------- /examples/signature-checking/signature.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyrolasting/denxi/2547f0b3c016e0c5761267fa61ae1be591cc8e7b/examples/signature-checking/signature.bin -------------------------------------------------------------------------------- /examples/versioning/README.md: -------------------------------------------------------------------------------- 1 | This example has three definitions that each represent a release of 2 | the same product. The editions indicate the design used for the 3 | software, and the revisions indicate implementations for that design 4 | made over time. See `raco docs denxi/version` for definitions. 5 | 6 | Each package definition includes its own version information, but they 7 | share the name `greeting`. If we abbreviate our installation request 8 | using `do +a` or `do ++install-abbreviated`, then Denxi will pick 9 | `greeting` as the name of each version's link. We avoid this conflict 10 | by building a transaction that uses the default output, but allows us 11 | to specify our own link names. 12 | 13 | ``` 14 | racket launch.rkt do \ 15 | +d loud0 loud0.rkt \ 16 | +d loud1 loud1.rkt \ 17 | +d quiet quiet0.rkt 18 | ``` 19 | 20 | Once that's done, run `denxi show installed`. The output will look 21 | something like this. 22 | 23 | ``` 24 | example.com:greeting:loud:0:0:ii default yk2bp2qbwrdgtb9gwacv8tdqrjakyp2d 25 | example.com:greeting:loud:1:1:ii default y19r2j34yja9g3b8ygzmc4rmjspkj85h 26 | example.com:greeting:quiet:0:0:ii default yja5p81pjtdsqaztrkhh0b1n9sjgb1vj 27 | ``` 28 | 29 | The first column is an _exact package query_ that identifies the 30 | package definition used at the time (`raco docs denxi/query`). The 31 | second column is the name of the output used in that definition. The 32 | third column is the name of the directory in the workspace's `objects` 33 | directory representing the installed output. 34 | -------------------------------------------------------------------------------- /examples/versioning/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | (current-chfs (list snake-oil-chf)) 3 | (DENXI_TRUST_BAD_DIGEST #t) 4 | (DENXI_WORKSPACE (build-path (current-directory) "workspace")) 5 | (module+ main (launch-denxi!)) 6 | -------------------------------------------------------------------------------- /examples/versioning/loud0.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | (provider "example.com") 3 | (name "greeting") 4 | (edition "loud") 5 | (revision-number 0) 6 | (revision-names "initial" "oldest") 7 | (input "hello.txt" (artifact (text-source "HELLO, WORLD!!") #f #f)) 8 | (output "default" (keep-input "hello.txt")) 9 | -------------------------------------------------------------------------------- /examples/versioning/loud1.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | (provider "example.com") 3 | (name "greeting") 4 | (edition "loud") 5 | (revision-number 1) 6 | (revision-names "no-exclaim") 7 | (input "hello.txt" (artifact (text-source "HELLO, WORLD.") #f #f)) 8 | (output "default" (keep-input "hello.txt")) 9 | -------------------------------------------------------------------------------- /examples/versioning/quiet0.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | (provider "example.com") 3 | (name "greeting") 4 | (edition "quiet") 5 | (revision-number 0) 6 | (revision-names "initial" "oldest") 7 | (input "hello.txt" (artifact (text-source "hello, world") #f #f)) 8 | (output "default" (keep-input "hello.txt")) 9 | -------------------------------------------------------------------------------- /examples/workspaces/.gitignore: -------------------------------------------------------------------------------- 1 | workspace 2 | -------------------------------------------------------------------------------- /examples/workspaces/README.md: -------------------------------------------------------------------------------- 1 | A workspace is a directory that contains a persistent state for Denxi. 2 | Run `raco doc workspace L:denxi/state` for more. 3 | 4 | When you launch, `DENXI_WORKSPACE` initializes to a default workspace 5 | directory. You can see its path outside of a custom launcher with this 6 | command. 7 | 8 | ``` 9 | $ racket -l racket/base -l denxi/state -e '(DENXI_WORKSPACE)' 10 | ``` 11 | 12 | This example has two launchers. Each pick their workspace locations 13 | differently. Use each of them to install the provided definition. 14 | Look inside the new workspace directories. You'll find 15 | content-addressable files and links in an `objects` directory, and a 16 | SQLite3 database called `db`. Denxi keeps the database 17 | logically-consistent with the `objects` directory. 18 | 19 | **Protect your workspaces!** Workspaces are an input that can 20 | change or break Denxi's behavior, so they are part of the attack 21 | surface. Limit workspace write access to the user who owns it, and 22 | limit the write permissions of that user. 23 | -------------------------------------------------------------------------------- /examples/workspaces/definition.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi 2 | 3 | (input "hello.txt" 4 | (artifact (text-source "Hello, world!") #f #f)) 5 | 6 | (output "default" 7 | (keep-input "hello.txt")) 8 | -------------------------------------------------------------------------------- /examples/workspaces/launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | (module+ main (launch-denxi!)) 3 | 4 | (current-chfs (list snake-oil-chf)) 5 | (DENXI_TRUST_BAD_DIGEST #t) 6 | 7 | ; Tell Denxi to perform transactions against a fixed, adjacent 8 | ; directory. After running this launcher against the definition, 9 | ; you'll see a `workspace` directory appear with state information. 10 | ; 11 | ; Links are issued from there. 12 | 13 | (require racket/runtime-path) 14 | (define-runtime-path workspace "workspace") 15 | (DENXI_WORKSPACE workspace) 16 | -------------------------------------------------------------------------------- /examples/workspaces/launcher2.rkt: -------------------------------------------------------------------------------- 1 | #lang denxi/launcher 2 | 3 | ; This version sets the workspace based on the current directory. Try 4 | ; going to different directories to run installatons. 5 | (module+ main (launch-denxi!)) 6 | 7 | (DENXI_WORKSPACE (build-path (current-directory) "following")) 8 | (current-chfs (list snake-oil-chf)) 9 | (DENXI_TRUST_BAD_DIGEST #t) 10 | -------------------------------------------------------------------------------- /file.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ; Extend file system operations 4 | 5 | (provide (all-defined-out) 6 | (all-from-out racket/file)) 7 | 8 | (require file/glob 9 | racket/file 10 | racket/format 11 | racket/function 12 | racket/generator 13 | racket/port 14 | racket/sequence 15 | "codec.rkt" 16 | "message.rkt" 17 | "path.rkt" 18 | "subprogram.rkt" 19 | "url.rkt") 20 | 21 | (define+provide-message $path-not-found (pattern wrt)) 22 | 23 | ; Use module-level cache because filesystem-root-list may take a while 24 | ; on Windows. 25 | (define filesystem-root-list/cached 26 | (let ([cache #f]) 27 | (λ () 28 | (unless cache 29 | (set! cache (filesystem-root-list))) 30 | cache))) 31 | 32 | 33 | (define-subprogram (path-matching variant [wrt (current-directory)]) 34 | (with-handlers ([exn? (λ (e) ($fail ($path-not-found variant wrt)))]) 35 | (sequence-ref (in-paths variant wrt) 0))) 36 | 37 | (define (in-paths variant [wrt (current-directory)]) 38 | (sequence-filter (cond [(or (regexp? variant) 39 | (pregexp? variant) 40 | (byte-pregexp? variant) 41 | (byte-regexp? variant)) 42 | (curry regexp-match? variant)] 43 | [(string? variant) 44 | (λ (p) 45 | (parameterize ([current-directory wrt]) 46 | (glob-match? variant p)))]) 47 | (in-directory wrt))) 48 | 49 | (define (delete-file* path) 50 | (when (or (file-exists? path) (link-exists? path)) 51 | (delete-file path))) 52 | 53 | 54 | 55 | (define (in-matching-files patterns start-dir) 56 | (in-generator 57 | (for ([path (in-directory start-dir (negate link-exists?))]) 58 | (define rel-path (find-relative-path start-dir path)) 59 | (when (and (file-exists? rel-path) 60 | (ormap (λ (p) (regexp-match? p rel-path)) patterns)) 61 | (yield rel-path))))) 62 | 63 | 64 | (define (make-link/clobber to link-path) 65 | (make-directory* (or (path-only link-path) (current-directory))) 66 | (when (link-exists? link-path) 67 | (delete-file link-path)) 68 | (make-file-or-directory-link to link-path)) 69 | 70 | 71 | (define (delete-directory/files/empty-parents path) 72 | (delete-directory/files path) 73 | (define cpath (path->complete-path path)) 74 | (let loop ([current (simplify-path cpath)] [next (../ cpath)]) 75 | (if (or (equal? current next) 76 | (not (directory-empty? next))) 77 | (void) 78 | (begin (delete-directory next) 79 | (loop next (../ next)))))) 80 | 81 | 82 | (define (directory-empty? path) 83 | (null? (directory-list path))) 84 | 85 | 86 | (define (something-exists? path) 87 | (or (file-exists? path) 88 | (directory-exists? path) 89 | (link-exists? path))) 90 | 91 | 92 | (define (linked? link-path path) 93 | (and (link-exists? link-path) 94 | (something-exists? path) 95 | (equal? (file-or-directory-identity link-path) 96 | (file-or-directory-identity path)))) 97 | 98 | 99 | (define (file-link-exists? path) 100 | (and (link-exists? path) 101 | (file-exists? path))) 102 | 103 | 104 | (define (call-with-temporary-directory f #:cd? [cd? #t] #:base [base #f]) 105 | (when base (make-directory* base)) 106 | (define tmp-dir (make-temporary-file "rktdir~a" 'directory base)) 107 | (dynamic-wind void 108 | (λ () (parameterize ([current-directory (if cd? tmp-dir (current-directory))]) 109 | (f tmp-dir))) 110 | (λ () 111 | (when (directory-exists? tmp-dir) 112 | (delete-directory/files tmp-dir))))) 113 | 114 | (define (call-with-temporary-file proc) 115 | (define tmp (make-temporary-file "~a")) 116 | (dynamic-wind void 117 | (λ () (proc tmp)) 118 | (λ () (delete-file tmp)))) 119 | 120 | 121 | (define-syntax-rule (with-temporary-directory body ...) 122 | (call-with-temporary-directory 123 | (λ (tmp-dir) body ...))) 124 | -------------------------------------------------------------------------------- /info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define collection "denxi") 4 | 5 | (define version "0.0") ; Don't use. This project defines its own scheme. 6 | 7 | (define deps 8 | '("base" 9 | "compatibility-lib" 10 | "db-lib" 11 | "rackunit-lib" 12 | "sandbox-lib" 13 | "scribble-lib")) 14 | 15 | (define build-deps '("net-doc" "racket-doc")) 16 | 17 | (define test-omit-paths 18 | '("docs" 19 | "crypto/openssl" 20 | "crypto/dist")) 21 | 22 | (define compile-omit-paths 23 | '("examples" 24 | "crypto/openssl" 25 | "crypto/dist")) 26 | 27 | (define racket-launcher-names '("denxi")) 28 | (define racket-launcher-libraries '("cli.rkt")) 29 | 30 | (define scribblings 31 | '(("docs/index/denxi-index.scrbl" () (tool)) 32 | ("docs/reference/denxi-reference.scrbl" (multi-page) (tool-library)) 33 | ("docs/guide/denxi-guide.scrbl" () (tool)) 34 | ("docs/journal/denxi-journal.scrbl" (multi-page) (other)) 35 | ("docs/white-paper/denxi-white-paper.scrbl" () (other)))) 36 | -------------------------------------------------------------------------------- /l10n.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ; Loads human-readable strings dynamically 4 | 5 | (provide get-message-formatter 6 | get-localized-string 7 | run+print-subprogram) 8 | 9 | (require racket/list 10 | racket/runtime-path 11 | racket/sequence 12 | "codec.rkt" 13 | "format.rkt" 14 | "subprogram.rkt" 15 | "printer.rkt") 16 | 17 | (define-runtime-path here "l10n") 18 | 19 | (define (run+print-subprogram subprogram-inst) 20 | (parameterize ([current-message-formatter (get-message-formatter)]) 21 | (define-values (result messages) (run-subprogram subprogram-inst)) 22 | (write-message-log messages (current-message-formatter)) 23 | result)) 24 | 25 | (define (dynamic-require/localized key) 26 | (let ([on-failure (americentric-fallback key)]) 27 | (with-handlers ([exn:fail:filesystem? on-failure]) 28 | (dynamic-require (get-module-path (system-language+country)) 29 | key 30 | on-failure)))) 31 | 32 | (define (get-message-formatter) 33 | (define f 34 | (combine-message-formatters (dynamic-require/localized 'format-message/locale) 35 | default-message-formatter)) 36 | (λ (m) 37 | (parameterize ([current-message-formatter f]) 38 | (f m)))) 39 | 40 | (define (get-localized-string-lookup) 41 | (dynamic-require/localized 'get-string)) 42 | 43 | (define (get-localized-string sym) 44 | ((get-localized-string-lookup) sym)) 45 | 46 | (define (get-module-path locale) 47 | (path-replace-extension 48 | (build-path here 49 | (string-downcase 50 | (coerce-string 51 | (regexp-replace 52 | #rx"_" 53 | (regexp-replace #px"\\..+" locale "") 54 | "-")))) 55 | #".rkt")) 56 | 57 | (define (americentric-fallback sym) 58 | (λ _ (dynamic-require (get-module-path "en-us") sym))) 59 | 60 | (module+ test 61 | (require rackunit 62 | "message.rkt") 63 | 64 | (test-not-exn "Americentric fallback is always available" 65 | (λ () 66 | (check-pred procedure? (americentric-fallback 'format-message/locale)) 67 | (check-pred procedure? (americentric-fallback 'get-string)))) 68 | 69 | (test-case "Can get formatter on current system" 70 | (define formatter (get-message-formatter)) 71 | (check-pred procedure? formatter) 72 | (check-eq? (procedure-arity formatter) 1) 73 | (check-equal? (formatter ($show-string "a")) "a"))) 74 | -------------------------------------------------------------------------------- /l10n/shared.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (define-syntax-rule (r s ...) 4 | (begin (begin (provide (all-from-out s)) 5 | (require s)) ...)) 6 | 7 | (r racket/exn 8 | "../artifact.rkt" 9 | "../archive.rkt" 10 | "../cli-flag.rkt" 11 | "../cmdline.rkt" 12 | "../crypto.rkt" 13 | "../dig.rkt" 14 | "../codec.rkt" 15 | "../file.rkt" 16 | "../format.rkt" 17 | "../input.rkt" 18 | "../integrity.rkt" 19 | "../state.rkt" 20 | "../message.rkt" 21 | "../package.rkt" 22 | "../printer.rkt" 23 | "../port.rkt" 24 | "../query.rkt" 25 | "../racket-module.rkt" 26 | "../racket-version.rkt" 27 | "../security.rkt" 28 | "../setting.rkt" 29 | "../signature.rkt" 30 | "../source.rkt" 31 | "../string.rkt" 32 | "../subprogram.rkt" 33 | "../system.rkt" 34 | "../url.rkt") 35 | -------------------------------------------------------------------------------- /launcher.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (define-syntax-rule (r s ...) 4 | (begin (begin (provide (all-from-out s)) 5 | (require s)) ...)) 6 | 7 | (r racket/base 8 | "archive.rkt" 9 | "artifact.rkt" 10 | "cli-flag.rkt" 11 | "cli.rkt" 12 | "cmdline.rkt" 13 | "codec.rkt" 14 | "crypto.rkt" 15 | "dig.rkt" 16 | "dig/filesystem.rkt" 17 | "dig/http.rkt" 18 | "file.rkt" 19 | "format.rkt" 20 | "input.rkt" 21 | "integrity.rkt" 22 | "l10n.rkt" 23 | "message.rkt" 24 | "monad.rkt" 25 | "notary.rkt" 26 | "output.rkt" 27 | "package.rkt" 28 | "path.rkt" 29 | "port.rkt" 30 | "printer.rkt" 31 | "query.rkt" 32 | "racket-module.rkt" 33 | "racket-version.rkt" 34 | "rfc4648.rkt" 35 | "security.rkt" 36 | "setting.rkt" 37 | "signature.rkt" 38 | "source.rkt" 39 | "state.rkt" 40 | "string.rkt" 41 | "subprogram.rkt" 42 | "system.rkt" 43 | "transaction.rkt" 44 | "url.rkt" 45 | "version.rkt") 46 | 47 | (module reader syntax/module-reader denxi/launcher) 48 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyrolasting/denxi/2547f0b3c016e0c5761267fa61ae1be591cc8e7b/logo.png -------------------------------------------------------------------------------- /main.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | (require denxi/pkgdef) 3 | (provide (all-from-out denxi/pkgdef)) 4 | (module reader syntax/module-reader denxi/pkgdef) 5 | -------------------------------------------------------------------------------- /message.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ; Define $message, the root of a prefab structure type tree. Prefab 4 | ; structures are flexible enough to accomodate all of the following 5 | ; use cases at once: 6 | ; 7 | ; - Visibility into data when something breaks 8 | ; - Transmit data over place channels or ports 9 | ; - Return values for monadic operations 10 | ; - Compose data in custom shapes 11 | 12 | (provide (struct-out $message) 13 | define-message 14 | define+provide-message 15 | scope-message 16 | call-in-message-scope 17 | call-in-message-scope* 18 | in-message-scope 19 | get-message-scope) 20 | 21 | (define-syntax define-message 22 | (syntax-rules () 23 | [(_ id super-id (fields ...)) 24 | (struct id super-id (fields ...) #:prefab)] 25 | [(_ id (fields ...)) 26 | (define-message id $message (fields ...))])) 27 | 28 | (define-syntax-rule (define+provide-message id rem ...) 29 | (begin (provide (struct-out id)) 30 | (define-message id rem ...))) 31 | 32 | 33 | (struct $message () #:prefab) 34 | (define+provide-message $show-datum (value)) 35 | (define+provide-message $show-string (message)) 36 | (define+provide-message $regarding (subject body)) 37 | 38 | 39 | (define mark-key (string->uninterned-symbol "denxi:message-scope")) 40 | 41 | (define (get-message-scope) 42 | (or (continuation-mark-set-first 43 | (current-continuation-marks) 44 | mark-key) 45 | null)) 46 | 47 | (define (scope-message m [scope (get-message-scope)]) 48 | (if (null? scope) m 49 | (scope-message ($regarding (car scope) m) 50 | (cdr scope)))) 51 | 52 | (define (call-in-message-scope* ms proc) 53 | (with-continuation-mark mark-key ms 54 | (proc))) 55 | 56 | (define (call-in-message-scope m proc) 57 | (call-in-message-scope* (cons m (get-message-scope)) 58 | proc)) 59 | 60 | (define-syntax-rule (in-message-scope m body ...) 61 | (call-in-message-scope m (λ () body ...))) 62 | 63 | 64 | (module+ test 65 | (require rackunit) 66 | 67 | (define-message $foo (a b c)) 68 | (define foo-inst ($foo 1 2 3)) 69 | 70 | (check-pred $foo? foo-inst) 71 | (check-equal? ($foo-a foo-inst) 1) 72 | (check-equal? ($foo-b foo-inst) 2) 73 | (check-equal? ($foo-c foo-inst) 3) 74 | 75 | (define-message $scope (v)) 76 | (test-case "Scope messages" 77 | (in-message-scope ($scope 1) 78 | (in-message-scope ($scope 2) 79 | (check-equal? (get-message-scope) 80 | (list ($scope 2) 81 | ($scope 1))) 82 | (check-equal? (scope-message foo-inst) 83 | ($regarding ($scope 1) 84 | ($regarding ($scope 2) 85 | foo-inst))))))) 86 | -------------------------------------------------------------------------------- /monad.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ; Limited monad library with monomorphic bind and related do notation 4 | 5 | (provide (all-defined-out)) 6 | 7 | (require (for-syntax racket/base 8 | syntax/parse) 9 | racket/generic) 10 | 11 | (define-generics monad 12 | [bind monad continue]) 13 | 14 | (define-syntax (:= stx) 15 | (raise-syntax-error #f ":= outside mcompose" stx)) 16 | 17 | (define-syntax (mdo stx) 18 | (syntax-parse stx #:literals (:=) 19 | [(_) #'(void)] 20 | [(_ e:expr) #'e] 21 | [(_ target:id := e:expr . body) 22 | #'(bind e (λ (target) (mdo . body)))] 23 | [(_ e:expr . body) 24 | #'(bind e (λ _ (mdo . body)))])) 25 | 26 | 27 | (module+ test 28 | (require racket/function 29 | rackunit) 30 | 31 | (struct include-string (proc) 32 | #:methods gen:monad 33 | [(define (bind ma f) (include-string-bind ma f))]) 34 | 35 | (define (include-string-bind ma f) 36 | (include-string 37 | (λ (str) 38 | (define-values (v str*) ((include-string-proc ma) str)) 39 | (define-values (v* str**) ((include-string-proc (f v)) str*)) 40 | (values v* str**)))) 41 | 42 | (define (include-string-return v) 43 | (include-string (curry values v))) 44 | 45 | (define program 46 | (mdo a := (include-string-return 1) 47 | b := (include-string (λ (str) (values (add1 a) (string-append str "+")))) 48 | (include-string (λ (str) (values (* b 2) (string-append str "*")))))) 49 | 50 | (test-pred "Allow empty (mdo) forms" 51 | void? 52 | (mdo)) 53 | 54 | (test-pred "Adopts value of monadic type using mdo" 55 | include-string? 56 | program) 57 | 58 | (test-case "Compose operations with mdo" 59 | (call-with-values (λ () ((include-string-proc program) "start")) 60 | (λ (v str) 61 | (check-equal? v 4) 62 | (check-equal? str "start+*"))))) 63 | -------------------------------------------------------------------------------- /notary.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require racket/contract) 4 | 5 | (provide (struct-out notary) 6 | (contract-out 7 | [lazy-notary notary?] 8 | [make-fraudulent-notary 9 | (-> symbol? notary?)] 10 | [make-notary 11 | (->* () 12 | (#:chf symbol? 13 | #:private-key 14 | (or/c #f source-variant?) 15 | #:public-key-source 16 | (or/c #f source-variant?) 17 | #:private-key-password 18 | (or/c #f source-variant?)) 19 | notary?)] 20 | [notarize 21 | (-> notary? 22 | (or/c artifact? source-variant?) 23 | (subprogram/c artifact?))])) 24 | 25 | (require racket/file 26 | racket/match 27 | "artifact.rkt" 28 | "crypto.rkt" 29 | "integrity.rkt" 30 | "signature.rkt" 31 | "source.rkt" 32 | "subprogram.rkt") 33 | 34 | (struct notary 35 | (chf 36 | public-key-source 37 | private-key 38 | private-key-password)) 39 | 40 | (define (make-notary #:chf [chf (get-default-chf)] 41 | #:public-key-source [pb #f] 42 | #:private-key [pk #f] 43 | #:private-key-password [pkp #f]) 44 | (notary chf pb pk pkp)) 45 | 46 | 47 | (define lazy-notary 48 | (make-notary)) 49 | 50 | ; Don't reduce to normal constructor call. This counts as implicit 51 | ; test coverage based on module instantiations. 52 | (define (make-fraudulent-notary [chf (get-default-chf)]) 53 | (make-notary #:chf chf 54 | #:public-key-source 55 | snake-oil-public-key 56 | #:private-key 57 | snake-oil-private-key 58 | #:private-key-password 59 | snake-oil-private-key-password)) 60 | 61 | 62 | (define (notarize the-notary content) 63 | (match-define (notary chf pubkey prvkey prvkeypass) the-notary) 64 | 65 | (define user-source 66 | (if (source-variant? content) 67 | content 68 | (artifact-source content))) 69 | 70 | (define content-source 71 | (coerce-source user-source)) 72 | 73 | (if chf 74 | (subprogram-fetch 75 | "notary" 76 | content-source 77 | (λ (in est-size) 78 | (define intinfo 79 | (and chf 80 | (integrity 81 | chf 82 | (make-digest in chf)))) 83 | 84 | (close-input-port in) 85 | 86 | (artifact user-source 87 | intinfo 88 | (and intinfo 89 | pubkey 90 | prvkey 91 | (signature 92 | pubkey 93 | (make-signature 94 | (integrity-digest intinfo) 95 | (integrity-chf-symbol intinfo) 96 | prvkey 97 | prvkeypass)))))) 98 | (subprogram-unit (artifact user-source #f #f)))) 99 | 100 | (module+ test 101 | (require rackunit 102 | (submod "subprogram.rkt" test)) 103 | (check-pred notary? (make-notary)) 104 | (call-with-snake-oil-cipher-trust 105 | (λ () 106 | (check-match 107 | (get-subprogram-value 108 | (notarize (make-fraudulent-notary) 109 | (make-artifact #"abc"))) 110 | (artifact #"abc" 111 | (integrity (? symbol? _) 112 | (? bytes? _)) 113 | (signature (? bytes? _) 114 | (? bytes? _))))))) 115 | -------------------------------------------------------------------------------- /output.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require racket/contract) 4 | 5 | (provide transparent-package-output 6 | (struct-out package-output) 7 | (contract-out 8 | [encode-package-output 9 | (-> package-output? 10 | list?)] 11 | [find-package-output 12 | (-> string? 13 | (listof package-output?) 14 | (or/c #f package-output?))])) 15 | 16 | (require (for-syntax racket/base 17 | "string.rkt") 18 | syntax/parse/define 19 | "monad.rkt" 20 | "subprogram.rkt") 21 | 22 | (struct package-output (name steps make-subprogram)) 23 | 24 | (define-syntax-parse-rule (transparent-package-output name:non-empty-string steps:expr ...) 25 | (package-output 26 | name 27 | (quote (mdo steps ...)) 28 | (lambda () (coerce-subprogram (mdo steps ...))))) 29 | 30 | (define (encode-package-output output) 31 | `(output ,(package-output-name output) 32 | . ,(package-output-steps output))) 33 | 34 | (define (find-package-output name outputs) 35 | (findf (λ (o) (equal? name (package-output-name o))) 36 | outputs)) 37 | -------------------------------------------------------------------------------- /path.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ; Extend racket/path 4 | 5 | (provide (all-defined-out) 6 | (all-from-out racket/path)) 7 | 8 | (require racket/path) 9 | 10 | (define (../ path) 11 | (simplify-path (build-path path 'up))) 12 | 13 | (define (path-prefix? to-check prefix-pathy) 14 | (define maybe-prefixed (explode-path (simplify-path (path->complete-path to-check)))) 15 | (define pref (explode-path (simplify-path (path->complete-path prefix-pathy)))) 16 | 17 | (and (<= (length pref) 18 | (length maybe-prefixed)) 19 | (for/and ([(el index) (in-indexed pref)]) 20 | (equal? (list-ref maybe-prefixed index) 21 | el)))) 22 | 23 | (module+ test 24 | (require racket/file 25 | rackunit) 26 | 27 | (test-case "Do not bypass root directory in upward traversals" 28 | (for ([root (filesystem-root-list)]) 29 | (check-equal? root (../ root)))) 30 | 31 | (test-case "Detect path prefixes" 32 | (define paths '("/a/b/c" ".")) 33 | (for ([p (in-list paths)]) 34 | (check-true (path-prefix? p (../ p))) 35 | (check-false (path-prefix? (../ p) p))))) 36 | -------------------------------------------------------------------------------- /printer.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ; Write $message instances to ports 4 | 5 | (require racket/contract 6 | racket/fasl 7 | racket/list 8 | racket/pretty 9 | racket/serialize 10 | "format.rkt" 11 | "message.rkt" 12 | "setting.rkt" 13 | "subprogram.rkt") 14 | 15 | 16 | (provide (contract-out 17 | [write-message-log 18 | (-> (or/c $message? subprogram-log/c) 19 | message-formatter/c 20 | void?)] 21 | [write-message 22 | (->* ($message?) (#:newline? any/c message-formatter/c output-port?) void?)])) 23 | 24 | (define+provide-message $verbose (message)) 25 | (define+provide-setting DENXI_FASL_OUTPUT boolean? #f) 26 | (define+provide-setting DENXI_VERBOSE boolean? #f) 27 | (define+provide-setting DENXI_READER_FRIENDLY_OUTPUT boolean? #f) 28 | 29 | (define (filter-output m) 30 | (if ($verbose? m) 31 | (and (DENXI_VERBOSE) 32 | ($verbose-message m)) 33 | m)) 34 | 35 | 36 | ; Program output can be a subprogram log so that users don't always have to 37 | ; construct an organized list of messages. 38 | (define (write-message-log program-output format-message) 39 | (define messages 40 | (if (list? program-output) 41 | (reverse (flatten program-output)) 42 | (in-value program-output))) 43 | (for ([m messages]) 44 | (write-message m format-message))) 45 | 46 | 47 | (define (write-message v 48 | #:newline? [newline? #t] 49 | [formatter (current-message-formatter)] 50 | [out (current-output-port)]) 51 | (define maybe-message (filter-output v)) 52 | (when maybe-message 53 | (parameterize ([current-output-port out]) 54 | (define to-send 55 | (if (DENXI_READER_FRIENDLY_OUTPUT) 56 | maybe-message 57 | (formatter maybe-message))) 58 | 59 | (if (DENXI_FASL_OUTPUT) 60 | (s-exp->fasl (serialize to-send) (current-output-port)) 61 | (if (DENXI_READER_FRIENDLY_OUTPUT) 62 | (pretty-write #:newline? newline? to-send) 63 | ((if newline? displayln display) to-send))) 64 | (flush-output)))) 65 | 66 | (module+ test 67 | (require racket/format 68 | rackunit 69 | "setting.rkt") 70 | 71 | (define dummy ($show-string "Testing: Blah")) 72 | 73 | (define (write-output v [out (current-output-port)]) 74 | (write-message v default-message-formatter out)) 75 | 76 | (define (capture-bytes p) 77 | (define buffer (open-output-bytes)) 78 | (p buffer) 79 | (get-output-bytes buffer #t)) 80 | 81 | (define (test-output msg v expected) 82 | (define buffer (open-output-bytes)) 83 | 84 | ; parameterize adds coverage for default value in write-output 85 | (parameterize ([current-output-port buffer]) 86 | (write-output v)) 87 | 88 | (test-true msg 89 | (if (or (regexp? expected) 90 | (pregexp? expected)) 91 | (regexp-match? expected (get-output-bytes buffer #t)) 92 | (equal? (get-output-bytes buffer #t) expected)))) 93 | 94 | (test-output "By default, program output is human-friendly" 95 | dummy 96 | #px"Testing: Blah") 97 | 98 | (DENXI_READER_FRIENDLY_OUTPUT #t 99 | (λ () 100 | (test-output "Allow reader-friendly output" 101 | dummy 102 | (capture-bytes 103 | (λ (o) 104 | (pretty-write #:newline? #t dummy o)))) 105 | 106 | (DENXI_FASL_OUTPUT #t 107 | (λ () 108 | (test-case "Allow FASL output" 109 | (define in 110 | (open-input-bytes 111 | (capture-bytes 112 | (λ (o) (write-output dummy o))))) 113 | 114 | (check-equal? (deserialize (fasl->s-exp in)) 115 | dummy)))))) 116 | 117 | (test-case "Control verbose output" 118 | (DENXI_VERBOSE #f 119 | (λ () 120 | (test-output "Opt out of verbose output" 121 | ($verbose dummy) 122 | #""))) 123 | 124 | (DENXI_VERBOSE #t 125 | (λ () (DENXI_READER_FRIENDLY_OUTPUT #t 126 | (λ () 127 | (test-output "Opt into verbose output" 128 | ($verbose dummy) 129 | #px"\\$show-string"))))))) 130 | -------------------------------------------------------------------------------- /signature/base.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require racket/contract) 4 | 5 | (provide 6 | (struct-out signature) 7 | (contract-out 8 | [check-signature 9 | (-> #:trust-public-key? (-> input-port? any/c) 10 | #:verify-signature verify-signature/c 11 | #:trust-unsigned any/c 12 | #:trust-bad-digest any/c 13 | (or/c #f signature?) 14 | (or/c #f integrity?) 15 | symbol?)] 16 | [current-make-signature 17 | (parameter/c make-signature/c)] 18 | [current-verify-signature 19 | (parameter/c verify-signature/c)] 20 | [make-signature/c 21 | chaperone-contract?] 22 | [raw-signature? 23 | flat-contract?] 24 | [signature-check-passed? 25 | flat-contract?] 26 | [verify-signature/c 27 | chaperone-contract?])) 28 | 29 | (require "../integrity.rkt" 30 | "../message.rkt" 31 | "ffi.rkt") 32 | 33 | (define make-signature/c 34 | (-> bytes? symbol? bytes? (or/c #f bytes?) bytes?)) 35 | 36 | (define verify-signature/c 37 | (-> bytes? symbol? bytes? bytes? boolean?)) 38 | 39 | (define current-verify-signature 40 | (make-parameter 41 | (if (signature-ffi-available?!) 42 | signature-ffi-verify-signature! 43 | (λ _ #f)))) 44 | 45 | 46 | (define current-make-signature 47 | (make-parameter 48 | (if (signature-ffi-available?!) 49 | signature-ffi-make-signature! 50 | (λ _ #"")))) 51 | 52 | 53 | (struct signature (public-key body) 54 | #:transparent) 55 | 56 | (define raw-signature? 57 | (struct/c signature bytes? bytes?)) 58 | 59 | (define signature-check-passed? 60 | (and/c symbol? 61 | (or/c 'signature-verified 62 | 'skip 63 | 'skip-unsigned))) 64 | 65 | (define (check-signature #:trust-bad-digest trust-bad-digest 66 | #:trust-unsigned trust-unsigned 67 | #:trust-public-key? trust-pk? 68 | #:verify-signature trust-sig? 69 | siginfo 70 | intinfo) 71 | (if trust-bad-digest 72 | 'skip 73 | (if (and (raw-signature? siginfo) 74 | (raw-integrity? intinfo)) 75 | (let ([public-key (signature-public-key siginfo)]) 76 | (if (trust-pk? (open-input-bytes public-key)) 77 | (if (trust-sig? 78 | (integrity-digest intinfo) 79 | (integrity-chf-symbol intinfo) 80 | (signature-body siginfo) 81 | public-key) 82 | 'signature-verified 83 | 'signature-unverified) 84 | 'blocked-public-key)) 85 | (if trust-unsigned 86 | 'skip-unsigned 87 | 'unsigned)))) 88 | 89 | 90 | (module+ test 91 | (require rackunit) 92 | 93 | (define intinfo (integrity 'snake-oil #"")) 94 | (define siginfo (signature #"pubkey" #"body")) 95 | (define unsigned (signature #"pubkey" #f)) 96 | (define (T . _) #t) 97 | (define (F . _) #f) 98 | 99 | (check-true (signature-check-passed? 'skip)) 100 | (check-true (signature-check-passed? 'signature-verified)) 101 | (check-true (signature-check-passed? 'skip-unsigned)) 102 | (check-false (signature-check-passed? 'blocked-public-key)) 103 | (check-false (signature-check-passed? 'unsigned)) 104 | (check-false (signature-check-passed? 'signature-unverified)) 105 | 106 | (check-eq? (check-signature 107 | #:trust-public-key? F 108 | #:verify-signature F 109 | #:trust-unsigned #f 110 | #:trust-bad-digest #t 111 | siginfo 112 | intinfo) 113 | 'skip) 114 | 115 | (check-eq? (check-signature 116 | #:trust-public-key? F 117 | #:verify-signature F 118 | #:trust-unsigned #f 119 | #:trust-bad-digest #f 120 | siginfo 121 | intinfo) 122 | 'blocked-public-key) 123 | 124 | (check-eq? (check-signature 125 | #:trust-public-key? T 126 | #:verify-signature F 127 | #:trust-unsigned #f 128 | #:trust-bad-digest #f 129 | siginfo 130 | intinfo) 131 | 'signature-unverified) 132 | 133 | (check-eq? (check-signature 134 | #:trust-public-key? T 135 | #:verify-signature T 136 | #:trust-unsigned #f 137 | #:trust-bad-digest #f 138 | siginfo 139 | intinfo) 140 | 'signature-verified)) 141 | -------------------------------------------------------------------------------- /signature/snake-oil.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ; Intentionally leak a PEM-encoded RSA keypair for tests. 4 | 5 | (require racket/contract) 6 | (provide 7 | (contract-out 8 | [snake-oil-private-key bytes?] 9 | [snake-oil-public-key bytes?] 10 | [snake-oil-private-key-password bytes?])) 11 | 12 | ; usage: racket -l denxi/signature/snake-oil 13 | (module+ main 14 | (require racket/port) 15 | (define (<< path bstr) 16 | (call-with-output-file path (λ (out) (copy-port (open-input-bytes bstr) out)))) 17 | (<< "LEAKED-private-key-password.txt" snake-oil-private-key-password) 18 | (<< "LEAKED-private-key.pem" snake-oil-private-key) 19 | (<< "public-key.pem" snake-oil-public-key)) 20 | 21 | (define snake-oil-private-key-password #"foobar") 22 | 23 | (define snake-oil-private-key (string->bytes/utf-8 #<bytes/utf-8 #< url-variant? url?)] 15 | [coerce-url-string (-> url-variant? url-string?)])) 16 | 17 | (define (url-variant? v) 18 | (or (url? v) 19 | (url-string? v))) 20 | 21 | (define (coerce-url-string v) 22 | (if (url? v) 23 | (url->string v) 24 | v)) 25 | 26 | (define (coerce-url v) 27 | (if (string? v) 28 | (string->url v) 29 | v)) 30 | 31 | (define (url-string? s) 32 | (with-handlers ([exn:fail? (λ _ #f)]) 33 | (and (string->url s) 34 | #t))) 35 | 36 | (define-syntax-class url-string 37 | (pattern (~var str string) 38 | #:when (url-string? (syntax-e #'str)))) 39 | 40 | (module+ test 41 | (require rackunit) 42 | 43 | (define valid-url-strings 44 | '("foo" 45 | "https://example.com" 46 | "?a=b")) 47 | 48 | (for ([valid valid-url-strings]) 49 | (check-pred url-string? valid) 50 | (check-pred url-variant? valid) 51 | (check-pred url-string? (coerce-url-string valid)) 52 | (check-pred url? (coerce-url valid)) 53 | (check-true (syntax-parse (datum->syntax #'whatever valid) 54 | [v:url-string #t] 55 | [_ #f])))) 56 | --------------------------------------------------------------------------------