Skip to content

Commit

Permalink
Merge pull request #7 from dlmelendez/rel/v2.x
Browse files Browse the repository at this point in the history
Rel/v2.x
  • Loading branch information
dlmelendez authored Nov 29, 2024
2 parents 896ba7f + 4a614d0 commit da7b424
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 64 deletions.
2 changes: 1 addition & 1 deletion src/PKISharp.SimplePKI/PKISharp.SimplePKI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@


<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
</ItemGroup>

</Project>
18 changes: 13 additions & 5 deletions src/PKISharp.SimplePKI/PkiCertificate.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
Expand All @@ -11,6 +11,9 @@
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using BclCertificate = System.Security.Cryptography.X509Certificates.X509Certificate2;
#if NET9_0_OR_GREATER
using CertLoader = System.Security.Cryptography.X509Certificates.X509CertificateLoader;
#endif

namespace PKISharp.SimplePKI
{
Expand All @@ -22,15 +25,20 @@ internal PkiCertificate()
public string SubjectName => NativeCertificate.SubjectDN.ToString();

public IEnumerable<string> SubjectAlternativeNames =>
NativeCertificate.GetSubjectAlternativeNames()?.Cast<ArrayList>()
.SelectMany(x => x.Cast<object>().Where(y => y is string)
.Select(y => (string)y));
NativeCertificate.GetSubjectAlternativeNameExtension()?
.GetNames()?
.Where(w => w?.Name != null)
.Select(s => s.Name.ToString());

internal X509Certificate NativeCertificate { get; set; }

public BclCertificate ToBclCertificate()
{
#if NET9_0_OR_GREATER
return CertLoader.LoadCertificate(NativeCertificate.GetEncoded());
#else
return new BclCertificate(NativeCertificate.GetEncoded());
#endif
}

public static PkiCertificate From(BclCertificate bclCert)
Expand Down Expand Up @@ -166,4 +174,4 @@ public PkiCertificate Recover()
}
}
}
}
}
4 changes: 2 additions & 2 deletions src/PKISharp.SimplePKI/PkiCertificateSigningRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,8 @@ internal PkiCertificate Create(X509Name issuerName, PkiKey issuerPrivateKey,
X509KeyUsage.DigitalSignature);
if (extKeyUsage == null)
extKeyUsage = [
KeyPurposeID.IdKPClientAuth,
KeyPurposeID.IdKPServerAuth
KeyPurposeID.id_kp_clientAuth,
KeyPurposeID.id_kp_serverAuth
];

certGen.AddExtension("2.5.29.15", true, keyUsage);
Expand Down
12 changes: 6 additions & 6 deletions src/PKISharp.SimplePKI/PkiKeyPair.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Xml.Serialization;
using Org.BouncyCastle.Asn1.Nist;
Expand Down Expand Up @@ -311,9 +311,9 @@ internal static object ExportRsJwk(PkiKeyPair keys, bool @private)
// As per RFC 7638 Section 3, these are the *required* elements of the
// JWK and are sorted in lexicographic order to produce a canonical form

e = Base64Tool.Instance.UrlEncode(pub.Exponent.ToByteArray()),
e = Base64Tool.UrlEncode(pub.Exponent.ToByteArray()).ToString(),
kty = "RSA", // https://tools.ietf.org/html/rfc7518#section-6.3
n = Base64Tool.Instance.UrlEncode(pub.Modulus.ToByteArray()),
n = Base64Tool.UrlEncode(pub.Modulus.ToByteArray()).ToString(),
};
}

Expand All @@ -330,8 +330,8 @@ internal static object ExportEcJwk(int bits, PkiKeyPair keys, bool @private)

crv = $"P-{bits}",
kty = "EC", // https://tools.ietf.org/html/rfc7518#section-6.2
x = Base64Tool.Instance.UrlEncode(pub.Q.XCoord.GetEncoded()),
y = Base64Tool.Instance.UrlEncode(pub.Q.YCoord.GetEncoded()),
x = Base64Tool.UrlEncode(pub.Q.XCoord.GetEncoded()).ToString(),
y = Base64Tool.UrlEncode(pub.Q.YCoord.GetEncoded()).ToString(),
};
}

Expand Down Expand Up @@ -431,4 +431,4 @@ public PkiKeyPairEcdsaParams(int bits)
}
}
}
}
}
113 changes: 81 additions & 32 deletions src/PKISharp.SimplePKI/Util/Base64Tool.cs
Original file line number Diff line number Diff line change
@@ -1,65 +1,114 @@
using System;
using System;
using System.Text;

