Compare commits

...

2 Commits

Author SHA1 Message Date
Emily
f3b80ee197 autopen: init at 0.2.0 2026-08-14 09:12:29 +01:00
Emily
9acceb309f maintainers/team-list: add boot security team 2026-08-14 09:12:29 +01:00
11 changed files with 897 additions and 0 deletions

View File

@@ -526,3 +526,6 @@ pkgs/by-name/wa/warp-terminal/ @emilytrau @imadnyc @4evy @johnrtitor
/nixos/lib/testing @NixOS/test-driver
/nixos/tests/nixos-test-driver @NixOS/test-driver
/nixos/modules/virtualisation/nspawn-container/run-nspawn @NixOS/test-driver
# Boot security
/pkgs/by-name/au/autopen @NixOS/boot-security

View File

@@ -91,6 +91,10 @@ with lib.maintainers;
shortName = "Blockchains";
};
boot-security = {
github = "boot-security";
};
budgie = {
members = [
bobby285271

View File

@@ -0,0 +1,170 @@
{
lib,
buildPackages,
autopen,
}:
let
inherit (lib)
concatMapStringsSep
escapeURL
extendMkDerivation
getLib
splitString
unsafeGetAttrPos
;
inherit (autopen.lib)
sign
;
inherit (autopen.lib.internal)
mkCliDerivationBuilder
;
inherit (autopen.lib.authenticode)
attachSignature
mkSignedAttrsForPe
;
mkSystemdSbsignDerivation = mkCliDerivationBuilder {
package = getLib buildPackages.systemd;
exe = "${getLib buildPackages.systemd}/lib/systemd/systemd-sbsign";
attrPrefix = "systemdSbsign";
};
escapeURLPath = path: concatMapStringsSep "/" escapeURL (splitString "/" path);
in
{
mkSignedAttrsForPe = extendMkDerivation {
constructDrv = mkSystemdSbsignDerivation;
extendDrvArgs =
finalAttrs:
{
pname,
version,
certificate,
peFile,
pos ? unsafeGetAttrPos "pname" args,
...
}@args:
{
name = "${pname}-${version}-${baseNameOf peFile}.signed-attrs.der";
systemdSbsignArgs = [
"sign"
finalAttrs.certificateArgs
{
prepareOfflineSigning = true;
output = placeholder "out";
}
peFile
];
# `systemd-sbsign(1)` expects a PEMencoded certificate, but
# autopen produces DER-encoded certificates. We explicitly
# use the default OpenSSL provider, which takes `file://`
# URLs and accepts both encodings.
certificateArgs = {
certificateSource = "provider:default";
certificate = "file://${escapeURLPath "${certificate}"}";
};
certificateNotBefore = certificate.certificateParams.notBefore;
preSystemdSbsign = ''
export SOURCE_DATE_EPOCH="$(date --date="$certificateNotBefore" +%s)"
'';
inherit pos;
};
};
attachSignature = extendMkDerivation {
constructDrv = mkSystemdSbsignDerivation;
extendDrvArgs =
finalAttrs:
{
pname,
version,
signature,
passthru ? { },
pos ? unsafeGetAttrPos "pname" args,
...
}@args:
let
signedAttrs = signature.message;
in
{
name = "${pname}-${version}-${baseNameOf signedAttrs.peFile}.signed";
systemdSbsignArgs = [
"sign"
signedAttrs.certificateArgs
{
signedData = signedAttrs;
signedDataSignature = signature;
output = placeholder "out";
}
signedAttrs.peFile
];
passthru = {
inherit (signedAttrs) certificate peFile;
inherit signedAttrs;
}
// passthru;
inherit pos;
};
};
mkSignedPe = extendMkDerivation {
constructDrv = attachSignature;
# Ensure that the signing key doesnt leak from the derivation.
excludeDrvArgNames = [ "signingKey" ];
extendDrvArgs =
finalAttrs:
{
pname,
version,
signingKey,
certificate,
peFile,
pos ? unsafeGetAttrPos "pname" args,
meta ? { },
...
}@args:
{
signature = sign {
inherit signingKey;
message = mkSignedAttrsForPe {
inherit
pname
version
certificate
peFile
pos
;
meta = meta // {
${if meta ? description then "description" else null} = "${meta.description} (to be signed)";
};
};
inherit pos;
meta = meta // {
${if meta ? description then "description" else null} = "${meta.description} (signature)";
};
};
inherit pos;
};
};
}

View File

@@ -0,0 +1,9 @@
{ callPackage }:
{
internal = callPackage ./internal.nix { };
signingKey = callPackage ./signing-key.nix { };
sign = callPackage ./sign.nix { };
x509 = callPackage ./x509.nix { };
authenticode = callPackage ./authenticode.nix { };
}

