Skip to content

Commit d1530c5

Browse files
committed
v61.0.0 - Adding support for ECDsa keys and certificates
1 parent 35a1693 commit d1530c5

29 files changed

+726
-259
lines changed

InterlockLedger.Tags.UnitTests/InterlockLedger.Tags.UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@
4040
</ItemGroup>
4141
<Target Name="TagSources" />
4242
<Target Name="NugetOrg" />
43+
<Target Name="PublishLocally" />
4344
</Project>

InterlockLedger.Tags/Core/ILTagId.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,14 @@ public struct ILTagId
9797
public const ulong ECParameters = 58;
9898
// EdDSA
9999
public const ulong EdDSAParameters = 59;
100-
public const ulong EdDSAPublicParameters = 60;
100+
public const ulong EdDSAParametersPublic = 60;
101+
// ECDSA
102+
public const ulong ECParametersPublic = 61;
101103

102-
// ********** Free range 61-126 **************
104+
// ********** Free range 62-126 **************
103105

104106
// Metadata
105107
public const ulong DataField = 225; // DataField for Published Apps Records
106-
107108
public const ulong DataIndex = 226; // DataIndex for Published Apps Records
108109
public const ulong DataModel = 224; // DataModel for Published Apps Records
109110
}

InterlockLedger.Tags/Core/Tags/TagProvider.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ public static void RegisterDeserializersFrom(params ITagDeserializersProvider[]
123123
[ILTagId.InterlockKeyAppPermission] = (s => new AppPermissions.Tag(s), NoJson),
124124
[ILTagId.EncryptedText] = (s => new EncryptedText.Payload(ILTagId.EncryptedText, s), NoJson),
125125
[ILTagId.EdDSAParameters] = (s => new TagEdDSAParameters(s), NoJson),
126-
[ILTagId.EdDSAPublicParameters] = (s => new TagEdDSAPublicParameters(s), NoJson),
126+
[ILTagId.EdDSAParametersPublic] = (s => new TagEdDSAPublicParameters(s), NoJson),
127+
[ILTagId.ECParameters] = (s => new TagEcDSAParameters(s), NoJson),
128+
[ILTagId.ECParametersPublic] = (s => new TagEcDSAPublicParameters(s), NoJson),
127129
[ILTagId.DataModel] = (s => new ILTagDataModel(s), NoJson),
128130
[ILTagId.DataField] = (s => new ILTagDataField(s), NoJson),
129131
[ILTagId.DataIndex] = (s => new ILTagDataIndex(s), NoJson),

InterlockLedger.Tags/Crypto/Tags/TagPubKey.cs

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
//
3131
// ******************************************************************************************************************************
3232

33-
3433
using System.Security.Cryptography.X509Certificates;
3534

3635
namespace InterlockLedger.Tags;
@@ -45,25 +44,27 @@ public partial class TagPubKey : ILTagOfExplicitTextual<TagKeyParts>, ITextual<T
4544
public TagHash Hash => TagHash.HashSha256Of(Data);
4645
public virtual KeyStrength Strength => KeyStrength.Normal;
4746
public bool IsEmpty { get; private init; }
48-
4947
public static TagPubKey Resolve(X509Certificate2 certificate) {
50-
var RSA = certificate.GetRSAPublicKey() ?? throw new NotSupportedException("Not yet supporting other kinds of certificates!");
51-
return new TagPubRSAKey(RSA.ExportParameters(false));
48+
var RSA = certificate.GetRSAPublicKey();
49+
var EcDSA = certificate.GetECDsaPublicKey();
50+
return RSA is not null
51+
? new TagPubRSAKey(RSA.ExportParameters(false))
52+
: EcDSA is not null
53+
? new TagPubEcDSAKey(EcDSA.ExportParameters(false))
54+
: throw new NotSupportedException("Not yet supporting other kinds of certificates, than RSA and EcDSA!");
5255
}
53-
5456
private static TagPubKey ResolveAs(Algorithm algorithm, byte[] data)
5557
=> algorithm switch {
5658
Algorithm.RSA => new TagPubRSAKey(data),
59+
Algorithm.EcDSA => new TagPubEcDSAKey(data),
5760
Algorithm.EdDSA => new TagPublicEdDSAKey(data),
58-
_ => throw new NotSupportedException("Only support RSA/EdDSA for now!!!")
61+
_ => throw new NotSupportedException("Only support RSA/EcDSA/EdDSA for now!!!")
5962
};
60-
6163
public static TagPubKey InvalidBy(string cause) => throw new NotSupportedException(cause);
6264
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out TagPubKey result) {
6365
result = Parse(s.Safe(), provider);
6466
return !result.IsInvalid();
6567
}
66-
6768
public static TagPubKey Parse(string s, IFormatProvider? provider) {
6869
if (string.IsNullOrWhiteSpace(s))
6970
throw new ArgumentException("Can't have empty pubkey textual representation!!!", nameof(s));
@@ -74,37 +75,26 @@ public static TagPubKey Parse(string s, IFormatProvider? provider) {
7475
? throw new ArgumentException($"Bad format of pubkey textual representation: '{s}'!!!", nameof(s))
7576
: ResolveAs(algorithm, parts[1].FromSafeBase64());
7677
}
77-
7878
public static TagPubKey Empty { get; } = new TagPubKey() { IsEmpty = true };
79-
public static Regex Mask { get; } = AnythingRegex();
79+
public static Regex Mask { get; } = PubKeyRegex();
8080
public ITextual<TagPubKey> Textual => this;
81-
82-
[GeneratedRegex(".+")]
83-
private static partial Regex AnythingRegex();
84-
81+
[GeneratedRegex(@"PubKey!.+#\w+")]
82+
private static partial Regex PubKeyRegex();
8583
public virtual byte[] Encrypt(byte[] bytes) => throw new NotImplementedException();
8684
protected override bool AreEquivalent(ILTagOf<TagKeyParts?> other) => other.Value is not null && other.Value.Algorithm == Algorithm && other.Value.Data.HasSameBytesAs(Data);
8785
public override string ToString() => Textual.FullRepresentation();
88-
8986
public bool Equals(TagPubKey? other) => other is not null && (Algorithm == other.Algorithm) && Data.HasSameBytesAs(other.Data);
90-
91-
// public virtual bool Verify<T>(T data, TagSignature signature) where T : Signable<T>, new() => false;
92-
9387
public virtual bool Verify(Stream dataStream, TagSignature signature) => false;
94-
9588
internal static TagPubKey Resolve(Stream s) {
9689
var pubKey = new TagPubKey(s);
9790
return ResolveAs(pubKey.Algorithm, pubKey.Data);
9891
}
99-
10092
protected TagPubKey(Algorithm algorithm, byte[] data) : base(ILTagId.PubKey, new TagKeyParts(algorithm, data)) { }
101-
10293
private TagPubKey(Stream s) : base(ILTagId.PubKey, s) { }
10394
protected override string BuildTextualRepresentation() => $"PubKey!{Data.ToSafeBase64()}#{Algorithm}";
10495
protected override async Task<TagKeyParts?> ValueFromStreamAsync(WrappedReadonlyStream s) =>
10596
new((Algorithm)s.BigEndianReadUShort(), await s.ReadAllBytesAsync().ConfigureAwait(false));
10697
protected override Task<Stream> ValueToStreamAsync(Stream s) =>
10798
Task.FromResult(s.BigEndianWriteUShort((ushort)Value!.Algorithm).WriteBytes(Value.Data));
108-
10999
private TagPubKey() : this(Algorithm.Invalid, []) { }
110100
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// ******************************************************************************************************************************
2+
//
3+
// Copyright (c) 2018-2025 InterlockLedger Network
4+
// All rights reserved.
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are met
8+
//
9+
// * Redistributions of source code must retain the above copyright notice, this
10+
// list of conditions and the following disclaimer.
11+
//
12+
// * Redistributions in binary form must reproduce the above copyright notice,
13+
// this list of conditions and the following disclaimer in the documentation
14+
// and/or other materials provided with the distribution.
15+
//
16+
// * Neither the name of the copyright holder nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
// SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) HOWEVER
27+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
//
31+
// ******************************************************************************************************************************
32+
33+
using System.Security.Cryptography.X509Certificates;
34+
35+
namespace InterlockLedger.Tags;
36+
37+
public abstract class BaseCertificateSigningKey : InterlockSigningKey
38+
{
39+
public BaseCertificateSigningKey(InterlockSigningKeyData data, byte[] certificateBytes, string password) :
40+
this(data, BuildCert(certificateBytes, password)) {
41+
}
42+
public BaseCertificateSigningKey(InterlockSigningKeyData data, X509Certificate2 x509Certificate) :
43+
base(data) {
44+
_x509Certificate = x509Certificate;
45+
if (data.Required().EncryptedContentType != EncryptedContentType.EmbeddedCertificate)
46+
throw new ArgumentException($"Wrong kind of EncryptedContentType {data.EncryptedContentType}", nameof(data));
47+
if (!_x509Certificate.HasPrivateKey)
48+
throw new InvalidOperationException("The private key is missing");
49+
if (!_hasCorrectPrivateKey)
50+
throw new InvalidOperationException($"The private key is of the incorrect type - certificate key type is {_x509Certificate.GetKeyAlgorithm}");
51+
}
52+
protected readonly X509Certificate2 _x509Certificate;
53+
private bool _hasCorrectPrivateKey =>
54+
KeyData.PublicKey.Algorithm switch {
55+
Algorithm.RSA => _x509Certificate.GetRSAPrivateKey() is not null,
56+
Algorithm.EcDSA => _x509Certificate.GetECDsaPrivateKey() is not null,
57+
_ => throw new NotSupportedException($"Unsupported certificate key algorithm {KeyData.PublicKey.Algorithm}")
58+
};
59+
protected sealed override void DisposeManagedResources() =>
60+
_x509Certificate?.Dispose();
61+
private static X509Certificate2 BuildCert(byte[] certificateBytes, string password) =>
62+
new(certificateBytes.Required(),
63+
password.Required(),
64+
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
65+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// ******************************************************************************************************************************
2+
//
3+
// Copyright (c) 2018-2025 InterlockLedger Network
4+
// All rights reserved.
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are met
8+
//
9+
// * Redistributions of source code must retain the above copyright notice, this
10+
// list of conditions and the following disclaimer.
11+
//
12+
// * Redistributions in binary form must reproduce the above copyright notice,
13+
// this list of conditions and the following disclaimer in the documentation
14+
// and/or other materials provided with the distribution.
15+
//
16+
// * Neither the name of the copyright holder nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
// SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) HOWEVER
27+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
//
31+
// ******************************************************************************************************************************
32+
33+
using System.Security.Cryptography;
34+
35+
36+
namespace InterlockLedger.Tags;
37+
38+
public static class ECParametersExtensions
39+
{
40+
public static Stream EncodeHashAlgorithm(this Stream s, HashAlgorithmName? hashAlgorithm) =>
41+
s.EncodeString(hashAlgorithm?.Name);
42+
43+
public static HashAlgorithmName? DecodeHashAlgorithm(this WrappedReadonlyStream s) {
44+
var name = s.DecodeString();
45+
return name is null ? null : new HashAlgorithmName(name);
46+
}
47+
48+
public static Stream EncodeCurveType(this Stream s, ECCurve.ECCurveType curveType) =>
49+
s.EncodeILInt((ulong)curveType);
50+
51+
public static ECCurve.ECCurveType DecodeCurveType(this WrappedReadonlyStream s) =>
52+
(ECCurve.ECCurveType)s.DecodeILInt();
53+
54+
55+
public static Stream EncodeECParametersPublicParts(this Stream s, ECParameters parameters) {
56+
s.Required();
57+
if (parameters.Curve.Oid is null) {
58+
s.EncodeString(null) // No OID
59+
.EncodeByteArray(parameters.Curve.A)
60+
.EncodeByteArray(parameters.Curve.B)
61+
.EncodeByteArray(parameters.Curve.Cofactor)
62+
.EncodeCurveType(parameters.Curve.CurveType)
63+
.EncodeByteArray(parameters.Curve.G.X)
64+
.EncodeByteArray(parameters.Curve.G.Y)
65+
.EncodeHashAlgorithm(parameters.Curve.Hash)
66+
.EncodeByteArray(parameters.Curve.Order)
67+
.EncodeByteArray(parameters.Curve.Polynomial)
68+
.EncodeByteArray(parameters.Curve.Prime)
69+
.EncodeByteArray(parameters.Curve.Seed);
70+
} else {
71+
s.EncodeString(parameters.Curve.Oid.Value); // OID
72+
}
73+
s.EncodeByteArray(parameters.Q.X)
74+
.EncodeByteArray(parameters.Q.Y);
75+
return s;
76+
}
77+
public static Stream EncodeECParameters(this Stream s, ECParameters parameters) =>
78+
s.EncodeByteArray(parameters.D.Required())
79+
.EncodeECParametersPublicParts(parameters);
80+
81+
public static ECParameters DecodeECParameters(this WrappedReadonlyStream s) {
82+
var privateKey = s.DecodeByteArray().Required("ECParameters.PrivateKey");
83+
var publicParts = s.DecodeECParametersPublicParts();
84+
var result = new ECParameters {
85+
D = privateKey,
86+
Curve = publicParts.Curve,
87+
Q = publicParts.Q
88+
};
89+
result.Validate();
90+
return result;
91+
}
92+
public static ECParameters DecodeECParametersPublicParts(this WrappedReadonlyStream s) {
93+
var oid = s.DecodeString();
94+
ECCurve Curve;
95+
if (string.IsNullOrEmpty(oid)) {
96+
Curve = new ECCurve() {
97+
A = s.DecodeByteArray(),
98+
B = s.DecodeByteArray(),
99+
Cofactor = s.DecodeByteArray(),
100+
CurveType = s.DecodeCurveType(),
101+
G = new ECPoint {
102+
X = s.DecodeByteArray(),
103+
Y = s.DecodeByteArray()
104+
},
105+
Hash = s.DecodeHashAlgorithm(),
106+
Order = s.DecodeByteArray(),
107+
Polynomial = s.DecodeByteArray(),
108+
Prime = s.DecodeByteArray(),
109+
Seed = s.DecodeByteArray()
110+
};
111+
} else {
112+
// New format
113+
Curve = ECCurve.CreateFromOid(new Oid(oid));
114+
}
115+
var result = new ECParameters {
116+
Curve = Curve,
117+
// Q is the public key point
118+
Q = new ECPoint {
119+
X = s.DecodeByteArray(),
120+
Y = s.DecodeByteArray()
121+
}
122+
};
123+
return result;
124+
}
125+
126+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// ******************************************************************************************************************************
2+
//
3+
// Copyright (c) 2018-2025 InterlockLedger Network
4+
// All rights reserved.
5+
//
6+
// Redistribution and use in source and binary forms, with or without
7+
// modification, are permitted provided that the following conditions are met
8+
//
9+
// * Redistributions of source code must retain the above copyright notice, this
10+
// list of conditions and the following disclaimer.
11+
//
12+
// * Redistributions in binary form must reproduce the above copyright notice,
13+
// this list of conditions and the following disclaimer in the documentation
14+
// and/or other materials provided with the distribution.
15+
//
16+
// * Neither the name of the copyright holder nor the names of its
17+
// contributors may be used to endorse or promote products derived from
18+
// this software without specific prior written permission.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
// SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) HOWEVER
27+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
//
31+
// ******************************************************************************************************************************
32+
33+
using System.Security.Cryptography;
34+
using System.Security.Cryptography.X509Certificates;
35+
36+
namespace InterlockLedger.Tags;
37+
38+
public sealed class ECDsaCertificateSigningKey : BaseCertificateSigningKey
39+
{
40+
public ECDsaCertificateSigningKey(InterlockSigningKeyData data, byte[] certificateBytes, string password)
41+
: base(data, certificateBytes, password) { }
42+
public ECDsaCertificateSigningKey(InterlockSigningKeyData data, X509Certificate2 certificate)
43+
: base(data, certificate) { }
44+
protected override byte[] HashAndSignStream(Stream dataStream) {
45+
using var ecdsa = _x509Certificate.GetECDsaPrivateKey().Required();
46+
return ecdsa.SignData(dataStream, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
47+
}
48+
49+
protected override bool VerifySignatureOnStream(Stream dataStream, TagSignature signature) {
50+
using var ecdsa = _x509Certificate.GetECDsaPublicKey().Required();
51+
return ecdsa.VerifyData(dataStream, signature.Data, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
52+
}
53+
}

0 commit comments

Comments
 (0)