Skip to content
Open
2 changes: 1 addition & 1 deletion src/libexpr/primops/wasm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ struct NixWasmInstance
while (!args.empty()) {
auto arg = &getValue(args[0]);
auto tmp = state.allocValue();
tmp->mkApp(res, {arg});
tmp->mkApp(res, arg);
res = tmp;
args = args.subspan(1);
}
Expand Down
4 changes: 2 additions & 2 deletions src/libstore/binary-cache-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ BinaryCacheStore::BinaryCacheStore(Config & config)
: config{config}
{
if (!config.secretKeyFile.get().empty())
signers.push_back(std::make_unique<LocalSigner>(SecretKey{readFile(config.secretKeyFile.get())}));
signers.push_back(std::make_unique<LocalSigner>(SecretKey::parse(readFile(config.secretKeyFile.get()))));

if (config.secretKeyFiles != "") {
std::stringstream ss(config.secretKeyFiles);
std::string keyPath;
while (std::getline(ss, keyPath, ',')) {
signers.push_back(std::make_unique<LocalSigner>(SecretKey{readFile(keyPath)}));
signers.push_back(std::make_unique<LocalSigner>(SecretKey::parse(readFile(keyPath))));
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/libstore/keys.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ PublicKeys getDefaultPublicKeys()
// FIXME: filter duplicates

for (const auto & s : settings.trustedPublicKeys.get()) {
PublicKey key(s);
publicKeys.emplace(key.name, key);
auto key = PublicKey::parse(s);
auto name = key->name;
publicKeys.emplace(name, std::move(key));
}

// FIXME: keep secret keys in memory (see Store::signRealisation()).
for (const auto & secretKeyFile : settings.secretKeyFiles.get()) {
try {
SecretKey secretKey(readFile(secretKeyFile));
publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
auto secretKey = SecretKey::parse(readFile(secretKeyFile));
publicKeys.emplace(secretKey->name, secretKey->toPublicKey());
} catch (SystemError & e) {
/* Ignore unreadable key files. That's normal in a
multi-user installation. */
Expand Down
6 changes: 2 additions & 4 deletions src/libstore/store-api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1269,8 +1269,7 @@ void Store::signPathInfo(ValidPathInfo & info)
auto secretKeyFiles = settings.secretKeyFiles;

for (auto & secretKeyFile : secretKeyFiles.get()) {
SecretKey secretKey(readFile(secretKeyFile));
LocalSigner signer(std::move(secretKey));
LocalSigner signer(SecretKey::parse(readFile(secretKeyFile)));
info.sign(*this, signer);
}
}
Expand All @@ -1282,8 +1281,7 @@ void Store::signRealisation(Realisation & realisation)
auto secretKeyFiles = settings.secretKeyFiles;

for (auto & secretKeyFile : secretKeyFiles.get()) {
SecretKey secretKey(readFile(secretKeyFile));
LocalSigner signer(std::move(secretKey));
LocalSigner signer(SecretKey::parse(readFile(secretKeyFile)));
realisation.sign(realisation.id, signer);
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/libutil/experimental-features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct ExperimentalFeatureDetails
* feature, we either have no issue at all if few features are not added
* at the end of the list, or a proper merge conflict if they are.
*/
constexpr size_t numXpFeatures = 1 + static_cast<size_t>(Xp::Provenance);
constexpr size_t numXpFeatures = 1 + static_cast<size_t>(Xp::MLDSA);

constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails = {{
{
Expand Down Expand Up @@ -305,6 +305,14 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
)",
.trackingUrl = "",
},
{
.tag = Xp::MLDSA,
.name = "ml-dsa",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were to add RSA and ECDSA support too, would those be separate features? I may implement that next and am wondering, since some people may simply want the new quantum resistant algorithms without P-{256,384,521} or RSA, so should we have a prefix indicating that we have a family of options for signature algorithms here? Maybe sig-ml-dsa so we can also have a sig-ecdsa and sig-rsa?

FWIW I'd also like to add an OpenSSL implementation of Ed25519 for related reasons, there are cases where OpenSSL may be more suitable than libsodium but where the keys and signatures would be compatible.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can use the same experimental feature flag.

I wouldn't mind switching to OpenSSL for Ed25519 if it generates compatible signatures, since it would allow us to drop the libsodium dependency.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a more generic feature flag for enabled OpenSSL algorithms, then?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, any algorithm OpenSSL supports can also be pkcs11-ified using pkcs11-provider if you have a URL to the key. The wisdom I'd share there is that you just can't assume you can export the key as a pkcs8 if it's backed by a hardware keystore, but it's mostly the same underlying OpenSSL objects as of OpenSSL 3.

.description = R"(
Enable support for ML-DSA keys and signatures.
)",
.trackingUrl = "",
},
}};

static_assert(
Expand Down
1 change: 1 addition & 0 deletions src/libutil/include/nix/util/experimental-features.hh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ enum struct ExperimentalFeature {
WasmBuiltin,
WasmDerivations,
Provenance,
MLDSA,
};

extern std::set<std::string> stabilizedFeatures;
Expand Down
69 changes: 40 additions & 29 deletions src/libutil/include/nix/util/signature/local-keys.hh
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,27 @@ struct Signature
auto operator<=>(const Signature &) const = default;
};

enum KeyType {
Ed25519,
MLDSA44,
MLDSA65,
MLDSA87,
};

KeyType parseKeyType(std::string_view s);

const StringSet & getKeyTypes();

// FIXME: remove this class.
struct Key
{
std::string name;
std::string key;
const std::string name;
const std::string key;

std::string to_string() const;

protected:

/**
* Construct Key from a string in the format
* ‘<name>:<key-in-base64>’.
*
* @param sensitiveValue Avoid displaying the raw Base64 in error
* messages to avoid leaking private keys.
*/
Key(std::string_view s, bool sensitiveValue);

Key(std::string_view name, std::string && key)
: name(name)
, key(std::move(key))
Expand All @@ -70,27 +73,35 @@ struct PublicKey;

struct SecretKey : Key
{
SecretKey(std::string_view s);
using Key::Key;

virtual ~SecretKey() {};

static std::unique_ptr<SecretKey> parse(std::string_view s);

/**
* Return a detached signature of the given string.
*/
Signature signDetached(std::string_view s) const;
virtual Signature signDetached(std::string_view s) const;

PublicKey toPublicKey() const;
virtual std::unique_ptr<PublicKey> toPublicKey() const;

static SecretKey generate(std::string_view name);
/**
* Return a PEM PKCS#8 encoding of this secret key. The Nix-specific
* key name is not included. Only ML-DSA keys are supported.
*/
virtual std::string toPEM() const;

private:
SecretKey(std::string_view name, std::string && key)
: Key(name, std::move(key))
{
}
static std::unique_ptr<SecretKey> generate(std::string_view name, KeyType type);
};

struct PublicKey : Key
{
PublicKey(std::string_view data);
using Key::Key;

virtual ~PublicKey() {};

static std::unique_ptr<PublicKey> parse(std::string_view s);

/**
* @return true iff `sig` and this key's names match, and `sig` is a
Expand All @@ -104,20 +115,20 @@ struct PublicKey : Key
*
* @param sig the raw signature bytes (not Base64 encoded).
*/
bool verifyDetachedAnon(std::string_view data, const Signature & sig) const;
virtual bool verifyDetachedAnon(std::string_view data, const Signature & sig) const;

private:
PublicKey(std::string_view name, std::string && key)
: Key(name, std::move(key))
{
}
friend struct SecretKey;
/**
* Return a PEM SubjectPublicKeyInfo encoding of this public key.
* The Nix-specific key name is not included. Only ML-DSA keys are
* supported.
*/
virtual std::string toPEM() const;
};

/**
* Map from key names to public keys
*/
typedef std::map<std::string, PublicKey> PublicKeys;
typedef std::map<std::string, std::unique_ptr<PublicKey>> PublicKeys;

/**
* @return true iff ‘sig’ is a correct signature over ‘data’ using one
Expand Down
6 changes: 3 additions & 3 deletions src/libutil/include/nix/util/signature/signer.hh
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ using Signers = std::map<std::string, Signer *>;
*/
struct LocalSigner : Signer
{
LocalSigner(SecretKey && privateKey);
LocalSigner(std::unique_ptr<SecretKey> && privateKey);

Signature signDetached(std::string_view s) const override;

const PublicKey & getPublicKey() override;

private:

SecretKey privateKey;
PublicKey publicKey;
const std::unique_ptr<SecretKey> privateKey;
const std::unique_ptr<PublicKey> publicKey;
};

} // namespace nix
Loading
Loading