View File

@@ -0,0 +1,148 @@
{
lib,
stdenvNoCC,
autopen,
}:
let
inherit (lib)
concatMap
concatMapStringsSep
extendMkDerivation
isAttrs
isDerivation
match
splitStringBy
substring
toLower
toUpper
;
inherit (lib.generators)
mkValueStringDefault
;
inherit (lib.cli)
toCommandLine
;
inherit (autopen.lib.internal)
mkCliDerivationBuilder
;
splitCamelCase = splitStringBy (_prev: curr: match "[A-Z]" curr != null) true;
toKebabCase = camelCase: concatMapStringsSep "-" toLower (splitCamelCase camelCase);
optionFormat = optionName: {
option = "--${toKebabCase optionName}";
sep = "=";
explicitBool = false;
};
in
{
mkCliDerivationBuilder =
{
package,
exe,
attrPrefix,
}:
extendMkDerivation {
constructDrv = stdenvNoCC.mkDerivation;
extendDrvArgs =
finalAttrs:
{
nativeBuildInputs ? [ ],
...
}@args:
{
nativeBuildInputs = [ package ] ++ nativeBuildInputs;
buildCommand = ''
runHook "pre$hookName"
echoCmd "$exeName flags" "''${exeFlags[@]}"
"$exe" "''${exeFlags[@]}"
runHook "post$hookName"
'';
inherit exe;
exeName = baseNameOf exe;
exeFlags = concatMap (
component:
if isAttrs component && !isDerivation component then
toCommandLine optionFormat component
else
[ (mkValueStringDefault { } component) ]
) args."${attrPrefix}Args";
hookName = toUpper (substring 0 1 attrPrefix) + substring 1 (-1) attrPrefix;
strictDeps = true;
__structuredAttrs = true;
};
};
mkAutopenDerivation = mkCliDerivationBuilder {
package = autopen;
exe = "autopen";
attrPrefix = "autopen";
};
/**
Hide a derivations internals.
This is used to abstract away implementation details so that
secret capabilities used at build time dont trivially leak out of
a derivation to consumers of its outputs.
Note that pulling `drv.drvPath` pulls in the transitive build
dependency closure of `drv`, including built outputs, so this is
not foolproof. Hiding `drvPath` wouldnt solve this, as any
derivation that uses the output of `drv` would itself have a
`drvPath` that behaves the same way. Therefore, this should
unfortunately be considered a besteffort approach under current
Nix semantics.
# Inputs
`drv`
: The derivation to hide the internals of.
# Type
```
hideDerivation :: Derivation -> Derivation
```
*/
hideDerivation =
drv:
assert isDerivation drv && drv.outputs == [ "out" ];
let
hiddenDrv = {
inherit (drv)
type
name
system
outPath
drvPath
outputs
outputName
strictDeps
__structuredAttrs
passthru
meta
;
out = hiddenDrv;
all = [ hiddenDrv ];
}
// drv.passthru;
in
hiddenDrv;
}

View File

@@ -0,0 +1,76 @@
{
lib,
autopen,
}:
let
inherit (lib)
unsafeGetAttrPos
;
inherit (autopen.lib.internal)
hideDerivation
mkAutopenDerivation
;
in
/**
Sign a message with a signing key.
# Inputs
`signingKey`
: The signing key to use.
`message`
: The path to the message to sign.
`pos` (optional)
: The position for the signature derivation.
`meta` (optional)
: The metadata for the signature derivation.
# Type
```
sign ::
{
signingKey :: SigningKey,
message :: StorePath,
pos :: { file :: String, line :: Int } | Null,
meta :: AttrSet,
} -> Derivation
```
*/
{
signingKey,
message,
pos ? unsafeGetAttrPos "message" args,
meta ? { },
}@args:
hideDerivation (mkAutopenDerivation {
name = "${message.name}.sig";
inherit signingKey message;
autopenArgs = [
"sign"
{
inherit signingKey;
output = placeholder "out";
}
message
];
# Keys shouldnt propagate to outputs.
allowedRequisites = [ "out" ];
passthru = {
# TODO: This is technically a little strange in terms of the
# cryptographic semantics, but its convenient.
inherit message;
};
inherit pos meta;
})

View File

