Skip to content
This repository has been archived by the owner on Jul 26, 2023. It is now read-only.

Add BCrypt types and test for CCM block chaining mode #74

Merged
merged 1 commit into from
Nov 23, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/BCrypt.Tests/BCrypt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,84 @@ public void EncryptDecrypt_NoPadding()
}
}

/// <summary>
/// Demonstrates use of an authenticated block chaining mode
/// that requires use of several more struct types than
/// the default CBC mode for AES.
/// </summary>
[Fact]
public unsafe void EncryptDecrypt_AesCcm()
{
var random = new Random();

using (var provider = BCryptOpenAlgorithmProvider(AlgorithmIdentifiers.BCRYPT_AES_ALGORITHM))
{
BCryptSetProperty(provider, PropertyNames.ChainingMode, ChainingModes.Ccm);

byte[] plainText;
byte[] cipherText;

var nonceBuffer = new byte[12];
random.NextBytes(nonceBuffer);

var tagLengths = BCryptGetProperty<BCRYPT_AUTH_TAG_LENGTHS_STRUCT>(provider, PropertyNames.AuthTagLength);
var tagBuffer = new byte[tagLengths.MaxLength];

int blockSize = BCryptGetProperty<int>(provider, PropertyNames.BlockLength);
plainText = new byte[blockSize];
random.NextBytes(plainText);

byte[] keyMaterial = new byte[blockSize];
RandomNumberGenerator.Create().GetBytes(keyMaterial);

using (var key = BCryptGenerateSymmetricKey(provider, keyMaterial))
{
var authInfo = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Create();
fixed (byte* pTagBuffer = &tagBuffer[0])
fixed (byte* pNonce = &nonceBuffer[0])
{
authInfo.pbNonce = new IntPtr(pNonce);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Still feel bad to have IntPtr instead of pointers here, i'm still conflicted on the subject. Using pointers would block a few esoteric languages (PowerShell is the one with the biggest user base I can think of) but not using it is forcing all callers to copy paste the same conversion.

On that subject I remarket that the current design for System.Span only exposes the pointer (UnsafePointer) but not an IntPtr version, maybe there's a trend starting (Or it's just because the lib development is still in flux and it will change).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. I'm really not liking the IntPtr either. I wonder if we had two P/Invoke overloads (one with IntPtr the other with T*) if Powershell would be able to pick the right overload with its limited typing system.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll open an issue to revisit changing IntPtr's to pointers in general, since for this PR I'm just conforming to what BCrypt and other libs AFAIK already have.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes let's discuss on the issue #76, we'll need to list the full pro/cons of each solution before starting moving code in any direction.

authInfo.cbNonce = nonceBuffer.Length;
authInfo.pbTag = new IntPtr(pTagBuffer);
authInfo.cbTag = tagBuffer.Length;

var pAuthInfo = new IntPtr(&authInfo);
int cipherTextLength;
BCryptEncrypt(key, plainText, plainText.Length, pAuthInfo, null, 0, null, 0, out cipherTextLength, BCryptEncryptFlags.None).ThrowOnError();
cipherText = new byte[cipherTextLength];
BCryptEncrypt(key, plainText, plainText.Length, pAuthInfo, null, 0, cipherText, cipherText.Length, out cipherTextLength, BCryptEncryptFlags.None).ThrowOnError();
}

Assert.NotEqual<byte>(plainText, cipherText);
}

// Renew the key to prove we can decrypt it with a fresh key.
using (var key = BCryptGenerateSymmetricKey(provider, keyMaterial))
{
byte[] decryptedText;

var authInfo = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.Create();
fixed (byte* pTagBuffer = &tagBuffer[0])
fixed (byte* pNonce = &nonceBuffer[0])
{
authInfo.pbNonce = new IntPtr(pNonce);
authInfo.cbNonce = nonceBuffer.Length;
authInfo.pbTag = new IntPtr(pTagBuffer);
authInfo.cbTag = tagBuffer.Length;

var pAuthInfo = new IntPtr(&authInfo);
int plainTextLength;
BCryptDecrypt(key, cipherText, cipherText.Length, pAuthInfo, null, 0, null, 0, out plainTextLength, BCryptEncryptFlags.None).ThrowOnError();
decryptedText = new byte[plainTextLength];
BCryptEncrypt(key, cipherText, cipherText.Length, pAuthInfo, null, 0, decryptedText, decryptedText.Length, out plainTextLength, BCryptEncryptFlags.None).ThrowOnError();
Array.Resize(ref decryptedText, plainTextLength);
}

Assert.Equal<byte>(plainText, decryptedText);
}
}
}

