Skip to content

Commit

Permalink
Made CreateUploadablePackage public
Browse files Browse the repository at this point in the history
Accepts a folder to package
  • Loading branch information
svrooij committed Dec 3, 2024
1 parent 68c28ce commit 0959fd3
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 30 deletions.
23 changes: 15 additions & 8 deletions src/SvR.ContentPrep/Encryptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ internal static async Task<FileEncryptionInfo> EncryptStreamToStreamAsync(Stream

private static byte[] CreateAesKey()
{
using AesCryptoServiceProvider cryptoServiceProvider = new AesCryptoServiceProvider();
cryptoServiceProvider.GenerateKey();
return cryptoServiceProvider.Key;
using Aes aes = Aes.Create();
aes.GenerateKey();
return aes.Key;
}

private static byte[] GenerateAesIV()
Expand Down Expand Up @@ -160,7 +160,14 @@ private static async Task<byte[]> EncryptStreamWithIVAsync(
// Copy the sourceStream to the cryptoStream
cancellationToken.ThrowIfCancellationRequested();
await sourceStream.CopyToAsync(cryptoStream, DefaultBufferSize, cancellationToken);
#if NET6_0_OR_GREATER
await cryptoStream.FlushFinalBlockAsync(cancellationToken);
#else
cryptoStream.FlushFinalBlock();
#endif
// not sure if this is needed, but because we want to keep the stream open, it's better to flush anyway.
await cryptoStream.FlushAsync(cancellationToken);
await targetStream.FlushAsync(cancellationToken);
}
cancellationToken.ThrowIfCancellationRequested();

Expand All @@ -173,17 +180,17 @@ private static async Task<byte[]> EncryptStreamWithIVAsync(
// Rewind the targetStream to the exact position of the start of the IV (which should be included in the hash)
targetStream.Seek(hashSize, SeekOrigin.Begin);
// Compute the hash of the targetStream
byte[] hash = await hmac.ComputeHashAsync(targetStream, cancellationToken);
byte[]? hash = await hmac.ComputeHashAsync(targetStream, cancellationToken);
encryptedFileHash = hash;
// Rewind the targetStream to the beginning
targetStream.Seek(0L, SeekOrigin.Begin);
// Write the hash to the targetStream
await targetStream.WriteAsync(hash, 0, hash.Length, cancellationToken);
await targetStream.WriteAsync(hash, 0, hash!.Length, cancellationToken);
await targetStream.FlushAsync(cancellationToken);

// At this point the targetStream will the hash (of the IV and the encrypted data), the IV and the encrypted data

return encryptedFileHash;
return encryptedFileHash!;
}

/// <summary>
Expand All @@ -205,7 +212,7 @@ internal static async Task<Stream> DecryptStreamAsync(Stream inputStream, string
int offset = hmac.HashSize / 8;
byte[] buffer = new byte[offset];
await inputStream.ReadAsync(buffer, 0, offset, cancellationToken);
byte[] hash = await hmac.ComputeHashAsync(inputStream, cancellationToken);
byte[] hash = (await hmac.ComputeHashAsync(inputStream, cancellationToken))!;

if (!buffer.CompareHashes(hash))
{
Expand All @@ -230,7 +237,7 @@ internal static async Task<Stream> DecryptStreamAsync(Stream inputStream, string

internal static class HashAlgorithmExtensions
{
internal static async Task<byte[]> ComputeHashAsync(this HashAlgorithm hashAlgorithm, Stream stream, CancellationToken cancellationToken)
internal static async Task<byte[]?> ComputeHashAsync(this HashAlgorithm hashAlgorithm, Stream stream, CancellationToken cancellationToken)
{
byte[] buffer = new byte[4096];
int bytesRead;
Expand Down
10 changes: 5 additions & 5 deletions src/SvR.ContentPrep/Models/ApplicationInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,18 @@ internal static ApplicationInfo FromXml(string xml)
{
using (StringReader input = new StringReader(xml))
{
return ApplicationInfo.FromXml(input);
return ApplicationInfo.FromXml(input)!;
}
}

internal static ApplicationInfo FromXml(TextReader textReader)
internal static ApplicationInfo? FromXml(TextReader textReader)
{
return (ApplicationInfo)new XmlSerializer(typeof(ApplicationInfo)).Deserialize(textReader);
return new XmlSerializer(typeof(ApplicationInfo)).Deserialize(textReader) as ApplicationInfo;
}

internal static ApplicationInfo FromXml(Stream xml)
internal static ApplicationInfo? FromXml(Stream xml)
{
return (ApplicationInfo)new XmlSerializer(typeof(ApplicationInfo)).Deserialize(xml);
return new XmlSerializer(typeof(ApplicationInfo)).Deserialize(xml) as ApplicationInfo;
}
}
}
38 changes: 37 additions & 1 deletion src/SvR.ContentPrep/Packager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ public class Packager
internal const string PackageFileExtension = ".intunewin";
// Version of the latest IntuneWinAppUtil tool
internal const string ToolVersion = "1.8.4.0";
internal const string EncryptedPackageFileName = "IntunePackage.intunewin";

/// <summary>
/// Mandatory filename for the encrypted package
/// </summary>
public const string EncryptedPackageFileName = "IntunePackage.intunewin";

/// <summary>
/// Creates a new instance of the Packager class
Expand Down Expand Up @@ -145,6 +149,38 @@ public Packager(ILogger<Packager>? logger = null)
return applicationInfo;
}

/// <summary>
/// Package a folder to an inner intunewin file
/// </summary>
/// <param name="folderToPackage">Location you want to package.</param>
/// <param name="outputStream">Stream to return the uploadable package to.</param>
/// <param name="applicationDetails">Specify the information about the package. `SetupFile` and `Name` are mandatory.</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <remarks>Normally you would use the <see cref="CreatePackage(string, string, string, ApplicationDetails?, CancellationToken)"/> method, but if you want to upload that to Intune, you'll have to extract it anyway. <see href="https://svrooij.io/2023/08/31/publish-apps-to-intune/#extracting-the-intunewin-file">this blog</see></remarks>
public async Task<ApplicationInfo?> CreateUploadablePackage(
string folderToPackage,
Stream outputStream,
ApplicationDetails applicationDetails,
CancellationToken cancellationToken = default)
{
string tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
string encryptedPackageLocation = Path.Combine(tempFolder, EncryptedPackageFileName);
try
{
await Zipper.ZipDirectory(folderToPackage, encryptedPackageLocation, false, false);
using (FileStream fileStream = new FileStream(encryptedPackageLocation, FileMode.Open, FileAccess.Read, FileShare.None, bufferSize: 4096, useAsync: true))
{
return await CreateUploadablePackage(fileStream, outputStream, applicationDetails, cancellationToken);
}
}
finally
{
if (File.Exists(encryptedPackageLocation))
File.Delete(encryptedPackageLocation);
}
}

/// <summary>
/// Decrypt an intunewin file to a folder
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/SvR.ContentPrep/SvR.ContentPrep.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageReference Include="Microsoft.NETCore.Platforms" Version="7.0.4" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
Expand Down
6 changes: 3 additions & 3 deletions src/SvR.ContentPrep/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Direct",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
"requested": "[6.1.0, )",
"resolved": "6.1.0",
"contentHash": "5o/HZxx6RVqYlhKSq8/zronDkALJZUT2Vz0hx43f0gwe8mwlM0y2nYlqdBwLMzr262Bwvpikeb/yEwkAa5PADg=="
},
"System.Buffers": {
"type": "Transitive",
Expand Down
16 changes: 4 additions & 12 deletions tests/SvR.ContentPrep.Tests/Packager.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,24 @@ public async Task Packager_CreateUploadablePackage_Succeeds(int sizeInMb, int mi
var setupFolder = TestHelper.GetTestFolder();
var setupFile = await TestHelper.GenerateTempFileInFolder(setupFolder, setupFileName, sizeInMb, cts.Token);

var zipFolder = TestHelper.GetTestFolder();
var zipFilename = Path.Combine(zipFolder, "intunewin.tmp");
var zipStream = new FileStream(zipFilename, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete, 8192, true);
ZipFile.CreateFromDirectory(setupFolder, zipStream, CompressionLevel.NoCompression, false);
long zipSize = zipStream.Length;
var setupFileInfo = new FileInfo(setupFile);

var outputDirectory = TestHelper.GetTestFolder();
var outputFile = Path.Combine(outputDirectory, "setup.intunewin");
var intuneWinStream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete, 8192, true);
zipStream.Seek(0, SeekOrigin.Begin);
var packager = new Packager();

try
{

var stopwatch = new Stopwatch();
stopwatch.Start();
var info = await packager.CreateUploadablePackage(zipStream, intuneWinStream, new Models.ApplicationDetails { Name = "Test", SetupFile = setupFileName }, cts.Token);
var info = await packager.CreateUploadablePackage(setupFolder, intuneWinStream, new Models.ApplicationDetails { Name = "Test", SetupFile = setupFileName }, cts.Token);
stopwatch.Stop();
var fileExists = File.Exists(outputFile);
fileExists.Should().BeTrue("output package should exist");
var outputFilesize = new FileInfo(outputFile).Length;
zipSize.Should().BeLessThan(outputFilesize, "output package should have encryption data attached");
info!.UnencryptedContentSize.Should().Be(zipSize, "library should report correct input size");
var expectedSize = zipSize + 60;
outputFilesize.Should().Be(expectedSize, "output file should be 60 bytes bigger then input file");
info!.UnencryptedContentSize.Should().BeLessThan(outputFilesize, "output package should have encryption data attached");
expectedPackageMs.Should().BeGreaterThan(stopwatch.ElapsedMilliseconds, $"It took {stopwatch.ElapsedMilliseconds}ms to package instead of {expectedPackageMs}");

}
Expand All @@ -97,7 +89,7 @@ public async Task Packager_CreateUploadablePackage_Succeeds(int sizeInMb, int mi
finally
{
TestHelper.RemoveFolderIfExists(setupFolder);
TestHelper.RemoveFolderIfExists(zipFolder);
//TestHelper.RemoveFolderIfExists(zipFolder);
TestHelper.RemoveFolderIfExists(outputDirectory);
}

Expand Down

0 comments on commit 0959fd3

Please sign in to comment.