namespace PKISharp.SimplePKI.Util
{
// TODO:!!!!!!
// This needs to be reconciled with the version in ACMESharp!!!!

/// <summary>
/// Collection of convenient crypto operations working
/// with URL-safe Base64 encoding.
/// </summary>
internal class Base64Tool
internal static class Base64Tool
{
public static Base64Tool Instance = new Base64Tool();


/// <summary>
/// URL-safe Base64 encoding as prescribed in RFC 7515 Appendix C.
/// </summary>
public string UrlEncode(string raw, Encoding encoding = null)
public static ReadOnlySpan<char> UrlEncode(ReadOnlySpan<char> raw, Encoding encoding = null)
{
if (encoding == null)
encoding = Encoding.UTF8;
return UrlEncode(encoding.GetBytes(raw));
encoding ??= Encoding.UTF8;
Span<byte> rawBytes = stackalloc byte[encoding.GetMaxByteCount(raw.Length)];
bool getBytes = encoding.TryGetBytes(raw, rawBytes, out int bytesWritten);
if (!getBytes)
{
throw new InvalidOperationException("Failed to encode the input data to bytes.");
}
return UrlEncode(new Span<byte>([.. rawBytes.Slice(0, bytesWritten)]));
}

/// <summary>
/// URL-safe Base64 encoding as prescribed in RFC 7515 Appendix C.
/// </summary>
public string UrlEncode(byte[] raw)
public static ReadOnlySpan<char> UrlEncode(ReadOnlySpan<byte> raw)
{
Span<char> enc = stackalloc char[GetMaxBase64Length(raw.Length)];
bool encoded = Convert.TryToBase64Chars(raw, enc, out int encodingCharCount, Base64FormattingOptions.None);
if (!encoded)
{
throw new InvalidOperationException("Failed to encode the input data to base64.");
}
enc.Replace('+', '-'); // 62nd char of encoding
enc.Replace('/', '_'); // 63rd char of encoding
//Find the first '=' and take the substring up to that point
int indexOfFiller = enc.IndexOf<char>('=');
if (indexOfFiller > 0)
{
return new ReadOnlySpan<char>([.. enc.Slice(0, indexOfFiller)]);
}
return new ReadOnlySpan<char>([.. enc.Slice(0, encodingCharCount)]);
}

/// <summary>
/// Calculate the maximum length of a URL-safe Base64 encoding from byte array length
/// </summary>
/// <param name="byteArrayLength"></param>
/// <returns></returns>
public static int GetMaxBase64Length(int byteArrayLength)
{
string enc = Convert.ToBase64String(raw); // Regular base64 encoder
enc = enc.Split('=')[0]; // Remove any trailing '='s
enc = enc.Replace('+', '-'); // 62nd char of encoding
enc = enc.Replace('/', '_'); // 63rd char of encoding
return enc;
return ((byteArrayLength + 2) / 3) * 4;
}


/// <summary>
/// URL-safe Base64 decoding as prescribed in RFC 7515 Appendix C.
/// </summary>
public byte[] UrlDecode(string enc)
public static ReadOnlySpan<byte> UrlDecode(ReadOnlySpan<char> enc)
{
string raw = enc;
raw = raw.Replace('-', '+'); // 62nd char of encoding
raw = raw.Replace('_', '/'); // 63rd char of encoding
switch (raw.Length % 4) // Pad with trailing '='s
int encodedLength = enc.Length;
//Create a Span<byte> to hold the encoded chars plus maximum padding
Span<char> raw = stackalloc char[encodedLength + 2];
enc.CopyTo(raw);
raw.Replace('-', '+'); // 62nd char of encoding
raw.Replace('_', '/'); // 63rd char of encoding
int padding = 0;

// Pad with trailing '='s
int mod = encodedLength % 4;
if (mod != 0)
{
if (mod == 2)
{
// Two pad chars
padding = 2;
raw[encodedLength] = '=';
raw[encodedLength + 1] = '=';
}
else if (mod == 3)
{
// One pad char
padding = 1;
raw[encodedLength] = '=';
}
else
{
throw new InvalidOperationException("Illegal base64url string!");
}
}

int totalEncodedLength = encodedLength + padding;
int maxByteLength = (totalEncodedLength / 4) * 3;
Span<byte> rawBytes = stackalloc byte[maxByteLength];
bool decoded = Convert.TryFromBase64Chars(raw.Slice(0, totalEncodedLength), rawBytes, out int bytesWritten);
if (!decoded)
{
case 0: break; // No pad chars in this case
case 2: raw += "=="; break; // Two pad chars
case 3: raw += "="; break; // One pad char
default:
throw new System.Exception("Illegal base64url string!");
throw new InvalidOperationException($"Failed to decode the input data from base64: {enc.ToString()}");
}
return Convert.FromBase64String(raw); // Standard base64 decoder
return new ReadOnlySpan<byte>([.. rawBytes.Slice(0, bytesWritten)]); // Standard base64 decoder
}

public string UrlDecodeToString(string enc, Encoding encoding = null)
public static string UrlDecodeToString(ReadOnlySpan<char> enc, Encoding encoding = null)
{
if (encoding == null)
encoding = Encoding.UTF8;
encoding ??= Encoding.UTF8;
return encoding.GetString(UrlDecode(enc));
}
}}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<IsPackable>false</IsPackable>
<StartupObject>ACMESharp.IntegrationTests.DebugMain</StartupObject>
<LangVersion>7.3</LangVersion>
<LangVersion>13.0</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>
Expand All @@ -11,7 +11,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.6.3" />
<PackageReference Include="MSTest.TestFramework" Version="3.6.3" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,15 @@ public void RsaCaCertificateSignsImportedPemCsr()
if (BitConverter.IsLittleEndian)
serNumBytes = serNumBytes.Reverse().ToArray();

// Prepend a zero byte to ensure the BigInteger is interpreted as unsigned
byte[] unsignedSerNumBytes = new byte[serNumBytes.Length + 1];
unsignedSerNumBytes[0] = 0x00;
Array.Copy(serNumBytes, 0, unsignedSerNumBytes, 1, serNumBytes.Length);

var crt = csr2.Create(caCrt, caKpr.PrivateKey,
DateTimeOffset.Now.AddHours(-1),
DateTimeOffset.Now.AddHours(-1),
DateTimeOffset.Now.AddHours(24),
serNumBytes);
unsignedSerNumBytes);

var crtDer = crt.Export(PkiEncodingFormat.Der);
var crtPem = crt.Export(PkiEncodingFormat.Pem);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion test/ACMESharp.UnitTests/ACMESharp.UnitTests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>

<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>

<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down
Loading

0 comments on commit da7b424

Please sign in to comment.