[Fact]
public void Hash()
{
Expand Down
36 changes: 36 additions & 0 deletions src/BCrypt/BCrypt+AuthModeFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) to owners found in https://github.com/AArnott/pinvoke/blob/master/COPYRIGHT.md. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.

namespace PInvoke
{
using System;

/// <content>
/// Contains the <see cref="AuthModeFlags"/> nested type.
/// </content>
public partial class BCrypt
{
/// <summary>
/// Flags for the <see cref="BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.dwFlags"/> field.
/// </summary>
[Flags]
public enum AuthModeFlags
{
/// <summary>
/// No flags.
/// </summary>
None = 0x0,

/// <summary>
/// Indicates that <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> and <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> function calls are being chained and that the MAC value will not be computed. On the last call in the chain, clear this value to compute the MAC value for the entire chain.
/// </summary>
BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG = 0x1,

/// <summary>
/// Indicates that this <see cref="BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO"/> structure is being used in a sequence of chained <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> or <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> function calls. This flag is set and maintained internally.
/// Note: During the chaining sequence, this flag value is maintained internally and must not be changed or the value of the computed MAC will be corrupted.
/// </summary>
BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG = 0x2,
}
}
}
138 changes: 138 additions & 0 deletions src/BCrypt/BCrypt+BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) to owners found in https://github.com/AArnott/pinvoke/blob/master/COPYRIGHT.md. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.

