diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e4dbf255..8020c7ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,17 +11,17 @@ jobs: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 steps: - name: Set up .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 6.0.x - 7.0.x + 8.0.x - run: dotnet --info - - uses: actions/checkout@v3 - - name: Test (.NET 7.0/Debug) - run: dotnet test tests -f net7.0 -c Debug - - name: Test (.NET 7.0/Release) - run: dotnet test tests -f net7.0 -c Release + - uses: actions/checkout@v4 + - name: Test (.NET 8.0/Debug) + run: dotnet test tests -f net8.0 -c Debug + - name: Test (.NET 8.0/Release) + run: dotnet test tests -f net8.0 -c Release - name: Test (.NET 6.0/Debug) run: dotnet test tests -f net6.0 -c Debug - name: Test (.NET 6.0/Release) @@ -29,7 +29,7 @@ jobs: - name: Pack run: dotnet pack -c Release - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: '**/*.nupkg' @@ -39,26 +39,26 @@ jobs: fail-fast: false matrix: include: - - os: macos-11 - os: macos-12 - os: macos-13 + - os: macos-14 runs-on: ${{ matrix.os }} env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 steps: - name: Set up .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 6.0.x - 7.0.x + 8.0.x - run: dotnet --info - - uses: actions/checkout@v3 - - name: Test (.NET 7.0/Debug) - run: dotnet test tests -f net7.0 -c Debug - - name: Test (.NET 7.0/Release) - run: dotnet test tests -f net7.0 -c Release + - uses: actions/checkout@v4 + - name: Test (.NET 8.0/Debug) + run: dotnet test tests -f net8.0 -c Debug + - name: Test (.NET 8.0/Release) + run: dotnet test tests -f net8.0 -c Release - name: Test (.NET 6.0/Debug) run: dotnet test tests -f net6.0 -c Debug - name: Test (.NET 6.0/Release) @@ -70,15 +70,15 @@ jobs: fail-fast: false matrix: include: - - os: centos:7 - os: debian:10 - os: debian:11 - - os: fedora:37 + - os: debian:12 - os: fedora:38 - - os: ubuntu:16.04 - - os: ubuntu:18.04 + - os: fedora:39 + - os: fedora:40 - os: ubuntu:20.04 - os: ubuntu:22.04 + - os: ubuntu:24.04 runs-on: ubuntu-latest container: image: ${{ matrix.os }} @@ -99,17 +99,17 @@ jobs: run: apt-get -qq update && apt-get -qq install --yes --no-install-recommends curl ca-certificates gettext if: ${{ startsWith(matrix.os, 'ubuntu') }} - name: Set up .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 6.0.x - 7.0.x + 8.0.x - run: dotnet --info - - uses: actions/checkout@v3 - - name: Test (.NET 7.0/Debug) - run: dotnet test tests -f net7.0 -c Debug - - name: Test (.NET 7.0/Release) - run: dotnet test tests -f net7.0 -c Release + - uses: actions/checkout@v4 + - name: Test (.NET 8.0/Debug) + run: dotnet test tests -f net8.0 -c Debug + - name: Test (.NET 8.0/Release) + run: dotnet test tests -f net8.0 -c Release - name: Test (.NET 6.0/Debug) run: dotnet test tests -f net6.0 -c Debug - name: Test (.NET 6.0/Release) @@ -120,17 +120,18 @@ jobs: fail-fast: false matrix: include: - - os: alpine3.17 + - os: alpine3.18 + - os: alpine3.19 runs-on: ubuntu-latest container: - image: mcr.microsoft.com/dotnet/sdk:7.0-${{ matrix.os }} + image: mcr.microsoft.com/dotnet/sdk:8.0-${{ matrix.os }} env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 steps: - run: dotnet --info - - uses: actions/checkout@v3 - - name: Test (.NET 7.0/Debug) - run: dotnet test tests -f net7.0 -c Debug - - name: Test (.NET 7.0/Release) - run: dotnet test tests -f net7.0 -c Release + - uses: actions/checkout@v4 + - name: Test (.NET 8.0/Debug) + run: dotnet test tests -f net8.0 -c Debug + - name: Test (.NET 8.0/Release) + run: dotnet test tests -f net8.0 -c Release diff --git a/LICENSE b/LICENSE index f53d1507..33f80545 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Klaus Hartke +Copyright (c) 2024 Klaus Hartke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NOTICE b/NOTICE index d5ed078d..054451aa 100644 --- a/NOTICE +++ b/NOTICE @@ -4,7 +4,7 @@ License Notice for libsodium This software is based on and contains code derived from libsodium, which is available under the ISC license. -Copyright (c) 2013-2023 Frank Denis +Copyright (c) 2013-2024 Frank Denis Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/NSec.sln b/NSec.sln index 937219c0..90d2b87c 100644 --- a/NSec.sln +++ b/NSec.sln @@ -62,6 +62,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{F058E702-CA9 docs\api\nsec.cryptography.hashalgorithm.md = docs\api\nsec.cryptography.hashalgorithm.md docs\api\nsec.cryptography.incrementalhash.md = docs\api\nsec.cryptography.incrementalhash.md docs\api\nsec.cryptography.incrementalmac.md = docs\api\nsec.cryptography.incrementalmac.md + docs\api\nsec.cryptography.incrementalsignature.md = docs\api\nsec.cryptography.incrementalsignature.md + docs\api\nsec.cryptography.incrementalsignatureverification.md = docs\api\nsec.cryptography.incrementalsignatureverification.md docs\api\nsec.cryptography.key.md = docs\api\nsec.cryptography.key.md docs\api\nsec.cryptography.keyagreementalgorithm.md = docs\api\nsec.cryptography.keyagreementalgorithm.md docs\api\nsec.cryptography.keyblobformat.md = docs\api\nsec.cryptography.keyblobformat.md @@ -78,6 +80,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{F058E702-CA9 docs\api\nsec.cryptography.sharedsecretblobformat.md = docs\api\nsec.cryptography.sharedsecretblobformat.md docs\api\nsec.cryptography.sharedsecretcreationparameters.md = docs\api\nsec.cryptography.sharedsecretcreationparameters.md docs\api\nsec.cryptography.signaturealgorithm.md = docs\api\nsec.cryptography.signaturealgorithm.md + docs\api\nsec.cryptography.signaturealgorithm2.md = docs\api\nsec.cryptography.signaturealgorithm2.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{030978CE-A451-4F42-887C-1B200DF7211C}" diff --git a/README.md b/README.md index 53fb1eac..8bc90aea 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # NSec -[![Maintenance](https://img.shields.io/maintenance/yes/2023)](https://github.com/ektrah/nsec) +[![Maintenance](https://img.shields.io/maintenance/yes/2024)](https://github.com/ektrah/nsec) [![License](https://img.shields.io/github/license/ektrah/nsec)](https://nsec.rocks/license) -[![NuGet](https://img.shields.io/nuget/vpre/NSec.Cryptography)](https://www.nuget.org/packages/NSec.Cryptography/23.5.0-preview.1) +[![NuGet](https://img.shields.io/nuget/vpre/NSec.Cryptography)](https://www.nuget.org/packages/NSec.Cryptography/24.4.0) [NSec](https://nsec.rocks/) is a cryptographic library for [.NET 6+](https://dotnet.microsoft.com/) based on diff --git a/build/Package.props b/build/Package.props index ca6179cc..e6dcde00 100644 --- a/build/Package.props +++ b/build/Package.props @@ -6,8 +6,8 @@ - - $([System.DateTime]::UtcNow.ToString(y.M.0))-preview.2-$(BuildNumberMajor)-$(BuildNumberMinor) + + $([System.DateTime]::UtcNow.ToString(y.M.0))-preview.1-$(BuildNumberMajor)-$(BuildNumberMinor) diff --git a/docs/api/nsec.cryptography.aeadalgorithm.md b/docs/api/nsec.cryptography.aeadalgorithm.md index 479e31ab..40b79076 100644 --- a/docs/api/nsec.cryptography.aeadalgorithm.md +++ b/docs/api/nsec.cryptography.aeadalgorithm.md @@ -9,6 +9,8 @@ Represents an authenticated encryption with associated data (AEAD) algorithm. * [[Algorithm|Algorithm Class]] * **AeadAlgorithm** + * Aegis128L + * Aegis256 * Aes256Gcm * ChaCha20Poly1305 * XChaCha20Poly1305 @@ -20,6 +22,20 @@ Represents an authenticated encryption with associated data (AEAD) algorithm. ## Static Properties +### Aegis128L + +Gets the AEGIS-128L AEAD algorithm. + + public static Aegis128L Aegis128L { get; } + + +### Aegis256 + +Gets the AEGIS-256 AEAD algorithm. + + public static Aegis256 Aegis256 { get; } + + ### Aes256Gcm Gets the AES256-GCM AEAD algorithm. @@ -33,10 +49,9 @@ PlatformNotSupportedException #### Remarks -The implementation of AES-GCM in NSec is hardware-accelerated and requires an -x64 processor with the AES-NI extension. The availability of this extension can -be determined at runtime using the static `IsSupported` property of the -`NSec.Cryptography.Aes256Gcm` class. +The AES-GCM implementation in NSec is hardware-accelerated and may not be +available on all architectures. Support can be determined at runtime using +the static `IsSupported` property of the `NSec.Cryptography.Aes256Gcm` class. ### ChaCha20Poly1305 @@ -121,8 +136,9 @@ nonce catastrophic loss of security. : To prevent nonce reuse when encrypting multiple plaintexts with the same key, - it is recommended to increment the previous nonce; a randomly generated - nonce is not suitable. + it is recommended to increment the previous nonce. A randomly generated + nonce is unsafe unless the [[nonce size|AeadAlgorithm Class#NonceSize]] + is at least 24 bytes. associatedData : Optional additional data to be authenticated during decryption. @@ -184,8 +200,9 @@ nonce catastrophic loss of security. : To prevent nonce reuse when encrypting multiple plaintexts with the same key, - it is recommended to increment the previous nonce; a randomly generated - nonce is not suitable. + it is recommended to increment the previous nonce. A randomly generated + nonce is unsafe unless the [[nonce size|AeadAlgorithm Class#NonceSize]] + is at least 24 bytes. associatedData : Optional additional data to be authenticated during decryption. diff --git a/docs/api/nsec.cryptography.algorithm.md b/docs/api/nsec.cryptography.algorithm.md index e7e7eff4..96a076bc 100644 --- a/docs/api/nsec.cryptography.algorithm.md +++ b/docs/api/nsec.cryptography.algorithm.md @@ -11,8 +11,11 @@ This class has no public members. * **Algorithm** * [[AeadAlgorithm|AeadAlgorithm Class]] + * Aegis128L + * Aegis256 * Aes256Gcm * ChaCha20Poly1305 + * XChaCha20Poly1305 * [[HashAlgorithm|HashAlgorithm Class]] * Blake2b * Sha256 diff --git a/docs/api/nsec.cryptography.incrementalsignature.md b/docs/api/nsec.cryptography.incrementalsignature.md new file mode 100644 index 00000000..1ab1be68 --- /dev/null +++ b/docs/api/nsec.cryptography.incrementalsignature.md @@ -0,0 +1,163 @@ +# IncrementalSignature Struct + +Represents the state of a signature algorithm that can be incrementally updated with +segments of data. + + public readonly struct IncrementalSignature + +The type provides an "init, update, final" interface for computing a signature: +First, a state needs to be initialized with the signing key to be used. The state can +then be updated zero or more times with segments of data. Finalizing the state +yields a result that is identical to the signature of the concatenated segments. + +!!! Note + [[IncrementalSignature|IncrementalSignature Struct]] instances have + value-type semantics: Passing an instance to a method or assigning it to a + variable creates a copy of the state. It is therefore recommended to always + pass instances using `ref`, `in`, or `out`. + + +## Example + +The following C# example shows how to compute a signature from multiple segments of +data: + + {{Incremental Signing}} + + +## [TOC] Summary + + +## Properties + + +### Algorithm + +Gets the algorithm that was used to initialize the state. + + public SignatureAlgorithm2? Algorithm { get; } + +#### Property Value + +An instance of the [[SignatureAlgorithm2|SignatureAlgorithm2 Class]] class, or `null` if the +current instance has not been initialized yet or if it has been finalized. + + +## Static Methods + + +### Initialize(Key, out IncrementalSignature) + +Initializes the [[IncrementalSignature|IncrementalSignature Struct]] state with the +specified private key. + + public static void Initialize( + Key privateKey, + out IncrementalSignature state) + +#### Parameters + +privateKey +: The private key to use for computing the signature. + +state +: When this method returns, contains the initialized state. + +#### Exceptions + +ArgumentNullException +: `privateKey` is `null`. + +ArgumentException +: `privateKey.Algorithm` is not an instance of the + [[SignatureAlgorithm2|SignatureAlgorithm2 Class]] class. + + +### Update(ref IncrementalSignature, ReadOnlySpan) + +Updates the [[IncrementalSignature|IncrementalSignature Struct]] state with the specified +span of bytes. + + public static void Update( + ref IncrementalSignature state, + ReadOnlySpan data) + +#### Parameters + +state +: The state to be updated with `data`. + +data +: A segment of the data to sign. + +#### Exceptions + +InvalidOperationException +: `state` has not been initialized yet or has already been finalized. + + +### Finalize(ref IncrementalSignature) + +Completes the signature computation and returns the result as an array of bytes. + + public static byte[] Finalize( + ref IncrementalSignature state) + +#### Parameters + +state +: The state to be finalized. + +#### Return Value + +The computed signature. + +#### Exceptions + +InvalidOperationException +: `state` has not been initialized yet or has already been finalized. + + +### Finalize(ref IncrementalSignature, Span) + +Completes the signature computation and fills the specified span of bytes with the +result. + + public static void Finalize( + ref IncrementalSignature state, + Span signature) + +#### Parameters + +state +: The state to be finalized. + +signature +: The span to fill with the computed signature. + +#### Exceptions + +InvalidOperationException +: `state` has not been initialized yet or has already been finalized. + +ArgumentException +: `signature.Length` is not equal to [[SignatureSize|SignatureAlgorithm2 Class#SignatureSize]]. + +ObjectDisposedException +: The private key used to initialize the state has been disposed. + + +## Thread Safety + +Any public static members of this type are thread safe. Any instance members are +not guaranteed to be thread safe. As with any other type, reading and writing to +a shared variable that contains an instance of this type must be protected by a +lock to guarantee thread safety. + + +## See Also + +* API Reference + * [[IncrementalSignatureVerification Struct]] + * [[Key Class]] + * [[SignatureAlgorithm2 Class]] diff --git a/docs/api/nsec.cryptography.incrementalsignatureverification.md b/docs/api/nsec.cryptography.incrementalsignatureverification.md new file mode 100644 index 00000000..b24db4f3 --- /dev/null +++ b/docs/api/nsec.cryptography.incrementalsignatureverification.md @@ -0,0 +1,140 @@ +# IncrementalSignatureVerification Struct + +Represents the state of a signature verification algorithm that can be incrementally updated with +segments of data. + + public readonly struct IncrementalSignatureVerification + +The type provides an "init, update, final" interface for verifying data given a +public key and a signature. First, a state needs to be initialized with the +public key. The state can then be updated zero or more times with segments of +data. Finalizing the state gives a result as to whether verification of the +concatenated segments was successful. + +!!! Note + [[IncrementalSignatureVerification|IncrementalSignatureVerification Struct]] + instances have value-type semantics: Passing an instance to a method or + assigning it to a variable creates a copy of the state. It is therefore + recommended to always pass instances using `ref`, `in`, or `out`. + + +## Example + +The following C# example shows how to verify multiple segments of data given a +public key and a signature: + + {{Incremental Signature Verification}} + + +## [TOC] Summary + + +## Properties + + +### Algorithm + +Gets the algorithm that was used to initialize the state. + + public SignatureAlgorithm2? Algorithm { get; } + +#### Property Value + +An instance of the [[SignatureAlgorithm2|SignatureAlgorithm2 Class]] class, or `null` if the +current instance has not been initialized yet or if it has been finalized. + + +## Static Methods + + +### Initialize(PublicKey, out IncrementalSignatureVerification) + +Initializes the +[[IncrementalSignatureVerification|IncrementalSignatureVerification Struct]] +state with the specified public key. + + public static void Initialize( + PublicKey publicKey, + out IncrementalSignatureVerification state) + +#### Parameters + +publicKey +: The public key to use for verifying the data. + +state +: When this method returns, contains the initialized state. + +#### Exceptions + +ArgumentNullException +: `publicKey` is `null`. + +ArgumentException +: `publicKey.Algorithm` is not an instance of the + [[SignatureAlgorithm2|SignatureAlgorithm2 Class]] class. + + +### Update(ref IncrementalSignatureVerification, ReadOnlySpan) + +Updates the [[IncrementalSignatureVerification|IncrementalSignatureVerification Struct]] state with the specified +span of bytes. + + public static void Update( + ref IncrementalSignatureVerification state, + ReadOnlySpan data) + +#### Parameters + +state +: The state to be updated with `data`. + +data +: A segment of the data to verify. + +#### Exceptions + +InvalidOperationException +: `state` has not been initialized yet or has already been finalized. + + +### FinalizeAndVerify(ref IncrementalSignatureVerification, ReadOnlySpan) + +Completes the verification. + + public static bool FinalizeAndVerify( + ref IncrementalSignatureVerification state, + ReadOnlySpan signature) + +#### Parameters + +state +: The state to be finalized. + +signature +: The signature of the data to verify. + +#### Return Value + +`true` if verification succeeds; otherwise, `false`. + +#### Exceptions + +InvalidOperationException +: `state` has not been initialized yet or has already been finalized. + + +## Thread Safety + +Any public static members of this type are thread safe. Any instance members are +not guaranteed to be thread safe. As with any other type, reading and writing to +a shared variable that contains an instance of this type must be protected by a +lock to guarantee thread safety. + + +## See Also + +* API Reference + * [[IncrementalSignature Struct]] + * [[PublicKey Class]] + * [[SignatureAlgorithm2 Class]] diff --git a/docs/api/nsec.cryptography.md b/docs/api/nsec.cryptography.md index 3279a4ee..7d30471f 100644 --- a/docs/api/nsec.cryptography.md +++ b/docs/api/nsec.cryptography.md @@ -2,6 +2,8 @@ * [[Algorithm Class]] * [[AeadAlgorithm Class]] + * Aegis128L Class + * Aegis256 Class * Aes256Gcm Class * ChaCha20Poly1305 Class * XChaCha20Poly1305 Class @@ -26,8 +28,12 @@ * [[ScryptParameters Struct]] * [[SignatureAlgorithm Class]] * Ed25519 Class + * [[SignatureAlgorithm2 Class]] + * Ed25519ph Class * [[IncrementalHash Struct]] * [[IncrementalMac Struct]] +* [[IncrementalSignature Struct]] +* [[IncrementalSignatureVerification Struct]] * [[Key Class]] * [[KeyCreationParameters Struct]] * [[KeyExportPolicies Enum]] diff --git a/docs/api/nsec.cryptography.signaturealgorithm.md b/docs/api/nsec.cryptography.signaturealgorithm.md index f0463174..12dbfe8a 100644 --- a/docs/api/nsec.cryptography.signaturealgorithm.md +++ b/docs/api/nsec.cryptography.signaturealgorithm.md @@ -10,6 +10,8 @@ Represents a digital signature algorithm. * [[Algorithm|Algorithm Class]] * **SignatureAlgorithm** * Ed25519 + * [[SignatureAlgorithm2|SignatureAlgorithm2 Class]] + * Ed25519ph ## [TOC] Summary @@ -25,6 +27,19 @@ Gets the Ed25519 signature algorithm. public static Ed25519 Ed25519 { get; } +### Ed25519ph + +Gets the Ed25519ph signature algorithm. + + public static Ed25519ph Ed25519ph { get; } + +!!! Note + Ed25519ph incorporates a pre-hashing step to support [[incremental + signing|IncrementalSignature Struct]]. As a result, the outputs of Ed25519 + and Ed25519ph are not the same. For most cryptographic applications, it is + recommended to use Ed25519 due to its broader adoption. + + ## Properties @@ -189,3 +204,4 @@ All methods yield the same result for the same arguments. * [[Algorithm Class]] * [[Key Class]] * [[PublicKey Class]] + * [[SignatureAlgorithm2 Class]] diff --git a/docs/api/nsec.cryptography.signaturealgorithm2.md b/docs/api/nsec.cryptography.signaturealgorithm2.md new file mode 100644 index 00000000..fc210163 --- /dev/null +++ b/docs/api/nsec.cryptography.signaturealgorithm2.md @@ -0,0 +1,202 @@ +# SignatureAlgorithm2 Class + +Represents a digital signature algorithm that supports [[incremental +signing|IncrementalSignature Struct]] and [[incremental signature +verification|IncrementalSignatureVerification Struct]]. + + public abstract class SignatureAlgorithm2 : SignatureAlgorithm + + +## Inheritance Hierarchy + +* [[Algorithm|Algorithm Class]] + * [[SignatureAlgorithm|SignatureAlgorithm Class]] + * Ed25519 + * **SignatureAlgorithm2** + * Ed25519ph + + +## [TOC] Summary + + +## Static Properties + + +### Ed25519ph + +Gets the Ed25519ph signature algorithm. + + public static Ed25519ph Ed25519ph { get; } + +!!! Note + Ed25519ph incorporates a pre-hashing step to support [[incremental + signing|IncrementalSignature Struct]]. As a result, the outputs of Ed25519 + and Ed25519ph are not the same. For most cryptographic applications, it is + recommended to use Ed25519 due to its broader adoption. + + +## Properties + + +### PrivateKeySize + +Gets the size of private keys. + + public int PrivateKeySize { get; } + +#### Property Value + +The private key size, in bytes. + + +### PublicKeySize + +Gets the size of public keys. + + public int PublicKeySize { get; } + +#### Property Value + +The public key size, in bytes. + + +### SignatureSize + +Gets the size of a signature. + + public int SignatureSize { get; } + +#### Property Value + +The signature size, in bytes. + + +## Methods + + +### Sign(Key, ReadOnlySpan) + +Signs the specified input data using the specified key and returns the signature +as an array of bytes. + + public byte[] Sign( + Key key, + ReadOnlySpan data) + +#### Parameters + +key +: The [[Key|Key Class]] to use for signing. + +data +: The data to sign. + +#### Return Value + +The signature for the data. + +#### Exceptions + +ArgumentNullException +: `key` is `null`. + +ArgumentException +: `key.Algorithm` is not the same object as the current + [[SignatureAlgorithm|SignatureAlgorithm Class]] object. + +ObjectDisposedException +: `key` has been disposed. + + +### Sign(Key, ReadOnlySpan, Span) + +Signs the specified input data using the specified key and fills the specified +span of bytes with the signature. + + public void Sign( + Key key, + ReadOnlySpan data, + Span signature) + +#### Parameters + +key +: The [[Key|Key Class]] to use for signing. + +data +: The data to sign. + +signature +: The span to fill with the signature for the data. + +#### Exceptions + +ArgumentNullException +: `key` is `null`. + +ArgumentException +: `key.Algorithm` is not the same object as the current + [[SignatureAlgorithm|SignatureAlgorithm Class]] object. + +ArgumentException +: `signature.Length` is not equal to + [[SignatureSize|SignatureAlgorithm Class#SignatureSize]]. + +ObjectDisposedException +: `key` has been disposed. + + +### Verify(PublicKey, ReadOnlySpan, ReadOnlySpan) + +Verifies specified input data using the specified public key and signature. + + public bool Verify( + PublicKey publicKey, + ReadOnlySpan data, + ReadOnlySpan signature) + +#### Parameters + +publicKey +: The [[PublicKey|PublicKey Class]] to use for verification. + Verification fails if this is not the public key for the private key used + when signing the data. + +data +: The data to verify. + Verification fails if the integrity of the data was compromised. + +signature +: The signature of the data to verify. + +#### Return Value + +`true` if verification succeeds; otherwise, `false`. + +#### Exceptions + +ArgumentNullException +: `publicKey` is `null`. + +ArgumentException +: `publicKey.Algorithm` is not the same object as the current + [[SignatureAlgorithm|SignatureAlgorithm Class]] object. + + +## Thread Safety + +All members of this type are thread safe. + + +## Purity + +All methods yield the same result for the same arguments. + + +## See Also + +* API Reference + * [[Algorithm Class]] + * [[Key Class]] + * [[PublicKey Class]] + * [[SignatureAlgorithm Class]] diff --git a/docs/install.md b/docs/install.md index e3d9d90f..a4267f83 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,27 +1,15 @@ # Installation -NSec is available as -[a NuGet package from nuget.org](https://www.nuget.org/packages/NSec.Cryptography/23.5.0-preview.1). -It can be added to a project in a number of ways, depending on the project type -and tools used: +Use the following command to install the +[NSec.Cryptography NuGet package](https://www.nuget.org/packages/NSec.Cryptography/24.4.0): - -#### dotnet CLI - - $ dotnet add package NSec.Cryptography --version 23.5.0-preview.1 - -#### Visual Studio - - PM> Install-Package NSec.Cryptography -Version 23.5.0-preview.1 - -#### .csproj - - + $ dotnet add package NSec.Cryptography --version 24.4.0 ## Supported Platforms -NSec is intended to run on +[NSec 24.4.0](https://www.nuget.org/packages/NSec.Cryptography/24.4.0) +is intended to run on all [supported versions of .NET](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core) on the following platforms: @@ -34,38 +22,56 @@ on the following platforms: | **`ios-`** | | | | | | **`android-`** | | | | | -Specifically, -[NSec 23.5.0-preview.1](https://www.nuget.org/packages/NSec.Cryptography/23.5.0-preview.1) + +Please note: + +1. For Windows, the + [Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022](https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist) + is required. This is part of the .NET SDK but might not be present on a + clean Windows installation. + +2. The AES-GCM implementation in NSec is hardware-accelerated and may not be + available on all architectures. Support can be determined at runtime using + the static `IsSupported` property of the `NSec.Cryptography.Aes256Gcm` class. + + +## Tested Platforms + +[NSec 24.4.0](https://www.nuget.org/packages/NSec.Cryptography/24.4.0) has been tested to run on the following platforms and .NET versions: -| OS | Version | Architectures | .NET | -|:-------------------- |:-------- |:------------- |:--------------- | -| Windows 11 | 22H2 | x64 | 7.0.5 / 6.0.16 | -| Windows Server | 2022 | x64 | 7.0.5 / 6.0.16 | -| | | | | -| macOS | 11.7 | x64 | 7.0.5 / 6.0.16 | -| | 12.6 | x64 | 7.0.5 / 6.0.16 | -| | 13.3 | x64 | 7.0.5 / 6.0.16 | -| | | | | -| Alpine | 3.17 | x64 | 7.0.4 | -| CentOS | 7.9.2009 | x64 | 7.0.5 / 6.0.16 | -| Debian | 10.13 | x64 | 7.0.5 / 6.0.16 | -| | 11.7 | x64 | 7.0.5 / 6.0.16 | -| Fedora | 37 | x64 | 7.0.5 / 6.0.16 | -| | 38 | x64 | 7.0.5 / 6.0.16 | -| Ubuntu | 16.04 | x64 | 7.0.5 / 6.0.16 | -| | 18.04 | x64 | 7.0.5 / 6.0.16 | -| | 20.04 | x64 | 7.0.5 / 6.0.16 | -| | 22.04 | x64 | 7.0.5 / 6.0.16 | - -Other, similar platforms supported by .NET should work as well but have not been tested. - -Using NSec on Windows requires the -[Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads). -This dependency is included in the .NET SDK but might -not be present, for example, when deploying a self-contained application. - -The implementation of AES-GCM in NSec is hardware-accelerated and requires an -x64 processor with the AES-NI extension. The availability of this extension can -be determined at runtime using the static `IsSupported` property of the -`NSec.Cryptography.Aes256Gcm` class. +| OS | Version | Architecture | .NET | +|:-------------------- |:-------- |:------------- |:-------------- | +| Windows 11 | 23H2 | x64 | 8.0.4 / 6.0.29 | +| Windows Server | 2022 | x64 | 8.0.4 / 6.0.29 | +| | | | | +| macOS | 12.7 | x64 | 8.0.4 / 6.0.29 | +| | 13.6 | x64 | 8.0.4 / 6.0.29 | +| | 14.4 | arm64 | 8.0.4 / 6.0.29 | +| | | | | +| Alpine Linux | 3.18 | x64 | 8.0.4 | +| | 3.19 | x64 | 8.0.4 | +| Debian | 10 | x64 | 8.0.4 / 6.0.29 | +| | 11 | x64 | 8.0.4 / 6.0.29 | +| | 12 | x64 | 8.0.4 / 6.0.29 | +| Fedora | 38 | x64 | 8.0.4 / 6.0.29 | +| | 39 | x64 | 8.0.4 / 6.0.29 | +| | 40 | x64 | 8.0.4 / 6.0.29 | +| Ubuntu | 20.04 | x64 | 8.0.4 / 6.0.29 | +| | 22.04 | x64 | 8.0.4 / 6.0.29 | +| | 24.04 | x64 | 8.0.4 / 6.0.29 | + +The other supported platforms should work as well, but haven't been tested. + + +## Frequently Asked Questions + +Below are some frequently asked questions: + +**Q**: What causes a *System.DllNotFoundException: Unable to load shared +library 'libsodium' or one of its dependencies.* when using the +NSec.Cryptography NuGet package? +**A**: This exception can occur if the operating system or architecture is not +supported, or if the Visual C++ Redistributable has not been installed on a +Windows system. Please refer to the [Supported Platforms](#supported-platforms) +section above. diff --git a/index.md b/index.md index 0e7cef50..004a8a34 100644 --- a/index.md +++ b/index.md @@ -42,7 +42,7 @@ and verify the signature: ## Installation - $ dotnet add package NSec.Cryptography --version 23.5.0-preview.1 + $ dotnet add package NSec.Cryptography --version 24.4.0 NSec works with .NET 6 and later on Windows, Linux and macOS. @@ -53,21 +53,24 @@ See [[Installation]] for more details. | Class | Algorithms | |:----------------------------------------------- |:------------------------- | -| [[AeadAlgorithm Class]] | AES256-GCM | +| [[AeadAlgorithm Class]] | AEGIS-128L | +| | AEGIS-256 | +| | AES256-GCM | | | ChaCha20-Poly1305 | | | XChaCha20-Poly1305 | -| [[HashAlgorithm Class]] | BLAKE2b *-- unkeyed* | +| [[HashAlgorithm Class]] | BLAKE2b *(unkeyed)* | | | SHA-256 | | | SHA-512 | | [[KeyAgreementAlgorithm Class]] | X25519 | | [[KeyDerivationAlgorithm2 Class]] | HKDF-SHA-256 | | | HKDF-SHA-512 | -| [[MacAlgorithm Class]] | BLAKE2b *-- keyed* | +| [[MacAlgorithm Class]] | BLAKE2b *(keyed)* | | | HMAC-SHA-256 | | | HMAC-SHA-512 | | [[PasswordBasedKeyDerivationAlgorithm Class]] | Argon2id | | | scrypt | | [[SignatureAlgorithm Class]] | Ed25519 | +| [[SignatureAlgorithm2 Class]] | Ed25519ph | See [[API Reference|NSec.Cryptography Namespace]] for detailed information. diff --git a/notice.md b/notice.md index f86f4892..c6e16a22 100644 --- a/notice.md +++ b/notice.md @@ -12,7 +12,7 @@ permission except when reproducing the unmodified NSec documentation. [NSec](https://github.com/ektrah/nsec) is licensed under the MIT license. -Copyright © 2023 Klaus Hartke +Copyright © 2024 Klaus Hartke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -39,7 +39,7 @@ NSec is based on and contains code derived from [libsodium](https://github.com/jedisct1/libsodium), which is available under the ISC license. -Copyright © 2013-2023 Frank Denis +Copyright © 2013-2024 Frank Denis Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/src/Cryptography/AeadAlgorithm.cs b/src/Cryptography/AeadAlgorithm.cs index 03fcde95..b4eccde3 100644 --- a/src/Cryptography/AeadAlgorithm.cs +++ b/src/Cryptography/AeadAlgorithm.cs @@ -23,6 +23,8 @@ namespace NSec.Cryptography // public abstract class AeadAlgorithm : Algorithm { + private static Aegis128L? s_Aegis128L; + private static Aegis256? s_Aegis256; private static Aes256Gcm? s_Aes256Gcm; private static ChaCha20Poly1305? s_ChaCha20Poly1305; private static XChaCha20Poly1305? s_XChaCha20Poly1305; @@ -37,7 +39,7 @@ private protected AeadAlgorithm( int tagSize) { Debug.Assert(keySize > 0); - Debug.Assert(nonceSize >= 0 && nonceSize <= 24); + Debug.Assert(nonceSize >= 0 && nonceSize <= 32); Debug.Assert(tagSize >= 0 && tagSize <= 255); _keySize = keySize; @@ -45,6 +47,34 @@ private protected AeadAlgorithm( _tagSize = tagSize; } + public static Aegis128L Aegis128L + { + get + { + Aegis128L? instance = s_Aegis128L; + if (instance == null) + { + Interlocked.CompareExchange(ref s_Aegis128L, new Aegis128L(), null); + instance = s_Aegis128L; + } + return instance; + } + } + + public static Aegis256 Aegis256 + { + get + { + Aegis256? instance = s_Aegis256; + if (instance == null) + { + Interlocked.CompareExchange(ref s_Aegis256, new Aegis256(), null); + instance = s_Aegis256; + } + return instance; + } + } + public static Aes256Gcm Aes256Gcm { get diff --git a/src/Cryptography/Aegis128L.cs b/src/Cryptography/Aegis128L.cs new file mode 100644 index 00000000..fbeeed96 --- /dev/null +++ b/src/Cryptography/Aegis128L.cs @@ -0,0 +1,179 @@ +using System; +using System.Diagnostics; +using System.Threading; +using NSec.Cryptography.Formatting; +using static Interop.Libsodium; + +namespace NSec.Cryptography +{ + // + // AEGIS-128L + // + // The AEGIS-128L authenticated encryption with associated data (AEAD) + // algorithm + // + // References: + // + // draft-irtf-cfrg-aegis-aead-04 - The AEGIS Family of Authenticated + // Encryption Algorithms + // + // RFC 5116 - An Interface and Algorithms for Authenticated Encryption + // + // Parameters: + // + // Key Size - 16 bytes. + // + // Nonce Size - 16 bytes. + // + // Tag Size - 32 bytes. + // + // Plaintext Size - Between 0 and 2^61-1 bytes. (A Span can hold + // only up to 2^31-1 bytes.) + // + // Associated Data Size - Between 0 and 2^64-1 bytes. + // + // Ciphertext Size - The ciphertext always has the size of the + // plaintext plus the tag size. + // + public sealed class Aegis128L : AeadAlgorithm + { + private const uint NSecBlobHeader = 0xDE614ADE; + + private static int s_selfTest; + + public Aegis128L() : base( + keySize: crypto_aead_aegis128l_KEYBYTES, + nonceSize: crypto_aead_aegis128l_NPUBBYTES, + tagSize: crypto_aead_aegis128l_ABYTES) + { + if (s_selfTest == 0) + { + SelfTest(); + Interlocked.Exchange(ref s_selfTest, 1); + } + } + + internal override void CreateKey( + ReadOnlySpan seed, + out SecureMemoryHandle keyHandle, + out PublicKey? publicKey) + { + Debug.Assert(seed.Length == crypto_aead_aegis128l_KEYBYTES); + + publicKey = null; + keyHandle = SecureMemoryHandle.CreateFrom(seed); + } + + private protected unsafe override void EncryptCore( + SecureMemoryHandle keyHandle, + ReadOnlySpan nonce, + ReadOnlySpan associatedData, + ReadOnlySpan plaintext, + Span ciphertext) + { + Debug.Assert(keyHandle.Size == crypto_aead_aegis128l_KEYBYTES); + Debug.Assert(nonce.Length == crypto_aead_aegis128l_NPUBBYTES); + Debug.Assert(ciphertext.Length == plaintext.Length + crypto_aead_aegis128l_ABYTES); + + fixed (byte* c = ciphertext) + fixed (byte* m = plaintext) + fixed (byte* ad = associatedData) + fixed (byte* n = nonce) + { + int error = crypto_aead_aegis128l_encrypt( + c, + out ulong clen_p, + m, + (ulong)plaintext.Length, + ad, + (ulong)associatedData.Length, + null, + n, + keyHandle); + + Debug.Assert(error == 0); + Debug.Assert((ulong)ciphertext.Length == clen_p); + } + } + + internal override int GetSeedSize() + { + return crypto_aead_aegis128l_KEYBYTES; + } + + private protected unsafe override bool DecryptCore( + SecureMemoryHandle keyHandle, + ReadOnlySpan nonce, + ReadOnlySpan associatedData, + ReadOnlySpan ciphertext, + Span plaintext) + { + Debug.Assert(keyHandle.Size == crypto_aead_aegis128l_KEYBYTES); + Debug.Assert(nonce.Length == crypto_aead_aegis128l_NPUBBYTES); + Debug.Assert(plaintext.Length == ciphertext.Length - crypto_aead_aegis128l_ABYTES); + + fixed (byte* m = plaintext) + fixed (byte* c = ciphertext) + fixed (byte* ad = associatedData) + fixed (byte* n = nonce) + { + int error = crypto_aead_aegis128l_decrypt( + m, + out ulong mlen_p, + null, + c, + (ulong)ciphertext.Length, + ad, + (ulong)associatedData.Length, + n, + keyHandle); + + // libsodium clears plaintext if decryption fails + + Debug.Assert(error != 0 || (ulong)plaintext.Length == mlen_p); + return error == 0; + } + } + + internal override bool TryExportKey( + SecureMemoryHandle keyHandle, + KeyBlobFormat format, + Span blob, + out int blobSize) + { + return format switch + { + KeyBlobFormat.RawSymmetricKey => RawKeyFormatter.TryExport(keyHandle, blob, out blobSize), + KeyBlobFormat.NSecSymmetricKey => NSecKeyFormatter.TryExport(NSecBlobHeader, crypto_aead_aegis128l_KEYBYTES, crypto_aead_aegis128l_ABYTES, keyHandle, blob, out blobSize), + _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()), + }; + } + + internal override bool TryImportKey( + ReadOnlySpan blob, + KeyBlobFormat format, + out SecureMemoryHandle? keyHandle, + out PublicKey? publicKey) + { + publicKey = null; + + return format switch + { + KeyBlobFormat.RawSymmetricKey => RawKeyFormatter.TryImport(crypto_aead_aegis128l_KEYBYTES, blob, out keyHandle), + KeyBlobFormat.NSecSymmetricKey => NSecKeyFormatter.TryImport(NSecBlobHeader, crypto_aead_aegis128l_KEYBYTES, crypto_aead_aegis128l_ABYTES, blob, out keyHandle), + _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()), + }; + } + + private static void SelfTest() + { + if ((crypto_aead_aegis128l_abytes() != crypto_aead_aegis128l_ABYTES) || + (crypto_aead_aegis128l_keybytes() != crypto_aead_aegis128l_KEYBYTES) || + (crypto_aead_aegis128l_npubbytes() != crypto_aead_aegis128l_NPUBBYTES) || + (crypto_aead_aegis128l_nsecbytes() != crypto_aead_aegis128l_NSECBYTES)) + { + throw Error.InvalidOperation_InitializationFailed(); + } + } + } +} diff --git a/src/Cryptography/Aegis256.cs b/src/Cryptography/Aegis256.cs new file mode 100644 index 00000000..1a8acfa8 --- /dev/null +++ b/src/Cryptography/Aegis256.cs @@ -0,0 +1,179 @@ +using System; +using System.Diagnostics; +using System.Threading; +using NSec.Cryptography.Formatting; +using static Interop.Libsodium; + +namespace NSec.Cryptography +{ + // + // AEGIS-256 + // + // The AEGIS-256 authenticated encryption with associated data (AEAD) + // algorithm + // + // References: + // + // draft-irtf-cfrg-aegis-aead-04 - The AEGIS Family of Authenticated + // Encryption Algorithms + // + // RFC 5116 - An Interface and Algorithms for Authenticated Encryption + // + // Parameters: + // + // Key Size - 32 bytes. + // + // Nonce Size - 32 bytes. + // + // Tag Size - 32 bytes. + // + // Plaintext Size - Between 0 and 2^61-1 bytes. (A Span can hold + // only up to 2^31-1 bytes.) + // + // Associated Data Size - Between 0 and 2^64-1 bytes. + // + // Ciphertext Size - The ciphertext always has the size of the + // plaintext plus the tag size. + // + public sealed class Aegis256 : AeadAlgorithm + { + private const uint NSecBlobHeader = 0xDE614BDE; + + private static int s_selfTest; + + public Aegis256() : base( + keySize: crypto_aead_aegis256_KEYBYTES, + nonceSize: crypto_aead_aegis256_NPUBBYTES, + tagSize: crypto_aead_aegis256_ABYTES) + { + if (s_selfTest == 0) + { + SelfTest(); + Interlocked.Exchange(ref s_selfTest, 1); + } + } + + internal override void CreateKey( + ReadOnlySpan seed, + out SecureMemoryHandle keyHandle, + out PublicKey? publicKey) + { + Debug.Assert(seed.Length == crypto_aead_aegis256_KEYBYTES); + + publicKey = null; + keyHandle = SecureMemoryHandle.CreateFrom(seed); + } + + private protected unsafe override void EncryptCore( + SecureMemoryHandle keyHandle, + ReadOnlySpan nonce, + ReadOnlySpan associatedData, + ReadOnlySpan plaintext, + Span ciphertext) + { + Debug.Assert(keyHandle.Size == crypto_aead_aegis256_KEYBYTES); + Debug.Assert(nonce.Length == crypto_aead_aegis256_NPUBBYTES); + Debug.Assert(ciphertext.Length == plaintext.Length + crypto_aead_aegis256_ABYTES); + + fixed (byte* c = ciphertext) + fixed (byte* m = plaintext) + fixed (byte* ad = associatedData) + fixed (byte* n = nonce) + { + int error = crypto_aead_aegis256_encrypt( + c, + out ulong clen_p, + m, + (ulong)plaintext.Length, + ad, + (ulong)associatedData.Length, + null, + n, + keyHandle); + + Debug.Assert(error == 0); + Debug.Assert((ulong)ciphertext.Length == clen_p); + } + } + + internal override int GetSeedSize() + { + return crypto_aead_aegis256_KEYBYTES; + } + + private protected unsafe override bool DecryptCore( + SecureMemoryHandle keyHandle, + ReadOnlySpan nonce, + ReadOnlySpan associatedData, + ReadOnlySpan ciphertext, + Span plaintext) + { + Debug.Assert(keyHandle.Size == crypto_aead_aegis256_KEYBYTES); + Debug.Assert(nonce.Length == crypto_aead_aegis256_NPUBBYTES); + Debug.Assert(plaintext.Length == ciphertext.Length - crypto_aead_aegis256_ABYTES); + + fixed (byte* m = plaintext) + fixed (byte* c = ciphertext) + fixed (byte* ad = associatedData) + fixed (byte* n = nonce) + { + int error = crypto_aead_aegis256_decrypt( + m, + out ulong mlen_p, + null, + c, + (ulong)ciphertext.Length, + ad, + (ulong)associatedData.Length, + n, + keyHandle); + + // libsodium clears plaintext if decryption fails + + Debug.Assert(error != 0 || (ulong)plaintext.Length == mlen_p); + return error == 0; + } + } + + internal override bool TryExportKey( + SecureMemoryHandle keyHandle, + KeyBlobFormat format, + Span blob, + out int blobSize) + { + return format switch + { + KeyBlobFormat.RawSymmetricKey => RawKeyFormatter.TryExport(keyHandle, blob, out blobSize), + KeyBlobFormat.NSecSymmetricKey => NSecKeyFormatter.TryExport(NSecBlobHeader, crypto_aead_aegis256_KEYBYTES, crypto_aead_aegis256_ABYTES, keyHandle, blob, out blobSize), + _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()), + }; + } + + internal override bool TryImportKey( + ReadOnlySpan blob, + KeyBlobFormat format, + out SecureMemoryHandle? keyHandle, + out PublicKey? publicKey) + { + publicKey = null; + + return format switch + { + KeyBlobFormat.RawSymmetricKey => RawKeyFormatter.TryImport(crypto_aead_aegis256_KEYBYTES, blob, out keyHandle), + KeyBlobFormat.NSecSymmetricKey => NSecKeyFormatter.TryImport(NSecBlobHeader, crypto_aead_aegis256_KEYBYTES, crypto_aead_aegis256_ABYTES, blob, out keyHandle), + _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()), + }; + } + + private static void SelfTest() + { + if ((crypto_aead_aegis256_abytes() != crypto_aead_aegis256_ABYTES) || + (crypto_aead_aegis256_keybytes() != crypto_aead_aegis256_KEYBYTES) || + (crypto_aead_aegis256_npubbytes() != crypto_aead_aegis256_NPUBBYTES) || + (crypto_aead_aegis256_nsecbytes() != crypto_aead_aegis256_NSECBYTES)) + { + throw Error.InvalidOperation_InitializationFailed(); + } + } + } +} diff --git a/src/Cryptography/Ed25519.cs b/src/Cryptography/Ed25519.cs index dc7fac40..795e713a 100644 --- a/src/Cryptography/Ed25519.cs +++ b/src/Cryptography/Ed25519.cs @@ -10,7 +10,8 @@ namespace NSec.Cryptography // // Ed25519 // - // Digital Signature Algorithm (EdDSA) based on the edwards25519 curve + // Digital Signature Algorithm based on the edwards25519 curve in + // pure mode (PureEdDSA) // // References: // diff --git a/src/Cryptography/Ed25519ph.cs b/src/Cryptography/Ed25519ph.cs new file mode 100644 index 00000000..2f3c4aa1 --- /dev/null +++ b/src/Cryptography/Ed25519ph.cs @@ -0,0 +1,296 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using NSec.Cryptography.Formatting; +using static Interop.Libsodium; + +namespace NSec.Cryptography +{ + // + // Ed25519ph + // + // Digital Signature Algorithm based on the edwards25519 curve in + // pre-hash mode (HashEdDSA) + // + // References: + // + // RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA) + // + // Parameters: + // + // Private Key Size - The private key is 32 bytes (256 bits). However, + // the libsodium representation of a private key is 64 bytes. We + // expose private keys as 32-byte byte strings and internally + // convert from/to the libsodium format as necessary. + // + // Public Key Size - 32 bytes. + // + // Signature Size - 64 bytes. + // + public sealed class Ed25519ph : SignatureAlgorithm2 + { + private static readonly PrivateKeyFormatter s_nsecPrivateKeyFormatter = new Ed25519PrivateKeyFormatter(new byte[] { 0xDE, 0x64, 0x48, 0xDE, crypto_sign_ed25519_SEEDBYTES, 0, crypto_sign_ed25519_BYTES, 0 }); + + private static readonly PublicKeyFormatter s_nsecPublicKeyFormatter = new Ed25519PublicKeyFormatter(new byte[] { 0xDE, 0x65, 0x48, 0xDE, crypto_sign_ed25519_PUBLICKEYBYTES, 0, crypto_sign_ed25519_BYTES, 0 }); + + private static readonly PrivateKeyFormatter s_rawPrivateKeyFormatter = new Ed25519PrivateKeyFormatter(Array.Empty()); + + private static readonly PublicKeyFormatter s_rawPublicKeyFormatter = new Ed25519PublicKeyFormatter(Array.Empty()); + + private static int s_selfTest; + + public Ed25519ph() : base( + privateKeySize: crypto_sign_ed25519_SEEDBYTES, + publicKeySize: crypto_sign_ed25519_PUBLICKEYBYTES, + signatureSize: crypto_sign_ed25519_BYTES) + { + if (s_selfTest == 0) + { + SelfTest(); + Interlocked.Exchange(ref s_selfTest, 1); + } + } + + internal override unsafe void CreateKey( + ReadOnlySpan seed, + out SecureMemoryHandle keyHandle, + out PublicKey? publicKey) + { + if (Unsafe.SizeOf() != crypto_sign_ed25519_PUBLICKEYBYTES) + { + throw Error.InvalidOperation_InternalError(); + } + + Debug.Assert(seed.Length == crypto_sign_ed25519_SEEDBYTES); + + publicKey = new PublicKey(this); + keyHandle = SecureMemoryHandle.Create(crypto_sign_ed25519_SECRETKEYBYTES); + + fixed (PublicKeyBytes* pk = publicKey) + fixed (byte* seed_ = seed) + { + int error = crypto_sign_ed25519_seed_keypair(pk, keyHandle, seed_); + + Debug.Assert(error == 0); + } + } + + internal override int GetSeedSize() + { + return crypto_sign_ed25519_SEEDBYTES; + } + + private protected unsafe override void SignCore( + SecureMemoryHandle keyHandle, + ReadOnlySpan data, + Span signature) + { + Debug.Assert(keyHandle.Size == crypto_sign_ed25519_SECRETKEYBYTES); + Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES); + + fixed (byte* sig = signature) + fixed (byte* m = data) + { + crypto_sign_ed25519ph_state state; + + crypto_sign_ed25519ph_init( + &state); + + crypto_sign_ed25519ph_update( + &state, + m, + (ulong)data.Length); + + int error = crypto_sign_ed25519ph_final_create( + &state, + sig, + out ulong signatureLength, + keyHandle); + + Debug.Assert(error == 0); + Debug.Assert((ulong)signature.Length == signatureLength); + } + } + + private protected unsafe override bool VerifyCore( + in PublicKeyBytes publicKeyBytes, + ReadOnlySpan data, + ReadOnlySpan signature) + { + if (Unsafe.SizeOf() != crypto_sign_ed25519_PUBLICKEYBYTES) + { + throw Error.InvalidOperation_InternalError(); + } + + Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES); + + fixed (byte* sig = signature) + fixed (byte* m = data) + fixed (PublicKeyBytes* pk = &publicKeyBytes) + { + crypto_sign_ed25519ph_state state; + + crypto_sign_ed25519ph_init( + &state); + + crypto_sign_ed25519ph_update( + &state, + m, + (ulong)data.Length); + + int error = crypto_sign_ed25519ph_final_verify( + &state, + sig, + pk); + + return error == 0; + } + } + + internal unsafe override void InitializeCore( + out IncrementalSignatureState state) + { + fixed (crypto_sign_ed25519ph_state* state_ = &state.ed25519ph) + { + int error = crypto_sign_ed25519ph_init(state_); + + Debug.Assert(error == 0); + } + } + + internal unsafe override void UpdateCore( + ref IncrementalSignatureState state, + ReadOnlySpan data) + { + fixed (crypto_sign_ed25519ph_state* state_ = &state.ed25519ph) + fixed (byte* @in = data) + { + int error = crypto_sign_ed25519ph_update( + state_, + @in, + (ulong)data.Length); + + Debug.Assert(error == 0); + } + } + + internal unsafe override void FinalSignCore( + ref IncrementalSignatureState state, + SecureMemoryHandle keyHandle, + Span signature) + { + Debug.Assert(keyHandle.Size == crypto_sign_ed25519_SECRETKEYBYTES); + Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES); + + fixed (crypto_sign_ed25519ph_state* state_ = &state.ed25519ph) + fixed (byte* sig = signature) + { + int error = crypto_sign_ed25519ph_final_create( + state_, + sig, + out ulong signatureLength, + keyHandle); + + Debug.Assert(error == 0); + Debug.Assert((ulong)signature.Length == signatureLength); + } + } + + internal unsafe override bool FinalVerifyCore( + ref IncrementalSignatureState state, + in PublicKeyBytes publicKeyBytes, + ReadOnlySpan signature) + { + if (Unsafe.SizeOf() != crypto_sign_ed25519_PUBLICKEYBYTES) + { + throw Error.InvalidOperation_InternalError(); + } + + Debug.Assert(signature.Length == crypto_sign_ed25519_BYTES); + + fixed (crypto_sign_ed25519ph_state* state_ = &state.ed25519ph) + fixed (byte* sig = signature) + fixed (PublicKeyBytes* pk = &publicKeyBytes) + { + int error = crypto_sign_ed25519ph_final_verify( + state_, + sig, + pk); + + return error == 0; + } + } + + internal override bool TryExportKey( + SecureMemoryHandle keyHandle, + KeyBlobFormat format, + Span blob, + out int blobSize) + { + return format switch + { + KeyBlobFormat.RawPrivateKey => s_rawPrivateKeyFormatter.TryExport(keyHandle, blob, out blobSize), + KeyBlobFormat.NSecPrivateKey => s_nsecPrivateKeyFormatter.TryExport(keyHandle, blob, out blobSize), + _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()), + }; + } + + internal override bool TryExportPublicKey( + PublicKey publicKey, + KeyBlobFormat format, + Span blob, + out int blobSize) + { + return format switch + { + KeyBlobFormat.RawPublicKey => s_rawPublicKeyFormatter.TryExport(in publicKey.GetPinnableReference(), blob, out blobSize), + KeyBlobFormat.NSecPublicKey => s_nsecPublicKeyFormatter.TryExport(in publicKey.GetPinnableReference(), blob, out blobSize), + _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()), + }; + } + + internal override bool TryImportKey( + ReadOnlySpan blob, + KeyBlobFormat format, + out SecureMemoryHandle? keyHandle, + out PublicKey? publicKey) + { + publicKey = new PublicKey(this); + + return format switch + { + KeyBlobFormat.RawPrivateKey => s_rawPrivateKeyFormatter.TryImport(blob, out keyHandle, out publicKey.GetPinnableReference()), + KeyBlobFormat.NSecPrivateKey => s_nsecPrivateKeyFormatter.TryImport(blob, out keyHandle, out publicKey.GetPinnableReference()), + _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()), + }; + } + + internal override bool TryImportPublicKey( + ReadOnlySpan blob, + KeyBlobFormat format, + out PublicKey publicKey) + { + publicKey = new PublicKey(this); + + return format switch + { + KeyBlobFormat.RawPublicKey => s_rawPublicKeyFormatter.TryImport(blob, out publicKey.GetPinnableReference()), + KeyBlobFormat.NSecPublicKey => s_nsecPublicKeyFormatter.TryImport(blob, out publicKey.GetPinnableReference()), + _ => throw Error.Argument_FormatNotSupported(nameof(format), format.ToString()), + }; + } + + private static void SelfTest() + { + if ((crypto_sign_ed25519_bytes() != crypto_sign_ed25519_BYTES) || + (crypto_sign_ed25519_publickeybytes() != crypto_sign_ed25519_PUBLICKEYBYTES) || + (crypto_sign_ed25519_secretkeybytes() != crypto_sign_ed25519_SECRETKEYBYTES) || + (crypto_sign_ed25519_seedbytes() != crypto_sign_ed25519_SEEDBYTES) || + (crypto_sign_ed25519ph_statebytes() != (nuint)Unsafe.SizeOf())) + { + throw Error.InvalidOperation_InitializationFailed(); + } + } + } +} diff --git a/src/Cryptography/Error.cs b/src/Cryptography/Error.cs index d988ac92..f12f3418 100644 --- a/src/Cryptography/Error.cs +++ b/src/Cryptography/Error.cs @@ -221,6 +221,12 @@ internal static ArgumentException Argument_SharedSecretLength( return new ArgumentException(string.Format(ResourceManager.GetString(nameof(Argument_SharedSecretLength))!, arg0), paramName); } + internal static ArgumentException Argument_SignatureKeyRequired( + string paramName) + { + return new ArgumentException(ResourceManager.GetString(nameof(Argument_SignatureKeyRequired)), paramName); + } + internal static ArgumentException Argument_SignatureLength( string paramName, object? arg0) diff --git a/src/Cryptography/Error.resx b/src/Cryptography/Error.resx index ad728a24..2b04a665 100644 --- a/src/Cryptography/Error.resx +++ b/src/Cryptography/Error.resx @@ -258,6 +258,9 @@ The length of the shared secret must be less than or equal to {0}. + + Cannot use a key for an incremental signature that is not a signature key. + The signature must have a length of {0} bytes. diff --git a/src/Cryptography/IncrementalSignature.cs b/src/Cryptography/IncrementalSignature.cs new file mode 100644 index 00000000..a4523aa7 --- /dev/null +++ b/src/Cryptography/IncrementalSignature.cs @@ -0,0 +1,127 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace NSec.Cryptography +{ + [StructLayout(LayoutKind.Sequential)] + public readonly struct IncrementalSignature + { + private readonly IncrementalSignatureState _state; + private readonly SignatureAlgorithm2? _algorithm; + private readonly Key? _privateKey; + + public SignatureAlgorithm2? Algorithm => _algorithm; + + [EditorBrowsable(EditorBrowsableState.Never)] + public static new bool Equals( + object? objA, + object? objB) + { + return object.Equals(objA, objB); + } + + public static void Initialize( + Key privateKey, + out IncrementalSignature state) + { + if (privateKey == null) + { + throw Error.ArgumentNull_Key(nameof(privateKey)); + } + if (privateKey.Algorithm is not SignatureAlgorithm2 algorithm) + { + throw Error.Argument_SignatureKeyRequired(nameof(privateKey)); + } + + state = default; + algorithm.InitializeCore(out Unsafe.AsRef(in state._state)); + Unsafe.AsRef(in state._algorithm) = algorithm; + Unsafe.AsRef(in state._privateKey) = privateKey; + } + + public static void Update( + ref IncrementalSignature state, + ReadOnlySpan data) + { + if (state._algorithm == null) + { + throw Error.InvalidOperation_UninitializedState(); + } + + state._algorithm.UpdateCore(ref Unsafe.AsRef(in state._state), data); + } + + public static byte[] Finalize( + ref IncrementalSignature state) + { + if (state._algorithm == null || state._privateKey == null) + { + throw Error.InvalidOperation_UninitializedState(); + } + + try + { + byte[] signature = new byte[state._algorithm.SignatureSize]; + state._algorithm.FinalSignCore(ref Unsafe.AsRef(in state._state), state._privateKey.Handle, signature); + return signature; + } + finally + { + Unsafe.AsRef(in state._algorithm) = null; + Unsafe.AsRef(in state._privateKey) = null; + } + } + + public static void Finalize( + ref IncrementalSignature state, + Span signature) + { + if (state._algorithm == null || state._privateKey == null) + { + throw Error.InvalidOperation_UninitializedState(); + } + if (signature.Length != state._algorithm.SignatureSize) + { + throw Error.Argument_SignatureLength(nameof(signature), state._algorithm.SignatureSize); + } + + try + { + state._algorithm.FinalSignCore(ref Unsafe.AsRef(in state._state), state._privateKey.Handle, signature); + } + finally + { + Unsafe.AsRef(in state._algorithm) = null; + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static new bool ReferenceEquals( + object? objA, + object? objB) + { + return object.ReferenceEquals(objA, objB); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals( + object? obj) + { + throw Error.NotSupported_Operation(); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + throw Error.NotSupported_Operation(); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + { + return typeof(IncrementalSignature).ToString(); + } + } +} diff --git a/src/Cryptography/IncrementalSignatureState.cs b/src/Cryptography/IncrementalSignatureState.cs new file mode 100644 index 00000000..b8eaf5d8 --- /dev/null +++ b/src/Cryptography/IncrementalSignatureState.cs @@ -0,0 +1,11 @@ +namespace NSec.Cryptography +{ + using System.Runtime.InteropServices; + + [StructLayout(LayoutKind.Explicit)] + internal struct IncrementalSignatureState + { + [FieldOffset(0)] + internal Interop.Libsodium.crypto_sign_ed25519ph_state ed25519ph; + } +} diff --git a/src/Cryptography/IncrementalSignatureVerification.cs b/src/Cryptography/IncrementalSignatureVerification.cs new file mode 100644 index 00000000..fec4597f --- /dev/null +++ b/src/Cryptography/IncrementalSignatureVerification.cs @@ -0,0 +1,104 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace NSec.Cryptography +{ + [StructLayout(LayoutKind.Sequential)] + public readonly struct IncrementalSignatureVerification + { + private readonly IncrementalSignatureState _state; + private readonly SignatureAlgorithm2? _algorithm; + private readonly PublicKey? _publicKey; + + public SignatureAlgorithm2? Algorithm => _algorithm; + + [EditorBrowsable(EditorBrowsableState.Never)] + public static new bool Equals( + object? objA, + object? objB) + { + return object.Equals(objA, objB); + } + + public static void Initialize( + PublicKey publicKey, + out IncrementalSignatureVerification state) + { + if (publicKey == null) + { + throw Error.ArgumentNull_Key(nameof(publicKey)); + } + if (publicKey.Algorithm is not SignatureAlgorithm2 algorithm) + { + throw Error.Argument_SignatureKeyRequired(nameof(publicKey)); + } + + state = default; + algorithm.InitializeCore(out Unsafe.AsRef(in state._state)); + Unsafe.AsRef(in state._algorithm) = algorithm; + Unsafe.AsRef(in state._publicKey) = publicKey; + } + + public static void Update( + ref IncrementalSignatureVerification state, + ReadOnlySpan data) + { + if (state._algorithm == null) + { + throw Error.InvalidOperation_UninitializedState(); + } + + state._algorithm.UpdateCore(ref Unsafe.AsRef(in state._state), data); + } + + public static bool FinalizeAndVerify( + ref IncrementalSignatureVerification state, + ReadOnlySpan signature) + { + if (state._algorithm == null || state._publicKey == null) + { + throw Error.InvalidOperation_UninitializedState(); + } + + try + { + return signature.Length == state._algorithm.SignatureSize && + state._algorithm.FinalVerifyCore(ref Unsafe.AsRef(in state._state), state._publicKey.GetPinnableReference(), signature); + } + finally + { + Unsafe.AsRef(in state._algorithm) = null; + Unsafe.AsRef(in state._publicKey) = null; + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static new bool ReferenceEquals( + object? objA, + object? objB) + { + return object.ReferenceEquals(objA, objB); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals( + object? obj) + { + throw Error.NotSupported_Operation(); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + throw Error.NotSupported_Operation(); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + { + return typeof(IncrementalSignatureVerification).ToString(); + } + } +} diff --git a/src/Cryptography/NSec.Cryptography.csproj b/src/Cryptography/NSec.Cryptography.csproj index 52138822..77bd7149 100644 --- a/src/Cryptography/NSec.Cryptography.csproj +++ b/src/Cryptography/NSec.Cryptography.csproj @@ -19,7 +19,7 @@ NSec.Cryptography.X25519 - + diff --git a/src/Cryptography/SignatureAlgorithm.cs b/src/Cryptography/SignatureAlgorithm.cs index 0bd0a583..967a05b8 100644 --- a/src/Cryptography/SignatureAlgorithm.cs +++ b/src/Cryptography/SignatureAlgorithm.cs @@ -20,6 +20,7 @@ namespace NSec.Cryptography public abstract class SignatureAlgorithm : Algorithm { private static Ed25519? s_Ed25519; + private static Ed25519ph? s_Ed25519ph; private readonly int _privateKeySize; private readonly int _publicKeySize; @@ -53,6 +54,20 @@ public static Ed25519 Ed25519 } } + public static Ed25519ph Ed25519ph + { + get + { + Ed25519ph? instance = s_Ed25519ph; + if (instance == null) + { + Interlocked.CompareExchange(ref s_Ed25519ph, new Ed25519ph(), null); + instance = s_Ed25519ph; + } + return instance; + } + } + public int PrivateKeySize => _privateKeySize; public int PublicKeySize => _publicKeySize; diff --git a/src/Cryptography/SignatureAlgorithm2.cs b/src/Cryptography/SignatureAlgorithm2.cs new file mode 100644 index 00000000..851aa2ce --- /dev/null +++ b/src/Cryptography/SignatureAlgorithm2.cs @@ -0,0 +1,45 @@ +using System; +using System.Diagnostics; +using System.Threading; +using static Interop.Libsodium; + +namespace NSec.Cryptography +{ + // + // A digital signature algorithm supporting the "init, update, final" interface + // + // Candidates + // + // | Algorithm | Reference | Key Size | Signature Size | + // | ----------- | ---------- | -------- | -------------- | + // | Ed25519ph | RFC 8032 | 32 | 64 | + // | Ed448ph | RFC 8032 | 57 | 114 | + // + public abstract class SignatureAlgorithm2 : SignatureAlgorithm + { + private protected SignatureAlgorithm2( + int privateKeySize, + int publicKeySize, + int signatureSize) + : base(privateKeySize, publicKeySize, signatureSize) + { + } + + internal abstract void InitializeCore( + out IncrementalSignatureState state); + + internal abstract void UpdateCore( + ref IncrementalSignatureState state, + ReadOnlySpan data); + + internal abstract void FinalSignCore( + ref IncrementalSignatureState state, + SecureMemoryHandle keyHandle, + Span signature); + + internal abstract bool FinalVerifyCore( + ref IncrementalSignatureState state, + in PublicKeyBytes publicKeyBytes, + ReadOnlySpan signature); + } +} diff --git a/src/Experimental/PasswordBased/PasswordBasedKeyExporter.cs b/src/Experimental/PasswordBased/PasswordBasedKeyExporter.cs index 40f964e2..520a9d49 100644 --- a/src/Experimental/PasswordBased/PasswordBasedKeyExporter.cs +++ b/src/Experimental/PasswordBased/PasswordBasedKeyExporter.cs @@ -189,6 +189,16 @@ private static void ReadEncryptionParameters( Read(ref reader, out nonce); break; + case Aegis128L _: + Read(ref reader, 0x2004); + Read(ref reader, out nonce); + break; + + case Aegis256 _: + Read(ref reader, 0x2005); + Read(ref reader, out nonce); + break; + default: throw new NotSupportedException(); } @@ -275,6 +285,16 @@ private static void WriteEncryptionParameters( Write(ref writer, nonce); break; + case Aegis128L _: + Write(ref writer, 0x2004); + Write(ref writer, nonce); + break; + + case Aegis256 _: + Write(ref writer, 0x2005); + Write(ref writer, nonce); + break; + default: throw new NotSupportedException(); } diff --git a/src/Interop/Interop.Aead.Aegis128L.cs b/src/Interop/Interop.Aead.Aegis128L.cs new file mode 100644 index 00000000..f19e9b33 --- /dev/null +++ b/src/Interop/Interop.Aead.Aegis128L.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Libsodium + { + internal const int crypto_aead_aegis128l_ABYTES = 32; + internal const int crypto_aead_aegis128l_KEYBYTES = 16; + internal const int crypto_aead_aegis128l_NPUBBYTES = 16; + internal const int crypto_aead_aegis128l_NSECBYTES = 0; + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static extern nuint crypto_aead_aegis128l_abytes(); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int crypto_aead_aegis128l_decrypt( + byte* m, + out ulong mlen_p, + byte* nsec, + byte* c, + ulong clen, + byte* ad, + ulong adlen, + byte* npub, + SecureMemoryHandle k); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int crypto_aead_aegis128l_encrypt( + byte* c, + out ulong clen_p, + byte* m, + ulong mlen, + byte* ad, + ulong adlen, + byte* nsec, + byte* npub, + SecureMemoryHandle k); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static extern nuint crypto_aead_aegis128l_keybytes(); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static extern nuint crypto_aead_aegis128l_npubbytes(); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static extern nuint crypto_aead_aegis128l_nsecbytes(); + } +} diff --git a/src/Interop/Interop.Aead.Aegis256.cs b/src/Interop/Interop.Aead.Aegis256.cs new file mode 100644 index 00000000..4b22d0bb --- /dev/null +++ b/src/Interop/Interop.Aead.Aegis256.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Libsodium + { + internal const int crypto_aead_aegis256_ABYTES = 32; + internal const int crypto_aead_aegis256_KEYBYTES = 32; + internal const int crypto_aead_aegis256_NPUBBYTES = 32; + internal const int crypto_aead_aegis256_NSECBYTES = 0; + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static extern nuint crypto_aead_aegis256_abytes(); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int crypto_aead_aegis256_decrypt( + byte* m, + out ulong mlen_p, + byte* nsec, + byte* c, + ulong clen, + byte* ad, + ulong adlen, + byte* npub, + SecureMemoryHandle k); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int crypto_aead_aegis256_encrypt( + byte* c, + out ulong clen_p, + byte* m, + ulong mlen, + byte* ad, + ulong adlen, + byte* nsec, + byte* npub, + SecureMemoryHandle k); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static extern nuint crypto_aead_aegis256_keybytes(); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static extern nuint crypto_aead_aegis256_npubbytes(); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static extern nuint crypto_aead_aegis256_nsecbytes(); + } +} diff --git a/src/Interop/Interop.Sign.Ed25519.cs b/src/Interop/Interop.Sign.Ed25519.cs index 4a5afca2..e576e250 100644 --- a/src/Interop/Interop.Sign.Ed25519.cs +++ b/src/Interop/Interop.Sign.Ed25519.cs @@ -57,5 +57,36 @@ internal static unsafe extern int crypto_sign_ed25519_verify_detached( byte* m, ulong mlen, PublicKeyBytes* pk); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int crypto_sign_ed25519ph_final_create( + crypto_sign_ed25519ph_state* state, + byte* sig, + out ulong siglen_p, + SecureMemoryHandle sk); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int crypto_sign_ed25519ph_final_verify( + crypto_sign_ed25519ph_state* state, + byte* sig, + PublicKeyBytes* pk); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int crypto_sign_ed25519ph_init( + crypto_sign_ed25519ph_state* state); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static extern nuint crypto_sign_ed25519ph_statebytes(); + + [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] + internal static unsafe extern int crypto_sign_ed25519ph_update( + crypto_sign_ed25519ph_state* state, + byte* m, + ulong mlen); + + [StructLayout(LayoutKind.Explicit, Size = 208)] + internal struct crypto_sign_ed25519ph_state + { + } } } diff --git a/src/Interop/Interop.Version.cs b/src/Interop/Interop.Version.cs index 143f420e..2bc5c7d3 100644 --- a/src/Interop/Interop.Version.cs +++ b/src/Interop/Interop.Version.cs @@ -5,9 +5,9 @@ internal static partial class Interop { internal static partial class Libsodium { - internal const int SODIUM_LIBRARY_VERSION_MAJOR = 10; - internal const int SODIUM_LIBRARY_VERSION_MINOR = 3; - internal const string SODIUM_VERSION_STRING = "1.0.18"; + internal const int SODIUM_LIBRARY_VERSION_MAJOR = 26; + internal const int SODIUM_LIBRARY_VERSION_MINOR = 1; + internal const string SODIUM_VERSION_STRING = "1.0.19"; [DllImport(Libraries.Libsodium, CallingConvention = CallingConvention.Cdecl)] internal static extern int sodium_library_version_major(); diff --git a/src/Interop/Interop.projitems b/src/Interop/Interop.projitems index 20eb0f2f..dad831ee 100644 --- a/src/Interop/Interop.projitems +++ b/src/Interop/Interop.projitems @@ -5,6 +5,8 @@ true + + diff --git a/src/Interop/Interop.yaml b/src/Interop/Interop.yaml index 5d04e90c..fd4f6148 100644 --- a/src/Interop/Interop.yaml +++ b/src/Interop/Interop.yaml @@ -6,6 +6,36 @@ Interop.Core.cs: - sodium_init - sodium_set_misuse_handler +Interop.Aead.Aegis128L.cs: + include: include/sodium/crypto_aead_aegis128l.h + constants: + - crypto_aead_aegis128l_ABYTES + - crypto_aead_aegis128l_KEYBYTES + - crypto_aead_aegis128l_NPUBBYTES + - crypto_aead_aegis128l_NSECBYTES + functions: + - crypto_aead_aegis128l_abytes + - crypto_aead_aegis128l_decrypt (out ulong mlen_p, SecureMemoryHandle k) + - crypto_aead_aegis128l_encrypt (out ulong clen_p, SecureMemoryHandle k) + - crypto_aead_aegis128l_keybytes + - crypto_aead_aegis128l_npubbytes + - crypto_aead_aegis128l_nsecbytes + +Interop.Aead.Aegis256.cs: + include: include/sodium/crypto_aead_aegis256.h + constants: + - crypto_aead_aegis256_ABYTES + - crypto_aead_aegis256_KEYBYTES + - crypto_aead_aegis256_NPUBBYTES + - crypto_aead_aegis256_NSECBYTES + functions: + - crypto_aead_aegis256_abytes + - crypto_aead_aegis256_decrypt (out ulong mlen_p, SecureMemoryHandle k) + - crypto_aead_aegis256_encrypt (out ulong clen_p, SecureMemoryHandle k) + - crypto_aead_aegis256_keybytes + - crypto_aead_aegis256_npubbytes + - crypto_aead_aegis256_nsecbytes + Interop.Aead.Aes256Gcm.cs: include: include/sodium/crypto_aead_aes256gcm.h constants: @@ -288,11 +318,18 @@ Interop.Sign.Ed25519.cs: - crypto_sign_ed25519_pk_to_curve25519 (PublicKeyBytes* curve25519_pk, PublicKeyBytes* ed25519_pk) - crypto_sign_ed25519_publickeybytes - crypto_sign_ed25519_secretkeybytes - - crypto_sign_ed25519_sk_to_curve25519 (SecureMemoryHandle ed25519_sk) - - crypto_sign_ed25519_sk_to_seed (SecureMemoryHandle sk) - crypto_sign_ed25519_seed_keypair (PublicKeyBytes* pk, SecureMemoryHandle sk) - crypto_sign_ed25519_seedbytes + - crypto_sign_ed25519_sk_to_curve25519 (SecureMemoryHandle ed25519_sk) + - crypto_sign_ed25519_sk_to_seed (SecureMemoryHandle sk) - crypto_sign_ed25519_verify_detached (PublicKeyBytes* pk) + - crypto_sign_ed25519ph_final_create (out ulong siglen_p, SecureMemoryHandle sk) + - crypto_sign_ed25519ph_final_verify (PublicKeyBytes* pk) + - crypto_sign_ed25519ph_init + - crypto_sign_ed25519ph_statebytes + - crypto_sign_ed25519ph_update + structs: + - crypto_sign_ed25519ph_state #Interop.Sign.Edwards25519Sha512Batch.cs: # include: include/sodium/crypto_sign_edwards25519sha512batch.h diff --git a/tests/Algorithms/Aegis128LTests.cs b/tests/Algorithms/Aegis128LTests.cs new file mode 100644 index 00000000..f199c06e --- /dev/null +++ b/tests/Algorithms/Aegis128LTests.cs @@ -0,0 +1,50 @@ +using System; +using NSec.Cryptography; +using Xunit; + +namespace NSec.Tests.Algorithms +{ + public static class Aegis128LTests + { + public static readonly TheoryData PlaintextLengths = Utilities.Primes; + + #region Properties + + [Fact] + public static void Properties() + { + var a = AeadAlgorithm.Aegis128L; + + Assert.Equal(16, a.KeySize); + Assert.Equal(16, a.NonceSize); + Assert.Equal(32, a.TagSize); + } + + #endregion + + #region Encrypt/Decrypt + + [Theory] + [MemberData(nameof(PlaintextLengths))] + public static void EncryptDecrypt(int length) + { + var a = AeadAlgorithm.Aegis128L; + + using var k = new Key(a); + var n = Utilities.RandomBytes.Slice(0, a.NonceSize); + var ad = Utilities.RandomBytes.Slice(0, 100); + + var expected = Utilities.RandomBytes.Slice(0, length).ToArray(); + + var ciphertext = a.Encrypt(k, n, ad, expected); + Assert.NotNull(ciphertext); + Assert.Equal(length + a.TagSize, ciphertext.Length); + + var actual = a.Decrypt(k, n, ad, ciphertext); + Assert.NotNull(actual); + Assert.Equal(expected, actual); + } + + #endregion + } +} diff --git a/tests/Algorithms/Aegis256Tests.cs b/tests/Algorithms/Aegis256Tests.cs new file mode 100644 index 00000000..1659fab8 --- /dev/null +++ b/tests/Algorithms/Aegis256Tests.cs @@ -0,0 +1,50 @@ +using System; +using NSec.Cryptography; +using Xunit; + +namespace NSec.Tests.Algorithms +{ + public static class Aegis256Tests + { + public static readonly TheoryData PlaintextLengths = Utilities.Primes; + + #region Properties + + [Fact] + public static void Properties() + { + var a = AeadAlgorithm.Aegis256; + + Assert.Equal(32, a.KeySize); + Assert.Equal(32, a.NonceSize); + Assert.Equal(32, a.TagSize); + } + + #endregion + + #region Encrypt/Decrypt + + [Theory] + [MemberData(nameof(PlaintextLengths))] + public static void EncryptDecrypt(int length) + { + var a = AeadAlgorithm.Aegis256; + + using var k = new Key(a); + var n = Utilities.RandomBytes.Slice(0, a.NonceSize); + var ad = Utilities.RandomBytes.Slice(0, 100); + + var expected = Utilities.RandomBytes.Slice(0, length).ToArray(); + + var ciphertext = a.Encrypt(k, n, ad, expected); + Assert.NotNull(ciphertext); + Assert.Equal(length + a.TagSize, ciphertext.Length); + + var actual = a.Decrypt(k, n, ad, ciphertext); + Assert.NotNull(actual); + Assert.Equal(expected, actual); + } + + #endregion + } +} diff --git a/tests/Base/AeadAlgorithmTests.cs b/tests/Base/AeadAlgorithmTests.cs index 7a724dda..7911cc17 100644 --- a/tests/Base/AeadAlgorithmTests.cs +++ b/tests/Base/AeadAlgorithmTests.cs @@ -17,7 +17,7 @@ public static class AeadAlgorithmTests public static void Properties(AeadAlgorithm a) { Assert.True(a.KeySize > 0); - Assert.InRange(a.NonceSize, 0, 24); + Assert.InRange(a.NonceSize, 0, 32); Assert.InRange(a.TagSize, 0, 255); } diff --git a/tests/Base/IncrementalSignatureTests.cs b/tests/Base/IncrementalSignatureTests.cs new file mode 100644 index 00000000..bf33da09 --- /dev/null +++ b/tests/Base/IncrementalSignatureTests.cs @@ -0,0 +1,156 @@ +using System; +using NSec.Cryptography; +using Xunit; + +namespace NSec.Tests.Base +{ + public static class IncrementalSignatureTests + { + public static readonly TheoryData IncrementalSignatureAlgorithms = Registry.IncrementalSignatureAlgorithms; + + #region Initialize + + [Fact] + public static void InitializeWithNullKey() + { + Assert.Throws("privateKey", () => IncrementalSignature.Initialize(null!, out _)); + } + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void InitializeWithWrongKey(SignatureAlgorithm2 a) + { + using var k = new Key(SignatureAlgorithm.Ed25519); + + Assert.Throws("privateKey", () => IncrementalSignature.Initialize(k, out var state)); + } + + #endregion + + #region Finalize #1 + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeInvalid(SignatureAlgorithm2 a) + { + var state = default(IncrementalSignature); + + Assert.Throws(() => IncrementalSignature.Finalize(ref state)); + } + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeSuccess(SignatureAlgorithm2 a) + { + using var k = new Key(a); + var state = default(IncrementalSignature); + + Assert.Null(state.Algorithm); + + IncrementalSignature.Initialize(k, out state); + + Assert.Same(a, state.Algorithm); + + IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(0, 100)); + IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(100, 100)); + IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(200, 100)); + + var actual = IncrementalSignature.Finalize(ref state); + + Assert.Null(state.Algorithm); + + var expected = a.Sign(k, Utilities.RandomBytes.Slice(0, 300)); + + Assert.Equal(expected, actual); + } + + #endregion + + #region Finalize #2 + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeWithSpanInvalid(SignatureAlgorithm2 a) + { + var state = default(IncrementalSignature); + + Assert.Throws(() => IncrementalSignature.Finalize(ref state, new byte[a.SignatureSize])); + } + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeWithSpanTooSmall(SignatureAlgorithm2 a) + { + using var k = new Key(a); + + IncrementalSignature.Initialize(k, out var state); + + Assert.Throws("signature", () => IncrementalSignature.Finalize(ref state, new byte[a.SignatureSize - 1])); + } + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeWithSpanTooLarge(SignatureAlgorithm2 a) + { + using var k = new Key(a); + + IncrementalSignature.Initialize(k, out var state); + + Assert.Throws("signature", () => IncrementalSignature.Finalize(ref state, new byte[a.SignatureSize + 1])); + } + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeWithSpanSuccess(SignatureAlgorithm2 a) + { + using var k = new Key(a); + var state = default(IncrementalSignature); + + Assert.Null(state.Algorithm); + + IncrementalSignature.Initialize(k, out state); + + Assert.Same(a, state.Algorithm); + + IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(0, 100)); + IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(100, 100)); + IncrementalSignature.Update(ref state, Utilities.RandomBytes.Slice(200, 100)); + + var actual = new byte[a.SignatureSize]; + + IncrementalSignature.Finalize(ref state, actual); + + Assert.Null(state.Algorithm); + + var expected = a.Sign(k, Utilities.RandomBytes.Slice(0, 300)); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeWithSpanSuccessNoUpdate(SignatureAlgorithm2 a) + { + using var k = new Key(a); + var state = default(IncrementalSignature); + + Assert.Null(state.Algorithm); + + IncrementalSignature.Initialize(k, out state); + + Assert.Same(a, state.Algorithm); + + var actual = new byte[a.SignatureSize]; + + IncrementalSignature.Finalize(ref state, actual); + + Assert.Null(state.Algorithm); + + var expected = a.Sign(k, ReadOnlySpan.Empty); + + Assert.Equal(expected, actual); + } + + #endregion + } +} diff --git a/tests/Base/IncrementalSignatureVerificationTests.cs b/tests/Base/IncrementalSignatureVerificationTests.cs new file mode 100644 index 00000000..f1bf7792 --- /dev/null +++ b/tests/Base/IncrementalSignatureVerificationTests.cs @@ -0,0 +1,69 @@ +using System; +using NSec.Cryptography; +using Xunit; + +namespace NSec.Tests.Base +{ + public static class IncrementalSignatureVerificationTests + { + public static readonly TheoryData IncrementalSignatureAlgorithms = Registry.IncrementalSignatureAlgorithms; + + #region Initialize + + [Fact] + public static void InitializeWithNullKey() + { + Assert.Throws("publicKey", () => IncrementalSignatureVerification.Initialize(null!, out _)); + } + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void InitializeWithWrongKey(SignatureAlgorithm2 a) + { + using var k = new Key(SignatureAlgorithm.Ed25519); + + Assert.Throws("publicKey", () => IncrementalSignatureVerification.Initialize(k.PublicKey, out var state)); + } + + #endregion + + #region FinalizeAndVerify + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeAndVerifyInvalid(SignatureAlgorithm2 a) + { + var state = default(IncrementalSignatureVerification); + + Assert.Throws(() => IncrementalSignatureVerification.FinalizeAndVerify(ref state, new byte[a.SignatureSize])); + } + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeAndVerifyFail(SignatureAlgorithm2 a) + { + using var k = new Key(a); + + IncrementalSignatureVerification.Initialize(k.PublicKey, out var state); + + Assert.False(IncrementalSignatureVerification.FinalizeAndVerify(ref state, new byte[a.SignatureSize])); + + Assert.Null(state.Algorithm); + } + + [Theory] + [MemberData(nameof(IncrementalSignatureAlgorithms))] + public static void FinalizeAndVerifySuccess(SignatureAlgorithm2 a) + { + using var k = new Key(a); + + IncrementalSignatureVerification.Initialize(k.PublicKey, out var state); + + Assert.True(IncrementalSignatureVerification.FinalizeAndVerify(ref state, a.Sign(k, ReadOnlySpan.Empty))); + + Assert.Null(state.Algorithm); + } + + #endregion + } +} diff --git a/tests/Examples/Incremental.cs b/tests/Examples/Incremental.cs index e798a492..549fa201 100644 --- a/tests/Examples/Incremental.cs +++ b/tests/Examples/Incremental.cs @@ -77,5 +77,90 @@ public static void Mac() Assert.Equal(algorithm.Mac(key, Encoding.UTF8.GetBytes(string.Concat(lines))), mac); } + + [Fact] + public static void Signature() + { + #region Incremental Signing + + // define some data to be signed + var lines = new[] + { + "Luke Skywalker has returned to\n", + "his home planet of Tatooine in\n", + "an attempt to rescue his\n", + "friend Han Solo from the\n", + "clutches of the vile gangster\n", + "Jabba the Hutt.\n", + }; + + // select the Ed25519ph algorithm + var algorithm = SignatureAlgorithm.Ed25519ph; + + // create a new key pair + using var key = Key.Create(algorithm); + + // initialize the state with the private key + IncrementalSignature.Initialize(key, out var state); + + // incrementally update the state with the data + foreach (var line in lines) + { + IncrementalSignature.Update(ref state, Encoding.UTF8.GetBytes(line)); + } + + // finalize the computation and get the result + var signature = IncrementalSignature.Finalize(ref state); + + #endregion + + Assert.Equal(algorithm.Sign(key, Encoding.UTF8.GetBytes(string.Concat(lines))), signature); + } + + [Fact] + public static void SignatureVerification() + { + using var key = Key.Create(SignatureAlgorithm.Ed25519ph); + + #region Incremental Signature Verification + + // define some data to be verified + var lines = new[] + { + "Luke Skywalker has returned to\n", + "his home planet of Tatooine in\n", + "an attempt to rescue his\n", + "friend Han Solo from the\n", + "clutches of the vile gangster\n", + "Jabba the Hutt.\n", + }; + + // select the Ed25519ph algorithm + var algorithm = SignatureAlgorithm.Ed25519ph; + + // obtain the public key + var publicKey = /*{*/key.PublicKey;/*}*/ + + // obtain the signature + var signature = /*{*/algorithm.Sign(key, Encoding.UTF8.GetBytes(string.Concat(lines)));/*}*/ + + // initialize the state with the public key + IncrementalSignatureVerification.Initialize(publicKey, out var state); + + // incrementally update the state with the data + foreach (var line in lines) + { + IncrementalSignatureVerification.Update(ref state, Encoding.UTF8.GetBytes(line)); + } + + // verify the data using the signature + if (IncrementalSignatureVerification.FinalizeAndVerify(ref state, signature)) + { + // verified! + /*{*//*}*/ + } + + #endregion + } } } diff --git a/tests/Formatting/NSecTests.cs b/tests/Formatting/NSecTests.cs index 12ae4be0..6d67a8f9 100644 --- a/tests/Formatting/NSecTests.cs +++ b/tests/Formatting/NSecTests.cs @@ -7,6 +7,8 @@ namespace NSec.Tests.Formatting public static class NSecTests { [Theory] + [InlineData(typeof(Aegis128L), new byte[] { 0xDE, 0x61, 0x4A, 0xDE })] + [InlineData(typeof(Aegis256), new byte[] { 0xDE, 0x61, 0x4B, 0xDE })] [InlineData(typeof(Aes256Gcm), new byte[] { 0xDE, 0x61, 0x44, 0xDE })] [InlineData(typeof(ChaCha20Poly1305), new byte[] { 0xDE, 0x61, 0x43, 0xDE })] public static void Aead(Type algorithmType, byte[] blobHeader) @@ -43,6 +45,22 @@ public static void Ed25519Public() Test(a, a.PrivateKeySize, KeyBlobFormat.RawPrivateKey, a.PublicKeySize, a.SignatureSize, KeyBlobFormat.NSecPublicKey, new byte[] { 0xDE, 0x65, 0x42, 0xDE }); } + [Fact] + public static void Ed25519phPrivate() + { + var a = SignatureAlgorithm.Ed25519ph; + + Test(a, a.PrivateKeySize, KeyBlobFormat.RawPrivateKey, a.PrivateKeySize, a.SignatureSize, KeyBlobFormat.NSecPrivateKey, new byte[] { 0xDE, 0x64, 0x48, 0xDE }); + } + + [Fact] + public static void Ed25519phPublic() + { + var a = SignatureAlgorithm.Ed25519ph; + + Test(a, a.PrivateKeySize, KeyBlobFormat.RawPrivateKey, a.PublicKeySize, a.SignatureSize, KeyBlobFormat.NSecPublicKey, new byte[] { 0xDE, 0x65, 0x48, 0xDE }); + } + [Fact] public static void X25519Private() { diff --git a/tests/Registry.cs b/tests/Registry.cs index 04bbbc52..8fe26fd5 100644 --- a/tests/Registry.cs +++ b/tests/Registry.cs @@ -12,6 +12,8 @@ internal static class Registry public static readonly TheoryData AeadAlgorithms = new() { + AeadAlgorithm.Aegis128L, + AeadAlgorithm.Aegis256, AeadAlgorithm.Aes256Gcm, AeadAlgorithm.ChaCha20Poly1305, AeadAlgorithm.XChaCha20Poly1305, @@ -80,6 +82,12 @@ internal static class Registry public static readonly TheoryData SignatureAlgorithms = new() { SignatureAlgorithm.Ed25519, + SignatureAlgorithm.Ed25519ph, + }; + + public static readonly TheoryData IncrementalSignatureAlgorithms = new() + { + SignatureAlgorithm.Ed25519ph, }; #endregion @@ -90,10 +98,13 @@ internal static class Registry { KeyAgreementAlgorithm.X25519, SignatureAlgorithm.Ed25519, + SignatureAlgorithm.Ed25519ph, }; public static readonly TheoryData SymmetricAlgorithms = new() { + AeadAlgorithm.Aegis128L, + AeadAlgorithm.Aegis256, AeadAlgorithm.Aes256Gcm, AeadAlgorithm.ChaCha20Poly1305, AeadAlgorithm.XChaCha20Poly1305, @@ -140,6 +151,8 @@ internal static class Registry { SignatureAlgorithm.Ed25519, KeyBlobFormat.NSecPublicKey }, { SignatureAlgorithm.Ed25519, KeyBlobFormat.PkixPublicKey }, { SignatureAlgorithm.Ed25519, KeyBlobFormat.PkixPublicKeyText }, + { SignatureAlgorithm.Ed25519ph, KeyBlobFormat.RawPublicKey }, + { SignatureAlgorithm.Ed25519ph, KeyBlobFormat.NSecPublicKey }, }; public static readonly TheoryData PrivateKeyBlobFormats = new() @@ -152,10 +165,16 @@ internal static class Registry { SignatureAlgorithm.Ed25519, KeyBlobFormat.NSecPrivateKey }, { SignatureAlgorithm.Ed25519, KeyBlobFormat.PkixPrivateKey }, { SignatureAlgorithm.Ed25519, KeyBlobFormat.PkixPrivateKeyText }, + { SignatureAlgorithm.Ed25519ph, KeyBlobFormat.RawPrivateKey }, + { SignatureAlgorithm.Ed25519ph, KeyBlobFormat.NSecPrivateKey }, }; public static readonly TheoryData SymmetricKeyBlobFormats = new() { + { AeadAlgorithm.Aegis128L, KeyBlobFormat.RawSymmetricKey }, + { AeadAlgorithm.Aegis128L, KeyBlobFormat.NSecSymmetricKey }, + { AeadAlgorithm.Aegis256, KeyBlobFormat.RawSymmetricKey }, + { AeadAlgorithm.Aegis256, KeyBlobFormat.NSecSymmetricKey }, { AeadAlgorithm.Aes256Gcm, KeyBlobFormat.RawSymmetricKey }, { AeadAlgorithm.Aes256Gcm, KeyBlobFormat.NSecSymmetricKey }, { MacAlgorithm.Blake2b_128, KeyBlobFormat.RawSymmetricKey }, diff --git a/tests/Tests.csproj b/tests/Tests.csproj index 0f36c842..bfaaf4ce 100644 --- a/tests/Tests.csproj +++ b/tests/Tests.csproj @@ -1,7 +1,7 @@  - net7.0;net6.0 + net8.0;net7.0;net6.0 @@ -15,9 +15,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers