From c57bfb1947029444533f14bfcd5174f3e07f6168 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Wed, 27 Mar 2024 15:52:07 +0700 Subject: Rework SIgnedPublicKeyAndChallenge --- crypto/src/asn1/mozilla/PublicKeyAndChallenge.cs | 11 +- .../asn1/mozilla/SignedPublicKeyAndChallenge.cs | 155 ++++++--------------- crypto/src/mozilla/SignedPublicKeyAndChallenge.cs | 48 +++++++ crypto/test/src/mozilla/test/SpkacTest.cs | 34 +++++ 4 files changed, 134 insertions(+), 114 deletions(-) create mode 100644 crypto/src/mozilla/SignedPublicKeyAndChallenge.cs create mode 100644 crypto/test/src/mozilla/test/SpkacTest.cs (limited to 'crypto') diff --git a/crypto/src/asn1/mozilla/PublicKeyAndChallenge.cs b/crypto/src/asn1/mozilla/PublicKeyAndChallenge.cs index eafbfd72b..6b43b1c1f 100644 --- a/crypto/src/asn1/mozilla/PublicKeyAndChallenge.cs +++ b/crypto/src/asn1/mozilla/PublicKeyAndChallenge.cs @@ -19,9 +19,6 @@ namespace Org.BouncyCastle.Asn1.Mozilla public class PublicKeyAndChallenge : Asn1Encodable { - private readonly SubjectPublicKeyInfo m_spki; - private readonly DerIA5String m_challenge; - public static PublicKeyAndChallenge GetInstance(object obj) { if (obj == null) @@ -40,6 +37,9 @@ namespace Org.BouncyCastle.Asn1.Mozilla #pragma warning restore CS0618 // Type or member is obsolete } + private readonly SubjectPublicKeyInfo m_spki; + private readonly DerIA5String m_challenge; + public PublicKeyAndChallenge(SubjectPublicKeyInfo spki, DerIA5String challenge) { m_spki = spki ?? throw new ArgumentNullException(nameof(spki)); @@ -49,6 +49,11 @@ namespace Org.BouncyCastle.Asn1.Mozilla [Obsolete("Use 'GetInstance' instead")] public PublicKeyAndChallenge(Asn1Sequence seq) { + if (seq == null) + throw new ArgumentNullException(nameof(seq)); + if (seq.Count != 2) + throw new ArgumentException($"Expected 2 elements, but found {seq.Count}", nameof(seq)); + m_spki = SubjectPublicKeyInfo.GetInstance(seq[0]); m_challenge = DerIA5String.GetInstance(seq[1]); } diff --git a/crypto/src/asn1/mozilla/SignedPublicKeyAndChallenge.cs b/crypto/src/asn1/mozilla/SignedPublicKeyAndChallenge.cs index 243cbefd6..76f988c28 100644 --- a/crypto/src/asn1/mozilla/SignedPublicKeyAndChallenge.cs +++ b/crypto/src/asn1/mozilla/SignedPublicKeyAndChallenge.cs @@ -1,138 +1,71 @@ using System; using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Operators; -using Org.BouncyCastle.Security; namespace Org.BouncyCastle.Asn1.Mozilla { - /** - * This is designed to parse - * the SignedPublicKeyAndChallenge created by the KEYGEN tag included by - * Mozilla based browsers. - *
-     *
-     *  SignedPublicKeyAndChallenge ::= SEQUENCE {
-     *    publicKeyAndChallenge PublicKeyAndChallenge,
-     *    signatureAlgorithm AlgorithmIdentifier,
-     *    signature BIT STRING
-     *  }
-     *  
- */ - internal class SignedPublicKeyAndChallenge : Asn1Encodable + /// + /// For parsing the SignedPublicKeyAndChallenge created by the KEYGEN tag included by Mozilla based browsers. + /// + /// + /// + /// SignedPublicKeyAndChallenge ::= SEQUENCE + /// { + /// publicKeyAndChallenge PublicKeyAndChallenge, + /// signatureAlgorithm AlgorithmIdentifier, + /// signature BIT STRING + /// } + /// + /// + public class SignedPublicKeyAndChallenge + : Asn1Encodable { - private Asn1Sequence seq; - private PublicKeyAndChallenge publicKeyAndChallenge; - private AlgorithmIdentifier algorithmIdentifier; - private DerBitString signature; - - public PublicKeyAndChallenge PublicKeyAndChallenge - { - get { return publicKeyAndChallenge; } - } - - public AlgorithmIdentifier AlgorithmIdentifier - { - get { return algorithmIdentifier; } - } - - public DerBitString Signature - { - get { return signature; } - } - - public static SignedPublicKeyAndChallenge GetInstance(Asn1TaggedObject obj, bool explicitly) - { - return GetInstance(Asn1Sequence.GetInstance(obj, explicitly)); - } - public static SignedPublicKeyAndChallenge GetInstance(object obj) { - if (null == obj) - { + if (obj == null) return null; - } - - if (obj is SignedPublicKeyAndChallenge value) - { - return value; - } - - if (obj is Asn1Sequence asn1Sequence) - { - return new SignedPublicKeyAndChallenge(asn1Sequence); - } - - throw new ArgumentException(string.Format("object of unexpected type provided, type=[{0}]", obj.GetType().FullName)); + if (obj is SignedPublicKeyAndChallenge signedPublicKeyAndChallenge) + return signedPublicKeyAndChallenge; + return new SignedPublicKeyAndChallenge(Asn1Sequence.GetInstance(obj)); } - public SignedPublicKeyAndChallenge( - PublicKeyAndChallenge publicKeyAndChallenge, - AlgorithmIdentifier algorithmIdentifier, - DerBitString signature) + public static SignedPublicKeyAndChallenge GetInstance(Asn1TaggedObject taggedObject, bool declaredExplicit) { - this.publicKeyAndChallenge = publicKeyAndChallenge; - this.algorithmIdentifier = algorithmIdentifier; - this.signature = signature; + return new SignedPublicKeyAndChallenge(Asn1Sequence.GetInstance(taggedObject, declaredExplicit)); } - public SignedPublicKeyAndChallenge( - Asn1Sequence seq) - { - this.seq = seq; + private readonly PublicKeyAndChallenge m_publicKeyAndChallenge; + private readonly AlgorithmIdentifier m_signatureAlgorithm; + private readonly DerBitString m_signature; - if (seq.Count != 3) - throw new FormatException($"Sequence contains {seq.Count} elements. Expected 3 elements"); - - this.publicKeyAndChallenge = PublicKeyAndChallenge.GetInstance(seq[0]); - this.algorithmIdentifier = AlgorithmIdentifier.GetInstance(seq[1]); - this.signature = DerBitString.GetInstance(seq[2]); - } - - public bool Verify() + public SignedPublicKeyAndChallenge(PublicKeyAndChallenge publicKeyAndChallenge, + AlgorithmIdentifier signatureAlgorithm, DerBitString signature) { - AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(this.PublicKeyAndChallenge.SubjectPublicKeyInfo); - Asn1VerifierFactoryProvider factory = new Asn1VerifierFactoryProvider(publicKey); - IVerifierFactory verifier = factory.CreateVerifierFactory(this.AlgorithmIdentifier); - - try - { - byte[] derEncoded = this.PublicKeyAndChallenge.GetEncoded(); - IStreamCalculator streamCalculator = verifier.CreateCalculator(); - streamCalculator.Stream.Write(derEncoded, 0, derEncoded.Length); - streamCalculator.Stream.Dispose(); - - return streamCalculator.GetResult().IsVerified(this.Signature.GetOctets()); - } - catch (Exception exception) - { - throw new SignatureException("exception encoding SPKAC request", exception); - } + m_publicKeyAndChallenge = publicKeyAndChallenge + ?? throw new ArgumentNullException(nameof(publicKeyAndChallenge)); + m_signatureAlgorithm = signatureAlgorithm ?? throw new ArgumentNullException(nameof(signatureAlgorithm)); + m_signature = signature ?? throw new ArgumentNullException(nameof(signature)); } - public override Asn1Object ToAsn1Object() + private SignedPublicKeyAndChallenge(Asn1Sequence seq) { - if (null == this.seq) - { - Asn1EncodableVector v = new Asn1EncodableVector(); + if (seq == null) + throw new ArgumentNullException(nameof(seq)); + if (seq.Count != 3) + throw new ArgumentException($"Expected 3 elements, but found {seq.Count}", nameof(seq)); - if (null == this.PublicKeyAndChallenge) - throw new FormatException($"{nameof(this.PublicKeyAndChallenge)} can not be null"); + m_publicKeyAndChallenge = PublicKeyAndChallenge.GetInstance(seq[0]); + m_signatureAlgorithm = AlgorithmIdentifier.GetInstance(seq[1]); + m_signature = DerBitString.GetInstance(seq[2]); + } - if (null == this.AlgorithmIdentifier) - throw new FormatException($"{nameof(this.AlgorithmIdentifier)} can not be null"); + public PublicKeyAndChallenge PublicKeyAndChallenge => m_publicKeyAndChallenge; - if (null == this.Signature) - throw new FormatException($"{nameof(this.Signature)} can not be null"); + public DerBitString Signature => m_signature; - v.Add(this.PublicKeyAndChallenge); - v.Add(this.AlgorithmIdentifier); - v.Add(this.Signature); + public AlgorithmIdentifier SignatureAlgorithm => m_signatureAlgorithm; - this.seq = new DerSequence(v); - } - return this.seq; - } + public override Asn1Object ToAsn1Object() => + new DerSequence(m_publicKeyAndChallenge, m_signatureAlgorithm, m_signature); } } diff --git a/crypto/src/mozilla/SignedPublicKeyAndChallenge.cs b/crypto/src/mozilla/SignedPublicKeyAndChallenge.cs new file mode 100644 index 000000000..03b8e90ac --- /dev/null +++ b/crypto/src/mozilla/SignedPublicKeyAndChallenge.cs @@ -0,0 +1,48 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Mozilla +{ + public sealed class SignedPublicKeyAndChallenge + { + private readonly Asn1.Mozilla.SignedPublicKeyAndChallenge m_spkac; + + public SignedPublicKeyAndChallenge(byte[] encoding) + : this(Asn1.Mozilla.SignedPublicKeyAndChallenge.GetInstance(encoding)) + { + } + + public SignedPublicKeyAndChallenge(Asn1.Mozilla.SignedPublicKeyAndChallenge spkac) + { + m_spkac = spkac ?? throw new ArgumentNullException(nameof(spkac)); + } + + public AsymmetricKeyParameter GetPublicKey() => PublicKeyFactory.CreateKey(m_spkac.PublicKeyAndChallenge.Spki); + + public bool IsSignatureValid(AsymmetricKeyParameter publicKey) => + CheckSignatureValid(new Asn1VerifierFactory(m_spkac.SignatureAlgorithm, publicKey)); + + public bool IsSignatureValid(IVerifierFactoryProvider verifierProvider) => + CheckSignatureValid(verifierProvider.CreateVerifierFactory(m_spkac.SignatureAlgorithm)); + + public Asn1.Mozilla.SignedPublicKeyAndChallenge ToAsn1Structure() => m_spkac; + + public void Verify(AsymmetricKeyParameter publicKey) => + CheckSignature(new Asn1VerifierFactory(m_spkac.SignatureAlgorithm, publicKey)); + + public void Verify(IVerifierFactoryProvider verifierProvider) => + CheckSignature(verifierProvider.CreateVerifierFactory(m_spkac.SignatureAlgorithm)); + + private void CheckSignature(IVerifierFactory verifier) + { + if (!CheckSignatureValid(verifier)) + throw new InvalidKeyException("Public key presented not for SPKAC signature"); + } + + private bool CheckSignatureValid(IVerifierFactory verifier) => + X509.X509Utilities.VerifySignature(verifier, m_spkac.PublicKeyAndChallenge, m_spkac.Signature); + } +} diff --git a/crypto/test/src/mozilla/test/SpkacTest.cs b/crypto/test/src/mozilla/test/SpkacTest.cs new file mode 100644 index 000000000..3d7533865 --- /dev/null +++ b/crypto/test/src/mozilla/test/SpkacTest.cs @@ -0,0 +1,34 @@ +using NUnit.Framework; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Org.BouncyCastle.Mozilla.Tests +{ + [TestFixture] + public class SpkacTest + { + private static readonly byte[] Spkac = Base64.Decode( + "MIIBOjCBpDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApne7ti0ibPhV8Iht" + + "7Pws5iRckM7x4mtZYxEpeX5/IO8tDsBFdY86ewuY2f2KCca0oMWr43kdkZbPyzf4" + + "CSV+0fZm9MJyNMywygZjoOCC+rS8kr0Ef31iHChhYsyejJnjw116Jnn96syhdHY6" + + "lVD1rK0nn5ZkHjxU74gjoZu6BJMCAwEAARYAMA0GCSqGSIb3DQEBBAUAA4GBAKFL" + + "g/luv0C7gMTI8ZKfFoSyi7Q7kiSQcmSj1WJgT56ouIRJO5NdvB/1n4GNik8VOAU0" + + "NRztvGy3ZGqgbSav7lrxcNEvXH+dLbtS97s7yiaozpsOcEHqsBribpLOTRzYa8ci" + + "CwkPmIiYqcby11diKLpd+W9RFYNme2v0rrbM2CyV"); + + [Test] + public void TestSpkac() + { + var spkac = new SignedPublicKeyAndChallenge(Spkac); + + var reencoded = spkac.ToAsn1Structure().GetEncoded(Asn1Encodable.Der); + Assert.True(Arrays.AreEqual(Spkac, reencoded)); + + var publicKey = spkac.GetPublicKey(); + var shouldVerify = spkac.IsSignatureValid(publicKey); + Assert.True(shouldVerify); + } + } +} -- cgit 1.4.1