namespace PInvoke
{
using System;
using System.Runtime.InteropServices;

/// <content>
/// Contains the <see cref="BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO"/> nested type.
/// </content>
public partial class BCrypt
{
/// <summary>
/// Used with the <see cref="BCryptEncrypt(SafeKeyHandle, byte[], IntPtr, byte[], BCryptEncryptFlags)"/> and <see cref="BCryptDecrypt(SafeKeyHandle, byte[], IntPtr, byte[], BCryptEncryptFlags)"/> functions
/// to contain additional information related to authenticated cipher modes.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
{
/// <summary>
/// The version of the <see cref="BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO"/> struct.
/// </summary>
public const uint BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION = 1;

/// <summary>
/// The size, in bytes, of this structure.
/// Do not set this field directly. Use the <see cref="Create"/> method instead.
/// </summary>
public uint cbSize;

/// <summary>
/// The version number of the structure.
/// The only supported value is <see cref="BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION"/>.
/// Do not set this field directly. Use the <see cref="Create"/> method instead.
/// </summary>
public uint dwInfoVersion;

/// <summary>
/// A pointer to a buffer that contains a nonce.
/// The Microsoft algorithm providers for Advanced Encryption Standard (AES)
/// require a nonce for the Counter with CBC-MAC (CCM) and Galois/Counter Mode (GCM)
/// chaining modes, and will return an error if none is present.
/// If a nonce is not used, this member must be set to NULL.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't know what we should do about documentation in cases like that, I replaced NULL with <see cref="IntPtr.Zero" /> in a few places but that's tedious.

That's just a detail but might be interesting to unify on it at some point (Maybe a big XML doc cleanup before we reach 1.0).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ya, in all my xml doc comment fixups (adding see cref's etc.) I haven't bothered fixing references to NULL. Sometimes it will be IntPtr.Zero. Other times it will be null. While the doc would be cleaner, I figure unless there's an automated way to fix it up, it's probably not worth our time since readers should be able to figure out what it means.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think there is a good way to make it automated but it could be made assisted, also the point is moot if we decide to do pointer only or to codegen the IntPtr variants.

/// </summary>
public IntPtr pbNonce;

/// <summary>
/// The size, in bytes, of the buffer pointed to by the <see cref="pbNonce"/> member.
/// If a nonce is not used, this member must be set to zero.
/// </summary>
public int cbNonce;

/// <summary>
/// A pointer to a buffer that contains the authenticated data.
/// This is data that will be included in the Message Authentication Code (MAC) but not encrypted.
/// If there is no authenticated data, this member must be set to NULL.
/// </summary>
public IntPtr pbAuthData;

/// <summary>
/// The size, in bytes, of the buffer pointed to by the <see cref="pbAuthData"/> member.
/// If there is no authenticated data, this member must be set to zero.
/// </summary>
public int cbAuthData;

/// <summary>
/// A pointer to a buffer.
/// The use of this member depends on the function to which the structure is passed.
/// For <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/>
/// the buffer will receive the authentication tag.
/// For <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/>
/// the buffer contains the authentication tag to be checked against.
/// If there is no tag, this member must be set to NULL.
/// </summary>
public IntPtr pbTag;

/// <summary>
/// The size, in bytes, of the <see cref="pbTag"/> buffer.
/// The buffer must be long enough to include the whole authentication tag.
/// Some authentication modes, such as CCM and GCM, support checking against a tag
/// with multiple lengths. To obtain the valid authentication tag lengths use
/// <see cref="BCryptGetProperty{T}(SafeHandle, string, BCryptGetPropertyFlags)"/> to query the <see cref="PropertyNames.AuthTagLength"/> property.
/// If there is no tag, this member must be set to zero.
/// </summary>
public int cbTag;

/// <summary>
/// A pointer to a buffer that stores the partially computed MAC between calls to <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> and <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> when chaining encryption or decryption.
/// If the input to encryption or decryption is scattered across multiple buffers, then you must chain calls to the <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> and <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> functions. Chaining is indicated by setting the <see cref="AuthModeFlags.BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG"/> flag in the <see cref="dwFlags"/> member.
/// This buffer must be supplied by the caller and must be at least as large as the maximum length of an authentication tag for the cipher you are using. To get the valid authentication tag lengths, use <see cref="BCryptGetProperty{T}(SafeHandle, string, BCryptGetPropertyFlags)"/> to query the <see cref="PropertyNames.AuthTagLength"/> property.
/// If <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> and <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> calls are not being chained, this member must be set to NULL.
/// </summary>
public IntPtr pbMacContext;

/// <summary>
/// The size, in bytes, of the buffer pointed to by the <see cref="pbMacContext"/> member.
/// If <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> and <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> calls are not being chained, this member must be set to zero.
/// </summary>
public int cbMacContext;

/// <summary>
/// The length, in bytes, of additional authenticated data (AAD) to be used by the <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> and <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> functions. This member is used only when chaining calls.
/// This member is used only when the <see cref="AuthModeFlags.BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG"/> flag in the <see cref="dwFlags"/> member is set.
/// On the first call to <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> or <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> you must set this field to zero.
/// Note: During the chaining sequence, this member is maintained internally and must not be changed or the value of the computed MAC will be corrupted.
/// </summary>
public int cbAAD;

/// <summary>
/// The length, in bytes, of the payload data that was encrypted or decrypted. This member is used only when chaining calls.
/// This member is used only when the <see cref="AuthModeFlags.BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG"/> flag in the <see cref="dwFlags"/> member is set.
/// On the first call to <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> or <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> you must set this field to zero.
/// Note: During the chaining sequence, this member is maintained internally and must not be changed or the value of the computed MAC will be corrupted.
/// </summary>
public long cbData;

/// <summary>
/// This flag is used when chaining <see cref="BCryptEncrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> or <see cref="BCryptDecrypt(SafeKeyHandle, byte[], int, IntPtr, byte[], int, byte[], int, out int, BCryptEncryptFlags)"/> function calls.
/// If calls are not being chained, this member must be set to zero.
/// </summary>
public AuthModeFlags dwFlags;

/// <summary>
/// Initializes a new instance of the <see cref="BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO"/> struct.
/// </summary>
/// <returns>An initialized instance.</returns>
public static BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO Create()
{
return new BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
{
cbSize = (uint)Marshal.SizeOf(typeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO)),
dwInfoVersion = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION,
};
}
}
}
}
50 changes: 50 additions & 0 deletions src/BCrypt/BCrypt+BCRYPT_AUTH_TAG_LENGTHS_STRUCT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) to owners found in https://github.com/AArnott/pinvoke/blob/master/COPYRIGHT.md. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.