@@ -0,0 +1,242 @@
{
lib,
autopen,
}:
let
inherit (lib)
hashString
unsafeGetAttrPos
;
inherit (autopen.lib.internal)
hideDerivation
mkAutopenDerivation
;
/**
Produce a fake derivation object with a given `outPath`.
This differs from `lib.toDerivation` in that it does not depend on
`storePaths`.
# Inputs
`outPath`
: The output path for the derivation.
`attrs`
: Any additional attributes to include in the derivation.
# Type
```
fakeDerivation :: String -> AttrSet -> Derivation
```
*/
fakeDerivation =
outPath: attrs:
let
drv = {
type = "derivation";
outputs = [ "out" ];
out = drv;
all = [ drv ];
outputName = "out";
inherit outPath;
}
// attrs;
in
drv;
in
{
/**
Import a signing key from an existing path.
::: {.warning}
The contents of the path will be copied into the store; **this is
insecure to use with software keys**.
Outside of testing, you should use remote keys that do not
contain private key material, likely through
`autopen.lib.signingKey.remote`.
:::
# Inputs
`name`
: The name to use for the signing key derivation.
`path`
: The path containing the signing key.
`pos` (optional)
: The position for the signing key derivation.
`meta` (optional)
: The metadata for the signing key derivation.
# Type
```
import ::
{
name :: String,
path :: Path | Derivation,
pos :: { file :: String, line :: Int } | Null,
meta :: AttrSet,
} -> SigningKey
```
*/
import =
{
name,
path,
pos ? unsafeGetAttrPos "name" args,
meta ? { },
}@args:
let
signingKey = fakeDerivation "${path}" {
inherit name verificationKey;
meta = meta // {
${if pos != null then "position" else null} = "${pos.file}:${toString pos.line}";
};
};
verificationKey = hideDerivation (mkAutopenDerivation {
name = "${name}-verification-key";
autopenArgs = [
"signing-key"
"get-verification-key"
{
inherit signingKey;
output = placeholder "out";
}
];
inherit pos;
meta = meta // {
${if meta ? description then "description" else null} = "${meta.description} (verification key)";
};
});
in
signingKey;
/**
Create a remote signing key.
These are stubs that reference signing keys accessible through
a remote server (see `autopen signing-key remote` and
`autopen serve`).
# Inputs
`name`
: The name to use for the signing key derivation.
`socketPath` (optional, default: `"/run/autopen/socket"`)
: The path to the autopen servers Unix socket inside the Nix
build environment.
`verificationKey`
: The path to the verification key file corresponding to the
signing key.
`pos` (optional)
: The position for the signing key derivation.
`meta` (optional)
: The metadata for the signing key derivation.
# Type
```
remote ::
{
name :: String,
socketPath :: String,
verificationKey :: VerificationKey,
pos :: { file :: String, line :: Int } | Null,
meta :: AttrSet,
} -> SigningKey
```
*/
remote =
{
name,
socketPath ? "/run/autopen/socket",
verificationKey,
pos ? unsafeGetAttrPos "name" args,
meta ? { },
}@args:
let
verificationKey = fakeDerivation "${args.verificationKey}" {
name = "${name}-verification-key";
meta = meta // {
${if meta ? description then "description" else null} = "${meta.description} (verification key)";
${if pos != null then "position" else null} = "${pos.file}:${toString pos.line}";
};
};
# We need a store path to use as a file reference for the
# remote key.
#
# Ideally, this would be an unforgeable capability
# passed down from the builders autopen configuration. The
# best we can do with Nix as it exists is to create a store
# path that is stable and unique for a given remote key
# configuration.
#
# The fundamental property this ensures is that derivations
# that dont include this store path in their buildtime
# closure cannot use the corresponding key. This preserves the
# fundamental guarantees of the Nix model, and lets us expose
# signing capabilities inside the build sandbox without letting
# arbitrary packages with compromised upstreams sign anything.
#
# We cant stop derivations that shouldnt have
# access to this from reconstructing it independently for a
# given key configuration, but this and the `drvPath` hole can
# be detected by CI. In any case, this is more a matter of
# defenceindepth; the capability model of autopen is not
# intended as a strong protection against a compromise of
# Nixpkgs itself.
#
# We use an empty directory here, as files can could be subject
# to a hardlinking attack via store optimization.
fileRefPath = builtins.path {
path = ./.;
name = "${name}-key-handle-${hashString "sha256" "${verificationKey}"}";
filter = _: _: false;
};
in
mkAutopenDerivation {
name = "${name}-signing-key";
autopenArgs = [
"signing-key"
"remote"
"create"
{
inherit socketPath fileRefPath verificationKey;
output = placeholder "out";
}
];
allowedRequisites = [
"out"
fileRefPath
];
passthru = {
inherit socketPath fileRefPath verificationKey;
};
inherit pos meta;
};
}

View File

