├── .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 | 
8 | [](https://racket-lang.org)
9 | [](./COPYING)
10 | [](https://docs.racket-lang.org/denxi-white-paper/index.html)
11 | [](https://docs.racket-lang.org/denxi-index/index.html)
12 | [](https://youtu.be/bIi-tUzOwdw?t=2330)
13 | [](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 |
--------------------------------------------------------------------------------