Skip to content
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
12 changes: 10 additions & 2 deletions .github/workflows/psscriptanalyzer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ name: Run PSScriptAnalyzer

on:
push:
branches: [main, dev]
pull_request:
branches: [main]

permissions:
security-events: write
contents: read

jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Run PSScriptAnalyzer
uses: microsoft/psscriptanalyzer-action@v1.1
Expand All @@ -19,6 +27,6 @@ jobs:
output: results.sarif

- name: Upload SARIF results file
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif
10 changes: 8 additions & 2 deletions DNSHealth/DNSHealth.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,14 @@
# Modules that must be imported into the global environment prior to importing this module
#RequiredModules = @('')

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Assemblies that must be loaded prior to importing this module.
# Preloads the precompiled SevenTinyRsa.dll (Source/SevenTinyRsa/) so
# Get-RsaPublicKeyInfo finds [SevenTiny.Bantina.Security.RSACommon]
# already registered — its existing Add-Type guard then short-circuits
# via the `-as [type]` check. Required for hosts where runtime
# `Add-Type -Language CSharp` fails (e.g. embedded-PowerShell hosts
# with no $PSHOME/ref reference-assemblies directory).
RequiredAssemblies = @('SevenTinyRsa.dll')

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = @()
Expand Down
Binary file added DNSHealth/SevenTinyRsa.dll
Binary file not shown.
4 changes: 4 additions & 0 deletions Source/SevenTinyRsa/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# .NET build intermediates — the built DLL is committed under
# ../../DNSHealth/SevenTinyRsa.dll (see README.md).
bin/
obj/
44 changes: 44 additions & 0 deletions Source/SevenTinyRsa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# SevenTinyRsa

Precompiled `[SevenTiny.Bantina.Security.RSACommon]` — the RSA public-key
parser that `Private/Get-RsaPublicKeyInfo.ps1` uses to decode DKIM keys.

## Why this exists

Upstream `Get-RsaPublicKeyInfo.ps1` ships the C# source inline and compiles
it at runtime via `Add-Type -TypeDefinition $source -Language CSharp`. That
works fine when DNSHealth is loaded by a normal `pwsh` process (which has
the .NET reference assemblies bundled at `$PSHOME/ref/`), but fails in any
host that embeds PowerShell as a library — `$PSHOME` resolves to the host's
own directory and Roslyn can't find the ref assemblies, so `Add-Type`
throws `DirectoryNotFoundException`.

Bundling a precompiled DLL and listing it in `DNSHealth.psd1`'s
`RequiredAssemblies` registers the type before the module's runtime code
runs. `Get-RsaPublicKeyInfo`'s existing
`if (!('SevenTiny.Bantina.Security.RSACommon' -as [type]))` guard then
short-circuits and `Add-Type` is never reached. The inline source path
stays in place as a fallback for any future host that doesn't ship the
DLL.

Behaviour on standard `pwsh` is unchanged — the type lookup just hits the
preloaded assembly instead of going through the runtime compile.

## Rebuild

```pwsh
cd Source/SevenTinyRsa
dotnet build SevenTinyRsa.csproj -c Release -o ../../DNSHealth/
```

The output `SevenTinyRsa.dll` lands next to `DNSHealth.psd1`.
`build.psd1`'s `CopyDirectories` includes `SevenTinyRsa.dll`, so
`Build-Module` carries it into the built `Output/DNSHealth/` directory
beside the generated `.psm1`.

## Provenance

The C# source is verbatim from
[sevenTiny/Bamboo](https://github.com/sevenTiny/Bamboo) at
`10-Code/SevenTiny.Bantina/Security/RSACommon.cs`. The original
`Get-RsaPublicKeyInfo.ps1` cites the same source in its `.NOTES` block.
119 changes: 119 additions & 0 deletions Source/SevenTinyRsa/RSACommon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Sourced verbatim from DNSHealth 1.1.8's vendored inline C# (DNSHealth.psm1
// Private/Get-RsaPublicKeyInfo.ps1, lines 71-183 of the built .psm1).
//
// Origin: https://github.com/sevenTiny/Bamboo
// 10-Code/SevenTiny.Bantina/Security/RSACommon.cs

using System;
using System.IO;
using System.Security.Cryptography;

namespace SevenTiny.Bantina.Security {
public static class RSACommon {
public static RSA CreateRsaProviderFromPublicKey(string publicKeyString)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
byte[] seq = new byte[15];

var x509Key = Convert.FromBase64String(publicKeyString);

// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
using (MemoryStream mem = new MemoryStream(x509Key))
{
using (BinaryReader binr = new BinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading
{
byte bt = 0;
ushort twobytes = 0;

twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;

seq = binr.ReadBytes(15); //read the Sequence OID
if (!CompareBytearrays(seq, seqOid)) //make sure Sequence for OID is correct
return null;

twobytes = binr.ReadUInt16();
if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8203)
binr.ReadInt16(); //advance 2 bytes
else
return null;

bt = binr.ReadByte();
if (bt != 0x00) //expect null byte next
return null;

twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;

twobytes = binr.ReadUInt16();
byte lowbyte = 0x00;
byte highbyte = 0x00;

if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
else if (twobytes == 0x8202)
{
highbyte = binr.ReadByte(); //advance 2 bytes
lowbyte = binr.ReadByte();
}
else
return null;
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order
int modsize = BitConverter.ToInt32(modint, 0);

int firstbyte = binr.PeekChar();
if (firstbyte == 0x00)
{ //if first byte (highest order) of modulus is zero, don't include it
binr.ReadByte(); //skip this null byte
modsize -= 1; //reduce modulus buffer size by 1
}

byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes

if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data
return null;
int expbytes = (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)
byte[] exponent = binr.ReadBytes(expbytes);

// ------- create RSACryptoServiceProvider instance and initialize with public key -----
var rsa = System.Security.Cryptography.RSA.Create();
RSAParameters rsaKeyInfo = new RSAParameters
{
Modulus = modulus,
Exponent = exponent
};
rsa.ImportParameters(rsaKeyInfo);

return rsa;
}
}
}

private static bool CompareBytearrays(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
int i = 0;
foreach (byte c in a)
{
if (c != b[i])
return false;
i++;
}
return true;
}
}
}
17 changes: 17 additions & 0 deletions Source/SevenTinyRsa/SevenTinyRsa.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>SevenTinyRsa</AssemblyName>
<RootNamespace>SevenTiny.Bantina.Security</RootNamespace>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
<OutputType>Library</OutputType>
<Optimize>true</Optimize>
<DebugType>none</DebugType>
<GenerateDependencyFile>false</GenerateDependencyFile>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>bin\</OutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
</Project>
8 changes: 7 additions & 1 deletion build.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@
# Subsequent relative paths are to the ModuleManifest
OutputDirectory = '..\Output\'
VersionedOutputDirectory = $false
CopyDirectories = @('MailProviders')
# SevenTinyRsa.dll is the precompiled [SevenTiny.Bantina.Security.RSACommon]
# helper used by Get-RsaPublicKeyInfo. Bundling it lets DNSHealth.psd1's
# RequiredAssemblies preload the type so the inline Add-Type fallback
# short-circuits — required on hosts where runtime CSharp compilation
# fails (no $PSHOME/ref). Source: Source/SevenTinyRsa/. CopyDirectories
# is ModuleBuilder's alias for CopyPaths, which accepts files too.
CopyDirectories = @('MailProviders', 'SevenTinyRsa.dll')
}
Loading