@@ -0,0 +1,138 @@
{
lib,
autopen,
}:
let
inherit (lib)
extendMkDerivation
unsafeGetAttrPos
;
inherit (autopen.lib)
sign
;
inherit (autopen.lib.internal)
mkAutopenDerivation
;
inherit (autopen.lib.x509)
attachSignature
mkTbsCertificateForSelfSigning
;
in
{
mkTbsCertificateForSelfSigning = extendMkDerivation {
constructDrv = mkAutopenDerivation;
extendDrvArgs =
finalAttrs:
{
name,
passthru ? { },
pos ? unsafeGetAttrPos "name" args,
...
}@args:
{
name = "${name}.tbs-certificate.der";
autopenArgs = [
"x509"
"create-tbs-certificate"
finalAttrs.certificateParams
{ output = placeholder "out"; }
];
passthru = {
inherit (finalAttrs.certificateParams) verificationKey;
}
// passthru;
inherit pos;
};
};
attachSignature = extendMkDerivation {
constructDrv = mkAutopenDerivation;
extendDrvArgs =
finalAttrs:
{
name,
signature,
passthru ? { },
pos ? unsafeGetAttrPos "name" args,
...
}@args:
let
tbsCertificate = signature.message;
in
{
name = "${name}.cer";
autopenArgs = [
"x509"
"create-certificate"
tbsCertificate.certificateParams
{
inherit signature;
output = placeholder "out";
}
];
passthru = {
inherit (tbsCertificate) verificationKey certificateParams;
inherit tbsCertificate signature;
}
// passthru;
inherit pos;
};
};
mkSelfSignedCertificate = extendMkDerivation {
constructDrv = attachSignature;
# Ensure that the signing key doesnt leak from the derivation.
excludeDrvArgNames = [ "signingKey" ];
extendDrvArgs =
finalAttrs:
{
name,
signingKey,
certificateParams,
pos ? unsafeGetAttrPos "name" args,
meta ? { },
...
}@args:
{
signature = sign {
inherit signingKey;
message = mkTbsCertificateForSelfSigning {
inherit name;
certificateParams = certificateParams // {
inherit (signingKey) verificationKey;
};
inherit pos;
meta = meta // {
${if meta ? description then "description" else null} = "${meta.description} (to be signed)";
};
};
inherit pos;
meta = meta // {
${if meta ? description then "description" else null} = "${meta.description} (signature)";
};
};
inherit pos meta;
};
};
}

View File

@@ -0,0 +1,62 @@
{
lib,
callPackage,
stdenv,
rustPlatform,
fetchFromGitHub,
capnproto,
autopen,
}:
rustPlatform.buildRustPackage (finalAttrs: {
pname = "autopen";
version = "0.2.0";
src = fetchFromGitHub {
owner = "emilazy";
repo = "autopen";
tag = "v${finalAttrs.version}";
hash = "sha256-7/uAwpNNQByKAjrpr6R40dkVWg5t15bqLD4KJNT53wI=";
};
cargoHash = "sha256-baV0suVBCgV/gFnAD5uo6fpfQg/CYreyFgsiEGTmH3A=";
nativeBuildInputs = [
capnproto
];
useNextest = true;
cargoTestFlags = [ "--max-fail=all" ];
strictDeps = true;
__structuredAttrs = true;
passthru = {
lib = callPackage ./lib { };
mkTest = callPackage ./tests { };
testSigningKey = autopen.lib.signingKey.import {
name = "autopen-test-rsa3072-pkcs1-sha256";
path = ./tests/test-rsa3072-pkcs1-sha256-signing-key.bin;
};
tests = {
softwareKey = autopen.mkTest {
signingKey = autopen.testSigningKey;
};
};
};
meta = {
description = "Cryptographic signing tool with an objectcapability interface";
homepage = "https://github.com/emilazy/autopen";
license = lib.licenses.blueOak100;
sourceProvenance = [ lib.sourceTypes.fromSource ];
teams = [ lib.teams.boot-security ];
mainProgram = "autopen";
platforms = lib.platforms.unix;
};
})

View File

@@ -0,0 +1,45 @@
{
writeText,
autopen,
linkFarm,
runCommand,
}:
{
signingKey,
}:
let
message = writeText "autopen-test-message" ''
squeamish ossifrage
'';
signature = autopen.lib.sign {
inherit signingKey message;
};
in
linkFarm "autopen-test" {
inherit signingKey;
inherit (signingKey) verificationKey;
inherit message signature;
signature-check =
runCommand "autopen-test-signature-check"
{
nativeBuildInputs = [
autopen
];
inherit (signingKey) verificationKey;
inherit signature message;
strictDeps = true;
__structuredAttrs = true;
}
''
autopen verify \
--verification-key="$verificationKey" \
--signature="$signature" \
-- "$message"
touch -- "$out"
'';
}