namespace PInvoke
{
using System.Collections.Generic;
using System.Runtime.InteropServices;

/// <content>
/// Contains the <see cref="BCRYPT_AUTH_TAG_LENGTHS_STRUCT"/> nested type.
/// </content>
public partial class BCrypt
{
/// <summary>
/// defines the range of tag sizes that are supported by the provider. This structure is used with the <see cref="PropertyNames.AuthTagLength"/> property.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct BCRYPT_AUTH_TAG_LENGTHS_STRUCT
{
/// <summary>
/// The minimum length, in bytes, of a tag.
/// </summary>
public int MinLength;

/// <summary>
/// The maximum length, in bytes, of a tag.
/// </summary>
public int MaxLength;

/// <summary>
/// The number of bytes that the tag size can be incremented between dwMinLength and dwMaxLength.
/// </summary>
public int Increment;

/// <summary>
/// Gets a sequence of allowed tag sizes, from smallest to largest.
/// </summary>
public IEnumerable<int> TagSizes
{
get
{
for (int tagLength = this.MinLength; tagLength <= this.MaxLength; tagLength += this.Increment)
{
yield return tagLength;
}
}
}
}
}
}
5 changes: 5 additions & 0 deletions src/BCrypt/BCrypt+BCRYPT_KEY_DATA_BLOB_HEADER.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public partial class BCrypt
[StructLayout(LayoutKind.Sequential)]
public struct BCRYPT_KEY_DATA_BLOB_HEADER
{
/// <summary>
/// The version of the <see cref="BCRYPT_KEY_DATA_BLOB_HEADER"/> struct.
/// </summary>
public const uint BCRYPT_KEY_DATA_BLOB_VERSION1 = 1;

/// <summary>
/// The magic value for the key.
/// This member must be the following value.
Expand Down
2 changes: 1 addition & 1 deletion src/BCrypt/BCrypt+PropertyNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public static class PropertyNames
public const string HashBlockLength = "HashBlockLength";

/// <summary>
/// The authentication tag lengths that are supported by the algorithm. This property is a BCRYPT_AUTH_TAG_LENGTHS_STRUCT structure. This property only applies to algorithms.
/// The authentication tag lengths that are supported by the algorithm. This property is a <see cref="BCRYPT_AUTH_TAG_LENGTHS_STRUCT"/> structure. This property only applies to algorithms.
/// </summary>
public const string AuthTagLength = "AuthTagLength";

Expand Down
5 changes: 0 additions & 5 deletions src/BCrypt/BCrypt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ namespace PInvoke
/// </summary>
public static partial class BCrypt
{
/// <summary>
/// Version 1.
/// </summary>
public const uint BCRYPT_KEY_DATA_BLOB_VERSION1 = 1;

/// <summary>
/// Loads and initializes a CNG provider.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/BCrypt/BCrypt.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<ItemGroup>
<Compile Include="BCrypt+AlgorithmIdentifiers.cs" />
<Compile Include="BCrypt+AsymmetricKeyBlobTypes.cs" />
<Compile Include="BCrypt+AuthModeFlags.cs" />
<Compile Include="BCrypt+BCryptBuffer.cs" />
<Compile Include="BCrypt+BCryptBufferDesc.cs" />
<Compile Include="BCrypt+BCryptCloseAlgorithmProviderFlags.cs" />
Expand All @@ -45,6 +46,8 @@
<Compile Include="BCrypt+BCryptSecretAgreementFlags.cs" />
<Compile Include="BCrypt+BCryptSetPropertyFlags.cs" />
<Compile Include="BCrypt+BCryptSignHashFlags.cs" />
<Compile Include="BCrypt+BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.cs" />
<Compile Include="BCrypt+BCRYPT_AUTH_TAG_LENGTHS_STRUCT.cs" />
<Compile Include="BCrypt+BCRYPT_KEY_DATA_BLOB_HEADER.cs" />
<Compile Include="BCrypt+BCRYPT_KEY_LENGTHS_STRUCT.cs" />
<Compile Include="BCrypt+BufferType.cs" />
Expand Down